fsnotifyModule
Filesystem notification.
Places marks on filesystem objects and hands the events they report to a Lua callback. Event masks are the linux.fsnotify bits, named without theFS_ prefix:
ACCESS, MODIFY, ATTRIB, CLOSE_WRITE, CLOSE_NOWRITE, OPEN, MOVED_FROM,
MOVED_TO, CREATE, DELETE, DELETE_SELF, MOVE_SELF, OPEN_EXEC, UNMOUNT,
Q_OVERFLOW, ERROR and RENAME; the flags EVENT_ON_CHILD and ISDIR; and the
permission events OPEN_PERM, ACCESS_PERM and OPEN_EXEC_PERM.
A mark goes on one inode, on a mount, or on a whole filesystem. The last two
reach every file they cover, so a mark on the mount or the superblock of /
sends every access on the machine through the callback; mark a scratch
subtree.
Delivery is synchronous: the callback runs inside the syscall of the process
performing the access, holding the runtime lock, so every watched access on
the machine is serialized behind it. That task may also hold the lock of the
directory the event is about, so watch:mark, watch:find and mark:mask
called from a callback resolve their path from the directory cache alone.
A permission event parks the syscall in the callback and takes its return
value as the answer: fsnotify.action.ALLOW lets the access happen,
fsnotify.action.DENY fails it with EPERM, and any other negative errno
fails it with that errno, but for the kernel’s own, from ERESTARTSYS on,
which no user program is given: those allow and log invalid errno.
Anything else allows, a callback that returns
nothing or raises included, so a rule that fails, or forgets to answer,
takes nothing away. An exec asks twice, OPEN_EXEC_PERM and then
OPEN_PERM, each through the marks that carry it, and a denial on the
first ends it there. The kernel asks once the LSMs' open and read hooks have
allowed the access, and a check an LSM makes later, AppArmor’s on an exec
among them, still applies, so a verdict can refuse what they let through and
never let through what they refuse.
Class fsnotify_watch
| fsnotify_watch:find (path[, kind]) | Finds the mark this watch placed on an object. |
| fsnotify_watch:mark (path, mask[, kind]) | Places a mark on a filesystem object. |
| fsnotify_watch:stop () | Stops the watch. |
Class fsnotify_mark
| fsnotify_mark:ignore ([mask]) | Reads the mark’s ignore mask, or sets it. |
| fsnotify_mark:mask ([mask]) | Reads the mark’s event mask, or sets it. |
| fsnotify_mark:stop () | Stops the mark. |
Class fsnotify_event
| fsnotify_event:dir () | Returns the inode number of the directory the entry the event names lives in. |
| fsnotify_event:ino () | Returns the inode number of the object the event is about. |
| fsnotify_event:isdir () | Tells whether the event is about a directory. |
| fsnotify_event:name () | Returns the directory entry name the event is about. |
| fsnotify_event:path () | Returns the full path of the object the event is about. |
| fsnotify_event:pid () | Returns the pid of the task performing the access. |
fsnotify
| action | What a callback answers a permission event with. |
| watch (callback) | Creates a watch. |
Class fsnotify_watch in detail
fsnotify.watch(), owning an fsnotify group and
every mark placed through it.
- fsnotify_watch:find (path[, kind])
-
Finds the mark this watch placed on an object.
Resolves path and asks the kernel, which keys the marks by the object they
are on, so a script does not have to keep a table of its own.
Parameters
- path string path of the marked object
- kind
string
"inode"(default),"mount"or"sb";"mount"needs kernel 6.10 or later (optional)
Returns
-
fsnotify_mark
the mark this watch placed there, or
nilwhen it has noneRaises
if path does not resolve,EAGAINwhen it is resolved from a callback and the directory cache alone cannot answer it, ifkindis not one this kernel offers, or if the watch has been stoppedUsage
local mark = watch:find("/tmp/scratch", "mount")
- fsnotify_watch:mark (path, mask[, kind])
-
Places a mark on a filesystem object.
Resolves path and marks what
kindnames for the events in mask: the inode it resolves to, the mount it is on, or its whole filesystem. An inode mark on a directory reports events on the files directly inside it only when mask carrieslinux.fsnotify.EVENT_ON_CHILD, and never on anything deeper; a mount or superblock mark reports every file it covers without it.EVENT_ON_CHILDreaches a file through its parent in the directory cache. A file reached through a disconnected dentry, opened by handle withopen_by_handle_atafter the cache dropped its entry, has no parent there: its events reach its own inode mark and the mount and superblock marks only, never the directory’s.Parameters
- path string path of the object to mark
- mask integer event mask, a combination of linux.fsnotify bits
- kind
string
"inode"(default),"mount"or"sb";"mount"needs kernel 6.10 or later (optional)
Returns
-
fsnotify_mark
the mark, which the watch keeps until it is removed
Raises
if path does not resolve,EAGAINwhen it is resolved from a callback and the directory cache alone cannot answer it, ifkindis not one this kernel offers, if this watch already marks the object, if the watch has been stopped, or if mask carries a permission event on a kernel built withoutCONFIG_FANOTIFY_ACCESS_PERMISSIONS; “out of bounds” if mask is negative or past 32 bitsUsage
local mark = watch:mark("/tmp/scratch", fs.OPEN | fs.MODIFY, "mount")
- fsnotify_watch:stop ()
-
Stops the watch.
Removes every mark it placed and releases its group, so no further event
reaches the callback. Calling it again does nothing. A to-be-closed variable
holding the watch stops it the same way.
Returns
-
nil
Usage
watch:stop()
Class fsnotify_mark in detail
watch:mark returns and watch:find hands back. The watch owns
the mark: dropping the handle leaves the mark in place, and watch:stop
removes every mark the watch placed. A handle whose mark is gone raises,
but for stop.
- fsnotify_mark:ignore ([mask])
-
Reads the mark’s ignore mask, or sets it.
An event in the ignore mask is not reported through this mark, whatever its
event mask carries, and the ignore mask a script sets holds until it sets
another: a write to the object does not clear it.
Parameters
- mask integer the events to ignore, a combination of linux.fsnotify bits (optional)
Returns
-
integer
the mark’s ignore mask
Raises
if the mark has been removed, or “out of bounds” if mask is negative or past 32 bitsUsage
mark:ignore(fs.OPEN) - fsnotify_mark:mask ([mask])
-
Reads the mark’s event mask, or sets it.
Setting it removes the mark and adds it again, because the mask an object is
watched for is a sum over its marks that only the kernel’s own add
recalculates: path is resolved once more, and an event in between is not
reported. The handle stays the same.
Parameters
- mask integer the new event mask, a combination of linux.fsnotify bits (optional)
Returns
-
integer
the mark’s event mask
Raises
if the mark has been removed, “out of bounds” if mask is negative or past 32 bits, if mask carries a permission event on a kernel built withoutCONFIG_FANOTIFY_ACCESS_PERMISSIONS, or if the mark cannot be added again, because the path no longer resolves,EAGAINwhen a callback resolving it finds the directory cache alone cannot answer it, or the kernel refuses it, which leaves the mark removedUsage
mark:mask(mark:mask() | fs.MODIFY)
- fsnotify_mark:stop ()
-
Stops the mark.
No further event reaches the callback through it, and the watch drops it.
Calling it again, or after
watch:stop, does nothing. A to-be-closed variable holding the mark stops it the same way.Returns
-
nil
Usage
mark:stop()
Class fsnotify_event in detail
What an accessor can answer depends on what the kernel attached to the event:
an event on an open file carries a struct path and answers everything, while
a directory entry event carries only the entry’s dentry or inode and has no
path. Each accessor returns nil for a field the event does not carry.
- fsnotify_event:dir ()
-
Returns the inode number of the directory the entry the event names lives in.
Carried by the directory entry events, the old directory for
RENAME, and by an event on an object whose parent directory watches its children, whether or not name is.Returns
-
integer
inode number, or
nilwhen the event names no directoryRaises
if the event is used after its callback returned - fsnotify_event:ino ()
-
Returns the inode number of the object the event is about.
Absent only when the kernel attached neither an inode, a dentry nor a path to
the event, and for a directory entry whose inode the filesystem instantiates
after reporting the entry.
Returns
-
integer
inode number, or
nilwhen the event carries no inodeRaises
if the event is used after its callback returned - fsnotify_event:isdir ()
-
Tells whether the event is about a directory.
Reads
linux.fsnotify.ISDIRoff the mask, so it answers for every event.Returns
-
boolean
Raises
if the event is used after its callback returned - fsnotify_event:name ()
-
Returns the directory entry name the event is about.
Carried by the directory entry events (
CREATE,DELETE,MOVED_FROM,MOVED_TO, andRENAME, whose name is the old one) and by an event a parent directory marked withlinux.fsnotify.EVENT_ON_CHILDis interested in; an event only the object’s own mark reports carries none.Returns
-
string
entry name, or
nilwhen the event carries noneRaises
if the event is used after its callback returned - fsnotify_event:path ()
-
Returns the full path of the object the event is about.
Only an event the kernel raised from an open file carries a
struct path; a directory entry event does not, and answersnil. This is the expensive accessor: it resolves the path into aPATH_MAXbuffer on every call, so a callback that matches on name never pays for it.The path is the one the accessing task sees:
d_pathrenders it against that task’s own root, so a chrooted or containerised accessor gets its own view of it, and a file already unlinked carries a trailing" (deleted)".Returns
-
string
path, or
nilwhen the event carries noneRaises
if the path cannot be resolved, or if the event is used after its callback returned - fsnotify_event:pid ()
-
Returns the pid of the task performing the access.
Delivery is synchronous, in the syscall of the process being watched, so this
is that process and not a bookkeeping artefact. It is a thread id, the same
number
task:pid()reports.Returns
-
integer
pid
Raises
if the event is used after its callback returned
fsnotify in detail
- action
-
What a callback answers a permission event with.
Fields
- ALLOW let the access happen
- DENY
refuse it; the parked syscall fails with
EPERM
Usage
if event:path() == "/srv/secret" then return fsnotify.action.DENY end
- watch (callback)
-
Creates a watch.
Allocates an fsnotify group whose events are delivered to
callback. Nothing arrives untilwatch:markplaces a mark. An event reaches the callback once per watch, however many of its marks match it: when a file and its parent directory, marked withlinux.fsnotify.EVENT_ON_CHILD, are both marked by the same watch, an event on the file arrives once, taggedEVENT_ON_CHILDand carrying the entry’s name and dir.An event whose delivery would need a lock its own task already holds is dropped rather than run: this callback touching a path it marks, and equally a thread body, a device file operation or another module’s callback in the same runtime.
Parameters
- callback
function
invoked as
callback(mask, event), where mask is the event mask that fired, testable against linux.fsnotify bits, andeventis an fsnotify_event valid only for the length of the call. For a permission event its return value is the verdict, an fsnotify.action; for every other event it is ignored.
Returns
-
fsnotify_watch
the watch, which fsnotify.watch keeps for its runtime:
dropping it stops nothing, and its marks stay until stop or the end of the runtime
Raises
if the group cannot be allocated, if called from an interrupt-context runtime, or if called from a percpu runtime;not allowed while the runtime closesfrom a finalizer that runs at its closeUsage
local watch = fsnotify.watch(function (mask, event) print(mask, event:name()) end) watch:mark("/tmp/scratch", fs.OPEN)
- callback
function
invoked as