rhymuproc 1.1.2

Discover and interact with operating system processes
Documentation
use crate::ProcessInfo;
use std::{
    borrow::Borrow,
    collections::HashSet,
    ffi::c_void,
    os::raw::{
        c_int,
        c_longlong,
        c_short,
        c_uint,
        c_ushort,
    },
    path::PathBuf,
};

const AF_INET: c_int = 2;
const SOCKINFO_TCP: c_int = 2;
const PROC_PIDPATHINFO_MAXSIZE: usize = 4096;
const PROC_PIDLISTFDS: c_int = 1;
const PROC_PIDFDSOCKETINFO: c_int = 3;
const PROX_FDTYPE_SOCKET: u32 = 2;
const TSI_T_NTIMERS: usize = 4;
const SOCK_MAXADDRLEN: usize = 255;
const IF_NAMESIZE: usize = 16;
const MAX_KCTL_NAME: usize = 96;

#[allow(non_camel_case_types)]
type pid_t = c_int;
#[allow(non_camel_case_types)]
type off_t = c_longlong;
#[allow(non_camel_case_types)]
type uid_t = c_uint;
#[allow(non_camel_case_types)]
type gid_t = c_uint;
#[allow(non_camel_case_types)]
type in_addr_t = c_uint;
#[allow(non_camel_case_types)]
type sa_family_t = u8;

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct proc_fdinfo {
    proc_fd: i32,
    proc_fdtype: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct proc_fileinfo {
    fi_openflags: u32,
    fi_status: u32,
    fi_offset: off_t,
    fi_type: i32,
    fi_guardflags: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct vinfo_stat {
    vst_dev: u32,
    vst_mode: u16,
    vst_nlink: u16,
    vst_ino: u64,
    vst_uid: uid_t,
    vst_gid: gid_t,
    vst_atime: i64,
    vst_atimensec: i64,
    vst_mtime: i64,
    vst_mtimensec: i64,
    vst_ctime: i64,
    vst_ctimensec: i64,
    vst_birthtime: i64,
    vst_birthtimensec: i64,
    vst_size: off_t,
    vst_blocks: i64,
    vst_blksize: i32,
    vst_flags: u32,
    vst_gen: u32,
    vst_rdev: u32,
    vst_qspare: [i64; 2],
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct sockbuf_info {
    sbi_cc: u32,
    sbi_hiwat: u32,
    sbi_mbcnt: u32,
    sbi_mbmax: u32,
    sbi_lowat: u32,
    sbi_flags: c_short,
    sbi_timeo: c_short,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct in_addr {
    s_addr: in_addr_t,
}

#[repr(C)]
#[derive(Clone, Copy)]
union u6_addr {
    __u6_addr8: [u8; 16],
    __u6_addr16: [u16; 8],
    __u6_addr32: [u32; 4],
}

impl Default for u6_addr {
    fn default() -> Self {
        Self {
            __u6_addr32: [0; 4],
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct in6_addr {
    __u6_addr: u6_addr,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct in4in6_addr {
    i46a_pad32: [u32; 3],
    i46a_addr4: in_addr,
}

#[repr(C)]
#[derive(Clone, Copy)]
union insi_faddr_t {
    ina_46: in4in6_addr,
    ina_6: in6_addr,
}

impl Default for insi_faddr_t {
    fn default() -> Self {
        Self {
            ina_6: in6_addr::default(),
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy)]
union insi_laddr_t {
    ina_46: in4in6_addr,
    ina_6: in6_addr,
}

impl Default for insi_laddr_t {
    fn default() -> Self {
        Self {
            ina_6: in6_addr::default(),
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct insi_v4_t {
    in4_tos: u8,
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct insi_v6_t {
    in6_hlim: u8,
    in6_cksum: c_int,
    in6_ifindex: c_ushort,
    in6_hops: c_short,
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct in_sockinfo {
    insi_fport: c_int,
    insi_lport: c_int,
    insi_gencnt: u64,
    insi_flags: u32,
    insi_flow: u32,
    insi_vflag: u8,
    insi_ip_ttl: u8,
    rfu_1: u32,
    insi_faddr: insi_faddr_t,
    insi_laddr: insi_laddr_t,
    insi_v4: insi_v4_t,
    insi_v6: insi_v6_t,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct tcp_sockinfo {
    tcpsi_ini: in_sockinfo,
    tcpsi_state: c_int,
    tcpsi_timer: [c_int; TSI_T_NTIMERS],
    tcpsi_mss: c_int,
    tcpsi_flags: u32,
    rfu_1: u32,
    tcpsi_tp: u64,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct sockaddr_un {
    sun_len: u8,
    sun_family: sa_family_t,
    sun_path: [u8; 104],
}

#[repr(C)]
#[derive(Clone, Copy)]
union unsi_addr_t {
    ua_sun: sockaddr_un,
    ua_dummy: [u8; SOCK_MAXADDRLEN],
}

#[repr(C)]
#[derive(Clone, Copy)]
union unsi_caddr_t {
    ua_sun: sockaddr_un,
    ua_dummy: [u8; SOCK_MAXADDRLEN],
}

#[repr(C)]
#[derive(Clone, Copy)]
struct un_sockinfo {
    unsi_conn_so: u64,
    unsi_conn_pcb: u64,
    unsi_addr: unsi_addr_t,
    unsi_caddr: unsi_caddr_t,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct ndrv_info {
    ndrvsi_if_family: u32,
    ndrvsi_if_unit: u32,
    ndrvsi_if_name: [u8; IF_NAMESIZE],
}

#[repr(C)]
#[derive(Clone, Copy)]
struct kern_event_info {
    kesi_vendor_code_filter: u32,
    kesi_class_filter: u32,
    kesi_subclass_filter: u32,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct kern_ctl_info {
    kcsi_id: u32,
    kcsi_reg_unit: u32,
    kcsi_flags: u32,
    kcsi_recvbufsize: u32,
    kcsi_sendbufsize: u32,
    kcsi_unit: u32,
    kcsi_name: [u8; MAX_KCTL_NAME],
}

#[repr(C)]
#[derive(Clone, Copy)]
union soi_proto_t {
    pri_in: in_sockinfo,
    pri_tcp: tcp_sockinfo,
    pri_un: un_sockinfo,
    pri_ndrv: ndrv_info,
    pri_kern_event: kern_event_info,
    pri_kern_ctl: kern_ctl_info,
}

impl Default for soi_proto_t {
    fn default() -> Self {
        Self {
            pri_in: in_sockinfo::default(),
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct socket_info {
    soi_stat: vinfo_stat,
    soi_so: u64,
    soi_pcb: u64,
    soi_type: c_int,
    soi_protocol: c_int,
    soi_family: c_int,
    soi_options: c_short,
    soi_linger: c_short,
    soi_state: c_short,
    soi_qlen: c_short,
    soi_incqlen: c_short,
    soi_qlimit: c_short,
    soi_timeo: c_short,
    soi_error: c_ushort,
    soi_oobmark: u32,
    soi_rcv: sockbuf_info,
    soi_snd: sockbuf_info,
    soi_kind: c_int,
    rfu_1: u32,
    soi_proto: soi_proto_t,
}

#[repr(C)]
#[derive(Clone, Copy, Default)]
struct socket_fdinfo {
    pfi: proc_fileinfo,
    psi: socket_info,
}

#[link(name = "proc")]
extern "C" {
    fn proc_listallpids(
        buffer: *const c_void,
        buffersize: c_int,
    ) -> c_int;
    fn proc_pidfdinfo(
        pid: c_int,
        fd: c_int,
        flavor: c_int,
        buffer: *const c_void,
        buffersize: c_int,
    ) -> c_int;
    fn proc_pidinfo(
        pid: c_int,
        flavor: c_int,
        arg: u64,
        buffer: *mut c_void,
        buffersize: c_int,
    ) -> c_int;
    fn proc_pidpath(
        pid: c_int,
        buffer: *mut u8,
        buffersize: u32,
    ) -> c_int;
}

fn list_process_ids() -> Vec<pid_t> {
    let buffer_size = unsafe { proc_listallpids(std::ptr::null(), 0) };
    if buffer_size < 0 {
        return Vec::default();
    }
    #[allow(clippy::cast_sign_loss)]
    let mut pids = vec![0; buffer_size as usize];
    let buffer_size = unsafe {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        proc_listallpids(
            pids.as_mut_ptr() as *const c_void,
            buffer_size * std::mem::size_of::<pid_t>() as c_int,
        )
    };
    #[allow(clippy::cast_sign_loss)]
    pids.truncate(buffer_size as usize);
    pids
}

fn process_image(pid: pid_t) -> PathBuf {
    let mut name_chars = vec![0; PROC_PIDPATHINFO_MAXSIZE];
    let buffer_size = unsafe {
        #[allow(clippy::cast_possible_truncation)]
        proc_pidpath(pid, name_chars.as_mut_ptr(), name_chars.len() as u32)
    };
    #[allow(clippy::cast_sign_loss)]
    name_chars.truncate(buffer_size as usize);
    PathBuf::from(String::from_utf8_lossy(&name_chars).to_string())
}

fn fd_socket_info(
    pid: pid_t,
    fd: proc_fdinfo,
) -> Option<socket_fdinfo> {
    let mut socket_info = socket_fdinfo::default();
    let socket_info_ptr: *mut socket_fdinfo = &mut socket_info;
    if unsafe {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        proc_pidfdinfo(
            pid,
            fd.proc_fd,
            PROC_PIDFDSOCKETINFO,
            socket_info_ptr as *const c_void,
            std::mem::size_of::<socket_fdinfo>() as i32,
        )
    } >= 0
    {
        Some(socket_info)
    } else {
        None
    }
}

fn tcp_server_port<T: Borrow<socket_fdinfo>>(socket_info: T) -> Option<u16> {
    let socket_info = socket_info.borrow();
    if (socket_info.psi.soi_family == AF_INET)
        && (socket_info.psi.soi_kind == SOCKINFO_TCP)
        && unsafe {
            socket_info.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport == 0
        }
    {
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_sign_loss)]
        Some(
            unsafe {
                socket_info.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport as u16
            }
            .to_be(),
        )
    } else {
        None
    }
}

fn process_tcp_server_ports(pid: pid_t) -> HashSet<u16> {
    let buffer_size = unsafe {
        proc_pidinfo(pid, PROC_PIDLISTFDS, 0, std::ptr::null_mut(), 0)
    };
    if buffer_size < 0 {
        return HashSet::default();
    }
    #[allow(clippy::cast_sign_loss)]
    let mut fds = vec![
        proc_fdinfo::default();
        buffer_size as usize / std::mem::size_of::<proc_fdinfo>()
    ];
    let buffer_size = unsafe {
        proc_pidinfo(
            pid,
            PROC_PIDLISTFDS,
            0,
            fds.as_mut_ptr() as *mut libc::c_void,
            buffer_size,
        )
    };
    #[allow(clippy::cast_sign_loss)]
    fds.truncate(buffer_size as usize / std::mem::size_of::<proc_fdinfo>());
    fds.into_iter()
        .filter(|fd| fd.proc_fdtype == PROX_FDTYPE_SOCKET)
        .filter_map(|fd| fd_socket_info(pid, fd))
        .filter_map(tcp_server_port)
        .collect()
}

pub fn list_processes_internal() -> impl Iterator<Item = ProcessInfo> {
    list_process_ids().into_iter().map(|pid| {
        #[allow(clippy::cast_sign_loss)]
        let id = pid as usize;
        ProcessInfo {
            id,
            image: process_image(pid),
            tcp_server_ports: process_tcp_server_ports(pid),
        }
    })
}

pub fn close_all_files_except(keep_open: libc::c_int) {
    let pid = unsafe { libc::getpid() };
    let buffer_size = unsafe {
        proc_pidinfo(pid, PROC_PIDLISTFDS, 0, std::ptr::null_mut(), 0)
    };
    if buffer_size < 0 {
        return;
    }
    #[allow(clippy::cast_sign_loss)]
    let mut fds = vec![
        proc_fdinfo::default();
        buffer_size as usize / std::mem::size_of::<proc_fdinfo>()
    ];
    let buffer_size = unsafe {
        proc_pidinfo(
            pid,
            PROC_PIDLISTFDS,
            0,
            fds.as_mut_ptr() as *mut libc::c_void,
            buffer_size,
        )
    };
    if buffer_size < 0 {
        return;
    }
    #[allow(clippy::cast_sign_loss)]
    fds.truncate(buffer_size as usize / std::mem::size_of::<proc_fdinfo>());
    for fd in fds {
        if fd.proc_fd != keep_open {
            unsafe { libc::close(fd.proc_fd) };
        }
    }
}