blob: 2e97e357161008ef3705b19568f6009d7e1bfeb2 [file] [log] [blame]
Raul Tambrea04028c2019-05-13 17:23:36 +00001# coding=utf-8
maruel@chromium.org4f6852c2012-04-20 20:39:20 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org4860f052011-03-25 20:34:38 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Collection of subprocess wrapper functions.
6
7In theory you shouldn't need anything else in subprocess, or this module failed.
8"""
9
John Budorick9875e182018-12-05 22:57:31 +000010import codecs
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000011import errno
maruel@chromium.org4860f052011-03-25 20:34:38 +000012import logging
13import os
14import subprocess
15import sys
maruel@chromium.org4860f052011-03-25 20:34:38 +000016import threading
17
John Budorick9875e182018-12-05 22:57:31 +000018# Cache the string-escape codec to ensure subprocess can find it later.
19# See crbug.com/912292#c2 for context.
Raul Tambreb946b232019-03-26 14:48:46 +000020if sys.version_info.major == 2:
21 codecs.lookup('string-escape')
Edward Lesmescf06cad2020-12-14 22:03:23 +000022 # Sends stdout or stderr to os.devnull.
23 DEVNULL = open(os.devnull, 'r+')
Edward Lemur1556fbc2019-08-09 15:24:48 +000024else:
Aaron Gableac9b0f32019-04-18 17:38:37 +000025 # pylint: disable=redefined-builtin
Edward Lemur1556fbc2019-08-09 15:24:48 +000026 basestring = (str, bytes)
Edward Lesmescf06cad2020-12-14 22:03:23 +000027 DEVNULL = subprocess.DEVNULL
Aaron Gableac9b0f32019-04-18 17:38:37 +000028
29
maruel@chromium.org4860f052011-03-25 20:34:38 +000030# Constants forwarded from subprocess.
31PIPE = subprocess.PIPE
32STDOUT = subprocess.STDOUT
33
maruel@chromium.org4860f052011-03-25 20:34:38 +000034
35class CalledProcessError(subprocess.CalledProcessError):
36 """Augment the standard exception with more data."""
37 def __init__(self, returncode, cmd, cwd, stdout, stderr):
tandrii@chromium.orgc15fe572014-09-19 11:51:43 +000038 super(CalledProcessError, self).__init__(returncode, cmd, output=stdout)
39 self.stdout = self.output # for backward compatibility.
maruel@chromium.org4860f052011-03-25 20:34:38 +000040 self.stderr = stderr
41 self.cwd = cwd
42
43 def __str__(self):
sbc@chromium.org217330f2015-06-01 22:10:14 +000044 out = 'Command %r returned non-zero exit status %s' % (
maruel@chromium.org4860f052011-03-25 20:34:38 +000045 ' '.join(self.cmd), self.returncode)
46 if self.cwd:
47 out += ' in ' + self.cwd
Edward Lemur7ad1d092020-03-11 00:58:39 +000048 if self.stdout:
49 out += '\n' + self.stdout.decode('utf-8', 'ignore')
50 if self.stderr:
51 out += '\n' + self.stderr.decode('utf-8', 'ignore')
52 return out
maruel@chromium.org4860f052011-03-25 20:34:38 +000053
54
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000055class CygwinRebaseError(CalledProcessError):
56 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
57
58
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000059## Utility functions
60
61
62def kill_pid(pid):
63 """Kills a process by its process id."""
64 try:
65 # Unable to import 'module'
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -080066 # pylint: disable=no-member,F0401
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000067 import signal
Raul Tambree99d4b42019-05-24 18:34:41 +000068 return os.kill(pid, signal.SIGTERM)
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000069 except ImportError:
70 pass
71
72
maruel@chromium.org4860f052011-03-25 20:34:38 +000073def get_english_env(env):
74 """Forces LANG and/or LANGUAGE to be English.
75
76 Forces encoding to utf-8 for subprocesses.
77
78 Returns None if it is unnecessary.
79 """
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +000080 if sys.platform == 'win32':
81 return None
maruel@chromium.org4860f052011-03-25 20:34:38 +000082 env = env or os.environ
83
84 # Test if it is necessary at all.
85 is_english = lambda name: env.get(name, 'en').startswith('en')
86
87 if is_english('LANG') and is_english('LANGUAGE'):
88 return None
89
90 # Requires modifications.
91 env = env.copy()
92 def fix_lang(name):
93 if not is_english(name):
94 env[name] = 'en_US.UTF-8'
95 fix_lang('LANG')
96 fix_lang('LANGUAGE')
97 return env
98
99
maruel@google.comef77f9e2011-11-24 15:24:02 +0000100class Popen(subprocess.Popen):
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000101 """Wraps subprocess.Popen() with various workarounds.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000102
maruel@chromium.org421982f2011-04-01 17:38:06 +0000103 - Forces English output since it's easier to parse the stdout if it is always
104 in English.
105 - Sets shell=True on windows by default. You can override this by forcing
106 shell parameter to a value.
Edward Lesmescf06cad2020-12-14 22:03:23 +0000107 - Adds support for DEVNULL to not buffer when not needed.
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000108 - Adds self.start property.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000109
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000110 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
111 exceptions generated by cygwin when it fails trying to emulate fork().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000112 """
torne@chromium.org434e7902015-09-15 09:57:01 +0000113 # subprocess.Popen.__init__() is not threadsafe; there is a race between
114 # creating the exec-error pipe for the child and setting it to CLOEXEC during
115 # which another thread can fork and cause the pipe to be inherited by its
116 # descendents, which will cause the current Popen to hang until all those
117 # descendents exit. Protect this with a lock so that only one fork/exec can
118 # happen at a time.
119 popen_lock = threading.Lock()
120
maruel@google.comef77f9e2011-11-24 15:24:02 +0000121 def __init__(self, args, **kwargs):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000122 env = get_english_env(kwargs.get('env'))
123 if env:
124 kwargs['env'] = env
Raul Tambree9730d72020-01-15 19:28:48 +0000125 if kwargs.get('env') is not None and sys.version_info.major != 2:
126 # Subprocess expects environment variables to be strings in Python 3.
127 def ensure_str(value):
128 if isinstance(value, bytes):
129 return value.decode()
130 return value
131
132 kwargs['env'] = {
133 ensure_str(k): ensure_str(v)
134 for k, v in kwargs['env'].items()
135 }
maruel@google.comef77f9e2011-11-24 15:24:02 +0000136 if kwargs.get('shell') is None:
137 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
138 # the executable, but shell=True makes subprocess on Linux fail when it's
139 # called with a list because it only tries to execute the first item in
140 # the list.
141 kwargs['shell'] = bool(sys.platform=='win32')
maruel@chromium.org4860f052011-03-25 20:34:38 +0000142
Aaron Gableac9b0f32019-04-18 17:38:37 +0000143 if isinstance(args, basestring):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000144 tmp_str = args
145 elif isinstance(args, (list, tuple)):
146 tmp_str = ' '.join(args)
147 else:
148 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
149 if kwargs.get('cwd', None):
150 tmp_str += '; cwd=%s' % kwargs['cwd']
151 logging.debug(tmp_str)
maruel@chromium.org421982f2011-04-01 17:38:06 +0000152
maruel@google.comef77f9e2011-11-24 15:24:02 +0000153 try:
torne@chromium.org434e7902015-09-15 09:57:01 +0000154 with self.popen_lock:
155 super(Popen, self).__init__(args, **kwargs)
Raul Tambreb946b232019-03-26 14:48:46 +0000156 except OSError as e:
maruel@google.comef77f9e2011-11-24 15:24:02 +0000157 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
158 # Convert fork() emulation failure into a CygwinRebaseError().
159 raise CygwinRebaseError(
160 e.errno,
161 args,
162 kwargs.get('cwd'),
163 None,
164 'Visit '
165 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
166 'to learn how to fix this error; you need to rebase your cygwin '
167 'dlls')
luqui@chromium.org7f627a92014-03-28 00:57:44 +0000168 # Popen() can throw OSError when cwd or args[0] doesn't exist.
pgervais@chromium.orgfb653b62014-04-29 17:29:18 +0000169 raise OSError('Execution failed with error: %s.\n'
170 'Check that %s or %s exist and have execution permission.'
171 % (str(e), kwargs.get('cwd'), args[0]))
maruel@chromium.org4860f052011-03-25 20:34:38 +0000172
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000173
Edward Lemur1556fbc2019-08-09 15:24:48 +0000174def communicate(args, **kwargs):
175 """Wraps subprocess.Popen().communicate().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000176
maruel@chromium.org421982f2011-04-01 17:38:06 +0000177 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000178
szager@chromium.orge0558e62013-05-02 02:48:51 +0000179 - If the subprocess runs for |nag_timer| seconds without producing terminal
180 output, print a warning to stderr.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000181 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000182 """
Edward Lemur1556fbc2019-08-09 15:24:48 +0000183 stdin = None
184 # When stdin is passed as an argument, use it as the actual input data and
185 # set the Popen() parameter accordingly.
186 if 'stdin' in kwargs and isinstance(kwargs['stdin'], basestring):
187 stdin = kwargs['stdin']
188 kwargs['stdin'] = PIPE
maruel@chromium.org4860f052011-03-25 20:34:38 +0000189
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000190 proc = Popen(args, **kwargs)
Edward Lemur1556fbc2019-08-09 15:24:48 +0000191 return proc.communicate(stdin), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000192
193
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000194def call(args, **kwargs):
195 """Emulates subprocess.call().
196
Edward Lesmescf06cad2020-12-14 22:03:23 +0000197 Automatically convert stdout=PIPE or stderr=PIPE to DEVNULL.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000198 In no case they can be returned since no code path raises
199 subprocess2.CalledProcessError.
Josip Sokcevic252ff1f2020-06-01 23:08:00 +0000200
201 Returns exit code.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000202 """
203 if kwargs.get('stdout') == PIPE:
Edward Lesmescf06cad2020-12-14 22:03:23 +0000204 kwargs['stdout'] = DEVNULL
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000205 if kwargs.get('stderr') == PIPE:
Edward Lesmescf06cad2020-12-14 22:03:23 +0000206 kwargs['stderr'] = DEVNULL
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000207 return communicate(args, **kwargs)[1]
208
209
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000210def check_call_out(args, **kwargs):
maruel@chromium.org421982f2011-04-01 17:38:06 +0000211 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000212
maruel@chromium.org421982f2011-04-01 17:38:06 +0000213 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000214 """
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000215 out, returncode = communicate(args, **kwargs)
maruel@chromium.org4860f052011-03-25 20:34:38 +0000216 if returncode:
217 raise CalledProcessError(
218 returncode, args, kwargs.get('cwd'), out[0], out[1])
219 return out
220
221
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000222def check_call(args, **kwargs):
223 """Emulate subprocess.check_call()."""
224 check_call_out(args, **kwargs)
225 return 0
226
227
maruel@chromium.org4860f052011-03-25 20:34:38 +0000228def capture(args, **kwargs):
229 """Captures stdout of a process call and returns it.
230
maruel@chromium.org421982f2011-04-01 17:38:06 +0000231 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000232
maruel@chromium.org421982f2011-04-01 17:38:06 +0000233 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000234 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000235 """
Edward Lesmescf06cad2020-12-14 22:03:23 +0000236 kwargs.setdefault('stdin', DEVNULL)
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000237
238 # Like check_output, deny the caller from using stdout arg.
239 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000240
241
242def check_output(args, **kwargs):
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000243 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000244
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000245 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000246
maruel@chromium.org421982f2011-04-01 17:38:06 +0000247 - Throws if return code is not 0.
248 - Works even prior to python 2.7.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000249 - Blocks stdin by default if not specified since no output will be visible.
250 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000251 """
Edward Lesmescf06cad2020-12-14 22:03:23 +0000252 kwargs.setdefault('stdin', DEVNULL)
maruel@chromium.orgdb59bfc2011-11-30 14:03:14 +0000253 if 'stdout' in kwargs:
pgervais@chromium.org022d06e2014-04-29 17:08:12 +0000254 raise ValueError('stdout argument not allowed, it would be overridden.')
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000255 return check_call_out(args, stdout=PIPE, **kwargs)[0]