1use std::{
9 ffi::CStr,
10 fs, io,
11 path::Path,
12 time::{Duration, SystemTime, UNIX_EPOCH},
13};
14
15pub const EMPTY: i16 = libc::EMPTY;
16pub const RUN_LVL: i16 = libc::RUN_LVL;
17pub const BOOT_TIME: i16 = libc::BOOT_TIME;
18pub const NEW_TIME: i16 = libc::NEW_TIME;
19pub const OLD_TIME: i16 = libc::OLD_TIME;
20pub const INIT_PROCESS: i16 = libc::INIT_PROCESS;
21pub const LOGIN_PROCESS: i16 = libc::LOGIN_PROCESS;
22pub const USER_PROCESS: i16 = libc::USER_PROCESS;
23pub const DEAD_PROCESS: i16 = libc::DEAD_PROCESS;
24
25#[derive(Debug, Clone)]
27pub struct UtmpEntry {
28 pub ut_type: i16,
29 pub pid: i32,
30 pub line: String,
32 pub id: String,
34 pub user: String,
36 pub host: String,
38 pub login_time: SystemTime,
40}
41
42pub const DEFAULT_UTMP_PATH: &str = "/var/run/utmp";
44
45pub fn read(path: impl AsRef<Path>) -> io::Result<Vec<UtmpEntry>> {
47 let data = fs::read(path)?;
48 Ok(parse(&data))
49}
50
51pub fn parse(data: &[u8]) -> Vec<UtmpEntry> {
54 let stride = std::mem::size_of::<libc::utmpx>();
55 data.chunks_exact(stride).filter_map(parse_entry).collect()
56}
57
58fn parse_entry(buf: &[u8]) -> Option<UtmpEntry> {
59 let utx: libc::utmpx =
62 unsafe { std::ptr::read_unaligned(buf.as_ptr().cast()) };
63
64 if utx.ut_type == EMPTY {
65 return None;
66 }
67
68 Some(UtmpEntry {
69 ut_type: utx.ut_type,
70 pid: utx.ut_pid,
71 line: c_array_to_string(&utx.ut_line),
72 id: c_array_to_string(&utx.ut_id),
73 user: c_array_to_string(&utx.ut_user),
74 host: c_array_to_string(&utx.ut_host),
75 login_time: UNIX_EPOCH
76 + Duration::new(
77 utx.ut_tv.tv_sec as u64,
78 utx.ut_tv.tv_usec as u32 * 1000,
79 ),
80 })
81}
82
83fn c_array_to_string(buf: &[libc::c_char]) -> String {
84 let bytes: &[u8] =
86 unsafe { std::slice::from_raw_parts(buf.as_ptr().cast(), buf.len()) };
87 let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
88 CStr::from_bytes_with_nul(&[&bytes[..end], &[0]].concat())
89 .ok()
90 .and_then(|s| s.to_str().ok().map(str::to_owned))
91 .unwrap_or_default()
92}
93
94pub fn count_user_processes(entries: &[UtmpEntry]) -> usize {
97 entries.iter().filter(|e| e.ut_type == USER_PROCESS).count()
98}