diff options
Diffstat (limited to 'lib/server/Daemon.cpp')
-rwxr-xr-x | lib/server/Daemon.cpp | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/lib/server/Daemon.cpp b/lib/server/Daemon.cpp new file mode 100755 index 00000000..31997fb6 --- /dev/null +++ b/lib/server/Daemon.cpp @@ -0,0 +1,530 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Daemon.cpp +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <unistd.h> +#include <syslog.h> +#include <signal.h> +#include <string.h> +#include <stdarg.h> + +#include "Daemon.h" +#include "Configuration.h" +#include "ServerException.h" +#include "Guards.h" +#include "UnixUser.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; + const char *configfile = 0; + + try + { + // Find filename of config file + configfile = 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) + { + configfile = argv[2]; + } + else + { + configfile = 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 = Configuration::LoadAndVerify(configfile, GetConfigVerify(), errors); + + // Got errors? + if(pconfig.get() == 0 || !errors.empty()) + { + // Tell user about errors + fprintf(stderr, "%s: Errors in config file %s:\n%s", DaemonName(), configfile, errors.c_str()); + // And give up + return 1; + } + + // Store configuration + mpConfiguration = pconfig.release(); + + // Server configuration + const Configuration &serverConfig(mpConfiguration->GetSubConfiguration("Server")); + + // Let the derived class have a go at setting up stuff in the initial process + SetupInInitialProcess(); + + // Set signal handler + if(::signal(SIGHUP, SignalHandler) == SIG_ERR || ::signal(SIGTERM, SignalHandler) == SIG_ERR) + { + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } + + // 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; + } + } + + // open the log + ::openlog(DaemonName(), LOG_PID, LOG_LOCAL6); + // Log the start message + ::syslog(LOG_INFO, "Starting daemon (config: %s) (version " BOX_VERSION ")", configfile); + + // 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) + } + + // 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) + { + // 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); + } + + // And definately 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: exception %s (%d/%d)\n", DaemonName(), e.what(), e.GetType(), e.GetSubType()); + return 1; + } + catch(std::exception &e) + { + fprintf(stderr, "%s: exception %s\n", DaemonName(), e.what()); + return 1; + } + catch(...) + { + fprintf(stderr, "%s: unknown exception\n", DaemonName()); + 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)", configfile); + std::string errors; + std::auto_ptr<Configuration> pconfig = Configuration::LoadAndVerify(configfile, GetConfigVerify(), errors); + + // Got errors? + if(pconfig.get() == 0 || !errors.empty()) + { + // Tell user about errors + ::syslog(LOG_ERR, "Errors in config file %s:\n%s", configfile, errors.c_str()); + // And give up + return 1; + } + + // delete old configuration + delete mpConfiguration; + mpConfiguration = 0; + + // Store configuration + mpConfiguration = pconfig.release(); + + // 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, "exception %s (%d/%d) -- terminating", e.what(), e.GetType(), e.GetSubType()); + return 1; + } + catch(std::exception &e) + { + ::syslog(LOG_ERR, "exception %s -- terminating", e.what()); + return 1; + } + catch(...) + { + ::syslog(LOG_ERR, "unknown exception -- terminating"); + 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() +{ + // Unset signal handlers + ::signal(SIGHUP, SIG_DFL); + ::signal(SIGTERM, SIG_DFL); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::SignalHandler(int) +// Purpose: Signal handler +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +void Daemon::SignalHandler(int sigraised) +{ + if(spDaemon != 0) + { + switch(sigraised) + { + case SIGHUP: + spDaemon->mReloadConfigWanted = true; + break; + + case SIGTERM: + spDaemon->mTerminateWanted = true; + break; + + default: + break; + } + } +} + +// -------------------------------------------------------------------------- +// +// 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 PLATFORM_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 // PLATFORM_HAVE_setproctitle +} + + |