#![allow(
clippy::not_unsafe_ptr_arg_deref,
reason = "This module mirrors raw Win32 path, handle, and CRT entry points."
)]
use std::{
ffi::OsString,
io,
os::windows::{ffi::OsStringExt, io::AsRawHandle},
path::Path,
};
use core::sync::atomic::{AtomicBool, Ordering};
use crate::{
crt_fd,
fileutils::{
StatStruct,
windows::{FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, stat_basic_info_to_stat},
},
windows::{CheckWin32Bool, CheckWin32Handle, CheckWin32Sentinel, HandleToOwned},
};
use libc::intptr_t;
use widestring::WideCString;
use windows_sys::{
Win32::{
Foundation::{
CloseHandle, ERROR_ACCESS_DENIED, ERROR_BAD_NET_NAME, ERROR_BAD_NETPATH,
ERROR_BAD_PATHNAME, ERROR_CANT_ACCESS_FILE, ERROR_DIRECTORY, ERROR_FILE_NOT_FOUND,
ERROR_FILENAME_EXCED_RANGE, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_FUNCTION,
ERROR_INVALID_HANDLE, ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_MORE_DATA,
ERROR_NO_MORE_FILES, ERROR_NOT_READY, ERROR_NOT_SUPPORTED, ERROR_PATH_NOT_FOUND,
ERROR_SHARING_VIOLATION, FILETIME, GENERIC_READ, GENERIC_WRITE, GetHandleInformation,
GetLastError, HANDLE, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, MAX_PATH,
SetHandleInformation,
},
Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte},
Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, CreateFileW, CreateSymbolicLinkW, DeleteFileW,
FILE_ATTRIBUTE_TAG_INFO, FILE_BASIC_INFO, FILE_DEVICE_CD_ROM, FILE_DEVICE_DISK,
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO,
FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
FILE_TYPE_CHAR, FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN,
FILE_WRITE_ATTRIBUTES, FileAttributeTagInfo as FileAttributeTagInfoClass,
FileBasicInfo, FileIdInfo, FindClose, FindFirstFileW, FindNextFileW,
GetDiskFreeSpaceExW, GetDriveTypeW, GetFileAttributesExW, GetFileAttributesW,
GetFileInformationByHandle, GetFileInformationByHandleEx, GetFileType,
GetFullPathNameW, GetLogicalDriveStringsW, GetVolumePathNameW,
GetVolumePathNamesForVolumeNameW, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING,
RemoveDirectoryW, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,
SYMBOLIC_LINK_FLAG_DIRECTORY, SetFileAttributesW, SetFileInformationByHandle,
SetFileTime, WIN32_FILE_ATTRIBUTE_DATA, WIN32_FIND_DATAW,
},
System::{
Console,
IO::DeviceIoControl,
Ioctl::{
FILE_DEVICE_VIRTUAL_DISK, FSCTL_GET_REPARSE_POINT,
FSCTL_QUERY_PERSISTENT_VOLUME_STATE,
},
Threading,
WindowsProgramming::{DRIVE_FIXED, GetUserNameW},
},
},
w,
};
pub use windows_sys::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE,
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN,
FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_NORMAL,
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM,
FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_VIRTUAL,
};
pub type Handle = HANDLE;
pub const MAX_PATH_USIZE: usize = MAX_PATH as usize;
pub const ERROR_INVALID_HANDLE_I32: i32 = ERROR_INVALID_HANDLE as i32;
pub const LOAD_LIBRARY_SEARCH_APPLICATION_DIR: u32 =
windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_APPLICATION_DIR;
pub const LOAD_LIBRARY_SEARCH_DEFAULT_DIRS: u32 =
windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_DEFAULT_DIRS;
pub const LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR: u32 =
windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 =
windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_SYSTEM32;
pub const LOAD_LIBRARY_SEARCH_USER_DIRS: u32 =
windows_sys::Win32::System::LibraryLoader::LOAD_LIBRARY_SEARCH_USER_DIRS;
pub use windows_sys::Win32::System::SystemServices::{
IO_REPARSE_TAG_APPEXECLINK, IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK,
};
#[cfg(target_env = "msvc")]
unsafe extern "C" {
fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t;
fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t;
fn _wexecve(cmdname: *const u16, argv: *const *const u16, envp: *const *const u16) -> intptr_t;
fn _wspawnv(mode: i32, cmdname: *const u16, argv: *const *const u16) -> intptr_t;
fn _wspawnve(
mode: i32,
cmdname: *const u16,
argv: *const *const u16,
envp: *const *const u16,
) -> intptr_t;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TestType {
RegularFile,
Directory,
Symlink,
Junction,
LinkReparsePoint,
RegularReparsePoint,
}
const S_IFMT: u16 = libc::S_IFMT as u16;
const S_IFDIR_MODE: u16 = libc::S_IFDIR as u16;
const S_IFCHR_MODE: u16 = libc::S_IFCHR as u16;
const S_IFIFO_MODE: u16 = crate::fileutils::windows::S_IFIFO as u16;
#[repr(C)]
#[derive(Default)]
struct FileAttributeTagInfo {
file_attributes: u32,
reparse_tag: u32,
}
const fn win32_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 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS) as libc::time_t;
(sec, nsec)
}
const fn win32_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 - crate::fileutils::windows::SECS_BETWEEN_EPOCHS) as libc::time_t;
(sec, nsec)
}
fn win32_attribute_data_to_stat(
info: &BY_HANDLE_FILE_INFORMATION,
reparse_tag: u32,
basic_info: Option<&FILE_BASIC_INFO>,
id_info: Option<&FILE_ID_INFO>,
) -> StatStruct {
let mut st_mode: u16 = 0;
if info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
st_mode |= S_IFDIR_MODE | 0o111;
} else {
st_mode |= libc::S_IFREG as u16;
}
if info.dwFileAttributes & FILE_ATTRIBUTE_READONLY != 0 {
st_mode |= 0o444;
} else {
st_mode |= 0o666;
}
let st_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64);
let st_dev = id_info.map_or(info.dwVolumeSerialNumber, |id| id.VolumeSerialNumber as u32);
let st_nlink = info.nNumberOfLinks as i32;
let (st_birthtime, st_birthtime_nsec, st_mtime, st_mtime_nsec, st_atime, st_atime_nsec) =
if let Some(bi) = basic_info {
let (birth, birth_nsec) = win32_large_integer_to_time(bi.CreationTime);
let (mtime, mtime_nsec) = win32_large_integer_to_time(bi.LastWriteTime);
let (atime, atime_nsec) = win32_large_integer_to_time(bi.LastAccessTime);
(birth, birth_nsec, mtime, mtime_nsec, atime, atime_nsec)
} else {
let (birth, birth_nsec) = win32_filetime_to_time(
info.ftCreationTime.dwLowDateTime,
info.ftCreationTime.dwHighDateTime,
);
let (mtime, mtime_nsec) = win32_filetime_to_time(
info.ftLastWriteTime.dwLowDateTime,
info.ftLastWriteTime.dwHighDateTime,
);
let (atime, atime_nsec) = win32_filetime_to_time(
info.ftLastAccessTime.dwLowDateTime,
info.ftLastAccessTime.dwHighDateTime,
);
(birth, birth_nsec, mtime, mtime_nsec, atime, atime_nsec)
};
let (st_ino, st_ino_high) = if let Some(id) = id_info {
let bytes = id.FileId.Identifier;
(
u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
)
} else {
(
((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64),
0,
)
};
if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
&& reparse_tag == IO_REPARSE_TAG_SYMLINK
{
st_mode = (st_mode & !S_IFMT) | crate::fileutils::windows::S_IFLNK as u16;
}
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,
}
}
pub fn visible_env_vars() -> impl Iterator<Item = (String, String)> {
crate::os::vars().filter(|(key, _)| !key.starts_with('='))
}
#[derive(Debug)]
pub enum ReadlinkError {
Io(io::Error),
NotSymbolicLink,
InvalidReparseData,
}
#[derive(Debug)]
pub enum ReadConsoleError {
Io(io::Error),
BufferTooSmall { available: usize, required: usize },
}
pub fn access(path: &Path, mode: u8) -> bool {
let Ok(wide) = WideCString::from_os_str(path.as_os_str()) else {
return false;
};
let attr = unsafe { GetFileAttributesW(wide.as_ptr()) };
attr != INVALID_FILE_ATTRIBUTES
&& (mode & 2 == 0
|| attr & FILE_ATTRIBUTE_READONLY == 0
|| attr & FILE_ATTRIBUTE_DIRECTORY != 0)
}
pub fn remove(path: &widestring::WideCStr) -> io::Result<()> {
let attrs = unsafe { GetFileAttributesW(path.as_ptr()) };
let mut is_directory = false;
let mut is_link = false;
if attrs != INVALID_FILE_ATTRIBUTES {
is_directory = (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
if is_directory && (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(path.as_ptr(), &mut find_data) };
if handle != INVALID_HANDLE_VALUE {
is_link = find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK
|| find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT;
unsafe { FindClose(handle) };
}
}
}
if is_directory && is_link {
unsafe { RemoveDirectoryW(path.as_ptr()) }
} else {
unsafe { DeleteFileW(path.as_ptr()) }
}
.check_win32_bool()
}
pub fn supports_virtual_terminal() -> bool {
let mut mode = 0;
let handle = unsafe { Console::GetStdHandle(Console::STD_ERROR_HANDLE) };
(unsafe { Console::GetConsoleMode(handle, &mut mode) }) != 0
&& mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0
}
pub fn symlink(
_src: &Path,
_dst: &Path,
src_wide: &widestring::WideCStr,
dst_wide: &widestring::WideCStr,
target_is_directory: bool,
) -> io::Result<()> {
static HAS_UNPRIVILEGED_FLAG: AtomicBool = AtomicBool::new(true);
fn check_dir(src: &widestring::WideCStr, dst: &widestring::WideCStr) -> bool {
use windows_sys::Win32::Storage::FileSystem::GetFileExInfoStandard;
let src = src.as_slice();
let dst = dst.as_slice();
let max_path = MAX_PATH_USIZE;
if dst.len() >= max_path {
return false;
}
let parent_len = dst
.iter()
.rposition(|&unit| unit == b'\\' as u16 || unit == b'/' as u16)
.unwrap_or(0);
let parent = &dst[..parent_len];
let absolute = src
.first()
.is_some_and(|&unit| unit == b'\\' as u16 || unit == b'/' as u16)
|| (src.first().is_some_and(|&unit| unit != 0) && src.get(1) == Some(&(b':' as u16)));
let mut resolved = Vec::with_capacity(max_path);
if absolute {
if src.len() >= max_path {
return false;
}
resolved.extend_from_slice(src);
} else {
let separator_len = usize::from(!parent.is_empty());
if parent.len() + separator_len + src.len() >= max_path {
return false;
}
resolved.extend_from_slice(parent);
if !parent.is_empty() {
resolved.push(b'\\' as u16);
}
resolved.extend_from_slice(src);
}
resolved.push(0);
let mut info: WIN32_FILE_ATTRIBUTE_DATA = unsafe { core::mem::zeroed() };
let ok = unsafe {
GetFileAttributesExW(
resolved.as_ptr(),
GetFileExInfoStandard,
(&mut info as *mut WIN32_FILE_ATTRIBUTE_DATA).cast(),
)
};
ok != 0 && (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0
}
let mut flags = 0u32;
if HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) {
flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
}
if target_is_directory || check_dir(src_wide, dst_wide) {
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
let mut result = unsafe { CreateSymbolicLinkW(dst_wide.as_ptr(), src_wide.as_ptr(), flags) };
if !result
&& HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed)
&& unsafe { GetLastError() } == ERROR_INVALID_PARAMETER
{
let flags = flags & !SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
result = unsafe { CreateSymbolicLinkW(dst_wide.as_ptr(), src_wide.as_ptr(), flags) };
if result || unsafe { GetLastError() } != ERROR_INVALID_PARAMETER {
HAS_UNPRIVILEGED_FLAG.store(false, Ordering::Relaxed);
}
}
if result {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn set_file_times(
path: &widestring::WideCStr,
atime_sec: i64,
atime_nsec: i64,
mtime_sec: i64,
mtime_nsec: i64,
) -> io::Result<()> {
const EPOCH_DIFF: i64 = 11_644_473_600;
let to_filetime = |sec: i64, nsec: i64| -> FILETIME {
let ticks = sec
.wrapping_add(EPOCH_DIFF)
.wrapping_mul(10_000_000)
.wrapping_add(nsec / 100) as u64;
FILETIME {
dwLowDateTime: ticks as u32,
dwHighDateTime: (ticks >> 32) as u32,
}
};
let atime = to_filetime(atime_sec, atime_nsec);
let mtime = to_filetime(mtime_sec, mtime_nsec);
let handle = unsafe {
CreateFileW(
path.as_ptr(),
FILE_WRITE_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let wrote = unsafe { SetFileTime(handle, core::ptr::null(), &atime, &mtime) };
let error = (wrote == 0).then(io::Error::last_os_error);
unsafe { CloseHandle(handle) };
match error {
Some(error) => Err(error),
None => Ok(()),
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn win32_hchmod(handle: HANDLE, mode: u32, write_bit: u32) -> io::Result<()> {
let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() };
unsafe {
GetFileInformationByHandleEx(
handle,
FileBasicInfo,
(&mut info as *mut FILE_BASIC_INFO).cast(),
core::mem::size_of::<FILE_BASIC_INFO>() as u32,
)
}
.check_win32_bool()?;
if mode & write_bit != 0 {
info.FileAttributes &= !FILE_ATTRIBUTE_READONLY;
} else {
info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
}
unsafe {
SetFileInformationByHandle(
handle,
FileBasicInfo,
(&info as *const FILE_BASIC_INFO).cast(),
core::mem::size_of::<FILE_BASIC_INFO>() as u32,
)
}
.check_win32_bool()
}
pub fn fchmod(fd: i32, mode: u32, write_bit: u32) -> io::Result<()> {
let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) };
let handle = crt_fd::as_handle(borrowed)?;
win32_hchmod(handle.as_raw_handle() as HANDLE, mode, write_bit)
}
pub fn win32_lchmod(path: &widestring::WideCStr, mode: u32, write_bit: u32) -> io::Result<()> {
let attr = unsafe { GetFileAttributesW(path.as_ptr()) }.check_ne(INVALID_FILE_ATTRIBUTES)?;
let new_attr = if mode & write_bit != 0 {
attr & !FILE_ATTRIBUTE_READONLY
} else {
attr | FILE_ATTRIBUTE_READONLY
};
unsafe { SetFileAttributesW(path.as_ptr(), new_attr) }.check_win32_bool()
}
pub fn chmod_follow(path: &widestring::WideCStr, mode: u32, write_bit: u32) -> io::Result<()> {
let handle = unsafe {
CreateFileW(
path.as_ptr(),
FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
use std::os::windows::io::AsRawHandle;
let handle = handle.into_owned().ok_or_else(io::Error::last_os_error)?;
win32_hchmod(handle.as_raw_handle() as HANDLE, mode, write_bit)
}
pub fn find_first_file_name(path: &widestring::WideCStr) -> io::Result<OsString> {
let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(path.as_ptr(), &mut find_data) }.check_valid()?;
unsafe { FindClose(handle) };
let len = find_data
.cFileName
.iter()
.position(|&c| c == 0)
.unwrap_or(find_data.cFileName.len());
Ok(OsString::from_wide(&find_data.cFileName[..len]))
}
pub fn join_path_filename(dir: &[u16], name: &[u16]) -> Vec<u16> {
let mut joined = dir.to_vec();
if let Some(&last) = joined.last() {
if last != b'\\' as u16 && last != b'/' as u16 && last != b':' as u16 {
joined.push(b'\\' as u16);
}
joined.extend_from_slice(name);
}
joined
}
fn filetime_ticks(ft: FILETIME) -> u64 {
((ft.dwHighDateTime as u64) << 32) | u64::from(ft.dwLowDateTime)
}
#[derive(Clone, Debug)]
pub struct ScandirEntry {
pub name: OsString,
pub file_attributes: u32,
pub reserved0: u32,
pub file_size: u64,
pub creation_ticks: u64,
pub last_access_ticks: u64,
pub last_write_ticks: u64,
}
impl ScandirEntry {
fn from_find_data(data: &WIN32_FIND_DATAW) -> Self {
let len = data
.cFileName
.iter()
.position(|&unit| unit == 0)
.unwrap_or(data.cFileName.len());
Self {
name: OsString::from_wide(&data.cFileName[..len]),
file_attributes: data.dwFileAttributes,
reserved0: data.dwReserved0,
file_size: ((data.nFileSizeHigh as u64) << 32) | u64::from(data.nFileSizeLow),
creation_ticks: filetime_ticks(data.ftCreationTime),
last_access_ticks: filetime_ticks(data.ftLastAccessTime),
last_write_ticks: filetime_ticks(data.ftLastWriteTime),
}
}
pub fn is_dot_or_dotdot(&self) -> bool {
self.name == "." || self.name == ".."
}
pub fn is_directory(&self) -> bool {
self.file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0
}
pub fn is_symlink(&self) -> bool {
self.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
&& self.reserved0 == IO_REPARSE_TAG_SYMLINK
}
pub fn is_file(&self) -> bool {
!self.is_directory() && !self.is_symlink()
}
pub fn to_stat_struct(&self) -> crate::fileutils::StatStruct {
use crate::fileutils::windows::{S_IFLNK, SECS_BETWEEN_EPOCHS};
let reparse_tag = if self.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
self.reserved0
} else {
0
};
let mut mode = if self.is_directory() {
libc::S_IFDIR | 0o111
} else {
libc::S_IFREG
};
mode |= if self.file_attributes & FILE_ATTRIBUTE_READONLY != 0 {
0o444
} else {
0o666
};
if reparse_tag == IO_REPARSE_TAG_SYMLINK {
mode = (mode & !libc::S_IFMT) | S_IFLNK;
}
let to_time = |ticks: u64| -> (libc::time_t, i32) {
let ticks = ticks as i64;
(
(ticks / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t,
((ticks % 10_000_000) * 100) as i32,
)
};
let (birthtime, birthtime_nsec) = to_time(self.creation_ticks);
let (mtime, mtime_nsec) = to_time(self.last_write_ticks);
let (atime, atime_nsec) = to_time(self.last_access_ticks);
crate::fileutils::StatStruct {
st_dev: 0,
st_ino: 0,
st_mode: mode as libc::c_ushort,
st_nlink: 0,
st_uid: 0,
st_gid: 0,
st_rdev: 0,
st_size: self.file_size,
st_atime: atime,
st_atime_nsec: atime_nsec,
st_mtime: mtime,
st_mtime_nsec: mtime_nsec,
st_ctime: birthtime,
st_ctime_nsec: birthtime_nsec,
st_birthtime: birthtime,
st_birthtime_nsec: birthtime_nsec,
st_file_attributes: self.file_attributes as libc::c_ulong,
st_reparse_tag: reparse_tag,
st_ino_high: 0,
}
}
}
pub struct Scandir {
handle: isize,
pending: Option<WIN32_FIND_DATAW>,
dir: Vec<u16>,
}
impl core::fmt::Debug for Scandir {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Scandir").finish_non_exhaustive()
}
}
pub fn scandir(path: &widestring::WideCStr) -> io::Result<Scandir> {
let dir = path.as_slice().to_vec();
let mut mask = join_path_filename(&dir, &[b'*' as u16, b'.' as u16, b'*' as u16]);
mask.push(0);
let mut data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(mask.as_ptr(), &mut data) };
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
Ok(Scandir {
handle: handle as isize,
pending: Some(data),
dir,
})
}
impl Scandir {
pub fn next_entry(&mut self) -> io::Result<Option<ScandirEntry>> {
if self.handle == INVALID_HANDLE_VALUE as isize {
return Ok(None);
}
let data = if let Some(data) = self.pending.take() {
data
} else {
let mut data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
if unsafe { FindNextFileW(self.handle as HANDLE, &mut data) } == 0 {
let error = io::Error::last_os_error();
self.close();
return if error.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32) {
Ok(None)
} else {
Err(error)
};
}
data
};
Ok(Some(ScandirEntry::from_find_data(&data)))
}
pub fn dir_units(&self) -> &[u16] {
&self.dir
}
pub fn entry_path(&self, entry: &ScandirEntry) -> OsString {
use std::os::windows::ffi::OsStrExt;
OsString::from_wide(&join_path_filename(
&self.dir,
&entry.name.encode_wide().collect::<Vec<_>>(),
))
}
pub fn close(&mut self) {
if self.handle != INVALID_HANDLE_VALUE as isize {
unsafe { FindClose(self.handle as HANDLE) };
self.handle = INVALID_HANDLE_VALUE as isize;
}
self.pending = None;
}
}
impl Drop for Scandir {
fn drop(&mut self) {
self.close();
}
}
pub fn path_isdevdrive(path: &widestring::WideCStr) -> io::Result<bool> {
const PERSISTENT_VOLUME_STATE_DEV_VOLUME: u32 = 0x0000_2000;
#[repr(C)]
struct FileFsPersistentVolumeInformation {
volume_flags: u32,
flag_mask: u32,
version: u32,
reserved: u32,
}
let mut volume = [0u16; MAX_PATH as usize];
unsafe { GetVolumePathNameW(path.as_ptr(), volume.as_mut_ptr(), volume.len() as _) }
.check_win32_bool()?;
if unsafe { GetDriveTypeW(volume.as_ptr()) } != DRIVE_FIXED {
return Ok(false);
}
let handle = unsafe {
CreateFileW(
volume.as_ptr(),
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
}
.check_valid()?;
let mut volume_state = FileFsPersistentVolumeInformation {
volume_flags: 0,
flag_mask: PERSISTENT_VOLUME_STATE_DEV_VOLUME,
version: 1,
reserved: 0,
};
let ok = unsafe {
DeviceIoControl(
handle,
FSCTL_QUERY_PERSISTENT_VOLUME_STATE,
(&volume_state as *const FileFsPersistentVolumeInformation).cast(),
core::mem::size_of::<FileFsPersistentVolumeInformation>() as u32,
(&mut volume_state as *mut FileFsPersistentVolumeInformation).cast(),
core::mem::size_of::<FileFsPersistentVolumeInformation>() as u32,
core::ptr::null_mut(),
core::ptr::null_mut(),
)
};
unsafe { CloseHandle(handle) };
if ok == 0 {
let err = io::Error::last_os_error();
if err.raw_os_error()
== Some(windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER as i32)
{
return Ok(false);
}
return Err(err);
}
Ok((volume_state.volume_flags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) != 0)
}
pub const fn is_reparse_tag_name_surrogate(tag: u32) -> bool {
(tag & 0x20000000) != 0
}
pub const fn file_info_error_is_trustworthy(error: u32) -> bool {
matches!(
error,
ERROR_FILE_NOT_FOUND
| ERROR_PATH_NOT_FOUND
| ERROR_NOT_READY
| ERROR_BAD_NET_NAME
| ERROR_BAD_NETPATH
| ERROR_BAD_PATHNAME
| ERROR_INVALID_NAME
| ERROR_FILENAME_EXCED_RANGE
)
}
pub const fn test_info(
attributes: u32,
reparse_tag: u32,
disk_device: bool,
tested_type: TestType,
) -> bool {
match tested_type {
TestType::RegularFile => {
disk_device && attributes != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0
}
TestType::Directory => (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0,
TestType::Symlink => {
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
&& reparse_tag == IO_REPARSE_TAG_SYMLINK
}
TestType::Junction => {
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
&& reparse_tag == IO_REPARSE_TAG_MOUNT_POINT
}
TestType::LinkReparsePoint => {
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
&& is_reparse_tag_name_surrogate(reparse_tag)
}
TestType::RegularReparsePoint => {
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
&& reparse_tag != 0
&& !is_reparse_tag_name_surrogate(reparse_tag)
}
}
}
pub fn test_file_type_by_handle(handle: HANDLE, tested_type: TestType, disk_only: bool) -> bool {
let disk_device = unsafe { GetFileType(handle) } == FILE_TYPE_DISK;
if disk_only && !disk_device {
return false;
}
if tested_type != TestType::RegularFile && tested_type != TestType::Directory {
let mut info: FILE_ATTRIBUTE_TAG_INFO = unsafe { core::mem::zeroed() };
let ret = unsafe {
GetFileInformationByHandleEx(
handle,
FileAttributeTagInfoClass,
(&mut info as *mut FILE_ATTRIBUTE_TAG_INFO).cast(),
core::mem::size_of::<FILE_ATTRIBUTE_TAG_INFO>() as u32,
)
};
if ret == 0 {
return false;
}
test_info(
info.FileAttributes,
info.ReparseTag,
disk_device,
tested_type,
)
} else {
let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() };
let ret = unsafe {
GetFileInformationByHandleEx(
handle,
FileBasicInfo,
(&mut info as *mut FILE_BASIC_INFO).cast(),
core::mem::size_of::<FILE_BASIC_INFO>() as u32,
)
};
if ret == 0 {
return false;
}
test_info(info.FileAttributes, 0, disk_device, tested_type)
}
}
fn win32_xstat_attributes_from_dir(
path: &widestring::WideCStr,
) -> io::Result<(BY_HANDLE_FILE_INFORMATION, u32)> {
let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(path.as_ptr(), &mut find_data) }.check_valid()?;
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: &widestring::WideCStr, traverse: bool) -> io::Result<StatStruct> {
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(
path.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 = io::Error::last_os_error();
match error.raw_os_error().unwrap_or(0) as u32 {
ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => {
let (info, reparse_tag) = win32_xstat_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(
path.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(
path.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 = (|| -> 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 = io::Error::last_os_error();
if err.raw_os_error().unwrap_or(0) != 0 {
return Err(err);
}
}
let file_attributes = unsafe { GetFileAttributesW(path.as_ptr()) };
let mut st_mode = 0;
if file_attributes != INVALID_FILE_ATTRIBUTES
&& file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0
{
st_mode = S_IFDIR_MODE;
} else if file_type == FILE_TYPE_CHAR {
st_mode = S_IFCHR_MODE;
} else if file_type == FILE_TYPE_PIPE {
st_mode = S_IFIFO_MODE;
}
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,
FileAttributeTagInfoClass,
(&mut local_tag_info as *mut FileAttributeTagInfo).cast(),
core::mem::size_of::<FileAttributeTagInfo>() as u32,
)
};
if ret == 0 {
match io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32 {
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(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(io::Error::from_raw_os_error(
ERROR_CANT_ACCESS_FILE as i32,
));
}
} else if !is_unhandled_tag {
unsafe { CloseHandle(h_file) };
h_file = INVALID_HANDLE_VALUE;
return win32_xstat_slow_impl(path, true);
}
}
tag_info = local_tag_info;
}
if unsafe { GetFileInformationByHandle(h_file, &mut file_info) } == 0 {
match io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32 {
ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => {
return Ok(StatStruct {
st_mode: 0x6000,
..Default::default()
});
}
_ => return Err(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 FILE_BASIC_INFO).cast(),
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 FILE_ID_INFO).cast(),
core::mem::size_of::<FILE_ID_INFO>() as u32,
)
} != 0;
let mut result = win32_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.to_os_string(), file_info.dwFileAttributes);
Ok(result)
} else {
let mut result =
win32_attribute_data_to_stat(&file_info, tag_info.reparse_tag, None, None);
result.update_st_mode_from_path(&path.to_os_string(), file_info.dwFileAttributes);
Ok(result)
}
})();
if h_file != INVALID_HANDLE_VALUE {
unsafe { CloseHandle(h_file) };
}
result
}
pub fn win32_xstat(path: &widestring::WideCStr, traverse: bool) -> io::Result<StatStruct> {
match get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo) {
Ok(stat_info) => {
if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == 0)
|| (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag))
{
let mut result = stat_basic_info_to_stat(&stat_info);
if result.st_ino != 0 || result.st_ino_high != 0 {
result.update_st_mode_from_path(&path.to_os_string(), stat_info.FileAttributes);
result.st_ctime = result.st_birthtime;
result.st_ctime_nsec = result.st_birthtime_nsec;
return Ok(result);
}
}
}
Err(err) => {
if let Some(errno) = err.raw_os_error()
&& matches!(
errno as u32,
ERROR_FILE_NOT_FOUND
| ERROR_PATH_NOT_FOUND
| ERROR_NOT_READY
| ERROR_BAD_NET_NAME
)
{
return Err(err);
}
}
}
let mut result = win32_xstat_slow_impl(path, traverse)?;
result.st_ctime = result.st_birthtime;
result.st_ctime_nsec = result.st_birthtime_nsec;
Ok(result)
}
pub fn test_file_type_by_name(path: &widestring::WideCStr, tested_type: TestType) -> bool {
match get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo) {
Ok(info) => {
let disk_device = matches!(
info.DeviceType,
FILE_DEVICE_DISK | FILE_DEVICE_VIRTUAL_DISK | FILE_DEVICE_CD_ROM
);
let result = test_info(
info.FileAttributes,
info.ReparseTag,
disk_device,
tested_type,
);
if !result
|| !matches!(tested_type, TestType::RegularFile | TestType::Directory)
|| (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0
{
return result;
}
}
Err(err) => {
if let Some(code) = err.raw_os_error()
&& file_info_error_is_trustworthy(code as u32)
{
return false;
}
}
}
let mut flags = FILE_FLAG_BACKUP_SEMANTICS;
if !matches!(tested_type, TestType::RegularFile | TestType::Directory) {
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
let handle = unsafe {
CreateFileW(
path.as_ptr(),
FILE_READ_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
flags,
core::ptr::null_mut(),
)
};
if handle != INVALID_HANDLE_VALUE {
let result = test_file_type_by_handle(handle, tested_type, false);
unsafe { CloseHandle(handle) };
return result;
}
match unsafe { GetLastError() } {
windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED
| windows_sys::Win32::Foundation::ERROR_SHARING_VIOLATION
| windows_sys::Win32::Foundation::ERROR_CANT_ACCESS_FILE
| windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER => {
let stat = win32_xstat(
path,
matches!(tested_type, TestType::RegularFile | TestType::Directory),
);
if let Ok(st) = stat {
let disk_device = (st.st_mode & libc::S_IFREG as u16) != 0;
return test_info(
st.st_file_attributes,
st.st_reparse_tag,
disk_device,
tested_type,
);
}
}
_ => {}
}
false
}
pub fn test_file_exists_by_name(path: &widestring::WideCStr, follow_links: bool) -> bool {
match get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo) {
Ok(info) => {
if (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0
|| (!follow_links && is_reparse_tag_name_surrogate(info.ReparseTag))
{
return true;
}
}
Err(err) => {
if let Some(code) = err.raw_os_error()
&& file_info_error_is_trustworthy(code as u32)
{
return false;
}
}
}
let mut flags = FILE_FLAG_BACKUP_SEMANTICS;
if !follow_links {
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
let handle = unsafe {
CreateFileW(
path.as_ptr(),
FILE_READ_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
flags,
core::ptr::null_mut(),
)
};
if handle != INVALID_HANDLE_VALUE {
if follow_links {
unsafe { CloseHandle(handle) };
return true;
}
let is_regular_reparse_point =
test_file_type_by_handle(handle, TestType::RegularReparsePoint, false);
unsafe { CloseHandle(handle) };
if !is_regular_reparse_point {
return true;
}
let handle = unsafe {
CreateFileW(
path.as_ptr(),
FILE_READ_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if handle != INVALID_HANDLE_VALUE {
unsafe { CloseHandle(handle) };
return true;
}
}
match unsafe { GetLastError() } {
ERROR_ACCESS_DENIED
| ERROR_SHARING_VIOLATION
| ERROR_CANT_ACCESS_FILE
| ERROR_INVALID_PARAMETER => {
return win32_xstat(path, follow_links).is_ok();
}
_ => {}
}
false
}
pub fn path_exists_via_open(path: &Path, follow_links: bool) -> bool {
let Ok(wide_path) = WideCString::from_os_str(path.as_os_str()) else {
return false;
};
let mut flags = FILE_FLAG_BACKUP_SEMANTICS;
if !follow_links {
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
let handle = unsafe {
CreateFileW(
wide_path.as_ptr(),
FILE_READ_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
flags,
core::ptr::null_mut(),
)
};
if handle != INVALID_HANDLE_VALUE {
if follow_links {
unsafe { CloseHandle(handle) };
return true;
}
let is_regular_reparse_point =
test_file_type_by_handle(handle, TestType::RegularReparsePoint, false);
unsafe { CloseHandle(handle) };
if !is_regular_reparse_point {
return true;
}
let handle = unsafe {
CreateFileW(
wide_path.as_ptr(),
FILE_READ_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if handle != INVALID_HANDLE_VALUE {
unsafe { CloseHandle(handle) };
return true;
}
}
false
}
pub fn fd_exists(fd: crate::crt_fd::Borrowed<'_>) -> bool {
let handle = match crate::crt_fd::as_handle(fd) {
Ok(handle) => handle,
Err(_) => return false,
};
let file_type = unsafe { GetFileType(handle.as_raw_handle() as _) };
if file_type != FILE_TYPE_UNKNOWN {
true
} else {
unsafe { GetLastError() == 0 }
}
}
pub fn pipe() -> io::Result<(i32, i32)> {
use std::os::windows::io::{AsRawHandle, IntoRawHandle};
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
use windows_sys::Win32::System::Pipes::CreatePipe;
let mut attr = SECURITY_ATTRIBUTES {
nLength: core::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: core::ptr::null_mut(),
bInheritHandle: 0,
};
let (read_handle, write_handle) = unsafe {
let mut read = core::mem::MaybeUninit::<isize>::uninit();
let mut write = core::mem::MaybeUninit::<isize>::uninit();
CreatePipe(
read.as_mut_ptr() as *mut _,
write.as_mut_ptr() as *mut _,
&mut attr as *mut _,
0,
)
.check_win32_bool()?;
(read.assume_init() as HANDLE, write.assume_init() as HANDLE)
};
let read_handle = read_handle
.into_owned()
.expect("CreatePipe returned valid read handle");
let write_handle = write_handle
.into_owned()
.expect("CreatePipe returned valid write handle");
const O_NOINHERIT: i32 = 0x80;
let read_fd = crate::msvcrt::open_osfhandle(read_handle.as_raw_handle() as isize, O_NOINHERIT)?;
let _ = read_handle.into_raw_handle();
let write_fd = match crate::msvcrt::open_osfhandle(
write_handle.as_raw_handle() as isize,
crate::os::O_WRONLY | O_NOINHERIT,
) {
Ok(fd) => {
let _ = write_handle.into_raw_handle();
fd
}
Err(err) => {
let _ = unsafe { crt_fd::Owned::from_raw(read_fd) };
return Err(err);
}
};
Ok((read_fd, write_fd))
}
pub fn mkdir(path: &widestring::WideCStr, mode: i32) -> io::Result<()> {
use windows_sys::Win32::Foundation::LocalFree;
use windows_sys::Win32::Security::Authorization::{
ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1,
};
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
let ok = if mode == 0o700 {
let mut sec_attr = SECURITY_ATTRIBUTES {
nLength: core::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: core::ptr::null_mut(),
bInheritHandle: 0,
};
let sddl = w!("D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)");
unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl,
SDDL_REVISION_1,
&mut sec_attr.lpSecurityDescriptor,
core::ptr::null_mut(),
)
}
.check_win32_bool()?;
let ok = unsafe {
windows_sys::Win32::Storage::FileSystem::CreateDirectoryW(
path.as_ptr(),
(&sec_attr as *const SECURITY_ATTRIBUTES).cast(),
)
};
unsafe { LocalFree(sec_attr.lpSecurityDescriptor) };
ok
} else {
unsafe {
windows_sys::Win32::Storage::FileSystem::CreateDirectoryW(
path.as_ptr(),
core::ptr::null_mut(),
)
}
};
ok.check_win32_bool()
}
unsafe extern "C" {
fn _umask(mask: i32) -> i32;
fn _wputenv(envstring: *const u16) -> libc::c_int;
}
pub fn umask(mask: i32) -> io::Result<i32> {
let result = unsafe { _umask(mask) };
if result < 0 {
Err(crate::os::errno_io_error())
} else {
Ok(result)
}
}
pub fn wputenv(envstring: &widestring::WideCStr) -> io::Result<()> {
let result = unsafe { crate::suppress_iph!(_wputenv(envstring.as_ptr())) };
if result != 0 {
Err(crate::os::errno_io_error())
} else {
Ok(())
}
}
fn set_fd_inheritable(fd: i32, inheritable: bool) -> io::Result<()> {
let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) };
let handle = crt_fd::as_handle(borrowed)?;
set_handle_inheritable(handle.as_raw_handle() as _, inheritable)
}
pub fn dup(fd: i32) -> io::Result<i32> {
let fd2 = unsafe { crate::suppress_iph!(libc::dup(fd)) };
if fd2 < 0 {
return Err(crate::os::errno_io_error());
}
if let Err(err) = set_fd_inheritable(fd2, false) {
let _ = unsafe { crt_fd::Owned::from_raw(fd2) };
return Err(err);
}
Ok(fd2)
}
pub fn dup2(fd: i32, fd2: i32, inheritable: bool) -> io::Result<i32> {
let result = unsafe { crate::suppress_iph!(libc::dup2(fd, fd2)) };
if result < 0 {
return Err(crate::os::errno_io_error());
}
if !inheritable && let Err(err) = set_fd_inheritable(fd2, false) {
let _ = unsafe { crt_fd::Owned::from_raw(fd2) };
return Err(err);
}
Ok(fd2)
}
pub fn readlink(path: &widestring::WideCStr) -> Result<OsString, ReadlinkError> {
let handle = unsafe {
CreateFileW(
path.as_ptr(),
0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(ReadlinkError::Io(io::Error::last_os_error()));
}
const BUFFER_SIZE: usize = 16384;
let mut buffer = vec![0u8; BUFFER_SIZE];
let mut bytes_returned: u32 = 0;
let ok = unsafe {
DeviceIoControl(
handle,
FSCTL_GET_REPARSE_POINT,
core::ptr::null(),
0,
buffer.as_mut_ptr() as *mut _,
BUFFER_SIZE as u32,
&mut bytes_returned,
core::ptr::null_mut(),
)
};
unsafe { CloseHandle(handle) };
if ok == 0 {
return Err(ReadlinkError::Io(io::Error::last_os_error()));
}
let reparse_tag = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
let (substitute_offset, substitute_length, path_buffer_start) =
if reparse_tag == IO_REPARSE_TAG_SYMLINK {
(
u16::from_le_bytes([buffer[8], buffer[9]]) as usize,
u16::from_le_bytes([buffer[10], buffer[11]]) as usize,
20usize,
)
} else if reparse_tag == IO_REPARSE_TAG_MOUNT_POINT {
(
u16::from_le_bytes([buffer[8], buffer[9]]) as usize,
u16::from_le_bytes([buffer[10], buffer[11]]) as usize,
16usize,
)
} else {
return Err(ReadlinkError::NotSymbolicLink);
};
let path_start = path_buffer_start + substitute_offset;
let path_end = path_start + substitute_length;
if path_end > buffer.len() {
return Err(ReadlinkError::InvalidReparseData);
}
let path_slice = &buffer[path_start..path_end];
let mut wide_chars: Vec<u16> = path_slice
.as_chunks::<2>()
.0
.iter()
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
if wide_chars.len() > 4
&& wide_chars[0] == b'\\' as u16
&& wide_chars[1] == b'?' as u16
&& wide_chars[2] == b'?' as u16
&& wide_chars[3] == b'\\' as u16
{
wide_chars[1] = b'\\' as u16;
}
Ok(OsString::from_wide(&wide_chars))
}
pub fn kill(pid: u32, sig: u32) -> io::Result<()> {
if sig == Console::CTRL_C_EVENT || sig == Console::CTRL_BREAK_EVENT {
let ok = unsafe { Console::GenerateConsoleCtrlEvent(sig, pid) };
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
} else {
let handle = unsafe { Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid) }
.check_nonnull()?;
let result = unsafe { Threading::TerminateProcess(handle, sig) }.check_win32_bool();
unsafe { CloseHandle(handle) };
result
}
}
pub fn getfinalpathname(path: &widestring::WideCStr) -> io::Result<OsString> {
use windows_sys::Win32::Storage::FileSystem::{GetFinalPathNameByHandleW, VOLUME_NAME_DOS};
let handle = unsafe {
CreateFileW(
path.as_ptr(),
0,
0,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
}
.check_valid()?;
let mut buffer = vec![0u16; MAX_PATH as usize];
let result = loop {
let ret = unsafe {
GetFinalPathNameByHandleW(
handle,
buffer.as_mut_ptr(),
buffer.len() as u32,
VOLUME_NAME_DOS,
)
};
if ret == 0 {
break Err(io::Error::last_os_error());
}
if ret as usize >= buffer.len() {
buffer.resize(ret as usize, 0);
continue;
}
break Ok(OsString::from_wide(&buffer[..ret as usize]));
};
unsafe { CloseHandle(handle) };
result
}
pub fn getfullpathname(path: &widestring::WideCStr) -> io::Result<OsString> {
let mut buffer = vec![0u16; MAX_PATH as usize];
let mut ret = unsafe {
windows_sys::Win32::Storage::FileSystem::GetFullPathNameW(
path.as_ptr(),
buffer.len() as u32,
buffer.as_mut_ptr(),
core::ptr::null_mut(),
)
}
.check_ne(0)?;
if ret as usize > buffer.len() {
buffer.resize(ret as usize, 0);
ret = unsafe {
windows_sys::Win32::Storage::FileSystem::GetFullPathNameW(
path.as_ptr(),
buffer.len() as u32,
buffer.as_mut_ptr(),
core::ptr::null_mut(),
)
}
.check_ne(0)?;
}
let _ = ret;
Ok(widestring::WideCString::from_vec_truncate(buffer).to_os_string())
}
pub fn getvolumepathname(path: &widestring::WideCStr) -> io::Result<OsString> {
let buflen = core::cmp::max(path.len(), MAX_PATH as usize);
let mut buffer = vec![0u16; buflen];
unsafe {
windows_sys::Win32::Storage::FileSystem::GetVolumePathNameW(
path.as_ptr(),
buffer.as_mut_ptr(),
buflen as u32,
)
}
.check_win32_bool()?;
Ok(widestring::WideCString::from_vec_truncate(buffer).to_os_string())
}
pub fn getdiskusage(path: &widestring::WideCStr) -> io::Result<(u64, u64)> {
let mut free_to_me = 0u64;
let mut total = 0u64;
let mut free = 0u64;
let ok = unsafe { GetDiskFreeSpaceExW(path.as_ptr(), &mut free_to_me, &mut total, &mut free) };
if ok != 0 {
return Ok((total, free));
}
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_DIRECTORY as i32)
&& let Some(parent) = Path::new(&path.to_os_string()).parent()
{
let parent = widestring::WideCString::from_os_str(parent)
.expect("interior NULs are impossible because parent was constructed from a WideCStr");
let ok =
unsafe { GetDiskFreeSpaceExW(parent.as_ptr(), &mut free_to_me, &mut total, &mut free) };
if ok != 0 {
return Ok((total, free));
}
}
Err(err)
}
pub fn get_handle_inheritable(handle: intptr_t) -> io::Result<bool> {
let mut flags = 0;
let ok = unsafe { GetHandleInformation(handle as _, &mut flags) };
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(flags & HANDLE_FLAG_INHERIT != 0)
}
}
pub fn set_handle_inheritable(handle: intptr_t, inheritable: bool) -> io::Result<()> {
let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 };
let ok = unsafe { SetHandleInformation(handle as _, HANDLE_FLAG_INHERIT, flags) };
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub fn getlogin() -> io::Result<String> {
let mut buffer = [0u16; 257];
let mut size = buffer.len() as u32;
let ok = unsafe { GetUserNameW(buffer.as_mut_ptr(), &mut size) };
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(OsString::from_wide(&buffer[..(size - 1) as usize])
.to_str()
.unwrap()
.to_string())
}
pub fn listdrives() -> io::Result<Vec<OsString>> {
let mut buffer = [0u16; 256];
let len = unsafe { GetLogicalDriveStringsW(buffer.len() as u32, buffer.as_mut_ptr()) };
if len == 0 {
return Err(io::Error::last_os_error());
}
if len as usize >= buffer.len() {
return Err(io::Error::from_raw_os_error(ERROR_MORE_DATA as i32));
}
Ok(buffer[..(len - 1) as usize]
.split(|&c| c == 0)
.map(OsString::from_wide)
.collect())
}
pub fn listvolumes() -> io::Result<Vec<OsString>> {
let mut result = Vec::new();
let mut buffer = [0u16; MAX_PATH as usize + 1];
let find = unsafe {
windows_sys::Win32::Storage::FileSystem::FindFirstVolumeW(
buffer.as_mut_ptr(),
buffer.len() as u32,
)
};
if find == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
loop {
let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
result.push(OsString::from_wide(&buffer[..len]));
let ok = unsafe {
windows_sys::Win32::Storage::FileSystem::FindNextVolumeW(
find,
buffer.as_mut_ptr(),
buffer.len() as u32,
)
};
if ok == 0 {
let err = io::Error::last_os_error();
unsafe { windows_sys::Win32::Storage::FileSystem::FindVolumeClose(find) };
if err.raw_os_error()
== Some(windows_sys::Win32::Foundation::ERROR_NO_MORE_FILES as i32)
{
break;
}
return Err(err);
}
}
Ok(result)
}
pub fn listmounts(volume: &widestring::WideCStr) -> io::Result<Vec<OsString>> {
let mut buflen: u32 = MAX_PATH + 1;
let mut buffer = vec![0u16; buflen as usize];
loop {
let ok = unsafe {
GetVolumePathNamesForVolumeNameW(
volume.as_ptr(),
buffer.as_mut_ptr(),
buflen,
&mut buflen,
)
};
if ok != 0 {
break;
}
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_MORE_DATA as i32) {
buffer.resize(buflen as usize, 0);
continue;
}
return Err(err);
}
let mut result = Vec::new();
let mut start = 0;
for (i, &c) in buffer.iter().enumerate() {
if c == 0 {
if i > start {
result.push(OsString::from_wide(&buffer[start..i]));
}
start = i + 1;
if start < buffer.len() && buffer[start] == 0 {
break;
}
}
}
Ok(result)
}
pub fn getppid() -> u32 {
use windows_sys::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
type NtQueryInformationProcessFn = unsafe extern "system" fn(
process_handle: isize,
process_information_class: u32,
process_information: *mut core::ffi::c_void,
process_information_length: u32,
return_length: *mut u32,
) -> i32;
let ntdll = unsafe {
windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(windows_sys::w!("ntdll.dll"))
};
if ntdll.is_null() {
return 0;
}
let func = unsafe {
windows_sys::Win32::System::LibraryLoader::GetProcAddress(
ntdll,
c"NtQueryInformationProcess".as_ptr() as *const u8,
)
};
let Some(func) = func else {
return 0;
};
let nt_query: NtQueryInformationProcessFn = unsafe { core::mem::transmute(func) };
let mut info: PROCESS_BASIC_INFORMATION = unsafe { core::mem::zeroed() };
let status = unsafe {
nt_query(
GetCurrentProcess() as isize,
0,
(&mut info as *mut PROCESS_BASIC_INFORMATION).cast(),
core::mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32,
core::ptr::null_mut(),
)
};
if status >= 0
&& info.InheritedFromUniqueProcessId != 0
&& info.InheritedFromUniqueProcessId < u32::MAX as usize
{
info.InheritedFromUniqueProcessId as u32
} else {
0
}
}
pub fn path_skip_root(path: &widestring::WideCStr) -> Option<usize> {
let mut end: *const u16 = core::ptr::null();
let hr = unsafe { windows_sys::Win32::UI::Shell::PathCchSkipRoot(path.as_ptr(), &mut end) };
if hr >= 0 {
assert!(!end.is_null());
Some(
unsafe { end.offset_from(path.as_ptr()) }
.try_into()
.expect("len must be non-negative"),
)
} else {
None
}
}
pub fn get_terminal_size_handle(h: HANDLE) -> io::Result<(usize, usize)> {
let mut csbi = core::mem::MaybeUninit::uninit();
let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) };
if ret == 0 {
let err = unsafe { GetLastError() };
if err != ERROR_ACCESS_DENIED {
return Err(io::Error::last_os_error());
}
let conout = w!("CONOUT$");
let console_handle = unsafe {
CreateFileW(
conout,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
0,
core::ptr::null_mut(),
)
};
if console_handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let ret = unsafe { Console::GetConsoleScreenBufferInfo(console_handle, csbi.as_mut_ptr()) };
unsafe { CloseHandle(console_handle) };
if ret == 0 {
return Err(io::Error::last_os_error());
}
}
let csbi = unsafe { csbi.assume_init() };
let window = csbi.srWindow;
let columns = (window.Right - window.Left + 1) as usize;
let lines = (window.Bottom - window.Top + 1) as usize;
Ok((columns, lines))
}
pub fn handle_from_fd(fd: i32) -> HANDLE {
unsafe { crate::suppress_iph!(libc::get_osfhandle(fd)) as HANDLE }
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FileInformation {
pub volume_serial_number: u32,
pub file_index_high: u32,
pub file_index_low: u32,
}
pub fn get_file_information(handle: HANDLE) -> io::Result<FileInformation> {
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() };
unsafe { GetFileInformationByHandle(handle, &mut info) }.check_win32_bool()?;
Ok(FileInformation {
volume_serial_number: info.dwVolumeSerialNumber,
file_index_high: info.nFileIndexHigh,
file_index_low: info.nFileIndexLow,
})
}
pub fn console_type(handle: HANDLE) -> char {
if is_invalid_handle(handle) {
return '\0';
}
let mut mode: u32 = 0;
if unsafe { Console::GetConsoleMode(handle, &mut mode) } == 0 {
return '\0';
}
let mut peek_count: u32 = 0;
if unsafe { Console::GetNumberOfConsoleInputEvents(handle, &mut peek_count) } != 0 {
'r'
} else {
'w'
}
}
pub fn is_invalid_handle(handle: Handle) -> bool {
handle == INVALID_HANDLE_VALUE || handle.is_null()
}
pub fn console_type_from_fd(fd: i32) -> char {
if fd < 0 {
'\0'
} else {
console_type(handle_from_fd(fd))
}
}
pub fn console_type_from_name(name: &str) -> char {
if name.eq_ignore_ascii_case("CONIN$") {
return 'r';
}
if name.eq_ignore_ascii_case("CONOUT$") {
return 'w';
}
if name.eq_ignore_ascii_case("CON") {
return 'x';
}
let wide: Vec<u16> = name.encode_utf16().chain(core::iter::once(0)).collect();
let mut buf = [0u16; MAX_PATH as usize];
let length = unsafe {
GetFullPathNameW(
wide.as_ptr(),
buf.len() as u32,
buf.as_mut_ptr(),
core::ptr::null_mut(),
)
};
if length == 0 || length as usize > buf.len() {
return '\0';
}
let full_path = &buf[..length as usize];
let path_part = if full_path.len() >= 4
&& full_path[0] == b'\\' as u16
&& full_path[1] == b'\\' as u16
&& (full_path[2] == b'.' as u16 || full_path[2] == b'?' as u16)
&& full_path[3] == b'\\' as u16
{
&full_path[4..]
} else {
full_path
};
let path_str = String::from_utf16_lossy(path_part);
if path_str.eq_ignore_ascii_case("CONIN$") {
'r'
} else if path_str.eq_ignore_ascii_case("CONOUT$") {
'w'
} else if path_str.eq_ignore_ascii_case("CON") {
'x'
} else {
'\0'
}
}
fn copy_from_small_buf(buf: &mut [u8; 4], dest: &mut [u8]) -> usize {
let mut n = 0;
while buf[0] != 0 && n < dest.len() {
dest[n] = buf[0];
n += 1;
for i in 1..buf.len() {
buf[i - 1] = buf[i];
}
buf[buf.len() - 1] = 0;
}
n
}
fn find_last_utf8_boundary(buf: &[u8], len: usize) -> usize {
let len = len.min(buf.len());
for count in 1..=3.min(len) {
let c = buf[len - count];
if c < 0x80 {
return len;
}
if c >= 0xc0 {
let incomplete = if c < 0xe0 {
count < 2
} else if c < 0xf0 {
count < 3
} else {
c < 0xf8
};
if incomplete {
return len - count;
}
return len;
}
}
len
}
fn wchar_to_utf8_count(data: &[u8], mut len: usize, mut n: u32) -> usize {
let mut start: usize = 0;
loop {
let mut mid = 0;
for i in (len / 2)..=len {
mid = find_last_utf8_boundary(data, i);
if mid != 0 {
break;
}
}
if mid == len {
return start + len;
}
if mid == 0 {
mid = if len > 1 { len - 1 } else { 1 };
}
let wlen = unsafe {
MultiByteToWideChar(
CP_UTF8,
0,
data[start..].as_ptr(),
mid as i32,
core::ptr::null_mut(),
0,
)
} as u32;
if wlen <= n {
start += mid;
len -= mid;
n -= wlen;
} else {
len = mid;
}
}
}
pub fn read_console_into(
handle: HANDLE,
dest: &mut [u8],
smallbuf: &mut [u8; 4],
) -> Result<usize, ReadConsoleError> {
if dest.is_empty() {
return Ok(0);
}
let mut wlen = (dest.len() / 4) as u32;
if wlen == 0 {
wlen = 1;
}
let mut read_len = copy_from_small_buf(smallbuf, dest);
if read_len > 0 {
wlen = wlen.saturating_sub(1);
}
if read_len >= dest.len() || wlen == 0 {
return Ok(read_len);
}
let mut wbuf = vec![0u16; wlen as usize];
let mut nread: u32 = 0;
if unsafe {
Console::ReadConsoleW(
handle,
wbuf.as_mut_ptr().cast(),
wlen,
&mut nread,
core::ptr::null(),
)
} == 0
{
return Err(ReadConsoleError::Io(io::Error::last_os_error()));
}
if nread == 0 || wbuf[0] == 0x1A {
return Ok(read_len);
}
let remaining = dest.len() - read_len;
let u8n = if remaining < 4 {
let converted = unsafe {
WideCharToMultiByte(
CP_UTF8,
0,
wbuf.as_ptr(),
nread as i32,
smallbuf.as_mut_ptr().cast(),
smallbuf.len() as i32,
core::ptr::null(),
core::ptr::null_mut(),
)
};
if converted > 0 {
copy_from_small_buf(smallbuf, &mut dest[read_len..]) as i32
} else {
0
}
} else {
unsafe {
WideCharToMultiByte(
CP_UTF8,
0,
wbuf.as_ptr(),
nread as i32,
dest[read_len..].as_mut_ptr().cast(),
remaining as i32,
core::ptr::null(),
core::ptr::null_mut(),
)
}
};
if u8n > 0 {
read_len += u8n as usize;
return Ok(read_len);
}
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_INSUFFICIENT_BUFFER as i32) {
let needed = unsafe {
WideCharToMultiByte(
CP_UTF8,
0,
wbuf.as_ptr(),
nread as i32,
core::ptr::null_mut(),
0,
core::ptr::null(),
core::ptr::null_mut(),
)
};
if needed > 0 {
return Err(ReadConsoleError::BufferTooSmall {
available: remaining,
required: needed as usize,
});
}
}
Err(ReadConsoleError::Io(err))
}
pub fn read_console_all(handle: HANDLE, smallbuf: &mut [u8; 4]) -> io::Result<Vec<u8>> {
let mut result = Vec::new();
let mut tmp = [0u8; 4];
let n = copy_from_small_buf(smallbuf, &mut tmp);
result.extend_from_slice(&tmp[..n]);
let mut wbuf = vec![0u16; 8192];
loop {
let mut nread: u32 = 0;
if unsafe {
Console::ReadConsoleW(
handle,
wbuf.as_mut_ptr().cast(),
wbuf.len() as u32,
&mut nread,
core::ptr::null(),
)
} == 0
{
return Err(io::Error::last_os_error());
}
if nread == 0 || wbuf[0] == 0x1A {
break;
}
let needed = unsafe {
WideCharToMultiByte(
CP_UTF8,
0,
wbuf.as_ptr(),
nread as i32,
core::ptr::null_mut(),
0,
core::ptr::null(),
core::ptr::null_mut(),
)
};
if needed == 0 {
return Err(io::Error::last_os_error());
}
let offset = result.len();
result.resize(offset + needed as usize, 0);
if unsafe {
WideCharToMultiByte(
CP_UTF8,
0,
wbuf.as_ptr(),
nread as i32,
result[offset..].as_mut_ptr().cast(),
needed,
core::ptr::null(),
core::ptr::null_mut(),
)
} == 0
{
return Err(io::Error::last_os_error());
}
if nread < wbuf.len() as u32 {
break;
}
}
Ok(result)
}
pub fn write_console_utf8(handle: HANDLE, data: &[u8], max_bytes: usize) -> io::Result<usize> {
if data.is_empty() {
return Ok(0);
}
let mut len = data.len().min(max_bytes);
let max_wlen: u32 = 32766 / 2;
len = len.min(max_wlen as usize * 3);
let wlen = loop {
len = find_last_utf8_boundary(data, len);
let wlen = unsafe {
MultiByteToWideChar(
CP_UTF8,
0,
data.as_ptr(),
len as i32,
core::ptr::null_mut(),
0,
)
};
if wlen as u32 <= max_wlen {
break wlen;
}
len /= 2;
};
if wlen == 0 {
return Ok(0);
}
let mut wbuf = vec![0u16; wlen as usize];
let wlen = unsafe {
MultiByteToWideChar(
CP_UTF8,
0,
data.as_ptr(),
len as i32,
wbuf.as_mut_ptr(),
wlen,
)
};
if wlen == 0 {
return Err(io::Error::last_os_error());
}
let mut written: u32 = 0;
if unsafe {
Console::WriteConsoleW(
handle,
wbuf.as_ptr().cast(),
wlen as u32,
&mut written,
core::ptr::null(),
)
} == 0
{
return Err(io::Error::last_os_error());
}
if written < wlen as u32 {
len = wchar_to_utf8_count(data, len, written);
}
Ok(len)
}
pub fn open_console_path_fd(path: &widestring::WideCStr, writable: bool) -> io::Result<i32> {
let access = if writable {
GENERIC_WRITE
} else {
GENERIC_READ
};
let mut handle = unsafe {
CreateFileW(
path.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
0,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
handle = unsafe {
CreateFileW(
path.as_ptr(),
access,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
0,
core::ptr::null_mut(),
)
};
}
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let osf_flags = if writable {
crate::os::O_WRONLY | crate::os::O_BINARY | 0x80
} else {
crate::os::O_RDONLY | crate::os::O_BINARY | 0x80
};
match crate::msvcrt::open_osfhandle(handle as isize, osf_flags) {
Ok(fd) => Ok(fd),
Err(err) => {
unsafe { CloseHandle(handle) };
Err(err)
}
}
}
#[cfg(target_env = "msvc")]
pub fn cwait(pid: intptr_t, opt: i32) -> io::Result<(intptr_t, i32)> {
let mut status = 0;
let pid = unsafe { crate::suppress_iph!(_cwait(&mut status, pid, opt)) };
if pid == -1 {
Err(crate::os::errno_io_error())
} else {
Ok((pid, status))
}
}
#[cfg(target_env = "msvc")]
fn null_terminated_ptrs(strings: &[&widestring::WideCStr]) -> Vec<*const u16> {
strings
.iter()
.map(|s| s.as_ptr())
.chain(core::iter::once(core::ptr::null()))
.collect()
}
#[cfg(target_env = "msvc")]
pub fn spawnv(
mode: i32,
path: &widestring::WideCStr,
argv: &[&widestring::WideCStr],
) -> io::Result<intptr_t> {
crate::os::ensure_drive_current_directory();
let argv_ptrs = null_terminated_ptrs(argv);
let result = unsafe { crate::suppress_iph!(_wspawnv(mode, path.as_ptr(), argv_ptrs.as_ptr())) };
if result == -1 {
Err(crate::os::errno_io_error())
} else {
Ok(result)
}
}
#[cfg(target_env = "msvc")]
pub fn spawnve(
mode: i32,
path: &widestring::WideCStr,
argv: &[&widestring::WideCStr],
envp: &[&widestring::WideCStr],
) -> io::Result<intptr_t> {
crate::os::ensure_drive_current_directory();
let argv_ptrs = null_terminated_ptrs(argv);
let envp_ptrs = null_terminated_ptrs(envp);
let result = unsafe {
crate::suppress_iph!(_wspawnve(
mode,
path.as_ptr(),
argv_ptrs.as_ptr(),
envp_ptrs.as_ptr()
))
};
if result == -1 {
Err(crate::os::errno_io_error())
} else {
Ok(result)
}
}
#[cfg(target_env = "msvc")]
pub fn execv(path: &widestring::WideCStr, argv: &[&widestring::WideCStr]) -> io::Result<()> {
crate::os::ensure_drive_current_directory();
let argv_ptrs = null_terminated_ptrs(argv);
let result = unsafe { crate::suppress_iph!(_wexecv(path.as_ptr(), argv_ptrs.as_ptr())) };
if result == -1 {
Err(crate::os::errno_io_error())
} else {
Ok(())
}
}
#[cfg(target_env = "msvc")]
pub fn execve(
path: &widestring::WideCStr,
argv: &[&widestring::WideCStr],
envp: &[&widestring::WideCStr],
) -> io::Result<()> {
crate::os::ensure_drive_current_directory();
let argv_ptrs = null_terminated_ptrs(argv);
let envp_ptrs = null_terminated_ptrs(envp);
let result = unsafe {
crate::suppress_iph!(_wexecve(
path.as_ptr(),
argv_ptrs.as_ptr(),
envp_ptrs.as_ptr()
))
};
if result == -1 {
Err(crate::os::errno_io_error())
} else {
Ok(())
}
}
#[cfg(test)]
mod find_last_utf8_boundary_tests {
use super::find_last_utf8_boundary;
#[test]
fn keeps_a_complete_tail() {
assert_eq!(find_last_utf8_boundary(b"abc", 3), 3);
assert_eq!(find_last_utf8_boundary("a\u{a2}".as_bytes(), 3), 3);
assert_eq!(find_last_utf8_boundary("a\u{20ac}".as_bytes(), 4), 4);
assert_eq!(find_last_utf8_boundary("a\u{10348}".as_bytes(), 5), 5);
}
#[test]
fn cuts_an_incomplete_tail() {
assert_eq!(find_last_utf8_boundary(&[b'a', 0xc2], 2), 1);
assert_eq!(find_last_utf8_boundary(&[b'a', 0xe2, 0x82], 3), 1);
assert_eq!(find_last_utf8_boundary(&[b'a', 0xf0, 0x90, 0x8d], 4), 1);
}
#[test]
fn leaves_a_byte_that_starts_no_sequence() {
for c in 0xf8..=0xffu8 {
assert_eq!(find_last_utf8_boundary(&[b'a', c], 2), 2, "{c:#04x}");
assert_eq!(find_last_utf8_boundary(&[c], 1), 1, "{c:#04x}");
}
assert_eq!(find_last_utf8_boundary(&[b'a', 0x80, 0x80, 0x80], 4), 4);
}
#[test]
fn handles_an_empty_buffer() {
assert_eq!(find_last_utf8_boundary(b"", 0), 0);
assert_eq!(find_last_utf8_boundary(b"ab", 9), 2);
}
}