use super::*;
use crate::net::{TcpNetEntry, UdpNetEntry};
use crate::sys::kernel::Version;
pub use procfs_core::process::*;
use rustix::fd::{AsFd, BorrowedFd, OwnedFd, RawFd};
use rustix::fs::{AtFlags, Mode, OFlags, RawMode};
#[cfg(feature = "serde1")]
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fs::read_link;
use std::io::{self, Read};
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::str::FromStr;
mod namespaces;
pub use namespaces::*;
mod task;
pub use task::*;
mod pagemap;
pub use pagemap::*;
#[cfg(test)]
mod tests;
bitflags! {
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
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;
}
}
#[derive(Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
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 flags = match Version::cached() {
Ok(v) if v < KernelVersion::new(3, 6, 0) => OFlags::NOFOLLOW | OFlags::CLOEXEC,
Ok(_) => OFlags::NOFOLLOW | OFlags::PATH | OFlags::CLOEXEC,
Err(_) => OFlags::NOFOLLOW | OFlags::PATH | OFlags::CLOEXEC,
};
let file = wrap_io_error!(root, rustix::fs::openat(dirfd, p, flags, 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 flags = match Version::cached() {
Ok(v) if v < KernelVersion::new(3, 6, 0) => OFlags::DIRECTORY | OFlags::CLOEXEC,
Ok(_) => OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC,
Err(_) => OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC,
};
let file = wrap_io_error!(root, rustix::fs::openat(rustix::fs::CWD, &root, flags, 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 {
if let Ok(stat) = self.stat() {
stat.state != 'Z' && stat.state != 'X'
} else {
false
}
}
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> {
FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "io")?)
}
pub fn maps(&self) -> ProcResult<MemoryMaps> {
FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "maps")?)
}
pub fn smaps(&self) -> ProcResult<MemoryMaps> {
FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "smaps")?)
}
pub fn smaps_rollup(&self) -> ProcResult<SmapsRollup> {
FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, "smaps_rollup")?)
}
pub fn mountstats(&self) -> ProcResult<MountStats> {
self.read("mountstats")
}
pub fn mountinfo(&self) -> ProcResult<MountInfos> {
self.read("mountinfo")
}
pub fn fd_count(&self) -> ProcResult<usize> {
let stat = wrap_io_error!(
self.root.join("fd"),
rustix::fs::statat(&self.fd, "fd", AtFlags::empty())
)?;
if stat.st_size > 0 {
return Ok(stat.st_size as 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>> {
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);
let mut buf = 0usize.to_ne_bytes();
loop {
file.read_exact(&mut buf)?;
let key = usize::from_ne_bytes(buf) as u64;
file.read_exact(&mut buf)?;
let value = usize::from_ne_bytes(buf) 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> {
self.read("status")
}
pub fn stat(&self) -> ProcResult<Stat> {
self.read("stat")
}
pub fn limits(&self) -> ProcResult<Limits> {
self.read("limits")
}
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<u16> {
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!(u16, oom.trim()))
}
pub fn oom_score_adj(&self) -> ProcResult<i16> {
let mut file = FileWrapper::open_at(&self.root, &self.fd, "oom_score_adj")?;
let mut oom = String::new();
file.read_to_string(&mut oom)?;
Ok(from_str!(i16, oom.trim()))
}
pub fn set_oom_score_adj(&self, new_oom_score_adj: i16) -> ProcResult<()> {
let path = self.root.join("oom_score_adj");
write_value(path, new_oom_score_adj)
}
pub fn statm(&self) -> ProcResult<StatM> {
self.read("statm")
}
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> {
self.read("schedstat")
}
pub fn tasks(&self) -> ProcResult<TasksIter> {
let task_path = self.root.join("task");
let dir_fd = wrap_io_error!(
&task_path,
rustix::fs::openat(
&self.fd,
"task",
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty()
)
)?;
let dir = wrap_io_error!(&task_path, rustix::fs::Dir::read_from(&dir_fd))?;
Ok(TasksIter {
pid: self.pid,
inner: dir,
inner_fd: dir_fd,
root: task_path,
})
}
pub fn tcp(&self) -> ProcResult<Vec<TcpNetEntry>> {
self.read_si("net/tcp").map(|net::TcpNetEntries(e)| e)
}
pub fn tcp6(&self) -> ProcResult<Vec<TcpNetEntry>> {
self.read_si("net/tcp6").map(|net::TcpNetEntries(e)| e)
}
pub fn udp(&self) -> ProcResult<Vec<UdpNetEntry>> {
self.read_si("net/udp").map(|net::UdpNetEntries(e)| e)
}
pub fn udp6(&self) -> ProcResult<Vec<UdpNetEntry>> {
self.read_si("net/udp6").map(|net::UdpNetEntries(e)| e)
}
pub fn dev_status(&self) -> ProcResult<HashMap<String, net::DeviceStatus>> {
self.read("net/dev").map(|net::InterfaceDeviceStatus(e)| e)
}
pub fn unix(&self) -> ProcResult<Vec<net::UnixNetEntry>> {
self.read("net/unix").map(|net::UnixNetEntries(e)| e)
}
pub fn arp(&self) -> ProcResult<Vec<net::ARPEntry>> {
self.read("net/arp").map(|net::ArpEntries(e)| e)
}
pub fn route(&self) -> ProcResult<Vec<net::RouteEntry>> {
self.read("net/route").map(|net::RouteEntries(e)| e)
}
pub fn snmp(&self) -> ProcResult<net::Snmp> {
self.read("net/snmp")
}
pub fn snmp6(&self) -> ProcResult<net::Snmp6> {
self.read("net/snmp6")
}
pub fn mem(&self) -> ProcResult<File> {
let file = FileWrapper::open_at(&self.root, &self.fd, "mem")?;
Ok(file.inner())
}
pub fn open_relative(&self, path: &str) -> ProcResult<File> {
let file = FileWrapper::open_at(&self.root, &self.fd, path)?;
Ok(file.inner())
}
pub fn read<T: FromRead>(&self, path: &str) -> ProcResult<T> {
FromRead::from_read(FileWrapper::open_at(&self.root, &self.fd, path)?)
}
pub fn read_si<T: FromReadSI>(&self, path: &str) -> ProcResult<T> {
FromReadSI::from_read(
FileWrapper::open_at(&self.root, &self.fd, path)?,
crate::current_system_info(),
)
}
pub fn clear_refs(&self, clear: ClearRefs) -> ProcResult<()> {
write_value(self.root.join("clear_refs"), clear)
}
}
#[derive(Debug)]
pub struct FDsIter {
inner: rustix::fs::Dir,
inner_fd: rustix::fd::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::fd::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 {
root: PathBuf::from(root),
inner: dir,
})
}
#[derive(Debug)]
pub struct ProcessesIter {
root: PathBuf,
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()) {
break Some(Process::new_with_root(self.root.join(pid.to_string())));
}
}
Some(Err(e)) => break Some(Err(io::Error::from(e).into())),
None => break None,
}
}
}
}