wendy

inotify based node watcher
git clone git://git.2f30.org/wendy
Log | Files | Refs | README | LICENSE

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 }