blob: be843ded8971d793ce6a07bd90366d4a21194378 [file] [log] [blame]
#!/usr/bin/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, 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, 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("%s" % str(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())