Skip to main content

relibc/header/pwd/
mod.rs

1//! `pwd.h` implementation.
2//!
3//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/pwd.h.html>.
4
5use alloc::{boxed::Box, vec::Vec};
6use core::{
7    ops::{Deref, DerefMut},
8    pin::Pin,
9    ptr,
10};
11
12use crate::{
13    fs::File,
14    header::{errno, fcntl, string::strcmp},
15    io::{BufReader, SeekFrom, prelude::*},
16    platform::{
17        self,
18        types::{c_char, c_int, gid_t, size_t, uid_t},
19    },
20    raw_cell::RawCell,
21};
22
23#[cfg(target_os = "linux")]
24mod linux;
25#[cfg(target_os = "redox")]
26mod redox;
27
28#[cfg(target_os = "linux")]
29use self::linux as sys;
30#[cfg(target_os = "redox")]
31use self::redox as sys;
32
33/// cbindgen:ignore
34#[cfg(target_os = "linux")]
35const SEPARATOR: u8 = b':';
36
37/// cbindgen:ignore
38#[cfg(target_os = "redox")]
39const SEPARATOR: u8 = b';';
40
41/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/pwd.h.html>
42/// for POSIX minimum requirements, and
43/// <https://www.man7.org/linux/man-pages/man3/getpwnam.3.html> for further
44/// details.
45#[allow(non_camel_case_types)]
46#[repr(C)]
47#[derive(Debug)]
48pub struct passwd {
49    pub pw_name: *mut c_char,
50    pub pw_passwd: *mut c_char,
51    pub pw_uid: uid_t,
52    pub pw_gid: gid_t,
53    pub pw_gecos: *mut c_char,
54    pub pw_dir: *mut c_char,
55    pub pw_shell: *mut c_char,
56}
57
58/// cbindgen:ignore
59static mut PASSWD_BUF: Option<MaybeAllocated> = None;
60/// cbindgen:ignore
61static PASSWD: RawCell<passwd> = RawCell::new(passwd {
62    pw_name: ptr::null_mut(),
63    pw_passwd: ptr::null_mut(),
64    pw_uid: 0,
65    pw_gid: 0,
66    pw_gecos: ptr::null_mut(),
67    pw_dir: ptr::null_mut(),
68    pw_shell: ptr::null_mut(),
69});
70
71#[derive(Clone, Copy, Debug)]
72struct DestBuffer {
73    ptr: *mut u8,
74    len: usize,
75}
76
77#[derive(Debug)]
78enum MaybeAllocated {
79    Owned(Pin<Box<[u8]>>),
80    Borrowed(DestBuffer),
81}
82impl Deref for MaybeAllocated {
83    type Target = [u8];
84
85    fn deref(&self) -> &Self::Target {
86        match self {
87            MaybeAllocated::Owned(boxed) => boxed,
88            MaybeAllocated::Borrowed(dst) => unsafe {
89                core::slice::from_raw_parts(dst.ptr, dst.len)
90            },
91        }
92    }
93}
94impl DerefMut for MaybeAllocated {
95    fn deref_mut(&mut self) -> &mut Self::Target {
96        match self {
97            MaybeAllocated::Owned(boxed) => boxed,
98            MaybeAllocated::Borrowed(dst) => unsafe {
99                core::slice::from_raw_parts_mut(dst.ptr, dst.len)
100            },
101        }
102    }
103}
104
105#[derive(Debug)]
106struct OwnedPwd {
107    buffer: MaybeAllocated,
108    reference: passwd,
109}
110
111impl OwnedPwd {
112    fn into_global(self) -> *mut passwd {
113        unsafe {
114            PASSWD_BUF = Some(self.buffer);
115            PASSWD.unsafe_set(self.reference);
116            PASSWD.as_mut_ptr()
117        }
118    }
119}
120
121#[derive(Clone, Copy, Debug)]
122enum Cause {
123    Eof,
124    Other,
125}
126
127/// cbindgen:ignore
128static READER: RawCell<Option<BufReader<File>>> = RawCell::new(None);
129
130fn parsed<I, O>(buf: Option<I>) -> Option<O>
131where
132    I: core::borrow::Borrow<[u8]>,
133    O: core::str::FromStr,
134{
135    let buf = buf?;
136    let string = core::str::from_utf8(buf.borrow()).ok()?;
137    string.parse().ok()
138}
139
140/// See <https://www.man7.org/linux/man-pages/man3/getpwent_r.3.html>.
141///
142/// Non-POSIX
143fn getpwent_r(
144    reader: &mut BufReader<File>,
145    destination: Option<DestBuffer>,
146) -> Result<OwnedPwd, Cause> {
147    let mut buf = Vec::new();
148    if reader
149        .read_until(b'\n', &mut buf)
150        .map_err(|_| Cause::Other)?
151        == 0
152    {
153        return Err(Cause::Eof);
154    }
155
156    // Replace all occurences of seperator with terminating NUL byte
157    let mut start = 0;
158    while let Some(i) = memchr::memchr(SEPARATOR, &buf[start..]) {
159        buf[start + i] = 0;
160        start += i + 1;
161    }
162
163    // Place terminating NUL byte at the end, replace newline
164    let last = buf.last_mut();
165    if last == Some(&mut b'\n') {
166        *last.unwrap() = 0;
167    } else {
168        buf.push(0);
169    }
170
171    let mut buf = match destination {
172        None => MaybeAllocated::Owned(Box::into_pin(buf.into_boxed_slice())),
173        Some(dst) => {
174            let mut new = MaybeAllocated::Borrowed(dst);
175            if new.len() < buf.len() {
176                platform::ERRNO.set(errno::ERANGE);
177                return Err(Cause::Other);
178            }
179            new[..buf.len()].copy_from_slice(&buf);
180            new
181        }
182    };
183
184    // Chop up the result into a valid structure
185    let passwd = sys::split(&mut buf).ok_or(Cause::Other)?;
186
187    Ok(OwnedPwd {
188        buffer: buf,
189        reference: passwd,
190    })
191}
192
193fn pwd_lookup<F>(mut matches: F, destination: Option<DestBuffer>) -> Result<OwnedPwd, Cause>
194where
195    F: FnMut(&passwd) -> bool,
196{
197    let file = match File::open(c"/etc/passwd".into(), fcntl::O_RDONLY) {
198        Ok(file) => file,
199        Err(_) => return Err(Cause::Other),
200    };
201
202    let mut reader = BufReader::new(file);
203
204    loop {
205        let entry = getpwent_r(&mut reader, destination)?;
206
207        if matches(&entry.reference) {
208            return Ok(entry);
209        }
210    }
211}
212
213unsafe fn mux(
214    status: Result<OwnedPwd, Cause>,
215    out: *mut passwd,
216    result: *mut *mut passwd,
217) -> c_int {
218    match status {
219        Ok(owned) => {
220            unsafe { *out = owned.reference };
221            unsafe { *result = out };
222            0
223        }
224        Err(Cause::Eof) => {
225            unsafe { *result = ptr::null_mut() };
226            0
227        }
228        Err(Cause::Other) => {
229            unsafe { *result = ptr::null_mut() };
230            -1
231        }
232    }
233}
234
235/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endpwent.html>.
236#[unsafe(no_mangle)]
237pub extern "C" fn endpwent() {
238    unsafe {
239        READER.unsafe_set(None);
240    }
241}
242
243/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endpwent.html>.
244#[unsafe(no_mangle)]
245pub extern "C" fn getpwent() -> *mut passwd {
246    let reader = match unsafe { &mut *READER.as_mut_ptr() } {
247        Some(reader) => reader,
248        None => {
249            let file = match File::open(c"/etc/passwd".into(), fcntl::O_RDONLY) {
250                Ok(file) => file,
251                Err(_) => return ptr::null_mut(),
252            };
253            let reader = BufReader::new(file);
254            unsafe {
255                READER.unsafe_set(Some(reader));
256                READER.unsafe_mut().as_mut().unwrap()
257            }
258        }
259    };
260    getpwent_r(reader, None)
261        .map(|res| res.into_global())
262        .unwrap_or(ptr::null_mut())
263}
264
265/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getpwnam.html>.
266#[unsafe(no_mangle)]
267pub unsafe extern "C" fn getpwnam(name: *const c_char) -> *mut passwd {
268    pwd_lookup(|parts| unsafe { strcmp(parts.pw_name, name) } == 0, None)
269        .map(|res| res.into_global())
270        .unwrap_or(ptr::null_mut())
271}
272
273/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getpwnam.html>.
274#[unsafe(no_mangle)]
275pub unsafe extern "C" fn getpwnam_r(
276    name: *const c_char,
277    out: *mut passwd,
278    buf: *mut c_char,
279    size: size_t,
280    result: *mut *mut passwd,
281) -> c_int {
282    unsafe {
283        mux(
284            pwd_lookup(
285                |parts| strcmp(parts.pw_name, name) == 0,
286                Some(DestBuffer {
287                    ptr: buf.cast::<u8>(),
288                    len: size,
289                }),
290            ),
291            out,
292            result,
293        )
294    }
295}
296
297/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getpwuid.html>.
298#[unsafe(no_mangle)]
299pub extern "C" fn getpwuid(uid: uid_t) -> *mut passwd {
300    pwd_lookup(|parts| parts.pw_uid == uid, None)
301        .map(|res| res.into_global())
302        .unwrap_or(ptr::null_mut())
303}
304
305/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getpwuid.html>.
306#[unsafe(no_mangle)]
307pub unsafe extern "C" fn getpwuid_r(
308    uid: uid_t,
309    out: *mut passwd,
310    buf: *mut c_char,
311    size: size_t,
312    result: *mut *mut passwd,
313) -> c_int {
314    let slice = unsafe { core::slice::from_raw_parts_mut(buf.cast::<u8>(), size) };
315    unsafe {
316        mux(
317            pwd_lookup(
318                |part| part.pw_uid == uid,
319                Some(DestBuffer {
320                    ptr: buf.cast::<u8>(),
321                    len: size,
322                }),
323            ),
324            out,
325            result,
326        )
327    }
328}
329
330/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endpwent.html>.
331#[unsafe(no_mangle)]
332pub extern "C" fn setpwent() {
333    if let Some(reader) = unsafe { &mut *READER.as_mut_ptr() } {
334        let _ = reader.seek(SeekFrom::Start(0));
335    }
336}