mod binding;
use binding::{
cn_msg, nlmsghdr, proc_cn_mcast_op, sockaddr_nl, CN_IDX_PROC, NETLINK_CONNECTOR,
PROC_CN_MCAST_LISTEN,
};
use libc;
use std::collections::VecDeque;
use std::io::{Error, Result};
#[inline]
fn nlmsg_align(len: usize) -> usize {
(len + 3) & !3
}
#[inline]
fn nlmsg_hdrlen() -> usize {
nlmsg_align(std::mem::size_of::<nlmsghdr>())
}
#[inline]
fn nlmsg_length(len: usize) -> usize {
len + nlmsg_hdrlen()
}
#[derive(Debug)]
pub enum PidEvent {
Exec(libc::c_int),
Fork(libc::c_int),
Coredump(libc::c_int),
Exit(libc::c_int),
}
#[derive(Debug)]
pub struct PidMonitor {
fd: libc::c_int,
id: u32,
queue: VecDeque<PidEvent>,
}
impl PidMonitor {
pub fn new() -> Result<PidMonitor> {
PidMonitor::from_id(std::process::id())
}
pub fn from_id(id: u32) -> Result<PidMonitor> {
let fd = unsafe {
libc::socket(
libc::PF_NETLINK,
libc::SOCK_DGRAM,
NETLINK_CONNECTOR as i32,
)
};
let mut nl = unsafe { std::mem::zeroed::<sockaddr_nl>() };
nl.nl_pid = id;
nl.nl_family = libc::AF_NETLINK as u16;
nl.nl_groups = CN_IDX_PROC;
if unsafe {
libc::bind(
fd,
&nl as *const sockaddr_nl as _,
std::mem::size_of_val(&nl) as _,
)
} < 0
{
return Err(Error::last_os_error());
}
let mut monitor = PidMonitor {
fd,
id,
queue: VecDeque::new(),
};
monitor.listen()?;
return Ok(monitor);
}
fn listen(&mut self) -> Result<()> {
let val = true as libc::c_int;
if unsafe {
libc::setsockopt(
self.fd,
libc::SOL_NETLINK,
binding::NETLINK_NO_ENOBUFS as i32,
&val as *const libc::c_int as _,
std::mem::size_of_val(&val) as _,
)
} < 0
{
return Err(std::io::Error::last_os_error());
}
let mut iov_vec = Vec::<libc::iovec>::new();
let mut msghdr: nlmsghdr = unsafe { std::mem::zeroed() };
msghdr.nlmsg_len =
nlmsg_length(std::mem::size_of::<cn_msg>() + std::mem::size_of::<proc_cn_mcast_op>())
as u32;
msghdr.nlmsg_pid = self.id;
msghdr.nlmsg_type = binding::NLMSG_DONE as u16;
iov_vec.push(libc::iovec {
iov_len: std::mem::size_of::<nlmsghdr>(),
iov_base: &msghdr as *const nlmsghdr as _,
});
let mut cnmesg: cn_msg = unsafe { std::mem::zeroed() };
cnmesg.id.idx = CN_IDX_PROC;
cnmesg.id.val = binding::CN_VAL_PROC;
cnmesg.len = std::mem::size_of::<proc_cn_mcast_op>() as u16;
iov_vec.push(libc::iovec {
iov_len: std::mem::size_of::<cn_msg>(),
iov_base: &cnmesg as *const cn_msg as _,
});
let op = PROC_CN_MCAST_LISTEN;
iov_vec.push(libc::iovec {
iov_len: std::mem::size_of_val(&op),
iov_base: &op as *const proc_cn_mcast_op as _,
});
if unsafe { libc::writev(self.fd, iov_vec.as_ptr() as _, 3) } < 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
fn get_events(&mut self) -> Result<()> {
let page_size = std::cmp::min(unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize }, 8192);
let mut buffer = Vec::<u32>::with_capacity(page_size);
let buff_size = buffer.capacity();
unsafe {
buffer.set_len(buff_size);
}
let len = unsafe { libc::recv(self.fd, buffer.as_mut_ptr() as _, buff_size * 4, 0) };
if len < 0 {
return Err(Error::last_os_error());
}
let mut header = buffer.as_ptr() as *const nlmsghdr;
let mut len = len as usize;
loop {
if len < nlmsg_hdrlen() {
break;
}
let msg_len = unsafe { (*header).nlmsg_len } as usize;
if len < msg_len {
break;
}
let msg_type = unsafe { (*header).nlmsg_type } as u32;
match msg_type {
binding::NLMSG_ERROR | binding::NLMSG_NOOP => continue,
_ => {
if let Some(pidevent) = unsafe { parse_msg(header) } {
self.queue.push_back(pidevent)
}
}
};
let aligned_len = nlmsg_align(msg_len);
header = (header as usize + aligned_len) as *const nlmsghdr;
match len.checked_sub(aligned_len) {
Some(v) => len = v,
None => break,
};
}
Ok(())
}
pub fn recv(&mut self) -> Option<PidEvent> {
if self.queue.is_empty() {
match self.get_events() {
Ok(_) => self.queue.pop_front(),
Err(_) => None,
}
} else {
self.queue.pop_front()
}
}
}
unsafe fn parse_msg(header: *const nlmsghdr) -> Option<PidEvent> {
let msg = (header as usize + nlmsg_length(0)) as *const cn_msg;
if (*msg).id.idx != binding::CN_IDX_PROC || (*msg).id.val != binding::CN_VAL_PROC {
return None;
};
let proc_ev = (*msg).data.as_ptr() as *const binding::proc_event;
match (*proc_ev).what {
binding::PROC_EVENT_FORK => {
let pid = (*proc_ev).event_data.fork.child_pid;
Some(PidEvent::Fork(pid))
}
binding::PROC_EVENT_EXEC => {
let pid = (*proc_ev).event_data.exec.process_pid;
Some(PidEvent::Exec(pid))
}
binding::PROC_EVENT_EXIT => {
let pid = (*proc_ev).event_data.exit.process_pid;
Some(PidEvent::Exit(pid))
}
binding::PROC_EVENT_COREDUMP => {
let pid = (*proc_ev).event_data.coredump.process_pid;
Some(PidEvent::Coredump(pid))
}
_ => None,
}
}
impl Drop for PidMonitor {
fn drop(&mut self) {
unsafe { libc::close(self.fd) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {}
}