From 7ceada00488daae7a2810ea7164aa94e02d9c169 Mon Sep 17 00:00:00 2001 From: Joshua Megnauth <48846352+joshuamegnauth54@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:38:57 -0400 Subject: [PATCH] Dedupe Unix & WASI stdlib (#8635) Follow up to #8591 because I figured out how to deduplicate the code. This is nicer for future patches that improve WASI support because Unix-likes and WASI can often share the same dispatch functions. --- crates/vm/src/stdlib/mod.rs | 2 + crates/vm/src/stdlib/nt.rs | 12 +----- crates/vm/src/stdlib/os.rs | 13 +++++++ crates/vm/src/stdlib/posix.rs | 37 +----------------- crates/vm/src/stdlib/posix_compat.rs | 52 +++---------------------- crates/vm/src/stdlib/posix_unix_like.rs | 35 +++++++++++++++++ 6 files changed, 59 insertions(+), 92 deletions(-) create mode 100644 crates/vm/src/stdlib/posix_unix_like.rs diff --git a/crates/vm/src/stdlib/mod.rs b/crates/vm/src/stdlib/mod.rs index f504bad44..40ee2eda8 100644 --- a/crates/vm/src/stdlib/mod.rs +++ b/crates/vm/src/stdlib/mod.rs @@ -37,6 +37,8 @@ pub mod posix; #[cfg(all(feature = "host_env", not(any(unix, windows))))] #[path = "posix_compat.rs"] pub mod posix; +#[cfg(all(feature = "host_env", any(unix, target_os = "wasi")))] +pub mod posix_unix_like; #[cfg(all( feature = "host_env", diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 2dfe98fb9..6a27906f2 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -13,7 +13,7 @@ pub(crate) mod module { function::{ArgMapping, Either, OptionalArg}, host_env::crt_fd, ospath::{OsPath, OsPathOrFd}, - stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, + stdlib::os::{_os, DirFd, SupportFunc, SymlinkArgs}, }; use core::hint::cold_path; use libc::intptr_t; @@ -101,16 +101,6 @@ pub(crate) mod module { host_nt::supports_virtual_terminal() } - #[derive(FromArgs)] - pub(super) struct SymlinkArgs<'fd> { - src: OsPath, - dst: OsPath, - #[pyarg(flatten)] - target_is_directory: TargetIsDirectory, - #[pyarg(flatten)] - _dir_fd: DirFd<'fd, { _os::SYMLINK_DIR_FD as usize }>, - } - #[pyfunction] pub(super) fn symlink(args: SymlinkArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { let src = args.src.to_wide_cstring(vm)?; diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 049bf1da6..f6ab86413 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -7,6 +7,7 @@ use crate::{ convert::{IntoPyException, ToPyException, ToPyObject}, function::{ArgumentError, FromArgs, FuncArgs}, host_env::{crt_fd, posix::RawMode}, + ospath::OsPath, }; use core::marker::PhantomData; use std::{io, path::Path}; @@ -123,6 +124,18 @@ impl FromArgs for DirFd<'_, AVAILABLE, } } +#[derive(FromArgs)] +pub(crate) struct SymlinkArgs<'fd> { + pub src: OsPath, + pub dst: OsPath, + #[cfg_attr(any(unix, target_os = "wasi"), expect(unused))] + #[pyarg(flatten)] + pub target_is_directory: TargetIsDirectory, + #[cfg_attr(not(unix), expect(unused))] + #[pyarg(flatten)] + pub dir_fd: DirFd<'fd, { _os::SYMLINK_DIR_FD as usize }>, +} + #[derive(FromArgs)] pub(super) struct FollowSymlinks( #[pyarg(named, name = "follow_symlinks", default = true)] pub bool, diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 52904ecbc..41caab2dd 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -6,6 +6,7 @@ pub use rustpython_host_env::posix::set_inheritable; #[pymodule(name = "posix", with( super::os::_os, + super::posix_unix_like::_posix_unix_like, #[cfg(any( target_os = "linux", target_os = "netbsd", @@ -23,8 +24,7 @@ pub mod module { function::{ArgMapping, Either, KwArgs, OptionalArg}, ospath::{OsPath, OsPathOrFd}, stdlib::os::{ - _os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata, - warn_if_bool_fd, + _os, DirFd, FollowSymlinks, SupportFunc, SymlinkArgs, fs_metadata, warn_if_bool_fd, }, }; #[cfg(any( @@ -384,18 +384,6 @@ pub mod module { .map_err(|err| err.to_pyexception(vm)) } - #[pyattr] - fn environ(vm: &VirtualMachine) -> PyDictRef { - let environ = vm.ctx.new_dict(); - 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(); - } - - environ - } - #[pyfunction] fn _create_environ(vm: &VirtualMachine) -> PyDictRef { let environ = vm.ctx.new_dict(); @@ -407,16 +395,6 @@ pub mod module { environ } - #[derive(FromArgs)] - pub(super) struct SymlinkArgs<'fd> { - src: OsPath, - dst: OsPath, - #[pyarg(flatten)] - _target_is_directory: TargetIsDirectory, - #[pyarg(flatten)] - dir_fd: DirFd<'fd, { _os::SYMLINK_DIR_FD as usize }>, - } - #[pyfunction] pub(super) fn symlink(args: SymlinkArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { let src = args.src.into_cstring(vm)?; @@ -433,17 +411,6 @@ pub mod module { } } - #[pyfunction] - #[pyfunction(name = "unlink")] - fn remove( - path: OsPath, - dir_fd: DirFd<'_, { _os::UNLINK_DIR_FD as usize }>, - vm: &VirtualMachine, - ) -> PyResult<()> { - rustpython_host_env::posix::unlinkat(dir_fd.get_opt(), &path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) - } - #[cfg(not(target_os = "redox"))] #[pyfunction] fn fchdir(fd: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { diff --git a/crates/vm/src/stdlib/posix_compat.rs b/crates/vm/src/stdlib/posix_compat.rs index f9702c241..bd14f7870 100644 --- a/crates/vm/src/stdlib/posix_compat.rs +++ b/crates/vm/src/stdlib/posix_compat.rs @@ -4,38 +4,24 @@ pub(crate) use module::module_def; -#[pymodule(name = "posix", with(super::os::_os))] +#[pymodule(name = "posix", with( + super::os::_os, + #[cfg(any(unix, target_os = "wasi"))] + super::posix_unix_like::_posix_unix_like, +))] pub(crate) mod module { use crate::{ Py, PyObjectRef, PyResult, VirtualMachine, builtins::PyStrRef, ospath::OsPath, - stdlib::os::{_os, DirFd, SupportFunc, TargetIsDirectory}, + stdlib::os::{_os, DirFd, SupportFunc, SymlinkArgs, TargetIsDirectory}, }; - #[cfg(not(target_os = "wasi"))] - use {crate::convert::IntoPyException, std::fs}; - - #[cfg(target_os = "wasi")] - use crate::exceptions::OSErrorBuilder; - #[pyfunction] pub(super) fn access(_path: PyStrRef, _mode: u8, vm: &VirtualMachine) -> PyResult { os_unimpl("os.access", vm) } - #[cfg(target_os = "wasi")] - #[pyfunction] - #[pyfunction(name = "unlink")] - fn remove( - path: OsPath, - dir_fd: DirFd<'_, { _os::UNLINK_DIR_FD as usize }>, - vm: &VirtualMachine, - ) -> PyResult<()> { - rustpython_host_env::posix::unlinkat(dir_fd.get_opt(), &path) - .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) - } - #[cfg(not(target_os = "wasi"))] #[pyfunction] #[pyfunction(name = "unlink")] @@ -44,37 +30,11 @@ pub(crate) mod module { fs::remove_file(&path).map_err(|err| err.into_pyexception(vm)) } - #[derive(FromArgs)] - #[allow(unused)] - pub(super) struct SymlinkArgs<'a> { - src: OsPath, - dst: OsPath, - #[pyarg(flatten)] - _target_is_directory: TargetIsDirectory, - #[pyarg(flatten)] - _dir_fd: DirFd<'a, { _os::SYMLINK_DIR_FD as usize }>, - } - #[pyfunction] pub(super) fn symlink(_args: SymlinkArgs<'_>, vm: &VirtualMachine) -> PyResult<()> { os_unimpl("os.symlink", vm) } - #[cfg(target_os = "wasi")] - #[pyattr] - fn environ(vm: &VirtualMachine) -> crate::builtins::PyDictRef { - use rustpython_host_env::os::ffi::OsStringExt; - - let environ = vm.ctx.new_dict(); - 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(); - } - - environ - } - #[allow(dead_code)] fn os_unimpl(func: &str, vm: &VirtualMachine) -> PyResult { Err(vm.new_os_error(format!("{func} is not supported on this platform"))) diff --git a/crates/vm/src/stdlib/posix_unix_like.rs b/crates/vm/src/stdlib/posix_unix_like.rs new file mode 100644 index 000000000..6811510ef --- /dev/null +++ b/crates/vm/src/stdlib/posix_unix_like.rs @@ -0,0 +1,35 @@ +#[pymodule(sub)] +pub(crate) mod _posix_unix_like { + use rustpython_host_env::os::ffi::OsStringExt; + + use crate::{ + PyObjectRef, PyResult, VirtualMachine, + builtins::PyDictRef, + exceptions::OSErrorBuilder, + ospath::OsPath, + stdlib::os::{_os, DirFd}, + }; + + #[pyattr] + fn environ(vm: &VirtualMachine) -> PyDictRef { + let environ = vm.ctx.new_dict(); + 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(); + } + + environ + } + + #[pyfunction] + #[pyfunction(name = "unlink")] + fn remove( + path: OsPath, + dir_fd: DirFd<'_, { _os::UNLINK_DIR_FD as usize }>, + vm: &VirtualMachine, + ) -> PyResult<()> { + rustpython_host_env::posix::unlinkat(dir_fd.get_opt(), &path) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) + } +}