// -------------------------------------------------------------------------- // // File // Name: Daemon.cpp // Purpose: Basic daemon functionality // Created: 2003/07/29 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include #include #include #include #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 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 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: (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 }