use std::{
ffi::CStr,
fs, io,
path::Path,
time::{Duration, SystemTime, UNIX_EPOCH},
};
pub const EMPTY: i16 = libc::EMPTY;
pub const RUN_LVL: i16 = libc::RUN_LVL;
pub const BOOT_TIME: i16 = libc::BOOT_TIME;
pub const NEW_TIME: i16 = libc::NEW_TIME;
pub const OLD_TIME: i16 = libc::OLD_TIME;
pub const INIT_PROCESS: i16 = libc::INIT_PROCESS;
pub const LOGIN_PROCESS: i16 = libc::LOGIN_PROCESS;
pub const USER_PROCESS: i16 = libc::USER_PROCESS;
pub const DEAD_PROCESS: i16 = libc::DEAD_PROCESS;
#[derive(Debug, Clone)]
pub struct UtmpEntry {
pub ut_type: i16,
pub pid: i32,
pub line: String,
pub id: String,
pub user: String,
pub host: String,
pub login_time: SystemTime,
}
pub const DEFAULT_UTMP_PATH: &str = "/var/run/utmp";
pub fn read(path: impl AsRef<Path>) -> io::Result<Vec<UtmpEntry>> {
let data = fs::read(path)?;
Ok(parse(&data))
}
pub fn parse(data: &[u8]) -> Vec<UtmpEntry> {
let stride = std::mem::size_of::<libc::utmpx>();
data.chunks_exact(stride).filter_map(parse_entry).collect()
}
fn parse_entry(buf: &[u8]) -> Option<UtmpEntry> {
let utx: libc::utmpx =
unsafe { std::ptr::read_unaligned(buf.as_ptr().cast()) };
if utx.ut_type == EMPTY {
return None;
}
Some(UtmpEntry {
ut_type: utx.ut_type,
pid: utx.ut_pid,
line: c_array_to_string(&utx.ut_line),
id: c_array_to_string(&utx.ut_id),
user: c_array_to_string(&utx.ut_user),
host: c_array_to_string(&utx.ut_host),
login_time: UNIX_EPOCH
+ Duration::new(
utx.ut_tv.tv_sec as u64,
utx.ut_tv.tv_usec as u32 * 1000,
),
})
}
fn c_array_to_string(buf: &[libc::c_char]) -> String {
let bytes: &[u8] =
unsafe { std::slice::from_raw_parts(buf.as_ptr().cast(), buf.len()) };
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
CStr::from_bytes_with_nul(&[&bytes[..end], &[0]].concat())
.ok()
.and_then(|s| s.to_str().ok().map(str::to_owned))
.unwrap_or_default()
}
pub fn count_user_processes(entries: &[UtmpEntry]) -> usize {
entries.iter().filter(|e| e.ut_type == USER_PROCESS).count()
}