#[cfg(any(target_os = "linux", target_os = "macos"))]
use std::ffi::CString;
use std::{
collections::{hash_set, HashSet},
convert::TryFrom,
error::Error as StdError,
fmt::{self, Display},
io,
path::Path,
};
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
use std::{
fs::{self, File},
io::{BufReader, Read},
mem, slice,
};
use super::{Pid, TimeVal};
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
use libc::__exit_status as ExitStatus;
#[cfg(all(target_os = "linux", any(target_arch = "x86_64")))]
use libc::c_int;
#[cfg(all(target_os = "linux", not(any(target_arch = "x86_64"))))]
use libc::c_long;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use libc::utmpxname;
#[cfg(target_os = "solaris")]
use libc::{c_int, c_short, exit_status as ExitStatus};
use libc::{endutxent, getutxent, setutxent, suseconds_t, time_t, utmpx};
use bstr::{BStr, BString, ByteSlice};
use time::{Duration, OffsetDateTime as DateTime};
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum Error {
OsNoKind,
OsNoNumber,
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> { None }
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::OsNoKind => {
write!(f, "This OS has not this UtmpxKind and a number related to that kind")
},
Self::OsNoNumber => write!(f, "This OS has no UtmpxKind related to this number"),
}
}
}
#[repr(u16)]
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UtmpxKind {
Accounting,
BootTime,
DeadProcess,
Empty,
InitProcess,
LoginProcess,
NewTime,
OldTime,
RunLevel,
Signature,
ShutdownProcess,
UserProcess,
DownTime,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Utmpx {
user: BString,
host: BString,
pid: Pid,
id: BString,
line: BString,
ut_type: UtmpxKind,
timeval: TimeVal,
#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "solaris"))]
exit: ExitStatus,
#[cfg(all(target_os = "linux", any(target_arch = "x86_64")))]
session: c_int,
#[cfg(target_os = "solaris")]
session: c_int,
#[cfg(all(target_os = "linux", not(any(target_arch = "x86_64"))))]
session: c_long,
#[cfg(any(target_os = "netbsd", target_os = "dragonfly"))]
session: u16,
#[cfg(target_os = "linux")]
addr_v6: [i32; 4],
#[cfg(target_os = "netbsd")]
ss: libc::sockaddr_storage,
#[cfg(target_os = "solaris")]
syslen: c_short,
}
impl Utmpx {
pub fn from_c_utmpx(utm: utmpx) -> Self { Self::from(utm) }
pub fn user(&self) -> &BStr { self.user.as_bstr() }
pub fn host(&self) -> &BStr { self.host.as_bstr() }
pub fn process_id(&self) -> Pid { self.pid }
pub fn id(&self) -> &BStr { self.id.as_bstr() }
pub fn device_name(&self) -> &BStr { self.line.as_bstr() }
pub const fn entry_type(&self) -> UtmpxKind { self.ut_type }
pub const fn timeval(&self) -> TimeVal { self.timeval }
pub fn login_time(&self) -> DateTime {
DateTime::from_unix_timestamp(self.timeval.tv_sec as i64)
+ Duration::microseconds(self.timeval.tv_usec as i64)
}
#[cfg(all(target_os = "linux", any(target_arch = "x86_64")))]
pub const fn session(&self) -> c_int { self.session }
#[cfg(target_os = "solaris")]
pub const fn session(&self) -> c_int { self.session }
#[cfg(all(target_os = "linux", not(any(target_arch = "x86_64"))))]
pub const fn session(&self) -> c_long { self.session }
#[cfg(any(target_os = "netbsd", target_os = "dragonfly"))]
pub const fn session(&self) -> u16 { self.session }
#[cfg(target_os = "linux")]
pub const fn v6_addr(&self) -> [i32; 4] { self.addr_v6 }
#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "solaris"))]
pub const fn exit_status(&self) -> ExitStatus { self.exit }
}
impl From<utmpx> for Utmpx {
fn from(c_utmpx: utmpx) -> Self {
#[cfg(not(any(target_os = "netbsd", target_os = "dragonfly")))]
let user = {
let cstr: String = c_utmpx
.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 = "dragonfly"))]
let user = {
let cstr: String = c_utmpx
.ut_name
.iter()
.map(|cc| *cc as u8 as char)
.filter(|cc| cc != &'\0')
.collect();
BString::from(cstr.as_bytes())
};
let host = {
let cstr: String = c_utmpx
.ut_host
.iter()
.map(|cc| *cc as u8 as char)
.filter(|cc| cc != &'\0')
.collect();
BString::from(cstr.as_bytes())
};
let pid = c_utmpx.ut_pid;
let id = {
let cstr: String =
c_utmpx.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 = c_utmpx
.ut_line
.iter()
.map(|cc| *cc as u8 as char)
.filter(|cc| cc != &'\0')
.collect();
BString::from(cstr.as_bytes())
};
let ut_type = match UtmpxKind::try_from(c_utmpx.ut_type) {
Ok(ut) => ut,
Err(err) => panic!(format!("{}", err)),
};
let timeval = TimeVal {
tv_sec: c_utmpx.ut_tv.tv_sec as time_t,
tv_usec: c_utmpx.ut_tv.tv_usec as suseconds_t,
};
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris"
))]
let session = c_utmpx.ut_session;
#[cfg(target_os = "linux")]
let addr_v6 = c_utmpx.ut_addr_v6;
#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "solaris"))]
let exit = c_utmpx.ut_exit;
#[cfg(any(target_os = "netbsd"))]
let ss = c_utmpx.ut_ss;
#[cfg(target_os = "solaris")]
let syslen = c_utmpx.ut_syslen;
Utmpx {
user,
host,
pid,
id,
line,
ut_type,
timeval,
#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "solaris"))]
exit,
#[cfg(any(
target_os = "linux",
target_os = "netbsd",
target_os = "dragonfly",
target_os = "solaris"
))]
session,
#[cfg(target_os = "linux")]
addr_v6,
#[cfg(target_os = "netbsd")]
ss,
#[cfg(target_os = "solaris")]
syslen,
}
}
}
#[derive(Debug)]
pub struct UtmpxSet(HashSet<Utmpx>);
impl UtmpxSet {
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn from_file(path: impl AsRef<Path>) -> io::Result<Self> {
let file = {
let str = match path.as_ref().to_str() {
Some(s) => s,
None => "",
};
CString::new(str).unwrap_or_default()
};
let mut set = HashSet::new();
unsafe {
let res = utmpxname(file.as_ptr());
if res != 0 {
return Err(io::Error::last_os_error());
}
loop {
let ut = getutxent();
if ut.is_null() {
break;
} else {
let utm = Utmpx::from_c_utmpx(*ut);
set.insert(utm);
}
}
endutxent();
}
Ok(UtmpxSet(set))
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub fn from_file(path: impl AsRef<Path>) -> io::Result<Self> {
let struct_size = mem::size_of::<utmpx>();
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(Utmpx::from_c_utmpx(raw_utm));
}
Ok(UtmpxSet(set))
}
pub fn system() -> Self {
let mut set = HashSet::new();
unsafe {
setutxent();
loop {
let ut = getutxent();
if ut.is_null() {
break;
} else {
let utm = Utmpx::from_c_utmpx(*ut);
set.insert(utm);
}
}
endutxent();
}
UtmpxSet(set)
}
pub fn is_empty(&self) -> bool { self.0.is_empty() }
pub fn iter(&self) -> hash_set::Iter<'_, Utmpx> { self.0.iter() }
pub fn len(&self) -> usize { self.0.len() }
}
impl IntoIterator for UtmpxSet {
type IntoIter = hash_set::IntoIter<Utmpx>;
type Item = Utmpx;
#[inline]
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
}
macro_rules! utmpxkind_impl_from {
($($t:ty)+) => (
$(
#[cfg(target_os = "freebsd")]
impl TryFrom<$t> for UtmpxKind {
type Error = Error;
fn try_from(num: $t) -> Result<Self, Error> {
match num {
0 => Ok(Self::Empty),
1 => Ok(Self::BootTime),
2 => Ok(Self::OldTime),
3 => Ok(Self::NewTime),
4 => Ok(Self::UserProcess),
5 => Ok(Self::InitProcess),
6 => Ok(Self::LoginProcess),
7 => Ok(Self::DeadProcess),
8 => Ok(Self::ShutdownProcess),
_ => Err(Error::OsNoNumber),
}
}
}
#[cfg(target_os = "netbsd")]
impl TryFrom<$t> for UtmpxKind {
type Error = Error;
fn try_from(num: $t) -> Result<Self, Error> {
match num {
0 => Ok(Self::Empty),
1 => Ok(Self::RunLevel),
2 => Ok(Self::BootTime),
3 => Ok(Self::OldTime),
4 => Ok(Self::NewTime),
5 => Ok(Self::InitProcess),
6 => Ok(Self::LoginProcess),
7 => Ok(Self::UserProcess),
8 => Ok(Self::DeadProcess),
9 => Ok(Self::Accounting),
10 => Ok(Self::Signature),
11 => Ok(Self::DownTime),
_ => Err(Error::OsNoNumber),
}
}
}
#[cfg(any(target_os = "dragonfly"))]
impl TryFrom<$t> for UtmpxKind {
type Error = Error;
fn try_from(num: $t) -> Result<Self, Error> {
match num {
0 => Ok(Self::Empty),
1 => Ok(Self::RunLevel),
2 => Ok(Self::BootTime),
3 => Ok(Self::NewTime),
4 => Ok(Self::OldTime),
5 => Ok(Self::InitProcess),
6 => Ok(Self::LoginProcess),
7 => Ok(Self::UserProcess),
8 => Ok(Self::DeadProcess),
_ => Err(Error::OsNoNumber),
}
}
}
#[cfg(target_os = "solaris")]
impl TryFrom<$t> for UtmpxKind {
type Error = Error;
fn try_from(num: $t) -> Result<Self, Error> {
match num {
0 => Ok(Self::Empty),
1 => Ok(Self::RunLevel),
2 => Ok(Self::BootTime),
3 => Ok(Self::OldTime),
4 => Ok(Self::NewTime),
5 => Ok(Self::InitProcess),
6 => Ok(Self::LoginProcess),
7 => Ok(Self::UserProcess),
8 => Ok(Self::DeadProcess),
9 => Ok(Self::Accounting),
10 => Ok(Self::DownTime),
_ => Err(Error::OsNoNumber),
}
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
impl TryFrom<$t> for UtmpxKind {
type Error = Error;
fn try_from(num: $t) -> Result<Self, Error> {
match num {
0 => Ok(Self::Empty),
1 => Ok(Self::RunLevel),
2 => Ok(Self::BootTime),
3 => Ok(Self::NewTime),
4 => Ok(Self::OldTime),
5 => Ok(Self::InitProcess),
6 => Ok(Self::LoginProcess),
7 => Ok(Self::UserProcess),
8 => Ok(Self::DeadProcess),
9 => Ok(Self::Accounting),
#[cfg(target_os = "macos")]
10 => Ok(Self::Signature),
#[cfg(target_os = "macos")]
11 => Ok(Self::ShutdownProcess),
_ => Err(Error::OsNoNumber),
}
}
}
#[cfg(target_os = "freebsd")]
impl TryFrom<UtmpxKind> for $t {
type Error = Error;
fn try_from(utype: UtmpxKind) -> Result<Self, Error> {
match utype {
UtmpxKind::Empty => Ok(0),
UtmpxKind::BootTime => Ok(1),
UtmpxKind::OldTime => Ok(2),
UtmpxKind::NewTime => Ok(3),
UtmpxKind::UserProcess => Ok(4),
UtmpxKind::InitProcess => Ok(5),
UtmpxKind::LoginProcess => Ok(6),
UtmpxKind::DeadProcess => Ok(7),
UtmpxKind::ShutdownProcess => Ok(8),
_ => Err(Error::OsNoKind),
}
}
}
#[cfg(target_os = "netbsd")]
impl TryFrom<UtmpxKind> for $t {
type Error = Error;
fn try_from(utype: UtmpxKind) -> Result<Self, Error> {
match utype {
UtmpxKind::Empty => Ok(0),
UtmpxKind::RunLevel => Ok(1),
UtmpxKind::BootTime => Ok(2),
UtmpxKind::OldTime => Ok(3),
UtmpxKind::NewTime => Ok(4),
UtmpxKind::InitProcess => Ok(5),
UtmpxKind::LoginProcess => Ok(6),
UtmpxKind::UserProcess => Ok(7),
UtmpxKind::DeadProcess => Ok(8),
UtmpxKind::Accounting => Ok(9),
UtmpxKind::Signature => Ok(10),
UtmpxKind::DownTime => Ok(11),
_ => Err(Error::OsNoKind),
}
}
}
#[cfg(any(target_os = "dragonfly"))]
impl TryFrom<UtmpxKind> for $t {
type Error = Error;
fn try_from(utype: UtmpxKind) -> Result<Self, Error> {
match utype {
UtmpxKind::Empty => Ok(0),
UtmpxKind::RunLevel => Ok(1),
UtmpxKind::BootTime => Ok(2),
UtmpxKind::NewTime => Ok(3),
UtmpxKind::OldTime => Ok(4),
UtmpxKind::InitProcess => Ok(5),
UtmpxKind::LoginProcess => Ok(6),
UtmpxKind::UserProcess => Ok(7),
UtmpxKind::DeadProcess => Ok(8),
_ => Err(Error::OsNoKind),
}
}
}
#[cfg(target_os = "solaris")]
impl TryFrom<UtmpxKind> for $t {
type Error = Error;
fn try_from(utype: UtmpxKind) -> Result<Self, Error> {
match utype {
UtmpxKind::Empty => Ok(0),
UtmpxKind::RunLevel => Ok(1),
UtmpxKind::BootTime => Ok(2),
UtmpxKind::OldTime => Ok(3),
UtmpxKind::NewTime => Ok(4),
UtmpxKind::InitProcess => Ok(5),
UtmpxKind::LoginProcess => Ok(6),
UtmpxKind::UserProcess => Ok(7),
UtmpxKind::DeadProcess => Ok(8),
UtmpxKind::Accounting => Ok(9),
UtmpxKind::DownTime => Ok(10),
_ => Err(Error::OsNoKind),
}
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
impl TryFrom<UtmpxKind> for $t {
type Error = Error;
fn try_from(utype: UtmpxKind) -> Result<Self, Error> {
match utype {
UtmpxKind::Empty => Ok(0),
UtmpxKind::RunLevel => Ok(1),
UtmpxKind::BootTime => Ok(2),
UtmpxKind::NewTime => Ok(3),
UtmpxKind::OldTime => Ok(4),
UtmpxKind::InitProcess => Ok(5),
UtmpxKind::LoginProcess => Ok(6),
UtmpxKind::UserProcess => Ok(7),
UtmpxKind::DeadProcess => Ok(8),
UtmpxKind::Accounting => Ok(9),
#[cfg(target_os = "macos")]
UtmpxKind::Signature => Ok(10),
#[cfg(target_os = "macos")]
UtmpxKind::ShutdownProcess => Ok(11),
_ => Err(Error::OsNoKind),
}
}
}
)+
);
}
utmpxkind_impl_from!(i8 i16 i32 i64 i128 u8 u16 u32 u64 u128);