1 /* direvent - directory content watcher daemon
2    Copyright (C) 2012-2016 Sergey Poznyakoff
3 
4    Direvent is free software; you can redistribute it and/or modify it
5    under the terms of the GNU General Public License as published by the
6    Free Software Foundation; either version 3 of the License, or (at your
7    option) any later version.
8 
9    Direvent is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License along
15    with direvent. If not, see <http://www.gnu.org/licenses/>. */
16 
17 #include "direvent.h"
18 #include <grecs.h>
19 #include <pwd.h>
20 #include <grp.h>
21 
22 static struct transtab kwpri[] = {
23 	{ "emerg", LOG_EMERG },
24 	{ "alert", LOG_ALERT },
25 	{ "crit", LOG_CRIT },
26 	{ "err", LOG_ERR },
27 	{ "warning", LOG_WARNING },
28 	{ "notice", LOG_NOTICE },
29 	{ "info", LOG_INFO },
30 	{ "debug", LOG_DEBUG },
31 	{ NULL }
32 };
33 
34 static struct transtab kwfac[] = {
35 	{ "user",    LOG_USER },
36 	{ "daemon",  LOG_DAEMON },
37 	{ "auth",    LOG_AUTH },
38 	{ "authpriv",LOG_AUTHPRIV },
39 	{ "mail",    LOG_MAIL },
40 	{ "cron",    LOG_CRON },
41 	{ "local0",  LOG_LOCAL0 },
42 	{ "local1",  LOG_LOCAL1 },
43 	{ "local2",  LOG_LOCAL2 },
44 	{ "local3",  LOG_LOCAL3 },
45 	{ "local4",  LOG_LOCAL4 },
46 	{ "local5",  LOG_LOCAL5 },
47 	{ "local6",  LOG_LOCAL6 },
48 	{ "local7",  LOG_LOCAL7 },
49 	{ NULL }
50 };
51 
52 int
get_facility(const char * arg)53 get_facility(const char *arg)
54 {
55 	int f;
56 	char *p;
57 
58 	errno = 0;
59 	f = strtoul (arg, &p, 0);
60 	if (*p == 0 && errno == 0)
61 		return f;
62 	if (trans_strtotok(kwfac, arg, &f)) {
63 		diag(LOG_CRIT, _("unknown syslog facility: %s"), arg);
64 		exit(1);
65 	}
66 	return f;
67 }
68 
69 int
get_priority(const char * arg)70 get_priority(const char *arg)
71 {
72 	int f;
73 	char *p;
74 
75 	errno = 0;
76 	f = strtoul (arg, &p, 0);
77 	if (*p == 0 && errno == 0)
78 		return f;
79 	if (trans_strtotok(kwpri, arg, &f)) {
80 		diag(LOG_CRIT, _("unknown syslog priority: %s"), arg);
81 		exit(1);
82 	}
83 	return f;
84 }
85 
86 #define ASSERT_SCALAR(cmd, locus)					\
87 	if ((cmd) != grecs_callback_set_value) {			\
88 		grecs_error(locus, 0, _("unexpected block statement"));	\
89 		return 1;						\
90 	}
91 
92 int
assert_grecs_value_type(grecs_locus_t * locus,const grecs_value_t * value,int type)93 assert_grecs_value_type(grecs_locus_t *locus,
94 			const grecs_value_t *value, int type)
95 {
96 	if (GRECS_VALUE_EMPTY_P(value)) {
97 		grecs_error(locus, 0, _("expected %s"),
98 			    grecs_data_type_string(type));
99 		return 1;
100 	}
101 	if (value->type != type) {
102 		grecs_error(locus, 0, _("expected %s, but found %s"),
103 			    grecs_data_type_string(type),
104 			    grecs_data_type_string(value->type));
105 		return 1;
106 	}
107 	return 0;
108 }
109 
110 static int
cb_syslog_facility(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)111 cb_syslog_facility(enum grecs_callback_command cmd, grecs_node_t *node,
112 		   void *varptr, void *cb_data)
113 {
114 	grecs_locus_t *locus = &node->locus;
115 	grecs_value_t *value = node->v.value;
116 	int fac;
117 
118 	ASSERT_SCALAR(cmd, locus);
119 	if (assert_grecs_value_type(&value->locus, value, GRECS_TYPE_STRING))
120 		return 1;
121 
122 	if (trans_strtotok(kwfac, value->v.string, &fac))
123 		grecs_error(&value->locus, 0,
124 			    _("unknown syslog facility `%s'"),
125 			    value->v.string);
126 	else
127 		*(int*)varptr = fac;
128 	return 0;
129 }
130 
131 static struct grecs_keyword syslog_kw[] = {
132 	{ "facility",
133 	  N_("name"),
134 	  N_("Set syslog facility. Arg is one of the following: user, daemon, "
135 	     "auth, authpriv, mail, cron, local0 through local7 "
136 	     "(case-insensitive), or a facility number."),
137 	  grecs_type_string, GRECS_DFLT,
138 	  &facility, 0, cb_syslog_facility },
139 	{ "tag", N_("string"), N_("Tag syslog messages with this string"),
140 	  grecs_type_string, GRECS_DFLT,
141 	  &tag },
142 	{ "print-priority", N_("arg"),
143 	  N_("Prefix each message with its priority"),
144 	  grecs_type_bool, GRECS_DFLT,
145 	  &syslog_include_prio },
146 	{ NULL },
147 };
148 
149 struct eventconf {
150 	struct grecs_list *pathlist;
151 	event_mask ev_mask;
152 	filpatlist_t fpat;
153 	struct prog_handler prog_handler;
154 };
155 
156 static struct eventconf eventconf;
157 
158 static void
eventconf_init(void)159 eventconf_init(void)
160 {
161 	memset(&eventconf, 0, sizeof eventconf);
162 	eventconf.prog_handler.timeout = DEFAULT_TIMEOUT;
163 }
164 
165 static void
eventconf_free(void)166 eventconf_free(void)
167 {
168 	grecs_list_free(eventconf.pathlist);
169 	prog_handler_free(&eventconf.prog_handler);
170 	filpatlist_destroy(&eventconf.fpat);
171 }
172 
173 void
eventconf_flush(grecs_locus_t * loc)174 eventconf_flush(grecs_locus_t *loc)
175 {
176 	struct grecs_list_entry *ep;
177 	struct handler *hp = prog_handler_alloc(eventconf.ev_mask,
178 						eventconf.fpat,
179 						&eventconf.prog_handler);
180 
181 	for (ep = eventconf.pathlist->head; ep; ep = ep->next) {
182 		struct pathent *pe = ep->data;
183 		struct watchpoint *wpt;
184 		int isnew;
185 
186 		wpt = watchpoint_install(pe->path, &isnew);
187 		if (!wpt)
188 			abort();
189 		if (!isnew && wpt->depth != pe->depth)
190 			grecs_error(loc, 0,
191 				    _("%s: recursion depth does not match previous definition"),
192 				    pe->path);
193 		wpt->depth = pe->depth;
194 		handler_list_append(wpt->handler_list, hp);
195 	}
196 	grecs_list_free(eventconf.pathlist);
197 	eventconf_init();
198 }
199 
200 static int
cb_watcher(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)201 cb_watcher(enum grecs_callback_command cmd, grecs_node_t *node,
202 	   void *varptr, void *cb_data)
203 {
204 	int err = 0;
205 
206 	switch (cmd) {
207 	case grecs_callback_section_begin:
208 		eventconf_init();
209 		break;
210 	case grecs_callback_section_end:
211 		if (!eventconf.pathlist) {
212 			grecs_error(&node->locus, 0, _("no paths configured"));
213 			++err;
214 		}
215 		if (!eventconf.prog_handler.command) {
216 			grecs_error(&node->locus, 0,
217 				    _("no command configured"));
218 			++err;
219 		}
220 		if (evtnullp(&eventconf.ev_mask))
221 			evtsetall(&eventconf.ev_mask);
222 		if (err == 0)
223 			eventconf_flush(&node->locus);
224 		else
225 			eventconf_free();
226 		break;
227 	case grecs_callback_set_value:
228 		grecs_error(&node->locus, 0,
229 			    _("invalid use of block statement"));
230 	}
231 	return 0;
232 }
233 
234 static struct pathent *
pathent_alloc(char * s,long depth)235 pathent_alloc(char *s, long depth)
236 {
237 	size_t len = strlen(s);
238 	struct pathent *p = emalloc(sizeof(*p) + len);
239 	p->len = len;
240 	strcpy(p->path, s);
241 	p->depth = depth;
242 	return p;
243 }
244 
245 static int
cb_path(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)246 cb_path(enum grecs_callback_command cmd, grecs_node_t *node,
247 	void *varptr, void *cb_data)
248 {
249         grecs_locus_t *locus = &node->locus;
250 	grecs_value_t *val = node->v.value;
251 	struct grecs_list **lpp = varptr, *lp;
252 	struct pathent *pe;
253 	char *s;
254 	long depth = 0;
255 
256 	ASSERT_SCALAR(cmd, locus);
257 
258 	switch (val->type) {
259 	case GRECS_TYPE_STRING:
260 		s = val->v.string;
261 		break;
262 
263 	case GRECS_TYPE_ARRAY:
264 		if (assert_grecs_value_type(&val->v.arg.v[0]->locus,
265 					    val->v.arg.v[0],
266 					    GRECS_TYPE_STRING))
267 			return 1;
268 		if (assert_grecs_value_type(&val->v.arg.v[1]->locus,
269 					    val->v.arg.v[1],
270 					    GRECS_TYPE_STRING))
271 			return 1;
272 		if (strcmp(val->v.arg.v[1]->v.string, "recursive")) {
273 			grecs_error(&val->v.arg.v[1]->locus, 0,
274 				    _("expected \"recursive\" or end of statement"));
275 			return 1;
276 		}
277 		switch (val->v.arg.c) {
278 		case 2:
279 			depth = -1;
280 			break;
281 		case 3:
282 			if (grecs_string_convert(&depth, grecs_type_long,
283 						 val->v.arg.v[2]->v.string,
284 						 &val->v.arg.v[2]->locus))
285 				return 1;
286 			break;
287 		default:
288 			grecs_error(&val->v.arg.v[3]->locus, 0,
289 				    _("surplus argument"));
290 			return 1;
291 		}
292 		s = val->v.arg.v[0]->v.string;
293 		break;
294 	case GRECS_TYPE_LIST:
295 		grecs_error(locus, 0, _("unexpected list"));
296 		return 1;
297 	}
298 	pe = pathent_alloc(s, depth);
299         if (*lpp)
300 		lp = *lpp;
301 	else {
302 		lp = _grecs_simple_list_create(1);
303 		*lpp = lp;
304 	}
305 	grecs_list_append(lp, pe);
306 	return 0;
307 }
308 
309 static int
cb_eventlist(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)310 cb_eventlist(enum grecs_callback_command cmd, grecs_node_t *node,
311 	     void *varptr, void *cb_data)
312 {
313         grecs_locus_t *locus = &node->locus;
314 	grecs_value_t *val = node->v.value;
315 	event_mask *mask = varptr;
316 	event_mask m;
317 	struct grecs_list_entry *ep;
318 	int i;
319 
320 	ASSERT_SCALAR(cmd, locus);
321 
322 	switch (val->type) {
323 	case GRECS_TYPE_STRING:
324 		if (getevt(val->v.string, &m)) {
325 			grecs_error(&val->locus, 0,
326 				    _("unrecognized event code"));
327 			return 1;
328 		}
329 		mask->gen_mask |= m.gen_mask;
330 		mask->sys_mask |= m.sys_mask;
331 		break;
332 
333 	case GRECS_TYPE_ARRAY:
334 		for (i = 0; i < val->v.arg.c; i++) {
335 			if (assert_grecs_value_type(&val->v.arg.v[i]->locus,
336 						    val->v.arg.v[i],
337 						    GRECS_TYPE_STRING))
338 				return 1;
339 			if (getevt(val->v.arg.v[i]->v.string, &m)) {
340 				grecs_error(&val->v.arg.v[i]->locus, 0,
341 					    _("unrecognized event code"));
342 				return 1;
343 			}
344 			mask->gen_mask |= m.gen_mask;
345 			mask->sys_mask |= m.sys_mask;
346 		}
347 		break;
348 	case GRECS_TYPE_LIST:
349 		for (ep = val->v.list->head; ep; ep = ep->next)	{
350 			grecs_value_t *vp = ep->data;
351 			if (assert_grecs_value_type(&vp->locus, vp,
352 						    GRECS_TYPE_STRING))
353 				return 1;
354 			if (getevt(vp->v.string, &m)) {
355 				grecs_error(&vp->locus, 0,
356 					    _("unrecognized event code"));
357 				return 1;
358 			}
359 			mask->gen_mask |= m.gen_mask;
360 			mask->sys_mask |= m.sys_mask;
361 		}
362 		break;
363 	}
364 	return 0;
365 }
366 
367 static int
membergid(gid_t gid,size_t gc,gid_t * gv)368 membergid(gid_t gid, size_t gc, gid_t *gv)
369 {
370 	int i;
371 	for (i = 0; i < gc; i++)
372 		if (gv[i] == gid)
373 			return 1;
374 	return 0;
375 }
376 
377 static void
get_user_groups(char * user,gid_t gid,size_t * pgidc,gid_t ** pgidv)378 get_user_groups(char *user, gid_t gid, size_t *pgidc, gid_t **pgidv)
379 {
380 	size_t gidc = 0, n = 0;
381 	gid_t *gidv = NULL;
382 	struct group *gr;
383 
384 	n = 32;
385 	gidv = emalloc(n * sizeof(gidv[0]));
386 	gidv[0] = gid;
387 	gidc = 1;
388 
389 	setgrent();
390 	while (gr = getgrent()) {
391 		char **p;
392 		for (p = gr->gr_mem; *p; p++)
393 			if (strcmp(*p, user) == 0) {
394 				if (n == gidc) {
395 					n += 32;
396 					gidv = erealloc(gidv,
397 							n * sizeof(gidv[0]));
398 				}
399 				if (!membergid(gr->gr_gid, gidc, gidv))
400 					gidv[gidc++] = gr->gr_gid;
401 			}
402 	}
403 	endgrent();
404 	*pgidc = gidc;
405 	*pgidv = gidv;
406 }
407 
408 static int
cb_user(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)409 cb_user(enum grecs_callback_command cmd, grecs_node_t *node,
410 	void *varptr, void *cb_data)
411 {
412         grecs_locus_t *locus = &node->locus;
413 	grecs_value_t *val = node->v.value;
414 	struct passwd *pw;
415 	struct group *gr;
416 	grecs_value_t *uv, *gv = NULL;
417 	gid_t gid;
418 
419 	ASSERT_SCALAR(cmd, locus);
420 	switch (val->type) {
421 	case GRECS_TYPE_STRING:
422 		uv = val;
423 		break;
424 
425 	case GRECS_TYPE_ARRAY:
426 		if (assert_grecs_value_type(&val->v.arg.v[0]->locus,
427 					    val->v.arg.v[0],
428 					    GRECS_TYPE_STRING))
429 			return 1;
430 		if (assert_grecs_value_type(&val->v.arg.v[1]->locus,
431 					    val->v.arg.v[1],
432 					    GRECS_TYPE_STRING))
433 			return 1;
434 		if (val->v.arg.c > 2) {
435 			grecs_locus_t loc;
436 			loc.beg = val->v.arg.v[2]->locus.beg;
437 			loc.end = val->v.arg.v[val->v.arg.c - 1]->locus.end;
438 			grecs_error(&loc, 0, _("surplus arguments"));
439 			return 1;
440 		}
441 		uv = val->v.arg.v[0];
442 		gv = val->v.arg.v[1];
443 		break;
444 
445 	case GRECS_TYPE_LIST:
446 		grecs_error(locus, 0, _("unexpected list"));
447 		return 1;
448 	}
449 
450 	pw = getpwnam(uv->v.string);
451 	if (!pw) {
452 		grecs_error(&uv->locus, 0, _("no such user"));
453 		return 1;
454 	}
455 
456 	if (gv) {
457 		gr = getgrnam(gv->v.string);
458 		if (!gr) {
459 			grecs_error(&gv->locus, 0, _("no such group"));
460 			return 1;
461 		}
462 		gid = gr->gr_gid;
463 	} else
464 		gid = pw->pw_gid;
465 
466 	eventconf.prog_handler.uid = pw->pw_uid;
467 	get_user_groups(uv->v.string, gid,
468 			&eventconf.prog_handler.gidc, &eventconf.prog_handler.gidv);
469 
470 	return 0;
471 }
472 
473 static int
cb_option(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)474 cb_option(enum grecs_callback_command cmd, grecs_node_t *node,
475 	  void *varptr, void *cb_data)
476 {
477         grecs_locus_t *locus = &node->locus;
478 	grecs_value_t *val = node->v.value;
479 	struct grecs_list_entry *ep;
480 
481 	ASSERT_SCALAR(cmd, locus);
482 	if (assert_grecs_value_type(&val->locus, val, GRECS_TYPE_LIST))
483 		return 1;
484 
485 	for (ep = val->v.list->head; ep; ep = ep->next)	{
486 		grecs_value_t *vp = ep->data;
487 		if (assert_grecs_value_type(&vp->locus, vp,
488 					    GRECS_TYPE_STRING))
489 			return 1;
490 		if (strcmp(vp->v.string, "nowait") == 0)
491 			eventconf.prog_handler.flags |= HF_NOWAIT;
492 		else if (strcmp(vp->v.string, "wait") == 0)
493 			eventconf.prog_handler.flags &= ~HF_NOWAIT;
494 		else if (strcmp(vp->v.string, "stdout") == 0)
495 			eventconf.prog_handler.flags |= HF_STDOUT;
496 		else if (strcmp(vp->v.string, "stderr") == 0)
497 			eventconf.prog_handler.flags |= HF_STDERR;
498 		else if (strcmp(vp->v.string, "shell") == 0)
499 			eventconf.prog_handler.flags |= HF_SHELL;
500 		else
501 			grecs_error(&vp->locus, 0, _("unrecognized option"));
502 	}
503 	return 0;
504 }
505 
506 static int
cb_environ(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)507 cb_environ(enum grecs_callback_command cmd, grecs_node_t *node,
508 	   void *varptr, void *cb_data)
509 {
510         grecs_locus_t *locus = &node->locus;
511 	grecs_value_t *val = node->v.value;
512 	struct grecs_list_entry *ep;
513 	int i, j;
514 
515 	ASSERT_SCALAR(cmd, locus);
516 	switch (val->type) {
517 	case GRECS_TYPE_STRING:
518 		if (assert_grecs_value_type(&val->locus, val,
519 					    GRECS_TYPE_STRING))
520 			return 1;
521 		i = prog_handler_envrealloc(&eventconf.prog_handler, 1);
522 		eventconf.prog_handler.env[i] = estrdup(val->v.string);
523 		eventconf.prog_handler.env[i+1] = NULL;
524 		break;
525 
526 	case GRECS_TYPE_ARRAY:
527 		j = prog_handler_envrealloc(&eventconf.prog_handler, val->v.arg.c);
528 		for (i = 0; i < val->v.arg.c; i++, j++) {
529 			if (assert_grecs_value_type(&val->v.arg.v[i]->locus,
530 						    val->v.arg.v[i],
531 						    GRECS_TYPE_STRING))
532 				return 1;
533 			eventconf.prog_handler.env[j] = estrdup(val->v.arg.v[i]->v.string);
534 		}
535 		eventconf.prog_handler.env[j] = NULL;
536 		break;
537 
538 	case GRECS_TYPE_LIST:
539 		j = prog_handler_envrealloc(&eventconf.prog_handler,
540 					    val->v.list->count);
541 		for (ep = val->v.list->head; ep; ep = ep->next, j++) {
542 			grecs_value_t *vp = ep->data;
543 			if (assert_grecs_value_type(&vp->locus, vp,
544 						    GRECS_TYPE_STRING))
545 				return 1;
546 			eventconf.prog_handler.env[j] = estrdup(vp->v.string);
547 		}
548 		eventconf.prog_handler.env[j] = NULL;
549 	}
550 	return 0;
551 }
552 
553 static int
file_name_pattern(filpatlist_t * fptr,grecs_value_t * val)554 file_name_pattern(filpatlist_t *fptr, grecs_value_t *val)
555 {
556 	if (assert_grecs_value_type(&val->locus, val, GRECS_TYPE_STRING))
557 		return 1;
558 	return filpatlist_add(fptr, val->v.string, &val->locus);
559 }
560 
561 static int
cb_file_pattern(enum grecs_callback_command cmd,grecs_node_t * node,void * varptr,void * cb_data)562 cb_file_pattern(enum grecs_callback_command cmd, grecs_node_t *node,
563 		void *varptr, void *cb_data)
564 {
565 	grecs_value_t *val = node->v.value;
566 	filpatlist_t *fpat = varptr;
567 	struct grecs_list_entry *ep;
568 	int i;
569 
570 	ASSERT_SCALAR(cmd, &node->locus);
571 
572 	switch (val->type) {
573 	case GRECS_TYPE_STRING:
574 		file_name_pattern(fpat, val);
575 		break;
576 
577 	case GRECS_TYPE_ARRAY:
578 		for (i = 0; i < val->v.arg.c; i++)
579 			if (file_name_pattern(fpat, val->v.arg.v[i]))
580 				break;
581 		break;
582 
583 	case GRECS_TYPE_LIST:
584 		for (ep = val->v.list->head; ep; ep = ep->next)
585 			if (file_name_pattern(fpat,
586 					      (grecs_value_t *) ep->data))
587 				break;
588 		break;
589 	}
590 
591 	return 0;
592 }
593 
594 static struct grecs_keyword watcher_kw[] = {
595 	{ "path", NULL, N_("Pathname to watch"),
596 	  grecs_type_string, GRECS_DFLT, &eventconf.pathlist, 0,
597 	  cb_path },
598 	{ "event", NULL, N_("Events to watch for"),
599 	  grecs_type_string, GRECS_LIST, &eventconf.ev_mask, 0,
600 	  cb_eventlist },
601 	{ "file", N_("regexp"), N_("Files to watch for"),
602 	  grecs_type_string, GRECS_LIST, &eventconf.fpat, 0,
603 	  cb_file_pattern },
604 	{ "command", NULL, N_("Command to execute on event"),
605 	  grecs_type_string, GRECS_DFLT, &eventconf.prog_handler.command },
606 	{ "user", N_("name"), N_("Run command as this user"),
607 	  grecs_type_string, GRECS_DFLT, NULL, 0,
608 	  cb_user },
609 	{ "timeout", N_("seconds"), N_("Timeout for the command"),
610 	  grecs_type_uint, GRECS_DFLT, &eventconf.prog_handler.timeout },
611 	{ "option", NULL, N_("List of additional options"),
612 	  grecs_type_string, GRECS_LIST, NULL, 0,
613 	  cb_option },
614 	{ "environ", N_("<arg: string> <arg: string>..."),
615 	  N_("Modify environment"),
616 	  grecs_type_string, GRECS_DFLT, NULL, 0,
617 	  cb_environ },
618 	{ NULL }
619 };
620 
621 static struct grecs_keyword direvent_kw[] = {
622 	{ "user", NULL, N_("Run as this user"),
623 	  grecs_type_string, GRECS_DFLT, &user },
624 	{ "foreground", NULL, N_("Run in foreground"),
625 	  grecs_type_bool, GRECS_DFLT, &foreground },
626 	{ "pidfile", N_("file"), N_("Set pid file name"),
627 	  grecs_type_string, GRECS_DFLT, &pidfile },
628 	{ "syslog", NULL, N_("Configure syslog logging"),
629 	  grecs_type_section, GRECS_DFLT, NULL, 0, NULL, NULL, syslog_kw },
630 	{ "debug", N_("level"), N_("Set debug level"),
631 	  grecs_type_int, GRECS_DFLT, &debug_level },
632 	{ "watcher", NULL, N_("Configure event watcher"),
633 	  grecs_type_section, GRECS_DFLT, NULL, 0,
634 	  cb_watcher, NULL, watcher_kw },
635 	{ NULL }
636 };
637 
638 
639 void
config_help()640 config_help()
641 {
642 	static char docstring[] =
643 		N_("Configuration file structure for direvent.\n"
644 		   "For more information, use `info direvent configuration'.");
645 	grecs_print_docstring(docstring, 0, stdout);
646 	grecs_print_statement_array(direvent_kw, 1, 0, stdout);
647 }
648 
649 void
config_init(void)650 config_init(void)
651 {
652 	grecs_include_path_setup(INCLUDE_PATH_ARGS, NULL);
653 }
654 
655 void
config_parse(char const * conffile)656 config_parse(char const *conffile)
657 {
658 	struct grecs_node *tree;
659 
660 	grecs_parser_options = GRECS_OPTION_QUOTED_STRING_CONCAT;
661 	tree = grecs_parse(conffile);
662 	if (!tree)
663 		exit(1);
664 	if (grecs_tree_process(tree, direvent_kw))
665 		exit(1);
666 
667 }
668