use super::*;
use crate::from_iter;
use crate::net::{read_tcp_table, read_udp_table, TcpNetEntry, UdpNetEntry};
use rustix::fd::{AsFd, BorrowedFd, RawFd};
use rustix::fs::{AtFlags, Mode, OFlags, RawMode};
use rustix::io::OwnedFd;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fs::read_link;
use std::io::{self, Read};
use std::mem;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::str::FromStr;
mod limit;
pub use limit::*;
mod stat;
pub use stat::*;
mod mount;
pub use mount::*;
mod namespaces;
pub use namespaces::*;
mod status;
pub use status::*;
mod schedstat;
pub use schedstat::*;
mod task;
pub use task::*;
mod pagemap;
pub use pagemap::*;
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 FDPermissions: u16 {
const READ = Mode::RUSR.bits() as u16;
const WRITE = Mode::WUSR.bits() as u16;
const EXECUTE = Mode::XUSR.bits() as u16;
}
}
bitflags! {
pub struct VmFlags: u32 {
const INVALID = 0;
const RD = 1 << 0;
const WR = 1 << 1;
const EX = 1 << 2;
const SH = 1 << 3;
const MR = 1 << 4;
const MW = 1 << 5;
const ME = 1 << 6;
const MS = 1 << 7;
const GD = 1 << 8;
const PF = 1 << 9;
const DW = 1 << 10;
const LO = 1 << 11;
const IO = 1 << 12;
const SR = 1 << 13;
const RR = 1 << 14;
const DC = 1 << 15;
const DE = 1 << 16;
const AC = 1 << 17;
const NR = 1 << 18;
const HT = 1 << 19;
const SF = 1 << 20;
const NL = 1 << 21;
const AR = 1 << 22;
const WF = 1 << 23;
const DD = 1 << 24;
const SD = 1 << 25;
const MM = 1 << 26;
const HG = 1 << 27;
const NH = 1 << 28;
const MG = 1 << 29;
const UM = 1 << 30;
const UW = 1 << 31;
}
}
impl VmFlags {
fn from_str(flag: &str) -> Option<Self> {
if flag.len() != 2 {
return None;
}
match flag {
"rd" => Some(VmFlags::RD),
"wr" => Some(VmFlags::WR),
"ex" => Some(VmFlags::EX),
"sh" => Some(VmFlags::SH),
"mr" => Some(VmFlags::MR),
"mw" => Some(VmFlags::MW),
"me" => Some(VmFlags::ME),
"ms" => Some(VmFlags::MS),
"gd" => Some(VmFlags::GD),
"pf" => Some(VmFlags::PF),
"dw" => Some(VmFlags::DW),
"lo" => Some(VmFlags::LO),
"io" => Some(VmFlags::IO),
"sr" => Some(VmFlags::SR),
"rr" => Some(VmFlags::RR),
"dc" => Some(VmFlags::DC),
"de" => Some(VmFlags::DE),
"ac" => Some(VmFlags::AC),
"nr" => Some(VmFlags::NR),
"ht" => Some(VmFlags::HT),
"sf" => Some(VmFlags::SF),
"nl" => Some(VmFlags::NL),
"ar" => Some(VmFlags::AR),
"wf" => Some(VmFlags::WF),
"dd" => Some(VmFlags::DD),
"sd" => Some(VmFlags::SD),
"mm" => Some(VmFlags::MM),
"hg" => Some(VmFlags::HG),
"nh" => Some(VmFlags::NH),
"mg" => Some(VmFlags::MG),
"um" => Some(VmFlags::UM),
"uw" => Some(VmFlags::UW),
_ => None,
}
}
}
#[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, 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, PartialEq, Eq, Clone, Hash)]
pub enum MMapPath {
Path(PathBuf),
Heap,
Stack,
TStack(u32),
Vdso,
Vvar,
Vsyscall,
Anonymous,
Vsys(i32),
Other(String),
}
impl MMapPath {
fn new() -> MMapPath {
MMapPath::Anonymous
}
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 if x.starts_with("/SYSV") => MMapPath::Vsys(u32::from_str_radix(&x[5..13], 16)? as i32), x => MMapPath::Path(PathBuf::from(x)),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct MemoryMap {
pub address: (u64, u64),
pub perms: String,
pub offset: u64,
pub dev: (i32, i32),
pub inode: u64,
pub pathname: MMapPath,
}
impl MemoryMap {
fn new() -> Self {
Self {
address: (0, 0),
perms: "".into(),
offset: 0,
dev: (0, 0),
inode: 0,
pathname: MMapPath::new(),
}
}
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)?,
})
}
}
#[derive(Default, Debug)]
pub struct MemoryMapData {
pub map: HashMap<String, u64>,
pub vm_flags: Option<VmFlags>,
}
impl Io {
pub fn from_reader<R: io::Read>(r: R) -> ProcResult<Io> {
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")),
};
assert!(!cfg!(test) || map.is_empty(), "io map is not empty: {:#?}", map);
Ok(io)
}
}
#[derive(Clone, Debug)]
pub enum FDTarget {
Path(PathBuf),
Socket(u64),
Net(u64),
Pipe(u64),
AnonInode(String),
MemFD(String),
Other(String, u64),
}
impl FromStr for FDTarget {
type Err = ProcError;
fn from_str(s: &str) -> Result<FDTarget, ProcError> {
fn strip_first_last(s: &str) -> ProcResult<&str> {
if s.len() > 2 {
let mut c = s.chars();
let _ = c.next();
let _ = c.next_back();
Ok(c.as_str())
} else {
Err(ProcError::Incomplete(None))
}
}
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!(u64::from_str_radix(strip_first_last(inode)?, 10));
Ok(FDTarget::Socket(inode))
}
"net" => {
let inode = expect!(s.next(), "net inode");
let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10));
Ok(FDTarget::Net(inode))
}
"pipe" => {
let inode = expect!(s.next(), "pipe inode");
let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 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())),
"" => Err(ProcError::Incomplete(None)),
x => {
let inode = expect!(s.next(), "other inode");
let inode = expect!(u64::from_str_radix(strip_first_last(inode)?, 10));
Ok(FDTarget::Other(x.to_string(), inode))
}
}
} else {
Ok(FDTarget::Path(PathBuf::from(s)))
}
}
}
#[derive(Clone)]
pub struct FDInfo {
pub fd: i32,
pub mode: u16,
pub target: FDTarget,
}
impl FDInfo {
pub fn from_raw_fd(pid: i32, raw_fd: i32) -> ProcResult<Self> {
Self::from_raw_fd_with_root("/proc", pid, raw_fd)
}
pub fn from_raw_fd_with_root(root: impl AsRef<Path>, pid: i32, raw_fd: i32) -> ProcResult<Self> {
let path = root.as_ref().join(pid.to_string()).join("fd").join(raw_fd.to_string());
let link = wrap_io_error!(path, read_link(&path))?;
let md = wrap_io_error!(path, path.symlink_metadata())?;
let link_os: &OsStr = link.as_ref();
Ok(Self {
fd: raw_fd,
mode: ((md.mode() as RawMode) & Mode::RWXU.bits()) as u16,
target: expect!(FDTarget::from_str(expect!(link_os.to_str()))),
})
}
fn from_process_at<P: AsRef<Path>, Q: AsRef<Path>>(
base: P,
dirfd: BorrowedFd,
path: Q,
fd: i32,
) -> ProcResult<Self> {
let p = path.as_ref();
let root = base.as_ref().join(p);
let file = wrap_io_error!(
root,
rustix::fs::openat(
dirfd,
p,
OFlags::NOFOLLOW | OFlags::PATH | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let link = rustix::fs::readlinkat(&file, "", Vec::new()).map_err(io::Error::from)?;
let md =
rustix::fs::statat(&file, "", AtFlags::SYMLINK_NOFOLLOW | AtFlags::EMPTY_PATH).map_err(io::Error::from)?;
let link_os = link.to_string_lossy();
let target = FDTarget::from_str(link_os.as_ref())?;
Ok(FDInfo {
fd,
mode: (md.st_mode & Mode::RWXU.bits()) as u16,
target,
})
}
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
)
}
}
#[derive(Debug)]
pub struct Process {
fd: OwnedFd,
pub pid: i32,
pub(crate) root: PathBuf,
}
impl Process {
pub fn new(pid: i32) -> ProcResult<Process> {
let root = PathBuf::from("/proc").join(pid.to_string());
Self::new_with_root(root)
}
pub fn new_with_root(root: PathBuf) -> ProcResult<Process> {
let file = wrap_io_error!(
root,
rustix::fs::openat(
&rustix::fs::cwd(),
&root,
OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let pidres = root
.as_path()
.components()
.last()
.and_then(|c| match c {
std::path::Component::Normal(s) => Some(s),
_ => None,
})
.and_then(|s| s.to_string_lossy().parse::<i32>().ok())
.or_else(|| {
rustix::fs::readlinkat(&rustix::fs::cwd(), &root, Vec::new())
.ok()
.and_then(|s| s.to_string_lossy().parse::<i32>().ok())
});
let pid = match pidres {
Some(pid) => pid,
None => return Err(ProcError::NotFound(Some(root))),
};
Ok(Process { fd: file, pid, root })
}
pub fn myself() -> ProcResult<Process> {
let root = PathBuf::from("/proc/self");
Self::new_with_root(root)
}
}
impl Process {
pub fn cmdline(&self) -> ProcResult<Vec<String>> {
let mut buf = String::new();
let mut f = FileWrapper::open_at(&self.root, &self.fd, "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) -> i32 {
self.pid
}
pub fn is_alive(&self) -> bool {
rustix::fs::statat(&self.fd, "stat", AtFlags::empty()).is_ok()
}
pub fn uid(&self) -> ProcResult<u32> {
Ok(self.metadata()?.st_uid)
}
fn metadata(&self) -> ProcResult<rustix::fs::Stat> {
Ok(rustix::fs::fstat(&self.fd).map_err(io::Error::from)?)
}
pub fn cwd(&self) -> ProcResult<PathBuf> {
Ok(PathBuf::from(OsString::from_vec(
wrap_io_error!(
self.root.join("cwd"),
rustix::fs::readlinkat(&self.fd, "cwd", Vec::new())
)?
.into_bytes(),
)))
}
pub fn root(&self) -> ProcResult<PathBuf> {
Ok(PathBuf::from(OsString::from_vec(
wrap_io_error!(
self.root.join("root"),
rustix::fs::readlinkat(&self.fd, "root", Vec::new())
)?
.into_bytes(),
)))
}
pub fn environ(&self) -> ProcResult<HashMap<OsString, OsString>> {
use std::os::unix::ffi::OsStrExt;
let mut map = HashMap::new();
let mut file = FileWrapper::open_at(&self.root, &self.fd, "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(PathBuf::from(OsString::from_vec(
wrap_io_error!(
self.root.join("exe"),
rustix::fs::readlinkat(&self.fd, "exe", Vec::new())
)?
.into_bytes(),
)))
}
pub fn io(&self) -> ProcResult<Io> {
let file = FileWrapper::open_at(&self.root, &self.fd, "io")?;
Io::from_reader(file)
}
pub fn maps(&self) -> ProcResult<Vec<MemoryMap>> {
let file = FileWrapper::open_at(&self.root, &self.fd, "maps")?;
let reader = BufReader::new(file);
let mut vec = Vec::new();
for line in reader.lines() {
let line = line.map_err(|_| ProcError::Incomplete(Some(self.root.join("maps"))))?;
vec.push(MemoryMap::from_line(&line)?);
}
Ok(vec)
}
pub fn smaps(&self) -> ProcResult<Vec<(MemoryMap, MemoryMapData)>> {
let file = FileWrapper::open_at(&self.root, &self.fd, "smaps")?;
let reader = BufReader::new(file);
let mut vec: Vec<(MemoryMap, MemoryMapData)> = Vec::new();
let mut current_mapping = MemoryMap::new();
let mut current_data = Default::default();
for line in reader.lines() {
let line = line.map_err(|_| ProcError::Incomplete(Some(self.root.join("smaps"))))?;
if let Ok(mapping) = MemoryMap::from_line(&line) {
vec.push((current_mapping, current_data));
current_mapping = mapping;
current_data = Default::default();
} else {
if line.starts_with("VmFlags") {
let flags = line.split_ascii_whitespace();
let flags = flags.skip(1);
let flags = flags
.map(|v| match VmFlags::from_str(v) {
None => VmFlags::INVALID,
Some(v) => v,
})
.fold(VmFlags::INVALID, |a, b| a | b);
current_data.vm_flags = Some(flags);
} else {
let mut parts = line.split_ascii_whitespace();
let key = parts.next();
let value = parts.next();
if let (Some(k), Some(v)) = (key, value) {
let size_suffix = parts.next();
let size_multiplier = if size_suffix.is_some() { 1024 } else { 1 };
let v = v.parse::<u64>().map_err(|_| {
ProcError::Other("Value in `Key: Value` pair was not actually a number".into())
})?;
let k = k.trim_end_matches(':');
current_data.map.insert(k.into(), v * size_multiplier);
}
}
}
}
Ok(vec)
}
pub fn pagemap(&self) -> ProcResult<PageMap> {
let path = self.root.join("pagemap");
let file = FileWrapper::open(&path)?;
Ok(PageMap::from_file_wrapper(file))
}
pub fn fd_count(&self) -> ProcResult<usize> {
let fds = wrap_io_error!(
self.root.join("fd"),
rustix::fs::openat(
&self.fd,
"fd",
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let fds = wrap_io_error!(self.root.join("fd"), rustix::fs::Dir::read_from(fds))?;
Ok(fds.count())
}
pub fn fd(&self) -> ProcResult<FDsIter> {
let dir_fd = wrap_io_error!(
self.root.join("fd"),
rustix::fs::openat(
&self.fd,
"fd",
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let dir = wrap_io_error!(self.root.join("fd"), rustix::fs::Dir::read_from(&dir_fd))?;
Ok(FDsIter {
inner: dir,
inner_fd: dir_fd,
root: self.root.clone(),
})
}
pub fn fd_from_fd(&self, fd: i32) -> ProcResult<FDInfo> {
let path = PathBuf::from("fd").join(fd.to_string());
FDInfo::from_process_at(&self.root, self.fd.as_fd(), path, fd)
}
pub fn coredump_filter(&self) -> ProcResult<Option<CoredumpFlags>> {
let mut file = FileWrapper::open_at(&self.root, &self.fd, "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.pid);
Ok(Some(expect!(CoredumpFlags::from_bits(flags))))
}
pub fn autogroup(&self) -> ProcResult<String> {
let mut s = String::new();
let mut file = FileWrapper::open_at(&self.root, &self.fd, "autogroup")?;
file.read_to_string(&mut s)?;
Ok(s)
}
pub fn auxv(&self) -> ProcResult<HashMap<u64, u64>> {
use byteorder::{NativeEndian, ReadBytesExt};
let mut file = FileWrapper::open_at(&self.root, &self.fd, "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_uint::<NativeEndian>(mem::size_of::<usize>())? as u64;
let value = file.read_uint::<NativeEndian>(mem::size_of::<usize>())? as u64;
if key == 0 && value == 0 {
break;
}
map.insert(key, value);
}
Ok(map)
}
pub fn wchan(&self) -> ProcResult<String> {
let mut s = String::new();
let mut file = FileWrapper::open_at(&self.root, &self.fd, "wchan")?;
file.read_to_string(&mut s)?;
Ok(s)
}
pub fn status(&self) -> ProcResult<Status> {
let file = FileWrapper::open_at(&self.root, &self.fd, "status")?;
Status::from_reader(file)
}
pub fn stat(&self) -> ProcResult<Stat> {
let file = FileWrapper::open_at(&self.root, &self.fd, "stat")?;
let stat = Stat::from_reader(file)?;
Ok(stat)
}
pub fn loginuid(&self) -> ProcResult<u32> {
let mut uid = String::new();
let mut file = FileWrapper::open_at(&self.root, &self.fd, "loginuid")?;
file.read_to_string(&mut uid)?;
Status::parse_uid_gid(&uid, 0)
}
pub fn oom_score(&self) -> ProcResult<u32> {
let mut file = FileWrapper::open_at(&self.root, &self.fd, "oom_score")?;
let mut oom = String::new();
file.read_to_string(&mut oom)?;
Ok(from_str!(u32, oom.trim()))
}
pub fn statm(&self) -> ProcResult<StatM> {
let file = FileWrapper::open_at(&self.root, &self.fd, "statm")?;
StatM::from_reader(file)
}
pub fn task_main_thread(&self) -> ProcResult<Task> {
self.task_from_tid(self.pid)
}
pub fn task_from_tid(&self, tid: i32) -> ProcResult<Task> {
let path = PathBuf::from("task").join(tid.to_string());
Task::from_process_at(&self.root, self.fd.as_fd(), path, self.pid, tid)
}
pub fn schedstat(&self) -> ProcResult<Schedstat> {
let file = FileWrapper::open_at(&self.root, &self.fd, "schedstat")?;
Schedstat::from_reader(file)
}
pub fn tasks(&self) -> ProcResult<TasksIter> {
let dir_fd = wrap_io_error!(
self.root.join("task"),
rustix::fs::openat(
&self.fd,
"task",
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let dir = wrap_io_error!(self.root.join("task"), rustix::fs::Dir::read_from(&dir_fd))?;
Ok(TasksIter {
pid: self.pid,
inner: dir,
inner_fd: dir_fd,
root: self.root.clone(),
})
}
pub fn tcp(&self) -> ProcResult<Vec<TcpNetEntry>> {
let file = FileWrapper::open_at(&self.root, &self.fd, "net/tcp")?;
read_tcp_table(BufReader::new(file))
}
pub fn tcp6(&self) -> ProcResult<Vec<TcpNetEntry>> {
let file = FileWrapper::open_at(&self.root, &self.fd, "net/tcp6")?;
read_tcp_table(BufReader::new(file))
}
pub fn udp(&self) -> ProcResult<Vec<UdpNetEntry>> {
let file = FileWrapper::open_at(&self.root, &self.fd, "net/udp")?;
read_udp_table(BufReader::new(file))
}
pub fn udp6(&self) -> ProcResult<Vec<UdpNetEntry>> {
let file = FileWrapper::open_at(&self.root, &self.fd, "net/udp6")?;
read_udp_table(BufReader::new(file))
}
pub fn mem(&self) -> ProcResult<File> {
let file = FileWrapper::open_at(&self.root, &self.fd, "mem")?;
Ok(file.inner())
}
}
#[derive(Debug)]
pub struct FDsIter {
inner: rustix::fs::Dir,
inner_fd: rustix::io::OwnedFd,
root: PathBuf,
}
impl std::iter::Iterator for FDsIter {
type Item = ProcResult<FDInfo>;
fn next(&mut self) -> Option<ProcResult<FDInfo>> {
loop {
match self.inner.next() {
Some(Ok(entry)) => {
let name = entry.file_name().to_string_lossy();
if let Ok(fd) = RawFd::from_str(&name) {
if let Ok(info) = FDInfo::from_process_at(&self.root, self.inner_fd.as_fd(), name.as_ref(), fd)
{
break Some(Ok(info));
}
}
}
Some(Err(e)) => break Some(Err(io::Error::from(e).into())),
None => break None,
}
}
}
}
#[derive(Debug)]
pub struct TasksIter {
pid: i32,
inner: rustix::fs::Dir,
inner_fd: rustix::io::OwnedFd,
root: PathBuf,
}
impl std::iter::Iterator for TasksIter {
type Item = ProcResult<Task>;
fn next(&mut self) -> Option<ProcResult<Task>> {
loop {
match self.inner.next() {
Some(Ok(tp)) => {
if let Ok(tid) = i32::from_str(&tp.file_name().to_string_lossy()) {
if let Ok(task) =
Task::from_process_at(&self.root, self.inner_fd.as_fd(), tid.to_string(), self.pid, tid)
{
break Some(Ok(task));
}
}
}
Some(Err(e)) => break Some(Err(io::Error::from(e).into())),
None => break None,
}
}
}
}
pub fn all_processes() -> ProcResult<ProcessesIter> {
all_processes_with_root("/proc")
}
pub fn all_processes_with_root(root: impl AsRef<Path>) -> ProcResult<ProcessesIter> {
let root = root.as_ref();
let dir = wrap_io_error!(
root,
rustix::fs::openat(
&rustix::fs::cwd(),
root,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let dir = wrap_io_error!(root, rustix::fs::Dir::read_from(dir))?;
Ok(ProcessesIter { inner: dir })
}
#[derive(Debug)]
pub struct ProcessesIter {
inner: rustix::fs::Dir,
}
impl std::iter::Iterator for ProcessesIter {
type Item = ProcResult<Process>;
fn next(&mut self) -> Option<ProcResult<Process>> {
loop {
match self.inner.next() {
Some(Ok(entry)) => {
if let Ok(pid) = i32::from_str(&entry.file_name().to_string_lossy()) {
if let Ok(proc) = Process::new(pid) {
break Some(Ok(proc));
}
}
}
Some(Err(e)) => break Some(Err(io::Error::from(e).into())),
None => break None,
}
}
}
}
#[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;