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 |