cc866911f5345f1ded15f3fc22e1dfa0936adde4
[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 0: // initialize port {{{
269       if (len != 2)
270         return -1;
271
272       //context->recursive = buf[0]; // TODO: implement me
273       context->real_path = buf[1];
274
275       return 0;
276     // }}}
277
278     case 1: // add/update watch {{{
279       if (len < 5) // uint32_t + at least one character for filename
280         return -1;
281
282       flags = flags_to_inotify((uint8_t)buf[0] << (8 * 3) |
283                                (uint8_t)buf[1] << (8 * 2) |
284                                (uint8_t)buf[2] << (8 * 1) |
285                                (uint8_t)buf[3] << (8 * 0));
286       if (copy_path(buf + 4, len - 4, path, context->real_path) < 0)
287         return store_errno(errno, *rbuf, rlen);
288
289       if ((wd = inotify_add_watch(context->fd, path, flags)) >= 0) {
290         if (watch_add(context, wd, flags, path) != 0) {
291           driver_failure_posix(context->erl_port, ENOMEM);
292           return 0;
293         }
294         return 0;
295       } else { // error
296         return store_errno(errno, *rbuf, rlen);
297       }
298     // }}}
299
300     case 2: // remove watch {{{
301       if (len < 1) // at least one character for filename
302         return -1;
303
304       if (copy_path(buf, len, path, context->real_path) < 0)
305         return store_errno(errno, *rbuf, rlen);
306
307       wd = watch_find_wd(context, path);
308       if (wd >= 0 && inotify_rm_watch(context->fd, wd) < 0)
309         return store_errno(errno, *rbuf, rlen);
310
311       return 0;
312     // }}}
313
314     case 3: // list watches {{{
315       if (4 > rlen) *rbuf = driver_alloc(4);
316       caller = driver_caller(context->erl_port);
317
318       for (watch = context->watches;
319            watch < context->watches + context->nwatches;
320            ++watch) {
321         send_watch_entry(context, caller, watch);
322       }
323       (*rbuf)[0] = (context->nwatches >> (8 * 3)) & 0xff;
324       (*rbuf)[1] = (context->nwatches >> (8 * 2)) & 0xff;
325       (*rbuf)[2] = (context->nwatches >> (8 * 1)) & 0xff;
326       (*rbuf)[3] = (context->nwatches >> (8 * 0)) & 0xff;
327       return 4;
328     // }}}
329
330     default: // unknown request
331       return -1;
332   }
333
334   return -1; // never reached
335 }
336
337 static
338 size_t store_errno(int error, char *buf, ErlDrvSizeT len)
339 {
340   char *error_str = erl_errno_id(errno);
341   size_t i;
342   // let's hope that `rlen' is long enough; it's just a dozen bytes top, so it
343   // should be
344   for (i = 0; error_str[i] != 0 && i < len; ++i)
345     buf[i] = error_str[i];
346   return i;
347 }
348
349 // }}}
350 //----------------------------------------------------------
351 // Erlang input on select descriptor {{{
352
353 static
354 void cdrv_ready_input(ErlDrvData drv_data, ErlDrvEvent event)
355 {
356   struct inotify_context *context = (struct inotify_context *)drv_data;
357   // `event' is the input descriptor
358
359   char buffer[INOTIFY_MAX_EVENT_SIZE * 32];
360
361   int result = read((long)event, buffer, sizeof(buffer));
362   if (result < 0) {
363     if (errno != EAGAIN && errno != EWOULDBLOCK)
364       driver_failure_posix(context->erl_port, errno);
365     return;
366   }
367   if (result == 0) {
368     driver_failure_eof(context->erl_port);
369     return;
370   }
371
372   char *next_event = buffer;
373   while (next_event < buffer + result) {
374     struct inotify_event *ievent = (struct inotify_event *)next_event;
375     next_event += sizeof(struct inotify_event) + ievent->len;
376
377     // short circuit for overflow error
378     if ((ievent->mask & IN_Q_OVERFLOW) != 0) {
379       send_inotify_error(context, atom_queue_overflow);
380       driver_failure_eof(context->erl_port);
381       return;
382     }
383
384     // TODO: add recursively if ((ievent->mask & IN_ISDIR) != 0)
385
386     // XXX: `watch_removed' and `unmount' flags are always sent as a separate
387     // messages, though inotify docs don't guarantee that; if `unmount' and/or
388     // `watch_removed' come with other flags, other flags are sent first, and
389     // `watch_removed' is sent last
390
391     if ((ievent->mask & ~(IN_UNMOUNT | IN_IGNORED)) != 0)
392       send_inotify_event(context, ievent);
393
394     if ((ievent->mask & IN_UNMOUNT) != 0)
395       send_inotify_single_flag_event(context, ievent, atom_unmount);
396
397     if ((ievent->mask & IN_IGNORED) != 0) {
398       send_inotify_single_flag_event(context, ievent, atom_watch_removed);
399       watch_remove(context, ievent->wd);
400     }
401   }
402 }
403
404 // }}}
405 //----------------------------------------------------------
406
407 //----------------------------------------------------------------------------
408 // sending listing messages {{{
409
410 static
411 int send_watch_entry(struct inotify_context *context, ErlDrvTermData receiver,
412                      struct watch *watch)
413 {
414   // XXX: watch the size of this array and maximum message size
415   ErlDrvTermData message[64];
416   size_t len = 0;
417
418   message[len++] = ERL_DRV_ATOM;
419   message[len++] = atom_inotify_listing;
420   message[len++] = ERL_DRV_PORT;
421   message[len++] = driver_mk_port(context->erl_port);
422
423   message[len++] = ERL_DRV_INT;
424   message[len++] = (ErlDrvTermData)watch->wd;
425
426   message[len++] = ERL_DRV_STRING;
427   message[len++] = (ErlDrvTermData)watch->path;
428   message[len++] = watch->path_len;
429
430   size_t nflags = 0;
431
432 #define ADD_FLAG(flag, atom) \
433   if ((watch->flags & flag) != 0) { \
434     ++nflags; \
435     message[len++] = ERL_DRV_ATOM; \
436     message[len++] = atom; \
437   }
438
439   ADD_FLAG(IN_ACCESS,        atom_access);
440   ADD_FLAG(IN_MODIFY,        atom_modify);
441   ADD_FLAG(IN_ATTRIB,        atom_attrib);
442   ADD_FLAG(IN_CREATE,        atom_create);
443   ADD_FLAG(IN_DELETE,        atom_delete);
444   ADD_FLAG(IN_OPEN,          atom_open);
445   ADD_FLAG(IN_CLOSE_WRITE,   atom_close_write);
446   ADD_FLAG(IN_CLOSE_NOWRITE, atom_close_nowrite);
447   ADD_FLAG(IN_MOVED_FROM,    atom_move_from);
448   ADD_FLAG(IN_MOVED_TO,      atom_move_to);
449   ADD_FLAG(IN_DELETE_SELF,   atom_delete_self);
450   ADD_FLAG(IN_MOVE_SELF,     atom_move_self);
451
452 #undef ADD_FLAG
453
454   message[len++] = ERL_DRV_NIL;
455   message[len++] = ERL_DRV_LIST;
456   message[len++] = nflags + 1 /* for ERL_DRV_NIL */;
457
458   message[len++] = ERL_DRV_TUPLE;
459   message[len++] = 5; // {inotify_listing, Port, WD, Path, Flags}
460
461   return driver_send_term(context->erl_port, receiver, message, len);
462 }
463
464 // }}}
465 //----------------------------------------------------------------------------
466 // sending events {{{
467
468 static
469 int send_inotify_single_flag_event(struct inotify_context *context,
470                                    struct inotify_event *event,
471                                    ErlDrvTermData flag_atom)
472 {
473   // XXX: watch the size of this array and maximum message size
474   ErlDrvTermData message[20];
475   size_t len = 0;
476
477   message[len++] = ERL_DRV_ATOM;
478   message[len++] = atom_inotify;
479   message[len++] = ERL_DRV_PORT;
480   message[len++] = driver_mk_port(context->erl_port);
481
482   size_t path_len = 0;
483   char *path = watch_find(context, event, &path_len);
484
485   if (path != NULL) {
486     message[len++] = ERL_DRV_STRING;
487     message[len++] = (ErlDrvTermData)path;
488     message[len++] = path_len;
489   } else { // (path == NULL); this should never happen
490     message[len++] = ERL_DRV_ATOM;
491     message[len++] = driver_mk_atom("undefined");
492   }
493
494   message[len++] = ERL_DRV_UINT;
495   message[len++] = event->cookie;
496
497   message[len++] = ERL_DRV_ATOM;
498   message[len++] = flag_atom;
499   message[len++] = ERL_DRV_NIL;
500   message[len++] = ERL_DRV_LIST;
501   message[len++] = 2 /* atom + NIL (`[]') */;
502
503   message[len++] = ERL_DRV_TUPLE;
504   message[len++] = 5; // {inotify, Port, Path, Cookie, [Flag]}
505
506   return driver_output_term(context->erl_port, message, len);
507 }
508
509 static
510 int send_inotify_event(struct inotify_context *context,
511                        struct inotify_event *event)
512 {
513   // XXX: watch the size of this array and maximum message size
514   ErlDrvTermData message[64];
515   size_t len = 0;
516
517   message[len++] = ERL_DRV_ATOM;
518   message[len++] = atom_inotify;
519   message[len++] = ERL_DRV_PORT;
520   message[len++] = driver_mk_port(context->erl_port);
521
522   size_t path_len = 0;
523   char *path = watch_find(context, event, &path_len);
524
525   if (path != NULL) {
526     message[len++] = ERL_DRV_STRING;
527     message[len++] = (ErlDrvTermData)path;
528     message[len++] = path_len;
529   } else { // (path == NULL); this should never happen
530     message[len++] = ERL_DRV_ATOM;
531     message[len++] = driver_mk_atom("undefined");
532   }
533
534   message[len++] = ERL_DRV_UINT;
535   message[len++] = event->cookie;
536
537   size_t nflags = 0;
538
539 #define ADD_FLAG(flag, atom) \
540   if ((event->mask & flag) != 0) { \
541     ++nflags; \
542     message[len++] = ERL_DRV_ATOM; \
543     message[len++] = atom; \
544   }
545
546   // XXX: this flag is promised to go first
547   ADD_FLAG(IN_ISDIR, atom_is_dir);
548
549   ADD_FLAG(IN_ACCESS,        atom_access);
550   ADD_FLAG(IN_MODIFY,        atom_modify);
551   ADD_FLAG(IN_ATTRIB,        atom_attrib);
552   ADD_FLAG(IN_CREATE,        atom_create);
553   ADD_FLAG(IN_DELETE,        atom_delete);
554   ADD_FLAG(IN_OPEN,          atom_open);
555   ADD_FLAG(IN_CLOSE_WRITE,   atom_close_write);
556   ADD_FLAG(IN_CLOSE_NOWRITE, atom_close_nowrite);
557   ADD_FLAG(IN_MOVED_FROM,    atom_move_from);
558   ADD_FLAG(IN_MOVED_TO,      atom_move_to);
559   ADD_FLAG(IN_DELETE_SELF,   atom_delete_self);
560   ADD_FLAG(IN_MOVE_SELF,     atom_move_self);
561
562 #undef ADD_FLAG
563
564   message[len++] = ERL_DRV_NIL;
565   message[len++] = ERL_DRV_LIST;
566   message[len++] = nflags + 1 /* for ERL_DRV_NIL */;
567
568   message[len++] = ERL_DRV_TUPLE;
569   message[len++] = 5; // {inotify, Port, Path, Cookie, Flags}
570
571   return driver_output_term(context->erl_port, message, len);
572 }
573
574 static
575 int send_inotify_error(struct inotify_context *context,
576                        ErlDrvTermData error_atom)
577 {
578   ErlDrvTermData message[] = {
579     ERL_DRV_ATOM, atom_inotify_error,
580     ERL_DRV_PORT, driver_mk_port(context->erl_port),
581     ERL_DRV_ATOM, error_atom,
582     ERL_DRV_TUPLE, 3
583   };
584
585   return driver_output_term(context->erl_port, message,
586                             sizeof(message) / sizeof(message[0]));
587 }
588
589 // }}}
590 //----------------------------------------------------------------------------
591 // watch management {{{
592
593 static
594 int watch_add(struct inotify_context *context, int wd, uint32_t flags,
595               char *path)
596 {
597   // TODO: keep order by `wd', so binary search works
598
599   int flags_reset = ((flags & IN_MASK_ADD) == 0);
600   flags &= ~(IN_MASK_ADD | IN_DONT_FOLLOW | IN_ONESHOT | IN_ONLYDIR);
601
602   if (context->watches != NULL) {
603     struct watch *end = context->watches + context->nwatches;
604     struct watch *result = context->watches;
605
606     while (result < end && result->wd != wd)
607       ++result;
608
609     if (result != end) {
610       if (flags_reset)
611         result->flags = flags;
612       else
613         result->flags |= flags;
614
615       return 0;
616     }
617   }
618
619   if (context->watches == NULL) {
620     context->max_watches = 64;
621     size_t memsize = sizeof(struct watch) * context->max_watches;
622     context->watches = driver_alloc(memsize);
623   } else if (context->max_watches == context->nwatches) {
624     context->max_watches *= 2;
625     size_t memsize = sizeof(struct watch) * context->max_watches;
626     context->watches = driver_realloc(context->watches, memsize);
627     if (context->watches == NULL)
628       return -1;
629   }
630
631   struct watch *watch = context->watches + context->nwatches++;
632
633   watch->wd = wd;
634   watch->flags = flags;
635   watch->path_len = strlen(path);
636   // this must hold the actual path, slash, filename, and trailing NUL byte
637   watch->path = driver_alloc(watch->path_len + 1 + NAME_MAX + 1);
638
639   memcpy(watch->path, path, watch->path_len);
640   watch->path[watch->path_len] = 0;
641
642   return 0;
643 }
644
645 static
646 void watch_remove(struct inotify_context *context, int wd)
647 {
648   struct watch *end = context->watches + context->nwatches;
649   struct watch *current = context->watches;
650
651   while (current < end && current->wd != wd)
652     ++current;
653
654   if (current < end) {
655     driver_free(current->path);
656     current->path = NULL;
657     --context->nwatches;
658
659     if (current < end - 1) {
660       memcpy(current, end - 1, sizeof(struct watch));
661       (end - 1)->path = NULL;
662     }
663   }
664 }
665
666 static
667 int watch_find_wd(struct inotify_context *context, char *path)
668 {
669   size_t path_len = strlen(path);
670
671   struct watch *end = context->watches + context->nwatches;
672   struct watch *current;
673
674   for (current = context->watches; current < end; ++current) {
675     if (current->path_len == path_len &&
676         strncmp(current->path, path, path_len) == 0)
677       return current->wd;
678   }
679
680   return -1;
681 }
682
683 static
684 char* watch_find(struct inotify_context *context, struct inotify_event *event,
685                  size_t *path_len)
686 {
687   struct watch *end = context->watches + context->nwatches;
688   struct watch *result = context->watches;
689
690   while (result < end && result->wd != event->wd)
691     ++result;
692
693   if (result == end)
694     return NULL;
695
696   size_t name_len = result->path_len;
697
698   if (event->len > 0) {
699     result->path[name_len++] = '/';
700     size_t event_name_len = event->len;
701     while (event->name[event_name_len - 1] == 0)
702       --event_name_len;
703     memcpy(result->path + name_len, event->name, event_name_len);
704     name_len += event_name_len;
705   }
706
707   result->path[name_len] = 0;
708
709   if (path_len != NULL)
710     *path_len = name_len;
711
712   return result->path;
713 }
714
715 // }}}
716 //----------------------------------------------------------------------------
717 // copy path to NUL-terminated buffer, maybe with resolving symlinks {{{
718
719 static
720 ssize_t copy_path(char *path, size_t path_len, char *output, int real)
721 {
722   if (path_len >= PATH_MAX) {
723     errno = ENAMETOOLONG;
724     return -1;
725   }
726
727   if (!real) {
728     memcpy(output, path, path_len);
729     output[path_len] = 0;
730     return path_len;
731   }
732
733   char tmppath[PATH_MAX];
734   memcpy(tmppath, path, path_len);
735   tmppath[path_len] = 0;
736
737   if (realpath(tmppath, output) == NULL)
738     return -1;
739
740   return 0;
741 }
742
743 // }}}
744 //----------------------------------------------------------------------------
745 // flags translation {{{
746
747 static
748 uint32_t flags_to_inotify(uint32_t flags)
749 {
750   uint32_t result = 0;
751
752   if ((flags & FLAG_ACCESS)        != 0) result |= IN_ACCESS;
753   if ((flags & FLAG_MODIFY)        != 0) result |= IN_MODIFY;
754   if ((flags & FLAG_ATTRIB)        != 0) result |= IN_ATTRIB;
755   if ((flags & FLAG_CREATE)        != 0) result |= IN_CREATE;
756   if ((flags & FLAG_DELETE)        != 0) result |= IN_DELETE;
757   if ((flags & FLAG_OPEN)          != 0) result |= IN_OPEN;
758   if ((flags & FLAG_CLOSE_WRITE)   != 0) result |= IN_CLOSE_WRITE;
759   if ((flags & FLAG_CLOSE_NOWRITE) != 0) result |= IN_CLOSE_NOWRITE;
760   if ((flags & FLAG_MOVED_FROM)    != 0) result |= IN_MOVED_FROM;
761   if ((flags & FLAG_MOVED_TO)      != 0) result |= IN_MOVED_TO;
762   if ((flags & FLAG_DELETE_SELF)   != 0) result |= IN_DELETE_SELF;
763   if ((flags & FLAG_MOVE_SELF)     != 0) result |= IN_MOVE_SELF;
764   if ((flags & FLAG_DONT_FOLLOW)   != 0) result |= IN_DONT_FOLLOW;
765   if ((flags & FLAG_EXCL_UNLINK)   != 0) result |= IN_EXCL_UNLINK;
766   if ((flags & FLAG_ONESHOT)       != 0) result |= IN_ONESHOT;
767   if ((flags & FLAG_ONLYDIR)       != 0) result |= IN_ONLYDIR;
768   if ((flags & FLAG_MASK_ADD)      != 0) result |= IN_MASK_ADD;
769
770   return result;
771 }
772
773 // }}}
774 //----------------------------------------------------------------------------
775 // vim:ft=c:foldmethod=marker:nowrap