Skip to content

Commit 667d060

Browse files
committed
Move remove_dir_all impl into a module
1 parent 374229a commit 667d060

File tree

5 files changed

+253
-169
lines changed

5 files changed

+253
-169
lines changed

Diff for: std/src/sys/pal/windows/api.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ pub struct WinError {
254254
pub code: u32,
255255
}
256256
impl WinError {
257-
const fn new(code: u32) -> Self {
257+
pub const fn new(code: u32) -> Self {
258258
Self { code }
259259
}
260260
}
@@ -272,8 +272,11 @@ impl WinError {
272272
// tidy-alphabetical-start
273273
pub const ACCESS_DENIED: Self = Self::new(c::ERROR_ACCESS_DENIED);
274274
pub const ALREADY_EXISTS: Self = Self::new(c::ERROR_ALREADY_EXISTS);
275+
pub const BAD_NET_NAME: Self = Self::new(c::ERROR_BAD_NET_NAME);
276+
pub const BAD_NETPATH: Self = Self::new(c::ERROR_BAD_NETPATH);
275277
pub const CANT_ACCESS_FILE: Self = Self::new(c::ERROR_CANT_ACCESS_FILE);
276278
pub const DELETE_PENDING: Self = Self::new(c::ERROR_DELETE_PENDING);
279+
pub const DIR_NOT_EMPTY: Self = Self::new(c::ERROR_DIR_NOT_EMPTY);
277280
pub const DIRECTORY: Self = Self::new(c::ERROR_DIRECTORY);
278281
pub const FILE_NOT_FOUND: Self = Self::new(c::ERROR_FILE_NOT_FOUND);
279282
pub const INSUFFICIENT_BUFFER: Self = Self::new(c::ERROR_INSUFFICIENT_BUFFER);

Diff for: std/src/sys/pal/windows/c/bindings.txt

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Windows.Wdk.Storage.FileSystem.FILE_WRITE_THROUGH
3434
Windows.Wdk.Storage.FileSystem.NtCreateFile
3535
Windows.Wdk.Storage.FileSystem.NTCREATEFILE_CREATE_DISPOSITION
3636
Windows.Wdk.Storage.FileSystem.NTCREATEFILE_CREATE_OPTIONS
37+
Windows.Wdk.Storage.FileSystem.NtOpenFile
3738
Windows.Wdk.Storage.FileSystem.NtReadFile
3839
Windows.Wdk.Storage.FileSystem.NtWriteFile
3940
Windows.Wdk.Storage.FileSystem.SYMLINK_FLAG_RELATIVE
@@ -1931,10 +1932,14 @@ Windows.Win32.Foundation.RtlNtStatusToDosError
19311932
Windows.Win32.Foundation.SetHandleInformation
19321933
Windows.Win32.Foundation.SetLastError
19331934
Windows.Win32.Foundation.STATUS_DELETE_PENDING
1935+
Windows.Win32.Foundation.STATUS_DIRECTORY_NOT_EMPTY
19341936
Windows.Win32.Foundation.STATUS_END_OF_FILE
1937+
Windows.Win32.Foundation.STATUS_FILE_DELETED
1938+
Windows.Win32.Foundation.STATUS_INVALID_HANDLE
19351939
Windows.Win32.Foundation.STATUS_INVALID_PARAMETER
19361940
Windows.Win32.Foundation.STATUS_NOT_IMPLEMENTED
19371941
Windows.Win32.Foundation.STATUS_PENDING
1942+
Windows.Win32.Foundation.STATUS_SHARING_VIOLATION
19381943
Windows.Win32.Foundation.STATUS_SUCCESS
19391944
Windows.Win32.Foundation.TRUE
19401945
Windows.Win32.Foundation.UNICODE_STRING

Diff for: std/src/sys/pal/windows/c/windows_sys.rs

+5
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ windows_targets::link!("kernel32.dll" "system" fn WideCharToMultiByte(codepage :
105105
windows_targets::link!("kernel32.dll" "system" fn WriteConsoleW(hconsoleoutput : HANDLE, lpbuffer : PCWSTR, nnumberofcharstowrite : u32, lpnumberofcharswritten : *mut u32, lpreserved : *const core::ffi::c_void) -> BOOL);
106106
windows_targets::link!("kernel32.dll" "system" fn WriteFileEx(hfile : HANDLE, lpbuffer : *const u8, nnumberofbytestowrite : u32, lpoverlapped : *mut OVERLAPPED, lpcompletionroutine : LPOVERLAPPED_COMPLETION_ROUTINE) -> BOOL);
107107
windows_targets::link!("ntdll.dll" "system" fn NtCreateFile(filehandle : *mut HANDLE, desiredaccess : FILE_ACCESS_RIGHTS, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, allocationsize : *const i64, fileattributes : FILE_FLAGS_AND_ATTRIBUTES, shareaccess : FILE_SHARE_MODE, createdisposition : NTCREATEFILE_CREATE_DISPOSITION, createoptions : NTCREATEFILE_CREATE_OPTIONS, eabuffer : *const core::ffi::c_void, ealength : u32) -> NTSTATUS);
108+
windows_targets::link!("ntdll.dll" "system" fn NtOpenFile(filehandle : *mut HANDLE, desiredaccess : u32, objectattributes : *const OBJECT_ATTRIBUTES, iostatusblock : *mut IO_STATUS_BLOCK, shareaccess : u32, openoptions : u32) -> NTSTATUS);
108109
windows_targets::link!("ntdll.dll" "system" fn NtReadFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *mut core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS);
109110
windows_targets::link!("ntdll.dll" "system" fn NtWriteFile(filehandle : HANDLE, event : HANDLE, apcroutine : PIO_APC_ROUTINE, apccontext : *const core::ffi::c_void, iostatusblock : *mut IO_STATUS_BLOCK, buffer : *const core::ffi::c_void, length : u32, byteoffset : *const i64, key : *const u32) -> NTSTATUS);
110111
windows_targets::link!("ntdll.dll" "system" fn RtlNtStatusToDosError(status : NTSTATUS) -> u32);
@@ -2982,10 +2983,14 @@ pub struct STARTUPINFOW {
29822983
}
29832984
pub type STARTUPINFOW_FLAGS = u32;
29842985
pub const STATUS_DELETE_PENDING: NTSTATUS = 0xC0000056_u32 as _;
2986+
pub const STATUS_DIRECTORY_NOT_EMPTY: NTSTATUS = 0xC0000101_u32 as _;
29852987
pub const STATUS_END_OF_FILE: NTSTATUS = 0xC0000011_u32 as _;
2988+
pub const STATUS_FILE_DELETED: NTSTATUS = 0xC0000123_u32 as _;
2989+
pub const STATUS_INVALID_HANDLE: NTSTATUS = 0xC0000008_u32 as _;
29862990
pub const STATUS_INVALID_PARAMETER: NTSTATUS = 0xC000000D_u32 as _;
29872991
pub const STATUS_NOT_IMPLEMENTED: NTSTATUS = 0xC0000002_u32 as _;
29882992
pub const STATUS_PENDING: NTSTATUS = 0x103_u32 as _;
2993+
pub const STATUS_SHARING_VIOLATION: NTSTATUS = 0xC0000043_u32 as _;
29892994
pub const STATUS_SUCCESS: NTSTATUS = 0x0_u32 as _;
29902995
pub const STD_ERROR_HANDLE: STD_HANDLE = 4294967284u32;
29912996
pub type STD_HANDLE = u32;

Diff for: std/src/sys/pal/windows/fs.rs

+43-168
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ use crate::sys::handle::Handle;
1414
use crate::sys::path::maybe_verbatim;
1515
use crate::sys::time::SystemTime;
1616
use crate::sys::{c, cvt, Align8};
17-
use crate::sys_common::{ignore_notfound, AsInner, FromInner, IntoInner};
18-
use crate::{fmt, ptr, slice, thread};
17+
use crate::sys_common::{AsInner, FromInner, IntoInner};
18+
use crate::{fmt, ptr, slice};
19+
20+
mod remove_dir_all;
21+
use remove_dir_all::remove_dir_all_iterative;
1922

2023
pub struct File {
2124
handle: Handle,
@@ -646,6 +649,22 @@ impl File {
646649
Ok(info)
647650
}
648651
}
652+
653+
/// Deletes the file, consuming the file handle to ensure the delete occurs
654+
/// as immediately as possible.
655+
/// This attempts to use `posix_delete` but falls back to `win32_delete`
656+
/// if that is not supported by the filesystem.
657+
#[allow(unused)]
658+
fn delete(self) -> Result<(), WinError> {
659+
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
660+
match self.posix_delete() {
661+
Err(WinError::INVALID_PARAMETER)
662+
| Err(WinError::NOT_SUPPORTED)
663+
| Err(WinError::INVALID_FUNCTION) => self.win32_delete(),
664+
result => result,
665+
}
666+
}
667+
649668
/// Delete using POSIX semantics.
650669
///
651670
/// Files will be deleted as soon as the handle is closed. This is supported
@@ -654,21 +673,23 @@ impl File {
654673
///
655674
/// If the operation is not supported for this filesystem or OS version
656675
/// then errors will be `ERROR_NOT_SUPPORTED` or `ERROR_INVALID_PARAMETER`.
657-
fn posix_delete(&self) -> io::Result<()> {
676+
#[allow(unused)]
677+
fn posix_delete(&self) -> Result<(), WinError> {
658678
let info = c::FILE_DISPOSITION_INFO_EX {
659679
Flags: c::FILE_DISPOSITION_FLAG_DELETE
660680
| c::FILE_DISPOSITION_FLAG_POSIX_SEMANTICS
661681
| c::FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE,
662682
};
663-
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()
683+
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info)
664684
}
665685

666686
/// Delete a file using win32 semantics. The file won't actually be deleted
667687
/// until all file handles are closed. However, marking a file for deletion
668688
/// will prevent anyone from opening a new handle to the file.
669-
fn win32_delete(&self) -> io::Result<()> {
689+
#[allow(unused)]
690+
fn win32_delete(&self) -> Result<(), WinError> {
670691
let info = c::FILE_DISPOSITION_INFO { DeleteFile: c::TRUE as _ };
671-
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()
692+
api::set_file_information_by_handle(self.handle.as_raw_handle(), &info)
672693
}
673694

674695
/// Fill the given buffer with as many directory entries as will fit.
@@ -684,21 +705,23 @@ impl File {
684705
/// A symlink directory is simply an empty directory with some "reparse" metadata attached.
685706
/// So if you open a link (not its target) and iterate the directory,
686707
/// you will always iterate an empty directory regardless of the target.
687-
fn fill_dir_buff(&self, buffer: &mut DirBuff, restart: bool) -> io::Result<bool> {
708+
#[allow(unused)]
709+
fn fill_dir_buff(&self, buffer: &mut DirBuff, restart: bool) -> Result<bool, WinError> {
688710
let class =
689711
if restart { c::FileIdBothDirectoryRestartInfo } else { c::FileIdBothDirectoryInfo };
690712

691713
unsafe {
692-
let result = cvt(c::GetFileInformationByHandleEx(
693-
self.handle.as_raw_handle(),
714+
let result = c::GetFileInformationByHandleEx(
715+
self.as_raw_handle(),
694716
class,
695717
buffer.as_mut_ptr().cast(),
696718
buffer.capacity() as _,
697-
));
698-
match result {
699-
Ok(_) => Ok(true),
700-
Err(e) if e.raw_os_error() == Some(c::ERROR_NO_MORE_FILES as _) => Ok(false),
701-
Err(e) => Err(e),
719+
);
720+
if result == 0 {
721+
let err = api::get_last_error();
722+
if err.code == c::ERROR_NO_MORE_FILES { Ok(false) } else { Err(err) }
723+
} else {
724+
Ok(true)
702725
}
703726
}
704727
}
@@ -804,62 +827,6 @@ unsafe fn from_maybe_unaligned<'a>(p: *const u16, len: usize) -> Cow<'a, [u16]>
804827
}
805828
}
806829

807-
/// Open a link relative to the parent directory, ensure no symlinks are followed.
808-
fn open_link_no_reparse(parent: &File, name: &[u16], access: u32) -> io::Result<File> {
809-
// This is implemented using the lower level `NtCreateFile` function as
810-
// unfortunately opening a file relative to a parent is not supported by
811-
// win32 functions. It is however a fundamental feature of the NT kernel.
812-
//
813-
// See https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile
814-
unsafe {
815-
let mut handle = ptr::null_mut();
816-
let mut io_status = c::IO_STATUS_BLOCK::PENDING;
817-
let mut name_str = c::UNICODE_STRING::from_ref(name);
818-
use crate::sync::atomic::{AtomicU32, Ordering};
819-
// The `OBJ_DONT_REPARSE` attribute ensures that we haven't been
820-
// tricked into following a symlink. However, it may not be available in
821-
// earlier versions of Windows.
822-
static ATTRIBUTES: AtomicU32 = AtomicU32::new(c::OBJ_DONT_REPARSE);
823-
let object = c::OBJECT_ATTRIBUTES {
824-
ObjectName: &mut name_str,
825-
RootDirectory: parent.as_raw_handle(),
826-
Attributes: ATTRIBUTES.load(Ordering::Relaxed),
827-
..c::OBJECT_ATTRIBUTES::default()
828-
};
829-
let status = c::NtCreateFile(
830-
&mut handle,
831-
access,
832-
&object,
833-
&mut io_status,
834-
crate::ptr::null_mut(),
835-
0,
836-
c::FILE_SHARE_DELETE | c::FILE_SHARE_READ | c::FILE_SHARE_WRITE,
837-
c::FILE_OPEN,
838-
// If `name` is a symlink then open the link rather than the target.
839-
c::FILE_OPEN_REPARSE_POINT,
840-
crate::ptr::null_mut(),
841-
0,
842-
);
843-
// Convert an NTSTATUS to the more familiar Win32 error codes (aka "DosError")
844-
if c::nt_success(status) {
845-
Ok(File::from_raw_handle(handle))
846-
} else if status == c::STATUS_DELETE_PENDING {
847-
// We make a special exception for `STATUS_DELETE_PENDING` because
848-
// otherwise this will be mapped to `ERROR_ACCESS_DENIED` which is
849-
// very unhelpful.
850-
Err(io::Error::from_raw_os_error(c::ERROR_DELETE_PENDING as i32))
851-
} else if status == c::STATUS_INVALID_PARAMETER
852-
&& ATTRIBUTES.load(Ordering::Relaxed) == c::OBJ_DONT_REPARSE
853-
{
854-
// Try without `OBJ_DONT_REPARSE`. See above.
855-
ATTRIBUTES.store(0, Ordering::Relaxed);
856-
open_link_no_reparse(parent, name, access)
857-
} else {
858-
Err(io::Error::from_raw_os_error(c::RtlNtStatusToDosError(status) as _))
859-
}
860-
}
861-
}
862-
863830
impl AsInner<Handle> for File {
864831
#[inline]
865832
fn as_inner(&self) -> &Handle {
@@ -1142,114 +1109,22 @@ pub fn rmdir(p: &Path) -> io::Result<()> {
11421109
Ok(())
11431110
}
11441111

1145-
/// Open a file or directory without following symlinks.
1146-
fn open_link(path: &Path, access_mode: u32) -> io::Result<File> {
1112+
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
1113+
// Open a file or directory without following symlinks.
11471114
let mut opts = OpenOptions::new();
1148-
opts.access_mode(access_mode);
1115+
opts.access_mode(c::FILE_LIST_DIRECTORY);
11491116
// `FILE_FLAG_BACKUP_SEMANTICS` allows opening directories.
11501117
// `FILE_FLAG_OPEN_REPARSE_POINT` opens a link instead of its target.
11511118
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);
1152-
File::open(path, &opts)
1153-
}
1154-
1155-
pub fn remove_dir_all(path: &Path) -> io::Result<()> {
1156-
let file = open_link(path, c::DELETE | c::FILE_LIST_DIRECTORY)?;
1119+
let file = File::open(path, &opts)?;
11571120

11581121
// Test if the file is not a directory or a symlink to a directory.
11591122
if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {
11601123
return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));
11611124
}
11621125

1163-
match ignore_notfound(remove_dir_all_iterative(&file, File::posix_delete)) {
1164-
Err(e) => {
1165-
if let Some(code) = e.raw_os_error() {
1166-
match code as u32 {
1167-
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
1168-
c::ERROR_NOT_SUPPORTED
1169-
| c::ERROR_INVALID_FUNCTION
1170-
| c::ERROR_INVALID_PARAMETER => {
1171-
remove_dir_all_iterative(&file, File::win32_delete)
1172-
}
1173-
_ => Err(e),
1174-
}
1175-
} else {
1176-
Err(e)
1177-
}
1178-
}
1179-
ok => ok,
1180-
}
1181-
}
1182-
1183-
fn remove_dir_all_iterative(f: &File, delete: fn(&File) -> io::Result<()>) -> io::Result<()> {
1184-
// When deleting files we may loop this many times when certain error conditions occur.
1185-
// This allows remove_dir_all to succeed when the error is temporary.
1186-
const MAX_RETRIES: u32 = 10;
1187-
1188-
let mut buffer = DirBuff::new();
1189-
let mut dirlist = vec![f.duplicate()?];
1190-
1191-
// FIXME: This is a hack so we can push to the dirlist vec after borrowing from it.
1192-
fn copy_handle(f: &File) -> mem::ManuallyDrop<File> {
1193-
unsafe { mem::ManuallyDrop::new(File::from_raw_handle(f.as_raw_handle())) }
1194-
}
1195-
1196-
let mut restart = true;
1197-
while let Some(dir) = dirlist.last() {
1198-
let dir = copy_handle(dir);
1199-
1200-
// Fill the buffer and iterate the entries.
1201-
let more_data = dir.fill_dir_buff(&mut buffer, restart)?;
1202-
restart = false;
1203-
for (name, is_directory) in buffer.iter() {
1204-
if is_directory {
1205-
let child_dir = open_link_no_reparse(
1206-
&dir,
1207-
&name,
1208-
c::SYNCHRONIZE | c::DELETE | c::FILE_LIST_DIRECTORY,
1209-
);
1210-
// On success, add the handle to the queue.
1211-
// If opening the directory fails we treat it the same as a file
1212-
if let Ok(child_dir) = child_dir {
1213-
dirlist.push(child_dir);
1214-
continue;
1215-
}
1216-
}
1217-
for i in 1..=MAX_RETRIES {
1218-
let result = open_link_no_reparse(&dir, &name, c::SYNCHRONIZE | c::DELETE);
1219-
match result {
1220-
Ok(f) => delete(&f)?,
1221-
// Already deleted, so skip.
1222-
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
1223-
// Retry a few times if the file is locked or a delete is already in progress.
1224-
Err(e)
1225-
if i < MAX_RETRIES
1226-
&& (e.raw_os_error() == Some(c::ERROR_DELETE_PENDING as _)
1227-
|| e.raw_os_error() == Some(c::ERROR_SHARING_VIOLATION as _)) => {}
1228-
// Otherwise return the error.
1229-
Err(e) => return Err(e),
1230-
}
1231-
thread::yield_now();
1232-
}
1233-
}
1234-
// If there were no more files then delete the directory.
1235-
if !more_data {
1236-
if let Some(dir) = dirlist.pop() {
1237-
// Retry deleting a few times in case we need to wait for a file to be deleted.
1238-
for i in 1..=MAX_RETRIES {
1239-
let result = delete(&dir);
1240-
if let Err(e) = result {
1241-
if i == MAX_RETRIES || e.kind() != io::ErrorKind::DirectoryNotEmpty {
1242-
return Err(e);
1243-
}
1244-
thread::yield_now();
1245-
} else {
1246-
break;
1247-
}
1248-
}
1249-
}
1250-
}
1251-
}
1252-
Ok(())
1126+
// Remove the directory and all its contents.
1127+
remove_dir_all_iterative(file).io_result()
12531128
}
12541129

12551130
pub fn readlink(path: &Path) -> io::Result<PathBuf> {

0 commit comments

Comments
 (0)