]> granicus.if.org Git - python/commitdiff
Fix SF bug # 1330039, patch # 1331635 from Lars Gustaebel (tarfile maintainer)
authorNeal Norwitz <nnorwitz@gmail.com>
Thu, 20 Oct 2005 04:50:13 +0000 (04:50 +0000)
committerNeal Norwitz <nnorwitz@gmail.com>
Thu, 20 Oct 2005 04:50:13 +0000 (04:50 +0000)
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
Lib/test/test_tarfile.py

index 2f2197184c1939aa170beaf9d4d07f5c82954ba5..c86248c1a4749f4854a98d3b8c2fa58445971fd4 100644 (file)
@@ -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
index 66409cd1152e520c21f7f238268ae7a41fa10bf1..b202ea586873fd58385d20055c4c65c736ad1d8d 100644 (file)
@@ -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([