use super::*;
type ByteBuffer = Vec<MaybeUninit<c_char>>;
#[must_use]
pub(crate) struct ByteBufToken {}
pub(crate) trait FromLibc<Input>: Sized {
unsafe fn from_libc(
input: Input,
buffer_live: &'_ ByteBufToken,
) -> Result<Self, io::Error>;
}
type InnerCall<'c> = &'c mut dyn FnMut(
&mut [MaybeUninit<c_char>],
) -> c_int;
pub(crate) type LookupCall<'c, L> = &'c mut dyn FnMut(
*mut L,
*mut *mut L,
*mut c_char,
size_t,
) -> c_int;
pub(crate) fn call_repeatedly_with_bigger_buffer<'b>(
mlibc: impl MockableLibc,
buffer: &'b mut ByteBuffer,
sysconf: c_int,
call: InnerCall,
) -> Result<&'b ByteBufToken, io::Error> {
let mut want_size: usize = cmp::max(
unsafe { (mlibc.sysconf)(sysconf) }.try_into().unwrap_or(0),
100,
);
loop {
buffer.resize(want_size, MaybeUninit::uninit());
let r = call(buffer);
if r == 0 {
return Ok(&ByteBufToken {});
}
if r != libc::ERANGE {
return Err(io::Error::from_raw_os_error(r));
}
want_size = buffer
.len()
.checked_mul(2)
.ok_or(TooLargeBufferRequiredError)?;
}
}
impl FromLibc<Id> for Id {
unsafe fn from_libc(
input: Id,
_: &ByteBufToken,
) -> Result<Self, io::Error> {
Ok(input)
}
}
impl FromLibc<*mut c_char> for RawSafe {
unsafe fn from_libc(
input: *mut c_char,
_buffer_live: &ByteBufToken,
) -> Result<Self, io::Error> {
let input: *const c_char = input as _;
if input.is_null() {
return Err(UnexpectedNullPointerError.into());
}
let input = CStr::from_ptr(input);
Ok(input.to_bytes().into())
}
}
impl FromLibc<*mut *mut c_char> for Box<[RawSafe]> {
unsafe fn from_libc(
input: *mut *mut c_char,
buffer_live: &ByteBufToken,
) -> Result<Self, io::Error> {
if input.is_null() {
return Err(UnexpectedNullPointerError.into());
}
let pointers = (0..)
.map(|offset| input.offset(offset).read())
.take_while(|pointer| !pointer.is_null());
let mut output = Vec::with_capacity(pointers.clone().count());
for pointer in pointers {
output.push(FromLibc::from_libc(pointer, buffer_live)?);
}
Ok(output.into())
}
}
define_derive_deftly! {
FromLibc for struct, expect items:
impl FromLibc<NonNull<libc::${snake_case $tname}>>
for $tname<RawSafe>
{
unsafe fn from_libc(
input: NonNull<libc::${snake_case $tname}>,
buffer_live: &ByteBufToken,
) -> Result<Self, io::Error> {
let input = input.as_ref();
let output = $tname { $( ${select1 fmeta(dummy) {
$fname: NonExhaustive {}
} else {
$fname: {
let p = input
.${paste ${tmeta(abbrev) as str} _ $fname};
FromLibc::from_libc(p, buffer_live)?
},
}})};
Ok(output)
}
}
}
define_derive_deftly! {
Lookup for struct, expect items:
impl $tname<RawSafe> {
fn lookup(
mlibc: impl MockableLibc,
call: LookupCall<libc::${snake_case $tname}>
) -> io::Result<Option<Self>> {
#[allow(non_camel_case_types)]
type libc_struct = libc::${snake_case $tname};
let mut buffer = Default::default();
let mut out_buf = MaybeUninit::<libc_struct>::uninit();
let mut result = MaybeUninit::<*mut libc_struct>::uninit();
let out_buf = &mut out_buf;
let result = &mut result;
let sysconf = libc::
${paste _SC_GET
${shouty_snake_case ${tmeta(abbrev) as str}}
_R_SIZE_MAX};
let buffer_live = call_repeatedly_with_bigger_buffer(
mlibc,
&mut buffer,
sysconf,
&mut |buffer| {
let buffer_len = buffer.len();
let buffer: *mut MaybeUninit<c_char> = buffer.as_mut_ptr();
let buffer: *mut c_char = buffer as _;
call(
out_buf.as_mut_ptr(),
result.as_mut_ptr(),
buffer,
buffer_len,
)
},
)?;
let result: *mut libc_struct = unsafe { result.assume_init() };
let result = match NonNull::new(result) {
None => return Ok(None),
Some(y) => y,
};
let result: $tname<RawSafe> = unsafe {
FromLibc::from_libc(result, buffer_live)
}?;
Ok(Some(result))
}
}
}
pub(crate) fn getpwnam_inner<ML: MockableLibc>(
mlibc: ML,
name: &[u8],
) -> io::Result<Option<Passwd<RawSafe>>> {
let name = cstring_from(name)?;
Passwd::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
(mlibc.getpwnam_r)(name.as_ptr() as _, out_buf, buf, buflen, result)
})
}
pub(crate) fn getpwuid_inner<ML: MockableLibc>(
mlibc: ML,
uid: Id,
) -> io::Result<Option<Passwd<RawSafe>>> {
Passwd::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
(mlibc.getpwuid_r)(uid, out_buf, buf, buflen, result)
})
}
pub(crate) fn getgrnam_inner<ML: MockableLibc>(
mlibc: ML,
name: &[u8],
) -> io::Result<Option<Group<RawSafe>>> {
let name = cstring_from(name)?;
Group::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
(mlibc.getgrnam_r)(name.as_ptr() as _, out_buf, buf, buflen, result)
})
}
pub(crate) fn getgrgid_inner<ML: MockableLibc>(
mlibc: ML,
gid: Id,
) -> io::Result<Option<Group<RawSafe>>> {
Group::lookup(mlibc, &mut |out_buf, result, buf, buflen| unsafe {
(mlibc.getgrgid_r)(gid, out_buf, buf, buflen, result)
})
}
pub(crate) fn getgroups_inner(
mlibc: impl MockableLibc,
) -> io::Result<Vec<Id>> {
let overflow = |_| TooLargeBufferRequiredError;
let mut want_size = 0;
let mut buffer: Vec<Id> = vec![];
loop {
buffer.reserve(want_size);
buffer.truncate(0);
let r = unsafe {
let size = buffer.capacity().try_into().map_err(overflow)?;
let list: *mut Id = buffer.as_mut_ptr();
(mlibc.getgroups)(size, list)
};
if r < 0 {
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(libc::EINVAL) {
want_size = 0;
continue;
}
return Err(error);
}
let r: usize = r.try_into().map_err(overflow)?;
if r <= buffer.capacity() {
unsafe { buffer.set_len(r) };
return Ok(buffer);
}
want_size = r;
}
}
macro_rules! define_getid_unsafe { {
$fn:ident: $id:ident. $f:ident, $doc:literal $( $real:literal )?
} => { paste!{
#[inline]
pub(crate) fn [<$fn _inner>](
mlibc: impl MockableLibc,
) -> Id {
unsafe { (mlibc.$fn)() }
}
} }; {
$fn:ident: $id:ident. ($( $f:ident )*), $doc:literal $( $real:literal )?
} => { paste!{
#[inline]
pub(crate) fn [<$fn _inner>](
mlibc: impl MockableLibc,
) -> (Id, Id, Id) {
let mut buf: (Id, Id, Id) = Default::default();
let r = unsafe {
(mlibc.$fn)(
&mut buf.0,
&mut buf.1,
&mut buf.2,
)
};
assert!(r == 0, "getres* failed!");
buf
}
} } }
for_getid_wrappers! { define_getid_unsafe }