summaryrefslogtreecommitdiff
path: root/lib/server/Daemon.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/server/Daemon.cpp')
-rw-r--r--lib/server/Daemon.cpp704
1 files changed, 704 insertions, 0 deletions
diff --git a/lib/server/Daemon.cpp b/lib/server/Daemon.cpp
new file mode 100644
index 00000000..bcea638e
--- /dev/null
+++ b/lib/server/Daemon.cpp
@@ -0,0 +1,704 @@
+// distribution boxbackup-0.10 (svn version: 494)
+//
+// Copyright (c) 2003 - 2006
+// Ben Summers and contributors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All use of this software and associated advertising materials must
+// display the following acknowledgment:
+// This product includes software developed by Ben Summers.
+// 4. The names of the Authors may not be used to endorse or promote
+// products derived from this software without specific prior written
+// permission.
+//
+// [Where legally impermissible the Authors do not disclaim liability for
+// direct physical injury or death caused solely by defects in the software
+// unless it is modified by a third party.]
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT,
+// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+//
+//
+// --------------------------------------------------------------------------
+//
+// File
+// Name: Daemon.cpp
+// Purpose: Basic daemon functionality
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <stdarg.h>
+
+#ifdef HAVE_SYSLOG_H
+ #include <syslog.h>
+#endif
+
+#include "Daemon.h"
+#include "Configuration.h"
+#include "ServerException.h"
+#include "Guards.h"
+#include "UnixUser.h"
+#include "FileModificationTime.h"
+
+#include "MemLeakFindOn.h"
+
+Daemon *Daemon::spDaemon = 0;
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::Daemon()
+// Purpose: Constructor
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+Daemon::Daemon()
+ : mpConfiguration(0),
+ mReloadConfigWanted(false),
+ mTerminateWanted(false)
+{
+ if(spDaemon != 0)
+ {
+ THROW_EXCEPTION(ServerException, AlreadyDaemonConstructed)
+ }
+ spDaemon = this;
+
+ // And in debug builds, we'll switch on assert failure logging to syslog
+ ASSERT_FAILS_TO_SYSLOG_ON
+ // And trace goes to syslog too
+ TRACE_TO_SYSLOG(true)
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::~Daemon()
+// Purpose: Destructor
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+Daemon::~Daemon()
+{
+ if(mpConfiguration)
+ {
+ delete mpConfiguration;
+ mpConfiguration = 0;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::Main(const char *, int, const char *[])
+// Purpose: Starts the daemon off -- equivalent of C main() function
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[])
+{
+ // Banner (optional)
+ {
+ const char *banner = DaemonBanner();
+ if(banner != 0)
+ {
+ printf("%s", banner);
+ }
+ }
+
+ std::string pidFileName;
+
+ try
+ {
+ // Find filename of config file
+ mConfigFileName = DefaultConfigFile;
+ if(argc >= 2)
+ {
+ // First argument is config file, or it's -c and the next arg is the config file
+ if(::strcmp(argv[1], "-c") == 0 && argc >= 3)
+ {
+ mConfigFileName = argv[2];
+ }
+ else
+ {
+ mConfigFileName = argv[1];
+ }
+ }
+
+ // Test mode with no daemonisation?
+ bool asDaemon = true;
+ if(argc >= 3)
+ {
+ if(::strcmp(argv[2], "SINGLEPROCESS") == 0)
+ {
+ asDaemon = false;
+ }
+ }
+
+ // Load the configuration file.
+ std::string errors;
+ std::auto_ptr<Configuration> pconfig;
+
+ try
+ {
+ pconfig = Configuration::LoadAndVerify(
+ mConfigFileName.c_str(),
+ GetConfigVerify(), errors);
+ }
+ catch(BoxException &e)
+ {
+ if(e.GetType() == CommonException::ExceptionType &&
+ e.GetSubType() == CommonException::OSFileOpenError)
+ {
+ fprintf(stderr, "%s: failed to start: "
+ "failed to open configuration file: "
+ "%s", DaemonName(),
+ mConfigFileName.c_str());
+#ifdef WIN32
+ ::syslog(LOG_ERR, "%s: failed to start: "
+ "failed to open configuration file: "
+ "%s", DaemonName(),
+ mConfigFileName.c_str());
+#endif
+ return 1;
+ }
+
+ throw;
+ }
+
+ // Got errors?
+ if(pconfig.get() == 0 || !errors.empty())
+ {
+ // Tell user about errors
+ fprintf(stderr, "%s: Errors in config file %s:\n%s",
+ DaemonName(), mConfigFileName.c_str(),
+ errors.c_str());
+#ifdef WIN32
+ ::syslog(LOG_ERR, "%s: Errors in config file %s:\n%s",
+ DaemonName(), mConfigFileName.c_str(),
+ errors.c_str());
+#endif
+ // And give up
+ return 1;
+ }
+
+ // Store configuration
+ mpConfiguration = pconfig.release();
+ mLoadedConfigModifiedTime = GetConfigFileModifiedTime();
+
+ // Let the derived class have a go at setting up stuff in the initial process
+ SetupInInitialProcess();
+
+#ifndef WIN32
+ // Set signal handler
+ struct sigaction sa;
+ sa.sa_handler = SignalHandler;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask); // macro
+ if(::sigaction(SIGHUP, &sa, NULL) != 0 || ::sigaction(SIGTERM, &sa, NULL) != 0)
+ {
+ THROW_EXCEPTION(ServerException, DaemoniseFailed)
+ }
+
+ // Server configuration
+ const Configuration &serverConfig(
+ mpConfiguration->GetSubConfiguration("Server"));
+
+ // Open PID file for writing
+ pidFileName = serverConfig.GetKeyValue("PidFile");
+ FileHandleGuard<(O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)> pidFile(pidFileName.c_str());
+
+ // Handle changing to a different user
+ if(serverConfig.KeyExists("User"))
+ {
+ // Config file specifies an user -- look up
+ UnixUser daemonUser(serverConfig.GetKeyValue("User").c_str());
+
+ // Change the owner on the PID file, so it can be deleted properly on termination
+ if(::fchown(pidFile, daemonUser.GetUID(), daemonUser.GetGID()) != 0)
+ {
+ THROW_EXCEPTION(ServerException, CouldNotChangePIDFileOwner)
+ }
+
+ // Change the process ID
+ daemonUser.ChangeProcessUser();
+ }
+
+ if(asDaemon)
+ {
+ // Let's go... Daemonise...
+ switch(::fork())
+ {
+ case -1:
+ // error
+ THROW_EXCEPTION(ServerException, DaemoniseFailed)
+ break;
+
+ default:
+ // parent
+ _exit(0);
+ return 0;
+ break;
+
+ case 0:
+ // child
+ break;
+ }
+
+ // In child
+
+ // Set new session
+ if(::setsid() == -1)
+ {
+ ::syslog(LOG_ERR, "can't setsid");
+ THROW_EXCEPTION(ServerException, DaemoniseFailed)
+ }
+
+ // Fork again...
+ switch(::fork())
+ {
+ case -1:
+ // error
+ THROW_EXCEPTION(ServerException, DaemoniseFailed)
+ break;
+
+ default:
+ // parent
+ _exit(0);
+ return 0;
+ break;
+
+ case 0:
+ // child
+ break;
+ }
+ }
+#endif // ! WIN32
+
+ // open the log
+ ::openlog(DaemonName(), LOG_PID, LOG_DAEMON);
+ // Log the start message
+ ::syslog(LOG_INFO, "Starting daemon (config: %s) (version "
+ BOX_VERSION ")", mConfigFileName.c_str());
+
+#ifndef WIN32
+ // Write PID to file
+ char pid[32];
+ int pidsize = sprintf(pid, "%d", (int)getpid());
+ if(::write(pidFile, pid, pidsize) != pidsize)
+ {
+ ::syslog(LOG_ERR, "can't write pid file");
+ THROW_EXCEPTION(ServerException, DaemoniseFailed)
+ }
+#endif
+
+ // Set up memory leak reporting
+ #ifdef BOX_MEMORY_LEAK_TESTING
+ {
+ char filename[256];
+ sprintf(filename, "%s.memleaks", DaemonName());
+ memleakfinder_setup_exit_report(filename, DaemonName());
+ }
+ #endif // BOX_MEMORY_LEAK_TESTING
+
+ if(asDaemon)
+ {
+#ifndef WIN32
+ // Close standard streams
+ ::close(0);
+ ::close(1);
+ ::close(2);
+
+ // Open and redirect them into /dev/null
+ int devnull = ::open(PLATFORM_DEV_NULL, O_RDWR, 0);
+ if(devnull == -1)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+ // Then duplicate them to all three handles
+ if(devnull != 0) dup2(devnull, 0);
+ if(devnull != 1) dup2(devnull, 1);
+ if(devnull != 2) dup2(devnull, 2);
+ // Close the original handle if it was opened above the std* range
+ if(devnull > 2)
+ {
+ ::close(devnull);
+ }
+#endif // ! WIN32
+
+ // And definitely don't try and send anything to those file descriptors
+ // -- this has in the past sent text to something which isn't expecting it.
+ TRACE_TO_STDOUT(false);
+ }
+ }
+ catch(BoxException &e)
+ {
+ fprintf(stderr, "%s: failed to start: exception %s (%d/%d)\n",
+ DaemonName(), e.what(), e.GetType(), e.GetSubType());
+#ifdef WIN32
+ ::syslog(LOG_ERR, "%s: failed to start: "
+ "exception %s (%d/%d)\n", DaemonName(),
+ e.what(), e.GetType(), e.GetSubType());
+#endif
+ return 1;
+ }
+ catch(std::exception &e)
+ {
+ fprintf(stderr, "%s: failed to start: exception %s\n",
+ DaemonName(), e.what());
+#ifdef WIN32
+ ::syslog(LOG_ERR, "%s: failed to start: exception %s\n",
+ DaemonName(), e.what());
+#endif
+ return 1;
+ }
+ catch(...)
+ {
+ fprintf(stderr, "%s: failed to start: unknown exception\n",
+ DaemonName());
+#ifdef WIN32
+ ::syslog(LOG_ERR, "%s: failed to start: unknown exception\n",
+ DaemonName());
+#endif
+ return 1;
+ }
+
+ // Main Daemon running
+ try
+ {
+ while(!mTerminateWanted)
+ {
+ Run();
+
+ if(mReloadConfigWanted && !mTerminateWanted)
+ {
+ // Need to reload that config file...
+ ::syslog(LOG_INFO, "Reloading configuration "
+ "(config: %s)",
+ mConfigFileName.c_str());
+ std::string errors;
+ std::auto_ptr<Configuration> pconfig =
+ Configuration::LoadAndVerify(
+ mConfigFileName.c_str(),
+ GetConfigVerify(), errors);
+
+ // Got errors?
+ if(pconfig.get() == 0 || !errors.empty())
+ {
+ // Tell user about errors
+ ::syslog(LOG_ERR, "Errors in config "
+ "file %s:\n%s",
+ mConfigFileName.c_str(),
+ errors.c_str());
+ // And give up
+ return 1;
+ }
+
+ // delete old configuration
+ delete mpConfiguration;
+ mpConfiguration = 0;
+
+ // Store configuration
+ mpConfiguration = pconfig.release();
+ mLoadedConfigModifiedTime =
+ GetConfigFileModifiedTime();
+
+ // Stop being marked for loading config again
+ mReloadConfigWanted = false;
+ }
+ }
+
+ // Delete the PID file
+ ::unlink(pidFileName.c_str());
+
+ // Log
+ ::syslog(LOG_INFO, "Terminating daemon");
+ }
+ catch(BoxException &e)
+ {
+ ::syslog(LOG_ERR, "%s: terminating due to exception %s "
+ "(%d/%d)", DaemonName(), e.what(), e.GetType(),
+ e.GetSubType());
+ return 1;
+ }
+ catch(std::exception &e)
+ {
+ ::syslog(LOG_ERR, "%s: terminating due to exception %s",
+ DaemonName(), e.what());
+ return 1;
+ }
+ catch(...)
+ {
+ ::syslog(LOG_ERR, "%s: terminating due to unknown exception",
+ DaemonName());
+ return 1;
+ }
+
+ return 0;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::EnterChild()
+// Purpose: Sets up for a child task of the main server. Call just after fork()
+// Created: 2003/07/31
+//
+// --------------------------------------------------------------------------
+void Daemon::EnterChild()
+{
+#ifndef WIN32
+ // Unset signal handlers
+ struct sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask); // macro
+ ::sigaction(SIGHUP, &sa, NULL);
+ ::sigaction(SIGTERM, &sa, NULL);
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::SignalHandler(int)
+// Purpose: Signal handler
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+void Daemon::SignalHandler(int sigraised)
+{
+#ifndef WIN32
+ if(spDaemon != 0)
+ {
+ switch(sigraised)
+ {
+ case SIGHUP:
+ spDaemon->mReloadConfigWanted = true;
+ break;
+
+ case SIGTERM:
+ spDaemon->mTerminateWanted = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+#endif
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::DaemonName()
+// Purpose: Returns name of the daemon
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+const char *Daemon::DaemonName() const
+{
+ return "generic-daemon";
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::DaemonBanner()
+// Purpose: Returns the text banner for this daemon's startup
+// Created: 1/1/04
+//
+// --------------------------------------------------------------------------
+const char *Daemon::DaemonBanner() const
+{
+ return 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::Run()
+// Purpose: Main run function after basic Daemon initialisation
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+void Daemon::Run()
+{
+ while(!StopRun())
+ {
+ ::sleep(10);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::GetConfigVerify()
+// Purpose: Returns the configuration file verification structure for this daemon
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+const ConfigurationVerify *Daemon::GetConfigVerify() const
+{
+ static ConfigurationVerifyKey verifyserverkeys[] =
+ {
+ DAEMON_VERIFY_SERVER_KEYS
+ };
+
+ static ConfigurationVerify verifyserver[] =
+ {
+ {
+ "Server",
+ 0,
+ verifyserverkeys,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ }
+ };
+
+ static ConfigurationVerify verify =
+ {
+ "root",
+ verifyserver,
+ 0,
+ ConfigTest_Exists | ConfigTest_LastEntry,
+ 0
+ };
+
+ return &verify;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::GetConfiguration()
+// Purpose: Returns the daemon configuration object
+// Created: 2003/07/29
+//
+// --------------------------------------------------------------------------
+const Configuration &Daemon::GetConfiguration() const
+{
+ if(mpConfiguration == 0)
+ {
+ // Shouldn't get anywhere near this if a configuration file can't be loaded
+ THROW_EXCEPTION(ServerException, Internal)
+ }
+
+ return *mpConfiguration;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::SetupInInitialProcess()
+// Purpose: A chance for the daemon to do something initial setting up in the process which
+// initiates everything, and after the configuration file has been read and verified.
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+void Daemon::SetupInInitialProcess()
+{
+ // Base class doesn't do anything.
+}
+
+
+void Daemon::SetProcessTitle(const char *format, ...)
+{
+ // On OpenBSD, setproctitle() sets the process title to imagename: <text> (imagename)
+ // -- make sure other platforms include the image name somewhere so ps listings give
+ // useful information.
+
+#ifdef HAVE_SETPROCTITLE
+ // optional arguments
+ va_list args;
+ va_start(args, format);
+
+ // Make the string
+ char title[256];
+ ::vsnprintf(title, sizeof(title), format, args);
+
+ // Set process title
+ ::setproctitle("%s", title);
+
+#endif // HAVE_SETPROCTITLE
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::GetConfigFileModifiedTime()
+// Purpose: Returns the timestamp when the configuration file
+// was last modified
+//
+// Created: 2006/01/29
+//
+// --------------------------------------------------------------------------
+
+box_time_t Daemon::GetConfigFileModifiedTime() const
+{
+ struct stat st;
+
+ if(::stat(GetConfigFileName().c_str(), &st) != 0)
+ {
+ if (errno == ENOENT)
+ {
+ return 0;
+ }
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+
+ return FileModificationTime(st);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::GetLoadedConfigModifiedTime()
+// Purpose: Returns the timestamp when the configuration file
+// had been last modified, at the time when it was
+// loaded
+//
+// Created: 2006/01/29
+//
+// --------------------------------------------------------------------------
+
+box_time_t Daemon::GetLoadedConfigModifiedTime() const
+{
+ return mLoadedConfigModifiedTime;
+}
+