Skip to content

Commit abe6f52

Browse files
[3.13] pythongh-75989: TarFile.extractall and TarFile.extract now overwrite symlinks when extracting hardlinks (pythonGH-137316) (pythonGH-139771)
(cherry picked from commit 481d5b5) Co-authored-by: Alexander Urieles <[email protected]>
1 parent 69a3493 commit abe6f52

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

Lib/tarfile.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,9 @@ def makelink_with_filter(self, tarinfo, targetpath,
26602660
return
26612661
else:
26622662
if os.path.exists(tarinfo._link_target):
2663+
if os.path.lexists(targetpath):
2664+
# Avoid FileExistsError on following os.link.
2665+
os.unlink(targetpath)
26632666
os.link(tarinfo._link_target, targetpath)
26642667
return
26652668
except symlink_exception:

Lib/test/test_tarfile.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,57 @@ def test_next_on_empty_tarfile(self):
833833
with tarfile.open(fileobj=fd, mode="r") as tf:
834834
self.assertEqual(tf.next(), None)
835835

836+
def _setup_symlink_to_target(self, temp_dirpath):
837+
target_filepath = os.path.join(temp_dirpath, "target")
838+
ustar_dirpath = os.path.join(temp_dirpath, "ustar")
839+
hardlink_filepath = os.path.join(ustar_dirpath, "lnktype")
840+
with open(target_filepath, "wb") as f:
841+
f.write(b"target")
842+
os.makedirs(ustar_dirpath)
843+
os.symlink(target_filepath, hardlink_filepath)
844+
return target_filepath, hardlink_filepath
845+
846+
def _assert_on_file_content(self, filepath, digest):
847+
with open(filepath, "rb") as f:
848+
data = f.read()
849+
self.assertEqual(sha256sum(data), digest)
850+
851+
@unittest.skipUnless(
852+
hasattr(os, "link"), "Missing hardlink implementation"
853+
)
854+
@os_helper.skip_unless_symlink
855+
def test_extract_hardlink_on_symlink(self):
856+
"""
857+
This test verifies that extracting a hardlink will not follow an
858+
existing symlink after a FileExistsError on os.link.
859+
"""
860+
with os_helper.temp_dir() as DIR:
861+
target_filepath, hardlink_filepath = self._setup_symlink_to_target(DIR)
862+
with tarfile.open(tarname, encoding="iso8859-1") as tar:
863+
tar.extract("ustar/regtype", DIR, filter="data")
864+
tar.extract("ustar/lnktype", DIR, filter="data")
865+
self._assert_on_file_content(target_filepath, sha256sum(b"target"))
866+
self._assert_on_file_content(hardlink_filepath, sha256_regtype)
867+
868+
@unittest.skipUnless(
869+
hasattr(os, "link"), "Missing hardlink implementation"
870+
)
871+
@os_helper.skip_unless_symlink
872+
def test_extractall_hardlink_on_symlink(self):
873+
"""
874+
This test verifies that extracting a hardlink will not follow an
875+
existing symlink after a FileExistsError on os.link.
876+
"""
877+
with os_helper.temp_dir() as DIR:
878+
target_filepath, hardlink_filepath = self._setup_symlink_to_target(DIR)
879+
with tarfile.open(tarname, encoding="iso8859-1") as tar:
880+
tar.extractall(
881+
DIR, members=["ustar/regtype", "ustar/lnktype"], filter="data",
882+
)
883+
self._assert_on_file_content(target_filepath, sha256sum(b"target"))
884+
self._assert_on_file_content(hardlink_filepath, sha256_regtype)
885+
886+
836887
class MiscReadTest(MiscReadTestBase, unittest.TestCase):
837888
test_fail_comp = None
838889

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`tarfile.TarFile.extractall` and :func:`tarfile.TarFile.extract` now
2+
overwrite symlinks when extracting hardlinks.
3+
(Contributed by Alexander Enrique Urieles Nieto in :gh:`75989`.)

0 commit comments

Comments
 (0)