fuse_backend_rs/passthrough/
statx.rs

1// Copyright 2021 Red Hat, Inc. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
21/*
22 * Fields in libc::statx are only valid if their respective flag in
23 * .stx_mask is set.  This trait provides functions that allow safe
24 * access to the libc::statx components we are interested in.
25 *
26 * (The implementations of these functions need to check whether the
27 * associated flag is set, and then extract the respective information
28 * to return it.)
29 */
30trait 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            /*
43             * Unfortunately, we cannot use an initializer to create the
44             * stat64 object, because it may contain padding and reserved
45             * fields (depending on the architecture), and it does not
46             * implement the Default trait.
47             * So we take a zeroed struct and set what we can.
48             * (Zero in all fields is wrong, but safe.)
49             */
50            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
91// Only works on Linux, and libc::SYS_statx is only defined for these
92// environments
93/// Performs a statx() syscall.  libc provides libc::statx() that does
94/// the same, however, the system's libc may not have a statx() wrapper
95/// (e.g. glibc before 2.28), so linking to it may fail.
96/// libc::syscall() and libc::SYS_statx are always present, though, so
97/// we can safely rely on them.
98unsafe 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
108/// Execute `statx()` to get extended status with mount id.
109pub fn statx(dir: &impl AsRawFd, path: Option<&CStr>) -> io::Result<StatExt> {
110    let mut stx_ui = MaybeUninit::<statx_st>::zeroed();
111
112    // Safe because this is a constant value and a valid C string.
113    let path = path.unwrap_or_else(|| unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) });
114
115    // Safe because the kernel will only write data in `stx_ui` and we
116    // check the return value.
117    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        // Safe because we are only going to use the SafeStatXAccess
128        // trait methods
129        let stx = unsafe { stx_ui.assume_init() };
130
131        // if `statx()` doesn't provide the mount id (before kernel 5.8),
132        // let's try `name_to_handle_at()`, if everything fails just use 0
133        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}