Rollup merge of #129800 - ChrisDenton:remove-dir-all2, r=Amanieu · patricklam/verify-rust-std@e41afdc (original) (raw)

`@@ -14,8 +14,11 @@ use crate::sys::handle::Handle;

`

14

14

`use crate::sys::path::maybe_verbatim;

`

15

15

`use crate::sys::time::SystemTime;

`

16

16

`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;

`

19

22

``

20

23

`pub struct File {

`

21

24

`handle: Handle,

`

`@@ -646,6 +649,22 @@ impl File {

`

646

649

`Ok(info)

`

647

650

`}

`

648

651

`}

`

``

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

+

649

668

`/// Delete using POSIX semantics.

`

650

669

`///

`

651

670

`/// Files will be deleted as soon as the handle is closed. This is supported

`

`@@ -654,21 +673,23 @@ impl File {

`

654

673

`///

`

655

674

`/// If the operation is not supported for this filesystem or OS version

`

656

675

`` /// 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> {

`

658

678

`let info = c::FILE_DISPOSITION_INFO_EX {

`

659

679

`Flags: c::FILE_DISPOSITION_FLAG_DELETE

`

660

680

` | c::FILE_DISPOSITION_FLAG_POSIX_SEMANTICS

`

661

681

` | c::FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE,

`

662

682

`};

`

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)

`

664

684

`}

`

665

685

``

666

686

`/// Delete a file using win32 semantics. The file won't actually be deleted

`

667

687

`/// until all file handles are closed. However, marking a file for deletion

`

668

688

`/// 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> {

`

670

691

`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)

`

672

693

`}

`

673

694

``

674

695

`/// Fill the given buffer with as many directory entries as will fit.

`

`@@ -684,21 +705,23 @@ impl File {

`

684

705

`/// A symlink directory is simply an empty directory with some "reparse" metadata attached.

`

685

706

`/// So if you open a link (not its target) and iterate the directory,

`

686

707

`/// you will always iterate an empty directory regardless of the target.

`

687

``

`-

fn fill_dir_buff(&self, buffer: &mut DirBuff, restart: bool) -> io::Result {

`

``

708

`+

#[allow(unused)]

`

``

709

`+

fn fill_dir_buff(&self, buffer: &mut DirBuff, restart: bool) -> Result<bool, WinError> {

`

688

710

`let class =

`

689

711

`if restart { c::FileIdBothDirectoryRestartInfo } else { c::FileIdBothDirectoryInfo };

`

690

712

``

691

713

`unsafe {

`

692

``

`-

let result = cvt(c::GetFileInformationByHandleEx(

`

693

``

`-

self.handle.as_raw_handle(),

`

``

714

`+

let result = c::GetFileInformationByHandleEx(

`

``

715

`+

self.as_raw_handle(),

`

694

716

` class,

`

695

717

` buffer.as_mut_ptr().cast(),

`

696

718

` 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)

`

702

725

`}

`

703

726

`}

`

704

727

`}

`

`@@ -804,62 +827,6 @@ unsafe fn from_maybe_unaligned<'a>(p: *const u16, len: usize) -> Cow<'a, [u16]>

`

804

827

`}

`

805

828

`}

`

806

829

``

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 {

`

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

``

-

863

830

`impl AsInner for File {

`

864

831

`#[inline]

`

865

832

`fn as_inner(&self) -> &Handle {

`

`@@ -1142,114 +1109,22 @@ pub fn rmdir(p: &Path) -> io::Result<()> {

`

1142

1109

`Ok(())

`

1143

1110

`}

`

1144

1111

``

1145

``

`-

/// Open a file or directory without following symlinks.

`

1146

``

`-

fn open_link(path: &Path, access_mode: u32) -> io::Result {

`

``

1112

`+

pub fn remove_dir_all(path: &Path) -> io::Result<()> {

`

``

1113

`+

// Open a file or directory without following symlinks.

`

1147

1114

`let mut opts = OpenOptions::new();

`

1148

``

`-

opts.access_mode(access_mode);

`

``

1115

`+

opts.access_mode(c::FILE_LIST_DIRECTORY);

`

1149

1116

`` // FILE_FLAG_BACKUP_SEMANTICS allows opening directories.

``

1150

1117

`` // FILE_FLAG_OPEN_REPARSE_POINT opens a link instead of its target.

``

1151

1118

` 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)?;

`

1157

1120

``

1158

1121

`// Test if the file is not a directory or a symlink to a directory.

`

1159

1122

`if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {

`

1160

1123

`return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));

`

1161

1124

`}

`

1162

1125

``

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 {

`

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()

`

1253

1128

`}

`

1254

1129

``

1255

1130

`pub fn readlink(path: &Path) -> io::Result {

`