mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
* 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.
2263 lines
71 KiB
Rust
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(())
|
|
}
|
|
}
|