gh-131492, gh-131461: handle exceptions in GzipFile constructor while owning resources (#131462)

Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Thomas Grainger 2025-03-20 17:06:21 +00:00 committed by GitHub
parent f53e7de6a8
commit ce79274e9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 65 additions and 49 deletions

View file

@ -202,51 +202,58 @@ class GzipFile(_compression.BaseStream):
raise ValueError("Invalid mode: {!r}".format(mode)) raise ValueError("Invalid mode: {!r}".format(mode))
if mode and 'b' not in mode: if mode and 'b' not in mode:
mode += 'b' mode += 'b'
if fileobj is None:
fileobj = self.myfileobj = builtins.open(filename, mode or 'rb') try:
if filename is None: if fileobj is None:
filename = getattr(fileobj, 'name', '') fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
if not isinstance(filename, (str, bytes)): if filename is None:
filename = '' filename = getattr(fileobj, 'name', '')
else: if not isinstance(filename, (str, bytes)):
filename = os.fspath(filename) filename = ''
origmode = mode else:
if mode is None: filename = os.fspath(filename)
mode = getattr(fileobj, 'mode', 'rb') origmode = mode
if mode is None:
mode = getattr(fileobj, 'mode', 'rb')
if mode.startswith('r'): if mode.startswith('r'):
self.mode = READ self.mode = READ
raw = _GzipReader(fileobj) raw = _GzipReader(fileobj)
self._buffer = io.BufferedReader(raw) self._buffer = io.BufferedReader(raw)
self.name = filename self.name = filename
elif mode.startswith(('w', 'a', 'x')): elif mode.startswith(('w', 'a', 'x')):
if origmode is None: if origmode is None:
import warnings import warnings
warnings.warn( warnings.warn(
"GzipFile was opened for writing, but this will " "GzipFile was opened for writing, but this will "
"change in future Python releases. " "change in future Python releases. "
"Specify the mode argument for opening it for writing.", "Specify the mode argument for opening it for writing.",
FutureWarning, 2) FutureWarning, 2)
self.mode = WRITE self.mode = WRITE
self._init_write(filename) self._init_write(filename)
self.compress = zlib.compressobj(compresslevel, self.compress = zlib.compressobj(compresslevel,
zlib.DEFLATED, zlib.DEFLATED,
-zlib.MAX_WBITS, -zlib.MAX_WBITS,
zlib.DEF_MEM_LEVEL, zlib.DEF_MEM_LEVEL,
0) 0)
self._write_mtime = mtime self._write_mtime = mtime
self._buffer_size = _WRITE_BUFFER_SIZE self._buffer_size = _WRITE_BUFFER_SIZE
self._buffer = io.BufferedWriter(_WriteBufferStream(self), self._buffer = io.BufferedWriter(_WriteBufferStream(self),
buffer_size=self._buffer_size) buffer_size=self._buffer_size)
else: else:
raise ValueError("Invalid mode: {!r}".format(mode)) raise ValueError("Invalid mode: {!r}".format(mode))
self.fileobj = fileobj self.fileobj = fileobj
if self.mode == WRITE: if self.mode == WRITE:
self._write_gzip_header(compresslevel) self._write_gzip_header(compresslevel)
except:
# Avoid a ResourceWarning if the write fails,
# eg read-only file or KeyboardInterrupt
self._close()
raise
@property @property
def mtime(self): def mtime(self):
@ -387,11 +394,14 @@ class GzipFile(_compression.BaseStream):
elif self.mode == READ: elif self.mode == READ:
self._buffer.close() self._buffer.close()
finally: finally:
self.fileobj = None self._close()
myfileobj = self.myfileobj
if myfileobj: def _close(self):
self.myfileobj = None self.fileobj = None
myfileobj.close() myfileobj = self.myfileobj
if myfileobj is not None:
self.myfileobj = None
myfileobj.close()
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH): def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
self._check_not_closed() self._check_not_closed()

View file

@ -19,6 +19,7 @@ from test import archiver_tests
from test import support from test import support
from test.support import os_helper from test.support import os_helper
from test.support import script_helper from test.support import script_helper
from test.support import warnings_helper
# Check for our compression modules. # Check for our compression modules.
try: try:
@ -1638,10 +1639,13 @@ class WriteTest(WriteTestBase, unittest.TestCase):
raise exctype raise exctype
f = BadFile() f = BadFile()
with self.assertRaises(exctype): with (
tar = tarfile.open(tmpname, self.mode, fileobj=f, warnings_helper.check_no_resource_warning(self),
format=tarfile.PAX_FORMAT, self.assertRaises(exctype),
pax_headers={'non': 'empty'}) ):
tarfile.open(tmpname, self.mode, fileobj=f,
format=tarfile.PAX_FORMAT,
pax_headers={'non': 'empty'})
self.assertFalse(f.closed) self.assertFalse(f.closed)
def test_missing_fileobj(self): def test_missing_fileobj(self):

View file

@ -0,0 +1 @@
Fix :exc:`ResourceWarning` when constructing a :class:`gzip.GzipFile` in write mode with a broken file object.

View file

@ -0,0 +1 @@
Fix a resource leak when constructing a :class:`gzip.GzipFile` with a filename fails, for example when passing an invalid ``compresslevel``.