/*The author disclaims copyright to this source code: Public domain. * Do whatever you want with it. * Contact: adhocify at quitesimple period org */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUF_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1) * 1024 #define STREQ(s1,s2) ( strcmp(s1,s2) == 0 ) struct watchlistentry { int ifd; char *path; bool isdir; struct watchlistentry *next; }; struct watchlistentry *watchlist_head = NULL; struct watchlistentry **watchlist = &watchlist_head; struct ignorelist { char *ignore; struct ignorelist *next; }; struct ignorelist *ignorelist_head = NULL; struct ignorelist **ignorelist_current = &ignorelist_head; /* Write once globals. Set from process_arguments*/ bool silent = false; bool noappend = false; bool fromstdin = false; bool forkbombcheck = true; bool daemonize = false; uint32_t mask = 0; char *prog = NULL; char *path_logfile = NULL; void *xmalloc(size_t size) { void *m = malloc(size); if(m == NULL) { perror("malloc"); exit(EXIT_FAILURE); } return m; } char *xstrdup(const char *s) { char *tmp = strdup(s); if(tmp == NULL) { perror("strdup"); exit(EXIT_FAILURE); } return tmp; } char *xrealpath(const char *path, char *resolved_path) { char *tmp = realpath(path, resolved_path); if(tmp == NULL) { perror("realpath"); exit(EXIT_FAILURE); } return tmp; } char *ndirname(const char *path) { if(path == NULL) return xstrdup("."); char *c = strdupa(path); return xstrdup(dirname(c)); } char *find_ifd_path(int ifd) { for(struct watchlistentry *lkp = watchlist_head; lkp != NULL; lkp = lkp->next) if(lkp->ifd == ifd) return lkp->path; return NULL; } bool is_ignored(const char *filename) { for(struct ignorelist *l = ignorelist_head; l != NULL; l = l->next) if(fnmatch(l->ignore, filename, 0) == 0) return true; return false; } bool path_is_directory(const char *path) { struct stat sb; int r = stat(path, &sb); if(r == -1) { perror("stat"); return false; } return S_ISDIR(sb.st_mode); } static inline bool file_exists(const char *path) { return access(path, F_OK) == 0; } void add_ignore_list(const char *str) { *ignorelist_current = xmalloc(sizeof(struct ignorelist)); (*ignorelist_current)->ignore = xstrdup(str); ignorelist_current = &(*ignorelist_current)->next; } void logwrite(const char *format, ...) { if(silent) return; va_list args; va_start(args, format); vfprintf(stdout, format, args); fflush(stdout); va_end(args); } void logerror(const char *format, ...) { va_list args; va_start(args, format); char *prefix = "Error: "; char *tmp = alloca(strlen(format) + strlen(prefix) + 1); strcpy(tmp, prefix); strcat(tmp, format); vfprintf(stderr, tmp, args); fflush(stderr); va_end(args); } void watchqueue_addpath(const char *pathname) { *watchlist = xmalloc(sizeof(struct watchlistentry)); struct watchlistentry *e = *watchlist; char *path = xrealpath(pathname, NULL); e->ifd = 0; e->path = path; e->isdir = path_is_directory(pathname); e->next = NULL; watchlist= &e->next; } void create_watches(int fd, uint32_t mask) { for(struct watchlistentry *lkp = watchlist_head; lkp != NULL; lkp = lkp->next) { int ret = inotify_add_watch(fd, lkp->path, mask); if(ret == -1) { perror("inotify_add_watch"); exit(EXIT_FAILURE); } lkp->ifd = ret; } } bool redirect_stdout(const char *outfile) { int fd = open(outfile, O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR); if(fd == -1) { perror("open"); return false; } if(dup2(fd, 1) == -1 || dup2(fd, 2) == -1) { perror("dup2"); return false; } return true; } bool run_prog(const char *eventfile, uint32_t eventmask) { pid_t pid = fork(); if(pid == 0) { if(path_logfile) { if(! redirect_stdout(path_logfile)) return false; } const char *argv0 = memrchr(prog, '/', strlen(prog)); argv0 = ( argv0 == NULL ) ? prog : argv0+1; char envvar[30]; snprintf(envvar, sizeof(envvar), "adhocifyevent=%"PRIu32, eventmask); putenv(envvar); execl(prog, argv0, (! noappend) ? eventfile : NULL, NULL); perror("execlp"); return false; } if(pid == -1) { perror("fork"); return false; } return true; } uint32_t nameToMask(const char *name) { if(STREQ(name, "IN_CLOSE_WRITE")) return IN_CLOSE_WRITE; else if(STREQ(name, "IN_OPEN")) return IN_OPEN; else if(STREQ(name, "IN_MODIFY")) return IN_MODIFY; else if(STREQ(name, "IN_DELETE")) return IN_DELETE; else if(STREQ(name, "IN_ATTRIB")) return IN_ATTRIB; else if(STREQ(name, "IN_CLOSE_NOWRITE")) return IN_CLOSE_NOWRITE; else if(STREQ(name, "IN_MOVED_FROM")) return IN_MOVED_FROM; else if(STREQ(name, "IN_MOVED_TO")) return IN_MOVED_TO; else if(STREQ(name, "IN_CREATE")) return IN_CREATE; else if(STREQ(name, "IN_DELETE_SELF")) return IN_DELETE_SELF; else if(STREQ(name, "IN_MOVE_SELF")) return IN_DELETE_SELF; else if(STREQ(name, "IN_ALL_EVENTS")) return IN_ALL_EVENTS; else if(STREQ(name, "IN_CLOSE")) return IN_CLOSE; else if(STREQ(name, "IN_MOVE")) return IN_MOVE; else return 0; } void check_forkbomb(const char *path_logfile, const char *path_prog) { char *dir_log = ndirname(path_logfile); char *dir_prog = ndirname(path_prog); struct watchlistentry *lkp = watchlist_head; while(lkp) { if(lkp->isdir) { char *dir_lkpPath = lkp->path; if( STREQ(dir_lkpPath, dir_log) || STREQ(dir_lkpPath, dir_prog) ) { logerror("Don't place your logfiles or prog in a directory you are watching for events. Pass -b to bypass this check.\n"); exit(EXIT_FAILURE); } } lkp = lkp->next; } free(dir_log); free(dir_prog); } void queue_watches_from_stdin() { char *line = NULL; size_t n = 0; ssize_t r; while((r = getline(&line, &n, stdin)) != -1) { if(line[r-1] == '\n') line[r-1] = 0; watchqueue_addpath(line); } } char *get_eventfile_abspath(struct inotify_event *event) { char *wdpath = find_ifd_path(event->wd); if(wdpath == NULL) return NULL; size_t nameLen = strlen(event->name); char *abspath = xmalloc((strlen(wdpath) + nameLen + 2) * sizeof(char)); strcpy(abspath, wdpath); if(nameLen > 0) { strcat(abspath, "/"); strcat(abspath, event->name); } return abspath; } void handle_event(struct inotify_event *event) { if(event->mask & mask) { char *eventfile_abspath = get_eventfile_abspath(event); if(eventfile_abspath == NULL) { logerror("Could not get absoulte path for event. Watch descriptor %i\n", event->wd); exit(EXIT_FAILURE); } if(is_ignored(eventfile_abspath)) { free(eventfile_abspath); return; } logwrite("Starting execution of child %s\n", prog); bool r = run_prog(eventfile_abspath, event->mask); if(!r) { logerror("Execution of child %s failed\n", prog); exit(EXIT_FAILURE); } fflush(stdout); fflush(stderr); free (eventfile_abspath); } } static inline char *get_cwd() { return getcwd(NULL,0); } void print_usage() { printf("adhocify [OPTIONS]script\n"); printf("--daemon, -d\t\t\tdaemonize\n"); printf("--path, -w\t\t\tpath -- adds the specified path to the watchlist\n"); printf("--logfile, -o\t\t\tlogfile -- output goes here\n"); printf("--mask, -m\t\t\tmaskval -- inotify mask value. Can be specified multiple times, will be ORed.\n"); printf("--no-env, -a\t\t\tif specified, the inotify event which occured won't be passed to the script as an envvar.\n"); printf("--silent, -q\t\t\tsilent\n"); printf("--stdin, -s\t\t\tRead the paths which must be added to the watchlist from stdin. Each path in a seperate line\n"); printf("--no-forkbomb-check, -b\t\tDisable fork bomb detection\n"); printf("--ignore, -i\t\t\tpattern -- Ignore events on files for which the pattern matches\n"); } static struct option long_options[] = { { "daemon", no_argument, 0, 'd' }, { "logfile", required_argument, 0, 'o' }, { "mask", required_argument, 0, 'm' }, { "path", required_argument, 0, 'w' }, { "no-env", no_argument, 0, 'a' }, { "stdin", no_argument, 0, 's' }, { "no-forkbomb-check", no_argument, 0, 'b' }, { "ignore", required_argument, 0, 'i' }, { "silent", no_argument, 0, 'q' }, { "help", no_argument, 0, 'h' } }; void parse_options(int argc, char **argv) { char *watchpath = NULL; int option; int option_index; uint32_t optmask = 0; while((option = getopt_long(argc, argv, "absdo:w:m:l:i:", long_options, &option_index)) != -1) { switch(option) { case 'd': daemonize = true; break; case 'o': path_logfile = optarg; break; case 'm': optmask = nameToMask(optarg); if(optmask == 0) { logerror("Not supported inotify event: %s\n", optmask); exit(EXIT_FAILURE); } mask |= optmask; break; case 'w': watchpath = optarg; watchqueue_addpath(watchpath); break; case 'a': noappend=true; break; case 's': fromstdin=true; break; case 'b': forkbombcheck=false; break; case 'i': add_ignore_list(optarg); break; case 'q': silent=true; break; case 'h': print_usage(); exit(EXIT_SUCCESS); break; } } if(optind >= argc) { logerror("missing prog/script path\n"); exit(EXIT_FAILURE); } prog = argv[optind]; } void process_options() { if(fromstdin) queue_watches_from_stdin(); if(daemonize) { if(daemon(0,0) == -1) { perror("daemon"); exit(EXIT_FAILURE); } } if(watchlist_head == NULL) watchqueue_addpath(get_cwd()); if(mask == 0) mask |= IN_CLOSE_WRITE; if(! prog || ! file_exists(prog)) { fprintf(stderr, "File %s does not exist", prog); exit(EXIT_FAILURE); } if(path_logfile) path_logfile = xrealpath(path_logfile, NULL); if(forkbombcheck) { char *path_prog = xrealpath(prog, NULL); check_forkbomb(path_logfile, path_prog); } } void start_monitoring(int ifd) { while(1) { int len; int offset =0; char buf[BUF_SIZE]; len = read(ifd, buf, BUF_SIZE); if(len == -1) { if(errno == EINTR) continue; perror("read"); exit(EXIT_FAILURE); } while(offset < len) { struct inotify_event *event = (struct inotify_event *)&buf[offset]; handle_event(event); offset+=sizeof(struct inotify_event) + event->len; } } } int main(int argc, char **argv) { if(argc < 2) { print_usage(); exit(EXIT_FAILURE); } signal(SIGCHLD, SIG_IGN); parse_options(argc, argv); process_options(); int ifd = inotify_init(); if(ifd == -1) { perror("inotify_init"); exit(EXIT_FAILURE); } create_watches(ifd, mask); start_monitoring(ifd); }