tar: fix mishandling of repeated hardlink in tarball; expand tests
authorDenys Vlasenko <vda.linux@googlemail.com>
Fri, 9 Apr 2010 08:52:52 +0000 (10:52 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 9 Apr 2010 08:52:52 +0000 (10:52 +0200)
function                                             old     new   delta
data_extract_all                                     727     767     +40
get_header_tar                                      1576    1572      -4

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
archival/libunarchive/data_extract_all.c
archival/libunarchive/get_header_tar.c
testsuite/tar.tests

index cc4894229b5e7ba6ec24d91aebf7cc9f9c4f9515..de2367ac260054ab37ff0e9972819b03e6ab3473 100644 (file)
@@ -34,12 +34,30 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
 
        if (archive_handle->ah_flags & ARCHIVE_UNLINK_OLD) {
                /* Remove the entry if it exists */
-               if ((!S_ISDIR(file_header->mode))
-                && (unlink(file_header->name) == -1)
-                && (errno != ENOENT)
-               ) {
-                       bb_perror_msg_and_die("can't remove old file %s",
-                                       file_header->name);
+               if (!S_ISDIR(file_header->mode)) {
+                       /* Is it hardlink?
+                        * We encode hard links as regular files of size 0 with a symlink */
+                       if (S_ISREG(file_header->mode)
+                        && file_header->link_target
+                        && file_header->size == 0
+                       ) {
+                               /* Ugly special case:
+                                * tar cf t.tar hardlink1 hardlink2 hardlink1
+                                * results in this tarball structure:
+                                * hardlink1
+                                * hardlink2 -> hardlink1
+                                * hardlink1 -> hardlink1 <== !!!
+                                */
+                               if (strcmp(file_header->link_target, file_header->name) == 0)
+                                       goto ret;
+                       }
+                       /* Proceed with deleting */
+                       if (unlink(file_header->name) == -1
+                        && errno != ENOENT
+                       ) {
+                               bb_perror_msg_and_die("can't remove old file %s",
+                                               file_header->name);
+                       }
                }
        }
        else if (archive_handle->ah_flags & ARCHIVE_EXTRACT_NEWER) {
@@ -65,7 +83,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
        }
 
        /* Handle hard links separately
-        * We identified hard links as regular files of size 0 with a symlink */
+        * We encode hard links as regular files of size 0 with a symlink */
        if (S_ISREG(file_header->mode)
         && file_header->link_target
         && file_header->size == 0
index cf0b9ab028a00c248ca9fe0d2e85e4a6b14281b1..adb4c157b4996184044b0ad07a8cd6537447cbbf 100644 (file)
@@ -507,8 +507,9 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle)
                archive_handle->action_header(/*archive_handle->*/ file_header);
                /* Note that we kill the '/' only after action_header() */
                /* (like GNU tar 1.15.1: verbose mode outputs "dir/dir/") */
-               if (cp) *cp = '\0';
-               archive_handle->ah_flags |= ARCHIVE_EXTRACT_QUIET;
+               if (cp)
+                       *cp = '\0';
+               //archive_handle->ah_flags |= ARCHIVE_EXTRACT_QUIET; // why??
                archive_handle->action_data(archive_handle);
                llist_add_to(&(archive_handle->passed), file_header->name);
        } else {
index 71095cb20514d326c376a32801deb1558e1996c3..dd8f110620c717ac821c48e0e41bba81676d6d9e 100755 (executable)
@@ -4,20 +4,25 @@
 
 . ./testing.sh
 
-mkdir tempdir && cd tempdir || exit 1
+rm -rf tar.tempdir 2>/dev/null
+mkdir tar.tempdir && cd tar.tempdir || exit 1
 
 # testing "test name" "script" "expected result" "file input" "stdin"
 
-testing "tar hardlinks and repeated files" "\
+testing "tar hardlinks and repeated files" '\
 rm -rf input_* test.tar 2>/dev/null
 >input_hard1
 ln input_hard1 input_hard2
 mkdir input_dir
 >input_dir/file
+chmod -R 644 *
+chmod    755 input_dir
 tar cf test.tar input input_dir/ input_hard1 input_hard2 input_hard1 input_dir/ input
-tar tvf test.tar | sed 's/.*[0-9] input/input/'
-tar xf test.tar 2>&1 && echo Ok
-" "\
+tar tvf test.tar | sed "s/.*[0-9] input/input/"
+tar xf test.tar 2>&1
+echo Ok: $?
+ls -l . input_dir/* | grep input_ | sed "s/\\(^[^ ]*\\) .* input/\\1 input/"
+' "\
 input
 input_dir/
 input_dir/file
@@ -27,7 +32,40 @@ input_hard1 -> input_hard1
 input_dir/
 input_dir/file
 input
-Ok
+Ok: 0
+-rw-r--r-- input_dir/file
+drwxr-xr-x input_dir
+-rw-r--r-- input_hard1
+-rw-r--r-- input_hard2
+" \
+"" ""
+
+testing "tar hardlinks mode" '\
+rm -rf input_* test.tar 2>/dev/null
+>input_hard1
+chmod 741 input_hard1
+ln input_hard1 input_hard2
+mkdir input_dir
+chmod 550 input_dir
+ln input_hard1 input_dir
+ln input_hard2 input_dir
+tar cf test.tar input_*
+tar tvf test.tar | sed "s/.*[0-9] input/input/"
+tar xf test.tar 2>&1
+echo Ok: $?
+ls -l . input_dir/* | grep input_ | sed "s/\\(^[^ ]*\\) .* input/\\1 input/"
+' "\
+input_dir/
+input_dir/input_hard1
+input_dir/input_hard2 -> input_dir/input_hard1
+input_hard1 -> input_dir/input_hard1
+input_hard2 -> input_dir/input_hard1
+Ok: 0
+-rwxr----x input_dir/input_hard1
+-rwxr----x input_dir/input_hard2
+dr-xr-x--- input_dir
+-rwxr----x input_hard1
+-rwxr----x input_hard2
 " \
 "" ""
 
@@ -46,6 +84,6 @@ Ok
 "Ok\n" ""
 SKIP=
 
-cd .. && rm -rf tempdir || exit 1
+cd .. && rm -rf tar.tempdir || exit 1
 
 exit $FAILCOUNT