From: Tim Kientzle Date: Sat, 5 Jun 2010 02:28:31 +0000 (-0400) Subject: Support for reading Mac OS-style metadata blobs. X-Git-Tag: v3.0.0a~975 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=09869533718e0f8e4b99affdffc8a7860241d509;p=thirdparty%2Flibarchive.git Support for reading Mac OS-style metadata blobs. 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 --- diff --git a/Makefile.am b/Makefile.am index 8cc87c414..041998996 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/libarchive/archive_entry.c b/libarchive/archive_entry.c index 9b89b8127..0a736a193 100644 --- a/libarchive/archive_entry.c +++ b/libarchive/archive_entry.c @@ -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 diff --git a/libarchive/archive_entry.h b/libarchive/archive_entry.h index 1a5869cf8..76a40ffe7 100644 --- a/libarchive/archive_entry.h +++ b/libarchive/archive_entry.h @@ -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 diff --git a/libarchive/archive_entry_private.h b/libarchive/archive_entry_private.h index c7c2154c8..3351baa33 100644 --- a/libarchive/archive_entry_private.h +++ b/libarchive/archive_entry_private.h @@ -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; diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c index 6fc164f50..c1ddaeb5c 100644 --- a/libarchive/archive_read_support_format_tar.c +++ b/libarchive/archive_read_support_format_tar.c @@ -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. */ diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index 812075015..c3da942fc 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -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 index 000000000..44e694261 --- /dev/null +++ b/libarchive/test/test_compat_mac_gnutar.c @@ -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 index 000000000..680f6e87d --- /dev/null +++ b/libarchive/test/test_compat_mac_gnutar.tgz.uu @@ -0,0 +1,17 @@ +begin 644 test_compat_mac_gnutar.tgz +M'XL(`.UT"4P``^V9WV[3,!3)"[!:)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#0Z_G12MW]HW8MW`Z#XN4Q0A[[/ +M=*!+WY2$4EWI4H%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/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)BA+J/]3_IO'OB0#U/PI<@B+* +MTOY?Z'FF%BCTZ\*A_R14IO\'WQCTWWHH+-32^_]9_'M!@/@'```````````` +7``````````````#@,GX!9EY(W`!0```` +` +end