filewatcherd is a daemon inspired by cron, that run commands based on
file changes instead of time.
In principle it is similar to incron, but it's simpler, more limited,
and does not depend on anything outside of FreeBSD base.
Usage of filewatcherd is quite straightforward: the daemon has a few
basic command-line options, and takes a watchtab file as main input.
The watchtab is heavily inspired from crontab. Blank lines are ignored,
leading and trailing blanks in line are ignored, line starting with a
hash sign (#) are ignored as comments.
Environment lines are defined as having an equal sign (=) before any
backslash (\\) or tabulation character. They represent environment
variables available for commands, and only affect the entries below them.
Entry lines consist of 3 to 6 tabulation-separated fields. A complete line contains the following fields in respective order:
- Path of the file to watch
- Event set to consider
- Delay between the first triggering event and command run
- User, and optionally group, to set for the command
chrootto set for the command- The command itself
When only 5 fields are present, chroot is skipped. When there are only
4 fields, user is also skipped. When there are only 3 field, delay is
considered 0.
In path, chroot and command fields, backslashes (\\) act as an escape
character, allowing to embed tabulations, backslashes and/or equal signs
in those fields without misinterpretation.
The event set can be a single star sign (*) to mean all available events,
or a list of any number of event names separated by a single non-letter
byte. The available events are delete, write, extend, attrib,
link, rename and revoke, with semantics matching those of
similar-named fflags for vnode filter.
The delay is given in seconds and can be fractional, up to the nanosecond
(though most system probably do not have such a resolution in
nanosleep(3)).
The user can be a login string or a numeric id, and is optionally followed
by a group string or numeric id after a colon (:). When specified, those
must exist and have a matching passwd or group entry.
The command is run in a clean environment, containing only variables
explicitly declared in the watchtab file, and SHELL, PATH, HOME,
TRIGGER, USER, LOGNAME.
SHELLandPATHdefault respectively to/bin/shand/usr/bin:/bin, but they can be overwritten in the watchtab.HOMEdefault to the home directory of the user running the command, but can be overwritten in the watchtab.USERandLOGNAMEare both forced to the login name of the user running the command, and values provided in the watchtab are ignored.TRIGGERis forced to the path of the file triggering the event (seen from outside thechroot), ignoring any value provided in the watchtab.
The watchtab is automatically watched by filewatcherd itself, and is
automatically reloaded when it changes.
filewatcherd is split between 4 .c modules:
log.cimplements logging functions, which means all user-facing outputwatchtab.cimplements watchtab parsing and upkeep of structures related to watchtab entriesrun.cimplements actual execution of a watchtab entryfilewatcherd.cimplements the event loop directly inmain()function
Watchtab entries oscillate between two states:
- waiting for an
EVFILT_VNODEevent described in the watchtab, which triggers execution of the associated command - waiting for an
EVFILT_PROCevent that signals the end of the command to switch back toEVFILT_VNODEwait.
Events are not reused, at each step of cycle a new one is added to the
kernel queue with EV_ONESHOT flag.
This architecture guarantees that there cannot be more than one file
descriptor per watchtab entry or more than one process started per watchtab
entry. System resources consumed by filewatcherd are therefore bounded
by the watchtab length.
Whenever an error happens, e.g. when spawning the command or opening the watched path, the cycle is broken and the watchtab entry becomes inactive until the watchtab is reloaded.
There is currently no way to re-enable a single inactive watchtab entry.
The watchtab file itself is also watched by filewatcherd, in a process
similar to a watchtab entry except that EVFILT_VNODE events trigger an
EVFILT_TIMER addition before reloading the file.
Errors are handled so that this cycle can only be broken by a failure to insert an event in the queue:
- When the watchtab file cannot be opened, the timer event is left in the kernel queue to trigger another attempt after the delay. To prevent log spamming, only the first failure is logged, even though subsequent failures might have other causes.
- When the watchtab file is opened, the timer event is removed and an
EVFILT_VNODEfilter is added to track watchtab changes. Should a parse error occurs, the old watchtab is used instead, and a subsequent change in the watchtab file will trigger a reload.