pub extern crate time;
use std::ffi::CString;
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::ptr;
use std::sync::{Mutex, MutexGuard};
#[cfg(feature = "feat_systemd_logind")]
use crate::features::systemd_logind;
pub use self::ut::*;
#[cfg_attr(target_env = "musl", allow(deprecated))]
pub use libc::endutxent;
#[cfg_attr(target_env = "musl", allow(deprecated))]
pub use libc::getutxent;
#[cfg_attr(target_env = "musl", allow(deprecated))]
pub use libc::setutxent;
use libc::utmpx;
#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "netbsd"))]
#[cfg_attr(target_env = "musl", allow(deprecated))]
pub use libc::utmpxname;
#[cfg(target_os = "freebsd")]
pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
0
}
use crate::*;
macro_rules! chars2string {
($arr:expr) => {
$arr.iter()
.take_while(|i| **i > 0)
.map(|&i| i as u8 as char)
.collect::<String>()
};
}
#[cfg(target_os = "linux")]
mod ut {
pub static DEFAULT_FILE: &str = "/var/run/utmp";
#[cfg(not(target_env = "musl"))]
pub use libc::__UT_HOSTSIZE as UT_HOSTSIZE;
#[cfg(target_env = "musl")]
pub use libc::UT_HOSTSIZE;
#[cfg(not(target_env = "musl"))]
pub use libc::__UT_LINESIZE as UT_LINESIZE;
#[cfg(target_env = "musl")]
pub use libc::UT_LINESIZE;
#[cfg(not(target_env = "musl"))]
pub use libc::__UT_NAMESIZE as UT_NAMESIZE;
#[cfg(target_env = "musl")]
pub use libc::UT_NAMESIZE;
pub const UT_IDSIZE: usize = 4;
pub use libc::ACCOUNTING;
pub use libc::BOOT_TIME;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::RUN_LVL;
pub use libc::USER_PROCESS;
}
#[cfg(target_vendor = "apple")]
mod ut {
pub static DEFAULT_FILE: &str = "/var/run/utmpx";
pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
pub use libc::_UTX_IDSIZE as UT_IDSIZE;
pub use libc::_UTX_LINESIZE as UT_LINESIZE;
pub use libc::_UTX_USERSIZE as UT_NAMESIZE;
pub use libc::ACCOUNTING;
pub use libc::BOOT_TIME;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::RUN_LVL;
pub use libc::SHUTDOWN_TIME;
pub use libc::SIGNATURE;
pub use libc::USER_PROCESS;
}
#[cfg(target_os = "freebsd")]
mod ut {
pub static DEFAULT_FILE: &str = "";
pub const UT_LINESIZE: usize = 16;
pub const UT_NAMESIZE: usize = 32;
pub const UT_IDSIZE: usize = 8;
pub const UT_HOSTSIZE: usize = 128;
pub use libc::BOOT_TIME;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::SHUTDOWN_TIME;
pub use libc::USER_PROCESS;
}
#[cfg(target_os = "netbsd")]
mod ut {
pub static DEFAULT_FILE: &str = "/var/run/utmpx";
pub const ACCOUNTING: usize = 9;
pub const SHUTDOWN_TIME: usize = 11;
pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
pub use libc::_UTX_IDSIZE as UT_IDSIZE;
pub use libc::_UTX_LINESIZE as UT_LINESIZE;
pub use libc::_UTX_USERSIZE as UT_NAMESIZE;
pub use libc::ACCOUNTING;
pub use libc::DEAD_PROCESS;
pub use libc::EMPTY;
pub use libc::INIT_PROCESS;
pub use libc::LOGIN_PROCESS;
pub use libc::NEW_TIME;
pub use libc::OLD_TIME;
pub use libc::RUN_LVL;
pub use libc::SIGNATURE;
pub use libc::USER_PROCESS;
}
pub struct Utmpx {
inner: utmpx,
}
impl Utmpx {
pub fn record_type(&self) -> i16 {
self.inner.ut_type
}
pub fn pid(&self) -> i32 {
self.inner.ut_pid
}
pub fn terminal_suffix(&self) -> String {
chars2string!(self.inner.ut_id)
}
pub fn user(&self) -> String {
chars2string!(self.inner.ut_user)
}
pub fn host(&self) -> String {
chars2string!(self.inner.ut_host)
}
pub fn tty_device(&self) -> String {
chars2string!(self.inner.ut_line)
}
pub fn login_time(&self) -> time::OffsetDateTime {
#[allow(clippy::unnecessary_cast)]
let ts_nanos: i128 = (1_000_000_000_i64 * self.inner.ut_tv.tv_sec as i64
+ 1_000_i64 * self.inner.ut_tv.tv_usec as i64)
.into();
let local_offset =
time::OffsetDateTime::now_local().map_or_else(|_| time::UtcOffset::UTC, |v| v.offset());
time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)
.unwrap()
.to_offset(local_offset)
}
#[cfg(target_os = "linux")]
pub fn exit_status(&self) -> (i16, i16) {
(self.inner.ut_exit.e_termination, self.inner.ut_exit.e_exit)
}
#[cfg(not(target_os = "linux"))]
pub fn exit_status(&self) -> (i16, i16) {
(0, 0)
}
pub fn into_inner(self) -> utmpx {
self.inner
}
pub fn is_user_process(&self) -> bool {
!self.user().is_empty() && self.record_type() == USER_PROCESS
}
pub fn canon_host(&self) -> IOResult<String> {
let host = self.host();
let (hostname, display) = host.split_once(':').unwrap_or((&host, ""));
if !hostname.is_empty() {
use dns_lookup::{AddrInfoHints, getaddrinfo};
const AI_CANONNAME: i32 = 0x2;
let hints = AddrInfoHints {
flags: AI_CANONNAME,
..AddrInfoHints::default()
};
if let Ok(sockets) = getaddrinfo(Some(hostname), None, Some(hints)) {
let sockets = sockets.collect::<IOResult<Vec<_>>>()?;
for socket in sockets {
if let Some(ai_canonname) = socket.canonname {
return Ok(if display.is_empty() {
ai_canonname
} else {
format!("{ai_canonname}:{display}")
});
}
}
} else {
return Ok(hostname.to_string());
}
}
Ok(host)
}
pub fn iter_all_records() -> UtmpxIter {
#[cfg(feature = "feat_systemd_logind")]
{
UtmpxIter::new_systemd()
}
#[cfg(not(feature = "feat_systemd_logind"))]
{
let iter = UtmpxIter::new();
unsafe {
#[cfg_attr(target_env = "musl", allow(deprecated))]
setutxent();
}
iter
}
}
pub fn iter_all_records_from<P: AsRef<Path>>(path: P) -> UtmpxIter {
#[cfg(feature = "feat_systemd_logind")]
{
if path.as_ref() == Path::new(DEFAULT_FILE) {
return UtmpxIter::new_systemd();
}
}
let iter = UtmpxIter::new();
let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
unsafe {
#[cfg_attr(target_env = "musl", allow(deprecated))]
utmpxname(path.as_ptr());
#[cfg_attr(target_env = "musl", allow(deprecated))]
setutxent();
}
iter
}
}
static LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
pub struct UtmpxIter {
#[allow(dead_code)]
guard: MutexGuard<'static, ()>,
phantom: PhantomData<std::rc::Rc<()>>,
#[cfg(feature = "feat_systemd_logind")]
systemd_iter: Option<systemd_logind::SystemdUtmpxIter>,
}
impl UtmpxIter {
fn new() -> Self {
let guard = LOCK.lock().unwrap_or_else(|err| err.into_inner());
Self {
guard,
phantom: PhantomData,
#[cfg(feature = "feat_systemd_logind")]
systemd_iter: None,
}
}
#[cfg(feature = "feat_systemd_logind")]
fn new_systemd() -> Self {
let guard = LOCK.lock().unwrap_or_else(|err| err.into_inner());
let systemd_iter = match systemd_logind::SystemdUtmpxIter::new() {
Ok(iter) => iter,
Err(_) => {
systemd_logind::SystemdUtmpxIter::empty()
}
};
Self {
guard,
phantom: PhantomData,
systemd_iter: Some(systemd_iter),
}
}
}
pub enum UtmpxRecord {
Traditional(Box<Utmpx>),
#[cfg(feature = "feat_systemd_logind")]
Systemd(systemd_logind::SystemdUtmpxCompat),
}
impl UtmpxRecord {
pub fn record_type(&self) -> i16 {
match self {
Self::Traditional(utmpx) => utmpx.record_type(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.record_type(),
}
}
pub fn pid(&self) -> i32 {
match self {
Self::Traditional(utmpx) => utmpx.pid(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.pid(),
}
}
pub fn terminal_suffix(&self) -> String {
match self {
Self::Traditional(utmpx) => utmpx.terminal_suffix(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.terminal_suffix(),
}
}
pub fn user(&self) -> String {
match self {
Self::Traditional(utmpx) => utmpx.user(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.user(),
}
}
pub fn host(&self) -> String {
match self {
Self::Traditional(utmpx) => utmpx.host(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.host(),
}
}
pub fn tty_device(&self) -> String {
match self {
Self::Traditional(utmpx) => utmpx.tty_device(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.tty_device(),
}
}
pub fn login_time(&self) -> time::OffsetDateTime {
match self {
Self::Traditional(utmpx) => utmpx.login_time(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.login_time(),
}
}
pub fn exit_status(&self) -> (i16, i16) {
match self {
Self::Traditional(utmpx) => utmpx.exit_status(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.exit_status(),
}
}
pub fn is_user_process(&self) -> bool {
match self {
Self::Traditional(utmpx) => utmpx.is_user_process(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.is_user_process(),
}
}
pub fn canon_host(&self) -> IOResult<String> {
match self {
Self::Traditional(utmpx) => utmpx.canon_host(),
#[cfg(feature = "feat_systemd_logind")]
Self::Systemd(systemd) => systemd.canon_host(),
}
}
}
impl Iterator for UtmpxIter {
type Item = UtmpxRecord;
fn next(&mut self) -> Option<Self::Item> {
#[cfg(feature = "feat_systemd_logind")]
{
if let Some(ref mut systemd_iter) = self.systemd_iter {
return systemd_iter.next().map(UtmpxRecord::Systemd);
}
}
unsafe {
#[cfg_attr(target_env = "musl", allow(deprecated))]
let res = getutxent();
if res.is_null() {
None
} else {
Some(UtmpxRecord::Traditional(Box::new(Utmpx {
inner: ptr::read(res as *const _),
})))
}
}
}
}
impl Drop for UtmpxIter {
fn drop(&mut self) {
unsafe {
#[cfg_attr(target_env = "musl", allow(deprecated))]
endutxent();
}
}
}