#!/usr/bin/env python ## Copyright (C) 2006 Red Hat, Inc. ## Written by Gary Benson ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. import os import signal import sys import syslog import time import traceback class Error(Exception): pass def which(cmd): for path in os.environ["PATH"].split(":"): path = os.path.join(path, cmd) if os.access(path, os.X_OK): return path raise Error, "no %s in (%s)" % (cmd, os.environ["PATH"]) def pidof(cmd): if not os.path.dirname(cmd): cmd = which(cmd) cmd = os.path.realpath(cmd) pids = [] seen = False for pid in os.listdir("/proc"): try: pid = int(pid) except ValueError: continue seen = True path = os.path.join("/proc", str(pid), "exe") try: if os.path.realpath(path) == cmd: pids.append(pid) except OSError: pass assert seen return pids class Watcher: def __init__(self, cmd, args): self.name = os.path.basename(cmd) if not os.path.dirname(cmd): cmd = which(cmd) self.cmd = os.path.realpath(cmd) self.args = args def start(self): syslog.syslog(syslog.LOG_NOTICE, "starting %s" % self.name) pid = os.fork() if pid == 0: os.execv(self.cmd, [self.name] + self.args) status = os.waitpid(pid, 0)[1] if os.WIFSIGNALED(status): raise Error, "%s killed with signal %d" % ( self.cmd, os.WTERMSIG(status)) if not os.WIFEXITED(status): raise RuntimeError, "os.waitpid returned %d" % status status = os.WEXITSTATUS(status) if status: raise Error, "%s exited with code %d" % (self.cmd, status) pids = pidof(self.cmd) if not pids: raise Error, "%s is not running!" % self.cmd if len(pids) != 1: pids = ", ".join(map(str, pids)) raise Error, "multiple copies of %s running!" % (self.cmd, pids) self.pid = pids[0] syslog.syslog(syslog.LOG_NOTICE, "%s started" % self.name) def detach(self): if os.fork(): sys.exit() os.setsid() if os.fork(): sys.exit() null = os.open("/dev/null", os.O_RDWR) for fd in range(3): os.dup2(null, fd) def isrunning(self): return os.access(os.path.join("/proc", str(self.pid)), os.F_OK) def stop(self): syslog.syslog(syslog.LOG_NOTICE, "stopping %s" % self.name) for sig in (signal.SIGTERM, signal.SIGKILL): try: os.kill(self.pid, signal.SIGTERM) except OSError: continue for i in xrange(30): if not self.isrunning(): break time.sleep(1) if not self.isrunning(): break if self.isrunning(): raise Error, "%s (%d) didn't die!" % (self.name, self.pid) def signal(self, num, frame): if num == signal.SIGHUP: self.do_restart = True if num == signal.SIGTERM: self.do_exit = True def run(self): syslog.openlog("vpnc-watch", syslog.LOG_PID, syslog.LOG_DAEMON) pids = pidof(self.cmd) if pids: pids = ", ".join(map(str, pids)) raise Error, "%s already running (%s)" % (self.cmd, pids) self.start() self.detach() try: signal.signal(signal.SIGHUP, self.signal) signal.signal(signal.SIGTERM, self.signal) self.do_exit = False while not self.do_exit: self.do_restart = False time.sleep(1) running = self.isrunning() if not running: syslog.syslog(syslog.LOG_WARNING, "%s died" % self.name) elif self.do_exit or self.do_restart: self.stop() if self.do_restart or not running: self.start() syslog.syslog(syslog.LOG_INFO, "exiting") except Error, e: syslog.syslog(syslog.LOG_ERR, "error: " + str(e)) sys.exit(1) except: msg = "".join(apply(traceback.format_exception, sys.exc_info())) for line in msg.split("\n"): if line: syslog.syslog(syslog.LOG_ERR, line) sys.exit(1) if __name__ == "__main__": if len(sys.argv) < 2: print >>sys.stderr, "usage: %s COMMAND [ARGS]" % sys.argv[0] sys.exit(1) try: Watcher(sys.argv[1], sys.argv[2:]).run() except Error, e: print >>sys.stderr, "error:", e sys.exit(1)