Skip to content

Commit d8e2a8a

Browse files
committed
fs: implement inotify(2)
inotify was stubbed (inotify_init returned EMFILE, add_watch/rm_watch returned EINVAL), so programs that watch the filesystem for changes could not run. Implement it against OSv's VFS. All path mutations route through a handful of central functions in fs/vfs/vfs_syscalls.cc; those now call osv_inotify_notify() with the absolute path affected and the event mask. libc/inotify.cc keeps a registry of inotify instances (each a pollable special_file, modeled on eventfd), matches every event against their watches, and queues a struct inotify_event for the fd, waking any poller. - inotify_init/inotify_init1 create the fd (IN_CLOEXEC/IN_NONBLOCK honored). - inotify_add_watch resolves the path to absolute, adds (or merges, with IN_MASK_ADD) a watch, and returns a watch descriptor. - inotify_rm_watch removes a watch and queues IN_IGNORED. - read() returns as many packed inotify_event structures as fit in the buffer (blocking, or EAGAIN when non-blocking), rejecting a too-small buffer with EINVAL; poll() reports POLLIN when events are queued. A directory watch fires for changes to entries within it (reporting the entry name); a watch on the object itself fires with no name. Events wired: IN_CREATE (mkdir, open O_CREAT), IN_DELETE (rmdir, unlink), IN_MOVED_FROM / IN_MOVED_TO (rename), each with IN_ISDIR when the object is a directory. The rename hook rebuilds the full destination path because sys_rename truncates `dest` to its parent in place before the VOP. Not yet covered (follow-ups): IN_MODIFY/IN_CLOSE_WRITE on writes (needs an fd-to-path lookup), IN_ACCESS/IN_OPEN, and recursive watches. Add tests/tst-inotify.cc covering create/delete/move on files and a subdirectory (names and IN_ISDIR), IN_IGNORED on watch removal, and the EAGAIN/EINVAL paths. Passes on OSv under KVM; tst-remove (unlink/rename/rmdir) continues to pass, so the VFS mutation paths are unaffected.
1 parent 3aba46c commit d8e2a8a

5 files changed

Lines changed: 430 additions & 16 deletions

File tree

fs/vfs/vfs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ int sys_link(char *oldpath, char *newpath);
113113
int sys_unlink(char *path);
114114
int sys_symlink(const char *oldpath, const char *newpath);
115115
int sys_access(char *path, int mode);
116+
117+
/* inotify VFS notification hook (libc/inotify.cc). */
118+
void osv_inotify_notify(const char *path, uint32_t mask, int is_dir);
116119
int sys_stat(char *path, struct stat *st);
117120
int sys_lstat(char *path, struct stat *st);
118121
int sys_statfs(char *path, struct statfs *buf);

fs/vfs/vfs_syscalls.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
#include <sys/stat.h>
4343
#include <dirent.h>
44+
#include <sys/inotify.h>
4445

4546
#include <limits.h>
4647
#include <unistd.h>
@@ -141,6 +142,7 @@ sys_open(char *path, int flags, mode_t mode, struct file **fpp)
141142

142143
if (error)
143144
return error;
145+
osv_inotify_notify(path, IN_CREATE, 0);
144146
if ((error = namei(path, &dp)) != 0)
145147
return error;
146148

@@ -568,6 +570,9 @@ sys_mkdir(char *path, mode_t mode)
568570
mode |= S_IFDIR;
569571

570572
error = VOP_MKDIR(ddp->d_vnode, name, mode);
573+
if (!error) {
574+
osv_inotify_notify(path, IN_CREATE, 1);
575+
}
571576
out:
572577
vn_unlock(ddp->d_vnode);
573578
drele(ddp);
@@ -609,6 +614,9 @@ sys_rmdir(char *path)
609614
error = VOP_RMDIR(ddp->d_vnode, vp, name);
610615
vn_unlock(ddp->d_vnode);
611616

617+
if (!error) {
618+
osv_inotify_notify(path, IN_DELETE, 1);
619+
}
612620
vn_unlock(vp);
613621
dentry_remove(dp);
614622
drele(ddp);
@@ -824,6 +832,19 @@ sys_rename(char *src, char *dest)
824832

825833
error = VOP_RENAME(dvp1, vp1, sname, dvp2, vp2, dname);
826834

835+
if (!error) {
836+
int is_dir = (vp1->v_type == VDIR) ? 1 : 0;
837+
// `src` is still the full source path, but `dest` was truncated to its
838+
// parent above (*dname was NUL'd), so rebuild the full dest path.
839+
char full_dest[PATH_MAX];
840+
if (dest[0] == '/' && dest[1] == '\0') {
841+
snprintf(full_dest, sizeof(full_dest), "/%s", dname);
842+
} else {
843+
snprintf(full_dest, sizeof(full_dest), "%s/%s", dest, dname);
844+
}
845+
osv_inotify_notify(src, IN_MOVED_FROM, is_dir);
846+
osv_inotify_notify(full_dest, IN_MOVED_TO, is_dir);
847+
}
827848
dentry_move(dp1, ddp2, dname);
828849
if (dp2)
829850
dentry_remove(dp2);
@@ -1016,6 +1037,9 @@ sys_unlink(char *path)
10161037
error = VOP_REMOVE(ddp->d_vnode, vp, name);
10171038
vn_unlock(ddp->d_vnode);
10181039

1040+
if (!error) {
1041+
osv_inotify_notify(path, IN_DELETE, 0);
1042+
}
10191043
vn_unlock(vp);
10201044
dentry_remove(dp);
10211045
drele(ddp);

0 commit comments

Comments
 (0)