pwd_grp/
unsafe_.rs

1//! Unsafe internal
2
3use super::*;
4//use mock::*;
5
6/// Byte buffer, that wes pass to `get*_r`
7type ByteBuffer = Vec<MaybeUninit<c_char>>;
8
9/// Lifetime token that represents validity of borrows, for C pointers
10#[must_use]
11pub(crate) struct ByteBufToken {}
12
13/// Conversion from raw libc pointer output to safe Rust types
14///
15/// `Input` is typically a raw pointer, such as found in `struct passwd`.
16///
17/// `Self` is a safe, owned, Rust type.  Typically, `Raw` aka `Box<[u8]>`.
18pub(crate) trait FromLibc<Input>: Sized {
19    /// Converts `Input` to `Self`
20    ///
21    /// ### SAFETY
22    ///
23    /// Any raw pointers in `Input` are guaranteed to live
24    /// (only) as long as `buffer_live`.
25    unsafe fn from_libc(
26        input: Input,
27        buffer_live: &'_ ByteBufToken,
28    ) -> Result<Self, io::Error>;
29}
30
31/// Closure parameter to `call_repeatedly_with_bigger_buffer`
32///
33/// ### SAFETY
34///
35/// The returned integer must have the semantics documented in `get*_r`.
36/// The buffer is for passing to `get*_r`.
37type InnerCall<'c> = &'c mut dyn FnMut(
38    // The byte buffer, for passing to `get*_r`
39    &mut [MaybeUninit<c_char>],
40) -> c_int;
41
42/// Closure parameter to `Passwd::lookup` and `Group::lookup`
43///
44/// The parameters are all for passing through to `get*_r`.
45///
46/// `L` is the libc type: `struct passwd` or `struct group`.
47///
48/// ### SAFETY
49///
50/// The returned integer must have the semantics documented in `get*_r`.
51/// The arguments must be passed to `get*_r`.
52pub(crate) type LookupCall<'c, L> = &'c mut dyn FnMut(
53    // `grp` or `pwd`, the output struct buffer
54    *mut L,
55    // `result`, where the output pointer will be stored
56    *mut *mut L,
57    // `buffer` - the byte buffer
58    *mut c_char,
59    // `bufsize` - the byte buffer length
60    size_t,
61) -> c_int;
62
63/// Repeatedly calls a `get_*r` function until the buffer is big enough
64///
65/// ### SAFETY
66///
67/// `sysconf` must be a reasonable value to pass to `sysconf(2)`
68///  to enquire about the initial buffer size.
69///
70/// `call` must have the correct semantics, as described in `InnerCall`.
71///
72/// On `Ok` return `call` returned zero (no error).
73///
74/// The `ByteBufToken` is borrowed from `buffer`:
75/// all the outputs written by `call` are valid at least that long.
76pub(crate) fn call_repeatedly_with_bigger_buffer<'b>(
77    mlibc: impl MockableLibc,
78    buffer: &'b mut ByteBuffer,
79    sysconf: c_int,
80    call: InnerCall,
81) -> Result<&'b ByteBufToken, io::Error> {
82    let mut want_size: usize = cmp::max(
83        unsafe { (mlibc.sysconf)(sysconf) }.try_into().unwrap_or(0),
84        100,
85    );
86    loop {
87        buffer.resize(want_size, MaybeUninit::uninit());
88
89        let r = call(buffer);
90        if r == 0 {
91            return Ok(&ByteBufToken {});
92        }
93        if r != libc::ERANGE {
94            return Err(io::Error::from_raw_os_error(r));
95        }
96        want_size = buffer
97            .len()
98            .checked_mul(2)
99            .ok_or(TooLargeBufferRequiredError)?;
100    }
101}
102
103impl FromLibc<Id> for Id {
104    unsafe fn from_libc(
105        input: Id,
106        _: &ByteBufToken,
107    ) -> Result<Self, io::Error> {
108        Ok(input)
109    }
110}
111
112impl FromLibc<*mut c_char> for RawSafe {
113    unsafe fn from_libc(
114        input: *mut c_char,
115        _buffer_live: &ByteBufToken,
116    ) -> Result<Self, io::Error> {
117        let input: *const c_char = input as _;
118        if input.is_null() {
119            return Err(UnexpectedNullPointerError.into());
120        }
121        let input = CStr::from_ptr(input);
122        Ok(input.to_bytes().into())
123    }
124}
125
126/// For `group.gr_mem`, the list of group member names
127impl FromLibc<*mut *mut c_char> for Box<[RawSafe]> {
128    unsafe fn from_libc(
129        input: *mut *mut c_char,
130        buffer_live: &ByteBufToken,
131    ) -> Result<Self, io::Error> {
132        if input.is_null() {
133            return Err(UnexpectedNullPointerError.into());
134        }
135        let pointers = (0..)
136            .map(|offset| input.offset(offset).read())
137            .take_while(|pointer| !pointer.is_null());
138
139        let mut output = Vec::with_capacity(pointers.clone().count());
140        for pointer in pointers {
141            output.push(FromLibc::from_libc(pointer, buffer_live)?);
142        }
143        Ok(output.into())
144    }
145}
146
147define_derive_deftly! {
148    FromLibc for struct, expect items:
149
150    impl FromLibc<NonNull<libc::${snake_case $tname}>>
151        for $tname<RawSafe>
152    {
153        unsafe fn from_libc(
154            // Eg, `struct passwd`
155            input: NonNull<libc::${snake_case $tname}>,
156            buffer_live: &ByteBufToken,
157        ) -> Result<Self, io::Error> {
158            let input = input.as_ref();
159            // Construct the output by calling FromLibc::from_libc
160            // on each field in `input`.
161            let output = $tname { $( ${select1 fmeta(dummy) {
162                $fname: NonExhaustive {}
163            } else {
164                $fname: {
165                    let p = input
166                        // Input fields are pw_* and gr_*
167                        .${paste ${tmeta(abbrev) as str} _ $fname};
168
169                    FromLibc::from_libc(p, buffer_live)?
170                },
171            }})};
172            Ok(output)
173        }
174    }
175}
176
177define_derive_deftly! {
178    Lookup for struct, expect items:
179
180    impl $tname<RawSafe> {
181        /// Perform a lookup and yield a safe Rust type containing `RawSafe`
182        ///
183        /// ### SAFETY
184        ///
185        /// `call` must match the description of `LookupCall`.
186        fn lookup(
187            mlibc: impl MockableLibc,
188            call: LookupCall<libc::${snake_case $tname}>
189        ) -> io::Result<Option<Self>> {
190            #[allow(non_camel_case_types)]
191            type libc_struct = libc::${snake_case $tname};
192
193            let mut buffer = Default::default();
194            let mut out_buf = MaybeUninit::<libc_struct>::uninit();
195            let mut result = MaybeUninit::<*mut libc_struct>::uninit();
196
197            // SAFETY
198            // MaybeUninit is Copy! It's important that we don't move these,
199            // or we could give get*_r pointers to the copies.
200            let out_buf = &mut out_buf;
201            let result = &mut result;
202
203            // _SC_GET_PW_R_SIZE_MAX and ..._GR_...
204            let sysconf = libc::
205                ${paste _SC_GET
206                        ${shouty_snake_case ${tmeta(abbrev) as str}}
207                        _R_SIZE_MAX};
208
209            let buffer_live = call_repeatedly_with_bigger_buffer(
210                mlibc,
211                &mut buffer,
212                sysconf,
213                &mut |buffer| {
214                    // SAFETY
215                    // We convert the borrows of `buffer` and so on into the
216                    // arguments to `get*_r`.  Those functions don't care
217                    // about initialised data.
218                    let buffer_len = buffer.len();
219                    let buffer: *mut MaybeUninit<c_char> = buffer.as_mut_ptr();
220                    let buffer: *mut c_char = buffer as _;
221                    call(
222                        out_buf.as_mut_ptr(),
223                        result.as_mut_ptr(),
224                        buffer,
225                        buffer_len,
226                    )
227                },
228            )?;
229
230            // SAFETY
231            // `call_repeatedly_with_bigger_buffer` only returns Ok
232            // if `call` returned zero, which means `get*_r*` returned zero
233            // which means they did store the answer in `result`.
234            // It might be a null pointer.
235            let result: *mut libc_struct = unsafe { result.assume_init() };
236
237            let result = match NonNull::new(result) {
238                None => return Ok(None),
239                Some(y) => y,
240            };
241
242            // SAFETY
243            // `result` did come from `getpw*_r`, as above,
244            // and it's all borrwed from `buffer` (as per buffer_live).
245            let result: $tname<RawSafe> = unsafe {
246                FromLibc::from_libc(result, buffer_live)
247            }?;
248
249            Ok(Some(result))
250        }
251    }
252}
253
254pub(crate) fn getpwnam_inner<ML: MockableLibc>(
255    mlibc: ML,
256    name: &[u8],
257) -> io::Result<Option<Passwd<RawSafe>>> {
258    let name = cstring_from(name)?;
259
260    Passwd::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
261        (mlibc.getpwnam_r)(name.as_ptr() as _, out_buf, buf, buflen, result)
262    })
263}
264
265pub(crate) fn getpwuid_inner<ML: MockableLibc>(
266    mlibc: ML,
267    uid: Id,
268) -> io::Result<Option<Passwd<RawSafe>>> {
269    Passwd::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
270        (mlibc.getpwuid_r)(uid, out_buf, buf, buflen, result)
271    })
272}
273
274pub(crate) fn getgrnam_inner<ML: MockableLibc>(
275    mlibc: ML,
276    name: &[u8],
277) -> io::Result<Option<Group<RawSafe>>> {
278    let name = cstring_from(name)?;
279
280    Group::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
281        (mlibc.getgrnam_r)(name.as_ptr() as _, out_buf, buf, buflen, result)
282    })
283}
284
285pub(crate) fn getgrgid_inner<ML: MockableLibc>(
286    mlibc: ML,
287    gid: Id,
288) -> io::Result<Option<Group<RawSafe>>> {
289    Group::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
290        (mlibc.getgrgid_r)(gid, out_buf, buf, buflen, result)
291    })
292}
293
294pub(crate) fn getgroups_inner(
295    mlibc: impl MockableLibc,
296) -> io::Result<Vec<Id>> {
297    // This is a bit like call_repeatedly_with_bigger_buffer
298    // but getgroups has a different calling convention to get*_r
299    let overflow = |_| TooLargeBufferRequiredError;
300
301    let mut want_size = 0;
302    let mut buffer: Vec<Id> = vec![];
303    loop {
304        buffer.reserve(want_size);
305        buffer.truncate(0);
306        let r = unsafe {
307            let size = buffer.capacity().try_into().map_err(overflow)?;
308            let list: *mut Id = buffer.as_mut_ptr();
309            (mlibc.getgroups)(size, list)
310        };
311        if r < 0 {
312            let error = io::Error::last_os_error();
313            if error.raw_os_error() == Some(libc::EINVAL) {
314                // We'll go around again asking what the length should be
315                want_size = 0;
316                continue;
317            }
318            return Err(error);
319        }
320        let r: usize = r.try_into().map_err(overflow)?;
321
322        if r <= buffer.capacity() {
323            unsafe { buffer.set_len(r) };
324            return Ok(buffer);
325        }
326
327        // Now r > buffer.capcity(), we must go around and resze
328        want_size = r;
329    }
330}
331
332macro_rules! define_getid_unsafe { {
333    $fn:ident: $id:ident. $f:ident, $doc:literal $( $real:literal )?
334} => { paste!{
335    #[inline]
336    pub(crate) fn [<$fn _inner>](
337        mlibc: impl MockableLibc,
338    ) -> Id {
339        // get{,r}{uid,gid} are defined to never fail
340        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/getuid.html
341        unsafe { (mlibc.$fn)() }
342    }
343} }; {
344    $fn:ident: $id:ident. ($( $f:ident )*), $doc:literal $( $real:literal )?
345} => { paste!{
346    #[inline]
347    pub(crate) fn [<$fn _inner>](
348        mlibc: impl MockableLibc,
349    ) -> (Id, Id, Id) {
350        let mut buf: (Id, Id, Id) = Default::default();
351
352        let r = unsafe {
353            (mlibc.$fn)(
354                &mut buf.0,
355                &mut buf.1,
356                &mut buf.2,
357            )
358        };
359        assert!(r == 0, "getres* failed!");
360
361        buf
362    }
363} } }
364
365for_getid_wrappers! { define_getid_unsafe }