/* -*- Mode: C++ -*- * Worldvisions Tunnel Vision Software: * Copyright (C) 1997-2002 Net Integration Technologies, Inc. * * High-level abstraction for creating daemon processes. Handles * command-line argument processing, forking into the background, * and signal handling. */ #include "wvdaemon.h" #include "wvlinklist.h" #include "wvsyslog.h" #ifndef _WIN32 #include "wvcrash.h" #include "wvcrashlog.h" #include "wvfile.h" #include "wvatomicfile.h" #include #include #include #include #else #include "wvlogrcv.h" #endif #ifndef _WIN32 # define CAN_SYSLOG true # define CAN_DAEMONIZE true #else # define CAN_SYSLOG false # define CAN_DAEMONIZE false #endif #ifdef _MSC_VER static const int STDOUT_FILENO = 0; #endif WvDaemon *WvDaemon::singleton = NULL; #ifndef _WIN32 static void sighup_handler(int signum) { signal(signum, SIG_IGN); WvDaemon::me()->log(WvLog::Notice, "Restarting on signal %s.\n", signum); WvDaemon::me()->restart(); } static void sigterm_handler(int signum) { signal(signum, SIG_DFL); WvDaemon::me()->log(WvLog::Notice, "Dying on signal %s.\n", signum); WvDaemon::me()->die(); } static void sigquit_handler(int signum) { signal(signum, SIG_IGN); exit(1); } #endif // _WIN32 void WvDaemon::init(WvStringParm _name, WvStringParm _version, WvDaemonCallback _start_callback, WvDaemonCallback _run_callback, WvDaemonCallback _stop_callback) { name = _name; version = _version; pid_file = WvString("/var/run/%s.pid", _name); daemonize = false; log_level = WvLog::Info; syslog = false; start_callback = _start_callback; run_callback = _run_callback; stop_callback = _stop_callback; assert(singleton == NULL); singleton = this; args.add_option('q', "quiet", "Decrease log level (can be used multiple times)", wv::bind(&WvDaemon::dec_log_level, this, _1)); args.add_option('v', "verbose", "Increase log level (can be used multiple times)", wv::bind(&WvDaemon::inc_log_level, this, _1)); if (CAN_DAEMONIZE) args.add_option('d', "daemonize", "Fork into background and return (implies --syslog)", wv::bind(&WvDaemon::set_daemonize, this, _1)); if (CAN_SYSLOG) { args.add_set_bool_option('s', "syslog", "Write log entries to syslog", syslog); args.add_reset_bool_option(0, "no-syslog", "Do not write log entries to syslog", syslog); } args.set_version(WvString("%s version %s", name, version).cstr()); } WvDaemon::~WvDaemon() { } int WvDaemon::run(const char *argv0) { #ifndef _WIN32 if (CAN_DAEMONIZE && daemonize) { pid_t pid = ::fork(); if (pid < 0) { wverr->print("Failed to fork daemon: %s\n", strerror(errno)); return 3; } else if (pid == 0) { setsid(); pid = fork(); if (pid < 0) { wverr->print("Failed to double-fork daemon: %s\n", strerror(errno)); } else if (pid == 0) { // FIXME: this happens *before* we do the daemon setup! // We should only fork into the background *after* doing // things like opening our listen sockets. ::chdir("/"); ::umask(0); int null_fd; do { null_fd = ::open("/dev/null", O_RDWR); if (null_fd == -1) { log(WvLog::Error, "Failed to open /dev/null: %s\n", strerror(errno)); _exit(1); } } while (null_fd == 0 || null_fd == 1 || null_fd == 2); if (::dup2(null_fd, 0) == -1 || ::dup2(null_fd, 1) == -1 || ::dup2(null_fd, 2) == -1) { log(WvLog::Error, "Failed to dup2(null_fd, (0|1|2)): %s\n", strerror(errno)); _exit(1); } ::close(null_fd); // Make sure the close-on-exec flag is not set for // the first three descriptors, since many programs // assume that they are open after exec() if (::fcntl(0, F_SETFD, 0) == -1 || ::fcntl(1, F_SETFD, 0) == -1 || ::fcntl(2, F_SETFD, 0) == -1) { log(WvLog::Warning, "Failed to fcntl((0|1|2), F_SETFD, 0): %s\n", strerror(errno)); } return _run(argv0); // Make sure destructors are called } _exit(0); } return 0; } else #endif // !_WIN32 { WvLogConsole console_log(STDOUT_FILENO, log_level); if (CAN_SYSLOG && syslog) { WvSyslog syslog(name, false); return _run(argv0); } else return _run(argv0); } } int WvDaemon::run(int argc, char **argv) { if (!args.process(argc, argv, &_extra_args)) return 1; return run(argv[0]); } int WvDaemon::_run(const char *argv0) { WvLogRcv *logr = NULL; #ifndef _WIN32 WvCrashLog crashlog; wvcrash_setup(argv0, version); #endif if (CAN_SYSLOG && syslog) logr = new WvSyslog(name, false); _want_to_die = false; do_load(); while (!want_to_die()) { _want_to_restart = false; do_start(); while (should_run()) do_run(); do_stop(); } do_unload(); if (logr) delete logr; return _exit_status; } void WvDaemon::do_load() { #ifndef _WIN32 if (!!pid_file && daemonize) { // FIXME: this is racy! // First, make sure we aren't already running WvFile old_pid_fd(pid_file, O_RDONLY); if (old_pid_fd.isok()) { WvString line = old_pid_fd.getline(0); if (!!line) { pid_t old_pid = line.num(); if (old_pid > 0 && (kill(old_pid, 0) == 0 || errno == EPERM)) { log(WvLog::Error, "%s is already running (pid %s); exiting\n", name, old_pid); die(); } } } old_pid_fd.close(); if (want_to_die()) return; // Now write our new PID file WvAtomicFile pid_fd(pid_file, O_WRONLY, 0600); pid_fd.print("%s\n", getpid()); if (!pid_fd.isok()) log(WvLog::Warning, "Failed to write PID file %s: %s\n", pid_file, pid_fd.errstr()); pid_fd.close(); } #endif log(WvLog::Notice, "Starting %s version %s.\n", name, version); #ifndef _WIN32 if (daemonize) signal(SIGINT, SIG_IGN); else signal(SIGINT, sigterm_handler); signal(SIGTERM, sigterm_handler); signal(SIGQUIT, sigquit_handler); signal(SIGHUP, sighup_handler); #endif if (load_callback) load_callback(); } void WvDaemon::do_start() { if (start_callback) start_callback(); } void WvDaemon::do_run() { if (run_callback) run_callback(); } void WvDaemon::do_stop() { if (stop_callback) stop_callback(); } void WvDaemon::do_unload() { if (unload_callback) unload_callback(); #ifndef _WIN32 signal(SIGHUP, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGTERM, SIG_DFL); #endif log(WvLog::Notice, "Exiting with status %s\n", _exit_status); #ifndef _WIN32 if (!!pid_file && daemonize) ::unlink(pid_file); #endif } bool WvDaemon::set_daemonize(void *) { daemonize = true; syslog = true; return true; }