blob: 288401ec9731c3feb423e198c1e1dd17a281bfca [file] [log] [blame]
#!/usr/bin/python
# Copyright 2013 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 usefull in running
background processes from recipes. Lookat chromium_android module for
example.
USAGE:
daemonizer.py [options] -- <script> [args]
- options are options to this script.
- script is the script to daemonize or run in the background
- args are the arguments that one might want to pass the <script>
"""
# TODO(sivachandra): Enhance this script by enforcing a protocol of
# communication between the parent (this script) and the daemon script.
# TODO(bpastene): Improve file handling by adding flocks over file io
# as implemented in infra/libs/service_utils/_daemon_nix.py
import argparse
import logging
import os
import signal
import subprocess
import sys
def restart(cmd, pid_file_path):
# Check for the pid_file to see if the daemon's already running
# and restart it if it is.
if pid_file_path == None:
logging.error('pid_file_path arg must be specified when '
'restarting a daemon')
return 1
try:
with open(pid_file_path, '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",
pid_file_path,
pid)
try:
os.kill(pid, signal.SIGTERM)
except OSError:
logging.exception("Unable to kill old daemon process")
return daemonize(cmd, pid_file_path)
def daemonize(cmd, pid_file_path):
"""This function is based on the Python recipe provided here:
http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
"""
# 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 pid_file_path:
try:
with open(pid_file_path, '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(pid_file_path):
if pid_file_path == None:
logging.error("pid_file_path arg must be specified when stopping a daemon")
return 1
try:
with open(pid_file_path) as pid_file:
pid = int(pid_file.readline())
logging.info('Sending SIGTERM to %d', pid)
os.kill(pid, signal.SIGTERM)
os.remove(pid_file_path)
except (IOError, OSError):
logging.exception('Error terminating daemon process')
def main():
parser = argparse.ArgumentParser(
description='Launch, or shutdown, a daemon process.')
parser.add_argument(
'--action',
default='daemonize',
choices=['restart','stop','daemonize'],
help='What action to take. Both restart and stop attempt to write & read '
'the pid to a file so it can kill or restart it, while daemonize simply '
'fires and forgets.')
parser.add_argument(
'--pid-file-path',
type=str,
default=None,
help='Path of tmp file to store the daemon\'s pid.')
parser.add_argument(
'--', dest='',
required=False,
help='Optional delimiter dividing daemonizer options with the command. '
'This is here to ensure it\'s backwards compatible with the previous '
'version of daemonizer.')
parser.add_argument('cmd', help='Command (+ args) to daemonize', nargs='*')
args = parser.parse_args()
if args.action == 'restart':
return restart(args.cmd, args.pid_file_path)
elif args.action == 'daemonize':
return daemonize(args.cmd, None)
elif args.action == 'stop':
return stop(args.pid_file_path)
if __name__ == '__main__':
sys.exit(main())