use std::str::FromStr;
use std::fs::{self, File};
use std::path::{self, PathBuf};
use std::io::{BufRead, BufReader};
use tracing::{debug};
use crate::PathBufInteger;
pub fn get_comm(
path: &mut path::PathBuf) -> Option<String> {
debug!("Retrieving comm: path={:?}", path);
path.push("comm");
let result = fs::read_to_string(&path);
path.pop();
match result {
Ok(mut comm) => {
comm.pop();
if comm.len() == 15 &&
!comm.starts_with("kworker/") {
if let Some(long_comm) =
parse_long_comm(path) {
debug!("Found long comm from cmdline: comm={}", long_comm);
return Some(long_comm);
}
}
debug!("Retrieved comm: comm={}", comm);
Some(comm)
},
Err(_) => {
debug!("Failed to read comm from procfs");
None
},
}
}
const MOD_FLAG_READ: u8 = 1u8 << 0;
const MOD_FLAG_WRITE: u8 = 1u8 << 1;
const MOD_FLAG_EXEC: u8 = 1u8 << 2;
const MOD_FLAG_PRIVATE: u8 = 1u8 << 3;
#[derive(Default)]
pub struct ModuleInfo<'a> {
pub start_addr: u64,
pub end_addr: u64,
pub offset: u64,
pub ino: u64,
pub dev_maj: u32,
pub dev_min: u32,
pub path: Option<&'a str>,
flags: u8,
}
impl<'a> ModuleInfo<'a> {
pub fn len(&self) -> u64 {
(self.end_addr - self.start_addr) + 1
}
pub fn is_read(&self) -> bool { self.flags & MOD_FLAG_READ != 0 }
pub fn is_write(&self) -> bool { self.flags & MOD_FLAG_WRITE != 0 }
pub fn is_exec(&self) -> bool { self.flags & MOD_FLAG_EXEC != 0 }
pub fn is_private(&self) -> bool { self.flags & MOD_FLAG_PRIVATE != 0 }
pub fn from_line(line: &'a str) -> Option<Self> {
let parts = line.split_whitespace();
let mut module = ModuleInfo::default();
for (index, part) in parts.enumerate() {
match index {
0 => {
for address in part.split('-') {
if let Ok(address) = u64::from_str_radix(address, 16) {
if module.start_addr == 0 {
module.start_addr = address;
} else {
module.end_addr = address;
}
} else {
return None;
}
}
},
1 => {
if part.contains('r') {
module.flags |= MOD_FLAG_READ;
}
if part.contains('w') {
module.flags |= MOD_FLAG_WRITE;
}
if part.contains('x') {
module.flags |= MOD_FLAG_EXEC;
}
if part.contains('p') {
module.flags |= MOD_FLAG_PRIVATE;
}
},
2 => {
if let Ok(offset) = u64::from_str_radix(part, 16) {
module.offset = offset;
} else {
return None;
}
},
3 => {
let mut i = 0;
for index in part.split(':') {
if let Ok(value) = u32::from_str_radix(index, 16) {
if i == 0 {
module.dev_maj = value;
} else {
module.dev_min = value;
}
i += 1;
} else {
return None;
}
}
},
4 => {
if let Ok(ino) = u64::from_str(part) {
module.ino = ino;
} else {
return None;
}
},
5 => {
module.path = Some(part);
},
_ => {
break;
}
}
}
Some(module)
}
}
pub fn ns_pid(
path_buf: &mut PathBuf,
pid: u32) -> Option<u32> {
path_buf.clear();
path_buf.push("/proc");
if pid != 0 {
path_buf.push_u32(pid);
} else {
path_buf.push("self");
}
path_buf.push("status");
if let Ok(file) = File::open(&path_buf) {
for line in BufReader::new(file).lines() {
match line {
Ok(line) => {
if let Some(nspid) = try_parse_nspid_line(&line) {
return Some(nspid);
}
},
Err(_) => { break; },
}
}
}
None
}
fn try_parse_nspid_line(line: &str) -> Option<u32> {
line.strip_prefix("NSpid:\t")
.and_then(|value| {
value
.trim_end()
.rsplit('\t')
.next()
.and_then(|pid| pid.parse::<u32>().ok())
})
}
pub fn iter_proc_tasks(
pid: u32,
mut callback: impl FnMut(u32)) {
let mut path_buf = PathBuf::new();
path_buf.push("/proc");
path_buf.push_u32(pid);
path_buf.push("task");
let dirs = fs::read_dir(path_buf);
if let Ok(dirs) = dirs {
for entry in dirs {
if let Ok(entry) = entry {
let path = entry.path();
if path.components().count() == 5 {
let mut iter = path.iter();
iter.next(); iter.next(); iter.next(); iter.next();
if let Some(task_str) = iter.next() { let s = task_str.to_str().unwrap();
if let Ok(task) = s.parse::<u32>() {
if task != pid {
(callback)(task);
}
}
}
}
}
}
}
}
pub fn iter_proc_modules(
pid: u32,
mut callback: impl FnMut(&ModuleInfo)) {
let mut path_buf = PathBuf::new();
path_buf.push("/proc");
if pid != 0 {
path_buf.push_u32(pid);
} else {
path_buf.push("self");
}
path_buf.push("maps");
if let Ok(file) = File::open(&path_buf) {
for line in BufReader::new(file).lines() {
match line {
Ok(line) => {
if let Some(module) = ModuleInfo::from_line(&line) {
(callback)(&module);
}
},
Err(_) => { break; },
}
}
}
}
pub fn iter_modules(
mut callback: impl FnMut(u32, &ModuleInfo)) {
iter_processes(|pid,path| {
path.push("maps");
let result = File::open(&path);
path.pop();
if let Ok(file) = result {
for line in BufReader::new(file).lines() {
match line {
Ok(line) => {
if let Some(module) = ModuleInfo::from_line(&line) {
(callback)(pid, &module);
}
},
Err(_) => { break; },
}
}
}
});
}
fn parse_long_comm(
path: &mut path::PathBuf) -> Option<String> {
path.push("cmdline");
let result = fs::read_to_string(&path);
path.pop();
match result {
Ok(mut cmdline) => {
if let Some(index) = cmdline.find('\0') {
cmdline.truncate(index);
}
if cmdline.is_empty() {
return None;
}
if let Some(index) = cmdline.rfind('/') {
cmdline = cmdline.split_off(index + 1);
}
Some(cmdline)
},
Err(_) => {
None
},
}
}
pub fn iter_processes(mut callback: impl FnMut(u32, &mut PathBuf)) {
let mut path_buf = PathBuf::new();
path_buf.push("/proc");
for entry in fs::read_dir(path_buf)
.expect("Unable to open procfs") {
let entry = entry.expect("Unable to get path");
let mut path = entry.path();
if path.components().count() == 3 {
let mut iter = path.iter();
iter.next(); iter.next();
if let Some(pid_str) = iter.next() { let s = pid_str.to_str().unwrap();
if let Ok(pid)= s.parse::<u32>() {
(callback)(pid, &mut path);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_nspid_line_single_namespace() {
assert_eq!(try_parse_nspid_line("NSpid:\t1234"), Some(1234));
assert_eq!(try_parse_nspid_line("NSpid:\t1234\n"), Some(1234));
}
#[test]
fn parse_nspid_line_nested_namespace_uses_innermost_pid() {
assert_eq!(try_parse_nspid_line("NSpid:\t1234\t5678"), Some(5678));
assert_eq!(try_parse_nspid_line("NSpid:\t1234\t5678\t9012\n"), Some(9012));
}
#[test]
fn parse_nspid_line_invalid_or_missing_tag() {
assert_eq!(try_parse_nspid_line("Pid:\t1234"), None);
assert_eq!(try_parse_nspid_line("NSpid:"), None);
}
}