1#![allow(unused_imports)] #[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 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
62pub 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 if let Some(pstr) = path.to_str()
132 && pstr.contains("/")
133 {
134 return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
135 }
136 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); 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); 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 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); 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 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 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 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#[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 unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
422 })
423 .max(1);
424 while bytes_needed_including_nul > buf.capacity() {
429 buf.reserve(bytes_needed_including_nul);
435 bytes_needed_including_nul =
442 unsafe { libc::confstr(key, buf.as_mut_ptr().cast::<c_char>(), buf.capacity()) };
443 }
444 if bytes_needed_including_nul == 0 {
446 return Err(io::Error::last_os_error());
447 }
448 unsafe {
452 buf.set_len(bytes_needed_including_nul);
453 let last_byte = buf.pop();
455 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 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#[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}