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 # TODO(bpastene): Improve file handling by adding flocks over file io |
| 24 # as implemented in infra/libs/service_utils/_daemon_nix.py |
| 25 |
| 26 import argparse |
| 27 import logging |
23 import os | 28 import os |
| 29 import signal |
24 import subprocess | 30 import subprocess |
25 import sys | 31 import sys |
26 | 32 |
27 | 33 |
28 def daemonize(): | 34 def restart(cmd, pid_file_path): |
| 35 # Check for the pid_file to see if the daemon's already running |
| 36 # and restart it if it is. |
| 37 if pid_file_path == None: |
| 38 logging.error('pid_file_path arg must be specified when ' |
| 39 'restarting a daemon') |
| 40 return 1 |
| 41 try: |
| 42 with open(pid_file_path, 'r') as pid_file: |
| 43 pid = int(pid_file.readline()) |
| 44 except (IOError, ValueError): |
| 45 pid = None |
| 46 |
| 47 if pid: |
| 48 logging.info( |
| 49 "%s pid file already exists, attempting to kill process %d", |
| 50 pid_file_path, |
| 51 pid) |
| 52 try: |
| 53 os.kill(pid, signal.SIGTERM) |
| 54 except OSError: |
| 55 logging.exception("Unable to kill old daemon process") |
| 56 |
| 57 return daemonize(cmd, pid_file_path) |
| 58 |
| 59 |
| 60 def daemonize(cmd, pid_file_path): |
29 """This function is based on the Python recipe provided here: | 61 """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/ | 62 http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ |
31 """ | 63 """ |
32 # Spawn a detached child process. | 64 # Spawn a detached child process. |
33 try: | 65 try: |
34 pid = os.fork() | 66 pid = os.fork() |
35 if pid > 0: | 67 if pid > 0: |
36 # exit first parent | 68 # exit first parent |
37 sys.exit(0) | 69 sys.exit(0) |
38 except OSError, e: | 70 except OSError, e: |
(...skipping 20 matching lines...) Expand all Loading... |
59 # redirect standard file descriptors | 91 # redirect standard file descriptors |
60 sys.stdout.flush() | 92 sys.stdout.flush() |
61 sys.stderr.flush() | 93 sys.stderr.flush() |
62 si = file('/dev/null', 'r') | 94 si = file('/dev/null', 'r') |
63 so = file('/dev/null', 'a+') | 95 so = file('/dev/null', 'a+') |
64 se = file('/dev/null', 'a+', 0) | 96 se = file('/dev/null', 'a+', 0) |
65 os.dup2(si.fileno(), sys.stdin.fileno()) | 97 os.dup2(si.fileno(), sys.stdin.fileno()) |
66 os.dup2(so.fileno(), sys.stdout.fileno()) | 98 os.dup2(so.fileno(), sys.stdout.fileno()) |
67 os.dup2(se.fileno(), sys.stderr.fileno()) | 99 os.dup2(se.fileno(), sys.stderr.fileno()) |
68 | 100 |
| 101 proc = subprocess.Popen(cmd) |
69 | 102 |
70 def print_usage(err_msg): | 103 # Write pid to file if applicable. |
71 print >> sys.stderr, err_msg | 104 if pid_file_path: |
72 sys.exit('Usage: daemonizer.py [options] -- arg0 [argN...]') | 105 try: |
| 106 with open(pid_file_path, 'w') as pid_file: |
| 107 pid_file.write('%s' % str(proc.pid)) |
| 108 except (IOError): |
| 109 logging.exception("Unable to write pid to file") |
| 110 |
| 111 proc.communicate() |
| 112 return proc.returncode |
| 113 |
| 114 |
| 115 def stop(pid_file_path): |
| 116 if pid_file_path == None: |
| 117 logging.error("pid_file_path arg must be specified when stopping a daemon") |
| 118 return 1 |
| 119 try: |
| 120 with open(pid_file_path) as pid_file: |
| 121 pid = int(pid_file.readline()) |
| 122 logging.info('Sending SIGTERM to %d', pid) |
| 123 os.kill(pid, signal.SIGTERM) |
| 124 os.remove(pid_file_path) |
| 125 except (IOError, OSError): |
| 126 logging.exception('Error terminating daemon process') |
73 | 127 |
74 | 128 |
75 def main(): | 129 def main(): |
76 try: | 130 parser = argparse.ArgumentParser( |
77 idx = sys.argv.index('--') | 131 description='Launch, or shutdown, a daemon process.') |
78 except ValueError: | 132 parser.add_argument( |
79 print_usage('Separator -- not found') | 133 '--action', |
| 134 default='daemonize', |
| 135 choices=['restart','stop','daemonize'], |
| 136 help='What action to take. Both restart and stop attempt to write & read ' |
| 137 'the pid to a file so it can kill or restart it, while daemonize simply ' |
| 138 'fires and forgets.') |
| 139 parser.add_argument( |
| 140 '--pid-file-path', |
| 141 type=str, |
| 142 default=None, |
| 143 help='Path of tmp file to store the daemon\'s pid.') |
| 144 parser.add_argument( |
| 145 '--', dest='', |
| 146 required=False, |
| 147 help='Optional delimiter dividing daemonizer options with the command. ' |
| 148 'This is here to ensure it\'s backwards compatible with the previous ' |
| 149 'version of daemonizer.') |
| 150 parser.add_argument('cmd', help='Command (+ args) to daemonize', nargs='*') |
| 151 args = parser.parse_args() |
80 | 152 |
81 cmd = sys.argv[idx+1:] | 153 if args.action == 'restart': |
82 if not cmd: | 154 return restart(args.cmd, args.pid_file_path) |
83 print_usage('arg0 not specified for sub command') | 155 elif args.action == 'daemonize': |
84 | 156 return daemonize(args.cmd, None) |
85 # TODO(sivachandra): When required in the future, use optparse to parse args | 157 elif args.action == 'stop': |
86 # from sys.argv[:idx] | 158 return stop(args.pid_file_path) |
87 | |
88 daemonize() | |
89 return subprocess.call(cmd) | |
90 | 159 |
91 | 160 |
92 if __name__ == '__main__': | 161 if __name__ == '__main__': |
93 sys.exit(main()) | 162 sys.exit(main()) |
OLD | NEW |