Skip to main content

std/sys/paths/
unix.rs

1//! Implementation of `std::os` functionality for unix systems
2
3#![allow(unused_imports)] // lots of cfg code here
4
5use libc::{c_char, c_int, c_void};
6
7use crate::ffi::{CStr, OsStr, OsString};
8use crate::os::unix::prelude::*;
9use crate::path::{self, Path, PathBuf};
10use crate::sys::helpers::run_path_with_cstr;
11use crate::sys::pal::cvt;
12use crate::{fmt, io, iter, mem, ptr, slice, str};
13
14const PATH_SEPARATOR: u8 = b':';
15
16#[cfg(target_os = "espidf")]
17pub fn getcwd() -> io::Result<PathBuf> {
18    Ok(PathBuf::from("/"))
19}
20
21#[cfg(not(target_os = "espidf"))]
22pub fn getcwd() -> io::Result<PathBuf> {
23    let mut buf = Vec::with_capacity(512);
24    loop {
25        unsafe {
26            let ptr = buf.as_mut_ptr() as *mut libc::c_char;
27            if !libc::getcwd(ptr, buf.capacity()).is_null() {
28                let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
29                buf.set_len(len);
30                buf.shrink_to_fit();
31                return Ok(PathBuf::from(OsString::from_vec(buf)));
32            } else {
33                let error = io::Error::last_os_error();
34                if error.raw_os_error() != Some(libc::ERANGE) {
35                    return Err(error);
36                }
37            }
38
39            // Trigger the internal buffer resizing logic of `Vec` by requiring
40            // more space than the current capacity.
41            let cap = buf.capacity();
42            buf.set_len(cap);
43            buf.reserve(1);
44        }
45    }
46}
47
48#[cfg(target_os = "espidf")]
49pub fn chdir(_p: &path::Path) -> io::Result<()> {
50    crate::sys::pal::unsupported()
51}
52
53#[cfg(not(target_os = "espidf"))]
54pub fn chdir(p: &path::Path) -> io::Result<()> {
55    let result = run_path_with_cstr(p, &|p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
56    if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
57}
58
59// This can't just be `impl Iterator` because that requires `'a` to be live on
60// drop (see #146045).
61pub type SplitPaths<'a> = iter::Map<
62    slice::Split<'a, u8, impl FnMut(&u8) -> bool + 'static>,
63    impl FnMut(&[u8]) -> PathBuf + 'static,
64>;
65
66#[define_opaque(SplitPaths)]
67pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
68    fn is_separator(&b: &u8) -> bool {
69        b == PATH_SEPARATOR
70    }
71
72    fn into_pathbuf(part: &[u8]) -> PathBuf {
73        PathBuf::from(OsStr::from_bytes(part))
74    }
75
76    unparsed.as_bytes().split(is_separator).map(into_pathbuf)
77}
78
79pub type SplitPathsRef<'a> = iter::Map<
80    slice::Split<'a, u8, impl FnMut(&u8) -> bool + 'static>,
81    impl FnMut(&[u8]) -> &'_ Path + 'static,
82>;
83
84pub fn split_paths_ref(unparsed: &OsStr) -> Option<SplitPathsRef<'_>> {
85    #[define_opaque(SplitPathsRef)]
86    fn split_paths_ref(unparsed: &OsStr) -> SplitPathsRef<'_> {
87        fn is_separator(&b: &u8) -> bool {
88            b == PATH_SEPARATOR
89        }
90
91        fn into_path(part: &[u8]) -> &Path {
92            Path::new(OsStr::from_bytes(part))
93        }
94
95        unparsed.as_bytes().split(is_separator).map(into_path)
96    }
97
98    Some(split_paths_ref(unparsed))
99}
100
101#[derive(#[automatically_derived]
impl ::core::fmt::Debug for JoinPathsError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "JoinPathsError")
    }
}Debug)]
102pub struct JoinPathsError;
103
104pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
105where
106    I: Iterator<Item = T>,
107    T: AsRef<OsStr>,
108{
109    let mut joined = Vec::new();
110
111    for (i, path) in paths.enumerate() {
112        let path = path.as_ref().as_bytes();
113        if i > 0 {
114            joined.push(PATH_SEPARATOR)
115        }
116        if path.contains(&PATH_SEPARATOR) {
117            return Err(JoinPathsError);
118        }
119        joined.extend_from_slice(path);
120    }
121    Ok(OsStringExt::from_vec(joined))
122}
123
124impl fmt::Display for JoinPathsError {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        f.write_fmt(format_args!("path segment contains separator `{0}`",
        char::from(PATH_SEPARATOR)))write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
127    }
128}
129
130impl crate::error::Error for JoinPathsError {}
131
132#[cfg(target_os = "aix")]
133pub fn current_exe() -> io::Result<PathBuf> {
134    #[cfg(test)]
135    use realstd::env;
136
137    #[cfg(not(test))]
138    use crate::env;
139    use crate::io;
140
141    let exe_path = env::args().next().ok_or(io::const_error!(
142        io::ErrorKind::NotFound,
143        "an executable path was not found because no arguments were provided through argv",
144    ))?;
145    let path = PathBuf::from(exe_path);
146    if path.is_absolute() {
147        return path.canonicalize();
148    }
149    // Search PWD to infer current_exe.
150    if let Some(pstr) = path.to_str()
151        && pstr.contains("/")
152    {
153        return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
154    }
155    // Search PATH to infer current_exe.
156    if let Some(p) = env::var_os(OsStr::from_bytes("PATH".as_bytes())) {
157        for search_path in split_paths(&p) {
158            let pb = search_path.join(&path);
159            if pb.is_file()
160                && let Ok(metadata) = crate::fs::metadata(&pb)
161                && metadata.permissions().mode() & 0o111 != 0
162            {
163                return pb.canonicalize();
164            }
165        }
166    }
167    Err(io::const_error!(io::ErrorKind::NotFound, "an executable path was not found"))
168}
169
170#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
171pub fn current_exe() -> io::Result<PathBuf> {
172    unsafe {
173        let mut mib = [
174            libc::CTL_KERN as c_int,
175            libc::KERN_PROC as c_int,
176            libc::KERN_PROC_PATHNAME as c_int,
177            -1 as c_int,
178        ];
179        let mut sz = 0;
180        cvt(libc::sysctl(
181            mib.as_mut_ptr(),
182            mib.len() as libc::c_uint,
183            ptr::null_mut(),
184            &mut sz,
185            ptr::null_mut(),
186            0,
187        ))?;
188        if sz == 0 {
189            return Err(io::Error::last_os_error());
190        }
191        let mut v: Vec<u8> = Vec::with_capacity(sz);
192        cvt(libc::sysctl(
193            mib.as_mut_ptr(),
194            mib.len() as libc::c_uint,
195            v.as_mut_ptr() as *mut libc::c_void,
196            &mut sz,
197            ptr::null_mut(),
198            0,
199        ))?;
200        if sz == 0 {
201            return Err(io::Error::last_os_error());
202        }
203        v.set_len(sz - 1); // chop off trailing NUL
204        Ok(PathBuf::from(OsString::from_vec(v)))
205    }
206}
207
208#[cfg(target_os = "netbsd")]
209pub fn current_exe() -> io::Result<PathBuf> {
210    fn sysctl() -> io::Result<PathBuf> {
211        unsafe {
212            let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
213            let mut path_len: usize = 0;
214            cvt(libc::sysctl(
215                mib.as_ptr(),
216                mib.len() as libc::c_uint,
217                ptr::null_mut(),
218                &mut path_len,
219                ptr::null(),
220                0,
221            ))?;
222            if path_len <= 1 {
223                return Err(io::const_error!(
224                    io::ErrorKind::Uncategorized,
225                    "KERN_PROC_PATHNAME sysctl returned zero-length string",
226                ));
227            }
228            let mut path: Vec<u8> = Vec::with_capacity(path_len);
229            cvt(libc::sysctl(
230                mib.as_ptr(),
231                mib.len() as libc::c_uint,
232                path.as_mut_ptr() as *mut libc::c_void,
233                &mut path_len,
234                ptr::null(),
235                0,
236            ))?;
237            path.set_len(path_len - 1); // chop off NUL
238            Ok(PathBuf::from(OsString::from_vec(path)))
239        }
240    }
241    fn procfs() -> io::Result<PathBuf> {
242        let curproc_exe = path::Path::new("/proc/curproc/exe");
243        if curproc_exe.is_file() {
244            return crate::fs::read_link(curproc_exe);
245        }
246        Err(io::const_error!(
247            io::ErrorKind::Uncategorized,
248            "/proc/curproc/exe doesn't point to regular file.",
249        ))
250    }
251    sysctl().or_else(|_| procfs())
252}
253
254#[cfg(target_os = "openbsd")]
255pub fn current_exe() -> io::Result<PathBuf> {
256    unsafe {
257        let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
258        let mib = mib.as_mut_ptr();
259
260        // Determine the required size (in bytes) for the argument array ...
261        let mut argv_size = 0;
262        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_size, ptr::null_mut(), 0))?;
263
264        // ... allocate a buffer for it ...
265        let argc = argv_size.div_exact(size_of::<*const libc::c_char>()).unwrap();
266        let mut argv = Vec::<*const libc::c_char>::with_capacity(argc);
267
268        // ... and retrieve the argument array.
269        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_size, ptr::null_mut(), 0))?;
270        let argc = argv_size.div_exact(size_of::<*const libc::c_char>()).unwrap();
271        argv.set_len(argc);
272
273        if argv[0].is_null() {
274            return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
275        }
276        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
277        if argv0.iter().any(|b| *b == b'/') {
278            // The program name is path-like, so try to canonicalize it.
279            crate::fs::canonicalize(OsStr::from_bytes(argv0))
280        } else {
281            // The program was probably found in the PATH. Instead of trying to
282            // find it again (which might not succeed if PATH has changed), just
283            // return the program name – this function is best-effort anyway.
284            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
285        }
286    }
287}
288
289#[cfg(any(
290    target_os = "linux",
291    target_os = "cygwin",
292    target_os = "hurd",
293    target_os = "android",
294    target_os = "nuttx",
295    target_os = "emscripten"
296))]
297pub fn current_exe() -> io::Result<PathBuf> {
298    match crate::fs::read_link("/proc/self/exe") {
299        Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(::core::hint::must_use(::core::io::Error::from_static_message(const {
                &::core::io::SimpleMessage {
                        kind: io::ErrorKind::Uncategorized,
                        message: "no /proc/self/exe available. Is /proc mounted?",
                    }
            }))io::const_error!(
300            io::ErrorKind::Uncategorized,
301            "no /proc/self/exe available. Is /proc mounted?",
302        )),
303        other => other,
304    }
305}
306
307#[cfg(any(target_os = "nto", target_os = "qnx"))]
308pub fn current_exe() -> io::Result<PathBuf> {
309    let mut e = crate::fs::read("/proc/self/exefile")?;
310    // Current versions of QNX SDP provide a null-terminated path.
311    // Ensure the trailing null byte is not returned here.
312    if let Some(0) = e.last() {
313        e.pop();
314    }
315    Ok(PathBuf::from(OsString::from_vec(e)))
316}
317
318#[cfg(target_vendor = "apple")]
319pub fn current_exe() -> io::Result<PathBuf> {
320    unsafe {
321        let mut sz: u32 = 0;
322        #[expect(deprecated)]
323        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
324        if sz == 0 {
325            return Err(io::Error::last_os_error());
326        }
327        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
328        #[expect(deprecated)]
329        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
330        if err != 0 {
331            return Err(io::Error::last_os_error());
332        }
333        v.set_len(sz as usize - 1); // chop off trailing NUL
334        Ok(PathBuf::from(OsString::from_vec(v)))
335    }
336}
337
338#[cfg(any(target_os = "solaris", target_os = "illumos"))]
339pub fn current_exe() -> io::Result<PathBuf> {
340    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
341        Ok(path)
342    } else {
343        unsafe {
344            let path = libc::getexecname();
345            if path.is_null() {
346                Err(io::Error::last_os_error())
347            } else {
348                let filename = CStr::from_ptr(path).to_bytes();
349                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
350
351                // Prepend a current working directory to the path if
352                // it doesn't contain an absolute pathname.
353                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
354            }
355        }
356    }
357}
358
359#[cfg(target_os = "haiku")]
360pub fn current_exe() -> io::Result<PathBuf> {
361    let mut name = vec![0; libc::PATH_MAX as usize];
362    unsafe {
363        let result = libc::find_path(
364            crate::ptr::null_mut(),
365            libc::B_FIND_PATH_IMAGE_PATH,
366            crate::ptr::null_mut(),
367            name.as_mut_ptr(),
368            name.len(),
369        );
370        if result != libc::B_OK {
371            Err(io::const_error!(io::ErrorKind::Uncategorized, "error getting executable path"))
372        } else {
373            // find_path adds the null terminator.
374            let name = CStr::from_ptr(name.as_ptr()).to_bytes();
375            Ok(PathBuf::from(OsStr::from_bytes(name)))
376        }
377    }
378}
379
380#[cfg(target_os = "redox")]
381pub fn current_exe() -> io::Result<PathBuf> {
382    crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
383}
384
385#[cfg(target_os = "rtems")]
386pub fn current_exe() -> io::Result<PathBuf> {
387    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
388}
389
390#[cfg(target_os = "l4re")]
391pub fn current_exe() -> io::Result<PathBuf> {
392    Err(io::const_error!(io::ErrorKind::Unsupported, "not yet implemented!"))
393}
394
395#[cfg(target_os = "vxworks")]
396pub fn current_exe() -> io::Result<PathBuf> {
397    #[cfg(test)]
398    use realstd::env;
399
400    #[cfg(not(test))]
401    use crate::env;
402
403    let exe_path = env::args().next().unwrap();
404    let path = path::Path::new(&exe_path);
405    path.canonicalize()
406}
407
408#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
409pub fn current_exe() -> io::Result<PathBuf> {
410    crate::sys::pal::unsupported()
411}
412
413#[cfg(target_os = "fuchsia")]
414pub fn current_exe() -> io::Result<PathBuf> {
415    #[cfg(test)]
416    use realstd::env;
417
418    #[cfg(not(test))]
419    use crate::env;
420
421    let exe_path = env::args().next().ok_or(io::const_error!(
422        io::ErrorKind::Uncategorized,
423        "an executable path was not found because no arguments were provided through argv",
424    ))?;
425    let path = PathBuf::from(exe_path);
426
427    // Prepend the current working directory to the path if it's not absolute.
428    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
429}
430
431#[cfg(target_vendor = "apple")]
432fn darwin_temp_dir() -> PathBuf {
433    crate::sys::pal::conf::confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64))
434        .map(PathBuf::from)
435        .unwrap_or_else(|_| {
436            // It failed for whatever reason (there are several possible reasons),
437            // so return the global one.
438            PathBuf::from("/tmp")
439        })
440}
441
442pub fn temp_dir() -> PathBuf {
443    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
444        cfg_select! {
445            target_vendor = "apple" => darwin_temp_dir(),
446            target_os = "android" => PathBuf::from("/data/local/tmp"),
447            _ => PathBuf::from("/tmp"),
448        }
449    })
450}
451
452pub fn home_dir() -> Option<PathBuf> {
453    return crate::env::var_os("HOME")
454        .filter(|s| !s.is_empty())
455        .or_else(|| unsafe { fallback() })
456        .map(PathBuf::from);
457
458    #[cfg(any(
459        target_os = "android",
460        target_os = "emscripten",
461        target_os = "redox",
462        target_os = "vxworks",
463        target_os = "espidf",
464        target_os = "horizon",
465        target_os = "vita",
466        target_os = "nuttx",
467        all(target_vendor = "apple", not(target_os = "macos")),
468    ))]
469    unsafe fn fallback() -> Option<OsString> {
470        None
471    }
472    #[cfg(not(any(
473        target_os = "android",
474        target_os = "emscripten",
475        target_os = "redox",
476        target_os = "vxworks",
477        target_os = "espidf",
478        target_os = "horizon",
479        target_os = "vita",
480        target_os = "nuttx",
481        all(target_vendor = "apple", not(target_os = "macos")),
482    )))]
483    unsafe fn fallback() -> Option<OsString> {
484        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
485            n if n < 0 => 512 as usize,
486            n => n as usize,
487        };
488        let mut buf = Vec::with_capacity(amt);
489        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
490        let mut result = ptr::null_mut();
491        match libc::getpwuid_r(
492            libc::getuid(),
493            p.as_mut_ptr(),
494            buf.as_mut_ptr(),
495            buf.capacity(),
496            &mut result,
497        ) {
498            0 if !result.is_null() => {
499                let ptr = (*result).pw_dir as *const _;
500                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
501                Some(OsStringExt::from_vec(bytes))
502            }
503            _ => None,
504        }
505    }
506}