1use core::{fmt, mem::MaybeUninit};
4
5use linux_raw_sys::general::{
6 S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
7};
8
9use crate::{CStr, Dev, DevSplit, FileType, Mode, RawFd, StatAtFlags, Timestamp};
10
11#[cfg_attr(
12 all(not(feature = "linux_4_11"), target_arch = "aarch64"),
13 path = "aarch64.rs"
14)]
15#[cfg_attr(all(not(feature = "linux_4_11"), target_arch = "arm"), path = "arm.rs")]
16#[cfg_attr(
17 all(not(feature = "linux_4_11"), target_arch = "mips"),
18 path = "mips.rs"
19)]
20#[cfg_attr(
21 all(not(feature = "linux_4_11"), target_arch = "mips64"),
22 path = "mips64.rs"
23)]
24#[cfg_attr(
25 all(not(feature = "linux_4_11"), target_arch = "powerpc"),
26 path = "powerpc.rs"
27)]
28#[cfg_attr(
29 all(not(feature = "linux_4_11"), target_arch = "powerpc64"),
30 path = "powerpc64.rs"
31)]
32#[cfg_attr(
33 all(not(feature = "linux_4_11"), target_arch = "riscv64"),
34 path = "riscv64.rs"
35)]
36#[cfg_attr(
37 all(not(feature = "linux_4_11"), target_arch = "s390x"),
38 path = "s390x.rs"
39)]
40#[cfg_attr(all(not(feature = "linux_4_11"), target_arch = "x86"), path = "x86.rs")]
41#[cfg_attr(
42 all(not(feature = "linux_4_11"), target_arch = "x86_64"),
43 path = "x86_64.rs"
44)]
45mod stat_imp;
46
47#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
48pub use stat_imp::stat;
49
50use linux_syscalls::{bitflags, syscall, Errno, Sysno};
51
52bitflags! {
53 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
55 pub enum StatXMask: u32 {
56 TYPE = linux_raw_sys::general::STATX_TYPE,
58 MODE = linux_raw_sys::general::STATX_MODE,
60 NLINK = linux_raw_sys::general::STATX_NLINK,
62 UID = linux_raw_sys::general::STATX_UID,
64 GID = linux_raw_sys::general::STATX_GID,
66 ATIME = linux_raw_sys::general::STATX_ATIME,
68 MTIME = linux_raw_sys::general::STATX_MTIME,
70 CTIME = linux_raw_sys::general::STATX_CTIME,
72 INO = linux_raw_sys::general::STATX_INO,
74 SIZE = linux_raw_sys::general::STATX_SIZE,
76 BLOCKS = linux_raw_sys::general::STATX_BLOCKS,
78 BASIC_STATS = linux_raw_sys::general::STATX_BASIC_STATS,
80 BTIME = linux_raw_sys::general::STATX_BTIME,
82 ALL = linux_raw_sys::general::STATX_ALL,
84 MNT_ID = linux_raw_sys::general::STATX_MNT_ID,
86 DIOALIGN = linux_raw_sys::general::STATX_DIOALIGN,
89 }
90}
91
92bitflags! {
93 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98 pub enum StatXAttr: u64 {
99 COMPRESSED = linux_raw_sys::general::STATX_ATTR_COMPRESSED as u64,
102 IMMUTABLE = linux_raw_sys::general::STATX_ATTR_IMMUTABLE as u64,
106 APPEND = linux_raw_sys::general::STATX_ATTR_APPEND as u64,
109 NODUMP = linux_raw_sys::general::STATX_ATTR_NODUMP as u64,
112 ENCRYPTED = linux_raw_sys::general::STATX_ATTR_ENCRYPTED as u64,
115 AUTOMOUNT = linux_raw_sys::general::STATX_ATTR_AUTOMOUNT as u64,
116 MOUNT_ROOT = linux_raw_sys::general::STATX_ATTR_MOUNT_ROOT as u64,
117 VERITY = linux_raw_sys::general::STATX_ATTR_VERITY as u64,
121 DAX = linux_raw_sys::general::STATX_ATTR_DAX as u64,
144 }
145}
146
147#[repr(C)]
148#[derive(Copy, Clone)]
149#[allow(non_camel_case_types)]
150struct timestamp {
151 tv_sec: i64,
152 tv_nsec: u32,
153 __pad: u32,
154}
155
156impl fmt::Debug for timestamp {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.debug_struct("timestamp")
159 .field("tv_sec", &self.tv_sec)
160 .field("tv_nsec", &self.tv_nsec)
161 .finish()
162 }
163}
164
165#[repr(C)]
167#[derive(Copy, Clone)]
168pub struct Statx {
169 stx_mask: StatXMask,
170 stx_blksize: i32,
171 stx_attributes: u64,
172 stx_nlink: u32,
173 stx_uid: u32,
174 stx_gid: u32,
175 stx_mode: u16,
176 stx_ino: u64,
177 stx_size: i64,
178 stx_blocks: i64,
179 stx_attributes_mask: StatXAttr,
180 stx_atime: timestamp,
181 stx_btime: timestamp,
182 stx_ctime: timestamp,
183 stx_mtime: timestamp,
184 stx_rdev_major: u32,
185 stx_rdev_minor: u32,
186 stx_dev_major: u32,
187 stx_dev_minor: u32,
188 stx_mnt_id: u64,
189 stx_dio_mem_align: u32,
190 stx_dio_offset_align: u32,
191 spare: [u64; 14],
192}
193
194#[inline(always)]
195const fn file_type(mode: u16) -> FileType {
196 match mode as u32 & S_IFMT {
197 S_IFSOCK => FileType::Socket,
198 S_IFLNK => FileType::Link,
199 S_IFREG => FileType::Regular,
200 S_IFBLK => FileType::Block,
201 S_IFDIR => FileType::Directory,
202 S_IFCHR => FileType::Character,
203 S_IFIFO => FileType::Fifo,
204 _ => FileType::Unknown,
205 }
206}
207
208impl Statx {
209 #[inline]
213 pub const fn block_size(&self) -> i32 {
214 self.stx_blksize
215 }
216 #[inline]
219 pub const fn attributes(&self) -> u64 {
220 self.stx_attributes
221 }
222
223 #[inline]
225 pub const fn nlink(&self) -> u32 {
226 self.stx_nlink
227 }
228
229 #[inline]
231 pub const fn uid(&self) -> u32 {
232 self.stx_uid
233 }
234
235 #[inline]
237 pub const fn gid(&self) -> u32 {
238 self.stx_gid
239 }
240
241 #[inline]
243 pub const fn mode(&self) -> Mode {
244 Mode(self.stx_mode & !(S_IFMT as u16))
245 }
246
247 pub const fn file_type(&self) -> FileType {
249 file_type(self.stx_mode)
250 }
251
252 #[inline]
254 pub const fn is_socket(&self) -> bool {
255 self.stx_mode as u32 & S_IFMT == S_IFSOCK
256 }
257
258 #[inline]
260 pub const fn is_link(&self) -> bool {
261 self.stx_mode as u32 & S_IFMT == S_IFLNK
262 }
263
264 #[inline]
266 pub const fn is_regular(&self) -> bool {
267 self.stx_mode as u32 & S_IFMT == S_IFREG
268 }
269
270 #[inline]
272 pub const fn is_block(&self) -> bool {
273 self.stx_mode as u32 & S_IFMT == S_IFBLK
274 }
275
276 #[inline]
278 pub const fn is_directory(&self) -> bool {
279 self.stx_mode as u32 & S_IFMT == S_IFDIR
280 }
281
282 #[inline]
284 pub const fn is_dir(&self) -> bool {
285 self.is_directory()
286 }
287
288 #[inline]
290 pub const fn is_character(&self) -> bool {
291 self.stx_mode as u32 & S_IFMT == S_IFCHR
292 }
293
294 #[inline]
296 pub const fn is_char(&self) -> bool {
297 self.is_character()
298 }
299
300 #[inline]
302 pub const fn is_fifo(&self) -> bool {
303 self.stx_mode as u32 & S_IFMT == S_IFIFO
304 }
305
306 #[inline]
308 pub const fn inode(&self) -> u64 {
309 self.stx_ino
310 }
311
312 #[inline]
316 pub const fn size(&self) -> i64 {
317 self.stx_size
318 }
319
320 #[inline]
324 pub const fn blocks(&self) -> i64 {
325 self.stx_blocks
326 }
327
328 #[inline]
331 pub const fn attributes_mask(&self) -> StatXAttr {
332 self.stx_attributes_mask
333 }
334
335 #[inline]
337 pub const fn atime(&self) -> Timestamp {
338 Timestamp {
339 secs: self.stx_atime.tv_sec,
340 nsecs: self.stx_atime.tv_nsec,
341 }
342 }
343
344 #[inline]
346 pub const fn btime(&self) -> Timestamp {
347 Timestamp {
348 secs: self.stx_btime.tv_sec,
349 nsecs: self.stx_btime.tv_nsec,
350 }
351 }
352
353 #[inline]
355 pub const fn ctime(&self) -> Timestamp {
356 Timestamp {
357 secs: self.stx_ctime.tv_sec,
358 nsecs: self.stx_ctime.tv_nsec,
359 }
360 }
361
362 #[inline]
364 pub const fn mtime(&self) -> Timestamp {
365 Timestamp {
366 secs: self.stx_mtime.tv_sec,
367 nsecs: self.stx_mtime.tv_nsec,
368 }
369 }
370
371 #[inline]
374 pub const fn rdev_major(&self) -> u32 {
375 self.stx_rdev_major
376 }
377
378 #[inline]
381 pub const fn rdev_minor(&self) -> u32 {
382 self.stx_rdev_minor
383 }
384
385 #[inline]
388 pub const fn rdev(&self) -> Dev {
389 Dev::Split(DevSplit::new(self.rdev_major(), self.rdev_minor()))
390 }
391
392 #[inline]
394 pub const fn dev_major(&self) -> u32 {
395 self.stx_dev_major
396 }
397
398 #[inline]
400 pub const fn dev_minor(&self) -> u32 {
401 self.stx_dev_minor
402 }
403
404 #[inline]
406 pub const fn dev(&self) -> Dev {
407 Dev::Split(DevSplit::new(self.dev_major(), self.dev_minor()))
408 }
409
410 #[inline]
415 pub const fn mount_id(&self) -> u64 {
416 self.stx_mnt_id
417 }
418
419 #[inline]
428 pub const fn dio_mem_align(&self) -> u32 {
429 self.stx_dio_mem_align
430 }
431
432 #[inline]
437 pub const fn dio_offset_align(&self) -> u32 {
438 self.stx_dio_offset_align
439 }
440
441 pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
442 f.debug_struct(name)
443 .field("dev", &self.dev())
444 .field("ino", &self.inode())
445 .field("nlink", &self.nlink())
446 .field("mode", &self.mode())
447 .field("uid", &self.uid())
448 .field("gid", &self.gid())
449 .field("rdev", &self.rdev())
450 .field("size", &self.size())
451 .field("block_size", &self.block_size())
452 .field("blocks", &self.blocks())
453 .field("atime", &self.atime())
454 .field("btime", &self.btime())
455 .field("mtime", &self.mtime())
456 .field("ctime", &self.ctime())
457 .field("attributes", &self.attributes())
458 .field("attributes_mask", &self.attributes_mask())
459 .field("mount_id", &self.mount_id())
460 .field("dio_mem_align", &self.dio_mem_align())
461 .field("dio_offset_align", &self.dio_offset_align())
462 .finish()
463 }
464}
465
466impl fmt::Debug for Statx {
467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468 self.debug(f, "Statx")
469 }
470}
471
472impl Statx {
473 #[doc(hidden)]
474 pub fn uninit() -> MaybeUninit<Self> {
475 let mut buf = MaybeUninit::<Self>::uninit();
476 unsafe {
477 let buf = &mut *buf.as_mut_ptr();
478 core::ptr::write_bytes(
479 &mut buf.spare[0] as *mut u64 as *mut u8,
480 0,
481 core::mem::size_of_val(&buf.spare),
482 );
483 }
484 buf
485 }
486}
487
488#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
489impl stat {
490 #[inline]
492 pub const fn mode(&self) -> Mode {
493 Mode(self.raw_mode() & !(S_IFMT as u16))
494 }
495
496 pub const fn file_type(&self) -> FileType {
498 file_type(self.raw_mode())
499 }
500
501 #[inline]
503 pub const fn is_socket(&self) -> bool {
504 self.raw_mode() as u32 & S_IFMT == S_IFSOCK
505 }
506
507 #[inline]
509 pub const fn is_link(&self) -> bool {
510 self.raw_mode() as u32 & S_IFMT == S_IFLNK
511 }
512
513 #[inline]
515 pub const fn is_regular(&self) -> bool {
516 self.raw_mode() as u32 & S_IFMT == S_IFREG
517 }
518
519 #[inline]
521 pub const fn is_block(&self) -> bool {
522 self.raw_mode() as u32 & S_IFMT == S_IFBLK
523 }
524
525 #[inline]
527 pub const fn is_directory(&self) -> bool {
528 self.raw_mode() as u32 & S_IFMT == S_IFDIR
529 }
530
531 #[inline]
533 pub const fn is_dir(&self) -> bool {
534 self.is_directory()
535 }
536
537 #[inline]
539 pub const fn is_character(&self) -> bool {
540 self.raw_mode() as u32 & S_IFMT == S_IFCHR
541 }
542
543 #[inline]
545 pub const fn is_char(&self) -> bool {
546 self.is_character()
547 }
548
549 #[inline]
551 pub const fn is_fifo(&self) -> bool {
552 self.raw_mode() as u32 & S_IFMT == S_IFIFO
553 }
554
555 #[inline]
557 pub const fn dev_minor(&self) -> u32 {
558 self.dev().minor()
559 }
560
561 #[inline]
563 pub const fn dev_major(&self) -> u32 {
564 self.dev().major()
565 }
566
567 #[inline]
570 pub const fn rdev_minor(&self) -> u32 {
571 self.rdev().minor()
572 }
573
574 #[inline]
577 pub const fn rdev_major(&self) -> u32 {
578 self.rdev().major()
579 }
580
581 pub(crate) fn debug(&self, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
582 f.debug_struct(name)
583 .field("dev", &self.dev())
584 .field("ino", &self.inode())
585 .field("nlink", &self.nlink())
586 .field("mode", &self.mode())
587 .field("uid", &self.uid())
588 .field("gid", &self.gid())
589 .field("rdev", &self.rdev())
590 .field("size", &self.size())
591 .field("block_size", &self.block_size())
592 .field("blocks", &self.blocks())
593 .field("atime", &self.atime())
594 .field("mtime", &self.mtime())
595 .field("ctime", &self.ctime())
596 .finish()
597 }
598}
599
600#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
601impl fmt::Debug for stat {
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603 self.debug(f, "stat")
604 }
605}
606
607#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
614#[inline]
615pub unsafe fn fstatat<P: AsRef<crate::Path>>(
616 dirfd: RawFd,
617 path: P,
618 flags: StatAtFlags,
619) -> Result<stat, Errno> {
620 crate::run_with_cstr(path, |path| fstatat_cstr(dirfd, path, flags))
621}
622
623#[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
630#[inline]
631pub unsafe fn fstatat_cstr(dirfd: RawFd, path: &CStr, flags: StatAtFlags) -> Result<stat, Errno> {
632 let mut buf = stat::uninit();
633 syscall!(
634 stat_imp::SYS_FSTATAT,
635 dirfd,
636 path.as_ptr(),
637 buf.as_mut_ptr(),
638 flags.bits()
639 )?;
640 Ok(buf.assume_init())
641}
642
643#[inline]
650pub unsafe fn statx<P: AsRef<crate::Path>>(
651 dirfd: RawFd,
652 path: P,
653 flags: StatAtFlags,
654 mask: StatXMask,
655) -> Result<Statx, Errno> {
656 crate::run_with_cstr(path, |path| statx_cstr(dirfd, path, flags, mask))
657}
658
659#[inline]
666pub unsafe fn statx_cstr(
667 dirfd: RawFd,
668 path: &CStr,
669 flags: StatAtFlags,
670 mask: StatXMask,
671) -> Result<Statx, Errno> {
672 let mut buf = Statx::uninit();
673 syscall!(
674 Sysno::statx,
675 dirfd,
676 path.as_ptr(),
677 flags.bits(),
678 mask.bits(),
679 buf.as_mut_ptr(),
680 )?;
681 Ok(buf.assume_init())
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 #[cfg(all(not(feature = "linux_4_11"), not(target_arch = "loongarch64")))]
689 #[test]
690 #[allow(clippy::unnecessary_cast)]
691 fn stat64_dev_null() {
692 linux_syscalls::init();
693
694 let c_stat = crate::tests::retry(crate::tests::c_stat);
695 assert!(c_stat.is_ok());
696 let c_stat = c_stat.unwrap();
697
698 let stat = crate::tests::retry(|| unsafe {
699 fstatat(
700 crate::CURRENT_DIRECTORY,
701 crate::tests::dev_null(),
702 StatAtFlags::empty(),
703 )
704 });
705 assert!(stat.is_ok());
706 let stat = stat.unwrap();
707
708 assert_eq!(stat.dev(), c_stat.st_dev);
709 assert_eq!(stat.inode(), c_stat.st_ino as u64);
710 assert_eq!(stat.nlink(), c_stat.st_nlink as u32);
711 assert_eq!(
712 stat.mode().as_u16() | stat.file_type().as_u16(),
713 c_stat.st_mode as u16
714 );
715 assert_eq!(stat.uid(), c_stat.st_uid as u32);
716 assert_eq!(stat.gid(), c_stat.st_gid as u32);
717 assert_eq!(stat.rdev(), c_stat.st_rdev);
718 assert_eq!(stat.size(), c_stat.st_size as i64);
719 assert_eq!(stat.block_size(), c_stat.st_blksize as i32);
720 assert_eq!(stat.blocks(), c_stat.st_blocks as i64);
721 assert_eq!(stat.atime().secs, c_stat.st_atime as i64);
722 assert_eq!(stat.atime().nsecs, c_stat.st_atime_nsec as u32);
723 assert_eq!(stat.mtime().secs, c_stat.st_mtime as i64);
724 assert_eq!(stat.mtime().nsecs, c_stat.st_mtime_nsec as u32);
725 assert_eq!(stat.ctime().secs, c_stat.st_ctime as i64);
726 assert_eq!(stat.ctime().nsecs, c_stat.st_ctime_nsec as u32);
727 }
728
729 #[test]
730 #[allow(clippy::unnecessary_cast)]
731 #[cfg_attr(target_arch = "s390x", ignore)]
732 fn statx_dev_null() {
733 linux_syscalls::init();
734
735 let c_stat = crate::tests::retry(crate::tests::c_stat);
736 assert!(c_stat.is_ok());
737 let c_stat = c_stat.unwrap();
738
739 let statx = crate::tests::retry(|| unsafe {
740 statx(
741 crate::CURRENT_DIRECTORY,
742 crate::tests::dev_null(),
743 StatAtFlags::empty(),
744 StatXMask::empty(),
745 )
746 });
747 assert!(statx.is_ok());
748 let statx = statx.unwrap();
749
750 assert_eq!(statx.dev(), c_stat.st_dev);
751 assert_eq!(statx.inode(), c_stat.st_ino as u64);
752 assert_eq!(statx.nlink(), c_stat.st_nlink as u32);
753 assert_eq!(
754 statx.mode().as_u16() | statx.file_type().as_u16(),
755 c_stat.st_mode as u16
756 );
757 assert_eq!(statx.uid(), c_stat.st_uid as u32);
758 assert_eq!(statx.gid(), c_stat.st_gid as u32);
759 assert_eq!(statx.rdev(), c_stat.st_rdev);
760 assert_eq!(statx.size(), c_stat.st_size as i64);
761 assert_eq!(statx.block_size(), c_stat.st_blksize as i32);
762 assert_eq!(statx.blocks(), c_stat.st_blocks as i64);
763 assert_eq!(statx.atime().secs, c_stat.st_atime as i64);
764 assert_eq!(statx.atime().nsecs, c_stat.st_atime_nsec as u32);
765 assert_eq!(statx.mtime().secs, c_stat.st_mtime as i64);
766 assert_eq!(statx.mtime().nsecs, c_stat.st_mtime_nsec as u32);
767 assert_eq!(statx.ctime().secs, c_stat.st_ctime as i64);
768 assert_eq!(statx.ctime().nsecs, c_stat.st_ctime_nsec as u32);
769 }
770}