Files
Jeong, YunWon ae3804fb21 more hostenv isolation (#7886)
* Convert host_env Windows path/argv params from raw *const u16 to &WideCStr

* Migrate remaining winapi raw u16 pointer signatures to typed references

* Migrate winreg pub unsafe fn string parameters to typed references

* Add ToPyException impls for host_env error types (PyPy wrap_oserror analog)

* Add CheckLibcResult helper and apply to socket/fcntl/shm/posix_wasi

* Add Win32 BOOL/HANDLE check helpers; apply check helpers across host_env

* Apply Win32/libc check helpers to overlapped/testconsole/os.rs

* Apply Win32 check helpers to winapi.rs (partial)

* Apply Win32 check helpers across more winapi.rs functions

* Apply Win32 check helpers to nt.rs (partial)

* Add CheckWin32Sentinel helper; apply to nt.rs INVALID_HANDLE_VALUE/INVALID_FILE_ATTRIBUTES patterns

* Add OwnedHandle / HandleToOwned helper; apply to mmap create_named_mapping leak path

* Use OwnedHandle RAII in nt::pipe to eliminate manual cleanup on error path

* Use OwnedHandle in nt::chmod_follow; hoist HandleToOwned import

* Drop rustix dependency from vm crate

Remove unused IntoPyException impl for rustix::io::Errno and the
rustix entry in crates/vm/Cargo.toml. rustix is now only depended on
by host_env.

* Fix CI failures: cross-platform regressions

- winapi.rs: pass None to create_event_w; the recent Option<&WideCStr>
  migration left one call site still passing a raw null pointer.
- exceptions.rs: gate ToPyException for LockfError with
  cfg(any(unix, target_os = "wasi")), matching host_env::fcntl's own
  cfg. The previous cfg let it compile on wasm32-unknown-unknown where
  host_env::fcntl does not exist.
- io_unsupported.rs: derive Eq on FileMode alongside PartialEq to
  satisfy clippy::derive_partial_eq_without_eq.

* Fix CI failures: cfg gates and unused imports

- exceptions.rs: gate ToPyException for LockfError with
  cfg(all(unix, not(target_os = "redox"))) to match the type's own
  cfg in host_env/src/fcntl.rs (LockfError is not built on wasi).
- signal.rs: CheckLibcResult is only used in unix-gated functions;
  split import so it is not pulled in for windows.
- mmap.rs: remove CheckWin32Handle from imports; no longer used after
  switching to HandleToOwned-based RAII.
- overlapped.rs: remove INVALID_HANDLE_VALUE from connect_pipe import;
  the call now uses .check_valid().

* Fix CI failures: rustfmt and windows unused import

- signal.rs: reorder cfg-gated imports per rustfmt.
- socket.rs: gate ToPyException import to cfg(all(unix, not(target_os = "redox")));
  it is only used inside sendmsg which has the same gate, so it was unused
  on windows.

* Push remaining libc/extern callsites from vm into host_env

Add host_env wrappers and replace the corresponding vm call sites:
- host_env::errno::strerror_string for libc::strerror
- host_env::io::write_stderr_raw for libc::write(STDERR_FILENO,...)
- host_env::locale::localeconv_data reused from vm::format
- host_env::os::abort for the inline abort extern
- host_env::os::urandom wraps getrandom; getrandom moves from vm to host_env
- host_env::posix::lchmod for the macOS/BSD lchmod extern
- host_env::posix::fcopyfile for the macOS fcopyfile extern
- host_env::nt::wputenv for the Windows _wputenv extern

vm/format.rs's get_locale_info now uses host_env on both unix and windows
instead of the unix-only libc::localeconv path.

* Move time tz state and winsound FFI into host_env

- host_env::time::tz: wraps the libc tzset/timezone/daylight/tzname
  globals on non-msvc, non-wasm32 targets. vm::stdlib::time now reads
  these via the typed wrappers instead of declaring its own externs.
- host_env::winsound (windows): exposes PlaySoundW (via a typed
  PlaySoundSource enum), Beep, and MessageBeep. vm::stdlib::winsound
  drops its inline FFI block and routes through host_env.

* Migrate unsetenv to host_env::nt::wputenv; rustfmt

- vm::stdlib::os::unsetenv had a second _wputenv call site that still
  referenced the removed inline extern. Route it through
  host_env::nt::wputenv like putenv.
- rustfmt fixups in exceptions.rs (boolean chain layout) and the two
  winsound files.

* Address PR review comments

- host_env::winapi::create_process: assert that the command_line buffer
  is NUL-terminated and that the env block ends with a double-NUL,
  matching the Win32 CreateProcessW contract.
- stdlib::overlapped CreateEvent: replace WideCString::from_str_truncate
  with the fallible from_str(), so embedded NULs in the event name
  surface as ValueError instead of being silently truncated.
- vm::exceptions::ReadlinkError::NotSymbolicLink now maps to OSError
  (matches Win32 ERROR_NOT_A_REPARSE_POINT semantics) rather than
  ValueError.
- winreg::ConnectRegistry: route the non-zero return through the
  existing os_error_from_windows_code helper so the resulting exception
  carries the real winerror/message instead of a generic OSError.

* Fix CI failures and address review follow-ups

CI failures:
- rustfmt cleanup in exceptions.rs after the ReadlinkError change.
- vm/stdlib/os.rs: drop unused ToWideString import that the wputenv
  migration left behind.
- vm/stdlib/winsound.rs: replace explicit `&*buf` with `&buf` to
  satisfy clippy::explicit_auto_deref.
- Lib/test/test_format.py, Lib/test/test_types.py: drop the now-stale
  expectedFailureIfWindows decorators on the locale-format tests; the
  Windows path now reads real `localeconv` data via host_env so these
  tests pass.

Review follow-ups:
- host_env::winapi::create_process: switch the new buffer terminator
  checks from `assert!` to fallible validators returning
  `io::ErrorKind::InvalidInput`, so bad inputs stay recoverable at
  the API boundary.
- host_env::winsound::play_sound: reject `Memory(_)` together with
  `SND_ASYNC` (lifetime-unsafe) and `SND_MEMORY` without a
  `Memory(_)` source. Expand `PlaySoundError` into a variant enum.
- vm::stdlib::_winapi::CreateProcess: route the Win32 path/argv strings
  through `as_wtf8().to_wide_cstring()` like the rest of the Windows
  API surface; `expect_str()` could panic on Python strings containing
  lone surrogates.
2026-05-20 16:16:14 +09:00

2263 lines
71 KiB
Rust

#![allow(
clippy::not_unsafe_ptr_arg_deref,
reason = "This module mirrors raw Win32 path, handle, and CRT entry points."
)]
// cspell:ignore hchmod
use std::{
ffi::{OsStr, 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, ToWideString},
};
use libc::intptr_t;
use windows_sys::Win32::{
Foundation::{
CloseHandle, ERROR_INVALID_HANDLE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH,
},
Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte},
Storage::FileSystem::{
CreateFileW, FILE_BASIC_INFO, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_READ_ATTRIBUTES, FILE_TYPE_UNKNOWN, FileBasicInfo, FindClose, FindFirstFileW,
GetFileAttributesW, GetFileInformationByHandleEx, GetFileType, GetFullPathNameW,
INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, SetFileAttributesW, SetFileInformationByHandle,
WIN32_FIND_DATAW,
},
System::{Console, Threading},
};
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::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,
};
#[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 IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C;
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,
}
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)
}
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: &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 windows_sys::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_REPARSE_POINT,
};
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 wide = path.as_os_str().to_wide_with_nul();
let attr = unsafe { GetFileAttributesW(wide.as_ptr()) };
attr != INVALID_FILE_ATTRIBUTES
&& (mode & 2 == 0
|| attr & FILE_ATTRIBUTE_READONLY == 0
|| attr & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0)
}
pub fn remove(path: &Path) -> io::Result<()> {
use windows_sys::Win32::Storage::FileSystem::{
DeleteFileW, RemoveDirectoryW, WIN32_FIND_DATAW,
};
use windows_sys::Win32::System::SystemServices::{
IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK,
};
let wide_path = path.as_os_str().to_wide_with_nul();
let attrs = unsafe { GetFileAttributesW(wide_path.as_ptr()) };
let mut is_directory = false;
let mut is_link = false;
if attrs != INVALID_FILE_ATTRIBUTES {
is_directory =
(attrs & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY) != 0;
if is_directory
&& (attrs & windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT) != 0
{
let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(wide_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(wide_path.as_ptr()) }
} else {
unsafe { DeleteFileW(wide_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<()> {
use windows_sys::Win32::Storage::FileSystem::WIN32_FILE_ATTRIBUTE_DATA;
use windows_sys::Win32::Storage::FileSystem::{
CreateSymbolicLinkW, FILE_ATTRIBUTE_DIRECTORY, GetFileAttributesExW,
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE, SYMBOLIC_LINK_FLAG_DIRECTORY,
};
static HAS_UNPRIVILEGED_FLAG: AtomicBool = AtomicBool::new(true);
fn check_dir(src: &Path, dst: &Path) -> bool {
use windows_sys::Win32::Storage::FileSystem::GetFileExInfoStandard;
let Some(dst_parent) = dst.parent() else {
return false;
};
let resolved = if src.is_absolute() {
src.to_path_buf()
} else {
dst_parent.join(src)
};
let wide = match widestring::WideCString::from_os_str(&resolved) {
Ok(wide) => wide,
Err(_) => return false,
};
let mut info: WIN32_FILE_ATTRIBUTE_DATA = unsafe { core::mem::zeroed() };
let ok = unsafe {
GetFileAttributesExW(
wide.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, dst) {
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 { windows_sys::Win32::Foundation::GetLastError() }
== windows_sys::Win32::Foundation::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 { windows_sys::Win32::Foundation::GetLastError() }
!= windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER
{
HAS_UNPRIVILEGED_FLAG.store(false, Ordering::Relaxed);
}
}
if result {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[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: &OsStr, mode: u32, write_bit: u32) -> io::Result<()> {
let wide = path.to_wide_with_nul();
let attr = unsafe { GetFileAttributesW(wide.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(wide.as_ptr(), new_attr) }.check_win32_bool()
}
pub fn chmod_follow(path: &widestring::WideCStr, mode: u32, write_bit: u32) -> io::Result<()> {
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, OPEN_EXISTING,
};
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: &Path) -> io::Result<OsString> {
let wide_path = path.as_os_str().to_wide_with_nul();
let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() };
let handle = unsafe { FindFirstFileW(wide_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 path_isdevdrive(path: &Path) -> io::Result<bool> {
use windows_sys::Win32::Storage::FileSystem::{
FILE_SHARE_READ, FILE_SHARE_WRITE, GetDriveTypeW, GetVolumePathNameW,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
use windows_sys::Win32::System::Ioctl::FSCTL_QUERY_PERSISTENT_VOLUME_STATE;
use windows_sys::Win32::System::WindowsProgramming::DRIVE_FIXED;
const PERSISTENT_VOLUME_STATE_DEV_VOLUME: u32 = 0x0000_2000;
#[repr(C)]
struct FileFsPersistentVolumeInformation {
volume_flags: u32,
flag_mask: u32,
version: u32,
reserved: u32,
}
let wide_path = path.as_os_str().to_wide_with_nul();
let mut volume = [0u16; MAX_PATH as usize];
unsafe { GetVolumePathNameW(wide_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 fn is_reparse_tag_name_surrogate(tag: u32) -> bool {
(tag & 0x20000000) != 0
}
pub fn file_info_error_is_trustworthy(error: u32) -> bool {
use windows_sys::Win32::Foundation;
matches!(
error,
Foundation::ERROR_FILE_NOT_FOUND
| Foundation::ERROR_PATH_NOT_FOUND
| Foundation::ERROR_NOT_READY
| Foundation::ERROR_BAD_NET_NAME
| Foundation::ERROR_BAD_NETPATH
| Foundation::ERROR_BAD_PATHNAME
| Foundation::ERROR_INVALID_NAME
| Foundation::ERROR_FILENAME_EXCED_RANGE
)
}
pub fn test_info(
attributes: u32,
reparse_tag: u32,
disk_device: bool,
tested_type: TestType,
) -> bool {
use windows_sys::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT,
};
use windows_sys::Win32::System::SystemServices::{
IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK,
};
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 {
use windows_sys::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_TAG_INFO, FILE_TYPE_DISK, FileAttributeTagInfo as FileAttributeTagInfoClass,
};
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: &OsStr,
) -> 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,
};
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) }.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: &OsStr, traverse: bool) -> io::Result<StatStruct> {
use windows_sys::Win32::{
Foundation::{
ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION,
ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, GENERIC_READ,
},
Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL,
FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, FILE_ID_INFO, FILE_SHARE_READ,
FILE_SHARE_WRITE, FILE_TYPE_CHAR, FILE_TYPE_PIPE,
FileAttributeTagInfo as FileAttributeTagInfoClass, FileBasicInfo, FileIdInfo,
GetFileAttributesW, GetFileInformationByHandle,
},
};
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 = 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(
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 = (|| -> io::Result<StatStruct> {
if h_file != INVALID_HANDLE_VALUE {
let file_type = unsafe { GetFileType(h_file) };
if file_type != windows_sys::Win32::Storage::FileSystem::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(wide.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, 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, file_info.dwFileAttributes);
Ok(result)
}
})();
if h_file != INVALID_HANDLE_VALUE {
unsafe { CloseHandle(h_file) };
}
result
}
pub fn win32_xstat(path: &OsStr, traverse: bool) -> io::Result<StatStruct> {
use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT};
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, 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,
Foundation::ERROR_FILE_NOT_FOUND
| Foundation::ERROR_PATH_NOT_FOUND
| Foundation::ERROR_NOT_READY
| Foundation::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: &Path, tested_type: TestType) -> bool {
match get_file_information_by_name(
path.as_os_str(),
FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo,
) {
Ok(info) => {
let disk_device = matches!(
info.DeviceType,
windows_sys::Win32::Storage::FileSystem::FILE_DEVICE_DISK
| windows_sys::Win32::System::Ioctl::FILE_DEVICE_VIRTUAL_DISK
| windows_sys::Win32::Storage::FileSystem::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
& windows_sys::Win32::Storage::FileSystem::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 wide_path = path.as_os_str().to_wide_with_nul();
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 {
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.as_os_str(),
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: &Path, follow_links: bool) -> bool {
match get_file_information_by_name(
path.as_os_str(),
FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo,
) {
Ok(info) => {
if (info.FileAttributes
& windows_sys::Win32::Storage::FileSystem::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 wide_path = path.as_os_str().to_wide_with_nul();
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;
}
}
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 => {
return win32_xstat(path.as_os_str(), follow_links).is_ok();
}
_ => {}
}
false
}
pub fn path_exists_via_open(path: &Path, follow_links: bool) -> bool {
let wide_path = path.as_os_str().to_wide_with_nul();
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)
};
// RAII wrappers: both handles are auto-closed on any early return below.
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)?;
// Ownership of the read handle now belongs to the CRT fd.
let _ = read_handle.into_raw_handle();
let write_fd = match crate::msvcrt::open_osfhandle(
write_handle.as_raw_handle() as isize,
libc::O_WRONLY | O_NOINHERIT,
) {
Ok(fd) => {
let _ = write_handle.into_raw_handle();
fd
}
Err(err) => {
// Close the CRT fd we already created; `write_handle` auto-closes via Drop.
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: Vec<u16> = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0"
.encode_utf16()
.collect();
unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl.as_ptr(),
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)
}
}
/// Update the CRT environment via `_wputenv`.
/// `envstring` must point to a nul-terminated wide string of the form `KEY=value`.
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: &Path) -> Result<OsString, ReadlinkError> {
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE,
FILE_SHARE_READ, FILE_SHARE_WRITE,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
use windows_sys::Win32::System::SystemServices::{
IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK,
};
let wide_path = path.as_os_str().to_wide_with_nul();
let handle = unsafe {
CreateFileW(
wide_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
.chunks_exact(2)
.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: &Path) -> io::Result<OsString> {
use windows_sys::Win32::Storage::FileSystem::{GetFinalPathNameByHandleW, VOLUME_NAME_DOS};
let wide = path.as_os_str().to_wide_with_nul();
let handle = unsafe {
CreateFileW(
wide.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: &Path) -> io::Result<OsString> {
let wide = path.as_os_str().to_wide_with_nul();
let mut buffer = vec![0u16; MAX_PATH as usize];
let mut ret = unsafe {
windows_sys::Win32::Storage::FileSystem::GetFullPathNameW(
wide.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(
wide.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: &Path) -> io::Result<OsString> {
let wide = path.as_os_str().to_wide_with_nul();
let buflen = core::cmp::max(wide.len(), MAX_PATH as usize);
let mut buffer = vec![0u16; buflen];
unsafe {
windows_sys::Win32::Storage::FileSystem::GetVolumePathNameW(
wide.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: &Path) -> io::Result<(u64, u64)> {
use windows_sys::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
let wide = path.as_os_str().to_wide_with_nul();
let mut free_to_me = 0u64;
let mut total = 0u64;
let mut free = 0u64;
let ok = unsafe { GetDiskFreeSpaceExW(wide.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(windows_sys::Win32::Foundation::ERROR_DIRECTORY as i32)
&& let Some(parent) = path.parent()
{
let parent = widestring::WideCString::from_os_str(parent).unwrap();
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 { windows_sys::Win32::Foundation::GetHandleInformation(handle as _, &mut flags) };
if ok == 0 {
Err(io::Error::last_os_error())
} else {
Ok(flags & windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT != 0)
}
}
pub fn set_handle_inheritable(handle: intptr_t, inheritable: bool) -> io::Result<()> {
let flags = if inheritable {
windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT
} else {
0
};
let ok = unsafe {
windows_sys::Win32::Foundation::SetHandleInformation(
handle as _,
windows_sys::Win32::Foundation::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 {
windows_sys::Win32::System::WindowsProgramming::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 {
windows_sys::Win32::Storage::FileSystem::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(
windows_sys::Win32::Foundation::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: &Path) -> io::Result<Vec<OsString>> {
let wide = volume.as_os_str().to_wide_with_nul();
let mut buflen: u32 = MAX_PATH + 1;
let mut buffer = vec![0u16; buflen as usize];
loop {
let ok = unsafe {
windows_sys::Win32::Storage::FileSystem::GetVolumePathNamesForVolumeNameW(
wide.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(windows_sys::Win32::Foundation::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 != windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED {
return Err(io::Error::last_os_error());
}
let conout: Vec<u16> = "CONOUT$\0".encode_utf16().collect();
let console_handle = unsafe {
CreateFileW(
conout.as_ptr(),
windows_sys::Win32::Foundation::GENERIC_READ
| windows_sys::Win32::Foundation::GENERIC_WRITE,
windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ
| windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE,
core::ptr::null(),
windows_sys::Win32::Storage::FileSystem::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 }
}
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..=4.min(len) {
let c = buf[len - count];
if c < 0x80 {
return len;
}
if c >= 0xc0 {
let expected = if c < 0xe0 {
2
} else if c < 0xf0 {
3
} else {
4
};
if count < expected {
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(windows_sys::Win32::Foundation::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> {
use windows_sys::Win32::{
Foundation::{GENERIC_READ, GENERIC_WRITE},
Storage::FileSystem::{FILE_SHARE_READ, FILE_SHARE_WRITE},
};
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 {
libc::O_WRONLY | libc::O_BINARY | 0x80
} else {
libc::O_RDONLY | libc::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> {
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> {
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<()> {
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<()> {
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(())
}
}