1#![allow(unused_imports)] #[cfg(test)]
6mod tests;
7
8use libc::{c_char, c_int, c_void};
9
10use crate::error::Error as StdError;
11use crate::ffi::{CStr, OsStr, OsString};
12use crate::os::unix::prelude::*;
13use crate::path::{self, PathBuf};
14use crate::sys::common::small_c_string::run_path_with_cstr;
15use crate::sys::cvt;
16use crate::{fmt, io, iter, mem, ptr, slice, str};
17
18const TMPBUF_SZ: usize = 128;
19
20cfg_if::cfg_if! {
21 if #[cfg(target_os = "redox")] {
22 const PATH_SEPARATOR: u8 = b';';
23 } else {
24 const PATH_SEPARATOR: u8 = b':';
25 }
26}
27
28unsafe extern "C" {
29 #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
30 #[cfg_attr(
31 any(
32 target_os = "linux",
33 target_os = "emscripten",
34 target_os = "fuchsia",
35 target_os = "l4re",
36 target_os = "hurd",
37 ),
38 link_name = "__errno_location"
39 )]
40 #[cfg_attr(
41 any(
42 target_os = "netbsd",
43 target_os = "openbsd",
44 target_os = "cygwin",
45 target_os = "android",
46 target_os = "redox",
47 target_os = "nuttx",
48 target_env = "newlib"
49 ),
50 link_name = "__errno"
51 )]
52 #[cfg_attr(any(target_os = "solaris", target_os = "illumos"), link_name = "___errno")]
53 #[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")]
54 #[cfg_attr(any(target_os = "freebsd", target_vendor = "apple"), link_name = "__error")]
55 #[cfg_attr(target_os = "haiku", link_name = "_errnop")]
56 #[cfg_attr(target_os = "aix", link_name = "_Errno")]
57 #[unsafe(ffi_const)]
59 pub safe fn errno_location() -> *mut c_int;
60}
61
62#[cfg(not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")))]
64#[inline]
65pub fn errno() -> i32 {
66 unsafe { (*errno_location()) as i32 }
67}
68
69#[cfg(all(not(target_os = "dragonfly"), not(target_os = "vxworks"), not(target_os = "rtems")))]
72#[allow(dead_code)] #[inline]
74pub fn set_errno(e: i32) {
75 unsafe { *errno_location() = e as c_int }
76}
77
78#[cfg(target_os = "vxworks")]
79#[inline]
80pub fn errno() -> i32 {
81 unsafe { libc::errnoGet() }
82}
83
84#[cfg(target_os = "rtems")]
85#[inline]
86pub fn errno() -> i32 {
87 unsafe extern "C" {
88 #[thread_local]
89 static _tls_errno: c_int;
90 }
91
92 unsafe { _tls_errno as i32 }
93}
94
95#[cfg(target_os = "dragonfly")]
96#[inline]
97pub fn errno() -> i32 {
98 unsafe extern "C" {
99 #[thread_local]
100 static errno: c_int;
101 }
102
103 unsafe { errno as i32 }
104}
105
106#[cfg(target_os = "dragonfly")]
107#[allow(dead_code)]
108#[inline]
109pub fn set_errno(e: i32) {
110 unsafe extern "C" {
111 #[thread_local]
112 static mut errno: c_int;
113 }
114
115 unsafe {
116 errno = e;
117 }
118}
119
120pub fn error_string(errno: i32) -> String {
122 unsafe extern "C" {
123 #[cfg_attr(
124 all(
125 any(
126 target_os = "linux",
127 target_os = "hurd",
128 target_env = "newlib",
129 target_os = "cygwin"
130 ),
131 not(target_env = "ohos")
132 ),
133 link_name = "__xpg_strerror_r"
134 )]
135 fn strerror_r(errnum: c_int, buf: *mut c_char, buflen: libc::size_t) -> c_int;
136 }
137
138 let mut buf = [0 as c_char; TMPBUF_SZ];
139
140 let p = buf.as_mut_ptr();
141 unsafe {
142 if strerror_r(errno as c_int, p, buf.len()) < 0 {
143 panic!("strerror_r failure");
144 }
145
146 let p = p as *const _;
147 String::from_utf8_lossy(CStr::from_ptr(p).to_bytes()).into()
150 }
151}
152
153#[cfg(target_os = "espidf")]
154pub fn getcwd() -> io::Result<PathBuf> {
155 Ok(PathBuf::from("/"))
156}
157
158#[cfg(not(target_os = "espidf"))]
159pub fn getcwd() -> io::Result<PathBuf> {
160 let mut buf = Vec::with_capacity(512);
161 loop {
162 unsafe {
163 let ptr = buf.as_mut_ptr() as *mut libc::c_char;
164 if !libc::getcwd(ptr, buf.capacity()).is_null() {
165 let len = CStr::from_ptr(buf.as_ptr() as *const libc::c_char).to_bytes().len();
166 buf.set_len(len);
167 buf.shrink_to_fit();
168 return Ok(PathBuf::from(OsString::from_vec(buf)));
169 } else {
170 let error = io::Error::last_os_error();
171 if error.raw_os_error() != Some(libc::ERANGE) {
172 return Err(error);
173 }
174 }
175
176 let cap = buf.capacity();
179 buf.set_len(cap);
180 buf.reserve(1);
181 }
182 }
183}
184
185#[cfg(target_os = "espidf")]
186pub fn chdir(_p: &path::Path) -> io::Result<()> {
187 super::unsupported::unsupported()
188}
189
190#[cfg(not(target_os = "espidf"))]
191pub fn chdir(p: &path::Path) -> io::Result<()> {
192 let result = run_path_with_cstr(p, &|p| unsafe { Ok(libc::chdir(p.as_ptr())) })?;
193 if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
194}
195
196pub struct SplitPaths<'a> {
197 iter: iter::Map<slice::Split<'a, u8, fn(&u8) -> bool>, fn(&'a [u8]) -> PathBuf>,
198}
199
200pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
201 fn bytes_to_path(b: &[u8]) -> PathBuf {
202 PathBuf::from(<OsStr as OsStrExt>::from_bytes(b))
203 }
204 fn is_separator(b: &u8) -> bool {
205 *b == PATH_SEPARATOR
206 }
207 let unparsed = unparsed.as_bytes();
208 SplitPaths {
209 iter: unparsed
210 .split(is_separator as fn(&u8) -> bool)
211 .map(bytes_to_path as fn(&[u8]) -> PathBuf),
212 }
213}
214
215impl<'a> Iterator for SplitPaths<'a> {
216 type Item = PathBuf;
217 fn next(&mut self) -> Option<PathBuf> {
218 self.iter.next()
219 }
220 fn size_hint(&self) -> (usize, Option<usize>) {
221 self.iter.size_hint()
222 }
223}
224
225#[derive(Debug)]
226pub struct JoinPathsError;
227
228pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
229where
230 I: Iterator<Item = T>,
231 T: AsRef<OsStr>,
232{
233 let mut joined = Vec::new();
234
235 for (i, path) in paths.enumerate() {
236 let path = path.as_ref().as_bytes();
237 if i > 0 {
238 joined.push(PATH_SEPARATOR)
239 }
240 if path.contains(&PATH_SEPARATOR) {
241 return Err(JoinPathsError);
242 }
243 joined.extend_from_slice(path);
244 }
245 Ok(OsStringExt::from_vec(joined))
246}
247
248impl fmt::Display for JoinPathsError {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR))
251 }
252}
253
254impl StdError for JoinPathsError {
255 #[allow(deprecated)]
256 fn description(&self) -> &str {
257 "failed to join paths"
258 }
259}
260
261#[cfg(target_os = "aix")]
262pub fn current_exe() -> io::Result<PathBuf> {
263 #[cfg(test)]
264 use realstd::env;
265
266 #[cfg(not(test))]
267 use crate::env;
268 use crate::io::ErrorKind;
269
270 let exe_path = env::args().next().ok_or(io::const_error!(
271 ErrorKind::NotFound,
272 "an executable path was not found because no arguments were provided through argv",
273 ))?;
274 let path = PathBuf::from(exe_path);
275 if path.is_absolute() {
276 return path.canonicalize();
277 }
278 if let Some(pstr) = path.to_str()
280 && pstr.contains("/")
281 {
282 return getcwd().map(|cwd| cwd.join(path))?.canonicalize();
283 }
284 if let Some(p) = getenv(OsStr::from_bytes("PATH".as_bytes())) {
286 for search_path in split_paths(&p) {
287 let pb = search_path.join(&path);
288 if pb.is_file()
289 && let Ok(metadata) = crate::fs::metadata(&pb)
290 && metadata.permissions().mode() & 0o111 != 0
291 {
292 return pb.canonicalize();
293 }
294 }
295 }
296 Err(io::const_error!(ErrorKind::NotFound, "an executable path was not found"))
297}
298
299#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
300pub fn current_exe() -> io::Result<PathBuf> {
301 unsafe {
302 let mut mib = [
303 libc::CTL_KERN as c_int,
304 libc::KERN_PROC as c_int,
305 libc::KERN_PROC_PATHNAME as c_int,
306 -1 as c_int,
307 ];
308 let mut sz = 0;
309 cvt(libc::sysctl(
310 mib.as_mut_ptr(),
311 mib.len() as libc::c_uint,
312 ptr::null_mut(),
313 &mut sz,
314 ptr::null_mut(),
315 0,
316 ))?;
317 if sz == 0 {
318 return Err(io::Error::last_os_error());
319 }
320 let mut v: Vec<u8> = Vec::with_capacity(sz);
321 cvt(libc::sysctl(
322 mib.as_mut_ptr(),
323 mib.len() as libc::c_uint,
324 v.as_mut_ptr() as *mut libc::c_void,
325 &mut sz,
326 ptr::null_mut(),
327 0,
328 ))?;
329 if sz == 0 {
330 return Err(io::Error::last_os_error());
331 }
332 v.set_len(sz - 1); Ok(PathBuf::from(OsString::from_vec(v)))
334 }
335}
336
337#[cfg(target_os = "netbsd")]
338pub fn current_exe() -> io::Result<PathBuf> {
339 fn sysctl() -> io::Result<PathBuf> {
340 unsafe {
341 let mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, -1, libc::KERN_PROC_PATHNAME];
342 let mut path_len: usize = 0;
343 cvt(libc::sysctl(
344 mib.as_ptr(),
345 mib.len() as libc::c_uint,
346 ptr::null_mut(),
347 &mut path_len,
348 ptr::null(),
349 0,
350 ))?;
351 if path_len <= 1 {
352 return Err(io::const_error!(
353 io::ErrorKind::Uncategorized,
354 "KERN_PROC_PATHNAME sysctl returned zero-length string",
355 ));
356 }
357 let mut path: Vec<u8> = Vec::with_capacity(path_len);
358 cvt(libc::sysctl(
359 mib.as_ptr(),
360 mib.len() as libc::c_uint,
361 path.as_ptr() as *mut libc::c_void,
362 &mut path_len,
363 ptr::null(),
364 0,
365 ))?;
366 path.set_len(path_len - 1); Ok(PathBuf::from(OsString::from_vec(path)))
368 }
369 }
370 fn procfs() -> io::Result<PathBuf> {
371 let curproc_exe = path::Path::new("/proc/curproc/exe");
372 if curproc_exe.is_file() {
373 return crate::fs::read_link(curproc_exe);
374 }
375 Err(io::const_error!(
376 io::ErrorKind::Uncategorized,
377 "/proc/curproc/exe doesn't point to regular file.",
378 ))
379 }
380 sysctl().or_else(|_| procfs())
381}
382
383#[cfg(target_os = "openbsd")]
384pub fn current_exe() -> io::Result<PathBuf> {
385 unsafe {
386 let mut mib = [libc::CTL_KERN, libc::KERN_PROC_ARGS, libc::getpid(), libc::KERN_PROC_ARGV];
387 let mib = mib.as_mut_ptr();
388 let mut argv_len = 0;
389 cvt(libc::sysctl(mib, 4, ptr::null_mut(), &mut argv_len, ptr::null_mut(), 0))?;
390 let mut argv = Vec::<*const libc::c_char>::with_capacity(argv_len as usize);
391 cvt(libc::sysctl(mib, 4, argv.as_mut_ptr() as *mut _, &mut argv_len, ptr::null_mut(), 0))?;
392 argv.set_len(argv_len as usize);
393 if argv[0].is_null() {
394 return Err(io::const_error!(io::ErrorKind::Uncategorized, "no current exe available"));
395 }
396 let argv0 = CStr::from_ptr(argv[0]).to_bytes();
397 if argv0[0] == b'.' || argv0.iter().any(|b| *b == b'/') {
398 crate::fs::canonicalize(OsStr::from_bytes(argv0))
399 } else {
400 Ok(PathBuf::from(OsStr::from_bytes(argv0)))
401 }
402 }
403}
404
405#[cfg(any(
406 target_os = "linux",
407 target_os = "cygwin",
408 target_os = "hurd",
409 target_os = "android",
410 target_os = "nuttx",
411 target_os = "emscripten"
412))]
413pub fn current_exe() -> io::Result<PathBuf> {
414 match crate::fs::read_link("/proc/self/exe") {
415 Err(ref e) if e.kind() == io::ErrorKind::NotFound => Err(io::const_error!(
416 io::ErrorKind::Uncategorized,
417 "no /proc/self/exe available. Is /proc mounted?",
418 )),
419 other => other,
420 }
421}
422
423#[cfg(target_os = "nto")]
424pub fn current_exe() -> io::Result<PathBuf> {
425 let mut e = crate::fs::read("/proc/self/exefile")?;
426 if let Some(0) = e.last() {
429 e.pop();
430 }
431 Ok(PathBuf::from(OsString::from_vec(e)))
432}
433
434#[cfg(target_vendor = "apple")]
435pub fn current_exe() -> io::Result<PathBuf> {
436 unsafe {
437 let mut sz: u32 = 0;
438 #[expect(deprecated)]
439 libc::_NSGetExecutablePath(ptr::null_mut(), &mut sz);
440 if sz == 0 {
441 return Err(io::Error::last_os_error());
442 }
443 let mut v: Vec<u8> = Vec::with_capacity(sz as usize);
444 #[expect(deprecated)]
445 let err = libc::_NSGetExecutablePath(v.as_mut_ptr() as *mut i8, &mut sz);
446 if err != 0 {
447 return Err(io::Error::last_os_error());
448 }
449 v.set_len(sz as usize - 1); Ok(PathBuf::from(OsString::from_vec(v)))
451 }
452}
453
454#[cfg(any(target_os = "solaris", target_os = "illumos"))]
455pub fn current_exe() -> io::Result<PathBuf> {
456 if let Ok(path) = crate::fs::read_link("/proc/self/path/a.out") {
457 Ok(path)
458 } else {
459 unsafe {
460 let path = libc::getexecname();
461 if path.is_null() {
462 Err(io::Error::last_os_error())
463 } else {
464 let filename = CStr::from_ptr(path).to_bytes();
465 let path = PathBuf::from(<OsStr as OsStrExt>::from_bytes(filename));
466
467 if filename[0] == b'/' { Ok(path) } else { getcwd().map(|cwd| cwd.join(path)) }
470 }
471 }
472 }
473}
474
475#[cfg(target_os = "haiku")]
476pub fn current_exe() -> io::Result<PathBuf> {
477 let mut name = vec![0; libc::PATH_MAX as usize];
478 unsafe {
479 let result = libc::find_path(
480 crate::ptr::null_mut(),
481 libc::path_base_directory::B_FIND_PATH_IMAGE_PATH,
482 crate::ptr::null_mut(),
483 name.as_mut_ptr(),
484 name.len(),
485 );
486 if result != libc::B_OK {
487 use crate::io::ErrorKind;
488 Err(io::const_error!(ErrorKind::Uncategorized, "error getting executable path"))
489 } else {
490 let name = CStr::from_ptr(name.as_ptr()).to_bytes();
492 Ok(PathBuf::from(OsStr::from_bytes(name)))
493 }
494 }
495}
496
497#[cfg(target_os = "redox")]
498pub fn current_exe() -> io::Result<PathBuf> {
499 crate::fs::read_to_string("/scheme/sys/exe").map(PathBuf::from)
500}
501
502#[cfg(target_os = "rtems")]
503pub fn current_exe() -> io::Result<PathBuf> {
504 crate::fs::read_to_string("sys:exe").map(PathBuf::from)
505}
506
507#[cfg(target_os = "l4re")]
508pub fn current_exe() -> io::Result<PathBuf> {
509 use crate::io::ErrorKind;
510 Err(io::const_error!(ErrorKind::Unsupported, "not yet implemented!"))
511}
512
513#[cfg(target_os = "vxworks")]
514pub fn current_exe() -> io::Result<PathBuf> {
515 #[cfg(test)]
516 use realstd::env;
517
518 #[cfg(not(test))]
519 use crate::env;
520
521 let exe_path = env::args().next().unwrap();
522 let path = path::Path::new(&exe_path);
523 path.canonicalize()
524}
525
526#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
527pub fn current_exe() -> io::Result<PathBuf> {
528 super::unsupported::unsupported()
529}
530
531#[cfg(target_os = "fuchsia")]
532pub fn current_exe() -> io::Result<PathBuf> {
533 #[cfg(test)]
534 use realstd::env;
535
536 #[cfg(not(test))]
537 use crate::env;
538 use crate::io::ErrorKind;
539
540 let exe_path = env::args().next().ok_or(io::const_error!(
541 ErrorKind::Uncategorized,
542 "an executable path was not found because no arguments were provided through argv",
543 ))?;
544 let path = PathBuf::from(exe_path);
545
546 if !path.is_absolute() { getcwd().map(|cwd| cwd.join(path)) } else { Ok(path) }
548}
549
550#[cfg(not(target_os = "espidf"))]
551pub fn page_size() -> usize {
552 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
553}
554
555#[cfg(all(target_vendor = "apple", not(miri)))]
564fn confstr(key: c_int, size_hint: Option<usize>) -> io::Result<OsString> {
565 let mut buf: Vec<u8> = Vec::with_capacity(0);
566 let mut bytes_needed_including_nul = size_hint
567 .unwrap_or_else(|| {
568 unsafe { libc::confstr(key, core::ptr::null_mut(), 0) }
573 })
574 .max(1);
575 while bytes_needed_including_nul > buf.capacity() {
580 buf.reserve(bytes_needed_including_nul);
586 bytes_needed_including_nul =
593 unsafe { libc::confstr(key, buf.as_mut_ptr().cast::<c_char>(), buf.capacity()) };
594 }
595 if bytes_needed_including_nul == 0 {
597 return Err(io::Error::last_os_error());
598 }
599 unsafe {
603 buf.set_len(bytes_needed_including_nul);
604 let last_byte = buf.pop();
606 assert_eq!(last_byte, Some(0), "`confstr` provided a string which wasn't nul-terminated");
608 };
609 Ok(OsString::from_vec(buf))
610}
611
612#[cfg(all(target_vendor = "apple", not(miri)))]
613fn darwin_temp_dir() -> PathBuf {
614 confstr(libc::_CS_DARWIN_USER_TEMP_DIR, Some(64)).map(PathBuf::from).unwrap_or_else(|_| {
615 PathBuf::from("/tmp")
618 })
619}
620
621pub fn temp_dir() -> PathBuf {
622 crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| {
623 cfg_if::cfg_if! {
624 if #[cfg(all(target_vendor = "apple", not(miri)))] {
625 darwin_temp_dir()
626 } else if #[cfg(target_os = "android")] {
627 PathBuf::from("/data/local/tmp")
628 } else {
629 PathBuf::from("/tmp")
630 }
631 }
632 })
633}
634
635pub fn home_dir() -> Option<PathBuf> {
636 return crate::env::var_os("HOME").or_else(|| unsafe { fallback() }).map(PathBuf::from);
637
638 #[cfg(any(
639 target_os = "android",
640 target_os = "emscripten",
641 target_os = "redox",
642 target_os = "vxworks",
643 target_os = "espidf",
644 target_os = "horizon",
645 target_os = "vita",
646 target_os = "nuttx",
647 all(target_vendor = "apple", not(target_os = "macos")),
648 ))]
649 unsafe fn fallback() -> Option<OsString> {
650 None
651 }
652 #[cfg(not(any(
653 target_os = "android",
654 target_os = "emscripten",
655 target_os = "redox",
656 target_os = "vxworks",
657 target_os = "espidf",
658 target_os = "horizon",
659 target_os = "vita",
660 target_os = "nuttx",
661 all(target_vendor = "apple", not(target_os = "macos")),
662 )))]
663 unsafe fn fallback() -> Option<OsString> {
664 let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) {
665 n if n < 0 => 512 as usize,
666 n => n as usize,
667 };
668 let mut buf = Vec::with_capacity(amt);
669 let mut p = mem::MaybeUninit::<libc::passwd>::uninit();
670 let mut result = ptr::null_mut();
671 match libc::getpwuid_r(
672 libc::getuid(),
673 p.as_mut_ptr(),
674 buf.as_mut_ptr(),
675 buf.capacity(),
676 &mut result,
677 ) {
678 0 if !result.is_null() => {
679 let ptr = (*result).pw_dir as *const _;
680 let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
681 Some(OsStringExt::from_vec(bytes))
682 }
683 _ => None,
684 }
685 }
686}
687
688pub fn exit(code: i32) -> ! {
689 crate::sys::exit_guard::unique_thread_exit();
690 unsafe { libc::exit(code as c_int) }
691}
692
693pub fn getpid() -> u32 {
694 unsafe { libc::getpid() as u32 }
695}
696
697pub fn getppid() -> u32 {
698 unsafe { libc::getppid() as u32 }
699}
700
701#[cfg(all(target_os = "linux", target_env = "gnu"))]
702pub fn glibc_version() -> Option<(usize, usize)> {
703 unsafe extern "C" {
704 fn gnu_get_libc_version() -> *const libc::c_char;
705 }
706 let version_cstr = unsafe { CStr::from_ptr(gnu_get_libc_version()) };
707 if let Ok(version_str) = version_cstr.to_str() {
708 parse_glibc_version(version_str)
709 } else {
710 None
711 }
712}
713
714#[cfg(all(target_os = "linux", target_env = "gnu"))]
717fn parse_glibc_version(version: &str) -> Option<(usize, usize)> {
718 let mut parsed_ints = version.split('.').map(str::parse::<usize>).fuse();
719 match (parsed_ints.next(), parsed_ints.next()) {
720 (Some(Ok(major)), Some(Ok(minor))) => Some((major, minor)),
721 _ => None,
722 }
723}