Skip to content

Commit dd685cc

Browse files
fix(VFS): prevent Path::create_dir_all() failures to when executing in parallel (fixes #47)
1 parent 0a14d16 commit dd685cc

File tree

5 files changed

+86
-13
lines changed

5 files changed

+86
-13
lines changed

src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ pub enum VfsErrorKind {
115115
/// Generic error variant
116116
Other(String),
117117

118+
/// There is already a directory at the given path
119+
DirectoryExists,
120+
121+
/// There is already a file at the given path
122+
FileExists,
123+
118124
/// Functionality not supported by this filesystem
119125
NotSupported,
120126
}
@@ -137,6 +143,12 @@ impl fmt::Display for VfsErrorKind {
137143
VfsErrorKind::NotSupported => {
138144
write!(f, "Functionality not supported by this filesystem")
139145
}
146+
VfsErrorKind::DirectoryExists => {
147+
write!(f, "Directory already exists")
148+
}
149+
VfsErrorKind::FileExists => {
150+
write!(f, "File already exists")
151+
}
140152
}
141153
}
142154
}

src/impls/memory.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::VfsResult;
55
use crate::{FileSystem, VfsFileType};
66
use crate::{SeekAndRead, VfsMetadata};
77
use core::cmp;
8+
use std::collections::hash_map::Entry;
89
use std::collections::HashMap;
910
use std::fmt;
1011
use std::fmt::{Debug, Formatter};
@@ -149,13 +150,25 @@ impl FileSystem for MemoryFS {
149150

150151
fn create_dir(&self, path: &str) -> VfsResult<()> {
151152
self.ensure_has_parent(path)?;
152-
self.handle.write().unwrap().files.insert(
153-
path.to_string(),
154-
MemoryFile {
155-
file_type: VfsFileType::Directory,
156-
content: Default::default(),
157-
},
158-
);
153+
let map = &mut self.handle.write().unwrap().files;
154+
let entry = map.entry(path.to_string());
155+
match entry {
156+
Entry::Occupied(file) => {
157+
return match file.get().file_type {
158+
VfsFileType::File => Err(VfsErrorKind::FileExists.into()),
159+
VfsFileType::Directory => Err(VfsErrorKind::DirectoryExists.into()),
160+
}
161+
}
162+
Entry::Vacant(_) => {
163+
map.insert(
164+
path.to_string(),
165+
MemoryFile {
166+
file_type: VfsFileType::Directory,
167+
content: Default::default(),
168+
},
169+
);
170+
}
171+
}
159172
Ok(())
160173
}
161174

src/impls/physical.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! A "physical" file system implementation using the underlying OS file system
22
33
use crate::error::VfsErrorKind;
4-
use crate::VfsResult;
54
use crate::{FileSystem, VfsMetadata};
65
use crate::{SeekAndRead, VfsFileType};
6+
use crate::{VfsError, VfsResult};
77
use std::fs::{File, OpenOptions};
8-
use std::io::Write;
8+
use std::io::{ErrorKind, Write};
99
use std::path::{Path, PathBuf};
1010

1111
/// A physical filesystem implementation using the underlying OS file system
@@ -41,7 +41,17 @@ impl FileSystem for PhysicalFS {
4141
}
4242

4343
fn create_dir(&self, path: &str) -> VfsResult<()> {
44-
std::fs::create_dir(self.get_path(path))?;
44+
let fs_path = self.get_path(path);
45+
std::fs::create_dir(&fs_path).map_err(|err| match err.kind() {
46+
ErrorKind::AlreadyExists => {
47+
let metadata = std::fs::metadata(&fs_path).unwrap();
48+
if metadata.is_dir() {
49+
return VfsError::from(VfsErrorKind::DirectoryExists);
50+
}
51+
VfsError::from(VfsErrorKind::FileExists)
52+
}
53+
_ => err.into(),
54+
})?;
4555
Ok(())
4656
}
4757

src/path.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,10 @@ impl VfsPath {
174174

175175
/// Creates the directory at this path
176176
///
177-
/// Note that the parent directory must exist.
177+
/// Note that the parent directory must exist, while the given path must not exist.
178+
///
179+
/// Returns VfsErrorKind::FileExists if a file already exists at the given path
180+
/// Returns VfsErrorKind::DirectoryExists if a directory already exists at the given path
178181
///
179182
/// ```
180183
/// # use vfs::{MemoryFS, VfsError, VfsFileType, VfsPath};
@@ -225,8 +228,15 @@ impl VfsPath {
225228
.map(|it| it + pos)
226229
.unwrap_or_else(|| path.len());
227230
let directory = &path[..end];
228-
if !self.fs.fs.exists(directory)? {
229-
self.fs.fs.create_dir(directory)?;
231+
if let Err(error) = self.fs.fs.create_dir(directory) {
232+
match error.kind() {
233+
VfsErrorKind::DirectoryExists => {}
234+
_ => {
235+
return Err(error.with_path(directory).with_context(|| {
236+
format!("Could not create directories at '{}'", path)
237+
}))
238+
}
239+
}
230240
}
231241
if end == path.len() {
232242
break;

src/test_macros.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,34 @@ macro_rules! test_vfs {
109109
let metadata = path.metadata().unwrap();
110110
assert_eq!(metadata.file_type, VfsFileType::Directory);
111111
assert_eq!(metadata.len, 0);
112+
path.create_dir_all().unwrap();
113+
root.create_dir_all().unwrap();
114+
Ok(())
115+
}
116+
117+
#[test]
118+
fn create_dir_all_should_fail_for_existing_file() -> VfsResult<()>{
119+
let root = create_root();
120+
let _string = String::new();
121+
let path = root.join("foo").unwrap();
122+
let path2 = root.join("foo/bar").unwrap();
123+
path.create_file().unwrap();
124+
let result = path2.create_dir_all();
125+
match result {
126+
Ok(_) => {panic!("Expected error");}
127+
Err(err) => {
128+
let error_message = format!("{}", err);
129+
if let VfsErrorKind::FileExists = err.kind() {
130+
131+
} else {
132+
panic!("Expected file exists error")
133+
}
134+
assert!(
135+
error_message.eq("Could not create directories at '/foo/bar' for '/foo': File already exists"),
136+
"Actual message: {}",
137+
error_message);
138+
}
139+
}
112140
Ok(())
113141
}
114142

0 commit comments

Comments
 (0)