]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Write side of FreeBSD-specific extended attribute support.
authorTim Kientzle <kientzle@gmail.com>
Mon, 12 Jan 2009 05:14:24 +0000 (00:14 -0500)
committerTim Kientzle <kientzle@gmail.com>
Mon, 12 Jan 2009 05:14:24 +0000 (00:14 -0500)
SVN-Revision: 416

configure.ac
libarchive/archive_write_disk.c
libarchive/config_freebsd.h
libarchive/test/Makefile
libarchive/test/test_extattr_freebsd.c [new file with mode: 0644]

index 1afe4abb23a5118374c1df42ff02bd07653098b1..dd530e64dc595bb2a73f5b45a3c95e86a34197ca 100644 (file)
@@ -165,7 +165,8 @@ AC_HEADER_SYS_WAIT
 AC_CHECK_HEADERS([errno.h ext2fs/ext2_fs.h fcntl.h  grp.h])
 AC_CHECK_HEADERS([inttypes.h langinfo.h limits.h linux/fs.h])
 AC_CHECK_HEADERS([locale.h paths.h poll.h pwd.h regex.h stdarg.h])
-AC_CHECK_HEADERS([stdint.h stdlib.h string.h sys/acl.h sys/ioctl.h])
+AC_CHECK_HEADERS([stdint.h stdlib.h string.h])
+AC_CHECK_HEADERS([sys/acl.h sys/extattr.h sys/ioctl.h])
 AC_CHECK_HEADERS([sys/param.h sys/poll.h sys/select.h sys/time.h sys/utime.h])
 AC_CHECK_HEADERS([time.h unistd.h utime.h wchar.h wctype.h])
 
@@ -258,7 +259,8 @@ AC_FUNC_STAT
 AC_FUNC_STRERROR_R
 AC_FUNC_STRFTIME
 AC_FUNC_VPRINTF
-AC_CHECK_FUNCS([chflags chown chroot fchdir fchflags fchmod fchown fcntl fork])
+AC_CHECK_FUNCS([chflags chown chroot extattr_set_fd extattr_set_file])
+AC_CHECK_FUNCS([fchdir fchflags fchmod fchown fcntl fork])
 AC_CHECK_FUNCS([fstat ftruncate futimes geteuid getpid])
 AC_CHECK_FUNCS([lchflags lchmod lchown])
 AC_CHECK_FUNCS([lutimes memmove memset mkdir mkfifo mknod])
index c1ddf77e734400d771fab11246ab25c436a53706..38f780b83693663130b9e16b50ea75d3b27900a9 100644 (file)
@@ -33,6 +33,9 @@ __FBSDID("$FreeBSD: src/lib/libarchive/archive_write_disk.c,v 1.42 2008/12/06 05
 #ifdef HAVE_SYS_ACL_H
 #include <sys/acl.h>
 #endif
+#ifdef HAVE_SYS_EXTATTR_H
+#include <sys/extattr.h>
+#endif
 #ifdef HAVE_ATTR_XATTR_H
 #include <attr/xattr.h>
 #endif
@@ -411,6 +414,8 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
                a->todo |= TODO_TIMES;
        if (a->flags & ARCHIVE_EXTRACT_ACL)
                a->todo |= TODO_ACLS;
+       if (a->flags & ARCHIVE_EXTRACT_XATTR)
+               a->todo |= TODO_XATTR;
        if (a->flags & ARCHIVE_EXTRACT_FFLAGS)
                a->todo |= TODO_FFLAGS;
        if (a->flags & ARCHIVE_EXTRACT_SECURE_SYMLINKS) {
@@ -425,6 +430,17 @@ _archive_write_header(struct archive *_a, struct archive_entry *entry)
 
        ret = restore_entry(a);
 
+       /*
+        * On the GNU tar mailing list, some people working with new
+        * Linux filesystems observed that system xattrs used as
+        * layout hints need to be restored before the file contents
+        * are written, so this can't be done at file close.
+        */
+       if (a->todo & TODO_XATTR) {
+               int r2 = set_xattrs(a);
+               if (r2 < ret) ret = r2;
+       }
+
 #ifdef HAVE_FCHDIR
        /* If we changed directory above, restore it here. */
        if (a->restore_pwd >= 0) {
@@ -716,14 +732,18 @@ _archive_write_finish_entry(struct archive *_a)
                int r2 = set_acls(a);
                if (r2 < ret) ret = r2;
        }
-       if (a->todo & TODO_XATTR) {
-               int r2 = set_xattrs(a);
-               if (r2 < ret) ret = r2;
-       }
+       /*
+        * Some flags prevent file modification; they must be restored after
+        * file contents are written.
+        */
        if (a->todo & TODO_FFLAGS) {
                int r2 = set_fflags(a);
                if (r2 < ret) ret = r2;
        }
+       /*
+        * Time has to be restored after all other metadata;
+        * otherwise atime will get changed.
+        */
        if (a->todo & TODO_TIMES) {
                int r2 = set_times(a);
                if (r2 < ret) ret = r2;
@@ -1008,7 +1028,8 @@ restore_entry(struct archive_write_disk *a)
 
        if (en) {
                /* Everything failed; give up here. */
-               archive_set_error(&a->archive, en, "Can't create '%s'", a->name);
+               archive_set_error(&a->archive, en, "Can't create '%s'",
+                   a->name);
                return (ARCHIVE_WARN);
        }
 
@@ -1709,7 +1730,8 @@ create_dir(struct archive_write_disk *a, char *path)
        if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
                return (ARCHIVE_OK);
 
-       archive_set_error(&a->archive, errno, "Failed to create dir '%s'", path);
+       archive_set_error(&a->archive, errno, "Failed to create dir '%s'",
+           path);
        return (ARCHIVE_WARN);
 }
 
@@ -2378,6 +2400,71 @@ set_xattrs(struct archive_write_disk *a)
        }
        return (ret);
 }
+#elif HAVE_EXTATTR_SET_FILE
+/*
+ * Restore extended attributes -  FreeBSD implementation
+ */
+static int
+set_xattrs(struct archive_write_disk *a)
+{
+       struct archive_entry *entry = a->entry;
+       static int warning_done = 0;
+       int ret = ARCHIVE_OK;
+       int i = archive_entry_xattr_reset(entry);
+
+       while (i--) {
+               const char *name;
+               const void *value;
+               size_t size;
+               archive_entry_xattr_next(entry, &name, &value, &size);
+               if (name != NULL) {
+                       int e;
+                       int namespace;
+
+                       if (strncmp(name, "user.", 5) == 0) {
+                               /* "user." attributes go to user namespace */
+                               name += 5;
+                               namespace = EXTATTR_NAMESPACE_USER;
+                       } else {
+                               /* Warn about other extended attributes. */
+                               archive_set_error(&a->archive,
+                                   ARCHIVE_ERRNO_FILE_FORMAT,
+                                   "Can't restore extended attribute ``%s''",
+                                   name);
+                               ret = ARCHIVE_WARN;
+                               continue;
+                       }
+                       errno = 0;
+#if HAVE_EXTATTR_SET_FD
+                       if (a->fd >= 0)
+                               e = extattr_set_fd(a->fd, namespace, name, value, size);
+                       else
+#endif
+                       /* TODO: should we use extattr_set_link() instead? */
+                       {
+                               e = extattr_set_file(archive_entry_pathname(entry),
+                                   namespace, name, value, size);
+                       }
+                       if (e != (int)size) {
+                               if (errno == ENOTSUP) {
+                                       if (!warning_done) {
+                                               warning_done = 1;
+                                               archive_set_error(&a->archive, errno,
+                                                   "Cannot restore extended "
+                                                   "attributes on this file "
+                                                   "system");
+                                       }
+                               } else {
+                                       archive_set_error(&a->archive, errno,
+                                           "Failed to set extended attribute");
+                               }
+
+                               ret = ARCHIVE_WARN;
+                       }
+               }
+       }
+       return (ret);
+}
 #else
 /*
  * Restore extended attributes - stub implementation for unsupported systems
index c4c1380c100ecd027d35f58fae4102df1df2b91a..a559950ee25bc900efe2e1bbf9263a0ef739b636 100644 (file)
@@ -25,7 +25,7 @@
  * $FreeBSD: src/lib/libarchive/config_freebsd.h,v 1.15 2008/09/30 03:53:03 kientzle Exp $
  */
 
-/* FreeBSD 5.0 and later have ACL support. */
+/* FreeBSD 5.0 and later have ACL and extattr support. */
 #if __FreeBSD__ > 4
 #define        HAVE_ACL_CREATE_ENTRY 1
 #define        HAVE_ACL_GET_PERM_NP 1
 #define        HAVE_ACL_SET_FD_NP 1
 #define        HAVE_ACL_SET_FILE 1
 #define        HAVE_ACL_USER 1
+#define        HAVE_EXTATTR_SET_FD 1
+#define        HAVE_EXTATTR_SET_FILE 1
+#define        HAVE_SYS_ACL_H 1
+#define        HAVE_SYS_EXTATTR_H 1
 #endif
 
 #define        HAVE_BZLIB_H 1
@@ -94,7 +98,6 @@
 #define        HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC 1
 #define        HAVE_STRUCT_STAT_ST_FLAGS 1
 #define        HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1
-#define        HAVE_SYS_ACL_H 1
 #define        HAVE_SYS_IOCTL_H 1
 #define        HAVE_SYS_SELECT_H 1
 #define        HAVE_SYS_STAT_H 1
index f744a3401a5eca250a94c2a00bac2a7601f50206..52434da2ea26b3dd9b30a380769421c6605fb85f 100644 (file)
@@ -21,6 +21,7 @@ TESTS= \
        test_empty_write.c                      \
        test_entry.c                            \
        test_entry_strmode.c                    \
+       test_extattr_freebsd.c                  \
        test_fuzz.c                             \
        test_link_resolver.c                    \
        test_pax_filename_encoding.c            \
diff --git a/libarchive/test/test_extattr_freebsd.c b/libarchive/test/test_extattr_freebsd.c
new file mode 100644 (file)
index 0000000..72575dd
--- /dev/null
@@ -0,0 +1,154 @@
+/*-
+ * Copyright (c) 2003-2009 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$");
+
+#if defined(__FreeBSD__) && __FreeBSD__ > 4
+#include <sys/extattr.h>
+#endif
+
+/*
+ * Verify extended attribute restore-to-disk.  This test is FreeBSD-specific.
+ */
+
+DEFINE_TEST(test_extattr_freebsd)
+{
+#if !defined(__FreeBSD__)
+       skipping("FreeBSD-specific extattr restore test");
+#elif __FreeBSD__ < 5
+       skipping("extattr restore supported only on FreeBSD 5.0 and later");
+#else
+       char buff[64];
+       struct stat st;
+       struct archive *a;
+       struct archive_entry *ae;
+       int n, fd;
+       int extattr_privilege_bug = 0;
+
+       /*
+        * First, do a quick manual set/read of an extended attribute
+        * to verify that the local filesystem does support it.  If it
+        * doesn't, we'll simply skip the remaining tests.
+        */
+       /* Create a test file and try to set an ACL on it. */
+       fd = open("pretest", O_RDWR | O_CREAT, 0777);
+       failure("Could not create test file?!");
+       if (!assert(fd >= 0))
+               return;
+
+       errno = 0;
+       n = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, "testattr", "1234", 4);
+       if (n != 4 && errno == EOPNOTSUPP) {
+               close(fd);
+               skipping("extattr tests require that extattr support be enabled on the filesystem");
+               return;
+       }
+       failure("extattr_set_fd(): errno=%d (%s)", errno, strerror(errno));
+       assertEqualInt(4, n);
+       close(fd);
+
+       /*
+        * Repeat the above, but with file permissions set to 0000.
+        * This should work (extattr_set_fd() should follow fd
+        * permissions, not file permissions), but is known broken on
+        * some versions of FreeBSD.
+        */
+       fd = open("pretest2", O_RDWR | O_CREAT, 00000);
+       failure("Could not create test file?!");
+       if (!assert(fd >= 0))
+               return;
+
+       n = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, "testattr", "1234", 4);
+       if (n != 4) {
+               skipping("Restoring xattr to an unwritable file (broken in some versions of FreeBSD");
+               extattr_privilege_bug = 1;
+       }
+       close(fd);
+
+       /* Create a write-to-disk object. */
+       assert(NULL != (a = archive_write_disk_new()));
+       archive_write_disk_set_options(a,
+           ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_XATTR);
+
+       /* Populate an archive entry with an extended attribute. */
+       ae = archive_entry_new();
+       assert(ae != NULL);
+       archive_entry_set_pathname(ae, "test0");
+       archive_entry_set_mtime(ae, 123456, 7890);
+       archive_entry_set_size(ae, 0);
+       archive_entry_set_mode(ae, 0755);
+       archive_entry_xattr_add_entry(ae, "user.foo", "12345", 5);
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /* Another entry; similar but with mode = 0. */
+       ae = archive_entry_new();
+       assert(ae != NULL);
+       archive_entry_set_pathname(ae, "test1");
+       archive_entry_set_mtime(ae, 12345678, 7890);
+       archive_entry_set_size(ae, 0);
+       archive_entry_set_mode(ae, 0);
+       archive_entry_xattr_add_entry(ae, "user.bar", "123456", 6);
+       if (extattr_privilege_bug)
+               /* If the bug is here, write_header will return warning. */
+               assertEqualIntA(a, ARCHIVE_WARN,
+                   archive_write_header(a, ae));
+       else
+               assertEqualIntA(a, ARCHIVE_OK,
+                   archive_write_header(a, ae));
+       archive_entry_free(ae);
+
+       /* Close the archive. */
+       assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_write_finish(a));
+
+       /* Verify the data on disk. */
+       assertEqualInt(0, stat("test0", &st));
+       assertEqualInt(st.st_mtime, 123456);
+       /* Verify extattr */
+       n = extattr_get_file("test0", EXTATTR_NAMESPACE_USER,
+           "foo", buff, sizeof(buff));
+       if (assertEqualInt(n, 5)) {
+               buff[n] = '\0';
+               assertEqualString(buff, "12345");
+       }
+
+       /* Verify the data on disk. */
+       assertEqualInt(0, stat("test1", &st));
+       assertEqualInt(st.st_mtime, 12345678);
+       /* Verify extattr */
+       n = extattr_get_file("test1", EXTATTR_NAMESPACE_USER,
+           "bar", buff, sizeof(buff));
+       if (extattr_privilege_bug) {
+               /* If we have the bug, the extattr won't have been written. */
+               assertEqualInt(n, -1);
+       } else {
+               if (assertEqualInt(n, 6)) {
+                       buff[n] = '\0';
+                       assertEqualString(buff, "123456");
+               }
+       }
+#endif
+}