|
| 1 | +use std::{ |
| 2 | + ffi::OsStr, |
| 3 | + fs::File, |
| 4 | + io::{ErrorKind, Result}, |
| 5 | + path::Path, |
| 6 | +}; |
| 7 | + |
| 8 | +#[cfg(feature = "parallel")] |
| 9 | +use rayon::prelude::*; |
| 10 | + |
| 11 | +mod io; |
| 12 | +mod path_components; |
| 13 | + |
| 14 | +cfg_if::cfg_if! { |
| 15 | + if #[cfg(windows)] { |
| 16 | + mod win; |
| 17 | + pub(crate) use win::WindowsIo as OsIo; |
| 18 | + } else { |
| 19 | + mod unix; |
| 20 | + pub(crate) use unix::UnixIo as OsIo; |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +impl super::RemoveDir for std::fs::File { |
| 25 | + fn remove_dir_contents(&mut self, debug_root: Option<&Path>) -> Result<()> { |
| 26 | + // thunk over to the free version adding in the os-specific IO trait impl |
| 27 | + let debug_root = match debug_root { |
| 28 | + None => PathComponents::Path(Path::new("")), |
| 29 | + Some(debug_root) => PathComponents::Path(debug_root), |
| 30 | + }; |
| 31 | + _remove_dir_contents::<OsIo>(self, &debug_root) |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// Entry point for deprecated function |
| 36 | +pub(crate) fn _ensure_empty_dir_path<I: io::Io, P: AsRef<Path>>(path: P) -> Result<()> { |
| 37 | + // This is as TOCTOU safe as we can make it. Attacks via link replacements |
| 38 | + // in interior components of the path is still possible. if the create |
| 39 | + // succeeds, mission accomplished. if the create fails, open the dir |
| 40 | + // (subject to races again), and then proceed to delete the contents via the |
| 41 | + // descriptor. |
| 42 | + match std::fs::create_dir(&path) { |
| 43 | + Err(e) if e.kind() == ErrorKind::AlreadyExists => { |
| 44 | + // Exists and is a dir. Open it |
| 45 | + let mut existing_dir = I::open_dir(path.as_ref())?; |
| 46 | + existing_dir.remove_dir_contents(Some(path.as_ref())) |
| 47 | + } |
| 48 | + otherwise => otherwise, |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +// Deprecated entry point |
| 53 | +pub(crate) fn _remove_dir_contents_path<I: io::Io, P: AsRef<Path>>(path: P) -> Result<()> { |
| 54 | + let mut d = I::open_dir(path.as_ref())?; |
| 55 | + _remove_dir_contents::<I>(&mut d, &PathComponents::Path(path.as_ref())) |
| 56 | +} |
| 57 | + |
| 58 | +/// exterior lifetime interface to dir removal |
| 59 | +fn _remove_dir_contents<I: io::Io>(d: &mut File, debug_root: &PathComponents<'_>) -> Result<()> { |
| 60 | + let owned_handle = I::duplicate_fd(d)?; |
| 61 | + remove_dir_contents_recursive::<I>(owned_handle, debug_root) |
| 62 | +} |
| 63 | + |
| 64 | +/// deprecated interface |
| 65 | +pub(crate) fn remove_dir_all_path<I: io::Io, P: AsRef<Path>>(path: P) -> Result<()> { |
| 66 | + let p = path.as_ref(); |
| 67 | + // Opportunity 1 for races |
| 68 | + let d = I::open_dir(p)?; |
| 69 | + let debug_root = PathComponents::Path(if p.has_root() { p } else { Path::new(".") }); |
| 70 | + remove_dir_contents_recursive::<OsIo>(d, &debug_root)?; |
| 71 | + // Opportunity 2 for races |
| 72 | + std::fs::remove_dir(&path) |
| 73 | +} |
| 74 | + |
| 75 | +use crate::RemoveDir; |
| 76 | + |
| 77 | +use self::path_components::PathComponents; |
| 78 | + |
| 79 | +// Core workhorse, heading towards this being able to be tasks. |
| 80 | +#[allow(clippy::map_identity)] |
| 81 | +fn remove_dir_contents_recursive<I: io::Io>( |
| 82 | + mut d: File, |
| 83 | + debug_root: &PathComponents<'_>, |
| 84 | +) -> Result<()> { |
| 85 | + #[cfg(feature = "log")] |
| 86 | + log::trace!("scanning {}", &debug_root); |
| 87 | + // We take a os level clone of the FD so that there are no lifetime |
| 88 | + // concerns. It would *not* be ok to do readdir on one file twice |
| 89 | + // concurrently because of shared kernel state. |
| 90 | + let dirfd = I::duplicate_fd(&mut d)?; |
| 91 | + cfg_if::cfg_if! { |
| 92 | + if #[cfg(feature = "parallel")] { |
| 93 | + let iter = fs_at::read_dir(&mut d)?; |
| 94 | + let iter = iter.par_bridge(); |
| 95 | + } else { |
| 96 | + let mut iter = fs_at::read_dir(&mut d)?; |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + iter.try_for_each(|dir_entry| -> Result<()> { |
| 101 | + let dir_entry = dir_entry?; |
| 102 | + let name = dir_entry.name(); |
| 103 | + if name == OsStr::new(".") || name == OsStr::new("..") { |
| 104 | + return Ok(()); |
| 105 | + } |
| 106 | + let dir_path = Path::new(name); |
| 107 | + let dir_debug_root = PathComponents::Component(debug_root, dir_path); |
| 108 | + // Windows optimised: open everything always, which is not bad for |
| 109 | + // linux, and portable to OS's and FS's that don't expose inode type in |
| 110 | + // the readdir entries. |
| 111 | + |
| 112 | + let mut opts = fs_at::OpenOptions::default(); |
| 113 | + opts.read(true) |
| 114 | + .write(fs_at::OpenOptionsWriteMode::Write) |
| 115 | + .follow(false); |
| 116 | + |
| 117 | + let child_result = opts.open_dir_at(&dirfd, name); |
| 118 | + let is_dir = match child_result { |
| 119 | + Err(e) if !I::is_eloop(&e) => return Err(e), |
| 120 | + Err(_) => false, |
| 121 | + Ok(child_file) => { |
| 122 | + let metadata = child_file.metadata()?; |
| 123 | + let is_dir = metadata.is_dir(); |
| 124 | + I::clear_readonly(&child_file, &dir_debug_root, &metadata)?; |
| 125 | + |
| 126 | + if is_dir { |
| 127 | + remove_dir_contents_recursive::<I>(child_file, &dir_debug_root)?; |
| 128 | + #[cfg(feature = "log")] |
| 129 | + log::trace!("rmdir: {}", &dir_debug_root); |
| 130 | + let opts = fs_at::OpenOptions::default(); |
| 131 | + opts.rmdir_at(&dirfd, name).map_err(|e| { |
| 132 | + #[cfg(feature = "log")] |
| 133 | + log::debug!("error removing {}", dir_debug_root); |
| 134 | + e |
| 135 | + })?; |
| 136 | + } |
| 137 | + is_dir |
| 138 | + } |
| 139 | + }; |
| 140 | + if !is_dir { |
| 141 | + #[cfg(feature = "log")] |
| 142 | + log::trace!("unlink: {}", &dir_debug_root); |
| 143 | + opts.unlink_at(&dirfd, name).map_err(|e| { |
| 144 | + #[cfg(feature = "log")] |
| 145 | + log::debug!("error removing {}", dir_debug_root); |
| 146 | + e |
| 147 | + })?; |
| 148 | + } |
| 149 | + |
| 150 | + #[cfg(feature = "log")] |
| 151 | + log::trace!("removed {}", dir_debug_root); |
| 152 | + Ok(()) |
| 153 | + })?; |
| 154 | + #[cfg(feature = "log")] |
| 155 | + log::trace!("scanned {}", &debug_root); |
| 156 | + Ok(()) |
| 157 | +} |
0 commit comments