summaryrefslogtreecommitdiff
path: root/src/inotifywait.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/inotifywait.c')
-rw-r--r--src/inotifywait.c288
1 files changed, 216 insertions, 72 deletions
diff --git a/src/inotifywait.c b/src/inotifywait.c
index 9c031bc..98aadd4 100644
--- a/src/inotifywait.c
+++ b/src/inotifywait.c
@@ -1,33 +1,31 @@
-// kate: replace-tabs off; space-indent off;
-
#include "../config.h"
#include "common.h"
-#include <stdbool.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
-#include <fcntl.h>
-#include <errno.h>
+
#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
#include <inotifytools/inotifytools.h>
#include <inotifytools/inotify.h>
-#include <getopt.h>
-
extern char *optarg;
extern int optind, opterr, optopt;
-#define EXIT_OK 0
-#define EXIT_ERROR 1
-#define EXIT_TIMEOUT 2
-
#define MAX_STRLEN 4096
#define EXCLUDE_CHUNK 1024
@@ -43,9 +41,12 @@ bool parse_opts(
unsigned long int * timeout,
int * recursive,
bool * csv,
+ bool * daemon,
+ bool * syslog,
char ** format,
char ** timefmt,
char ** fromfile,
+ char ** outfile,
char ** regex,
char ** iregex
);
@@ -53,7 +54,6 @@ bool parse_opts(
void print_help();
-
char * csv_escape( char * string ) {
static char csv[MAX_STRLEN+1];
static unsigned int i, ind;
@@ -89,14 +89,13 @@ char * csv_escape( char * string ) {
}
-
void validate_format( char * fmt ) {
// Make a fake event
struct inotify_event * event =
(struct inotify_event *)malloc(sizeof(struct inotify_event) + 4);
if ( !event ) {
fprintf( stderr, "Seem to be out of memory... yikes!\n" );
- exit(EXIT_ERROR);
+ exit(EXIT_FAILURE);
}
event->wd = 0;
event->mask = IN_ALL_EVENTS;
@@ -110,7 +109,7 @@ void validate_format( char * fmt ) {
}
if ( -1 == inotifytools_fprintf( devnull, event, fmt ) ) {
fprintf( stderr, "Something is wrong with your format string.\n" );
- exit(EXIT_ERROR);
+ exit(EXIT_FAILURE);
}
free( event );
fclose(devnull);
@@ -118,7 +117,10 @@ void validate_format( char * fmt ) {
void output_event_csv( struct inotify_event * event ) {
- printf("%s,", csv_escape( inotifytools_filename_from_wd( event->wd )));
+ char *filename = csv_escape(inotifytools_filename_from_wd(event->wd));
+ if (filename != NULL)
+ printf("%s,", csv_escape(filename));
+
printf("%s,", csv_escape( inotifytools_event_to_str( event->mask ) ) );
if ( event->len > 0 )
printf("%s", csv_escape( event->name ) );
@@ -126,25 +128,43 @@ void output_event_csv( struct inotify_event * event ) {
}
+void output_error( bool syslog, char* fmt, ... ) {
+ va_list va;
+ va_start(va, fmt);
+ if ( syslog ) {
+ vsyslog(LOG_INFO, fmt, va);
+ } else {
+ vfprintf(stderr, fmt, va);
+ }
+ va_end(va);
+}
+
+
int main(int argc, char ** argv)
{
int events = 0;
+ int orig_events;
bool monitor = false;
int quiet = 0;
unsigned long int timeout = 0;
int recursive = 0;
bool csv = false;
+ bool daemon = false;
+ bool syslog = false;
char * format = NULL;
char * timefmt = NULL;
char * fromfile = NULL;
+ char * outfile = NULL;
char * regex = NULL;
char * iregex = NULL;
+ pid_t pid;
+ int fd;
// Parse commandline options, aborting if something goes wrong
if ( !parse_opts(&argc, &argv, &events, &monitor, &quiet, &timeout,
- &recursive, &csv, &format, &timefmt, &fromfile,
- &regex, &iregex) ) {
- return EXIT_ERROR;
+ &recursive, &csv, &daemon, &syslog, &format, &timefmt,
+ &fromfile, &outfile, &regex, &iregex) ) {
+ return EXIT_FAILURE;
}
if ( !inotifytools_initialize() ) {
@@ -155,7 +175,7 @@ int main(int argc, char ** argv)
"something mysterious has gone wrong. Please e-mail "
PACKAGE_BUGREPORT "\n"
" and mention that you saw this message.\n");
- return EXIT_ERROR;
+ return EXIT_FAILURE;
}
if ( timefmt ) inotifytools_set_printf_timefmt( timefmt );
@@ -165,7 +185,7 @@ int main(int argc, char ** argv)
REG_ICASE))
) {
fprintf(stderr, "Error in `exclude' regular expression.\n");
- return EXIT_ERROR;
+ return EXIT_FAILURE;
}
@@ -175,21 +195,96 @@ int main(int argc, char ** argv)
// If events is still 0, make it all events.
if (events == 0)
events = IN_ALL_EVENTS;
+ orig_events = events;
+ if ( monitor && recursive ) {
+ events = events | IN_CREATE | IN_MOVED_TO | IN_MOVED_FROM;
+ }
FileList list = construct_path_list( argc, argv, fromfile );
if (0 == list.watch_files[0]) {
fprintf(stderr, "No files specified to watch!\n");
- return EXIT_ERROR;
+ return EXIT_FAILURE;
}
+
+ // Daemonize - BSD double-fork approach
+ if ( daemon ) {
+
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "Failed to fork1 whilst daemonizing!\n");
+ return EXIT_FAILURE;
+ }
+ if (pid > 0) {
+ _exit(0);
+ }
+ if (setsid() < 0) {
+ fprintf(stderr, "Failed to setsid whilst daemonizing!\n");
+ return EXIT_FAILURE;
+ }
+ signal(SIGHUP,SIG_IGN);
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "Failed to fork2 whilst daemonizing!\n");
+ return EXIT_FAILURE;
+ }
+ if (pid > 0) {
+ _exit(0);
+ }
+ if (chdir("/") < 0) {
+ fprintf(stderr, "Failed to chdir whilst daemonizing!\n");
+ return EXIT_FAILURE;
+ }
+
+ // Redirect stdin from /dev/null
+ fd = open("/dev/null", O_RDONLY);
+ if (fd != fileno(stdin)) {
+ dup2(fd, fileno(stdin));
+ close(fd);
+ }
+
+ // Redirect stdout to a file
+ fd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+ if (fd < 0) {
+ fprintf( stderr, "Failed to open output file %s\n", outfile );
+ return EXIT_FAILURE;
+ }
+ if (fd != fileno(stdout)) {
+ dup2(fd, fileno(stdout));
+ close(fd);
+ }
+
+ // Redirect stderr to /dev/null
+ fd = open("/dev/null", O_WRONLY);
+ if (fd != fileno(stderr)) {
+ dup2(fd, fileno(stderr));
+ close(fd);
+ }
+
+ } else if (outfile != NULL) { // Redirect stdout to a file if specified
+ fd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, 0600);
+ if (fd < 0) {
+ fprintf( stderr, "Failed to open output file %s\n", outfile );
+ return EXIT_FAILURE;
+ }
+ if (fd != fileno(stdout)) {
+ dup2(fd, fileno(stdout));
+ close(fd);
+ }
+ }
+
+ if ( syslog ) {
+ openlog ("inotifywait", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ }
+
if ( !quiet ) {
- fprintf( stderr, "Setting up watches. " );
if ( recursive ) {
- fprintf( stderr, "Beware: since -r was given, this may take a "
- "while!" );
+ output_error( syslog, "Setting up watches. Beware: since -r "
+ "was given, this may take a while!\n" );
+ } else {
+ output_error( syslog, "Setting up watches.\n" );
}
- fprintf( stderr, "\n" );
}
// now watch files
@@ -201,22 +296,22 @@ int main(int argc, char ** argv)
list.exclude_files ))
|| (!recursive && !inotifytools_watch_file( this_file, events )) ){
if ( inotifytools_error() == ENOSPC ) {
- fprintf(stderr, "Failed to watch %s; upper limit on inotify "
+ output_error( syslog, "Failed to watch %s; upper limit on inotify "
"watches reached!\n", this_file );
- fprintf(stderr, "Please increase the amount of inotify watches "
+ output_error( syslog, "Please increase the amount of inotify watches "
"allowed per user via `/proc/sys/fs/inotify/"
"max_user_watches'.\n");
}
else {
- fprintf(stderr, "Couldn't watch %s: %s\n", this_file,
+ output_error( syslog, "Couldn't watch %s: %s\n", this_file,
strerror( inotifytools_error() ) );
}
- return EXIT_ERROR;
+ return EXIT_FAILURE;
}
}
if ( !quiet ) {
- fprintf( stderr, "Watches established.\n" );
+ output_error( syslog, "Watches established.\n" );
}
// Now wait till we get event
@@ -230,12 +325,12 @@ int main(int argc, char ** argv)
return EXIT_TIMEOUT;
}
else {
- fprintf(stderr, "%s\n", strerror( inotifytools_error() ) );
- return EXIT_ERROR;
+ output_error( syslog, "%s\n", strerror( inotifytools_error() ) );
+ return EXIT_FAILURE;
}
}
- if ( quiet < 2 ) {
+ if ( quiet < 2 && (event->mask & orig_events) ) {
if ( csv ) {
output_event_csv( event );
}
@@ -251,7 +346,7 @@ int main(int argc, char ** argv)
// moved_from file must have been moved outside of tree - so unwatch it.
if ( moved_from && !(event->mask & IN_MOVED_TO) ) {
if ( !inotifytools_remove_watch_by_filename( moved_from ) ) {
- fprintf( stderr, "Error removing watch on %s: %s\n",
+ output_error( syslog, "Error removing watch on %s: %s\n",
moved_from, strerror(inotifytools_error()) );
}
free( moved_from );
@@ -270,7 +365,7 @@ int main(int argc, char ** argv)
if ( isdir(new_file) &&
!inotifytools_watch_recursively( new_file, events ) ) {
- fprintf( stderr, "Couldn't watch new directory %s: %s\n",
+ output_error( syslog, "Couldn't watch new directory %s: %s\n",
new_file, strerror( inotifytools_error() ) );
}
free( new_file );
@@ -305,15 +400,13 @@ int main(int argc, char ** argv)
// If we weren't trying to listen for this event...
if ( (events & event->mask) == 0 ) {
// ...then most likely something bad happened, like IGNORE etc.
- return EXIT_ERROR;
+ return EXIT_FAILURE;
}
- return EXIT_OK;
+ return EXIT_SUCCESS;
}
-
-
bool parse_opts(
int * argc,
char *** argv,
@@ -323,21 +416,25 @@ bool parse_opts(
unsigned long int * timeout,
int * recursive,
bool * csv,
+ bool * daemon,
+ bool * syslog,
char ** format,
char ** timefmt,
char ** fromfile,
+ char ** outfile,
char ** regex,
char ** iregex
) {
assert( argc ); assert( argv ); assert( events ); assert( monitor );
- assert( quiet ); assert( timeout ); assert( csv ); assert( format );
- assert( timefmt ); assert( fromfile ); assert( regex ); assert( iregex );
+ assert( quiet ); assert( timeout ); assert( csv ); assert( daemon );
+ assert( syslog ); assert( format ); assert( timefmt ); assert( fromfile );
+ assert( outfile ); assert( regex ); assert( iregex );
// Short options
- char * opt_string = "mrhcqt:fe:";
+ char * opt_string = "mrhcdsqt:fo:e:";
// Construct array
- struct option long_opts[14];
+ struct option long_opts[17];
// --help
long_opts[0].name = "help";
@@ -381,38 +478,53 @@ bool parse_opts(
long_opts[7].has_arg = 0;
long_opts[7].flag = NULL;
long_opts[7].val = (int)'c';
- // --format
- long_opts[8].name = "format";
- long_opts[8].has_arg = 1;
+ // --daemon
+ long_opts[8].name = "daemon";
+ long_opts[8].has_arg = 0;
long_opts[8].flag = NULL;
- long_opts[8].val = (int)'n';
- // format with trailing newline
- static char * newlineformat;
- // --timefmt
- long_opts[9].name = "timefmt";
- long_opts[9].has_arg = 1;
+ long_opts[8].val = (int)'d';
+ // --syslog
+ long_opts[9].name = "syslog";
+ long_opts[9].has_arg = 0;
long_opts[9].flag = NULL;
- long_opts[9].val = (int)'i';
- // --fromfile
- long_opts[10].name = "fromfile";
+ long_opts[9].val = (int)'s';
+ // --format
+ long_opts[10].name = "format";
long_opts[10].has_arg = 1;
long_opts[10].flag = NULL;
- long_opts[10].val = (int)'z';
- // --exclude
- long_opts[11].name = "exclude";
+ long_opts[10].val = (int)'n';
+ // format with trailing newline
+ static char * newlineformat;
+ // --timefmt
+ long_opts[11].name = "timefmt";
long_opts[11].has_arg = 1;
long_opts[11].flag = NULL;
- long_opts[11].val = (int)'a';
- // --excludei
- long_opts[12].name = "excludei";
+ long_opts[11].val = (int)'i';
+ // --fromfile
+ long_opts[12].name = "fromfile";
long_opts[12].has_arg = 1;
long_opts[12].flag = NULL;
- long_opts[12].val = (int)'b';
+ long_opts[12].val = (int)'z';
+ // --outfile
+ long_opts[13].name = "outfile";
+ long_opts[13].has_arg = 1;
+ long_opts[13].flag = NULL;
+ long_opts[13].val = (int)'o';
+ // --exclude
+ long_opts[14].name = "exclude";
+ long_opts[14].has_arg = 1;
+ long_opts[14].flag = NULL;
+ long_opts[14].val = (int)'a';
+ // --excludei
+ long_opts[15].name = "excludei";
+ long_opts[15].has_arg = 1;
+ long_opts[15].flag = NULL;
+ long_opts[15].val = (int)'b';
// Empty last element
- long_opts[13].name = 0;
- long_opts[13].has_arg = 0;
- long_opts[13].flag = 0;
- long_opts[13].val = 0;
+ long_opts[16].name = 0;
+ long_opts[16].has_arg = 0;
+ long_opts[16].flag = 0;
+ long_opts[16].val = 0;
// Get first option
char curr_opt = getopt_long(*argc, *argv, opt_string, long_opts, NULL);
@@ -449,6 +561,18 @@ bool parse_opts(
(*csv) = true;
break;
+ // --daemon or -d
+ case 'd':
+ (*daemon) = true;
+ (*monitor) = true;
+ (*syslog) = true;
+ break;
+
+ // --syslog or -s
+ case 's':
+ (*syslog) = true;
+ break;
+
// --filename or -f
case 'f':
fprintf(stderr, "The '--filename' option no longer exists. "
@@ -489,6 +613,15 @@ bool parse_opts(
(*fromfile) = optarg;
break;
+ // --outfile
+ case 'o':
+ if (*outfile) {
+ fprintf(stderr, "Multiple --outfile options given.\n");
+ return false;
+ }
+ (*outfile) = optarg;
+ break;
+
// --timeout or -t
case 't':
*timeout = strtoul(optarg, &timeout_end, 10);
@@ -554,6 +687,11 @@ bool parse_opts(
return false;
}
+ if ( *daemon && *outfile == NULL ) {
+ fprintf(stderr, "-o must be specified with -d.\n");
+ return false;
+ }
+
(*argc) -= optind;
*argv = &(*argv)[optind];
@@ -580,10 +718,16 @@ void print_help()
printf("\t-m|--monitor \tKeep listening for events forever. Without\n"
"\t \tthis option, inotifywait will exit after one\n"
"\t \tevent is received.\n");
+ printf("\t-d|--daemon \tSame as --monitor, except run in the background\n"
+ "\t \tlogging events to a file specified by --outfile.\n"
+ "\t \tImplies --syslog.\n");
printf("\t-r|--recursive\tWatch directories recursively.\n");
printf("\t--fromfile <file>\n"
"\t \tRead files to watch from <file> or `-' for "
"stdin.\n");
+ printf("\t-o|--outfile <file>\n"
+ "\t \tPrint events to <file> rather than stdout.\n");
+ printf("\t-s|--syslog \tSend errors to syslog rather than stderr.\n");
printf("\t-q|--quiet \tPrint less (only print events).\n");
printf("\t-qq \tPrint nothing (not even events).\n");
printf("\t--format <fmt>\tPrint using a specified printf-like format\n"
@@ -602,9 +746,9 @@ void print_help()
"\t\tlistened for.\n\n");
printf("Exit status:\n");
printf("\t%d - An event you asked to watch for was received.\n",
- EXIT_OK );
+ EXIT_SUCCESS );
printf("\t%d - An event you did not ask to watch for was received\n",
- EXIT_ERROR);
+ EXIT_FAILURE);
printf("\t (usually delete_self or unmount), or some error "
"occurred.\n");
printf("\t%d - The --timeout option was given and no events occurred\n",