blob: 5e69515b66599943876e8ecc1e5c3545ffe0e8b0 [file]
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) })
}
}