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