inotify.c 11.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
/* Inotify support for Emacs

Copyright (C) 2012
  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_INOTIFY

#include "lisp.h"
#include "coding.h"
#include "process.h"
#include "keyboard.h"
#include "character.h"
#include "frame.h" /* Required for termhooks.h.  */
#include "termhooks.h"

static Lisp_Object Qaccess;        /* IN_ACCESS */
static Lisp_Object Qattrib;        /* IN_ATTRIB */
static Lisp_Object Qclose_write;   /* IN_CLOSE_WRITE */
static Lisp_Object Qclose_nowrite; /* IN_CLOSE_NOWRITE */
static Lisp_Object Qcreate;        /* IN_CREATE */
static Lisp_Object Qdelete;        /* IN_DELETE */
static Lisp_Object Qdelete_self;   /* IN_DELETE_SELF */
static Lisp_Object Qmodify;        /* IN_MODIFY */
static Lisp_Object Qmove_self;     /* IN_MOVE_SELF */
static Lisp_Object Qmoved_from;    /* IN_MOVED_FROM */
static Lisp_Object Qmoved_to;      /* IN_MOVED_TO */
static Lisp_Object Qopen;          /* IN_OPEN */

static Lisp_Object Qall_events;    /* IN_ALL_EVENTS */
static Lisp_Object Qmove;          /* IN_MOVE */
static Lisp_Object Qclose;         /* IN_CLOSE */

static Lisp_Object Qdont_follow;   /* IN_DONT_FOLLOW */
static Lisp_Object Qexcl_unlink;   /* IN_EXCL_UNLINK */
static Lisp_Object Qmask_add;      /* IN_MASK_ADD */
static Lisp_Object Qoneshot;       /* IN_ONESHOT */
static Lisp_Object Qonlydir;       /* IN_ONLYDIR */

static Lisp_Object Qignored;       /* IN_IGNORED */
static Lisp_Object Qisdir;         /* IN_ISDIR */
static Lisp_Object Qq_overflow;    /* IN_Q_OVERFLOW */
static Lisp_Object Qunmount;       /* IN_UNMOUNT */

#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

enum { uninitialized = -100 };
/* File handle for inotify.  */
static int inotifyfd = uninitialized;

/* Assoc list of files being watched.
   Format:
   (watch-descriptor . callback)
 */
static Lisp_Object watch_list;

static Lisp_Object
make_watch_descriptor (int wd)
{
  /* TODO replace this with a Misc Object! */
  return make_number (wd);
}

static Lisp_Object
mask_to_aspects (uint32_t mask) {
  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 Lisp_Object
inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev)
{
  Lisp_Object name = Qnil;
  if (ev->len > 0)
    {
      size_t const len = strlen (ev->name);
      name = make_unibyte_string (ev->name, min (len, ev->len));
      name = DECODE_FILE (name);
    }

  return list2 (list4 (make_watch_descriptor (ev->wd),
                       mask_to_aspects (ev->mask),
                       make_number (ev->cookie),
                       name),
                XCDR (watch_object));
}

/* 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 *_)
{
  struct input_event event;
  Lisp_Object watch_object;
  int to_read;
  char *buffer;
  ssize_t n;
  size_t i;

  to_read = 0;
  if (ioctl (fd, FIONREAD, &to_read) == -1)
    report_file_error ("Error while trying to retrieve file system events",
                       Qnil);
  buffer = xmalloc (to_read);
  n = read (fd, buffer, to_read);
  if (n < 0)
    {
      xfree (buffer);
      report_file_error ("Error while trying to read file system events",
                         Qnil);
    }

  EVENT_INIT (event);
  event.kind = FILE_NOTIFY_EVENT;

  i = 0;
  while (i < (size_t)n)
    {
      struct inotify_event *ev = (struct inotify_event*)&buffer[i];

      watch_object = Fassoc (make_watch_descriptor (ev->wd), watch_list);
      if (!NILP (watch_object))
        {
          event.arg = inotifyevent_to_event (watch_object, ev);

          /* If event was removed automatically: Drop it from watch list.  */
          if (ev->mask & IN_IGNORED)
            watch_list = Fdelete (watch_object, watch_list);
189 190 191

	  if (!NILP (event.arg))
	    kbd_buffer_store_event (&event);
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
        }

      i += sizeof (*ev) + ev->len;
    }

  xfree (buffer);
}

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;
  else if (EQ (symb, Qexcl_unlink))
    return IN_EXCL_UNLINK;
  else if (EQ (symb, Qmask_add))
    return IN_MASK_ADD;
  else if (EQ (symb, Qoneshot))
    return IN_ONESHOT;
  else if (EQ (symb, Qonlydir))
    return IN_ONLYDIR;

  else if (EQ (symb, Qt) || EQ (symb, Qall_events))
    return IN_ALL_EVENTS;
  else
    signal_error ("Unknown aspect", symb);
}

static uint32_t
aspect_to_inotifymask (Lisp_Object aspect)
{
  if (CONSP (aspect))
    {
      Lisp_Object x = aspect;
      uint32_t mask = 0;
      while (CONSP (x))
        {
          mask |= symbol_to_inotifymask (XCAR (x));
          x = XCDR (x);
        }
      return mask;
    }
  else
    return symbol_to_inotifymask (aspect);
}

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

A WATCH-DESCRIPTOR is returned on success.  ASPECT might be one of the following
symbols or a list of those symbols:

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

all-events or t
move
close

The following symbols can also be added to a list of aspects

dont-follow
excl-unlink
mask-add
oneshot
onlydir

Watching a directory is not recursive.  CALLBACK gets called in case of an
event.  It gets passed a single argument EVENT which contains an event structure
of the format

(WATCH-DESCRIPTOR ASPECTS COOKIE NAME)

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

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

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

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.
             */)
     (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback)
{
  uint32_t mask;
  Lisp_Object watch_object;
327
  Lisp_Object encoded_file_name;
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
  Lisp_Object watch_descriptor;
  int watchdesc = -1;

  CHECK_STRING (file_name);

  if (inotifyfd == uninitialized)
    {
      inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
      if (inotifyfd == -1)
        {
          inotifyfd = uninitialized;
          report_file_error ("File watching feature (inotify) is not available",
                             Qnil);
        }
      watch_list = Qnil;
      add_read_fd (inotifyfd, &inotify_callback, NULL);
    }

  mask = aspect_to_inotifymask (aspect);
347 348
  encoded_file_name = ENCODE_FILE (file_name);
  watchdesc = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
  if (watchdesc == -1)
    report_file_error ("Could not add watch for file", Fcons (file_name, Qnil));

  watch_descriptor = make_watch_descriptor (watchdesc);

  /* Delete existing watch object. */
  watch_object = Fassoc (watch_descriptor, watch_list);
  if (!NILP (watch_object))
      watch_list = Fdelete (watch_object, watch_list);

  /* Store watch object in watch list. */
  watch_object = Fcons (watch_descriptor, callback);
  watch_list = Fcons (watch_object, watch_list);

  return watch_descriptor;
}

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'.

See inotify_rm_watch(2) for more information.
             */)
     (Lisp_Object watch_descriptor)
{
  Lisp_Object watch_object;
  int wd = XINT (watch_descriptor);

  if (inotify_rm_watch (inotifyfd, wd) == -1)
    report_file_error ("Could not rm watch", Fcons (watch_descriptor,
                                                    Qnil));

  /* Remove watch descriptor from watch list. */
  watch_object = Fassoc (watch_descriptor, watch_list);
  if (!NILP (watch_object))
    watch_list = Fdelete (watch_object, watch_list);

  /* Cleanup if no more files are watched. */
  if (NILP (watch_list))
    {
      close (inotifyfd);
      delete_read_fd (inotifyfd);
      inotifyfd = uninitialized;
    }

  return Qt;
}

void
syms_of_inotify (void)
{
  DEFSYM (Qaccess, "access");
  DEFSYM (Qattrib, "attrib");
  DEFSYM (Qclose_write, "close-write");
  DEFSYM (Qclose_nowrite, "close-nowrite");
  DEFSYM (Qcreate, "create");
  DEFSYM (Qdelete, "delete");
  DEFSYM (Qdelete_self, "delete-self");
  DEFSYM (Qmodify, "modify");
  DEFSYM (Qmove_self, "move-self");
  DEFSYM (Qmoved_from, "moved-from");
  DEFSYM (Qmoved_to, "moved-to");
  DEFSYM (Qopen, "open");

  DEFSYM (Qall_events, "all-events");
  DEFSYM (Qmove, "move");
  DEFSYM (Qclose, "close");

  DEFSYM (Qdont_follow, "dont-follow");
  DEFSYM (Qexcl_unlink, "excl-unlink");
  DEFSYM (Qmask_add, "mask-add");
  DEFSYM (Qoneshot, "oneshot");
  DEFSYM (Qonlydir, "onlydir");

  DEFSYM (Qignored, "ignored");
  DEFSYM (Qisdir, "isdir");
  DEFSYM (Qq_overflow, "q-overflow");
  DEFSYM (Qunmount, "unmount");

  defsubr (&Sinotify_add_watch);
  defsubr (&Sinotify_rm_watch);

  staticpro (&watch_list);

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

#endif /* HAVE_INOTIFY */