linux_stat/raw/
mod.rs

1//! Raw structures and functions implementations.
2
3use 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    /// A mask to tell the kernel which fields the caller is interested in.
54    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
55    pub enum StatXMask: u32 {
56        /// Want stx_mode & S_IFMT
57        TYPE = linux_raw_sys::general::STATX_TYPE,
58        /// Want stx_mode & ~S_IFMT
59        MODE = linux_raw_sys::general::STATX_MODE,
60        /// Want stx_nlink
61        NLINK = linux_raw_sys::general::STATX_NLINK,
62        /// Want stx_uid
63        UID = linux_raw_sys::general::STATX_UID,
64        /// Want stx_gid
65        GID = linux_raw_sys::general::STATX_GID,
66        /// Want stx_atime
67        ATIME = linux_raw_sys::general::STATX_ATIME,
68        /// Want stx_mtime
69        MTIME = linux_raw_sys::general::STATX_MTIME,
70        /// Want stx_ctime
71        CTIME = linux_raw_sys::general::STATX_CTIME,
72        /// Want stx_ino
73        INO = linux_raw_sys::general::STATX_INO,
74        /// Want stx_size
75        SIZE = linux_raw_sys::general::STATX_SIZE,
76        /// Want stx_blocks
77        BLOCKS = linux_raw_sys::general::STATX_BLOCKS,
78        /// [All of the above]
79        BASIC_STATS = linux_raw_sys::general::STATX_BASIC_STATS,
80        /// Want stx_btime
81        BTIME = linux_raw_sys::general::STATX_BTIME,
82        /// The same as STATX_BASIC_STATS | STATX_BTIME.
83        ALL = linux_raw_sys::general::STATX_ALL,
84        /// Want stx_mnt_id (since Linux 5.8)
85        MNT_ID = linux_raw_sys::general::STATX_MNT_ID,
86        /// Want stx_dio_mem_align and stx_dio_offset_align
87        /// (since Linux 6.1; support varies by filesystem)
88        DIOALIGN = linux_raw_sys::general::STATX_DIOALIGN,
89    }
90}
91
92bitflags! {
93    /// A set of ORed flags that indicate additional attributes of the file.
94    /// Note that any attribute that is not indicated as supported by
95    /// stx_attributes_mask has no usable value here.
96    /// The bits in stx_attributes_mask correspond bit-by-bit to stx_attributes.
97    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98    pub enum StatXAttr: u64 {
99        /// The file is compressed by the filesystem and may take extra
100        /// resources to access
101        COMPRESSED = linux_raw_sys::general::STATX_ATTR_COMPRESSED as u64,
102        /// The file cannot be modified: it cannot be deleted or renamed, no
103        /// hard links can be created to this file and no data can be
104        /// written to it.  See chattr(1).
105        IMMUTABLE = linux_raw_sys::general::STATX_ATTR_IMMUTABLE as u64,
106        /// The file can only be opened in append mode for writing.  Random
107        /// access writing is not permitted.  See chattr(1).
108        APPEND = linux_raw_sys::general::STATX_ATTR_APPEND as u64,
109        /// File is not a candidate for backup when a backup program such as
110        /// dump(8) is run.  See chattr(1).
111        NODUMP = linux_raw_sys::general::STATX_ATTR_NODUMP as u64,
112        /// A key is required for the file to be encrypted by the
113        /// filesystem.
114        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        /// The file has fs-verity enabled.  It cannot be written to, and
118        /// all reads from it will be verified against a cryptographic hash
119        /// that covers the entire file (e.g., via a Merkle tree).
120        VERITY = linux_raw_sys::general::STATX_ATTR_VERITY as u64,
121        /// The file is in the DAX (cpu direct access) state.  DAX state
122        /// attempts to minimize software cache effects for both I/O and
123        /// memory mappings of this file.  It requires a file system which
124        /// has been configured to support DAX.
125        ///
126        /// DAX generally assumes all accesses are via CPU load / store
127        /// instructions which can minimize overhead for small accesses, but
128        /// may adversely affect CPU utilization for large transfers.
129        ///
130        /// File I/O is done directly to/from user-space buffers and memory
131        /// mapped I/O may be performed with direct memory mappings that
132        /// bypass the kernel page cache.
133        ///
134        /// While the DAX property tends to result in data being transferred
135        /// synchronously, it does not give the same guarantees as the
136        /// O_SYNC flag (see open(2)), where data and the necessary metadata
137        /// are transferred together.
138        ///
139        /// A DAX file may support being mapped with the MAP_SYNC flag,
140        /// which enables a program to use CPU cache flush instructions to
141        /// persist CPU store operations without an explicit fsync(2).  See
142        /// mmap(2) for more information
143        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/// `statx()` file informations representation.
166#[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    /// Returns the "preferred" block size for efficient filesystem I/O.
210    /// (Writing to a file in smaller chunks may cause an inefficient
211    /// read-modify-rewrite.)
212    #[inline]
213    pub const fn block_size(&self) -> i32 {
214        self.stx_blksize
215    }
216    /// Returns further status information about the file (see below for more
217    /// information).
218    #[inline]
219    pub const fn attributes(&self) -> u64 {
220        self.stx_attributes
221    }
222
223    /// Returns the number of hard links on a file.
224    #[inline]
225    pub const fn nlink(&self) -> u32 {
226        self.stx_nlink
227    }
228
229    /// Returns the user ID of the owner of the file.
230    #[inline]
231    pub const fn uid(&self) -> u32 {
232        self.stx_uid
233    }
234
235    /// Returns the ID of the group owner of the file.
236    #[inline]
237    pub const fn gid(&self) -> u32 {
238        self.stx_gid
239    }
240
241    /// Returns the file mode.
242    #[inline]
243    pub const fn mode(&self) -> Mode {
244        Mode(self.stx_mode & !(S_IFMT as u16))
245    }
246
247    /// Returns the file type.
248    pub const fn file_type(&self) -> FileType {
249        file_type(self.stx_mode)
250    }
251
252    /// Returns true if file type is socket.
253    #[inline]
254    pub const fn is_socket(&self) -> bool {
255        self.stx_mode as u32 & S_IFMT == S_IFSOCK
256    }
257
258    /// Returns true if file type is link.
259    #[inline]
260    pub const fn is_link(&self) -> bool {
261        self.stx_mode as u32 & S_IFMT == S_IFLNK
262    }
263
264    /// Returns true if file type is regular.
265    #[inline]
266    pub const fn is_regular(&self) -> bool {
267        self.stx_mode as u32 & S_IFMT == S_IFREG
268    }
269
270    /// Returns true if file type is block.
271    #[inline]
272    pub const fn is_block(&self) -> bool {
273        self.stx_mode as u32 & S_IFMT == S_IFBLK
274    }
275
276    /// Returns true if file type is directory.
277    #[inline]
278    pub const fn is_directory(&self) -> bool {
279        self.stx_mode as u32 & S_IFMT == S_IFDIR
280    }
281
282    /// Alias for `Self::is_directory()`.
283    #[inline]
284    pub const fn is_dir(&self) -> bool {
285        self.is_directory()
286    }
287
288    /// Returns true if file type is character.
289    #[inline]
290    pub const fn is_character(&self) -> bool {
291        self.stx_mode as u32 & S_IFMT == S_IFCHR
292    }
293
294    /// Alias for `Self::is_character()`.
295    #[inline]
296    pub const fn is_char(&self) -> bool {
297        self.is_character()
298    }
299
300    /// Returns true if file type is FIFO.
301    #[inline]
302    pub const fn is_fifo(&self) -> bool {
303        self.stx_mode as u32 & S_IFMT == S_IFIFO
304    }
305
306    /// Returns the inode number of the file.
307    #[inline]
308    pub const fn inode(&self) -> u64 {
309        self.stx_ino
310    }
311
312    /// Returns the size of the file (if it is a regular file or a symbolic
313    /// link) in bytes. The size of a symbolic link is the length of
314    /// the pathname it contains, without a terminating null byte.
315    #[inline]
316    pub const fn size(&self) -> i64 {
317        self.stx_size
318    }
319
320    /// Returns the number of blocks allocated to the file on the medium, in
321    /// 512-byte units. (This may be smaller than stx_size/512 when the
322    /// file has holes.)
323    #[inline]
324    pub const fn blocks(&self) -> i64 {
325        self.stx_blocks
326    }
327
328    /// A mask indicating which bits in stx_attributes are supported by
329    /// the VFS and the filesystem.
330    #[inline]
331    pub const fn attributes_mask(&self) -> StatXAttr {
332        self.stx_attributes_mask
333    }
334
335    /// Returns the file's last access timestamp.
336    #[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    /// Returns the file's creation timestamp
345    #[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    /// Returns the file's last status change timestamp.
354    #[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    /// Returns the file's last modification timestamp.
363    #[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    /// Returns the major device that this file (inode) represents if the file
372    /// is of block or character device type
373    #[inline]
374    pub const fn rdev_major(&self) -> u32 {
375        self.stx_rdev_major
376    }
377
378    /// Returns the minor device that this file (inode) represents if the file
379    /// is of block or character device type
380    #[inline]
381    pub const fn rdev_minor(&self) -> u32 {
382        self.stx_rdev_minor
383    }
384
385    /// Returns the device that this file (inode) represents if the file is of
386    /// block or character device type
387    #[inline]
388    pub const fn rdev(&self) -> Dev {
389        Dev::Split(DevSplit::new(self.rdev_major(), self.rdev_minor()))
390    }
391
392    /// Returns the major device on which this file (inode) resides.
393    #[inline]
394    pub const fn dev_major(&self) -> u32 {
395        self.stx_dev_major
396    }
397
398    /// Returns the minor device on which this file (inode) resides.
399    #[inline]
400    pub const fn dev_minor(&self) -> u32 {
401        self.stx_dev_minor
402    }
403
404    /// Returns the device on which this file (inode) resides.
405    #[inline]
406    pub const fn dev(&self) -> Dev {
407        Dev::Split(DevSplit::new(self.dev_major(), self.dev_minor()))
408    }
409
410    /// The mount ID of the mount containing the file.  This is the same
411    /// number reported by name_to_handle_at(2) and corresponds to the
412    /// number in the first field in one of the records in
413    /// /proc/self/mountinfo.
414    #[inline]
415    pub const fn mount_id(&self) -> u64 {
416        self.stx_mnt_id
417    }
418
419    /// Returns the alignment (in bytes) required for user memory buffers for
420    /// direct I/O (O_DIRECT) on this file, or 0 if direct I/O is not
421    /// supported on this file.
422    ///
423    /// STATX_DIOALIGN (stx_dio_mem_align and stx_dio_offset_align) is
424    /// supported on block devices since Linux 6.1.  The support on
425    /// regular files varies by filesystem; it is supported by ext4,
426    /// f2fs, and xfs since Linux 6.1.
427    #[inline]
428    pub const fn dio_mem_align(&self) -> u32 {
429        self.stx_dio_mem_align
430    }
431
432    /// Returns the alignment (in bytes) required for file offsets and I/O
433    /// segment lengths for direct I/O (O_DIRECT) on this file, or 0 if
434    /// direct I/O is not supported on this file.  This will only be
435    /// nonzero if stx_dio_mem_align is nonzero, and vice versa.
436    #[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    /// Returns the file mode.
491    #[inline]
492    pub const fn mode(&self) -> Mode {
493        Mode(self.raw_mode() & !(S_IFMT as u16))
494    }
495
496    /// Returns the file type.
497    pub const fn file_type(&self) -> FileType {
498        file_type(self.raw_mode())
499    }
500
501    /// Returns true if file type is socket.
502    #[inline]
503    pub const fn is_socket(&self) -> bool {
504        self.raw_mode() as u32 & S_IFMT == S_IFSOCK
505    }
506
507    /// Returns true if file type is link.
508    #[inline]
509    pub const fn is_link(&self) -> bool {
510        self.raw_mode() as u32 & S_IFMT == S_IFLNK
511    }
512
513    /// Returns true if file type is regular.
514    #[inline]
515    pub const fn is_regular(&self) -> bool {
516        self.raw_mode() as u32 & S_IFMT == S_IFREG
517    }
518
519    /// Returns true if file type is block.
520    #[inline]
521    pub const fn is_block(&self) -> bool {
522        self.raw_mode() as u32 & S_IFMT == S_IFBLK
523    }
524
525    /// Returns true if file type is directory.
526    #[inline]
527    pub const fn is_directory(&self) -> bool {
528        self.raw_mode() as u32 & S_IFMT == S_IFDIR
529    }
530
531    /// Alias for `Self::is_directory()`.
532    #[inline]
533    pub const fn is_dir(&self) -> bool {
534        self.is_directory()
535    }
536
537    /// Returns true if file type is character.
538    #[inline]
539    pub const fn is_character(&self) -> bool {
540        self.raw_mode() as u32 & S_IFMT == S_IFCHR
541    }
542
543    /// Alias for `Self::is_character()`.
544    #[inline]
545    pub const fn is_char(&self) -> bool {
546        self.is_character()
547    }
548
549    /// Returns true if file type is FIFO.
550    #[inline]
551    pub const fn is_fifo(&self) -> bool {
552        self.raw_mode() as u32 & S_IFMT == S_IFIFO
553    }
554
555    /// Returns the minor device on which this file (inode) resides.
556    #[inline]
557    pub const fn dev_minor(&self) -> u32 {
558        self.dev().minor()
559    }
560
561    /// Returns the major device on which this file (inode) resides.
562    #[inline]
563    pub const fn dev_major(&self) -> u32 {
564        self.dev().major()
565    }
566
567    /// Returns the minor device that this file (inode) represents if the file
568    /// is of block or character device type
569    #[inline]
570    pub const fn rdev_minor(&self) -> u32 {
571        self.rdev().minor()
572    }
573
574    /// Returns the major device that this file (inode) represents if the file
575    /// is of block or character device type
576    #[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/// Invoke `fstatat` system call.
608///
609/// # Safety
610///
611/// This functions is inherently unsafe because it just wrap the system call
612/// and directory file descriptor (`dirfd`) cannot be checked.
613#[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/// Invoke `fstatat` system call with `path` as a [crate::CStr].
624///
625/// # Safety
626///
627/// This functions is inherently unsafe because it just wrap the system call
628/// and directory file descriptor (`dirfd`) cannot be checked.
629#[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/// Invoke `statx` system call.
644///
645/// # Safety
646///
647/// This functions is inherently unsafe because it just wrap the system call
648/// and directory file descriptor (`dirfd`) cannot be checked.
649#[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/// Invoke `statx` system call with `path` as a [crate::CStr].
660///
661/// # Safety
662///
663/// This functions is inherently unsafe because it just wrap the system call
664/// and directory file descriptor (`dirfd`) cannot be checked.
665#[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}