| use crate::alloc::{Layout, alloc, dealloc}; |
| use crate::ffi::c_void; |
| use crate::mem::offset_of; |
| use crate::os::windows::io::{ |
| AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, HandleOrInvalid, IntoRawHandle, |
| OwnedHandle, RawHandle, |
| }; |
| use crate::path::Path; |
| use crate::sys::api::{UnicodeStrRef, WinError}; |
| use crate::sys::fs::windows::debug_path_handle; |
| use crate::sys::fs::{File, FileAttr, OpenOptions}; |
| use crate::sys::handle::Handle; |
| use crate::sys::path::{WCStr, with_native_path}; |
| use crate::sys::{AsInner, FromInner, IntoInner, IoResult, c, to_u16s}; |
| use crate::{fmt, fs, io, ptr}; |
| |
| pub struct Dir { |
| handle: Handle, |
| } |
| |
| fn to_u16s_without_nul(path: &Path) -> io::Result<Vec<u16>> { |
| let mut path = to_u16s(path)?; |
| path.pop(); |
| Ok(path) |
| } |
| |
| /// A wrapper around a raw NtCreateFile call. |
| /// |
| /// This isn't completely safe because `OBJECT_ATTRIBUTES` contains raw pointers. |
| unsafe fn nt_create_file( |
| opts: &OpenOptions, |
| object_attributes: &c::OBJECT_ATTRIBUTES, |
| create_options: c::NTCREATEFILE_CREATE_OPTIONS, |
| ) -> io::Result<Handle> { |
| let mut handle = ptr::null_mut(); |
| let mut io_status = c::IO_STATUS_BLOCK::PENDING; |
| // SYNCHRONIZE is included in FILE_GENERIC_READ, but not GENERIC_READ, so we add it manually |
| let access = opts.get_access_mode()? | c::SYNCHRONIZE; |
| // one of FILE_SYNCHRONOUS_IO_{,NON}ALERT is required for later operations to succeed. |
| let options = create_options | c::FILE_SYNCHRONOUS_IO_NONALERT; |
| let status = unsafe { |
| c::NtCreateFile( |
| &mut handle, |
| access, |
| object_attributes, |
| &mut io_status, |
| ptr::null(), |
| c::FILE_ATTRIBUTE_NORMAL, |
| opts.share_mode, |
| opts.get_disposition()?, |
| options, |
| ptr::null(), |
| 0, |
| ) |
| }; |
| if c::nt_success(status) { |
| // SAFETY: nt_success guarantees that handle is no longer null |
| unsafe { Ok(Handle::from_raw_handle(handle)) } |
| } else { |
| Err(WinError::new(unsafe { c::RtlNtStatusToDosError(status) })).io_result() |
| } |
| } |
| |
| impl Dir { |
| pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<Self> { |
| with_native_path(path, &|path| Self::open_with_native(path, opts)) |
| } |
| |
| pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result<File> { |
| // NtCreateFile will fail if given an absolute path and a non-null RootDirectory |
| if path.is_absolute() { |
| return File::open(path, opts); |
| } |
| let path = to_u16s_without_nul(path)?; |
| self.open_file_native(&path, opts, false).map(|handle| File { handle }) |
| } |
| |
| pub fn remove_file(&self, path: &Path) -> io::Result<()> { |
| let path = to_u16s_without_nul(path)?; |
| self.remove_native(&path, false) |
| } |
| |
| pub fn rename(&self, from: &Path, to_dir: &Self, to: &Path) -> io::Result<()> { |
| let is_dir = from.is_dir(); |
| let from = to_u16s_without_nul(from)?; |
| let to = to_u16s_without_nul(to)?; |
| self.rename_native(&from, to_dir, &to, is_dir) |
| } |
| |
| fn open_with_native(path: &WCStr, opts: &OpenOptions) -> io::Result<Self> { |
| let creation = opts.get_creation_mode()?; |
| let sa = c::SECURITY_ATTRIBUTES { |
| nLength: size_of::<c::SECURITY_ATTRIBUTES>() as u32, |
| lpSecurityDescriptor: ptr::null_mut(), |
| bInheritHandle: opts.inherit_handle as c::BOOL, |
| }; |
| let handle = unsafe { |
| c::CreateFileW( |
| path.as_ptr(), |
| opts.get_access_mode()?, |
| opts.share_mode, |
| &raw const sa, |
| creation, |
| // FILE_FLAG_BACKUP_SEMANTICS is required to open a directory |
| opts.get_flags_and_attributes() | c::FILE_FLAG_BACKUP_SEMANTICS, |
| ptr::null_mut(), |
| ) |
| }; |
| match OwnedHandle::try_from(unsafe { HandleOrInvalid::from_raw_handle(handle) }) { |
| Ok(handle) => Ok(Self { handle: Handle::from_inner(handle) }), |
| Err(_) => Err(io::Error::last_os_error()), |
| } |
| } |
| |
| fn open_file_native(&self, path: &[u16], opts: &OpenOptions, dir: bool) -> io::Result<Handle> { |
| let name = UnicodeStrRef::from_slice(path); |
| let object_attributes = c::OBJECT_ATTRIBUTES { |
| RootDirectory: self.handle.as_raw_handle(), |
| ObjectName: name.as_ptr(), |
| ..c::OBJECT_ATTRIBUTES::with_length() |
| }; |
| let create_opt = if dir { c::FILE_DIRECTORY_FILE } else { c::FILE_NON_DIRECTORY_FILE }; |
| unsafe { nt_create_file(opts, &object_attributes, create_opt) } |
| } |
| |
| fn remove_native(&self, path: &[u16], dir: bool) -> io::Result<()> { |
| let mut opts = OpenOptions::new(); |
| opts.access_mode(c::DELETE); |
| let handle = self.open_file_native(path, &opts, dir)?; |
| File::from_inner(handle).delete().io_result() |
| } |
| |
| fn rename_native(&self, from: &[u16], to_dir: &Self, to: &[u16], dir: bool) -> io::Result<()> { |
| let mut opts = OpenOptions::new(); |
| opts.access_mode(c::DELETE); |
| opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); |
| let handle = self.open_file_native(from, &opts, dir)?; |
| // Calculate the layout of the `FILE_RENAME_INFORMATION` we pass to `NtSetInformationFile` |
| // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size. |
| const too_long_err: io::Error = |
| io::const_error!(io::ErrorKind::InvalidFilename, "Filename too long"); |
| let struct_size = to |
| .len() |
| .checked_mul(2) |
| .and_then(|x| x.checked_add(offset_of!(c::FILE_RENAME_INFORMATION, FileName))) |
| .ok_or(too_long_err)?; |
| let layout = Layout::from_size_align(struct_size, align_of::<c::FILE_RENAME_INFORMATION>()) |
| .map_err(|_| too_long_err)?; |
| let struct_size = u32::try_from(struct_size).map_err(|_| too_long_err)?; |
| let to_byte_len = u32::try_from(to.len() * 2).map_err(|_| too_long_err)?; |
| |
| let file_rename_info; |
| // SAFETY: We allocate enough memory for a full FILE_RENAME_INFORMATION struct and the filename. |
| unsafe { |
| file_rename_info = alloc(layout).cast::<c::FILE_RENAME_INFORMATION>(); |
| if file_rename_info.is_null() { |
| return Err(io::ErrorKind::OutOfMemory.into()); |
| } |
| |
| (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFORMATION_0 { |
| Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, |
| }); |
| |
| (&raw mut (*file_rename_info).RootDirectory).write(to_dir.handle.as_raw_handle()); |
| // Don't include the NULL in the size |
| (&raw mut (*file_rename_info).FileNameLength).write(to_byte_len); |
| |
| to.as_ptr().copy_to_nonoverlapping( |
| (&raw mut (*file_rename_info).FileName).cast::<u16>(), |
| to.len(), |
| ); |
| } |
| |
| let status = unsafe { |
| c::NtSetInformationFile( |
| handle.as_raw_handle(), |
| &mut c::IO_STATUS_BLOCK::default(), |
| file_rename_info.cast::<c_void>(), |
| struct_size, |
| c::FileRenameInformation, |
| ) |
| }; |
| unsafe { dealloc(file_rename_info.cast::<u8>(), layout) }; |
| if c::nt_success(status) { |
| // SAFETY: nt_success guarantees that handle is no longer null |
| Ok(()) |
| } else { |
| Err(WinError::new(unsafe { c::RtlNtStatusToDosError(status) })) |
| } |
| .io_result() |
| } |
| |
| pub fn metadata(&self) -> io::Result<FileAttr> { |
| // Reuse the implementation for files, which should work for all handles. |
| let handle = self.handle.as_raw_handle(); |
| let f = core::mem::ManuallyDrop::new(File { |
| // SAFETY: we borrowed `self` so the handle will not be closed while this function runs. |
| handle: unsafe { Handle::from_raw_handle(handle) }, |
| }); |
| f.file_attr() |
| } |
| } |
| |
| impl fmt::Debug for Dir { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let mut b = debug_path_handle(self.handle.as_handle(), f, "Dir"); |
| b.finish() |
| } |
| } |
| |
| #[unstable(feature = "dirfd", issue = "120426")] |
| impl AsRawHandle for fs::Dir { |
| fn as_raw_handle(&self) -> RawHandle { |
| self.as_inner().handle.as_raw_handle() |
| } |
| } |
| |
| #[unstable(feature = "dirfd", issue = "120426")] |
| impl IntoRawHandle for fs::Dir { |
| fn into_raw_handle(self) -> RawHandle { |
| self.into_inner().handle.into_raw_handle() |
| } |
| } |
| |
| #[unstable(feature = "dirfd", issue = "120426")] |
| impl FromRawHandle for fs::Dir { |
| unsafe fn from_raw_handle(handle: RawHandle) -> Self { |
| Self::from_inner(Dir { handle: unsafe { FromRawHandle::from_raw_handle(handle) } }) |
| } |
| } |
| |
| #[unstable(feature = "dirfd", issue = "120426")] |
| impl AsHandle for fs::Dir { |
| fn as_handle(&self) -> BorrowedHandle<'_> { |
| self.as_inner().handle.as_handle() |
| } |
| } |
| |
| #[unstable(feature = "dirfd", issue = "120426")] |
| impl From<fs::Dir> for OwnedHandle { |
| fn from(value: fs::Dir) -> Self { |
| value.into_inner().handle.into_inner() |
| } |
| } |
| |
| #[unstable(feature = "dirfd", issue = "120426")] |
| impl From<OwnedHandle> for fs::Dir { |
| fn from(value: OwnedHandle) -> Self { |
| Self::from_inner(Dir { handle: Handle::from_inner(value) }) |
| } |
| } |