filewatcherd.c (7445B)
1 /* filewatcherd.c - main function for file watcher daemon */ 2 3 /* 4 * Copyright (c) 2013, Natacha Porté 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <fcntl.h> 20 #include <getopt.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <syslog.h> 24 #include <signal.h> 25 26 #include <sys/types.h> 27 #include <sys/event.h> 28 #include <sys/time.h> 29 30 #include "log.h" 31 #include "run.h" 32 #include "watchtab.h" 33 34 /* insert_entry - wait for an event described by the given watchtab entry */ 35 static int 36 insert_entry(int kq, struct watch_entry *wentry) { 37 struct kevent event; 38 wentry->fd = open(wentry->path, O_RDONLY | O_CLOEXEC); 39 if (wentry->fd < 0) { 40 log_open_entry(wentry->path); 41 wentry->fd = -1; 42 return -1; 43 } 44 EV_SET(&event, wentry->fd, 45 EVFILT_VNODE, 46 EV_ADD | EV_ONESHOT, 47 wentry->events, 48 0, 49 wentry); 50 if (kevent(kq, &event, 1, 0, 0, 0) < 0) { 51 log_kevent_entry(wentry->path); 52 close(wentry->fd); 53 wentry->fd = -1; 54 return -1; 55 } 56 57 log_entry_wait(wentry); 58 return 0; 59 } 60 61 62 63 int 64 main(int argc, char **argv) { 65 int kq; /* file descriptor for the kernel queue */ 66 int argerr = 0; /* whether arguments are invalid */ 67 int help = 0; /* whether help text should be displayed */ 68 int daemonize = 1; /* whether fork to background and use syslog */ 69 const char *tabpath = 0;/* path to the watchtab file */ 70 int tab_fd; /* file descriptor of watchtab */ 71 FILE *tab_f; /* file stream of watchtab */ 72 struct watchtab wtab; /* current watchtab data */ 73 intptr_t delay = 100; /* delay in ms before reloading watchtab */ 74 int wtab_error = 0; /* whether watchtab can't be opened */ 75 76 struct option longopts[] = { 77 { "foreground", no_argument, 0, 'd' }, 78 { "help", no_argument, 0, 'h' }, 79 { "wait", required_argument, 0, 'w' }, 80 { 0, 0, 0, 0 } 81 }; 82 83 /* Temporary variables */ 84 struct kevent event; 85 struct watch_entry *wentry; 86 pid_t pid; 87 int c; 88 char *s; 89 90 91 /*************************** 92 * COMMAND LINE PROCESSING * 93 ***************************/ 94 95 /* Process options */ 96 while (!argerr 97 && (c = getopt_long(argc, argv, "dhw:", longopts, 0)) != -1) { 98 switch (c) { 99 case 'd': 100 daemonize = 0; 101 break; 102 case 'h': 103 help = 1; 104 break; 105 case 'w': 106 delay = strtol(optarg, &s, 10); 107 if (!s[0]) { 108 log_bad_delay(optarg); 109 argerr = 1; 110 } 111 break; 112 default: 113 argerr = 1; 114 } 115 } 116 117 /* Use the first argument as watchtab, discard the reset */ 118 if (optind < argc) 119 tabpath = argv[optind]; 120 else 121 argerr = 1; 122 123 /* Display help text and terminate */ 124 if (argerr || help) { 125 print_usage(!help, argc, argv); 126 return help ? EXIT_SUCCESS : EXIT_FAILURE; 127 } 128 129 130 /****************** 131 * INITIALIZATION * 132 ******************/ 133 134 if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) { 135 log_signal(SIGCHLD); 136 return EXIT_FAILURE; 137 } 138 139 /* Try to open and read the watchtab */ 140 tab_fd = open(tabpath, O_RDONLY | O_CLOEXEC); 141 if (tab_fd < 0) { 142 log_open_watchtab(tabpath); 143 return EXIT_FAILURE; 144 } 145 tab_f = fdopen(tab_fd, "r"); 146 if (!tab_f) { 147 log_open_watchtab(tabpath); 148 return EXIT_FAILURE; 149 } 150 SLIST_INIT(&wtab); 151 if (wtab_readfile(&wtab, tab_f, tabpath) < 0) 152 return EXIT_FAILURE; 153 log_watchtab_loaded(tabpath); 154 155 /* Fork to background */ 156 if (daemonize) { 157 daemon(0, 0); 158 set_report(&syslog); 159 } 160 161 /* Create a kernel queue */ 162 kq = kqueue(); 163 if (kq == -1) { 164 log_kqueue(); 165 return EXIT_FAILURE; 166 } 167 168 /* Insert config file watcher */ 169 EV_SET(&event, tab_fd, 170 EVFILT_VNODE, 171 EV_ADD | EV_ONESHOT, 172 NOTE_DELETE | NOTE_WRITE | NOTE_RENAME | NOTE_REVOKE, 173 0, 0); 174 if (kevent(kq, &event, 1, 0, 0, 0) < 0) { 175 log_kevent_watchtab(tabpath); 176 return EXIT_FAILURE; 177 } 178 179 /* Insert initial watchers */ 180 SLIST_FOREACH(wentry, &wtab, next) { 181 insert_entry(kq, wentry); 182 } 183 184 185 /************* 186 * MAIN LOOP * 187 *************/ 188 189 while (1) { 190 /* Wait for a single event */ 191 if (kevent(kq, 0, 0, &event, 1, 0) < 0) { 192 log_kevent_wait(); 193 break; 194 } 195 196 switch (event.filter) { 197 case EVFILT_VNODE: 198 if (!event.udata) { 199 /* 200 * Something happened on the watchtab: 201 * close everything and start the timer before 202 * reloading it. 203 */ 204 fclose(tab_f); /* also closes tab_fd */ 205 EV_SET(&event, 42, 206 EVFILT_TIMER, 207 EV_ADD, 208 0, 209 delay, /* ms */ 210 0); 211 if (kevent(kq, &event, 1, 0, 0, 0) < 0) { 212 log_kevent_timer(); 213 exit(EXIT_FAILURE); 214 } 215 break; 216 } 217 218 /* A watchtab entry has been triggered */ 219 wentry = event.udata; 220 if (wentry->fd < 0 221 || (uintptr_t)wentry->fd != event.ident) { 222 LOG_ASSERT("wentry->fd"); 223 exit(EXIT_FAILURE); 224 } 225 close(wentry->fd); 226 wentry->fd = -1; 227 pid = run_entry(wentry); 228 if (!pid) break; 229 230 /* Wait for the command to finish */ 231 EV_SET(&event, pid, 232 EVFILT_PROC, 233 EV_ADD | EV_ONESHOT, 234 NOTE_EXIT, 235 0, 236 wentry); 237 if (kevent(kq, &event, 1, 0, 0, 0) < 0) 238 log_kevent_proc(wentry, pid); 239 break; 240 241 case EVFILT_PROC: 242 /* 243 * The command has finished, re-insert the path to 244 * watch it. 245 */ 246 insert_entry(kq, event.udata); 247 break; 248 249 case EVFILT_TIMER: 250 /* 251 * Timer for watchtab reload has expired, try to 252 * reopen and reload it. 253 * When open fails, keep the timer around to try 254 * again after delay (suppressing errors). 255 * When loading fails, keep the old watchtab but add 256 * the event filter anyway to try again on next update. 257 */ 258 259 /* Try opening the watchtab file */ 260 tab_fd = open(tabpath, O_RDONLY | O_CLOEXEC); 261 if (tab_fd < 0) { 262 if (!wtab_error) 263 log_open_watchtab(tabpath); 264 wtab_error = 1; 265 break; 266 } 267 tab_f = fdopen(tab_fd, "r"); 268 if (!tab_f) { 269 if (!wtab_error) 270 log_open_watchtab(tabpath); 271 wtab_error = 1; 272 close(tab_fd); 273 break; 274 } 275 276 /* Delete the timer */ 277 event.flags = EV_DELETE; 278 if (kevent(kq, &event, 1, 0, 0, 0) < 0) { 279 log_kevent_timer_off(); 280 /* timer is still around, close files */ 281 fclose(tab_f); 282 break; 283 } 284 285 /* Watch the file for changes */ 286 EV_SET(&event, tab_fd, 287 EVFILT_VNODE, 288 EV_ADD | EV_ONESHOT, 289 NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE 290 | NOTE_WRITE, 291 0, 0); 292 if (kevent(kq, &event, 1, 0, 0, 0) < 0) 293 log_kevent_watchtab(tabpath); 294 295 /* Load watchtab contents on a temporary variable */ 296 /* local */{ 297 struct watchtab new_wtab 298 = SLIST_HEAD_INITIALIZER(new_wtab); 299 300 if (wtab_readfile(&new_wtab, 301 tab_f, tabpath) < 0) { 302 wtab_release(&new_wtab); 303 break; 304 } 305 306 wtab_release(&wtab); 307 wtab = new_wtab; 308 SLIST_FOREACH(wentry, &wtab, next) { 309 insert_entry(kq, wentry); 310 } 311 } 312 313 log_watchtab_loaded(tabpath); 314 break; 315 } 316 } 317 318 return EXIT_SUCCESS; 319 }