1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# Copyright BitBake Contributors 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun 7*4882a593Smuzhiyunimport logging 8*4882a593Smuzhiyunimport signal 9*4882a593Smuzhiyunimport subprocess 10*4882a593Smuzhiyunimport errno 11*4882a593Smuzhiyunimport select 12*4882a593Smuzhiyunimport bb 13*4882a593Smuzhiyun 14*4882a593Smuzhiyunlogger = logging.getLogger('BitBake.Process') 15*4882a593Smuzhiyun 16*4882a593Smuzhiyundef subprocess_setup(): 17*4882a593Smuzhiyun # Python installs a SIGPIPE handler by default. This is usually not what 18*4882a593Smuzhiyun # non-Python subprocesses expect. 19*4882a593Smuzhiyun signal.signal(signal.SIGPIPE, signal.SIG_DFL) 20*4882a593Smuzhiyun 21*4882a593Smuzhiyunclass CmdError(RuntimeError): 22*4882a593Smuzhiyun def __init__(self, command, msg=None): 23*4882a593Smuzhiyun self.command = command 24*4882a593Smuzhiyun self.msg = msg 25*4882a593Smuzhiyun 26*4882a593Smuzhiyun def __str__(self): 27*4882a593Smuzhiyun if not isinstance(self.command, str): 28*4882a593Smuzhiyun cmd = subprocess.list2cmdline(self.command) 29*4882a593Smuzhiyun else: 30*4882a593Smuzhiyun cmd = self.command 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun msg = "Execution of '%s' failed" % cmd 33*4882a593Smuzhiyun if self.msg: 34*4882a593Smuzhiyun msg += ': %s' % self.msg 35*4882a593Smuzhiyun return msg 36*4882a593Smuzhiyun 37*4882a593Smuzhiyunclass NotFoundError(CmdError): 38*4882a593Smuzhiyun def __str__(self): 39*4882a593Smuzhiyun return CmdError.__str__(self) + ": command not found" 40*4882a593Smuzhiyun 41*4882a593Smuzhiyunclass ExecutionError(CmdError): 42*4882a593Smuzhiyun def __init__(self, command, exitcode, stdout = None, stderr = None): 43*4882a593Smuzhiyun CmdError.__init__(self, command) 44*4882a593Smuzhiyun self.exitcode = exitcode 45*4882a593Smuzhiyun self.stdout = stdout 46*4882a593Smuzhiyun self.stderr = stderr 47*4882a593Smuzhiyun self.extra_message = None 48*4882a593Smuzhiyun 49*4882a593Smuzhiyun def __str__(self): 50*4882a593Smuzhiyun message = "" 51*4882a593Smuzhiyun if self.stderr: 52*4882a593Smuzhiyun message += self.stderr 53*4882a593Smuzhiyun if self.stdout: 54*4882a593Smuzhiyun message += self.stdout 55*4882a593Smuzhiyun if message: 56*4882a593Smuzhiyun message = ":\n" + message 57*4882a593Smuzhiyun return (CmdError.__str__(self) + 58*4882a593Smuzhiyun " with exit code %s" % self.exitcode + message + (self.extra_message or "")) 59*4882a593Smuzhiyun 60*4882a593Smuzhiyunclass Popen(subprocess.Popen): 61*4882a593Smuzhiyun defaults = { 62*4882a593Smuzhiyun "close_fds": True, 63*4882a593Smuzhiyun "preexec_fn": subprocess_setup, 64*4882a593Smuzhiyun "stdout": subprocess.PIPE, 65*4882a593Smuzhiyun "stderr": subprocess.PIPE, 66*4882a593Smuzhiyun "stdin": subprocess.PIPE, 67*4882a593Smuzhiyun "shell": False, 68*4882a593Smuzhiyun } 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 71*4882a593Smuzhiyun options = dict(self.defaults) 72*4882a593Smuzhiyun options.update(kwargs) 73*4882a593Smuzhiyun subprocess.Popen.__init__(self, *args, **options) 74*4882a593Smuzhiyun 75*4882a593Smuzhiyundef _logged_communicate(pipe, log, input, extrafiles): 76*4882a593Smuzhiyun if pipe.stdin: 77*4882a593Smuzhiyun if input is not None: 78*4882a593Smuzhiyun pipe.stdin.write(input) 79*4882a593Smuzhiyun pipe.stdin.close() 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun outdata, errdata = [], [] 82*4882a593Smuzhiyun rin = [] 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun if pipe.stdout is not None: 85*4882a593Smuzhiyun bb.utils.nonblockingfd(pipe.stdout.fileno()) 86*4882a593Smuzhiyun rin.append(pipe.stdout) 87*4882a593Smuzhiyun if pipe.stderr is not None: 88*4882a593Smuzhiyun bb.utils.nonblockingfd(pipe.stderr.fileno()) 89*4882a593Smuzhiyun rin.append(pipe.stderr) 90*4882a593Smuzhiyun for fobj, _ in extrafiles: 91*4882a593Smuzhiyun bb.utils.nonblockingfd(fobj.fileno()) 92*4882a593Smuzhiyun rin.append(fobj) 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun def readextras(selected): 95*4882a593Smuzhiyun for fobj, func in extrafiles: 96*4882a593Smuzhiyun if fobj in selected: 97*4882a593Smuzhiyun try: 98*4882a593Smuzhiyun data = fobj.read() 99*4882a593Smuzhiyun except IOError as err: 100*4882a593Smuzhiyun if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK: 101*4882a593Smuzhiyun data = None 102*4882a593Smuzhiyun if data is not None: 103*4882a593Smuzhiyun func(data) 104*4882a593Smuzhiyun 105*4882a593Smuzhiyun def read_all_pipes(log, rin, outdata, errdata): 106*4882a593Smuzhiyun rlist = rin 107*4882a593Smuzhiyun stdoutbuf = b"" 108*4882a593Smuzhiyun stderrbuf = b"" 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun try: 111*4882a593Smuzhiyun r,w,e = select.select (rlist, [], [], 1) 112*4882a593Smuzhiyun except OSError as e: 113*4882a593Smuzhiyun if e.errno != errno.EINTR: 114*4882a593Smuzhiyun raise 115*4882a593Smuzhiyun 116*4882a593Smuzhiyun readextras(r) 117*4882a593Smuzhiyun 118*4882a593Smuzhiyun if pipe.stdout in r: 119*4882a593Smuzhiyun data = stdoutbuf + pipe.stdout.read() 120*4882a593Smuzhiyun if data is not None and len(data) > 0: 121*4882a593Smuzhiyun try: 122*4882a593Smuzhiyun data = data.decode("utf-8") 123*4882a593Smuzhiyun outdata.append(data) 124*4882a593Smuzhiyun log.write(data) 125*4882a593Smuzhiyun log.flush() 126*4882a593Smuzhiyun stdoutbuf = b"" 127*4882a593Smuzhiyun except UnicodeDecodeError: 128*4882a593Smuzhiyun stdoutbuf = data 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun if pipe.stderr in r: 131*4882a593Smuzhiyun data = stderrbuf + pipe.stderr.read() 132*4882a593Smuzhiyun if data is not None and len(data) > 0: 133*4882a593Smuzhiyun try: 134*4882a593Smuzhiyun data = data.decode("utf-8") 135*4882a593Smuzhiyun errdata.append(data) 136*4882a593Smuzhiyun log.write(data) 137*4882a593Smuzhiyun log.flush() 138*4882a593Smuzhiyun stderrbuf = b"" 139*4882a593Smuzhiyun except UnicodeDecodeError: 140*4882a593Smuzhiyun stderrbuf = data 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun try: 143*4882a593Smuzhiyun # Read all pipes while the process is open 144*4882a593Smuzhiyun while pipe.poll() is None: 145*4882a593Smuzhiyun read_all_pipes(log, rin, outdata, errdata) 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun # Process closed, drain all pipes... 148*4882a593Smuzhiyun read_all_pipes(log, rin, outdata, errdata) 149*4882a593Smuzhiyun finally: 150*4882a593Smuzhiyun log.flush() 151*4882a593Smuzhiyun 152*4882a593Smuzhiyun if pipe.stdout is not None: 153*4882a593Smuzhiyun pipe.stdout.close() 154*4882a593Smuzhiyun if pipe.stderr is not None: 155*4882a593Smuzhiyun pipe.stderr.close() 156*4882a593Smuzhiyun return ''.join(outdata), ''.join(errdata) 157*4882a593Smuzhiyun 158*4882a593Smuzhiyundef run(cmd, input=None, log=None, extrafiles=None, **options): 159*4882a593Smuzhiyun """Convenience function to run a command and return its output, raising an 160*4882a593Smuzhiyun exception when the command fails""" 161*4882a593Smuzhiyun 162*4882a593Smuzhiyun if not extrafiles: 163*4882a593Smuzhiyun extrafiles = [] 164*4882a593Smuzhiyun 165*4882a593Smuzhiyun if isinstance(cmd, str) and not "shell" in options: 166*4882a593Smuzhiyun options["shell"] = True 167*4882a593Smuzhiyun 168*4882a593Smuzhiyun try: 169*4882a593Smuzhiyun pipe = Popen(cmd, **options) 170*4882a593Smuzhiyun except OSError as exc: 171*4882a593Smuzhiyun if exc.errno == 2: 172*4882a593Smuzhiyun raise NotFoundError(cmd) 173*4882a593Smuzhiyun else: 174*4882a593Smuzhiyun raise CmdError(cmd, exc) 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun if log: 177*4882a593Smuzhiyun stdout, stderr = _logged_communicate(pipe, log, input, extrafiles) 178*4882a593Smuzhiyun else: 179*4882a593Smuzhiyun stdout, stderr = pipe.communicate(input) 180*4882a593Smuzhiyun if not stdout is None: 181*4882a593Smuzhiyun stdout = stdout.decode("utf-8") 182*4882a593Smuzhiyun if not stderr is None: 183*4882a593Smuzhiyun stderr = stderr.decode("utf-8") 184*4882a593Smuzhiyun 185*4882a593Smuzhiyun if pipe.returncode != 0: 186*4882a593Smuzhiyun if log: 187*4882a593Smuzhiyun # Don't duplicate the output in the exception if logging it 188*4882a593Smuzhiyun raise ExecutionError(cmd, pipe.returncode, None, None) 189*4882a593Smuzhiyun raise ExecutionError(cmd, pipe.returncode, stdout, stderr) 190*4882a593Smuzhiyun return stdout, stderr 191