root/src/inotify.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. mask_to_aspects
  2. symbol_to_inotifymask
  3. aspect_to_inotifymask
  4. inotifyevent_to_event
  5. add_watch
  6. find_descriptor
  7. remove_descriptor
  8. remove_watch
  9. inotify_callback
  10. valid_watch_descriptor
  11. DEFUN
  12. DEFUN
  13. DEFUN
  14. DEFUN
  15. syms_of_inotify

     1 /* Inotify support for Emacs
     2 
     3 Copyright (C) 2012-2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 #include <config.h>
    21 
    22 #include "lisp.h"
    23 #include "coding.h"
    24 #include "process.h"
    25 #include "keyboard.h"
    26 #include "termhooks.h"
    27 
    28 #include <errno.h>
    29 #include <sys/inotify.h>
    30 #include <sys/ioctl.h>
    31 
    32 /* Ignore bits that might be undefined on old GNU/Linux systems.  */
    33 #ifndef IN_EXCL_UNLINK
    34 # define IN_EXCL_UNLINK 0
    35 #endif
    36 #ifndef IN_DONT_FOLLOW
    37 # define IN_DONT_FOLLOW 0
    38 #endif
    39 #ifndef IN_ONLYDIR
    40 # define IN_ONLYDIR 0
    41 #endif
    42 
    43 /* File handle for inotify.  */
    44 static int inotifyfd = -1;
    45 
    46 /* Alist of files being watched.  We want the returned descriptor to
    47    be unique for every watch, but inotify returns the same descriptor
    48    WD for multiple calls to inotify_add_watch with the same file.
    49    Supply a nonnegative integer ID, so that WD and ID together
    50    uniquely identify a watch/file combination.
    51 
    52    For the same reason, we also need to store the watch's mask and we
    53    can't allow the following flags to be used.
    54 
    55    IN_EXCL_UNLINK
    56    IN_MASK_ADD
    57    IN_ONESHOT
    58 
    59    Each element of this list is of the form (DESCRIPTOR . WATCHES)
    60    where no two DESCRIPTOR values are the same.  DESCRIPTOR represents
    61    the inotify watch descriptor and WATCHES is a list with elements of
    62    the form (ID FILENAME CALLBACK MASK), where ID is the integer
    63    described above, FILENAME names the file being watched, CALLBACK is
    64    invoked when the event occurs, and MASK represents the aspects
    65    being watched.  The WATCHES list is sorted by ID.  Although
    66    DESCRIPTOR and MASK are ordinarily integers, they are conses when
    67    representing integers outside of fixnum range.  */
    68 
    69 static Lisp_Object watch_list;
    70 
    71 static Lisp_Object
    72 mask_to_aspects (uint32_t mask)
    73 {
    74   Lisp_Object aspects = Qnil;
    75   if (mask & IN_ACCESS)
    76     aspects = Fcons (Qaccess, aspects);
    77   if (mask & IN_ATTRIB)
    78     aspects = Fcons (Qattrib, aspects);
    79   if (mask & IN_CLOSE_WRITE)
    80     aspects = Fcons (Qclose_write, aspects);
    81   if (mask & IN_CLOSE_NOWRITE)
    82     aspects = Fcons (Qclose_nowrite, aspects);
    83   if (mask & IN_CREATE)
    84     aspects = Fcons (Qcreate, aspects);
    85   if (mask & IN_DELETE)
    86     aspects = Fcons (Qdelete, aspects);
    87   if (mask & IN_DELETE_SELF)
    88     aspects = Fcons (Qdelete_self, aspects);
    89   if (mask & IN_MODIFY)
    90     aspects = Fcons (Qmodify, aspects);
    91   if (mask & IN_MOVE_SELF)
    92     aspects = Fcons (Qmove_self, aspects);
    93   if (mask & IN_MOVED_FROM)
    94     aspects = Fcons (Qmoved_from, aspects);
    95   if (mask & IN_MOVED_TO)
    96     aspects = Fcons (Qmoved_to, aspects);
    97   if (mask & IN_OPEN)
    98     aspects = Fcons (Qopen,  aspects);
    99   if (mask & IN_IGNORED)
   100     aspects = Fcons (Qignored, aspects);
   101   if (mask & IN_ISDIR)
   102     aspects = Fcons (Qisdir, aspects);
   103   if (mask & IN_Q_OVERFLOW)
   104     aspects = Fcons (Qq_overflow, aspects);
   105   if (mask & IN_UNMOUNT)
   106     aspects = Fcons (Qunmount, aspects);
   107   return aspects;
   108 }
   109 
   110 static uint32_t
   111 symbol_to_inotifymask (Lisp_Object symb)
   112 {
   113   if (EQ (symb, Qaccess))
   114     return IN_ACCESS;
   115   else if (EQ (symb, Qattrib))
   116     return IN_ATTRIB;
   117   else if (EQ (symb, Qclose_write))
   118     return IN_CLOSE_WRITE;
   119   else if (EQ (symb, Qclose_nowrite))
   120     return IN_CLOSE_NOWRITE;
   121   else if (EQ (symb, Qcreate))
   122     return IN_CREATE;
   123   else if (EQ (symb, Qdelete))
   124     return IN_DELETE;
   125   else if (EQ (symb, Qdelete_self))
   126     return IN_DELETE_SELF;
   127   else if (EQ (symb, Qmodify))
   128     return IN_MODIFY;
   129   else if (EQ (symb, Qmove_self))
   130     return IN_MOVE_SELF;
   131   else if (EQ (symb, Qmoved_from))
   132     return IN_MOVED_FROM;
   133   else if (EQ (symb, Qmoved_to))
   134     return IN_MOVED_TO;
   135   else if (EQ (symb, Qopen))
   136     return IN_OPEN;
   137   else if (EQ (symb, Qmove))
   138     return IN_MOVE;
   139   else if (EQ (symb, Qclose))
   140     return IN_CLOSE;
   141 
   142   else if (EQ (symb, Qdont_follow))
   143     return IN_DONT_FOLLOW;
   144   else if (EQ (symb, Qonlydir))
   145     return IN_ONLYDIR;
   146 
   147   else if (EQ (symb, Qt) || EQ (symb, Qall_events))
   148     return IN_ALL_EVENTS;
   149   else
   150     {
   151       errno = EINVAL;
   152       report_file_notify_error ("Unknown aspect", symb);
   153     }
   154 }
   155 
   156 static uint32_t
   157 aspect_to_inotifymask (Lisp_Object aspect)
   158 {
   159   if (CONSP (aspect) || NILP (aspect))
   160     {
   161       Lisp_Object x = aspect;
   162       uint32_t mask = 0;
   163       FOR_EACH_TAIL (x)
   164         mask |= symbol_to_inotifymask (XCAR (x));
   165       CHECK_LIST_END (x, aspect);
   166       return mask;
   167     }
   168   else
   169     return symbol_to_inotifymask (aspect);
   170 }
   171 
   172 static Lisp_Object
   173 inotifyevent_to_event (Lisp_Object watch, struct inotify_event const *ev)
   174 {
   175   Lisp_Object name;
   176   uint32_t mask;
   177   CONS_TO_INTEGER (Fnth (make_fixnum (3), watch), uint32_t, mask);
   178 
   179   if (! (mask & ev->mask))
   180     return Qnil;
   181 
   182   if (ev->len > 0)
   183     {
   184       name = make_unibyte_string (ev->name, strnlen (ev->name, ev->len));
   185       name = DECODE_FILE (name);
   186     }
   187   else
   188     name = XCAR (XCDR (watch));
   189 
   190   return list2 (list4 (Fcons (INT_TO_INTEGER (ev->wd), XCAR (watch)),
   191                        mask_to_aspects (ev->mask),
   192                        name,
   193                        INT_TO_INTEGER (ev->cookie)),
   194                 Fnth (make_fixnum (2), watch));
   195 }
   196 
   197 /* Add a new watch to watch-descriptor WD watching FILENAME and using
   198    IMASK and CALLBACK.  Return a cons (DESCRIPTOR . ID) uniquely
   199    identifying the new watch.  */
   200 static Lisp_Object
   201 add_watch (int wd, Lisp_Object filename,
   202            uint32_t imask, Lisp_Object callback)
   203 {
   204   Lisp_Object descriptor = INT_TO_INTEGER (wd);
   205   Lisp_Object tail = assoc_no_quit (descriptor, watch_list);
   206   Lisp_Object watch, watch_id;
   207   Lisp_Object mask = INT_TO_INTEGER (imask);
   208 
   209   EMACS_INT id = 0;
   210   if (NILP (tail))
   211     {
   212       tail = list1 (descriptor);
   213       watch_list = Fcons (tail, watch_list);
   214     }
   215   else
   216     {
   217       /* Assign a watch ID that is not already in use, by looking
   218          for a gap in the existing sorted list.  */
   219       for (; ! NILP (XCDR (tail)); tail = XCDR (tail), id++)
   220         if (!BASE_EQ (XCAR (XCAR (XCDR (tail))), make_fixnum (id)))
   221           break;
   222       if (MOST_POSITIVE_FIXNUM < id)
   223         emacs_abort ();
   224     }
   225 
   226   /* Insert the newly-assigned ID into the previously-discovered gap,
   227      which is possibly at the end of the list.  Inserting it there
   228      keeps the list sorted.  */
   229   watch_id = make_fixnum (id);
   230   watch = list4 (watch_id, filename, callback, mask);
   231   XSETCDR (tail, Fcons (watch, XCDR (tail)));
   232 
   233   return Fcons (descriptor, watch_id);
   234 }
   235 
   236 /* Find the watch list element (if any) matching DESCRIPTOR.  Return
   237    nil if not found.  If found, return t if the first element matches
   238    DESCRIPTOR; otherwise, return the cons whose cdr matches
   239    DESCRIPTOR.  This lets the caller easily remove the element
   240    matching DESCRIPTOR without having to search for it again, and
   241    without calling Fdelete (which might quit).  */
   242 
   243 static Lisp_Object
   244 find_descriptor (Lisp_Object descriptor)
   245 {
   246   Lisp_Object tail, prevtail = Qt;
   247   for (tail = watch_list; !NILP (tail); prevtail = tail, tail = XCDR (tail))
   248     if (equal_no_quit (XCAR (XCAR (tail)), descriptor))
   249       return prevtail;
   250   return Qnil;
   251 }
   252 
   253 /*  Remove all watches associated with the watch list element after
   254     PREVTAIL, or after the first element if PREVTAIL is t.  If INVALID_P
   255     is true, the descriptor is already invalid, i.e., it received a
   256     IN_IGNORED event.  In this case skip calling inotify_rm_watch.  */
   257 static void
   258 remove_descriptor (Lisp_Object prevtail, bool invalid_p)
   259 {
   260   Lisp_Object tail = CONSP (prevtail) ? XCDR (prevtail) : watch_list;
   261 
   262   int inotify_errno = 0;
   263   if (! invalid_p)
   264     {
   265       int wd;
   266       CONS_TO_INTEGER (XCAR (XCAR (tail)), int, wd);
   267       if (inotify_rm_watch (inotifyfd, wd) != 0)
   268         inotify_errno = errno;
   269     }
   270 
   271   if (CONSP (prevtail))
   272     XSETCDR (prevtail, XCDR (tail));
   273   else
   274     {
   275       watch_list = XCDR (tail);
   276       if (NILP (watch_list))
   277         {
   278           delete_read_fd (inotifyfd);
   279           emacs_close (inotifyfd);
   280           inotifyfd = -1;
   281         }
   282     }
   283 
   284   if (inotify_errno != 0)
   285     {
   286       errno = inotify_errno;
   287       report_file_notify_error ("Could not rm watch", XCAR (tail));
   288     }
   289 }
   290 
   291 /*  Remove watch associated with (descriptor, id).  */
   292 static void
   293 remove_watch (Lisp_Object descriptor, Lisp_Object id)
   294 {
   295   Lisp_Object prevtail = find_descriptor (descriptor);
   296   if (NILP (prevtail))
   297     return;
   298 
   299   Lisp_Object elt = XCAR (CONSP (prevtail) ? XCDR (prevtail) : watch_list);
   300   for (Lisp_Object prev = elt; !NILP (XCDR (prev)); prev = XCDR (prev))
   301     if (EQ (id, XCAR (XCAR (XCDR (prev)))))
   302       {
   303         XSETCDR (prev, XCDR (XCDR (prev)));
   304         if (NILP (XCDR (elt)))
   305           remove_descriptor (prevtail, false);
   306         break;
   307       }
   308 }
   309 
   310 /* This callback is called when the FD is available for read.  The inotify
   311    events are read from FD and converted into input_events.  */
   312 static void
   313 inotify_callback (int fd, void *_)
   314 {
   315   int to_read;
   316   if (ioctl (fd, FIONREAD, &to_read) < 0)
   317     report_file_notify_error ("Error while retrieving file system events",
   318                               Qnil);
   319   USE_SAFE_ALLOCA;
   320   char *buffer = SAFE_ALLOCA (to_read);
   321   ssize_t n = read (fd, buffer, to_read);
   322   if (n < 0)
   323     report_file_notify_error ("Error while reading file system events", Qnil);
   324 
   325   struct input_event event;
   326   EVENT_INIT (event);
   327   event.kind = FILE_NOTIFY_EVENT;
   328 
   329   for (ssize_t i = 0; i < n; )
   330     {
   331       struct inotify_event *ev = (struct inotify_event *) &buffer[i];
   332       Lisp_Object descriptor = INT_TO_INTEGER (ev->wd);
   333       Lisp_Object prevtail = find_descriptor (descriptor);
   334 
   335       if (! NILP (prevtail))
   336         {
   337           Lisp_Object tail = CONSP (prevtail) ? XCDR (prevtail) : watch_list;
   338           for (Lisp_Object watches = XCDR (XCAR (tail)); ! NILP (watches);
   339                watches = XCDR (watches))
   340             {
   341               event.arg = inotifyevent_to_event (XCAR (watches), ev);
   342               if (!NILP (event.arg))
   343                 kbd_buffer_store_event (&event);
   344             }
   345           /* If event was removed automatically: Drop it from watch list.  */
   346           if (ev->mask & IN_IGNORED)
   347             remove_descriptor (prevtail, true);
   348         }
   349       i += sizeof (*ev) + ev->len;
   350     }
   351 
   352   SAFE_FREE ();
   353 }
   354 
   355 DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
   356        doc: /* Add a watch for FILE-NAME to inotify.
   357 
   358 Return a watch descriptor.  The watch will look for ASPECT events and
   359 invoke CALLBACK when an event occurs.
   360 
   361 ASPECT might be one of the following symbols or a list of those symbols:
   362 
   363 access
   364 attrib
   365 close-write
   366 close-nowrite
   367 create
   368 delete
   369 delete-self
   370 modify
   371 move-self
   372 moved-from
   373 moved-to
   374 open
   375 
   376 all-events or t
   377 move
   378 close
   379 
   380 ASPECT can also contain the following symbols, which control whether
   381 the watch descriptor will be created:
   382 
   383 dont-follow
   384 onlydir
   385 
   386 Watching a directory is not recursive.  CALLBACK is passed a single argument
   387 EVENT which contains an event structure of the format
   388 
   389 \(WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
   390 
   391 WATCH-DESCRIPTOR is the same object that was returned by this function.  It can
   392 be tested for equality using `equal'.  ASPECTS describes the event.  It is a
   393 list of ASPECT symbols described above and can also contain one of the following
   394 symbols
   395 
   396 ignored
   397 isdir
   398 q-overflow
   399 unmount
   400 
   401 If a directory is watched then NAME is the name of file that caused the event.
   402 
   403 COOKIE is an object that can be compared using `equal' to identify two matching
   404 renames (moved-from and moved-to).
   405 
   406 See inotify(7) and inotify_add_watch(2) for further information.  The
   407 inotify fd is managed internally and there is no corresponding
   408 inotify_init.  Use `inotify-rm-watch' to remove a watch.
   409 
   410 The following inotify bit-masks cannot be used because descriptors are
   411 shared across different callers.
   412 
   413 IN_EXCL_UNLINK
   414 IN_MASK_ADD
   415 IN_ONESHOT  */)
   416      (Lisp_Object filename, Lisp_Object aspect, Lisp_Object callback)
   417 {
   418   Lisp_Object encoded_file_name;
   419   int wd = -1;
   420   uint32_t imask = aspect_to_inotifymask (aspect);
   421   uint32_t mask = imask | IN_MASK_ADD | IN_EXCL_UNLINK;
   422 
   423   CHECK_STRING (filename);
   424 
   425   if (inotifyfd < 0)
   426     {
   427       inotifyfd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC);
   428       if (inotifyfd < 0)
   429         report_file_notify_error ("File watching is not available", Qnil);
   430       watch_list = Qnil;
   431       add_read_fd (inotifyfd, &inotify_callback, NULL);
   432     }
   433 
   434   encoded_file_name = ENCODE_FILE (filename);
   435   wd = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
   436   if (wd < 0)
   437     report_file_notify_error ("Could not add watch for file", filename);
   438 
   439   return add_watch (wd, filename, imask, callback);
   440 }
   441 
   442 static bool
   443 valid_watch_descriptor (Lisp_Object wd)
   444 {
   445   return (CONSP (wd)
   446           && (RANGED_FIXNUMP (0, XCAR (wd), INT_MAX)
   447               || (CONSP (XCAR (wd))
   448                   && RANGED_FIXNUMP ((MOST_POSITIVE_FIXNUM >> 16) + 1,
   449                                       XCAR (XCAR (wd)), INT_MAX >> 16)
   450                   && RANGED_FIXNUMP (0, XCDR (XCAR (wd)), (1 << 16) - 1)))
   451           && FIXNATP (XCDR (wd)));
   452 }
   453 
   454 DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
   455        doc: /* Remove an existing WATCH-DESCRIPTOR.
   456 
   457 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
   458 
   459 See inotify_rm_watch(2) for more information.  */)
   460      (Lisp_Object watch_descriptor)
   461 {
   462 
   463   Lisp_Object descriptor, id;
   464 
   465   if (! valid_watch_descriptor (watch_descriptor))
   466     report_file_notify_error ("Invalid descriptor ", watch_descriptor);
   467 
   468   descriptor = XCAR (watch_descriptor);
   469   id = XCDR (watch_descriptor);
   470   remove_watch (descriptor, id);
   471 
   472   return Qt;
   473 }
   474 
   475 DEFUN ("inotify-valid-p", Finotify_valid_p, Sinotify_valid_p, 1, 1, 0,
   476        doc: /* Check a watch specified by its WATCH-DESCRIPTOR.
   477 
   478 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
   479 
   480 A watch can become invalid if the file or directory it watches is
   481 deleted, or if the watcher thread exits abnormally for any other
   482 reason.  Removing the watch by calling `inotify-rm-watch' also makes
   483 it invalid.  */)
   484      (Lisp_Object watch_descriptor)
   485 {
   486   if (! valid_watch_descriptor (watch_descriptor))
   487     return Qnil;
   488   Lisp_Object tail = assoc_no_quit (XCAR (watch_descriptor), watch_list);
   489   if (NILP (tail))
   490     return Qnil;
   491   Lisp_Object watch = assq_no_quit (XCDR (watch_descriptor), XCDR (tail));
   492   return ! NILP (watch) ? Qt : Qnil;
   493 }
   494 
   495 #ifdef INOTIFY_DEBUG
   496 DEFUN ("inotify-watch-list", Finotify_watch_list, Sinotify_watch_list, 0, 0, 0,
   497        doc: /* Return a copy of the internal watch_list.  */)
   498 {
   499   return Fcopy_sequence (watch_list);
   500 }
   501 
   502 DEFUN ("inotify-allocated-p", Finotify_allocated_p, Sinotify_allocated_p, 0, 0, 0,
   503        doc: /* Return non-nil, if an inotify instance is allocated.  */)
   504 {
   505   return inotifyfd < 0 ? Qnil : Qt;
   506 }
   507 #endif
   508 
   509 void
   510 syms_of_inotify (void)
   511 {
   512   DEFSYM (Qaccess, "access");           /* IN_ACCESS */
   513   DEFSYM (Qattrib, "attrib");           /* IN_ATTRIB */
   514   DEFSYM (Qclose_write, "close-write"); /* IN_CLOSE_WRITE */
   515   DEFSYM (Qclose_nowrite, "close-nowrite");
   516                                         /* IN_CLOSE_NOWRITE */
   517   DEFSYM (Qcreate, "create");           /* IN_CREATE */
   518   DEFSYM (Qdelete, "delete");           /* IN_DELETE */
   519   DEFSYM (Qdelete_self, "delete-self"); /* IN_DELETE_SELF */
   520   DEFSYM (Qmodify, "modify");           /* IN_MODIFY */
   521   DEFSYM (Qmove_self, "move-self");     /* IN_MOVE_SELF */
   522   DEFSYM (Qmoved_from, "moved-from");   /* IN_MOVED_FROM */
   523   DEFSYM (Qmoved_to, "moved-to");       /* IN_MOVED_TO */
   524   DEFSYM (Qopen, "open");               /* IN_OPEN */
   525 
   526   DEFSYM (Qall_events, "all-events");   /* IN_ALL_EVENTS */
   527   DEFSYM (Qmove, "move");               /* IN_MOVE */
   528   DEFSYM (Qclose, "close");             /* IN_CLOSE */
   529 
   530   DEFSYM (Qdont_follow, "dont-follow"); /* IN_DONT_FOLLOW */
   531   DEFSYM (Qonlydir, "onlydir");         /* IN_ONLYDIR */
   532 
   533 #if 0
   534   /* Defined in coding.c, which uses it on all platforms.  */
   535   DEFSYM (Qignored, "ignored");         /* IN_IGNORED */
   536 #endif
   537   DEFSYM (Qisdir, "isdir");             /* IN_ISDIR */
   538   DEFSYM (Qq_overflow, "q-overflow");   /* IN_Q_OVERFLOW */
   539   DEFSYM (Qunmount, "unmount");         /* IN_UNMOUNT */
   540 
   541   defsubr (&Sinotify_add_watch);
   542   defsubr (&Sinotify_rm_watch);
   543   defsubr (&Sinotify_valid_p);
   544 
   545 #ifdef INOTIFY_DEBUG
   546   defsubr (&Sinotify_watch_list);
   547   defsubr (&Sinotify_allocated_p);
   548 #endif
   549   staticpro (&watch_list);
   550 
   551   Fprovide (intern_c_string ("inotify"), Qnil);
   552 }

/* [<][>][^][v][top][bottom][index][help] */