use super::*;
use crate::from_iter;
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;
mod limit;
pub use limit::*;
mod stat;
pub use stat::*;
mod mount;
pub use mount::*;
mod status;
pub use status::*;
#[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 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, 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, 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> {
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
)
}
}
#[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>> {
pub(crate) 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)?,
})
}
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 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 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, 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;