diff --git a/kernel/src/fs/cgroupfs/fs.rs b/kernel/src/fs/cgroupfs/fs.rs index cbc142d01..2adeb950b 100644 --- a/kernel/src/fs/cgroupfs/fs.rs +++ b/kernel/src/fs/cgroupfs/fs.rs @@ -11,7 +11,10 @@ use crate::{ fs::{ cgroupfs::systree_node::CgroupSystem, registry::{FsProperties, FsType}, - utils::{systree_inode::SysTreeInodeTy, FileSystem, FsFlags, Inode, SuperBlock}, + utils::{ + systree_inode::SysTreeInodeTy, FileSystem, FsEventSubscriberStats, FsFlags, Inode, + SuperBlock, + }, Result, }, prelude::*, @@ -21,6 +24,7 @@ use crate::{ pub(super) struct CgroupFs { sb: SuperBlock, root: Arc, + fs_event_subscriber_stats: FsEventSubscriberStats, } // Magic number for cgroupfs v2 (taken from Linux) @@ -43,6 +47,7 @@ impl CgroupFs { Arc::new(Self { sb, root: root_inode, + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) } } @@ -64,6 +69,10 @@ impl FileSystem for CgroupFs { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } pub(super) struct CgroupFsType; diff --git a/kernel/src/fs/cgroupfs/inode.rs b/kernel/src/fs/cgroupfs/inode.rs index 51f8a3c74..45175451f 100644 --- a/kernel/src/fs/cgroupfs/inode.rs +++ b/kernel/src/fs/cgroupfs/inode.rs @@ -8,6 +8,7 @@ use super::fs::CgroupFs; use crate::{ fs::{ cgroupfs::CgroupNode, + notify::FsEventPublisher, path::{is_dot, is_dotdot}, utils::{ systree_inode::{SysTreeInodeTy, SysTreeNodeKind}, @@ -23,6 +24,8 @@ pub(super) struct CgroupInode { node_kind: SysTreeNodeKind, /// The metadata of this inode. metadata: Metadata, + /// FS event publisher. + fs_event_publisher: FsEventPublisher, /// The file mode (permissions) of this inode, protected by a lock. mode: RwLock, /// Weak reference to the parent inode. @@ -44,6 +47,7 @@ impl SysTreeInodeTy for CgroupInode { Arc::new_cyclic(|this| Self { node_kind, metadata, + fs_event_publisher: FsEventPublisher::new(), mode: RwLock::new(mode), parent, this: this.clone(), @@ -58,6 +62,10 @@ impl SysTreeInodeTy for CgroupInode { &self.metadata } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + fn mode(&self) -> Result { Ok(*self.mode.read()) } diff --git a/kernel/src/fs/configfs/fs.rs b/kernel/src/fs/configfs/fs.rs index 3038a3141..908d3f2e6 100644 --- a/kernel/src/fs/configfs/fs.rs +++ b/kernel/src/fs/configfs/fs.rs @@ -11,7 +11,10 @@ use crate::{ fs::{ configfs::systree_node::ConfigRootNode, registry::{FsProperties, FsType}, - utils::{systree_inode::SysTreeInodeTy, FileSystem, FsFlags, Inode, SuperBlock}, + utils::{ + systree_inode::SysTreeInodeTy, FileSystem, FsEventSubscriberStats, FsFlags, Inode, + SuperBlock, + }, Result, }, prelude::*, @@ -26,6 +29,7 @@ use crate::{ pub struct ConfigFs { sb: SuperBlock, root: Arc, + fs_event_subscriber_stats: FsEventSubscriberStats, } // Magic number for `ConfigFs` (taken from Linux). @@ -48,6 +52,7 @@ impl ConfigFs { Arc::new(Self { sb, root: root_inode, + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) } } @@ -69,6 +74,10 @@ impl FileSystem for ConfigFs { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } pub(super) struct ConfigFsType; diff --git a/kernel/src/fs/configfs/inode.rs b/kernel/src/fs/configfs/inode.rs index 85df8cdbf..e9f9f2aa1 100644 --- a/kernel/src/fs/configfs/inode.rs +++ b/kernel/src/fs/configfs/inode.rs @@ -7,6 +7,7 @@ use ostd::sync::RwLock; use crate::{ fs::{ configfs::fs::ConfigFs, + notify::FsEventPublisher, utils::{ systree_inode::{SysTreeInodeTy, SysTreeNodeKind}, FileSystem, Inode, InodeMode, Metadata, @@ -21,6 +22,8 @@ pub struct ConfigInode { node_kind: SysTreeNodeKind, /// The metadata of this inode. metadata: Metadata, + /// FS event publisher. + fs_event_publisher: FsEventPublisher, /// The file mode (permissions) of this inode, protected by a lock. mode: RwLock, /// Weak reference to the parent inode. @@ -42,6 +45,7 @@ impl SysTreeInodeTy for ConfigInode { Arc::new_cyclic(|this| Self { node_kind, metadata, + fs_event_publisher: FsEventPublisher::new(), mode: RwLock::new(mode), parent, this: this.clone(), @@ -69,6 +73,10 @@ impl SysTreeInodeTy for ConfigInode { &self.parent } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + fn this(&self) -> Arc { self.this.upgrade().expect("Weak ref invalid") } diff --git a/kernel/src/fs/devpts/mod.rs b/kernel/src/fs/devpts/mod.rs index daf0895e1..f88366ca2 100644 --- a/kernel/src/fs/devpts/mod.rs +++ b/kernel/src/fs/devpts/mod.rs @@ -9,15 +9,16 @@ use id_alloc::IdAlloc; pub use self::ptmx::Ptmx; use self::slave::PtySlaveInode; -use super::utils::{InodeIo, MknodType, StatusFlags}; +use super::utils::{MknodType, StatusFlags}; use crate::{ device::PtyMaster, fs::{ device::{Device, DeviceType}, + notify::FsEventPublisher, registry::{FsProperties, FsType}, utils::{ - mkmod, DirEntryVecExt, DirentVisitor, FileSystem, FsFlags, Inode, InodeMode, InodeType, - Metadata, SuperBlock, NAME_MAX, + mkmod, DirEntryVecExt, DirentVisitor, FileSystem, FsEventSubscriberStats, FsFlags, + Inode, InodeIo, InodeMode, InodeType, Metadata, SuperBlock, NAME_MAX, }, }, prelude::*, @@ -47,6 +48,7 @@ pub struct DevPts { sb: SuperBlock, root: Arc, index_alloc: Mutex, + fs_event_subscriber_stats: FsEventSubscriberStats, this: Weak, } @@ -56,6 +58,7 @@ impl DevPts { sb: SuperBlock::new(DEVPTS_MAGIC, BLOCK_SIZE, NAME_MAX), root: RootInode::new(weak_self.clone()), index_alloc: Mutex::new(IdAlloc::with_capacity(MAX_PTY_NUM)), + fs_event_subscriber_stats: FsEventSubscriberStats::new(), this: weak_self.clone(), }) } @@ -109,6 +112,10 @@ impl FileSystem for DevPts { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } struct DevPtsType; @@ -144,6 +151,7 @@ struct RootInode { ptmx: Arc, slaves: RwLock)>>, metadata: RwLock, + fs_event_publisher: FsEventPublisher, fs: Weak, } @@ -153,6 +161,7 @@ impl RootInode { ptmx: Ptmx::new(fs.clone()), slaves: RwLock::new(SlotVec::new()), metadata: RwLock::new(Metadata::new_dir(ROOT_INO, mkmod!(a+rx, u+w), BLOCK_SIZE)), + fs_event_publisher: FsEventPublisher::new(), fs, }) } @@ -191,6 +200,10 @@ impl Inode for RootInode { *self.metadata.read() } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + fn ino(&self) -> u64 { self.metadata.read().ino as _ } diff --git a/kernel/src/fs/devpts/ptmx.rs b/kernel/src/fs/devpts/ptmx.rs index d148efb49..770433640 100644 --- a/kernel/src/fs/devpts/ptmx.rs +++ b/kernel/src/fs/devpts/ptmx.rs @@ -5,7 +5,8 @@ use device_id::{DeviceId, MajorId, MinorId}; use super::*; use crate::fs::{ inode_handle::FileIo, - utils::{AccessMode, StatusFlags}, + notify::FsEventPublisher, + utils::{AccessMode, InodeIo, StatusFlags}, }; /// Same major number with Linux. @@ -20,6 +21,7 @@ const PTMX_MINOR_NUM: u32 = 2; pub struct Ptmx { inner: Inner, metadata: RwLock, + fs_event_publisher: FsEventPublisher, } #[derive(Clone)] @@ -36,6 +38,7 @@ impl Ptmx { &inner, )), inner, + fs_event_publisher: FsEventPublisher::new(), }) } @@ -79,6 +82,10 @@ impl Inode for Ptmx { *self.metadata.read() } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + fn ino(&self) -> u64 { self.metadata.read().ino as _ } diff --git a/kernel/src/fs/devpts/slave.rs b/kernel/src/fs/devpts/slave.rs index 088e3a006..b38620094 100644 --- a/kernel/src/fs/devpts/slave.rs +++ b/kernel/src/fs/devpts/slave.rs @@ -8,7 +8,8 @@ use crate::{ device::PtySlave, fs::{ inode_handle::FileIo, - utils::{AccessMode, StatusFlags}, + notify::FsEventPublisher, + utils::{AccessMode, InodeIo, StatusFlags}, }, }; @@ -19,6 +20,7 @@ const SLAVE_MAJOR_NUM: u32 = 3; pub struct PtySlaveInode { device: Arc, metadata: RwLock, + fs_event_publisher: FsEventPublisher, fs: Weak, } @@ -32,6 +34,7 @@ impl PtySlaveInode { device.as_ref(), )), device, + fs_event_publisher: FsEventPublisher::new(), fs, }) } @@ -78,6 +81,10 @@ impl Inode for PtySlaveInode { *self.metadata.read() } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + fn ino(&self) -> u64 { self.metadata.read().ino as _ } diff --git a/kernel/src/fs/exfat/fs.rs b/kernel/src/fs/exfat/fs.rs index cebd655a1..9ce77d242 100644 --- a/kernel/src/fs/exfat/fs.rs +++ b/kernel/src/fs/exfat/fs.rs @@ -26,7 +26,10 @@ use crate::{ fs::{ exfat::{constants::*, inode::Ino}, registry::{FsProperties, FsType}, - utils::{CachePage, FileSystem, FsFlags, Inode, PageCache, PageCacheBackend, SuperBlock}, + utils::{ + CachePage, FileSystem, FsEventSubscriberStats, FsFlags, Inode, PageCache, + PageCacheBackend, SuperBlock, + }, }, prelude::*, }; @@ -53,6 +56,8 @@ pub struct ExfatFs { //A global lock, We need to hold the mutex before accessing bitmap or inode, otherwise there will be deadlocks. mutex: Mutex<()>, + + fs_event_subscriber_stats: FsEventSubscriberStats, } const FAT_LRU_CACHE_SIZE: usize = 1024; @@ -80,6 +85,7 @@ impl ExfatFs { )), meta_cache: PageCache::with_capacity(fs_size, weak_self.clone() as _).unwrap(), mutex: Mutex::new(()), + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }); // TODO: if the main superblock is corrupted, should we load the backup? @@ -417,6 +423,10 @@ impl FileSystem for ExfatFs { fn sb(&self) -> SuperBlock { SuperBlock::new(BOOT_SIGNATURE as u64, self.sector_size(), MAX_NAME_LENGTH) } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } #[derive(Clone, Debug, Default)] diff --git a/kernel/src/fs/exfat/inode.rs b/kernel/src/fs/exfat/inode.rs index fb9afe6d5..e580ee08e 100644 --- a/kernel/src/fs/exfat/inode.rs +++ b/kernel/src/fs/exfat/inode.rs @@ -27,6 +27,7 @@ use super::{ use crate::{ fs::{ exfat::{dentry::ExfatDentryIterator, fat::ExfatChain, fs::ExfatFs}, + notify::FsEventPublisher, path::{is_dot, is_dot_or_dotdot, is_dotdot}, utils::{ mkmod, CachePage, DirentVisitor, Extension, Inode, InodeIo, InodeMode, InodeType, @@ -78,6 +79,7 @@ impl FatAttr { pub struct ExfatInode { inner: RwMutex, extension: Extension, + fs_event_publisher: FsEventPublisher, } #[derive(Debug)] @@ -862,6 +864,7 @@ impl ExfatInode { page_cache: PageCache::with_capacity(size, weak_self.clone() as _).unwrap(), }), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), }); let inner = inode.inner.upread(); @@ -972,6 +975,7 @@ impl ExfatInode { page_cache: PageCache::with_capacity(size, weak_self.clone() as _).unwrap(), }), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), }); if matches!(inode_type, InodeType::Dir) { @@ -1735,4 +1739,8 @@ impl Inode for ExfatInode { fn extension(&self) -> Option<&Extension> { Some(&self.extension) } + + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } } diff --git a/kernel/src/fs/ext2/fs.rs b/kernel/src/fs/ext2/fs.rs index 2188a272d..16281fcc3 100644 --- a/kernel/src/fs/ext2/fs.rs +++ b/kernel/src/fs/ext2/fs.rs @@ -11,7 +11,7 @@ use super::{ }; use crate::fs::{ registry::{FsProperties, FsType}, - utils::{FileSystem, FsFlags}, + utils::{FileSystem, FsEventSubscriberStats, FsFlags}, }; /// The root inode number. @@ -28,6 +28,7 @@ pub struct Ext2 { inode_size: usize, block_size: usize, group_descriptors_segment: USegment, + fs_event_subscriber_stats: FsEventSubscriberStats, self_ref: Weak, } @@ -98,6 +99,7 @@ impl Ext2 { block_device, super_block: RwMutex::new(Dirty::new(super_block)), group_descriptors_segment, + fs_event_subscriber_stats: FsEventSubscriberStats::new(), self_ref: weak_ref.clone(), }); Ok(ext2) @@ -437,6 +439,10 @@ impl Ext2 { fn block_idx(&self, bid: Ext2Bid) -> Ext2Bid { bid % self.blocks_per_group } + + pub fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } pub(super) struct Ext2Type; diff --git a/kernel/src/fs/ext2/impl_for_vfs/fs.rs b/kernel/src/fs/ext2/impl_for_vfs/fs.rs index ded2ab844..09a003207 100644 --- a/kernel/src/fs/ext2/impl_for_vfs/fs.rs +++ b/kernel/src/fs/ext2/impl_for_vfs/fs.rs @@ -5,7 +5,7 @@ use ostd::sync::RwMutexReadGuard; use crate::{ fs::{ ext2::{utils::Dirty, Ext2, SuperBlock as Ext2SuperBlock, MAGIC_NUM as EXT2_MAGIC}, - utils::{FileSystem, Inode, SuperBlock, NAME_MAX}, + utils::{FileSystem, FsEventSubscriberStats, Inode, SuperBlock, NAME_MAX}, }, prelude::*, }; @@ -30,6 +30,10 @@ impl FileSystem for Ext2 { fn sb(&self) -> SuperBlock { SuperBlock::from(self.super_block()) } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + self.fs_event_subscriber_stats() + } } impl From>> for SuperBlock { diff --git a/kernel/src/fs/ext2/impl_for_vfs/inode.rs b/kernel/src/fs/ext2/impl_for_vfs/inode.rs index a66b861f6..6be3af678 100644 --- a/kernel/src/fs/ext2/impl_for_vfs/inode.rs +++ b/kernel/src/fs/ext2/impl_for_vfs/inode.rs @@ -5,6 +5,7 @@ use core::time::Duration; use crate::{ fs::{ ext2::{FilePerm, Inode as Ext2Inode}, + notify::FsEventPublisher, utils::{ DirentVisitor, Extension, FallocMode, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, MknodType, StatusFlags, SymbolicLink, XattrName, XattrNamespace, @@ -200,6 +201,10 @@ impl Inode for Ext2Inode { Some(self.extension()) } + fn fs_event_publisher(&self) -> &FsEventPublisher { + self.fs_event_publisher() + } + fn set_xattr( &self, name: XattrName, diff --git a/kernel/src/fs/ext2/inode.rs b/kernel/src/fs/ext2/inode.rs index 3d306b36f..754938178 100644 --- a/kernel/src/fs/ext2/inode.rs +++ b/kernel/src/fs/ext2/inode.rs @@ -20,6 +20,7 @@ use super::{ }; use crate::{ fs::{ + notify::FsEventPublisher, path::{is_dot, is_dot_or_dotdot, is_dotdot}, utils::{ Extension, FallocMode, Inode as _, InodeMode, Metadata, Permission, XattrName, @@ -43,6 +44,7 @@ pub struct Inode { inner: RwMutex, fs: Weak, extension: Extension, + fs_event_publisher: FsEventPublisher, xattr: Option, } @@ -63,6 +65,7 @@ impl Inode { inner: RwMutex::new(InodeInner::new(desc, weak_self.clone(), fs.clone())), fs, extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), }) } @@ -107,6 +110,10 @@ impl Inode { } } + pub fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + pub fn resize(&self, new_size: usize) -> Result<()> { if self.type_ == InodeType::Dir { return_errno!(Errno::EISDIR); diff --git a/kernel/src/fs/file_handle.rs b/kernel/src/fs/file_handle.rs index a0524e36b..40f131f0a 100644 --- a/kernel/src/fs/file_handle.rs +++ b/kernel/src/fs/file_handle.rs @@ -8,7 +8,7 @@ use core::fmt::Display; use ostd::io::IoMem; -use super::inode_handle::InodeHandle; +use super::{inode_handle::InodeHandle, path::Path}; use crate::{ fs::{ file_table::FdFlags, @@ -143,6 +143,12 @@ impl dyn FileLike { .ok_or_else(|| Error::with_message(Errno::ENOTSOCK, "the file is not a socket")) } + pub fn path(&self) -> Option<&Path> { + self.as_inode_handle_or_err() + .ok() + .map(|inode_handle| inode_handle.path()) + } + pub fn as_inode_handle_or_err(&self) -> Result<&InodeHandle> { self.downcast_ref().ok_or_else(|| { Error::with_message(Errno::EINVAL, "the file is not related to an inode") diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs index 0aa41d6b5..5b2e003ca 100644 --- a/kernel/src/fs/mod.rs +++ b/kernel/src/fs/mod.rs @@ -11,6 +11,7 @@ pub mod file_handle; pub mod file_table; pub mod fs_resolver; pub mod inode_handle; +pub mod notify; pub mod overlayfs; pub mod path; pub mod pipe; diff --git a/kernel/src/fs/notify/mod.rs b/kernel/src/fs/notify/mod.rs new file mode 100644 index 000000000..a2caa0ec5 --- /dev/null +++ b/kernel/src/fs/notify/mod.rs @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{sync::Arc, vec::Vec}; +use core::{ + any::Any, + sync::atomic::{AtomicBool, AtomicU32, Ordering}, +}; + +use atomic_integer_wrapper::define_atomic_version_of_integer_like_type; +use bitflags::bitflags; +use ostd::sync::RwLock; + +use crate::{ + fs::{file_handle::FileLike, path::Path, utils::AccessMode}, + prelude::*, +}; + +use super::utils::{Inode, InodeType}; + +/// Publishes filesystem events to subscribers. +/// +/// Each inode has an associated `FsEventPublisher` that maintains a list of +/// subscribers interested in filesystem events. When an event occurs, the publisher +/// notifies all subscribers whose interesting events match the event. +pub struct FsEventPublisher { + /// List of FS event subscribers. + subscribers: RwLock>>, + /// All interesting FS event types (aggregated from all subscribers). + all_interesting_events: AtomicFsEvents, + /// Whether this publisher still accepts new subscribers. + accepts_new_subscribers: AtomicBool, +} + +impl Debug for FsEventPublisher { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let subscribers = self.subscribers.read(); + write!( + f, + "FsEventPublisher: num_subscribers: {}", + subscribers.len() + )?; + Ok(()) + } +} + +impl Default for FsEventPublisher { + fn default() -> Self { + Self::new() + } +} + +impl FsEventPublisher { + pub fn new() -> Self { + Self { + subscribers: RwLock::new(Vec::new()), + all_interesting_events: AtomicFsEvents::new(FsEvents::empty()), + accepts_new_subscribers: AtomicBool::new(true), + } + } + + /// Adds a subscriber to this publisher. + pub fn add_subscriber(&self, subscriber: Arc) -> bool { + if !self.accepts_new_subscribers.load(Ordering::Acquire) { + return false; + } + let mut subscribers = self.subscribers.write(); + let current = self.all_interesting_events.load(Ordering::Relaxed); + self.all_interesting_events + .store(current | subscriber.interesting_events(), Ordering::Relaxed); + subscribers.push(subscriber); + true + } + + /// Removes a subscriber from this publisher. + pub fn remove_subscriber(&self, subscriber: &Arc) -> bool { + let mut subscribers = self.subscribers.write(); + let orig_len = subscribers.len(); + subscribers.retain(|m| !Arc::ptr_eq(m, subscriber)); + let removed = subscribers.len() != orig_len; + if removed { + subscriber.deliver_event(FsEvents::IN_IGNORED, None); + self.recalc_interesting_events(&subscribers); + } + removed + } + + /// Removes all subscribers from this publisher. + pub fn remove_all_subscribers(&self) -> usize { + let mut subscribers = self.subscribers.write(); + for subscriber in subscribers.iter() { + subscriber.deliver_event(FsEvents::IN_IGNORED, None); + } + let num_subscribers = subscribers.len(); + subscribers.clear(); + + self.all_interesting_events + .store(FsEvents::empty(), Ordering::Relaxed); + + num_subscribers + } + + /// Forbids new subscribers from attaching to this publisher. + pub fn disable_new_subscribers(&self) { + self.accepts_new_subscribers.store(false, Ordering::Release); + } + + /// Broadcasts an event to all the subscribers of this publisher. + pub fn publish_event(&self, events: FsEvents, name: Option) { + let interesting = self.all_interesting_events.load(Ordering::Relaxed); + if !interesting.intersects(events) { + return; + } + + let subscribers = self.subscribers.read(); + for subscriber in subscribers.iter() { + subscriber.deliver_event(events, name.clone()); + } + } + + /// Recalculates the aggregated interesting events from all subscribers. + fn recalc_interesting_events(&self, subscribers: &[Arc]) { + let mut new_events = FsEvents::empty(); + for subscriber in subscribers.iter() { + new_events |= subscriber.interesting_events(); + } + self.all_interesting_events + .store(new_events, Ordering::Relaxed); + } + + /// Updates the aggregated events when a subscriber's interesting events change. + pub fn update_subscriber_events(&self) { + let subscribers = self.subscribers.read(); + let mut new_events = FsEvents::empty(); + for subscriber in subscribers.iter() { + new_events |= subscriber.interesting_events(); + } + self.all_interesting_events + .store(new_events, Ordering::Relaxed); + } + + /// Finds a subscriber and applies an action if found. + /// + /// The matcher should return `Some(T)` if the subscriber matches and processing + /// should stop, or `None` to continue searching. + pub fn find_subscriber_and_process(&self, mut matcher: F) -> Option + where + F: FnMut(&Arc) -> Option, + { + let subscribers = self.subscribers.read(); + for subscriber in subscribers.iter() { + if let Some(result) = matcher(subscriber) { + return Some(result); + } + } + None + } +} + +/// Represents a subscriber to filesystem events on an `FsEventPublisher`. +/// +/// A subscriber receives notifications from a publisher when filesystem events occur +/// that match the subscriber's interesting events. The subscriber specifies which events +/// it is interested in using `FsEvents`, which define the types of events (e.g., +/// read, write, modify, delete) the subscriber wants to be notified about. When an event +/// occurs, the publisher (attached to an inode) broadcasts it to all subscribers whose +/// interesting events match the event type. +pub trait FsEventSubscriber: Any + Send + Sync { + /// Delivers a filesystem event notification to the subscriber. + /// + /// Invariant: This method must not sleep or perform blocking operations. The publisher + /// may hold a spin lock when calling this method. + fn deliver_event(&self, events: FsEvents, name: Option); + /// Returns the events that this subscriber is interested in. + fn interesting_events(&self) -> FsEvents; +} + +bitflags! { + /// Represents filesystem events that have occurred. + /// + /// These events are used to notify subscribers about specific filesystem actions. + /// Subscribers specify which events they are interested in to filter and receive + /// only the events they care about. + pub struct FsEvents: u32 { + const ACCESS = 0x00000001; // File was accessed + const MODIFY = 0x00000002; // File was modified + const ATTRIB = 0x00000004; // Metadata changed + const CLOSE_WRITE = 0x00000008; // Writable file was closed + const CLOSE_NOWRITE = 0x00000010; // Unwritable file closed + const OPEN = 0x00000020; // File was opened + const MOVED_FROM = 0x00000040; // File was moved from X + const MOVED_TO = 0x00000080; // File was moved to Y + const CREATE = 0x00000100; // Subfile was created + const DELETE = 0x00000200; // Subfile was deleted + const DELETE_SELF = 0x00000400; // Self was deleted + const MOVE_SELF = 0x00000800; // Self was moved + const OPEN_EXEC = 0x00001000; // File was opened for exec + const UNMOUNT = 0x00002000; // Inode on umount fs + const Q_OVERFLOW = 0x00004000; // Event queued overflowed + const ERROR = 0x00008000; // Filesystem Error (fanotify) + const IN_IGNORED = 0x00008000; // Last inotify event here (inotify) + const OPEN_PERM = 0x00010000; // Open event in a permission hook + const ACCESS_PERM = 0x00020000; // Access event in a permissions hook + const OPEN_EXEC_PERM = 0x00040000; // Open/exec event in a permission hook + const EVENT_ON_CHILD = 0x08000000; // Set on inode mark that cares about things that happen to its children. + const RENAME = 0x10000000; // File was renamed + const DN_MULTISHOT = 0x20000000; // dnotify multishot + const ISDIR = 0x40000000; // Event occurred against dir + } +} + +impl From for FsEvents { + fn from(value: u32) -> Self { + Self::from_bits_truncate(value) + } +} + +impl From for u32 { + fn from(value: FsEvents) -> Self { + value.bits() + } +} + +define_atomic_version_of_integer_like_type!(FsEvents, { + #[derive(Debug)] + pub(super) struct AtomicFsEvents(AtomicU32); +}); + +/// Notifies that a file was accessed. +pub fn on_access(file: &Arc) { + // TODO: Check fmode flags (FMODE_NONOTIFY, FMODE_NONOTIFY_PERM). + if let Some(path) = file.path() { + if !path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_parent(path, FsEvents::ACCESS, path.effective_name()); + } +} + +/// Notifies that a file was modified. +pub fn on_modify(file: &Arc) { + // TODO: Check fmode flags (FMODE_NONOTIFY, FMODE_NONOTIFY_PERM). + let Some(path) = file.path() else { + return; + }; + + if !path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_parent(path, FsEvents::MODIFY, path.effective_name()); +} + +/// Notifies that a path's content was changed. +pub fn on_change(path: &Path) { + if !path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_parent(path, FsEvents::MODIFY, path.effective_name()); +} + +/// Notifies that a file was deleted from a directory. +pub fn on_delete( + dir_inode: &Arc, + inode: &Arc, + name: impl FnOnce() -> String, +) { + if !dir_inode + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + + let name = name(); + if inode.type_() == InodeType::Dir { + notify_inode(dir_inode, FsEvents::DELETE | FsEvents::ISDIR, Some(name)) + } else { + notify_inode(dir_inode, FsEvents::DELETE, Some(name)) + } +} + +/// Notifies that an inode's link count changed. +pub fn on_link_count(inode: &Arc) { + if !inode.fs().fs_event_subscriber_stats().has_any_subscribers() { + return; + } + notify_inode(inode, FsEvents::ATTRIB, None); +} + +/// Notifies that an inode was removed (link count reached 0). +pub fn on_inode_removed(inode: &Arc) { + if !inode.fs().fs_event_subscriber_stats().has_any_subscribers() { + return; + } + notify_inode(inode, FsEvents::DELETE_SELF, None); +} + +/// Notifies that a file was linked to a directory. +pub fn on_link(dir_inode: &Arc, inode: &Arc, name: String) { + if !dir_inode + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_inode(inode, FsEvents::ATTRIB, None); + notify_inode(dir_inode, FsEvents::CREATE, Some(name)); +} + +/// Notifies that a directory was created. +pub fn on_mkdir(dir_path: &Path, name: String) { + if !dir_path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_inode( + dir_path.inode(), + FsEvents::CREATE | FsEvents::ISDIR, + Some(name), + ); +} + +/// Notifies that a file was created. +pub fn on_create(file_path: &Path, name: String) { + if !file_path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_inode(file_path.inode(), FsEvents::CREATE, Some(name)); +} + +/// Notifies that a file was opened. +pub fn on_open(file: &Arc) { + // TODO: Check fmode flags (FMODE_NONOTIFY, FMODE_NONOTIFY_PERM). + if let Some(path) = file.path() { + if !path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_parent(path, FsEvents::OPEN, path.effective_name()); + } +} + +/// Notifies that a file was closed. +pub fn on_close(file: &Arc) { + // TODO: Check fmode flags (FMODE_NONOTIFY, FMODE_NONOTIFY_PERM). + if let Some(path) = file.path() { + if !path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + let events = match file.access_mode() { + AccessMode::O_RDONLY => FsEvents::CLOSE_NOWRITE, + _ => FsEvents::CLOSE_WRITE, + }; + notify_parent(path, events, path.effective_name()); + } +} + +/// Notifies that a file's attributes changed. +pub fn on_attr_change(path: &Path) { + if !path + .inode() + .fs() + .fs_event_subscriber_stats() + .has_any_subscribers() + { + return; + } + notify_parent(path, FsEvents::ATTRIB, path.effective_name()); +} + +/// Notifies a path's parent and the path itself about filesystem events. +/// +/// If the parent is watching or if subscribers have registered interesting events with +/// parent and name information, notifies the parent with child name info. +/// Otherwise, notifies only the child without name information. +/// This function is already called after filesystem checking in the callers. +fn notify_parent(path: &Path, mut events: FsEvents, name: String) { + if path.inode().type_() == InodeType::Dir { + events |= FsEvents::ISDIR; + } + + let parent = path.effective_parent(); + if let Some(parent) = parent { + notify_inode(parent.inode(), events, Some(name)); + } + notify_inode(path.inode(), events, None); +} + +/// Sends a filesystem notification event to all subscribers of an inode. +/// +/// This is the main entry point for fsnotify. The VFS layer calls hook-specific +/// functions in `fs/notify/`, which then call this function to broadcast events +/// to all registered subscribers through the inode's publisher. +fn notify_inode(inode: &Arc, events: FsEvents, name: Option) { + inode.fs_event_publisher().publish_event(events, name); +} diff --git a/kernel/src/fs/overlayfs/fs.rs b/kernel/src/fs/overlayfs/fs.rs index 34e1f709f..dee180a9d 100644 --- a/kernel/src/fs/overlayfs/fs.rs +++ b/kernel/src/fs/overlayfs/fs.rs @@ -21,12 +21,14 @@ use crate::{ fs::{ fs_resolver::FsPath, inode_handle::FileIo, + notify::FsEventPublisher, path::Path, registry::{FsProperties, FsType}, utils::{ - mkmod, AccessMode, DirentCounter, DirentVisitor, FallocMode, FileSystem, FsFlags, - Inode, InodeIo, InodeMode, InodeType, Metadata, MknodType, StatusFlags, SuperBlock, - SymbolicLink, XattrName, XattrNamespace, XattrSetFlags, NAME_MAX, XATTR_VALUE_MAX_LEN, + mkmod, AccessMode, DirentCounter, DirentVisitor, FallocMode, FileSystem, + FsEventSubscriberStats, FsFlags, Inode, InodeIo, InodeMode, InodeType, Metadata, + MknodType, StatusFlags, SuperBlock, SymbolicLink, XattrName, XattrNamespace, + XattrSetFlags, NAME_MAX, XATTR_VALUE_MAX_LEN, }, }, prelude::*, @@ -52,6 +54,8 @@ pub struct OverlayFs { sb: OverlaySB, /// Unique inode number generator. next_ino: AtomicU64, + /// FS event subscriber stats for this file system. + fs_event_subscriber_stats: FsEventSubscriberStats, /// Weak self reference. self_: Weak, } @@ -87,6 +91,8 @@ struct OverlayInode { /// This field is used to build hierarchical upper inodes. /// The lock is intended to implement `rename`. name_upon_creation: SpinLock, + /// FS event publisher. + fs_event_publisher: FsEventPublisher, /// The parent inode. `None` for root inode. parent: Option>, /// The mutable upper regular inode. @@ -126,6 +132,7 @@ impl OverlayFs { config: OverlayConfig::default(), sb: OverlaySB, next_ino: AtomicU64::new(0), + fs_event_subscriber_stats: FsEventSubscriberStats::new(), self_: weak.clone(), })) } @@ -166,6 +173,7 @@ impl FileSystem for OverlayFs { ino, type_: InodeType::Dir, name_upon_creation: SpinLock::new(String::from("")), + fs_event_publisher: FsEventPublisher::new(), parent: None, upper: Mutex::new(Some(upper_inode)), upper_is_opaque: false, @@ -190,6 +198,10 @@ impl FileSystem for OverlayFs { // TODO: Fill the super block with valid field values. SuperBlock::new(OVERLAY_FS_MAGIC, BLOCK_SIZE, NAME_MAX) } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } impl OverlayFs { @@ -267,6 +279,7 @@ impl OverlayInode { ino: new_upper.ino(), type_, name_upon_creation: SpinLock::new(String::from(name)), + fs_event_publisher: FsEventPublisher::new(), parent: Some(self.self_.upgrade().unwrap()), upper: Mutex::new(Some(new_upper)), upper_is_opaque, @@ -439,6 +452,10 @@ impl OverlayInode { self.type_ } + pub fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + pub fn page_cache(&self) -> Option> { let _ = self.get_top_valid_inode().page_cache()?; // Do copy-up for the potential memory mapping operations @@ -676,6 +693,7 @@ impl OverlayInode { ino, type_: type_.unwrap(), name_upon_creation: SpinLock::new(String::from(name)), + fs_event_publisher: FsEventPublisher::new(), parent: Some(self.self_.upgrade().unwrap()), upper: Mutex::new(upper_child), upper_is_opaque, @@ -916,6 +934,7 @@ impl Inode for OverlayInode { fn size(&self) -> usize; fn resize(&self, new_size: usize) -> Result<()>; fn metadata(&self) -> Metadata; + fn fs_event_publisher(&self) -> &FsEventPublisher; fn ino(&self) -> u64; fn type_(&self) -> InodeType; fn mode(&self) -> Result; diff --git a/kernel/src/fs/path/dentry.rs b/kernel/src/fs/path/dentry.rs index b3b2c004e..e84aa50b8 100644 --- a/kernel/src/fs/path/dentry.rs +++ b/kernel/src/fs/path/dentry.rs @@ -5,8 +5,9 @@ use core::sync::atomic::{AtomicU32, Ordering}; use hashbrown::HashMap; use ostd::sync::RwMutexWriteGuard; -use super::is_dot_or_dotdot; +use super::{is_dot, is_dot_or_dotdot, is_dotdot}; use crate::{ + fs, fs::utils::{Inode, InodeMode, InodeType, MknodType}, prelude::*, }; @@ -229,8 +230,9 @@ impl Dentry { ); if dentry.is_dentry_cacheable() { - children.upgrade().insert(name, dentry.clone()); + children.upgrade().insert(name.clone(), dentry.clone()); } + fs::notify::on_link(dentry.parent().unwrap().inode(), dentry.inode(), name); Ok(()) } @@ -240,13 +242,39 @@ impl Dentry { return_errno!(Errno::ENOTDIR); } + if is_dot_or_dotdot(name) { + return_errno_with_message!(Errno::EINVAL, "unlink on . or .."); + } + let children = self.children.upread(); children.check_mountpoint(name)?; - self.inode.unlink(name)?; - let mut children = children.upgrade(); - children.delete(name); + let cached_child = children.delete(name); + + let child_inode = match cached_child { + Some(child) => { + // Cache hit: use the cached dentry + child.inode().clone() + } + None => { + // Cache miss: need to lookup from the underlying filesystem + drop(children); + self.inode.lookup(name)? + } + }; + + self.inode.unlink(name)?; + fs::notify::on_link_count(&child_inode); + if child_inode.metadata().nlinks == 0 { + fs::notify::on_inode_removed(&child_inode); + let deleted_watches = child_inode.fsnotify_publisher().remove_all_subscribers(); + child_inode + .fs() + .fsnotify_info() + .remove_subscribers(deleted_watches); + } + fs::notify::on_delete(self.inode(), &child_inode, String::from(name)); Ok(()) } @@ -256,13 +284,41 @@ impl Dentry { return_errno!(Errno::ENOTDIR); } + if is_dot(name) { + return_errno_with_message!(Errno::EINVAL, "rmdir on ."); + } + if is_dotdot(name) { + return_errno_with_message!(Errno::ENOTEMPTY, "rmdir on .."); + } + let children = self.children.upread(); children.check_mountpoint(name)?; - self.inode.rmdir(name)?; - let mut children = children.upgrade(); - children.delete(name); + let cached_child = children.delete(name); + + let child_inode = match cached_child { + Some(child) => { + // Cache hit: use the cached dentry + child.inode().clone() + } + None => { + // Cache miss: need to lookup from the underlying filesystem + drop(children); + self.inode.lookup(name)? + } + }; + + self.inode.rmdir(name)?; + if child_inode.metadata().nlinks == 0 { + fs::notify::on_inode_removed(&child_inode); + let deleted_watches = child_inode.fsnotify_publisher().remove_all_subscribers(); + child_inode + .fs() + .fsnotify_info() + .remove_subscribers(deleted_watches); + } + fs::notify::on_delete(self.inode(), &child_inode, String::from(name)); Ok(()) } diff --git a/kernel/src/fs/path/mod.rs b/kernel/src/fs/path/mod.rs index 9a501c44d..f86fea4b2 100644 --- a/kernel/src/fs/path/mod.rs +++ b/kernel/src/fs/path/mod.rs @@ -145,9 +145,7 @@ impl Path { /// /// If it is the root of a mount, it will go up to the mountpoint /// to get the name of the mountpoint recursively. - // - // FIXME: This method needs to be aware of the current process's root path. - fn effective_name(&self) -> String { + pub fn effective_name(&self) -> String { if !self.is_mount_root() { return self.dentry.name(); } @@ -167,7 +165,7 @@ impl Path { /// /// If it is the root of a mount, it will go up to the mountpoint /// to get the parent of the mountpoint recursively. - fn effective_parent(&self) -> Option { + pub fn effective_parent(&self) -> Option { if !self.is_mount_root() { return Some(Self::new(self.mount.clone(), self.dentry.parent().unwrap())); } diff --git a/kernel/src/fs/procfs/mod.rs b/kernel/src/fs/procfs/mod.rs index 113dd4368..24af861b9 100644 --- a/kernel/src/fs/procfs/mod.rs +++ b/kernel/src/fs/procfs/mod.rs @@ -23,7 +23,10 @@ use crate::{ fs::{ procfs::{filesystems::FileSystemsFileOps, stat::StatFileOps}, registry::{FsProperties, FsType}, - utils::{mkmod, DirEntryVecExt, FileSystem, FsFlags, Inode, SuperBlock, NAME_MAX}, + utils::{ + mkmod, DirEntryVecExt, FileSystem, FsEventSubscriberStats, FsFlags, Inode, SuperBlock, + NAME_MAX, + }, }, prelude::*, process::{ @@ -64,6 +67,7 @@ struct ProcFs { sb: SuperBlock, root: Arc, inode_allocator: AtomicU64, + fs_event_subscriber_stats: FsEventSubscriberStats, } impl ProcFs { @@ -72,6 +76,7 @@ impl ProcFs { sb: SuperBlock::new(PROC_MAGIC, BLOCK_SIZE, NAME_MAX), root: RootDirOps::new_inode(weak_fs.clone()), inode_allocator: AtomicU64::new(PROC_ROOT_INO + 1), + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) } @@ -96,6 +101,10 @@ impl FileSystem for ProcFs { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } struct ProcFsType; diff --git a/kernel/src/fs/procfs/template/dir.rs b/kernel/src/fs/procfs/template/dir.rs index 00c795465..8da263f19 100644 --- a/kernel/src/fs/procfs/template/dir.rs +++ b/kernel/src/fs/procfs/template/dir.rs @@ -9,6 +9,7 @@ use ostd::sync::RwMutexUpgradeableGuard; use super::{Common, ProcFs}; use crate::{ fs::{ + notify::FsEventPublisher, path::{is_dot, is_dotdot}, utils::{ DirEntryVecExt, DirentVisitor, FileSystem, Inode, InodeIo, InodeMode, InodeType, @@ -95,6 +96,7 @@ impl InodeIo for ProcDir { impl Inode for ProcDir { fn size(&self) -> usize; fn metadata(&self) -> Metadata; + fn fs_event_publisher(&self) -> &FsEventPublisher; fn ino(&self) -> u64; fn mode(&self) -> Result; fn set_mode(&self, mode: InodeMode) -> Result<()>; diff --git a/kernel/src/fs/procfs/template/file.rs b/kernel/src/fs/procfs/template/file.rs index a750b698c..43c0b6a30 100644 --- a/kernel/src/fs/procfs/template/file.rs +++ b/kernel/src/fs/procfs/template/file.rs @@ -6,8 +6,11 @@ use inherit_methods_macro::inherit_methods; use super::{Common, ProcFs}; use crate::{ - fs::utils::{ - FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, StatusFlags, SymbolicLink, + fs::{ + notify::FsEventPublisher, + utils::{ + FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, StatusFlags, SymbolicLink, + }, }, prelude::*, process::{Gid, Uid}, @@ -66,6 +69,7 @@ impl InodeIo for ProcFile { impl Inode for ProcFile { fn size(&self) -> usize; fn metadata(&self) -> Metadata; + fn fs_event_publisher(&self) -> &FsEventPublisher; fn ino(&self) -> u64; fn mode(&self) -> Result; fn set_mode(&self, mode: InodeMode) -> Result<()>; diff --git a/kernel/src/fs/procfs/template/mod.rs b/kernel/src/fs/procfs/template/mod.rs index 752fcb436..e70b456f5 100644 --- a/kernel/src/fs/procfs/template/mod.rs +++ b/kernel/src/fs/procfs/template/mod.rs @@ -10,7 +10,10 @@ pub(super) use self::{ }; use super::{ProcFs, BLOCK_SIZE}; use crate::{ - fs::utils::{FileSystem, InodeMode, InodeType, Metadata}, + fs::{ + notify::FsEventPublisher, + utils::{FileSystem, InodeMode, InodeType, Metadata}, + }, prelude::*, process::{Gid, Uid}, }; @@ -22,6 +25,7 @@ mod sym; struct Common { metadata: RwLock, + fs_event_publisher: FsEventPublisher, fs: Weak, is_volatile: bool, } @@ -30,6 +34,7 @@ impl Common { pub fn new(metadata: Metadata, fs: Weak, is_volatile: bool) -> Self { Self { metadata: RwLock::new(metadata), + fs_event_publisher: FsEventPublisher::new(), fs, is_volatile, } @@ -109,4 +114,8 @@ impl Common { pub fn is_volatile(&self) -> bool { self.is_volatile } + + pub fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } } diff --git a/kernel/src/fs/procfs/template/sym.rs b/kernel/src/fs/procfs/template/sym.rs index c5deeffac..46bc326b7 100644 --- a/kernel/src/fs/procfs/template/sym.rs +++ b/kernel/src/fs/procfs/template/sym.rs @@ -6,8 +6,11 @@ use inherit_methods_macro::inherit_methods; use super::{Common, ProcFs}; use crate::{ - fs::utils::{ - FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, StatusFlags, SymbolicLink, + fs::{ + notify::FsEventPublisher, + utils::{ + FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, StatusFlags, SymbolicLink, + }, }, prelude::*, process::{Gid, Uid}, @@ -63,6 +66,7 @@ impl InodeIo for ProcSym { impl Inode for ProcSym { fn size(&self) -> usize; fn metadata(&self) -> Metadata; + fn fs_event_publisher(&self) -> &FsEventPublisher; fn ino(&self) -> u64; fn mode(&self) -> Result; fn set_mode(&self, mode: InodeMode) -> Result<()>; diff --git a/kernel/src/fs/pseudofs.rs b/kernel/src/fs/pseudofs.rs index f6f0bd26d..936549bbc 100644 --- a/kernel/src/fs/pseudofs.rs +++ b/kernel/src/fs/pseudofs.rs @@ -7,9 +7,11 @@ use spin::Once; use super::utils::{InodeIo, StatusFlags}; use crate::{ fs::{ + notify::FsEventPublisher, registry::{FsProperties, FsType}, utils::{ - mkmod, FileSystem, FsFlags, Inode, InodeMode, InodeType, Metadata, SuperBlock, NAME_MAX, + mkmod, FileSystem, FsEventSubscriberStats, FsFlags, Inode, InodeMode, InodeType, + Metadata, SuperBlock, NAME_MAX, }, }, prelude::*, @@ -22,6 +24,7 @@ pub struct PseudoFs { name: &'static str, sb: SuperBlock, root: Arc, + fs_event_subscriber_stats: FsEventSubscriberStats, } impl FileSystem for PseudoFs { @@ -41,6 +44,10 @@ impl FileSystem for PseudoFs { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } impl PseudoFs { @@ -63,6 +70,7 @@ impl PseudoFs { aster_block::BLOCK_SIZE, weak_fs.clone(), )), + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) }) } @@ -174,6 +182,7 @@ const ANON_INODEFS_MAGIC: u64 = 0x09041934; /// A pseudo inode that does not correspond to any real path in the file system. pub struct PseudoInode { metadata: SpinLock, + fs_events_publisher: FsEventPublisher, fs: Weak, } @@ -206,6 +215,7 @@ impl PseudoInode { }; PseudoInode { metadata: SpinLock::new(metadata), + fs_events_publisher: FsEventPublisher::new(), fs, } } @@ -250,6 +260,10 @@ impl Inode for PseudoInode { *self.metadata.lock() } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_events_publisher + } + fn ino(&self) -> u64 { self.metadata.lock().ino } diff --git a/kernel/src/fs/ramfs/fs.rs b/kernel/src/fs/ramfs/fs.rs index 606c11dd4..dbd44e3ed 100644 --- a/kernel/src/fs/ramfs/fs.rs +++ b/kernel/src/fs/ramfs/fs.rs @@ -19,14 +19,15 @@ use crate::{ fs::{ device::Device, inode_handle::FileIo, + notify::FsEventPublisher, path::{is_dot, is_dot_or_dotdot, is_dotdot}, pipe::NamedPipe, registry::{FsProperties, FsType}, utils::{ mkmod, AccessMode, CStr256, CachePage, DirentVisitor, Extension, FallocMode, - FileSystem, FsFlags, Inode, InodeIo, InodeMode, InodeType, Metadata, MknodType, - PageCache, PageCacheBackend, Permission, StatusFlags, SuperBlock, SymbolicLink, - XattrName, XattrNamespace, XattrSetFlags, + FileSystem, FsEventSubscriberStats, FsFlags, Inode, InodeIo, InodeMode, InodeType, + Metadata, MknodType, PageCache, PageCacheBackend, Permission, StatusFlags, SuperBlock, + SymbolicLink, XattrName, XattrNamespace, XattrSetFlags, }, }, prelude::*, @@ -43,6 +44,8 @@ pub struct RamFs { root: Arc, /// An inode allocator inode_allocator: AtomicU64, + /// FS event subscriber stats for this file system + fs_event_subscriber_stats: FsEventSubscriberStats, } impl RamFs { @@ -62,8 +65,10 @@ impl RamFs { fs: weak_fs.clone(), extension: Extension::new(), xattr: RamXattr::new(), + fs_event_publisher: FsEventPublisher::new(), }), inode_allocator: AtomicU64::new(ROOT_INO + 1), + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) } @@ -89,6 +94,10 @@ impl FileSystem for RamFs { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } /// An inode of `RamFs`. @@ -107,6 +116,8 @@ pub(super) struct RamInode { fs: Weak, /// Extensions extension: Extension, + /// FS event publisher + fs_event_publisher: FsEventPublisher, /// Extended attributes xattr: RamXattr, } @@ -411,6 +422,7 @@ impl RamInode { this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } @@ -424,6 +436,7 @@ impl RamInode { this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } @@ -443,6 +456,7 @@ impl RamInode { this: Weak::new(), fs: Weak::new(), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), } } @@ -456,6 +470,7 @@ impl RamInode { this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } @@ -475,6 +490,7 @@ impl RamInode { this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } @@ -488,6 +504,7 @@ impl RamInode { this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } @@ -501,6 +518,7 @@ impl RamInode { this: weak_self.clone(), fs: Arc::downgrade(fs), extension: Extension::new(), + fs_event_publisher: FsEventPublisher::new(), xattr: RamXattr::new(), }) } @@ -1182,6 +1200,10 @@ impl Inode for RamInode { Some(&self.extension) } + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } + fn set_xattr( &self, name: XattrName, diff --git a/kernel/src/fs/ramfs/memfd.rs b/kernel/src/fs/ramfs/memfd.rs index 18f786ab1..cc35dd7b2 100644 --- a/kernel/src/fs/ramfs/memfd.rs +++ b/kernel/src/fs/ramfs/memfd.rs @@ -22,6 +22,7 @@ use crate::{ file_handle::{FileLike, Mappable}, file_table::FdFlags, inode_handle::{do_fallocate_util, do_resize_util, do_seek_util}, + notify::FsEventPublisher, path::{check_open_util, RESERVED_MOUNT_ID}, tmpfs::TmpFs, utils::{ @@ -178,6 +179,7 @@ impl Inode for MemfdInode { fn set_group(&self, gid: Gid) -> Result<()>; fn page_cache(&self) -> Option>; fn extension(&self) -> Option<&Extension>; + fn fs_event_publisher(&self) -> &FsEventPublisher; fn set_xattr( &self, name: XattrName, diff --git a/kernel/src/fs/sysfs/fs.rs b/kernel/src/fs/sysfs/fs.rs index 40f45d6b7..5331df8b6 100644 --- a/kernel/src/fs/sysfs/fs.rs +++ b/kernel/src/fs/sysfs/fs.rs @@ -6,7 +6,10 @@ use crate::{ fs::{ registry::{FsProperties, FsType}, sysfs::{self, inode::SysFsInode}, - utils::{systree_inode::SysTreeInodeTy, FileSystem, FsFlags, Inode, SuperBlock}, + utils::{ + systree_inode::SysTreeInodeTy, FileSystem, FsEventSubscriberStats, FsFlags, Inode, + SuperBlock, + }, Result, }, prelude::*, @@ -17,6 +20,7 @@ use crate::{ pub(super) struct SysFs { sb: SuperBlock, root: Arc, + fs_event_subscriber_stats: FsEventSubscriberStats, } const MAGIC_NUMBER: u64 = 0x62656572; // SYSFS_MAGIC @@ -44,6 +48,7 @@ impl SysFs { Arc::new(Self { sb, root: root_inode, + fs_event_subscriber_stats: FsEventSubscriberStats::new(), }) } } @@ -65,6 +70,10 @@ impl FileSystem for SysFs { fn sb(&self) -> SuperBlock { self.sb.clone() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + &self.fs_event_subscriber_stats + } } pub(super) struct SysFsType; diff --git a/kernel/src/fs/sysfs/inode.rs b/kernel/src/fs/sysfs/inode.rs index b2a2a83ed..c01707337 100644 --- a/kernel/src/fs/sysfs/inode.rs +++ b/kernel/src/fs/sysfs/inode.rs @@ -6,9 +6,12 @@ use ostd::sync::RwLock; use super::fs::SysFs; use crate::{ - fs::utils::{ - systree_inode::{SysTreeInodeTy, SysTreeNodeKind}, - FileSystem, Inode, InodeMode, InodeType, Metadata, + fs::{ + notify::FsEventPublisher, + utils::{ + systree_inode::{SysTreeInodeTy, SysTreeNodeKind}, + FileSystem, Inode, InodeMode, InodeType, Metadata, + }, }, prelude::*, }; @@ -25,6 +28,8 @@ pub(super) struct SysFsInode { /// Currently, the only mutable metadata is `mode`, /// which allows user space to `chmod` an inode on sysfs. metadata: Metadata, + /// FS event publisher. + fs_event_publisher: FsEventPublisher, /// The file mode (permissions) of this inode, protected by a lock. mode: RwLock, /// Weak reference to the parent inode. @@ -46,6 +51,7 @@ impl SysTreeInodeTy for SysFsInode { Arc::new_cyclic(|this| Self { node_kind, metadata, + fs_event_publisher: FsEventPublisher::new(), mode: RwLock::new(mode), parent, this: this.clone(), @@ -78,6 +84,10 @@ impl SysTreeInodeTy for SysFsInode { .upgrade() .expect("invalid weak reference to `self`") } + + fn fs_event_publisher(&self) -> &FsEventPublisher { + &self.fs_event_publisher + } } impl Inode for SysFsInode { diff --git a/kernel/src/fs/tmpfs/fs.rs b/kernel/src/fs/tmpfs/fs.rs index a18c262b3..89a3bf093 100644 --- a/kernel/src/fs/tmpfs/fs.rs +++ b/kernel/src/fs/tmpfs/fs.rs @@ -4,7 +4,7 @@ use crate::{ fs::{ ramfs::RamFs, registry::{FsProperties, FsType}, - utils::{FileSystem, FsFlags, Inode, SuperBlock}, + utils::{FileSystem, FsEventSubscriberStats, FsFlags, Inode, SuperBlock}, }, prelude::*, }; @@ -43,6 +43,10 @@ impl FileSystem for TmpFs { fn sb(&self) -> SuperBlock { self.inner.sb() } + + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats { + self.inner.fs_event_subscriber_stats() + } } pub(super) struct TmpFsType; diff --git a/kernel/src/fs/utils/fs.rs b/kernel/src/fs/utils/fs.rs index 2b49ece5b..dcf43778f 100644 --- a/kernel/src/fs/utils/fs.rs +++ b/kernel/src/fs/utils/fs.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use core::sync::atomic::AtomicU32; +use core::sync::atomic::{AtomicI64, AtomicU32, Ordering}; use atomic_integer_wrapper::define_atomic_version_of_integer_like_type; @@ -103,6 +103,57 @@ define_atomic_version_of_integer_like_type!(FsFlags, { pub struct AtomicFsFlags(AtomicU32); }); +#[derive(Debug)] +pub struct FsEventSubscriberStats { + // The number of subscribers to this file system. + num_subscribers: AtomicI64, +} + +impl FsEventSubscriberStats { + pub fn new() -> Self { + Self { + num_subscribers: AtomicI64::new(0), + } + } + + pub fn add_subscriber(&self) { + self.num_subscribers.fetch_add(1, Ordering::Release); + } + + pub fn remove_subscriber(&self) { + let subscribers = self.num_subscribers.fetch_sub(1, Ordering::Release); + debug_assert!( + subscribers >= 0, + "The number of subscribers is negative: {}", + subscribers + ); + } + + pub fn remove_subscribers(&self, num_subscribers: usize) { + let num_subscribers = num_subscribers as i64; + let old_value = self.num_subscribers.load(Ordering::Acquire); + debug_assert!( + old_value >= num_subscribers, + "integer overflow: attempting to remove {} subscribers when only {} exist", + num_subscribers, + old_value + ); + + let subscribers = self + .num_subscribers + .fetch_sub(num_subscribers, Ordering::Release); + debug_assert!( + subscribers >= 0, + "The number of subscribers is negative: {}", + subscribers + ); + } + + pub fn has_any_subscribers(&self) -> bool { + self.num_subscribers.load(Ordering::Acquire) > 0 + } +} + pub trait FileSystem: Any + Sync + Send { /// Gets the name of this FS type such as `"ext4"` or `"sysfs"`. fn name(&self) -> &'static str; @@ -129,6 +180,9 @@ pub trait FileSystem: Any + Sync + Send { warn!("setting file system flags is not implemented"); Ok(()) } + + /// Returns the FS event subscriber stats of this file system. + fn fs_event_subscriber_stats(&self) -> &FsEventSubscriberStats; } impl dyn FileSystem { diff --git a/kernel/src/fs/utils/inode.rs b/kernel/src/fs/utils/inode.rs index 3ba3fb5ee..2e5599b40 100644 --- a/kernel/src/fs/utils/inode.rs +++ b/kernel/src/fs/utils/inode.rs @@ -16,6 +16,7 @@ use crate::{ device::{Device, DeviceType}, fs_resolver::PathOrInode, inode_handle::FileIo, + notify::FsEventPublisher, path::Path, utils::StatusFlags, }, @@ -409,6 +410,16 @@ pub trait Inode: Any + InodeIo + Send + Sync { None } + // TODO: Add `FsEventPublisher` as an extension. + // + // Conceptually, an `FsEventPublisher` attached to an inode is also a kind of extension + // as this object is required by the VFS layer, not by FS implementations. + // But we do not add it to `Extension` because `FsEventPublisher` is used in the most time-critical path + // and looking up an extension object in an `Extension` incurs some extra overheads. + // If we could make `Extension` a zero-cost abstraction, + // then `FsEventPublisher` can be moved into `Extension`. + fn fs_event_publisher(&self) -> &FsEventPublisher; + fn set_xattr( &self, name: XattrName, diff --git a/kernel/src/fs/utils/mod.rs b/kernel/src/fs/utils/mod.rs index 9bba5da0e..691ff9eb1 100644 --- a/kernel/src/fs/utils/mod.rs +++ b/kernel/src/fs/utils/mod.rs @@ -10,7 +10,7 @@ pub use endpoint::{Endpoint, EndpointState}; pub use falloc_mode::FallocMode; pub use file_creation_mask::{AtomicFileCreationMask, FileCreationMask}; pub use flock::{FlockItem, FlockList, FlockType}; -pub use fs::{FileSystem, FsFlags, SuperBlock}; +pub use fs::{FileSystem, FsEventSubscriberStats, FsFlags, SuperBlock}; pub use id_bitmap::IdBitmap; pub use inode::{ Extension, Inode, InodeIo, InodeType, Metadata, MknodType, Permission, SymbolicLink, diff --git a/kernel/src/fs/utils/systree_inode.rs b/kernel/src/fs/utils/systree_inode.rs index ef293e7d3..1dab15ac6 100644 --- a/kernel/src/fs/utils/systree_inode.rs +++ b/kernel/src/fs/utils/systree_inode.rs @@ -13,13 +13,13 @@ use aster_systree::{ SysAttr, SysBranchNode, SysNode, SysNodeId, SysNodeType, SysObj, SysStr, SysSymlink, }; -use super::InodeIo; use crate::{ fs::{ inode_handle::FileIo, + notify::FsEventPublisher, utils::{ - mkmod, AccessMode, DirentVisitor, FallocMode, FileSystem, Inode, InodeMode, InodeType, - Metadata, MknodType, StatusFlags, SymbolicLink, + mkmod, AccessMode, DirentVisitor, FallocMode, FileSystem, Inode, InodeIo, InodeMode, + InodeType, Metadata, MknodType, StatusFlags, SymbolicLink, }, }, prelude::*, @@ -54,6 +54,8 @@ pub(in crate::fs) trait SysTreeInodeTy: Send + Sync + 'static { fn set_mode(&self, mode: InodeMode) -> Result<()>; + fn fs_event_publisher(&self) -> &FsEventPublisher; + fn parent(&self) -> &Weak; fn this(&self) -> Arc @@ -579,6 +581,10 @@ impl Inode for KInode { default fn is_dentry_cacheable(&self) -> bool { true } + + default fn fs_event_publisher(&self) -> &FsEventPublisher { + self.fs_event_publisher() + } } // Update AttrDentryIter to filter by min_ino