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