diff options
Diffstat (limited to 'lib/server/Daemon.cpp')
-rw-r--r-- | lib/server/Daemon.cpp | 704 |
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; +} + |