From b0e32e2b71633211be6a62d1afaa250fb23cd3b4 Mon Sep 17 00:00:00 2001 From: Neal Norwitz Date: Thu, 20 Oct 2005 04:50:13 +0000 Subject: [PATCH] Fix SF bug # 1330039, patch # 1331635 from Lars Gustaebel (tarfile maintainer) Problem: if two files are assigned the same inode number by the filesystem, the second one will be added as a hardlink to the first, which means that the content will be lost. The patched code checks if the file's st_nlink is greater 1. So only for files that actually have several links pointing to them hardlinks will be created, which is what GNU tar does. Will backport. --- Lib/tarfile.py | 3 ++- Lib/test/test_tarfile.py | 49 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 2f2197184c1..c86248c1a47 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -1150,7 +1150,8 @@ class TarFile(object): stmd = statres.st_mode if stat.S_ISREG(stmd): inode = (statres.st_ino, statres.st_dev) - if inode in self.inodes and not self.dereference: + if not self.dereference and \ + statres.st_nlink > 1 and inode in self.inodes: # Is it a hardlink to an already # archived file? type = LNKTYPE diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 66409cd1152..b202ea58687 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -372,6 +372,53 @@ class ExtractHardlinkTest(BaseTest): if e.errno == errno.ENOENT: self.fail("hardlink not extracted properly") +class CreateHardlinkTest(BaseTest): + """Test the creation of LNKTYPE (hardlink) members in an archive. + In this respect tarfile.py mimics the behaviour of GNU tar: If + a file has a st_nlink > 1, it will be added a REGTYPE member + only the first time. + """ + + def setUp(self): + self.tar = tarfile.open(tmpname(), "w") + + self.foo = os.path.join(dirname(), "foo") + self.bar = os.path.join(dirname(), "bar") + + if os.path.exists(self.foo): + os.remove(self.foo) + if os.path.exists(self.bar): + os.remove(self.bar) + + file(self.foo, "w").write("foo") + self.tar.add(self.foo) + + def test_add_twice(self): + # If st_nlink == 1 then the same file will be added as + # REGTYPE every time. + tarinfo = self.tar.gettarinfo(self.foo) + self.assertEqual(tarinfo.type, tarfile.REGTYPE, + "add file as regular failed") + + def test_add_hardlink(self): + # If st_nlink > 1 then the same file will be added as + # LNKTYPE. + os.link(self.foo, self.bar) + tarinfo = self.tar.gettarinfo(self.foo) + self.assertEqual(tarinfo.type, tarfile.LNKTYPE, + "add file as hardlink failed") + + tarinfo = self.tar.gettarinfo(self.bar) + self.assertEqual(tarinfo.type, tarfile.LNKTYPE, + "add file as hardlink failed") + + def test_dereference_hardlink(self): + self.tar.dereference = True + os.link(self.foo, self.bar) + tarinfo = self.tar.gettarinfo(self.bar) + self.assertEqual(tarinfo.type, tarfile.REGTYPE, + "dereferencing hardlink failed") + # Gzip TestCases class ReadTestGzip(ReadTest): @@ -387,7 +434,6 @@ class ReadAsteriskTestGzip(ReadAsteriskTest): class ReadStreamAsteriskTestGzip(ReadStreamAsteriskTest): comp = "gz" - # Filemode test cases class FileModeTest(unittest.TestCase): @@ -440,6 +486,7 @@ def test_main(): if hasattr(os, "link"): tests.append(ExtractHardlinkTest) + tests.append(CreateHardlinkTest) if gzip: tests.extend([