From 6935f1423e52189679d7cdf6d385978fa75b04df Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Wed, 17 Jun 2026 16:35:48 -0400 Subject: [PATCH] host_env: os.mkdir for Windows, Redox Python supports `mkdir` on Windows. I deferred to calling Rust's implementation for now. However, like `rename`, Python's implementation supports additional features that are currently unsupported on RustPython. I added a note for future reference. Redox supports `mkdirat` which significantly cleans up the implementation. --- crates/host_env/Cargo.toml | 2 ++ crates/host_env/src/crt_fd.rs | 22 +++++++------- crates/host_env/src/lib.rs | 3 ++ crates/host_env/src/posix.rs | 28 +++++++----------- crates/host_env/src/posix_wasi.rs | 20 ++++++------- crates/host_env/src/posix_windows.rs | 22 ++++++++++++++ crates/vm/src/stdlib/os.rs | 44 +++++++++++++--------------- 7 files changed, 79 insertions(+), 62 deletions(-) create mode 100644 crates/host_env/src/posix_windows.rs diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml index 718e3f41aab..91a56ea91df 100644 --- a/crates/host_env/Cargo.toml +++ b/crates/host_env/Cargo.toml @@ -20,6 +20,8 @@ paste = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true } + +[target.'cfg(any(unix, target_os = "macos", target_os = "redox", target_os = "wasi"))'.dependencies] rustix = { workspace = true } [target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies] diff --git a/crates/host_env/src/crt_fd.rs b/crates/host_env/src/crt_fd.rs index ee21934bee2..b681081be89 100644 --- a/crates/host_env/src/crt_fd.rs +++ b/crates/host_env/src/crt_fd.rs @@ -5,7 +5,7 @@ use alloc::fmt; use core::cmp; use std::{ffi, io}; -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] use std::os::fd::AsFd; #[cfg(not(windows))] use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; @@ -209,42 +209,42 @@ impl Owned { } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl From for OwnedFd { fn from(fd: Owned) -> Self { fd.inner } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl From for Owned { fn from(fd: OwnedFd) -> Self { Self { inner: fd } } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl AsFd for Owned { fn as_fd(&self) -> BorrowedFd<'_> { self.inner.as_fd() } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl AsRawFd for Owned { fn as_raw_fd(&self) -> RawFd { self.as_raw() } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl FromRawFd for Owned { unsafe fn from_raw_fd(fd: RawFd) -> Self { unsafe { Self::from_raw(fd) } } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl IntoRawFd for Owned { fn into_raw_fd(self) -> RawFd { self.into_raw() @@ -287,28 +287,28 @@ impl Borrowed<'_> { } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl<'fd> From> for BorrowedFd<'fd> { fn from(fd: Borrowed<'fd>) -> Self { fd.inner } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl<'fd> From> for Borrowed<'fd> { fn from(fd: BorrowedFd<'fd>) -> Self { Self { inner: fd } } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl AsFd for Borrowed<'_> { fn as_fd(&self) -> BorrowedFd<'_> { self.inner.as_fd() } } -#[cfg(unix)] +#[cfg(any(unix, target_os = "wasi"))] impl AsRawFd for Borrowed<'_> { fn as_raw_fd(&self) -> RawFd { self.as_raw() diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs index 99f67b2b496..15f816ea8f4 100644 --- a/crates/host_env/src/lib.rs +++ b/crates/host_env/src/lib.rs @@ -52,6 +52,9 @@ pub mod posix; #[cfg(target_os = "wasi")] #[path = "posix_wasi.rs"] pub mod posix; +#[cfg(windows)] +#[path = "posix_windows.rs"] +pub mod posix; #[cfg(unix)] pub mod pwd; #[cfg(unix)] diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index f10e99ac938..1239d3526c4 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -10,6 +10,8 @@ use std::os::fd::FromRawFd; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd}; use std::path::Path; +use crate::crt_fd; + pub struct UnameInfo { pub sysname: String, pub nodename: String, @@ -174,24 +176,14 @@ pub fn fcopyfile(in_fd: i32, out_fd: i32, flags: u32) -> std::io::Result<()> { } } -#[cfg(not(windows))] -pub fn make_dir(path: &CStr, mode: u32) -> std::io::Result<()> { - let ret = unsafe { libc::mkdir(path.as_ptr(), mode as _) }; - if ret < 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(()) - } -} - -#[cfg(all(not(windows), not(target_os = "redox")))] -pub fn make_dir_at(dir_fd: i32, path: &CStr, mode: u32) -> std::io::Result<()> { - let ret = unsafe { libc::mkdirat(dir_fd, path.as_ptr(), mode as _) }; - if ret < 0 { - Err(std::io::Error::last_os_error()) - } else { - Ok(()) - } +#[cfg(any(unix, target_os = "wasi"))] +pub fn make_dir( + dir_fd: Option>, + path: &impl AsRef, + mode: libc::mode_t, +) -> std::io::Result<()> { + let dir_fd = dir_fd.as_ref().map_or(rustix::fs::CWD, AsFd::as_fd); + rustix::fs::mkdirat(dir_fd, path.as_ref(), mode.into()).map_err(Into::into) } #[cfg(unix)] diff --git a/crates/host_env/src/posix_wasi.rs b/crates/host_env/src/posix_wasi.rs index d4eff8c6866..f1883fccd58 100644 --- a/crates/host_env/src/posix_wasi.rs +++ b/crates/host_env/src/posix_wasi.rs @@ -1,17 +1,17 @@ use alloc::ffi::CString; use core::{ffi::CStr, time::Duration}; -use std::{ffi::OsStr, io}; +use rustix::fd::AsFd; +use std::{ffi::OsStr, io, path::Path}; -use crate::os::CheckLibcResult; +use crate::{crt_fd, os::CheckLibcResult}; -pub fn make_dir(path: &CStr, mode: u32) -> io::Result<()> { - unsafe { libc::mkdir(path.as_ptr(), mode as _) }.check_libc_neg()?; - Ok(()) -} - -pub fn make_dir_at(dir_fd: i32, path: &CStr, mode: u32) -> io::Result<()> { - unsafe { libc::mkdirat(dir_fd, path.as_ptr(), mode as _) }.check_libc_neg()?; - Ok(()) +pub fn make_dir( + dir_fd: Option>, + path: &impl AsRef, + mode: libc::mode_t, +) -> std::io::Result<()> { + let dir_fd = dir_fd.as_ref().map_or(rustix::fs::CWD, AsFd::as_fd); + rustix::fs::mkdirat(dir_fd, path.as_ref(), mode.into()).map_err(Into::into) } pub fn remove_dir_at(dir_fd: i32, path: &CStr) -> io::Result<()> { diff --git a/crates/host_env/src/posix_windows.rs b/crates/host_env/src/posix_windows.rs new file mode 100644 index 00000000000..ac9e2cfa808 --- /dev/null +++ b/crates/host_env/src/posix_windows.rs @@ -0,0 +1,22 @@ +//! POSIX-compatible API for Windows. +//! +//! Python wraps POSIX syscalls such as `mkdir` and `open`. Windows doesn't directly implement +//! these syscalls, but they can be emulated with a mix of the Windows API and the Rust standard +//! library, the latter of which calls the former. + +use std::{fs, io, path::Path}; + +use crate::crt_fd; + +#[expect(non_camel_case_types)] +pub type mode_t = u32; + +pub fn make_dir( + dir_fd: Option>, + path: &impl AsRef, + _mode: mode_t, +) -> io::Result<()> { + debug_assert!(dir_fd.is_none()); + // TODO: On Windows, Python has an override if the mode is 0o700 + fs::create_dir(path) +} diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 4a1cbe2aecd..db18b6bc720 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -10,6 +10,12 @@ use crate::{ }; use std::{io, path::Path}; +#[cfg(not(windows))] +use libc::mode_t; + +#[cfg(windows)] +use crate::host_env::posix::mode_t; + pub(crate) fn fs_metadata>( path: P, follow_symlink: bool, @@ -29,7 +35,7 @@ pub struct TargetIsDirectory { } cfg_select! { - all(any(unix, target_os = "wasi"), not(target_os = "redox")) => { + any(unix, target_os = "wasi") => { use libc::AT_FDCWD; } _ => { @@ -154,7 +160,7 @@ impl ToPyObject for crt_fd::Borrowed<'_> { #[pymodule(sub)] pub(super) mod _os { - use super::{DirFd, FollowSymlinks, SupportFunc}; + use super::{DirFd, FollowSymlinks, SupportFunc, mode_t}; use crate::host_env::fileutils::StatStruct; #[cfg(any(unix, windows))] use crate::utils::ToCString; @@ -184,7 +190,7 @@ pub(super) mod _os { 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"))); + pub(crate) const MKDIR_DIR_FD: bool = cfg!(any(unix, target_os = "wasi")); const STAT_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); pub(crate) const SYMLINK_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); @@ -338,32 +344,24 @@ pub(super) mod _os { } } - #[cfg(not(windows))] #[pyfunction] fn mkdir( path: OsPath, - mode: OptionalArg, - dir_fd: DirFd<'_, { MKDIR_DIR_FD as usize }>, + mode: OptionalArg, + #[cfg_attr(not(any(unix, target_os = "wasi")), expect(unused_variables))] dir_fd: DirFd< + '_, + { MKDIR_DIR_FD as usize }, + >, vm: &VirtualMachine, ) -> PyResult<()> { let mode = mode.unwrap_or(0o777); - let c_path = path.clone().into_cstring(vm)?; - #[cfg(not(target_os = "redox"))] - if let Some(fd) = dir_fd.raw_opt() { - return if let Err(err) = - crate::host_env::posix::make_dir_at(fd, c_path.as_c_str(), mode as u32) - { - Err(OSErrorBuilder::with_filename(&err, path, vm)) - } else { - Ok(()) - }; - } - #[cfg(target_os = "redox")] - let [] = dir_fd.0; - if let Err(err) = crate::host_env::posix::make_dir(c_path.as_c_str(), mode as u32) { - return Err(OSErrorBuilder::with_filename(&err, path, vm)); - } - Ok(()) + #[cfg(any(unix, target_os = "wasi"))] + let dir_fd = dir_fd.get_opt(); + #[cfg(not(any(unix, target_os = "wasi")))] + let dir_fd = None; + + crate::host_env::posix::make_dir(dir_fd, &path.path, mode) + .map_err(|err| OSErrorBuilder::with_filename(&err, path, vm)) } #[pyfunction]