Visitar URL original
more hostenv isolation by youknowone · Pull Request #7886 · RustPython/RustPython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a5ac299
Convert host_env Windows path/argv params from raw *const u16 to &Wid…
youknowone May 16, 2026
cea48cc
Migrate remaining winapi raw u16 pointer signatures to typed references
youknowone May 16, 2026
25a9d40
Migrate winreg pub unsafe fn string parameters to typed references
youknowone May 16, 2026
0acdcad
Add ToPyException impls for host_env error types (PyPy wrap_oserror a…
youknowone May 16, 2026
dc57418
Add CheckLibcResult helper and apply to socket/fcntl/shm/posix_wasi
youknowone May 16, 2026
2d4be2c
Add Win32 BOOL/HANDLE check helpers; apply check helpers across host_env
youknowone May 16, 2026
592e8b4
Apply Win32/libc check helpers to overlapped/testconsole/os.rs
youknowone May 16, 2026
86055d2
Apply Win32 check helpers to winapi.rs (partial)
youknowone May 16, 2026
fe71c58
Apply Win32 check helpers across more winapi.rs functions
youknowone May 16, 2026
a7c567a
Apply Win32 check helpers to nt.rs (partial)
youknowone May 16, 2026
5329cc5
Add CheckWin32Sentinel helper; apply to nt.rs INVALID_HANDLE_VALUE/IN…
youknowone May 16, 2026
f1532e2
Add OwnedHandle / HandleToOwned helper; apply to mmap create_named_ma…
youknowone May 16, 2026
e768278
Use OwnedHandle RAII in nt::pipe to eliminate manual cleanup on error…
youknowone May 16, 2026
b730f7e
Use OwnedHandle in nt::chmod_follow; hoist HandleToOwned import
youknowone May 16, 2026
67ece5b
Drop rustix dependency from vm crate
youknowone May 17, 2026
a61d9bd
Fix CI failures: cross-platform regressions
youknowone May 19, 2026
3320c2a
Fix CI failures: cfg gates and unused imports
youknowone May 19, 2026
e9ec6e8
Fix CI failures: rustfmt and windows unused import
youknowone May 19, 2026
ba3f4fa
Push remaining libc/extern callsites from vm into host_env
youknowone May 19, 2026
45c83dd
Move time tz state and winsound FFI into host_env
youknowone May 19, 2026
49c13f8
Migrate unsetenv to host_env::nt::wputenv; rustfmt
youknowone May 20, 2026
61e0fd4
Address PR review comments
youknowone May 20, 2026
783c6b5
Fix CI failures and address review follow-ups
youknowone May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Push remaining libc/extern callsites from vm into host_env
Add host_env wrappers and replace the corresponding vm call sites:
- host_env::errno::strerror_string for libc::strerror
- host_env::io::write_stderr_raw for libc::write(STDERR_FILENO,...)
- host_env::locale::localeconv_data reused from vm::format
- host_env::os::abort for the inline abort extern
- host_env::os::urandom wraps getrandom; getrandom moves from vm to host_env
- host_env::posix::lchmod for the macOS/BSD lchmod extern
- host_env::posix::fcopyfile for the macOS fcopyfile extern
- host_env::nt::wputenv for the Windows _wputenv extern

vm/format.rs's get_locale_info now uses host_env on both unix and windows
instead of the unix-only libc::localeconv path.
  • Loading branch information
youknowone committed May 20, 2026
commit ba3f4fa579ffa869271684041e8e68b5a038c9e7
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/host_env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ license.workspace = true
rustpython-wtf8 = { workspace = true }

bitflags = { workspace = true }
getrandom = { workspace = true }
libc = { workspace = true }
num-traits = { workspace = true }
parking_lot = { workspace = true }
Expand Down
13 changes: 13 additions & 0 deletions crates/host_env/src/errno.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
// spell-checker:disable

/// Return the platform `strerror(errno)` message as an owned `String`.
/// Returns `None` when the runtime gives no description for `errno`.
#[cfg(any(unix, windows))]
#[must_use]
pub fn strerror_string(errno: i32) -> Option<String> {
let ptr = unsafe { libc::strerror(errno) };
if ptr.is_null() {
return None;
}
let s = unsafe { core::ffi::CStr::from_ptr(ptr) }.to_string_lossy();
Some(s.into_owned())
}

#[cfg(any(unix, windows, target_os = "wasi"))]
pub mod errors {
pub use libc::*;
Expand Down
10 changes: 10 additions & 0 deletions crates/host_env/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,13 @@ pub fn write_once(fd: crt_fd::Borrowed<'_>, buf: &[u8]) -> io::Result<usize> {
pub fn close_owned_fd(fd: crt_fd::Owned) -> io::Result<()> {
crt_fd::close(fd)
}

/// Async-signal-safe raw write to the platform stderr file descriptor.
/// Avoids `std::io::stderr()` locking so it is safe to call from fork
/// children and signal handlers.
#[cfg(unix)]
pub fn write_stderr_raw(buf: &[u8]) {
unsafe {
let _ = libc::write(libc::STDERR_FILENO, buf.as_ptr().cast(), buf.len());
}
}
12 changes: 12 additions & 0 deletions crates/host_env/src/nt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@ pub fn mkdir(path: &widestring::WideCStr, mode: i32) -> io::Result<()> {

unsafe extern "C" {
fn _umask(mask: i32) -> i32;
fn _wputenv(envstring: *const u16) -> libc::c_int;
}

pub fn umask(mask: i32) -> io::Result<i32> {
Expand All @@ -1217,6 +1218,17 @@ pub fn umask(mask: i32) -> io::Result<i32> {
}
}

/// Update the CRT environment via `_wputenv`.
/// `envstring` must point to a nul-terminated wide string of the form `KEY=value`.
pub fn wputenv(envstring: &widestring::WideCStr) -> io::Result<()> {
let result = unsafe { crate::suppress_iph!(_wputenv(envstring.as_ptr())) };
if result != 0 {
Err(crate::os::errno_io_error())
} else {
Ok(())
}
}

fn set_fd_inheritable(fd: i32, inheritable: bool) -> io::Result<()> {
let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) };
let handle = crt_fd::as_handle(borrowed)?;
Expand Down
15 changes: 15 additions & 0 deletions crates/host_env/src/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,21 @@ pub fn exit(code: i32) -> ! {
std::process::exit(code)
}

/// Wrapper around the C `abort()` call: terminates the process abnormally.
pub fn abort() -> ! {
unsafe extern "C" {
fn abort() -> !;
}
unsafe { abort() }
}

/// Read `size` cryptographically random bytes from the OS.
pub fn urandom(size: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; size];
getrandom::fill(&mut buf).map_err(io::Error::from)?;
Ok(buf)
}

#[cfg(any(unix, windows, target_os = "wasi"))]
pub fn isatty(fd: i32) -> bool {
unsafe { suppress_iph!(libc::isatty(fd)) != 0 }
Expand Down
30 changes: 30 additions & 0 deletions crates/host_env/src/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,36 @@ pub fn unlinkat(dir_fd: i32, path: &CStr) -> std::io::Result<()> {
}
}

#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))]
pub fn lchmod(path: &CStr, mode: libc::mode_t) -> std::io::Result<()> {
unsafe extern "C" {
fn lchmod(path: *const libc::c_char, mode: libc::mode_t) -> libc::c_int;
}
if unsafe { lchmod(path.as_ptr(), mode) } == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}

#[cfg(target_os = "macos")]
pub fn fcopyfile(in_fd: i32, out_fd: i32, flags: u32) -> std::io::Result<()> {
unsafe extern "C" {
fn fcopyfile(
in_fd: libc::c_int,
out_fd: libc::c_int,
state: *mut libc::c_void,
flags: u32,
) -> libc::c_int;
}
let ret = unsafe { fcopyfile(in_fd, out_fd, core::ptr::null_mut(), flags) };
if ret < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}

#[cfg(not(windows))]
pub fn make_dir(path: &CStr, mode: u32) -> std::io::Result<()> {
let ret = unsafe { libc::mkdir(path.as_ptr(), mode as _) };
Expand Down
1 change: 0 additions & 1 deletion crates/vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ crossbeam-utils = { workspace = true }
chrono = { workspace = true }
constant_time_eq = { workspace = true }
flame = { workspace = true, optional = true }
getrandom = { workspace = true }
hex = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
Expand Down
24 changes: 6 additions & 18 deletions crates/vm/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1385,28 +1385,16 @@ impl ToOSErrorBuilder for std::io::Error {
// Use C runtime's strerror for POSIX errno values.
// For Windows-specific error codes, fall back to FormatMessage.
const MAX_POSIX_ERRNO: i32 = 127;
if errno > 0 && errno <= MAX_POSIX_ERRNO {
let ptr = unsafe { libc::strerror(errno) };
if !ptr.is_null() {
let s = unsafe { core::ffi::CStr::from_ptr(ptr) }.to_string_lossy();
if !s.starts_with("Unknown error") {
break 'msg s.into_owned();
}
}
if errno > 0 && errno <= MAX_POSIX_ERRNO
&& let Some(s) = crate::host_env::errno::strerror_string(errno)
&& !s.starts_with("Unknown error")
{
break 'msg s;
}
self.to_string()
};
#[cfg(unix)]
let msg = {
let ptr = unsafe { libc::strerror(errno) };
if !ptr.is_null() {
unsafe { core::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned()
} else {
self.to_string()
}
};
let msg = crate::host_env::errno::strerror_string(errno).unwrap_or_else(|| self.to_string());
#[cfg(not(any(windows, unix)))]
let msg = self.to_string();

Expand Down
63 changes: 15 additions & 48 deletions crates/vm/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,25 @@ use crate::common::format::*;
use crate::common::wtf8::{Wtf8, Wtf8Buf};

/// Get locale information from C `localeconv()` for the 'n' format specifier.
#[cfg(unix)]
#[cfg(any(unix, windows))]
pub(crate) fn get_locale_info() -> LocaleInfo {
use core::ffi::CStr;
unsafe {
let lc = libc::localeconv();
if lc.is_null() {
return LocaleInfo {
thousands_sep: String::new(),
decimal_point: ".".to_string(),
grouping: vec![],
};
}
let thousands_sep = CStr::from_ptr((*lc).thousands_sep)
.to_string_lossy()
.into_owned();
let decimal_point = CStr::from_ptr((*lc).decimal_point)
.to_string_lossy()
.into_owned();
let grouping = parse_grouping((*lc).grouping);
LocaleInfo {
thousands_sep,
decimal_point,
grouping,
}
let lc = crate::host_env::locale::localeconv_data();
let mut grouping: Vec<u8> = lc.grouping.iter().map(|&c| c as u8).collect();
if !grouping.is_empty() {
grouping.push(0);
}
LocaleInfo {
thousands_sep: String::from_utf8_lossy(&lc.thousands_sep).into_owned(),
decimal_point: if lc.decimal_point.is_empty() {
".".to_string()
} else {
String::from_utf8_lossy(&lc.decimal_point).into_owned()
},
grouping,
}
}

#[cfg(not(unix))]
#[cfg(not(any(unix, windows)))]
pub(crate) fn get_locale_info() -> LocaleInfo {
LocaleInfo {
thousands_sep: String::new(),
Expand All @@ -46,30 +37,6 @@ pub(crate) fn get_locale_info() -> LocaleInfo {
}
}

/// Parse C `lconv.grouping` into a `Vec<u8>`.
/// Reads bytes until 0 or CHAR_MAX, then appends 0 (meaning "repeat last group").
#[cfg(unix)]
unsafe fn parse_grouping(grouping: *const libc::c_char) -> Vec<u8> {
let mut result = Vec::new();
if grouping.is_null() {
return result;
}

unsafe {
let mut ptr = grouping;
while ![0, libc::c_char::MAX].contains(&*ptr) {
result.push(*ptr as _);
ptr = ptr.add(1);
}
}

if !result.is_empty() {
result.push(0);
}

result
}

impl IntoPyException for FormatSpecError {
fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
match self {
Expand Down
28 changes: 8 additions & 20 deletions crates/vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,11 +466,6 @@ pub(super) mod _os {
}
}

#[cfg(windows)]
unsafe extern "C" {
fn _wputenv(envstring: *const u16) -> libc::c_int;
}

/// Check if wide string length exceeds Windows environment variable limit.
#[cfg(windows)]
fn check_env_var_len(wide_len: usize, vm: &VirtualMachine) -> PyResult<()> {
Expand Down Expand Up @@ -498,15 +493,13 @@ pub(super) mod _os {
return Err(vm.new_value_error("illegal environment variable name"));
}
let env_str = format!("{key_str}={value_str}");
let wide = env_str.to_wide_with_nul();
check_env_var_len(wide.len(), vm)?;
// env_str is guaranteed nul-free by the checks above.
let wide = widestring::WideCString::from_str(&env_str)
.expect("env_str validated to contain no NUL");
check_env_var_len(wide.len() + 1, vm)?;

// Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ
let result = unsafe { rustpython_host_env::suppress_iph!(_wputenv(wide.as_ptr())) };
if result != 0 {
return Err(vm.new_last_errno_error());
}
Ok(())
// Use _wputenv (not SetEnvironmentVariableW) to update CRT environ.
rustpython_host_env::nt::wputenv(&wide).map_err(|e| e.into_pyexception(vm))
}

#[cfg(not(windows))]
Expand Down Expand Up @@ -1434,20 +1427,15 @@ pub(super) mod _os {

#[pyfunction]
fn abort() {
unsafe extern "C" {
fn abort();
}
unsafe { abort() }
crate::host_env::os::abort()
}

#[pyfunction]
fn urandom(size: isize, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
if size < 0 {
return Err(vm.new_value_error("negative argument not allowed"));
}
let mut buf = vec![0u8; size as usize];
getrandom::fill(&mut buf).map_err(|e| io::Error::from(e).into_pyexception(vm))?;
Ok(buf)
crate::host_env::os::urandom(size as usize).map_err(|e| e.into_pyexception(vm))
}

#[pyfunction]
Expand Down
31 changes: 4 additions & 27 deletions crates/vm/src/stdlib/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,16 +999,9 @@ pub mod module {
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd",))]
#[pyfunction]
fn lchmod(path: OsPath, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
unsafe extern "C" {
fn lchmod(path: *const libc::c_char, mode: libc::mode_t) -> libc::c_int;
}
let c_path = path.clone().into_cstring(vm)?;
if unsafe { lchmod(c_path.as_ptr(), mode as libc::mode_t) } == 0 {
Ok(())
} else {
let err = std::io::Error::last_os_error();
Err(OSErrorBuilder::with_filename(&err, path, vm))
}
rustpython_host_env::posix::lchmod(&c_path, mode as libc::mode_t)
.map_err(|err| OSErrorBuilder::with_filename(&err, path, vm))
}

#[pyfunction]
Expand Down Expand Up @@ -1660,27 +1653,11 @@ pub mod module {
Ok(_os::TerminalSizeData { columns, lines })
}

// from libstd:
// https://github.com/rust-lang/rust/blob/daecab3a784f28082df90cebb204998051f3557d/src/libstd/sys/unix/fs.rs#L1251
#[cfg(target_os = "macos")]
unsafe extern "C" {
fn fcopyfile(
in_fd: libc::c_int,
out_fd: libc::c_int,
state: *mut libc::c_void, // copyfile_state_t (unused)
flags: u32, // copyfile_flags_t
) -> libc::c_int;
}

#[cfg(target_os = "macos")]
#[pyfunction]
fn _fcopyfile(in_fd: i32, out_fd: i32, flags: i32, vm: &VirtualMachine) -> PyResult<()> {
let ret = unsafe { fcopyfile(in_fd, out_fd, core::ptr::null_mut(), flags as u32) };
if ret < 0 {
Err(vm.new_last_errno_error())
} else {
Ok(())
}
rustpython_host_env::posix::fcopyfile(in_fd, out_fd, flags as u32)
.map_err(|err| err.into_pyexception(vm))
}

#[pyfunction]
Expand Down
4 changes: 1 addition & 3 deletions crates/vm/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,9 +572,7 @@ pub(super) fn stw_trace(msg: core::fmt::Arguments<'_>) {
crate::stdlib::_thread::get_ident(),
msg
);
unsafe {
let _ = libc::write(libc::STDERR_FILENO, out.buf.as_ptr().cast(), out.len);
}
crate::host_env::io::write_stderr_raw(&out.buf[..out.len]);
}
}

Expand Down