Skip to content

Commit d046874

Browse files
authored
Merge pull request #22 from ijackson/remove-dir-contents
Provide remove_dir_contents
2 parents 0b8a207 + 69369e1 commit d046874

File tree

6 files changed

+161
-4
lines changed

6 files changed

+161
-4
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ num_cpus = "1.13"
2323
rayon = "1.4"
2424
winapi = {version = "0.3", features = ["std", "errhandlingapi", "winerror", "fileapi", "winbase"]}
2525

26+
[target.'cfg(not(windows))'.dependencies]
27+
libc = "0.2"
28+
2629
[dev-dependencies]
2730
doc-comment = "0.3"
2831
env_logger = "0.8.1"

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66

77
## Description
88

9-
A reliable implementation of `remove_dir_all` for Windows. For Unix systems
10-
re-exports `std::fs::remove_dir_all`.
9+
Reliable and fast directory removal functions.
10+
11+
* `remove_dir_all` - on non-Windows this is a re-export of
12+
`std::fs::remove_dir_all`. For Windows an implementation that handles the
13+
locking of directories that occurs when deleting directory trees rapidly.
14+
15+
* `remove_dir_contents` - as for `remove_dir_all` but does not delete the
16+
supplied root directory.
17+
18+
* `ensure_empty_dir` - as for `remove_dir_contents` but will create the
19+
directory if it does not exist.
1120

1221
```rust,no_run
1322
extern crate remove_dir_all;
@@ -16,6 +25,7 @@ use remove_dir_all::*;
1625
1726
fn main() {
1827
remove_dir_all("./temp/").unwrap();
28+
remove_dir_contents("./cache/").unwrap();
1929
}
2030
```
2131

src/fs.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
6060
// To handle files with names like `CON` and `morse .. .`, and when a
6161
// directory structure is so deep it needs long path names the path is first
6262
// converted to the Win32 file namespace by calling `canonicalize()`.
63-
let path = path.as_ref().canonicalize()?;
64-
_delete_dir_contents(&path)?;
63+
64+
let path = _remove_dir_contents(path)?;
6565
let metadata = path.metadata()?;
6666
if metadata.permissions().readonly() {
6767
delete_readonly(metadata, &path)?;
@@ -76,6 +76,13 @@ pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
7676
Ok(())
7777
}
7878

79+
/// Returns the canonicalised path, for one of caller's convenience.
80+
pub fn _remove_dir_contents<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
81+
let path = path.as_ref().canonicalize()?;
82+
_delete_dir_contents(&path)?;
83+
Ok(path)
84+
}
85+
7986
fn _delete_dir_contents(path: &PathBuf) -> io::Result<()> {
8087
log::trace!("scanning {}", &path.display());
8188
let iter = path.read_dir()?.par_bridge();

src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
//!
33
//! This library provides a reliable implementation of `remove_dir_all` for Windows.
44
//! For Unix systems, it re-exports `std::fs::remove_dir_all`.
5+
//!
6+
//! It also provides `remove_dir_contents` and `ensure_empty_dir`
7+
//! for both Unix and Windows.
58
69
#![deny(missing_debug_implementations)]
710
#![deny(missing_docs)]
@@ -19,8 +22,16 @@ doctest!("../README.md");
1922
#[cfg(windows)]
2023
mod fs;
2124

25+
#[cfg(not(windows))]
26+
mod unix;
27+
28+
mod portable;
29+
2230
#[cfg(windows)]
2331
pub use self::fs::remove_dir_all;
2432

2533
#[cfg(not(windows))]
2634
pub use std::fs::remove_dir_all;
35+
36+
pub use portable::ensure_empty_dir;
37+
pub use portable::remove_dir_contents;

src/portable.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use std::io;
2+
use std::path::Path;
3+
4+
#[cfg(windows)]
5+
use crate::fs::_remove_dir_contents;
6+
7+
#[cfg(not(windows))]
8+
use crate::unix::_remove_dir_contents;
9+
10+
/// Deletes the contents of `path`, but not the directory iteself.
11+
///
12+
/// If `path` is a symlink to a directory, deletes the contents
13+
/// of that directory. Fails if `path` does not exist.
14+
pub fn remove_dir_contents<P: AsRef<Path>>(path: P) -> io::Result<()> {
15+
// This wrapper function exists because the core function
16+
// for Windows, in crate::fs, returns a PathBuf, which our
17+
// caller shouldn't see.
18+
_remove_dir_contents(path)?;
19+
Ok(())
20+
}
21+
22+
/// Makes `path` an empty directory: if it does not exist, it is
23+
/// created it as an empty directory (as if with
24+
/// `std::fs::create_dir`); if it does exist, its contents are
25+
/// deleted (as if with `remove_dir_contents`).
26+
///
27+
/// It is an error if `path` exists but is not a directory (or
28+
/// a symlink to one).
29+
pub fn ensure_empty_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
30+
match std::fs::create_dir(&path) {
31+
Err(e) if e.kind() == io::ErrorKind::AlreadyExists
32+
=> remove_dir_contents(path),
33+
otherwise => otherwise,
34+
}
35+
}
36+
37+
#[cfg(test)]
38+
mod test {
39+
use tempfile::TempDir;
40+
use crate::remove_dir_all;
41+
use crate::remove_dir_contents;
42+
use crate::ensure_empty_dir;
43+
use std::fs::{self, File};
44+
use std::path::PathBuf;
45+
use std::io;
46+
47+
fn expect_failure<T>(k: io::ErrorKind, r: io::Result<T>) -> io::Result<()> {
48+
match r {
49+
Err(e) if e.kind() == k => Ok(()),
50+
Err(e) => Err(e),
51+
Ok(_) => Err(io::Error::new(
52+
io::ErrorKind::Other,
53+
"unexpected success".to_string(),
54+
)),
55+
}
56+
}
57+
58+
struct Prep {
59+
_tmp: TempDir,
60+
ours: PathBuf,
61+
file: PathBuf,
62+
}
63+
64+
fn prep() -> Result<Prep, io::Error> {
65+
let tmp = TempDir::new()?;
66+
let ours = tmp.path().join("t.mkdir");
67+
let file = ours.join("file");
68+
fs::create_dir(&ours)?;
69+
File::create(&file)?;
70+
File::open(&file)?;
71+
Ok(Prep { _tmp: tmp, ours, file })
72+
}
73+
74+
#[test]
75+
fn mkdir_rm() -> Result<(), io::Error> {
76+
let p = prep()?;
77+
78+
expect_failure(io::ErrorKind::Other, remove_dir_contents(&p.file))?;
79+
80+
remove_dir_contents(&p.ours)?;
81+
expect_failure(io::ErrorKind::NotFound, File::open(&p.file))?;
82+
83+
remove_dir_contents(&p.ours)?;
84+
remove_dir_all(&p.ours)?;
85+
expect_failure(io::ErrorKind::NotFound, remove_dir_contents(&p.ours))?;
86+
Ok(())
87+
}
88+
89+
#[test]
90+
fn ensure_rm() -> Result<(), io::Error> {
91+
let p = prep()?;
92+
93+
expect_failure(io::ErrorKind::Other, ensure_empty_dir(&p.file))?;
94+
95+
ensure_empty_dir(&p.ours)?;
96+
expect_failure(io::ErrorKind::NotFound, File::open(&p.file))?;
97+
ensure_empty_dir(&p.ours)?;
98+
99+
remove_dir_all(&p.ours)?;
100+
ensure_empty_dir(&p.ours)?;
101+
File::create(&p.file)?;
102+
103+
Ok(())
104+
}
105+
}

src/unix.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::fs;
2+
use std::io;
3+
use std::path::Path;
4+
5+
fn remove_file_or_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
6+
match fs::remove_file(&path) {
7+
// Unfortunately, there is no ErrorKind for EISDIR
8+
Err(e) if e.raw_os_error() == Some(libc::EISDIR) =>
9+
fs::remove_dir_all(&path),
10+
r => r,
11+
}
12+
}
13+
14+
pub fn _remove_dir_contents<P: AsRef<Path>>(path: P) -> Result<(), io::Error> {
15+
for entry in fs::read_dir(path)? {
16+
let entry_path = entry?.path();
17+
remove_file_or_dir_all(&entry_path)?;
18+
}
19+
20+
Ok(())
21+
}

0 commit comments

Comments
 (0)