Kaydet (Commit) 16f344df authored tarafından Martin v. Löwis's avatar Martin v. Löwis

Issue #10184: Touch directories only once when extracting a tarfile.

üst bbea35f1
...@@ -336,12 +336,13 @@ be finalized; only the internally used file object will be closed. See the ...@@ -336,12 +336,13 @@ be finalized; only the internally used file object will be closed. See the
dots ``".."``. dots ``".."``.
.. method:: TarFile.extract(member, path="") .. method:: TarFile.extract(member, path="", set_attrs=True)
Extract a member from the archive to the current working directory, using its Extract a member from the archive to the current working directory, using its
full name. Its file information is extracted as accurately as possible. *member* full name. Its file information is extracted as accurately as possible. *member*
may be a filename or a :class:`TarInfo` object. You can specify a different may be a filename or a :class:`TarInfo` object. You can specify a different
directory using *path*. directory using *path*. File attributes (owner, mtime, mode) are set unless
*set_attrs* is False.
.. note:: .. note::
...@@ -352,6 +353,8 @@ be finalized; only the internally used file object will be closed. See the ...@@ -352,6 +353,8 @@ be finalized; only the internally used file object will be closed. See the
See the warning for :meth:`extractall`. See the warning for :meth:`extractall`.
.. versionchanged:: 3.2
Added the *set_attrs* parameter.
.. method:: TarFile.extractfile(member) .. method:: TarFile.extractfile(member)
......
...@@ -2131,7 +2131,8 @@ class TarFile(object): ...@@ -2131,7 +2131,8 @@ class TarFile(object):
directories.append(tarinfo) directories.append(tarinfo)
tarinfo = copy.copy(tarinfo) tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0o700 tarinfo.mode = 0o700
self.extract(tarinfo, path) # Do not set_attrs directories, as we will do that further down
self.extract(tarinfo, path, set_attrs=not tarinfo.isdir())
# Reverse sort directories. # Reverse sort directories.
directories.sort(key=lambda a: a.name) directories.sort(key=lambda a: a.name)
...@@ -2150,11 +2151,12 @@ class TarFile(object): ...@@ -2150,11 +2151,12 @@ class TarFile(object):
else: else:
self._dbg(1, "tarfile: %s" % e) self._dbg(1, "tarfile: %s" % e)
def extract(self, member, path=""): def extract(self, member, path="", set_attrs=True):
"""Extract a member from the archive to the current working directory, """Extract a member from the archive to the current working directory,
using its full name. Its file information is extracted as accurately using its full name. Its file information is extracted as accurately
as possible. `member' may be a filename or a TarInfo object. You can as possible. `member' may be a filename or a TarInfo object. You can
specify a different directory using `path'. specify a different directory using `path'. File attributes (owner,
mtime, mode) are set unless `set_attrs' is False.
""" """
self._check("r") self._check("r")
...@@ -2168,7 +2170,8 @@ class TarFile(object): ...@@ -2168,7 +2170,8 @@ class TarFile(object):
tarinfo._link_target = os.path.join(path, tarinfo.linkname) tarinfo._link_target = os.path.join(path, tarinfo.linkname)
try: try:
self._extract_member(tarinfo, os.path.join(path, tarinfo.name)) self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
set_attrs=set_attrs)
except EnvironmentError as e: except EnvironmentError as e:
if self.errorlevel > 0: if self.errorlevel > 0:
raise raise
...@@ -2221,7 +2224,7 @@ class TarFile(object): ...@@ -2221,7 +2224,7 @@ class TarFile(object):
# blkdev, etc.), return None instead of a file object. # blkdev, etc.), return None instead of a file object.
return None return None
def _extract_member(self, tarinfo, targetpath): def _extract_member(self, tarinfo, targetpath, set_attrs=True):
"""Extract the TarInfo object tarinfo to a physical """Extract the TarInfo object tarinfo to a physical
file called targetpath. file called targetpath.
""" """
...@@ -2258,10 +2261,11 @@ class TarFile(object): ...@@ -2258,10 +2261,11 @@ class TarFile(object):
else: else:
self.makefile(tarinfo, targetpath) self.makefile(tarinfo, targetpath)
self.chown(tarinfo, targetpath) if set_attrs:
if not tarinfo.issym(): self.chown(tarinfo, targetpath)
self.chmod(tarinfo, targetpath) if not tarinfo.issym():
self.utime(tarinfo, targetpath) self.chmod(tarinfo, targetpath)
self.utime(tarinfo, targetpath)
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
# Below are the different file methods. They are called via # Below are the different file methods. They are called via
......
...@@ -377,6 +377,15 @@ class MiscReadTest(CommonReadTest): ...@@ -377,6 +377,15 @@ class MiscReadTest(CommonReadTest):
finally: finally:
tar.close() tar.close()
def test_extract_directory(self):
dirtype = "ustar/dirtype"
with tarfile.open(tarname, encoding="iso8859-1") as tar:
tarinfo = tar.getmember(dirtype)
tar.extract(tarinfo)
self.assertEqual(os.path.getmtime(dirtype), tarinfo.mtime)
if sys.platform != "win32":
self.assertEqual(os.stat(dirtype).st_mode & 0o777, 0o755)
def test_init_close_fobj(self): def test_init_close_fobj(self):
# Issue #7341: Close the internal file object in the TarFile # Issue #7341: Close the internal file object in the TarFile
# constructor in case of an error. For the test we rely on # constructor in case of an error. For the test we rely on
......
...@@ -59,6 +59,8 @@ Core and Builtins ...@@ -59,6 +59,8 @@ Core and Builtins
Library Library
------- -------
- Issue #10184: Touch directories only once when extracting a tarfile.
- Issue #10199: New package, ``turtledemo`` now contains selected demo - Issue #10199: New package, ``turtledemo`` now contains selected demo
scripts that were formerly found under Demo/turtle. scripts that were formerly found under Demo/turtle.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment