diff --git a/Lib/tarfile.py b/Lib/tarfile.py index e2b60532f693d4..7311fa1613c61e 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2232,6 +2232,8 @@ def makelink(self, tarinfo, targetpath): try: # For systems that support symbolic and hard links. if tarinfo.issym(): + if os.path.lexists(targetpath): + os.unlink(targetpath) os.symlink(tarinfo.linkname, targetpath) else: # See extract(). diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index cae96802ded67e..54916c47d6bb70 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -755,6 +755,18 @@ def test_compare_members(self): finally: tar1.close() + @support.skip_unless_symlink + def test_symlink_overwrite(self): + # Test for issue #40049: Extracting a symlink over an existing file + # in stream mode causes a backwards seek + sympath = os.path.join(TEMPDIR, 'symtype2') + pathlib.Path(sympath).touch() + try: + self.tar.extract('symtype2', TEMPDIR) + self.assertTrue(os.path.islink(sympath)) + finally: + pathlib.Path(sympath).unlink(missing_ok=True) + class GzipStreamReadTest(GzipTest, StreamReadTest): pass diff --git a/Misc/NEWS.d/next/Library/2020-03-27-20-49-32.bpo-40049.8079ca.rst b/Misc/NEWS.d/next/Library/2020-03-27-20-49-32.bpo-40049.8079ca.rst new file mode 100644 index 00000000000000..48cca7e5c0787d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-03-27-20-49-32.bpo-40049.8079ca.rst @@ -0,0 +1,3 @@ +The :mod:`tarfile` module now checks for an existing symlink before creating a +new one during extraction. This prevents premature scanning of the +archive and errors when extracting from a stream.