From 5d80fe9273977aa7c21f26f3481c013086c0ac53 Mon Sep 17 00:00:00 2001 From: Olof-Joachim Frahm Date: Sat, 25 Feb 2012 11:46:35 +0100 Subject: [PATCH] Added two macros; more intro for the README. Also exported more symbols and update copyright info. --- README.md | 143 +++++++++++++++++++++++++++++++++++++++++++++++--------- cl-inotify.asd | 2 +- inotify.lisp | 38 +++++++++++++-- package.lisp | 20 +++++++- 4 files changed, 176 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 655aaf2..457cfe6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ CL-INOTIFY - Interface to the Linux inotify(7) API. -Copyright (C) 2011 Olof-Joachim Frahm +Copyright (C) 2011-12 Olof-Joachim Frahm Released under a Simplified BSD license. @@ -9,24 +9,76 @@ Working, but unfinished. Implementations currently running on: SBCL. Uses CFFI, binary-types (from [my Github][1] or see [CLiki][2]) and -trivial-utf-8. Doesn't use iolib, because we don't need most of the +trivial-utf-8. Doesn't use iolib, because I don't need most of the functionality, although it might gain us some implementation -independence (patches which can be conditionally compiled are welcome). +independence (patches which can be conditionally compiled are most +welcome; in any case patches are welcome). A similar package is at [stassats Github][3]. +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. + + +# REPLSHOT + +Macros make keeping track easier, so the following example is +straightforward: + + > (with-inotify (inotify T ("." :all-events)) + > (loop (format T "~{~A~%~}" (next-events inotify)))) + > => + > #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. + + +# LOWER-LEVEL USAGE EXAMPLE + +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*) + # HOWTO +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 currently determines if we use the -standard `CL:LISTEN` function or `SB-UNIX:UNIX-READ` to check for -available events. +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) -manpage; you can use a combination of the flags (as keywords) to create +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. @@ -61,19 +113,68 @@ The raw API, which doesn't register watched paths, consists of they're exported in case someone doesn't like the upper layers. +# EVENT-BASED PROCESSING + 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. - - -# EXAMPLE - - > (use-package '#:cl-inotify) - > (defvar *tmp*) - > (setf *tmp* (make-notify)) - > (watch *tmp* "/var/tmp/" :all-events) - > (next-events *tmp*) - > (close-inotify *tmp*) +afterwards. Currently no such functionality is integrated here, however +the following sketch shows how something can be accomplished using +implementation-specific and -internal functionality (since I couldn't +yet get iolib to build again on my system). So, given SBCL, we register +ourselves for event notification on the inotify file descriptor: + + > (with-unregistered-inotify (inotify T ("." :all-events)) + > (flet ((inotify-input (handler) + > (declare (ignore handler)) + > (format T "~{~A~%~}" (next-events inotify)))) + > (sb-sys:with-fd-handler ((inotify-fd inotify) :input #'inotify-input) + > (loop (sb-sys:serve-all-events 1))))) + +Note that we perform all inotify business only when something happens in +that directory, so instead of that loop 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. + + +# REFERENCE + +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`. # TODO @@ -83,9 +184,9 @@ afterwards. Currently no such functionality is integrated here. - make things more implementation independent - (maybe) don't use the libc for this, direct syscall - (maybe) add iolib replacement for io functions -- easier interface for (e)poll/select maybe using iolib (done, using - CL:LISTEN and/or SB-UNIX:UNIX-READ) - +- easier interface for (e)poll/select maybe using iolib (done partly + using CL:LISTEN and/or SB-UNIX:UNIX-READ) +- add some cleanup handler to close queue on garbage collection [1]: https://github.com/Ferada/binary-types [2]: http://www.cliki.net/Binary-types diff --git a/cl-inotify.asd b/cl-inotify.asd index 79aaa1b..f35ddbf 100644 --- a/cl-inotify.asd +++ b/cl-inotify.asd @@ -1,6 +1,6 @@ ;;; -*- mode: lisp; syntax: common-lisp; coding: utf-8; package: cl-user; -*- -;; Copyright (c) 2011, Olof-Joachim Frahm +;; Copyright (c) 2011-12, Olof-Joachim Frahm ;; All rights reserved. ;; Redistribution and use in source and binary forms, with or without diff --git a/inotify.lisp b/inotify.lisp index 9277300..3ff12d7 100644 --- a/inotify.lisp +++ b/inotify.lisp @@ -1,6 +1,6 @@ ;;; -*- mode: lisp; syntax: common-lisp; coding: utf-8; package: cl-inotify; -*- -;; Copyright (c) 2011, Olof-Joachim Frahm +;; Copyright (c) 2011-12, Olof-Joachim Frahm ;; All rights reserved. ;; Redistribution and use in source and binary forms, with or without @@ -209,9 +209,9 @@ the file descriptor is set to non-blocking I/O." (sb-posix:close fd))))) inotify) -(defun make-unregistered-inotify () +(defun make-unregistered-inotify (&optional (nonblocking T)) "Creates a new unregistered INOTIFY instance." - (init-unregistered-inotify (make-inotify-instance))) + (init-unregistered-inotify (make-inotify-instance) nonblocking)) (defun close-inotify (inotify) "Closes the inotify event queue." @@ -377,3 +377,35 @@ terminates if no events are available." (loop while (event-available-p inotify) collect (read-event inotify))) + +;;; this has the longer name, because this way you actually have to read +;;; about the differences, at least i hope so +(defmacro with-unregistered-inotify ((&optional inotify (nonblocking T) &rest rest) &body body) + "Like WITH-INOTIFY, but uses MAKE-UNREGISTERED-INOTIFY and WATCH-RAW +instead. Useful if you need to monitor just a fixed set of paths." + `(let* ((,inotify (make-unregistered-inotify ,nonblocking))) + (unwind-protect + (progn + ,.(mapcar (lambda (specifier) + `(watch-raw ,inotify ,.specifier)) + rest) + ,.body) + (close-inotify ,inotify)))) + +(defmacro with-inotify ((&optional inotify (nonblocking T) &rest rest) &body body) + "Executes BODY with a newly created queue bound to INOTIFY if true. +See MAKE-INOTIFY for more information about possible arguments. + +The REST is a list of argument forms for the WATCH function, i.e. one or +more forms (PATHNAME FLAGS &KEY (REPLACE-P T)). + +Since the QUEUE is closed on unwinding, this macro doesn't bother with +UNWATCH calls on all WATCHed paths." + `(let* ((,inotify (make-inotify ,nonblocking))) + (unwind-protect + (progn + ,.(mapcar (lambda (specifier) + `(watch ,inotify ,.specifier)) + rest) + ,.body) + (close-inotify ,inotify)))) diff --git a/package.lisp b/package.lisp index 1d29263..1951899 100644 --- a/package.lisp +++ b/package.lisp @@ -1,6 +1,6 @@ ;;; -*- mode: lisp; syntax: common-lisp; coding: utf-8; package: cl-user; -*- -;; Copyright (c) 2011, Olof-Joachim Frahm +;; Copyright (c) 2011-12, Olof-Joachim Frahm ;; All rights reserved. ;; Redistribution and use in source and binary forms, with or without @@ -38,14 +38,26 @@ ;;; very raw #:read-raw-event-from-stream + ;;; basic stuff #:close-inotify + ;;; inotify accessors + #:inotify-fd + #:inotify-stream + #:inotify-nonblocking + ;;; event parsing functions #:make-unregistered-inotify #:read-event-from-stream #:watch-raw #:unwatch-raw + ;;; event accessors + #:inotify-event-wd + #:inotify-event-mask + #:inotify-event-cookie + #:inotify-event-name + ;;; enhanced functionality #:make-inotify #:pathname-handle/flags @@ -59,5 +71,9 @@ ;;; convenience functions #:list-watched #:do-events - #:next-events) + #:next-events + + ;;; macros + #:with-inotify + #:with-unregistered-inotify) (:documentation "A binding (not only?) for the LINUX inotify(7) API.")) -- 1.7.10.4