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 _LogCommand(args
, cwd
=None):
119 if not isinstance(args
, basestring
):
120 args
= ' '.join(SingleQuote(c
) for c
in args
)
125 logging
.info('[host]%s> %s', cwd
, args
)
128 def GetCmdStatusAndOutput(args
, cwd
=None, shell
=False):
129 """Executes a subprocess and returns its exit code and output.
132 args: A string or a sequence of program arguments. The program to execute is
133 the string or the first item in the args sequence.
134 cwd: If not None, the subprocess's current directory will be changed to
135 |cwd| before it's executed.
136 shell: Whether to execute args as a shell command.
139 The 2-tuple (exit code, output).
141 if isinstance(args
, basestring
):
143 raise Exception('string args must be run with shell=True')
145 raise Exception('array args must be run with shell=False')
147 _LogCommand(args
, cwd
)
148 pipe
= Popen(args
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
149 shell
=shell
, cwd
=cwd
)
150 stdout
, stderr
= pipe
.communicate()
153 logging
.critical(stderr
)
154 if len(stdout
) > 4096:
155 logging
.debug('Truncated output:')
156 logging
.debug(stdout
[:4096])
157 return (pipe
.returncode
, stdout
)
160 class TimeoutError(Exception):
161 """Module-specific timeout exception."""
165 def GetCmdStatusAndOutputWithTimeout(args
, timeout
, cwd
=None, shell
=False,
167 """Executes a subprocess with a timeout.
170 args: List of arguments to the program, the program to execute is the first
172 timeout: the timeout in seconds or None to wait forever.
173 cwd: If not None, the subprocess's current directory will be changed to
174 |cwd| before it's executed.
175 shell: Whether to execute args as a shell command.
176 logfile: Optional file-like object that will receive output from the
177 command as it is running.
180 The 2-tuple (exit code, output).
182 assert fcntl
, 'fcntl module is required'
183 if isinstance(args
, basestring
):
185 raise Exception('string args must be run with shell=True')
187 raise Exception('array args must be run with shell=False')
189 _LogCommand(args
, cwd
)
190 process
= Popen(args
, cwd
=cwd
, shell
=shell
, stdout
=subprocess
.PIPE
,
191 stderr
=subprocess
.STDOUT
)
193 end_time
= (time
.time() + timeout
) if timeout
else None
196 child_fd
= process
.stdout
.fileno()
197 output
= StringIO
.StringIO()
199 # Enable non-blocking reads from the child's stdout.
200 fl
= fcntl
.fcntl(child_fd
, fcntl
.F_GETFL
)
201 fcntl
.fcntl(child_fd
, fcntl
.F_SETFL
, fl | os
.O_NONBLOCK
)
204 if end_time
and time
.time() > end_time
:
206 read_fds
, _
, _
= select
.select([child_fd
], [], [], poll_interval
)
207 if child_fd
in read_fds
:
208 data
= os
.read(child_fd
, buffer_size
)
214 if process
.poll() is not None:
218 # Make sure the process doesn't stick around if we fail with an
224 return process
.returncode
, output
.getvalue()