]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Support for reading Mac OS-style metadata blobs.
authorTim Kientzle <kientzle@gmail.com>
Sat, 5 Jun 2010 02:28:31 +0000 (22:28 -0400)
committerTim Kientzle <kientzle@gmail.com>
Sat, 5 Jun 2010 02:28:31 +0000 (22:28 -0400)
This adds support for such blobs to archive_entry and
extends the tar reader to identify the special entries
and store them in the archive_entry.

Remaining: extend archive_write_disk to actually
restore the metadata to the file on disk.

SVN-Revision: 2446

Makefile.am
libarchive/archive_entry.c
libarchive/archive_entry.h
libarchive/archive_entry_private.h
libarchive/archive_read_support_format_tar.c
libarchive/test/CMakeLists.txt
libarchive/test/test_compat_mac_gnutar.c [new file with mode: 0644]
libarchive/test/test_compat_mac_gnutar.tgz.uu [new file with mode: 0644]

index 8cc87c414ce2c3cb0a678763bc9cc52c5d1d85d1..041998996cfab1eef9ba2fdb84504a8d31b6d32d 100644 (file)
@@ -245,6 +245,7 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_compat_gzip.c                      \
        libarchive/test/test_compat_lzip.c                      \
        libarchive/test/test_compat_lzma.c                      \
+       libarchive/test/test_compat_mac_gnutar.c                \
        libarchive/test/test_compat_solaris_tar_acl.c           \
        libarchive/test/test_compat_tar_hardlink.c              \
        libarchive/test/test_compat_xz.c                        \
@@ -376,6 +377,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_compat_lzma_1.tlz.uu                       \
        libarchive/test/test_compat_lzma_2.tlz.uu                       \
        libarchive/test/test_compat_lzma_3.tlz.uu                       \
+       libarchive/test/test_compat_mac_gnutar.tgz.uu                   \
        libarchive/test/test_compat_solaris_tar_acl.tar.uu              \
        libarchive/test/test_compat_tar_hardlink_1.tar.uu               \
        libarchive/test/test_compat_xz_1.txz.uu                         \
index 9b89b812796842654dc134eecf0fbed7c30cbef2..0a736a1937bd1bfa101e91a4b76c0c01ce662c5b 100644 (file)
@@ -396,6 +396,7 @@ archive_entry_clear(struct archive_entry *entry)
        aes_clean(&entry->ae_sourcepath);
        aes_clean(&entry->ae_symlink);
        aes_clean(&entry->ae_uname);
+       archive_entry_copy_mac_metadata(entry, NULL, 0);
        archive_entry_acl_clear(entry);
        archive_entry_xattr_clear(entry);
        archive_entry_sparse_clear(entry);
@@ -411,6 +412,8 @@ archive_entry_clone(struct archive_entry *entry)
        struct ae_acl *ap, *ap2;
        struct ae_xattr *xp;
        struct ae_sparse *sp;
+       size_t s;
+       const void *p;
 
        /* Allocate new structure and copy over all of the fields. */
        entry2 = archive_entry_new();
@@ -439,6 +442,10 @@ archive_entry_clone(struct archive_entry *entry)
                ap = ap->next;
        }
 
+       /* Copy Mac OS metadata. */
+       p = archive_entry_mac_metadata(entry, &s);
+       archive_entry_copy_mac_metadata(entry2, p, s);
+
        /* Copy xattr data over. */
        xp = entry->xattr_head;
        while (xp != NULL) {
@@ -1217,6 +1224,30 @@ archive_entry_update_uname_utf8(struct archive_entry *entry, const char *name)
        return (aes_update_utf8(&entry->ae_uname, name));
 }
 
+const void *
+archive_entry_mac_metadata(struct archive_entry *entry, size_t *s)
+{
+  *s = entry->mac_metadata_size;
+  return entry->mac_metadata;
+}
+
+void
+archive_entry_copy_mac_metadata(struct archive_entry *entry,
+    const void *p, size_t s)
+{
+  free(entry->mac_metadata);
+  if (p == NULL || s == 0) {
+    entry->mac_metadata = NULL;
+    entry->mac_metadata_size = 0;
+  } else {
+    entry->mac_metadata_size = s;
+    entry->mac_metadata = malloc(s);
+    if (entry->mac_metadata == NULL)
+      abort();
+    memcpy(entry->mac_metadata, p, s);
+  }
+}
+
 /*
  * ACL management.  The following would, of course, be a lot simpler
  * if: 1) the last draft of POSIX.1e were a really thorough and
index 1a5869cf8f357ddad573ff3092b585f8cf204e80..76a40ffe7b7e3bb9bc5ff4e2b590abec27565620 100644 (file)
@@ -321,6 +321,15 @@ __LA_DECL int      archive_entry_update_uname_utf8(struct archive_entry *, const char
 __LA_DECL const struct stat    *archive_entry_stat(struct archive_entry *);
 __LA_DECL void archive_entry_copy_stat(struct archive_entry *, const struct stat *);
 
+/*
+ * Storage for Mac OS-specific AppleDouble metadata information.
+ * Apple-format tar files store a separate binary blob containing
+ * encoded metadata with ACL, extended attributes, etc.
+ * This provides a place to store that blob.
+ */
+
+__LA_DECL const void * archive_entry_mac_metadata(struct archive_entry *, size_t *);
+__LA_DECL void archive_entry_copy_mac_metadata(struct archive_entry *, const void *, size_t);
 
 /*
  * ACL routines.  This used to simply store and return text-format ACL
index c7c2154c85e9597a735a072df92d853e90744585..3351baa33d48deaddcd080fd7a423b5eebdda8ae 100644 (file)
@@ -173,6 +173,9 @@ struct archive_entry {
        /* Not used within libarchive; useful for some clients. */
        struct aes ae_sourcepath;       /* Path this entry is sourced from. */
 
+       void *mac_metadata;
+       size_t mac_metadata_size;
+
        /* ACL support. */
        struct ae_acl   *acl_head;
        struct ae_acl   *acl_p;
index 6fc164f50386d4c856f6c6a25a436d3506139508..c1ddaeb5c3be80f2c88634587b1e5877a3727b97 100644 (file)
@@ -199,6 +199,8 @@ static int  header_longlink(struct archive_read *, struct tar *,
                    struct archive_entry *, const void *h);
 static int     header_longname(struct archive_read *, struct tar *,
                    struct archive_entry *, const void *h);
+static int     read_mac_metadata_blob(struct archive_read *, struct tar *,
+                   struct archive_entry *, const void *h);
 static int     header_volume(struct archive_read *, struct tar *,
                    struct archive_entry *, const void *h);
 static int     header_ustar(struct archive_read *, struct tar *,
@@ -643,6 +645,18 @@ tar_read_header(struct archive_read *a, struct tar *tar,
                }
        }
        --tar->header_recursion_depth;
+       /* Yuck.  Apple's design here ends up storing long pathname
+        * extensions for both the AppleDouble extension entry and the
+        * regular entry.
+        */
+       // TODO: Should this be disabled on non-Mac platforms?
+       if ((err == ARCHIVE_WARN || err == ARCHIVE_OK) &&
+           tar->header_recursion_depth == 0) {
+               int err2 = read_mac_metadata_blob(a, tar, entry, h);
+               if (err2 < err)
+                       err = err2;
+       }
+
        /* We return warnings or success as-is.  Anything else is fatal. */
        if (err == ARCHIVE_WARN || err == ARCHIVE_OK) {
                if (tar->sparse_gnu_pending) {
@@ -1098,6 +1112,50 @@ header_old_tar(struct archive_read *a, struct tar *tar,
        return (0);
 }
 
+/*
+ * Read a Mac AppleDouble-encoded blob of file metadata,
+ * if there is one.
+ */
+static int
+read_mac_metadata_blob(struct archive_read *a, struct tar *tar,
+    struct archive_entry *entry, const void *h)
+{
+       size_t size, padded_size;
+       const void *data;
+       const char *p, *name;
+
+       // Find the last path element.
+       name = p = archive_entry_pathname(entry);
+       for (; *p != '\0'; ++p) {
+               if (p[0] == '/' && p[1] != '\0')
+                       name = p + 1;
+       }
+       // If last path element starts with "._", then
+       // this is a Mac extension.
+       if (name[0] != '.' || name[1] != '_' || name[2] == '\0')
+               return ARCHIVE_OK;
+
+       /* Read the body as a Mac OS metadata blob. */
+       size = archive_entry_size(entry);
+       padded_size = (size + 511) & ~ 511;
+       /*
+        * TODO: Look beyond the body here to peek at the next header.
+        * If it's a regular header (not an extension header)
+        * that has the wrong name, just return the current
+        * entry as-is, without consuming the body here.
+        * That would reduce the risk of us mis-identifying
+        * an ordinary file that just happened to have
+        * a name starting with "._".
+        */
+       data = __archive_read_ahead(a, size, NULL);
+       if (data == NULL)
+               return (ARCHIVE_FATAL);
+       archive_entry_copy_mac_metadata(entry, data, size);
+       if (padded_size != __archive_read_consume(a, padded_size))
+               return (ARCHIVE_FATAL);
+       return (tar_read_header(a, tar, entry));
+}
+
 /*
  * Parse a file header for a pax extended archive entry.
  */
index 81207501557d941c754fc812038b114828b4e2c2..c3da942fcbac4522d86488a81ef6220b74ebb57d 100644 (file)
@@ -19,6 +19,7 @@ IF(ENABLE_TEST)
     test_compat_gzip.c
     test_compat_lzip.c
     test_compat_lzma.c
+    test_compat_mac_gnutar.c
     test_compat_solaris_tar_acl.c
     test_compat_tar_hardlink.c
     test_compat_xz.c
diff --git a/libarchive/test/test_compat_mac_gnutar.c b/libarchive/test/test_compat_mac_gnutar.c
new file mode 100644 (file)
index 0000000..44e6942
--- /dev/null
@@ -0,0 +1,131 @@
+/*-
+ * Copyright (c) 2003-2010 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$");
+
+/* The sample has some files in a directory with a very long name. */
+#define TESTPATH "abcdefghijklmnopqrstuvwxyz/" \
+       "abcdefghijklmnopqrstuvwxyz/"   \
+       "abcdefghijklmnopqrstuvwxyz/"   \
+       "abcdefghijklmnopqrstuvwxyz/"   \
+       "abcdefghijklmnopqrstuvwxyz/"   \
+       "abcdefghijklmnopqrstuvwxyz/"   \
+       "abcdefghijklmnopqrstuvwxyz/"
+
+/*
+ * Apple shipped an extended version of GNU tar with Mac OX X 10.5
+ * and earlier.
+ */
+DEFINE_TEST(test_compat_mac_gnutar)
+{
+       char name[] = "test_compat_mac_gnutar.tgz";
+       struct archive_entry *ae;
+       struct archive *a;
+       const void *attr;
+       size_t attrSize;
+       int r;
+
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_compression_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+       extract_reference_file(name);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 10240));
+
+       assertEqualIntA(a, ARCHIVE_OK, r = archive_read_next_header(a, &ae));
+       assertEqualString(TESTPATH, archive_entry_pathname(ae));
+       assertEqualInt(1275688109, archive_entry_mtime(ae));
+       assertEqualInt(95594, archive_entry_uid(ae));
+       assertEqualString("kientzle", archive_entry_uname(ae));
+       assertEqualInt(5000, archive_entry_gid(ae));
+       assertEqualString("", archive_entry_gname(ae));
+       assertEqualInt(040755, archive_entry_mode(ae));
+
+       attr = archive_entry_mac_metadata(ae, &attrSize);
+       assert(attr == NULL);
+       assertEqualInt(0, attrSize);
+
+       assertEqualIntA(a, ARCHIVE_OK, r = archive_read_next_header(a, &ae));
+       assertEqualString(TESTPATH "dir/", archive_entry_pathname(ae));
+       assertEqualInt(1275687611, archive_entry_mtime(ae));
+       assertEqualInt(95594, archive_entry_uid(ae));
+       assertEqualString("kientzle", archive_entry_uname(ae));
+       assertEqualInt(5000, archive_entry_gid(ae));
+       assertEqualString("", archive_entry_gname(ae));
+       assertEqualInt(040755, archive_entry_mode(ae));
+
+       attr = archive_entry_mac_metadata(ae, &attrSize);
+       assert(attr != NULL);
+       assertEqualInt(225, attrSize);
+
+       assertEqualIntA(a, ARCHIVE_OK, r = archive_read_next_header(a, &ae));
+       assertEqualString(TESTPATH "file", archive_entry_pathname(ae));
+       assertEqualInt(1275687588, archive_entry_mtime(ae));
+       assertEqualInt(95594, archive_entry_uid(ae));
+       assertEqualString("kientzle", archive_entry_uname(ae));
+       assertEqualInt(5000, archive_entry_gid(ae));
+       assertEqualString("", archive_entry_gname(ae));
+       assertEqualInt(0100644, archive_entry_mode(ae));
+
+       attr = archive_entry_mac_metadata(ae, &attrSize);
+       assert(attr != NULL);
+       assertEqualInt(225, attrSize);
+
+       assertEqualIntA(a, ARCHIVE_OK, r = archive_read_next_header(a, &ae));
+       assertEqualString("dir/", archive_entry_pathname(ae));
+       assertEqualInt(1275688064, archive_entry_mtime(ae));
+       assertEqualInt(95594, archive_entry_uid(ae));
+       assertEqualString("kientzle", archive_entry_uname(ae));
+       assertEqualInt(5000, archive_entry_gid(ae));
+       assertEqualString("", archive_entry_gname(ae));
+       assertEqualInt(040755, archive_entry_mode(ae));
+
+       attr = archive_entry_mac_metadata(ae, &attrSize);
+       assert(attr != NULL);
+       assertEqualInt(225, attrSize);
+
+       assertEqualIntA(a, ARCHIVE_OK, r = archive_read_next_header(a, &ae));
+       assertEqualString("file", archive_entry_pathname(ae));
+       assertEqualInt(1275625860, archive_entry_mtime(ae));
+       assertEqualInt(95594, archive_entry_uid(ae));
+       assertEqualString("kientzle", archive_entry_uname(ae));
+       assertEqualInt(5000, archive_entry_gid(ae));
+       assertEqualString("", archive_entry_gname(ae));
+       assertEqualInt(0100644, archive_entry_mode(ae));
+
+       attr = archive_entry_mac_metadata(ae, &attrSize);
+       assert(attr != NULL);
+       assertEqualInt(225, attrSize);
+
+       /* Verify the end-of-archive. */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+       /* Verify that the format detection worked. */
+       assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_GZIP);
+       assertEqualInt(archive_format(a), ARCHIVE_FORMAT_TAR_GNUTAR);
+
+       assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
diff --git a/libarchive/test/test_compat_mac_gnutar.tgz.uu b/libarchive/test/test_compat_mac_gnutar.tgz.uu
new file mode 100644 (file)
index 0000000..680f6e8
--- /dev/null
@@ -0,0 +1,17 @@
+begin 644 test_compat_mac_gnutar.tgz
+M'XL(`.UT"4P``^V9WV[3,!3&#1)"[!:)2Q3$=5/;<1+4*SK^2$A%D[9=<(="
+MFW59TZ1DZ3KV),`-C\`;<+,GX4VPUW9=J6"E6T[-]OTD*W'LM)&;\QU_IV[=
+MK3]OY5FWE60]5@U\S)^.7(;!^?G9=2%"X3&G5='SS#$\+*/"<5B1Y^7?YHWV
+MXSBE>"!:H@_M3KS7W4\.>FD_RP<?B\-R>#0Z_G12MW]HW8MW`Z#XN4Q0A[[/
+M=*!+WY<ZUH4(!)^+>2$4EWI<FGF>4H%DCD^Q`-/X[R5Q5IZD,<5W6H1K@?Y[
+MW%O0?RZA_Q18HN2K#;GO.TFQ[A7\O[%!_STEIOHO9.B-]5\QAU,LP"W7?W;O
+MT7UVE[&W4=O9VG'>.1/,-?9`-ZG;=]U,_^=R']G<W=UFC[^<3N[XK-N;WZ;<
+MF5U_V,[[;C08I+$;M5.WC(^-#C]YJCN.V.@6^7#0:&Z^>/GJ=7.S9@XUTZN=
+M=\?OD,\;4:>?9(UG^B1-\U%C5"1EO'$]BW1SL2/_R\7\+Y#_*;`DDZ\VI+,_
+M/.#5L"'_7_!_L_POX/\HL$/_U:+^H_Y'@B5*OJK_VTMN7<1>+U3Z'RBUG/\S
+M\[3^^R'\'PG5^;\?#/[/>NS(__!_Z\*23+[:$++_E;$A_\_YOTG^5\C_)+@$
+M?Z)<YO\O[/^D/--_'@C4_VFH;/_WM<.P_[,>BA+J/]3_IO'OB0#U/PI<@B+*
+MTOY?Z'FF%BCTZ\*A_R14IO\'WQCTWWHH+-32^_]9_'M!@/@'````````````
+7``````````````#@,GX!9EY(W`!0````
+`
+end