#!/usr/bin/python """ Helper script for running long-living processes. (Name says daemon, but that is intended to mean "long-living", we assume child process does not double-fork.) We start the command passed as arguments, with /dev/null as stdin, and then wait for EOF on stdin. When EOF is seen on stdin, the child process is killed. When the child process exits, this helper exits too. usage: daemon-helper [--kill-group] [nostdin] command... """ import fcntl import os import select import signal import struct import subprocess import sys end_signal = signal.SIGKILL if sys.argv[1] == "term": end_signal = signal.SIGTERM cmd_start = 2 group = False if sys.argv[cmd_start] == "--kill-group": group = True cmd_start += 1 nostdin = False if sys.argv[cmd_start] == "nostdin": nostdin = True cmd_start += 1 proc = None if nostdin: proc = subprocess.Popen( args=sys.argv[cmd_start:], ) else: with file('/dev/null', 'rb') as devnull: proc = subprocess.Popen( args=sys.argv[cmd_start:], stdin=devnull, preexec_fn=os.setsid, ) flags = fcntl.fcntl(0, fcntl.F_GETFL) fcntl.fcntl(0, fcntl.F_SETFL, flags | os.O_NDELAY) saw_eof = False while True: r,w,x = select.select([0], [], [0], 0.2) if r: data = os.read(0, 1) if not data: saw_eof = True if not group: proc.send_signal(end_signal) else: os.killpg(proc.pid, end_signal) break else: sig, = struct.unpack('!b', data) if not group: proc.send_signal(sig) else: os.killpg(proc.pid, end_signal) if proc.poll() is not None: # child exited break exitstatus = proc.wait() if exitstatus > 0: print >>sys.stderr, '{me}: command failed with exit status {exitstatus:d}'.format( me=os.path.basename(sys.argv[0]), exitstatus=exitstatus, ) sys.exit(exitstatus) elif exitstatus < 0: if saw_eof and exitstatus == -end_signal: # suppress error from the exit we intentionally caused pass else: print >>sys.stderr, '{me}: command crashed with signal {signal:d}'.format( me=os.path.basename(sys.argv[0]), signal=-exitstatus, ) sys.exit(1)