Added listing one subdirectory of a watched directory.
[erlang-gen_inotify.git] / c_src / gen_inotify_drv.c
1 //----------------------------------------------------------------------------
2 // preamble
3
4 //----------------------------------------------------------
5 // unix OS {{{
6
7 #include <stdint.h>
8 #include <sys/inotify.h>
9 #include <unistd.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <limits.h>
17
18 // }}}
19 //----------------------------------------------------------
20 // Erlang port driver {{{
21
22 #include <erl_driver.h>
23 #include <ei.h> // Erlang term manipulation
24
25 // }}}
26 //----------------------------------------------------------
27 // definitions {{{
28
29 #define PORT_DRIVER_NAME      "gen_inotify_drv"
30 #define PORT_DRIVER_NAME_SYM   gen_inotify_drv
31
32 #define PORT_DRIVER_NAME_LEN (sizeof(PORT_DRIVER_NAME) - 1)
33
34 #define FLAG_ACCESS            0x0001
35 #define FLAG_MODIFY            0x0002
36 #define FLAG_ATTRIB            0x0004
37 #define FLAG_CREATE            0x0008
38 #define FLAG_DELETE            0x0010
39 #define FLAG_OPEN              0x0020
40 #define FLAG_CLOSE_WRITE       0x0040
41 #define FLAG_CLOSE_NOWRITE     0x0080
42 #define FLAG_MOVED_FROM        0x0100
43 #define FLAG_MOVED_TO          0x0200
44 #define FLAG_DELETE_SELF       0x0400
45 #define FLAG_MOVE_SELF         0x0800
46
47 #define FLAG_DONT_FOLLOW       0x1000
48 #define FLAG_EXCL_UNLINK       0x2000
49 #define FLAG_ONESHOT           0x4000
50 #define FLAG_ONLYDIR           0x8000
51
52 #define FLAG_MASK_ADD        0x010000
53 #define FLAG_SCAN            0x100000
54
55 #define INOTIFY_MAX_EVENT_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1)
56
57 // }}}
58 //----------------------------------------------------------
59
60 //----------------------------------------------------------------------------
61 // Erlang port driver API
62
63 struct watch {
64   int wd;         // watch descriptor
65   uint32_t flags;
66   char *path; // this will actually hold (path_len + 1 + NAME_MAX + 1) bytes
67   size_t path_len;
68 };
69
70 struct inotify_context {
71   ErlDrvPort erl_port;
72   int fd;
73   uint8_t real_path;
74   struct watch *watches;
75   size_t nwatches;
76   size_t max_watches;
77 };
78
79 static ssize_t copy_path(char *path, size_t path_len, char *output, int real);
80 static uint32_t flags_to_inotify(uint32_t flags);
81 static int send_inotify_event(struct inotify_context *context, struct inotify_event *event);
82 static int send_inotify_single_flag_event(struct inotify_context *context, struct inotify_event *event, ErlDrvTermData flag_atom);
83 static int send_inotify_error(struct inotify_context *context, ErlDrvTermData error_atom);
84 static int send_watch_entry(struct inotify_context *context, ErlDrvTermData receiver, struct watch *watch);
85 static void scan_directory(struct inotify_context *context, char *dir_path, size_t dir_path_len);
86
87 static int   watch_add(struct inotify_context *context, int wd, uint32_t flags, char *path);
88 static int   watch_find_wd(struct inotify_context *context, char *path);
89 static char* watch_find(struct inotify_context *context, struct inotify_event *event, size_t *path_len);
90 static void  watch_remove(struct inotify_context *context, int wd);
91
92 // tuple tags
93 static ErlDrvTermData atom_inotify;
94 static ErlDrvTermData atom_inotify_listing;
95 static ErlDrvTermData atom_inotify_error;
96 // errors
97 static ErlDrvTermData atom_queue_overflow;
98 static ErlDrvTermData atom_eol;
99 // event flags
100 static ErlDrvTermData atom_watch_removed;
101 static ErlDrvTermData atom_is_dir;
102 static ErlDrvTermData atom_unmount;
103 static ErlDrvTermData atom_access;
104 static ErlDrvTermData atom_modify;
105 static ErlDrvTermData atom_attrib;
106 static ErlDrvTermData atom_create;
107 static ErlDrvTermData atom_delete;
108 static ErlDrvTermData atom_open;
109 static ErlDrvTermData atom_close_write;
110 static ErlDrvTermData atom_close_nowrite;
111 static ErlDrvTermData atom_move_from;
112 static ErlDrvTermData atom_move_to;
113 static ErlDrvTermData atom_delete_self;
114 static ErlDrvTermData atom_move_self;
115 static ErlDrvTermData atom_present;
116 static ErlDrvTermData atom_name;
117
118 //----------------------------------------------------------
119 // entry point definition {{{
120
121 static int          cdrv_init(void);
122 static ErlDrvData   cdrv_start(ErlDrvPort port, char *cmd);
123 static void         cdrv_stop(ErlDrvData drv_data);
124 static ErlDrvSSizeT cdrv_control(ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen);
125 static void         cdrv_ready_input(ErlDrvData drv_data, ErlDrvEvent event);
126 static void         cdrv_stop_select(ErlDrvEvent event, void *reserved);
127
128 ErlDrvEntry driver_entry = {
129   cdrv_init,                    // int        init(void)
130   cdrv_start,                   // ErlDrvData start(ErlDrvPort port, char *cmd)
131   cdrv_stop,                    // void       stop(ErlDrvData drv_data)
132   NULL,                         // void       output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) // port_command/2 handler
133   cdrv_ready_input,             // void       ready_input(ErlDrvData, ErlDrvEvent)  // "ready for reading" event
134   NULL,                         // void       ready_output(ErlDrvData, ErlDrvEvent) // "ready for writing" event
135   PORT_DRIVER_NAME,             // <driver name>
136   NULL,                         // void       finish(void)
137   NULL,                         // <reserved>
138   cdrv_control,                 // int        control(...) // port_control/3 handler
139   NULL,                         // void       timeout(ErlDrvData drv_data)
140   NULL,                         // void       outputv(ErlDrvData drv_data, ErlIOVec *ev) // port_command/2 handler, faster
141   NULL,                         // void       ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data)
142   NULL,                         // void       flush(ErlDrvData drv_data)
143   NULL,                         // int        call(...) // erlang:port_call/3 handler
144   NULL,                         // void       event(ErlDrvData drv_data, ErlDrvEvent event, ErlDrvEventData event_data)
145   ERL_DRV_EXTENDED_MARKER,
146   ERL_DRV_EXTENDED_MAJOR_VERSION,
147   ERL_DRV_EXTENDED_MINOR_VERSION,
148   ERL_DRV_FLAG_USE_PORT_LOCKING,  // driver flags
149   NULL,                         // <reserved>
150   NULL,                         // void  process_exit(...) // called when monitored process dies
151   cdrv_stop_select              // void  stop_select(ErlDrvEvent event, void *reserved) // called to close an event object
152 };
153
154 // the same as <driver name> in structure above, but as identifer instead of
155 // string
156 DRIVER_INIT(PORT_DRIVER_NAME_SYM)
157 {
158   return &driver_entry;
159 }
160
161 // }}}
162 //----------------------------------------------------------
163 // Erlang port driver initialization {{{
164
165 static
166 int cdrv_init(void)
167 {
168   atom_inotify_listing = driver_mk_atom("inotify_listing");
169   atom_eol = driver_mk_atom("eol");
170   atom_inotify_error  = driver_mk_atom("inotify_error");
171   atom_queue_overflow = driver_mk_atom("queue_overflow");
172   atom_inotify        = driver_mk_atom("inotify");
173   atom_watch_removed  = driver_mk_atom("watch_removed");
174   atom_is_dir         = driver_mk_atom("is_dir");
175   atom_unmount        = driver_mk_atom("unmount");
176   atom_access         = driver_mk_atom("access");
177   atom_modify         = driver_mk_atom("modify");
178   atom_attrib         = driver_mk_atom("attrib");
179   atom_create         = driver_mk_atom("create");
180   atom_delete         = driver_mk_atom("delete");
181   atom_open           = driver_mk_atom("open");
182   atom_close_write    = driver_mk_atom("close_write");
183   atom_close_nowrite  = driver_mk_atom("close_nowrite");
184   atom_move_from      = driver_mk_atom("move_from");
185   atom_move_to        = driver_mk_atom("move_to");
186   atom_delete_self    = driver_mk_atom("delete_self");
187   atom_move_self      = driver_mk_atom("move_self");
188   atom_present        = driver_mk_atom("present");
189   atom_name           = driver_mk_atom("name");
190
191   return 0;
192 }
193
194 // }}}
195 //----------------------------------------------------------
196 // Erlang port start {{{
197
198 static
199 ErlDrvData cdrv_start(ErlDrvPort port, char *cmd)
200 {
201   struct inotify_context *context =
202     driver_alloc(sizeof(struct inotify_context));
203
204   context->erl_port = port;
205   context->real_path = 1;
206   context->watches = NULL;
207   context->nwatches = 0;
208   context->max_watches = 0;
209   context->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
210   if (context->fd < 0) {
211     driver_free(context);
212     return ERL_DRV_ERROR_ERRNO;
213   }
214
215   ErlDrvEvent event = (ErlDrvEvent)((long int)context->fd);
216   driver_select(context->erl_port, event, ERL_DRV_USE | ERL_DRV_READ, 1);
217
218   // port_control() should return binaries
219   set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
220
221   return (ErlDrvData)context;
222 }
223
224 // }}}
225 //----------------------------------------------------------
226 // Erlang port stop {{{
227
228 static
229 void cdrv_stop(ErlDrvData drv_data)
230 {
231   struct inotify_context *context = (struct inotify_context *)drv_data;
232
233   ErlDrvEvent event = (ErlDrvEvent)((long int)context->fd);
234   driver_select(context->erl_port, event, ERL_DRV_USE | ERL_DRV_READ, 0);
235
236   if (context->watches != NULL) {
237     while (context->nwatches > 0)
238       driver_free(context->watches[--context->nwatches].path);
239     driver_free(context->watches);
240   }
241
242   driver_free(context);
243 }
244
245 // }}}
246 //----------------------------------------------------------
247 // Erlang event close (after port stop) {{{
248
249 static
250 void cdrv_stop_select(ErlDrvEvent event, void *reserved)
251 {
252   long int fd = (long int)event;
253   close(fd);
254 }
255
256 // }}}
257 //----------------------------------------------------------
258 // Erlang port control {{{
259
260 static size_t store_errno(int error, char *buf, ErlDrvSizeT len);
261
262 static
263 ErlDrvSSizeT cdrv_control(ErlDrvData drv_data, unsigned int command,
264                           char *buf, ErlDrvSizeT len,
265                           char **rbuf, ErlDrvSizeT rlen)
266 {
267   struct inotify_context *context = (struct inotify_context *)drv_data;
268
269   uint32_t request_flags;
270   uint32_t inotify_flags;
271   char path[PATH_MAX];
272   int wd;
273
274   struct watch *watch;
275   ErlDrvTermData caller;
276
277   switch (command) {
278     case 0: // initialize port {{{
279       if (len != 2)
280         return -1;
281
282       //context->recursive = buf[0]; // TODO: implement me
283       context->real_path = buf[1];
284
285       return 0;
286     // }}}
287
288     case 1: // add/update watch {{{
289       if (len < 5) // uint32_t + at least one character for filename
290         return -1;
291
292       request_flags = (uint8_t)buf[0] << (8 * 3) | (uint8_t)buf[1] << (8 * 2) |
293                       (uint8_t)buf[2] << (8 * 1) | (uint8_t)buf[3] << (8 * 0);
294       inotify_flags = flags_to_inotify(request_flags);
295       if (copy_path(buf + 4, len - 4, path, context->real_path) < 0)
296         return store_errno(errno, *rbuf, rlen);
297
298       if ((wd = inotify_add_watch(context->fd, path, inotify_flags)) >= 0) {
299         if (watch_add(context, wd, inotify_flags, path) != 0) {
300           driver_failure_posix(context->erl_port, ENOMEM);
301           return 0;
302         }
303
304         if ((request_flags & FLAG_SCAN) != 0)
305           scan_directory(context, path, 0);
306
307         return 0;
308       } else { // error
309         return store_errno(errno, *rbuf, rlen);
310       }
311     // }}}
312
313     case 2: // remove watch {{{
314       if (len < 1) // at least one character for filename
315         return -1;
316
317       if (copy_path(buf, len, path, context->real_path) < 0)
318         return store_errno(errno, *rbuf, rlen);
319
320       wd = watch_find_wd(context, path);
321       if (wd >= 0 && inotify_rm_watch(context->fd, wd) < 0)
322         return store_errno(errno, *rbuf, rlen);
323
324       return 0;
325     // }}}
326
327     case 3: // list watches {{{
328       if (4 > rlen) *rbuf = driver_alloc(4);
329       caller = driver_caller(context->erl_port);
330
331       for (watch = context->watches;
332            watch < context->watches + context->nwatches;
333            ++watch) {
334         send_watch_entry(context, caller, watch);
335       }
336       (*rbuf)[0] = (context->nwatches >> (8 * 3)) & 0xff;
337       (*rbuf)[1] = (context->nwatches >> (8 * 2)) & 0xff;
338       (*rbuf)[2] = (context->nwatches >> (8 * 1)) & 0xff;
339       (*rbuf)[3] = (context->nwatches >> (8 * 0)) & 0xff;
340       return 4;
341     // }}}
342
343     default: // unknown request
344       return -1;
345   }
346
347   return -1; // never reached
348 }
349
350 static
351 size_t store_errno(int error, char *buf, ErlDrvSizeT len)
352 {
353   char *error_str = erl_errno_id(errno);
354   size_t i;
355   // let's hope that `rlen' is long enough; it's just a dozen bytes top, so it
356   // should be
357   for (i = 0; error_str[i] != 0 && i < len; ++i)
358     buf[i] = error_str[i];
359   return i;
360 }
361
362 // }}}
363 //----------------------------------------------------------
364 // Erlang input on select descriptor {{{
365
366 static
367 void cdrv_ready_input(ErlDrvData drv_data, ErlDrvEvent event)
368 {
369   struct inotify_context *context = (struct inotify_context *)drv_data;
370   // `event' is the input descriptor
371
372   char buffer[INOTIFY_MAX_EVENT_SIZE * 32];
373
374   int result = read((long)event, buffer, sizeof(buffer));
375   if (result < 0) {
376     if (errno != EAGAIN && errno != EWOULDBLOCK)
377       driver_failure_posix(context->erl_port, errno);
378     return;
379   }
380   if (result == 0) {
381     driver_failure_eof(context->erl_port);
382     return;
383   }
384
385   char *next_event = buffer;
386   while (next_event < buffer + result) {
387     struct inotify_event *ievent = (struct inotify_event *)next_event;
388     next_event += sizeof(struct inotify_event) + ievent->len;
389
390     // short circuit for overflow error
391     if ((ievent->mask & IN_Q_OVERFLOW) != 0) {
392       send_inotify_error(context, atom_queue_overflow);
393       driver_failure_eof(context->erl_port);
394       return;
395     }
396
397     // TODO: add recursively if ((ievent->mask & IN_ISDIR) != 0)
398
399     // XXX: `watch_removed' and `unmount' flags are always sent as a separate
400     // messages, though inotify docs don't guarantee that; if `unmount' and/or
401     // `watch_removed' come with other flags, other flags are sent first, and
402     // `watch_removed' is sent last
403
404     if ((ievent->mask & ~(IN_UNMOUNT | IN_IGNORED)) != 0)
405       send_inotify_event(context, ievent);
406
407     if ((ievent->mask & IN_UNMOUNT) != 0)
408       send_inotify_single_flag_event(context, ievent, atom_unmount);
409
410     if ((ievent->mask & IN_IGNORED) != 0) {
411       send_inotify_single_flag_event(context, ievent, atom_watch_removed);
412       watch_remove(context, ievent->wd);
413     }
414   }
415 }
416
417 // }}}
418 //----------------------------------------------------------
419
420 //----------------------------------------------------------------------------
421 // scanning directory for already present files/subdirs {{{
422
423 static int scan_dir_send_entry(struct inotify_context *context,
424                                char *path, size_t path_len,
425                                char *basename, size_t basename_len,
426                                int is_dir);
427 static int scan_dir_send_error(struct inotify_context *context,
428                                char *path, size_t path_len,
429                                ErlDrvTermData error_atom);
430 static int is_directory(char *path, struct dirent *dir_entry);
431
432 static
433 void scan_directory(struct inotify_context *context, char *dir_path,
434                     size_t dir_path_len)
435 {
436   if (dir_path_len == 0)
437     dir_path_len = strlen(dir_path);
438
439   if (dir_path_len >= PATH_MAX - 1)
440     return;
441
442   char file_path[PATH_MAX + NAME_MAX + 1]; // more than enough for a file path
443   memcpy(file_path, dir_path, dir_path_len);
444   file_path[dir_path_len] = 0;
445
446   // dir_path is not guaranteed to be NUL terminated; use file_path instead
447   DIR *dir = opendir(file_path);
448   if (dir == NULL) {
449     scan_dir_send_error(context, file_path, dir_path_len,
450                         driver_mk_atom(erl_errno_id(errno)));
451     return;
452   }
453   file_path[dir_path_len] = '/';
454
455   struct dirent entry;
456   struct dirent *result = NULL;
457   while (readdir_r(dir, &entry, &result) == 0 && result != NULL) {
458     if ((entry.d_name[0] == '.' && entry.d_name[1] == 0) ||
459         (entry.d_name[0] == '.' && entry.d_name[1] == '.' &&
460          entry.d_name[2] == 0))
461       continue;
462
463     size_t basename_len = strlen(entry.d_name);
464     // XXX: OS guarantees that the name is NUL-terminated and short enough
465     memcpy(file_path + dir_path_len + 1, entry.d_name, basename_len);
466
467     scan_dir_send_entry(context, file_path, dir_path_len + 1 + basename_len,
468                         entry.d_name, basename_len,
469                         is_directory(file_path, &entry));
470   }
471   // the only possible error here is EBADF, according to `man readdir'
472   closedir(dir);
473 }
474
475 static
476 int is_directory(char *path, struct dirent *dir_entry)
477 {
478   struct stat buffer;
479   return (dir_entry->d_type == DT_DIR) ||
480          (dir_entry->d_type == DT_UNKNOWN &&
481           lstat(path, &buffer) == 0 && S_ISDIR(buffer.st_mode));
482 }
483
484 static
485 int scan_dir_send_entry(struct inotify_context *context,
486                         char *path, size_t path_len,
487                         char *basename, size_t basename_len,
488                         int is_dir)
489 {
490   if (is_dir) {
491     ErlDrvTermData message[] = {
492       ERL_DRV_ATOM, atom_inotify,
493       ERL_DRV_PORT, driver_mk_port(context->erl_port),
494       ERL_DRV_STRING, (ErlDrvTermData)path, path_len,
495       ERL_DRV_INT, 0, // cookie
496         ERL_DRV_ATOM, atom_is_dir,
497         ERL_DRV_ATOM, atom_present,
498           ERL_DRV_ATOM, atom_name,
499           ERL_DRV_STRING, (ErlDrvTermData)basename, basename_len,
500         ERL_DRV_TUPLE, 2, // {name,Basename}
501         ERL_DRV_NIL,
502       ERL_DRV_LIST, 4,
503
504       ERL_DRV_TUPLE, 5
505     };
506     return driver_output_term(context->erl_port, message,
507                               sizeof(message) / sizeof(message[0]));
508   } else {
509     ErlDrvTermData message[] = {
510       ERL_DRV_ATOM, atom_inotify,
511       ERL_DRV_PORT, driver_mk_port(context->erl_port),
512       ERL_DRV_STRING, (ErlDrvTermData)path, path_len,
513       ERL_DRV_INT, 0, // cookie
514         ERL_DRV_ATOM, atom_present,
515           ERL_DRV_ATOM, atom_name,
516           ERL_DRV_STRING, (ErlDrvTermData)basename, basename_len,
517         ERL_DRV_TUPLE, 2, // {name,Basename}
518         ERL_DRV_NIL,
519       ERL_DRV_LIST, 3,
520
521       ERL_DRV_TUPLE, 5
522     };
523     return driver_output_term(context->erl_port, message,
524                               sizeof(message) / sizeof(message[0]));
525   }
526 }
527
528 static
529 int scan_dir_send_error(struct inotify_context *context,
530                          char *path, size_t path_len,
531                          ErlDrvTermData error_atom)
532 {
533   ErlDrvTermData message[] = {
534     ERL_DRV_ATOM, atom_inotify_error,
535     ERL_DRV_PORT, driver_mk_port(context->erl_port),
536       ERL_DRV_STRING, (ErlDrvTermData)path, path_len,
537       ERL_DRV_ATOM, error_atom,
538       ERL_DRV_TUPLE, 2,
539     ERL_DRV_TUPLE, 3
540   };
541
542   return driver_output_term(context->erl_port, message,
543                             sizeof(message) / sizeof(message[0]));
544 }
545
546 // }}}
547 //----------------------------------------------------------------------------
548 // sending listing messages {{{
549
550 static
551 int send_watch_entry(struct inotify_context *context, ErlDrvTermData receiver,
552                      struct watch *watch)
553 {
554   // XXX: watch the size of this array and maximum message size
555   ErlDrvTermData message[64];
556   size_t len = 0;
557
558   message[len++] = ERL_DRV_ATOM;
559   message[len++] = atom_inotify_listing;
560   message[len++] = ERL_DRV_PORT;
561   message[len++] = driver_mk_port(context->erl_port);
562
563   message[len++] = ERL_DRV_INT;
564   message[len++] = (ErlDrvTermData)watch->wd;
565
566   message[len++] = ERL_DRV_STRING;
567   message[len++] = (ErlDrvTermData)watch->path;
568   message[len++] = watch->path_len;
569
570   size_t nflags = 0;
571
572 #define ADD_FLAG(flag, atom) \
573   if ((watch->flags & flag) != 0) { \
574     ++nflags; \
575     message[len++] = ERL_DRV_ATOM; \
576     message[len++] = atom; \
577   }
578
579   ADD_FLAG(IN_ACCESS,        atom_access);
580   ADD_FLAG(IN_MODIFY,        atom_modify);
581   ADD_FLAG(IN_ATTRIB,        atom_attrib);
582   ADD_FLAG(IN_CREATE,        atom_create);
583   ADD_FLAG(IN_DELETE,        atom_delete);
584   ADD_FLAG(IN_OPEN,          atom_open);
585   ADD_FLAG(IN_CLOSE_WRITE,   atom_close_write);
586   ADD_FLAG(IN_CLOSE_NOWRITE, atom_close_nowrite);
587   ADD_FLAG(IN_MOVED_FROM,    atom_move_from);
588   ADD_FLAG(IN_MOVED_TO,      atom_move_to);
589   ADD_FLAG(IN_DELETE_SELF,   atom_delete_self);
590   ADD_FLAG(IN_MOVE_SELF,     atom_move_self);
591
592 #undef ADD_FLAG
593
594   message[len++] = ERL_DRV_NIL;
595   message[len++] = ERL_DRV_LIST;
596   message[len++] = nflags + 1 /* for ERL_DRV_NIL */;
597
598   message[len++] = ERL_DRV_TUPLE;
599   message[len++] = 5; // {inotify_listing, Port, WD, Path, Flags}
600
601   return driver_send_term(context->erl_port, receiver, message, len);
602 }
603
604 // }}}
605 //----------------------------------------------------------------------------
606 // sending events {{{
607
608 static
609 int send_inotify_single_flag_event(struct inotify_context *context,
610                                    struct inotify_event *event,
611                                    ErlDrvTermData flag_atom)
612 {
613   // XXX: watch the size of this array and maximum message size
614   ErlDrvTermData message[20];
615   size_t len = 0;
616
617   message[len++] = ERL_DRV_ATOM;
618   message[len++] = atom_inotify;
619   message[len++] = ERL_DRV_PORT;
620   message[len++] = driver_mk_port(context->erl_port);
621
622   size_t path_len = 0;
623   char *path = watch_find(context, event, &path_len);
624
625   if (path != NULL) {
626     message[len++] = ERL_DRV_STRING;
627     message[len++] = (ErlDrvTermData)path;
628     message[len++] = path_len;
629   } else { // (path == NULL); this should never happen
630     message[len++] = ERL_DRV_ATOM;
631     message[len++] = driver_mk_atom("undefined");
632   }
633
634   message[len++] = ERL_DRV_UINT;
635   message[len++] = event->cookie;
636
637   message[len++] = ERL_DRV_ATOM;
638   message[len++] = flag_atom;
639   message[len++] = ERL_DRV_NIL;
640   message[len++] = ERL_DRV_LIST;
641   message[len++] = 2 /* atom + NIL (`[]') */;
642
643   message[len++] = ERL_DRV_TUPLE;
644   message[len++] = 5; // {inotify, Port, Path, Cookie, [Flag]}
645
646   return driver_output_term(context->erl_port, message, len);
647 }
648
649 static
650 int send_inotify_event(struct inotify_context *context,
651                        struct inotify_event *event)
652 {
653   // XXX: watch the size of this array and maximum message size
654   ErlDrvTermData message[64];
655   size_t len = 0;
656
657   message[len++] = ERL_DRV_ATOM;
658   message[len++] = atom_inotify;
659   message[len++] = ERL_DRV_PORT;
660   message[len++] = driver_mk_port(context->erl_port);
661
662   size_t path_len = 0;
663   char *path = watch_find(context, event, &path_len);
664
665   if (path != NULL) {
666     message[len++] = ERL_DRV_STRING;
667     message[len++] = (ErlDrvTermData)path;
668     message[len++] = path_len;
669   } else { // (path == NULL); this should never happen
670     message[len++] = ERL_DRV_ATOM;
671     message[len++] = driver_mk_atom("undefined");
672   }
673
674   message[len++] = ERL_DRV_UINT;
675   message[len++] = event->cookie;
676
677   size_t nflags = 0;
678
679 #define ADD_FLAG(flag, atom) \
680   if ((event->mask & flag) != 0) { \
681     ++nflags; \
682     message[len++] = ERL_DRV_ATOM; \
683     message[len++] = atom; \
684   }
685
686   // XXX: this flag is promised to go first
687   ADD_FLAG(IN_ISDIR, atom_is_dir);
688
689   ADD_FLAG(IN_ACCESS,        atom_access);
690   ADD_FLAG(IN_MODIFY,        atom_modify);
691   ADD_FLAG(IN_ATTRIB,        atom_attrib);
692   ADD_FLAG(IN_CREATE,        atom_create);
693   ADD_FLAG(IN_DELETE,        atom_delete);
694   ADD_FLAG(IN_OPEN,          atom_open);
695   ADD_FLAG(IN_CLOSE_WRITE,   atom_close_write);
696   ADD_FLAG(IN_CLOSE_NOWRITE, atom_close_nowrite);
697   ADD_FLAG(IN_MOVED_FROM,    atom_move_from);
698   ADD_FLAG(IN_MOVED_TO,      atom_move_to);
699   ADD_FLAG(IN_DELETE_SELF,   atom_delete_self);
700   ADD_FLAG(IN_MOVE_SELF,     atom_move_self);
701
702 #undef ADD_FLAG
703
704   message[len++] = ERL_DRV_NIL;
705   message[len++] = ERL_DRV_LIST;
706   message[len++] = nflags + 1 /* for ERL_DRV_NIL */;
707
708   message[len++] = ERL_DRV_TUPLE;
709   message[len++] = 5; // {inotify, Port, Path, Cookie, Flags}
710
711   return driver_output_term(context->erl_port, message, len);
712 }
713
714 static
715 int send_inotify_error(struct inotify_context *context,
716                        ErlDrvTermData error_atom)
717 {
718   ErlDrvTermData message[] = {
719     ERL_DRV_ATOM, atom_inotify_error,
720     ERL_DRV_PORT, driver_mk_port(context->erl_port),
721     ERL_DRV_ATOM, error_atom,
722     ERL_DRV_TUPLE, 3
723   };
724
725   return driver_output_term(context->erl_port, message,
726                             sizeof(message) / sizeof(message[0]));
727 }
728
729 // }}}
730 //----------------------------------------------------------------------------
731 // watch management {{{
732
733 static
734 int watch_add(struct inotify_context *context, int wd, uint32_t flags,
735               char *path)
736 {
737   // TODO: keep order by `wd', so binary search works
738
739   int flags_reset = ((flags & IN_MASK_ADD) == 0);
740   flags &= ~(IN_MASK_ADD | IN_DONT_FOLLOW | IN_ONESHOT | IN_ONLYDIR);
741
742   if (context->watches != NULL) {
743     struct watch *end = context->watches + context->nwatches;
744     struct watch *result = context->watches;
745
746     while (result < end && result->wd != wd)
747       ++result;
748
749     if (result != end) {
750       if (flags_reset)
751         result->flags = flags;
752       else
753         result->flags |= flags;
754
755       return 0;
756     }
757   }
758
759   if (context->watches == NULL) {
760     context->max_watches = 64;
761     size_t memsize = sizeof(struct watch) * context->max_watches;
762     context->watches = driver_alloc(memsize);
763   } else if (context->max_watches == context->nwatches) {
764     context->max_watches *= 2;
765     size_t memsize = sizeof(struct watch) * context->max_watches;
766     context->watches = driver_realloc(context->watches, memsize);
767     if (context->watches == NULL)
768       return -1;
769   }
770
771   struct watch *watch = context->watches + context->nwatches++;
772
773   watch->wd = wd;
774   watch->flags = flags;
775   watch->path_len = strlen(path);
776   // this must hold the actual path, slash, filename, and trailing NUL byte
777   watch->path = driver_alloc(watch->path_len + 1 + NAME_MAX + 1);
778
779   memcpy(watch->path, path, watch->path_len);
780   watch->path[watch->path_len] = 0;
781
782   return 0;
783 }
784
785 static
786 void watch_remove(struct inotify_context *context, int wd)
787 {
788   struct watch *end = context->watches + context->nwatches;
789   struct watch *current = context->watches;
790
791   while (current < end && current->wd != wd)
792     ++current;
793
794   if (current < end) {
795     driver_free(current->path);
796     current->path = NULL;
797     --context->nwatches;
798
799     if (current < end - 1) {
800       memcpy(current, end - 1, sizeof(struct watch));
801       (end - 1)->path = NULL;
802     }
803   }
804 }
805
806 static
807 int watch_find_wd(struct inotify_context *context, char *path)
808 {
809   size_t path_len = strlen(path);
810
811   struct watch *end = context->watches + context->nwatches;
812   struct watch *current;
813
814   for (current = context->watches; current < end; ++current) {
815     if (current->path_len == path_len &&
816         strncmp(current->path, path, path_len) == 0)
817       return current->wd;
818   }
819
820   return -1;
821 }
822
823 static
824 char* watch_find(struct inotify_context *context, struct inotify_event *event,
825                  size_t *path_len)
826 {
827   struct watch *end = context->watches + context->nwatches;
828   struct watch *result = context->watches;
829
830   while (result < end && result->wd != event->wd)
831     ++result;
832
833   if (result == end)
834     return NULL;
835
836   size_t name_len = result->path_len;
837
838   if (event->len > 0) {
839     result->path[name_len++] = '/';
840     size_t event_name_len = event->len;
841     while (event->name[event_name_len - 1] == 0)
842       --event_name_len;
843     memcpy(result->path + name_len, event->name, event_name_len);
844     name_len += event_name_len;
845   }
846
847   result->path[name_len] = 0;
848
849   if (path_len != NULL)
850     *path_len = name_len;
851
852   return result->path;
853 }
854
855 // }}}
856 //----------------------------------------------------------------------------
857 // copy path to NUL-terminated buffer, maybe with resolving symlinks {{{
858
859 static
860 ssize_t copy_path(char *path, size_t path_len, char *output, int real)
861 {
862   if (path_len >= PATH_MAX) {
863     errno = ENAMETOOLONG;
864     return -1;
865   }
866
867   if (!real) {
868     memcpy(output, path, path_len);
869     output[path_len] = 0;
870     return path_len;
871   }
872
873   char tmppath[PATH_MAX];
874   memcpy(tmppath, path, path_len);
875   tmppath[path_len] = 0;
876
877   if (realpath(tmppath, output) == NULL)
878     return -1;
879
880   return 0;
881 }
882
883 // }}}
884 //----------------------------------------------------------------------------
885 // flags translation {{{
886
887 static
888 uint32_t flags_to_inotify(uint32_t flags)
889 {
890   uint32_t result = 0;
891
892   if ((flags & FLAG_ACCESS)        != 0) result |= IN_ACCESS;
893   if ((flags & FLAG_MODIFY)        != 0) result |= IN_MODIFY;
894   if ((flags & FLAG_ATTRIB)        != 0) result |= IN_ATTRIB;
895   if ((flags & FLAG_CREATE)        != 0) result |= IN_CREATE;
896   if ((flags & FLAG_DELETE)        != 0) result |= IN_DELETE;
897   if ((flags & FLAG_OPEN)          != 0) result |= IN_OPEN;
898   if ((flags & FLAG_CLOSE_WRITE)   != 0) result |= IN_CLOSE_WRITE;
899   if ((flags & FLAG_CLOSE_NOWRITE) != 0) result |= IN_CLOSE_NOWRITE;
900   if ((flags & FLAG_MOVED_FROM)    != 0) result |= IN_MOVED_FROM;
901   if ((flags & FLAG_MOVED_TO)      != 0) result |= IN_MOVED_TO;
902   if ((flags & FLAG_DELETE_SELF)   != 0) result |= IN_DELETE_SELF;
903   if ((flags & FLAG_MOVE_SELF)     != 0) result |= IN_MOVE_SELF;
904   if ((flags & FLAG_DONT_FOLLOW)   != 0) result |= IN_DONT_FOLLOW;
905   if ((flags & FLAG_EXCL_UNLINK)   != 0) result |= IN_EXCL_UNLINK;
906   if ((flags & FLAG_ONESHOT)       != 0) result |= IN_ONESHOT;
907   if ((flags & FLAG_ONLYDIR)       != 0) result |= IN_ONLYDIR;
908   if ((flags & FLAG_MASK_ADD)      != 0) result |= IN_MASK_ADD;
909
910   return result;
911 }
912
913 // }}}
914 //----------------------------------------------------------------------------
915 // vim:ft=c:foldmethod=marker:nowrap