utmpx/
lib.rs

1//! This library contains bindings to the types and functions in `utmpx.h`.
2//!
3//! The [`sys`] module contains the raw bindings. The root module contains idiomatic wrappers.
4//!
5//! The underlying `utmpx` functions work with per-thread cursors. Care should be taken when using
6//! this in multiple threads, but all functions should be usable from different threads.
7//!
8//! For platforms that implement `utmp.h` but not `utmpx.h`, the intent is for wrappers to call the
9//! `utmp.h` equivalents.
10use 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
23/// Error creating Utmpx instance.
24pub enum UtmpxError {
25    LineTooLong,
26    IdTooLong,
27    UserTooLong,
28    HostTooLong,
29}
30
31impl Utmpx {
32    /// Create a new instance
33    ///
34    /// # Errors
35    ///
36    /// If any `Cstring` is longer that the allows values.
37    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
90//// Convert a `CStr` into an array of `c_char`.
91///
92/// Returns `None` if the input string is too long.
93fn 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
115/// Convert an `IpAddr` to the format expected by `Utmpx`.
116fn 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
133/// Change the filename of the database opened by the current thread.
134///
135/// The default filename is `/etc/utmpx`.
136pub 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
147/// Resets the cursor for the current thread.
148///
149/// The cursor is reset to the beginning of the utmpx database.
150pub fn reset_cursor() {
151    unsafe { sys::setutxent() }
152}
153
154/// Write the provided structure into the utmpx database.
155///
156/// Replaces an entry based on `getutxid`, if any. Otherwise inserts a new one.
157pub 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    // SAFETY: pointer is not null and points to a valid utmp structure.
164    let r: Utmpx = unsafe { *res };
165    Ok(r)
166}
167
168/// Close the utmpx database for the current thread.
169pub fn close_database() {
170    unsafe { sys::endutxent() }
171}
172
173/// Find the next entry by `ut_type` and `ut_id`.
174///
175/// The search begins at the current cursor position and ends at EOF.
176pub 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    // SAFETY: pointer is not null and points to a valid utmp structure.
183    let r: Utmpx = unsafe { *res };
184    Ok(r)
185}
186
187/// Find the next entry by `ut_line`.
188///
189/// The search begins at the current cursor position and ends at EOF.
190pub 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    // SAFETY: pointer is not null and points to a valid utmp structure.
197    let r: Utmpx = unsafe { *res };
198    Ok(r)
199}
200
201/// Read in the next entry from the utmpx database.
202///
203/// If the database is not already open, it opens it.
204pub fn read_next_entry() -> Result<Utmpx, ()> {
205    let res = unsafe { sys::getutxent() };
206    if res.is_null() {
207        return Err(());
208    }
209    // SAFETY: pointer is not null and points to a valid utmp structure.
210    let r: Utmpx = unsafe { *res };
211    Ok(r)
212}