Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
unsafe impl Send for FindNextFileHandle {}
1#![allow(nonstandard_style)]23use crate::alloc::{Layout, alloc, dealloc};4use crate::borrow::Cow;5use crate::ffi::{OsStr, OsString, c_void};6use crate::fs::TryLockError;7use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};8use crate::mem::{self, MaybeUninit, offset_of};9use crate::os::windows::io::{AsHandle, BorrowedHandle};10use crate::os::windows::prelude::*;11use crate::path::{Path, PathBuf};12use crate::sync::Arc;13use crate::sys::handle::Handle;14use crate::sys::pal::api::{self, WinError, set_file_information_by_handle};15use crate::sys::pal::{IoResult, fill_utf16_buf, to_u16s, truncate_utf16_at_nul};16use crate::sys::path::{WCStr, maybe_verbatim};17use crate::sys::time::SystemTime;18use crate::sys::{Align8, AsInner, FromInner, IntoInner, c, cvt};19use crate::{fmt, ptr, slice};2021mod dir;22pub use dir::Dir;23mod remove_dir_all;24use remove_dir_all::remove_dir_all_iterative;2526pub struct File {27 handle: Handle,28}2930#[derive(Clone)]31pub struct FileAttr {32 attributes: u32,33 creation_time: c::FILETIME,34 last_access_time: c::FILETIME,35 last_write_time: c::FILETIME,36 change_time: Option<c::FILETIME>,37 file_size: u64,38 reparse_tag: u32,39 volume_serial_number: Option<u32>,40 number_of_links: Option<u32>,41 file_index: Option<u64>,42}4344#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]45pub struct FileType {46 is_directory: bool,47 is_symlink: bool,48}4950pub struct ReadDir {51 handle: Option<FindNextFileHandle>,52 root: Arc<PathBuf>,53 first: Option<c::WIN32_FIND_DATAW>,54}5556struct FindNextFileHandle(c::HANDLE);5758unsafe impl Send for FindNextFileHandle {}59unsafe impl Sync for FindNextFileHandle {}6061pub struct DirEntry {62 root: Arc<PathBuf>,63 data: c::WIN32_FIND_DATAW,64}6566unsafe impl Send for OpenOptions {}67unsafe impl Sync for OpenOptions {}6869#[derive(Clone, Debug)]70pub struct OpenOptions {71 // generic72 read: bool,73 write: bool,74 append: bool,75 truncate: bool,76 create: bool,77 create_new: bool,78 // system-specific79 custom_flags: u32,80 access_mode: Option<u32>,81 attributes: u32,82 share_mode: u32,83 security_qos_flags: u32,84 inherit_handle: bool,85 freeze_last_access_time: bool,86 freeze_last_write_time: bool,87}8889#[derive(Clone, PartialEq, Eq, Debug)]90pub struct FilePermissions {91 attrs: u32,92}9394#[derive(Copy, Clone, Debug, Default)]95pub struct FileTimes {96 accessed: Option<c::FILETIME>,97 modified: Option<c::FILETIME>,98 created: Option<c::FILETIME>,99}100101impl fmt::Debug for c::FILETIME {102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {103 let time = ((self.dwHighDateTime as u64) << 32) | self.dwLowDateTime as u64;104 f.debug_tuple("FILETIME").field(&time).finish()105 }106}107108#[derive(Debug)]109pub struct DirBuilder;110111impl fmt::Debug for ReadDir {112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {113 // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame.114 // Thus the result will be e g 'ReadDir("C:\")'115 fmt::Debug::fmt(&*self.root, f)116 }117}118119impl Iterator for ReadDir {120 type Item = io::Result<DirEntry>;121 fn next(&mut self) -> Option<io::Result<DirEntry>> {122 let Some(handle) = self.handle.as_ref() else {123 // This iterator was initialized with an `INVALID_HANDLE_VALUE` as its handle.124 // Simply return `None` because this is only the case when `FindFirstFileExW` in125 // the construction of this iterator returns `ERROR_FILE_NOT_FOUND` which means126 // no matchhing files can be found.127 return None;128 };129 if let Some(first) = self.first.take() {130 if let Some(e) = DirEntry::new(&self.root, &first) {131 return Some(Ok(e));132 }133 }134 unsafe {135 let mut wfd = mem::zeroed();136 loop {137 if c::FindNextFileW(handle.0, &mut wfd) == 0 {138 self.handle = None;139 match api::get_last_error() {140 WinError::NO_MORE_FILES => return None,141 WinError { code } => {142 return Some(Err(Error::from_raw_os_error(code as i32)));143 }144 }145 }146 if let Some(e) = DirEntry::new(&self.root, &wfd) {147 return Some(Ok(e));148 }149 }150 }151 }152}153154impl Drop for FindNextFileHandle {155 fn drop(&mut self) {156 let r = unsafe { c::FindClose(self.0) };157 debug_assert!(r != 0);158 }159}160161impl DirEntry {162 fn new(root: &Arc<PathBuf>, wfd: &c::WIN32_FIND_DATAW) -> Option<DirEntry> {163 match &wfd.cFileName[0..3] {164 // check for '.' and '..'165 &[46, 0, ..] | &[46, 46, 0, ..] => return None,166 _ => {}167 }168169 Some(DirEntry { root: root.clone(), data: *wfd })170 }171172 pub fn path(&self) -> PathBuf {173 self.root.join(self.file_name())174 }175176 pub fn file_name(&self) -> OsString {177 let filename = truncate_utf16_at_nul(&self.data.cFileName);178 OsString::from_wide(filename)179 }180181 pub fn file_type(&self) -> io::Result<FileType> {182 Ok(FileType::new(183 self.data.dwFileAttributes,184 /* reparse_tag = */ self.data.dwReserved0,185 ))186 }187188 pub fn metadata(&self) -> io::Result<FileAttr> {189 Ok(self.data.into())190 }191}192193impl OpenOptions {194 pub fn new() -> OpenOptions {195 OpenOptions {196 // generic197 read: false,198 write: false,199 append: false,200 truncate: false,201 create: false,202 create_new: false,203 // system-specific204 custom_flags: 0,205 access_mode: None,206 share_mode: c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE,207 attributes: 0,208 security_qos_flags: 0,209 inherit_handle: false,210 freeze_last_access_time: false,211 freeze_last_write_time: false,212 }213 }214215 pub fn read(&mut self, read: bool) {216 self.read = read;217 }218 pub fn write(&mut self, write: bool) {219 self.write = write;220 }221 pub fn append(&mut self, append: bool) {222 self.append = append;223 }224 pub fn truncate(&mut self, truncate: bool) {225 self.truncate = truncate;226 }227 pub fn create(&mut self, create: bool) {228 self.create = create;229 }230 pub fn create_new(&mut self, create_new: bool) {231 self.create_new = create_new;232 }233234 pub fn custom_flags(&mut self, flags: u32) {235 self.custom_flags = flags;236 }237 pub fn access_mode(&mut self, access_mode: u32) {238 self.access_mode = Some(access_mode);239 }240 pub fn share_mode(&mut self, share_mode: u32) {241 self.share_mode = share_mode;242 }243 pub fn attributes(&mut self, attrs: u32) {244 self.attributes = attrs;245 }246 pub fn security_qos_flags(&mut self, flags: u32) {247 // We have to set `SECURITY_SQOS_PRESENT` here, because one of the valid flags we can248 // receive is `SECURITY_ANONYMOUS = 0x0`, which we can't check for later on.249 self.security_qos_flags = flags | c::SECURITY_SQOS_PRESENT;250 }251 pub fn inherit_handle(&mut self, inherit: bool) {252 self.inherit_handle = inherit;253 }254 pub fn freeze_last_access_time(&mut self, freeze: bool) {255 self.freeze_last_access_time = freeze;256 }257 pub fn freeze_last_write_time(&mut self, freeze: bool) {258 self.freeze_last_write_time = freeze;259 }260261 fn get_access_mode(&self) -> io::Result<u32> {262 match (self.read, self.write, self.append, self.access_mode) {263 (.., Some(mode)) => Ok(mode),264 (true, false, false, None) => Ok(c::GENERIC_READ),265 (false, true, false, None) => Ok(c::GENERIC_WRITE),266 (true, true, false, None) => Ok(c::GENERIC_READ | c::GENERIC_WRITE),267 (false, _, true, None) => Ok(c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA),268 (true, _, true, None) => {269 Ok(c::GENERIC_READ | (c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA))270 }271 (false, false, false, None) => {272 // If no access mode is set, check if any creation flags are set273 // to provide a more descriptive error message274 if self.create || self.create_new || self.truncate {275 Err(io::Error::new(276 io::ErrorKind::InvalidInput,277 "creating or truncating a file requires write or append access",278 ))279 } else {280 Err(io::Error::new(281 io::ErrorKind::InvalidInput,282 "must specify at least one of read, write, or append access",283 ))284 }285 }286 }287 }288289 fn get_cmode_disposition(&self) -> io::Result<(u32, u32)> {290 match (self.write, self.append) {291 (true, false) => {}292 (false, false) => {293 if self.truncate || self.create || self.create_new {294 return Err(io::Error::new(295 io::ErrorKind::InvalidInput,296 "creating or truncating a file requires write or append access",297 ));298 }299 }300 (_, true) => {301 if self.truncate && !self.create_new {302 return Err(io::Error::new(303 io::ErrorKind::InvalidInput,304 "creating or truncating a file requires write or append access",305 ));306 }307 }308 }309310 Ok(match (self.create, self.truncate, self.create_new) {311 (false, false, false) => (c::OPEN_EXISTING, c::FILE_OPEN),312 (true, false, false) => (c::OPEN_ALWAYS, c::FILE_OPEN_IF),313 (false, true, false) => (c::TRUNCATE_EXISTING, c::FILE_OVERWRITE),314 // `CREATE_ALWAYS` has weird semantics so we emulate it using315 // `OPEN_ALWAYS` and a manual truncation step. See #115745.316 (true, true, false) => (c::OPEN_ALWAYS, c::FILE_OVERWRITE_IF),317 (_, _, true) => (c::CREATE_NEW, c::FILE_CREATE),318 })319 }320321 fn get_creation_mode(&self) -> io::Result<u32> {322 self.get_cmode_disposition().map(|(mode, _)| mode)323 }324325 fn get_disposition(&self) -> io::Result<u32> {326 self.get_cmode_disposition().map(|(_, mode)| mode)327 }328329 fn get_flags_and_attributes(&self) -> u32 {330 self.custom_flags331 | self.attributes332 | self.security_qos_flags333 | if self.create_new { c::FILE_FLAG_OPEN_REPARSE_POINT } else { 0 }334 }335}336337impl File {338 pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {339 let path = maybe_verbatim(path)?;340 // SAFETY: maybe_verbatim returns null-terminated strings341 let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) };342 Self::open_native(&path, opts)343 }344345 fn open_native(path: &WCStr, opts: &OpenOptions) -> io::Result<File> {346 let creation = opts.get_creation_mode()?;347 let sa = c::SECURITY_ATTRIBUTES {348 nLength: size_of::<c::SECURITY_ATTRIBUTES>() as u32,349 lpSecurityDescriptor: ptr::null_mut(),350 bInheritHandle: opts.inherit_handle as c::BOOL,351 };352 let handle = unsafe {353 c::CreateFileW(354 path.as_ptr(),355 opts.get_access_mode()?,356 opts.share_mode,357 if opts.inherit_handle { &sa } else { ptr::null() },358 creation,359 opts.get_flags_and_attributes(),360 ptr::null_mut(),361 )362 };363 let handle = unsafe { HandleOrInvalid::from_raw_handle(handle) };364 if let Ok(handle) = OwnedHandle::try_from(handle) {365 if opts.freeze_last_access_time || opts.freeze_last_write_time {366 let file_time =367 c::FILETIME { dwLowDateTime: 0xFFFFFFFF, dwHighDateTime: 0xFFFFFFFF };368 cvt(unsafe {369 c::SetFileTime(370 handle.as_raw_handle(),371 core::ptr::null(),372 if opts.freeze_last_access_time { &file_time } else { core::ptr::null() },373 if opts.freeze_last_write_time { &file_time } else { core::ptr::null() },374 )375 })?;376 }377 // Manual truncation. See #115745.378 if opts.truncate379 && creation == c::OPEN_ALWAYS380 && api::get_last_error() == WinError::ALREADY_EXISTS381 {382 // This first tries `FileAllocationInfo` but falls back to383 // `FileEndOfFileInfo` in order to support WINE.384 // If WINE gains support for FileAllocationInfo, we should385 // remove the fallback.386 let alloc = c::FILE_ALLOCATION_INFO { AllocationSize: 0 };387 set_file_information_by_handle(handle.as_raw_handle(), &alloc)388 .or_else(|_| {389 let eof = c::FILE_END_OF_FILE_INFO { EndOfFile: 0 };390 set_file_information_by_handle(handle.as_raw_handle(), &eof)391 })392 .io_result()?;393 }394 Ok(File { handle: Handle::from_inner(handle) })395 } else {396 Err(Error::last_os_error())397 }398 }399400 pub fn fsync(&self) -> io::Result<()> {401 cvt(unsafe { c::FlushFileBuffers(self.handle.as_raw_handle()) })?;402 Ok(())403 }404405 pub fn datasync(&self) -> io::Result<()> {406 self.fsync()407 }408409 fn acquire_lock(&self, flags: c::LOCK_FILE_FLAGS) -> io::Result<()> {410 unsafe {411 let mut overlapped: c::OVERLAPPED = mem::zeroed();412 let event = c::CreateEventW(ptr::null_mut(), c::FALSE, c::FALSE, ptr::null());413 if event.is_null() {414 return Err(io::Error::last_os_error());415 }416 overlapped.hEvent = event;417 let lock_result = cvt(c::LockFileEx(418 self.handle.as_raw_handle(),419 flags,420 0,421 u32::MAX,422 u32::MAX,423 &mut overlapped,424 ));425426 let final_result = match lock_result {427 Ok(_) => Ok(()),428 Err(err) => {429 if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) {430 // Wait for the lock to be acquired, and get the lock operation status.431 // This can happen asynchronously, if the file handle was opened for async IO432 let mut bytes_transferred = 0;433 cvt(c::GetOverlappedResult(434 self.handle.as_raw_handle(),435 &mut overlapped,436 &mut bytes_transferred,437 c::TRUE,438 ))439 .map(|_| ())440 } else {441 Err(err)442 }443 }444 };445 c::CloseHandle(overlapped.hEvent);446 final_result447 }448 }449450 pub fn lock(&self) -> io::Result<()> {451 self.acquire_lock(c::LOCKFILE_EXCLUSIVE_LOCK)452 }453454 pub fn lock_shared(&self) -> io::Result<()> {455 self.acquire_lock(0)456 }457458 pub fn try_lock(&self) -> Result<(), TryLockError> {459 let result = cvt(unsafe {460 let mut overlapped = mem::zeroed();461 c::LockFileEx(462 self.handle.as_raw_handle(),463 c::LOCKFILE_EXCLUSIVE_LOCK | c::LOCKFILE_FAIL_IMMEDIATELY,464 0,465 u32::MAX,466 u32::MAX,467 &mut overlapped,468 )469 });470471 match result {472 Ok(_) => Ok(()),473 Err(err) if err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) => {474 Err(TryLockError::WouldBlock)475 }476 Err(err) => Err(TryLockError::Error(err)),477 }478 }479480 pub fn try_lock_shared(&self) -> Result<(), TryLockError> {481 let result = cvt(unsafe {482 let mut overlapped = mem::zeroed();483 c::LockFileEx(484 self.handle.as_raw_handle(),485 c::LOCKFILE_FAIL_IMMEDIATELY,486 0,487 u32::MAX,488 u32::MAX,489 &mut overlapped,490 )491 });492493 match result {494 Ok(_) => Ok(()),495 Err(err) if err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) => {496 Err(TryLockError::WouldBlock)497 }498 Err(err) => Err(TryLockError::Error(err)),499 }500 }501502 pub fn unlock(&self) -> io::Result<()> {503 // Unlock the handle twice because LockFileEx() allows a file handle to acquire504 // both an exclusive and shared lock, in which case the documentation states that:505 // "...two unlock operations are necessary to unlock the region; the first unlock operation506 // unlocks the exclusive lock, the second unlock operation unlocks the shared lock"507 cvt(unsafe { c::UnlockFile(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX) })?;508 let result =509 cvt(unsafe { c::UnlockFile(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX) });510 match result {511 Ok(_) => Ok(()),512 Err(err) if err.raw_os_error() == Some(c::ERROR_NOT_LOCKED as i32) => Ok(()),513 Err(err) => Err(err),514 }515 }516517 pub fn truncate(&self, size: u64) -> io::Result<()> {518 let info = c::FILE_END_OF_FILE_INFO { EndOfFile: size as i64 };519 api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()520 }521522 #[cfg(not(target_vendor = "uwp"))]523 pub fn file_attr(&self) -> io::Result<FileAttr> {524 unsafe {525 let mut info: c::BY_HANDLE_FILE_INFORMATION = mem::zeroed();526 cvt(c::GetFileInformationByHandle(self.handle.as_raw_handle(), &mut info))?;527 let mut reparse_tag = 0;528 if info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {529 let mut attr_tag: c::FILE_ATTRIBUTE_TAG_INFO = mem::zeroed();530 cvt(c::GetFileInformationByHandleEx(531 self.handle.as_raw_handle(),532 c::FileAttributeTagInfo,533 (&raw mut attr_tag).cast(),534 size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(),535 ))?;536 if attr_tag.FileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {537 reparse_tag = attr_tag.ReparseTag;538 }539 }540 Ok(FileAttr {541 attributes: info.dwFileAttributes,542 creation_time: info.ftCreationTime,543 last_access_time: info.ftLastAccessTime,544 last_write_time: info.ftLastWriteTime,545 change_time: None, // Only available in FILE_BASIC_INFO546 file_size: (info.nFileSizeLow as u64) | ((info.nFileSizeHigh as u64) << 32),547 reparse_tag,548 volume_serial_number: Some(info.dwVolumeSerialNumber),549 number_of_links: Some(info.nNumberOfLinks),550 file_index: Some(551 (info.nFileIndexLow as u64) | ((info.nFileIndexHigh as u64) << 32),552 ),553 })554 }555 }556557 #[cfg(target_vendor = "uwp")]558 pub fn file_attr(&self) -> io::Result<FileAttr> {559 unsafe {560 let mut info: c::FILE_BASIC_INFO = mem::zeroed();561 let size = size_of_val(&info);562 cvt(c::GetFileInformationByHandleEx(563 self.handle.as_raw_handle(),564 c::FileBasicInfo,565 (&raw mut info) as *mut c_void,566 size as u32,567 ))?;568 let mut attr = FileAttr {569 attributes: info.FileAttributes,570 creation_time: c::FILETIME {571 dwLowDateTime: info.CreationTime as u32,572 dwHighDateTime: (info.CreationTime >> 32) as u32,573 },574 last_access_time: c::FILETIME {575 dwLowDateTime: info.LastAccessTime as u32,576 dwHighDateTime: (info.LastAccessTime >> 32) as u32,577 },578 last_write_time: c::FILETIME {579 dwLowDateTime: info.LastWriteTime as u32,580 dwHighDateTime: (info.LastWriteTime >> 32) as u32,581 },582 change_time: Some(c::FILETIME {583 dwLowDateTime: info.ChangeTime as u32,584 dwHighDateTime: (info.ChangeTime >> 32) as u32,585 }),586 file_size: 0,587 reparse_tag: 0,588 volume_serial_number: None,589 number_of_links: None,590 file_index: None,591 };592 let mut info: c::FILE_STANDARD_INFO = mem::zeroed();593 let size = size_of_val(&info);594 cvt(c::GetFileInformationByHandleEx(595 self.handle.as_raw_handle(),596 c::FileStandardInfo,597 (&raw mut info) as *mut c_void,598 size as u32,599 ))?;600 attr.file_size = info.AllocationSize as u64;601 attr.number_of_links = Some(info.NumberOfLinks);602 if attr.attributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {603 let mut attr_tag: c::FILE_ATTRIBUTE_TAG_INFO = mem::zeroed();604 cvt(c::GetFileInformationByHandleEx(605 self.handle.as_raw_handle(),606 c::FileAttributeTagInfo,607 (&raw mut attr_tag).cast(),608 size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(),609 ))?;610 if attr_tag.FileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {611 attr.reparse_tag = attr_tag.ReparseTag;612 }613 }614 Ok(attr)615 }616 }617618 pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {619 self.handle.read(buf)620 }621622 pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {623 self.handle.read_vectored(bufs)624 }625626 #[inline]627 pub fn is_read_vectored(&self) -> bool {628 self.handle.is_read_vectored()629 }630631 pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {632 self.handle.read_at(buf, offset)633 }634635 pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> {636 self.handle.read_buf(cursor)637 }638639 pub fn read_buf_at(&self, cursor: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {640 self.handle.read_buf_at(cursor, offset)641 }642643 pub fn write(&self, buf: &[u8]) -> io::Result<usize> {644 self.handle.write(buf)645 }646647 pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {648 self.handle.write_vectored(bufs)649 }650651 #[inline]652 pub fn is_write_vectored(&self) -> bool {653 self.handle.is_write_vectored()654 }655656 pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {657 self.handle.write_at(buf, offset)658 }659660 pub fn flush(&self) -> io::Result<()> {661 Ok(())662 }663664 pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {665 let (whence, pos) = match pos {666 // Casting to `i64` is fine, `SetFilePointerEx` reinterprets this667 // integer as `u64`.668 SeekFrom::Start(n) => (c::FILE_BEGIN, n as i64),669 SeekFrom::End(n) => (c::FILE_END, n),670 SeekFrom::Current(n) => (c::FILE_CURRENT, n),671 };672 let pos = pos as i64;673 let mut newpos = 0;674 cvt(unsafe { c::SetFilePointerEx(self.handle.as_raw_handle(), pos, &mut newpos, whence) })?;675 Ok(newpos as u64)676 }677678 pub fn size(&self) -> Option<io::Result<u64>> {679 let mut result = 0;680 Some(681 cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })682 .map(|_| result as u64),683 )684 }685686 pub fn tell(&self) -> io::Result<u64> {687 self.seek(SeekFrom::Current(0))688 }689690 pub fn duplicate(&self) -> io::Result<File> {691 Ok(Self { handle: self.handle.try_clone()? })692 }693694 // NB: returned pointer is derived from `space`, and has provenance to695 // match. A raw pointer is returned rather than a reference in order to696 // avoid narrowing provenance to the actual `REPARSE_DATA_BUFFER`.697 fn reparse_point(698 &self,699 space: &mut Align8<[MaybeUninit<u8>]>,700 ) -> io::Result<(u32, *mut c::REPARSE_DATA_BUFFER)> {701 unsafe {702 let mut bytes = 0;703 cvt({704 // Grab this in advance to avoid it invalidating the pointer705 // we get from `space.0.as_mut_ptr()`.706 let len = space.0.len();707 c::DeviceIoControl(708 self.handle.as_raw_handle(),709 c::FSCTL_GET_REPARSE_POINT,710 ptr::null_mut(),711 0,712 space.0.as_mut_ptr().cast(),713 len as u32,714 &mut bytes,715 ptr::null_mut(),716 )717 })?;718 const _: () = assert!(align_of::<c::REPARSE_DATA_BUFFER>() <= 8);719 Ok((bytes, space.0.as_mut_ptr().cast::<c::REPARSE_DATA_BUFFER>()))720 }721 }722723 fn readlink(&self) -> io::Result<PathBuf> {724 let mut space =725 Align8([MaybeUninit::<u8>::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]);726 let (_bytes, buf) = self.reparse_point(&mut space)?;727 unsafe {728 let (path_buffer, subst_off, subst_len, relative) = match (*buf).ReparseTag {729 c::IO_REPARSE_TAG_SYMLINK => {730 let info: *mut c::SYMBOLIC_LINK_REPARSE_BUFFER = (&raw mut (*buf).rest).cast();731 assert!(info.is_aligned());732 (733 (&raw mut (*info).PathBuffer).cast::<u16>(),734 (*info).SubstituteNameOffset / 2,735 (*info).SubstituteNameLength / 2,736 (*info).Flags & c::SYMLINK_FLAG_RELATIVE != 0,737 )738 }739 c::IO_REPARSE_TAG_MOUNT_POINT => {740 let info: *mut c::MOUNT_POINT_REPARSE_BUFFER = (&raw mut (*buf).rest).cast();741 assert!(info.is_aligned());742 (743 (&raw mut (*info).PathBuffer).cast::<u16>(),744 (*info).SubstituteNameOffset / 2,745 (*info).SubstituteNameLength / 2,746 false,747 )748 }749 _ => {750 return Err(io::const_error!(751 io::ErrorKind::Uncategorized,752 "Unsupported reparse point type",753 ));754 }755 };756 let subst_ptr = path_buffer.add(subst_off.into());757 let subst = slice::from_raw_parts_mut(subst_ptr, subst_len as usize);758 // Absolute paths start with an NT internal namespace prefix `\??\`759 // We should not let it leak through.760 if !relative && subst.starts_with(&[92u16, 63u16, 63u16, 92u16]) {761 // Turn `\??\` into `\\?\` (a verbatim path).762 subst[1] = b'\\' as u16;763 // Attempt to convert to a more user-friendly path.764 let user = crate::sys::args::from_wide_to_user_path(765 subst.iter().copied().chain([0]).collect(),766 )?;767 Ok(PathBuf::from(OsString::from_wide(user.strip_suffix(&[0]).unwrap_or(&user))))768 } else {769 Ok(PathBuf::from(OsString::from_wide(subst)))770 }771 }772 }773774 pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {775 let info = c::FILE_BASIC_INFO {776 CreationTime: 0,777 LastAccessTime: 0,778 LastWriteTime: 0,779 ChangeTime: 0,780 FileAttributes: perm.attrs,781 };782 api::set_file_information_by_handle(self.handle.as_raw_handle(), &info).io_result()783 }784785 pub fn set_times(&self, times: FileTimes) -> io::Result<()> {786 let is_zero = |t: c::FILETIME| t.dwLowDateTime == 0 && t.dwHighDateTime == 0;787 if times.accessed.map_or(false, is_zero)788 || times.modified.map_or(false, is_zero)789 || times.created.map_or(false, is_zero)790 {791 return Err(io::const_error!(792 io::ErrorKind::InvalidInput,793 "cannot set file timestamp to 0",794 ));795 }796 let is_max = |t: c::FILETIME| t.dwLowDateTime == u32::MAX && t.dwHighDateTime == u32::MAX;797 if times.accessed.map_or(false, is_max)798 || times.modified.map_or(false, is_max)799 || times.created.map_or(false, is_max)800 {801 return Err(io::const_error!(802 io::ErrorKind::InvalidInput,803 "cannot set file timestamp to 0xFFFF_FFFF_FFFF_FFFF",804 ));805 }806 cvt(unsafe {807 let created =808 times.created.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());809 let accessed =810 times.accessed.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());811 let modified =812 times.modified.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());813 c::SetFileTime(self.as_raw_handle(), created, accessed, modified)814 })?;815 Ok(())816 }817818 /// Gets only basic file information such as attributes and file times.819 fn basic_info(&self) -> io::Result<c::FILE_BASIC_INFO> {820 unsafe {821 let mut info: c::FILE_BASIC_INFO = mem::zeroed();822 let size = size_of_val(&info);823 cvt(c::GetFileInformationByHandleEx(824 self.handle.as_raw_handle(),825 c::FileBasicInfo,826 (&raw mut info) as *mut c_void,827 size as u32,828 ))?;829 Ok(info)830 }831 }832833 /// Deletes the file, consuming the file handle to ensure the delete occurs834 /// as immediately as possible.835 /// This attempts to use `posix_delete` but falls back to `win32_delete`836 /// if that is not supported by the filesystem.837 #[allow(unused)]838 fn delete(self) -> Result<(), WinError> {839 // If POSIX delete is not supported for this filesystem then fallback to win32 delete.840 match self.posix_delete() {841 Err(WinError::INVALID_PARAMETER)842 | Err(WinError::NOT_SUPPORTED)843 | Err(WinError::INVALID_FUNCTION) => self.win32_delete(),844 result => result,845 }846 }847848 /// Delete using POSIX semantics.849 ///850 /// Files will be deleted as soon as the handle is closed. This is supported851 /// for Windows 10 1607 (aka RS1) and later. However some filesystem852 /// drivers will not support it even then, e.g. FAT32.853 ///854 /// If the operation is not supported for this filesystem or OS version855 /// then errors will be `ERROR_NOT_SUPPORTED` or `ERROR_INVALID_PARAMETER`.856 #[allow(unused)]857 fn posix_delete(&self) -> Result<(), WinError> {858 let info = c::FILE_DISPOSITION_INFO_EX {859 Flags: c::FILE_DISPOSITION_FLAG_DELETE860 | c::FILE_DISPOSITION_FLAG_POSIX_SEMANTICS861 | c::FILE_DISPOSITION_FLAG_IGNORE_READONLY_ATTRIBUTE,862 };863 api::set_file_information_by_handle(self.handle.as_raw_handle(), &info)864 }865866 /// Delete a file using win32 semantics. The file won't actually be deleted867 /// until all file handles are closed. However, marking a file for deletion868 /// will prevent anyone from opening a new handle to the file.869 #[allow(unused)]870 fn win32_delete(&self) -> Result<(), WinError> {871 let info = c::FILE_DISPOSITION_INFO { DeleteFile: true };872 api::set_file_information_by_handle(self.handle.as_raw_handle(), &info)873 }874875 /// Fill the given buffer with as many directory entries as will fit.876 /// This will remember its position and continue from the last call unless877 /// `restart` is set to `true`.878 ///879 /// The returned bool indicates if there are more entries or not.880 /// It is an error if `self` is not a directory.881 ///882 /// # Symlinks and other reparse points883 ///884 /// On Windows a file is either a directory or a non-directory.885 /// A symlink directory is simply an empty directory with some "reparse" metadata attached.886 /// So if you open a link (not its target) and iterate the directory,887 /// you will always iterate an empty directory regardless of the target.888 #[allow(unused)]889 fn fill_dir_buff(&self, buffer: &mut DirBuff, restart: bool) -> Result<bool, WinError> {890 let class =891 if restart { c::FileIdBothDirectoryRestartInfo } else { c::FileIdBothDirectoryInfo };892893 unsafe {894 let result = c::GetFileInformationByHandleEx(895 self.as_raw_handle(),896 class,897 buffer.as_mut_ptr().cast(),898 buffer.capacity() as _,899 );900 if result == 0 {901 let err = api::get_last_error();902 if err.code == c::ERROR_NO_MORE_FILES { Ok(false) } else { Err(err) }903 } else {904 Ok(true)905 }906 }907 }908}909910/// A buffer for holding directory entries.911struct DirBuff {912 buffer: Box<Align8<[MaybeUninit<u8>; Self::BUFFER_SIZE]>>,913}914impl DirBuff {915 const BUFFER_SIZE: usize = 1024;916 fn new() -> Self {917 Self {918 // Safety: `Align8<[MaybeUninit<u8>; N]>` does not need919 // initialization.920 buffer: unsafe { Box::new_uninit().assume_init() },921 }922 }923 fn capacity(&self) -> usize {924 self.buffer.0.len()925 }926 fn as_mut_ptr(&mut self) -> *mut u8 {927 self.buffer.0.as_mut_ptr().cast()928 }929 /// Returns a `DirBuffIter`.930 fn iter(&self) -> DirBuffIter<'_> {931 DirBuffIter::new(self)932 }933}934impl AsRef<[MaybeUninit<u8>]> for DirBuff {935 fn as_ref(&self) -> &[MaybeUninit<u8>] {936 &self.buffer.0937 }938}939940/// An iterator over entries stored in a `DirBuff`.941///942/// Currently only returns file names (UTF-16 encoded).943struct DirBuffIter<'a> {944 buffer: Option<&'a [MaybeUninit<u8>]>,945 cursor: usize,946}947impl<'a> DirBuffIter<'a> {948 fn new(buffer: &'a DirBuff) -> Self {949 Self { buffer: Some(buffer.as_ref()), cursor: 0 }950 }951}952impl<'a> Iterator for DirBuffIter<'a> {953 type Item = (Cow<'a, [u16]>, bool);954 fn next(&mut self) -> Option<Self::Item> {955 let buffer = &self.buffer?[self.cursor..];956957 // Get the name and next entry from the buffer.958 // SAFETY:959 // - The buffer contains a `FILE_ID_BOTH_DIR_INFO` struct but the last960 // field (the file name) is unsized. So an offset has to be used to961 // get the file name slice.962 // - The OS has guaranteed initialization of the fields of963 // `FILE_ID_BOTH_DIR_INFO` and the trailing filename (for at least964 // `FileNameLength` bytes)965 let (name, is_directory, next_entry) = unsafe {966 let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();967 // While this is guaranteed to be aligned in documentation for968 // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_both_dir_info969 // it does not seem that reality is so kind, and assuming this970 // caused crashes in some cases (https://github.com/rust-lang/rust/issues/104530)971 // presumably, this can be blamed on buggy filesystem drivers, but who knows.972 let next_entry = (&raw const (*info).NextEntryOffset).read_unaligned() as usize;973 let length = (&raw const (*info).FileNameLength).read_unaligned() as usize;974 let attrs = (&raw const (*info).FileAttributes).read_unaligned();975 let name = from_maybe_unaligned(976 (&raw const (*info).FileName).cast::<u16>(),977 length / size_of::<u16>(),978 );979 let is_directory = (attrs & c::FILE_ATTRIBUTE_DIRECTORY) != 0;980981 (name, is_directory, next_entry)982 };983984 if next_entry == 0 {985 self.buffer = None986 } else {987 self.cursor += next_entry988 }989990 // Skip `.` and `..` pseudo entries.991 const DOT: u16 = b'.' as u16;992 match &name[..] {993 [DOT] | [DOT, DOT] => self.next(),994 _ => Some((name, is_directory)),995 }996 }997}998999unsafe fn from_maybe_unaligned<'a>(p: *const u16, len: usize) -> Cow<'a, [u16]> {1000 unsafe {1001 if p.is_aligned() {1002 Cow::Borrowed(crate::slice::from_raw_parts(p, len))1003 } else {1004 Cow::Owned((0..len).map(|i| p.add(i).read_unaligned()).collect())1005 }1006 }1007}10081009impl AsInner<Handle> for File {1010 #[inline]1011 fn as_inner(&self) -> &Handle {1012 &self.handle1013 }1014}10151016impl IntoInner<Handle> for File {1017 fn into_inner(self) -> Handle {1018 self.handle1019 }1020}10211022impl FromInner<Handle> for File {1023 fn from_inner(handle: Handle) -> File {1024 File { handle }1025 }1026}10271028impl AsHandle for File {1029 fn as_handle(&self) -> BorrowedHandle<'_> {1030 self.as_inner().as_handle()1031 }1032}10331034impl AsRawHandle for File {1035 fn as_raw_handle(&self) -> RawHandle {1036 self.as_inner().as_raw_handle()1037 }1038}10391040impl IntoRawHandle for File {1041 fn into_raw_handle(self) -> RawHandle {1042 self.into_inner().into_raw_handle()1043 }1044}10451046impl FromRawHandle for File {1047 unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {1048 unsafe {1049 Self { handle: FromInner::from_inner(FromRawHandle::from_raw_handle(raw_handle)) }1050 }1051 }1052}10531054fn debug_path_handle<'a, 'b>(1055 handle: BorrowedHandle<'a>,1056 f: &'a mut fmt::Formatter<'b>,1057 name: &str,1058) -> fmt::DebugStruct<'a, 'b> {1059 // FIXME(#24570): add more info here (e.g., mode)1060 let mut b = f.debug_struct(name);1061 b.field("handle", &handle.as_raw_handle());1062 if let Ok(path) = get_path(handle) {1063 b.field("path", &path);1064 }1065 b1066}10671068impl fmt::Debug for File {1069 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {1070 let mut b = debug_path_handle(self.handle.as_handle(), f, "File");1071 b.finish()1072 }1073}10741075impl FileAttr {1076 pub fn size(&self) -> u64 {1077 self.file_size1078 }10791080 pub fn perm(&self) -> FilePermissions {1081 FilePermissions { attrs: self.attributes }1082 }10831084 pub fn attrs(&self) -> u32 {1085 self.attributes1086 }10871088 pub fn file_type(&self) -> FileType {1089 FileType::new(self.attributes, self.reparse_tag)1090 }10911092 pub fn modified(&self) -> io::Result<SystemTime> {1093 Ok(SystemTime::from(self.last_write_time))1094 }10951096 pub fn accessed(&self) -> io::Result<SystemTime> {1097 Ok(SystemTime::from(self.last_access_time))1098 }10991100 pub fn created(&self) -> io::Result<SystemTime> {1101 Ok(SystemTime::from(self.creation_time))1102 }11031104 pub fn modified_u64(&self) -> u64 {1105 to_u64(&self.last_write_time)1106 }11071108 pub fn accessed_u64(&self) -> u64 {1109 to_u64(&self.last_access_time)1110 }11111112 pub fn created_u64(&self) -> u64 {1113 to_u64(&self.creation_time)1114 }11151116 pub fn changed_u64(&self) -> Option<u64> {1117 self.change_time.as_ref().map(|c| to_u64(c))1118 }11191120 pub fn volume_serial_number(&self) -> Option<u32> {1121 self.volume_serial_number1122 }11231124 pub fn number_of_links(&self) -> Option<u32> {1125 self.number_of_links1126 }11271128 pub fn file_index(&self) -> Option<u64> {1129 self.file_index1130 }1131}1132impl From<c::WIN32_FIND_DATAW> for FileAttr {1133 fn from(wfd: c::WIN32_FIND_DATAW) -> Self {1134 FileAttr {1135 attributes: wfd.dwFileAttributes,1136 creation_time: wfd.ftCreationTime,1137 last_access_time: wfd.ftLastAccessTime,1138 last_write_time: wfd.ftLastWriteTime,1139 change_time: None,1140 file_size: ((wfd.nFileSizeHigh as u64) << 32) | (wfd.nFileSizeLow as u64),1141 reparse_tag: if wfd.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {1142 // reserved unless this is a reparse point1143 wfd.dwReserved01144 } else {1145 01146 },1147 volume_serial_number: None,1148 number_of_links: None,1149 file_index: None,1150 }1151 }1152}11531154fn to_u64(ft: &c::FILETIME) -> u64 {1155 (ft.dwLowDateTime as u64) | ((ft.dwHighDateTime as u64) << 32)1156}11571158impl FilePermissions {1159 pub fn readonly(&self) -> bool {1160 self.attrs & c::FILE_ATTRIBUTE_READONLY != 01161 }11621163 pub fn set_readonly(&mut self, readonly: bool) {1164 if readonly {1165 self.attrs |= c::FILE_ATTRIBUTE_READONLY;1166 } else {1167 self.attrs &= !c::FILE_ATTRIBUTE_READONLY;1168 }1169 }11701171 pub fn file_attributes(&self) -> u32 {1172 self.attrs as u321173 }1174}11751176impl FromInner<u32> for FilePermissions {1177 fn from_inner(attrs: u32) -> FilePermissions {1178 FilePermissions { attrs }1179 }1180}11811182impl FileTimes {1183 pub fn set_accessed(&mut self, t: SystemTime) {1184 self.accessed = Some(t.into_inner());1185 }11861187 pub fn set_modified(&mut self, t: SystemTime) {1188 self.modified = Some(t.into_inner());1189 }11901191 pub fn set_created(&mut self, t: SystemTime) {1192 self.created = Some(t.into_inner());1193 }1194}11951196impl FileType {1197 fn new(attributes: u32, reparse_tag: u32) -> FileType {1198 let is_directory = attributes & c::FILE_ATTRIBUTE_DIRECTORY != 0;1199 let is_symlink = {1200 let is_reparse_point = attributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0;1201 let is_reparse_tag_name_surrogate = reparse_tag & 0x20000000 != 0;1202 is_reparse_point && is_reparse_tag_name_surrogate1203 };1204 FileType { is_directory, is_symlink }1205 }1206 pub fn is_dir(&self) -> bool {1207 !self.is_symlink && self.is_directory1208 }1209 pub fn is_file(&self) -> bool {1210 !self.is_symlink && !self.is_directory1211 }1212 pub fn is_symlink(&self) -> bool {1213 self.is_symlink1214 }1215 pub fn is_symlink_dir(&self) -> bool {1216 self.is_symlink && self.is_directory1217 }1218 pub fn is_symlink_file(&self) -> bool {1219 self.is_symlink && !self.is_directory1220 }1221}12221223impl DirBuilder {1224 pub fn new() -> DirBuilder {1225 DirBuilder1226 }12271228 pub fn mkdir(&self, p: &Path) -> io::Result<()> {1229 let p = maybe_verbatim(p)?;1230 cvt(unsafe { c::CreateDirectoryW(p.as_ptr(), ptr::null_mut()) })?;1231 Ok(())1232 }1233}12341235pub fn readdir(p: &Path) -> io::Result<ReadDir> {1236 // We push a `*` to the end of the path which cause the empty path to be1237 // treated as the current directory. So, for consistency with other platforms,1238 // we explicitly error on the empty path.1239 if p.as_os_str().is_empty() {1240 // Return an error code consistent with other ways of opening files.1241 // E.g. fs::metadata or File::open.1242 return Err(io::Error::from_raw_os_error(c::ERROR_PATH_NOT_FOUND as i32));1243 }1244 let root = p.to_path_buf();1245 let star = p.join("*");1246 let path = maybe_verbatim(&star)?;12471248 unsafe {1249 let mut wfd: c::WIN32_FIND_DATAW = mem::zeroed();1250 // this is like FindFirstFileW (see https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfileexw),1251 // but with FindExInfoBasic it should skip filling WIN32_FIND_DATAW.cAlternateFileName1252 // (see https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw)1253 // (which will be always null string value and currently unused) and should be faster.1254 //1255 // We can pass FIND_FIRST_EX_LARGE_FETCH to dwAdditionalFlags to speed up things more,1256 // but as we don't know user's use profile of this function, lets be conservative.1257 let find_handle = c::FindFirstFileExW(1258 path.as_ptr(),1259 c::FindExInfoBasic,1260 &mut wfd as *mut _ as _,1261 c::FindExSearchNameMatch,1262 ptr::null(),1263 0,1264 );12651266 if find_handle != c::INVALID_HANDLE_VALUE {1267 Ok(ReadDir {1268 handle: Some(FindNextFileHandle(find_handle)),1269 root: Arc::new(root),1270 first: Some(wfd),1271 })1272 } else {1273 // The status `ERROR_FILE_NOT_FOUND` is returned by the `FindFirstFileExW` function1274 // if no matching files can be found, but not necessarily that the path to find the1275 // files in does not exist.1276 //1277 // Hence, a check for whether the path to search in exists is added when the last1278 // os error returned by Windows is `ERROR_FILE_NOT_FOUND` to handle this scenario.1279 // If that is the case, an empty `ReadDir` iterator is returned as it returns `None`1280 // in the initial `.next()` invocation because `ERROR_NO_MORE_FILES` would have been1281 // returned by the `FindNextFileW` function.1282 //1283 // See issue #120040: https://github.com/rust-lang/rust/issues/120040.1284 let last_error = api::get_last_error();1285 if last_error == WinError::FILE_NOT_FOUND {1286 return Ok(ReadDir { handle: None, root: Arc::new(root), first: None });1287 }12881289 // Just return the error constructed from the raw OS error if the above is not the case.1290 //1291 // Note: `ERROR_PATH_NOT_FOUND` would have been returned by the `FindFirstFileExW` function1292 // when the path to search in does not exist in the first place.1293 Err(Error::from_raw_os_error(last_error.code as i32))1294 }1295 }1296}12971298pub fn unlink(path: &WCStr) -> io::Result<()> {1299 if unsafe { c::DeleteFileW(path.as_ptr()) } == 0 {1300 let err = api::get_last_error();1301 // if `DeleteFileW` fails with ERROR_ACCESS_DENIED then try to remove1302 // the file while ignoring the readonly attribute.1303 // This is accomplished by calling the `posix_delete` function on an open file handle.1304 if err == WinError::ACCESS_DENIED {1305 let mut opts = OpenOptions::new();1306 opts.access_mode(c::DELETE);1307 opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT);1308 if let Ok(f) = File::open_native(&path, &opts) {1309 if f.posix_delete().is_ok() {1310 return Ok(());1311 }1312 }1313 }1314 // return the original error if any of the above fails.1315 Err(io::Error::from_raw_os_error(err.code as i32))1316 } else {1317 Ok(())1318 }1319}13201321pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> {1322 if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 {1323 let err = api::get_last_error();1324 // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move1325 // the file while ignoring the readonly attribute.1326 // This is accomplished by calling `SetFileInformationByHandle` with `FileRenameInfoEx`.1327 if err == WinError::ACCESS_DENIED {1328 let mut opts = OpenOptions::new();1329 opts.access_mode(c::DELETE);1330 opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);1331 let Ok(f) = File::open_native(&old, &opts) else { return Err(err).io_result() };13321333 // Calculate the layout of the `FILE_RENAME_INFO` we pass to `SetFileInformation`1334 // This is a dynamically sized struct so we need to get the position of the last field to calculate the actual size.1335 let Ok(new_len_without_nul_in_bytes): Result<u32, _> =1336 ((new.count_bytes() - 1) * 2).try_into()1337 else {1338 return Err(err).io_result();1339 };1340 let offset: u32 = offset_of!(c::FILE_RENAME_INFO, FileName).try_into().unwrap();1341 let struct_size = offset + new_len_without_nul_in_bytes + 2;1342 let layout =1343 Layout::from_size_align(struct_size as usize, align_of::<c::FILE_RENAME_INFO>())1344 .unwrap();13451346 let file_rename_info;1347 // SAFETY: We allocate enough memory for a full FILE_RENAME_INFO struct and a filename.1348 unsafe {1349 file_rename_info = alloc(layout).cast::<c::FILE_RENAME_INFO>();1350 if file_rename_info.is_null() {1351 return Err(io::ErrorKind::OutOfMemory.into());1352 }13531354 (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 {1355 Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS1356 | c::FILE_RENAME_FLAG_POSIX_SEMANTICS,1357 });13581359 (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut());1360 // Don't include the NULL in the size1361 (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes);13621363 new.as_ptr().copy_to_nonoverlapping(1364 (&raw mut (*file_rename_info).FileName).cast::<u16>(),1365 new.count_bytes(),1366 );1367 }13681369 let result = unsafe {1370 c::SetFileInformationByHandle(1371 f.as_raw_handle(),1372 c::FileRenameInfoEx,1373 file_rename_info.cast::<c_void>(),1374 struct_size,1375 )1376 };1377 unsafe { dealloc(file_rename_info.cast::<u8>(), layout) };1378 if result == 0 {1379 if api::get_last_error() == WinError::DIR_NOT_EMPTY {1380 return Err(WinError::DIR_NOT_EMPTY).io_result();1381 } else {1382 return Err(err).io_result();1383 }1384 }1385 } else {1386 return Err(err).io_result();1387 }1388 }1389 Ok(())1390}13911392pub fn rmdir(p: &WCStr) -> io::Result<()> {1393 cvt(unsafe { c::RemoveDirectoryW(p.as_ptr()) })?;1394 Ok(())1395}13961397pub fn remove_dir_all(path: &WCStr) -> io::Result<()> {1398 // Open a file or directory without following symlinks.1399 let mut opts = OpenOptions::new();1400 opts.access_mode(c::FILE_LIST_DIRECTORY);1401 // `FILE_FLAG_BACKUP_SEMANTICS` allows opening directories.1402 // `FILE_FLAG_OPEN_REPARSE_POINT` opens a link instead of its target.1403 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);1404 let file = File::open_native(path, &opts)?;14051406 // Test if the file is not a directory or a symlink to a directory.1407 if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {1408 return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));1409 }14101411 // Remove the directory and all its contents.1412 remove_dir_all_iterative(file).io_result()1413}14141415pub fn readlink(path: &WCStr) -> io::Result<PathBuf> {1416 // Open the link with no access mode, instead of generic read.1417 // By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and Settings", so1418 // this is needed for a common case.1419 let mut opts = OpenOptions::new();1420 opts.access_mode(0);1421 opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);1422 let file = File::open_native(&path, &opts)?;1423 file.readlink()1424}14251426pub fn symlink(original: &Path, link: &Path) -> io::Result<()> {1427 symlink_inner(original, link, false)1428}14291430pub fn symlink_inner(original: &Path, link: &Path, dir: bool) -> io::Result<()> {1431 let original = to_u16s(original)?;1432 let link = maybe_verbatim(link)?;1433 let flags = if dir { c::SYMBOLIC_LINK_FLAG_DIRECTORY } else { 0 };1434 // Formerly, symlink creation required the SeCreateSymbolicLink privilege. For the Windows 101435 // Creators Update, Microsoft loosened this to allow unprivileged symlink creation if the1436 // computer is in Developer Mode, but SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE must be1437 // added to dwFlags to opt into this behavior.1438 let result = cvt(unsafe {1439 c::CreateSymbolicLinkW(1440 link.as_ptr(),1441 original.as_ptr(),1442 flags | c::SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,1443 ) as c::BOOL1444 });1445 if let Err(err) = result {1446 if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as i32) {1447 // Older Windows objects to SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,1448 // so if we encounter ERROR_INVALID_PARAMETER, retry without that flag.1449 cvt(unsafe {1450 c::CreateSymbolicLinkW(link.as_ptr(), original.as_ptr(), flags) as c::BOOL1451 })?;1452 } else {1453 return Err(err);1454 }1455 }1456 Ok(())1457}14581459#[cfg(not(target_vendor = "uwp"))]1460pub fn link(original: &WCStr, link: &WCStr) -> io::Result<()> {1461 cvt(unsafe { c::CreateHardLinkW(link.as_ptr(), original.as_ptr(), ptr::null_mut()) })?;1462 Ok(())1463}14641465#[cfg(target_vendor = "uwp")]1466pub fn link(_original: &WCStr, _link: &WCStr) -> io::Result<()> {1467 return Err(io::const_error!(io::ErrorKind::Unsupported, "hard link are not supported on UWP"));1468}14691470pub fn stat(path: &WCStr) -> io::Result<FileAttr> {1471 match metadata(path, ReparsePoint::Follow) {1472 Err(err) if err.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => {1473 if let Ok(attrs) = lstat(path) {1474 if !attrs.file_type().is_symlink() {1475 return Ok(attrs);1476 }1477 }1478 Err(err)1479 }1480 result => result,1481 }1482}14831484pub fn lstat(path: &WCStr) -> io::Result<FileAttr> {1485 metadata(path, ReparsePoint::Open)1486}14871488#[repr(u32)]1489#[derive(Clone, Copy, PartialEq, Eq)]1490enum ReparsePoint {1491 Follow = 0,1492 Open = c::FILE_FLAG_OPEN_REPARSE_POINT,1493}1494impl ReparsePoint {1495 fn as_flag(self) -> u32 {1496 self as u321497 }1498}14991500fn metadata(path: &WCStr, reparse: ReparsePoint) -> io::Result<FileAttr> {1501 let mut opts = OpenOptions::new();1502 // No read or write permissions are necessary1503 opts.access_mode(0);1504 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | reparse.as_flag());15051506 // Attempt to open the file normally.1507 // If that fails with `ERROR_SHARING_VIOLATION` then retry using `FindFirstFileExW`.1508 // If the fallback fails for any reason we return the original error.1509 match File::open_native(&path, &opts) {1510 Ok(file) => file.file_attr(),1511 Err(e)1512 if [Some(c::ERROR_SHARING_VIOLATION as _), Some(c::ERROR_ACCESS_DENIED as _)]1513 .contains(&e.raw_os_error()) =>1514 {1515 // `ERROR_ACCESS_DENIED` is returned when the user doesn't have permission for the resource.1516 // One such example is `System Volume Information` as default but can be created as well1517 // `ERROR_SHARING_VIOLATION` will almost never be returned.1518 // Usually if a file is locked you can still read some metadata.1519 // However, there are special system files, such as1520 // `C:\hiberfil.sys`, that are locked in a way that denies even that.1521 unsafe {1522 // `FindFirstFileExW` accepts wildcard file names.1523 // Fortunately wildcards are not valid file names and1524 // `ERROR_SHARING_VIOLATION` means the file exists (but is locked)1525 // therefore it's safe to assume the file name given does not1526 // include wildcards.1527 let mut wfd: c::WIN32_FIND_DATAW = mem::zeroed();1528 let handle = c::FindFirstFileExW(1529 path.as_ptr(),1530 c::FindExInfoBasic,1531 &mut wfd as *mut _ as _,1532 c::FindExSearchNameMatch,1533 ptr::null(),1534 0,1535 );15361537 if handle == c::INVALID_HANDLE_VALUE {1538 // This can fail if the user does not have read access to the1539 // directory.1540 Err(e)1541 } else {1542 // We no longer need the find handle.1543 c::FindClose(handle);15441545 // `FindFirstFileExW` reads the cached file information from the1546 // directory. The downside is that this metadata may be outdated.1547 let attrs = FileAttr::from(wfd);1548 if reparse == ReparsePoint::Follow && attrs.file_type().is_symlink() {1549 Err(e)1550 } else {1551 Ok(attrs)1552 }1553 }1554 }1555 }1556 Err(e) => Err(e),1557 }1558}15591560pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> {1561 unsafe {1562 cvt(c::SetFileAttributesW(p.as_ptr(), perm.attrs))?;1563 Ok(())1564 }1565}15661567pub fn set_times(p: &WCStr, times: FileTimes) -> io::Result<()> {1568 let mut opts = OpenOptions::new();1569 opts.access_mode(c::FILE_WRITE_ATTRIBUTES);1570 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);1571 let file = File::open_native(p, &opts)?;1572 file.set_times(times)1573}15741575pub fn set_times_nofollow(p: &WCStr, times: FileTimes) -> io::Result<()> {1576 let mut opts = OpenOptions::new();1577 opts.access_mode(c::FILE_WRITE_ATTRIBUTES);1578 // `FILE_FLAG_OPEN_REPARSE_POINT` for no_follow behavior1579 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);1580 let file = File::open_native(p, &opts)?;1581 file.set_times(times)1582}15831584fn get_path(f: impl AsRawHandle) -> io::Result<PathBuf> {1585 fill_utf16_buf(1586 |buf, sz| unsafe {1587 c::GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS)1588 },1589 |buf| PathBuf::from(OsString::from_wide(buf)),1590 )1591}15921593pub fn canonicalize(p: &WCStr) -> io::Result<PathBuf> {1594 let mut opts = OpenOptions::new();1595 // No read or write permissions are necessary1596 opts.access_mode(0);1597 // This flag is so we can open directories too1598 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);1599 let f = File::open_native(p, &opts)?;1600 get_path(f.handle)1601}16021603pub fn copy(from: &WCStr, to: &WCStr) -> io::Result<u64> {1604 unsafe extern "system" fn callback(1605 _TotalFileSize: i64,1606 _TotalBytesTransferred: i64,1607 _StreamSize: i64,1608 StreamBytesTransferred: i64,1609 dwStreamNumber: u32,1610 _dwCallbackReason: u32,1611 _hSourceFile: c::HANDLE,1612 _hDestinationFile: c::HANDLE,1613 lpData: *const c_void,1614 ) -> u32 {1615 unsafe {1616 if dwStreamNumber == 1 {1617 *(lpData as *mut i64) = StreamBytesTransferred;1618 }1619 c::PROGRESS_CONTINUE1620 }1621 }1622 let mut size = 0i64;1623 cvt(unsafe {1624 c::CopyFileExW(1625 from.as_ptr(),1626 to.as_ptr(),1627 Some(callback),1628 (&raw mut size) as *mut _,1629 ptr::null_mut(),1630 0,1631 )1632 })?;1633 Ok(size as u64)1634}16351636pub fn junction_point(original: &Path, link: &Path) -> io::Result<()> {1637 // Create and open a new directory in one go.1638 let mut opts = OpenOptions::new();1639 opts.create_new(true);1640 opts.write(true);1641 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_POSIX_SEMANTICS);1642 opts.attributes(c::FILE_ATTRIBUTE_DIRECTORY);16431644 let d = File::open(link, &opts)?;16451646 // We need to get an absolute, NT-style path.1647 let path_bytes = original.as_os_str().as_encoded_bytes();1648 let abs_path: Vec<u16> = if path_bytes.starts_with(br"\\?\") || path_bytes.starts_with(br"\??\")1649 {1650 // It's already an absolute path, we just need to convert the prefix to `\??\`1651 let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&path_bytes[4..]) };1652 r"\??\".encode_utf16().chain(bytes.encode_wide()).collect()1653 } else {1654 // Get an absolute path and then convert the prefix to `\??\`1655 let abs_path = crate::path::absolute(original)?.into_os_string().into_encoded_bytes();1656 if abs_path.len() > 0 && abs_path[1..].starts_with(br":\") {1657 let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path) };1658 r"\??\".encode_utf16().chain(bytes.encode_wide()).collect()1659 } else if abs_path.starts_with(br"\\.\") {1660 let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path[4..]) };1661 r"\??\".encode_utf16().chain(bytes.encode_wide()).collect()1662 } else if abs_path.starts_with(br"\\") {1663 let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path[2..]) };1664 r"\??\UNC\".encode_utf16().chain(bytes.encode_wide()).collect()1665 } else {1666 return Err(io::const_error!(io::ErrorKind::InvalidInput, "path is not valid"));1667 }1668 };1669 // Defined inline so we don't have to mess about with variable length buffer.1670 #[repr(C)]1671 pub struct MountPointBuffer {1672 ReparseTag: u32,1673 ReparseDataLength: u16,1674 Reserved: u16,1675 SubstituteNameOffset: u16,1676 SubstituteNameLength: u16,1677 PrintNameOffset: u16,1678 PrintNameLength: u16,1679 PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],1680 }1681 let data_len = 12 + (abs_path.len() * 2);1682 if data_len > u16::MAX as usize {1683 return Err(io::const_error!(io::ErrorKind::InvalidInput, "`original` path is too long"));1684 }1685 let data_len = data_len as u16;1686 let mut header = MountPointBuffer {1687 ReparseTag: c::IO_REPARSE_TAG_MOUNT_POINT,1688 ReparseDataLength: data_len,1689 Reserved: 0,1690 SubstituteNameOffset: 0,1691 SubstituteNameLength: (abs_path.len() * 2) as u16,1692 PrintNameOffset: ((abs_path.len() + 1) * 2) as u16,1693 PrintNameLength: 0,1694 PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],1695 };1696 unsafe {1697 let ptr = header.PathBuffer.as_mut_ptr();1698 ptr.copy_from(abs_path.as_ptr().cast_uninit(), abs_path.len());16991700 let mut ret = 0;1701 cvt(c::DeviceIoControl(1702 d.as_raw_handle(),1703 c::FSCTL_SET_REPARSE_POINT,1704 (&raw const header).cast::<c_void>(),1705 data_len as u32 + 8,1706 ptr::null_mut(),1707 0,1708 &mut ret,1709 ptr::null_mut(),1710 ))1711 .map(drop)1712 }1713}17141715// Try to see if a file exists but, unlike `exists`, report I/O errors.1716pub fn exists(path: &WCStr) -> io::Result<bool> {1717 // Open the file to ensure any symlinks are followed to their target.1718 let mut opts = OpenOptions::new();1719 // No read, write, etc access rights are needed.1720 opts.access_mode(0);1721 // Backup semantics enables opening directories as well as files.1722 opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);1723 match File::open_native(path, &opts) {1724 Err(e) => match e.kind() {1725 // The file definitely does not exist1726 io::ErrorKind::NotFound => Ok(false),17271728 // `ERROR_SHARING_VIOLATION` means that the file has been locked by1729 // another process. This is often temporary so we simply report it1730 // as the file existing.1731 _ if e.raw_os_error() == Some(c::ERROR_SHARING_VIOLATION as i32) => Ok(true),17321733 // `ERROR_CANT_ACCESS_FILE` means that a file exists but that the1734 // reparse point could not be handled by `CreateFile`.1735 // This can happen for special files such as:1736 // * Unix domain sockets which you need to `connect` to1737 // * App exec links which require using `CreateProcess`1738 _ if e.raw_os_error() == Some(c::ERROR_CANT_ACCESS_FILE as i32) => Ok(true),17391740 // Other errors such as `ERROR_ACCESS_DENIED` may indicate that the1741 // file exists. However, these types of errors are usually more1742 // permanent so we report them here.1743 _ => Err(e),1744 },1745 // The file was opened successfully therefore it must exist,1746 Ok(_) => Ok(true),1747 }1748}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.