From 05a0202d4372a72f47c636770229bcccee9c0900 Mon Sep 17 00:00:00 2001 From: Noah <33094578+coolreader18@users.noreply.github.com> Date: Tue, 9 Jun 2020 15:47:38 -0500 Subject: [PATCH] Add os.posix_spawn[p] --- vm/src/obj/objdict.rs | 8 +- vm/src/stdlib/os.rs | 210 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 210 insertions(+), 8 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index c9b088b03..da645b4e6 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -39,6 +39,7 @@ impl PyValue for PyDict { } // Python dict methods: +#[allow(clippy::len_without_is_empty)] #[pyimpl(flags(BASETYPE))] impl PyDictRef { #[pyslot] @@ -179,7 +180,7 @@ impl PyDictRef { } #[pymethod(magic)] - fn len(self) -> usize { + pub fn len(self) -> usize { self.entries.len() } @@ -531,6 +532,11 @@ impl Iterator for DictIter { None => None, } } + + fn size_hint(&self) -> (usize, Option) { + let l = self.dict.entries.len_from_entry_index(self.position); + (l, Some(l)) + } } macro_rules! dict_iterator { diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 0c2ffabf8..6a012fc40 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,7 +1,9 @@ +use std::convert::TryFrom; use std::ffi; use std::fs::File; use std::fs::OpenOptions; use std::io::{self, ErrorKind, Read, Write}; +use std::mem::MaybeUninit; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; #[cfg(windows)] @@ -41,13 +43,8 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -// cfg from nix -#[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" -))] +// just to avoid unused import warnings +#[cfg(unix)] use crate::pyobject::PyIterable; #[cfg(windows)] @@ -1581,6 +1578,196 @@ fn os_setgroups(group_ids: PyIterable, vm: &VirtualMachine) -> PyResult<()> ret.map_err(|err| convert_nix_error(vm, err)) } +#[cfg(unix)] +fn envp_from_dict(dict: PyDictRef, vm: &VirtualMachine) -> PyResult> { + use std::os::unix::ffi::OsStringExt; + dict.into_iter() + .map(|(k, v)| { + let k = PyPathLike::try_from_object(vm, k)?.path.into_vec(); + let v = PyPathLike::try_from_object(vm, v)?.path.into_vec(); + 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() +} + +#[derive(FromArgs)] +struct PosixSpawnArgs { + #[pyarg(positional_only)] + path: PyPathLike, + #[pyarg(positional_only)] + args: PyIterable, + #[pyarg(positional_only)] + env: PyDictRef, + #[pyarg(keyword_only, default = "None")] + file_actions: Option>, + #[pyarg(keyword_only, default = "None")] + 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 { + use std::os::unix::ffi::OsStringExt; + let path = ffi::CString::new(self.path.path.into_vec()) + .map_err(|_| vm.new_value_error("path should not have nul bytes".to_owned()))?; + + let mut file_actions = unsafe { + let mut fa = 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_object(vm, id.clone())?; + let id = PosixSpawnFileActionIdentifier::try_from(id) + .map_err(|_| vm.new_type_error("Unknown file_actions identifier".to_owned()))?; + let args = PyFuncArgs::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.path.into_vec()).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 = 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?.path.into_vec()) + .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, 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"))] +fn os_posix_spawn(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult { + args.spawn(false, vm) +} +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] +fn os_posix_spawnp(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult { + args.spawn(true, vm) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -1846,6 +2033,15 @@ fn extend_module_platform_specific(vm: &VirtualMachine, module: &PyObjectRef) { extend_module!(vm, module, { "pipe2" => ctx.new_function(os_pipe2), }); + + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))] + extend_module!(vm, module, { + "posix_spawn" => ctx.new_function(os_posix_spawn), + "posix_spawnp" => ctx.new_function(os_posix_spawnp), + "POSIX_SPAWN_OPEN" => ctx.new_int(i32::from(PosixSpawnFileActionIdentifier::Open)), + "POSIX_SPAWN_CLOSE" => ctx.new_int(i32::from(PosixSpawnFileActionIdentifier::Close)), + "POSIX_SPAWN_DUP2" => ctx.new_int(i32::from(PosixSpawnFileActionIdentifier::Dup2)), + }); } #[cfg(windows)]