1 #include "../config.h"
2 #include "../libinotifytools/src/inotifytools_p.h"
3 #include "common.h"
4 
5 #include <sys/select.h>
6 #include <sys/stat.h>
7 #include <sys/time.h>
8 #include <sys/types.h>
9 
10 #include <assert.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <getopt.h>
14 #if defined(__FreeBSD__) || defined(__DragonFly__)
15 #include <pthread.h>
16 #endif // __FreeBSD__
17 #include <limits.h>
18 #include <regex.h>
19 #include <signal.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include <inotifytools/inotify.h>
27 #include <inotifytools/inotifytools.h>
28 
29 extern char *optarg;
30 extern int optind, opterr, optopt;
31 
32 // METHODS
33 static bool parse_opts(int* argc,
34 		       char*** argv,
35 		       int* events,
36 		       unsigned int* timeout,
37 		       int* verbose,
38 		       int* zero,
39 		       int* sort,
40 		       int* recursive,
41 		       int* no_dereference,
42 		       char** fromfile,
43 		       char** exc_regex,
44 		       char** exc_iregex,
45 		       char** inc_regex,
46 		       char** inc_iregex,
47 		       int* fanotify,
48 		       bool* filesystem);
49 
50 void print_help();
51 
52 static bool done;
53 
handle_impatient_user(int signal)54 void handle_impatient_user(int signal __attribute__((unused))) {
55     static int times_called = 0;
56     if (times_called) {
57         fprintf(stderr, "No statistics collected, asked to abort before all "
58                         "watches could be established.\n");
59         exit(1);
60     }
61     fprintf(stderr,
62             "No statistics have been collected because I haven't "
63             "finished establishing\n"
64             "inotify watches yet.  If you are sure you want me to exit, "
65             "interrupt me again.\n");
66     ++times_called;
67 }
68 
handle_signal(int signal)69 void handle_signal(int signal __attribute__((unused))) {
70     done = true;
71 }
72 
73 int print_info();
74 
print_info_now(int signal)75 void print_info_now(int signal __attribute__((unused))) {
76     print_info();
77     printf("\n");
78 }
79 
80 int events;
81 int sort;
82 int zero;
83 
main(int argc,char ** argv)84 int main(int argc, char **argv) {
85     events = 0;
86     unsigned int timeout = BLOCKING_TIMEOUT;
87     int verbose = 0;
88     zero = 0;
89     int recursive = 0;
90     int fanotify = DEFAULT_FANOTIFY_MODE;
91     bool filesystem = false;
92     int no_dereference = 0;
93     char *fromfile = 0;
94     sort = -1;
95     done = false;
96     char *exc_regex = NULL;
97     char *exc_iregex = NULL;
98     char *inc_regex = NULL;
99     char *inc_iregex = NULL;
100     int rc;
101 
102 #if defined(__FreeBSD__) || defined(__DragonFly__)
103     sigset_t set, oset;
104 #endif // __FreeBSD__
105 
106     signal(SIGINT, handle_impatient_user);
107 
108 #if defined(__FreeBSD__) || defined(__DragonFly__)
109     // Block some signals in libinotify's worker thread, so that
110     // handle_signal runs in the context of the main thread and
111     // the 'done' flag is actually honored.
112     sigemptyset(&set);
113     sigaddset(&set, SIGINT);
114     sigaddset(&set, SIGHUP);
115     sigaddset(&set, SIGTERM);
116     sigaddset(&set, SIGALRM);
117     pthread_sigmask(SIG_BLOCK, &set, &oset);
118 #endif // __FreeBSD__
119 
120     // Parse commandline options, aborting if something goes wrong
121     if (!parse_opts(&argc, &argv, &events, &timeout, &verbose, &zero, &sort,
122 		    &recursive, &no_dereference, &fromfile, &exc_regex,
123 		    &exc_iregex, &inc_regex, &inc_iregex, &fanotify,
124 		    &filesystem)) {
125 	    return EXIT_FAILURE;
126     }
127 
128     if ((exc_regex &&
129          !inotifytools_ignore_events_by_regex(exc_regex, REG_EXTENDED)) ||
130         (exc_iregex &&
131          !inotifytools_ignore_events_by_regex(exc_iregex,
132                                               REG_EXTENDED | REG_ICASE))) {
133         fprintf(stderr, "Error in `exclude' regular expression.\n");
134         return EXIT_FAILURE;
135     }
136 
137     if ((inc_regex &&
138          !inotifytools_ignore_events_by_inverted_regex(inc_regex,
139                                                        REG_EXTENDED)) ||
140         (inc_iregex &&
141          !inotifytools_ignore_events_by_inverted_regex(
142              inc_iregex, REG_EXTENDED | REG_ICASE))) {
143         fprintf(stderr, "Error in `include' regular expression.\n");
144         return EXIT_FAILURE;
145     }
146 
147     rc = inotifytools_init(fanotify, filesystem, verbose);
148     if (!rc) {
149 	    warn_inotify_init_error(fanotify);
150 	    return EXIT_FAILURE;
151     }
152 
153 #if defined(__FreeBSD__) || defined(__DragonFly__)
154     pthread_sigmask(SIG_SETMASK, &oset, NULL);
155 #endif // __FreeBSD__
156 
157     // Attempt to watch file
158     // If events is still 0, make it all events.
159     if (!events)
160         events = IN_ALL_EVENTS;
161     if (no_dereference)
162         events = events | IN_DONT_FOLLOW;
163 
164     if (fanotify)
165 	    events |= IN_ISDIR;
166 
167     FileList list;
168     construct_path_list(argc, argv, fromfile, &list);
169 
170     if (0 == list.watch_files[0]) {
171         fprintf(stderr, "No files specified to watch!\n");
172 	goto failure;
173     }
174 
175     unsigned int num_watches = 0;
176     unsigned int status;
177     fprintf(stderr, "Establishing watches...\n");
178     for (int i = 0; list.watch_files[i]; ++i) {
179 	    char const* this_file = list.watch_files[i];
180 	    if (filesystem) {
181 		    fprintf(stderr, "Setting up filesystem watch on %s\n",
182 			    this_file);
183 		    if (!inotifytools_watch_files(list.watch_files, events)) {
184 			    fprintf(stderr,
185 				    "Couldn't add filesystem watch %s: %s\n",
186 				    this_file, strerror(inotifytools_error()));
187 			    goto failure;
188 		    }
189 		    break;
190 	    }
191 
192 	    if (recursive && verbose) {
193 		    fprintf(stderr, "Setting up watch(es) on %s\n", this_file);
194 	    }
195 
196 	    if (recursive) {
197 		    status = inotifytools_watch_recursively_with_exclude(
198 			this_file, events, list.exclude_files);
199 	    } else {
200 		    status = inotifytools_watch_file(this_file, events);
201 	    }
202 	    if (!status) {
203 		    if (inotifytools_error() == ENOSPC) {
204 			    const char* backend =
205 				fanotify ? "fanotify" : "inotify";
206 			    const char* resource =
207 				fanotify ? "marks" : "watches";
208 			    fprintf(stderr,
209 				    "Failed to watch %s; upper limit on %s %s "
210 				    "reached!\n",
211 				    this_file, backend, resource);
212 			    fprintf(stderr,
213 				    "Please increase the amount of %s %s "
214 				    "allowed per user via `/proc/sys/fs/%s/"
215 				    "max_user_%s'.\n",
216 				    backend, resource, backend, resource);
217 		    } else {
218 			    fprintf(stderr, "Failed to watch %s: %s\n",
219 				    this_file, strerror(inotifytools_error()));
220 		    }
221 
222 		    goto failure;
223 	    }
224 	    if (recursive && verbose) {
225 		    fprintf(stderr, "OK, %s is now being watched.\n",
226 			    this_file);
227 	    }
228     }
229     num_watches = inotifytools_get_num_watches();
230 
231     if (verbose) {
232 	    fprintf(stderr, "Total of %u watches.\n", num_watches);
233     }
234     fprintf(stderr,
235             "Finished establishing watches, now collecting statistics.\n");
236 
237     if (timeout && verbose) {
238 	    fprintf(stderr, "Will listen for events for %u seconds.\n",
239 		    timeout);
240     }
241 
242     signal(SIGINT, handle_signal);
243     signal(SIGHUP, handle_signal);
244     signal(SIGTERM, handle_signal);
245     if (timeout) {
246 	    signal(SIGALRM, handle_signal);
247 	    alarm(timeout);
248     } else {
249 	    alarm(UINT_MAX);
250     }
251 
252     signal(SIGUSR1, print_info_now);
253 
254     inotifytools_initialize_stats();
255     // Now wait till we get event
256     struct inotify_event *event;
257     char *moved_from = 0;
258 
259     do {
260         event = inotifytools_next_event(BLOCKING_TIMEOUT);
261         if (!event) {
262             if (!inotifytools_error()) {
263 		    goto timeout;
264 	    } else if (inotifytools_error() != EINTR) {
265 		    fprintf(stderr, "%s\n", strerror(inotifytools_error()));
266 
267 		    goto failure;
268 	    } else {
269 		    continue;
270 	    }
271 	}
272 
273 	// TODO: replace filename of renamed filesystem watch entries
274 	if (filesystem)
275 		continue;
276 
277 	// if we last had MOVED_FROM and don't currently have MOVED_TO,
278         // moved_from file must have been moved outside of tree - so unwatch it.
279         if (moved_from && !(event->mask & IN_MOVED_TO)) {
280             if (!inotifytools_remove_watch_by_filename(moved_from)) {
281                 fprintf(stderr, "Error removing watch on %s: %s\n", moved_from,
282                         strerror(inotifytools_error()));
283             }
284             free(moved_from);
285             moved_from = 0;
286         }
287 
288         if (recursive) {
289             if ((event->mask & IN_CREATE) ||
290                 (!moved_from && (event->mask & IN_MOVED_TO))) {
291                 // New file - if it is a directory, watch it
292 		char* new_file = inotifytools_dirpath_from_event(event);
293 		if (new_file && *new_file && isdir(new_file) &&
294 		    !inotifytools_watch_recursively(new_file, events)) {
295 			fprintf(stderr, "Couldn't watch new directory %s: %s\n",
296 				new_file, strerror(inotifytools_error()));
297 		}
298 		free(new_file);
299             } // IN_CREATE
300             else if (event->mask & IN_MOVED_FROM) {
301 		    moved_from = inotifytools_dirpath_from_event(event);
302 		    // if not watched...
303 		    if (inotifytools_wd_from_filename(moved_from) == -1) {
304 			    free(moved_from);
305 			    moved_from = 0;
306                 }
307             } // IN_MOVED_FROM
308             else if (event->mask & IN_MOVED_TO) {
309                 if (moved_from) {
310 			char* new_name = inotifytools_dirpath_from_event(event);
311 			inotifytools_replace_filename(moved_from, new_name);
312 			free(new_name);
313 			free(moved_from);
314 			moved_from = 0;
315                 } // moved_from
316             }
317         }
318 
319     } while (!done);
320 
321     free_list(argc, argv, &list);
322 
323     return print_info();
324 
325 failure:
326 	free_list(argc, argv, &list);
327 
328 	return EXIT_FAILURE;
329 
330 timeout:
331 	free_list(argc, argv, &list);
332 
333 	return EXIT_TIMEOUT;
334 }
335 
print_info()336 int print_info() {
337     if (!inotifytools_get_stat_total(0)) {
338         fprintf(stderr, "No events occurred.\n");
339         return EXIT_SUCCESS;
340     }
341 
342     // OK, go through the watches and print stats.
343     printf("total  ");
344     if ((IN_ACCESS & events) &&
345         (zero || inotifytools_get_stat_total(IN_ACCESS)))
346         printf("access  ");
347     if ((IN_MODIFY & events) &&
348         (zero || inotifytools_get_stat_total(IN_MODIFY)))
349         printf("modify  ");
350     if ((IN_ATTRIB & events) &&
351         (zero || inotifytools_get_stat_total(IN_ATTRIB)))
352         printf("attrib  ");
353     if ((IN_CLOSE_WRITE & events) &&
354         (zero || inotifytools_get_stat_total(IN_CLOSE_WRITE)))
355         printf("close_write  ");
356     if ((IN_CLOSE_NOWRITE & events) &&
357         (zero || inotifytools_get_stat_total(IN_CLOSE_NOWRITE)))
358         printf("close_nowrite  ");
359     if ((IN_OPEN & events) && (zero || inotifytools_get_stat_total(IN_OPEN)))
360         printf("open  ");
361     if ((IN_MOVED_FROM & events) &&
362         (zero || inotifytools_get_stat_total(IN_MOVED_FROM)))
363         printf("moved_from  ");
364     if ((IN_MOVED_TO & events) &&
365         (zero || inotifytools_get_stat_total(IN_MOVED_TO)))
366         printf("moved_to  ");
367     if ((IN_MOVE_SELF & events) &&
368         (zero || inotifytools_get_stat_total(IN_MOVE_SELF)))
369         printf("move_self  ");
370     if ((IN_CREATE & events) &&
371         (zero || inotifytools_get_stat_total(IN_CREATE)))
372         printf("create  ");
373     if ((IN_DELETE & events) &&
374         (zero || inotifytools_get_stat_total(IN_DELETE)))
375         printf("delete  ");
376     if ((IN_DELETE_SELF & events) &&
377         (zero || inotifytools_get_stat_total(IN_DELETE_SELF)))
378         printf("delete_self  ");
379     if ((IN_UNMOUNT & events) &&
380         (zero || inotifytools_get_stat_total(IN_UNMOUNT)))
381         printf("unmount  ");
382 
383     printf("filename\n");
384 
385     struct rbtree *tree = inotifytools_wd_sorted_by_event(sort);
386     RBLIST *rblist = rbopenlist(tree);
387     watch *w = (watch *)rbreadlist(rblist);
388 
389     while (w) {
390         if (!zero && !w->hit_total) {
391             w = (watch *)rbreadlist(rblist);
392             continue;
393         }
394         printf("%-5u  ", w->hit_total);
395         if ((IN_ACCESS & events) &&
396             (zero || inotifytools_get_stat_total(IN_ACCESS)))
397             printf("%-6u  ", w->hit_access);
398         if ((IN_MODIFY & events) &&
399             (zero || inotifytools_get_stat_total(IN_MODIFY)))
400             printf("%-6u  ", w->hit_modify);
401         if ((IN_ATTRIB & events) &&
402             (zero || inotifytools_get_stat_total(IN_ATTRIB)))
403             printf("%-6u  ", w->hit_attrib);
404         if ((IN_CLOSE_WRITE & events) &&
405             (zero || inotifytools_get_stat_total(IN_CLOSE_WRITE)))
406             printf("%-11u  ", w->hit_close_write);
407         if ((IN_CLOSE_NOWRITE & events) &&
408             (zero || inotifytools_get_stat_total(IN_CLOSE_NOWRITE)))
409             printf("%-13u  ", w->hit_close_nowrite);
410         if ((IN_OPEN & events) &&
411             (zero || inotifytools_get_stat_total(IN_OPEN)))
412             printf("%-4u  ", w->hit_open);
413         if ((IN_MOVED_FROM & events) &&
414             (zero || inotifytools_get_stat_total(IN_MOVED_FROM)))
415             printf("%-10u  ", w->hit_moved_from);
416         if ((IN_MOVED_TO & events) &&
417             (zero || inotifytools_get_stat_total(IN_MOVED_TO)))
418             printf("%-8u  ", w->hit_moved_to);
419         if ((IN_MOVE_SELF & events) &&
420             (zero || inotifytools_get_stat_total(IN_MOVE_SELF)))
421             printf("%-9u  ", w->hit_move_self);
422         if ((IN_CREATE & events) &&
423             (zero || inotifytools_get_stat_total(IN_CREATE)))
424             printf("%-6u  ", w->hit_create);
425         if ((IN_DELETE & events) &&
426             (zero || inotifytools_get_stat_total(IN_DELETE)))
427             printf("%-6u  ", w->hit_delete);
428         if ((IN_DELETE_SELF & events) &&
429             (zero || inotifytools_get_stat_total(IN_DELETE_SELF)))
430             printf("%-11u  ", w->hit_delete_self);
431         if ((IN_UNMOUNT & events) &&
432             (zero || inotifytools_get_stat_total(IN_UNMOUNT)))
433             printf("%-7u  ", w->hit_unmount);
434 
435 	printf("%s\n", inotifytools_filename_from_watch(w));
436 	w = (watch *)rbreadlist(rblist);
437     }
438     rbcloselist(rblist);
439     rbdestroy(tree);
440 
441     return EXIT_SUCCESS;
442 }
443 
parse_opts(int * argc,char *** argv,int * e,unsigned int * timeout,int * verbose,int * z,int * s,int * recursive,int * no_dereference,char ** fromfile,char ** exc_regex,char ** exc_iregex,char ** inc_regex,char ** inc_iregex,int * fanotify,bool * filesystem)444 static bool parse_opts(int* argc,
445 		       char*** argv,
446 		       int* e,
447 		       unsigned int* timeout,
448 		       int* verbose,
449 		       int* z,
450 		       int* s,
451 		       int* recursive,
452 		       int* no_dereference,
453 		       char** fromfile,
454 		       char** exc_regex,
455 		       char** exc_iregex,
456 		       char** inc_regex,
457 		       char** inc_iregex,
458 		       int* fanotify,
459 		       bool* filesystem) {
460 	assert(argc);
461 	assert(argv);
462 	assert(e);
463 	assert(timeout);
464 	assert(verbose);
465 	assert(z);
466 	assert(s);
467 	assert(recursive);
468 	assert(fanotify);
469 	assert(filesystem);
470 	assert(no_dereference);
471 	assert(fromfile);
472 	assert(exc_regex);
473 	assert(exc_iregex);
474 	assert(inc_regex);
475 	assert(inc_iregex);
476 
477 	// Settings for options
478 	int new_event;
479 	bool sort_set = false;
480 
481 	// Short options
482 	static const char opt_string[] = "hrPa:d:zve:t:IFS";
483 
484 	// Construct array
485 	static const struct option long_opts[] = {
486 	    {"help", no_argument, NULL, 'h'},
487 	    {"event", required_argument, NULL, 'e'},
488 	    {"timeout", required_argument, NULL, 't'},
489 	    {"verbose", no_argument, NULL, 'v'},
490 	    {"zero", no_argument, NULL, 'z'},
491 	    {"ascending", required_argument, NULL, 'a'},
492 	    {"descending", required_argument, NULL, 'd'},
493 	    {"recursive", no_argument, NULL, 'r'},
494 	    {"inotify", no_argument, NULL, 'I'},
495 	    {"fanotify", no_argument, NULL, 'F'},
496 	    {"filesystem", no_argument, NULL, 'S'},
497 	    {"no-dereference", no_argument, NULL, 'P'},
498 	    {"fromfile", required_argument, NULL, 'o'},
499 	    {"exclude", required_argument, NULL, 'c'},
500 	    {"excludei", required_argument, NULL, 'b'},
501 	    {"include", required_argument, NULL, 'j'},
502 	    {"includei", required_argument, NULL, 'k'},
503 	    {NULL, 0, 0, 0},
504 	};
505 
506 	// Get first option
507 	char curr_opt = getopt_long(*argc, *argv, opt_string, long_opts, NULL);
508 
509 	// While more options exist...
510 	while ((curr_opt != '?') && (curr_opt != (char)-1)) {
511 		switch (curr_opt) {
512 			// --help or -h
513 			case 'h':
514 				print_help();
515 				// Shouldn't process any further...
516 				return false;
517 				break;
518 
519 			// --verbose or -v
520 			case 'v':
521 				++(*verbose);
522 				break;
523 
524 			// --recursive or -r
525 			case 'r':
526 				++(*recursive);
527 				break;
528 
529 #ifdef ENABLE_FANOTIFY
530 			// --inotify or -I
531 			case 'I':
532 				(*fanotify) = 0;
533 				break;
534 
535 			// --fanotify or -F
536 			case 'F':
537 				(*fanotify) = 1;
538 				break;
539 
540 			// --filesystem or -S
541 			case 'S':
542 				(*filesystem) = true;
543 				(*fanotify) = 1;
544 				break;
545 #endif
546 
547 			case 'P':
548 				++(*no_dereference);
549 				break;
550 
551 			// --zero or -z
552 			case 'z':
553 				++(*z);
554 				break;
555 
556 			// --exclude
557 			case 'c':
558 				(*exc_regex) = optarg;
559 				break;
560 
561 			// --excludei
562 			case 'b':
563 				(*exc_iregex) = optarg;
564 				break;
565 
566 			// --include
567 			case 'j':
568 				(*inc_regex) = optarg;
569 				break;
570 
571 			// --includei
572 			case 'k':
573 				(*inc_iregex) = optarg;
574 				break;
575 
576 			// --fromfile
577 			case 'o':
578 				if (*fromfile) {
579 					fprintf(stderr,
580 						"Multiple --fromfile options "
581 						"given.\n");
582 					return false;
583 				}
584 				(*fromfile) = optarg;
585 				break;
586 
587 			// --timeout or -t
588 			case 't':
589 				if (!is_timeout_option_valid(timeout, optarg)) {
590 					return false;
591 				}
592 				break;
593 
594 			// --event or -e
595 			case 'e':
596 				// Get event mask from event string
597 				new_event = inotifytools_str_to_event(optarg);
598 
599 				// If optarg was invalid, abort
600 				if (new_event == -1) {
601 					fprintf(
602 					    stderr,
603 					    "'%s' is not a valid event!  Run "
604 					    "with the "
605 					    "'--help' option to see a list of "
606 					    "events.\n",
607 					    optarg);
608 					return false;
609 				}
610 
611 				// Add the new event to the event mask
612 				(*e) = ((*e) | new_event);
613 
614 				break;
615 
616 			// --ascending or -a
617 			case 'a':
618 				assert(optarg);
619 				if (sort_set) {
620 					fprintf(stderr,
621 						"Please specify -a or -d once "
622 						"only!\n");
623 					return false;
624 				}
625 
626 				if (0 == strcasecmp(optarg, "total")) {
627 					(*s) = 0;
628 				} else if (0 == strcasecmp(optarg, "move")) {
629 					fprintf(
630 					    stderr,
631 					    "Cannot sort by `move' event; "
632 					    "please use "
633 					    "`moved_from' or `moved_to'.\n");
634 					return false;
635 				} else if (0 == strcasecmp(optarg, "close")) {
636 					fprintf(stderr,
637 						"Cannot sort by `close' event; "
638 						"please use "
639 						"`close_write' or "
640 						"`close_nowrite'.\n");
641 					return false;
642 				} else {
643 					int event =
644 					    inotifytools_str_to_event(optarg);
645 
646 					// If optarg was invalid, abort
647 					if (event == -1) {
648 						fprintf(stderr,
649 							"'%s' is not a valid "
650 							"key for "
651 							"sorting!\n",
652 							optarg);
653 						return false;
654 					}
655 
656 					(*s) = event;
657 				}
658 				sort_set = true;
659 				break;
660 
661 			// --descending or -d
662 			case 'd':
663 				assert(optarg);
664 				if (sort_set) {
665 					fprintf(stderr,
666 						"Please specify -a or -d once "
667 						"only!\n");
668 					return false;
669 				}
670 
671 				if (0 == strcasecmp(optarg, "total")) {
672 					(*s) = -1;
673 				} else {
674 					int event =
675 					    inotifytools_str_to_event(optarg);
676 
677 					// If optarg was invalid, abort
678 					if (event == -1) {
679 						fprintf(stderr,
680 							"'%s' is not a valid "
681 							"key for "
682 							"sorting!\n",
683 							optarg);
684 						return false;
685 					}
686 
687 					(*s) = -event;
688 				}
689 				break;
690 		}
691 
692 		curr_opt =
693 		    getopt_long(*argc, *argv, opt_string, long_opts, NULL);
694 	}
695 
696 	(*argc) -= optind;
697 	*argv = &(*argv)[optind];
698 
699 	if ((*s) != 0 && (*s) != -1 &&
700 	    !(abs(*s) & ((*e) ? (*e) : IN_ALL_EVENTS))) {
701 		fprintf(stderr,
702 			"Can't sort by an event which isn't being watched "
703 			"for!\n");
704 		return false;
705 	}
706 
707 	if (*exc_regex && *exc_iregex) {
708 		fprintf(stderr,
709 			"--exclude and --excludei cannot both be specified.\n");
710 		return false;
711 	}
712 	if (*inc_regex && *inc_iregex) {
713 		fprintf(stderr,
714 			"--include and --includei cannot both be specified.\n");
715 		return false;
716 	}
717 	if ((*inc_regex && *exc_regex) || (*inc_regex && *exc_iregex) ||
718 	    (*inc_iregex && *exc_regex) || (*inc_iregex && *exc_iregex)) {
719 		fprintf(
720 		    stderr,
721 		    "include and exclude regexp cannot both be specified.\n");
722 		return false;
723 	}
724 
725 	// If ? returned, invalid option
726 	return (curr_opt != '?');
727 }
728 
729 #define TOOL_NAME TOOLS_PREFIX "watch"
730 
print_help()731 void print_help() {
732 	printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
733 	printf("Gather filesystem usage statistics using %s.\n", TOOL_NAME);
734 	printf("Usage: %s [ options ] file1 [ file2 ] [ ... ]\n", TOOL_NAME);
735 	printf("Options:\n");
736 	printf("\t-h|--help    \tShow this help text.\n");
737 	printf("\t-v|--verbose \tBe verbose.\n");
738 	printf(
739 	    "\t@<file>       \tExclude the specified file from being "
740 	    "watched.\n");
741 	printf(
742 	    "\t--fromfile <file>\n"
743 	    "\t\tRead files to watch from <file> or `-' for stdin.\n");
744 	printf(
745 	    "\t--exclude <pattern>\n"
746 	    "\t\tExclude all events on files matching the extended regular\n"
747 	    "\t\texpression <pattern>.\n");
748 	printf(
749 	    "\t--excludei <pattern>\n"
750 	    "\t\tLike --exclude but case insensitive.\n");
751 	printf(
752 	    "\t--include <pattern>\n"
753 	    "\t\tExclude all events on files except the ones\n"
754 	    "\t\tmatching the extended regular expression\n"
755 	    "\t\t<pattern>.\n");
756 	printf(
757 	    "\t--includei <pattern>\n"
758 	    "\t\tLike --include but case insensitive.\n");
759 	printf(
760 	    "\t-z|--zero\n"
761 	    "\t\tIn the final table of results, output rows and columns even\n"
762 	    "\t\tif they consist only of zeros (the default is to not output\n"
763 	    "\t\tthese rows and columns).\n");
764 	printf("\t-r|--recursive\tWatch directories recursively.\n");
765 #ifdef ENABLE_FANOTIFY
766 	printf("\t-I|--inotify\tWatch with inotify.\n");
767 	printf("\t-F|--fanotify\tWatch with fanotify.\n");
768 	printf("\t-S|--filesystem\tWatch entire filesystem with fanotify.\n");
769 #endif
770 	printf(
771 	    "\t-P|--no-dereference\n"
772 	    "\t\tDo not follow symlinks.\n");
773 	printf(
774 	    "\t-t|--timeout <seconds>\n"
775 	    "\t\tListen only for specified amount of time in seconds; if\n"
776 	    "\t\tomitted or zero, %s will execute until receiving an\n"
777 	    "\t\tinterrupt signal.\n",
778 	    TOOL_NAME);
779 	printf(
780 	    "\t-e|--event <event1> [ -e|--event <event2> ... ]\n"
781 	    "\t\tListen for specific event(s).  If omitted, all events are \n"
782 	    "\t\tlistened for.\n");
783 	printf(
784 	    "\t-a|--ascending <event>\n"
785 	    "\t\tSort ascending by a particular event, or `total'.\n");
786 	printf(
787 	    "\t-d|--descending <event>\n"
788 	    "\t\tSort descending by a particular event, or `total'.\n\n");
789 	printf("Exit status:\n");
790 	printf("\t%d  -  Exited normally.\n", EXIT_SUCCESS);
791 	printf("\t%d  -  Some error occurred.\n\n", EXIT_FAILURE);
792 	printf("Events:\n");
793 	print_event_descriptions();
794 }
795