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 core::slice::memchr;
9
10use libc::{c_char, c_int, c_void};
11
12use crate::error::Error as StdError;
13use crate::ffi::{CStr, CString, OsStr, OsString};
14use crate::os::unix::prelude::*;
15use crate::path::{self, PathBuf};
16use crate::sync::{PoisonError, RwLock};
17use crate::sys::common::small_c_string::{run_path_with_cstr, run_with_cstr};
18#[cfg(all(target_env = "gnu", not(target_os = "vxworks")))]
19use crate::sys::weak::weak;
20use crate::sys::{cvt, fd};
21use crate::{fmt, io, iter, mem, ptr, slice, str, vec};
22
23const TMPBUF_SZ: usize = 128;
24
25cfg_if::cfg_if! {
26    if #[cfg(target_os = "redox")] {
27        const PATH_SEPARATOR: u8 = b';';
28    } else {
29        const PATH_SEPARATOR: u8 = b':';
30    }
31}
32
33unsafe extern "C" {
34    #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
35    #[cfg_attr(
36        any(
37            target_os = "linux",
38            target_os = "emscripten",
39            target_os = "fuchsia",
40            target_os = "l4re",
41            target_os = "hurd",
42        ),
43        link_name = "__errno_location"
44    )]
45    #[cfg_attr(
46        any(
47            target_os = "netbsd",
48            target_os = "openbsd",
49            target_os = "cygwin",
50            target_os = "android",
51            target_os = "redox",
52            target_os = "nuttx",
53            target_env = "newlib"
54        ),
55        link_name = "__errno"
56    )]
57    #[cfg_attr(any(target_os = "solaris", target_os = "illumos"), link_name = "___errno")]
58    #[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
59    #[cfg_attr(any(target_os = "freebsd", target_vendor = "apple"), link_name = "__error")]
60    #[cfg_attr(target_os = "haiku", link_name = "_errnop")]
61    #[cfg_attr(target_os = "aix", link_name = "_Errno")]
62    // SAFETY: this will always return the same pointer on a given thread.
63    #[unsafe(ffi_const)]
64    fn errno_location() -> *mut c_int;
65}
66
67/// Returns the platform-specific value of errno
68#[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
69#[inline]
70pub fn errno() -> i32 {
71    unsafe { (*errno_location()) as i32 }
72}
73
74/// Sets the platform-specific value of errno
75// needed for readdir and syscall!
76#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks"), not(target_os = "rtems")))]
77#[allow(dead_code)] // but not all target cfgs actually end up using it
78#[inline]
79pub fn set_errno(e: i32) {
80    unsafe { *errno_location() = e as c_int }
81}
82
83#[cfg(target_os = "vxworks")]
84#[inline]
85pub fn errno() -> i32 {
86    unsafe { libc::errnoGet() }
87}
88
89#[cfg(target_os = "rtems")]
90#[inline]
91pub fn errno() -> i32 {
92    unsafe extern "C" {
93        #[thread_local]
94        static _tls_errno: c_int;
95    }
96
97    unsafe { _tls_errno as i32 }
98}
99
100#[cfg(target_os = "dragonfly")]
101#[inline]
102pub fn errno() -> i32 {
103    unsafe extern "C" {
104        #[thread_local]
105        static errno: c_int;
106    }
107
108    unsafe { errno as i32 }
109}
110
111#[cfg(target_os = "dragonfly")]
112#[allow(dead_code)]
113#[inline]
114pub fn set_errno(e: i32) {
115    unsafe extern "C" {
116        #[thread_local]
117        static mut errno: c_int;
118    }
119
120    unsafe {
121        errno = e;
122    }
123}
124
125/// Gets a detailed string description for the given error number.
126pub fn error_string(errno: i32) -> String {
127    unsafe extern "C" {
128        #[cfg_attr(
129            all(
130                any(
131                    target_os = "linux",
132                    target_os = "hurd",
133                    target_env = "newlib",
134                    target_os = "cygwin"
135                ),
136                not(target_env = "ohos")
137            ),
138            link_name = "__xpg_strerror_r"
139        )]
140        fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: libc::size_t) -> c_int;
141    }
142
143    let mut buf = [0 as c_char; TMPBUF_SZ];
144
145    let p = buf.as_mut_ptr();
146    unsafe {
147        if strerror_r(errno as c_int, p, buf.len()) < 0 {
148            panic!("strerror_r failure");
149        }
150
151        let p = p as *const _;
152        // We can't always expect a UTF-8 environment. When we don't get that luxury,
153        // it's better to give a low-quality error message than none at all.
154        String::from_utf8_lossy(CStr::from_ptr(p).to_bytes()).into()
155    }
156}
157
158#[cfg(target_os = "espidf")]
159pub fn getcwd() -> io::Result<PathBuf> {
160    Ok(PathBuf::from("/"))
161}
162
163#[cfg(not(target_os = "espidf"))]
164pub fn getcwd() -> io::Result<PathBuf> {
165    let mut buf = Vec::with_capacity(512);
166    loop {
167        unsafe {
168            let ptr = buf.as_mut_ptr() as *mut libc::c_char;
169            if !libc::getcwd(ptr, buf.capacity()).is_null() {
170                let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
171                buf.set_len(len);
172                buf.shrink_to_fit();
173                return Ok(PathBuf::from(OsString::from_vec(buf)));
174            } else {
175                let error = io::Error::last_os_error();
176                if error.raw_os_error() != Some(libc::ERANGE) {
177                    return Err(error);
178                }
179            }
180
181            // Trigger the internal buffer resizing logic of `Vec` by requiring
182            // more space than the current capacity.
183            let cap = buf.capacity();
184            buf.set_len(cap);
185            buf.reserve(1);
186        }
187    }
188}
189
190#[cfg(target_os = "espidf")]
191pub fn chdir(_p: &path::Path) -> io::Result<()> {
192    super::unsupported::unsupported()
193}
194
195#[cfg(not(target_os = "espidf"))]
196pub fn chdir(p: &path::Path) -> io::Result<()> {
197    let result = run_path_with_cstr(p, &|p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
198    if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
199}
200
201pub struct SplitPaths<'a> {
202    iter: iter::Map<slice::Split<'a, u8, fn(&u8) -> bool>, fn(&'a [u8]) -> PathBuf>,
203}
204
205pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
206    fn bytes_to_path(b: &[u8]) -> PathBuf {
207        PathBuf::from(<OsStr as OsStrExt>::from_bytes(b))
208    }
209    fn is_separator(b: &u8) -> bool {
210        *b == PATH_SEPARATOR
211    }
212    let unparsed = unparsed.as_bytes();
213    SplitPaths {
214        iter: unparsed
215            .split(is_separator as fn(&u8) -> bool)
216            .map(bytes_to_path as fn(&[u8]) -> PathBuf),
217    }
218}
219
220impl<'a> Iterator for SplitPaths<'a> {
221    type Item = PathBuf;
222    fn next(&mut self) -> Option<PathBuf> {
223        self.iter.next()
224    }
225    fn size_hint(&self) -> (usize, Option<usize>) {
226        self.iter.size_hint()
227    }
228}
229
230#[derive(Debug)]
231pub struct JoinPathsError;
232
233pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
234where
235    I: Iterator<Item = T>,
236    T: AsRef<OsStr>,
237{
238    let mut joined = Vec::new();
239
240    for (i, path) in paths.enumerate() {
241        let path = path.as_ref().as_bytes();
242        if i > 0 {
243            joined.push(PATH_SEPARATOR)
244        }
245        if path.contains(&PATH_SEPARATOR) {
246            return Err(JoinPathsError);
247        }
248        joined.extend_from_slice(path);
249    }
250    Ok(OsStringExt::from_vec(joined))
251}
252
253impl fmt::Display for JoinPathsError {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
256    }
257}
258
259impl StdError for JoinPathsError {
260    #[allow(deprecated)]
261    fn description(&self) -> &str {
262        "failed to join paths"
263    }
264}
265
266#[cfg(target_os = "aix")]
267pub fn current_exe() -> io::Result<PathBuf> {
268    #[cfg(test)]
269    use realstd::env;
270
271    #[cfg(not(test))]
272    use crate::env;
273    use crate::io::ErrorKind;
274
275    let exe_path = env::args().next().ok_or(io::const_error!(
276        ErrorKind::NotFound,
277        "an executable path was not found because no arguments were provided through argv",
278    ))?;
279    let path = PathBuf::from(exe_path);
280    if path.is_absolute() {
281        return path.canonicalize();
282    }
283    // Search PWD to infer current_exe.
284    if let Some(pstr) = path.to_str()
285        && pstr.contains("/")
286    {
287        return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
288    }
289    // Search PATH to infer current_exe.
290    if let Some(p) = getenv(OsStr::from_bytes("PATH".as_bytes())) {
291        for search_path in split_paths(&p) {
292            let pb = search_path.join(&path);
293            if pb.is_file()
294                && let Ok(metadata) = crate::fs::metadata(&pb)
295                && metadata.permissions().mode() & 0o111 != 0
296            {
297                return pb.canonicalize();
298            }
299        }
300    }
301    Err(io::const_error!(ErrorKind::NotFound, "an executable path was not found"))
302}
303
304#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
305pub fn current_exe() -> io::Result<PathBuf> {
306    unsafe {
307        let mut mib = [
308            libc::CTL_KERN as c_int,
309            libc::KERN_PROC as c_int,
310            libc::KERN_PROC_PATHNAME as c_int,
311            -1 as c_int,
312        ];
313        let mut sz = 0;
314        cvt(libc::sysctl(
315            mib.as_mut_ptr(),
316            mib.len() as libc::c_uint,
317            ptr::null_mut(),
318            &mut sz,
319            ptr::null_mut(),
320            0,
321        ))?;
322        if sz == 0 {
323            return Err(io::Error::last_os_error());
324        }
325        let mut v: Vec<u8> = Vec::with_capacity(sz);
326        cvt(libc::sysctl(
327            mib.as_mut_ptr(),
328            mib.len() as libc::c_uint,
329            v.as_mut_ptr() as *mut libc::c_void,
330            &mut sz,
331            ptr::null_mut(),
332            0,
333        ))?;
334        if sz == 0 {
335            return Err(io::Error::last_os_error());
336        }
337        v.set_len(sz - 1); // chop off trailing NUL
338        Ok(PathBuf::from(OsString::from_vec(v)))
339    }
340}
341
342#[cfg(target_os = "netbsd")]
343pub fn current_exe() -> io::Result<PathBuf> {
344    fn sysctl() -> io::Result<PathBuf> {
345        unsafe {
346            let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
347            let mut path_len: usize = 0;
348            cvt(libc::sysctl(
349                mib.as_ptr(),
350                mib.len() as libc::c_uint,
351                ptr::null_mut(),
352                &mut path_len,
353                ptr::null(),
354                0,
355            ))?;
356            if path_len <= 1 {
357                return Err(io::const_error!(
358                    io::ErrorKind::Uncategorized,
359                    "KERN_PROC_PATHNAME sysctl returned zero-length string",
360                ));
361            }
362            let mut path: Vec<u8> = Vec::with_capacity(path_len);
363            cvt(libc::sysctl(
364                mib.as_ptr(),
365                mib.len() as libc::c_uint,
366                path.as_ptr() as *mut libc::c_void,
367                &mut path_len,
368                ptr::null(),
369                0,
370            ))?;
371            path.set_len(path_len - 1); // chop off NUL
372            Ok(PathBuf::from(OsString::from_vec(path)))
373        }
374    }
375    fn procfs() -> io::Result<PathBuf> {
376        let curproc_exe = path::Path::new("/proc/curproc/exe");
377        if curproc_exe.is_file() {
378            return crate::fs::read_link(curproc_exe);
379        }
380        Err(io::const_error!(
381            io::ErrorKind::Uncategorized,
382            "/proc/curproc/exe doesn't point to regular file.",
383        ))
384    }
385    sysctl().or_else(|_| procfs())
386}
387
388#[cfg(target_os = "openbsd")]
389pub fn current_exe() -> io::Result<PathBuf> {
390    unsafe {
391        let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
392        let mib = mib.as_mut_ptr();
393        let mut argv_len = 0;
394        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_len, ptr::null_mut(), 0))?;
395        let mut argv = Vec::<*const libc::c_char>::with_capacity(argv_len as usize);
396        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_len, ptr::null_mut(), 0))?;
397        argv.set_len(argv_len as usize);
398        if argv[0].is_null() {
399            return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
400        }
401        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
402        if argv0[0] == b'.' || argv0.iter().any(|b| *b == b'/') {
403            crate::fs::canonicalize(OsStr::from_bytes(argv0))
404        } else {
405            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
406        }
407    }
408}
409
410#[cfg(any(
411    target_os = "linux",
412    target_os = "cygwin",
413    target_os = "hurd",
414    target_os = "android",
415    target_os = "nuttx",
416    target_os = "emscripten"
417))]
418pub fn current_exe() -> io::Result<PathBuf> {
419    match crate::fs::read_link("/proc/self/exe") {
420        Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_error!(
421            io::ErrorKind::Uncategorized,
422            "no /proc/self/exe available. Is /proc mounted?",
423        )),
424        other => other,
425    }
426}
427
428#[cfg(target_os = "nto")]
429pub fn current_exe() -> io::Result<PathBuf> {
430    let mut e = crate::fs::read("/proc/self/exefile")?;
431    // Current versions of QNX Neutrino provide a null-terminated path.
432    // Ensure the trailing null byte is not returned here.
433    if let Some(0) = e.last() {
434        e.pop();
435    }
436    Ok(PathBuf::from(OsString::from_vec(e)))
437}
438
439#[cfg(target_vendor = "apple")]
440pub fn current_exe() -> io::Result<PathBuf> {
441    unsafe {
442        let mut sz: u32 = 0;
443        #[expect(deprecated)]
444        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
445        if sz == 0 {
446            return Err(io::Error::last_os_error());
447        }
448        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
449        #[expect(deprecated)]
450        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
451        if err != 0 {
452            return Err(io::Error::last_os_error());
453        }
454        v.set_len(sz as usize - 1); // chop off trailing NUL
455        Ok(PathBuf::from(OsString::from_vec(v)))
456    }
457}
458
459#[cfg(any(target_os = "solaris", target_os = "illumos"))]
460pub fn current_exe() -> io::Result<PathBuf> {
461    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
462        Ok(path)
463    } else {
464        unsafe {
465            let path = libc::getexecname();
466            if path.is_null() {
467                Err(io::Error::last_os_error())
468            } else {
469                let filename = CStr::from_ptr(path).to_bytes();
470                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
471
472                // Prepend a current working directory to the path if
473                // it doesn't contain an absolute pathname.
474                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
475            }
476        }
477    }
478}
479
480#[cfg(target_os = "haiku")]
481pub fn current_exe() -> io::Result<PathBuf> {
482    let mut name = vec![0; libc::PATH_MAX as usize];
483    unsafe {
484        let result = libc::find_path(
485            crate::ptr::null_mut(),
486            libc::path_base_directory::B_FIND_PATH_IMAGE_PATH,
487            crate::ptr::null_mut(),
488            name.as_mut_ptr(),
489            name.len(),
490        );
491        if result != libc::B_OK {
492            use crate::io::ErrorKind;
493            Err(io::const_error!(ErrorKind::Uncategorized, "error getting executable path"))
494        } else {
495            // find_path adds the null terminator.
496            let name = CStr::from_ptr(name.as_ptr()).to_bytes();
497            Ok(PathBuf::from(OsStr::from_bytes(name)))
498        }
499    }
500}
501
502#[cfg(target_os = "redox")]
503pub fn current_exe() -> io::Result<PathBuf> {
504    crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
505}
506
507#[cfg(target_os = "rtems")]
508pub fn current_exe() -> io::Result<PathBuf> {
509    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
510}
511
512#[cfg(target_os = "l4re")]
513pub fn current_exe() -> io::Result<PathBuf> {
514    use crate::io::ErrorKind;
515    Err(io::const_error!(ErrorKind::Unsupported, "not yet implemented!"))
516}
517
518#[cfg(target_os = "vxworks")]
519pub fn current_exe() -> io::Result<PathBuf> {
520    #[cfg(test)]
521    use realstd::env;
522
523    #[cfg(not(test))]
524    use crate::env;
525
526    let exe_path = env::args().next().unwrap();
527    let path = path::Path::new(&exe_path);
528    path.canonicalize()
529}
530
531#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
532pub fn current_exe() -> io::Result<PathBuf> {
533    super::unsupported::unsupported()
534}
535
536#[cfg(target_os = "fuchsia")]
537pub fn current_exe() -> io::Result<PathBuf> {
538    #[cfg(test)]
539    use realstd::env;
540
541    #[cfg(not(test))]
542    use crate::env;
543    use crate::io::ErrorKind;
544
545    let exe_path = env::args().next().ok_or(io::const_error!(
546        ErrorKind::Uncategorized,
547        "an executable path was not found because no arguments were provided through argv",
548    ))?;
549    let path = PathBuf::from(exe_path);
550
551    // Prepend the current working directory to the path if it's not absolute.
552    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
553}
554
555pub struct Env {
556    iter: vec::IntoIter<(OsString, OsString)>,
557}
558
559// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when <OsStr as Debug>::fmt matches <str as Debug>::fmt.
560pub struct EnvStrDebug<'a> {
561    slice: &'a [(OsString, OsString)],
562}
563
564impl fmt::Debug for EnvStrDebug<'_> {
565    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
566        let Self { slice } = self;
567        f.debug_list()
568            .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap())))
569            .finish()
570    }
571}
572
573impl Env {
574    pub fn str_debug(&self) -> impl fmt::Debug + '_ {
575        let Self { iter } = self;
576        EnvStrDebug { slice: iter.as_slice() }
577    }
578}
579
580impl fmt::Debug for Env {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        let Self { iter } = self;
583        f.debug_list().entries(iter.as_slice()).finish()
584    }
585}
586
587impl !Send for Env {}
588impl !Sync for Env {}
589
590impl Iterator for Env {
591    type Item = (OsString, OsString);
592    fn next(&mut self) -> Option<(OsString, OsString)> {
593        self.iter.next()
594    }
595    fn size_hint(&self) -> (usize, Option<usize>) {
596        self.iter.size_hint()
597    }
598}
599
600// Use `_NSGetEnviron` on Apple platforms.
601//
602// `_NSGetEnviron` is the documented alternative (see `man environ`), and has
603// been available since the first versions of both macOS and iOS.
604//
605// Nowadays, specifically since macOS 10.8, `environ` has been exposed through
606// `libdyld.dylib`, which is linked via. `libSystem.dylib`:
607// <https://github.com/apple-oss-distributions/dyld/blob/dyld-1160.6/libdyld/libdyldGlue.cpp#L913>
608//
609// So in the end, it likely doesn't really matter which option we use, but the
610// performance cost of using `_NSGetEnviron` is extremely miniscule, and it
611// might be ever so slightly more supported, so let's just use that.
612//
613// NOTE: The header where this is defined (`crt_externs.h`) was added to the
614// iOS 13.0 SDK, which has been the source of a great deal of confusion in the
615// past about the availability of this API.
616//
617// NOTE(madsmtm): Neither this nor using `environ` has been verified to not
618// cause App Store rejections; if this is found to be the case, an alternative
619// implementation of this is possible using `[NSProcessInfo environment]`
620// - which internally uses `_NSGetEnviron` and a system-wide lock on the
621// environment variables to protect against `setenv`, so using that might be
622// desirable anyhow? Though it also means that we have to link to Foundation.
623#[cfg(target_vendor = "apple")]
624pub unsafe fn environ() -> *mut *const *const c_char {
625    libc::_NSGetEnviron() as *mut *const *const c_char
626}
627
628// Use the `environ` static which is part of POSIX.
629#[cfg(not(target_vendor = "apple"))]
630pub unsafe fn environ() -> *mut *const *const c_char {
631    unsafe extern "C" {
632        static mut environ: *const *const c_char;
633    }
634    &raw mut environ
635}
636
637static ENV_LOCK: RwLock<()> = RwLock::new(());
638
639pub fn env_read_lock() -> impl Drop {
640    ENV_LOCK.read().unwrap_or_else(PoisonError::into_inner)
641}
642
643/// Returns a vector of (variable, value) byte-vector pairs for all the
644/// environment variables of the current process.
645pub fn env() -> Env {
646    unsafe {
647        let _guard = env_read_lock();
648        let mut environ = *environ();
649        let mut result = Vec::new();
650        if !environ.is_null() {
651            while !(*environ).is_null() {
652                if let Some(key_value) = parse(CStr::from_ptr(*environ).to_bytes()) {
653                    result.push(key_value);
654                }
655                environ = environ.add(1);
656            }
657        }
658        return Env { iter: result.into_iter() };
659    }
660
661    fn parse(input: &[u8]) -> Option<(OsString, OsString)> {
662        // Strategy (copied from glibc): Variable name and value are separated
663        // by an ASCII equals sign '='. Since a variable name must not be
664        // empty, allow variable names starting with an equals sign. Skip all
665        // malformed lines.
666        if input.is_empty() {
667            return None;
668        }
669        let pos = memchr::memchr(b'=', &input[1..]).map(|p| p + 1);
670        pos.map(|p| {
671            (
672                OsStringExt::from_vec(input[..p].to_vec()),
673                OsStringExt::from_vec(input[p + 1..].to_vec()),
674            )
675        })
676    }
677}
678
679pub fn getenv(k: &OsStr) -> Option<OsString> {
680    // environment variables with a nul byte can't be set, so their value is
681    // always None as well
682    run_with_cstr(k.as_bytes(), &|k| {
683        let _guard = env_read_lock();
684        let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char;
685
686        if v.is_null() {
687            Ok(None)
688        } else {
689            // SAFETY: `v` cannot be mutated while executing this line since we've a read lock
690            let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec();
691
692            Ok(Some(OsStringExt::from_vec(bytes)))
693        }
694    })
695    .ok()
696    .flatten()
697}
698
699pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
700    run_with_cstr(k.as_bytes(), &|k| {
701        run_with_cstr(v.as_bytes(), &|v| {
702            let _guard = ENV_LOCK.write();
703            cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop)
704        })
705    })
706}
707
708pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
709    run_with_cstr(n.as_bytes(), &|nbuf| {
710        let _guard = ENV_LOCK.write();
711        cvt(libc::unsetenv(nbuf.as_ptr())).map(drop)
712    })
713}
714
715#[cfg(not(target_os = "espidf"))]
716pub fn page_size() -> usize {
717    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
718}
719
720// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only
721// used on Darwin, but should work on any unix (in case we need to get
722// `_CS_PATH` or `_CS_V[67]_ENV` in the future).
723//
724// [posix_confstr]:
725//     https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html
726//
727// FIXME: Support `confstr` in Miri.
728#[cfg(all(target_vendor = "apple", not(miri)))]
729fn confstr(key: c_int, size_hint: Option<usize>) -> io::Result<OsString> {
730    let mut buf: Vec<u8> = Vec::with_capacity(0);
731    let mut bytes_needed_including_nul = size_hint
732        .unwrap_or_else(|| {
733            // Treat "None" as "do an extra call to get the length". In theory
734            // we could move this into the loop below, but it's hard to do given
735            // that it isn't 100% clear if it's legal to pass 0 for `len` when
736            // the buffer isn't null.
737            unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
738        })
739        .max(1);
740    // If the value returned by `confstr` is greater than the len passed into
741    // it, then the value was truncated, meaning we need to retry. Note that
742    // while `confstr` results don't seem to change for a process, it's unclear
743    // if this is guaranteed anywhere, so looping does seem required.
744    while bytes_needed_including_nul > buf.capacity() {
745        // We write into the spare capacity of `buf`. This lets us avoid
746        // changing buf's `len`, which both simplifies `reserve` computation,
747        // allows working with `Vec<u8>` instead of `Vec<MaybeUninit<u8>>`, and
748        // may avoid a copy, since the Vec knows that none of the bytes are needed
749        // when reallocating (well, in theory anyway).
750        buf.reserve(bytes_needed_including_nul);
751        // `confstr` returns
752        // - 0 in the case of errors: we break and return an error.
753        // - The number of bytes written, iff the provided buffer is enough to
754        //   hold the entire value: we break and return the data in `buf`.
755        // - Otherwise, the number of bytes needed (including nul): we go
756        //   through the loop again.
757        bytes_needed_including_nul =
758            unsafe { libc::confstr(key, buf.as_mut_ptr().cast::<c_char>(), buf.capacity()) };
759    }
760    // `confstr` returns 0 in the case of an error.
761    if bytes_needed_including_nul == 0 {
762        return Err(io::Error::last_os_error());
763    }
764    // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a
765    // non-zero value, meaning `bytes_needed_including_nul` bytes were
766    // initialized.
767    unsafe {
768        buf.set_len(bytes_needed_including_nul);
769        // Remove the NUL-terminator.
770        let last_byte = buf.pop();
771        // ... and smoke-check that it *was* a NUL-terminator.
772        assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated");
773    };
774    Ok(OsString::from_vec(buf))
775}
776
777#[cfg(all(target_vendor = "apple", not(miri)))]
778fn darwin_temp_dir() -> PathBuf {
779    confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)).map(PathBuf::from).unwrap_or_else(|_| {
780        // It failed for whatever reason (there are several possible reasons),
781        // so return the global one.
782        PathBuf::from("/tmp")
783    })
784}
785
786pub fn temp_dir() -> PathBuf {
787    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
788        cfg_if::cfg_if! {
789            if #[cfg(all(target_vendor = "apple", not(miri)))] {
790                darwin_temp_dir()
791            } else if #[cfg(target_os = "android")] {
792                PathBuf::from("/data/local/tmp")
793            } else {
794                PathBuf::from("/tmp")
795            }
796        }
797    })
798}
799
800pub fn home_dir() -> Option<PathBuf> {
801    return crate::env::var_os("HOME").or_else(|| unsafe { fallback() }).map(PathBuf::from);
802
803    #[cfg(any(
804        target_os = "android",
805        target_os = "emscripten",
806        target_os = "redox",
807        target_os = "vxworks",
808        target_os = "espidf",
809        target_os = "horizon",
810        target_os = "vita",
811        target_os = "nuttx",
812        all(target_vendor = "apple", not(target_os = "macos")),
813    ))]
814    unsafe fn fallback() -> Option<OsString> {
815        None
816    }
817    #[cfg(not(any(
818        target_os = "android",
819        target_os = "emscripten",
820        target_os = "redox",
821        target_os = "vxworks",
822        target_os = "espidf",
823        target_os = "horizon",
824        target_os = "vita",
825        target_os = "nuttx",
826        all(target_vendor = "apple", not(target_os = "macos")),
827    )))]
828    unsafe fn fallback() -> Option<OsString> {
829        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
830            n if n < 0 => 512 as usize,
831            n => n as usize,
832        };
833        let mut buf = Vec::with_capacity(amt);
834        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
835        let mut result = ptr::null_mut();
836        match libc::getpwuid_r(
837            libc::getuid(),
838            p.as_mut_ptr(),
839            buf.as_mut_ptr(),
840            buf.capacity(),
841            &mut result,
842        ) {
843            0 if !result.is_null() => {
844                let ptr = (*result).pw_dir as *const _;
845                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
846                Some(OsStringExt::from_vec(bytes))
847            }
848            _ => None,
849        }
850    }
851}
852
853pub fn exit(code: i32) -> ! {
854    crate::sys::exit_guard::unique_thread_exit();
855    unsafe { libc::exit(code as c_int) }
856}
857
858pub fn getpid() -> u32 {
859    unsafe { libc::getpid() as u32 }
860}
861
862pub fn getppid() -> u32 {
863    unsafe { libc::getppid() as u32 }
864}
865
866#[cfg(all(target_os = "linux", target_env = "gnu"))]
867pub fn glibc_version() -> Option<(usize, usize)> {
868    unsafe extern "C" {
869        fn gnu_get_libc_version() -> *const libc::c_char;
870    }
871    let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
872    if let Ok(version_str) = version_cstr.to_str() {
873        parse_glibc_version(version_str)
874    } else {
875        None
876    }
877}
878
879// Returns Some((major, minor)) if the string is a valid "x.y" version,
880// ignoring any extra dot-separated parts. Otherwise return None.
881#[cfg(all(target_os = "linux", target_env = "gnu"))]
882fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
883    let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
884    match (parsed_ints.next(), parsed_ints.next()) {
885        (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
886        _ => None,
887    }
888}