Filesystem events

Updated: April 19, 2023

Keeping up with a very “live” filesystem that changes often and quickly is a challenge for the programs that work with it. To help with this challenge, QNX Neutrino includes filesystem events.

These events let an application keep track of changes—including the creation and deletion of files and directory, changes of ownership and permissions, mount and unmount operations, and more—in a filesystem that's of interest. The management of filesystem events involves an event manager, an event mechanism, and client event handlers.

The event manager, fsevmgr, is a process that registers a fixed name in the system namespace (the default is /dev/fsevents) and receives the filesystem events, possibly from multiple io-blk.so instances, determines which clients need which events, and gives the events to those clients.

The event mechanism is part of io-blk.so, where any filesystem or parts of the block I/O system can easily use it. This gives the most flexibility since we can coordinate events coming into the filesystem at the system-call level, from within the filesystems, partitions, caching, and even the block device driver if needed.

The mechanism places events into a buffer to eventually be sent to the event manager. This mechanism is limited to an API that reports a locale where the event came from, a unique identifier indicating the event, and properties that further describe the event. The io-blk.so library is responsible for packaging each event and sending it to the event manager. The implemention uses a timer to ensure events aren't left sitting in this buffer for an extended period of time.

During startup, io-blk.so looks for the event manager. If it isn't loaded, the event mechanism is disabled. Three configuration parameters are provided:

During the loading of the event manager, a DCMD_FSYS_FSEVMGR_CHECK devctl command is sent to the various io-blk.so mountpoints to notify them that an event manager has been loaded.

Finally a client event handler may instantiate a thread that blocks on reads from /dev/fsevents or selects the file descriptor for notification once data is available.

Here's an overview of how the client typically interacts with the event manager:

open()
FSE_DEFAULT_MANAGER_NAME (defined in <sys/fs_events.h>) describes the default path of the event manager. Keep in mind that it may be overridden by the event manager, and io-blk.so may also be changed to look for another event manager.

The client should generally open the event manager in O_RDONLY mode to simply read events. Write mode is necessary only if the client needs to send events. Set the O_NONBLOCK flag if the client is going to poll the event manager.

read()
The client reads events from the Event Manager into an array of bytes. The byte array is filled up to the size required to store whole events. In other words, partial events aren't returned to readers. No assumptions can be made reguarding the alignment of events within the buffers.

FSE_READ_EVENT_S(pev, pbytes) is provided as a wrapper to memcpy() an event from the byte array into an aligned structure. From there, the client can use the other FSE_* accessor macros to interpret event characteristics.

While the event manager generally has a large queue to hold events, it will inevitably fill and begin to overwrite old events. If any event clients haven't kept up with the addition of new events, those readers will be placed into an overflow state. In other words, if the event manager has begun to overwrite events that a reader hasn't consumed, the next read from that descriptor indicate an error of EOVERFLOW. The subsequent read() returns the oldest event.

lseek()
The event manager supports a limited seek functionality. Generally, a client starts reading from the head of the queue and immediately blocks waiting for new events. If a client must evaluate past events, it can use lseek().

A whence value of SEEK_SET and an offset of zero position the file descriptor to the oldest event in the queue. Be aware that this condition may cause an EOVERFLOW during a subsequent read().

A whence of SEEK_END and an offset of zero position the reader at the end of the queue to read new events.

The client can use event counts as the offset, with either SEEK_SET or SEEK_END, to index some count of events from the head or tail of the queue, respectively.

write()
The client can use write() to send events to the event manager. The event manager checks the event data to ensure it's well-formed; on failure, the event manager aborts the writing of all events that were sent in a single write operation.
close()
Once the file descriptor is no longer needed, it may be closed.

The <sys/fs_events.h> header file includes everything that's necessary to read and process events from the event manager. For more information, see FSE_*() in the C Library Reference.