URI: 
       twendy.c - wendy - watch files/directories and run commands on any event
  HTML git clone git://z3bra.org/wendy
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       twendy.c (5255B)
       ---
            1 #include <errno.h>
            2 #include <stdio.h>
            3 #include <stdlib.h>
            4 #include <unistd.h>
            5 #include <limits.h>
            6 #include <string.h>
            7 #include <sys/inotify.h>
            8 #include <sys/wait.h>
            9 
           10 #include "arg.h"
           11 #include "queue.h"
           12 #include "strlcpy.h"
           13 
           14 #define EVSZ (sizeof(struct inotify_event) + NAME_MAX + 1)
           15 #define MASK (IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MODIFY|IN_MOVE|IN_MOVE_SELF|IN_CLOSE_WRITE)
           16 
           17 struct watcher {
           18         int wd;
           19         char path[PATH_MAX];
           20         SLIST_ENTRY(watcher) entries;
           21 };
           22 
           23 SLIST_HEAD(watchers, watcher) head;
           24 
           25 char *evname[IN_ALL_EVENTS] = {
           26         [IN_ACCESS] =        "ACCESS",
           27         [IN_MODIFY] =        "MODIFY",
           28         [IN_ATTRIB] =        "ATTRIB",
           29         [IN_CLOSE_WRITE] =   "CLOSE_WRITE",
           30         [IN_CLOSE_NOWRITE] = "CLOSE_NOWRITE",
           31         [IN_OPEN] =          "OPEN",
           32         [IN_MOVED_FROM] =    "MOVED_FROM",
           33         [IN_MOVED_TO] =      "MOVED_TO",
           34         [IN_CREATE] =        "CREATE",
           35         [IN_DELETE] =        "DELETE",
           36         [IN_DELETE_SELF] =   "DELETE_SELF",
           37         [IN_MOVE_SELF] =     "MOVE_SELF",
           38 };
           39 
           40 int verbose = 0;
           41 int aflag = 0, cflag = 0, dflag = 0, lflag = 0, rflag = 0;
           42 
           43 void
           44 usage(char *name)
           45 {
           46         fprintf(stderr, "usage: %s [-adrv] [-m mask] [-w file] [command [args...]]\n", name);
           47         exit(1);
           48 }
           49 
           50 void
           51 clrscr()
           52 {
           53         /* hardcoded clear screen sequence for VT100 */
           54         printf("\x1b[H\x1b[J");
           55         fflush(stdout);
           56 }
           57 
           58 char *
           59 basename(char *p)
           60 {
           61         char *b = strrchr(p, '/');
           62         return b ? b + 1 : p;
           63 }
           64 
           65 int
           66 listevents(char **ev)
           67 {
           68         int i;
           69         for (i=0; i<IN_ALL_EVENTS; i++)
           70                 if (ev[i])
           71                         printf("%s\t%d\n", ev[i], i);
           72         return 0;
           73 }
           74 
           75 struct watcher *
           76 getwatcher(struct watchers *h, int wd)
           77 {
           78         struct watcher *tmp;
           79 
           80         SLIST_FOREACH(tmp, h, entries)
           81                 if (tmp->wd == wd)
           82                         return tmp;
           83 
           84         return NULL;
           85 }
           86 
           87 struct watcher *
           88 watch(int fd, char *pathname, int mask)
           89 {
           90         size_t len;
           91         struct watcher *w;
           92 
           93         w = malloc(sizeof(*w));
           94         if (!w)
           95                 return NULL;
           96 
           97         /* store inode path, eventually removing trailing '/' */
           98         len = strlcpy(w->path, pathname, PATH_MAX);
           99         if (w->path[len - 1] == '/')
          100                 w->path[len - 1] = '\0';
          101 
          102         w->wd = inotify_add_watch(fd, w->path, mask);
          103         if (w->wd < 0) {
          104                 /* triggered when dflag is set, so it is expected */
          105                 if (errno != ENOTDIR)
          106                         perror(pathname);
          107 
          108                 free(w);
          109                 return NULL;
          110         }
          111 
          112         SLIST_INSERT_HEAD(&head, w, entries);
          113         return w;
          114 }
          115 
          116 int
          117 watchstream(int fd, FILE *stream, int mask)
          118 {
          119         size_t l, n = 0;
          120         char *p = NULL;
          121 
          122         while (getline(&p, &n, stream) > 0) {
          123                 l = strlen(p);
          124                 p[l-1] = '\0';
          125                 watch(fd, p, mask);
          126         }
          127         free(p);
          128 
          129         return 0;
          130 }
          131 
          132 char *
          133 wdpath(struct inotify_event *e, struct watcher *w)
          134 {
          135         size_t len;
          136         static char pathname[PATH_MAX];
          137 
          138         len = strlcpy(pathname, w->path, PATH_MAX);
          139         if (e->len) {
          140                 strncat(pathname, "/", PATH_MAX - len - 1);
          141                 len = strnlen(pathname, PATH_MAX - 1);
          142                 strncat(pathname, e->name, PATH_MAX - len - 1);
          143         }
          144 
          145         return pathname;
          146 }
          147 
          148 
          149 int
          150 main (int argc, char **argv)
          151 {
          152         int fd;
          153         uint8_t buf[EVSZ];
          154         uint32_t mask = MASK;
          155         ssize_t len, off = 0;
          156         char **cmd, *argv0 = NULL;
          157         struct watcher *w;
          158         struct inotify_event *e;
          159 
          160         /* get file descriptor */
          161         fd = inotify_init();
          162         if (fd < 0)
          163                 perror("inotify_init");
          164 
          165         ARGBEGIN {
          166         case 'a':
          167                 aflag = 1;
          168                 break;
          169         case 'c':
          170                 cflag = 1;
          171                 break;
          172         case 'd':
          173                 dflag = 1;
          174                 break;
          175         case 'l':
          176                 lflag = 1;
          177                 break;
          178         case 'r':
          179                 rflag = 1;
          180                 break;
          181         case 'm':
          182                 mask = atoi(EARGF(usage(argv0)));
          183                 break;
          184         case 'v':
          185                 verbose++;
          186                 break;
          187         case 'w':
          188                 watch(fd, EARGF(usage(argv0)), mask);
          189                 break;
          190         default:
          191                 usage(argv0);
          192         } ARGEND;
          193 
          194         if (lflag) {
          195                 listevents(evname);
          196                 return 0;
          197         }
          198 
          199         /* remaining arguments is the command to run on event */
          200         cmd = (argc > 0) ? argv : NULL;
          201 
          202         /* ensure that only directories are watched for */
          203         if (dflag)
          204                 mask |= IN_ONLYDIR;
          205 
          206         if (SLIST_EMPTY(&head))
          207                 watchstream(fd, stdin, mask);
          208 
          209         while (!SLIST_EMPTY(&head) && (off || (len=read(fd, buf, EVSZ))>0)) {
          210 
          211                 /* cast buffer into the event structure */
          212                 e = (struct inotify_event *) (buf + off);
          213 
          214                 /* skip watch descriptors not in out list */
          215                 if (!(w = getwatcher(&head, e->wd))) {
          216                         inotify_rm_watch(fd, e->wd);
          217                         goto skip;
          218                 }
          219 
          220                 /* skip hidden files when aflag is not set */
          221                 if (!aflag && basename(wdpath(e, w))[0] == '.')
          222                         goto skip;
          223 
          224                 if (cflag)
          225                         clrscr();
          226 
          227                 if (verbose && e->mask & IN_ALL_EVENTS) {
          228                         printf("%s\t%s\n", evname[e->mask & IN_ALL_EVENTS], wdpath(e, w));
          229                         fflush(stdout);
          230                 }
          231 
          232                 /* orphan process */
          233                 if (cmd) {
          234                         if (!fork()) {
          235                                 if (!fork()) {
          236                                         setenv("WENDY_INODE", wdpath(e, w), 1);
          237                                         setenv("WENDY_EVENT", evname[e->mask & IN_ALL_EVENTS], 1);
          238                                         execvp(cmd[0], cmd);
          239                                         return -1; /* NOTREACHED */
          240                                 }
          241                                 return 0;
          242                         }
          243                         wait(NULL);
          244                 }
          245 
          246                 switch(e->mask & (IN_ALL_EVENTS|IN_IGNORED)) {
          247                 case IN_CREATE:
          248                         /* Watch subdirectories upon creation */
          249                         if (rflag)
          250                                 watch(fd, wdpath(e, w), mask);
          251                         break;
          252 
          253                 /*
          254                  * IN_IGNORED is triggered when a file watched
          255                  * doesn't exists anymore. In this case we first try to
          256                  * watch it again (in case it was recreated), and if it
          257                  * fails, remove the watcher completely.
          258                  */
          259                 case IN_IGNORED:
          260                         inotify_rm_watch(fd, e->wd);
          261                         if ((w->wd = inotify_add_watch(fd, w->path, mask)) < 0) {
          262                                 SLIST_REMOVE(&head, w, watcher, entries);
          263                                 free(w);
          264                         }
          265                         break;
          266                 }
          267 
          268 skip:
          269                 /* shift buffer offset when there's more events to read */
          270                 off += sizeof(*e) + e->len;
          271                 if (off >= len)
          272                         off = 0;
          273         }
          274 
          275         close(fd);
          276 
          277         return 0;
          278 }