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_mut_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
238        // Determine the required size (in bytes) for the argument array ...
239        let mut argv_size = 0;
240        cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_size, ptr::null_mut(), 0))?;
241
242        // ... allocate a buffer for it ...
243        let argc = argv_size.div_exact(size_of::<*const libc::c_char>()).unwrap();
244        let mut argv = Vec::<*const libc::c_char>::with_capacity(argc);
245
246        // ... and retrieve the argument array.
247        cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_size, ptr::null_mut(), 0))?;
248        let argc = argv_size.div_exact(size_of::<*const libc::c_char>()).unwrap();
249        argv.set_len(argc);
250
251        if argv[0].is_null() {
252            return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
253        }
254        let argv0 = CStr::from_ptr(argv[0]).to_bytes();
255        if argv0.iter().any(|b| *b == b'/') {
256            // The program name is path-like, so try to canonicalize it.
257            crate::fs::canonicalize(OsStr::from_bytes(argv0))
258        } else {
259            // The program was probably found in the PATH. Instead of trying to
260            // find it again (which might not succeed if PATH has changed), just
261            // return the program name – this function is best-effort anyway.
262            Ok(PathBuf::from(OsStr::from_bytes(argv0)))
263        }
264    }
265}
266
267#[cfg(any(
268    target_os = "linux",
269    target_os = "cygwin",
270    target_os = "hurd",
271    target_os = "android",
272    target_os = "nuttx",
273    target_os = "emscripten"
274))]
275pub fn current_exe() -> io::Result<PathBuf> {
276    match crate::fs::read_link("/proc/self/exe") {
277        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!(
278            io::ErrorKind::Uncategorized,
279            "no /proc/self/exe available. Is /proc mounted?",
280        )),
281        other => other,
282    }
283}
284
285#[cfg(any(target_os = "nto", target_os = "qnx"))]
286pub fn current_exe() -> io::Result<PathBuf> {
287    let mut e = crate::fs::read("/proc/self/exefile")?;
288    // Current versions of QNX SDP provide a null-terminated path.
289    // Ensure the trailing null byte is not returned here.
290    if let Some(0) = e.last() {
291        e.pop();
292    }
293    Ok(PathBuf::from(OsString::from_vec(e)))
294}
295
296#[cfg(target_vendor = "apple")]
297pub fn current_exe() -> io::Result<PathBuf> {
298    unsafe {
299        let mut sz: u32 = 0;
300        #[expect(deprecated)]
301        libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
302        if sz == 0 {
303            return Err(io::Error::last_os_error());
304        }
305        let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
306        #[expect(deprecated)]
307        let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
308        if err != 0 {
309            return Err(io::Error::last_os_error());
310        }
311        v.set_len(sz as usize - 1); // chop off trailing NUL
312        Ok(PathBuf::from(OsString::from_vec(v)))
313    }
314}
315
316#[cfg(any(target_os = "solaris", target_os = "illumos"))]
317pub fn current_exe() -> io::Result<PathBuf> {
318    if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
319        Ok(path)
320    } else {
321        unsafe {
322            let path = libc::getexecname();
323            if path.is_null() {
324                Err(io::Error::last_os_error())
325            } else {
326                let filename = CStr::from_ptr(path).to_bytes();
327                let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
328
329                // Prepend a current working directory to the path if
330                // it doesn't contain an absolute pathname.
331                if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
332            }
333        }
334    }
335}
336
337#[cfg(target_os = "haiku")]
338pub fn current_exe() -> io::Result<PathBuf> {
339    let mut name = vec![0; libc::PATH_MAX as usize];
340    unsafe {
341        let result = libc::find_path(
342            crate::ptr::null_mut(),
343            libc::B_FIND_PATH_IMAGE_PATH,
344            crate::ptr::null_mut(),
345            name.as_mut_ptr(),
346            name.len(),
347        );
348        if result != libc::B_OK {
349            Err(io::const_error!(io::ErrorKind::Uncategorized, "error getting executable path"))
350        } else {
351            // find_path adds the null terminator.
352            let name = CStr::from_ptr(name.as_ptr()).to_bytes();
353            Ok(PathBuf::from(OsStr::from_bytes(name)))
354        }
355    }
356}
357
358#[cfg(target_os = "redox")]
359pub fn current_exe() -> io::Result<PathBuf> {
360    crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
361}
362
363#[cfg(target_os = "rtems")]
364pub fn current_exe() -> io::Result<PathBuf> {
365    crate::fs::read_to_string("sys:exe").map(PathBuf::from)
366}
367
368#[cfg(target_os = "l4re")]
369pub fn current_exe() -> io::Result<PathBuf> {
370    Err(io::const_error!(io::ErrorKind::Unsupported, "not yet implemented!"))
371}
372
373#[cfg(target_os = "vxworks")]
374pub fn current_exe() -> io::Result<PathBuf> {
375    #[cfg(test)]
376    use realstd::env;
377
378    #[cfg(not(test))]
379    use crate::env;
380
381    let exe_path = env::args().next().unwrap();
382    let path = path::Path::new(&exe_path);
383    path.canonicalize()
384}
385
386#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
387pub fn current_exe() -> io::Result<PathBuf> {
388    crate::sys::pal::unsupported::unsupported()
389}
390
391#[cfg(target_os = "fuchsia")]
392pub fn current_exe() -> io::Result<PathBuf> {
393    #[cfg(test)]
394    use realstd::env;
395
396    #[cfg(not(test))]
397    use crate::env;
398
399    let exe_path = env::args().next().ok_or(io::const_error!(
400        io::ErrorKind::Uncategorized,
401        "an executable path was not found because no arguments were provided through argv",
402    ))?;
403    let path = PathBuf::from(exe_path);
404
405    // Prepend the current working directory to the path if it's not absolute.
406    if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
407}
408
409#[cfg(target_vendor = "apple")]
410fn darwin_temp_dir() -> PathBuf {
411    crate::sys::pal::conf::confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64))
412        .map(PathBuf::from)
413        .unwrap_or_else(|_| {
414            // It failed for whatever reason (there are several possible reasons),
415            // so return the global one.
416            PathBuf::from("/tmp")
417        })
418}
419
420pub fn temp_dir() -> PathBuf {
421    crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
422        cfg_select! {
423            target_vendor = "apple" => darwin_temp_dir(),
424            target_os = "android" => PathBuf::from("/data/local/tmp"),
425            _ => PathBuf::from("/tmp"),
426        }
427    })
428}
429
430pub fn home_dir() -> Option<PathBuf> {
431    return crate::env::var_os("HOME")
432        .filter(|s| !s.is_empty())
433        .or_else(|| unsafe { fallback() })
434        .map(PathBuf::from);
435
436    #[cfg(any(
437        target_os = "android",
438        target_os = "emscripten",
439        target_os = "redox",
440        target_os = "vxworks",
441        target_os = "espidf",
442        target_os = "horizon",
443        target_os = "vita",
444        target_os = "nuttx",
445        all(target_vendor = "apple", not(target_os = "macos")),
446    ))]
447    unsafe fn fallback() -> Option<OsString> {
448        None
449    }
450    #[cfg(not(any(
451        target_os = "android",
452        target_os = "emscripten",
453        target_os = "redox",
454        target_os = "vxworks",
455        target_os = "espidf",
456        target_os = "horizon",
457        target_os = "vita",
458        target_os = "nuttx",
459        all(target_vendor = "apple", not(target_os = "macos")),
460    )))]
461    unsafe fn fallback() -> Option<OsString> {
462        let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
463            n if n < 0 => 512 as usize,
464            n => n as usize,
465        };
466        let mut buf = Vec::with_capacity(amt);
467        let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
468        let mut result = ptr::null_mut();
469        match libc::getpwuid_r(
470            libc::getuid(),
471            p.as_mut_ptr(),
472            buf.as_mut_ptr(),
473            buf.capacity(),
474            &mut result,
475        ) {
476            0 if !result.is_null() => {
477                let ptr = (*result).pw_dir as *const _;
478                let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
479                Some(OsStringExt::from_vec(bytes))
480            }
481            _ => None,
482        }
483    }
484}