1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# Copyright BitBake Contributors 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# Helper library to implement streaming compression and decompression using an 7*4882a593Smuzhiyun# external process 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun# This library should be used directly by end users; a wrapper library for the 10*4882a593Smuzhiyun# specific compression tool should be created 11*4882a593Smuzhiyun 12*4882a593Smuzhiyunimport builtins 13*4882a593Smuzhiyunimport io 14*4882a593Smuzhiyunimport os 15*4882a593Smuzhiyunimport subprocess 16*4882a593Smuzhiyun 17*4882a593Smuzhiyun 18*4882a593Smuzhiyundef open_wrap( 19*4882a593Smuzhiyun cls, filename, mode="rb", *, encoding=None, errors=None, newline=None, **kwargs 20*4882a593Smuzhiyun): 21*4882a593Smuzhiyun """ 22*4882a593Smuzhiyun Open a compressed file in binary or text mode. 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun Users should not call this directly. A specific compression library can use 25*4882a593Smuzhiyun this helper to provide it's own "open" command 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun The filename argument can be an actual filename (a str or bytes object), or 28*4882a593Smuzhiyun an existing file object to read from or write to. 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for 31*4882a593Smuzhiyun binary mode, or "rt", "wt", "xt" or "at" for text mode. The default mode is 32*4882a593Smuzhiyun "rb". 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun For binary mode, this function is equivalent to the cls constructor: 35*4882a593Smuzhiyun cls(filename, mode). In this case, the encoding, errors and newline 36*4882a593Smuzhiyun arguments must not be provided. 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun For text mode, a cls object is created, and wrapped in an 39*4882a593Smuzhiyun io.TextIOWrapper instance with the specified encoding, error handling 40*4882a593Smuzhiyun behavior, and line ending(s). 41*4882a593Smuzhiyun """ 42*4882a593Smuzhiyun if "t" in mode: 43*4882a593Smuzhiyun if "b" in mode: 44*4882a593Smuzhiyun raise ValueError("Invalid mode: %r" % (mode,)) 45*4882a593Smuzhiyun else: 46*4882a593Smuzhiyun if encoding is not None: 47*4882a593Smuzhiyun raise ValueError("Argument 'encoding' not supported in binary mode") 48*4882a593Smuzhiyun if errors is not None: 49*4882a593Smuzhiyun raise ValueError("Argument 'errors' not supported in binary mode") 50*4882a593Smuzhiyun if newline is not None: 51*4882a593Smuzhiyun raise ValueError("Argument 'newline' not supported in binary mode") 52*4882a593Smuzhiyun 53*4882a593Smuzhiyun file_mode = mode.replace("t", "") 54*4882a593Smuzhiyun if isinstance(filename, (str, bytes, os.PathLike, int)): 55*4882a593Smuzhiyun binary_file = cls(filename, file_mode, **kwargs) 56*4882a593Smuzhiyun elif hasattr(filename, "read") or hasattr(filename, "write"): 57*4882a593Smuzhiyun binary_file = cls(None, file_mode, fileobj=filename, **kwargs) 58*4882a593Smuzhiyun else: 59*4882a593Smuzhiyun raise TypeError("filename must be a str or bytes object, or a file") 60*4882a593Smuzhiyun 61*4882a593Smuzhiyun if "t" in mode: 62*4882a593Smuzhiyun return io.TextIOWrapper( 63*4882a593Smuzhiyun binary_file, encoding, errors, newline, write_through=True 64*4882a593Smuzhiyun ) 65*4882a593Smuzhiyun else: 66*4882a593Smuzhiyun return binary_file 67*4882a593Smuzhiyun 68*4882a593Smuzhiyun 69*4882a593Smuzhiyunclass CompressionError(OSError): 70*4882a593Smuzhiyun pass 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun 73*4882a593Smuzhiyunclass PipeFile(io.RawIOBase): 74*4882a593Smuzhiyun """ 75*4882a593Smuzhiyun Class that implements generically piping to/from a compression program 76*4882a593Smuzhiyun 77*4882a593Smuzhiyun Derived classes should add the function get_compress() and get_decompress() 78*4882a593Smuzhiyun that return the required commands. Input will be piped into stdin and the 79*4882a593Smuzhiyun (de)compressed output should be written to stdout, e.g.: 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun class FooFile(PipeCompressionFile): 82*4882a593Smuzhiyun def get_decompress(self): 83*4882a593Smuzhiyun return ["fooc", "--decompress", "--stdout"] 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun def get_compress(self): 86*4882a593Smuzhiyun return ["fooc", "--compress", "--stdout"] 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun """ 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun READ = 0 91*4882a593Smuzhiyun WRITE = 1 92*4882a593Smuzhiyun 93*4882a593Smuzhiyun def __init__(self, filename=None, mode="rb", *, stderr=None, fileobj=None): 94*4882a593Smuzhiyun if "t" in mode or "U" in mode: 95*4882a593Smuzhiyun raise ValueError("Invalid mode: {!r}".format(mode)) 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun if not "b" in mode: 98*4882a593Smuzhiyun mode += "b" 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun if mode.startswith("r"): 101*4882a593Smuzhiyun self.mode = self.READ 102*4882a593Smuzhiyun elif mode.startswith("w"): 103*4882a593Smuzhiyun self.mode = self.WRITE 104*4882a593Smuzhiyun else: 105*4882a593Smuzhiyun raise ValueError("Invalid mode %r" % mode) 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun if fileobj is not None: 108*4882a593Smuzhiyun self.fileobj = fileobj 109*4882a593Smuzhiyun else: 110*4882a593Smuzhiyun self.fileobj = builtins.open(filename, mode or "rb") 111*4882a593Smuzhiyun 112*4882a593Smuzhiyun if self.mode == self.READ: 113*4882a593Smuzhiyun self.p = subprocess.Popen( 114*4882a593Smuzhiyun self.get_decompress(), 115*4882a593Smuzhiyun stdin=self.fileobj, 116*4882a593Smuzhiyun stdout=subprocess.PIPE, 117*4882a593Smuzhiyun stderr=stderr, 118*4882a593Smuzhiyun close_fds=True, 119*4882a593Smuzhiyun ) 120*4882a593Smuzhiyun self.pipe = self.p.stdout 121*4882a593Smuzhiyun else: 122*4882a593Smuzhiyun self.p = subprocess.Popen( 123*4882a593Smuzhiyun self.get_compress(), 124*4882a593Smuzhiyun stdin=subprocess.PIPE, 125*4882a593Smuzhiyun stdout=self.fileobj, 126*4882a593Smuzhiyun stderr=stderr, 127*4882a593Smuzhiyun close_fds=True, 128*4882a593Smuzhiyun ) 129*4882a593Smuzhiyun self.pipe = self.p.stdin 130*4882a593Smuzhiyun 131*4882a593Smuzhiyun self.__closed = False 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun def _check_process(self): 134*4882a593Smuzhiyun if self.p is None: 135*4882a593Smuzhiyun return 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun returncode = self.p.wait() 138*4882a593Smuzhiyun if returncode: 139*4882a593Smuzhiyun raise CompressionError("Process died with %d" % returncode) 140*4882a593Smuzhiyun self.p = None 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun def close(self): 143*4882a593Smuzhiyun if self.closed: 144*4882a593Smuzhiyun return 145*4882a593Smuzhiyun 146*4882a593Smuzhiyun self.pipe.close() 147*4882a593Smuzhiyun if self.p is not None: 148*4882a593Smuzhiyun self._check_process() 149*4882a593Smuzhiyun self.fileobj.close() 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun self.__closed = True 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun @property 154*4882a593Smuzhiyun def closed(self): 155*4882a593Smuzhiyun return self.__closed 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun def fileno(self): 158*4882a593Smuzhiyun return self.pipe.fileno() 159*4882a593Smuzhiyun 160*4882a593Smuzhiyun def flush(self): 161*4882a593Smuzhiyun self.pipe.flush() 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun def isatty(self): 164*4882a593Smuzhiyun return self.pipe.isatty() 165*4882a593Smuzhiyun 166*4882a593Smuzhiyun def readable(self): 167*4882a593Smuzhiyun return self.mode == self.READ 168*4882a593Smuzhiyun 169*4882a593Smuzhiyun def writable(self): 170*4882a593Smuzhiyun return self.mode == self.WRITE 171*4882a593Smuzhiyun 172*4882a593Smuzhiyun def readinto(self, b): 173*4882a593Smuzhiyun if self.mode != self.READ: 174*4882a593Smuzhiyun import errno 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun raise OSError( 177*4882a593Smuzhiyun errno.EBADF, "read() on write-only %s object" % self.__class__.__name__ 178*4882a593Smuzhiyun ) 179*4882a593Smuzhiyun size = self.pipe.readinto(b) 180*4882a593Smuzhiyun if size == 0: 181*4882a593Smuzhiyun self._check_process() 182*4882a593Smuzhiyun return size 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun def write(self, data): 185*4882a593Smuzhiyun if self.mode != self.WRITE: 186*4882a593Smuzhiyun import errno 187*4882a593Smuzhiyun 188*4882a593Smuzhiyun raise OSError( 189*4882a593Smuzhiyun errno.EBADF, "write() on read-only %s object" % self.__class__.__name__ 190*4882a593Smuzhiyun ) 191*4882a593Smuzhiyun data = self.pipe.write(data) 192*4882a593Smuzhiyun 193*4882a593Smuzhiyun if not data: 194*4882a593Smuzhiyun self._check_process() 195*4882a593Smuzhiyun 196*4882a593Smuzhiyun return data 197