//! libc syscalls supporting `rustix::process`. #[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))] use super::types::RawCpuSet; use crate::backend::c; #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] use crate::backend::conv::ret_infallible; #[cfg(linux_kernel)] use crate::backend::conv::ret_u32; #[cfg(not(target_os = "wasi"))] use crate::backend::conv::{borrowed_fd, ret_pid_t, ret_usize}; #[cfg(feature = "fs")] use crate::backend::conv::{c_str, ret_discarded_char_ptr}; use crate::backend::conv::{ret, ret_c_int}; #[cfg(not(target_os = "wasi"))] use crate::fd::BorrowedFd; #[cfg(target_os = "linux")] use crate::fd::{AsRawFd, OwnedFd, RawFd}; #[cfg(feature = "fs")] use crate::ffi::CStr; #[cfg(feature = "fs")] use crate::fs::Mode; use crate::io; #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] use crate::process::Uid; #[cfg(linux_kernel)] use crate::process::{Cpuid, MembarrierCommand, MembarrierQuery}; #[cfg(not(target_os = "wasi"))] use crate::process::{Gid, Pid, RawPid, Signal, WaitOptions, WaitStatus}; #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] use crate::process::{Resource, Rlimit}; #[cfg(not(any(target_os = "redox", target_os = "openbsd", target_os = "wasi")))] use crate::process::{WaitId, WaitidOptions, WaitidStatus}; use core::mem::MaybeUninit; #[cfg(target_os = "linux")] use { super::super::conv::ret_owned_fd, crate::process::PidfdFlags, crate::process::PidfdGetfdFlags, }; #[cfg(feature = "fs")] #[cfg(not(target_os = "wasi"))] pub(crate) fn chdir(path: &CStr) -> io::Result<()> { unsafe { ret(c::chdir(c_str(path))) } } #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] pub(crate) fn fchdir(dirfd: BorrowedFd<'_>) -> io::Result<()> { unsafe { ret(c::fchdir(borrowed_fd(dirfd))) } } #[cfg(feature = "fs")] #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] pub(crate) fn chroot(path: &CStr) -> io::Result<()> { unsafe { ret(c::chroot(c_str(path))) } } #[cfg(feature = "fs")] #[cfg(not(target_os = "wasi"))] pub(crate) fn getcwd(buf: &mut [MaybeUninit]) -> io::Result<()> { unsafe { ret_discarded_char_ptr(c::getcwd(buf.as_mut_ptr().cast(), buf.len())) } } // The `membarrier` syscall has a third argument, but it's only used when // the `flags` argument is `MEMBARRIER_CMD_FLAG_CPU`. #[cfg(linux_kernel)] syscall! { fn membarrier_all( cmd: c::c_int, flags: c::c_uint ) via SYS_membarrier -> c::c_int } #[cfg(linux_kernel)] pub(crate) fn membarrier_query() -> MembarrierQuery { // glibc does not have a wrapper for `membarrier`; [the documentation] // says to use `syscall`. // // [the documentation]: https://man7.org/linux/man-pages/man2/membarrier.2.html#NOTES const MEMBARRIER_CMD_QUERY: u32 = 0; unsafe { match ret_u32(membarrier_all(MEMBARRIER_CMD_QUERY as i32, 0)) { Ok(query) => MembarrierQuery::from_bits_retain(query), Err(_) => MembarrierQuery::empty(), } } } #[cfg(linux_kernel)] pub(crate) fn membarrier(cmd: MembarrierCommand) -> io::Result<()> { unsafe { ret(membarrier_all(cmd as i32, 0)) } } #[cfg(linux_kernel)] pub(crate) fn membarrier_cpu(cmd: MembarrierCommand, cpu: Cpuid) -> io::Result<()> { const MEMBARRIER_CMD_FLAG_CPU: u32 = 1; syscall! { fn membarrier_cpu( cmd: c::c_int, flags: c::c_uint, cpu_id: c::c_int ) via SYS_membarrier -> c::c_int } unsafe { ret(membarrier_cpu( cmd as i32, MEMBARRIER_CMD_FLAG_CPU, bitcast!(cpu.as_raw()), )) } } #[cfg(not(target_os = "wasi"))] #[inline] #[must_use] pub(crate) fn getppid() -> Option { unsafe { let pid: i32 = c::getppid(); Pid::from_raw(pid) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn getpgid(pid: Option) -> io::Result { unsafe { let pgid = ret_pid_t(c::getpgid(Pid::as_raw(pid) as _))?; Ok(Pid::from_raw_unchecked(pgid)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn setpgid(pid: Option, pgid: Option) -> io::Result<()> { unsafe { ret(c::setpgid(Pid::as_raw(pid) as _, Pid::as_raw(pgid) as _)) } } #[cfg(not(target_os = "wasi"))] #[inline] #[must_use] pub(crate) fn getpgrp() -> Pid { unsafe { let pgid = c::getpgrp(); Pid::from_raw_unchecked(pgid) } } #[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))] #[inline] pub(crate) fn sched_getaffinity(pid: Option, cpuset: &mut RawCpuSet) -> io::Result<()> { unsafe { ret(c::sched_getaffinity( Pid::as_raw(pid) as _, core::mem::size_of::(), cpuset, )) } } #[cfg(any(linux_kernel, target_os = "dragonfly", target_os = "fuchsia"))] #[inline] pub(crate) fn sched_setaffinity(pid: Option, cpuset: &RawCpuSet) -> io::Result<()> { unsafe { ret(c::sched_setaffinity( Pid::as_raw(pid) as _, core::mem::size_of::(), cpuset, )) } } #[inline] pub(crate) fn sched_yield() { unsafe { let _ = c::sched_yield(); } } #[cfg(not(target_os = "wasi"))] #[cfg(feature = "fs")] #[inline] pub(crate) fn umask(mask: Mode) -> Mode { unsafe { Mode::from_bits_retain(c::umask(mask.bits() as c::mode_t).into()) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn nice(inc: i32) -> io::Result { libc_errno::set_errno(libc_errno::Errno(0)); let r = unsafe { c::nice(inc) }; if libc_errno::errno().0 != 0 { ret_c_int(r) } else { Ok(r) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn getpriority_user(uid: Uid) -> io::Result { libc_errno::set_errno(libc_errno::Errno(0)); let r = unsafe { c::getpriority(c::PRIO_USER, uid.as_raw() as _) }; if libc_errno::errno().0 != 0 { ret_c_int(r) } else { Ok(r) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn getpriority_pgrp(pgid: Option) -> io::Result { libc_errno::set_errno(libc_errno::Errno(0)); let r = unsafe { c::getpriority(c::PRIO_PGRP, Pid::as_raw(pgid) as _) }; if libc_errno::errno().0 != 0 { ret_c_int(r) } else { Ok(r) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn getpriority_process(pid: Option) -> io::Result { libc_errno::set_errno(libc_errno::Errno(0)); let r = unsafe { c::getpriority(c::PRIO_PROCESS, Pid::as_raw(pid) as _) }; if libc_errno::errno().0 != 0 { ret_c_int(r) } else { Ok(r) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn setpriority_user(uid: Uid, priority: i32) -> io::Result<()> { unsafe { ret(c::setpriority(c::PRIO_USER, uid.as_raw() as _, priority)) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn setpriority_pgrp(pgid: Option, priority: i32) -> io::Result<()> { unsafe { ret(c::setpriority( c::PRIO_PGRP, Pid::as_raw(pgid) as _, priority, )) } } #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] #[inline] pub(crate) fn setpriority_process(pid: Option, priority: i32) -> io::Result<()> { unsafe { ret(c::setpriority( c::PRIO_PROCESS, Pid::as_raw(pid) as _, priority, )) } } #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] #[inline] pub(crate) fn getrlimit(limit: Resource) -> Rlimit { let mut result = MaybeUninit::::uninit(); unsafe { ret_infallible(c::getrlimit(limit as _, result.as_mut_ptr())); rlimit_from_libc(result.assume_init()) } } #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] #[inline] pub(crate) fn setrlimit(limit: Resource, new: Rlimit) -> io::Result<()> { let lim = rlimit_to_libc(new)?; unsafe { ret(c::setrlimit(limit as _, &lim)) } } #[cfg(linux_kernel)] #[inline] pub(crate) fn prlimit(pid: Option, limit: Resource, new: Rlimit) -> io::Result { let lim = rlimit_to_libc(new)?; let mut result = MaybeUninit::::uninit(); unsafe { ret(c::prlimit( Pid::as_raw(pid), limit as _, &lim, result.as_mut_ptr(), ))?; Ok(rlimit_from_libc(result.assume_init())) } } /// Convert a Rust [`Rlimit`] to a C `c::rlimit`. #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] fn rlimit_from_libc(lim: c::rlimit) -> Rlimit { let current = if lim.rlim_cur == c::RLIM_INFINITY { None } else { Some(lim.rlim_cur.try_into().unwrap()) }; let maximum = if lim.rlim_max == c::RLIM_INFINITY { None } else { Some(lim.rlim_max.try_into().unwrap()) }; Rlimit { current, maximum } } /// Convert a C `c::rlimit` to a Rust `Rlimit`. #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] fn rlimit_to_libc(lim: Rlimit) -> io::Result { let Rlimit { current, maximum } = lim; let rlim_cur = match current { Some(r) => r.try_into().map_err(|_e| io::Errno::INVAL)?, None => c::RLIM_INFINITY as _, }; let rlim_max = match maximum { Some(r) => r.try_into().map_err(|_e| io::Errno::INVAL)?, None => c::RLIM_INFINITY as _, }; Ok(c::rlimit { rlim_cur, rlim_max }) } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn wait(waitopts: WaitOptions) -> io::Result> { _waitpid(!0, waitopts) } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn waitpid( pid: Option, waitopts: WaitOptions, ) -> io::Result> { _waitpid(Pid::as_raw(pid), waitopts) } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn _waitpid( pid: RawPid, waitopts: WaitOptions, ) -> io::Result> { unsafe { let mut status: c::c_int = 0; let pid = ret_c_int(c::waitpid(pid as _, &mut status, waitopts.bits() as _))?; Ok(Pid::from_raw(pid).map(|pid| (pid, WaitStatus::new(status as _)))) } } #[cfg(not(any(target_os = "wasi", target_os = "redox", target_os = "openbsd")))] #[inline] pub(crate) fn waitid(id: WaitId<'_>, options: WaitidOptions) -> io::Result> { // Get the id to wait on. match id { WaitId::All => _waitid_all(options), WaitId::Pid(pid) => _waitid_pid(pid, options), #[cfg(target_os = "linux")] WaitId::PidFd(fd) => _waitid_pidfd(fd, options), #[cfg(not(target_os = "linux"))] WaitId::__EatLifetime(_) => unreachable!(), } } #[cfg(not(any(target_os = "wasi", target_os = "redox", target_os = "openbsd")))] #[inline] fn _waitid_all(options: WaitidOptions) -> io::Result> { // `waitid` can return successfully without initializing the struct (no // children found when using `WNOHANG`) let mut status = MaybeUninit::::zeroed(); unsafe { ret(c::waitid( c::P_ALL, 0, status.as_mut_ptr(), options.bits() as _, ))? }; Ok(unsafe { cvt_waitid_status(status) }) } #[cfg(not(any(target_os = "wasi", target_os = "redox", target_os = "openbsd")))] #[inline] fn _waitid_pid(pid: Pid, options: WaitidOptions) -> io::Result> { // `waitid` can return successfully without initializing the struct (no // children found when using `WNOHANG`) let mut status = MaybeUninit::::zeroed(); unsafe { ret(c::waitid( c::P_PID, Pid::as_raw(Some(pid)) as _, status.as_mut_ptr(), options.bits() as _, ))? }; Ok(unsafe { cvt_waitid_status(status) }) } #[cfg(target_os = "linux")] #[inline] fn _waitid_pidfd(fd: BorrowedFd<'_>, options: WaitidOptions) -> io::Result> { // `waitid` can return successfully without initializing the struct (no // children found when using `WNOHANG`) let mut status = MaybeUninit::::zeroed(); unsafe { ret(c::waitid( c::P_PIDFD, fd.as_raw_fd() as _, status.as_mut_ptr(), options.bits() as _, ))? }; Ok(unsafe { cvt_waitid_status(status) }) } /// Convert a `siginfo_t` to a `WaitidStatus`. /// /// # Safety /// /// The caller must ensure that `status` is initialized and that `waitid` /// returned successfully. #[cfg(not(any(target_os = "wasi", target_os = "redox", target_os = "openbsd")))] #[inline] unsafe fn cvt_waitid_status(status: MaybeUninit) -> Option { let status = status.assume_init(); // `si_pid` is supposedly the better way to check that the struct has been // filled, e.g. the Linux manpage says about the `WNOHANG` case “zero out // the si_pid field before the call and check for a nonzero value”. // But e.g. NetBSD/OpenBSD don't have it exposed in the libc crate for now, // and some platforms don't have it at all. For simplicity, always check // `si_signo`. We have zero-initialized the whole struct, and all kernels // should set `SIGCHLD` here. if status.si_signo == 0 { None } else { Some(WaitidStatus(status)) } } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] #[inline] pub(crate) fn getsid(pid: Option) -> io::Result { unsafe { let pid = ret_pid_t(c::getsid(Pid::as_raw(pid) as _))?; Ok(Pid::from_raw_unchecked(pid)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn setsid() -> io::Result { unsafe { let pid = ret_c_int(c::setsid())?; Ok(Pid::from_raw_unchecked(pid)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn kill_process(pid: Pid, sig: Signal) -> io::Result<()> { unsafe { ret(c::kill(pid.as_raw_nonzero().get(), sig as i32)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> { unsafe { ret(c::kill( pid.as_raw_nonzero().get().wrapping_neg(), sig as i32, )) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn kill_current_process_group(sig: Signal) -> io::Result<()> { unsafe { ret(c::kill(0, sig as i32)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn test_kill_process(pid: Pid) -> io::Result<()> { unsafe { ret(c::kill(pid.as_raw_nonzero().get(), 0)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn test_kill_process_group(pid: Pid) -> io::Result<()> { unsafe { ret(c::kill(pid.as_raw_nonzero().get().wrapping_neg(), 0)) } } #[cfg(not(target_os = "wasi"))] #[inline] pub(crate) fn test_kill_current_process_group() -> io::Result<()> { unsafe { ret(c::kill(0, 0)) } } #[cfg(freebsdlike)] #[inline] pub(crate) unsafe fn procctl( idtype: c::idtype_t, id: c::id_t, option: c::c_int, data: *mut c::c_void, ) -> io::Result<()> { ret(c::procctl(idtype, id, option, data)) } #[cfg(target_os = "linux")] pub(crate) fn pidfd_open(pid: Pid, flags: PidfdFlags) -> io::Result { syscall! { fn pidfd_open( pid: c::pid_t, flags: c::c_uint ) via SYS_pidfd_open -> c::c_int } unsafe { ret_owned_fd(pidfd_open( pid.as_raw_nonzero().get(), bitflags_bits!(flags), )) } } #[cfg(target_os = "linux")] pub(crate) fn pidfd_getfd( pidfd: BorrowedFd<'_>, targetfd: RawFd, flags: PidfdGetfdFlags, ) -> io::Result { syscall! { fn pidfd_getfd( pidfd: c::c_int, targetfd: c::c_int, flags: c::c_uint ) via SYS_pidfd_getfd -> c::c_int } unsafe { ret_owned_fd(pidfd_getfd( borrowed_fd(pidfd), targetfd, bitflags_bits!(flags), )) } } #[cfg(not(target_os = "wasi"))] pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result { let len = buf.len().try_into().map_err(|_| io::Errno::NOMEM)?; unsafe { ret_usize(c::getgroups(len, buf.as_mut_ptr().cast()) as isize) } } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] #[inline] pub(crate) fn ioctl_tiocsctty(fd: BorrowedFd<'_>) -> io::Result<()> { unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCSCTTY as _, &0_u32)) } }