diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 19c8725fa6..26921e0408 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -46,6 +46,7 @@ prec preinitialized PYTHREAD_NAME SA_ONSTACK +SOABI stackdepth stringlib structseq diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index 3475c026bb..89e4fe1882 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -352,13 +352,6 @@ def test_invalid_paths(self): with self.assertRaisesRegex(ValueError, 'embedded null'): func(b'/tmp\x00abcds') - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samestat_on_symlink(self): - return super().test_samestat_on_symlink() - - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samefile_on_symlink(self): - return super().test_samefile_on_symlink() # Following TestCase is not supposed to be run from test_genericpath. # It is inherited by other test modules (ntpath, posixpath). diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 3ec6c3c3a1..c0f49fac6d 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -6384,7 +6384,6 @@ def rotator(source, dest): rh.close() class TimedRotatingFileHandlerTest(BaseFileTest): - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.") def test_should_not_rollover(self): # See bpo-45401. Should only ever rollover regular files diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 96c20fe893..023be1a965 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1458,8 +1458,6 @@ def test_isfile_named_pipe(self): finally: _winapi.CloseHandle(h) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(sys.platform != 'win32', "windows only") def test_con_device(self): self.assertFalse(os.path.isfile(r"\\.\CON")) @@ -1527,14 +1525,6 @@ def test_expandvars(self): def test_expandvars_nonascii(self): return super().test_expandvars_nonascii() - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samefile_on_symlink(self): - return super().test_samefile_on_symlink() - - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON') - def test_samestat_on_symlink(self): - return super().test_samestat_on_symlink() - class PathLikeTests(NtpathTestCase): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 91bc986aab..124430c892 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1476,7 +1476,6 @@ def test_dont_copy_file_onto_link_to_itself(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) - @unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; AssertionError: SameFileError not raised for copyfile') @os_helper.skip_unless_symlink def test_dont_copy_file_onto_symlink_to_itself(self): # bug 851123. @@ -2577,7 +2576,6 @@ def test_destinsrc_false_positive(self): finally: os_helper.rmtree(TESTFN) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_file_symlink(self): @@ -2587,7 +2585,6 @@ def test_move_file_symlink(self): self.assertTrue(os.path.islink(self.dst_file)) self.assertTrue(os.path.samefile(self.src_file, self.dst_file)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_file_symlink_to_dir(self): @@ -2611,7 +2608,6 @@ def test_move_dangling_symlink(self): self.assertTrue(os.path.islink(dst_link)) self.assertEqual(os.path.realpath(src), os.path.realpath(dst_link)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink @mock_rename def test_move_dir_symlink(self): @@ -2687,12 +2683,10 @@ def _test_move_symlink_to_dir_into_dir(self, dst): self.assertTrue(os.path.samefile(self.dst_dir, dst_link)) self.assertTrue(os.path.exists(src)) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_move_symlink_to_dir_into_dir(self): self._test_move_symlink_to_dir_into_dir(self.dst_dir) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") @os_helper.skip_unless_symlink def test_move_symlink_to_dir_into_symlink_to_dir(self): dst = os.path.join(self.src_dir, 'otherlinktodir') diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 9ed89b0bab..e2d4b41cc3 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -475,6 +475,20 @@ pub(crate) mod module { Ok(()) } + unsafe extern "C" { + fn _umask(mask: i32) -> i32; + } + + #[pyfunction] + fn umask(mask: i32, vm: &VirtualMachine) -> PyResult { + let result = unsafe { _umask(mask) }; + if result < 0 { + Err(errno_err(vm)) + } else { + Ok(result) + } + } + pub(crate) fn support_funcs() -> Vec { Vec::new() } diff --git a/crates/vm/src/stdlib/sysconfig.rs b/crates/vm/src/stdlib/sysconfig.rs index 2e0a8a51c7..df5b7100a9 100644 --- a/crates/vm/src/stdlib/sysconfig.rs +++ b/crates/vm/src/stdlib/sysconfig.rs @@ -7,8 +7,19 @@ pub(crate) mod sysconfig { #[pyfunction] fn config_vars(vm: &VirtualMachine) -> PyDictRef { let vars = vm.ctx.new_dict(); + + // FIXME: This is an entirely wrong implementation of EXT_SUFFIX. + // EXT_SUFFIX must be a string starting with "." for pip compatibility + // Using ".pyd" causes pip's _generic_abi() to fall back to _cpython_abis() + vars.set_item("EXT_SUFFIX", ".pyd".to_pyobject(vm), vm) + .unwrap(); + vars.set_item("SOABI", vm.ctx.none(), vm).unwrap(); + vars.set_item("Py_GIL_DISABLED", true.to_pyobject(vm), vm) .unwrap(); + vars.set_item("Py_DEBUG", false.to_pyobject(vm), vm) + .unwrap(); + vars } } diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index 907f61c7b8..26fec85021 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -7,7 +7,7 @@ use crate::{ convert::{ToPyObject, ToPyResult}, stdlib::os::errno_err, }; -use std::{ffi::OsStr, time::SystemTime}; +use std::ffi::OsStr; use windows::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::{BOOL, HANDLE as RAW_HANDLE, INVALID_HANDLE_VALUE}; @@ -93,6 +93,413 @@ fn is_reparse_tag_name_surrogate(tag: u32) -> bool { (tag & 0x20000000) > 0 } +// Constants +const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; +const S_IFMT: u16 = libc::S_IFMT as u16; +const S_IFDIR: u16 = libc::S_IFDIR as u16; +const S_IFREG: u16 = libc::S_IFREG as u16; +const S_IFCHR: u16 = libc::S_IFCHR as u16; +const S_IFLNK: u16 = crate::common::fileutils::windows::S_IFLNK as u16; +const S_IFIFO: u16 = crate::common::fileutils::windows::S_IFIFO as u16; + +/// FILE_ATTRIBUTE_TAG_INFO structure for GetFileInformationByHandleEx +#[repr(C)] +#[derive(Default)] +struct FileAttributeTagInfo { + file_attributes: u32, + reparse_tag: u32, +} + +/// Ported from attributes_to_mode (fileutils.c) +fn attributes_to_mode(attr: u32) -> u16 { + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, + }; + let mut m: u16 = 0; + if attr & FILE_ATTRIBUTE_DIRECTORY != 0 { + m |= S_IFDIR | 0o111; // IFEXEC for user,group,other + } else { + m |= S_IFREG; + } + if attr & FILE_ATTRIBUTE_READONLY != 0 { + m |= 0o444; + } else { + m |= 0o666; + } + m +} + +/// Ported from _Py_attribute_data_to_stat (fileutils.c) +/// Converts BY_HANDLE_FILE_INFORMATION to StatStruct +fn attribute_data_to_stat( + info: &windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, + reparse_tag: u32, + basic_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_BASIC_INFO>, + id_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_ID_INFO>, +) -> StatStruct { + use crate::common::fileutils::windows::SECS_BETWEEN_EPOCHS; + use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; + + let mut st_mode = attributes_to_mode(info.dwFileAttributes); + let st_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64); + let st_dev = id_info + .map(|id| id.VolumeSerialNumber as u32) + .unwrap_or(info.dwVolumeSerialNumber); + let st_nlink = info.nNumberOfLinks as i32; + + // Convert FILETIME/LARGE_INTEGER to (time_t, nsec) + let filetime_to_time = |ft_low: u32, ft_high: u32| -> (libc::time_t, i32) { + let ticks = ((ft_high as i64) << 32) | (ft_low as i64); + let nsec = ((ticks % 10_000_000) * 100) as i32; + let sec = (ticks / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; + (sec, nsec) + }; + + let large_integer_to_time = |li: i64| -> (libc::time_t, i32) { + let nsec = ((li % 10_000_000) * 100) as i32; + let sec = (li / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; + (sec, nsec) + }; + + let (st_birthtime, st_birthtime_nsec); + let (st_mtime, st_mtime_nsec); + let (st_atime, st_atime_nsec); + + if let Some(bi) = basic_info { + (st_birthtime, st_birthtime_nsec) = large_integer_to_time(bi.CreationTime); + (st_mtime, st_mtime_nsec) = large_integer_to_time(bi.LastWriteTime); + (st_atime, st_atime_nsec) = large_integer_to_time(bi.LastAccessTime); + } else { + (st_birthtime, st_birthtime_nsec) = filetime_to_time( + info.ftCreationTime.dwLowDateTime, + info.ftCreationTime.dwHighDateTime, + ); + (st_mtime, st_mtime_nsec) = filetime_to_time( + info.ftLastWriteTime.dwLowDateTime, + info.ftLastWriteTime.dwHighDateTime, + ); + (st_atime, st_atime_nsec) = filetime_to_time( + info.ftLastAccessTime.dwLowDateTime, + info.ftLastAccessTime.dwHighDateTime, + ); + } + + // Get file ID from id_info or fallback to file index + let (st_ino, st_ino_high) = if let Some(id) = id_info { + // FILE_ID_INFO.FileId is FILE_ID_128 which is [u8; 16] + let bytes = id.FileId.Identifier; + let low = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); + let high = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); + (low, high) + } else { + let ino = ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64); + (ino, 0u64) + }; + + // Set symlink mode if applicable + if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && reparse_tag == IO_REPARSE_TAG_SYMLINK + { + st_mode = (st_mode & !S_IFMT) | S_IFLNK; + } + + StatStruct { + st_dev, + st_ino, + st_ino_high, + st_mode, + st_nlink, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + st_size, + st_atime, + st_atime_nsec, + st_mtime, + st_mtime_nsec, + st_ctime: 0, // Will be set by caller + st_ctime_nsec: 0, + st_birthtime, + st_birthtime_nsec, + st_file_attributes: info.dwFileAttributes, + st_reparse_tag: reparse_tag, + } +} + +/// Get file info using FindFirstFileW (fallback when CreateFileW fails) +/// Ported from attributes_from_dir +fn attributes_from_dir( + path: &OsStr, +) -> std::io::Result<( + windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, + u32, +)> { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FindClose, FindFirstFileW, + WIN32_FIND_DATAW, + }; + + let wide: Vec = path.encode_wide().chain(std::iter::once(0)).collect(); + let mut find_data: WIN32_FIND_DATAW = unsafe { std::mem::zeroed() }; + + let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(std::io::Error::last_os_error()); + } + unsafe { FindClose(handle) }; + + let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() }; + info.dwFileAttributes = find_data.dwFileAttributes; + info.ftCreationTime = find_data.ftCreationTime; + info.ftLastAccessTime = find_data.ftLastAccessTime; + info.ftLastWriteTime = find_data.ftLastWriteTime; + info.nFileSizeHigh = find_data.nFileSizeHigh; + info.nFileSizeLow = find_data.nFileSizeLow; + + let reparse_tag = if find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + find_data.dwReserved0 + } else { + 0 + }; + + Ok((info, reparse_tag)) +} + +/// Ported from win32_xstat_slow_impl +fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::{ + Foundation::{ + CloseHandle, ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION, + ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, GENERIC_READ, + INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{ + BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY, + FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_TYPE_CHAR, + FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN, FileAttributeTagInfo, FileBasicInfo, + FileIdInfo, GetFileAttributesW, GetFileInformationByHandle, + GetFileInformationByHandleEx, GetFileType, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, + }, + }; + + let wide: Vec = path.encode_wide().chain(std::iter::once(0)).collect(); + + let access = FILE_READ_ATTRIBUTES; + let mut flags = FILE_FLAG_BACKUP_SEMANTICS; + if !traverse { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + + let mut h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access, + 0, + std::ptr::null(), + OPEN_EXISTING, + flags, + std::ptr::null_mut(), + ) + }; + + let mut file_info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() }; + let mut tag_info = FileAttributeTagInfo::default(); + let mut is_unhandled_tag = false; + + if h_file == INVALID_HANDLE_VALUE { + let error = std::io::Error::last_os_error(); + let error_code = error.raw_os_error().unwrap_or(0) as u32; + + match error_code { + ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => { + // Try reading the parent directory using FindFirstFileW + let (info, reparse_tag) = attributes_from_dir(path)?; + file_info = info; + tag_info.reparse_tag = reparse_tag; + + if file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + && (traverse || !is_reparse_tag_name_surrogate(tag_info.reparse_tag)) + { + return Err(error); + } + // h_file remains INVALID_HANDLE_VALUE, we'll use file_info from FindFirstFileW + } + ERROR_INVALID_PARAMETER => { + // Retry with GENERIC_READ (needed for \\.\con) + h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null(), + OPEN_EXISTING, + flags, + std::ptr::null_mut(), + ) + }; + if h_file == INVALID_HANDLE_VALUE { + return Err(error); + } + } + ERROR_CANT_ACCESS_FILE if traverse => { + // bpo37834: open unhandled reparse points if traverse fails + is_unhandled_tag = true; + h_file = unsafe { + CreateFileW( + wide.as_ptr(), + access, + 0, + std::ptr::null(), + OPEN_EXISTING, + flags | FILE_FLAG_OPEN_REPARSE_POINT, + std::ptr::null_mut(), + ) + }; + if h_file == INVALID_HANDLE_VALUE { + return Err(error); + } + } + _ => return Err(error), + } + } + + // Scope for handle cleanup + let result = (|| -> std::io::Result { + if h_file != INVALID_HANDLE_VALUE { + // Handle types other than files on disk + let file_type = unsafe { GetFileType(h_file) }; + if file_type != FILE_TYPE_DISK { + if file_type == FILE_TYPE_UNKNOWN { + let err = std::io::Error::last_os_error(); + if err.raw_os_error().unwrap_or(0) != 0 { + return Err(err); + } + } + let file_attributes = unsafe { GetFileAttributesW(wide.as_ptr()) }; + let mut st_mode: u16 = 0; + if file_attributes != INVALID_FILE_ATTRIBUTES + && file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0 + { + st_mode = S_IFDIR; + } else if file_type == FILE_TYPE_CHAR { + st_mode = S_IFCHR; + } else if file_type == FILE_TYPE_PIPE { + st_mode = S_IFIFO; + } + return Ok(StatStruct { + st_mode, + ..Default::default() + }); + } + + // Query the reparse tag + if !traverse || is_unhandled_tag { + let mut local_tag_info: FileAttributeTagInfo = unsafe { std::mem::zeroed() }; + let ret = unsafe { + GetFileInformationByHandleEx( + h_file, + FileAttributeTagInfo, + &mut local_tag_info as *mut _ as *mut _, + std::mem::size_of::() as u32, + ) + }; + if ret == 0 { + let err_code = + std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; + match err_code { + ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { + local_tag_info.file_attributes = FILE_ATTRIBUTE_NORMAL; + local_tag_info.reparse_tag = 0; + } + _ => return Err(std::io::Error::last_os_error()), + } + } else if local_tag_info.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + if is_reparse_tag_name_surrogate(local_tag_info.reparse_tag) { + if is_unhandled_tag { + return Err(std::io::Error::from_raw_os_error( + ERROR_CANT_ACCESS_FILE as i32, + )); + } + // This is a symlink, keep the tag info + } else if !is_unhandled_tag { + // Traverse a non-link reparse point + unsafe { CloseHandle(h_file) }; + return win32_xstat_slow_impl(path, true); + } + } + tag_info = local_tag_info; + } + + // Get file information + let ret = unsafe { GetFileInformationByHandle(h_file, &mut file_info) }; + if ret == 0 { + let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; + match err_code { + ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { + // Volumes and physical disks are block devices + return Ok(StatStruct { + st_mode: 0x6000, // S_IFBLK + ..Default::default() + }); + } + _ => return Err(std::io::Error::last_os_error()), + } + } + + // Get FILE_BASIC_INFO + let mut basic_info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() }; + let has_basic_info = unsafe { + GetFileInformationByHandleEx( + h_file, + FileBasicInfo, + &mut basic_info as *mut _ as *mut _, + std::mem::size_of::() as u32, + ) + } != 0; + + // Get FILE_ID_INFO (optional) + let mut id_info: FILE_ID_INFO = unsafe { std::mem::zeroed() }; + let has_id_info = unsafe { + GetFileInformationByHandleEx( + h_file, + FileIdInfo, + &mut id_info as *mut _ as *mut _, + std::mem::size_of::() as u32, + ) + } != 0; + + let mut result = attribute_data_to_stat( + &file_info, + tag_info.reparse_tag, + if has_basic_info { + Some(&basic_info) + } else { + None + }, + if has_id_info { Some(&id_info) } else { None }, + ); + result.update_st_mode_from_path(path, file_info.dwFileAttributes); + Ok(result) + } else { + // We got file_info from attributes_from_dir + let mut result = attribute_data_to_stat(&file_info, tag_info.reparse_tag, None, None); + result.update_st_mode_from_path(path, file_info.dwFileAttributes); + Ok(result) + } + })(); + + // Cleanup + if h_file != INVALID_HANDLE_VALUE { + unsafe { CloseHandle(h_file) }; + } + + result +} + fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result { use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT}; @@ -124,83 +531,6 @@ fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result } } - // TODO: check if win32_xstat_slow_impl(&path, result, traverse) is required - meta_to_stat( - &crate::stdlib::os::fs_metadata(path, traverse)?, - file_id(path)?, - ) -} - -// Ported from zed: https://github.com/zed-industries/zed/blob/v0.131.6/crates/fs/src/fs.rs#L1532-L1562 -// can we get file id not open the file twice? -// https://github.com/rust-lang/rust/issues/63010 -fn file_id(path: &OsStr) -> std::io::Result { - use std::os::windows::{fs::OpenOptionsExt, io::AsRawHandle}; - use windows_sys::Win32::{ - Foundation::HANDLE, - Storage::FileSystem::{ - BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS, GetFileInformationByHandle, - }, - }; - - let file = std::fs::OpenOptions::new() - .read(true) - .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) - .open(path)?; - - let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() }; - // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle - // This function supports Windows XP+ - let ret = unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &mut info) }; - if ret == 0 { - return Err(std::io::Error::last_os_error()); - }; - - Ok(((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64)) -} - -fn meta_to_stat(meta: &std::fs::Metadata, file_id: u64) -> std::io::Result { - let st_mode = { - // Based on CPython fileutils.c' attributes_to_mode - let mut m = 0; - if meta.is_dir() { - m |= libc::S_IFDIR | 0o111; /* IFEXEC for user,group,other */ - } else { - m |= libc::S_IFREG; - } - if meta.is_symlink() { - m |= 0o100000; - } - if meta.permissions().readonly() { - m |= 0o444; - } else { - m |= 0o666; - } - m as _ - }; - let (atime, mtime, ctime) = (meta.accessed()?, meta.modified()?, meta.created()?); - let sec = |systime: SystemTime| match systime.duration_since(SystemTime::UNIX_EPOCH) { - Ok(d) => d.as_secs() as libc::time_t, - Err(e) => -(e.duration().as_secs() as libc::time_t), - }; - let nsec = |systime: SystemTime| match systime.duration_since(SystemTime::UNIX_EPOCH) { - Ok(d) => d.subsec_nanos() as i32, - Err(e) => -(e.duration().subsec_nanos() as i32), - }; - Ok(StatStruct { - st_dev: 0, - st_ino: file_id, - st_mode, - st_nlink: 0, - st_uid: 0, - st_gid: 0, - st_size: meta.len(), - st_atime: sec(atime), - st_mtime: sec(mtime), - st_birthtime: sec(ctime), - st_atime_nsec: nsec(atime), - st_mtime_nsec: nsec(mtime), - st_birthtime_nsec: nsec(ctime), - ..Default::default() - }) + // Fallback to slow implementation + win32_xstat_slow_impl(path, traverse) }