Skip to content

Commit 993fe55

Browse files
committed
Check env file already open with samefile.
Samefile abstract away differences between Unixes and windows to check if two files lead to the same file on the underlying filesystem. Now it should follow through soft-links, hard-links and file moved.
1 parent 6be994d commit 993fe55

File tree

2 files changed

+172
-12
lines changed

2 files changed

+172
-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: 171 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,75 @@ mod tests {
830844
.unwrap();
831845
}
832846

847+
#[test]
848+
fn open_env_with_named_path_hardlink_and_no_subdir() {
849+
let dir = tempfile::tempdir().unwrap();
850+
let dir_symlink = tempfile::tempdir().unwrap();
851+
let env_name = dir.path().join("babar.mdb");
852+
let hardlink_name = dir_symlink.path().join("babar.mdb.link");
853+
854+
let mut envbuilder = EnvOpenOptions::new();
855+
unsafe { envbuilder.flag(crate::Flag::NoSubDir) };
856+
let _env = envbuilder
857+
.map_size(10 * 1024 * 1024) // 10MB
858+
.open(&env_name)
859+
.unwrap();
860+
861+
std::os::unix::fs::symlink(&dir.path(), &hardlink_name).unwrap();
862+
let _env = envbuilder
863+
.map_size(10 * 1024 * 1024) // 10MB
864+
.open(&hardlink_name)
865+
.unwrap();
866+
867+
let _env = envbuilder
868+
.map_size(10 * 1024 * 1024) // 10MB
869+
.open(&env_name)
870+
.unwrap();
871+
}
872+
873+
#[test]
874+
#[cfg(unix)]
875+
fn open_env_with_named_path_symlink() {
876+
let dir = tempfile::tempdir().unwrap();
877+
let dir_symlink = tempfile::tempdir().unwrap();
878+
879+
let env_name = dir.path().join("babar.mdb");
880+
let symlink_name = dir_symlink.path().join("babar.mdb.link");
881+
fs::create_dir_all(&env_name).unwrap();
882+
883+
std::os::unix::fs::symlink(&env_name, &symlink_name).unwrap();
884+
let _env = EnvOpenOptions::new()
885+
.map_size(10 * 1024 * 1024) // 10MB
886+
.open(&symlink_name)
887+
.unwrap();
888+
889+
let _env = EnvOpenOptions::new()
890+
.map_size(10 * 1024 * 1024) // 10MB
891+
.open(&env_name)
892+
.unwrap();
893+
}
894+
895+
#[test]
896+
fn open_env_with_named_path_rename() {
897+
let dir = tempfile::tempdir().unwrap();
898+
899+
let env_name = dir.path().join("babar.mdb");
900+
fs::create_dir_all(&env_name).unwrap();
901+
902+
let _env = EnvOpenOptions::new()
903+
.map_size(10 * 1024 * 1024) // 10MB
904+
.open(&env_name)
905+
.unwrap();
906+
907+
let env_renamed = dir.path().join("serafina.mdb");
908+
std::fs::rename(&env_name, &env_renamed).unwrap();
909+
910+
let _env = EnvOpenOptions::new()
911+
.map_size(10 * 1024 * 1024) // 10MB
912+
.open(&env_renamed)
913+
.unwrap();
914+
}
915+
833916
#[test]
834917
#[cfg(not(windows))]
835918
fn open_database_with_writemap_flag() {
@@ -853,6 +936,82 @@ mod tests {
853936
let _env = envbuilder.open(&dir.path().join("data.mdb")).unwrap();
854937
}
855938

939+
#[test]
940+
#[cfg(unix)]
941+
fn open_env_with_named_path_symlink_and_no_subdir() {
942+
let dir = tempfile::tempdir().unwrap();
943+
let dir_symlink = tempfile::tempdir().unwrap();
944+
let env_name = dir.path().join("babar.mdb");
945+
let symlink_name = dir_symlink.path().join("babar.mdb.link");
946+
947+
let mut envbuilder = EnvOpenOptions::new();
948+
unsafe { envbuilder.flag(crate::Flag::NoSubDir) };
949+
let _env = envbuilder
950+
.map_size(10 * 1024 * 1024) // 10MB
951+
.open(&env_name)
952+
.unwrap();
953+
954+
std::os::unix::fs::symlink(&dir.path(), &symlink_name).unwrap();
955+
let _env = envbuilder
956+
.map_size(10 * 1024 * 1024) // 10MB
957+
.open(&symlink_name)
958+
.unwrap();
959+
960+
let _env = envbuilder
961+
.map_size(10 * 1024 * 1024) // 10MB
962+
.open(&env_name)
963+
.unwrap();
964+
}
965+
966+
#[test]
967+
#[cfg(windows)]
968+
fn open_env_with_named_path_symlinkfile_and_no_subdir() {
969+
let dir = tempfile::tempdir().unwrap();
970+
let dir_symlink = tempfile::tempdir().unwrap();
971+
let env_name = dir.path().join("babar.mdb");
972+
let symlink_name = dir_symlink.path().join("babar.mdb.link");
973+
974+
let mut envbuilder = EnvOpenOptions::new();
975+
unsafe { envbuilder.flag(crate::Flag::NoSubDir) };
976+
let _env = envbuilder
977+
.map_size(10 * 1024 * 1024) // 10MB
978+
.open(&env_name)
979+
.unwrap();
980+
981+
std::os::windows::fs::symlink_file(&dir.path(), &symlink_name).unwrap();
982+
let _env = envbuilder
983+
.map_size(10 * 1024 * 1024) // 10MB
984+
.open(&symlink_name)
985+
.unwrap();
986+
987+
let _env = envbuilder
988+
.map_size(10 * 1024 * 1024) // 10MB
989+
.open(&env_name)
990+
.unwrap();
991+
}
992+
993+
#[test]
994+
#[cfg(windows)]
995+
fn open_env_with_named_path_symlink() {
996+
let dir = tempfile::tempdir().unwrap();
997+
let dir_symlink = tempfile::tempdir().unwrap();
998+
999+
let env_name = dir.path().join("babar.mdb");
1000+
let symlink_name = dir_symlink.path().join("babar.mdb.link");
1001+
fs::create_dir_all(&env_name).unwrap();
1002+
1003+
std::os::windows::fs::symlink_dir(&env_name, &symlink_name).unwrap();
1004+
let _env = EnvOpenOptions::new()
1005+
.map_size(10 * 1024 * 1024) // 10MB
1006+
.open(&symlink_name)
1007+
.unwrap();
1008+
1009+
let _env = EnvOpenOptions::new()
1010+
.map_size(10 * 1024 * 1024) // 10MB
1011+
.open(&env_name)
1012+
.unwrap();
1013+
}
1014+
8561015
#[test]
8571016
fn create_database_without_commit() {
8581017
let dir = tempfile::tempdir().unwrap();

0 commit comments

Comments
 (0)