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