use thiserror::Error;
use time::OffsetDateTime;
use utmp_rs::UtmpEntry;
#[derive(Debug)]
pub enum Exit {
Logout(OffsetDateTime),
Crash(OffsetDateTime),
Reboot(OffsetDateTime),
StillLoggedIn,
}
#[derive(Debug)]
pub struct Enter {
pub user: String,
pub host: String,
pub line: String,
pub login_time: OffsetDateTime,
pub exit: Exit,
}
#[derive(Error, Debug)]
pub enum LastError {
#[error(transparent)]
UtmpParse(#[from] utmp_rs::ParseError),
}
fn find_accompanying_logout(entries: &[UtmpEntry], target_line: &str) -> Option<Exit> {
entries.iter().rev().find_map(|x| match x {
UtmpEntry::DeadProcess { line, time, .. } if line == target_line => {
Some(Exit::Logout(*time))
}
UtmpEntry::ShutdownTime { time, .. } => Some(Exit::Reboot(*time)),
UtmpEntry::BootTime { time, .. } => Some(Exit::Crash(*time)),
_ => None,
})
}
pub fn get_logins(file: &str) -> Result<Vec<Enter>, LastError> {
let mut entries = utmp_rs::parse_from_path(file)?;
entries.reverse();
Ok(entries
.iter()
.enumerate()
.filter_map(|(i, x)| match x {
UtmpEntry::UserProcess {
user,
host,
time,
line,
..
} => {
let exit = find_accompanying_logout(&entries[..i], &line[..])
.unwrap_or(Exit::StillLoggedIn);
Some(Enter {
user: user.to_owned(),
host: host.to_owned(),
line: line.to_owned(),
login_time: *time,
exit,
})
}
_ => None,
})
.collect())
}