use std::{
ffi::{c_char, c_int, c_uint, CStr},
net::IpAddr,
};
use libc::{pid_t, timeval};
use sys::{ExitStatus, UtType, Utmpx, UT_HOSTSIZE, UT_LINESIZE, UT_NAMESIZE};
use crate::sys::pututxline;
pub mod sys;
pub enum UtmpxError {
LineTooLong,
IdTooLong,
UserTooLong,
HostTooLong,
}
impl Utmpx {
pub fn new(
ut_type: UtType,
ut_pid: pid_t,
ut_line: &CStr,
ut_id: &CStr,
ut_user: &CStr,
ut_host: &CStr,
ut_exit: ExitStatus,
ut_session: i32,
ut_tv: timeval,
ut_addr_v6: IpAddr,
) -> Result<Self, UtmpxError> {
Ok(Utmpx {
ut_type,
__ut_pad1: 0,
ut_pid,
ut_line: cast_cstring(ut_line).ok_or(UtmpxError::LineTooLong)?,
ut_id: cast_cstring(ut_id).ok_or(UtmpxError::IdTooLong)?,
ut_user: cast_cstring(ut_user).ok_or(UtmpxError::UserTooLong)?,
ut_host: cast_cstring(ut_host).ok_or(UtmpxError::HostTooLong)?,
ut_exit,
ut_session,
__ut_pad2: 0,
ut_tv,
ut_addr_v6: cast_addr(ut_addr_v6),
__unused: [0; 20],
})
}
}
impl Default for Utmpx {
fn default() -> Self {
Utmpx {
ut_type: UtType::EMPTY,
__ut_pad1: 0,
ut_pid: 0,
ut_line: [0; UT_LINESIZE],
ut_id: [0; 4],
ut_user: [0; UT_NAMESIZE],
ut_host: [0; UT_HOSTSIZE],
ut_exit: ExitStatus::default(),
ut_session: 0,
__ut_pad2: 0,
ut_tv: timeval {
tv_sec: 0,
tv_usec: 0,
},
ut_addr_v6: [0; 4],
__unused: [0; 20],
}
}
}
fn cast_cstring<const S: usize>(cstr: &CStr) -> Option<[c_char; S]> {
let source = cstr.to_bytes_with_nul();
if source.len() > S {
return None;
}
let mut ret: [c_char; S] = [0; S];
for (r, s) in ret.iter_mut().zip(source.iter()) {
*r = *s as c_char;
}
Some(ret)
}
#[test]
fn test_cast_cstring() {
use std::ffi::CString;
let hello = CString::new("hello").unwrap();
let array: [c_char; 6] = cast_cstring(hello.as_c_str()).unwrap();
assert_eq!(unsafe { CStr::from_ptr(array.as_ptr()) }, hello.as_c_str());
}
fn cast_addr(addr: IpAddr) -> [c_uint; 4usize] {
match addr {
IpAddr::V4(ipv4addr) => [u32::from(ipv4addr), 0, 0, 0],
IpAddr::V6(ipv6addr) => {
let octets = ipv6addr.octets();
let mut ret = [0u32; 4];
for i in 0..4 {
ret[i] = u32::from(octets[i * 4]) << 24
| u32::from(octets[i * 4 + 1]) << 16
| u32::from(octets[i * 4 + 2]) << 8
| u32::from(octets[i * 4 + 3]);
}
ret
}
}
}
pub fn set_filename(filename: &CStr) -> Result<(), c_int> {
let ptr = filename.as_ptr();
let r = unsafe { sys::utmpxname(ptr) };
if r == 0 {
Ok(())
} else {
Err(r)
}
}
pub fn reset_cursor() {
unsafe { sys::setutxent() }
}
pub fn write_line(line: &Utmpx) -> Result<Utmpx, ()> {
let ptr: *const Utmpx = line;
let res = unsafe { pututxline(ptr) };
if res.is_null() {
return Err(());
}
let r: Utmpx = unsafe { *res };
Ok(r)
}
pub fn close_database() {
unsafe { sys::endutxent() }
}
pub fn find_by_id(b: &Utmpx) -> Result<Utmpx, ()> {
let ptr: *const Utmpx = b;
let res = unsafe { sys::getutxid(ptr) };
if res.is_null() {
return Err(());
}
let r: Utmpx = unsafe { *res };
Ok(r)
}
pub fn find_by_line(b: &Utmpx) -> Result<Utmpx, ()> {
let ptr: *const Utmpx = b;
let res = unsafe { sys::getutxline(ptr) };
if res.is_null() {
return Err(());
}
let r: Utmpx = unsafe { *res };
Ok(r)
}
pub fn read_next_entry() -> Result<Utmpx, ()> {
let res = unsafe { sys::getutxent() };
if res.is_null() {
return Err(());
}
let r: Utmpx = unsafe { *res };
Ok(r)
}