diff src/wflogs-daemon.cpp @ 0:0aa1171aebd2 stable-1-0-0

initial version
author Carl Byington <carl@five-ten-sg.com>
date Wed, 15 May 2013 13:15:59 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/wflogs-daemon.cpp	Wed May 15 13:15:59 2013 -0700
@@ -0,0 +1,302 @@
+/*
+
+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;
+}