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