| 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: u32, | |
| ) -> 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_for_traversal(path: &Path) -> io::Result<Self> { | |
| let mut opts = OpenOptions::new(); | |
| opts.access_mode(c::FILE_TRAVERSE); | |
| 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) | |
| } | |
| pub fn create_dir(&self, path: &Path) -> io::Result<()> { | |
| let mut opts = OpenOptions::new(); | |
| opts.read(true); | |
| opts.write(true); | |
| opts.create_new(true); | |
| self.open_dir(path, &opts).map(|_| ()) | |
| } | |
| pub fn open_dir(&self, path: &Path, opts: &OpenOptions) -> io::Result<Self> { | |
| let path = to_u16s_without_nul(&path)?; | |
| self.open_file_native(&path, &opts, true).map(|handle| Self { handle }) | |
| } | |
| pub fn remove_dir(&self, path: &Path) -> io::Result<()> { | |
| let path = to_u16s_without_nul(&path)?; | |
| self.remove_native(&path, true) | |
| } | |
| 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().cast_mut(), | |
| ..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) }) | |
| } | |
| } |