|
| 1 | +//! 文件系统事件通知统一层(fsnotify)。 |
| 2 | +//! |
| 3 | +//! 本模块是 VFS 写路径 hook 与具体后端(当前仅 inotify)之间的解耦层。 |
| 4 | +//! VFS hook 只调用 [`fsnotify`],由它查全局 mark 索引并把事件分发给匹配的 watch。 |
| 5 | +//! |
| 6 | +//! 设计原则(见 `docs/kernel/filesystem/inotify.md` §0/§3): |
| 7 | +//! - `fsnotify()` 尽力而为,绝不影响 syscall 返回值; |
| 8 | +//! - `fsnotify()` 内部只取本层自旋锁与 group 队列锁,绝不回调 `IndexNode` 写方法; |
| 9 | +//! - 锁序:`MountFSInode/File 锁` → `FSNOTIFY 全局锁` → `group 队列锁`,永不反向。 |
| 10 | +
|
| 11 | +pub mod group; |
| 12 | +pub mod mark; |
| 13 | + |
| 14 | +use alloc::sync::Arc; |
| 15 | +use alloc::vec::Vec; |
| 16 | +use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; |
| 17 | + |
| 18 | +use hashbrown::HashMap; |
| 19 | + |
| 20 | +use crate::filesystem::vfs::{FileType, IndexNode, InodeId}; |
| 21 | +use crate::libs::spinlock::SpinLock; |
| 22 | +use system_error::SystemError; |
| 23 | + |
| 24 | +pub use group::FsNotifyGroup; |
| 25 | +pub use mark::FsNotifyMark; |
| 26 | + |
| 27 | +// 事件 mask:对应 Linux 内核 `FS_*` 事件,其比特位与用户态 `IN_*` 完全一致, |
| 28 | +// 故可直接作为用户态 mask 使用(仅 `ISDIR` 由 dispatch 按需设置)。 |
| 29 | +// |
| 30 | +// 参考:Linux `include/uapi/linux/inotify.h`、`include/linux/fsnotify_backend.h`。 |
| 31 | +bitflags::bitflags! { |
| 32 | + pub struct FsEvent: u32 { |
| 33 | + const ACCESS = 0x00000001; // IN_ACCESS |
| 34 | + const MODIFY = 0x00000002; // IN_MODIFY |
| 35 | + const ATTRIB = 0x00000004; // IN_ATTRIB |
| 36 | + const CLOSE_WRITE = 0x00000008; // IN_CLOSE_WRITE |
| 37 | + const CLOSE_NOWRITE= 0x00000010; // IN_CLOSE_NOWRITE |
| 38 | + const OPEN = 0x00000020; // IN_OPEN |
| 39 | + const MOVED_FROM = 0x00000040; // IN_MOVED_FROM |
| 40 | + const MOVED_TO = 0x00000080; // IN_MOVED_TO |
| 41 | + const CREATE = 0x00000100; // IN_CREATE |
| 42 | + const DELETE = 0x00000200; // IN_DELETE |
| 43 | + const DELETE_SELF = 0x00000400; // IN_DELETE_SELF |
| 44 | + const MOVE_SELF = 0x00000800; // IN_MOVE_SELF |
| 45 | + const UNMOUNT = 0x00002000; // IN_UNMOUNT(文件系统卸载) |
| 46 | + const Q_OVERFLOW = 0x00004000; // IN_Q_OVERFLOW(队列溢出) |
| 47 | + const IN_IGNORED = 0x00008000; // watch 被撤销(inode 删除/卸载) |
| 48 | + const ISDIR = 0x40000000; // 事件对象是目录(由 dispatch 设置) |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +/// 后端接口(最小抽象)。 |
| 53 | +/// |
| 54 | +/// fsnotify 层通过此 trait 调用具体后端,保持 VFS → fsnotify → inotify 单向依赖。 |
| 55 | +pub trait FsNotifyBackend: Send + Sync + core::fmt::Debug { |
| 56 | + /// 处理一个事件:格式化、(可选)合并、入队,并唤醒等待者。 |
| 57 | + fn handle_event( |
| 58 | + &self, |
| 59 | + group: &FsNotifyGroup, |
| 60 | + mark: &FsNotifyMark, |
| 61 | + mask: FsEvent, |
| 62 | + name: Option<&str>, |
| 63 | + cookie: u32, |
| 64 | + ); |
| 65 | + /// mark 销毁时从后端内部结构(如 wd 表)移除。 |
| 66 | + fn free_mark(&self, mark: &FsNotifyMark); |
| 67 | + /// mark 被撤销时向消费者投递一个 IN_IGNORED 事件(rm_watch/oneshot/DELETE_SELF/UNMOUNT)。 |
| 68 | + /// fd close(shutdown) 路径不调用此方法。 |
| 69 | + fn notify_ignored(&self, group: &FsNotifyGroup, mark: &FsNotifyMark); |
| 70 | + /// poll 用:队列是否非空。 |
| 71 | + fn queue_nonempty(&self) -> bool; |
| 72 | +} |
| 73 | + |
| 74 | +/// 全局 watch 计数:绝大多数时刻为 0。`fsnotify` 的第一道闸门—— |
| 75 | +/// 无 watch 时零锁开销(对齐 Linux `i_fsnotify_mask` 快速跳过)。 |
| 76 | +static TOTAL_WATCHES: AtomicUsize = AtomicUsize::new(0); |
| 77 | + |
| 78 | +/// move 事件 cookie 分配器:每次 rename 取一个,FROM/TO 共享。 |
| 79 | +/// 0 表示「无 move」,故从 1 开始,回绕时跳过 0。 |
| 80 | +static NEXT_COOKIE: AtomicU32 = AtomicU32::new(1); |
| 81 | + |
| 82 | +/// 取一个新的非零 move cookie。 |
| 83 | +pub fn next_cookie() -> u32 { |
| 84 | + loop { |
| 85 | + let c = NEXT_COOKIE.fetch_add(1, Ordering::Relaxed); |
| 86 | + if c != 0 { |
| 87 | + return c; |
| 88 | + } |
| 89 | + } |
| 90 | +} |
| 91 | +// 全局 mark 索引:用 `InodeId` 反查「挂在该 inode 上的所有 mark」。 |
| 92 | +// |
| 93 | +// 存 `Weak<FsNotifyMark>`:group 拥有 mark(强引用),索引只做查找,不阻止回收。 |
| 94 | +// dispatch 时 `Weak::upgrade()` 失败的死引用会被惰性剔除。 |
| 95 | +lazy_static::lazy_static! { |
| 96 | + static ref FSNOTIFY_MARKS: SpinLock<HashMap<InodeId, Vec<alloc::sync::Weak<FsNotifyMark>>>> = |
| 97 | + SpinLock::new(HashMap::new()); |
| 98 | +} |
| 99 | + |
| 100 | +/// 记录一次 watch 计数变更(add +1,撤销 -1)。仅用于短路,Relaxed 即可。 |
| 101 | +pub(crate) fn adjust_total_watches(delta: i32) { |
| 102 | + if delta >= 0 { |
| 103 | + TOTAL_WATCHES.fetch_add(delta as usize, Ordering::Relaxed); |
| 104 | + } else { |
| 105 | + TOTAL_WATCHES.fetch_sub((-delta) as usize, Ordering::Relaxed); |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +/// 系统中是否存在任意 inotify watch。供 VFS 热路径(open/read/write/close)做廉价短路: |
| 110 | +/// 无 watch 时完全跳过 parent 解析与 fsnotify 调用(零开销)。 |
| 111 | +pub fn has_any_watch() -> bool { |
| 112 | + TOTAL_WATCHES.load(Ordering::Relaxed) != 0 |
| 113 | +} |
| 114 | + |
| 115 | +/// 原子地预留一个 watch 槽位(用于上限检查)。 |
| 116 | +/// 成功时 TOTAL_WATCHES 已 +1;超限时回退并返回 ENOSPC。 |
| 117 | +/// 这是唯一的全局 watch 计数器,同时服务「快速路径短路」和「max_user_watches 上限检查」。 |
| 118 | +pub(crate) fn try_reserve_watch(max: usize) -> Result<(), SystemError> { |
| 119 | + let prev = TOTAL_WATCHES.fetch_add(1, Ordering::Relaxed); |
| 120 | + if prev >= max { |
| 121 | + TOTAL_WATCHES.fetch_sub(1, Ordering::Relaxed); |
| 122 | + return Err(SystemError::ENOSPC); |
| 123 | + } |
| 124 | + Ok(()) |
| 125 | +} |
| 126 | + |
| 127 | +/// 把 mark 加入全局索引(add_watch 调用)。 |
| 128 | +pub(crate) fn index_add(mark: &Arc<FsNotifyMark>) { |
| 129 | + let id = mark.inode_id(); |
| 130 | + let mut idx = FSNOTIFY_MARKS.lock_irqsave(); |
| 131 | + idx.entry(id).or_default().push(Arc::downgrade(mark)); |
| 132 | +} |
| 133 | + |
| 134 | +/// 把 mark 从全局索引移除(按指针相等匹配,rm_watch / 撤销时调用)。 |
| 135 | +pub(crate) fn index_remove(mark: &FsNotifyMark) { |
| 136 | + let id = mark.inode_id(); |
| 137 | + let self_ptr = mark as *const FsNotifyMark; |
| 138 | + let mut idx = FSNOTIFY_MARKS.lock_irqsave(); |
| 139 | + if let Some(vec) = idx.get_mut(&id) { |
| 140 | + let mut i = 0; |
| 141 | + while i < vec.len() { |
| 142 | + // 剔除:指针相等(同一个 mark),或 Weak 已死。 |
| 143 | + let drop_it = match vec[i].upgrade() { |
| 144 | + Some(arc) => alloc::sync::Arc::as_ptr(&arc) as *const FsNotifyMark == self_ptr, |
| 145 | + None => true, |
| 146 | + }; |
| 147 | + if drop_it { |
| 148 | + vec.swap_remove(i); |
| 149 | + } else { |
| 150 | + i += 1; |
| 151 | + } |
| 152 | + } |
| 153 | + if vec.is_empty() { |
| 154 | + idx.remove(&id); |
| 155 | + } |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +/// 统一事件投递入口。在 VFS 操作**成功之后**调用,尽力而为,不影响调用方返回值。 |
| 160 | +/// |
| 161 | +/// - `parent`:对子项事件(CREATE/DELETE/MOVED_*),传 `(父目录 inode, 子项名)`; |
| 162 | +/// - `child`:对自身事件(DELETE_SELF/MOVE_SELF/MODIFY/CLOSE/OPEN/ATTRIB),传目标 inode; |
| 163 | +/// - 二者可同时非空(如 unlink:父目录得 `IN_DELETE`,子项得 `IN_DELETE_SELF`)。 |
| 164 | +/// |
| 165 | +/// # 安全性 |
| 166 | +/// 内部只读 `inode.metadata()`(inode 活着、metadata 只读不锁),不调用任何写方法, |
| 167 | +/// 避免在调用方持有的 VFS/File 锁下重入。 |
| 168 | +pub fn fsnotify( |
| 169 | + mask: FsEvent, |
| 170 | + parent: Option<(&Arc<dyn IndexNode>, &str)>, |
| 171 | + child: Option<&Arc<dyn IndexNode>>, |
| 172 | + cookie: u32, |
| 173 | +) { |
| 174 | + // ① 快速路径:系统无任何 watch → 直接返回(read/write/close 热路径零成本)。 |
| 175 | + if TOTAL_WATCHES.load(Ordering::Relaxed) == 0 { |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + // ② 预取 inode 元数据(inode 活着、metadata 只读不锁,安全)。 |
| 180 | + // 事件的「主体」是 child(被创建/删除/移动/修改的对象);IN_ISDIR 由主体是否为 |
| 181 | + // 目录决定,对 parent/child 两类 watch 一视同仁。若无 child(仅父目录自身事件 |
| 182 | + // 的退化情况),ISDIR 不置位。 |
| 183 | + let (child_id, event_is_dir, child_unlinked) = match child { |
| 184 | + Some(c) => match c.metadata() { |
| 185 | + Ok(md) => ( |
| 186 | + Some(md.inode_id), |
| 187 | + md.file_type == FileType::Dir, |
| 188 | + md.nlinks == 0, |
| 189 | + ), |
| 190 | + Err(_) => (None, false, false), |
| 191 | + }, |
| 192 | + None => (None, false, false), |
| 193 | + }; |
| 194 | + let parent_id = parent.and_then(|(p, _)| p.metadata().ok().map(|md| md.inode_id)); |
| 195 | + |
| 196 | + if child_id.is_none() && parent_id.is_none() { |
| 197 | + return; |
| 198 | + } |
| 199 | + |
| 200 | + // ③ 收集候选 mark 快照:临界区仅做哈希查表(秒放),不做后端工作。 |
| 201 | + // (mark 强引用, name, is_parent) |
| 202 | + let mut snapshot: Vec<(Arc<FsNotifyMark>, Option<&str>, bool)> = Vec::new(); |
| 203 | + { |
| 204 | + let idx = FSNOTIFY_MARKS.lock_irqsave(); |
| 205 | + if let Some(pid) = parent_id { |
| 206 | + let name = parent.map(|(_, n)| n); |
| 207 | + if let Some(vec) = idx.get(&pid) { |
| 208 | + for w in vec.iter() { |
| 209 | + if let Some(m) = w.upgrade() { |
| 210 | + snapshot.push((m, name, true)); |
| 211 | + } |
| 212 | + } |
| 213 | + } |
| 214 | + } |
| 215 | + if let Some(cid) = child_id { |
| 216 | + if let Some(vec) = idx.get(&cid) { |
| 217 | + for w in vec.iter() { |
| 218 | + if let Some(m) = w.upgrade() { |
| 219 | + snapshot.push((m, None, false)); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + } |
| 224 | + } |
| 225 | + // 注:死 Weak 在 lock 内 upgrade 失败时被跳过;惰性清理留给 index_remove。 |
| 226 | + |
| 227 | + // 事件路由(Linux 模型): |
| 228 | + // - 命名空间事件 CREATE/DELETE/MOVED_FROM/MOVED_TO:仅父目录 watch 收(带 name); |
| 229 | + // - 自身事件 DELETE_SELF/MOVE_SELF:仅子项自身 watch 收; |
| 230 | + // - 内容类事件 ACCESS/MODIFY/ATTRIB/CLOSE_*/OPEN:父目录 watch(带 name)与子项自身 watch |
| 231 | + // 均收——使「监听目录」能收到子文件被读/写/开关/改属性的事件(inotify 头号用例)。 |
| 232 | + // 一次 fsnotify 调用可同时通知父目录与子项(如 unlink:父得 IN_DELETE,子得 IN_DELETE_SELF)。 |
| 233 | + let self_only = FsEvent::DELETE_SELF | FsEvent::MOVE_SELF; |
| 234 | + let parent_only = FsEvent::CREATE | FsEvent::DELETE | FsEvent::MOVED_FROM | FsEvent::MOVED_TO; |
| 235 | + // 内容类事件(IN_EXCL_UNLINK 抑制对象)。 |
| 236 | + let content_type = FsEvent::MODIFY |
| 237 | + | FsEvent::ACCESS |
| 238 | + | FsEvent::ATTRIB |
| 239 | + | FsEvent::CLOSE_WRITE |
| 240 | + | FsEvent::CLOSE_NOWRITE |
| 241 | + | FsEvent::OPEN; |
| 242 | + |
| 243 | + // ④ 锁外投递。 |
| 244 | + for (mark, name, is_parent) in snapshot { |
| 245 | + // 父 mark 收除 self_only 外的全部;自身 mark 收除 parent_only 外的全部。 |
| 246 | + let routed = if is_parent { |
| 247 | + mask & !self_only |
| 248 | + } else { |
| 249 | + mask & !parent_only |
| 250 | + }; |
| 251 | + if routed.is_empty() { |
| 252 | + continue; |
| 253 | + } |
| 254 | + let subscribed = mark.mask.load(Ordering::Relaxed); |
| 255 | + if (subscribed & routed.bits()) == 0 { |
| 256 | + // 该 watch 未订阅此事件 |
| 257 | + continue; |
| 258 | + } |
| 259 | + |
| 260 | + // IN_EXCL_UNLINK:仅对子项的「内容类」事件、且子项已 unlink 时抑制。 |
| 261 | + if is_parent && mark.excl_unlink && routed.intersects(content_type) && child_unlinked { |
| 262 | + continue; |
| 263 | + } |
| 264 | + |
| 265 | + // dispatch 设置 ISDIR(主体是目录时)。 |
| 266 | + let mut delivered = routed; |
| 267 | + if event_is_dir { |
| 268 | + delivered |= FsEvent::ISDIR; |
| 269 | + } |
| 270 | + |
| 271 | + let destroy = delivered.contains(FsEvent::DELETE_SELF) |
| 272 | + || delivered.contains(FsEvent::UNMOUNT) |
| 273 | + || mark.oneshot.load(Ordering::Relaxed); |
| 274 | + |
| 275 | + if let Some(group) = mark.group.upgrade() { |
| 276 | + group |
| 277 | + .backend |
| 278 | + .handle_event(&group, &mark, delivered, name, cookie); |
| 279 | + } |
| 280 | + |
| 281 | + if destroy { |
| 282 | + mark::destroy_mark(&mark); |
| 283 | + } |
| 284 | + } |
| 285 | +} |
0 commit comments