1use std::ffi::CStr;
2use std::os::raw::c_short;
3use thiserror::Error;
4use time::OffsetDateTime;
5use utmp_classic_raw::x32::utmp as utmp32;
6use utmp_classic_raw::x64::{timeval as timeval64, utmp as utmp64};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
10#[non_exhaustive]
11pub enum UtmpEntry {
12 UTMP {
13 line: String,
15 user: String,
17 host: String,
19 time: OffsetDateTime,
21 },
23}
24
25impl<'a> TryFrom<&'a utmp32> for UtmpEntry {
26 type Error = UtmpError;
27
28 fn try_from(from: &utmp32) -> Result<Self, UtmpError> {
29 UtmpEntry::try_from(&utmp64 {
30 ut_line: from.ut_line,
31 ut_user: from.ut_user,
32 ut_host: from.ut_host,
33 ut_tv: timeval64 {
34 tv_sec: i64::from(from.ut_tv.tv_sec),
35 tv_usec: i64::from(from.ut_tv.tv_usec),
36 },
37 })
38 }
39}
40
41impl<'a> TryFrom<&'a utmp64> for UtmpEntry {
42 type Error = UtmpError;
43
44 fn try_from(from: &utmp64) -> Result<Self, UtmpError> {
45 Ok(UtmpEntry::UTMP {
46 line: string_from_bytes(&from.ut_line).map_err(UtmpError::InvalidLine)?,
47 user: string_from_bytes(&from.ut_user).map_err(UtmpError::InvalidUser)?,
48 host: string_from_bytes(&from.ut_host).map_err(UtmpError::InvalidHost)?,
49 time: time_from_tv(from.ut_tv)?,
50 })
51 }
52}
53
54#[derive(Debug, Error)]
55#[non_exhaustive]
56pub enum UtmpError {
57 #[error("unknown type {0}")]
58 UnknownType(c_short),
59 #[error("invalid time value {0:?}")]
60 InvalidTime(timeval64),
61 #[error("invalid line value `{0:?}`")]
62 InvalidLine(Box<[u8]>),
63 #[error("invalid user value `{0:?}`")]
64 InvalidUser(Box<[u8]>),
65 #[error("invalid host value `{0:?}`")]
66 InvalidHost(Box<[u8]>),
67}
68
69fn time_from_tv(tv: timeval64) -> Result<OffsetDateTime, UtmpError> {
70 let timeval64 { tv_sec, tv_usec } = tv;
71 if tv_usec < 0 {
72 return Err(UtmpError::InvalidTime(tv));
73 }
74 let usec = i128::from(tv_sec) * 1_000_000 + i128::from(tv_usec);
75 OffsetDateTime::from_unix_timestamp_nanos(usec * 1000).map_err(|_| UtmpError::InvalidTime(tv))
76}
77
78fn string_from_bytes(bytes: &[u8]) -> Result<String, Box<[u8]>> {
79 bytes
80 .iter()
81 .position(|b| *b == 0)
82 .and_then(|pos| {
83 let cstr = unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..=pos]) };
85 Some(cstr.to_str().ok()?.to_string())
86 })
87 .ok_or_else(|| bytes.to_owned().into_boxed_slice())
88}