Skip to main content

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