1# Copyright 2017 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Command-related utilities."""
16
17from collections import namedtuple
18import logging
19import os
20import subprocess
21
22
23CommandResult = namedtuple('CommandResult', 'returncode stdoutdata, stderrdata')
24PIPE = subprocess.PIPE
25
26
27def run_command(command, read_stdout=False, read_stderr=False,
28                log_stdout=False, log_stderr=False,
29                raise_on_error=True, sudo=False, **kwargs):
30  """Runs a command and returns the results.
31
32  Args:
33    command: A sequence of command arguments or else a single string.
34    read_stdout: If True, includes stdout data in the returned tuple.
35      Otherwise includes None in the returned tuple.
36    read_stderr: If True, includes stderr data in the returned tuple.
37      Otherwise includes None in the returned tuple.
38    log_stdout: If True, logs stdout data.
39    log_stderr: If True, logs stderro data.
40    raise_on_error: If True, raise exception if return code is nonzero.
41    sudo: Prepends 'sudo' to command if user is not root.
42    **kwargs: the keyword arguments passed to subprocess.Popen().
43
44  Returns:
45    A namedtuple CommandResult(returncode, stdoutdata, stderrdata).
46    The latter two fields will be set only when read_stdout/read_stderr
47    is True, respectively. Otherwise, they will be None.
48
49  Raises:
50    OSError: Not such a command to execute, raised by subprocess.Popen().
51    subprocess.CalledProcessError: The return code of the command is nonzero.
52  """
53  if sudo and os.getuid() != 0:
54    if kwargs.pop('shell', False):
55      command = ['sudo', 'sh', '-c', command]
56    else:
57      command = ['sudo'] + command
58
59  if kwargs.get('shell'):
60    command_in_log = command
61  else:
62    command_in_log = ' '.join(arg for arg in command)
63
64  if read_stdout or log_stdout:
65    assert kwargs.get('stdout') in [None, PIPE]
66    kwargs['stdout'] = PIPE
67  if read_stderr or log_stderr:
68    assert kwargs.get('stderr') in [None, PIPE]
69    kwargs['stderr'] = PIPE
70
71  need_communicate = (read_stdout or read_stderr or
72                      log_stdout or log_stderr)
73  proc = subprocess.Popen(command, **kwargs)
74  if need_communicate:
75    stdout, stderr = proc.communicate()
76  else:
77    proc.wait()  # no need to communicate; just wait.
78
79  log_level = logging.ERROR if proc.returncode != 0 else logging.INFO
80  logging.log(log_level, 'Executed command: %r (ret: %d)',
81              command_in_log, proc.returncode)
82  if log_stdout:
83    logging.log(log_level, '  stdout: %r', stdout)
84  if log_stderr:
85    logging.log(log_level, '  stderr: %r', stderr)
86
87  if proc.returncode != 0 and raise_on_error:
88    raise subprocess.CalledProcessError(proc.returncode, command)
89
90  return CommandResult(proc.returncode,
91                       stdout if read_stdout else None,
92                       stderr if read_stderr else None)
93