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