diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 2c06f9160c658a..6837a4e7e9ee99 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2226,6 +2226,9 @@ def makelink(self, tarinfo, targetpath): try: # For systems that support symbolic and hard links. if tarinfo.issym(): + if os.path.islink(targetpath) or os.path.isfile(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 7e32cbccd6c56d..cd3c9724d2f832 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -2498,6 +2498,70 @@ def test_partial_input_bz2(self): self._test_partial_input("r:bz2") +class SymlinkOverwriteTest(unittest.TestCase): + # The testcase checks for correct overwriting of an + # existing symlink (issue #12800) + + @support.skip_unless_symlink + def test_overwrite_symlink(self): + tmpdir = support.temp_cwd('overwrite_symlink') + source = 'source' + link = 'link' + try: + with open(source, 'wb'): + pass + os.symlink(source, link) + with tarfile.open(tmpname, 'w') as tar: + tar.add(source, arcname=os.path.basename(source)) + tar.add(link, arcname=os.path.basename(link)) + + with open(tmpname, 'rb') as fileobj: + with tarfile.open(fileobj=fileobj, mode='r|') as tar: + tar.extractall(path=support.SAVEDCWD) + finally: + try: + os.unlink(link) + except: + pass + + try: + os.unlink(source) + except: + pass + + @support.skip_unless_symlink + def test_overwrite_file_with_symlink(self): + tmpdir = support.temp_cwd('overwrite_file_with_symlink') + source = 'source' + link = 'link' + try: + with open(source, 'wb'): + pass + os.symlink(source, link) + with tarfile.open(tmpname, 'w') as tar: + tar.add(source, arcname=os.path.basename(source)) + tar.add(link, arcname=os.path.basename(link)) + + os.unlink(link) + + with open(link, 'wb'): + pass + + with open(tmpname, 'rb') as fileobj: + with tarfile.open(fileobj=fileobj, mode='r|') as tar: + tar.extractall(path=support.SAVEDCWD) + finally: + try: + os.unlink(link) + except: + pass + + try: + os.unlink(source) + except: + pass + + def root_is_uid_gid_0(): try: import pwd, grp diff --git a/Misc/NEWS.d/next/Library/2019-05-09-18-37-37.bpo-12800.TyjRQq.rst b/Misc/NEWS.d/next/Library/2019-05-09-18-37-37.bpo-12800.TyjRQq.rst new file mode 100644 index 00000000000000..a1208cb176746a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-05-09-18-37-37.bpo-12800.TyjRQq.rst @@ -0,0 +1 @@ +Extracting a symlink from a tarball using the tarfile module in stream mode ('r|') fails with an exception when a file or symlink of the same name already exists. The fix is to remove the existing file or symlink before extraction. \ No newline at end of file