From e69ad87802139b7b62fc06ff5d5d09cc4245d5fc Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 9 Apr 2010 14:11:45 +0200 Subject: [PATCH] tar: fix "hardlinks to symlinks chown" bug 1519. Signed-off-by: Denys Vlasenko --- archival/libunarchive/data_extract_all.c | 102 ++++++++++---------- archival/libunarchive/header_verbose_list.c | 1 + testsuite/tar.tests | 29 ++++++ 3 files changed, 82 insertions(+), 50 deletions(-) diff --git a/archival/libunarchive/data_extract_all.c b/archival/libunarchive/data_extract_all.c index de2367ac2..815261036 100644 --- a/archival/libunarchive/data_extract_all.c +++ b/archival/libunarchive/data_extract_all.c @@ -96,58 +96,60 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) file_header->name, file_header->link_target); } - } else { - /* Create the filesystem entry */ - switch (file_header->mode & S_IFMT) { - case S_IFREG: { - /* Regular file */ - int flags = O_WRONLY | O_CREAT | O_EXCL; - if (archive_handle->ah_flags & ARCHIVE_O_TRUNC) - flags = O_WRONLY | O_CREAT | O_TRUNC; - dst_fd = xopen3(file_header->name, - flags, - file_header->mode - ); - bb_copyfd_exact_size(archive_handle->src_fd, dst_fd, file_header->size); - close(dst_fd); - break; + /* Hardlinks have no separate mode/ownership, skip chown/chmod */ + goto ret; + } + + /* Create the filesystem entry */ + switch (file_header->mode & S_IFMT) { + case S_IFREG: { + /* Regular file */ + int flags = O_WRONLY | O_CREAT | O_EXCL; + if (archive_handle->ah_flags & ARCHIVE_O_TRUNC) + flags = O_WRONLY | O_CREAT | O_TRUNC; + dst_fd = xopen3(file_header->name, + flags, + file_header->mode + ); + bb_copyfd_exact_size(archive_handle->src_fd, dst_fd, file_header->size); + close(dst_fd); + break; + } + case S_IFDIR: + res = mkdir(file_header->name, file_header->mode); + if ((res == -1) + && (errno != EISDIR) /* btw, Linux doesn't return this */ + && (errno != EEXIST) + && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("can't make dir %s", file_header->name); } - case S_IFDIR: - res = mkdir(file_header->name, file_header->mode); - if ((res == -1) - && (errno != EISDIR) /* btw, Linux doesn't return this */ - && (errno != EEXIST) - && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) - ) { - bb_perror_msg("can't make dir %s", file_header->name); - } - break; - case S_IFLNK: - /* Symlink */ - res = symlink(file_header->link_target, file_header->name); - if ((res == -1) - && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) - ) { - bb_perror_msg("can't create %slink " - "from %s to %s", "sym", - file_header->name, - file_header->link_target); - } - break; - case S_IFSOCK: - case S_IFBLK: - case S_IFCHR: - case S_IFIFO: - res = mknod(file_header->name, file_header->mode, file_header->device); - if ((res == -1) - && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) - ) { - bb_perror_msg("can't create node %s", file_header->name); - } - break; - default: - bb_error_msg_and_die("unrecognized file type"); + break; + case S_IFLNK: + /* Symlink */ + res = symlink(file_header->link_target, file_header->name); + if ((res == -1) + && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("can't create %slink " + "from %s to %s", "sym", + file_header->name, + file_header->link_target); } + break; + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + res = mknod(file_header->name, file_header->mode, file_header->device); + if ((res == -1) + && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("can't create node %s", file_header->name); + } + break; + default: + bb_error_msg_and_die("unrecognized file type"); } if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_OWNER)) { diff --git a/archival/libunarchive/header_verbose_list.c b/archival/libunarchive/header_verbose_list.c index f6f04cfd5..3319e63a9 100644 --- a/archival/libunarchive/header_verbose_list.c +++ b/archival/libunarchive/header_verbose_list.c @@ -61,6 +61,7 @@ void FAST_FUNC header_verbose_list(const file_header_t *file_header) #endif /* FEATURE_TAR_UNAME_GNAME */ + /* NB: GNU tar shows "->" for symlinks and "link to" for hardlinks */ if (file_header->link_target) { printf(" -> %s", file_header->link_target); } diff --git a/testsuite/tar.tests b/testsuite/tar.tests index dd8f11062..a96382932 100755 --- a/testsuite/tar.tests +++ b/testsuite/tar.tests @@ -69,6 +69,35 @@ dr-xr-x--- input_dir " \ "" "" +testing "tar symlinks mode" '\ +rm -rf input_* test.tar 2>/dev/null +>input_file +chmod 741 input_file +ln -s input_file input_soft +mkdir input_dir +chmod 550 input_dir +ln input_file input_dir +ln input_soft 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_file +input_dir/input_soft -> input_file +input_file -> input_dir/input_file +input_soft -> input_dir/input_soft +Ok: 0 +-rwxr----x input_dir/input_file +lrwxrwxrwx input_file +dr-xr-x--- input_dir +-rwxr----x input_file +lrwxrwxrwx input_file +" \ +"" "" + optional FEATURE_TAR_LONG_OPTIONS testing "tar --overwrite" "\ rm -rf input_* test.tar 2>/dev/null