tar: fix mishandling of repeated hardlink in tarball; expand tests
No binary (build failed)
—
(log yok)
| @@ -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 | |
| @@ -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 { | |
| @@ -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 | |