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    fn errno_location() -> *mut c_int;
63}
64
65/// Returns the platform-specific value of errno
66#[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
67pub fn errno() -> i32 {
68    unsafe { (*errno_location()) as i32 }
69}
70
71/// Sets the platform-specific value of errno
72// needed for readdir and syscall!
73#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks"), not(target_os = "rtems")))]
74#[allow(dead_code)] // but not all target cfgs actually end up using it
75pub fn set_errno(e: i32) {
76    unsafe { *errno_location() = e as c_int }
77}
78
79#[cfg(target_os = "vxworks")]
80pub fn errno() -> i32 {
81    unsafe { libc::errnoGet() }
82}
83
84#[cfg(target_os = "rtems")]
85pub fn errno() -> i32 {
86    unsafe extern "C" {
87        #[thread_local]
88        static _tls_errno: c_int;
89    }
90
91    unsafe { _tls_errno as i32 }
92}
93
94#[cfg(target_os = "dragonfly")]
95pub fn errno() -> i32 {
96    unsafe extern "C" {
97        #[thread_local]
98        static errno: c_int;
99    }
100
101    unsafe { errno as i32 }
102}
103
104#[cfg(target_os = "dragonfly")]
105#[allow(dead_code)]
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) = getenv(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
547pub struct Env {
548    iter: vec::IntoIter<(OsString, OsString)>,
549}
550
551// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when <OsStr as Debug>::fmt matches <str as Debug>::fmt.
552pub struct EnvStrDebug<'a> {
553    slice: &'a [(OsString, OsString)],
554}
555
556impl fmt::Debug for EnvStrDebug<'_> {
557    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558        let Self { slice } = self;
559        f.debug_list()
560            .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap())))
561            .finish()
562    }
563}
564
565impl Env {
566    pub fn str_debug(&self) -> impl fmt::Debug + '_ {
567        let Self { iter } = self;
568        EnvStrDebug { slice: iter.as_slice() }
569    }
570}
571
572impl fmt::Debug for Env {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        let Self { iter } = self;
575        f.debug_list().entries(iter.as_slice()).finish()
576    }
577}
578
579impl !Send for Env {}
580impl !Sync for Env {}
581
582impl Iterator for Env {
583    type Item = (OsString, OsString);
584    fn next(&mut self) -> Option<(OsString, OsString)> {
585        self.iter.next()
586    }
587    fn size_hint(&self) -> (usize, Option<usize>) {
588        self.iter.size_hint()
589    }
590}
591
592// Use `_NSGetEnviron` on Apple platforms.
593//
594// `_NSGetEnviron` is the documented alternative (see `man environ`), and has
595// been available since the first versions of both macOS and iOS.
596//
597// Nowadays, specifically since macOS 10.8, `environ` has been exposed through
598// `libdyld.dylib`, which is linked via. `libSystem.dylib`:
599// <https://github.com/apple-oss-distributions/dyld/blob/dyld-1160.6/libdyld/libdyldGlue.cpp#L913>
600//
601// So in the end, it likely doesn't really matter which option we use, but the
602// performance cost of using `_NSGetEnviron` is extremely miniscule, and it
603// might be ever so slightly more supported, so let's just use that.
604//
605// NOTE: The header where this is defined (`crt_externs.h`) was added to the
606// iOS 13.0 SDK, which has been the source of a great deal of confusion in the
607// past about the availability of this API.
608//
609// NOTE(madsmtm): Neither this nor using `environ` has been verified to not
610// cause App Store rejections; if this is found to be the case, an alternative
611// implementation of this is possible using `[NSProcessInfo environment]`
612// - which internally uses `_NSGetEnviron` and a system-wide lock on the
613// environment variables to protect against `setenv`, so using that might be
614// desirable anyhow? Though it also means that we have to link to Foundation.
615#[cfg(target_vendor = "apple")]
616pub unsafe fn environ() -> *mut *const *const c_char {
617    libc::_NSGetEnviron() as *mut *const *const c_char
618}
619
620// Use the `environ` static which is part of POSIX.
621#[cfg(not(target_vendor = "apple"))]
622pub unsafe fn environ() -> *mut *const *const c_char {
623    unsafe extern "C" {
624        static mut environ: *const *const c_char;
625    }
626    &raw mut environ
627}
628
629static ENV_LOCK: RwLock<()> = RwLock::new(());
630
631pub fn env_read_lock() -> impl Drop {
632    ENV_LOCK.read().unwrap_or_else(PoisonError::into_inner)
633}
634
635/// Returns a vector of (variable, value) byte-vector pairs for all the
636/// environment variables of the current process.
637pub fn env() -> Env {
638    unsafe {
639        let _guard = env_read_lock();
640        let mut environ = *environ();
641        let mut result = Vec::new();
642        if !environ.is_null() {
643            while !(*environ).is_null() {
644                if let Some(key_value) = parse(CStr::from_ptr(*environ).to_bytes()) {
645                    result.push(key_value);
646                }
647                environ = environ.add(1);
648            }
649        }
650        return Env { iter: result.into_iter() };
651    }
652
653    fn parse(input: &[u8]) -> Option<(OsString, OsString)> {
654        // Strategy (copied from glibc): Variable name and value are separated
655        // by an ASCII equals sign '='. Since a variable name must not be
656        // empty, allow variable names starting with an equals sign. Skip all
657        // malformed lines.
658        if input.is_empty() {
659            return None;
660        }
661        let pos = memchr::memchr(b'=', &input[1..]).map(|p| p + 1);
662        pos.map(|p| {
663            (
664                OsStringExt::from_vec(input[..p].to_vec()),
665                OsStringExt::from_vec(input[p + 1..].to_vec()),
666            )
667        })
668    }
669}
670
671pub fn getenv(k: &OsStr) -> Option<OsString> {
672    // environment variables with a nul byte can't be set, so their value is
673    // always None as well
674    run_with_cstr(k.as_bytes(), &|k| {
675        let _guard = env_read_lock();
676        let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char;
677
678        if v.is_null() {
679            Ok(None)
680        } else {
681            // SAFETY: `v` cannot be mutated while executing this line since we've a read lock
682            let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec();
683
684            Ok(Some(OsStringExt::from_vec(bytes)))
685        }
686    })
687    .ok()
688    .flatten()
689}
690
691pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
692    run_with_cstr(k.as_bytes(), &|k| {
693        run_with_cstr(v.as_bytes(), &|v| {
694            let _guard = ENV_LOCK.write();
695            cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop)
696        })
697    })
698}
699
700pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
701    run_with_cstr(n.as_bytes(), &|nbuf| {
702        let _guard = ENV_LOCK.write();
703        cvt(libc::unsetenv(nbuf.as_ptr())).map(drop)
704    })
705}
706
707#[cfg(not(target_os = "espidf"))]
708pub fn page_size() -> usize {
709    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
710}
711
712// Returns the value for [`confstr(key, ...)`][posix_confstr]. Currently only
713// used on Darwin, but should work on any unix (in case we need to get
714// `_CS_PATH` or `_CS_V[67]_ENV` in the future).
715//
716// [posix_confstr]:
717//     https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html
718//
719// FIXME: Support `confstr` in Miri.
720#[cfg(all(target_vendor = "apple", not(miri)))]
721fn confstr(key: c_int, size_hint: Option<usize>) -> io::Result<OsString> {
722    let mut buf: Vec<u8> = Vec::with_capacity(0);
723    let mut bytes_needed_including_nul = size_hint
724        .unwrap_or_else(|| {
725            // Treat "None" as "do an extra call to get the length". In theory
726            // we could move this into the loop below, but it's hard to do given
727            // that it isn't 100% clear if it's legal to pass 0 for `len` when
728            // the buffer isn't null.
729            unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
730        })
731        .max(1);
732    // If the value returned by `confstr` is greater than the len passed into
733    // it, then the value was truncated, meaning we need to retry. Note that
734    // while `confstr` results don't seem to change for a process, it's unclear
735    // if this is guaranteed anywhere, so looping does seem required.
736    while bytes_needed_including_nul > buf.capacity() {
737        // We write into the spare capacity of `buf`. This lets us avoid
738        // changing buf's `len`, which both simplifies `reserve` computation,
739        // allows working with `Vec<u8>` instead of `Vec<MaybeUninit<u8>>`, and
740        // may avoid a copy, since the Vec knows that none of the bytes are needed
741        // when reallocating (well, in theory anyway).
742        buf.reserve(bytes_needed_including_nul);
743        // `confstr` returns
744        // - 0 in the case of errors: we break and return an error.
745        // - The number of bytes written, iff the provided buffer is enough to
746        //   hold the entire value: we break and return the data in `buf`.
747        // - Otherwise, the number of bytes needed (including nul): we go
748        //   through the loop again.
749        bytes_needed_including_nul =
750            unsafe { libc::confstr(key, buf.as_mut_ptr().cast::<c_char>(), buf.capacity()) };
751    }
752    // `confstr` returns 0 in the case of an error.
753    if bytes_needed_including_nul == 0 {
754        return Err(io::Error::last_os_error());
755    }
756    // Safety: `confstr(..., buf.as_mut_ptr(), buf.capacity())` returned a
757    // non-zero value, meaning `bytes_needed_including_nul` bytes were
758    // initialized.
759    unsafe {
760        buf.set_len(bytes_needed_including_nul);
761        // Remove the NUL-terminator.
762        let last_byte = buf.pop();
763        // ... and smoke-check that it *was* a NUL-terminator.
764        assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated");
765    };
766    Ok(OsString::from_vec(buf))
767}
768
769#[cfg(all(target_vendor = "apple", not(miri)))]
770fn darwin_temp_dir() -> PathBuf {
771    confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)).map(PathBuf::from).unwrap_or_else(|_| {
772        // It failed for whatever reason (there are several possible reasons),
773        // so return the global one.
774        PathBuf::from("/tmp")
775    })
776}
777
778pub fn temp_dir() -> PathBuf {
779    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
780        cfg_if::cfg_if! {
781            if #[cfg(all(target_vendor = "apple", not(miri)))] {
782                darwin_temp_dir()
783            } else if #[cfg(target_os = "android")] {
784                PathBuf::from("/data/local/tmp")
785            } else {
786                PathBuf::from("/tmp")
787            }
788        }
789    })
790}
791
792pub fn home_dir() -> Option<PathBuf> {
793    return crate::env::var_os("HOME").or_else(|| unsafe { fallback() }).map(PathBuf::from);
794
795    #[cfg(any(
796        target_os = "android",
797        target_os = "emscripten",
798        target_os = "redox",
799        target_os = "vxworks",
800        target_os = "espidf",
801        target_os = "horizon",
802        target_os = "vita",
803        target_os = "nuttx",
804        all(target_vendor = "apple", not(target_os = "macos")),
805    ))]
806    unsafe fn fallback() -> Option<OsString> {
807        None
808    }
809    #[cfg(not(any(
810        target_os = "android",
811        target_os = "emscripten",
812        target_os = "redox",
813        target_os = "vxworks",
814        target_os = "espidf",
815        target_os = "horizon",
816        target_os = "vita",
817        target_os = "nuttx",
818        all(target_vendor = "apple", not(target_os = "macos")),
819    )))]
820    unsafe fn fallback() -> Option<OsString> {
821        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
822            n if n < 0 => 512 as usize,
823            n => n as usize,
824        };
825        let mut buf = Vec::with_capacity(amt);
826        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
827        let mut result = ptr::null_mut();
828        match libc::getpwuid_r(
829            libc::getuid(),
830            p.as_mut_ptr(),
831            buf.as_mut_ptr(),
832            buf.capacity(),
833            &mut result,
834        ) {
835            0 if !result.is_null() => {
836                let ptr = (*result).pw_dir as *const _;
837                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
838                Some(OsStringExt::from_vec(bytes))
839            }
840            _ => None,
841        }
842    }
843}
844
845pub fn exit(code: i32) -> ! {
846    crate::sys::exit_guard::unique_thread_exit();
847    unsafe { libc::exit(code as c_int) }
848}
849
850pub fn getpid() -> u32 {
851    unsafe { libc::getpid() as u32 }
852}
853
854pub fn getppid() -> u32 {
855    unsafe { libc::getppid() as u32 }
856}
857
858#[cfg(all(target_os = "linux", target_env = "gnu"))]
859pub fn glibc_version() -> Option<(usize, usize)> {
860    unsafe extern "C" {
861        fn gnu_get_libc_version() -> *const libc::c_char;
862    }
863    let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
864    if let Ok(version_str) = version_cstr.to_str() {
865        parse_glibc_version(version_str)
866    } else {
867        None
868    }
869}
870
871// Returns Some((major, minor)) if the string is a valid "x.y" version,
872// ignoring any extra dot-separated parts. Otherwise return None.
873#[cfg(all(target_os = "linux", target_env = "gnu"))]
874fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
875    let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
876    match (parsed_ints.next(), parsed_ints.next()) {
877        (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
878        _ => None,
879    }
880}