From 199eeee815baa069ddfc4ffadf0811f8357347e9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 22 Sep 2021 13:29:32 +0900 Subject: [PATCH 1/6] split `posix` from `os` --- vm/src/stdlib/mod.rs | 2 + vm/src/stdlib/os.rs | 1871 ++---------------------------- vm/src/stdlib/posix.rs | 1662 ++++++++++++++++++++++++++ vm/src/stdlib/posixsubprocess.rs | 17 +- 4 files changed, 1779 insertions(+), 1773 deletions(-) create mode 100644 vm/src/stdlib/posix.rs diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 6b513d6f7e..0405e8503e 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -52,6 +52,8 @@ mod zlib; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] #[macro_use] pub(crate) mod os; +#[cfg(unix)] +pub(crate) mod posix; #[cfg(not(target_arch = "wasm32"))] mod faulthandler; diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index bcc50da1b4..406905c327 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -3,17 +3,15 @@ use crate::common::lock::PyRwLock; use crate::crt_fd::{Fd, Offset}; use crate::{ buffer::PyBuffer, - builtins::{ - int, PyBytes, PyBytesRef, PyDictRef, PySet, PyStr, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, - }, + builtins::{int, PyBytes, PyBytesRef, PySet, PyStr, PyStrRef, PyTuple, PyTupleRef, PyTypeRef}, byteslike::ArgBytesLike, exceptions::{IntoPyException, PyBaseExceptionRef}, function::{ArgumentError, FromArgs, FuncArgs, OptionalArg}, slots::PyIter, utils::Either, vm::{ReprGuard, VirtualMachine}, - IntoPyObject, ItemProtocol, PyObjectRef, PyRef, PyResult, PyStructSequence, PyValue, - StaticType, TryFromBorrowedObject, TryFromObject, TypeProtocol, + IntoPyObject, PyObjectRef, PyRef, PyResult, PyStructSequence, PyValue, StaticType, + TryFromBorrowedObject, TryFromObject, TypeProtocol, }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; @@ -21,13 +19,9 @@ use num_bigint::BigInt; use std::ffi; use std::fs::OpenOptions; use std::io::{self, ErrorKind, Read, Write}; -#[cfg(unix)] -use std::os::unix::fs::DirEntryExt; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; use std::{env, fs}; -#[cfg(unix)] -use strum_macros::EnumString; #[cfg(unix)] use std::os::unix::ffi as ffi_ext; @@ -99,7 +93,10 @@ impl PyPathLike { } } -fn fs_metadata>(path: P, follow_symlink: bool) -> io::Result { +pub(super) fn fs_metadata>( + path: P, + follow_symlink: bool, +) -> io::Result { if follow_symlink { fs::metadata(path.as_ref()) } else { @@ -302,7 +299,7 @@ const DEFAULT_DIR_FD: Fd = Fd(AT_FDCWD); // XXX: AVAILABLE should be a bool, but we can't yet have it as a bool and just cast it to usize #[derive(Copy, Clone)] -pub struct DirFd([Fd; AVAILABLE]); +pub struct DirFd(pub(crate) [Fd; AVAILABLE]); impl Default for DirFd { fn default() -> Self { @@ -314,12 +311,12 @@ impl Default for DirFd { #[allow(unused)] impl DirFd<1> { #[inline(always)] - fn fd_opt(&self) -> Option { + pub(crate) fn fd_opt(&self) -> Option { self.get_opt().map(Fd) } #[inline] - fn get_opt(&self) -> Option { + pub(crate) fn get_opt(&self) -> Option { let fd = self.fd(); if fd == DEFAULT_DIR_FD { None @@ -329,7 +326,7 @@ impl DirFd<1> { } #[inline(always)] - fn fd(&self) -> Fd { + pub(crate) fn fd(&self) -> Fd { self.0[0] } } @@ -360,10 +357,15 @@ impl FromArgs for DirFd { } #[derive(FromArgs)] -struct FollowSymlinks(#[pyarg(named, name = "follow_symlinks", default = "true")] bool); +pub(super) struct FollowSymlinks( + #[pyarg(named, name = "follow_symlinks", default = "true")] pub bool, +); + +#[cfg(unix)] +use super::posix::bytes_as_osstr; #[cfg(unix)] -use posix::bytes_as_osstr; +use super::posix::posix as platform; #[cfg(not(unix))] fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsStr> { @@ -399,30 +401,21 @@ fn os_unimpl(func: &str, vm: &VirtualMachine) -> PyResult { } #[pymodule(name = "os")] -mod _os { +pub(super) mod _os { use super::*; use rustpython_common::lock::OnceCell; + const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); + const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); + const STAT_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); + const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); + #[pyattr] use libc::{ O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, }; - #[cfg(not(any(windows, target_os = "wasi")))] - #[pyattr] - use libc::{PRIO_PGRP, PRIO_PROCESS, PRIO_USER}; - #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] - #[pyattr] - use libc::{SEEK_DATA, SEEK_HOLE}; - #[pyattr] - pub(super) const F_OK: u8 = 0; - #[pyattr] - pub(super) const R_OK: u8 = 4; - #[pyattr] - pub(super) const W_OK: u8 = 2; - #[pyattr] - pub(super) const X_OK: u8 = 1; #[pyfunction] fn close(fileno: i32, vm: &VirtualMachine) -> PyResult<()> { @@ -449,9 +442,6 @@ mod _os { dir_fd: DirFd<{ OPEN_DIR_FD as usize }>, } - #[cfg(any(unix, windows, target_os = "wasi"))] - const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); - #[pyfunction] fn open(args: OpenArgs, vm: &VirtualMachine) -> PyResult { os_open(args.path, args.flags, args.mode, args.dir_fd, vm) @@ -493,103 +483,6 @@ mod _os { fd.map(|fd| fd.0).map_err(|e| e.into_pyexception(vm)) } - #[cfg(any(target_os = "linux", target_os = "macos"))] - #[derive(FromArgs)] - struct SendFileArgs { - #[pyarg(any)] - out_fd: i32, - #[pyarg(any)] - in_fd: i32, - #[pyarg(any)] - offset: Offset, - #[pyarg(any)] - count: i64, - #[cfg(target_os = "macos")] - #[pyarg(any, optional)] - headers: OptionalArg, - #[cfg(target_os = "macos")] - #[pyarg(any, optional)] - trailers: OptionalArg, - #[cfg(target_os = "macos")] - #[allow(dead_code)] - #[pyarg(any, default)] - // TODO: not implemented - flags: OptionalArg, - } - - #[cfg(target_os = "linux")] - #[pyfunction] - fn sendfile(args: SendFileArgs, vm: &VirtualMachine) -> PyResult { - let mut file_offset = args.offset; - - let res = nix::sys::sendfile::sendfile( - args.out_fd, - args.in_fd, - Some(&mut file_offset), - args.count as usize, - ) - .map_err(|err| err.into_pyexception(vm))?; - Ok(vm.ctx.new_int(res as u64)) - } - - #[cfg(target_os = "macos")] - fn _extract_vec_bytes( - x: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult>> { - let inner = match x.into_option() { - Some(v) => { - let v = vm.extract_elements::(&v)?; - if v.is_empty() { - None - } else { - Some(v) - } - } - None => None, - }; - Ok(inner) - } - - #[cfg(target_os = "macos")] - #[pyfunction] - fn sendfile(args: SendFileArgs, vm: &VirtualMachine) -> PyResult { - let headers = _extract_vec_bytes(args.headers, vm)?; - let count = headers - .as_ref() - .map(|v| v.iter().map(|s| s.len()).sum()) - .unwrap_or(0) as i64 - + args.count; - - let headers = headers - .as_ref() - .map(|v| v.iter().map(|b| b.borrow_buf()).collect::>()); - let headers = headers - .as_ref() - .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); - let headers = headers.as_deref(); - - let trailers = _extract_vec_bytes(args.trailers, vm)?; - let trailers = trailers - .as_ref() - .map(|v| v.iter().map(|b| b.borrow_buf()).collect::>()); - let trailers = trailers - .as_ref() - .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); - let trailers = trailers.as_deref(); - - let (res, written) = nix::sys::sendfile::sendfile( - args.in_fd, - args.out_fd, - args.offset, - Some(count), - headers, - trailers, - ); - res.map_err(|err| err.into_pyexception(vm))?; - Ok(vm.ctx.new_int(written as u64)) - } - #[pyfunction] fn fsync(fd: i32, vm: &VirtualMachine) -> PyResult<()> { Fd(fd).fsync().map_err(|err| err.into_pyexception(vm)) @@ -632,8 +525,7 @@ mod _os { res.map_err(|err| err.into_pyexception(vm)) } - const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); - + #[cfg(not(windows))] #[pyfunction] fn mkdir( path: PyPathLike, @@ -642,39 +534,21 @@ mod _os { vm: &VirtualMachine, ) -> PyResult<()> { let mode = mode.unwrap_or(0o777); - #[cfg(windows)] - { - let [] = dir_fd.0; - let _ = mode; - let wide = path.to_widecstring(vm)?; - let res = unsafe { - winapi::um::fileapi::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) - }; - if res == 0 { - Err(errno_err(vm)) - } else { - Ok(()) - } + let path = path.into_cstring(vm)?; + #[cfg(not(target_os = "redox"))] + if let Some(fd) = dir_fd.get_opt() { + let res = unsafe { libc::mkdirat(fd, path.as_ptr(), mode as _) }; + let res = if res < 0 { Err(errno_err(vm)) } else { Ok(()) }; + return res; } - #[cfg(not(windows))] - { - let path = path.into_cstring(vm)?; - #[cfg(not(target_os = "redox"))] - if let Some(fd) = dir_fd.get_opt() { - let res = unsafe { libc::mkdirat(fd, path.as_ptr(), mode as _) }; - let res = if res < 0 { Err(errno_err(vm)) } else { Ok(()) }; - return res; - } - #[cfg(target_os = "redox")] - let [] = dir_fd.0; - let res = unsafe { libc::mkdir(path.as_ptr(), mode as _) }; - if res < 0 { - Err(errno_err(vm)) - } else { - Ok(()) - } + #[cfg(target_os = "redox")] + let [] = dir_fd.0; + let res = unsafe { libc::mkdir(path.as_ptr(), mode as _) }; + if res < 0 { + Err(errno_err(vm)) + } else { + Ok(()) } - // fs::create_dir(path).map_err(|err| err.into_pyexception(vm)) } #[pyfunction] @@ -692,10 +566,7 @@ mod _os { #[pyfunction] fn listdir(path: OptionalArg, vm: &VirtualMachine) -> PyResult { - let path = match path { - OptionalArg::Present(path) => path, - OptionalArg::Missing => PathOrFd::Path(PyPathLike::new_str(".")), - }; + let path = path.unwrap_or_else(|| PathOrFd::Path(PyPathLike::new_str("."))); let list = match path { PathOrFd::Path(path) => { let dir_iter = fs::read_dir(&path).map_err(|err| err.into_pyexception(vm))?; @@ -909,6 +780,7 @@ mod _os { #[cfg(unix)] #[pymethod] fn inode(&self, _vm: &VirtualMachine) -> PyResult { + use std::os::unix::fs::DirEntryExt; Ok(self.entry.ino()) } @@ -1185,8 +1057,6 @@ mod _os { }) } - const STAT_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); - fn stat_inner( file: PathOrFd, dir_fd: DirFd<{ STAT_DIR_FD as usize }>, @@ -1379,8 +1249,6 @@ mod _os { fs::hard_link(src.path, dst.path).map_err(|err| err.into_pyexception(vm)) } - const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); - #[derive(FromArgs)] struct UtimeArgs { #[pyarg(any)] @@ -1716,7 +1584,7 @@ mod _os { #[pyclass(module = "os", name = "terminal_size")] #[derive(PyStructSequence)] #[allow(dead_code)] - pub(super) struct PyTerminalSize { + pub(crate) struct PyTerminalSize { pub columns: usize, pub lines: usize, } @@ -1726,7 +1594,7 @@ mod _os { #[pyattr] #[pyclass(module = "os", name = "uname_result")] #[derive(Debug, PyStructSequence)] - pub(super) struct UnameResult { + pub(crate) struct UnameResult { pub sysname: String, pub nodename: String, pub release: String, @@ -1737,229 +1605,6 @@ mod _os { #[pyimpl(with(PyStructSequence))] impl UnameResult {} - #[cfg(unix)] - struct ConfName(i32); - - #[cfg(unix)] - impl TryFromObject for ConfName { - fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let i = match obj.downcast::() { - Ok(int) => int::try_to_primitive(int.as_bigint(), vm)?, - Err(obj) => { - let s = PyStrRef::try_from_object(vm, obj)?; - s.as_str().parse::().map_err(|_| { - vm.new_value_error("unrecognized configuration name".to_string()) - })? as i32 - } - }; - Ok(Self(i)) - } - } - - // Copy from [nix::unistd::PathconfVar](https://docs.rs/nix/0.21.0/nix/unistd/enum.PathconfVar.html) - // Change enum name to fit python doc - #[cfg(unix)] - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumString)] - #[repr(i32)] - #[allow(non_camel_case_types)] - pub enum PathconfVar { - #[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox" - ))] - /// Minimum number of bits needed to represent, as a signed integer value, - /// the maximum size of a regular file allowed in the specified directory. - PC_FILESIZEBITS = libc::_PC_FILESIZEBITS, - /// Maximum number of links to a single file. - PC_LINK_MAX = libc::_PC_LINK_MAX, - /// Maximum number of bytes in a terminal canonical input line. - PC_MAX_CANON = libc::_PC_MAX_CANON, - /// Minimum number of bytes for which space is available in a terminal input - /// queue; therefore, the maximum number of bytes a conforming application - /// may require to be typed as input before reading them. - PC_MAX_INPUT = libc::_PC_MAX_INPUT, - /// Maximum number of bytes in a filename (not including the terminating - /// null of a filename string). - PC_NAME_MAX = libc::_PC_NAME_MAX, - /// Maximum number of bytes the implementation will store as a pathname in a - /// user-supplied buffer of unspecified size, including the terminating null - /// character. Minimum number the implementation will accept as the maximum - /// number of bytes in a pathname. - PC_PATH_MAX = libc::_PC_PATH_MAX, - /// Maximum number of bytes that is guaranteed to be atomic when writing to - /// a pipe. - PC_PIPE_BUF = libc::_PC_PIPE_BUF, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris" - ))] - /// Symbolic links can be created. - PC_2_SYMLINKS = libc::_PC_2_SYMLINKS, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd", - target_os = "redox" - ))] - /// Minimum number of bytes of storage actually allocated for any portion of - /// a file. - PC_ALLOC_SIZE_MIN = libc::_PC_ALLOC_SIZE_MIN, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - ))] - /// Recommended increment for file transfer sizes between the - /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values. - PC_REC_INCR_XFER_SIZE = libc::_PC_REC_INCR_XFER_SIZE, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd", - target_os = "redox" - ))] - /// Maximum recommended file transfer size. - PC_REC_MAX_XFER_SIZE = libc::_PC_REC_MAX_XFER_SIZE, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd", - target_os = "redox" - ))] - /// Minimum recommended file transfer size. - PC_REC_MIN_XFER_SIZE = libc::_PC_REC_MIN_XFER_SIZE, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd", - target_os = "redox" - ))] - /// Recommended file transfer buffer alignment. - PC_REC_XFER_ALIGN = libc::_PC_REC_XFER_ALIGN, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris" - ))] - /// Maximum number of bytes in a symbolic link. - PC_SYMLINK_MAX = libc::_PC_SYMLINK_MAX, - /// The use of `chown` and `fchown` is restricted to a process with - /// appropriate privileges, and to changing the group ID of a file only to - /// the effective group ID of the process or to one of its supplementary - /// group IDs. - PC_CHOWN_RESTRICTED = libc::_PC_CHOWN_RESTRICTED, - /// Pathname components longer than {NAME_MAX} generate an error. - PC_NO_TRUNC = libc::_PC_NO_TRUNC, - /// This symbol shall be defined to be the value of a character that shall - /// disable terminal special character handling. - PC_VDISABLE = libc::_PC_VDISABLE, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris" - ))] - /// Asynchronous input or output operations may be performed for the - /// associated file. - PC_ASYNC_IO = libc::_PC_ASYNC_IO, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris" - ))] - /// Prioritized input or output operations may be performed for the - /// associated file. - PC_PRIO_IO = libc::_PC_PRIO_IO, - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "illumos", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris" - ))] - /// Synchronized input or output operations may be performed for the - /// associated file. - PC_SYNC_IO = libc::_PC_SYNC_IO, - #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] - /// The resolution in nanoseconds for all file timestamps. - PC_TIMESTAMP_RESOLUTION = libc::_PC_TIMESTAMP_RESOLUTION, - } - - #[cfg(unix)] - #[pyfunction] - fn pathconf( - path: PathOrFd, - ConfName(name): ConfName, - vm: &VirtualMachine, - ) -> PyResult> { - use nix::errno::{self, Errno}; - - Errno::clear(); - let raw = match path { - PathOrFd::Path(path) => { - let path = ffi::CString::new(path.into_bytes()) - .map_err(|_| vm.new_value_error("embedded null character".to_owned()))?; - unsafe { libc::pathconf(path.as_ptr(), name) } - } - PathOrFd::Fd(fd) => unsafe { libc::fpathconf(fd, name) }, - }; - - if raw == -1 { - if errno::errno() == 0 { - Ok(None) - } else { - Err(io::Error::from(Errno::last()).into_pyexception(vm)) - } - } else { - Ok(Some(raw)) - } - } - - #[cfg(unix)] - #[pyfunction] - fn fpathconf(fd: i32, name: ConfName, vm: &VirtualMachine) -> PyResult> { - pathconf(PathOrFd::Fd(fd), name, vm) - } - pub(super) fn support_funcs() -> Vec { let mut supports = super::platform::support_funcs(); supports.extend(vec![ @@ -1971,8 +1616,6 @@ mod _os { SupportFunc::new("mkdir", Some(false), Some(MKDIR_DIR_FD), Some(false)), // mkfifo Some Some None // mknod Some Some None - #[cfg(unix)] - SupportFunc::new("pathconf", Some(true), None, None), SupportFunc::new("readlink", Some(false), None, Some(false)), SupportFunc::new("remove", Some(false), None, Some(false)), SupportFunc::new("unlink", Some(false), None, Some(false)), @@ -2001,7 +1644,7 @@ mod _os { } pub(crate) use _os::{ftruncate, isatty, lseek}; -struct SupportFunc { +pub(crate) struct SupportFunc { name: &'static str, // realistically, each of these is just a bool of "is this function in the supports_* set". // However, None marks that the function maybe _should_ support fd/dir_fd/follow_symlinks, but @@ -2012,7 +1655,7 @@ struct SupportFunc { } impl<'a> SupportFunc { - fn new( + pub(crate) fn new( name: &'static str, fd: Option, dir_fd: Option, @@ -2060,256 +1703,29 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { } pub(crate) use _os::os_open as open; -#[cfg(unix)] +#[cfg(windows)] #[pymodule] -mod posix { +mod nt { use super::*; + #[cfg(target_env = "msvc")] + use crate::builtins::PyListRef; + use crate::{builtins::PyDictRef, ItemProtocol}; + use winapi::vc::vcruntime::intptr_t; - use crate::{builtins::PyListRef, slots::SlotConstructor, utils::ToCString}; - use bitflags::bitflags; - use nix::unistd::{self, Gid, Pid, Uid}; - #[allow(unused_imports)] // TODO: use will be unnecessary in edition 2021 - use std::convert::TryFrom; - use std::os::unix::io::RawFd; - - #[cfg(not(any(target_os = "redox", target_os = "freebsd")))] - #[pyattr] - use libc::O_DSYNC; - #[pyattr] - use libc::{O_CLOEXEC, O_NONBLOCK, WNOHANG}; - #[cfg(not(target_os = "redox"))] - #[pyattr] - use libc::{O_NDELAY, O_NOCTTY}; - - #[pyattr] - const EX_OK: i8 = exitcode::OK as i8; - #[pyattr] - const EX_USAGE: i8 = exitcode::USAGE as i8; - #[pyattr] - const EX_DATAERR: i8 = exitcode::DATAERR as i8; - #[pyattr] - const EX_NOINPUT: i8 = exitcode::NOINPUT as i8; - #[pyattr] - const EX_NOUSER: i8 = exitcode::NOUSER as i8; - #[pyattr] - const EX_NOHOST: i8 = exitcode::NOHOST as i8; - #[pyattr] - const EX_UNAVAILABLE: i8 = exitcode::UNAVAILABLE as i8; - #[pyattr] - const EX_SOFTWARE: i8 = exitcode::SOFTWARE as i8; - #[pyattr] - const EX_OSERR: i8 = exitcode::OSERR as i8; - #[pyattr] - const EX_OSFILE: i8 = exitcode::OSFILE as i8; - #[pyattr] - const EX_CANTCREAT: i8 = exitcode::CANTCREAT as i8; - #[pyattr] - const EX_IOERR: i8 = exitcode::IOERR as i8; - #[pyattr] - const EX_TEMPFAIL: i8 = exitcode::TEMPFAIL as i8; - #[pyattr] - const EX_PROTOCOL: i8 = exitcode::PROTOCOL as i8; - #[pyattr] - const EX_NOPERM: i8 = exitcode::NOPERM as i8; - #[pyattr] - const EX_CONFIG: i8 = exitcode::CONFIG as i8; - - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "netbsd" - ))] - #[pyattr] - const SCHED_RR: i32 = libc::SCHED_RR; - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "netbsd" - ))] - #[pyattr] - const SCHED_FIFO: i32 = libc::SCHED_FIFO; - #[cfg(any( - target_os = "linux", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "netbsd" - ))] - #[pyattr] - const SCHED_OTHER: i32 = libc::SCHED_OTHER; - #[cfg(any(target_os = "linux", target_os = "android"))] - #[pyattr] - const SCHED_IDLE: i32 = libc::SCHED_IDLE; - #[cfg(any(target_os = "linux", target_os = "android"))] - #[pyattr] - const SCHED_BATCH: i32 = libc::SCHED_BATCH; - - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[pyattr] - const POSIX_SPAWN_OPEN: i32 = PosixSpawnFileActionIdentifier::Open as i32; - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[pyattr] - const POSIX_SPAWN_CLOSE: i32 = PosixSpawnFileActionIdentifier::Close as i32; - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[pyattr] - const POSIX_SPAWN_DUP2: i32 = PosixSpawnFileActionIdentifier::Dup2 as i32; - - #[cfg(target_os = "macos")] #[pyattr] - const _COPYFILE_DATA: u32 = 1 << 3; - - // Flags for os_access - bitflags! { - pub struct AccessFlags: u8{ - const F_OK = super::_os::F_OK; - const R_OK = super::_os::R_OK; - const W_OK = super::_os::W_OK; - const X_OK = super::_os::X_OK; - } - } - - struct Permissions { - is_readable: bool, - is_writable: bool, - is_executable: bool, - } - - fn get_permissions(mode: u32) -> Permissions { - Permissions { - is_readable: mode & 4 != 0, - is_writable: mode & 2 != 0, - is_executable: mode & 1 != 0, - } - } - - fn get_right_permission( - mode: u32, - file_owner: Uid, - file_group: Gid, - ) -> nix::Result { - let owner_mode = (mode & 0o700) >> 6; - let owner_permissions = get_permissions(owner_mode); - - let group_mode = (mode & 0o070) >> 3; - let group_permissions = get_permissions(group_mode); - - let others_mode = mode & 0o007; - let others_permissions = get_permissions(others_mode); - - let user_id = nix::unistd::getuid(); - let groups_ids = getgroups_impl()?; - - if file_owner == user_id { - Ok(owner_permissions) - } else if groups_ids.contains(&file_group) { - Ok(group_permissions) - } else { - Ok(others_permissions) - } - } - - #[cfg(any(target_os = "macos", target_os = "ios"))] - fn getgroups_impl() -> nix::Result> { - use libc::{c_int, gid_t}; - use nix::errno::Errno; - use std::ptr; - let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; - let mut groups = Vec::::with_capacity(Errno::result(ret)? as usize); - let ret = unsafe { - libc::getgroups( - groups.capacity() as c_int, - groups.as_mut_ptr() as *mut gid_t, - ) - }; - - Errno::result(ret).map(|s| { - unsafe { groups.set_len(s as usize) }; - groups - }) - } - - #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))] - use nix::unistd::getgroups as getgroups_impl; - - #[cfg(target_os = "redox")] - fn getgroups_impl() -> nix::Result> { - Err(nix::Error::EOPNOTSUPP) - } - - #[pyfunction] - fn getgroups(vm: &VirtualMachine) -> PyResult { - let group_ids = getgroups_impl().map_err(|e| e.into_pyexception(vm))?; - Ok(vm.ctx.new_list( - group_ids - .into_iter() - .map(|gid| vm.ctx.new_int(gid.as_raw())) - .collect(), - )) - } + use libc::{O_BINARY, O_TEMPORARY}; #[pyfunction] pub(super) fn access(path: PyPathLike, mode: u8, vm: &VirtualMachine) -> PyResult { - use std::os::unix::fs::MetadataExt; - - let flags = AccessFlags::from_bits(mode).ok_or_else(|| { - vm.new_value_error( - "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK" - .to_owned(), - ) - })?; - - let metadata = fs::metadata(&path.path); - - // if it's only checking for F_OK - if flags == AccessFlags::F_OK { - return Ok(metadata.is_ok()); - } - - let metadata = metadata.map_err(|err| err.into_pyexception(vm))?; - - let user_id = metadata.uid(); - let group_id = metadata.gid(); - let mode = metadata.mode(); - - let perm = get_right_permission(mode, Uid::from_raw(user_id), Gid::from_raw(group_id)) - .map_err(|err| err.into_pyexception(vm))?; - - let r_ok = !flags.contains(AccessFlags::R_OK) || perm.is_readable; - let w_ok = !flags.contains(AccessFlags::W_OK) || perm.is_writable; - let x_ok = !flags.contains(AccessFlags::X_OK) || perm.is_executable; - - Ok(r_ok && w_ok && x_ok) - } - - pub(super) fn bytes_as_osstr<'a>( - b: &'a [u8], - _vm: &VirtualMachine, - ) -> PyResult<&'a ffi::OsStr> { - use std::os::unix::ffi::OsStrExt; - Ok(ffi::OsStr::from_bytes(b)) - } - - #[pyattr] - fn environ(vm: &VirtualMachine) -> PyDictRef { - let environ = vm.ctx.new_dict(); - use ffi_ext::OsStringExt; - for (key, value) in env::vars_os() { - environ - .set_item( - vm.ctx.new_bytes(key.into_vec()), - vm.ctx.new_bytes(value.into_vec()), - vm, - ) - .unwrap(); - } - - environ + use winapi::um::{fileapi, winnt}; + let attr = unsafe { fileapi::GetFileAttributesW(path.to_widecstring(vm)?.as_ptr()) }; + Ok(attr != fileapi::INVALID_FILE_ATTRIBUTES + && (mode & 2 == 0 + || attr & winnt::FILE_ATTRIBUTE_READONLY == 0 + || attr & winnt::FILE_ATTRIBUTE_DIRECTORY != 0)) } - pub(super) const SYMLINK_DIR_FD: bool = cfg!(not(target_os = "redox")); + pub const SYMLINK_DIR_FD: bool = false; #[derive(FromArgs)] pub(super) struct SimlinkArgs { @@ -2318,1138 +1734,45 @@ mod posix { #[pyarg(any)] dst: PyPathLike, #[pyarg(flatten)] - _target_is_directory: TargetIsDirectory, + target_is_directory: TargetIsDirectory, #[pyarg(flatten)] - dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, } #[pyfunction] pub(super) fn symlink(args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { - let src = args.src.into_cstring(vm)?; - let dst = args.dst.into_cstring(vm)?; - #[cfg(not(target_os = "redox"))] - { - nix::unistd::symlinkat(&*src, args.dir_fd.get_opt(), &*dst) - .map_err(|err| err.into_pyexception(vm)) - } - #[cfg(target_os = "redox")] - { - let [] = args.dir_fd.0; - let res = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }; - if res < 0 { - Err(errno_err(vm)) - } else { - Ok(()) - } - } + use std::os::windows::fs as win_fs; + let dir = args.target_is_directory.target_is_directory + || args + .dst + .path + .parent() + .and_then(|dst_parent| dst_parent.join(&args.src).symlink_metadata().ok()) + .map_or(false, |meta| meta.is_dir()); + let res = if dir { + win_fs::symlink_dir(args.src.path, args.dst.path) + } else { + win_fs::symlink_file(args.src.path, args.dst.path) + }; + res.map_err(|err| err.into_pyexception(vm)) } - #[cfg(not(target_os = "redox"))] #[pyfunction] - fn fchdir(fd: RawFd, vm: &VirtualMachine) -> PyResult<()> { - nix::unistd::fchdir(fd).map_err(|err| err.into_pyexception(vm)) + fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> { + let handle = Fd(fd).to_raw_handle().map_err(|e| e.into_pyexception(vm))?; + set_handle_inheritable(handle as _, inheritable, vm) } - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn chroot(path: PyPathLike, vm: &VirtualMachine) -> PyResult<()> { - nix::unistd::chroot(&*path.path).map_err(|err| err.into_pyexception(vm)) - } - - // As of now, redox does not seems to support chown command (cf. https://gitlab.redox-os.org/redox-os/coreutils , last checked on 05/07/2020) - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn chown( - path: PathOrFd, - uid: isize, - gid: isize, - dir_fd: DirFd<1>, - follow_symlinks: FollowSymlinks, - vm: &VirtualMachine, - ) -> PyResult<()> { - let uid = if uid >= 0 { - Some(nix::unistd::Uid::from_raw(uid as u32)) - } else if uid == -1 { - None - } else { - return Err(vm.new_os_error(String::from("Specified uid is not valid."))); - }; - - let gid = if gid >= 0 { - Some(nix::unistd::Gid::from_raw(gid as u32)) - } else if gid == -1 { - None - } else { - return Err(vm.new_os_error(String::from("Specified gid is not valid."))); - }; - - let flag = if follow_symlinks.0 { - nix::unistd::FchownatFlags::FollowSymlink - } else { - nix::unistd::FchownatFlags::NoFollowSymlink - }; - - let dir_fd = dir_fd.get_opt(); - match path { - PathOrFd::Path(p) => nix::unistd::fchownat(dir_fd, p.path.as_os_str(), uid, gid, flag), - PathOrFd::Fd(fd) => nix::unistd::fchown(fd, uid, gid), - } - .map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn lchown(path: PyPathLike, uid: isize, gid: isize, vm: &VirtualMachine) -> PyResult<()> { - chown( - PathOrFd::Path(path), - uid, - gid, - DirFd::default(), - FollowSymlinks(false), - vm, - ) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn fchown(fd: i32, uid: isize, gid: isize, vm: &VirtualMachine) -> PyResult<()> { - chown( - PathOrFd::Fd(fd), - uid, - gid, - DirFd::default(), - FollowSymlinks(true), - vm, - ) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn nice(increment: i32, vm: &VirtualMachine) -> PyResult { - use nix::errno::{errno, Errno}; - Errno::clear(); - let res = unsafe { libc::nice(increment) }; - if res == -1 && errno() != 0 { - Err(errno_err(vm)) - } else { - Ok(res) - } - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn sched_get_priority_max(policy: i32, vm: &VirtualMachine) -> PyResult { - let max = unsafe { libc::sched_get_priority_max(policy) }; - if max == -1 { - Err(errno_err(vm)) - } else { - Ok(max) - } - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn sched_get_priority_min(policy: i32, vm: &VirtualMachine) -> PyResult { - let min = unsafe { libc::sched_get_priority_min(policy) }; - if min == -1 { - Err(errno_err(vm)) - } else { - Ok(min) - } - } - - #[pyfunction] - fn sched_yield(vm: &VirtualMachine) -> PyResult<()> { - let _ = nix::sched::sched_yield().map_err(|e| e.into_pyexception(vm))?; - Ok(()) - } - - #[pyattr] - #[pyclass(name = "sched_param")] - #[derive(Debug, PyValue)] - struct SchedParam { - sched_priority: PyObjectRef, - } - - impl TryFromObject for SchedParam { - fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - Ok(SchedParam { - sched_priority: obj, - }) - } - } - - #[pyimpl(with(SlotConstructor))] - impl SchedParam { - #[pyproperty] - fn sched_priority(&self, vm: &VirtualMachine) -> PyObjectRef { - self.sched_priority.clone().into_pyobject(vm) - } - - #[pymethod(magic)] - fn repr(&self, vm: &VirtualMachine) -> PyResult { - let sched_priority_repr = vm.to_repr(&self.sched_priority)?; - Ok(format!( - "posix.sched_param(sched_priority = {})", - sched_priority_repr.as_str() - )) - } - } - - impl SlotConstructor for SchedParam { - type Args = SchedParam; - fn py_new(cls: PyTypeRef, sched_param: Self::Args, vm: &VirtualMachine) -> PyResult { - sched_param.into_pyresult_with_type(vm, cls) - } - } - - #[pyfunction] - fn get_inheritable(fd: RawFd, vm: &VirtualMachine) -> PyResult { - use nix::fcntl::fcntl; - use nix::fcntl::FcntlArg; - let flags = fcntl(fd, FcntlArg::F_GETFD); - match flags { - Ok(ret) => Ok((ret & libc::FD_CLOEXEC) == 0), - Err(err) => Err(err.into_pyexception(vm)), - } - } - - pub(crate) fn raw_set_inheritable(fd: RawFd, inheritable: bool) -> nix::Result<()> { - use nix::fcntl; - let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?); - let mut new_flags = flags; - new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable); - if flags != new_flags { - fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?; - } - Ok(()) - } - - #[pyfunction] - fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> { - raw_set_inheritable(fd, inheritable).map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn get_blocking(fd: RawFd, vm: &VirtualMachine) -> PyResult { - use nix::fcntl::fcntl; - use nix::fcntl::FcntlArg; - let flags = fcntl(fd, FcntlArg::F_GETFL); - match flags { - Ok(ret) => Ok((ret & libc::O_NONBLOCK) == 0), - Err(err) => Err(err.into_pyexception(vm)), - } - } - - #[pyfunction] - fn set_blocking(fd: RawFd, blocking: bool, vm: &VirtualMachine) -> PyResult<()> { - let _set_flag = || { - use nix::fcntl::fcntl; - use nix::fcntl::FcntlArg; - use nix::fcntl::OFlag; - - let flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?); - let mut new_flags = flags; - new_flags.set(OFlag::from_bits_truncate(libc::O_NONBLOCK), !blocking); - if flags != new_flags { - fcntl(fd, FcntlArg::F_SETFL(new_flags))?; - } - Ok(()) - }; - _set_flag().map_err(|err: nix::Error| err.into_pyexception(vm)) - } - - #[pyfunction] - fn pipe(vm: &VirtualMachine) -> PyResult<(RawFd, RawFd)> { - use nix::unistd::close; - use nix::unistd::pipe; - let (rfd, wfd) = pipe().map_err(|err| err.into_pyexception(vm))?; - set_inheritable(rfd, false, vm) - .and_then(|_| set_inheritable(wfd, false, vm)) - .map_err(|err| { - let _ = close(rfd); - let _ = close(wfd); - err - })?; - Ok((rfd, wfd)) - } - - // cfg from nix - #[cfg(any( - target_os = "android", - target_os = "dragonfly", - target_os = "emscripten", - target_os = "freebsd", - target_os = "linux", - target_os = "netbsd", - target_os = "openbsd" - ))] - #[pyfunction] - fn pipe2(flags: libc::c_int, vm: &VirtualMachine) -> PyResult<(RawFd, RawFd)> { - use nix::fcntl::OFlag; - use nix::unistd::pipe2; - let oflags = OFlag::from_bits_truncate(flags); - pipe2(oflags).map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn system(command: PyStrRef, vm: &VirtualMachine) -> PyResult { - let cstr = command.to_cstring(vm)?; - let x = unsafe { libc::system(cstr.as_ptr()) }; - Ok(x) - } - - #[pyfunction] - fn chmod( - path: PyPathLike, - dir_fd: DirFd<0>, - mode: u32, - follow_symlinks: FollowSymlinks, - vm: &VirtualMachine, - ) -> PyResult<()> { - let [] = dir_fd.0; - let body = move || { - use std::os::unix::fs::PermissionsExt; - let meta = fs_metadata(&path, follow_symlinks.0)?; - let mut permissions = meta.permissions(); - permissions.set_mode(mode); - fs::set_permissions(&path, permissions) - }; - body().map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn execv( - path: PyStrRef, - argv: Either, - vm: &VirtualMachine, - ) -> PyResult<()> { - let path = path.to_cstring(vm)?; - - let argv = vm.extract_elements_func(argv.as_object(), |obj| { - PyStrRef::try_from_object(vm, obj)?.to_cstring(vm) - })?; - let argv: Vec<&ffi::CStr> = argv.iter().map(|entry| entry.as_c_str()).collect(); - - let first = argv - .first() - .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty".to_owned()))?; - if first.to_bytes().is_empty() { - return Err( - vm.new_value_error("execv() arg 2 first element cannot be empty".to_owned()) - ); - } - - unistd::execv(&path, &argv) - .map(|_ok| ()) - .map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn execve( - path: PyPathLike, - argv: Either, - env: PyDictRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - let path = path.into_cstring(vm)?; - - let argv = vm.extract_elements_func(argv.as_object(), |obj| { - PyStrRef::try_from_object(vm, obj)?.to_cstring(vm) - })?; - let argv: Vec<&ffi::CStr> = argv.iter().map(|entry| entry.as_c_str()).collect(); - - let first = argv - .first() - .ok_or_else(|| vm.new_value_error("execve() arg 2 must not be empty".to_owned()))?; - - if first.to_bytes().is_empty() { - return Err( - vm.new_value_error("execve() arg 2 first element cannot be empty".to_owned()) - ); - } - - let env = env - .into_iter() - .map(|(k, v)| -> PyResult<_> { - let (key, value) = ( - PyPathLike::try_from_object(vm, k)?.into_bytes(), - PyPathLike::try_from_object(vm, v)?.into_bytes(), - ); - - if memchr::memchr(b'=', &key).is_some() { - return Err(vm.new_value_error("illegal environment variable name".to_owned())); - } - - let mut entry = key; - entry.push(b'='); - entry.extend_from_slice(&value); - - ffi::CString::new(entry).map_err(|err| err.into_pyexception(vm)) - }) - .collect::, _>>()?; - - let env: Vec<&ffi::CStr> = env.iter().map(|entry| entry.as_c_str()).collect(); - - unistd::execve(&path, &argv, &env).map_err(|err| err.into_pyexception(vm))?; - Ok(()) - } - - #[pyfunction] - fn getppid(vm: &VirtualMachine) -> PyObjectRef { - let ppid = unistd::getppid().as_raw(); - vm.ctx.new_int(ppid) - } - - #[pyfunction] - fn getgid(vm: &VirtualMachine) -> PyObjectRef { - let gid = unistd::getgid().as_raw(); - vm.ctx.new_int(gid) - } - - #[pyfunction] - fn getegid(vm: &VirtualMachine) -> PyObjectRef { - let egid = unistd::getegid().as_raw(); - vm.ctx.new_int(egid) - } - - #[pyfunction] - fn getpgid(pid: u32, vm: &VirtualMachine) -> PyResult { - match unistd::getpgid(Some(Pid::from_raw(pid as i32))) { - Ok(pgid) => Ok(vm.ctx.new_int(pgid.as_raw())), - Err(err) => Err(err.into_pyexception(vm)), - } - } - - #[pyfunction] - fn getpgrp(vm: &VirtualMachine) -> PyResult { - Ok(vm.ctx.new_int(unistd::getpgrp().as_raw())) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn getsid(pid: u32, vm: &VirtualMachine) -> PyResult { - match unistd::getsid(Some(Pid::from_raw(pid as i32))) { - Ok(sid) => Ok(vm.ctx.new_int(sid.as_raw())), - Err(err) => Err(err.into_pyexception(vm)), - } - } - - #[pyfunction] - fn getuid(vm: &VirtualMachine) -> PyObjectRef { - let uid = unistd::getuid().as_raw(); - vm.ctx.new_int(uid) - } - - #[pyfunction] - fn geteuid(vm: &VirtualMachine) -> PyObjectRef { - let euid = unistd::geteuid().as_raw(); - vm.ctx.new_int(euid) - } - - #[pyfunction] - fn setgid(gid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setgid(Gid::from_raw(gid)).map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn setegid(egid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setegid(Gid::from_raw(egid)).map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn setpgid(pid: u32, pgid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setpgid(Pid::from_raw(pid as i32), Pid::from_raw(pgid as i32)) - .map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn setsid(vm: &VirtualMachine) -> PyResult<()> { - unistd::setsid() - .map(|_ok| ()) - .map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn setuid(uid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setuid(Uid::from_raw(uid)).map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn seteuid(euid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::seteuid(Uid::from_raw(euid)).map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn setreuid(ruid: u32, euid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setuid(Uid::from_raw(ruid)).map_err(|err| err.into_pyexception(vm))?; - unistd::seteuid(Uid::from_raw(euid)).map_err(|err| err.into_pyexception(vm)) - } - - // cfg from nix - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - ))] - #[pyfunction] - fn setresuid(ruid: u32, euid: u32, suid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setresuid( - Uid::from_raw(ruid), - Uid::from_raw(euid), - Uid::from_raw(suid), - ) - .map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn openpty(vm: &VirtualMachine) -> PyResult { - let r = nix::pty::openpty(None, None).map_err(|err| err.into_pyexception(vm))?; - for fd in &[r.master, r.slave] { - raw_set_inheritable(*fd, false).map_err(|e| e.into_pyexception(vm))?; - } - Ok(vm - .ctx - .new_tuple(vec![vm.ctx.new_int(r.master), vm.ctx.new_int(r.slave)])) - } - - #[pyfunction] - fn ttyname(fd: i32, vm: &VirtualMachine) -> PyResult { - let name = unsafe { libc::ttyname(fd) }; - if name.is_null() { - Err(errno_err(vm)) - } else { - let name = unsafe { ffi::CStr::from_ptr(name) }.to_str().unwrap(); - Ok(vm.ctx.new_utf8_str(name)) - } - } - - #[pyfunction] - fn umask(mask: libc::mode_t) -> libc::mode_t { - unsafe { libc::umask(mask) } - } - - #[pyfunction] - fn uname(vm: &VirtualMachine) -> PyResult { - let info = uname::uname().map_err(|err| err.into_pyexception(vm))?; - Ok(super::_os::UnameResult { - sysname: info.sysname, - nodename: info.nodename, - release: info.release, - version: info.version, - machine: info.machine, - }) - } - - #[pyfunction] - fn sync() { - #[cfg(not(any(target_os = "redox", target_os = "android")))] - unsafe { - libc::sync(); - } - } - - // cfg from nix - #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] - #[pyfunction] - fn getresuid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { - let mut ruid = 0; - let mut euid = 0; - let mut suid = 0; - let ret = unsafe { libc::getresuid(&mut ruid, &mut euid, &mut suid) }; - if ret == 0 { - Ok((ruid, euid, suid)) - } else { - Err(errno_err(vm)) - } - } - - // cfg from nix - #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] - #[pyfunction] - fn getresgid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { - let mut rgid = 0; - let mut egid = 0; - let mut sgid = 0; - let ret = unsafe { libc::getresgid(&mut rgid, &mut egid, &mut sgid) }; - if ret == 0 { - Ok((rgid, egid, sgid)) - } else { - Err(errno_err(vm)) - } - } - - // cfg from nix - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - ))] - #[pyfunction] - fn setresgid(rgid: u32, egid: u32, sgid: u32, vm: &VirtualMachine) -> PyResult<()> { - unistd::setresgid( - Gid::from_raw(rgid), - Gid::from_raw(egid), - Gid::from_raw(sgid), - ) - .map_err(|err| err.into_pyexception(vm)) - } - - // cfg from nix - #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] - #[pyfunction] - fn setregid(rgid: u32, egid: u32, vm: &VirtualMachine) -> PyResult<()> { - let ret = unsafe { libc::setregid(rgid, egid) }; - if ret == 0 { - Ok(()) - } else { - Err(errno_err(vm)) - } - } - - // cfg from nix - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - ))] - #[pyfunction] - fn initgroups(user_name: PyStrRef, gid: u32, vm: &VirtualMachine) -> PyResult<()> { - let user = ffi::CString::new(user_name.as_str()).unwrap(); - let gid = Gid::from_raw(gid); - unistd::initgroups(&user, gid).map_err(|err| err.into_pyexception(vm)) - } - - // cfg from nix - #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] - #[pyfunction] - fn setgroups( - group_ids: crate::function::ArgIterable, - vm: &VirtualMachine, - ) -> PyResult<()> { - let gids = group_ids - .iter(vm)? - .map(|entry| match entry { - Ok(id) => Ok(unistd::Gid::from_raw(id)), - Err(err) => Err(err), - }) - .collect::, _>>()?; - let ret = unistd::setgroups(&gids); - ret.map_err(|err| err.into_pyexception(vm)) - } - - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - fn envp_from_dict(dict: PyDictRef, vm: &VirtualMachine) -> PyResult> { - dict.into_iter() - .map(|(k, v)| { - let k = PyPathLike::try_from_object(vm, k)?.into_bytes(); - let v = PyPathLike::try_from_object(vm, v)?.into_bytes(); - if k.contains(&0) { - return Err( - vm.new_value_error("envp dict key cannot contain a nul byte".to_owned()) - ); - } - if k.contains(&b'=') { - return Err(vm.new_value_error( - "envp dict key cannot contain a '=' character".to_owned(), - )); - } - if v.contains(&0) { - return Err( - vm.new_value_error("envp dict value cannot contain a nul byte".to_owned()) - ); - } - let mut env = k; - env.push(b'='); - env.extend(v); - Ok(unsafe { ffi::CString::from_vec_unchecked(env) }) - }) - .collect() - } - - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[derive(FromArgs)] - pub(super) struct PosixSpawnArgs { - #[pyarg(positional)] - path: PyPathLike, - #[pyarg(positional)] - args: crate::function::ArgIterable, - #[pyarg(positional)] - env: crate::builtins::dict::PyMapping, - #[pyarg(named, default)] - file_actions: Option>, - #[pyarg(named, default)] - setsigdef: Option>, - } - - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] - #[repr(i32)] - enum PosixSpawnFileActionIdentifier { - Open, - Close, - Dup2, - } - - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - impl PosixSpawnArgs { - fn spawn(self, spawnp: bool, vm: &VirtualMachine) -> PyResult { - let path = ffi::CString::new(self.path.into_bytes()) - .map_err(|_| vm.new_value_error("path should not have nul bytes".to_owned()))?; - - let mut file_actions = unsafe { - let mut fa = std::mem::MaybeUninit::uninit(); - assert!(libc::posix_spawn_file_actions_init(fa.as_mut_ptr()) == 0); - fa.assume_init() - }; - if let Some(it) = self.file_actions { - for action in it.iter(vm)? { - let action = action?; - let (id, args) = action.as_slice().split_first().ok_or_else(|| { - vm.new_type_error( - "Each file_actions element must be a non-empty tuple".to_owned(), - ) - })?; - let id = i32::try_from_borrowed_object(vm, id)?; - let id = PosixSpawnFileActionIdentifier::try_from(id).map_err(|_| { - vm.new_type_error("Unknown file_actions identifier".to_owned()) - })?; - let args = FuncArgs::from(args.to_vec()); - let ret = match id { - PosixSpawnFileActionIdentifier::Open => { - let (fd, path, oflag, mode): (_, PyPathLike, _, _) = args.bind(vm)?; - let path = ffi::CString::new(path.into_bytes()).map_err(|_| { - vm.new_value_error( - "POSIX_SPAWN_OPEN path should not have nul bytes".to_owned(), - ) - })?; - unsafe { - libc::posix_spawn_file_actions_addopen( - &mut file_actions, - fd, - path.as_ptr(), - oflag, - mode, - ) - } - } - PosixSpawnFileActionIdentifier::Close => { - let (fd,) = args.bind(vm)?; - unsafe { - libc::posix_spawn_file_actions_addclose(&mut file_actions, fd) - } - } - PosixSpawnFileActionIdentifier::Dup2 => { - let (fd, newfd) = args.bind(vm)?; - unsafe { - libc::posix_spawn_file_actions_adddup2(&mut file_actions, fd, newfd) - } - } - }; - if ret != 0 { - return Err(errno_err(vm)); - } - } - } - - let mut attrp = unsafe { - let mut sa = std::mem::MaybeUninit::uninit(); - assert!(libc::posix_spawnattr_init(sa.as_mut_ptr()) == 0); - sa.assume_init() - }; - if let Some(sigs) = self.setsigdef { - use nix::sys::signal; - let mut set = signal::SigSet::empty(); - for sig in sigs.iter(vm)? { - let sig = sig?; - let sig = signal::Signal::try_from(sig).map_err(|_| { - vm.new_value_error(format!("signal number {} out of range", sig)) - })?; - set.add(sig); - } - assert!( - unsafe { libc::posix_spawnattr_setsigdefault(&mut attrp, set.as_ref()) } == 0 - ); - } - - let mut args: Vec = self - .args - .iter(vm)? - .map(|res| { - ffi::CString::new(res?.into_bytes()).map_err(|_| { - vm.new_value_error("path should not have nul bytes".to_owned()) - }) - }) - .collect::>()?; - let argv: Vec<*mut libc::c_char> = args - .iter_mut() - .map(|s| s.as_ptr() as _) - .chain(std::iter::once(std::ptr::null_mut())) - .collect(); - let mut env = envp_from_dict(self.env.into_dict(), vm)?; - let envp: Vec<*mut libc::c_char> = env - .iter_mut() - .map(|s| s.as_ptr() as _) - .chain(std::iter::once(std::ptr::null_mut())) - .collect(); - - let mut pid = 0; - let ret = unsafe { - if spawnp { - libc::posix_spawnp( - &mut pid, - path.as_ptr(), - &file_actions, - &attrp, - argv.as_ptr(), - envp.as_ptr(), - ) - } else { - libc::posix_spawn( - &mut pid, - path.as_ptr(), - &file_actions, - &attrp, - argv.as_ptr(), - envp.as_ptr(), - ) - } - }; - - if ret == 0 { - Ok(pid) - } else { - Err(errno_err(vm)) - } - } - } - - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[pyfunction] - fn posix_spawn(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult { - args.spawn(false, vm) - } - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] - #[pyfunction] - fn posix_spawnp(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult { - args.spawn(true, vm) - } - - #[pyfunction(name = "WIFSIGNALED")] - fn wifsignaled(status: i32) -> bool { - libc::WIFSIGNALED(status) - } - #[pyfunction(name = "WIFSTOPPED")] - fn wifstopped(status: i32) -> bool { - libc::WIFSTOPPED(status) - } - #[pyfunction(name = "WIFEXITED")] - fn wifexited(status: i32) -> bool { - libc::WIFEXITED(status) - } - #[pyfunction(name = "WTERMSIG")] - fn wtermsig(status: i32) -> i32 { - libc::WTERMSIG(status) - } - #[pyfunction(name = "WSTOPSIG")] - fn wstopsig(status: i32) -> i32 { - libc::WSTOPSIG(status) - } - #[pyfunction(name = "WEXITSTATUS")] - fn wexitstatus(status: i32) -> i32 { - libc::WEXITSTATUS(status) - } - - #[pyfunction] - fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { - let mut status = 0; - let pid = unsafe { libc::waitpid(pid, &mut status, opt) }; - let pid = nix::Error::result(pid).map_err(|err| err.into_pyexception(vm))?; - Ok((pid, status)) - } - #[pyfunction] - fn wait(vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { - waitpid(-1, 0, vm) - } - - #[pyfunction] - fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { - { - let ret = unsafe { libc::kill(pid, sig as i32) }; - if ret == -1 { - Err(errno_err(vm)) - } else { - Ok(()) - } - } - } - - #[pyfunction] - fn get_terminal_size( - fd: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let (columns, lines) = { - nix::ioctl_read_bad!(winsz, libc::TIOCGWINSZ, libc::winsize); - let mut w = libc::winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - unsafe { winsz(fd.unwrap_or(libc::STDOUT_FILENO), &mut w) } - .map_err(|err| err.into_pyexception(vm))?; - (w.ws_col.into(), w.ws_row.into()) - }; - Ok(super::_os::PyTerminalSize { columns, lines }) - } - - // from libstd: - // https://github.com/rust-lang/rust/blob/daecab3a784f28082df90cebb204998051f3557d/src/libstd/sys/unix/fs.rs#L1251 - #[cfg(target_os = "macos")] - 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, std::ptr::null_mut(), flags as u32) }; - if ret < 0 { - Err(errno_err(vm)) - } else { - Ok(()) - } - } - - #[pyfunction] - fn dup(fd: i32, vm: &VirtualMachine) -> PyResult { - let fd = nix::unistd::dup(fd).map_err(|e| e.into_pyexception(vm))?; - raw_set_inheritable(fd, false).map(|()| fd).map_err(|e| { - let _ = nix::unistd::close(fd); - e.into_pyexception(vm) - }) - } - - #[derive(FromArgs)] - struct Dup2Args { - #[pyarg(positional)] - fd: i32, - #[pyarg(positional)] - fd2: i32, - #[pyarg(any, default = "true")] - inheritable: bool, - } - - #[pyfunction] - fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult { - let fd = nix::unistd::dup2(args.fd, args.fd2).map_err(|e| e.into_pyexception(vm))?; - if !args.inheritable { - raw_set_inheritable(fd, false).map_err(|e| { - let _ = nix::unistd::close(fd); - e.into_pyexception(vm) - })? - } - Ok(fd) - } - - pub(super) fn support_funcs() -> Vec { - vec![ - SupportFunc::new("chmod", Some(false), Some(false), Some(false)), - #[cfg(not(target_os = "redox"))] - SupportFunc::new("chroot", Some(false), None, None), - #[cfg(not(target_os = "redox"))] - SupportFunc::new("chown", Some(true), Some(true), Some(true)), - #[cfg(not(target_os = "redox"))] - SupportFunc::new("lchown", None, None, None), - #[cfg(not(target_os = "redox"))] - SupportFunc::new("fchown", Some(true), None, Some(true)), - SupportFunc::new("umask", Some(false), Some(false), Some(false)), - SupportFunc::new("execv", None, None, None), - ] - } - - /// Return a string containing the name of the user logged in on the - /// controlling terminal of the process. - /// - /// Exceptions: - /// - /// - `OSError`: Raised if login name could not be determined (`getlogin()` - /// returned a null pointer). - /// - `UnicodeDecodeError`: Raised if login name contained invalid UTF-8 bytes. - #[pyfunction] - fn getlogin(vm: &VirtualMachine) -> PyResult { - // Get a pointer to the login name string. The string is statically - // allocated and might be overwritten on subsequent calls to this - // function or to `cuserid()`. See man getlogin(3) for more information. - let ptr = unsafe { libc::getlogin() }; - if ptr.is_null() { - return Err(vm.new_os_error("unable to determine login name".to_owned())); - } - let slice = unsafe { ffi::CStr::from_ptr(ptr) }; - slice - .to_str() - .map(|s| s.to_owned()) - .map_err(|e| vm.new_unicode_decode_error(format!("unable to decode login name: {}", e))) - } - - // cfg from nix - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - ))] - #[pyfunction] - fn getgrouplist(user: PyStrRef, group: u32, vm: &VirtualMachine) -> PyResult { - let user = ffi::CString::new(user.as_str()).unwrap(); - let gid = Gid::from_raw(group); - let group_ids = unistd::getgrouplist(&user, gid).map_err(|err| err.into_pyexception(vm))?; - Ok(vm.ctx.new_list( - group_ids - .into_iter() - .map(|gid| vm.ctx.new_int(gid.as_raw())) - .collect(), - )) - } - - #[cfg(not(target_os = "redox"))] - cfg_if::cfg_if! { - if #[cfg(all(target_os = "linux", target_env = "gnu"))] { - type PriorityWhichType = libc::__priority_which_t; - } else { - type PriorityWhichType = libc::c_int; - } - } - #[cfg(not(target_os = "redox"))] - cfg_if::cfg_if! { - if #[cfg(target_os = "freebsd")] { - type PriorityWhoType = i32; - } else { - type PriorityWhoType = u32; - } - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn getpriority( - which: PriorityWhichType, - who: PriorityWhoType, - vm: &VirtualMachine, - ) -> PyResult { - use nix::errno::{errno, Errno}; - Errno::clear(); - let retval = unsafe { libc::getpriority(which, who) }; - if errno() != 0 { - Err(errno_err(vm)) - } else { - Ok(vm.ctx.new_int(retval)) - } - } - - #[cfg(not(target_os = "redox"))] - #[pyfunction] - fn setpriority( - which: PriorityWhichType, - who: PriorityWhoType, - priority: i32, - vm: &VirtualMachine, - ) -> PyResult<()> { - let retval = unsafe { libc::setpriority(which, who, priority) }; - if retval == -1 { - Err(errno_err(vm)) - } else { - Ok(()) - } - } -} -#[cfg(unix)] -use posix as platform; -#[cfg(unix)] -pub(crate) use posix::raw_set_inheritable; - -#[cfg(windows)] -#[pymodule] -mod nt { - use super::*; - #[cfg(target_env = "msvc")] - use crate::builtins::list::PyListRef; - use winapi::vc::vcruntime::intptr_t; - - #[pyattr] - use libc::{O_BINARY, O_TEMPORARY}; - - #[pyfunction] - pub(super) fn access(path: PyPathLike, mode: u8, vm: &VirtualMachine) -> PyResult { - use winapi::um::{fileapi, winnt}; - let attr = unsafe { fileapi::GetFileAttributesW(path.to_widecstring(vm)?.as_ptr()) }; - Ok(attr != fileapi::INVALID_FILE_ATTRIBUTES - && (mode & 2 == 0 - || attr & winnt::FILE_ATTRIBUTE_READONLY == 0 - || attr & winnt::FILE_ATTRIBUTE_DIRECTORY != 0)) - } - - pub const SYMLINK_DIR_FD: bool = false; - - #[derive(FromArgs)] - pub(super) struct SimlinkArgs { - #[pyarg(any)] - src: PyPathLike, - #[pyarg(any)] - dst: PyPathLike, - #[pyarg(flatten)] - target_is_directory: TargetIsDirectory, - #[pyarg(flatten)] - _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, - } - - #[pyfunction] - pub(super) fn symlink(args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { - use std::os::windows::fs as win_fs; - let dir = args.target_is_directory.target_is_directory - || args - .dst - .path - .parent() - .and_then(|dst_parent| dst_parent.join(&args.src).symlink_metadata().ok()) - .map_or(false, |meta| meta.is_dir()); - let res = if dir { - win_fs::symlink_dir(args.src.path, args.dst.path) - } else { - win_fs::symlink_file(args.src.path, args.dst.path) - }; - res.map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> { - let handle = Fd(fd).to_raw_handle().map_err(|e| e.into_pyexception(vm))?; - set_handle_inheritable(handle as _, inheritable, vm) - } - - #[pyattr] - fn environ(vm: &VirtualMachine) -> PyDictRef { - let environ = vm.ctx.new_dict(); - - for (key, value) in env::vars() { - environ - .set_item(vm.ctx.new_utf8_str(key), vm.ctx.new_utf8_str(value), vm) - .unwrap(); - } - environ + #[pyattr] + fn environ(vm: &VirtualMachine) -> PyDictRef { + let environ = vm.ctx.new_dict(); + + for (key, value) in env::vars() { + environ + .set_item(vm.ctx.new_utf8_str(key), vm.ctx.new_utf8_str(value), vm) + .unwrap(); + } + environ } #[pyfunction] @@ -3754,6 +2077,25 @@ mod nt { raw_set_handle_inheritable(handle, inheritable).map_err(|e| e.into_pyexception(vm)) } + #[pyfunction] + fn mkdir( + path: PyPathLike, + mode: OptionalArg, + dir_fd: DirFd<{ super::_os::MKDIR_DIR_FD as usize }>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let mode = mode.unwrap_or(0o777); + let [] = dir_fd.0; + let _ = mode; + let wide = path.to_widecstring(vm)?; + let res = + unsafe { winapi::um::fileapi::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) }; + if res == 0 { + return Err(errno_err(vm)); + } + Ok(()) + } + pub(super) fn support_funcs() -> Vec { Vec::new() } @@ -3769,6 +2111,7 @@ pub use nt::{_set_thread_local_invalid_parameter_handler, silent_iph_handler}; #[pymodule(name = "posix")] mod minor { use super::*; + use crate::builtins::PyDictRef; #[pyfunction] pub(super) fn access(_path: PyStrRef, _mode: u8, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/stdlib/posix.rs b/vm/src/stdlib/posix.rs new file mode 100644 index 0000000000..3a8a2df634 --- /dev/null +++ b/vm/src/stdlib/posix.rs @@ -0,0 +1,1662 @@ +use crate::{PyResult, VirtualMachine}; +use nix; +use std::os::unix::io::RawFd; + +pub(crate) fn raw_set_inheritable(fd: RawFd, inheritable: bool) -> nix::Result<()> { + use nix::fcntl; + let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?); + let mut new_flags = flags; + new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable); + if flags != new_flags { + fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?; + } + Ok(()) +} + +pub(super) fn bytes_as_osstr<'a>( + b: &'a [u8], + _vm: &VirtualMachine, +) -> PyResult<&'a std::ffi::OsStr> { + use std::os::unix::ffi::OsStrExt; + Ok(std::ffi::OsStr::from_bytes(b)) +} + +#[pymodule] +pub mod posix { + use crate::{ + builtins::{int, PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, + crt_fd::Offset, + exceptions::IntoPyException, + function::{FuncArgs, OptionalArg}, + slots::SlotConstructor, + stdlib::os::{ + errno_err, DirFd, FollowSymlinks, PathOrFd, PyPathLike, SupportFunc, TargetIsDirectory, + _os, fs_metadata, + }, + utils::{Either, ToCString}, + IntoPyObject, ItemProtocol, PyObjectRef, PyResult, PyValue, StaticType, + TryFromBorrowedObject, TryFromObject, VirtualMachine, + }; + use bitflags::bitflags; + use nix::fcntl; + use nix::unistd::{self, Gid, Pid, Uid}; + #[allow(unused_imports)] // TODO: use will be unnecessary in edition 2021 + use std::convert::TryFrom; + use std::ffi::{CStr, CString}; + use std::os::unix::ffi as ffi_ext; + use std::os::unix::io::RawFd; + use std::{env, fs, io}; + use strum_macros::EnumString; + + #[pyattr] + use libc::{PRIO_PGRP, PRIO_PROCESS, PRIO_USER}; + + #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] + #[pyattr] + use libc::{SEEK_DATA, SEEK_HOLE}; + #[pyattr] + pub(crate) const F_OK: u8 = 0; + #[pyattr] + pub(crate) const R_OK: u8 = 4; + #[pyattr] + pub(crate) const W_OK: u8 = 2; + #[pyattr] + pub(crate) const X_OK: u8 = 1; + + #[cfg(not(any(target_os = "redox", target_os = "freebsd")))] + #[pyattr] + use libc::O_DSYNC; + #[pyattr] + use libc::{O_CLOEXEC, O_NONBLOCK, WNOHANG}; + #[cfg(not(target_os = "redox"))] + #[pyattr] + use libc::{O_NDELAY, O_NOCTTY}; + + #[pyattr] + const EX_OK: i8 = exitcode::OK as i8; + #[pyattr] + const EX_USAGE: i8 = exitcode::USAGE as i8; + #[pyattr] + const EX_DATAERR: i8 = exitcode::DATAERR as i8; + #[pyattr] + const EX_NOINPUT: i8 = exitcode::NOINPUT as i8; + #[pyattr] + const EX_NOUSER: i8 = exitcode::NOUSER as i8; + #[pyattr] + const EX_NOHOST: i8 = exitcode::NOHOST as i8; + #[pyattr] + const EX_UNAVAILABLE: i8 = exitcode::UNAVAILABLE as i8; + #[pyattr] + const EX_SOFTWARE: i8 = exitcode::SOFTWARE as i8; + #[pyattr] + const EX_OSERR: i8 = exitcode::OSERR as i8; + #[pyattr] + const EX_OSFILE: i8 = exitcode::OSFILE as i8; + #[pyattr] + const EX_CANTCREAT: i8 = exitcode::CANTCREAT as i8; + #[pyattr] + const EX_IOERR: i8 = exitcode::IOERR as i8; + #[pyattr] + const EX_TEMPFAIL: i8 = exitcode::TEMPFAIL as i8; + #[pyattr] + const EX_PROTOCOL: i8 = exitcode::PROTOCOL as i8; + #[pyattr] + const EX_NOPERM: i8 = exitcode::NOPERM as i8; + #[pyattr] + const EX_CONFIG: i8 = exitcode::CONFIG as i8; + + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd" + ))] + #[pyattr] + const SCHED_RR: i32 = libc::SCHED_RR; + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd" + ))] + #[pyattr] + const SCHED_FIFO: i32 = libc::SCHED_FIFO; + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd" + ))] + #[pyattr] + const SCHED_OTHER: i32 = libc::SCHED_OTHER; + #[cfg(any(target_os = "linux", target_os = "android"))] + #[pyattr] + const SCHED_IDLE: i32 = libc::SCHED_IDLE; + #[cfg(any(target_os = "linux", target_os = "android"))] + #[pyattr] + const SCHED_BATCH: i32 = libc::SCHED_BATCH; + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[pyattr] + const POSIX_SPAWN_OPEN: i32 = PosixSpawnFileActionIdentifier::Open as i32; + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[pyattr] + const POSIX_SPAWN_CLOSE: i32 = PosixSpawnFileActionIdentifier::Close as i32; + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[pyattr] + const POSIX_SPAWN_DUP2: i32 = PosixSpawnFileActionIdentifier::Dup2 as i32; + + #[cfg(target_os = "macos")] + #[pyattr] + const _COPYFILE_DATA: u32 = 1 << 3; + + pub(crate) const SYMLINK_DIR_FD: bool = cfg!(not(target_os = "redox")); + + // Flags for os_access + bitflags! { + pub struct AccessFlags: u8{ + const F_OK = F_OK; + const R_OK = R_OK; + const W_OK = W_OK; + const X_OK = X_OK; + } + } + + struct Permissions { + is_readable: bool, + is_writable: bool, + is_executable: bool, + } + + fn get_permissions(mode: u32) -> Permissions { + Permissions { + is_readable: mode & 4 != 0, + is_writable: mode & 2 != 0, + is_executable: mode & 1 != 0, + } + } + + fn get_right_permission( + mode: u32, + file_owner: Uid, + file_group: Gid, + ) -> nix::Result { + let owner_mode = (mode & 0o700) >> 6; + let owner_permissions = get_permissions(owner_mode); + + let group_mode = (mode & 0o070) >> 3; + let group_permissions = get_permissions(group_mode); + + let others_mode = mode & 0o007; + let others_permissions = get_permissions(others_mode); + + let user_id = nix::unistd::getuid(); + let groups_ids = getgroups_impl()?; + + if file_owner == user_id { + Ok(owner_permissions) + } else if groups_ids.contains(&file_group) { + Ok(group_permissions) + } else { + Ok(others_permissions) + } + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn getgroups_impl() -> nix::Result> { + use libc::{c_int, gid_t}; + use nix::errno::Errno; + use std::ptr; + let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; + let mut groups = Vec::::with_capacity(Errno::result(ret)? as usize); + let ret = unsafe { + libc::getgroups( + groups.capacity() as c_int, + groups.as_mut_ptr() as *mut gid_t, + ) + }; + + Errno::result(ret).map(|s| { + unsafe { groups.set_len(s as usize) }; + groups + }) + } + + #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "redox")))] + use nix::unistd::getgroups as getgroups_impl; + + #[cfg(target_os = "redox")] + fn getgroups_impl() -> nix::Result> { + Err(nix::Error::EOPNOTSUPP) + } + + #[pyfunction] + fn getgroups(vm: &VirtualMachine) -> PyResult { + let group_ids = getgroups_impl().map_err(|e| e.into_pyexception(vm))?; + Ok(vm.ctx.new_list( + group_ids + .into_iter() + .map(|gid| vm.ctx.new_int(gid.as_raw())) + .collect(), + )) + } + + #[pyfunction] + pub(super) fn access(path: PyPathLike, mode: u8, vm: &VirtualMachine) -> PyResult { + use std::os::unix::fs::MetadataExt; + + let flags = AccessFlags::from_bits(mode).ok_or_else(|| { + vm.new_value_error( + "One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK" + .to_owned(), + ) + })?; + + let metadata = fs::metadata(&path.path); + + // if it's only checking for F_OK + if flags == AccessFlags::F_OK { + return Ok(metadata.is_ok()); + } + + let metadata = metadata.map_err(|err| err.into_pyexception(vm))?; + + let user_id = metadata.uid(); + let group_id = metadata.gid(); + let mode = metadata.mode(); + + let perm = get_right_permission(mode, Uid::from_raw(user_id), Gid::from_raw(group_id)) + .map_err(|err| err.into_pyexception(vm))?; + + let r_ok = !flags.contains(AccessFlags::R_OK) || perm.is_readable; + let w_ok = !flags.contains(AccessFlags::W_OK) || perm.is_writable; + let x_ok = !flags.contains(AccessFlags::X_OK) || perm.is_executable; + + Ok(r_ok && w_ok && x_ok) + } + + #[pyattr] + fn environ(vm: &VirtualMachine) -> PyDictRef { + use ffi_ext::OsStringExt; + + let environ = vm.ctx.new_dict(); + for (key, value) in env::vars_os() { + environ + .set_item( + vm.ctx.new_bytes(key.into_vec()), + vm.ctx.new_bytes(value.into_vec()), + vm, + ) + .unwrap(); + } + + environ + } + + #[derive(FromArgs)] + pub(super) struct SimlinkArgs { + #[pyarg(any)] + src: PyPathLike, + #[pyarg(any)] + dst: PyPathLike, + #[pyarg(flatten)] + _target_is_directory: TargetIsDirectory, + #[pyarg(flatten)] + dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + } + + #[pyfunction] + pub(super) fn symlink(args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { + let src = args.src.into_cstring(vm)?; + let dst = args.dst.into_cstring(vm)?; + #[cfg(not(target_os = "redox"))] + { + nix::unistd::symlinkat(&*src, args.dir_fd.get_opt(), &*dst) + .map_err(|err| err.into_pyexception(vm)) + } + #[cfg(target_os = "redox")] + { + let [] = args.dir_fd.0; + let res = unsafe { libc::symlink(src.as_ptr(), dst.as_ptr()) }; + if res < 0 { + Err(errno_err(vm)) + } else { + Ok(()) + } + } + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn fchdir(fd: RawFd, vm: &VirtualMachine) -> PyResult<()> { + nix::unistd::fchdir(fd).map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn chroot(path: PyPathLike, vm: &VirtualMachine) -> PyResult<()> { + nix::unistd::chroot(&*path.path).map_err(|err| err.into_pyexception(vm)) + } + + // As of now, redox does not seems to support chown command (cf. https://gitlab.redox-os.org/redox-os/coreutils , last checked on 05/07/2020) + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn chown( + path: PathOrFd, + uid: isize, + gid: isize, + dir_fd: DirFd<1>, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, + ) -> PyResult<()> { + let uid = if uid >= 0 { + Some(nix::unistd::Uid::from_raw(uid as u32)) + } else if uid == -1 { + None + } else { + return Err(vm.new_os_error(String::from("Specified uid is not valid."))); + }; + + let gid = if gid >= 0 { + Some(nix::unistd::Gid::from_raw(gid as u32)) + } else if gid == -1 { + None + } else { + return Err(vm.new_os_error(String::from("Specified gid is not valid."))); + }; + + let flag = if follow_symlinks.0 { + nix::unistd::FchownatFlags::FollowSymlink + } else { + nix::unistd::FchownatFlags::NoFollowSymlink + }; + + let dir_fd = dir_fd.get_opt(); + match path { + PathOrFd::Path(p) => nix::unistd::fchownat(dir_fd, p.path.as_os_str(), uid, gid, flag), + PathOrFd::Fd(fd) => nix::unistd::fchown(fd, uid, gid), + } + .map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn lchown(path: PyPathLike, uid: isize, gid: isize, vm: &VirtualMachine) -> PyResult<()> { + chown( + PathOrFd::Path(path), + uid, + gid, + DirFd::default(), + FollowSymlinks(false), + vm, + ) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn fchown(fd: i32, uid: isize, gid: isize, vm: &VirtualMachine) -> PyResult<()> { + chown( + PathOrFd::Fd(fd), + uid, + gid, + DirFd::default(), + FollowSymlinks(true), + vm, + ) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn nice(increment: i32, vm: &VirtualMachine) -> PyResult { + use nix::errno::{errno, Errno}; + Errno::clear(); + let res = unsafe { libc::nice(increment) }; + if res == -1 && errno() != 0 { + Err(errno_err(vm)) + } else { + Ok(res) + } + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn sched_get_priority_max(policy: i32, vm: &VirtualMachine) -> PyResult { + let max = unsafe { libc::sched_get_priority_max(policy) }; + if max == -1 { + Err(errno_err(vm)) + } else { + Ok(max) + } + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn sched_get_priority_min(policy: i32, vm: &VirtualMachine) -> PyResult { + let min = unsafe { libc::sched_get_priority_min(policy) }; + if min == -1 { + Err(errno_err(vm)) + } else { + Ok(min) + } + } + + #[pyfunction] + fn sched_yield(vm: &VirtualMachine) -> PyResult<()> { + let _ = nix::sched::sched_yield().map_err(|e| e.into_pyexception(vm))?; + Ok(()) + } + + #[pyattr] + #[pyclass(name = "sched_param")] + #[derive(Debug, PyValue)] + struct SchedParam { + sched_priority: PyObjectRef, + } + + impl TryFromObject for SchedParam { + fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + Ok(SchedParam { + sched_priority: obj, + }) + } + } + + #[pyimpl(with(SlotConstructor))] + impl SchedParam { + #[pyproperty] + fn sched_priority(&self, vm: &VirtualMachine) -> PyObjectRef { + self.sched_priority.clone().into_pyobject(vm) + } + + #[pymethod(magic)] + fn repr(&self, vm: &VirtualMachine) -> PyResult { + let sched_priority_repr = vm.to_repr(&self.sched_priority)?; + Ok(format!( + "posix.sched_param(sched_priority = {})", + sched_priority_repr.as_str() + )) + } + } + + impl SlotConstructor for SchedParam { + type Args = SchedParam; + fn py_new(cls: PyTypeRef, sched_param: Self::Args, vm: &VirtualMachine) -> PyResult { + sched_param.into_pyresult_with_type(vm, cls) + } + } + + #[pyfunction] + fn get_inheritable(fd: RawFd, vm: &VirtualMachine) -> PyResult { + let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD); + match flags { + Ok(ret) => Ok((ret & libc::FD_CLOEXEC) == 0), + Err(err) => Err(err.into_pyexception(vm)), + } + } + + #[pyfunction] + fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> { + super::raw_set_inheritable(fd, inheritable).map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn get_blocking(fd: RawFd, vm: &VirtualMachine) -> PyResult { + let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFL); + match flags { + Ok(ret) => Ok((ret & libc::O_NONBLOCK) == 0), + Err(err) => Err(err.into_pyexception(vm)), + } + } + + #[pyfunction] + fn set_blocking(fd: RawFd, blocking: bool, vm: &VirtualMachine) -> PyResult<()> { + let _set_flag = || { + use nix::fcntl::{fcntl, FcntlArg, OFlag}; + + let flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?); + let mut new_flags = flags; + new_flags.set(OFlag::from_bits_truncate(libc::O_NONBLOCK), !blocking); + if flags != new_flags { + fcntl(fd, FcntlArg::F_SETFL(new_flags))?; + } + Ok(()) + }; + _set_flag().map_err(|err: nix::Error| err.into_pyexception(vm)) + } + + #[pyfunction] + fn pipe(vm: &VirtualMachine) -> PyResult<(RawFd, RawFd)> { + use nix::unistd::close; + use nix::unistd::pipe; + let (rfd, wfd) = pipe().map_err(|err| err.into_pyexception(vm))?; + set_inheritable(rfd, false, vm) + .and_then(|_| set_inheritable(wfd, false, vm)) + .map_err(|err| { + let _ = close(rfd); + let _ = close(wfd); + err + })?; + Ok((rfd, wfd)) + } + + // cfg from nix + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + #[pyfunction] + fn pipe2(flags: libc::c_int, vm: &VirtualMachine) -> PyResult<(RawFd, RawFd)> { + let oflags = fcntl::OFlag::from_bits_truncate(flags); + nix::unistd::pipe2(oflags).map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn system(command: PyStrRef, vm: &VirtualMachine) -> PyResult { + let cstr = command.to_cstring(vm)?; + let x = unsafe { libc::system(cstr.as_ptr()) }; + Ok(x) + } + + #[pyfunction] + fn chmod( + path: PyPathLike, + dir_fd: DirFd<0>, + mode: u32, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, + ) -> PyResult<()> { + let [] = dir_fd.0; + let body = move || { + use std::os::unix::fs::PermissionsExt; + let meta = fs_metadata(&path, follow_symlinks.0)?; + let mut permissions = meta.permissions(); + permissions.set_mode(mode); + fs::set_permissions(&path, permissions) + }; + body().map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn execv( + path: PyStrRef, + argv: Either, + vm: &VirtualMachine, + ) -> PyResult<()> { + let path = path.to_cstring(vm)?; + + let argv = vm.extract_elements_func(argv.as_object(), |obj| { + PyStrRef::try_from_object(vm, obj)?.to_cstring(vm) + })?; + let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect(); + + let first = argv + .first() + .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty".to_owned()))?; + if first.to_bytes().is_empty() { + return Err( + vm.new_value_error("execv() arg 2 first element cannot be empty".to_owned()) + ); + } + + unistd::execv(&path, &argv) + .map(|_ok| ()) + .map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn execve( + path: PyPathLike, + argv: Either, + env: PyDictRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + let path = path.into_cstring(vm)?; + + let argv = vm.extract_elements_func(argv.as_object(), |obj| { + PyStrRef::try_from_object(vm, obj)?.to_cstring(vm) + })?; + let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect(); + + let first = argv + .first() + .ok_or_else(|| vm.new_value_error("execve() arg 2 must not be empty".to_owned()))?; + + if first.to_bytes().is_empty() { + return Err( + vm.new_value_error("execve() arg 2 first element cannot be empty".to_owned()) + ); + } + + let env = env + .into_iter() + .map(|(k, v)| -> PyResult<_> { + let (key, value) = ( + PyPathLike::try_from_object(vm, k)?.into_bytes(), + PyPathLike::try_from_object(vm, v)?.into_bytes(), + ); + + if memchr::memchr(b'=', &key).is_some() { + return Err(vm.new_value_error("illegal environment variable name".to_owned())); + } + + let mut entry = key; + entry.push(b'='); + entry.extend_from_slice(&value); + + CString::new(entry).map_err(|err| err.into_pyexception(vm)) + }) + .collect::, _>>()?; + + let env: Vec<&CStr> = env.iter().map(|entry| entry.as_c_str()).collect(); + + unistd::execve(&path, &argv, &env).map_err(|err| err.into_pyexception(vm))?; + Ok(()) + } + + #[pyfunction] + fn getppid(vm: &VirtualMachine) -> PyObjectRef { + let ppid = unistd::getppid().as_raw(); + vm.ctx.new_int(ppid) + } + + #[pyfunction] + fn getgid(vm: &VirtualMachine) -> PyObjectRef { + let gid = unistd::getgid().as_raw(); + vm.ctx.new_int(gid) + } + + #[pyfunction] + fn getegid(vm: &VirtualMachine) -> PyObjectRef { + let egid = unistd::getegid().as_raw(); + vm.ctx.new_int(egid) + } + + #[pyfunction] + fn getpgid(pid: u32, vm: &VirtualMachine) -> PyResult { + match unistd::getpgid(Some(Pid::from_raw(pid as i32))) { + Ok(pgid) => Ok(vm.ctx.new_int(pgid.as_raw())), + Err(err) => Err(err.into_pyexception(vm)), + } + } + + #[pyfunction] + fn getpgrp(vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_int(unistd::getpgrp().as_raw())) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn getsid(pid: u32, vm: &VirtualMachine) -> PyResult { + match unistd::getsid(Some(Pid::from_raw(pid as i32))) { + Ok(sid) => Ok(vm.ctx.new_int(sid.as_raw())), + Err(err) => Err(err.into_pyexception(vm)), + } + } + + #[pyfunction] + fn getuid(vm: &VirtualMachine) -> PyObjectRef { + let uid = unistd::getuid().as_raw(); + vm.ctx.new_int(uid) + } + + #[pyfunction] + fn geteuid(vm: &VirtualMachine) -> PyObjectRef { + let euid = unistd::geteuid().as_raw(); + vm.ctx.new_int(euid) + } + + #[pyfunction] + fn setgid(gid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setgid(Gid::from_raw(gid)).map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn setegid(egid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setegid(Gid::from_raw(egid)).map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn setpgid(pid: u32, pgid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setpgid(Pid::from_raw(pid as i32), Pid::from_raw(pgid as i32)) + .map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn setsid(vm: &VirtualMachine) -> PyResult<()> { + unistd::setsid() + .map(|_ok| ()) + .map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn setuid(uid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setuid(Uid::from_raw(uid)).map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn seteuid(euid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::seteuid(Uid::from_raw(euid)).map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn setreuid(ruid: u32, euid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setuid(Uid::from_raw(ruid)).map_err(|err| err.into_pyexception(vm))?; + unistd::seteuid(Uid::from_raw(euid)).map_err(|err| err.into_pyexception(vm)) + } + + // cfg from nix + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + ))] + #[pyfunction] + fn setresuid(ruid: u32, euid: u32, suid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setresuid( + Uid::from_raw(ruid), + Uid::from_raw(euid), + Uid::from_raw(suid), + ) + .map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn openpty(vm: &VirtualMachine) -> PyResult { + let r = nix::pty::openpty(None, None).map_err(|err| err.into_pyexception(vm))?; + for fd in &[r.master, r.slave] { + super::raw_set_inheritable(*fd, false).map_err(|e| e.into_pyexception(vm))?; + } + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_int(r.master), vm.ctx.new_int(r.slave)])) + } + + #[pyfunction] + fn ttyname(fd: i32, vm: &VirtualMachine) -> PyResult { + let name = unsafe { libc::ttyname(fd) }; + if name.is_null() { + Err(errno_err(vm)) + } else { + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + Ok(vm.ctx.new_utf8_str(name)) + } + } + + #[pyfunction] + fn umask(mask: libc::mode_t) -> libc::mode_t { + unsafe { libc::umask(mask) } + } + + #[pyfunction] + fn uname(vm: &VirtualMachine) -> PyResult<_os::UnameResult> { + let info = uname::uname().map_err(|err| err.into_pyexception(vm))?; + Ok(_os::UnameResult { + sysname: info.sysname, + nodename: info.nodename, + release: info.release, + version: info.version, + machine: info.machine, + }) + } + + #[pyfunction] + fn sync() { + #[cfg(not(any(target_os = "redox", target_os = "android")))] + unsafe { + libc::sync(); + } + } + + // cfg from nix + #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] + #[pyfunction] + fn getresuid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { + let mut ruid = 0; + let mut euid = 0; + let mut suid = 0; + let ret = unsafe { libc::getresuid(&mut ruid, &mut euid, &mut suid) }; + if ret == 0 { + Ok((ruid, euid, suid)) + } else { + Err(errno_err(vm)) + } + } + + // cfg from nix + #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] + #[pyfunction] + fn getresgid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { + let mut rgid = 0; + let mut egid = 0; + let mut sgid = 0; + let ret = unsafe { libc::getresgid(&mut rgid, &mut egid, &mut sgid) }; + if ret == 0 { + Ok((rgid, egid, sgid)) + } else { + Err(errno_err(vm)) + } + } + + // cfg from nix + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + ))] + #[pyfunction] + fn setresgid(rgid: u32, egid: u32, sgid: u32, vm: &VirtualMachine) -> PyResult<()> { + unistd::setresgid( + Gid::from_raw(rgid), + Gid::from_raw(egid), + Gid::from_raw(sgid), + ) + .map_err(|err| err.into_pyexception(vm)) + } + + // cfg from nix + #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] + #[pyfunction] + fn setregid(rgid: u32, egid: u32, vm: &VirtualMachine) -> PyResult<()> { + let ret = unsafe { libc::setregid(rgid, egid) }; + if ret == 0 { + Ok(()) + } else { + Err(errno_err(vm)) + } + } + + // cfg from nix + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + ))] + #[pyfunction] + fn initgroups(user_name: PyStrRef, gid: u32, vm: &VirtualMachine) -> PyResult<()> { + let user = CString::new(user_name.as_str()).unwrap(); + let gid = Gid::from_raw(gid); + unistd::initgroups(&user, gid).map_err(|err| err.into_pyexception(vm)) + } + + // cfg from nix + #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] + #[pyfunction] + fn setgroups( + group_ids: crate::function::ArgIterable, + vm: &VirtualMachine, + ) -> PyResult<()> { + let gids = group_ids + .iter(vm)? + .map(|entry| match entry { + Ok(id) => Ok(unistd::Gid::from_raw(id)), + Err(err) => Err(err), + }) + .collect::, _>>()?; + let ret = unistd::setgroups(&gids); + ret.map_err(|err| err.into_pyexception(vm)) + } + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + fn envp_from_dict(dict: PyDictRef, vm: &VirtualMachine) -> PyResult> { + dict.into_iter() + .map(|(k, v)| { + let k = PyPathLike::try_from_object(vm, k)?.into_bytes(); + let v = PyPathLike::try_from_object(vm, v)?.into_bytes(); + if k.contains(&0) { + return Err( + vm.new_value_error("envp dict key cannot contain a nul byte".to_owned()) + ); + } + if k.contains(&b'=') { + return Err(vm.new_value_error( + "envp dict key cannot contain a '=' character".to_owned(), + )); + } + if v.contains(&0) { + return Err( + vm.new_value_error("envp dict value cannot contain a nul byte".to_owned()) + ); + } + let mut env = k; + env.push(b'='); + env.extend(v); + Ok(unsafe { CString::from_vec_unchecked(env) }) + }) + .collect() + } + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[derive(FromArgs)] + pub(super) struct PosixSpawnArgs { + #[pyarg(positional)] + path: PyPathLike, + #[pyarg(positional)] + args: crate::function::ArgIterable, + #[pyarg(positional)] + env: crate::builtins::dict::PyMapping, + #[pyarg(named, default)] + file_actions: Option>, + #[pyarg(named, default)] + setsigdef: Option>, + } + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] + #[repr(i32)] + enum PosixSpawnFileActionIdentifier { + Open, + Close, + Dup2, + } + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + impl PosixSpawnArgs { + fn spawn(self, spawnp: bool, vm: &VirtualMachine) -> PyResult { + let path = CString::new(self.path.into_bytes()) + .map_err(|_| vm.new_value_error("path should not have nul bytes".to_owned()))?; + + let mut file_actions = unsafe { + let mut fa = std::mem::MaybeUninit::uninit(); + assert!(libc::posix_spawn_file_actions_init(fa.as_mut_ptr()) == 0); + fa.assume_init() + }; + if let Some(it) = self.file_actions { + for action in it.iter(vm)? { + let action = action?; + let (id, args) = action.as_slice().split_first().ok_or_else(|| { + vm.new_type_error( + "Each file_actions element must be a non-empty tuple".to_owned(), + ) + })?; + let id = i32::try_from_borrowed_object(vm, id)?; + let id = PosixSpawnFileActionIdentifier::try_from(id).map_err(|_| { + vm.new_type_error("Unknown file_actions identifier".to_owned()) + })?; + let args = FuncArgs::from(args.to_vec()); + let ret = match id { + PosixSpawnFileActionIdentifier::Open => { + let (fd, path, oflag, mode): (_, PyPathLike, _, _) = args.bind(vm)?; + let path = CString::new(path.into_bytes()).map_err(|_| { + vm.new_value_error( + "POSIX_SPAWN_OPEN path should not have nul bytes".to_owned(), + ) + })?; + unsafe { + libc::posix_spawn_file_actions_addopen( + &mut file_actions, + fd, + path.as_ptr(), + oflag, + mode, + ) + } + } + PosixSpawnFileActionIdentifier::Close => { + let (fd,) = args.bind(vm)?; + unsafe { + libc::posix_spawn_file_actions_addclose(&mut file_actions, fd) + } + } + PosixSpawnFileActionIdentifier::Dup2 => { + let (fd, newfd) = args.bind(vm)?; + unsafe { + libc::posix_spawn_file_actions_adddup2(&mut file_actions, fd, newfd) + } + } + }; + if ret != 0 { + return Err(errno_err(vm)); + } + } + } + + let mut attrp = unsafe { + let mut sa = std::mem::MaybeUninit::uninit(); + assert!(libc::posix_spawnattr_init(sa.as_mut_ptr()) == 0); + sa.assume_init() + }; + if let Some(sigs) = self.setsigdef { + use nix::sys::signal; + let mut set = signal::SigSet::empty(); + for sig in sigs.iter(vm)? { + let sig = sig?; + let sig = signal::Signal::try_from(sig).map_err(|_| { + vm.new_value_error(format!("signal number {} out of range", sig)) + })?; + set.add(sig); + } + assert!( + unsafe { libc::posix_spawnattr_setsigdefault(&mut attrp, set.as_ref()) } == 0 + ); + } + + let mut args: Vec = self + .args + .iter(vm)? + .map(|res| { + CString::new(res?.into_bytes()).map_err(|_| { + vm.new_value_error("path should not have nul bytes".to_owned()) + }) + }) + .collect::>()?; + let argv: Vec<*mut libc::c_char> = args + .iter_mut() + .map(|s| s.as_ptr() as _) + .chain(std::iter::once(std::ptr::null_mut())) + .collect(); + let mut env = envp_from_dict(self.env.into_dict(), vm)?; + let envp: Vec<*mut libc::c_char> = env + .iter_mut() + .map(|s| s.as_ptr() as _) + .chain(std::iter::once(std::ptr::null_mut())) + .collect(); + + let mut pid = 0; + let ret = unsafe { + if spawnp { + libc::posix_spawnp( + &mut pid, + path.as_ptr(), + &file_actions, + &attrp, + argv.as_ptr(), + envp.as_ptr(), + ) + } else { + libc::posix_spawn( + &mut pid, + path.as_ptr(), + &file_actions, + &attrp, + argv.as_ptr(), + envp.as_ptr(), + ) + } + }; + + if ret == 0 { + Ok(pid) + } else { + Err(errno_err(vm)) + } + } + } + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[pyfunction] + fn posix_spawn(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult { + args.spawn(false, vm) + } + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + #[pyfunction] + fn posix_spawnp(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult { + args.spawn(true, vm) + } + + #[pyfunction(name = "WIFSIGNALED")] + fn wifsignaled(status: i32) -> bool { + libc::WIFSIGNALED(status) + } + #[pyfunction(name = "WIFSTOPPED")] + fn wifstopped(status: i32) -> bool { + libc::WIFSTOPPED(status) + } + #[pyfunction(name = "WIFEXITED")] + fn wifexited(status: i32) -> bool { + libc::WIFEXITED(status) + } + #[pyfunction(name = "WTERMSIG")] + fn wtermsig(status: i32) -> i32 { + libc::WTERMSIG(status) + } + #[pyfunction(name = "WSTOPSIG")] + fn wstopsig(status: i32) -> i32 { + libc::WSTOPSIG(status) + } + #[pyfunction(name = "WEXITSTATUS")] + fn wexitstatus(status: i32) -> i32 { + libc::WEXITSTATUS(status) + } + + #[pyfunction] + fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { + let mut status = 0; + let pid = unsafe { libc::waitpid(pid, &mut status, opt) }; + let pid = nix::Error::result(pid).map_err(|err| err.into_pyexception(vm))?; + Ok((pid, status)) + } + #[pyfunction] + fn wait(vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { + waitpid(-1, 0, vm) + } + + #[pyfunction] + fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { + { + let ret = unsafe { libc::kill(pid, sig as i32) }; + if ret == -1 { + Err(errno_err(vm)) + } else { + Ok(()) + } + } + } + + #[pyfunction] + fn get_terminal_size( + fd: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<_os::PyTerminalSize> { + let (columns, lines) = { + nix::ioctl_read_bad!(winsz, libc::TIOCGWINSZ, libc::winsize); + let mut w = libc::winsize { + ws_row: 0, + ws_col: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + unsafe { winsz(fd.unwrap_or(libc::STDOUT_FILENO), &mut w) } + .map_err(|err| err.into_pyexception(vm))?; + (w.ws_col.into(), w.ws_row.into()) + }; + Ok(_os::PyTerminalSize { columns, lines }) + } + + // from libstd: + // https://github.com/rust-lang/rust/blob/daecab3a784f28082df90cebb204998051f3557d/src/libstd/sys/unix/fs.rs#L1251 + #[cfg(target_os = "macos")] + 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, std::ptr::null_mut(), flags as u32) }; + if ret < 0 { + Err(errno_err(vm)) + } else { + Ok(()) + } + } + + #[pyfunction] + fn dup(fd: i32, vm: &VirtualMachine) -> PyResult { + let fd = nix::unistd::dup(fd).map_err(|e| e.into_pyexception(vm))?; + super::raw_set_inheritable(fd, false) + .map(|()| fd) + .map_err(|e| { + let _ = nix::unistd::close(fd); + e.into_pyexception(vm) + }) + } + + #[derive(FromArgs)] + struct Dup2Args { + #[pyarg(positional)] + fd: i32, + #[pyarg(positional)] + fd2: i32, + #[pyarg(any, default = "true")] + inheritable: bool, + } + + #[pyfunction] + fn dup2(args: Dup2Args, vm: &VirtualMachine) -> PyResult { + let fd = nix::unistd::dup2(args.fd, args.fd2).map_err(|e| e.into_pyexception(vm))?; + if !args.inheritable { + super::raw_set_inheritable(fd, false).map_err(|e| { + let _ = nix::unistd::close(fd); + e.into_pyexception(vm) + })? + } + Ok(fd) + } + + pub(crate) fn support_funcs() -> Vec { + vec![ + SupportFunc::new("chmod", Some(false), Some(false), Some(false)), + #[cfg(not(target_os = "redox"))] + SupportFunc::new("chroot", Some(false), None, None), + #[cfg(not(target_os = "redox"))] + SupportFunc::new("chown", Some(true), Some(true), Some(true)), + #[cfg(not(target_os = "redox"))] + SupportFunc::new("lchown", None, None, None), + #[cfg(not(target_os = "redox"))] + SupportFunc::new("fchown", Some(true), None, Some(true)), + SupportFunc::new("umask", Some(false), Some(false), Some(false)), + SupportFunc::new("execv", None, None, None), + SupportFunc::new("pathconf", Some(true), None, None), + ] + } + + /// Return a string containing the name of the user logged in on the + /// controlling terminal of the process. + /// + /// Exceptions: + /// + /// - `OSError`: Raised if login name could not be determined (`getlogin()` + /// returned a null pointer). + /// - `UnicodeDecodeError`: Raised if login name contained invalid UTF-8 bytes. + #[pyfunction] + fn getlogin(vm: &VirtualMachine) -> PyResult { + // Get a pointer to the login name string. The string is statically + // allocated and might be overwritten on subsequent calls to this + // function or to `cuserid()`. See man getlogin(3) for more information. + let ptr = unsafe { libc::getlogin() }; + if ptr.is_null() { + return Err(vm.new_os_error("unable to determine login name".to_owned())); + } + let slice = unsafe { CStr::from_ptr(ptr) }; + slice + .to_str() + .map(|s| s.to_owned()) + .map_err(|e| vm.new_unicode_decode_error(format!("unable to decode login name: {}", e))) + } + + // cfg from nix + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + ))] + #[pyfunction] + fn getgrouplist(user: PyStrRef, group: u32, vm: &VirtualMachine) -> PyResult { + let user = CString::new(user.as_str()).unwrap(); + let gid = Gid::from_raw(group); + let group_ids = unistd::getgrouplist(&user, gid).map_err(|err| err.into_pyexception(vm))?; + Ok(vm.ctx.new_list( + group_ids + .into_iter() + .map(|gid| vm.ctx.new_int(gid.as_raw())) + .collect(), + )) + } + + #[cfg(not(target_os = "redox"))] + cfg_if::cfg_if! { + if #[cfg(all(target_os = "linux", target_env = "gnu"))] { + type PriorityWhichType = libc::__priority_which_t; + } else { + type PriorityWhichType = libc::c_int; + } + } + #[cfg(not(target_os = "redox"))] + cfg_if::cfg_if! { + if #[cfg(target_os = "freebsd")] { + type PriorityWhoType = i32; + } else { + type PriorityWhoType = u32; + } + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn getpriority( + which: PriorityWhichType, + who: PriorityWhoType, + vm: &VirtualMachine, + ) -> PyResult { + use nix::errno::{errno, Errno}; + Errno::clear(); + let retval = unsafe { libc::getpriority(which, who) }; + if errno() != 0 { + Err(errno_err(vm)) + } else { + Ok(vm.ctx.new_int(retval)) + } + } + + #[cfg(not(target_os = "redox"))] + #[pyfunction] + fn setpriority( + which: PriorityWhichType, + who: PriorityWhoType, + priority: i32, + vm: &VirtualMachine, + ) -> PyResult<()> { + let retval = unsafe { libc::setpriority(which, who, priority) }; + if retval == -1 { + Err(errno_err(vm)) + } else { + Ok(()) + } + } + + struct ConfName(i32); + + impl TryFromObject for ConfName { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + let i = match obj.downcast::() { + Ok(int) => int::try_to_primitive(int.as_bigint(), vm)?, + Err(obj) => { + let s = PyStrRef::try_from_object(vm, obj)?; + s.as_str().parse::().map_err(|_| { + vm.new_value_error("unrecognized configuration name".to_string()) + })? as i32 + } + }; + Ok(Self(i)) + } + } + + // Copy from [nix::unistd::PathconfVar](https://docs.rs/nix/0.21.0/nix/unistd/enum.PathconfVar.html) + // Change enum name to fit python doc + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, EnumString)] + #[repr(i32)] + #[allow(non_camel_case_types)] + pub enum PathconfVar { + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox" + ))] + /// Minimum number of bits needed to represent, as a signed integer value, + /// the maximum size of a regular file allowed in the specified directory. + PC_FILESIZEBITS = libc::_PC_FILESIZEBITS, + /// Maximum number of links to a single file. + PC_LINK_MAX = libc::_PC_LINK_MAX, + /// Maximum number of bytes in a terminal canonical input line. + PC_MAX_CANON = libc::_PC_MAX_CANON, + /// Minimum number of bytes for which space is available in a terminal input + /// queue; therefore, the maximum number of bytes a conforming application + /// may require to be typed as input before reading them. + PC_MAX_INPUT = libc::_PC_MAX_INPUT, + /// Maximum number of bytes in a filename (not including the terminating + /// null of a filename string). + PC_NAME_MAX = libc::_PC_NAME_MAX, + /// Maximum number of bytes the implementation will store as a pathname in a + /// user-supplied buffer of unspecified size, including the terminating null + /// character. Minimum number the implementation will accept as the maximum + /// number of bytes in a pathname. + PC_PATH_MAX = libc::_PC_PATH_MAX, + /// Maximum number of bytes that is guaranteed to be atomic when writing to + /// a pipe. + PC_PIPE_BUF = libc::_PC_PIPE_BUF, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris" + ))] + /// Symbolic links can be created. + PC_2_SYMLINKS = libc::_PC_2_SYMLINKS, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd", + target_os = "redox" + ))] + /// Minimum number of bytes of storage actually allocated for any portion of + /// a file. + PC_ALLOC_SIZE_MIN = libc::_PC_ALLOC_SIZE_MIN, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + ))] + /// Recommended increment for file transfer sizes between the + /// `POSIX_REC_MIN_XFER_SIZE` and `POSIX_REC_MAX_XFER_SIZE` values. + PC_REC_INCR_XFER_SIZE = libc::_PC_REC_INCR_XFER_SIZE, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd", + target_os = "redox" + ))] + /// Maximum recommended file transfer size. + PC_REC_MAX_XFER_SIZE = libc::_PC_REC_MAX_XFER_SIZE, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd", + target_os = "redox" + ))] + /// Minimum recommended file transfer size. + PC_REC_MIN_XFER_SIZE = libc::_PC_REC_MIN_XFER_SIZE, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd", + target_os = "redox" + ))] + /// Recommended file transfer buffer alignment. + PC_REC_XFER_ALIGN = libc::_PC_REC_XFER_ALIGN, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris" + ))] + /// Maximum number of bytes in a symbolic link. + PC_SYMLINK_MAX = libc::_PC_SYMLINK_MAX, + /// The use of `chown` and `fchown` is restricted to a process with + /// appropriate privileges, and to changing the group ID of a file only to + /// the effective group ID of the process or to one of its supplementary + /// group IDs. + PC_CHOWN_RESTRICTED = libc::_PC_CHOWN_RESTRICTED, + /// Pathname components longer than {NAME_MAX} generate an error. + PC_NO_TRUNC = libc::_PC_NO_TRUNC, + /// This symbol shall be defined to be the value of a character that shall + /// disable terminal special character handling. + PC_VDISABLE = libc::_PC_VDISABLE, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris" + ))] + /// Asynchronous input or output operations may be performed for the + /// associated file. + PC_ASYNC_IO = libc::_PC_ASYNC_IO, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris" + ))] + /// Prioritized input or output operations may be performed for the + /// associated file. + PC_PRIO_IO = libc::_PC_PRIO_IO, + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris" + ))] + /// Synchronized input or output operations may be performed for the + /// associated file. + PC_SYNC_IO = libc::_PC_SYNC_IO, + #[cfg(any(target_os = "dragonfly", target_os = "openbsd"))] + /// The resolution in nanoseconds for all file timestamps. + PC_TIMESTAMP_RESOLUTION = libc::_PC_TIMESTAMP_RESOLUTION, + } + + #[cfg(unix)] + #[pyfunction] + fn pathconf( + path: PathOrFd, + ConfName(name): ConfName, + vm: &VirtualMachine, + ) -> PyResult> { + use nix::errno::{self, Errno}; + + Errno::clear(); + let raw = match path { + PathOrFd::Path(path) => { + let path = CString::new(path.into_bytes()) + .map_err(|_| vm.new_value_error("embedded null character".to_owned()))?; + unsafe { libc::pathconf(path.as_ptr(), name) } + } + PathOrFd::Fd(fd) => unsafe { libc::fpathconf(fd, name) }, + }; + + if raw == -1 { + if errno::errno() == 0 { + Ok(None) + } else { + Err(io::Error::from(Errno::last()).into_pyexception(vm)) + } + } else { + Ok(Some(raw)) + } + } + + #[pyfunction] + fn fpathconf(fd: i32, name: ConfName, vm: &VirtualMachine) -> PyResult> { + pathconf(PathOrFd::Fd(fd), name, vm) + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + #[derive(FromArgs)] + struct SendFileArgs { + #[pyarg(any)] + out_fd: i32, + #[pyarg(any)] + in_fd: i32, + #[pyarg(any)] + offset: Offset, + #[pyarg(any)] + count: i64, + #[cfg(target_os = "macos")] + #[pyarg(any, optional)] + headers: OptionalArg, + #[cfg(target_os = "macos")] + #[pyarg(any, optional)] + trailers: OptionalArg, + #[cfg(target_os = "macos")] + #[allow(dead_code)] + #[pyarg(any, default)] + // TODO: not implemented + flags: OptionalArg, + } + + #[cfg(target_os = "linux")] + #[pyfunction] + fn sendfile(args: SendFileArgs, vm: &VirtualMachine) -> PyResult { + let mut file_offset = args.offset; + + let res = nix::sys::sendfile::sendfile( + args.out_fd, + args.in_fd, + Some(&mut file_offset), + args.count as usize, + ) + .map_err(|err| err.into_pyexception(vm))?; + Ok(vm.ctx.new_int(res as u64)) + } + + #[cfg(target_os = "macos")] + fn _extract_vec_bytes( + x: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult>> { + let inner = match x.into_option() { + Some(v) => { + let v = vm.extract_elements::(&v)?; + if v.is_empty() { + None + } else { + Some(v) + } + } + None => None, + }; + Ok(inner) + } + + #[cfg(target_os = "macos")] + #[pyfunction] + fn sendfile(args: SendFileArgs, vm: &VirtualMachine) -> PyResult { + let headers = _extract_vec_bytes(args.headers, vm)?; + let count = headers + .as_ref() + .map(|v| v.iter().map(|s| s.len()).sum()) + .unwrap_or(0) as i64 + + args.count; + + let headers = headers + .as_ref() + .map(|v| v.iter().map(|b| b.borrow_buf()).collect::>()); + let headers = headers + .as_ref() + .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); + let headers = headers.as_deref(); + + let trailers = _extract_vec_bytes(args.trailers, vm)?; + let trailers = trailers + .as_ref() + .map(|v| v.iter().map(|b| b.borrow_buf()).collect::>()); + let trailers = trailers + .as_ref() + .map(|v| v.iter().map(|borrowed| &**borrowed).collect::>()); + let trailers = trailers.as_deref(); + + let (res, written) = nix::sys::sendfile::sendfile( + args.in_fd, + args.out_fd, + args.offset, + Some(count), + headers, + trailers, + ); + res.map_err(|err| err.into_pyexception(vm))?; + Ok(vm.ctx.new_int(written as u64)) + } +} diff --git a/vm/src/stdlib/posixsubprocess.rs b/vm/src/stdlib/posixsubprocess.rs index 4d31f1959f..be137eb2f4 100644 --- a/vm/src/stdlib/posixsubprocess.rs +++ b/vm/src/stdlib/posixsubprocess.rs @@ -28,6 +28,9 @@ mod _posixsubprocess { } } +use super::os::PyPathLike; +use super::posix; +use crate::{PyObjectRef, PyResult, PySequence, TryFromObject, VirtualMachine}; use nix::{errno::Errno, unistd}; use std::convert::Infallible as Never; #[cfg(not(target_os = "redox"))] @@ -37,10 +40,6 @@ use std::io::{self, prelude::*}; #[cfg(not(target_os = "redox"))] use std::os::unix::io::AsRawFd; -use super::os; -use crate::VirtualMachine; -use crate::{PyObjectRef, PyResult, PySequence, TryFromObject}; - macro_rules! gen_args { ($($field:ident: $t:ty),*$(,)?) => { #[derive(FromArgs)] @@ -55,7 +54,7 @@ struct CStrPathLike { } impl TryFromObject for CStrPathLike { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let s = os::PyPathLike::try_from_object(vm, obj)?.into_cstring(vm)?; + let s = PyPathLike::try_from_object(vm, obj)?.into_cstring(vm)?; Ok(CStrPathLike { s }) } } @@ -93,7 +92,7 @@ fn exec(args: &ForkExecArgs, procargs: ProcArgs) -> ! { fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { for &fd in args.fds_to_keep.as_slice() { if fd != args.errpipe_write { - os::raw_set_inheritable(fd, true)? + posix::raw_set_inheritable(fd, true)? } } @@ -106,7 +105,7 @@ fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { let c2pwrite = if args.c2pwrite == 0 { let fd = unistd::dup(args.c2pwrite)?; - os::raw_set_inheritable(fd, true)?; + posix::raw_set_inheritable(fd, true)?; fd } else { args.c2pwrite @@ -115,12 +114,12 @@ fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { let mut errwrite = args.errwrite; while errwrite == 0 || errwrite == 1 { errwrite = unistd::dup(errwrite)?; - os::raw_set_inheritable(errwrite, true)?; + posix::raw_set_inheritable(errwrite, true)?; } let dup_into_stdio = |fd, io_fd| { if fd == io_fd { - os::raw_set_inheritable(fd, true) + posix::raw_set_inheritable(fd, true) } else if fd != -1 { unistd::dup2(fd, io_fd).map(drop) } else { From f61b73cd3f35a5b1607a052751d8b36a5e7a0d26 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 22 Sep 2021 16:06:38 +0900 Subject: [PATCH 2/6] split `posix_compat` from `os` --- vm/src/stdlib/io.rs | 12 +++--- vm/src/stdlib/mod.rs | 6 +++ vm/src/stdlib/os.rs | 62 +------------------------------ vm/src/stdlib/posix_compat.rs | 69 +++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 66 deletions(-) create mode 100644 vm/src/stdlib/posix_compat.rs diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index cbf9e26889..42d7ffcb5b 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -9,8 +9,6 @@ cfg_if::cfg_if! { } } -#[cfg(unix)] -use crate::stdlib::os::{errno_err, PathOrFd}; use crate::{PyObjectRef, PyResult, TryFromObject, VirtualMachine}; pub(crate) use _io::io_open as open; @@ -89,7 +87,7 @@ mod _io { PyThreadMutex, PyThreadMutexGuard, }; use crate::common::rc::PyRc; - use crate::exceptions::{self, IntoPyException, PyBaseExceptionRef}; + use crate::exceptions::{self, PyBaseExceptionRef}; use crate::function::{ArgIterable, FuncArgs, OptionalArg, OptionalOption}; use crate::slots::SlotConstructor; use crate::utils::Either; @@ -159,6 +157,7 @@ mod _io { fn os_err(vm: &VirtualMachine, err: io::Error) -> PyBaseExceptionRef { #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] { + use crate::exceptions::IntoPyException; err.into_pyexception(vm) } #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] @@ -3530,8 +3529,11 @@ mod _io { // check file descriptor validity #[cfg(unix)] - if let Ok(PathOrFd::Fd(fd)) = PathOrFd::try_from_object(vm, file.clone()) { - nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD).map_err(|_| errno_err(vm))?; + if let Ok(crate::stdlib::os::PathOrFd::Fd(fd)) = + TryFromObject::try_from_object(vm, file.clone()) + { + nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD) + .map_err(|_| crate::stdlib::os::errno_err(vm))?; } // Construct a FileIO (subclass of RawIOBase) diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 0405e8503e..f29792c068 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -54,6 +54,12 @@ mod zlib; pub(crate) mod os; #[cfg(unix)] pub(crate) mod posix; +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +#[cfg(not(any(unix, windows)))] +pub(crate) mod posix_compat; +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +#[cfg(not(any(unix, windows)))] +pub(crate) use posix_compat as posix; #[cfg(not(target_arch = "wasm32"))] mod faulthandler; diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 406905c327..6a6fca64a1 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -364,7 +364,7 @@ pub(super) struct FollowSymlinks( #[cfg(unix)] use super::posix::bytes_as_osstr; -#[cfg(unix)] +#[cfg(not(windows))] use super::posix::posix as platform; #[cfg(not(unix))] @@ -395,11 +395,6 @@ macro_rules! suppress_iph { }; } -#[allow(dead_code)] -fn os_unimpl(func: &str, vm: &VirtualMachine) -> PyResult { - Err(vm.new_os_error(format!("{} is not supported on this platform", func))) -} - #[pymodule(name = "os")] pub(super) mod _os { use super::*; @@ -2107,59 +2102,4 @@ pub(crate) use nt::raw_set_handle_inheritable; #[cfg(all(windows, target_env = "msvc"))] pub use nt::{_set_thread_local_invalid_parameter_handler, silent_iph_handler}; -#[cfg(not(any(unix, windows)))] -#[pymodule(name = "posix")] -mod minor { - use super::*; - use crate::builtins::PyDictRef; - - #[pyfunction] - pub(super) fn access(_path: PyStrRef, _mode: u8, vm: &VirtualMachine) -> PyResult { - os_unimpl("os.access", vm) - } - - pub const SYMLINK_DIR_FD: bool = false; - - #[derive(FromArgs)] - #[allow(unused)] - pub(super) struct SimlinkArgs { - #[pyarg(any)] - src: PyPathLike, - #[pyarg(any)] - dst: PyPathLike, - #[pyarg(flatten)] - _target_is_directory: TargetIsDirectory, - #[pyarg(flatten)] - _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, - } - - #[pyfunction] - pub(super) fn symlink(_args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { - os_unimpl("os.symlink", vm) - } - - #[pyattr] - fn environ(vm: &VirtualMachine) -> PyDictRef { - let environ = vm.ctx.new_dict(); - use ffi_ext::OsStringExt; - for (key, value) in env::vars_os() { - environ - .set_item( - vm.ctx.new_bytes(key.into_vec()), - vm.ctx.new_bytes(value.into_vec()), - vm, - ) - .unwrap(); - } - - environ - } - - pub(super) fn support_funcs() -> Vec { - Vec::new() - } -} -#[cfg(not(any(unix, windows)))] -use minor as platform; - pub(crate) use platform::MODULE_NAME; diff --git a/vm/src/stdlib/posix_compat.rs b/vm/src/stdlib/posix_compat.rs new file mode 100644 index 0000000000..a4eacf3936 --- /dev/null +++ b/vm/src/stdlib/posix_compat.rs @@ -0,0 +1,69 @@ +//! `posix` compatible module for `not(any(unix, windows))` + +#[pymodule(name = "posix")] +pub(crate) mod posix { + use crate::{ + builtins::PyStrRef, + stdlib::os::{DirFd, PyPathLike, SupportFunc, TargetIsDirectory}, + PyResult, VirtualMachine, + }; + use std::env; + #[cfg(unix)] + use std::os::unix::ffi as ffi_ext; + #[cfg(target_os = "wasi")] + use std::os::wasi::ffi as ffi_ext; + + #[pyfunction] + pub(super) fn access(_path: PyStrRef, _mode: u8, vm: &VirtualMachine) -> PyResult { + os_unimpl("os.access", vm) + } + + pub const SYMLINK_DIR_FD: bool = false; + + #[derive(FromArgs)] + #[allow(unused)] + pub(super) struct SimlinkArgs { + #[pyarg(any)] + src: PyPathLike, + #[pyarg(any)] + dst: PyPathLike, + #[pyarg(flatten)] + _target_is_directory: TargetIsDirectory, + #[pyarg(flatten)] + _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + } + + #[pyfunction] + pub(super) fn symlink(_args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { + os_unimpl("os.symlink", vm) + } + + #[cfg(target_os = "wasi")] + #[pyattr] + fn environ(vm: &VirtualMachine) -> crate::builtins::PyDictRef { + use crate::ItemProtocol; + use ffi_ext::OsStringExt; + + let environ = vm.ctx.new_dict(); + for (key, value) in env::vars_os() { + environ + .set_item( + vm.ctx.new_bytes(key.into_vec()), + vm.ctx.new_bytes(value.into_vec()), + vm, + ) + .unwrap(); + } + + environ + } + + #[allow(dead_code)] + fn os_unimpl(func: &str, vm: &VirtualMachine) -> PyResult { + Err(vm.new_os_error(format!("{} is not supported on this platform", func))) + } + + pub(crate) fn support_funcs() -> Vec { + Vec::new() + } +} From abc4b4196e6d97ff06c19204121a1996bbb65fd9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 22 Sep 2021 17:34:50 +0900 Subject: [PATCH 3/6] FsPath::to_pathlike --- vm/src/stdlib/os.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 6a6fca64a1..eca04809ed 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -123,12 +123,16 @@ impl FsPath { FsPath::Bytes(b) => bytes_as_osstr(b.as_bytes(), vm), } } - fn to_output_mode(&self) -> OutputMode { - match self { + + fn to_pathlike(&self, vm: &VirtualMachine) -> PyResult { + let path = self.as_os_str(vm)?.to_owned().into(); + let mode = match self { Self::Str(_) => OutputMode::String, Self::Bytes(_) => OutputMode::Bytes, - } + }; + Ok(PyPathLike { path, mode }) } + #[cfg(not(target_os = "redox"))] pub(crate) fn as_bytes(&self) -> &[u8] { // TODO: FS encodings @@ -202,11 +206,8 @@ impl TryFromObject for PyPathLike { Ok(buffer) => PyBytes::from(buffer.internal.obj_bytes().to_vec()).into_pyobject(vm), Err(_) => obj, }; - let path = fspath(obj, true, vm)?; - Ok(Self { - path: path.as_os_str(vm)?.to_owned().into(), - mode: path.to_output_mode(), - }) + let fs_path = fspath(obj, true, vm)?; + fs_path.to_pathlike(vm) } } @@ -402,7 +403,7 @@ pub(super) mod _os { use rustpython_common::lock::OnceCell; const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); - const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); + pub(crate) const MKDIR_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); const STAT_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); const UTIME_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); From c65bd6651e24b43ad0f7ce5ef82692cb35b03a83 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 22 Sep 2021 17:48:06 +0900 Subject: [PATCH 4/6] split `nt` from `os` --- vm/src/stdlib/mod.rs | 2 + vm/src/stdlib/msvcrt.rs | 2 +- vm/src/stdlib/nt.rs | 422 ++++++++++++++++++++++++++++++ vm/src/stdlib/os.rs | 559 ++++++---------------------------------- 4 files changed, 499 insertions(+), 486 deletions(-) create mode 100644 vm/src/stdlib/nt.rs diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index f29792c068..6f27b67cff 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -52,6 +52,8 @@ mod zlib; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] #[macro_use] pub(crate) mod os; +#[cfg(windows)] +pub(crate) mod nt; #[cfg(unix)] pub(crate) mod posix; #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] diff --git a/vm/src/stdlib/msvcrt.rs b/vm/src/stdlib/msvcrt.rs index 0b0cf30e0e..e11fb990eb 100644 --- a/vm/src/stdlib/msvcrt.rs +++ b/vm/src/stdlib/msvcrt.rs @@ -1,7 +1,7 @@ use super::os::errno_err; use crate::{ builtins::{PyBytes, PyStrRef}, - PyObjectRef, PyRef, PyResult, VirtualMachine, + suppress_iph, PyObjectRef, PyRef, PyResult, VirtualMachine, }; use itertools::Itertools; use winapi::{ diff --git a/vm/src/stdlib/nt.rs b/vm/src/stdlib/nt.rs new file mode 100644 index 0000000000..96d7e981dc --- /dev/null +++ b/vm/src/stdlib/nt.rs @@ -0,0 +1,422 @@ +#[pymodule] +pub(crate) mod nt { + use crate::{ + builtins::{PyStrRef, PyTupleRef}, + crt_fd::Fd, + exceptions::IntoPyException, + function::OptionalArg, + stdlib::os::{ + errno_err, DirFd, FollowSymlinks, PyPathLike, SupportFunc, TargetIsDirectory, _os, + errno, + }, + suppress_iph, + utils::Either, + PyResult, TryFromObject, VirtualMachine, + }; + use std::io; + use std::{env, fs}; + + #[cfg(target_env = "msvc")] + use crate::builtins::PyListRef; + use crate::{builtins::PyDictRef, ItemProtocol}; + use winapi::{um, vc::vcruntime::intptr_t}; + + #[pyattr] + use libc::{O_BINARY, O_TEMPORARY}; + + #[pyfunction] + pub(super) fn access(path: PyPathLike, mode: u8, vm: &VirtualMachine) -> PyResult { + use um::{fileapi, winnt}; + let attr = unsafe { fileapi::GetFileAttributesW(path.to_widecstring(vm)?.as_ptr()) }; + Ok(attr != fileapi::INVALID_FILE_ATTRIBUTES + && (mode & 2 == 0 + || attr & winnt::FILE_ATTRIBUTE_READONLY == 0 + || attr & winnt::FILE_ATTRIBUTE_DIRECTORY != 0)) + } + + pub const SYMLINK_DIR_FD: bool = false; + + #[derive(FromArgs)] + pub(super) struct SimlinkArgs { + #[pyarg(any)] + src: PyPathLike, + #[pyarg(any)] + dst: PyPathLike, + #[pyarg(flatten)] + target_is_directory: TargetIsDirectory, + #[pyarg(flatten)] + _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + } + + #[pyfunction] + pub(super) fn symlink(args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { + use std::os::windows::fs as win_fs; + let dir = args.target_is_directory.target_is_directory + || args + .dst + .path + .parent() + .and_then(|dst_parent| dst_parent.join(&args.src).symlink_metadata().ok()) + .map_or(false, |meta| meta.is_dir()); + let res = if dir { + win_fs::symlink_dir(args.src.path, args.dst.path) + } else { + win_fs::symlink_file(args.src.path, args.dst.path) + }; + res.map_err(|err| err.into_pyexception(vm)) + } + + #[pyfunction] + fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> { + let handle = Fd(fd).to_raw_handle().map_err(|e| e.into_pyexception(vm))?; + set_handle_inheritable(handle as _, inheritable, vm) + } + + #[pyattr] + fn environ(vm: &VirtualMachine) -> PyDictRef { + let environ = vm.ctx.new_dict(); + + for (key, value) in env::vars() { + environ + .set_item(vm.ctx.new_utf8_str(key), vm.ctx.new_utf8_str(value), vm) + .unwrap(); + } + environ + } + + #[pyfunction] + fn chmod( + path: PyPathLike, + dir_fd: DirFd<0>, + mode: u32, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, + ) -> PyResult<()> { + const S_IWRITE: u32 = 128; + let [] = dir_fd.0; + let metadata = if follow_symlinks.0 { + fs::metadata(&path) + } else { + fs::symlink_metadata(&path) + }; + let meta = metadata.map_err(|err| err.into_pyexception(vm))?; + let mut permissions = meta.permissions(); + permissions.set_readonly(mode & S_IWRITE == 0); + fs::set_permissions(&path, permissions).map_err(|err| err.into_pyexception(vm)) + } + + // cwait is available on MSVC only (according to CPython) + #[cfg(target_env = "msvc")] + extern "C" { + fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t; + } + + #[cfg(target_env = "msvc")] + #[pyfunction] + fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> { + let mut status = 0; + let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) }; + if pid == -1 { + Err(errno_err(vm)) + } else { + Ok((pid, status << 8)) + } + } + + #[cfg(target_env = "msvc")] + #[pyfunction] + fn wait(vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> { + waitpid(-1, 0, vm) + } + + #[pyfunction] + fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { + { + use um::{handleapi, processthreadsapi, wincon, winnt}; + let sig = sig as u32; + let pid = pid as u32; + + if sig == wincon::CTRL_C_EVENT || sig == wincon::CTRL_BREAK_EVENT { + let ret = unsafe { wincon::GenerateConsoleCtrlEvent(sig, pid) }; + let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) }; + return res; + } + + let h = unsafe { processthreadsapi::OpenProcess(winnt::PROCESS_ALL_ACCESS, 0, pid) }; + if h.is_null() { + return Err(errno_err(vm)); + } + let ret = unsafe { processthreadsapi::TerminateProcess(h, sig) }; + let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) }; + unsafe { handleapi::CloseHandle(h) }; + res + } + } + + #[pyfunction] + fn get_terminal_size( + fd: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<_os::PyTerminalSize> { + let (columns, lines) = { + use um::{handleapi, processenv, winbase, wincon}; + let stdhandle = match fd { + OptionalArg::Present(0) => winbase::STD_INPUT_HANDLE, + OptionalArg::Present(1) | OptionalArg::Missing => winbase::STD_OUTPUT_HANDLE, + OptionalArg::Present(2) => winbase::STD_ERROR_HANDLE, + _ => return Err(vm.new_value_error("bad file descriptor".to_owned())), + }; + let h = unsafe { processenv::GetStdHandle(stdhandle) }; + if h.is_null() { + return Err(vm.new_os_error("handle cannot be retrieved".to_owned())); + } + if h == handleapi::INVALID_HANDLE_VALUE { + return Err(errno_err(vm)); + } + let mut csbi = wincon::CONSOLE_SCREEN_BUFFER_INFO::default(); + let ret = unsafe { wincon::GetConsoleScreenBufferInfo(h, &mut csbi) }; + if ret == 0 { + return Err(errno_err(vm)); + } + let w = csbi.srWindow; + ( + (w.Right - w.Left + 1) as usize, + (w.Bottom - w.Top + 1) as usize, + ) + }; + Ok(_os::PyTerminalSize { columns, lines }) + } + + #[cfg(target_env = "msvc")] + type InvalidParamHandler = extern "C" fn( + *const libc::wchar_t, + *const libc::wchar_t, + *const libc::wchar_t, + libc::c_uint, + libc::uintptr_t, + ); + #[cfg(target_env = "msvc")] + extern "C" { + #[doc(hidden)] + pub fn _set_thread_local_invalid_parameter_handler( + pNew: InvalidParamHandler, + ) -> InvalidParamHandler; + } + + #[cfg(target_env = "msvc")] + #[doc(hidden)] + pub extern "C" fn silent_iph_handler( + _: *const libc::wchar_t, + _: *const libc::wchar_t, + _: *const libc::wchar_t, + _: libc::c_uint, + _: libc::uintptr_t, + ) { + } + + #[cfg(target_env = "msvc")] + extern "C" { + fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t; + } + + #[cfg(target_env = "msvc")] + #[pyfunction] + fn execv( + path: PyStrRef, + argv: Either, + vm: &VirtualMachine, + ) -> PyResult<()> { + use std::iter::once; + + let make_widestring = |s: &str| { + widestring::WideCString::from_os_str(s).map_err(|err| err.into_pyexception(vm)) + }; + + let path = make_widestring(path.as_str())?; + + let argv = vm.extract_elements_func(argv.as_object(), |obj| { + let arg = PyStrRef::try_from_object(vm, obj)?; + make_widestring(arg.as_str()) + })?; + + let first = argv + .first() + .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty".to_owned()))?; + + if first.is_empty() { + return Err( + vm.new_value_error("execv() arg 2 first element cannot be empty".to_owned()) + ); + } + + let argv_execv: Vec<*const u16> = argv + .iter() + .map(|v| v.as_ptr()) + .chain(once(std::ptr::null())) + .collect(); + + if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) { + Err(errno_err(vm)) + } else { + Ok(()) + } + } + + #[pyfunction] + fn _getfinalpathname(path: PyPathLike, vm: &VirtualMachine) -> PyResult { + let real = path + .as_ref() + .canonicalize() + .map_err(|e| e.into_pyexception(vm))?; + path.mode.process_path(real, vm) + } + + #[pyfunction] + fn _getfullpathname(path: PyPathLike, vm: &VirtualMachine) -> PyResult { + let wpath = path.to_widecstring(vm)?; + let mut buffer = vec![0u16; winapi::shared::minwindef::MAX_PATH]; + let ret = unsafe { + um::fileapi::GetFullPathNameW( + wpath.as_ptr(), + buffer.len() as _, + buffer.as_mut_ptr(), + std::ptr::null_mut(), + ) + }; + if ret == 0 { + return Err(errno_err(vm)); + } + if ret as usize > buffer.len() { + buffer.resize(ret as usize, 0); + let ret = unsafe { + um::fileapi::GetFullPathNameW( + wpath.as_ptr(), + buffer.len() as _, + buffer.as_mut_ptr(), + std::ptr::null_mut(), + ) + }; + if ret == 0 { + return Err(errno_err(vm)); + } + } + let buffer = widestring::WideCString::from_vec_with_nul(buffer).unwrap(); + path.mode.process_path(buffer.to_os_string(), vm) + } + + #[pyfunction] + fn _getvolumepathname(path: PyPathLike, vm: &VirtualMachine) -> PyResult { + let wide = path.to_widecstring(vm)?; + let buflen = std::cmp::max(wide.len(), winapi::shared::minwindef::MAX_PATH); + let mut buffer = vec![0u16; buflen]; + let ret = unsafe { + um::fileapi::GetVolumePathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buflen as _) + }; + if ret == 0 { + return Err(errno_err(vm)); + } + let buffer = widestring::WideCString::from_vec_with_nul(buffer).unwrap(); + path.mode.process_path(buffer.to_os_string(), vm) + } + + #[pyfunction] + fn _getdiskusage(path: PyPathLike, vm: &VirtualMachine) -> PyResult<(u64, u64)> { + use um::fileapi::GetDiskFreeSpaceExW; + use winapi::shared::{ntdef::ULARGE_INTEGER, winerror}; + let wpath = path.to_widecstring(vm)?; + let mut _free_to_me = ULARGE_INTEGER::default(); + let mut total = ULARGE_INTEGER::default(); + let mut free = ULARGE_INTEGER::default(); + let ret = + unsafe { GetDiskFreeSpaceExW(wpath.as_ptr(), &mut _free_to_me, &mut total, &mut free) }; + if ret != 0 { + return unsafe { Ok((*total.QuadPart(), *free.QuadPart())) }; + } + let err = io::Error::last_os_error(); + if err.raw_os_error() == Some(winerror::ERROR_DIRECTORY as i32) { + if let Some(parent) = path.as_ref().parent() { + let parent = widestring::WideCString::from_os_str(parent).unwrap(); + + let ret = unsafe { + GetDiskFreeSpaceExW(parent.as_ptr(), &mut _free_to_me, &mut total, &mut free) + }; + + if ret == 0 { + return Err(errno_err(vm)); + } else { + return unsafe { Ok((*total.QuadPart(), *free.QuadPart())) }; + } + } + } + return Err(err.into_pyexception(vm)); + } + + #[pyfunction] + fn get_handle_inheritable(handle: intptr_t, vm: &VirtualMachine) -> PyResult { + let mut flags = 0; + if unsafe { um::handleapi::GetHandleInformation(handle as _, &mut flags) } == 0 { + Err(errno_err(vm)) + } else { + Ok(flags & um::winbase::HANDLE_FLAG_INHERIT != 0) + } + } + + pub(crate) fn raw_set_handle_inheritable( + handle: intptr_t, + inheritable: bool, + ) -> io::Result<()> { + use um::winbase::HANDLE_FLAG_INHERIT; + let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 }; + let res = + unsafe { um::handleapi::SetHandleInformation(handle as _, HANDLE_FLAG_INHERIT, flags) }; + if res == 0 { + Err(errno()) + } else { + Ok(()) + } + } + + #[pyfunction] + fn set_handle_inheritable( + handle: intptr_t, + inheritable: bool, + vm: &VirtualMachine, + ) -> PyResult<()> { + raw_set_handle_inheritable(handle, inheritable).map_err(|e| e.into_pyexception(vm)) + } + + #[pyfunction] + fn mkdir( + path: PyPathLike, + mode: OptionalArg, + dir_fd: DirFd<{ _os::MKDIR_DIR_FD as usize }>, + vm: &VirtualMachine, + ) -> PyResult<()> { + let mode = mode.unwrap_or(0o777); + let [] = dir_fd.0; + let _ = mode; + let wide = path.to_widecstring(vm)?; + let res = unsafe { um::fileapi::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) }; + if res == 0 { + return Err(errno_err(vm)); + } + Ok(()) + } + + pub(crate) fn support_funcs() -> Vec { + Vec::new() + } +} + +#[cfg(all(windows, target_env = "msvc"))] +#[macro_export] +macro_rules! suppress_iph { + ($e:expr) => {{ + let old = $crate::stdlib::nt::module::_set_thread_local_invalid_parameter_handler( + $crate::stdlib::nt::module::silent_iph_handler, + ); + let ret = $e; + $crate::stdlib::nt::module::_set_thread_local_invalid_parameter_handler(old); + ret + }}; +} diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index eca04809ed..1132c3c1ed 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -29,13 +29,13 @@ use std::os::unix::ffi as ffi_ext; use std::os::wasi::ffi as ffi_ext; #[derive(Debug, Copy, Clone)] -enum OutputMode { +pub(super) enum OutputMode { String, Bytes, } impl OutputMode { - fn process_path(self, path: impl Into, vm: &VirtualMachine) -> PyResult { + pub(super) fn process_path(self, path: impl Into, vm: &VirtualMachine) -> PyResult { fn inner(mode: OutputMode, path: PathBuf, vm: &VirtualMachine) -> PyResult { let path_as_string = |p: PathBuf| { p.into_os_string().into_string().map_err(|_| { @@ -65,7 +65,7 @@ impl OutputMode { pub struct PyPathLike { pub path: PathBuf, - mode: OutputMode, + pub(super) mode: OutputMode, } impl PyPathLike { @@ -266,27 +266,27 @@ pub fn errno_err(vm: &VirtualMachine) -> PyBaseExceptionRef { errno().into_pyexception(vm) } +#[cfg(windows)] pub fn errno() -> io::Error { - cfg_if::cfg_if! { - if #[cfg(windows)] { - let err = io::Error::last_os_error(); - // FIXME: probably not ideal, we need a bigger dichotomy between GetLastError and errno - if err.raw_os_error() == Some(0) { - io::Error::from_raw_os_error(super::msvcrt::get_errno()) - } else { - err - } - } else { - io::Error::last_os_error() - } + let err = io::Error::last_os_error(); + // FIXME: probably not ideal, we need a bigger dichotomy between GetLastError and errno + if err.raw_os_error() == Some(0) { + io::Error::from_raw_os_error(super::msvcrt::get_errno()) + } else { + err } } +#[cfg(not(windows))] +pub fn errno() -> io::Error { + io::Error::last_os_error() +} + #[allow(dead_code)] #[derive(FromArgs, Default)] pub struct TargetIsDirectory { #[pyarg(any, default = "false")] - target_is_directory: bool, + pub(crate) target_is_directory: bool, } cfg_if::cfg_if! { @@ -375,31 +375,10 @@ fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsS .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8".to_owned())) } -#[cfg(all(windows, target_env = "msvc"))] -#[macro_export] -macro_rules! suppress_iph { - ($e:expr) => {{ - let old = $crate::stdlib::os::_set_thread_local_invalid_parameter_handler( - $crate::stdlib::os::silent_iph_handler, - ); - let ret = $e; - $crate::stdlib::os::_set_thread_local_invalid_parameter_handler(old); - ret - }}; -} - -#[cfg(not(all(windows, target_env = "msvc")))] -#[macro_export] -macro_rules! suppress_iph { - ($e:expr) => { - $e - }; -} - #[pymodule(name = "os")] pub(super) mod _os { use super::*; - + use crate::suppress_iph; use rustpython_common::lock::OnceCell; const OPEN_DIR_FD: bool = cfg!(not(any(windows, target_os = "redox"))); @@ -1053,60 +1032,63 @@ pub(super) mod _os { }) } + #[cfg(windows)] fn stat_inner( file: PathOrFd, dir_fd: DirFd<{ STAT_DIR_FD as usize }>, follow_symlinks: FollowSymlinks, ) -> io::Result> { - #[cfg(windows)] - { - // TODO: replicate CPython's win32_xstat - let [] = dir_fd.0; - let meta = match file { - PathOrFd::Path(path) => fs_metadata(&path, follow_symlinks.0)?, - PathOrFd::Fd(fno) => Fd(fno).as_rust_file()?.metadata()?, - }; - meta_to_stat(&meta).map(Some) - } - #[cfg(not(windows))] - { - let mut stat = std::mem::MaybeUninit::uninit(); - let ret = match file { - PathOrFd::Path(path) => { - use ffi_ext::OsStrExt; - let path = path.as_ref().as_os_str().as_bytes(); - let path = match ffi::CString::new(path) { - Ok(x) => x, - Err(_) => return Ok(None), - }; + // TODO: replicate CPython's win32_xstat + let [] = dir_fd.0; + let meta = match file { + PathOrFd::Path(path) => fs_metadata(&path, follow_symlinks.0)?, + PathOrFd::Fd(fno) => Fd(fno).as_rust_file()?.metadata()?, + }; + meta_to_stat(&meta).map(Some) + } - #[cfg(not(target_os = "redox"))] - let fstatat_ret = dir_fd.get_opt().map(|dir_fd| { - let flags = if follow_symlinks.0 { - 0 - } else { - libc::AT_SYMLINK_NOFOLLOW - }; - unsafe { libc::fstatat(dir_fd, path.as_ptr(), stat.as_mut_ptr(), flags) } - }); - #[cfg(target_os = "redox")] - let ([], fstatat_ret) = (dir_fd.0, None); - - fstatat_ret.unwrap_or_else(|| { - if follow_symlinks.0 { - unsafe { libc::stat(path.as_ptr(), stat.as_mut_ptr()) } - } else { - unsafe { libc::lstat(path.as_ptr(), stat.as_mut_ptr()) } - } - }) - } - PathOrFd::Fd(fd) => unsafe { libc::fstat(fd, stat.as_mut_ptr()) }, - }; - if ret < 0 { - return Err(io::Error::last_os_error()); + #[cfg(not(windows))] + fn stat_inner( + file: PathOrFd, + dir_fd: DirFd<{ STAT_DIR_FD as usize }>, + follow_symlinks: FollowSymlinks, + ) -> io::Result> { + let mut stat = std::mem::MaybeUninit::uninit(); + let ret = match file { + PathOrFd::Path(path) => { + use ffi_ext::OsStrExt; + let path = path.as_ref().as_os_str().as_bytes(); + let path = match ffi::CString::new(path) { + Ok(x) => x, + Err(_) => return Ok(None), + }; + + #[cfg(not(target_os = "redox"))] + let fstatat_ret = dir_fd.get_opt().map(|dir_fd| { + let flags = if follow_symlinks.0 { + 0 + } else { + libc::AT_SYMLINK_NOFOLLOW + }; + unsafe { libc::fstatat(dir_fd, path.as_ptr(), stat.as_mut_ptr(), flags) } + }); + #[cfg(target_os = "redox")] + let ([], fstatat_ret) = (dir_fd.0, None); + + fstatat_ret.unwrap_or_else(|| { + if follow_symlinks.0 { + unsafe { libc::stat(path.as_ptr(), stat.as_mut_ptr()) } + } else { + unsafe { libc::lstat(path.as_ptr(), stat.as_mut_ptr()) } + } + }) } - Ok(Some(unsafe { stat.assume_init() })) + PathOrFd::Fd(fd) => unsafe { libc::fstat(fd, stat.as_mut_ptr()) }, + }; + if ret < 0 { + return Err(io::Error::last_os_error()); } + Ok(Some(unsafe { stat.assume_init() })) } #[pyfunction] @@ -1700,407 +1682,14 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { pub(crate) use _os::os_open as open; #[cfg(windows)] -#[pymodule] -mod nt { - use super::*; - #[cfg(target_env = "msvc")] - use crate::builtins::PyListRef; - use crate::{builtins::PyDictRef, ItemProtocol}; - use winapi::vc::vcruntime::intptr_t; - - #[pyattr] - use libc::{O_BINARY, O_TEMPORARY}; - - #[pyfunction] - pub(super) fn access(path: PyPathLike, mode: u8, vm: &VirtualMachine) -> PyResult { - use winapi::um::{fileapi, winnt}; - let attr = unsafe { fileapi::GetFileAttributesW(path.to_widecstring(vm)?.as_ptr()) }; - Ok(attr != fileapi::INVALID_FILE_ATTRIBUTES - && (mode & 2 == 0 - || attr & winnt::FILE_ATTRIBUTE_READONLY == 0 - || attr & winnt::FILE_ATTRIBUTE_DIRECTORY != 0)) - } - - pub const SYMLINK_DIR_FD: bool = false; - - #[derive(FromArgs)] - pub(super) struct SimlinkArgs { - #[pyarg(any)] - src: PyPathLike, - #[pyarg(any)] - dst: PyPathLike, - #[pyarg(flatten)] - target_is_directory: TargetIsDirectory, - #[pyarg(flatten)] - _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, - } - - #[pyfunction] - pub(super) fn symlink(args: SimlinkArgs, vm: &VirtualMachine) -> PyResult<()> { - use std::os::windows::fs as win_fs; - let dir = args.target_is_directory.target_is_directory - || args - .dst - .path - .parent() - .and_then(|dst_parent| dst_parent.join(&args.src).symlink_metadata().ok()) - .map_or(false, |meta| meta.is_dir()); - let res = if dir { - win_fs::symlink_dir(args.src.path, args.dst.path) - } else { - win_fs::symlink_file(args.src.path, args.dst.path) - }; - res.map_err(|err| err.into_pyexception(vm)) - } - - #[pyfunction] - fn set_inheritable(fd: i32, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> { - let handle = Fd(fd).to_raw_handle().map_err(|e| e.into_pyexception(vm))?; - set_handle_inheritable(handle as _, inheritable, vm) - } - - #[pyattr] - fn environ(vm: &VirtualMachine) -> PyDictRef { - let environ = vm.ctx.new_dict(); +use super::nt::nt as platform; - for (key, value) in env::vars() { - environ - .set_item(vm.ctx.new_utf8_str(key), vm.ctx.new_utf8_str(value), vm) - .unwrap(); - } - environ - } - - #[pyfunction] - fn chmod( - path: PyPathLike, - dir_fd: DirFd<0>, - mode: u32, - follow_symlinks: FollowSymlinks, - vm: &VirtualMachine, - ) -> PyResult<()> { - const S_IWRITE: u32 = 128; - let [] = dir_fd.0; - let metadata = if follow_symlinks.0 { - fs::metadata(&path) - } else { - fs::symlink_metadata(&path) - }; - let meta = metadata.map_err(|err| err.into_pyexception(vm))?; - let mut permissions = meta.permissions(); - permissions.set_readonly(mode & S_IWRITE == 0); - fs::set_permissions(&path, permissions).map_err(|err| err.into_pyexception(vm)) - } - - // cwait is available on MSVC only (according to CPython) - #[cfg(target_env = "msvc")] - extern "C" { - fn _cwait(termstat: *mut i32, procHandle: intptr_t, action: i32) -> intptr_t; - } - - #[cfg(target_env = "msvc")] - #[pyfunction] - fn waitpid(pid: intptr_t, opt: i32, vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> { - let mut status = 0; - let pid = unsafe { suppress_iph!(_cwait(&mut status, pid, opt)) }; - if pid == -1 { - Err(errno_err(vm)) - } else { - Ok((pid, status << 8)) - } - } - - #[cfg(target_env = "msvc")] - #[pyfunction] - fn wait(vm: &VirtualMachine) -> PyResult<(intptr_t, i32)> { - waitpid(-1, 0, vm) - } - - #[pyfunction] - fn kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> { - { - use winapi::um::{handleapi, processthreadsapi, wincon, winnt}; - let sig = sig as u32; - let pid = pid as u32; - - if sig == wincon::CTRL_C_EVENT || sig == wincon::CTRL_BREAK_EVENT { - let ret = unsafe { wincon::GenerateConsoleCtrlEvent(sig, pid) }; - let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) }; - return res; - } - - let h = unsafe { processthreadsapi::OpenProcess(winnt::PROCESS_ALL_ACCESS, 0, pid) }; - if h.is_null() { - return Err(errno_err(vm)); - } - let ret = unsafe { processthreadsapi::TerminateProcess(h, sig) }; - let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) }; - unsafe { handleapi::CloseHandle(h) }; - res - } - } - - #[pyfunction] - fn get_terminal_size( - fd: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let (columns, lines) = { - use winapi::um::{handleapi, processenv, winbase, wincon}; - let stdhandle = match fd { - OptionalArg::Present(0) => winbase::STD_INPUT_HANDLE, - OptionalArg::Present(1) | OptionalArg::Missing => winbase::STD_OUTPUT_HANDLE, - OptionalArg::Present(2) => winbase::STD_ERROR_HANDLE, - _ => return Err(vm.new_value_error("bad file descriptor".to_owned())), - }; - let h = unsafe { processenv::GetStdHandle(stdhandle) }; - if h.is_null() { - return Err(vm.new_os_error("handle cannot be retrieved".to_owned())); - } - if h == handleapi::INVALID_HANDLE_VALUE { - return Err(errno_err(vm)); - } - let mut csbi = wincon::CONSOLE_SCREEN_BUFFER_INFO::default(); - let ret = unsafe { wincon::GetConsoleScreenBufferInfo(h, &mut csbi) }; - if ret == 0 { - return Err(errno_err(vm)); - } - let w = csbi.srWindow; - ( - (w.Right - w.Left + 1) as usize, - (w.Bottom - w.Top + 1) as usize, - ) - }; - Ok(super::_os::PyTerminalSize { columns, lines }) - } - - #[cfg(target_env = "msvc")] - type InvalidParamHandler = extern "C" fn( - *const libc::wchar_t, - *const libc::wchar_t, - *const libc::wchar_t, - libc::c_uint, - libc::uintptr_t, - ); - #[cfg(target_env = "msvc")] - extern "C" { - #[doc(hidden)] - pub fn _set_thread_local_invalid_parameter_handler( - pNew: InvalidParamHandler, - ) -> InvalidParamHandler; - } - - #[cfg(target_env = "msvc")] - #[doc(hidden)] - pub extern "C" fn silent_iph_handler( - _: *const libc::wchar_t, - _: *const libc::wchar_t, - _: *const libc::wchar_t, - _: libc::c_uint, - _: libc::uintptr_t, - ) { - } - - #[cfg(target_env = "msvc")] - extern "C" { - fn _wexecv(cmdname: *const u16, argv: *const *const u16) -> intptr_t; - } - - #[cfg(target_env = "msvc")] - #[pyfunction] - fn execv( - path: PyStrRef, - argv: Either, - vm: &VirtualMachine, - ) -> PyResult<()> { - use std::iter::once; - - let make_widestring = |s: &str| { - widestring::WideCString::from_os_str(s).map_err(|err| err.into_pyexception(vm)) - }; - - let path = make_widestring(path.as_str())?; - - let argv = vm.extract_elements_func(argv.as_object(), |obj| { - let arg = PyStrRef::try_from_object(vm, obj)?; - make_widestring(arg.as_str()) - })?; - - let first = argv - .first() - .ok_or_else(|| vm.new_value_error("execv() arg 2 must not be empty".to_owned()))?; - - if first.is_empty() { - return Err( - vm.new_value_error("execv() arg 2 first element cannot be empty".to_owned()) - ); - } - - let argv_execv: Vec<*const u16> = argv - .iter() - .map(|v| v.as_ptr()) - .chain(once(std::ptr::null())) - .collect(); - - if (unsafe { suppress_iph!(_wexecv(path.as_ptr(), argv_execv.as_ptr())) } == -1) { - Err(errno_err(vm)) - } else { - Ok(()) - } - } - - #[pyfunction] - fn _getfinalpathname(path: PyPathLike, vm: &VirtualMachine) -> PyResult { - let real = path - .as_ref() - .canonicalize() - .map_err(|e| e.into_pyexception(vm))?; - path.mode.process_path(real, vm) - } - - #[pyfunction] - fn _getfullpathname(path: PyPathLike, vm: &VirtualMachine) -> PyResult { - let wpath = path.to_widecstring(vm)?; - let mut buffer = vec![0u16; winapi::shared::minwindef::MAX_PATH]; - let ret = unsafe { - winapi::um::fileapi::GetFullPathNameW( - wpath.as_ptr(), - buffer.len() as _, - buffer.as_mut_ptr(), - std::ptr::null_mut(), - ) - }; - if ret == 0 { - return Err(errno_err(vm)); - } - if ret as usize > buffer.len() { - buffer.resize(ret as usize, 0); - let ret = unsafe { - winapi::um::fileapi::GetFullPathNameW( - wpath.as_ptr(), - buffer.len() as _, - buffer.as_mut_ptr(), - std::ptr::null_mut(), - ) - }; - if ret == 0 { - return Err(errno_err(vm)); - } - } - let buffer = widestring::WideCString::from_vec_with_nul(buffer).unwrap(); - path.mode.process_path(buffer.to_os_string(), vm) - } - - #[pyfunction] - fn _getvolumepathname(path: PyPathLike, vm: &VirtualMachine) -> PyResult { - let wide = path.to_widecstring(vm)?; - let buflen = std::cmp::max(wide.len(), winapi::shared::minwindef::MAX_PATH); - let mut buffer = vec![0u16; buflen]; - let ret = unsafe { - winapi::um::fileapi::GetVolumePathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buflen as _) - }; - if ret == 0 { - return Err(errno_err(vm)); - } - let buffer = widestring::WideCString::from_vec_with_nul(buffer).unwrap(); - path.mode.process_path(buffer.to_os_string(), vm) - } - - #[pyfunction] - fn _getdiskusage(path: PyPathLike, vm: &VirtualMachine) -> PyResult<(u64, u64)> { - use winapi::shared::{ntdef::ULARGE_INTEGER, winerror}; - use winapi::um::fileapi::GetDiskFreeSpaceExW; - let wpath = path.to_widecstring(vm)?; - let mut _free_to_me = ULARGE_INTEGER::default(); - let mut total = ULARGE_INTEGER::default(); - let mut free = ULARGE_INTEGER::default(); - let ret = - unsafe { GetDiskFreeSpaceExW(wpath.as_ptr(), &mut _free_to_me, &mut total, &mut free) }; - if ret != 0 { - return unsafe { Ok((*total.QuadPart(), *free.QuadPart())) }; - } - let err = io::Error::last_os_error(); - if err.raw_os_error() == Some(winerror::ERROR_DIRECTORY as i32) { - if let Some(parent) = path.as_ref().parent() { - let parent = widestring::WideCString::from_os_str(parent).unwrap(); - - let ret = unsafe { - GetDiskFreeSpaceExW(parent.as_ptr(), &mut _free_to_me, &mut total, &mut free) - }; - - if ret == 0 { - return Err(errno_err(vm)); - } else { - return unsafe { Ok((*total.QuadPart(), *free.QuadPart())) }; - } - } - } - Err(err.into_pyexception(vm)) - } - - #[pyfunction] - fn get_handle_inheritable(handle: intptr_t, vm: &VirtualMachine) -> PyResult { - let mut flags = 0; - if unsafe { winapi::um::handleapi::GetHandleInformation(handle as _, &mut flags) } == 0 { - Err(errno_err(vm)) - } else { - Ok(flags & winapi::um::winbase::HANDLE_FLAG_INHERIT != 0) - } - } - - pub(crate) fn raw_set_handle_inheritable( - handle: intptr_t, - inheritable: bool, - ) -> io::Result<()> { - use winapi::um::winbase::HANDLE_FLAG_INHERIT; - let flags = if inheritable { HANDLE_FLAG_INHERIT } else { 0 }; - let res = unsafe { - winapi::um::handleapi::SetHandleInformation(handle as _, HANDLE_FLAG_INHERIT, flags) - }; - if res == 0 { - Err(errno()) - } else { - Ok(()) - } - } - - #[pyfunction] - fn set_handle_inheritable( - handle: intptr_t, - inheritable: bool, - vm: &VirtualMachine, - ) -> PyResult<()> { - raw_set_handle_inheritable(handle, inheritable).map_err(|e| e.into_pyexception(vm)) - } - - #[pyfunction] - fn mkdir( - path: PyPathLike, - mode: OptionalArg, - dir_fd: DirFd<{ super::_os::MKDIR_DIR_FD as usize }>, - vm: &VirtualMachine, - ) -> PyResult<()> { - let mode = mode.unwrap_or(0o777); - let [] = dir_fd.0; - let _ = mode; - let wide = path.to_widecstring(vm)?; - let res = - unsafe { winapi::um::fileapi::CreateDirectoryW(wide.as_ptr(), std::ptr::null_mut()) }; - if res == 0 { - return Err(errno_err(vm)); - } - Ok(()) - } +pub(crate) use platform::MODULE_NAME; - pub(super) fn support_funcs() -> Vec { - Vec::new() - } +#[cfg(not(all(windows, target_env = "msvc")))] +#[macro_export] +macro_rules! suppress_iph { + ($e:expr) => { + $e + }; } -#[cfg(windows)] -use nt as platform; -#[cfg(windows)] -pub(crate) use nt::raw_set_handle_inheritable; -#[cfg(all(windows, target_env = "msvc"))] -pub use nt::{_set_thread_local_invalid_parameter_handler, silent_iph_handler}; - -pub(crate) use platform::MODULE_NAME; From 4cae03b5e09ec623a91a4baecafc9b8cad2e6364 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 22 Sep 2021 18:09:09 +0900 Subject: [PATCH 5/6] posix/nt owns its make_module --- vm/src/stdlib/mod.rs | 16 +++--- vm/src/stdlib/nt.rs | 18 +++++-- vm/src/stdlib/os.rs | 97 ++++++++++++++++++----------------- vm/src/stdlib/posix.rs | 16 +++--- vm/src/stdlib/posix_compat.rs | 16 ++++-- vm/src/stdlib/socket.rs | 2 +- 6 files changed, 93 insertions(+), 72 deletions(-) diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 6f27b67cff..10da81158e 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,8 +1,3 @@ -use crate::vm::VirtualMachine; -use crate::PyObjectRef; -use std::borrow::Cow; -use std::collections::HashMap; - mod array; #[cfg(feature = "rustpython-ast")] pub(crate) mod ast; @@ -93,6 +88,11 @@ mod winapi; #[cfg(windows)] mod winreg; +use crate::vm::VirtualMachine; +use crate::PyObjectRef; +use std::borrow::Cow; +use std::collections::HashMap; + pub type StdlibInitFunc = Box PyObjectRef)>; pub type StdlibMap = HashMap, StdlibInitFunc, ahash::RandomState>; @@ -162,12 +162,9 @@ pub fn get_module_inits() -> StdlibMap { { "symtable" => symtable::make_module, } - #[cfg(any(unix, windows, target_os = "wasi"))] - { - os::MODULE_NAME => os::make_module, - } #[cfg(any(unix, target_os = "wasi"))] { + "posix" => posix::make_module, "fcntl" => fcntl::make_module, } // disable some modules on WASM @@ -205,6 +202,7 @@ pub fn get_module_inits() -> StdlibMap { // Windows-only #[cfg(windows)] { + "nt" => nt::make_module, "msvcrt" => msvcrt::make_module, "_winapi" => winapi::make_module, "winreg" => winreg::make_module, diff --git a/vm/src/stdlib/nt.rs b/vm/src/stdlib/nt.rs index 96d7e981dc..4d41f62499 100644 --- a/vm/src/stdlib/nt.rs +++ b/vm/src/stdlib/nt.rs @@ -1,5 +1,15 @@ -#[pymodule] -pub(crate) mod nt { +use crate::{PyObjectRef, VirtualMachine}; + +pub(crate) use module::raw_set_handle_inheritable; + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let module = module::make_module(vm); + super::os::extend_module(vm, &module); + module +} + +#[pymodule(name = "nt")] +pub(crate) mod module { use crate::{ builtins::{PyStrRef, PyTupleRef}, crt_fd::Fd, @@ -34,8 +44,6 @@ pub(crate) mod nt { || attr & winnt::FILE_ATTRIBUTE_DIRECTORY != 0)) } - pub const SYMLINK_DIR_FD: bool = false; - #[derive(FromArgs)] pub(super) struct SimlinkArgs { #[pyarg(any)] @@ -45,7 +53,7 @@ pub(crate) mod nt { #[pyarg(flatten)] target_is_directory: TargetIsDirectory, #[pyarg(flatten)] - _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + _dir_fd: DirFd<{ _os::SYMLINK_DIR_FD as usize }>, } #[pyfunction] diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 1132c3c1ed..0278d91c8f 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,27 +1,17 @@ use super::errno::errors; -use crate::common::lock::PyRwLock; -use crate::crt_fd::{Fd, Offset}; +use crate::crt_fd::Fd; use crate::{ buffer::PyBuffer, - builtins::{int, PyBytes, PyBytesRef, PySet, PyStr, PyStrRef, PyTuple, PyTupleRef, PyTypeRef}, - byteslike::ArgBytesLike, + builtins::{int, PyBytes, PyBytesRef, PySet, PyStr, PyStrRef}, exceptions::{IntoPyException, PyBaseExceptionRef}, - function::{ArgumentError, FromArgs, FuncArgs, OptionalArg}, - slots::PyIter, - utils::Either, - vm::{ReprGuard, VirtualMachine}, - IntoPyObject, PyObjectRef, PyRef, PyResult, PyStructSequence, PyValue, StaticType, - TryFromBorrowedObject, TryFromObject, TypeProtocol, + function::{ArgumentError, FromArgs, FuncArgs}, + IntoPyObject, PyObjectRef, PyResult, PyValue, TryFromBorrowedObject, TryFromObject, + TypeProtocol, VirtualMachine, }; -use crossbeam_utils::atomic::AtomicCell; -use itertools::Itertools; -use num_bigint::BigInt; use std::ffi; -use std::fs::OpenOptions; -use std::io::{self, ErrorKind, Read, Write}; +use std::fs; +use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; -use std::time::{Duration, SystemTime}; -use std::{env, fs}; #[cfg(unix)] use std::os::unix::ffi as ffi_ext; @@ -363,10 +353,7 @@ pub(super) struct FollowSymlinks( ); #[cfg(unix)] -use super::posix::bytes_as_osstr; - -#[cfg(not(windows))] -use super::posix::posix as platform; +use platform::bytes_as_osstr; #[cfg(not(unix))] fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsStr> { @@ -377,14 +364,38 @@ fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsS #[pymodule(name = "os")] pub(super) mod _os { - use super::*; - use crate::suppress_iph; - use rustpython_common::lock::OnceCell; + use super::{ + errno_err, DirFd, FollowSymlinks, FsPath, OutputMode, PathOrFd, PyPathLike, SupportFunc, + }; + use crate::common::lock::{OnceCell, PyRwLock}; + use crate::crt_fd::{Fd, Offset}; + use crate::{ + builtins::{int, PyBytesRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef}, + byteslike::ArgBytesLike, + exceptions::IntoPyException, + function::{FuncArgs, OptionalArg}, + slots::PyIter, + suppress_iph, + utils::Either, + vm::{ReprGuard, VirtualMachine}, + IntoPyObject, PyObjectRef, PyRef, PyResult, PyStructSequence, PyValue, StaticType, + TryFromBorrowedObject, TryFromObject, TypeProtocol, + }; + use crossbeam_utils::atomic::AtomicCell; + use itertools::Itertools; + use num_bigint::BigInt; + use std::ffi; + use std::fs::OpenOptions; + use std::io::{self, Read, Write}; + use std::path::PathBuf; + use std::time::{Duration, SystemTime}; + use std::{env, fs}; 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"))); 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"))); #[pyattr] use libc::{ @@ -562,7 +573,7 @@ pub(super) mod _os { } #[cfg(all(unix, not(target_os = "redox")))] { - use ffi_ext::OsStrExt; + use super::ffi_ext::OsStrExt; let new_fd = nix::unistd::dup(fno).map_err(|e| e.into_pyexception(vm))?; let mut dir = nix::dir::Dir::from_fd(new_fd).map_err(|e| e.into_pyexception(vm))?; @@ -597,11 +608,11 @@ pub(super) mod _os { ) -> PyResult<()> { let key: &ffi::OsStr = match key { Either::A(ref s) => s.as_str().as_ref(), - Either::B(ref b) => bytes_as_osstr(b.as_bytes(), vm)?, + Either::B(ref b) => super::bytes_as_osstr(b.as_bytes(), vm)?, }; let value: &ffi::OsStr = match value { Either::A(ref s) => s.as_str().as_ref(), - Either::B(ref b) => bytes_as_osstr(b.as_bytes(), vm)?, + Either::B(ref b) => super::bytes_as_osstr(b.as_bytes(), vm)?, }; env::set_var(key, value); Ok(()) @@ -611,7 +622,7 @@ pub(super) mod _os { fn unsetenv(key: Either, vm: &VirtualMachine) -> PyResult<()> { let key: &ffi::OsStr = match key { Either::A(ref s) => s.as_str().as_ref(), - Either::B(ref b) => bytes_as_osstr(b.as_bytes(), vm)?, + Either::B(ref b) => super::bytes_as_osstr(b.as_bytes(), vm)?, }; env::remove_var(key); Ok(()) @@ -655,7 +666,7 @@ pub(super) mod _os { action: fn(fs::Metadata) -> bool, vm: &VirtualMachine, ) -> PyResult { - match fs_metadata(self.entry.path(), follow_symlinks.0) { + match super::fs_metadata(self.entry.path(), follow_symlinks.0) { Ok(meta) => Ok(action(meta)), Err(e) => { // FileNotFoundError is caught and not raised @@ -1041,7 +1052,7 @@ pub(super) mod _os { // TODO: replicate CPython's win32_xstat let [] = dir_fd.0; let meta = match file { - PathOrFd::Path(path) => fs_metadata(&path, follow_symlinks.0)?, + PathOrFd::Path(path) => super::fs_metadata(&path, follow_symlinks.0)?, PathOrFd::Fd(fno) => Fd(fno).as_rust_file()?.metadata()?, }; meta_to_stat(&meta).map(Some) @@ -1056,7 +1067,7 @@ pub(super) mod _os { let mut stat = std::mem::MaybeUninit::uninit(); let ret = match file { PathOrFd::Path(path) => { - use ffi_ext::OsStrExt; + use super::ffi_ext::OsStrExt; let path = path.as_ref().as_os_str().as_bytes(); let path = match ffi::CString::new(path) { Ok(x) => x, @@ -1584,7 +1595,7 @@ pub(super) mod _os { impl UnameResult {} pub(super) fn support_funcs() -> Vec { - let mut supports = super::platform::support_funcs(); + let mut supports = super::platform::module::support_funcs(); supports.extend(vec![ SupportFunc::new("open", Some(false), Some(OPEN_DIR_FD), Some(false)), SupportFunc::new("access", Some(false), Some(false), None), @@ -1603,12 +1614,7 @@ pub(super) mod _os { SupportFunc::new("scandir", None, Some(false), Some(false)), SupportFunc::new("stat", Some(true), Some(STAT_DIR_FD), Some(true)), SupportFunc::new("fstat", Some(true), Some(STAT_DIR_FD), Some(true)), - SupportFunc::new( - "symlink", - Some(false), - Some(platform::SYMLINK_DIR_FD), - Some(false), - ), + SupportFunc::new("symlink", Some(false), Some(SYMLINK_DIR_FD), Some(false)), SupportFunc::new("truncate", Some(true), Some(false), Some(false)), SupportFunc::new( "utime", @@ -1648,10 +1654,8 @@ impl<'a> SupportFunc { } } -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { - let module = platform::make_module(vm); - - _os::extend_module(vm, &module); +pub fn extend_module(vm: &VirtualMachine, module: &PyObjectRef) { + _os::extend_module(vm, module); let support_funcs = _os::support_funcs(); let supports_fd = PySet::default().into_ref(vm); @@ -1676,15 +1680,16 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "supports_follow_symlinks" => supports_follow_symlinks.into_object(), "error" => vm.ctx.exceptions.os_error.clone(), }); - - module } pub(crate) use _os::os_open as open; +#[cfg(not(windows))] +use super::posix as platform; + #[cfg(windows)] -use super::nt::nt as platform; +use super::nt as platform; -pub(crate) use platform::MODULE_NAME; +pub(crate) use platform::module::MODULE_NAME; #[cfg(not(all(windows, target_env = "msvc")))] #[macro_export] diff --git a/vm/src/stdlib/posix.rs b/vm/src/stdlib/posix.rs index 3a8a2df634..621bb96413 100644 --- a/vm/src/stdlib/posix.rs +++ b/vm/src/stdlib/posix.rs @@ -1,4 +1,4 @@ -use crate::{PyResult, VirtualMachine}; +use crate::{PyObjectRef, PyResult, VirtualMachine}; use nix; use std::os::unix::io::RawFd; @@ -21,8 +21,14 @@ pub(super) fn bytes_as_osstr<'a>( Ok(std::ffi::OsStr::from_bytes(b)) } -#[pymodule] -pub mod posix { +pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let module = module::make_module(vm); + super::os::extend_module(vm, &module); + module +} + +#[pymodule(name = "posix")] +pub mod module { use crate::{ builtins::{int, PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, crt_fd::Offset, @@ -152,8 +158,6 @@ pub mod posix { #[pyattr] const _COPYFILE_DATA: u32 = 1 << 3; - pub(crate) const SYMLINK_DIR_FD: bool = cfg!(not(target_os = "redox")); - // Flags for os_access bitflags! { pub struct AccessFlags: u8{ @@ -304,7 +308,7 @@ pub mod posix { #[pyarg(flatten)] _target_is_directory: TargetIsDirectory, #[pyarg(flatten)] - dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + dir_fd: DirFd<{ _os::SYMLINK_DIR_FD as usize }>, } #[pyfunction] diff --git a/vm/src/stdlib/posix_compat.rs b/vm/src/stdlib/posix_compat.rs index a4eacf3936..7b6ec9871e 100644 --- a/vm/src/stdlib/posix_compat.rs +++ b/vm/src/stdlib/posix_compat.rs @@ -1,10 +1,18 @@ //! `posix` compatible module for `not(any(unix, windows))` +use crate::{PyObjectRef, VirtualMachine}; + +pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let module = module::make_module(vm); + super::os::extend_module(vm, &module); + module +} + #[pymodule(name = "posix")] -pub(crate) mod posix { +pub(crate) mod module { use crate::{ builtins::PyStrRef, - stdlib::os::{DirFd, PyPathLike, SupportFunc, TargetIsDirectory}, + stdlib::os::{DirFd, PyPathLike, SupportFunc, TargetIsDirectory, _os}, PyResult, VirtualMachine, }; use std::env; @@ -18,8 +26,6 @@ pub(crate) mod posix { os_unimpl("os.access", vm) } - pub const SYMLINK_DIR_FD: bool = false; - #[derive(FromArgs)] #[allow(unused)] pub(super) struct SimlinkArgs { @@ -30,7 +36,7 @@ pub(crate) mod posix { #[pyarg(flatten)] _target_is_directory: TargetIsDirectory, #[pyarg(flatten)] - _dir_fd: DirFd<{ SYMLINK_DIR_FD as usize }>, + _dir_fd: DirFd<{ _os::SYMLINK_DIR_FD as usize }>, } #[pyfunction] diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index de7513ed1e..6887faa29a 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -1778,7 +1778,7 @@ fn _socket_dup(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { let newsock = sock.try_clone().map_err(|e| e.into_pyexception(vm))?; let fd = into_sock_fileno(newsock); #[cfg(windows)] - super::os::raw_set_handle_inheritable(fd as _, false).map_err(|e| e.into_pyexception(vm))?; + super::nt::raw_set_handle_inheritable(fd as _, false).map_err(|e| e.into_pyexception(vm))?; Ok(fd) } From 56fb733b0dc80e69c1fb67200792fdc0918e4763 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 22 Sep 2021 21:29:43 +0900 Subject: [PATCH 6/6] Fix os.py for nt --- vm/src/stdlib/os.rs | 11 ++++++++++- vm/src/stdlib/posix.rs | 16 ++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 0278d91c8f..15f1e5ccfd 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -362,7 +362,7 @@ fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsS .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8".to_owned())) } -#[pymodule(name = "os")] +#[pymodule(name = "_os")] pub(super) mod _os { use super::{ errno_err, DirFd, FollowSymlinks, FsPath, OutputMode, PathOrFd, PyPathLike, SupportFunc, @@ -403,6 +403,15 @@ pub(super) mod _os { SEEK_SET, }; + #[pyattr] + pub(crate) const F_OK: u8 = 0; + #[pyattr] + pub(crate) const R_OK: u8 = 1 << 2; + #[pyattr] + pub(crate) const W_OK: u8 = 1 << 1; + #[pyattr] + pub(crate) const X_OK: u8 = 1 << 0; + #[pyfunction] fn close(fileno: i32, vm: &VirtualMachine) -> PyResult<()> { Fd(fileno).close().map_err(|e| e.into_pyexception(vm)) diff --git a/vm/src/stdlib/posix.rs b/vm/src/stdlib/posix.rs index 621bb96413..58d2bff55d 100644 --- a/vm/src/stdlib/posix.rs +++ b/vm/src/stdlib/posix.rs @@ -60,14 +60,6 @@ pub mod module { #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "linux"))] #[pyattr] use libc::{SEEK_DATA, SEEK_HOLE}; - #[pyattr] - pub(crate) const F_OK: u8 = 0; - #[pyattr] - pub(crate) const R_OK: u8 = 4; - #[pyattr] - pub(crate) const W_OK: u8 = 2; - #[pyattr] - pub(crate) const X_OK: u8 = 1; #[cfg(not(any(target_os = "redox", target_os = "freebsd")))] #[pyattr] @@ -161,10 +153,10 @@ pub mod module { // Flags for os_access bitflags! { pub struct AccessFlags: u8{ - const F_OK = F_OK; - const R_OK = R_OK; - const W_OK = W_OK; - const X_OK = X_OK; + const F_OK = _os::F_OK; + const R_OK = _os::R_OK; + const W_OK = _os::W_OK; + const X_OK = _os::X_OK; } }