libinotifytools
inotifytools.c
1// kate: replace-tabs off; space-indent off;
2
15#include "../../config.h"
17#include "inotifytools_p.h"
18#include "stats.h"
19
20#include <string.h>
21#include <strings.h>
22#include <stdlib.h>
23#include <stdint.h>
24#include <stdio.h>
25#include <errno.h>
26#include <sys/select.h>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <sys/ioctl.h>
30#include <unistd.h>
31#include <dirent.h>
32#include <time.h>
33#include <regex.h>
34#include <setjmp.h>
35
36#include "inotifytools/inotify.h"
37
38#ifdef __FreeBSD__
39struct fanotify_event_fid;
40
41#define FAN_EVENT_INFO_TYPE_FID 1
42#define FAN_EVENT_INFO_TYPE_DFID_NAME 2
43#define FAN_EVENT_INFO_TYPE_DFID 3
44
45#else
46// Linux only
47#define LINUX_FANOTIFY
48
49#include <fcntl.h>
50#include <sys/vfs.h>
51#include "inotifytools/fanotify.h"
52
53struct fanotify_event_fid {
54 struct fanotify_event_info_fid info;
55 struct file_handle handle;
56};
57#endif
58
145#define MAX_EVENTS 4096
146#define INOTIFY_PROCDIR "/proc/sys/fs/inotify/"
147#define WATCHES_SIZE_PATH INOTIFY_PROCDIR "max_user_watches"
148#define QUEUE_SIZE_PATH INOTIFY_PROCDIR "max_queued_watches"
149#define INSTANCES_PATH INOTIFY_PROCDIR "max_user_instances"
150
151static int inotify_fd = -1;
152
153int collect_stats = 0;
154
155struct rbtree *tree_wd = 0;
156struct rbtree* tree_fid = 0;
157struct rbtree *tree_filename = 0;
158static int error = 0;
159int init = 0;
160int verbosity = 0;
161int fanotify_mode = 0;
162int fanotify_mark_type = 0;
163static char* timefmt = 0;
164static regex_t* regex = 0;
165/* 0: --exclude[i], 1: --include[i] */
166static int invert_regexp = 0;
167
168static int isdir( char const * path );
169void record_stats( struct inotify_event const * event );
170int onestr_to_event(char const * event);
171
172#define nasprintf(...) niceassert( -1 != asprintf(__VA_ARGS__), "out of memory")
173
191void _niceassert( long cond, int line, char const * file,
192 char const * condstr, char const * mesg ) {
193 if ( cond ) return;
194
195 if ( mesg ) {
196 fprintf(stderr, "%s:%d assertion ( %s ) failed: %s\n", file, line,
197 condstr, mesg );
198 }
199 else {
200 fprintf(stderr, "%s:%d assertion ( %s ) failed.\n", file, line, condstr);
201 }
202}
203
204static void charcat(char* s, const char c) {
205 size_t l = strlen(s);
206 s[l] = c;
207 s[++l] = 0;
208}
209
213static int read_num_from_file(char* filename, int* num) {
214 FILE * file = fopen( filename, "r" );
215 if ( !file ) {
216 error = errno;
217 return 0;
218 }
219
220 if ( EOF == fscanf( file, "%d", num ) ) {
221 error = errno;
222 const int fclose_ret = fclose(file);
223 niceassert(!fclose_ret, 0);
224 return 0;
225 }
226
227 const int fclose_ret = fclose(file);
228 niceassert(!fclose_ret, 0);
229
230 return 1;
231}
232
233static int wd_compare(const void* d1, const void* d2, const void* config) {
234 if (!d1 || !d2) return d1 - d2;
235 return ((watch*)d1)->wd - ((watch*)d2)->wd;
236}
237
238static int fid_compare(const void* d1, const void* d2, const void* config) {
239#ifdef LINUX_FANOTIFY
240 if (!d1 || !d2)
241 return d1 - d2;
242 watch* w1 = (watch*)d1;
243 watch* w2 = (watch*)d2;
244 int n1, n2;
245 n1 = w1->fid->info.hdr.len;
246 n2 = w2->fid->info.hdr.len;
247 if (n1 != n2)
248 return n1 - n2;
249 return memcmp(w1->fid, w2->fid, n1);
250#else
251 return d1 - d2;
252#endif
253}
254
255static int filename_compare(const void* d1,
256 const void* d2,
257 const void* config) {
258 if (!d1 || !d2) return d1 - d2;
259 return strcmp(((watch*)d1)->filename, ((watch*)d2)->filename);
260}
261
265watch *watch_from_wd( int wd ) {
266 watch w;
267 w.wd = wd;
268 return (watch*)rbfind(&w, tree_wd);
269}
270
274watch* watch_from_fid(struct fanotify_event_fid* fid) {
275 watch w;
276 w.fid = fid;
277 return (watch*)rbfind(&w, tree_fid);
278}
279
283watch *watch_from_filename( char const *filename ) {
284 watch w;
285 w.filename = (char*)filename;
286 return (watch*)rbfind(&w, tree_filename);
287}
288
299int inotifytools_init(int fanotify, int watch_filesystem, int verbose) {
300 if (init) return 1;
301
302 error = 0;
303 verbosity = verbose;
304 // Try to initialise inotify/fanotify
305 if (fanotify) {
306#ifdef LINUX_FANOTIFY
307 fanotify_mode = 1;
308 fanotify_mark_type =
309 watch_filesystem ? FAN_MARK_FILESYSTEM : FAN_MARK_INODE;
310 inotify_fd =
311 fanotify_init(FAN_REPORT_FID | FAN_REPORT_DFID_NAME, 0);
312#endif
313 } else {
314 fanotify_mode = 0;
315 inotify_fd = inotify_init();
316 }
317 if (inotify_fd < 0) {
318 error = errno;
319 return 0;
320 }
321
322 collect_stats = 0;
323 init = 1;
324 tree_wd = rbinit(wd_compare, 0);
325 tree_fid = rbinit(fid_compare, 0);
326 tree_filename = rbinit(filename_compare, 0);
327 timefmt = 0;
328
329 return 1;
330}
331
332int inotifytools_initialize() {
333 return inotifytools_init(0, 0, 0);
334}
335
339void destroy_watch(watch *w) {
340 if (w->filename) free(w->filename);
341 if (w->fid)
342 free(w->fid);
343 if (w->dirf)
344 close(w->dirf);
345 free(w);
346}
347
351void cleanup_tree(const void *nodep,
352 const VISIT which,
353 const int depth, void* arg) {
354 if (which != endorder && which != leaf) return;
355 watch *w = (watch*)nodep;
356 destroy_watch(w);
357}
358
366 if (!init) return;
367
368 init = 0;
369 close(inotify_fd);
370 collect_stats = 0;
371 error = 0;
372 timefmt = 0;
373
374 if (regex) {
375 regfree(regex);
376 free(regex);
377 regex = 0;
378 }
379
380 rbwalk(tree_wd, cleanup_tree, 0);
381 rbdestroy(tree_wd);
382 rbdestroy(tree_fid);
383 rbdestroy(tree_filename);
384 tree_wd = 0;
385 tree_fid = 0;
386 tree_filename = 0;
387}
388
389
390
394struct replace_filename_data {
395 char const *old_name;
396 char const *new_name;
397 size_t old_len;
398};
399
403static void replace_filename(const void* nodep,
404 const VISIT which,
405 const int depth,
406 const struct replace_filename_data* data) {
407 if (which != endorder && which != leaf)
408 return;
409 watch *w = (watch*)nodep;
410 char *name;
411 if ( 0 == strncmp( data->old_name, w->filename, data->old_len ) ) {
412 nasprintf( &name, "%s%s", data->new_name, &(w->filename[data->old_len]) );
413 if (!strcmp( w->filename, data->new_name )) {
414 free(name);
415 } else {
416 rbdelete(w, tree_filename);
417 free( w->filename );
418 w->filename = name;
419 rbsearch(w, tree_filename);
420 }
421 }
422}
423
427static void get_num(const void* nodep,
428 const VISIT which,
429 const int depth,
430 void* arg) {
431 if (which != endorder && which != leaf)
432 return;
433 ++(*((int*)arg));
434}
435
463int inotifytools_str_to_event_sep(char const * event, char sep) {
464 if ( strchr( "_" "abcdefghijklmnopqrstuvwxyz"
465 "ABCDEFGHIJKLMNOPQRSTUVWXYZ", sep ) ) {
466 return -1;
467 }
468
469 int ret, len;
470 char * event1, * event2;
471 static const size_t eventstr_size = 4096;
472 char eventstr[eventstr_size];
473 ret = 0;
474
475 if ( !event || !event[0] ) return 0;
476
477 event1 = (char *)event;
478 event2 = strchr( event1, sep );
479 while ( event1 && event1[0] ) {
480 if ( event2 ) {
481 len = event2 - event1;
482 niceassert(len < eventstr_size,
483 "malformed event string (very long)");
484 }
485 else {
486 len = strlen(event1);
487 }
488 if (len > eventstr_size - 1)
489 len = eventstr_size - 1;
490
491 strncpy(eventstr, event1, len);
492
493 eventstr[len] = 0;
494
495 int ret1 = onestr_to_event(eventstr);
496 if ( 0 == ret1 || -1 == ret1 ) {
497 ret = ret1;
498 break;
499 }
500 ret |= ret1;
501
502 event1 = event2;
503 if ( event1 && event1[0] ) {
504 // jump over 'sep' character
505 ++event1;
506 // if last character was 'sep'...
507 if ( !event1[0] ) return 0;
508 event2 = strchr( event1, sep );
509 }
510 }
511
512 return ret;
513}
514
538int inotifytools_str_to_event(char const * event) {
539 return inotifytools_str_to_event_sep( event, ',' );
540}
541
553int onestr_to_event(char const * event)
554{
555 static int ret;
556 ret = -1;
557
558 if ( !event || !event[0] )
559 ret = 0;
560 else if ( 0 == strcasecmp(event, "ACCESS") )
561 ret = IN_ACCESS;
562 else if ( 0 == strcasecmp(event, "MODIFY") )
563 ret = IN_MODIFY;
564 else if ( 0 == strcasecmp(event, "ATTRIB") )
565 ret = IN_ATTRIB;
566 else if ( 0 == strcasecmp(event, "CLOSE_WRITE") )
567 ret = IN_CLOSE_WRITE;
568 else if ( 0 == strcasecmp(event, "CLOSE_NOWRITE") )
569 ret = IN_CLOSE_NOWRITE;
570 else if ( 0 == strcasecmp(event, "OPEN") )
571 ret = IN_OPEN;
572 else if ( 0 == strcasecmp(event, "MOVED_FROM") )
573 ret = IN_MOVED_FROM;
574 else if ( 0 == strcasecmp(event, "MOVED_TO") )
575 ret = IN_MOVED_TO;
576 else if ( 0 == strcasecmp(event, "CREATE") )
577 ret = IN_CREATE;
578 else if ( 0 == strcasecmp(event, "DELETE") )
579 ret = IN_DELETE;
580 else if ( 0 == strcasecmp(event, "DELETE_SELF") )
581 ret = IN_DELETE_SELF;
582 else if ( 0 == strcasecmp(event, "UNMOUNT") )
583 ret = IN_UNMOUNT;
584 else if ( 0 == strcasecmp(event, "Q_OVERFLOW") )
585 ret = IN_Q_OVERFLOW;
586 else if ( 0 == strcasecmp(event, "IGNORED") )
587 ret = IN_IGNORED;
588 else if ( 0 == strcasecmp(event, "CLOSE") )
589 ret = IN_CLOSE;
590 else if ( 0 == strcasecmp(event, "MOVE_SELF") )
591 ret = IN_MOVE_SELF;
592 else if ( 0 == strcasecmp(event, "MOVE") )
593 ret = IN_MOVE;
594 else if ( 0 == strcasecmp(event, "ISDIR") )
595 ret = IN_ISDIR;
596 else if ( 0 == strcasecmp(event, "ONESHOT") )
597 ret = IN_ONESHOT;
598 else if ( 0 == strcasecmp(event, "ALL_EVENTS") )
599 ret = IN_ALL_EVENTS;
600
601 return ret;
602}
603
625char * inotifytools_event_to_str(int events) {
626 return inotifytools_event_to_str_sep(events, ',');
627}
628
653char * inotifytools_event_to_str_sep(int events, char sep)
654{
655 static char ret[1024];
656 ret[0] = '\0';
657 ret[1] = '\0';
658
659 if ( IN_ACCESS & events ) {
660 charcat(ret, sep);
661 strncat(ret, "ACCESS", 7);
662 }
663 if ( IN_MODIFY & events ) {
664 charcat(ret, sep);
665 strncat(ret, "MODIFY", 7);
666 }
667 if ( IN_ATTRIB & events ) {
668 charcat(ret, sep);
669 strncat(ret, "ATTRIB", 7);
670 }
671 if ( IN_CLOSE_WRITE & events ) {
672 charcat(ret, sep);
673 strncat(ret, "CLOSE_WRITE", 12);
674 }
675 if ( IN_CLOSE_NOWRITE & events ) {
676 charcat(ret, sep);
677 strncat(ret, "CLOSE_NOWRITE", 14);
678 }
679 if ( IN_OPEN & events ) {
680 charcat(ret, sep);
681 strncat(ret, "OPEN", 5);
682 }
683 if ( IN_MOVED_FROM & events ) {
684 charcat(ret, sep);
685 strncat(ret, "MOVED_FROM", 11);
686 }
687 if ( IN_MOVED_TO & events ) {
688 charcat(ret, sep);
689 strncat(ret, "MOVED_TO", 9);
690 }
691 if ( IN_CREATE & events ) {
692 charcat(ret, sep);
693 strncat(ret, "CREATE", 7);
694 }
695 if ( IN_DELETE & events ) {
696 charcat(ret, sep);
697 strncat(ret, "DELETE", 7);
698 }
699 if ( IN_DELETE_SELF & events ) {
700 charcat(ret, sep);
701 strncat(ret, "DELETE_SELF", 12);
702 }
703 if ( IN_UNMOUNT & events ) {
704 charcat(ret, sep);
705 strncat(ret, "UNMOUNT", 8);
706 }
707 if ( IN_Q_OVERFLOW & events ) {
708 charcat(ret, sep);
709 strncat(ret, "Q_OVERFLOW", 11);
710 }
711 if ( IN_IGNORED & events ) {
712 charcat(ret, sep);
713 strncat(ret, "IGNORED", 8);
714 }
715 if ( IN_CLOSE & events ) {
716 charcat(ret, sep);
717 strncat(ret, "CLOSE", 6);
718 }
719 if ( IN_MOVE_SELF & events ) {
720 charcat(ret, sep);
721 strncat(ret, "MOVE_SELF", 10);
722 }
723 if ( IN_ISDIR & events ) {
724 charcat(ret, sep);
725 strncat(ret, "ISDIR", 6);
726 }
727 if ( IN_ONESHOT & events ) {
728 charcat(ret, sep);
729 strncat(ret, "ONESHOT", 8);
730 }
731
732 // Maybe we didn't match any... ?
733 if (ret[0] == '\0') {
734 niceassert( -1 != sprintf( ret, "%c0x%08x", sep, events ), 0 );
735 }
736
737 return &ret[1];
738}
739
746static const char* inotifytools_filename_from_fid(
747 struct fanotify_event_fid* fid) {
748#ifdef LINUX_FANOTIFY
749 static char filename[PATH_MAX];
750 struct fanotify_event_fid fsid = {};
751 int dirf = 0, mount_fd = AT_FDCWD;
752 int len = 0, name_len = 0;
753
754 // Match mount_fd from fid->fsid (and null fhandle)
755 fsid.info.fsid.val[0] = fid->info.fsid.val[0];
756 fsid.info.fsid.val[1] = fid->info.fsid.val[1];
757 fsid.info.hdr.info_type = FAN_EVENT_INFO_TYPE_FID;
758 fsid.info.hdr.len = sizeof(fsid);
759 watch* mnt = watch_from_fid(&fsid);
760 if (mnt)
761 mount_fd = mnt->dirf;
762
763 if (fid->info.hdr.info_type == FAN_EVENT_INFO_TYPE_DFID_NAME) {
764 int fid_len = sizeof(*fid) + fid->handle.handle_bytes;
765
766 name_len = fid->info.hdr.len - fid_len;
767 if (name_len && !fid->handle.f_handle[fid->handle.handle_bytes])
768 name_len = 0; // empty name??
769 }
770
771 // Try to get path from file handle
772 dirf = open_by_handle_at(mount_fd, &fid->handle, 0);
773 if (dirf > 0) {
774 // Got path by handle
775 } else if (fanotify_mark_type == FAN_MARK_FILESYSTEM) {
776 fprintf(stderr, "Failed to decode directory fid.\n");
777 return NULL;
778 } else if (name_len) {
779 // For recursive watch look for watch by fid without the name
780 fid->info.hdr.info_type = FAN_EVENT_INFO_TYPE_DFID;
781 fid->info.hdr.len -= name_len;
782
783 watch* w = watch_from_fid(fid);
784
785 fid->info.hdr.info_type = FAN_EVENT_INFO_TYPE_DFID_NAME;
786 fid->info.hdr.len += name_len;
787
788 if (!w) {
789 fprintf(stderr,
790 "Failed to lookup path by directory fid.\n");
791 return NULL;
792 }
793
794 dirf = w->dirf ? dup(w->dirf) : -1;
795 if (dirf < 0) {
796 fprintf(stderr, "Failed to get directory fd.\n");
797 return NULL;
798 }
799 } else {
800 // Fallthrough to stored filename
801 return NULL;
802 }
803 char sym[30];
804 sprintf(sym, "/proc/self/fd/%d", dirf);
805
806 // PATH_MAX - 2 because we have to append two characters to this path,
807 // '/' and 0
808 len = readlink(sym, filename, PATH_MAX - 2);
809 if (len < 0) {
810 close(dirf);
811 fprintf(stderr, "Failed to resolve path from directory fd.\n");
812 return NULL;
813 }
814
815 filename[len++] = '/';
816 filename[len] = 0;
817
818 if (name_len > 0) {
819 const char* name = (const char*)fid->handle.f_handle +
820 fid->handle.handle_bytes;
821 int deleted = faccessat(dirf, name, F_OK, AT_SYMLINK_NOFOLLOW);
822 if (deleted && errno != ENOENT) {
823 fprintf(stderr, "Failed to access file %s (%s).\n",
824 name, strerror(errno));
825 close(dirf);
826 return NULL;
827 }
828 memcpy(filename + len, name, name_len);
829 if (deleted)
830 strncat(filename, " (deleted)", 11);
831 }
832 close(dirf);
833 return filename;
834#else
835 return NULL;
836#endif
837}
838
845const char* inotifytools_filename_from_watch(watch* w) {
846 if (!w)
847 return "";
848 if (!w->fid || !fanotify_mark_type)
849 return w->filename;
850
851 return inotifytools_filename_from_fid(w->fid) ?: w->filename;
852}
853
874const char* inotifytools_filename_from_wd(int wd) {
875 niceassert( init, "inotifytools_initialize not called yet" );
876 if (!wd)
877 return "";
878 watch *w = watch_from_wd(wd);
879 if (!w)
880 return "";
881
883}
884
893const char* inotifytools_dirname_from_event(struct inotify_event* event,
894 size_t* dirnamelen) {
895 const char* filename = inotifytools_filename_from_wd(event->wd);
896 char* dirsep;
897
898 if (!filename) {
899 return NULL;
900 }
901
902 dirsep = strrchr(filename, '/');
903 if (!dirsep) {
904 return NULL;
905 }
906
907 *dirnamelen = dirsep - filename + 1;
908 return filename;
909}
910
919char* inotifytools_dirpath_from_event(struct inotify_event* event) {
920 const char* filename = inotifytools_filename_from_wd(event->wd);
921
922 if (!filename || !*filename || !(event->mask & IN_ISDIR)) {
923 return NULL;
924 }
925
926 /*
927 * fanotify watch->filename includes the name, so no need to add the
928 * event->name again.
929 */
930 char* path;
931 nasprintf(&path, "%s%s/", filename, fanotify_mode ? "" : event->name);
932
933 return path;
934}
935
950int inotifytools_wd_from_filename( char const * filename ) {
951 niceassert( init, "inotifytools_initialize not called yet" );
952 if (!filename || !*filename)
953 return -1;
954 watch *w = watch_from_filename(filename);
955 if (!w) return -1;
956 return w->wd;
957}
958
973void inotifytools_set_filename_by_wd( int wd, char const * filename ) {
974 niceassert( init, "inotifytools_initialize not called yet" );
975 watch *w = watch_from_wd(wd);
976 if (!w) return;
977 if (w->filename) free(w->filename);
978 w->filename = strdup(filename);
979}
980
995void inotifytools_set_filename_by_filename( char const * oldname,
996 char const * newname ) {
997 watch *w = watch_from_filename(oldname);
998 if (!w) return;
999 if (w->filename) free(w->filename);
1000 w->filename = strdup(newname);
1001}
1002
1025void inotifytools_replace_filename( char const * oldname,
1026 char const * newname ) {
1027 if (!oldname || !newname)
1028 return;
1029 if (!*oldname || !*newname)
1030 return;
1031 struct replace_filename_data data;
1032 data.old_name = oldname;
1033 data.new_name = newname;
1034 data.old_len = strlen(oldname);
1035 rbwalk(tree_filename, (void *)replace_filename, (void *)&data);
1036}
1037
1041int remove_inotify_watch(watch *w) {
1042 error = 0;
1043 // There is no kernel object representing the watch with fanotify
1044 if (w->fid)
1045 return 0;
1046 int status = inotify_rm_watch( inotify_fd, w->wd );
1047 if ( status < 0 ) {
1048 fprintf(stderr, "Failed to remove watch on %s: %s\n", w->filename,
1049 strerror(status) );
1050 error = status;
1051 return 0;
1052 }
1053 return 1;
1054}
1055
1059watch* create_watch(int wd,
1060 struct fanotify_event_fid* fid,
1061 const char* filename,
1062 int dirf) {
1063 if (wd < 0 || !filename)
1064 return 0;
1065
1066 watch *w = (watch*)calloc(1, sizeof(watch));
1067 if (!w) {
1068 fprintf(stderr, "Failed to allocate watch.\n");
1069 return NULL;
1070 }
1071 w->wd = wd ?: (unsigned long)fid;
1072 w->fid = fid;
1073 w->dirf = dirf;
1074 w->filename = strdup(filename);
1075 rbsearch(w, tree_wd);
1076 if (fid)
1077 rbsearch(w, tree_fid);
1078
1079 rbsearch(w, tree_filename);
1080 return w;
1081}
1082
1096 niceassert( init, "inotifytools_initialize not called yet" );
1097 watch *w = watch_from_wd(wd);
1098 if (!w) return 1;
1099
1100 if (!remove_inotify_watch(w)) return 0;
1101 rbdelete(w, tree_wd);
1102 if (w->fid)
1103 rbdelete(w, tree_fid);
1104 rbdelete(w, tree_filename);
1105 destroy_watch(w);
1106 return 1;
1107}
1108
1120int inotifytools_remove_watch_by_filename( char const * filename ) {
1121 niceassert( init, "inotifytools_initialize not called yet" );
1122 watch *w = watch_from_filename(filename);
1123 if (!w) return 1;
1124
1125 if (!remove_inotify_watch(w)) return 0;
1126 rbdelete(w, tree_wd);
1127 if (w->fid)
1128 rbdelete(w, tree_fid);
1129 rbdelete(w, tree_filename);
1130 destroy_watch(w);
1131 return 1;
1132}
1133
1145int inotifytools_watch_file(char const* filename, int events) {
1146 static char const* filenames[2];
1147 filenames[0] = filename;
1148 filenames[1] = NULL;
1149 return inotifytools_watch_files( filenames, events );
1150}
1151
1167int inotifytools_watch_files(char const* filenames[], int events) {
1168 niceassert( init, "inotifytools_initialize not called yet" );
1169 error = 0;
1170
1171 static int i;
1172 for ( i = 0; filenames[i]; ++i ) {
1173 int wd = -1;
1174 if (fanotify_mode) {
1175#ifdef LINUX_FANOTIFY
1176 unsigned int flags = FAN_MARK_ADD | fanotify_mark_type;
1177
1178 if (events & IN_DONT_FOLLOW) {
1179 events &= ~IN_DONT_FOLLOW;
1180 flags |= FAN_MARK_DONT_FOLLOW;
1181 }
1182
1183 wd = fanotify_mark(inotify_fd, flags,
1184 events | FAN_EVENT_ON_CHILD,
1185 AT_FDCWD, filenames[i]);
1186#endif
1187 } else {
1188 wd =
1189 inotify_add_watch(inotify_fd, filenames[i], events);
1190 }
1191 if ( wd < 0 ) {
1192 if ( wd == -1 ) {
1193 error = errno;
1194 return 0;
1195 } // if ( wd == -1 )
1196 else {
1197 fprintf( stderr, "Failed to watch %s: returned wd was %d "
1198 "(expected -1 or >0 )", filenames[i], wd );
1199 // no appropriate value for error
1200 return 0;
1201 } // else
1202 } // if ( wd < 0 )
1203
1204 const char* filename = filenames[i];
1205 size_t filenamelen = strlen(filename);
1206 char* dirname;
1207 int dirf = 0;
1208 // Always end filename with / if it is a directory
1209 if (!isdir(filename)) {
1210 dirname = NULL;
1211 } else if (filename[filenamelen - 1] == '/') {
1212 dirname = strdup(filename);
1213 } else {
1214 nasprintf(&dirname, "%s/", filename);
1215 filename = dirname;
1216 filenamelen++;
1217 }
1218
1219 struct fanotify_event_fid* fid = NULL;
1220#ifdef LINUX_FANOTIFY
1221 if (!wd) {
1222 fid = calloc(1, sizeof(*fid) + MAX_FID_LEN);
1223 if (!fid) {
1224 fprintf(stderr, "Failed to allocate fid");
1225 free(dirname);
1226 return 0;
1227 }
1228
1229 struct statfs buf;
1230 if (statfs(filenames[i], &buf)) {
1231 free(fid);
1232 fprintf(stderr, "Statfs failed on %s: %s\n",
1233 filenames[i], strerror(errno));
1234 free(dirname);
1235 return 0;
1236 }
1237 memcpy(&fid->info.fsid, &buf.f_fsid,
1238 sizeof(__kernel_fsid_t));
1239
1240 // Hash mount_fd with fid->fsid (and null fhandle)
1241 int ret, mntid;
1242 watch* mnt = dirname ? watch_from_fid(fid) : NULL;
1243 if (dirname && !mnt) {
1244 struct fanotify_event_fid* fsid;
1245
1246 fsid = calloc(1, sizeof(*fsid));
1247 if (!fsid) {
1248 free(fid);
1249 fprintf(stderr,
1250 "Failed to allocate fsid");
1251 free(dirname);
1252 return 0;
1253 }
1254 fsid->info.fsid.val[0] = fid->info.fsid.val[0];
1255 fsid->info.fsid.val[1] = fid->info.fsid.val[1];
1256 fsid->info.hdr.info_type =
1257 FAN_EVENT_INFO_TYPE_FID;
1258 fsid->info.hdr.len = sizeof(*fsid);
1259 mntid = open(dirname, O_RDONLY);
1260 if (mntid < 0) {
1261 free(fid);
1262 free(fsid);
1263 fprintf(stderr,
1264 "Failed to open %s: %s\n",
1265 dirname, strerror(errno));
1266 free(dirname);
1267 return 0;
1268 }
1269 // Hash mount_fd without terminating /
1270 dirname[filenamelen - 1] = 0;
1271 create_watch(0, fsid, dirname, mntid);
1272 dirname[filenamelen - 1] = '/';
1273 }
1274
1275 fid->handle.handle_bytes = MAX_FID_LEN;
1276 ret = name_to_handle_at(AT_FDCWD, filenames[i],
1277 (void*)&fid->handle, &mntid, 0);
1278 if (ret || fid->handle.handle_bytes > MAX_FID_LEN) {
1279 free(fid);
1280 fprintf(stderr, "Encode fid failed on %s: %s\n",
1281 filenames[i], strerror(errno));
1282 free(dirname);
1283 return 0;
1284 }
1285 fid->info.hdr.info_type = dirname
1286 ? FAN_EVENT_INFO_TYPE_DFID
1287 : FAN_EVENT_INFO_TYPE_FID;
1288 fid->info.hdr.len =
1289 sizeof(*fid) + fid->handle.handle_bytes;
1290 if (dirname) {
1291 dirf = open(dirname, O_PATH);
1292 if (dirf < 0) {
1293 free(fid);
1294 fprintf(stderr,
1295 "Failed to open %s: %s\n",
1296 dirname, strerror(errno));
1297 free(dirname);
1298 return 0;
1299 }
1300 }
1301 }
1302#endif
1303 create_watch(wd, fid, filename, dirf);
1304 free(dirname);
1305 } // for
1306
1307 return 1;
1308}
1309
1336struct inotify_event * inotifytools_next_event( long int timeout ) {
1337 if (!timeout) {
1338 timeout = -1;
1339 }
1340
1341 return inotifytools_next_events( timeout, 1 );
1342}
1343
1344
1393struct inotify_event * inotifytools_next_events( long int timeout, int num_events ) {
1394 niceassert( init, "inotifytools_initialize not called yet" );
1395 niceassert( num_events <= MAX_EVENTS, "too many events requested" );
1396
1397 if ( num_events < 1 ) return NULL;
1398
1399 // second half of event[] buffer is for fanotify->inotify conversion
1400 static struct inotify_event event[2 * MAX_EVENTS];
1401 static struct inotify_event * ret;
1402 static int first_byte = 0;
1403 static ssize_t bytes;
1404 static ssize_t this_bytes;
1405 static jmp_buf jmp;
1406 static struct nstring match_name;
1407 static char match_name_string[MAX_STRLEN+1];
1408
1409 setjmp(jmp);
1410
1411 error = 0;
1412
1413 // first_byte is index into event buffer
1414 if ( first_byte != 0
1415 && first_byte <= (int)(bytes - sizeof(struct inotify_event)) ) {
1416
1417 ret = (struct inotify_event *)((char *)&event[0] + first_byte);
1418 if (!fanotify_mode &&
1419 first_byte + sizeof(*ret) + ret->len > bytes) {
1420 // oh... no. this can't be happening. An incomplete event.
1421 // Copy what we currently have into first element, call self to
1422 // read remainder.
1423 // oh, and they BETTER NOT overlap.
1424 // Boy I hope this code works.
1425 // But I think this can never happen due to how inotify is written.
1426 niceassert( (long)((char *)&event[0] +
1427 sizeof(struct inotify_event) +
1428 event[0].len) <= (long)ret,
1429 "extremely unlucky user, death imminent" );
1430 // how much of the event do we have?
1431 bytes = (char *)&event[0] + bytes - (char *)ret;
1432 memcpy( &event[0], ret, bytes );
1433 return inotifytools_next_events( timeout, num_events );
1434 }
1435 this_bytes = 0;
1436 goto more_events;
1437
1438 }
1439
1440 else if ( first_byte == 0 ) {
1441 bytes = 0;
1442 }
1443
1444
1445 static unsigned int bytes_to_read;
1446 static int rc;
1447 static fd_set read_fds;
1448
1449 static struct timeval read_timeout;
1450 read_timeout.tv_sec = timeout;
1451 read_timeout.tv_usec = 0;
1452 static struct timeval * read_timeout_ptr;
1453 read_timeout_ptr = ( timeout < 0 ? NULL : &read_timeout );
1454
1455 FD_ZERO(&read_fds);
1456 FD_SET(inotify_fd, &read_fds);
1457 rc = select(inotify_fd + 1, &read_fds,
1458 NULL, NULL, read_timeout_ptr);
1459 if ( rc < 0 ) {
1460 // error
1461 error = errno;
1462 return NULL;
1463 }
1464 else if ( rc == 0 ) {
1465 // timeout
1466 return NULL;
1467 }
1468
1469 // wait until we have enough bytes to read
1470 do {
1471 rc = ioctl( inotify_fd, FIONREAD, &bytes_to_read );
1472 } while ( !rc &&
1473 bytes_to_read < sizeof(struct inotify_event)*num_events );
1474
1475 if ( rc == -1 ) {
1476 error = errno;
1477 return NULL;
1478 }
1479
1480 this_bytes = read(inotify_fd, &event[0] + bytes,
1481 sizeof(struct inotify_event)*MAX_EVENTS - bytes);
1482 if ( this_bytes < 0 ) {
1483 error = errno;
1484 return NULL;
1485 }
1486 if ( this_bytes == 0 ) {
1487 fprintf(stderr, "Inotify reported end-of-file. Possibly too many "
1488 "events occurred at once.\n");
1489 return NULL;
1490 }
1491more_events:
1492 ret = (struct inotify_event*)((char*)&event[0] + first_byte);
1493#ifdef LINUX_FANOTIFY
1494 // convert fanotify events to inotify events
1495 if (fanotify_mode) {
1496 struct fanotify_event_metadata* meta = (void*)ret;
1497 struct fanotify_event_info_fid* info = (void*)(meta + 1);
1498 struct fanotify_event_fid* fid = NULL;
1499 const char* name = "";
1500 int fid_len = 0;
1501 int name_len = 0;
1502
1503 first_byte += meta->event_len;
1504
1505 if (meta->event_len > sizeof(*meta)) {
1506 switch (info->hdr.info_type) {
1507 case FAN_EVENT_INFO_TYPE_FID:
1508 case FAN_EVENT_INFO_TYPE_DFID:
1509 case FAN_EVENT_INFO_TYPE_DFID_NAME:
1510 fid = (void*)info;
1511 fid_len = sizeof(*fid) +
1512 fid->handle.handle_bytes;
1513 if (info->hdr.info_type ==
1514 FAN_EVENT_INFO_TYPE_DFID_NAME) {
1515 name_len =
1516 info->hdr.len - fid_len;
1517 }
1518 if (name_len > 0) {
1519 name =
1520 (const char*)
1521 fid->handle.f_handle +
1522 fid->handle.handle_bytes;
1523 }
1524 // Convert zero padding to zero
1525 // name_len. For some events on
1526 // directories, the fid is that of the
1527 // dir and name is ".". Do not include
1528 // "." name in fid hash, but keep it for
1529 // debug print.
1530 if (name_len &&
1531 (!*name || !strcmp(name, "."))) {
1532 info->hdr.len -= name_len;
1533 name_len = 0;
1534 }
1535 break;
1536 }
1537 }
1538 if (!fid) {
1539 fprintf(stderr, "No fid in fanotify event.\n");
1540 return NULL;
1541 }
1542 if (verbosity > 1) {
1543 printf(
1544 "fanotify_event: bytes=%zd, first_byte=%d, "
1545 "this_bytes=%zd, event_len=%u, fid_len=%d, "
1546 "name_len=%d, name=%s\n",
1547 bytes, first_byte, this_bytes, meta->event_len,
1548 fid_len, name_len, name);
1549 }
1550
1551 ret = &event[MAX_EVENTS];
1552 watch* w = watch_from_fid(fid);
1553 if (!w) {
1554 struct fanotify_event_fid* newfid =
1555 calloc(1, info->hdr.len);
1556 if (!newfid) {
1557 fprintf(stderr, "Failed to allocate fid.\n");
1558 return NULL;
1559 }
1560 memcpy(newfid, fid, info->hdr.len);
1561 const char* filename =
1562 inotifytools_filename_from_fid(fid);
1563 if (filename) {
1564 w = create_watch(0, newfid, filename, 0);
1565 if (!w) {
1566 free(newfid);
1567 return NULL;
1568 }
1569 }
1570
1571 if (verbosity) {
1572 unsigned long id;
1573 memcpy((void*)&id, fid->handle.f_handle,
1574 sizeof(id));
1575 printf("[fid=%x.%x.%lx;name='%s'] %s\n",
1576 fid->info.fsid.val[0],
1577 fid->info.fsid.val[1], id, name,
1578 filename ?: "");
1579 }
1580 }
1581 ret->wd = w ? w->wd : 0;
1582 ret->mask = (uint32_t)meta->mask;
1583 ret->len = name_len;
1584 if (name_len > 0)
1585 memcpy(ret->name, name, name_len);
1586 } else {
1587 first_byte += sizeof(struct inotify_event) + ret->len;
1588 }
1589#endif
1590
1591 bytes += this_bytes;
1592 niceassert( first_byte <= bytes, "ridiculously long filename, things will "
1593 "almost certainly screw up." );
1594 if ( first_byte == bytes ) {
1595 first_byte = 0;
1596 }
1597
1598 if (regex) {
1599 inotifytools_snprintf(&match_name, MAX_STRLEN, ret, "%w%f");
1600 memcpy(&match_name_string, &match_name.buf, match_name.len);
1601 match_name_string[match_name.len] = '\0';
1602 if (0 == regexec(regex, match_name_string, 0, 0, 0)) {
1603 if (!invert_regexp)
1604 longjmp(jmp, 0);
1605 } else {
1606 if (invert_regexp)
1607 longjmp(jmp, 0);
1608 }
1609 }
1610
1611 if (collect_stats) {
1612 record_stats(ret);
1613 }
1614
1615 return ret;
1616}
1617
1643int inotifytools_watch_recursively(char const* path, int events) {
1644 return inotifytools_watch_recursively_with_exclude( path, events, 0 );
1645}
1646
1680 int events,
1681 char const** exclude_list) {
1682 niceassert( init, "inotifytools_initialize not called yet" );
1683
1684 DIR * dir;
1685 char * my_path;
1686 error = 0;
1687 dir = opendir( path );
1688 if ( !dir ) {
1689 // If not a directory, don't need to do anything special
1690 if ( errno == ENOTDIR ) {
1691 return inotifytools_watch_file( path, events );
1692 }
1693 else {
1694 error = errno;
1695 return 0;
1696 }
1697 }
1698
1699 if ( path[strlen(path)-1] != '/' ) {
1700 nasprintf( &my_path, "%s/", path );
1701 }
1702 else {
1703 my_path = (char *)path;
1704 }
1705
1706 static struct dirent * ent;
1707 char * next_file;
1708 static struct stat64 my_stat;
1709 ent = readdir( dir );
1710 // Watch each directory within this directory
1711 while ( ent ) {
1712 if ( (0 != strcmp( ent->d_name, "." )) &&
1713 (0 != strcmp( ent->d_name, ".." )) ) {
1714 nasprintf(&next_file,"%s%s", my_path, ent->d_name);
1715 if ( -1 == lstat64( next_file, &my_stat ) ) {
1716 error = errno;
1717 free( next_file );
1718 if ( errno != EACCES ) {
1719 error = errno;
1720 if ( my_path != path ) free( my_path );
1721 closedir( dir );
1722 return 0;
1723 }
1724 }
1725 else if ( S_ISDIR( my_stat.st_mode ) &&
1726 !S_ISLNK( my_stat.st_mode )) {
1727 free( next_file );
1728 nasprintf(&next_file,"%s%s/", my_path, ent->d_name);
1729 static unsigned int no_watch;
1730 static char const** exclude_entry;
1731
1732 no_watch = 0;
1733 for (exclude_entry = exclude_list;
1734 exclude_entry && *exclude_entry && !no_watch;
1735 ++exclude_entry) {
1736 static int exclude_length;
1737
1738 exclude_length = strlen(*exclude_entry);
1739 if ((*exclude_entry)[exclude_length-1] == '/') {
1740 --exclude_length;
1741 }
1742 if ( strlen(next_file) == (unsigned)(exclude_length + 1) &&
1743 !strncmp(*exclude_entry, next_file, exclude_length)) {
1744 // directory found in exclude list
1745 no_watch = 1;
1746 }
1747 }
1748 if (!no_watch) {
1749 static int status;
1751 next_file,
1752 events,
1753 exclude_list );
1754 // For some errors, we will continue.
1755 if ( !status && (EACCES != error) && (ENOENT != error) &&
1756 (ELOOP != error) ) {
1757 free( next_file );
1758 if ( my_path != path ) free( my_path );
1759 closedir( dir );
1760 return 0;
1761 }
1762 } // if !no_watch
1763 free( next_file );
1764 } // if isdir and not islnk
1765 else {
1766 free( next_file );
1767 }
1768 }
1769 ent = readdir( dir );
1770 error = 0;
1771 }
1772
1773 closedir( dir );
1774
1775 int ret = inotifytools_watch_file( my_path, events );
1776 if ( my_path != path ) free( my_path );
1777 return ret;
1778}
1779
1791 return error;
1792}
1793
1797static int isdir( char const * path ) {
1798 static struct stat64 my_stat;
1799
1800 if ( -1 == lstat64( path, &my_stat ) ) {
1801 if (errno == ENOENT) return 0;
1802 fprintf(stderr, "Stat failed on %s: %s\n", path, strerror(errno));
1803 return 0;
1804 }
1805
1806 return S_ISDIR( my_stat.st_mode ) && !S_ISLNK( my_stat.st_mode );
1807}
1808
1809
1817 int ret = 0;
1818 rbwalk(tree_filename, get_num, (void*)&ret);
1819 return ret;
1820}
1821
1866int inotifytools_printf( struct inotify_event* event, char* fmt ) {
1867 return inotifytools_fprintf( stdout, event, fmt );
1868}
1869
1915int inotifytools_fprintf( FILE* file, struct inotify_event* event, char* fmt ) {
1916 static struct nstring out;
1917 static int ret;
1918 ret = inotifytools_sprintf( &out, event, fmt );
1919 if ( -1 != ret ) fwrite( out.buf, sizeof(char), out.len, file );
1920 return ret;
1921}
1922
1975int inotifytools_sprintf( struct nstring * out, struct inotify_event* event, char* fmt ) {
1976 return inotifytools_snprintf( out, MAX_STRLEN, event, fmt );
1977}
1978
1979
2030int inotifytools_snprintf( struct nstring * out, int size,
2031 struct inotify_event* event, char* fmt ) {
2032 static const char* filename;
2033 static char *eventname, *eventstr;
2034 static unsigned int i, ind;
2035 static char ch1;
2036 static char timestr[MAX_STRLEN];
2037 static time_t now;
2038
2039 if ( event->len > 0 ) {
2040 eventname = event->name;
2041 }
2042 else {
2043 eventname = NULL;
2044 }
2045
2046 size_t dirnamelen = 0;
2047 filename = inotifytools_dirname_from_event(event, &dirnamelen);
2048
2049 if ( !fmt || 0 == strlen(fmt) ) {
2050 error = EINVAL;
2051 return -1;
2052 }
2053 if ( strlen(fmt) > MAX_STRLEN || size > MAX_STRLEN) {
2054 error = EMSGSIZE;
2055 return -1;
2056 }
2057
2058 ind = 0;
2059 for ( i = 0; i < strlen(fmt) &&
2060 (int)ind < size - 1; ++i ) {
2061 if ( fmt[i] != '%' ) {
2062 out->buf[ind++] = fmt[i];
2063 continue;
2064 }
2065
2066 if ( i == strlen(fmt) - 1 ) {
2067 // last character is %, invalid
2068 error = EINVAL;
2069 return ind;
2070 }
2071
2072 ch1 = fmt[i+1];
2073
2074 if ( ch1 == '%' ) {
2075 out->buf[ind++] = '%';
2076 ++i;
2077 continue;
2078 }
2079
2080 if ( ch1 == '0' ) {
2081 out->buf[ind++] = '\0';
2082 ++i;
2083 continue;
2084 }
2085
2086 if ( ch1 == 'n' ) {
2087 out->buf[ind++] = '\n';
2088 ++i;
2089 continue;
2090 }
2091
2092 if ( ch1 == 'w' ) {
2093 if (filename && dirnamelen <= size - ind) {
2094 strncpy(&out->buf[ind], filename, dirnamelen);
2095 ind += dirnamelen;
2096 }
2097 ++i;
2098 continue;
2099 }
2100
2101 if ( ch1 == 'f' ) {
2102 if ( eventname ) {
2103 strncpy( &out->buf[ind], eventname, size - ind );
2104 ind += strlen(eventname);
2105 }
2106 ++i;
2107 continue;
2108 }
2109
2110 if ( ch1 == 'c' ) {
2111 ind += snprintf( &out->buf[ind], size-ind, "%x", event->cookie);
2112 ++i;
2113 continue;
2114 }
2115
2116 if ( ch1 == 'e' ) {
2117 eventstr = inotifytools_event_to_str( event->mask );
2118 strncpy( &out->buf[ind], eventstr, size - ind );
2119 ind += strlen(eventstr);
2120 ++i;
2121 continue;
2122 }
2123
2124 if ( ch1 == 'T' ) {
2125
2126 if ( timefmt ) {
2127 now = time(0);
2128 struct tm now_tm;
2129 if (!strftime(timestr, MAX_STRLEN - 1, timefmt,
2130 localtime_r(&now, &now_tm))) {
2131 // time format probably invalid
2132 error = EINVAL;
2133 return ind;
2134 }
2135 }
2136 else {
2137 timestr[0] = 0;
2138 }
2139
2140 strncpy( &out->buf[ind], timestr, size - ind );
2141 ind += strlen(timestr);
2142 ++i;
2143 continue;
2144 }
2145
2146 // Check if next char in fmt is e
2147 if ( i < strlen(fmt) - 2 && fmt[i+2] == 'e' ) {
2148 eventstr = inotifytools_event_to_str_sep( event->mask, ch1 );
2149 strncpy( &out->buf[ind], eventstr, size - ind );
2150 ind += strlen(eventstr);
2151 i += 2;
2152 continue;
2153 }
2154
2155 // OK, this wasn't a special format character, just output it as normal
2156 if ( ind < MAX_STRLEN ) out->buf[ind++] = '%';
2157 if ( ind < MAX_STRLEN ) out->buf[ind++] = ch1;
2158 ++i;
2159 }
2160 out->len = ind;
2161
2162 return ind - 1;
2163}
2164
2175 timefmt = fmt;
2176}
2177
2187 int ret;
2188 if ( !read_num_from_file( QUEUE_SIZE_PATH, &ret ) ) return -1;
2189 return ret;
2190}
2191
2202 int ret;
2203 if ( !read_num_from_file( INSTANCES_PATH, &ret ) ) return -1;
2204 return ret;
2205}
2206
2217 int ret;
2218 if ( !read_num_from_file( WATCHES_SIZE_PATH, &ret ) ) return -1;
2219 return ret;
2220}
2221
2235static int do_ignore_events_by_regex( char const *pattern, int flags, int invert ) {
2236 if (!pattern) {
2237 if (regex) {
2238 regfree(regex);
2239 free(regex);
2240 regex = 0;
2241 }
2242 return 1;
2243 }
2244
2245 if (regex) { regfree(regex); }
2246 else { regex = (regex_t *)malloc(sizeof(regex_t)); }
2247
2248 invert_regexp = invert;
2249 int ret = regcomp(regex, pattern, flags | REG_NOSUB);
2250 if (0 == ret) return 1;
2251
2252 regfree(regex);
2253 free(regex);
2254 regex = 0;
2255 error = EINVAL;
2256 return 0;
2257}
2258
2270int inotifytools_ignore_events_by_regex( char const *pattern, int flags ) {
2271 return do_ignore_events_by_regex(pattern, flags, 0);
2272}
2273
2285int inotifytools_ignore_events_by_inverted_regex( char const *pattern, int flags ) {
2286 return do_ignore_events_by_regex(pattern, flags, 1);
2287}
2288
2289int event_compare(const void *p1, const void *p2, const void *config)
2290{
2291 if (!p1 || !p2) return p1 - p2;
2292 char asc = 1;
2293 long sort_event = (long)config;
2294 if (sort_event == -1) {
2295 sort_event = 0;
2296 asc = 0;
2297 } else if (sort_event < 0) {
2298 sort_event = -sort_event;
2299 asc = 0;
2300 }
2301 unsigned int *i1 = stat_ptr((watch*)p1, sort_event);
2302 unsigned int *i2 = stat_ptr((watch*)p2, sort_event);
2303 if (0 == *i1 - *i2) {
2304 return ((watch*)p1)->wd - ((watch*)p2)->wd;
2305 }
2306 if (asc)
2307 return *i1 - *i2;
2308 else
2309 return *i2 - *i1;
2310}
2311
2312struct rbtree *inotifytools_wd_sorted_by_event(int sort_event)
2313{
2314 struct rbtree *ret = rbinit(event_compare, (void*)(uintptr_t)sort_event);
2315 RBLIST *all = rbopenlist(tree_wd);
2316 void const *p = rbreadlist(all);
2317 while (p) {
2318 void const *r = rbsearch(p, ret);
2319 niceassert((int)(r == p), "Couldn't insert watch into new tree");
2320 p = rbreadlist(all);
2321 }
2322 rbcloselist(all);
2323 return ret;
2324}
inotifytools library public interface.
int inotifytools_init(int fanotify, int watch_filesystem, int verbose)
Definition: inotifytools.c:299
const char * inotifytools_filename_from_wd(int wd)
Definition: inotifytools.c:874
int inotifytools_ignore_events_by_regex(char const *pattern, int flags)
int inotifytools_get_max_queued_events()
int inotifytools_watch_recursively_with_exclude(char const *path, int events, char const **exclude_list)
int inotifytools_remove_watch_by_filename(char const *filename)
int inotifytools_error()
int inotifytools_watch_recursively(char const *path, int events)
int inotifytools_wd_from_filename(char const *filename)
Definition: inotifytools.c:950
int inotifytools_fprintf(FILE *file, struct inotify_event *event, char *fmt)
int inotifytools_ignore_events_by_inverted_regex(char const *pattern, int flags)
void inotifytools_set_filename_by_filename(char const *oldname, char const *newname)
Definition: inotifytools.c:995
int inotifytools_remove_watch_by_wd(int wd)
char * inotifytools_event_to_str_sep(int events, char sep)
Definition: inotifytools.c:653
const char * inotifytools_dirname_from_event(struct inotify_event *event, size_t *dirnamelen)
Definition: inotifytools.c:893
int inotifytools_str_to_event_sep(char const *event, char sep)
Definition: inotifytools.c:463
void inotifytools_cleanup()
Definition: inotifytools.c:365
int inotifytools_watch_files(char const *filenames[], int events)
int inotifytools_snprintf(struct nstring *out, int size, struct inotify_event *event, char *fmt)
char * inotifytools_dirpath_from_event(struct inotify_event *event)
Definition: inotifytools.c:919
int inotifytools_get_max_user_instances()
struct inotify_event * inotifytools_next_event(long int timeout)
int inotifytools_get_num_watches()
char * inotifytools_event_to_str(int events)
Definition: inotifytools.c:625
int inotifytools_printf(struct inotify_event *event, char *fmt)
int inotifytools_str_to_event(char const *event)
Definition: inotifytools.c:538
void inotifytools_replace_filename(char const *oldname, char const *newname)
const char * inotifytools_filename_from_watch(struct watch *w)
Definition: inotifytools.c:845
int inotifytools_get_max_user_watches()
struct inotify_event * inotifytools_next_events(long int timeout, int num_events)
void inotifytools_set_printf_timefmt(char *fmt)
int inotifytools_sprintf(struct nstring *out, struct inotify_event *event, char *fmt)
void inotifytools_set_filename_by_wd(int wd, char const *filename)
Definition: inotifytools.c:973
int inotifytools_watch_file(char const *filename, int events)
This structure holds string that can contain any character including NULL.
Definition: inotifytools.h:25
unsigned int len
Definition: inotifytools.h:27
char buf[MAX_STRLEN]
Definition: inotifytools.h:26