1pub extern crate time;
35
36use std::ffi::CString;
37use std::io::Result as IOResult;
38use std::marker::PhantomData;
39use std::os::unix::ffi::OsStrExt;
40use std::path::Path;
41use std::ptr;
42use std::sync::{Mutex, MutexGuard};
43
44#[cfg(feature = "feat_systemd_logind")]
45use crate::features::systemd_logind;
46
47pub use self::ut::*;
48
49#[cfg_attr(target_env = "musl", allow(deprecated))]
53pub use libc::endutxent;
54#[cfg_attr(target_env = "musl", allow(deprecated))]
55pub use libc::getutxent;
56#[cfg_attr(target_env = "musl", allow(deprecated))]
57pub use libc::setutxent;
58use libc::utmpx;
59#[cfg(any(
60 target_vendor = "apple",
61 target_os = "linux",
62 target_os = "netbsd",
63 target_os = "cygwin"
64))]
65#[cfg_attr(target_env = "musl", allow(deprecated))]
66pub use libc::utmpxname;
67
68#[cfg(target_os = "freebsd")]
71pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
72 0
73}
74
75use crate::libc; macro_rules! chars2string {
79 ($arr:expr) => {
80 $arr.iter()
81 .take_while(|i| **i > 0)
82 .map(|&i| i as u8 as char)
83 .collect::<String>()
84 };
85}
86
87#[cfg(target_os = "linux")]
88mod ut {
89 pub static DEFAULT_FILE: &str = "/var/run/utmp";
90
91 #[cfg(not(target_env = "musl"))]
92 pub use libc::__UT_HOSTSIZE as UT_HOSTSIZE;
93 #[cfg(target_env = "musl")]
94 pub use libc::UT_HOSTSIZE;
95
96 #[cfg(not(target_env = "musl"))]
97 pub use libc::__UT_LINESIZE as UT_LINESIZE;
98 #[cfg(target_env = "musl")]
99 pub use libc::UT_LINESIZE;
100
101 #[cfg(not(target_env = "musl"))]
102 pub use libc::__UT_NAMESIZE as UT_NAMESIZE;
103 #[cfg(target_env = "musl")]
104 pub use libc::UT_NAMESIZE;
105
106 pub const UT_IDSIZE: usize = 4;
107
108 pub use libc::ACCOUNTING;
109 pub use libc::BOOT_TIME;
110 pub use libc::DEAD_PROCESS;
111 pub use libc::EMPTY;
112 pub use libc::INIT_PROCESS;
113 pub use libc::LOGIN_PROCESS;
114 pub use libc::NEW_TIME;
115 pub use libc::OLD_TIME;
116 pub use libc::RUN_LVL;
117 pub use libc::USER_PROCESS;
118}
119
120#[cfg(target_vendor = "apple")]
121mod ut {
122 pub static DEFAULT_FILE: &str = "/var/run/utmpx";
123
124 pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
125 pub use libc::_UTX_IDSIZE as UT_IDSIZE;
126 pub use libc::_UTX_LINESIZE as UT_LINESIZE;
127 pub use libc::_UTX_USERSIZE as UT_NAMESIZE;
128
129 pub use libc::ACCOUNTING;
130 pub use libc::BOOT_TIME;
131 pub use libc::DEAD_PROCESS;
132 pub use libc::EMPTY;
133 pub use libc::INIT_PROCESS;
134 pub use libc::LOGIN_PROCESS;
135 pub use libc::NEW_TIME;
136 pub use libc::OLD_TIME;
137 pub use libc::RUN_LVL;
138 pub use libc::SHUTDOWN_TIME;
139 pub use libc::SIGNATURE;
140 pub use libc::USER_PROCESS;
141}
142
143#[cfg(target_os = "freebsd")]
144mod ut {
145 pub static DEFAULT_FILE: &str = "";
146
147 pub const UT_LINESIZE: usize = 16;
148 pub const UT_NAMESIZE: usize = 32;
149 pub const UT_IDSIZE: usize = 8;
150 pub const UT_HOSTSIZE: usize = 128;
151
152 pub use libc::BOOT_TIME;
153 pub use libc::DEAD_PROCESS;
154 pub use libc::EMPTY;
155 pub use libc::INIT_PROCESS;
156 pub use libc::LOGIN_PROCESS;
157 pub use libc::NEW_TIME;
158 pub use libc::OLD_TIME;
159 pub use libc::SHUTDOWN_TIME;
160 pub use libc::USER_PROCESS;
161}
162
163#[cfg(target_os = "netbsd")]
164mod ut {
165 pub static DEFAULT_FILE: &str = "/var/run/utmpx";
166
167 pub const SHUTDOWN_TIME: usize = 11;
168
169 pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
170 pub use libc::_UTX_IDSIZE as UT_IDSIZE;
171 pub use libc::_UTX_LINESIZE as UT_LINESIZE;
172 pub use libc::_UTX_USERSIZE as UT_NAMESIZE;
173
174 pub use libc::ACCOUNTING;
175 pub const BOOT_TIME: i16 = libc::BOOT_TIME as i16;
176 pub const DEAD_PROCESS: i16 = libc::DEAD_PROCESS as i16;
177 pub const EMPTY: i16 = libc::EMPTY as i16;
178 pub const INIT_PROCESS: i16 = libc::INIT_PROCESS as i16;
179 pub const LOGIN_PROCESS: i16 = libc::LOGIN_PROCESS as i16;
180 pub const NEW_TIME: i16 = libc::NEW_TIME as i16;
181 pub const OLD_TIME: i16 = libc::OLD_TIME as i16;
182 pub const RUN_LVL: i16 = libc::RUN_LVL as i16;
183 pub const SIGNATURE: i16 = libc::SIGNATURE as i16;
184 pub const USER_PROCESS: i16 = libc::USER_PROCESS as i16;
185}
186
187#[cfg(target_os = "cygwin")]
188mod ut {
189 pub static DEFAULT_FILE: &str = "";
190
191 pub use libc::UT_HOSTSIZE;
192 pub use libc::UT_IDLEN;
193 pub use libc::UT_LINESIZE;
194 pub use libc::UT_NAMESIZE;
195
196 pub use libc::BOOT_TIME;
197 pub use libc::DEAD_PROCESS;
198 pub use libc::INIT_PROCESS;
199 pub use libc::LOGIN_PROCESS;
200 pub use libc::NEW_TIME;
201 pub use libc::OLD_TIME;
202 pub use libc::RUN_LVL;
203 pub use libc::USER_PROCESS;
204}
205
206pub struct Utmpx {
208 inner: utmpx,
209}
210
211#[cfg(target_os = "netbsd")]
212impl Utmpx {
213 fn ut_type(&self) -> i16 {
214 self.inner.ut_type as i16
215 }
216 fn ut_user(&self) -> String {
217 chars2string!(self.inner.ut_name)
218 }
219}
220
221#[cfg(not(target_os = "netbsd"))]
222impl Utmpx {
223 fn ut_type(&self) -> i16 {
224 self.inner.ut_type
225 }
226 fn ut_user(&self) -> String {
227 chars2string!(self.inner.ut_user)
228 }
229}
230
231impl Utmpx {
232 pub fn record_type(&self) -> i16 {
234 self.ut_type()
235 }
236 pub fn pid(&self) -> i32 {
238 self.inner.ut_pid
239 }
240 pub fn terminal_suffix(&self) -> String {
242 chars2string!(self.inner.ut_id)
243 }
244 pub fn user(&self) -> String {
246 self.ut_user()
247 }
248 pub fn host(&self) -> String {
250 chars2string!(self.inner.ut_host)
251 }
252 pub fn tty_device(&self) -> String {
254 chars2string!(self.inner.ut_line)
255 }
256 pub fn login_time(&self) -> time::OffsetDateTime {
258 #[allow(clippy::unnecessary_cast)]
259 let ts_nanos: i128 = (1_000_000_000_i64 * self.inner.ut_tv.tv_sec as i64
260 + 1_000_i64 * self.inner.ut_tv.tv_usec as i64)
261 .into();
262 let local_offset = time::OffsetDateTime::now_local()
263 .map_or_else(|_| time::UtcOffset::UTC, time::OffsetDateTime::offset);
264 time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)
265 .unwrap()
266 .to_offset(local_offset)
267 }
268 #[cfg(target_os = "linux")]
272 pub fn exit_status(&self) -> (i16, i16) {
273 (self.inner.ut_exit.e_termination, self.inner.ut_exit.e_exit)
274 }
275 #[cfg(not(target_os = "linux"))]
279 pub fn exit_status(&self) -> (i16, i16) {
280 (0, 0)
281 }
282 pub fn into_inner(self) -> utmpx {
284 self.inner
285 }
286 pub fn is_user_process(&self) -> bool {
288 !self.user().is_empty() && self.record_type() == USER_PROCESS
289 }
290
291 pub fn canon_host(&self) -> IOResult<String> {
293 let host = self.host();
294
295 let (hostname, display) = host.split_once(':').unwrap_or((&host, ""));
296
297 if !hostname.is_empty() {
298 use dns_lookup::{AddrInfoHints, getaddrinfo};
299
300 const AI_CANONNAME: i32 = 0x2;
301 let hints = AddrInfoHints {
302 flags: AI_CANONNAME,
303 ..AddrInfoHints::default()
304 };
305 if let Ok(sockets) = getaddrinfo(Some(hostname), None, Some(hints)) {
306 let sockets = sockets.collect::<IOResult<Vec<_>>>()?;
307 for socket in sockets {
308 if let Some(ai_canonname) = socket.canonname {
309 return Ok(if display.is_empty() {
310 ai_canonname
311 } else {
312 format!("{ai_canonname}:{display}")
313 });
314 }
315 }
316 } else {
317 return Ok(hostname.to_string());
319 }
320 }
321
322 Ok(host)
323 }
324
325 pub fn iter_all_records() -> UtmpxIter {
336 #[cfg(feature = "feat_systemd_logind")]
337 {
338 UtmpxIter::new_systemd()
340 }
341
342 #[cfg(not(feature = "feat_systemd_logind"))]
343 {
344 let iter = UtmpxIter::new();
345 unsafe {
346 #[cfg_attr(target_env = "musl", allow(deprecated))]
350 setutxent();
351 }
352 iter
353 }
354 }
355
356 pub fn iter_all_records_from<P: AsRef<Path>>(path: P) -> UtmpxIter {
368 #[cfg(feature = "feat_systemd_logind")]
369 {
370 if path.as_ref() == Path::new(DEFAULT_FILE) {
372 return UtmpxIter::new_systemd();
373 }
374 }
375
376 let iter = UtmpxIter::new();
377 let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
378 unsafe {
379 #[cfg_attr(target_env = "musl", allow(deprecated))]
388 utmpxname(path.as_ptr());
389 #[cfg_attr(target_env = "musl", allow(deprecated))]
390 setutxent();
391 }
392 iter
393 }
394}
395
396static LOCK: Mutex<()> = Mutex::new(());
404
405pub struct UtmpxIter {
407 #[allow(dead_code)]
408 guard: MutexGuard<'static, ()>,
409 phantom: PhantomData<std::rc::Rc<()>>,
412 #[cfg(feature = "feat_systemd_logind")]
413 systemd_iter: Option<systemd_logind::SystemdUtmpxIter>,
414}
415
416impl UtmpxIter {
417 fn new() -> Self {
418 let guard = LOCK
420 .lock()
421 .unwrap_or_else(std::sync::PoisonError::into_inner);
422 Self {
423 guard,
424 phantom: PhantomData,
425 #[cfg(feature = "feat_systemd_logind")]
426 systemd_iter: None,
427 }
428 }
429
430 #[cfg(feature = "feat_systemd_logind")]
431 fn new_systemd() -> Self {
432 let guard = LOCK
434 .lock()
435 .unwrap_or_else(std::sync::PoisonError::into_inner);
436 let systemd_iter = match systemd_logind::SystemdUtmpxIter::new() {
437 Ok(iter) => iter,
438 Err(_) => {
439 systemd_logind::SystemdUtmpxIter::empty()
442 }
443 };
444 Self {
445 guard,
446 phantom: PhantomData,
447 systemd_iter: Some(systemd_iter),
448 }
449 }
450}
451
452pub enum UtmpxRecord {
454 Traditional(Box<Utmpx>),
455 #[cfg(feature = "feat_systemd_logind")]
456 Systemd(systemd_logind::SystemdUtmpxCompat),
457}
458
459impl UtmpxRecord {
460 pub fn record_type(&self) -> i16 {
462 match self {
463 Self::Traditional(utmpx) => utmpx.record_type(),
464 #[cfg(feature = "feat_systemd_logind")]
465 Self::Systemd(systemd) => systemd.record_type(),
466 }
467 }
468
469 pub fn pid(&self) -> i32 {
471 match self {
472 Self::Traditional(utmpx) => utmpx.pid(),
473 #[cfg(feature = "feat_systemd_logind")]
474 Self::Systemd(systemd) => systemd.pid(),
475 }
476 }
477
478 pub fn terminal_suffix(&self) -> String {
480 match self {
481 Self::Traditional(utmpx) => utmpx.terminal_suffix(),
482 #[cfg(feature = "feat_systemd_logind")]
483 Self::Systemd(systemd) => systemd.terminal_suffix(),
484 }
485 }
486
487 pub fn user(&self) -> String {
489 match self {
490 Self::Traditional(utmpx) => utmpx.user(),
491 #[cfg(feature = "feat_systemd_logind")]
492 Self::Systemd(systemd) => systemd.user(),
493 }
494 }
495
496 pub fn host(&self) -> String {
498 match self {
499 Self::Traditional(utmpx) => utmpx.host(),
500 #[cfg(feature = "feat_systemd_logind")]
501 Self::Systemd(systemd) => systemd.host(),
502 }
503 }
504
505 pub fn tty_device(&self) -> String {
507 match self {
508 Self::Traditional(utmpx) => utmpx.tty_device(),
509 #[cfg(feature = "feat_systemd_logind")]
510 Self::Systemd(systemd) => systemd.tty_device(),
511 }
512 }
513
514 pub fn login_time(&self) -> time::OffsetDateTime {
516 match self {
517 Self::Traditional(utmpx) => utmpx.login_time(),
518 #[cfg(feature = "feat_systemd_logind")]
519 Self::Systemd(systemd) => systemd.login_time(),
520 }
521 }
522
523 pub fn exit_status(&self) -> (i16, i16) {
527 match self {
528 Self::Traditional(utmpx) => utmpx.exit_status(),
529 #[cfg(feature = "feat_systemd_logind")]
530 Self::Systemd(systemd) => systemd.exit_status(),
531 }
532 }
533
534 pub fn is_user_process(&self) -> bool {
536 match self {
537 Self::Traditional(utmpx) => utmpx.is_user_process(),
538 #[cfg(feature = "feat_systemd_logind")]
539 Self::Systemd(systemd) => systemd.is_user_process(),
540 }
541 }
542
543 pub fn canon_host(&self) -> IOResult<String> {
545 match self {
546 Self::Traditional(utmpx) => utmpx.canon_host(),
547 #[cfg(feature = "feat_systemd_logind")]
548 Self::Systemd(systemd) => Ok(systemd.canon_host()),
549 }
550 }
551}
552
553impl Iterator for UtmpxIter {
554 type Item = UtmpxRecord;
555 fn next(&mut self) -> Option<Self::Item> {
556 #[cfg(feature = "feat_systemd_logind")]
557 {
558 if let Some(ref mut systemd_iter) = self.systemd_iter {
559 return systemd_iter.next().map(UtmpxRecord::Systemd);
561 }
562 }
563
564 unsafe {
566 #[cfg_attr(target_env = "musl", allow(deprecated))]
567 let res = getutxent();
568 if res.is_null() {
569 None
570 } else {
571 Some(UtmpxRecord::Traditional(Box::new(Utmpx {
576 inner: ptr::read(res.cast_const()),
577 })))
578 }
579 }
580 }
581}
582
583impl Drop for UtmpxIter {
584 fn drop(&mut self) {
585 unsafe {
586 #[cfg_attr(target_env = "musl", allow(deprecated))]
587 endutxent();
588 }
589 }
590}