/* direvent - directory content watcher daemon Copyright (C) 2012-2016 Sergey Poznyakoff Direvent is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Direvent is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with direvent. If not, see . */ #include "direvent.h" #include #include #include #include #include #include #include #include #include "wordsplit.h" #ifndef SYSCONFDIR # define SYSCONFDIR "/etc" #endif #define DEFAULT_CONFFILE SYSCONFDIR "/direvent.conf" /* Configuration settings */ const char *program_name; /* This program name */ const char *conffile = DEFAULT_CONFFILE; int foreground; /* Remain in the foreground */ char *self_test_prog; char *tag; /* Syslog tag */ int facility = -1; /* Use this syslog facility for logging. -1 means log to stderr */ int syslog_include_prio; int debug_level; /* Debug verbosity level */ char *pidfile = NULL; /* Store PID to this file */ char *user = NULL; /* User to run as */ int log_to_stderr = LOG_DEBUG; /* Diagnostic functions */ const char * severity(int prio) { switch (prio) { case LOG_EMERG: return "EMERG"; case LOG_ALERT: return "ALERT"; case LOG_CRIT: return "CRIT"; case LOG_ERR: return "ERROR"; case LOG_WARNING: return "WARNING"; case LOG_NOTICE: return "NOTICE"; case LOG_INFO: return "INFO"; case LOG_DEBUG: return "DEBUG"; } return NULL; } void vdiag(int prio, const char *fmt, va_list ap) { const char *s; va_list tmp; if (log_to_stderr >= prio) { fprintf(stderr, "%s: ", program_name); s = severity(prio); if (s) fprintf(stderr, "[%s] ", s); va_copy(tmp, ap); vfprintf(stderr, fmt, tmp); fputc('\n', stderr); va_end(tmp); } if (facility > 0) { if (syslog_include_prio && (s = severity(prio)) != NULL) { static char *fmtbuf; static size_t fmtsize; size_t len = strlen(fmt) + strlen(s) + 4; char *p; if (len > fmtsize) { fmtbuf = erealloc(fmtbuf, len); fmtsize = len; } p = fmtbuf; *p++ = '['; while (*s) *p++ = *s++; *p++ = ']'; *p++ = ' '; while (*p++ = *fmt++); vsyslog(prio, fmtbuf, ap); } else vsyslog(prio, fmt, ap); } } void diag(int prio, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vdiag(prio, fmt, ap); va_end(ap); } void debugprt(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vdiag(LOG_DEBUG, fmt, ap); va_end(ap); } /* Memory allocation with error checking */ void * emalloc(size_t size) { void *p = malloc(size); if (!p) { diag(LOG_CRIT, _("not enough memory")); exit(2); } return p; } void * ecalloc(size_t nmemb, size_t size) { void *p = calloc(nmemb, size); if (!p) { diag(LOG_CRIT, "not enough memory"); exit(2); } return p; } void * erealloc(void *ptr, size_t size) { void *p = realloc(ptr, size); if (!p) { diag(LOG_CRIT, _("not enough memory")); exit(2); } return p; } char * estrdup(const char *str) { size_t len = strlen(str); char *p = emalloc(len + 1); memcpy(p, str, len); p[len] = 0; return p; } /* Create a full file name from directory and file name */ char * mkfilename(const char *dir, const char *file) { char *tmp; size_t dirlen = strlen(dir); size_t fillen = strlen(file); size_t len; if (!file || file[0] == 0) return strdup(dir); while (dirlen > 0 && dir[dirlen-1] == '/') dirlen--; len = dirlen + (dir[0] ? 1 : 0) + fillen; tmp = malloc(len + 1); if (tmp) { memcpy(tmp, dir, dirlen); if (dir[0]) tmp[dirlen++] = '/'; memcpy(tmp + dirlen, file, fillen); tmp[len] = 0; } return tmp; } int trans_strtotok(struct transtab *tab, const char *str, int *ret) { for (; tab->name; tab++) if (strcmp(tab->name, str) == 0) { *ret = tab->tok; return 0; } return -1; } char * trans_toktostr(struct transtab *tab, int tok) { for (; tab->name; tab++) if (tab->tok == tok) return tab->name; return NULL; } char * trans_toknext(struct transtab *tab, int tok, int *next) { int i; for (i = *next; tab[i].name; i++) if (tab[i].tok & tok) { *next = i + 1; return tab[i].name; } *next = i; return NULL; } char * trans_tokfirst(struct transtab *tab, int tok, int *next) { *next = 0; return trans_toknext(tab, tok, next); } /* Command line processing and auxiliary functions */ static void set_program_name(const char *arg) { char *p = strrchr(arg, '/'); if (p) program_name = p + 1; else program_name = arg; } void signal_setup(void (*sf) (int)) { static int sigv[] = { SIGTERM, SIGQUIT, SIGINT, SIGHUP, SIGALRM, SIGUSR1, SIGUSR1, SIGCHLD }; sigv_set_all(sf, NITEMS(sigv), sigv, NULL); } void storepid(const char *pidfile) { FILE *fp = fopen(pidfile, "w"); if (!fp) { diag(LOG_ERR, _("cannot open pidfile %s for writing: %s"), pidfile, strerror(errno)); } else { fprintf(fp, "%lu\n", (unsigned long) getpid()); fclose(fp); } } static int membergid(gid_t gid, size_t gc, gid_t *gv) { int i; for (i = 0; i < gc; i++) if (gv[i] == gid) return 1; return 0; } static void get_user_groups(uid_t uid, size_t *pgidc, gid_t **pgidv) { size_t gidc = 0, n = 0; gid_t *gidv = NULL; struct passwd *pw; struct group *gr; pw = getpwuid(uid); if (!pw) { diag(LOG_ERR, 0, _("no user with UID %lu"), (unsigned long)uid); exit(2); } n = 32; gidv = ecalloc(n, sizeof(gidv[0])); gidv[0] = pw->pw_gid; gidc = 1; setgrent(); while (gr = getgrent()) { char **p; for (p = gr->gr_mem; *p; p++) if (strcmp(*p, pw->pw_name) == 0) { if (n == gidc) { n += 32; gidv = erealloc(gidv, n * sizeof(gidv[0])); } if (!membergid(gr->gr_gid, gidc, gidv)) gidv[gidc++] = gr->gr_gid; } } endgrent(); *pgidc = gidc; *pgidv = gidv; } void setuser(const char *user) { struct passwd *pw; size_t gidc; gid_t *gidv; pw = getpwnam(user); if (!pw) { diag(LOG_CRIT, "getpwnam(%s): %s", user, strerror(errno)); exit(2); } if (pw->pw_uid == 0) return; get_user_groups(pw->pw_uid, &gidc, &gidv); if (setgroups(gidc, gidv) < 0) { diag(LOG_CRIT, "setgroups: %s", strerror(errno)); exit(2); } free(gidv); if (setgid(pw->pw_gid)) { diag(LOG_CRIT, "setgid(%lu): %s", (unsigned long) pw->pw_gid, strerror(errno)); exit(2); } if (setuid(pw->pw_uid)) { diag(LOG_CRIT, "setuid(%lu): %s", (unsigned long) pw->pw_uid, strerror(errno)); exit(2); } } void ev_log(int flags, struct watchpoint *dp) { int i; char *p; if (debug_level > 0) { for (p = trans_tokfirst(sysev_transtab, flags, &i); p; p = trans_toknext(sysev_transtab, flags, &i)) debug(1, ("%s: %s", dp->dirname, p)); } } /* Initialize generic event table */ void genev_init() { int i; for (i = 0; i < genev_xlat[i].gen_mask; i++) defevt(trans_toktostr(genev_transtab, genev_xlat[i].gen_mask), &genev_xlat[i], 0); } int signo = 0; int stop = 0; pid_t self_test_pid; int exit_code = 0; void sigmain(int sig) { signo = sig; switch (signo) { case SIGCHLD: case SIGALRM: break; default: stop = 1; } } void self_test() { pid_t pid; char *args[4]; pid = fork(); if (pid == (pid_t)-1) { diag(LOG_CRIT, _("cannot run `%s': fork failed: %s"), self_test_prog, strerror(errno)); exit(2); } if (pid != 0) { self_test_pid = pid; return; } args[0] = "/bin/sh"; args[1] = "-c"; args[2] = self_test_prog; args[3] = NULL; execv(args[0], args); diag(LOG_ERR, "execv: %s: %s", self_test_prog, strerror(errno)); _exit(127); } #if USE_IFACE == IFACE_INOTIFY # define INTERFACE "inotify" #elif USE_IFACE == IFACE_KQUEUE # define INTERFACE "kqueue" #endif static int opt_debug_level = 0; static int opt_foreground = 0; static char *opt_pidfile = NULL; static char *opt_user = NULL; static int opt_facility = -1; static int lint_only = 0; #include "cmdline.h" int main(int argc, char **argv) { int i; #ifdef ENABLE_NLS setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif set_program_name(argv[0]); tag = estrdup(program_name); genev_init(); config_init(); parse_options(argc, argv, &i); argc -= i; argv += i; switch (argc) { default: diag(LOG_CRIT, _("too many arguments")); exit(1); case 1: conffile = argv[0]; break; case 0: break; } config_parse(conffile); if (lint_only) return 0; if (opt_debug_level) debug_level += opt_debug_level; if (opt_foreground) foreground = opt_foreground; if (opt_pidfile) pidfile = opt_pidfile; if (opt_facility != -1) facility = opt_facility; if (!foreground && facility <= 0) facility = LOG_DAEMON; if (opt_user) user = opt_user; if (facility > 0) { openlog(tag, LOG_PID, facility); grecs_log_to_stderr = 0; } if (foreground) setup_watchers(); else { /* Become a daemon */ if (detach(setup_watchers)) { diag(LOG_CRIT, "daemon: %s", strerror(errno)); exit(1); } log_to_stderr = -1; } diag(LOG_INFO, _("%s %s started"), program_name, VERSION); /* Write pidfile */ if (pidfile) storepid(pidfile); /* Relinquish superuser privileges */ if (user && getuid() == 0) setuser(user); signal_setup(sigmain); if (self_test_prog) self_test(); /* Main loop */ while (!stop && sysev_select() == 0) { process_timeouts(); process_cleanup(0); } shutdown_watchers(); diag(LOG_INFO, _("%s %s stopped"), program_name, VERSION); if (pidfile) unlink(pidfile); return exit_code; }