use crate::common::fileutils::{
StatStruct,
windows::{FILE_INFO_BY_NAME_CLASS, get_file_information_by_name},
};
use crate::{
PyObjectRef, PyResult, TryFromObject, VirtualMachine,
convert::{ToPyObject, ToPyResult},
};
use rustpython_common::windows::ToWideString;
use std::ffi::OsStr;
use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
#[derive(Clone, Copy)]
pub struct WinHandle(pub HANDLE);
pub(crate) trait WindowsSysResultValue {
type Ok: ToPyObject;
fn is_err(&self) -> bool;
fn into_ok(self) -> Self::Ok;
}
impl WindowsSysResultValue for HANDLE {
type Ok = WinHandle;
fn is_err(&self) -> bool {
*self == INVALID_HANDLE_VALUE
}
fn into_ok(self) -> Self::Ok {
WinHandle(self)
}
}
impl WindowsSysResultValue for i32 {
type Ok = ();
fn is_err(&self) -> bool {
*self == 0
}
fn into_ok(self) -> Self::Ok {}
}
pub(crate) struct WindowsSysResult<T>(pub T);
impl<T: WindowsSysResultValue> WindowsSysResult<T> {
pub fn is_err(&self) -> bool {
self.0.is_err()
}
pub fn into_pyresult(self, vm: &VirtualMachine) -> PyResult<T::Ok> {
if !self.is_err() {
Ok(self.0.into_ok())
} else {
Err(vm.new_last_os_error())
}
}
}
impl<T: WindowsSysResultValue> ToPyResult for WindowsSysResult<T> {
fn to_pyresult(self, vm: &VirtualMachine) -> PyResult {
let ok = self.into_pyresult(vm)?;
Ok(ok.to_pyobject(vm))
}
}
type HandleInt = isize;
impl TryFromObject for WinHandle {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let handle = HandleInt::try_from_object(vm, obj)?;
Ok(WinHandle(handle as HANDLE))
}
}
impl ToPyObject for WinHandle {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
(self.0 as HandleInt).to_pyobject(vm)
}
}
pub fn init_winsock() {
static WSA_INIT: parking_lot::Once = parking_lot::Once::new();
WSA_INIT.call_once(|| unsafe {
let mut wsa_data = core::mem::MaybeUninit::uninit();
let _ = windows_sys::Win32::Networking::WinSock::WSAStartup(0x0101, wsa_data.as_mut_ptr());
})
}
pub fn win32_xstat(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> {
let mut result = win32_xstat_impl(path, traverse)?;
result.st_ctime = result.st_birthtime;
result.st_ctime_nsec = result.st_birthtime_nsec;
Ok(result)
}
fn is_reparse_tag_name_surrogate(tag: u32) -> bool {
(tag & 0x20000000) > 0
}
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;
#[repr(C)]
#[derive(Default)]
struct FileAttributeTagInfo {
file_attributes: u32,
reparse_tag: u32,
}
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; } else {
m |= S_IFREG;
}
if attr & FILE_ATTRIBUTE_READONLY != 0 {
m |= 0o444;
} else {
m |= 0o666;
}
m
}
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;
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,
);
}
let (st_ino, st_ino_high) = if let Some(id) = id_info {
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)
};
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, st_ctime_nsec: 0,
st_birthtime,
st_birthtime_nsec,
st_file_attributes: info.dwFileAttributes,
st_reparse_tag: reparse_tag,
}
}
fn attributes_from_dir(
path: &OsStr,
) -> std::io::Result<(
windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION,
u32,
)> {
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FindClose, FindFirstFileW,
WIN32_FIND_DATAW,
};
let wide: Vec<u16> = path.to_wide_with_nul();
let mut find_data: WIN32_FIND_DATAW = unsafe { core::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 { core::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))
}
fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> {
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<u16> = path.to_wide_with_nul();
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,
core::ptr::null(),
OPEN_EXISTING,
flags,
core::ptr::null_mut(),
)
};
let mut file_info: BY_HANDLE_FILE_INFORMATION = unsafe { core::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 => {
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);
}
}
ERROR_INVALID_PARAMETER => {
h_file = unsafe {
CreateFileW(
wide.as_ptr(),
access | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
flags,
core::ptr::null_mut(),
)
};
if h_file == INVALID_HANDLE_VALUE {
return Err(error);
}
}
ERROR_CANT_ACCESS_FILE if traverse => {
is_unhandled_tag = true;
h_file = unsafe {
CreateFileW(
wide.as_ptr(),
access,
0,
core::ptr::null(),
OPEN_EXISTING,
flags | FILE_FLAG_OPEN_REPARSE_POINT,
core::ptr::null_mut(),
)
};
if h_file == INVALID_HANDLE_VALUE {
return Err(error);
}
}
_ => return Err(error),
}
}
let result = (|| -> std::io::Result<StatStruct> {
if h_file != INVALID_HANDLE_VALUE {
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()
});
}
if !traverse || is_unhandled_tag {
let mut local_tag_info: FileAttributeTagInfo = unsafe { core::mem::zeroed() };
let ret = unsafe {
GetFileInformationByHandleEx(
h_file,
FileAttributeTagInfo,
&mut local_tag_info as *mut _ as *mut _,
core::mem::size_of::<FileAttributeTagInfo>() 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,
));
}
} else if !is_unhandled_tag {
unsafe { CloseHandle(h_file) };
return win32_xstat_slow_impl(path, true);
}
}
tag_info = local_tag_info;
}
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 => {
return Ok(StatStruct {
st_mode: 0x6000, ..Default::default()
});
}
_ => return Err(std::io::Error::last_os_error()),
}
}
let mut basic_info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() };
let has_basic_info = unsafe {
GetFileInformationByHandleEx(
h_file,
FileBasicInfo,
&mut basic_info as *mut _ as *mut _,
core::mem::size_of::<FILE_BASIC_INFO>() as u32,
)
} != 0;
let mut id_info: FILE_ID_INFO = unsafe { core::mem::zeroed() };
let has_id_info = unsafe {
GetFileInformationByHandleEx(
h_file,
FileIdInfo,
&mut id_info as *mut _ as *mut _,
core::mem::size_of::<FILE_ID_INFO>() 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 {
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)
}
})();
if h_file != INVALID_HANDLE_VALUE {
unsafe { CloseHandle(h_file) };
}
result
}
fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result<StatStruct> {
use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT};
let stat_info =
get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo);
match stat_info {
Ok(stat_info) => {
if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == 0)
|| (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag))
{
let mut result =
crate::common::fileutils::windows::stat_basic_info_to_stat(&stat_info);
if result.st_ino != 0 || result.st_ino_high != 0 {
result.update_st_mode_from_path(path, stat_info.FileAttributes);
return Ok(result);
}
}
}
Err(e) => {
if let Some(errno) = e.raw_os_error()
&& matches!(
errno as u32,
Foundation::ERROR_FILE_NOT_FOUND
| Foundation::ERROR_PATH_NOT_FOUND
| Foundation::ERROR_NOT_READY
| Foundation::ERROR_BAD_NET_NAME
)
{
return Err(e);
}
}
}
win32_xstat_slow_impl(path, traverse)
}