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 }