Repository navigation
Update gzip, tarfile, and shutil from CPython 3.15 #9112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -550,7 +550,7 @@ def nreadahead(): | |
| res += b | ||
| if res.endswith(b"\n"): | ||
| break | ||
| return bytes(res) | ||
| return res.take_bytes() | ||
|
|
||
| def __iter__(self): | ||
| self._checkClosed() | ||
|
|
@@ -624,15 +624,15 @@ def read(self, size=-1): | |
| if n < 0 or n > len(b): | ||
| raise ValueError(f"readinto returned {n} outside buffer size {len(b)}") | ||
| del b[n:] | ||
| return bytes(b) | ||
| return b.take_bytes() | ||
|
|
||
| def readall(self): | ||
| """Read until EOF, using multiple read() call.""" | ||
| res = bytearray() | ||
| while data := self.read(DEFAULT_BUFFER_SIZE): | ||
| res += data | ||
| if res: | ||
| return bytes(res) | ||
| return res.take_bytes() | ||
| else: | ||
| # b'' or None | ||
| return data | ||
|
|
@@ -882,16 +882,28 @@ class BytesIO(BufferedIOBase): | |
| _buffer = None | ||
|
|
||
| def __init__(self, initial_bytes=None): | ||
| # Use to keep self._buffer and self._pos consistent. | ||
| self._lock = Lock() | ||
|
|
||
| buf = bytearray() | ||
| if initial_bytes is not None: | ||
| buf += initial_bytes | ||
| self._buffer = buf | ||
| self._pos = 0 | ||
|
|
||
| with self._lock: | ||
| self._buffer = buf | ||
| self._pos = 0 | ||
|
|
||
| def __getstate__(self): | ||
| if self.closed: | ||
| raise ValueError("__getstate__ on closed file") | ||
| return self.__dict__.copy() | ||
| with self._lock: | ||
| state = self.__dict__.copy() | ||
| del state['_lock'] | ||
| return state | ||
|
|
||
| def __setstate__(self, state): | ||
| self.__dict__.update(state) | ||
| self._lock = Lock() | ||
|
|
||
| def getvalue(self): | ||
| """Return the bytes value (contents) of the buffer | ||
|
|
@@ -924,14 +936,16 @@ def read(self, size=-1): | |
| raise TypeError(f"{size!r} is not an integer") | ||
| else: | ||
| size = size_index() | ||
| if size < 0: | ||
| size = len(self._buffer) | ||
| if len(self._buffer) <= self._pos: | ||
| return b"" | ||
| newpos = min(len(self._buffer), self._pos + size) | ||
| b = self._buffer[self._pos : newpos] | ||
| self._pos = newpos | ||
| return bytes(b) | ||
|
|
||
| with self._lock: | ||
| if size < 0: | ||
| size = len(self._buffer) | ||
| if len(self._buffer) <= self._pos: | ||
| return b"" | ||
| newpos = min(len(self._buffer), self._pos + size) | ||
| b = self._buffer[self._pos : newpos] | ||
| self._pos = newpos | ||
| return bytes(b) | ||
|
|
||
| def read1(self, size=-1): | ||
| """This is the same as read. | ||
|
|
@@ -949,13 +963,14 @@ def write(self, b): | |
| if n == 0: | ||
| return 0 | ||
|
|
||
| pos = self._pos | ||
| if pos > len(self._buffer): | ||
| # Pad buffer to pos with null bytes. | ||
| self._buffer.resize(pos) | ||
| self._buffer[pos:pos + n] = view | ||
| self._pos += n | ||
| return n | ||
| with self._lock: | ||
| pos = self._pos | ||
| if pos > len(self._buffer): | ||
| # Pad buffer to pos with null bytes. | ||
| self._buffer.resize(pos) | ||
| self._buffer[pos:pos + n] = view | ||
| self._pos += n | ||
| return n | ||
|
|
||
| def seek(self, pos, whence=0): | ||
| if self.closed: | ||
|
|
@@ -971,9 +986,11 @@ def seek(self, pos, whence=0): | |
| raise ValueError("negative seek position %r" % (pos,)) | ||
| self._pos = pos | ||
| elif whence == 1: | ||
| self._pos = max(0, self._pos + pos) | ||
| with self._lock: | ||
| self._pos = max(0, self._pos + pos) | ||
| elif whence == 2: | ||
| self._pos = max(0, len(self._buffer) + pos) | ||
| with self._lock: | ||
| self._pos = max(0, len(self._buffer) + pos) | ||
| else: | ||
| raise ValueError("unsupported whence value") | ||
| return self._pos | ||
|
|
@@ -986,18 +1003,20 @@ def tell(self): | |
| def truncate(self, pos=None): | ||
| if self.closed: | ||
| raise ValueError("truncate on closed file") | ||
| if pos is None: | ||
| pos = self._pos | ||
| else: | ||
| try: | ||
| pos_index = pos.__index__ | ||
| except AttributeError: | ||
| raise TypeError(f"{pos!r} is not an integer") | ||
|
|
||
| with self._lock: | ||
| if pos is None: | ||
| pos = self._pos | ||
| else: | ||
| pos = pos_index() | ||
| if pos < 0: | ||
| raise ValueError("negative truncate position %r" % (pos,)) | ||
| del self._buffer[pos:] | ||
| try: | ||
| pos_index = pos.__index__ | ||
| except AttributeError: | ||
| raise TypeError(f"{pos!r} is not an integer") | ||
| else: | ||
| pos = pos_index() | ||
| if pos < 0: | ||
| raise ValueError("negative truncate position %r" % (pos,)) | ||
| del self._buffer[pos:] | ||
| return pos | ||
|
|
||
| def readable(self): | ||
|
|
@@ -1486,6 +1505,7 @@ class FileIO(RawIOBase): | |
| _writable = False | ||
| _appending = False | ||
| _seekable = None | ||
| _truncate = False | ||
| _closefd = True | ||
|
|
||
| def __init__(self, file, mode='r', closefd=True, opener=None): | ||
|
|
@@ -1546,6 +1566,7 @@ def __init__(self, file, mode='r', closefd=True, opener=None): | |
| flags = 0 | ||
| elif 'w' in mode: | ||
| self._writable = True | ||
| self._truncate = True | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the same pure-Python Useful? React with 👍 / 👎. |
||
| flags = os.O_CREAT | os.O_TRUNC | ||
| elif 'a' in mode: | ||
| self._writable = True | ||
|
|
@@ -1727,7 +1748,7 @@ def readall(self): | |
| assert len(result) - bytes_read >= 1, \ | ||
| "os.readinto buffer size 0 will result in erroneous EOF / returns 0" | ||
| result.resize(bytes_read) | ||
| return bytes(result) | ||
| return result.take_bytes() | ||
|
|
||
| def readinto(self, buffer): | ||
| """Same as RawIOBase.readinto().""" | ||
|
|
@@ -1871,7 +1892,10 @@ def mode(self): | |
| return 'ab' | ||
| elif self._readable: | ||
| if self._writable: | ||
| return 'rb+' | ||
| if self._truncate: | ||
| return 'wb+' | ||
| else: | ||
| return 'rb+' | ||
| else: | ||
| return 'rb' | ||
| else: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,7 @@ | |
| _WRITE_BUFFER_SIZE = 4 * io.DEFAULT_BUFFER_SIZE | ||
|
|
||
|
|
||
| def open(filename, mode="rb", compresslevel=_COMPRESS_LEVEL_BEST, | ||
| def open(filename, mode="rb", compresslevel=_COMPRESS_LEVEL_TRADEOFF, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This changes the public default to Useful? React with 👍 / 👎. |
||
| encoding=None, errors=None, newline=None): | ||
| """Open a gzip-compressed file in binary or text mode. | ||
|
|
||
|
|
@@ -158,7 +158,7 @@ class GzipFile(_streams.BaseStream): | |
| myfileobj = None | ||
|
|
||
| def __init__(self, filename=None, mode=None, | ||
| compresslevel=_COMPRESS_LEVEL_BEST, fileobj=None, mtime=None): | ||
| compresslevel=_COMPRESS_LEVEL_TRADEOFF, fileobj=None, mtime=None): | ||
| """Constructor for the GzipFile class. | ||
|
|
||
| At least one of fileobj and filename must be given a | ||
|
|
@@ -189,9 +189,10 @@ def __init__(self, filename=None, mode=None, | |
|
|
||
| The optional mtime argument is the timestamp requested by gzip. | ||
| The time is in Unix format, i.e., seconds since 00:00:00 UTC, | ||
| January 1, 1970. If mtime is omitted or None, the current time | ||
| is used. Use mtime = 0 to generate a compressed stream that does | ||
| not depend on creation time. | ||
| January 1, 1970. Set mtime to 0 to generate a compressed stream | ||
| that does not depend on creation time. If mtime is omitted or None, | ||
| the current time is used. If the resulting mtime is outside the | ||
| range 0 to 2**32-1, then the value 0 is used instead. | ||
|
|
||
| """ | ||
|
|
||
|
|
@@ -270,8 +271,6 @@ def _init_write(self, filename): | |
| self.name = filename | ||
| self.crc = zlib.crc32(b"") | ||
| self.size = 0 | ||
| self.writebuf = [] | ||
| self.bufsize = 0 | ||
| self.offset = 0 # Current file offset for seek(), tell(), etc | ||
|
|
||
| def tell(self): | ||
|
|
@@ -299,6 +298,8 @@ def _write_gzip_header(self, compresslevel): | |
| mtime = self._write_mtime | ||
| if mtime is None: | ||
| mtime = time.time() | ||
| if not 0 <= mtime < 2**32: | ||
| mtime = 0 | ||
| write32u(self.fileobj, int(mtime)) | ||
| if compresslevel == _COMPRESS_LEVEL_BEST: | ||
| xfl = b'\002' | ||
|
|
@@ -488,40 +489,73 @@ def _read_exact(fp, n): | |
| return data | ||
|
|
||
|
|
||
| def _read_until_null(fp, crc=None): | ||
| '''Read until the first encountered null byte in fp. | ||
| If crc is not None, update and return the CRC. | ||
| ''' | ||
| if crc is None: | ||
| while True: | ||
| s = fp.read(1) | ||
| if not s or s == b'\000': | ||
| break | ||
| else: | ||
| while True: | ||
| s = fp.read(1) | ||
| crc = zlib.crc32(s, crc) | ||
| if not s or s == b'\000': | ||
| break | ||
| return crc | ||
|
|
||
|
|
||
| def _read_gzip_header(fp): | ||
| '''Read a gzip header from `fp` and progress to the end of the header. | ||
|
|
||
| Returns last mtime if header was present or None otherwise. | ||
| ''' | ||
| magic = fp.read(2) | ||
| if magic == b'': | ||
| if not magic: | ||
| return None | ||
|
|
||
| if magic != b'\037\213': | ||
| raise BadGzipFile('Not a gzipped file (%r)' % magic) | ||
|
|
||
| (method, flag, last_mtime) = struct.unpack("<BBIxx", _read_exact(fp, 8)) | ||
| base_header = _read_exact(fp, 8) | ||
| (method, flag, last_mtime) = struct.unpack("<BBIxx", base_header) | ||
| if method != 8: | ||
| raise BadGzipFile('Unknown compression method') | ||
|
|
||
| # Most common cases are no flags (gzip.compress, zlib.compress) or only | ||
| # FNAME set (GzipFile, gzip command line application). Exit early | ||
| # in those cases. | ||
| if not flag: | ||
| return last_mtime | ||
| if flag == FNAME: | ||
| # Read and discard a null-terminated string containing the filename | ||
| _read_until_null(fp) | ||
| return last_mtime | ||
|
|
||
| # Processing for more complex flags. Save header parts for FHCRC checking. | ||
| if flag & FHCRC: | ||
| crc = zlib.crc32(magic + base_header) | ||
| else: | ||
| crc = None | ||
| if flag & FEXTRA: | ||
| # Read & discard the extra field, if present | ||
| extra_len, = struct.unpack("<H", _read_exact(fp, 2)) | ||
| _read_exact(fp, extra_len) | ||
| extra_len_bytes = _read_exact(fp, 2) | ||
| extra_len, = struct.unpack("<H", extra_len_bytes) | ||
| extra = _read_exact(fp, extra_len) | ||
| if crc is not None: | ||
| crc = zlib.crc32(extra_len_bytes, crc) | ||
| crc = zlib.crc32(extra, crc) | ||
| if flag & FNAME: | ||
| # Read and discard a null-terminated string containing the filename | ||
| while True: | ||
| s = fp.read(1) | ||
| if not s or s==b'\000': | ||
| break | ||
| crc = _read_until_null(fp, crc) | ||
| if flag & FCOMMENT: | ||
| # Read and discard a null-terminated string containing a comment | ||
| while True: | ||
| s = fp.read(1) | ||
| if not s or s==b'\000': | ||
| break | ||
| if flag & FHCRC: | ||
| _read_exact(fp, 2) # Read & discard the 16-bit header CRC | ||
| crc = _read_until_null(fp, crc) | ||
| if crc is not None: | ||
| # Header CRC is the last 16 bits of a crc32. | ||
| header_crc, = struct.unpack("<H", _read_exact(fp, 2)) | ||
| crc = crc & 0xFFFF | ||
| if header_crc != crc: | ||
| raise BadGzipFile(f"Corrupted gzip header. Checksums do not " | ||
| f"match: {crc:04x} != {header_crc:04x}") | ||
| return last_mtime | ||
|
|
||
|
|
||
|
|
@@ -623,7 +657,7 @@ def _rewind(self): | |
| self._new_member = True | ||
|
|
||
|
|
||
| def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=0): | ||
| def compress(data, compresslevel=_COMPRESS_LEVEL_TRADEOFF, *, mtime=0): | ||
| """Compress data in one shot and return the compressed string. | ||
|
|
||
| compresslevel sets the compression level in range of 0-9. | ||
|
|
@@ -634,6 +668,8 @@ def compress(data, compresslevel=_COMPRESS_LEVEL_BEST, *, mtime=0): | |
| gzip_data = zlib.compress(data, level=compresslevel, wbits=31) | ||
| if mtime is None: | ||
| mtime = time.time() | ||
| if not 0 <= mtime < 2**32: | ||
| mtime = 0 | ||
| # Reuse gzip header created by zlib, replace mtime and OS byte for | ||
| # consistency. | ||
| header = struct.pack("<4sLBB", gzip_data, int(mtime), gzip_data[8], 255) | ||
|
|
@@ -668,15 +704,14 @@ def decompress(data): | |
| def main(): | ||
| from argparse import ArgumentParser | ||
| parser = ArgumentParser(description= | ||
| "A simple command line interface for the gzip module: act like gzip, " | ||
| "A simple command line interface for the `gzip` module: act like `gzip`, " | ||
| "but do not delete the input file.", | ||
| color=True, | ||
| ) | ||
| group = parser.add_mutually_exclusive_group() | ||
| group.add_argument('--fast', action='store_true', help='compress faster') | ||
| group.add_argument('--best', action='store_true', help='compress better') | ||
| group.add_argument("-d", "--decompress", action="store_true", | ||
| help="act like gunzip instead of gzip") | ||
| help="act like `gunzip` instead of `gzip`") | ||
|
|
||
| parser.add_argument("args", nargs="*", default=["-"], metavar='file') | ||
| args = parser.parse_args() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
pos.__index__()calls back into the same buffer, for example by invokingb.read(1),truncate()already owns the non-reentrant_thread.lockand the callback waits on that same lock forever. Resolve the user-defined index before entering the critical section, asseek()does, and lock only the state access and mutation.Useful? React with 👍 / 👎.