b446bfee77ec5002897951e94094764e86d0a797
[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     case 4: // count watches {{{
344       if (4 > rlen) *rbuf = driver_alloc(4);
345
346       (*rbuf)[0] = (context->nwatches >> (8 * 3)) & 0xff;
347       (*rbuf)[1] = (context->nwatches >> (8 * 2)) & 0xff;
348       (*rbuf)[2] = (context->nwatches >> (8 * 1)) & 0xff;
349       (*rbuf)[3] = (context->nwatches >> (8 * 0)) & 0xff;
350       return 4;
351     // }}}
352
353     default: // unknown request
354       return -1;
355   }
356
357   return -1; // never reached
358 }
359
360 static
361 size_t store_errno(int error, char *buf, ErlDrvSizeT len)
362 {
363   char *error_str = erl_errno_id(errno);
364   size_t i;
365   // let's hope that `rlen' is long enough; it's just a dozen bytes top, so it
366   // should be
367   for (i = 0; error_str[i] != 0 && i < len; ++i)
368     buf[i] = error_str[i];
369   return i;
370 }
371
372 // }}}
373 //----------------------------------------------------------
374 // Erlang input on select descriptor {{{
375
376 static
377 void cdrv_ready_input(ErlDrvData drv_data, ErlDrvEvent event)
378 {
379   struct inotify_context *context = (struct inotify_context *)drv_data;
380   // `event' is the input descriptor
381
382   char buffer[INOTIFY_MAX_EVENT_SIZE * 32];
383
384   int result = read((long)event, buffer, sizeof(buffer));
385   if (result < 0) {
386     if (errno != EAGAIN && errno != EWOULDBLOCK)
387       driver_failure_posix(context->erl_port, errno);
388     return;
389   }
390   if (result == 0) {
391     driver_failure_eof(context->erl_port);
392     return;
393   }
394
395   char *next_event = buffer;
396   while (next_event < buffer + result) {
397     struct inotify_event *ievent = (struct inotify_event *)next_event;
398     next_event += sizeof(struct inotify_event) + ievent->len;
399
400     // short circuit for overflow error
401     if ((ievent->mask & IN_Q_OVERFLOW) != 0) {
402       send_inotify_error(context, atom_queue_overflow);
403       driver_failure_eof(context->erl_port);
404       return;
405     }
406
407     // TODO: add recursively if ((ievent->mask & IN_ISDIR) != 0)
408
409     // XXX: `watch_removed' and `unmount' flags are always sent as a separate
410     // messages, though inotify docs don't guarantee that; if `unmount' and/or
411     // `watch_removed' come with other flags, other flags are sent first, and
412     // `watch_removed' is sent last
413
414     if ((ievent->mask & ~(IN_UNMOUNT | IN_IGNORED)) != 0)
415       send_inotify_event(context, ievent);
416
417     if ((ievent->mask & IN_UNMOUNT) != 0)
418       send_inotify_single_flag_event(context, ievent, atom_unmount);
419
420     if ((ievent->mask & IN_IGNORED) != 0) {
421       send_inotify_single_flag_event(context, ievent, atom_watch_removed);
422       watch_remove(context, ievent->wd);
423     }
424   }
425 }
426
427 // }}}
428 //----------------------------------------------------------
429
430 //----------------------------------------------------------------------------
431 // scanning directory for already present files/subdirs {{{
432
433 static int scan_dir_send_entry(struct inotify_context *context,
434                                char *path, size_t path_len,
435                                char *basename, size_t basename_len,
436                                int is_dir);
437 static int scan_dir_send_error(struct inotify_context *context,
438                                char *path, size_t path_len,
439                                ErlDrvTermData error_atom);
440 static int is_directory(char *path, struct dirent *dir_entry);
441
442 static
443 void scan_directory(struct inotify_context *context, char *dir_path,
444                     size_t dir_path_len)
445 {
446   if (dir_path_len == 0)
447     dir_path_len = strlen(dir_path);
448
449   if (dir_path_len >= PATH_MAX - 1)
450     return;
451
452   char file_path[PATH_MAX + NAME_MAX + 1]; // more than enough for a file path
453   memcpy(file_path, dir_path, dir_path_len);
454   file_path[dir_path_len] = 0;
455
456   // dir_path is not guaranteed to be NUL terminated; use file_path instead
457   DIR *dir = opendir(file_path);
458   if (dir == NULL) {
459     scan_dir_send_error(context, file_path, dir_path_len,
460                         driver_mk_atom(erl_errno_id(errno)));
461     return;
462   }
463   file_path[dir_path_len] = '/';
464
465   struct dirent entry;
466   struct dirent *result = NULL;
467   while (readdir_r(dir, &entry, &result) == 0 && result != NULL) {
468     if ((entry.d_name[0] == '.' && entry.d_name[1] == 0) ||
469         (entry.d_name[0] == '.' && entry.d_name[1] == '.' &&
470          entry.d_name[2] == 0))
471       continue;
472
473     size_t basename_len = strlen(entry.d_name);
474     // XXX: OS guarantees that the name is NUL-terminated and short enough
475     memcpy(file_path + dir_path_len + 1, entry.d_name, basename_len);
476
477     scan_dir_send_entry(context, file_path, dir_path_len + 1 + basename_len,
478                         entry.d_name, basename_len,
479                         is_directory(file_path, &entry));
480   }
481   // the only possible error here is EBADF, according to `man readdir'
482   closedir(dir);
483 }
484
485 static
486 int is_directory(char *path, struct dirent *dir_entry)
487 {
488   struct stat buffer;
489   return (dir_entry->d_type == DT_DIR) ||
490          (dir_entry->d_type == DT_UNKNOWN &&
491           lstat(path, &buffer) == 0 && S_ISDIR(buffer.st_mode));
492 }
493
494 static
495 int scan_dir_send_entry(struct inotify_context *context,
496                         char *path, size_t path_len,
497                         char *basename, size_t basename_len,
498                         int is_dir)
499 {
500   if (is_dir) {
501     ErlDrvTermData message[] = {
502       ERL_DRV_ATOM, atom_inotify,
503       ERL_DRV_PORT, driver_mk_port(context->erl_port),
504       ERL_DRV_STRING, (ErlDrvTermData)path, path_len,
505       ERL_DRV_INT, 0, // cookie
506         ERL_DRV_ATOM, atom_is_dir,
507         ERL_DRV_ATOM, atom_present,
508           ERL_DRV_ATOM, atom_name,
509           ERL_DRV_STRING, (ErlDrvTermData)basename, basename_len,
510         ERL_DRV_TUPLE, 2, // {name,Basename}
511         ERL_DRV_NIL,
512       ERL_DRV_LIST, 4,
513
514       ERL_DRV_TUPLE, 5
515     };
516     return driver_output_term(context->erl_port, message,
517                               sizeof(message) / sizeof(message[0]));
518   } else {
519     ErlDrvTermData message[] = {
520       ERL_DRV_ATOM, atom_inotify,
521       ERL_DRV_PORT, driver_mk_port(context->erl_port),
522       ERL_DRV_STRING, (ErlDrvTermData)path, path_len,
523       ERL_DRV_INT, 0, // cookie
524         ERL_DRV_ATOM, atom_present,
525           ERL_DRV_ATOM, atom_name,
526           ERL_DRV_STRING, (ErlDrvTermData)basename, basename_len,
527         ERL_DRV_TUPLE, 2, // {name,Basename}
528         ERL_DRV_NIL,
529       ERL_DRV_LIST, 3,
530
531       ERL_DRV_TUPLE, 5
532     };
533     return driver_output_term(context->erl_port, message,
534                               sizeof(message) / sizeof(message[0]));
535   }
536 }
537
538 static
539 int scan_dir_send_error(struct inotify_context *context,
540                          char *path, size_t path_len,
541                          ErlDrvTermData error_atom)
542 {
543   ErlDrvTermData message[] = {
544     ERL_DRV_ATOM, atom_inotify_error,
545     ERL_DRV_PORT, driver_mk_port(context->erl_port),
546       ERL_DRV_STRING, (ErlDrvTermData)path, path_len,
547       ERL_DRV_ATOM, error_atom,
548       ERL_DRV_TUPLE, 2,
549     ERL_DRV_TUPLE, 3
550   };
551
552   return driver_output_term(context->erl_port, message,
553                             sizeof(message) / sizeof(message[0]));
554 }
555
556 // }}}
557 //----------------------------------------------------------------------------
558 // sending listing messages {{{
559
560 static
561 int send_watch_entry(struct inotify_context *context, ErlDrvTermData receiver,
562                      struct watch *watch)
563 {
564   // XXX: watch the size of this array and maximum message size
565   ErlDrvTermData message[64];
566   size_t len = 0;
567
568   message[len++] = ERL_DRV_ATOM;
569   message[len++] = atom_inotify_listing;
570   message[len++] = ERL_DRV_PORT;
571   message[len++] = driver_mk_port(context->erl_port);
572
573   message[len++] = ERL_DRV_INT;
574   message[len++] = (ErlDrvTermData)watch->wd;
575
576   message[len++] = ERL_DRV_STRING;
577   message[len++] = (ErlDrvTermData)watch->path;
578   message[len++] = watch->path_len;
579
580   size_t nflags = 0;
581
582 #define ADD_FLAG(flag, atom) \
583   if ((watch->flags & flag) != 0) { \
584     ++nflags; \
585     message[len++] = ERL_DRV_ATOM; \
586     message[len++] = atom; \
587   }
588
589   ADD_FLAG(IN_ACCESS,        atom_access);
590   ADD_FLAG(IN_MODIFY,        atom_modify);
591   ADD_FLAG(IN_ATTRIB,        atom_attrib);
592   ADD_FLAG(IN_CREATE,        atom_create);
593   ADD_FLAG(IN_DELETE,        atom_delete);
594   ADD_FLAG(IN_OPEN,          atom_open);
595   ADD_FLAG(IN_CLOSE_WRITE,   atom_close_write);
596   ADD_FLAG(IN_CLOSE_NOWRITE, atom_close_nowrite);
597   ADD_FLAG(IN_MOVED_FROM,    atom_move_from);
598   ADD_FLAG(IN_MOVED_TO,      atom_move_to);
599   ADD_FLAG(IN_DELETE_SELF,   atom_delete_self);
600   ADD_FLAG(IN_MOVE_SELF,     atom_move_self);
601
602 #undef ADD_FLAG
603
604   message[len++] = ERL_DRV_NIL;
605   message[len++] = ERL_DRV_LIST;
606   message[len++] = nflags + 1 /* for ERL_DRV_NIL */;
607
608   message[len++] = ERL_DRV_TUPLE;
609   message[len++] = 5; // {inotify_listing, Port, WD, Path, Flags}
610
611   return driver_send_term(context->erl_port, receiver, message, len);
612 }
613
614 // }}}
615 //----------------------------------------------------------------------------
616 // sending events {{{
617
618 static
619 int send_inotify_single_flag_event(struct inotify_context *context,
620                                    struct inotify_event *event,
621                                    ErlDrvTermData flag_atom)
622 {
623   // XXX: watch the size of this array and maximum message size
624   ErlDrvTermData message[20];
625   size_t len = 0;
626
627   message[len++] = ERL_DRV_ATOM;
628   message[len++] = atom_inotify;
629   message[len++] = ERL_DRV_PORT;
630   message[len++] = driver_mk_port(context->erl_port);
631
632   size_t path_len = 0;
633   char *path = watch_find(context, event, &path_len);
634
635   if (path != NULL) {
636     message[len++] = ERL_DRV_STRING;
637     message[len++] = (ErlDrvTermData)path;
638     message[len++] = path_len;
639   } else { // (path == NULL); this should never happen
640     message[len++] = ERL_DRV_ATOM;
641     message[len++] = driver_mk_atom("undefined");
642   }
643
644   message[len++] = ERL_DRV_UINT;
645   message[len++] = event->cookie;
646
647   message[len++] = ERL_DRV_ATOM;
648   message[len++] = flag_atom;
649   message[len++] = ERL_DRV_NIL;
650   message[len++] = ERL_DRV_LIST;
651   message[len++] = 2 /* atom + NIL (`[]') */;
652
653   message[len++] = ERL_DRV_TUPLE;
654   message[len++] = 5; // {inotify, Port, Path, Cookie, [Flag]}
655
656   return driver_output_term(context->erl_port, message, len);
657 }
658
659 static
660 int send_inotify_event(struct inotify_context *context,
661                        struct inotify_event *event)
662 {
663   // XXX: watch the size of this array and maximum message size
664   ErlDrvTermData message[64];
665   size_t len = 0;
666
667   message[len++] = ERL_DRV_ATOM;
668   message[len++] = atom_inotify;
669   message[len++] = ERL_DRV_PORT;
670   message[len++] = driver_mk_port(context->erl_port);
671
672   size_t path_len = 0;
673   char *path = watch_find(context, event, &path_len);
674
675   if (path != NULL) {
676     message[len++] = ERL_DRV_STRING;
677     message[len++] = (ErlDrvTermData)path;
678     message[len++] = path_len;
679   } else { // (path == NULL); this should never happen
680     message[len++] = ERL_DRV_ATOM;
681     message[len++] = driver_mk_atom("undefined");
682   }
683
684   message[len++] = ERL_DRV_UINT;
685   message[len++] = event->cookie;
686
687   size_t nflags = 0;
688
689 #define ADD_FLAG(flag, atom) \
690   if ((event->mask & flag) != 0) { \
691     ++nflags; \
692     message[len++] = ERL_DRV_ATOM; \
693     message[len++] = atom; \
694   }
695
696   // XXX: this flag is promised to go first
697   ADD_FLAG(IN_ISDIR, atom_is_dir);
698
699   ADD_FLAG(IN_ACCESS,        atom_access);
700   ADD_FLAG(IN_MODIFY,        atom_modify);
701   ADD_FLAG(IN_ATTRIB,        atom_attrib);
702   ADD_FLAG(IN_CREATE,        atom_create);
703   ADD_FLAG(IN_DELETE,        atom_delete);
704   ADD_FLAG(IN_OPEN,          atom_open);
705   ADD_FLAG(IN_CLOSE_WRITE,   atom_close_write);
706   ADD_FLAG(IN_CLOSE_NOWRITE, atom_close_nowrite);
707   ADD_FLAG(IN_MOVED_FROM,    atom_move_from);
708   ADD_FLAG(IN_MOVED_TO,      atom_move_to);
709   ADD_FLAG(IN_DELETE_SELF,   atom_delete_self);
710   ADD_FLAG(IN_MOVE_SELF,     atom_move_self);
711
712 #undef ADD_FLAG
713
714   message[len++] = ERL_DRV_NIL;
715   message[len++] = ERL_DRV_LIST;
716   message[len++] = nflags + 1 /* for ERL_DRV_NIL */;
717
718   message[len++] = ERL_DRV_TUPLE;
719   message[len++] = 5; // {inotify, Port, Path, Cookie, Flags}
720
721   return driver_output_term(context->erl_port, message, len);
722 }
723
724 static
725 int send_inotify_error(struct inotify_context *context,
726                        ErlDrvTermData error_atom)
727 {
728   ErlDrvTermData message[] = {
729     ERL_DRV_ATOM, atom_inotify_error,
730     ERL_DRV_PORT, driver_mk_port(context->erl_port),
731     ERL_DRV_ATOM, error_atom,
732     ERL_DRV_TUPLE, 3
733   };
734
735   return driver_output_term(context->erl_port, message,
736                             sizeof(message) / sizeof(message[0]));
737 }
738
739 // }}}
740 //----------------------------------------------------------------------------
741 // watch management {{{
742
743 static
744 int watch_add(struct inotify_context *context, int wd, uint32_t flags,
745               char *path)
746 {
747   // TODO: keep order by `wd', so binary search works
748
749   int flags_reset = ((flags & IN_MASK_ADD) == 0);
750   flags &= ~(IN_MASK_ADD | IN_DONT_FOLLOW | IN_ONESHOT | IN_ONLYDIR);
751
752   if (context->watches != NULL) {
753     struct watch *end = context->watches + context->nwatches;
754     struct watch *result = context->watches;
755
756     while (result < end && result->wd != wd)
757       ++result;
758
759     if (result != end) {
760       if (flags_reset)
761         result->flags = flags;
762       else
763         result->flags |= flags;
764
765       return 0;
766     }
767   }
768
769   if (context->watches == NULL) {
770     context->max_watches = 64;
771     size_t memsize = sizeof(struct watch) * context->max_watches;
772     context->watches = driver_alloc(memsize);
773   } else if (context->max_watches == context->nwatches) {
774     context->max_watches *= 2;
775     size_t memsize = sizeof(struct watch) * context->max_watches;
776     context->watches = driver_realloc(context->watches, memsize);
777     if (context->watches == NULL)
778       return -1;
779   }
780
781   struct watch *watch = context->watches + context->nwatches++;
782
783   watch->wd = wd;
784   watch->flags = flags;
785   watch->path_len = strlen(path);
786   // this must hold the actual path, slash, filename, and trailing NUL byte
787   watch->path = driver_alloc(watch->path_len + 1 + NAME_MAX + 1);
788
789   memcpy(watch->path, path, watch->path_len);
790   watch->path[watch->path_len] = 0;
791
792   return 0;
793 }
794
795 static
796 void watch_remove(struct inotify_context *context, int wd)
797 {
798   struct watch *end = context->watches + context->nwatches;
799   struct watch *current = context->watches;
800
801   while (current < end && current->wd != wd)
802     ++current;
803
804   if (current < end) {
805     driver_free(current->path);
806     current->path = NULL;
807     --context->nwatches;
808
809     if (current < end - 1) {
810       memcpy(current, end - 1, sizeof(struct watch));
811       (end - 1)->path = NULL;
812     }
813   }
814 }
815
816 static
817 int watch_find_wd(struct inotify_context *context, char *path)
818 {
819   size_t path_len = strlen(path);
820
821   struct watch *end = context->watches + context->nwatches;
822   struct watch *current;
823
824   for (current = context->watches; current < end; ++current) {
825     if (current->path_len == path_len &&
826         strncmp(current->path, path, path_len) == 0)
827       return current->wd;
828   }
829
830   return -1;
831 }
832
833 static
834 char* watch_find(struct inotify_context *context, struct inotify_event *event,
835                  size_t *path_len)
836 {
837   struct watch *end = context->watches + context->nwatches;
838   struct watch *result = context->watches;
839
840   while (result < end && result->wd != event->wd)
841     ++result;
842
843   if (result == end)
844     return NULL;
845
846   size_t name_len = result->path_len;
847
848   if (event->len > 0) {
849     result->path[name_len++] = '/';
850     size_t event_name_len = event->len;
851     while (event->name[event_name_len - 1] == 0)
852       --event_name_len;
853     memcpy(result->path + name_len, event->name, event_name_len);
854     name_len += event_name_len;
855   }
856
857   result->path[name_len] = 0;
858
859   if (path_len != NULL)
860     *path_len = name_len;
861
862   return result->path;
863 }
864
865 // }}}
866 //----------------------------------------------------------------------------
867 // copy path to NUL-terminated buffer, maybe with resolving symlinks {{{
868
869 static
870 ssize_t copy_path(char *path, size_t path_len, char *output, int real)
871 {
872   if (path_len >= PATH_MAX) {
873     errno = ENAMETOOLONG;
874     return -1;
875   }
876
877   if (!real) {
878     memcpy(output, path, path_len);
879     output[path_len] = 0;
880     return path_len;
881   }
882
883   char tmppath[PATH_MAX];
884   memcpy(tmppath, path, path_len);
885   tmppath[path_len] = 0;
886
887   if (realpath(tmppath, output) == NULL)
888     return -1;
889
890   return 0;
891 }
892
893 // }}}
894 //----------------------------------------------------------------------------
895 // flags translation {{{
896
897 static
898 uint32_t flags_to_inotify(uint32_t flags)
899 {
900   uint32_t result = 0;
901
902   if ((flags & FLAG_ACCESS)        != 0) result |= IN_ACCESS;
903   if ((flags & FLAG_MODIFY)        != 0) result |= IN_MODIFY;
904   if ((flags & FLAG_ATTRIB)        != 0) result |= IN_ATTRIB;
905   if ((flags & FLAG_CREATE)        != 0) result |= IN_CREATE;
906   if ((flags & FLAG_DELETE)        != 0) result |= IN_DELETE;
907   if ((flags & FLAG_OPEN)          != 0) result |= IN_OPEN;
908   if ((flags & FLAG_CLOSE_WRITE)   != 0) result |= IN_CLOSE_WRITE;
909   if ((flags & FLAG_CLOSE_NOWRITE) != 0) result |= IN_CLOSE_NOWRITE;
910   if ((flags & FLAG_MOVED_FROM)    != 0) result |= IN_MOVED_FROM;
911   if ((flags & FLAG_MOVED_TO)      != 0) result |= IN_MOVED_TO;
912   if ((flags & FLAG_DELETE_SELF)   != 0) result |= IN_DELETE_SELF;
913   if ((flags & FLAG_MOVE_SELF)     != 0) result |= IN_MOVE_SELF;
914   if ((flags & FLAG_DONT_FOLLOW)   != 0) result |= IN_DONT_FOLLOW;
915   if ((flags & FLAG_EXCL_UNLINK)   != 0) result |= IN_EXCL_UNLINK;
916   if ((flags & FLAG_ONESHOT)       != 0) result |= IN_ONESHOT;
917   if ((flags & FLAG_ONLYDIR)       != 0) result |= IN_ONLYDIR;
918   if ((flags & FLAG_MASK_ADD)      != 0) result |= IN_MASK_ADD;
919
920   return result;
921 }
922
923 // }}}
924 //----------------------------------------------------------------------------
925 // vim:ft=c:foldmethod=marker:nowrap