Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/host_env/clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
disallowed-methods = []
disallowed-types = []
23 changes: 23 additions & 0 deletions crates/host_env/src/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,29 @@ pub fn login_tty(fd: i32) -> std::io::Result<()> {
}
}

#[cfg(any(target_os = "solaris", target_os = "illumos"))]
pub fn login_tty(fd: i32) -> std::io::Result<()> {
if unsafe { libc::setsid() } < 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::EPERM) {
return Err(err);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if unsafe { libc::ioctl(fd, libc::TIOCSCTTY, core::ptr::null::<libc::c_char>()) } < 0 {
return Err(std::io::Error::last_os_error());
}
if unsafe { libc::dup2(fd, 0) } < 0
|| unsafe { libc::dup2(fd, 1) } < 0
|| unsafe { libc::dup2(fd, 2) } < 0
{
return Err(std::io::Error::last_os_error());
}
if fd > 2 {
let _ = unsafe { libc::close(fd) };
}
Ok(())
}

#[cfg(not(target_os = "redox"))]
pub fn openpty() -> std::io::Result<(OwnedFd, OwnedFd)> {
let pty = nix::pty::openpty(None, None).map_err(std::io::Error::from)?;
Expand Down
31 changes: 31 additions & 0 deletions crates/host_env/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@ pub struct RUsage {
pub ru_nivcsw: libc::c_long,
}

impl RUsage {
pub fn utime_secs(&self) -> f64 {
timeval_to_secs(self.ru_utime)
}

pub fn stime_secs(&self) -> f64 {
timeval_to_secs(self.ru_stime)
}

pub fn total_cpu_duration(&self) -> Option<core::time::Duration> {
let utime = timeval_to_nanos(self.ru_utime)?;
let stime = timeval_to_nanos(self.ru_stime)?;
let total = utime.checked_add(stime)?;
Some(core::time::Duration::from_nanos(total as u64))
}
}

fn timeval_to_secs(tv: libc::timeval) -> f64 {
tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0)
}

fn timeval_to_nanos(tv: libc::timeval) -> Option<i64> {
let secs = widen_to_i64(tv.tv_sec)?.checked_mul(1_000_000_000)?;
let usecs = widen_to_i64(tv.tv_usec)?.checked_mul(1_000)?;
secs.checked_add(usecs)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

fn widen_to_i64<T: Copy + TryInto<i64>>(v: T) -> Option<i64> {
v.try_into().ok()
}

impl From<libc::rusage> for RUsage {
fn from(rusage: libc::rusage) -> Self {
Self {
Expand Down
258 changes: 256 additions & 2 deletions crates/host_env/src/select.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use core::mem::MaybeUninit;
use core::time::Duration;
use std::io;
use std::time::Instant;

#[cfg(unix)]
pub use libc::{
Expand Down Expand Up @@ -228,6 +230,161 @@ pub fn sec_to_timeval(sec: f64) -> timeval {
}
}

pub fn duration_to_timeval(d: Duration) -> timeval {
let mut tv = timeval {
tv_sec: 0 as _,
tv_usec: d.subsec_micros() as _,
};
tv.tv_sec = saturate_secs(d.as_secs(), tv.tv_sec);
tv
}

fn saturate_secs<T>(secs: u64, _sample: T) -> T
where
T: TryFrom<u64> + TryFrom<i32>,
{
T::try_from(secs).unwrap_or_else(|_| {
T::try_from(i32::MAX).unwrap_or_else(|_| {
T::try_from(0u64).unwrap_or_else(|_| unreachable!("tv_sec holds 0"))
})
})
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WaitKind {
Read,
Write,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WaitFd {
Ready,
Timeout,
}

#[derive(Debug)]
pub enum WaitFdError {
Interrupted,
Io(io::Error),
}

#[cfg(windows)]
type WaitFdArg = std::os::windows::io::RawSocket;
#[cfg(not(windows))]
type WaitFdArg = RawFd;

pub fn wait_fd(
fd: WaitFdArg,
kind: WaitKind,
deadline: Option<Instant>,
) -> Result<WaitFd, WaitFdError> {
#[cfg(unix)]
{
wait_fd_poll(fd, kind, deadline)
}
#[cfg(windows)]
{
wait_fd_select(fd as RawFd, kind, deadline)
}
#[cfg(not(any(unix, windows)))]
{
wait_fd_select(fd, kind, deadline)
}
}

#[cfg(unix)]
fn wait_fd_poll(
fd: RawFd,
kind: WaitKind,
deadline: Option<Instant>,
) -> Result<WaitFd, WaitFdError> {
let events = match kind {
WaitKind::Read => POLLIN | POLLPRI,
WaitKind::Write => POLLOUT,
};
let mut fds = [PollFd {
fd,
events,
revents: 0,
}];
loop {
let (timeout, is_capped) = match deadline {
None => (-1, false),
Some(deadline) => {
let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
return Ok(WaitFd::Timeout);
};
match duration_as_millis_ceiling(remaining) {
Some(ms) => (ms, false),
None => (i32::MAX, true),
}
}
};
match poll_fds(&mut fds, timeout) {
Ok(0) if is_capped => {}
Ok(0) => return Ok(WaitFd::Timeout),
Ok(_) if fds[0].revents & POLLNVAL != 0 => {
return Err(WaitFdError::Io(io::Error::from_raw_os_error(libc::EBADF)));
}
Ok(_) => return Ok(WaitFd::Ready),
Err(err) if err.kind() == io::ErrorKind::Interrupted => {
return Err(WaitFdError::Interrupted);
}
Err(err) => return Err(WaitFdError::Io(err)),
}
}
}

#[cfg(not(unix))]
fn wait_fd_select(
fd: RawFd,
kind: WaitKind,
deadline: Option<Instant>,
) -> Result<WaitFd, WaitFdError> {
let mut reads = FdSet::new();
let mut writes = FdSet::new();
let mut errs = FdSet::new();
match kind {
WaitKind::Read => {
reads.insert(fd);
errs.insert(fd);
}
WaitKind::Write => {
writes.insert(fd);
errs.insert(fd);
}
}
let mut timeout = match deadline {
None => None,
Some(deadline) => {
let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
return Ok(WaitFd::Timeout);
};
Some(duration_to_timeval(remaining))
}
};
let nfds = cfg_select! {
windows => 0,
_ => fd.saturating_add(1) as libc::c_int,
};
match select(nfds, &mut reads, &mut writes, &mut errs, timeout.as_mut()) {
Ok(0) => Ok(WaitFd::Timeout),
Ok(_) => Ok(WaitFd::Ready),
Err(err) if err.kind() == io::ErrorKind::Interrupted => Err(WaitFdError::Interrupted),
Err(err) => Err(WaitFdError::Io(err)),
}
}

/// Convert a duration to a `poll(2)` millisecond timeout, rounding toward +∞.
#[cfg(unix)]
pub fn duration_as_millis_ceiling(d: Duration) -> Option<i32> {
let mut ms = d.as_millis();
if Duration::from_millis(ms.min(u128::from(u64::MAX)) as u64) < d {
ms = ms.saturating_add(1);
}
i32::try_from(ms).ok()
}

#[cfg(unix)]
#[inline]
pub fn search_poll_fd(fds: &[PollFd], fd: i32) -> Result<usize, usize> {
Expand Down Expand Up @@ -342,6 +499,7 @@ pub mod epoll {
pub mod kqueue {
use alloc::sync::{Arc, Weak};
use core::sync::atomic::{AtomicI32, Ordering};
use core::time::Duration;
use parking_lot::Mutex;
use std::io;
use std::os::fd::BorrowedFd;
Expand All @@ -354,7 +512,55 @@ pub mod kqueue {
NOTE_REVOKE, NOTE_TRACK, NOTE_TRACKERR, NOTE_WRITE,
};

// NetBSD widths differ from i16/u16; the cast is a no-op elsewhere.
#[allow(clippy::unnecessary_cast)]
pub const DEFAULT_FILTER: i16 = EVFILT_READ as i16;
#[allow(clippy::unnecessary_cast)]
pub const DEFAULT_FLAGS: u16 = EV_ADD as u16;

#[derive(Copy, Clone, Debug)]
pub struct Timespec {
pub sec: i64,
pub nsec: i64,
}

impl Timespec {
pub fn from_secs(secs: f64) -> Option<Self> {
if !secs.is_finite() || secs > i64::MAX as f64 {
return None;
}
let mut sec = secs.trunc() as i64;
let mut nsec = ((secs - sec as f64) * 1e9).round() as i64;
if nsec >= 1_000_000_000 {
sec = sec.saturating_add(1);
nsec -= 1_000_000_000;
}
Some(Self { sec, nsec })
}

pub fn from_duration(d: Duration) -> Self {
Self {
sec: d.as_secs() as i64,
nsec: i64::from(d.subsec_nanos()),
}
}

pub fn to_duration(self) -> Option<Duration> {
if self.sec < 0 || !(0..1_000_000_000).contains(&self.nsec) {
return None;
}
Some(Duration::new(self.sec as u64, self.nsec as u32))
}

fn to_libc(self) -> libc::timespec {
libc::timespec {
tv_sec: self.sec,
tv_nsec: self.nsec,
}
}
}

#[derive(Copy, Clone, Debug, Default)]
pub struct Event {
pub ident: usize,
pub filter: i16,
Expand Down Expand Up @@ -441,11 +647,12 @@ pub mod kqueue {
kq: i32,
changelist: &[Event],
eventlist: &mut [Event],
timeout: Option<&libc::timespec>,
timeout: Option<&Timespec>,
) -> io::Result<usize> {
let chl: Vec<libc::kevent> = changelist.iter().copied().map(Event::to_libc).collect();
let mut evl = vec![unsafe { core::mem::zeroed() }; eventlist.len()];
let timeout = timeout.map_or(core::ptr::null(), |t| t);
let timeout = timeout.map(|t| t.to_libc());
let timeout = timeout.as_ref().map_or(core::ptr::null(), |t| t);
let ret = unsafe {
libc::kevent(
kq,
Expand Down Expand Up @@ -480,3 +687,50 @@ pub mod kqueue {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn duration_to_timeval_uses_micros() {
let tv = duration_to_timeval(Duration::from_micros(1_500_250));
assert_eq!(tv.tv_sec as u64, 1);
assert_eq!(tv.tv_usec as u32, 500_250);
}

#[cfg(unix)]
#[test]
fn poll_timeout_rounds_fractional_millis_up() {
assert_eq!(duration_as_millis_ceiling(Duration::ZERO), Some(0));
assert_eq!(
duration_as_millis_ceiling(Duration::from_micros(100)),
Some(1)
);
assert_eq!(
duration_as_millis_ceiling(Duration::from_millis(1)),
Some(1)
);
assert_eq!(
duration_as_millis_ceiling(Duration::from_micros(1100)),
Some(2)
);
}

#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))]
#[test]
fn timespec_carries_nanosecond_overflow() {
let ts = kqueue::Timespec::from_secs(0.999_999_999_9).unwrap();
assert_eq!(ts.sec, 1);
assert!(ts.nsec < 1_000_000_000);
assert!(kqueue::Timespec::from_secs(f64::INFINITY).is_none());
assert!(kqueue::Timespec::from_secs(f64::NAN).is_none());
}
}
19 changes: 19 additions & 0 deletions crates/stdlib/clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,23 @@ disallowed-methods = [
{ path = "std::net::TcpStream::connect", reason = "use rustpython_host_env for host network access" },
{ path = "std::net::TcpListener::bind", reason = "use rustpython_host_env for host network access" },
{ path = "std::net::UdpSocket::bind", reason = "use rustpython_host_env for host network access" },
{ path = "libc::select", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::pselect", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::poll", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::ppoll", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::kqueue", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::kevent", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::kevent64", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::epoll_create", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::epoll_create1", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::epoll_ctl", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::epoll_wait", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::epoll_pwait", reason = "use rustpython_host_env::select", allow-invalid = true },
{ path = "libc::epoll_pwait2", reason = "use rustpython_host_env::select", allow-invalid = true },
]

disallowed-types = [
{ path = "libc::fd_set", reason = "use rustpython_host_env::select::FdSet", allow-invalid = true },
{ path = "libc::kevent", reason = "use rustpython_host_env::select::kqueue::Event", allow-invalid = true },
{ path = "libc::epoll_event", reason = "use rustpython_host_env::select::epoll", allow-invalid = true },
]
Loading
Loading