filewatcherd

FreeBSD daemon that watches files and runs commands when they change
git clone https://git.instinctive.eu/filewatcherd.git
Log | Files | Refs | README | LICENSE

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 }