use super::*;
use crate::from_iter;
use libc::rlim_t;
use std::ffi::OsString;
use std::io::{self, Read};
#[cfg(unix)]
use std::os::linux::fs::MetadataExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
#[cfg(windows)]
trait FakeMedatadataExt {
fn st_uid(&self) -> u32;
}
#[cfg(windows)]
impl FakeMedatadataExt for std::fs::Metadata {
fn st_uid(&self) -> u32 {
panic!()
}
}
bitflags! {
pub struct StatFlags: u32 {
const PF_IDLE = 0x0000_0002;
const PF_EXITING = 0x0000_0004;
const PF_EXITPIDONE = 0x0000_0008;
const PF_VCPU = 0x0000_0010;
const PF_WQ_WORKER = 0x0000_0020;
const PF_FORKNOEXEC = 0x0000_0040;
const PF_MCE_PROCESS = 0x0000_0080;
const PF_SUPERPRIV = 0x0000_0100;
const PF_DUMPCORE = 0x0000_0200;
const PF_SIGNALED = 0x0000_0400;
const PF_MEMALLOC = 0x0000_0800;
const PF_NPROC_EXCEEDED = 0x0000_1000;
const PF_USED_MATH = 0x0000_2000;
const PF_USED_ASYNC = 0x0000_4000;
const PF_NOFREEZE = 0x0000_8000;
const PF_FROZEN = 0x0001_0000;
const PF_KSWAPD = 0x0002_0000;
const PF_MEMALLOC_NOFS = 0x0004_0000;
const PF_MEMALLOC_NOIO = 0x0008_0000;
const PF_LESS_THROTTLE = 0x0010_0000;
const PF_KTHREAD = 0x0020_0000;
const PF_RANDOMIZE = 0x0040_0000;
const PF_SWAPWRITE = 0x0080_0000;
const PF_MEMSTALL = 0x0100_0000;
const PF_UMH = 0x0200_0000;
const PF_NO_SETAFFINITY = 0x0400_0000;
const PF_MCE_EARLY = 0x0800_0000;
const PF_MEMALLOC_NOCMA = 0x1000_0000;
const PF_MUTEX_TESTER = 0x2000_0000;
const PF_FREEZER_SKIP = 0x4000_0000;
const PF_SUSPEND_TASK = 0x8000_0000;
}
}
bitflags! {
pub struct CoredumpFlags: u32 {
const ANONYMOUS_PRIVATE_MAPPINGS = 0x01;
const ANONYMOUS_SHARED_MAPPINGS = 0x02;
const FILEBACKED_PRIVATE_MAPPINGS = 0x04;
const FILEBACKED_SHARED_MAPPINGS = 0x08;
const ELF_HEADERS = 0x10;
const PROVATE_HUGEPAGES = 0x20;
const SHARED_HUGEPAGES = 0x40;
const PRIVATE_DAX_PAGES = 0x80;
const SHARED_DAX_PAGES = 0x100;
}
}
bitflags! {
pub struct NFSServerCaps: u32 {
const NFS_CAP_READDIRPLUS = 1;
const NFS_CAP_HARDLINKS = (1 << 1);
const NFS_CAP_SYMLINKS = (1 << 2);
const NFS_CAP_ACLS = (1 << 3);
const NFS_CAP_ATOMIC_OPEN = (1 << 4);
const NFS_CAP_LGOPEN = (1 << 5);
const NFS_CAP_FILEID = (1 << 6);
const NFS_CAP_MODE = (1 << 7);
const NFS_CAP_NLINK = (1 << 8);
const NFS_CAP_OWNER = (1 << 9);
const NFS_CAP_OWNER_GROUP = (1 << 10);
const NFS_CAP_ATIME = (1 << 11);
const NFS_CAP_CTIME = (1 << 12);
const NFS_CAP_MTIME = (1 << 13);
const NFS_CAP_POSIX_LOCK = (1 << 14);
const NFS_CAP_UIDGID_NOMAP = (1 << 15);
const NFS_CAP_STATEID_NFSV41 = (1 << 16);
const NFS_CAP_ATOMIC_OPEN_V1 = (1 << 17);
const NFS_CAP_SECURITY_LABEL = (1 << 18);
const NFS_CAP_SEEK = (1 << 19);
const NFS_CAP_ALLOCATE = (1 << 20);
const NFS_CAP_DEALLOCATE = (1 << 21);
const NFS_CAP_LAYOUTSTATS = (1 << 22);
const NFS_CAP_CLONE = (1 << 23);
const NFS_CAP_COPY = (1 << 24);
const NFS_CAP_OFFLOAD_CANCEL = (1 << 25);
}
}
bitflags! {
pub struct FDPermissions: u32 {
const READ = libc::S_IRUSR;
const WRITE = libc::S_IWUSR;
const EXECUTE = libc::S_IXUSR;
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ProcState {
Running,
Sleeping,
Waiting,
Zombie,
Stopped,
Tracing,
Dead,
Wakekill,
Waking,
Parked,
Idle,
}
impl ProcState {
pub fn from_char(c: char) -> Option<ProcState> {
match c {
'R' => Some(ProcState::Running),
'S' => Some(ProcState::Sleeping),
'D' => Some(ProcState::Waiting),
'Z' => Some(ProcState::Zombie),
'T' => Some(ProcState::Stopped),
't' => Some(ProcState::Tracing),
'X' | 'x' => Some(ProcState::Dead),
'K' => Some(ProcState::Wakekill),
'W' => Some(ProcState::Waking),
'P' => Some(ProcState::Parked),
'I' => Some(ProcState::Idle),
_ => None,
}
}
}
impl FromStr for ProcState {
type Err = ProcError;
fn from_str(s: &str) -> Result<ProcState, ProcError> {
ProcState::from_char(expect!(s.chars().next(), "empty string"))
.ok_or_else(|| build_internal_error!("failed to convert"))
}
}
#[derive(Debug, Clone)]
pub struct Stat {
pub pid: i32,
pub comm: String,
pub state: char,
pub ppid: i32,
pub pgrp: i32,
pub session: i32,
pub tty_nr: i32,
pub tpgid: i32,
pub flags: u32,
pub minflt: u64,
pub cminflt: u64,
pub majflt: u64,
pub cmajflt: u64,
pub utime: u64,
pub stime: u64,
pub cutime: i64,
pub cstime: i64,
pub priority: i64,
pub nice: i64,
pub num_threads: i64,
pub itrealvalue: i64,
#[cfg_attr(
feature = "chrono",
doc = "See also the [Stat::starttime()] method to get the starttime as a `DateTime` object"
)]
#[cfg_attr(
not(feature = "chrono"),
doc = "If you compile with the optional `chrono` feature, you can use the `starttime()` method to get the starttime as a `DateTime` object"
)]
pub starttime: u64,
pub vsize: u64,
pub rss: i64,
pub rsslim: u64,
pub startcode: u64,
pub endcode: u64,
pub startstack: u64,
pub kstkesp: u64,
pub kstkeip: u64,
pub signal: u64,
pub blocked: u64,
pub sigignore: u64,
pub sigcatch: u64,
pub wchan: u64,
pub nswap: u64,
pub cnswap: u64,
pub exit_signal: Option<i32>,
pub processor: Option<i32>,
pub rt_priority: Option<u32>,
pub policy: Option<u32>,
pub delayacct_blkio_ticks: Option<u64>,
pub guest_time: Option<u64>,
pub cguest_time: Option<i64>,
pub start_data: Option<usize>,
pub end_data: Option<usize>,
pub start_brk: Option<usize>,
pub arg_start: Option<usize>,
pub arg_end: Option<usize>,
pub env_start: Option<usize>,
pub env_end: Option<usize>,
pub exit_code: Option<i32>,
}
#[derive(Debug, Copy, Clone)]
pub struct Io {
pub rchar: u64,
pub wchar: u64,
pub syscr: u64,
pub syscw: u64,
pub read_bytes: u64,
pub write_bytes: u64,
pub cancelled_write_bytes: u64,
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MountStat {
pub device: Option<String>,
pub mount_point: PathBuf,
pub fs: String,
pub statistics: Option<MountNFSStatistics>,
}
impl MountStat {
pub fn from_reader<R: io::Read>(r: R) -> ProcResult<Vec<MountStat>> {
use std::io::{BufRead, BufReader};
let mut v = Vec::new();
let bufread = BufReader::new(r);
let mut lines = bufread.lines();
while let Some(Ok(line)) = lines.next() {
if line.starts_with("device ") {
let mut s = line.split_whitespace();
let device = Some(expect!(s.nth(1)).to_owned());
let mount_point = PathBuf::from(expect!(s.nth(2)));
let fs = expect!(s.nth(2)).to_owned();
let statistics = match s.next() {
Some(stats) if stats.starts_with("statvers=") => {
Some(MountNFSStatistics::from_lines(&mut lines, &stats[9..])?)
}
_ => None,
};
v.push(MountStat {
device,
mount_point,
fs,
statistics,
});
}
}
Ok(v)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct MountNFSStatistics {
pub version: String,
pub opts: Vec<String>,
pub age: Duration,
pub caps: Vec<String>,
pub sec: Vec<String>,
pub events: NFSEventCounter,
pub bytes: NFSByteCounter,
pub per_op_stats: NFSPerOpStats,
}
impl MountNFSStatistics {
fn from_lines<B: io::BufRead>(
r: &mut io::Lines<B>,
statsver: &str,
) -> ProcResult<MountNFSStatistics> {
let mut parsing_per_op = false;
let mut opts: Option<Vec<String>> = None;
let mut age = None;
let mut caps = None;
let mut sec = None;
let mut bytes = None;
let mut events = None;
let mut per_op = HashMap::new();
while let Some(Ok(line)) = r.next() {
let line = line.trim();
if line.trim() == "" {
break;
}
if !parsing_per_op {
if line.starts_with("opts:") {
opts = Some(line[5..].trim().split(',').map(|s| s.to_string()).collect());
} else if line.starts_with("age:") {
age = Some(Duration::from_secs(from_str!(u64, &line[4..].trim())));
} else if line.starts_with("caps:") {
caps = Some(line[5..].trim().split(',').map(|s| s.to_string()).collect());
} else if line.starts_with("sec:") {
sec = Some(line[4..].trim().split(',').map(|s| s.to_string()).collect());
} else if line.starts_with("bytes:") {
bytes = Some(NFSByteCounter::from_str(&line[6..].trim())?);
} else if line.starts_with("events:") {
events = Some(NFSEventCounter::from_str(&line[7..].trim())?);
}
if line == "per-op statistics" {
parsing_per_op = true;
}
} else {
let mut split = line.split(':');
let name = expect!(split.next()).to_string();
let stats = NFSOperationStat::from_str(expect!(split.next()))?;
per_op.insert(name, stats);
}
}
Ok(MountNFSStatistics {
version: statsver.to_string(),
opts: expect!(opts, "Failed to find opts field in nfs stats"),
age: expect!(age, "Failed to find age field in nfs stats"),
caps: expect!(caps, "Failed to find caps field in nfs stats"),
sec: expect!(sec, "Failed to find sec field in nfs stats"),
events: expect!(events, "Failed to find events section in nfs stats"),
bytes: expect!(bytes, "Failed to find bytes section in nfs stats"),
per_op_stats: per_op,
})
}
pub fn server_caps(&self) -> ProcResult<Option<NFSServerCaps>> {
for data in &self.caps {
if data.starts_with("caps=0x") {
let val = from_str!(u32, &data[7..], 16);
return Ok(NFSServerCaps::from_bits(val));
}
}
Ok(None)
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NFSEventCounter {
inode_revalidate: libc::c_ulong,
deny_try_revalidate: libc::c_ulong,
data_invalidate: libc::c_ulong,
attr_invalidate: libc::c_ulong,
vfs_open: libc::c_ulong,
vfs_lookup: libc::c_ulong,
vfs_access: libc::c_ulong,
vfs_update_page: libc::c_ulong,
vfs_read_page: libc::c_ulong,
vfs_read_pages: libc::c_ulong,
vfs_write_page: libc::c_ulong,
vfs_write_pages: libc::c_ulong,
vfs_get_dents: libc::c_ulong,
vfs_set_attr: libc::c_ulong,
vfs_flush: libc::c_ulong,
vfs_fs_sync: libc::c_ulong,
vfs_lock: libc::c_ulong,
vfs_release: libc::c_ulong,
congestion_wait: libc::c_ulong,
set_attr_trunc: libc::c_ulong,
extend_write: libc::c_ulong,
silly_rename: libc::c_ulong,
short_read: libc::c_ulong,
short_write: libc::c_ulong,
delay: libc::c_ulong,
pnfs_read: libc::c_ulong,
pnfs_write: libc::c_ulong,
}
impl NFSEventCounter {
fn from_str(s: &str) -> ProcResult<NFSEventCounter> {
use libc::c_ulong;
let mut s = s.split_whitespace();
Ok(NFSEventCounter {
inode_revalidate: from_str!(c_ulong, expect!(s.next())),
deny_try_revalidate: from_str!(c_ulong, expect!(s.next())),
data_invalidate: from_str!(c_ulong, expect!(s.next())),
attr_invalidate: from_str!(c_ulong, expect!(s.next())),
vfs_open: from_str!(c_ulong, expect!(s.next())),
vfs_lookup: from_str!(c_ulong, expect!(s.next())),
vfs_access: from_str!(c_ulong, expect!(s.next())),
vfs_update_page: from_str!(c_ulong, expect!(s.next())),
vfs_read_page: from_str!(c_ulong, expect!(s.next())),
vfs_read_pages: from_str!(c_ulong, expect!(s.next())),
vfs_write_page: from_str!(c_ulong, expect!(s.next())),
vfs_write_pages: from_str!(c_ulong, expect!(s.next())),
vfs_get_dents: from_str!(c_ulong, expect!(s.next())),
vfs_set_attr: from_str!(c_ulong, expect!(s.next())),
vfs_flush: from_str!(c_ulong, expect!(s.next())),
vfs_fs_sync: from_str!(c_ulong, expect!(s.next())),
vfs_lock: from_str!(c_ulong, expect!(s.next())),
vfs_release: from_str!(c_ulong, expect!(s.next())),
congestion_wait: from_str!(c_ulong, expect!(s.next())),
set_attr_trunc: from_str!(c_ulong, expect!(s.next())),
extend_write: from_str!(c_ulong, expect!(s.next())),
silly_rename: from_str!(c_ulong, expect!(s.next())),
short_read: from_str!(c_ulong, expect!(s.next())),
short_write: from_str!(c_ulong, expect!(s.next())),
delay: from_str!(c_ulong, expect!(s.next())),
pnfs_read: from_str!(c_ulong, expect!(s.next())),
pnfs_write: from_str!(c_ulong, expect!(s.next())),
})
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NFSByteCounter {
pub normal_read: libc::c_ulonglong,
pub normal_write: libc::c_ulonglong,
pub direct_read: libc::c_ulonglong,
pub direct_write: libc::c_ulonglong,
pub server_read: libc::c_ulonglong,
pub server_write: libc::c_ulonglong,
pub pages_read: libc::c_ulonglong,
pub pages_write: libc::c_ulonglong,
}
impl NFSByteCounter {
fn from_str(s: &str) -> ProcResult<NFSByteCounter> {
use libc::c_ulonglong;
let mut s = s.split_whitespace();
Ok(NFSByteCounter {
normal_read: from_str!(c_ulonglong, expect!(s.next())),
normal_write: from_str!(c_ulonglong, expect!(s.next())),
direct_read: from_str!(c_ulonglong, expect!(s.next())),
direct_write: from_str!(c_ulonglong, expect!(s.next())),
server_read: from_str!(c_ulonglong, expect!(s.next())),
server_write: from_str!(c_ulonglong, expect!(s.next())),
pages_read: from_str!(c_ulonglong, expect!(s.next())),
pages_write: from_str!(c_ulonglong, expect!(s.next())),
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct NFSOperationStat {
pub operations: libc::c_ulong,
pub transmissions: libc::c_ulong,
pub major_timeouts: libc::c_ulong,
pub bytes_sent: libc::c_ulonglong,
pub bytes_recv: libc::c_ulonglong,
pub cum_queue_time: Duration,
pub cum_resp_time: Duration,
pub cum_total_req_time: Duration,
}
impl NFSOperationStat {
fn from_str(s: &str) -> ProcResult<NFSOperationStat> {
use libc::{c_ulong, c_ulonglong};
let mut s = s.split_whitespace();
let operations = from_str!(c_ulong, expect!(s.next()));
let transmissions = from_str!(c_ulong, expect!(s.next()));
let major_timeouts = from_str!(c_ulong, expect!(s.next()));
let bytes_sent = from_str!(c_ulonglong, expect!(s.next()));
let bytes_recv = from_str!(c_ulonglong, expect!(s.next()));
let cum_queue_time_ms = from_str!(u64, expect!(s.next()));
let cum_resp_time_ms = from_str!(u64, expect!(s.next()));
let cum_total_req_time_ms = from_str!(u64, expect!(s.next()));
Ok(NFSOperationStat {
operations,
transmissions,
major_timeouts,
bytes_sent,
bytes_recv,
cum_queue_time: Duration::from_millis(cum_queue_time_ms),
cum_resp_time: Duration::from_millis(cum_resp_time_ms),
cum_total_req_time: Duration::from_millis(cum_total_req_time_ms),
})
}
}
pub type NFSPerOpStats = HashMap<String, NFSOperationStat>;
#[derive(Debug, PartialEq, Clone)]
pub enum MMapPath {
Path(PathBuf),
Heap,
Stack,
TStack(u32),
Vdso,
Vvar,
Vsyscall,
Anonymous,
Other(String),
}
impl MMapPath {
fn from(path: &str) -> ProcResult<MMapPath> {
Ok(match path.trim() {
"" => MMapPath::Anonymous,
"[heap]" => MMapPath::Heap,
"[stack]" => MMapPath::Stack,
"[vdso]" => MMapPath::Vdso,
"[vvar]" => MMapPath::Vvar,
"[vsyscall]" => MMapPath::Vsyscall,
x if x.starts_with("[stack:") => {
let mut s = x[1..x.len() - 1].split(':');
let tid = from_str!(u32, expect!(s.nth(1)));
MMapPath::TStack(tid)
}
x if x.starts_with('[') && x.ends_with(']') => {
MMapPath::Other(x[1..x.len() - 1].to_string())
}
x => MMapPath::Path(PathBuf::from(x)),
})
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct MemoryMap {
pub address: (u64, u64),
pub perms: String,
pub offset: u64,
pub dev: (i32, i32),
pub inode: u64,
pub pathname: MMapPath,
}
impl Io {
pub fn from_reader<R: io::Read>(r: R) -> ProcResult<Io> {
use std::io::{BufRead, BufReader};
let mut map = HashMap::new();
let reader = BufReader::new(r);
for line in reader.lines() {
let line = line?;
if line.is_empty() || !line.contains(' ') {
continue;
}
let mut s = line.split_whitespace();
let field = expect!(s.next());
let value = expect!(s.next());
let value = from_str!(u64, value);
map.insert(field[..field.len() - 1].to_string(), value);
}
let io = Io {
rchar: expect!(map.remove("rchar")),
wchar: expect!(map.remove("wchar")),
syscr: expect!(map.remove("syscr")),
syscw: expect!(map.remove("syscw")),
read_bytes: expect!(map.remove("read_bytes")),
write_bytes: expect!(map.remove("write_bytes")),
cancelled_write_bytes: expect!(map.remove("cancelled_write_bytes")),
};
if cfg!(test) && !map.is_empty() {
panic!("io map is not empty: {:#?}", map);
}
Ok(io)
}
}
#[derive(Clone, Debug)]
pub enum FDTarget {
Path(PathBuf),
Socket(u32),
Net(u32),
Pipe(u32),
AnonInode(String),
MemFD(String),
Other(String, u32),
}
impl FromStr for FDTarget {
type Err = ProcError;
fn from_str(s: &str) -> Result<FDTarget, ProcError> {
if !s.starts_with('/') && s.contains(':') {
let mut s = s.split(':');
let fd_type = expect!(s.next());
match fd_type {
"socket" => {
let inode = expect!(s.next(), "socket inode");
let inode = expect!(u32::from_str_radix(&inode[1..inode.len() - 1], 10));
Ok(FDTarget::Socket(inode))
}
"net" => {
let inode = expect!(s.next(), "net inode");
let inode = expect!(u32::from_str_radix(&inode[1..inode.len() - 1], 10));
Ok(FDTarget::Net(inode))
}
"pipe" => {
let inode = expect!(s.next(), "pipe inode");
let inode = expect!(u32::from_str_radix(&inode[1..inode.len() - 1], 10));
Ok(FDTarget::Pipe(inode))
}
"anon_inode" => Ok(FDTarget::AnonInode(
expect!(s.next(), "anon inode").to_string(),
)),
"/memfd" => Ok(FDTarget::MemFD(expect!(s.next(), "memfd name").to_string())),
x => {
let inode = expect!(s.next(), "other inode");
let inode = expect!(u32::from_str_radix(&inode[1..inode.len() - 1], 10));
Ok(FDTarget::Other(x.to_string(), inode))
}
}
} else {
Ok(FDTarget::Path(PathBuf::from(s)))
}
}
}
#[derive(Clone)]
pub struct FDInfo {
pub fd: u32,
pub mode: u32,
pub target: FDTarget,
}
impl FDInfo {
pub fn mode(&self) -> FDPermissions {
FDPermissions::from_bits_truncate(self.mode)
}
}
impl std::fmt::Debug for FDInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"FDInfo {{ fd: {:?}, mode: 0{:o}, target: {:?} }}",
self.fd, self.mode, self.target
)
}
}
macro_rules! since_kernel {
($a:tt, $b:tt, $c:tt, $e:expr) => {
if *KERNEL >= KernelVersion::new($a, $b, $c) {
Some($e)
} else {
None
}
};
}
impl Stat {
#[allow(clippy::cognitive_complexity)]
pub fn from_reader<R: io::Read>(mut r: R) -> ProcResult<Stat> {
let mut buf = Vec::with_capacity(512);
r.read_to_end(&mut buf)?;
let line = String::from_utf8_lossy(&buf);
let buf = line.trim();
let start_paren = expect!(buf.find('('));
let end_paren = expect!(buf.rfind(')'));
let pid_s = &buf[..start_paren - 1];
let comm = buf[start_paren + 1..end_paren].to_string();
let rest = &buf[end_paren + 2..];
let pid = expect!(FromStr::from_str(pid_s));
let mut rest = rest.split(' ');
let state = expect!(expect!(rest.next()).chars().next());
let ppid = expect!(from_iter(&mut rest));
let pgrp = expect!(from_iter(&mut rest));
let session = expect!(from_iter(&mut rest));
let tty_nr = expect!(from_iter(&mut rest));
let tpgid = expect!(from_iter(&mut rest));
let flags = expect!(from_iter(&mut rest));
let minflt = expect!(from_iter(&mut rest));
let cminflt = expect!(from_iter(&mut rest));
let majflt = expect!(from_iter(&mut rest));
let cmajflt = expect!(from_iter(&mut rest));
let utime = expect!(from_iter(&mut rest));
let stime = expect!(from_iter(&mut rest));
let cutime = expect!(from_iter(&mut rest));
let cstime = expect!(from_iter(&mut rest));
let priority = expect!(from_iter(&mut rest));
let nice = expect!(from_iter(&mut rest));
let num_threads = expect!(from_iter(&mut rest));
let itrealvalue = expect!(from_iter(&mut rest));
let starttime = expect!(from_iter(&mut rest));
let vsize = expect!(from_iter(&mut rest));
let rss = expect!(from_iter(&mut rest));
let rsslim = expect!(from_iter(&mut rest));
let startcode = expect!(from_iter(&mut rest));
let endcode = expect!(from_iter(&mut rest));
let startstack = expect!(from_iter(&mut rest));
let kstkesp = expect!(from_iter(&mut rest));
let kstkeip = expect!(from_iter(&mut rest));
let signal = expect!(from_iter(&mut rest));
let blocked = expect!(from_iter(&mut rest));
let sigignore = expect!(from_iter(&mut rest));
let sigcatch = expect!(from_iter(&mut rest));
let wchan = expect!(from_iter(&mut rest));
let nswap = expect!(from_iter(&mut rest));
let cnswap = expect!(from_iter(&mut rest));
let exit_signal = since_kernel!(2, 1, 22, expect!(from_iter(&mut rest)));
let processor = since_kernel!(2, 2, 8, expect!(from_iter(&mut rest)));
let rt_priority = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest)));
let policy = since_kernel!(2, 5, 19, expect!(from_iter(&mut rest)));
let delayacct_blkio_ticks = since_kernel!(2, 6, 18, expect!(from_iter(&mut rest)));
let guest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest)));
let cguest_time = since_kernel!(2, 6, 24, expect!(from_iter(&mut rest)));
let start_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest)));
let end_data = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest)));
let start_brk = since_kernel!(3, 3, 0, expect!(from_iter(&mut rest)));
let arg_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
let arg_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
let env_start = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
let env_end = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
let exit_code = since_kernel!(3, 5, 0, expect!(from_iter(&mut rest)));
Ok(Stat {
pid,
comm,
state,
ppid,
pgrp,
session,
tty_nr,
tpgid,
flags,
minflt,
cminflt,
majflt,
cmajflt,
utime,
stime,
cutime,
cstime,
priority,
nice,
num_threads,
itrealvalue,
starttime,
vsize,
rss,
rsslim,
startcode,
endcode,
startstack,
kstkesp,
kstkeip,
signal,
blocked,
sigignore,
sigcatch,
wchan,
nswap,
cnswap,
exit_signal,
processor,
rt_priority,
policy,
delayacct_blkio_ticks,
guest_time,
cguest_time,
start_data,
end_data,
start_brk,
arg_start,
arg_end,
env_start,
env_end,
exit_code,
})
}
pub fn state(&self) -> ProcResult<ProcState> {
ProcState::from_char(self.state).ok_or_else(|| {
build_internal_error!(format!(
"{:?} is not a recognized process state",
self.state
))
})
}
pub fn tty_nr(&self) -> (i32, i32) {
let major = (self.tty_nr & 0xfff00) >> 8;
let minor = (self.tty_nr & 0x000ff) | ((self.tty_nr >> 12) & 0xfff00);
(major, minor)
}
pub fn flags(&self) -> ProcResult<StatFlags> {
StatFlags::from_bits(self.flags).ok_or_else(|| {
build_internal_error!(format!(
"Can't construct flags bitfield from {:?}",
self.flags
))
})
}
#[cfg(feature = "chrono")]
pub fn starttime(&self) -> ProcResult<DateTime<Local>> {
let seconds_since_boot = self.starttime as f32 / *TICKS_PER_SECOND as f32;
let boot_time = boot_time()?;
Ok(boot_time + chrono::Duration::milliseconds((seconds_since_boot * 1000.0) as i64))
}
pub fn rss_bytes(&self) -> i64 {
self.rss * *PAGESIZE
}
}
#[derive(Debug, Clone)]
pub struct Status {
pub name: String,
pub umask: Option<u32>,
pub state: String,
pub tgid: i32,
pub ngid: Option<i32>,
pub pid: i32,
pub ppid: i32,
pub tracerpid: i32,
pub ruid: u32,
pub euid: u32,
pub suid: u32,
pub fuid: u32,
pub rgid: u32,
pub egid: u32,
pub sgid: u32,
pub fgid: u32,
pub fdsize: u32,
pub groups: Vec<i32>,
pub nstgid: Option<Vec<i32>>,
pub nspid: Option<Vec<i32>>,
pub nspgid: Option<Vec<i32>>,
pub nssid: Option<Vec<i32>>,
pub vmpeak: Option<u64>,
pub vmsize: Option<u64>,
pub vmlck: Option<u64>,
pub vmpin: Option<u64>,
pub vmhwm: Option<u64>,
pub vmrss: Option<u64>,
pub rssanon: Option<u64>,
pub rssfile: Option<u64>,
pub rssshmem: Option<u64>,
pub vmdata: Option<u64>,
pub vmstk: Option<u64>,
pub vmexe: Option<u64>,
pub vmlib: Option<u64>,
pub vmpte: Option<u64>,
pub vmswap: Option<u64>,
pub hugetblpages: Option<u64>,
pub threads: u64,
pub sigq: (u64, u64),
pub sigpnd: u64,
pub shdpnd: u64,
pub sigblk: u64,
pub sigign: u64,
pub sigcgt: u64,
pub capinh: u64,
pub capprm: u64,
pub capeff: u64,
pub capbnd: Option<u64>,
pub capamb: Option<u64>,
pub nonewprivs: Option<u64>,
pub seccomp: Option<u32>,
pub speculation_store_bypass: Option<String>,
pub cpus_allowed: Option<Vec<u32>>,
pub cpus_allowed_list: Option<Vec<(u32, u32)>>,
pub mems_allowed: Option<Vec<u32>>,
pub mems_allowed_list: Option<Vec<(u32, u32)>>,
pub voluntary_ctxt_switches: Option<u64>,
pub nonvoluntary_ctxt_switches: Option<u64>,
pub core_dumping: Option<bool>,
pub thp_enabled: Option<bool>,
}
impl Status {
pub fn from_reader<R: io::Read>(r: R) -> ProcResult<Status> {
use std::io::{BufRead, BufReader};
let mut map = HashMap::new();
let reader = BufReader::new(r);
for line in reader.lines() {
let line = line?;
if line.is_empty() {
continue;
}
let mut s = line.split(':');
let field = expect!(s.next());
let value = expect!(s.next()).trim();
map.insert(field.to_string(), value.to_string());
}
let status = Status {
name: expect!(map.remove("Name")),
umask: map
.remove("Umask")
.map(|x| Ok(from_str!(u32, &x, 8)))
.transpose()?,
state: expect!(map.remove("State")),
tgid: from_str!(i32, &expect!(map.remove("Tgid"))),
ngid: map
.remove("Ngid")
.map(|x| Ok(from_str!(i32, &x)))
.transpose()?,
pid: from_str!(i32, &expect!(map.remove("Pid"))),
ppid: from_str!(i32, &expect!(map.remove("PPid"))),
tracerpid: from_str!(i32, &expect!(map.remove("TracerPid"))),
ruid: expect!(Status::parse_uid_gid(&expect!(map.get("Uid")), 0)),
euid: expect!(Status::parse_uid_gid(&expect!(map.get("Uid")), 1)),
suid: expect!(Status::parse_uid_gid(&expect!(map.get("Uid")), 2)),
fuid: expect!(Status::parse_uid_gid(&expect!(map.remove("Uid")), 3)),
rgid: expect!(Status::parse_uid_gid(&expect!(map.get("Gid")), 0)),
egid: expect!(Status::parse_uid_gid(&expect!(map.get("Gid")), 1)),
sgid: expect!(Status::parse_uid_gid(&expect!(map.get("Gid")), 2)),
fgid: expect!(Status::parse_uid_gid(&expect!(map.remove("Gid")), 3)),
fdsize: from_str!(u32, &expect!(map.remove("FDSize"))),
groups: Status::parse_list(&expect!(map.remove("Groups")))?,
nstgid: map
.remove("NStgid")
.map(|x| Status::parse_list(&x))
.transpose()?,
nspid: map
.remove("NSpid")
.map(|x| Status::parse_list(&x))
.transpose()?,
nspgid: map
.remove("NSpgid")
.map(|x| Status::parse_list(&x))
.transpose()?,
nssid: map
.remove("NSsid")
.map(|x| Status::parse_list(&x))
.transpose()?,
vmpeak: Status::parse_with_kb(map.remove("VmPeak"))?,
vmsize: Status::parse_with_kb(map.remove("VmSize"))?,
vmlck: Status::parse_with_kb(map.remove("VmLck"))?,
vmpin: Status::parse_with_kb(map.remove("VmPin"))?,
vmhwm: Status::parse_with_kb(map.remove("VmHWM"))?,
vmrss: Status::parse_with_kb(map.remove("VmRSS"))?,
rssanon: Status::parse_with_kb(map.remove("RssAnon"))?,
rssfile: Status::parse_with_kb(map.remove("RssFile"))?,
rssshmem: Status::parse_with_kb(map.remove("RssShmem"))?,
vmdata: Status::parse_with_kb(map.remove("VmData"))?,
vmstk: Status::parse_with_kb(map.remove("VmStk"))?,
vmexe: Status::parse_with_kb(map.remove("VmExe"))?,
vmlib: Status::parse_with_kb(map.remove("VmLib"))?,
vmpte: Status::parse_with_kb(map.remove("VmPTE"))?,
vmswap: Status::parse_with_kb(map.remove("VmSwap"))?,
hugetblpages: Status::parse_with_kb(map.remove("HugetlbPages"))?,
threads: from_str!(u64, &expect!(map.remove("Threads"))),
sigq: expect!(Status::parse_sigq(&expect!(map.remove("SigQ")))),
sigpnd: from_str!(u64, &expect!(map.remove("SigPnd")), 16),
shdpnd: from_str!(u64, &expect!(map.remove("ShdPnd")), 16),
sigblk: from_str!(u64, &expect!(map.remove("SigBlk")), 16),
sigign: from_str!(u64, &expect!(map.remove("SigIgn")), 16),
sigcgt: from_str!(u64, &expect!(map.remove("SigCgt")), 16),
capinh: from_str!(u64, &expect!(map.remove("CapInh")), 16),
capprm: from_str!(u64, &expect!(map.remove("CapPrm")), 16),
capeff: from_str!(u64, &expect!(map.remove("CapEff")), 16),
capbnd: map
.remove("CapBnd")
.map(|x| Ok(from_str!(u64, &x, 16)))
.transpose()?,
capamb: map
.remove("CapAmb")
.map(|x| Ok(from_str!(u64, &x, 16)))
.transpose()?,
nonewprivs: map
.remove("NoNewPrivs")
.map(|x| Ok(from_str!(u64, &x)))
.transpose()?,
seccomp: map
.remove("Seccomp")
.map(|x| Ok(from_str!(u32, &x)))
.transpose()?,
speculation_store_bypass: map.remove("Speculation_Store_Bypass"),
cpus_allowed: map
.remove("Cpus_allowed")
.map(|x| Status::parse_allowed(&x))
.transpose()?,
cpus_allowed_list: map
.remove("Cpus_allowed_list")
.and_then(|x| Status::parse_allowed_list(&x).ok()),
mems_allowed: map
.remove("Mems_allowed")
.map(|x| Status::parse_allowed(&x))
.transpose()?,
mems_allowed_list: map
.remove("Mems_allowed_list")
.and_then(|x| Status::parse_allowed_list(&x).ok()),
voluntary_ctxt_switches: map
.remove("voluntary_ctxt_switches")
.map(|x| Ok(from_str!(u64, &x)))
.transpose()?,
nonvoluntary_ctxt_switches: map
.remove("nonvoluntary_ctxt_switches")
.map(|x| Ok(from_str!(u64, &x)))
.transpose()?,
core_dumping: map.remove("CoreDumping").map(|x| x == "1"),
thp_enabled: map.remove("THP_enabled").map(|x| x == "1"),
};
if cfg!(test) && !map.is_empty() {
eprintln!("Warning: status map is not empty: {:#?}", map);
}
Ok(status)
}
fn parse_with_kb<T: FromStrRadix>(s: Option<String>) -> ProcResult<Option<T>> {
if let Some(s) = s {
Ok(Some(from_str!(T, &s.replace(" kB", ""))))
} else {
Ok(None)
}
}
fn parse_uid_gid(s: &str, i: usize) -> ProcResult<u32> {
Ok(from_str!(u32, expect!(s.split_whitespace().nth(i))))
}
fn parse_sigq(s: &str) -> ProcResult<(u64, u64)> {
let mut iter = s.split('/');
let first = from_str!(u64, expect!(iter.next()));
let second = from_str!(u64, expect!(iter.next()));
Ok((first, second))
}
fn parse_list<T: FromStrRadix>(s: &str) -> ProcResult<Vec<T>> {
let mut ret = Vec::new();
for i in s.split_whitespace() {
ret.push(from_str!(T, i));
}
Ok(ret)
}
fn parse_allowed(s: &str) -> ProcResult<Vec<u32>> {
let mut ret = Vec::new();
for i in s.split(',') {
ret.push(from_str!(u32, i, 16));
}
Ok(ret)
}
fn parse_allowed_list(s: &str) -> ProcResult<Vec<(u32, u32)>> {
let mut ret = Vec::new();
for s in s.split(',') {
if s.contains('-') {
let mut s = s.split('-');
let beg = from_str!(u32, expect!(s.next()));
if let Some(x) = s.next() {
let end = from_str!(u32, x);
ret.push((beg, end));
}
} else {
let beg = from_str!(u32, s);
let end = from_str!(u32, s);
ret.push((beg, end));
}
}
Ok(ret)
}
}
#[derive(Debug, Clone)]
pub struct Process {
pub pid: i32,
pub stat: Stat,
pub owner: u32,
pub(crate) root: PathBuf,
}
impl Process {
pub fn new(pid: pid_t) -> ProcResult<Process> {
let root = PathBuf::from("/proc").join(format!("{}", pid));
Self::new_with_root(root)
}
pub fn new_with_root(root: PathBuf) -> ProcResult<Process> {
let path = root.join("stat");
let stat = Stat::from_reader(FileWrapper::open(&path)?)?;
let md = std::fs::metadata(&root)?;
Ok(Process {
pid: stat.pid,
root,
stat,
owner: md.st_uid(),
})
}
pub fn myself() -> ProcResult<Process> {
let root = PathBuf::from("/proc/self");
Self::new_with_root(root)
}
pub fn cmdline(&self) -> ProcResult<Vec<String>> {
let mut buf = String::new();
let mut f = FileWrapper::open(self.root.join("cmdline"))?;
f.read_to_string(&mut buf)?;
Ok(buf
.split('\0')
.filter_map(|s| {
if !s.is_empty() {
Some(s.to_string())
} else {
None
}
})
.collect())
}
pub fn pid(&self) -> pid_t {
self.stat.pid
}
pub fn is_alive(&self) -> bool {
match Process::new(self.pid()) {
Ok(prc) => {
prc.stat.comm == self.stat.comm
&& prc.owner == self.owner
&& prc.stat.starttime == self.stat.starttime
&& prc
.stat
.state()
.map(|s| s != ProcState::Zombie)
.unwrap_or(false)
&& self
.stat
.state()
.map(|s| s != ProcState::Zombie)
.unwrap_or(false)
}
_ => false,
}
}
pub fn cwd(&self) -> ProcResult<PathBuf> {
Ok(std::fs::read_link(self.root.join("cwd"))?)
}
pub fn root(&self) -> ProcResult<PathBuf> {
Ok(std::fs::read_link(self.root.join("root"))?)
}
pub fn environ(&self) -> ProcResult<HashMap<OsString, OsString>> {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
let mut map = HashMap::new();
let mut file = FileWrapper::open(self.root.join("environ"))?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
for slice in buf.split(|b| *b == 0) {
let mut split = slice.splitn(2, |b| *b == b'=');
if let (Some(k), Some(v)) = (split.next(), split.next()) {
map.insert(
OsStr::from_bytes(k).to_os_string(),
OsStr::from_bytes(v).to_os_string(),
);
};
}
Ok(map)
}
pub fn exe(&self) -> ProcResult<PathBuf> {
Ok(std::fs::read_link(self.root.join("exe"))?)
}
pub fn io(&self) -> ProcResult<Io> {
let path = self.root.join("io");
let file = FileWrapper::open(&path)?;
Io::from_reader(file)
}
pub fn maps(&self) -> ProcResult<Vec<MemoryMap>> {
fn from_line(line: &str) -> ProcResult<MemoryMap> {
let mut s = line.splitn(6, ' ');
let address = expect!(s.next());
let perms = expect!(s.next());
let offset = expect!(s.next());
let dev = expect!(s.next());
let inode = expect!(s.next());
let path = expect!(s.next());
Ok(MemoryMap {
address: split_into_num(address, '-', 16)?,
perms: perms.to_string(),
offset: from_str!(u64, offset, 16),
dev: split_into_num(dev, ':', 16)?,
inode: from_str!(u64, inode),
pathname: MMapPath::from(path)?,
})
}
use std::io::{BufRead, BufReader};
let path = self.root.join("maps");
let file = FileWrapper::open(&path)?;
let reader = BufReader::new(file);
let mut vec = Vec::new();
for line in reader.lines() {
let line = line.map_err(|_| ProcError::Incomplete(Some(path.clone())))?;
vec.push(from_line(&line)?);
}
Ok(vec)
}
pub fn fd(&self) -> ProcResult<Vec<FDInfo>> {
use std::ffi::OsStr;
use std::fs::read_link;
let mut vec = Vec::new();
let path = self.root.join("fd");
for dir in wrap_io_error!(path, path.read_dir())? {
let entry = dir?;
let file_name = entry.file_name();
let fd = from_str!(u32, expect!(file_name.to_str()), 10);
if let (Ok(link), Ok(md)) = (read_link(entry.path()), entry.metadata()) {
let link_os: &OsStr = link.as_ref();
vec.push(FDInfo {
fd,
mode: md.st_mode() & libc::S_IRWXU,
target: expect!(FDTarget::from_str(expect!(link_os.to_str()))),
});
}
}
Ok(vec)
}
pub fn coredump_filter(&self) -> ProcResult<Option<CoredumpFlags>> {
let mut file = FileWrapper::open(self.root.join("coredump_filter"))?;
let mut s = String::new();
file.read_to_string(&mut s)?;
if s.trim().is_empty() {
return Ok(None);
}
let flags = from_str!(u32, &s.trim(), 16, pid:self.stat.pid);
Ok(Some(expect!(CoredumpFlags::from_bits(flags))))
}
pub fn autogroup(&self) -> ProcResult<String> {
let mut s = String::new();
let mut file = FileWrapper::open(self.root.join("autogroup"))?;
file.read_to_string(&mut s)?;
Ok(s)
}
pub fn auxv(&self) -> ProcResult<HashMap<u32, u32>> {
use byteorder::{NativeEndian, ReadBytesExt};
let mut file = FileWrapper::open(self.root.join("auxv"))?;
let mut map = HashMap::new();
let mut buf = Vec::new();
let bytes_read = file.read_to_end(&mut buf)?;
if bytes_read == 0 {
return Ok(map);
}
buf.truncate(bytes_read);
let mut file = std::io::Cursor::new(buf);
loop {
let key = file.read_u32::<NativeEndian>()?;
let value = file.read_u32::<NativeEndian>()?;
if key == 0 && value == 0 {
break;
}
map.insert(key, value);
}
Ok(map)
}
pub fn mountstats(&self) -> ProcResult<Vec<MountStat>> {
let path = self.root.join("mountstats");
let file = FileWrapper::open(&path)?;
MountStat::from_reader(file)
}
pub fn wchan(&self) -> ProcResult<String> {
let mut s = String::new();
let mut file = FileWrapper::open(self.root.join("wchan"))?;
file.read_to_string(&mut s)?;
Ok(s)
}
pub fn status(&self) -> ProcResult<Status> {
let path = self.root.join("status");
let file = FileWrapper::open(&path)?;
Status::from_reader(file)
}
pub fn stat(&self) -> ProcResult<Stat> {
let path = self.root.join("stat");
let stat = Stat::from_reader(FileWrapper::open(&path)?)?;
Ok(stat)
}
pub fn loginuid(&self) -> ProcResult<u32> {
let mut uid = String::new();
let path = self.root.join("loginuid");
let mut file = FileWrapper::open(&path)?;
file.read_to_string(&mut uid)?;
Status::parse_uid_gid(&uid, 0)
}
pub fn limits(&self) -> ProcResult<Limits> {
let path = self.root.join("limits");
let file = FileWrapper::open(&path)?;
Limits::from_reader(file)
}
pub fn mountinfo(&self) -> ProcResult<Vec<MountInfo>> {
use std::io::{BufRead, BufReader};
let path = self.root.join("mountinfo");
let file = FileWrapper::open(&path)?;
let bufread = BufReader::new(file);
let lines = bufread.lines();
let mut vec = Vec::new();
for line in lines {
vec.push(MountInfo::from_line(&line?)?);
}
Ok(vec)
}
pub fn oom_score(&self) -> ProcResult<u32> {
let path = self.root.join("oom_score");
let mut file = FileWrapper::open(&path)?;
let mut oom = String::new();
file.read_to_string(&mut oom)?;
Ok(from_str!(u32, oom.trim()))
}
pub fn statm(&self) -> ProcResult<StatM> {
let path = self.root.join("statm");
let file = FileWrapper::open(&path)?;
StatM::from_reader(file)
}
}
pub fn all_processes() -> ProcResult<Vec<Process>> {
let mut v = Vec::new();
for dir in expect!(std::fs::read_dir("/proc/"), "No /proc/ directory") {
if let Ok(entry) = dir {
if let Ok(pid) = i32::from_str(&entry.file_name().to_string_lossy()) {
match Process::new(pid) {
Ok(prc) => v.push(prc),
Err(ProcError::InternalError(e)) => return Err(ProcError::InternalError(e)),
_ => {}
}
}
}
}
Ok(v)
}
#[derive(Debug, Clone)]
pub struct Limits {
pub max_cpu_time: Limit,
pub max_file_size: Limit,
pub max_data_size: Limit,
pub max_stack_size: Limit,
pub max_core_file_size: Limit,
pub max_resident_set: Limit,
pub max_processes: Limit,
pub max_open_files: Limit,
pub max_locked_memory: Limit,
pub max_address_space: Limit,
pub max_file_locks: Limit,
pub max_pending_signals: Limit,
pub max_msgqueue_size: Limit,
pub max_nice_priority: Limit,
pub max_realtime_priority: Limit,
pub max_realtime_timeout: Limit,
}
impl Limits {
fn from_reader<R: Read>(r: R) -> ProcResult<Limits> {
use std::io::{BufRead, BufReader};
let bufread = BufReader::new(r);
let mut lines = bufread.lines();
let mut map = HashMap::new();
while let Some(Ok(line)) = lines.next() {
let line = line.trim();
if line.starts_with("Limit") {
continue;
}
let s: Vec<_> = line.split_whitespace().collect();
let l = s.len();
let (hard_limit, soft_limit, name) = if line.starts_with("Max nice priority")
|| line.starts_with("Max realtime priority")
{
let hard_limit = expect!(s.get(l - 1)).to_owned();
let soft_limit = expect!(s.get(l - 2)).to_owned();
let name = s[0..l - 2].join(" ");
(hard_limit, soft_limit, name)
} else {
let hard_limit = expect!(s.get(l - 2)).to_owned();
let soft_limit = expect!(s.get(l - 3)).to_owned();
let name = s[0..l - 3].join(" ");
(hard_limit, soft_limit, name)
};
let _units = expect!(s.get(l - 1));
map.insert(
name.to_owned(),
(soft_limit.to_owned(), hard_limit.to_owned()),
);
}
let limits = Limits {
max_cpu_time: Limit::from_pair(expect!(map.remove("Max cpu time")))?,
max_file_size: Limit::from_pair(expect!(map.remove("Max file size")))?,
max_data_size: Limit::from_pair(expect!(map.remove("Max data size")))?,
max_stack_size: Limit::from_pair(expect!(map.remove("Max stack size")))?,
max_core_file_size: Limit::from_pair(expect!(map.remove("Max core file size")))?,
max_resident_set: Limit::from_pair(expect!(map.remove("Max resident set")))?,
max_processes: Limit::from_pair(expect!(map.remove("Max processes")))?,
max_open_files: Limit::from_pair(expect!(map.remove("Max open files")))?,
max_locked_memory: Limit::from_pair(expect!(map.remove("Max locked memory")))?,
max_address_space: Limit::from_pair(expect!(map.remove("Max address space")))?,
max_file_locks: Limit::from_pair(expect!(map.remove("Max file locks")))?,
max_pending_signals: Limit::from_pair(expect!(map.remove("Max pending signals")))?,
max_msgqueue_size: Limit::from_pair(expect!(map.remove("Max msgqueue size")))?,
max_nice_priority: Limit::from_pair(expect!(map.remove("Max nice priority")))?,
max_realtime_priority: Limit::from_pair(expect!(map.remove("Max realtime priority")))?,
max_realtime_timeout: Limit::from_pair(expect!(map.remove("Max realtime timeout")))?,
};
if cfg!(test) {
assert!(map.is_empty(), "Map isn't empty: {:?}", map);
}
Ok(limits)
}
}
#[derive(Debug, Copy, Clone)]
pub struct Limit {
pub soft_limit: LimitValue,
pub hard_limit: LimitValue,
}
impl Limit {
fn from_pair(l: (String, String)) -> ProcResult<Limit> {
let (soft, hard) = l;
Ok(Limit {
soft_limit: LimitValue::from_str(&soft)?,
hard_limit: LimitValue::from_str(&hard)?,
})
}
}
#[derive(Debug, Copy, Clone)]
pub enum LimitValue {
Unlimited,
Value(rlim_t),
}
impl LimitValue {
#[cfg(test)]
fn as_rlim_t(&self) -> libc::rlim_t {
match self {
LimitValue::Unlimited => libc::RLIM_INFINITY,
LimitValue::Value(v) => *v,
}
}
}
impl FromStr for LimitValue {
type Err = ProcError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "unlimited" {
Ok(LimitValue::Unlimited)
} else {
Ok(LimitValue::Value(from_str!(rlim_t, s)))
}
}
}
#[derive(Debug, Clone)]
pub enum MountOptFields {
Shared(u32),
Master(u32),
PropagateFrom(u32),
Unbindable,
}
#[derive(Debug, Clone)]
pub struct MountInfo {
pub mnt_id: i32,
pub pid: i32,
pub majmin: String,
pub root: String,
pub mount_point: PathBuf,
pub mount_options: HashMap<String, Option<String>>,
pub opt_fields: Vec<MountOptFields>,
pub fs_type: String,
pub mount_source: Option<String>,
pub super_options: HashMap<String, Option<String>>,
}
impl MountInfo {
fn from_line(line: &str) -> ProcResult<MountInfo> {
let mut split = line.split_whitespace();
let mnt_id = expect!(from_iter(&mut split));
let pid = expect!(from_iter(&mut split));
let majmin: String = expect!(from_iter(&mut split));
let root = expect!(from_iter(&mut split));
let mount_point = expect!(from_iter(&mut split));
let mount_options = {
let mut map = HashMap::new();
let all_opts = expect!(split.next());
for opt in all_opts.split(',') {
let mut s = opt.splitn(2, '=');
let opt_name = expect!(s.next());
map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned()));
}
map
};
let mut opt_fields = Vec::new();
loop {
let f = expect!(split.next());
if f == "-" {
break;
}
let mut s = f.split(':');
let opt = match expect!(s.next()) {
"shared" => {
let val = expect!(from_iter(&mut s));
MountOptFields::Shared(val)
}
"master" => {
let val = expect!(from_iter(&mut s));
MountOptFields::Master(val)
}
"propagate_from" => {
let val = expect!(from_iter(&mut s));
MountOptFields::PropagateFrom(val)
}
"unbindable" => MountOptFields::Unbindable,
_ => continue,
};
opt_fields.push(opt);
}
let fs_type: String = expect!(from_iter(&mut split));
let mount_source = match expect!(split.next()) {
"none" => None,
x => Some(x.to_owned()),
};
let super_options = {
let mut map = HashMap::new();
let all_opts = expect!(split.next());
for opt in all_opts.split(',') {
let mut s = opt.splitn(2, '=');
let opt_name = expect!(s.next());
map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned()));
}
map
};
Ok(MountInfo {
mnt_id,
pid,
majmin,
root,
mount_point,
mount_options,
opt_fields,
fs_type,
mount_source,
super_options,
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct StatM {
pub size: u64,
pub resident: u64,
pub shared: u64,
pub text: u64,
pub lib: u64,
pub data: u64,
pub dt: u64,
}
impl StatM {
fn from_reader<R: io::Read>(mut r: R) -> ProcResult<StatM> {
let mut line = String::new();
r.read_to_string(&mut line)?;
let mut s = line.split_whitespace();
let size = expect!(from_iter(&mut s));
let resident = expect!(from_iter(&mut s));
let shared = expect!(from_iter(&mut s));
let text = expect!(from_iter(&mut s));
let lib = expect!(from_iter(&mut s));
let data = expect!(from_iter(&mut s));
let dt = expect!(from_iter(&mut s));
if cfg!(test) {
assert!(s.next().is_none());
}
Ok(StatM {
size,
resident,
shared,
text,
lib,
data,
dt,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check_unwrap<T>(prc: &Process, val: ProcResult<T>) {
match val {
Ok(_t) => {}
Err(ProcError::PermissionDenied(_)) if unsafe { libc::geteuid() } != 0 => {
}
Err(ProcError::NotFound(path)) => {
if prc.is_alive() {
panic!("{:?} not found", path)
}
}
Err(err) => panic!("{:?}", err),
}
}
#[allow(clippy::cognitive_complexity)]
#[test]
fn test_self_proc() {
let myself = Process::myself().unwrap();
println!("{:#?}", myself);
println!("state: {:?}", myself.stat.state());
println!("tty: {:?}", myself.stat.tty_nr());
println!("flags: {:?}", myself.stat.flags());
#[cfg(feature = "chrono")]
println!("starttime: {:#?}", myself.stat.starttime());
let kernel = KernelVersion::current().unwrap();
if kernel >= KernelVersion::new(2, 1, 22) {
assert!(myself.stat.exit_signal.is_some());
} else {
assert!(myself.stat.exit_signal.is_none());
}
if kernel >= KernelVersion::new(2, 2, 8) {
assert!(myself.stat.processor.is_some());
} else {
assert!(myself.stat.processor.is_none());
}
if kernel >= KernelVersion::new(2, 5, 19) {
assert!(myself.stat.rt_priority.is_some());
} else {
assert!(myself.stat.rt_priority.is_none());
}
if kernel >= KernelVersion::new(2, 5, 19) {
assert!(myself.stat.rt_priority.is_some());
assert!(myself.stat.policy.is_some());
} else {
assert!(myself.stat.rt_priority.is_none());
assert!(myself.stat.policy.is_none());
}
if kernel >= KernelVersion::new(2, 6, 18) {
assert!(myself.stat.delayacct_blkio_ticks.is_some());
} else {
assert!(myself.stat.delayacct_blkio_ticks.is_none());
}
if kernel >= KernelVersion::new(2, 6, 24) {
assert!(myself.stat.guest_time.is_some());
assert!(myself.stat.cguest_time.is_some());
} else {
assert!(myself.stat.guest_time.is_none());
assert!(myself.stat.cguest_time.is_none());
}
if kernel >= KernelVersion::new(3, 3, 0) {
assert!(myself.stat.start_data.is_some());
assert!(myself.stat.end_data.is_some());
assert!(myself.stat.start_brk.is_some());
} else {
assert!(myself.stat.start_data.is_none());
assert!(myself.stat.end_data.is_none());
assert!(myself.stat.start_brk.is_none());
}
if kernel >= KernelVersion::new(3, 5, 0) {
assert!(myself.stat.arg_start.is_some());
assert!(myself.stat.arg_end.is_some());
assert!(myself.stat.env_start.is_some());
assert!(myself.stat.env_end.is_some());
assert!(myself.stat.exit_code.is_some());
} else {
assert!(myself.stat.arg_start.is_none());
assert!(myself.stat.arg_end.is_none());
assert!(myself.stat.env_start.is_none());
assert!(myself.stat.env_end.is_none());
assert!(myself.stat.exit_code.is_none());
}
}
#[test]
fn test_all() {
for prc in all_processes().unwrap() {
println!("{} {}", prc.pid(), prc.stat.comm);
prc.stat.flags().unwrap();
prc.stat.state().unwrap();
#[cfg(feature = "chrono")]
prc.stat.starttime().unwrap();
if prc.stat.state().unwrap() == ProcState::Zombie {
continue;
}
check_unwrap(&prc, prc.cmdline());
check_unwrap(&prc, prc.environ());
check_unwrap(&prc, prc.fd());
check_unwrap(&prc, prc.io());
check_unwrap(&prc, prc.maps());
check_unwrap(&prc, prc.coredump_filter());
check_unwrap(&prc, prc.autogroup());
check_unwrap(&prc, prc.auxv());
check_unwrap(&prc, prc.cgroups());
check_unwrap(&prc, prc.wchan());
check_unwrap(&prc, prc.status());
check_unwrap(&prc, prc.mountinfo());
check_unwrap(&prc, prc.mountstats());
check_unwrap(&prc, prc.oom_score());
}
}
#[test]
fn test_proc_alive() {
let myself = Process::myself().unwrap();
assert!(myself.is_alive());
}
#[test]
fn test_proc_environ() {
let myself = Process::myself().unwrap();
let proc_environ = myself.environ().unwrap();
let std_environ: HashMap<_, _> = std::env::vars_os().collect();
assert_eq!(proc_environ, std_environ);
}
#[test]
fn test_error_handling() {
let init = Process::new(1).unwrap();
let i_am_root = unsafe { libc::geteuid() } == 0;
if !i_am_root {
assert!(!init.cwd().is_ok());
assert!(!init.environ().is_ok());
}
}
#[test]
fn test_proc_exe() {
let myself = Process::myself().unwrap();
let proc_exe = myself.exe().unwrap();
let std_exe = std::env::current_exe().unwrap();
assert_eq!(proc_exe, std_exe);
}
#[test]
fn test_proc_io() {
let myself = Process::myself().unwrap();
let kernel = KernelVersion::current().unwrap();
let io = myself.io();
println!("{:?}", io);
if io.is_ok() {
assert!(kernel >= KernelVersion::new(2, 6, 20));
}
}
#[test]
fn test_proc_maps() {
let myself = Process::myself().unwrap();
let maps = myself.maps().unwrap();
for map in maps {
println!("{:?}", map);
}
}
#[test]
fn test_mmap_path() {
assert_eq!(MMapPath::from("[stack]").unwrap(), MMapPath::Stack);
assert_eq!(
MMapPath::from("[foo]").unwrap(),
MMapPath::Other("foo".to_owned())
);
assert_eq!(MMapPath::from("").unwrap(), MMapPath::Anonymous);
assert_eq!(
MMapPath::from("[stack:154]").unwrap(),
MMapPath::TStack(154)
);
assert_eq!(
MMapPath::from("/lib/libfoo.so").unwrap(),
MMapPath::Path(PathBuf::from("/lib/libfoo.so"))
);
}
#[test]
fn test_proc_fd() {
let myself = Process::myself().unwrap();
for fd in myself.fd().unwrap() {
println!("{:?} {:?}", fd, fd.mode());
}
}
#[test]
fn test_proc_coredump() {
let myself = Process::myself().unwrap();
let flags = myself.coredump_filter();
println!("{:?}", flags);
}
#[test]
fn test_proc_auxv() {
let myself = Process::myself().unwrap();
let auxv = myself.auxv().unwrap();
println!("{:?}", auxv);
}
#[test]
fn test_proc_mountstats() {
let simple = MountStat::from_reader(
"device /dev/md127 mounted on /boot with fstype ext2
device /dev/md124 mounted on /home with fstype ext4
device tmpfs mounted on /run/user/0 with fstype tmpfs
"
.as_bytes(),
)
.unwrap();
let simple_parsed = vec![
MountStat {
device: Some("/dev/md127".to_string()),
mount_point: PathBuf::from("/boot"),
fs: "ext2".to_string(),
statistics: None,
},
MountStat {
device: Some("/dev/md124".to_string()),
mount_point: PathBuf::from("/home"),
fs: "ext4".to_string(),
statistics: None,
},
MountStat {
device: Some("tmpfs".to_string()),
mount_point: PathBuf::from("/run/user/0"),
fs: "tmpfs".to_string(),
statistics: None,
},
];
assert_eq!(simple, simple_parsed);
let mountstats = MountStat::from_reader("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1
opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none
age: 3542
impl_id: name='',domain='',date='0,0'
caps: caps=0x3ffdf,wtmult=512,dtsize=32768,bsize=0,namlen=255
nfsv4: bm0=0xfdffbfff,bm1=0x40f9be3e,bm2=0x803,acl=0x3,sessions,pnfs=not configured
sec: flavor=6,pseudoflavor=390003
events: 114 1579 5 3 132 20 3019 1 2 3 4 5 115 1 4 1 2 4 3 4 5 6 7 8 9 0 1
bytes: 1 2 3 4 5 6 7 8
RPC iostats version: 1.0 p/v: 100003/4 (nfs)
xprt: tcp 909 0 1 0 2 294 294 0 294 0 2 0 0
per-op statistics
NULL: 0 0 0 0 0 0 0 0
READ: 1 2 3 4 5 6 7 8
WRITE: 0 0 0 0 0 0 0 0
COMMIT: 0 0 0 0 0 0 0 0
OPEN: 1 1 0 320 420 0 124 124
".as_bytes()).unwrap();
let nfs_v4 = &mountstats[0];
match &nfs_v4.statistics {
Some(stats) => {
assert_eq!(
"1.1".to_string(),
stats.version,
"mountstats version wrongly parsed."
);
assert_eq!(Duration::from_secs(3542), stats.age);
assert_eq!(1, stats.bytes.normal_read);
assert_eq!(114, stats.events.inode_revalidate);
assert!(stats.server_caps().unwrap().is_some());
}
None => {
panic!("Failed to retrieve nfs statistics");
}
}
}
#[test]
fn test_proc_mountstats_live() {
let stats =
MountStat::from_reader(FileWrapper::open("/proc/self/mountstats").unwrap()).unwrap();
for stat in stats {
println!("{:#?}", stat);
if let Some(nfs) = stat.statistics {
println!(" {:?}", nfs.server_caps().unwrap());
}
}
}
#[test]
fn test_proc_wchan() {
let myself = Process::myself().unwrap();
let wchan = myself.wchan().unwrap();
println!("{:?}", wchan);
}
#[test]
fn test_proc_loginuid() {
if !Path::new("/proc/self/loginuid").exists() {
return;
}
let myself = Process::myself().unwrap();
let loginuid = myself.loginuid().unwrap();
println!("{:?}", loginuid);
}
#[test]
fn test_proc_status() {
let myself = Process::myself().unwrap();
let status = myself.status().unwrap();
println!("{:?}", status);
assert_eq!(status.name, myself.stat.comm);
assert_eq!(status.pid, myself.stat.pid);
assert_eq!(status.ppid, myself.stat.ppid);
}
#[test]
fn test_proc_status_for_kthreadd() {
let kthreadd = Process::new(2).unwrap();
let status = kthreadd.status().unwrap();
println!("{:?}", status);
assert_eq!(status.pid, 2);
assert_eq!(status.vmpeak, None);
assert_eq!(status.vmsize, None);
assert_eq!(status.vmlck, None);
assert_eq!(status.vmpin, None);
assert_eq!(status.vmhwm, None);
assert_eq!(status.vmrss, None);
assert_eq!(status.rssanon, None);
assert_eq!(status.rssfile, None);
assert_eq!(status.rssshmem, None);
assert_eq!(status.vmdata, None);
assert_eq!(status.vmstk, None);
assert_eq!(status.vmexe, None);
assert_eq!(status.vmlib, None);
assert_eq!(status.vmpte, None);
assert_eq!(status.vmswap, None);
assert_eq!(status.hugetblpages, None);
}
#[test]
fn test_nopanic() {
fn inner() -> ProcResult<u8> {
let a = vec!["xyz"];
from_iter(a)
}
assert!(inner().is_err());
}
#[test]
fn test_limits() {
let me = Process::myself().unwrap();
let limits = me.limits().unwrap();
println!("{:#?}", limits);
let mut libc_lim = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_CPU, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_cpu_time.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_cpu_time.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_FSIZE, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_file_size.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_file_size.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_DATA, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_data_size.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_data_size.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_STACK, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_stack_size.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_stack_size.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_CORE, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_core_file_size.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_core_file_size.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_RSS, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_resident_set.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_resident_set.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_NPROC, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_processes.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_processes.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_open_files.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_open_files.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_MEMLOCK, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_locked_memory.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_locked_memory.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_AS, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_address_space.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_address_space.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_LOCKS, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_file_locks.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_file_locks.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_SIGPENDING, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_pending_signals.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_pending_signals.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_MSGQUEUE, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_msgqueue_size.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_msgqueue_size.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_NICE, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_nice_priority.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_nice_priority.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_RTPRIO, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_realtime_priority.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_realtime_priority.hard_limit.as_rlim_t()
);
assert_eq!(
unsafe { libc::getrlimit(libc::RLIMIT_RTTIME, &mut libc_lim) },
0
);
assert_eq!(
libc_lim.rlim_cur,
limits.max_realtime_timeout.soft_limit.as_rlim_t()
);
assert_eq!(
libc_lim.rlim_max,
limits.max_realtime_timeout.hard_limit.as_rlim_t()
);
}
#[test]
fn test_procinfo() {
fn diff_mem(a: f32, b: f32) {
let diff = (a - b).abs();
assert!(diff < 20000.0, "diff:{}", diff);
}
std::thread::sleep(std::time::Duration::from_secs(1));
let procinfo_stat = procinfo::pid::stat_self().unwrap();
let me = Process::myself().unwrap();
let me_stat = me.stat;
diff_mem(procinfo_stat.vsize as f32, me_stat.vsize as f32);
assert_eq!(me_stat.priority, procinfo_stat.priority as i64);
assert_eq!(me_stat.nice, procinfo_stat.nice as i64);
assert_eq!(me_stat.pid, procinfo_stat.pid);
assert_eq!(me_stat.ppid, procinfo_stat.ppid);
}
#[test]
fn test_mountinfo() {
let s = "25 0 8:1 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro";
let stat = MountInfo::from_line(s).unwrap();
println!("{:?}", stat);
}
#[test]
fn test_mountinfo_live() {
let me = Process::myself().unwrap();
let mounts = me.mountinfo().unwrap();
println!("{:#?}", mounts);
}
#[test]
fn test_statm() {
let me = Process::myself().unwrap();
let statm = me.statm().unwrap();
println!("{:#?}", statm);
}
}