#include #include #include #include #include #include #include #include #include #include "strlist.h" typedef struct path_status { char* path; bool existed; struct stat statbuf; // previous result of stat struct path_status* next; // linear list } PathStatus; typedef bool (*EventCheck)(PathStatus* ps, bool exists, struct stat* newbuf); bool appeared(PathStatus* ps, bool exists, struct stat* newbuf) { return !ps->existed && exists; } bool vanished(PathStatus* ps, bool exists, struct stat* newbuf) { return ps->existed && !exists; } bool grown(PathStatus* ps, bool exists, struct stat* newbuf) { return exists && (ps->statbuf.st_size < newbuf->st_size); } bool shrunken(PathStatus* ps, bool exists, struct stat* newbuf) { return exists && (ps->statbuf.st_size > newbuf->st_size); } bool modified(PathStatus* ps, bool exists, struct stat* newbuf) { return exists && (ps->statbuf.st_mtime < newbuf->st_mtime); } bool accessed(PathStatus* ps, bool exists, struct stat* newbuf) { return exists && (ps->statbuf.st_atime < newbuf->st_atime); } struct { char* eventName; EventCheck eventCheck; bool included; } events[] = { {"appeared", appeared, false}, {"vanished", vanished, false}, {"grown", grown, false}, {"shrunken", shrunken, false}, {"modified", modified, false}, {"accessed", accessed, false}, }; #define DIM(vec) ((sizeof(vec))/sizeof(vec[0])) char* cmdname; void usage() { fprintf(stderr, "Usage: %s [-i interval] {-e event} hook path...\n" " %s -l\n", cmdname, cmdname); exit(1); } void nomem() { fprintf(stderr, "%s: out of memory\n", cmdname); exit(1); } void list_events(void) { puts("all\t(watch for all supported events)"); for (int i = 0; i < DIM(events); ++i) { puts(events[i].eventName); } } void include_event(char* eventName) { if (strcmp(eventName, "all") == 0) { for (int i = 0; i < DIM(events); ++i) { events[i].included = true; } return; } for (int i = 0; i < DIM(events); ++i) { if (strcmp(eventName, events[i].eventName) == 0) { events[i].included = true; return; } } fprintf(stderr, "%s: unknown event: %s\n", cmdname, eventName); exit(1); } void update_status(char* filename, struct stat* statbuf, bool* exists) { if (lstat(filename, statbuf) < 0) { if (errno == ENOENT) { *exists = false; } else { fprintf(stderr, "%s: %s: %s\n", cmdname, filename, strerror(errno)); exit(1); } } else { *exists = true; } } int main(int argc, char* argv[]) { PathStatus* pathlist = 0; unsigned int interval = 1; char* hook; cmdname = *argv++; --argc; if (*argv && strcmp(*argv, "-l")==0) { list_events(); exit(0); } while (argc > 0 && **argv == '-') { switch (argv[0][1]) { case 'e': --argc; ++argv; if (argc == 0) usage(); include_event(*argv); --argc; ++argv; break; case 'i': --argc; ++argv; if (argc == 0) usage(); interval = atoi(*argv); if (interval == 0) usage(); --argc; ++argv; break; default: usage(); } } if (argc < 2) usage(); hook = *argv; ++argv; --argc; while (argc > 0) { PathStatus* ps = malloc(sizeof(PathStatus)); if (!ps) nomem(); ps->path = *argv; --argc; ++argv; update_status(ps->path, &ps->statbuf, &ps->existed); ps->next = pathlist; pathlist = ps; } for(;;) { sleep(interval); strlist argv = {0}; strlist_push(&argv, hook); bool anyevent = false; for (PathStatus* ps = pathlist; ps; ps = ps->next) { struct stat newbuf; bool exists; update_status(ps->path, &newbuf, &exists); for (int i = 0; i < DIM(events); ++i) { if (!events[i].included) continue; if (!events[i].eventCheck(ps, exists, &newbuf)) continue; anyevent = true; strlist_push(&argv, events[i].eventName); strlist_push(&argv, ps->path); } ps->statbuf = newbuf; ps->existed = exists; } if (anyevent) { pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { strlist_push(&argv, 0); execvp(hook, argv.list); perror(hook); exit(255); } } strlist_free(&argv); // collect terminated kids, if any while (waitpid(-1, NULL, WNOHANG) > 0) { continue; } } }