host_env: os.mkdir for Windows, Redox (#8128)

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.
This commit is contained in:
Joshua Megnauth authored and GitHub committed 2026-07-05 01:15:23 +09:00
1 parent 4caf959e58
commit f32e96ce90
7 files changed
+79 -62

No files matched your search

+2
View File
@@ -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]
+11 -11
View File
@@ -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<Owned> for OwnedFd {
fn from(fd: Owned) -> Self {
fd.inner
}
}
#[cfg(unix)]
#[cfg(any(unix, target_os = "wasi"))]
impl From<OwnedFd> 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<Borrowed<'fd>> for BorrowedFd<'fd> {
fn from(fd: Borrowed<'fd>) -> Self {
fd.inner
}
}
#[cfg(unix)]
#[cfg(any(unix, target_os = "wasi"))]
impl<'fd> From<BorrowedFd<'fd>> 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()
+3
View File
@@ -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)]
+10 -18
View File
@@ -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<crt_fd::Borrowed<'_>>,
path: &impl AsRef<Path>,
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)]
+10 -10
View File
@@ -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<crt_fd::Borrowed<'_>>,
path: &impl AsRef<Path>,
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<()> {
+22
View File
@@ -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<crt_fd::Borrowed<'_>>,
path: &impl AsRef<Path>,
_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)
}
+21 -23
View File
@@ -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<P: AsRef<Path>>(
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<i32>,
dir_fd: DirFd<'_, { MKDIR_DIR_FD as usize }>,
mode: OptionalArg<mode_t>,
#[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]