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