utwt/
entry.rs

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/// Parsed utmp entry.
10#[derive(Clone, Debug, Eq, PartialEq)]
11#[non_exhaustive]
12pub enum UtmpEntry {
13    /// Record does not contain valid info
14    Empty,
15    /// Change in system run-level (see `init(8)`)
16    RunLevel {
17        /// Kernel version
18        kernel_version: String,
19        /// Time entry was made
20        time_in_micros: i64,
21    },
22    /// Time of system boot
23    BootTime {
24        /// Kernel version
25        kernel_version: String,
26        /// Time entry was made
27        time_in_micros: i64,
28    },
29    /// Time of system shutdown
30    ShutdownTime {
31        /// Kernel version
32        kernel_version: String,
33        /// Time entry was made
34        time_in_micros: i64,
35    },
36    /// Time after system clock change
37    NewTime(i64),
38    /// Time before system clock change
39    OldTime(i64),
40    /// Process spawned by `init(8)`
41    InitProcess {
42        /// PID of the init process
43        pid: pid_t,
44        /// Time entry was made
45        time_in_micros: i64,
46    },
47    /// Session leader process for user login
48    LoginProcess {
49        /// PID of the login process
50        pid: pid_t,
51        /// Time entry was made
52        time_in_micros: i64,
53    },
54    /// Normal process
55    UserProcess {
56        /// PID of login process
57        pid: pid_t,
58        /// Device name of tty
59        line: String,
60        /// Username
61        user: String,
62        /// Hostname for remote login
63        host: String,
64        /// Session ID (`getsid(2)`)
65        session: pid_t,
66        /// Time entry was made
67        time_in_micros: i64,
68        // TODO: Figure out the correct byte order to parse the address
69        // address: IpAddr,
70    },
71    /// Terminated process
72    DeadProcess {
73        /// PID of the terminated process
74        pid: pid_t,
75        /// Device name of tty
76        line: String,
77        /// Time entry was made
78        time_in_micros: i64,
79    },
80    /// Not implemented
81    #[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            // This is safe because we manually located the first zero byte above.
192            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}