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