]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
archive_read_disk_entry_from_file() knows how to pull
authorTim Kientzle <kientzle@gmail.com>
Thu, 8 Jan 2009 06:52:41 +0000 (01:52 -0500)
committerTim Kientzle <kientzle@gmail.com>
Thu, 8 Jan 2009 06:52:41 +0000 (01:52 -0500)
all metadata about a file from the filesystem to populate
an entry object.  This takes advantage of the user/group
name lookup capabilities of the archive_read_disk object.

This should simplify bsdtar and bsdcpio by removing the
comparable code from those utilities.

Tests and documentation forthcoming...

SVN-Revision: 405

Makefile.am
libarchive/Makefile
libarchive/archive.h
libarchive/archive_read_disk.c
libarchive/archive_read_disk_entry_from_file.c [new file with mode: 0644]
libarchive/archive_read_disk_private.h

index 61538b3f619d3090ad18ed901eb59443195cc602..718222e05849b141b4b92c799af3b471095a6b63 100644 (file)
@@ -87,6 +87,7 @@ libarchive_la_SOURCES=                                                \
        libarchive/archive_read.c                               \
        libarchive/archive_read_data_into_fd.c                  \
        libarchive/archive_read_disk.c                          \
+       libarchive/archive_read_disk_entry_from_file.c          \
        libarchive/archive_read_disk_private.h                  \
        libarchive/archive_read_disk_set_standard_lookup.c      \
        libarchive/archive_read_extract.c                       \
index eecf3c33961ec95221608a0117a56e92a4b5450a..47ed7888b08d7297b88de4fbbf35306897db5901 100644 (file)
@@ -26,6 +26,7 @@ SRCS= archive_check_magic.c                           \
        archive_read.c                                  \
        archive_read_data_into_fd.c                     \
        archive_read_disk.c                             \
+       archive_read_disk_entry_from_file.c             \
        archive_read_disk_set_standard_lookup.c         \
        archive_read_extract.c                          \
        archive_read_open_fd.c                          \
index d9fb40711343ba94a13598c6702c03f8fd0905bd..5526ca5dd1a998aee49159b9fcb889ccb31cad9f 100644 (file)
@@ -36,6 +36,7 @@
  * platform macros.
  */
 
+#include <sys/stat.h>
 #include <sys/types.h>  /* Linux requires this for off_t */
 #if !defined(__WATCOMC__) && !defined(_MSC_VER)
 /* Header unavailable on Watcom C or MS Visual C++. */
@@ -607,11 +608,29 @@ __LA_DECL int      archive_write_disk_set_user_lookup(struct archive *,
 
 /*
  * ARCHIVE_READ_DISK API
+ *
+ * This is still evolving and somewhat experimental.
  */
 __LA_DECL struct archive *archive_read_disk_new(void);
-__LA_DECL int  archive_read_disk_set_standard_lookup(struct archive *);
+/* The names for symlink modes here correspond to an old BSD
+ * command-line argument convention: -L, -P, -H */
+/* Follow all symlinks. */
+__LA_DECL int archive_read_disk_set_symlink_logical(struct archive *);
+/* Follow no symlinks. */
+__LA_DECL int archive_read_disk_set_symlink_physical(struct archive *);
+/* Follow symlink initially, then not. */
+__LA_DECL int archive_read_disk_set_symlink_hybrid(struct archive *);
+/* TODO: Handle Linux stat32/stat64 ugliness. <sigh> */
+__LA_DECL int archive_read_disk_entry_from_file(struct archive *,
+    struct archive_entry *, int /* fd */, const struct stat *);
+/* Look up gname for gid or uname for uid. */
+/* Default implementations are very, very stupid. */
 __LA_DECL const char *archive_read_disk_gname(struct archive *, __LA_GID_T);
 __LA_DECL const char *archive_read_disk_uname(struct archive *, __LA_UID_T);
+/* "Standard" implementation uses getpwuid_r, getgrgid_r and caches the
+ * results for performance. */
+__LA_DECL int  archive_read_disk_set_standard_lookup(struct archive *);
+/* You can install your own lookups if you like. */
 __LA_DECL int  archive_read_disk_set_gname_lookup(struct archive *,
     void * /* private_data */,
     const char *(* /* lookup_fn */)(void *, __LA_GID_T),
index 010865c9dce4f88338c10c5e2b6ac80f7864c7d8..9b6d1c3c2e27e1e523a6040476c66241ef123c2d 100644 (file)
@@ -140,6 +140,33 @@ _archive_read_finish(struct archive *_a)
        return (ARCHIVE_OK);
 }
 
+int
+archive_read_disk_set_symlink_logical(struct archive *_a)
+{
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       a->symlink_mode = 'L';
+       a->follow_symlinks = 1;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_read_disk_set_symlink_physical(struct archive *_a)
+{
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       a->symlink_mode = 'P';
+       a->follow_symlinks = 0;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_read_disk_set_symlink_hybrid(struct archive *_a)
+{
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       a->symlink_mode = 'H';
+       a->follow_symlinks = 1; /* Follow symlinks initially. */
+       return (ARCHIVE_OK);
+}
+
 /*
  * Trivial implementations of gname/uname lookup functions.
  * These are normally overridden by the client, but these stub
diff --git a/libarchive/archive_read_disk_entry_from_file.c b/libarchive/archive_read_disk_entry_from_file.c
new file mode 100644 (file)
index 0000000..c3f6cfd
--- /dev/null
@@ -0,0 +1,393 @@
+/*-
+ * 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 "archive_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_ACL_H
+#include <sys/acl.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "archive_private.h"
+#include "archive_read_disk_private.h"
+
+/*
+ * Linux and FreeBSD plug this obvious hole in POSIX.1e in
+ * different ways.
+ */
+#if HAVE_ACL_GET_PERM
+#define        ACL_GET_PERM acl_get_perm
+#elif HAVE_ACL_GET_PERM_NP
+#define        ACL_GET_PERM acl_get_perm_np
+#endif
+
+static int setup_acls_posix1e(struct archive_read_disk *,
+    struct archive_entry *, int fd);
+static int setup_xattrs(struct archive_read_disk *,
+    struct archive_entry *, int fd);
+
+int
+archive_read_disk_entry_from_file(struct archive *_a,
+    struct archive_entry *entry,
+    int fd, const struct stat *st)
+{
+       struct archive_read_disk *a = (struct archive_read_disk *)_a;
+       static char linkbuffer[PATH_MAX + 1];
+       const char *path;
+       struct stat s;
+       int initial_fd = fd;
+
+       path = archive_entry_sourcepath(entry);
+       if (path == NULL)
+               path = archive_entry_pathname(entry);
+
+#ifdef EXT2_IOC_GETFLAGS
+       /* Linux requires an extra ioctl to pull the flags.  Although
+        * this is an extra step, it has a nice side-effect: We get an
+        * open file descriptor which we can use in the subsequent lookups. */
+       if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode))) {
+               if (fd < 0)
+                       fd = open(pathname, O_RDONLY | O_NONBLOCK);
+               if (fd >= 0) {
+                       unsigned long stflags;
+                       int r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags);
+                       if (r == 0 && stflags != 0)
+                               archive_entry_set_fflags(entry, stflags, 0);
+               }
+       }
+#endif
+
+       if (st == NULL) {
+#if HAVE_FSTAT
+               if (fd >= 0) {
+                       if (fstat(fd, &s) != 0)
+                               return (ARCHIVE_FATAL);
+               } else
+#endif
+#if HAVE_LSTAT
+               if (!a->follow_symlinks) {
+                       if (lstat(path, &s) != 0)
+                               return (ARCHIVE_FATAL);
+               } else
+#endif
+               if (stat(path, &s) != 0)
+                       return (ARCHIVE_FATAL);
+               st = &s;
+       }
+       archive_entry_copy_stat(entry, st);
+
+#ifdef HAVE_STRUCT_STAT_ST_FLAGS
+       /* On FreeBSD, we get flags for free with the stat. */
+       /* TODO: Does this belong in copy_stat()? */
+       if (st->st_flags != 0)
+               archive_entry_set_fflags(entry, st->st_flags, 0);
+#endif
+
+       if (S_ISLNK(st->st_mode)) {
+               int lnklen = readlink(path, linkbuffer, PATH_MAX);
+               if (lnklen < 0) {
+                       archive_set_error(&a->archive, errno,
+                           "Couldn't read link data");
+                       return (ARCHIVE_WARN);
+               }
+               linkbuffer[lnklen] = 0;
+               archive_entry_set_symlink(entry, linkbuffer);
+       }
+
+       /* TODO: collect errors here. */
+       setup_acls_posix1e(a, entry, fd);
+       setup_xattrs(a, entry, fd);
+       /* If we opened the file earlier in this function, close it. */
+       if (initial_fd != fd)
+               close(fd);
+       return (ARCHIVE_OK);
+}
+
+#ifdef HAVE_POSIX_ACL
+static void setup_acl_posix1e(struct archive_read_disk *a,
+    struct archive_entry *entry, acl_t acl, int archive_entry_acl_type);
+
+static int
+setup_acls_posix1e(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       const char      *accpath;
+       acl_t            acl;
+
+       accpath = archive_entry_pathname(entry);
+       if (accpath == NULL)
+               accpath = archive_entry_sourcepath(entry);
+
+       archive_entry_acl_clear(entry);
+
+       /* Retrieve access ACL from file. */
+       if (fd >= 0)
+               acl = acl_get_fd(fd);
+#if HAVE_ACL_GET_LINK_NP
+       else if (!a->follow_symlinks)
+               acl = acl_get_link_np(accpath, ACL_TYPE_ACCESS);
+#endif
+       else
+               acl = acl_get_file(accpath, ACL_TYPE_ACCESS);
+       if (acl != NULL) {
+               setup_acl_posix1e(a, entry, acl,
+                   ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+               acl_free(acl);
+       }
+
+       /* Only directories can have default ACLs. */
+       if (S_ISDIR(archive_entry_mode(entry))) {
+               acl = acl_get_file(accpath, ACL_TYPE_DEFAULT);
+               if (acl != NULL) {
+                       setup_acl_posix1e(a, entry, acl,
+                           ARCHIVE_ENTRY_ACL_TYPE_DEFAULT);
+                       acl_free(acl);
+               }
+       }
+}
+
+/*
+ * Translate POSIX.1e ACL into libarchive internal structure.
+ */
+static void
+setup_acl_posix1e(struct archive_read_disk *a,
+    struct archive_entry *entry, acl_t acl, int archive_entry_acl_type)
+{
+       acl_tag_t        acl_tag;
+       acl_entry_t      acl_entry;
+       acl_permset_t    acl_permset;
+       int              s, ae_id, ae_tag, ae_perm;
+       const char      *ae_name;
+
+       s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry);
+       while (s == 1) {
+               ae_id = -1;
+               ae_name = NULL;
+
+               acl_get_tag_type(acl_entry, &acl_tag);
+               if (acl_tag == ACL_USER) {
+                       ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry);
+                       ae_name = archive_read_disk_uname(&a->archive, ae_id);
+                       ae_tag = ARCHIVE_ENTRY_ACL_USER;
+               } else if (acl_tag == ACL_GROUP) {
+                       ae_id = (int)*(gid_t *)acl_get_qualifier(acl_entry);
+                       ae_name = archive_read_disk_gname(&a->archive, ae_id);
+                       ae_tag = ARCHIVE_ENTRY_ACL_GROUP;
+               } else if (acl_tag == ACL_MASK) {
+                       ae_tag = ARCHIVE_ENTRY_ACL_MASK;
+               } else if (acl_tag == ACL_USER_OBJ) {
+                       ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
+               } else if (acl_tag == ACL_GROUP_OBJ) {
+                       ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
+               } else if (acl_tag == ACL_OTHER) {
+                       ae_tag = ARCHIVE_ENTRY_ACL_OTHER;
+               } else {
+                       /* Skip types that libarchive can't support. */
+                       continue;
+               }
+
+               acl_get_permset(acl_entry, &acl_permset);
+               ae_perm = 0;
+               /*
+                * acl_get_perm() is spelled differently on different
+                * platforms; see above.
+                */
+               if (ACL_GET_PERM(acl_permset, ACL_EXECUTE))
+                       ae_perm |= ARCHIVE_ENTRY_ACL_EXECUTE;
+               if (ACL_GET_PERM(acl_permset, ACL_READ))
+                       ae_perm |= ARCHIVE_ENTRY_ACL_READ;
+               if (ACL_GET_PERM(acl_permset, ACL_WRITE))
+                       ae_perm |= ARCHIVE_ENTRY_ACL_WRITE;
+
+               archive_entry_acl_add_entry(entry,
+                   archive_entry_acl_type, ae_perm, ae_tag,
+                   ae_id, ae_name);
+
+               s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
+       }
+}
+#else
+static void
+setup_acls_posix1e(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       (void)a;      /* UNUSED */
+       (void)entry;  /* UNUSED */
+       (void)fd;     /* UNUSED */
+}
+#endif
+
+#if HAVE_LISTXATTR && HAVE_GETXATTR
+
+/*
+ * Linux extended attribute support.
+ *
+ * TODO:  By using a stack-allocated buffer for the first
+ * call to getxattr(), we might be able to avoid the second
+ * call entirely.  We only need the second call if the
+ * stack-allocated buffer is too small.  But a modest buffer
+ * of 1024 bytes or so will often be big enough.  Same applies
+ * to listxattr().
+ */
+
+static int
+setup_xattr(struct archive_read_disk *a,
+    struct archive_entry *entry, const char *name, int fd)
+{
+       ssize_t size;
+       void *value = NULL;
+       const char *accpath;
+
+       (void)fd; /* UNUSED */
+
+       accpath = archive_entry_sourcepath(entry);
+       if (accpath == NULL)
+               accpath = archive_entry_pathname(entry);
+
+       if (!a->follow_symlinks)
+               size = lgetxattr(accpath, name, NULL, 0);
+       else
+               size = getxattr(accpath, name, NULL, 0);
+
+       if (size == -1) {
+               archive_set_error(&a->archive, errno,
+                   "Couldn't query extended attribute");
+               return (ARCHIVE_WARN);
+       }
+
+       if (size > 0 && (value = malloc(size)) == NULL) {
+               archive_set_error(&a->archive, errno, "Out of memory");
+               return (ARCHIVE_FATAL);
+       }
+
+       if (!a->follow_symlinks)
+               size = lgetxattr(accpath, name, value, size);
+       else
+               size = getxattr(accpath, name, value, size);
+
+       if (size == -1) {
+               archive_set_error(&a->archive, errno,
+                   "Couldn't read extended attribute");
+               return (ARCHIVE_WARN);
+       }
+
+       archive_entry_xattr_add_entry(entry, name, value, size);
+
+       free(value);
+       return (ARCHIVE_OK);
+}
+
+static int
+setup_xattrs(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       char *list, *p;
+       ssize_t list_size;
+
+       if (!a->follow_symlinks)
+               list_size = llistxattr(NULL, 0);
+       else
+               list_size = listxattr(NULL, 0);
+
+       if (list_size == -1) {
+               archive_set_error(&a->archive, errno,
+                       "Couldn't list extended attributes");
+               return (ARCHIVE_WARN);
+       }
+
+       if (list_size == 0)
+               return (ARCHIVE_OK);
+
+       if ((list = malloc(list_size)) == NULL) {
+               archive_set_error(&a->archive, errno, "Out of memory");
+               return (ARCHIVE_FATAL);
+       }
+
+       if (!a->follow_symlinks)
+               list_size = llistxattr(list, list_size);
+       else
+               list_size = listxattr(list, list_size);
+
+       if (list_size == -1) {
+               archive_set_error(&a->archive, errno,
+                       "Couldn't retrieve extended attributes");
+               free(list);
+               return (ARCHIVE_WARN);
+       }
+
+       for (p = list; (p - list) < list_size; p += strlen(p) + 1) {
+               if (strncmp(p, "system.", 7) == 0 ||
+                               strncmp(p, "xfsroot.", 8) == 0)
+                       continue;
+               setup_xattr(a, entry, p, fd);
+       }
+
+       free(list);
+       return (ARCHIVE_OK);
+}
+
+#elif HAVE_EXTATTR_GET_FILE && HAVE_EXTATTR_LIST_FILE
+
+/*
+ * FreeBSD extattr interface.
+ */
+
+/* TODO: Implement this.  Follow the Linux model above, but
+ * with FreeBSD-specific system calls, of course.  Be careful
+ * to not include the system extattrs that hold ACLs; we handle
+ * those separately.
+ */
+static int
+setup_xattrs(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       (void)a;     /* UNUSED */
+       (void)entry; /* UNUSED */
+       (void)fd;    /* UNUSED */
+}
+
+#else
+
+/*
+ * Generic (stub) extended attribute support.
+ */
+static int
+setup_xattrs(struct archive_read_disk *a,
+    struct archive_entry *entry, int fd)
+{
+       (void)a;     /* UNUSED */
+       (void)entry; /* UNUSED */
+       (void)fd;    /* UNUSED */
+}
+
+#endif
index 758e66b0a8b25d679b979db802d3bd6ebd768602..fd385a6b77e6aca6a59c43a2ae98cb53dab9e9c3 100644 (file)
 struct archive_read_disk {
        struct archive  archive;
 
+       /*
+        * Symlink mode is one of 'L'ogical, 'P'hysical, or 'H'ybrid,
+        * following an old BSD convention.  'L' follows all symlinks,
+        * 'P' follows none, 'H' follows symlinks only for the first
+        * item.
+        */
+       char    symlink_mode;
+
+       /*
+        * Since symlink interaction changes, we need to track whether
+        * we're following symlinks for the current item.  'L' mode above
+        * sets this true, 'P' sets it false, 'H' changes it as we traverse.
+        */
+       char    follow_symlinks;  /* Either 'L' or 'P'. */
+
        const char * (*lookup_gname)(void *private, gid_t gid);
        void    (*cleanup_gname)(void *private);
        void     *lookup_gname_data;