| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """A wrapper for subprocess to make calling shell commands easier.""" | 5 """A wrapper for subprocess to make calling shell commands easier.""" |
| 6 | 6 |
| 7 import logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import pipes | 9 import pipes |
| 10 import select | 10 import select |
| 11 import signal | 11 import signal |
| 12 import string | |
| 13 import StringIO | 12 import StringIO |
| 14 import subprocess | 13 import subprocess |
| 15 import time | 14 import time |
| 16 | 15 |
| 17 # fcntl is not available on Windows. | 16 # fcntl is not available on Windows. |
| 18 try: | 17 try: |
| 19 import fcntl | 18 import fcntl |
| 20 except ImportError: | 19 except ImportError: |
| 21 fcntl = None | 20 fcntl = None |
| 22 | 21 |
| 23 _SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./') | |
| 24 | |
| 25 def SingleQuote(s): | |
| 26 """Return an shell-escaped version of the string using single quotes. | |
| 27 | |
| 28 Reliably quote a string which may contain unsafe characters (e.g. space, | |
| 29 quote, or other special characters such as '$'). | |
| 30 | |
| 31 The returned value can be used in a shell command line as one token that gets | |
| 32 to be interpreted literally. | |
| 33 | |
| 34 Args: | |
| 35 s: The string to quote. | |
| 36 | |
| 37 Return: | |
| 38 The string quoted using single quotes. | |
| 39 """ | |
| 40 return pipes.quote(s) | |
| 41 | |
| 42 def DoubleQuote(s): | |
| 43 """Return an shell-escaped version of the string using double quotes. | |
| 44 | |
| 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 | |
| 47 interpolation. | |
| 48 | |
| 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. | |
| 51 | |
| 52 The set of characters that retain their special meaning may depend on the | |
| 53 shell implementation. This set usually includes: '$', '`', '\', '!', '*', | |
| 54 and '@'. | |
| 55 | |
| 56 Args: | |
| 57 s: The string to quote. | |
| 58 | |
| 59 Return: | |
| 60 The string quoted using double quotes. | |
| 61 """ | |
| 62 if not s: | |
| 63 return '""' | |
| 64 elif all(c in _SafeShellChars for c in s): | |
| 65 return s | |
| 66 else: | |
| 67 return '"' + s.replace('"', '\\"') + '"' | |
| 68 | |
| 69 | 22 |
| 70 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): | 23 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
| 71 return subprocess.Popen( | 24 return subprocess.Popen( |
| 72 args=args, cwd=cwd, stdout=stdout, stderr=stderr, | 25 args=args, cwd=cwd, stdout=stdout, stderr=stderr, |
| 73 shell=shell, close_fds=True, env=env, | 26 shell=shell, close_fds=True, env=env, |
| 74 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) | 27 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) |
| 75 | 28 |
| 76 | 29 |
| 77 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): | 30 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, | 31 pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 128 Returns: | 81 Returns: |
| 129 The 2-tuple (exit code, output). | 82 The 2-tuple (exit code, output). |
| 130 """ | 83 """ |
| 131 if isinstance(args, basestring): | 84 if isinstance(args, basestring): |
| 132 args_repr = args | 85 args_repr = args |
| 133 if not shell: | 86 if not shell: |
| 134 raise Exception('string args must be run with shell=True') | 87 raise Exception('string args must be run with shell=True') |
| 135 elif shell: | 88 elif shell: |
| 136 raise Exception('array args must be run with shell=False') | 89 raise Exception('array args must be run with shell=False') |
| 137 else: | 90 else: |
| 138 args_repr = ' '.join(map(SingleQuote, args)) | 91 args_repr = ' '.join(map(pipes.quote, args)) |
| 139 | 92 |
| 140 s = '[host]' | 93 s = '[host]' |
| 141 if cwd: | 94 if cwd: |
| 142 s += ':' + cwd | 95 s += ':' + cwd |
| 143 s += '> ' + args_repr | 96 s += '> ' + args_repr |
| 144 logging.info(s) | 97 logging.info(s) |
| 145 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 98 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| 146 shell=shell, cwd=cwd) | 99 shell=shell, cwd=cwd) |
| 147 stdout, stderr = pipe.communicate() | 100 stdout, stderr = pipe.communicate() |
| 148 | 101 |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 205 break | 158 break |
| 206 finally: | 159 finally: |
| 207 try: | 160 try: |
| 208 # Make sure the process doesn't stick around if we fail with an | 161 # Make sure the process doesn't stick around if we fail with an |
| 209 # exception. | 162 # exception. |
| 210 process.kill() | 163 process.kill() |
| 211 except OSError: | 164 except OSError: |
| 212 pass | 165 pass |
| 213 process.wait() | 166 process.wait() |
| 214 return process.returncode, output.getvalue() | 167 return process.returncode, output.getvalue() |
| OLD | NEW |