Snap for 7908497 from a87ae1a97c7f35d2ef25ccb15e0044aaa66d8a32 to tm-d1-release Change-Id: I3d6b7eca9aa62285b7319ccd4f003148b86a2eea
diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..c715d69 --- /dev/null +++ b/Android.bp
@@ -0,0 +1,59 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +package { + default_applicable_licenses: [ + "external_rust_crates_remove_dir_all_license", + ], +} + +// Added automatically by a large-scale-change that took the approach of +// 'apply every license found to every target'. While this makes sure we respect +// every license restriction, it may not be entirely correct. +// +// e.g. GPL in an MIT project might only apply to the contrib/ directory. +// +// Please consider splitting the single license below into multiple licenses, +// taking care not to lose any license_kind information, and overriding the +// default license using the 'licenses: [...]' property on targets as needed. +// +// For unused files, consider creating a 'fileGroup' with "//visibility:private" +// to attach the license to, and including a comment whether the files may be +// used in the current project. +// +// large-scale-change included anything that looked like it might be a license +// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc. +// +// Please consider removing redundant or irrelevant files from 'license_text:'. +// See: http://go/android-license-faq +license { + name: "external_rust_crates_remove_dir_all_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-MIT", + ], + license_text: [ + "LICENCE-APACHE", + "LICENCE-MIT", + ], +} + +rust_library { + name: "libremove_dir_all", + host_supported: true, + crate_name: "remove_dir_all", + cargo_env_compat: true, + cargo_pkg_version: "0.7.1-alpha.0", + srcs: ["src/lib.rs"], + edition: "2018", + rustlibs: [ + "liblibc", + ], + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], +}
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c0350b5 --- /dev/null +++ b/CHANGELOG.md
@@ -0,0 +1,11 @@ +# 0.5.2 + +- Added support for `aarch64-pc-windows-msvc`. + +# 0.5.1 + +- Fixed deletion of readonly items. + +# 0.5.0 + +- Upgraded to winapi 0.3.
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3e11e09 --- /dev/null +++ b/Cargo.toml
@@ -0,0 +1,32 @@ +[package] +authors = ["Erin P. <[email protected]>", "Robert C. <[email protected]>"] +categories = ["filesystem"] +description = "A safe, reliable implementation of remove_dir_all for Windows" +edition = "2018" +include = [ + "Cargo.toml", + "LICENCE-APACHE", + "LICENCE-MIT", + "src/**/*", + "README.md", +] +keywords = ["utility", "filesystem", "remove_dir", "windows"] +license = "MIT/Apache-2.0" +name = "remove_dir_all" +readme = "README.md" +repository = "https://github.com/XAMPPRocky/remove_dir_all.git" +version = "0.7.1-alpha.0" + +[target.'cfg(windows)'.dependencies] +log = "0.4.11" +num_cpus = "1.13" +rayon = "1.4" +winapi = {version = "0.3", features = ["std", "errhandlingapi", "winerror", "fileapi", "winbase"]} + +[target.'cfg(not(windows))'.dependencies] +libc = "0.2" + +[dev-dependencies] +doc-comment = "0.3" +env_logger = "0.8.1" +tempfile = "3.1"
diff --git a/LICENCE-APACHE b/LICENCE-APACHE new file mode 100644 index 0000000..325f80b --- /dev/null +++ b/LICENCE-APACHE
@@ -0,0 +1,191 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +Copyright 2017 Aaron Power + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +
diff --git a/LICENCE-MIT b/LICENCE-MIT new file mode 100644 index 0000000..3c50c82 --- /dev/null +++ b/LICENCE-MIT
@@ -0,0 +1,26 @@ +Copyright (c) 2017 Aaron Power + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +
diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..e405cd5 --- /dev/null +++ b/LICENSE
@@ -0,0 +1 @@ +LICENCE-APACHE \ No newline at end of file
diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..9b441ce --- /dev/null +++ b/METADATA
@@ -0,0 +1,17 @@ +name: "remove_dir_all" +description: + "Reliable and fast directory removal functions." + +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/remove_dir_all" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/tempfile/remove_dir_all-0.7.0.crate" + } + version: "0.7.0" + last_upgrade_date { year: 2021 month: 10 day: 25 } + license_type: NOTICE +}
diff --git a/MODULE_LICENSE_APACHE b/MODULE_LICENSE_APACHE new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE
diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..46fc303 --- /dev/null +++ b/OWNERS
@@ -0,0 +1 @@ +include platform/prebuilts/rust:/OWNERS
diff --git a/README.md b/README.md new file mode 100644 index 0000000..825b179 --- /dev/null +++ b/README.md
@@ -0,0 +1,33 @@ +# remove_dir_all + +[](https://crates.io/crates/remove_dir_all) +[](https://docs.rs/remove_dir_all) +[](https://github.com/XAMPPRocky/remove_dir_all) + +## Description + +Reliable and fast directory removal functions. + +* `remove_dir_all` - on non-Windows this is a re-export of + `std::fs::remove_dir_all`. For Windows an implementation that handles the + locking of directories that occurs when deleting directory trees rapidly. + +* `remove_dir_contents` - as for `remove_dir_all` but does not delete the + supplied root directory. + +* `ensure_empty_dir` - as for `remove_dir_contents` but will create the + directory if it does not exist. + +```rust,no_run +extern crate remove_dir_all; + +use remove_dir_all::*; + +fn main() { + remove_dir_all("./temp/").unwrap(); + remove_dir_contents("./cache/").unwrap(); +} +``` + +## Minimum Rust Version +The minimum rust version for `remove_dir_all` is the latest stable release, and the minimum version may be bumped through patch releases. You can pin to a specific version by setting by add `=` to your version (e.g. `=0.6.0`), or commiting a `Cargo.lock` file to your project.
diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..5c18d15 --- /dev/null +++ b/cargo2android.json
@@ -0,0 +1,9 @@ +{ + "apex-available": [ + "//apex_available:platform", + "com.android.virt" + ], + "dependencies": true, + "device": true, + "run": true +}
diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..7c4e4a4 --- /dev/null +++ b/src/fs.rs
@@ -0,0 +1,152 @@ +use std::{ + fs::{self, OpenOptions}, + io, + mem::size_of, + os::windows::prelude::*, + path::{Path, PathBuf}, +}; + +use rayon::prelude::*; +use winapi::shared::minwindef::*; +use winapi::um::fileapi::*; +use winapi::um::minwinbase::*; +use winapi::um::winbase::*; +use winapi::um::winnt::*; + +/// Reliably removes a directory and all of its children. +/// +/// ```rust +/// use std::fs; +/// use remove_dir_all::*; +/// +/// fs::create_dir("./temp/").unwrap(); +/// remove_dir_all("./temp/").unwrap(); +/// ``` +pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> { + // On Windows it is not enough to just recursively remove the contents of a + // directory and then the directory itself. Deleting does not happen + // instantaneously, but is delayed by IO being completed in the fs stack. + // + // Further, typical Windows machines can handle many more concurrent IOs + // than a single threaded application is capable of submitting: the + // overlapped (async) calls available do not cover the operations needed to + // perform directory removal. + // + // To work around this, we use a work stealing scheduler and submit + // deletions concurrently with directory scanning, and delete sibling + // directories in parallel. This allows the slight latency of + // STATUS_DELETE_PENDING to only have logarithmic effect: a very deep tree + // will pay wall clock time for that overhead per level as the tree traverse + // completes, but not for every interior not as a simple recursive deletion + // would result in. + // + // Earlier versions of this crate moved the contents of the directory being + // deleted to become siblings of `base_dir`, which required write access to + // the parent directory under all circumstances; this is no longer required + // - though it may be re-instated if in-use files turn out to be handled + // very poorly with this new threaded implementation. + // + // There is a single small race condition where external side effects may be + // left: when deleting a hard linked readonly file, the syscalls required + // are: + // - open + // - set rw + // - unlink (SetFileDispositionDelete) + // - set ro + // + // A crash or power failure could lead to the loss of the readonly bit on + // the hardlinked inode. + // + // To handle files with names like `CON` and `morse .. .`, and when a + // directory structure is so deep it needs long path names the path is first + // converted to the Win32 file namespace by calling `canonicalize()`. + + let path = _remove_dir_contents(path)?; + let metadata = path.metadata()?; + if metadata.permissions().readonly() { + delete_readonly(metadata, &path)?; + } else { + log::trace!("removing {}", &path.display()); + fs::remove_dir(&path).map_err(|e| { + log::debug!("error removing {}", &path.display()); + e + })?; + log::trace!("removed {}", &path.display()); + } + Ok(()) +} + +/// Returns the canonicalised path, for one of caller's convenience. +pub fn _remove_dir_contents<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> { + let path = path.as_ref().canonicalize()?; + _delete_dir_contents(&path)?; + Ok(path) +} + +fn _delete_dir_contents(path: &PathBuf) -> io::Result<()> { + log::trace!("scanning {}", &path.display()); + let iter = path.read_dir()?.par_bridge(); + iter.try_for_each(|dir_entry| -> io::Result<()> { + let dir_entry = dir_entry?; + let metadata = dir_entry.metadata()?; + let is_dir = dir_entry.file_type()?.is_dir(); + let dir_path = dir_entry.path(); + if is_dir { + _delete_dir_contents(&dir_path)?; + } + log::trace!("removing {}", &dir_path.display()); + if metadata.permissions().readonly() { + delete_readonly(metadata, &dir_path).map_err(|e| { + log::debug!("error removing {}", &dir_path.display()); + e + })?; + } else if is_dir { + fs::remove_dir(&dir_path).map_err(|e| { + log::debug!("error removing {}", &dir_path.display()); + e + })?; + } else { + fs::remove_file(&dir_path).map_err(|e| { + log::debug!("error removing {}", &dir_path.display()); + e + })?; + } + log::trace!("removed {}", &dir_path.display()); + Ok(()) + })?; + log::trace!("scanned {}", &path.display()); + Ok(()) +} + +// Delete a file or directory that is readonly +fn delete_readonly(metadata: fs::Metadata, path: &Path) -> io::Result<()> { + // Open, drop the readonly bit, set delete-on-close, close. + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); + + let file = opts.open(path)?; + let mut perms = metadata.permissions(); + perms.set_readonly(false); + file.set_permissions(perms)?; + + let mut info = FILE_DISPOSITION_INFO { + DeleteFile: TRUE as u8, + }; + let result = unsafe { + SetFileInformationByHandle( + file.as_raw_handle(), + FileDispositionInfo, + &mut info as *mut FILE_DISPOSITION_INFO as LPVOID, + size_of::<FILE_DISPOSITION_INFO>() as u32, + ) + }; + + if result == 0 { + return Err(io::Error::last_os_error()); + } + + file.set_permissions(metadata.permissions())?; + drop(file); + Ok(()) +}
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..eedbe41 --- /dev/null +++ b/src/lib.rs
@@ -0,0 +1,37 @@ +//! Reliably remove a directory and all of its children. +//! +//! This library provides a reliable implementation of `remove_dir_all` for Windows. +//! For Unix systems, it re-exports `std::fs::remove_dir_all`. +//! +//! It also provides `remove_dir_contents` and `ensure_empty_dir` +//! for both Unix and Windows. + +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![deny(rust_2018_idioms)] +// See under "known problems" https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic +#![allow(clippy::mutex_atomic)] + +#[cfg(doctest)] +#[macro_use] +extern crate doc_comment; + +#[cfg(doctest)] +doctest!("../README.md"); + +#[cfg(windows)] +mod fs; + +#[cfg(not(windows))] +mod unix; + +mod portable; + +#[cfg(windows)] +pub use self::fs::remove_dir_all; + +#[cfg(not(windows))] +pub use std::fs::remove_dir_all; + +pub use portable::ensure_empty_dir; +pub use portable::remove_dir_contents;
diff --git a/src/portable.rs b/src/portable.rs new file mode 100644 index 0000000..11f4deb --- /dev/null +++ b/src/portable.rs
@@ -0,0 +1,105 @@ +use std::io; +use std::path::Path; + +#[cfg(windows)] +use crate::fs::_remove_dir_contents; + +#[cfg(not(windows))] +use crate::unix::_remove_dir_contents; + +/// Deletes the contents of `path`, but not the directory iteself. +/// +/// If `path` is a symlink to a directory, deletes the contents +/// of that directory. Fails if `path` does not exist. +pub fn remove_dir_contents<P: AsRef<Path>>(path: P) -> io::Result<()> { + // This wrapper function exists because the core function + // for Windows, in crate::fs, returns a PathBuf, which our + // caller shouldn't see. + _remove_dir_contents(path)?; + Ok(()) +} + +/// Makes `path` an empty directory: if it does not exist, it is +/// created it as an empty directory (as if with +/// `std::fs::create_dir`); if it does exist, its contents are +/// deleted (as if with `remove_dir_contents`). +/// +/// It is an error if `path` exists but is not a directory (or +/// a symlink to one). +pub fn ensure_empty_dir<P: AsRef<Path>>(path: P) -> io::Result<()> { + match std::fs::create_dir(&path) { + Err(e) if e.kind() == io::ErrorKind::AlreadyExists + => remove_dir_contents(path), + otherwise => otherwise, + } +} + +#[cfg(test)] +mod test { + use tempfile::TempDir; + use crate::remove_dir_all; + use crate::remove_dir_contents; + use crate::ensure_empty_dir; + use std::fs::{self, File}; + use std::path::PathBuf; + use std::io; + + fn expect_failure<T>(k: io::ErrorKind, r: io::Result<T>) -> io::Result<()> { + match r { + Err(e) if e.kind() == k => Ok(()), + Err(e) => Err(e), + Ok(_) => Err(io::Error::new( + io::ErrorKind::Other, + "unexpected success".to_string(), + )), + } + } + + struct Prep { + _tmp: TempDir, + ours: PathBuf, + file: PathBuf, + } + + fn prep() -> Result<Prep, io::Error> { + let tmp = TempDir::new()?; + let ours = tmp.path().join("t.mkdir"); + let file = ours.join("file"); + fs::create_dir(&ours)?; + File::create(&file)?; + File::open(&file)?; + Ok(Prep { _tmp: tmp, ours, file }) + } + + #[test] + fn mkdir_rm() -> Result<(), io::Error> { + let p = prep()?; + + expect_failure(io::ErrorKind::Other, remove_dir_contents(&p.file))?; + + remove_dir_contents(&p.ours)?; + expect_failure(io::ErrorKind::NotFound, File::open(&p.file))?; + + remove_dir_contents(&p.ours)?; + remove_dir_all(&p.ours)?; + expect_failure(io::ErrorKind::NotFound, remove_dir_contents(&p.ours))?; + Ok(()) + } + + #[test] + fn ensure_rm() -> Result<(), io::Error> { + let p = prep()?; + + expect_failure(io::ErrorKind::Other, ensure_empty_dir(&p.file))?; + + ensure_empty_dir(&p.ours)?; + expect_failure(io::ErrorKind::NotFound, File::open(&p.file))?; + ensure_empty_dir(&p.ours)?; + + remove_dir_all(&p.ours)?; + ensure_empty_dir(&p.ours)?; + File::create(&p.file)?; + + Ok(()) + } +}
diff --git a/src/unix.rs b/src/unix.rs new file mode 100644 index 0000000..a60fde7 --- /dev/null +++ b/src/unix.rs
@@ -0,0 +1,21 @@ +use std::fs; +use std::io; +use std::path::Path; + +fn remove_file_or_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> { + match fs::remove_file(&path) { + // Unfortunately, there is no ErrorKind for EISDIR + Err(e) if e.raw_os_error() == Some(libc::EISDIR) => + fs::remove_dir_all(&path), + r => r, + } +} + +pub fn _remove_dir_contents<P: AsRef<Path>>(path: P) -> Result<(), io::Error> { + for entry in fs::read_dir(path)? { + let entry_path = entry?.path(); + remove_file_or_dir_all(&entry_path)?; + } + + Ok(()) +}
diff --git a/tests/windows.rs b/tests/windows.rs new file mode 100644 index 0000000..264ff0e --- /dev/null +++ b/tests/windows.rs
@@ -0,0 +1,100 @@ +#![cfg(windows)] +use remove_dir_all::remove_dir_all; +use std::fs::{self, File}; + +macro_rules! assert_not_found { + ($path:expr) => {{ + match fs::metadata($path) { + Ok(_) => panic!("did not expect to retrieve metadata for {}", $path), + Err(ref err) if err.kind() != ::std::io::ErrorKind::NotFound => { + panic!("expected path {} to be NotFound, was {:?}", $path, err) + } + _ => {} + } + }}; +} + +#[test] +fn removes_empty() { + fs::create_dir_all("./empty").unwrap(); + assert!(fs::metadata("./empty").unwrap().is_dir()); + + remove_dir_all("./empty").unwrap(); + assert_not_found!("./empty"); +} + +#[test] +fn removes_files() { + fs::create_dir_all("./files").unwrap(); + + for i in 0..5 { + let path = format!("./files/empty-{}.txt", i); + + { + let mut _file = File::create(&path); + } + + assert!(fs::metadata(&path).unwrap().is_file()); + } + + remove_dir_all("./files").unwrap(); + assert_not_found!("./files"); +} + +#[test] +fn removes_dirs() { + for i in 0..5 { + let path = format!("./dirs/{}/subdir", i); + + fs::create_dir_all(&path).unwrap(); + + assert!(fs::metadata(&path).unwrap().is_dir()); + } + + remove_dir_all("./dirs").unwrap(); + assert_not_found!("./dirs"); +} + +#[test] +fn removes_read_only() { + env_logger::init(); + for i in 0..5 { + let path = format!("./readonly/{}/subdir", i); + + fs::create_dir_all(&path).unwrap(); + + let file_path = format!("{}/file.txt", path); + { + let file = File::create(&file_path).unwrap(); + + if i % 2 == 0 { + let metadata = file.metadata().unwrap(); + let mut permissions = metadata.permissions(); + permissions.set_readonly(true); + + fs::set_permissions(&file_path, permissions).unwrap(); + } + } + + assert_eq!( + i % 2 == 0, + fs::metadata(&file_path).unwrap().permissions().readonly() + ); + + if i % 2 == 1 { + let metadata = fs::metadata(&path).unwrap(); + + let mut permissions = metadata.permissions(); + permissions.set_readonly(true); + + fs::set_permissions(&path, permissions).unwrap(); + + assert!(fs::metadata(&path).unwrap().permissions().readonly()); + } + } + + remove_dir_all("./readonly").unwrap(); + assert_not_found!("./readonly"); +} + +// TODO: Should probably test readonly hard links...