Skip to main content

std/sys/pal/unix/
conf.rs

1#[cfg(test)]
2mod tests;
3
4#[cfg(not(target_os = "espidf"))]
5pub fn page_size() -> usize {
6    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
7}
8
9/// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only
10/// used on Darwin, but should work on any unix (in case we need to get
11/// `_CS_PATH` or `_CS_V[67]_ENV` in the future).
12///
13/// [posix_confstr]:
14///     https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html
15//
16// FIXME: Support `confstr` in Miri.
17#[cfg(all(target_vendor = "apple", not(miri)))]
18pub fn confstr(
19    key: crate::ffi::c_int,
20    size_hint: Option<usize>,
21) -> crate::io::Result<crate::ffi::OsString> {
22    use crate::ffi::OsString;
23    use crate::io;
24    use crate::os::unix::ffi::OsStringExt;
25
26    let mut buf: Vec<u8> = Vec::with_capacity(0);
27    let mut bytes_needed_including_nul = size_hint
28        .unwrap_or_else(|| {
29            // Treat "None" as "do an extra call to get the length". In theory
30            // we could move this into the loop below, but it's hard to do given
31            // that it isn't 100% clear if it's legal to pass 0 for `len` when
32            // the buffer isn't null.
33            unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
34        })
35        .max(1);
36    // If the value returned by `confstr` is greater than the len passed into
37    // it, then the value was truncated, meaning we need to retry. Note that
38    // while `confstr` results don't seem to change for a process, it's unclear
39    // if this is guaranteed anywhere, so looping does seem required.
40    while bytes_needed_including_nul > buf.capacity() {
41        // We write into the spare capacity of `buf`. This lets us avoid
42        // changing buf's `len`, which both simplifies `reserve` computation,
43        // allows working with `Vec<u8>` instead of `Vec<MaybeUninit<u8>>`, and
44        // may avoid a copy, since the Vec knows that none of the bytes are needed
45        // when reallocating (well, in theory anyway).
46        buf.reserve(bytes_needed_including_nul);
47        // `confstr` returns
48        // - 0 in the case of errors: we break and return an error.
49        // - The number of bytes written, iff the provided buffer is enough to
50        //   hold the entire value: we break and return the data in `buf`.
51        // - Otherwise, the number of bytes needed (including nul): we go
52        //   through the loop again.
53        bytes_needed_including_nul =
54            unsafe { libc::confstr(key, buf.as_mut_ptr().cast(), buf.capacity()) };
55    }
56    // `confstr` returns 0 in the case of an error.
57    if bytes_needed_including_nul == 0 {
58        return Err(io::Error::last_os_error());
59    }
60    // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a
61    // non-zero value, meaning `bytes_needed_including_nul` bytes were
62    // initialized.
63    unsafe {
64        buf.set_len(bytes_needed_including_nul);
65        // Remove the NUL-terminator.
66        let last_byte = buf.pop();
67        // ... and smoke-check that it *was* a NUL-terminator.
68        assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated");
69    };
70    Ok(OsString::from_vec(buf))
71}
72
73#[cfg(all(target_os = "linux", target_env = "gnu"))]
74pub fn glibc_version() -> Option<(usize, usize)> {
75    use crate::ffi::CStr;
76
77    unsafe extern "C" {
78        fn gnu_get_libc_version() -> *const libc::c_char;
79    }
80    let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
81    if let Ok(version_str) = version_cstr.to_str() {
82        parse_glibc_version(version_str)
83    } else {
84        None
85    }
86}
87
88/// Returns Some((major, minor)) if the string is a valid "x.y" version,
89/// ignoring any extra dot-separated parts. Otherwise return None.
90#[cfg(all(target_os = "linux", target_env = "gnu"))]
91fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
92    let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
93    match (parsed_ints.next(), parsed_ints.next()) {
94        (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
95        _ => None,
96    }
97}