aboutsummaryrefslogtreecommitdiff
path: root/ca/rpki-nanny
diff options
context:
space:
mode:
Diffstat (limited to 'ca/rpki-nanny')
-rwxr-xr-xca/rpki-nanny217
1 files changed, 217 insertions, 0 deletions
diff --git a/ca/rpki-nanny b/ca/rpki-nanny
new file mode 100755
index 00000000..914c8584
--- /dev/null
+++ b/ca/rpki-nanny
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+# Portions copyright (C) 2009--2013 Internet Systems Consortium ("ISC")
+# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notices and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
+# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
+# ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Start servers, using config file to figure out which servers the user
+wants started.
+"""
+
+import os
+import pwd
+import sys
+import time
+import signal
+import logging
+import argparse
+import subprocess
+
+import rpki.log
+import rpki.config
+import rpki.autoconf
+import rpki.daemonize
+
+from logging.handlers import SysLogHandler
+
+logger = logging.getLogger(__name__)
+
+signames = dict((getattr(signal, sig), sig)
+ for sig in dir(signal)
+ if sig.startswith("SIG")
+ and sig.isalnum()
+ and sig.isupper()
+ and isinstance(getattr(signal, sig), int))
+
+
+class Daemon(object):
+ """
+ Representation and control of one daemon under our care.
+ """
+
+ def __init__(self, name):
+ self.name = name
+ self.proc = None
+ self.next_restart = 0
+ if cfg.getboolean(option = "start_" + name, section = "myrpki", default = False):
+ self.cmd = (os.path.join(rpki.autoconf.libexecdir, name), "--foreground")
+ else:
+ self.cmd = ()
+
+ def start_maybe(self, output):
+ if self.cmd and self.proc is None and time.time() > self.next_restart:
+ try:
+ self.proc = subprocess.Popen(self.cmd, stdout = output, stderr = output)
+ self.next_restart = int(time.time() + args.restart_delay)
+ logger.debug("Started %s[%s]", self.name, self.proc.pid)
+ except:
+ logger.exception("Trouble starting %s", self.name)
+
+ def terminate(self):
+ if self.proc is not None:
+ try:
+ logger.debug("Terminating daemon %s[%s]", self.name, self.proc.pid)
+ self.proc.terminate()
+ except:
+ logger.exception("Trouble terminating %s[%s]", self.name, self.proc.pid)
+
+ def delay(self):
+ return max(0, int(self.next_restart - time.time())) if self.cmd and self.proc is None else 0
+
+ def reap(self):
+ if self.proc is not None and self.proc.poll() is not None:
+ code = self.proc.wait()
+ if code < 0:
+ logger.warn("%s[%s] exited on signal %s",
+ self.name, self.proc.pid, signames.get(-code, "???"))
+ else:
+ logger.warn("%s[%s] exited with status %s",
+ self.name, self.proc.pid, code)
+ self.proc = None
+
+
+class Signals(object):
+ """
+
+ Convert POSIX signals into something we can use in a loop at main
+ program level. Assumes that we use signal.pause() to block, so
+ simply receiving the signal is enough to wake us up.
+
+ Calling the constructed Signals object with one or more signal
+ numbers returns True if any of those signals have been received,
+ and clears the internal flag for the first such signal.
+ """
+
+ def __init__(self, *sigs):
+ self._active = set()
+ for sig in sigs:
+ signal.signal(sig, self._handler)
+
+ def _handler(self, sig, frame):
+ self._active.add(sig)
+ #logger.debug("Received %s", signames.get(sig, "???"))
+
+ def __call__(self, *sigs):
+ for sig in sigs:
+ try:
+ self._active.remove(sig)
+ return True
+ except KeyError:
+ pass
+ return False
+
+
+def non_negative_integer(s):
+ if int(s) < 0:
+ raise ValueError
+ return s
+
+def positive_integer(s):
+ if int(s) <= 0:
+ raise ValueError
+ return s
+
+
+if __name__ == "__main__":
+
+ os.environ.update(TZ = "UTC")
+ time.tzset()
+
+ cfg = rpki.config.argparser(section = "rpki-nanny", doc = __doc__)
+
+ cfg.add_argument("--restart-delay", type = positive_integer, default = 60,
+ help = "how long to wait before restarting a crashed daemon")
+ cfg.add_argument("--pidfile",
+ default = os.path.join(rpki.daemonize.default_pid_directory, "rpki-nanny.pid"),
+ help = "override default location of pid file")
+ cfg.add_boolean_argument("--foreground", default = False,
+ help = "whether to stay in foreground rather than daemonizing")
+ cfg.add_boolean_argument("--capture-stdout-stderr", default = True,
+ help = "whether to capture output incorrectly sent to stdout/stderr")
+ cfg.add_logging_arguments()
+
+ args = cfg.argparser.parse_args()
+
+ # Drop privs before daemonizing or opening log file
+ pw = pwd.getpwnam(rpki.autoconf.RPKI_USER)
+ os.setgid(pw.pw_gid)
+ os.setuid(pw.pw_uid)
+
+ cfg.configure_logging(ident = "rpki-nanny", args = args)
+
+ if not args.foreground:
+ rpki.daemonize.daemon(pidfile = args.pidfile)
+
+ if args.capture_stdout_stderr:
+ try:
+ logger_pipe = os.pipe()
+ logger_pid = os.fork()
+ if logger_pid == 0:
+ os.close(logger_pipe[1])
+ with os.fdopen(logger_pipe[0]) as f:
+ for line in f:
+ logger.warn("Captured: %s", line.rstrip())
+ # Should never get here, but just in case
+ logger.error("[Unexpected EOF in stdout/stderr capture logger]")
+ sys.exit(1)
+ else:
+ os.close(logger_pipe[0])
+ except:
+ logger.exception("Trouble setting up stdout/stderr capture process")
+ sys.exit(1)
+
+ daemon_output = logger_pipe[1] if args.capture_stdout_stderr else None
+
+ signals = Signals(signal.SIGALRM, signal.SIGCHLD, signal.SIGTERM, signal.SIGINT)
+ daemons = [Daemon(name) for name in ("irdbd", "rpkid", "pubd", "rootd")]
+ exiting = False
+
+ try:
+ while not exiting or not all(daemon.proc is None for daemon in daemons):
+ if not exiting and signals(signal.SIGTERM, signal.SIGINT):
+ logger.info("Received exit signal")
+ exiting = True
+ for daemon in daemons:
+ daemon.terminate()
+ if not exiting:
+ for daemon in daemons:
+ daemon.start_maybe(daemon_output)
+ alarms = tuple(daemon.delay() for daemon in daemons)
+ signal.alarm(min(a for a in alarms if a > 0) + 1 if any(alarms) else 0)
+ if not signals(signal.SIGCHLD, signal.SIGALRM):
+ signal.pause()
+ for daemon in daemons:
+ daemon.reap()
+ if args.capture_stdout_stderr:
+ os.kill(logger_pid, signal.SIGTERM)
+ except:
+ logger.exception("Unhandled exception in main loop")
+ for daemon in daemons:
+ daemon.terminate()
+ sys.exit(1)