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