diff --git a/proxmox-sys/src/linux.rs b/proxmox-sys/src/linux.rs index be27e734..bd65f741 100644 --- a/proxmox-sys/src/linux.rs +++ b/proxmox-sys/src/linux.rs @@ -4,6 +4,7 @@ use failure::*; use proxmox_tools as tools; pub mod magic; +pub mod pid; pub mod procfs; /// Get pseudo random data (/dev/urandom) diff --git a/proxmox-sys/src/linux/pid.rs b/proxmox-sys/src/linux/pid.rs new file mode 100644 index 00000000..b6edef2a --- /dev/null +++ b/proxmox-sys/src/linux/pid.rs @@ -0,0 +1,175 @@ +//! PID file descriptor handling. + +use std::fs::File; +use std::io; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; + +use nix::fcntl::OFlag; +use nix::sys::signal::Signal; +use nix::sys::signalfd::siginfo; +use nix::sys::stat::Mode; +use nix::unistd::Pid; +use nix::NixPath; + +use crate::error::{io_err_other, SysResult}; +use crate::linux::procfs::{MountInfo, PidStat}; +use crate::{c_result, c_str, c_try}; +use proxmox_tools::fd::Fd; + +/// asm-generic pidfd_open syscall number +#[allow(non_upper_case_globals)] +pub const SYS_pidfd_open: libc::c_long = 434; + +/// asm-generic pidfd_send_signal syscall number +#[allow(non_upper_case_globals)] +pub const SYS_pidfd_send_signal: libc::c_long = 424; + +unsafe fn pidfd_open(pid: libc::pid_t, flags: libc::c_uint) -> libc::c_long { + libc::syscall(SYS_pidfd_open, pid, flags) +} + +unsafe fn pidfd_send_signal( + pidfd: RawFd, + sig: libc::c_int, + info: *mut libc::siginfo_t, + flags: libc::c_uint, +) -> libc::c_long { + libc::syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags) +} + +/// File descriptor refernce to a process. +pub struct PidFd { + fd: Fd, + pid: Pid, +} + +impl PidFd { + /// Get a pidfd to the current process. + pub fn current() -> io::Result { + Self::open(Pid::this()) + } + + /// Open a pidfd for the given process id. + pub fn open(pid: Pid) -> io::Result { + let fd = unsafe { Fd::from_raw_fd(c_try!(pidfd_open(pid.as_raw(), 0)) as RawFd) }; + Ok(Self { fd, pid }) + } + + /// Send a signal to the process. + pub fn send_signal>>( + &self, + sig: S, + info: Option<&mut siginfo>, + ) -> io::Result<()> { + let sig = match sig.into() { + Some(sig) => sig as libc::c_int, + None => 0, + }; + let info = match info { + Some(info) => info as *mut siginfo as *mut libc::siginfo_t, + None => std::ptr::null_mut(), + }; + c_result!(unsafe { pidfd_send_signal(self.fd.as_raw_fd(), sig, info, 0) }).map(drop) + } + + /// Get the original PID number used to open this pidfd. Note that this may not be the correct + /// pid if the PID namespace was changed (which is currently only possible by forking, which is + /// why this is usually safe under normal circumstances.) + #[inline] + pub const fn pid(&self) -> Pid { + self.pid + } + + /// Open a procfs file from. This is equivalent to opening `/proc//` using this + /// process actual pid. This also works if the file descriptor has been sent over + pub fn open_file(&self, path: &P) -> io::Result { + Fd::openat( + self, + path, + OFlag::O_RDONLY | OFlag::O_CLOEXEC, + Mode::empty(), + ) + .map(|fd| unsafe { File::from_raw_fd(fd.into_raw_fd()) }) + .into_io_result() + } + + /// Convenience helper to read a procfs file into memory. + /// + /// This calls `self.open_file()` reads it to the end. + pub fn read_file(&self, path: &P) -> io::Result> { + use io::Read; + + let mut reader = self.open_file(path)?; + let mut out = Vec::new(); + reader.read_to_end(&mut out)?; + Ok(out) + } + + /// Get the `PidStat` structure for this process. (`/proc/PID/stat`) + pub fn get_stat(&self) -> io::Result { + let data = self.read_file(c_str!("stat"))?; + let data = String::from_utf8(data).map_err(io_err_other)?; + PidStat::parse(&data).map_err(io_err_other) + } + + /// Read this process' `/proc/PID/mountinfo` file. + pub fn get_mount_info(&self) -> io::Result { + MountInfo::parse(&self.read_file(c_str!("mountinfo"))?).map_err(io_err_other) + } + + /// Attempt to get a `PidFd` from a raw file descriptor. + /// + /// This will attempt to read the pid number via the file descriptor. + pub fn try_from_raw_fd(fd: RawFd) -> io::Result { + let mut this = Self { + fd: unsafe { Fd::from_raw_fd(fd) }, + pid: Pid::from_raw(1), + }; + // Simple check first: is it a valid pid file descriptor: + if let Err(err) = this.send_signal(None, None) { + if err.kind() == io::ErrorKind::PermissionDenied { + // valid pidfd, but we probably can't do much with it, proceed anyway... + } else { + // make sure we don't try to close the file descriptor: + let _ = this.fd.into_raw_fd(); + // if err.raw_os_error() == Some(libc::EBADF) + // => not a valid pid fd, pass the error through + // if err.raw_os_error() == Some(libc::ENOSYS) + // => kernel too old, things most likely won't work anyway + return Err(err); + } + } + + match this.get_stat() { + Ok(stat) => { + this.pid = stat.pid; + Ok(this) + } + Err(err) => { + // make sure we don't close the raw file descriptor: + let _ = this.fd.into_raw_fd(); + Err(err) + } + } + } +} + +impl AsRawFd for PidFd { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for PidFd { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + +impl FromRawFd for PidFd { + /// Panics if the file descriptor is not an actual pid fd (or at least a reference to its proc + /// directory). + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self::try_from_raw_fd(fd).unwrap() + } +}