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