1use std::{
11 ffi::{c_char, c_int, c_uint, CStr},
12 net::IpAddr,
13};
14
15use libc::{pid_t, timeval};
16
17use sys::{ExitStatus, UtType, Utmpx, UT_HOSTSIZE, UT_LINESIZE, UT_NAMESIZE};
18
19use crate::sys::pututxline;
20
21pub mod sys;
22
23pub enum UtmpxError {
25 LineTooLong,
26 IdTooLong,
27 UserTooLong,
28 HostTooLong,
29}
30
31impl Utmpx {
32 pub fn new(
38 ut_type: UtType,
39 ut_pid: pid_t,
40 ut_line: &CStr,
41 ut_id: &CStr,
42 ut_user: &CStr,
43 ut_host: &CStr,
44 ut_exit: ExitStatus,
45 ut_session: i32,
46 ut_tv: timeval,
47 ut_addr_v6: IpAddr,
48 ) -> Result<Self, UtmpxError> {
49 Ok(Utmpx {
50 ut_type,
51 __ut_pad1: 0,
52 ut_pid,
53 ut_line: cast_cstring(ut_line).ok_or(UtmpxError::LineTooLong)?,
54 ut_id: cast_cstring(ut_id).ok_or(UtmpxError::IdTooLong)?,
55 ut_user: cast_cstring(ut_user).ok_or(UtmpxError::UserTooLong)?,
56 ut_host: cast_cstring(ut_host).ok_or(UtmpxError::HostTooLong)?,
57 ut_exit,
58 ut_session,
59 __ut_pad2: 0,
60 ut_tv,
61 ut_addr_v6: cast_addr(ut_addr_v6),
62 __unused: [0; 20],
63 })
64 }
65}
66
67impl Default for Utmpx {
68 fn default() -> Self {
69 Utmpx {
70 ut_type: UtType::EMPTY,
71 __ut_pad1: 0,
72 ut_pid: 0,
73 ut_line: [0; UT_LINESIZE],
74 ut_id: [0; 4],
75 ut_user: [0; UT_NAMESIZE],
76 ut_host: [0; UT_HOSTSIZE],
77 ut_exit: ExitStatus::default(),
78 ut_session: 0,
79 __ut_pad2: 0,
80 ut_tv: timeval {
81 tv_sec: 0,
82 tv_usec: 0,
83 },
84 ut_addr_v6: [0; 4],
85 __unused: [0; 20],
86 }
87 }
88}
89
90fn cast_cstring<const S: usize>(cstr: &CStr) -> Option<[c_char; S]> {
94 let source = cstr.to_bytes_with_nul();
95 if source.len() > S {
96 return None;
97 }
98 let mut ret: [c_char; S] = [0; S];
99 for (r, s) in ret.iter_mut().zip(source.iter()) {
100 *r = *s as c_char;
101 }
102
103 Some(ret)
104}
105
106#[test]
107fn test_cast_cstring() {
108 use std::ffi::CString;
109
110 let hello = CString::new("hello").unwrap();
111 let array: [c_char; 6] = cast_cstring(hello.as_c_str()).unwrap();
112 assert_eq!(unsafe { CStr::from_ptr(array.as_ptr()) }, hello.as_c_str());
113}
114
115fn cast_addr(addr: IpAddr) -> [c_uint; 4usize] {
117 match addr {
118 IpAddr::V4(ipv4addr) => [u32::from(ipv4addr), 0, 0, 0],
119 IpAddr::V6(ipv6addr) => {
120 let octets = ipv6addr.octets();
121 let mut ret = [0u32; 4];
122 for i in 0..4 {
123 ret[i] = u32::from(octets[i * 4]) << 24
124 | u32::from(octets[i * 4 + 1]) << 16
125 | u32::from(octets[i * 4 + 2]) << 8
126 | u32::from(octets[i * 4 + 3]);
127 }
128 ret
129 }
130 }
131}
132
133pub fn set_filename(filename: &CStr) -> Result<(), c_int> {
137 let ptr = filename.as_ptr();
138 let r = unsafe { sys::utmpxname(ptr) };
139
140 if r == 0 {
141 Ok(())
142 } else {
143 Err(r)
144 }
145}
146
147pub fn reset_cursor() {
151 unsafe { sys::setutxent() }
152}
153
154pub fn write_line(line: &Utmpx) -> Result<Utmpx, ()> {
158 let ptr: *const Utmpx = line;
159 let res = unsafe { pututxline(ptr) };
160 if res.is_null() {
161 return Err(());
162 }
163 let r: Utmpx = unsafe { *res };
165 Ok(r)
166}
167
168pub fn close_database() {
170 unsafe { sys::endutxent() }
171}
172
173pub fn find_by_id(b: &Utmpx) -> Result<Utmpx, ()> {
177 let ptr: *const Utmpx = b;
178 let res = unsafe { sys::getutxid(ptr) };
179 if res.is_null() {
180 return Err(());
181 }
182 let r: Utmpx = unsafe { *res };
184 Ok(r)
185}
186
187pub fn find_by_line(b: &Utmpx) -> Result<Utmpx, ()> {
191 let ptr: *const Utmpx = b;
192 let res = unsafe { sys::getutxline(ptr) };
193 if res.is_null() {
194 return Err(());
195 }
196 let r: Utmpx = unsafe { *res };
198 Ok(r)
199}
200
201pub fn read_next_entry() -> Result<Utmpx, ()> {
205 let res = unsafe { sys::getutxent() };
206 if res.is_null() {
207 return Err(());
208 }
209 let r: Utmpx = unsafe { *res };
211 Ok(r)
212}