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}