Skip to content

Commit a4c8679

Browse files
authored
Unrolled build for rust-lang#129800
Rollup merge of rust-lang#129800 - ChrisDenton:remove-dir-all2, r=Amanieu Move the Windows remove_dir_all impl into a module and make it more race resistant This attempts to make the Windows implementation of `remove_dir_all` easier to understand and work with by separating out different concerns into their own functions. The code is mostly the same as before just moved around. There are some changes to make it more robust against races (e.g. two calls to `remove_dir_all` running concurrently). The module level comment explains the issue. try-job: x86_64-msvc try-job: i686-msvc
2 parents 6199b69 + bb9d5c4 commit a4c8679

File tree

5 files changed

+253
-169
lines changed

5 files changed

+253
-169
lines changed

library/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);

library/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

library/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;

library/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)