use std::os::unix::ffi::OsStrExt;
use std::{ffi, io, mem, path, ptr};
use libc::{c_char, c_int, c_void};
use crate::libproc::bindings;
use crate::libproc::processes::ProcFilter;
impl ProcFilter {
pub(crate) fn typeinfo(self) -> u32 {
match self {
ProcFilter::All => 0, ProcFilter::ByProgramGroup { pgrpid } => pgrpid,
ProcFilter::ByTTY { tty } => tty,
ProcFilter::ByUID { uid } => uid,
ProcFilter::ByRealUID { ruid } => ruid,
ProcFilter::ByParentProcess { ppid } => ppid,
}
}
}
impl From<ProcFilter> for u32 {
fn from(proc_type: ProcFilter) -> Self {
match proc_type {
ProcFilter::All => bindings::PROC_ALL_PIDS,
ProcFilter::ByProgramGroup { .. } => bindings::PROC_PGRP_ONLY,
ProcFilter::ByTTY { .. } => bindings::PROC_TTY_ONLY,
ProcFilter::ByUID { .. } => bindings::PROC_UID_ONLY,
ProcFilter::ByRealUID { .. } => bindings::PROC_RUID_ONLY,
ProcFilter::ByParentProcess { .. } => bindings::PROC_PPID_ONLY,
}
}
}
fn check_listpid_ret(ret: c_int) -> io::Result<Vec<u32>> {
if ret < 0 || (ret == 0 && io::Error::last_os_error().raw_os_error().unwrap_or(0) != 0) {
return Err(io::Error::last_os_error());
}
#[allow(clippy::cast_sign_loss)]
let capacity = ret as usize / mem::size_of::<u32>();
Ok(Vec::with_capacity(capacity))
}
fn list_pids_ret(ret: c_int, mut pids: Vec<u32>) -> io::Result<Vec<u32>> {
match ret {
value
if value < 0
|| (ret == 0 && io::Error::last_os_error().raw_os_error().unwrap_or(0) != 0) =>
{
Err(io::Error::last_os_error())
}
_ => {
#[allow(clippy::cast_sign_loss)]
let items_count = ret as usize / mem::size_of::<u32>();
unsafe {
pids.set_len(items_count);
}
Ok(pids)
}
}
}
pub(crate) fn listpids(proc_type: ProcFilter) -> io::Result<Vec<u32>> {
let buffer_size = unsafe {
bindings::proc_listpids(proc_type.into(), proc_type.typeinfo(), ptr::null_mut(), 0)
};
let mut pids = check_listpid_ret(buffer_size)?;
let buffer_ptr = pids.as_mut_ptr().cast::<c_void>();
let ret = unsafe {
bindings::proc_listpids(
proc_type.into(),
proc_type.typeinfo(),
buffer_ptr,
buffer_size,
)
};
list_pids_ret(ret, pids)
}
pub(crate) fn listpidspath(
proc_type: ProcFilter,
path: &path::Path,
is_volume: bool,
exclude_event_only: bool,
) -> io::Result<Vec<u32>> {
let path_bytes = path.as_os_str().as_bytes();
let c_path = ffi::CString::new(path_bytes)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "CString::new failed"))?;
let mut pathflags: u32 = 0;
if is_volume {
pathflags |= bindings::PROC_LISTPIDSPATH_PATH_IS_VOLUME;
}
if exclude_event_only {
pathflags |= bindings::PROC_LISTPIDSPATH_EXCLUDE_EVTONLY;
}
let buffer_size = unsafe {
bindings::proc_listpidspath(
proc_type.into(),
proc_type.typeinfo(),
c_path.as_ptr().cast::<c_char>(),
pathflags,
ptr::null_mut(),
0,
)
};
let mut pids = check_listpid_ret(buffer_size)?;
let buffer_ptr = pids.as_mut_ptr().cast::<c_void>();
let ret = unsafe {
bindings::proc_listpidspath(
proc_type.into(),
proc_type.typeinfo(),
c_path.as_ptr().cast::<c_char>(),
0,
buffer_ptr,
buffer_size,
)
};
list_pids_ret(ret, pids)
}
#[cfg(test)]
mod test {
use std::collections::{HashMap, HashSet};
use super::*;
use crate::libproc::{bsd_info, proc_pid};
#[allow(clippy::cast_possible_wrap)]
fn get_all_pid_bsdinfo() -> io::Result<Vec<bsd_info::BSDInfo>> {
let pids = listpids(ProcFilter::All)?;
Ok(pids
.iter()
.filter_map(|pid| proc_pid::pidinfo::<bsd_info::BSDInfo>(*pid as i32, 0).ok())
.collect())
}
#[test]
fn test_listpids() -> io::Result<()> {
let pid = std::process::id();
let pids = listpids(ProcFilter::All)?;
assert!(!pids.is_empty());
assert!(pids.contains(&pid));
Ok(())
}
const PROCESS_DIFF_TOLERANCE: usize = 15;
#[test]
fn test_listpids_pgid() {
let mut bsdinfo_pgrps: HashMap<_, HashSet<_>> = HashMap::new();
for info in get_all_pid_bsdinfo().expect("Could not get all pids info") {
if info.pbi_pgid == info.pbi_pid {
continue;
}
bsdinfo_pgrps
.entry(info.pbi_pgid)
.and_modify(|pids| {
pids.insert(info.pbi_pid);
})
.or_insert_with(|| vec![info.pbi_pid].into_iter().collect());
}
let mut not_matched = 0;
for (pgrp, bsdinfo_pids) in &mut bsdinfo_pgrps {
if bsdinfo_pids.len() <= 1 {
continue;
}
let pids =
listpids(ProcFilter::ByProgramGroup { pgrpid: *pgrp }).expect("Could not listpids");
for pid in pids {
if !bsdinfo_pids.remove(&pid) {
not_matched += 1;
break;
}
}
if !bsdinfo_pids.is_empty() {
not_matched += 1;
}
}
assert!(not_matched <= PROCESS_DIFF_TOLERANCE);
}
const NODEV: u32 = u32::MAX;
#[test]
fn test_listpids_tty() {
let mut bsdinfo_ttys: HashMap<_, HashSet<_>> = HashMap::new();
for info in get_all_pid_bsdinfo().expect("Could not get all pids info") {
if info.e_tdev == NODEV || info.e_tpgid == info.pbi_pid {
continue;
}
bsdinfo_ttys
.entry(info.e_tdev)
.and_modify(|pids| {
pids.insert(info.pbi_pid);
})
.or_insert_with(|| vec![info.pbi_pid].into_iter().collect());
}
let mut not_matched = 0;
for (tty_nr, bsdinfo_pids) in &mut bsdinfo_ttys {
if bsdinfo_pids.len() <= 1 {
continue;
}
let pids = listpids(ProcFilter::ByTTY { tty: *tty_nr }).expect("Could not listpids");
for pid in pids {
if !bsdinfo_pids.remove(&pid) {
not_matched += 1;
break;
}
}
if !bsdinfo_pids.is_empty() {
not_matched += 1;
}
}
assert!(not_matched <= PROCESS_DIFF_TOLERANCE);
}
#[test]
fn test_listpids_uid() {
let mut bsdinfo_uids: HashMap<_, HashSet<_>> = HashMap::new();
for info in get_all_pid_bsdinfo().expect("Could not get all pids info") {
bsdinfo_uids
.entry(info.pbi_uid)
.and_modify(|pids| {
pids.insert(info.pbi_pid);
})
.or_insert_with(|| vec![info.pbi_pid].into_iter().collect());
}
let mut not_matched = 0;
for (uid, bsdinfo_pids) in &mut bsdinfo_uids {
if bsdinfo_pids.len() <= 1 {
continue;
}
let pids = listpids(ProcFilter::ByUID { uid: *uid }).expect("Could not listpids");
for pid in pids {
if !bsdinfo_pids.remove(&pid) {
not_matched += 1;
break;
}
}
if !bsdinfo_pids.is_empty() {
not_matched += 1;
}
}
assert!(not_matched <= PROCESS_DIFF_TOLERANCE);
}
#[test]
fn test_listpids_real_uid() {
let mut bsdinfo_ruids: HashMap<_, HashSet<_>> = HashMap::new();
for info in get_all_pid_bsdinfo().expect("Could not get all pids info") {
bsdinfo_ruids
.entry(info.pbi_ruid)
.and_modify(|pids| {
pids.insert(info.pbi_pid);
})
.or_insert_with(|| vec![info.pbi_pid].into_iter().collect());
}
let mut not_matched = 0;
for (ruid, bsdinfo_pids) in &mut bsdinfo_ruids {
if bsdinfo_pids.len() <= 1 {
continue;
}
let pids = listpids(ProcFilter::ByRealUID { ruid: *ruid }).expect("Could not listpids");
for pid in pids {
if !bsdinfo_pids.remove(&pid) {
not_matched += 1;
println!("pid {pid} not matched for ruid {ruid}");
break;
}
}
}
assert!(not_matched <= PROCESS_DIFF_TOLERANCE);
}
#[test]
fn test_listpids_parent_pid() {
let mut bsdinfo_ppids: HashMap<_, HashSet<_>> = HashMap::new();
for info in get_all_pid_bsdinfo().expect("Could not get all pids info") {
bsdinfo_ppids
.entry(info.pbi_ppid)
.and_modify(|pids| {
pids.insert(info.pbi_pid);
})
.or_insert_with(|| vec![info.pbi_pid].into_iter().collect());
}
let mut not_matched = 0;
for (ppid, bsdinfo_pids) in &mut bsdinfo_ppids {
let pids = listpids(ProcFilter::ByParentProcess { ppid: *ppid })
.expect("Could not listpids by parent process");
for pid in pids {
if !bsdinfo_pids.remove(&pid) {
not_matched += 1;
break;
}
}
}
assert!(not_matched <= PROCESS_DIFF_TOLERANCE);
}
#[test]
fn test_listpids_invalid_parent_pid() {
let pids = listpids(ProcFilter::ByParentProcess { ppid: u32::MAX })
.expect("Error requesting children of inexistant process");
assert!(pids.is_empty());
}
#[test]
fn test_listpidspath() {
let root = std::path::Path::new("/");
let pids: Vec<u32> =
listpidspath(ProcFilter::All, root, true, false).expect("Failed to load PIDs for path");
assert!(!pids.is_empty());
}
}