fuse_backend_rs/passthrough/
statx.rs1use std::ffi::CStr;
6use std::io;
7use std::mem::MaybeUninit;
8use std::os::unix::io::AsRawFd;
9
10use super::os_compat::{statx_st, STATX_BASIC_STATS, STATX_MNT_ID};
11use super::FileHandle;
12use crate::api::EMPTY_CSTR;
13
14pub type MountId = u64;
15
16pub struct StatExt {
17 pub st: libc::stat64,
18 pub mnt_id: MountId,
19}
20
21trait SafeStatXAccess {
31 fn stat64(&self) -> Option<libc::stat64>;
32 fn mount_id(&self) -> Option<MountId>;
33}
34
35impl SafeStatXAccess for statx_st {
36 fn stat64(&self) -> Option<libc::stat64> {
37 fn makedev(maj: libc::c_uint, min: libc::c_uint) -> libc::dev_t {
38 libc::makedev(maj, min)
39 }
40
41 if self.stx_mask & STATX_BASIC_STATS != 0 {
42 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
51
52 st.st_dev = makedev(self.stx_dev_major, self.stx_dev_minor);
53 st.st_ino = self.stx_ino;
54 st.st_mode = self.stx_mode as _;
55 st.st_nlink = self.stx_nlink as _;
56 st.st_uid = self.stx_uid;
57 st.st_gid = self.stx_gid;
58 st.st_rdev = makedev(self.stx_rdev_major, self.stx_rdev_minor);
59 st.st_size = self.stx_size as _;
60 st.st_blksize = self.stx_blksize as _;
61 st.st_blocks = self.stx_blocks as _;
62 st.st_atime = self.stx_atime.tv_sec;
63 st.st_atime_nsec = self.stx_atime.tv_nsec as _;
64 st.st_mtime = self.stx_mtime.tv_sec;
65 st.st_mtime_nsec = self.stx_mtime.tv_nsec as _;
66 st.st_ctime = self.stx_ctime.tv_sec;
67 st.st_ctime_nsec = self.stx_ctime.tv_nsec as _;
68
69 Some(st)
70 } else {
71 None
72 }
73 }
74
75 fn mount_id(&self) -> Option<MountId> {
76 if self.stx_mask & STATX_MNT_ID != 0 {
77 Some(self.stx_mnt_id)
78 } else {
79 None
80 }
81 }
82}
83
84fn get_mount_id(dir: &impl AsRawFd, path: &CStr) -> Option<MountId> {
85 match FileHandle::from_name_at(dir, path) {
86 Ok(Some(v)) => Some(v.mnt_id),
87 _ => None,
88 }
89}
90
91unsafe fn do_statx(
99 dirfd: libc::c_int,
100 pathname: *const libc::c_char,
101 flags: libc::c_int,
102 mask: libc::c_uint,
103 statxbuf: *mut statx_st,
104) -> libc::c_int {
105 libc::syscall(libc::SYS_statx, dirfd, pathname, flags, mask, statxbuf) as libc::c_int
106}
107
108pub fn statx(dir: &impl AsRawFd, path: Option<&CStr>) -> io::Result<StatExt> {
110 let mut stx_ui = MaybeUninit::<statx_st>::zeroed();
111
112 let path = path.unwrap_or_else(|| unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) });
114
115 let res = unsafe {
118 do_statx(
119 dir.as_raw_fd(),
120 path.as_ptr(),
121 libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
122 STATX_BASIC_STATS | STATX_MNT_ID,
123 stx_ui.as_mut_ptr(),
124 )
125 };
126 if res >= 0 {
127 let stx = unsafe { stx_ui.assume_init() };
130
131 let mnt_id = stx
134 .mount_id()
135 .or_else(|| get_mount_id(dir, path))
136 .unwrap_or(0);
137 let st = stx
138 .stat64()
139 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOSYS))?;
140
141 Ok(StatExt { st, mnt_id })
142 } else {
143 Err(io::Error::last_os_error())
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use std::ffi::CString;
151 use std::fs::File;
152
153 #[test]
154 fn test_statx() {
155 let topdir = env!("CARGO_MANIFEST_DIR");
156 let dir = File::open(topdir).unwrap();
157 let filename = CString::new("build.rs").unwrap();
158
159 let st1 = statx(&dir, None).unwrap();
160 let st2 = statx(&dir, Some(&filename)).unwrap();
161 let mnt_id = get_mount_id(&dir, &filename).unwrap();
162
163 assert_eq!(st1.mnt_id, st2.mnt_id);
164 assert_eq!(st1.mnt_id, mnt_id);
165 }
166}