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, 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::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
79#[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)]
80pub struct JoinPathsError;
81
82pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
83where
84    I: Iterator<Item = T>,
85    T: AsRef<OsStr>,
86{
87    let mut joined = Vec::new();
88
89    for (i, path) in paths.enumerate() {
90        let path = path.as_ref().as_bytes();
91        if i > 0 {
92            joined.push(PATH_SEPARATOR)
93        }
94        if path.contains(&PATH_SEPARATOR) {
95            return Err(JoinPathsError);
96        }
97        joined.extend_from_slice(path);
98    }
99    Ok(OsStringExt::from_vec(joined))
100}
101
102impl fmt::Display for JoinPathsError {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.write_fmt(format_args!("path segment contains separator `{0}`",
        char::from(PATH_SEPARATOR)))write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
105    }
106}
107
108impl crate::error::Error for JoinPathsError {}
109
110#[cfg(target_os = "aix")]
111pub fn current_exe() -> io::Result<PathBuf> {
112    #[cfg(test)]
113    use realstd::env;
114
115    #[cfg(not(test))]
116    use crate::env;
117    use crate::io;
118
119    let exe_path = env::args().next().ok_or(io::const_error!(
120        io::ErrorKind::NotFound,
121        "an executable path was not found because no arguments were provided through argv",
122    ))?;
123    let path = PathBuf::from(exe_path);
124    if path.is_absolute() {
125        return path.canonicalize();
126    }
127    // Search PWD to infer current_exe.
128    if let Some(pstr) = path.to_str()
129        && pstr.contains("/")
130    {
131        return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
132    }
133    // Search PATH to infer current_exe.
134    if let Some(p) = env::var_os(OsStr::from_bytes("PATH".as_bytes())) {
135        for search_path in split_paths(&p) {
136            let pb = search_path.join(&path);
137            if pb.is_file()
138                && let Ok(metadata) = crate::fs::metadata(&pb)
139                && metadata.permissions().mode() & 0o111 != 0
140            {
141                return pb.canonicalize();
142            }
143        }
144    }
145    Err(io::const_error!(io::ErrorKind::NotFound, "an executable path was not found"))
146}
147
148#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
149pub fn current_exe() -> io::Result<PathBuf> {
150    unsafe {
151        let mut mib = [
152            libc::CTL_KERN as c_int,
153            libc::KERN_PROC as c_int,
154            libc::KERN_PROC_PATHNAME as c_int,
155            -1 as c_int,
156        ];
157        let mut sz = 0;
158        cvt(libc::sysctl(
159            mib.as_mut_ptr(),
160            mib.len() as libc::c_uint,
161            ptr::null_mut(),
162            &mut sz,
163            ptr::null_mut(),
164            0,
165        ))?;
166        if sz == 0 {
167            return Err(io::Error::last_os_error());
168        }
169        let mut v: Vec<u8> = Vec::with_capacity(sz);
170        cvt(libc::sysctl(
171            mib.as_mut_ptr(),
172            mib.len() as libc::c_uint,
173            v.as_mut_ptr() as *mut libc::c_void,
174            &mut sz,
175            ptr::null_mut(),
176            0,
177        ))?;
178        if sz == 0 {
179            return Err(io::Error::last_os_error());
180        }
181        v.set_len(sz - 1); // chop off trailing NUL
182        Ok(PathBuf::from(OsString::from_vec(v)))
183    }
184}
185
186#[cfg(target_os = "netbsd")]
187pub fn current_exe() -> io::Result<PathBuf> {
188    fn sysctl() -> io::Result<PathBuf> {
189        unsafe {
190            let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
191            let mut path_len: usize = 0;
192            cvt(libc::sysctl(
193                mib.as_ptr(),
194                mib.len() as libc::c_uint,
195                ptr::null_mut(),
196                &mut path_len,
197                ptr::null(),
198                0,
199            ))?;
200            if path_len <= 1 {
201                return Err(io::const_error!(
202                    io::ErrorKind::Uncategorized,
203                    "KERN_PROC_PATHNAME sysctl returned zero-length string",
204                ));
205            }
206            let mut path: Vec<u8> = Vec::with_capacity(path_len);
207            cvt(libc::sysctl(
208                mib.as_ptr(),
209                mib.len() as libc::c_uint,
210                path.as_ptr() as *mut libc::c_void,
211                &mut path_len,
212                ptr::null(),
213                0,
214            ))?;
215            path.set_len(path_len - 1); // chop off NUL
216            Ok(PathBuf::from(OsString::from_vec(path)))
217        }
218    }
219    fn procfs() -> io::Result<PathBuf> {
220        let curproc_exe = path::Path::new("/proc/curproc/exe");
221        if curproc_exe.is_file() {
222            return crate::fs::read_link(curproc_exe);
223        }
224        Err(io::const_error!(
225            io::ErrorKind::Uncategorized,
226            "/proc/curproc/exe doesn't point to regular file.",
227        ))
228    }
229    sysctl().or_else(|_| procfs())
230}
231
232#[cfg(target_os = "openbsd")]
233pub fn current_exe() -> io::Result<PathBuf> {
234    unsafe {
235        let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
236        let mib = mib.as_mut_ptr();
237        let mut argv_len = 0;
238        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_len, ptr::null_mut(), 0))?;
239        let mut argv = Vec::<*const libc::c_char>::with_capacity(argv_len as usize);
240        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_len, ptr::null_mut(), 0))?;
241        argv.set_len(argv_len as usize);
242        if argv[0].is_null() {
243            return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
244        }
245        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
246        if argv0[0] == b'.' || argv0.iter().any(|b| *b == b'/') {
247            crate::fs::canonicalize(OsStr::from_bytes(argv0))
248        } else {
249            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
250        }
251    }
252}
253
254#[cfg(any(
255    target_os = "linux",
256    target_os = "cygwin",
257    target_os = "hurd",
258    target_os = "android",
259    target_os = "nuttx",
260    target_os = "emscripten"
261))]
262pub fn current_exe() -> io::Result<PathBuf> {
263    match crate::fs::read_link("/proc/self/exe") {
264        Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(crate::hint::must_use(crate::io::Error::from_static_message(const {
                &crate::io::SimpleMessage {
                        kind: io::ErrorKind::Uncategorized,
                        message: "no /proc/self/exe available. Is /proc mounted?",
                    }
            }))io::const_error!(
265            io::ErrorKind::Uncategorized,
266            "no /proc/self/exe available. Is /proc mounted?",
267        )),
268        other => other,
269    }
270}
271
272#[cfg(target_os = "nto")]
273pub fn current_exe() -> io::Result<PathBuf> {
274    let mut e = crate::fs::read("/proc/self/exefile")?;
275    // Current versions of QNX Neutrino provide a null-terminated path.
276    // Ensure the trailing null byte is not returned here.
277    if let Some(0) = e.last() {
278        e.pop();
279    }
280    Ok(PathBuf::from(OsString::from_vec(e)))
281}
282
283#[cfg(target_vendor = "apple")]
284pub fn current_exe() -> io::Result<PathBuf> {
285    unsafe {
286        let mut sz: u32 = 0;
287        #[expect(deprecated)]
288        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
289        if sz == 0 {
290            return Err(io::Error::last_os_error());
291        }
292        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
293        #[expect(deprecated)]
294        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
295        if err != 0 {
296            return Err(io::Error::last_os_error());
297        }
298        v.set_len(sz as usize - 1); // chop off trailing NUL
299        Ok(PathBuf::from(OsString::from_vec(v)))
300    }
301}
302
303#[cfg(any(target_os = "solaris", target_os = "illumos"))]
304pub fn current_exe() -> io::Result<PathBuf> {
305    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
306        Ok(path)
307    } else {
308        unsafe {
309            let path = libc::getexecname();
310            if path.is_null() {
311                Err(io::Error::last_os_error())
312            } else {
313                let filename = CStr::from_ptr(path).to_bytes();
314                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
315
316                // Prepend a current working directory to the path if
317                // it doesn't contain an absolute pathname.
318                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
319            }
320        }
321    }
322}
323
324#[cfg(target_os = "haiku")]
325pub fn current_exe() -> io::Result<PathBuf> {
326    let mut name = vec![0; libc::PATH_MAX as usize];
327    unsafe {
328        let result = libc::find_path(
329            crate::ptr::null_mut(),
330            libc::B_FIND_PATH_IMAGE_PATH,
331            crate::ptr::null_mut(),
332            name.as_mut_ptr(),
333            name.len(),
334        );
335        if result != libc::B_OK {
336            Err(io::const_error!(io::ErrorKind::Uncategorized, "error getting executable path"))
337        } else {
338            // find_path adds the null terminator.
339            let name = CStr::from_ptr(name.as_ptr()).to_bytes();
340            Ok(PathBuf::from(OsStr::from_bytes(name)))
341        }
342    }
343}
344
345#[cfg(target_os = "redox")]
346pub fn current_exe() -> io::Result<PathBuf> {
347    crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
348}
349
350#[cfg(target_os = "rtems")]
351pub fn current_exe() -> io::Result<PathBuf> {
352    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
353}
354
355#[cfg(target_os = "l4re")]
356pub fn current_exe() -> io::Result<PathBuf> {
357    Err(io::const_error!(io::ErrorKind::Unsupported, "not yet implemented!"))
358}
359
360#[cfg(target_os = "vxworks")]
361pub fn current_exe() -> io::Result<PathBuf> {
362    #[cfg(test)]
363    use realstd::env;
364
365    #[cfg(not(test))]
366    use crate::env;
367
368    let exe_path = env::args().next().unwrap();
369    let path = path::Path::new(&exe_path);
370    path.canonicalize()
371}
372
373#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
374pub fn current_exe() -> io::Result<PathBuf> {
375    crate::sys::pal::unsupported::unsupported()
376}
377
378#[cfg(target_os = "fuchsia")]
379pub fn current_exe() -> io::Result<PathBuf> {
380    #[cfg(test)]
381    use realstd::env;
382
383    #[cfg(not(test))]
384    use crate::env;
385
386    let exe_path = env::args().next().ok_or(io::const_error!(
387        io::ErrorKind::Uncategorized,
388        "an executable path was not found because no arguments were provided through argv",
389    ))?;
390    let path = PathBuf::from(exe_path);
391
392    // Prepend the current working directory to the path if it's not absolute.
393    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
394}
395
396#[cfg(all(target_vendor = "apple", not(miri)))]
397fn darwin_temp_dir() -> PathBuf {
398    crate::sys::pal::conf::confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64))
399        .map(PathBuf::from)
400        .unwrap_or_else(|_| {
401            // It failed for whatever reason (there are several possible reasons),
402            // so return the global one.
403            PathBuf::from("/tmp")
404        })
405}
406
407pub fn temp_dir() -> PathBuf {
408    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
409        cfg_select! {
410            all(target_vendor = "apple", not(miri)) => darwin_temp_dir(),
411            target_os = "android" => PathBuf::from("/data/local/tmp"),
412            _ => PathBuf::from("/tmp"),
413        }
414    })
415}
416
417pub fn home_dir() -> Option<PathBuf> {
418    return crate::env::var_os("HOME")
419        .filter(|s| !s.is_empty())
420        .or_else(|| unsafe { fallback() })
421        .map(PathBuf::from);
422
423    #[cfg(any(
424        target_os = "android",
425        target_os = "emscripten",
426        target_os = "redox",
427        target_os = "vxworks",
428        target_os = "espidf",
429        target_os = "horizon",
430        target_os = "vita",
431        target_os = "nuttx",
432        all(target_vendor = "apple", not(target_os = "macos")),
433    ))]
434    unsafe fn fallback() -> Option<OsString> {
435        None
436    }
437    #[cfg(not(any(
438        target_os = "android",
439        target_os = "emscripten",
440        target_os = "redox",
441        target_os = "vxworks",
442        target_os = "espidf",
443        target_os = "horizon",
444        target_os = "vita",
445        target_os = "nuttx",
446        all(target_vendor = "apple", not(target_os = "macos")),
447    )))]
448    unsafe fn fallback() -> Option<OsString> {
449        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
450            n if n < 0 => 512 as usize,
451            n => n as usize,
452        };
453        let mut buf = Vec::with_capacity(amt);
454        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
455        let mut result = ptr::null_mut();
456        match libc::getpwuid_r(
457            libc::getuid(),
458            p.as_mut_ptr(),
459            buf.as_mut_ptr(),
460            buf.capacity(),
461            &mut result,
462        ) {
463            0 if !result.is_null() => {
464                let ptr = (*result).pw_dir as *const _;
465                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
466                Some(OsStringExt::from_vec(bytes))
467            }
468            _ => None,
469        }
470    }
471}