Skip to content

Commit 0fba17a

Browse files
committed
Check env file with samefile.
Samefile abstract away differences between Unixes and windows to check if two files lead to the same file on the underlying filesystem.
1 parent 6be994d commit 0fba17a

File tree

2 files changed

+97
-12
lines changed

2 files changed

+97
-12
lines changed

heed/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ once_cell = "1.16.0"
2121
page_size = "0.5.0"
2222
serde = { version = "1.0.151", features = ["derive"], optional = true }
2323
synchronoise = "1.0.1"
24+
same-file = "1.0.6"
2425

2526
[dev-dependencies]
2627
serde = { version = "1.0.151", features = ["derive"] }

heed/src/env.rs

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::{
1919
use std::{fmt, io, mem, ptr, sync};
2020

2121
use once_cell::sync::Lazy;
22+
use same_file::Handle;
2223
use synchronoise::event::SignalEvent;
2324

2425
use crate::mdb::error::mdb_result;
@@ -32,7 +33,10 @@ use crate::{
3233
/// noone tries to open the same environment between these two phases.
3334
///
3435
/// Trying to open a None marked environment returns an error to the user trying to open it.
35-
static OPENED_ENV: Lazy<RwLock<HashMap<PathBuf, EnvEntry>>> = Lazy::new(RwLock::default);
36+
///
37+
/// [samefile::Handle] abstract the platform details of checking if the given paths points to
38+
/// the same file on the filesystem.
39+
static OPENED_ENV: Lazy<RwLock<HashMap<Handle, EnvEntry>>> = Lazy::new(RwLock::default);
3640

3741
struct EnvEntry {
3842
env: Option<Env>,
@@ -180,22 +184,29 @@ impl EnvOpenOptions {
180184
pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<Env> {
181185
let mut lock = OPENED_ENV.write().unwrap();
182186

183-
let path = match canonicalize_path(path.as_ref()) {
187+
let (path, handle) = match canonicalize_path(path.as_ref()) {
184188
Err(err) => {
185189
if err.kind() == NotFound && self.flags & (Flag::NoSubDir as u32) != 0 {
186190
let path = path.as_ref();
187191
match path.parent().zip(path.file_name()) {
188-
Some((dir, file_name)) => canonicalize_path(dir)?.join(file_name),
192+
Some((dir, file_name)) => {
193+
let handle = Handle::from_path(dir)?;
194+
let path = canonicalize_path(dir)?.join(file_name);
195+
(path, handle)
196+
}
189197
None => return Err(err.into()),
190198
}
191199
} else {
192200
return Err(err.into());
193201
}
194202
}
195-
Ok(path) => path,
203+
Ok(path) => {
204+
let handle = Handle::from_path(&path)?;
205+
(path, handle)
206+
}
196207
};
197208

198-
match lock.entry(path) {
209+
match lock.entry(handle) {
199210
Entry::Occupied(entry) => {
200211
let env = entry.get().env.clone().ok_or(Error::DatabaseClosing)?;
201212
let options = entry.get().options.clone();
@@ -206,7 +217,6 @@ impl EnvOpenOptions {
206217
}
207218
}
208219
Entry::Vacant(entry) => {
209-
let path = entry.key();
210220
let path_str = CString::new(path.as_os_str().as_bytes()).unwrap();
211221

212222
unsafe {
@@ -255,6 +265,7 @@ impl EnvOpenOptions {
255265
env,
256266
dbi_open_mutex: sync::Mutex::default(),
257267
path: path.clone(),
268+
handle: Handle::from_path(path)?,
258269
};
259270
let env = Env(Arc::new(inner));
260271
let cache_entry = EnvEntry {
@@ -277,9 +288,11 @@ impl EnvOpenOptions {
277288
}
278289

279290
/// Returns a struct that allows to wait for the effective closing of an environment.
280-
pub fn env_closing_event<P: AsRef<Path>>(path: P) -> Option<EnvClosingEvent> {
291+
pub fn env_closing_event<P: AsRef<Path>>(path: P) -> Result<Option<EnvClosingEvent>> {
281292
let lock = OPENED_ENV.read().unwrap();
282-
lock.get(path.as_ref()).map(|e| EnvClosingEvent(e.signal_event.clone()))
293+
let handle = Handle::from_path(path)?;
294+
295+
Ok(lock.get(&handle).map(|e| EnvClosingEvent(e.signal_event.clone())))
283296
}
284297

285298
/// An environment handle constructed by using [`EnvOpenOptions`].
@@ -288,7 +301,7 @@ pub struct Env(Arc<EnvInner>);
288301

289302
impl fmt::Debug for Env {
290303
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291-
let EnvInner { env: _, dbi_open_mutex: _, path } = self.0.as_ref();
304+
let EnvInner { env: _, dbi_open_mutex: _, path, .. } = self.0.as_ref();
292305
f.debug_struct("Env").field("path", &path.display()).finish_non_exhaustive()
293306
}
294307
}
@@ -297,6 +310,7 @@ struct EnvInner {
297310
env: *mut ffi::MDB_env,
298311
dbi_open_mutex: sync::Mutex<HashMap<u32, Option<(TypeId, TypeId)>>>,
299312
path: PathBuf,
313+
handle: Handle,
300314
}
301315

302316
unsafe impl Send for EnvInner {}
@@ -307,7 +321,7 @@ impl Drop for EnvInner {
307321
fn drop(&mut self) {
308322
let mut lock = OPENED_ENV.write().unwrap();
309323

310-
match lock.remove(&self.path) {
324+
match lock.remove(&self.handle) {
311325
None => panic!("It seems another env closed this env before"),
312326
Some(EnvEntry { signal_event, .. }) => {
313327
unsafe {
@@ -653,7 +667,7 @@ impl Env {
653667
/// when all references are dropped, the last one will eventually close the environment.
654668
pub fn prepare_for_closing(self) -> EnvClosingEvent {
655669
let mut lock = OPENED_ENV.write().unwrap();
656-
match lock.get_mut(self.path()) {
670+
match lock.get_mut(&self.0.handle) {
657671
None => panic!("cannot find the env that we are trying to close"),
658672
Some(EnvEntry { env, signal_event, .. }) => {
659673
// We remove the env from the global list and replace it with a None.
@@ -797,7 +811,7 @@ mod tests {
797811
eprintln!("env closed successfully");
798812

799813
// Make sure we don't have a reference to the env
800-
assert!(env_closing_event(&dir.path()).is_none());
814+
assert!(env_closing_event(&dir.path()).unwrap().is_none());
801815
}
802816

803817
#[test]
@@ -830,6 +844,49 @@ mod tests {
830844
.unwrap();
831845
}
832846

847+
#[test]
848+
#[cfg(unix)]
849+
fn open_env_with_named_path_symlink() {
850+
let dir = tempfile::tempdir().unwrap();
851+
let dir_symlink = tempfile::tempdir().unwrap();
852+
853+
let env_name = dir.path().join("babar.mdb");
854+
let symlink_name = dir_symlink.path().join("babar.mdb.link");
855+
fs::create_dir_all(&env_name).unwrap();
856+
857+
std::os::unix::fs::symlink(&env_name, &symlink_name).unwrap();
858+
let _env = EnvOpenOptions::new()
859+
.map_size(10 * 1024 * 1024) // 10MB
860+
.open(&symlink_name)
861+
.unwrap();
862+
863+
let _env = EnvOpenOptions::new()
864+
.map_size(10 * 1024 * 1024) // 10MB
865+
.open(&symlink_name)
866+
.unwrap();
867+
}
868+
869+
#[test]
870+
fn open_env_with_named_path_rename() {
871+
let dir = tempfile::tempdir().unwrap();
872+
873+
let env_name = dir.path().join("babar.mdb");
874+
fs::create_dir_all(&env_name).unwrap();
875+
876+
let _env = EnvOpenOptions::new()
877+
.map_size(10 * 1024 * 1024) // 10MB
878+
.open(&env_name)
879+
.unwrap();
880+
881+
let env_renamed = dir.path().join("serafina.mdb");
882+
std::fs::rename(&env_name, &env_renamed).unwrap();
883+
884+
let _env = EnvOpenOptions::new()
885+
.map_size(10 * 1024 * 1024) // 10MB
886+
.open(&env_renamed)
887+
.unwrap();
888+
}
889+
833890
#[test]
834891
#[cfg(not(windows))]
835892
fn open_database_with_writemap_flag() {
@@ -853,6 +910,33 @@ mod tests {
853910
let _env = envbuilder.open(&dir.path().join("data.mdb")).unwrap();
854911
}
855912

913+
#[test]
914+
#[cfg(unix)]
915+
fn open_env_with_named_path_symlink_and_no_subdir() {
916+
let dir = tempfile::tempdir().unwrap();
917+
let dir_symlink = tempfile::tempdir().unwrap();
918+
let env_name = dir.path().join("babar.mdb");
919+
let symlink_name = dir_symlink.path().join("babar.mdb.link");
920+
921+
let mut envbuilder = EnvOpenOptions::new();
922+
unsafe { envbuilder.flag(crate::Flag::NoSubDir) };
923+
let _env = envbuilder
924+
.map_size(10 * 1024 * 1024) // 10MB
925+
.open(&env_name)
926+
.unwrap();
927+
928+
std::os::unix::fs::symlink(&dir.path(), &symlink_name).unwrap();
929+
let _env = envbuilder
930+
.map_size(10 * 1024 * 1024) // 10MB
931+
.open(&symlink_name)
932+
.unwrap();
933+
934+
let _env = envbuilder
935+
.map_size(10 * 1024 * 1024) // 10MB
936+
.open(&env_name)
937+
.unwrap();
938+
}
939+
856940
#[test]
857941
fn create_database_without_commit() {
858942
let dir = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)