sysinfo/unix/
users.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{
4    common::{Gid, Uid},
5    Group, User,
6};
7use libc::{getgrgid_r, getgrouplist};
8
9#[cfg(not(target_os = "android"))]
10use libc::{endpwent, getpwent, setpwent};
11
12// See `https://github.com/rust-lang/libc/issues/3014`.
13#[cfg(target_os = "android")]
14extern "C" {
15    fn getpwent() -> *mut libc::passwd;
16    fn setpwent();
17    fn endpwent();
18}
19
20pub(crate) struct UserInner {
21    pub(crate) uid: Uid,
22    pub(crate) gid: Gid,
23    pub(crate) name: String,
24    c_user: Vec<u8>,
25}
26
27impl UserInner {
28    pub(crate) fn new(uid: Uid, gid: Gid, name: String) -> Self {
29        let mut c_user = name.as_bytes().to_vec();
30        c_user.push(0);
31        Self {
32            uid,
33            gid,
34            name,
35            c_user,
36        }
37    }
38
39    pub(crate) fn id(&self) -> &Uid {
40        &self.uid
41    }
42
43    pub(crate) fn group_id(&self) -> Gid {
44        self.gid
45    }
46
47    pub(crate) fn name(&self) -> &str {
48        &self.name
49    }
50
51    pub(crate) fn groups(&self) -> Vec<Group> {
52        unsafe { get_user_groups(self.c_user.as_ptr() as *const _, self.gid.0 as _) }
53    }
54}
55
56pub(crate) unsafe fn get_group_name(
57    id: libc::gid_t,
58    buffer: &mut Vec<libc::c_char>,
59) -> Option<String> {
60    let mut g = std::mem::MaybeUninit::<libc::group>::uninit();
61    let mut tmp_ptr = std::ptr::null_mut();
62    let mut last_errno = 0;
63    loop {
64        if retry_eintr!(set_to_0 => last_errno => getgrgid_r(
65            id as _,
66            g.as_mut_ptr() as _,
67            buffer.as_mut_ptr(),
68            buffer.capacity() as _,
69            &mut tmp_ptr as _
70        )) != 0
71        {
72            // If there was not enough memory, we give it more.
73            if last_errno == libc::ERANGE as _ {
74                // Needs to be updated for `Vec::reserve` to actually add additional capacity.
75                // In here it's "fine" since we never read from `buffer`.
76                buffer.set_len(buffer.capacity());
77                buffer.reserve(2048);
78                continue;
79            }
80            return None;
81        }
82        break;
83    }
84    let g = g.assume_init();
85    super::utils::cstr_to_rust(g.gr_name)
86}
87
88pub(crate) unsafe fn get_user_groups(
89    name: *const libc::c_char,
90    group_id: libc::gid_t,
91) -> Vec<Group> {
92    let mut buffer = Vec::with_capacity(2048);
93    let mut groups = Vec::with_capacity(256);
94
95    loop {
96        let mut nb_groups = groups.capacity();
97        if getgrouplist(
98            name,
99            group_id as _,
100            groups.as_mut_ptr(),
101            &mut nb_groups as *mut _ as *mut _,
102        ) == -1
103        {
104            // Ensure the length matches the number of returned groups.
105            // Needs to be updated for `Vec::reserve` to actually add additional capacity.
106            groups.set_len(nb_groups as _);
107            groups.reserve(256);
108            continue;
109        }
110        groups.set_len(nb_groups as _);
111        return groups
112            .iter()
113            .filter_map(|group_id| {
114                let name = get_group_name(*group_id as _, &mut buffer)?;
115                Some(Group {
116                    inner: crate::GroupInner::new(Gid(*group_id as _), name),
117                })
118            })
119            .collect();
120    }
121}
122
123pub(crate) fn get_users(users: &mut Vec<User>) {
124    fn filter(shell: *const std::ffi::c_char, uid: u32) -> bool {
125        !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536
126    }
127
128    users.clear();
129
130    let mut users_map = std::collections::HashMap::with_capacity(10);
131
132    unsafe {
133        setpwent();
134        loop {
135            let pw = getpwent();
136            if pw.is_null() {
137                // The call was interrupted by a signal, retrying.
138                if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
139                    continue;
140                }
141                break;
142            }
143
144            if !filter((*pw).pw_shell, (*pw).pw_uid) {
145                // This is not a "real" or "local" user.
146                continue;
147            }
148            if let Some(name) = crate::unix::utils::cstr_to_rust((*pw).pw_name) {
149                if users_map.contains_key(&name) {
150                    continue;
151                }
152
153                let uid = (*pw).pw_uid;
154                let gid = (*pw).pw_gid;
155                users_map.insert(name, (Uid(uid), Gid(gid)));
156            }
157        }
158        endpwent();
159    }
160    for (name, (uid, gid)) in users_map {
161        users.push(User {
162            inner: UserInner::new(uid, gid, name),
163        });
164    }
165}
166
167fn endswith(s1: *const std::ffi::c_char, s2: &[u8]) -> bool {
168    if s1.is_null() {
169        return false;
170    }
171    unsafe {
172        let mut len = libc::strlen(s1) as isize - 1;
173        let mut i = s2.len() as isize - 1;
174        while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as _ {
175            i -= 1;
176            len -= 1;
177        }
178        i == -1
179    }
180}