#[cfg(target_os = "solaris")]
use std::convert::{TryFrom, TryInto};
use std::{
collections::{hash_set, HashSet},
fs::{self, File},
io::{self, BufReader, Read},
mem,
path::Path,
slice,
};
use super::Time;
use libc::{c_char, utmp};
use bstr::{BStr, BString, ByteSlice};
use time::OffsetDateTime as DataTime;
#[cfg(target_os = "solaris")]
use super::utmpx::UtmpxKind;
#[cfg(target_os = "solaris")]
use libc::{c_short, exit_status as ExitStatus};
#[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(target_os = "solaris")]
id: BString,
#[cfg(target_os = "solaris")]
pid: c_short,
#[cfg(target_os = "solaris")]
ut_type: UtmpxKind,
#[cfg(target_os = "solaris")]
exit: ExitStatus,
}
impl Utmp {
pub fn from_c_utmp(utm: utmp) -> Self { Self::from(utm) }
pub fn user(&self) -> &BStr { self.user.as_bstr() }
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
pub fn host(&self) -> &BStr { self.host.as_bstr() }
#[cfg(target_os = "solaris")]
pub fn id(&self) -> &BStr { self.id.as_bstr() }
pub fn device_name(&self) -> &BStr { self.line.as_bstr() }
pub const fn time(&self) -> Time { self.time }
pub fn login_time(&self) -> DataTime { DataTime::from_unix_timestamp(self.time) }
#[cfg(target_os = "solaris")]
pub fn pid(&self) -> c_short { self.pid }
#[cfg(target_os = "solaris")]
pub fn entry_type(&self) -> UtmpxKind { self.ut_type }
#[cfg(target_os = "solaris")]
pub fn exit_status(&self) -> ExitStatus { self.exit }
}
impl From<utmp> for Utmp {
fn from(utm: utmp) -> Self {
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
let user = {
let cstr: String =
utm.ut_name.iter().map(|cc| *cc as u8 as char).filter(|cc| cc != &'\0').collect();
BString::from(cstr.as_bytes())
};
#[cfg(target_os = "solaris")]
let user = {
let cstr: String =
utm.ut_user.iter().map(|cc| *cc as u8 as char).filter(|cc| cc != &'\0').collect();
BString::from(cstr.as_bytes())
};
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
let host = {
let cstr: String =
utm.ut_host.iter().map(|cc| *cc as u8 as char).filter(|cc| cc != &'\0').collect();
BString::from(cstr.as_bytes())
};
#[cfg(target_os = "solaris")]
let id = {
let cstr: String =
utm.ut_id.iter().map(|cc| *cc as u8 as char).filter(|cc| cc != &'\0').collect();
BString::from(cstr.as_bytes())
};
let line = {
let cstr: String =
utm.ut_line.iter().map(|cc| *cc as u8 as char).filter(|cc| cc != &'\0').collect();
BString::from(cstr.as_bytes())
};
let time = utm.ut_time;
#[cfg(target_os = "solaris")]
let ut_type = match UtmpxKind::try_from(utm.ut_type) {
Ok(ut) => ut,
Err(err) => panic!(format!("{}", err)),
};
Utmp {
user,
line,
time,
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
host,
#[cfg(target_os = "solaris")]
id,
#[cfg(target_os = "solaris")]
pid: utm.ut_pid,
#[cfg(target_os = "solaris")]
ut_type,
#[cfg(target_os = "solaris")]
exit: utm.ut_exit,
}
}
}
#[derive(Debug)]
pub struct UtmpSet(HashSet<Utmp>);
impl UtmpSet {
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))
}
pub fn system() -> io::Result<Self> { Self::from_file("/var/run/utmp") }
pub fn is_empty(&self) -> bool { self.0.is_empty() }
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(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 {
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(target_os = "solaris")]
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(target_os = "solaris")]
utm.id.iter().enumerate().for_each(|(i, c)| ut_id[i] = *c as c_char);
#[cfg(target_os = "solaris")]
let ut_type = match utm.ut_type.try_into() {
Ok(a) => a,
Err(e) => panic!(format!("{}", e)),
};
utmp {
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
ut_name,
#[cfg(target_os = "solaris")]
ut_user: ut_name,
ut_line,
ut_time: utm.time,
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
ut_host,
#[cfg(target_os = "solaris")]
ut_id,
#[cfg(target_os = "solaris")]
ut_pid: utm.pid,
#[cfg(target_os = "solaris")]
ut_type,
#[cfg(target_os = "solaris")]
ut_exit: utm.exit,
}
}
}