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