xref: /OK3568_Linux_fs/yocto/bitbake/lib/bb/compress/_pipecompress.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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