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