Scheduler: Daemon class: Better IPC in daemonize()
g0dil [Mon, 12 Nov 2007 11:40:58 +0000 (11:40 +0000)]
Scheduler: Daemon class: Implement default argument parsing

git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@506 270642c3-0616-0410-b53a-bc976706d245

Scheduler/Daemon.cc
Scheduler/Daemon.hh
Scheduler/Daemon.test.cc

index 534b333..3782974 100644 (file)
@@ -36,6 +36,8 @@
 #include <signal.h>
 #include <sstream>
 #include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/trim.hpp>
 #include "../Utils/Exception.hh"
 #include "../Utils/membind.hh"
 
@@ -67,23 +69,30 @@ prefix_ bool senf::Daemon::daemon()
 
 prefix_ void senf::Daemon::consoleLog(std::string const & path, StdStream which)
 {
+    switch (which) {
+    case StdOut : stdoutLog_ = path; break;
+    case StdErr : stderrLog_ = path; break;
+    case Both : stdoutLog_ = path; stderrLog_ = path; break;
+    }
+}
+
+
+prefix_ void senf::Daemon::openLog()
+{
     int fd (-1);
-    if (! path.empty()) {
-        fd = ::open(path.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
+    if (! stdoutLog_.empty()) {
+        fd = ::open(stdoutLog_.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
         if (fd < 0)
             throwErrno("::open()");
-    }
-    switch (which) {
-    case StdOut:
         stdout_ = fd;
-        break;
-    case StdErr:
+    }
+    if (stderrLog_ == stdoutLog_)
         stderr_ = fd;
-        break;
-    case Both:
-        stdout_ = fd;
+    else if (! stderrLog_.empty()) {
+        fd = ::open(stdoutLog_.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
+        if (fd < 0)
+            throwErrno("::open()");
         stderr_ = fd;
-        break;
     }
 }
 
@@ -92,19 +101,49 @@ prefix_ void senf::Daemon::pidFile(std::string const & f)
     pidfile_ = f;
 }
 
+namespace {
+    bool signaled (false);
+    void waitusr(int) {
+        signaled = true;
+    }
+}
+
 prefix_ void senf::Daemon::detach()
 {
     if (daemonize_) {
+        // Wow .. ouch .. 
+        // To ensure all data is written to the console log file in the correct order, we suspend
+        // execution here until the parent process tells us to continue via SIGUSR1: We block
+        // SIGUSR1 and install our own signal handler saving the old handler and signal mask. Then
+        // we close stdin/stderr which will send a HUP condition to the parent process. We wait for
+        // SIGUSR1 and reinstall the old signal mask and action.
+        ::sigset_t oldsig;
+        ::sigset_t usrsig;
+        ::sigemptyset(&usrsig);
+        LIBC_CALL( ::sigaddset, (&usrsig, SIGUSR1) );
+        LIBC_CALL( ::sigprocmask, (SIG_BLOCK, &usrsig, &oldsig) );
+        struct ::sigaction oldact;
+        struct ::sigaction usract;
+        ::memset(&usract, 0, sizeof(usract));
+        usract.sa_handler = &waitusr;
+        LIBC_CALL( ::sigaction, (SIGUSR1, &usract, &oldact) );
+        ::sigset_t waitsig (oldsig);
+        LIBC_CALL( ::sigdelset, (&waitsig, SIGUSR1) );
+
         LIBC_CALL_RV( nul, ::open, ("/dev/null", O_WRONLY) );
         LIBC_CALL( ::dup2, (stdout_ == -1 ? nul : stdout_, 1) );
         LIBC_CALL( ::dup2, (stderr_ == -1 ? nul : stderr_, 2) );
         LIBC_CALL( ::close, (nul) );
 
-        // We need to wait here to give the daemon watcher time to flush all data to the log file.
-        struct timespec ts;
-        ts.tv_sec = 0;
-        ts.tv_nsec = 100 * 1000000ul;
-        while (::nanosleep(&ts,&ts) < 0 && errno == EINTR) ;
+        signaled = false;
+        while (! signaled) {
+            ::sigsuspend(&waitsig);
+            if (errno != EINTR)
+                throwErrno("::sigsuspend()");
+        }
+
+        LIBC_CALL( ::sigaction, (SIGUSR1, &oldact, 0) );
+        LIBC_CALL( ::sigprocmask, (SIG_SETMASK, &oldsig, 0) );
     }
 }
 
@@ -121,8 +160,10 @@ prefix_ int senf::Daemon::start(int argc, char const ** argv)
 
         configure();
 
-        if (daemonize_)
+        if (daemonize_) {
+            openLog();
             fork();
+        }
         if (! pidfile_.empty() && ! pidfileCreate()) {
             std::cerr << "\n*** PID file '" << pidfile_ << "' creation failed. Daemon running ?" 
                       << std::endl;
@@ -160,7 +201,31 @@ prefix_ senf::Daemon::Daemon()
 // private members
 
 prefix_ void senf::Daemon::configure()
-{}
+{
+    for (int i (1); i<argc_; ++i) {
+        if (argv_[i] == std::string("--no-daemon"))
+            daemonize(false);
+        else if (boost::starts_with(argv_[i], std::string("--console-log="))) {
+            std::string arg (std::string(argv_[i]), 14u);
+            std::string::size_type komma (arg.find(','));
+            if (komma == std::string::npos) {
+                boost::trim(arg);
+                consoleLog(arg);
+            } else {
+                std::string arg1 (arg,0,komma);
+                std::string arg2 (arg,komma+1);
+                boost::trim(arg1);
+                boost::trim(arg2);
+                if (arg1 == std::string("none")) consoleLog("",StdOut);
+                else if (! arg1.empty() )        consoleLog(arg1, StdOut);
+                if (arg2 == std::string("none")) consoleLog("",StdErr);
+                else if (! arg2.empty() )        consoleLog(arg2, StdErr);
+            }
+        }
+        else if (boost::starts_with(argv_[i], std::string("--pid-file="))) 
+            pidFile(std::string(std::string(argv_[i]), 11u));
+    }
+}
 
 prefix_ void senf::Daemon::main()
 {
@@ -339,6 +404,8 @@ prefix_ void senf::detail::DaemonWatcher::pipeClosed(int id)
     if (coutpipe_ == -1 && cerrpipe_ == -1) {
         if (sigChld_)
             childDied(); // does not return
+        if (::kill(childPid_, SIGUSR1) < 0)
+            if (errno != ESRCH) throwErrno("::kill()");
         Scheduler::instance().timeout(
             Scheduler::instance().eventTime() + ClockService::seconds(1),
             senf::membind(&DaemonWatcher::childOk, this));
index ab70527..3526cb3 100644 (file)
@@ -135,13 +135,14 @@ namespace senf {
     protected:
         Daemon();
 
+        virtual void configure();       ///< Called before forking to configure the daemon class
+
 #   ifdef DOXYGEN
     protected:
 #   else
     private:
 #   endif
 
-        virtual void configure();       ///< Called before forking to configure the daemon class
         virtual void main();            ///< Called after forking to execute the main application
                                         /**< The default implementation will call init(), detach()
                                              and then run(). It is preferred to override init() and
@@ -161,6 +162,7 @@ namespace senf {
                                              implementation is not overridden. */
     private:
 
+        void openLog();
         void fork();
         bool pidfileCreate();
 
@@ -168,6 +170,8 @@ namespace senf {
         char const ** argv_;
 
         bool daemonize_;
+        std::string stdoutLog_;
+        std::string stderrLog_;
         int stdout_;
         int stderr_;
         std::string pidfile_;
index ff44b22..aceed1c 100644 (file)
@@ -56,8 +56,9 @@ namespace {
     {
         void configure() { 
             std::cout << "Running configure()" << std::endl; 
-            pidFile("testDaemon.pid");
-            consoleLog("testDaemon.log");
+            pidFile("invalid.pid");
+            consoleLog("invalid.log");
+            senf::Daemon::configure();
         }
 
         void init() { 
@@ -92,9 +93,13 @@ namespace {
 
 BOOST_AUTO_UNIT_TEST(testDaemon)
 {
-    char const * args[] = { "run", 0 };
-    BOOST_CHECK_EQUAL( run(1,args), 0 );
+    char const * args[] = { "run", 
+                            "--console-log=testDaemon.log,none", 
+                            "--pid-file=testDaemon.pid" };
+    BOOST_CHECK_EQUAL( run(sizeof(args)/sizeof(*args),args), 0 );
 
+    BOOST_CHECK( ! boost::filesystem::exists("invalid.log") );
+    BOOST_CHECK( ! boost::filesystem::exists("invalid.pid") );
     BOOST_CHECK( boost::filesystem::exists("testDaemon.pid") );
     delay(1000);
     BOOST_CHECK( ! boost::filesystem::exists("testDaemon.pid") );