1#![cfg(any(target_os = "linux", target_os = "android"))]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4#[cfg(feature = "std")]
5#[doc(no_inline)]
6pub use std::os::unix::io::RawFd;
7#[cfg(all(not(feature = "std"), not(target_arch = "loongarch64")))]
8pub type RawFd = cty::c_int;
10#[cfg(all(not(feature = "std"), target_arch = "loongarch64"))]
11pub type RawFd = core::ffi::c_int;
13
14#[cfg(not(extern_cstr))]
15pub use core::ffi::CStr;
16#[cfg(extern_cstr)]
17pub use cstr_core::CStr;
18
19#[cfg(feature = "std")]
20pub use std::path::Path;
21#[cfg(not(feature = "std"))]
22pub use CStr as Path;
23
24pub use linux_syscalls::Errno;
25
26mod dev;
27pub mod raw;
28
29use core::fmt;
30
31use linux_syscalls::bitflags;
32
33pub use self::dev::*;
34
35pub const CURRENT_DIRECTORY: RawFd = linux_raw_sys::general::AT_FDCWD;
37
38bitflags! {
39 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41 pub enum StatAtFlags: u32 {
42 EMPTY_PATH = linux_raw_sys::general::AT_EMPTY_PATH,
49 NO_AUTOMOUNT = linux_raw_sys::general::AT_NO_AUTOMOUNT,
53 SYMLINK_NOFOLLOW = linux_raw_sys::general::AT_SYMLINK_NOFOLLOW,
57 }
58}
59
60bitflags! {
61 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63 pub enum ModePermission : u8 {
64 READ = 0o4,
66 WRITE = 0o2,
68 EXEC = 0o1,
70 }
71}
72
73#[repr(transparent)]
75#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
76pub struct Mode(pub(crate) u16);
77
78impl Mode {
79 #[inline]
81 pub const fn owner(&self) -> ModePermission {
82 ModePermission::from_bits(((self.0 >> 6) & 0o7) as u8)
83 }
84
85 #[inline]
87 pub const fn group(&self) -> ModePermission {
88 ModePermission::from_bits(((self.0 >> 3) & 0o7) as u8)
89 }
90
91 #[inline]
93 pub const fn other(&self) -> ModePermission {
94 ModePermission::from_bits((self.0 & 0o7) as u8)
95 }
96
97 #[inline]
99 pub const fn suid(&self) -> bool {
100 const S_ISUID: u16 = linux_raw_sys::general::S_ISUID as u16;
101
102 self.0 & S_ISUID == S_ISUID
103 }
104
105 #[inline]
107 pub const fn sgid(&self) -> bool {
108 const S_ISGID: u16 = linux_raw_sys::general::S_ISGID as u16;
109
110 self.0 & S_ISGID == S_ISGID
111 }
112
113 #[inline]
115 pub const fn svtx(&self) -> bool {
116 const S_ISVTX: u16 = linux_raw_sys::general::S_ISVTX as u16;
117
118 self.0 & S_ISVTX == S_ISVTX
119 }
120
121 #[inline]
123 pub const fn from_u16(value: u16) -> Self {
124 Self(value)
125 }
126
127 #[inline]
129 pub const fn as_u16(&self) -> u16 {
130 self.0
131 }
132}
133
134impl From<u16> for Mode {
135 #[inline]
136 fn from(value: u16) -> Self {
137 Self(value)
138 }
139}
140
141impl fmt::Debug for Mode {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 fn format_c(f: &mut fmt::Formatter<'_>, b: bool, c: char) -> fmt::Result {
144 if b {
145 fmt::Display::fmt(&c, f)
146 } else {
147 fmt::Display::fmt(&'-', f)
148 }
149 }
150
151 fn format_perm(
152 f: &mut fmt::Formatter<'_>,
153 p: ModePermission,
154 x: Option<char>,
155 ) -> fmt::Result {
156 format_c(f, p.contains(ModePermission::READ), 'r')?;
157 format_c(f, p.contains(ModePermission::WRITE), 'w')?;
158 if let Some(x) = x {
159 fmt::Display::fmt(&x, f)
160 } else {
161 format_c(f, p.contains(ModePermission::EXEC), 'x')
162 }
163 }
164
165 write!(f, "Mode(")?;
166 format_perm(f, self.owner(), if self.suid() { Some('s') } else { None })?;
167 format_perm(f, self.group(), if self.sgid() { Some('s') } else { None })?;
168 format_perm(f, self.other(), None)?;
169 write!(f, ")")
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
175pub struct Timestamp {
176 secs: i64,
177 nsecs: u32,
178}
179
180impl Timestamp {
181 #[inline]
183 pub const fn seconds(&self) -> i64 {
184 self.secs
185 }
186
187 #[inline]
189 pub const fn secs(&self) -> i64 {
190 self.secs
191 }
192
193 #[inline]
195 pub const fn nanoseconds(&self) -> u32 {
196 self.nsecs
197 }
198
199 #[inline]
201 pub const fn nanosecs(&self) -> u32 {
202 self.nsecs
203 }
204
205 #[inline]
207 pub const fn nsecs(&self) -> u32 {
208 self.nsecs
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
214pub enum FileType {
215 Block,
217 Character,
219 Directory,
221 Fifo,
223 Link,
225 Regular,
227 Socket,
229 Unknown,
231}
232
233impl FileType {
234 pub fn as_u16(&self) -> u16 {
235 use linux_raw_sys::general::{
236 S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFREG, S_IFSOCK,
237 };
238
239 (match *self {
240 FileType::Socket => S_IFSOCK,
241 FileType::Link => S_IFLNK,
242 FileType::Regular => S_IFREG,
243 FileType::Block => S_IFBLK,
244 FileType::Directory => S_IFDIR,
245 FileType::Character => S_IFCHR,
246 FileType::Fifo => S_IFIFO,
247 FileType::Unknown => 0,
248 }) as u16
249 }
250}
251
252#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
253static mut HAS_STATX: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(2);
254
255#[cfg(any(feature = "linux_4_11", target_arch = "loongarch64"))]
257pub type Stat = crate::raw::Statx;
258
259#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
261#[derive(Clone, Copy)]
262pub enum Stat {
263 Stat64(crate::raw::stat),
264 Statx(crate::raw::Statx),
265}
266
267#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
268macro_rules! with_stat {
269 ($outer:expr, |$name:ident| $($tt:tt)+) => {
270 match $outer {
271 $crate::Stat::Stat64($name) => $($tt)+,
272 $crate::Stat::Statx($name) => $($tt)+,
273 }
274 };
275}
276
277#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
278impl Stat {
279 #[inline]
283 pub const fn block_size(&self) -> i32 {
284 with_stat!(self, |s| s.block_size())
285 }
286
287 #[inline]
289 pub const fn nlink(&self) -> u32 {
290 with_stat!(self, |s| s.nlink())
291 }
292
293 #[inline]
295 pub const fn uid(&self) -> u32 {
296 with_stat!(self, |s| s.uid())
297 }
298
299 #[inline]
301 pub const fn gid(&self) -> u32 {
302 with_stat!(self, |s| s.gid())
303 }
304
305 #[inline]
307 pub const fn mode(&self) -> Mode {
308 with_stat!(self, |s| s.mode())
309 }
310
311 pub const fn file_type(&self) -> FileType {
313 with_stat!(self, |s| s.file_type())
314 }
315
316 #[inline]
318 pub const fn is_socket(&self) -> bool {
319 with_stat!(self, |s| s.is_socket())
320 }
321
322 #[inline]
324 pub const fn is_link(&self) -> bool {
325 with_stat!(self, |s| s.is_link())
326 }
327
328 #[inline]
330 pub const fn is_regular(&self) -> bool {
331 with_stat!(self, |s| s.is_regular())
332 }
333
334 #[inline]
336 pub const fn is_block(&self) -> bool {
337 with_stat!(self, |s| s.is_block())
338 }
339
340 #[inline]
342 pub const fn is_directory(&self) -> bool {
343 with_stat!(self, |s| s.is_directory())
344 }
345
346 #[inline]
348 pub const fn is_dir(&self) -> bool {
349 with_stat!(self, |s| s.is_dir())
350 }
351
352 #[inline]
354 pub const fn is_character(&self) -> bool {
355 with_stat!(self, |s| s.is_character())
356 }
357
358 #[inline]
360 pub const fn is_char(&self) -> bool {
361 with_stat!(self, |s| s.is_char())
362 }
363
364 #[inline]
366 pub const fn is_fifo(&self) -> bool {
367 with_stat!(self, |s| s.is_fifo())
368 }
369
370 #[inline]
372 pub const fn inode(&self) -> u64 {
373 with_stat!(self, |s| s.inode())
374 }
375
376 #[inline]
380 pub const fn size(&self) -> i64 {
381 with_stat!(self, |s| s.size())
382 }
383
384 #[inline]
388 pub const fn blocks(&self) -> i64 {
389 with_stat!(self, |s| s.blocks())
390 }
391
392 #[inline]
394 pub const fn atime(&self) -> Timestamp {
395 with_stat!(self, |s| s.atime())
396 }
397
398 #[inline]
400 pub const fn ctime(&self) -> Timestamp {
401 with_stat!(self, |s| s.ctime())
402 }
403
404 #[inline]
406 pub const fn mtime(&self) -> Timestamp {
407 with_stat!(self, |s| s.mtime())
408 }
409
410 #[inline]
413 pub const fn rdev_major(&self) -> u32 {
414 with_stat!(self, |s| s.rdev_major())
415 }
416
417 #[inline]
420 pub const fn rdev_minor(&self) -> u32 {
421 with_stat!(self, |s| s.rdev_minor())
422 }
423
424 #[inline]
427 pub const fn rdev(&self) -> Dev {
428 with_stat!(self, |s| s.rdev())
429 }
430
431 #[inline]
433 pub const fn dev_major(&self) -> u32 {
434 with_stat!(self, |s| s.dev_major())
435 }
436
437 #[inline]
439 pub const fn dev_minor(&self) -> u32 {
440 with_stat!(self, |s| s.dev_minor())
441 }
442
443 #[inline]
445 pub const fn dev(&self) -> Dev {
446 with_stat!(self, |s| s.dev())
447 }
448}
449
450#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
451impl fmt::Debug for Stat {
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 with_stat!(self, |s| s.debug(f, "Stat"))
454 }
455}
456
457#[inline]
459pub fn empty_path() -> &'static Path {
460 #[cfg(feature = "std")]
461 let empty: &Path = Path::new("");
462 #[cfg(not(feature = "std"))]
463 let empty: &Path = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
464 empty
465}
466
467#[cfg(feature = "std")]
468#[inline(always)]
469pub(crate) fn run_with_cstr<P, T, F>(path: P, f: F) -> Result<T, Errno>
470where
471 P: AsRef<Path>,
472 F: FnOnce(&CStr) -> Result<T, Errno>,
473{
474 use core::mem::MaybeUninit;
475 #[cfg(extern_cstr)]
476 use cstr_core::CString;
477 #[cfg(not(extern_cstr))]
478 use std::ffi::CString;
479 use std::os::unix::ffi::OsStrExt;
480
481 #[cfg(not(target_os = "espidf"))]
482 const MAX_STACK_ALLOCATION: usize = 384;
483 #[cfg(target_os = "espidf")]
484 const MAX_STACK_ALLOCATION: usize = 32;
485
486 let path = path.as_ref().as_os_str().as_bytes();
487
488 if path.is_empty() {
489 return f(unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") });
490 }
491
492 if path.last().map(|&c| c == 0).unwrap_or(false) {
493 return f(CStr::from_bytes_with_nul(path).map_err(|_| Errno::ENOENT)?);
494 }
495
496 if path.len() >= MAX_STACK_ALLOCATION {
497 return CString::new(path).map_or(Err(Errno::ENOENT), |path| f(&path));
498 }
499
500 let mut buf = MaybeUninit::<[u8; MAX_STACK_ALLOCATION]>::uninit();
501 let buf_ptr = buf.as_mut_ptr() as *mut u8;
502
503 unsafe {
504 core::ptr::copy_nonoverlapping(path.as_ptr(), buf_ptr, path.len());
505 buf_ptr.add(path.len()).write(0);
506 }
507
508 CStr::from_bytes_with_nul(unsafe { core::slice::from_raw_parts(buf_ptr, path.len() + 1) })
509 .map_or(Err(Errno::ENOENT), f)
510}
511
512#[cfg(not(feature = "std"))]
513#[inline(always)]
514pub(crate) fn run_with_cstr<P, T, F>(path: P, f: F) -> Result<T, Errno>
515where
516 P: AsRef<Path>,
517 F: FnOnce(&CStr) -> Result<T, Errno>,
518{
519 f(path.as_ref())
520}
521
522#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
530pub unsafe fn fstatat<P: AsRef<Path>>(
531 dirfd: RawFd,
532 path: P,
533 flags: StatAtFlags,
534) -> Result<Stat, Errno> {
535 let path = path.as_ref();
536
537 run_with_cstr(path, |path| fstatat_cstr(dirfd, path, flags))
538}
539
540#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
549pub unsafe fn fstatat_cstr(dirfd: RawFd, path: &CStr, flags: StatAtFlags) -> Result<Stat, Errno> {
550 use core::sync::atomic::Ordering;
551
552 match HAS_STATX.load(Ordering::Relaxed) {
553 0 => crate::raw::fstatat_cstr(dirfd, path, flags).map(Stat::Stat64),
554 1 => crate::raw::statx_cstr(dirfd, path, flags, crate::raw::StatXMask::BASIC_STATS)
555 .map(Stat::Statx),
556 _ => match crate::raw::statx_cstr(dirfd, path, flags, crate::raw::StatXMask::BASIC_STATS) {
557 Err(Errno::ENOSYS) => {
558 HAS_STATX.store(0, Ordering::Relaxed);
559 crate::raw::fstatat_cstr(dirfd, path, flags).map(Stat::Stat64)
560 }
561 other => {
562 HAS_STATX.store(1, Ordering::Relaxed);
563 other.map(Stat::Statx)
564 }
565 },
566 }
567}
568
569#[cfg(any(feature = "linux_4_11", target_arch = "loongarch64"))]
577#[inline]
578pub unsafe fn fstatat<P: AsRef<Path>>(
579 dirfd: RawFd,
580 path: P,
581 flags: StatAtFlags,
582) -> Result<Stat, Errno> {
583 run_with_cstr(path, |path| fstatat_cstr(dirfd, path, flags))
584}
585
586#[cfg(any(feature = "linux_4_11", target_arch = "loongarch64"))]
595#[inline]
596pub unsafe fn fstatat_cstr(dirfd: RawFd, path: &CStr, flags: StatAtFlags) -> Result<Stat, Errno> {
597 raw::statx_cstr(dirfd, path, flags, crate::raw::StatXMask::empty())
598}
599
600#[inline]
603pub fn stat<P: AsRef<Path>>(path: P) -> Result<Stat, Errno> {
604 run_with_cstr(path, stat_cstr)
605}
606
607#[inline]
610pub fn stat_cstr(path: &CStr) -> Result<Stat, Errno> {
611 unsafe { fstatat_cstr(CURRENT_DIRECTORY, path, StatAtFlags::empty()) }
612}
613
614#[inline]
617pub fn lstat<P: AsRef<Path>>(path: P) -> Result<Stat, Errno> {
618 run_with_cstr(path, lstat_cstr)
619}
620
621#[inline]
624pub fn lstat_cstr(path: &CStr) -> Result<Stat, Errno> {
625 unsafe { fstatat_cstr(CURRENT_DIRECTORY, path, StatAtFlags::SYMLINK_NOFOLLOW) }
626}
627
628#[inline]
635pub unsafe fn fstat(dirfd: RawFd) -> Result<Stat, Errno> {
636 if dirfd < 0 {
637 return Err(Errno::EBADF);
638 }
639
640 fstatat(dirfd, empty_path(), StatAtFlags::EMPTY_PATH)
641}
642
643#[cfg(test)]
644pub(crate) mod tests {
645 use super::*;
646
647 #[cfg(feature = "std")]
648 pub fn dev_null() -> &'static Path {
649 Path::new("/dev/null\0")
650 }
651
652 #[cfg(not(feature = "std"))]
653 pub fn dev_null() -> &'static CStr {
654 unsafe { CStr::from_bytes_with_nul_unchecked(b"/dev/null\0") }
655 }
656
657 pub fn retry<T, F: Fn() -> Result<T, Errno>>(f: F) -> Result<T, Errno> {
658 loop {
659 match f() {
660 Err(Errno::EINTR) => (),
661 other => return other,
662 }
663 }
664 }
665
666 #[cfg(target_os = "android")]
667 pub use libc::__errno as errno;
668 #[cfg(target_os = "linux")]
669 pub use libc::__errno_location as errno;
670
671 pub fn c_stat() -> Result<libc::stat64, Errno> {
672 unsafe {
673 let mut buf = core::mem::MaybeUninit::<libc::stat64>::uninit();
674 if libc::fstatat64(
675 libc::AT_FDCWD,
676 b"/dev/null\0".as_ptr() as *const _,
677 buf.as_mut_ptr(),
678 0,
679 ) == -1
680 {
681 return Err(Errno::new(*errno()));
682 }
683 Ok(buf.assume_init())
684 }
685 }
686
687 #[test]
688 #[allow(clippy::unnecessary_cast)]
689 fn stat_dev_null() {
690 linux_syscalls::init();
691
692 let c_stat = retry(c_stat);
693 assert!(c_stat.is_ok());
694 let c_stat = c_stat.unwrap();
695
696 let stat = retry(|| stat(dev_null()));
697 assert!(stat.is_ok());
698 let stat = stat.unwrap();
699
700 assert_eq!(stat.dev(), c_stat.st_dev);
701 assert_eq!(stat.inode(), c_stat.st_ino as u64);
702 assert_eq!(stat.nlink(), c_stat.st_nlink as u32);
703 assert_eq!(
704 stat.mode().as_u16() | stat.file_type().as_u16(),
705 c_stat.st_mode as u16
706 );
707 assert_eq!(stat.uid(), c_stat.st_uid as u32);
708 assert_eq!(stat.gid(), c_stat.st_gid as u32);
709 assert_eq!(stat.rdev(), c_stat.st_rdev);
710 assert_eq!(stat.size(), c_stat.st_size as i64);
711 assert_eq!(stat.block_size(), c_stat.st_blksize as i32);
712 assert_eq!(stat.blocks(), c_stat.st_blocks as i64);
713 assert_eq!(stat.atime().secs, c_stat.st_atime as i64);
714 assert_eq!(stat.atime().nsecs, c_stat.st_atime_nsec as u32);
715 assert_eq!(stat.mtime().secs, c_stat.st_mtime as i64);
716 assert_eq!(stat.mtime().nsecs, c_stat.st_mtime_nsec as u32);
717 assert_eq!(stat.ctime().secs, c_stat.st_ctime as i64);
718 assert_eq!(stat.ctime().nsecs, c_stat.st_ctime_nsec as u32);
719 }
720}