]> code.delx.au - gnu-emacs/commitdiff
Merge branch 'feature/standard-test-location'
authorPhillip Lord <phillip.lord@russet.org.uk>
Thu, 26 Nov 2015 21:11:29 +0000 (21:11 +0000)
committerPhillip Lord <phillip.lord@russet.org.uk>
Thu, 26 Nov 2015 21:11:29 +0000 (21:11 +0000)
14 files changed:
configure.ac
doc/lispref/os.texi
etc/NEWS
lisp/emacs-lisp/eieio-core.el
lisp/emacs-lisp/eieio.el
lisp/filenotify.el
src/Makefile.in
src/emacs.c
src/inotify.c
src/keyboard.c
src/kqueue.c [new file with mode: 0644]
src/lisp.h
test/lisp/autorevert-tests.el
test/lisp/filenotify-tests.el

index 2d832d03ed9eb574cca7b00d86651bdb12689ccd..0b7b4032482abcfd8e082696291c8e417373e7ba 100644 (file)
@@ -355,17 +355,18 @@ OPTION_DEFAULT_ON([gnutls],[don't use -lgnutls for SSL/TLS support])
 OPTION_DEFAULT_ON([zlib],[don't compile with zlib decompression support])
 
 AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
- [use a file notification library (LIB one of: yes, gfile, inotify, w32, no)])],
+ [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])],
  [ case "${withval}" in
     y | ye | yes )     val=yes ;;
     n | no )           val=no  ;;
-    g | gf | gfi | gfil | gfile )      val=gfile ;;
     i | in | ino | inot | inoti | inotif | inotify )   val=inotify ;;
+    k | kq | kqu | kque | kqueu | kqueue )     val=kqueue ;;
+    g | gf | gfi | gfil | gfile )      val=gfile ;;
     w | w3 | w32 )     val=w32 ;;
     * ) AC_MSG_ERROR(['--with-file-notification=$withval' is invalid;
-this option's value should be 'yes', 'no', 'gfile', 'inotify' or 'w32'.
+this option's value should be 'yes', 'no', 'inotify', 'kqeue', 'gfile' or 'w32'.
 'yes' is a synonym for 'w32' on MS-Windows, for 'no' on Nextstep,
-otherwise for the first of 'inotify' or 'gfile' that is usable.])
+otherwise for the first of 'inotify', 'kqueue' or 'gfile' that is usable.])
     ;;
    esac
    with_file_notification=$val
@@ -2690,12 +2691,6 @@ AC_SUBST(LIBGNUTLS_CFLAGS)
 NOTIFY_OBJ=
 NOTIFY_SUMMARY=no
 
-dnl FIXME?  Don't auto-detect on NS, but do allow someone to specify
-dnl a particular library.  This doesn't make much sense?
-if test "${HAVE_NS}" = yes && test ${with_file_notification} = yes; then
-  with_file_notification=no
-fi
-
 dnl MS Windows native file monitor is available for mingw32 only.
 case $with_file_notification,$opsys in
   w32,cygwin)
@@ -2726,16 +2721,41 @@ case $with_file_notification,$NOTIFY_OBJ in
     fi ;;
 esac
 
+dnl kqueue is available on BSD-like systems.
+case $with_file_notification,$NOTIFY_OBJ in
+  kqueue,* | yes,)
+    EMACS_CHECK_MODULES([KQUEUE], [libkqueue])
+    if test "$HAVE_KQUEUE" = "yes"; then
+       AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.])
+       CPPFLAGS="$CPPFLAGS -I/usr/include/kqueue"
+       NOTIFY_CFLAGS=$KQUEUE_CFLAGS
+       NOTIFY_LIBS=$KQUEUE_LIBS
+       NOTIFY_OBJ=kqueue.o
+       NOTIFY_SUMMARY="yes -lkqueue"
+    else
+       AC_SEARCH_LIBS(kqueue, [])
+       if test "$ac_cv_search_kqueue" != no; then
+         AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.])
+        NOTIFY_OBJ=kqueue.o
+        NOTIFY_SUMMARY="yes (kqueue)"
+       fi
+    fi ;;
+esac
+
 dnl g_file_monitor exists since glib 2.18.  G_FILE_MONITOR_EVENT_MOVED
 dnl has been added in glib 2.24.  It has been tested under
 dnl GNU/Linux only.
 case $with_file_notification,$NOTIFY_OBJ in
   gfile,* | yes,)
-    EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24])
-    if test "$HAVE_GFILENOTIFY" = "yes"; then
-       AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.])
-       NOTIFY_OBJ=gfilenotify.o
-       NOTIFY_SUMMARY="yes -lgio (gfile)"
+    if test "${HAVE_NS}" != yes; then
+       EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24])
+       if test "$HAVE_GFILENOTIFY" = "yes"; then
+         AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.])
+         NOTIFY_CFLAGS=$GFILENOTIFY_CFLAGS
+         NOTIFY_LIBS=$GFILENOTIFY_LIBS
+         NOTIFY_OBJ=gfilenotify.o
+         NOTIFY_SUMMARY="yes -lgio (gfile)"
+       fi
     fi ;;
 esac
 
@@ -2747,9 +2767,9 @@ esac
 if test -n "$NOTIFY_OBJ"; then
    AC_DEFINE(USE_FILE_NOTIFY, 1, [Define to 1 if using file notifications.])
 fi
+AC_SUBST(NOTIFY_CFLAGS)
+AC_SUBST(NOTIFY_LIBS)
 AC_SUBST(NOTIFY_OBJ)
-AC_SUBST(GFILENOTIFY_CFLAGS)
-AC_SUBST(GFILENOTIFY_LIBS)
 
 dnl Do not put whitespace before the #include statements below.
 dnl Older compilers (eg sunos4 cc) choke on it.
@@ -4066,8 +4086,8 @@ OLDCFLAGS="$CFLAGS"
 OLDLIBS="$LIBS"
 CFLAGS="$CFLAGS $GTK_CFLAGS $RSVG_CFLAGS $DBUS_CFLAGS $SETTINGS_CFLAGS"
 LIBS="$LIBS $GTK_LIBS $RSVG_LIBS $DBUS_LIBS $SETTINGS_LIBS"
-CFLAGS="$CFLAGS $GFILENOTIFY_CFLAGS $CAIRO_CFLAGS"
-LIBS="$LIBS $GFILENOTIFY_LIBS $CAIRO_LIBS"
+CFLAGS="$CFLAGS $NOTIFY_CFLAGS $CAIRO_CFLAGS"
+LIBS="$LIBS $NOTIFY_LIBS $CAIRO_LIBS"
 AC_MSG_CHECKING([whether GLib is linked in])
 AC_LINK_IFELSE([AC_LANG_PROGRAM(
        [[#include <glib.h>
index f3c4e29cca26f5a5b08a90505cfb04064566beae..17a0b47ad064e858f427959de032cd2b66ca7309 100644 (file)
@@ -2640,9 +2640,9 @@ This function removes the tray notification given by its unique
 
 Several operating systems support watching of filesystems for changes
 of files.  If configured properly, Emacs links a respective library
-like @file{gfilenotify}, @file{inotify}, or @file{w32notify}
-statically.  These libraries enable watching of filesystems on the
-local machine.
+like @file{inotify}, @file{kqueue}, @file{gfilenotify}, or
+@file{w32notify} statically.  These libraries enable watching of
+filesystems on the local machine.
 
 It is also possible to watch filesystems on remote machines,
 @pxref{Remote Files,, Remote Files, emacs, The GNU Emacs Manual}
@@ -2713,7 +2713,8 @@ watching @var{file} has been stopped
 Note that the @file{w32notify} library does not report
 @code{attribute-changed} events.  When some file's attribute, like
 permissions or modification time, has changed, this library reports a
-@code{changed} event.
+@code{changed} event.  Likewise, the @file{kqueue} library does not
+report reliably file attribute changes when watching a directory.
 
 The @code{stopped} event reports, that watching the file has been
 stopped.  This could be because @code{file-notify-rm-watch} was called
@@ -2752,7 +2753,7 @@ being reported.  For example:
 @group
 (write-region "bla" nil "/tmp/foo")
      @result{} Event (35025468 created "/tmp/.#foo")
-        Event (35025468 changed "/tmp/foo") [2 times]
+        Event (35025468 changed "/tmp/foo")
         Event (35025468 deleted "/tmp/.#foo")
 @end group
 
@@ -2798,14 +2799,14 @@ also makes it invalid.
 @example
 @group
 (make-directory "/tmp/foo")
-     @result{} nil
+     @result{} Event (35025468 created "/tmp/foo")
 @end group
 
 @group
 (setq desc
       (file-notify-add-watch
         "/tmp/foo" '(change) 'my-notify-callback))
-     @result{} 35025468
+     @result{} 11359632
 @end group
 
 @group
@@ -2815,32 +2816,34 @@ also makes it invalid.
 
 @group
 (write-region "bla" nil "/tmp/foo/bla")
-     @result{} Event (35025468 created "/tmp/foo/.#bla")
-        Event (35025468 created "/tmp/foo/bla")
-        Event (35025468 changed "/tmp/foo/bla")
-        Event (35025468 changed "/tmp/foo/.#bla")
+     @result{} Event (11359632 created "/tmp/foo/.#bla")
+        Event (11359632 created "/tmp/foo/bla")
+        Event (11359632 changed "/tmp/foo/bla")
+        Event (11359632 deleted "/tmp/foo/.#bla")
 @end group
 
 @group
 ;; Deleting a file in the directory doesn't invalidate the watch.
 (delete-file "/tmp/foo/bla")
-     @result{} Event (35025468 deleted "/tmp/foo/bla")
+     @result{} Event (11359632 deleted "/tmp/foo/bla")
 @end group
 
 @group
 (write-region "bla" nil "/tmp/foo/bla")
-     @result{} Event (35025468 created "/tmp/foo/.#bla")
-        Event (35025468 created "/tmp/foo/bla")
-        Event (35025468 changed "/tmp/foo/bla")
-        Event (35025468 changed "/tmp/foo/.#bla")
+     @result{} Event (11359632 created "/tmp/foo/.#bla")
+        Event (11359632 created "/tmp/foo/bla")
+        Event (11359632 changed "/tmp/foo/bla")
+        Event (11359632 deleted "/tmp/foo/.#bla")
 @end group
 
 @group
 ;; Deleting the directory invalidates the watch.
+;; Events arrive for different watch descriptors.
 (delete-directory "/tmp/foo" 'recursive)
-     @result{} Event (35025468 deleted "/tmp/foo/bla")
-        Event (35025468 deleted "/tmp/foo")
-        Event (35025468 stopped "/tmp/foo")
+     @result{} Event (35025468 deleted "/tmp/foo")
+        Event (11359632 deleted "/tmp/foo/bla")
+        Event (11359632 deleted "/tmp/foo")
+        Event (11359632 stopped "/tmp/foo")
 @end group
 
 @group
index 46910b021c7da74e94ef0f9d6da8dbb9c286dd02..1ef496c62d0e6cbe3d91f843f38e72d69167a05e 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -22,6 +22,19 @@ Temporary note:
 When you add a new item, use the appropriate mark if you are sure it applies,
 otherwise leave it unmarked.
 
+\f
+* Installation Changes in Emacs 25.2
+
+** 'configure' detects the kqueue file notification library on *BSD
+and Mac OS X machines.
+
+\f
+* Changes in Specialized Modes and Packages in Emacs 25.2
+
+** File Notifications
+
+*** The kqueue library is integrated for *BSD and Mac OS X machines.
+
 \f
 * Installation Changes in Emacs 25.1
 
index dcaaab69cf5c013ce7ee89dac662731cac9b06c1..c54cd22115df18c3cd9dbbe2042604ca01448d6f 100644 (file)
@@ -756,9 +756,7 @@ Argument FN is the function calling this verifier."
          ;; The slot-missing method is a cool way of allowing an object author
          ;; to intercept missing slot definitions.  Since it is also the LAST
          ;; thing called in this fn, its return value would be retrieved.
-         (slot-missing obj slot 'oref)
-         ;;(signal 'invalid-slot-name (list (eieio-object-name obj) slot))
-         )
+         (slot-missing obj slot 'oref))
       (cl-check-type obj eieio-object)
       (eieio-barf-if-slot-unbound (aref obj c) obj slot 'oref))))
 
@@ -780,9 +778,7 @@ Fills in OBJ's SLOT with its default value."
            ;; Oref that slot.
            (aref (eieio--class-class-allocation-values cl)
                  c)
-         (slot-missing obj slot 'oref-default)
-         ;;(signal 'invalid-slot-name (list (class-name cl) slot))
-         )
+         (slot-missing obj slot 'oref-default))
       (eieio-barf-if-slot-unbound
        (let ((val (cl--slot-descriptor-initform
                    (aref (eieio--class-slots cl)
@@ -822,9 +818,7 @@ Fills in OBJ's SLOT with VALUE."
              (aset (eieio--class-class-allocation-values class)
                    c value))
          ;; See oref for comment on `slot-missing'
-         (slot-missing obj slot 'oset value)
-         ;;(signal 'invalid-slot-name (list (eieio-object-name obj) slot))
-         )
+         (slot-missing obj slot 'oset value))
       (eieio--validate-slot-value class c value slot)
       (aset obj c value))))
 
index 790e8bc9e0e3cce930cc0551c7630b90b59c0f0a..0ec07fe7b41cf687b399f3d2a4a3ce5930f68039 100644 (file)
@@ -678,7 +678,8 @@ This class is not stored in the `parent' slot of a class vector."
 
 (setq eieio-default-superclass (cl--find-class 'eieio-default-superclass))
 
-(defalias 'standard-class 'eieio-default-superclass)
+(define-obsolete-function-alias 'standard-class
+  'eieio-default-superclass "25.2")
 
 (cl-defgeneric make-instance (class &rest initargs)
   "Make a new instance of CLASS based on INITARGS.
@@ -765,11 +766,7 @@ dynamically set from SLOTS."
   ;; Shared initialize will parse our slots for us.
   (shared-initialize this slots))
 
-(cl-defgeneric slot-missing (object slot-name operation &optional new-value)
-  "Method invoked when an attempt to access a slot in OBJECT fails.")
-
-(cl-defmethod slot-missing ((object eieio-default-superclass) slot-name
-                        _operation &optional _new-value)
+(cl-defgeneric slot-missing (object slot-name _operation &optional _new-value)
   "Method invoked when an attempt to access a slot in OBJECT fails.
 SLOT-NAME is the name of the failed slot, OPERATION is the type of access
 that was requested, and optional NEW-VALUE is the value that was desired
@@ -777,8 +774,9 @@ to be set.
 
 This method is called from `oref', `oset', and other functions which
 directly reference slots in EIEIO objects."
-  (signal 'invalid-slot-name (list (eieio-object-name object)
-                                  slot-name)))
+  (signal 'invalid-slot-name
+          (list (if (eieio-object-p object) (eieio-object-name object) object)
+                slot-name)))
 
 (cl-defgeneric slot-unbound (object class slot-name fn)
   "Slot unbound is invoked during an attempt to reference an unbound slot.")
@@ -815,22 +813,19 @@ first and modify the returned object.")
     (if params (shared-initialize nobj params))
     nobj))
 
-(cl-defgeneric destructor (this &rest params)
-  "Destructor for cleaning up any dynamic links to our object.")
-
-(cl-defmethod destructor ((_this eieio-default-superclass) &rest _params)
-  "Destructor for cleaning up any dynamic links to our object.
-Argument THIS is the object being destroyed.  PARAMS are additional
-ignored parameters."
+(cl-defgeneric destructor (_this &rest _params)
+  "Destructor for cleaning up any dynamic links to our object."
+  (declare (obsolete nil "25.2"))
   ;; No cleanup... yet.
-  )
+  nil)
 
-(cl-defgeneric object-print (this &rest strings)
-  "Pretty printer for object THIS.  Call function `object-name' with STRINGS.
+(cl-defgeneric object-print (this &rest _strings)
+  "Pretty printer for object THIS.
 
 It is sometimes useful to put a summary of the object into the
 default #<notation> string when using EIEIO browsing tools.
-Implement this method to customize the summary.")
+Implement this method to customize the summary."
+  (format "%S" this))
 
 (cl-defmethod object-print ((this eieio-default-superclass) &rest strings)
   "Pretty printer for object THIS.  Call function `object-name' with STRINGS.
@@ -938,11 +933,12 @@ this object."
 \f
 ;;; Unimplemented functions from CLOS
 ;;
-(defun change-class (_obj _class)
+(defun eieio-change-class (_obj _class)
   "Change the class of OBJ to type CLASS.
 This may create or delete slots, but does not affect the return value
 of `eq'."
   (error "EIEIO: `change-class' is unimplemented"))
+(define-obsolete-function-alias 'change-class 'eieio-change-class "25.2")
 
 ;; Hook ourselves into help system for describing classes and methods.
 ;; FIXME: This is not actually needed any more since we can click on the
index 4c5d43fb44eaca7156e553c862460197e141e22c..b6c1f686fe15b4457da4fa1f71d76c0af41f9d6c 100644 (file)
 ;;; Commentary
 
 ;; This package is an abstraction layer from the different low-level
-;; file notification packages `gfilenotify', `inotify' and
+;; file notification packages `inotify', `kqueue', `gfilenotify' and
 ;; `w32notify'.
 
 ;;; Code:
 
 (defconst file-notify--library
   (cond
-   ((featurep 'gfilenotify) 'gfilenotify)
    ((featurep 'inotify) 'inotify)
+   ((featurep 'kqueue) 'kqueue)
+   ((featurep 'gfilenotify) 'gfilenotify)
    ((featurep 'w32notify) 'w32notify))
   "Non-nil when Emacs has been compiled with file notification support.
 The value is the name of the low-level file notification package
@@ -40,25 +41,24 @@ could use another implementation.")
 (defvar file-notify-descriptors (make-hash-table :test 'equal)
   "Hash table for registered file notification descriptors.
 A key in this hash table is the descriptor as returned from
-`gfilenotify', `inotify', `w32notify' or a file name handler.
-The value in the hash table is a list
+`inotify', `kqueue', `gfilenotify', `w32notify' or a file name
+handler.  The value in the hash table is a list
 
   (DIR (FILE . CALLBACK) (FILE . CALLBACK) ...)
 
 Several values for a given DIR happen only for `inotify', when
 different files from the same directory are watched.")
 
-(defun file-notify--rm-descriptor (descriptor &optional what)
+(defun file-notify--rm-descriptor (descriptor)
   "Remove DESCRIPTOR from `file-notify-descriptors'.
 DESCRIPTOR should be an object returned by `file-notify-add-watch'.
-If it is registered in `file-notify-descriptors', a stopped event is sent.
-WHAT is a file or directory name to be removed, needed just for `inotify'."
+If it is registered in `file-notify-descriptors', a stopped event is sent."
   (let* ((desc (if (consp descriptor) (car descriptor) descriptor))
         (file (if (consp descriptor) (cdr descriptor)))
          (registered (gethash desc file-notify-descriptors))
         (dir (car registered)))
 
-    (when (and (consp registered) (or (null what) (string-equal dir what)))
+    (when (consp registered)
       ;; Send `stopped' event.
       (dolist (entry (cdr registered))
        (funcall (cdr entry)
@@ -76,7 +76,8 @@ WHAT is a file or directory name to be removed, needed just for `inotify'."
            (remhash desc file-notify-descriptors)
          (puthash desc registered file-notify-descriptors))))))
 
-;; This function is used by `gfilenotify', `inotify' and `w32notify' events.
+;; This function is used by `inotify', `kqueue', `gfilenotify' and
+;; `w32notify' events.
 ;;;###autoload
 (defun file-notify-handle-event (event)
   "Handle file system monitoring event.
@@ -159,7 +160,7 @@ EVENT is the cadr of the event in `file-notify-handle-event'
        (setq actions nil))
 
       ;; Loop over actions.  In fact, more than one action happens only
-      ;; for `inotify'.
+      ;; for `inotify' and `kqueue'.
       (dolist (action actions)
 
        ;; Send pending event, if it doesn't match.
@@ -184,19 +185,17 @@ EVENT is the cadr of the event in `file-notify-handle-event'
        ;; Map action.  We ignore all events which cannot be mapped.
        (setq action
              (cond
-              ;; gfilenotify.
-              ((memq action '(attribute-changed changed created deleted))
+              ((memq action
+                      '(attribute-changed changed created deleted renamed))
                action)
-              ((eq action 'moved)
+              ((memq action '(moved rename))
                (setq file1 (file-notify--event-file1-name event))
                'renamed)
-
-              ;; inotify, w32notify.
               ((eq action 'ignored)
                 (setq stopped t actions nil))
-              ((eq action 'attrib) 'attribute-changed)
+              ((memq action '(attrib link)) 'attribute-changed)
               ((memq action '(create added)) 'created)
-              ((memq action '(modify modified)) 'changed)
+              ((memq action '(modify modified write)) 'changed)
               ((memq action '(delete delete-self move-self removed)) 'deleted)
               ;; Make the event pending.
               ((memq action '(moved-from renamed-from))
@@ -236,7 +235,6 @@ EVENT is the cadr of the event in `file-notify-handle-event'
           (setq pending-event nil))
 
         ;; Check for stopped.
-       ;;(message "file-notify-callback %S %S" file registered)
         (setq
          stopped
          (or
@@ -244,10 +242,13 @@ EVENT is the cadr of the event in `file-notify-handle-event'
           (and
            (memq action '(deleted renamed))
            (= (length (cdr registered)) 1)
-           (string-equal
-            (file-name-nondirectory file)
-           (or (file-name-nondirectory (car registered))
-               (car (cadr registered)))))))
+           (or
+            (string-equal
+             (file-name-nondirectory file)
+            (file-name-nondirectory (car registered)))
+            (string-equal
+             (file-name-nondirectory file)
+             (car (cadr registered)))))))
 
        ;; Apply callback.
        (when (and action
@@ -258,10 +259,17 @@ EVENT is the cadr of the event in `file-notify-handle-event'
                    ;; File matches.
                    (string-equal
                     (nth 0 entry) (file-name-nondirectory file))
+                   ;; Directory matches.
+                   (string-equal
+                    (file-name-nondirectory file)
+                    (file-name-nondirectory (car registered)))
                    ;; File1 matches.
                    (and (stringp file1)
                         (string-equal
                          (nth 0 entry) (file-name-nondirectory file1)))))
+          ;;(message
+           ;;"file-notify-callback %S %S %S %S %S"
+           ;;(file-notify--descriptor desc file) action file file1 registered)
          (if file1
              (funcall
               callback
@@ -272,11 +280,10 @@ EVENT is the cadr of the event in `file-notify-handle-event'
 
       ;; Modify `file-notify-descriptors'.
       (when stopped
-        (file-notify--rm-descriptor
-         (file-notify--descriptor desc file) file)))))
+        (file-notify-rm-watch (file-notify--descriptor desc file))))))
 
-;; `gfilenotify' and `w32notify' return a unique descriptor for every
-;; `file-notify-add-watch', while `inotify' returns a unique
+;; `kqueue', `gfilenotify' and `w32notify' return a unique descriptor
+;; for every `file-notify-add-watch', while `inotify' returns a unique
 ;; descriptor per inode only.
 (defun file-notify-add-watch (file flags callback)
   "Add a watch for filesystem events pertaining to FILE.
@@ -329,7 +336,7 @@ FILE is the name of the file whose event is being reported."
               (if (file-directory-p file)
                   file
                 (file-name-directory file))))
-       desc func l-flags registered)
+       desc func l-flags registered entry)
 
     (unless (file-directory-p dir)
       (signal 'file-notify-error `("Directory does not exist" ,dir)))
@@ -338,7 +345,12 @@ FILE is the name of the file whose event is being reported."
        ;; A file name handler could exist even if there is no local
        ;; file notification support.
        (setq desc (funcall
-                   handler 'file-notify-add-watch dir flags callback))
+                   handler 'file-notify-add-watch
+                    ;; kqueue does not report file changes in
+                    ;; directory monitor.  So we must watch the file
+                    ;; itself.
+                    (if (eq file-notify--library 'kqueue) file dir)
+                    flags callback))
 
       ;; Check, whether Emacs has been compiled with file notification
       ;; support.
@@ -349,8 +361,9 @@ FILE is the name of the file whose event is being reported."
       ;; Determine low-level function to be called.
       (setq func
            (cond
-            ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
             ((eq file-notify--library 'inotify) 'inotify-add-watch)
+            ((eq file-notify--library 'kqueue) 'kqueue-add-watch)
+            ((eq file-notify--library 'gfilenotify) 'gfile-add-watch)
             ((eq file-notify--library 'w32notify) 'w32notify-add-watch)))
 
       ;; Determine respective flags.
@@ -362,30 +375,32 @@ FILE is the name of the file whose event is being reported."
           (cond
            ((eq file-notify--library 'inotify)
             '(create delete delete-self modify move-self move))
+           ((eq file-notify--library 'kqueue)
+            '(create delete write extend rename))
            ((eq file-notify--library 'w32notify)
             '(file-name directory-name size last-write-time)))))
        (when (memq 'attribute-change flags)
          (push (cond
                  ((eq file-notify--library 'inotify) 'attrib)
+                 ((eq file-notify--library 'kqueue) 'attrib)
                  ((eq file-notify--library 'w32notify) 'attributes))
                 l-flags)))
 
       ;; Call low-level function.
-      (setq desc (funcall func dir l-flags 'file-notify-callback)))
+      (setq desc (funcall
+                  func (if (eq file-notify--library 'kqueue) file dir)
+                  l-flags 'file-notify-callback)))
 
     ;; Modify `file-notify-descriptors'.
-    (setq registered (gethash desc file-notify-descriptors))
-    (puthash
-     desc
-     `(,dir
-       (,(unless (file-directory-p file) (file-name-nondirectory file))
-       . ,callback)
-       . ,(cdr registered))
-     file-notify-descriptors)
+    (setq file (unless (file-directory-p file) (file-name-nondirectory file))
+         desc (if (consp desc) (car desc) desc)
+         registered (gethash desc file-notify-descriptors)
+         entry `(,file . ,callback))
+    (unless (member entry (cdr registered))
+      (puthash desc `(,dir ,entry . ,(cdr registered)) file-notify-descriptors))
 
     ;; Return descriptor.
-    (file-notify--descriptor
-     desc (unless (file-directory-p file) (file-name-nondirectory file)))))
+    (file-notify--descriptor desc file)))
 
 (defun file-notify-rm-watch (descriptor)
   "Remove an existing watch specified by its DESCRIPTOR.
@@ -410,8 +425,9 @@ DESCRIPTOR should be an object returned by `file-notify-add-watch'."
 
               (funcall
                (cond
-                ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
                 ((eq file-notify--library 'inotify) 'inotify-rm-watch)
+                ((eq file-notify--library 'kqueue) 'kqueue-rm-watch)
+                ((eq file-notify--library 'gfilenotify) 'gfile-rm-watch)
                 ((eq file-notify--library 'w32notify) 'w32notify-rm-watch))
                desc))
           (file-notify-error nil)))
@@ -441,8 +457,9 @@ DESCRIPTOR should be an object returned by `file-notify-add-watch'."
                (funcall handler 'file-notify-valid-p descriptor)
              (funcall
               (cond
-               ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
                ((eq file-notify--library 'inotify) 'inotify-valid-p)
+               ((eq file-notify--library 'kqueue) 'kqueue-valid-p)
+               ((eq file-notify--library 'gfilenotify) 'gfile-valid-p)
                ((eq file-notify--library 'w32notify) 'w32notify-valid-p))
               desc))
            t))))
index d667c55ee33ddee1800f034870e0e071b749556d..d7ad3954579a635b9dba0b969572198ef7165c27 100644 (file)
@@ -160,12 +160,13 @@ SETTINGS_LIBS = @SETTINGS_LIBS@
 ## gtkutil.o if USE_GTK, else empty.
 GTK_OBJ=@GTK_OBJ@
 
-## gfilenotify.o if HAVE_GFILENOTIFY.
 ## inotify.o if HAVE_INOTIFY.
+## kqueue.o if HAVE_KQUEUE.
+## gfilenotify.o if HAVE_GFILENOTIFY.
 ## w32notify.o if HAVE_W32NOTIFY.
 NOTIFY_OBJ = @NOTIFY_OBJ@
-GFILENOTIFY_CFLAGS = @GFILENOTIFY_CFLAGS@
-GFILENOTIFY_LIBS = @GFILENOTIFY_LIBS@
+NOTIFY_CFLAGS = @NOTIFY_CFLAGS@
+NOTIFY_LIBS = @NOTIFY_LIBS@
 
 ## -ltermcap, or -lncurses, or -lcurses, or "".
 LIBS_TERMCAP=@LIBS_TERMCAP@
@@ -355,7 +356,7 @@ ALL_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \
   $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) \
   $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
   $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
-  $(LIBGNUTLS_CFLAGS) $(GFILENOTIFY_CFLAGS) $(CAIRO_CFLAGS) \
+  $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \
   $(WARN_CFLAGS) $(WERROR_CFLAGS) $(CFLAGS)
 ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS)
 
@@ -468,7 +469,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \
    $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \
    $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
    $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) \
-   $(GFILENOTIFY_LIBS) $(LIB_MATH) $(LIBZ)
+   $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ)
 
 $(leimdir)/leim-list.el: bootstrap-emacs$(EXEEXT)
        $(MAKE) -C ../leim leim-list.el EMACS="$(bootstrap_exe)"
index b4052b851d770b0660abf1c6a03b38f80d408849..2e9f950851ad0bd237097d66912e75f0bba671a1 100644 (file)
@@ -1350,6 +1350,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
     tzset ();
 #endif /* MSDOS */
 
+#ifdef HAVE_KQUEUE
+  globals_of_kqueue ();
+#endif
+
 #ifdef HAVE_GFILENOTIFY
   globals_of_gfilenotify ();
 #endif
@@ -1520,14 +1524,18 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
 
       syms_of_gnutls ();
 
-#ifdef HAVE_GFILENOTIFY
-      syms_of_gfilenotify ();
-#endif /* HAVE_GFILENOTIFY */
-
 #ifdef HAVE_INOTIFY
       syms_of_inotify ();
 #endif /* HAVE_INOTIFY */
 
+#ifdef HAVE_KQUEUE
+      syms_of_kqueue ();
+#endif /* HAVE_KQUEUE */
+
+#ifdef HAVE_GFILENOTIFY
+      syms_of_gfilenotify ();
+#endif /* HAVE_GFILENOTIFY */
+
 #ifdef HAVE_DBUS
       syms_of_dbusbind ();
 #endif /* HAVE_DBUS */
index d1a80bbad1bd60d297dbfc7e01c63fddb4f5b223..6577ee28cd135029fe904dc4bd5462e7888daf73 100644 (file)
@@ -46,8 +46,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 static int inotifyfd = -1;
 
 /* Assoc list of files being watched.
-   Format:
-   (watch-descriptor . callback)
+   Format: (watch-descriptor name callback)
  */
 static Lisp_Object watch_list;
 
@@ -106,12 +105,14 @@ inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev)
       name = make_unibyte_string (ev->name, min (len, ev->len));
       name = DECODE_FILE (name);
     }
+  else
+    name = XCAR (XCDR (watch_object));
 
   return list2 (list4 (make_watch_descriptor (ev->wd),
                        mask_to_aspects (ev->mask),
                        name,
                        make_number (ev->cookie)),
-                XCDR (watch_object));
+               Fnth (make_number (2), watch_object));
 }
 
 /* This callback is called when the FD is available for read.  The inotify
@@ -325,7 +326,7 @@ is managed internally and there is no corresponding inotify_init.  Use
       watch_list = Fdelete (watch_object, watch_list);
 
   /* Store watch object in watch list.  */
-  watch_object = Fcons (watch_descriptor, callback);
+  watch_object = list3 (watch_descriptor, encoded_file_name, callback);
   watch_list = Fcons (watch_object, watch_list);
 
   return watch_descriptor;
index 2449abb7dfcbd4472f5eee2923417f04e35dade8..ab7cb34a030f99de07d89795e1547bb66bed43c7 100644 (file)
@@ -5945,12 +5945,12 @@ make_lispy_event (struct input_event *event)
       }
 #endif /* HAVE_DBUS */
 
-#if defined HAVE_GFILENOTIFY || defined HAVE_INOTIFY
+#if defined HAVE_INOTIFY || defined HAVE_KQUEUE || defined HAVE_GFILENOTIFY
     case FILE_NOTIFY_EVENT:
       {
         return Fcons (Qfile_notify, event->arg);
       }
-#endif /* defined HAVE_GFILENOTIFY || defined HAVE_INOTIFY */
+#endif /* HAVE_INOTIFY || HAVE_KQUEUE || HAVE_GFILENOTIFY */
 
     case CONFIG_CHANGED_EVENT:
        return list3 (Qconfig_changed_event,
diff --git a/src/kqueue.c b/src/kqueue.c
new file mode 100644 (file)
index 0000000..1830040
--- /dev/null
@@ -0,0 +1,520 @@
+/* Filesystem notifications support with kqueue API.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#ifdef HAVE_KQUEUE
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include "lisp.h"
+#include "keyboard.h"
+#include "process.h"
+
+\f
+/* File handle for kqueue.  */
+static int kqueuefd = -1;
+
+/* This is a list, elements are (DESCRIPTOR FILE FLAGS CALLBACK [DIRLIST]).  */
+static Lisp_Object watch_list;
+
+/* Generate a list from the directory_files_internal output.
+   Items are (INODE FILE-NAME LAST-MOD LAST-STATUS-MOD SIZE).  */
+Lisp_Object
+kqueue_directory_listing (Lisp_Object directory_files)
+{
+  Lisp_Object dl, result = Qnil;
+
+  for (dl = directory_files; ! NILP (dl); dl = XCDR (dl)) {
+    /* We ignore "." and "..".  */
+    if ((strcmp (".", SSDATA (XCAR (XCAR (dl)))) == 0) ||
+       (strcmp ("..", SSDATA (XCAR (XCAR (dl)))) == 0))
+      continue;
+
+    result = Fcons
+      (list5 (/* inode.  */
+             Fnth (make_number (11), XCAR (dl)),
+             /* filename.  */
+             XCAR (XCAR (dl)),
+             /* last modification time.  */
+             Fnth (make_number (6), XCAR (dl)),
+             /* last status change time.  */
+             Fnth (make_number (7), XCAR (dl)),
+             /* size.  */
+             Fnth (make_number (8), XCAR (dl))),
+       result);
+  }
+  return result;
+}
+
+/* Generate a file notification event.  */
+static void
+kqueue_generate_event
+(Lisp_Object watch_object, Lisp_Object actions,
+ Lisp_Object file, Lisp_Object file1)
+{
+  Lisp_Object flags, action, entry;
+  struct input_event event;
+
+  /* Check, whether all actions shall be monitored.  */
+  flags = Fnth (make_number (2), watch_object);
+  action = actions;
+  do {
+    if (NILP (action))
+      break;
+    entry = XCAR (action);
+    if (NILP (Fmember (entry, flags))) {
+      action = XCDR (action);
+      actions = Fdelq (entry, actions);
+    } else
+      action = XCDR (action);
+  } while (1);
+
+  /* Store it into the input event queue.  */
+  if (! NILP (actions)) {
+    EVENT_INIT (event);
+    event.kind = FILE_NOTIFY_EVENT;
+    event.frame_or_window = Qnil;
+    event.arg = list2 (Fcons (XCAR (watch_object),
+                             Fcons (actions,
+                                    NILP (file1)
+                                    ? Fcons (file, Qnil)
+                                    : list2 (file, file1))),
+                      Fnth (make_number (3), watch_object));
+    kbd_buffer_store_event (&event);
+  }
+}
+
+/* This compares two directory listings in case of a `write' event for
+   a directory.  Generate resulting file notification events.  The old
+   directory listing is retrieved from watch_object, it will be
+   replaced by the new directory listing at the end of this
+   function.  */
+static void
+kqueue_compare_dir_list
+(Lisp_Object watch_object)
+{
+  Lisp_Object dir, pending_dl, deleted_dl;
+  Lisp_Object old_directory_files, old_dl, new_directory_files, new_dl, dl;
+
+  dir = XCAR (XCDR (watch_object));
+  pending_dl = Qnil;
+  deleted_dl = Qnil;
+
+  old_directory_files = Fnth (make_number (4), watch_object);
+  old_dl = kqueue_directory_listing (old_directory_files);
+
+  /* When the directory is not accessible anymore, it has been deleted.  */
+  if (NILP (Ffile_directory_p (dir))) {
+    kqueue_generate_event (watch_object, Fcons (Qdelete, Qnil), dir, Qnil);
+    return;
+  }
+  new_directory_files =
+    directory_files_internal (dir, Qnil, Qnil, Qnil, 1, Qnil);
+  new_dl = kqueue_directory_listing (new_directory_files);
+
+  /* Parse through the old list.  */
+  dl = old_dl;
+  while (1) {
+    Lisp_Object old_entry, new_entry, dl1;
+    if (NILP (dl))
+      break;
+
+    /* Search for an entry with the same inode.  */
+    old_entry = XCAR (dl);
+    new_entry = assq_no_quit (XCAR (old_entry), new_dl);
+    if (! NILP (Fequal (old_entry, new_entry))) {
+      /* Both entries are identical.  Nothing to do.  */
+      new_dl = Fdelq (new_entry, new_dl);
+      goto the_end;
+    }
+
+    /* Both entries have the same inode.  */
+    if (! NILP (new_entry)) {
+      /* Both entries have the same file name.  */
+      if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
+                 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
+       /* Modification time has been changed, the file has been written.  */
+       if (NILP (Fequal (Fnth (make_number (2), old_entry),
+                         Fnth (make_number (2), new_entry))))
+         kqueue_generate_event
+           (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (old_entry)), Qnil);
+       /* Status change time has been changed, the file attributes
+          have changed.  */
+         if (NILP (Fequal (Fnth (make_number (3), old_entry),
+                           Fnth (make_number (3), new_entry))))
+         kqueue_generate_event
+           (watch_object, Fcons (Qattrib, Qnil),
+            XCAR (XCDR (old_entry)), Qnil);
+
+      } else {
+       /* The file has been renamed.  */
+       kqueue_generate_event
+         (watch_object, Fcons (Qrename, Qnil),
+          XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
+       deleted_dl = Fcons (new_entry, deleted_dl);
+      }
+      new_dl = Fdelq (new_entry, new_dl);
+      goto the_end;
+    }
+
+    /* Search, whether there is a file with the same name but another
+       inode.  */
+    for (dl1 = new_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
+      new_entry = XCAR (dl1);
+      if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
+                 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
+       pending_dl = Fcons (new_entry, pending_dl);
+       new_dl = Fdelq (new_entry, new_dl);
+       goto the_end;
+      }
+    }
+
+    /* Check, whether this a pending file.  */
+    new_entry = assq_no_quit (XCAR (old_entry), pending_dl);
+
+    if (NILP (new_entry)) {
+      /* Check, whether this is an already deleted file (by rename).  */
+      for (dl1 = deleted_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
+       new_entry = XCAR (dl1);
+       if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
+                   SSDATA (XCAR (XCDR (new_entry)))) == 0) {
+         deleted_dl = Fdelq (new_entry, deleted_dl);
+         goto the_end;
+       }
+      }
+      /* The file has been deleted.  */
+      kqueue_generate_event
+       (watch_object, Fcons (Qdelete, Qnil), XCAR (XCDR (old_entry)), Qnil);
+
+    } else {
+      /* The file has been renamed.  */
+      kqueue_generate_event
+       (watch_object, Fcons (Qrename, Qnil),
+        XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
+      pending_dl = Fdelq (new_entry, pending_dl);
+    }
+
+  the_end:
+    dl = XCDR (dl);
+    old_dl = Fdelq (old_entry, old_dl);
+  }
+
+  /* Parse through the resulting new list.  */
+  dl = new_dl;
+  while (1) {
+    Lisp_Object entry;
+    if (NILP (dl))
+      break;
+
+    /* A new file has appeared.  */
+    entry = XCAR (dl);
+    kqueue_generate_event
+      (watch_object, Fcons (Qcreate, Qnil), XCAR (XCDR (entry)), Qnil);
+
+    /* Check size of that file.  */
+    Lisp_Object size = Fnth (make_number (4), entry);
+    if (FLOATP (size) || (XINT (size) > 0))
+      kqueue_generate_event
+       (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
+
+    dl = XCDR (dl);
+    new_dl = Fdelq (entry, new_dl);
+  }
+
+  /* Parse through the resulting pending_dl list.  */
+  dl = pending_dl;
+  while (1) {
+    Lisp_Object entry;
+    if (NILP (dl))
+      break;
+
+    /* A file is still pending.  Assume it was a write.  */
+    entry = XCAR (dl);
+    kqueue_generate_event
+      (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
+
+    dl = XCDR (dl);
+    pending_dl = Fdelq (entry, pending_dl);
+  }
+
+  /* At this point, old_dl, new_dl and pending_dl shall be empty.
+     deleted_dl might not be empty when there was a rename to a
+     nonexisting file.  Let's make a check for this (might be removed
+     once the code is stable).  */
+  if (! NILP (old_dl))
+    report_file_error ("Old list not empty", old_dl);
+  if (! NILP (new_dl))
+    report_file_error ("New list not empty", new_dl);
+  if (! NILP (pending_dl))
+    report_file_error ("Pending events list not empty", pending_dl);
+  //  if (! NILP (deleted_dl))
+  //    report_file_error ("Deleted events list not empty", deleted_dl);
+
+  /* Replace old directory listing with the new one.  */
+  XSETCDR (Fnthcdr (make_number (3), watch_object),
+          Fcons (new_directory_files, Qnil));
+  return;
+}
+
+/* This is the callback function for arriving input on kqueuefd.  It
+   shall create a Lisp event, and put it into the Emacs input queue.  */
+static void
+kqueue_callback (int fd, void *data)
+{
+  for (;;) {
+    struct kevent kev;
+    static const struct timespec nullts = { 0, 0 };
+    Lisp_Object descriptor, watch_object, file, actions;
+
+    /* Read one event.  */
+    int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts);
+    if (ret < 1) {
+      /* All events read.  */
+      return;
+    }
+
+    /* Determine descriptor and file name.  */
+    descriptor = make_number (kev.ident);
+    watch_object = assq_no_quit (descriptor, watch_list);
+    if (CONSP (watch_object))
+      file = XCAR (XCDR (watch_object));
+    else
+      continue;
+
+    /* Determine event actions.  */
+    actions = Qnil;
+    if (kev.fflags & NOTE_DELETE)
+      actions = Fcons (Qdelete, actions);
+    if (kev.fflags & NOTE_WRITE) {
+      /* Check, whether this is a directory event.  */
+      if (NILP (Fnth (make_number (4), watch_object)))
+       actions = Fcons (Qwrite, actions);
+      else
+       kqueue_compare_dir_list (watch_object);
+    }
+    if (kev.fflags & NOTE_EXTEND)
+      actions = Fcons (Qextend, actions);
+    if (kev.fflags & NOTE_ATTRIB)
+      actions = Fcons (Qattrib, actions);
+    if (kev.fflags & NOTE_LINK)
+      actions = Fcons (Qlink, actions);
+    /* It would be useful to know the target of the rename operation.
+       At this point, it is not possible.  Happens only when the upper
+       directory is monitored.  */
+    if (kev.fflags & NOTE_RENAME)
+      actions = Fcons (Qrename, actions);
+
+    /* Create the event.  */
+    if (! NILP (actions))
+      kqueue_generate_event (watch_object, actions, file, Qnil);
+
+    /* Cancel monitor if file or directory is deleted or renamed.  */
+    if (kev.fflags & (NOTE_DELETE | NOTE_RENAME))
+      Fkqueue_rm_watch (descriptor);
+  }
+  return;
+}
+
+DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0,
+       doc: /* Add a watch for filesystem events pertaining to FILE.
+
+This arranges for filesystem events pertaining to FILE to be reported
+to Emacs.  Use `kqueue-rm-watch' to cancel the watch.
+
+Returned value is a descriptor for the added watch.  If the file cannot be
+watched for some reason, this function signals a `file-notify-error' error.
+
+FLAGS is a list of events to be watched for.  It can include the
+following symbols:
+
+  `create' -- FILE was created
+  `delete' -- FILE was deleted
+  `write'  -- FILE has changed
+  `extend' -- FILE was extended
+  `attrib' -- a FILE attribute was changed
+  `link'   -- a FILE's link count was changed
+  `rename' -- FILE was moved to FILE1
+
+When any event happens, Emacs will call the CALLBACK function passing
+it a single argument EVENT, which is of the form
+
+  (DESCRIPTOR ACTIONS FILE [FILE1])
+
+DESCRIPTOR is the same object as the one returned by this function.
+ACTIONS is a list of events.
+
+FILE is the name of the file whose event is being reported.  FILE1
+will be reported only in case of the `rename' event.  This is possible
+only when the upper directory of the renamed file is watched.  */)
+  (Lisp_Object file, Lisp_Object flags, Lisp_Object callback)
+{
+  Lisp_Object watch_object, dir_list;
+  int fd, oflags;
+  u_short fflags = 0;
+  struct kevent kev;
+
+  /* Check parameters.  */
+  CHECK_STRING (file);
+  file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
+  if (NILP (Ffile_exists_p (file)))
+    report_file_error ("File does not exist", file);
+
+  CHECK_LIST (flags);
+
+  if (! FUNCTIONP (callback))
+    wrong_type_argument (Qinvalid_function, callback);
+
+  if (kqueuefd < 0)
+    {
+      /* Create kqueue descriptor.  */
+      kqueuefd = kqueue ();
+      if (kqueuefd < 0)
+       report_file_notify_error ("File watching is not available", Qnil);
+
+      /* Start monitoring for possible I/O.  */
+      add_read_fd (kqueuefd, kqueue_callback, NULL);
+
+      watch_list = Qnil;
+    }
+
+  /* Open file.  */
+  file = ENCODE_FILE (file);
+  oflags = O_NONBLOCK;
+#if O_EVTONLY
+  oflags |= O_EVTONLY;
+#else
+  oflags |= O_RDONLY;
+#endif
+#if O_SYMLINK
+    oflags |= O_SYMLINK;
+#else
+    oflags |= O_NOFOLLOW;
+#endif
+  fd = emacs_open (SSDATA (file), oflags, 0);
+  if (fd == -1)
+    report_file_error ("File cannot be opened", file);
+
+  /* Assemble filter flags  */
+  if (! NILP (Fmember (Qdelete, flags))) fflags |= NOTE_DELETE;
+  if (! NILP (Fmember (Qwrite, flags)))  fflags |= NOTE_WRITE;
+  if (! NILP (Fmember (Qextend, flags))) fflags |= NOTE_EXTEND;
+  if (! NILP (Fmember (Qattrib, flags))) fflags |= NOTE_ATTRIB;
+  if (! NILP (Fmember (Qlink, flags)))   fflags |= NOTE_LINK;
+  if (! NILP (Fmember (Qrename, flags))) fflags |= NOTE_RENAME;
+
+  /* Register event.  */
+  EV_SET (&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
+         fflags, 0, NULL);
+
+  if (kevent (kqueuefd, &kev, 1, NULL, 0, NULL) < 0) {
+    emacs_close (fd);
+    report_file_error ("Cannot watch file", file);
+  }
+
+  /* Store watch object in watch list.  */
+  Lisp_Object watch_descriptor = make_number (fd);
+  if (NILP (Ffile_directory_p (file)))
+    watch_object = list4 (watch_descriptor, file, flags, callback);
+  else {
+    dir_list = directory_files_internal (file, Qnil, Qnil, Qnil, 1, Qnil);
+    watch_object = list5 (watch_descriptor, file, flags, callback, dir_list);
+  }
+  watch_list = Fcons (watch_object, watch_list);
+
+  return watch_descriptor;
+}
+
+DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch, Skqueue_rm_watch, 1, 1, 0,
+       doc: /* Remove an existing WATCH-DESCRIPTOR.
+
+WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.  */)
+     (Lisp_Object watch_descriptor)
+{
+  Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list);
+
+  if (! CONSP (watch_object))
+    xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"),
+             watch_descriptor);
+
+  eassert (INTEGERP (watch_descriptor));
+  int fd = XINT (watch_descriptor);
+  if ( fd >= 0)
+    emacs_close (fd);
+
+  /* Remove watch descriptor from watch list.  */
+  watch_list = Fdelq (watch_object, watch_list);
+
+  if (NILP (watch_list) && (kqueuefd >= 0)) {
+    delete_read_fd (kqueuefd);
+    emacs_close (kqueuefd);
+    kqueuefd = -1;
+  }
+
+  return Qt;
+}
+
+DEFUN ("kqueue-valid-p", Fkqueue_valid_p, Skqueue_valid_p, 1, 1, 0,
+       doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
+
+WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
+
+A watch can become invalid if the file or directory it watches is
+deleted, or if the watcher thread exits abnormally for any other
+reason.  Removing the watch by calling `kqueue-rm-watch' also makes it
+invalid.  */)
+     (Lisp_Object watch_descriptor)
+{
+  return NILP (assq_no_quit (watch_descriptor, watch_list)) ? Qnil : Qt;
+}
+
+\f
+void
+globals_of_kqueue (void)
+{
+  watch_list = Qnil;
+}
+
+void
+syms_of_kqueue (void)
+{
+  defsubr (&Skqueue_add_watch);
+  defsubr (&Skqueue_rm_watch);
+  defsubr (&Skqueue_valid_p);
+
+  /* Event types.  */
+  DEFSYM (Qcreate, "create");
+  DEFSYM (Qdelete, "delete");  /* NOTE_DELETE  */
+  DEFSYM (Qwrite, "write");    /* NOTE_WRITE  */
+  DEFSYM (Qextend, "extend");  /* NOTE_EXTEND  */
+  DEFSYM (Qattrib, "attrib");  /* NOTE_ATTRIB  */
+  DEFSYM (Qlink, "link");      /* NOTE_LINK  */
+  DEFSYM (Qrename, "rename");  /* NOTE_RENAME  */
+
+  staticpro (&watch_list);
+
+  Fprovide (intern_c_string ("kqueue"), Qnil);
+}
+
+#endif /* HAVE_KQUEUE  */
+
+/* PROBLEMS
+   * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
+     prevents tests on Ubuntu.  */
index 3efa492e0e8e0959090484652b058a2d30c425b7..426b6c949e923e0126a0cc10f54523f02351bdca 100644 (file)
@@ -4257,17 +4257,23 @@ extern void init_font (void);
 extern void syms_of_fontset (void);
 #endif
 
+/* Defined in inotify.c */
+#ifdef HAVE_INOTIFY
+extern void syms_of_inotify (void);
+#endif
+
+/* Defined in kqueue.c */
+#ifdef HAVE_KQUEUE
+extern void globals_of_kqueue (void);
+extern void syms_of_kqueue (void);
+#endif
+
 /* Defined in gfilenotify.c */
 #ifdef HAVE_GFILENOTIFY
 extern void globals_of_gfilenotify (void);
 extern void syms_of_gfilenotify (void);
 #endif
 
-/* Defined in inotify.c */
-#ifdef HAVE_INOTIFY
-extern void syms_of_inotify (void);
-#endif
-
 #ifdef HAVE_W32NOTIFY
 /* Defined on w32notify.c.  */
 extern void syms_of_w32notify (void);
index 2745f106087fd938e059427a7c88782ccb29690f..6f186973ee75f7e34c34dd07d01efcec3389f073 100644 (file)
           ;; Strange, that `copy-directory' does not work as expected.
           ;; The following shell command is not portable on all
           ;; platforms, unfortunately.
-          (shell-command (format "%s -f %s/* %s" cp tmpdir2 tmpdir1))
+          (shell-command (format "%s %s/* %s" cp tmpdir2 tmpdir1))
 
           ;; Check, that the buffers have been reverted.
           (dolist (buf (list buf1 buf2))
index 67e929a647728d2f3fbb73633131662d31846182..b665dddb6315a610c11c81e8d601871c085ac441 100644 (file)
 
 (defun file-notify--test-timeout ()
   "Timeout to wait for arriving events, in seconds."
-  (if (file-remote-p temporary-file-directory) 6 3))
+  (cond
+   ((file-remote-p temporary-file-directory) 6)
+   ((string-equal (file-notify--test-library) "w32notify") 20)
+   ((eq system-type 'cygwin) 10)
+   (t 3)))
 
 (defun file-notify--test-cleanup ()
   "Cleanup after a test."
@@ -133,6 +137,18 @@ being the result.")
   ;; Return result.
   (cdr file-notify--test-remote-enabled-checked))
 
+(defun file-notify--test-library ()
+  "The used libray for the test, as string.
+In the remote case, it is the process name which runs on the
+remote host, or nil."
+  (if (null (file-remote-p temporary-file-directory))
+      (symbol-name file-notify--library)
+    (and (consp file-notify--test-remote-enabled-checked)
+        (processp (cdr file-notify--test-remote-enabled-checked))
+        (replace-regexp-in-string
+         "<[[:digit:]]+>\\'" ""
+         (process-name (cdr file-notify--test-remote-enabled-checked))))))
+
 (defmacro file-notify--deftest-remote (test docstring)
   "Define ert `TEST-remote' for remote files."
   (declare (indent 1))
@@ -150,12 +166,7 @@ being the result.")
   "Test availability of `file-notify'."
   (skip-unless (file-notify--test-local-enabled))
   ;; Report the native library which has been used.
-  (if (null (file-remote-p temporary-file-directory))
-      (message "Local library: `%s'" file-notify--library)
-    (message "Remote command: `%s'"
-             (replace-regexp-in-string
-              "<[[:digit:]]+>\\'" ""
-              (process-name (cdr file-notify--test-remote-enabled-checked)))))
+  (message "Library: `%s'" (file-notify--test-library))
   (should
    (setq file-notify--test-desc
          (file-notify-add-watch temporary-file-directory '(change) 'ignore)))
@@ -189,12 +200,13 @@ being the result.")
          (file-notify-add-watch
           temporary-file-directory '(change attribute-change) 'ignore)))
   (file-notify-rm-watch file-notify--test-desc)
-  ;; The file does not need to exist, just the upper directory.
+  (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
   (should
    (setq file-notify--test-desc
          (file-notify-add-watch
           file-notify--test-tmpfile '(change attribute-change) 'ignore)))
   (file-notify-rm-watch file-notify--test-desc)
+  (delete-file file-notify--test-tmpfile)
 
   ;; Check error handling.
   (should-error (file-notify-add-watch 1 2 3 4)
@@ -235,16 +247,17 @@ is bound somewhere."
   (should
    (or (string-equal (file-notify--event-file-name file-notify--test-event)
                     file-notify--test-tmpfile)
-       (string-equal (directory-file-name
-                     (file-name-directory
-                      (file-notify--event-file-name file-notify--test-event)))
-                    file-notify--test-tmpfile)))
+       (string-equal (file-notify--event-file-name file-notify--test-event)
+                    file-notify--test-tmpfile1)
+       (string-equal (file-notify--event-file-name file-notify--test-event)
+                    temporary-file-directory)))
   ;; Check the second file name if exists.
   (when (eq (nth 1 file-notify--test-event) 'renamed)
     (should
-     (string-equal
-      (file-notify--event-file1-name file-notify--test-event)
-      file-notify--test-tmpfile1))))
+     (or (string-equal (file-notify--event-file1-name file-notify--test-event)
+                      file-notify--test-tmpfile1)
+        (string-equal (file-notify--event-file1-name file-notify--test-event)
+                      temporary-file-directory)))))
 
 (defun file-notify--test-event-handler (event)
   "Run a test over FILE-NOTIFY--TEST-EVENT.
@@ -253,7 +266,7 @@ and the event to `file-notify--test-events'."
   (let* ((file-notify--test-event event)
          (result
           (ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
-    ;; Do not add temporary files, this would confuse the checks.
+    ;; Do not add lock files, this would confuse the checks.
     (unless (string-match
             (regexp-quote ".#")
             (file-notify--event-file-name file-notify--test-event))
@@ -280,13 +293,19 @@ TIMEOUT is the maximum time to wait for, in seconds."
 Don't wait longer than timeout seconds for the events to be delivered."
   (declare (indent 1))
   (let ((outer (make-symbol "outer")))
-    `(let ((,outer file-notify--test-events))
+    `(let ((,outer file-notify--test-events)
+           create-lockfiles)
        (setq file-notify--test-expected-events
             (append file-notify--test-expected-events ,events))
+       ;; Flush pending events.
+       (file-notify--wait-for-events
+        (file-notify--test-timeout)
+        (input-pending-p))
        (let (file-notify--test-events)
          ,@body
          (file-notify--wait-for-events
-          (file-notify--test-timeout)
+          ;; More events need more time.  Use some fudge factor.
+          (* (ceiling (length ,events) 100) (file-notify--test-timeout))
           (= (length ,events) (length file-notify--test-events)))
          (should (equal ,events (mapcar #'cadr file-notify--test-events)))
          (setq ,outer (append ,outer file-notify--test-events)))
@@ -295,111 +314,207 @@ Don't wait longer than timeout seconds for the events to be delivered."
 (ert-deftest file-notify-test02-events ()
   "Check file creation/change/removal notifications."
   (skip-unless (file-notify--test-local-enabled))
-  ;; Under cygwin there are so bad timings that it doesn't make sense to test.
-  (skip-unless (not (eq system-type 'cygwin)))
-
-  (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-       file-notify--test-tmpfile1 (file-notify--test-make-temp-name))
 
   (unwind-protect
       (progn
-        ;; Check creation, change and deletion.
-       (setq file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile
-               '(change) 'file-notify--test-event-handler))
-        (file-notify--test-with-events '(created changed deleted)
+        ;; Check file creation, change and deletion.  It doesn't work
+        ;; for cygwin and kqueue, because we don't use an implicit
+        ;; directory monitor (kqueue), or the timings are too bad (cygwin).
+        (unless (or (eq system-type 'cygwin)
+                   (string-equal (file-notify--test-library) "kqueue"))
+          (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+          (should
+           (setq file-notify--test-desc
+                 (file-notify-add-watch
+                  file-notify--test-tmpfile
+                  '(change) 'file-notify--test-event-handler)))
+          (file-notify--test-with-events
+              (cond
+               ;; cygwin recognizes only `deleted' and `stopped' events.
+               ((eq system-type 'cygwin)
+                '(deleted stopped))
+               (t '(created changed deleted stopped)))
+            (write-region
+             "another text" nil file-notify--test-tmpfile nil 'no-message)
+            (read-event nil nil 0.1)
+            (delete-file file-notify--test-tmpfile))
+          ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
+          (let (file-notify--test-events)
+            (file-notify-rm-watch file-notify--test-desc)))
+
+        ;; Check file change and deletion.
+       (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+        (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+       (should
+        (setq file-notify--test-desc
+              (file-notify-add-watch
+               file-notify--test-tmpfile
+               '(change) 'file-notify--test-event-handler)))
+        (file-notify--test-with-events
+           (cond
+            ;; cygwin recognizes only `deleted' and `stopped' events.
+            ((eq system-type 'cygwin)
+             '(deleted stopped))
+             ;; inotify, kqueueg and gfilenotify raise just one
+             ;; `changed' event, the other backends show us two of
+             ;; them.
+             ((or (string-equal "inotify" (file-notify--test-library))
+                  (string-equal "kqueue" (file-notify--test-library))
+                  (string-equal "gfilenotify" (file-notify--test-library)))
+             '(changed deleted stopped))
+            (t '(changed changed deleted stopped)))
+          (read-event nil nil 0.1)
           (write-region
-           "any text" nil file-notify--test-tmpfile nil 'no-message)
+           "another text" nil file-notify--test-tmpfile nil 'no-message)
+          (read-event nil nil 0.1)
           (delete-file file-notify--test-tmpfile))
        ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
        (let (file-notify--test-events)
          (file-notify-rm-watch file-notify--test-desc))
 
-        ;; Check creation, change and deletion.  There must be a
-        ;; `stopped' event when deleting the directory.  It doesn't
-        ;; work for w32notify.
-        (unless (eq file-notify--library 'w32notify)
-         (make-directory file-notify--test-tmpfile)
-         (setq file-notify--test-desc
-               (file-notify-add-watch
-                file-notify--test-tmpfile
-                '(change) 'file-notify--test-event-handler))
+        ;; Check file creation, change and deletion when watching a
+        ;; directory.  There must be a `stopped' event when deleting
+        ;; the directory.
+       (let ((temporary-file-directory
+              (make-temp-file "file-notify-test-parent" t)))
+         (should
+          (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+                file-notify--test-desc
+                (file-notify-add-watch
+                 temporary-file-directory
+                 '(change) 'file-notify--test-event-handler)))
          (file-notify--test-with-events
-             ;; There are two `deleted' events, for the file and for
-             ;; the directory.
-             '(created changed deleted deleted stopped)
+             (cond
+              ;; w32notify does raise a `stopped' event when a
+              ;; watched directory is deleted.
+              ((string-equal (file-notify--test-library) "w32notify")
+               '(created changed deleted))
+              ;; cygwin recognizes only `deleted' and `stopped' events.
+              ((eq system-type 'cygwin)
+               '(deleted stopped))
+              ;; There are two `deleted' events, for the file and for
+              ;; the directory.  Except for kqueue.
+              ((string-equal (file-notify--test-library) "kqueue")
+               '(created changed deleted stopped))
+              (t '(created changed deleted deleted stopped)))
+           (read-event nil nil 0.1)
            (write-region
-            "any text" nil (expand-file-name "foo" file-notify--test-tmpfile)
-            nil 'no-message)
-           (delete-directory file-notify--test-tmpfile 'recursive))
+            "any text" nil file-notify--test-tmpfile nil 'no-message)
+           (read-event nil nil 0.1)
+           (delete-directory temporary-file-directory 'recursive))
          ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
          (let (file-notify--test-events)
            (file-notify-rm-watch file-notify--test-desc)))
 
-        ;; Check copy.
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile
-               '(change) 'file-notify--test-event-handler))
-        (should file-notify--test-desc)
-        (file-notify--test-with-events
-            ;; w32notify does not distinguish between `changed' and
-            ;; `attribute-changed'.
-            (if (eq file-notify--library 'w32notify)
-                '(created changed changed deleted)
-              '(created changed deleted))
-          (write-region
-           "any text" nil file-notify--test-tmpfile nil 'no-message)
-          (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
-          ;; The next two events shall not be visible.
-          (set-file-modes file-notify--test-tmpfile 000)
-          (read-event nil nil 0.1) ; In order to distinguish the events.
-          (set-file-times file-notify--test-tmpfile '(0 0))
-          (delete-file file-notify--test-tmpfile)
-          (delete-file file-notify--test-tmpfile1))
-       ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
-       (let (file-notify--test-events)
-         (file-notify-rm-watch file-notify--test-desc))
+        ;; Check copy of files inside a directory.
+       (let ((temporary-file-directory
+              (make-temp-file "file-notify-test-parent" t)))
+         (should
+          (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+                file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
+                file-notify--test-desc
+                (file-notify-add-watch
+                 temporary-file-directory
+                 '(change) 'file-notify--test-event-handler)))
+         (file-notify--test-with-events
+             (cond
+              ;; w32notify does not distinguish between `changed' and
+              ;; `attribute-changed'.
+              ((string-equal (file-notify--test-library) "w32notify")
+               '(created changed created changed changed changed changed
+                 deleted deleted))
+              ;; cygwin recognizes only `deleted' and `stopped' events.
+              ((eq system-type 'cygwin)
+               '(deleted stopped))
+              ;; There are three `deleted' events, for two files and
+              ;; for the directory.  Except for kqueue.
+              ((string-equal (file-notify--test-library) "kqueue")
+               '(created changed created changed deleted stopped))
+              (t '(created changed created changed
+                   deleted deleted deleted stopped)))
+           (read-event nil nil 0.1)
+           (write-region
+            "any text" nil file-notify--test-tmpfile nil 'no-message)
+           (read-event nil nil 0.1)
+           (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
+           ;; The next two events shall not be visible.
+           (read-event nil nil 0.1)
+           (set-file-modes file-notify--test-tmpfile 000)
+           (read-event nil nil 0.1)
+           (set-file-times file-notify--test-tmpfile '(0 0))
+           (read-event nil nil 0.1)
+           (delete-directory temporary-file-directory 'recursive))
+         ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
+         (let (file-notify--test-events)
+           (file-notify-rm-watch file-notify--test-desc)))
 
-        ;; Check rename.
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile
-               '(change) 'file-notify--test-event-handler))
-        (should file-notify--test-desc)
-        (file-notify--test-with-events '(created changed renamed)
-          (write-region
-           "any text" nil file-notify--test-tmpfile nil 'no-message)
-          (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
-          ;; After the rename, we won't get events anymore.
-          (delete-file file-notify--test-tmpfile1))
-       ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
-       (let (file-notify--test-events)
-         (file-notify-rm-watch file-notify--test-desc))
+        ;; Check rename of files inside a directory.
+       (let ((temporary-file-directory
+              (make-temp-file "file-notify-test-parent" t)))
+         (should
+          (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+                file-notify--test-tmpfile1 (file-notify--test-make-temp-name)
+                file-notify--test-desc
+                (file-notify-add-watch
+                 temporary-file-directory
+                 '(change) 'file-notify--test-event-handler)))
+         (file-notify--test-with-events
+             (cond
+              ;; w32notify does not distinguish between `changed' and
+              ;; `attribute-changed'.
+              ((string-equal (file-notify--test-library) "w32notify")
+               '(created changed renamed deleted))
+              ;; cygwin recognizes only `deleted' and `stopped' events.
+              ((eq system-type 'cygwin)
+               '(deleted stopped))
+              ;; There are two `deleted' events, for the file and for
+              ;; the directory.  Except for kqueue.
+              ((string-equal (file-notify--test-library) "kqueue")
+               '(created changed renamed deleted stopped))
+              (t '(created changed renamed deleted deleted stopped)))
+           (read-event nil nil 0.1)
+           (write-region
+            "any text" nil file-notify--test-tmpfile nil 'no-message)
+           (read-event nil nil 0.1)
+           (rename-file file-notify--test-tmpfile file-notify--test-tmpfile1)
+           ;; After the rename, we won't get events anymore.
+           (read-event nil nil 0.1)
+           (delete-directory temporary-file-directory 'recursive))
+         ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
+         (let (file-notify--test-events)
+           (file-notify-rm-watch file-notify--test-desc)))
 
-        ;; Check attribute change.  It doesn't work for w32notify.
-        (unless (eq file-notify--library 'w32notify)
-          (setq file-notify--test-desc
-                (file-notify-add-watch
-                 file-notify--test-tmpfile
-                 '(attribute-change) 'file-notify--test-event-handler))
-          (file-notify--test-with-events
-              (if (file-remote-p temporary-file-directory)
-                  ;; In the remote case, `write-region' raises also an
-                  ;; `attribute-changed' event.
-                  '(attribute-changed attribute-changed attribute-changed)
-                '(attribute-changed attribute-changed))
-            ;; We must use short delays between the operations.
-            ;; Otherwise, not all events arrive us in the remote case.
-            (write-region
-             "any text" nil file-notify--test-tmpfile nil 'no-message)
-            (read-event nil nil 0.1)
-            (set-file-modes file-notify--test-tmpfile 000)
-            (read-event nil nil 0.1)
-            (set-file-times file-notify--test-tmpfile '(0 0))
-            (read-event nil nil 0.1)
-            (delete-file file-notify--test-tmpfile))
+        ;; Check attribute change.  Does not work for cygwin.
+       (unless (eq system-type 'cygwin)
+         (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+         (write-region
+          "any text" nil file-notify--test-tmpfile nil 'no-message)
+         (should
+          (setq file-notify--test-desc
+                (file-notify-add-watch
+                 file-notify--test-tmpfile
+                 '(attribute-change) 'file-notify--test-event-handler)))
+         (file-notify--test-with-events
+             (cond
+              ;; w32notify does not distinguish between `changed' and
+              ;; `attribute-changed'.
+              ((string-equal (file-notify--test-library) "w32notify")
+               '(changed changed changed changed))
+              ;; For kqueue and in the remote case, `write-region'
+              ;; raises also an `attribute-changed' event.
+              ((or (string-equal (file-notify--test-library) "kqueue")
+                   (file-remote-p temporary-file-directory))
+               '(attribute-changed attribute-changed attribute-changed))
+              (t '(attribute-changed attribute-changed)))
+           (read-event nil nil 0.1)
+           (write-region
+            "any text" nil file-notify--test-tmpfile nil 'no-message)
+           (read-event nil nil 0.1)
+           (set-file-modes file-notify--test-tmpfile 000)
+           (read-event nil nil 0.1)
+           (set-file-times file-notify--test-tmpfile '(0 0))
+           (read-event nil nil 0.1)
+           (delete-file file-notify--test-tmpfile))
          ;; `file-notify-rm-watch' fires the `stopped' event.  Suppress it.
          (let (file-notify--test-events)
            (file-notify-rm-watch file-notify--test-desc)))
@@ -475,28 +590,31 @@ Don't wait longer than timeout seconds for the events to be delivered."
            (should (string-match "another text" (buffer-string)))
 
             ;; Stop file notification.  Autorevert shall still work via polling.
-           (file-notify-rm-watch auto-revert-notify-watch-descriptor)
-            (file-notify--wait-for-events
-             timeout (null auto-revert-use-notify))
-           (should-not auto-revert-use-notify)
-           (should-not auto-revert-notify-watch-descriptor)
-
-           ;; Modify file.  We wait for two seconds, in order to have
-           ;; another timestamp.  One second seems to be too short.
-            (with-current-buffer (get-buffer-create "*Messages*")
-              (narrow-to-region (point-max) (point-max)))
-           (sleep-for 2)
-            (write-region
-             "foo bla" nil file-notify--test-tmpfile nil 'no-message)
-
-           ;; Check, that the buffer has been reverted.
-           (with-current-buffer (get-buffer-create "*Messages*")
+           ;; It doesn't work for `w32notify'.
+           (unless (string-equal (file-notify--test-library) "w32notify")
+             (file-notify-rm-watch auto-revert-notify-watch-descriptor)
              (file-notify--wait-for-events
-              timeout
-              (string-match
-                (format-message "Reverting buffer `%s'." (buffer-name buf))
-                (buffer-string))))
-           (should (string-match "foo bla" (buffer-string)))))
+              timeout (null auto-revert-use-notify))
+             (should-not auto-revert-use-notify)
+             (should-not auto-revert-notify-watch-descriptor)
+
+             ;; Modify file.  We wait for two seconds, in order to
+             ;; have another timestamp.  One second seems to be too
+             ;; short.
+             (with-current-buffer (get-buffer-create "*Messages*")
+               (narrow-to-region (point-max) (point-max)))
+             (sleep-for 2)
+             (write-region
+              "foo bla" nil file-notify--test-tmpfile nil 'no-message)
+
+             ;; Check, that the buffer has been reverted.
+             (with-current-buffer (get-buffer-create "*Messages*")
+               (file-notify--wait-for-events
+                timeout
+                (string-match
+                 (format-message "Reverting buffer `%s'." (buffer-name buf))
+                 (buffer-string))))
+             (should (string-match "foo bla" (buffer-string))))))
 
       ;; Cleanup.
       (with-current-buffer "*Messages*" (widen))
@@ -509,51 +627,92 @@ Don't wait longer than timeout seconds for the events to be delivered."
 (ert-deftest file-notify-test04-file-validity ()
   "Check `file-notify-valid-p' for files."
   (skip-unless (file-notify--test-local-enabled))
-  ;; Under cygwin there are so bad timings that it doesn't make sense to test.
-  (skip-unless (not (eq system-type 'cygwin)))
 
   (unwind-protect
       (progn
-        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-              file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile
-               '(change) #'file-notify--test-event-handler))
-        (file-notify--test-with-events '(created changed deleted)
+        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+       (should
+        (setq file-notify--test-desc
+              (file-notify-add-watch
+               file-notify--test-tmpfile
+               '(change) #'file-notify--test-event-handler)))
+        (should (file-notify-valid-p file-notify--test-desc))
+       ;; After calling `file-notify-rm-watch', the descriptor is not
+       ;; valid anymore.
+        (file-notify-rm-watch file-notify--test-desc)
+        (should-not (file-notify-valid-p file-notify--test-desc))
+       (delete-file file-notify--test-tmpfile))
+
+    ;; Cleanup.
+    (file-notify--test-cleanup))
+
+  (unwind-protect
+      (progn
+        (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+       (write-region "any text" nil file-notify--test-tmpfile nil 'no-message)
+       (should
+        (setq file-notify--test-desc
+              (file-notify-add-watch
+               file-notify--test-tmpfile
+               '(change) #'file-notify--test-event-handler)))
+        (file-notify--test-with-events
+            (cond
+             ;; cygwin recognizes only `deleted' and `stopped' events.
+            ((eq system-type 'cygwin)
+             '(deleted stopped))
+             ;; inotify, kqueueg and gfilenotify raise just one
+             ;; `changed' event, the other backends show us two of
+             ;; them.
+             ((or (string-equal "inotify" (file-notify--test-library))
+                  (string-equal "kqueue" (file-notify--test-library))
+                  (string-equal "gfilenotify" (file-notify--test-library)))
+             '(changed deleted stopped))
+            (t '(changed changed deleted stopped)))
+         (read-event nil nil 0.1)
           (should (file-notify-valid-p file-notify--test-desc))
           (write-region
-           "any text" nil file-notify--test-tmpfile nil 'no-message)
+           "another text" nil file-notify--test-tmpfile nil 'no-message)
+         (read-event nil nil 0.1)
          (delete-file file-notify--test-tmpfile))
-       ;; After deleting the file, the descriptor is still valid.
-        (should (file-notify-valid-p file-notify--test-desc))
-       ;; After removing the watch, the descriptor must not be valid
-        ;; anymore.
-        (file-notify-rm-watch file-notify--test-desc)
-        (should-not (file-notify-valid-p file-notify--test-desc)))
+       ;; After deleting the file, the descriptor is not valid anymore.
+        (should-not (file-notify-valid-p file-notify--test-desc))
+        (file-notify-rm-watch file-notify--test-desc))
 
     ;; Cleanup.
     (file-notify--test-cleanup))
 
   (unwind-protect
-      ;; The batch-mode operation of w32notify is fragile (there's no
-      ;; input threads to send the message to).
-      ;(unless (and noninteractive (eq file-notify--library 'w32notify))
-      (unless (eq file-notify--library 'w32notify)
-        (let ((temporary-file-directory
+      ;; w32notify does not send a `stopped' event when deleting a
+      ;; directory.  The test does not work, therefore.
+      (unless (string-equal (file-notify--test-library) "w32notify")
+       (let ((temporary-file-directory
               (make-temp-file "file-notify-test-parent" t)))
-          (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
-                file-notify--test-desc
-                (file-notify-add-watch
-                 file-notify--test-tmpfile
-                 '(change) #'file-notify--test-event-handler))
-          (file-notify--test-with-events '(created changed deleted stopped)
-            (should (file-notify-valid-p file-notify--test-desc))
-            (write-region
-             "any text" nil file-notify--test-tmpfile nil 'no-message)
+         (should
+          (setq file-notify--test-tmpfile (file-notify--test-make-temp-name)
+                file-notify--test-desc
+                (file-notify-add-watch
+                 temporary-file-directory
+                 '(change) #'file-notify--test-event-handler)))
+         (file-notify--test-with-events
+             (cond
+              ;; cygwin recognizes only `deleted' and `stopped' events.
+              ((eq system-type 'cygwin)
+               '(deleted stopped))
+              ;; There are two `deleted' events, for the file and for
+              ;; the directory.  Except for kqueue.
+              ((string-equal (file-notify--test-library) "kqueue")
+               '(created changed deleted stopped))
+              (t '(created changed deleted deleted stopped)))
+           (should (file-notify-valid-p file-notify--test-desc))
+           (read-event nil nil 0.1)
+           (write-region
+            "any text" nil file-notify--test-tmpfile nil 'no-message)
+           (read-event nil nil 0.1)
            (delete-directory temporary-file-directory t))
-          ;; After deleting the parent directory, the descriptor must
-          ;; not be valid anymore.
-          (should-not (file-notify-valid-p file-notify--test-desc))))
+         ;; After deleting the parent directory, the descriptor must
+         ;; not be valid anymore.
+         (should-not (file-notify-valid-p file-notify--test-desc))))
 
     ;; Cleanup.
     (file-notify--test-cleanup)))
@@ -570,10 +729,11 @@ Don't wait longer than timeout seconds for the events to be delivered."
         (setq file-notify--test-tmpfile
              (file-name-as-directory (file-notify--test-make-temp-name)))
         (make-directory file-notify--test-tmpfile)
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile
-               '(change) #'file-notify--test-event-handler))
+       (should
+        (setq file-notify--test-desc
+              (file-notify-add-watch
+               file-notify--test-tmpfile
+               '(change) #'file-notify--test-event-handler)))
         (should (file-notify-valid-p file-notify--test-desc))
         ;; After removing the watch, the descriptor must not be valid
         ;; anymore.
@@ -589,20 +749,22 @@ Don't wait longer than timeout seconds for the events to be delivered."
   (unwind-protect
       ;; The batch-mode operation of w32notify is fragile (there's no
       ;; input threads to send the message to).
-      (unless (and noninteractive (eq file-notify--library 'w32notify))
+      (unless (and noninteractive
+                  (string-equal (file-notify--test-library) "w32notify"))
         (setq file-notify--test-tmpfile
              (file-name-as-directory (file-notify--test-make-temp-name)))
         (make-directory file-notify--test-tmpfile)
-        (setq file-notify--test-desc
-              (file-notify-add-watch
-               file-notify--test-tmpfile
-               '(change) #'file-notify--test-event-handler))
+       (should
+        (setq file-notify--test-desc
+              (file-notify-add-watch
+               file-notify--test-tmpfile
+               '(change) #'file-notify--test-event-handler)))
         (should (file-notify-valid-p file-notify--test-desc))
         ;; After deleting the directory, the descriptor must not be
         ;; valid anymore.
         (delete-directory file-notify--test-tmpfile t)
         (file-notify--wait-for-events
-         (file-notify--test-timeout)
+        (file-notify--test-timeout)
         (not (file-notify-valid-p file-notify--test-desc)))
         (should-not (file-notify-valid-p file-notify--test-desc)))
 
@@ -612,6 +774,61 @@ Don't wait longer than timeout seconds for the events to be delivered."
 (file-notify--deftest-remote file-notify-test05-dir-validity
   "Check `file-notify-valid-p' via file notification for remote directories.")
 
+(ert-deftest file-notify-test06-many-events ()
+  "Check that events are not dropped."
+  (skip-unless (file-notify--test-local-enabled))
+  ;; Under cygwin events arrive in random order.  Impossible to define a test.
+  (skip-unless (not (eq system-type 'cygwin)))
+
+  (setq file-notify--test-tmpfile (file-notify--test-make-temp-name))
+  (make-directory file-notify--test-tmpfile)
+  (should
+   (setq file-notify--test-desc
+        (file-notify-add-watch
+         file-notify--test-tmpfile
+         '(change) 'file-notify--test-event-handler)))
+  (unwind-protect
+      (let ((n 1000)
+            source-file-list target-file-list
+            (default-directory file-notify--test-tmpfile))
+        (dotimes (i n)
+         ;; It matters which direction we rename, at least for
+         ;; kqueue.  This backend parses directories in alphabetic
+         ;; order (x%d before y%d).  So we rename both directions.
+         (if (zerop (mod i 2))
+             (progn
+               (push (expand-file-name (format "x%d" i)) source-file-list)
+               (push (expand-file-name (format "y%d" i)) target-file-list))
+           (push (expand-file-name (format "y%d" i)) source-file-list)
+           (push (expand-file-name (format "x%d" i)) target-file-list)))
+        (file-notify--test-with-events (make-list (+ n n) 'created)
+          (let ((source-file-list source-file-list)
+                (target-file-list target-file-list))
+            (while (and source-file-list target-file-list)
+              (read-event nil nil 0.1)
+              (write-region "" nil (pop source-file-list) nil 'no-message)
+              (read-event nil nil 0.1)
+              (write-region "" nil (pop target-file-list) nil 'no-message))))
+        (file-notify--test-with-events
+           (cond
+            ;; w32notify fires both `deleted' and `renamed' events.
+            ((string-equal (file-notify--test-library) "w32notify")
+             (let (r)
+               (dotimes (i n r)
+                 (setq r (append '(deleted renamed) r)))))
+            (t (make-list n 'renamed)))
+          (let ((source-file-list source-file-list)
+                (target-file-list target-file-list))
+            (while (and source-file-list target-file-list)
+              (rename-file (pop source-file-list) (pop target-file-list) t))))
+        (file-notify--test-with-events (make-list n 'deleted)
+          (dolist (file target-file-list)
+            (delete-file file))))
+    (file-notify--test-cleanup)))
+
+(file-notify--deftest-remote file-notify-test06-many-events
+   "Check that events are not dropped for remote directories.")
+
 (defun file-notify-test-all (&optional interactive)
   "Run all tests for \\[file-notify]."
   (interactive "p")
@@ -622,7 +839,7 @@ Don't wait longer than timeout seconds for the events to be delivered."
 ;; TODO:
 
 ;; * For w32notify, no stopped events arrive when a directory is removed.
-;; * Try to handle arriving events under cygwin reliably.
+;; * Check, why cygwin recognizes only `deleted' and `stopped' events.
 
 (provide 'file-notify-tests)
 ;;; file-notify-tests.el ends here