Native Inotify in FreeBSD

fanf21 pts0 comments

Native inotify in FreeBSD - Klara Systems Skip to content Home Resources Articles Native inotify in FreeBSD

Native inotify in FreeBSD<br>June 9, 2026A seemingly simple file monitoring problem exposed deep limitations in FreeBSD’s traditional EVFILT_VNODE approach. This article explores how race conditions, scalability issues, and Linux compatibility challenges ultimately led to the development of a native inotify implementation for FreeBSD 15.

Enter your email address to subscribe to Klara's newsletter.*

Mark Johnston

Did you know?Improve the way you make use of FreeBSD in your company.<br>Find out more about what makes us the most reliable FreeBSD development organization, and take the next step in engaging with us for your next FreeBSD project.<br>FreeBSD Support FreeBSD Development

Additional Articles<br>Here are more interesting articles on FreeBSD that you may find useful:<br>Jails vs LXC: What’s the Right Choice for Infrastructure?<br>Jails, Not Containers: FreeBSD Isolation Done Right<br>Using Object Storage with OpenZFS and SeaweedFS<br>Managing Cache and DirectIO for Databases on ZFS<br>FreeBSD and OpenZFS in the Quest for Technical Independence: A Storage Architect’s View​<br>View More Articles

A Simple File Monitoring Problem<br>Some time ago a customer presented us with a seemingly simple problem.  They had a workflow where files were uploaded to a server with scp, and they wanted to run an action on the server after the upload had finished.  Their initial solution was to use the inotifywait utility to watch the upload directory and wait for a CLOSE notification, upon which they would run their action.  `inotifywait` can monitor a file hierarchy for accesses or modifications, such as file creation, writes, deletes, and so on.  This solution worked perhaps 99% of the time, but once in a while inotifywait would fail to emit the CLOSE notification after an upload.<br>The root cause of the missing notifications had to do with how inotifywait is implemented under the hood.  It used a FreeBSD-specific library, `libinotify.so`, which implements Linux's inotify interface in userspace using the `EVFILT_VNODE` kqueue filter. Linux's inotify and FreeBSD's EVFILT_VNODE are both system call interfaces which let an application passively monitor a file or directory for changes.<br>The interfaces themselves are quite different from each other; below is a sample function which monitors all files in a given directory for file close events and prints events when they occur, effectively the same functionality that our customer wants.  First, the inotify-based version:<br>void<br>watchdir_inotify(const char *dirname)<br>struct inotify_event *ev;<br>size_t evsz;<br>int ifd, wd;

ifd = inotify_init();<br>if (ifd len > 0)<br>printf("CLOSE: %s\n", ev->name);<br>}This is straightforward: we create an inotify descriptor, ask it to watch the specified directory for IN_CLOSE events, and then loop, reading event descriptions from the descriptor.  This can easily be integrated into a larger event loop by using kevent() or epoll() to wait for data to arrive on the inotify descriptor.

Here is a version which uses kqueue() and EVFILT_VNODE to try and implement the same functionality:<br>struct entry {<br>LIST_ENTRY(entry) link;

char name[NAME_MAX + 1];<br>int fd;<br>bool seen;<br>};

LIST_HEAD(entry_list, entry);

static void<br>scan(int kq, DIR *d, struct entry_list *entries)<br>struct dirent *dp;<br>struct kevent kev;<br>struct entry *e, *tmp;<br>int error;

LIST_FOREACH(e, entries, link)<br>e->seen = false;

rewinddir(d);<br>while ((dp = readdir(d)) != NULL) {<br>int flags;

if (strcmp(dp->d_name, ".") == 0 ||<br>strcmp(dp->d_name, "..") == 0)<br>continue;

/* Search for an existing entry. */<br>LIST_FOREACH(e, entries, link) {<br>if (strcmp(e->name, dp->d_name) == 0) {<br>e->seen = true;<br>break;

if (e == NULL) {<br>int fd;

/* Not found, create a new entry. */<br>fd = openat(dirfd(d), dp->d_name, O_RDONLY);<br>if (fd d_name);

e = malloc(sizeof(*e));<br>if (e == NULL)<br>err(1, "malloc");

(void)strlcpy(e->name, dp->d_name, sizeof(e->name));<br>e->fd = fd;<br>e->seen = true;

LIST_INSERT_HEAD(entries, e, link);

flags = EV_ADD | EV_CLEAR;<br>} else {<br>flags = EV_CLEAR;

/* Watch the file for close events. */<br>EV_SET(&kev, e->fd, EVFILT_VNODE, flags,<br>NOTE_CLOSE | NOTE_CLOSE_WRITE |<br>NOTE_RENAME | NOTE_DELETE,<br>0, e);

error = kevent(kq, &kev, 1, NULL, 0, NULL);<br>if (error seen)<br>continue;

LIST_REMOVE(e, link);<br>close(e->fd);<br>free(e);

/* Add the directory itself to the queue. */<br>EV_SET(&kev, dirfd(d), EVFILT_VNODE,<br>EV_ADD | EV_CLEAR,<br>NOTE_WRITE,<br>0, 0);

error = kevent(kq, &kev, 1, NULL, 0, NULL);<br>if (error &entries);

kq = kqueue();<br>if (kq &entries);

/* Main loop. */<br>while (true) {<br>error = kevent(kq, NULL, 0, &kev, 1, NULL);<br>if (error &entries);<br>} else {<br>struct entry *e;

e = (struct entry *)kev.udata;

if ((kev.fflags &<br>(NOTE_RENAME | NOTE_DELETE)) != 0) {<br>/* File was renamed, scan. */<br>scan(kq, d, &entries);

if ((kev.fflags &<br>(NOTE_CLOSE | NOTE_CLOSE_WRITE)) != 0)<br>printf("CLOSE: %s\n", e->name);<br>}We can immediately see that this is way more complex. ...

freebsd inotify null file struct entry

Related Articles