1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2011 The Chromium OS Authors. 3# 4 5import os 6 7from patman import cros_subprocess 8 9"""Shell command ease-ups for Python.""" 10 11class CommandResult: 12 """A class which captures the result of executing a command. 13 14 Members: 15 stdout: stdout obtained from command, as a string 16 stderr: stderr obtained from command, as a string 17 return_code: Return code from command 18 exception: Exception received, or None if all ok 19 """ 20 def __init__(self): 21 self.stdout = None 22 self.stderr = None 23 self.combined = None 24 self.return_code = None 25 self.exception = None 26 27 def __init__(self, stdout='', stderr='', combined='', return_code=0, 28 exception=None): 29 self.stdout = stdout 30 self.stderr = stderr 31 self.combined = combined 32 self.return_code = return_code 33 self.exception = exception 34 35 def ToOutput(self, binary): 36 if not binary: 37 self.stdout = self.stdout.decode('utf-8') 38 self.stderr = self.stderr.decode('utf-8') 39 self.combined = self.combined.decode('utf-8') 40 return self 41 42 43# This permits interception of RunPipe for test purposes. If it is set to 44# a function, then that function is called with the pipe list being 45# executed. Otherwise, it is assumed to be a CommandResult object, and is 46# returned as the result for every RunPipe() call. 47# When this value is None, commands are executed as normal. 48test_result = None 49 50def RunPipe(pipe_list, infile=None, outfile=None, 51 capture=False, capture_stderr=False, oneline=False, 52 raise_on_error=True, cwd=None, binary=False, 53 output_func=None, **kwargs): 54 """ 55 Perform a command pipeline, with optional input/output filenames. 56 57 Args: 58 pipe_list: List of command lines to execute. Each command line is 59 piped into the next, and is itself a list of strings. For 60 example [ ['ls', '.git'] ['wc'] ] will pipe the output of 61 'ls .git' into 'wc'. 62 infile: File to provide stdin to the pipeline 63 outfile: File to store stdout 64 capture: True to capture output 65 capture_stderr: True to capture stderr 66 oneline: True to strip newline chars from output 67 output_func: Output function to call with each output fragment 68 (if it returns True the function terminates) 69 kwargs: Additional keyword arguments to cros_subprocess.Popen() 70 Returns: 71 CommandResult object 72 """ 73 if test_result: 74 if hasattr(test_result, '__call__'): 75 result = test_result(pipe_list=pipe_list) 76 if result: 77 return result 78 else: 79 return test_result 80 # No result: fall through to normal processing 81 result = CommandResult(b'', b'', b'') 82 last_pipe = None 83 pipeline = list(pipe_list) 84 user_pipestr = '|'.join([' '.join(pipe) for pipe in pipe_list]) 85 kwargs['stdout'] = None 86 kwargs['stderr'] = None 87 while pipeline: 88 cmd = pipeline.pop(0) 89 if last_pipe is not None: 90 kwargs['stdin'] = last_pipe.stdout 91 elif infile: 92 kwargs['stdin'] = open(infile, 'rb') 93 if pipeline or capture: 94 kwargs['stdout'] = cros_subprocess.PIPE 95 elif outfile: 96 kwargs['stdout'] = open(outfile, 'wb') 97 if capture_stderr: 98 kwargs['stderr'] = cros_subprocess.PIPE 99 100 try: 101 last_pipe = cros_subprocess.Popen(cmd, cwd=cwd, **kwargs) 102 except Exception as err: 103 result.exception = err 104 if raise_on_error: 105 raise Exception("Error running '%s': %s" % (user_pipestr, str)) 106 result.return_code = 255 107 return result.ToOutput(binary) 108 109 if capture: 110 result.stdout, result.stderr, result.combined = ( 111 last_pipe.CommunicateFilter(output_func)) 112 if result.stdout and oneline: 113 result.output = result.stdout.rstrip(b'\r\n') 114 result.return_code = last_pipe.wait() 115 else: 116 result.return_code = os.waitpid(last_pipe.pid, 0)[1] 117 if raise_on_error and result.return_code: 118 raise Exception("Error running '%s'" % user_pipestr) 119 return result.ToOutput(binary) 120 121def Output(*cmd, **kwargs): 122 kwargs['raise_on_error'] = kwargs.get('raise_on_error', True) 123 return RunPipe([cmd], capture=True, **kwargs).stdout 124 125def OutputOneLine(*cmd, **kwargs): 126 """Run a command and output it as a single-line string 127 128 The command us expected to produce a single line of output 129 130 Returns: 131 String containing output of command 132 """ 133 raise_on_error = kwargs.pop('raise_on_error', True) 134 result = RunPipe([cmd], capture=True, oneline=True, 135 raise_on_error=raise_on_error, **kwargs).stdout.strip() 136 return result 137 138def Run(*cmd, **kwargs): 139 return RunPipe([cmd], **kwargs).stdout 140 141def RunList(cmd): 142 return RunPipe([cmd], capture=True).stdout 143 144def StopAll(): 145 cros_subprocess.stay_alive = False 146