| #!/usr/bin/env python |
| # Copyright 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| This script can daemonize another script. This is useful in running |
| background processes from recipes. |
| """ |
| |
| import argparse |
| import logging |
| import os |
| import signal |
| import subprocess |
| import sys |
| |
| |
| def daemonize(cmd, pidfile): |
| """ |
| This function is based on the Python recipe provided here: |
| http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ |
| """ |
| # flush the output |
| sys.stdout.flush() |
| sys.stderr.flush() |
| |
| # spawn a detached child process |
| try: |
| pid = os.fork() |
| if pid > 0: |
| # exit first parent |
| sys.exit(0) |
| except OSError as e: |
| sys.stderr.write( |
| "fork #1 failed, unable to daemonize: %d (%s)\n" % (e.errno, e.strerror) |
| ) |
| sys.exit(1) |
| |
| # decouple from parent environment |
| os.chdir("/") |
| os.setsid() |
| os.umask(0) |
| |
| # do second fork |
| try: |
| pid = os.fork() |
| if pid > 0: |
| # exit from second parent |
| sys.exit(0) |
| except OSError as e: |
| sys.stderr.write( |
| "fork #2 failed, unable to daemonize: %d (%s)\n" % (e.errno, e.strerror) |
| ) |
| sys.exit(1) |
| |
| # redirect standard file descriptors |
| sys.stdout.flush() |
| sys.stderr.flush() |
| si = file("/dev/null", "r") |
| so = file("/dev/null", "a+") |
| se = file("/dev/null", "a+", 0) |
| os.dup2(si.fileno(), sys.stdin.fileno()) |
| os.dup2(so.fileno(), sys.stdout.fileno()) |
| os.dup2(se.fileno(), sys.stderr.fileno()) |
| |
| proc = subprocess.Popen(cmd) |
| |
| # Write pid to file if applicable. |
| if pidfile: |
| try: |
| with open(pidfile, "w") as pid_file: |
| pid_file.write("%d" % proc.pid) |
| except (IOError): |
| logging.exception("Unable to write pid to file") |
| |
| proc.communicate() |
| return proc.returncode |
| |
| |
| def stop(pidfile): |
| if not pidfile: |
| logging.error("pidfile arg must be specified when stopping a daemon") |
| return 1 |
| try: |
| with open(pidfile) as pid_file: |
| pid = int(pid_file.readline()) |
| logging.info("Sending SIGTERM to %d", pid) |
| os.kill(pid, signal.SIGTERM) |
| os.remove(pidfile) |
| except (IOError, OSError): |
| logging.exception("Error terminating daemon process") |
| |
| |
| def restart(cmd, pidfile): |
| # check for the pidfile to see if the daemon's already running |
| if not pidfile: |
| logging.error("pidfile arg must be specified when restarting a daemon") |
| return 1 |
| try: |
| with open(pidfile, "r") as pid_file: |
| pid = int(pid_file.readline()) |
| except (IOError, ValueError): |
| pid = None |
| |
| if pid: |
| logging.info( |
| "%s pid file already exists, attempting to kill process %d", |
| pidfile, |
| pid, |
| ) |
| try: |
| os.kill(pid, signal.SIGTERM) |
| except OSError: |
| logging.exception("Unable to kill old daemon process") |
| |
| return daemonize(cmd, pidfile) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "--action", |
| default="daemonize", |
| choices=["daemonize", "stop", "restart"], |
| help="What action to take", |
| ) |
| parser.add_argument( |
| "--pidfile", |
| type=str, |
| help="Path to store the daemon's pid", |
| ) |
| parser.add_argument("cmd", help="Command to daemonize", nargs=argparse.REMAINDER) |
| args = parser.parse_args() |
| |
| if args.action == "daemonize": |
| return daemonize(args.cmd, args.pidfile) |
| elif args.action == "stop": |
| return stop(args.pidfile) |
| elif args.action == "restart": |
| return restart(args.cmd, args.pidfile) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |