wendy.c (5074B)
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, 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 char * 51 basename(char *p) 52 { 53 char *b = strrchr(p, '/'); 54 return b ? b + 1 : p; 55 } 56 57 int 58 listevents(char **ev) 59 { 60 int i; 61 for (i=0; i<IN_ALL_EVENTS; i++) 62 if (ev[i]) 63 printf("%s\t%d\n", ev[i], i); 64 return 0; 65 } 66 67 struct watcher * 68 getwatcher(struct watchers *h, int wd) 69 { 70 struct watcher *tmp; 71 72 SLIST_FOREACH(tmp, h, entries) 73 if (tmp->wd == wd) 74 return tmp; 75 76 return NULL; 77 } 78 79 struct watcher * 80 watch(int fd, char *pathname, int mask) 81 { 82 size_t len; 83 struct watcher *w; 84 85 w = malloc(sizeof(*w)); 86 if (!w) 87 return NULL; 88 89 /* store inode path, eventually removing trailing '/' */ 90 len = strlcpy(w->path, pathname, PATH_MAX); 91 if (w->path[len - 1] == '/') 92 w->path[len - 1] = '\0'; 93 94 w->wd = inotify_add_watch(fd, w->path, mask); 95 if (w->wd < 0) { 96 /* triggered when dflag is set, so it is expected */ 97 if (errno != ENOTDIR) 98 perror(pathname); 99 100 free(w); 101 return NULL; 102 } 103 104 SLIST_INSERT_HEAD(&head, w, entries); 105 return w; 106 } 107 108 int 109 watchstream(int fd, FILE *stream, int mask) 110 { 111 size_t l, n = 0; 112 char *p = NULL; 113 114 while (getline(&p, &n, stream) > 0) { 115 l = strlen(p); 116 p[l-1] = '\0'; 117 watch(fd, p, mask); 118 } 119 free(p); 120 121 return 0; 122 } 123 124 char * 125 wdpath(struct inotify_event *e, struct watcher *w) 126 { 127 size_t len; 128 static char pathname[PATH_MAX]; 129 130 len = strlcpy(pathname, w->path, PATH_MAX); 131 if (e->len) { 132 strncat(pathname, "/", PATH_MAX - len - 1); 133 len = strnlen(pathname, PATH_MAX - 1); 134 strncat(pathname, e->name, PATH_MAX - len - 1); 135 } 136 137 return pathname; 138 } 139 140 141 int 142 main (int argc, char **argv) 143 { 144 int fd; 145 uint8_t buf[EVSZ]; 146 uint32_t mask = MASK; 147 ssize_t len, off = 0; 148 char **cmd, *argv0 = NULL; 149 struct watcher *w; 150 struct inotify_event *e; 151 152 /* get file descriptor */ 153 fd = inotify_init(); 154 if (fd < 0) 155 perror("inotify_init"); 156 157 ARGBEGIN { 158 case 'a': 159 aflag = 1; 160 break; 161 case 'd': 162 dflag = 1; 163 break; 164 case 'l': 165 lflag = 1; 166 break; 167 case 'r': 168 rflag = 1; 169 break; 170 case 'm': 171 mask = atoi(EARGF(usage(argv0))); 172 break; 173 case 'v': 174 verbose++; 175 break; 176 case 'w': 177 watch(fd, EARGF(usage(argv0)), mask); 178 break; 179 default: 180 usage(argv0); 181 } ARGEND; 182 183 if (lflag) { 184 listevents(evname); 185 return 0; 186 } 187 188 /* remaining arguments is the command to run on event */ 189 cmd = (argc > 0) ? argv : NULL; 190 191 /* ensure that only directories are watched for */ 192 if (dflag) 193 mask |= IN_ONLYDIR; 194 195 if (SLIST_EMPTY(&head)) 196 watchstream(fd, stdin, mask); 197 198 while (!SLIST_EMPTY(&head) && (off || (len=read(fd, buf, EVSZ))>0)) { 199 200 /* cast buffer into the event structure */ 201 e = (struct inotify_event *) (buf + off); 202 203 /* skip watch descriptors not in out list */ 204 if (!(w = getwatcher(&head, e->wd))) { 205 inotify_rm_watch(fd, e->wd); 206 goto skip; 207 } 208 209 /* skip hidden files when aflag is not set */ 210 if (!aflag && basename(wdpath(e, w))[0] == '.') 211 goto skip; 212 213 if (verbose && e->mask & IN_ALL_EVENTS) { 214 printf("%s\t%s\n", evname[e->mask & IN_ALL_EVENTS], wdpath(e, w)); 215 fflush(stdout); 216 } 217 218 /* orphan process */ 219 if (cmd) { 220 if (!fork()) { 221 if (!fork()) { 222 setenv("WENDY_INODE", wdpath(e, w), 1); 223 setenv("WENDY_EVENT", evname[e->mask & IN_ALL_EVENTS], 1); 224 execvp(cmd[0], cmd); 225 return -1; /* NOTREACHED */ 226 } 227 return 0; 228 } 229 wait(NULL); 230 } 231 232 switch(e->mask & (IN_ALL_EVENTS|IN_IGNORED)) { 233 case IN_CREATE: 234 /* Watch subdirectories upon creation */ 235 if (rflag) 236 watch(fd, wdpath(e, w), mask); 237 break; 238 239 /* 240 * IN_IGNORED is triggered when a file watched 241 * doesn't exists anymore. In this case we first try to 242 * watch it again (in case it was recreated), and if it 243 * fails, remove the watcher completely. 244 */ 245 case IN_IGNORED: 246 inotify_rm_watch(fd, e->wd); 247 if ((w->wd = inotify_add_watch(fd, w->path, mask)) < 0) { 248 SLIST_REMOVE(&head, w, watcher, entries); 249 free(w); 250 } 251 break; 252 } 253 254 skip: 255 /* shift buffer offset when there's more events to read */ 256 off += sizeof(*e) + e->len; 257 if (off >= len) 258 off = 0; 259 } 260 261 close(fd); 262 263 return 0; 264 }