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 <stdarg.h>
19 #include <fcntl.h>
20 #include <getopt.h>
21 #include <pwd.h>
22 #include <grp.h>
23 #include <signal.h>
24 #include <grecs.h>
25 #include <locale.h>
26 #include "wordsplit.h"
27
28 #ifndef SYSCONFDIR
29 # define SYSCONFDIR "/etc"
30 #endif
31 #define DEFAULT_CONFFILE SYSCONFDIR "/direvent.conf"
32
33 /* Configuration settings */
34 const char *program_name; /* This program name */
35 const char *conffile = DEFAULT_CONFFILE;
36 int foreground; /* Remain in the foreground */
37 char *self_test_prog;
38 char *tag; /* Syslog tag */
39 int facility = -1; /* Use this syslog facility for logging.
40 -1 means log to stderr */
41 int syslog_include_prio;
42 int debug_level; /* Debug verbosity level */
43 char *pidfile = NULL; /* Store PID to this file */
44 char *user = NULL; /* User to run as */
45
46 int log_to_stderr = LOG_DEBUG;
47
48
49 /* Diagnostic functions */
50 const char *
severity(int prio)51 severity(int prio)
52 {
53 switch (prio) {
54 case LOG_EMERG:
55 return "EMERG";
56 case LOG_ALERT:
57 return "ALERT";
58 case LOG_CRIT:
59 return "CRIT";
60 case LOG_ERR:
61 return "ERROR";
62 case LOG_WARNING:
63 return "WARNING";
64 case LOG_NOTICE:
65 return "NOTICE";
66 case LOG_INFO:
67 return "INFO";
68 case LOG_DEBUG:
69 return "DEBUG";
70 }
71 return NULL;
72 }
73
74 void
vdiag(int prio,const char * fmt,va_list ap)75 vdiag(int prio, const char *fmt, va_list ap)
76 {
77 const char *s;
78 va_list tmp;
79
80 if (log_to_stderr >= prio) {
81 fprintf(stderr, "%s: ", program_name);
82 s = severity(prio);
83 if (s)
84 fprintf(stderr, "[%s] ", s);
85 va_copy(tmp, ap);
86 vfprintf(stderr, fmt, tmp);
87 fputc('\n', stderr);
88 va_end(tmp);
89 }
90
91 if (facility > 0) {
92 if (syslog_include_prio && (s = severity(prio)) != NULL) {
93 static char *fmtbuf;
94 static size_t fmtsize;
95 size_t len = strlen(fmt) + strlen(s) + 4;
96 char *p;
97
98 if (len > fmtsize) {
99 fmtbuf = erealloc(fmtbuf, len);
100 fmtsize = len;
101 }
102
103 p = fmtbuf;
104 *p++ = '[';
105 while (*s)
106 *p++ = *s++;
107 *p++ = ']';
108 *p++ = ' ';
109 while (*p++ = *fmt++);
110 vsyslog(prio, fmtbuf, ap);
111 } else
112 vsyslog(prio, fmt, ap);
113 }
114 }
115
116 void
diag(int prio,const char * fmt,...)117 diag(int prio, const char *fmt, ...)
118 {
119 va_list ap;
120
121 va_start(ap, fmt);
122 vdiag(prio, fmt, ap);
123 va_end(ap);
124 }
125
126 void
debugprt(const char * fmt,...)127 debugprt(const char *fmt, ...)
128 {
129 va_list ap;
130
131 va_start(ap, fmt);
132 vdiag(LOG_DEBUG, fmt, ap);
133 va_end(ap);
134 }
135
136 /* Memory allocation with error checking */
137 void *
emalloc(size_t size)138 emalloc(size_t size)
139 {
140 void *p = malloc(size);
141 if (!p) {
142 diag(LOG_CRIT, _("not enough memory"));
143 exit(2);
144 }
145 return p;
146 }
147
148 void *
ecalloc(size_t nmemb,size_t size)149 ecalloc(size_t nmemb, size_t size)
150 {
151 void *p = calloc(nmemb, size);
152 if (!p) {
153 diag(LOG_CRIT, "not enough memory");
154 exit(2);
155 }
156 return p;
157 }
158
159 void *
erealloc(void * ptr,size_t size)160 erealloc(void *ptr, size_t size)
161 {
162 void *p = realloc(ptr, size);
163 if (!p) {
164 diag(LOG_CRIT, _("not enough memory"));
165 exit(2);
166 }
167 return p;
168 }
169
170 char *
estrdup(const char * str)171 estrdup(const char *str)
172 {
173 size_t len = strlen(str);
174 char *p = emalloc(len + 1);
175 memcpy(p, str, len);
176 p[len] = 0;
177 return p;
178 }
179
180 /* Create a full file name from directory and file name */
181 char *
mkfilename(const char * dir,const char * file)182 mkfilename(const char *dir, const char *file)
183 {
184 char *tmp;
185 size_t dirlen = strlen(dir);
186 size_t fillen = strlen(file);
187 size_t len;
188
189 if (!file || file[0] == 0)
190 return strdup(dir);
191 while (dirlen > 0 && dir[dirlen-1] == '/')
192 dirlen--;
193
194 len = dirlen + (dir[0] ? 1 : 0) + fillen;
195 tmp = malloc(len + 1);
196 if (tmp) {
197 memcpy(tmp, dir, dirlen);
198 if (dir[0])
199 tmp[dirlen++] = '/';
200 memcpy(tmp + dirlen, file, fillen);
201 tmp[len] = 0;
202 }
203 return tmp;
204 }
205
206 int
trans_strtotok(struct transtab * tab,const char * str,int * ret)207 trans_strtotok(struct transtab *tab, const char *str, int *ret)
208 {
209 for (; tab->name; tab++)
210 if (strcmp(tab->name, str) == 0) {
211 *ret = tab->tok;
212 return 0;
213 }
214 return -1;
215 }
216
217 char *
trans_toktostr(struct transtab * tab,int tok)218 trans_toktostr(struct transtab *tab, int tok)
219 {
220 for (; tab->name; tab++)
221 if (tab->tok == tok)
222 return tab->name;
223 return NULL;
224 }
225
226 char *
trans_toknext(struct transtab * tab,int tok,int * next)227 trans_toknext(struct transtab *tab, int tok, int *next)
228 {
229 int i;
230
231 for (i = *next; tab[i].name; i++)
232 if (tab[i].tok & tok) {
233 *next = i + 1;
234 return tab[i].name;
235 }
236 *next = i;
237 return NULL;
238 }
239
240 char *
trans_tokfirst(struct transtab * tab,int tok,int * next)241 trans_tokfirst(struct transtab *tab, int tok, int *next)
242 {
243 *next = 0;
244 return trans_toknext(tab, tok, next);
245 }
246
247
248 /* Command line processing and auxiliary functions */
249
250 static void
set_program_name(const char * arg)251 set_program_name(const char *arg)
252 {
253 char *p = strrchr(arg, '/');
254 if (p)
255 program_name = p + 1;
256 else
257 program_name = arg;
258 }
259
260
261 void
signal_setup(void (* sf)(int))262 signal_setup(void (*sf) (int))
263 {
264 static int sigv[] = { SIGTERM, SIGQUIT, SIGINT, SIGHUP, SIGALRM,
265 SIGUSR1, SIGUSR1, SIGCHLD };
266 sigv_set_all(sf, NITEMS(sigv), sigv, NULL);
267 }
268
269 void
storepid(const char * pidfile)270 storepid(const char *pidfile)
271 {
272 FILE *fp = fopen(pidfile, "w");
273 if (!fp) {
274 diag(LOG_ERR, _("cannot open pidfile %s for writing: %s"),
275 pidfile, strerror(errno));
276 } else {
277 fprintf(fp, "%lu\n", (unsigned long) getpid());
278 fclose(fp);
279 }
280 }
281
282 static int
membergid(gid_t gid,size_t gc,gid_t * gv)283 membergid(gid_t gid, size_t gc, gid_t *gv)
284 {
285 int i;
286 for (i = 0; i < gc; i++)
287 if (gv[i] == gid)
288 return 1;
289 return 0;
290 }
291
292 static void
get_user_groups(uid_t uid,size_t * pgidc,gid_t ** pgidv)293 get_user_groups(uid_t uid, size_t *pgidc, gid_t **pgidv)
294 {
295 size_t gidc = 0, n = 0;
296 gid_t *gidv = NULL;
297 struct passwd *pw;
298 struct group *gr;
299
300 pw = getpwuid(uid);
301 if (!pw) {
302 diag(LOG_ERR, 0, _("no user with UID %lu"),
303 (unsigned long)uid);
304 exit(2);
305 }
306
307 n = 32;
308 gidv = ecalloc(n, sizeof(gidv[0]));
309
310 gidv[0] = pw->pw_gid;
311 gidc = 1;
312
313 setgrent();
314 while (gr = getgrent()) {
315 char **p;
316 for (p = gr->gr_mem; *p; p++)
317 if (strcmp(*p, pw->pw_name) == 0) {
318 if (n == gidc) {
319 n += 32;
320 gidv = erealloc(gidv,
321 n * sizeof(gidv[0]));
322 }
323 if (!membergid(gr->gr_gid, gidc, gidv))
324 gidv[gidc++] = gr->gr_gid;
325 }
326 }
327 endgrent();
328 *pgidc = gidc;
329 *pgidv = gidv;
330 }
331
332 void
setuser(const char * user)333 setuser(const char *user)
334 {
335 struct passwd *pw;
336 size_t gidc;
337 gid_t *gidv;
338
339 pw = getpwnam(user);
340 if (!pw) {
341 diag(LOG_CRIT, "getpwnam(%s): %s", user, strerror(errno));
342 exit(2);
343 }
344 if (pw->pw_uid == 0)
345 return;
346
347 get_user_groups(pw->pw_uid, &gidc, &gidv);
348 if (setgroups(gidc, gidv) < 0) {
349 diag(LOG_CRIT, "setgroups: %s", strerror(errno));
350 exit(2);
351 }
352 free(gidv);
353
354 if (setgid(pw->pw_gid)) {
355 diag(LOG_CRIT, "setgid(%lu): %s", (unsigned long) pw->pw_gid,
356 strerror(errno));
357 exit(2);
358 }
359 if (setuid(pw->pw_uid)) {
360 diag(LOG_CRIT, "setuid(%lu): %s", (unsigned long) pw->pw_uid,
361 strerror(errno));
362 exit(2);
363 }
364 }
365
366 void
ev_log(int flags,struct watchpoint * dp)367 ev_log(int flags, struct watchpoint *dp)
368 {
369 int i;
370 char *p;
371
372 if (debug_level > 0) {
373 for (p = trans_tokfirst(sysev_transtab, flags, &i); p;
374 p = trans_toknext(sysev_transtab, flags, &i))
375 debug(1, ("%s: %s", dp->dirname, p));
376 }
377 }
378
379
380 /* Initialize generic event table */
381 void
genev_init()382 genev_init()
383 {
384 int i;
385
386 for (i = 0; i < genev_xlat[i].gen_mask; i++)
387 defevt(trans_toktostr(genev_transtab, genev_xlat[i].gen_mask),
388 &genev_xlat[i], 0);
389 }
390
391
392 int signo = 0;
393 int stop = 0;
394
395 pid_t self_test_pid;
396 int exit_code = 0;
397
398 void
sigmain(int sig)399 sigmain(int sig)
400 {
401 signo = sig;
402 switch (signo) {
403 case SIGCHLD:
404 case SIGALRM:
405 break;
406 default:
407 stop = 1;
408 }
409 }
410
411 void
self_test()412 self_test()
413 {
414 pid_t pid;
415 char *args[4];
416
417 pid = fork();
418 if (pid == (pid_t)-1) {
419 diag(LOG_CRIT,
420 _("cannot run `%s': fork failed: %s"),
421 self_test_prog, strerror(errno));
422 exit(2);
423 }
424
425 if (pid != 0) {
426 self_test_pid = pid;
427 return;
428 }
429
430 args[0] = "/bin/sh";
431 args[1] = "-c";
432 args[2] = self_test_prog;
433 args[3] = NULL;
434 execv(args[0], args);
435
436 diag(LOG_ERR, "execv: %s: %s", self_test_prog, strerror(errno));
437 _exit(127);
438 }
439
440
441 #if USE_IFACE == IFACE_INOTIFY
442 # define INTERFACE "inotify"
443 #elif USE_IFACE == IFACE_KQUEUE
444 # define INTERFACE "kqueue"
445 #endif
446
447 static int opt_debug_level = 0;
448 static int opt_foreground = 0;
449 static char *opt_pidfile = NULL;
450 static char *opt_user = NULL;
451 static int opt_facility = -1;
452 static int lint_only = 0;
453
454 #include "cmdline.h"
455
456 int
main(int argc,char ** argv)457 main(int argc, char **argv)
458 {
459 int i;
460
461 #ifdef ENABLE_NLS
462 setlocale(LC_ALL, "");
463 bindtextdomain(PACKAGE, LOCALEDIR);
464 textdomain(PACKAGE);
465 #endif
466
467 set_program_name(argv[0]);
468 tag = estrdup(program_name);
469
470 genev_init();
471 config_init();
472
473 parse_options(argc, argv, &i);
474
475 argc -= i;
476 argv += i;
477
478 switch (argc) {
479 default:
480 diag(LOG_CRIT, _("too many arguments"));
481 exit(1);
482 case 1:
483 conffile = argv[0];
484 break;
485 case 0:
486 break;
487 }
488
489 config_parse(conffile);
490 if (lint_only)
491 return 0;
492
493 if (opt_debug_level)
494 debug_level += opt_debug_level;
495 if (opt_foreground)
496 foreground = opt_foreground;
497 if (opt_pidfile)
498 pidfile = opt_pidfile;
499 if (opt_facility != -1)
500 facility = opt_facility;
501 if (!foreground && facility <= 0)
502 facility = LOG_DAEMON;
503 if (opt_user)
504 user = opt_user;
505
506 if (facility > 0) {
507 openlog(tag, LOG_PID, facility);
508 grecs_log_to_stderr = 0;
509 }
510
511 if (foreground)
512 setup_watchers();
513 else {
514 /* Become a daemon */
515 if (detach(setup_watchers)) {
516 diag(LOG_CRIT, "daemon: %s", strerror(errno));
517 exit(1);
518 }
519 log_to_stderr = -1;
520 }
521
522 diag(LOG_INFO, _("%s %s started"), program_name, VERSION);
523
524 /* Write pidfile */
525 if (pidfile)
526 storepid(pidfile);
527
528 /* Relinquish superuser privileges */
529 if (user && getuid() == 0)
530 setuser(user);
531
532 signal_setup(sigmain);
533
534 if (self_test_prog)
535 self_test();
536
537 /* Main loop */
538 while (!stop && sysev_select() == 0) {
539 process_timeouts();
540 process_cleanup(0);
541 watchpoint_gc();
542 }
543
544 shutdown_watchers();
545
546 diag(LOG_INFO, _("%s %s stopped"), program_name, VERSION);
547
548 if (pidfile)
549 unlink(pidfile);
550
551 return exit_code;
552 }
553