commit 337fadeede2d4418ae569173cdf19ed2465444e3 Author: Zack Weinberg zackw@cmu.edu Date: Wed Jun 13 22:09:58 2012 -0400
Add support for daemonizing and writing a pid file. --- src/audit-globals.sh | 2 + src/main.cc | 53 +++++++++++++++++++----- src/subprocess-unix.cc | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ src/subprocess.h | 39 ++++++++++++++++++ 4 files changed, 188 insertions(+), 11 deletions(-)
diff --git a/src/audit-globals.sh b/src/audit-globals.sh index 27cab3a..7c28f21 100644 --- a/src/audit-globals.sh +++ b/src/audit-globals.sh @@ -39,7 +39,9 @@ sed ' /^crypt crypto_initialized$/d /^crypt crypto_errs_initialized$/d /^main allow_kq$/d + /^main daemon_mode$/d /^main handle_signal_cb(int, short, void*)::got_sigint$/d + /^main pidfile_name$/d /^main registration_helper$/d /^main the_event_base$/d /^network listeners$/d diff --git a/src/main.cc b/src/main.cc index dd69e68..42687e1 100644 --- a/src/main.cc +++ b/src/main.cc @@ -37,6 +37,8 @@ using std::string;
static struct event_base *the_event_base; static bool allow_kq = false; +static bool daemon_mode = false; +static string pidfile_name; static string registration_helper;
/** @@ -234,7 +236,11 @@ usage(void) "--log-min-severity=warn|info|debug ~ set minimum logging severity\n" "--no-log ~ disable logging\n" "--timestamp-logs ~ add timestamps to all log messages\n" - "--allow-kqueue ~ allow use of kqueue(2) (may be buggy)\n"); + "--allow-kqueue ~ allow use of kqueue(2) (may be buggy)\n" + "--registration-helper=<helper> ~ use <helper> to register with " + "a relay database\n" + "--pid-file=<file> ~ write process ID to <file> after startup\n" + "--daemon ~ run as a daemon");
exit(1); } @@ -256,7 +262,8 @@ handle_generic_args(const char *const *argv) bool logsev_set = false; bool allow_kq_set = false; bool timestamps_set = false; - bool registration_helper_set=false; + bool registration_helper_set = false; + bool pidfile_set = false; int i = 1;
while (argv[i] && @@ -278,21 +285,19 @@ handle_generic_args(const char *const *argv) fprintf(stderr, "you've already set a min. log severity!\n"); exit(1); } - if (log_set_min_severity((char *)argv[i]+19) < 0) { - fprintf(stderr, "error at setting logging severity"); + if (log_set_min_severity(argv[i]+19) < 0) { + fprintf(stderr, "invalid min. log severity '%s'", argv[i]+19); exit(1); } logsev_set = true; } else if (!strcmp(argv[i], "--no-log")) { - if (logsev_set) { - fprintf(stderr, "you've already set a min. log severity!\n"); + if (logsev_set || logmethod_set) { + fprintf(stderr, "can't ask for both some logs and no logs!\n"); exit(1); } - if (log_set_method(LOG_METHOD_NULL, NULL) < 0) { - fprintf(stderr, "error at setting logging severity.\n"); - exit(1); - } - logsev_set = true; + log_set_method(LOG_METHOD_NULL, NULL); + logsev_set = true; + logmethod_set = true; } else if (!strcmp(argv[i], "--timestamp-logs")) { if (timestamps_set) { fprintf(stderr, "you've already asked for timestamps!\n"); @@ -314,6 +319,19 @@ handle_generic_args(const char *const *argv) } registration_helper = string(argv[i]+22); registration_helper_set = true; + } else if (!strncmp(argv[i], "--pid-file=", 11)) { + if (pidfile_set) { + fprintf(stderr, "you've already set a pid file!\n"); + exit(1); + } + pidfile_name = string(argv[i]+11); + pidfile_set = true; + } else if (!strcmp(argv[i], "--daemon")) { + if (daemon_mode) { + fprintf(stderr, "you've already requested daemon mode!\n"); + exit(1); + } + daemon_mode = true; } else { fprintf(stderr, "unrecognizable argument '%s'\n", argv[i]); exit(1); @@ -321,6 +339,12 @@ handle_generic_args(const char *const *argv) i++; }
+ /* Cross-option consistency checks. */ + if (daemon_mode && !logmethod_set) { + log_warn("cannot log to stderr in daemon mode"); + log_set_method(LOG_METHOD_NULL, NULL); + } + return i; }
@@ -380,6 +404,13 @@ main(int, const char *const *argv) log_assert(configs.size() > 0);
/* Configurations have been established; proceed with initialization. */ + if (daemon_mode) + daemonize(); + + pidfile pf(pidfile_name); + if (!pf) + log_warn("failed to create pid-file '%s': %s", pf.pathname().c_str(), + pf.errmsg());
init_crypto();
diff --git a/src/subprocess-unix.cc b/src/subprocess-unix.cc index 7e075d9..573b167 100644 --- a/src/subprocess-unix.cc +++ b/src/subprocess-unix.cc @@ -13,6 +13,7 @@ #include "subprocess.h"
#include <map> +#include <sstream>
#include <sys/stat.h> #include <dirent.h> @@ -465,3 +466,107 @@ get_environ(const char *exclude)
return result; } + +void +daemonize() +{ + if (getppid() == 1) // already a daemon + return; + + // Close standard I/O file descriptors and reopen them on /dev/null. + // We do this before forking (a) to avoid any possibility of + // double-flushed stdio buffers, and (b) so we can exit + // unsuccessfully in the unlikely event of a failure. + fflush(NULL); // flush all open stdio buffers + + close(0); + if (open("/dev/null", O_RDONLY) != 0) + log_abort("/dev/null: %s", strerror(errno)); + + close(1); + if (open("/dev/null", O_WRONLY) != 1) + log_abort("/dev/null: %s", strerror(errno)); + + // N.B. log_abort might be writing somewhere other than stderr. + // In fact, we rather hope it is, 'cos otherwise all logs from + // the child are gonna go to the bit bucket. + close(2); + if (open("/dev/null", O_WRONLY) != 2) + log_abort("/dev/null: %s", strerror(errno)); + + pid_t pid = fork(); + if (pid < 0) + log_abort("fork failed: %s", strerror(errno)); + + if (pid > 0) // Parent + // The use of _exit instead of exit here is deliberate. + // It's the process that carries on from this function that + // should do atexit cleanups (eventually). + _exit(0); + + // Become a session leader, and then fork one more time and exit in + // the parent. (This puts the process that will actually be the + // daemon in an orphaned process group. On some systems, this is + // necessary to ensure that the daemon can never acquire a controlling + // terminal again. XXX On some systems will the grandchild receive + // an unwanted, probably-fatal SIGHUP when its session leader exits?) + setsid(); + if (fork()) + _exit(0); + + // For the moment we do not chdir anywhere, because the HTTP steg expects + // to find its traces relative to the cwd. FIXME. +} + +pidfile::pidfile(std::string const& p) + : path(p), errcode(0) +{ + if (path.empty()) + return; + + std::ostringstream ss; + ss << getpid() << '\n'; + const char *b = ss.str().c_str(); + size_t n = ss.str().size(); + + int f = open(path.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0666); + if (f == -1) { + errcode = errno; + return; + } + + do { + ssize_t r = write(f, b, n); + if (r < 0) { + errcode = errno; + close(f); + remove(path.c_str()); + return; + } + n -= r; + b += r; + } while (n > 0); + + // Sadly, close() can fail, and in this case it actually matters. + if (close(f)) { + errcode = errno; + remove(path.c_str()); + } +} + +pidfile::~pidfile() +{ + if (!errcode && !path.empty()) + remove(path.c_str()); +} + +pidfile::operator bool() const +{ + return !errcode; +} + +const char * +pidfile::errmsg() const +{ + return errcode ? strerror(errcode) : 0; +} diff --git a/src/subprocess.h b/src/subprocess.h index 95fe848..73eeece 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -65,4 +65,43 @@ struct subprocess // begins with those characters will be excluded from the result. extern std::vectorstd::string get_environ(const char *exclude = 0);
+// These are in here because they involve process management 'under the hood', +// and because (like other process management) their Unix and Windows +// implementations have to be radically different. + +// Turn into a daemon; detach from the parent process and any +// controlling terminal. Closes standard I/O streams and reopens them +// to /dev/null. If this returns, it succeeded. +extern void daemonize(); + +// Instantiating this class causes a file to be created at the specified +// pathname, which contains the decimal process ID of the current process. +// On destruction, the file is deleted. +// +// If you're going to call daemonize(), you need to do it _before_ creating +// one of these, because daemonize() changes the process ID. + +class pidfile +{ +public: + pidfile(const std::string& p); + ~pidfile(); + + const std::string& pathname() const { return path; } + + // True if pid-file creation succeeded. + operator bool() const; + + // If pid-file creation did *not* succeed, returns the underlying system + // error message. You should combine that with the pathname and some + // text to the effect that this is a process ID file for the actual error + // message printed to the user. + // If pid-file creation *did* succeed, returns NULL. + const char *errmsg() const; + +private: + std::string path; + int errcode; +}; + #endif
tor-commits@lists.torproject.org