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,
6};
7
8#[cfg(not(any(target_os = "macos", target_os = "ios")))]
9use crate::User;
10
11use libc::{getgrgid_r, getgrouplist};
12
13pub(crate) struct UserInner {
14    pub(crate) uid: Uid,
15    pub(crate) gid: Gid,
16    pub(crate) name: String,
17    c_user: Vec<u8>,
18}
19
20impl UserInner {
21    pub(crate) fn new(uid: Uid, gid: Gid, name: String) -> Self {
22        let mut c_user = name.as_bytes().to_vec();
23        c_user.push(0);
24        Self {
25            uid,
26            gid,
27            name,
28            c_user,
29        }
30    }
31
32    pub(crate) fn id(&self) -> &Uid {
33        &self.uid
34    }
35
36    pub(crate) fn group_id(&self) -> Gid {
37        self.gid
38    }
39
40    pub(crate) fn name(&self) -> &str {
41        &self.name
42    }
43
44    pub(crate) fn groups(&self) -> Vec<Group> {
45        unsafe { get_user_groups(self.c_user.as_ptr() as *const _, self.gid.0 as _) }
46    }
47}
48
49pub(crate) unsafe fn get_group_name(
50    id: libc::gid_t,
51    buffer: &mut Vec<libc::c_char>,
52) -> Option<String> {
53    let mut g = std::mem::MaybeUninit::<libc::group>::uninit();
54    let mut tmp_ptr = std::ptr::null_mut();
55    let mut last_errno = 0;
56    loop {
57        if retry_eintr!(set_to_0 => last_errno => getgrgid_r(
58            id as _,
59            g.as_mut_ptr() as _,
60            buffer.as_mut_ptr(),
61            buffer.capacity() as _,
62            &mut tmp_ptr as _
63        )) != 0
64        {
65            // If there was not enough memory, we give it more.
66            if last_errno == libc::ERANGE as _ {
67                // Needs to be updated for `Vec::reserve` to actually add additional capacity.
68                // In here it's "fine" since we never read from `buffer`.
69                buffer.set_len(buffer.capacity());
70                buffer.reserve(2048);
71                continue;
72            }
73            return None;
74        }
75        break;
76    }
77    let g = g.assume_init();
78    super::utils::cstr_to_rust(g.gr_name)
79}
80
81pub(crate) unsafe fn get_user_groups(
82    name: *const libc::c_char,
83    group_id: libc::gid_t,
84) -> Vec<Group> {
85    let mut buffer = Vec::with_capacity(2048);
86    let mut groups = Vec::with_capacity(256);
87
88    loop {
89        let mut nb_groups = groups.capacity();
90        if getgrouplist(
91            name,
92            group_id as _,
93            groups.as_mut_ptr(),
94            &mut nb_groups as *mut _ as *mut _,
95        ) == -1
96        {
97            // Ensure the length matches the number of returned groups.
98            // Needs to be updated for `Vec::reserve` to actually add additional capacity.
99            groups.set_len(nb_groups as _);
100            groups.reserve(256);
101            continue;
102        }
103        groups.set_len(nb_groups as _);
104        return groups
105            .iter()
106            .filter_map(|group_id| {
107                let name = get_group_name(*group_id as _, &mut buffer)?;
108                Some(Group {
109                    inner: crate::GroupInner::new(Gid(*group_id as _), name),
110                })
111            })
112            .collect();
113    }
114}
115
116// Not used by mac.
117#[cfg(not(any(target_os = "macos", target_os = "ios")))]
118pub(crate) fn get_users(users: &mut Vec<User>) {
119    use std::fs::File;
120    use std::io::Read;
121
122    #[inline]
123    fn parse_id(id: &str) -> Option<u32> {
124        id.parse::<u32>().ok()
125    }
126
127    users.clear();
128
129    let mut s = String::new();
130
131    let _ = File::open("/etc/passwd").and_then(|mut f| f.read_to_string(&mut s));
132    for line in s.lines() {
133        let mut parts = line.split(':');
134        if let Some(username) = parts.next() {
135            let mut parts = parts.skip(1);
136            // Skip the user if the uid cannot be parsed correctly
137            if let Some(uid) = parts.next().and_then(parse_id) {
138                if let Some(group_id) = parts.next().and_then(parse_id) {
139                    users.push(User {
140                        inner: UserInner::new(Uid(uid), Gid(group_id), username.to_owned()),
141                    });
142                }
143            }
144        }
145    }
146}
147
148#[cfg(any(target_os = "macos", target_os = "ios"))]
149pub(crate) use crate::unix::apple::users::get_users;