(sagittarius filewatch) - Monitoring filesystem

Library (sagittarius filewatch)

Monitoring filesystem cannot be done efficiently without support of underlying operating system. This library provides unified interface of the mechanism.

The following simple tail (1) like script shows how it works:

(import (rnrs) (getopt) (sagittarius filewatch) (prefix (binary io) binary:))

(define (tail file offset)
  (define watcher (make-filesystem-watcher))
  (define in (open-file-input-port file))
  ;; dump contents to stdout
  (define (dump)
    (let loop ()
      (let ((line (binary:get-line in)))
        (unless (eof-object? line) 
          (put-bytevector (standard-output-port) line)
          (put-bytevector (standard-output-port) #vu8(10))
          (loop)))))
  (define size (file-size-in-bytes file))
  ;; move port position if the size if more than offset
  (when (> size offset) (set-port-position! in (- size offset)))
  ;; dump first
  (dump)
  ;; add path to file watcher
  (filesystem-watcher-add-path! watcher file '(modify) 
                                (lambda (path event) (dump)))
  ;; monitor on foreground.
  (filesystem-watcher-start-monitoring! watcher :background #f))

;; this tail is not line oriented
;; it shows tail of the file from the given offset.
(define (main args)
  (with-args (cdr args)
      ((offset (#\o "offset") #t "1024")
       . rest)
    (tail (car rest) (string->number offset))))
Function make-filesystem-watcher :key error-handler

Creates and returns filesystem watcher object.

The keyword argument error-handler is specified, which must be a procedure accepts one argument, then it is called with a condition when monitoring handler raised an error.

Function release-filesystem-watcher! watcher

Releasing the watcher.

Released filesystem watcher can not be reused.

Function filesystem-watcher? o

Returns #t if the given o is a filesystem watcher object, otherwise #f.

Function filesystem-watcher-add-path! watcher path flags monitoring-handler

Adds monitoring targets to the watcher.

The path must be a string and indicating existing path.

The flags must be one of the following symbols or list of the symbols:

access

Checks if the path is accessed.

modify

Checks if the path is modified.

delete

Checks if the path is deleted.

move

Checks if the path is moved.

attribute

Checks if the path's attribute is changed.

NOTE: The flags might not be supported depending on the platform. See implementation limitation section for more details.

The monitoring-handler must be a procedure accepts 2 arguments. The procedure is called if the path gets an event specified flags. When the monitoring-handler is invoked, then the path and a symbol of the invoking event are passed respectively. The possible event symbols are the followings:

accessed

Checks if the path is accessed.

modified

Checks if the path is modified.

deleted

Checks if the path is deleted.

moved

Checks if the path is moved.

attribute

Checks if the path's attribute is changed.

The procedure filesystem-watcher-add-path! returns the watcher.

If the watcher started monitoring, then the procedure raises &assertion.

Function filesystem-watcher-remove-path! watcher path

Removes given path from the watcher. And returns watcher,

If the watcher started monitoring, then the procedure raises &assertion.

Function filesystem-watcher-start-monitoring! watcher :key (background #t)

Starts monitoring filesystem on given watcher.

If the keyword argument background is true value, then the procedure creates a thread and let the thread monitor the filesystem. (So the procedure returns after the thread invocation.) Otherwise, the procedure blocks and wait until other thread calls filesystem-watcher-stop-monitoring!.

Function filesystem-watcher-stop-monitoring! watcher

Stops monitoring of given watcher.

If the watcher is started on background, then the monitoring thread may not stop immediately.

Implementation limitation

Even the library provides unified APIs however users still should know the limitations per operating system to avoid unexpected behaviours. The following sections describes the known limitations.

Linux

On Linux, the library is constructed on top of inotify (7) and poll (2). If users add too many paths, then it may reach the maximum number of watch descriptor.

The IN_MOVED_FROM and IN_MOVED_TO flags are passed as moved. So it is users responsibility to detect which file is moved from and which file is moved to.

BSD Unix

On BSD Unix, the library is constructed on top of kqueue (2). This implementation contains 3 major issues. Possibility of number of file descriptor explosion, not access flag support, and no support of directory monitoring.

The kqueue requires file descriptor per monitoring path. Thus if the number of paths is large, then it reaches the maxinum number of file descriptors. (NB: kern.maxfiles on FreeBSD).

kqueue doesn't support path access monitoring (e.g. IN_ACCESS on inotify). So it is impossible to monitor file access.

Current implementation of (sagittarius filewatch) using kqueuedoesn't allow users to monitor directory. This is because by default, kqueue doesn't provide facility to detect which file is added. To do it, we need manual management. To keep our code as simple as possible, we decided not to do it for now. This decision may be changed if there's enough demands.

OS X

On OS X, the library is constructed on top of kqueue, thus the same limitation as BSD Unix is applied.

Windows

Staring Windows Vista, Microsoft decided not to change timestamp just accessing the file or directory by default. So access flag may or may not work on Windows depending on the configuration of the platform.

Due to the lack of deletion detect, delete and move work the same. Thus the monitoring handler may get both deleted and movedeven though it's only specified delete or move.