15f35090dSAndrew Geissler# 2*92b42cb3SPatrick Williams# Copyright BitBake Contributors 3*92b42cb3SPatrick Williams# 45f35090dSAndrew Geissler# SPDX-License-Identifier: GPL-2.0-only 55f35090dSAndrew Geissler# 65f35090dSAndrew Geissler# Helper library to implement streaming compression and decompression using an 75f35090dSAndrew Geissler# external process 85f35090dSAndrew Geissler# 95f35090dSAndrew Geissler# This library should be used directly by end users; a wrapper library for the 105f35090dSAndrew Geissler# specific compression tool should be created 115f35090dSAndrew Geissler 125f35090dSAndrew Geisslerimport builtins 135f35090dSAndrew Geisslerimport io 145f35090dSAndrew Geisslerimport os 155f35090dSAndrew Geisslerimport subprocess 165f35090dSAndrew Geissler 175f35090dSAndrew Geissler 185f35090dSAndrew Geisslerdef open_wrap( 195f35090dSAndrew Geissler cls, filename, mode="rb", *, encoding=None, errors=None, newline=None, **kwargs 205f35090dSAndrew Geissler): 215f35090dSAndrew Geissler """ 225f35090dSAndrew Geissler Open a compressed file in binary or text mode. 235f35090dSAndrew Geissler 245f35090dSAndrew Geissler Users should not call this directly. A specific compression library can use 255f35090dSAndrew Geissler this helper to provide it's own "open" command 265f35090dSAndrew Geissler 275f35090dSAndrew Geissler The filename argument can be an actual filename (a str or bytes object), or 285f35090dSAndrew Geissler an existing file object to read from or write to. 295f35090dSAndrew Geissler 305f35090dSAndrew Geissler The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for 315f35090dSAndrew Geissler binary mode, or "rt", "wt", "xt" or "at" for text mode. The default mode is 325f35090dSAndrew Geissler "rb". 335f35090dSAndrew Geissler 345f35090dSAndrew Geissler For binary mode, this function is equivalent to the cls constructor: 355f35090dSAndrew Geissler cls(filename, mode). In this case, the encoding, errors and newline 365f35090dSAndrew Geissler arguments must not be provided. 375f35090dSAndrew Geissler 385f35090dSAndrew Geissler For text mode, a cls object is created, and wrapped in an 395f35090dSAndrew Geissler io.TextIOWrapper instance with the specified encoding, error handling 405f35090dSAndrew Geissler behavior, and line ending(s). 415f35090dSAndrew Geissler """ 425f35090dSAndrew Geissler if "t" in mode: 435f35090dSAndrew Geissler if "b" in mode: 445f35090dSAndrew Geissler raise ValueError("Invalid mode: %r" % (mode,)) 455f35090dSAndrew Geissler else: 465f35090dSAndrew Geissler if encoding is not None: 475f35090dSAndrew Geissler raise ValueError("Argument 'encoding' not supported in binary mode") 485f35090dSAndrew Geissler if errors is not None: 495f35090dSAndrew Geissler raise ValueError("Argument 'errors' not supported in binary mode") 505f35090dSAndrew Geissler if newline is not None: 515f35090dSAndrew Geissler raise ValueError("Argument 'newline' not supported in binary mode") 525f35090dSAndrew Geissler 535f35090dSAndrew Geissler file_mode = mode.replace("t", "") 54eff27476SAndrew Geissler if isinstance(filename, (str, bytes, os.PathLike, int)): 555f35090dSAndrew Geissler binary_file = cls(filename, file_mode, **kwargs) 565f35090dSAndrew Geissler elif hasattr(filename, "read") or hasattr(filename, "write"): 575f35090dSAndrew Geissler binary_file = cls(None, file_mode, fileobj=filename, **kwargs) 585f35090dSAndrew Geissler else: 595f35090dSAndrew Geissler raise TypeError("filename must be a str or bytes object, or a file") 605f35090dSAndrew Geissler 615f35090dSAndrew Geissler if "t" in mode: 625f35090dSAndrew Geissler return io.TextIOWrapper( 635f35090dSAndrew Geissler binary_file, encoding, errors, newline, write_through=True 645f35090dSAndrew Geissler ) 655f35090dSAndrew Geissler else: 665f35090dSAndrew Geissler return binary_file 675f35090dSAndrew Geissler 685f35090dSAndrew Geissler 695f35090dSAndrew Geisslerclass CompressionError(OSError): 705f35090dSAndrew Geissler pass 715f35090dSAndrew Geissler 725f35090dSAndrew Geissler 735f35090dSAndrew Geisslerclass PipeFile(io.RawIOBase): 745f35090dSAndrew Geissler """ 755f35090dSAndrew Geissler Class that implements generically piping to/from a compression program 765f35090dSAndrew Geissler 775f35090dSAndrew Geissler Derived classes should add the function get_compress() and get_decompress() 785f35090dSAndrew Geissler that return the required commands. Input will be piped into stdin and the 795f35090dSAndrew Geissler (de)compressed output should be written to stdout, e.g.: 805f35090dSAndrew Geissler 815f35090dSAndrew Geissler class FooFile(PipeCompressionFile): 825f35090dSAndrew Geissler def get_decompress(self): 835f35090dSAndrew Geissler return ["fooc", "--decompress", "--stdout"] 845f35090dSAndrew Geissler 855f35090dSAndrew Geissler def get_compress(self): 865f35090dSAndrew Geissler return ["fooc", "--compress", "--stdout"] 875f35090dSAndrew Geissler 885f35090dSAndrew Geissler """ 895f35090dSAndrew Geissler 905f35090dSAndrew Geissler READ = 0 915f35090dSAndrew Geissler WRITE = 1 925f35090dSAndrew Geissler 935f35090dSAndrew Geissler def __init__(self, filename=None, mode="rb", *, stderr=None, fileobj=None): 945f35090dSAndrew Geissler if "t" in mode or "U" in mode: 955f35090dSAndrew Geissler raise ValueError("Invalid mode: {!r}".format(mode)) 965f35090dSAndrew Geissler 975f35090dSAndrew Geissler if not "b" in mode: 985f35090dSAndrew Geissler mode += "b" 995f35090dSAndrew Geissler 1005f35090dSAndrew Geissler if mode.startswith("r"): 1015f35090dSAndrew Geissler self.mode = self.READ 1025f35090dSAndrew Geissler elif mode.startswith("w"): 1035f35090dSAndrew Geissler self.mode = self.WRITE 1045f35090dSAndrew Geissler else: 1055f35090dSAndrew Geissler raise ValueError("Invalid mode %r" % mode) 1065f35090dSAndrew Geissler 1075f35090dSAndrew Geissler if fileobj is not None: 1085f35090dSAndrew Geissler self.fileobj = fileobj 1095f35090dSAndrew Geissler else: 1105f35090dSAndrew Geissler self.fileobj = builtins.open(filename, mode or "rb") 1115f35090dSAndrew Geissler 1125f35090dSAndrew Geissler if self.mode == self.READ: 1135f35090dSAndrew Geissler self.p = subprocess.Popen( 1145f35090dSAndrew Geissler self.get_decompress(), 1155f35090dSAndrew Geissler stdin=self.fileobj, 1165f35090dSAndrew Geissler stdout=subprocess.PIPE, 1175f35090dSAndrew Geissler stderr=stderr, 1185f35090dSAndrew Geissler close_fds=True, 1195f35090dSAndrew Geissler ) 1205f35090dSAndrew Geissler self.pipe = self.p.stdout 1215f35090dSAndrew Geissler else: 1225f35090dSAndrew Geissler self.p = subprocess.Popen( 1235f35090dSAndrew Geissler self.get_compress(), 1245f35090dSAndrew Geissler stdin=subprocess.PIPE, 1255f35090dSAndrew Geissler stdout=self.fileobj, 1265f35090dSAndrew Geissler stderr=stderr, 1275f35090dSAndrew Geissler close_fds=True, 1285f35090dSAndrew Geissler ) 1295f35090dSAndrew Geissler self.pipe = self.p.stdin 1305f35090dSAndrew Geissler 1315f35090dSAndrew Geissler self.__closed = False 1325f35090dSAndrew Geissler 1335f35090dSAndrew Geissler def _check_process(self): 1345f35090dSAndrew Geissler if self.p is None: 1355f35090dSAndrew Geissler return 1365f35090dSAndrew Geissler 1375f35090dSAndrew Geissler returncode = self.p.wait() 1385f35090dSAndrew Geissler if returncode: 1395f35090dSAndrew Geissler raise CompressionError("Process died with %d" % returncode) 1405f35090dSAndrew Geissler self.p = None 1415f35090dSAndrew Geissler 1425f35090dSAndrew Geissler def close(self): 1435f35090dSAndrew Geissler if self.closed: 1445f35090dSAndrew Geissler return 1455f35090dSAndrew Geissler 1465f35090dSAndrew Geissler self.pipe.close() 1475f35090dSAndrew Geissler if self.p is not None: 1485f35090dSAndrew Geissler self._check_process() 1495f35090dSAndrew Geissler self.fileobj.close() 1505f35090dSAndrew Geissler 1515f35090dSAndrew Geissler self.__closed = True 1525f35090dSAndrew Geissler 1535f35090dSAndrew Geissler @property 1545f35090dSAndrew Geissler def closed(self): 1555f35090dSAndrew Geissler return self.__closed 1565f35090dSAndrew Geissler 1575f35090dSAndrew Geissler def fileno(self): 1585f35090dSAndrew Geissler return self.pipe.fileno() 1595f35090dSAndrew Geissler 1605f35090dSAndrew Geissler def flush(self): 1615f35090dSAndrew Geissler self.pipe.flush() 1625f35090dSAndrew Geissler 1635f35090dSAndrew Geissler def isatty(self): 1645f35090dSAndrew Geissler return self.pipe.isatty() 1655f35090dSAndrew Geissler 1665f35090dSAndrew Geissler def readable(self): 1675f35090dSAndrew Geissler return self.mode == self.READ 1685f35090dSAndrew Geissler 1695f35090dSAndrew Geissler def writable(self): 1705f35090dSAndrew Geissler return self.mode == self.WRITE 1715f35090dSAndrew Geissler 1725f35090dSAndrew Geissler def readinto(self, b): 1735f35090dSAndrew Geissler if self.mode != self.READ: 1745f35090dSAndrew Geissler import errno 1755f35090dSAndrew Geissler 1765f35090dSAndrew Geissler raise OSError( 1775f35090dSAndrew Geissler errno.EBADF, "read() on write-only %s object" % self.__class__.__name__ 1785f35090dSAndrew Geissler ) 1795f35090dSAndrew Geissler size = self.pipe.readinto(b) 1805f35090dSAndrew Geissler if size == 0: 1815f35090dSAndrew Geissler self._check_process() 1825f35090dSAndrew Geissler return size 1835f35090dSAndrew Geissler 1845f35090dSAndrew Geissler def write(self, data): 1855f35090dSAndrew Geissler if self.mode != self.WRITE: 1865f35090dSAndrew Geissler import errno 1875f35090dSAndrew Geissler 1885f35090dSAndrew Geissler raise OSError( 1895f35090dSAndrew Geissler errno.EBADF, "write() on read-only %s object" % self.__class__.__name__ 1905f35090dSAndrew Geissler ) 1915f35090dSAndrew Geissler data = self.pipe.write(data) 1925f35090dSAndrew Geissler 1935f35090dSAndrew Geissler if not data: 1945f35090dSAndrew Geissler self._check_process() 1955f35090dSAndrew Geissler 1965f35090dSAndrew Geissler return data 197