Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """ | 6 """ |
| 7 This script can daemonize another script. This is usefull in running | 7 This script can daemonize another script. This is usefull in running |
| 8 background processes from recipes. Lookat chromium_android module for | 8 background processes from recipes. Lookat chromium_android module for |
| 9 example. | 9 example. |
| 10 | 10 |
| 11 USAGE: | 11 USAGE: |
| 12 | 12 |
| 13 daemonizer.py [options] -- <script> [args] | 13 daemonizer.py [options] -- <script> [args] |
| 14 | 14 |
| 15 - options are options to this script. Note, currently there are none! | 15 - options are options to this script. |
| 16 - script is the script to daemonize or run in the background | 16 - script is the script to daemonize or run in the background |
| 17 - args are the arguments that one might want to pass the <script> | 17 - args are the arguments that one might want to pass the <script> |
| 18 """ | 18 """ |
| 19 | 19 |
| 20 # TODO(sivachandra): Enhance this script by enforcing a protocol of | 20 # TODO(sivachandra): Enhance this script by enforcing a protocol of |
| 21 # communication between the parent (this script) and the daemon script. | 21 # communication between the parent (this script) and the daemon script. |
| 22 | 22 |
| 23 import argparse | |
| 24 import logging | |
| 23 import os | 25 import os |
| 26 import signal | |
| 24 import subprocess | 27 import subprocess |
| 25 import sys | 28 import sys |
| 26 | 29 |
| 30 def start(cmd, pid_file_path): | |
|
luqui
2015/09/08 18:18:10
seems like this action ought to be called 'restart
bpastene
2015/09/08 20:50:47
Done.
| |
| 31 # Check for the pid_file to see if the daemon's already running | |
| 32 # and restart it if it is | |
| 33 if (pid_file_path == None): | |
|
luqui
2015/09/08 18:18:10
You don't need parens around if's conditional in p
bpastene
2015/09/08 20:50:47
Done.
| |
| 34 logging.error('pid_file_path arg must be specified when starting a daemon') | |
| 35 return 1 | |
| 36 try: | |
| 37 with open(pid_file_path, 'r') as pid_file: | |
| 38 pid = int(pid_file.readline()) | |
| 39 except (IOError, ValueError): | |
| 40 pid = None | |
| 27 | 41 |
| 28 def daemonize(): | 42 if (pid): |
|
luqui
2015/09/08 18:18:10
no parens
bpastene
2015/09/08 20:50:47
Done.
| |
| 43 logging.info( | |
| 44 "%s pid file already exists, attempting to kill process %d", | |
| 45 pid_file_path, | |
| 46 pid) | |
| 47 try: | |
| 48 os.kill(pid, signal.SIGTERM) | |
| 49 except (OSError): | |
|
luqui
2015/09/08 18:18:10
no parens
bpastene
2015/09/08 20:50:47
Done.
| |
| 50 logging.exception("Unable to kill old daemon process") | |
| 51 | |
| 52 return daemonize(cmd, pid_file_path) | |
| 53 | |
| 54 def daemonize(cmd, pid_file_path): | |
| 29 """This function is based on the Python recipe provided here: | 55 """This function is based on the Python recipe provided here: |
| 30 http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ | 56 http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ |
| 31 """ | 57 """ |
| 32 # Spawn a detached child process. | 58 # Spawn a detached child process. |
| 33 try: | 59 try: |
| 34 pid = os.fork() | 60 pid = os.fork() |
| 35 if pid > 0: | 61 if pid > 0: |
| 36 # exit first parent | 62 # exit first parent |
| 37 sys.exit(0) | 63 sys.exit(0) |
| 38 except OSError, e: | 64 except OSError, e: |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 59 # redirect standard file descriptors | 85 # redirect standard file descriptors |
| 60 sys.stdout.flush() | 86 sys.stdout.flush() |
| 61 sys.stderr.flush() | 87 sys.stderr.flush() |
| 62 si = file('/dev/null', 'r') | 88 si = file('/dev/null', 'r') |
| 63 so = file('/dev/null', 'a+') | 89 so = file('/dev/null', 'a+') |
| 64 se = file('/dev/null', 'a+', 0) | 90 se = file('/dev/null', 'a+', 0) |
| 65 os.dup2(si.fileno(), sys.stdin.fileno()) | 91 os.dup2(si.fileno(), sys.stdin.fileno()) |
| 66 os.dup2(so.fileno(), sys.stdout.fileno()) | 92 os.dup2(so.fileno(), sys.stdout.fileno()) |
| 67 os.dup2(se.fileno(), sys.stderr.fileno()) | 93 os.dup2(se.fileno(), sys.stderr.fileno()) |
| 68 | 94 |
| 95 proc = subprocess.Popen(cmd) | |
| 69 | 96 |
| 70 def print_usage(err_msg): | 97 # write pid to file if applicable |
| 71 print >> sys.stderr, err_msg | 98 if (pid_file_path): |
| 72 sys.exit('Usage: daemonizer.py [options] -- arg0 [argN...]') | 99 try: |
| 100 with open(pid_file_path, 'w+') as pid_file: | |
|
luqui
2015/09/08 18:18:10
I think you mean 'w' mode instead of 'w+'.
bpastene
2015/09/08 20:50:47
Done.
| |
| 101 pid_file.write('%s' % str(proc.pid)) | |
| 102 except (IOError): | |
| 103 logging.exception("Unable to write pid to file") | |
| 73 | 104 |
| 105 proc.communicate() | |
|
luqui
2015/09/08 18:18:10
This will make daemonize block, which is the oppos
bpastene
2015/09/08 20:50:47
Previously, it would do just that, and return the
luqui
2015/09/08 21:15:57
Oh I misunderstood, I missed the fork() above. I
| |
| 106 return proc.returncode | |
| 107 | |
| 108 def stop(pid_file_path): | |
| 109 if (pid_file_path == None): | |
| 110 logging.error("pid_file_path arg must be specified when stopping a daemon") | |
| 111 return 1 | |
| 112 try: | |
| 113 with open(pid_file_path) as pid_file: | |
| 114 pid = int(pid_file.readline()) | |
| 115 logging.info('Sending SIGTERM to %d', pid) | |
| 116 os.kill(pid, signal.SIGTERM) | |
| 117 os.remove(pid_file_path) | |
| 118 except (IOError, OSError): | |
| 119 logging.exception('Error terminating daemon process') | |
|
luqui
2015/09/08 18:18:10
Log the exception here so we have some idea what t
bpastene
2015/09/08 20:50:47
logging.exception() automatically logs the excepti
luqui
2015/09/08 21:15:57
Oh cool :-)
| |
| 74 | 120 |
| 75 def main(): | 121 def main(): |
| 76 try: | 122 parser = argparse.ArgumentParser( |
| 77 idx = sys.argv.index('--') | 123 description='Launch, or shutdown, a daemon process.') |
| 78 except ValueError: | 124 parser.add_argument( |
| 79 print_usage('Separator -- not found') | 125 '--action', |
| 126 default='daemonize', | |
| 127 choices=['start','stop','daemonize'], | |
| 128 help='What action to take. Both start and stop attempt to write & read ' | |
| 129 'the pid to a file so it can kill or restart it, while daemonize simply ' | |
| 130 'fires and forgets.') | |
| 131 parser.add_argument( | |
| 132 '--pid-file-path', | |
| 133 type=str, | |
| 134 default=None, | |
| 135 help='Path of tmp file to store the daemon\'s pid.') | |
| 136 parser.add_argument( | |
| 137 '--', dest='', | |
| 138 required=False, | |
| 139 help='Optional delimiter dividing daemonizer options with the command. ' | |
| 140 'This is here to ensure it\'s backwards compatible with the previous ' | |
| 141 'version of daemonizer.') | |
| 142 parser.add_argument('cmd', help='Command (+ args) to daemonize', nargs='*') | |
| 143 args = parser.parse_args() | |
| 80 | 144 |
| 81 cmd = sys.argv[idx+1:] | 145 if (args.action == 'start'): |
| 82 if not cmd: | 146 return start(args.cmd, args.pid_file_path) |
| 83 print_usage('arg0 not specified for sub command') | 147 elif (args.action == 'daemonize'): |
| 84 | 148 return daemonize(args.cmd, None) |
| 85 # TODO(sivachandra): When required in the future, use optparse to parse args | 149 elif (args.action == 'stop'): |
| 86 # from sys.argv[:idx] | 150 return stop(args.pid_file_path) |
| 87 | |
| 88 daemonize() | |
| 89 return subprocess.call(cmd) | |
| 90 | |
| 91 | 151 |
| 92 if __name__ == '__main__': | 152 if __name__ == '__main__': |
| 93 sys.exit(main()) | 153 sys.exit(main()) |
| OLD | NEW |