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 = 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
189// This can't just be `impl Iterator` because that requires `'a` to be live on
190// drop (see #146045).
191pub type SplitPaths<'a> = iter::Map<
192    slice::Split<'a, u8, impl FnMut(&u8) -> bool + 'static>,
193    impl FnMut(&[u8]) -> PathBuf + 'static,
194>;
195
196#[define_opaque(SplitPaths)]
197pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
198    fn is_separator(&b: &u8) -> bool {
199        b == PATH_SEPARATOR
200    }
201
202    fn into_pathbuf(part: &[u8]) -> PathBuf {
203        PathBuf::from(OsStr::from_bytes(part))
204    }
205
206    unparsed.as_bytes().split(is_separator).map(into_pathbuf)
207}
208
209#[derive(Debug)]
210pub struct JoinPathsError;
211
212pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
213where
214    I: Iterator<Item = T>,
215    T: AsRef<OsStr>,
216{
217    let mut joined = Vec::new();
218
219    for (i, path) in paths.enumerate() {
220        let path = path.as_ref().as_bytes();
221        if i > 0 {
222            joined.push(PATH_SEPARATOR)
223        }
224        if path.contains(&PATH_SEPARATOR) {
225            return Err(JoinPathsError);
226        }
227        joined.extend_from_slice(path);
228    }
229    Ok(OsStringExt::from_vec(joined))
230}
231
232impl fmt::Display for JoinPathsError {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
235    }
236}
237
238impl crate::error::Error for JoinPathsError {}
239
240#[cfg(target_os = "aix")]
241pub fn current_exe() -> io::Result<PathBuf> {
242    #[cfg(test)]
243    use realstd::env;
244
245    #[cfg(not(test))]
246    use crate::env;
247    use crate::io::ErrorKind;
248
249    let exe_path = env::args().next().ok_or(io::const_error!(
250        ErrorKind::NotFound,
251        "an executable path was not found because no arguments were provided through argv",
252    ))?;
253    let path = PathBuf::from(exe_path);
254    if path.is_absolute() {
255        return path.canonicalize();
256    }
257    // Search PWD to infer current_exe.
258    if let Some(pstr) = path.to_str()
259        && pstr.contains("/")
260    {
261        return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
262    }
263    // Search PATH to infer current_exe.
264    if let Some(p) = env::var_os(OsStr::from_bytes("PATH".as_bytes())) {
265        for search_path in split_paths(&p) {
266            let pb = search_path.join(&path);
267            if pb.is_file()
268                && let Ok(metadata) = crate::fs::metadata(&pb)
269                && metadata.permissions().mode() & 0o111 != 0
270            {
271                return pb.canonicalize();
272            }
273        }
274    }
275    Err(io::const_error!(ErrorKind::NotFound, "an executable path was not found"))
276}
277
278#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
279pub fn current_exe() -> io::Result<PathBuf> {
280    unsafe {
281        let mut mib = [
282            libc::CTL_KERN as c_int,
283            libc::KERN_PROC as c_int,
284            libc::KERN_PROC_PATHNAME as c_int,
285            -1 as c_int,
286        ];
287        let mut sz = 0;
288        cvt(libc::sysctl(
289            mib.as_mut_ptr(),
290            mib.len() as libc::c_uint,
291            ptr::null_mut(),
292            &mut sz,
293            ptr::null_mut(),
294            0,
295        ))?;
296        if sz == 0 {
297            return Err(io::Error::last_os_error());
298        }
299        let mut v: Vec<u8> = Vec::with_capacity(sz);
300        cvt(libc::sysctl(
301            mib.as_mut_ptr(),
302            mib.len() as libc::c_uint,
303            v.as_mut_ptr() as *mut libc::c_void,
304            &mut sz,
305            ptr::null_mut(),
306            0,
307        ))?;
308        if sz == 0 {
309            return Err(io::Error::last_os_error());
310        }
311        v.set_len(sz - 1); // chop off trailing NUL
312        Ok(PathBuf::from(OsString::from_vec(v)))
313    }
314}
315
316#[cfg(target_os = "netbsd")]
317pub fn current_exe() -> io::Result<PathBuf> {
318    fn sysctl() -> io::Result<PathBuf> {
319        unsafe {
320            let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
321            let mut path_len: usize = 0;
322            cvt(libc::sysctl(
323                mib.as_ptr(),
324                mib.len() as libc::c_uint,
325                ptr::null_mut(),
326                &mut path_len,
327                ptr::null(),
328                0,
329            ))?;
330            if path_len <= 1 {
331                return Err(io::const_error!(
332                    io::ErrorKind::Uncategorized,
333                    "KERN_PROC_PATHNAME sysctl returned zero-length string",
334                ));
335            }
336            let mut path: Vec<u8> = Vec::with_capacity(path_len);
337            cvt(libc::sysctl(
338                mib.as_ptr(),
339                mib.len() as libc::c_uint,
340                path.as_ptr() as *mut libc::c_void,
341                &mut path_len,
342                ptr::null(),
343                0,
344            ))?;
345            path.set_len(path_len - 1); // chop off NUL
346            Ok(PathBuf::from(OsString::from_vec(path)))
347        }
348    }
349    fn procfs() -> io::Result<PathBuf> {
350        let curproc_exe = path::Path::new("/proc/curproc/exe");
351        if curproc_exe.is_file() {
352            return crate::fs::read_link(curproc_exe);
353        }
354        Err(io::const_error!(
355            io::ErrorKind::Uncategorized,
356            "/proc/curproc/exe doesn't point to regular file.",
357        ))
358    }
359    sysctl().or_else(|_| procfs())
360}
361
362#[cfg(target_os = "openbsd")]
363pub fn current_exe() -> io::Result<PathBuf> {
364    unsafe {
365        let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
366        let mib = mib.as_mut_ptr();
367        let mut argv_len = 0;
368        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_len, ptr::null_mut(), 0))?;
369        let mut argv = Vec::<*const libc::c_char>::with_capacity(argv_len as usize);
370        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_len, ptr::null_mut(), 0))?;
371        argv.set_len(argv_len as usize);
372        if argv[0].is_null() {
373            return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
374        }
375        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
376        if argv0[0] == b'.' || argv0.iter().any(|b| *b == b'/') {
377            crate::fs::canonicalize(OsStr::from_bytes(argv0))
378        } else {
379            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
380        }
381    }
382}
383
384#[cfg(any(
385    target_os = "linux",
386    target_os = "cygwin",
387    target_os = "hurd",
388    target_os = "android",
389    target_os = "nuttx",
390    target_os = "emscripten"
391))]
392pub fn current_exe() -> io::Result<PathBuf> {
393    match crate::fs::read_link("/proc/self/exe") {
394        Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_error!(
395            io::ErrorKind::Uncategorized,
396            "no /proc/self/exe available. Is /proc mounted?",
397        )),
398        other => other,
399    }
400}
401
402#[cfg(target_os = "nto")]
403pub fn current_exe() -> io::Result<PathBuf> {
404    let mut e = crate::fs::read("/proc/self/exefile")?;
405    // Current versions of QNX Neutrino provide a null-terminated path.
406    // Ensure the trailing null byte is not returned here.
407    if let Some(0) = e.last() {
408        e.pop();
409    }
410    Ok(PathBuf::from(OsString::from_vec(e)))
411}
412
413#[cfg(target_vendor = "apple")]
414pub fn current_exe() -> io::Result<PathBuf> {
415    unsafe {
416        let mut sz: u32 = 0;
417        #[expect(deprecated)]
418        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
419        if sz == 0 {
420            return Err(io::Error::last_os_error());
421        }
422        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
423        #[expect(deprecated)]
424        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
425        if err != 0 {
426            return Err(io::Error::last_os_error());
427        }
428        v.set_len(sz as usize - 1); // chop off trailing NUL
429        Ok(PathBuf::from(OsString::from_vec(v)))
430    }
431}
432
433#[cfg(any(target_os = "solaris", target_os = "illumos"))]
434pub fn current_exe() -> io::Result<PathBuf> {
435    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
436        Ok(path)
437    } else {
438        unsafe {
439            let path = libc::getexecname();
440            if path.is_null() {
441                Err(io::Error::last_os_error())
442            } else {
443                let filename = CStr::from_ptr(path).to_bytes();
444                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
445
446                // Prepend a current working directory to the path if
447                // it doesn't contain an absolute pathname.
448                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
449            }
450        }
451    }
452}
453
454#[cfg(target_os = "haiku")]
455pub fn current_exe() -> io::Result<PathBuf> {
456    let mut name = vec![0; libc::PATH_MAX as usize];
457    unsafe {
458        let result = libc::find_path(
459            crate::ptr::null_mut(),
460            libc::B_FIND_PATH_IMAGE_PATH,
461            crate::ptr::null_mut(),
462            name.as_mut_ptr(),
463            name.len(),
464        );
465        if result != libc::B_OK {
466            use crate::io::ErrorKind;
467            Err(io::const_error!(ErrorKind::Uncategorized, "error getting executable path"))
468        } else {
469            // find_path adds the null terminator.
470            let name = CStr::from_ptr(name.as_ptr()).to_bytes();
471            Ok(PathBuf::from(OsStr::from_bytes(name)))
472        }
473    }
474}
475
476#[cfg(target_os = "redox")]
477pub fn current_exe() -> io::Result<PathBuf> {
478    crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
479}
480
481#[cfg(target_os = "rtems")]
482pub fn current_exe() -> io::Result<PathBuf> {
483    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
484}
485
486#[cfg(target_os = "l4re")]
487pub fn current_exe() -> io::Result<PathBuf> {
488    use crate::io::ErrorKind;
489    Err(io::const_error!(ErrorKind::Unsupported, "not yet implemented!"))
490}
491
492#[cfg(target_os = "vxworks")]
493pub fn current_exe() -> io::Result<PathBuf> {
494    #[cfg(test)]
495    use realstd::env;
496
497    #[cfg(not(test))]
498    use crate::env;
499
500    let exe_path = env::args().next().unwrap();
501    let path = path::Path::new(&exe_path);
502    path.canonicalize()
503}
504
505#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
506pub fn current_exe() -> io::Result<PathBuf> {
507    super::unsupported::unsupported()
508}
509
510#[cfg(target_os = "fuchsia")]
511pub fn current_exe() -> io::Result<PathBuf> {
512    #[cfg(test)]
513    use realstd::env;
514
515    #[cfg(not(test))]
516    use crate::env;
517    use crate::io::ErrorKind;
518
519    let exe_path = env::args().next().ok_or(io::const_error!(
520        ErrorKind::Uncategorized,
521        "an executable path was not found because no arguments were provided through argv",
522    ))?;
523    let path = PathBuf::from(exe_path);
524
525    // Prepend the current working directory to the path if it's not absolute.
526    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
527}
528
529#[cfg(not(target_os = "espidf"))]
530pub fn page_size() -> usize {
531    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
532}
533
534// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only
535// used on Darwin, but should work on any unix (in case we need to get
536// `_CS_PATH` or `_CS_V[67]_ENV` in the future).
537//
538// [posix_confstr]:
539//     https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html
540//
541// FIXME: Support `confstr` in Miri.
542#[cfg(all(target_vendor = "apple", not(miri)))]
543fn confstr(key: c_int, size_hint: Option<usize>) -> io::Result<OsString> {
544    let mut buf: Vec<u8> = Vec::with_capacity(0);
545    let mut bytes_needed_including_nul = size_hint
546        .unwrap_or_else(|| {
547            // Treat "None" as "do an extra call to get the length". In theory
548            // we could move this into the loop below, but it's hard to do given
549            // that it isn't 100% clear if it's legal to pass 0 for `len` when
550            // the buffer isn't null.
551            unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
552        })
553        .max(1);
554    // If the value returned by `confstr` is greater than the len passed into
555    // it, then the value was truncated, meaning we need to retry. Note that
556    // while `confstr` results don't seem to change for a process, it's unclear
557    // if this is guaranteed anywhere, so looping does seem required.
558    while bytes_needed_including_nul > buf.capacity() {
559        // We write into the spare capacity of `buf`. This lets us avoid
560        // changing buf's `len`, which both simplifies `reserve` computation,
561        // allows working with `Vec<u8>` instead of `Vec<MaybeUninit<u8>>`, and
562        // may avoid a copy, since the Vec knows that none of the bytes are needed
563        // when reallocating (well, in theory anyway).
564        buf.reserve(bytes_needed_including_nul);
565        // `confstr` returns
566        // - 0 in the case of errors: we break and return an error.
567        // - The number of bytes written, iff the provided buffer is enough to
568        //   hold the entire value: we break and return the data in `buf`.
569        // - Otherwise, the number of bytes needed (including nul): we go
570        //   through the loop again.
571        bytes_needed_including_nul =
572            unsafe { libc::confstr(key, buf.as_mut_ptr().cast::<c_char>(), buf.capacity()) };
573    }
574    // `confstr` returns 0 in the case of an error.
575    if bytes_needed_including_nul == 0 {
576        return Err(io::Error::last_os_error());
577    }
578    // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a
579    // non-zero value, meaning `bytes_needed_including_nul` bytes were
580    // initialized.
581    unsafe {
582        buf.set_len(bytes_needed_including_nul);
583        // Remove the NUL-terminator.
584        let last_byte = buf.pop();
585        // ... and smoke-check that it *was* a NUL-terminator.
586        assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated");
587    };
588    Ok(OsString::from_vec(buf))
589}
590
591#[cfg(all(target_vendor = "apple", not(miri)))]
592fn darwin_temp_dir() -> PathBuf {
593    confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)).map(PathBuf::from).unwrap_or_else(|_| {
594        // It failed for whatever reason (there are several possible reasons),
595        // so return the global one.
596        PathBuf::from("/tmp")
597    })
598}
599
600pub fn temp_dir() -> PathBuf {
601    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
602        cfg_select! {
603            all(target_vendor = "apple", not(miri)) => darwin_temp_dir(),
604            target_os = "android" => PathBuf::from("/data/local/tmp"),
605            _ => PathBuf::from("/tmp"),
606        }
607    })
608}
609
610pub fn home_dir() -> Option<PathBuf> {
611    return crate::env::var_os("HOME")
612        .filter(|s| !s.is_empty())
613        .or_else(|| unsafe { fallback() })
614        .map(PathBuf::from);
615
616    #[cfg(any(
617        target_os = "android",
618        target_os = "emscripten",
619        target_os = "redox",
620        target_os = "vxworks",
621        target_os = "espidf",
622        target_os = "horizon",
623        target_os = "vita",
624        target_os = "nuttx",
625        all(target_vendor = "apple", not(target_os = "macos")),
626    ))]
627    unsafe fn fallback() -> Option<OsString> {
628        None
629    }
630    #[cfg(not(any(
631        target_os = "android",
632        target_os = "emscripten",
633        target_os = "redox",
634        target_os = "vxworks",
635        target_os = "espidf",
636        target_os = "horizon",
637        target_os = "vita",
638        target_os = "nuttx",
639        all(target_vendor = "apple", not(target_os = "macos")),
640    )))]
641    unsafe fn fallback() -> Option<OsString> {
642        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
643            n if n < 0 => 512 as usize,
644            n => n as usize,
645        };
646        let mut buf = Vec::with_capacity(amt);
647        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
648        let mut result = ptr::null_mut();
649        match libc::getpwuid_r(
650            libc::getuid(),
651            p.as_mut_ptr(),
652            buf.as_mut_ptr(),
653            buf.capacity(),
654            &mut result,
655        ) {
656            0 if !result.is_null() => {
657                let ptr = (*result).pw_dir as *const _;
658                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
659                Some(OsStringExt::from_vec(bytes))
660            }
661            _ => None,
662        }
663    }
664}
665
666pub fn exit(code: i32) -> ! {
667    crate::sys::exit_guard::unique_thread_exit();
668    unsafe { libc::exit(code as c_int) }
669}
670
671pub fn getpid() -> u32 {
672    unsafe { libc::getpid() as u32 }
673}
674
675pub fn getppid() -> u32 {
676    unsafe { libc::getppid() as u32 }
677}
678
679#[cfg(all(target_os = "linux", target_env = "gnu"))]
680pub fn glibc_version() -> Option<(usize, usize)> {
681    unsafe extern "C" {
682        fn gnu_get_libc_version() -> *const libc::c_char;
683    }
684    let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
685    if let Ok(version_str) = version_cstr.to_str() {
686        parse_glibc_version(version_str)
687    } else {
688        None
689    }
690}
691
692// Returns Some((major, minor)) if the string is a valid "x.y" version,
693// ignoring any extra dot-separated parts. Otherwise return None.
694#[cfg(all(target_os = "linux", target_env = "gnu"))]
695fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
696    let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
697    match (parsed_ints.next(), parsed_ints.next()) {
698        (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
699        _ => None,
700    }
701}