std/sys/pal/unix/
os.rs

1//! Implementation of `std::os` functionality for unix systems
2
3#![allow(unused_imports)] // lots of cfg code here
4
5#[cfg(test)]
6mod tests;
7
8use libc::{c_char, c_int, c_void};
9
10use crate::ffi::{CStr, OsStr, OsString};
11use crate::os::unix::prelude::*;
12use crate::path::{self, PathBuf};
13use crate::sys::common::small_c_string::run_path_with_cstr;
14use crate::sys::cvt;
15use crate::{fmt, io, iter, mem, ptr, slice, str};
16
17const TMPBUF_SZ: usize = 128;
18
19const PATH_SEPARATOR: u8 = if cfg!(target_os = "redox") { b';' } else { b':' };
20
21unsafe extern "C" {
22    #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
23    #[cfg_attr(
24        any(
25            target_os = "linux",
26            target_os = "emscripten",
27            target_os = "fuchsia",
28            target_os = "l4re",
29            target_os = "hurd",
30        ),
31        link_name = "__errno_location"
32    )]
33    #[cfg_attr(
34        any(
35            target_os = "netbsd",
36            target_os = "openbsd",
37            target_os = "cygwin",
38            target_os = "android",
39            target_os = "redox",
40            target_os = "nuttx",
41            target_env = "newlib"
42        ),
43        link_name = "__errno"
44    )]
45    #[cfg_attr(any(target_os = "solaris", target_os = "illumos"), link_name = "___errno")]
46    #[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
47    #[cfg_attr(any(target_os = "freebsd", target_vendor = "apple"), link_name = "__error")]
48    #[cfg_attr(target_os = "haiku", link_name = "_errnop")]
49    #[cfg_attr(target_os = "aix", link_name = "_Errno")]
50    // SAFETY: this will always return the same pointer on a given thread.
51    #[unsafe(ffi_const)]
52    pub safe fn errno_location() -> *mut c_int;
53}
54
55/// Returns the platform-specific value of errno
56#[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
57#[inline]
58pub fn errno() -> i32 {
59    unsafe { (*errno_location()) as i32 }
60}
61
62/// Sets the platform-specific value of errno
63// needed for readdir and syscall!
64#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks"), not(target_os = "rtems")))]
65#[allow(dead_code)] // but not all target cfgs actually end up using it
66#[inline]
67pub fn set_errno(e: i32) {
68    unsafe { *errno_location() = e as c_int }
69}
70
71#[cfg(target_os = "vxworks")]
72#[inline]
73pub fn errno() -> i32 {
74    unsafe { libc::errnoGet() }
75}
76
77#[cfg(target_os = "rtems")]
78#[inline]
79pub fn errno() -> i32 {
80    unsafe extern "C" {
81        #[thread_local]
82        static _tls_errno: c_int;
83    }
84
85    unsafe { _tls_errno as i32 }
86}
87
88#[cfg(target_os = "dragonfly")]
89#[inline]
90pub fn errno() -> i32 {
91    unsafe extern "C" {
92        #[thread_local]
93        static errno: c_int;
94    }
95
96    unsafe { errno as i32 }
97}
98
99#[cfg(target_os = "dragonfly")]
100#[allow(dead_code)]
101#[inline]
102pub fn set_errno(e: i32) {
103    unsafe extern "C" {
104        #[thread_local]
105        static mut errno: c_int;
106    }
107
108    unsafe {
109        errno = e;
110    }
111}
112
113/// Gets a detailed string description for the given error number.
114pub fn error_string(errno: i32) -> String {
115    unsafe extern "C" {
116        #[cfg_attr(
117            all(
118                any(
119                    target_os = "linux",
120                    target_os = "hurd",
121                    target_env = "newlib",
122                    target_os = "cygwin"
123                ),
124                not(target_env = "ohos")
125            ),
126            link_name = "__xpg_strerror_r"
127        )]
128        fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: libc::size_t) -> c_int;
129    }
130
131    let mut buf = [0 as c_char; TMPBUF_SZ];
132
133    let p = buf.as_mut_ptr();
134    unsafe {
135        if strerror_r(errno as c_int, p, buf.len()) < 0 {
136            panic!("strerror_r failure");
137        }
138
139        let p = p as *const _;
140        // We can't always expect a UTF-8 environment. When we don't get that luxury,
141        // it's better to give a low-quality error message than none at all.
142        String::from_utf8_lossy(CStr::from_ptr(p).to_bytes()).into()
143    }
144}
145
146#[cfg(target_os = "espidf")]
147pub fn getcwd() -> io::Result<PathBuf> {
148    Ok(PathBuf::from("/"))
149}
150
151#[cfg(not(target_os = "espidf"))]
152pub fn getcwd() -> io::Result<PathBuf> {
153    let mut buf = Vec::with_capacity(512);
154    loop {
155        unsafe {
156            let ptr = buf.as_mut_ptr() as *mut libc::c_char;
157            if !libc::getcwd(ptr, buf.capacity()).is_null() {
158                let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
159                buf.set_len(len);
160                buf.shrink_to_fit();
161                return Ok(PathBuf::from(OsString::from_vec(buf)));
162            } else {
163                let error = io::Error::last_os_error();
164                if error.raw_os_error() != Some(libc::ERANGE) {
165                    return Err(error);
166                }
167            }
168
169            // Trigger the internal buffer resizing logic of `Vec` by requiring
170            // more space than the current capacity.
171            let cap = buf.capacity();
172            buf.set_len(cap);
173            buf.reserve(1);
174        }
175    }
176}
177
178#[cfg(target_os = "espidf")]
179pub fn chdir(_p: &path::Path) -> io::Result<()> {
180    super::unsupported::unsupported()
181}
182
183#[cfg(not(target_os = "espidf"))]
184pub fn chdir(p: &path::Path) -> io::Result<()> {
185    let result = run_path_with_cstr(p, &|p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
186    if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
187}
188
189pub type SplitPaths<'a> = impl Iterator<Item = PathBuf>;
190
191#[define_opaque(SplitPaths)]
192pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
193    unparsed
194        .as_bytes()
195        .split(|&b| b == PATH_SEPARATOR)
196        .map(|part| PathBuf::from(OsStr::from_bytes(part)))
197}
198
199#[derive(Debug)]
200pub struct JoinPathsError;
201
202pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
203where
204    I: Iterator<Item = T>,
205    T: AsRef<OsStr>,
206{
207    let mut joined = Vec::new();
208
209    for (i, path) in paths.enumerate() {
210        let path = path.as_ref().as_bytes();
211        if i > 0 {
212            joined.push(PATH_SEPARATOR)
213        }
214        if path.contains(&PATH_SEPARATOR) {
215            return Err(JoinPathsError);
216        }
217        joined.extend_from_slice(path);
218    }
219    Ok(OsStringExt::from_vec(joined))
220}
221
222impl fmt::Display for JoinPathsError {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
225    }
226}
227
228impl crate::error::Error for JoinPathsError {}
229
230#[cfg(target_os = "aix")]
231pub fn current_exe() -> io::Result<PathBuf> {
232    #[cfg(test)]
233    use realstd::env;
234
235    #[cfg(not(test))]
236    use crate::env;
237    use crate::io::ErrorKind;
238
239    let exe_path = env::args().next().ok_or(io::const_error!(
240        ErrorKind::NotFound,
241        "an executable path was not found because no arguments were provided through argv",
242    ))?;
243    let path = PathBuf::from(exe_path);
244    if path.is_absolute() {
245        return path.canonicalize();
246    }
247    // Search PWD to infer current_exe.
248    if let Some(pstr) = path.to_str()
249        && pstr.contains("/")
250    {
251        return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
252    }
253    // Search PATH to infer current_exe.
254    if let Some(p) = env::var_os(OsStr::from_bytes("PATH".as_bytes())) {
255        for search_path in split_paths(&p) {
256            let pb = search_path.join(&path);
257            if pb.is_file()
258                && let Ok(metadata) = crate::fs::metadata(&pb)
259                && metadata.permissions().mode() & 0o111 != 0
260            {
261                return pb.canonicalize();
262            }
263        }
264    }
265    Err(io::const_error!(ErrorKind::NotFound, "an executable path was not found"))
266}
267
268#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
269pub fn current_exe() -> io::Result<PathBuf> {
270    unsafe {
271        let mut mib = [
272            libc::CTL_KERN as c_int,
273            libc::KERN_PROC as c_int,
274            libc::KERN_PROC_PATHNAME as c_int,
275            -1 as c_int,
276        ];
277        let mut sz = 0;
278        cvt(libc::sysctl(
279            mib.as_mut_ptr(),
280            mib.len() as libc::c_uint,
281            ptr::null_mut(),
282            &mut sz,
283            ptr::null_mut(),
284            0,
285        ))?;
286        if sz == 0 {
287            return Err(io::Error::last_os_error());
288        }
289        let mut v: Vec<u8> = Vec::with_capacity(sz);
290        cvt(libc::sysctl(
291            mib.as_mut_ptr(),
292            mib.len() as libc::c_uint,
293            v.as_mut_ptr() as *mut libc::c_void,
294            &mut sz,
295            ptr::null_mut(),
296            0,
297        ))?;
298        if sz == 0 {
299            return Err(io::Error::last_os_error());
300        }
301        v.set_len(sz - 1); // chop off trailing NUL
302        Ok(PathBuf::from(OsString::from_vec(v)))
303    }
304}
305
306#[cfg(target_os = "netbsd")]
307pub fn current_exe() -> io::Result<PathBuf> {
308    fn sysctl() -> io::Result<PathBuf> {
309        unsafe {
310            let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
311            let mut path_len: usize = 0;
312            cvt(libc::sysctl(
313                mib.as_ptr(),
314                mib.len() as libc::c_uint,
315                ptr::null_mut(),
316                &mut path_len,
317                ptr::null(),
318                0,
319            ))?;
320            if path_len <= 1 {
321                return Err(io::const_error!(
322                    io::ErrorKind::Uncategorized,
323                    "KERN_PROC_PATHNAME sysctl returned zero-length string",
324                ));
325            }
326            let mut path: Vec<u8> = Vec::with_capacity(path_len);
327            cvt(libc::sysctl(
328                mib.as_ptr(),
329                mib.len() as libc::c_uint,
330                path.as_ptr() as *mut libc::c_void,
331                &mut path_len,
332                ptr::null(),
333                0,
334            ))?;
335            path.set_len(path_len - 1); // chop off NUL
336            Ok(PathBuf::from(OsString::from_vec(path)))
337        }
338    }
339    fn procfs() -> io::Result<PathBuf> {
340        let curproc_exe = path::Path::new("/proc/curproc/exe");
341        if curproc_exe.is_file() {
342            return crate::fs::read_link(curproc_exe);
343        }
344        Err(io::const_error!(
345            io::ErrorKind::Uncategorized,
346            "/proc/curproc/exe doesn't point to regular file.",
347        ))
348    }
349    sysctl().or_else(|_| procfs())
350}
351
352#[cfg(target_os = "openbsd")]
353pub fn current_exe() -> io::Result<PathBuf> {
354    unsafe {
355        let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
356        let mib = mib.as_mut_ptr();
357        let mut argv_len = 0;
358        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_len, ptr::null_mut(), 0))?;
359        let mut argv = Vec::<*const libc::c_char>::with_capacity(argv_len as usize);
360        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_len, ptr::null_mut(), 0))?;
361        argv.set_len(argv_len as usize);
362        if argv[0].is_null() {
363            return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
364        }
365        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
366        if argv0[0] == b'.' || argv0.iter().any(|b| *b == b'/') {
367            crate::fs::canonicalize(OsStr::from_bytes(argv0))
368        } else {
369            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
370        }
371    }
372}
373
374#[cfg(any(
375    target_os = "linux",
376    target_os = "cygwin",
377    target_os = "hurd",
378    target_os = "android",
379    target_os = "nuttx",
380    target_os = "emscripten"
381))]
382pub fn current_exe() -> io::Result<PathBuf> {
383    match crate::fs::read_link("/proc/self/exe") {
384        Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_error!(
385            io::ErrorKind::Uncategorized,
386            "no /proc/self/exe available. Is /proc mounted?",
387        )),
388        other => other,
389    }
390}
391
392#[cfg(target_os = "nto")]
393pub fn current_exe() -> io::Result<PathBuf> {
394    let mut e = crate::fs::read("/proc/self/exefile")?;
395    // Current versions of QNX Neutrino provide a null-terminated path.
396    // Ensure the trailing null byte is not returned here.
397    if let Some(0) = e.last() {
398        e.pop();
399    }
400    Ok(PathBuf::from(OsString::from_vec(e)))
401}
402
403#[cfg(target_vendor = "apple")]
404pub fn current_exe() -> io::Result<PathBuf> {
405    unsafe {
406        let mut sz: u32 = 0;
407        #[expect(deprecated)]
408        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
409        if sz == 0 {
410            return Err(io::Error::last_os_error());
411        }
412        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
413        #[expect(deprecated)]
414        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
415        if err != 0 {
416            return Err(io::Error::last_os_error());
417        }
418        v.set_len(sz as usize - 1); // chop off trailing NUL
419        Ok(PathBuf::from(OsString::from_vec(v)))
420    }
421}
422
423#[cfg(any(target_os = "solaris", target_os = "illumos"))]
424pub fn current_exe() -> io::Result<PathBuf> {
425    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
426        Ok(path)
427    } else {
428        unsafe {
429            let path = libc::getexecname();
430            if path.is_null() {
431                Err(io::Error::last_os_error())
432            } else {
433                let filename = CStr::from_ptr(path).to_bytes();
434                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
435
436                // Prepend a current working directory to the path if
437                // it doesn't contain an absolute pathname.
438                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
439            }
440        }
441    }
442}
443
444#[cfg(target_os = "haiku")]
445pub fn current_exe() -> io::Result<PathBuf> {
446    let mut name = vec![0; libc::PATH_MAX as usize];
447    unsafe {
448        let result = libc::find_path(
449            crate::ptr::null_mut(),
450            libc::B_FIND_PATH_IMAGE_PATH,
451            crate::ptr::null_mut(),
452            name.as_mut_ptr(),
453            name.len(),
454        );
455        if result != libc::B_OK {
456            use crate::io::ErrorKind;
457            Err(io::const_error!(ErrorKind::Uncategorized, "error getting executable path"))
458        } else {
459            // find_path adds the null terminator.
460            let name = CStr::from_ptr(name.as_ptr()).to_bytes();
461            Ok(PathBuf::from(OsStr::from_bytes(name)))
462        }
463    }
464}
465
466#[cfg(target_os = "redox")]
467pub fn current_exe() -> io::Result<PathBuf> {
468    crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
469}
470
471#[cfg(target_os = "rtems")]
472pub fn current_exe() -> io::Result<PathBuf> {
473    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
474}
475
476#[cfg(target_os = "l4re")]
477pub fn current_exe() -> io::Result<PathBuf> {
478    use crate::io::ErrorKind;
479    Err(io::const_error!(ErrorKind::Unsupported, "not yet implemented!"))
480}
481
482#[cfg(target_os = "vxworks")]
483pub fn current_exe() -> io::Result<PathBuf> {
484    #[cfg(test)]
485    use realstd::env;
486
487    #[cfg(not(test))]
488    use crate::env;
489
490    let exe_path = env::args().next().unwrap();
491    let path = path::Path::new(&exe_path);
492    path.canonicalize()
493}
494
495#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
496pub fn current_exe() -> io::Result<PathBuf> {
497    super::unsupported::unsupported()
498}
499
500#[cfg(target_os = "fuchsia")]
501pub fn current_exe() -> io::Result<PathBuf> {
502    #[cfg(test)]
503    use realstd::env;
504
505    #[cfg(not(test))]
506    use crate::env;
507    use crate::io::ErrorKind;
508
509    let exe_path = env::args().next().ok_or(io::const_error!(
510        ErrorKind::Uncategorized,
511        "an executable path was not found because no arguments were provided through argv",
512    ))?;
513    let path = PathBuf::from(exe_path);
514
515    // Prepend the current working directory to the path if it's not absolute.
516    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
517}
518
519#[cfg(not(target_os = "espidf"))]
520pub fn page_size() -> usize {
521    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
522}
523
524// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only
525// used on Darwin, but should work on any unix (in case we need to get
526// `_CS_PATH` or `_CS_V[67]_ENV` in the future).
527//
528// [posix_confstr]:
529//     https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html
530//
531// FIXME: Support `confstr` in Miri.
532#[cfg(all(target_vendor = "apple", not(miri)))]
533fn confstr(key: c_int, size_hint: Option<usize>) -> io::Result<OsString> {
534    let mut buf: Vec<u8> = Vec::with_capacity(0);
535    let mut bytes_needed_including_nul = size_hint
536        .unwrap_or_else(|| {
537            // Treat "None" as "do an extra call to get the length". In theory
538            // we could move this into the loop below, but it's hard to do given
539            // that it isn't 100% clear if it's legal to pass 0 for `len` when
540            // the buffer isn't null.
541            unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
542        })
543        .max(1);
544    // If the value returned by `confstr` is greater than the len passed into
545    // it, then the value was truncated, meaning we need to retry. Note that
546    // while `confstr` results don't seem to change for a process, it's unclear
547    // if this is guaranteed anywhere, so looping does seem required.
548    while bytes_needed_including_nul > buf.capacity() {
549        // We write into the spare capacity of `buf`. This lets us avoid
550        // changing buf's `len`, which both simplifies `reserve` computation,
551        // allows working with `Vec<u8>` instead of `Vec<MaybeUninit<u8>>`, and
552        // may avoid a copy, since the Vec knows that none of the bytes are needed
553        // when reallocating (well, in theory anyway).
554        buf.reserve(bytes_needed_including_nul);
555        // `confstr` returns
556        // - 0 in the case of errors: we break and return an error.
557        // - The number of bytes written, iff the provided buffer is enough to
558        //   hold the entire value: we break and return the data in `buf`.
559        // - Otherwise, the number of bytes needed (including nul): we go
560        //   through the loop again.
561        bytes_needed_including_nul =
562            unsafe { libc::confstr(key, buf.as_mut_ptr().cast::<c_char>(), buf.capacity()) };
563    }
564    // `confstr` returns 0 in the case of an error.
565    if bytes_needed_including_nul == 0 {
566        return Err(io::Error::last_os_error());
567    }
568    // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a
569    // non-zero value, meaning `bytes_needed_including_nul` bytes were
570    // initialized.
571    unsafe {
572        buf.set_len(bytes_needed_including_nul);
573        // Remove the NUL-terminator.
574        let last_byte = buf.pop();
575        // ... and smoke-check that it *was* a NUL-terminator.
576        assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated");
577    };
578    Ok(OsString::from_vec(buf))
579}
580
581#[cfg(all(target_vendor = "apple", not(miri)))]
582fn darwin_temp_dir() -> PathBuf {
583    confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)).map(PathBuf::from).unwrap_or_else(|_| {
584        // It failed for whatever reason (there are several possible reasons),
585        // so return the global one.
586        PathBuf::from("/tmp")
587    })
588}
589
590pub fn temp_dir() -> PathBuf {
591    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
592        cfg_select! {
593            all(target_vendor = "apple", not(miri)) => darwin_temp_dir(),
594            target_os = "android" => PathBuf::from("/data/local/tmp"),
595            _ => PathBuf::from("/tmp"),
596        }
597    })
598}
599
600pub fn home_dir() -> Option<PathBuf> {
601    return crate::env::var_os("HOME")
602        .filter(|s| !s.is_empty())
603        .or_else(|| unsafe { fallback() })
604        .map(PathBuf::from);
605
606    #[cfg(any(
607        target_os = "android",
608        target_os = "emscripten",
609        target_os = "redox",
610        target_os = "vxworks",
611        target_os = "espidf",
612        target_os = "horizon",
613        target_os = "vita",
614        target_os = "nuttx",
615        all(target_vendor = "apple", not(target_os = "macos")),
616    ))]
617    unsafe fn fallback() -> Option<OsString> {
618        None
619    }
620    #[cfg(not(any(
621        target_os = "android",
622        target_os = "emscripten",
623        target_os = "redox",
624        target_os = "vxworks",
625        target_os = "espidf",
626        target_os = "horizon",
627        target_os = "vita",
628        target_os = "nuttx",
629        all(target_vendor = "apple", not(target_os = "macos")),
630    )))]
631    unsafe fn fallback() -> Option<OsString> {
632        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
633            n if n < 0 => 512 as usize,
634            n => n as usize,
635        };
636        let mut buf = Vec::with_capacity(amt);
637        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
638        let mut result = ptr::null_mut();
639        match libc::getpwuid_r(
640            libc::getuid(),
641            p.as_mut_ptr(),
642            buf.as_mut_ptr(),
643            buf.capacity(),
644            &mut result,
645        ) {
646            0 if !result.is_null() => {
647                let ptr = (*result).pw_dir as *const _;
648                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
649                Some(OsStringExt::from_vec(bytes))
650            }
651            _ => None,
652        }
653    }
654}
655
656pub fn exit(code: i32) -> ! {
657    crate::sys::exit_guard::unique_thread_exit();
658    unsafe { libc::exit(code as c_int) }
659}
660
661pub fn getpid() -> u32 {
662    unsafe { libc::getpid() as u32 }
663}
664
665pub fn getppid() -> u32 {
666    unsafe { libc::getppid() as u32 }
667}
668
669#[cfg(all(target_os = "linux", target_env = "gnu"))]
670pub fn glibc_version() -> Option<(usize, usize)> {
671    unsafe extern "C" {
672        fn gnu_get_libc_version() -> *const libc::c_char;
673    }
674    let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
675    if let Ok(version_str) = version_cstr.to_str() {
676        parse_glibc_version(version_str)
677    } else {
678        None
679    }
680}
681
682// Returns Some((major, minor)) if the string is a valid "x.y" version,
683// ignoring any extra dot-separated parts. Otherwise return None.
684#[cfg(all(target_os = "linux", target_env = "gnu"))]
685fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
686    let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
687    match (parsed_ints.next(), parsed_ints.next()) {
688        (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
689        _ => None,
690    }
691}