pub mod fd;
pub mod net;
pub mod numa;
pub mod pidfd;
pub mod signal;
use std::collections::BTreeSet;
use std::ffi::OsString;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use crate::model;
use crate::model::auxv::AuxvType;
use crate::source::ProcSource;
pub struct ProcHandle {
source: Box<dyn ProcSource>,
is_core: bool,
}
impl ProcHandle {
pub fn is_core(&self) -> bool {
self.is_core
}
pub fn trace_thread(
&self,
tid: u32,
options: &crate::stack::TraceOptions,
) -> Vec<crate::stack::Frame> {
self.source.trace_thread(tid, options)
}
pub fn read_auxv_string(&self, typ: AuxvType) -> io::Result<OsString> {
self.source.read_auxv_string(typ)
}
pub fn pid(&self) -> u64 {
self.source.pid()
}
pub fn word_size(&self) -> usize {
self.source.word_size()
}
pub fn auxv(&self) -> io::Result<model::auxv::Auxv> {
self.source.read_auxv()
}
pub fn comm(&self) -> io::Result<OsString> {
self.source.read_comm()
}
pub fn exe(&self) -> io::Result<PathBuf> {
self.source.read_exe()
}
pub fn tids(&self) -> io::Result<Vec<u64>> {
self.source.list_tids()
}
fn fds(&self) -> io::Result<Vec<u64>> {
self.source.list_fds()
}
fn fd_path(&self, fd: u64) -> io::Result<PathBuf> {
self.source.read_fd_link(fd)
}
fn fdinfo(&self, fd: u64) -> io::Result<model::fdinfo::FdInfo> {
self.source.read_fdinfo(fd)
}
pub fn state(&self) -> io::Result<model::stat::ProcState> {
self.source
.read_stat()?
.state
.ok_or_else(|| unsupported("stat", "state"))
}
pub fn utime(&self) -> io::Result<u64> {
self.source
.read_stat()?
.utime
.ok_or_else(|| unsupported("stat", "utime"))
}
pub fn stime(&self) -> io::Result<u64> {
self.source
.read_stat()?
.stime
.ok_or_else(|| unsupported("stat", "stime"))
}
pub fn utime_us(&self) -> io::Result<u64> {
self.source.read_utime_us()
}
pub fn stime_us(&self) -> io::Result<u64> {
self.source.read_stime_us()
}
pub fn cutime_us(&self) -> io::Result<u64> {
self.source.read_cutime_us()
}
pub fn cstime_us(&self) -> io::Result<u64> {
self.source.read_cstime_us()
}
pub fn pgrp(&self) -> io::Result<u64> {
self.source
.read_stat()?
.pgrp
.ok_or_else(|| unsupported("stat", "pgrp"))
}
pub fn sid(&self) -> io::Result<u64> {
self.source
.read_stat()?
.sid
.ok_or_else(|| unsupported("stat", "sid"))
}
pub fn nice(&self) -> io::Result<i32> {
self.source
.read_stat()?
.nice
.ok_or_else(|| unsupported("stat", "nice"))
}
pub fn starttime(&self) -> io::Result<u64> {
self.source
.read_stat()?
.starttime
.ok_or_else(|| unsupported("stat", "starttime"))
}
pub fn ppid(&self) -> io::Result<u64> {
self.source
.read_status()?
.ppid
.ok_or_else(|| unsupported("status", "PPid"))
}
pub fn ruid(&self) -> io::Result<u32> {
self.source
.read_status()?
.ruid
.ok_or_else(|| unsupported("status", "Uid"))
}
pub fn euid(&self) -> io::Result<u32> {
self.source
.read_status()?
.euid
.ok_or_else(|| unsupported("status", "Uid"))
}
pub fn suid(&self) -> io::Result<u32> {
self.source
.read_status()?
.suid
.ok_or_else(|| unsupported("status", "Uid"))
}
pub fn fsuid(&self) -> io::Result<u32> {
self.source
.read_status()?
.fsuid
.ok_or_else(|| unsupported("status", "Uid"))
}
pub fn rgid(&self) -> io::Result<u32> {
self.source
.read_status()?
.rgid
.ok_or_else(|| unsupported("status", "Gid"))
}
pub fn egid(&self) -> io::Result<u32> {
self.source
.read_status()?
.egid
.ok_or_else(|| unsupported("status", "Gid"))
}
pub fn sgid(&self) -> io::Result<u32> {
self.source
.read_status()?
.sgid
.ok_or_else(|| unsupported("status", "Gid"))
}
pub fn fsgid(&self) -> io::Result<u32> {
self.source
.read_status()?
.fsgid
.ok_or_else(|| unsupported("status", "Gid"))
}
pub fn groups(&self) -> io::Result<Vec<u32>> {
self.source
.read_status()?
.groups
.ok_or_else(|| unsupported("status", "Groups"))
}
pub fn umask(&self) -> io::Result<u32> {
self.source
.read_status()?
.umask
.ok_or_else(|| unsupported("status", "Umask"))
}
pub fn thread_count(&self) -> io::Result<usize> {
self.source
.read_status()?
.threads
.ok_or_else(|| unsupported("status", "Threads"))
}
pub fn thread_cpu(&self, tid: u64) -> io::Result<u32> {
self.source
.read_tid_stat(tid)?
.processor
.ok_or_else(|| unsupported("task/stat", "processor"))
}
pub fn thread_affinity(&self, tid: u64) -> io::Result<BTreeSet<usize>> {
self.source
.read_tid_status(tid)?
.cpus_allowed
.ok_or_else(|| unsupported("task/status", "Cpus_allowed_list"))
}
pub fn read_cmdline(&self) -> io::Result<(Vec<OsString>, bool)> {
if let Ok(args) = self.source.read_initial_args() {
return Ok((args, false));
}
if let Ok((args, lossy)) = self.source.read_current_args() {
return Ok((args, lossy));
}
self.source.read_fallback_args().map(|args| (args, true))
}
pub fn read_environ(&self) -> io::Result<Vec<OsString>> {
if let Ok(env) = self.source.read_current_environ() {
return Ok(env);
}
match self.source.read_initial_environ() {
Ok(env) => return Ok(env),
Err(e) if !self.is_core => eprintln!("process_vm_readv: {e}"),
Err(_) => {}
}
if !self.is_core {
eprintln!(
"warning: showing environment at time of \
process launch, which may not reflect runtime changes",
);
}
self.source.read_fallback_environ()
}
pub fn run_time_ns(&self) -> io::Result<u64> {
Ok(self.source.read_schedstat()?.run_time_ns)
}
pub fn wait_time_ns(&self) -> io::Result<u64> {
Ok(self.source.read_schedstat()?.wait_time_ns)
}
pub fn nofile_limit(&self) -> io::Result<model::limits::Limit> {
self.source
.read_limits()?
.get(nix::sys::resource::Resource::RLIMIT_NOFILE)
.cloned()
.ok_or_else(|| unsupported("limits", "RLIMIT_NOFILE"))
}
pub fn resource_limits(&self) -> io::Result<model::limits::Limits> {
self.source.read_limits()
}
}
impl ProcHandle {
pub fn from_pid(pid: u64) -> Self {
ProcHandle {
source: crate::source::open_live(pid),
is_core: false,
}
}
}
pub fn parse_proc_path(s: &str) -> Option<u64> {
let suffix = s.strip_prefix("/proc/")?;
suffix.parse::<u64>().ok().filter(|&pid| pid >= 1)
}
pub enum PidArg {
Pid(u64),
Skip,
}
pub fn parse_pid_arg(s: &str) -> Result<PidArg, String> {
if let Ok(pid) = s.parse::<u64>() {
if pid < 1 || pid > i32::MAX as u64 {
return Err(format!("invalid PID '{s}'"));
}
return Ok(PidArg::Pid(pid));
}
if let Some(pid) = parse_proc_path(s) {
if pid > i32::MAX as u64 {
return Err(format!("invalid PID '{s}'"));
}
return Ok(PidArg::Pid(pid));
}
if s.starts_with("/proc/") {
return Ok(PidArg::Skip);
}
Err(format!("invalid PID '{s}'"))
}
pub fn is_non_pid_proc_path(s: &str) -> bool {
s.strip_prefix("/proc/")
.is_some_and(|suffix| !suffix.is_empty() && suffix.parse::<u64>().is_err())
}
struct PidSpec {
pid: u64,
tid: Option<u64>,
}
fn parse_pid_spec(s: &str) -> io::Result<PidSpec> {
if let Some(pid) = parse_proc_path(s) {
return Ok(PidSpec { pid, tid: None });
}
if let Some((pid_str, tid_str)) = s.split_once('/') {
let pid = pid_str
.parse::<u64>()
.map_err(|e| io::Error::other(format!("Error parsing PID '{pid_str}': {e}")))?;
let tid = tid_str
.parse::<u64>()
.map_err(|e| io::Error::other(format!("Error parsing thread ID '{tid_str}': {e}")))?;
if pid == 0 {
return Err(io::Error::other("PID must be >= 1".to_string()));
}
Ok(PidSpec {
pid,
tid: Some(tid),
})
} else {
let pid = s
.parse::<u64>()
.map_err(|e| io::Error::other(format!("Error parsing PID '{s}': {e}")))?;
if pid == 0 {
return Err(io::Error::other("PID must be >= 1".to_string()));
}
Ok(PidSpec { pid, tid: None })
}
}
fn grab_core(path: &Path) -> io::Result<ProcHandle> {
let source = crate::source::open_coredump(path)?;
Ok(ProcHandle {
source,
is_core: true,
})
}
fn unsupported(file: &str, field: &str) -> io::Error {
io::Error::new(
io::ErrorKind::Unsupported,
format!("{field} not available from /proc/[pid]/{file}"),
)
}
pub fn resolve_operand(arg: &str) -> io::Result<ProcHandle> {
match arg.parse::<u64>() {
Ok(pid) => {
if pid == 0 {
return Err(io::Error::other("PID must be >= 1".to_string()));
}
Ok(ProcHandle::from_pid(pid))
}
Err(_) => {
if let Some(pid) = parse_proc_path(arg) {
return Ok(ProcHandle::from_pid(pid));
}
let path = PathBuf::from(arg);
grab_core(&path)
}
}
}
pub fn resolve_operand_with_tid(arg: &str) -> io::Result<(ProcHandle, Option<u64>)> {
match parse_pid_spec(arg) {
Ok(spec) => Ok((ProcHandle::from_pid(spec.pid), spec.tid)),
Err(e) => {
if arg.bytes().all(|b| b.is_ascii_digit()) {
return Err(e);
}
let path = PathBuf::from(arg);
grab_core(&path).map(|h| (h, None))
}
}
}