fix(fsnotify): harden inotify lifecycle and semantics - #2204
Conversation
Implements inotify (issue DragonOS-Community#2151): the fsnotify core notification layer, the inotify pseudo-device, 4 syscalls (init/init1/add_watch/rm_watch), and VFS write-path hooks for all standard events (create/delete/move/ modify/access/close/attrib/self events). Architecture: - fsnotify/ unified dispatch layer: global (inode_id, dev_id) index, TOTAL_WATCHES atomic fast-path (zero cost when no watches), lock-family separation (global index lock / events lock / wd lock never nested). - inotify.rs device: InotifyInode implements IndexNode + PollableInode, epoll-integrated via LockedEPItemLinkedList, exact inotify_event layout (name field aligned to sizeof(inotify_event)=16, matching Linux ABI). - VFS hooks placed in syscall-core layer (vcore/open/rename_utils/...), NOT per-filesystem: single anchor covers ext4/tmpfs/overlayfs/fuse. Hooks fire only after success and never alter syscall return values. Review fixes incorporated: - Directory watches receive child content events (issue B): IN_MODIFY/ ACCESS/OPEN/CLOSE delivered to parent dir watch with child name. MountFSInode::as_any_ref() returns the inner inode's Any, so use downcast_arc instead of downcast_ref for parent resolution. - Guard DELETE_SELF on hardlink unlink/rename-over: only emit when i_nlink reaches 0, matching Linux fsnotify_link_count() semantics. - Composite mark index key (inode_id, dev_id): prevents FUSE cross-mount event leakage when multiple mounts reuse same inode number. - Remove has_any_watch() gate on File::inotify_parent resolution so watches added after open receive content events. - Tolerate metadata failure after unlink/rename-over (FUSE GETATTR can return ENOENT); cache nlinks before the namespace operation. - SYS_INOTIFY_INIT only registered on x86_64 (generic syscall ABI uses inotify_init1); riscv64/loongarch64 lack the legacy init syscall. - Skip MOVED events on no-op rename; EOF reads no longer deliver IN_ACCESS; reject mask==0 and IN_MASK_ADD|IN_MASK_CREATE. Test: dunitest inotify_dir_watch (2 tests) + inotify_events (6 tests) covering content/namespace/self events, multi-instance, and poll. Design doc: docs/kernel/filesystem/inotify.md Signed-off-by: sparkzky <sparkhhhhhhhhhh@outlook.com>
Linearize mark publication, dispatch, and removal around stable object identities so watch updates, one-shot delivery, inode deletion, and final unmount cannot race. Make queue overflow, read consumption, allocation rollback, user quotas, and dentry snapshots safe under concurrency and memory pressure. Align VFS event routing with Linux semantics for open, close, links, rename, attributes, exclusion of unlinked paths, and deletion ordering. Add dunitest coverage for namespace events, inode lifecycle, O_PATH, hard links, rename aliases, multi-instance delivery, and poll readiness. Validated with make kernel and 12 passing inotify tests on both host Linux and a DragonOS QEMU guest. Signed-off-by: longjin <longjin@dragonos.org>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8ef2adf4d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // fsnotify:打开成功 → 被打开 inode 得 IN_OPEN。 | ||
| if r.is_ok() && !how.o_flags.contains(FileFlags::O_PATH) { |
There was a problem hiding this comment.
当文件通过 execve/execveat 打开时,实际执行的是 do_open_execat_with_flags(),该函数在 open.rs:608 直接创建 File,不会经过这里仅属于 do_sys_openat2() 的通知逻辑;后续 ELF 读取和 File::drop 仍会产生 IN_ACCESS、IN_CLOSE_NOWRITE,导致监听可执行文件的审计程序收到关闭事件却完全漏掉对应的 IN_OPEN。应让普通打开与 exec 打开共用 OPEN 通知入口。
AGENTS.md reference: AGENTS.md:L9-L9
Useful? React with 👍 / 👎.
| let r = current_inode.mkdir(name, final_mode); | ||
| if let Ok(new_inode) = &r { |
There was a problem hiding this comment.
这里开始按具体 syscall 在创建成功后投递 IN_CREATE,但 sys_mknod.rs:82 和 sys_mknodat.rs:77 仍直接调用 IndexNode::mknod 后返回,MountFSInode::mknod 也不投递通知。因此通过 mkfifo、mknod 创建 FIFO、设备节点或普通文件时,父目录 watch 会永久漏掉 Linux 应产生的带节点名 IN_CREATE;需要在两个 mknod 入口成功后执行同样的通知。
AGENTS.md reference: AGENTS.md:L9-L9
Useful? React with 👍 / 👎.
| if r.is_ok() { | ||
| fsnotify::fsnotify_inode(FsEvent::MODIFY, &inode); | ||
| } |
There was a problem hiding this comment.
在 O_TRUNC 的 MODIFY 之前发布 IN_OPEN
当调用 open(path, O_TRUNC) 时,do_sys_openat2() 在 open.rs:484-485 先调用这里的 truncate 路径并立即排入 IN_MODIFY,直到 open.rs:512 才排入 IN_OPEN,所以用户可见顺序变成 IN_MODIFY → IN_OPEN;Linux 6.6 的打开流程先发布 OPEN,再执行截断通知。依赖 OPEN 划分文件操作边界的消费者会把修改归入错误的一次打开,应在该打开流程中把截断的 MODIFY 延迟到 IN_OPEN 之后。
AGENTS.md reference: AGENTS.md:L9-L9
Useful? React with 👍 / 👎.
| let consumes_oneshot = matches!( | ||
| enqueue_result, | ||
| EnqueueResult::Queued | EnqueueResult::Merged | EnqueueResult::DroppedQueueFull | ||
| ); |
There was a problem hiding this comment.
当首个匹配的 one-shot 事件因名称或队列分配失败返回 AllocationFailed 时,后端已经用溢出记录表示该事件丢失,但这个匹配列表排除了该结果,watch 因而不会撤销;待溢出被读走后,它还能消费后续事件,违反 IN_ONESHOT 在首个匹配事件后自动移除的语义,也延迟了 IN_IGNORED。应将 AllocationFailed 也视为已经触发 one-shot。
AGENTS.md reference: AGENTS.md:L9-L9
Useful? React with 👍 / 👎.
Summary
Validation
make kernel