1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """A wrapper for subprocess to make calling shell commands easier."""
17 # fcntl is not available on Windows.
23 _SafeShellChars
= frozenset(string
.ascii_letters
+ string
.digits
+ '@%_-+=:,./')
26 """Return an shell-escaped version of the string using single quotes.
28 Reliably quote a string which may contain unsafe characters (e.g. space,
29 quote, or other special characters such as '$').
31 The returned value can be used in a shell command line as one token that gets
32 to be interpreted literally.
35 s: The string to quote.
38 The string quoted using single quotes.
43 """Return an shell-escaped version of the string using double quotes.
45 Reliably quote a string which may contain unsafe characters (e.g. space
46 or quote characters), while retaining some shell features such as variable
49 The returned value can be used in a shell command line as one token that gets
50 to be further interpreted by the shell.
52 The set of characters that retain their special meaning may depend on the
53 shell implementation. This set usually includes: '$', '`', '\', '!', '*',
57 s: The string to quote.
60 The string quoted using double quotes.
64 elif all(c
in _SafeShellChars
for c
in s
):
67 return '"' + s
.replace('"', '\\"') + '"'
70 def Popen(args
, stdout
=None, stderr
=None, shell
=None, cwd
=None, env
=None):
71 return subprocess
.Popen(
72 args
=args
, cwd
=cwd
, stdout
=stdout
, stderr
=stderr
,
73 shell
=shell
, close_fds
=True, env
=env
,
74 preexec_fn
=lambda: signal
.signal(signal
.SIGPIPE
, signal
.SIG_DFL
))
77 def Call(args
, stdout
=None, stderr
=None, shell
=None, cwd
=None, env
=None):
78 pipe
= Popen(args
, stdout
=stdout
, stderr
=stderr
, shell
=shell
, cwd
=cwd
,
84 def RunCmd(args
, cwd
=None):
85 """Opens a subprocess to execute a program and returns its return value.
88 args: A string or a sequence of program arguments. The program to execute is
89 the string or the first item in the args sequence.
90 cwd: If not None, the subprocess's current directory will be changed to
91 |cwd| before it's executed.
94 Return code from the command execution.
96 logging
.info(str(args
) + ' ' + (cwd
or ''))
97 return Call(args
, cwd
=cwd
)
100 def GetCmdOutput(args
, cwd
=None, shell
=False):
101 """Open a subprocess to execute a program and returns its output.
104 args: A string or a sequence of program arguments. The program to execute is
105 the string or the first item in the args sequence.
106 cwd: If not None, the subprocess's current directory will be changed to
107 |cwd| before it's executed.
108 shell: Whether to execute args as a shell command.
111 Captures and returns the command's stdout.
112 Prints the command's stderr to logger (which defaults to stdout).
114 (_
, output
) = GetCmdStatusAndOutput(args
, cwd
, shell
)
118 def _ValidateAndLogCommand(args
, cwd
, shell
):
119 if isinstance(args
, basestring
):
121 raise Exception('string args must be run with shell=True')
124 raise Exception('array args must be run with shell=False')
125 args
= ' '.join(SingleQuote(c
) for c
in args
)
130 logging
.info('[host]%s> %s', cwd
, args
)
134 def GetCmdStatusAndOutput(args
, cwd
=None, shell
=False):
135 """Executes a subprocess and returns its exit code and output.
138 args: A string or a sequence of program arguments. The program to execute is
139 the string or the first item in the args sequence.
140 cwd: If not None, the subprocess's current directory will be changed to
141 |cwd| before it's executed.
142 shell: Whether to execute args as a shell command. Must be True if args
143 is a string and False if args is a sequence.
146 The 2-tuple (exit code, output).
148 _ValidateAndLogCommand(args
, cwd
, shell
)
149 pipe
= Popen(args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
150 shell
=shell
, cwd
=cwd
)
151 stdout
, stderr
= pipe
.communicate()
154 logging
.critical(stderr
)
155 if len(stdout
) > 4096:
156 logging
.debug('Truncated output:')
157 logging
.debug(stdout
[:4096])
158 return (pipe
.returncode
, stdout
)
161 class TimeoutError(Exception):
162 """Module-specific timeout exception."""
166 def _IterProcessStdout(process
, timeout
=None, buffer_size
=4096,
168 assert fcntl
, 'fcntl module is required'
170 # Enable non-blocking reads from the child's stdout.
171 child_fd
= process
.stdout
.fileno()
172 fl
= fcntl
.fcntl(child_fd
, fcntl
.F_GETFL
)
173 fcntl
.fcntl(child_fd
, fcntl
.F_SETFL
, fl | os
.O_NONBLOCK
)
175 end_time
= (time
.time() + timeout
) if timeout
else None
177 if end_time
and time
.time() > end_time
:
179 read_fds
, _
, _
= select
.select([child_fd
], [], [], poll_interval
)
180 if child_fd
in read_fds
:
181 data
= os
.read(child_fd
, buffer_size
)
185 if process
.poll() is not None:
189 # Make sure the process doesn't stick around if we fail with an
197 def GetCmdStatusAndOutputWithTimeout(args
, timeout
, cwd
=None, shell
=False,
199 """Executes a subprocess with a timeout.
202 args: List of arguments to the program, the program to execute is the first
204 timeout: the timeout in seconds or None to wait forever.
205 cwd: If not None, the subprocess's current directory will be changed to
206 |cwd| before it's executed.
207 shell: Whether to execute args as a shell command. Must be True if args
208 is a string and False if args is a sequence.
209 logfile: Optional file-like object that will receive output from the
210 command as it is running.
213 The 2-tuple (exit code, output).
215 _ValidateAndLogCommand(args
, cwd
, shell
)
216 output
= StringIO
.StringIO()
217 process
= Popen(args
, cwd
=cwd
, shell
=shell
, stdout
=subprocess
.PIPE
,
218 stderr
=subprocess
.STDOUT
)
219 for data
in _IterProcessStdout(process
, timeout
=timeout
):
223 return process
.returncode
, output
.getvalue()
226 def IterCmdOutputLines(args
, timeout
=None, cwd
=None, shell
=False,
228 """Executes a subprocess and continuously yields lines from its output.
231 args: List of arguments to the program, the program to execute is the first
233 cwd: If not None, the subprocess's current directory will be changed to
234 |cwd| before it's executed.
235 shell: Whether to execute args as a shell command. Must be True if args
236 is a string and False if args is a sequence.
237 check_status: A boolean indicating whether to check the exit status of the
238 process after all output has been read.
241 The output of the subprocess, line by line.
244 CalledProcessError if check_status is True and the process exited with a
245 non-zero exit status.
247 cmd
= _ValidateAndLogCommand(args
, cwd
, shell
)
248 process
= Popen(args
, cwd
=cwd
, shell
=shell
, stdout
=subprocess
.PIPE
,
249 stderr
=subprocess
.STDOUT
)
251 for data
in _IterProcessStdout(process
, timeout
=timeout
):
252 buffer_output
+= data
253 has_incomplete_line
= buffer_output
[-1] not in '\r\n'
254 lines
= buffer_output
.splitlines()
255 buffer_output
= lines
.pop() if has_incomplete_line
else ''
260 if check_status
and process
.returncode
:
261 raise subprocess
.CalledProcessError(process
.returncode
, cmd
)