From 8152e7e62c304c95764504058ab86909a7c369f8 Mon Sep 17 00:00:00 2001 From: Noa Date: Wed, 18 Sep 2024 17:17:09 -0500 Subject: [PATCH 1/2] Make Gid/Uid less janky --- vm/src/stdlib/posix.rs | 115 +++++++++++++---------------------------- 1 file changed, 35 insertions(+), 80 deletions(-) diff --git a/vm/src/stdlib/posix.rs b/vm/src/stdlib/posix.rs index 39a450b4f9..e134bf28c5 100644 --- a/vm/src/stdlib/posix.rs +++ b/vm/src/stdlib/posix.rs @@ -1111,17 +1111,13 @@ pub mod module { } #[pyfunction] - fn setgid(gid: Option, vm: &VirtualMachine) -> PyResult<()> { - let gid = - gid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?; + fn setgid(gid: Gid, vm: &VirtualMachine) -> PyResult<()> { unistd::setgid(gid).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[pyfunction] - fn setegid(egid: Option, vm: &VirtualMachine) -> PyResult<()> { - let egid = - egid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?; + fn setegid(egid: Gid, vm: &VirtualMachine) -> PyResult<()> { unistd::setegid(egid).map_err(|err| err.into_pyexception(vm)) } @@ -1139,7 +1135,7 @@ pub mod module { .map_err(|err| err.into_pyexception(vm)) } - fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult> { + fn try_from_id(vm: &VirtualMachine, obj: PyObjectRef, typ_name: &str) -> PyResult { use std::cmp::Ordering; let i = obj .try_to_ref::(vm) @@ -1152,53 +1148,47 @@ pub mod module { .try_to_primitive::(vm)?; match i.cmp(&-1) { - Ordering::Greater => Ok(Some(i.try_into().map_err(|_| { + Ordering::Greater => Ok(i.try_into().map_err(|_| { vm.new_overflow_error(format!("{typ_name} is larger than maximum")) - })?)), + })?), Ordering::Less => { Err(vm.new_overflow_error(format!("{typ_name} is less than minimum"))) } - Ordering::Equal => Ok(None), // -1 means does not change the value + // -1 means does not change the value + // In CPython, this is `(uid_t) -1`, rustc gets mad when we try to declare + // a negative unsigned integer :). + Ordering::Equal => Ok(-1i32 as u32), } } - impl TryFromObject for Option { + impl TryFromObject for Uid { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - Ok(try_from_id(vm, obj, "uid")?.map(Uid::from_raw)) + try_from_id(vm, obj, "uid").map(Uid::from_raw) } } - impl TryFromObject for Option { + impl TryFromObject for Gid { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - Ok(try_from_id(vm, obj, "gid")?.map(Gid::from_raw)) + try_from_id(vm, obj, "gid").map(Gid::from_raw) } } #[pyfunction] - fn setuid(uid: Option, vm: &VirtualMachine) -> PyResult<()> { - let uid = - uid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?; - unistd::setuid(uid).map_err(|err| err.into_pyexception(vm)) + fn setuid(uid: Uid) -> nix::Result<()> { + unistd::setuid(uid) } #[cfg(not(target_os = "redox"))] #[pyfunction] - fn seteuid(euid: Option, vm: &VirtualMachine) -> PyResult<()> { - let euid = - euid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?; - unistd::seteuid(euid).map_err(|err| err.into_pyexception(vm)) + fn seteuid(euid: Uid) -> nix::Result<()> { + unistd::seteuid(euid) } #[cfg(not(target_os = "redox"))] #[pyfunction] - fn setreuid(ruid: Option, euid: Option, vm: &VirtualMachine) -> PyResult<()> { - if let Some(ruid) = ruid { - unistd::setuid(ruid).map_err(|err| err.into_pyexception(vm))?; - } - if let Some(euid) = euid { - unistd::seteuid(euid).map_err(|err| err.into_pyexception(vm))?; - } - Ok(()) + fn setreuid(ruid: Uid, euid: Uid) -> nix::Result<()> { + let ret = unsafe { libc::setreuid(ruid.as_raw(), euid.as_raw()) }; + nix::Error::result(ret).map(drop) } // cfg from nix @@ -1209,20 +1199,8 @@ pub mod module { target_os = "openbsd" ))] #[pyfunction] - fn setresuid( - ruid: Option, - euid: Option, - suid: Option, - vm: &VirtualMachine, - ) -> PyResult<()> { - let unwrap_or_unchanged = - |u: Option| u.unwrap_or_else(|| Uid::from_raw(libc::uid_t::MAX)); - unistd::setresuid( - unwrap_or_unchanged(ruid), - unwrap_or_unchanged(euid), - unwrap_or_unchanged(suid), - ) - .map_err(|err| err.into_pyexception(vm)) + fn setresuid(ruid: Uid, euid: Uid, suid: Uid) -> nix::Result<()> { + unistd::setresuid(ruid, euid, suid) } #[cfg(not(target_os = "redox"))] @@ -1271,8 +1249,8 @@ pub mod module { // cfg from nix #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] #[pyfunction] - fn getresuid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { - let ret = unistd::getresuid().map_err(|e| e.into_pyexception(vm))?; + fn getresuid() -> nix::Result<(u32, u32, u32)> { + let ret = unistd::getresuid()?; Ok(( ret.real.as_raw(), ret.effective.as_raw(), @@ -1283,8 +1261,8 @@ pub mod module { // cfg from nix #[cfg(any(target_os = "android", target_os = "linux", target_os = "openbsd"))] #[pyfunction] - fn getresgid(vm: &VirtualMachine) -> PyResult<(u32, u32, u32)> { - let ret = unistd::getresgid().map_err(|e| e.into_pyexception(vm))?; + fn getresgid() -> nix::Result<(u32, u32, u32)> { + let ret = unistd::getresgid()?; Ok(( ret.real.as_raw(), ret.effective.as_raw(), @@ -1300,32 +1278,15 @@ pub mod module { target_os = "openbsd" ))] #[pyfunction] - fn setresgid( - rgid: Option, - egid: Option, - sgid: Option, - vm: &VirtualMachine, - ) -> PyResult<()> { - let unwrap_or_unchanged = - |u: Option| u.unwrap_or_else(|| Gid::from_raw(libc::gid_t::MAX)); - unistd::setresgid( - unwrap_or_unchanged(rgid), - unwrap_or_unchanged(egid), - unwrap_or_unchanged(sgid), - ) - .map_err(|err| err.into_pyexception(vm)) + fn setresgid(rgid: Gid, egid: Gid, sgid: Gid, vm: &VirtualMachine) -> PyResult<()> { + unistd::setresgid(rgid, egid, sgid).map_err(|err| err.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[pyfunction] - fn setregid(rgid: Option, egid: Option, vm: &VirtualMachine) -> PyResult<()> { - if let Some(rgid) = rgid { - unistd::setgid(rgid).map_err(|err| err.into_pyexception(vm))?; - } - if let Some(egid) = egid { - unistd::setegid(egid).map_err(|err| err.into_pyexception(vm))?; - } - Ok(()) + fn setregid(rgid: Gid, egid: Gid) -> nix::Result<()> { + let ret = unsafe { libc::setregid(rgid.as_raw(), egid.as_raw()) }; + nix::Error::result(ret).map(drop) } // cfg from nix @@ -1336,10 +1297,8 @@ pub mod module { target_os = "openbsd" ))] #[pyfunction] - fn initgroups(user_name: PyStrRef, gid: Option, vm: &VirtualMachine) -> PyResult<()> { + fn initgroups(user_name: PyStrRef, gid: Gid, vm: &VirtualMachine) -> PyResult<()> { let user = user_name.to_cstring(vm)?; - let gid = - gid.ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?; unistd::initgroups(&user, gid).map_err(|err| err.into_pyexception(vm)) } @@ -1347,15 +1306,11 @@ pub mod module { #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] #[pyfunction] fn setgroups( - group_ids: crate::function::ArgIterable>, + group_ids: crate::function::ArgIterable, vm: &VirtualMachine, ) -> PyResult<()> { - let gids = group_ids - .iter(vm)? - .collect::>, _>>()? - .ok_or_else(|| vm.new_errno_error(1, "Operation not permitted".to_string()))?; - let ret = unistd::setgroups(&gids); - ret.map_err(|err| err.into_pyexception(vm)) + let gids = group_ids.iter(vm)?.collect::, _>>()?; + unistd::setgroups(&gids).map_err(|err| err.into_pyexception(vm)) } #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] From 7933edad439203c39cf527f0ff6f508ec0a0f048 Mon Sep 17 00:00:00 2001 From: Noa Date: Wed, 18 Sep 2024 14:07:44 -0500 Subject: [PATCH 2/2] Add missing functionality to posixsubprocess --- Lib/test/test_exception_hierarchy.py | 1 - Lib/test/test_subprocess.py | 8 - stdlib/src/posixsubprocess.rs | 309 ++++++++++++++++++++------- vm/src/exceptions.rs | 3 +- vm/src/function/protocol.rs | 15 ++ 5 files changed, 250 insertions(+), 86 deletions(-) diff --git a/Lib/test/test_exception_hierarchy.py b/Lib/test/test_exception_hierarchy.py index 74664f7926..efee88cd5e 100644 --- a/Lib/test/test_exception_hierarchy.py +++ b/Lib/test/test_exception_hierarchy.py @@ -81,7 +81,6 @@ def _make_map(s): return _map _map = _make_map(_pep_map) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON") def test_errno_mapping(self): # The OSError constructor maps errnos to subclasses # A sample test for the basic functionality diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 1794acb742..bc898567f7 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1614,8 +1614,6 @@ def test_file_not_found_includes_filename(self): subprocess.call(['/opt/nonexistent_binary', 'with', 'some', 'args']) self.assertEqual(c.exception.filename, '/opt/nonexistent_binary') - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipIf(mswindows, "behavior currently not supported on Windows") def test_file_not_found_with_bad_cwd(self): with self.assertRaises(FileNotFoundError) as c: @@ -2033,8 +2031,6 @@ def test_start_new_session(self): child_sid = int(output) self.assertNotEqual(parent_sid, child_sid) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(hasattr(os, 'setpgid') and hasattr(os, 'getpgid'), 'no setpgid or getpgid on platform') def test_process_group_0(self): @@ -2053,8 +2049,6 @@ def test_process_group_0(self): child_pgid = int(output) self.assertNotEqual(parent_pgid, child_pgid) - # TODO: RUSTPYTHON - @unittest.expectedFailure @unittest.skipUnless(hasattr(os, 'setreuid'), 'no setreuid on platform') def test_user(self): # For code coverage of the user parameter. We don't care if we get a @@ -2112,8 +2106,6 @@ def test_user_error(self): with self.assertRaises(ValueError): subprocess.check_call(ZERO_RETURN_CMD, user=65535) - # TODO: RUSTPYTHON, observed gids do not match expected gids - @unittest.expectedFailure @unittest.skipUnless(hasattr(os, 'setregid'), 'no setregid() on platform') def test_group(self): gid = os.getegid() diff --git a/stdlib/src/posixsubprocess.rs b/stdlib/src/posixsubprocess.rs index cbc78d962b..47e19d317c 100644 --- a/stdlib/src/posixsubprocess.rs +++ b/stdlib/src/posixsubprocess.rs @@ -5,23 +5,29 @@ use crate::vm::{ stdlib::posix, {PyObjectRef, PyResult, TryFromObject, VirtualMachine}, }; -use nix::{errno::Errno, unistd}; -#[cfg(not(target_os = "redox"))] -use std::ffi::CStr; -#[cfg(not(target_os = "redox"))] -use std::os::unix::io::AsRawFd; +use itertools::Itertools; +use nix::{ + errno::Errno, + unistd::{self, Pid}, +}; use std::{ convert::Infallible as Never, - ffi::CString, - io::{self, prelude::*}, + ffi::{CStr, CString}, + io::prelude::*, + marker::PhantomData, + ops::Deref, + os::fd::FromRawFd, }; +use std::{fs::File, os::unix::io::AsRawFd}; use unistd::{Gid, Uid}; pub(crate) use _posixsubprocess::make_module; #[pymodule] mod _posixsubprocess { - use super::{exec, CStrPathLike, ForkExecArgs, ProcArgs}; + use rustpython_vm::{AsObject, TryFromBorrowedObject}; + + use super::*; use crate::vm::{convert::IntoPyException, PyResult, VirtualMachine}; #[pyfunction] @@ -29,19 +35,20 @@ mod _posixsubprocess { if args.preexec_fn.is_some() { return Err(vm.new_not_implemented_error("preexec_fn not supported yet".to_owned())); } - let cstrs_to_ptrs = |cstrs: &[CStrPathLike]| { - cstrs - .iter() - .map(|s| s.s.as_ptr()) - .chain(std::iter::once(std::ptr::null())) - .collect::>() + let extra_groups = args + .groups_list + .as_ref() + .map(|l| Vec::::try_from_borrowed_object(vm, l.as_object())) + .transpose()?; + let argv = CharPtrVec::from_iter(args.args.iter()); + let envp = args.env_list.as_ref().map(CharPtrVec::from_iter); + let procargs = ProcArgs { + argv: &argv, + envp: envp.as_deref(), + extra_groups: extra_groups.as_deref(), }; - let argv = cstrs_to_ptrs(&args.args); - let argv = &argv; - let envp = args.env_list.as_ref().map(|s| cstrs_to_ptrs(s)); - let envp = envp.as_deref(); match unsafe { nix::unistd::fork() }.map_err(|err| err.into_pyexception(vm))? { - nix::unistd::ForkResult::Child => exec(&args, ProcArgs { argv, envp }), + nix::unistd::ForkResult::Child => exec(&args, procargs), nix::unistd::ForkResult::Parent { child } => Ok(child.as_raw()), } } @@ -49,7 +56,6 @@ mod _posixsubprocess { macro_rules! gen_args { ($($field:ident: $t:ty),*$(,)?) => { - #[allow(dead_code)] #[derive(FromArgs)] struct ForkExecArgs { $(#[pyarg(positional)] $field: $t,)* @@ -66,6 +72,52 @@ impl TryFromObject for CStrPathLike { Ok(CStrPathLike { s }) } } +impl AsRef for CStrPathLike { + fn as_ref(&self) -> &CStr { + &self.s + } +} + +#[derive(Default)] +struct CharPtrVec<'a> { + vec: Vec<*const libc::c_char>, + marker: PhantomData>, +} + +impl<'a, T: AsRef> FromIterator<&'a T> for CharPtrVec<'a> { + fn from_iter>(iter: I) -> Self { + let vec = iter + .into_iter() + .map(|x| x.as_ref().as_ptr()) + .chain(std::iter::once(std::ptr::null())) + .collect(); + Self { + vec, + marker: PhantomData, + } + } +} + +impl<'a> Deref for CharPtrVec<'a> { + type Target = CharPtrSlice<'a>; + fn deref(&self) -> &Self::Target { + unsafe { + &*(self.vec.as_slice() as *const [*const libc::c_char] as *const CharPtrSlice<'a>) + } + } +} + +#[repr(transparent)] +struct CharPtrSlice<'a> { + marker: PhantomData<[&'a CStr]>, + slice: [*const libc::c_char], +} + +impl<'a> CharPtrSlice<'a> { + fn as_ptr(&self) -> *const *const libc::c_char { + self.slice.as_ptr() + } +} gen_args! { args: ArgSequence /* list */, @@ -84,39 +136,56 @@ gen_args! { errpipe_write: i32, restore_signals: bool, call_setsid: bool, - // TODO: Difference between gid_to_set and gid_object. - // One is a `gid_t` and the other is a `PyObject` in CPython. - gid_to_set: Option>, - gid_object: PyObjectRef, + pgid_to_set: libc::pid_t, + gid: Option, groups_list: Option, - uid: Option>, + uid: Option, child_umask: i32, preexec_fn: Option, - use_vfork: bool, + _use_vfork: bool, } // can't reallocate inside of exec(), so we reallocate prior to fork() and pass this along struct ProcArgs<'a> { - argv: &'a [*const libc::c_char], - envp: Option<&'a [*const libc::c_char]>, + argv: &'a CharPtrSlice<'a>, + envp: Option<&'a CharPtrSlice<'a>>, + extra_groups: Option<&'a [Gid]>, } fn exec(args: &ForkExecArgs, procargs: ProcArgs) -> ! { - match exec_inner(args, procargs) { + let mut ctx = ExecErrorContext::NoExec; + match exec_inner(args, procargs, &mut ctx) { Ok(x) => match x {}, Err(e) => { - let buf: &mut [u8] = &mut [0; 256]; - let mut cur = io::Cursor::new(&mut *buf); - // TODO: check if reached preexec, if not then have "noexec" after - let _ = write!(cur, "OSError:{}:", e as i32); - let pos = cur.position(); - let _ = unistd::write(args.errpipe_write, &buf[..pos as usize]); + let mut pipe = + std::mem::ManuallyDrop::new(unsafe { File::from_raw_fd(args.errpipe_write) }); + let _ = write!(pipe, "OSError:{}:{}", e as i32, ctx.as_msg()); std::process::exit(255) } } } -fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { +enum ExecErrorContext { + NoExec, + ChDir, + Exec, +} + +impl ExecErrorContext { + fn as_msg(&self) -> &'static str { + match self { + ExecErrorContext::NoExec => "noexec", + ExecErrorContext::ChDir => "noexec:chdir", + ExecErrorContext::Exec => "", + } + } +} + +fn exec_inner( + args: &ForkExecArgs, + procargs: ProcArgs, + ctx: &mut ExecErrorContext, +) -> nix::Result { for &fd in args.fds_to_keep.as_slice() { if fd != args.errpipe_write { posix::raw_set_inheritable(fd, true)? @@ -158,7 +227,7 @@ fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { dup_into_stdio(errwrite, 2)?; if let Some(ref cwd) = args.cwd { - unistd::chdir(cwd.s.as_c_str())? + unistd::chdir(cwd.s.as_c_str()).inspect_err(|_| *ctx = ExecErrorContext::ChDir)? } if args.child_umask >= 0 { @@ -170,28 +239,35 @@ fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { } if args.call_setsid { - #[cfg(not(target_os = "redox"))] unistd::setsid()?; } - if let Some(_groups_list) = args.groups_list.as_ref() { - // TODO: setgroups - // unistd::setgroups(groups_size, groups); + if args.pgid_to_set > -1 { + unistd::setpgid(Pid::from_raw(0), Pid::from_raw(args.pgid_to_set))?; } - if let Some(_gid) = args.gid_to_set.as_ref() { - // TODO: setgid - // unistd::setregid(gid, gid)?; + if let Some(_groups) = procargs.extra_groups { + #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "redox")))] + unistd::setgroups(_groups)?; } - if let Some(_uid) = args.uid.as_ref() { - // TODO: setuid - // unistd::setreuid(uid, uid)?; + if let Some(gid) = args.gid.filter(|x| x.as_raw() != u32::MAX) { + let ret = unsafe { libc::setregid(gid.as_raw(), gid.as_raw()) }; + nix::Error::result(ret)?; } + if let Some(uid) = args.uid.filter(|x| x.as_raw() != u32::MAX) { + let ret = unsafe { libc::setreuid(uid.as_raw(), uid.as_raw()) }; + nix::Error::result(ret)?; + } + + *ctx = ExecErrorContext::Exec; + if args.close_fds { - #[cfg(not(target_os = "redox"))] - close_fds(3, &args.fds_to_keep)?; + close_fds(KeepFds { + above: 2, + keep: &args.fds_to_keep, + }); } let mut first_err = None; @@ -211,47 +287,128 @@ fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { Err(first_err.unwrap_or_else(Errno::last)) } +#[derive(Copy, Clone)] +struct KeepFds<'a> { + above: i32, + keep: &'a [i32], +} + +impl KeepFds<'_> { + fn should_keep(self, fd: i32) -> bool { + fd > self.above && self.keep.binary_search(&fd).is_err() + } +} + +fn close_fds(keep: KeepFds<'_>) { + #[cfg(not(target_os = "redox"))] + if close_dir_fds(keep).is_ok() { + return; + } + #[cfg(target_os = "redox")] + if close_filetable_fds(keep).is_ok() { + return; + } + close_fds_brute_force(keep) +} + #[cfg(not(target_os = "redox"))] -fn close_fds(above: i32, keep: &[i32]) -> nix::Result<()> { +fn close_dir_fds(keep: KeepFds<'_>) -> nix::Result<()> { use nix::{dir::Dir, fcntl::OFlag}; - // TODO: close fds by brute force if readdir doesn't work: - // https://github.com/python/cpython/blob/3.8/Modules/_posixsubprocess.c#L220 + + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_vendor = "apple", + ))] + let fd_dir_name = c"/dev/fd"; + + #[cfg(any(target_os = "linux", target_os = "android"))] + let fd_dir_name = c"/proc/self/fd"; + let mut dir = Dir::open( - FD_DIR_NAME, + fd_dir_name, OFlag::O_RDONLY | OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty(), )?; let dirfd = dir.as_raw_fd(); - for e in dir.iter() { - if let Some(fd) = pos_int_from_ascii(e?.file_name()) { - if fd != dirfd && fd > above && !keep.contains(&fd) { - unistd::close(fd)? + 'outer: for e in dir.iter() { + let e = e?; + let mut parser = IntParser::default(); + for &c in e.file_name().to_bytes() { + if parser.feed(c).is_err() { + continue 'outer; } } + let fd = parser.num; + if fd != dirfd && keep.should_keep(fd) { + let _ = unistd::close(fd); + } } Ok(()) } -#[cfg(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_vendor = "apple", -))] -const FD_DIR_NAME: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/dev/fd\0") }; - -#[cfg(any(target_os = "linux", target_os = "android"))] -const FD_DIR_NAME: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc/self/fd\0") }; +#[cfg(target_os = "redox")] +fn close_filetable_fds(keep: KeepFds<'_>) -> nix::Result<()> { + use nix::fcntl; + use std::os::fd::{FromRawFd, OwnedFd}; + let fd = fcntl::open( + c"/scheme/thisproc/current/filetable", + fcntl::OFlag::O_RDONLY, + nix::sys::stat::Mode::empty(), + )?; + let filetable = unsafe { OwnedFd::from_raw_fd(fd) }; + let read_one = || -> nix::Result<_> { + let mut byte = 0; + let n = nix::unistd::read(filetable.as_raw_fd(), std::slice::from_mut(&mut byte))?; + Ok((n > 0).then_some(byte)) + }; + while let Some(c) = read_one()? { + let mut parser = IntParser::default(); + if parser.feed(c).is_err() { + continue; + } + let done = loop { + let Some(c) = read_one()? else { break true }; + if parser.feed(c).is_err() { + break false; + } + }; -#[cfg(not(target_os = "redox"))] -fn pos_int_from_ascii(name: &CStr) -> Option { - let mut num = 0; - for c in name.to_bytes() { - if !c.is_ascii_digit() { - return None; + let fd = parser.num as i32; + if fd != filetable.as_raw_fd() && keep.should_keep(fd) { + let _ = unistd::close(fd); + } + if done { + break; } - num = num * 10 + i32::from(c - b'0') } - Some(num) + Ok(()) +} + +fn close_fds_brute_force(keep: KeepFds<'_>) { + let max_fd = nix::unistd::sysconf(nix::unistd::SysconfVar::OPEN_MAX) + .ok() + .flatten() + .unwrap_or(256) as i32; + let fds = itertools::chain![Some(keep.above), keep.keep.iter().copied(), Some(max_fd)]; + for fd in fds.tuple_windows().flat_map(|(start, end)| start + 1..end) { + let _ = unistd::close(fd); + } +} + +#[derive(Default)] +struct IntParser { + num: i32, +} + +struct NonDigit; +impl IntParser { + fn feed(&mut self, c: u8) -> Result<(), NonDigit> { + let digit = (c as char).to_digit(10).ok_or(NonDigit)?; + self.num *= 10; + self.num += digit as i32; + Ok(()) + } } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 990d74ea0f..66ceeca67f 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1139,7 +1139,8 @@ pub(crate) fn errno_to_exc_type(errno: i32, vm: &VirtualMachine) -> Option<&'sta use crate::stdlib::errno::errors; let excs = &vm.ctx.exceptions; match errno { - errors::EWOULDBLOCK => Some(excs.blocking_io_error), + #[allow(unreachable_patterns)] // EAGAIN is sometimes the same as EWOULDBLOCK + errors::EWOULDBLOCK | errors::EAGAIN => Some(excs.blocking_io_error), errors::EALREADY => Some(excs.blocking_io_error), errors::EINPROGRESS => Some(excs.blocking_io_error), errors::EPIPE => Some(excs.broken_pipe_error), diff --git a/vm/src/function/protocol.rs b/vm/src/function/protocol.rs index 14aa5ad841..295332e480 100644 --- a/vm/src/function/protocol.rs +++ b/vm/src/function/protocol.rs @@ -224,6 +224,21 @@ impl std::ops::Deref for ArgSequence { } } +impl<'a, T> IntoIterator for &'a ArgSequence { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl IntoIterator for ArgSequence { + type Item = T; + type IntoIter = std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + impl TryFromObject for ArgSequence { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { obj.try_to_value(vm).map(Self)