From 194fa59c89df0e9806d4cfece3ff52faafc831d2 Mon Sep 17 00:00:00 2001 From: Jeff Kim Date: Sat, 3 Oct 2026 20:34:24 +0900 Subject: [PATCH 1/2] Fix `StatVfsMountFlags::RELATIME` on the linux_raw backend The linux_raw backend defined the `StatVfsMountFlags` constants with the `MS_*` mount flag values, because linux-raw-sys has no `ST_*` names. `statvfs` fills `f_flag` with the kernel's `statfs` `f_flags` unconverted, and those are `ST_*` values. Most of the two sets agree, but `ST_RELATIME` is 0x1000 while `MS_RELATIME` is 1 << 21, so `f_flag.contains(StatVfsMountFlags::RELATIME)` was false on every `relatime` mount, and the bit the kernel did set was left unnamed. `f_flag` therefore could not tell a `relatime` mount from a strictatime one. A caller that derives the atime mode from these flags (neither `NOATIME` nor `RELATIME` means strictatime) and restates it in a bind remount changed every `relatime` mount it touched to strictatime, or got `EPERM` on a mount whose atime flags are locked, such as one inherited by a less privileged mount namespace. The `ST_*` values are not in the kernel's uapi headers, which linux-raw-sys is generated from, so spell them out, as the libc backend gets them from libc. Add a test that checks them against libc's on glibc, and one that compares what `statvfs` reports for `/` and `/proc` with the options in their `/proc/self/mountinfo` lines; both fail without the fix. --- src/backend/linux_raw/fs/types.rs | 23 +++++++----- tests/fs/statfs.rs | 60 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/backend/linux_raw/fs/types.rs b/src/backend/linux_raw/fs/types.rs index 7abd66539..dc25356b8 100644 --- a/src/backend/linux_raw/fs/types.rs +++ b/src/backend/linux_raw/fs/types.rs @@ -534,35 +534,40 @@ bitflags! { bitflags! { /// `ST_*` constants for use with [`StatVfs`]. + /// + /// These are the values the kernel reports in `statfs`'s `f_flags`, + /// which `statvfs` passes through unconverted. linux-raw-sys does not + /// define the `ST_*` names, and they are not all equal to the `MS_*` + /// ones: `ST_RELATIME` is `0x1000`, while `MS_RELATIME` is `1 << 21`. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct StatVfsMountFlags: u64 { /// `ST_MANDLOCK` - const MANDLOCK = linux_raw_sys::general::MS_MANDLOCK as u64; + const MANDLOCK = 0x0040; /// `ST_NOATIME` - const NOATIME = linux_raw_sys::general::MS_NOATIME as u64; + const NOATIME = 0x0400; /// `ST_NODEV` - const NODEV = linux_raw_sys::general::MS_NODEV as u64; + const NODEV = 0x0004; /// `ST_NODIRATIME` - const NODIRATIME = linux_raw_sys::general::MS_NODIRATIME as u64; + const NODIRATIME = 0x0800; /// `ST_NOEXEC` - const NOEXEC = linux_raw_sys::general::MS_NOEXEC as u64; + const NOEXEC = 0x0008; /// `ST_NOSUID` - const NOSUID = linux_raw_sys::general::MS_NOSUID as u64; + const NOSUID = 0x0002; /// `ST_RDONLY` - const RDONLY = linux_raw_sys::general::MS_RDONLY as u64; + const RDONLY = 0x0001; /// `ST_RELATIME` - const RELATIME = linux_raw_sys::general::MS_RELATIME as u64; + const RELATIME = 0x1000; /// `ST_SYNCHRONOUS` - const SYNCHRONOUS = linux_raw_sys::general::MS_SYNCHRONOUS as u64; + const SYNCHRONOUS = 0x0010; /// const _ = !0; diff --git a/tests/fs/statfs.rs b/tests/fs/statfs.rs index c3ff160ce..eb39d1e08 100644 --- a/tests/fs/statfs.rs +++ b/tests/fs/statfs.rs @@ -77,6 +77,66 @@ fn test_statvfs() { assert_ne!(f_frsize, 0); } +/// `StatVfsMountFlags` has the `ST_*` values, not the `MS_*` ones. +#[cfg(all(linux_kernel, target_env = "gnu"))] +#[test] +fn test_statvfs_mount_flags_abi() { + use rustix::fs::StatVfsMountFlags as Flags; + + assert_eq!(Flags::MANDLOCK.bits(), libc::ST_MANDLOCK as u64); + assert_eq!(Flags::NOATIME.bits(), libc::ST_NOATIME as u64); + assert_eq!(Flags::NODEV.bits(), libc::ST_NODEV as u64); + assert_eq!(Flags::NODIRATIME.bits(), libc::ST_NODIRATIME as u64); + assert_eq!(Flags::NOEXEC.bits(), libc::ST_NOEXEC as u64); + assert_eq!(Flags::NOSUID.bits(), libc::ST_NOSUID as u64); + assert_eq!(Flags::RDONLY.bits(), libc::ST_RDONLY as u64); + assert_eq!(Flags::RELATIME.bits(), libc::ST_RELATIME as u64); + assert_eq!(Flags::SYNCHRONOUS.bits(), libc::ST_SYNCHRONOUS as u64); +} + +/// The flags `statvfs` reports for a mount are the per-mount options its +/// `/proc/self/mountinfo` line lists. +#[cfg(linux_kernel)] +#[test] +fn test_statvfs_mount_flags_match_mountinfo() { + use rustix::fs::StatVfsMountFlags as Flags; + + let table = std::fs::read_to_string("/proc/self/mountinfo").unwrap(); + let mut checked = 0; + for point in ["/", "/proc"] { + // The last line at a path is the mount on top, which the path + // reaches. Field 5 is the mount point, field 6 the per-mount options. + let Some(options) = table + .lines() + .map(|line| line.split(' ').collect::>()) + .filter(|fields| fields.len() > 5 && fields[4] == point) + .map(|fields| fields[5].to_owned()) + .next_back() + else { + continue; + }; + let options: Vec<&str> = options.split(',').collect(); + let flags = rustix::fs::statvfs(point).unwrap().f_flag; + for (option, flag) in [ + ("ro", Flags::RDONLY), + ("nosuid", Flags::NOSUID), + ("nodev", Flags::NODEV), + ("noexec", Flags::NOEXEC), + ("noatime", Flags::NOATIME), + ("nodiratime", Flags::NODIRATIME), + ("relatime", Flags::RELATIME), + ] { + assert_eq!( + flags.contains(flag), + options.contains(&option), + "{point}: {option} in {options:?}, statvfs says {flags:?}" + ); + } + checked += 1; + } + assert_ne!(checked, 0, "neither / nor /proc is in the mount table"); +} + #[test] fn test_fstatvfs() { let file = std::fs::File::open("Cargo.toml").unwrap(); From 36289576370aa38bed1771e7970117682cb818b8 Mon Sep 17 00:00:00 2001 From: Jeff Kim Date: Sat, 3 Oct 2026 20:56:34 +0900 Subject: [PATCH 2/2] Check `RELATIME` against mountinfo only where it is defined The libc backend defines `StatVfsMountFlags::RELATIME` only where libc has `ST_RELATIME` (Android and glibc), so the mountinfo test did not compile for x86_64-unknown-linux-musl with use-libc. Compare the other flags everywhere, and `RELATIME` where the constant exists. --- tests/fs/statfs.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/fs/statfs.rs b/tests/fs/statfs.rs index eb39d1e08..e951eba69 100644 --- a/tests/fs/statfs.rs +++ b/tests/fs/statfs.rs @@ -117,15 +117,24 @@ fn test_statvfs_mount_flags_match_mountinfo() { }; let options: Vec<&str> = options.split(',').collect(); let flags = rustix::fs::statvfs(point).unwrap().f_flag; - for (option, flag) in [ + #[allow(unused_mut)] + let mut pairs = vec![ ("ro", Flags::RDONLY), ("nosuid", Flags::NOSUID), ("nodev", Flags::NODEV), ("noexec", Flags::NOEXEC), ("noatime", Flags::NOATIME), ("nodiratime", Flags::NODIRATIME), - ("relatime", Flags::RELATIME), - ] { + ]; + // The libc backend has `RELATIME` only where libc defines + // `ST_RELATIME`. + #[cfg(any( + linux_raw, + target_os = "android", + all(target_os = "linux", target_env = "gnu") + ))] + pairs.push(("relatime", Flags::RELATIME)); + for (option, flag) in pairs { assert_eq!( flags.contains(flag), options.contains(&option),