Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion crates/host_env/src/crt_fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,13 @@ pub fn wopen(path: &widestring::WideCStr, flags: i32, mode: i32) -> io::Result<O

#[cfg(all(any(unix, target_os = "wasi"), not(target_os = "redox")))]
pub fn openat(dir: Borrowed<'_>, path: &ffi::CStr, flags: i32, mode: i32) -> io::Result<Owned> {
cvt_fd(unsafe { c::openat(dir.as_raw(), path.as_ptr(), flags, mode) })
openat_raw(dir.as_raw(), path, flags, mode)
}

#[cfg(all(any(unix, target_os = "wasi"), not(target_os = "redox")))]
// Accept a descriptor number without asserting it stays open across calls.
pub fn openat_raw(dir: Raw, path: &ffi::CStr, flags: i32, mode: i32) -> io::Result<Owned> {
cvt_fd(unsafe { c::openat(dir, path.as_ptr(), flags, mode) })
}

pub fn fsync(fd: Borrowed<'_>) -> io::Result<()> {
Expand Down
61 changes: 61 additions & 0 deletions crates/host_env/src/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,67 @@ pub fn statvfs_fd(fd: i32) -> std::io::Result<StatVfsInfo> {
}
}

#[cfg(not(target_os = "redox"))]
pub fn mkfifo(path: &CStr, mode: libc::mode_t) -> std::io::Result<()> {
let ret = unsafe { libc::mkfifo(path.as_ptr(), mode) };
if ret == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}

#[cfg(target_vendor = "apple")]
type MkfifoAt = unsafe extern "C" fn(libc::c_int, *const libc::c_char, libc::mode_t) -> libc::c_int;

#[cfg(target_vendor = "apple")]
fn mkfifoat_symbol() -> Option<MkfifoAt> {
static MKFIFOAT: std::sync::OnceLock<Option<MkfifoAt>> = std::sync::OnceLock::new();
*MKFIFOAT.get_or_init(|| {
// mkfifoat was added in macOS 13 / iOS 16. Resolve it at runtime so
// ordinary mkfifo remains usable on older Apple systems.
let symbol = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"mkfifoat".as_ptr()) };
if symbol.is_null() {
None
} else {
// SAFETY: the system symbol has the MkfifoAt signature and stays
// loaded for the lifetime of the process.
Some(unsafe { core::mem::transmute::<*mut libc::c_void, MkfifoAt>(symbol) })
}
})
}

#[cfg(not(target_os = "redox"))]
pub fn has_mkfifoat() -> bool {
#[cfg(target_vendor = "apple")]
{
mkfifoat_symbol().is_some()
}
#[cfg(not(target_vendor = "apple"))]
{
!cfg!(target_os = "android")
}
}

#[cfg(not(any(target_os = "redox", target_os = "android")))]
pub fn mkfifoat(dir_fd: i32, path: &CStr, mode: libc::mode_t) -> std::io::Result<()> {
#[cfg(target_vendor = "apple")]
let mkfifoat = mkfifoat_symbol().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::Unsupported,
"mkfifoat unavailable on this platform",
)
})?;
#[cfg(not(target_vendor = "apple"))]
let mkfifoat = libc::mkfifoat;
let ret = unsafe { mkfifoat(dir_fd, path.as_ptr(), mode) };
if ret == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}

#[cfg(not(target_os = "redox"))]
pub fn mknod(path: &CStr, mode: libc::mode_t, device: libc::dev_t) -> std::io::Result<()> {
let ret = unsafe { libc::mknod(path.as_ptr(), mode, device) };
Expand Down
19 changes: 12 additions & 7 deletions crates/vm/src/stdlib/_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5930,7 +5930,7 @@ mod _io {
#[pymodule]
mod fileio {
use super::{_io::*, Offset, iobase_finalize};
use crate::host_env::crt_fd;
use crate::host_env::{crt_fd, os::ErrorExt};
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBaseExceptionRef, PyUtf8Str, PyUtf8StrRef},
Expand Down Expand Up @@ -6044,14 +6044,19 @@ mod fileio {
} else {
let path = OsPath::try_from_fspath(name.clone(), vm)?;
#[cfg(any(unix, target_os = "wasi"))]
let fd = host_io::open_path(&path.clone().into_cstring(vm)?, flags, 0o666);
let raw_path = path.clone().into_cstring(vm)?;
#[cfg(windows)]
let fd = host_io::open_path(&path.to_wide_cstring(vm)?, flags, 0o666);
let raw_path = path.to_wide_cstring(vm)?;
let filename = OsPathOrFd::Path(path);
match fd {
Ok(fd) => (fd.into_raw(), Some(filename)),
Err(e) => {
return Err(OSErrorBuilder::with_filename_from_errno(&e, filename, vm));
loop {
match vm.allow_threads(|| host_io::open_path(&raw_path, flags, 0o666)) {
Ok(fd) => break (fd.into_raw(), Some(filename)),
Err(e) if e.posix_errno() == libc::EINTR => vm.check_signals()?,
Err(e) => {
return Err(OSErrorBuilder::with_filename_from_errno(
&e, filename, vm,
));
}
}
}
}
Expand Down
118 changes: 88 additions & 30 deletions crates/vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ impl ToPyObject for crt_fd::Borrowed<'_> {

#[pymodule(sub, name = "posix")]
pub(super) mod _os {
use super::{DirFd, DstDirFd, FollowSymlinks, RawMode, SrcDirFd, SupportFunc};
use super::{
AT_FDCWD, DefaultDirFd, DirFd, DirFdKeyword, DstDirFd, FollowSymlinks, RawMode, SrcDirFd,
SupportFunc, warn_if_bool_fd,
};
#[cfg(not(windows))]
use crate::exceptions;
use crate::host_env::fileutils::StatStruct;
Expand All @@ -212,9 +215,11 @@ pub(super) mod _os {
},
class::PyClassDef,
common::lock::{OnceCell, PyRwLock},
convert::{IntoPyException, ToPyObject},
convert::{IntoPyException, ToPyException, ToPyObject},
exceptions::{OSErrorBuilder, ToOSErrorBuilder},
function::{ArgBytesLike, ArgMemoryBuffer, FsPath, FuncArgs},
function::{
ArgBytesLike, ArgMemoryBuffer, ArgumentError, FromArgs, FsPath, FuncArgs, Param,
},
host_env::crt_fd,
ospath::{OsPath, OsPathOrFd, OutputMode, PathConverter},
protocol::PyIterReturn,
Expand Down Expand Up @@ -276,55 +281,108 @@ pub(super) mod _os {
crt_fd::closerange(fd_low, fd_high);
}

// Signal callbacks can close this descriptor between open attempts.
#[cfg(any(unix, windows, target_os = "wasi"))]
struct OpenDirFd(crt_fd::Raw);

#[cfg(any(unix, windows, target_os = "wasi"))]
impl FromArgs for OpenDirFd {
const PARAMS: Option<&'static [Param]> = Some(DefaultDirFd::PARAMS);

fn from_args(vm: &VirtualMachine, args: &mut FuncArgs) -> Result<Self, ArgumentError> {
let fd = match args.take_keyword("dir_fd") {
Some(o) if vm.is_none(&o) => AT_FDCWD,
None => AT_FDCWD,
Some(o) => {
warn_if_bool_fd(&o, vm).map_err(Into::<ArgumentError>::into)?;
let fd = o.try_index_opt(vm).unwrap_or_else(|| {
Err(vm.new_type_error(format!(
"argument should be integer or None, not {}",
o.class().name()
)))
})?;
fd.try_to_primitive(vm)?
}
};
if fd == -1 {
return Err(io::Error::from_raw_os_error(libc::EBADF)
.to_pyexception(vm)
.into());
}
if !OPEN_DIR_FD && fd != AT_FDCWD {
return Err(vm
.new_not_implemented_error("dir_fd unavailable on this platform")
.into());
}
Ok(Self(fd))
}
}

#[cfg(any(unix, windows, target_os = "wasi"))]
#[derive(FromArgs)]
struct OpenArgs<'fd> {
struct OpenArgs {
path: OsPath,
flags: i32,
#[pyarg(any, default = 0o777)]
mode: i32,
#[pyarg(flatten)]
dir_fd: DirFd<'fd, { OPEN_DIR_FD as usize }>,
dir_fd: OpenDirFd,
}

#[pyfunction]
fn open(args: OpenArgs<'_>, vm: &VirtualMachine) -> PyResult<crt_fd::Owned> {
os_open(args.path, args.flags, args.mode, args.dir_fd, vm)
fn open(args: OpenArgs, vm: &VirtualMachine) -> PyResult<crt_fd::Owned> {
os_open(args.path, args.flags, args.mode, args.dir_fd.0, vm)
}

#[cfg(any(unix, windows, target_os = "wasi"))]
pub(crate) fn os_open(
name: OsPath,
flags: i32,
mode: i32,
dir_fd: DirFd<'_, { OPEN_DIR_FD as usize }>,
dir_fd: crt_fd::Raw,
vm: &VirtualMachine,
) -> PyResult<crt_fd::Owned> {
use crate::host_env::os::ErrorExt;

#[cfg(windows)]
let fd = {
let [] = dir_fd.0;
let name = name.to_wide_cstring(vm)?;
let flags = flags | crate::host_env::os::O_NOINHERIT;
crt_fd::wopen(&name, flags, mode)
};
let raw_name = name.to_wide_cstring(vm)?;
#[cfg(not(windows))]
let fd = {
let name = name.clone().into_cstring(vm)?;
#[cfg(not(target_os = "wasi"))]
let flags = flags | crate::host_env::os::O_CLOEXEC;
#[cfg(not(target_os = "redox"))]
if let Some(dir_fd) = dir_fd.get_opt() {
crt_fd::openat(dir_fd, &name, flags, mode)
} else {
crt_fd::open(&name, flags, mode)
}
#[cfg(target_os = "redox")]
{
let [] = dir_fd.0;
crt_fd::open(&name, flags, mode)
let raw_name = name.clone().into_cstring(vm)?;
#[cfg(windows)]
let flags = flags | crate::host_env::os::O_NOINHERIT;
#[cfg(all(not(windows), not(target_os = "wasi")))]
let flags = flags | crate::host_env::os::O_CLOEXEC;

loop {
let fd = vm.allow_threads(|| {
#[cfg(windows)]
{
let _ = dir_fd;
crt_fd::wopen(&raw_name, flags, mode)
}
#[cfg(not(windows))]
{
#[cfg(not(target_os = "redox"))]
if dir_fd != AT_FDCWD {
crt_fd::openat_raw(dir_fd, &raw_name, flags, mode)
} else {
crt_fd::open(&raw_name, flags, mode)
}
#[cfg(target_os = "redox")]
{
let _ = dir_fd;
crt_fd::open(&raw_name, flags, mode)
}
}
});
match fd {
Ok(fd) => return Ok(fd),
Err(err) if err.posix_errno() == libc::EINTR => vm.check_signals()?,
Err(err) => {
return Err(OSErrorBuilder::with_filename_from_errno(&err, name, vm));
}
}
};
fd.map_err(|err| OSErrorBuilder::with_filename_from_errno(&err, name, vm))
}
}

#[derive(FromArgs)]
Expand Down
92 changes: 92 additions & 0 deletions crates/vm/src/stdlib/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,91 @@ pub mod module {
}
}

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
struct MkfifoPath(OsPath);

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
impl TryFromObject for MkfifoPath {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
crate::ospath::PathConverter::new()
.function("mkfifo")
.try_path(obj, vm)
.map(Self)
}
}

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
struct MkfifoMode(i32);

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
impl TryFromObject for MkfifoMode {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let mode = obj.try_index(vm)?;
i32::try_from(mode.as_bigint())
.map(Self)
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C int"))
}
}

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
#[derive(FromArgs)]
struct MkfifoArgs {
#[pyarg(any)]
path: MkfifoPath,
#[pyarg(any, default = MkfifoMode(0o666), py_default = "438")]
mode: MkfifoMode,
#[pyarg(named, optional, py_default = "None")]
dir_fd: OptionalArg<PyObjectRef>,
}

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
#[pyfunction]
fn mkfifo(args: MkfifoArgs, vm: &VirtualMachine) -> PyResult<()> {
let c_path = args.path.0.into_cstring(vm)?;
let mode = args.mode.0 as libc::mode_t;
// Keep a raw C int: even -1 is valid with an absolute path, for which
// mkfifoat ignores dir_fd. BorrowedFd cannot represent that value.
let dir_fd = match args.dir_fd {
OptionalArg::Present(fd) if !vm.is_none(&fd) => {
warn_if_bool_fd(&fd, vm)?;
let fd = fd.try_index_opt(vm).unwrap_or_else(|| {
Err(vm.new_type_error(format!(
"argument should be integer or None, not {}",
fd.class().name()
)))
})?;
let fd = i32::try_from(fd.as_bigint()).map_err(|_| {
let message = if fd.as_bigint().sign() == malachite_bigint::Sign::Minus {
"fd is less than minimum"
} else {
"fd is greater than maximum"
};
vm.new_overflow_error(message)
})?;
(fd != rustpython_host_env::os::AT_FDCWD).then_some(fd)
}
_ => None,
};
if dir_fd.is_some() && !host_posix::has_mkfifoat() {
return Err(vm.new_not_implemented_error("dir_fd unavailable on this platform"));
}

loop {
let result = vm.allow_threads(|| {
#[cfg(not(target_os = "android"))]
if let Some(dir_fd) = dir_fd {
return host_posix::mkfifoat(dir_fd, &c_path, mode);
}
host_posix::mkfifo(&c_path, mode)
});
match result {
Ok(()) => return Ok(()),
Err(err) if err.raw_os_error() == Some(libc::EINTR) => vm.check_signals()?,
Err(err) => return Err(err.into_pyexception(vm)),
}
}
}

#[cfg(not(target_os = "redox"))]
const MKNOD_DIR_FD: bool = cfg!(not(target_vendor = "apple"));

Expand Down Expand Up @@ -2243,6 +2328,13 @@ pub mod module {
SupportFunc::new("lchown", None, None, None),
#[cfg(not(target_os = "redox"))]
SupportFunc::new("fchown", Some(true), None, Some(true)),
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
SupportFunc::new(
"mkfifo",
Some(false),
Some(host_posix::has_mkfifoat()),
Some(false),
),
#[cfg(not(target_os = "redox"))]
SupportFunc::new("mknod", Some(true), Some(MKNOD_DIR_FD), Some(false)),
SupportFunc::new("umask", Some(false), Some(false), Some(false)),
Expand Down
Loading
Loading