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

watchtab.c (15650B)


      1 /* watchtab.c - configuration tables for file watches */
      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 <errno.h>
     20 #include <grp.h>
     21 #include <pwd.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 
     25 #include <sys/types.h>
     26 #include <sys/event.h>
     27 
     28 #include "log.h"
     29 #include "watchtab.h"
     30 
     31 /* number of pointers allocated at once in watch_env */
     32 #define WENV_ALLOC_UNIT 16;
     33 
     34 /*********************
     35  * LOCAL SUBPROGRAMS *
     36  *********************/
     37 
     38 /* parse_events - process a configuration string into fflags vnode events */
     39 static u_int
     40 parse_events(const char *line, size_t len) {
     41 	u_int result = 0;
     42 	size_t i = 0;
     43 
     44 	/* Check wildcard */
     45 	if (len == 1 && line[0] == '*')
     46 		return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB
     47 		    | NOTE_LINK | NOTE_RENAME | NOTE_REVOKE;
     48 
     49 	/*
     50 	 * Parse entry as a list of token separated by a single
     51 	 * non-letter byte.
     52 	 */
     53 	while (i < len) {
     54 		if (strncmp(line + i, "delete", 6) == 0
     55 		    || strncmp(line + i, "DELETE", 6) == 0) {
     56 			result |= NOTE_DELETE;
     57 			i += 6;
     58 		}
     59 		else if (strncmp(line + i, "write", 5) == 0
     60 		    || strncmp(line + i, "WRITE", 5) == 0) {
     61 			result |= NOTE_WRITE;
     62 			i += 5;
     63 		}
     64 		else if (strncmp(line + i, "extend", 6) == 0
     65 		    || strncmp(line + i, "EXTEND", 6) == 0) {
     66 			result |= NOTE_EXTEND;
     67 			i += 6;
     68 		}
     69 		else if (strncmp(line + i, "attrib", 6) == 0
     70 		    || strncmp(line + i, "ATTRIB", 6) == 0) {
     71 			result |= NOTE_ATTRIB;
     72 			i += 6;
     73 		}
     74 		else if (strncmp(line + i, "link", 4) == 0
     75 		    || strncmp(line + i, "LINK", 4) == 0) {
     76 			result |= NOTE_LINK;
     77 			i += 4;
     78 		}
     79 		else if (strncmp(line + i, "rename", 6) == 0
     80 		    || strncmp(line + i, "RENAME", 6) == 0) {
     81 			result |= NOTE_RENAME;
     82 			i += 6;
     83 		}
     84 		else if (strncmp(line + i, "revoke", 6) == 0
     85 		    || strncmp(line + i, "REVOKE", 6) == 0) {
     86 			result |= NOTE_REVOKE;
     87 			i += 6;
     88 		}
     89 		else return 0;
     90 
     91 		if (i < len && ((line[i] >= 'a' && line[i] <= 'z')
     92 		    || (line[i] >= 'A' && line[i] <= 'Z')))
     93 			return 0;
     94 		else
     95 			i++;
     96 	}
     97 
     98 	return result;
     99 }
    100 
    101 
    102 /* strdupesc - duplicate and unescape an input string */
    103 static char *
    104 strdupesc(const char *src, size_t len) {
    105 	size_t s = 0, d = 0;
    106 	char *dest = malloc(len + 1);
    107 
    108 	if (!dest) {
    109 		log_alloc("watchtab entry internal string");
    110 		return 0;
    111 	}
    112 
    113 	while (src[s] && s < len) {
    114 		if (src[s] != '\\' || (s > 0 && src[s-1] == '\\'))
    115 			dest[d++] = src[s];
    116 		s++;
    117 	}
    118 	dest[d] = 0;
    119 	return dest;
    120 }
    121 
    122 /* wenv_resize - preallocate enough storage for new_size pointers */
    123 static int
    124 wenv_resize(struct watch_env *wenv, size_t new_size) {
    125 	size_t new_cap, i;
    126 	const char **new_env;
    127 
    128 	/* Initialize if needed */
    129 	if (!wenv->environ && wenv_init(wenv) < 0)
    130 		return -1;
    131 
    132 	/* Don't realloc when enough capacity is available */
    133 	if (new_size <= wenv->capacity) return 0;
    134 
    135 	/* Actually resize */
    136 	new_cap = wenv->capacity;
    137 	while (new_cap < new_size) new_cap += WENV_ALLOC_UNIT;
    138 	new_env = realloc(wenv->environ, sizeof *wenv->environ * new_cap);
    139 	if (!new_env) {
    140 		log_alloc("environment variables");
    141 		return -1;
    142 	}
    143 
    144 	/* Clear new pointers */
    145 	for (i = wenv->capacity; i < new_cap; i++)
    146 		new_env[i] = 0;
    147 
    148 	/* Update watch_env */
    149 	wenv->capacity = new_cap;
    150 	wenv->environ = new_env;
    151 	return 0;
    152 }
    153 
    154 
    155 
    156 /********************
    157  * PUBLIC INTERFACE *
    158  ********************/
    159 
    160 /* wentry_init - initialize a watch_entry with null values */
    161 void
    162 wentry_init(struct watch_entry *wentry) {
    163 	if (!wentry) return;
    164 
    165 	wentry->path = 0;
    166 	wentry->events = 0;
    167 	wentry->delay.tv_sec = 0;
    168 	wentry->delay.tv_nsec = 0;
    169 	wentry->uid = 0;
    170 	wentry->gid = 0;
    171 	wentry->chroot = 0;
    172 	wentry->command = 0;
    173 	wentry->envp = 0;
    174 	wentry->fd = -1;
    175 }
    176 
    177 
    178 /* wentry_release - free internal objects from a watch_entry */
    179 void
    180 wentry_release(struct watch_entry *wentry) {
    181 	if (!wentry) return;
    182 
    183 	free((void *)(wentry->path));
    184 	free((void *)(wentry->chroot));
    185 	free((void *)(wentry->command));
    186 	wentry->path = 0;
    187 	wentry->chroot = 0;
    188 	wentry->command = 0;
    189 
    190 	if (wentry->envp) {
    191 		size_t i = 0;
    192 		while (wentry->envp[i])
    193 			free(wentry->envp[i++]);
    194 		free(wentry->envp);
    195 	}
    196 	wentry->envp = 0;
    197 
    198 	if (wentry->fd != -1)
    199 		close(wentry->fd);
    200 	wentry->fd = -1;
    201 }
    202 
    203 
    204 /* wentry_free - free a watch_entry and the string it contains */
    205 void
    206 wentry_free(struct watch_entry *wentry) {
    207 	if (!wentry) return;
    208 
    209 	wentry_release(wentry);
    210 	free(wentry);
    211 }
    212 
    213 
    214 /* wentry_readline - parse a config file line and fill a struct watch_entry */
    215 /*   Return 0 on success or -1 on failure. */
    216 int
    217 wentry_readline(struct watch_entry *dest, char *line,
    218     struct watch_env *base_env, int has_home,
    219     const char *filename, unsigned line_no) {
    220 	size_t path_len = 0;
    221 	size_t event_first = 0, event_len = 0;
    222 	size_t delay_first = 0, delay_len = 0;
    223 	size_t user_first = 0, user_len = 0;
    224 	size_t chroot_first = 0, chroot_len = 0;
    225 	size_t cmd_first = 0, cmd_len = 0;
    226 	struct passwd *pw = 0;
    227 	struct group *grp = 0;
    228 	size_t i = 1;
    229 
    230 	/* Sanity checks */
    231 	if (!line || line[0] == 0 || line[0] == '\t') {
    232 		LOG_ASSERT(0);
    233 		return -1;
    234 	}
    235 
    236 	/* Look for fields boundaries */
    237 	while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++;
    238 	path_len = i;
    239 	while (line[i] == '\t') i++;
    240 	event_first = i;
    241 	while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++;
    242 	event_len = i - event_first;
    243 	while (line[i] == '\t') i++;
    244 	delay_first = i;
    245 	while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++;
    246 	delay_len = i - delay_first;
    247 	while (line[i] == '\t') i++;
    248 	user_first = i;
    249 	while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++;
    250 	user_len = i - user_first;
    251 	while (line[i] == '\t') i++;
    252 	chroot_first = i;
    253 	while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++;
    254 	chroot_len = i - chroot_first;
    255 	while (line[i] == '\t') i++;
    256 	cmd_first = i;
    257 	while (line[i] != 0) i++;
    258 	cmd_len = i - cmd_first;
    259 
    260 	/* Less than 3 fields found is a parse error */
    261 	if (line[delay_first] == 0) {
    262 		log_watchtab_invalid_action(filename, line_no);
    263 		return -1;
    264 	}
    265 
    266 	/* Adjust offsets depending on which fields are omitted */
    267 	if (line[user_first] == 0) {
    268 		/* 3-field line: path, events, command */
    269 		cmd_first = delay_first;
    270 		cmd_len = delay_len;
    271 		delay_first = delay_len = 0;
    272 		user_first = user_len = 0;
    273 		chroot_first = chroot_len = 0;
    274 	}
    275 	else if (line[chroot_first] == 0) {
    276 		/* 4-field line: path, events, delay, command */
    277 		cmd_first = user_first;
    278 		cmd_len = user_len;
    279 		user_first = user_len = chroot_first = chroot_len = 0;
    280 	}
    281 	else if (line[cmd_first] == 0) {
    282 		/* 5-field line: path, events, delay, user, command */
    283 		cmd_first = chroot_first;
    284 		cmd_len = chroot_len;
    285 		chroot_first = chroot_len = 0;
    286 	}
    287 
    288 	/* Parse event set */
    289 	dest->events = parse_events(line + event_first, event_len);
    290 	if (dest->events == 0) {
    291 		log_watchtab_invalid_events(filename, line_no,
    292 		    line + event_first, event_len);
    293 		return -1;
    294 	}
    295 
    296 	/* Parse delay */
    297 	dest->delay.tv_sec = 0;
    298 	dest->delay.tv_nsec = 0;
    299 	if (delay_len > 0
    300 	    && !(delay_len == 1 && line[delay_first] == '*')) {
    301 		char *s;
    302 
    303 		/* Decode integer part */
    304 		dest->delay.tv_sec = strtol(line + delay_first, &s, 10);
    305 
    306 		/* Decode fractional part if any */
    307 		if (*s == '.') {
    308 			char *ns;
    309 			dest->delay.tv_nsec = strtol(s + 1, &ns, 10);
    310 			while (ns - s <= 9) {
    311 				dest->delay.tv_nsec *= 10;
    312 				s--;
    313 			}
    314 			s = ns;
    315 		}
    316 
    317 		/* Check trailing non-digits */
    318 		if (s < line + delay_first + delay_len) {
    319 			line[delay_first + delay_len] = 0;
    320 			log_watchtab_invalid_delay(filename, line_no,
    321 			    line + delay_first);
    322 			return -1;
    323 		}
    324 	}
    325 
    326 	/* Process user name and optional group name */
    327 	if (user_len > 0) {
    328 		char *login = line + user_first;
    329 		char *group = 0;
    330 
    331 		line[user_first + user_len] = 0;
    332 
    333 		/* Process group */
    334 		group = strchr(login, ':');
    335 		if (group) {
    336 			*group = 0;
    337 			group++;
    338 			for (i = 0; group[i] >= '0' && group[i] <= '9'; i++);
    339 			errno = 0;
    340 			grp = group[i]
    341 			    ? getgrnam(group)
    342 			    : getgrgid(strtol(group, 0, 10));
    343 			if (!grp) {
    344 				log_lookup_group(group);
    345 				return -1;
    346 			}
    347 		}
    348 
    349 		/* Lookup user name */
    350 		for (i = 0; login[i] >= '0' && login[i] <= '9'; i++);
    351 		errno = 0;
    352 		pw = login[i]
    353 		    ? getpwnam(login)
    354 		    : getpwuid(strtol(login, 0, 10));
    355 		if (!pw) {
    356 			log_lookup_pw(login);
    357 			return -1;
    358 		}
    359 	}
    360 
    361 	/* Store numeric ids */
    362 	dest->uid = pw ? pw->pw_uid : 0;
    363 	dest->gid = grp ? grp->gr_gid : (pw ? pw->pw_gid : 0);
    364 
    365 	/* Lookup self name if not overridden */
    366 	if (!pw) {
    367 		char *login;
    368 		errno = 0;
    369 		login = getlogin();
    370 		pw = login ? getpwnam(login) : 0;
    371 		if (!pw) {
    372 			log_lookup_self();
    373 			return -1;
    374 		}
    375 	}
    376 
    377 	/* At this point, no parse error can occur, filling in data */
    378 
    379 	/* Clean up destination */
    380 	wentry_release(dest);
    381 
    382 	/* Copy string parameters */
    383 	dest->path = strdupesc(line, path_len);
    384 	dest->command = strdupesc(line + cmd_first, cmd_len);
    385 
    386 	if (chroot_len > 0)
    387 		dest->chroot = strdupesc(line + chroot_first, chroot_len);
    388 	else
    389 		dest->chroot = 0;
    390 
    391 	/* Setup environment */
    392 	wenv_set(base_env, "LOGNAME", pw->pw_name, 1);
    393 	wenv_set(base_env, "USER", pw->pw_name, 1);
    394 	wenv_set(base_env, "HOME", pw->pw_dir, !has_home);
    395 	wenv_set(base_env, "TRIGGER", dest->path, 1);
    396 	dest->envp = wenv_dup(base_env);
    397 
    398 	return 0;
    399 }
    400 
    401 
    402 
    403 /***********************
    404  * WATCH_ENV INTERFACE *
    405  ***********************/
    406 
    407 /* wenv_init - create an empty environment list */
    408 int
    409 wenv_init(struct watch_env *wenv) {
    410 	if (!wenv) {
    411 		LOG_ASSERT(0);
    412 		return -1;
    413 	}
    414 
    415 	wenv->capacity = WENV_ALLOC_UNIT;
    416 	wenv->size = 0;
    417 	wenv->environ = malloc(sizeof *wenv->environ * wenv->capacity);
    418 	if (!wenv->environ) {
    419 		log_alloc("initial environment variables");
    420 		return -1;
    421 	}
    422 
    423 	return 0;
    424 }
    425 
    426 
    427 /* wenv_release - free string memory in a struct watch_env but not the struct*/
    428 void
    429 wenv_release(struct watch_env *wenv) {
    430 	free(wenv->environ);
    431 	wenv->size = 0;
    432 	wenv->environ = 0;
    433 }
    434 
    435 
    436 /* wenv_add - append a string to an existing struct watch_env */
    437 int
    438 wenv_add(struct watch_env *wenv, const char *env_str) {
    439 	if (!wenv || !env_str) {
    440 		LOG_ASSERT(0);
    441 		return -1;
    442 	}
    443 
    444 	/* Increase array size if needed */
    445 	if (wenv_resize(wenv, wenv->size + 2) < 0) return -1;
    446 
    447 	/* Store a copy of the provided string */
    448 	wenv->environ[wenv->size] = strdup(env_str);
    449 	wenv->size++;
    450 	return 0;
    451 }
    452 
    453 /* wenv_set - insert or reset an environment variable */
    454 int
    455 wenv_set(struct watch_env *wenv, const char *name, const char *value,
    456     int overwrite) {
    457 	size_t namelen, linelen, i;
    458 	char *line;
    459 
    460 	if (!wenv || !name || !value) return -1;
    461 
    462 	/* Initialize if needed */
    463 	if (!wenv->environ && wenv_init(wenv) < 0)
    464 		return -1;
    465 
    466 	/* Build the environment line */
    467 	namelen = strlen(name);
    468 	linelen = namelen + 1 + strlen(value);
    469 	line = malloc(linelen + 1);
    470 	if (!line) {
    471 		log_alloc("environment variable entry");
    472 		return -1;
    473 	}
    474 	strncpy(line, name, namelen);
    475 	line[namelen] = '=';
    476 	strncpy(line + namelen + 1, value, linelen - (namelen + 1));
    477 	line[linelen] = 0;
    478 
    479 	/* Look for an existing entry for the name */
    480 	i = 0;
    481 	while (wenv->environ[i]) {
    482 		if (strncmp(wenv->environ[i], line, namelen + 1) == 0)
    483 			break;
    484 		i++;
    485 	}
    486 
    487 	/* If not found, insert the crafted line */
    488 	if (wenv->environ[i] == 0) {
    489 		if (wenv_resize(wenv, wenv->size + 2) < 0) {
    490 			free(line);
    491 			return -1;
    492 		}
    493 		wenv->environ[wenv->size] = line;
    494 		wenv->size++;
    495 		return 0;
    496 	}
    497 
    498 	/* Exit when environment variable exist but overwriting is forbidden */
    499 	if (!overwrite) return 0;
    500 
    501 	/* Replace found variable with new environment line */
    502 	free((void *)wenv->environ[i]);
    503 	wenv->environ[i] = line;
    504 	return 0;
    505 }
    506 
    507 /* wenv_get - lookup environment variable */
    508 const char *
    509 wenv_get(struct watch_env *wenv, const char *name) {
    510 	size_t namelen, i;
    511 
    512 	if (!wenv || !wenv->environ || !name) {
    513 		LOG_ASSERT(0);
    514 		return 0;
    515 	}
    516 	namelen = strlen(name);
    517 
    518 	for (i = 0; wenv->environ[i]; i++) {
    519 		if (strncmp(wenv->environ[i], name, namelen) == 0
    520 		    && wenv->environ[i][namelen] == '=')
    521 			return wenv->environ[i] + (namelen + 1);
    522 	}
    523 
    524 	return 0;
    525 }
    526 
    527 
    528 /* wenv_dup - deep copy environment strings */
    529 char **
    530 wenv_dup(struct watch_env *wenv) {
    531 	char **result;
    532 	size_t len, i;
    533 	int reported = 0;
    534 
    535 	if (!wenv) return 0;
    536 	len = (wenv->environ ? wenv->size : 0);
    537 	result = malloc((len + 1) * sizeof *result);
    538 	if (!result) {
    539 		log_alloc("environment duplicate");
    540 		return 0;
    541 	}
    542 
    543 	for (i = 0; i < len; i++) {
    544 		result[i] = strdup(wenv->environ[i]);
    545 		if (!result[i] && !reported) {
    546 			log_alloc("environment item duplication");
    547 			reported = 1;
    548 		}
    549 	}
    550 	result[len] = 0;
    551 
    552 	return result;
    553 }
    554 
    555 
    556 
    557 /**********************
    558  * WATCHTAB INTERFACE *
    559  **********************/
    560 
    561 /* wtab_release - release children objects but not the struct watchtab */
    562 void
    563 wtab_release(struct watchtab *tab) {
    564 	struct watch_entry *entry = 0;
    565 
    566 	if (!tab) return;
    567 
    568 	while ((entry = SLIST_FIRST(tab)) != 0) {
    569 		SLIST_REMOVE_HEAD(tab, next);
    570 		wentry_free(entry);
    571 	}
    572 }
    573 
    574 
    575 /* wtab_readfile - parse the given file to build a new watchtab */
    576 int
    577 wtab_readfile(struct watchtab *tab, FILE *input, const char *filename) {
    578 	char *line = 0;
    579 	size_t linecap = 0;
    580 	ssize_t linelen;
    581 	unsigned line_no = 0;
    582 	struct watch_entry *entry = 0;
    583 	int result = 0, has_home = 0;
    584 	size_t i, skip;
    585 	struct watch_env env;
    586 
    587 	if (!tab) {
    588 		LOG_ASSERT(0);
    589 		return -1;
    590 	}
    591 
    592 	/* Setup default environment */
    593 	wenv_init(&env);
    594 	wenv_set(&env, "SHELL", "/bin/sh", 1);
    595 	wenv_set(&env, "PATH", "/usr/bin:/bin", 1);
    596 
    597 	/* Read the input data */
    598 	while ((linelen = getdelim(&line, &linecap, '\n', input)) >= 0) {
    599 		line_no++;
    600 
    601 		/* Skip leading blanks */
    602 		skip = 0;
    603 		while (line[skip] == ' ' || line[skip] == '\t') skip++;
    604 
    605 		/* Trim trailing blanks */
    606 		while ((size_t)linelen > skip && (line[linelen-1] == '\n'
    607 		    || line[linelen-1] == '\r' || line[linelen-1] == ' '
    608 		    || line[linelen-1] == '\t'))
    609 			linelen--;
    610 		line[linelen] = 0;
    611 
    612 		/* Ignore empty lines and comments */
    613 		if ((size_t)linelen <= skip || line[skip] == '#')
    614 			continue;
    615 
    616 		/*
    617 		 * Define environment lines as lines having an '=' before any
    618 		 * tabulation ('\t') or backslash ('\\').
    619 		 */
    620 
    621 		i = skip;
    622 		while (line[i] != 0 && line[i] != '='
    623 		    && line[i] != '\\' && line[i] != '\t')
    624 			i++;
    625 
    626 		/* Record an environment variable */
    627 		if (line[i] == '=') {
    628 			/* Compute bounds of variable name */
    629 			size_t j = i - 1;
    630 			while (line[j] == ' ' && j > skip) j--;
    631 			if (j + 1 < i) line[j + 1] = 0;
    632 
    633 			/* Check whether this explicitly sets HOME */
    634 			if (strcmp(line + skip, "HOME") == 0)
    635 				has_home = 1;
    636 
    637 			/* Compute bounds of variable value */
    638 			j = i + 1;
    639 			while (line[j] == ' ') j++;
    640 
    641 			/* Set the variable */
    642 			wenv_set(&env, line + skip, line + j, 1);
    643 			continue;
    644 		}
    645 
    646 		/* Parse an entry line */
    647 		entry = malloc(sizeof *entry);
    648 		if (!entry) {
    649 			log_alloc("watchtab entry");
    650 			return -1;
    651 		}
    652 		wentry_init(entry);
    653 		if (wentry_readline(entry, line + skip, &env, has_home,
    654 		    filename, line_no) < 0) {
    655 			/* propagate an error but keep parsing */
    656 			result = -1;
    657 			wentry_free(entry);
    658 			continue;
    659 		}
    660 
    661 		/* Insert the entry in the list */
    662 		SLIST_INSERT_HEAD(tab, entry, next);
    663 	}
    664 
    665 	if (ferror(input)) {
    666 		log_watchtab_read();
    667 		return -1;
    668 	}
    669 
    670 	return result;
    671 }