diff --git a/Cargo.lock b/Cargo.lock index e177c9773..32afa5b21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1349,7 +1349,20 @@ dependencies = [ "bitflags", "cfg-if", "libc", - "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", + "static_assertions", ] [[package]] @@ -1611,6 +1624,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.26" @@ -2120,7 +2139,7 @@ dependencies = [ "memchr", "memmap2", "mt19937", - "nix 0.24.2", + "nix 0.26.2", "num-bigint", "num-complex", "num-integer", @@ -2188,7 +2207,7 @@ dependencies = [ "log", "memchr", "memoffset 0.6.5", - "nix 0.24.2", + "nix 0.26.2", "num-bigint", "num-complex", "num-integer", diff --git a/Cargo.toml b/Cargo.toml index 3ea9c165e..f305420ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ insta = "1.14.0" itertools = "0.10.3" libc = "0.2.133" log = "0.4.16" -nix = "0.24" +nix = "0.26" num-complex = "0.4.0" num-bigint = "0.4.3" num-integer = "0.1.44" diff --git a/stdlib/src/posixsubprocess.rs b/stdlib/src/posixsubprocess.rs index a1ea0c134..ad3d89227 100644 --- a/stdlib/src/posixsubprocess.rs +++ b/stdlib/src/posixsubprocess.rs @@ -177,6 +177,8 @@ fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result { let mut first_err = None; for exec in args.exec_list.as_slice() { + // not using nix's versions of these functions because those allocate the char-ptr array, + // and we can't allocate if let Some(envp) = procargs.envp { unsafe { libc::execve(exec.s.as_ptr(), procargs.argv.as_ptr(), envp.as_ptr()) }; } else { @@ -195,9 +197,8 @@ fn close_fds(above: i32, keep: &[i32]) -> 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 - let path = unsafe { CStr::from_bytes_with_nul_unchecked(FD_DIR_NAME) }; let mut dir = Dir::open( - path, + FD_DIR_NAME, OFlag::O_RDONLY | OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty(), )?; @@ -219,10 +220,10 @@ fn close_fds(above: i32, keep: &[i32]) -> nix::Result<()> { target_os = "openbsd", target_vendor = "apple", ))] -const FD_DIR_NAME: &[u8] = b"/dev/fd\0"; +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: &[u8] = b"/proc/self/fd\0"; +const FD_DIR_NAME: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc/self/fd\0") }; #[cfg(not(target_os = "redox"))] fn pos_int_from_ascii(name: &CStr) -> Option { diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index 8b9d4fb0c..d513b93e2 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -891,53 +891,8 @@ mod _socket { use std::os::unix::ffi::OsStrExt; let buf = crate::vm::function::ArgStrOrBytesLike::try_from_object(vm, addr)?; let path = &*buf.borrow_bytes(); - if cfg!(any(target_os = "linux", target_os = "android")) - && path.first() == Some(&0) - { - use libc::{sa_family_t, socklen_t}; - use {socket2::SockAddr, std::ptr}; - unsafe { - // based on SockAddr::unix - // TODO: upstream or fix socklen check for SockAddr::unix()? - SockAddr::init(|storage, len| { - // Safety: `SockAddr::init` zeros the address, which is a valid - // representation. - let storage: &mut libc::sockaddr_un = &mut *storage.cast(); - let len: &mut socklen_t = &mut *len; - - let bytes = path; - if bytes.len() > storage.sun_path.len() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "path must be shorter than SUN_LEN", - )); - } - - storage.sun_family = libc::AF_UNIX as sa_family_t; - // Safety: `bytes` and `addr.sun_path` are not overlapping and - // both point to valid memory. - // `SockAddr::init` zeroes the memory, so the path is already - // null terminated. - ptr::copy_nonoverlapping( - bytes.as_ptr(), - storage.sun_path.as_mut_ptr() as *mut u8, - bytes.len(), - ); - - let base = storage as *const _ as usize; - let path = &storage.sun_path as *const _ as usize; - let sun_path_offset = path - base; - let length = sun_path_offset + bytes.len(); - *len = length as socklen_t; - - Ok(()) - }) - } - .map(|(_, addr)| addr) - } else { - socket2::SockAddr::unix(ffi::OsStr::from_bytes(path)) - } - .map_err(|_| vm.new_os_error("AF_UNIX path too long".to_owned()).into()) + socket2::SockAddr::unix(ffi::OsStr::from_bytes(path)) + .map_err(|_| vm.new_os_error("AF_UNIX path too long".to_owned()).into()) } c::AF_INET => { let tuple: PyTupleRef = addr.downcast().map_err(|obj| { @@ -1087,20 +1042,7 @@ mod _socket { _ => {} } if socket_kind == -1 { - // TODO: when socket2 cuts a new release, type will be available on all os - // socket_kind = sock.r#type().map_err(|e| e.into_pyexception(vm))?.into(); - let res = unsafe { - c::getsockopt( - sock_fileno(&sock) as _, - c::SOL_SOCKET, - c::SO_TYPE, - &mut socket_kind as *mut libc::c_int as *mut _, - &mut (std::mem::size_of::() as _), - ) - }; - if res < 0 { - return Err(crate::common::os::errno().into()); - } + socket_kind = sock.r#type().map_err(|e| e.into_pyexception(vm))?.into(); } cfg_if::cfg_if! { if #[cfg(any( @@ -1601,30 +1543,23 @@ mod _socket { if let Some(addr) = addr.as_socket() { return get_ip_addr_tuple(&addr, vm); } - match addr.family() as i32 { - #[cfg(unix)] - libc::AF_UNIX => { - let addr_len = addr.len() as usize; - let unix_addr = unsafe { &*(addr.as_ptr() as *const libc::sockaddr_un) }; - let path_u8 = unsafe { &*(&unix_addr.sun_path[..] as *const [_] as *const [u8]) }; - let sun_path_offset = - &unix_addr.sun_path as *const _ as usize - unix_addr as *const _ as usize; - if cfg!(any(target_os = "linux", target_os = "android")) - && addr_len > sun_path_offset - && unix_addr.sun_path[0] == 0 - { - let abstractaddrlen = addr_len - sun_path_offset; - let abstractpath = &path_u8[..abstractaddrlen]; - vm.ctx.new_bytes(abstractpath.to_vec()).into() - } else { - let len = memchr::memchr(b'\0', path_u8).unwrap_or(path_u8.len()); - let path = &path_u8[..len]; - vm.ctx.new_str(String::from_utf8_lossy(path)).into() - } + #[cfg(unix)] + use nix::sys::socket::{SockaddrLike, UnixAddr}; + #[cfg(unix)] + if let Some(unix_addr) = unsafe { UnixAddr::from_raw(addr.as_ptr(), Some(addr.len())) } { + use std::os::unix::ffi::OsStrExt; + #[cfg(any(target_os = "android", target_os = "linux"))] + if let Some(abstractpath) = unix_addr.as_abstract() { + return vm.ctx.new_bytes([b"\0", abstractpath].concat()).into(); } - // TODO: support more address families - _ => (String::new(), 0).to_pyobject(vm), + // necessary on macos + let path = ffi::OsStr::as_bytes(unix_addr.path().unwrap_or("".as_ref()).as_ref()); + let nul_pos = memchr::memchr(b'\0', path).unwrap_or(path.len()); + let path = ffi::OsStr::from_bytes(&path[..nul_pos]); + return vm.ctx.new_str(path.to_string_lossy()).into(); } + // TODO: support more address families + (String::new(), 0).to_pyobject(vm) } #[pyfunction] @@ -1764,25 +1699,19 @@ mod _socket { let fd = sock_fileno(sock); #[cfg(unix)] { - let mut pollfd = libc::pollfd { - fd, - events: match kind { - SelectKind::Read => libc::POLLIN, - SelectKind::Write => libc::POLLOUT, - SelectKind::Connect => libc::POLLOUT | libc::POLLERR, - }, - revents: 0, + use nix::poll::*; + let events = match kind { + SelectKind::Read => PollFlags::POLLIN, + SelectKind::Write => PollFlags::POLLOUT, + SelectKind::Connect => PollFlags::POLLOUT | PollFlags::POLLERR, }; + let mut pollfd = [PollFd::new(fd, events)]; let timeout = match interval { Some(d) => d.as_millis() as _, None => -1, }; - let ret = unsafe { libc::poll(&mut pollfd, 1, timeout) }; - if ret < 0 { - Err(io::Error::last_os_error()) - } else { - Ok(ret == 0) - } + let ret = poll(&mut pollfd, timeout)?; + Ok(ret == 0) } #[cfg(windows)] { diff --git a/vm/src/stdlib/posix.rs b/vm/src/stdlib/posix.rs index 2d16d0b42..7f14b2db5 100644 --- a/vm/src/stdlib/posix.rs +++ b/vm/src/stdlib/posix.rs @@ -1091,13 +1091,9 @@ pub mod module { #[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_str(name).into()) - } + let name = unistd::ttyname(fd).map_err(|e| e.into_pyexception(vm))?; + let name = name.into_os_string().into_string().unwrap(); + Ok(vm.ctx.new_str(name).into()) } #[pyfunction] @@ -1129,30 +1125,24 @@ pub mod module { #[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)) - } + let ret = unistd::getresuid().map_err(|e| e.into_pyexception(vm))?; + Ok(( + ret.real.as_raw(), + ret.effective.as_raw(), + ret.saved.as_raw(), + )) } // 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)) - } + let ret = unistd::getresgid().map_err(|e| e.into_pyexception(vm))?; + Ok(( + ret.real.as_raw(), + ret.effective.as_raw(), + ret.saved.as_raw(), + )) } // cfg from nix diff --git a/vm/src/stdlib/time.rs b/vm/src/stdlib/time.rs index a9ab54174..fbf7538bb 100644 --- a/vm/src/stdlib/time.rs +++ b/vm/src/stdlib/time.rs @@ -389,11 +389,11 @@ mod unix { use super::{SEC_TO_NS, US_TO_NS}; #[cfg_attr(target_os = "macos", allow(unused_imports))] use crate::{ - builtins::{try_bigint_to_f64, PyFloat, PyIntRef, PyNamespace, PyStrRef}, - function::Either, - stdlib::os, - PyRef, PyResult, VirtualMachine, + builtins::{PyNamespace, PyStrRef}, + convert::IntoPyException, + PyObject, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine, }; + use nix::{sys::time::TimeSpec, time::ClockId}; use std::time::Duration; #[cfg(target_os = "solaris")] @@ -425,83 +425,62 @@ mod unix { #[pyattr] use libc::{CLOCK_PROF, CLOCK_UPTIME}; - fn get_clock_time(clk_id: PyIntRef, vm: &VirtualMachine) -> PyResult { - let mut timespec = std::mem::MaybeUninit::uninit(); - let ts: libc::timespec = unsafe { - if libc::clock_gettime(clk_id.try_to_primitive(vm)?, timespec.as_mut_ptr()) == -1 { - return Err(os::errno_err(vm)); - } - timespec.assume_init() - }; - Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32)) + impl TryFromBorrowedObject for ClockId { + fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult { + obj.try_to_value(vm).map(ClockId::from_raw) + } + } + + fn get_clock_time(clk_id: ClockId, vm: &VirtualMachine) -> PyResult { + let ts = nix::time::clock_gettime(clk_id).map_err(|e| e.into_pyexception(vm))?; + Ok(ts.into()) } #[pyfunction] - fn clock_gettime(clk_id: PyIntRef, vm: &VirtualMachine) -> PyResult { + fn clock_gettime(clk_id: ClockId, vm: &VirtualMachine) -> PyResult { get_clock_time(clk_id, vm).map(|d| d.as_secs_f64()) } #[pyfunction] - fn clock_gettime_ns(clk_id: PyIntRef, vm: &VirtualMachine) -> PyResult { + fn clock_gettime_ns(clk_id: ClockId, vm: &VirtualMachine) -> PyResult { get_clock_time(clk_id, vm).map(|d| d.as_nanos()) } #[cfg(not(target_os = "redox"))] #[pyfunction] - fn clock_getres(clk_id: PyIntRef, vm: &VirtualMachine) -> PyResult { - let mut timespec = std::mem::MaybeUninit::uninit(); - let ts: libc::timespec = unsafe { - if libc::clock_getres(clk_id.try_to_primitive(vm)?, timespec.as_mut_ptr()) == -1 { - return Err(os::errno_err(vm)); - } - timespec.assume_init() - }; - Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32).as_secs_f64()) + fn clock_getres(clk_id: ClockId, vm: &VirtualMachine) -> PyResult { + let ts = nix::time::clock_getres(clk_id).map_err(|e| e.into_pyexception(vm))?; + Ok(Duration::from(ts).as_secs_f64()) } #[cfg(not(target_os = "redox"))] - #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))] - fn set_clock_time( - clk_id: PyIntRef, - timespec: libc::timespec, - vm: &VirtualMachine, - ) -> PyResult<()> { - let res = unsafe { libc::clock_settime(clk_id.try_to_primitive(vm)?, ×pec) }; - if res == -1 { - return Err(os::errno_err(vm)); - } - Ok(()) + #[cfg(not(target_vendor = "apple"))] + fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> { + nix::time::clock_settime(clk_id, timespec).map_err(|e| e.into_pyexception(vm)) + } + + #[cfg(not(target_os = "redox"))] + #[cfg(target_os = "macos")] + fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> { + // idk why nix disables clock_settime on macos + let ret = unsafe { libc::clock_settime(clk_id.as_raw(), timespec.as_ref()) }; + nix::Error::result(ret) + .map(drop) + .map_err(|e| e.into_pyexception(vm)) } #[cfg(not(target_os = "redox"))] #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))] #[pyfunction] - fn clock_settime( - clk_id: PyIntRef, - time: Either, PyIntRef>, - vm: &VirtualMachine, - ) -> PyResult<()> { - let time = match time { - Either::A(f) => f.to_f64(), - Either::B(z) => try_bigint_to_f64(z.as_bigint(), vm)?, - }; - let nanos = time.fract() * (SEC_TO_NS as f64); - let ts = libc::timespec { - tv_sec: time.floor() as libc::time_t, - tv_nsec: nanos as _, - }; - set_clock_time(clk_id, ts, vm) + fn clock_settime(clk_id: ClockId, time: Duration, vm: &VirtualMachine) -> PyResult<()> { + set_clock_time(clk_id, time.into(), vm) } #[cfg(not(target_os = "redox"))] #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))] #[pyfunction] - fn clock_settime_ns(clk_id: PyIntRef, time: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { - let time: libc::time_t = time.try_to_primitive(vm)?; - let ts = libc::timespec { - tv_sec: time / (SEC_TO_NS as libc::time_t), - tv_nsec: time.rem_euclid(SEC_TO_NS as libc::time_t) as _, - }; + fn clock_settime_ns(clk_id: ClockId, time: libc::time_t, vm: &VirtualMachine) -> PyResult<()> { + let ts = Duration::from_nanos(time as _).into(); set_clock_time(clk_id, ts, vm) } @@ -522,25 +501,25 @@ mod unix { false, "time.clock_gettime(CLOCK_MONOTONIC)", true, - clock_getres(vm.ctx.new_int(CLOCK_MONOTONIC), vm)?, + clock_getres(ClockId::CLOCK_MONOTONIC, vm)?, ), "process_time" => ( false, "time.clock_gettime(CLOCK_PROCESS_CPUTIME_ID)", true, - clock_getres(vm.ctx.new_int(CLOCK_PROCESS_CPUTIME_ID), vm)?, + clock_getres(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)?, ), "thread_time" => ( false, "time.clock_gettime(CLOCK_THREAD_CPUTIME_ID)", true, - clock_getres(vm.ctx.new_int(CLOCK_THREAD_CPUTIME_ID), vm)?, + clock_getres(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)?, ), "time" => ( true, "time.clock_gettime(CLOCK_REALTIME)", false, - clock_getres(vm.ctx.new_int(CLOCK_REALTIME), vm)?, + clock_getres(ClockId::CLOCK_REALTIME, vm)?, ), _ => return Err(vm.new_value_error("unknown clock".to_owned())), }; @@ -568,22 +547,19 @@ mod unix { } pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult { - get_clock_time(vm.ctx.new_int(CLOCK_MONOTONIC), vm) + get_clock_time(ClockId::CLOCK_MONOTONIC, vm) } pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult { - get_clock_time(vm.ctx.new_int(CLOCK_MONOTONIC), vm) + get_clock_time(ClockId::CLOCK_MONOTONIC, vm) } #[pyfunction] fn sleep(dur: Duration, vm: &VirtualMachine) -> PyResult<()> { // this is basically std::thread::sleep, but that catches interrupts and we don't want to; - let mut ts = libc::timespec { - tv_sec: std::cmp::min(libc::time_t::max_value() as u64, dur.as_secs()) as libc::time_t, - tv_nsec: dur.subsec_nanos() as _, - }; - let res = unsafe { libc::nanosleep(&ts, &mut ts) }; + let ts = TimeSpec::from(dur); + let res = unsafe { libc::nanosleep(ts.as_ref(), std::ptr::null_mut()) }; let interrupted = res == -1 && nix::errno::errno() == libc::EINTR; if interrupted { @@ -600,14 +576,7 @@ mod unix { target_os = "redox" )))] pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult { - let time: libc::timespec = unsafe { - let mut time = std::mem::MaybeUninit::uninit(); - if libc::clock_gettime(CLOCK_THREAD_CPUTIME_ID, time.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - time.assume_init() - }; - Ok(Duration::new(time.tv_sec as u64, time.tv_nsec as u32)) + get_clock_time(ClockId::CLOCK_THREAD_CPUTIME_ID, vm) } #[cfg(target_os = "solaris")] @@ -622,14 +591,7 @@ mod unix { target_os = "openbsd", )))] pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { - let time: libc::timespec = unsafe { - let mut time = std::mem::MaybeUninit::uninit(); - if libc::clock_gettime(CLOCK_PROCESS_CPUTIME_ID, time.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - time.assume_init() - }; - Ok(Duration::new(time.tv_sec as u64, time.tv_nsec as u32)) + get_clock_time(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm) } #[cfg(any( @@ -639,6 +601,7 @@ mod unix { target_os = "openbsd", ))] pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult { + use nix::sys::resource::{getrusage, UsageWho}; fn from_timeval(tv: libc::timeval, vm: &VirtualMachine) -> PyResult { (|tv: libc::timeval| { let t = tv.tv_sec.checked_mul(SEC_TO_NS)?; @@ -649,15 +612,9 @@ mod unix { vm.new_overflow_error("timestamp too large to convert to i64".to_owned()) }) } - let ru: libc::rusage = unsafe { - let mut ru = std::mem::MaybeUninit::uninit(); - if libc::getrusage(libc::RUSAGE_SELF, ru.as_mut_ptr()) == -1 { - return Err(vm.new_os_error("Failed to get clock time".to_owned())); - } - ru.assume_init() - }; - let utime = from_timeval(ru.ru_utime, vm)?; - let stime = from_timeval(ru.ru_stime, vm)?; + let ru = getrusage(UsageWho::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?; + let utime = from_timeval(ru.user_time().into(), vm)?; + let stime = from_timeval(ru.system_time().into(), vm)?; Ok(Duration::from_nanos((utime + stime) as u64)) }