1use libc::pid_t;
2use std::convert::TryFrom;
3use std::ffi::CStr;
4use std::os::raw::c_short;
5use thiserror::Error;
6use utwt_raw::x32::utmp as utmp32;
7use utwt_raw::x64::{timeval as timeval64, utmp as utmp64};
8
9#[derive(Clone, Debug, Eq, PartialEq)]
11#[non_exhaustive]
12pub enum UtmpEntry {
13 Empty,
15 RunLevel {
17 kernel_version: String,
19 time_in_micros: i64,
21 },
22 BootTime {
24 kernel_version: String,
26 time_in_micros: i64,
28 },
29 ShutdownTime {
31 kernel_version: String,
33 time_in_micros: i64,
35 },
36 NewTime(i64),
38 OldTime(i64),
40 InitProcess {
42 pid: pid_t,
44 time_in_micros: i64,
46 },
47 LoginProcess {
49 pid: pid_t,
51 time_in_micros: i64,
53 },
54 UserProcess {
56 pid: pid_t,
58 line: String,
60 user: String,
62 host: String,
64 session: pid_t,
66 time_in_micros: i64,
68 },
71 DeadProcess {
73 pid: pid_t,
75 line: String,
77 time_in_micros: i64,
79 },
80 #[non_exhaustive]
82 Accounting,
83}
84
85impl<'a> TryFrom<&'a utmp32> for UtmpEntry {
86 type Error = UtmpError;
87
88 fn try_from(from: &utmp32) -> Result<Self, UtmpError> {
89 UtmpEntry::try_from(&utmp64 {
90 ut_type: from.ut_type,
91 ut_pid: from.ut_pid,
92 ut_line: from.ut_line,
93 ut_id: from.ut_id,
94 ut_user: from.ut_user,
95 ut_host: from.ut_host,
96 ut_exit: from.ut_exit,
97 ut_session: i64::from(from.ut_session),
98 ut_tv: timeval64 {
99 tv_sec: i64::from(from.ut_tv.tv_sec),
100 tv_usec: i64::from(from.ut_tv.tv_usec),
101 },
102 ut_addr_v6: from.ut_addr_v6,
103 __unused: from.__unused,
104 })
105 }
106}
107
108impl<'a> TryFrom<&'a utmp64> for UtmpEntry {
109 type Error = UtmpError;
110
111 fn try_from(from: &utmp64) -> Result<Self, UtmpError> {
112 Ok(match from.ut_type {
113 utwt_raw::EMPTY => UtmpEntry::Empty,
114 utwt_raw::RUN_LVL => {
115 let kernel_version =
116 string_from_bytes(&from.ut_host).map_err(UtmpError::InvalidHost)?;
117 let time_in_micros = time_from_tv(from.ut_tv)?;
118 if from.ut_line[0] == b'~' && from.ut_user.starts_with(b"shutdown\0") {
119 UtmpEntry::ShutdownTime {
120 kernel_version,
121 time_in_micros,
122 }
123 } else {
124 UtmpEntry::RunLevel {
125 kernel_version,
126 time_in_micros,
127 }
128 }
129 }
130 utwt_raw::BOOT_TIME => UtmpEntry::BootTime {
131 kernel_version: string_from_bytes(&from.ut_host).map_err(UtmpError::InvalidHost)?,
132 time_in_micros: time_from_tv(from.ut_tv)?,
133 },
134 utwt_raw::NEW_TIME => UtmpEntry::NewTime(time_from_tv(from.ut_tv)?),
135 utwt_raw::OLD_TIME => UtmpEntry::OldTime(time_from_tv(from.ut_tv)?),
136 utwt_raw::INIT_PROCESS => UtmpEntry::InitProcess {
137 pid: from.ut_pid,
138 time_in_micros: time_from_tv(from.ut_tv)?,
139 },
140 utwt_raw::LOGIN_PROCESS => UtmpEntry::LoginProcess {
141 pid: from.ut_pid,
142 time_in_micros: time_from_tv(from.ut_tv)?,
143 },
144 utwt_raw::USER_PROCESS => UtmpEntry::UserProcess {
145 pid: from.ut_pid,
146 line: string_from_bytes(&from.ut_line).map_err(UtmpError::InvalidLine)?,
147 user: string_from_bytes(&from.ut_user).map_err(UtmpError::InvalidUser)?,
148 host: string_from_bytes(&from.ut_host).map_err(UtmpError::InvalidHost)?,
149 session: from.ut_session as pid_t,
150 time_in_micros: time_from_tv(from.ut_tv)?,
151 },
152 utwt_raw::DEAD_PROCESS => UtmpEntry::DeadProcess {
153 pid: from.ut_pid,
154 line: string_from_bytes(&from.ut_line).map_err(UtmpError::InvalidLine)?,
155 time_in_micros: time_from_tv(from.ut_tv)?,
156 },
157 utwt_raw::ACCOUNTING => UtmpEntry::Accounting,
158 _ => return Err(UtmpError::UnknownType(from.ut_type)),
159 })
160 }
161}
162
163#[derive(Debug, Error)]
164#[non_exhaustive]
165pub enum UtmpError {
166 #[error("unknown type {0}")]
167 UnknownType(c_short),
168 #[error("invalid time value {0:?}")]
169 InvalidTime(timeval64),
170 #[error("invalid line value `{0:?}`")]
171 InvalidLine(Box<[u8]>),
172 #[error("invalid user value `{0:?}`")]
173 InvalidUser(Box<[u8]>),
174 #[error("invalid host value `{0:?}`")]
175 InvalidHost(Box<[u8]>),
176}
177
178fn time_from_tv(tv: timeval64) -> Result<i64, UtmpError> {
179 let timeval64 { tv_sec, tv_usec } = tv;
180 if tv_usec < 0 {
181 return Err(UtmpError::InvalidTime(tv));
182 }
183 Ok(tv_sec * 1_000_000 + tv_usec)
184}
185
186fn string_from_bytes(bytes: &[u8]) -> Result<String, Box<[u8]>> {
187 bytes
188 .iter()
189 .position(|b| *b == 0)
190 .and_then(|pos| {
191 let cstr = unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..=pos]) };
193 Some(cstr.to_str().ok()?.to_string())
194 })
195 .ok_or_else(|| bytes.to_owned().into_boxed_slice())
196}