inotify.c 16 KB
Newer Older
1 2
/* Inotify support for Emacs

Paul Eggert's avatar
Paul Eggert committed
3
Copyright (C) 2012-2020 Free Software Foundation, Inc.
4 5 6 7 8

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
9 10
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
11 12 13 14 15 16 17

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
18
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
19 20 21 22 23 24 25 26 27

#include <config.h>

#include "lisp.h"
#include "coding.h"
#include "process.h"
#include "keyboard.h"
#include "termhooks.h"

28
#include <errno.h>
29 30 31 32 33 34 35 36 37 38 39 40 41
#include <sys/inotify.h>
#include <sys/ioctl.h>

/* Ignore bits that might be undefined on old GNU/Linux systems.  */
#ifndef IN_EXCL_UNLINK
# define IN_EXCL_UNLINK 0
#endif
#ifndef IN_DONT_FOLLOW
# define IN_DONT_FOLLOW 0
#endif
#ifndef IN_ONLYDIR
# define IN_ONLYDIR 0
#endif
42

43
/* File handle for inotify.  */
44
static int inotifyfd = -1;
45

46 47
/* Alist of files being watched.  We want the returned descriptor to
   be unique for every watch, but inotify returns the same descriptor
Paul Eggert's avatar
Paul Eggert committed
48 49 50
   WD for multiple calls to inotify_add_watch with the same file.
   Supply a nonnegative integer ID, so that WD and ID together
   uniquely identify a watch/file combination.
51 52 53 54 55 56 57

   For the same reason, we also need to store the watch's mask and we
   can't allow the following flags to be used.

   IN_EXCL_UNLINK
   IN_MASK_ADD
   IN_ONESHOT
58

Paul Eggert's avatar
Paul Eggert committed
59 60 61 62 63 64 65 66 67 68
   Each element of this list is of the form (DESCRIPTOR . WATCHES)
   where no two DESCRIPTOR values are the same.  DESCRIPTOR represents
   the inotify watch descriptor and WATCHES is a list with elements of
   the form (ID FILENAME CALLBACK MASK), where ID is the integer
   described above, FILENAME names the file being watched, CALLBACK is
   invoked when the event occurs, and MASK represents the aspects
   being watched.  The WATCHES list is sorted by ID.  Although
   DESCRIPTOR and MASK are ordinarily integers, they are conses when
   representing integers outside of fixnum range.  */

69 70 71
static Lisp_Object watch_list;

static Lisp_Object
Paul Eggert's avatar
Paul Eggert committed
72 73
mask_to_aspects (uint32_t mask)
{
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  Lisp_Object aspects = Qnil;
  if (mask & IN_ACCESS)
    aspects = Fcons (Qaccess, aspects);
  if (mask & IN_ATTRIB)
    aspects = Fcons (Qattrib, aspects);
  if (mask & IN_CLOSE_WRITE)
    aspects = Fcons (Qclose_write, aspects);
  if (mask & IN_CLOSE_NOWRITE)
    aspects = Fcons (Qclose_nowrite, aspects);
  if (mask & IN_CREATE)
    aspects = Fcons (Qcreate, aspects);
  if (mask & IN_DELETE)
    aspects = Fcons (Qdelete, aspects);
  if (mask & IN_DELETE_SELF)
    aspects = Fcons (Qdelete_self, aspects);
  if (mask & IN_MODIFY)
    aspects = Fcons (Qmodify, aspects);
  if (mask & IN_MOVE_SELF)
    aspects = Fcons (Qmove_self, aspects);
  if (mask & IN_MOVED_FROM)
    aspects = Fcons (Qmoved_from, aspects);
  if (mask & IN_MOVED_TO)
    aspects = Fcons (Qmoved_to, aspects);
  if (mask & IN_OPEN)
    aspects = Fcons (Qopen,  aspects);
  if (mask & IN_IGNORED)
    aspects = Fcons (Qignored, aspects);
  if (mask & IN_ISDIR)
    aspects = Fcons (Qisdir, aspects);
  if (mask & IN_Q_OVERFLOW)
    aspects = Fcons (Qq_overflow, aspects);
  if (mask & IN_UNMOUNT)
    aspects = Fcons (Qunmount, aspects);
  return aspects;
}

static uint32_t
symbol_to_inotifymask (Lisp_Object symb)
{
  if (EQ (symb, Qaccess))
    return IN_ACCESS;
  else if (EQ (symb, Qattrib))
    return IN_ATTRIB;
  else if (EQ (symb, Qclose_write))
    return IN_CLOSE_WRITE;
  else if (EQ (symb, Qclose_nowrite))
    return IN_CLOSE_NOWRITE;
  else if (EQ (symb, Qcreate))
    return IN_CREATE;
  else if (EQ (symb, Qdelete))
    return IN_DELETE;
  else if (EQ (symb, Qdelete_self))
    return IN_DELETE_SELF;
  else if (EQ (symb, Qmodify))
    return IN_MODIFY;
  else if (EQ (symb, Qmove_self))
    return IN_MOVE_SELF;
  else if (EQ (symb, Qmoved_from))
    return IN_MOVED_FROM;
  else if (EQ (symb, Qmoved_to))
    return IN_MOVED_TO;
  else if (EQ (symb, Qopen))
    return IN_OPEN;
  else if (EQ (symb, Qmove))
    return IN_MOVE;
  else if (EQ (symb, Qclose))
    return IN_CLOSE;

  else if (EQ (symb, Qdont_follow))
    return IN_DONT_FOLLOW;
Paul Eggert's avatar
Paul Eggert committed
144 145
  else if (EQ (symb, Qonlydir))
    return IN_ONLYDIR;
146 147 148 149

  else if (EQ (symb, Qt) || EQ (symb, Qall_events))
    return IN_ALL_EVENTS;
  else
150 151
    {
      errno = EINVAL;
152
      report_file_notify_error ("Unknown aspect", symb);
153
    }
154 155 156 157 158
}

static uint32_t
aspect_to_inotifymask (Lisp_Object aspect)
{
Paul Eggert's avatar
Paul Eggert committed
159
  if (CONSP (aspect) || NILP (aspect))
160 161 162
    {
      Lisp_Object x = aspect;
      uint32_t mask = 0;
Paul Eggert's avatar
Paul Eggert committed
163 164 165
      FOR_EACH_TAIL (x)
	mask |= symbol_to_inotifymask (XCAR (x));
      CHECK_LIST_END (x, aspect);
166 167 168 169 170 171
      return mask;
    }
  else
    return symbol_to_inotifymask (aspect);
}

172 173 174
static Lisp_Object
inotifyevent_to_event (Lisp_Object watch, struct inotify_event const *ev)
{
Paul Eggert's avatar
Paul Eggert committed
175 176
  Lisp_Object name;
  uint32_t mask;
177
  CONS_TO_INTEGER (Fnth (make_fixnum (3), watch), uint32_t, mask);
178

Paul Eggert's avatar
Paul Eggert committed
179
  if (! (mask & ev->mask))
180 181 182 183
    return Qnil;

  if (ev->len > 0)
    {
184
      name = make_unibyte_string (ev->name, strnlen (ev->name, ev->len));
185 186 187 188 189
      name = DECODE_FILE (name);
    }
  else
    name = XCAR (XCDR (watch));

190
  return list2 (list4 (Fcons (INT_TO_INTEGER (ev->wd), XCAR (watch)),
191 192
                       mask_to_aspects (ev->mask),
                       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 199
   IMASK and CALLBACK.  Return a cons (DESCRIPTOR . ID) uniquely
   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);
Paul Eggert's avatar
Paul Eggert committed
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

Paul Eggert's avatar
Paul Eggert committed
209 210 211 212 213 214 215
  EMACS_INT id = 0;
  if (NILP (tail))
    {
      tail = list1 (descriptor);
      watch_list = Fcons (tail, watch_list);
    }
  else
216
    {
Paul Eggert's avatar
Paul Eggert committed
217 218 219
      /* Assign a watch ID that is not already in use, by looking
	 for a gap in the existing sorted list.  */
      for (; ! NILP (XCDR (tail)); tail = XCDR (tail), id++)
220
	if (!EQ (XCAR (XCAR (XCDR (tail))), make_fixnum (id)))
Paul Eggert's avatar
Paul Eggert committed
221 222 223
	  break;
      if (MOST_POSITIVE_FIXNUM < id)
	emacs_abort ();
224 225
    }

226 227 228
  /* Insert the newly-assigned ID into the previously-discovered gap,
     which is possibly at the end of the list.  Inserting it there
     keeps the list sorted.  */
229
  watch_id = make_fixnum (id);
230
  watch = list4 (watch_id, filename, callback, mask);
Paul Eggert's avatar
Paul Eggert committed
231
  XSETCDR (tail, Fcons (watch, XCDR (tail)));
232 233 234 235

  return Fcons (descriptor, watch_id);
}

Paul Eggert's avatar
Paul Eggert committed
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
/* Find the watch list element (if any) matching DESCRIPTOR.  Return
   nil if not found.  If found, return t if the first element matches
   DESCRIPTOR; otherwise, return the cons whose cdr matches
   DESCRIPTOR.  This lets the caller easily remove the element
   matching DESCRIPTOR without having to search for it again, and
   without calling Fdelete (which might quit).  */

static Lisp_Object
find_descriptor (Lisp_Object descriptor)
{
  Lisp_Object tail, prevtail = Qt;
  for (tail = watch_list; !NILP (tail); prevtail = tail, tail = XCDR (tail))
    if (equal_no_quit (XCAR (XCAR (tail)), descriptor))
      return prevtail;
  return Qnil;
}

/*  Remove all watches associated with the watch list element after
    PREVTAIL, or after the first element if PREVTAIL is t.  If INVALID_P
    is true, the descriptor is already invalid, i.e., it received a
    IN_IGNORED event.  In this case skip calling inotify_rm_watch.  */
257
static void
Paul Eggert's avatar
Paul Eggert committed
258
remove_descriptor (Lisp_Object prevtail, bool invalid_p)
259
{
Paul Eggert's avatar
Paul Eggert committed
260
  Lisp_Object tail = CONSP (prevtail) ? XCDR (prevtail) : watch_list;
261

Paul Eggert's avatar
Paul Eggert committed
262 263
  int inotify_errno = 0;
  if (! invalid_p)
264
    {
Paul Eggert's avatar
Paul Eggert committed
265 266 267 268 269
      int wd;
      CONS_TO_INTEGER (XCAR (XCAR (tail)), int, wd);
      if (inotify_rm_watch (inotifyfd, wd) != 0)
	inotify_errno = errno;
    }
270

Paul Eggert's avatar
Paul Eggert committed
271 272 273 274 275 276 277 278 279 280 281
  if (CONSP (prevtail))
    XSETCDR (prevtail, XCDR (tail));
  else
    {
      watch_list = XCDR (tail);
      if (NILP (watch_list))
	{
	  delete_read_fd (inotifyfd);
	  emacs_close (inotifyfd);
	  inotifyfd = -1;
	}
282
    }
Paul Eggert's avatar
Paul Eggert committed
283 284

  if (inotify_errno != 0)
285
    {
Paul Eggert's avatar
Paul Eggert committed
286 287
      errno = inotify_errno;
      report_file_notify_error ("Could not rm watch", XCAR (tail));
288 289 290
    }
}

291
/*  Remove watch associated with (descriptor, id).  */
292 293 294
static void
remove_watch (Lisp_Object descriptor, Lisp_Object id)
{
Paul Eggert's avatar
Paul Eggert committed
295 296 297 298 299 300 301 302 303 304 305 306 307
  Lisp_Object prevtail = find_descriptor (descriptor);
  if (NILP (prevtail))
    return;

  Lisp_Object elt = XCAR (CONSP (prevtail) ? XCDR (prevtail) : watch_list);
  for (Lisp_Object prev = elt; !NILP (XCDR (prev)); prev = XCDR (prev))
    if (EQ (id, XCAR (XCAR (XCDR (prev)))))
      {
	XSETCDR (prev, XCDR (XCDR (prev)));
	if (NILP (XCDR (elt)))
	  remove_descriptor (prevtail, false);
	break;
      }
308 309 310 311 312 313 314 315
}

/* This callback is called when the FD is available for read.  The inotify
   events are read from FD and converted into input_events.  */
static void
inotify_callback (int fd, void *_)
{
  int to_read;
Paul Eggert's avatar
Paul Eggert committed
316
  if (ioctl (fd, FIONREAD, &to_read) < 0)
317 318
    report_file_notify_error ("Error while retrieving file system events",
			      Qnil);
Paul Eggert's avatar
Paul Eggert committed
319 320 321
  USE_SAFE_ALLOCA;
  char *buffer = SAFE_ALLOCA (to_read);
  ssize_t n = read (fd, buffer, to_read);
322
  if (n < 0)
Paul Eggert's avatar
Paul Eggert committed
323
    report_file_notify_error ("Error while reading file system events", Qnil);
324

Paul Eggert's avatar
Paul Eggert committed
325
  struct input_event event;
326 327 328
  EVENT_INIT (event);
  event.kind = FILE_NOTIFY_EVENT;

Paul Eggert's avatar
Paul Eggert committed
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);
Paul Eggert's avatar
Paul Eggert committed
333
      Lisp_Object prevtail = find_descriptor (descriptor);
334

Paul Eggert's avatar
Paul Eggert committed
335
      if (! NILP (prevtail))
336
        {
Paul Eggert's avatar
Paul Eggert committed
337 338 339
	  Lisp_Object tail = CONSP (prevtail) ? XCDR (prevtail) : watch_list;
	  for (Lisp_Object watches = XCDR (XCAR (tail)); ! NILP (watches);
	       watches = XCDR (watches))
340 341 342 343 344 345 346
            {
              event.arg = inotifyevent_to_event (XCAR (watches), ev);
              if (!NILP (event.arg))
                kbd_buffer_store_event (&event);
            }
          /* If event was removed automatically: Drop it from watch list.  */
          if (ev->mask & IN_IGNORED)
Paul Eggert's avatar
Paul Eggert committed
347
	    remove_descriptor (prevtail, true);
348 349 350 351
        }
      i += sizeof (*ev) + ev->len;
    }

Paul Eggert's avatar
Paul Eggert committed
352
  SAFE_FREE ();
353 354
}

355 356 357
DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
       doc: /* Add a watch for FILE-NAME to inotify.

358 359 360 361
Return a watch descriptor.  The watch will look for ASPECT events and
invoke CALLBACK when an event occurs.

ASPECT might be one of the following symbols or a list of those symbols:
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379

access
attrib
close-write
close-nowrite
create
delete
delete-self
modify
move-self
moved-from
moved-to
open

all-events or t
move
close

Paul Eggert's avatar
Paul Eggert committed
380 381
ASPECT can also contain the following symbols, which control whether
the watch descriptor will be created:
382 383

dont-follow
Paul Eggert's avatar
Paul Eggert committed
384
onlydir
385

386 387
Watching a directory is not recursive.  CALLBACK is passed a single argument
EVENT which contains an event structure of the format
388

389
\(WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
390 391 392 393 394 395 396 397 398 399 400

WATCH-DESCRIPTOR is the same object that was returned by this function.  It can
be tested for equality using `equal'.  ASPECTS describes the event.  It is a
list of ASPECT symbols described above and can also contain one of the following
symbols

ignored
isdir
q-overflow
unmount

401 402
If a directory is watched then NAME is the name of file that caused the event.

403
COOKIE is an object that can be compared using `equal' to identify two matching
404 405
renames (moved-from and moved-to).

406 407 408 409
See inotify(7) and inotify_add_watch(2) for further information.  The
inotify fd is managed internally and there is no corresponding
inotify_init.  Use `inotify-rm-watch' to remove a watch.

410 411
The following inotify bit-masks cannot be used because descriptors are
shared across different callers.
412 413 414

IN_EXCL_UNLINK
IN_MASK_ADD
Paul Eggert's avatar
Paul Eggert committed
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
    {
Paul Eggert's avatar
Paul Eggert committed
427
      inotifyfd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC);
428
      if (inotifyfd < 0)
429
	report_file_notify_error ("File watching is not available", Qnil);
430 431 432 433
      watch_list = Qnil;
      add_read_fd (inotifyfd, &inotify_callback, NULL);
    }

434 435
  encoded_file_name = ENCODE_FILE (filename);
  wd = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
Paul Eggert's avatar
Paul Eggert committed
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
}

Paul Eggert's avatar
Paul Eggert committed
442 443 444 445
static bool
valid_watch_descriptor (Lisp_Object wd)
{
  return (CONSP (wd)
446
	  && (RANGED_FIXNUMP (0, XCAR (wd), INT_MAX)
Paul Eggert's avatar
Paul Eggert committed
447
	      || (CONSP (XCAR (wd))
448
		  && RANGED_FIXNUMP ((MOST_POSITIVE_FIXNUM >> 16) + 1,
Paul Eggert's avatar
Paul Eggert committed
449
				      XCAR (XCAR (wd)), INT_MAX >> 16)
450 451
		  && RANGED_FIXNUMP (0, XCDR (XCAR (wd)), (1 << 16) - 1)))
	  && FIXNATP (XCDR (wd)));
Paul Eggert's avatar
Paul Eggert committed
452 453
}

454 455 456 457 458
DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
       doc: /* Remove an existing WATCH-DESCRIPTOR.

WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.

459
See inotify_rm_watch(2) for more information.  */)
460 461 462
     (Lisp_Object watch_descriptor)
{

463
  Lisp_Object descriptor, id;
464

Paul Eggert's avatar
Paul Eggert committed
465
  if (! valid_watch_descriptor (watch_descriptor))
466
    report_file_notify_error ("Invalid descriptor ", watch_descriptor);
467

468 469 470
  descriptor = XCAR (watch_descriptor);
  id = XCDR (watch_descriptor);
  remove_watch (descriptor, id);
471 472 473 474

  return Qt;
}

475
DEFUN ("inotify-valid-p", Finotify_valid_p, Sinotify_valid_p, 1, 1, 0,
Michael Albinus's avatar
Michael Albinus committed
476
       doc: /* Check a watch specified by its WATCH-DESCRIPTOR.
477

478 479 480 481 482 483
WATCH-DESCRIPTOR should be an object returned by `inotify-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 `inotify-rm-watch' also makes
it invalid.  */)
484 485
     (Lisp_Object watch_descriptor)
{
Paul Eggert's avatar
Paul Eggert committed
486
  if (! valid_watch_descriptor (watch_descriptor))
487
    return Qnil;
Paul Eggert's avatar
Paul Eggert committed
488 489 490 491
  Lisp_Object tail = assoc_no_quit (XCAR (watch_descriptor), watch_list);
  if (NILP (tail))
    return Qnil;
  Lisp_Object watch = assq_no_quit (XCDR (watch_descriptor), XCDR (tail));
492 493 494 495 496
  return ! NILP (watch) ? Qt : Qnil;
}

#ifdef INOTIFY_DEBUG
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 506 507 508
{
  return inotifyfd < 0 ? Qnil : Qt;
}
#endif

509 510 511
void
syms_of_inotify (void)
{
512 513 514
  DEFSYM (Qaccess, "access");		/* IN_ACCESS */
  DEFSYM (Qattrib, "attrib");		/* IN_ATTRIB */
  DEFSYM (Qclose_write, "close-write");	/* IN_CLOSE_WRITE */
515
  DEFSYM (Qclose_nowrite, "close-nowrite");
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
					/* IN_CLOSE_NOWRITE */
  DEFSYM (Qcreate, "create");		/* IN_CREATE */
  DEFSYM (Qdelete, "delete");		/* IN_DELETE */
  DEFSYM (Qdelete_self, "delete-self");	/* IN_DELETE_SELF */
  DEFSYM (Qmodify, "modify");		/* IN_MODIFY */
  DEFSYM (Qmove_self, "move-self");	/* IN_MOVE_SELF */
  DEFSYM (Qmoved_from, "moved-from");	/* IN_MOVED_FROM */
  DEFSYM (Qmoved_to, "moved-to");	/* IN_MOVED_TO */
  DEFSYM (Qopen, "open");		/* IN_OPEN */

  DEFSYM (Qall_events, "all-events");	/* IN_ALL_EVENTS */
  DEFSYM (Qmove, "move");		/* IN_MOVE */
  DEFSYM (Qclose, "close");		/* IN_CLOSE */

  DEFSYM (Qdont_follow, "dont-follow");	/* IN_DONT_FOLLOW */
Paul Eggert's avatar
Paul Eggert committed
531
  DEFSYM (Qonlydir, "onlydir");		/* IN_ONLYDIR */
532

533 534
#if 0
  /* Defined in coding.c, which uses it on all platforms.  */
535
  DEFSYM (Qignored, "ignored");		/* IN_IGNORED */
536
#endif
537 538 539
  DEFSYM (Qisdir, "isdir");		/* IN_ISDIR */
  DEFSYM (Qq_overflow, "q-overflow");	/* IN_Q_OVERFLOW */
  DEFSYM (Qunmount, "unmount");		/* IN_UNMOUNT */
540 541 542

  defsubr (&Sinotify_add_watch);
  defsubr (&Sinotify_rm_watch);
543
  defsubr (&Sinotify_valid_p);
544

545 546 547 548
#ifdef INOTIFY_DEBUG
  defsubr (&Sinotify_watch_list);
  defsubr (&Sinotify_allocated_p);
#endif
549 550 551 552
  staticpro (&watch_list);

  Fprovide (intern_c_string ("inotify"), Qnil);
}