1 /*
2 ###########################################################################
3 ### wait_on : Enable shell scripts to monitor directories for new files ###
4 ###########################################################################
5
6 Copyright 2002 Andrew Stevenson <andrew@ugh.net.au>
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10
11 1. Redistributions of source code must retain the above copyright notice,
12 this list of conditions and the following disclaimer.
13
14 2. Redistributions in binary form must reproduce the above copyright
15 notice, this list of conditions and the following disclaimer in the
16 documentation and/or other materials provided with the distribution.
17
18 3. Neither the name of the copyright holder nor the names of its
19 contributors may be used to endorse or promote products derived from this
20 software without specific prior written permission.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 POSSIBILITY OF SUCH DAMAGE.
33
34 ###########################################################################
35 That said I'd appreciate a message if you use this software in anyway.
36 Andrew <andrew@ugh.net.au>
37 ###########################################################################
38 */
39
40 #ifndef lint
41 static const char rcsid[] =
42 "$Id: wait_on.c,v 1.4 2003/05/31 16:20:32 andrew Exp $";
43 #endif
44
45 #include <sys/types.h>
46 #include <sys/event.h>
47 #include <sys/time.h>
48
49 #include <err.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <limits.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <sysexits.h>
57 #include <unistd.h>
58
59 #include "wait_on.h"
60
61 static void usage(void)
62 ATTRIBUTE_NORETURN;
63 static void version(void)
64 ATTRIBUTE_NORETURN;
65
main(int argc,char * const argv[])66 int main(int argc, char *const argv[]) {
67 int c, /* function returns + misc */
68 ev, /* value to exit with */
69 i, /* loop counter + misc */
70 kq; /* kernel queue descriptor */
71 struct kevent *events;
72 struct timespec *pts, /* pointer to ts or NULL */
73 ts; /* timeout */
74 u_int8_t options;
75
76 /* make sure we remember our program name */
77 setprogname(argv[0]);
78
79 /* defaults */
80 options = ts.tv_nsec = 0;
81 pts = NULL;
82
83 /* handle command line options */
84 while ((c = getopt(argc, argv, "chit:vw")) != -1) {
85 switch (c) {
86 case 'c':
87 /* copyright/version */
88 version();
89 break;
90 case 'h':
91 /* human output */
92 options |= H_FLAG;
93 break;
94 case 'i':
95 /* set exit value to indicate which file/directory changed */
96 options |= I_FLAG;
97 break;
98 case 't':
99 /* timeout */
100 pts = &ts;
101 ts.tv_sec = (time_t)strtol(optarg, (char **)NULL, 10);
102 if (ts.tv_sec < 0 || (ts.tv_sec == LONG_MAX && errno == ERANGE)) {
103 warnc(ERANGE, "invalid timeout");
104 usage();
105 }
106 break;
107 case 'v':
108 /* copyright/version */
109 version();
110 break;
111 case 'w':
112 /* only interested in writes */
113 options |= W_FLAG;
114 break;
115 case '?':
116 default:
117 /* unknown option */
118 usage();
119 }
120 }
121 argc -= optind;
122 argv += optind;
123
124 /* make sure there is at least one file/directory to watch */
125 if (argc < 1) {
126 usage();
127 }
128
129 /* see if there are soo many files/directories to watch that that our exit
130 * value may be ambiguous */
131 if ((options & I_FLAG) && (argc >= EX__BASE)) {
132 warnx("-i specified with >= %d files - exit code may be ambiguous", EX__BASE);
133 }
134
135 /* create the event array */
136 if ((events = (struct kevent *)malloc(argc * sizeof(struct kevent))) == NULL) {
137 err(EX_UNAVAILABLE, "malloc");
138 }
139
140 /* fill the event array */
141 for (i = 0; i < argc; ++i) {
142 /* optain a file descriptor for the file/directory */
143 if ((c = open(argv[i], O_RDONLY | O_NONBLOCK)) == -1) {
144 err(EX_NOINPUT, "can't open \"%s\" for reading", argv[i]);
145 }
146 events[i].ident = c;
147 events[i].filter = EVFILT_VNODE;
148 events[i].flags = EV_ADD | EV_ENABLE | EV_CLEAR;
149 if (options & W_FLAG) {
150 /* only interested in writes */
151 events[i].fflags = NOTE_WRITE;
152 } else {
153 /* interested in everything */
154 events[i].fflags = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | NOTE_REVOKE;
155 }
156 /* store filename in udata for later retrieval */
157 events[i].udata = (void *)argv[i];
158 }
159
160 /* obtain a kq */
161 if ((kq = kqueue()) == -1) {
162 err(EX_OSERR, "kqueue");
163 }
164
165 /* thats the preamble - now do the waiting
166 *
167 * we are only interested in one event as we can only signal one event
168 * so in the hope of efficiency we lie the second time about the size of
169 * the events array */
170 switch (c = kevent(kq, events, argc, events, 1, pts)) {
171 case -1:
172 /* kevent failed */
173 err(EX_UNAVAILABLE, "kevent");
174 break;
175 case 0:
176 /* timeout */
177 exit(EXIT_TIMEOUT);
178 break;
179 default:
180 /* something for us to look at in events */
181 for (i = 0; i < c; ++i) {
182 if (events[i].flags & EV_ERROR) {
183 /* error occured while processing a filter */
184 warnc(events[i].data, "%s", (char *)events[i].udata);
185 /* set ev in case this is all we get */
186 ev = EX_UNAVAILABLE;
187 continue;
188 } else {
189 /* something reportable must have happened */
190 if (options & H_FLAG) {
191 /* human readable output */
192 display_human(&events[i]);
193 }
194 if (options & I_FLAG) {
195 /* user wants to know which file */
196 ev = which_happened(&events[i], argc, argv);
197 } else {
198 /* user wants to know what happened */
199 ev = what_happened(&events[i]);
200 }
201 exit(ev);
202 }
203 }
204 break;
205 }
206
207 /* NOT REACHED */
208 return 0;
209 }
210
display_human(struct kevent * event)211 void display_human(struct kevent *event) {
212
213 /* display event to stdout */
214 printf("%s:", (char *)event->udata);
215 if (event->fflags & NOTE_DELETE) {
216 printf(" deleted");
217 }
218 if (event->fflags & NOTE_WRITE) {
219 printf(" written");
220 }
221 if (event->fflags & NOTE_EXTEND) {
222 printf(" extended");
223 }
224 if (event->fflags & NOTE_ATTRIB) {
225 printf(" attributed");
226 }
227 if (event->fflags & NOTE_LINK) {
228 printf(" linked");
229 }
230 if (event->fflags & NOTE_RENAME) {
231 printf(" renamed");
232 }
233 if (event->fflags & NOTE_REVOKE) {
234 printf(" revoked");
235 }
236 printf("\n");
237 }
238
239 /* indicate what sort of event occured (returns an EXIT_ value) */
what_happened(struct kevent * event)240 int what_happened(struct kevent *event) {
241
242 if (event->fflags & NOTE_DELETE) {
243 return(EXIT_DELETE);
244 }
245 if (event->fflags & NOTE_WRITE) {
246 return(EXIT_WRITE);
247 }
248 if (event->fflags & NOTE_EXTEND) {
249 return(EXIT_EXTEND);
250 }
251 if (event->fflags & NOTE_ATTRIB) {
252 return(EXIT_ATTRIB);
253 }
254 if (event->fflags & NOTE_LINK) {
255 return(EXIT_LINK);
256 }
257 if (event->fflags & NOTE_RENAME) {
258 return(EXIT_RENAME);
259 }
260 if (event->fflags & NOTE_REVOKE) {
261 return(EXIT_REVOKE);
262 }
263
264 /* unknown event */
265 warnx("unknown event of type %u occured to %s", event->fflags, (char *)event->udata);
266 return EX_OSERR;
267 }
268
269 /* indicate which file the event occured to (index starts at 1) */
which_happened(struct kevent * event,int file_count,char * const filenames[])270 int which_happened(struct kevent *event, int file_count, char *const filenames[]) {
271 int i;
272
273 for (i = 0; i < file_count; ++i) {
274 if (strcmp((char *)event->udata, filenames[i]) == 0) {
275 /* we have a match */
276 return (i + 1);
277 }
278 }
279
280 /* it was a file we didn't ask about...*/
281 warnx("event on unknown file (%s)", (char *)event->udata);
282 return EX_OSERR;
283 }
284
usage(void)285 static void usage(void) {
286
287 fprintf(stderr, "usage: %s [-chivw] [-t timeout] file1 [...]\n", getprogname());
288 exit(EX_USAGE);
289 }
290
version(void)291 static void version(void) {
292
293 printf("%s version 1.0 Copyright 2002 Andrew Stevenson <andrew@ugh.net.au>\n", getprogname());
294 exit(EX_OK);
295 }
296