`ZipFile.close()` writes wrong offsets after a failed `repack()`
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 35.9k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
Bug description:
_ZipRepacker.repack() updates ZipInfo.header_offset for each member before moving that member's bytes, so a failing _copy_bytes() leaves the in-memory offsets describing a layout that was never written. remove() has already set _didModify, so a later normal close() commits a central directory built from those offsets: the caller handles the OSError, closes cleanly, and gets an archive that zipfile itself cannot read.
import io, zipfile
class Flaky(io.BytesIO):
countdown = None
def write(self, b):
if self.countdown is not None:
self.countdown -= 1
if self.countdown < 0:
raise OSError(28, 'No space left on device')
return super().write(b)
buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
for c in 'abcd':
zf.writestr(c + '.txt', c.upper().encode() * 5000)
flaky = Flaky(buf.getvalue())
with zipfile.ZipFile(flaky, 'a') as zf:
flaky.countdown = 1
try:
zf.repack([zf.remove('b.txt')], chunk_size=4096)
except OSError as exc:
print('repack raised:', exc)
flaky.countdown = None # space freed; the caller closes normally
with zipfile.ZipFile(flaky) as zf:
print('testzip:', zf.testzip())
repack raised: [Errno 28] No space left on device
testzip: c.txt
Expected: after the caller has handled the OSError, close() should either leave a readable archive or raise, rather than committing offsets that no write produced.
remove() and repack() are new in 3.16, so no released version is affected.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-156435
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu từ zipfile._ZipRepacker.repack(), _copy_bytes(), remove() và close(), sử dụng Flaky BytesIO reproducer để theo dõi các offset sau OSError. Hoàn tất có nghĩa là đường dẫn close sau lỗi để lại một archive có thể đọc được hoặc phát sinh exception, thay vì commit các offset cho dữ liệu chưa được ghi; hãy kiểm tra PR được liên kết gh-156435 trước khi bắt đầu.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- backend
- Loại issue
- Lỗi
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 25/100