From fcfeb7587435dbd38f8ebd556cee1b13061ed65c Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sun, 12 Apr 2026 04:53:11 +0900 Subject: [PATCH 01/11] Create rustpython-host-env crate; move host abstractions out of common Move os, crt_fd, fileutils, windows, macros modules from rustpython-common into the new rustpython-host-env crate. This isolates host OS API wrappers behind a crate boundary with zero Python runtime dependency. - Add crates/host_env to workspace - Drop nix, windows-sys, widestring deps from common - Wire vm and stdlib to depend on rustpython-host-env - Migrate all imports from common::{os,crt_fd,fileutils,windows} to rustpython_host_env:: --- Cargo.lock | 17 +++++++++-- Cargo.toml | 1 + crates/common/Cargo.toml | 14 --------- crates/common/src/lib.rs | 12 -------- crates/host_env/Cargo.toml | 32 ++++++++++++++++++++ crates/{common => host_env}/src/crt_fd.rs | 0 crates/{common => host_env}/src/fileutils.rs | 0 crates/host_env/src/lib.rs | 16 ++++++++++ crates/{common => host_env}/src/macros.rs | 0 crates/{common => host_env}/src/os.rs | 0 crates/{common => host_env}/src/windows.rs | 0 crates/stdlib/Cargo.toml | 1 + crates/stdlib/src/faulthandler.rs | 2 +- crates/stdlib/src/mmap.rs | 4 +-- crates/stdlib/src/openssl.rs | 22 +++++++------- crates/stdlib/src/overlapped.rs | 2 +- crates/stdlib/src/posixshmem.rs | 8 ++--- crates/stdlib/src/socket.rs | 12 ++++---- crates/stdlib/src/termios.rs | 2 +- crates/vm/Cargo.toml | 1 + crates/vm/src/exceptions.rs | 4 +-- crates/vm/src/function/fspath.rs | 2 +- crates/vm/src/lib.rs | 1 + crates/vm/src/ospath.rs | 2 +- crates/vm/src/stdlib/_codecs.rs | 6 ++-- crates/vm/src/stdlib/_ctypes/function.rs | 6 ++-- crates/vm/src/stdlib/_io.rs | 9 +++--- crates/vm/src/stdlib/_signal.rs | 7 +++-- crates/vm/src/stdlib/_winapi.rs | 3 +- crates/vm/src/stdlib/msvcrt.rs | 2 +- crates/vm/src/stdlib/nt.rs | 18 +++++------ crates/vm/src/stdlib/os.rs | 32 +++++++++----------- crates/vm/src/stdlib/posix.rs | 10 +++--- crates/vm/src/stdlib/posix_compat.rs | 2 +- crates/vm/src/stdlib/sys.rs | 2 +- crates/vm/src/stdlib/time.rs | 4 +-- crates/vm/src/stdlib/winreg.rs | 2 +- crates/vm/src/stdlib/winsound.rs | 2 +- crates/vm/src/vm/vm_new.rs | 2 +- crates/vm/src/windows.rs | 12 ++++---- examples/generator.rs | 2 +- examples/package_embed.rs | 2 +- src/lib.rs | 2 +- 43 files changed, 158 insertions(+), 122 deletions(-) create mode 100644 crates/host_env/Cargo.toml rename crates/{common => host_env}/src/crt_fd.rs (100%) rename crates/{common => host_env}/src/fileutils.rs (100%) create mode 100644 crates/host_env/src/lib.rs rename crates/{common => host_env}/src/macros.rs (100%) rename crates/{common => host_env}/src/os.rs (100%) rename crates/{common => host_env}/src/windows.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 16941b8265a..dfb0f56c9ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,7 +3143,6 @@ dependencies = [ "malachite-base", "malachite-bigint", "malachite-q", - "nix 0.30.1", "num-complex", "num-traits", "parking_lot", @@ -3152,8 +3151,6 @@ dependencies = [ "rustpython-wtf8", "siphasher", "unicode_names2 2.0.0", - "widestring", - "windows-sys 0.61.2", ] [[package]] @@ -3222,6 +3219,18 @@ dependencies = [ "phf 0.13.1", ] +[[package]] +name = "rustpython-host-env" +version = "0.5.0" +dependencies = [ + "libc", + "nix 0.30.1", + "num-traits", + "rustpython-wtf8", + "widestring", + "windows-sys 0.61.2", +] + [[package]] name = "rustpython-jit" version = "0.5.0" @@ -3409,6 +3418,7 @@ dependencies = [ "rustls-platform-verifier", "rustpython-common", "rustpython-derive", + "rustpython-host-env", "rustpython-ruff_python_ast", "rustpython-ruff_python_parser", "rustpython-ruff_source_file", @@ -3488,6 +3498,7 @@ dependencies = [ "rustpython-compiler", "rustpython-compiler-core", "rustpython-derive", + "rustpython-host-env", "rustpython-jit", "rustpython-literal", "rustpython-ruff_python_ast", diff --git a/Cargo.toml b/Cargo.toml index cbc9c8f042f..9da6e7d83f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,6 +145,7 @@ rustpython-compiler-core = { path = "crates/compiler-core", version = "0.5.0" } rustpython-compiler = { path = "crates/compiler", version = "0.5.0" } rustpython-codegen = { path = "crates/codegen", version = "0.5.0" } rustpython-common = { path = "crates/common", version = "0.5.0" } +rustpython-host-env = { path = "crates/host_env", version = "0.5.0" } rustpython-derive = { path = "crates/derive", version = "0.5.0" } rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" } rustpython-jit = { path = "crates/jit", version = "0.5.0" } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 555336f059a..d93ac6b9ecd 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -36,19 +36,5 @@ lock_api = "0.4" siphasher = "1" num-complex.workspace = true -[target.'cfg(unix)'.dependencies] -nix = { workspace = true } - -[target.'cfg(windows)'.dependencies] -widestring = { workspace = true } -windows-sys = { workspace = true, features = [ - "Win32_Foundation", - "Win32_Networking_WinSock", - "Win32_Storage_FileSystem", - "Win32_System_Ioctl", - "Win32_System_LibraryLoader", - "Win32_System_SystemServices", -] } - [lints] workspace = true diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index e514c17541f..f3dca3689ff 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -4,34 +4,22 @@ extern crate alloc; -#[macro_use] -mod macros; -pub use macros::*; - pub mod atomic; pub mod borrow; pub mod boxvec; pub mod cformat; -#[cfg(all(feature = "std", any(unix, windows, target_os = "wasi")))] -pub mod crt_fd; pub mod encodings; -#[cfg(all(feature = "std", any(not(target_arch = "wasm32"), target_os = "wasi")))] -pub mod fileutils; pub mod float_ops; pub mod format; pub mod hash; pub mod int; pub mod linked_list; pub mod lock; -#[cfg(feature = "std")] -pub mod os; pub mod rand; pub mod rc; pub mod refcount; pub mod static_cell; pub mod str; -#[cfg(all(feature = "std", windows))] -pub mod windows; pub use rustpython_wtf8 as wtf8; diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml new file mode 100644 index 00000000000..d405f794c7d --- /dev/null +++ b/crates/host_env/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "rustpython-host-env" +description = "Host OS API abstractions for RustPython" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +rustpython-wtf8 = { workspace = true } + +libc = { workspace = true } +num-traits = { workspace = true } + +[target.'cfg(unix)'.dependencies] +nix = { workspace = true } + +[target.'cfg(windows)'.dependencies] +widestring = { workspace = true } +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_Networking_WinSock", + "Win32_Storage_FileSystem", + "Win32_System_Ioctl", + "Win32_System_LibraryLoader", + "Win32_System_SystemServices", +] } + +[lints] +workspace = true diff --git a/crates/common/src/crt_fd.rs b/crates/host_env/src/crt_fd.rs similarity index 100% rename from crates/common/src/crt_fd.rs rename to crates/host_env/src/crt_fd.rs diff --git a/crates/common/src/fileutils.rs b/crates/host_env/src/fileutils.rs similarity index 100% rename from crates/common/src/fileutils.rs rename to crates/host_env/src/fileutils.rs diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs new file mode 100644 index 00000000000..b2c4ddacf6d --- /dev/null +++ b/crates/host_env/src/lib.rs @@ -0,0 +1,16 @@ +extern crate alloc; + +#[macro_use] +mod macros; +pub use macros::*; + +pub mod os; + +#[cfg(any(unix, windows, target_os = "wasi"))] +pub mod crt_fd; + +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +pub mod fileutils; + +#[cfg(windows)] +pub mod windows; diff --git a/crates/common/src/macros.rs b/crates/host_env/src/macros.rs similarity index 100% rename from crates/common/src/macros.rs rename to crates/host_env/src/macros.rs diff --git a/crates/common/src/os.rs b/crates/host_env/src/os.rs similarity index 100% rename from crates/common/src/os.rs rename to crates/host_env/src/os.rs diff --git a/crates/common/src/windows.rs b/crates/host_env/src/windows.rs similarity index 100% rename from crates/common/src/windows.rs rename to crates/host_env/src/windows.rs diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index f828507d6cf..5739a3f4a97 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -30,6 +30,7 @@ flame-it = ["flame"] rustpython-derive = { workspace = true } rustpython-vm = { workspace = true, default-features = false, features = ["compiler"]} rustpython-common = { workspace = true } +rustpython-host-env = { workspace = true } ruff_python_parser = { workspace = true } ruff_python_ast = { workspace = true } diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index 9c4373c312e..96e024e4c62 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -13,7 +13,7 @@ mod decl { use core::time::Duration; use parking_lot::{Condvar, Mutex}; #[cfg(any(unix, windows))] - use rustpython_common::os::{get_errno, set_errno}; + use rustpython_host_env::os::{get_errno, set_errno}; use std::thread; /// fault_handler_t diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index a99d0cc131d..c492c960750 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -30,10 +30,10 @@ mod mmap { #[cfg(unix)] use nix::{sys::stat::fstat, unistd}; #[cfg(unix)] - use rustpython_common::crt_fd; + use rustpython_host_env::crt_fd; #[cfg(windows)] - use rustpython_common::suppress_iph; + use rustpython_host_env::suppress_iph; #[cfg(windows)] use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; #[cfg(windows)] diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index 88e616aa5d3..7cef74470b1 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -1671,17 +1671,17 @@ mod _ssl { // Open the file using fopen (cross-platform) let fp = - rustpython_common::fileutils::fopen(path.as_path(), "rb").map_err(|e| { - match e.kind() { - std::io::ErrorKind::NotFound => vm - .new_os_subtype_error( - vm.ctx.exceptions.file_not_found_error.to_owned(), - Some(libc::ENOENT), - e.to_string(), - ) - .upcast(), - _ => vm.new_os_error(e.to_string()), - } + rustpython_host_env::fileutils::fopen(path.as_path(), "rb").map_err(|e| match e + .kind() + { + std::io::ErrorKind::NotFound => vm + .new_os_subtype_error( + vm.ctx.exceptions.file_not_found_error.to_owned(), + Some(libc::ENOENT), + e.to_string(), + ) + .upcast(), + _ => vm.new_os_error(e.to_string()), })?; // Read DH parameters diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index 76d18cb7a9a..2230991b643 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -283,7 +283,7 @@ mod _overlapped { } else { err }; - let errno = crate::vm::common::os::winerror_to_errno(err as i32); + let errno = rustpython_host_env::os::winerror_to_errno(err as i32); let message = std::io::Error::from_raw_os_error(err as i32).to_string(); let exc = vm.new_errno_error(errno, message); let _ = exc diff --git a/crates/stdlib/src/posixshmem.rs b/crates/stdlib/src/posixshmem.rs index f5481619bba..da1a8cf1d89 100644 --- a/crates/stdlib/src/posixshmem.rs +++ b/crates/stdlib/src/posixshmem.rs @@ -6,12 +6,10 @@ pub(crate) use _posixshmem::module_def; mod _posixshmem { use alloc::ffi::CString; - use crate::{ - common::os::errno_io_error, - vm::{ - FromArgs, PyResult, VirtualMachine, builtins::PyUtf8StrRef, convert::IntoPyException, - }, + use crate::vm::{ + FromArgs, PyResult, VirtualMachine, builtins::PyUtf8StrRef, convert::IntoPyException, }; + use rustpython_host_env::os::errno_io_error; #[derive(FromArgs)] struct ShmOpenArgs { diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index d3fe59144ef..fe2aa6be3da 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -14,7 +14,6 @@ mod _socket { PyBaseExceptionRef, PyListRef, PyModule, PyOSError, PyStrRef, PyTupleRef, PyTypeRef, PyUtf8StrRef, }, - common::os::ErrorExt, convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject}, function::{ ArgBytesLike, ArgIntoFloat, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, @@ -23,6 +22,7 @@ mod _socket { types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable}, utils::ToCString, }; + use rustpython_host_env::os::ErrorExt; pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { #[cfg(windows)] @@ -2270,7 +2270,7 @@ mod _socket { ) }; if ret < 0 { - return Err(crate::common::os::errno_io_error().into()); + return Err(rustpython_host_env::os::errno_io_error().into()); } Ok(vm.ctx.new_int(flag).into()) } else { @@ -2291,7 +2291,7 @@ mod _socket { ) }; if ret < 0 { - return Err(crate::common::os::errno_io_error().into()); + return Err(rustpython_host_env::os::errno_io_error().into()); } buf.truncate(buflen as usize); Ok(vm.ctx.new_bytes(buf).into()) @@ -2332,7 +2332,7 @@ mod _socket { } }; if ret < 0 { - Err(crate::common::os::errno_io_error().into()) + Err(rustpython_host_env::os::errno_io_error().into()) } else { Ok(()) } @@ -3084,7 +3084,7 @@ mod _socket { fn if_nametoindex(name: FsPath, vm: &VirtualMachine) -> PyResult { let name = name.to_cstring(vm)?; // in case 'if_nametoindex' does not set errno - crate::common::os::set_errno(libc::ENODEV); + rustpython_host_env::os::set_errno(libc::ENODEV); let ret = unsafe { c::if_nametoindex(name.as_ptr() as _) }; if ret == 0 { Err(vm.new_last_errno_error()) @@ -3098,7 +3098,7 @@ mod _socket { fn if_indextoname(index: IfIndex, vm: &VirtualMachine) -> PyResult { let mut buf = [0; c::IF_NAMESIZE + 1]; // in case 'if_indextoname' does not set errno - crate::common::os::set_errno(libc::ENXIO); + rustpython_host_env::os::set_errno(libc::ENXIO); let ret = unsafe { c::if_indextoname(index, buf.as_mut_ptr()) }; if ret.is_null() { Err(vm.new_last_errno_error()) diff --git a/crates/stdlib/src/termios.rs b/crates/stdlib/src/termios.rs index de402724434..8f7963c159f 100644 --- a/crates/stdlib/src/termios.rs +++ b/crates/stdlib/src/termios.rs @@ -7,9 +7,9 @@ mod termios { use crate::vm::{ PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyBytes, PyInt, PyListRef, PyTypeRef}, - common::os::ErrorExt, convert::ToPyObject, }; + use rustpython_host_env::os::ErrorExt; use termios::Termios; // TODO: more ioctl numbers diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index b721418a4cc..0d02a89a889 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -33,6 +33,7 @@ rustpython-compiler = { workspace = true, optional = true } rustpython-codegen = { workspace = true, optional = true } rustpython-common = { workspace = true } rustpython-derive = { workspace = true } +rustpython-host-env = { workspace = true } rustpython-jit = { workspace = true, optional = true } ruff_python_ast = { workspace = true, optional = true } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 1f82c3cd72c..6a90a160ff5 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1380,7 +1380,7 @@ impl IntoPyException for OSErrorBuilder { impl ToOSErrorBuilder for std::io::Error { fn to_os_error_builder(&self, vm: &VirtualMachine) -> OSErrorBuilder { - use crate::common::os::ErrorExt; + use crate::host_env::os::ErrorExt; let errno = self.posix_errno(); #[cfg(windows)] @@ -1937,7 +1937,7 @@ pub(super) mod types { .as_ref() .and_then(|w| w.downcast_ref::()) .and_then(|w| w.try_to_primitive::(vm).ok()) - .map(crate::common::os::winerror_to_errno) + .map(crate::host_env::os::winerror_to_errno) { let errno_obj = vm.new_pyobj(errno); let _ = unsafe { exc.errno.swap(Some(errno_obj.clone())) }; diff --git a/crates/vm/src/function/fspath.rs b/crates/vm/src/function/fspath.rs index 732fd0ca35a..c3ec1221627 100644 --- a/crates/vm/src/function/fspath.rs +++ b/crates/vm/src/function/fspath.rs @@ -123,7 +123,7 @@ impl FsPath { } pub fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ffi::OsStr> { - rustpython_common::os::bytes_as_os_str(b) + rustpython_host_env::os::bytes_as_os_str(b) .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8")) } } diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index 21762466f13..78669d2fce2 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -105,6 +105,7 @@ pub use self::vm::{Context, Interpreter, InterpreterBuilder, Settings, VirtualMa pub use rustpython_common as common; pub use rustpython_compiler_core::{bytecode, frozen}; +pub use rustpython_host_env as host_env; pub use rustpython_literal as literal; #[doc(hidden)] diff --git a/crates/vm/src/ospath.rs b/crates/vm/src/ospath.rs index d3123a87acb..15cda4b82b5 100644 --- a/crates/vm/src/ospath.rs +++ b/crates/vm/src/ospath.rs @@ -1,4 +1,4 @@ -use rustpython_common::crt_fd; +use rustpython_host_env::crt_fd; use crate::{ AsObject, PyObjectRef, PyResult, VirtualMachine, diff --git a/crates/vm/src/stdlib/_codecs.rs b/crates/vm/src/stdlib/_codecs.rs index 39ebb3599bd..9253823d459 100644 --- a/crates/vm/src/stdlib/_codecs.rs +++ b/crates/vm/src/stdlib/_codecs.rs @@ -387,7 +387,7 @@ mod _codecs_windows { #[pyfunction] fn mbcs_encode(args: MbcsEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec, usize)> { - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; use windows_sys::Win32::Globalization::{ CP_ACP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, }; @@ -573,7 +573,7 @@ mod _codecs_windows { #[pyfunction] fn oem_encode(args: OemEncodeArgs, vm: &VirtualMachine) -> PyResult<(Vec, usize)> { - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; use windows_sys::Win32::Globalization::{ CP_OEMCP, WC_NO_BEST_FIT_CHARS, WideCharToMultiByte, }; @@ -1049,7 +1049,7 @@ mod _codecs_windows { args: CodePageEncodeArgs, vm: &VirtualMachine, ) -> PyResult<(Vec, usize)> { - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; if args.code_page < 0 { return Err(vm.new_value_error("invalid code page number")); diff --git a/crates/vm/src/stdlib/_ctypes/function.rs b/crates/vm/src/stdlib/_ctypes/function.rs index e28fd91abbc..9136304c296 100644 --- a/crates/vm/src/stdlib/_ctypes/function.rs +++ b/crates/vm/src/stdlib/_ctypes/function.rs @@ -2154,7 +2154,7 @@ unsafe extern "C" fn thunk_callback( // Swap errno before call if FUNCFLAG_USE_ERRNO is set let use_errno = userdata.flags & StgInfoFlags::FUNCFLAG_USE_ERRNO.bits() != 0; let saved_errno = if use_errno { - let current = rustpython_common::os::get_errno(); + let current = rustpython_host_env::os::get_errno(); // TODO: swap with ctypes stored errno (thread-local) Some(current) } else { @@ -2175,10 +2175,10 @@ unsafe extern "C" fn thunk_callback( // Swap errno back after call if use_errno { - let _current = rustpython_common::os::get_errno(); + let _current = rustpython_host_env::os::get_errno(); // TODO: store current errno to ctypes storage if let Some(saved) = saved_errno { - rustpython_common::os::set_errno(saved); + rustpython_host_env::os::set_errno(saved); } } diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index c238dda3725..15111952d56 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -7,7 +7,7 @@ pub(crate) use _io::reinit_std_streams_after_fork; cfg_if::cfg_if! { if #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] { - use crate::common::crt_fd::Offset; + use rustpython_host_env::crt_fd::Offset; } else { type Offset = i64; } @@ -5323,11 +5323,12 @@ mod _io { #[pymodule] mod fileio { use super::{_io::*, Offset, iobase_finalize}; + use crate::host_env::crt_fd; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyUtf8Str, PyUtf8StrRef}, - common::{crt_fd, wtf8::Wtf8Buf}, + common::wtf8::Wtf8Buf, convert::{IntoPyException, ToPyException}, exceptions::OSErrorBuilder, function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption}, @@ -5554,7 +5555,7 @@ mod fileio { // TODO: _Py_set_inheritable - let fd_fstat = crate::common::fileutils::fstat(fd); + let fd_fstat = rustpython_host_env::fileutils::fstat(fd); #[cfg(windows)] { @@ -6017,7 +6018,7 @@ mod winconsoleio { const BUFMAX: usize = 32 * 1024 * 1024; fn handle_from_fd(fd: i32) -> HANDLE { - unsafe { rustpython_common::suppress_iph!(libc::get_osfhandle(fd)) as HANDLE } + unsafe { rustpython_host_env::suppress_iph!(libc::get_osfhandle(fd)) as HANDLE } } fn is_invalid_handle(handle: HANDLE) -> bool { diff --git a/crates/vm/src/stdlib/_signal.rs b/crates/vm/src/stdlib/_signal.rs index a69d766ce51..eddf8ce8e46 100644 --- a/crates/vm/src/stdlib/_signal.rs +++ b/crates/vm/src/stdlib/_signal.rs @@ -404,16 +404,17 @@ pub(crate) mod _signal { let fd_i32 = i32::try_from(fd).map_err(|_| vm.new_value_error("invalid fd"))?; // Verify the fd is valid by trying to fstat it let borrowed_fd = - unsafe { crate::common::crt_fd::Borrowed::try_borrow_raw(fd_i32) } + unsafe { rustpython_host_env::crt_fd::Borrowed::try_borrow_raw(fd_i32) } .map_err(|e| e.into_pyexception(vm))?; - crate::common::fileutils::fstat(borrowed_fd).map_err(|e| e.into_pyexception(vm))?; + rustpython_host_env::fileutils::fstat(borrowed_fd) + .map_err(|e| e.into_pyexception(vm))?; } is_socket } else { false }; #[cfg(unix)] - if let Ok(fd) = unsafe { crate::common::crt_fd::Borrowed::try_borrow_raw(fd) } { + if let Ok(fd) = unsafe { rustpython_host_env::crt_fd::Borrowed::try_borrow_raw(fd) } { use nix::fcntl; let oflags = fcntl::fcntl(fd, fcntl::F_GETFL).map_err(|e| e.into_pyexception(vm))?; let nonblock = diff --git a/crates/vm/src/stdlib/_winapi.rs b/crates/vm/src/stdlib/_winapi.rs index 665ff0d9e58..a90613bd7a2 100644 --- a/crates/vm/src/stdlib/_winapi.rs +++ b/crates/vm/src/stdlib/_winapi.rs @@ -8,7 +8,7 @@ mod _winapi { use crate::{ Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, builtins::PyStrRef, - common::{lock::PyMutex, windows::ToWideString}, + common::lock::PyMutex, convert::{ToPyException, ToPyResult}, function::{ArgMapping, ArgSequence, OptionalArg}, types::Constructor, @@ -16,6 +16,7 @@ mod _winapi { }; use core::ptr::{null, null_mut}; use rustpython_common::wtf8::Wtf8Buf; + use rustpython_host_env::windows::ToWideString; use windows_sys::Win32::Foundation::{HANDLE, MAX_PATH}; #[pyattr] diff --git a/crates/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs index 93364ea3596..46beb306fc6 100644 --- a/crates/vm/src/stdlib/msvcrt.rs +++ b/crates/vm/src/stdlib/msvcrt.rs @@ -7,8 +7,8 @@ mod msvcrt { use crate::{ PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyStrRef}, - common::{crt_fd, suppress_iph}, convert::IntoPyException, + host_env::{crt_fd, suppress_iph}, }; use itertools::Itertools; use std::os::windows::io::AsRawHandle; diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 5dd4cf4f001..97f5b310238 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -10,10 +10,10 @@ pub(crate) mod module { builtins::{ PyBaseExceptionRef, PyBytes, PyDictRef, PyListRef, PyStr, PyStrRef, PyTupleRef, }, - common::{crt_fd, suppress_iph, windows::ToWideString}, convert::ToPyException, exceptions::OSErrorBuilder, function::{ArgMapping, Either, OptionalArg}, + host_env::{crt_fd, suppress_iph, windows::ToWideString}, ospath::{OsPath, OsPathOrFd}, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; @@ -411,7 +411,7 @@ pub(crate) mod module { /// Uses FindFirstFileW to get the name as stored on the filesystem. #[pyfunction] fn _findfirstfile(path: OsPath, vm: &VirtualMachine) -> PyResult { - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; use std::os::windows::ffi::OsStringExt; use windows_sys::Win32::Storage::FileSystem::{ FindClose, FindFirstFileW, WIN32_FIND_DATAW, @@ -575,10 +575,10 @@ pub(crate) mod module { /// _testFileTypeByName - test file type by path name fn _test_file_type_by_name(path: &std::path::Path, tested_type: u32) -> bool { - use crate::common::fileutils::windows::{ + use crate::host_env::fileutils::windows::{ FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, }; - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; use windows_sys::Win32::Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, @@ -670,10 +670,10 @@ pub(crate) mod module { /// _testFileExistsByName - test if path exists fn _test_file_exists_by_name(path: &std::path::Path, follow_links: bool) -> bool { - use crate::common::fileutils::windows::{ + use crate::host_env::fileutils::windows::{ FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, }; - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; use windows_sys::Win32::Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, @@ -761,7 +761,7 @@ pub(crate) mod module { fn _test_file_type(path_or_fd: &OsPathOrFd<'_>, tested_type: u32) -> bool { match path_or_fd { OsPathOrFd::Fd(fd) => { - if let Ok(handle) = crate::common::crt_fd::as_handle(*fd) { + if let Ok(handle) = crate::host_env::crt_fd::as_handle(*fd) { use std::os::windows::io::AsRawHandle; _test_file_type_by_handle(handle.as_raw_handle() as _, tested_type, true) } else { @@ -778,7 +778,7 @@ pub(crate) mod module { match path_or_fd { OsPathOrFd::Fd(fd) => { - if let Ok(handle) = crate::common::crt_fd::as_handle(*fd) { + if let Ok(handle) = crate::host_env::crt_fd::as_handle(*fd) { use std::os::windows::io::AsRawHandle; let file_type = unsafe { GetFileType(handle.as_raw_handle() as _) }; // GetFileType(hfile) != FILE_TYPE_UNKNOWN || !GetLastError() @@ -2156,7 +2156,7 @@ pub(crate) mod module { /// returns the substitute name from reparse data which includes the prefix #[pyfunction] fn readlink(path: OsPath, vm: &VirtualMachine) -> PyResult { - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::Storage::FileSystem::{ CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 949fda8252a..e31017f0682 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -3,9 +3,9 @@ use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, builtins::{PyModule, PySet}, - common::crt_fd, convert::{IntoPyException, ToPyException, ToPyObject}, function::{ArgumentError, FromArgs, FuncArgs}, + host_env::crt_fd, }; use std::{fs, io, path::Path}; @@ -101,7 +101,7 @@ pub(super) struct FollowSymlinks( #[cfg(not(windows))] fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ffi::OsStr> { - rustpython_common::os::bytes_as_os_str(b) + rustpython_host_env::os::bytes_as_os_str(b) .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8")) } @@ -152,8 +152,9 @@ impl ToPyObject for crt_fd::Borrowed<'_> { #[pymodule(sub)] pub(super) mod _os { use super::{DirFd, FollowSymlinks, SupportFunc}; + use crate::host_env::fileutils::StatStruct; #[cfg(windows)] - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; #[cfg(any(unix, windows))] use crate::utils::ToCString; use crate::{ @@ -161,15 +162,11 @@ pub(super) mod _os { builtins::{ PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, }, - common::{ - crt_fd, - fileutils::StatStruct, - lock::{OnceCell, PyRwLock}, - suppress_iph, - }, + common::lock::{OnceCell, PyRwLock}, convert::{IntoPyException, ToPyObject}, exceptions::{OSErrorBuilder, ToOSErrorBuilder}, function::{ArgBytesLike, ArgMemoryBuffer, FsPath, FuncArgs, OptionalArg}, + host_env::crt_fd, ospath::{OsPath, OsPathOrFd, OutputMode, PathConverter}, protocol::PyIterReturn, recursion::ReprGuard, @@ -179,6 +176,7 @@ pub(super) mod _os { use core::time::Duration; use crossbeam_utils::atomic::AtomicCell; use rustpython_common::wtf8::Wtf8Buf; + use rustpython_host_env::suppress_iph; use std::{env, fs, fs::OpenOptions, io, path::PathBuf, time::SystemTime}; const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); @@ -349,7 +347,7 @@ pub(super) mod _os { if let Some(fd) = dir_fd.raw_opt() { let res = unsafe { libc::mkdirat(fd, c_path.as_ptr(), mode as _) }; return if res < 0 { - let err = crate::common::os::errno_io_error(); + let err = crate::host_env::os::errno_io_error(); Err(OSErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) @@ -359,7 +357,7 @@ pub(super) mod _os { let [] = dir_fd.0; let res = unsafe { libc::mkdir(c_path.as_ptr(), mode as _) }; if res < 0 { - let err = crate::common::os::errno_io_error(); + let err = crate::host_env::os::errno_io_error(); return Err(OSErrorBuilder::with_filename(&err, path, vm)); } Ok(()) @@ -383,7 +381,7 @@ pub(super) mod _os { let c_path = path.clone().into_cstring(vm)?; let res = unsafe { libc::unlinkat(fd, c_path.as_ptr(), libc::AT_REMOVEDIR) }; return if res < 0 { - let err = crate::common::os::errno_io_error(); + let err = crate::host_env::os::errno_io_error(); Err(OSErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) @@ -437,7 +435,7 @@ pub(super) mod _os { } #[cfg(all(unix, not(target_os = "redox")))] { - use rustpython_common::os::ffi::OsStrExt; + use rustpython_host_env::os::ffi::OsStrExt; use std::os::unix::io::IntoRawFd; let new_fd = nix::unistd::dup(fno).map_err(|e| e.into_pyexception(vm))?; let raw_fd = new_fd.into_raw_fd(); @@ -490,7 +488,7 @@ pub(super) mod _os { /// Check if wide string length exceeds Windows environment variable limit. #[cfg(windows)] fn check_env_var_len(wide_len: usize, vm: &VirtualMachine) -> PyResult<()> { - use crate::common::windows::_MAX_ENV; + use crate::host_env::windows::_MAX_ENV; if wide_len > _MAX_ENV + 1 { return Err(vm.new_value_error(format!( "the environment variable is longer than {_MAX_ENV} characters", @@ -1124,7 +1122,7 @@ pub(super) mod _os { #[cfg(all(unix, not(target_os = "redox")))] impl IterNext for ScandirIteratorFd { fn next(zelf: &crate::Py, vm: &VirtualMachine) -> PyResult { - use rustpython_common::os::ffi::OsStrExt; + use rustpython_host_env::os::ffi::OsStrExt; let mut guard = zelf.dir.lock(); let dir = match guard.as_mut() { None => return Ok(PyIterReturn::StopIteration(None)), @@ -1400,7 +1398,7 @@ pub(super) mod _os { let [] = dir_fd.0; match file { OsPathOrFd::Path(path) => crate::windows::win32_xstat(&path.path, follow_symlinks.0), - OsPathOrFd::Fd(fd) => crate::common::fileutils::fstat(fd), + OsPathOrFd::Fd(fd) => crate::host_env::fileutils::fstat(fd), } .map(Some) } @@ -1414,7 +1412,7 @@ pub(super) mod _os { let mut stat = core::mem::MaybeUninit::uninit(); let ret = match file { OsPathOrFd::Path(path) => { - use rustpython_common::os::ffi::OsStrExt; + use rustpython_host_env::os::ffi::OsStrExt; let path = path.as_ref().as_os_str().as_bytes(); let path = match alloc::ffi::CString::new(path) { Ok(x) => x, diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index f1f4ddeaac8..b7a54459796 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -53,7 +53,7 @@ pub mod module { fcntl, unistd::{self, Gid, Pid, Uid}, }; - use rustpython_common::os::ffi::OsStringExt; + use rustpython_host_env::os::ffi::OsStringExt; use std::{ env, fs, io, os::fd::{AsFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, @@ -574,7 +574,7 @@ pub mod module { let c_path = path.clone().into_cstring(vm)?; let res = unsafe { libc::unlinkat(fd, c_path.as_ptr(), 0) }; return if res < 0 { - let err = crate::common::os::errno_io_error(); + let err = crate::host_env::os::errno_io_error(); Err(OSErrorBuilder::with_filename(&err, path, vm)) } else { Ok(()) @@ -2598,9 +2598,9 @@ pub mod module { #[pyfunction] fn sysconf(name: SysconfName, vm: &VirtualMachine) -> PyResult { - crate::common::os::set_errno(0); + crate::host_env::os::set_errno(0); let r = unsafe { libc::sysconf(name.0) }; - if r == -1 && crate::common::os::get_errno() != 0 { + if r == -1 && crate::host_env::os::get_errno() != 0 { return Err(vm.new_last_errno_error()); } Ok(r) @@ -2626,7 +2626,7 @@ pub mod module { struct SendFileArgs<'fd> { out_fd: BorrowedFd<'fd>, in_fd: BorrowedFd<'fd>, - offset: crate::common::crt_fd::Offset, + offset: rustpython_host_env::crt_fd::Offset, count: i64, #[cfg(target_os = "macos")] #[pyarg(any, optional)] diff --git a/crates/vm/src/stdlib/posix_compat.rs b/crates/vm/src/stdlib/posix_compat.rs index 89d3d94d7b2..2b4b70c2457 100644 --- a/crates/vm/src/stdlib/posix_compat.rs +++ b/crates/vm/src/stdlib/posix_compat.rs @@ -46,7 +46,7 @@ pub(crate) mod module { #[cfg(target_os = "wasi")] #[pyattr] fn environ(vm: &VirtualMachine) -> crate::builtins::PyDictRef { - use rustpython_common::os::ffi::OsStringExt; + use rustpython_host_env::os::ffi::OsStringExt; let environ = vm.ctx.new_dict(); for (key, value) in env::vars_os() { diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 72379494ddb..64d16a2c9c7 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1091,7 +1091,7 @@ mod sys { #[cfg(windows)] fn get_kernel32_version() -> std::io::Result<(u32, u32, u32)> { - use crate::common::windows::ToWideString; + use crate::host_env::windows::ToWideString; unsafe { // Create a wide string for "kernel32.dll" let module_name: Vec = std::ffi::OsStr::new("kernel32.dll").to_wide_with_nul(); diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 2cc2f5f50b8..54b4f92cd68 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -687,7 +687,7 @@ mod decl { loop { let mut out = vec![0u16; size]; let written = unsafe { - rustpython_common::suppress_iph!(wcsftime( + rustpython_host_env::suppress_iph!(wcsftime( out.as_mut_ptr(), out.len(), fmt_wide.as_ptr(), @@ -1450,7 +1450,7 @@ mod platform { pub(super) fn win_mktime(t: &StructTimeData, vm: &VirtualMachine) -> PyResult { let mut tm = super::decl::tm_from_struct_time(t, vm)?; - let timestamp = unsafe { rustpython_common::suppress_iph!(c_mktime(&mut tm)) }; + let timestamp = unsafe { rustpython_host_env::suppress_iph!(c_mktime(&mut tm)) }; if timestamp == -1 && tm.tm_wday == -1 { return Err(vm.new_overflow_error("mktime argument out of range")); } diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index 264d14327da..89e4cb8e8aa 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -7,9 +7,9 @@ pub(crate) use winreg::module_def; mod winreg { use crate::builtins::{PyInt, PyStr, PyTuple, PyTypeRef}; use crate::common::hash::PyHash; - use crate::common::windows::ToWideString; use crate::convert::TryFromObject; use crate::function::FuncArgs; + use crate::host_env::windows::ToWideString; use crate::object::AsObject; use crate::protocol::PyNumberMethods; use crate::types::{AsNumber, Hashable}; diff --git a/crates/vm/src/stdlib/winsound.rs b/crates/vm/src/stdlib/winsound.rs index 0ca2e9a2258..fea00d10c63 100644 --- a/crates/vm/src/stdlib/winsound.rs +++ b/crates/vm/src/stdlib/winsound.rs @@ -18,8 +18,8 @@ mod win32 { #[pymodule] mod winsound { use crate::builtins::{PyBytes, PyStr}; - use crate::common::windows::ToWideString; use crate::convert::{IntoPyException, TryFromBorrowedObject}; + use crate::host_env::windows::ToWideString; use crate::protocol::PyBuffer; use crate::{AsObject, PyObjectRef, PyResult, VirtualMachine}; diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index fb40268c569..63de652ac7a 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -249,7 +249,7 @@ impl VirtualMachine { /// On windows, CRT errno are expected to be handled by this function. /// This is identical to `new_last_os_error` on non-Windows platforms. pub fn new_last_errno_error(&self) -> PyBaseExceptionRef { - let err = crate::common::os::errno_io_error(); + let err = crate::host_env::os::errno_io_error(); err.to_pyexception(self) } diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index ec9ac8f18fb..fe109c95ee5 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -1,4 +1,4 @@ -use crate::common::fileutils::{ +use crate::host_env::fileutils::{ StatStruct, windows::{FILE_INFO_BY_NAME_CLASS, get_file_information_by_name}, }; @@ -6,7 +6,7 @@ use crate::{ PyObjectRef, PyResult, TryFromObject, VirtualMachine, convert::{ToPyObject, ToPyResult}, }; -use rustpython_common::windows::ToWideString; +use rustpython_host_env::windows::ToWideString; use std::ffi::OsStr; use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; @@ -103,8 +103,8 @@ const S_IFMT: u16 = libc::S_IFMT as u16; const S_IFDIR: u16 = libc::S_IFDIR as u16; const S_IFREG: u16 = libc::S_IFREG as u16; const S_IFCHR: u16 = libc::S_IFCHR as u16; -const S_IFLNK: u16 = crate::common::fileutils::windows::S_IFLNK as u16; -const S_IFIFO: u16 = crate::common::fileutils::windows::S_IFIFO as u16; +const S_IFLNK: u16 = crate::host_env::fileutils::windows::S_IFLNK as u16; +const S_IFIFO: u16 = crate::host_env::fileutils::windows::S_IFIFO as u16; /// FILE_ATTRIBUTE_TAG_INFO structure for GetFileInformationByHandleEx #[repr(C)] @@ -141,7 +141,7 @@ fn attribute_data_to_stat( basic_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_BASIC_INFO>, id_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_ID_INFO>, ) -> StatStruct { - use crate::common::fileutils::windows::SECS_BETWEEN_EPOCHS; + use crate::host_env::fileutils::windows::SECS_BETWEEN_EPOCHS; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; let mut st_mode = attributes_to_mode(info.dwFileAttributes); @@ -513,7 +513,7 @@ fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result || (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag)) { let mut result = - crate::common::fileutils::windows::stat_basic_info_to_stat(&stat_info); + crate::host_env::fileutils::windows::stat_basic_info_to_stat(&stat_info); // If st_ino is 0, fall through to slow path to get proper file ID if result.st_ino != 0 || result.st_ino_high != 0 { result.update_st_mode_from_path(path, stat_info.FileAttributes); diff --git a/examples/generator.rs b/examples/generator.rs index 55841c767a1..f695f094681 100644 --- a/examples/generator.rs +++ b/examples/generator.rs @@ -46,5 +46,5 @@ fn main() -> ExitCode { let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx); let interp = builder.add_native_modules(&defs).build(); let result = py_main(&interp); - vm::common::os::exit_code(interp.run(|_vm| result)) + vm::host_env::os::exit_code(interp.run(|_vm| result)) } diff --git a/examples/package_embed.rs b/examples/package_embed.rs index bb2f29e3f5f..cf2593facd8 100644 --- a/examples/package_embed.rs +++ b/examples/package_embed.rs @@ -26,5 +26,5 @@ fn main() -> ExitCode { let result = result.map(|result| { println!("name: {result}"); }); - vm::common::os::exit_code(interp.run(|_vm| result)) + vm::host_env::os::exit_code(interp.run(|_vm| result)) } diff --git a/src/lib.rs b/src/lib.rs index 02b514b6e17..b9ddcc3aeb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,7 +120,7 @@ pub fn run(mut builder: InterpreterBuilder) -> ExitCode { let interp = builder.interpreter(); let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode)); - rustpython_vm::common::os::exit_code(exitcode) + rustpython_vm::host_env::os::exit_code(exitcode) } fn get_pip(scope: Scope, vm: &VirtualMachine) -> PyResult<()> { From affb7d783834fa2aead64e43d68e2ba846098951 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:14:22 +0000 Subject: [PATCH 02/11] refactor: extract host helpers Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/48d1e64d-37ce-409f-b511-8e61a349665c Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- Cargo.lock | 1 + crates/host_env/Cargo.toml | 9 ++ crates/host_env/src/fcntl.rs | 75 +++++++++++++ crates/host_env/src/lib.rs | 24 +++++ crates/host_env/src/msvcrt.rs | 126 ++++++++++++++++++++++ crates/host_env/src/nt.rs | 73 +++++++++++++ crates/host_env/src/posix.rs | 13 +++ crates/host_env/src/select.rs | 176 ++++++++++++++++++++++++++++++ crates/host_env/src/shm.rs | 23 ++++ crates/host_env/src/signal.rs | 17 +++ crates/host_env/src/syslog.rs | 68 ++++++++++++ crates/host_env/src/termios.rs | 23 ++++ crates/host_env/src/time.rs | 46 ++++++++ crates/host_env/src/winapi.rs | 17 +++ crates/stdlib/src/fcntl.rs | 54 +++------- crates/stdlib/src/posixshmem.rs | 20 +--- crates/stdlib/src/select.rs | 183 +------------------------------- crates/stdlib/src/socket.rs | 12 +-- crates/stdlib/src/syslog.rs | 55 ++-------- crates/stdlib/src/termios.rs | 17 ++- crates/vm/src/stdlib/_signal.rs | 23 +--- crates/vm/src/stdlib/_winapi.rs | 9 +- crates/vm/src/stdlib/msvcrt.rs | 99 ++++------------- crates/vm/src/stdlib/nt.rs | 69 +----------- crates/vm/src/stdlib/posix.rs | 13 +-- crates/vm/src/stdlib/time.rs | 48 +++------ 26 files changed, 786 insertions(+), 507 deletions(-) create mode 100644 crates/host_env/src/fcntl.rs create mode 100644 crates/host_env/src/msvcrt.rs create mode 100644 crates/host_env/src/nt.rs create mode 100644 crates/host_env/src/posix.rs create mode 100644 crates/host_env/src/select.rs create mode 100644 crates/host_env/src/shm.rs create mode 100644 crates/host_env/src/signal.rs create mode 100644 crates/host_env/src/syslog.rs create mode 100644 crates/host_env/src/termios.rs create mode 100644 crates/host_env/src/time.rs create mode 100644 crates/host_env/src/winapi.rs diff --git a/Cargo.lock b/Cargo.lock index dfb0f56c9ea..e7e38637645 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3227,6 +3227,7 @@ dependencies = [ "nix 0.30.1", "num-traits", "rustpython-wtf8", + "termios", "widestring", "windows-sys 0.61.2", ] diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml index d405f794c7d..22dc659786a 100644 --- a/crates/host_env/Cargo.toml +++ b/crates/host_env/Cargo.toml @@ -17,15 +17,24 @@ num-traits = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true } +[target.'cfg(all(unix, not(target_os = "ios"), not(target_os = "redox")))'.dependencies] +termios = "0.3.3" + [target.'cfg(windows)'.dependencies] widestring = { workspace = true } windows-sys = { workspace = true, features = [ "Win32_Foundation", + "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Diagnostics_Debug", "Win32_System_Ioctl", "Win32_System_LibraryLoader", + "Win32_System_SystemInformation", "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_Time", ] } [lints] diff --git a/crates/host_env/src/fcntl.rs b/crates/host_env/src/fcntl.rs new file mode 100644 index 00000000000..b4dba53fa3d --- /dev/null +++ b/crates/host_env/src/fcntl.rs @@ -0,0 +1,75 @@ +use std::io; + +pub fn fcntl_int(fd: i32, cmd: i32, arg: i32) -> io::Result { + let ret = unsafe { libc::fcntl(fd, cmd, arg) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn fcntl_with_bytes(fd: i32, cmd: i32, arg: &mut [u8]) -> io::Result { + let ret = unsafe { libc::fcntl(fd, cmd, arg.as_mut_ptr()) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +/// # Safety +/// +/// `arg` must be a valid pointer for the `request` passed to `ioctl` and must +/// satisfy the platform ABI requirements for that request. +pub unsafe fn ioctl_ptr( + fd: i32, + request: libc::c_ulong, + arg: *mut libc::c_void, +) -> io::Result { + let ret = unsafe { libc::ioctl(fd, request as _, arg) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn ioctl_int(fd: i32, request: libc::c_ulong, arg: i32) -> io::Result { + let ret = unsafe { libc::ioctl(fd, request as _, arg) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +#[cfg(not(any(target_os = "wasi", target_os = "redox")))] +pub fn flock(fd: i32, operation: i32) -> io::Result { + let ret = unsafe { libc::flock(fd, operation) }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +#[cfg(not(any(target_os = "wasi", target_os = "redox")))] +pub fn lockf(fd: i32, cmd: i32, lock: &libc::flock) -> io::Result { + let ret = unsafe { + libc::fcntl( + fd, + if (cmd & libc::LOCK_NB) != 0 { + libc::F_SETLK + } else { + libc::F_SETLKW + }, + lock, + ) + }; + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs index b2c4ddacf6d..6ae544c0147 100644 --- a/crates/host_env/src/lib.rs +++ b/crates/host_env/src/lib.rs @@ -14,3 +14,27 @@ pub mod fileutils; #[cfg(windows)] pub mod windows; + +#[cfg(any(unix, target_os = "wasi"))] +pub mod fcntl; +#[cfg(any(unix, windows, target_os = "wasi"))] +pub mod select; +#[cfg(unix)] +pub mod syslog; +#[cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))] +pub mod termios; + +#[cfg(unix)] +pub mod posix; +#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] +pub mod shm; +#[cfg(unix)] +pub mod signal; +pub mod time; + +#[cfg(windows)] +pub mod msvcrt; +#[cfg(windows)] +pub mod nt; +#[cfg(windows)] +pub mod winapi; diff --git a/crates/host_env/src/msvcrt.rs b/crates/host_env/src/msvcrt.rs new file mode 100644 index 00000000000..00a599e3944 --- /dev/null +++ b/crates/host_env/src/msvcrt.rs @@ -0,0 +1,126 @@ +use alloc::{string::String, vec::Vec}; +use std::io; + +use crate::crt_fd; +use windows_sys::Win32::System::Diagnostics::Debug; + +pub const LK_UNLCK: i32 = 0; +pub const LK_LOCK: i32 = 1; +pub const LK_NBLCK: i32 = 2; +pub const LK_RLCK: i32 = 3; +pub const LK_NBRLCK: i32 = 4; + +unsafe extern "C" { + fn _getch() -> i32; + fn _getwch() -> u32; + fn _getche() -> i32; + fn _getwche() -> u32; + fn _putch(c: u32) -> i32; + fn _putwch(c: u16) -> u32; + fn _ungetch(c: i32) -> i32; + fn _ungetwch(c: u32) -> u32; + fn _locking(fd: i32, mode: i32, nbytes: i64) -> i32; + fn _heapmin() -> i32; + fn _kbhit() -> i32; + fn _setmode(fd: crt_fd::Borrowed<'_>, flags: i32) -> i32; +} + +pub fn setmode_binary(fd: crt_fd::Borrowed<'_>) { + unsafe { suppress_iph!(_setmode(fd, libc::O_BINARY)) }; +} + +pub fn getch() -> Vec { + vec![unsafe { _getch() } as u8] +} + +pub fn getwch() -> String { + let value = unsafe { _getwch() }; + char::from_u32(value) + .unwrap_or_else(|| panic!("invalid unicode {value:#x} from _getwch")) + .to_string() +} + +pub fn getche() -> Vec { + vec![unsafe { _getche() } as u8] +} + +pub fn getwche() -> String { + let value = unsafe { _getwche() }; + char::from_u32(value) + .unwrap_or_else(|| panic!("invalid unicode {value:#x} from _getwche")) + .to_string() +} + +pub fn putch(c: u8) { + unsafe { suppress_iph!(_putch(c.into())) }; +} + +pub fn putwch(c: char) { + unsafe { suppress_iph!(_putwch(c as u16)) }; +} + +pub fn ungetch(c: u8) -> io::Result<()> { + let ret = unsafe { suppress_iph!(_ungetch(c as i32)) }; + if ret == -1 { + Err(io::Error::from_raw_os_error(libc::ENOSPC)) + } else { + Ok(()) + } +} + +pub fn ungetwch(c: char) -> io::Result<()> { + let ret = unsafe { suppress_iph!(_ungetwch(c as u32)) }; + if ret == 0xFFFF { + Err(io::Error::from_raw_os_error(libc::ENOSPC)) + } else { + Ok(()) + } +} + +pub fn kbhit() -> i32 { + unsafe { _kbhit() } +} + +pub fn locking(fd: i32, mode: i32, nbytes: i64) -> io::Result<()> { + let ret = unsafe { suppress_iph!(_locking(fd, mode, nbytes)) }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn heapmin() -> io::Result<()> { + let ret = unsafe { suppress_iph!(_heapmin()) }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn setmode(fd: crt_fd::Borrowed<'_>, flags: i32) -> io::Result { + let ret = unsafe { suppress_iph!(_setmode(fd, flags)) }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn open_osfhandle(handle: isize, flags: i32) -> io::Result { + let ret = unsafe { suppress_iph!(libc::open_osfhandle(handle, flags)) }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn get_error_mode() -> u32 { + unsafe { suppress_iph!(Debug::GetErrorMode()) } +} + +pub fn set_error_mode(mode: Debug::THREAD_ERROR_MODE) -> u32 { + unsafe { suppress_iph!(Debug::SetErrorMode(mode)) } +} diff --git a/crates/host_env/src/nt.rs b/crates/host_env/src/nt.rs new file mode 100644 index 00000000000..c6771aad40a --- /dev/null +++ b/crates/host_env/src/nt.rs @@ -0,0 +1,73 @@ +// cspell:ignore hchmod +use std::{ffi::OsStr, io, os::windows::io::AsRawHandle}; + +use crate::{crt_fd, windows::ToWideString}; +use windows_sys::Win32::{ + Foundation::HANDLE, + Storage::FileSystem::{ + FILE_ATTRIBUTE_READONLY, FILE_BASIC_INFO, FileBasicInfo, GetFileAttributesW, + GetFileInformationByHandleEx, INVALID_FILE_ATTRIBUTES, SetFileAttributesW, + SetFileInformationByHandle, + }, +}; + +#[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() }; + let ret = unsafe { + GetFileInformationByHandleEx( + handle, + FileBasicInfo, + (&mut info as *mut FILE_BASIC_INFO).cast(), + core::mem::size_of::() as u32, + ) + }; + if ret == 0 { + return Err(io::Error::last_os_error()); + } + + if mode & write_bit != 0 { + info.FileAttributes &= !FILE_ATTRIBUTE_READONLY; + } else { + info.FileAttributes |= FILE_ATTRIBUTE_READONLY; + } + + let ret = unsafe { + SetFileInformationByHandle( + handle, + FileBasicInfo, + (&info as *const FILE_BASIC_INFO).cast(), + core::mem::size_of::() as u32, + ) + }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +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()) }; + if attr == INVALID_FILE_ATTRIBUTES { + return Err(io::Error::last_os_error()); + } + let new_attr = if mode & write_bit != 0 { + attr & !FILE_ATTRIBUTE_READONLY + } else { + attr | FILE_ATTRIBUTE_READONLY + }; + let ret = unsafe { SetFileAttributesW(wide.as_ptr(), new_attr) }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs new file mode 100644 index 00000000000..e624db10a15 --- /dev/null +++ b/crates/host_env/src/posix.rs @@ -0,0 +1,13 @@ +use std::os::fd::BorrowedFd; + +pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> { + use nix::fcntl; + + let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?); + let mut new_flags = flags; + new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable); + if flags != new_flags { + fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?; + } + Ok(()) +} diff --git a/crates/host_env/src/select.rs b/crates/host_env/src/select.rs new file mode 100644 index 00000000000..e5ddc4284f7 --- /dev/null +++ b/crates/host_env/src/select.rs @@ -0,0 +1,176 @@ +use core::mem::MaybeUninit; +use std::io; + +#[cfg(unix)] +mod platform { + pub use libc::{FD_ISSET, FD_SET, FD_SETSIZE, FD_ZERO, fd_set, select, timeval}; + pub use std::os::unix::io::RawFd; + + pub const fn check_err(x: i32) -> bool { + x < 0 + } +} + +#[allow(non_snake_case)] +#[cfg(windows)] +mod platform { + pub use WinSock::{FD_SET as fd_set, FD_SETSIZE, SOCKET as RawFd, TIMEVAL as timeval, select}; + use windows_sys::Win32::Networking::WinSock; + + pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) { + let mut slot = unsafe { (&raw mut (*set).fd_array).cast::() }; + let fd_count = unsafe { (*set).fd_count }; + for _ in 0..fd_count { + if unsafe { *slot } == fd { + return; + } + slot = unsafe { slot.add(1) }; + } + if fd_count < FD_SETSIZE { + unsafe { + *slot = fd as RawFd; + (*set).fd_count += 1; + } + } + } + + pub unsafe fn FD_ZERO(set: *mut fd_set) { + unsafe { (*set).fd_count = 0 }; + } + + pub unsafe fn FD_ISSET(fd: RawFd, set: *mut fd_set) -> bool { + use WinSock::__WSAFDIsSet; + unsafe { __WSAFDIsSet(fd as _, set) != 0 } + } + + pub fn check_err(x: i32) -> bool { + x == WinSock::SOCKET_ERROR + } +} + +#[cfg(target_os = "wasi")] +mod platform { + pub use libc::{FD_SETSIZE, timeval}; + pub use std::os::fd::RawFd; + + pub const fn check_err(x: i32) -> bool { + x < 0 + } + + #[repr(C)] + pub struct fd_set { + __nfds: usize, + __fds: [libc::c_int; FD_SETSIZE], + } + + #[allow(non_snake_case)] + pub unsafe fn FD_ISSET(fd: RawFd, set: *const fd_set) -> bool { + let set = unsafe { &*set }; + for p in &set.__fds[..set.__nfds] { + if *p == fd { + return true; + } + } + false + } + + #[allow(non_snake_case)] + pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) { + let set = unsafe { &mut *set }; + for p in &set.__fds[..set.__nfds] { + if *p == fd { + return; + } + } + let n = set.__nfds; + assert!(n < set.__fds.len(), "fd_set full"); + set.__fds[n] = fd; + set.__nfds = n + 1; + } + + #[allow(non_snake_case)] + pub unsafe fn FD_ZERO(set: *mut fd_set) { + unsafe { (*set).__nfds = 0 }; + } + + unsafe extern "C" { + pub fn select( + nfds: libc::c_int, + readfds: *mut fd_set, + writefds: *mut fd_set, + errorfds: *mut fd_set, + timeout: *const timeval, + ) -> libc::c_int; + } +} + +pub use platform::{RawFd, timeval}; + +#[repr(transparent)] +pub struct FdSet(MaybeUninit); + +impl FdSet { + pub fn new() -> Self { + let mut fdset = MaybeUninit::zeroed(); + unsafe { platform::FD_ZERO(fdset.as_mut_ptr()) }; + Self(fdset) + } + + pub fn insert(&mut self, fd: RawFd) { + unsafe { platform::FD_SET(fd, self.0.as_mut_ptr()) }; + } + + pub fn contains(&mut self, fd: RawFd) -> bool { + unsafe { platform::FD_ISSET(fd, self.0.as_mut_ptr()) } + } + + pub fn clear(&mut self) { + unsafe { platform::FD_ZERO(self.0.as_mut_ptr()) }; + } + + pub fn highest(&mut self) -> Option { + (0..platform::FD_SETSIZE as RawFd) + .rev() + .find(|&fd| self.contains(fd)) + } +} + +impl Default for FdSet { + fn default() -> Self { + Self::new() + } +} + +pub fn select( + nfds: libc::c_int, + readfds: &mut FdSet, + writefds: &mut FdSet, + errfds: &mut FdSet, + timeout: Option<&mut timeval>, +) -> io::Result { + let timeout = match timeout { + Some(tv) => tv as *mut timeval, + None => core::ptr::null_mut(), + }; + let ret = unsafe { + platform::select( + nfds, + readfds.0.as_mut_ptr(), + writefds.0.as_mut_ptr(), + errfds.0.as_mut_ptr(), + timeout, + ) + }; + if platform::check_err(ret) { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn sec_to_timeval(sec: f64) -> timeval { + timeval { + tv_sec: sec.trunc() as _, + tv_usec: (sec.fract() * 1e6) as _, + } +} diff --git a/crates/host_env/src/shm.rs b/crates/host_env/src/shm.rs new file mode 100644 index 00000000000..08a2ae9787c --- /dev/null +++ b/crates/host_env/src/shm.rs @@ -0,0 +1,23 @@ +use core::ffi::CStr; +use std::io; + +pub fn shm_open(name: &CStr, flags: libc::c_int, mode: libc::c_uint) -> io::Result { + #[cfg(target_os = "freebsd")] + let mode = mode.try_into().unwrap(); + + let fd = unsafe { libc::shm_open(name.as_ptr(), flags, mode) }; + if fd == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(fd) + } +} + +pub fn shm_unlink(name: &CStr) -> io::Result<()> { + let ret = unsafe { libc::shm_unlink(name.as_ptr()) }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/signal.rs b/crates/host_env/src/signal.rs new file mode 100644 index 00000000000..13a7206c68a --- /dev/null +++ b/crates/host_env/src/signal.rs @@ -0,0 +1,17 @@ +pub fn timeval_to_double(tv: &libc::timeval) -> f64 { + tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0) +} + +pub fn double_to_timeval(val: f64) -> libc::timeval { + libc::timeval { + tv_sec: val.trunc() as _, + tv_usec: (val.fract() * 1_000_000.0) as _, + } +} + +pub fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { + ( + timeval_to_double(&it.it_value), + timeval_to_double(&it.it_interval), + ) +} diff --git a/crates/host_env/src/syslog.rs b/crates/host_env/src/syslog.rs new file mode 100644 index 00000000000..67c10f6f21a --- /dev/null +++ b/crates/host_env/src/syslog.rs @@ -0,0 +1,68 @@ +use alloc::boxed::Box; +use core::ffi::CStr; +use std::{ + os::raw::c_char, + sync::{OnceLock, RwLock}, +}; + +#[derive(Debug)] +enum GlobalIdent { + Explicit(Box), + Implicit, +} + +impl GlobalIdent { + fn as_ptr(&self) -> *const c_char { + match self { + Self::Explicit(cstr) => cstr.as_ptr(), + Self::Implicit => core::ptr::null(), + } + } +} + +fn global_ident() -> &'static RwLock> { + static IDENT: OnceLock>> = OnceLock::new(); + IDENT.get_or_init(|| RwLock::new(None)) +} + +pub fn is_open() -> bool { + global_ident() + .read() + .expect("syslog lock poisoned") + .is_some() +} + +pub fn openlog(ident: Option>, logoption: i32, facility: i32) { + let ident = match ident { + Some(ident) => GlobalIdent::Explicit(ident), + None => GlobalIdent::Implicit, + }; + let mut locked_ident = global_ident().write().expect("syslog lock poisoned"); + unsafe { libc::openlog(ident.as_ptr(), logoption, facility) }; + *locked_ident = Some(ident); +} + +pub fn syslog(priority: i32, msg: &CStr) { + let cformat = c"%s"; + unsafe { libc::syslog(priority, cformat.as_ptr(), msg.as_ptr()) }; +} + +pub fn closelog() { + if is_open() { + let mut locked_ident = global_ident().write().expect("syslog lock poisoned"); + unsafe { libc::closelog() }; + *locked_ident = None; + } +} + +pub fn setlogmask(maskpri: i32) -> i32 { + unsafe { libc::setlogmask(maskpri) } +} + +pub const fn log_mask(pri: i32) -> i32 { + pri << 1 +} + +pub const fn log_upto(pri: i32) -> i32 { + (1 << (pri + 1)) - 1 +} diff --git a/crates/host_env/src/termios.rs b/crates/host_env/src/termios.rs new file mode 100644 index 00000000000..76bcd0c9f01 --- /dev/null +++ b/crates/host_env/src/termios.rs @@ -0,0 +1,23 @@ +pub fn tcgetattr(fd: i32) -> std::io::Result<::termios::Termios> { + ::termios::Termios::from_fd(fd) +} + +pub fn tcsetattr(fd: i32, when: i32, termios: &::termios::Termios) -> std::io::Result<()> { + ::termios::tcsetattr(fd, when, termios) +} + +pub fn tcsendbreak(fd: i32, duration: i32) -> std::io::Result<()> { + ::termios::tcsendbreak(fd, duration) +} + +pub fn tcdrain(fd: i32) -> std::io::Result<()> { + ::termios::tcdrain(fd) +} + +pub fn tcflush(fd: i32, queue: i32) -> std::io::Result<()> { + ::termios::tcflush(fd, queue) +} + +pub fn tcflow(fd: i32, action: i32) -> std::io::Result<()> { + ::termios::tcflow(fd, action) +} diff --git a/crates/host_env/src/time.rs b/crates/host_env/src/time.rs new file mode 100644 index 00000000000..2050356e406 --- /dev/null +++ b/crates/host_env/src/time.rs @@ -0,0 +1,46 @@ +use core::time::Duration; +use std::{ + io, + time::{SystemTime, UNIX_EPOCH}, +}; + +pub const SEC_TO_MS: i64 = 1000; +pub const MS_TO_US: i64 = 1000; +pub const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US; +pub const US_TO_NS: i64 = 1000; +pub const MS_TO_NS: i64 = MS_TO_US * US_TO_NS; +pub const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS; +pub const NS_TO_MS: i64 = 1000 * 1000; +pub const NS_TO_US: i64 = 1000; + +pub fn duration_since_system_now() -> io::Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(io::Error::other) +} + +#[cfg(target_env = "msvc")] +#[cfg(not(target_arch = "wasm32"))] +pub fn get_tz_info() -> windows_sys::Win32::System::Time::TIME_ZONE_INFORMATION { + let mut info = unsafe { core::mem::zeroed() }; + unsafe { windows_sys::Win32::System::Time::GetTimeZoneInformation(&mut info) }; + info +} + +#[cfg(any(unix, windows))] +pub fn asctime_from_tm(tm: &libc::tm) -> String { + const WDAY_NAME: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const MON_NAME: [&str; 12] = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ]; + format!( + "{} {}{:>3} {:02}:{:02}:{:02} {}", + WDAY_NAME[tm.tm_wday as usize], + MON_NAME[tm.tm_mon as usize], + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + tm.tm_year + 1900 + ) +} diff --git a/crates/host_env/src/winapi.rs b/crates/host_env/src/winapi.rs new file mode 100644 index 00000000000..26f46969315 --- /dev/null +++ b/crates/host_env/src/winapi.rs @@ -0,0 +1,17 @@ +use windows_sys::Win32::Foundation::HANDLE; + +pub fn get_acp() -> u32 { + unsafe { windows_sys::Win32::Globalization::GetACP() } +} + +pub fn get_current_process() -> HANDLE { + unsafe { windows_sys::Win32::System::Threading::GetCurrentProcess() } +} + +pub fn get_last_error() -> u32 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } +} + +pub fn get_version() -> u32 { + unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() } +} diff --git a/crates/stdlib/src/fcntl.rs b/crates/stdlib/src/fcntl.rs index 0f75a09ba0f..6d27b3cee57 100644 --- a/crates/stdlib/src/fcntl.rs +++ b/crates/stdlib/src/fcntl.rs @@ -4,6 +4,8 @@ pub(crate) use fcntl::module_def; #[pymodule] mod fcntl { + use rustpython_host_env::fcntl as host_fcntl; + use crate::vm::{ PyResult, VirtualMachine, builtins::PyIntRef, @@ -73,19 +75,15 @@ mod fcntl { .ok_or_else(|| vm.new_value_error("fcntl string arg too long"))? .copy_from_slice(&s) } - let ret = unsafe { libc::fcntl(fd, cmd, buf.as_mut_ptr()) }; - if ret < 0 { - return Err(vm.new_last_errno_error()); - } + host_fcntl::fcntl_with_bytes(fd, cmd, &mut buf[..arg_len]) + .map_err(|_| vm.new_last_errno_error())?; return Ok(vm.ctx.new_bytes(buf[..arg_len].to_vec()).into()); } OptionalArg::Present(Either::B(i)) => i.as_u32_mask(), OptionalArg::Missing => 0, }; - let ret = unsafe { libc::fcntl(fd, cmd, int as i32) }; - if ret < 0 { - return Err(vm.new_last_errno_error()); - } + let ret = + host_fcntl::fcntl_int(fd, cmd, int as i32).map_err(|_| vm.new_last_errno_error())?; Ok(vm.new_pyobj(ret)) } @@ -118,11 +116,10 @@ mod fcntl { let mutate_flag = mutate_flag.unwrap_or(true); let mut arg_buf = rw_arg.borrow_buf_mut(); if mutate_flag { - let ret = - unsafe { libc::ioctl(fd, request as _, arg_buf.as_mut_ptr()) }; - if ret < 0 { - return Err(vm.new_last_errno_error()); + let ret = unsafe { + host_fcntl::ioctl_ptr(fd, request, arg_buf.as_mut_ptr().cast()) } + .map_err(|_| vm.new_last_errno_error())?; return Ok(vm.ctx.new_int(ret).into()); } // treat like an immutable buffer @@ -130,17 +127,13 @@ mod fcntl { } Either::B(ro_buf) => fill_buf(&ro_buf.borrow_bytes())?, }; - let ret = unsafe { libc::ioctl(fd, request as _, buf.as_mut_ptr()) }; - if ret < 0 { - return Err(vm.new_last_errno_error()); - } + unsafe { host_fcntl::ioctl_ptr(fd, request, buf.as_mut_ptr().cast()) } + .map_err(|_| vm.new_last_errno_error())?; Ok(vm.ctx.new_bytes(buf[..buf_len].to_vec()).into()) } Either::B(i) => { - let ret = unsafe { libc::ioctl(fd, request as _, i) }; - if ret < 0 { - return Err(vm.new_last_errno_error()); - } + let ret = + host_fcntl::ioctl_int(fd, request, i).map_err(|_| vm.new_last_errno_error())?; Ok(vm.ctx.new_int(ret).into()) } } @@ -150,11 +143,7 @@ mod fcntl { #[cfg(not(any(target_os = "wasi", target_os = "redox")))] #[pyfunction] fn flock(_io::Fildes(fd): _io::Fildes, operation: i32, vm: &VirtualMachine) -> PyResult { - let ret = unsafe { libc::flock(fd, operation) }; - // TODO: add support for platforms that don't have a builtin `flock` syscall - if ret < 0 { - return Err(vm.new_last_errno_error()); - } + let ret = host_fcntl::flock(fd, operation).map_err(|_| vm.new_last_errno_error())?; Ok(vm.ctx.new_int(ret).into()) } @@ -201,20 +190,7 @@ mod fcntl { .map_err(|e| vm.new_overflow_error(format!("{e}")))?, OptionalArg::Missing => 0, }; - let ret = unsafe { - libc::fcntl( - fd, - if (cmd & libc::LOCK_NB) != 0 { - libc::F_SETLK - } else { - libc::F_SETLKW - }, - &l, - ) - }; - if ret < 0 { - return Err(vm.new_last_errno_error()); - } + let ret = host_fcntl::lockf(fd, cmd, &l).map_err(|_| vm.new_last_errno_error())?; Ok(vm.ctx.new_int(ret).into()) } } diff --git a/crates/stdlib/src/posixshmem.rs b/crates/stdlib/src/posixshmem.rs index da1a8cf1d89..91fdf4aafbc 100644 --- a/crates/stdlib/src/posixshmem.rs +++ b/crates/stdlib/src/posixshmem.rs @@ -9,7 +9,7 @@ mod _posixshmem { use crate::vm::{ FromArgs, PyResult, VirtualMachine, builtins::PyUtf8StrRef, convert::IntoPyException, }; - use rustpython_host_env::os::errno_io_error; + use rustpython_host_env::shm; #[derive(FromArgs)] struct ShmOpenArgs { @@ -25,26 +25,12 @@ mod _posixshmem { fn shm_open(args: ShmOpenArgs, vm: &VirtualMachine) -> PyResult { let name = CString::new(args.name.as_str()).map_err(|e| e.into_pyexception(vm))?; let mode: libc::c_uint = args.mode as _; - #[cfg(target_os = "freebsd")] - let mode = mode.try_into().unwrap(); - // SAFETY: `name` is a NUL-terminated string and `shm_open` does not write through it. - let fd = unsafe { libc::shm_open(name.as_ptr(), args.flags, mode) }; - if fd == -1 { - Err(errno_io_error().into_pyexception(vm)) - } else { - Ok(fd) - } + shm::shm_open(name.as_c_str(), args.flags, mode).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] fn shm_unlink(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<()> { let name = CString::new(name.as_str()).map_err(|e| e.into_pyexception(vm))?; - // SAFETY: `name` is a valid NUL-terminated string and `shm_unlink` only reads it. - let ret = unsafe { libc::shm_unlink(name.as_ptr()) }; - if ret == -1 { - Err(errno_io_error().into_pyexception(vm)) - } else { - Ok(()) - } + shm::shm_unlink(name.as_c_str()).map_err(|e| e.into_pyexception(vm)) } } diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index b52144247f7..1cfe22e7c61 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -5,119 +5,9 @@ pub(crate) use decl::module_def; use crate::vm::{ PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::PyListRef, }; -use core::mem; +use rustpython_host_env::select::{self as host_select, FdSet, RawFd}; use std::io; -#[cfg(unix)] -mod platform { - pub use libc::{FD_ISSET, FD_SET, FD_SETSIZE, FD_ZERO, fd_set, select, timeval}; - pub use std::os::unix::io::RawFd; - - pub const fn check_err(x: i32) -> bool { - x < 0 - } -} - -#[allow(non_snake_case)] -#[cfg(windows)] -mod platform { - pub use WinSock::{FD_SET as fd_set, FD_SETSIZE, SOCKET as RawFd, TIMEVAL as timeval, select}; - use windows_sys::Win32::Networking::WinSock; - - // based off winsock2.h: https://gist.github.com/piscisaureus/906386#file-winsock2-h-L128-L141 - - pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) { - unsafe { - let mut slot = (&raw mut (*set).fd_array).cast::(); - let fd_count = (*set).fd_count; - for _ in 0..fd_count { - if *slot == fd { - return; - } - slot = slot.add(1); - } - // slot == &fd_array[fd_count] at this point - if fd_count < FD_SETSIZE { - *slot = fd as RawFd; - (*set).fd_count += 1; - } - } - } - - pub unsafe fn FD_ZERO(set: *mut fd_set) { - unsafe { (*set).fd_count = 0 }; - } - - pub unsafe fn FD_ISSET(fd: RawFd, set: *mut fd_set) -> bool { - use WinSock::__WSAFDIsSet; - unsafe { __WSAFDIsSet(fd as _, set) != 0 } - } - - pub fn check_err(x: i32) -> bool { - x == WinSock::SOCKET_ERROR - } -} - -#[cfg(target_os = "wasi")] -mod platform { - pub use libc::{FD_SETSIZE, timeval}; - pub use std::os::fd::RawFd; - - pub fn check_err(x: i32) -> bool { - x < 0 - } - - #[repr(C)] - pub struct fd_set { - __nfds: usize, - __fds: [libc::c_int; FD_SETSIZE], - } - - #[allow(non_snake_case)] - pub unsafe fn FD_ISSET(fd: RawFd, set: *const fd_set) -> bool { - let set = unsafe { &*set }; - let n = set.__nfds; - for p in &set.__fds[..n] { - if *p == fd { - return true; - } - } - false - } - - #[allow(non_snake_case)] - pub unsafe fn FD_SET(fd: RawFd, set: *mut fd_set) { - let set = unsafe { &mut *set }; - let n = set.__nfds; - for p in &set.__fds[..n] { - if *p == fd { - return; - } - } - set.__nfds = n + 1; - set.__fds[n] = fd; - } - - #[allow(non_snake_case)] - pub unsafe fn FD_ZERO(set: *mut fd_set) { - let set = unsafe { &mut *set }; - set.__nfds = 0; - } - - unsafe extern "C" { - pub fn select( - nfds: libc::c_int, - readfds: *mut fd_set, - writefds: *mut fd_set, - errorfds: *mut fd_set, - timeout: *const timeval, - ) -> libc::c_int; - } -} - -use platform::RawFd; -pub use platform::timeval; - #[derive(Traverse)] struct Selectable { obj: PyObjectRef, @@ -139,72 +29,6 @@ impl TryFromObject for Selectable { } } -// Keep it in a MaybeUninit, since on windows FD_ZERO doesn't actually zero the whole thing -#[repr(transparent)] -pub struct FdSet(mem::MaybeUninit); - -impl FdSet { - pub fn new() -> Self { - // it's just ints, and all the code that's actually - // interacting with it is in C, so it's safe to zero - let mut fdset = core::mem::MaybeUninit::zeroed(); - unsafe { platform::FD_ZERO(fdset.as_mut_ptr()) }; - Self(fdset) - } - - pub fn insert(&mut self, fd: RawFd) { - unsafe { platform::FD_SET(fd, self.0.as_mut_ptr()) }; - } - - pub fn contains(&mut self, fd: RawFd) -> bool { - unsafe { platform::FD_ISSET(fd, self.0.as_mut_ptr()) } - } - - pub fn clear(&mut self) { - unsafe { platform::FD_ZERO(self.0.as_mut_ptr()) }; - } - - pub fn highest(&mut self) -> Option { - (0..platform::FD_SETSIZE as RawFd) - .rev() - .find(|&i| self.contains(i)) - } -} - -pub fn select( - nfds: libc::c_int, - readfds: &mut FdSet, - writefds: &mut FdSet, - errfds: &mut FdSet, - timeout: Option<&mut timeval>, -) -> io::Result { - let timeout = match timeout { - Some(tv) => tv as *mut timeval, - None => core::ptr::null_mut(), - }; - let ret = unsafe { - platform::select( - nfds, - readfds.0.as_mut_ptr(), - writefds.0.as_mut_ptr(), - errfds.0.as_mut_ptr(), - timeout, - ) - }; - if platform::check_err(ret) { - Err(io::Error::last_os_error()) - } else { - Ok(ret) - } -} - -fn sec_to_timeval(sec: f64) -> timeval { - timeval { - tv_sec: sec.trunc() as _, - tv_usec: (sec.fract() * 1e6) as _, - } -} - #[pymodule(name = "select")] mod decl { use super::*; @@ -279,8 +103,9 @@ mod decl { .map_or(0, |n| n + 1) as _; loop { - let mut tv = timeout.map(sec_to_timeval); - let res = vm.allow_threads(|| super::select(nfds, &mut r, &mut w, &mut x, tv.as_mut())); + let mut tv = timeout.map(host_select::sec_to_timeval); + let res = + vm.allow_threads(|| host_select::select(nfds, &mut r, &mut w, &mut x, tv.as_mut())); match res { Ok(_) => break, diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index fe2aa6be3da..f14e6ab62d7 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -2787,13 +2787,13 @@ mod _socket { } #[cfg(windows)] { - use crate::select; + use rustpython_host_env::select as host_select; let fd = sock_fileno(sock); - let mut reads = select::FdSet::new(); - let mut writes = select::FdSet::new(); - let mut errs = select::FdSet::new(); + let mut reads = host_select::FdSet::new(); + let mut writes = host_select::FdSet::new(); + let mut errs = host_select::FdSet::new(); let fd = fd as usize; match kind { @@ -2805,12 +2805,12 @@ mod _socket { } } - let mut interval = interval.map(|dur| select::timeval { + let mut interval = interval.map(|dur| host_select::timeval { tv_sec: dur.as_secs() as _, tv_usec: dur.subsec_micros() as _, }); - select::select( + host_select::select( fd as i32 + 1, &mut reads, &mut writes, diff --git a/crates/stdlib/src/syslog.rs b/crates/stdlib/src/syslog.rs index 8f446a8e161..fab82f14f56 100644 --- a/crates/stdlib/src/syslog.rs +++ b/crates/stdlib/src/syslog.rs @@ -4,15 +4,13 @@ pub(crate) use syslog::module_def; #[pymodule(name = "syslog")] mod syslog { - use crate::common::lock::PyRwLock; use crate::vm::{ PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyStr, PyStrRef}, function::{OptionalArg, OptionalOption}, utils::ToCString, }; - use core::ffi::CStr; - use std::os::raw::c_char; + use rustpython_host_env::syslog as host_syslog; #[pyattr] use libc::{ @@ -41,28 +39,6 @@ mod syslog { None } - #[derive(Debug)] - enum GlobalIdent { - Explicit(Box), - Implicit, - } - - impl GlobalIdent { - fn as_ptr(&self) -> *const c_char { - match self { - Self::Explicit(cstr) => cstr.as_ptr(), - Self::Implicit => core::ptr::null(), - } - } - } - - fn global_ident() -> &'static PyRwLock> { - rustpython_common::static_cell! { - static IDENT: PyRwLock>; - }; - IDENT.get_or_init(|| PyRwLock::new(None)) - } - #[derive(Default, FromArgs)] struct OpenLogArgs { #[pyarg(any, optional)] @@ -83,16 +59,7 @@ mod syslog { } .map(|ident| ident.into_boxed_c_str()); - let ident = match ident { - Some(ident) => GlobalIdent::Explicit(ident), - None => GlobalIdent::Implicit, - }; - - { - let mut locked_ident = global_ident().write(); - unsafe { libc::openlog(ident.as_ptr(), logoption, facility) }; - *locked_ident = Some(ident); - } + host_syslog::openlog(ident, logoption, facility); Ok(()) } @@ -111,38 +78,34 @@ mod syslog { None => (LOG_INFO, args.priority.try_into_value(vm)?), }; - if global_ident().read().is_none() { + if !host_syslog::is_open() { openlog(OpenLogArgs::default(), vm)?; } - let (cformat, cmsg) = ("%s".to_cstring(vm)?, msg.to_cstring(vm)?); - unsafe { libc::syslog(priority, cformat.as_ptr(), cmsg.as_ptr()) }; + let cmsg = msg.to_cstring(vm)?; + host_syslog::syslog(priority, cmsg.as_c_str()); Ok(()) } #[pyfunction] fn closelog() { - if global_ident().read().is_some() { - let mut locked_ident = global_ident().write(); - unsafe { libc::closelog() }; - *locked_ident = None; - } + host_syslog::closelog(); } #[pyfunction] fn setlogmask(maskpri: i32) -> i32 { - unsafe { libc::setlogmask(maskpri) } + host_syslog::setlogmask(maskpri) } #[inline] #[pyfunction(name = "LOG_MASK")] const fn log_mask(pri: i32) -> i32 { - pri << 1 + host_syslog::log_mask(pri) } #[inline] #[pyfunction(name = "LOG_UPTO")] const fn log_upto(pri: i32) -> i32 { - (1 << (pri + 1)) - 1 + host_syslog::log_upto(pri) } } diff --git a/crates/stdlib/src/termios.rs b/crates/stdlib/src/termios.rs index 8f7963c159f..919b4ff702a 100644 --- a/crates/stdlib/src/termios.rs +++ b/crates/stdlib/src/termios.rs @@ -9,8 +9,7 @@ mod termios { builtins::{PyBaseExceptionRef, PyBytes, PyInt, PyListRef, PyTypeRef}, convert::ToPyObject, }; - use rustpython_host_env::os::ErrorExt; - use termios::Termios; + use rustpython_host_env::{os::ErrorExt, termios as host_termios}; // TODO: more ioctl numbers // NOTE: B2500000, B3000000, B3500000, B4000000, and CIBAUD @@ -171,7 +170,7 @@ mod termios { #[pyfunction] fn tcgetattr(fd: i32, vm: &VirtualMachine) -> PyResult> { - let termios = Termios::from_fd(fd).map_err(|e| termios_error(e, vm))?; + let termios = host_termios::tcgetattr(fd).map_err(|e| termios_error(e, vm))?; let noncanon = (termios.c_lflag & termios::ICANON) == 0; let cc = termios .c_cc @@ -200,7 +199,7 @@ mod termios { <&[PyObjectRef; 7]>::try_from(&*attributes.borrow_vec()) .map_err(|_| vm.new_type_error("tcsetattr, arg 3: must be 7 element list"))? .clone(); - let mut termios = Termios::from_fd(fd).map_err(|e| termios_error(e, vm))?; + let mut termios = host_termios::tcgetattr(fd).map_err(|e| termios_error(e, vm))?; termios.c_iflag = iflag.try_into_value(vm)?; termios.c_oflag = oflag.try_into_value(vm)?; termios.c_cflag = cflag.try_into_value(vm)?; @@ -231,32 +230,32 @@ mod termios { }; } - termios::tcsetattr(fd, when, &termios).map_err(|e| termios_error(e, vm))?; + host_termios::tcsetattr(fd, when, &termios).map_err(|e| termios_error(e, vm))?; Ok(()) } #[pyfunction] fn tcsendbreak(fd: i32, duration: i32, vm: &VirtualMachine) -> PyResult<()> { - termios::tcsendbreak(fd, duration).map_err(|e| termios_error(e, vm))?; + host_termios::tcsendbreak(fd, duration).map_err(|e| termios_error(e, vm))?; Ok(()) } #[pyfunction] fn tcdrain(fd: i32, vm: &VirtualMachine) -> PyResult<()> { - termios::tcdrain(fd).map_err(|e| termios_error(e, vm))?; + host_termios::tcdrain(fd).map_err(|e| termios_error(e, vm))?; Ok(()) } #[pyfunction] fn tcflush(fd: i32, queue: i32, vm: &VirtualMachine) -> PyResult<()> { - termios::tcflush(fd, queue).map_err(|e| termios_error(e, vm))?; + host_termios::tcflush(fd, queue).map_err(|e| termios_error(e, vm))?; Ok(()) } #[pyfunction] fn tcflow(fd: i32, action: i32, vm: &VirtualMachine) -> PyResult<()> { - termios::tcflow(fd, action).map_err(|e| termios_error(e, vm))?; + host_termios::tcflow(fd, action).map_err(|e| termios_error(e, vm))?; Ok(()) } diff --git a/crates/vm/src/stdlib/_signal.rs b/crates/vm/src/stdlib/_signal.rs index eddf8ce8e46..0d9dfad311d 100644 --- a/crates/vm/src/stdlib/_signal.rs +++ b/crates/vm/src/stdlib/_signal.rs @@ -13,6 +13,8 @@ pub(crate) mod _signal { function::{ArgIntoFloat, OptionalArg}, }; use core::sync::atomic::{self, Ordering}; + #[cfg(unix)] + use rustpython_host_env::signal::{double_to_timeval, itimerval_to_tuple}; #[cfg(any(unix, windows))] use libc::sighandler_t; @@ -280,27 +282,6 @@ pub(crate) mod _signal { Ok(()) } - #[cfg(unix)] - fn timeval_to_double(tv: &libc::timeval) -> f64 { - tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0) - } - - #[cfg(unix)] - fn double_to_timeval(val: f64) -> libc::timeval { - libc::timeval { - tv_sec: val.trunc() as _, - tv_usec: ((val.fract()) * 1_000_000.0) as _, - } - } - - #[cfg(unix)] - fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { - ( - timeval_to_double(&it.it_value), - timeval_to_double(&it.it_interval), - ) - } - #[cfg(unix)] #[pyfunction] fn setitimer( diff --git a/crates/vm/src/stdlib/_winapi.rs b/crates/vm/src/stdlib/_winapi.rs index a90613bd7a2..9edb740cdb0 100644 --- a/crates/vm/src/stdlib/_winapi.rs +++ b/crates/vm/src/stdlib/_winapi.rs @@ -16,6 +16,7 @@ mod _winapi { }; use core::ptr::{null, null_mut}; use rustpython_common::wtf8::Wtf8Buf; + use rustpython_host_env::winapi as host_winapi; use rustpython_host_env::windows::ToWideString; use windows_sys::Win32::Foundation::{HANDLE, MAX_PATH}; @@ -201,12 +202,12 @@ mod _winapi { #[pyfunction] fn GetACP() -> u32 { - unsafe { windows_sys::Win32::Globalization::GetACP() } + host_winapi::get_acp() } #[pyfunction] fn GetCurrentProcess() -> WinHandle { - WinHandle(unsafe { windows_sys::Win32::System::Threading::GetCurrentProcess() }) + WinHandle(host_winapi::get_current_process()) } #[pyfunction] @@ -224,12 +225,12 @@ mod _winapi { #[pyfunction] fn GetLastError() -> u32 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } + host_winapi::get_last_error() } #[pyfunction] fn GetVersion() -> u32 { - unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() } + host_winapi::get_version() } #[derive(FromArgs)] diff --git a/crates/vm/src/stdlib/msvcrt.rs b/crates/vm/src/stdlib/msvcrt.rs index 46beb306fc6..f10de1238d5 100644 --- a/crates/vm/src/stdlib/msvcrt.rs +++ b/crates/vm/src/stdlib/msvcrt.rs @@ -8,9 +8,10 @@ mod msvcrt { PyRef, PyResult, VirtualMachine, builtins::{PyBytes, PyStrRef}, convert::IntoPyException, - host_env::{crt_fd, suppress_iph}, + host_env::crt_fd, }; use itertools::Itertools; + use rustpython_host_env::msvcrt as host_msvcrt; use std::os::windows::io::AsRawHandle; use windows_sys::Win32::System::Diagnostics::Debug; @@ -21,54 +22,36 @@ mod msvcrt { }; pub fn setmode_binary(fd: crt_fd::Borrowed<'_>) { - unsafe { suppress_iph!(_setmode(fd, libc::O_BINARY)) }; - } - - unsafe extern "C" { - fn _getch() -> i32; - fn _getwch() -> u32; - fn _getche() -> i32; - fn _getwche() -> u32; - fn _putch(c: u32) -> i32; - fn _putwch(c: u16) -> u32; - fn _ungetch(c: i32) -> i32; - fn _ungetwch(c: u32) -> u32; - fn _locking(fd: i32, mode: i32, nbytes: i64) -> i32; - fn _heapmin() -> i32; - fn _kbhit() -> i32; + host_msvcrt::setmode_binary(fd); } // Locking mode constants #[pyattr] - const LK_UNLCK: i32 = 0; // Unlock + const LK_UNLCK: i32 = host_msvcrt::LK_UNLCK; // Unlock #[pyattr] - const LK_LOCK: i32 = 1; // Lock (blocking) + const LK_LOCK: i32 = host_msvcrt::LK_LOCK; // Lock (blocking) #[pyattr] - const LK_NBLCK: i32 = 2; // Non-blocking lock + const LK_NBLCK: i32 = host_msvcrt::LK_NBLCK; // Non-blocking lock #[pyattr] - const LK_RLCK: i32 = 3; // Lock for reading (same as LK_LOCK) + const LK_RLCK: i32 = host_msvcrt::LK_RLCK; // Lock for reading (same as LK_LOCK) #[pyattr] - const LK_NBRLCK: i32 = 4; // Non-blocking lock for reading (same as LK_NBLCK) + const LK_NBRLCK: i32 = host_msvcrt::LK_NBRLCK; // Non-blocking lock for reading (same as LK_NBLCK) #[pyfunction] fn getch() -> Vec { - let c = unsafe { _getch() }; - vec![c as u8] + host_msvcrt::getch() } #[pyfunction] fn getwch() -> String { - let c = unsafe { _getwch() }; - char::from_u32(c).unwrap().to_string() + host_msvcrt::getwch() } #[pyfunction] fn getche() -> Vec { - let c = unsafe { _getche() }; - vec![c as u8] + host_msvcrt::getche() } #[pyfunction] fn getwche() -> String { - let c = unsafe { _getwche() }; - char::from_u32(c).unwrap().to_string() + host_msvcrt::getwche() } #[pyfunction] fn putch(b: PyRef, vm: &VirtualMachine) -> PyResult<()> { @@ -76,7 +59,7 @@ mod msvcrt { b.as_bytes().iter().exactly_one().map_err(|_| { vm.new_type_error("putch() argument must be a byte string of length 1") })?; - unsafe { suppress_iph!(_putch(c.into())) }; + host_msvcrt::putch(c); Ok(()) } #[pyfunction] @@ -86,7 +69,7 @@ mod msvcrt { .chars() .exactly_one() .map_err(|_| vm.new_type_error("putch() argument must be a string of length 1"))?; - unsafe { suppress_iph!(_putwch(c as u16)) }; + host_msvcrt::putwch(c); Ok(()) } @@ -95,13 +78,7 @@ mod msvcrt { let &c = b.as_bytes().iter().exactly_one().map_err(|_| { vm.new_type_error("ungetch() argument must be a byte string of length 1") })?; - let ret = unsafe { suppress_iph!(_ungetch(c as i32)) }; - if ret == -1 { - // EOF returned means the buffer is full - Err(vm.new_os_error(libc::ENOSPC)) - } else { - Ok(()) - } + host_msvcrt::ungetch(c).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] @@ -110,62 +87,32 @@ mod msvcrt { s.expect_str().chars().exactly_one().map_err(|_| { vm.new_type_error("ungetwch() argument must be a string of length 1") })?; - let ret = unsafe { suppress_iph!(_ungetwch(c as u32)) }; - if ret == 0xFFFF { - // WEOF returned means the buffer is full - Err(vm.new_os_error(libc::ENOSPC)) - } else { - Ok(()) - } + host_msvcrt::ungetwch(c).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] fn kbhit() -> i32 { - unsafe { _kbhit() } + host_msvcrt::kbhit() } #[pyfunction] fn locking(fd: i32, mode: i32, nbytes: i64, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { suppress_iph!(_locking(fd, mode, nbytes)) }; - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_msvcrt::locking(fd, mode, nbytes).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] fn heapmin(vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { suppress_iph!(_heapmin()) }; - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } - } - - unsafe extern "C" { - fn _setmode(fd: crt_fd::Borrowed<'_>, flags: i32) -> i32; + host_msvcrt::heapmin().map_err(|e| e.into_pyexception(vm)) } #[pyfunction] fn setmode(fd: crt_fd::Borrowed<'_>, flags: i32, vm: &VirtualMachine) -> PyResult { - let flags = unsafe { suppress_iph!(_setmode(fd, flags)) }; - if flags == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(flags) - } + host_msvcrt::setmode(fd, flags).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] fn open_osfhandle(handle: isize, flags: i32, vm: &VirtualMachine) -> PyResult { - let ret = unsafe { suppress_iph!(libc::open_osfhandle(handle, flags)) }; - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(ret) - } + host_msvcrt::open_osfhandle(handle, flags).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] @@ -178,12 +125,12 @@ mod msvcrt { #[allow(non_snake_case)] #[pyfunction] fn GetErrorMode() -> u32 { - unsafe { suppress_iph!(Debug::GetErrorMode()) } + host_msvcrt::get_error_mode() } #[allow(non_snake_case)] #[pyfunction] fn SetErrorMode(mode: Debug::THREAD_ERROR_MODE, _: &VirtualMachine) -> u32 { - unsafe { suppress_iph!(Debug::SetErrorMode(mode)) } + host_msvcrt::set_error_mode(mode) } } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 97f5b310238..992ca580ee4 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -20,6 +20,7 @@ pub(crate) mod module { use core::mem::MaybeUninit; use libc::intptr_t; use rustpython_common::wtf8::Wtf8Buf; + use rustpython_host_env::nt as host_nt; use std::os::windows::io::AsRawHandle; use std::{env, io, os::windows::ffi::OsStringExt}; use windows_sys::Win32::{ @@ -274,76 +275,16 @@ pub(crate) mod module { const S_IWRITE: u32 = 128; fn win32_hchmod(handle: Foundation::HANDLE, mode: u32, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Storage::FileSystem::{ - FILE_BASIC_INFO, FileBasicInfo, GetFileInformationByHandleEx, - SetFileInformationByHandle, - }; - - // Get current file info - let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - handle, - FileBasicInfo, - &mut info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - // Modify readonly attribute based on S_IWRITE bit - if mode & S_IWRITE != 0 { - info.FileAttributes &= !FileSystem::FILE_ATTRIBUTE_READONLY; - } else { - info.FileAttributes |= FileSystem::FILE_ATTRIBUTE_READONLY; - } - - // Set the new attributes - let ret = unsafe { - SetFileInformationByHandle( - handle, - FileBasicInfo, - &info as *const _ as *const _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - Ok(()) + host_nt::win32_hchmod(handle, mode, S_IWRITE).map_err(|e| e.to_pyexception(vm)) } fn fchmod_impl(fd: i32, mode: u32, vm: &VirtualMachine) -> PyResult<()> { - // Get Windows HANDLE from fd - let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; - let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?; - let hfile = handle.as_raw_handle() as Foundation::HANDLE; - win32_hchmod(hfile, mode, vm) + host_nt::fchmod(fd, mode, S_IWRITE).map_err(|e| e.to_pyexception(vm)) } fn win32_lchmod(path: &OsPath, mode: u32, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Storage::FileSystem::{GetFileAttributesW, SetFileAttributesW}; - - let wide = path.to_wide_cstring(vm)?; - let attr = unsafe { GetFileAttributesW(wide.as_ptr()) }; - if attr == FileSystem::INVALID_FILE_ATTRIBUTES { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)); - } - let new_attr = if mode & S_IWRITE != 0 { - attr & !FileSystem::FILE_ATTRIBUTE_READONLY - } else { - attr | FileSystem::FILE_ATTRIBUTE_READONLY - }; - let ret = unsafe { SetFileAttributesW(wide.as_ptr(), new_attr) }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)); - } - Ok(()) + host_nt::win32_lchmod(path.path.as_os_str(), mode, S_IWRITE) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm)) } #[pyfunction] diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index b7a54459796..8a1e6923a53 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1,19 +1,8 @@ // spell-checker:disable -use std::os::fd::BorrowedFd; - pub(crate) use module::module_def; -pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> { - use nix::fcntl; - let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?); - let mut new_flags = flags; - new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable); - if flags != new_flags { - fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?; - } - Ok(()) -} +pub use rustpython_host_env::posix::set_inheritable; #[pymodule(name = "posix", with( super::os::_os, diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index 54b4f92cd68..b017fea4e9e 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -41,9 +41,12 @@ mod decl { naive::{NaiveDate, NaiveDateTime, NaiveTime}, }; use core::time::Duration; + #[cfg(any(unix, windows))] + use rustpython_host_env::time::asctime_from_tm; + use rustpython_host_env::time::{self as host_time}; #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] - use windows_sys::Win32::System::Time::{GetTimeZoneInformation, TIME_ZONE_INFORMATION}; + use windows_sys::Win32::System::Time::TIME_ZONE_INFORMATION; #[cfg(windows)] unsafe extern "C" { @@ -56,27 +59,24 @@ mod decl { } #[allow(dead_code)] - pub(super) const SEC_TO_MS: i64 = 1000; + pub(super) const SEC_TO_MS: i64 = host_time::SEC_TO_MS; #[allow(dead_code)] - pub(super) const MS_TO_US: i64 = 1000; + pub(super) const MS_TO_US: i64 = host_time::MS_TO_US; #[allow(dead_code)] - pub(super) const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US; + pub(super) const SEC_TO_US: i64 = host_time::SEC_TO_US; #[allow(dead_code)] - pub(super) const US_TO_NS: i64 = 1000; + pub(super) const US_TO_NS: i64 = host_time::US_TO_NS; #[allow(dead_code)] - pub(super) const MS_TO_NS: i64 = MS_TO_US * US_TO_NS; + pub(super) const MS_TO_NS: i64 = host_time::MS_TO_NS; #[allow(dead_code)] - pub(super) const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS; + pub(super) const SEC_TO_NS: i64 = host_time::SEC_TO_NS; #[allow(dead_code)] - pub(super) const NS_TO_MS: i64 = 1000 * 1000; + pub(super) const NS_TO_MS: i64 = host_time::NS_TO_MS; #[allow(dead_code)] - pub(super) const NS_TO_US: i64 = 1000; + pub(super) const NS_TO_US: i64 = host_time::NS_TO_US; fn duration_since_system_now(vm: &VirtualMachine) -> PyResult { - use std::time::{SystemTime, UNIX_EPOCH}; - - SystemTime::now() - .duration_since(UNIX_EPOCH) + host_time::duration_since_system_now() .map_err(|e| vm.new_value_error(format!("Time error: {e:?}"))) } @@ -218,9 +218,7 @@ mod decl { #[cfg(target_env = "msvc")] #[cfg(not(target_arch = "wasm32"))] pub(super) fn get_tz_info() -> TIME_ZONE_INFORMATION { - let mut info: TIME_ZONE_INFORMATION = unsafe { core::mem::zeroed() }; - unsafe { GetTimeZoneInformation(&mut info) }; - info + host_time::get_tz_info() } // #[pyfunction] @@ -486,24 +484,6 @@ mod decl { } } - #[cfg(any(unix, windows))] - fn asctime_from_tm(tm: &libc::tm) -> String { - const WDAY_NAME: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const MON_NAME: [&str; 12] = [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - ]; - format!( - "{} {}{:>3} {:02}:{:02}:{:02} {}", - WDAY_NAME[tm.tm_wday as usize], - MON_NAME[tm.tm_mon as usize], - tm.tm_mday, - tm.tm_hour, - tm.tm_min, - tm.tm_sec, - tm.tm_year + 1900 - ) - } - #[cfg(not(any(unix, windows)))] impl OptionalArg { fn naive_or_local(self, vm: &VirtualMachine) -> PyResult { From ea07e67b1fb5de4e05784232b8b2c4ed8d29596a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:01:58 +0000 Subject: [PATCH 03/11] lint: enforce direct host API boundaries Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/97225fb7-7b3d-4197-a77c-eb44aead5b13 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/common/clippy.toml | 32 ++++++++++++++++++++++++++++ crates/common/src/lib.rs | 1 + crates/common/src/refcount.rs | 4 ++++ crates/host_env/clippy.toml | 1 + crates/stdlib/build.rs | 5 +++++ crates/stdlib/clippy.toml | 32 ++++++++++++++++++++++++++++ crates/stdlib/src/faulthandler.rs | 5 +++++ crates/stdlib/src/lib.rs | 2 +- crates/stdlib/src/openssl.rs | 5 +++++ crates/stdlib/src/openssl/cert.rs | 5 +++++ crates/stdlib/src/posixsubprocess.rs | 5 +++++ crates/stdlib/src/ssl.rs | 4 ++++ crates/stdlib/src/ssl/cert.rs | 5 +++++ crates/stdlib/src/tkinter.rs | 5 +++++ crates/vm/build.rs | 5 +++++ crates/vm/clippy.toml | 32 ++++++++++++++++++++++++++++ crates/vm/src/builtins/code.rs | 5 +++++ crates/vm/src/exceptions.rs | 5 +++++ crates/vm/src/getpath.rs | 5 +++++ crates/vm/src/import.rs | 5 +++++ crates/vm/src/lib.rs | 1 + crates/vm/src/readline.rs | 5 +++++ crates/vm/src/stdlib/os.rs | 5 +++++ crates/vm/src/stdlib/posix.rs | 5 +++++ crates/vm/src/stdlib/sys.rs | 5 +++++ crates/vm/src/vm/mod.rs | 5 +++++ crates/vm/src/vm/python_run.rs | 5 +++++ 27 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 crates/common/clippy.toml create mode 100644 crates/host_env/clippy.toml create mode 100644 crates/stdlib/clippy.toml create mode 100644 crates/vm/clippy.toml diff --git a/crates/common/clippy.toml b/crates/common/clippy.toml new file mode 100644 index 00000000000..a83f3d6ddda --- /dev/null +++ b/crates/common/clippy.toml @@ -0,0 +1,32 @@ +disallowed-methods = [ + { path = "std::fs::read", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::write", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::read_to_string", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::read_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::create_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::create_dir_all", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::remove_file", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::remove_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::metadata", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::symlink_metadata", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::canonicalize", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::File::open", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::File::create", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::OpenOptions::open", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::env::var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::var_os", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::set_var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::remove_var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::vars", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::vars_os", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::current_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::set_current_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::temp_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::process::Command::new", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::exit", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::abort", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::id", reason = "use rustpython_host_env for host process access" }, + { path = "std::net::TcpStream::connect", reason = "use rustpython_host_env for host network access" }, + { path = "std::net::TcpListener::bind", reason = "use rustpython_host_env for host network access" }, + { path = "std::net::UdpSocket::bind", reason = "use rustpython_host_env for host network access" }, +] diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index f3dca3689ff..53a8e0d752b 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,6 +1,7 @@ //! A crate to hold types and functions common to all rustpython components. #![cfg_attr(not(feature = "std"), no_std)] +#![deny(clippy::disallowed_methods)] extern crate alloc; diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index 4d871a67a0d..4bd0d7631d2 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -13,6 +13,10 @@ const STRONG: usize = (1 << STRONG_WIDTH) - 1; const COUNT: usize = 1; const WEAK_COUNT: usize = 1 << STRONG_WIDTH; +#[allow( + clippy::disallowed_methods, + reason = "refcount overflow must abort immediately under std" +)] #[inline(never)] #[cold] fn refcount_overflow() -> ! { diff --git a/crates/host_env/clippy.toml b/crates/host_env/clippy.toml new file mode 100644 index 00000000000..5539ef094fe --- /dev/null +++ b/crates/host_env/clippy.toml @@ -0,0 +1 @@ +disallowed-methods = [] diff --git a/crates/stdlib/build.rs b/crates/stdlib/build.rs index 95c34c4fb3c..644b18228b5 100644 --- a/crates/stdlib/build.rs +++ b/crates/stdlib/build.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "build scripts cannot use rustpython-host-env" +)] + // spell-checker:ignore ossl osslconf fn main() { diff --git a/crates/stdlib/clippy.toml b/crates/stdlib/clippy.toml new file mode 100644 index 00000000000..a83f3d6ddda --- /dev/null +++ b/crates/stdlib/clippy.toml @@ -0,0 +1,32 @@ +disallowed-methods = [ + { path = "std::fs::read", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::write", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::read_to_string", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::read_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::create_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::create_dir_all", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::remove_file", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::remove_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::metadata", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::symlink_metadata", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::canonicalize", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::File::open", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::File::create", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::OpenOptions::open", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::env::var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::var_os", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::set_var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::remove_var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::vars", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::vars_os", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::current_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::set_current_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::temp_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::process::Command::new", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::exit", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::abort", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::id", reason = "use rustpython_host_env for host process access" }, + { path = "std::net::TcpStream::connect", reason = "use rustpython_host_env for host network access" }, + { path = "std::net::TcpListener::bind", reason = "use rustpython_host_env for host network access" }, + { path = "std::net::UdpSocket::bind", reason = "use rustpython_host_env for host network access" }, +] diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index 96e024e4c62..d40955d3095 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "faulthandler fallback exits still use direct host APIs until later extraction" +)] + pub(crate) use decl::module_def; #[allow(static_mut_refs)] // TODO: group code only with static mut refs diff --git a/crates/stdlib/src/lib.rs b/crates/stdlib/src/lib.rs index 941de413bfc..6d91500227f 100644 --- a/crates/stdlib/src/lib.rs +++ b/crates/stdlib/src/lib.rs @@ -1,6 +1,6 @@ // to allow `mod foo {}` in foo.rs; clippy thinks this is a mistake/misunderstanding of // how `mod` works, but we want this sometimes for pymodule declarations - +#![deny(clippy::disallowed_methods)] #![allow(clippy::module_inception)] #[macro_use] diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index 7cef74470b1..30db6ab018c 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "remaining openssl file access has not been extracted into rustpython-host-env yet" +)] + // spell-checker:disable mod cert; diff --git a/crates/stdlib/src/openssl/cert.rs b/crates/stdlib/src/openssl/cert.rs index b63d824a837..5f08f0cf140 100644 --- a/crates/stdlib/src/openssl/cert.rs +++ b/crates/stdlib/src/openssl/cert.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "remaining openssl certificate file access has not been extracted into rustpython-host-env yet" +)] + pub(super) use ssl_cert::{PySSLCertificate, cert_to_certificate, cert_to_py, obj2txt}; // Certificate type for SSL module diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index fec2ceb16d5..b6131d4f401 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "posixsubprocess exit path still uses direct host APIs until later extraction" +)] + // spell-checker:disable use crate::vm::{ diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 06c0010a79d..99db75837d5 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1,3 +1,7 @@ +#![allow( + clippy::disallowed_methods, + reason = "remaining ssl file access has not been extracted into rustpython-host-env yet" +)] // spell-checker: ignore ssleof aesccm aesgcm capath getblocking setblocking ENDTLS TLSEXT //! Pure Rust SSL/TLS implementation using rustls diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index cd39972cf41..7ed2729b78d 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -1,5 +1,10 @@ // cspell: ignore accessdescs +#![allow( + clippy::disallowed_methods, + reason = "remaining certificate file access has not been extracted into rustpython-host-env yet" +)] + //! Certificate parsing, validation, and conversion utilities for SSL/TLS //! //! This module provides reusable functions for working with X.509 certificates: diff --git a/crates/stdlib/src/tkinter.rs b/crates/stdlib/src/tkinter.rs index b258002c129..b19468a7faa 100644 --- a/crates/stdlib/src/tkinter.rs +++ b/crates/stdlib/src/tkinter.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "tkinter environment setup still uses direct host APIs until later extraction" +)] + // spell-checker:ignore createcommand pub(crate) use self::_tkinter::module_def; diff --git a/crates/vm/build.rs b/crates/vm/build.rs index f76bf3f5cbd..e8fef7285fd 100644 --- a/crates/vm/build.rs +++ b/crates/vm/build.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "build scripts cannot use rustpython-host-env" +)] + use itertools::Itertools; use std::{env, io::prelude::*, path::PathBuf, process::Command}; diff --git a/crates/vm/clippy.toml b/crates/vm/clippy.toml new file mode 100644 index 00000000000..a83f3d6ddda --- /dev/null +++ b/crates/vm/clippy.toml @@ -0,0 +1,32 @@ +disallowed-methods = [ + { path = "std::fs::read", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::write", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::read_to_string", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::read_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::create_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::create_dir_all", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::remove_file", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::remove_dir", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::metadata", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::symlink_metadata", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::canonicalize", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::File::open", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::File::create", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::fs::OpenOptions::open", reason = "use rustpython_host_env for host filesystem access" }, + { path = "std::env::var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::var_os", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::set_var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::remove_var", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::vars", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::vars_os", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::current_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::set_current_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::env::temp_dir", reason = "use rustpython_host_env for host environment access" }, + { path = "std::process::Command::new", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::exit", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::abort", reason = "use rustpython_host_env for host process access" }, + { path = "std::process::id", reason = "use rustpython_host_env for host process access" }, + { path = "std::net::TcpStream::connect", reason = "use rustpython_host_env for host network access" }, + { path = "std::net::TcpListener::bind", reason = "use rustpython_host_env for host network access" }, + { path = "std::net::UdpSocket::bind", reason = "use rustpython_host_env for host network access" }, +] diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index aafbe196a07..b50478b4e10 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "pyc loading still uses direct host APIs until later extraction" +)] + //! Infamous code object. The python class `code` use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, set::PyFrozenSet}; diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 6a90a160ff5..84a45750519 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "traceback source loading still uses direct host APIs until later extraction" +)] + use self::types::{PyBaseException, PyBaseExceptionRef}; use crate::common::lock::PyRwLock; use crate::object::{Traverse, TraverseFn}; diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index 31fa0617b45..e52942a00cc 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "path bootstrap still uses direct host APIs until later extraction" +)] + //! Path configuration for RustPython (ref: Modules/getpath.py) //! //! This module implements Python path calculation logic following getpath.py. diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index f8f41c12081..fe4783e418c 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "import path shadow checks still use direct host APIs until later extraction" +)] + //! Import mechanics use crate::{ diff --git a/crates/vm/src/lib.rs b/crates/vm/src/lib.rs index 78669d2fce2..5e2e88a074a 100644 --- a/crates/vm/src/lib.rs +++ b/crates/vm/src/lib.rs @@ -8,6 +8,7 @@ // to allow `mod foo {}` in foo.rs; clippy thinks this is a mistake/misunderstanding of // how `mod` works, but we want this sometimes for pymodule declarations +#![deny(clippy::disallowed_methods)] #![allow(clippy::module_inception)] // we want to mirror python naming conventions when defining python structs, so that does mean // uppercase acronyms, e.g. TextIOWrapper instead of TextIoWrapper diff --git a/crates/vm/src/readline.rs b/crates/vm/src/readline.rs index d62d520ecbd..9ffb8c0d85f 100644 --- a/crates/vm/src/readline.rs +++ b/crates/vm/src/readline.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "readline history file setup still uses direct host APIs until later extraction" +)] + //! Readline interface for REPLs //! //! This module provides a common interface for reading lines from the console, with support for history and completion. diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index e31017f0682..759063839f1 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "remaining os host calls have not been extracted into rustpython-host-env yet" +)] + // spell-checker:disable use crate::{ diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 8a1e6923a53..1118311791e 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -15,6 +15,11 @@ pub use rustpython_host_env::posix::set_inheritable; posix_sched ))] pub mod module { + #![allow( + clippy::disallowed_methods, + reason = "remaining posix host calls have not been extracted into rustpython-host-env yet" + )] + use crate::{ AsObject, Py, PyObjectRef, PyResult, VirtualMachine, builtins::{PyDictRef, PyInt, PyListRef, PyTupleRef, PyUtf8Str}, diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 64d16a2c9c7..7546d890600 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "sys breakpoint configuration still uses direct host APIs until later extraction" +)] + pub(crate) mod monitoring; use crate::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject}; diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 882ffe5f54e..0460138a6fa 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "vm initialization still uses direct host APIs until later extraction" +)] + //! Implement virtual machine to run instructions. //! //! See also: diff --git a/crates/vm/src/vm/python_run.rs b/crates/vm/src/vm/python_run.rs index 2f6f0bbee01..53b48a751d1 100644 --- a/crates/vm/src/vm/python_run.rs +++ b/crates/vm/src/vm/python_run.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::disallowed_methods, + reason = "script loading still uses direct host APIs until later extraction" +)] + //! Python code execution functions. use crate::{ From b2873e10349200c958e77489d3993b958d370e37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 01:46:12 +0000 Subject: [PATCH 04/11] refactor: extract remaining host env helpers Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/d96f57e1-b196-4460-9983-97d5ff118835 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/common/src/refcount.rs | 8 +-- crates/host_env/src/fileutils.rs | 46 ++++++++++++++ crates/host_env/src/os.rs | 48 ++++++++++++++- crates/host_env/src/posix.rs | 75 +++++++++++++++++++++++ crates/stdlib/src/faulthandler.rs | 9 +-- crates/stdlib/src/openssl.rs | 7 +-- crates/stdlib/src/openssl/cert.rs | 7 +-- crates/stdlib/src/posixsubprocess.rs | 7 +-- crates/stdlib/src/ssl.rs | 12 ++-- crates/stdlib/src/ssl/cert.rs | 15 ++--- crates/vm/src/builtins/code.rs | 16 ++--- crates/vm/src/exceptions.rs | 24 +++++--- crates/vm/src/getpath.rs | 14 ++--- crates/vm/src/import.rs | 7 +-- crates/vm/src/readline.rs | 35 +++++++---- crates/vm/src/stdlib/os.rs | 42 ++++++------- crates/vm/src/stdlib/posix.rs | 92 +++------------------------- crates/vm/src/stdlib/sys.rs | 14 ++--- crates/vm/src/vm/mod.rs | 11 +--- crates/vm/src/vm/python_run.rs | 9 +-- 20 files changed, 277 insertions(+), 221 deletions(-) diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index 4bd0d7631d2..0090b3c3fd4 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -13,15 +13,13 @@ const STRONG: usize = (1 << STRONG_WIDTH) - 1; const COUNT: usize = 1; const WEAK_COUNT: usize = 1 << STRONG_WIDTH; -#[allow( - clippy::disallowed_methods, - reason = "refcount overflow must abort immediately under std" -)] #[inline(never)] #[cold] fn refcount_overflow() -> ! { #[cfg(feature = "std")] - std::process::abort(); + unsafe { + libc::abort() + }; #[cfg(not(feature = "std"))] core::panic!("refcount overflow"); } diff --git a/crates/host_env/src/fileutils.rs b/crates/host_env/src/fileutils.rs index a20140a6e04..285e535c1bd 100644 --- a/crates/host_env/src/fileutils.rs +++ b/crates/host_env/src/fileutils.rs @@ -1,6 +1,12 @@ // Python/fileutils.c in CPython #![allow(non_snake_case)] +use std::{ + fs::{self, File, Metadata, ReadDir}, + io, + path::Path, +}; + #[cfg(not(windows))] pub use libc::stat as StatStruct; @@ -20,6 +26,46 @@ pub fn fstat(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result { } } +pub fn open(path: impl AsRef) -> io::Result { + File::open(path) +} + +pub fn read(path: impl AsRef) -> io::Result> { + fs::read(path) +} + +pub fn read_to_string(path: impl AsRef) -> io::Result { + fs::read_to_string(path) +} + +pub fn read_dir(path: impl AsRef) -> io::Result { + fs::read_dir(path) +} + +pub fn create_dir_all(path: impl AsRef) -> io::Result<()> { + fs::create_dir_all(path) +} + +pub fn remove_dir(path: impl AsRef) -> io::Result<()> { + fs::remove_dir(path) +} + +pub fn remove_file(path: impl AsRef) -> io::Result<()> { + fs::remove_file(path) +} + +pub fn metadata(path: impl AsRef) -> io::Result { + fs::metadata(path) +} + +pub fn symlink_metadata(path: impl AsRef) -> io::Result { + fs::symlink_metadata(path) +} + +pub fn open_write(path: impl AsRef) -> io::Result { + fs::OpenOptions::new().write(true).open(path) +} + #[cfg(windows)] pub mod windows { use crate::crt_fd; diff --git a/crates/host_env/src/os.rs b/crates/host_env/src/os.rs index 2a318f477e5..a2e804faaf0 100644 --- a/crates/host_env/src/os.rs +++ b/crates/host_env/src/os.rs @@ -2,7 +2,13 @@ // TODO: we can move more os-specific bindings/interfaces from stdlib::{os, posix, nt} to here use core::str::Utf8Error; -use std::{io, process::ExitCode}; +use std::{ + env, + ffi::{OsStr, OsString}, + io, + path::PathBuf, + process::ExitCode, +}; /// Convert exit code to std::process::ExitCode /// @@ -22,6 +28,46 @@ pub fn exit_code(code: u32) -> ExitCode { ExitCode::from(code as u8) } +pub fn current_dir() -> io::Result { + env::current_dir() +} + +pub fn temp_dir() -> PathBuf { + env::temp_dir() +} + +pub fn var(key: &str) -> Result { + env::var(key) +} + +pub fn var_os(key: impl AsRef) -> Option { + env::var_os(key) +} + +pub fn vars_os() -> env::VarsOs { + env::vars_os() +} + +pub fn set_var(key: impl AsRef, value: impl AsRef) { + unsafe { env::set_var(key, value) }; +} + +pub fn remove_var(key: impl AsRef) { + unsafe { env::remove_var(key) }; +} + +pub fn set_current_dir(path: impl AsRef) -> io::Result<()> { + env::set_current_dir(path) +} + +pub fn process_id() -> u32 { + std::process::id() +} + +pub fn exit(code: i32) -> ! { + std::process::exit(code) +} + pub trait ErrorExt { fn posix_errno(&self) -> i32; } diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index e624db10a15..98a8af4a1b3 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -11,3 +11,78 @@ pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> } Ok(()) } + +#[cfg(target_os = "macos")] +pub fn get_number_of_os_threads() -> isize { + type MachPortT = libc::c_uint; + type KernReturnT = libc::c_int; + type MachMsgTypeNumberT = libc::c_uint; + type ThreadActArrayT = *mut MachPortT; + const KERN_SUCCESS: KernReturnT = 0; + unsafe extern "C" { + fn mach_task_self() -> MachPortT; + fn task_for_pid( + task: MachPortT, + pid: libc::c_int, + target_task: *mut MachPortT, + ) -> KernReturnT; + fn task_threads( + target_task: MachPortT, + act_list: *mut ThreadActArrayT, + act_list_cnt: *mut MachMsgTypeNumberT, + ) -> KernReturnT; + fn vm_deallocate( + target_task: MachPortT, + address: libc::uintptr_t, + size: libc::uintptr_t, + ) -> KernReturnT; + } + + let self_task = unsafe { mach_task_self() }; + let mut proc_task: MachPortT = 0; + if unsafe { task_for_pid(self_task, libc::getpid(), &mut proc_task) } == KERN_SUCCESS { + let mut threads: ThreadActArrayT = core::ptr::null_mut(); + let mut n_threads: MachMsgTypeNumberT = 0; + if unsafe { task_threads(proc_task, &mut threads, &mut n_threads) } == KERN_SUCCESS { + if !threads.is_null() { + let _ = unsafe { + vm_deallocate( + self_task, + threads as libc::uintptr_t, + (n_threads as usize * core::mem::size_of::()) as libc::uintptr_t, + ) + }; + } + return n_threads as isize; + } + } + 0 +} + +#[cfg(target_os = "linux")] +pub fn get_number_of_os_threads() -> isize { + use std::io::Read as _; + + let mut file = match crate::fileutils::open("/proc/self/stat") { + Ok(f) => f, + Err(_) => return 0, + }; + let mut buf = [0u8; 160]; + let n = match file.read(&mut buf) { + Ok(n) => n, + Err(_) => return 0, + }; + let line = match core::str::from_utf8(&buf[..n]) { + Ok(s) => s, + Err(_) => return 0, + }; + if let Some(field) = line.split_whitespace().nth(19) { + return field.parse::().unwrap_or(0); + } + 0 +} + +#[cfg(not(any(target_os = "macos", target_os = "linux")))] +pub fn get_number_of_os_threads() -> isize { + 0 +} diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index d40955d3095..e562a382345 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "faulthandler fallback exits still use direct host APIs until later extraction" -)] - pub(crate) use decl::module_def; #[allow(static_mut_refs)] // TODO: group code only with static mut refs @@ -592,7 +587,7 @@ mod decl { } // Fallback - std::process::exit(1); + rustpython_host_env::os::exit(1); } // Windows vectored exception handler (faulthandler.c:417-480) @@ -885,7 +880,7 @@ mod decl { } if exit { - std::process::exit(1); + rustpython_host_env::os::exit(1); } } diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index 30db6ab018c..c49ca2f855b 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "remaining openssl file access has not been extracted into rustpython-host-env yet" -)] - // spell-checker:disable mod cert; @@ -1885,7 +1880,7 @@ mod _ssl { const PEM_BUFSIZE: usize = 1024; // Read key file data - let key_data = std::fs::read(key_file_path) + let key_data = rustpython_host_env::fileutils::read(key_file_path) .map_err(|e| crate::vm::convert::ToPyException::to_pyexception(&e, vm))?; let pkey = if let Some(ref pw_obj) = password { diff --git a/crates/stdlib/src/openssl/cert.rs b/crates/stdlib/src/openssl/cert.rs index 5f08f0cf140..4d5460f6fdb 100644 --- a/crates/stdlib/src/openssl/cert.rs +++ b/crates/stdlib/src/openssl/cert.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "remaining openssl certificate file access has not been extracted into rustpython-host-env yet" -)] - pub(super) use ssl_cert::{PySSLCertificate, cert_to_certificate, cert_to_py, obj2txt}; // Certificate type for SSL module @@ -364,7 +359,7 @@ pub(crate) mod ssl_cert { #[pyfunction] pub(crate) fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { let path = path.to_path_buf(vm)?; - let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; + let pem = rustpython_host_env::fileutils::read(path).map_err(|e| e.to_pyexception(vm))?; let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; cert_to_py(vm, &x509, false) } diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index b6131d4f401..01b0d466ffc 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "posixsubprocess exit path still uses direct host APIs until later extraction" -)] - // spell-checker:disable use crate::vm::{ @@ -253,7 +248,7 @@ fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>, vm: &VirtualMachine) -> // errno is written in hex format let _ = write!(pipe, "OSError:{:x}:{}", e as i32, ctx.as_msg()); } - std::process::exit(255) + rustpython_host_env::os::exit(255) } } } diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index 99db75837d5..e8688a94fe9 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1,7 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "remaining ssl file access has not been extracted into rustpython-host-env yet" -)] // spell-checker: ignore ssleof aesccm aesgcm capath getblocking setblocking ENDTLS TLSEXT //! Pure Rust SSL/TLS implementation using rustls @@ -1770,8 +1766,8 @@ mod _ssl { // Validate that the file contains DH parameters // Read the file and check for DH PARAMETERS header - let contents = - std::fs::read_to_string(&path_str).map_err(|e| vm.new_os_error(e.to_string()))?; + let contents = rustpython_host_env::fileutils::read_to_string(&path_str) + .map_err(|e| vm.new_os_error(e.to_string()))?; if !contents.contains("BEGIN DH PARAMETERS") && !contents.contains("BEGIN X9.42 DH PARAMETERS") @@ -2158,7 +2154,7 @@ mod _ssl { path: &str, vm: &VirtualMachine, ) -> PyResult>> { - let data = std::fs::read(path).map_err(|e| match e.kind() { + let data = rustpython_host_env::fileutils::read(path).map_err(|e| match e.kind() { std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => { e.into_pyexception(vm) } @@ -4917,7 +4913,7 @@ mod _ssl { fn _test_decode_cert(path: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { // Read certificate file let path_str = path.as_str(); - let cert_data = std::fs::read(path_str).map_err(|e| { + let cert_data = rustpython_host_env::fileutils::read(path_str).map_err(|e| { vm.new_os_error(format!( "Failed to read certificate file {}: {}", path_str, e diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index 7ed2729b78d..9d2d3987e14 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -1,10 +1,5 @@ // cspell: ignore accessdescs -#![allow( - clippy::disallowed_methods, - reason = "remaining certificate file access has not been extracted into rustpython-host-env yet" -)] - //! Certificate parsing, validation, and conversion utilities for SSL/TLS //! //! This module provides reusable functions for working with X.509 certificates: @@ -644,7 +639,7 @@ impl<'a> CertLoader<'a> { /// /// Returns statistics about loaded certificates pub fn load_from_file(&mut self, path: &str) -> Result { - let contents = std::fs::read(path)?; + let contents = rustpython_host_env::fileutils::read(path)?; self.load_from_bytes(&contents) } @@ -653,7 +648,7 @@ impl<'a> CertLoader<'a> { /// Reads all files in the directory and attempts to parse them as certificates. /// Invalid files are silently skipped (matches OpenSSL capath behavior). pub fn load_from_dir(&mut self, dir_path: &str) -> Result { - let entries = std::fs::read_dir(dir_path)?; + let entries = rustpython_host_env::fileutils::read_dir(dir_path)?; let mut stats = CertStats::default(); for entry in entries { @@ -663,7 +658,7 @@ impl<'a> CertLoader<'a> { // Skip directories and process all files // OpenSSL capath uses hash-based naming like "4e1295a3.0" if path.is_file() - && let Ok(contents) = std::fs::read(&path) + && let Ok(contents) = rustpython_host_env::fileutils::read(&path) { // Ignore errors for individual files (some may not be certs) if let Ok(file_stats) = self.load_from_bytes(&contents) { @@ -1134,7 +1129,7 @@ pub(super) fn load_cert_chain_from_file( password: Option<&str>, ) -> Result<(Vec>, PrivateKeyDer<'static>), Box> { // Load certificate file - preserve io::Error for errno - let cert_contents = std::fs::read(cert_path)?; + let cert_contents = rustpython_host_env::fileutils::read(cert_path)?; // Parse certificates (PEM format) let mut cert_cursor = std::io::Cursor::new(&cert_contents); @@ -1147,7 +1142,7 @@ pub(super) fn load_cert_chain_from_file( } // Load private key file - preserve io::Error for errno - let key_contents = std::fs::read(key_path)?; + let key_contents = rustpython_host_env::fileutils::read(key_path)?; // Parse private key (supports PKCS8, RSA, EC formats) let private_key = if let Some(pwd) = password { diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index b50478b4e10..5077e1d426e 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -1,18 +1,15 @@ -#![allow( - clippy::disallowed_methods, - reason = "pyc loading still uses direct host APIs until later extraction" -)] - //! Infamous code object. The python class `code` use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, set::PyFrozenSet}; use crate::common::lock::PyMutex; +#[cfg(feature = "host_env")] +use crate::convert::ToPyException; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, builtins::PyStrInterned, bytecode::{self, AsBag, BorrowedConstant, CodeFlags, Constant, ConstantBag, Instruction}, class::{PyClassImpl, StaticType}, - convert::{ToPyException, ToPyObject}, + convert::ToPyObject, frozen, function::OptionalArg, types::{Comparable, Constructor, Hashable, Representable}, @@ -522,12 +519,13 @@ impl PyCode { Self::new_ref_with_bag(vm, code.decode(PyVmBag(vm))) } + #[cfg(feature = "host_env")] pub fn from_pyc_path(path: &std::path::Path, vm: &VirtualMachine) -> PyResult> { let name = match path.file_stem() { Some(stem) => stem.display().to_string(), None => "".to_owned(), }; - let content = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; + let content = crate::host_env::fileutils::read(path).map_err(|e| e.to_pyexception(vm))?; Self::from_pyc( &content, Some(&name), @@ -536,6 +534,10 @@ impl PyCode { vm, ) } + #[cfg(not(feature = "host_env"))] + pub fn from_pyc_path(_path: &std::path::Path, vm: &VirtualMachine) -> PyResult> { + Err(vm.new_runtime_error("loading a pyc file requires the `host_env` feature".to_owned())) + } pub fn from_pyc( pyc_bytes: &[u8], name: Option<&str>, diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 84a45750519..c8d719529cc 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "traceback source loading still uses direct host APIs until later extraction" -)] - use self::types::{PyBaseException, PyBaseExceptionRef}; use crate::common::lock::PyRwLock; use crate::object::{Traverse, TraverseFn}; @@ -22,10 +17,9 @@ use crate::{ }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; -use std::{ - collections::HashSet, - io::{self, BufRead, BufReader}, -}; +#[cfg(feature = "host_env")] +use std::io::{BufRead, BufReader}; +use std::{collections::HashSet, io}; pub use super::exception_group::exception_group; @@ -371,6 +365,7 @@ impl VirtualMachine { } } +#[cfg(feature = "host_env")] fn print_source_line( output: &mut W, filename: &str, @@ -378,7 +373,7 @@ fn print_source_line( ) -> Result<(), W::Error> { // TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/main/Python/traceback.c#L393 // TODO: support different encodings - let file = match std::fs::File::open(filename) { + let file = match crate::host_env::fileutils::open(filename) { Ok(file) => file, Err(_) => return Ok(()), }; @@ -397,6 +392,15 @@ fn print_source_line( Ok(()) } +#[cfg(not(feature = "host_env"))] +fn print_source_line( + _output: &mut W, + _filename: &str, + _lineno: usize, +) -> Result<(), W::Error> { + Ok(()) +} + /// Print exception occurrence location from traceback element fn write_traceback_entry( output: &mut W, diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index e52942a00cc..6cfea403a2a 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "path bootstrap still uses direct host APIs until later extraction" -)] - //! Path configuration for RustPython (ref: Modules/getpath.py) //! //! This module implements Python path calculation logic following getpath.py. @@ -125,7 +120,7 @@ pub fn init_path_config(settings: &Settings) -> Paths { // In this case: // - sys.executable should be the launcher path (where user invoked Python) // - sys._base_executable should be the real Python executable - let exe_dir = if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") { + let exe_dir = if let Ok(launcher) = crate::host_env::os::var("__PYVENV_LAUNCHER__") { paths.executable = launcher.clone(); paths.base_executable = real_executable; PathBuf::from(&launcher).parent().map(PathBuf::from) @@ -376,7 +371,7 @@ fn get_executable_path() -> Option { /// Parse pyvenv.cfg and extract the 'home' key value fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option { - let content = std::fs::read_to_string(pyvenv_cfg).ok()?; + let content = crate::host_env::fileutils::read_to_string(pyvenv_cfg).ok()?; for line in content.lines() { if let Some((key, value)) = line.split_once('=') @@ -404,7 +399,10 @@ mod tests { #[test] fn test_search_up() { // Test with a path that doesn't have any landmarks - let result = search_up_file(std::env::temp_dir(), &["nonexistent_landmark_xyz"]); + let result = search_up_file( + crate::host_env::os::temp_dir(), + &["nonexistent_landmark_xyz"], + ); assert!(result.is_none()); } diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index fe4783e418c..09033dd84f8 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "import path shadow checks still use direct host APIs until later extraction" -)] - //! Import mechanics use crate::{ @@ -345,7 +340,7 @@ pub(crate) fn is_possibly_shadowing_path(origin: &str, vm: &VirtualMachine) -> b }; let cmp_path = if sys_path_0.is_empty() { - match std::env::current_dir() { + match crate::host_env::os::current_dir() { Ok(d) => d.to_string_lossy().to_string(), Err(_) => return false, } diff --git a/crates/vm/src/readline.rs b/crates/vm/src/readline.rs index 9ffb8c0d85f..f82499f9cdf 100644 --- a/crates/vm/src/readline.rs +++ b/crates/vm/src/readline.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "readline history file setup still uses direct host APIs until later extraction" -)] - //! Readline interface for REPLs //! //! This module provides a common interface for reading lines from the console, with support for history and completion. @@ -110,18 +105,34 @@ mod rustyline_readline { } pub fn load_history(&mut self, path: &Path) -> OtherResult<()> { - self.repl.load_history(path)?; - Ok(()) + #[cfg(not(feature = "host_env"))] + { + let _ = path; + Err(io::Error::other("history requires the `host_env` feature").into()) + } + #[cfg(feature = "host_env")] + { + self.repl.load_history(path)?; + Ok(()) + } } pub fn save_history(&mut self, path: &Path) -> OtherResult<()> { - if !path.exists() - && let Some(parent) = path.parent() + #[cfg(not(feature = "host_env"))] { - std::fs::create_dir_all(parent)?; + let _ = path; + Err(io::Error::other("history requires the `host_env` feature").into()) + } + #[cfg(feature = "host_env")] + { + if !path.exists() + && let Some(parent) = path.parent() + { + crate::host_env::fileutils::create_dir_all(parent)?; + } + self.repl.save_history(path)?; + Ok(()) } - self.repl.save_history(path)?; - Ok(()) } pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> { diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 759063839f1..70e9d1b8da9 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "remaining os host calls have not been extracted into rustpython-host-env yet" -)] - // spell-checker:disable use crate::{ @@ -12,16 +7,16 @@ use crate::{ function::{ArgumentError, FromArgs, FuncArgs}, host_env::crt_fd, }; -use std::{fs, io, path::Path}; +use std::{io, path::Path}; pub(crate) fn fs_metadata>( path: P, follow_symlink: bool, -) -> io::Result { +) -> io::Result { if follow_symlink { - fs::metadata(path.as_ref()) + crate::host_env::fileutils::metadata(path.as_ref()) } else { - fs::symlink_metadata(path.as_ref()) + crate::host_env::fileutils::symlink_metadata(path.as_ref()) } } @@ -182,7 +177,7 @@ pub(super) mod _os { use crossbeam_utils::atomic::AtomicCell; use rustpython_common::wtf8::Wtf8Buf; use rustpython_host_env::suppress_iph; - use std::{env, fs, fs::OpenOptions, io, path::PathBuf, time::SystemTime}; + use std::{fs, io, path::PathBuf, time::SystemTime}; const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); pub(crate) const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); @@ -371,7 +366,8 @@ pub(super) mod _os { #[pyfunction] fn mkdirs(path: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { let os_path = vm.fsencode(&path)?; - fs::create_dir_all(&*os_path).map_err(|err| err.into_pyexception(vm)) + crate::host_env::fileutils::create_dir_all(&*os_path) + .map_err(|err| err.into_pyexception(vm)) } #[cfg(not(windows))] @@ -394,14 +390,16 @@ pub(super) mod _os { } #[cfg(target_os = "redox")] let [] = dir_fd.0; - fs::remove_dir(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + crate::host_env::fileutils::remove_dir(&path) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[cfg(windows)] #[pyfunction] fn rmdir(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { let [] = dir_fd.0; - fs::remove_dir(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + crate::host_env::fileutils::remove_dir(&path) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } const LISTDIR_FD: bool = cfg!(all(unix, not(target_os = "redox"))); @@ -416,7 +414,7 @@ pub(super) mod _os { .unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str("."))); let list = match path { OsPathOrFd::Path(path) => { - let dir_iter = match fs::read_dir(&path) { + let dir_iter = match crate::host_env::fileutils::read_dir(&path) { Ok(iter) => iter, Err(err) => { return Err(OSErrorBuilder::with_filename(&err, path, vm)); @@ -546,7 +544,7 @@ pub(super) mod _os { let key = super::bytes_as_os_str(key, vm)?; let value = super::bytes_as_os_str(value, vm)?; // SAFETY: requirements forwarded from the caller - unsafe { env::set_var(key, value) }; + crate::host_env::os::set_var(key, value); Ok(()) } @@ -598,7 +596,7 @@ pub(super) mod _os { } let key = super::bytes_as_os_str(key, vm)?; // SAFETY: requirements forwarded from the caller - unsafe { env::remove_var(key) }; + crate::host_env::os::remove_var(key); Ok(()) } @@ -1190,7 +1188,7 @@ pub(super) mod _os { .unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str("."))); match path { OsPathOrFd::Path(path) => { - let entries = fs::read_dir(&path.path) + let entries = crate::host_env::fileutils::read_dir(&path.path) .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; Ok(ScandirIterator { entries: PyRwLock::new(Some(entries)), @@ -1476,7 +1474,7 @@ pub(super) mod _os { } fn curdir_inner(vm: &VirtualMachine) -> PyResult { - env::current_dir().map_err(|err| err.into_pyexception(vm)) + crate::host_env::os::current_dir().map_err(|err| err.into_pyexception(vm)) } #[pyfunction] @@ -1491,7 +1489,7 @@ pub(super) mod _os { #[pyfunction] fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { - env::set_current_dir(&path.path) + crate::host_env::os::set_current_dir(&path.path) .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; #[cfg(windows)] @@ -1561,7 +1559,7 @@ pub(super) mod _os { // https://github.com/WebAssembly/wasi-libc/blob/wasi-sdk-21/libc-bottom-half/getpid/getpid.c 42 } else { - std::process::id() + crate::host_env::os::process_id() }; vm.ctx.new_int(pid).into() } @@ -1574,7 +1572,7 @@ pub(super) mod _os { #[pyfunction] fn _exit(code: i32) { - std::process::exit(code) + crate::host_env::os::exit(code) } #[pyfunction] @@ -2053,7 +2051,7 @@ pub(super) mod _os { let path = OsPath::try_from_object(vm, path)?; // TODO: just call libc::truncate() on POSIX - let f = match OpenOptions::new().write(true).open(&path) { + let f = match crate::host_env::fileutils::open_write(&path) { Ok(f) => f, Err(e) => return Err(error(vm, e, path)), }; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 1118311791e..462fa29c582 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -15,11 +15,6 @@ pub use rustpython_host_env::posix::set_inheritable; posix_sched ))] pub mod module { - #![allow( - clippy::disallowed_methods, - reason = "remaining posix host calls have not been extracted into rustpython-host-env yet" - )] - use crate::{ AsObject, Py, PyObjectRef, PyResult, VirtualMachine, builtins::{PyDictRef, PyInt, PyListRef, PyTupleRef, PyUtf8Str}, @@ -49,7 +44,7 @@ pub mod module { }; use rustpython_host_env::os::ffi::OsStringExt; use std::{ - env, fs, io, + fs, io, os::fd::{AsFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd}, }; use strum::IntoEnumIterator; @@ -477,7 +472,7 @@ pub mod module { ) })?; - let metadata = match fs::metadata(&path.path) { + let metadata = match crate::host_env::fileutils::metadata(&path.path) { Ok(m) => m, // If the file doesn't exist, return False for any access check Err(_) => return Ok(false), @@ -505,7 +500,7 @@ pub mod module { #[pyattr] fn environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in env::vars_os() { + for (key, value) in crate::host_env::os::vars_os() { let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into(); let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into(); environ.set_item(&*key, value, vm).unwrap(); @@ -517,7 +512,7 @@ pub mod module { #[pyfunction] fn _create_environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in env::vars_os() { + for (key, value) in crate::host_env::os::vars_os() { let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into(); let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into(); environ.set_item(&*key, value, vm).unwrap(); @@ -576,7 +571,8 @@ pub mod module { } #[cfg(target_os = "redox")] let [] = dir_fd.0; - fs::remove_file(&path).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + crate::host_env::fileutils::remove_file(&path) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[cfg(not(target_os = "redox"))] @@ -870,79 +866,7 @@ pub mod module { /// Best-effort number of OS threads in this process. /// Returns <= 0 when unavailable. fn get_number_of_os_threads() -> isize { - #[cfg(target_os = "macos")] - { - type MachPortT = libc::c_uint; - type KernReturnT = libc::c_int; - type MachMsgTypeNumberT = libc::c_uint; - type ThreadActArrayT = *mut MachPortT; - const KERN_SUCCESS: KernReturnT = 0; - unsafe extern "C" { - fn mach_task_self() -> MachPortT; - fn task_for_pid( - task: MachPortT, - pid: libc::c_int, - target_task: *mut MachPortT, - ) -> KernReturnT; - fn task_threads( - target_task: MachPortT, - act_list: *mut ThreadActArrayT, - act_list_cnt: *mut MachMsgTypeNumberT, - ) -> KernReturnT; - fn vm_deallocate( - target_task: MachPortT, - address: libc::uintptr_t, - size: libc::uintptr_t, - ) -> KernReturnT; - } - - let self_task = unsafe { mach_task_self() }; - let mut proc_task: MachPortT = 0; - if unsafe { task_for_pid(self_task, libc::getpid(), &mut proc_task) } == KERN_SUCCESS { - let mut threads: ThreadActArrayT = core::ptr::null_mut(); - let mut n_threads: MachMsgTypeNumberT = 0; - if unsafe { task_threads(proc_task, &mut threads, &mut n_threads) } == KERN_SUCCESS - { - if !threads.is_null() { - let _ = unsafe { - vm_deallocate( - self_task, - threads as libc::uintptr_t, - (n_threads as usize * core::mem::size_of::()) - as libc::uintptr_t, - ) - }; - } - return n_threads as isize; - } - } - 0 - } - #[cfg(target_os = "linux")] - { - use std::io::Read as _; - let mut file = match std::fs::File::open("/proc/self/stat") { - Ok(f) => f, - Err(_) => return 0, - }; - let mut buf = [0u8; 160]; - let n = match file.read(&mut buf) { - Ok(n) => n, - Err(_) => return 0, - }; - let line = match core::str::from_utf8(&buf[..n]) { - Ok(s) => s, - Err(_) => return 0, - }; - if let Some(field) = line.split_whitespace().nth(19) { - return field.parse::().unwrap_or(0); - } - 0 - } - #[cfg(not(any(target_os = "macos", target_os = "linux")))] - { - 0 - } + rustpython_host_env::posix::get_number_of_os_threads() } /// Warn if forking from a multi-threaded process. @@ -1853,7 +1777,7 @@ pub mod module { } else { // env=None means use the current environment - env::vars_os() + crate::host_env::os::vars_os() .map(|(k, v)| { let mut entry = k.into_vec(); entry.push(b'='); diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 7546d890600..fdf66b457bc 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "sys breakpoint configuration still uses direct host APIs until later extraction" -)] - pub(crate) mod monitoring; use crate::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject}; @@ -58,7 +53,7 @@ mod sys { use core::sync::atomic::Ordering; use num_traits::ToPrimitive; use std::{ - env::{self, VarError}, + env, io::{IsTerminal, Read, Write}, }; @@ -876,15 +871,18 @@ mod sys { #[pyfunction(name = "__breakpointhook__")] #[pyfunction] pub fn breakpointhook(args: FuncArgs, vm: &VirtualMachine) -> PyResult { - let env_var = std::env::var("PYTHONBREAKPOINT") + #[cfg(feature = "host_env")] + let env_var = crate::host_env::os::var("PYTHONBREAKPOINT") .and_then(|env_var| { if env_var.is_empty() { - Err(VarError::NotPresent) + Err(std::env::VarError::NotPresent) } else { Ok(env_var) } }) .unwrap_or_else(|_| "pdb.set_trace".to_owned()); + #[cfg(not(feature = "host_env"))] + let env_var = "pdb.set_trace".to_owned(); if env_var.eq("0") { return Ok(vm.ctx.none()); diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 0460138a6fa..267382ed34d 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "vm initialization still uses direct host APIs until later extraction" -)] - //! Implement virtual machine to run instructions. //! //! See also: @@ -541,7 +536,7 @@ impl StopTheWorldState { #[cfg(all(unix, feature = "threading"))] pub(super) fn stw_trace_enabled() -> bool { static ENABLED: std::sync::OnceLock = std::sync::OnceLock::new(); - *ENABLED.get_or_init(|| std::env::var_os("RUSTPYTHON_STW_TRACE").is_some()) + *ENABLED.get_or_init(|| crate::host_env::os::var_os("RUSTPYTHON_STW_TRACE").is_some()) } #[cfg(all(unix, feature = "threading"))] @@ -793,8 +788,8 @@ impl VirtualMachine { #[cfg(feature = "encodings")] fn import_encodings(&mut self) -> PyResult<()> { self.import("encodings", 0).map_err(|import_err| { - let rustpythonpath_env = std::env::var("RUSTPYTHONPATH").ok(); - let pythonpath_env = std::env::var("PYTHONPATH").ok(); + let rustpythonpath_env = crate::host_env::os::var("RUSTPYTHONPATH").ok(); + let pythonpath_env = crate::host_env::os::var("PYTHONPATH").ok(); let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some(); let path_contains_env = self.state.config.paths.module_search_paths.iter().any(|s| { Some(s.as_str()) == rustpythonpath_env.as_deref() || Some(s.as_str()) == pythonpath_env.as_deref() diff --git a/crates/vm/src/vm/python_run.rs b/crates/vm/src/vm/python_run.rs index 53b48a751d1..bdc98d5ad85 100644 --- a/crates/vm/src/vm/python_run.rs +++ b/crates/vm/src/vm/python_run.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::disallowed_methods, - reason = "script loading still uses direct host APIs until later extraction" -)] - //! Python code execution functions. use crate::{ @@ -110,7 +105,7 @@ mod file_run { if path != "" { set_main_loader(module_dict, path, "SourceFileLoader", self)?; } - match std::fs::read_to_string(path) { + match crate::host_env::fileutils::read_to_string(path) { Ok(source) => { let code_obj = self .compile(&source, compiler::Mode::Exec, path.to_owned()) @@ -165,7 +160,7 @@ mod file_run { return Ok(false); } - let mut file = std::fs::File::open(path)?; + let mut file = crate::host_env::fileutils::open(path)?; let mut buf = [0u8; 2]; use std::io::Read; From 7ea62c677eaab51c417efd42a2c2d8b0b64d77a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 01:47:27 +0000 Subject: [PATCH 05/11] fix: clean extracted host env follow-up Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/d96f57e1-b196-4460-9983-97d5ff118835 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/common/src/refcount.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index 0090b3c3fd4..ae1be96150d 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -19,7 +19,7 @@ fn refcount_overflow() -> ! { #[cfg(feature = "std")] unsafe { libc::abort() - }; + } #[cfg(not(feature = "std"))] core::panic!("refcount overflow"); } From 866c98a742a814464f8fee84495a72b9ff7cc4cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 01:49:26 +0000 Subject: [PATCH 06/11] fix: document env mutation safety Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/d96f57e1-b196-4460-9983-97d5ff118835 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/common/src/refcount.rs | 3 ++- crates/host_env/src/os.rs | 10 ++++++++-- crates/vm/src/stdlib/os.rs | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index ae1be96150d..ea6c7def4ae 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -17,9 +17,10 @@ const WEAK_COUNT: usize = 1 << STRONG_WIDTH; #[cold] fn refcount_overflow() -> ! { #[cfg(feature = "std")] + // SAFETY: abort terminates the process immediately and does not return. unsafe { libc::abort() - } + }; #[cfg(not(feature = "std"))] core::panic!("refcount overflow"); } diff --git a/crates/host_env/src/os.rs b/crates/host_env/src/os.rs index a2e804faaf0..c125962bf75 100644 --- a/crates/host_env/src/os.rs +++ b/crates/host_env/src/os.rs @@ -48,11 +48,17 @@ pub fn vars_os() -> env::VarsOs { env::vars_os() } -pub fn set_var(key: impl AsRef, value: impl AsRef) { +/// # Safety +/// The caller must ensure no other threads can concurrently read or write +/// the process environment while this mutation is performed. +pub unsafe fn set_var(key: impl AsRef, value: impl AsRef) { unsafe { env::set_var(key, value) }; } -pub fn remove_var(key: impl AsRef) { +/// # Safety +/// The caller must ensure no other threads can concurrently read or write +/// the process environment while this mutation is performed. +pub unsafe fn remove_var(key: impl AsRef) { unsafe { env::remove_var(key) }; } diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 70e9d1b8da9..e02b20fa82c 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -544,7 +544,7 @@ pub(super) mod _os { let key = super::bytes_as_os_str(key, vm)?; let value = super::bytes_as_os_str(value, vm)?; // SAFETY: requirements forwarded from the caller - crate::host_env::os::set_var(key, value); + unsafe { crate::host_env::os::set_var(key, value) }; Ok(()) } @@ -596,7 +596,7 @@ pub(super) mod _os { } let key = super::bytes_as_os_str(key, vm)?; // SAFETY: requirements forwarded from the caller - crate::host_env::os::remove_var(key); + unsafe { crate::host_env::os::remove_var(key) }; Ok(()) } From 2bfe061e80e74b83b51f43c9995da149d1d402ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:06:20 +0000 Subject: [PATCH 07/11] refactor: split host fs helpers from fileutils Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/c57424c5-0e1d-490a-82b3-2d2f6c8cf2cd Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/common/src/refcount.rs | 4 ++- crates/host_env/src/fileutils.rs | 46 ------------------------------- crates/host_env/src/fs.rs | 45 ++++++++++++++++++++++++++++++ crates/host_env/src/lib.rs | 2 ++ crates/host_env/src/posix.rs | 2 +- crates/stdlib/src/openssl.rs | 2 +- crates/stdlib/src/openssl/cert.rs | 2 +- crates/stdlib/src/ssl.rs | 6 ++-- crates/stdlib/src/ssl/cert.rs | 10 +++---- crates/vm/src/builtins/code.rs | 2 +- crates/vm/src/exceptions.rs | 2 +- crates/vm/src/getpath.rs | 2 +- crates/vm/src/readline.rs | 2 +- crates/vm/src/stdlib/os.rs | 19 ++++++------- crates/vm/src/stdlib/posix.rs | 4 +-- crates/vm/src/vm/python_run.rs | 4 +-- 16 files changed, 78 insertions(+), 76 deletions(-) create mode 100644 crates/host_env/src/fs.rs diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index ea6c7def4ae..e3b40fc8dd3 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -16,11 +16,13 @@ const WEAK_COUNT: usize = 1 << STRONG_WIDTH; #[inline(never)] #[cold] fn refcount_overflow() -> ! { - #[cfg(feature = "std")] + #[cfg(all(feature = "std", not(target_arch = "wasm32")))] // SAFETY: abort terminates the process immediately and does not return. unsafe { libc::abort() }; + #[cfg(all(feature = "std", target_arch = "wasm32"))] + core::panic!("refcount overflow"); #[cfg(not(feature = "std"))] core::panic!("refcount overflow"); } diff --git a/crates/host_env/src/fileutils.rs b/crates/host_env/src/fileutils.rs index 285e535c1bd..a20140a6e04 100644 --- a/crates/host_env/src/fileutils.rs +++ b/crates/host_env/src/fileutils.rs @@ -1,12 +1,6 @@ // Python/fileutils.c in CPython #![allow(non_snake_case)] -use std::{ - fs::{self, File, Metadata, ReadDir}, - io, - path::Path, -}; - #[cfg(not(windows))] pub use libc::stat as StatStruct; @@ -26,46 +20,6 @@ pub fn fstat(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result { } } -pub fn open(path: impl AsRef) -> io::Result { - File::open(path) -} - -pub fn read(path: impl AsRef) -> io::Result> { - fs::read(path) -} - -pub fn read_to_string(path: impl AsRef) -> io::Result { - fs::read_to_string(path) -} - -pub fn read_dir(path: impl AsRef) -> io::Result { - fs::read_dir(path) -} - -pub fn create_dir_all(path: impl AsRef) -> io::Result<()> { - fs::create_dir_all(path) -} - -pub fn remove_dir(path: impl AsRef) -> io::Result<()> { - fs::remove_dir(path) -} - -pub fn remove_file(path: impl AsRef) -> io::Result<()> { - fs::remove_file(path) -} - -pub fn metadata(path: impl AsRef) -> io::Result { - fs::metadata(path) -} - -pub fn symlink_metadata(path: impl AsRef) -> io::Result { - fs::symlink_metadata(path) -} - -pub fn open_write(path: impl AsRef) -> io::Result { - fs::OpenOptions::new().write(true).open(path) -} - #[cfg(windows)] pub mod windows { use crate::crt_fd; diff --git a/crates/host_env/src/fs.rs b/crates/host_env/src/fs.rs new file mode 100644 index 00000000000..bda81cc8628 --- /dev/null +++ b/crates/host_env/src/fs.rs @@ -0,0 +1,45 @@ +use std::{ + fs::{self, File, Metadata, ReadDir}, + io, + path::Path, +}; + +pub fn open(path: impl AsRef) -> io::Result { + File::open(path) +} + +pub fn read(path: impl AsRef) -> io::Result> { + fs::read(path) +} + +pub fn read_to_string(path: impl AsRef) -> io::Result { + fs::read_to_string(path) +} + +pub fn read_dir(path: impl AsRef) -> io::Result { + fs::read_dir(path) +} + +pub fn create_dir_all(path: impl AsRef) -> io::Result<()> { + fs::create_dir_all(path) +} + +pub fn remove_dir(path: impl AsRef) -> io::Result<()> { + fs::remove_dir(path) +} + +pub fn remove_file(path: impl AsRef) -> io::Result<()> { + fs::remove_file(path) +} + +pub fn metadata(path: impl AsRef) -> io::Result { + fs::metadata(path) +} + +pub fn symlink_metadata(path: impl AsRef) -> io::Result { + fs::symlink_metadata(path) +} + +pub fn open_write(path: impl AsRef) -> io::Result { + fs::OpenOptions::new().write(true).open(path) +} diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs index 6ae544c0147..80c2109a46f 100644 --- a/crates/host_env/src/lib.rs +++ b/crates/host_env/src/lib.rs @@ -11,6 +11,8 @@ pub mod crt_fd; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] pub mod fileutils; +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +pub mod fs; #[cfg(windows)] pub mod windows; diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index 98a8af4a1b3..f3a5bbf4dcf 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -63,7 +63,7 @@ pub fn get_number_of_os_threads() -> isize { pub fn get_number_of_os_threads() -> isize { use std::io::Read as _; - let mut file = match crate::fileutils::open("/proc/self/stat") { + let mut file = match crate::fs::open("/proc/self/stat") { Ok(f) => f, Err(_) => return 0, }; diff --git a/crates/stdlib/src/openssl.rs b/crates/stdlib/src/openssl.rs index c49ca2f855b..9c7161d8048 100644 --- a/crates/stdlib/src/openssl.rs +++ b/crates/stdlib/src/openssl.rs @@ -1880,7 +1880,7 @@ mod _ssl { const PEM_BUFSIZE: usize = 1024; // Read key file data - let key_data = rustpython_host_env::fileutils::read(key_file_path) + let key_data = rustpython_host_env::fs::read(key_file_path) .map_err(|e| crate::vm::convert::ToPyException::to_pyexception(&e, vm))?; let pkey = if let Some(ref pw_obj) = password { diff --git a/crates/stdlib/src/openssl/cert.rs b/crates/stdlib/src/openssl/cert.rs index 4d5460f6fdb..4f0e222f14f 100644 --- a/crates/stdlib/src/openssl/cert.rs +++ b/crates/stdlib/src/openssl/cert.rs @@ -359,7 +359,7 @@ pub(crate) mod ssl_cert { #[pyfunction] pub(crate) fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { let path = path.to_path_buf(vm)?; - let pem = rustpython_host_env::fileutils::read(path).map_err(|e| e.to_pyexception(vm))?; + let pem = rustpython_host_env::fs::read(path).map_err(|e| e.to_pyexception(vm))?; let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; cert_to_py(vm, &x509, false) } diff --git a/crates/stdlib/src/ssl.rs b/crates/stdlib/src/ssl.rs index e8688a94fe9..51f5bc042f4 100644 --- a/crates/stdlib/src/ssl.rs +++ b/crates/stdlib/src/ssl.rs @@ -1766,7 +1766,7 @@ mod _ssl { // Validate that the file contains DH parameters // Read the file and check for DH PARAMETERS header - let contents = rustpython_host_env::fileutils::read_to_string(&path_str) + let contents = rustpython_host_env::fs::read_to_string(&path_str) .map_err(|e| vm.new_os_error(e.to_string()))?; if !contents.contains("BEGIN DH PARAMETERS") @@ -2154,7 +2154,7 @@ mod _ssl { path: &str, vm: &VirtualMachine, ) -> PyResult>> { - let data = rustpython_host_env::fileutils::read(path).map_err(|e| match e.kind() { + let data = rustpython_host_env::fs::read(path).map_err(|e| match e.kind() { std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => { e.into_pyexception(vm) } @@ -4913,7 +4913,7 @@ mod _ssl { fn _test_decode_cert(path: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { // Read certificate file let path_str = path.as_str(); - let cert_data = rustpython_host_env::fileutils::read(path_str).map_err(|e| { + let cert_data = rustpython_host_env::fs::read(path_str).map_err(|e| { vm.new_os_error(format!( "Failed to read certificate file {}: {}", path_str, e diff --git a/crates/stdlib/src/ssl/cert.rs b/crates/stdlib/src/ssl/cert.rs index 9d2d3987e14..0ae71208c5c 100644 --- a/crates/stdlib/src/ssl/cert.rs +++ b/crates/stdlib/src/ssl/cert.rs @@ -639,7 +639,7 @@ impl<'a> CertLoader<'a> { /// /// Returns statistics about loaded certificates pub fn load_from_file(&mut self, path: &str) -> Result { - let contents = rustpython_host_env::fileutils::read(path)?; + let contents = rustpython_host_env::fs::read(path)?; self.load_from_bytes(&contents) } @@ -648,7 +648,7 @@ impl<'a> CertLoader<'a> { /// Reads all files in the directory and attempts to parse them as certificates. /// Invalid files are silently skipped (matches OpenSSL capath behavior). pub fn load_from_dir(&mut self, dir_path: &str) -> Result { - let entries = rustpython_host_env::fileutils::read_dir(dir_path)?; + let entries = rustpython_host_env::fs::read_dir(dir_path)?; let mut stats = CertStats::default(); for entry in entries { @@ -658,7 +658,7 @@ impl<'a> CertLoader<'a> { // Skip directories and process all files // OpenSSL capath uses hash-based naming like "4e1295a3.0" if path.is_file() - && let Ok(contents) = rustpython_host_env::fileutils::read(&path) + && let Ok(contents) = rustpython_host_env::fs::read(&path) { // Ignore errors for individual files (some may not be certs) if let Ok(file_stats) = self.load_from_bytes(&contents) { @@ -1129,7 +1129,7 @@ pub(super) fn load_cert_chain_from_file( password: Option<&str>, ) -> Result<(Vec>, PrivateKeyDer<'static>), Box> { // Load certificate file - preserve io::Error for errno - let cert_contents = rustpython_host_env::fileutils::read(cert_path)?; + let cert_contents = rustpython_host_env::fs::read(cert_path)?; // Parse certificates (PEM format) let mut cert_cursor = std::io::Cursor::new(&cert_contents); @@ -1142,7 +1142,7 @@ pub(super) fn load_cert_chain_from_file( } // Load private key file - preserve io::Error for errno - let key_contents = rustpython_host_env::fileutils::read(key_path)?; + let key_contents = rustpython_host_env::fs::read(key_path)?; // Parse private key (supports PKCS8, RSA, EC formats) let private_key = if let Some(pwd) = password { diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index 5077e1d426e..dd5cb14f84b 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -525,7 +525,7 @@ impl PyCode { Some(stem) => stem.display().to_string(), None => "".to_owned(), }; - let content = crate::host_env::fileutils::read(path).map_err(|e| e.to_pyexception(vm))?; + let content = crate::host_env::fs::read(path).map_err(|e| e.to_pyexception(vm))?; Self::from_pyc( &content, Some(&name), diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index c8d719529cc..ffab36bd01d 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -373,7 +373,7 @@ fn print_source_line( ) -> Result<(), W::Error> { // TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/main/Python/traceback.c#L393 // TODO: support different encodings - let file = match crate::host_env::fileutils::open(filename) { + let file = match crate::host_env::fs::open(filename) { Ok(file) => file, Err(_) => return Ok(()), }; diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index 6cfea403a2a..89a1c6b470a 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -371,7 +371,7 @@ fn get_executable_path() -> Option { /// Parse pyvenv.cfg and extract the 'home' key value fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option { - let content = crate::host_env::fileutils::read_to_string(pyvenv_cfg).ok()?; + let content = crate::host_env::fs::read_to_string(pyvenv_cfg).ok()?; for line in content.lines() { if let Some((key, value)) = line.split_once('=') diff --git a/crates/vm/src/readline.rs b/crates/vm/src/readline.rs index f82499f9cdf..cf82c3ba1ff 100644 --- a/crates/vm/src/readline.rs +++ b/crates/vm/src/readline.rs @@ -128,7 +128,7 @@ mod rustyline_readline { if !path.exists() && let Some(parent) = path.parent() { - crate::host_env::fileutils::create_dir_all(parent)?; + crate::host_env::fs::create_dir_all(parent)?; } self.repl.save_history(path)?; Ok(()) diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index e02b20fa82c..e31b35d06e7 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -14,9 +14,9 @@ pub(crate) fn fs_metadata>( follow_symlink: bool, ) -> io::Result { if follow_symlink { - crate::host_env::fileutils::metadata(path.as_ref()) + crate::host_env::fs::metadata(path.as_ref()) } else { - crate::host_env::fileutils::symlink_metadata(path.as_ref()) + crate::host_env::fs::symlink_metadata(path.as_ref()) } } @@ -366,8 +366,7 @@ pub(super) mod _os { #[pyfunction] fn mkdirs(path: PyStrRef, vm: &VirtualMachine) -> PyResult<()> { let os_path = vm.fsencode(&path)?; - crate::host_env::fileutils::create_dir_all(&*os_path) - .map_err(|err| err.into_pyexception(vm)) + crate::host_env::fs::create_dir_all(&*os_path).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(windows))] @@ -390,7 +389,7 @@ pub(super) mod _os { } #[cfg(target_os = "redox")] let [] = dir_fd.0; - crate::host_env::fileutils::remove_dir(&path) + crate::host_env::fs::remove_dir(&path) .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } @@ -398,7 +397,7 @@ pub(super) mod _os { #[pyfunction] fn rmdir(path: OsPath, dir_fd: DirFd<'_, 0>, vm: &VirtualMachine) -> PyResult<()> { let [] = dir_fd.0; - crate::host_env::fileutils::remove_dir(&path) + crate::host_env::fs::remove_dir(&path) .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } @@ -414,7 +413,7 @@ pub(super) mod _os { .unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str("."))); let list = match path { OsPathOrFd::Path(path) => { - let dir_iter = match crate::host_env::fileutils::read_dir(&path) { + let dir_iter = match crate::host_env::fs::read_dir(&path) { Ok(iter) => iter, Err(err) => { return Err(OSErrorBuilder::with_filename(&err, path, vm)); @@ -1188,7 +1187,7 @@ pub(super) mod _os { .unwrap_or_else(|| OsPathOrFd::Path(OsPath::new_str("."))); match path { OsPathOrFd::Path(path) => { - let entries = crate::host_env::fileutils::read_dir(&path.path) + let entries = crate::host_env::fs::read_dir(&path.path) .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; Ok(ScandirIterator { entries: PyRwLock::new(Some(entries)), @@ -1502,7 +1501,7 @@ pub(super) mod _os { use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; - if let Ok(cwd) = env::current_dir() { + if let Ok(cwd) = crate::host_env::os::current_dir() { let cwd_str = cwd.as_os_str(); let mut cwd_wide: Vec = cwd_str.encode_wide().collect(); @@ -2051,7 +2050,7 @@ pub(super) mod _os { let path = OsPath::try_from_object(vm, path)?; // TODO: just call libc::truncate() on POSIX - let f = match crate::host_env::fileutils::open_write(&path) { + let f = match crate::host_env::fs::open_write(&path) { Ok(f) => f, Err(e) => return Err(error(vm, e, path)), }; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 462fa29c582..b97801d2362 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -472,7 +472,7 @@ pub mod module { ) })?; - let metadata = match crate::host_env::fileutils::metadata(&path.path) { + let metadata = match crate::host_env::fs::metadata(&path.path) { Ok(m) => m, // If the file doesn't exist, return False for any access check Err(_) => return Ok(false), @@ -571,7 +571,7 @@ pub mod module { } #[cfg(target_os = "redox")] let [] = dir_fd.0; - crate::host_env::fileutils::remove_file(&path) + crate::host_env::fs::remove_file(&path) .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } diff --git a/crates/vm/src/vm/python_run.rs b/crates/vm/src/vm/python_run.rs index bdc98d5ad85..26000b5e11f 100644 --- a/crates/vm/src/vm/python_run.rs +++ b/crates/vm/src/vm/python_run.rs @@ -105,7 +105,7 @@ mod file_run { if path != "" { set_main_loader(module_dict, path, "SourceFileLoader", self)?; } - match crate::host_env::fileutils::read_to_string(path) { + match crate::host_env::fs::read_to_string(path) { Ok(source) => { let code_obj = self .compile(&source, compiler::Mode::Exec, path.to_owned()) @@ -160,7 +160,7 @@ mod file_run { return Ok(false); } - let mut file = crate::host_env::fileutils::open(path)?; + let mut file = crate::host_env::fs::open(path)?; let mut buf = [0u8; 2]; use std::io::Read; From 6c48dc99b04974bb648275fa54282570e08bad1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:09:11 +0000 Subject: [PATCH 08/11] fix: resolve latest host env ci regressions Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/899eb717-ebc6-4a4a-870c-2a15c5f33e02 Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/host_env/src/os.rs | 4 ++++ crates/vm/src/getpath.rs | 6 ++++++ crates/vm/src/stdlib/nt.rs | 6 +++--- crates/vm/src/stdlib/posix_compat.rs | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/host_env/src/os.rs b/crates/host_env/src/os.rs index c125962bf75..9d5aac5178b 100644 --- a/crates/host_env/src/os.rs +++ b/crates/host_env/src/os.rs @@ -48,6 +48,10 @@ pub fn vars_os() -> env::VarsOs { env::vars_os() } +pub fn vars() -> env::Vars { + env::vars() +} + /// # Safety /// The caller must ensure no other threads can concurrently read or write /// the process environment while this mutation is performed. diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index 89a1c6b470a..789fc72f62a 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -370,6 +370,7 @@ fn get_executable_path() -> Option { } /// Parse pyvenv.cfg and extract the 'home' key value +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option { let content = crate::host_env::fs::read_to_string(pyvenv_cfg).ok()?; @@ -384,6 +385,11 @@ fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option { None } +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +fn parse_pyvenv_home(_pyvenv_cfg: &Path) -> Option { + None +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 992ca580ee4..809e26249c3 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -22,7 +22,7 @@ pub(crate) mod module { use rustpython_common::wtf8::Wtf8Buf; use rustpython_host_env::nt as host_nt; use std::os::windows::io::AsRawHandle; - use std::{env, io, os::windows::ffi::OsStringExt}; + use std::{io, os::windows::ffi::OsStringExt}; use windows_sys::Win32::{ Foundation::{self, INVALID_HANDLE_VALUE}, Storage::FileSystem, @@ -236,7 +236,7 @@ pub(crate) mod module { fn environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in env::vars() { + for (key, value) in crate::host_env::os::vars() { // Skip hidden Windows environment variables (e.g., =C:, =D:, =ExitCode) // These are internal cmd.exe bookkeeping variables that store per-drive // current directories and cannot be reliably modified via _wputenv(). @@ -251,7 +251,7 @@ pub(crate) mod module { #[pyfunction] fn _create_environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in env::vars() { + for (key, value) in crate::host_env::os::vars() { if key.starts_with('=') { continue; } diff --git a/crates/vm/src/stdlib/posix_compat.rs b/crates/vm/src/stdlib/posix_compat.rs index 2b4b70c2457..c50134a33a4 100644 --- a/crates/vm/src/stdlib/posix_compat.rs +++ b/crates/vm/src/stdlib/posix_compat.rs @@ -13,7 +13,7 @@ pub(crate) mod module { ospath::OsPath, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; - use std::{env, fs}; + use std::fs; #[pyfunction] pub(super) fn access(_path: PyStrRef, _mode: u8, vm: &VirtualMachine) -> PyResult { @@ -49,7 +49,7 @@ pub(crate) mod module { use rustpython_host_env::os::ffi::OsStringExt; let environ = vm.ctx.new_dict(); - for (key, value) in env::vars_os() { + for (key, value) in crate::host_env::os::vars_os() { let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into(); let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into(); environ.set_item(&*key, value, vm).unwrap(); From 6aa8d925c3709b090a0fb7e8b696fd21e5ced289 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:21:23 +0000 Subject: [PATCH 09/11] fix: resolve remaining windows clippy host fs calls Agent-Logs-Url: https://github.com/RustPython/RustPython/sessions/12f32740-8173-4b10-a1d6-00b29e90a8ec Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com> --- crates/host_env/src/fs.rs | 14 ++++++++++++++ crates/vm/src/stdlib/os.rs | 15 ++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/host_env/src/fs.rs b/crates/host_env/src/fs.rs index bda81cc8628..911bd4575b9 100644 --- a/crates/host_env/src/fs.rs +++ b/crates/host_env/src/fs.rs @@ -43,3 +43,17 @@ pub fn symlink_metadata(path: impl AsRef) -> io::Result { pub fn open_write(path: impl AsRef) -> io::Result { fs::OpenOptions::new().write(true).open(path) } + +pub fn canonicalize(path: impl AsRef) -> io::Result { + fs::canonicalize(path) +} + +#[cfg(windows)] +pub fn open_write_with_custom_flags(path: impl AsRef, flags: u32) -> io::Result { + use std::os::windows::fs::OpenOptionsExt; + + fs::OpenOptions::new() + .write(true) + .custom_flags(flags) + .open(path) +} diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index e31b35d06e7..429fef19eeb 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -1687,7 +1687,8 @@ pub(super) mod _os { let src_path = match follow_symlinks.into_option() { Some(true) => { // Explicit follow_symlinks=True: resolve symlinks - fs::canonicalize(&src.path).unwrap_or_else(|_| PathBuf::from(src.path.clone())) + crate::host_env::fs::canonicalize(&src.path) + .unwrap_or_else(|_| PathBuf::from(src.path.clone())) } Some(false) | None => { // Default or explicit no-follow: native hard_link behavior @@ -1835,7 +1836,7 @@ pub(super) mod _os { } #[cfg(windows)] { - use std::{fs::OpenOptions, os::windows::prelude::*}; + use std::os::windows::prelude::*; type DWORD = u32; use windows_sys::Win32::{Foundation::FILETIME, Storage::FileSystem}; @@ -1859,11 +1860,11 @@ pub(super) mod _os { let acc = ft(acc); let modif = ft(modif); - let f = OpenOptions::new() - .write(true) - .custom_flags(windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS) - .open(&path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + let f = crate::host_env::fs::open_write_with_custom_flags( + &path, + windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, + ) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let ret = unsafe { FileSystem::SetFileTime(f.as_raw_handle() as _, core::ptr::null(), &acc, &modif) From 61bb551ee82fe638b6686c59afe433fd300ad46a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Fri, 17 Apr 2026 22:40:50 +0900 Subject: [PATCH 10/11] host_env --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- crates/common/src/refcount.rs | 13 ++++++------- crates/host_env/Cargo.toml | 2 +- crates/host_env/src/time.rs | 11 +++-------- crates/stdlib/Cargo.toml | 2 +- crates/stdlib/build.rs | 2 +- crates/vm/Cargo.toml | 2 +- crates/vm/build.rs | 2 +- 9 files changed, 18 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7e38637645..6e07767c6df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3220,7 +3220,7 @@ dependencies = [ ] [[package]] -name = "rustpython-host-env" +name = "rustpython-host_env" version = "0.5.0" dependencies = [ "libc", @@ -3419,7 +3419,7 @@ dependencies = [ "rustls-platform-verifier", "rustpython-common", "rustpython-derive", - "rustpython-host-env", + "rustpython-host_env", "rustpython-ruff_python_ast", "rustpython-ruff_python_parser", "rustpython-ruff_source_file", @@ -3499,7 +3499,7 @@ dependencies = [ "rustpython-compiler", "rustpython-compiler-core", "rustpython-derive", - "rustpython-host-env", + "rustpython-host_env", "rustpython-jit", "rustpython-literal", "rustpython-ruff_python_ast", diff --git a/Cargo.toml b/Cargo.toml index 9da6e7d83f1..bd46b0fceae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,7 +145,7 @@ rustpython-compiler-core = { path = "crates/compiler-core", version = "0.5.0" } rustpython-compiler = { path = "crates/compiler", version = "0.5.0" } rustpython-codegen = { path = "crates/codegen", version = "0.5.0" } rustpython-common = { path = "crates/common", version = "0.5.0" } -rustpython-host-env = { path = "crates/host_env", version = "0.5.0" } +rustpython-host_env = { path = "crates/host_env", version = "0.5.0" } rustpython-derive = { path = "crates/derive", version = "0.5.0" } rustpython-derive-impl = { path = "crates/derive-impl", version = "0.5.0" } rustpython-jit = { path = "crates/jit", version = "0.5.0" } diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index e3b40fc8dd3..d17f5ccd9f3 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -15,14 +15,13 @@ const WEAK_COUNT: usize = 1 << STRONG_WIDTH; #[inline(never)] #[cold] +#[allow( + clippy::disallowed_methods, + reason = "refcount overflow must preserve upstream abort semantics" +)] fn refcount_overflow() -> ! { - #[cfg(all(feature = "std", not(target_arch = "wasm32")))] - // SAFETY: abort terminates the process immediately and does not return. - unsafe { - libc::abort() - }; - #[cfg(all(feature = "std", target_arch = "wasm32"))] - core::panic!("refcount overflow"); + #[cfg(feature = "std")] + std::process::abort(); #[cfg(not(feature = "std"))] core::panic!("refcount overflow"); } diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml index 22dc659786a..e826a8baaf4 100644 --- a/crates/host_env/Cargo.toml +++ b/crates/host_env/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rustpython-host-env" +name = "rustpython-host_env" description = "Host OS API abstractions for RustPython" version.workspace = true authors.workspace = true diff --git a/crates/host_env/src/time.rs b/crates/host_env/src/time.rs index 2050356e406..b643d9c2e39 100644 --- a/crates/host_env/src/time.rs +++ b/crates/host_env/src/time.rs @@ -1,8 +1,5 @@ use core::time::Duration; -use std::{ - io, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; pub const SEC_TO_MS: i64 = 1000; pub const MS_TO_US: i64 = 1000; @@ -13,10 +10,8 @@ pub const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS; pub const NS_TO_MS: i64 = 1000 * 1000; pub const NS_TO_US: i64 = 1000; -pub fn duration_since_system_now() -> io::Result { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(io::Error::other) +pub fn duration_since_system_now() -> Result { + SystemTime::now().duration_since(UNIX_EPOCH) } #[cfg(target_env = "msvc")] diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index 5739a3f4a97..6971a54f992 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -30,7 +30,7 @@ flame-it = ["flame"] rustpython-derive = { workspace = true } rustpython-vm = { workspace = true, default-features = false, features = ["compiler"]} rustpython-common = { workspace = true } -rustpython-host-env = { workspace = true } +rustpython-host_env = { workspace = true } ruff_python_parser = { workspace = true } ruff_python_ast = { workspace = true } diff --git a/crates/stdlib/build.rs b/crates/stdlib/build.rs index 644b18228b5..5a06d24a269 100644 --- a/crates/stdlib/build.rs +++ b/crates/stdlib/build.rs @@ -1,6 +1,6 @@ #![allow( clippy::disallowed_methods, - reason = "build scripts cannot use rustpython-host-env" + reason = "build scripts cannot use rustpython-host_env" )] // spell-checker:ignore ossl osslconf diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 0d02a89a889..1537a8fd517 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -33,7 +33,7 @@ rustpython-compiler = { workspace = true, optional = true } rustpython-codegen = { workspace = true, optional = true } rustpython-common = { workspace = true } rustpython-derive = { workspace = true } -rustpython-host-env = { workspace = true } +rustpython-host_env = { workspace = true } rustpython-jit = { workspace = true, optional = true } ruff_python_ast = { workspace = true, optional = true } diff --git a/crates/vm/build.rs b/crates/vm/build.rs index e8fef7285fd..f38ae993568 100644 --- a/crates/vm/build.rs +++ b/crates/vm/build.rs @@ -1,6 +1,6 @@ #![allow( clippy::disallowed_methods, - reason = "build scripts cannot use rustpython-host-env" + reason = "build scripts cannot use rustpython-host_env" )] use itertools::Itertools; From 3414c8d38ad9543e418bdfe3a886ae2ef9cac7e6 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Sat, 18 Apr 2026 16:20:09 +0900 Subject: [PATCH 11/11] more isolation --- Cargo.lock | 5 +- crates/host_env/Cargo.toml | 18 + crates/host_env/src/faulthandler.rs | 170 ++ crates/host_env/src/lib.rs | 20 + crates/host_env/src/locale.rs | 148 ++ crates/host_env/src/mmap.rs | 240 +++ crates/host_env/src/msvcrt.rs | 8 +- crates/host_env/src/multiprocessing.rs | 377 ++++ crates/host_env/src/nt.rs | 2177 +++++++++++++++++++++++- crates/host_env/src/os.rs | 69 +- crates/host_env/src/overlapped.rs | 1018 +++++++++++ crates/host_env/src/posix.rs | 396 ++++- crates/host_env/src/pwd.rs | 58 + crates/host_env/src/resource.rs | 87 + crates/host_env/src/select.rs | 93 +- crates/host_env/src/signal.rs | 300 ++++ crates/host_env/src/syslog.rs | 15 +- crates/host_env/src/testconsole.rs | 42 + crates/host_env/src/time.rs | 85 + crates/host_env/src/winapi.rs | 1249 +++++++++++++- crates/host_env/src/winreg.rs | 452 +++++ crates/host_env/src/wmi.rs | 676 ++++++++ crates/stdlib/src/_testconsole.rs | 45 +- crates/stdlib/src/faulthandler.rs | 234 +-- crates/stdlib/src/locale.rs | 221 +-- crates/stdlib/src/mmap.rs | 307 +--- crates/stdlib/src/multiprocessing.rs | 400 +---- crates/stdlib/src/overlapped.rs | 978 ++--------- crates/stdlib/src/posixsubprocess.rs | 46 +- crates/stdlib/src/resource.rs | 31 +- crates/stdlib/src/select.rs | 84 +- crates/vm/Cargo.toml | 3 - crates/vm/src/stdlib/_io.rs | 559 +----- crates/vm/src/stdlib/_signal.rs | 271 +-- crates/vm/src/stdlib/_winapi.rs | 1547 +++-------------- crates/vm/src/stdlib/_wmi.rs | 677 +------- crates/vm/src/stdlib/nt.rs | 1320 ++------------ crates/vm/src/stdlib/os.rs | 77 +- crates/vm/src/stdlib/posix.rs | 119 +- crates/vm/src/stdlib/pwd.rs | 55 +- crates/vm/src/stdlib/time.rs | 109 +- crates/vm/src/stdlib/winreg.rs | 337 +--- crates/vm/src/windows.rs | 470 ----- 43 files changed, 8769 insertions(+), 6824 deletions(-) create mode 100644 crates/host_env/src/faulthandler.rs create mode 100644 crates/host_env/src/locale.rs create mode 100644 crates/host_env/src/mmap.rs create mode 100644 crates/host_env/src/multiprocessing.rs create mode 100644 crates/host_env/src/overlapped.rs create mode 100644 crates/host_env/src/pwd.rs create mode 100644 crates/host_env/src/resource.rs create mode 100644 crates/host_env/src/testconsole.rs create mode 100644 crates/host_env/src/winreg.rs create mode 100644 crates/host_env/src/wmi.rs diff --git a/Cargo.lock b/Cargo.lock index 6e07767c6df..7b93f5976ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3223,9 +3223,13 @@ dependencies = [ name = "rustpython-host_env" version = "0.5.0" dependencies = [ + "junction", "libc", + "memmap2", "nix 0.30.1", "num-traits", + "parking_lot", + "rustix", "rustpython-wtf8", "termios", "widestring", @@ -3475,7 +3479,6 @@ dependencies = [ "indexmap", "is-macro", "itertools 0.14.0", - "junction", "libc", "libffi", "libloading 0.9.0", diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml index e826a8baaf4..249a0d1dd16 100644 --- a/crates/host_env/Cargo.toml +++ b/crates/host_env/Cargo.toml @@ -13,28 +13,46 @@ rustpython-wtf8 = { workspace = true } libc = { workspace = true } num-traits = { workspace = true } +parking_lot = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true } +[target.'cfg(any(target_os = "linux", target_os = "android", target_os = "redox"))'.dependencies] +rustix = { workspace = true } + [target.'cfg(all(unix, not(target_os = "ios"), not(target_os = "redox")))'.dependencies] termios = "0.3.3" [target.'cfg(windows)'.dependencies] +junction = { workspace = true } +memmap2 = "0.9.10" widestring = { workspace = true } windows-sys = { workspace = true, features = [ "Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", + "Win32_Security", + "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_Diagnostics_Debug", + "Win32_System_Environment", + "Win32_System_IO", "Win32_System_Ioctl", + "Win32_System_JobObjects", + "Win32_System_Kernel", "Win32_System_LibraryLoader", + "Win32_System_Memory", + "Win32_System_Pipes", + "Win32_System_Performance", + "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", + "Win32_System_WindowsProgramming", + "Win32_UI_Shell", ] } [lints] diff --git a/crates/host_env/src/faulthandler.rs b/crates/host_env/src/faulthandler.rs new file mode 100644 index 00000000000..ad75677a622 --- /dev/null +++ b/crates/host_env/src/faulthandler.rs @@ -0,0 +1,170 @@ +#![allow( + clippy::missing_safety_doc, + reason = "These wrappers expose low-level fault handler hooks with raw OS ABI semantics." +)] +#![allow( + clippy::result_unit_err, + reason = "These helpers preserve the existing fault-handler error surface." +)] + +#[cfg(windows)] +use windows_sys::Win32::System::{ + Diagnostics::Debug::{ + AddVectoredExceptionHandler, EXCEPTION_POINTERS, PVECTORED_EXCEPTION_HANDLER, + RaiseException, RemoveVectoredExceptionHandler, SEM_NOGPFAULTERRORBOX, SetErrorMode, + }, + Threading::GetCurrentThreadId, +}; + +pub fn write_fd(fd: i32, buf: &[u8]) { + let _ = unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len() as _) }; +} + +#[cfg(any(unix, windows))] +pub fn abort_process() -> ! { + unsafe { libc::abort() } +} + +#[cfg(any(unix, windows))] +pub fn raise_signal(signum: libc::c_int) { + unsafe { + libc::raise(signum); + } +} + +#[cfg(unix)] +#[inline] +pub fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } +} + +#[cfg(windows)] +#[inline] +pub fn current_thread_id() -> u64 { + unsafe { GetCurrentThreadId() as u64 } +} + +#[cfg(unix)] +pub fn install_sigaction( + signum: libc::c_int, + handler: extern "C" fn(libc::c_int), + flags: libc::c_int, + previous: &mut libc::sigaction, +) -> bool { + let mut action: libc::sigaction = unsafe { core::mem::zeroed() }; + action.sa_sigaction = handler as *const () as libc::sighandler_t; + action.sa_flags = flags; + unsafe { libc::sigaction(signum, &action, previous) == 0 } +} + +#[cfg(unix)] +pub fn restore_sigaction(signum: libc::c_int, previous: &libc::sigaction) { + unsafe { + libc::sigaction(signum, previous, core::ptr::null_mut()); + } +} + +#[cfg(unix)] +pub fn signal_default_and_raise(signum: libc::c_int) { + unsafe { + libc::signal(signum, libc::SIG_DFL); + libc::raise(signum); + } +} + +#[cfg(unix)] +pub fn exit_immediately(code: libc::c_int) -> ! { + unsafe { libc::_exit(code) } +} + +#[cfg(windows)] +pub fn install_signal_handler( + signum: libc::c_int, + handler: extern "C" fn(libc::c_int), +) -> Result { + let previous = unsafe { libc::signal(signum, handler as *const () as libc::sighandler_t) }; + if previous == libc::SIG_ERR as libc::sighandler_t { + Err(()) + } else { + Ok(previous) + } +} + +#[cfg(windows)] +pub fn restore_signal_handler(signum: libc::c_int, previous: libc::sighandler_t) { + unsafe { + libc::signal(signum, previous); + } +} + +#[cfg(windows)] +pub fn signal_default_and_raise(signum: libc::c_int) { + unsafe { + libc::signal(signum, libc::SIG_DFL); + libc::raise(signum); + } +} + +#[cfg(windows)] +pub fn add_vectored_exception_handler(handler: PVECTORED_EXCEPTION_HANDLER) -> usize { + unsafe { AddVectoredExceptionHandler(1, handler) as usize } +} + +#[cfg(windows)] +pub fn remove_vectored_exception_handler(handle: usize) { + if handle != 0 { + unsafe { + RemoveVectoredExceptionHandler(handle as *mut core::ffi::c_void); + } + } +} + +#[cfg(windows)] +pub fn suppress_crash_report() { + unsafe { + let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); + SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); + } +} + +#[cfg(windows)] +pub fn raise_exception(code: u32, flags: u32) { + unsafe { + RaiseException(code, flags, 0, core::ptr::null()); + } +} + +#[cfg(windows)] +pub fn ignore_exception(code: u32) -> bool { + if (code & 0x8000_0000) == 0 { + return true; + } + code == 0xE06D7363 || code == 0xE0434352 +} + +#[cfg(windows)] +pub fn exception_description(code: u32) -> Option<&'static str> { + match code { + 0xC0000005 => Some("access violation"), + 0xC000008C => Some("float divide by zero"), + 0xC0000091 => Some("float overflow"), + 0xC0000094 => Some("int divide by zero"), + 0xC0000095 => Some("integer overflow"), + 0xC0000006 => Some("page error"), + 0xC00000FD => Some("stack overflow"), + 0xC000001D => Some("illegal instruction"), + _ => None, + } +} + +#[cfg(windows)] +pub unsafe fn exception_code(exc_info: *mut EXCEPTION_POINTERS) -> u32 { + let record = unsafe { &*(*exc_info).ExceptionRecord }; + record.ExceptionCode as u32 +} + +#[cfg(windows)] +#[inline] +pub fn is_access_violation(code: u32) -> bool { + code == 0xC0000005 +} diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs index 80c2109a46f..9332ccc50bf 100644 --- a/crates/host_env/src/lib.rs +++ b/crates/host_env/src/lib.rs @@ -13,6 +13,8 @@ pub mod crt_fd; pub mod fileutils; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] pub mod fs; +#[cfg(any(unix, windows))] +pub mod locale; #[cfg(windows)] pub mod windows; @@ -28,15 +30,33 @@ pub mod termios; #[cfg(unix)] pub mod posix; +#[cfg(unix)] +pub mod pwd; +#[cfg(unix)] +pub mod resource; #[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))] pub mod shm; #[cfg(unix)] pub mod signal; pub mod time; +#[cfg(any(unix, windows))] +pub mod faulthandler; +#[cfg(windows)] +pub mod mmap; #[cfg(windows)] pub mod msvcrt; +#[cfg(any(unix, windows))] +pub mod multiprocessing; #[cfg(windows)] pub mod nt; #[cfg(windows)] +pub mod overlapped; +#[cfg(windows)] +pub mod testconsole; +#[cfg(windows)] pub mod winapi; +#[cfg(windows)] +pub mod winreg; +#[cfg(windows)] +pub mod wmi; diff --git a/crates/host_env/src/locale.rs b/crates/host_env/src/locale.rs new file mode 100644 index 00000000000..8752e212744 --- /dev/null +++ b/crates/host_env/src/locale.rs @@ -0,0 +1,148 @@ +use alloc::vec::Vec; +use core::{ffi::CStr, ptr}; + +#[cfg(windows)] +#[repr(C)] +struct RawLconv { + decimal_point: *mut libc::c_char, + thousands_sep: *mut libc::c_char, + grouping: *mut libc::c_char, + int_curr_symbol: *mut libc::c_char, + currency_symbol: *mut libc::c_char, + mon_decimal_point: *mut libc::c_char, + mon_thousands_sep: *mut libc::c_char, + mon_grouping: *mut libc::c_char, + positive_sign: *mut libc::c_char, + negative_sign: *mut libc::c_char, + int_frac_digits: libc::c_char, + frac_digits: libc::c_char, + p_cs_precedes: libc::c_char, + p_sep_by_space: libc::c_char, + n_cs_precedes: libc::c_char, + n_sep_by_space: libc::c_char, + p_sign_posn: libc::c_char, + n_sign_posn: libc::c_char, + int_p_cs_precedes: libc::c_char, + int_p_sep_by_space: libc::c_char, + int_n_cs_precedes: libc::c_char, + int_n_sep_by_space: libc::c_char, + int_p_sign_posn: libc::c_char, + int_n_sign_posn: libc::c_char, +} + +#[cfg(windows)] +unsafe extern "C" { + fn localeconv() -> *mut RawLconv; +} + +#[cfg(unix)] +use libc::localeconv; + +#[derive(Debug, Clone)] +pub struct LocaleConv { + pub decimal_point: Vec, + pub thousands_sep: Vec, + pub grouping: Vec, + pub int_curr_symbol: Vec, + pub currency_symbol: Vec, + pub mon_decimal_point: Vec, + pub mon_thousands_sep: Vec, + pub mon_grouping: Vec, + pub positive_sign: Vec, + pub negative_sign: Vec, + pub int_frac_digits: libc::c_char, + pub frac_digits: libc::c_char, + pub p_cs_precedes: libc::c_char, + pub p_sep_by_space: libc::c_char, + pub n_cs_precedes: libc::c_char, + pub n_sep_by_space: libc::c_char, + pub p_sign_posn: libc::c_char, + pub n_sign_posn: libc::c_char, +} + +fn copy_cstr(ptr: *const libc::c_char) -> Vec { + if ptr.is_null() { + Vec::new() + } else { + unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec() + } +} + +fn copy_grouping(ptr: *const libc::c_char) -> Vec { + if ptr.is_null() { + return Vec::new(); + } + let mut out = Vec::new(); + let mut cur = ptr; + unsafe { + while ![0, libc::c_char::MAX].contains(&*cur) { + out.push(*cur); + cur = cur.add(1); + } + } + out +} + +pub fn localeconv_data() -> LocaleConv { + let lc = unsafe { localeconv() }; + unsafe { + LocaleConv { + decimal_point: copy_cstr((*lc).decimal_point), + thousands_sep: copy_cstr((*lc).thousands_sep), + grouping: copy_grouping((*lc).grouping), + int_curr_symbol: copy_cstr((*lc).int_curr_symbol), + currency_symbol: copy_cstr((*lc).currency_symbol), + mon_decimal_point: copy_cstr((*lc).mon_decimal_point), + mon_thousands_sep: copy_cstr((*lc).mon_thousands_sep), + mon_grouping: copy_grouping((*lc).mon_grouping), + positive_sign: copy_cstr((*lc).positive_sign), + negative_sign: copy_cstr((*lc).negative_sign), + int_frac_digits: (*lc).int_frac_digits, + frac_digits: (*lc).frac_digits, + p_cs_precedes: (*lc).p_cs_precedes, + p_sep_by_space: (*lc).p_sep_by_space, + n_cs_precedes: (*lc).n_cs_precedes, + n_sep_by_space: (*lc).n_sep_by_space, + p_sign_posn: (*lc).p_sign_posn, + n_sign_posn: (*lc).n_sign_posn, + } + } +} + +pub fn strcoll(string1: &CStr, string2: &CStr) -> libc::c_int { + unsafe { libc::strcoll(string1.as_ptr(), string2.as_ptr()) } +} + +pub fn strxfrm(string: &CStr, initial_len: usize) -> Vec { + let mut buff = vec![0u8; initial_len]; + let n2 = unsafe { libc::strxfrm(buff.as_mut_ptr() as _, string.as_ptr(), initial_len) }; + buff = vec![0u8; n2 + 1]; + unsafe { + libc::strxfrm(buff.as_mut_ptr() as _, string.as_ptr(), n2 + 1); + } + buff +} + +pub fn setlocale(category: i32, locale: Option<&CStr>) -> Option> { + let result = unsafe { + match locale { + None => libc::setlocale(category, ptr::null()), + Some(locale) => libc::setlocale(category, locale.as_ptr()), + } + }; + (!result.is_null()).then(|| unsafe { CStr::from_ptr(result) }.to_bytes().to_vec()) +} + +#[cfg(windows)] +pub fn acp() -> u32 { + unsafe { windows_sys::Win32::Globalization::GetACP() } +} + +#[cfg(all( + unix, + not(any(target_os = "ios", target_os = "android", target_os = "redox")) +))] +pub fn nl_langinfo_codeset() -> Option> { + let codeset = unsafe { libc::nl_langinfo(libc::CODESET) }; + (!codeset.is_null()).then(|| unsafe { CStr::from_ptr(codeset) }.to_bytes().to_vec()) +} diff --git a/crates/host_env/src/mmap.rs b/crates/host_env/src/mmap.rs new file mode 100644 index 00000000000..48b3968f0d4 --- /dev/null +++ b/crates/host_env/src/mmap.rs @@ -0,0 +1,240 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "These helpers are thin wrappers around raw Windows mapping APIs." +)] + +use std::io; + +use memmap2::{Mmap, MmapMut, MmapOptions}; +use windows_sys::Win32::{ + Foundation::{ + CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, GetLastError, HANDLE, + INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx}, + System::{ + Memory::{ + CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile, + MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, PAGE_READONLY, PAGE_READWRITE, + PAGE_WRITECOPY, UnmapViewOfFile, + }, + Threading::GetCurrentProcess, + }, +}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AccessMode { + Default = 0, + Read = 1, + Write = 2, + Copy = 3, +} + +#[derive(Debug)] +pub struct NamedMmap { + map_handle: HANDLE, + view_ptr: *mut u8, + len: usize, +} + +#[derive(Debug)] +pub enum MappedFile { + Read(Mmap), + Write(MmapMut), +} + +impl MappedFile { + pub fn as_slice(&self) -> &[u8] { + match self { + Self::Read(mmap) => &mmap[..], + Self::Write(mmap) => &mmap[..], + } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + match self { + Self::Read(_) => panic!("mmap can't modify a readonly memory map."), + Self::Write(mmap) => &mut mmap[..], + } + } + + pub fn as_ptr(&self) -> *const u8 { + match self { + Self::Read(mmap) => mmap.as_ptr(), + Self::Write(mmap) => mmap.as_ptr(), + } + } + + pub fn flush_range(&self, offset: usize, size: usize) -> io::Result<()> { + match self { + Self::Read(_) => Ok(()), + Self::Write(mmap) => mmap.flush_range(offset, size), + } + } +} + +unsafe impl Send for NamedMmap {} +unsafe impl Sync for NamedMmap {} + +impl NamedMmap { + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.view_ptr, self.len) } + } + + pub fn ptr_at(&self, offset: usize) -> *const core::ffi::c_void { + unsafe { self.view_ptr.add(offset) as *const _ } + } +} + +impl Drop for NamedMmap { + fn drop(&mut self) { + unsafe { + if !self.view_ptr.is_null() { + UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { + Value: self.view_ptr as *mut _, + }); + } + if !self.map_handle.is_null() { + CloseHandle(self.map_handle); + } + } + } +} + +pub fn duplicate_handle(handle: HANDLE) -> io::Result { + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let result = unsafe { + DuplicateHandle( + GetCurrentProcess(), + handle, + GetCurrentProcess(), + &mut new_handle, + 0, + 0, + DUPLICATE_SAME_ACCESS, + ) + }; + if result == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(new_handle) + } +} + +pub fn get_file_len(handle: HANDLE) -> io::Result { + let mut high: u32 = 0; + let low = unsafe { GetFileSize(handle, &mut high) }; + if low == u32::MAX { + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(0) { + return Err(err); + } + } + Ok(((high as i64) << 32) | (low as i64)) +} + +pub fn is_invalid_handle_value(handle: isize) -> bool { + handle == INVALID_HANDLE_VALUE as isize +} + +pub fn extend_file(handle: HANDLE, size: i64) -> io::Result<()> { + if unsafe { SetFilePointerEx(handle, size, core::ptr::null_mut(), FILE_BEGIN) } == 0 { + return Err(io::Error::last_os_error()); + } + if unsafe { SetEndOfFile(handle) } == 0 { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +pub fn close_handle(handle: HANDLE) { + unsafe { CloseHandle(handle) }; +} + +pub fn flush_view(ptr: *const core::ffi::c_void, size: usize) -> io::Result<()> { + if unsafe { FlushViewOfFile(ptr, size) } == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn last_error() -> u32 { + unsafe { GetLastError() } +} + +pub fn create_named_mapping( + file_handle: HANDLE, + tag: &str, + access: AccessMode, + offset: i64, + map_size: usize, +) -> io::Result { + let (fl_protect, desired_access) = match access { + AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE), + AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ), + AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY), + }; + + let total_size = (offset as u64) + .checked_add(map_size as u64) + .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?; + let size_hi = (total_size >> 32) as u32; + let size_lo = total_size as u32; + let tag_wide: Vec = tag.encode_utf16().chain(core::iter::once(0)).collect(); + + let map_handle = unsafe { + CreateFileMappingW( + file_handle, + core::ptr::null(), + fl_protect, + size_hi, + size_lo, + tag_wide.as_ptr(), + ) + }; + if map_handle.is_null() { + return Err(io::Error::last_os_error()); + } + + let off_hi = (offset as u64 >> 32) as u32; + let off_lo = offset as u32; + let view = unsafe { MapViewOfFile(map_handle, desired_access, off_hi, off_lo, map_size) }; + if view.Value.is_null() { + unsafe { CloseHandle(map_handle) }; + return Err(io::Error::last_os_error()); + } + + Ok(NamedMmap { + map_handle, + view_ptr: view.Value as *mut u8, + len: map_size, + }) +} + +pub fn map_handle( + handle: HANDLE, + offset: i64, + size: usize, + access: AccessMode, +) -> io::Result { + use std::{ + fs::File, + os::windows::io::{FromRawHandle, RawHandle}, + }; + + let file = unsafe { File::from_raw_handle(handle as RawHandle) }; + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(size); + + let result = match access { + AccessMode::Default | AccessMode::Write => { + unsafe { mmap_opt.map_mut(&file) }.map(MappedFile::Write) + } + AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MappedFile::Read), + AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MappedFile::Write), + }; + + core::mem::forget(file); + result +} diff --git a/crates/host_env/src/msvcrt.rs b/crates/host_env/src/msvcrt.rs index 00a599e3944..1d29498e871 100644 --- a/crates/host_env/src/msvcrt.rs +++ b/crates/host_env/src/msvcrt.rs @@ -35,9 +35,7 @@ pub fn getch() -> Vec { pub fn getwch() -> String { let value = unsafe { _getwch() }; - char::from_u32(value) - .unwrap_or_else(|| panic!("invalid unicode {value:#x} from _getwch")) - .to_string() + char::from_u32(value).unwrap().to_string() } pub fn getche() -> Vec { @@ -46,9 +44,7 @@ pub fn getche() -> Vec { pub fn getwche() -> String { let value = unsafe { _getwche() }; - char::from_u32(value) - .unwrap_or_else(|| panic!("invalid unicode {value:#x} from _getwche")) - .to_string() + char::from_u32(value).unwrap().to_string() } pub fn putch(c: u8) { diff --git a/crates/host_env/src/multiprocessing.rs b/crates/host_env/src/multiprocessing.rs new file mode 100644 index 00000000000..d8a5ff0b23e --- /dev/null +++ b/crates/host_env/src/multiprocessing.rs @@ -0,0 +1,377 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "Semaphore helpers intentionally mirror OS handle and pointer APIs." +)] +#![allow( + clippy::result_unit_err, + reason = "These helpers preserve the existing host-facing error surface." +)] + +#[cfg(unix)] +use alloc::ffi::CString; +#[cfg(windows)] +use std::io; + +#[cfg(unix)] +use libc::sem_t; +#[cfg(unix)] +use nix::errno::Errno; + +#[cfg(unix)] +#[derive(Debug)] +pub struct SemHandle { + raw: *mut sem_t, +} + +#[cfg(windows)] +use windows_sys::Win32::{ + Foundation::{ + CloseHandle, ERROR_TOO_MANY_POSTS, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WAIT_FAILED, + WAIT_OBJECT_0, WAIT_TIMEOUT, + }, + System::Threading::{ + CreateSemaphoreW, GetCurrentThreadId, ReleaseSemaphore, WaitForSingleObjectEx, + }, +}; + +#[cfg(windows)] +#[derive(Debug)] +pub struct SemHandle { + raw: HANDLE, +} + +unsafe impl Send for SemHandle {} +unsafe impl Sync for SemHandle {} + +#[cfg(unix)] +impl SemHandle { + pub fn create(name: &str, value: u32, unlink: bool) -> Result<(Self, Option), Errno> { + let cname = semaphore_name(name).map_err(|_| Errno::EINVAL)?; + let raw = + unsafe { libc::sem_open(cname.as_ptr(), libc::O_CREAT | libc::O_EXCL, 0o600, value) }; + if raw == libc::SEM_FAILED { + return Err(Errno::last()); + } + if unlink { + unsafe { + libc::sem_unlink(cname.as_ptr()); + } + Ok((Self { raw }, None)) + } else { + Ok((Self { raw }, Some(name.to_owned()))) + } + } + + pub fn open_existing(name: &str) -> Result { + let cname = semaphore_name(name).map_err(|_| Errno::EINVAL)?; + let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) }; + if raw == libc::SEM_FAILED { + Err(Errno::last()) + } else { + Ok(Self { raw }) + } + } + + #[inline] + pub fn as_ptr(&self) -> *mut sem_t { + self.raw + } +} + +#[cfg(windows)] +impl SemHandle { + pub fn create(value: i32, maxvalue: i32) -> io::Result { + let handle = + unsafe { CreateSemaphoreW(core::ptr::null(), value, maxvalue, core::ptr::null()) }; + if handle == 0 as HANDLE { + Err(io::Error::last_os_error()) + } else { + Ok(Self { raw: handle }) + } + } + + #[inline] + pub fn from_raw(raw: HANDLE) -> Self { + Self { raw } + } + + #[inline] + pub fn as_raw(&self) -> HANDLE { + self.raw + } +} + +#[cfg(unix)] +impl Drop for SemHandle { + fn drop(&mut self) { + if !self.raw.is_null() { + unsafe { + libc::sem_close(self.raw); + } + } + } +} + +#[cfg(windows)] +impl Drop for SemHandle { + fn drop(&mut self) { + if self.raw != 0 as HANDLE && self.raw != INVALID_HANDLE_VALUE { + unsafe { + CloseHandle(self.raw); + } + } + } +} + +#[cfg(unix)] +#[inline] +pub fn current_thread_id() -> u64 { + unsafe { libc::pthread_self() as u64 } +} + +#[cfg(windows)] +#[inline] +pub fn current_thread_id() -> u32 { + unsafe { GetCurrentThreadId() } +} + +#[cfg(windows)] +#[inline] +pub fn wait_for_single_object(handle: HANDLE, timeout_ms: u32) -> u32 { + unsafe { WaitForSingleObjectEx(handle, timeout_ms, 0) } +} + +#[cfg(windows)] +#[inline] +pub fn wait_object_0() -> u32 { + WAIT_OBJECT_0 +} + +#[cfg(windows)] +#[inline] +pub fn wait_timeout() -> u32 { + WAIT_TIMEOUT +} + +#[cfg(windows)] +#[inline] +pub fn wait_failed() -> u32 { + WAIT_FAILED +} + +#[cfg(windows)] +pub fn release_semaphore(handle: HANDLE) -> Result<(), u32> { + if unsafe { ReleaseSemaphore(handle, 1, core::ptr::null_mut()) } == 0 { + Err(unsafe { GetLastError() }) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub fn get_semaphore_value(handle: HANDLE) -> Result { + match wait_for_single_object(handle, 0) { + WAIT_OBJECT_0 => { + let mut previous: i32 = 0; + if unsafe { ReleaseSemaphore(handle, 1, &mut previous) } == 0 { + Err(()) + } else { + Ok(previous + 1) + } + } + WAIT_TIMEOUT => Ok(0), + _ => Err(()), + } +} + +#[cfg(windows)] +#[inline] +pub fn is_too_many_posts(err: u32) -> bool { + err == ERROR_TOO_MANY_POSTS +} + +#[cfg(unix)] +pub fn semaphore_name(name: &str) -> Result { + let mut full = String::with_capacity(name.len() + 1); + if !name.starts_with('/') { + full.push('/'); + } + full.push_str(name); + CString::new(full) +} + +#[cfg(unix)] +pub fn sem_unlink(name: &str) -> Result<(), Errno> { + let cname = semaphore_name(name).map_err(|_| Errno::EINVAL)?; + let res = unsafe { libc::sem_unlink(cname.as_ptr()) }; + if res < 0 { Err(Errno::last()) } else { Ok(()) } +} + +#[cfg(all(unix, not(target_vendor = "apple")))] +/// # Safety +/// +/// `handle` must point to a valid `sem_t` that remains alive for the duration +/// of this call and is valid to pass to `sem_getvalue`. +pub unsafe fn get_semaphore_value(handle: *mut sem_t) -> Result { + let mut sval: libc::c_int = 0; + let res = unsafe { libc::sem_getvalue(handle, &mut sval) }; + if res < 0 { + Err(Errno::last()) + } else { + Ok(if sval < 0 { 0 } else { sval }) + } +} + +#[cfg(unix)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_trywait(handle: *mut sem_t) -> Result<(), Errno> { + if unsafe { libc::sem_trywait(handle) } < 0 { + Err(Errno::last()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_post(handle: *mut sem_t) -> Result<(), Errno> { + if unsafe { libc::sem_post(handle) } < 0 { + Err(Errno::last()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn sem_value_max() -> i32 { + let val = unsafe { libc::sysconf(libc::_SC_SEM_VALUE_MAX) }; + if val < 0 || val > i32::MAX as libc::c_long { + i32::MAX + } else { + val as i32 + } +} + +#[cfg(unix)] +pub fn gettimeofday() -> Result { + let mut tv = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + if unsafe { libc::gettimeofday(&mut tv, core::ptr::null_mut()) } < 0 { + Err(Errno::last()) + } else { + Ok(tv) + } +} + +#[cfg(unix)] +pub fn deadline_from_timeout(timeout: f64) -> Result { + let timeout = if timeout < 0.0 { 0.0 } else { timeout }; + let tv = gettimeofday()?; + let sec = timeout as libc::c_long; + let nsec = (1e9 * (timeout - sec as f64) + 0.5) as libc::c_long; + let mut deadline = libc::timespec { + tv_sec: tv.tv_sec + sec as libc::time_t, + tv_nsec: (tv.tv_usec as libc::c_long * 1000 + nsec) as _, + }; + deadline.tv_sec += (deadline.tv_nsec / 1_000_000_000) as libc::time_t; + deadline.tv_nsec %= 1_000_000_000; + Ok(deadline) +} + +#[cfg(unix)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_wait_save_errno(handle: *mut sem_t, deadline: Option<&libc::timespec>) -> (i32, Errno) { + #[cfg(not(target_vendor = "apple"))] + if let Some(deadline) = deadline { + let r = unsafe { libc::sem_timedwait(handle, deadline) }; + ( + r, + if r < 0 { + Errno::last() + } else { + Errno::from_raw(0) + }, + ) + } else { + let r = unsafe { libc::sem_wait(handle) }; + ( + r, + if r < 0 { + Errno::last() + } else { + Errno::from_raw(0) + }, + ) + } + + #[cfg(target_vendor = "apple")] + { + debug_assert!(deadline.is_none()); + let r = unsafe { libc::sem_wait(handle) }; + ( + r, + if r < 0 { + Errno::last() + } else { + Errno::from_raw(0) + }, + ) + } +} + +#[cfg(target_vendor = "apple")] +pub enum PollWaitStep { + Acquired, + Timeout, + Continue(u64), +} + +#[cfg(target_vendor = "apple")] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn sem_timedwait_poll_step( + handle: *mut sem_t, + deadline: &libc::timespec, + delay: u64, +) -> Result { + if unsafe { libc::sem_trywait(handle) } == 0 { + return Ok(PollWaitStep::Acquired); + } + let err = Errno::last(); + if err != Errno::EAGAIN { + return Err(err); + } + + let now = gettimeofday()?; + let deadline_usec = deadline.tv_sec * 1_000_000 + deadline.tv_nsec / 1000; + #[allow(clippy::unnecessary_cast)] + let now_usec = now.tv_sec as i64 * 1_000_000 + now.tv_usec as i64; + if now_usec >= deadline_usec { + return Ok(PollWaitStep::Timeout); + } + + let difference = (deadline_usec - now_usec) as u64; + let mut delay = delay + 1000; + if delay > 20000 { + delay = 20000; + } + if delay > difference { + delay = difference; + } + + let mut tv_delay = libc::timeval { + tv_sec: (delay / 1_000_000) as _, + tv_usec: (delay % 1_000_000) as _, + }; + unsafe { + libc::select( + 0, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut tv_delay, + ); + } + Ok(PollWaitStep::Continue(delay)) +} diff --git a/crates/host_env/src/nt.rs b/crates/host_env/src/nt.rs index c6771aad40a..771cf1ef7b8 100644 --- a/crates/host_env/src/nt.rs +++ b/crates/host_env/src/nt.rs @@ -1,16 +1,330 @@ +#![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, io, os::windows::io::AsRawHandle}; +use std::{ + ffi::{OsStr, OsString}, + io, + os::windows::{ffi::OsStringExt, io::AsRawHandle}, + path::Path, +}; -use crate::{crt_fd, windows::ToWideString}; +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::ToWideString, +}; +use libc::intptr_t; use windows_sys::Win32::{ - Foundation::HANDLE, + Foundation::{CloseHandle, GetLastError, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH}, + Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte}, Storage::FileSystem::{ - FILE_ATTRIBUTE_READONLY, FILE_BASIC_INFO, FileBasicInfo, GetFileAttributesW, - GetFileInformationByHandleEx, INVALID_FILE_ATTRIBUTES, SetFileAttributesW, - SetFileInformationByHandle, + CreateFileW, FILE_ATTRIBUTE_READONLY, 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}, }; +#[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(|id| id.VolumeSerialNumber as u32) + .unwrap_or(info.dwVolumeSerialNumber); + 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 { + 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) }; + } + } + } + + let ok = if is_directory && is_link { + unsafe { RemoveDirectoryW(wide_path.as_ptr()) } + } else { + unsafe { DeleteFileW(wide_path.as_ptr()) } + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +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() }; @@ -71,3 +385,1854 @@ pub fn win32_lchmod(path: &OsStr, mode: u32, write_bit: u32) -> io::Result<()> { Ok(()) } } + +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(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + let result = win32_hchmod(handle, mode, write_bit); + unsafe { CloseHandle(handle) }; + result +} + +pub fn find_first_file_name(path: &Path) -> io::Result { + 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) }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + 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 { + 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]; + let ok = + unsafe { GetVolumePathNameW(wide_path.as_ptr(), volume.as_mut_ptr(), volume.len() as _) }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + 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(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + 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::() as u32, + (&mut volume_state as *mut FileFsPersistentVolumeInformation).cast(), + core::mem::size_of::() 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::() 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::() 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 = path.to_wide_with_nul(); + let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; + + let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + unsafe { FindClose(handle) }; + + let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; + info.dwFileAttributes = find_data.dwFileAttributes; + info.ftCreationTime = find_data.ftCreationTime; + info.ftLastAccessTime = find_data.ftLastAccessTime; + info.ftLastWriteTime = find_data.ftLastWriteTime; + info.nFileSizeHigh = find_data.nFileSizeHigh; + info.nFileSizeLow = find_data.nFileSizeLow; + + let reparse_tag = if find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + find_data.dwReserved0 + } else { + 0 + }; + + Ok((info, reparse_tag)) +} + +fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> io::Result { + 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 = 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 { + 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::() 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::() 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::() 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 { + 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 windows_sys::Win32::Security::SECURITY_ATTRIBUTES; + use windows_sys::Win32::System::Pipes::CreatePipe; + + let mut attr = SECURITY_ATTRIBUTES { + nLength: core::mem::size_of::() as u32, + lpSecurityDescriptor: core::ptr::null_mut(), + bInheritHandle: 0, + }; + + let (read_handle, write_handle) = unsafe { + let mut read = core::mem::MaybeUninit::::uninit(); + let mut write = core::mem::MaybeUninit::::uninit(); + let ok = CreatePipe( + read.as_mut_ptr() as *mut _, + write.as_mut_ptr() as *mut _, + &mut attr as *mut _, + 0, + ); + if ok == 0 { + return Err(io::Error::last_os_error()); + } + (read.assume_init(), write.assume_init()) + }; + + const O_NOINHERIT: i32 = 0x80; + let read_fd = match crate::msvcrt::open_osfhandle(read_handle, O_NOINHERIT) { + Ok(fd) => fd, + Err(err) => { + unsafe { + CloseHandle(read_handle as _); + CloseHandle(write_handle as _); + } + return Err(err); + } + }; + let write_fd = match crate::msvcrt::open_osfhandle(write_handle, libc::O_WRONLY | O_NOINHERIT) { + Ok(fd) => fd, + Err(err) => { + let _ = unsafe { crt_fd::Owned::from_raw(read_fd) }; + unsafe { CloseHandle(write_handle as _) }; + 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::() as u32, + lpSecurityDescriptor: core::ptr::null_mut(), + bInheritHandle: 0, + }; + let sddl: Vec = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0" + .encode_utf16() + .collect(); + let convert_ok = unsafe { + ConvertStringSecurityDescriptorToSecurityDescriptorW( + sddl.as_ptr(), + SDDL_REVISION_1, + &mut sec_attr.lpSecurityDescriptor, + core::ptr::null_mut(), + ) + }; + if convert_ok == 0 { + return Err(io::Error::last_os_error()); + } + 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(), + ) + } + }; + + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +unsafe extern "C" { + fn _umask(mask: i32) -> i32; +} + +pub fn umask(mask: i32) -> io::Result { + let result = unsafe { _umask(mask) }; + if result < 0 { + Err(crate::os::errno_io_error()) + } else { + Ok(result) + } +} + +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 { + 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 { + 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 { + 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 = 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) }; + if handle.is_null() { + return Err(io::Error::last_os_error()); + } + let ok = unsafe { Threading::TerminateProcess(handle, sig) }; + let err = if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + }; + unsafe { CloseHandle(handle) }; + err + } +} + +pub fn getfinalpathname(path: &Path) -> io::Result { + 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(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + 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 { + 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(), + ) + }; + if ret == 0 { + return Err(io::Error::last_os_error()); + } + 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(), + ) + }; + if ret == 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(widestring::WideCString::from_vec_truncate(buffer).to_os_string()) +} + +pub fn getvolumepathname(path: &Path) -> io::Result { + 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]; + let ok = unsafe { + windows_sys::Win32::Storage::FileSystem::GetVolumePathNameW( + wide.as_ptr(), + buffer.as_mut_ptr(), + buflen as u32, + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + 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 { + 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 { + 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> { + 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> { + 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> { + 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::() 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: *const u16) -> Option { + let mut end: *const u16 = core::ptr::null(); + let hr = unsafe { windows_sys::Win32::UI::Shell::PathCchSkipRoot(path, &mut end) }; + if hr >= 0 { + assert!(!end.is_null()); + Some( + unsafe { end.offset_from(path) } + .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 = "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)) +} + +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 handle == INVALID_HANDLE_VALUE || handle.is_null() { + 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 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 = 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 { + 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> { + 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 { + 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: *const u16, writable: bool) -> io::Result { + 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, + 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, + 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")] +pub fn spawnv(mode: i32, path: *const u16, argv: *const *const u16) -> io::Result { + let result = unsafe { crate::suppress_iph!(_wspawnv(mode, path, argv)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(result) + } +} + +#[cfg(target_env = "msvc")] +pub fn spawnve( + mode: i32, + path: *const u16, + argv: *const *const u16, + envp: *const *const u16, +) -> io::Result { + let result = unsafe { crate::suppress_iph!(_wspawnve(mode, path, argv, envp)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(result) + } +} + +#[cfg(target_env = "msvc")] +pub fn execv(path: *const u16, argv: *const *const u16) -> io::Result<()> { + let result = unsafe { crate::suppress_iph!(_wexecv(path, argv)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} + +#[cfg(target_env = "msvc")] +pub fn execve( + path: *const u16, + argv: *const *const u16, + envp: *const *const u16, +) -> io::Result<()> { + let result = unsafe { crate::suppress_iph!(_wexecve(path, argv, envp)) }; + if result == -1 { + Err(crate::os::errno_io_error()) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/os.rs b/crates/host_env/src/os.rs index 9d5aac5178b..bb1e276b952 100644 --- a/crates/host_env/src/os.rs +++ b/crates/host_env/src/os.rs @@ -67,17 +67,84 @@ pub unsafe fn remove_var(key: impl AsRef) { } pub fn set_current_dir(path: impl AsRef) -> io::Result<()> { - env::set_current_dir(path) + env::set_current_dir(&path)?; + + #[cfg(windows)] + { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; + + if let Ok(cwd) = env::current_dir() { + let cwd_str = cwd.as_os_str(); + let mut cwd_wide: Vec = cwd_str.encode_wide().collect(); + + let is_unc_like_path = cwd_wide.len() >= 2 + && ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16) + || (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16)); + + if !is_unc_like_path { + let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0]; + cwd_wide.push(0); + unsafe { + SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr()); + } + } + } + } + + Ok(()) } pub fn process_id() -> u32 { std::process::id() } +pub fn device_encoding(_fd: i32) -> Option { + #[cfg(any(target_os = "android", target_os = "redox"))] + { + return Some("UTF-8".to_owned()); + } + + #[cfg(windows)] + { + use windows_sys::Win32::System::Console; + let cp = match _fd { + 0 => unsafe { Console::GetConsoleCP() }, + 1 | 2 => unsafe { Console::GetConsoleOutputCP() }, + _ => 0, + }; + + Some(format!("cp{cp}")) + } + + #[cfg(not(any(target_os = "android", target_os = "redox", windows)))] + { + let encoding = unsafe { + let encoding = libc::nl_langinfo(libc::CODESET); + if encoding.is_null() || encoding.read() == b'\0' as libc::c_char { + "UTF-8".to_owned() + } else { + core::ffi::CStr::from_ptr(encoding) + .to_string_lossy() + .into_owned() + } + }; + + Some(encoding) + } +} + pub fn exit(code: i32) -> ! { std::process::exit(code) } +pub fn rename( + from: impl AsRef, + to: impl AsRef, +) -> io::Result<()> { + std::fs::rename(from, to) +} + pub trait ErrorExt { fn posix_errno(&self) -> i32; } diff --git a/crates/host_env/src/overlapped.rs b/crates/host_env/src/overlapped.rs new file mode 100644 index 00000000000..f7848988e20 --- /dev/null +++ b/crates/host_env/src/overlapped.rs @@ -0,0 +1,1018 @@ +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "This module exposes raw overlapped I/O wrappers over Win32 and Winsock APIs." +)] +#![allow( + clippy::too_many_arguments, + reason = "These helpers preserve the underlying Win32 and Winsock call shapes." +)] + +use alloc::sync::Arc; +use std::{ + collections::HashMap, + io, + sync::{Mutex, OnceLock}, +}; +use windows_sys::Win32::{ + Foundation::{ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, HANDLE}, + Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, + System::{ + Diagnostics::Debug::{ + FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, + }, + IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED}, + Pipes::ConnectNamedPipe, + Threading::{CreateEventW, SetEvent}, + }, +}; + +pub struct TransferResult { + pub transferred: u32, + pub error: u32, +} + +pub struct OverlappedResult { + pub transferred: u32, + pub error: u32, +} + +pub struct Operation { + overlapped: Box, + handle: HANDLE, + pending: bool, + completed: bool, + read_buffer: Option>, + write_buffer: Option>, +} + +impl core::fmt::Debug for Operation { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Operation") + .field("handle", &self.handle) + .field("pending", &self.pending) + .field("completed", &self.completed) + .finish() + } +} + +unsafe impl Sync for Operation {} +unsafe impl Send for Operation {} + +impl Operation { + pub fn new(handle: HANDLE) -> io::Result { + let event = unsafe { CreateEventW(core::ptr::null(), 1, 0, core::ptr::null()) }; + if event.is_null() { + return Err(io::Error::last_os_error()); + } + + let mut overlapped: OVERLAPPED = unsafe { core::mem::zeroed() }; + overlapped.hEvent = event; + Ok(Self { + overlapped: Box::new(overlapped), + handle, + pending: false, + completed: false, + read_buffer: None, + write_buffer: None, + }) + } + + pub fn event(&self) -> HANDLE { + self.overlapped.hEvent + } + + pub fn is_completed(&self) -> bool { + self.completed + } + + pub fn read_buffer(&self) -> Option<&[u8]> { + self.read_buffer.as_deref() + } + + pub fn get_result(&mut self, wait: bool) -> io::Result { + use windows_sys::Win32::Foundation::{ + ERROR_IO_INCOMPLETE, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, GetLastError, + }; + + let mut transferred = 0; + let ret = unsafe { + GetOverlappedResult( + self.handle, + &*self.overlapped, + &mut transferred, + i32::from(wait), + ) + }; + + let err = if ret == 0 { + unsafe { GetLastError() } + } else { + ERROR_SUCCESS + }; + + match err { + ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_OPERATION_ABORTED => { + self.completed = true; + self.pending = false; + } + ERROR_IO_INCOMPLETE => {} + _ => { + self.pending = false; + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + + if self.completed + && let Some(read_buffer) = &mut self.read_buffer + && transferred != read_buffer.len() as u32 + { + read_buffer.truncate(transferred as usize); + } + + Ok(TransferResult { + transferred, + error: err, + }) + } + + pub fn cancel(&mut self) -> io::Result<()> { + let ret = if self.pending { + unsafe { CancelIoEx(self.handle, &*self.overlapped) } + } else { + 1 + }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + self.pending = false; + Ok(()) + } + + pub fn connect_named_pipe(&mut self) -> io::Result<()> { + use windows_sys::Win32::Foundation::ERROR_PIPE_CONNECTED; + + let err = start_connect_named_pipe(self.handle, &mut *self.overlapped); + match err { + ERROR_IO_PENDING => { + self.pending = true; + } + ERROR_PIPE_CONNECTED => { + if unsafe { SetEvent(self.overlapped.hEvent) } == 0 { + return Err(io::Error::last_os_error()); + } + } + _ => return Err(io::Error::from_raw_os_error(err as i32)), + } + Ok(()) + } + + pub fn write(&mut self, buffer: &[u8]) -> io::Result { + let len = core::cmp::min(buffer.len(), u32::MAX as usize) as u32; + self.write_buffer = Some(buffer[..len as usize].to_vec()); + let write_buf = self + .write_buffer + .as_ref() + .expect("write buffer initialized"); + let err = start_write_file(self.handle, write_buf.as_ptr(), len, &mut *self.overlapped); + + if err != ERROR_SUCCESS && err != ERROR_IO_PENDING { + return Err(io::Error::from_raw_os_error(err as i32)); + } + if err == ERROR_IO_PENDING { + self.pending = true; + } + + Ok(err) + } + + pub fn read(&mut self, size: u32) -> io::Result { + self.read_buffer = Some(vec![0u8; size as usize]); + let read_buf = self.read_buffer.as_mut().expect("read buffer initialized"); + let err = start_read_file( + self.handle, + read_buf.as_mut_ptr(), + size, + &mut *self.overlapped, + ); + + if err != ERROR_SUCCESS && err != ERROR_IO_PENDING && err != ERROR_MORE_DATA { + return Err(io::Error::from_raw_os_error(err as i32)); + } + if err == ERROR_IO_PENDING { + self.pending = true; + } + + Ok(err) + } +} + +impl Drop for Operation { + fn drop(&mut self) { + if !self.overlapped.hEvent.is_null() { + unsafe { windows_sys::Win32::Foundation::CloseHandle(self.overlapped.hEvent) }; + } + } +} + +pub struct QueuedCompletionStatus { + pub error: u32, + pub bytes_transferred: u32, + pub completion_key: usize, + pub overlapped: usize, +} + +pub struct WaitCallbackData { + completion_port: HANDLE, + overlapped: *mut OVERLAPPED, +} + +pub enum WaitResult { + Timeout, + Queued(QueuedCompletionStatus), +} + +pub enum SocketAddress { + V4 { + host: String, + port: u16, + }, + V6 { + host: String, + port: u16, + flowinfo: u32, + scope_id: u32, + }, +} + +static ACCEPT_EX: OnceLock = OnceLock::new(); +static CONNECT_EX: OnceLock = OnceLock::new(); +static DISCONNECT_EX: OnceLock = OnceLock::new(); +static TRANSMIT_FILE: OnceLock = OnceLock::new(); +static WAIT_CALLBACK_REGISTRY: OnceLock>>> = + OnceLock::new(); + +fn wait_callback_registry() -> &'static Mutex>> { + WAIT_CALLBACK_REGISTRY.get_or_init(|| Mutex::new(HashMap::new())) +} + +pub fn initialize_winsock_extensions() -> io::Result<()> { + use windows_sys::Win32::Networking::WinSock::{ + INVALID_SOCKET, IPPROTO_TCP, SIO_GET_EXTENSION_FUNCTION_POINTER, SOCK_STREAM, SOCKET_ERROR, + WSAGetLastError, WSAIoctl, closesocket, socket, + }; + + const WSAID_ACCEPTEX: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0xb5367df1, + data2: 0xcbac, + data3: 0x11cf, + data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], + }; + const WSAID_CONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0x25a207b9, + data2: 0xddf3, + data3: 0x4660, + data4: [0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e], + }; + const WSAID_DISCONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0x7fda2e11, + data2: 0x8630, + data3: 0x436f, + data4: [0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57], + }; + const WSAID_TRANSMITFILE: windows_sys::core::GUID = windows_sys::core::GUID { + data1: 0xb5367df0, + data2: 0xcbac, + data3: 0x11cf, + data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], + }; + + if ACCEPT_EX.get().is_some() + && CONNECT_EX.get().is_some() + && DISCONNECT_EX.get().is_some() + && TRANSMIT_FILE.get().is_some() + { + return Ok(()); + } + + let s = unsafe { socket(AF_INET as i32, SOCK_STREAM, IPPROTO_TCP) }; + if s == INVALID_SOCKET { + return Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )); + } + + let mut dw_bytes = 0; + + macro_rules! get_extension { + ($guid:expr, $lock:expr) => {{ + let mut func_ptr: usize = 0; + let ret = unsafe { + WSAIoctl( + s, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &$guid as *const _ as *const _, + core::mem::size_of_val(&$guid) as u32, + &mut func_ptr as *mut _ as *mut _, + core::mem::size_of::() as u32, + &mut dw_bytes, + core::ptr::null_mut(), + None, + ) + }; + if ret == SOCKET_ERROR { + let err = unsafe { WSAGetLastError() }; + unsafe { closesocket(s) }; + return Err(io::Error::from_raw_os_error(err as i32)); + } + let _ = $lock.set(func_ptr); + }}; + } + + get_extension!(WSAID_ACCEPTEX, ACCEPT_EX); + get_extension!(WSAID_CONNECTEX, CONNECT_EX); + get_extension!(WSAID_DISCONNECTEX, DISCONNECT_EX); + get_extension!(WSAID_TRANSMITFILE, TRANSMIT_FILE); + + unsafe { closesocket(s) }; + Ok(()) +} + +pub fn mark_as_completed(ov: &mut OVERLAPPED) { + ov.Internal = 0; + if !ov.hEvent.is_null() { + unsafe { + let _ = SetEvent(ov.hEvent); + } + } +} + +pub fn has_overlapped_io_completed(overlapped: &OVERLAPPED) -> bool { + overlapped.Internal != (windows_sys::Win32::Foundation::STATUS_PENDING as usize) +} + +pub fn cancel_overlapped(handle: HANDLE, overlapped: *const OVERLAPPED) -> io::Result<()> { + let ret = unsafe { CancelIoEx(handle, overlapped) }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + Ok(()) +} + +pub fn get_overlapped_result( + handle: HANDLE, + overlapped: *const OVERLAPPED, + wait: bool, +) -> OverlappedResult { + let mut transferred = 0; + let ret = unsafe { GetOverlappedResult(handle, overlapped, &mut transferred, i32::from(wait)) }; + let error = if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + }; + OverlappedResult { transferred, error } +} + +pub fn cancel_overlapped_for_drop( + handle: HANDLE, + overlapped: *const OVERLAPPED, +) -> OverlappedResult { + let cancelled = unsafe { CancelIoEx(handle, overlapped) } != 0; + get_overlapped_result(handle, overlapped, cancelled) +} + +pub fn start_read_file( + handle: HANDLE, + buffer: *mut u8, + len: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + let mut transferred = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::ReadFile( + handle, + buffer.cast(), + len, + &mut transferred, + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } +} + +pub fn start_write_file( + handle: HANDLE, + buffer: *const u8, + len: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + let mut transferred = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::WriteFile( + handle, + buffer.cast(), + len, + &mut transferred, + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } +} + +pub fn start_wsa_recv( + handle: usize, + buffer: *mut u8, + len: u32, + flags: *mut u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecv}; + + let wsabuf = WSABUF { + buf: buffer.cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSARecv( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn start_wsa_send( + handle: usize, + buffer: *const u8, + len: u32, + flags: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASend}; + + let wsabuf = WSABUF { + buf: buffer.cast_mut().cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSASend( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn start_accept_ex( + listen_socket: usize, + accept_socket: usize, + buffer: *mut u8, + address_size: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type AcceptExFn = unsafe extern "system" fn( + s_listen_socket: usize, + s_accept_socket: usize, + lp_output_buffer: *mut core::ffi::c_void, + dw_receive_data_length: u32, + dw_local_address_length: u32, + dw_remote_address_length: u32, + lpdw_bytes_received: *mut u32, + lp_overlapped: *mut OVERLAPPED, + ) -> i32; + + let accept_ex: AcceptExFn = unsafe { core::mem::transmute(*ACCEPT_EX.get().unwrap()) }; + let mut bytes_received = 0; + let ret = unsafe { + accept_ex( + listen_socket, + accept_socket, + buffer.cast(), + 0, + address_size, + address_size, + &mut bytes_received, + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_connect_ex( + socket: usize, + address: *const SOCKADDR, + address_len: i32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type ConnectExFn = unsafe extern "system" fn( + s: usize, + name: *const SOCKADDR, + namelen: i32, + lp_send_buffer: *const core::ffi::c_void, + dw_send_data_length: u32, + lpdw_bytes_sent: *mut u32, + lp_overlapped: *mut OVERLAPPED, + ) -> i32; + + let connect_ex: ConnectExFn = unsafe { core::mem::transmute(*CONNECT_EX.get().unwrap()) }; + let ret = unsafe { + connect_ex( + socket, + address, + address_len, + core::ptr::null(), + 0, + core::ptr::null_mut(), + overlapped, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_disconnect_ex(socket: usize, flags: u32, overlapped: *mut OVERLAPPED) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type DisconnectExFn = unsafe extern "system" fn( + s: usize, + lp_overlapped: *mut OVERLAPPED, + dw_flags: u32, + dw_reserved: u32, + ) -> i32; + + let disconnect_ex: DisconnectExFn = + unsafe { core::mem::transmute(*DISCONNECT_EX.get().unwrap()) }; + let ret = unsafe { disconnect_ex(socket, overlapped, flags, 0) }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_transmit_file( + socket: usize, + file: HANDLE, + count_to_write: u32, + count_per_send: u32, + flags: u32, + offset: u32, + offset_high: u32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::WSAGetLastError; + + type TransmitFileFn = unsafe extern "system" fn( + h_socket: usize, + h_file: HANDLE, + n_number_of_bytes_to_write: u32, + n_number_of_bytes_per_send: u32, + lp_overlapped: *mut OVERLAPPED, + lp_transmit_buffers: *const core::ffi::c_void, + dw_reserved: u32, + ) -> i32; + + unsafe { + (*overlapped).Anonymous.Anonymous.Offset = offset; + (*overlapped).Anonymous.Anonymous.OffsetHigh = offset_high; + } + + let transmit_file: TransmitFileFn = + unsafe { core::mem::transmute(*TRANSMIT_FILE.get().unwrap()) }; + let ret = unsafe { + transmit_file( + socket, + file, + count_to_write, + count_per_send, + overlapped, + core::ptr::null(), + flags, + ) + }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { WSAGetLastError() as u32 } + } +} + +pub fn start_connect_named_pipe(pipe: HANDLE, overlapped: *mut OVERLAPPED) -> u32 { + let ret = unsafe { ConnectNamedPipe(pipe, overlapped) }; + if ret != 0 { + ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } +} + +pub fn start_wsa_send_to( + handle: usize, + buffer: *const u8, + len: u32, + flags: u32, + address: *const SOCKADDR, + address_len: i32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASendTo}; + + let wsabuf = WSABUF { + buf: buffer.cast_mut().cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSASendTo( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + address, + address_len, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn start_wsa_recv_from( + handle: usize, + buffer: *mut u8, + len: u32, + flags: *mut u32, + address: *mut SOCKADDR, + address_len: *mut i32, + overlapped: *mut OVERLAPPED, +) -> u32 { + use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecvFrom}; + + let wsabuf = WSABUF { + buf: buffer.cast(), + len, + }; + let mut transferred = 0; + let ret = unsafe { + WSARecvFrom( + handle, + &wsabuf, + 1, + &mut transferred, + flags, + address, + address_len, + overlapped, + None, + ) + }; + if ret < 0 { + unsafe { WSAGetLastError() as u32 } + } else { + ERROR_SUCCESS + } +} + +pub fn connect_pipe(address: &str) -> io::Result { + use windows_sys::Win32::{ + Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, + Storage::FileSystem::{CreateFileW, FILE_FLAG_OVERLAPPED, OPEN_EXISTING}, + }; + + let address_wide: Vec = address.encode_utf16().chain(core::iter::once(0)).collect(); + let handle = unsafe { + CreateFileW( + address_wide.as_ptr(), + GENERIC_READ | GENERIC_WRITE, + 0, + core::ptr::null(), + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + core::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle as isize) + } +} + +pub fn create_io_completion_port( + handle: isize, + port: isize, + key: usize, + concurrency: u32, +) -> io::Result { + let r = unsafe { + windows_sys::Win32::System::IO::CreateIoCompletionPort( + handle as HANDLE, + port as HANDLE, + key, + concurrency, + ) as isize + }; + if r == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(r) + } +} + +pub fn get_queued_completion_status(port: isize, msecs: u32) -> io::Result { + let mut bytes_transferred = 0; + let mut completion_key = 0; + let mut overlapped: *mut OVERLAPPED = core::ptr::null_mut(); + let ret = unsafe { + windows_sys::Win32::System::IO::GetQueuedCompletionStatus( + port as HANDLE, + &mut bytes_transferred, + &mut completion_key, + &mut overlapped, + msecs, + ) + }; + let err = if ret != 0 { + windows_sys::Win32::Foundation::ERROR_SUCCESS + } else { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + }; + if overlapped.is_null() { + if err == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + Ok(WaitResult::Timeout) + } else { + Err(io::Error::from_raw_os_error(err as i32)) + } + } else { + Ok(WaitResult::Queued(QueuedCompletionStatus { + error: err, + bytes_transferred, + completion_key, + overlapped: overlapped as usize, + })) + } +} + +pub fn post_queued_completion_status( + port: isize, + bytes: u32, + key: usize, + address: usize, +) -> io::Result<()> { + let ret = unsafe { + windows_sys::Win32::System::IO::PostQueuedCompletionStatus( + port as HANDLE, + bytes, + key, + address as *mut OVERLAPPED, + ) + }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +unsafe impl Send for WaitCallbackData {} +unsafe impl Sync for WaitCallbackData {} + +unsafe extern "system" fn post_to_queue_callback( + parameter: *mut core::ffi::c_void, + timer_or_wait_fired: bool, +) { + let data = unsafe { Arc::from_raw(parameter as *const WaitCallbackData) }; + unsafe { + let _ = windows_sys::Win32::System::IO::PostQueuedCompletionStatus( + data.completion_port, + if timer_or_wait_fired { 1 } else { 0 }, + 0, + data.overlapped, + ); + } +} + +pub fn register_wait_with_queue( + object: isize, + completion_port: isize, + overlapped: usize, + timeout: u32, +) -> io::Result { + use windows_sys::Win32::System::Threading::{ + RegisterWaitForSingleObject, WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE, + }; + + let data = Arc::new(WaitCallbackData { + completion_port: completion_port as HANDLE, + overlapped: overlapped as *mut OVERLAPPED, + }); + let data_ptr = Arc::into_raw(data.clone()); + + let mut new_wait_object: HANDLE = core::ptr::null_mut(); + let ret = unsafe { + RegisterWaitForSingleObject( + &mut new_wait_object, + object as HANDLE, + Some(post_to_queue_callback), + data_ptr as *mut _, + timeout, + WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE, + ) + }; + if ret == 0 { + unsafe { + let _ = Arc::from_raw(data_ptr); + } + return Err(io::Error::last_os_error()); + } + + let wait_handle = new_wait_object as isize; + if let Ok(mut registry) = wait_callback_registry().lock() { + registry.insert(wait_handle, data); + } + Ok(wait_handle) +} + +fn cleanup_wait_callback_data(wait_handle: isize) { + if let Ok(mut registry) = wait_callback_registry().lock() { + registry.remove(&wait_handle); + } +} + +pub fn unregister_wait(wait_handle: isize) -> io::Result<()> { + let ret = + unsafe { windows_sys::Win32::System::Threading::UnregisterWait(wait_handle as HANDLE) }; + cleanup_wait_callback_data(wait_handle); + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn unregister_wait_ex(wait_handle: isize, event: isize) -> io::Result<()> { + let ret = unsafe { + windows_sys::Win32::System::Threading::UnregisterWaitEx( + wait_handle as HANDLE, + event as HANDLE, + ) + }; + cleanup_wait_callback_data(wait_handle); + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn bind_local(socket: isize, family: i32) -> io::Result<()> { + use windows_sys::Win32::Networking::WinSock::{ + INADDR_ANY, SOCKET_ERROR, WSAGetLastError, bind, + }; + + let ret = if family == AF_INET as i32 { + let mut addr: SOCKADDR_IN = unsafe { core::mem::zeroed() }; + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.S_un.S_addr = INADDR_ANY; + unsafe { + bind( + socket as _, + &addr as *const _ as *const SOCKADDR, + core::mem::size_of::() as i32, + ) + } + } else if family == AF_INET6 as i32 { + let mut addr: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; + addr.sin6_family = AF_INET6; + addr.sin6_port = 0; + unsafe { + bind( + socket as _, + &addr as *const _ as *const SOCKADDR, + core::mem::size_of::() as i32, + ) + } + } else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "expected tuple of length 2 or 4", + )); + }; + + if ret == SOCKET_ERROR { + Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )) + } else { + Ok(()) + } +} + +pub fn format_message(error_code: u32) -> String { + use windows_sys::Win32::Foundation::LocalFree; + + const LANG_NEUTRAL: u32 = 0; + const SUBLANG_DEFAULT: u32 = 1; + + let mut buffer: *mut u16 = core::ptr::null_mut(); + let len = unsafe { + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + core::ptr::null(), + error_code, + (SUBLANG_DEFAULT << 10) | LANG_NEUTRAL, + &mut buffer as *mut _ as *mut u16, + 0, + core::ptr::null(), + ) + }; + + if len == 0 || buffer.is_null() { + if !buffer.is_null() { + unsafe { LocalFree(buffer as *mut _) }; + } + return format!("unknown error code {}", error_code); + } + + let slice = unsafe { core::slice::from_raw_parts(buffer, len as usize) }; + let msg = String::from_utf16_lossy(slice).trim_end().to_string(); + unsafe { LocalFree(buffer as *mut _) }; + msg +} + +pub fn wsa_connect(socket: isize, addr_ptr: *const SOCKADDR, addr_len: i32) -> io::Result<()> { + use windows_sys::Win32::Networking::WinSock::{SOCKET_ERROR, WSAConnect, WSAGetLastError}; + + let ret = unsafe { + WSAConnect( + socket as _, + addr_ptr, + addr_len, + core::ptr::null(), + core::ptr::null_mut(), + core::ptr::null(), + core::ptr::null(), + ) + }; + if ret == SOCKET_ERROR { + Err(io::Error::from_raw_os_error( + unsafe { WSAGetLastError() } as i32 + )) + } else { + Ok(()) + } +} diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index f3a5bbf4dcf..d8ab983268b 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -1,4 +1,6 @@ -use std::os::fd::BorrowedFd; +use core::ffi::CStr; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd}; +use std::path::Path; pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> { use nix::fcntl; @@ -86,3 +88,395 @@ pub fn get_number_of_os_threads() -> isize { pub fn get_number_of_os_threads() -> isize { 0 } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Permissions { + pub is_readable: bool, + pub is_writable: bool, + pub is_executable: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AccessError { + InvalidMode, + Os(nix::Error), +} + +impl From for AccessError { + fn from(value: nix::Error) -> Self { + Self::Os(value) + } +} + +const F_OK: u8 = 0; +const R_OK: u8 = 4; +const W_OK: u8 = 2; +const X_OK: u8 = 1; + +fn get_permissions(mode: u32) -> Permissions { + Permissions { + is_readable: mode & 4 != 0, + is_writable: mode & 2 != 0, + is_executable: mode & 1 != 0, + } +} + +pub fn get_right_permission( + mode: u32, + file_owner: nix::unistd::Uid, + file_group: nix::unistd::Gid, +) -> nix::Result { + let owner_mode = (mode & 0o700) >> 6; + let owner_permissions = get_permissions(owner_mode); + + let group_mode = (mode & 0o070) >> 3; + let group_permissions = get_permissions(group_mode); + + let others_mode = mode & 0o007; + let others_permissions = get_permissions(others_mode); + + let user_id = nix::unistd::getuid(); + let groups_ids = getgroups()?; + + if file_owner == user_id { + Ok(owner_permissions) + } else if groups_ids.contains(&file_group) { + Ok(group_permissions) + } else { + Ok(others_permissions) + } +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub fn getgroups() -> nix::Result> { + use core::ptr; + use libc::{c_int, gid_t}; + use nix::errno::Errno; + + let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; + let mut groups = Vec::::with_capacity(Errno::result(ret)? as usize); + let ret = unsafe { + libc::getgroups( + groups.capacity() as c_int, + groups.as_mut_ptr() as *mut gid_t, + ) + }; + + Errno::result(ret).map(|s| { + unsafe { groups.set_len(s as usize) }; + groups + }) +} + +#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))] +pub use nix::unistd::getgroups; + +#[cfg(target_os = "redox")] +pub fn getgroups() -> nix::Result> { + Err(nix::Error::EOPNOTSUPP) +} + +pub fn check_access(path: &Path, mode: u8) -> Result { + use std::os::unix::fs::MetadataExt; + + if mode & !(R_OK | W_OK | X_OK) != 0 { + return Err(AccessError::InvalidMode); + } + + let metadata = match crate::fs::metadata(path) { + Ok(m) => m, + Err(_) => return Ok(false), + }; + + if mode == F_OK { + return Ok(true); + } + + let perm = get_right_permission( + metadata.mode(), + nix::unistd::Uid::from_raw(metadata.uid()), + nix::unistd::Gid::from_raw(metadata.gid()), + )?; + + let r_ok = (mode & R_OK == 0) || perm.is_readable; + let w_ok = (mode & W_OK == 0) || perm.is_writable; + let x_ok = (mode & X_OK == 0) || perm.is_executable; + + Ok(r_ok && w_ok && x_ok) +} + +pub fn close_fds(above: i32, keep: &[BorrowedFd<'_>]) { + #[cfg(not(target_os = "redox"))] + if close_dir_fds(above, keep).is_ok() { + return; + } + #[cfg(target_os = "redox")] + if close_filetable_fds(above, keep).is_ok() { + return; + } + close_fds_brute_force(above, keep) +} + +#[allow(clippy::too_many_arguments)] +pub fn setup_child_fds( + fds_to_keep: &[BorrowedFd<'_>], + errpipe_write: BorrowedFd<'_>, + p2cread: i32, + p2cwrite: i32, + c2pread: i32, + c2pwrite: i32, + errread: i32, + errwrite: i32, + errpipe_read: i32, +) -> nix::Result<()> { + for &fd in fds_to_keep { + if fd.as_raw_fd() != errpipe_write.as_raw_fd() { + set_inheritable(fd, true)?; + } + } + + for fd in [p2cwrite, c2pread, errread] { + if fd >= 0 { + nix::unistd::close(fd)?; + } + } + nix::unistd::close(errpipe_read)?; + + let c2pwrite = if c2pwrite == 0 { + let fd = unsafe { BorrowedFd::borrow_raw(c2pwrite) }; + let dup = nix::unistd::dup(fd)?; + set_inheritable(dup.as_fd(), true)?; + dup.as_raw_fd() + } else { + c2pwrite + }; + + let mut errwrite = errwrite; + while errwrite == 0 || errwrite == 1 { + let fd = unsafe { BorrowedFd::borrow_raw(errwrite) }; + let dup = nix::unistd::dup(fd)?; + set_inheritable(dup.as_fd(), true)?; + errwrite = dup.as_raw_fd(); + } + + dup_into_stdio(p2cread, 0)?; + dup_into_stdio(c2pwrite, 1)?; + dup_into_stdio(errwrite, 2)?; + Ok(()) +} + +fn dup_into_stdio(fd: i32, io_fd: i32) -> nix::Result<()> { + if fd < 0 { + return Ok(()); + } + let fd = unsafe { BorrowedFd::borrow_raw(fd) }; + if fd.as_raw_fd() == io_fd { + set_inheritable(fd, true) + } else { + match io_fd { + 0 => nix::unistd::dup2_stdin(fd), + 1 => nix::unistd::dup2_stdout(fd), + 2 => nix::unistd::dup2_stderr(fd), + _ => unreachable!(), + } + } +} + +pub fn chdir(cwd: &CStr) -> nix::Result<()> { + nix::unistd::chdir(cwd) +} + +pub fn set_umask(child_umask: i32) { + if child_umask >= 0 { + unsafe { libc::umask(child_umask as libc::mode_t) }; + } +} + +pub fn restore_signals() { + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + libc::signal(libc::SIGXFSZ, libc::SIG_DFL); + } +} + +pub fn setsid_if_needed(call_setsid: bool) -> nix::Result<()> { + if call_setsid { + nix::unistd::setsid()?; + } + Ok(()) +} + +pub fn setpgid_if_needed(pgid_to_set: libc::pid_t) -> nix::Result<()> { + if pgid_to_set > -1 { + nix::unistd::setpgid( + nix::unistd::Pid::from_raw(0), + nix::unistd::Pid::from_raw(pgid_to_set), + )?; + } + Ok(()) +} + +pub fn setgroups_if_needed(_groups: Option<&[nix::unistd::Gid]>) -> nix::Result<()> { + #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] + if let Some(groups) = _groups { + nix::unistd::setgroups(groups)?; + } + Ok(()) +} + +pub fn setregid_if_needed(gid: Option) -> nix::Result<()> { + if let Some(gid) = gid.filter(|x| x.as_raw() != u32::MAX) { + let ret = unsafe { libc::setregid(gid.as_raw(), gid.as_raw()) }; + nix::Error::result(ret)?; + } + Ok(()) +} + +pub fn setreuid_if_needed(uid: Option) -> nix::Result<()> { + if let Some(uid) = uid.filter(|x| x.as_raw() != u32::MAX) { + let ret = unsafe { libc::setreuid(uid.as_raw(), uid.as_raw()) }; + nix::Error::result(ret)?; + } + Ok(()) +} + +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn exec_replace>( + exec_list: &[T], + argv: *const *const libc::c_char, + envp: Option<*const *const libc::c_char>, +) -> nix::errno::Errno { + let mut first_err = None; + for exec in exec_list { + if let Some(envp) = envp { + unsafe { libc::execve(exec.as_ref().as_ptr(), argv, envp) }; + } else { + unsafe { libc::execv(exec.as_ref().as_ptr(), argv) }; + } + let e = nix::errno::Errno::last(); + if e != nix::errno::Errno::ENOENT && e != nix::errno::Errno::ENOTDIR && first_err.is_none() + { + first_err = Some(e); + } + } + first_err.unwrap_or_else(nix::errno::Errno::last) +} + +fn should_keep(above: i32, keep: &[BorrowedFd<'_>], fd: i32) -> bool { + fd > above + && keep + .binary_search_by_key(&fd, BorrowedFd::as_raw_fd) + .is_err() +} + +#[cfg(not(target_os = "redox"))] +fn close_dir_fds(above: i32, keep: &[BorrowedFd<'_>]) -> nix::Result<()> { + use nix::{dir::Dir, fcntl::OFlag}; + use std::os::fd::AsRawFd; + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_vendor = "apple", + ))] + let fd_dir_name = c"/dev/fd"; + + #[cfg(any(target_os = "linux", target_os = "android"))] + let fd_dir_name = c"/proc/self/fd"; + + let mut dir = Dir::open( + fd_dir_name, + OFlag::O_RDONLY | OFlag::O_DIRECTORY, + nix::sys::stat::Mode::empty(), + )?; + let dirfd = dir.as_raw_fd(); + 'outer: for e in dir.iter() { + let e = e?; + let mut parser = IntParser::default(); + for &c in e.file_name().to_bytes() { + if parser.feed(c).is_err() { + continue 'outer; + } + } + let fd = parser.num; + if fd != dirfd && should_keep(above, keep, fd) { + let _ = nix::unistd::close(fd); + } + } + Ok(()) +} + +#[cfg(target_os = "redox")] +fn close_filetable_fds(above: i32, keep: &[BorrowedFd<'_>]) -> nix::Result<()> { + use nix::fcntl; + use std::os::fd::AsRawFd; + + let filetable = fcntl::open( + c"/scheme/thisproc/current/filetable", + fcntl::OFlag::O_RDONLY, + nix::sys::stat::Mode::empty(), + )?; + let read_one = || -> nix::Result<_> { + let mut byte = 0; + let n = nix::unistd::read(&filetable, std::slice::from_mut(&mut byte))?; + Ok((n > 0).then_some(byte)) + }; + while let Some(c) = read_one()? { + let mut parser = IntParser::default(); + if parser.feed(c).is_err() { + continue; + } + let done = loop { + let Some(c) = read_one()? else { break true }; + if parser.feed(c).is_err() { + break false; + } + }; + + let fd = parser.num; + if fd != filetable.as_raw_fd() && should_keep(above, keep, fd) { + let _ = nix::unistd::close(fd); + } + if done { + break; + } + } + Ok(()) +} + +fn close_fds_brute_force(above: i32, keep: &[BorrowedFd<'_>]) { + let max_fd = nix::unistd::sysconf(nix::unistd::SysconfVar::OPEN_MAX) + .ok() + .flatten() + .unwrap_or(256) as i32; + + let mut prev = above; + for fd in keep + .iter() + .map(BorrowedFd::as_raw_fd) + .chain(core::iter::once(max_fd)) + { + for candidate in prev + 1..fd { + unsafe { libc::close(candidate) }; + } + prev = fd; + } +} + +#[derive(Default)] +struct IntParser { + num: i32, +} + +struct NonDigit; + +impl IntParser { + fn feed(&mut self, c: u8) -> Result<(), NonDigit> { + let digit = (c as char).to_digit(10).ok_or(NonDigit)?; + self.num *= 10; + self.num += digit as i32; + Ok(()) + } +} diff --git a/crates/host_env/src/pwd.rs b/crates/host_env/src/pwd.rs new file mode 100644 index 00000000000..58456e05ddc --- /dev/null +++ b/crates/host_env/src/pwd.rs @@ -0,0 +1,58 @@ +use nix::unistd::{self, User}; + +#[derive(Debug, Clone)] +pub struct Passwd { + pub name: String, + pub passwd: String, + pub uid: u32, + pub gid: u32, + pub gecos: String, + pub dir: String, + pub shell: String, +} + +impl From for Passwd { + fn from(user: User) -> Self { + let cstr_lossy = |s: alloc::ffi::CString| { + s.into_string() + .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) + }; + let pathbuf_lossy = |p: std::path::PathBuf| { + p.into_os_string() + .into_string() + .unwrap_or_else(|s| s.to_string_lossy().into_owned()) + }; + Self { + name: user.name, + passwd: cstr_lossy(user.passwd), + uid: user.uid.as_raw(), + gid: user.gid.as_raw(), + gecos: cstr_lossy(user.gecos), + dir: pathbuf_lossy(user.dir), + shell: pathbuf_lossy(user.shell), + } + } +} + +pub fn getpwnam(name: &str) -> Option { + User::from_name(name).ok().flatten().map(Into::into) +} + +pub fn getpwuid(uid: libc::uid_t) -> nix::Result> { + User::from_uid(unistd::Uid::from_raw(uid)).map(|user| user.map(Into::into)) +} + +#[cfg(not(target_os = "android"))] +pub fn getpwall() -> Vec { + static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(()); + let _guard = GETPWALL.lock(); + let mut list = Vec::new(); + + unsafe { libc::setpwent() }; + while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) { + list.push(User::from(unsafe { ptr.as_ref() }).into()); + } + unsafe { libc::endpwent() }; + + list +} diff --git a/crates/host_env/src/resource.rs b/crates/host_env/src/resource.rs new file mode 100644 index 00000000000..e64cfa02b0e --- /dev/null +++ b/crates/host_env/src/resource.rs @@ -0,0 +1,87 @@ +use std::io; + +#[derive(Debug, Clone, Copy)] +pub struct RUsage { + pub ru_utime: libc::timeval, + pub ru_stime: libc::timeval, + pub ru_maxrss: libc::c_long, + pub ru_ixrss: libc::c_long, + pub ru_idrss: libc::c_long, + pub ru_isrss: libc::c_long, + pub ru_minflt: libc::c_long, + pub ru_majflt: libc::c_long, + pub ru_nswap: libc::c_long, + pub ru_inblock: libc::c_long, + pub ru_oublock: libc::c_long, + pub ru_msgsnd: libc::c_long, + pub ru_msgrcv: libc::c_long, + pub ru_nsignals: libc::c_long, + pub ru_nvcsw: libc::c_long, + pub ru_nivcsw: libc::c_long, +} + +impl From for RUsage { + fn from(rusage: libc::rusage) -> Self { + Self { + ru_utime: rusage.ru_utime, + ru_stime: rusage.ru_stime, + ru_maxrss: rusage.ru_maxrss, + ru_ixrss: rusage.ru_ixrss, + ru_idrss: rusage.ru_idrss, + ru_isrss: rusage.ru_isrss, + ru_minflt: rusage.ru_minflt, + ru_majflt: rusage.ru_majflt, + ru_nswap: rusage.ru_nswap, + ru_inblock: rusage.ru_inblock, + ru_oublock: rusage.ru_oublock, + ru_msgsnd: rusage.ru_msgsnd, + ru_msgrcv: rusage.ru_msgrcv, + ru_nsignals: rusage.ru_nsignals, + ru_nvcsw: rusage.ru_nvcsw, + ru_nivcsw: rusage.ru_nivcsw, + } + } +} + +pub fn getrusage(who: i32) -> io::Result { + unsafe { + let mut rusage = core::mem::MaybeUninit::::uninit(); + if libc::getrusage(who, rusage.as_mut_ptr()) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(rusage.assume_init().into()) + } + } +} + +pub fn getrlimit(resource: i32) -> io::Result { + unsafe { + let mut rlimit = core::mem::MaybeUninit::::uninit(); + if libc::getrlimit(resource as _, rlimit.as_mut_ptr()) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(rlimit.assume_init()) + } + } +} + +pub fn setrlimit(resource: i32, limits: libc::rlimit) -> io::Result<()> { + unsafe { + if libc::setrlimit(resource as _, &limits) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } +} + +#[cfg(not(any(target_os = "redox", target_os = "wasi")))] +pub fn disable_core_dumps() { + let rl = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + unsafe { + let _ = libc::setrlimit(libc::RLIMIT_CORE, &rl); + } +} diff --git a/crates/host_env/src/select.rs b/crates/host_env/src/select.rs index e5ddc4284f7..1c89380f0f6 100644 --- a/crates/host_env/src/select.rs +++ b/crates/host_env/src/select.rs @@ -3,6 +3,7 @@ use std::io; #[cfg(unix)] mod platform { + pub use libc::pollfd; pub use libc::{FD_ISSET, FD_SET, FD_SETSIZE, FD_ZERO, fd_set, select, timeval}; pub use std::os::unix::io::RawFd; @@ -83,9 +84,8 @@ mod platform { } } let n = set.__nfds; - assert!(n < set.__fds.len(), "fd_set full"); - set.__fds[n] = fd; set.__nfds = n + 1; + set.__fds[n] = fd; } #[allow(non_snake_case)] @@ -106,6 +106,9 @@ mod platform { pub use platform::{RawFd, timeval}; +#[cfg(unix)] +pub type PollFd = platform::pollfd; + #[repr(transparent)] pub struct FdSet(MaybeUninit); @@ -174,3 +177,89 @@ pub fn sec_to_timeval(sec: f64) -> timeval { tv_usec: (sec.fract() * 1e6) as _, } } + +#[cfg(unix)] +#[inline] +pub fn search_poll_fd(fds: &[PollFd], fd: i32) -> Result { + fds.binary_search_by_key(&fd, |pfd| pfd.fd) +} + +#[cfg(unix)] +pub fn insert_poll_fd(fds: &mut Vec, fd: i32, events: i16) { + match search_poll_fd(fds, fd) { + Ok(i) => fds[i].events = events, + Err(i) => fds.insert( + i, + PollFd { + fd, + events, + revents: 0, + }, + ), + } +} + +#[cfg(unix)] +pub fn get_poll_fd_mut(fds: &mut [PollFd], fd: i32) -> Option<&mut PollFd> { + search_poll_fd(fds, fd).ok().map(move |i| &mut fds[i]) +} + +#[cfg(unix)] +pub fn remove_poll_fd(fds: &mut Vec, fd: i32) -> Option { + search_poll_fd(fds, fd).ok().map(|i| fds.remove(i)) +} + +#[cfg(unix)] +pub fn poll_fds(fds: &mut [PollFd], timeout: i32) -> nix::Result { + let res = unsafe { libc::poll(fds.as_mut_ptr(), fds.len() as _, timeout) }; + nix::Error::result(res) +} + +#[cfg(any(target_os = "linux", target_os = "android", target_os = "redox"))] +pub mod epoll { + use std::os::fd::{AsFd, IntoRawFd, OwnedFd}; + + pub use rustix::event::Timespec; + pub use rustix::event::epoll::{Event, EventData, EventFlags}; + + pub fn create() -> std::io::Result { + rustix::event::epoll::create(rustix::event::epoll::CreateFlags::CLOEXEC).map_err(Into::into) + } + + pub fn close(fd: OwnedFd) -> nix::Result<()> { + nix::unistd::close(fd.into_raw_fd()) + } + + pub fn add(epoll: &OwnedFd, fd: F, data: u64, events: u32) -> std::io::Result<()> { + rustix::event::epoll::add( + epoll, + fd, + EventData::new_u64(data), + EventFlags::from_bits_retain(events), + ) + .map_err(Into::into) + } + + pub fn modify(epoll: &OwnedFd, fd: F, data: u64, events: u32) -> std::io::Result<()> { + rustix::event::epoll::modify( + epoll, + fd, + EventData::new_u64(data), + EventFlags::from_bits_retain(events), + ) + .map_err(Into::into) + } + + pub fn delete(epoll: &OwnedFd, fd: F) -> std::io::Result<()> { + rustix::event::epoll::delete(epoll, fd).map_err(Into::into) + } + + pub fn wait( + epoll: &OwnedFd, + events: &mut Vec, + timeout: Option<&Timespec>, + ) -> rustix::io::Result { + events.clear(); + rustix::event::epoll::wait(epoll, rustix::buffer::spare_capacity(events), timeout) + } +} diff --git a/crates/host_env/src/signal.rs b/crates/host_env/src/signal.rs index 13a7206c68a..d6d82a995e6 100644 --- a/crates/host_env/src/signal.rs +++ b/crates/host_env/src/signal.rs @@ -1,3 +1,10 @@ +use std::io; +#[cfg(windows)] +use std::sync::Once; + +#[cfg(any(unix, windows))] +pub use libc::sighandler_t; + pub fn timeval_to_double(tv: &libc::timeval) -> f64 { tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0) } @@ -15,3 +22,296 @@ pub fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { timeval_to_double(&it.it_interval), ) } + +#[cfg(all(unix, not(target_os = "redox")))] +unsafe extern "C" { + #[link_name = "siginterrupt"] + fn c_siginterrupt(sig: i32, flag: i32) -> i32; +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod ffi { + unsafe extern "C" { + pub fn getitimer(which: libc::c_int, curr_value: *mut libc::itimerval) -> libc::c_int; + pub fn setitimer( + which: libc::c_int, + new_value: *const libc::itimerval, + old_value: *mut libc::itimerval, + ) -> libc::c_int; + } +} + +#[cfg(any(unix, windows))] +/// # Safety +/// +/// The caller must ensure `signalnum` is a valid platform signal number. +pub unsafe fn probe_handler(signalnum: i32) -> Option { + let handler = unsafe { libc::signal(signalnum, libc::SIG_IGN) }; + if handler == libc::SIG_ERR { + None + } else { + unsafe { libc::signal(signalnum, handler) }; + Some(handler) + } +} + +#[cfg(any(unix, windows))] +/// # Safety +/// +/// The caller must ensure `signalnum` is a valid platform signal number and +/// `handler` is accepted by the platform signal ABI. +pub unsafe fn install_handler(signalnum: i32, handler: sighandler_t) -> io::Result { + let old = unsafe { libc::signal(signalnum, handler) }; + if old == libc::SIG_ERR { + return Err(io::Error::last_os_error()); + } + #[cfg(all(unix, not(target_os = "redox")))] + let _ = siginterrupt(signalnum, 1); + Ok(old) +} + +#[cfg(any(unix, windows))] +pub fn raise_signal(signalnum: i32) -> io::Result<()> { + let res = unsafe { libc::raise(signalnum) }; + if res != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn setitimer(which: i32, new: &libc::itimerval) -> io::Result { + let mut old = core::mem::MaybeUninit::::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::setitimer(which, new, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::setitimer(which, new, old.as_mut_ptr()) }; + if ret != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { old.assume_init() }) + } +} + +#[cfg(unix)] +pub fn getitimer(which: i32) -> io::Result { + let mut old = core::mem::MaybeUninit::::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::getitimer(which, old.as_mut_ptr()) }; + if ret != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { old.assume_init() }) + } +} + +#[cfg(unix)] +pub fn sigemptyset() -> io::Result { + let mut set: libc::sigset_t = unsafe { core::mem::zeroed() }; + if unsafe { libc::sigemptyset(&mut set) } != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(set) + } +} + +#[cfg(unix)] +pub fn sigaddset(set: &mut libc::sigset_t, signum: i32) -> io::Result<()> { + if unsafe { libc::sigaddset(set, signum) } != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(unix)] +pub fn pthread_sigmask(how: i32, set: &libc::sigset_t) -> io::Result { + let mut old_mask: libc::sigset_t = unsafe { core::mem::zeroed() }; + let err = unsafe { libc::pthread_sigmask(how, set, &mut old_mask) }; + if err != 0 { + Err(io::Error::from_raw_os_error(err)) + } else { + Ok(old_mask) + } +} + +#[cfg(target_os = "linux")] +pub fn pidfd_send_signal(pidfd: i32, sig: i32, flags: u32) -> io::Result<()> { + let ret = unsafe { + libc::syscall( + libc::SYS_pidfd_send_signal, + pidfd, + sig, + core::ptr::null::(), + flags, + ) as libc::c_long + }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(all(unix, not(target_os = "redox")))] +pub fn siginterrupt(signalnum: i32, flag: i32) -> io::Result<()> { + let res = unsafe { c_siginterrupt(signalnum, flag) }; + if res < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +#[cfg(windows)] +pub const VALID_SIGNALS: &[i32] = &[ + libc::SIGINT, + libc::SIGILL, + libc::SIGFPE, + libc::SIGSEGV, + libc::SIGTERM, + 21, // SIGBREAK / _SIGBREAK + libc::SIGABRT, +]; + +#[cfg(windows)] +pub fn is_valid_signal(signalnum: i32) -> bool { + VALID_SIGNALS.contains(&signalnum) +} + +#[cfg(windows)] +fn init_winsock() { + static WSA_INIT: Once = Once::new(); + WSA_INIT.call_once(|| unsafe { + let mut wsa_data = core::mem::MaybeUninit::uninit(); + let _ = windows_sys::Win32::Networking::WinSock::WSAStartup(0x0101, wsa_data.as_mut_ptr()); + }); +} + +#[cfg(windows)] +pub fn wakeup_fd_is_socket(fd: libc::SOCKET) -> io::Result { + use windows_sys::Win32::Networking::WinSock; + + init_winsock(); + let mut res = 0i32; + let mut res_size = core::mem::size_of::() as i32; + let getsockopt_res = unsafe { + WinSock::getsockopt( + fd, + WinSock::SOL_SOCKET, + WinSock::SO_ERROR, + &mut res as *mut i32 as *mut _, + &mut res_size, + ) + }; + if getsockopt_res == 0 { + return Ok(true); + } + + let err = io::Error::last_os_error(); + if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) { + return Err(err); + } + + let fd_i32 = + i32::try_from(fd).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid fd"))?; + let borrowed = unsafe { crate::crt_fd::Borrowed::try_borrow_raw(fd_i32) }?; + crate::fileutils::fstat(borrowed)?; + Ok(false) +} + +#[cfg(windows)] +pub fn notify_signal( + signum: i32, + wakeup_fd: libc::SOCKET, + wakeup_is_socket: bool, + sigint_event: Option, +) { + if signum == libc::SIGINT + && let Some(handle) = sigint_event + { + unsafe { + windows_sys::Win32::System::Threading::SetEvent(handle as _); + } + } + + if wakeup_fd == windows_sys::Win32::Networking::WinSock::INVALID_SOCKET { + return; + } + + let sigbyte = signum as u8; + if wakeup_is_socket { + unsafe { + let _ = windows_sys::Win32::Networking::WinSock::send( + wakeup_fd, + &sigbyte as *const u8 as *const _, + 1, + 0, + ); + } + } else { + unsafe { + let _ = libc::write(wakeup_fd as _, &sigbyte as *const u8 as *const _, 1); + } + } +} + +#[cfg(unix)] +pub fn notify_signal(signum: i32, wakeup_fd: i32) { + if wakeup_fd == -1 { + return; + } + let sigbyte = signum as u8; + unsafe { + let _ = libc::write(wakeup_fd, &sigbyte as *const u8 as *const _, 1); + } +} + +#[cfg(unix)] +pub fn strsignal(signalnum: i32) -> Option { + let s = unsafe { libc::strsignal(signalnum) }; + if s.is_null() { + None + } else { + let cstr = unsafe { core::ffi::CStr::from_ptr(s) }; + Some(cstr.to_string_lossy().into_owned()) + } +} + +#[cfg(windows)] +pub fn strsignal(signalnum: i32) -> Option { + let name = match signalnum { + libc::SIGINT => "Interrupt", + libc::SIGILL => "Illegal instruction", + libc::SIGFPE => "Floating-point exception", + libc::SIGSEGV => "Segmentation fault", + libc::SIGTERM => "Terminated", + 21 => "Break", + libc::SIGABRT => "Aborted", + _ => return None, + }; + Some(name.to_owned()) +} + +#[cfg(unix)] +pub fn valid_signals(max_signum: usize) -> io::Result> { + let mut mask: libc::sigset_t = unsafe { core::mem::zeroed() }; + if unsafe { libc::sigfillset(&mut mask) } != 0 { + return Err(io::Error::last_os_error()); + } + let mut signals = Vec::new(); + for signum in 1..max_signum { + if unsafe { libc::sigismember(&mask, signum as i32) } == 1 { + signals.push(signum as i32); + } + } + Ok(signals) +} + +#[cfg(windows)] +pub fn valid_signals(_max_signum: usize) -> io::Result> { + Ok(VALID_SIGNALS.to_vec()) +} diff --git a/crates/host_env/src/syslog.rs b/crates/host_env/src/syslog.rs index 67c10f6f21a..4ada868cfcf 100644 --- a/crates/host_env/src/syslog.rs +++ b/crates/host_env/src/syslog.rs @@ -1,9 +1,7 @@ use alloc::boxed::Box; use core::ffi::CStr; -use std::{ - os::raw::c_char, - sync::{OnceLock, RwLock}, -}; +use parking_lot::RwLock; +use std::{os::raw::c_char, sync::OnceLock}; #[derive(Debug)] enum GlobalIdent { @@ -26,10 +24,7 @@ fn global_ident() -> &'static RwLock> { } pub fn is_open() -> bool { - global_ident() - .read() - .expect("syslog lock poisoned") - .is_some() + global_ident().read().is_some() } pub fn openlog(ident: Option>, logoption: i32, facility: i32) { @@ -37,7 +32,7 @@ pub fn openlog(ident: Option>, logoption: i32, facility: i32) { Some(ident) => GlobalIdent::Explicit(ident), None => GlobalIdent::Implicit, }; - let mut locked_ident = global_ident().write().expect("syslog lock poisoned"); + let mut locked_ident = global_ident().write(); unsafe { libc::openlog(ident.as_ptr(), logoption, facility) }; *locked_ident = Some(ident); } @@ -49,7 +44,7 @@ pub fn syslog(priority: i32, msg: &CStr) { pub fn closelog() { if is_open() { - let mut locked_ident = global_ident().write().expect("syslog lock poisoned"); + let mut locked_ident = global_ident().write(); unsafe { libc::closelog() }; *locked_ident = None; } diff --git a/crates/host_env/src/testconsole.rs b/crates/host_env/src/testconsole.rs new file mode 100644 index 00000000000..3377a497fa2 --- /dev/null +++ b/crates/host_env/src/testconsole.rs @@ -0,0 +1,42 @@ +use std::io; +use windows_sys::Win32::{ + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + System::Console::{INPUT_RECORD, KEY_EVENT, WriteConsoleInputW}, +}; + +pub fn write_console_input(fd: i32, data: &[u16]) -> io::Result<()> { + let handle = unsafe { libc::get_osfhandle(fd) } as HANDLE; + if handle == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let size = data.len() as u32; + let mut records: Vec = Vec::with_capacity(data.len()); + for &wc in data { + let mut rec: INPUT_RECORD = unsafe { core::mem::zeroed() }; + rec.EventType = KEY_EVENT as u16; + rec.Event.KeyEvent.bKeyDown = 1; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.uChar.UnicodeChar = wc; + records.push(rec); + } + + let mut total: u32 = 0; + while total < size { + let mut wrote: u32 = 0; + let res = unsafe { + WriteConsoleInputW( + handle, + records[total as usize..].as_ptr(), + size - total, + &mut wrote, + ) + }; + if res == 0 { + return Err(io::Error::last_os_error()); + } + total += wrote; + } + + Ok(()) +} diff --git a/crates/host_env/src/time.rs b/crates/host_env/src/time.rs index b643d9c2e39..bc55e6dea70 100644 --- a/crates/host_env/src/time.rs +++ b/crates/host_env/src/time.rs @@ -22,6 +22,91 @@ pub fn get_tz_info() -> windows_sys::Win32::System::Time::TIME_ZONE_INFORMATION info } +#[cfg(windows)] +fn u64_from_filetime(time: windows_sys::Win32::Foundation::FILETIME) -> u64 { + u64::from(time.dwLowDateTime) | (u64::from(time.dwHighDateTime) << 32) +} + +#[cfg(windows)] +pub fn query_performance_frequency() -> Option { + let mut freq = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::Performance::QueryPerformanceFrequency(freq.as_mut_ptr()) + } != 0) + .then(|| unsafe { freq.assume_init() }) +} + +#[cfg(windows)] +pub fn query_performance_counter() -> i64 { + let mut counter = core::mem::MaybeUninit::uninit(); + unsafe { + windows_sys::Win32::System::Performance::QueryPerformanceCounter(counter.as_mut_ptr()); + counter.assume_init() + } +} + +#[cfg(windows)] +pub fn get_system_time_adjustment() -> Option { + let mut time_adjustment = core::mem::MaybeUninit::uninit(); + let mut time_increment = core::mem::MaybeUninit::uninit(); + let mut is_time_adjustment_disabled = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::SystemInformation::GetSystemTimeAdjustment( + time_adjustment.as_mut_ptr(), + time_increment.as_mut_ptr(), + is_time_adjustment_disabled.as_mut_ptr(), + ) + } != 0) + .then(|| unsafe { time_increment.assume_init() }) +} + +#[cfg(windows)] +pub fn tick_count64() -> u64 { + unsafe { windows_sys::Win32::System::SystemInformation::GetTickCount64() } +} + +#[cfg(windows)] +pub fn get_thread_time_100ns() -> Option { + let mut creation_time = core::mem::MaybeUninit::uninit(); + let mut exit_time = core::mem::MaybeUninit::uninit(); + let mut kernel_time = core::mem::MaybeUninit::uninit(); + let mut user_time = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::Threading::GetThreadTimes( + windows_sys::Win32::System::Threading::GetCurrentThread(), + creation_time.as_mut_ptr(), + exit_time.as_mut_ptr(), + kernel_time.as_mut_ptr(), + user_time.as_mut_ptr(), + ) + } != 0) + .then(|| unsafe { + u64_from_filetime(kernel_time.assume_init()) + + u64_from_filetime(user_time.assume_init()) + }) +} + +#[cfg(windows)] +pub fn get_process_time_100ns() -> Option { + let mut creation_time = core::mem::MaybeUninit::uninit(); + let mut exit_time = core::mem::MaybeUninit::uninit(); + let mut kernel_time = core::mem::MaybeUninit::uninit(); + let mut user_time = core::mem::MaybeUninit::uninit(); + (unsafe { + windows_sys::Win32::System::Threading::GetProcessTimes( + windows_sys::Win32::System::Threading::GetCurrentProcess(), + creation_time.as_mut_ptr(), + exit_time.as_mut_ptr(), + kernel_time.as_mut_ptr(), + user_time.as_mut_ptr(), + ) + } != 0) + .then(|| unsafe { + u64_from_filetime(kernel_time.assume_init()) + + u64_from_filetime(user_time.assume_init()) + }) +} + #[cfg(any(unix, windows))] pub fn asctime_from_tm(tm: &libc::tm) -> String { const WDAY_NAME: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; diff --git a/crates/host_env/src/winapi.rs b/crates/host_env/src/winapi.rs index 26f46969315..29f02eceec0 100644 --- a/crates/host_env/src/winapi.rs +++ b/crates/host_env/src/winapi.rs @@ -1,13 +1,659 @@ -use windows_sys::Win32::Foundation::HANDLE; +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "This module mirrors Win32 APIs with raw handle and pointer parameters." +)] + +use std::{io, path::Path}; +use windows_sys::Win32::{ + Foundation::{HANDLE, HMODULE, WAIT_FAILED, WAIT_OBJECT_0}, + System::Threading::PROCESS_INFORMATION, +}; + +pub struct PeekNamedPipeResult { + pub data: Option>, + pub available: u32, + pub left_this_message: u32, +} + +pub struct ReadFileResult { + pub data: Vec, + pub error: u32, +} + +pub struct WriteFileResult { + pub written: u32, + pub error: u32, +} + +pub enum BatchedWaitResult { + All, + Indices(Vec), +} + +pub enum BatchedWaitError { + Timeout, + Interrupted, + Os(u32), +} + +pub enum BuildEnvironmentBlockError { + ContainsNul, + IllegalName, +} + +pub enum MimeRegistryReadError { + Os(u32), + Callback(E), +} + +pub struct AttrList { + handlelist: Vec, + attrlist: Vec, +} pub fn get_acp() -> u32 { unsafe { windows_sys::Win32::Globalization::GetACP() } } +pub fn close_handle(handle: HANDLE) -> i32 { + unsafe { windows_sys::Win32::Foundation::CloseHandle(handle) } +} + +impl AttrList { + pub fn as_mut_ptr(&mut self) -> *mut core::ffi::c_void { + self.attrlist.as_mut_ptr().cast() + } +} + +impl Drop for AttrList { + fn drop(&mut self) { + unsafe { + windows_sys::Win32::System::Threading::DeleteProcThreadAttributeList( + self.attrlist.as_mut_ptr().cast(), + ) + }; + } +} + +pub fn create_file_w( + file_name: *const u16, + desired_access: u32, + share_mode: u32, + creation_disposition: u32, + flags_and_attributes: u32, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::Storage::FileSystem::CreateFileW( + file_name, + desired_access, + share_mode, + core::ptr::null(), + creation_disposition, + flags_and_attributes, + core::ptr::null_mut(), + ) + }; + if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +/// # Safety +/// The pointer arguments must follow the Win32 `CreateProcessW` contract. +pub unsafe fn create_process_w( + app_name: *const u16, + command_line: *mut u16, + inherit_handles: i32, + creation_flags: u32, + env: *mut u16, + current_dir: *mut u16, + startup_info: *mut windows_sys::Win32::System::Threading::STARTUPINFOW, +) -> io::Result { + let mut procinfo = core::mem::MaybeUninit::::uninit(); + let ok = unsafe { + windows_sys::Win32::System::Threading::CreateProcessW( + app_name, + command_line, + core::ptr::null(), + core::ptr::null(), + inherit_handles, + creation_flags, + env.cast(), + current_dir, + startup_info, + procinfo.as_mut_ptr(), + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { procinfo.assume_init() }) + } +} + +pub fn create_junction(src: &Path, dst: &Path) -> io::Result<()> { + junction::create(src, dst) +} + +pub fn build_environment_block( + entries: Vec<(String, String)>, +) -> Result, BuildEnvironmentBlockError> { + use std::collections::HashMap; + + let mut last_entry: HashMap> = HashMap::new(); + for (key, value) in entries { + if key.contains('\0') || value.contains('\0') { + return Err(BuildEnvironmentBlockError::ContainsNul); + } + if key.is_empty() || key[1..].contains('=') { + return Err(BuildEnvironmentBlockError::IllegalName); + } + + let key_upper = key.to_uppercase(); + let mut entry: Vec = key.encode_utf16().collect(); + entry.push(b'=' as u16); + entry.extend(value.encode_utf16()); + entry.push(0); + last_entry.insert(key_upper, entry); + } + + let mut entries: Vec<(String, Vec)> = last_entry.into_iter().collect(); + entries.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut out = Vec::new(); + for (_, entry) in entries { + out.extend(entry); + } + if out.is_empty() { + out.push(0); + } + out.push(0); + Ok(out) +} + +pub fn create_handle_list_attribute_list( + handlelist: Option>, +) -> io::Result> { + let Some(handlelist) = handlelist else { + return Ok(None); + }; + + let mut size = 0; + let first = unsafe { + windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( + core::ptr::null_mut(), + 1, + 0, + &mut size, + ) + }; + if first != 0 + || unsafe { windows_sys::Win32::Foundation::GetLastError() } + != windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER + { + return Err(io::Error::last_os_error()); + } + + let mut attrs = AttrList { + handlelist, + attrlist: vec![0u8; size], + }; + let ok = unsafe { + windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( + attrs.attrlist.as_mut_ptr().cast(), + 1, + 0, + &mut size, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + + let ok = unsafe { + windows_sys::Win32::System::Threading::UpdateProcThreadAttribute( + attrs.attrlist.as_mut_ptr().cast(), + 0, + (2 & 0xffff) | 0x20000, + attrs.handlelist.as_mut_ptr().cast(), + (attrs.handlelist.len() * core::mem::size_of::()) as _, + core::ptr::null_mut(), + core::ptr::null(), + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + + Ok(Some(attrs)) +} + +pub fn get_std_handle( + std_handle: windows_sys::Win32::System::Console::STD_HANDLE, +) -> io::Result> { + let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; + if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else if handle.is_null() { + Ok(None) + } else { + Ok(Some(handle)) + } +} + +pub fn open_process( + desired_access: u32, + inherit_handle: bool, + process_id: u32, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::OpenProcess( + desired_access, + i32::from(inherit_handle), + process_id, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn create_pipe(size: u32) -> io::Result<(HANDLE, HANDLE)> { + let (read, write) = unsafe { + let mut read = core::mem::MaybeUninit::::uninit(); + let mut write = core::mem::MaybeUninit::::uninit(); + let ok = windows_sys::Win32::System::Pipes::CreatePipe( + read.as_mut_ptr(), + write.as_mut_ptr(), + core::ptr::null(), + size, + ); + if ok == 0 { + return Err(io::Error::last_os_error()); + } + (read.assume_init(), write.assume_init()) + }; + Ok((read, write)) +} + +pub fn create_event_w( + manual_reset: bool, + initial_state: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::CreateEventW( + core::ptr::null(), + i32::from(manual_reset), + i32::from(initial_state), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn set_event(handle: HANDLE) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::Threading::SetEvent(handle) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn reset_event(handle: HANDLE) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::Threading::ResetEvent(handle) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn wait_for_single_object(handle: HANDLE, milliseconds: u32) -> io::Result { + let ret = + unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(handle, milliseconds) }; + if ret == WAIT_FAILED { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn wait_for_multiple_objects( + handles: &[HANDLE], + wait_all: bool, + milliseconds: u32, +) -> io::Result { + let ret = unsafe { + windows_sys::Win32::System::Threading::WaitForMultipleObjects( + handles.len() as u32, + handles.as_ptr(), + i32::from(wait_all), + milliseconds, + ) + }; + if ret == WAIT_FAILED { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} + +pub fn batched_wait_for_multiple_objects( + handles: &[HANDLE], + wait_all: bool, + milliseconds: u32, + sigint_event: Option, +) -> Result { + use alloc::sync::Arc; + use core::sync::atomic::{AtomicU32, Ordering}; + use windows_sys::Win32::{ + Foundation::{CloseHandle, WAIT_ABANDONED_0}, + System::{ + SystemInformation::GetTickCount64, + Threading::{ + CreateThread, GetExitCodeThread, INFINITE, ResumeThread, TerminateThread, + WaitForMultipleObjects, + }, + }, + }; + + const MAXIMUM_WAIT_OBJECTS: usize = 64; + let batch_size = MAXIMUM_WAIT_OBJECTS - 1; + let mut batches: Vec<&[HANDLE]> = Vec::new(); + let mut i = 0; + while i < handles.len() { + let end = core::cmp::min(i + batch_size, handles.len()); + batches.push(&handles[i..end]); + i = end; + } + + if wait_all { + let mut err = None; + let deadline = if milliseconds != INFINITE { + Some(unsafe { GetTickCount64() } + milliseconds as u64) + } else { + None + }; + + for batch in &batches { + let timeout = if let Some(deadline) = deadline { + let now = unsafe { GetTickCount64() }; + if now >= deadline { + err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); + break; + } + (deadline - now) as u32 + } else { + INFINITE + }; + + let result = + unsafe { WaitForMultipleObjects(batch.len() as u32, batch.as_ptr(), 1, timeout) }; + if result == WAIT_FAILED { + err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + break; + } + if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); + break; + } + + if let Some(sigint_event) = sigint_event { + let sig_result = unsafe { + windows_sys::Win32::System::Threading::WaitForSingleObject(sigint_event, 0) + }; + if sig_result == WAIT_OBJECT_0 { + err = Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT); + break; + } + if sig_result == WAIT_FAILED { + err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + break; + } + } + } + + return match err { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) => Err(BatchedWaitError::Timeout), + Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) => { + Err(BatchedWaitError::Interrupted) + } + Some(err) => Err(BatchedWaitError::Os(err)), + None => Ok(BatchedWaitResult::All), + }; + } + + let cancel_event = create_event_w(true, false, core::ptr::null()) + .map_err(|err| BatchedWaitError::Os(err.raw_os_error().unwrap_or_default() as u32))?; + + struct BatchData { + handles: Vec, + cancel_event: HANDLE, + handle_base: usize, + result: AtomicU32, + thread: core::cell::UnsafeCell, + } + + unsafe impl Send for BatchData {} + unsafe impl Sync for BatchData {} + + extern "system" fn batch_wait_thread(param: *mut core::ffi::c_void) -> u32 { + let data = unsafe { &*(param as *const BatchData) }; + let result = unsafe { + windows_sys::Win32::System::Threading::WaitForMultipleObjects( + data.handles.len() as u32, + data.handles.as_ptr(), + 0, + windows_sys::Win32::System::Threading::INFINITE, + ) + }; + data.result.store(result, Ordering::SeqCst); + + if result == WAIT_FAILED { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + let _ = set_event(data.cancel_event); + err + } else if (WAIT_ABANDONED_0..WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS as u32) + .contains(&result) + { + data.result.store(WAIT_FAILED, Ordering::SeqCst); + let _ = set_event(data.cancel_event); + windows_sys::Win32::Foundation::ERROR_ABANDONED_WAIT_0 + } else { + 0 + } + } + + let batch_data: Vec> = batches + .iter() + .enumerate() + .map(|(idx, batch)| { + let base = idx * batch_size; + let mut handles_with_cancel = batch.to_vec(); + handles_with_cancel.push(cancel_event); + Arc::new(BatchData { + handles: handles_with_cancel, + cancel_event, + handle_base: base, + result: AtomicU32::new(WAIT_FAILED), + thread: core::cell::UnsafeCell::new(core::ptr::null_mut()), + }) + }) + .collect(); + + let mut thread_handles: Vec = Vec::new(); + for data in &batch_data { + let thread = unsafe { + CreateThread( + core::ptr::null(), + 1, + Some(batch_wait_thread), + Arc::as_ptr(data) as *const _ as *mut _, + 4, + core::ptr::null_mut(), + ) + }; + if thread.is_null() { + for &handle in &thread_handles { + unsafe { TerminateThread(handle, 0) }; + unsafe { CloseHandle(handle) }; + } + unsafe { CloseHandle(cancel_event) }; + return Err(BatchedWaitError::Os( + io::Error::last_os_error() + .raw_os_error() + .unwrap_or_default() as u32, + )); + } + unsafe { *data.thread.get() = thread }; + thread_handles.push(thread); + } + + for &thread in &thread_handles { + unsafe { ResumeThread(thread) }; + } + + let mut thread_handles_raw = thread_handles.clone(); + if let Some(sigint_event) = sigint_event { + thread_handles_raw.push(sigint_event); + } + let result = unsafe { + WaitForMultipleObjects( + thread_handles_raw.len() as u32, + thread_handles_raw.as_ptr(), + 0, + milliseconds, + ) + }; + + let err = if result == WAIT_FAILED { + Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }) + } else if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) + } else if sigint_event.is_some() && result == WAIT_OBJECT_0 + thread_handles_raw.len() as u32 { + Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) + } else { + None + }; + + let _ = set_event(cancel_event); + unsafe { + WaitForMultipleObjects( + thread_handles.len() as u32, + thread_handles.as_ptr(), + 1, + INFINITE, + ) + }; + + let mut thread_err = err; + for data in &batch_data { + if thread_err.is_none() && data.result.load(Ordering::SeqCst) == WAIT_FAILED { + let mut exit_code = 0; + let thread = unsafe { *data.thread.get() }; + if unsafe { GetExitCodeThread(thread, &mut exit_code) } == 0 { + thread_err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); + } else if exit_code != 0 { + thread_err = Some(exit_code); + } + } + let thread = unsafe { *data.thread.get() }; + unsafe { CloseHandle(thread) }; + } + unsafe { CloseHandle(cancel_event) }; + + match thread_err { + Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) => Err(BatchedWaitError::Timeout), + Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) => { + Err(BatchedWaitError::Interrupted) + } + Some(err) => Err(BatchedWaitError::Os(err)), + None => { + let mut triggered_indices = Vec::new(); + for data in &batch_data { + let result = data.result.load(Ordering::SeqCst); + let triggered = result as i32 - WAIT_OBJECT_0 as i32; + if triggered >= 0 && (triggered as usize) < data.handles.len() - 1 { + triggered_indices.push(data.handle_base + triggered as usize); + } + } + Ok(BatchedWaitResult::Indices(triggered_indices)) + } + } +} + +pub fn duplicate_handle( + src_process: HANDLE, + src: HANDLE, + target_process: HANDLE, + access: u32, + inherit: i32, + options: u32, +) -> io::Result { + let target = unsafe { + let mut target = core::mem::MaybeUninit::::uninit(); + let ok = windows_sys::Win32::Foundation::DuplicateHandle( + src_process, + src, + target_process, + target.as_mut_ptr(), + access, + inherit, + options, + ); + if ok == 0 { + return Err(io::Error::last_os_error()); + } + target.assume_init() + }; + Ok(target) +} + pub fn get_current_process() -> HANDLE { unsafe { windows_sys::Win32::System::Threading::GetCurrentProcess() } } +pub fn get_exit_code_process(handle: HANDLE) -> io::Result { + let mut exit_code = core::mem::MaybeUninit::::uninit(); + let ok = unsafe { + windows_sys::Win32::System::Threading::GetExitCodeProcess(handle, exit_code.as_mut_ptr()) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(unsafe { exit_code.assume_init() }) + } +} + +pub fn get_file_type( + handle: HANDLE, +) -> io::Result { + let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(handle) }; + if file_type == 0 && unsafe { windows_sys::Win32::Foundation::GetLastError() } != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(file_type) + } +} + +pub fn terminate_process(handle: HANDLE, exit_code: u32) -> i32 { + unsafe { windows_sys::Win32::System::Threading::TerminateProcess(handle, exit_code) } +} + +pub fn exit_process(exit_code: u32) -> ! { + unsafe { windows_sys::Win32::System::Threading::ExitProcess(exit_code) } +} + pub fn get_last_error() -> u32 { unsafe { windows_sys::Win32::Foundation::GetLastError() } } @@ -15,3 +661,604 @@ pub fn get_last_error() -> u32 { pub fn get_version() -> u32 { unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() } } + +pub fn create_job_object_w(name: *const u16) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::JobObjects::CreateJobObjectW(core::ptr::null(), name) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn assign_process_to_job_object(job: HANDLE, process: HANDLE) -> io::Result<()> { + let ok = + unsafe { windows_sys::Win32::System::JobObjects::AssignProcessToJobObject(job, process) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn terminate_job_object(job: HANDLE, exit_code: u32) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::JobObjects::TerminateJobObject(job, exit_code) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn set_job_object_kill_on_close(job: HANDLE) -> io::Result<()> { + use windows_sys::Win32::System::JobObjects::{ + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, + JobObjectExtendedLimitInformation, SetInformationJobObject, + }; + + let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { core::mem::zeroed() }; + info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + let ok = unsafe { + SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + (&info as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(), + core::mem::size_of::() as u32, + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn get_module_file_name(module: HMODULE, buffer: &mut [u16]) -> u32 { + unsafe { + windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW( + module, + buffer.as_mut_ptr(), + buffer.len() as u32, + ) + } +} + +pub fn get_short_path_name_w(path: *const u16) -> io::Result> { + get_path_name_impl( + path, + windows_sys::Win32::Storage::FileSystem::GetShortPathNameW, + ) +} + +pub fn get_long_path_name_w(path: *const u16) -> io::Result> { + get_path_name_impl( + path, + windows_sys::Win32::Storage::FileSystem::GetLongPathNameW, + ) +} + +fn get_path_name_impl( + path: *const u16, + api_fn: unsafe extern "system" fn(*const u16, *mut u16, u32) -> u32, +) -> io::Result> { + let size = unsafe { api_fn(path, core::ptr::null_mut(), 0) }; + if size == 0 { + return Err(io::Error::last_os_error()); + } + + let mut buffer = vec![0u16; size as usize]; + let result = unsafe { api_fn(path, buffer.as_mut_ptr(), buffer.len() as u32) }; + if result == 0 { + return Err(io::Error::last_os_error()); + } + buffer.truncate(result as usize); + Ok(buffer) +} + +pub fn open_mutex_w( + desired_access: u32, + inherit_handle: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::OpenMutexW( + desired_access, + i32::from(inherit_handle), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn release_mutex(handle: HANDLE) -> i32 { + unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle) } +} + +pub fn create_named_pipe_w( + name: *const u16, + open_mode: u32, + pipe_mode: u32, + max_instances: u32, + out_buffer_size: u32, + in_buffer_size: u32, + default_timeout: u32, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Pipes::CreateNamedPipeW( + name, + open_mode, + pipe_mode, + max_instances, + out_buffer_size, + in_buffer_size, + default_timeout, + core::ptr::null(), + ) + }; + if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn create_file_mapping_w( + file_handle: HANDLE, + protect: u32, + max_size_high: u32, + max_size_low: u32, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Memory::CreateFileMappingW( + file_handle, + core::ptr::null(), + protect, + max_size_high, + max_size_low, + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn open_file_mapping_w( + desired_access: u32, + inherit_handle: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Memory::OpenFileMappingW( + desired_access, + i32::from(inherit_handle), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn map_view_of_file( + file_map: HANDLE, + desired_access: u32, + file_offset_high: u32, + file_offset_low: u32, + number_bytes: usize, +) -> io::Result { + let address = unsafe { + windows_sys::Win32::System::Memory::MapViewOfFile( + file_map, + desired_access, + file_offset_high, + file_offset_low, + number_bytes, + ) + }; + let ptr = address.Value; + if ptr.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(ptr as isize) + } +} + +pub fn unmap_view_of_file(address: isize) -> io::Result<()> { + let view = windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS { + Value: address as *mut core::ffi::c_void, + }; + let ok = unsafe { windows_sys::Win32::System::Memory::UnmapViewOfFile(view) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn virtual_query_size(address: isize) -> io::Result { + let mut mbi: windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION = + unsafe { core::mem::zeroed() }; + let ret = unsafe { + windows_sys::Win32::System::Memory::VirtualQuery( + address as *const core::ffi::c_void, + &mut mbi, + core::mem::size_of::(), + ) + }; + if ret == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(mbi.RegionSize) + } +} + +pub fn copy_file2(src: *const u16, dst: *const u16, flags: u32) -> io::Result<()> { + let mut params: windows_sys::Win32::Storage::FileSystem::COPYFILE2_EXTENDED_PARAMETERS = + unsafe { core::mem::zeroed() }; + params.dwSize = core::mem::size_of_val(¶ms) as u32; + params.dwCopyFlags = flags; + + let hr = unsafe { windows_sys::Win32::Storage::FileSystem::CopyFile2(src, dst, ¶ms) }; + if hr < 0 { + let err = if (hr as u32 >> 16) == 0x8007 { + (hr as u32) & 0xFFFF + } else { + hr as u32 + }; + Err(io::Error::from_raw_os_error(err as i32)) + } else { + Ok(()) + } +} + +pub fn read_windows_mimetype_registry_in_batches( + mut on_entries: F, +) -> Result<(), MimeRegistryReadError> +where + F: FnMut(&mut Vec<(String, String)>) -> Result<(), E>, +{ + use windows_sys::Win32::System::Registry::{ + HKEY, HKEY_CLASSES_ROOT, KEY_READ, REG_SZ, RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, + RegQueryValueExW, + }; + + let mut hkcr: HKEY = core::ptr::null_mut(); + let err = + unsafe { RegOpenKeyExW(HKEY_CLASSES_ROOT, core::ptr::null(), 0, KEY_READ, &mut hkcr) }; + if err != 0 { + return Err(MimeRegistryReadError::Os(err)); + } + + let mut index = 0; + let mut entries = Vec::new(); + loop { + let mut ext_buf = [0u16; 128]; + let mut cch_ext = ext_buf.len() as u32; + let err = unsafe { + RegEnumKeyExW( + hkcr, + index, + ext_buf.as_mut_ptr(), + &mut cch_ext, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + }; + index += 1; + + if err == windows_sys::Win32::Foundation::ERROR_NO_MORE_ITEMS { + break; + } + if err != 0 && err != windows_sys::Win32::Foundation::ERROR_MORE_DATA { + unsafe { RegCloseKey(hkcr) }; + return Err(MimeRegistryReadError::Os(err)); + } + if cch_ext == 0 || ext_buf[0] != b'.' as u16 { + continue; + } + + let ext_wide = &ext_buf[..cch_ext as usize]; + let mut subkey: HKEY = core::ptr::null_mut(); + let err = unsafe { RegOpenKeyExW(hkcr, ext_buf.as_ptr(), 0, KEY_READ, &mut subkey) }; + if err == windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND + || err == windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED + { + continue; + } + if err != 0 { + unsafe { RegCloseKey(hkcr) }; + return Err(MimeRegistryReadError::Os(err)); + } + + let content_type_key: Vec = "Content Type\0".encode_utf16().collect(); + let mut type_buf = [0u16; 256]; + let mut cb_type = (type_buf.len() * 2) as u32; + let mut reg_type = 0; + let err = unsafe { + RegQueryValueExW( + subkey, + content_type_key.as_ptr(), + core::ptr::null_mut(), + &mut reg_type, + type_buf.as_mut_ptr().cast(), + &mut cb_type, + ) + }; + unsafe { RegCloseKey(subkey) }; + + if err != 0 || reg_type != REG_SZ || cb_type == 0 { + continue; + } + + let type_len = (cb_type as usize / 2).saturating_sub(1); + let type_str = String::from_utf16_lossy(&type_buf[..type_len]); + let ext_str = String::from_utf16_lossy(ext_wide); + if type_str.is_empty() { + continue; + } + + entries.push((type_str, ext_str)); + if entries.len() >= 64 { + on_entries(&mut entries).map_err(MimeRegistryReadError::Callback)?; + } + } + + unsafe { RegCloseKey(hkcr) }; + if !entries.is_empty() { + on_entries(&mut entries).map_err(MimeRegistryReadError::Callback)?; + } + Ok(()) +} + +pub fn lc_map_string_ex( + locale: *const u16, + flags: u32, + src: *const u16, + src_len: i32, +) -> io::Result> { + let dest_size = unsafe { + windows_sys::Win32::Globalization::LCMapStringEx( + locale, + flags, + src, + src_len, + core::ptr::null_mut(), + 0, + core::ptr::null(), + core::ptr::null(), + 0, + ) + }; + if dest_size <= 0 { + return Err(io::Error::last_os_error()); + } + + let mut dest = vec![0u16; dest_size as usize]; + let nmapped = unsafe { + windows_sys::Win32::Globalization::LCMapStringEx( + locale, + flags, + src, + src_len, + dest.as_mut_ptr(), + dest_size, + core::ptr::null(), + core::ptr::null(), + 0, + ) + }; + if nmapped <= 0 { + return Err(io::Error::last_os_error()); + } + dest.truncate(nmapped as usize); + Ok(dest) +} + +pub fn connect_named_pipe(handle: HANDLE) -> io::Result<()> { + let ret = unsafe { + windows_sys::Win32::System::Pipes::ConnectNamedPipe(handle, core::ptr::null_mut()) + }; + if ret == 0 { + let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; + if err != windows_sys::Win32::Foundation::ERROR_PIPE_CONNECTED { + return Err(io::Error::from_raw_os_error(err as i32)); + } + } + Ok(()) +} + +pub fn wait_named_pipe_w(name: *const u16, timeout: u32) -> io::Result<()> { + let ok = unsafe { windows_sys::Win32::System::Pipes::WaitNamedPipeW(name, timeout) }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn peek_named_pipe(handle: HANDLE, size: Option) -> io::Result { + let mut available = 0; + let mut left_this_message = 0; + match size { + Some(size) => { + let mut data = vec![0u8; size as usize]; + let mut read = 0; + let ok = unsafe { + windows_sys::Win32::System::Pipes::PeekNamedPipe( + handle, + data.as_mut_ptr().cast(), + size, + &mut read, + &mut available, + &mut left_this_message, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + data.truncate(read as usize); + Ok(PeekNamedPipeResult { + data: Some(data), + available, + left_this_message, + }) + } + None => { + let ok = unsafe { + windows_sys::Win32::System::Pipes::PeekNamedPipe( + handle, + core::ptr::null_mut(), + 0, + core::ptr::null_mut(), + &mut available, + &mut left_this_message, + ) + }; + if ok == 0 { + return Err(io::Error::last_os_error()); + } + Ok(PeekNamedPipeResult { + data: None, + available, + left_this_message, + }) + } + } +} + +pub fn write_file(handle: HANDLE, buffer: &[u8]) -> io::Result { + let len = core::cmp::min(buffer.len(), u32::MAX as usize) as u32; + let mut written = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::WriteFile( + handle, + buffer.as_ptr().cast(), + len, + &mut written, + core::ptr::null_mut(), + ) + }; + let err = if ret == 0 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } else { + 0 + }; + if ret == 0 { + Err(io::Error::from_raw_os_error(err as i32)) + } else { + Ok(WriteFileResult { + written, + error: err, + }) + } +} + +pub fn read_file(handle: HANDLE, size: u32) -> io::Result { + let mut data = vec![0u8; size as usize]; + let mut read = 0; + let ret = unsafe { + windows_sys::Win32::Storage::FileSystem::ReadFile( + handle, + data.as_mut_ptr().cast(), + size, + &mut read, + core::ptr::null_mut(), + ) + }; + let err = if ret == 0 { + unsafe { windows_sys::Win32::Foundation::GetLastError() } + } else { + 0 + }; + if ret == 0 && err != windows_sys::Win32::Foundation::ERROR_MORE_DATA { + return Err(io::Error::from_raw_os_error(err as i32)); + } + data.truncate(read as usize); + Ok(ReadFileResult { data, error: err }) +} + +pub fn set_named_pipe_handle_state( + handle: HANDLE, + mode: Option, + max_collection_count: Option, + collect_data_timeout: Option, +) -> io::Result<()> { + let mut dw_args = [ + mode.unwrap_or_default(), + max_collection_count.unwrap_or_default(), + collect_data_timeout.unwrap_or_default(), + ]; + let mut p_args = [core::ptr::null_mut(); 3]; + for (index, arg) in [mode, max_collection_count, collect_data_timeout] + .into_iter() + .enumerate() + { + if arg.is_some() { + p_args[index] = &mut dw_args[index]; + } + } + let ok = unsafe { + windows_sys::Win32::System::Pipes::SetNamedPipeHandleState( + handle, p_args[0], p_args[1], p_args[2], + ) + }; + if ok == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + +pub fn create_mutex_w(initial_owner: bool, name: *const u16) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::CreateMutexW( + core::ptr::null(), + i32::from(initial_owner), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn open_event_w( + desired_access: u32, + inherit_handle: bool, + name: *const u16, +) -> io::Result { + let handle = unsafe { + windows_sys::Win32::System::Threading::OpenEventW( + desired_access, + i32::from(inherit_handle), + name, + ) + }; + if handle.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(handle) + } +} + +pub fn need_current_directory_for_exe_path_w(exe_name: *const u16) -> bool { + unsafe { + windows_sys::Win32::System::Environment::NeedCurrentDirectoryForExePathW(exe_name) != 0 + } +} diff --git a/crates/host_env/src/winreg.rs b/crates/host_env/src/winreg.rs new file mode 100644 index 00000000000..2336c1001cc --- /dev/null +++ b/crates/host_env/src/winreg.rs @@ -0,0 +1,452 @@ +#![allow( + clippy::missing_safety_doc, + reason = "This module intentionally exposes raw Win32 registry wrappers." +)] +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "These wrappers mirror Win32 APIs that operate on caller-provided pointers." +)] +#![allow( + clippy::too_many_arguments, + reason = "These helpers preserve the underlying Win32 registry call shapes." +)] + +extern crate alloc; + +use alloc::string::FromUtf16Error; +use std::ffi::OsStr; + +use crate::windows::ToWideString; +use windows_sys::Win32::{ + Foundation, + Security::SECURITY_ATTRIBUTES, + System::{Environment, Registry}, +}; + +pub fn bytes_as_wide_slice(bytes: &[u8]) -> &[u16] { + let (prefix, u16_slice, suffix) = unsafe { bytes.align_to::() }; + debug_assert!( + prefix.is_empty() && suffix.is_empty(), + "Registry data should be u16-aligned" + ); + u16_slice +} + +pub fn close_key(hkey: Registry::HKEY) -> u32 { + unsafe { Registry::RegCloseKey(hkey) } +} + +pub unsafe fn connect_registry( + computer_name: *const u16, + key: Registry::HKEY, + out_key: *mut Registry::HKEY, +) -> u32 { + unsafe { Registry::RegConnectRegistryW(computer_name, key, out_key) } +} + +pub unsafe fn create_key( + key: Registry::HKEY, + sub_key: *const u16, + out_key: *mut Registry::HKEY, +) -> u32 { + unsafe { Registry::RegCreateKeyW(key, sub_key, out_key) } +} + +pub unsafe fn create_key_ex( + key: Registry::HKEY, + sub_key: *const u16, + reserved: u32, + class: *mut u16, + options: u32, + sam: u32, + security: *const SECURITY_ATTRIBUTES, + result: *mut Registry::HKEY, + disposition: *mut u32, +) -> u32 { + unsafe { + Registry::RegCreateKeyExW( + key, + sub_key, + reserved, + class, + options, + sam, + security, + result, + disposition, + ) + } +} + +pub unsafe fn delete_key(key: Registry::HKEY, sub_key: *const u16) -> u32 { + unsafe { Registry::RegDeleteKeyW(key, sub_key) } +} + +pub unsafe fn delete_key_ex( + key: Registry::HKEY, + sub_key: *const u16, + sam: u32, + reserved: u32, +) -> u32 { + unsafe { Registry::RegDeleteKeyExW(key, sub_key, sam, reserved) } +} + +pub unsafe fn delete_value(key: Registry::HKEY, value_name: *const u16) -> u32 { + unsafe { Registry::RegDeleteValueW(key, value_name) } +} + +pub unsafe fn enum_key_ex( + key: Registry::HKEY, + index: u32, + name: *mut u16, + name_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegEnumKeyExW( + key, + index, + name, + name_len, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + } +} + +pub unsafe fn query_info_key( + key: Registry::HKEY, + sub_keys: *mut u32, + values: *mut u32, + max_value_name_len: *mut u32, + max_value_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegQueryInfoKeyW( + key, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + sub_keys, + core::ptr::null_mut(), + core::ptr::null_mut(), + values, + max_value_name_len, + max_value_len, + core::ptr::null_mut(), + core::ptr::null_mut(), + ) + } +} + +pub struct QueryInfo { + pub sub_keys: u32, + pub values: u32, + pub last_write_time: u64, +} + +pub fn query_info_key_full(key: Registry::HKEY) -> Result { + let mut sub_keys = 0; + let mut values = 0; + let mut last_write_time: Foundation::FILETIME = unsafe { core::mem::zeroed() }; + let err = unsafe { + Registry::RegQueryInfoKeyW( + key, + core::ptr::null_mut(), + core::ptr::null_mut(), + 0 as _, + &mut sub_keys, + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut values, + core::ptr::null_mut(), + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut last_write_time, + ) + }; + if err != 0 { + return Err(err); + } + Ok(QueryInfo { + sub_keys, + values, + last_write_time: ((last_write_time.dwHighDateTime as u64) << 32) + | last_write_time.dwLowDateTime as u64, + }) +} + +pub unsafe fn enum_value( + key: Registry::HKEY, + index: u32, + value_name: *mut u16, + value_name_len: *mut u32, + value_type: *mut u32, + data: *mut u8, + data_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegEnumValueW( + key, + index, + value_name, + value_name_len, + core::ptr::null_mut(), + value_type, + data, + data_len, + ) + } +} + +pub fn flush_key(key: Registry::HKEY) -> u32 { + unsafe { Registry::RegFlushKey(key) } +} + +pub unsafe fn load_key(key: Registry::HKEY, sub_key: *const u16, file_name: *const u16) -> u32 { + unsafe { Registry::RegLoadKeyW(key, sub_key, file_name) } +} + +pub unsafe fn open_key_ex( + key: Registry::HKEY, + sub_key: *const u16, + options: u32, + sam: u32, + out_key: *mut Registry::HKEY, +) -> u32 { + unsafe { Registry::RegOpenKeyExW(key, sub_key, options, sam, out_key) } +} + +pub unsafe fn query_value_ex( + key: Registry::HKEY, + value_name: *const u16, + value_type: *mut u32, + data: *mut u8, + data_len: *mut u32, +) -> u32 { + unsafe { + Registry::RegQueryValueExW( + key, + value_name, + core::ptr::null_mut(), + value_type, + data, + data_len, + ) + } +} + +pub unsafe fn save_key(key: Registry::HKEY, file_name: *const u16) -> u32 { + unsafe { Registry::RegSaveKeyW(key, file_name, core::ptr::null_mut()) } +} + +pub unsafe fn set_value_ex( + key: Registry::HKEY, + value_name: *const u16, + typ: u32, + ptr: *const u8, + len: u32, +) -> u32 { + unsafe { Registry::RegSetValueExW(key, value_name, 0, typ, ptr, len) } +} + +pub fn disable_reflection_key(key: Registry::HKEY) -> u32 { + unsafe { Registry::RegDisableReflectionKey(key) } +} + +pub fn enable_reflection_key(key: Registry::HKEY) -> u32 { + unsafe { Registry::RegEnableReflectionKey(key) } +} + +pub unsafe fn query_reflection_key(key: Registry::HKEY, result: *mut i32) -> u32 { + unsafe { Registry::RegQueryReflectionKey(key, result) } +} + +pub enum ExpandEnvironmentStringsError { + Os, + Utf16(FromUtf16Error), +} + +pub enum QueryStringError { + Code(u32), + Utf16(FromUtf16Error), +} + +pub fn query_default_value( + hkey: Registry::HKEY, + sub_key: Option<&OsStr>, +) -> Result { + let child_key = if let Some(sub_key) = sub_key.filter(|s| !s.is_empty()) { + let wide_sub_key = sub_key.to_wide_with_nul(); + let mut out_key = core::ptr::null_mut(); + let res = unsafe { + open_key_ex( + hkey, + wide_sub_key.as_ptr(), + 0, + Registry::KEY_QUERY_VALUE, + &mut out_key, + ) + }; + if res != 0 { + return Err(QueryStringError::Code(res)); + } + Some(out_key) + } else { + None + }; + + let target_key = child_key.unwrap_or(hkey); + let mut buf_size: u32 = 256; + let mut buffer: Vec = vec![0; buf_size as usize]; + let mut reg_type: u32 = 0; + + let result = loop { + let mut size = buf_size; + let res = unsafe { + query_value_ex( + target_key, + core::ptr::null(), + &mut reg_type, + buffer.as_mut_ptr(), + &mut size, + ) + }; + if res == Foundation::ERROR_MORE_DATA { + buf_size *= 2; + buffer.resize(buf_size as usize, 0); + continue; + } + if res == Foundation::ERROR_FILE_NOT_FOUND { + break Ok(String::new()); + } + if res != 0 { + break Err(QueryStringError::Code(res)); + } + if reg_type != Registry::REG_SZ { + break Err(QueryStringError::Code(Foundation::ERROR_INVALID_DATA)); + } + + let u16_slice = bytes_as_wide_slice(&buffer[..size as usize]); + let len = u16_slice + .iter() + .position(|&c| c == 0) + .unwrap_or(u16_slice.len()); + break String::from_utf16(&u16_slice[..len]).map_err(QueryStringError::Utf16); + }; + + if let Some(ck) = child_key { + close_key(ck); + } + + result +} + +pub fn query_value_bytes(hkey: Registry::HKEY, value_name: &OsStr) -> Result<(Vec, u32), u32> { + let wide_name = value_name.to_wide_with_nul(); + let mut buf_size: u32 = 0; + let res = unsafe { + query_value_ex( + hkey, + wide_name.as_ptr(), + core::ptr::null_mut(), + core::ptr::null_mut(), + &mut buf_size, + ) + }; + if res == Foundation::ERROR_MORE_DATA || buf_size == 0 { + buf_size = 256; + } else if res != 0 { + return Err(res); + } + + let mut ret_buf = vec![0u8; buf_size as usize]; + let mut typ = 0; + + loop { + let mut ret_size = buf_size; + let res = unsafe { + query_value_ex( + hkey, + wide_name.as_ptr(), + &mut typ, + ret_buf.as_mut_ptr(), + &mut ret_size, + ) + }; + if res != Foundation::ERROR_MORE_DATA { + if res != 0 { + return Err(res); + } + ret_buf.truncate(ret_size as usize); + return Ok((ret_buf, typ)); + } + buf_size *= 2; + ret_buf.resize(buf_size as usize, 0); + } +} + +pub fn set_default_value(hkey: Registry::HKEY, sub_key: &OsStr, typ: u32, value: &OsStr) -> u32 { + let child_key = if !sub_key.is_empty() { + let wide_sub_key = sub_key.to_wide_with_nul(); + let mut out_key = core::ptr::null_mut(); + let res = unsafe { + create_key_ex( + hkey, + wide_sub_key.as_ptr(), + 0, + core::ptr::null_mut(), + 0, + Registry::KEY_SET_VALUE, + core::ptr::null(), + &mut out_key, + core::ptr::null_mut(), + ) + }; + if res != 0 { + return res; + } + Some(out_key) + } else { + None + }; + + let target_key = child_key.unwrap_or(hkey); + let wide_value = value.to_wide_with_nul(); + let res = unsafe { + set_value_ex( + target_key, + core::ptr::null(), + typ, + wide_value.as_ptr() as *const u8, + (wide_value.len() * 2) as u32, + ) + }; + + if let Some(ck) = child_key { + close_key(ck); + } + res +} + +pub fn expand_environment_strings(input: &OsStr) -> Result { + let wide_input = input.to_wide_with_nul(); + let required_size = unsafe { + Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), core::ptr::null_mut(), 0) + }; + if required_size == 0 { + return Err(ExpandEnvironmentStringsError::Os); + } + + let mut out = vec![0u16; required_size as usize]; + let written = unsafe { + Environment::ExpandEnvironmentStringsW(wide_input.as_ptr(), out.as_mut_ptr(), required_size) + }; + if written == 0 { + return Err(ExpandEnvironmentStringsError::Os); + } + + let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); + String::from_utf16(&out[..len]).map_err(ExpandEnvironmentStringsError::Utf16) +} diff --git a/crates/host_env/src/wmi.rs b/crates/host_env/src/wmi.rs new file mode 100644 index 00000000000..1165a756306 --- /dev/null +++ b/crates/host_env/src/wmi.rs @@ -0,0 +1,676 @@ +#![allow( + clippy::upper_case_acronyms, + reason = "These names mirror the Windows COM and ABI types they wrap." +)] +#![allow(non_snake_case)] +#![allow(unsafe_op_in_unsafe_fn)] + +use core::ffi::c_void; +use core::ptr::{null, null_mut}; +use windows_sys::Win32::Foundation::{ + CloseHandle, ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_NOT_ENOUGH_MEMORY, GetLastError, HANDLE, + WAIT_OBJECT_0, WAIT_TIMEOUT, +}; +use windows_sys::Win32::Storage::FileSystem::{ReadFile, WriteFile}; +use windows_sys::Win32::System::Pipes::CreatePipe; +use windows_sys::Win32::System::Threading::{ + CreateEventW, CreateThread, GetExitCodeThread, SetEvent, WaitForSingleObject, +}; + +const BUFFER_SIZE: usize = 8192; + +pub enum ExecQueryError { + MoreData, + Code(u32), +} + +type HRESULT = i32; + +#[repr(C)] +struct GUID { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], +} + +#[repr(C, align(8))] +struct VARIANT([u64; 3]); + +impl VARIANT { + fn zeroed() -> Self { + Self([0u64; 3]) + } +} + +const CLSID_WBEM_LOCATOR: GUID = GUID { + data1: 0x4590F811, + data2: 0x1D3A, + data3: 0x11D0, + data4: [0x89, 0x1F, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], +}; + +const IID_IWBEM_LOCATOR: GUID = GUID { + data1: 0xDC12A687, + data2: 0x737F, + data3: 0x11CF, + data4: [0x88, 0x4D, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], +}; + +const COINIT_APARTMENTTHREADED: u32 = 0x2; +const CLSCTX_INPROC_SERVER: u32 = 0x1; +const RPC_C_AUTHN_LEVEL_DEFAULT: u32 = 0; +const RPC_C_IMP_LEVEL_IMPERSONATE: u32 = 3; +const RPC_C_AUTHN_LEVEL_CALL: u32 = 3; +const RPC_C_AUTHN_WINNT: u32 = 10; +const RPC_C_AUTHZ_NONE: u32 = 0; +const EOAC_NONE: u32 = 0; +const RPC_E_TOO_LATE: HRESULT = 0x80010119_u32 as i32; +const WBEM_FLAG_FORWARD_ONLY: i32 = 0x20; +const WBEM_FLAG_RETURN_IMMEDIATELY: i32 = 0x10; +const WBEM_S_FALSE: HRESULT = 1; +const WBEM_S_NO_MORE_DATA: HRESULT = 0x40005; +const WBEM_INFINITE: i32 = -1; +const WBEM_FLAVOR_MASK_ORIGIN: i32 = 0x60; +const WBEM_FLAVOR_ORIGIN_SYSTEM: i32 = 0x40; + +#[link(name = "ole32")] +unsafe extern "system" { + fn CoInitializeEx(pvReserved: *mut c_void, dwCoInit: u32) -> HRESULT; + fn CoUninitialize(); + fn CoInitializeSecurity( + pSecDesc: *const c_void, + cAuthSvc: i32, + asAuthSvc: *const c_void, + pReserved1: *const c_void, + dwAuthnLevel: u32, + dwImpLevel: u32, + pAuthList: *const c_void, + dwCapabilities: u32, + pReserved3: *const c_void, + ) -> HRESULT; + fn CoCreateInstance( + rclsid: *const GUID, + pUnkOuter: *mut c_void, + dwClsContext: u32, + riid: *const GUID, + ppv: *mut *mut c_void, + ) -> HRESULT; + fn CoSetProxyBlanket( + pProxy: *mut c_void, + dwAuthnSvc: u32, + dwAuthzSvc: u32, + pServerPrincName: *const u16, + dwAuthnLevel: u32, + dwImpLevel: u32, + pAuthInfo: *const c_void, + dwCapabilities: u32, + ) -> HRESULT; +} + +#[link(name = "oleaut32")] +unsafe extern "system" { + fn SysAllocString(psz: *const u16) -> *mut u16; + fn SysFreeString(bstrString: *mut u16); + fn VariantClear(pvarg: *mut VARIANT) -> HRESULT; +} + +#[link(name = "propsys")] +unsafe extern "system" { + fn VariantToString(varIn: *const VARIANT, pszBuf: *mut u16, cchBuf: u32) -> HRESULT; +} + +unsafe fn com_release(this: *mut c_void) { + if !this.is_null() { + let vtable = *(this as *const *const usize); + let release: unsafe extern "system" fn(*mut c_void) -> u32 = + core::mem::transmute(*vtable.add(2)); + release(this); + } +} + +#[allow(clippy::too_many_arguments)] +unsafe fn locator_connect_server( + this: *mut c_void, + network_resource: *const u16, + user: *const u16, + password: *const u16, + locale: *const u16, + security_flags: i32, + authority: *const u16, + ctx: *mut c_void, + services: *mut *mut c_void, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + *const u16, + *const u16, + *const u16, + *const u16, + i32, + *const u16, + *mut c_void, + *mut *mut c_void, + ) -> HRESULT = core::mem::transmute(*vtable.add(3)); + method( + this, + network_resource, + user, + password, + locale, + security_flags, + authority, + ctx, + services, + ) +} + +unsafe fn services_exec_query( + this: *mut c_void, + query_language: *const u16, + query: *const u16, + flags: i32, + ctx: *mut c_void, + enumerator: *mut *mut c_void, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + *const u16, + *const u16, + i32, + *mut c_void, + *mut *mut c_void, + ) -> HRESULT = core::mem::transmute(*vtable.add(20)); + method(this, query_language, query, flags, ctx, enumerator) +} + +unsafe fn enum_next( + this: *mut c_void, + timeout: i32, + count: u32, + objects: *mut *mut c_void, + returned: *mut u32, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + i32, + u32, + *mut *mut c_void, + *mut u32, + ) -> HRESULT = core::mem::transmute(*vtable.add(4)); + method(this, timeout, count, objects, returned) +} + +unsafe fn object_begin_enumeration(this: *mut c_void, enum_flags: i32) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn(*mut c_void, i32) -> HRESULT = + core::mem::transmute(*vtable.add(8)); + method(this, enum_flags) +} + +unsafe fn object_next( + this: *mut c_void, + flags: i32, + name: *mut *mut u16, + val: *mut VARIANT, + cim_type: *mut i32, + flavor: *mut i32, +) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn( + *mut c_void, + i32, + *mut *mut u16, + *mut VARIANT, + *mut i32, + *mut i32, + ) -> HRESULT = core::mem::transmute(*vtable.add(9)); + method(this, flags, name, val, cim_type, flavor) +} + +unsafe fn object_end_enumeration(this: *mut c_void) -> HRESULT { + let vtable = *(this as *const *const usize); + let method: unsafe extern "system" fn(*mut c_void) -> HRESULT = + core::mem::transmute(*vtable.add(10)); + method(this) +} + +fn hresult_from_win32(err: u32) -> HRESULT { + if err == 0 { + 0 + } else { + ((err & 0xFFFF) | 0x80070000) as HRESULT + } +} + +fn succeeded(hr: HRESULT) -> bool { + hr >= 0 +} + +fn failed(hr: HRESULT) -> bool { + hr < 0 +} + +fn wide_str(s: &str) -> Vec { + s.encode_utf16().chain(core::iter::once(0)).collect() +} + +unsafe fn wcslen(s: *const u16) -> usize { + let mut len = 0; + while unsafe { *s.add(len) } != 0 { + len += 1; + } + len +} + +unsafe fn wait_event(event: HANDLE, timeout: u32) -> u32 { + match unsafe { WaitForSingleObject(event, timeout) } { + WAIT_OBJECT_0 => 0, + WAIT_TIMEOUT => WAIT_TIMEOUT, + _ => unsafe { GetLastError() }, + } +} + +struct QueryThreadData { + query: Vec, + write_pipe: HANDLE, + init_event: HANDLE, + connect_event: HANDLE, +} + +unsafe impl Send for QueryThreadData {} + +unsafe extern "system" fn query_thread(param: *mut c_void) -> u32 { + unsafe { query_thread_impl(param) } +} + +unsafe fn query_thread_impl(param: *mut c_void) -> u32 { + let data = unsafe { Box::from_raw(param as *mut QueryThreadData) }; + let write_pipe = data.write_pipe; + let init_event = data.init_event; + let connect_event = data.connect_event; + + let mut locator: *mut c_void = null_mut(); + let mut services: *mut c_void = null_mut(); + let mut enumerator: *mut c_void = null_mut(); + let mut hr: HRESULT = 0; + + let bstr_query = unsafe { SysAllocString(data.query.as_ptr()) }; + if bstr_query.is_null() { + hr = hresult_from_win32(ERROR_NOT_ENOUGH_MEMORY); + } + + drop(data); + + if succeeded(hr) { + hr = unsafe { CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED) }; + } + + if failed(hr) { + unsafe { + CloseHandle(write_pipe); + if !bstr_query.is_null() { + SysFreeString(bstr_query); + } + } + return hr as u32; + } + + hr = unsafe { + CoInitializeSecurity( + null(), + -1, + null(), + null(), + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + null(), + EOAC_NONE, + null(), + ) + }; + if hr == RPC_E_TOO_LATE { + hr = 0; + } + + if succeeded(hr) { + hr = unsafe { + CoCreateInstance( + &CLSID_WBEM_LOCATOR, + null_mut(), + CLSCTX_INPROC_SERVER, + &IID_IWBEM_LOCATOR, + &mut locator, + ) + }; + } + if succeeded(hr) && unsafe { SetEvent(init_event) } == 0 { + hr = hresult_from_win32(unsafe { GetLastError() }); + } + + if succeeded(hr) { + let root_cimv2 = wide_str("ROOT\\CIMV2"); + let bstr_root = unsafe { SysAllocString(root_cimv2.as_ptr()) }; + hr = unsafe { + locator_connect_server( + locator, + bstr_root, + null(), + null(), + null(), + 0, + null(), + null_mut(), + &mut services, + ) + }; + if !bstr_root.is_null() { + unsafe { SysFreeString(bstr_root) }; + } + } + if succeeded(hr) && unsafe { SetEvent(connect_event) } == 0 { + hr = hresult_from_win32(unsafe { GetLastError() }); + } + + if succeeded(hr) { + hr = unsafe { + CoSetProxyBlanket( + services, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + null(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + null(), + EOAC_NONE, + ) + }; + } + if succeeded(hr) { + let wql = wide_str("WQL"); + let bstr_wql = unsafe { SysAllocString(wql.as_ptr()) }; + hr = unsafe { + services_exec_query( + services, + bstr_wql, + bstr_query, + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + null_mut(), + &mut enumerator, + ) + }; + if !bstr_wql.is_null() { + unsafe { SysFreeString(bstr_wql) }; + } + } + + let mut value: *mut c_void; + let mut start_of_enum = true; + let null_sep: u16 = 0; + let eq_sign: u16 = b'=' as u16; + + while succeeded(hr) { + let mut got: u32 = 0; + let mut written: u32 = 0; + value = null_mut(); + hr = unsafe { enum_next(enumerator, WBEM_INFINITE, 1, &mut value, &mut got) }; + + if hr == WBEM_S_FALSE { + hr = 0; + break; + } + if failed(hr) || got != 1 || value.is_null() { + continue; + } + + if !start_of_enum + && unsafe { + WriteFile( + write_pipe, + &null_sep as *const u16 as *const _, + 2, + &mut written, + null_mut(), + ) + } == 0 + { + hr = hresult_from_win32(unsafe { GetLastError() }); + unsafe { com_release(value) }; + break; + } + start_of_enum = false; + + hr = unsafe { object_begin_enumeration(value, 0) }; + if failed(hr) { + unsafe { com_release(value) }; + break; + } + + while succeeded(hr) { + let mut prop_name: *mut u16 = null_mut(); + let mut prop_value = VARIANT::zeroed(); + let mut flavor: i32 = 0; + + hr = unsafe { + object_next( + value, + 0, + &mut prop_name, + &mut prop_value, + null_mut(), + &mut flavor, + ) + }; + + if hr == WBEM_S_NO_MORE_DATA { + hr = 0; + break; + } + + if succeeded(hr) && (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM { + let mut prop_str = [0u16; BUFFER_SIZE]; + hr = unsafe { + VariantToString(&prop_value, prop_str.as_mut_ptr(), BUFFER_SIZE as u32) + }; + + if succeeded(hr) { + let cb_str1 = (unsafe { wcslen(prop_name) } * 2) as u32; + let cb_str2 = (unsafe { wcslen(prop_str.as_ptr()) } * 2) as u32; + + if unsafe { + WriteFile( + write_pipe, + prop_name as *const _, + cb_str1, + &mut written, + null_mut(), + ) + } == 0 + || unsafe { + WriteFile( + write_pipe, + &eq_sign as *const u16 as *const _, + 2, + &mut written, + null_mut(), + ) + } == 0 + || unsafe { + WriteFile( + write_pipe, + prop_str.as_ptr() as *const _, + cb_str2, + &mut written, + null_mut(), + ) + } == 0 + || unsafe { + WriteFile( + write_pipe, + &null_sep as *const u16 as *const _, + 2, + &mut written, + null_mut(), + ) + } == 0 + { + hr = hresult_from_win32(unsafe { GetLastError() }); + } + } + + unsafe { + VariantClear(&mut prop_value); + SysFreeString(prop_name); + } + } + } + + unsafe { + object_end_enumeration(value); + com_release(value); + } + } + + unsafe { + if !bstr_query.is_null() { + SysFreeString(bstr_query); + } + if !enumerator.is_null() { + com_release(enumerator); + } + if !services.is_null() { + com_release(services); + } + if !locator.is_null() { + com_release(locator); + } + CoUninitialize(); + CloseHandle(write_pipe); + } + + hr as u32 +} + +pub fn exec_query(query_str: &str) -> Result { + let query_wide = wide_str(query_str); + + let mut h_thread: HANDLE = null_mut(); + let mut err: u32 = 0; + let mut buffer = [0u16; BUFFER_SIZE]; + let mut offset: u32 = 0; + let mut bytes_read: u32 = 0; + + let mut read_pipe: HANDLE = null_mut(); + let mut write_pipe: HANDLE = null_mut(); + + unsafe { + let init_event = CreateEventW(null(), 1, 0, null()); + let connect_event = CreateEventW(null(), 1, 0, null()); + + if init_event.is_null() + || connect_event.is_null() + || CreatePipe(&mut read_pipe, &mut write_pipe, null(), 0) == 0 + { + err = GetLastError(); + } else { + let thread_data = Box::new(QueryThreadData { + query: query_wide, + write_pipe, + init_event, + connect_event, + }); + let thread_data_ptr = Box::into_raw(thread_data); + + h_thread = CreateThread( + null(), + 0, + Some(query_thread), + thread_data_ptr as *const _ as *mut _, + 0, + null_mut(), + ); + + if h_thread.is_null() { + err = GetLastError(); + let data = Box::from_raw(thread_data_ptr); + CloseHandle(data.write_pipe); + } + } + + if err == 0 { + err = wait_event(init_event, 1000); + if err == 0 { + err = wait_event(connect_event, 100); + } + } + + while err == 0 { + let buf_ptr = (buffer.as_mut_ptr() as *mut u8).add(offset as usize); + let buf_remaining = (BUFFER_SIZE * 2) as u32 - offset; + + if ReadFile( + read_pipe, + buf_ptr as *mut _, + buf_remaining, + &mut bytes_read, + null_mut(), + ) != 0 + { + offset += bytes_read; + if offset >= (BUFFER_SIZE * 2) as u32 { + err = ERROR_MORE_DATA; + } + } else { + err = GetLastError(); + } + } + + if !read_pipe.is_null() { + CloseHandle(read_pipe); + } + + if !h_thread.is_null() { + let thread_err: u32; + match WaitForSingleObject(h_thread, 100) { + WAIT_OBJECT_0 => { + let mut exit_code: u32 = 0; + if GetExitCodeThread(h_thread, &mut exit_code) == 0 { + thread_err = GetLastError(); + } else { + thread_err = exit_code; + } + } + WAIT_TIMEOUT => { + thread_err = WAIT_TIMEOUT; + } + _ => { + thread_err = GetLastError(); + } + } + if err == 0 || err == ERROR_BROKEN_PIPE { + err = thread_err; + } + + CloseHandle(h_thread); + } + + CloseHandle(init_event); + CloseHandle(connect_event); + } + + if err == ERROR_MORE_DATA { + return Err(ExecQueryError::MoreData); + } + if err != 0 { + return Err(ExecQueryError::Code(err)); + } + if offset == 0 { + return Ok(String::new()); + } + + let char_count = (offset as usize) / 2 - 1; + Ok(String::from_utf16_lossy(&buffer[..char_count])) +} diff --git a/crates/stdlib/src/_testconsole.rs b/crates/stdlib/src/_testconsole.rs index 0db508e3da5..78cba3b397d 100644 --- a/crates/stdlib/src/_testconsole.rs +++ b/crates/stdlib/src/_testconsole.rs @@ -5,23 +5,14 @@ mod _testconsole { use crate::vm::{ PyObjectRef, PyResult, VirtualMachine, convert::IntoPyException, function::ArgBytesLike, }; - use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; - - type Handle = windows_sys::Win32::Foundation::HANDLE; + use rustpython_host_env::testconsole as host_testconsole; #[pyfunction] fn write_input(file: PyObjectRef, s: ArgBytesLike, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Console::{INPUT_RECORD, KEY_EVENT, WriteConsoleInputW}; - // Get the fd from the file object via fileno() let fd_obj = vm.call_method(&file, "fileno", ())?; let fd: i32 = fd_obj.try_into_value(vm)?; - let handle = unsafe { libc::get_osfhandle(fd) } as Handle; - if handle == INVALID_HANDLE_VALUE { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - let data = s.borrow_buf(); let data = &*data; @@ -33,39 +24,7 @@ mod _testconsole { .chunks_exact(2) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) .collect(); - - let size = wchars.len() as u32; - - // Create INPUT_RECORD array - let mut records: Vec = Vec::with_capacity(wchars.len()); - for &wc in &wchars { - // SAFETY: zeroing and accessing the union field for KEY_EVENT - let mut rec: INPUT_RECORD = unsafe { core::mem::zeroed() }; - rec.EventType = KEY_EVENT as u16; - rec.Event.KeyEvent.bKeyDown = 1; // TRUE - rec.Event.KeyEvent.wRepeatCount = 1; - rec.Event.KeyEvent.uChar.UnicodeChar = wc; - records.push(rec); - } - - let mut total: u32 = 0; - while total < size { - let mut wrote: u32 = 0; - let res = unsafe { - WriteConsoleInputW( - handle, - records[total as usize..].as_ptr(), - size - total, - &mut wrote, - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - total += wrote; - } - - Ok(()) + host_testconsole::write_console_input(fd, &wchars).map_err(|e| e.into_pyexception(vm)) } #[pyfunction] diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index e562a382345..c5673f61ad0 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -13,6 +13,8 @@ mod decl { use core::time::Duration; use parking_lot::{Condvar, Mutex}; #[cfg(any(unix, windows))] + use rustpython_host_env::faulthandler as host_faulthandler; + #[cfg(any(unix, windows))] use rustpython_host_env::os::{get_errno, set_errno}; use std::thread; @@ -119,30 +121,12 @@ mod decl { // PUTS macro #[cfg(any(unix, windows))] fn puts(fd: i32, s: &str) { - let _ = unsafe { - #[cfg(windows)] - { - libc::write(fd, s.as_ptr() as *const libc::c_void, s.len() as u32) - } - #[cfg(not(windows))] - { - libc::write(fd, s.as_ptr() as *const libc::c_void, s.len()) - } - }; + host_faulthandler::write_fd(fd, s.as_bytes()); } #[cfg(any(unix, windows))] fn puts_bytes(fd: i32, s: &[u8]) { - let _ = unsafe { - #[cfg(windows)] - { - libc::write(fd, s.as_ptr() as *const libc::c_void, s.len() as u32) - } - #[cfg(not(windows))] - { - libc::write(fd, s.as_ptr() as *const libc::c_void, s.len()) - } - }; + host_faulthandler::write_fd(fd, s); } // _Py_DumpHexadecimal (traceback.c) @@ -158,16 +142,7 @@ mod decl { buf[2 + i] = HEX_CHARS[digit]; } - let _ = unsafe { - #[cfg(windows)] - { - libc::write(fd, buf.as_ptr() as *const libc::c_void, (2 + width) as u32) - } - #[cfg(not(windows))] - { - libc::write(fd, buf.as_ptr() as *const libc::c_void, 2 + width) - } - }; + host_faulthandler::write_fd(fd, &buf[..2 + width]); } // _Py_DumpDecimal (traceback.c) @@ -188,28 +163,13 @@ mod decl { v /= 10; } - let len = buf.len() - i; - let _ = unsafe { - #[cfg(windows)] - { - libc::write(fd, buf[i..].as_ptr() as *const libc::c_void, len as u32) - } - #[cfg(not(windows))] - { - libc::write(fd, buf[i..].as_ptr() as *const libc::c_void, len) - } - }; + host_faulthandler::write_fd(fd, &buf[i..]); } /// Get current thread ID - #[cfg(unix)] - fn current_thread_id() -> u64 { - unsafe { libc::pthread_self() as u64 } - } - - #[cfg(windows)] + #[cfg(any(unix, windows))] fn current_thread_id() -> u64 { - unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() as u64 } + host_faulthandler::current_thread_id() } // write_thread_id (traceback.c:1240-1256) @@ -478,9 +438,7 @@ mod decl { return; } handler.enabled = false; - unsafe { - libc::sigaction(handler.signum, &handler.previous, core::ptr::null_mut()); - } + host_faulthandler::restore_sigaction(handler.signum, &handler.previous); } #[cfg(windows)] @@ -489,9 +447,7 @@ mod decl { return; } handler.enabled = false; - unsafe { - libc::signal(handler.signum, handler.previous); - } + host_faulthandler::restore_signal_handler(handler.signum, handler.previous); } // faulthandler_fatal_error @@ -535,15 +491,10 @@ mod decl { // We cannot just restore the previous handler because Rust's runtime // may have installed its own SIGSEGV handler (for stack overflow detection) // that doesn't terminate the process on software-raised signals. - unsafe { - libc::signal(signum, libc::SIG_DFL); - libc::raise(signum); - } + host_faulthandler::signal_default_and_raise(signum); // Fallback if raise() somehow didn't terminate the process - unsafe { - libc::_exit(1); - } + host_faulthandler::exit_immediately(1); } // faulthandler_fatal_error for Windows @@ -581,10 +532,7 @@ mod decl { set_errno(save_errno); - unsafe { - libc::signal(signum, libc::SIG_DFL); - libc::raise(signum); - } + host_faulthandler::signal_default_and_raise(signum); // Fallback rustpython_host_env::os::exit(1); @@ -596,15 +544,7 @@ mod decl { #[cfg(windows)] fn faulthandler_ignore_exception(code: u32) -> bool { - // bpo-30557: ignore exceptions which are not errors - if (code & 0x80000000) == 0 { - return true; - } - // bpo-31701: ignore MSC and COM exceptions - if code == 0xE06D7363 || code == 0xE0434352 { - return true; - } - false + host_faulthandler::ignore_exception(code) } #[cfg(windows)] @@ -617,8 +557,7 @@ mod decl { return EXCEPTION_CONTINUE_SEARCH; } - let record = unsafe { &*(*exc_info).ExceptionRecord }; - let code = record.ExceptionCode as u32; + let code = unsafe { host_faulthandler::exception_code(exc_info) }; if faulthandler_ignore_exception(code) { return EXCEPTION_CONTINUE_SEARCH; @@ -627,24 +566,16 @@ mod decl { let fd = FATAL_ERROR.fd.load(Ordering::Relaxed); puts(fd, "Windows fatal exception: "); - match code { - 0xC0000005 => puts(fd, "access violation"), - 0xC000008C => puts(fd, "float divide by zero"), - 0xC0000091 => puts(fd, "float overflow"), - 0xC0000094 => puts(fd, "int divide by zero"), - 0xC0000095 => puts(fd, "integer overflow"), - 0xC0000006 => puts(fd, "page error"), - 0xC00000FD => puts(fd, "stack overflow"), - 0xC000001D => puts(fd, "illegal instruction"), - _ => { - puts(fd, "code "); - dump_hexadecimal(fd, code as u64, 8); - } + if let Some(description) = host_faulthandler::exception_description(code) { + puts(fd, description); + } else { + puts(fd, "code "); + dump_hexadecimal(fd, code as u64, 8); } puts(fd, "\n\n"); // Disable SIGSEGV handler for access violations to avoid double output - if code == 0xC0000005 { + if host_faulthandler::is_access_violation(code) { unsafe { for handler in FAULTHANDLER_HANDLERS.iter_mut() { if handler.signum == libc::SIGSEGV { @@ -674,12 +605,12 @@ mod decl { continue; } - let mut action: libc::sigaction = core::mem::zeroed(); - action.sa_sigaction = faulthandler_fatal_error as *const () as libc::sighandler_t; - // SA_NODEFER flag - action.sa_flags = libc::SA_NODEFER; - - if libc::sigaction(handler.signum, &action, &mut handler.previous) != 0 { + if !host_faulthandler::install_sigaction( + handler.signum, + faulthandler_fatal_error, + libc::SA_NODEFER, + &mut handler.previous, + ) { return false; } @@ -703,15 +634,13 @@ mod decl { continue; } - handler.previous = libc::signal( + let Ok(previous) = host_faulthandler::install_signal_handler( handler.signum, - faulthandler_fatal_error as *const () as libc::sighandler_t, - ); - - // SIG_ERR is -1 as sighandler_t (which is usize on Windows) - if handler.previous == libc::SIG_ERR as libc::sighandler_t { + faulthandler_fatal_error, + ) else { return false; - } + }; + handler.previous = previous; handler.enabled = true; } @@ -720,8 +649,8 @@ mod decl { // Register Windows vectored exception handler #[cfg(windows)] { - use windows_sys::Win32::System::Diagnostics::Debug::AddVectoredExceptionHandler; - let h = unsafe { AddVectoredExceptionHandler(1, Some(faulthandler_exc_handler)) }; + let h = + host_faulthandler::add_vectored_exception_handler(Some(faulthandler_exc_handler)); EXC_HANDLER.store(h as usize, Ordering::Relaxed); } @@ -745,13 +674,8 @@ mod decl { // Remove Windows vectored exception handler #[cfg(windows)] { - use windows_sys::Win32::System::Diagnostics::Debug::RemoveVectoredExceptionHandler; let h = EXC_HANDLER.swap(0, Ordering::Relaxed); - if h != 0 { - unsafe { - RemoveVectoredExceptionHandler(h as *mut core::ffi::c_void); - } - } + host_faulthandler::remove_vectored_exception_handler(h); } } @@ -1062,21 +986,18 @@ mod decl { if user.chain { // Restore the previous handler and re-raise - unsafe { - libc::sigaction(signum, &user.previous, core::ptr::null_mut()); - } + host_faulthandler::restore_sigaction(signum, &user.previous); set_errno(save_errno); - unsafe { - libc::raise(signum); - } + host_faulthandler::raise_signal(signum); // Re-install our handler with the same flags as register() let save_errno2 = get_errno(); - unsafe { - let mut action: libc::sigaction = core::mem::zeroed(); - action.sa_sigaction = faulthandler_user_signal as *const () as libc::sighandler_t; - action.sa_flags = libc::SA_NODEFER; - libc::sigaction(signum, &action, core::ptr::null_mut()); - } + let mut ignored_previous: libc::sigaction = unsafe { core::mem::zeroed() }; + let _ = host_faulthandler::install_sigaction( + signum, + faulthandler_user_signal, + libc::SA_NODEFER, + &mut ignored_previous, + ); set_errno(save_errno2); } } @@ -1125,26 +1046,23 @@ mod decl { // Get current handler to save as previous let previous = if !user_signals::is_enabled(signum) { - unsafe { - let mut action: libc::sigaction = core::mem::zeroed(); - action.sa_sigaction = faulthandler_user_signal as *const () as libc::sighandler_t; - // SA_RESTART by default; SA_NODEFER only when chaining - // (faulthandler.c:860-864) - action.sa_flags = if args.chain { + let mut prev: libc::sigaction = unsafe { core::mem::zeroed() }; + if !host_faulthandler::install_sigaction( + args.signum, + faulthandler_user_signal, + if args.chain { libc::SA_NODEFER } else { libc::SA_RESTART - }; - - let mut prev: libc::sigaction = core::mem::zeroed(); - if libc::sigaction(args.signum, &action, &mut prev) != 0 { - return Err(vm.new_os_error(format!( - "Failed to register signal handler for signal {}", - args.signum - ))); - } - prev + }, + &mut prev, + ) { + return Err(vm.new_os_error(format!( + "Failed to register signal handler for signal {}", + args.signum + ))); } + prev } else { // Already registered, keep previous handler user_signals::get_user_signal(signum) @@ -1173,9 +1091,7 @@ mod decl { if let Some(old) = user_signals::clear_user_signal(signum as usize) { // Restore previous handler - unsafe { - libc::sigaction(signum, &old.previous, core::ptr::null_mut()); - } + host_faulthandler::restore_sigaction(signum, &old.previous); Ok(true) } else { Ok(false) @@ -1226,10 +1142,7 @@ mod decl { #[cfg(not(target_arch = "wasm32"))] { suppress_crash_report(); - - unsafe { - libc::abort(); - } + host_faulthandler::abort_process(); } } @@ -1238,10 +1151,7 @@ mod decl { #[cfg(not(target_arch = "wasm32"))] { suppress_crash_report(); - - unsafe { - libc::raise(libc::SIGFPE); - } + host_faulthandler::raise_signal(libc::SIGFPE); } } @@ -1264,28 +1174,14 @@ mod decl { fn suppress_crash_report() { #[cfg(windows)] { - use windows_sys::Win32::System::Diagnostics::Debug::{ - SEM_NOGPFAULTERRORBOX, SetErrorMode, - }; - unsafe { - let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); - SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); - } + host_faulthandler::suppress_crash_report(); } #[cfg(unix)] { - // Disable core dumps #[cfg(not(any(target_os = "redox", target_os = "wasi")))] { - use libc::{RLIMIT_CORE, rlimit, setrlimit}; - let rl = rlimit { - rlim_cur: 0, - rlim_max: 0, - }; - unsafe { - let _ = setrlimit(RLIMIT_CORE, &rl); - } + rustpython_host_env::resource::disable_core_dumps(); } } } @@ -1323,11 +1219,7 @@ mod decl { #[cfg(windows)] #[pyfunction] fn _raise_exception(args: RaiseExceptionArgs, _vm: &VirtualMachine) { - use windows_sys::Win32::System::Diagnostics::Debug::RaiseException; - suppress_crash_report(); - unsafe { - RaiseException(args.code, args.flags, 0, core::ptr::null()); - } + host_faulthandler::raise_exception(args.code, args.flags); } } diff --git a/crates/stdlib/src/locale.rs b/crates/stdlib/src/locale.rs index a22c6afe57e..2c4a914f6cc 100644 --- a/crates/stdlib/src/locale.rs +++ b/crates/stdlib/src/locale.rs @@ -2,55 +2,18 @@ pub(crate) use _locale::module_def; -#[cfg(windows)] -#[repr(C)] -struct lconv { - decimal_point: *mut libc::c_char, - thousands_sep: *mut libc::c_char, - grouping: *mut libc::c_char, - int_curr_symbol: *mut libc::c_char, - currency_symbol: *mut libc::c_char, - mon_decimal_point: *mut libc::c_char, - mon_thousands_sep: *mut libc::c_char, - mon_grouping: *mut libc::c_char, - positive_sign: *mut libc::c_char, - negative_sign: *mut libc::c_char, - int_frac_digits: libc::c_char, - frac_digits: libc::c_char, - p_cs_precedes: libc::c_char, - p_sep_by_space: libc::c_char, - n_cs_precedes: libc::c_char, - n_sep_by_space: libc::c_char, - p_sign_posn: libc::c_char, - n_sign_posn: libc::c_char, - int_p_cs_precedes: libc::c_char, - int_p_sep_by_space: libc::c_char, - int_n_cs_precedes: libc::c_char, - int_n_sep_by_space: libc::c_char, - int_p_sign_posn: libc::c_char, - int_n_sign_posn: libc::c_char, -} - -#[cfg(windows)] -unsafe extern "C" { - fn localeconv() -> *mut lconv; -} - -#[cfg(unix)] -use libc::localeconv; - #[pymodule] mod _locale { use alloc::ffi::CString; - use core::{ffi::CStr, ptr}; + #[cfg(windows)] + use core::ptr; + use rustpython_host_env::locale as host_locale; use rustpython_vm::{ PyObjectRef, PyResult, VirtualMachine, builtins::{PyDictRef, PyIntRef, PyListRef, PyTypeRef, PyUtf8StrRef}, convert::ToPyException, function::OptionalArg, }; - #[cfg(windows)] - use windows_sys::Win32::Globalization::GetACP; #[cfg(all( unix, @@ -78,19 +41,11 @@ mod _locale { vm.ctx.new_int(libc::c_char::MAX) } - unsafe fn copy_grouping(group: *const libc::c_char, vm: &VirtualMachine) -> PyListRef { + fn copy_grouping(group: &[libc::c_char], vm: &VirtualMachine) -> PyListRef { let mut group_vec: Vec = Vec::new(); - if group.is_null() { - return vm.ctx.new_list(group_vec); - } - - unsafe { - let mut ptr = group; - while ![0, libc::c_char::MAX].contains(&*ptr) { - let val = vm.ctx.new_int(*ptr); - group_vec.push(val.into()); - ptr = ptr.add(1); - } + for &value in group { + let val = vm.ctx.new_int(value); + group_vec.push(val.into()); } // https://github.com/python/cpython/blob/677320348728ce058fa3579017e985af74a236d4/Modules/_localemodule.c#L80 if !group_vec.is_empty() { @@ -99,11 +54,9 @@ mod _locale { vm.ctx.new_list(group_vec) } - unsafe fn pystr_from_raw_cstr(vm: &VirtualMachine, raw_ptr: *const libc::c_char) -> PyResult { - let slice = unsafe { CStr::from_ptr(raw_ptr) }; - + fn pystr_from_bytes(vm: &VirtualMachine, bytes: &[u8]) -> PyResult { // Fast path: ASCII/UTF-8 - if let Ok(s) = slice.to_str() { + if let Ok(s) = core::str::from_utf8(bytes) { return Ok(vm.new_pyobj(s)); } @@ -111,7 +64,6 @@ mod _locale { #[cfg(windows)] { use windows_sys::Win32::Globalization::{CP_ACP, MultiByteToWideChar}; - let bytes = slice.to_bytes(); unsafe { let len = MultiByteToWideChar( CP_ACP, @@ -136,7 +88,7 @@ mod _locale { } } - Ok(vm.new_pyobj(String::from_utf8_lossy(slice.to_bytes()).into_owned())) + Ok(vm.new_pyobj(String::from_utf8_lossy(bytes).into_owned())) } #[pyattr(name = "Error", once)] @@ -152,73 +104,59 @@ mod _locale { fn strcoll(string1: PyUtf8StrRef, string2: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { let cstr1 = CString::new(string1.as_str()).map_err(|e| e.to_pyexception(vm))?; let cstr2 = CString::new(string2.as_str()).map_err(|e| e.to_pyexception(vm))?; - Ok(vm.new_pyobj(unsafe { libc::strcoll(cstr1.as_ptr(), cstr2.as_ptr()) })) + Ok(vm.new_pyobj(host_locale::strcoll(&cstr1, &cstr2))) } #[pyfunction] fn strxfrm(string: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult { // https://github.com/python/cpython/blob/eaae563b6878aa050b4ad406b67728b6b066220e/Modules/_localemodule.c#L390-L442 let n1 = string.byte_len() + 1; - let mut buff = vec![0u8; n1]; - let cstr = CString::new(string.as_str()).map_err(|e| e.to_pyexception(vm))?; - let n2 = unsafe { libc::strxfrm(buff.as_mut_ptr() as _, cstr.as_ptr(), n1) }; - buff = vec![0u8; n2 + 1]; - unsafe { - libc::strxfrm(buff.as_mut_ptr() as _, cstr.as_ptr(), n2 + 1); - } + let buff = host_locale::strxfrm(&cstr, n1); Ok(vm.new_pyobj(String::from_utf8(buff).expect("strxfrm returned invalid utf-8 string"))) } #[pyfunction] fn localeconv(vm: &VirtualMachine) -> PyResult { let result = vm.ctx.new_dict(); + let lc = host_locale::localeconv_data(); - unsafe { - macro_rules! set_string_field { - ($lc:expr, $field:ident) => {{ - result.set_item( - stringify!($field), - pystr_from_raw_cstr(vm, (*$lc).$field)?, - vm, - )? - }}; - } - - macro_rules! set_int_field { - ($lc:expr, $field:ident) => {{ result.set_item(stringify!($field), vm.new_pyobj((*$lc).$field), vm)? }}; - } + macro_rules! set_string_field { + ($lc:expr, $field:ident) => {{ result.set_item(stringify!($field), pystr_from_bytes(vm, &$lc.$field)?, vm)? }}; + } - macro_rules! set_group_field { - ($lc:expr, $field:ident) => {{ - result.set_item( - stringify!($field), - copy_grouping((*$lc).$field, vm).into(), - vm, - )? - }}; - } + macro_rules! set_int_field { + ($lc:expr, $field:ident) => {{ result.set_item(stringify!($field), vm.new_pyobj($lc.$field), vm)? }}; + } - let lc = super::localeconv(); - set_group_field!(lc, mon_grouping); - set_group_field!(lc, grouping); - set_int_field!(lc, int_frac_digits); - set_int_field!(lc, frac_digits); - set_int_field!(lc, p_cs_precedes); - set_int_field!(lc, p_sep_by_space); - set_int_field!(lc, n_cs_precedes); - set_int_field!(lc, p_sign_posn); - set_int_field!(lc, n_sign_posn); - set_string_field!(lc, decimal_point); - set_string_field!(lc, thousands_sep); - set_string_field!(lc, int_curr_symbol); - set_string_field!(lc, currency_symbol); - set_string_field!(lc, mon_decimal_point); - set_string_field!(lc, mon_thousands_sep); - set_int_field!(lc, n_sep_by_space); - set_string_field!(lc, positive_sign); - set_string_field!(lc, negative_sign); + macro_rules! set_group_field { + ($lc:expr, $field:ident) => {{ + result.set_item( + stringify!($field), + copy_grouping(&$lc.$field, vm).into(), + vm, + )? + }}; } + + set_group_field!(lc, mon_grouping); + set_group_field!(lc, grouping); + set_int_field!(lc, int_frac_digits); + set_int_field!(lc, frac_digits); + set_int_field!(lc, p_cs_precedes); + set_int_field!(lc, p_sep_by_space); + set_int_field!(lc, n_cs_precedes); + set_int_field!(lc, p_sign_posn); + set_int_field!(lc, n_sign_posn); + set_string_field!(lc, decimal_point); + set_string_field!(lc, thousands_sep); + set_string_field!(lc, int_curr_symbol); + set_string_field!(lc, currency_symbol); + set_string_field!(lc, mon_decimal_point); + set_string_field!(lc, mon_thousands_sep); + set_int_field!(lc, n_sep_by_space); + set_string_field!(lc, positive_sign); + set_string_field!(lc, negative_sign); Ok(result) } @@ -264,35 +202,32 @@ mod _locale { if cfg!(windows) && (args.category < LC_ALL || args.category > LC_TIME) { return Err(vm.new_exception_msg(error, "unsupported locale setting".into())); } - unsafe { - let result = match args.locale.flatten() { - None => libc::setlocale(args.category, ptr::null()), - Some(locale) => { - let locale_str = locale.as_str(); - // On Windows, validate encoding name length - #[cfg(windows)] - { - let valid = if args.category == LC_ALL { - check_locale_name_all(locale_str) - } else { - check_locale_name(locale_str) - }; - if !valid { - return Err( - vm.new_exception_msg(error, "unsupported locale setting".into()) - ); - } + let result = match args.locale.flatten() { + None => host_locale::setlocale(args.category, None), + Some(locale) => { + let locale_str = locale.as_str(); + #[cfg(windows)] + { + let valid = if args.category == LC_ALL { + check_locale_name_all(locale_str) + } else { + check_locale_name(locale_str) + }; + if !valid { + return Err( + vm.new_exception_msg(error, "unsupported locale setting".into()) + ); } - let c_locale: CString = - CString::new(locale_str).map_err(|e| e.to_pyexception(vm))?; - libc::setlocale(args.category, c_locale.as_ptr()) } - }; - if result.is_null() { - return Err(vm.new_exception_msg(error, "unsupported locale setting".into())); + let c_locale: CString = + CString::new(locale_str).map_err(|e| e.to_pyexception(vm))?; + host_locale::setlocale(args.category, Some(&c_locale)) } - pystr_from_raw_cstr(vm, result) - } + }; + let Some(result) = result else { + return Err(vm.new_exception_msg(error, "unsupported locale setting".into())); + }; + pystr_from_bytes(vm, &result) } /// Get the current locale encoding. @@ -300,26 +235,20 @@ mod _locale { fn getencoding() -> String { #[cfg(windows)] { - // On Windows, use GetACP() to get the ANSI code page - let acp = unsafe { GetACP() }; - format!("cp{}", acp) + format!("cp{}", host_locale::acp()) } #[cfg(not(windows))] { - // On Unix, use nl_langinfo(CODESET) or fallback to UTF-8 #[cfg(all( unix, not(any(target_os = "ios", target_os = "android", target_os = "redox")) ))] { - unsafe { - let codeset = libc::nl_langinfo(libc::CODESET); - if !codeset.is_null() - && let Ok(s) = CStr::from_ptr(codeset).to_str() - && !s.is_empty() - { - return s.to_string(); - } + if let Some(codeset) = host_locale::nl_langinfo_codeset() + && let Ok(s) = core::str::from_utf8(&codeset) + && !s.is_empty() + { + return s.to_string(); } "UTF-8".to_string() } diff --git a/crates/stdlib/src/mmap.rs b/crates/stdlib/src/mmap.rs index c492c960750..06ca510ccc5 100644 --- a/crates/stdlib/src/mmap.rs +++ b/crates/stdlib/src/mmap.rs @@ -32,22 +32,14 @@ mod mmap { #[cfg(unix)] use rustpython_host_env::crt_fd; + #[cfg(windows)] + use rustpython_host_env::mmap as host_mmap; #[cfg(windows)] use rustpython_host_env::suppress_iph; #[cfg(windows)] - use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; + use std::os::windows::io::AsRawHandle; #[cfg(windows)] - use windows_sys::Win32::{ - Foundation::{ - CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE, INVALID_HANDLE_VALUE, - }, - Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx}, - System::Memory::{ - CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile, - MapViewOfFile, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, UnmapViewOfFile, - }, - System::Threading::GetCurrentProcess, - }; + use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; #[cfg(unix)] fn validate_advice(vm: &VirtualMachine, advice: i32) -> PyResult { @@ -197,57 +189,14 @@ mod mmap { vm.ctx.exceptions.os_error.to_owned() } - /// Named file mapping on Windows using raw Win32 APIs. - /// Supports tagname parameter for inter-process shared memory. - #[cfg(windows)] - struct NamedMmap { - map_handle: HANDLE, - view_ptr: *mut u8, - len: usize, - } - - #[cfg(windows)] - // SAFETY: The memory mapping is managed by the OS and is safe to share - // across threads. Access is synchronized by PyMutex in PyMmap. - unsafe impl Send for NamedMmap {} - #[cfg(windows)] - unsafe impl Sync for NamedMmap {} - - #[cfg(windows)] - impl core::fmt::Debug for NamedMmap { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("NamedMmap") - .field("map_handle", &self.map_handle) - .field("view_ptr", &self.view_ptr) - .field("len", &self.len) - .finish() - } - } - - #[cfg(windows)] - impl Drop for NamedMmap { - fn drop(&mut self) { - unsafe { - if !self.view_ptr.is_null() { - UnmapViewOfFile( - windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS { - Value: self.view_ptr as *mut _, - }, - ); - } - if !self.map_handle.is_null() { - CloseHandle(self.map_handle); - } - } - } - } - #[derive(Debug)] enum MmapObj { Write(MmapMut), Read(Mmap), #[cfg(windows)] - Named(NamedMmap), + File(host_mmap::MappedFile), + #[cfg(windows)] + Named(host_mmap::NamedMmap), } impl MmapObj { @@ -256,9 +205,9 @@ mod mmap { MmapObj::Read(mmap) => &mmap[..], MmapObj::Write(mmap) => &mmap[..], #[cfg(windows)] - MmapObj::Named(named) => unsafe { - core::slice::from_raw_parts(named.view_ptr, named.len) - }, + MmapObj::File(mmap) => mmap.as_slice(), + #[cfg(windows)] + MmapObj::Named(named) => named.as_slice(), } } } @@ -294,7 +243,7 @@ mod mmap { { let handle = self.handle.swap(INVALID_HANDLE_VALUE as isize); if handle != INVALID_HANDLE_VALUE as isize { - unsafe { CloseHandle(handle as HANDLE) }; + host_mmap::close_handle(handle as HANDLE); } } } @@ -590,74 +539,41 @@ mod mmap { let mut duplicated_handle: HANDLE = INVALID_HANDLE_VALUE; if let Some(fh) = fh { // Duplicate handle so Python code can close the original - let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let result = unsafe { - DuplicateHandle( - GetCurrentProcess(), - fh, - GetCurrentProcess(), - &mut new_handle, - 0, - 0, // not inheritable - DUPLICATE_SAME_ACCESS, - ) - }; - if result == 0 { - return Err(io::Error::last_os_error().to_pyexception(vm)); - } - duplicated_handle = new_handle; + duplicated_handle = + host_mmap::duplicate_handle(fh).map_err(|e| e.to_pyexception(vm))?; // Get file size - let mut high: u32 = 0; - let low = unsafe { GetFileSize(fh, &mut high) }; - if low == u32::MAX { - let err = io::Error::last_os_error(); - if err.raw_os_error() != Some(0) { - unsafe { CloseHandle(duplicated_handle) }; + let file_len = match host_mmap::get_file_len(fh) { + Ok(len) => len, + Err(err) => { + host_mmap::close_handle(duplicated_handle); return Err(err.to_pyexception(vm)); } - } - let file_len = ((high as i64) << 32) | (low as i64); + }; if map_size == 0 { if file_len == 0 { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); return Err(vm.new_value_error("cannot mmap an empty file")); } if offset >= file_len { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); return Err(vm.new_value_error("mmap offset is greater than file size")); } if file_len - offset > isize::MAX as i64 { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); return Err(vm.new_value_error("mmap length is too large")); } map_size = (file_len - offset) as usize; } else { // If map_size > file_len, extend the file (Windows behavior) let required_size = offset.checked_add(map_size as i64).ok_or_else(|| { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); vm.new_overflow_error("mmap size would cause file size overflow") })?; if required_size > file_len { - // Extend file using SetFilePointerEx + SetEndOfFile - let result = unsafe { - SetFilePointerEx( - duplicated_handle, - required_size, - core::ptr::null_mut(), - FILE_BEGIN, - ) - }; - if result == 0 { - let err = io::Error::last_os_error(); - unsafe { CloseHandle(duplicated_handle) }; - return Err(err.to_pyexception(vm)); - } - let result = unsafe { SetEndOfFile(duplicated_handle) }; - if result == 0 { - let err = io::Error::last_os_error(); - unsafe { CloseHandle(duplicated_handle) }; + if let Err(err) = host_mmap::extend_file(duplicated_handle, required_size) { + host_mmap::close_handle(duplicated_handle); return Err(err.to_pyexception(vm)); } } @@ -666,60 +582,36 @@ mod mmap { // When tagname is provided, use raw Win32 APIs for named shared memory if let Some(ref tag) = tag_str { - let (fl_protect, desired_access) = match access { - AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE), - AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ), - AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY), - }; - let fh = if let Some(fh) = fh { // Close the duplicated handle - we'll use the original // file handle for CreateFileMappingW if duplicated_handle != INVALID_HANDLE_VALUE { - unsafe { CloseHandle(duplicated_handle) }; + host_mmap::close_handle(duplicated_handle); } fh } else { INVALID_HANDLE_VALUE }; - let tag_wide: Vec = tag.encode_utf16().chain(core::iter::once(0)).collect(); - - let total_size = (offset as u64) - .checked_add(map_size as u64) - .ok_or_else(|| vm.new_overflow_error("mmap offset plus size would overflow"))?; - let size_hi = (total_size >> 32) as u32; - let size_lo = total_size as u32; - - let map_handle = unsafe { - CreateFileMappingW( - fh, - core::ptr::null(), - fl_protect, - size_hi, - size_lo, - tag_wide.as_ptr(), - ) - }; - if map_handle.is_null() { - return Err(io::Error::last_os_error().to_pyexception(vm)); - } - - let off_hi = (offset as u64 >> 32) as u32; - let off_lo = offset as u32; - - let view = - unsafe { MapViewOfFile(map_handle, desired_access, off_hi, off_lo, map_size) }; - if view.Value.is_null() { - unsafe { CloseHandle(map_handle) }; - return Err(io::Error::last_os_error().to_pyexception(vm)); - } - - let named = NamedMmap { - map_handle, - view_ptr: view.Value as *mut u8, - len: map_size, - }; + let named = host_mmap::create_named_mapping( + fh, + tag, + match access { + AccessMode::Default => host_mmap::AccessMode::Default, + AccessMode::Read => host_mmap::AccessMode::Read, + AccessMode::Write => host_mmap::AccessMode::Write, + AccessMode::Copy => host_mmap::AccessMode::Copy, + }, + offset, + map_size, + ) + .map_err(|err| { + if err.raw_os_error() == Some(libc::EOVERFLOW) { + vm.new_overflow_error("mmap offset plus size would overflow") + } else { + err.to_pyexception(vm) + } + })?; return Ok(Self { closed: AtomicCell::new(false), @@ -733,32 +625,14 @@ mod mmap { }); } - let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); - let (handle, mmap) = if duplicated_handle != INVALID_HANDLE_VALUE { - // Safety: We just duplicated this handle and it's valid - let owned_handle = - unsafe { OwnedHandle::from_raw_handle(duplicated_handle as RawHandle) }; - - let mmap_result = match access { - AccessMode::Default | AccessMode::Write => { - unsafe { mmap_opt.map_mut(&owned_handle) }.map(MmapObj::Write) - } - AccessMode::Read => unsafe { mmap_opt.map(&owned_handle) }.map(MmapObj::Read), - AccessMode::Copy => { - unsafe { mmap_opt.map_copy(&owned_handle) }.map(MmapObj::Write) - } - }; - - let mmap = mmap_result.map_err(|e| e.to_pyexception(vm))?; - - // Keep the handle alive - let raw = owned_handle.as_raw_handle() as isize; - core::mem::forget(owned_handle); - (raw, mmap) + let mmap = Self::create_mmap_windows(duplicated_handle, offset, map_size, &access) + .map_err(|e| e.to_pyexception(vm))?; + (duplicated_handle as isize, mmap) } else { // Anonymous mapping + let mut mmap_opt = MmapOptions::new(); + let mmap_opt = mmap_opt.offset(offset as u64).len(map_size); let mmap = mmap_opt.map_anon().map_err(|e| e.to_pyexception(vm))?; (INVALID_HANDLE_VALUE as isize, MmapObj::Write(mmap)) }; @@ -858,6 +732,8 @@ mod mmap { MmapObj::Read(_) => panic!("mmap can't modify a readonly memory map."), MmapObj::Write(mmap) => &mut mmap[..], #[cfg(windows)] + MmapObj::File(mmap) => mmap.as_mut_slice(), + #[cfg(windows)] MmapObj::Named(named) => unsafe { core::slice::from_raw_parts_mut(named.view_ptr, named.len) }, @@ -900,6 +776,8 @@ mod mmap { match self.check_valid(vm)?.deref_mut().as_mut().unwrap() { MmapObj::Write(mmap) => Ok(f(&mut mmap[..])), #[cfg(windows)] + MmapObj::File(mmap) => Ok(f(mmap.as_mut_slice())), + #[cfg(windows)] MmapObj::Named(named) => Ok(f(unsafe { core::slice::from_raw_parts_mut(named.view_ptr, named.len) })), @@ -1020,12 +898,14 @@ mod mmap { .map_err(|e| e.to_pyexception(vm))?; } #[cfg(windows)] + MmapObj::File(mmap) => { + mmap.flush_range(offset, size) + .map_err(|e| e.to_pyexception(vm))?; + } + #[cfg(windows)] MmapObj::Named(named) => { - let ptr = unsafe { named.view_ptr.add(offset) }; - let result = unsafe { FlushViewOfFile(ptr as *const _, size) }; - if result == 0 { - return Err(io::Error::last_os_error().to_pyexception(vm)); - } + host_mmap::flush_view(named.ptr_at(offset), size) + .map_err(|e| e.to_pyexception(vm))?; } } @@ -1207,7 +1087,7 @@ mod mmap { return Err(vm.new_os_error("mmap: cannot resize a named memory mapping")); } - let is_anonymous = handle == INVALID_HANDLE_VALUE as isize; + let is_anonymous = host_mmap::is_invalid_handle_value(handle); if is_anonymous { // For anonymous mmap, we need to: @@ -1239,26 +1119,9 @@ mod mmap { // Drop the current mmap to release the file mapping *mmap_guard = None; - // Resize the file let required_size = self.offset + newsize as i64; - let result = unsafe { - SetFilePointerEx( - handle as HANDLE, - required_size, - core::ptr::null_mut(), - FILE_BEGIN, - ) - }; - if result == 0 { + if let Err(err) = host_mmap::extend_file(handle as HANDLE, required_size) { // Restore original mmap on error - let err = io::Error::last_os_error(); - self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); - return Err(err.to_pyexception(vm)); - } - - let result = unsafe { SetEndOfFile(handle as HANDLE) }; - if result == 0 { - let err = io::Error::last_os_error(); self.try_restore_mmap(&mut mmap_guard, handle as HANDLE, self.size.load()); return Err(err.to_pyexception(vm)); } @@ -1332,20 +1195,13 @@ mod mmap { #[pymethod] fn size(&self, vm: &VirtualMachine) -> PyResult { let handle = self.handle.load(); - if handle == INVALID_HANDLE_VALUE as isize { + if host_mmap::is_invalid_handle_value(handle) { // Anonymous mapping, return the mmap size return Ok(PyInt::from(self.__len__()).into_ref(&vm.ctx)); } - let mut high: u32 = 0; - let low = unsafe { GetFileSize(handle as HANDLE, &mut high) }; - if low == u32::MAX { - let err = io::Error::last_os_error(); - if err.raw_os_error() != Some(0) { - return Err(err.to_pyexception(vm)); - } - } - let file_len = ((high as i64) << 32) | (low as i64); + let file_len = + host_mmap::get_file_len(handle as HANDLE).map_err(|e| e.to_pyexception(vm))?; Ok(PyInt::from(file_len).into_ref(&vm.ctx)) } @@ -1436,27 +1292,18 @@ mod mmap { size: usize, access: &AccessMode, ) -> io::Result { - use std::fs::File; - - // Create an owned handle wrapper for memmap2 - // We need to create a File from the handle - let file = unsafe { File::from_raw_handle(handle as RawHandle) }; - - let mut mmap_opt = MmapOptions::new(); - let mmap_opt = mmap_opt.offset(offset as u64).len(size); - - let result = match access { - AccessMode::Default | AccessMode::Write => { - unsafe { mmap_opt.map_mut(&file) }.map(MmapObj::Write) - } - AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MmapObj::Read), - AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MmapObj::Write), - }; - - // Don't close the file handle - we're borrowing it - core::mem::forget(file); - - result + host_mmap::map_handle( + handle, + offset, + size, + match access { + AccessMode::Default => host_mmap::AccessMode::Default, + AccessMode::Read => host_mmap::AccessMode::Read, + AccessMode::Write => host_mmap::AccessMode::Write, + AccessMode::Copy => host_mmap::AccessMode::Copy, + }, + ) + .map(MmapObj::File) } /// Try to restore mmap after a failed resize operation. diff --git a/crates/stdlib/src/multiprocessing.rs b/crates/stdlib/src/multiprocessing.rs index fe30abfdcb5..83b7677241b 100644 --- a/crates/stdlib/src/multiprocessing.rs +++ b/crates/stdlib/src/multiprocessing.rs @@ -6,18 +6,14 @@ mod _multiprocessing { use crate::vm::{ Context, FromArgs, Py, PyPayload, PyRef, PyResult, VirtualMachine, builtins::{PyDict, PyType, PyTypeRef}, + convert::ToPyException, function::{ArgBytesLike, FuncArgs, KwArgs}, types::Constructor, }; use core::sync::atomic::{AtomicI32, AtomicU32, Ordering}; - use windows_sys::Win32::Foundation::{ - CloseHandle, ERROR_TOO_MANY_POSTS, HANDLE, INVALID_HANDLE_VALUE, WAIT_FAILED, - WAIT_OBJECT_0, WAIT_TIMEOUT, - }; + use rustpython_host_env::multiprocessing as host_multiprocessing; use windows_sys::Win32::Networking::WinSock::{self, SOCKET}; - use windows_sys::Win32::System::Threading::{ - CreateSemaphoreW, GetCurrentThreadId, INFINITE, ReleaseSemaphore, WaitForSingleObjectEx, - }; + use windows_sys::Win32::{Foundation::HANDLE, System::Threading::INFINITE}; // These match the values in Lib/multiprocessing/synchronize.py const RECURSIVE_MUTEX: i32 = 0; @@ -26,7 +22,8 @@ mod _multiprocessing { macro_rules! ismine { ($self:expr) => { $self.count.load(Ordering::Acquire) > 0 - && $self.last_tid.load(Ordering::Acquire) == unsafe { GetCurrentThreadId() } + && $self.last_tid.load(Ordering::Acquire) + == host_multiprocessing::current_thread_id() }; } @@ -56,54 +53,7 @@ mod _multiprocessing { count: AtomicI32, } - #[derive(Debug)] - struct SemHandle { - raw: HANDLE, - } - - unsafe impl Send for SemHandle {} - unsafe impl Sync for SemHandle {} - - impl SemHandle { - fn create(value: i32, maxvalue: i32, vm: &VirtualMachine) -> PyResult { - let handle = - unsafe { CreateSemaphoreW(core::ptr::null(), value, maxvalue, core::ptr::null()) }; - if handle == 0 as HANDLE { - return Err(vm.new_last_os_error()); - } - Ok(SemHandle { raw: handle }) - } - - #[inline] - fn as_raw(&self) -> HANDLE { - self.raw - } - } - - impl Drop for SemHandle { - fn drop(&mut self) { - if self.raw != 0 as HANDLE && self.raw != INVALID_HANDLE_VALUE { - unsafe { - CloseHandle(self.raw); - } - } - } - } - - /// _GetSemaphoreValue - get value of semaphore by briefly acquiring and releasing - fn get_semaphore_value(handle: HANDLE) -> Result { - match unsafe { WaitForSingleObjectEx(handle, 0, 0) } { - WAIT_OBJECT_0 => { - let mut previous: i32 = 0; - if unsafe { ReleaseSemaphore(handle, 1, &mut previous) } == 0 { - return Err(()); - } - Ok(previous + 1) - } - WAIT_TIMEOUT => Ok(0), - _ => Err(()), - } - } + type SemHandle = host_multiprocessing::SemHandle; #[pyclass(with(Constructor), flags(BASETYPE))] impl SemLock { @@ -167,14 +117,14 @@ mod _multiprocessing { } // Check whether we can acquire without blocking - match unsafe { WaitForSingleObjectEx(self.handle.as_raw(), 0, 0) } { - WAIT_OBJECT_0 => { + match host_multiprocessing::wait_for_single_object(self.handle.as_raw(), 0) { + x if x == host_multiprocessing::wait_object_0() => { self.last_tid - .store(unsafe { GetCurrentThreadId() }, Ordering::Release); + .store(host_multiprocessing::current_thread_id(), Ordering::Release); self.count.fetch_add(1, Ordering::Release); return Ok(true); } - WAIT_FAILED => return Err(vm.new_last_os_error()), + x if x == host_multiprocessing::wait_failed() => return Err(vm.new_last_os_error()), _ => {} } @@ -194,22 +144,26 @@ mod _multiprocessing { }; let handle = self.handle.as_raw(); - let res = vm.allow_threads(|| unsafe { WaitForSingleObjectEx(handle, wait_ms, 0) }); + let res = vm.allow_threads(|| { + host_multiprocessing::wait_for_single_object(handle, wait_ms) + }); match res { - WAIT_OBJECT_0 => { + x if x == host_multiprocessing::wait_object_0() => { self.last_tid - .store(unsafe { GetCurrentThreadId() }, Ordering::Release); + .store(host_multiprocessing::current_thread_id(), Ordering::Release); self.count.fetch_add(1, Ordering::Release); return Ok(true); } - WAIT_TIMEOUT => { + x if x == host_multiprocessing::wait_timeout() => { vm.check_signals()?; if full_msecs != INFINITE { elapsed = elapsed.saturating_add(wait_ms); } } - WAIT_FAILED => return Err(vm.new_last_os_error()), + x if x == host_multiprocessing::wait_failed() => { + return Err(vm.new_last_os_error()); + } _ => { return Err(vm.new_runtime_error(format!( "WaitForSingleObject() gave unrecognized value {res}" @@ -234,9 +188,8 @@ mod _multiprocessing { } } - if unsafe { ReleaseSemaphore(self.handle.as_raw(), 1, core::ptr::null_mut()) } == 0 { - let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - if err == ERROR_TOO_MANY_POSTS { + if let Err(err) = host_multiprocessing::release_semaphore(self.handle.as_raw()) { + if host_multiprocessing::is_too_many_posts(err) { return Err(vm.new_value_error("semaphore or lock released too many times")); } return Err(vm.new_last_os_error()); @@ -273,9 +226,7 @@ mod _multiprocessing { ) -> PyResult { // On Windows, _rebuild receives the handle directly (no sem_open) let zelf = SemLock { - handle: SemHandle { - raw: handle as HANDLE, - }, + handle: SemHandle::from_raw(handle as HANDLE), kind, maxvalue, name, @@ -308,13 +259,14 @@ mod _multiprocessing { #[pymethod] fn _get_value(&self, vm: &VirtualMachine) -> PyResult { - get_semaphore_value(self.handle.as_raw()).map_err(|_| vm.new_last_os_error()) + host_multiprocessing::get_semaphore_value(self.handle.as_raw()) + .map_err(|_| vm.new_last_os_error()) } #[pymethod] fn _is_zero(&self, vm: &VirtualMachine) -> PyResult { - let val = - get_semaphore_value(self.handle.as_raw()).map_err(|_| vm.new_last_os_error())?; + let val = host_multiprocessing::get_semaphore_value(self.handle.as_raw()) + .map_err(|_| vm.new_last_os_error())?; Ok(val == 0) } @@ -346,7 +298,8 @@ mod _multiprocessing { return Err(vm.new_value_error("invalid value")); } - let handle = SemHandle::create(args.value, args.maxvalue, vm)?; + let handle = + SemHandle::create(args.value, args.maxvalue).map_err(|e| e.to_pyexception(vm))?; let name = if args.unlink { None } else { Some(args.name) }; Ok(SemLock { @@ -417,10 +370,10 @@ mod _multiprocessing { function::{FuncArgs, KwArgs}, types::Constructor, }; - use alloc::ffi::CString; use core::sync::atomic::{AtomicI32, AtomicU64, Ordering}; use libc::sem_t; use nix::errno::Errno; + use rustpython_host_env::multiprocessing as host_multiprocessing; /// Error type for sem_timedwait operations #[cfg(target_vendor = "apple")] @@ -441,60 +394,19 @@ mod _multiprocessing { let mut delay: u64 = 0; loop { - // poll: try to acquire - if unsafe { libc::sem_trywait(sem) } == 0 { - return Ok(()); - } - let err = Errno::last(); - if err != Errno::EAGAIN { - return Err(SemWaitError::OsError(err)); - } - - // get current time - let mut now = libc::timeval { - tv_sec: 0, - tv_usec: 0, - }; - if unsafe { libc::gettimeofday(&mut now, core::ptr::null_mut()) } < 0 { - return Err(SemWaitError::OsError(Errno::last())); - } - - // check for timeout - let deadline_usec = deadline.tv_sec * 1_000_000 + deadline.tv_nsec / 1000; - #[allow(clippy::unnecessary_cast)] - let now_usec = now.tv_sec as i64 * 1_000_000 + now.tv_usec as i64; - - if now_usec >= deadline_usec { - return Err(SemWaitError::Timeout); - } - - // calculate how much time is left - let difference = (deadline_usec - now_usec) as u64; - - // check delay not too long -- maximum is 20 msecs - delay += 1000; - if delay > 20000 { - delay = 20000; - } - if delay > difference { - delay = difference; + match vm.allow_threads(|| { + host_multiprocessing::sem_timedwait_poll_step(sem, deadline, delay) + }) { + Ok(host_multiprocessing::PollWaitStep::Acquired) => return Ok(()), + Ok(host_multiprocessing::PollWaitStep::Timeout) => { + return Err(SemWaitError::Timeout); + } + Ok(host_multiprocessing::PollWaitStep::Continue(next_delay)) => { + delay = next_delay; + } + Err(err) => return Err(SemWaitError::OsError(err)), } - // sleep using select - let mut tv_delay = libc::timeval { - tv_sec: (delay / 1_000_000) as _, - tv_usec: (delay % 1_000_000) as _, - }; - vm.allow_threads(|| unsafe { - libc::select( - 0, - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut tv_delay, - ) - }); - // check for signals - preserve the exception (e.g., KeyboardInterrupt) if let Err(exc) = vm.check_signals() { return Err(SemWaitError::SignalException(exc)); @@ -510,7 +422,8 @@ mod _multiprocessing { macro_rules! ismine { ($self:expr) => { $self.count.load(Ordering::Acquire) > 0 - && $self.last_tid.load(Ordering::Acquire) == current_thread_id() + && $self.last_tid.load(Ordering::Acquire) + == host_multiprocessing::current_thread_id() }; } @@ -540,70 +453,7 @@ mod _multiprocessing { count: AtomicI32, // int } - #[derive(Debug)] - struct SemHandle { - raw: *mut sem_t, - } - - unsafe impl Send for SemHandle {} - unsafe impl Sync for SemHandle {} - - impl SemHandle { - fn create( - name: &str, - value: u32, - unlink: bool, - vm: &VirtualMachine, - ) -> PyResult<(Self, Option)> { - let cname = semaphore_name(vm, name)?; - // SEM_CREATE(name, val, max) sem_open(name, O_CREAT | O_EXCL, 0600, val) - let raw = unsafe { - libc::sem_open(cname.as_ptr(), libc::O_CREAT | libc::O_EXCL, 0o600, value) - }; - if raw == libc::SEM_FAILED { - let err = Errno::last(); - return Err(os_error(vm, err)); - } - if unlink { - // SEM_UNLINK(name) sem_unlink(name) - unsafe { - libc::sem_unlink(cname.as_ptr()); - } - Ok((SemHandle { raw }, None)) - } else { - Ok((SemHandle { raw }, Some(name.to_owned()))) - } - } - - fn open_existing(name: &str, vm: &VirtualMachine) -> PyResult { - let cname = semaphore_name(vm, name)?; - let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) }; - if raw == libc::SEM_FAILED { - let err = Errno::last(); - return Err(os_error(vm, err)); - } - Ok(SemHandle { raw }) - } - - #[inline] - fn as_ptr(&self) -> *mut sem_t { - self.raw - } - } - - impl Drop for SemHandle { - fn drop(&mut self) { - // Guard against default/uninitialized state. - // Note: SEM_FAILED is (sem_t*)-1, not null, but valid handles are never null - // and SEM_FAILED is never stored (error is returned immediately on sem_open failure). - if !self.raw.is_null() { - // SEM_CLOSE(sem) sem_close(sem) - unsafe { - libc::sem_close(self.raw); - } - } - } - } + type SemHandle = host_multiprocessing::SemHandle; #[pyclass(with(Constructor), flags(BASETYPE))] impl SemLock { @@ -659,33 +509,10 @@ mod _multiprocessing { let timeout_obj = timeout_obj.unwrap(); // This accepts both int and float, converting to f64 let timeout: f64 = timeout_obj.try_float(vm)?.to_f64(); - let timeout = if timeout < 0.0 { 0.0 } else { timeout }; - - let mut tv = libc::timeval { - tv_sec: 0, - tv_usec: 0, - }; - let res = unsafe { libc::gettimeofday(&mut tv, core::ptr::null_mut()) }; - if res < 0 { - return Err(vm.new_os_error("gettimeofday failed".to_string())); - } - - // deadline calculation: - // long sec = (long) timeout; - // long nsec = (long) (1e9 * (timeout - sec) + 0.5); - // deadline.tv_sec = now.tv_sec + sec; - // deadline.tv_nsec = now.tv_usec * 1000 + nsec; - // deadline.tv_sec += (deadline.tv_nsec / 1000000000); - // deadline.tv_nsec %= 1000000000; - let sec = timeout as libc::c_long; - let nsec = (1e9 * (timeout - sec as f64) + 0.5) as libc::c_long; - let mut deadline = libc::timespec { - tv_sec: tv.tv_sec + sec as libc::time_t, - tv_nsec: (tv.tv_usec as libc::c_long * 1000 + nsec) as _, - }; - deadline.tv_sec += (deadline.tv_nsec / 1_000_000_000) as libc::time_t; - deadline.tv_nsec %= 1_000_000_000; - Some(deadline) + Some( + host_multiprocessing::deadline_from_timeout(timeout) + .map_err(|_| vm.new_os_error("gettimeofday failed".to_string()))?, + ) } else { None }; @@ -719,31 +546,9 @@ mod _multiprocessing { loop { let sem_ptr = self.handle.as_ptr(); // Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS - let (r, e) = if let Some(ref dl) = deadline { - vm.allow_threads(|| { - let r = unsafe { libc::sem_timedwait(sem_ptr, dl) }; - ( - r, - if r < 0 { - Errno::last() - } else { - Errno::from_raw(0) - }, - ) - }) - } else { - vm.allow_threads(|| { - let r = unsafe { libc::sem_wait(sem_ptr) }; - ( - r, - if r < 0 { - Errno::last() - } else { - Errno::from_raw(0) - }, - ) - }) - }; + let (r, e) = vm.allow_threads(|| { + host_multiprocessing::sem_wait_save_errno(sem_ptr, deadline.as_ref()) + }); res = r; saved_errno = e; @@ -782,15 +587,7 @@ mod _multiprocessing { loop { let sem_ptr = self.handle.as_ptr(); let (r, e) = vm.allow_threads(|| { - let r = unsafe { libc::sem_wait(sem_ptr) }; - ( - r, - if r < 0 { - Errno::last() - } else { - Errno::from_raw(0) - }, - ) + host_multiprocessing::sem_wait_save_errno(sem_ptr, None) }); res = r; saved_errno = e; @@ -821,7 +618,8 @@ mod _multiprocessing { } self.count.fetch_add(1, Ordering::Release); - self.last_tid.store(current_thread_id(), Ordering::Release); + self.last_tid + .store(host_multiprocessing::current_thread_id(), Ordering::Release); Ok(true) } @@ -849,11 +647,9 @@ mod _multiprocessing { #[cfg(not(target_vendor = "apple"))] { // Linux: use sem_getvalue - let mut sval: libc::c_int = 0; - let res = unsafe { libc::sem_getvalue(self.handle.as_ptr(), &mut sval) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); - } + let sval = + unsafe { host_multiprocessing::get_semaphore_value(self.handle.as_ptr()) } + .map_err(|err| os_error(vm, err))?; if sval >= self.maxvalue { return Err(vm.new_value_error("semaphore or lock released too many times")); } @@ -864,15 +660,15 @@ mod _multiprocessing { // We will only check properly the maxvalue == 1 case if self.maxvalue == 1 { // make sure that already locked - if unsafe { libc::sem_trywait(self.handle.as_ptr()) } < 0 { - if Errno::last() != Errno::EAGAIN { - return Err(os_error(vm, Errno::last())); + if let Err(err) = host_multiprocessing::sem_trywait(self.handle.as_ptr()) { + if err != Errno::EAGAIN { + return Err(os_error(vm, err)); } // it is already locked as expected } else { // it was not locked so undo wait and raise - if unsafe { libc::sem_post(self.handle.as_ptr()) } < 0 { - return Err(os_error(vm, Errno::last())); + if let Err(err) = host_multiprocessing::sem_post(self.handle.as_ptr()) { + return Err(os_error(vm, err)); } return Err( vm.new_value_error("semaphore or lock released too many times") @@ -882,9 +678,8 @@ mod _multiprocessing { } } - let res = unsafe { libc::sem_post(self.handle.as_ptr()) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); + if let Err(err) = host_multiprocessing::sem_post(self.handle.as_ptr()) { + return Err(os_error(vm, err)); } self.count.fetch_sub(1, Ordering::Release); @@ -926,7 +721,7 @@ mod _multiprocessing { let Some(ref name_str) = name else { return Err(vm.new_value_error("cannot rebuild SemLock without name")); }; - let handle = SemHandle::open_existing(name_str, vm)?; + let handle = SemHandle::open_existing(name_str).map_err(|err| os_error(vm, err))?; // return newsemlockobject(type, handle, kind, maxvalue, name_copy); let zelf = SemLock { handle, @@ -976,14 +771,8 @@ mod _multiprocessing { #[cfg(not(target_vendor = "apple"))] { // Linux: use sem_getvalue - let mut sval: libc::c_int = 0; - let res = unsafe { libc::sem_getvalue(self.handle.as_ptr(), &mut sval) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); - } - // some posix implementations use negative numbers to indicate - // the number of waiting threads - Ok(if sval < 0 { 0 } else { sval }) + unsafe { host_multiprocessing::get_semaphore_value(self.handle.as_ptr()) } + .map_err(|err| os_error(vm, err)) } #[cfg(target_vendor = "apple")] { @@ -1004,15 +793,15 @@ mod _multiprocessing { { // macOS: HAVE_BROKEN_SEM_GETVALUE // Try to acquire - if EAGAIN, value is 0 - if unsafe { libc::sem_trywait(self.handle.as_ptr()) } < 0 { - if Errno::last() == Errno::EAGAIN { + if let Err(err) = host_multiprocessing::sem_trywait(self.handle.as_ptr()) { + if err == Errno::EAGAIN { return Ok(true); } - return Err(os_error(vm, Errno::last())); + return Err(os_error(vm, err)); } // Successfully acquired - undo and return false - if unsafe { libc::sem_post(self.handle.as_ptr()) } < 0 { - return Err(os_error(vm, Errno::last())); + if let Err(err) = host_multiprocessing::sem_post(self.handle.as_ptr()) { + return Err(os_error(vm, err)); } Ok(false) } @@ -1027,14 +816,7 @@ mod _multiprocessing { class.set_attr(ctx.intern_str("SEMAPHORE"), ctx.new_int(SEMAPHORE).into()); // SEM_VALUE_MAX from system, or INT_MAX if negative // We use a reasonable default - let sem_value_max: i32 = unsafe { - let val = libc::sysconf(libc::_SC_SEM_VALUE_MAX); - if val < 0 || val > i32::MAX as libc::c_long { - i32::MAX - } else { - val as i32 - } - }; + let sem_value_max = host_multiprocessing::sem_value_max(); class.set_attr( ctx.intern_str("SEM_VALUE_MAX"), ctx.new_int(sem_value_max).into(), @@ -1057,7 +839,14 @@ mod _multiprocessing { } let value = args.value as u32; - let (handle, name) = SemHandle::create(&args.name, value, args.unlink, vm)?; + let (handle, name) = + SemHandle::create(&args.name, value, args.unlink).map_err(|err| { + if err == Errno::EINVAL && args.name.contains('\0') { + vm.new_value_error("embedded null character") + } else { + os_error(vm, err) + } + })?; // return newsemlockobject(type, handle, kind, maxvalue, name_copy); Ok(SemLock { @@ -1075,12 +864,13 @@ mod _multiprocessing { // _PyMp_sem_unlink. #[pyfunction] fn sem_unlink(name: String, vm: &VirtualMachine) -> PyResult<()> { - let cname = semaphore_name(vm, &name)?; - let res = unsafe { libc::sem_unlink(cname.as_ptr()) }; - if res < 0 { - return Err(os_error(vm, Errno::last())); - } - Ok(()) + host_multiprocessing::sem_unlink(&name).map_err(|err| { + if err == Errno::EINVAL && name.contains('\0') { + vm.new_value_error("embedded null character") + } else { + os_error(vm, err) + } + }) } /// Module-level flags dict. @@ -1111,16 +901,6 @@ mod _multiprocessing { flags } - fn semaphore_name(vm: &VirtualMachine, name: &str) -> PyResult { - // POSIX semaphore names must start with / - let mut full = String::with_capacity(name.len() + 1); - if !name.starts_with('/') { - full.push('/'); - } - full.push_str(name); - CString::new(full).map_err(|_| vm.new_value_error("embedded null character")) - } - fn handle_wait_error(vm: &VirtualMachine, saved_errno: Errno) -> PyResult { match saved_errno { Errno::EAGAIN | Errno::ETIMEDOUT => Ok(false), @@ -1139,12 +919,6 @@ mod _multiprocessing { vm.new_os_subtype_error(exc_type, Some(err as i32), err.desc().to_owned()) .upcast() } - - /// Get current thread identifier. - /// PyThread_get_thread_ident on Unix (pthread_self). - fn current_thread_id() -> u64 { - unsafe { libc::pthread_self() as u64 } - } } #[cfg(all(not(unix), not(windows)))] diff --git a/crates/stdlib/src/overlapped.rs b/crates/stdlib/src/overlapped.rs index 2230991b643..a0701efc0fd 100644 --- a/crates/stdlib/src/overlapped.rs +++ b/crates/stdlib/src/overlapped.rs @@ -17,6 +17,7 @@ mod _overlapped { protocol::PyBuffer, types::{Constructor, Destructor}, }; + use rustpython_host_env::{overlapped as host_overlapped, winapi as host_winapi}; use windows_sys::Win32::{ Foundation::{self, GetLastError, HANDLE}, Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6}, @@ -25,7 +26,8 @@ mod _overlapped { pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { let _ = vm.import("_socket", 0)?; - initialize_winsock_extensions(vm)?; + host_overlapped::initialize_winsock_extensions() + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))?; __module_exec(vm, module); Ok(()) } @@ -49,95 +51,6 @@ mod _overlapped { #[pyattr] const NULL: isize = 0; - // Function pointers for Winsock extension functions - static ACCEPT_EX: std::sync::OnceLock = std::sync::OnceLock::new(); - static CONNECT_EX: std::sync::OnceLock = std::sync::OnceLock::new(); - static DISCONNECT_EX: std::sync::OnceLock = std::sync::OnceLock::new(); - static TRANSMIT_FILE: std::sync::OnceLock = std::sync::OnceLock::new(); - - fn initialize_winsock_extensions(vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Networking::WinSock::{ - INVALID_SOCKET, IPPROTO_TCP, SIO_GET_EXTENSION_FUNCTION_POINTER, SOCK_STREAM, - SOCKET_ERROR, WSAGetLastError, WSAIoctl, closesocket, socket, - }; - - // GUIDs for extension functions - const WSAID_ACCEPTEX: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0xb5367df1, - data2: 0xcbac, - data3: 0x11cf, - data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], - }; - const WSAID_CONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0x25a207b9, - data2: 0xddf3, - data3: 0x4660, - data4: [0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e], - }; - const WSAID_DISCONNECTEX: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0x7fda2e11, - data2: 0x8630, - data3: 0x436f, - data4: [0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57], - }; - const WSAID_TRANSMITFILE: windows_sys::core::GUID = windows_sys::core::GUID { - data1: 0xb5367df0, - data2: 0xcbac, - data3: 0x11cf, - data4: [0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92], - }; - - // Check all four locks to prevent partial initialization - if ACCEPT_EX.get().is_some() - && CONNECT_EX.get().is_some() - && DISCONNECT_EX.get().is_some() - && TRANSMIT_FILE.get().is_some() - { - return Ok(()); - } - - let s = unsafe { socket(AF_INET as i32, SOCK_STREAM, IPPROTO_TCP) }; - if s == INVALID_SOCKET { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - - let mut dw_bytes: u32 = 0; - - macro_rules! get_extension { - ($guid:expr, $lock:expr) => {{ - let mut func_ptr: usize = 0; - let ret = unsafe { - WSAIoctl( - s, - SIO_GET_EXTENSION_FUNCTION_POINTER, - &$guid as *const _ as *const _, - core::mem::size_of_val(&$guid) as u32, - &mut func_ptr as *mut _ as *mut _, - core::mem::size_of::() as u32, - &mut dw_bytes, - core::ptr::null_mut(), - None, - ) - }; - if ret == SOCKET_ERROR { - let err = unsafe { WSAGetLastError() } as u32; - unsafe { closesocket(s) }; - return Err(set_from_windows_err(err, vm)); - } - let _ = $lock.set(func_ptr); - }}; - } - - get_extension!(WSAID_ACCEPTEX, ACCEPT_EX); - get_extension!(WSAID_CONNECTEX, CONNECT_EX); - get_extension!(WSAID_DISCONNECTEX, DISCONNECT_EX); - get_extension!(WSAID_TRANSMITFILE, TRANSMIT_FILE); - - unsafe { closesocket(s) }; - Ok(()) - } - #[pyattr] #[pyclass(name, traverse)] #[derive(PyPayload)] @@ -270,13 +183,6 @@ mod _overlapped { } } - fn mark_as_completed(ov: &mut OVERLAPPED) { - ov.Internal = 0; - if !ov.hEvent.is_null() { - unsafe { windows_sys::Win32::System::Threading::SetEvent(ov.hEvent) }; - } - } - fn set_from_windows_err(err: u32, vm: &VirtualMachine) -> PyBaseExceptionRef { let err = if err == 0 { unsafe { GetLastError() } @@ -292,10 +198,6 @@ mod _overlapped { exc.upcast() } - fn HasOverlappedIoCompleted(overlapped: &OVERLAPPED) -> bool { - overlapped.Internal != (Foundation::STATUS_PENDING as usize) - } - /// Parse a Python address tuple to SOCKADDR fn parse_address(addr_obj: &PyTupleRef, vm: &VirtualMachine) -> PyResult<(Vec, i32)> { use windows_sys::Win32::Networking::WinSock::{WSAGetLastError, WSAStringToAddressW}; @@ -423,7 +325,7 @@ mod _overlapped { #[pygetset] fn pending(&self, _vm: &VirtualMachine) -> bool { let inner = self.inner.lock(); - !HasOverlappedIoCompleted(&inner.overlapped) + !host_overlapped::has_overlapped_io_completed(&inner.overlapped) && !matches!(inner.data, OverlappedData::NotStarted) } @@ -448,16 +350,10 @@ mod _overlapped { ) { return Ok(()); } - let ret = if !HasOverlappedIoCompleted(&inner.overlapped) { - unsafe { - windows_sys::Win32::System::IO::CancelIoEx(inner.handle, &inner.overlapped) - } - } else { - 1 - }; - // CancelIoEx returns ERROR_NOT_FOUND if the I/O completed in-between - if ret == 0 && unsafe { GetLastError() } != Foundation::ERROR_NOT_FOUND { - return Err(set_from_windows_err(0, vm)); + if !host_overlapped::has_overlapped_io_completed(&inner.overlapped) { + host_overlapped::cancel_overlapped(inner.handle, &inner.overlapped).map_err( + |err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm), + )?; } Ok(()) } @@ -479,22 +375,10 @@ mod _overlapped { return Err(vm.new_value_error("operation failed to start")); } - // Get the result - let mut transferred: u32 = 0; - let ret = unsafe { - windows_sys::Win32::System::IO::GetOverlappedResult( - inner.handle, - &inner.overlapped, - &mut transferred, - if wait { 1 } else { 0 }, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let result = + host_overlapped::get_overlapped_result(inner.handle, &inner.overlapped, wait); + let transferred = result.transferred; + let err = result.error; inner.error = err; // Handle errors @@ -569,7 +453,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Storage::FileSystem::ReadFile; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -584,27 +467,17 @@ mod _overlapped { inner.handle = handle as HANDLE; inner.data = OverlappedData::Read(buf.clone()); - let mut nread: u32 = 0; - let ret = unsafe { - ReadFile( - handle as HANDLE, - buf.as_bytes().as_ptr() as *mut _, - size, - &mut nread, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_read_file( + handle as HANDLE, + buf.as_bytes().as_ptr() as *mut u8, + size, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -626,7 +499,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Storage::FileSystem::ReadFile; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -647,27 +519,17 @@ mod _overlapped { inner.data = OverlappedData::ReadInto(buf.clone()); - let mut nread: u32 = 0; - let ret = unsafe { - ReadFile( - handle as HANDLE, - contiguous.as_ptr() as *mut _, - buf_len as u32, - &mut nread, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_read_file( + handle as HANDLE, + contiguous.as_mut_ptr(), + buf_len as u32, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -690,7 +552,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecv}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -707,34 +568,18 @@ mod _overlapped { inner.handle = handle as HANDLE; inner.data = OverlappedData::Read(buf.clone()); - let wsabuf = WSABUF { - buf: buf.as_bytes().as_ptr() as *mut _, - len: size, - }; - let mut nread: u32 = 0; - - let ret = unsafe { - WSARecv( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv( + handle as usize, + buf.as_bytes().as_ptr() as *mut u8, + size, + &mut flags, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -757,7 +602,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecv}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -777,34 +621,18 @@ mod _overlapped { inner.data = OverlappedData::ReadInto(buf.clone()); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: buf_len as u32, - }; - let mut nread: u32 = 0; - - let ret = unsafe { - WSARecv( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv( + handle as usize, + contiguous.as_mut_ptr(), + buf_len as u32, + &mut flags, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -824,7 +652,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Storage::FileSystem::WriteFile; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -845,22 +672,12 @@ mod _overlapped { inner.data = OverlappedData::Write(buf.clone()); - let mut written: u32 = 0; - let ret = unsafe { - WriteFile( - handle as HANDLE, - contiguous.as_ptr() as *const _, - buf_len as u32, - &mut written, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = host_overlapped::start_write_file( + handle as HANDLE, + contiguous.as_ptr(), + buf_len as u32, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -882,7 +699,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASend}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -901,29 +717,13 @@ mod _overlapped { inner.data = OverlappedData::Write(buf.clone()); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: buf_len as u32, - }; - let mut written: u32 = 0; - - let ret = unsafe { - WSASend( - handle as _, - &wsabuf, - 1, - &mut written, - flags, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_send( + handle as usize, + contiguous.as_ptr(), + buf_len as u32, + flags, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -944,7 +744,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -959,39 +758,13 @@ mod _overlapped { inner.handle = listen_socket as HANDLE; inner.data = OverlappedData::Accept(buf.clone()); - let mut bytes_received: u32 = 0; - - type AcceptExFn = unsafe extern "system" fn( - sListenSocket: usize, - sAcceptSocket: usize, - lpOutputBuffer: *mut core::ffi::c_void, - dwReceiveDataLength: u32, - dwLocalAddressLength: u32, - dwRemoteAddressLength: u32, - lpdwBytesReceived: *mut u32, - lpOverlapped: *mut OVERLAPPED, - ) -> i32; - - let accept_ex: AcceptExFn = unsafe { core::mem::transmute(*ACCEPT_EX.get().unwrap()) }; - - let ret = unsafe { - accept_ex( - listen_socket as _, - accept_socket as _, - buf.as_bytes().as_ptr() as *mut _, - 0, - size as u32, - size as u32, - &mut bytes_received, - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = host_overlapped::start_accept_ex( + listen_socket as usize, + accept_socket as usize, + buf.as_bytes().as_ptr() as *mut u8, + size as u32, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1012,7 +785,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1025,42 +797,18 @@ mod _overlapped { // Store addr_bytes in OverlappedData to keep it alive during async operation inner.data = OverlappedData::Connect(addr_bytes); - type ConnectExFn = unsafe extern "system" fn( - s: usize, - name: *const SOCKADDR, - namelen: i32, - lpSendBuffer: *const core::ffi::c_void, - dwSendDataLength: u32, - lpdwBytesSent: *mut u32, - lpOverlapped: *mut OVERLAPPED, - ) -> i32; - - let connect_ex: ConnectExFn = - unsafe { core::mem::transmute(*CONNECT_EX.get().unwrap()) }; - // Get pointer to the stored address data let addr_ptr = match &inner.data { OverlappedData::Connect(bytes) => bytes.as_ptr(), _ => unreachable!(), }; - let ret = unsafe { - connect_ex( - socket as _, - addr_ptr as *const SOCKADDR, - addr_len, - core::ptr::null(), - 0, - core::ptr::null_mut(), - &mut inner.overlapped, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = host_overlapped::start_connect_ex( + socket as usize, + addr_ptr as *const SOCKADDR, + addr_len, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1081,7 +829,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1091,23 +838,8 @@ mod _overlapped { inner.handle = socket as HANDLE; inner.data = OverlappedData::Disconnect; - type DisconnectExFn = unsafe extern "system" fn( - s: usize, - lpOverlapped: *mut OVERLAPPED, - dwFlags: u32, - dwReserved: u32, - ) -> i32; - - let disconnect_ex: DisconnectExFn = - unsafe { core::mem::transmute(*DISCONNECT_EX.get().unwrap()) }; - - let ret = unsafe { disconnect_ex(socket as _, &mut inner.overlapped, flags, 0) }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = + host_overlapped::start_disconnect_ex(socket as usize, flags, &mut inner.overlapped); inner.error = err; match err { @@ -1137,7 +869,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::WSAGetLastError; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1146,39 +877,16 @@ mod _overlapped { inner.handle = socket as HANDLE; inner.data = OverlappedData::TransmitFile; - inner.overlapped.Anonymous.Anonymous.Offset = offset; - inner.overlapped.Anonymous.Anonymous.OffsetHigh = offset_high; - - type TransmitFileFn = unsafe extern "system" fn( - hSocket: usize, - hFile: HANDLE, - nNumberOfBytesToWrite: u32, - nNumberOfBytesPerSend: u32, - lpOverlapped: *mut OVERLAPPED, - lpTransmitBuffers: *const core::ffi::c_void, - dwReserved: u32, - ) -> i32; - - let transmit_file: TransmitFileFn = - unsafe { core::mem::transmute(*TRANSMIT_FILE.get().unwrap()) }; - - let ret = unsafe { - transmit_file( - socket as _, - file as HANDLE, - count_to_write, - count_per_send, - &mut inner.overlapped, - core::ptr::null(), - flags, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { WSAGetLastError() as u32 } - }; + let err = host_overlapped::start_transmit_file( + socket as usize, + file as HANDLE, + count_to_write, + count_per_send, + flags, + offset, + offset_high, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1196,7 +904,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, ERROR_SUCCESS, }; - use windows_sys::Win32::System::Pipes::ConnectNamedPipe; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1206,18 +913,13 @@ mod _overlapped { inner.handle = pipe as HANDLE; inner.data = OverlappedData::ConnectNamedPipe; - let ret = unsafe { ConnectNamedPipe(pipe as HANDLE, &mut inner.overlapped) }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; + let err = + host_overlapped::start_connect_named_pipe(pipe as HANDLE, &mut inner.overlapped); inner.error = err; match err { ERROR_PIPE_CONNECTED => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Ok(true) } ERROR_SUCCESS | ERROR_IO_PENDING => Ok(false), @@ -1239,7 +941,6 @@ mod _overlapped { vm: &VirtualMachine, ) -> PyResult { use windows_sys::Win32::Foundation::{ERROR_IO_PENDING, ERROR_SUCCESS}; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSASendTo}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1264,37 +965,21 @@ mod _overlapped { address: addr_bytes, }); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: buf_len as u32, - }; - let mut written: u32 = 0; - // Get pointer to the stored address data let addr_ptr = match &inner.data { OverlappedData::WriteTo(wt) => wt.address.as_ptr(), _ => unreachable!(), }; - let ret = unsafe { - WSASendTo( - handle as _, - &wsabuf, - 1, - &mut written, - flags, - addr_ptr as *const SOCKADDR, - addr_len, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_send_to( + handle as usize, + contiguous.as_ptr(), + buf_len as u32, + flags, + addr_ptr as *const SOCKADDR, + addr_len, + &mut inner.overlapped, + ); inner.error = err; match err { @@ -1318,7 +1003,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecvFrom}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1344,12 +1028,6 @@ mod _overlapped { address_length, }); - let wsabuf = WSABUF { - buf: buf.as_bytes().as_ptr() as *mut _, - len: size, - }; - let mut nread: u32 = 0; - // Get mutable reference to address in inner.data let (addr_ptr, addr_len_ptr) = match &mut inner.data { OverlappedData::ReadFrom(rf) => ( @@ -1359,30 +1037,20 @@ mod _overlapped { _ => unreachable!(), }; - let ret = unsafe { - WSARecvFrom( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - addr_ptr as *mut SOCKADDR, - addr_len_ptr, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv_from( + handle as usize, + buf.as_bytes().as_ptr() as *mut u8, + size, + &mut flags, + addr_ptr as *mut SOCKADDR, + addr_len_ptr, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -1406,7 +1074,6 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_MORE_DATA, ERROR_SUCCESS, }; - use windows_sys::Win32::Networking::WinSock::{WSABUF, WSAGetLastError, WSARecvFrom}; let mut inner = zelf.inner.lock(); if !matches!(inner.data, OverlappedData::None) { @@ -1435,12 +1102,6 @@ mod _overlapped { address_length, }); - let wsabuf = WSABUF { - buf: contiguous.as_ptr() as *mut _, - len: size, - }; - let mut nread: u32 = 0; - // Get mutable reference to address in inner.data let (addr_ptr, addr_len_ptr) = match &mut inner.data { OverlappedData::ReadFromInto(rfi) => ( @@ -1450,30 +1111,20 @@ mod _overlapped { _ => unreachable!(), }; - let ret = unsafe { - WSARecvFrom( - handle as _, - &wsabuf, - 1, - &mut nread, - &mut flags, - addr_ptr as *mut SOCKADDR, - addr_len_ptr, - &mut inner.overlapped, - None, - ) - }; - - let err = if ret < 0 { - unsafe { WSAGetLastError() as u32 } - } else { - ERROR_SUCCESS - }; + let err = host_overlapped::start_wsa_recv_from( + handle as usize, + contiguous.as_mut_ptr(), + size, + &mut flags, + addr_ptr as *mut SOCKADDR, + addr_len_ptr, + &mut inner.overlapped, + ); inner.error = err; match err { ERROR_BROKEN_PIPE => { - mark_as_completed(&mut inner.overlapped); + host_overlapped::mark_as_completed(&mut inner.overlapped); Err(set_from_windows_err(err, vm)) } ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_IO_PENDING => Ok(vm.ctx.none()), @@ -1492,17 +1143,11 @@ mod _overlapped { let mut event = event.unwrap_or(INVALID_HANDLE_VALUE); if event == INVALID_HANDLE_VALUE { - event = unsafe { - windows_sys::Win32::System::Threading::CreateEventW( - core::ptr::null(), - Foundation::TRUE, - Foundation::FALSE, - core::ptr::null(), - ) as isize - }; - if event == NULL { - return Err(set_from_windows_err(0, vm)); - } + event = host_winapi::create_event_w(true, false, core::ptr::null()) + .map(|handle| handle as isize) + .map_err(|err| { + set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm) + })?; } let mut overlapped: OVERLAPPED = unsafe { core::mem::zeroed() }; @@ -1526,32 +1171,17 @@ mod _overlapped { use windows_sys::Win32::Foundation::{ ERROR_NOT_FOUND, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, }; - use windows_sys::Win32::System::IO::{CancelIoEx, GetOverlappedResult}; let mut inner = zelf.inner.lock(); let olderr = unsafe { GetLastError() }; // Cancel pending I/O and wait for completion - if !HasOverlappedIoCompleted(&inner.overlapped) + if !host_overlapped::has_overlapped_io_completed(&inner.overlapped) && !matches!(inner.data, OverlappedData::NotStarted) { - let cancelled = unsafe { CancelIoEx(inner.handle, &inner.overlapped) } != 0; - let mut transferred: u32 = 0; - let ret = unsafe { - GetOverlappedResult( - inner.handle, - &inner.overlapped, - &mut transferred, - if cancelled { 1 } else { 0 }, - ) - }; - - let err = if ret != 0 { - ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; - match err { + match host_overlapped::cancel_overlapped_for_drop(inner.handle, &inner.overlapped) + .error + { ERROR_SUCCESS | ERROR_NOT_FOUND | ERROR_OPERATION_ABORTED => {} _ => { let msg = format!( @@ -1571,9 +1201,7 @@ mod _overlapped { // Close the event handle if !inner.overlapped.hEvent.is_null() { - unsafe { - Foundation::CloseHandle(inner.overlapped.hEvent); - } + let _ = host_winapi::close_handle(inner.overlapped.hEvent); inner.overlapped.hEvent = core::ptr::null_mut(); } @@ -1586,30 +1214,8 @@ mod _overlapped { #[pyfunction] fn ConnectPipe(address: String, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_OVERLAPPED, OPEN_EXISTING, - }; - - let address_wide: Vec = address.encode_utf16().chain(core::iter::once(0)).collect(); - - let handle = unsafe { - CreateFileW( - address_wide.as_ptr(), - GENERIC_READ | GENERIC_WRITE, - 0, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - core::ptr::null_mut(), - ) - }; - - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(set_from_windows_err(0, vm)); - } - - Ok(handle as isize) + host_overlapped::connect_pipe(&address) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] @@ -1620,54 +1226,26 @@ mod _overlapped { concurrency: u32, vm: &VirtualMachine, ) -> PyResult { - let r = unsafe { - windows_sys::Win32::System::IO::CreateIoCompletionPort( - handle as HANDLE, - port as HANDLE, - key, - concurrency, - ) as isize - }; - if r == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(r) + host_overlapped::create_io_completion_port(handle, port, key, concurrency) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn GetQueuedCompletionStatus(port: isize, msecs: u32, vm: &VirtualMachine) -> PyResult { - let mut bytes_transferred = 0; - let mut completion_key = 0; - let mut overlapped: *mut OVERLAPPED = core::ptr::null_mut(); - let ret = unsafe { - windows_sys::Win32::System::IO::GetQueuedCompletionStatus( - port as HANDLE, - &mut bytes_transferred, - &mut completion_key, - &mut overlapped, - msecs, - ) - }; - let err = if ret != 0 { - Foundation::ERROR_SUCCESS - } else { - unsafe { GetLastError() } - }; - if overlapped.is_null() { - if err == Foundation::WAIT_TIMEOUT { - return Ok(vm.ctx.none()); - } else { - return Err(set_from_windows_err(err, vm)); - } + match host_overlapped::get_queued_completion_status(port, msecs) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm))? + { + host_overlapped::WaitResult::Timeout => Ok(vm.ctx.none()), + host_overlapped::WaitResult::Queued(status) => Ok(vm + .ctx + .new_tuple(vec![ + status.error.to_pyobject(vm), + status.bytes_transferred.to_pyobject(vm), + status.completion_key.to_pyobject(vm), + status.overlapped.to_pyobject(vm), + ]) + .into()), } - - let value = vm.ctx.new_tuple(vec![ - err.to_pyobject(vm), - bytes_transferred.to_pyobject(vm), - completion_key.to_pyobject(vm), - (overlapped as usize).to_pyobject(vm), - ]); - Ok(value.into()) } #[pyfunction] @@ -1678,64 +1256,8 @@ mod _overlapped { address: usize, vm: &VirtualMachine, ) -> PyResult<()> { - let ret = unsafe { - windows_sys::Win32::System::IO::PostQueuedCompletionStatus( - port as HANDLE, - bytes, - key, - address as *mut OVERLAPPED, - ) - }; - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) - } - - // Registry to track callback data for proper cleanup - // Uses Arc for reference counting to prevent use-after-free when callback - // and UnregisterWait race - the data stays alive until both are done - static WAIT_CALLBACK_REGISTRY: std::sync::OnceLock< - std::sync::Mutex>>, - > = std::sync::OnceLock::new(); - - fn wait_callback_registry() -> &'static std::sync::Mutex< - std::collections::HashMap>, - > { - WAIT_CALLBACK_REGISTRY - .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())) - } - - // Callback data for RegisterWaitWithQueue - // Uses Arc to ensure the data stays alive while callback is executing - struct PostCallbackData { - completion_port: HANDLE, - overlapped: *mut OVERLAPPED, - } - - // SAFETY: The pointers are handles/addresses passed from Python and are - // only used to call Windows APIs. They are not dereferenced as Rust pointers. - unsafe impl Send for PostCallbackData {} - unsafe impl Sync for PostCallbackData {} - - unsafe extern "system" fn post_to_queue_callback( - parameter: *mut core::ffi::c_void, - timer_or_wait_fired: bool, - ) { - // Reconstruct Arc from raw pointer - this gives us ownership of one reference - // The Arc prevents use-after-free since we own a reference count - let data = unsafe { alloc::sync::Arc::from_raw(parameter as *const PostCallbackData) }; - - unsafe { - let _ = windows_sys::Win32::System::IO::PostQueuedCompletionStatus( - data.completion_port, - if timer_or_wait_fired { 1 } else { 0 }, - 0, - data.overlapped, - ); - } - // Arc is dropped here, decrementing refcount - // Memory is freed only when all references (callback + registry) are gone + host_overlapped::post_queued_completion_status(port, bytes, key, address) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] @@ -1746,193 +1268,43 @@ mod _overlapped { timeout: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::{ - RegisterWaitForSingleObject, WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE, - }; - - let data = alloc::sync::Arc::new(PostCallbackData { - completion_port: completion_port as HANDLE, - overlapped: overlapped as *mut OVERLAPPED, - }); - - // Create raw pointer for the callback - this increments refcount - let data_ptr = alloc::sync::Arc::into_raw(data.clone()); - - let mut new_wait_object: HANDLE = core::ptr::null_mut(); - let ret = unsafe { - RegisterWaitForSingleObject( - &mut new_wait_object, - object as HANDLE, - Some(post_to_queue_callback), - data_ptr as *mut _, - timeout, - WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE, - ) - }; - - if ret == 0 { - // Registration failed - reconstruct Arc to drop the extra reference - unsafe { - let _ = alloc::sync::Arc::from_raw(data_ptr); - } - return Err(set_from_windows_err(0, vm)); - } - - // Store in registry for cleanup tracking - let wait_handle = new_wait_object as isize; - if let Ok(mut registry) = wait_callback_registry().lock() { - registry.insert(wait_handle, data); - } - - Ok(wait_handle) - } - - // Helper to cleanup callback data when unregistering - // Just removes from registry - Arc ensures memory stays alive if callback is running - fn cleanup_wait_callback_data(wait_handle: isize) { - if let Ok(mut registry) = wait_callback_registry().lock() { - // Removing from registry drops one Arc reference - // If callback already ran, this frees the memory - // If callback is still pending/running, it holds the other reference - registry.remove(&wait_handle); - } + host_overlapped::register_wait_with_queue(object, completion_port, overlapped, timeout) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn UnregisterWait(wait_handle: isize, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::UnregisterWait; - - let ret = unsafe { UnregisterWait(wait_handle as HANDLE) }; - // Cleanup callback data regardless of UnregisterWait result - // (callback may have already fired, or may never fire) - cleanup_wait_callback_data(wait_handle); - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_overlapped::unregister_wait(wait_handle) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn UnregisterWaitEx(wait_handle: isize, event: isize, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::UnregisterWaitEx; - - let ret = unsafe { UnregisterWaitEx(wait_handle as HANDLE, event as HANDLE) }; - // Cleanup callback data regardless of UnregisterWaitEx result - cleanup_wait_callback_data(wait_handle); - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_overlapped::unregister_wait_ex(wait_handle, event) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn BindLocal(socket: isize, family: i32, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Networking::WinSock::{ - INADDR_ANY, SOCKET_ERROR, WSAGetLastError, bind, - }; - - let ret = if family == AF_INET as i32 { - let mut addr: SOCKADDR_IN = unsafe { core::mem::zeroed() }; - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.S_un.S_addr = INADDR_ANY; - unsafe { - bind( - socket as _, - &addr as *const _ as *const SOCKADDR, - core::mem::size_of::() as i32, - ) - } - } else if family == AF_INET6 as i32 { - // in6addr_any is all zeros, which we have from zeroed() - let mut addr: SOCKADDR_IN6 = unsafe { core::mem::zeroed() }; - addr.sin6_family = AF_INET6; - addr.sin6_port = 0; - unsafe { - bind( - socket as _, - &addr as *const _ as *const SOCKADDR, - core::mem::size_of::() as i32, - ) + host_overlapped::bind_local(socket, family).map_err(|err| { + if err.kind() == std::io::ErrorKind::InvalidInput { + vm.new_value_error("expected tuple of length 2 or 4") + } else { + set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm) } - } else { - return Err(vm.new_value_error("expected tuple of length 2 or 4")); - }; - - if ret == SOCKET_ERROR { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - Ok(()) + }) } #[pyfunction] fn FormatMessage(error_code: u32, _vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::LocalFree; - use windows_sys::Win32::System::Diagnostics::Debug::{ - FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, - FORMAT_MESSAGE_IGNORE_INSERTS, FormatMessageW, - }; - - // LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1 - const LANG_NEUTRAL: u32 = 0; - const SUBLANG_DEFAULT: u32 = 1; - - let mut buffer: *mut u16 = core::ptr::null_mut(); - - let len = unsafe { - FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS, - core::ptr::null(), - error_code, - (SUBLANG_DEFAULT << 10) | LANG_NEUTRAL, - &mut buffer as *mut _ as *mut u16, - 0, - core::ptr::null(), - ) - }; - - if len == 0 || buffer.is_null() { - if !buffer.is_null() { - unsafe { LocalFree(buffer as *mut _) }; - } - return Ok(format!("unknown error code {}", error_code)); - } - - // Convert to Rust string, trimming trailing whitespace - let slice = unsafe { core::slice::from_raw_parts(buffer, len as usize) }; - let msg = String::from_utf16_lossy(slice).trim_end().to_string(); - - unsafe { LocalFree(buffer as *mut _) }; - - Ok(msg) + Ok(host_overlapped::format_message(error_code)) } #[pyfunction] fn WSAConnect(socket: isize, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Networking::WinSock::{SOCKET_ERROR, WSAConnect, WSAGetLastError}; - let (addr_bytes, addr_len) = parse_address(&address, vm)?; - - let ret = unsafe { - WSAConnect( - socket as _, - addr_bytes.as_ptr() as *const SOCKADDR, - addr_len, - core::ptr::null(), - core::ptr::null_mut(), - core::ptr::null(), - core::ptr::null(), - ) - }; - - if ret == SOCKET_ERROR { - let err = unsafe { WSAGetLastError() } as u32; - return Err(set_from_windows_err(err, vm)); - } - Ok(()) + host_overlapped::wsa_connect(socket, addr_bytes.as_ptr() as *const SOCKADDR, addr_len) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] @@ -1949,40 +1321,24 @@ mod _overlapped { let name_wide: Option> = name.map(|n| n.encode_utf16().chain(core::iter::once(0)).collect()); - let name_ptr = name_wide - .as_ref() - .map(|n| n.as_ptr()) - .unwrap_or(core::ptr::null()); - - let event = unsafe { - windows_sys::Win32::System::Threading::CreateEventW( - core::ptr::null(), - if manual_reset { 1 } else { 0 }, - if initial_state { 1 } else { 0 }, - name_ptr, - ) as isize - }; - if event == NULL { - return Err(set_from_windows_err(0, vm)); - } - Ok(event) + host_winapi::create_event_w( + manual_reset, + initial_state, + name_wide.as_ref().map_or(core::ptr::null(), |n| n.as_ptr()), + ) + .map(|h| h as isize) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn SetEvent(handle: isize, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { windows_sys::Win32::System::Threading::SetEvent(handle as HANDLE) }; - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_winapi::set_event(handle as HANDLE) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } #[pyfunction] fn ResetEvent(handle: isize, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { windows_sys::Win32::System::Threading::ResetEvent(handle as HANDLE) }; - if ret == 0 { - return Err(set_from_windows_err(0, vm)); - } - Ok(()) + host_winapi::reset_event(handle as HANDLE) + .map_err(|err| set_from_windows_err(err.raw_os_error().unwrap_or(0) as u32, vm)) } } diff --git a/crates/stdlib/src/posixsubprocess.rs b/crates/stdlib/src/posixsubprocess.rs index 01b0d466ffc..2a242d51d3e 100644 --- a/crates/stdlib/src/posixsubprocess.rs +++ b/crates/stdlib/src/posixsubprocess.rs @@ -8,10 +8,8 @@ use crate::vm::{ {PyObjectRef, PyResult, TryFromObject, VirtualMachine}, }; use itertools::Itertools; -use nix::{ - errno::Errno, - unistd::{self, Pid}, -}; +use nix::{errno::Errno, unistd}; +use rustpython_host_env::posix as host_posix; use std::{ io::prelude::*, os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd}, @@ -328,42 +326,20 @@ fn exec_inner( dup_into_stdio(errwrite, 2, unistd::dup2_stderr)?; if let Some(ref cwd) = args.cwd { - unistd::chdir(cwd.s.as_c_str()).inspect_err(|_| *ctx = ExecErrorContext::ChDir)? + host_posix::chdir(cwd.s.as_c_str()).inspect_err(|_| *ctx = ExecErrorContext::ChDir)? } - if args.child_umask >= 0 { - unsafe { libc::umask(args.child_umask as libc::mode_t) }; - } + host_posix::set_umask(args.child_umask); if args.restore_signals { - unsafe { - libc::signal(libc::SIGPIPE, libc::SIG_DFL); - libc::signal(libc::SIGXFSZ, libc::SIG_DFL); - } + host_posix::restore_signals(); } - if args.call_setsid { - unistd::setsid()?; - } - - if args.pgid_to_set > -1 { - unistd::setpgid(Pid::from_raw(0), Pid::from_raw(args.pgid_to_set))?; - } - - if let Some(_groups) = procargs.extra_groups { - #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] - unistd::setgroups(_groups)?; - } - - if let Some(gid) = args.gid.filter(|x| x.as_raw() != u32::MAX) { - let ret = unsafe { libc::setregid(gid.as_raw(), gid.as_raw()) }; - nix::Error::result(ret)?; - } - - if let Some(uid) = args.uid.filter(|x| x.as_raw() != u32::MAX) { - let ret = unsafe { libc::setreuid(uid.as_raw(), uid.as_raw()) }; - nix::Error::result(ret)?; - } + host_posix::setsid_if_needed(args.call_setsid)?; + host_posix::setpgid_if_needed(args.pgid_to_set)?; + host_posix::setgroups_if_needed(procargs.extra_groups)?; + host_posix::setregid_if_needed(args.gid)?; + host_posix::setreuid_if_needed(args.uid)?; // Call preexec_fn after all process setup but before closing FDs if let Some(ref preexec_fn) = args.preexec_fn { @@ -388,8 +364,6 @@ fn exec_inner( let mut first_err = None; for exec in args.exec_list.as_slice() { - // not using nix's versions of these functions because those allocate the char-ptr array, - // and we can't allocate if let Some(envp) = procargs.envp { unsafe { libc::execve(exec.s.as_ptr(), procargs.argv.as_ptr(), envp.as_ptr()) }; } else { diff --git a/crates/stdlib/src/resource.rs b/crates/stdlib/src/resource.rs index 34c8161e0cd..f05bef4158c 100644 --- a/crates/stdlib/src/resource.rs +++ b/crates/stdlib/src/resource.rs @@ -9,7 +9,7 @@ mod resource { convert::{ToPyException, ToPyObject}, types::PyStructSequence, }; - use core::mem; + use rustpython_host_env::resource as host_resource; use std::io; cfg_if::cfg_if! { @@ -90,8 +90,8 @@ mod resource { #[pyclass(with(PyStructSequence))] impl PyRUsage {} - impl From for RUsageData { - fn from(rusage: libc::rusage) -> Self { + impl From for RUsageData { + fn from(rusage: host_resource::RUsage) -> Self { let tv = |tv: libc::timeval| tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0); Self { ru_utime: tv(rusage.ru_utime), @@ -116,14 +116,7 @@ mod resource { #[pyfunction] fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult { - let res = unsafe { - let mut rusage = mem::MaybeUninit::::uninit(); - if libc::getrusage(who, rusage.as_mut_ptr()) == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(rusage.assume_init()) - } - }; + let res = host_resource::getrusage(who); res.map(RUsageData::from).map_err(|e| { if e.kind() == io::ErrorKind::InvalidInput { vm.new_value_error("invalid who parameter") @@ -158,13 +151,7 @@ mod resource { if resource < 0 || resource >= RLIM_NLIMITS as i32 { return Err(vm.new_value_error("invalid resource specified")); } - let rlimit = unsafe { - let mut rlimit = mem::MaybeUninit::::uninit(); - if libc::getrlimit(resource as _, rlimit.as_mut_ptr()) == -1 { - return Err(vm.new_last_errno_error()); - } - rlimit.assume_init() - }; + let rlimit = host_resource::getrlimit(resource).map_err(|_| vm.new_last_errno_error())?; Ok(Limits(rlimit)) } @@ -174,13 +161,7 @@ mod resource { if resource < 0 || resource >= RLIM_NLIMITS as i32 { return Err(vm.new_value_error("invalid resource specified")); } - let res = unsafe { - if libc::setrlimit(resource as _, &limits.0) == -1 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - }; + let res = host_resource::setrlimit(resource, limits.0); res.map_err(|e| match e.kind() { io::ErrorKind::InvalidInput => { vm.new_value_error("current limit exceeds maximum limit") diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index 1cfe22e7c61..2d22306919f 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -165,7 +165,6 @@ mod decl { stdlib::_io::Fildes, }; use core::{convert::TryFrom, time::Duration}; - use libc::pollfd; use num_traits::{Signed, ToPrimitive}; use std::time::Instant; @@ -215,34 +214,7 @@ mod decl { #[derive(Default, Debug, PyPayload)] pub struct PyPoll { // keep sorted - fds: PyMutex>, - } - - #[inline] - fn search(fds: &[pollfd], fd: i32) -> Result { - fds.binary_search_by_key(&fd, |pfd| pfd.fd) - } - - fn insert_fd(fds: &mut Vec, fd: i32, events: i16) { - match search(fds, fd) { - Ok(i) => fds[i].events = events, - Err(i) => fds.insert( - i, - pollfd { - fd, - events, - revents: 0, - }, - ), - } - } - - fn get_fd_mut(fds: &mut [pollfd], fd: i32) -> Option<&mut pollfd> { - search(fds, fd).ok().map(move |i| &mut fds[i]) - } - - fn remove_fd(fds: &mut Vec, fd: i32) -> Option { - search(fds, fd).ok().map(|i| fds.remove(i)) + fds: PyMutex>, } // new EventMask type @@ -284,7 +256,7 @@ mod decl { OptionalArg::Present(event_mask) => event_mask.0, OptionalArg::Missing => DEFAULT_EVENTS, }; - insert_fd(&mut self.fds.lock(), fd, mask); + host_select::insert_poll_fd(&mut self.fds.lock(), fd, mask); Ok(()) } @@ -297,7 +269,7 @@ mod decl { ) -> PyResult<()> { let mut fds = self.fds.lock(); // CPython raises KeyError if fd is not registered, match that behavior - let pfd = get_fd_mut(&mut fds, fd) + let pfd = host_select::get_poll_fd_mut(&mut fds, fd) .ok_or_else(|| vm.new_key_error(vm.ctx.new_int(fd).into()))?; pfd.events = eventmask.0; Ok(()) @@ -305,7 +277,7 @@ mod decl { #[pymethod] fn unregister(&self, Fildes(fd): Fildes, vm: &VirtualMachine) -> PyResult<()> { - let removed = remove_fd(&mut self.fds.lock(), fd); + let removed = host_select::remove_poll_fd(&mut self.fds.lock(), fd); removed .map(drop) .ok_or_else(|| vm.new_key_error(vm.ctx.new_int(fd).into())) @@ -327,13 +299,10 @@ mod decl { let deadline = timeout.map(|d| Instant::now() + d); let mut poll_timeout = timeout_ms; loop { - let res = vm.allow_threads(|| unsafe { - libc::poll(fds.as_mut_ptr(), fds.len() as _, poll_timeout) - }); - match nix::Error::result(res) { + match vm.allow_threads(|| host_select::poll_fds(&mut fds, poll_timeout)) { Ok(_) => break, Err(nix::Error::EINTR) => vm.check_signals()?, - Err(e) => return Err(e.into_pyexception(vm)), + Err(err) => return Err(err.into_pyexception(vm)), } if let Some(d) = deadline { if let Some(remaining) = d.checked_duration_since(Instant::now()) { @@ -383,7 +352,6 @@ mod decl { types::Constructor, }; use core::ops::Deref; - use rustix::event::epoll::{self, EventData, EventFlags}; use std::os::fd::{AsRawFd, IntoRawFd, OwnedFd}; use std::time::Instant; @@ -426,7 +394,7 @@ mod decl { #[pyclass(with(Constructor))] impl PyEpoll { fn new() -> std::io::Result { - let epoll_fd = epoll::create(epoll::CreateFlags::CLOEXEC)?; + let epoll_fd = host_select::epoll::create()?; let epoll_fd = Some(epoll_fd).into(); Ok(Self { epoll_fd }) } @@ -435,7 +403,7 @@ mod decl { fn close(&self) -> std::io::Result<()> { let fd = self.epoll_fd.write().take(); if let Some(fd) = fd { - nix::unistd::close(fd.into_raw_fd())?; + host_select::epoll::close(fd)?; } Ok(()) } @@ -472,26 +440,28 @@ mod decl { vm: &VirtualMachine, ) -> PyResult<()> { let events = match eventmask { - OptionalArg::Present(mask) => EventFlags::from_bits_retain(mask), - OptionalArg::Missing => EventFlags::IN | EventFlags::PRI | EventFlags::OUT, + OptionalArg::Present(mask) => mask, + OptionalArg::Missing => (host_select::epoll::EventFlags::IN + | host_select::epoll::EventFlags::PRI + | host_select::epoll::EventFlags::OUT) + .bits(), }; let epoll_fd = &*self.get_epoll(vm)?; - let data = EventData::new_u64(fd.as_raw_fd() as u64); - epoll::add(epoll_fd, fd, data, events).map_err(|e| e.into_pyexception(vm)) + host_select::epoll::add(epoll_fd, fd, fd.as_raw_fd() as u64, events) + .map_err(|e| e.into_pyexception(vm)) } #[pymethod] fn modify(&self, fd: Fildes, eventmask: u32, vm: &VirtualMachine) -> PyResult<()> { - let events = EventFlags::from_bits_retain(eventmask); let epoll_fd = &*self.get_epoll(vm)?; - let data = EventData::new_u64(fd.as_raw_fd() as u64); - epoll::modify(epoll_fd, fd, data, events).map_err(|e| e.into_pyexception(vm)) + host_select::epoll::modify(epoll_fd, fd, fd.as_raw_fd() as u64, eventmask) + .map_err(|e| e.into_pyexception(vm)) } #[pymethod] fn unregister(&self, fd: Fildes, vm: &VirtualMachine) -> PyResult<()> { let epoll_fd = &*self.get_epoll(vm)?; - epoll::delete(epoll_fd, fd).map_err(|e| e.into_pyexception(vm)) + host_select::epoll::delete(epoll_fd, fd).map_err(|e| e.into_pyexception(vm)) } #[pymethod] @@ -499,11 +469,10 @@ mod decl { let poll::TimeoutArg(timeout) = args.timeout; let maxevents = args.maxevents; - let mut poll_timeout = - timeout - .map(rustix::event::Timespec::try_from) - .transpose() - .map_err(|_| vm.new_overflow_error("timeout is too large"))?; + let mut poll_timeout = timeout + .map(host_select::epoll::Timespec::try_from) + .transpose() + .map_err(|_| vm.new_overflow_error("timeout is too large"))?; let deadline = timeout.map(|d| Instant::now() + d); let maxevents = match maxevents { @@ -516,18 +485,13 @@ mod decl { _ => maxevents as usize, }; - let mut events = Vec::::with_capacity(maxevents); + let mut events = Vec::::with_capacity(maxevents); let epoll = &*self.get_epoll(vm)?; loop { - events.clear(); match vm.allow_threads(|| { - epoll::wait( - epoll, - rustix::buffer::spare_capacity(&mut events), - poll_timeout.as_ref(), - ) + host_select::epoll::wait(epoll, &mut events, poll_timeout.as_ref()) }) { Ok(_) => break, Err(rustix::io::Errno::INTR) => vm.check_signals()?, diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 1537a8fd517..b07504162bf 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -108,9 +108,6 @@ libloading = "0.9" [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] num_cpus = "1.17.0" -[target.'cfg(windows)'.dependencies] -junction = { workspace = true } - [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index 15111952d56..d8539eb3488 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -6001,16 +6001,8 @@ mod winconsoleio { types::{Constructor, DefaultConstructor, Destructor, Initializer, Representable}, }; use crossbeam_utils::atomic::AtomicCell; - use windows_sys::Win32::{ - Foundation::{self, GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, - Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte}, - Storage::FileSystem::{ - CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFullPathNameW, OPEN_EXISTING, - }, - System::Console::{ - GetConsoleMode, GetNumberOfConsoleInputEvents, ReadConsoleW, WriteConsoleW, - }, - }; + use rustpython_host_env::nt as host_nt; + use windows_sys::Win32::Foundation::{self, INVALID_HANDLE_VALUE}; type HANDLE = Foundation::HANDLE; @@ -6025,33 +6017,12 @@ mod winconsoleio { handle == INVALID_HANDLE_VALUE || handle.is_null() } - /// Check if a HANDLE is a console and what type ('r', 'w', or '\0'). - fn get_console_type(handle: HANDLE) -> char { - if is_invalid_handle(handle) { - return '\0'; - } - let mut mode: u32 = 0; - if unsafe { GetConsoleMode(handle, &mut mode) } == 0 { - return '\0'; - } - let mut peek_count: u32 = 0; - if unsafe { GetNumberOfConsoleInputEvents(handle, &mut peek_count) } != 0 { - 'r' - } else { - 'w' - } - } - /// Check if a Python object (fd or path string) refers to a console. /// Returns 'r' (input), 'w' (output), 'x' (generic CON), or '\0' (not a console). pub(super) fn pyio_get_console_type(path_or_fd: &PyObject, vm: &VirtualMachine) -> char { // Try as integer fd first if let Ok(fd) = i32::try_from_object(vm, path_or_fd.to_owned()) { - if fd >= 0 { - let handle = handle_from_fd(fd); - return get_console_type(handle); - } - return '\0'; + return host_nt::console_type_from_fd(fd); } // Try as string path @@ -6062,80 +6033,7 @@ mod winconsoleio { // Surrogate strings can't be console device names return '\0'; }; - - if name_str.eq_ignore_ascii_case("CONIN$") { - return 'r'; - } - if name_str.eq_ignore_ascii_case("CONOUT$") { - return 'w'; - } - if name_str.eq_ignore_ascii_case("CON") { - return 'x'; - } - - // Resolve full path and check for console device names - let wide: Vec = name_str.encode_utf16().chain(core::iter::once(0)).collect(); - let mut buf = [0u16; 260]; // MAX_PATH - 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]; - // Skip \\?\ or \\.\ prefix - 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' - } - } - - /// Find the last valid UTF-8 boundary in a byte slice. - 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 { - // Incomplete multibyte sequence - return len - count; - } - return len; - } - } - len + host_nt::console_type_from_name(name_str) } #[pyattr] @@ -6285,55 +6183,8 @@ mod winconsoleio { .chain(core::iter::once(0)) .collect(); - let access = if writable { - GENERIC_WRITE - } else { - GENERIC_READ - }; - - // Try read/write first, fall back to specific access - let mut handle: HANDLE = unsafe { - CreateFileW( - wide.as_ptr(), - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - core::ptr::null(), - OPEN_EXISTING, - 0, - core::ptr::null_mut(), - ) - }; - if is_invalid_handle(handle) { - handle = unsafe { - CreateFileW( - wide.as_ptr(), - access, - FILE_SHARE_READ | FILE_SHARE_WRITE, - core::ptr::null(), - OPEN_EXISTING, - 0, - core::ptr::null_mut(), - ) - }; - } - - if is_invalid_handle(handle) { - return Err(std::io::Error::last_os_error().to_pyexception(vm)); - } - - let osf_flags = if writable { - libc::O_WRONLY | libc::O_BINARY | 0x80 /* O_NOINHERIT */ - } else { - libc::O_RDONLY | libc::O_BINARY | 0x80 /* O_NOINHERIT */ - }; - - fd = unsafe { libc::open_osfhandle(handle as isize, osf_flags) }; - if fd < 0 { - unsafe { - Foundation::CloseHandle(handle); - } - return Err(std::io::Error::last_os_error().to_pyexception(vm)); - } + fd = host_nt::open_console_path_fd(wide.as_ptr(), writable) + .map_err(|err| err.to_pyexception(vm))?; _name_wide = Some(wide); } else { @@ -6346,7 +6197,7 @@ mod winconsoleio { // Validate console type if console_type == '\0' { let handle = handle_from_fd(fd); - console_type = get_console_type(handle); + console_type = host_nt::console_type(handle); } if console_type == '\0' { @@ -6559,116 +6410,18 @@ mod winconsoleio { return Err(std::io::Error::last_os_error().to_pyexception(vm)); } - // Each character may take up to 4 bytes in UTF-8. - let mut wlen = (len / 4) as u32; - if wlen == 0 { - wlen = 1; - } - let dest = &mut *buf_ref; - - // Copy from internal buffer first - let mut read_len = { - let mut buf = self.buf.lock(); - Self::copy_from_buf(&mut buf, dest) - }; - if read_len > 0 { - wlen = wlen.saturating_sub(1); - } - if read_len >= len || wlen == 0 { - return Ok(read_len); - } - - // Read from console - let mut wbuf = vec![0u16; wlen as usize]; - let mut nread: u32 = 0; - let res = unsafe { - ReadConsoleW( - handle, - wbuf.as_mut_ptr() as _, - wlen, - &mut nread, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - if nread == 0 { - return Ok(read_len); - } - - // Check for Ctrl+Z (EOF) - if nread > 0 && wbuf[0] == 0x1A { - return Ok(read_len); - } - - // Convert wchar to UTF-8 - let remaining = len - read_len; - let u8n; - if remaining < 4 { - // Buffer the result in the internal small buffer - let mut buf = self.buf.lock(); - let converted = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - buf.as_mut_ptr() as _, - SMALLBUF as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if converted > 0 { - u8n = Self::copy_from_buf(&mut buf, &mut dest[read_len..]) as i32; - } else { - u8n = 0; - } - } else { - u8n = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - dest[read_len..].as_mut_ptr() as _, - remaining as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; + let mut smallbuf = self.buf.lock(); + match host_nt::read_console_into(handle, dest, &mut smallbuf) { + Ok(read_len) => Ok(read_len), + Err(host_nt::ReadConsoleError::BufferTooSmall { + available, + required, + }) => Err(vm.new_system_error(format!( + "Buffer had room for {available} bytes but {required} bytes required", + ))), + Err(host_nt::ReadConsoleError::Io(err)) => Err(err.into_pyexception(vm)), } - - if u8n > 0 { - read_len += u8n as usize; - } else { - let err = std::io::Error::last_os_error(); - if err.raw_os_error() == Some(122) { - // ERROR_INSUFFICIENT_BUFFER - 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(vm.new_system_error(format!( - "Buffer had room for {remaining} bytes but {needed} bytes required", - ))); - } - } - return Err(err.into_pyexception(vm)); - } - - Ok(read_len) } #[pymethod] @@ -6682,77 +6435,9 @@ mod winconsoleio { return Err(std::io::Error::last_os_error().to_pyexception(vm)); } - let mut result = Vec::new(); - - // Copy any buffered bytes first - { - let mut buf = self.buf.lock(); - let mut tmp = [0u8; SMALLBUF]; - let n = Self::copy_from_buf(&mut buf, &mut tmp); - result.extend_from_slice(&tmp[..n]); - } - - let mut wbuf = vec![0u16; 8192]; - loop { - let mut nread: u32 = 0; - let res = unsafe { - ReadConsoleW( - handle, - wbuf.as_mut_ptr() as _, - wbuf.len() as u32, - &mut nread, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - if nread == 0 { - break; - } - // Ctrl+Z at start -> EOF - if wbuf[0] == 0x1A { - break; - } - // Convert to UTF-8 - 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(std::io::Error::last_os_error().into_pyexception(vm)); - } - let offset = result.len(); - result.resize(offset + needed as usize, 0); - let written = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - result[offset..].as_mut_ptr() as _, - needed, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if written == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - // If we didn't fill the buffer, no more data - if nread < wbuf.len() as u32 { - break; - } - } - + let mut smallbuf = self.buf.lock(); + let result = host_nt::read_console_all(handle, &mut smallbuf) + .map_err(|err| err.into_pyexception(vm))?; Ok(vm.ctx.new_bytes(result).into()) } @@ -6780,105 +6465,30 @@ mod winconsoleio { return Err(std::io::Error::last_os_error().to_pyexception(vm)); } - let len = size as usize; - - let mut wlen = (len / 4) as u32; - if wlen == 0 { - wlen = 1; - } - let mut read_len = { let mut ibuf = self.buf.lock(); Self::copy_from_buf(&mut ibuf, &mut buf) }; - if read_len > 0 { - wlen = wlen.saturating_sub(1); - } - if read_len >= len || wlen == 0 { + if read_len >= size as usize { buf.truncate(read_len); return Ok(vm.ctx.new_bytes(buf).into()); } - - let mut wbuf = vec![0u16; wlen as usize]; - let mut nread: u32 = 0; - let res = unsafe { - ReadConsoleW( - handle, - wbuf.as_mut_ptr() as _, - wlen, - &mut nread, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - if nread == 0 || wbuf[0] == 0x1A { - buf.truncate(read_len); - return Ok(vm.ctx.new_bytes(buf).into()); - } - - let remaining = len - read_len; - let u8n; - if remaining < 4 { + { let mut ibuf = self.buf.lock(); - let converted = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - ibuf.as_mut_ptr() as _, - SMALLBUF as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - if converted > 0 { - u8n = Self::copy_from_buf(&mut ibuf, &mut buf[read_len..]) as i32; - } else { - u8n = 0; - } - } else { - u8n = unsafe { - WideCharToMultiByte( - CP_UTF8, - 0, - wbuf.as_ptr(), - nread as i32, - buf[read_len..].as_mut_ptr() as _, - remaining as i32, - core::ptr::null(), - core::ptr::null_mut(), - ) - }; - } - - if u8n > 0 { - read_len += u8n as usize; - } else { - let err = std::io::Error::last_os_error(); - if err.raw_os_error() == Some(122) { - // ERROR_INSUFFICIENT_BUFFER - 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 { + match host_nt::read_console_into(handle, &mut buf[read_len..], &mut ibuf) { + Ok(n) => read_len += n, + Err(host_nt::ReadConsoleError::BufferTooSmall { + available, + required, + }) => { return Err(vm.new_system_error(format!( - "Buffer had room for {remaining} bytes but {needed} bytes required", + "Buffer had room for {available} bytes but {required} bytes required", ))); } + Err(host_nt::ReadConsoleError::Io(err)) => { + return Err(err.into_pyexception(vm)); + } } - return Err(err.into_pyexception(vm)); } buf.truncate(read_len); @@ -6908,72 +6518,8 @@ mod winconsoleio { return Ok(0); } - let mut len = data.len().min(BUFMAX); - - // Cap at 32766/2 wchars * 3 bytes (UTF-8 to wchar ratio is at most 3:1) - let max_wlen: u32 = 32766 / 2; - len = len.min(max_wlen as usize * 3); - - // Reduce len until wlen fits within max_wlen - let wlen; - loop { - len = find_last_utf8_boundary(data, len); - let w = unsafe { - MultiByteToWideChar( - CP_UTF8, - 0, - data.as_ptr(), - len as i32, - core::ptr::null_mut(), - 0, - ) - }; - if w as u32 <= max_wlen { - wlen = w; - break; - } - 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(std::io::Error::last_os_error().into_pyexception(vm)); - } - - let mut n_written: u32 = 0; - let res = unsafe { - WriteConsoleW( - handle, - wbuf.as_ptr() as _, - wlen as u32, - &mut n_written, - core::ptr::null(), - ) - }; - if res == 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } - - // If we wrote fewer wchars than expected, recalculate bytes consumed - if n_written < wlen as u32 { - // Binary search to find how many input bytes correspond to n_written wchars - len = wchar_to_utf8_count(data, len, n_written); - } - - Ok(len) + host_nt::write_console_utf8(handle, data, BUFMAX) + .map_err(|err| err.into_pyexception(vm)) } #[pymethod(name = "__reduce__")] @@ -6982,43 +6528,6 @@ mod winconsoleio { } } - /// Find how many UTF-8 bytes correspond to n wide chars. - 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; - } - } - } - impl Destructor for WindowsConsoleIO { fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> { if let Some(cio) = zelf.downcast_ref::() { diff --git a/crates/vm/src/stdlib/_signal.rs b/crates/vm/src/stdlib/_signal.rs index 0d9dfad311d..a1f39bc2848 100644 --- a/crates/vm/src/stdlib/_signal.rs +++ b/crates/vm/src/stdlib/_signal.rs @@ -13,11 +13,11 @@ pub(crate) mod _signal { function::{ArgIntoFloat, OptionalArg}, }; use core::sync::atomic::{self, Ordering}; + #[cfg(any(unix, windows))] + use rustpython_host_env::signal::{self as host_signal, sighandler_t}; #[cfg(unix)] use rustpython_host_env::signal::{double_to_timeval, itimerval_to_tuple}; - #[cfg(any(unix, windows))] - use libc::sighandler_t; #[allow(non_camel_case_types)] #[cfg(not(any(unix, windows)))] type sighandler_t = usize; @@ -56,6 +56,7 @@ pub(crate) mod _signal { } #[cfg(unix)] + #[allow(unused_imports)] pub use libc::SIG_ERR; #[cfg(unix)] pub use nix::unistd::alarm as sig_alarm; @@ -79,23 +80,6 @@ pub(crate) mod _signal { #[allow(dead_code)] pub const SIG_ERR: sighandler_t = -1 as _; - #[cfg(all(unix, not(target_os = "redox")))] - unsafe extern "C" { - fn siginterrupt(sig: i32, flag: i32) -> i32; - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - mod ffi { - unsafe extern "C" { - pub fn getitimer(which: libc::c_int, curr_value: *mut libc::itimerval) -> libc::c_int; - pub fn setitimer( - which: libc::c_int, - new_value: *const libc::itimerval, - old_value: *mut libc::itimerval, - ) -> libc::c_int; - } - } - #[pyattr] use crate::signal::NSIG; @@ -168,10 +152,9 @@ pub(crate) mod _signal { let sig_ign = vm.new_pyobj(SIG_IGN as u8); for signum in 1..NSIG { - let handler = unsafe { libc::signal(signum as i32, SIG_IGN) }; - if handler != SIG_ERR { - unsafe { libc::signal(signum as i32, handler) }; - } + let Some(handler) = (unsafe { host_signal::probe_handler(signum as i32) }) else { + continue; + }; let py_handler = if handler == SIG_DFL { Some(sig_dfl.clone()) } else if handler == SIG_IGN { @@ -211,16 +194,7 @@ pub(crate) mod _signal { signal::assert_in_range(signalnum, vm)?; #[cfg(windows)] { - const VALID_SIGNALS: &[i32] = &[ - libc::SIGINT, - libc::SIGILL, - libc::SIGFPE, - libc::SIGSEGV, - libc::SIGTERM, - SIGBREAK, - libc::SIGABRT, - ]; - if !VALID_SIGNALS.contains(&signalnum) { + if !host_signal::is_valid_signal(signalnum) { return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); } } @@ -239,14 +213,13 @@ pub(crate) mod _signal { }; signal::check_signals(vm)?; - let old = unsafe { libc::signal(signalnum, sig_handler) }; - if old == SIG_ERR { - return Err(vm.new_os_error("Failed to set signal".to_owned())); - } - #[cfg(all(unix, not(target_os = "redox")))] - unsafe { - siginterrupt(signalnum, 1); - } + let old = unsafe { host_signal::install_handler(signalnum, sig_handler) }; + let _old = match old { + Ok(old) => old, + Err(_) => { + return Err(vm.new_os_error("Failed to set signal".to_owned())); + } + }; let signal_handlers = vm.signal_handlers.get_or_init(signal::new_signal_handlers); let old_handler = signal_handlers.borrow_mut()[signalnum as usize].replace(handler); @@ -296,35 +269,25 @@ pub(crate) mod _signal { it_value: double_to_timeval(seconds), it_interval: double_to_timeval(interval), }; - let mut old = core::mem::MaybeUninit::::uninit(); - #[cfg(any(target_os = "linux", target_os = "android"))] - let ret = unsafe { ffi::setitimer(which, &new, old.as_mut_ptr()) }; - #[cfg(not(any(target_os = "linux", target_os = "android")))] - let ret = unsafe { libc::setitimer(which, &new, old.as_mut_ptr()) }; - if ret != 0 { - let err = std::io::Error::last_os_error(); - let itimer_error = itimer_error(vm); - return Err(vm.new_exception_msg(itimer_error, err.to_string().into())); + match host_signal::setitimer(which, &new) { + Ok(old) => Ok(itimerval_to_tuple(&old)), + Err(err) => { + let itimer_error = itimer_error(vm); + Err(vm.new_exception_msg(itimer_error, err.to_string().into())) + } } - let old = unsafe { old.assume_init() }; - Ok(itimerval_to_tuple(&old)) } #[cfg(unix)] #[pyfunction] fn getitimer(which: i32, vm: &VirtualMachine) -> PyResult<(f64, f64)> { - let mut old = core::mem::MaybeUninit::::uninit(); - #[cfg(any(target_os = "linux", target_os = "android"))] - let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; - #[cfg(not(any(target_os = "linux", target_os = "android")))] - let ret = unsafe { libc::getitimer(which, old.as_mut_ptr()) }; - if ret != 0 { - let err = std::io::Error::last_os_error(); - let itimer_error = itimer_error(vm); - return Err(vm.new_exception_msg(itimer_error, err.to_string().into())); + match host_signal::getitimer(which) { + Ok(old) => Ok(itimerval_to_tuple(&old)), + Err(err) => { + let itimer_error = itimer_error(vm); + Err(vm.new_exception_msg(itimer_error, err.to_string().into())) + } } - let old = unsafe { old.assume_init() }; - Ok(itimerval_to_tuple(&old)) } #[pyfunction] @@ -358,39 +321,13 @@ pub(crate) mod _signal { #[cfg(windows)] let is_socket = if fd != INVALID_WAKEUP { - use windows_sys::Win32::Networking::WinSock; - - crate::windows::init_winsock(); - let mut res = 0i32; - let mut res_size = core::mem::size_of::() as i32; - let res = unsafe { - WinSock::getsockopt( - fd, - WinSock::SOL_SOCKET, - WinSock::SO_ERROR, - &mut res as *mut i32 as *mut _, - &mut res_size, - ) - }; - // if getsockopt succeeded, fd is for sure a socket - let is_socket = res == 0; - if !is_socket { - let err = std::io::Error::last_os_error(); - // if getsockopt failed for some other reason, throw - if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) { - return Err(err.into_pyexception(vm)); + host_signal::wakeup_fd_is_socket(fd).map_err(|err| { + if err.kind() == std::io::ErrorKind::InvalidInput { + vm.new_value_error("invalid fd") + } else { + err.into_pyexception(vm) } - // Validate that fd is a valid file descriptor using fstat - // First check if SOCKET can be safely cast to i32 (file descriptor) - let fd_i32 = i32::try_from(fd).map_err(|_| vm.new_value_error("invalid fd"))?; - // Verify the fd is valid by trying to fstat it - let borrowed_fd = - unsafe { rustpython_host_env::crt_fd::Borrowed::try_borrow_raw(fd_i32) } - .map_err(|e| e.into_pyexception(vm))?; - rustpython_host_env::fileutils::fstat(borrowed_fd) - .map_err(|e| e.into_pyexception(vm))?; - } - is_socket + })? } else { false }; @@ -443,33 +380,14 @@ pub(crate) mod _signal { } let flags = flags.unwrap_or(0); - let ret = unsafe { - libc::syscall( - libc::SYS_pidfd_send_signal, - pidfd, - sig, - core::ptr::null::(), - flags, - ) as libc::c_long - }; - - if ret == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_signal::pidfd_send_signal(pidfd, sig, flags).map_err(|_| vm.new_last_errno_error()) } #[cfg(all(unix, not(target_os = "redox")))] #[pyfunction(name = "siginterrupt")] fn py_siginterrupt(signum: i32, flag: i32, vm: &VirtualMachine) -> PyResult<()> { signal::assert_in_range(signum, vm)?; - let res = unsafe { siginterrupt(signum, flag) }; - if res < 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_signal::siginterrupt(signum, flag).map_err(|_| vm.new_last_errno_error()) } /// CPython: signal_raise_signal (signalmodule.c) @@ -481,25 +399,14 @@ pub(crate) mod _signal { // On Windows, only certain signals are supported #[cfg(windows)] { - // Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGBREAK(21), SIGABRT(22) - const VALID_SIGNALS: &[i32] = &[ - libc::SIGINT, - libc::SIGILL, - libc::SIGFPE, - libc::SIGSEGV, - libc::SIGTERM, - SIGBREAK, - libc::SIGABRT, - ]; - if !VALID_SIGNALS.contains(&signalnum) { + if !host_signal::is_valid_signal(signalnum) { return Err(vm .new_errno_error(libc::EINVAL, "Invalid argument") .upcast()); } } - let res = unsafe { libc::raise(signalnum) }; - if res != 0 { + if host_signal::raise_signal(signalnum).is_err() { return Err(vm.new_os_error(format!("raise_signal failed for signal {}", signalnum))); } @@ -516,13 +423,7 @@ pub(crate) mod _signal { if signalnum < 1 || signalnum >= signal::NSIG as i32 { return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); } - let s = unsafe { libc::strsignal(signalnum) }; - if s.is_null() { - Ok(None) - } else { - let cstr = unsafe { core::ffi::CStr::from_ptr(s) }; - Ok(Some(cstr.to_string_lossy().into_owned())) - } + Ok(host_signal::strsignal(signalnum)) } #[cfg(windows)] @@ -531,18 +432,7 @@ pub(crate) mod _signal { if signalnum < 1 || signalnum >= signal::NSIG as i32 { return Err(vm.new_value_error(format!("signal number {} out of range", signalnum))); } - // Windows doesn't have strsignal(), provide our own mapping - let name = match signalnum { - libc::SIGINT => "Interrupt", - libc::SIGILL => "Illegal instruction", - libc::SIGFPE => "Floating-point exception", - libc::SIGSEGV => "Segmentation fault", - libc::SIGTERM => "Terminated", - SIGBREAK => "Break", - libc::SIGABRT => "Aborted", - _ => return Ok(None), - }; - Ok(Some(name.to_owned())) + Ok(host_signal::strsignal(signalnum)) } /// CPython: signal_valid_signals (signalmodule.c) @@ -551,35 +441,11 @@ pub(crate) mod _signal { use crate::PyPayload; use crate::builtins::PySet; let set = PySet::default().into_ref(&vm.ctx); - #[cfg(unix)] + #[cfg(any(unix, windows))] + for signum in host_signal::valid_signals(signal::NSIG) + .map_err(|_| vm.new_os_error("sigfillset failed".to_owned()))? { - // Use sigfillset to get all valid signals - let mut mask: libc::sigset_t = unsafe { core::mem::zeroed() }; - // SAFETY: mask is a valid pointer - if unsafe { libc::sigfillset(&mut mask) } != 0 { - return Err(vm.new_os_error("sigfillset failed".to_owned())); - } - // Convert the filled mask to a Python set - for signum in 1..signal::NSIG { - if unsafe { libc::sigismember(&mask, signum as i32) } == 1 { - set.add(vm.ctx.new_int(signum as i32).into(), vm)?; - } - } - } - #[cfg(windows)] - { - // Windows only supports a limited set of signals - for &signum in &[ - libc::SIGINT, - libc::SIGILL, - libc::SIGFPE, - libc::SIGSEGV, - libc::SIGTERM, - SIGBREAK, - libc::SIGABRT, - ] { - set.add(vm.ctx.new_int(signum).into(), vm)?; - } + set.add(vm.ctx.new_int(signum).into(), vm)?; } #[cfg(not(any(unix, windows)))] { @@ -613,11 +479,7 @@ pub(crate) mod _signal { use crate::convert::IntoPyException; // Initialize sigset - let mut sigset: libc::sigset_t = unsafe { core::mem::zeroed() }; - // SAFETY: sigset is a valid pointer - if unsafe { libc::sigemptyset(&mut sigset) } != 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } + let mut sigset = host_signal::sigemptyset().map_err(|e| e.into_pyexception(vm))?; // Add signals to the set for sig in mask.iter(vm)? { @@ -637,19 +499,11 @@ pub(crate) mod _signal { signal::NSIG - 1 ))); } - // SAFETY: sigset is a valid pointer and signum is validated - if unsafe { libc::sigaddset(&mut sigset, signum) } != 0 { - return Err(std::io::Error::last_os_error().into_pyexception(vm)); - } + host_signal::sigaddset(&mut sigset, signum).map_err(|e| e.into_pyexception(vm))?; } - // Call pthread_sigmask - let mut old_mask: libc::sigset_t = unsafe { core::mem::zeroed() }; - // SAFETY: all pointers are valid - let err = unsafe { libc::pthread_sigmask(how, &sigset, &mut old_mask) }; - if err != 0 { - return Err(std::io::Error::from_raw_os_error(err).into_pyexception(vm)); - } + let old_mask = + host_signal::pthread_sigmask(how, &sigset).map_err(|e| e.into_pyexception(vm))?; // Check for pending signals signal::check_signals(vm)?; @@ -663,31 +517,14 @@ pub(crate) mod _signal { signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed); signal::set_triggered(); #[cfg(windows)] - if signum == libc::SIGINT - && let Some(handle) = signal::get_sigint_event() - { - unsafe { - windows_sys::Win32::System::Threading::SetEvent(handle as _); - } - } - let wakeup_fd = WAKEUP.load(Ordering::Relaxed); - if wakeup_fd != INVALID_WAKEUP { - let sigbyte = signum as u8; - #[cfg(windows)] - if WAKEUP_IS_SOCKET.load(Ordering::Relaxed) { - let _res = unsafe { - windows_sys::Win32::Networking::WinSock::send( - wakeup_fd, - &sigbyte as *const u8 as *const _, - 1, - 0, - ) - }; - return; - } - let _res = unsafe { libc::write(wakeup_fd as _, &sigbyte as *const u8 as *const _, 1) }; - // TODO: handle _res < 1, support warn_on_full_buffer - } + host_signal::notify_signal( + signum, + WAKEUP.load(Ordering::Relaxed), + WAKEUP_IS_SOCKET.load(Ordering::Relaxed), + signal::get_sigint_event(), + ); + #[cfg(unix)] + host_signal::notify_signal(signum, WAKEUP.load(Ordering::Relaxed)); } /// Reset wakeup fd after fork in child process. diff --git a/crates/vm/src/stdlib/_winapi.rs b/crates/vm/src/stdlib/_winapi.rs index 9edb740cdb0..61393c6d1f3 100644 --- a/crates/vm/src/stdlib/_winapi.rs +++ b/crates/vm/src/stdlib/_winapi.rs @@ -16,6 +16,7 @@ mod _winapi { }; use core::ptr::{null, null_mut}; use rustpython_common::wtf8::Wtf8Buf; + use rustpython_host_env::overlapped as host_overlapped; use rustpython_host_env::winapi as host_winapi; use rustpython_host_env::windows::ToWideString; use windows_sys::Win32::Foundation::{HANDLE, MAX_PATH}; @@ -91,7 +92,7 @@ mod _winapi { #[pyfunction] fn CloseHandle(handle: WinHandle) -> WindowsSysResult { - WindowsSysResult(unsafe { windows_sys::Win32::Foundation::CloseHandle(handle.0) }) + WindowsSysResult(host_winapi::close_handle(handle.0)) } /// CreateFile - Create or open a file or I/O device. @@ -110,27 +111,16 @@ mod _winapi { _template_file: PyObjectRef, // Always NULL (0) vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::CreateFileW; - let file_name_wide = file_name.as_wtf8().to_wide_with_nul(); - - let handle = unsafe { - CreateFileW( - file_name_wide.as_ptr(), - desired_access, - share_mode, - null(), - creation_disposition, - flags_and_attributes, - null_mut(), - ) - }; - - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - Ok(WinHandle(handle)) + host_winapi::create_file_w( + file_name_wide.as_ptr(), + desired_access, + share_mode, + creation_disposition, + flags_and_attributes, + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -138,16 +128,9 @@ mod _winapi { std_handle: windows_sys::Win32::System::Console::STD_HANDLE, vm: &VirtualMachine, ) -> PyResult> { - let handle = unsafe { windows_sys::Win32::System::Console::GetStdHandle(std_handle) }; - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - Ok(if handle.is_null() { - // NULL handle - return None - None - } else { - Some(WinHandle(handle)) - }) + host_winapi::get_std_handle(std_handle) + .map(|handle| handle.map(WinHandle)) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -156,20 +139,9 @@ mod _winapi { size: u32, vm: &VirtualMachine, ) -> PyResult<(WinHandle, WinHandle)> { - use windows_sys::Win32::Foundation::HANDLE; - let (read, write) = unsafe { - let mut read = core::mem::MaybeUninit::::uninit(); - let mut write = core::mem::MaybeUninit::::uninit(); - WindowsSysResult(windows_sys::Win32::System::Pipes::CreatePipe( - read.as_mut_ptr(), - write.as_mut_ptr(), - core::ptr::null(), - size, - )) - .to_pyresult(vm)?; - (read.assume_init(), write.assume_init()) - }; - Ok((WinHandle(read), WinHandle(write))) + host_winapi::create_pipe(size) + .map(|(read, write)| (WinHandle(read), WinHandle(write))) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -182,22 +154,16 @@ mod _winapi { options: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::HANDLE; - let target = unsafe { - let mut target = core::mem::MaybeUninit::::uninit(); - WindowsSysResult(windows_sys::Win32::Foundation::DuplicateHandle( - src_process.0, - src.0, - target_process.0, - target.as_mut_ptr(), - access, - inherit, - options.unwrap_or(0), - )) - .to_pyresult(vm)?; - target.assume_init() - }; - Ok(WinHandle(target)) + host_winapi::duplicate_handle( + src_process.0, + src.0, + target_process.0, + access, + inherit, + options.unwrap_or(0), + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -215,12 +181,7 @@ mod _winapi { h: WinHandle, vm: &VirtualMachine, ) -> PyResult { - let file_type = unsafe { windows_sys::Win32::Storage::FileSystem::GetFileType(h.0) }; - if file_type == 0 && unsafe { windows_sys::Win32::Foundation::GetLastError() } != 0 { - Err(vm.new_last_os_error()) - } else { - Ok(file_type) - } + host_winapi::get_file_type(h.0).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -296,7 +257,7 @@ mod _winapi { getattributelist(args.startup_info.get_attr("lpAttributeList", vm)?, vm)?; si.lpAttributeList = attrlist .as_mut() - .map_or_else(null_mut, |l| l.attrlist.as_mut_ptr() as _); + .map_or_else(null_mut, |l| l.as_mut_ptr() as _); let wstr = |s: PyStrRef| { let ws = widestring::WideCString::from_str(s.expect_str()) @@ -330,23 +291,18 @@ mod _winapi { .map_or_else(null_mut, |w| w.as_mut_ptr()); let procinfo = unsafe { - let mut procinfo = core::mem::MaybeUninit::uninit(); - WindowsSysResult(windows_sys::Win32::System::Threading::CreateProcessW( + host_winapi::create_process_w( app_name, command_line, - core::ptr::null(), - core::ptr::null(), args.inherit_handles, args.creation_flags | windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT | windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT, - env as _, + env, current_dir, - &mut si as *mut _ as *mut _, - procinfo.as_mut_ptr(), - )) - .into_pyresult(vm)?; - procinfo.assume_init() + &mut si.StartupInfo, + ) + .map_err(|e| e.to_pyexception(vm))? }; Ok(( @@ -364,33 +320,20 @@ mod _winapi { process_id: u32, vm: &VirtualMachine, ) -> PyResult { - let handle = unsafe { - windows_sys::Win32::System::Threading::OpenProcess( - desired_access, - i32::from(inherit_handle), - process_id, - ) - }; - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_process(desired_access, inherit_handle, process_id) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn ExitProcess(exit_code: u32) { - unsafe { windows_sys::Win32::System::Threading::ExitProcess(exit_code) } + host_winapi::exit_process(exit_code) } #[pyfunction] fn NeedCurrentDirectoryForExePath(exe_name: PyStrRef) -> bool { let exe_name = exe_name.as_wtf8().to_wide_with_nul(); - let return_value = unsafe { - windows_sys::Win32::System::Environment::NeedCurrentDirectoryForExePathW( - exe_name.as_ptr(), - ) - }; - return_value != 0 + host_winapi::need_current_directory_for_exe_path_w(exe_name.as_ptr()) } #[pyfunction] @@ -401,8 +344,7 @@ mod _winapi { ) -> PyResult<()> { let src_path = std::path::Path::new(src_path.expect_str()); let dest_path = std::path::Path::new(dest_path.expect_str()); - - junction::create(src_path, dest_path).map_err(|e| e.to_pyexception(vm)) + host_winapi::create_junction(src_path, dest_path).map_err(|e| e.to_pyexception(vm)) } fn getenvironment(env: ArgMapping, vm: &VirtualMachine) -> PyResult> { @@ -416,127 +358,47 @@ mod _winapi { return Err(vm.new_runtime_error("environment changed size during iteration")); } - // Deduplicate case-insensitive keys, keeping the last value - use std::collections::HashMap; - let mut last_entry: HashMap = HashMap::new(); + let mut entries = Vec::with_capacity(keys.len()); for (k, v) in keys.into_iter().zip(values) { let k = PyStrRef::try_from_object(vm, k)?; - let k = k.expect_str(); + let k = k.expect_str().to_owned(); let v = PyStrRef::try_from_object(vm, v)?; - let v = v.expect_str(); - if k.contains('\0') || v.contains('\0') { - return Err(crate::exceptions::cstring_error(vm)); - } - if k.is_empty() || k[1..].contains('=') { - return Err(vm.new_value_error("illegal environment variable name")); - } - let key_upper = k.to_uppercase(); - let mut entry = widestring::WideString::new(); - entry.push_str(k); - entry.push_str("="); - entry.push_str(v); - entry.push_str("\0"); - last_entry.insert(key_upper, entry); - } - - // Sort by uppercase key for case-insensitive ordering - let mut entries: Vec<(String, widestring::WideString)> = last_entry.into_iter().collect(); - entries.sort_by(|a, b| a.0.cmp(&b.0)); - - let mut out = widestring::WideString::new(); - for (_, entry) in entries { - out.push(entry); - } - // Each entry ends with \0, so one more \0 terminates the block. - // For empty env, we need \0\0 as a valid empty environment block. - if out.is_empty() { - out.push_str("\0"); + let v = v.expect_str().to_owned(); + entries.push((k, v)); } - out.push_str("\0"); - Ok(out.into_vec()) - } - struct AttrList { - handlelist: Option>, - attrlist: Vec, - } - impl Drop for AttrList { - fn drop(&mut self) { - unsafe { - windows_sys::Win32::System::Threading::DeleteProcThreadAttributeList( - self.attrlist.as_mut_ptr() as *mut _, - ) - }; - } + host_winapi::build_environment_block(entries).map_err(|err| match err { + host_winapi::BuildEnvironmentBlockError::ContainsNul => { + crate::exceptions::cstring_error(vm) + } + host_winapi::BuildEnvironmentBlockError::IllegalName => { + vm.new_value_error("illegal environment variable name") + } + }) } - fn getattributelist(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult> { - >::try_from_object(vm, obj)? - .map(|mapping| { - let handlelist = mapping - .as_ref() - .get_item("handle_list", vm) - .ok() - .and_then(|obj| { - >>::try_from_object(vm, obj) - .map(|s| match s { - Some(s) if !s.is_empty() => Some(s.into_vec()), - _ => None, - }) - .transpose() - }) - .transpose()?; - - let attr_count = handlelist.is_some() as u32; - let (result, mut size) = unsafe { - let mut size = core::mem::MaybeUninit::uninit(); - let result = WindowsSysResult( - windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( - core::ptr::null_mut(), - attr_count, - 0, - size.as_mut_ptr(), - ), - ); - (result, size.assume_init()) - }; - if !result.is_err() - || unsafe { windows_sys::Win32::Foundation::GetLastError() } - != windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER - { - return Err(vm.new_last_os_error()); - } - let mut attrlist = vec![0u8; size]; - WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::InitializeProcThreadAttributeList( - attrlist.as_mut_ptr() as *mut _, - attr_count, - 0, - &mut size, - ) - }) - .into_pyresult(vm)?; - let mut attrs = AttrList { - handlelist, - attrlist, - }; - if let Some(ref mut handlelist) = attrs.handlelist { - WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::UpdateProcThreadAttribute( - attrs.attrlist.as_mut_ptr() as _, - 0, - (2 & 0xffff) | 0x20000, // PROC_THREAD_ATTRIBUTE_HANDLE_LIST - handlelist.as_mut_ptr() as _, - (handlelist.len() * core::mem::size_of::()) as _, - core::ptr::null_mut(), - core::ptr::null(), - ) + fn getattributelist( + obj: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult> { + let Some(mapping) = >::try_from_object(vm, obj)? else { + return Ok(None); + }; + let handlelist = mapping + .as_ref() + .get_item("handle_list", vm) + .ok() + .and_then(|obj| { + >>::try_from_object(vm, obj) + .map(|s| match s { + Some(s) if !s.is_empty() => Some(s.into_vec()), + _ => None, }) - .into_pyresult(vm)?; - } - Ok(attrs) + .transpose() }) - .transpose() + .transpose()?; + + host_winapi::create_handle_list_attribute_list(handlelist).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -549,12 +411,7 @@ mod _winapi { } else { ms as u32 }; - let ret = unsafe { windows_sys::Win32::System::Threading::WaitForSingleObject(h.0, ms) }; - if ret == windows_sys::Win32::Foundation::WAIT_FAILED { - Err(vm.new_last_os_error()) - } else { - Ok(ret) - } + host_winapi::wait_for_single_object(h.0, ms).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -564,9 +421,6 @@ mod _winapi { milliseconds: u32, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::Foundation::WAIT_FAILED; - use windows_sys::Win32::System::Threading::WaitForMultipleObjects as WinWaitForMultipleObjects; - let handles: Vec = handle_seq .into_vec() .into_iter() @@ -581,40 +435,18 @@ mod _winapi { return Err(vm.new_value_error("WaitForMultipleObjects supports at most 64 handles")); } - let ret = unsafe { - WinWaitForMultipleObjects( - handles.len() as u32, - handles.as_ptr(), - if wait_all { 1 } else { 0 }, - milliseconds, - ) - }; - - if ret == WAIT_FAILED { - Err(vm.new_last_os_error()) - } else { - Ok(ret) - } + host_winapi::wait_for_multiple_objects(&handles, wait_all, milliseconds) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn GetExitCodeProcess(h: WinHandle, vm: &VirtualMachine) -> PyResult { - unsafe { - let mut ec = core::mem::MaybeUninit::uninit(); - WindowsSysResult(windows_sys::Win32::System::Threading::GetExitCodeProcess( - h.0, - ec.as_mut_ptr(), - )) - .to_pyresult(vm)?; - Ok(ec.assume_init()) - } + host_winapi::get_exit_code_process(h.0).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn TerminateProcess(h: WinHandle, exit_code: u32) -> WindowsSysResult { - WindowsSysResult(unsafe { - windows_sys::Win32::System::Threading::TerminateProcess(h.0, exit_code) - }) + WindowsSysResult(host_winapi::terminate_process(h.0, exit_code)) } #[pyfunction] @@ -623,22 +455,10 @@ mod _winapi { name: OptionalArg>, vm: &VirtualMachine, ) -> PyResult { - let handle = unsafe { - match name.flatten() { - Some(name) => { - let name_wide = name.as_wtf8().to_wide_with_nul(); - windows_sys::Win32::System::JobObjects::CreateJobObjectW( - null(), - name_wide.as_ptr(), - ) - } - None => windows_sys::Win32::System::JobObjects::CreateJobObjectW(null(), null()), - } - }; - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + let name = name.flatten().map(|name| name.as_wtf8().to_wide_with_nul()); + host_winapi::create_job_object_w(name.as_ref().map_or(null(), |name| name.as_ptr())) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] @@ -647,59 +467,25 @@ mod _winapi { process: WinHandle, vm: &VirtualMachine, ) -> PyResult<()> { - let ret = unsafe { - windows_sys::Win32::System::JobObjects::AssignProcessToJobObject(job.0, process.0) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::assign_process_to_job_object(job.0, process.0) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn TerminateJobObject(job: WinHandle, exit_code: u32, vm: &VirtualMachine) -> PyResult<()> { - let ret = - unsafe { windows_sys::Win32::System::JobObjects::TerminateJobObject(job.0, exit_code) }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::terminate_job_object(job.0, exit_code).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn SetJobObjectKillOnClose(job: WinHandle, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::JobObjects::{ - JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation, - SetInformationJobObject, - }; - let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { core::mem::zeroed() }; - info.BasicLimitInformation.LimitFlags = - windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - let ret = unsafe { - SetInformationJobObject( - job.0, - JobObjectExtendedLimitInformation, - &info as *const _ as *const core::ffi::c_void, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::set_job_object_kill_on_close(job.0).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn GetModuleFileName(handle: isize, vm: &VirtualMachine) -> PyResult { let mut path: Vec = vec![0; MAX_PATH as usize]; - let length = unsafe { - windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW( - handle as windows_sys::Win32::Foundation::HMODULE, - path.as_mut_ptr(), - path.len() as u32, - ) - }; + let length = host_winapi::get_module_file_name(handle as _, &mut path); if length == 0 { return Err(vm.new_runtime_error("GetModuleFileName failed")); } @@ -716,22 +502,14 @@ mod _winapi { vm: &VirtualMachine, ) -> PyResult { let name_wide = name.as_wtf8().to_wide_with_nul(); - let handle = unsafe { - windows_sys::Win32::System::Threading::OpenMutexW( - desired_access, - i32::from(inherit_handle), - name_wide.as_ptr(), - ) - }; - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_mutex_w(desired_access, inherit_handle, name_wide.as_ptr()) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn ReleaseMutex(handle: WinHandle) -> WindowsSysResult { - WindowsSysResult(unsafe { windows_sys::Win32::System::Threading::ReleaseMutex(handle.0) }) + WindowsSysResult(host_winapi::release_mutex(handle.0)) } // LOCALE_NAME_INVARIANT is an empty string in Windows API @@ -757,7 +535,6 @@ mod _winapi { ) -> PyResult { use windows_sys::Win32::Globalization::{ LCMAP_BYTEREV, LCMAP_HASH, LCMAP_SORTHANDLE, LCMAP_SORTKEY, - LCMapStringEx as WinLCMapStringEx, }; // Reject unsupported flags @@ -773,46 +550,13 @@ mod _winapi { return Err(vm.new_overflow_error("input string is too long")); } - // First call to get required buffer size - let dest_size = unsafe { - WinLCMapStringEx( - locale_wide.as_ptr(), - flags, - src_wide.as_ptr(), - src_wide.len() as i32, - null_mut(), - 0, - null(), - null(), - 0, - ) - }; - - if dest_size <= 0 { - return Err(vm.new_last_os_error()); - } - - // Second call to perform the mapping - let mut dest = vec![0u16; dest_size as usize]; - let nmapped = unsafe { - WinLCMapStringEx( - locale_wide.as_ptr(), - flags, - src_wide.as_ptr(), - src_wide.len() as i32, - dest.as_mut_ptr(), - dest_size, - null(), - null(), - 0, - ) - }; - - if nmapped <= 0 { - return Err(vm.new_last_os_error()); - } - - dest.truncate(nmapped as usize); + let dest = host_winapi::lc_map_string_ex( + locale_wide.as_ptr(), + flags, + src_wide.as_ptr(), + src_wide.len() as i32, + ) + .map_err(|e| e.to_pyexception(vm))?; // Convert UTF-16 back to WTF-8 (handles surrogates properly) let result = Wtf8Buf::from_wide(&dest); @@ -842,28 +586,18 @@ mod _winapi { /// CreateNamedPipe - Create a named pipe #[pyfunction] fn CreateNamedPipe(args: CreateNamedPipeArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::System::Pipes::CreateNamedPipeW; - let name_wide = args.name.as_wtf8().to_wide_with_nul(); - - let handle = unsafe { - CreateNamedPipeW( - name_wide.as_ptr(), - args.open_mode, - args.pipe_mode, - args.max_instances, - args.out_buffer_size, - args.in_buffer_size, - args.default_timeout, - null(), // security_attributes - NULL for now - ) - }; - - if handle == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - Ok(WinHandle(handle)) + host_winapi::create_named_pipe_w( + name_wide.as_ptr(), + args.open_mode, + args.pipe_mode, + args.max_instances, + args.out_buffer_size, + args.in_buffer_size, + args.default_timeout, + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } // ==================== Overlapped class ==================== @@ -873,144 +607,51 @@ mod _winapi { #[pyclass(name = "Overlapped", module = "_winapi")] #[derive(Debug, PyPayload)] struct Overlapped { - inner: PyMutex, + inner: PyMutex, } - struct OverlappedInner { - // Box ensures the OVERLAPPED struct stays at a stable heap address - // even when the containing Overlapped Python object is moved during - // into_pyobject(). The OS holds a pointer to this struct for pending - // I/O operations, so it must not be relocated. - overlapped: Box, - handle: HANDLE, - pending: bool, - completed: bool, - read_buffer: Option>, - write_buffer: Option>, - } - - impl core::fmt::Debug for OverlappedInner { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("OverlappedInner") - .field("handle", &self.handle) - .field("pending", &self.pending) - .field("completed", &self.completed) - .finish() - } - } - - unsafe impl Sync for OverlappedInner {} - unsafe impl Send for OverlappedInner {} - #[pyclass(with(Constructor))] impl Overlapped { - fn new_with_handle(handle: HANDLE) -> Self { - use windows_sys::Win32::System::Threading::CreateEventW; - - let event = unsafe { CreateEventW(null(), 1, 0, null()) }; - let mut overlapped: windows_sys::Win32::System::IO::OVERLAPPED = - unsafe { core::mem::zeroed() }; - overlapped.hEvent = event; - - Overlapped { - inner: PyMutex::new(OverlappedInner { - overlapped: Box::new(overlapped), - handle, - pending: false, - completed: false, - read_buffer: None, - write_buffer: None, - }), - } + fn new_with_handle(handle: HANDLE, vm: &VirtualMachine) -> PyResult { + host_overlapped::Operation::new(handle) + .map(|inner| Overlapped { + inner: PyMutex::new(inner), + }) + .map_err(|e| e.to_pyexception(vm)) } #[pymethod] fn GetOverlappedResult(&self, wait: bool, vm: &VirtualMachine) -> PyResult<(u32, u32)> { - use windows_sys::Win32::Foundation::{ - ERROR_IO_INCOMPLETE, ERROR_MORE_DATA, ERROR_OPERATION_ABORTED, ERROR_SUCCESS, - GetLastError, - }; - use windows_sys::Win32::System::IO::GetOverlappedResult; - let mut inner = self.inner.lock(); - - let mut transferred: u32 = 0; - - let ret = unsafe { - GetOverlappedResult( - inner.handle, - &*inner.overlapped, - &mut transferred, - if wait { 1 } else { 0 }, - ) - }; - - let err = if ret == 0 { - unsafe { GetLastError() } - } else { - ERROR_SUCCESS - }; - - match err { - ERROR_SUCCESS | ERROR_MORE_DATA | ERROR_OPERATION_ABORTED => { - inner.completed = true; - inner.pending = false; - } - ERROR_IO_INCOMPLETE => {} - _ => { - inner.pending = false; - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - } - - if inner.completed - && let Some(read_buffer) = &mut inner.read_buffer - && transferred != read_buffer.len() as u32 - { - read_buffer.truncate(transferred as usize); - } - - Ok((transferred, err)) + inner + .get_result(wait) + .map(|result| (result.transferred, result.error)) + .map_err(|e| e.to_pyexception(vm)) } #[pymethod] fn getbuffer(&self, vm: &VirtualMachine) -> PyResult> { let inner = self.inner.lock(); - if !inner.completed { + if !inner.is_completed() { return Err(vm.new_value_error( "can't get read buffer before GetOverlappedResult() signals the operation completed", )); } Ok(inner - .read_buffer - .as_ref() - .map(|buf| vm.ctx.new_bytes(buf.clone()).into())) + .read_buffer() + .map(|buf| vm.ctx.new_bytes(buf.to_vec()).into())) } #[pymethod] fn cancel(&self, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::IO::CancelIoEx; - let mut inner = self.inner.lock(); - let ret = if inner.pending { - unsafe { CancelIoEx(inner.handle, &*inner.overlapped) } - } else { - 1 - }; - if ret == 0 { - let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - if err != windows_sys::Win32::Foundation::ERROR_NOT_FOUND { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - } - inner.pending = false; - Ok(()) + inner.cancel().map_err(|e| e.to_pyexception(vm)) } #[pygetset] fn event(&self) -> isize { let inner = self.inner.lock(); - inner.overlapped.hEvent as isize + inner.event() as isize } } @@ -1020,18 +661,9 @@ mod _winapi { fn py_new( _cls: &Py, _args: Self::Args, - _vm: &VirtualMachine, + vm: &VirtualMachine, ) -> PyResult { - Ok(Overlapped::new_with_handle(null_mut())) - } - } - - impl Drop for OverlappedInner { - fn drop(&mut self) { - use windows_sys::Win32::Foundation::CloseHandle; - if !self.overlapped.hEvent.is_null() { - unsafe { CloseHandle(self.overlapped.hEvent) }; - } + Overlapped::new_with_handle(null_mut(), vm) } } @@ -1046,122 +678,55 @@ mod _winapi { #[pyfunction] fn ConnectNamedPipe(args: ConnectNamedPipeArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::{ - ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, GetLastError, - }; - let handle = args.handle; let use_overlapped = args.overlapped.unwrap_or(false); if use_overlapped { - // Overlapped (async) mode - let ov = Overlapped::new_with_handle(handle.0); - - let _ret = { + let ov = Overlapped::new_with_handle(handle.0, vm)?; + { let mut inner = ov.inner.lock(); - unsafe { - windows_sys::Win32::System::Pipes::ConnectNamedPipe( - handle.0, - &mut *inner.overlapped, - ) - } - }; - - let err = unsafe { GetLastError() }; - match err { - ERROR_IO_PENDING => { - let mut inner = ov.inner.lock(); - inner.pending = true; - } - ERROR_PIPE_CONNECTED => { - let inner = ov.inner.lock(); - unsafe { - windows_sys::Win32::System::Threading::SetEvent(inner.overlapped.hEvent); - } - } - _ => { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } + inner + .connect_named_pipe() + .map_err(|e| e.to_pyexception(vm))?; } - Ok(ov.into_pyobject(vm)) } else { - // Synchronous mode - let ret = unsafe { - windows_sys::Win32::System::Pipes::ConnectNamedPipe(handle.0, null_mut()) - }; - - if ret == 0 { - let err = unsafe { GetLastError() }; - if err != ERROR_PIPE_CONNECTED { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - } - + host_winapi::connect_named_pipe(handle.0).map_err(|e| e.to_pyexception(vm))?; Ok(vm.ctx.none()) } } /// Helper for GetShortPathName and GetLongPathName - fn get_path_name_impl( - path: &PyStrRef, - api_fn: unsafe extern "system" fn(*const u16, *mut u16, u32) -> u32, - vm: &VirtualMachine, - ) -> PyResult { - let path_wide = path.as_wtf8().to_wide_with_nul(); - - // First call to get required buffer size - let size = unsafe { api_fn(path_wide.as_ptr(), null_mut(), 0) }; - - if size == 0 { - return Err(vm.new_last_os_error()); - } - - // Second call to get the actual path - let mut buffer: Vec = vec![0; size as usize]; - let result = - unsafe { api_fn(path_wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) }; - - if result == 0 { - return Err(vm.new_last_os_error()); - } - - // Truncate to actual length (excluding null terminator) - buffer.truncate(result as usize); - + fn path_name_result_to_pystr(wide: Vec, vm: &VirtualMachine) -> PyResult { // Convert UTF-16 back to WTF-8 (handles surrogates properly) - let result_str = Wtf8Buf::from_wide(&buffer); + let result_str = Wtf8Buf::from_wide(&wide); Ok(vm.ctx.new_str(result_str)) } /// GetShortPathName - Return the short version of the provided path. #[pyfunction] fn GetShortPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW; - get_path_name_impl(&path, GetShortPathNameW, vm) + let path_wide = path.as_wtf8().to_wide_with_nul(); + let wide = host_winapi::get_short_path_name_w(path_wide.as_ptr()) + .map_err(|e| e.to_pyexception(vm))?; + path_name_result_to_pystr(wide, vm) } /// GetLongPathName - Return the long version of the provided path. #[pyfunction] fn GetLongPathName(path: PyStrRef, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::GetLongPathNameW; - get_path_name_impl(&path, GetLongPathNameW, vm) + let path_wide = path.as_wtf8().to_wide_with_nul(); + let wide = host_winapi::get_long_path_name_w(path_wide.as_ptr()) + .map_err(|e| e.to_pyexception(vm))?; + path_name_result_to_pystr(wide, vm) } /// WaitNamedPipe - Wait for an instance of a named pipe to become available. #[pyfunction] fn WaitNamedPipe(name: PyStrRef, timeout: u32, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Pipes::WaitNamedPipeW; - let name_wide = name.as_wtf8().to_wide_with_nul(); - - let success = unsafe { WaitNamedPipeW(name_wide.as_ptr(), timeout) }; - - if success == 0 { - return Err(vm.new_last_os_error()); - } - - Ok(()) + host_winapi::wait_named_pipe_w(name_wide.as_ptr(), timeout) + .map_err(|e| e.to_pyexception(vm)) } /// PeekNamedPipe - Peek at data in a named pipe without removing it. @@ -1171,60 +736,33 @@ mod _winapi { size: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Pipes::PeekNamedPipe as WinPeekNamedPipe; - let size = size.unwrap_or(0); if size < 0 { return Err(vm.new_value_error("negative size")); } - let mut navail: u32 = 0; - let mut nleft: u32 = 0; - if size > 0 { - let mut buf = vec![0u8; size as usize]; - let mut nread: u32 = 0; - - let ret = unsafe { - WinPeekNamedPipe( - handle.0, - buf.as_mut_ptr() as *mut _, - size as u32, - &mut nread, - &mut navail, - &mut nleft, - ) - }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - buf.truncate(nread as usize); + let result = host_winapi::peek_named_pipe(handle.0, Some(size as u32)) + .map_err(|e| e.to_pyexception(vm))?; + let buf = result.data.unwrap_or_default(); let bytes: PyObjectRef = vm.ctx.new_bytes(buf).into(); Ok(vm .ctx .new_tuple(vec![ bytes, - vm.ctx.new_int(navail).into(), - vm.ctx.new_int(nleft).into(), + vm.ctx.new_int(result.available).into(), + vm.ctx.new_int(result.left_this_message).into(), ]) .into()) } else { - let ret = unsafe { - WinPeekNamedPipe(handle.0, null_mut(), 0, null_mut(), &mut navail, &mut nleft) - }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - + let result = + host_winapi::peek_named_pipe(handle.0, None).map_err(|e| e.to_pyexception(vm))?; Ok(vm .ctx .new_tuple(vec![ - vm.ctx.new_int(navail).into(), - vm.ctx.new_int(nleft).into(), + vm.ctx.new_int(result.available).into(), + vm.ctx.new_int(result.left_this_message).into(), ]) .into()) } @@ -1239,41 +777,19 @@ mod _winapi { name: Option, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::CreateEventW as WinCreateEventW; - let _ = security_attributes; // Ignored, always NULL let name_wide = name.map(|n| n.as_wtf8().to_wide_with_nul()); let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); - - let handle = unsafe { - WinCreateEventW( - null(), - i32::from(manual_reset), - i32::from(initial_state), - name_ptr, - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - - Ok(WinHandle(handle)) + host_winapi::create_event_w(manual_reset, initial_state, name_ptr) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// SetEvent - Set the specified event object to the signaled state. #[pyfunction] fn SetEvent(event: WinHandle, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::SetEvent as WinSetEvent; - - let ret = unsafe { WinSetEvent(event.0) }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - Ok(()) + host_winapi::set_event(event.0).map_err(|e| e.to_pyexception(vm)) } #[derive(FromArgs)] @@ -1289,46 +805,15 @@ mod _winapi { /// WriteFile - Write data to a file or I/O device. #[pyfunction] fn WriteFile(args: WriteFileArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::WriteFile as WinWriteFile; - let handle = args.handle; let use_overlapped = args.overlapped; let buf = args.buffer.borrow_buf(); - let len = core::cmp::min(buf.len(), u32::MAX as usize) as u32; if use_overlapped { - use windows_sys::Win32::Foundation::ERROR_IO_PENDING; - - let ov = Overlapped::new_with_handle(handle.0); + let ov = Overlapped::new_with_handle(handle.0, vm)?; let err = { let mut inner = ov.inner.lock(); - inner.write_buffer = Some(buf.to_vec()); - let write_buf = inner.write_buffer.as_ref().unwrap(); - let mut written: u32 = 0; - let ret = unsafe { - WinWriteFile( - handle.0, - write_buf.as_ptr() as *const _, - len, - &mut written, - &mut *inner.overlapped, - ) - }; - - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - - if ret == 0 && err != ERROR_IO_PENDING { - return Err(vm.new_last_os_error()); - } - if ret == 0 && err == ERROR_IO_PENDING { - inner.pending = true; - } - - err + inner.write(&buf).map_err(|e| e.to_pyexception(vm))? }; // Without GIL, the Python-level PipeConnection._send_bytes has a @@ -1337,13 +822,8 @@ mod _winapi { // ERROR_IO_PENDING and never blocks in WaitForMultipleObjects, // keeping the _send_ov window negligibly small. if err == ERROR_IO_PENDING { - let event = ov.inner.lock().overlapped.hEvent; - vm.allow_threads(|| unsafe { - windows_sys::Win32::System::Threading::WaitForSingleObject( - event, - windows_sys::Win32::System::Threading::INFINITE, - ); - }); + let event = ov.inner.lock().event(); + let _ = vm.allow_threads(|| host_winapi::wait_for_single_object(event, INFINITE)); let result = vm .ctx .new_tuple(vec![ov.into_pyobject(vm), vm.ctx.new_int(0u32).into()]); @@ -1356,29 +836,12 @@ mod _winapi { return Ok(result.into()); } - let mut written: u32 = 0; - let ret = unsafe { - WinWriteFile( - handle.0, - buf.as_ptr() as *const _, - len, - &mut written, - null_mut(), - ) - }; - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } + let result = host_winapi::write_file(handle.0, &buf).map_err(|e| e.to_pyexception(vm))?; Ok(vm .ctx .new_tuple(vec![ - vm.ctx.new_int(written).into(), - vm.ctx.new_int(err).into(), + vm.ctx.new_int(result.written).into(), + vm.ctx.new_int(result.error).into(), ]) .into()) } @@ -1396,45 +859,15 @@ mod _winapi { /// ReadFile - Read data from a file or I/O device. #[pyfunction] fn ReadFile(args: ReadFileArgs, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::ReadFile as WinReadFile; - let handle = args.handle; let size = args.size; let use_overlapped = args.overlapped; if use_overlapped { - use windows_sys::Win32::Foundation::ERROR_IO_PENDING; - - let ov = Overlapped::new_with_handle(handle.0); + let ov = Overlapped::new_with_handle(handle.0, vm)?; let err = { let mut inner = ov.inner.lock(); - inner.read_buffer = Some(vec![0u8; size as usize]); - let read_buf = inner.read_buffer.as_mut().unwrap(); - let mut nread: u32 = 0; - let ret = unsafe { - WinReadFile( - handle.0, - read_buf.as_mut_ptr() as *mut _, - size, - &mut nread, - &mut *inner.overlapped, - ) - }; - - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - - if ret == 0 && err != ERROR_IO_PENDING && err != ERROR_MORE_DATA { - return Err(vm.new_last_os_error()); - } - if ret == 0 && err == ERROR_IO_PENDING { - inner.pending = true; - } - - err + inner.read(size).map_err(|e| e.to_pyexception(vm))? }; let result = vm .ctx @@ -1442,31 +875,12 @@ mod _winapi { return Ok(result.into()); } - let mut buf = vec![0u8; size as usize]; - let mut nread: u32 = 0; - let ret = unsafe { - WinReadFile( - handle.0, - buf.as_mut_ptr() as *mut _, - size, - &mut nread, - null_mut(), - ) - }; - let err = if ret == 0 { - unsafe { windows_sys::Win32::Foundation::GetLastError() } - } else { - 0 - }; - if ret == 0 && err != ERROR_MORE_DATA { - return Err(vm.new_last_os_error()); - } - buf.truncate(nread as usize); + let result = host_winapi::read_file(handle.0, size).map_err(|e| e.to_pyexception(vm))?; Ok(vm .ctx .new_tuple(vec![ - vm.ctx.new_bytes(buf).into(), - vm.ctx.new_int(err).into(), + vm.ctx.new_bytes(result.data).into(), + vm.ctx.new_int(result.error).into(), ]) .into()) } @@ -1480,38 +894,21 @@ mod _winapi { collect_data_timeout: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - use windows_sys::Win32::System::Pipes::SetNamedPipeHandleState as WinSetNamedPipeHandleState; - - let mut dw_args: [u32; 3] = [0; 3]; - let mut p_args: [*mut u32; 3] = [null_mut(); 3]; - let objs = [&mode, &max_collection_count, &collect_data_timeout]; - for (i, obj) in objs.iter().enumerate() { + let mut values = [None; 3]; + for (index, obj) in objs.iter().enumerate() { if !vm.is_none(obj) { - dw_args[i] = u32::try_from_object(vm, (*obj).clone())?; - p_args[i] = &mut dw_args[i]; + values[index] = Some(u32::try_from_object(vm, (*obj).clone())?); } } - - let ret = - unsafe { WinSetNamedPipeHandleState(named_pipe.0, p_args[0], p_args[1], p_args[2]) }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::set_named_pipe_handle_state(named_pipe.0, values[0], values[1], values[2]) + .map_err(|e| e.to_pyexception(vm)) } /// ResetEvent - Reset the specified event object to the nonsignaled state. #[pyfunction] fn ResetEvent(event: WinHandle, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Threading::ResetEvent as WinResetEvent; - - let ret = unsafe { WinResetEvent(event.0) }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::reset_event(event.0).map_err(|e| e.to_pyexception(vm)) } /// CreateMutexW - Create or open a named or unnamed mutex object. @@ -1522,18 +919,12 @@ mod _winapi { name: Option, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::CreateMutexW as WinCreateMutexW; - let _ = security_attributes; let name_wide = name.map(|n| n.as_wtf8().to_wide_with_nul()); let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); - - let handle = unsafe { WinCreateMutexW(null(), i32::from(initial_owner), name_ptr) }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::create_mutex_w(initial_owner, name_ptr) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// OpenEventW - Open an existing named event object. @@ -1544,21 +935,10 @@ mod _winapi { name: PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Threading::OpenEventW as WinOpenEventW; - let name_wide = name.as_wtf8().to_wide_with_nul(); - let handle = unsafe { - WinOpenEventW( - desired_access, - i32::from(inherit_handle), - name_wide.as_ptr(), - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_event_w(desired_access, inherit_handle, name_wide.as_ptr()) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } const MAXIMUM_WAIT_OBJECTS: usize = 64; @@ -1571,21 +951,17 @@ mod _winapi { milliseconds: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - use alloc::sync::Arc; - use core::sync::atomic::{AtomicU32, Ordering}; - use windows_sys::Win32::Foundation::{CloseHandle, WAIT_FAILED, WAIT_OBJECT_0}; - use windows_sys::Win32::System::SystemInformation::GetTickCount64; - use windows_sys::Win32::System::Threading::{ - CreateEventW as WinCreateEventW, CreateThread, GetExitCodeThread, - INFINITE as WIN_INFINITE, ResumeThread, SetEvent as WinSetEvent, TerminateThread, - WaitForMultipleObjects, - }; + use windows_sys::Win32::System::Threading::INFINITE as WIN_INFINITE; let milliseconds = milliseconds.unwrap_or(WIN_INFINITE); // Get handles from sequence let seq = ArgSequence::::try_from_object(vm, handle_seq)?; - let handles: Vec = seq.into_vec(); + let handles: Vec = seq + .into_vec() + .into_iter() + .map(|handle| handle as _) + .collect(); let nhandles = handles.len(); if nhandles == 0 { @@ -1604,299 +980,55 @@ mod _winapi { ))); } - // Create batches of handles - let batch_size = MAXIMUM_WAIT_OBJECTS - 1; // Leave room for cancel_event - let mut batches: Vec> = Vec::new(); - let mut i = 0; - while i < nhandles { - let end = core::cmp::min(i + batch_size, nhandles); - batches.push(handles[i..end].to_vec()); - i = end; - } - #[cfg(feature = "threading")] let sigint_event = { let is_main = crate::stdlib::_thread::get_ident() == vm.state.main_thread_ident.load(); if is_main { - let handle = crate::signal::get_sigint_event().unwrap_or_else(|| { - let handle = unsafe { WinCreateEventW(null(), 1, 0, null()) }; - if !handle.is_null() { - crate::signal::set_sigint_event(handle as isize); - } - handle as isize - }); - if handle == 0 { None } else { Some(handle) } + let handle = crate::signal::get_sigint_event() + .map(|handle| handle as HANDLE) + .unwrap_or_else(|| { + let handle = host_winapi::create_event_w(true, false, null()) + .unwrap_or(core::ptr::null_mut()); + if !handle.is_null() { + crate::signal::set_sigint_event(handle as isize); + } + handle + }); + if handle.is_null() { None } else { Some(handle) } } else { None } }; #[cfg(not(feature = "threading"))] - let sigint_event: Option = None; - - if wait_all { - // For wait_all, we wait sequentially for each batch - let mut err: Option = None; - let deadline = if milliseconds != WIN_INFINITE { - Some(unsafe { GetTickCount64() } + milliseconds as u64) - } else { - None - }; - - for batch in &batches { - let timeout = if let Some(deadline) = deadline { - let now = unsafe { GetTickCount64() }; - if now >= deadline { - err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); - break; - } - (deadline - now) as u32 - } else { - WIN_INFINITE - }; - - let batch_handles: Vec<_> = batch.iter().map(|&h| h as _).collect(); - let result = unsafe { - WaitForMultipleObjects( - batch_handles.len() as u32, - batch_handles.as_ptr(), - 1, // wait_all = TRUE - timeout, - ) - }; - - if result == WAIT_FAILED { - err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); - break; - } - if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - err = Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT); - break; - } - - if let Some(sigint_event) = sigint_event { - let sig_result = unsafe { - windows_sys::Win32::System::Threading::WaitForSingleObject( - sigint_event as _, - 0, - ) - }; - if sig_result == WAIT_OBJECT_0 { - err = Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT); - break; - } - if sig_result == WAIT_FAILED { - err = Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); - break; - } - } - } - - if let Some(err) = err { - if err == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.timeout_error.to_owned(), - None, - "timed out", - ) - .upcast()); - } - if err == windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT { - return Err(vm - .new_errno_error(libc::EINTR, "Interrupted system call") - .upcast()); - } - return Err(vm.new_os_error(err as i32)); - } - - Ok(vm.ctx.none()) - } else { - // For wait_any, we use threads to wait on each batch in parallel - let cancel_event = unsafe { WinCreateEventW(null(), 1, 0, null()) }; // Manual reset, not signaled - if cancel_event.is_null() { - return Err(vm.new_last_os_error()); - } - - struct BatchData { - handles: Vec, - cancel_event: isize, - handle_base: usize, - result: AtomicU32, - thread: core::cell::UnsafeCell, - } - - unsafe impl Send for BatchData {} - unsafe impl Sync for BatchData {} - - let batch_data: Vec> = batches - .iter() - .enumerate() - .map(|(idx, batch)| { - let base = idx * batch_size; - let mut handles_with_cancel = batch.clone(); - handles_with_cancel.push(cancel_event as isize); - Arc::new(BatchData { - handles: handles_with_cancel, - cancel_event: cancel_event as isize, - handle_base: base, - result: AtomicU32::new(WAIT_FAILED), - thread: core::cell::UnsafeCell::new(0), - }) - }) - .collect(); - - // Thread function - extern "system" fn batch_wait_thread(param: *mut core::ffi::c_void) -> u32 { - let data = unsafe { &*(param as *const BatchData) }; - let handles: Vec<_> = data.handles.iter().map(|&h| h as _).collect(); - let result = unsafe { - WaitForMultipleObjects( - handles.len() as u32, - handles.as_ptr(), - 0, // wait_any - WIN_INFINITE, - ) - }; - data.result.store(result, Ordering::SeqCst); - - if result == WAIT_FAILED { - let err = unsafe { windows_sys::Win32::Foundation::GetLastError() }; - unsafe { WinSetEvent(data.cancel_event as _) }; - return err; - } else if result >= windows_sys::Win32::Foundation::WAIT_ABANDONED_0 - && result - < windows_sys::Win32::Foundation::WAIT_ABANDONED_0 - + MAXIMUM_WAIT_OBJECTS as u32 - { - data.result.store(WAIT_FAILED, Ordering::SeqCst); - unsafe { WinSetEvent(data.cancel_event as _) }; - return windows_sys::Win32::Foundation::ERROR_ABANDONED_WAIT_0; - } - 0 - } - - // Create threads - let mut thread_handles: Vec = Vec::new(); - for data in &batch_data { - let thread = unsafe { - CreateThread( - null(), - 1, // Smallest stack - Some(batch_wait_thread), - Arc::as_ptr(data) as *const _ as *mut _, - 4, // CREATE_SUSPENDED - null_mut(), - ) - }; - if thread.is_null() { - // Cleanup on error - for h in &thread_handles { - unsafe { TerminateThread(*h as _, 0) }; - unsafe { CloseHandle(*h as _) }; - } - unsafe { CloseHandle(cancel_event) }; - return Err(vm.new_last_os_error()); - } - unsafe { *data.thread.get() = thread as isize }; - thread_handles.push(thread as isize); - } - - // Resume all threads - for &thread in &thread_handles { - unsafe { ResumeThread(thread as _) }; - } - - // Wait for any thread to complete - let mut thread_handles_raw: Vec<_> = thread_handles.iter().map(|&h| h as _).collect(); - if let Some(sigint_event) = sigint_event { - thread_handles_raw.push(sigint_event as _); - } - let result = unsafe { - WaitForMultipleObjects( - thread_handles_raw.len() as u32, - thread_handles_raw.as_ptr(), - 0, // wait_any - milliseconds, + let sigint_event: Option = None; + + match host_winapi::batched_wait_for_multiple_objects( + &handles, + wait_all, + milliseconds, + sigint_event, + ) { + Ok(host_winapi::BatchedWaitResult::All) => Ok(vm.ctx.none()), + Ok(host_winapi::BatchedWaitResult::Indices(indices)) => Ok(vm + .ctx + .new_list( + indices + .into_iter() + .map(|index| vm.ctx.new_int(index).into()) + .collect(), ) - }; - - let err = if result == WAIT_FAILED { - Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }) - } else if result == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - Some(windows_sys::Win32::Foundation::WAIT_TIMEOUT) - } else if sigint_event.is_some() - && result == WAIT_OBJECT_0 + thread_handles_raw.len() as u32 - { - Some(windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT) - } else { - None - }; - - // Signal cancel event to stop other threads - unsafe { WinSetEvent(cancel_event) }; - - // Wait for all threads to finish - let thread_handles_only: Vec<_> = thread_handles.iter().map(|&h| h as _).collect(); - unsafe { - WaitForMultipleObjects( - thread_handles_only.len() as u32, - thread_handles_only.as_ptr(), - 1, // wait_all - WIN_INFINITE, + .into()), + Err(host_winapi::BatchedWaitError::Timeout) => Err(vm + .new_os_subtype_error( + vm.ctx.exceptions.timeout_error.to_owned(), + None, + "timed out", ) - }; - - // Check for errors from threads - let mut thread_err = err; - for data in &batch_data { - if thread_err.is_none() && data.result.load(Ordering::SeqCst) == WAIT_FAILED { - let mut exit_code: u32 = 0; - let thread = unsafe { *data.thread.get() }; - if unsafe { GetExitCodeThread(thread as _, &mut exit_code) } == 0 { - thread_err = - Some(unsafe { windows_sys::Win32::Foundation::GetLastError() }); - } else if exit_code != 0 { - thread_err = Some(exit_code); - } - } - let thread = unsafe { *data.thread.get() }; - unsafe { CloseHandle(thread as _) }; - } - - unsafe { CloseHandle(cancel_event) }; - - // Return result - if let Some(e) = thread_err { - if e == windows_sys::Win32::Foundation::WAIT_TIMEOUT { - return Err(vm - .new_os_subtype_error( - vm.ctx.exceptions.timeout_error.to_owned(), - None, - "timed out", - ) - .upcast()); - } - if e == windows_sys::Win32::Foundation::ERROR_CONTROL_C_EXIT { - return Err(vm - .new_errno_error(libc::EINTR, "Interrupted system call") - .upcast()); - } - return Err(vm.new_os_error(e as i32)); - } - - // Collect triggered indices - let mut triggered_indices: Vec = Vec::new(); - for data in &batch_data { - let result = data.result.load(Ordering::SeqCst); - let triggered = result as i32 - WAIT_OBJECT_0 as i32; - // Check if it's a valid handle index (not the cancel_event which is last) - if triggered >= 0 && (triggered as usize) < data.handles.len() - 1 { - let index = data.handle_base + triggered as usize; - triggered_indices.push(vm.ctx.new_int(index).into()); - } - } - - Ok(vm.ctx.new_list(triggered_indices).into()) + .upcast()), + Err(host_winapi::BatchedWaitError::Interrupted) => Err(vm + .new_errno_error(libc::EINTR, "Interrupted system call") + .upcast()), + Err(host_winapi::BatchedWaitError::Os(err)) => Err(vm.new_os_error(err as i32)), } } @@ -1922,22 +1054,15 @@ mod _winapi { } let name_wide = name.as_ref().map(|n| n.as_wtf8().to_wide_with_nul()); let name_ptr = name_wide.as_ref().map_or(null(), |n| n.as_ptr()); - - let handle = unsafe { - CreateFileMappingW( - file_handle.0, - null(), - protect, - max_size_high, - max_size_low, - name_ptr, - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::create_file_mapping_w( + file_handle.0, + protect, + max_size_high, + max_size_low, + name_ptr, + ) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// OpenFileMapping - Open a named file mapping object. @@ -1948,26 +1073,15 @@ mod _winapi { name: PyStrRef, vm: &VirtualMachine, ) -> PyResult { - use windows_sys::Win32::System::Memory::OpenFileMappingW; - if name.as_bytes().contains(&0) { return Err( vm.new_value_error("OpenFileMapping: name must not contain null characters") ); } let name_wide = name.as_wtf8().to_wide_with_nul(); - let handle = unsafe { - OpenFileMappingW( - desired_access, - i32::from(inherit_handle), - name_wide.as_ptr(), - ) - }; - - if handle.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(WinHandle(handle)) + host_winapi::open_file_mapping_w(desired_access, inherit_handle, name_wide.as_ptr()) + .map(WinHandle) + .map_err(|e| e.to_pyexception(vm)) } /// MapViewOfFile - Map a view of a file mapping into the address space. @@ -1980,57 +1094,26 @@ mod _winapi { number_bytes: usize, vm: &VirtualMachine, ) -> PyResult { - let address = unsafe { - windows_sys::Win32::System::Memory::MapViewOfFile( - file_map.0, - desired_access, - file_offset_high, - file_offset_low, - number_bytes, - ) - }; - - let ptr = address.Value; - if ptr.is_null() { - return Err(vm.new_last_os_error()); - } - Ok(ptr as isize) + host_winapi::map_view_of_file( + file_map.0, + desired_access, + file_offset_high, + file_offset_low, + number_bytes, + ) + .map_err(|e| e.to_pyexception(vm)) } /// UnmapViewOfFile - Unmap a mapped view of a file. #[pyfunction] fn UnmapViewOfFile(address: isize, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::System::Memory::MEMORY_MAPPED_VIEW_ADDRESS; - - let view = MEMORY_MAPPED_VIEW_ADDRESS { - Value: address as *mut core::ffi::c_void, - }; - let ret = unsafe { windows_sys::Win32::System::Memory::UnmapViewOfFile(view) }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) + host_winapi::unmap_view_of_file(address).map_err(|e| e.to_pyexception(vm)) } /// VirtualQuerySize - Return the size of a memory region. #[pyfunction] fn VirtualQuerySize(address: isize, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::System::Memory::{MEMORY_BASIC_INFORMATION, VirtualQuery}; - - let mut mbi: MEMORY_BASIC_INFORMATION = unsafe { core::mem::zeroed() }; - let ret = unsafe { - VirtualQuery( - address as *const core::ffi::c_void, - &mut mbi, - core::mem::size_of::(), - ) - }; - - if ret == 0 { - return Err(vm.new_last_os_error()); - } - Ok(mbi.RegionSize) + host_winapi::virtual_query_size(address).map_err(|e| e.to_pyexception(vm)) } /// CopyFile2 - Copy a file with extended parameters. @@ -2042,29 +1125,10 @@ mod _winapi { _progress_routine: OptionalArg, vm: &VirtualMachine, ) -> PyResult<()> { - use windows_sys::Win32::Storage::FileSystem::{ - COPYFILE2_EXTENDED_PARAMETERS, CopyFile2 as WinCopyFile2, - }; - let src_wide = existing_file_name.as_wtf8().to_wide_with_nul(); let dst_wide = new_file_name.as_wtf8().to_wide_with_nul(); - - let mut params: COPYFILE2_EXTENDED_PARAMETERS = unsafe { core::mem::zeroed() }; - params.dwSize = core::mem::size_of::() as u32; - params.dwCopyFlags = flags; - - let hr = unsafe { WinCopyFile2(src_wide.as_ptr(), dst_wide.as_ptr(), ¶ms) }; - - if hr < 0 { - // HRESULT failure - convert to Windows error code - let err = if (hr as u32 >> 16) == 0x8007 { - (hr as u32) & 0xFFFF - } else { - hr as u32 - }; - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - Ok(()) + host_winapi::copy_file2(src_wide.as_ptr(), dst_wide.as_ptr(), flags) + .map_err(|e| e.to_pyexception(vm)) } /// _mimetypes_read_windows_registry - Read MIME type associations from registry. @@ -2073,110 +1137,15 @@ mod _winapi { on_type_read: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - use windows_sys::Win32::System::Registry::{ - HKEY, HKEY_CLASSES_ROOT, KEY_READ, REG_SZ, RegCloseKey, RegEnumKeyExW, RegOpenKeyExW, - RegQueryValueExW, - }; - - let mut hkcr: HKEY = null_mut() as HKEY; - let err = unsafe { RegOpenKeyExW(HKEY_CLASSES_ROOT, null(), 0, KEY_READ, &mut hkcr) }; - if err != 0 { - return Err(vm.new_os_error(err as i32)); - } - scopeguard::defer! { unsafe { RegCloseKey(hkcr) }; } - - let mut i: u32 = 0; - let mut entries: Vec<(String, String)> = Vec::new(); - - loop { - let mut ext_buf = [0u16; 128]; - let mut cch_ext: u32 = ext_buf.len() as u32; - - let err = unsafe { - RegEnumKeyExW( - hkcr, - i, - ext_buf.as_mut_ptr(), - &mut cch_ext, - null_mut(), - null_mut(), - null_mut(), - null_mut(), - ) - }; - i += 1; - - if err == windows_sys::Win32::Foundation::ERROR_NO_MORE_ITEMS { - break; - } - if err != 0 && err != windows_sys::Win32::Foundation::ERROR_MORE_DATA { - return Err(vm.new_os_error(err as i32)); - } - - // Only process keys starting with '.' - if cch_ext == 0 || ext_buf[0] != b'.' as u16 { - continue; - } - - let ext_wide = &ext_buf[..cch_ext as usize]; - - // Open subkey to read Content Type - let mut subkey: HKEY = null_mut() as HKEY; - let err = unsafe { RegOpenKeyExW(hkcr, ext_buf.as_ptr(), 0, KEY_READ, &mut subkey) }; - if err == windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND - || err == windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED - { - continue; + host_winapi::read_windows_mimetype_registry_in_batches(|entries| { + for (mime_type, ext) in entries.drain(..) { + on_type_read.call((vm.ctx.new_str(mime_type), vm.ctx.new_str(ext)), vm)?; } - if err != 0 { - return Err(vm.new_os_error(err as i32)); - } - - let content_type_key: Vec = "Content Type\0".encode_utf16().collect(); - let mut type_buf = [0u16; 256]; - let mut cb_type: u32 = (type_buf.len() * 2) as u32; - let mut reg_type: u32 = 0; - - let err = unsafe { - RegQueryValueExW( - subkey, - content_type_key.as_ptr(), - null_mut(), - &mut reg_type, - type_buf.as_mut_ptr() as *mut u8, - &mut cb_type, - ) - }; - unsafe { RegCloseKey(subkey) }; - - if err != 0 || reg_type != REG_SZ || cb_type == 0 { - continue; - } - - // Convert wide strings to Rust strings - let type_len = (cb_type as usize / 2).saturating_sub(1); // exclude null terminator - let type_str = String::from_utf16_lossy(&type_buf[..type_len]); - let ext_str = String::from_utf16_lossy(ext_wide); - - if type_str.is_empty() { - continue; - } - - entries.push((type_str, ext_str)); - - // Flush buffer periodically to call Python callback - if entries.len() >= 64 { - for (mime_type, ext) in entries.drain(..) { - on_type_read.call((vm.ctx.new_str(mime_type), vm.ctx.new_str(ext)), vm)?; - } - } - } - - // Process remaining entries - for (mime_type, ext) in entries { - on_type_read.call((vm.ctx.new_str(mime_type), vm.ctx.new_str(ext)), vm)?; - } - - Ok(()) + Ok(()) + }) + .map_err(|err| match err { + host_winapi::MimeRegistryReadError::Os(err) => vm.new_os_error(err as i32), + host_winapi::MimeRegistryReadError::Callback(err) => err, + }) } } diff --git a/crates/vm/src/stdlib/_wmi.rs b/crates/vm/src/stdlib/_wmi.rs index 96275e5ac4b..93356d887e1 100644 --- a/crates/vm/src/stdlib/_wmi.rs +++ b/crates/vm/src/stdlib/_wmi.rs @@ -3,562 +3,15 @@ pub(crate) use _wmi::module_def; -// COM/WMI FFI declarations (not inside pymodule to avoid macro issues) -mod wmi_ffi { - #![allow(unsafe_op_in_unsafe_fn)] - use core::ffi::c_void; - - pub type HRESULT = i32; - - #[repr(C)] - pub struct GUID { - pub data1: u32, - pub data2: u16, - pub data3: u16, - pub data4: [u8; 8], - } - - // Opaque VARIANT type (24 bytes covers both 32-bit and 64-bit) - #[repr(C, align(8))] - pub struct VARIANT([u64; 3]); - - impl VARIANT { - pub fn zeroed() -> Self { - VARIANT([0u64; 3]) - } - } - - // CLSID_WbemLocator = {4590F811-1D3A-11D0-891F-00AA004B2E24} - pub const CLSID_WBEM_LOCATOR: GUID = GUID { - data1: 0x4590F811, - data2: 0x1D3A, - data3: 0x11D0, - data4: [0x89, 0x1F, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], - }; - - // IID_IWbemLocator = {DC12A687-737F-11CF-884D-00AA004B2E24} - pub const IID_IWBEM_LOCATOR: GUID = GUID { - data1: 0xDC12A687, - data2: 0x737F, - data3: 0x11CF, - data4: [0x88, 0x4D, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24], - }; - - // COM constants - pub const COINIT_APARTMENTTHREADED: u32 = 0x2; - pub const CLSCTX_INPROC_SERVER: u32 = 0x1; - pub const RPC_C_AUTHN_LEVEL_DEFAULT: u32 = 0; - pub const RPC_C_IMP_LEVEL_IMPERSONATE: u32 = 3; - pub const RPC_C_AUTHN_LEVEL_CALL: u32 = 3; - pub const RPC_C_AUTHN_WINNT: u32 = 10; - pub const RPC_C_AUTHZ_NONE: u32 = 0; - pub const EOAC_NONE: u32 = 0; - pub const RPC_E_TOO_LATE: HRESULT = 0x80010119_u32 as i32; - - // WMI constants - pub const WBEM_FLAG_FORWARD_ONLY: i32 = 0x20; - pub const WBEM_FLAG_RETURN_IMMEDIATELY: i32 = 0x10; - pub const WBEM_S_FALSE: HRESULT = 1; - pub const WBEM_S_NO_MORE_DATA: HRESULT = 0x40005; - pub const WBEM_INFINITE: i32 = -1; - pub const WBEM_FLAVOR_MASK_ORIGIN: i32 = 0x60; - pub const WBEM_FLAVOR_ORIGIN_SYSTEM: i32 = 0x40; - - #[link(name = "ole32")] - unsafe extern "system" { - pub fn CoInitializeEx(pvReserved: *mut c_void, dwCoInit: u32) -> HRESULT; - pub fn CoUninitialize(); - pub fn CoInitializeSecurity( - pSecDesc: *const c_void, - cAuthSvc: i32, - asAuthSvc: *const c_void, - pReserved1: *const c_void, - dwAuthnLevel: u32, - dwImpLevel: u32, - pAuthList: *const c_void, - dwCapabilities: u32, - pReserved3: *const c_void, - ) -> HRESULT; - pub fn CoCreateInstance( - rclsid: *const GUID, - pUnkOuter: *mut c_void, - dwClsContext: u32, - riid: *const GUID, - ppv: *mut *mut c_void, - ) -> HRESULT; - pub fn CoSetProxyBlanket( - pProxy: *mut c_void, - dwAuthnSvc: u32, - dwAuthzSvc: u32, - pServerPrincName: *const u16, - dwAuthnLevel: u32, - dwImpLevel: u32, - pAuthInfo: *const c_void, - dwCapabilities: u32, - ) -> HRESULT; - } - - #[link(name = "oleaut32")] - unsafe extern "system" { - pub fn SysAllocString(psz: *const u16) -> *mut u16; - pub fn SysFreeString(bstrString: *mut u16); - pub fn VariantClear(pvarg: *mut VARIANT) -> HRESULT; - } - - #[link(name = "propsys")] - unsafe extern "system" { - pub fn VariantToString(varIn: *const VARIANT, pszBuf: *mut u16, cchBuf: u32) -> HRESULT; - } - - /// Release a COM object (IUnknown::Release, vtable index 2) - pub unsafe fn com_release(this: *mut c_void) { - if !this.is_null() { - let vtable = *(this as *const *const usize); - let release: unsafe extern "system" fn(*mut c_void) -> u32 = - core::mem::transmute(*vtable.add(2)); - release(this); - } - } - - /// IWbemLocator::ConnectServer (vtable index 3) - #[allow(clippy::too_many_arguments)] - pub unsafe fn locator_connect_server( - this: *mut c_void, - network_resource: *const u16, - user: *const u16, - password: *const u16, - locale: *const u16, - security_flags: i32, - authority: *const u16, - ctx: *mut c_void, - services: *mut *mut c_void, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - *const u16, - *const u16, - *const u16, - *const u16, - i32, - *const u16, - *mut c_void, - *mut *mut c_void, - ) -> HRESULT = core::mem::transmute(*vtable.add(3)); - method( - this, - network_resource, - user, - password, - locale, - security_flags, - authority, - ctx, - services, - ) - } - - /// IWbemServices::ExecQuery (vtable index 20) - pub unsafe fn services_exec_query( - this: *mut c_void, - query_language: *const u16, - query: *const u16, - flags: i32, - ctx: *mut c_void, - enumerator: *mut *mut c_void, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - *const u16, - *const u16, - i32, - *mut c_void, - *mut *mut c_void, - ) -> HRESULT = core::mem::transmute(*vtable.add(20)); - method(this, query_language, query, flags, ctx, enumerator) - } - - /// IEnumWbemClassObject::Next (vtable index 4) - pub unsafe fn enum_next( - this: *mut c_void, - timeout: i32, - count: u32, - objects: *mut *mut c_void, - returned: *mut u32, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - i32, - u32, - *mut *mut c_void, - *mut u32, - ) -> HRESULT = core::mem::transmute(*vtable.add(4)); - method(this, timeout, count, objects, returned) - } - - /// IWbemClassObject::BeginEnumeration (vtable index 8) - pub unsafe fn object_begin_enumeration(this: *mut c_void, enum_flags: i32) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn(*mut c_void, i32) -> HRESULT = - core::mem::transmute(*vtable.add(8)); - method(this, enum_flags) - } - - /// IWbemClassObject::Next (vtable index 9) - pub unsafe fn object_next( - this: *mut c_void, - flags: i32, - name: *mut *mut u16, - val: *mut VARIANT, - cim_type: *mut i32, - flavor: *mut i32, - ) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn( - *mut c_void, - i32, - *mut *mut u16, - *mut VARIANT, - *mut i32, - *mut i32, - ) -> HRESULT = core::mem::transmute(*vtable.add(9)); - method(this, flags, name, val, cim_type, flavor) - } - - /// IWbemClassObject::EndEnumeration (vtable index 10) - pub unsafe fn object_end_enumeration(this: *mut c_void) -> HRESULT { - let vtable = *(this as *const *const usize); - let method: unsafe extern "system" fn(*mut c_void) -> HRESULT = - core::mem::transmute(*vtable.add(10)); - method(this) - } -} - #[pymodule] mod _wmi { - use super::wmi_ffi::*; use crate::builtins::PyStrRef; use crate::convert::ToPyException; use crate::{PyResult, VirtualMachine}; - use core::ffi::c_void; - use core::ptr::{null, null_mut}; - use windows_sys::Win32::Foundation::{ - CloseHandle, ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_NOT_ENOUGH_MEMORY, GetLastError, - HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT, - }; - use windows_sys::Win32::Storage::FileSystem::{ReadFile, WriteFile}; - use windows_sys::Win32::System::Pipes::CreatePipe; - use windows_sys::Win32::System::Threading::{ - CreateEventW, CreateThread, GetExitCodeThread, SetEvent, WaitForSingleObject, - }; + use rustpython_host_env::wmi as host_wmi; const BUFFER_SIZE: usize = 8192; - fn hresult_from_win32(err: u32) -> HRESULT { - if err == 0 { - 0 - } else { - ((err & 0xFFFF) | 0x80070000) as HRESULT - } - } - - fn succeeded(hr: HRESULT) -> bool { - hr >= 0 - } - - fn failed(hr: HRESULT) -> bool { - hr < 0 - } - - fn wide_str(s: &str) -> Vec { - s.encode_utf16().chain(core::iter::once(0)).collect() - } - - unsafe fn wcslen(s: *const u16) -> usize { - unsafe { - let mut len = 0; - while *s.add(len) != 0 { - len += 1; - } - len - } - } - - unsafe fn wait_event(event: HANDLE, timeout: u32) -> u32 { - unsafe { - match WaitForSingleObject(event, timeout) { - WAIT_OBJECT_0 => 0, - WAIT_TIMEOUT => WAIT_TIMEOUT, - _ => GetLastError(), - } - } - } - - struct QueryThreadData { - query: Vec, - write_pipe: HANDLE, - init_event: HANDLE, - connect_event: HANDLE, - } - - // SAFETY: QueryThreadData contains HANDLEs (isize) which are safe to send across threads - unsafe impl Send for QueryThreadData {} - - unsafe extern "system" fn query_thread(param: *mut c_void) -> u32 { - unsafe { query_thread_impl(param) } - } - - unsafe fn query_thread_impl(param: *mut c_void) -> u32 { - unsafe { - let data = Box::from_raw(param as *mut QueryThreadData); - let write_pipe = data.write_pipe; - let init_event = data.init_event; - let connect_event = data.connect_event; - - let mut locator: *mut c_void = null_mut(); - let mut services: *mut c_void = null_mut(); - let mut enumerator: *mut c_void = null_mut(); - let mut hr: HRESULT = 0; - - // gh-125315: Copy the query string first - let bstr_query = SysAllocString(data.query.as_ptr()); - if bstr_query.is_null() { - hr = hresult_from_win32(ERROR_NOT_ENOUGH_MEMORY); - } - - drop(data); - - if succeeded(hr) { - hr = CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED); - } - - if failed(hr) { - CloseHandle(write_pipe); - if !bstr_query.is_null() { - SysFreeString(bstr_query); - } - return hr as u32; - } - - hr = CoInitializeSecurity( - null(), - -1, - null(), - null(), - RPC_C_AUTHN_LEVEL_DEFAULT, - RPC_C_IMP_LEVEL_IMPERSONATE, - null(), - EOAC_NONE, - null(), - ); - // gh-96684: CoInitializeSecurity will fail if another part of the app has - // already called it. - if hr == RPC_E_TOO_LATE { - hr = 0; - } - - if succeeded(hr) { - hr = CoCreateInstance( - &CLSID_WBEM_LOCATOR, - null_mut(), - CLSCTX_INPROC_SERVER, - &IID_IWBEM_LOCATOR, - &mut locator, - ); - } - if succeeded(hr) && SetEvent(init_event) == 0 { - hr = hresult_from_win32(GetLastError()); - } - - if succeeded(hr) { - let root_cimv2 = wide_str("ROOT\\CIMV2"); - let bstr_root = SysAllocString(root_cimv2.as_ptr()); - hr = locator_connect_server( - locator, - bstr_root, - null(), - null(), - null(), - 0, - null(), - null_mut(), - &mut services, - ); - if !bstr_root.is_null() { - SysFreeString(bstr_root); - } - } - if succeeded(hr) && SetEvent(connect_event) == 0 { - hr = hresult_from_win32(GetLastError()); - } - - if succeeded(hr) { - hr = CoSetProxyBlanket( - services, - RPC_C_AUTHN_WINNT, - RPC_C_AUTHZ_NONE, - null(), - RPC_C_AUTHN_LEVEL_CALL, - RPC_C_IMP_LEVEL_IMPERSONATE, - null(), - EOAC_NONE, - ); - } - if succeeded(hr) { - let wql = wide_str("WQL"); - let bstr_wql = SysAllocString(wql.as_ptr()); - hr = services_exec_query( - services, - bstr_wql, - bstr_query, - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - null_mut(), - &mut enumerator, - ); - if !bstr_wql.is_null() { - SysFreeString(bstr_wql); - } - } - - // Enumerate results and write to pipe - let mut value: *mut c_void; - let mut start_of_enum = true; - let null_sep: u16 = 0; - let eq_sign: u16 = b'=' as u16; - - while succeeded(hr) { - let mut got: u32 = 0; - let mut written: u32 = 0; - value = null_mut(); - hr = enum_next(enumerator, WBEM_INFINITE, 1, &mut value, &mut got); - - if hr == WBEM_S_FALSE { - hr = 0; - break; - } - if failed(hr) || got != 1 || value.is_null() { - continue; - } - - if !start_of_enum - && WriteFile( - write_pipe, - &null_sep as *const u16 as *const _, - 2, - &mut written, - null_mut(), - ) == 0 - { - hr = hresult_from_win32(GetLastError()); - com_release(value); - break; - } - start_of_enum = false; - - hr = object_begin_enumeration(value, 0); - if failed(hr) { - com_release(value); - break; - } - - while succeeded(hr) { - let mut prop_name: *mut u16 = null_mut(); - let mut prop_value = VARIANT::zeroed(); - let mut flavor: i32 = 0; - - hr = object_next( - value, - 0, - &mut prop_name, - &mut prop_value, - null_mut(), - &mut flavor, - ); - - if hr == WBEM_S_NO_MORE_DATA { - hr = 0; - break; - } - - if succeeded(hr) - && (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM - { - let mut prop_str = [0u16; BUFFER_SIZE]; - hr = - VariantToString(&prop_value, prop_str.as_mut_ptr(), BUFFER_SIZE as u32); - - if succeeded(hr) { - let cb_str1 = (wcslen(prop_name) * 2) as u32; - let cb_str2 = (wcslen(prop_str.as_ptr()) * 2) as u32; - - if WriteFile( - write_pipe, - prop_name as *const _, - cb_str1, - &mut written, - null_mut(), - ) == 0 - || WriteFile( - write_pipe, - &eq_sign as *const u16 as *const _, - 2, - &mut written, - null_mut(), - ) == 0 - || WriteFile( - write_pipe, - prop_str.as_ptr() as *const _, - cb_str2, - &mut written, - null_mut(), - ) == 0 - || WriteFile( - write_pipe, - &null_sep as *const u16 as *const _, - 2, - &mut written, - null_mut(), - ) == 0 - { - hr = hresult_from_win32(GetLastError()); - } - } - - VariantClear(&mut prop_value); - SysFreeString(prop_name); - } - } - - object_end_enumeration(value); - com_release(value); - } - - // Cleanup - if !bstr_query.is_null() { - SysFreeString(bstr_query); - } - if !enumerator.is_null() { - com_release(enumerator); - } - if !services.is_null() { - com_release(services); - } - if !locator.is_null() { - com_release(locator); - } - CoUninitialize(); - CloseHandle(write_pipe); - - hr as u32 - } - } - - /// Runs a WMI query against the local machine. - /// - /// This returns a single string with 'name=value' pairs in a flat array separated - /// by null characters. #[pyfunction] fn exec_query(query: PyStrRef, vm: &VirtualMachine) -> PyResult { let query_str = query.expect_str(); @@ -570,129 +23,15 @@ mod _wmi { return Err(vm.new_value_error("only SELECT queries are supported")); } - let query_wide = wide_str(query_str); - - let mut h_thread: HANDLE = null_mut(); - let mut err: u32 = 0; - let mut buffer = [0u16; BUFFER_SIZE]; - let mut offset: u32 = 0; - let mut bytes_read: u32 = 0; - - let mut read_pipe: HANDLE = null_mut(); - let mut write_pipe: HANDLE = null_mut(); - - unsafe { - let init_event = CreateEventW(null(), 1, 0, null()); - let connect_event = CreateEventW(null(), 1, 0, null()); - - if init_event.is_null() - || connect_event.is_null() - || CreatePipe(&mut read_pipe, &mut write_pipe, null(), 0) == 0 - { - err = GetLastError(); - } else { - let thread_data = Box::new(QueryThreadData { - query: query_wide, - write_pipe, - init_event, - connect_event, - }); - let thread_data_ptr = Box::into_raw(thread_data); - - h_thread = CreateThread( - null(), - 0, - Some(query_thread), - thread_data_ptr as *const _ as *mut _, - 0, - null_mut(), - ); - - if h_thread.is_null() { - err = GetLastError(); - // Thread didn't start, so recover data and close write pipe - let data = Box::from_raw(thread_data_ptr); - CloseHandle(data.write_pipe); - } - } - - // gh-112278: Timeout for COM init and WMI connection - if err == 0 { - err = wait_event(init_event, 1000); - if err == 0 { - err = wait_event(connect_event, 100); - } - } - - // Read results from pipe - while err == 0 { - let buf_ptr = (buffer.as_mut_ptr() as *mut u8).add(offset as usize); - let buf_remaining = (BUFFER_SIZE * 2) as u32 - offset; - - if ReadFile( - read_pipe, - buf_ptr as *mut _, - buf_remaining, - &mut bytes_read, - null_mut(), - ) != 0 - { - offset += bytes_read; - if offset >= (BUFFER_SIZE * 2) as u32 { - err = ERROR_MORE_DATA; - } - } else { - err = GetLastError(); - } - } - - if !read_pipe.is_null() { - CloseHandle(read_pipe); - } - - if !h_thread.is_null() { - let thread_err: u32; - match WaitForSingleObject(h_thread, 100) { - WAIT_OBJECT_0 => { - let mut exit_code: u32 = 0; - if GetExitCodeThread(h_thread, &mut exit_code) == 0 { - thread_err = GetLastError(); - } else { - thread_err = exit_code; - } - } - WAIT_TIMEOUT => { - thread_err = WAIT_TIMEOUT; - } - _ => { - thread_err = GetLastError(); - } - } - if err == 0 || err == ERROR_BROKEN_PIPE { - err = thread_err; - } - - CloseHandle(h_thread); - } - - CloseHandle(init_event); - CloseHandle(connect_event); - } - - if err == ERROR_MORE_DATA { - return Err(vm.new_os_error(format!( + match host_wmi::exec_query(query_str) { + Ok(result) => Ok(result), + Err(host_wmi::ExecQueryError::MoreData) => Err(vm.new_os_error(format!( "Query returns more than {} characters", BUFFER_SIZE, - ))); - } else if err != 0 { - return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)); - } - - if offset == 0 { - return Ok(String::new()); + ))), + Err(host_wmi::ExecQueryError::Code(err)) => { + Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm)) + } } - - let char_count = (offset as usize) / 2 - 1; - Ok(String::from_utf16_lossy(&buffer[..char_count])) } } diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 809e26249c3..90a348dfca1 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -17,7 +17,6 @@ pub(crate) mod module { ospath::{OsPath, OsPathOrFd}, stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, }; - use core::mem::MaybeUninit; use libc::intptr_t; use rustpython_common::wtf8::Wtf8Buf; use rustpython_host_env::nt as host_nt; @@ -67,11 +66,8 @@ pub(crate) mod module { #[pyfunction] pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult { - let attr = unsafe { FileSystem::GetFileAttributesW(path.to_wide_cstring(vm)?.as_ptr()) }; - Ok(attr != FileSystem::INVALID_FILE_ATTRIBUTES - && (mode & 2 == 0 - || attr & FileSystem::FILE_ATTRIBUTE_READONLY == 0 - || attr & FileSystem::FILE_ATTRIBUTE_DIRECTORY != 0)) + let _ = path.to_wide_cstring(vm)?; + Ok(host_nt::access(path.as_ref(), mode)) } #[pyfunction] @@ -81,59 +77,14 @@ pub(crate) mod module { dir_fd: DirFd<'static, 0>, vm: &VirtualMachine, ) -> PyResult<()> { - // On Windows, use DeleteFileW directly. - // Rust's std::fs::remove_file may have different behavior for read-only files. - // See Py_DeleteFileW. - use windows_sys::Win32::Storage::FileSystem::{ - DeleteFileW, FindClose, FindFirstFileW, RemoveDirectoryW, WIN32_FIND_DATAW, - }; - use windows_sys::Win32::System::SystemServices::{ - IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, - }; - let [] = dir_fd.0; - let wide_path = path.to_wide_cstring(vm)?; - let attrs = unsafe { FileSystem::GetFileAttributesW(wide_path.as_ptr()) }; - - let mut is_directory = false; - let mut is_link = false; - - if attrs != FileSystem::INVALID_FILE_ATTRIBUTES { - is_directory = (attrs & FileSystem::FILE_ATTRIBUTE_DIRECTORY) != 0; - - // Check if it's a symlink or junction point - if is_directory && (attrs & 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) }; - } - } - } - - let result = if is_directory && is_link { - unsafe { RemoveDirectoryW(wide_path.as_ptr()) } - } else { - unsafe { DeleteFileW(wide_path.as_ptr()) } - }; - - if result == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - Ok(()) + let _ = path.to_wide_cstring(vm)?; + host_nt::remove(path.as_ref()).map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[pyfunction] pub(super) fn _supports_virtual_terminal() -> PyResult { - let mut mode = 0; - let handle = unsafe { Console::GetStdHandle(Console::STD_ERROR_HANDLE) }; - if unsafe { Console::GetConsoleMode(handle, &mut mode) } == 0 { - return Ok(false); - } - Ok(mode & Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) + Ok(host_nt::supports_virtual_terminal()) } #[derive(FromArgs)] @@ -149,69 +100,15 @@ pub(crate) mod module { #[pyfunction] pub(super) fn symlink(args: SymlinkArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { use crate::exceptions::ToOSErrorBuilder; - use core::sync::atomic::{AtomicBool, Ordering}; - 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: &OsPath, dst: &OsPath) -> bool { - use windows_sys::Win32::Storage::FileSystem::GetFileExInfoStandard; - - let dst_parent = dst.as_path().parent(); - let Some(dst_parent) = dst_parent else { - return false; - }; - let resolved = if src.as_path().is_absolute() { - src.as_path().to_path_buf() - } else { - dst_parent.join(src.as_path()) - }; - 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 _ as *mut _, - ) - }; - 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 args.target_is_directory.target_is_directory || check_dir(&args.src, &args.dst) { - flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; - } - let src = args.src.to_wide_cstring(vm)?; let dst = args.dst.to_wide_cstring(vm)?; - - let mut result = unsafe { CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), flags) }; - if !result - && HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) - && unsafe { Foundation::GetLastError() } == Foundation::ERROR_INVALID_PARAMETER - { - let flags = flags & !SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - result = unsafe { CreateSymbolicLinkW(dst.as_ptr(), src.as_ptr(), flags) }; - if result - || unsafe { Foundation::GetLastError() } != Foundation::ERROR_INVALID_PARAMETER - { - HAS_UNPRIVILEGED_FLAG.store(false, Ordering::Relaxed); - } - } - - if !result { - let err = io::Error::last_os_error(); + if let Err(err) = host_nt::symlink( + args.src.as_ref(), + args.dst.as_ref(), + &src, + &dst, + args.target_is_directory.target_is_directory, + ) { let builder = err.to_os_error_builder(vm); let builder = builder .filename(args.src.filename(vm)) @@ -236,10 +133,7 @@ pub(crate) mod module { fn environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in crate::host_env::os::vars() { - // Skip hidden Windows environment variables (e.g., =C:, =D:, =ExitCode) - // These are internal cmd.exe bookkeeping variables that store per-drive - // current directories and cannot be reliably modified via _wputenv(). + for (key, value) in host_nt::visible_env_vars() { if key.starts_with('=') { continue; } @@ -251,10 +145,7 @@ pub(crate) mod module { #[pyfunction] fn _create_environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); - for (key, value) in crate::host_env::os::vars() { - if key.starts_with('=') { - continue; - } + for (key, value) in host_nt::visible_env_vars() { environ.set_item(&key, vm.new_pyobj(value), vm).unwrap(); } environ @@ -319,30 +210,9 @@ pub(crate) mod module { let follow_symlinks = follow_symlinks.into_option().unwrap_or(false); if follow_symlinks { - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, - FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, OPEN_EXISTING, - }; - let wide = path.to_wide_cstring(vm)?; - let handle = unsafe { - CreateFileW( - wide.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(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - let result = win32_hchmod(handle, mode, vm); - unsafe { Foundation::CloseHandle(handle) }; - result + host_nt::chmod_follow(&wide, mode, S_IWRITE) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } else { win32_lchmod(&path, mode, vm) } @@ -352,31 +222,8 @@ pub(crate) mod module { /// Uses FindFirstFileW to get the name as stored on the filesystem. #[pyfunction] fn _findfirstfile(path: OsPath, vm: &VirtualMachine) -> PyResult { - use crate::host_env::windows::ToWideString; - use std::os::windows::ffi::OsStringExt; - use windows_sys::Win32::Storage::FileSystem::{ - FindClose, FindFirstFileW, WIN32_FIND_DATAW, - }; - - let wide_path = path.as_ref().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) }; - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - - unsafe { FindClose(handle) }; - - // Convert the filename from the find data to a Rust string - // cFileName is a null-terminated wide string - let len = find_data - .cFileName - .iter() - .position(|&c| c == 0) - .unwrap_or(find_data.cFileName.len()); - let filename = std::ffi::OsString::from_wide(&find_data.cFileName[..len]); + let filename = host_nt::find_first_file_name(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; let filename_str = filename .to_str() .ok_or_else(|| vm.new_unicode_decode_error("filename contains invalid UTF-8"))?; @@ -406,57 +253,24 @@ pub(crate) mod module { /// _testInfo - determine file type based on attributes and reparse tag fn _test_info(attributes: u32, reparse_tag: u32, disk_device: bool, tested_type: u32) -> 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, + let tested_type = match tested_type { + PY_IFREG => host_nt::TestType::RegularFile, + PY_IFDIR => host_nt::TestType::Directory, + PY_IFLNK => host_nt::TestType::Symlink, + PY_IFMNT => host_nt::TestType::Junction, + PY_IFLRP => host_nt::TestType::LinkReparsePoint, + PY_IFRRP => host_nt::TestType::RegularReparsePoint, + _ => return false, }; - - match tested_type { - PY_IFREG => { - // diskDevice && attributes && !(attributes & FILE_ATTRIBUTE_DIRECTORY) - disk_device && attributes != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 - } - PY_IFDIR => (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0, - PY_IFLNK => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && reparse_tag == IO_REPARSE_TAG_SYMLINK - } - PY_IFMNT => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && reparse_tag == IO_REPARSE_TAG_MOUNT_POINT - } - PY_IFLRP => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && is_reparse_tag_name_surrogate(reparse_tag) - } - PY_IFRRP => { - (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 - && reparse_tag != 0 - && !is_reparse_tag_name_surrogate(reparse_tag) - } - _ => false, - } + host_nt::test_info(attributes, reparse_tag, disk_device, tested_type) } fn is_reparse_tag_name_surrogate(tag: u32) -> bool { - (tag & 0x20000000) != 0 + host_nt::is_reparse_tag_name_surrogate(tag) } 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 - ) + host_nt::file_info_error_is_trustworthy(error) } /// _testFileTypeByHandle - test file type using an open handle @@ -465,237 +279,35 @@ pub(crate) mod module { tested_type: u32, disk_only: bool, ) -> bool { - use windows_sys::Win32::Storage::FileSystem::{ - FILE_ATTRIBUTE_TAG_INFO, FILE_BASIC_INFO, FILE_TYPE_DISK, - FileAttributeTagInfo as FileAttributeTagInfoClass, FileBasicInfo, - GetFileInformationByHandleEx, GetFileType, + let tested_type = match tested_type { + PY_IFREG => host_nt::TestType::RegularFile, + PY_IFDIR => host_nt::TestType::Directory, + PY_IFLNK => host_nt::TestType::Symlink, + PY_IFMNT => host_nt::TestType::Junction, + PY_IFLRP => host_nt::TestType::LinkReparsePoint, + PY_IFRRP => host_nt::TestType::RegularReparsePoint, + _ => return false, }; - - let disk_device = unsafe { GetFileType(handle) } == FILE_TYPE_DISK; - if disk_only && !disk_device { - return false; - } - - if tested_type != PY_IFREG && tested_type != PY_IFDIR { - // For symlinks/junctions, need FileAttributeTagInfo to get reparse tag - let mut info: FILE_ATTRIBUTE_TAG_INFO = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - handle, - FileAttributeTagInfoClass, - &mut info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return false; - } - _test_info( - info.FileAttributes, - info.ReparseTag, - disk_device, - tested_type, - ) - } else { - // For regular files/directories, FileBasicInfo is sufficient - let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - handle, - FileBasicInfo, - &mut info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - return false; - } - _test_info(info.FileAttributes, 0, disk_device, tested_type) - } + host_nt::test_file_type_by_handle(handle, tested_type, disk_only) } /// _testFileTypeByName - test file type by path name fn _test_file_type_by_name(path: &std::path::Path, tested_type: u32) -> bool { - use crate::host_env::fileutils::windows::{ - FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, - }; - use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_READ_ATTRIBUTES, OPEN_EXISTING, - }; - use windows_sys::Win32::Storage::FileSystem::{FILE_DEVICE_CD_ROM, FILE_DEVICE_DISK}; - use windows_sys::Win32::System::Ioctl::FILE_DEVICE_VIRTUAL_DISK; - - match get_file_information_by_name( - path.as_os_str(), - FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo, - ) { - Ok(info) => { - let disk_device = matches!( - info.DeviceType, - FILE_DEVICE_DISK | FILE_DEVICE_VIRTUAL_DISK | FILE_DEVICE_CD_ROM - ); - let result = _test_info( - info.FileAttributes, - info.ReparseTag, - disk_device, - tested_type, - ); - if !result - || (tested_type != PY_IFREG && tested_type != PY_IFDIR) - || (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 - { - return result; - } - } - Err(err) => { - if let Some(code) = err.raw_os_error() - && file_info_error_is_trustworthy(code as u32) - { - return false; - } - } - } - - let wide_path = path.to_wide_with_nul(); - - let mut flags = FILE_FLAG_BACKUP_SEMANTICS; - if tested_type != PY_IFREG && tested_type != PY_IFDIR { - 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(), - ) + let tested_type = match tested_type { + PY_IFREG => host_nt::TestType::RegularFile, + PY_IFDIR => host_nt::TestType::Directory, + PY_IFLNK => host_nt::TestType::Symlink, + PY_IFMNT => host_nt::TestType::Junction, + PY_IFLRP => host_nt::TestType::LinkReparsePoint, + PY_IFRRP => host_nt::TestType::RegularReparsePoint, + _ => return false, }; - - if handle != INVALID_HANDLE_VALUE { - let result = _test_file_type_by_handle(handle, tested_type, false); - unsafe { CloseHandle(handle) }; - return result; - } - - match unsafe { windows_sys::Win32::Foundation::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 = if tested_type == PY_IFREG || tested_type == PY_IFDIR { - crate::windows::win32_xstat(path.as_os_str(), true) - } else { - crate::windows::win32_xstat(path.as_os_str(), false) - }; - 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 + host_nt::test_file_type_by_name(path, tested_type) } /// _testFileExistsByName - test if path exists fn _test_file_exists_by_name(path: &std::path::Path, follow_links: bool) -> bool { - use crate::host_env::fileutils::windows::{ - FILE_INFO_BY_NAME_CLASS, get_file_information_by_name, - }; - use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, FILE_READ_ATTRIBUTES, OPEN_EXISTING, - }; - - match get_file_information_by_name( - path.as_os_str(), - FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo, - ) { - Ok(info) => { - if (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 - || (!follow_links && is_reparse_tag_name_surrogate(info.ReparseTag)) - { - return true; - } - } - Err(err) => { - if let Some(code) = err.raw_os_error() - && file_info_error_is_trustworthy(code as u32) - { - return false; - } - } - } - - let wide_path = path.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, PY_IFRRP, 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 { windows_sys::Win32::Foundation::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 = crate::windows::win32_xstat(path.as_os_str(), follow_links); - return stat.is_ok(); - } - _ => {} - } - - false + host_nt::test_file_exists_by_name(path, follow_links) } /// _testFileType wrapper - handles both fd and path @@ -718,20 +330,7 @@ pub(crate) mod module { use windows_sys::Win32::Storage::FileSystem::{FILE_TYPE_UNKNOWN, GetFileType}; match path_or_fd { - OsPathOrFd::Fd(fd) => { - if let Ok(handle) = crate::host_env::crt_fd::as_handle(*fd) { - use std::os::windows::io::AsRawHandle; - let file_type = unsafe { GetFileType(handle.as_raw_handle() as _) }; - // GetFileType(hfile) != FILE_TYPE_UNKNOWN || !GetLastError() - if file_type != FILE_TYPE_UNKNOWN { - return true; - } - // Check if GetLastError is 0 (no error means valid handle) - unsafe { windows_sys::Win32::Foundation::GetLastError() == 0 } - } else { - false - } - } + OsPathOrFd::Fd(fd) => host_nt::fd_exists(*fd), OsPathOrFd::Path(path) => _test_file_exists_by_name(path.as_ref(), follow_links), } } @@ -787,113 +386,18 @@ pub(crate) mod module { /// Check if a path is on a Windows Dev Drive. #[pyfunction] fn _path_isdevdrive(path: OsPath, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::CloseHandle; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, - FILE_SHARE_WRITE, GetDriveTypeW, GetVolumePathNameW, OPEN_EXISTING, - }; - 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; - - // PERSISTENT_VOLUME_STATE_DEV_VOLUME flag - not yet in windows-sys - const PERSISTENT_VOLUME_STATE_DEV_VOLUME: u32 = 0x00002000; - - // FILE_FS_PERSISTENT_VOLUME_INFORMATION structure - #[repr(C)] - struct FileFsPersistentVolumeInformation { - volume_flags: u32, - flag_mask: u32, - version: u32, - reserved: u32, - } - - let wide_path = path.to_wide_cstring(vm)?; - let mut volume = [0u16; Foundation::MAX_PATH as usize]; - - // Get volume path - let ret = unsafe { - GetVolumePathNameW(wide_path.as_ptr(), volume.as_mut_ptr(), volume.len() as _) - }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - - // Check if it's a fixed drive - if unsafe { GetDriveTypeW(volume.as_ptr()) } != DRIVE_FIXED { - return Ok(false); - } - - // Open the volume - 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(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - // Query persistent volume state - let mut volume_state = FileFsPersistentVolumeInformation { - volume_flags: 0, - flag_mask: PERSISTENT_VOLUME_STATE_DEV_VOLUME, - version: 1, - reserved: 0, - }; - - let ret = unsafe { - DeviceIoControl( - handle, - FSCTL_QUERY_PERSISTENT_VOLUME_STATE, - &volume_state as *const _ as *const core::ffi::c_void, - core::mem::size_of::() as u32, - &mut volume_state as *mut _ as *mut core::ffi::c_void, - core::mem::size_of::() as u32, - core::ptr::null_mut(), - core::ptr::null_mut(), - ) - }; - - unsafe { CloseHandle(handle) }; - - if ret == 0 { - let err = io::Error::last_os_error(); - // ERROR_INVALID_PARAMETER means not supported on this platform - if err.raw_os_error() == Some(Foundation::ERROR_INVALID_PARAMETER as i32) { - return Ok(false); - } - return Err(err.to_pyexception(vm)); - } - - Ok((volume_state.volume_flags & PERSISTENT_VOLUME_STATE_DEV_VOLUME) != 0) - } - - // cwait is available on MSVC only - #[cfg(target_env = "msvc")] - unsafe extern "C" { - fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t; + let _ = path.to_wide_cstring(vm)?; + host_nt::path_isdevdrive(path.as_ref()).map_err(|err| err.to_pyexception(vm)) } #[cfg(target_env = "msvc")] #[pyfunction] fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, u64)> { - let mut status: i32 = 0; - let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) }; - if pid == -1 { - Err(vm.new_last_errno_error()) - } else { - // Cast to unsigned to handle large exit codes (like 0xC000013A) - // then shift left by 8 to match POSIX waitpid format - let ustatus = (status as u32) as u64; - Ok((pid, ustatus << 8)) - } + let (pid, status) = host_nt::cwait(pid, opt).map_err(|_| vm.new_last_errno_error())?; + // Cast to unsigned to handle large exit codes (like 0xC000013A) + // then shift left by 8 to match POSIX waitpid format + let ustatus = (status as u32) as u64; + Ok((pid, ustatus << 8)) } #[cfg(target_env = "msvc")] @@ -904,31 +408,7 @@ pub(crate) mod module { #[pyfunction] fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { - let sig = sig as u32; - let pid = pid as u32; - - if sig == Console::CTRL_C_EVENT || sig == Console::CTRL_BREAK_EVENT { - let ret = unsafe { Console::GenerateConsoleCtrlEvent(sig, pid) }; - let res = if ret == 0 { - Err(vm.new_last_os_error()) - } else { - Ok(()) - }; - return res; - } - - let h = unsafe { Threading::OpenProcess(Threading::PROCESS_ALL_ACCESS, 0, pid) }; - if h.is_null() { - return Err(vm.new_last_os_error()); - } - let ret = unsafe { Threading::TerminateProcess(h, sig) }; - let res = if ret == 0 { - Err(vm.new_last_os_error()) - } else { - Ok(()) - }; - unsafe { Foundation::CloseHandle(h) }; - res + host_nt::kill(pid as u32, sig as u32).map_err(|err| err.to_pyexception(vm)) } #[pyfunction] @@ -937,68 +417,13 @@ pub(crate) mod module { vm: &VirtualMachine, ) -> PyResult<_os::TerminalSizeData> { let fd = fd.unwrap_or(1); // default to stdout - - // Use _get_osfhandle for all fds let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) }; let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?; - let h = handle.as_raw_handle() as Foundation::HANDLE; - - let mut csbi = MaybeUninit::uninit(); - let ret = unsafe { Console::GetConsoleScreenBufferInfo(h, csbi.as_mut_ptr()) }; - if ret == 0 { - // Check if error is due to lack of read access on a console handle - // ERROR_ACCESS_DENIED (5) means it's a console but without read permission - // In that case, try opening CONOUT$ directly with read access - let err = unsafe { Foundation::GetLastError() }; - if err != Foundation::ERROR_ACCESS_DENIED { - return Err(vm.new_last_os_error()); - } - let conout: Vec = "CONOUT$\0".encode_utf16().collect(); - let console_handle = unsafe { - FileSystem::CreateFileW( - conout.as_ptr(), - Foundation::GENERIC_READ | Foundation::GENERIC_WRITE, - FileSystem::FILE_SHARE_READ | FileSystem::FILE_SHARE_WRITE, - core::ptr::null(), - FileSystem::OPEN_EXISTING, - 0, - core::ptr::null_mut(), - ) - }; - if console_handle == INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - let ret = - unsafe { Console::GetConsoleScreenBufferInfo(console_handle, csbi.as_mut_ptr()) }; - unsafe { Foundation::CloseHandle(console_handle) }; - if ret == 0 { - return Err(vm.new_last_os_error()); - } - } - let csbi = unsafe { csbi.assume_init() }; - let w = csbi.srWindow; - let columns = (w.Right - w.Left + 1) as usize; - let lines = (w.Bottom - w.Top + 1) as usize; + let (columns, lines) = host_nt::get_terminal_size_handle(handle.as_raw_handle() as _) + .map_err(|_| vm.new_last_os_error())?; Ok(_os::TerminalSizeData { columns, lines }) } - #[cfg(target_env = "msvc")] - unsafe extern "C" { - 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; - } - #[cfg(target_env = "msvc")] #[pyfunction] fn spawnv( @@ -1031,12 +456,8 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - let result = unsafe { suppress_iph!(_wspawnv(mode, path.as_ptr(), argv_spawn.as_ptr())) }; - if result == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(result) - } + host_nt::spawnv(mode, path.as_ptr(), argv_spawn.as_ptr()) + .map_err(|_| vm.new_last_errno_error()) } #[cfg(target_env = "msvc")] @@ -1100,19 +521,8 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - let result = unsafe { - suppress_iph!(_wspawnve( - mode, - path.as_ptr(), - argv_spawn.as_ptr(), - envp.as_ptr() - )) - }; - if result == -1 { - Err(vm.new_last_errno_error()) - } else { - Ok(result) - } + host_nt::spawnve(mode, path.as_ptr(), argv_spawn.as_ptr(), envp.as_ptr()) + .map_err(|_| vm.new_last_errno_error()) } #[cfg(target_env = "msvc")] @@ -1148,11 +558,7 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_nt::execv(path.as_ptr(), argv_execv.as_ptr()).map_err(|_| vm.new_last_errno_error()) } #[cfg(target_env = "msvc")] @@ -1219,98 +625,24 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - if (unsafe { suppress_iph!(_wexecve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr())) } - == -1) - { - Err(vm.new_last_errno_error()) - } else { - Ok(()) - } + host_nt::execve(path.as_ptr(), argv_execve.as_ptr(), envp.as_ptr()) + .map_err(|_| vm.new_last_errno_error()) } #[pyfunction] fn _getfinalpathname(path: OsPath, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, GetFinalPathNameByHandleW, OPEN_EXISTING, - VOLUME_NAME_DOS, - }; - - let wide = path.to_wide_cstring(vm)?; - let handle = unsafe { - CreateFileW( - wide.as_ptr(), - 0, - 0, - core::ptr::null(), - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - core::ptr::null_mut(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - - let mut buffer: Vec = vec![0; Foundation::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 { - let err = io::Error::last_os_error(); - let _ = unsafe { Foundation::CloseHandle(handle) }; - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - if (ret as usize) < buffer.len() { - let final_path = std::ffi::OsString::from_wide(&buffer[..ret as usize]); - break Ok(path.mode().process_path(final_path, vm)); - } - buffer.resize(ret as usize, 0); - }; - - unsafe { Foundation::CloseHandle(handle) }; - result + let _ = path.to_wide_cstring(vm)?; + let final_path = host_nt::getfinalpathname(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + Ok(path.mode().process_path(final_path, vm)) } #[pyfunction] fn _getfullpathname(path: OsPath, vm: &VirtualMachine) -> PyResult { - let wpath = path.to_wide_cstring(vm)?; - let mut buffer = vec![0u16; Foundation::MAX_PATH as usize]; - let ret = unsafe { - FileSystem::GetFullPathNameW( - wpath.as_ptr(), - buffer.len() as _, - buffer.as_mut_ptr(), - core::ptr::null_mut(), - ) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)); - } - if ret as usize > buffer.len() { - buffer.resize(ret as usize, 0); - let ret = unsafe { - FileSystem::GetFullPathNameW( - wpath.as_ptr(), - buffer.len() as _, - buffer.as_mut_ptr(), - core::ptr::null_mut(), - ) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)); - } - } - let buffer = widestring::WideCString::from_vec_truncate(buffer); - Ok(path.mode().process_path(buffer.to_os_string(), vm)) + let _ = path.to_wide_cstring(vm)?; + let buffer = host_nt::getfullpathname(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + Ok(path.mode().process_path(buffer, vm)) } #[pyfunction] @@ -1320,16 +652,9 @@ pub(crate) mod module { if buflen > u32::MAX as usize { return Err(vm.new_overflow_error("path too long")); } - let mut buffer = vec![0u16; buflen]; - let ret = unsafe { - FileSystem::GetVolumePathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buflen as _) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - let buffer = widestring::WideCString::from_vec_truncate(buffer); - Ok(path.mode().process_path(buffer.to_os_string(), vm)) + let buffer = host_nt::getvolumepathname(path.as_ref()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path.clone(), vm))?; + Ok(path.mode().process_path(buffer, vm)) } /// Implements _Py_skiproot logic for Windows paths @@ -1488,15 +813,7 @@ pub(crate) mod module { .chain(core::iter::once(0)) // null-terminated .collect(); - let mut end: *const u16 = core::ptr::null(); - let hr = unsafe { - windows_sys::Win32::UI::Shell::PathCchSkipRoot(backslashed.as_ptr(), &mut end) - }; - if hr >= 0 { - assert!(!end.is_null()); - let len: usize = unsafe { end.offset_from(backslashed.as_ptr()) } - .try_into() - .expect("len must be non-negative"); + if let Some(len) = host_nt::path_skip_root(backslashed.as_ptr()) { assert!( len < backslashed.len(), // backslashed is null-terminated "path: {:?} {} < {}", @@ -1684,43 +1001,13 @@ pub(crate) mod module { #[pyfunction] fn _getdiskusage(path: OsPath, vm: &VirtualMachine) -> PyResult<(u64, u64)> { - use FileSystem::GetDiskFreeSpaceExW; - - let wpath = path.to_wide_cstring(vm)?; - let mut _free_to_me: u64 = 0; - let mut total: u64 = 0; - let mut free: u64 = 0; - let ret = - unsafe { GetDiskFreeSpaceExW(wpath.as_ptr(), &mut _free_to_me, &mut total, &mut free) }; - if ret != 0 { - return Ok((total, free)); - } - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(Foundation::ERROR_DIRECTORY as i32) - && let Some(parent) = path.as_ref().parent() - { - let parent = widestring::WideCString::from_os_str(parent).unwrap(); - - let ret = unsafe { - GetDiskFreeSpaceExW(parent.as_ptr(), &mut _free_to_me, &mut total, &mut free) - }; - - return if ret == 0 { - Err(err.to_pyexception(vm)) - } else { - Ok((total, free)) - }; - } - Err(err.to_pyexception(vm)) + let _ = path.to_wide_cstring(vm)?; + host_nt::getdiskusage(path.as_ref()).map_err(|err| err.to_pyexception(vm)) } #[pyfunction] fn get_handle_inheritable(handle: intptr_t, vm: &VirtualMachine) -> PyResult { - let mut flags = 0; - if unsafe { Foundation::GetHandleInformation(handle as _, &mut flags) } == 0 { - return Err(vm.new_last_os_error()); - } - Ok(flags & Foundation::HANDLE_FLAG_INHERIT != 0) + host_nt::get_handle_inheritable(handle).map_err(|err| err.to_pyexception(vm)) } #[pyfunction] @@ -1732,139 +1019,41 @@ pub(crate) mod module { #[pyfunction] fn getlogin(vm: &VirtualMachine) -> PyResult { - let mut buffer = [0u16; 257]; - let mut size = buffer.len() as u32; - - let success = unsafe { - windows_sys::Win32::System::WindowsProgramming::GetUserNameW( - buffer.as_mut_ptr(), - &mut size, - ) - }; - - if success != 0 { - // Convert the buffer (which is UTF-16) to a Rust String - let username = std::ffi::OsString::from_wide(&buffer[..(size - 1) as usize]); - Ok(username.to_str().unwrap().to_string()) - } else { - Err(vm.new_os_error(format!("Error code: {success}"))) - } + host_nt::getlogin().map_err(|_| vm.new_os_error("Error code: 0".to_owned())) } pub fn raw_set_handle_inheritable(handle: intptr_t, inheritable: bool) -> std::io::Result<()> { - let flags = if inheritable { - Foundation::HANDLE_FLAG_INHERIT - } else { - 0 - }; - let res = unsafe { - Foundation::SetHandleInformation(handle as _, Foundation::HANDLE_FLAG_INHERIT, flags) - }; - if res == 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(()) - } + host_nt::set_handle_inheritable(handle, inheritable) } #[pyfunction] fn listdrives(vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::ERROR_MORE_DATA; - - let mut buffer = [0u16; 256]; - let len = - unsafe { FileSystem::GetLogicalDriveStringsW(buffer.len() as _, buffer.as_mut_ptr()) }; - if len == 0 { - return Err(vm.new_last_os_error()); - } - if len as usize >= buffer.len() { - return Err(std::io::Error::from_raw_os_error(ERROR_MORE_DATA as _).to_pyexception(vm)); - } - let drives: Vec<_> = buffer[..(len - 1) as usize] - .split(|&c| c == 0) - .map(|drive| vm.new_pyobj(String::from_utf16_lossy(drive))) + let drives: Vec<_> = host_nt::listdrives() + .map_err(|err| err.to_pyexception(vm))? + .into_iter() + .map(|drive| vm.new_pyobj(drive.to_string_lossy().into_owned())) .collect(); Ok(vm.ctx.new_list(drives)) } #[pyfunction] fn listvolumes(vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::ERROR_NO_MORE_FILES; - - let mut result = Vec::new(); - let mut buffer = [0u16; Foundation::MAX_PATH as usize + 1]; - - let find = unsafe { FileSystem::FindFirstVolumeW(buffer.as_mut_ptr(), buffer.len() as _) }; - if find == INVALID_HANDLE_VALUE { - return Err(vm.new_last_os_error()); - } - - loop { - // Find the null terminator - let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len()); - let volume = String::from_utf16_lossy(&buffer[..len]); - result.push(vm.new_pyobj(volume)); - - let ret = unsafe { - FileSystem::FindNextVolumeW(find, buffer.as_mut_ptr(), buffer.len() as _) - }; - if ret == 0 { - let err = io::Error::last_os_error(); - unsafe { FileSystem::FindVolumeClose(find) }; - if err.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32) { - break; - } - return Err(err.to_pyexception(vm)); - } - } - + let result = host_nt::listvolumes() + .map_err(|err| err.to_pyexception(vm))? + .into_iter() + .map(|volume| vm.new_pyobj(volume.to_string_lossy().into_owned())) + .collect(); Ok(vm.ctx.new_list(result)) } #[pyfunction] fn listmounts(volume: OsPath, vm: &VirtualMachine) -> PyResult { - use windows_sys::Win32::Foundation::ERROR_MORE_DATA; - - let wide = volume.to_wide_cstring(vm)?; - let mut buflen: u32 = Foundation::MAX_PATH + 1; - let mut buffer: Vec = vec![0; buflen as usize]; - - loop { - let success = unsafe { - FileSystem::GetVolumePathNamesForVolumeNameW( - wide.as_ptr(), - buffer.as_mut_ptr(), - buflen, - &mut buflen, - ) - }; - if success != 0 { - break; - } - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(ERROR_MORE_DATA as i32) { - buffer.resize(buflen as usize, 0); - continue; - } - return Err(err.to_pyexception(vm)); - } - - // Parse null-separated strings - let mut result = Vec::new(); - let mut start = 0; - for (i, &c) in buffer.iter().enumerate() { - if c == 0 { - if i > start { - let mount = String::from_utf16_lossy(&buffer[start..i]); - result.push(vm.new_pyobj(mount)); - } - start = i + 1; - if start < buffer.len() && buffer[start] == 0 { - break; // Double null = end - } - } - } - + let _ = volume.to_wide_cstring(vm)?; + let result = host_nt::listmounts(volume.as_ref()) + .map_err(|err| err.to_pyexception(vm))? + .into_iter() + .map(|mount| vm.new_pyobj(mount.to_string_lossy().into_owned())) + .collect(); Ok(vm.ctx.new_list(result)) } @@ -1889,182 +1078,29 @@ pub(crate) mod module { #[pyfunction] fn mkdir(args: MkdirArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { - use windows_sys::Win32::Foundation::LocalFree; - use windows_sys::Win32::Security::Authorization::{ - ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, - }; - use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; - let [] = args.dir_fd.0; let wide = args.path.to_wide_cstring(vm)?; - - // special case: mode 0o700 sets a protected ACL - let res = if args.mode == 0o700 { - let mut sec_attr = SECURITY_ATTRIBUTES { - nLength: core::mem::size_of::() as u32, - lpSecurityDescriptor: core::ptr::null_mut(), - bInheritHandle: 0, - }; - // Set a discretionary ACL (D) that is protected (P) and includes - // inheritable (OICI) entries that allow (A) full control (FA) to - // SYSTEM (SY), Administrators (BA), and the owner (OW). - let sddl: Vec = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0" - .encode_utf16() - .collect(); - let convert_result = unsafe { - ConvertStringSecurityDescriptorToSecurityDescriptorW( - sddl.as_ptr(), - SDDL_REVISION_1, - &mut sec_attr.lpSecurityDescriptor, - core::ptr::null_mut(), - ) - }; - if convert_result == 0 { - return Err(vm.new_last_os_error()); - } - let res = - unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), &sec_attr as *const _ as _) }; - unsafe { LocalFree(sec_attr.lpSecurityDescriptor) }; - res - } else { - unsafe { FileSystem::CreateDirectoryW(wide.as_ptr(), core::ptr::null_mut()) } - }; - - if res == 0 { - return Err(vm.new_last_os_error()); - } - Ok(()) - } - - unsafe extern "C" { - fn _umask(mask: i32) -> i32; - } - - /// Close fd and convert error to PyException (PEP 446 cleanup) - #[cold] - fn close_fd_and_raise(fd: i32, err: std::io::Error, vm: &VirtualMachine) -> PyBaseExceptionRef { - let _ = unsafe { crt_fd::Owned::from_raw(fd) }; - err.to_pyexception(vm) + host_nt::mkdir(&wide, args.mode).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn umask(mask: i32, vm: &VirtualMachine) -> PyResult { - let result = unsafe { _umask(mask) }; - if result < 0 { - Err(vm.new_last_errno_error()) - } else { - Ok(result) - } + host_nt::umask(mask).map_err(|e| e.to_pyexception(vm)) } #[pyfunction] fn pipe(vm: &VirtualMachine) -> PyResult<(i32, i32)> { - use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; - use windows_sys::Win32::System::Pipes::CreatePipe; - - let mut attr = SECURITY_ATTRIBUTES { - nLength: core::mem::size_of::() as u32, - lpSecurityDescriptor: core::ptr::null_mut(), - bInheritHandle: 0, - }; - - let (read_handle, write_handle) = unsafe { - let mut read = MaybeUninit::::uninit(); - let mut write = MaybeUninit::::uninit(); - let res = CreatePipe( - read.as_mut_ptr() as *mut _, - write.as_mut_ptr() as *mut _, - &mut attr as *mut _, - 0, - ); - if res == 0 { - return Err(vm.new_last_os_error()); - } - (read.assume_init(), write.assume_init()) - }; - - // Convert handles to file descriptors - // O_NOINHERIT = 0x80 (MSVC CRT) - const O_NOINHERIT: i32 = 0x80; - let read_fd = unsafe { libc::open_osfhandle(read_handle, O_NOINHERIT) }; - let write_fd = unsafe { libc::open_osfhandle(write_handle, libc::O_WRONLY | O_NOINHERIT) }; - - if read_fd == -1 || write_fd == -1 { - unsafe { - Foundation::CloseHandle(read_handle as _); - Foundation::CloseHandle(write_handle as _); - } - return Err(vm.new_last_os_error()); - } - - Ok((read_fd, write_fd)) + host_nt::pipe().map_err(|e| e.to_pyexception(vm)) } #[pyfunction] 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, // ProcessBasicInformation - &mut info as *mut _ as *mut core::ffi::c_void, - core::mem::size_of::() as u32, - core::ptr::null_mut(), - ) - }; - - if status >= 0 - && info.InheritedFromUniqueProcessId != 0 - && info.InheritedFromUniqueProcessId < u32::MAX as usize - { - info.InheritedFromUniqueProcessId as u32 - } else { - 0 - } + host_nt::getppid() } #[pyfunction] fn dup(fd: i32, vm: &VirtualMachine) -> PyResult { - let fd2 = unsafe { suppress_iph!(libc::dup(fd)) }; - if fd2 < 0 { - return Err(vm.new_last_errno_error()); - } - let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd2) }; - let handle = crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(fd2, e, vm))?; - raw_set_handle_inheritable(handle.as_raw_handle() as _, false) - .map_err(|e| close_fd_and_raise(fd2, e, vm))?; - Ok(fd2) + host_nt::dup(fd).map_err(|e| e.to_pyexception(vm)) } #[derive(FromArgs)] @@ -2079,152 +1115,26 @@ pub(crate) mod module { #[pyfunction] fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult { - let result = unsafe { suppress_iph!(libc::dup2(args.fd, args.fd2)) }; - if result < 0 { - return Err(vm.new_last_errno_error()); - } - if !args.inheritable { - let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(args.fd2) }; - let handle = - crt_fd::as_handle(borrowed).map_err(|e| close_fd_and_raise(args.fd2, e, vm))?; - raw_set_handle_inheritable(handle.as_raw_handle() as _, false) - .map_err(|e| close_fd_and_raise(args.fd2, e, vm))?; - } - Ok(args.fd2) + host_nt::dup2(args.fd, args.fd2, args.inheritable).map_err(|e| e.to_pyexception(vm)) } /// Windows-specific readlink that preserves \\?\ prefix for junctions /// returns the substitute name from reparse data which includes the prefix #[pyfunction] fn readlink(path: OsPath, vm: &VirtualMachine) -> PyResult { - use crate::host_env::windows::ToWideString; - use windows_sys::Win32::Foundation::CloseHandle; - use windows_sys::Win32::Storage::FileSystem::{ - CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, - FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, - }; - use windows_sys::Win32::System::IO::DeviceIoControl; - use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT; - let mode = path.mode(); - let wide_path = path.as_ref().to_wide_with_nul(); - - // Open the file/directory with reparse point flag - let handle = unsafe { - CreateFileW( - wide_path.as_ptr(), - 0, // No access needed, just reading reparse data - 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(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path.clone(), - vm, - )); - } - - // Buffer for reparse data - MAXIMUM_REPARSE_DATA_BUFFER_SIZE is 16384 - const BUFFER_SIZE: usize = 16384; - let mut buffer = vec![0u8; BUFFER_SIZE]; - let mut bytes_returned: u32 = 0; - - let result = 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 result == 0 { - return Err(OSErrorBuilder::with_filename( - &io::Error::last_os_error(), - path.clone(), - vm, - )); - } - - // Parse the reparse data buffer - // REPARSE_DATA_BUFFER structure: - // DWORD ReparseTag - // WORD ReparseDataLength - // WORD Reserved - // For symlinks/junctions (IO_REPARSE_TAG_SYMLINK/MOUNT_POINT): - // WORD SubstituteNameOffset - // WORD SubstituteNameLength - // WORD PrintNameOffset - // WORD PrintNameLength - // (For symlinks only: DWORD Flags) - // PathBuffer... - - let reparse_tag = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); - - // Check if it's a symlink or mount point (junction) - use windows_sys::Win32::System::SystemServices::{ - IO_REPARSE_TAG_MOUNT_POINT, IO_REPARSE_TAG_SYMLINK, - }; - - let (substitute_offset, substitute_length, path_buffer_start) = - if reparse_tag == IO_REPARSE_TAG_SYMLINK { - // Symlink has Flags field (4 bytes) before PathBuffer - let sub_offset = u16::from_le_bytes([buffer[8], buffer[9]]) as usize; - let sub_length = u16::from_le_bytes([buffer[10], buffer[11]]) as usize; - // PathBuffer starts at offset 20 (after Flags at offset 16) - (sub_offset, sub_length, 20usize) - } else if reparse_tag == IO_REPARSE_TAG_MOUNT_POINT { - // Mount point (junction) has no Flags field - let sub_offset = u16::from_le_bytes([buffer[8], buffer[9]]) as usize; - let sub_length = u16::from_le_bytes([buffer[10], buffer[11]]) as usize; - // PathBuffer starts at offset 16 - (sub_offset, sub_length, 16usize) - } else { - return Err(vm.new_value_error("not a symbolic link")); - }; - - // Extract the substitute name - let path_start = path_buffer_start + substitute_offset; - let path_end = path_start + substitute_length; - - if path_end > buffer.len() { - return Err(vm.new_os_error("Invalid reparse data".to_owned())); - } - - // Convert from UTF-16LE - let path_slice = &buffer[path_start..path_end]; - let wide_chars: Vec = path_slice - .chunks_exact(2) - .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) - .collect(); - - let mut wide_chars = wide_chars; - // For mount points (junctions), the substitute name typically starts with \??\ - // Convert this to \\?\ - 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; + match host_nt::readlink(path.as_ref()) { + Ok(result_path) => Ok(mode.process_path(std::path::PathBuf::from(result_path), vm)), + Err(host_nt::ReadlinkError::Io(err)) => { + Err(OSErrorBuilder::with_filename(&err, path.clone(), vm)) + } + Err(host_nt::ReadlinkError::NotSymbolicLink) => { + Err(vm.new_value_error("not a symbolic link")) + } + Err(host_nt::ReadlinkError::InvalidReparseData) => { + Err(vm.new_os_error("Invalid reparse data".to_owned())) + } } - - let result_path = std::ffi::OsString::from_wide(&wide_chars); - - Ok(mode.process_path(std::path::PathBuf::from(result_path), vm)) } pub(crate) fn support_funcs() -> Vec { diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 429fef19eeb..933c4c213d6 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -7,6 +7,8 @@ use crate::{ function::{ArgumentError, FromArgs, FuncArgs}, host_env::crt_fd, }; +#[cfg(windows)] +use rustpython_host_env::nt as host_nt; use std::{io, path::Path}; pub(crate) fn fs_metadata>( @@ -176,6 +178,8 @@ pub(super) mod _os { use core::time::Duration; use crossbeam_utils::atomic::AtomicCell; use rustpython_common::wtf8::Wtf8Buf; + #[cfg(windows)] + use rustpython_host_env::nt as host_nt; use rustpython_host_env::suppress_iph; use std::{fs, io, path::PathBuf, time::SystemTime}; @@ -854,7 +858,10 @@ pub(super) mod _os { #[cfg(windows)] #[pymethod] fn is_junction(&self, _vm: &VirtualMachine) -> PyResult { - Ok(junction::exists(self.pathval.clone()).unwrap_or(false)) + Ok(host_nt::test_file_type_by_name( + &self.pathval, + host_nt::TestType::Junction, + )) } #[pymethod] @@ -986,7 +993,7 @@ pub(super) mod _os { let lstat = { let cell = OnceCell::new(); if let Ok(stat_struct) = - crate::windows::win32_xstat(pathval.as_os_str(), false) + host_nt::win32_xstat(pathval.as_os_str(), false) { let stat_obj = StatResultData::from_stat(&stat_struct, vm).to_pyobject(vm); @@ -1396,10 +1403,9 @@ pub(super) mod _os { dir_fd: DirFd<'_, { STAT_DIR_FD as usize }>, follow_symlinks: FollowSymlinks, ) -> io::Result> { - // TODO: replicate CPython's win32_xstat let [] = dir_fd.0; match file { - OsPathOrFd::Path(path) => crate::windows::win32_xstat(&path.path, follow_symlinks.0), + OsPathOrFd::Path(path) => host_nt::win32_xstat(&path.path, follow_symlinks.0), OsPathOrFd::Fd(fd) => crate::host_env::fileutils::fstat(fd), } .map(Some) @@ -1489,40 +1495,7 @@ pub(super) mod _os { #[pyfunction] fn chdir(path: OsPath, vm: &VirtualMachine) -> PyResult<()> { crate::host_env::os::set_current_dir(&path.path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))?; - - #[cfg(windows)] - { - // win32_wchdir() - - // On Windows, set the per-drive CWD environment variable (=X:) - // This is required for GetFullPathNameW to work correctly with drive-relative paths - - use std::os::windows::ffi::OsStrExt; - use windows_sys::Win32::System::Environment::SetEnvironmentVariableW; - - if let Ok(cwd) = crate::host_env::os::current_dir() { - let cwd_str = cwd.as_os_str(); - let mut cwd_wide: Vec = cwd_str.encode_wide().collect(); - - // Check for UNC-like paths (\\server\share or //server/share) - // wcsncmp(new_path, L"\\\\", 2) == 0 || wcsncmp(new_path, L"//", 2) == 0 - let is_unc_like_path = cwd_wide.len() >= 2 - && ((cwd_wide[0] == b'\\' as u16 && cwd_wide[1] == b'\\' as u16) - || (cwd_wide[0] == b'/' as u16 && cwd_wide[1] == b'/' as u16)); - - if !is_unc_like_path { - // Create env var name "=X:" where X is the drive letter - let env_name: [u16; 4] = [b'=' as u16, cwd_wide[0], b':' as u16, 0]; - cwd_wide.push(0); // null-terminate the path - unsafe { - SetEnvironmentVariableW(env_name.as_ptr(), cwd_wide.as_ptr()); - } - } - } - } - - Ok(()) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[pyfunction] @@ -1542,7 +1515,7 @@ pub(super) mod _os { .argument("dst") .try_path(dst, vm)?; - fs::rename(&src.path, &dst.path).map_err(|err| { + crate::host_env::os::rename(&src.path, &dst.path).map_err(|err| { let builder = err.to_os_error_builder(vm); let builder = builder.filename(src.filename(vm)); let builder = builder.filename2(dst.filename(vm)); @@ -2110,31 +2083,7 @@ pub(super) mod _os { return Ok(None); } - cfg_if::cfg_if! { - if #[cfg(any(target_os = "android", target_os = "redox"))] { - Ok(Some("UTF-8".to_owned())) - } else if #[cfg(windows)] { - use windows_sys::Win32::System::Console; - let cp = match fd { - 0 => unsafe { Console::GetConsoleCP() }, - 1 | 2 => unsafe { Console::GetConsoleOutputCP() }, - _ => 0, - }; - - Ok(Some(format!("cp{cp}"))) - } else { - let encoding = unsafe { - let encoding = libc::nl_langinfo(libc::CODESET); - if encoding.is_null() || encoding.read() == b'\0' as libc::c_char { - "UTF-8".to_owned() - } else { - core::ffi::CStr::from_ptr(encoding).to_string_lossy().into_owned() - } - }; - - Ok(Some(encoding)) - } - } + Ok(rustpython_host_env::os::device_encoding(fd)) } #[pystruct_sequence_data] diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index b97801d2362..f97ded88883 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -35,7 +35,6 @@ pub mod module { ))] use crate::{builtins::PyUtf8StrRef, utils::ToCString}; use alloc::ffi::CString; - use bitflags::bitflags; use core::ffi::CStr; use nix::{ errno::Errno, @@ -374,88 +373,10 @@ pub mod module { } } - // Flags for os_access - bitflags! { - #[derive(Copy, Clone, Debug, PartialEq)] - pub struct AccessFlags: u8 { - const F_OK = _os::F_OK; - const R_OK = _os::R_OK; - const W_OK = _os::W_OK; - const X_OK = _os::X_OK; - } - } - - struct Permissions { - is_readable: bool, - is_writable: bool, - is_executable: bool, - } - - const fn get_permissions(mode: u32) -> Permissions { - Permissions { - is_readable: mode & 4 != 0, - is_writable: mode & 2 != 0, - is_executable: mode & 1 != 0, - } - } - - fn get_right_permission( - mode: u32, - file_owner: Uid, - file_group: Gid, - ) -> nix::Result { - let owner_mode = (mode & 0o700) >> 6; - let owner_permissions = get_permissions(owner_mode); - - let group_mode = (mode & 0o070) >> 3; - let group_permissions = get_permissions(group_mode); - - let others_mode = mode & 0o007; - let others_permissions = get_permissions(others_mode); - - let user_id = nix::unistd::getuid(); - let groups_ids = getgroups_impl()?; - - if file_owner == user_id { - Ok(owner_permissions) - } else if groups_ids.contains(&file_group) { - Ok(group_permissions) - } else { - Ok(others_permissions) - } - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - fn getgroups_impl() -> nix::Result> { - use core::ptr; - use libc::{c_int, gid_t}; - - let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; - let mut groups = Vec::::with_capacity(Errno::result(ret)? as usize); - let ret = unsafe { - libc::getgroups( - groups.capacity() as c_int, - groups.as_mut_ptr() as *mut gid_t, - ) - }; - - Errno::result(ret).map(|s| { - unsafe { groups.set_len(s as usize) }; - groups - }) - } - - #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))] - use nix::unistd::getgroups as getgroups_impl; - - #[cfg(target_os = "redox")] - fn getgroups_impl() -> nix::Result> { - Err(nix::Error::EOPNOTSUPP) - } - #[pyfunction] fn getgroups(vm: &VirtualMachine) -> PyResult> { - let group_ids = getgroups_impl().map_err(|e| e.into_pyexception(vm))?; + let group_ids = + rustpython_host_env::posix::getgroups().map_err(|e| e.into_pyexception(vm))?; Ok(group_ids .into_iter() .map(|gid| vm.ctx.new_int(gid.as_raw()).into()) @@ -464,37 +385,13 @@ pub mod module { #[pyfunction] pub(super) fn access(path: OsPath, mode: u8, vm: &VirtualMachine) -> PyResult { - use std::os::unix::fs::MetadataExt; - - let flags = AccessFlags::from_bits(mode).ok_or_else(|| { - vm.new_value_error( - "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK", - ) - })?; - - let metadata = match crate::host_env::fs::metadata(&path.path) { - Ok(m) => m, - // If the file doesn't exist, return False for any access check - Err(_) => return Ok(false), - }; - - // if it's only checking for F_OK - if flags == AccessFlags::F_OK { - return Ok(true); // File exists + match rustpython_host_env::posix::check_access(path.as_ref(), mode) { + Ok(ok) => Ok(ok), + Err(rustpython_host_env::posix::AccessError::InvalidMode) => Err(vm.new_value_error( + "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK", + )), + Err(rustpython_host_env::posix::AccessError::Os(err)) => Err(err.into_pyexception(vm)), } - - let user_id = metadata.uid(); - let group_id = metadata.gid(); - let mode = metadata.mode(); - - let perm = get_right_permission(mode, Uid::from_raw(user_id), Gid::from_raw(group_id)) - .map_err(|err| err.into_pyexception(vm))?; - - let r_ok = !flags.contains(AccessFlags::R_OK) || perm.is_readable; - let w_ok = !flags.contains(AccessFlags::W_OK) || perm.is_writable; - let x_ok = !flags.contains(AccessFlags::X_OK) || perm.is_executable; - - Ok(r_ok && w_ok && x_ok) } #[pyattr] diff --git a/crates/vm/src/stdlib/pwd.rs b/crates/vm/src/stdlib/pwd.rs index e8aee608cc7..bbc36b3d7ac 100644 --- a/crates/vm/src/stdlib/pwd.rs +++ b/crates/vm/src/stdlib/pwd.rs @@ -11,7 +11,7 @@ mod pwd { exceptions, types::PyStructSequence, }; - use nix::unistd::{self, User}; + use rustpython_host_env::pwd as host_pwd; #[cfg(not(target_os = "android"))] use crate::{PyObjectRef, convert::ToPyObject}; @@ -34,26 +34,16 @@ mod pwd { #[pyclass(with(PyStructSequence))] impl PyPasswd {} - impl From for PasswdData { - fn from(user: User) -> Self { - // this is just a pain... - let cstr_lossy = |s: alloc::ffi::CString| { - s.into_string() - .unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned()) - }; - let pathbuf_lossy = |p: std::path::PathBuf| { - p.into_os_string() - .into_string() - .unwrap_or_else(|s| s.to_string_lossy().into_owned()) - }; + impl From for PasswdData { + fn from(user: host_pwd::Passwd) -> Self { PasswdData { pw_name: user.name, - pw_passwd: cstr_lossy(user.passwd), - pw_uid: user.uid.as_raw(), - pw_gid: user.gid.as_raw(), - pw_gecos: cstr_lossy(user.gecos), - pw_dir: pathbuf_lossy(user.dir), - pw_shell: pathbuf_lossy(user.shell), + pw_passwd: user.passwd, + pw_uid: user.uid, + pw_gid: user.gid, + pw_gecos: user.gecos, + pw_dir: user.dir, + pw_shell: user.shell, } } } @@ -64,7 +54,7 @@ mod pwd { if pw_name.contains('\0') { return Err(exceptions::cstring_error(vm)); } - let user = User::from_name(name.as_str()).ok().flatten(); + let user = host_pwd::getpwnam(name.as_str()); let user = user.ok_or_else(|| { vm.new_key_error( vm.ctx @@ -77,11 +67,9 @@ mod pwd { #[pyfunction] fn getpwuid(uid: PyIntRef, vm: &VirtualMachine) -> PyResult { - let uid_t = libc::uid_t::try_from(uid.as_bigint()) - .map(unistd::Uid::from_raw) - .ok(); + let uid_t = libc::uid_t::try_from(uid.as_bigint()).ok(); let user = uid_t - .map(User::from_uid) + .map(host_pwd::getpwuid) .transpose() .map_err(|err| err.into_pyexception(vm))? .flatten(); @@ -99,19 +87,10 @@ mod pwd { #[cfg(not(target_os = "android"))] #[pyfunction] fn getpwall(vm: &VirtualMachine) -> PyResult> { - // setpwent, getpwent, etc are not thread safe. Could use fgetpwent_r, but this is easier - static GETPWALL: parking_lot::Mutex<()> = parking_lot::Mutex::new(()); - let _guard = GETPWALL.lock(); - let mut list = Vec::new(); - - unsafe { libc::setpwent() }; - while let Some(ptr) = core::ptr::NonNull::new(unsafe { libc::getpwent() }) { - let user = User::from(unsafe { ptr.as_ref() }); - let passwd = PasswdData::from(user).to_pyobject(vm); - list.push(passwd); - } - unsafe { libc::endpwent() }; - - Ok(list) + Ok(host_pwd::getpwall() + .into_iter() + .map(PasswdData::from) + .map(|passwd| passwd.to_pyobject(vm)) + .collect()) } } diff --git a/crates/vm/src/stdlib/time.rs b/crates/vm/src/stdlib/time.rs index b017fea4e9e..5cdaf9e0ef1 100644 --- a/crates/vm/src/stdlib/time.rs +++ b/crates/vm/src/stdlib/time.rs @@ -1057,6 +1057,13 @@ mod platform { #[cfg_attr(target_env = "musl", allow(deprecated))] use libc::time_t; use nix::{sys::time::TimeSpec, time::ClockId}; + #[cfg(any( + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + ))] + use rustpython_host_env::resource as host_resource; #[cfg(target_os = "solaris")] #[pyattr] @@ -1315,7 +1322,6 @@ mod platform { target_os = "openbsd", ))] pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { - use nix::sys::resource::{UsageWho, getrusage}; fn from_timeval(tv: libc::timeval, vm: &VirtualMachine) -> PyResult { (|tv: libc::timeval| { let t = tv.tv_sec.checked_mul(SEC_TO_NS)?; @@ -1324,9 +1330,9 @@ mod platform { })(tv) .ok_or_else(|| vm.new_overflow_error("timestamp too large to convert to i64")) } - let ru = getrusage(UsageWho::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?; - let utime = from_timeval(ru.user_time().into(), vm)?; - let stime = from_timeval(ru.system_time().into(), vm)?; + let ru = host_resource::getrusage(libc::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?; + let utime = from_timeval(ru.ru_utime, vm)?; + let stime = from_timeval(ru.ru_stime, vm)?; Ok(Duration::from_nanos((utime + stime) as u64)) } @@ -1341,12 +1347,7 @@ mod platform { builtins::{PyNamespace, PyUtf8StrRef}, }; use core::time::Duration; - use windows_sys::Win32::{ - Foundation::FILETIME, - System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency}, - System::SystemInformation::{GetSystemTimeAdjustment, GetTickCount64}, - System::Threading::{GetCurrentProcess, GetCurrentThread, GetProcessTimes, GetThreadTimes}, - }; + use rustpython_host_env::time as host_time; unsafe extern "C" { fn _gmtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int; @@ -1437,19 +1438,9 @@ mod platform { Ok(timestamp as f64) } - fn u64_from_filetime(time: FILETIME) -> u64 { - let large: [u32; 2] = [time.dwLowDateTime, time.dwHighDateTime]; - unsafe { core::mem::transmute(large) } - } - fn win_perf_counter_frequency(vm: &VirtualMachine) -> PyResult { - let frequency = unsafe { - let mut freq = core::mem::MaybeUninit::uninit(); - if QueryPerformanceFrequency(freq.as_mut_ptr()) == 0 { - return Err(vm.new_last_os_error()); - } - freq.assume_init() - }; + let frequency = + host_time::query_performance_frequency().ok_or_else(|| vm.new_last_os_error())?; if frequency < 1 { Err(vm.new_runtime_error("invalid QueryPerformanceFrequency")) @@ -1470,11 +1461,7 @@ mod platform { } pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult { - let ticks = unsafe { - let mut performance_count = core::mem::MaybeUninit::uninit(); - QueryPerformanceCounter(performance_count.as_mut_ptr()); - performance_count.assume_init() - }; + let ticks = host_time::query_performance_counter(); Ok(Duration::from_nanos(time_muldiv( ticks, @@ -1484,25 +1471,11 @@ mod platform { } fn get_system_time_adjustment(vm: &VirtualMachine) -> PyResult { - let mut _time_adjustment = core::mem::MaybeUninit::uninit(); - let mut time_increment = core::mem::MaybeUninit::uninit(); - let mut _is_time_adjustment_disabled = core::mem::MaybeUninit::uninit(); - let time_increment = unsafe { - if GetSystemTimeAdjustment( - _time_adjustment.as_mut_ptr(), - time_increment.as_mut_ptr(), - _is_time_adjustment_disabled.as_mut_ptr(), - ) == 0 - { - return Err(vm.new_last_os_error()); - } - time_increment.assume_init() - }; - Ok(time_increment) + host_time::get_system_time_adjustment().ok_or_else(|| vm.new_last_os_error()) } pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult { - let ticks = unsafe { GetTickCount64() }; + let ticks = host_time::tick_count64(); Ok(Duration::from_nanos( (ticks as i64) @@ -1547,52 +1520,14 @@ mod platform { } pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult { - let (kernel_time, user_time) = unsafe { - let mut _creation_time = core::mem::MaybeUninit::uninit(); - let mut _exit_time = core::mem::MaybeUninit::uninit(); - let mut kernel_time = core::mem::MaybeUninit::uninit(); - let mut user_time = core::mem::MaybeUninit::uninit(); - - let thread = GetCurrentThread(); - if GetThreadTimes( - thread, - _creation_time.as_mut_ptr(), - _exit_time.as_mut_ptr(), - kernel_time.as_mut_ptr(), - user_time.as_mut_ptr(), - ) == 0 - { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - (kernel_time.assume_init(), user_time.assume_init()) - }; - let k_time = u64_from_filetime(kernel_time); - let u_time = u64_from_filetime(user_time); - Ok(Duration::from_nanos((k_time + u_time) * 100)) + let total = host_time::get_thread_time_100ns() + .ok_or_else(|| vm.new_os_error("Failed to get clock time".to_owned()))?; + Ok(Duration::from_nanos(total * 100)) } pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { - let (kernel_time, user_time) = unsafe { - let mut _creation_time = core::mem::MaybeUninit::uninit(); - let mut _exit_time = core::mem::MaybeUninit::uninit(); - let mut kernel_time = core::mem::MaybeUninit::uninit(); - let mut user_time = core::mem::MaybeUninit::uninit(); - - let process = GetCurrentProcess(); - if GetProcessTimes( - process, - _creation_time.as_mut_ptr(), - _exit_time.as_mut_ptr(), - kernel_time.as_mut_ptr(), - user_time.as_mut_ptr(), - ) == 0 - { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - (kernel_time.assume_init(), user_time.assume_init()) - }; - let k_time = u64_from_filetime(kernel_time); - let u_time = u64_from_filetime(user_time); - Ok(Duration::from_nanos((k_time + u_time) * 100)) + let total = host_time::get_process_time_100ns() + .ok_or_else(|| vm.new_os_error("Failed to get clock time".to_owned()))?; + Ok(Duration::from_nanos(total * 100)) } } diff --git a/crates/vm/src/stdlib/winreg.rs b/crates/vm/src/stdlib/winreg.rs index 89e4cb8e8aa..8fed76a3608 100644 --- a/crates/vm/src/stdlib/winreg.rs +++ b/crates/vm/src/stdlib/winreg.rs @@ -18,25 +18,13 @@ mod winreg { use crossbeam_utils::atomic::AtomicCell; use malachite_bigint::Sign; use num_traits::ToPrimitive; + use rustpython_host_env::winreg as host_winreg; use windows_sys::Win32::Foundation::{self, ERROR_MORE_DATA}; use windows_sys::Win32::System::Registry; /// Atomic HKEY handle type for lock-free thread-safe access type AtomicHKEY = AtomicCell; - /// Convert byte slice to UTF-16 slice (zero-copy when aligned) - fn bytes_as_wide_slice(bytes: &[u8]) -> &[u16] { - // SAFETY: Windows Registry API returns properly aligned UTF-16 data. - // align_to handles any edge cases safely by returning empty prefix/suffix - // if alignment doesn't match. - let (prefix, u16_slice, suffix) = unsafe { bytes.align_to::() }; - debug_assert!( - prefix.is_empty() && suffix.is_empty(), - "Registry data should be u16-aligned" - ); - u16_slice - } - fn os_error_from_windows_code( vm: &VirtualMachine, code: i32, @@ -185,7 +173,7 @@ mod winreg { if old_hkey.is_null() { return Ok(()); } - let res = unsafe { Registry::RegCloseKey(old_hkey) }; + let res = host_winreg::close_key(old_hkey); if res == 0 { Ok(()) } else { @@ -224,7 +212,7 @@ mod winreg { fn drop(&mut self) { let hkey = self.hkey.swap(core::ptr::null_mut()); if !hkey.is_null() { - unsafe { Registry::RegCloseKey(hkey) }; + host_winreg::close_key(hkey); } } } @@ -285,7 +273,7 @@ mod winreg { let mut ret_key = core::ptr::null_mut(); let wide_computer_name = computer_name.to_wide_with_nul(); let res = unsafe { - Registry::RegConnectRegistryW( + host_winreg::connect_registry( wide_computer_name.as_ptr(), key.hkey.load(), &mut ret_key, @@ -299,7 +287,7 @@ mod winreg { } else { let mut ret_key = core::ptr::null_mut(); let res = unsafe { - Registry::RegConnectRegistryW(core::ptr::null_mut(), key.hkey.load(), &mut ret_key) + host_winreg::connect_registry(core::ptr::null_mut(), key.hkey.load(), &mut ret_key) }; if res == 0 { Ok(PyHkey::new(ret_key)) @@ -314,7 +302,7 @@ mod winreg { let wide_sub_key = sub_key.to_wide_with_nul(); let mut out_key = core::ptr::null_mut(); let res = unsafe { - Registry::RegCreateKeyW(key.hkey.load(), wide_sub_key.as_ptr(), &mut out_key) + host_winreg::create_key(key.hkey.load(), wide_sub_key.as_ptr(), &mut out_key) }; if res == 0 { Ok(PyHkey::new(out_key)) @@ -341,11 +329,11 @@ mod winreg { let mut res: Registry::HKEY = core::ptr::null_mut(); let err = unsafe { let key = args.key.hkey.load(); - Registry::RegCreateKeyExW( + host_winreg::create_key_ex( key, wide_sub_key.as_ptr(), args.reserved, - core::ptr::null(), + core::ptr::null_mut(), Registry::REG_OPTION_NON_VOLATILE, args.access, core::ptr::null(), @@ -371,7 +359,7 @@ mod winreg { #[pyfunction] fn DeleteKey(key: PyRef, sub_key: String, vm: &VirtualMachine) -> PyResult<()> { let wide_sub_key = sub_key.to_wide_with_nul(); - let res = unsafe { Registry::RegDeleteKeyW(key.hkey.load(), wide_sub_key.as_ptr()) }; + let res = unsafe { host_winreg::delete_key(key.hkey.load(), wide_sub_key.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -385,7 +373,7 @@ mod winreg { let value_ptr = wide_value .as_ref() .map_or(core::ptr::null(), |v| v.as_ptr()); - let res = unsafe { Registry::RegDeleteValueW(key.hkey.load(), value_ptr) }; + let res = unsafe { host_winreg::delete_value(key.hkey.load(), value_ptr) }; if res == 0 { Ok(()) } else { @@ -409,7 +397,7 @@ mod winreg { fn DeleteKeyEx(args: DeleteKeyExArgs, vm: &VirtualMachine) -> PyResult<()> { let wide_sub_key = args.sub_key.to_wide_with_nul(); let res = unsafe { - Registry::RegDeleteKeyExW( + host_winreg::delete_key_ex( args.key.hkey.load(), wide_sub_key.as_ptr(), args.access, @@ -434,16 +422,7 @@ mod winreg { let mut tmpbuf = [0u16; 257]; let mut len = tmpbuf.len() as u32; let res = unsafe { - Registry::RegEnumKeyExW( - key.hkey.load(), - index as u32, - tmpbuf.as_mut_ptr(), - &mut len, - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - ) + host_winreg::enum_key_ex(key.hkey.load(), index as u32, tmpbuf.as_mut_ptr(), &mut len) }; if res != 0 { return Err(os_error_from_windows_code(vm, res as i32)); @@ -459,19 +438,12 @@ mod winreg { let mut ret_data_size: u32 = 0; let hkey: Registry::HKEY = hkey.hkey.load(); let rc = unsafe { - Registry::RegQueryInfoKeyW( + host_winreg::query_info_key( hkey, ptr::null_mut(), ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), &mut ret_value_size as *mut u32, &mut ret_data_size as *mut u32, - ptr::null_mut(), - ptr::null_mut(), ) }; if rc != 0 { @@ -494,12 +466,11 @@ mod winreg { let mut current_data_size = ret_data_size; let mut reg_type: u32 = 0; let rc = unsafe { - Registry::RegEnumValueW( + host_winreg::enum_value( hkey, index, ret_value_buf.as_mut_ptr(), &mut current_value_size as *mut u32, - ptr::null_mut(), &mut reg_type as *mut u32, ret_data_buf.as_mut_ptr(), &mut current_data_size as *mut u32, @@ -546,7 +517,7 @@ mod winreg { #[pyfunction] fn FlushKey(key: PyRef, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegFlushKey(key.hkey.load()) }; + let res = host_winreg::flush_key(key.hkey.load()); if res == 0 { Ok(()) } else { @@ -564,7 +535,7 @@ mod winreg { let sub_key = sub_key.to_wide_with_nul(); let file_name = file_name.to_wide_with_nul(); let res = - unsafe { Registry::RegLoadKeyW(key.hkey.load(), sub_key.as_ptr(), file_name.as_ptr()) }; + unsafe { host_winreg::load_key(key.hkey.load(), sub_key.as_ptr(), file_name.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -591,7 +562,7 @@ mod winreg { let mut res: Registry::HKEY = core::ptr::null_mut(); let err = unsafe { let key = args.key.hkey.load(); - Registry::RegOpenKeyExW( + host_winreg::open_key_ex( key, wide_sub_key.as_ptr(), args.reserved, @@ -612,35 +583,12 @@ mod winreg { #[pyfunction] fn QueryInfoKey(key: HKEYArg, vm: &VirtualMachine) -> PyResult> { let key = key.0; - let mut lpcsubkeys: u32 = 0; - let mut lpcvalues: u32 = 0; - let mut lpftlastwritetime: Foundation::FILETIME = unsafe { core::mem::zeroed() }; - let err = unsafe { - Registry::RegQueryInfoKeyW( - key, - core::ptr::null_mut(), - core::ptr::null_mut(), - 0 as _, - &mut lpcsubkeys, - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut lpcvalues, - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut lpftlastwritetime, - ) - }; - - if err != 0 { - return Err(vm.new_os_error(format!("error code: {err}"))); - } - let l: u64 = (lpftlastwritetime.dwHighDateTime as u64) << 32 - | lpftlastwritetime.dwLowDateTime as u64; + let info = host_winreg::query_info_key_full(key) + .map_err(|err| vm.new_os_error(format!("error code: {err}")))?; let tup: Vec = vec![ - vm.ctx.new_int(lpcsubkeys).into(), - vm.ctx.new_int(lpcvalues).into(), - vm.ctx.new_int(l).into(), + vm.ctx.new_int(info.sub_keys).into(), + vm.ctx.new_int(info.values).into(), + vm.ctx.new_int(info.last_write_time).into(), ]; Ok(vm.ctx.new_tuple(tup)) } @@ -656,140 +604,23 @@ mod winreg { )); } - // Open subkey if provided and non-empty - let child_key = if let Some(ref sk) = sub_key { - if !sk.is_empty() { - let wide_sub_key = sk.to_wide_with_nul(); - let mut out_key = core::ptr::null_mut(); - let res = unsafe { - Registry::RegOpenKeyExW( - hkey, - wide_sub_key.as_ptr(), - 0, - Registry::KEY_QUERY_VALUE, - &mut out_key, - ) - }; - if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); + host_winreg::query_default_value(hkey, sub_key.as_deref().map(std::ffi::OsStr::new)) + .map_err(|err| match err { + host_winreg::QueryStringError::Code(err) => { + os_error_from_windows_code(vm, err as i32) } - Some(out_key) - } else { - None - } - } else { - None - }; - - let target_key = child_key.unwrap_or(hkey); - let mut buf_size: u32 = 256; - let mut buffer: Vec = vec![0; buf_size as usize]; - let mut reg_type: u32 = 0; - - // Loop to handle ERROR_MORE_DATA - let result = loop { - let mut size = buf_size; - let res = unsafe { - Registry::RegQueryValueExW( - target_key, - core::ptr::null(), // NULL value name for default value - core::ptr::null_mut(), - &mut reg_type, - buffer.as_mut_ptr(), - &mut size, - ) - }; - if res == ERROR_MORE_DATA { - buf_size *= 2; - buffer.resize(buf_size as usize, 0); - continue; - } - if res == Foundation::ERROR_FILE_NOT_FOUND { - // Return empty string if there's no default value - break Ok(String::new()); - } - if res != 0 { - break Err(os_error_from_windows_code(vm, res as i32)); - } - if reg_type != Registry::REG_SZ { - break Err(os_error_from_windows_code( - vm, - Foundation::ERROR_INVALID_DATA as i32, - )); - } - - // Convert UTF-16 to String - let u16_slice = bytes_as_wide_slice(&buffer[..size as usize]); - let len = u16_slice - .iter() - .position(|&c| c == 0) - .unwrap_or(u16_slice.len()); - break String::from_utf16(&u16_slice[..len]) - .map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))); - }; - - // Close child key if we opened one - if let Some(ck) = child_key { - unsafe { Registry::RegCloseKey(ck) }; - } - - result + host_winreg::QueryStringError::Utf16(e) => { + vm.new_value_error(format!("UTF16 error: {e}")) + } + }) } #[pyfunction] fn QueryValueEx(key: HKEYArg, name: String, vm: &VirtualMachine) -> PyResult> { let hkey = key.0; - let wide_name = name.to_wide_with_nul(); - let mut buf_size: u32 = 0; - let res = unsafe { - Registry::RegQueryValueExW( - hkey, - wide_name.as_ptr(), - core::ptr::null_mut(), - core::ptr::null_mut(), - core::ptr::null_mut(), - &mut buf_size, - ) - }; - // Handle ERROR_MORE_DATA by using a default buffer size - if res == ERROR_MORE_DATA || buf_size == 0 { - buf_size = 256; - } else if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); - } - - let mut ret_buf = vec![0u8; buf_size as usize]; - let mut typ = 0; - let mut ret_size: u32; - - // Loop to handle ERROR_MORE_DATA - loop { - ret_size = buf_size; - let res = unsafe { - Registry::RegQueryValueExW( - hkey, - wide_name.as_ptr(), - core::ptr::null_mut(), - &mut typ, - ret_buf.as_mut_ptr(), - &mut ret_size, - ) - }; - - if res != ERROR_MORE_DATA { - if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); - } - break; - } - - // Double buffer size and retry - buf_size *= 2; - ret_buf.resize(buf_size as usize, 0); - } - - // Only pass the bytes actually returned by the API - let obj = reg_to_py(vm, &ret_buf[..ret_size as usize], typ)?; + let (ret_buf, typ) = host_winreg::query_value_bytes(hkey, std::ffi::OsStr::new(&name)) + .map_err(|err| os_error_from_windows_code(vm, err as i32))?; + let obj = reg_to_py(vm, &ret_buf, typ)?; // Return tuple (value, type) Ok(vm.ctx.new_tuple(vec![obj, vm.ctx.new_int(typ).into()])) } @@ -797,9 +628,7 @@ mod winreg { #[pyfunction] fn SaveKey(key: PyRef, file_name: String, vm: &VirtualMachine) -> PyResult<()> { let file_name = file_name.to_wide_with_nul(); - let res = unsafe { - Registry::RegSaveKeyW(key.hkey.load(), file_name.as_ptr(), core::ptr::null_mut()) - }; + let res = unsafe { host_winreg::save_key(key.hkey.load(), file_name.as_ptr()) }; if res == 0 { Ok(()) } else { @@ -827,49 +656,12 @@ mod winreg { )); } - // Create subkey if sub_key is non-empty - let child_key = if !sub_key.is_empty() { - let wide_sub_key = sub_key.to_wide_with_nul(); - let mut out_key = core::ptr::null_mut(); - let res = unsafe { - Registry::RegCreateKeyExW( - hkey, - wide_sub_key.as_ptr(), - 0, - core::ptr::null(), - 0, - Registry::KEY_SET_VALUE, - core::ptr::null(), - &mut out_key, - core::ptr::null_mut(), - ) - }; - if res != 0 { - return Err(os_error_from_windows_code(vm, res as i32)); - } - Some(out_key) - } else { - None - }; - - let target_key = child_key.unwrap_or(hkey); - // Convert value to UTF-16 for Wide API - let wide_value = value.to_wide_with_nul(); - let res = unsafe { - Registry::RegSetValueExW( - target_key, - core::ptr::null(), // value name is NULL - 0, - typ, - wide_value.as_ptr() as *const u8, - (wide_value.len() * 2) as u32, // byte count - ) - }; - - // Close child key if we created one - if let Some(ck) = child_key { - unsafe { Registry::RegCloseKey(ck) }; - } + let res = host_winreg::set_default_value( + hkey, + std::ffi::OsStr::new(&sub_key), + typ, + std::ffi::OsStr::new(&value), + ); if res == 0 { Ok(()) @@ -896,7 +688,7 @@ mod winreg { Ok(vm.ctx.new_int(val).into()) } REG_SZ | REG_EXPAND_SZ => { - let u16_slice = bytes_as_wide_slice(ret_data); + let u16_slice = host_winreg::bytes_as_wide_slice(ret_data); // Only use characters up to the first NUL. let len = u16_slice .iter() @@ -910,7 +702,7 @@ mod winreg { if ret_data.is_empty() { Ok(vm.ctx.new_list(vec![]).into()) } else { - let u16_slice = bytes_as_wide_slice(ret_data); + let u16_slice = host_winreg::bytes_as_wide_slice(ret_data); let u16_count = u16_slice.len(); // Remove trailing null if present (like countStrings) @@ -1056,7 +848,7 @@ mod winreg { None => (core::ptr::null(), 0), }; let res = - unsafe { Registry::RegSetValueExW(key.hkey.load(), value_name_ptr, 0, typ, ptr, len) }; + unsafe { host_winreg::set_value_ex(key.hkey.load(), value_name_ptr, typ, ptr, len) }; if res != 0 { return Err(os_error_from_windows_code(vm, res as i32)); } @@ -1065,7 +857,7 @@ mod winreg { #[pyfunction] fn DisableReflectionKey(key: PyRef, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegDisableReflectionKey(key.hkey.load()) }; + let res = host_winreg::disable_reflection_key(key.hkey.load()); if res == 0 { Ok(()) } else { @@ -1075,7 +867,7 @@ mod winreg { #[pyfunction] fn EnableReflectionKey(key: PyRef, vm: &VirtualMachine) -> PyResult<()> { - let res = unsafe { Registry::RegEnableReflectionKey(key.hkey.load()) }; + let res = host_winreg::enable_reflection_key(key.hkey.load()); if res == 0 { Ok(()) } else { @@ -1086,7 +878,7 @@ mod winreg { #[pyfunction] fn QueryReflectionKey(key: PyRef, vm: &VirtualMachine) -> PyResult { let mut result: i32 = 0; - let res = unsafe { Registry::RegQueryReflectionKey(key.hkey.load(), &mut result) }; + let res = unsafe { host_winreg::query_reflection_key(key.hkey.load(), &mut result) }; if res == 0 { Ok(result != 0) } else { @@ -1096,34 +888,13 @@ mod winreg { #[pyfunction] fn ExpandEnvironmentStrings(i: String, vm: &VirtualMachine) -> PyResult { - let wide_input = i.to_wide_with_nul(); - - // First call with size=0 to get required buffer size - let required_size = unsafe { - windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW( - wide_input.as_ptr(), - core::ptr::null_mut(), - 0, - ) - }; - if required_size == 0 { - return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); - } - - // Allocate buffer with exact size and expand - let mut out = vec![0u16; required_size as usize]; - let r = unsafe { - windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW( - wide_input.as_ptr(), - out.as_mut_ptr(), - required_size, - ) - }; - if r == 0 { - return Err(vm.new_os_error("ExpandEnvironmentStringsW failed".to_string())); - } - - let len = out.iter().position(|&c| c == 0).unwrap_or(out.len()); - String::from_utf16(&out[..len]).map_err(|e| vm.new_value_error(format!("UTF16 error: {e}"))) + host_winreg::expand_environment_strings(std::ffi::OsStr::new(&i)).map_err(|err| match err { + host_winreg::ExpandEnvironmentStringsError::Os => { + vm.new_os_error("ExpandEnvironmentStringsW failed".to_string()) + } + host_winreg::ExpandEnvironmentStringsError::Utf16(e) => { + vm.new_value_error(format!("UTF16 error: {e}")) + } + }) } } diff --git a/crates/vm/src/windows.rs b/crates/vm/src/windows.rs index fe109c95ee5..18cabfb493e 100644 --- a/crates/vm/src/windows.rs +++ b/crates/vm/src/windows.rs @@ -1,13 +1,7 @@ -use crate::host_env::fileutils::{ - StatStruct, - windows::{FILE_INFO_BY_NAME_CLASS, get_file_information_by_name}, -}; use crate::{ PyObjectRef, PyResult, TryFromObject, VirtualMachine, convert::{ToPyObject, ToPyResult}, }; -use rustpython_host_env::windows::ToWideString; -use std::ffi::OsStr; use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE}; /// Windows HANDLE wrapper for Python interop @@ -75,467 +69,3 @@ impl ToPyObject for WinHandle { (self.0 as HandleInt).to_pyobject(vm) } } - -pub fn init_winsock() { - static WSA_INIT: parking_lot::Once = parking_lot::Once::new(); - WSA_INIT.call_once(|| unsafe { - let mut wsa_data = core::mem::MaybeUninit::uninit(); - let _ = windows_sys::Win32::Networking::WinSock::WSAStartup(0x0101, wsa_data.as_mut_ptr()); - }) -} - -// win32_xstat in cpython -pub fn win32_xstat(path: &OsStr, traverse: bool) -> std::io::Result { - let mut result = win32_xstat_impl(path, traverse)?; - // ctime is only deprecated from 3.12, so we copy birthtime across - result.st_ctime = result.st_birthtime; - result.st_ctime_nsec = result.st_birthtime_nsec; - Ok(result) -} - -fn is_reparse_tag_name_surrogate(tag: u32) -> bool { - (tag & 0x20000000) > 0 -} - -// Constants -const IO_REPARSE_TAG_SYMLINK: u32 = 0xA000000C; -const S_IFMT: u16 = libc::S_IFMT as u16; -const S_IFDIR: u16 = libc::S_IFDIR as u16; -const S_IFREG: u16 = libc::S_IFREG as u16; -const S_IFCHR: u16 = libc::S_IFCHR as u16; -const S_IFLNK: u16 = crate::host_env::fileutils::windows::S_IFLNK as u16; -const S_IFIFO: u16 = crate::host_env::fileutils::windows::S_IFIFO as u16; - -/// FILE_ATTRIBUTE_TAG_INFO structure for GetFileInformationByHandleEx -#[repr(C)] -#[derive(Default)] -struct FileAttributeTagInfo { - file_attributes: u32, - reparse_tag: u32, -} - -/// Ported from attributes_to_mode (fileutils.c) -fn attributes_to_mode(attr: u32) -> u16 { - use windows_sys::Win32::Storage::FileSystem::{ - FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_READONLY, - }; - let mut m: u16 = 0; - if attr & FILE_ATTRIBUTE_DIRECTORY != 0 { - m |= S_IFDIR | 0o111; // IFEXEC for user,group,other - } else { - m |= S_IFREG; - } - if attr & FILE_ATTRIBUTE_READONLY != 0 { - m |= 0o444; - } else { - m |= 0o666; - } - m -} - -/// Ported from _Py_attribute_data_to_stat (fileutils.c) -/// Converts BY_HANDLE_FILE_INFORMATION to StatStruct -fn attribute_data_to_stat( - info: &windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, - reparse_tag: u32, - basic_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_BASIC_INFO>, - id_info: Option<&windows_sys::Win32::Storage::FileSystem::FILE_ID_INFO>, -) -> StatStruct { - use crate::host_env::fileutils::windows::SECS_BETWEEN_EPOCHS; - use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT; - - let mut st_mode = attributes_to_mode(info.dwFileAttributes); - let st_size = ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64); - let st_dev = id_info - .map(|id| id.VolumeSerialNumber as u32) - .unwrap_or(info.dwVolumeSerialNumber); - let st_nlink = info.nNumberOfLinks as i32; - - // Convert FILETIME/LARGE_INTEGER to (time_t, nsec) - let filetime_to_time = |ft_low: u32, ft_high: u32| -> (libc::time_t, i32) { - let ticks = ((ft_high as i64) << 32) | (ft_low as i64); - let nsec = ((ticks % 10_000_000) * 100) as i32; - let sec = (ticks / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; - (sec, nsec) - }; - - let large_integer_to_time = |li: i64| -> (libc::time_t, i32) { - let nsec = ((li % 10_000_000) * 100) as i32; - let sec = (li / 10_000_000 - SECS_BETWEEN_EPOCHS) as libc::time_t; - (sec, nsec) - }; - - let (st_birthtime, st_birthtime_nsec); - let (st_mtime, st_mtime_nsec); - let (st_atime, st_atime_nsec); - - if let Some(bi) = basic_info { - (st_birthtime, st_birthtime_nsec) = large_integer_to_time(bi.CreationTime); - (st_mtime, st_mtime_nsec) = large_integer_to_time(bi.LastWriteTime); - (st_atime, st_atime_nsec) = large_integer_to_time(bi.LastAccessTime); - } else { - (st_birthtime, st_birthtime_nsec) = filetime_to_time( - info.ftCreationTime.dwLowDateTime, - info.ftCreationTime.dwHighDateTime, - ); - (st_mtime, st_mtime_nsec) = filetime_to_time( - info.ftLastWriteTime.dwLowDateTime, - info.ftLastWriteTime.dwHighDateTime, - ); - (st_atime, st_atime_nsec) = filetime_to_time( - info.ftLastAccessTime.dwLowDateTime, - info.ftLastAccessTime.dwHighDateTime, - ); - } - - // Get file ID from id_info or fallback to file index - let (st_ino, st_ino_high) = if let Some(id) = id_info { - // FILE_ID_INFO.FileId is FILE_ID_128 which is [u8; 16] - let bytes = id.FileId.Identifier; - let low = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); - let high = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); - (low, high) - } else { - let ino = ((info.nFileIndexHigh as u64) << 32) | (info.nFileIndexLow as u64); - (ino, 0u64) - }; - - // Set symlink mode if applicable - if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 - && reparse_tag == IO_REPARSE_TAG_SYMLINK - { - st_mode = (st_mode & !S_IFMT) | S_IFLNK; - } - - StatStruct { - st_dev, - st_ino, - st_ino_high, - st_mode, - st_nlink, - st_uid: 0, - st_gid: 0, - st_rdev: 0, - st_size, - st_atime, - st_atime_nsec, - st_mtime, - st_mtime_nsec, - st_ctime: 0, // Will be set by caller - st_ctime_nsec: 0, - st_birthtime, - st_birthtime_nsec, - st_file_attributes: info.dwFileAttributes, - st_reparse_tag: reparse_tag, - } -} - -/// Get file info using FindFirstFileW (fallback when CreateFileW fails) -/// Ported from attributes_from_dir -fn attributes_from_dir( - path: &OsStr, -) -> std::io::Result<( - windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION, - u32, -)> { - use windows_sys::Win32::Storage::FileSystem::{ - BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FindClose, FindFirstFileW, - WIN32_FIND_DATAW, - }; - - let wide: Vec = path.to_wide_with_nul(); - let mut find_data: WIN32_FIND_DATAW = unsafe { core::mem::zeroed() }; - - let handle = unsafe { FindFirstFileW(wide.as_ptr(), &mut find_data) }; - if handle == INVALID_HANDLE_VALUE { - return Err(std::io::Error::last_os_error()); - } - unsafe { FindClose(handle) }; - - let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; - info.dwFileAttributes = find_data.dwFileAttributes; - info.ftCreationTime = find_data.ftCreationTime; - info.ftLastAccessTime = find_data.ftLastAccessTime; - info.ftLastWriteTime = find_data.ftLastWriteTime; - info.nFileSizeHigh = find_data.nFileSizeHigh; - info.nFileSizeLow = find_data.nFileSizeLow; - - let reparse_tag = if find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { - find_data.dwReserved0 - } else { - 0 - }; - - Ok((info, reparse_tag)) -} - -/// Ported from win32_xstat_slow_impl -fn win32_xstat_slow_impl(path: &OsStr, traverse: bool) -> std::io::Result { - use windows_sys::Win32::{ - Foundation::{ - CloseHandle, ERROR_ACCESS_DENIED, ERROR_CANT_ACCESS_FILE, ERROR_INVALID_FUNCTION, - ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, GENERIC_READ, - INVALID_HANDLE_VALUE, - }, - Storage::FileSystem::{ - BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY, - FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_REPARSE_POINT, FILE_BASIC_INFO, - FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_ID_INFO, - FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_TYPE_CHAR, - FILE_TYPE_DISK, FILE_TYPE_PIPE, FILE_TYPE_UNKNOWN, FileAttributeTagInfo, FileBasicInfo, - FileIdInfo, GetFileAttributesW, GetFileInformationByHandle, - GetFileInformationByHandleEx, GetFileType, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING, - }, - }; - - let wide: Vec = path.to_wide_with_nul(); - - let access = FILE_READ_ATTRIBUTES; - let mut flags = FILE_FLAG_BACKUP_SEMANTICS; - if !traverse { - flags |= FILE_FLAG_OPEN_REPARSE_POINT; - } - - let mut h_file = unsafe { - CreateFileW( - wide.as_ptr(), - access, - 0, - core::ptr::null(), - OPEN_EXISTING, - flags, - core::ptr::null_mut(), - ) - }; - - let mut file_info: BY_HANDLE_FILE_INFORMATION = unsafe { core::mem::zeroed() }; - let mut tag_info = FileAttributeTagInfo::default(); - let mut is_unhandled_tag = false; - - if h_file == INVALID_HANDLE_VALUE { - let error = std::io::Error::last_os_error(); - let error_code = error.raw_os_error().unwrap_or(0) as u32; - - match error_code { - ERROR_ACCESS_DENIED | ERROR_SHARING_VIOLATION => { - // Try reading the parent directory using FindFirstFileW - let (info, reparse_tag) = attributes_from_dir(path)?; - file_info = info; - tag_info.reparse_tag = reparse_tag; - - if file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 - && (traverse || !is_reparse_tag_name_surrogate(tag_info.reparse_tag)) - { - return Err(error); - } - // h_file remains INVALID_HANDLE_VALUE, we'll use file_info from FindFirstFileW - } - ERROR_INVALID_PARAMETER => { - // Retry with GENERIC_READ (needed for \\.\con) - h_file = unsafe { - CreateFileW( - wide.as_ptr(), - access | GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - 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 => { - // bpo37834: open unhandled reparse points if traverse fails - 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), - } - } - - // Scope for handle cleanup - let result = (|| -> std::io::Result { - if h_file != INVALID_HANDLE_VALUE { - // Handle types other than files on disk - let file_type = unsafe { GetFileType(h_file) }; - if file_type != FILE_TYPE_DISK { - if file_type == FILE_TYPE_UNKNOWN { - let err = std::io::Error::last_os_error(); - if err.raw_os_error().unwrap_or(0) != 0 { - return Err(err); - } - } - let file_attributes = unsafe { GetFileAttributesW(wide.as_ptr()) }; - let mut st_mode: u16 = 0; - if file_attributes != INVALID_FILE_ATTRIBUTES - && file_attributes & FILE_ATTRIBUTE_DIRECTORY != 0 - { - st_mode = S_IFDIR; - } else if file_type == FILE_TYPE_CHAR { - st_mode = S_IFCHR; - } else if file_type == FILE_TYPE_PIPE { - st_mode = S_IFIFO; - } - return Ok(StatStruct { - st_mode, - ..Default::default() - }); - } - - // Query the reparse tag - if !traverse || is_unhandled_tag { - let mut local_tag_info: FileAttributeTagInfo = unsafe { core::mem::zeroed() }; - let ret = unsafe { - GetFileInformationByHandleEx( - h_file, - FileAttributeTagInfo, - &mut local_tag_info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - }; - if ret == 0 { - let err_code = - std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; - match err_code { - ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { - local_tag_info.file_attributes = FILE_ATTRIBUTE_NORMAL; - local_tag_info.reparse_tag = 0; - } - _ => return Err(std::io::Error::last_os_error()), - } - } else if local_tag_info.file_attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { - if is_reparse_tag_name_surrogate(local_tag_info.reparse_tag) { - if is_unhandled_tag { - return Err(std::io::Error::from_raw_os_error( - ERROR_CANT_ACCESS_FILE as i32, - )); - } - // This is a symlink, keep the tag info - } else if !is_unhandled_tag { - // Traverse a non-link reparse point - unsafe { CloseHandle(h_file) }; - return win32_xstat_slow_impl(path, true); - } - } - tag_info = local_tag_info; - } - - // Get file information - let ret = unsafe { GetFileInformationByHandle(h_file, &mut file_info) }; - if ret == 0 { - let err_code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as u32; - match err_code { - ERROR_INVALID_PARAMETER | ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED => { - // Volumes and physical disks are block devices - return Ok(StatStruct { - st_mode: 0x6000, // S_IFBLK - ..Default::default() - }); - } - _ => return Err(std::io::Error::last_os_error()), - } - } - - // Get FILE_BASIC_INFO - let mut basic_info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() }; - let has_basic_info = unsafe { - GetFileInformationByHandleEx( - h_file, - FileBasicInfo, - &mut basic_info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - } != 0; - - // Get FILE_ID_INFO (optional) - let mut id_info: FILE_ID_INFO = unsafe { core::mem::zeroed() }; - let has_id_info = unsafe { - GetFileInformationByHandleEx( - h_file, - FileIdInfo, - &mut id_info as *mut _ as *mut _, - core::mem::size_of::() as u32, - ) - } != 0; - - let mut result = attribute_data_to_stat( - &file_info, - tag_info.reparse_tag, - if has_basic_info { - Some(&basic_info) - } else { - None - }, - if has_id_info { Some(&id_info) } else { None }, - ); - result.update_st_mode_from_path(path, file_info.dwFileAttributes); - Ok(result) - } else { - // We got file_info from attributes_from_dir - let mut result = attribute_data_to_stat(&file_info, tag_info.reparse_tag, None, None); - result.update_st_mode_from_path(path, file_info.dwFileAttributes); - Ok(result) - } - })(); - - // Cleanup - if h_file != INVALID_HANDLE_VALUE { - unsafe { CloseHandle(h_file) }; - } - - result -} - -fn win32_xstat_impl(path: &OsStr, traverse: bool) -> std::io::Result { - use windows_sys::Win32::{Foundation, Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT}; - - let stat_info = - get_file_information_by_name(path, FILE_INFO_BY_NAME_CLASS::FileStatBasicByNameInfo); - match stat_info { - Ok(stat_info) => { - if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT == 0) - || (!traverse && is_reparse_tag_name_surrogate(stat_info.ReparseTag)) - { - let mut result = - crate::host_env::fileutils::windows::stat_basic_info_to_stat(&stat_info); - // If st_ino is 0, fall through to slow path to get proper file ID - if result.st_ino != 0 || result.st_ino_high != 0 { - result.update_st_mode_from_path(path, stat_info.FileAttributes); - return Ok(result); - } - } - } - Err(e) => { - if let Some(errno) = e.raw_os_error() - && matches!( - errno as u32, - Foundation::ERROR_FILE_NOT_FOUND - | Foundation::ERROR_PATH_NOT_FOUND - | Foundation::ERROR_NOT_READY - | Foundation::ERROR_BAD_NET_NAME - ) - { - return Err(e); - } - } - } - - // Fallback to slow implementation - win32_xstat_slow_impl(path, traverse) -}