]> code.delx.au - gnu-emacs/blobdiff - src/kqueue.c
Merge from origin/emacs-25
[gnu-emacs] / src / kqueue.c
index 5caef67b92a2fea61957dbe98c66da21a15fde1b..f45bd0c4c24e505a30fcd3aed2929db084f930d2 100644 (file)
@@ -1,12 +1,13 @@
 /* Filesystem notifications support with kqueue API.
-   Copyright (C) 2015 Free Software Foundation, Inc.
+
+Copyright (C) 2015-2016 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.
+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
@@ -28,6 +29,10 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "keyboard.h"
 #include "process.h"
 
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif /* HAVE_SYS_RESOURCE_H  */
+
 \f
 /* File handle for kqueue.  */
 static int kqueuefd = -1;
@@ -66,22 +71,39 @@ kqueue_directory_listing (Lisp_Object directory_files)
 
 /* Generate a file notification event.  */
 static void
-kqueue_generate_event
-(Lisp_Object ident, Lisp_Object actions, Lisp_Object file, Lisp_Object file1,
- Lisp_Object callback)
+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;
-  EVENT_INIT (event);
-  event.kind = FILE_NOTIFY_EVENT;
-  event.frame_or_window = Qnil;
-  event.arg = list2 (Fcons (ident, Fcons (actions,
-                                         NILP (file1)
-                                         ? Fcons (file, Qnil)
-                                         : list2 (file, file1))),
-                    callback);
+
+  /* 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.  */
-  kbd_buffer_store_event (&event);
+  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
@@ -90,22 +112,21 @@ kqueue_generate_event
    replaced by the new directory listing at the end of this
    function.  */
 static void
-kqueue_compare_dir_list
-(Lisp_Object watch_object)
+kqueue_compare_dir_list (Lisp_Object watch_object)
 {
-  Lisp_Object dir, callback;
+  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));
-  callback = Fnth (make_number (3), 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
-      (XCAR (watch_object), Fcons (Qdelete, Qnil), dir, Qnil, callback);
+    kqueue_generate_event (watch_object, Fcons (Qdelete, Qnil), dir, Qnil);
     return;
   }
   new_directory_files =
@@ -121,7 +142,7 @@ kqueue_compare_dir_list
 
     /* Search for an entry with the same inode.  */
     old_entry = XCAR (dl);
-    new_entry = Fassoc (XCAR (old_entry), new_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);
@@ -137,21 +158,21 @@ kqueue_compare_dir_list
        if (NILP (Fequal (Fnth (make_number (2), old_entry),
                          Fnth (make_number (2), new_entry))))
          kqueue_generate_event
-           (XCAR (watch_object), Fcons (Qwrite, Qnil),
-            XCAR (XCDR (old_entry)), Qnil, callback);
+           (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
-           (XCAR (watch_object), Fcons (Qattrib, Qnil),
-            XCAR (XCDR (old_entry)), Qnil, callback);
+           (watch_object, Fcons (Qattrib, Qnil),
+            XCAR (XCDR (old_entry)), Qnil);
 
       } else {
        /* The file has been renamed.  */
        kqueue_generate_event
-         (XCAR (watch_object), Fcons (Qrename, Qnil),
-          XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)), callback);
+         (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;
@@ -163,18 +184,36 @@ kqueue_compare_dir_list
       new_entry = XCAR (dl1);
       if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
                  SSDATA (XCAR (XCDR (new_entry)))) == 0) {
-       kqueue_generate_event
-         (XCAR (watch_object), Fcons (Qwrite, Qnil),
-          XCAR (XCDR (old_entry)), Qnil, callback);
+       pending_dl = Fcons (new_entry, pending_dl);
        new_dl = Fdelq (new_entry, new_dl);
        goto the_end;
       }
     }
 
-    /* The file has been deleted.  */
-    kqueue_generate_event
-      (XCAR (watch_object), Fcons (Qdelete, Qnil),
-       XCAR (XCDR (old_entry)), Qnil, callback);
+    /* 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);
@@ -184,33 +223,53 @@ kqueue_compare_dir_list
   /* Parse through the resulting new list.  */
   dl = new_dl;
   while (1) {
-    Lisp_Object new_entry;
+    Lisp_Object entry;
     if (NILP (dl))
       break;
 
     /* A new file has appeared.  */
-    new_entry = XCAR (dl);
+    entry = XCAR (dl);
     kqueue_generate_event
-      (XCAR (watch_object), Fcons (Qcreate, Qnil),
-       XCAR (XCDR (new_entry)), Qnil, callback);
+      (watch_object, Fcons (Qcreate, Qnil), XCAR (XCDR (entry)), Qnil);
 
     /* Check size of that file.  */
-    Lisp_Object size = Fnth (make_number (4), new_entry);
+    Lisp_Object size = Fnth (make_number (4), entry);
     if (FLOATP (size) || (XINT (size) > 0))
       kqueue_generate_event
-       (XCAR (watch_object), Fcons (Qwrite, Qnil),
-        XCAR (XCDR (new_entry)), Qnil, callback);
+       (watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
 
     dl = XCDR (dl);
-    new_dl = Fdelq (new_entry, new_dl);
+    new_dl = Fdelq (entry, new_dl);
   }
 
-  /* At this point, both old_dl and new_dl shall be empty.  Let's make
-     a check for this (might be removed once the code is stable).  */
+  /* 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
+     nonexistent 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),
@@ -226,7 +285,7 @@ kqueue_callback (int fd, void *data)
   for (;;) {
     struct kevent kev;
     static const struct timespec nullts = { 0, 0 };
-    Lisp_Object descriptor, watch_object, file, callback, actions;
+    Lisp_Object descriptor, watch_object, file, actions;
 
     /* Read one event.  */
     int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts);
@@ -235,14 +294,11 @@ kqueue_callback (int fd, void *data)
       return;
     }
 
-    /* Determine descriptor, file name and callback function.  */
+    /* Determine descriptor and file name.  */
     descriptor = make_number (kev.ident);
     watch_object = assq_no_quit (descriptor, watch_list);
-
-    if (CONSP (watch_object)) {
+    if (CONSP (watch_object))
       file = XCAR (XCDR (watch_object));
-      callback = Fnth (make_number (3), watch_object);
-    }
     else
       continue;
 
@@ -271,7 +327,7 @@ kqueue_callback (int fd, void *data)
 
     /* Create the event.  */
     if (! NILP (actions))
-      kqueue_generate_event (descriptor, actions, file, Qnil, callback);
+      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))
@@ -314,9 +370,12 @@ 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;
+  int maxfd, fd, oflags;
   u_short fflags = 0;
   struct kevent kev;
+#ifdef HAVE_GETRLIMIT
+  struct rlimit rlim;
+#endif /* HAVE_GETRLIMIT  */
 
   /* Check parameters.  */
   CHECK_STRING (file);
@@ -329,6 +388,21 @@ only when the upper directory of the renamed file is watched.  */)
   if (! FUNCTIONP (callback))
     wrong_type_argument (Qinvalid_function, callback);
 
+  /* Check available file descriptors.  */
+#ifdef HAVE_GETRLIMIT
+  if (! getrlimit (RLIMIT_NOFILE, &rlim))
+    maxfd = rlim.rlim_cur;
+  else
+#endif /* HAVE_GETRLIMIT  */
+    maxfd = 256;
+
+  /* We assume 50 file descriptors are sufficient for the rest of Emacs.  */
+  if ((maxfd - 50) < XINT (Flength (watch_list)))
+    xsignal2
+      (Qfile_notify_error,
+       build_string ("File watching not possible, no file descriptor left"),
+       Flength (watch_list));
+
   if (kqueuefd < 0)
     {
       /* Create kqueue descriptor.  */