Skip to main content

coreutils_rs/users/
core.rs

1/// users -- print the user names of users currently logged in
2///
3/// Reads utmpx records and prints a sorted, space-separated list of login names
4/// for all USER_PROCESS entries.
5use std::ffi::CStr;
6#[cfg(target_os = "linux")]
7use std::ffi::CString;
8
9use crate::who;
10
11// utmpxname is a glibc extension to set the utmpx database file path.
12#[cfg(target_os = "linux")]
13unsafe extern "C" {
14    fn utmpxname(file: *const libc::c_char) -> libc::c_int;
15}
16
17/// Retrieve a sorted list of currently logged-in user names from utmpx.
18/// If `file` is Some, reads from that file; otherwise uses the default database
19/// with systemd fallback.
20///
21/// # Safety
22/// Uses libc's setutxent/getutxent/endutxent which are not thread-safe.
23/// This function must not be called concurrently.
24pub fn get_users() -> Vec<String> {
25    get_users_from(None)
26}
27
28pub fn get_users_from(file: Option<&str>) -> Vec<String> {
29    if let Some(_path) = file {
30        // Reading from a specific file — use direct utmpx API
31        let mut users = Vec::new();
32        unsafe {
33            #[cfg(target_os = "linux")]
34            {
35                if let Ok(cpath) = CString::new(_path) {
36                    utmpxname(cpath.as_ptr());
37                }
38            }
39
40            libc::setutxent();
41            loop {
42                let entry = libc::getutxent();
43                if entry.is_null() {
44                    break;
45                }
46                let entry = &*entry;
47                if entry.ut_type == libc::USER_PROCESS {
48                    let name = CStr::from_ptr(entry.ut_user.as_ptr())
49                        .to_string_lossy()
50                        .to_string();
51                    if !name.is_empty() {
52                        users.push(name);
53                    }
54                }
55            }
56            libc::endutxent();
57
58            // Reset to default database after reading custom file
59            #[cfg(target_os = "linux")]
60            {
61                if let Ok(cpath) = CString::new("/var/run/utmp") {
62                    utmpxname(cpath.as_ptr());
63                }
64            }
65        }
66        users.sort();
67        users
68    } else {
69        // Default: use shared utmpx reader with systemd fallback
70        let entries = who::read_utmpx_with_systemd_fallback();
71        let mut users: Vec<String> = entries
72            .iter()
73            .filter(|e| e.ut_type == 7) // USER_PROCESS
74            .map(|e| e.ut_user.clone())
75            .filter(|name| !name.is_empty())
76            .collect();
77        users.sort();
78        users
79    }
80}
81
82/// Format the user list as a single space-separated line (matching GNU users output).
83pub fn format_users(users: &[String]) -> String {
84    users.join(" ")
85}