From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../rust/remove_dir_all/.cargo-checksum.json | 1 + third_party/rust/remove_dir_all/Cargo.toml | 28 +++ third_party/rust/remove_dir_all/LICENCE-APACHE | 191 ++++++++++++++ third_party/rust/remove_dir_all/LICENCE-MIT | 26 ++ third_party/rust/remove_dir_all/README.md | 20 ++ third_party/rust/remove_dir_all/src/fs.rs | 278 +++++++++++++++++++++ third_party/rust/remove_dir_all/src/lib.rs | 26 ++ 7 files changed, 570 insertions(+) create mode 100644 third_party/rust/remove_dir_all/.cargo-checksum.json create mode 100644 third_party/rust/remove_dir_all/Cargo.toml create mode 100644 third_party/rust/remove_dir_all/LICENCE-APACHE create mode 100644 third_party/rust/remove_dir_all/LICENCE-MIT create mode 100644 third_party/rust/remove_dir_all/README.md create mode 100644 third_party/rust/remove_dir_all/src/fs.rs create mode 100644 third_party/rust/remove_dir_all/src/lib.rs (limited to 'third_party/rust/remove_dir_all') diff --git a/third_party/rust/remove_dir_all/.cargo-checksum.json b/third_party/rust/remove_dir_all/.cargo-checksum.json new file mode 100644 index 0000000000..bc449a299f --- /dev/null +++ b/third_party/rust/remove_dir_all/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"1e90fb0b342a93a8bd2d593c71bef703e69b760801099d31202556d3a4db0007","LICENCE-APACHE":"c6c8c9dbe29fb4d68d829c7a402f9f6baae3472ecf107cc2a57c75a9a8d1b85c","LICENCE-MIT":"db264505cb1856383e255c8373da9e5aeadc1cd92b570fcc94fd1fb7d892db78","README.md":"167f3796d716e1bb4a6b98d706fd3c02012dff55d488a24e7de822d896d3cc5a","src/fs.rs":"a7137d7f3a5769cd547daf2be2096a7a664d6114107a3f143c921c4aaab97719","src/lib.rs":"8155ac516b4d054de00d78ce70501175bea7248c0436e4a7f0d35823299f7dc2"},"package":"3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"} \ No newline at end of file diff --git a/third_party/rust/remove_dir_all/Cargo.toml b/third_party/rust/remove_dir_all/Cargo.toml new file mode 100644 index 0000000000..a847288925 --- /dev/null +++ b/third_party/rust/remove_dir_all/Cargo.toml @@ -0,0 +1,28 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "remove_dir_all" +version = "0.5.3" +authors = ["Aaronepower "] +include = ["Cargo.toml", "LICENCE-APACHE", "LICENCE-MIT", "src/**/*", "README.md"] +description = "A safe, reliable implementation of remove_dir_all for Windows" +readme = "README.md" +keywords = ["utility", "filesystem", "remove_dir", "windows"] +categories = ["filesystem"] +license = "MIT/Apache-2.0" +repository = "https://github.com/XAMPPRocky/remove_dir_all.git" +[dev-dependencies.doc-comment] +version = "0.3" +[target."cfg(windows)".dependencies.winapi] +version = "0.3" +features = ["std", "errhandlingapi", "winerror", "fileapi", "winbase"] diff --git a/third_party/rust/remove_dir_all/LICENCE-APACHE b/third_party/rust/remove_dir_all/LICENCE-APACHE new file mode 100644 index 0000000000..01de392358 --- /dev/null +++ b/third_party/rust/remove_dir_all/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/third_party/rust/remove_dir_all/LICENCE-MIT b/third_party/rust/remove_dir_all/LICENCE-MIT new file mode 100644 index 0000000000..627895f4e9 --- /dev/null +++ b/third_party/rust/remove_dir_all/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/third_party/rust/remove_dir_all/README.md b/third_party/rust/remove_dir_all/README.md new file mode 100644 index 0000000000..f6b21b2963 --- /dev/null +++ b/third_party/rust/remove_dir_all/README.md @@ -0,0 +1,20 @@ +# remove_dir_all + +[![Latest Version](https://img.shields.io/crates/v/remove_dir_all.svg)](https://crates.io/crates/remove_dir_all) +[![Docs](https://docs.rs/remove_dir_all/badge.svg)](https://docs.rs/remove_dir_all) +[![License](https://img.shields.io/github/license/XAMPPRocky/remove_dir_all.svg)](https://github.com/XAMPPRocky/remove_dir_all) + +## Description + +A reliable implementation of `remove_dir_all` for Windows. For Unix systems +re-exports `std::fs::remove_dir_all`. + +```rust,no_run +extern crate remove_dir_all; + +use remove_dir_all::*; + +fn main() { + remove_dir_all("./temp/").unwrap(); +} +``` diff --git a/third_party/rust/remove_dir_all/src/fs.rs b/third_party/rust/remove_dir_all/src/fs.rs new file mode 100644 index 0000000000..d81bd6563c --- /dev/null +++ b/third_party/rust/remove_dir_all/src/fs.rs @@ -0,0 +1,278 @@ +use std::ffi::OsString; +use std::fs::{self, File, OpenOptions}; +use std::os::windows::prelude::*; +use std::path::{Path, PathBuf}; +use std::{io, ptr}; + +use winapi::shared::minwindef::*; +use winapi::shared::winerror::*; +use winapi::um::errhandlingapi::*; +use winapi::um::fileapi::*; +use winapi::um::minwinbase::*; +use winapi::um::winbase::*; +use winapi::um::winnt::*; + +pub const VOLUME_NAME_DOS: DWORD = 0x0; + +struct RmdirContext<'a> { + base_dir: &'a Path, + readonly: bool, + counter: u64, +} + +/// Reliably removes a directory and all of its children. +/// +/// ```rust +/// extern crate remove_dir_all; +/// +/// use std::fs; +/// use remove_dir_all::*; +/// +/// fn main() { +/// fs::create_dir("./temp/").unwrap(); +/// remove_dir_all("./temp/").unwrap(); +/// } +/// ``` +pub fn remove_dir_all>(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 scheduled. + // To work around this, we move the file or directory to some `base_dir` + // right before deletion to avoid races. + // + // As `base_dir` we choose the parent dir of the directory we want to + // remove. We very probably have permission to create files here, as we + // already need write permission in this dir to delete the directory. And it + // should be on the same volume. + // + // 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 a `//?/`-path with `get_path()`. + // + // To make sure we don't leave a moved file laying around if the process + // crashes before we can delete the file, we do all operations on an file + // handle. By opening a file with `FILE_FLAG_DELETE_ON_CLOSE` Windows will + // always delete the file when the handle closes. + // + // All files are renamed to be in the `base_dir`, and have their name + // changed to "rm-". After every rename the counter is increased. + // Rename should not overwrite possibly existing files in the base dir. So + // if it fails with `AlreadyExists`, we just increase the counter and try + // again. + // + // For read-only files and directories we first have to remove the read-only + // attribute before we can move or delete them. This also removes the + // attribute from possible hardlinks to the file, so just before closing we + // restore the read-only attribute. + // + // If 'path' points to a directory symlink or junction we should not + // recursively remove the target of the link, but only the link itself. + // + // Moving and deleting is guaranteed to succeed if we are able to open the + // file with `DELETE` permission. If others have the file open we only have + // `DELETE` permission if they have specified `FILE_SHARE_DELETE`. We can + // also delete the file now, but it will not disappear until all others have + // closed the file. But no-one can open the file after we have flagged it + // for deletion. + + // Open the path once to get the canonical path, file type and attributes. + let (path, metadata) = { + let path = path.as_ref(); + let mut opts = OpenOptions::new(); + opts.access_mode(FILE_READ_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); + let file = opts.open(path)?; + (get_path(&file)?, path.metadata()?) + }; + + let mut ctx = RmdirContext { + base_dir: match path.parent() { + Some(dir) => dir, + None => { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Can't delete root directory", + )) + } + }, + readonly: metadata.permissions().readonly(), + counter: 0, + }; + + let filetype = metadata.file_type(); + if filetype.is_dir() { + if !filetype.is_symlink() { + remove_dir_all_recursive(path.as_ref(), &mut ctx) + } else { + remove_item(path.as_ref(), &mut ctx) + } + } else { + Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Not a directory", + )) + } +} + +fn remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { + if ctx.readonly { + // remove read-only permision + let mut permissions = path.metadata()?.permissions(); + permissions.set_readonly(false); + + fs::set_permissions(path, permissions)?; + } + + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE); + opts.custom_flags( + FILE_FLAG_BACKUP_SEMANTICS | // delete directory + FILE_FLAG_OPEN_REPARSE_POINT | // delete symlink + FILE_FLAG_DELETE_ON_CLOSE, + ); + let file = opts.open(path)?; + move_item(&file, ctx)?; + + if ctx.readonly { + // restore read-only flag just in case there are other hard links + match fs::metadata(&path) { + Ok(metadata) => { + let mut perm = metadata.permissions(); + perm.set_readonly(true); + fs::set_permissions(&path, perm)?; + } + Err(ref err) if err.kind() == io::ErrorKind::NotFound => {} + err => return err.map(|_| ()), + } + } + + Ok(()) +} + +fn move_item(file: &File, ctx: &mut RmdirContext) -> io::Result<()> { + let mut tmpname = ctx.base_dir.join(format! {"rm-{}", ctx.counter}); + ctx.counter += 1; + + // Try to rename the file. If it already exists, just retry with an other + // filename. + while let Err(err) = rename(file, &tmpname, false) { + if err.kind() != io::ErrorKind::AlreadyExists { + return Err(err); + }; + tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter)); + ctx.counter += 1; + } + + Ok(()) +} + +fn rename(file: &File, new: &Path, replace: bool) -> io::Result<()> { + // &self must be opened with DELETE permission + use std::iter; + #[cfg(target_pointer_width = "32")] + const STRUCT_SIZE: usize = 12; + #[cfg(target_pointer_width = "64")] + const STRUCT_SIZE: usize = 20; + + // FIXME: check for internal NULs in 'new' + let mut data: Vec = iter::repeat(0u16) + .take(STRUCT_SIZE / 2) + .chain(new.as_os_str().encode_wide()) + .collect(); + data.push(0); + let size = data.len() * 2; + + unsafe { + // Thanks to alignment guarantees on Windows this works + // (8 for 32-bit and 16 for 64-bit) + let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO; + // The type of ReplaceIfExists is BOOL, but it actually expects a + // BOOLEAN. This means true is -1, not c::TRUE. + (*info).ReplaceIfExists = if replace { -1 } else { FALSE }; + (*info).RootDirectory = ptr::null_mut(); + (*info).FileNameLength = (size - STRUCT_SIZE) as DWORD; + let result = SetFileInformationByHandle( + file.as_raw_handle(), + FileRenameInfo, + data.as_mut_ptr() as *mut _ as *mut _, + size as DWORD, + ); + + if result == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } +} + +fn get_path(f: &File) -> io::Result { + fill_utf16_buf( + |buf, sz| unsafe { GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, VOLUME_NAME_DOS) }, + |buf| PathBuf::from(OsString::from_wide(buf)), + ) +} + +fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { + let dir_readonly = ctx.readonly; + for child in fs::read_dir(path)? { + let child = child?; + let child_type = child.file_type()?; + ctx.readonly = child.metadata()?.permissions().readonly(); + if child_type.is_dir() { + remove_dir_all_recursive(&child.path(), ctx)?; + } else { + remove_item(&child.path().as_ref(), ctx)?; + } + } + ctx.readonly = dir_readonly; + remove_item(path, ctx) +} + +fn fill_utf16_buf(mut f1: F1, f2: F2) -> io::Result +where + F1: FnMut(*mut u16, DWORD) -> DWORD, + F2: FnOnce(&[u16]) -> T, +{ + // Start off with a stack buf but then spill over to the heap if we end up + // needing more space. + let mut stack_buf = [0u16; 512]; + let mut heap_buf = Vec::new(); + unsafe { + let mut n = stack_buf.len(); + + loop { + let buf = if n <= stack_buf.len() { + &mut stack_buf[..] + } else { + let extra = n - heap_buf.len(); + heap_buf.reserve(extra); + heap_buf.set_len(n); + &mut heap_buf[..] + }; + + // This function is typically called on windows API functions which + // will return the correct length of the string, but these functions + // also return the `0` on error. In some cases, however, the + // returned "correct length" may actually be 0! + // + // To handle this case we call `SetLastError` to reset it to 0 and + // then check it again if we get the "0 error value". If the "last + // error" is still 0 then we interpret it as a 0 length buffer and + // not an actual error. + SetLastError(0); + let k = match f1(buf.as_mut_ptr(), n as DWORD) { + 0 if GetLastError() == 0 => 0, + 0 => return Err(io::Error::last_os_error()), + n => n, + } as usize; + if k == n && GetLastError() == ERROR_INSUFFICIENT_BUFFER { + n *= 2; + } else if k >= n { + n = k; + } else { + return Ok(f2(&buf[..k])); + } + } + } +} diff --git a/third_party/rust/remove_dir_all/src/lib.rs b/third_party/rust/remove_dir_all/src/lib.rs new file mode 100644 index 0000000000..f7b3dc6347 --- /dev/null +++ b/third_party/rust/remove_dir_all/src/lib.rs @@ -0,0 +1,26 @@ +//! 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`. + +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] + +#[cfg(windows)] +extern crate winapi; + +#[cfg(doctest)] +#[macro_use] +extern crate doc_comment; + +#[cfg(doctest)] +doctest!("../README.md"); + +#[cfg(windows)] +mod fs; + +#[cfg(windows)] +pub use self::fs::remove_dir_all; + +#[cfg(not(windows))] +pub use std::fs::remove_dir_all; -- cgit v1.2.3