From d044d8ab615a5de8a62ee93fba0e80d05a879b94 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sun, 13 Jun 2021 21:28:45 +0800 Subject: [PATCH] os: Implement (f)pathconf Implement pathconf and fpathconf using libc::pathconf. os.pathconf_names is not implemented. --- Cargo.lock | 29 ++++++ vm/Cargo.toml | 2 + vm/src/stdlib/os.rs | 231 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 261 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 78876bc44..26a0c90e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.18" @@ -2082,6 +2091,8 @@ dependencies = [ "socket2", "sre-engine", "static_assertions", + "strum", + "strum_macros", "system-configuration", "termios", "thiserror", @@ -2360,6 +2371,24 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strum" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" + +[[package]] +name = "strum_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.4.0" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 7f44e979e..67332d358 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -117,6 +117,8 @@ rustpython-common = { path = "../common" } [target.'cfg(unix)'.dependencies] exitcode = "1.1.2" uname = "0.1.1" +strum = "0.21" +strum_macros = "0.21" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] gethostname = "0.2.0" diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 9c54019f7..79dddb2e7 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -10,6 +10,8 @@ use std::{env, fs}; use crate::crt_fd::Fd; use crossbeam_utils::atomic::AtomicCell; use num_bigint::BigInt; +#[cfg(unix)] +use strum_macros::EnumString; use super::errno::errors; use crate::builtins::bytes::{PyBytes, PyBytesRef}; @@ -1645,6 +1647,232 @@ mod _os { #[pyimpl(with(PyStructSequence))] impl UnameResult {} + enum NameOrVal { + Name(String), + Val(i32), + } + + impl TryFromObject for NameOrVal { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match obj.downcast::() { + Ok(int) => int::try_to_primitive(int.as_bigint(), vm).map(Self::Val), + Err(obj) => { + let cstring = std::ffi::CString::try_from_object(vm, obj)?; + cstring.into_string().map(Self::Name).map_err(|e| { + vm.new_os_error(format!("error while parsing string: {:?}", e)) + }) + } + } + } + } + + // 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, name: NameOrVal, vm: &VirtualMachine) -> PyResult> { + use nix::errno::{self, Errno}; + use std::str::FromStr; + let name = match name { + NameOrVal::Name(s) => PathconfVar::from_str(&s) + .map_err(|_| vm.new_value_error("unrecognized configuration name".to_string()))? + as i32, + NameOrVal::Val(v) => v, + }; + + 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: NameOrVal, 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![ @@ -1656,7 +1884,8 @@ mod _os { SupportFunc::new("mkdir", Some(false), Some(MKDIR_DIR_FD), Some(false)), // mkfifo Some Some None // mknod Some Some None - // pathconf Some None 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)),