CL-INOTIFY - Binding to the Linux inotify(7) API.
Copyright (C) 2011-15 Olof-Joachim Frahm
Released under a Simplified BSD license.
Working, but unfinished.
Uses CFFI, binary-types (from my Github or see CLiki) and trivial-utf-8. Doesn't require iolib, because I don't need most of the functionality, although it might add some implementation independence (patches which can be conditionally compiled are most welcome; in any case patches are always welcome). The tests require fiveam.
Similar packages are inotify and cl-fsnotify.
This document helps only with the aspects of this binding, so reading the man-page and other information on the inotify-facility may be needed. Reading the next sections and the docstrings of exported symbols should get you going, otherwise the source itself may also be of some value.
On Debian-like systems -
$ apt install libfixposix-dev
(ql:quickload :cl-inotify)
Macros make keeping track easier, so the following example is straightforward:
(with-inotify (inotify T ("." :all-events))
(do-events (event inotify :blocking-p T)
(format T "~A~%" event)))
;; =>
#S(CL-INOTIFY::INOTIFY-EVENT :WD 1 :MASK (CREATE) :COOKIE 0 :NAME .zshist.LOCK)
#S(CL-INOTIFY::INOTIFY-EVENT :WD 1 :MASK (OPEN) :COOKIE 0 :NAME .zshist)
#S(CL-INOTIFY::INOTIFY-EVENT :WD 1 :MASK (MODIFY) :COOKIE 0 :NAME .zshist)
#S(CL-INOTIFY::INOTIFY-EVENT :WD 1 :MASK (CLOSE-WRITE) :COOKIE 0 :NAME .zshist)
#S(CL-INOTIFY::INOTIFY-EVENT :WD 1 :MASK (DELETE) :COOKIE 0 :NAME .zshist.LOCK)
...
(Tilde-expansion has to happen at another level, else I would've used that.)
The first parameter is (per convention) the symbol to which the queue is
bound, the second is the parameter to MAKE-INOTIFY
. The &REST
list
consists of parameter lists for the WATCH
-function, which is called
for every list before the &BODY
is executed. We don't actually need
to UNWATCH
every watched path as closing the queue will also take care
of that.
You don't have to use macros: all functionality is available in function form, although some care should be taken as currently no cleanup handler is registered for opened queues, or rather their file handles.
(use-package '#:cl-inotify)
(defvar *tmp*)
(setf *tmp* (make-notify))
(watch *tmp* "/var/tmp/" :all-events)
(next-events *tmp*)
(close-inotify *tmp*)
So this section deals in depth with the various bits which make the examples above tick.
After loading the library use MAKE-INOTIFY
to create a new event
queue. The NONBLOCKING
argument sets the SB-POSIX:O-NONBLOCK
bit on
the stream so we don't block while reading. Nevertheless,
EVENT-AVAILABLE-P
works either way (by using CL:LISTEN
, or a custom
function which works directly on the file descriptor).
The result of MAKE-INOTIFY
is used with WATCH
and UNWATCH
, the first
being used to watch a file or directory, the second to stop watching
it. The FLAGS
parameter of WATCH
is described in the notify(7)
man-page; you can use a combination of the flags (as keywords) to create
a suitable bitmask. The types INOTIFY-ADD/READ-FLAG
,
INOTIFY-READ-FLAG
and INOTIFY-ADD-FLAG
are also defined and can be
examined.
For example, to watch for modified or closed files in a directory, call
(WATCH inotify "foo/" '(:modify :close))
.
The result of WATCH
is a handle (currently a FIXNUM
, but I wouldn't
rely on that) which can be fed to UNWATCH
and can be translated from
events with EVENT-PATHNAME/FLAGS
.
To finally get the events from the queue, use READ-EVENT
(which
blocks) or NEXT-EVENT
(which doesn't block). EVENT-AVAILABLE-P
does
what it should do, NEXT-EVENTS
retrieves all currently available
events as a list and DO-EVENTS
(nonblocking) iterates over available
events.
The enhanced API registers all watched paths in a hashtable, so you can
use PATHNAME-HANDLE/FLAGS
to check if a pathname (exact match) is
being watched and LIST-WATCHED
to return all watched paths as a list.
EVENT-PATHNAME/FLAGS
may be used to get the pathname and flags for a
read event.
UNWATCH
has to be called with the path or the handle of the watched
file or directory (a path will be looked up in the same table as with
PATHNAME-HANDLE/FLAGS
).
The raw API, which doesn't register watched paths, consists of
READ-RAW-EVENT-FROM-STREAM
, READ-EVENT-FROM-STREAM
, WATCH-RAW
and
UNWATCH-RAW
. They are just a thin wrapper around the C functions, but
they're exported in case someone doesn't like the upper layers.
In case you want to use epoll
or select
on the event queue you can
access the file descriptor yourself and then use the normal functions
afterwards. Currently no such functionality is integrated here, however
the following sketch shows how something can be accomplished using
iolib:
(with-unregistered-inotify (inotify T ("." :all-events))
(flet ((inotify-input (&rest rest)
(declare (ignore rest))
(format T "~{~A~%~}" (next-events inotify))))
(iolib:with-event-base (event-base)
(iolib:set-io-handler event-base (inotify-fd inotify) :read #'inotify-input)
(iolib:event-dispatch event-base))))
Note that we perform all inotify business only when something happens in
that directory, so instead of doing nothing, we could actually do useful
work, e.g. communicating with a process: This snippet was extracted
from a function which uses behaviour to monitor a LaTeX process for
written files to get the output file name without relying on heuristics
about the generated filename. As it stands you have to split this into
threads, or use IOLIB:EVENT-DISPATCH
with a timeout while periodically
checking the process status.
Here follows a list of valid keywords for the INOTIFY-FLAG
type:
:ACCESS
:MODIFY
:ATTRIB
:CLOSE-WRITE
:CLOSE-NOWRITE
:CLOSE
:OPEN
:MOVED-FROM
:MOVED-TO
:MOVE
:CREATE
:DELETE
:DELETE-SELF
:MOVE-SELF
:UNMOUNT
:Q-OVERFLOW
:IGNORED
:ONLYDIR
:DONT-FOLLOW
:MASK-ADD
:ISDIR
:ONESHOT
:ALL-EVENTS
The INOTIFY-EVENT
structure has the slots WD
, MASK
, COOKIE
and
NAME
(with default CONC-NAME
: INOTIFY-EVENT-
).
The INOTIFY-INSTANCE
structure has the slots FD
, STREAM
and
NONBLOCKING
with CONC-NAME
INOTIFY-
.
The REGISTERED-INOTIFY-INSTANCE
includes the previous structure and
only adds the WATCHED
slot under the same CONC-NAME
.
- more functionality to examine read events
- extend to other APIs?
- make things more implementation independent (partly done, still needs fd-streams everywhere, or skip them entirely)
- (maybe) don't use the libc for this, direct syscall
- (maybe) add iolib replacement for io functions
- the nonblocking mode is pretty useless, because for one the READ functions still block and also LISTEN seems to work just fine and it's not even needed for multiplexing, so why keep this in?