use std::{
collections::{hash_set, HashSet},
fs::{self, File},
io::{self, BufReader, Read},
mem,
path::Path,
slice,
};
use bstr::{BStr, BString, ByteSlice};
use libc::{c_char, utmp};
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
use libc::{c_short, exit_status as ExitStatus};
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
use libc::{endutent, getutent, setutent};
use time::OffsetDateTime as DataTime;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
use super::utmpx::UtmpxKind;
use super::Time;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Utmp {
user: BString,
line: BString,
time: Time,
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
host: BString,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
id: BString,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
pid: c_short,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_type: UtmpxKind,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
exit: ExitStatus,
}
impl Utmp {
#[inline]
pub fn from_c_utmp(utm: utmp) -> Self {
Self::from(utm)
}
#[inline]
pub fn user(&self) -> &BStr {
self.user.as_bstr()
}
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
#[inline]
pub fn host(&self) -> &BStr {
self.host.as_bstr()
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
#[inline]
pub fn id(&self) -> &BStr {
self.id.as_bstr()
}
#[inline]
pub fn device_name(&self) -> &BStr {
self.line.as_bstr()
}
#[inline]
pub const fn time(&self) -> Time {
self.time
}
#[inline]
pub fn login_time(&self) -> DataTime {
DataTime::from_unix_timestamp(self.time)
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
#[inline]
pub const fn pid(&self) -> c_short {
self.pid
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
#[inline]
pub const fn entry_type(&self) -> UtmpxKind {
self.ut_type
}
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
#[inline]
pub const fn exit_status(&self) -> ExitStatus {
self.exit
}
}
impl From<utmp> for Utmp {
#[inline]
fn from(utm: utmp) -> Self {
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
let user = {
let cstr: Vec<_> =
utm.ut_name.iter().map(|cc| *cc as u8).filter(|cc| cc != &b'\0').collect();
BString::from(cstr)
};
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let user = {
let cstr: Vec<_> =
utm.ut_user.iter().map(|cc| *cc as u8).filter(|cc| cc != &b'\0').collect();
BString::from(cstr)
};
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
let host = {
let cstr: Vec<_> =
utm.ut_host.iter().map(|cc| *cc as u8).filter(|cc| cc != &b'\0').collect();
BString::from(cstr)
};
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let id = {
let cstr: Vec<_> =
utm.ut_id.iter().map(|cc| *cc as u8).filter(|cc| cc != &b'\0').collect();
BString::from(cstr)
};
let line = {
let cstr: Vec<_> =
utm.ut_line.iter().map(|cc| *cc as u8).filter(|cc| cc != &b'\0').collect();
BString::from(cstr)
};
let time = utm.ut_time;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let ut_type = match UtmpxKind::try_from(utm.ut_type) {
Ok(ut) => ut,
Err(err) => panic!("{}", err),
};
Utmp {
user,
line,
time,
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
host,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
id,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
pid: utm.ut_pid,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_type,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
exit: utm.ut_exit,
}
}
}
#[derive(Debug)]
pub struct UtmpSet(HashSet<Utmp>);
impl UtmpSet {
#[cfg_attr(feature = "inline-more", inline)]
pub fn from_file(path: impl AsRef<Path>) -> io::Result<Self> {
let struct_size = mem::size_of::<utmp>();
let num_bytes = fs::metadata(&path)?.len() as usize;
let num_structs = num_bytes / struct_size;
let mut reader = BufReader::new(File::open(&path)?);
let mut vec = Vec::with_capacity(num_structs);
let mut set = HashSet::with_capacity(num_structs);
unsafe {
let mut buffer = slice::from_raw_parts_mut(vec.as_mut_ptr() as *mut u8, num_bytes);
reader.read_exact(&mut buffer)?;
vec.set_len(num_structs);
}
for raw_utm in vec {
set.insert(Utmp::from_c_utmp(raw_utm));
}
Ok(UtmpSet(set))
}
#[inline]
pub fn system() -> io::Result<Self> {
Self::from_file("/var/run/utmp")
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn iter(&self) -> hash_set::Iter<'_, Utmp> {
self.0.iter()
}
}
impl IntoIterator for UtmpSet {
type IntoIter = hash_set::IntoIter<Utmp>;
type Item = Utmp;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
#[derive(Debug)]
pub struct UtmpIter;
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
impl UtmpIter {
#[inline]
pub fn system() -> Self {
unsafe { setutent() };
Self
}
}
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
impl Iterator for UtmpIter {
type Item = Utmp;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let ut = getutent();
if ut.is_null() {
endutent();
None
} else {
let utm = Utmp::from(*ut);
Some(utm)
}
}
}
}
#[cfg(any(target_os = "netbsd", target_os = "solaris", target_os = "illumos"))]
impl std::iter::FusedIterator for UtmpIter {}
#[cfg(target_os = "netbsd")]
pub(crate) mod consts {
pub const USER_SIZE: usize = 8;
pub const LINE_SIZE: usize = 8;
pub const HOST_SIZE: usize = 16;
}
#[cfg(target_os = "solaris")]
pub(crate) mod consts {
pub const USER_SIZE: usize = 8;
pub const LINE_SIZE: usize = 12;
pub const ID_SIZE: usize = 4;
}
#[cfg(target_os = "openbsd")]
pub(crate) mod consts {
pub const USER_SIZE: usize = 32;
pub const LINE_SIZE: usize = 8;
pub const HOST_SIZE: usize = 256;
}
impl From<Utmp> for utmp {
#[inline]
fn from(utm: Utmp) -> Self {
use self::consts::*;
let mut ut_name = [0; USER_SIZE];
let mut ut_line = [0; LINE_SIZE];
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
let mut ut_host = [0; HOST_SIZE];
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let mut ut_id = [0; ID_SIZE];
utm.user.iter().enumerate().for_each(|(i, c)| ut_name[i] = *c as c_char);
utm.line.iter().enumerate().for_each(|(i, c)| ut_line[i] = *c as c_char);
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
utm.host.iter().enumerate().for_each(|(i, c)| ut_host[i] = *c as c_char);
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
utm.id.iter().enumerate().for_each(|(i, c)| ut_id[i] = *c as c_char);
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
let ut_type = match utm.ut_type.try_into() {
Ok(a) => a,
Err(e) => panic!("{}", e),
};
utmp {
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
ut_name,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_user: ut_name,
ut_line,
ut_time: utm.time,
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
ut_host,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_id,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_pid: utm.pid,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_type,
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
ut_exit: utm.exit,
}
}
}