From afed40db7f8330bc7c3e3d22cb728f11e2ee2993 Mon Sep 17 00:00:00 2001 From: wangyining Date: Mon, 1 Jun 2026 18:44:41 +0800 Subject: [PATCH 1/4] Add source metadata in mount point --- .../procfs/src/task_nodes/mounts.rs | 7 +- fs/foundation/kvfs/src/mount.rs | 65 ++++++++++++++++++- posix/fs/src/mount.rs | 19 ++++-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/fs/filesystems/procfs/src/task_nodes/mounts.rs b/fs/filesystems/procfs/src/task_nodes/mounts.rs index 415a41d45..936a54883 100644 --- a/fs/filesystems/procfs/src/task_nodes/mounts.rs +++ b/fs/filesystems/procfs/src/task_nodes/mounts.rs @@ -142,7 +142,12 @@ fn mount_source_for(is_root: bool, fs_type: &str) -> String { } fn mount_source(mount: &Arc, fs_type: &str) -> String { - mount_source_for(mount.is_root(), fs_type) + let source = mount.source(); + if source.is_empty() { + mount_source_for(mount.is_root(), fs_type) + } else { + source.to_string() + } } fn push_mount_option(options: &mut Vec<&'static str>, enabled: bool, name: &'static str) { diff --git a/fs/foundation/kvfs/src/mount.rs b/fs/foundation/kvfs/src/mount.rs index 02e66ce54..69b2cd880 100644 --- a/fs/foundation/kvfs/src/mount.rs +++ b/fs/foundation/kvfs/src/mount.rs @@ -66,6 +66,8 @@ pub struct Mountpoint { device: u64, /// Per-mount flags. flags: MountFlags, + /// User-visible source string for this mount. + source: String, /// Mount that this one covers (for overmount). /// /// When a new mount is created at a location that already has a mount, @@ -84,6 +86,21 @@ impl Mountpoint { fs: &Filesystem, location_in_parent: Option, flags: MountFlags, + ) -> Arc { + let source = if location_in_parent.is_none() { + "rootfs" + } else { + fs.name() + }; + Self::new_with_flags_and_source(fs, location_in_parent, flags, source) + } + + /// Creates a new mountpoint with per-mount flags and source metadata. + pub fn new_with_flags_and_source( + fs: &Filesystem, + location_in_parent: Option, + flags: MountFlags, + source: impl Into, ) -> Arc { static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(1); @@ -94,6 +111,7 @@ impl Mountpoint { child_mounts: Mutex::default(), device: DEVICE_COUNTER.fetch_add(1, Ordering::Relaxed), flags, + source: source.into(), covers: Mutex::default(), }) } @@ -108,6 +126,15 @@ impl Mountpoint { Self::new_with_flags(fs, None, flags) } + /// Creates a root mountpoint with per-mount flags and source metadata. + pub fn new_root_with_flags_and_source( + fs: &Filesystem, + flags: MountFlags, + source: impl Into, + ) -> Arc { + Self::new_with_flags_and_source(fs, None, flags, source) + } + /// Return a `Location` representing the mountpoint root. pub fn root_location(self: &Arc) -> Location { Location::new(self.clone(), self.root.clone()) @@ -164,6 +191,11 @@ impl Mountpoint { self.flags } + /// Returns this mountpoint's user-visible source string. + pub fn source(&self) -> &str { + &self.source + } + /// Returns whether this mountpoint is mounted read-only. pub fn is_readonly(&self) -> bool { self.flags.contains(MountFlags::RDONLY) @@ -479,9 +511,19 @@ impl Location { &self, fs: &Filesystem, flags: MountFlags, + ) -> VfsResult> { + self.mount_with_flags_and_source(fs, flags, fs.name()) + } + + /// Mounts a filesystem with per-mount flags and source metadata. + pub fn mount_with_flags_and_source( + &self, + fs: &Filesystem, + flags: MountFlags, + source: impl Into, ) -> VfsResult> { let mut mountpoint = self.entry.as_dir()?.mount_at_this_dir.lock(); - let result = Mountpoint::new_with_flags(fs, Some(self.clone()), flags); + let result = Mountpoint::new_with_flags_and_source(fs, Some(self.clone()), flags, source); // Overmount: the new mount covers any existing mount at this location. // The old mount is stashed so it can be restored on unmount. if let Some(old) = mountpoint.take() { @@ -763,6 +805,14 @@ mod tests { assert_eq!(root.check_writable_mount(), Ok(())); } + #[def_test] + fn test_root_mount_source_defaults_to_rootfs() { + let fs = mock_filesystem(0); + let mount = Mountpoint::new_root(&fs); + + assert_eq!(mount.source(), "rootfs"); + } + #[def_test] fn test_root_mount_can_be_readonly() { let fs = mock_filesystem(0); @@ -796,6 +846,19 @@ mod tests { assert!(!child_root.is_effectively_readonly()); } + #[def_test] + fn test_mount_with_explicit_source_records_source() { + let root_fs = mock_filesystem(0); + let child_fs = mock_filesystem(0); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir + .mount_with_flags_and_source(&child_fs, MountFlags::empty(), "none") + .unwrap(); + + assert_eq!(child_mount.source(), "none"); + } + #[def_test] fn test_filesystem_stat_readonly_makes_location_effectively_readonly() { let fs = mock_filesystem(crate::ST_RDONLY); diff --git a/posix/fs/src/mount.rs b/posix/fs/src/mount.rs index b52ab9e4c..1f6b74a96 100644 --- a/posix/fs/src/mount.rs +++ b/posix/fs/src/mount.rs @@ -99,7 +99,11 @@ pub fn sys_mount( return Err(KError::InvalidInput); } - let source = source.load_string()?; + let source = if source.is_null() { + None + } else { + Some(source.load_string()?) + }; let target = target.load_string()?; let fs_type = fs_type.load_string()?; debug!("sys_mount <= source: {source:?}, target: {target:?}, fs_type: {fs_type:?}"); @@ -108,13 +112,15 @@ pub fn sys_mount( return Err(KError::NoSuchDevice); } + let source = source.unwrap_or_else(|| "none".into()); let mount_flags = per_mount_flags(flags); + let process_state = current_process_state(); + let fs_context = process_state.fs_context(); + let fs_context = fs_context.lock(); + let target = fs_context.resolve(target)?; + target.check_is_dir()?; let fs = MemoryFs::new_with_name_and_flags("tmpfs", superblock_flags_from_sys_mount(flags)); - let target = current_process_state() - .fs_context() - .lock() - .resolve(target)?; - target.mount_with_flags(&fs, mount_flags)?; + target.mount_with_flags_and_source(&fs, mount_flags, source)?; Ok(0) } @@ -142,6 +148,7 @@ pub fn sys_umount2(target: UserConstPtr, flags: i32) -> KResult { .fs_context() .lock() .resolve(target)?; + target.filesystem().flush()?; target.unmount()?; Ok(0) } -- Gitee From 1db76f6a980bc020cdc8171786fb2d60335061d4 Mon Sep 17 00:00:00 2001 From: wangyining Date: Tue, 2 Jun 2026 11:02:39 +0800 Subject: [PATCH 2/4] remount / bind / move / propagation explicitly EINVAL --- .../procfs/src/task_nodes/mounts.rs | 92 +++++- fs/foundation/kvfs/src/mount.rs | 276 +++++++++++++++--- posix/fs/src/mount.rs | 201 +++++++++---- 3 files changed, 471 insertions(+), 98 deletions(-) diff --git a/fs/filesystems/procfs/src/task_nodes/mounts.rs b/fs/filesystems/procfs/src/task_nodes/mounts.rs index 936a54883..278eef1f1 100644 --- a/fs/filesystems/procfs/src/task_nodes/mounts.rs +++ b/fs/filesystems/procfs/src/task_nodes/mounts.rs @@ -70,7 +70,10 @@ fn show_mounts(item: &ProcMountEntry, buf: &mut String) -> core::fmt::Result { let mount_options = format_mount_options(item.mnt_flags, item.mount_ro || item.super_ro); buf.push_str(&format!( "{} {} {} {} 0 0\n", - item.source, item.mount_point, item.fs_type, mount_options + escape_mount_field(&item.source, true), + escape_mount_field(&item.mount_point, false), + escape_mount_field(&item.fs_type, true), + mount_options )); Ok(()) } @@ -84,11 +87,11 @@ fn show_mountinfo(item: &ProcMountEntry, buf: &mut String) -> core::fmt::Result item.parent_id, item.major, item.minor, - item.root, - item.mount_point, + escape_mount_field(&item.root, false), + escape_mount_field(&item.mount_point, false), mount_options, - item.fs_type, - item.source, + escape_mount_field(&item.fs_type, true), + escape_mount_field(&item.source, true), super_options, )); Ok(()) @@ -150,6 +153,26 @@ fn mount_source(mount: &Arc, fs_type: &str) -> String { } } +fn escape_mount_field(value: &str, escape_hash: bool) -> String { + let mut result = String::new(); + for ch in value.chars() { + let should_escape = matches!(ch, ' ' | '\t' | '\n' | '\\') || (escape_hash && ch == '#'); + if should_escape { + push_octal_escape(&mut result, ch as u8); + } else { + result.push(ch); + } + } + result +} + +fn push_octal_escape(buf: &mut String, byte: u8) { + buf.push('\\'); + buf.push((b'0' + ((byte >> 6) & 0o7)) as char); + buf.push((b'0' + ((byte >> 3) & 0o7)) as char); + buf.push((b'0' + (byte & 0o7)) as char); +} + fn push_mount_option(options: &mut Vec<&'static str>, enabled: bool, name: &'static str) { if enabled { options.push(name); @@ -322,6 +345,18 @@ mod tests { assert!(opts.contains("nosymfollow")); } + #[def_test] + fn test_escape_mount_field_uses_octal_escapes() { + assert_eq!( + super::escape_mount_field("a b\tc\nd\\e#f", true), + "a\\040b\\011c\\012d\\134e\\043f" + ); + assert_eq!( + super::escape_mount_field("a b\tc\nd\\e#f", false), + "a\\040b\\011c\\012d\\134e#f" + ); + } + #[def_test] fn test_mounts_uses_effective_readonly_options() { let entry = ProcMountEntry { @@ -344,6 +379,28 @@ mod tests { assert_eq!(buf, "rootfs / ext4 ro 0 0\n"); } + #[def_test] + fn test_mounts_escapes_mount_table_fields() { + let entry = ProcMountEntry { + mount_id: 1, + parent_id: 0, + major: 0, + minor: 1, + root: "/".into(), + mount_point: "/mnt/a b".into(), + fs_type: "tmp fs".into(), + source: "dev#x\\y".into(), + mnt_flags: MountFlags::empty(), + mount_ro: false, + super_ro: false, + }; + let mut buf = alloc::string::String::new(); + + super::show_mounts(&entry, &mut buf).unwrap(); + + assert_eq!(buf, "dev\\043x\\134y /mnt/a\\040b tmp\\040fs rw 0 0\n"); + } + #[def_test] fn test_mountinfo_separates_mount_and_super_readonly() { let entry = ProcMountEntry { @@ -365,4 +422,29 @@ mod tests { assert_eq!(buf, "1 0 0:1 / / rw - ext4 rootfs ro\n"); } + + #[def_test] + fn test_mountinfo_escapes_mount_table_fields() { + let entry = ProcMountEntry { + mount_id: 1, + parent_id: 0, + major: 0, + minor: 1, + root: "/root dir".into(), + mount_point: "/mnt/a b".into(), + fs_type: "tmp fs".into(), + source: "dev#x\\y".into(), + mnt_flags: MountFlags::empty(), + mount_ro: false, + super_ro: false, + }; + let mut buf = alloc::string::String::new(); + + super::show_mountinfo(&entry, &mut buf).unwrap(); + + assert_eq!( + buf, + "1 0 0:1 /root\\040dir /mnt/a\\040b rw - tmp\\040fs dev\\043x\\134y rw\n" + ); + } } diff --git a/fs/foundation/kvfs/src/mount.rs b/fs/foundation/kvfs/src/mount.rs index 69b2cd880..cd7673f15 100644 --- a/fs/foundation/kvfs/src/mount.rs +++ b/fs/foundation/kvfs/src/mount.rs @@ -546,6 +546,15 @@ impl Location { /// tail matches `mount_with_flags`; `self.child_mounts` is a distinct /// instance so no deadlock. The `child_mounts` check is done under /// `mount_at_this_dir` to close the TOCTOU window. + /// + /// `flush()` also runs before releasing `mount_at_this_dir`. This keeps the + /// mount visible if writeback fails, which matches the current VFS contract + /// better than detaching first and reattaching on error. Linux can split + /// this more cleanly because `umount_tree()` detaches mounts while mnt/super + /// references keep teardown alive, and `generic_shutdown_super()` later + /// performs filesystem sync. This VFS does not have that state machine yet; + /// once it grows detached-mount state and active superblock references, this + /// long filesystem callback should move out from under the mountpoint lock. pub fn unmount(&self) -> VfsResult<()> { if !self.is_root_of_mount() { return Err(VfsError::InvalidInput); @@ -553,34 +562,39 @@ impl Location { if !self.entry.ptr_eq(&self.mountpoint.root) { return Err(VfsError::InvalidInput); } - if let Some(parent_loc) = &self.mountpoint.location { - let mut mount_slot = parent_loc.entry.as_dir()?.mount_at_this_dir.lock(); - // Guard against overmount race: self must still be the visible mount - // at this dentry after we acquired the lock. - if !mount_slot - .as_ref() - .is_some_and(|m| Arc::ptr_eq(m, &self.mountpoint)) - { - return Err(VfsError::InvalidInput); - } - // Re-check child_mounts under mount_slot to close the TOCTOU window - // between the earlier check and acquiring mount_slot. - if !self.mountpoint.child_mounts.lock().is_empty() { - return Err(VfsError::ResourceBusy); + let Some(parent_loc) = &self.mountpoint.location else { + return Err(VfsError::InvalidInput); + }; + + let mut mount_slot = parent_loc.entry.as_dir()?.mount_at_this_dir.lock(); + // Guard against overmount race: self must still be the visible mount + // at this dentry after we acquired the lock. + if !mount_slot + .as_ref() + .is_some_and(|m| Arc::ptr_eq(m, &self.mountpoint)) + { + return Err(VfsError::InvalidInput); + } + // Re-check child_mounts under mount_slot to close the TOCTOU window + // between the earlier check and acquiring mount_slot. + if !self.mountpoint.child_mounts.lock().is_empty() { + return Err(VfsError::ResourceBusy); + } + + self.filesystem().flush()?; + + let mut parent_children = parent_loc.mountpoint.child_mounts.lock(); + let covered = self.mountpoint.covers.lock().take(); + *mount_slot = covered.clone(); + match covered { + Some(ref m) => { + parent_children.insert(parent_loc.entry.key(), Arc::downgrade(m)); } - let mut parent_children = parent_loc.mountpoint.child_mounts.lock(); - let covered = self.mountpoint.covers.lock().take(); - *mount_slot = covered.clone(); - match covered { - Some(ref m) => { - parent_children.insert(parent_loc.entry.key(), Arc::downgrade(m)); - } - None => { - parent_children.remove(&parent_loc.entry.key()); - } + None => { + parent_children.remove(&parent_loc.entry.key()); } - // Drop parent locks before forget. Order matches mount_with_flags. } + // Drop parent locks before forget. Order matches mount_with_flags. // forget() after parent state is committed — if the parent update // failed we must not have destroyed the dentry cache prematurely. self.entry.as_dir()?.forget(); @@ -588,6 +602,11 @@ impl Location { } /// Recursively unmount this filesystem and all children. + /// + /// This is the mount-tree teardown path used by kernel shutdown. Unlike + /// [`Self::unmount`], it may be called on the global root mount. The root + /// mount has no parent mountpoint to detach from, so this method only + /// detaches its children and leaves root filesystem flushing to the caller. pub fn unmount_all(&self) -> VfsResult<()> { if !self.is_root_of_mount() { return Err(VfsError::InvalidInput); @@ -595,12 +614,14 @@ impl Location { let mut children = self.mountpoint.child_mounts.lock(); let remaining = mem::take(&mut *children); drop(children); - let mut failed = false; + let mut first_error = None; for (key, child) in remaining { if let Some(m) = child.upgrade() - && let Err(_e) = m.root_location().unmount_all() + && let Err(error) = m.root_location().unmount_all() { - failed = true; + if first_error.is_none() { + first_error = Some(error); + } // Re-insert so the child is not orphaned. self.mountpoint .child_mounts @@ -608,10 +629,13 @@ impl Location { .insert(key, Arc::downgrade(&m)); } } - if failed { - return Err(VfsError::ResourceBusy); + if let Some(error) = first_error { + return Err(error); + } + if self.mountpoint.location.is_some() { + self.unmount()?; } - self.unmount() + Ok(()) } } @@ -627,7 +651,11 @@ mod tests { extern crate alloc; use alloc::{string::String, sync::Arc}; - use core::{any::Any, time::Duration}; + use core::{ + any::Any, + sync::atomic::{AtomicUsize, Ordering}, + time::Duration, + }; use unittest::{assert, assert_eq, def_test}; @@ -639,6 +667,40 @@ mod tests { struct MockFilesystem { mount_flags: u32, + flush_state: Arc, + } + + struct MockFlushState { + count: AtomicUsize, + error: Option, + } + + impl MockFlushState { + fn ok() -> Arc { + Arc::new(Self { + count: AtomicUsize::new(0), + error: None, + }) + } + + fn with_error(error: VfsError) -> Arc { + Arc::new(Self { + count: AtomicUsize::new(0), + error: Some(error), + }) + } + + fn count(&self) -> usize { + self.count.load(Ordering::Relaxed) + } + + fn flush(&self) -> VfsResult<()> { + self.count.fetch_add(1, Ordering::Relaxed); + if let Some(error) = self.error { + return Err(error); + } + Ok(()) + } } impl FilesystemOps for MockFilesystem { @@ -648,7 +710,13 @@ mod tests { fn root_dir(&self) -> DirEntry { DirEntry::new_dir( - |_| DirNode::new(Arc::new(MockDirNodeOps::new(self.mount_flags, 1))), + |_| { + DirNode::new(Arc::new(MockDirNodeOps::new( + self.mount_flags, + self.flush_state.clone(), + 1, + ))) + }, Reference::root(), ) } @@ -656,16 +724,25 @@ mod tests { fn stat(&self) -> VfsResult { statfs(self.mount_flags) } + + fn flush(&self) -> VfsResult<()> { + self.flush_state.flush() + } } struct MockDirNodeOps { mount_flags: u32, + flush_state: Arc, inode: u64, } impl MockDirNodeOps { - fn new(mount_flags: u32, inode: u64) -> Self { - Self { mount_flags, inode } + fn new(mount_flags: u32, flush_state: Arc, inode: u64) -> Self { + Self { + mount_flags, + flush_state, + inode, + } } } @@ -722,6 +799,10 @@ mod tests { fn stat(&self) -> VfsResult { statfs(self.mount_flags) } + + fn flush(&self) -> VfsResult<()> { + self.flush_state.flush() + } } impl DirNodeOps for MockDirNodeOps { @@ -738,6 +819,7 @@ mod tests { |_| { DirNode::new(Arc::new(MockDirNodeOps::new( self.mount_flags, + self.flush_state.clone(), self.inode + 1, ))) }, @@ -768,7 +850,20 @@ mod tests { } fn mock_filesystem(mount_flags: u32) -> Filesystem { - Filesystem::new(Arc::new(MockFilesystem { mount_flags })) + mock_filesystem_with_flush(mount_flags, MockFlushState::ok()).0 + } + + fn mock_filesystem_with_flush( + mount_flags: u32, + flush_state: Arc, + ) -> (Filesystem, Arc) { + ( + Filesystem::new(Arc::new(MockFilesystem { + mount_flags, + flush_state: flush_state.clone(), + })), + flush_state, + ) } fn statfs(mount_flags: u32) -> VfsResult { @@ -917,6 +1012,115 @@ mod tests { assert!(Arc::ptr_eq(&loc_a_again.mountpoint().clone(), &mount_a)); } + #[def_test] + fn test_unmount_flushes_mounted_filesystem_before_detach() { + let root_fs = mock_filesystem(0); + let (child_fs, child_flush) = mock_filesystem_with_flush(0, MockFlushState::ok()); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir.mount(&child_fs).unwrap(); + + child_mount.root_location().unmount().unwrap(); + + assert_eq!(child_flush.count(), 1); + let visible = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + assert!(!Arc::ptr_eq(&visible.mountpoint().clone(), &child_mount)); + } + + #[def_test] + fn test_unmount_flush_error_keeps_mount_attached() { + let root_fs = mock_filesystem(0); + let (child_fs, child_flush) = + mock_filesystem_with_flush(0, MockFlushState::with_error(VfsError::Unsupported)); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir.mount(&child_fs).unwrap(); + + let result = child_mount.root_location().unmount(); + + assert_eq!(result, Err(VfsError::Unsupported)); + assert_eq!(child_flush.count(), 1); + let visible = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + assert!(Arc::ptr_eq(&visible.mountpoint().clone(), &child_mount)); + } + + #[def_test] + fn test_unmount_busy_mount_does_not_flush() { + let root_fs = mock_filesystem(0); + let (child_fs, child_flush) = mock_filesystem_with_flush(0, MockFlushState::ok()); + let grandchild_fs = mock_filesystem(0); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir.mount(&child_fs).unwrap(); + let grandchild_dir = child_mount.root_location().lookup_no_follow("mnt").unwrap(); + grandchild_dir.mount(&grandchild_fs).unwrap(); + + let result = child_mount.root_location().unmount(); + + assert_eq!(result, Err(VfsError::ResourceBusy)); + assert_eq!(child_flush.count(), 0); + } + + #[def_test] + fn test_unmount_global_root_fails() { + let fs = mock_filesystem(0); + let mount = Mountpoint::new_root(&fs); + + assert_eq!(mount.root_location().unmount(), Err(VfsError::InvalidInput)); + } + + #[def_test] + fn test_unmount_all_on_global_root_detaches_children_only() { + let (root_fs, root_flush) = mock_filesystem_with_flush(0, MockFlushState::ok()); + let (child_fs, child_flush) = mock_filesystem_with_flush(0, MockFlushState::ok()); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir.mount(&child_fs).unwrap(); + + root_mount.root_location().unmount_all().unwrap(); + + assert_eq!(root_flush.count(), 0); + assert_eq!(child_flush.count(), 1); + let visible = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + assert!(!Arc::ptr_eq(&visible.mountpoint().clone(), &child_mount)); + } + + #[def_test] + fn test_unmount_all_on_global_root_recursively_detaches_children() { + let root_fs = mock_filesystem(0); + let (child_fs, child_flush) = mock_filesystem_with_flush(0, MockFlushState::ok()); + let grandchild_fs = mock_filesystem(0); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir.mount(&child_fs).unwrap(); + let grandchild_dir = child_mount.root_location().lookup_no_follow("mnt").unwrap(); + grandchild_dir.mount(&grandchild_fs).unwrap(); + + let result = root_mount.root_location().unmount_all(); + + assert_eq!(result, Ok(())); + assert_eq!(child_flush.count(), 1); + let visible = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + assert!(!Arc::ptr_eq(&visible.mountpoint().clone(), &child_mount)); + } + + #[def_test] + fn test_unmount_all_on_global_root_keeps_child_on_flush_error() { + let root_fs = mock_filesystem(0); + let (child_fs, child_flush) = + mock_filesystem_with_flush(0, MockFlushState::with_error(VfsError::Unsupported)); + let root_mount = Mountpoint::new_root(&root_fs); + let mount_dir = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + let child_mount = mount_dir.mount(&child_fs).unwrap(); + + let result = root_mount.root_location().unmount_all(); + + assert_eq!(result, Err(VfsError::Unsupported)); + assert_eq!(child_flush.count(), 1); + let visible = root_mount.root_location().lookup_no_follow("mnt").unwrap(); + assert!(Arc::ptr_eq(&visible.mountpoint().clone(), &child_mount)); + } + #[def_test] fn test_readonly_mount_blocks_create() { let fs = mock_filesystem(0); diff --git a/posix/fs/src/mount.rs b/posix/fs/src/mount.rs index 1f6b74a96..62821f1d3 100644 --- a/posix/fs/src/mount.rs +++ b/posix/fs/src/mount.rs @@ -9,55 +9,103 @@ use core::ffi::{c_char, c_void}; use kerrno::{KError, KResult}; use kthread::current_process_state; use kvfs::{MountFlags, ST_RDONLY}; +use linux_raw_sys::general; use memfs::MemoryFs; use posix_types::UserConstPtr; -fn superblock_flags_from_sys_mount(flags: i32) -> u32 { - let f = flags as u32; +const SUPPORTED_MOUNT_FLAGS: u32 = general::MS_RDONLY + | general::MS_NOSUID + | general::MS_NODEV + | general::MS_NOEXEC + | general::MS_NOATIME + | general::MS_NODIRATIME + | general::MS_RELATIME + | general::MS_STRICTATIME + | general::MS_NOSYMFOLLOW; + +const IGNORED_COMPATIBILITY_MOUNT_FLAGS: u32 = general::MS_SILENT; + +const UNSUPPORTED_MOUNT_OPERATION_FLAGS: u32 = general::MS_REMOUNT + | general::MS_BIND + | general::MS_MOVE + | general::MS_SHARED + | general::MS_PRIVATE + | general::MS_SLAVE + | general::MS_UNBINDABLE + | general::MS_REC; + +fn validate_mount_flags(flags: i32) -> KResult { + let mut f = flags as u32; + + // Linux accepts the legacy magic in the high 16 bits and strips it before + // interpreting the remaining mount flags. + if (f & general::MS_MGC_MSK) == general::MS_MGC_VAL { + f &= !general::MS_MGC_MSK; + } + + // MS_NOUSER is never allowed from userspace. + if f & general::MS_NOUSER != 0 { + return Err(KError::InvalidInput); + } + + // Reject operation types that aren't yet implemented. + // Each of these dispatches to a separate handler. + if f & UNSUPPORTED_MOUNT_OPERATION_FLAGS != 0 { + return Err(KError::InvalidInput); + } + + if f & !(SUPPORTED_MOUNT_FLAGS | IGNORED_COMPATIBILITY_MOUNT_FLAGS) != 0 { + return Err(KError::InvalidInput); + } + + Ok(f & !IGNORED_COMPATIBILITY_MOUNT_FLAGS) +} + +fn superblock_flags_from_sys_mount(flags: u32) -> u32 { + let f = flags; let mut sb_flags = 0; - if f & linux_raw_sys::general::MS_RDONLY != 0 { + if f & general::MS_RDONLY != 0 { sb_flags |= ST_RDONLY; } sb_flags } /// Map `mount(2)` MS_* flags to per-mount [`MountFlags`]. -fn per_mount_flags(flags: i32) -> MountFlags { - // `flags` is a non-negative bitmask from mount(2); safe to reinterpret. - let f = flags as u32; +fn per_mount_flags(flags: u32) -> MountFlags { + let f = flags; let mut mnt_flags = MountFlags::empty(); - // Default to relatime unless NOATIME is set. - if f & linux_raw_sys::general::MS_NOATIME == 0 { + // Match Linux legacy mount behavior: relatime is the default unless the + // caller explicitly requested noatime. MS_RELATIME is a bit in the same + // unordered flag set, so it must not cancel a simultaneous MS_NOATIME. + if f & general::MS_NOATIME == 0 { mnt_flags |= MountFlags::RELATIME; } - if f & linux_raw_sys::general::MS_RDONLY != 0 { + if f & general::MS_RDONLY != 0 { mnt_flags |= MountFlags::RDONLY; } - if f & linux_raw_sys::general::MS_NOSUID != 0 { + if f & general::MS_NOSUID != 0 { mnt_flags |= MountFlags::NOSUID; } - if f & linux_raw_sys::general::MS_NODEV != 0 { + if f & general::MS_NODEV != 0 { mnt_flags |= MountFlags::NODEV; } - if f & linux_raw_sys::general::MS_NOEXEC != 0 { + if f & general::MS_NOEXEC != 0 { mnt_flags |= MountFlags::NOEXEC; } - if f & linux_raw_sys::general::MS_NOATIME != 0 { + if f & general::MS_NOATIME != 0 { mnt_flags |= MountFlags::NOATIME; } - if f & linux_raw_sys::general::MS_NODIRATIME != 0 { + if f & general::MS_NODIRATIME != 0 { mnt_flags |= MountFlags::NODIRATIME; } - // No explicit MS_RELATIME → MNT_RELATIME mapping: RELATIME is controlled - // solely by the default logic above. // STRICTATIME takes priority — clear both RELATIME and NOATIME. - if f & linux_raw_sys::general::MS_STRICTATIME != 0 { + if f & general::MS_STRICTATIME != 0 { mnt_flags &= !(MountFlags::RELATIME | MountFlags::NOATIME); } - if f & linux_raw_sys::general::MS_NOSYMFOLLOW != 0 { + if f & general::MS_NOSYMFOLLOW != 0 { mnt_flags |= MountFlags::NOSYMFOLLOW; } mnt_flags @@ -69,35 +117,9 @@ pub fn sys_mount( target: UserConstPtr, fs_type: UserConstPtr, flags: i32, - _data: UserConstPtr, + data: UserConstPtr, ) -> KResult { - let f = flags as u32; - - // MS_NOUSER is never allowed from userspace. - if f & linux_raw_sys::general::MS_NOUSER != 0 { - return Err(KError::InvalidInput); - } - - // Reject operation types that aren't yet implemented. - // Each of these dispatches to a separate handler. - if f & linux_raw_sys::general::MS_REMOUNT != 0 { - return Err(KError::InvalidInput); - } - if f & linux_raw_sys::general::MS_BIND != 0 { - return Err(KError::InvalidInput); - } - if f & linux_raw_sys::general::MS_MOVE != 0 { - return Err(KError::InvalidInput); - } - if f & (linux_raw_sys::general::MS_SHARED - | linux_raw_sys::general::MS_PRIVATE - | linux_raw_sys::general::MS_SLAVE - | linux_raw_sys::general::MS_UNBINDABLE - | linux_raw_sys::general::MS_REC) - != 0 - { - return Err(KError::InvalidInput); - } + let flags = validate_mount_flags(flags)?; let source = if source.is_null() { None @@ -111,6 +133,9 @@ pub fn sys_mount( if fs_type != "tmpfs" { return Err(KError::NoSuchDevice); } + if !data.is_null() && !data.cast::().load_string()?.is_empty() { + return Err(KError::InvalidInput); + } let source = source.unwrap_or_else(|| "none".into()); let mount_flags = per_mount_flags(flags); @@ -148,7 +173,6 @@ pub fn sys_umount2(target: UserConstPtr, flags: i32) -> KResult { .fs_context() .lock() .resolve(target)?; - target.filesystem().flush()?; target.unmount()?; Ok(0) } @@ -156,16 +180,17 @@ pub fn sys_umount2(target: UserConstPtr, flags: i32) -> KResult { #[cfg(unittest)] mod tests { use kvfs::{MountFlags, ST_RDONLY}; + use linux_raw_sys::general; use unittest::{assert, assert_eq, def_test}; #[def_test] fn test_superblock_flags_from_mount_only_options_are_filtered() { - let flags = (linux_raw_sys::general::MS_NODEV - | linux_raw_sys::general::MS_NOEXEC - | linux_raw_sys::general::MS_NOSUID - | linux_raw_sys::general::MS_NOATIME - | linux_raw_sys::general::MS_NODIRATIME - | linux_raw_sys::general::MS_NOSYMFOLLOW) as i32; + let flags = general::MS_NODEV + | general::MS_NOEXEC + | general::MS_NOSUID + | general::MS_NOATIME + | general::MS_NODIRATIME + | general::MS_NOSYMFOLLOW; assert_eq!(super::superblock_flags_from_sys_mount(flags), 0); } @@ -173,20 +198,82 @@ mod tests { #[def_test] fn test_superblock_flags_preserve_readonly() { assert_eq!( - super::superblock_flags_from_sys_mount(linux_raw_sys::general::MS_RDONLY as i32), + super::superblock_flags_from_sys_mount(general::MS_RDONLY), ST_RDONLY ); } #[def_test] fn test_per_mount_flags_preserve_mount_options() { - let flags = (linux_raw_sys::general::MS_RDONLY - | linux_raw_sys::general::MS_NODEV - | linux_raw_sys::general::MS_NOEXEC) as i32; + let flags = general::MS_RDONLY | general::MS_NODEV | general::MS_NOEXEC; let result = super::per_mount_flags(flags); assert!(result.contains(MountFlags::RDONLY)); assert!(result.contains(MountFlags::NODEV)); assert!(result.contains(MountFlags::NOEXEC)); } + + #[def_test] + fn test_per_mount_flags_noatime_is_not_cleared_by_relatime() { + let flags = general::MS_NOATIME | general::MS_RELATIME; + let result = super::per_mount_flags(flags); + + assert!(!result.contains(MountFlags::RELATIME)); + assert!(result.contains(MountFlags::NOATIME)); + } + + #[def_test] + fn test_per_mount_flags_strictatime_overrides_atime_flags() { + let flags = general::MS_NOATIME | general::MS_RELATIME | general::MS_STRICTATIME; + let result = super::per_mount_flags(flags); + + assert!(!result.contains(MountFlags::RELATIME)); + assert!(!result.contains(MountFlags::NOATIME)); + } + + #[def_test] + fn test_validate_mount_flags_accepts_supported_flags() { + let flags = (general::MS_RDONLY + | general::MS_NOSUID + | general::MS_NODEV + | general::MS_NOEXEC + | general::MS_NOATIME + | general::MS_NODIRATIME + | general::MS_RELATIME + | general::MS_STRICTATIME + | general::MS_NOSYMFOLLOW) as i32; + + assert_eq!(super::validate_mount_flags(flags), Ok(flags as u32)); + } + + #[def_test] + fn test_validate_mount_flags_ignores_compatibility_flags() { + let flags = (general::MS_MGC_VAL | general::MS_SILENT | general::MS_RDONLY) as i32; + + assert_eq!(super::validate_mount_flags(flags), Ok(general::MS_RDONLY)); + } + + #[def_test] + fn test_validate_mount_flags_rejects_unsupported_operation_flags() { + assert_eq!( + super::validate_mount_flags(general::MS_BIND as i32), + Err(kerrno::KError::InvalidInput) + ); + assert_eq!( + super::validate_mount_flags(general::MS_REMOUNT as i32), + Err(kerrno::KError::InvalidInput) + ); + } + + #[def_test] + fn test_validate_mount_flags_rejects_unimplemented_plain_flags() { + assert_eq!( + super::validate_mount_flags(general::MS_SYNCHRONOUS as i32), + Err(kerrno::KError::InvalidInput) + ); + assert_eq!( + super::validate_mount_flags(general::MS_LAZYTIME as i32), + Err(kerrno::KError::InvalidInput) + ); + } } -- Gitee From f5f997f40f011ee3f5e80ba813728db1d1704637 Mon Sep 17 00:00:00 2001 From: wangyining Date: Tue, 2 Jun 2026 15:13:14 +0800 Subject: [PATCH 3/4] The new `parse_mount_request()` function adds new mount flags, which are categorized into unimplemented but Linux-compatible flags and unknown flags. --- .../procfs/src/task_nodes/mounts.rs | 34 +-- posix/fs/src/mount.rs | 201 ++++++++++++++---- 2 files changed, 184 insertions(+), 51 deletions(-) diff --git a/fs/filesystems/procfs/src/task_nodes/mounts.rs b/fs/filesystems/procfs/src/task_nodes/mounts.rs index 278eef1f1..c332e8b9f 100644 --- a/fs/filesystems/procfs/src/task_nodes/mounts.rs +++ b/fs/filesystems/procfs/src/task_nodes/mounts.rs @@ -70,9 +70,9 @@ fn show_mounts(item: &ProcMountEntry, buf: &mut String) -> core::fmt::Result { let mount_options = format_mount_options(item.mnt_flags, item.mount_ro || item.super_ro); buf.push_str(&format!( "{} {} {} {} 0 0\n", - escape_mount_field(&item.source, true), - escape_mount_field(&item.mount_point, false), - escape_mount_field(&item.fs_type, true), + escape_mount_field(&item.source, EscapeMountField::Devname), + escape_mount_field(&item.mount_point, EscapeMountField::Path), + escape_mount_field(&item.fs_type, EscapeMountField::Devname), mount_options )); Ok(()) @@ -87,11 +87,11 @@ fn show_mountinfo(item: &ProcMountEntry, buf: &mut String) -> core::fmt::Result item.parent_id, item.major, item.minor, - escape_mount_field(&item.root, false), - escape_mount_field(&item.mount_point, false), + escape_mount_field(&item.root, EscapeMountField::Path), + escape_mount_field(&item.mount_point, EscapeMountField::Path), mount_options, - escape_mount_field(&item.fs_type, true), - escape_mount_field(&item.source, true), + escape_mount_field(&item.fs_type, EscapeMountField::Devname), + escape_mount_field(&item.source, EscapeMountField::Devname), super_options, )); Ok(()) @@ -153,11 +153,17 @@ fn mount_source(mount: &Arc, fs_type: &str) -> String { } } -fn escape_mount_field(value: &str, escape_hash: bool) -> String { +#[derive(Clone, Copy)] +enum EscapeMountField { + Devname, + Path, +} + +fn escape_mount_field(value: &str, field: EscapeMountField) -> String { let mut result = String::new(); for ch in value.chars() { - let should_escape = matches!(ch, ' ' | '\t' | '\n' | '\\') || (escape_hash && ch == '#'); - if should_escape { + let should_escape_hash = matches!(field, EscapeMountField::Devname) && ch == '#'; + if matches!(ch, ' ' | '\t' | '\n' | '\\') || should_escape_hash { push_octal_escape(&mut result, ch as u8); } else { result.push(ch); @@ -348,12 +354,12 @@ mod tests { #[def_test] fn test_escape_mount_field_uses_octal_escapes() { assert_eq!( - super::escape_mount_field("a b\tc\nd\\e#f", true), - "a\\040b\\011c\\012d\\134e\\043f" + super::escape_mount_field("a b\tc\nd\\e#f", super::EscapeMountField::Path), + "a\\040b\\011c\\012d\\134e#f" ); assert_eq!( - super::escape_mount_field("a b\tc\nd\\e#f", false), - "a\\040b\\011c\\012d\\134e#f" + super::escape_mount_field("a b\tc\nd\\e#f", super::EscapeMountField::Devname), + "a\\040b\\011c\\012d\\134e\\043f" ); } diff --git a/posix/fs/src/mount.rs b/posix/fs/src/mount.rs index 62821f1d3..d2f8b31f7 100644 --- a/posix/fs/src/mount.rs +++ b/posix/fs/src/mount.rs @@ -8,12 +8,12 @@ use core::ffi::{c_char, c_void}; use kerrno::{KError, KResult}; use kthread::current_process_state; -use kvfs::{MountFlags, ST_RDONLY}; +use kvfs::{MetadataUpdate, MountFlags, NodePermission, ST_RDONLY}; use linux_raw_sys::general; use memfs::MemoryFs; use posix_types::UserConstPtr; -const SUPPORTED_MOUNT_FLAGS: u32 = general::MS_RDONLY +const IMPLEMENTED_NEW_MOUNT_FLAGS: u32 = general::MS_RDONLY | general::MS_NOSUID | general::MS_NODEV | general::MS_NOEXEC @@ -21,44 +21,155 @@ const SUPPORTED_MOUNT_FLAGS: u32 = general::MS_RDONLY | general::MS_NODIRATIME | general::MS_RELATIME | general::MS_STRICTATIME - | general::MS_NOSYMFOLLOW; + | general::MS_NOSYMFOLLOW + | general::MS_SILENT; + +const KNOWN_LINUX_NEW_MOUNT_FLAGS: u32 = IMPLEMENTED_NEW_MOUNT_FLAGS + | general::MS_SYNCHRONOUS + | general::MS_MANDLOCK + | general::MS_DIRSYNC + | general::MS_POSIXACL + | general::MS_I_VERSION + | general::MS_LAZYTIME; + +const PROPAGATION_FLAGS: u32 = + general::MS_SHARED | general::MS_PRIVATE | general::MS_SLAVE | general::MS_UNBINDABLE; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PropagationKind { + Shared, + Private, + Slave, + Unbindable, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum MountRequest { + BindRemount(u32), + Remount(u32), + Bind { + recursive: bool, + }, + Propagation { + kind: PropagationKind, + recursive: bool, + }, + Move(u32), + NewMount(u32), +} -const IGNORED_COMPATIBILITY_MOUNT_FLAGS: u32 = general::MS_SILENT; +fn parse_mount_request(flags: i32) -> KResult { + let f = normalize_mount_flags(flags)?; + + if f & (general::MS_REMOUNT | general::MS_BIND) == (general::MS_REMOUNT | general::MS_BIND) { + return Ok(MountRequest::BindRemount(f)); + } + if f & general::MS_REMOUNT != 0 { + return Ok(MountRequest::Remount(f)); + } + if f & general::MS_BIND != 0 { + return Ok(MountRequest::Bind { + recursive: f & general::MS_REC != 0, + }); + } + if f & PROPAGATION_FLAGS != 0 { + return parse_propagation_request(f); + } + if f & general::MS_MOVE != 0 { + return Ok(MountRequest::Move(f)); + } -const UNSUPPORTED_MOUNT_OPERATION_FLAGS: u32 = general::MS_REMOUNT - | general::MS_BIND - | general::MS_MOVE - | general::MS_SHARED - | general::MS_PRIVATE - | general::MS_SLAVE - | general::MS_UNBINDABLE - | general::MS_REC; + validate_new_mount_flags(f)?; + Ok(MountRequest::NewMount(f & !general::MS_SILENT)) +} -fn validate_mount_flags(flags: i32) -> KResult { +fn normalize_mount_flags(flags: i32) -> KResult { let mut f = flags as u32; - // Linux accepts the legacy magic in the high 16 bits and strips it before - // interpreting the remaining mount flags. if (f & general::MS_MGC_MSK) == general::MS_MGC_VAL { f &= !general::MS_MGC_MSK; } - // MS_NOUSER is never allowed from userspace. if f & general::MS_NOUSER != 0 { return Err(KError::InvalidInput); } - // Reject operation types that aren't yet implemented. - // Each of these dispatches to a separate handler. - if f & UNSUPPORTED_MOUNT_OPERATION_FLAGS != 0 { + Ok(f) +} + +fn validate_new_mount_flags(flags: u32) -> KResult<()> { + if flags & !KNOWN_LINUX_NEW_MOUNT_FLAGS != 0 { + return Err(KError::InvalidInput); + } + if flags & !IMPLEMENTED_NEW_MOUNT_FLAGS != 0 { + return Err(KError::InvalidInput); + } + Ok(()) +} + +fn parse_propagation_request(flags: u32) -> KResult { + let propagation_flags = flags & PROPAGATION_FLAGS; + if propagation_flags.count_ones() != 1 { return Err(KError::InvalidInput); } - if f & !(SUPPORTED_MOUNT_FLAGS | IGNORED_COMPATIBILITY_MOUNT_FLAGS) != 0 { + let allowed_flags = propagation_flags | general::MS_REC | general::MS_SILENT; + if flags & !allowed_flags != 0 { return Err(KError::InvalidInput); } - Ok(f & !IGNORED_COMPATIBILITY_MOUNT_FLAGS) + let kind = match propagation_flags { + general::MS_SHARED => PropagationKind::Shared, + general::MS_PRIVATE => PropagationKind::Private, + general::MS_SLAVE => PropagationKind::Slave, + general::MS_UNBINDABLE => PropagationKind::Unbindable, + _ => return Err(KError::InvalidInput), + }; + Ok(MountRequest::Propagation { + kind, + recursive: flags & general::MS_REC != 0, + }) +} + +struct TmpfsMountOptions { + mode: NodePermission, +} + +impl Default for TmpfsMountOptions { + fn default() -> Self { + Self { + mode: NodePermission::from_bits_truncate(0o1777), + } + } +} + +fn parse_tmpfs_mount_data(data: UserConstPtr) -> KResult { + if data.is_null() { + return Ok(TmpfsMountOptions::default()); + } + + let data = data.cast::().load_string()?; + let mut options = TmpfsMountOptions::default(); + if data.is_empty() { + return Ok(options); + } + + for option in data.split(',') { + let Some(mode) = option.strip_prefix("mode=") else { + return Err(KError::InvalidInput); + }; + options.mode = parse_tmpfs_mode(mode)?; + } + Ok(options) +} + +fn parse_tmpfs_mode(mode: &str) -> KResult { + if mode.is_empty() { + return Err(KError::InvalidInput); + } + + let mode = u32::from_str_radix(mode, 8).map_err(|_| KError::InvalidInput)? & 0o7777; + Ok(NodePermission::from_bits_truncate(mode as u16)) } fn superblock_flags_from_sys_mount(flags: u32) -> u32 { @@ -119,7 +230,15 @@ pub fn sys_mount( flags: i32, data: UserConstPtr, ) -> KResult { - let flags = validate_mount_flags(flags)?; + let flags = match parse_mount_request(flags)? { + MountRequest::NewMount(flags) => flags, + MountRequest::BindRemount(_) | MountRequest::Remount(_) | MountRequest::Move(_) => { + return Err(KError::InvalidInput); + } + MountRequest::Bind { .. } | MountRequest::Propagation { .. } => { + return Err(KError::InvalidInput); + } + }; let source = if source.is_null() { None @@ -133,9 +252,7 @@ pub fn sys_mount( if fs_type != "tmpfs" { return Err(KError::NoSuchDevice); } - if !data.is_null() && !data.cast::().load_string()?.is_empty() { - return Err(KError::InvalidInput); - } + let tmpfs_options = parse_tmpfs_mount_data(data)?; let source = source.unwrap_or_else(|| "none".into()); let mount_flags = per_mount_flags(flags); @@ -145,6 +262,10 @@ pub fn sys_mount( let target = fs_context.resolve(target)?; target.check_is_dir()?; let fs = MemoryFs::new_with_name_and_flags("tmpfs", superblock_flags_from_sys_mount(flags)); + fs.root_dir().update_metadata(MetadataUpdate { + mode: Some(tmpfs_options.mode), + ..Default::default() + })?; target.mount_with_flags_and_source(&fs, mount_flags, source)?; Ok(0) @@ -232,7 +353,7 @@ mod tests { } #[def_test] - fn test_validate_mount_flags_accepts_supported_flags() { + fn test_parse_mount_request_accepts_supported_new_mount_flags() { let flags = (general::MS_RDONLY | general::MS_NOSUID | general::MS_NODEV @@ -243,36 +364,42 @@ mod tests { | general::MS_STRICTATIME | general::MS_NOSYMFOLLOW) as i32; - assert_eq!(super::validate_mount_flags(flags), Ok(flags as u32)); + assert_eq!( + super::parse_mount_request(flags), + Ok(super::MountRequest::NewMount(flags as u32)) + ); } #[def_test] - fn test_validate_mount_flags_ignores_compatibility_flags() { + fn test_parse_mount_request_ignores_compatibility_flags() { let flags = (general::MS_MGC_VAL | general::MS_SILENT | general::MS_RDONLY) as i32; - assert_eq!(super::validate_mount_flags(flags), Ok(general::MS_RDONLY)); + assert_eq!( + super::parse_mount_request(flags), + Ok(super::MountRequest::NewMount(general::MS_RDONLY)) + ); } #[def_test] - fn test_validate_mount_flags_rejects_unsupported_operation_flags() { + fn test_parse_mount_request_recognizes_operation_flags() { assert_eq!( - super::validate_mount_flags(general::MS_BIND as i32), - Err(kerrno::KError::InvalidInput) + super::parse_mount_request(general::MS_BIND as i32), + Ok(super::MountRequest::Bind { recursive: false }) ); assert_eq!( - super::validate_mount_flags(general::MS_REMOUNT as i32), - Err(kerrno::KError::InvalidInput) + super::parse_mount_request(general::MS_REMOUNT as i32), + Ok(super::MountRequest::Remount(general::MS_REMOUNT)) ); } #[def_test] - fn test_validate_mount_flags_rejects_unimplemented_plain_flags() { + fn test_parse_mount_request_rejects_unimplemented_plain_flags() { assert_eq!( - super::validate_mount_flags(general::MS_SYNCHRONOUS as i32), + super::parse_mount_request(general::MS_SYNCHRONOUS as i32), Err(kerrno::KError::InvalidInput) ); assert_eq!( - super::validate_mount_flags(general::MS_LAZYTIME as i32), + super::parse_mount_request(general::MS_LAZYTIME as i32), Err(kerrno::KError::InvalidInput) ); } -- Gitee From b22f68172030c0fb84a7394e3ef5919995e8f7c3 Mon Sep 17 00:00:00 2001 From: wangyining Date: Tue, 2 Jun 2026 17:26:06 +0800 Subject: [PATCH 4/4] fix ai review --- .../procfs/src/task_nodes/mounts.rs | 16 +++++++++----- posix/fs/src/mount.rs | 22 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/fs/filesystems/procfs/src/task_nodes/mounts.rs b/fs/filesystems/procfs/src/task_nodes/mounts.rs index c332e8b9f..7589ea360 100644 --- a/fs/filesystems/procfs/src/task_nodes/mounts.rs +++ b/fs/filesystems/procfs/src/task_nodes/mounts.rs @@ -161,14 +161,18 @@ enum EscapeMountField { fn escape_mount_field(value: &str, field: EscapeMountField) -> String { let mut result = String::new(); - for ch in value.chars() { - let should_escape_hash = matches!(field, EscapeMountField::Devname) && ch == '#'; - if matches!(ch, ' ' | '\t' | '\n' | '\\') || should_escape_hash { - push_octal_escape(&mut result, ch as u8); - } else { - result.push(ch); + let mut unescaped_start = 0; + + for (index, byte) in value.bytes().enumerate() { + let should_escape_hash = matches!(field, EscapeMountField::Devname) && byte == b'#'; + if matches!(byte, b' ' | b'\t' | b'\n' | b'\\') || should_escape_hash { + result.push_str(&value[unescaped_start..index]); + push_octal_escape(&mut result, byte); + unescaped_start = index + 1; } } + + result.push_str(&value[unescaped_start..]); result } diff --git a/posix/fs/src/mount.rs b/posix/fs/src/mount.rs index d2f8b31f7..01e381fd0 100644 --- a/posix/fs/src/mount.rs +++ b/posix/fs/src/mount.rs @@ -173,10 +173,9 @@ fn parse_tmpfs_mode(mode: &str) -> KResult { } fn superblock_flags_from_sys_mount(flags: u32) -> u32 { - let f = flags; let mut sb_flags = 0; - if f & general::MS_RDONLY != 0 { + if flags & general::MS_RDONLY != 0 { sb_flags |= ST_RDONLY; } sb_flags @@ -184,39 +183,38 @@ fn superblock_flags_from_sys_mount(flags: u32) -> u32 { /// Map `mount(2)` MS_* flags to per-mount [`MountFlags`]. fn per_mount_flags(flags: u32) -> MountFlags { - let f = flags; let mut mnt_flags = MountFlags::empty(); // Match Linux legacy mount behavior: relatime is the default unless the // caller explicitly requested noatime. MS_RELATIME is a bit in the same // unordered flag set, so it must not cancel a simultaneous MS_NOATIME. - if f & general::MS_NOATIME == 0 { + if flags & general::MS_NOATIME == 0 { mnt_flags |= MountFlags::RELATIME; } - if f & general::MS_RDONLY != 0 { + if flags & general::MS_RDONLY != 0 { mnt_flags |= MountFlags::RDONLY; } - if f & general::MS_NOSUID != 0 { + if flags & general::MS_NOSUID != 0 { mnt_flags |= MountFlags::NOSUID; } - if f & general::MS_NODEV != 0 { + if flags & general::MS_NODEV != 0 { mnt_flags |= MountFlags::NODEV; } - if f & general::MS_NOEXEC != 0 { + if flags & general::MS_NOEXEC != 0 { mnt_flags |= MountFlags::NOEXEC; } - if f & general::MS_NOATIME != 0 { + if flags & general::MS_NOATIME != 0 { mnt_flags |= MountFlags::NOATIME; } - if f & general::MS_NODIRATIME != 0 { + if flags & general::MS_NODIRATIME != 0 { mnt_flags |= MountFlags::NODIRATIME; } // STRICTATIME takes priority — clear both RELATIME and NOATIME. - if f & general::MS_STRICTATIME != 0 { + if flags & general::MS_STRICTATIME != 0 { mnt_flags &= !(MountFlags::RELATIME | MountFlags::NOATIME); } - if f & general::MS_NOSYMFOLLOW != 0 { + if flags & general::MS_NOSYMFOLLOW != 0 { mnt_flags |= MountFlags::NOSYMFOLLOW; } mnt_flags -- Gitee