xref: /aosp_15_r20/tools/repohooks/rh/utils.py (revision d68f33bc6fb0cc2476107c2af0573a2f5a63dfc1)
1*d68f33bcSAndroid Build Coastguard Worker# Copyright 2016 The Android Open Source Project
2*d68f33bcSAndroid Build Coastguard Worker#
3*d68f33bcSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*d68f33bcSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*d68f33bcSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*d68f33bcSAndroid Build Coastguard Worker#
7*d68f33bcSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
8*d68f33bcSAndroid Build Coastguard Worker#
9*d68f33bcSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*d68f33bcSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*d68f33bcSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*d68f33bcSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*d68f33bcSAndroid Build Coastguard Worker# limitations under the License.
14*d68f33bcSAndroid Build Coastguard Worker
15*d68f33bcSAndroid Build Coastguard Worker"""Various utility functions."""
16*d68f33bcSAndroid Build Coastguard Worker
17*d68f33bcSAndroid Build Coastguard Workerimport errno
18*d68f33bcSAndroid Build Coastguard Workerimport functools
19*d68f33bcSAndroid Build Coastguard Workerimport os
20*d68f33bcSAndroid Build Coastguard Workerimport signal
21*d68f33bcSAndroid Build Coastguard Workerimport subprocess
22*d68f33bcSAndroid Build Coastguard Workerimport sys
23*d68f33bcSAndroid Build Coastguard Workerimport tempfile
24*d68f33bcSAndroid Build Coastguard Workerimport time
25*d68f33bcSAndroid Build Coastguard Worker
26*d68f33bcSAndroid Build Coastguard Worker_path = os.path.realpath(__file__ + '/../..')
27*d68f33bcSAndroid Build Coastguard Workerif sys.path[0] != _path:
28*d68f33bcSAndroid Build Coastguard Worker    sys.path.insert(0, _path)
29*d68f33bcSAndroid Build Coastguard Workerdel _path
30*d68f33bcSAndroid Build Coastguard Worker
31*d68f33bcSAndroid Build Coastguard Worker# pylint: disable=wrong-import-position
32*d68f33bcSAndroid Build Coastguard Workerimport rh.shell
33*d68f33bcSAndroid Build Coastguard Workerimport rh.signals
34*d68f33bcSAndroid Build Coastguard Worker
35*d68f33bcSAndroid Build Coastguard Worker
36*d68f33bcSAndroid Build Coastguard Workerdef timedelta_str(delta):
37*d68f33bcSAndroid Build Coastguard Worker    """A less noisy timedelta.__str__.
38*d68f33bcSAndroid Build Coastguard Worker
39*d68f33bcSAndroid Build Coastguard Worker    The default timedelta stringification contains a lot of leading zeros and
40*d68f33bcSAndroid Build Coastguard Worker    uses microsecond resolution.  This makes for noisy output.
41*d68f33bcSAndroid Build Coastguard Worker    """
42*d68f33bcSAndroid Build Coastguard Worker    total = delta.total_seconds()
43*d68f33bcSAndroid Build Coastguard Worker    hours, rem = divmod(total, 3600)
44*d68f33bcSAndroid Build Coastguard Worker    mins, secs = divmod(rem, 60)
45*d68f33bcSAndroid Build Coastguard Worker    ret = f'{int(secs)}.{delta.microseconds // 1000:03}s'
46*d68f33bcSAndroid Build Coastguard Worker    if mins:
47*d68f33bcSAndroid Build Coastguard Worker        ret = f'{int(mins)}m{ret}'
48*d68f33bcSAndroid Build Coastguard Worker    if hours:
49*d68f33bcSAndroid Build Coastguard Worker        ret = f'{int(hours)}h{ret}'
50*d68f33bcSAndroid Build Coastguard Worker    return ret
51*d68f33bcSAndroid Build Coastguard Worker
52*d68f33bcSAndroid Build Coastguard Worker
53*d68f33bcSAndroid Build Coastguard Workerclass CompletedProcess(subprocess.CompletedProcess):
54*d68f33bcSAndroid Build Coastguard Worker    """An object to store various attributes of a child process.
55*d68f33bcSAndroid Build Coastguard Worker
56*d68f33bcSAndroid Build Coastguard Worker    This is akin to subprocess.CompletedProcess.
57*d68f33bcSAndroid Build Coastguard Worker    """
58*d68f33bcSAndroid Build Coastguard Worker
59*d68f33bcSAndroid Build Coastguard Worker    def __init__(self, args=None, returncode=None, stdout=None, stderr=None):
60*d68f33bcSAndroid Build Coastguard Worker        super().__init__(
61*d68f33bcSAndroid Build Coastguard Worker            args=args, returncode=returncode, stdout=stdout, stderr=stderr)
62*d68f33bcSAndroid Build Coastguard Worker
63*d68f33bcSAndroid Build Coastguard Worker    @property
64*d68f33bcSAndroid Build Coastguard Worker    def cmd(self):
65*d68f33bcSAndroid Build Coastguard Worker        """Alias to self.args to better match other subprocess APIs."""
66*d68f33bcSAndroid Build Coastguard Worker        return self.args
67*d68f33bcSAndroid Build Coastguard Worker
68*d68f33bcSAndroid Build Coastguard Worker    @property
69*d68f33bcSAndroid Build Coastguard Worker    def cmdstr(self):
70*d68f33bcSAndroid Build Coastguard Worker        """Return self.cmd as a nicely formatted string (useful for logs)."""
71*d68f33bcSAndroid Build Coastguard Worker        return rh.shell.cmd_to_str(self.cmd)
72*d68f33bcSAndroid Build Coastguard Worker
73*d68f33bcSAndroid Build Coastguard Worker
74*d68f33bcSAndroid Build Coastguard Workerclass CalledProcessError(subprocess.CalledProcessError):
75*d68f33bcSAndroid Build Coastguard Worker    """Error caught in run() function.
76*d68f33bcSAndroid Build Coastguard Worker
77*d68f33bcSAndroid Build Coastguard Worker    This is akin to subprocess.CalledProcessError.  We do not support |output|,
78*d68f33bcSAndroid Build Coastguard Worker    only |stdout|.
79*d68f33bcSAndroid Build Coastguard Worker
80*d68f33bcSAndroid Build Coastguard Worker    Attributes:
81*d68f33bcSAndroid Build Coastguard Worker      returncode: The exit code of the process.
82*d68f33bcSAndroid Build Coastguard Worker      cmd: The command that triggered this exception.
83*d68f33bcSAndroid Build Coastguard Worker      msg: Short explanation of the error.
84*d68f33bcSAndroid Build Coastguard Worker    """
85*d68f33bcSAndroid Build Coastguard Worker
86*d68f33bcSAndroid Build Coastguard Worker    def __init__(self, returncode, cmd, stdout=None, stderr=None, msg=None):
87*d68f33bcSAndroid Build Coastguard Worker        super().__init__(returncode, cmd, stdout, stderr=stderr)
88*d68f33bcSAndroid Build Coastguard Worker
89*d68f33bcSAndroid Build Coastguard Worker        # The parent class will set |output|, so delete it. If Python ever drops
90*d68f33bcSAndroid Build Coastguard Worker        # this output/stdout compat logic, we can drop this to match.
91*d68f33bcSAndroid Build Coastguard Worker        del self.output
92*d68f33bcSAndroid Build Coastguard Worker        self._stdout = stdout
93*d68f33bcSAndroid Build Coastguard Worker
94*d68f33bcSAndroid Build Coastguard Worker        self.msg = msg
95*d68f33bcSAndroid Build Coastguard Worker
96*d68f33bcSAndroid Build Coastguard Worker    @property
97*d68f33bcSAndroid Build Coastguard Worker    def stdout(self):
98*d68f33bcSAndroid Build Coastguard Worker        """Override parent's usage of .output"""
99*d68f33bcSAndroid Build Coastguard Worker        return self._stdout
100*d68f33bcSAndroid Build Coastguard Worker
101*d68f33bcSAndroid Build Coastguard Worker    @stdout.setter
102*d68f33bcSAndroid Build Coastguard Worker    def stdout(self, value):
103*d68f33bcSAndroid Build Coastguard Worker        """Override parent's usage of .output"""
104*d68f33bcSAndroid Build Coastguard Worker        self._stdout = value
105*d68f33bcSAndroid Build Coastguard Worker
106*d68f33bcSAndroid Build Coastguard Worker    @property
107*d68f33bcSAndroid Build Coastguard Worker    def cmdstr(self):
108*d68f33bcSAndroid Build Coastguard Worker        """Return self.cmd as a well shell-quoted string for debugging."""
109*d68f33bcSAndroid Build Coastguard Worker        return '' if self.cmd is None else rh.shell.cmd_to_str(self.cmd)
110*d68f33bcSAndroid Build Coastguard Worker
111*d68f33bcSAndroid Build Coastguard Worker    def stringify(self, stdout=True, stderr=True):
112*d68f33bcSAndroid Build Coastguard Worker        """Custom method for controlling what is included in stringifying this.
113*d68f33bcSAndroid Build Coastguard Worker
114*d68f33bcSAndroid Build Coastguard Worker        Args:
115*d68f33bcSAndroid Build Coastguard Worker          stdout: Whether to include captured stdout in the return value.
116*d68f33bcSAndroid Build Coastguard Worker          stderr: Whether to include captured stderr in the return value.
117*d68f33bcSAndroid Build Coastguard Worker
118*d68f33bcSAndroid Build Coastguard Worker        Returns:
119*d68f33bcSAndroid Build Coastguard Worker          A summary string for this result.
120*d68f33bcSAndroid Build Coastguard Worker        """
121*d68f33bcSAndroid Build Coastguard Worker        items = [
122*d68f33bcSAndroid Build Coastguard Worker            f'return code: {self.returncode}; command: {self.cmdstr}',
123*d68f33bcSAndroid Build Coastguard Worker        ]
124*d68f33bcSAndroid Build Coastguard Worker        if stderr and self.stderr:
125*d68f33bcSAndroid Build Coastguard Worker            items.append(self.stderr)
126*d68f33bcSAndroid Build Coastguard Worker        if stdout and self.stdout:
127*d68f33bcSAndroid Build Coastguard Worker            items.append(self.stdout)
128*d68f33bcSAndroid Build Coastguard Worker        if self.msg:
129*d68f33bcSAndroid Build Coastguard Worker            items.append(self.msg)
130*d68f33bcSAndroid Build Coastguard Worker        return '\n'.join(items)
131*d68f33bcSAndroid Build Coastguard Worker
132*d68f33bcSAndroid Build Coastguard Worker    def __str__(self):
133*d68f33bcSAndroid Build Coastguard Worker        return self.stringify()
134*d68f33bcSAndroid Build Coastguard Worker
135*d68f33bcSAndroid Build Coastguard Worker
136*d68f33bcSAndroid Build Coastguard Workerclass TerminateCalledProcessError(CalledProcessError):
137*d68f33bcSAndroid Build Coastguard Worker    """We were signaled to shutdown while running a command.
138*d68f33bcSAndroid Build Coastguard Worker
139*d68f33bcSAndroid Build Coastguard Worker    Client code shouldn't generally know, nor care about this class.  It's
140*d68f33bcSAndroid Build Coastguard Worker    used internally to suppress retry attempts when we're signaled to die.
141*d68f33bcSAndroid Build Coastguard Worker    """
142*d68f33bcSAndroid Build Coastguard Worker
143*d68f33bcSAndroid Build Coastguard Worker
144*d68f33bcSAndroid Build Coastguard Workerdef _kill_child_process(proc, int_timeout, kill_timeout, cmd, original_handler,
145*d68f33bcSAndroid Build Coastguard Worker                        signum, frame):
146*d68f33bcSAndroid Build Coastguard Worker    """Used as a signal handler by RunCommand.
147*d68f33bcSAndroid Build Coastguard Worker
148*d68f33bcSAndroid Build Coastguard Worker    This is internal to Runcommand.  No other code should use this.
149*d68f33bcSAndroid Build Coastguard Worker    """
150*d68f33bcSAndroid Build Coastguard Worker    if signum:
151*d68f33bcSAndroid Build Coastguard Worker        # If we've been invoked because of a signal, ignore delivery of that
152*d68f33bcSAndroid Build Coastguard Worker        # signal from this point forward.  The invoking context of this func
153*d68f33bcSAndroid Build Coastguard Worker        # restores signal delivery to what it was prior; we suppress future
154*d68f33bcSAndroid Build Coastguard Worker        # delivery till then since this code handles SIGINT/SIGTERM fully
155*d68f33bcSAndroid Build Coastguard Worker        # including delivering the signal to the original handler on the way
156*d68f33bcSAndroid Build Coastguard Worker        # out.
157*d68f33bcSAndroid Build Coastguard Worker        signal.signal(signum, signal.SIG_IGN)
158*d68f33bcSAndroid Build Coastguard Worker
159*d68f33bcSAndroid Build Coastguard Worker    # Do not trust Popen's returncode alone; we can be invoked from contexts
160*d68f33bcSAndroid Build Coastguard Worker    # where the Popen instance was created, but no process was generated.
161*d68f33bcSAndroid Build Coastguard Worker    if proc.returncode is None and proc.pid is not None:
162*d68f33bcSAndroid Build Coastguard Worker        try:
163*d68f33bcSAndroid Build Coastguard Worker            while proc.poll_lock_breaker() is None and int_timeout >= 0:
164*d68f33bcSAndroid Build Coastguard Worker                time.sleep(0.1)
165*d68f33bcSAndroid Build Coastguard Worker                int_timeout -= 0.1
166*d68f33bcSAndroid Build Coastguard Worker
167*d68f33bcSAndroid Build Coastguard Worker            proc.terminate()
168*d68f33bcSAndroid Build Coastguard Worker            while proc.poll_lock_breaker() is None and kill_timeout >= 0:
169*d68f33bcSAndroid Build Coastguard Worker                time.sleep(0.1)
170*d68f33bcSAndroid Build Coastguard Worker                kill_timeout -= 0.1
171*d68f33bcSAndroid Build Coastguard Worker
172*d68f33bcSAndroid Build Coastguard Worker            if proc.poll_lock_breaker() is None:
173*d68f33bcSAndroid Build Coastguard Worker                # Still doesn't want to die.  Too bad, so sad, time to die.
174*d68f33bcSAndroid Build Coastguard Worker                proc.kill()
175*d68f33bcSAndroid Build Coastguard Worker        except EnvironmentError as e:
176*d68f33bcSAndroid Build Coastguard Worker            print(f'Ignoring unhandled exception in _kill_child_process: {e}',
177*d68f33bcSAndroid Build Coastguard Worker                  file=sys.stderr)
178*d68f33bcSAndroid Build Coastguard Worker
179*d68f33bcSAndroid Build Coastguard Worker        # Ensure our child process has been reaped, but don't wait forever.
180*d68f33bcSAndroid Build Coastguard Worker        proc.wait_lock_breaker(timeout=60)
181*d68f33bcSAndroid Build Coastguard Worker
182*d68f33bcSAndroid Build Coastguard Worker    if not rh.signals.relay_signal(original_handler, signum, frame):
183*d68f33bcSAndroid Build Coastguard Worker        # Mock up our own, matching exit code for signaling.
184*d68f33bcSAndroid Build Coastguard Worker        raise TerminateCalledProcessError(
185*d68f33bcSAndroid Build Coastguard Worker            signum << 8, cmd, msg=f'Received signal {signum}')
186*d68f33bcSAndroid Build Coastguard Worker
187*d68f33bcSAndroid Build Coastguard Worker
188*d68f33bcSAndroid Build Coastguard Workerclass _Popen(subprocess.Popen):
189*d68f33bcSAndroid Build Coastguard Worker    """subprocess.Popen derivative customized for our usage.
190*d68f33bcSAndroid Build Coastguard Worker
191*d68f33bcSAndroid Build Coastguard Worker    Specifically, we fix terminate/send_signal/kill to work if the child process
192*d68f33bcSAndroid Build Coastguard Worker    was a setuid binary; on vanilla kernels, the parent can wax the child
193*d68f33bcSAndroid Build Coastguard Worker    regardless, on goobuntu this apparently isn't allowed, thus we fall back
194*d68f33bcSAndroid Build Coastguard Worker    to the sudo machinery we have.
195*d68f33bcSAndroid Build Coastguard Worker
196*d68f33bcSAndroid Build Coastguard Worker    While we're overriding send_signal, we also suppress ESRCH being raised
197*d68f33bcSAndroid Build Coastguard Worker    if the process has exited, and suppress signaling all together if the
198*d68f33bcSAndroid Build Coastguard Worker    process has knowingly been waitpid'd already.
199*d68f33bcSAndroid Build Coastguard Worker    """
200*d68f33bcSAndroid Build Coastguard Worker
201*d68f33bcSAndroid Build Coastguard Worker    # pylint: disable=arguments-differ,arguments-renamed
202*d68f33bcSAndroid Build Coastguard Worker    def send_signal(self, signum):
203*d68f33bcSAndroid Build Coastguard Worker        if self.returncode is not None:
204*d68f33bcSAndroid Build Coastguard Worker            # The original implementation in Popen allows signaling whatever
205*d68f33bcSAndroid Build Coastguard Worker            # process now occupies this pid, even if the Popen object had
206*d68f33bcSAndroid Build Coastguard Worker            # waitpid'd.  Since we can escalate to sudo kill, we do not want
207*d68f33bcSAndroid Build Coastguard Worker            # to allow that.  Fixing this addresses that angle, and makes the
208*d68f33bcSAndroid Build Coastguard Worker            # API less sucky in the process.
209*d68f33bcSAndroid Build Coastguard Worker            return
210*d68f33bcSAndroid Build Coastguard Worker
211*d68f33bcSAndroid Build Coastguard Worker        try:
212*d68f33bcSAndroid Build Coastguard Worker            os.kill(self.pid, signum)
213*d68f33bcSAndroid Build Coastguard Worker        except EnvironmentError as e:
214*d68f33bcSAndroid Build Coastguard Worker            if e.errno == errno.ESRCH:
215*d68f33bcSAndroid Build Coastguard Worker                # Since we know the process is dead, reap it now.
216*d68f33bcSAndroid Build Coastguard Worker                # Normally Popen would throw this error- we suppress it since
217*d68f33bcSAndroid Build Coastguard Worker                # frankly that's a misfeature and we're already overriding
218*d68f33bcSAndroid Build Coastguard Worker                # this method.
219*d68f33bcSAndroid Build Coastguard Worker                self.poll()
220*d68f33bcSAndroid Build Coastguard Worker            else:
221*d68f33bcSAndroid Build Coastguard Worker                raise
222*d68f33bcSAndroid Build Coastguard Worker
223*d68f33bcSAndroid Build Coastguard Worker    def _lock_breaker(self, func, *args, **kwargs):
224*d68f33bcSAndroid Build Coastguard Worker        """Helper to manage the waitpid lock.
225*d68f33bcSAndroid Build Coastguard Worker
226*d68f33bcSAndroid Build Coastguard Worker        Workaround https://bugs.python.org/issue25960.
227*d68f33bcSAndroid Build Coastguard Worker        """
228*d68f33bcSAndroid Build Coastguard Worker        # If the lock doesn't exist, or is not locked, call the func directly.
229*d68f33bcSAndroid Build Coastguard Worker        lock = getattr(self, '_waitpid_lock', None)
230*d68f33bcSAndroid Build Coastguard Worker        if lock is not None and lock.locked():
231*d68f33bcSAndroid Build Coastguard Worker            try:
232*d68f33bcSAndroid Build Coastguard Worker                lock.release()
233*d68f33bcSAndroid Build Coastguard Worker                return func(*args, **kwargs)
234*d68f33bcSAndroid Build Coastguard Worker            finally:
235*d68f33bcSAndroid Build Coastguard Worker                if not lock.locked():
236*d68f33bcSAndroid Build Coastguard Worker                    lock.acquire()
237*d68f33bcSAndroid Build Coastguard Worker        else:
238*d68f33bcSAndroid Build Coastguard Worker            return func(*args, **kwargs)
239*d68f33bcSAndroid Build Coastguard Worker
240*d68f33bcSAndroid Build Coastguard Worker    def poll_lock_breaker(self, *args, **kwargs):
241*d68f33bcSAndroid Build Coastguard Worker        """Wrapper around poll() to break locks if needed."""
242*d68f33bcSAndroid Build Coastguard Worker        return self._lock_breaker(self.poll, *args, **kwargs)
243*d68f33bcSAndroid Build Coastguard Worker
244*d68f33bcSAndroid Build Coastguard Worker    def wait_lock_breaker(self, *args, **kwargs):
245*d68f33bcSAndroid Build Coastguard Worker        """Wrapper around wait() to break locks if needed."""
246*d68f33bcSAndroid Build Coastguard Worker        return self._lock_breaker(self.wait, *args, **kwargs)
247*d68f33bcSAndroid Build Coastguard Worker
248*d68f33bcSAndroid Build Coastguard Worker
249*d68f33bcSAndroid Build Coastguard Worker# We use the keyword arg |input| which trips up pylint checks.
250*d68f33bcSAndroid Build Coastguard Worker# pylint: disable=redefined-builtin
251*d68f33bcSAndroid Build Coastguard Workerdef run(cmd, redirect_stdout=False, redirect_stderr=False, cwd=None, input=None,
252*d68f33bcSAndroid Build Coastguard Worker        shell=False, env=None, extra_env=None, combine_stdout_stderr=False,
253*d68f33bcSAndroid Build Coastguard Worker        check=True, int_timeout=1, kill_timeout=1, capture_output=False):
254*d68f33bcSAndroid Build Coastguard Worker    """Runs a command.
255*d68f33bcSAndroid Build Coastguard Worker
256*d68f33bcSAndroid Build Coastguard Worker    Args:
257*d68f33bcSAndroid Build Coastguard Worker      cmd: cmd to run.  Should be input to subprocess.Popen.  If a string, shell
258*d68f33bcSAndroid Build Coastguard Worker          must be true.  Otherwise the command must be an array of arguments,
259*d68f33bcSAndroid Build Coastguard Worker          and shell must be false.
260*d68f33bcSAndroid Build Coastguard Worker      redirect_stdout: Returns the stdout.
261*d68f33bcSAndroid Build Coastguard Worker      redirect_stderr: Holds stderr output until input is communicated.
262*d68f33bcSAndroid Build Coastguard Worker      cwd: The working directory to run this cmd.
263*d68f33bcSAndroid Build Coastguard Worker      input: The data to pipe into this command through stdin.  If a file object
264*d68f33bcSAndroid Build Coastguard Worker          or file descriptor, stdin will be connected directly to that.
265*d68f33bcSAndroid Build Coastguard Worker      shell: Controls whether we add a shell as a command interpreter.  See cmd
266*d68f33bcSAndroid Build Coastguard Worker          since it has to agree as to the type.
267*d68f33bcSAndroid Build Coastguard Worker      env: If non-None, this is the environment for the new process.
268*d68f33bcSAndroid Build Coastguard Worker      extra_env: If set, this is added to the environment for the new process.
269*d68f33bcSAndroid Build Coastguard Worker          This dictionary is not used to clear any entries though.
270*d68f33bcSAndroid Build Coastguard Worker      combine_stdout_stderr: Combines stdout and stderr streams into stdout.
271*d68f33bcSAndroid Build Coastguard Worker      check: Whether to raise an exception when command returns a non-zero exit
272*d68f33bcSAndroid Build Coastguard Worker          code, or return the CompletedProcess object containing the exit code.
273*d68f33bcSAndroid Build Coastguard Worker          Note: will still raise an exception if the cmd file does not exist.
274*d68f33bcSAndroid Build Coastguard Worker      int_timeout: If we're interrupted, how long (in seconds) should we give
275*d68f33bcSAndroid Build Coastguard Worker          the invoked process to clean up before we send a SIGTERM.
276*d68f33bcSAndroid Build Coastguard Worker      kill_timeout: If we're interrupted, how long (in seconds) should we give
277*d68f33bcSAndroid Build Coastguard Worker          the invoked process to shutdown from a SIGTERM before we SIGKILL it.
278*d68f33bcSAndroid Build Coastguard Worker      capture_output: Set |redirect_stdout| and |redirect_stderr| to True.
279*d68f33bcSAndroid Build Coastguard Worker
280*d68f33bcSAndroid Build Coastguard Worker    Returns:
281*d68f33bcSAndroid Build Coastguard Worker      A CompletedProcess object.
282*d68f33bcSAndroid Build Coastguard Worker
283*d68f33bcSAndroid Build Coastguard Worker    Raises:
284*d68f33bcSAndroid Build Coastguard Worker      CalledProcessError: Raises exception on error.
285*d68f33bcSAndroid Build Coastguard Worker    """
286*d68f33bcSAndroid Build Coastguard Worker    if capture_output:
287*d68f33bcSAndroid Build Coastguard Worker        redirect_stdout, redirect_stderr = True, True
288*d68f33bcSAndroid Build Coastguard Worker
289*d68f33bcSAndroid Build Coastguard Worker    # Set default for variables.
290*d68f33bcSAndroid Build Coastguard Worker    popen_stdout = None
291*d68f33bcSAndroid Build Coastguard Worker    popen_stderr = None
292*d68f33bcSAndroid Build Coastguard Worker    stdin = None
293*d68f33bcSAndroid Build Coastguard Worker    result = CompletedProcess()
294*d68f33bcSAndroid Build Coastguard Worker
295*d68f33bcSAndroid Build Coastguard Worker    # Force the timeout to float; in the process, if it's not convertible,
296*d68f33bcSAndroid Build Coastguard Worker    # a self-explanatory exception will be thrown.
297*d68f33bcSAndroid Build Coastguard Worker    kill_timeout = float(kill_timeout)
298*d68f33bcSAndroid Build Coastguard Worker
299*d68f33bcSAndroid Build Coastguard Worker    def _get_tempfile():
300*d68f33bcSAndroid Build Coastguard Worker        try:
301*d68f33bcSAndroid Build Coastguard Worker            return tempfile.TemporaryFile(buffering=0)
302*d68f33bcSAndroid Build Coastguard Worker        except EnvironmentError as e:
303*d68f33bcSAndroid Build Coastguard Worker            if e.errno != errno.ENOENT:
304*d68f33bcSAndroid Build Coastguard Worker                raise
305*d68f33bcSAndroid Build Coastguard Worker            # This can occur if we were pointed at a specific location for our
306*d68f33bcSAndroid Build Coastguard Worker            # TMP, but that location has since been deleted.  Suppress that
307*d68f33bcSAndroid Build Coastguard Worker            # issue in this particular case since our usage gurantees deletion,
308*d68f33bcSAndroid Build Coastguard Worker            # and since this is primarily triggered during hard cgroups
309*d68f33bcSAndroid Build Coastguard Worker            # shutdown.
310*d68f33bcSAndroid Build Coastguard Worker            return tempfile.TemporaryFile(dir='/tmp', buffering=0)
311*d68f33bcSAndroid Build Coastguard Worker
312*d68f33bcSAndroid Build Coastguard Worker    # Modify defaults based on parameters.
313*d68f33bcSAndroid Build Coastguard Worker    # Note that tempfiles must be unbuffered else attempts to read
314*d68f33bcSAndroid Build Coastguard Worker    # what a separate process did to that file can result in a bad
315*d68f33bcSAndroid Build Coastguard Worker    # view of the file.
316*d68f33bcSAndroid Build Coastguard Worker    # The Popen API accepts either an int or a file handle for stdout/stderr.
317*d68f33bcSAndroid Build Coastguard Worker    # pylint: disable=redefined-variable-type
318*d68f33bcSAndroid Build Coastguard Worker    if redirect_stdout:
319*d68f33bcSAndroid Build Coastguard Worker        popen_stdout = _get_tempfile()
320*d68f33bcSAndroid Build Coastguard Worker
321*d68f33bcSAndroid Build Coastguard Worker    if combine_stdout_stderr:
322*d68f33bcSAndroid Build Coastguard Worker        popen_stderr = subprocess.STDOUT
323*d68f33bcSAndroid Build Coastguard Worker    elif redirect_stderr:
324*d68f33bcSAndroid Build Coastguard Worker        popen_stderr = _get_tempfile()
325*d68f33bcSAndroid Build Coastguard Worker    # pylint: enable=redefined-variable-type
326*d68f33bcSAndroid Build Coastguard Worker
327*d68f33bcSAndroid Build Coastguard Worker    # If subprocesses have direct access to stdout or stderr, they can bypass
328*d68f33bcSAndroid Build Coastguard Worker    # our buffers, so we need to flush to ensure that output is not interleaved.
329*d68f33bcSAndroid Build Coastguard Worker    if popen_stdout is None or popen_stderr is None:
330*d68f33bcSAndroid Build Coastguard Worker        sys.stdout.flush()
331*d68f33bcSAndroid Build Coastguard Worker        sys.stderr.flush()
332*d68f33bcSAndroid Build Coastguard Worker
333*d68f33bcSAndroid Build Coastguard Worker    # If input is a string, we'll create a pipe and send it through that.
334*d68f33bcSAndroid Build Coastguard Worker    # Otherwise we assume it's a file object that can be read from directly.
335*d68f33bcSAndroid Build Coastguard Worker    if isinstance(input, str):
336*d68f33bcSAndroid Build Coastguard Worker        stdin = subprocess.PIPE
337*d68f33bcSAndroid Build Coastguard Worker        input = input.encode('utf-8')
338*d68f33bcSAndroid Build Coastguard Worker    elif input is not None:
339*d68f33bcSAndroid Build Coastguard Worker        stdin = input
340*d68f33bcSAndroid Build Coastguard Worker        input = None
341*d68f33bcSAndroid Build Coastguard Worker
342*d68f33bcSAndroid Build Coastguard Worker    if isinstance(cmd, str):
343*d68f33bcSAndroid Build Coastguard Worker        if not shell:
344*d68f33bcSAndroid Build Coastguard Worker            raise Exception('Cannot run a string command without a shell')
345*d68f33bcSAndroid Build Coastguard Worker        cmd = ['/bin/bash', '-c', cmd]
346*d68f33bcSAndroid Build Coastguard Worker        shell = False
347*d68f33bcSAndroid Build Coastguard Worker    elif shell:
348*d68f33bcSAndroid Build Coastguard Worker        raise Exception('Cannot run an array command with a shell')
349*d68f33bcSAndroid Build Coastguard Worker
350*d68f33bcSAndroid Build Coastguard Worker    # If we are using enter_chroot we need to use enterchroot pass env through
351*d68f33bcSAndroid Build Coastguard Worker    # to the final command.
352*d68f33bcSAndroid Build Coastguard Worker    env = env.copy() if env is not None else os.environ.copy()
353*d68f33bcSAndroid Build Coastguard Worker    env.update(extra_env if extra_env else {})
354*d68f33bcSAndroid Build Coastguard Worker
355*d68f33bcSAndroid Build Coastguard Worker    def ensure_text(s):
356*d68f33bcSAndroid Build Coastguard Worker        """Make sure |s| is a string if it's bytes."""
357*d68f33bcSAndroid Build Coastguard Worker        if isinstance(s, bytes):
358*d68f33bcSAndroid Build Coastguard Worker            s = s.decode('utf-8', 'replace')
359*d68f33bcSAndroid Build Coastguard Worker        return s
360*d68f33bcSAndroid Build Coastguard Worker
361*d68f33bcSAndroid Build Coastguard Worker    result.args = cmd
362*d68f33bcSAndroid Build Coastguard Worker
363*d68f33bcSAndroid Build Coastguard Worker    proc = None
364*d68f33bcSAndroid Build Coastguard Worker    try:
365*d68f33bcSAndroid Build Coastguard Worker        proc = _Popen(cmd, cwd=cwd, stdin=stdin, stdout=popen_stdout,
366*d68f33bcSAndroid Build Coastguard Worker                      stderr=popen_stderr, shell=False, env=env,
367*d68f33bcSAndroid Build Coastguard Worker                      close_fds=True)
368*d68f33bcSAndroid Build Coastguard Worker
369*d68f33bcSAndroid Build Coastguard Worker        old_sigint = signal.getsignal(signal.SIGINT)
370*d68f33bcSAndroid Build Coastguard Worker        handler = functools.partial(_kill_child_process, proc, int_timeout,
371*d68f33bcSAndroid Build Coastguard Worker                                    kill_timeout, cmd, old_sigint)
372*d68f33bcSAndroid Build Coastguard Worker        # We have to ignore ValueError in case we're run from a thread.
373*d68f33bcSAndroid Build Coastguard Worker        try:
374*d68f33bcSAndroid Build Coastguard Worker            signal.signal(signal.SIGINT, handler)
375*d68f33bcSAndroid Build Coastguard Worker        except ValueError:
376*d68f33bcSAndroid Build Coastguard Worker            old_sigint = None
377*d68f33bcSAndroid Build Coastguard Worker
378*d68f33bcSAndroid Build Coastguard Worker        old_sigterm = signal.getsignal(signal.SIGTERM)
379*d68f33bcSAndroid Build Coastguard Worker        handler = functools.partial(_kill_child_process, proc, int_timeout,
380*d68f33bcSAndroid Build Coastguard Worker                                    kill_timeout, cmd, old_sigterm)
381*d68f33bcSAndroid Build Coastguard Worker        try:
382*d68f33bcSAndroid Build Coastguard Worker            signal.signal(signal.SIGTERM, handler)
383*d68f33bcSAndroid Build Coastguard Worker        except ValueError:
384*d68f33bcSAndroid Build Coastguard Worker            old_sigterm = None
385*d68f33bcSAndroid Build Coastguard Worker
386*d68f33bcSAndroid Build Coastguard Worker        try:
387*d68f33bcSAndroid Build Coastguard Worker            (result.stdout, result.stderr) = proc.communicate(input)
388*d68f33bcSAndroid Build Coastguard Worker        finally:
389*d68f33bcSAndroid Build Coastguard Worker            if old_sigint is not None:
390*d68f33bcSAndroid Build Coastguard Worker                signal.signal(signal.SIGINT, old_sigint)
391*d68f33bcSAndroid Build Coastguard Worker            if old_sigterm is not None:
392*d68f33bcSAndroid Build Coastguard Worker                signal.signal(signal.SIGTERM, old_sigterm)
393*d68f33bcSAndroid Build Coastguard Worker
394*d68f33bcSAndroid Build Coastguard Worker            if popen_stdout:
395*d68f33bcSAndroid Build Coastguard Worker                # The linter is confused by how stdout is a file & an int.
396*d68f33bcSAndroid Build Coastguard Worker                # pylint: disable=maybe-no-member,no-member
397*d68f33bcSAndroid Build Coastguard Worker                popen_stdout.seek(0)
398*d68f33bcSAndroid Build Coastguard Worker                result.stdout = popen_stdout.read()
399*d68f33bcSAndroid Build Coastguard Worker                popen_stdout.close()
400*d68f33bcSAndroid Build Coastguard Worker
401*d68f33bcSAndroid Build Coastguard Worker            if popen_stderr and popen_stderr != subprocess.STDOUT:
402*d68f33bcSAndroid Build Coastguard Worker                # The linter is confused by how stderr is a file & an int.
403*d68f33bcSAndroid Build Coastguard Worker                # pylint: disable=maybe-no-member,no-member
404*d68f33bcSAndroid Build Coastguard Worker                popen_stderr.seek(0)
405*d68f33bcSAndroid Build Coastguard Worker                result.stderr = popen_stderr.read()
406*d68f33bcSAndroid Build Coastguard Worker                popen_stderr.close()
407*d68f33bcSAndroid Build Coastguard Worker
408*d68f33bcSAndroid Build Coastguard Worker        result.returncode = proc.returncode
409*d68f33bcSAndroid Build Coastguard Worker
410*d68f33bcSAndroid Build Coastguard Worker        if check and proc.returncode:
411*d68f33bcSAndroid Build Coastguard Worker            msg = f'cwd={cwd}'
412*d68f33bcSAndroid Build Coastguard Worker            if extra_env:
413*d68f33bcSAndroid Build Coastguard Worker                msg += f', extra env={extra_env}'
414*d68f33bcSAndroid Build Coastguard Worker            raise CalledProcessError(
415*d68f33bcSAndroid Build Coastguard Worker                result.returncode, result.cmd, msg=msg,
416*d68f33bcSAndroid Build Coastguard Worker                stdout=ensure_text(result.stdout),
417*d68f33bcSAndroid Build Coastguard Worker                stderr=ensure_text(result.stderr))
418*d68f33bcSAndroid Build Coastguard Worker    except OSError as e:
419*d68f33bcSAndroid Build Coastguard Worker        # Avoid leaking tempfiles.
420*d68f33bcSAndroid Build Coastguard Worker        if popen_stdout is not None and not isinstance(popen_stdout, int):
421*d68f33bcSAndroid Build Coastguard Worker            popen_stdout.close()
422*d68f33bcSAndroid Build Coastguard Worker        if popen_stderr is not None and not isinstance(popen_stderr, int):
423*d68f33bcSAndroid Build Coastguard Worker            popen_stderr.close()
424*d68f33bcSAndroid Build Coastguard Worker
425*d68f33bcSAndroid Build Coastguard Worker        estr = str(e)
426*d68f33bcSAndroid Build Coastguard Worker        if e.errno == errno.EACCES:
427*d68f33bcSAndroid Build Coastguard Worker            estr += '; does the program need `chmod a+x`?'
428*d68f33bcSAndroid Build Coastguard Worker        if not check:
429*d68f33bcSAndroid Build Coastguard Worker            result = CompletedProcess(args=cmd, returncode=255)
430*d68f33bcSAndroid Build Coastguard Worker            if combine_stdout_stderr:
431*d68f33bcSAndroid Build Coastguard Worker                result.stdout = estr
432*d68f33bcSAndroid Build Coastguard Worker            else:
433*d68f33bcSAndroid Build Coastguard Worker                result.stderr = estr
434*d68f33bcSAndroid Build Coastguard Worker        else:
435*d68f33bcSAndroid Build Coastguard Worker            raise CalledProcessError(
436*d68f33bcSAndroid Build Coastguard Worker                result.returncode, result.cmd, msg=estr,
437*d68f33bcSAndroid Build Coastguard Worker                stdout=ensure_text(result.stdout),
438*d68f33bcSAndroid Build Coastguard Worker                stderr=ensure_text(result.stderr)) from e
439*d68f33bcSAndroid Build Coastguard Worker    finally:
440*d68f33bcSAndroid Build Coastguard Worker        if proc is not None:
441*d68f33bcSAndroid Build Coastguard Worker            # Ensure the process is dead.
442*d68f33bcSAndroid Build Coastguard Worker            # Some pylint3 versions are confused here.
443*d68f33bcSAndroid Build Coastguard Worker            # pylint: disable=too-many-function-args
444*d68f33bcSAndroid Build Coastguard Worker            _kill_child_process(proc, int_timeout, kill_timeout, cmd, None,
445*d68f33bcSAndroid Build Coastguard Worker                                None, None)
446*d68f33bcSAndroid Build Coastguard Worker
447*d68f33bcSAndroid Build Coastguard Worker    # Make sure output is returned as a string rather than bytes.
448*d68f33bcSAndroid Build Coastguard Worker    result.stdout = ensure_text(result.stdout)
449*d68f33bcSAndroid Build Coastguard Worker    result.stderr = ensure_text(result.stderr)
450*d68f33bcSAndroid Build Coastguard Worker
451*d68f33bcSAndroid Build Coastguard Worker    return result
452*d68f33bcSAndroid Build Coastguard Worker# pylint: enable=redefined-builtin
453