Mercurial > wflogs-daemon
view src/wflogs-daemon.cpp @ 2:400b1de6e1c6
allow multiple config contexts
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Fri, 17 May 2013 10:32:12 -0700 |
parents | 0aa1171aebd2 |
children |
line wrap: on
line source
/* Copyright (c) 2007 Carl Byington - 510 Software Group, released under the GPL version 3 or any later version at your choice available at http://www.gnu.org/licenses/gpl-3.0.txt */ // debug levels: // 3 - show syslog lines that match regex // 2 - show files open/close // 1 - show config files loading #include "includes.h" #include <iostream> #include <cstdlib> #include <errno.h> #include <sysexits.h> #include <pthread.h> #include <syslog.h> #include <sys/wait.h> /* header for waitpid() and various macros */ #include <signal.h> /* header for signal functions */ #ifndef HAVE_DAEMON #include "daemon.h" #include "daemon.c" #endif extern "C" { void sigchld(int sig); void sigterm(int sig); } int debug_syslog = 0; bool syslog_opened = false; bool use_syslog = true; // false to printf bool loader_run = true; // used to stop the config loader thread CONFIG *config = NULL; // protected by the config_mutex int generation = 0; // protected by the config_mutex const int maxlen = 1000; // used for snprintf buffers pthread_mutex_t config_mutex; pthread_mutex_t syslog_mutex; //////////////////////////////////////////////// // syslog a message // void my_syslog(const char *text) { if (use_syslog) { pthread_mutex_lock(&syslog_mutex); if (!syslog_opened) { openlog("wflogs-daemon", LOG_PID, LOG_AUTHPRIV); syslog_opened = true; } syslog(LOG_NOTICE, "%s", text); pthread_mutex_unlock(&syslog_mutex); } else { printf("%s\n", text); } } //////////////////////////////////////////////// // reload the config // CONFIG* new_conf(); CONFIG* new_conf() { CONFIG *newc = new CONFIG; pthread_mutex_lock(&config_mutex); newc->generation = generation++; pthread_mutex_unlock(&config_mutex); if (debug_syslog) { char buf[maxlen]; snprintf(buf, sizeof(buf), "loading configuration generation %d", newc->generation); my_syslog(buf); } if (load_conf(*newc, "wflogs-daemon.conf")) { newc->load_time = time(NULL); return newc; } delete newc; return NULL; } //////////////////////////////////////////////// // thread to watch the old config files for changes // and reload when needed. // void* config_loader(void *arg); void* config_loader(void *arg) { typedef set<CONFIG *> configp_set; while (loader_run) { sleep(180); // look for modifications every 3 minutes if (!loader_run) break; CONFIG &dc = *config; time_t then = dc.load_time; struct stat st; bool reload = false; for (string_set::const_iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { const char *fn = *i; if (stat(fn, &st)) reload = true; // file disappeared else if (st.st_mtime > then) reload = true; // file modified if (reload) break; } if (reload) { CONFIG *newc = new_conf(); if (newc) { // replace the global config pointer pthread_mutex_lock(&config_mutex); config = newc; pthread_mutex_unlock(&config_mutex); } else { // failed to load new config my_syslog("failed to load new configuration"); system("echo 'failed to load new /etc/wflogs-daemon.conf' | mail -s 'error in /etc/wflogs-daemon.conf' root"); // update the load time on the current config to prevent complaining every 3 minutes dc.load_time = time(NULL); } } } return NULL; } //////////////////////////////////////////////// // The signal handler function for child process terminations, // called when a child terminates. // void sigchld(int sig) { int status; /* Wait for any child without blocking */ while (waitpid(-1, &status, WNOHANG) > 0) { // ignore child exit status, we only do this to cleanup zombies } } //////////////////////////////////////////////// // The termination signal handler function, called to // request termination of this process. // void sigterm(int sig) { loader_run = false; signal(sig, SIG_DFL); // quit on repeated signals } void usage(char *prog); void usage(char *prog) { fprintf(stderr, "Usage: %s [-d [level]] [-c]\n", prog); fprintf(stderr, "-c will load and dump the config to stdout\n"); fprintf(stderr, "-d will set the syslog message level, currently 0 to 3\n"); } void worker(); void worker() { time_t t = time(NULL); CONFIG *c; pthread_mutex_lock(&config_mutex); c = config; c->reference_count++; pthread_mutex_unlock(&config_mutex); while (loader_run) { if (c != config) { pthread_mutex_lock(&config_mutex); CONFIG *old = c; old->reference_count--; c = config; c->reference_count++; pthread_mutex_unlock(&config_mutex); if (!old->reference_count) { if (debug_syslog) { char buf[maxlen]; snprintf(buf, sizeof(buf), "freeing memory for old configuration generation %d", old->generation); my_syslog(buf); } delete old; // destructor does all the work } } if (!c->read()) { c->sleep(2, t); } } // worker shutting down c->free_all(); } int main(int argc, char *argv[]) { token_init(); bool check = false; int c; const char *args = "d:ch"; extern char *optarg; // Process command line options while ((c = getopt(argc, argv, args)) != -1) { switch (c) { case 'c': check = true; break; case 'd': if (optarg == NULL || *optarg == '\0') debug_syslog = 1; else debug_syslog = atoi(optarg); break; case 'h': default: usage(argv[0]); exit(EX_USAGE); } } if (check) { use_syslog = false; debug_syslog = 10; config = new_conf(); if (config) { config->dump(); delete config; clear_strings(); // for valgrind checking return 0; } else { return 1; // config failed to load } } // switch to background mode if (daemon(1,0) < 0) { fprintf(stderr, "daemon() call failed\n"); exit(EX_UNAVAILABLE); } // write the pid const char *pidpath = "/var/run/wflogs-daemon.pid"; unlink(pidpath); FILE *f = fopen(pidpath, "w"); if (f) { #ifdef linux // from a comment in the DCC source code: // Linux threads are broken. Signals given the // original process are delivered to only the // thread that happens to have that PID. The // sendmail libmilter thread that needs to hear // SIGINT and other signals does not, and that breaks // scripts that need to stop milters. // However, signaling the process group works. fprintf(f, "-%d\n", (u_int)getpgrp()); #else fprintf(f, "%d\n", (u_int)getpid()); #endif fclose(f); } // setup signal handler for termination signals signal(SIGHUP, sigterm); signal(SIGTERM, sigterm); signal(SIGINT, sigterm); // initialize the thread sync objects pthread_mutex_init(&config_mutex, 0); pthread_mutex_init(&syslog_mutex, 0); // load the initial config config = new_conf(); if (!config) { my_syslog("failed to load initial configuration, quitting"); exit(1); } // setup sigchld handler to prevent zombies struct sigaction act; act.sa_handler = sigchld; // Assign sig_chld as our SIGCHLD handler sigemptyset(&act.sa_mask); // We don't want to block any other signals in this example act.sa_flags = SA_NOCLDSTOP; // only want children that have terminated if (sigaction(SIGCHLD, &act, NULL) < 0) { my_syslog("failed to setup SIGCHLD handler"); exit(1); } // only create threads after the fork() in daemon pthread_t tid; if (pthread_create(&tid, 0, config_loader, 0)) my_syslog("failed to create config loader thread"); if (pthread_detach(tid)) my_syslog("failed to detach config loader thread"); worker(); if (config) delete config; // for valgrind checking clear_strings(); // for valgrind checking return EXIT_SUCCESS; }