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

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