You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ZipFile.close() writes wrong offsets after a failed repack() #156434
_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.
importio, zipfileclassFlaky(io.BytesIO):
countdown=Nonedefwrite(self, b):
ifself.countdownisnotNone:
self.countdown-=1ifself.countdown<0:
raiseOSError(28, 'No space left on device')
returnsuper().write(b)
buf=io.BytesIO()
withzipfile.ZipFile(buf, 'w') aszf:
forcin'abcd':
zf.writestr(c+'.txt', c.upper().encode() *5000)
flaky=Flaky(buf.getvalue())
withzipfile.ZipFile(flaky, 'a') aszf:
flaky.countdown=1try:
zf.repack([zf.remove('b.txt')], chunk_size=4096)
exceptOSErrorasexc:
print('repack raised:', exc)
flaky.countdown=None# space freed; the caller closes normallywithzipfile.ZipFile(flaky) aszf:
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.
Do you have any real-world scenario of a failed repack in mind? I wonder if the No space left on device error is possible since the repack is shrinking a file.
Strictly speaking, a repack could fail after some entries have been moved but before the remaining entries have been processed. In that case, restoring the original header offsets alone would not necessarily restore the archive to a consistent state.
Also, repack sets _didModify after moving all entries, and it would not be set if an exception is raised during moving physical entries. (But may be set beforehand by remove)
More generally, in-place archive modifications are not inherently atomic. This applies even to a simple ZipFile.write(). If we want to guarantee that a failed modification leaves the original archive intact, we would need a transactional approach, such as writing to a temporary file and replacing the original only after the operation succeeds. However, this would probably be out of scope for the zipfile library.
Activity
danny0838 commentedon Oct 12, 2026
Do you have any real-world scenario of a failed repack in mind? I wonder if the
No space left on deviceerror is possible since the repack is shrinking a file.Strictly speaking, a repack could fail after some entries have been moved but before the remaining entries have been processed. In that case, restoring the original header offsets alone would not necessarily restore the archive to a consistent state.
Also,
repacksets_didModifyafter moving all entries, and it would not be set if an exception is raised during moving physical entries. (But may be set beforehand byremove)More generally, in-place archive modifications are not inherently atomic. This applies even to a simple
ZipFile.write(). If we want to guarantee that a failed modification leaves the original archive intact, we would need a transactional approach, such as writing to a temporary file and replacing the original only after the operation succeeds. However, this would probably be out of scope for thezipfilelibrary.