From c5e9786e6b2faf6767d4208f48cbb71f5ab881cb Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Sat, 13 Sep 2008 18:55:05 -0400 Subject: [PATCH] IFC SVN-Revision: 203 --- configure.ac | 2 + libarchive/archive_write_disk.c | 13 +-- tar/config_freebsd.h | 3 +- tar/test/Makefile | 3 +- tar/test/test_symlink_dir.c | 172 ++++++++++++++++++++++++++++++++ tar/write.c | 59 +++++------ 6 files changed, 215 insertions(+), 37 deletions(-) create mode 100644 tar/test/test_symlink_dir.c diff --git a/configure.ac b/configure.ac index 0fb7d37c6..7cdba25f6 100644 --- a/configure.ac +++ b/configure.ac @@ -195,6 +195,8 @@ AC_CHECK_MEMBERS([struct stat.st_mtimespec.tv_nsec]) AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec]) # Check for block size support in struct stat AC_CHECK_MEMBERS([struct stat.st_blksize]) +# Check for st_flags in struct stat (BSD fflags) +AC_CHECK_MEMBERS([struct stat.st_flags]) # If you have uintmax_t, we assume printf supports %ju # If you have unsigned long long, we assume printf supports %llu diff --git a/libarchive/archive_write_disk.c b/libarchive/archive_write_disk.c index 7a301ffe3..11ef08aa3 100644 --- a/libarchive/archive_write_disk.c +++ b/libarchive/archive_write_disk.c @@ -1963,7 +1963,10 @@ set_fflags(struct archive_write_disk *a) } -#if ( defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && !defined(__linux) +#if ( defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && defined(HAVE_STRUCT_STAT_ST_FLAGS) +/* + * BSD reads flags using stat() and sets them with one of {f,l,}chflags() + */ static int set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, mode_t mode, unsigned long set, unsigned long clear) @@ -2012,11 +2015,9 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, return (ARCHIVE_WARN); } -#elif defined(__linux) && defined(EXT2_IOC_GETFLAGS) && defined(EXT2_IOC_SETFLAGS) - +#elif defined(EXT2_IOC_GETFLAGS) && defined(EXT2_IOC_SETFLAGS) /* - * Linux has flags too, but uses ioctl() to access them instead of - * having a separate chflags() system call. + * Linux uses ioctl() to read and write file flags. */ static int set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, @@ -2084,7 +2085,7 @@ cleanup: return (ret); } -#else /* Not HAVE_CHFLAGS && Not __linux */ +#else /* * Of course, some systems have neither BSD chflags() nor Linux' flags diff --git a/tar/config_freebsd.h b/tar/config_freebsd.h index 69790d8f5..25aceea71 100644 --- a/tar/config_freebsd.h +++ b/tar/config_freebsd.h @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/usr.bin/tar/config_freebsd.h,v 1.5 2008/07/05 02:09:13 kientzle Exp $ + * $FreeBSD: src/usr.bin/tar/config_freebsd.h,v 1.6 2008/09/14 03:49:00 kientzle Exp $ */ /* A default configuration for FreeBSD, used if there is no config.h. */ @@ -89,6 +89,7 @@ #define HAVE_STRINGS_H 1 #define HAVE_STRING_H 1 #define HAVE_STRRCHR 1 +#define HAVE_STRUCT_STAT_ST_FLAGS 1 #undef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC #define HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1 #define HAVE_SYS_ACL_H 1 diff --git a/tar/test/Makefile b/tar/test/Makefile index 079210f2a..ac5c56f52 100644 --- a/tar/test/Makefile +++ b/tar/test/Makefile @@ -1,4 +1,4 @@ -# $FreeBSD: src/usr.bin/tar/test/Makefile,v 1.3 2008/08/22 01:22:55 kientzle Exp $ +# $FreeBSD: src/usr.bin/tar/test/Makefile,v 1.4 2008/09/14 02:16:04 kientzle Exp $ # Where to find the tar sources (for the internal unit tests) TAR_SRCDIR=${.CURDIR}/.. @@ -18,6 +18,7 @@ TESTS= \ test_option_q.c \ test_patterns.c \ test_stdio.c \ + test_symlink_dir.c \ test_version.c # Build the test program diff --git a/tar/test/test_symlink_dir.c b/tar/test/test_symlink_dir.c new file mode 100644 index 000000000..569da65a5 --- /dev/null +++ b/tar/test/test_symlink_dir.c @@ -0,0 +1,172 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/test/test_symlink_dir.c,v 1.1 2008/09/14 02:16:04 kientzle Exp $"); + +/* + * tar -x -P should follow existing symlinks for dirs, but not other + * content. Plain tar -x should remove symlinks when they're in the + * way of a dir extraction. + */ + +static int +mkfile(const char *name, int mode, const char *contents, ssize_t size) +{ + int fd = open(name, O_CREAT | O_WRONLY, mode); + if (fd < 0) + return (-1); + if (size != write(fd, contents, size)) { + close(fd); + return (-1); + } + close(fd); + return (0); +} + +DEFINE_TEST(test_symlink_dir) +{ + struct stat st, st2; + int oldumask; + + oldumask = umask(0); + + assertEqualInt(0, mkdir("source", 0755)); + assertEqualInt(0, mkfile("source/file", 0755, "a", 1)); + assertEqualInt(0, mkfile("source/file2", 0755, "ab", 2)); + assertEqualInt(0, mkdir("source/dir", 0755)); + assertEqualInt(0, mkdir("source/dir/d", 0755)); + assertEqualInt(0, mkfile("source/dir/f", 0755, "abc", 3)); + assertEqualInt(0, mkdir("source/dir2", 0755)); + assertEqualInt(0, mkdir("source/dir2/d2", 0755)); + assertEqualInt(0, mkfile("source/dir2/f2", 0755, "abcd", 4)); + assertEqualInt(0, mkdir("source/dir3", 0755)); + assertEqualInt(0, mkdir("source/dir3/d3", 0755)); + assertEqualInt(0, mkfile("source/dir3/f3", 0755, "abcde", 5)); + + assertEqualInt(0, + systemf("%s -cf test.tar -C source dir dir2 dir3 file file2", + testprog)); + + /* + * Extract with -x and without -P. + */ + assertEqualInt(0, mkdir("dest1", 0755)); + /* "dir" is a symlink to an existing "real_dir" */ + assertEqualInt(0, mkdir("dest1/real_dir", 0755)); + assertEqualInt(0, symlink("real_dir", "dest1/dir")); + /* "dir2" is a symlink to a non-existing "real_dir2" */ + assertEqualInt(0, symlink("real_dir2", "dest1/dir2")); + /* "dir3" is a symlink to an existing "non_dir3" */ + assertEqualInt(0, mkfile("dest1/non_dir3", 0755, "abcdef", 6)); + assertEqualInt(0, symlink("non_dir3", "dest1/dir3")); + /* "file" is a symlink to existing "real_file" */ + assertEqualInt(0, mkfile("dest1/real_file", 0755, "abcdefg", 7)); + assertEqualInt(0, symlink("real_file", "dest1/file")); + /* "file2" is a symlink to non-existing "real_file2" */ + assertEqualInt(0, symlink("real_file2", "dest1/file2")); + + assertEqualInt(0, systemf("%s -xf test.tar -C dest1", testprog)); + + /* dest1/dir symlink should be removed */ + assertEqualInt(0, lstat("dest1/dir", &st)); + failure("symlink to dir was followed when it shouldn't be"); + assert(S_ISDIR(st.st_mode)); + /* dest1/dir2 symlink should be removed */ + assertEqualInt(0, lstat("dest1/dir2", &st)); + failure("Broken symlink wasn't replaced with dir"); + assert(S_ISDIR(st.st_mode)); + /* dest1/dir3 symlink should be removed */ + assertEqualInt(0, lstat("dest1/dir3", &st)); + failure("Symlink to non-dir wasn't replaced with dir"); + assert(S_ISDIR(st.st_mode)); + /* dest1/file symlink should be removed */ + assertEqualInt(0, lstat("dest1/file", &st)); + failure("Symlink to existing file should be removed"); + assert(S_ISREG(st.st_mode)); + /* dest1/file2 symlink should be removed */ + assertEqualInt(0, lstat("dest1/file2", &st)); + failure("Symlink to non-existing file should be removed"); + assert(S_ISREG(st.st_mode)); + + /* + * Extract with both -x and -P + */ + assertEqualInt(0, mkdir("dest2", 0755)); + /* "dir" is a symlink to existing "real_dir" */ + assertEqualInt(0, mkdir("dest2/real_dir", 0755)); + assertEqualInt(0, symlink("real_dir", "dest2/dir")); + /* "dir2" is a symlink to a non-existing "real_dir2" */ + assertEqualInt(0, symlink("real_dir2", "dest2/dir2")); + /* "dir3" is a symlink to an existing "non_dir3" */ + assertEqualInt(0, mkfile("dest2/non_dir3", 0755, "abcdefgh", 8)); + assertEqualInt(0, symlink("non_dir3", "dest2/dir3")); + /* "file" is a symlink to existing "real_file" */ + assertEqualInt(0, mkfile("dest2/real_file", 0755, "abcdefghi", 9)); + assertEqualInt(0, symlink("real_file", "dest2/file")); + /* "file2" is a symlink to non-existing "real_file2" */ + assertEqualInt(0, symlink("real_file2", "dest2/file2")); + + assertEqualInt(0, systemf("%s -xPf test.tar -C dest2", testprog)); + + /* dest2/dir symlink should be followed */ + assertEqualInt(0, lstat("dest2/dir", &st)); + failure("tar -xP removed symlink instead of following it"); + if (assert(S_ISLNK(st.st_mode))) { + /* Only verify what the symlink points to if it + * really is a symlink. */ + failure("The symlink should point to a directory"); + assertEqualInt(0, stat("dest2/dir", &st)); + assert(S_ISDIR(st.st_mode)); + failure("The pre-existing directory should still be there"); + assertEqualInt(0, lstat("dest2/real_dir", &st2)); + assert(S_ISDIR(st2.st_mode)); + assertEqualInt(st.st_dev, st2.st_dev); + failure("symlink should still point to the existing directory"); + assertEqualInt(st.st_ino, st2.st_ino); + } + /* Contents of 'dir' should be restored */ + assertEqualInt(0, lstat("dest2/dir/d", &st)); + assert(S_ISDIR(st.st_mode)); + assertEqualInt(0, lstat("dest2/dir/f", &st)); + assert(S_ISREG(st.st_mode)); + assertEqualInt(3, st.st_size); + /* dest2/dir2 symlink should be removed */ + assertEqualInt(0, lstat("dest2/dir2", &st)); + failure("Broken symlink wasn't replaced with dir"); + assert(S_ISDIR(st.st_mode)); + /* dest2/dir3 symlink should be removed */ + assertEqualInt(0, lstat("dest2/dir3", &st)); + failure("Symlink to non-dir wasn't replaced with dir"); + assert(S_ISDIR(st.st_mode)); + /* dest2/file symlink should be removed; + * even -P shouldn't follow symlinks for files */ + assertEqualInt(0, lstat("dest2/file", &st)); + failure("Symlink to existing file should be removed"); + assert(S_ISREG(st.st_mode)); + /* dest2/file2 symlink should be removed */ + assertEqualInt(0, lstat("dest2/file2", &st)); + failure("Symlink to non-existing file should be removed"); + assert(S_ISREG(st.st_mode)); +} diff --git a/tar/write.c b/tar/write.c index d038fac9e..fca625688 100644 --- a/tar/write.c +++ b/tar/write.c @@ -24,7 +24,7 @@ */ #include "bsdtar_platform.h" -__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.76 2008/07/05 08:10:55 cperciva Exp $"); +__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.77 2008/09/14 03:49:00 kientzle Exp $"); #ifdef HAVE_SYS_TYPES_H #include @@ -641,10 +641,6 @@ write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path) dev_t first_dev = 0; int dev_recorded = 0; int tree_ret; -#ifdef __linux - int fd, r; - unsigned long fflags; -#endif tree = tree_open(path); @@ -707,23 +703,25 @@ write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path) * If this file/dir is flagged "nodump" and we're * honoring such flags, skip this file/dir. */ -#ifdef HAVE_CHFLAGS +#ifdef HAVE_STRUCT_STAT_ST_FLAGS + /* BSD systems store flags in struct stat */ if (bsdtar->option_honor_nodump && (lst->st_flags & UF_NODUMP)) continue; #endif -#ifdef __linux - /* - * Linux has a nodump flag too but to read it - * we have to open() the file/dir and do an ioctl on it... - */ - if (bsdtar->option_honor_nodump && - ((fd = open(name, O_RDONLY|O_NONBLOCK)) >= 0) && - ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags)), - close(fd), r) >= 0 && - (fflags & EXT2_NODUMP_FL)) - continue; +#if defined(EXT2_IOC_GETFLAGS) && defined(EXT2_NODUMP_FL) + /* Linux uses ioctl to read flags. */ + if (bsdtar->option_honor_nodump) { + int fd = open(name, O_RDONLY | O_NONBLOCK); + if (fd >= 0) { + unsigned long fflags; + int r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags); + close(fd); + if (r >= 0 && (fflags & EXT2_NODUMP_FL)) + continue; + } + } #endif /* @@ -849,10 +847,6 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, const char *pathname, const char *accpath) { struct archive_entry *entry, *sparse_entry; -#ifdef __linux - int r; - unsigned long stflags; -#endif static char linkbuffer[PATH_MAX+1]; entry = archive_entry_new(); @@ -902,18 +896,25 @@ write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, archive_entry_set_uname(entry, lookup_uname(bsdtar, st->st_uid)); archive_entry_set_gname(entry, lookup_gname(bsdtar, st->st_gid)); -#ifdef HAVE_CHFLAGS +#ifdef HAVE_STRUCT_STAT_ST_FLAGS + /* XXX TODO: archive_entry_copy_stat() should copy st_flags + * on platforms that support it. This should go away then. */ if (st->st_flags != 0) archive_entry_set_fflags(entry, st->st_flags, 0); #endif -#ifdef __linux - int fd; - if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode)) && - ((fd = open(accpath, O_RDONLY|O_NONBLOCK)) >= 0) && - ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags)), close(fd), (fd = -1), r) >= 0 && - stflags) { - archive_entry_set_fflags(entry, stflags, 0); +#ifdef EXT2_IOC_GETFLAGS + /* XXX TODO: Find a way to merge this with the + * flags fetch for nodump support earlier. */ + if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode))) { + int fd = open(accpath, O_RDONLY | O_NONBLOCK); + if (fd >= 0) { + unsigned long stflags; + int r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags); + close(fd); + if (r == 0 && stflags != 0) + archive_entry_set_fflags(entry, stflags, 0); + } } #endif -- 2.47.3