| 1 | /* Inotify support for Emacs |
| 2 | |
| 3 | Copyright (C) 2012-2014 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 |
| 10 | (at 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 <http://www.gnu.org/licenses/>. */ |
| 19 | |
| 20 | #include <config.h> |
| 21 | |
| 22 | #ifdef HAVE_INOTIFY |
| 23 | |
| 24 | #include "lisp.h" |
| 25 | #include "coding.h" |
| 26 | #include "process.h" |
| 27 | #include "keyboard.h" |
| 28 | #include "character.h" |
| 29 | #include "frame.h" /* Required for termhooks.h. */ |
| 30 | #include "termhooks.h" |
| 31 | |
| 32 | static Lisp_Object Qaccess; /* IN_ACCESS */ |
| 33 | static Lisp_Object Qattrib; /* IN_ATTRIB */ |
| 34 | static Lisp_Object Qclose_write; /* IN_CLOSE_WRITE */ |
| 35 | static Lisp_Object Qclose_nowrite; /* IN_CLOSE_NOWRITE */ |
| 36 | static Lisp_Object Qcreate; /* IN_CREATE */ |
| 37 | static Lisp_Object Qdelete; /* IN_DELETE */ |
| 38 | static Lisp_Object Qdelete_self; /* IN_DELETE_SELF */ |
| 39 | static Lisp_Object Qmodify; /* IN_MODIFY */ |
| 40 | static Lisp_Object Qmove_self; /* IN_MOVE_SELF */ |
| 41 | static Lisp_Object Qmoved_from; /* IN_MOVED_FROM */ |
| 42 | static Lisp_Object Qmoved_to; /* IN_MOVED_TO */ |
| 43 | static Lisp_Object Qopen; /* IN_OPEN */ |
| 44 | |
| 45 | static Lisp_Object Qall_events; /* IN_ALL_EVENTS */ |
| 46 | static Lisp_Object Qmove; /* IN_MOVE */ |
| 47 | static Lisp_Object Qclose; /* IN_CLOSE */ |
| 48 | |
| 49 | static Lisp_Object Qdont_follow; /* IN_DONT_FOLLOW */ |
| 50 | static Lisp_Object Qexcl_unlink; /* IN_EXCL_UNLINK */ |
| 51 | static Lisp_Object Qmask_add; /* IN_MASK_ADD */ |
| 52 | static Lisp_Object Qoneshot; /* IN_ONESHOT */ |
| 53 | static Lisp_Object Qonlydir; /* IN_ONLYDIR */ |
| 54 | |
| 55 | static Lisp_Object Qignored; /* IN_IGNORED */ |
| 56 | static Lisp_Object Qisdir; /* IN_ISDIR */ |
| 57 | static Lisp_Object Qq_overflow; /* IN_Q_OVERFLOW */ |
| 58 | static Lisp_Object Qunmount; /* IN_UNMOUNT */ |
| 59 | |
| 60 | #include <sys/inotify.h> |
| 61 | #include <sys/ioctl.h> |
| 62 | |
| 63 | /* Ignore bits that might be undefined on old GNU/Linux systems. */ |
| 64 | #ifndef IN_EXCL_UNLINK |
| 65 | # define IN_EXCL_UNLINK 0 |
| 66 | #endif |
| 67 | #ifndef IN_DONT_FOLLOW |
| 68 | # define IN_DONT_FOLLOW 0 |
| 69 | #endif |
| 70 | #ifndef IN_ONLYDIR |
| 71 | # define IN_ONLYDIR 0 |
| 72 | #endif |
| 73 | |
| 74 | /* File handle for inotify. */ |
| 75 | static int inotifyfd = -1; |
| 76 | |
| 77 | /* Assoc list of files being watched. |
| 78 | Format: |
| 79 | (watch-descriptor . callback) |
| 80 | */ |
| 81 | static Lisp_Object watch_list; |
| 82 | |
| 83 | static Lisp_Object |
| 84 | make_watch_descriptor (int wd) |
| 85 | { |
| 86 | /* TODO replace this with a Misc Object! */ |
| 87 | return make_number (wd); |
| 88 | } |
| 89 | |
| 90 | static Lisp_Object |
| 91 | mask_to_aspects (uint32_t mask) { |
| 92 | Lisp_Object aspects = Qnil; |
| 93 | if (mask & IN_ACCESS) |
| 94 | aspects = Fcons (Qaccess, aspects); |
| 95 | if (mask & IN_ATTRIB) |
| 96 | aspects = Fcons (Qattrib, aspects); |
| 97 | if (mask & IN_CLOSE_WRITE) |
| 98 | aspects = Fcons (Qclose_write, aspects); |
| 99 | if (mask & IN_CLOSE_NOWRITE) |
| 100 | aspects = Fcons (Qclose_nowrite, aspects); |
| 101 | if (mask & IN_CREATE) |
| 102 | aspects = Fcons (Qcreate, aspects); |
| 103 | if (mask & IN_DELETE) |
| 104 | aspects = Fcons (Qdelete, aspects); |
| 105 | if (mask & IN_DELETE_SELF) |
| 106 | aspects = Fcons (Qdelete_self, aspects); |
| 107 | if (mask & IN_MODIFY) |
| 108 | aspects = Fcons (Qmodify, aspects); |
| 109 | if (mask & IN_MOVE_SELF) |
| 110 | aspects = Fcons (Qmove_self, aspects); |
| 111 | if (mask & IN_MOVED_FROM) |
| 112 | aspects = Fcons (Qmoved_from, aspects); |
| 113 | if (mask & IN_MOVED_TO) |
| 114 | aspects = Fcons (Qmoved_to, aspects); |
| 115 | if (mask & IN_OPEN) |
| 116 | aspects = Fcons (Qopen, aspects); |
| 117 | if (mask & IN_IGNORED) |
| 118 | aspects = Fcons (Qignored, aspects); |
| 119 | if (mask & IN_ISDIR) |
| 120 | aspects = Fcons (Qisdir, aspects); |
| 121 | if (mask & IN_Q_OVERFLOW) |
| 122 | aspects = Fcons (Qq_overflow, aspects); |
| 123 | if (mask & IN_UNMOUNT) |
| 124 | aspects = Fcons (Qunmount, aspects); |
| 125 | return aspects; |
| 126 | } |
| 127 | |
| 128 | static Lisp_Object |
| 129 | inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev) |
| 130 | { |
| 131 | Lisp_Object name = Qnil; |
| 132 | if (ev->len > 0) |
| 133 | { |
| 134 | size_t const len = strlen (ev->name); |
| 135 | name = make_unibyte_string (ev->name, min (len, ev->len)); |
| 136 | name = DECODE_FILE (name); |
| 137 | } |
| 138 | |
| 139 | return list2 (list4 (make_watch_descriptor (ev->wd), |
| 140 | mask_to_aspects (ev->mask), |
| 141 | name, |
| 142 | make_number (ev->cookie)), |
| 143 | XCDR (watch_object)); |
| 144 | } |
| 145 | |
| 146 | /* This callback is called when the FD is available for read. The inotify |
| 147 | events are read from FD and converted into input_events. */ |
| 148 | static void |
| 149 | inotify_callback (int fd, void *_) |
| 150 | { |
| 151 | struct input_event event; |
| 152 | Lisp_Object watch_object; |
| 153 | int to_read; |
| 154 | char *buffer; |
| 155 | ssize_t n; |
| 156 | size_t i; |
| 157 | |
| 158 | to_read = 0; |
| 159 | if (ioctl (fd, FIONREAD, &to_read) == -1) |
| 160 | xsignal1 |
| 161 | (Qfile_notify_error, |
| 162 | build_string ("Error while trying to retrieve file system events")); |
| 163 | buffer = xmalloc (to_read); |
| 164 | n = read (fd, buffer, to_read); |
| 165 | if (n < 0) |
| 166 | { |
| 167 | xfree (buffer); |
| 168 | xsignal1 |
| 169 | (Qfile_notify_error, |
| 170 | build_string ("Error while trying to read file system events")); |
| 171 | } |
| 172 | |
| 173 | EVENT_INIT (event); |
| 174 | event.kind = FILE_NOTIFY_EVENT; |
| 175 | |
| 176 | i = 0; |
| 177 | while (i < (size_t)n) |
| 178 | { |
| 179 | struct inotify_event *ev = (struct inotify_event*)&buffer[i]; |
| 180 | |
| 181 | watch_object = Fassoc (make_watch_descriptor (ev->wd), watch_list); |
| 182 | if (!NILP (watch_object)) |
| 183 | { |
| 184 | event.arg = inotifyevent_to_event (watch_object, ev); |
| 185 | |
| 186 | /* If event was removed automatically: Drop it from watch list. */ |
| 187 | if (ev->mask & IN_IGNORED) |
| 188 | watch_list = Fdelete (watch_object, watch_list); |
| 189 | |
| 190 | if (!NILP (event.arg)) |
| 191 | kbd_buffer_store_event (&event); |
| 192 | } |
| 193 | |
| 194 | i += sizeof (*ev) + ev->len; |
| 195 | } |
| 196 | |
| 197 | xfree (buffer); |
| 198 | } |
| 199 | |
| 200 | static uint32_t |
| 201 | symbol_to_inotifymask (Lisp_Object symb) |
| 202 | { |
| 203 | if (EQ (symb, Qaccess)) |
| 204 | return IN_ACCESS; |
| 205 | else if (EQ (symb, Qattrib)) |
| 206 | return IN_ATTRIB; |
| 207 | else if (EQ (symb, Qclose_write)) |
| 208 | return IN_CLOSE_WRITE; |
| 209 | else if (EQ (symb, Qclose_nowrite)) |
| 210 | return IN_CLOSE_NOWRITE; |
| 211 | else if (EQ (symb, Qcreate)) |
| 212 | return IN_CREATE; |
| 213 | else if (EQ (symb, Qdelete)) |
| 214 | return IN_DELETE; |
| 215 | else if (EQ (symb, Qdelete_self)) |
| 216 | return IN_DELETE_SELF; |
| 217 | else if (EQ (symb, Qmodify)) |
| 218 | return IN_MODIFY; |
| 219 | else if (EQ (symb, Qmove_self)) |
| 220 | return IN_MOVE_SELF; |
| 221 | else if (EQ (symb, Qmoved_from)) |
| 222 | return IN_MOVED_FROM; |
| 223 | else if (EQ (symb, Qmoved_to)) |
| 224 | return IN_MOVED_TO; |
| 225 | else if (EQ (symb, Qopen)) |
| 226 | return IN_OPEN; |
| 227 | else if (EQ (symb, Qmove)) |
| 228 | return IN_MOVE; |
| 229 | else if (EQ (symb, Qclose)) |
| 230 | return IN_CLOSE; |
| 231 | |
| 232 | else if (EQ (symb, Qdont_follow)) |
| 233 | return IN_DONT_FOLLOW; |
| 234 | else if (EQ (symb, Qexcl_unlink)) |
| 235 | return IN_EXCL_UNLINK; |
| 236 | else if (EQ (symb, Qmask_add)) |
| 237 | return IN_MASK_ADD; |
| 238 | else if (EQ (symb, Qoneshot)) |
| 239 | return IN_ONESHOT; |
| 240 | else if (EQ (symb, Qonlydir)) |
| 241 | return IN_ONLYDIR; |
| 242 | |
| 243 | else if (EQ (symb, Qt) || EQ (symb, Qall_events)) |
| 244 | return IN_ALL_EVENTS; |
| 245 | else |
| 246 | xsignal2 (Qfile_notify_error, build_string ("Unknown aspect"), symb); |
| 247 | } |
| 248 | |
| 249 | static uint32_t |
| 250 | aspect_to_inotifymask (Lisp_Object aspect) |
| 251 | { |
| 252 | if (CONSP (aspect)) |
| 253 | { |
| 254 | Lisp_Object x = aspect; |
| 255 | uint32_t mask = 0; |
| 256 | while (CONSP (x)) |
| 257 | { |
| 258 | mask |= symbol_to_inotifymask (XCAR (x)); |
| 259 | x = XCDR (x); |
| 260 | } |
| 261 | return mask; |
| 262 | } |
| 263 | else |
| 264 | return symbol_to_inotifymask (aspect); |
| 265 | } |
| 266 | |
| 267 | DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0, |
| 268 | doc: /* Add a watch for FILE-NAME to inotify. |
| 269 | |
| 270 | Return a watch descriptor. The watch will look for ASPECT events and |
| 271 | invoke CALLBACK when an event occurs. |
| 272 | |
| 273 | ASPECT might be one of the following symbols or a list of those symbols: |
| 274 | |
| 275 | access |
| 276 | attrib |
| 277 | close-write |
| 278 | close-nowrite |
| 279 | create |
| 280 | delete |
| 281 | delete-self |
| 282 | modify |
| 283 | move-self |
| 284 | moved-from |
| 285 | moved-to |
| 286 | open |
| 287 | |
| 288 | all-events or t |
| 289 | move |
| 290 | close |
| 291 | |
| 292 | The following symbols can also be added to a list of aspects: |
| 293 | |
| 294 | dont-follow |
| 295 | excl-unlink |
| 296 | mask-add |
| 297 | oneshot |
| 298 | onlydir |
| 299 | |
| 300 | Watching a directory is not recursive. CALLBACK is passed a single argument |
| 301 | EVENT which contains an event structure of the format |
| 302 | |
| 303 | (WATCH-DESCRIPTOR ASPECTS NAME COOKIE) |
| 304 | |
| 305 | WATCH-DESCRIPTOR is the same object that was returned by this function. It can |
| 306 | be tested for equality using `equal'. ASPECTS describes the event. It is a |
| 307 | list of ASPECT symbols described above and can also contain one of the following |
| 308 | symbols |
| 309 | |
| 310 | ignored |
| 311 | isdir |
| 312 | q-overflow |
| 313 | unmount |
| 314 | |
| 315 | If a directory is watched then NAME is the name of file that caused the event. |
| 316 | |
| 317 | COOKIE is an object that can be compared using `equal' to identify two matching |
| 318 | renames (moved-from and moved-to). |
| 319 | |
| 320 | See inotify(7) and inotify_add_watch(2) for further information. The inotify fd |
| 321 | is managed internally and there is no corresponding inotify_init. Use |
| 322 | `inotify-rm-watch' to remove a watch. |
| 323 | */) |
| 324 | (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback) |
| 325 | { |
| 326 | uint32_t mask; |
| 327 | Lisp_Object watch_object; |
| 328 | Lisp_Object encoded_file_name; |
| 329 | Lisp_Object watch_descriptor; |
| 330 | int watchdesc = -1; |
| 331 | |
| 332 | CHECK_STRING (file_name); |
| 333 | |
| 334 | if (inotifyfd < 0) |
| 335 | { |
| 336 | inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC); |
| 337 | if (inotifyfd < 0) |
| 338 | xsignal1 |
| 339 | (Qfile_notify_error, |
| 340 | build_string ("File watching feature (inotify) is not available")); |
| 341 | watch_list = Qnil; |
| 342 | add_read_fd (inotifyfd, &inotify_callback, NULL); |
| 343 | } |
| 344 | |
| 345 | mask = aspect_to_inotifymask (aspect); |
| 346 | encoded_file_name = ENCODE_FILE (file_name); |
| 347 | watchdesc = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask); |
| 348 | if (watchdesc == -1) |
| 349 | xsignal2 (Qfile_notify_error, |
| 350 | build_string ("Could not add watch for file"), file_name); |
| 351 | |
| 352 | watch_descriptor = make_watch_descriptor (watchdesc); |
| 353 | |
| 354 | /* Delete existing watch object. */ |
| 355 | watch_object = Fassoc (watch_descriptor, watch_list); |
| 356 | if (!NILP (watch_object)) |
| 357 | watch_list = Fdelete (watch_object, watch_list); |
| 358 | |
| 359 | /* Store watch object in watch list. */ |
| 360 | watch_object = Fcons (watch_descriptor, callback); |
| 361 | watch_list = Fcons (watch_object, watch_list); |
| 362 | |
| 363 | return watch_descriptor; |
| 364 | } |
| 365 | |
| 366 | DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0, |
| 367 | doc: /* Remove an existing WATCH-DESCRIPTOR. |
| 368 | |
| 369 | WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'. |
| 370 | |
| 371 | See inotify_rm_watch(2) for more information. |
| 372 | */) |
| 373 | (Lisp_Object watch_descriptor) |
| 374 | { |
| 375 | Lisp_Object watch_object; |
| 376 | int wd = XINT (watch_descriptor); |
| 377 | |
| 378 | if (inotify_rm_watch (inotifyfd, wd) == -1) |
| 379 | xsignal2 (Qfile_notify_error, |
| 380 | build_string ("Could not rm watch"), watch_descriptor); |
| 381 | |
| 382 | /* Remove watch descriptor from watch list. */ |
| 383 | watch_object = Fassoc (watch_descriptor, watch_list); |
| 384 | if (!NILP (watch_object)) |
| 385 | watch_list = Fdelete (watch_object, watch_list); |
| 386 | |
| 387 | /* Cleanup if no more files are watched. */ |
| 388 | if (NILP (watch_list)) |
| 389 | { |
| 390 | emacs_close (inotifyfd); |
| 391 | delete_read_fd (inotifyfd); |
| 392 | inotifyfd = -1; |
| 393 | } |
| 394 | |
| 395 | return Qt; |
| 396 | } |
| 397 | |
| 398 | void |
| 399 | syms_of_inotify (void) |
| 400 | { |
| 401 | DEFSYM (Qaccess, "access"); |
| 402 | DEFSYM (Qattrib, "attrib"); |
| 403 | DEFSYM (Qclose_write, "close-write"); |
| 404 | DEFSYM (Qclose_nowrite, "close-nowrite"); |
| 405 | DEFSYM (Qcreate, "create"); |
| 406 | DEFSYM (Qdelete, "delete"); |
| 407 | DEFSYM (Qdelete_self, "delete-self"); |
| 408 | DEFSYM (Qmodify, "modify"); |
| 409 | DEFSYM (Qmove_self, "move-self"); |
| 410 | DEFSYM (Qmoved_from, "moved-from"); |
| 411 | DEFSYM (Qmoved_to, "moved-to"); |
| 412 | DEFSYM (Qopen, "open"); |
| 413 | |
| 414 | DEFSYM (Qall_events, "all-events"); |
| 415 | DEFSYM (Qmove, "move"); |
| 416 | DEFSYM (Qclose, "close"); |
| 417 | |
| 418 | DEFSYM (Qdont_follow, "dont-follow"); |
| 419 | DEFSYM (Qexcl_unlink, "excl-unlink"); |
| 420 | DEFSYM (Qmask_add, "mask-add"); |
| 421 | DEFSYM (Qoneshot, "oneshot"); |
| 422 | DEFSYM (Qonlydir, "onlydir"); |
| 423 | |
| 424 | DEFSYM (Qignored, "ignored"); |
| 425 | DEFSYM (Qisdir, "isdir"); |
| 426 | DEFSYM (Qq_overflow, "q-overflow"); |
| 427 | DEFSYM (Qunmount, "unmount"); |
| 428 | |
| 429 | defsubr (&Sinotify_add_watch); |
| 430 | defsubr (&Sinotify_rm_watch); |
| 431 | |
| 432 | staticpro (&watch_list); |
| 433 | |
| 434 | Fprovide (intern_c_string ("inotify"), Qnil); |
| 435 | } |
| 436 | |
| 437 | #endif /* HAVE_INOTIFY */ |