diff options
Diffstat (limited to 'Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c')
-rw-r--r-- | Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c | 1291 |
1 files changed, 1134 insertions, 157 deletions
diff --git a/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c b/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c index 99afab2..bbd50a6 100644 --- a/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c +++ b/Utilities/cmlibarchive/libarchive/archive_write_disk_posix.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2003-2010 Tim Kientzle + * Copyright (c) 2012 Michihiro NAKAJIMA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,15 +39,14 @@ __FBSDID("$FreeBSD$"); #ifdef HAVE_SYS_EXTATTR_H #include <sys/extattr.h> #endif -#ifdef HAVE_SYS_XATTR_H +#if defined(HAVE_SYS_XATTR_H) #include <sys/xattr.h> +#elif defined(HAVE_ATTR_XATTR_H) +#include <attr/xattr.h> #endif #ifdef HAVE_SYS_EA_H #include <sys/ea.h> #endif -#ifdef HAVE_ATTR_XATTR_H -#include <attr/xattr.h> -#endif #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> #endif @@ -118,6 +118,10 @@ __FBSDID("$FreeBSD$"); #endif #endif +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif + /* TODO: Support Mac OS 'quarantine' feature. This is really just a * standard tag to mark files that have been downloaded as "tainted". * On Mac OS, we should mark the extracted files as tainted if the @@ -127,12 +131,17 @@ __FBSDID("$FreeBSD$"); #include "archive.h" #include "archive_acl_private.h" #include "archive_string.h" +#include "archive_endian.h" #include "archive_entry.h" #include "archive_private.h" +#include "archive_write_disk_private.h" #ifndef O_BINARY #define O_BINARY 0 #endif +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif struct fixup_entry { struct fixup_entry *next; @@ -172,6 +181,7 @@ struct fixup_entry { #define TODO_SUID_CHECK 0x08000000 #define TODO_SGID 0x04000000 #define TODO_SGID_CHECK 0x02000000 +#define TODO_APPLEDOUBLE 0x01000000 #define TODO_MODE (TODO_MODE_BASE|TODO_SUID|TODO_SGID) #define TODO_TIMES ARCHIVE_EXTRACT_TIME #define TODO_OWNER ARCHIVE_EXTRACT_OWNER @@ -179,6 +189,7 @@ struct fixup_entry { #define TODO_ACLS ARCHIVE_EXTRACT_ACL #define TODO_XATTR ARCHIVE_EXTRACT_XATTR #define TODO_MAC_METADATA ARCHIVE_EXTRACT_MAC_METADATA +#define TODO_HFS_COMPRESSION ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED struct archive_write_disk { struct archive archive; @@ -188,8 +199,8 @@ struct archive_write_disk { struct fixup_entry *current_fixup; int64_t user_uid; int skip_file_set; - dev_t skip_file_dev; - ino_t skip_file_ino; + int64_t skip_file_dev; + int64_t skip_file_ino; time_t start_time; int64_t (*lookup_gid)(void *private, const char *gname, int64_t gid); @@ -239,6 +250,36 @@ struct archive_write_disk { /* UID/GID to use in restoring this entry. */ int64_t uid; int64_t gid; + /* + * HFS+ Compression. + */ + /* Xattr "com.apple.decmpfs". */ + uint32_t decmpfs_attr_size; + unsigned char *decmpfs_header_p; + /* ResourceFork set options used for fsetxattr. */ + int rsrc_xattr_options; + /* Xattr "com.apple.ResourceFork". */ + unsigned char *resource_fork; + size_t resource_fork_allocated_size; + unsigned int decmpfs_block_count; + uint32_t *decmpfs_block_info; + /* Buffer for compressed data. */ + unsigned char *compressed_buffer; + size_t compressed_buffer_size; + size_t compressed_buffer_remaining; + /* The offset of the ResourceFork where compressed data will + * be placed. */ + uint32_t compressed_rsrc_position; + uint32_t compressed_rsrc_position_v; + /* Buffer for uncompressed data. */ + char *uncompressed_buffer; + size_t block_remaining_bytes; + size_t file_remaining_bytes; +#ifdef HAVE_ZLIB_H + z_stream stream; + int stream_valid; + int decmpfs_compression_level; +#endif }; /* @@ -256,6 +297,35 @@ struct archive_write_disk { #define MINIMUM_DIR_MODE 0700 #define MAXIMUM_DIR_MODE 0775 +/* + * Maxinum uncompressed size of a decmpfs block. + */ +#define MAX_DECMPFS_BLOCK_SIZE (64 * 1024) +/* + * HFS+ compression type. + */ +#define CMP_XATTR 3/* Compressed data in xattr. */ +#define CMP_RESOURCE_FORK 4/* Compressed data in resource fork. */ +/* + * HFS+ compression resource fork. + */ +#define RSRC_H_SIZE 260 /* Base size of Resource fork header. */ +#define RSRC_F_SIZE 50 /* Size of Resource fork footer. */ +/* Size to write compressed data to resource fork. */ +#define COMPRESSED_W_SIZE (64 * 1024) +/* decmpfs difinitions. */ +#define MAX_DECMPFS_XATTR_SIZE 3802 +#ifndef DECMPFS_XATTR_NAME +#define DECMPFS_XATTR_NAME "com.apple.decmpfs" +#endif +#define DECMPFS_MAGIC 0x636d7066 +#define DECMPFS_COMPRESSION_MAGIC 0 +#define DECMPFS_COMPRESSION_TYPE 4 +#define DECMPFS_UNCOMPRESSED_SIZE 8 +#define DECMPFS_HEADER_SIZE 16 + +#define HFS_BLOCKS(s) ((s) >> 12) + static int check_symlinks(struct archive_write_disk *); static int create_filesystem_object(struct archive_write_disk *); static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); @@ -265,13 +335,11 @@ static void edit_deep_directories(struct archive_write_disk *ad); static int cleanup_pathname(struct archive_write_disk *); static int create_dir(struct archive_write_disk *, char *); static int create_parent_dir(struct archive_write_disk *, char *); +static ssize_t hfs_write_data_block(struct archive_write_disk *, + const char *, size_t); +static int fixup_appledouble(struct archive_write_disk *, const char *); static int older(struct stat *, struct archive_entry *); static int restore_entry(struct archive_write_disk *); -#ifdef HAVE_POSIX_ACL -static int set_acl(struct archive_write_disk *, int fd, const char *, struct archive_acl *, - acl_type_t, int archive_entry_acl_type, const char *tn); -#endif -static int set_acls(struct archive_write_disk *, int fd, const char *, struct archive_acl *); static int set_mac_metadata(struct archive_write_disk *, const char *, const void *, size_t); static int set_xattrs(struct archive_write_disk *); @@ -485,6 +553,39 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) else a->todo |= TODO_MAC_METADATA; } +#if defined(__APPLE__) && defined(UF_COMPRESSED) && defined(HAVE_ZLIB_H) + if ((a->flags & ARCHIVE_EXTRACT_NO_HFS_COMPRESSION) == 0) { + unsigned long set, clear; + archive_entry_fflags(a->entry, &set, &clear); + if ((set & ~clear) & UF_COMPRESSED) { + a->todo |= TODO_HFS_COMPRESSION; + a->decmpfs_block_count = (unsigned)-1; + } + } + if ((a->flags & ARCHIVE_EXTRACT_HFS_COMPRESSION_FORCED) != 0 && + (a->mode & AE_IFMT) == AE_IFREG && a->filesize > 0) { + a->todo |= TODO_HFS_COMPRESSION; + a->decmpfs_block_count = (unsigned)-1; + } + { + const char *p; + + /* Check if the current file name is a type of the + * resource fork file. */ + p = strrchr(a->name, '/'); + if (p == NULL) + p = a->name; + else + p++; + if (p[0] == '.' && p[1] == '_') { + /* Do not compress "._XXX" files. */ + a->todo &= ~TODO_HFS_COMPRESSION; + if (a->filesize > 0) + a->todo |= TODO_APPLEDOUBLE; + } + } +#endif + if (a->flags & ARCHIVE_EXTRACT_XATTR) a->todo |= TODO_XATTR; if (a->flags & ARCHIVE_EXTRACT_FFLAGS) @@ -501,6 +602,25 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) ret = restore_entry(a); +#if defined(__APPLE__) && defined(UF_COMPRESSED) && defined(HAVE_ZLIB_H) + /* + * Check if the filesystem the file is restoring on supports + * HFS+ Compression. If not, cancel HFS+ Compression. + */ + if (a->todo | TODO_HFS_COMPRESSION) { + /* + * NOTE: UF_COMPRESSED is ignored even if the filesystem + * supports HFS+ Compression because the file should + * have at least an extended attriute "com.apple.decmpfs" + * before the flag is set to indicate that the file have + * been compressed. If hte filesystem does not support + * HFS+ Compression the system call will fail. + */ + if (a->fd < 0 || fchflags(a->fd, UF_COMPRESSED) != 0) + a->todo &= ~TODO_HFS_COMPRESSION; + } +#endif + /* * TODO: There are rumours that some extended attributes must * be restored before file data is written. If this is true, @@ -532,6 +652,8 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) */ if (a->deferred & TODO_MODE) { fe = current_fixup(a, archive_entry_pathname(entry)); + if (fe == NULL) + return (ARCHIVE_FATAL); fe->fixup |= TODO_MODE_BASE; fe->mode = a->mode; } @@ -540,6 +662,8 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) && (archive_entry_mtime_is_set(entry) || archive_entry_atime_is_set(entry))) { fe = current_fixup(a, archive_entry_pathname(entry)); + if (fe == NULL) + return (ARCHIVE_FATAL); fe->mode = a->mode; fe->fixup |= TODO_TIMES; if (archive_entry_atime_is_set(entry)) { @@ -570,6 +694,9 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) if (a->deferred & TODO_ACLS) { fe = current_fixup(a, archive_entry_pathname(entry)); + if (fe == NULL) + return (ARCHIVE_FATAL); + fe->fixup |= TODO_ACLS; archive_acl_copy(&fe->acl, archive_entry_acl(entry)); } @@ -579,6 +706,8 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) metadata = archive_entry_mac_metadata(a->entry, &metadata_size); if (metadata != NULL && metadata_size > 0) { fe = current_fixup(a, archive_entry_pathname(entry)); + if (fe == NULL) + return (ARCHIVE_FATAL); fe->mac_metadata = malloc(metadata_size); if (fe->mac_metadata != NULL) { memcpy(fe->mac_metadata, metadata, metadata_size); @@ -590,6 +719,8 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry) if (a->deferred & TODO_FFLAGS) { fe = current_fixup(a, archive_entry_pathname(entry)); + if (fe == NULL) + return (ARCHIVE_FATAL); fe->fixup |= TODO_FFLAGS; /* TODO: Complete this.. defer fflags from below. */ } @@ -707,6 +838,616 @@ write_data_block(struct archive_write_disk *a, const char *buff, size_t size) return (start_size - size); } +#if defined(__APPLE__) && defined(UF_COMPRESSED) && defined(HAVE_SYS_XATTR_H)\ + && defined(HAVE_ZLIB_H) + +/* + * Set UF_COMPRESSED file flag. + * This have to be called after hfs_write_decmpfs() because if the + * file does not have "com.apple.decmpfs" xattr the flag is ignored. + */ +static int +hfs_set_compressed_fflag(struct archive_write_disk *a) +{ + int r; + + if ((r = lazy_stat(a)) != ARCHIVE_OK) + return (r); + + a->st.st_flags |= UF_COMPRESSED; + if (fchflags(a->fd, a->st.st_flags) != 0) { + archive_set_error(&a->archive, errno, + "Failed to set UF_COMPRESSED file flag"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +/* + * HFS+ Compression decmpfs + * + * +------------------------------+ +0 + * | Magic(LE 4 bytes) | + * +------------------------------+ + * | Type(LE 4 bytes) | + * +------------------------------+ + * | Uncompressed size(LE 8 bytes)| + * +------------------------------+ +16 + * | | + * | Compressed data | + * | (Placed only if Type == 3) | + * | | + * +------------------------------+ +3802 = MAX_DECMPFS_XATTR_SIZE + * + * Type is 3: decmpfs has compressed data. + * Type is 4: Resource Fork has compressed data. + */ +/* + * Write "com.apple.decmpfs" + */ +static int +hfs_write_decmpfs(struct archive_write_disk *a) +{ + int r; + uint32_t compression_type; + + r = fsetxattr(a->fd, DECMPFS_XATTR_NAME, a->decmpfs_header_p, + a->decmpfs_attr_size, 0, 0); + if (r < 0) { + archive_set_error(&a->archive, errno, + "Cannot restore xattr:%s", DECMPFS_XATTR_NAME); + compression_type = archive_le32dec( + &a->decmpfs_header_p[DECMPFS_COMPRESSION_TYPE]); + if (compression_type == CMP_RESOURCE_FORK) + fremovexattr(a->fd, XATTR_RESOURCEFORK_NAME, + XATTR_SHOWCOMPRESSION); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +/* + * HFS+ Compression Resource Fork + * + * +-----------------------------+ + * | Header(260 bytes) | + * +-----------------------------+ + * | Block count(LE 4 bytes) | + * +-----------------------------+ --+ + * +-- | Offset (LE 4 bytes) | | + * | | [distance from Block count] | | Block 0 + * | +-----------------------------+ | + * | | Compressed size(LE 4 bytes) | | + * | +-----------------------------+ --+ + * | | | + * | | .................. | + * | | | + * | +-----------------------------+ --+ + * | | Offset (LE 4 bytes) | | + * | +-----------------------------+ | Block (Block count -1) + * | | Compressed size(LE 4 bytes) | | + * +-> +-----------------------------+ --+ + * | Compressed data(n bytes) | Block 0 + * +-----------------------------+ + * | | + * | .................. | + * | | + * +-----------------------------+ + * | Compressed data(n bytes) | Block (Block count -1) + * +-----------------------------+ + * | Footer(50 bytes) | + * +-----------------------------+ + * + */ +/* + * Write the header of "com.apple.ResourceFork" + */ +static int +hfs_write_resource_fork(struct archive_write_disk *a, unsigned char *buff, + size_t bytes, uint32_t position) +{ + int ret; + + ret = fsetxattr(a->fd, XATTR_RESOURCEFORK_NAME, buff, bytes, + position, a->rsrc_xattr_options); + if (ret < 0) { + archive_set_error(&a->archive, errno, + "Cannot restore xattr: %s at %u pos %u bytes", + XATTR_RESOURCEFORK_NAME, + (unsigned)position, + (unsigned)bytes); + return (ARCHIVE_WARN); + } + a->rsrc_xattr_options &= ~XATTR_CREATE; + return (ARCHIVE_OK); +} + +static int +hfs_write_compressed_data(struct archive_write_disk *a, size_t bytes_compressed) +{ + int ret; + + ret = hfs_write_resource_fork(a, a->compressed_buffer, + bytes_compressed, a->compressed_rsrc_position); + if (ret == ARCHIVE_OK) + a->compressed_rsrc_position += bytes_compressed; + return (ret); +} + +static int +hfs_write_resource_fork_header(struct archive_write_disk *a) +{ + unsigned char *buff; + uint32_t rsrc_bytes; + uint32_t rsrc_header_bytes; + + /* + * Write resource fork header + block info. + */ + buff = a->resource_fork; + rsrc_bytes = a->compressed_rsrc_position - RSRC_F_SIZE; + rsrc_header_bytes = + RSRC_H_SIZE + /* Header base size. */ + 4 + /* Block count. */ + (a->decmpfs_block_count * 8);/* Block info */ + archive_be32enc(buff, 0x100); + archive_be32enc(buff + 4, rsrc_bytes); + archive_be32enc(buff + 8, rsrc_bytes - 256); + archive_be32enc(buff + 12, 0x32); + memset(buff + 16, 0, 240); + archive_be32enc(buff + 256, rsrc_bytes - 260); + return hfs_write_resource_fork(a, buff, rsrc_header_bytes, 0); +} + +static size_t +hfs_set_resource_fork_footer(unsigned char *buff, size_t buff_size) +{ + static const char rsrc_footer[RSRC_F_SIZE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1c, 0x00, 0x32, 0x00, 0x00, 'c', 'm', + 'p', 'f', 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + if (buff_size < sizeof(rsrc_footer)) + return (0); + memcpy(buff, rsrc_footer, sizeof(rsrc_footer)); + return (sizeof(rsrc_footer)); +} + +static int +hfs_reset_compressor(struct archive_write_disk *a) +{ + int ret; + + if (a->stream_valid) + ret = deflateReset(&a->stream); + else + ret = deflateInit(&a->stream, a->decmpfs_compression_level); + + if (ret != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to initialize compressor"); + return (ARCHIVE_FATAL); + } else + a->stream_valid = 1; + + return (ARCHIVE_OK); +} + +static int +hfs_decompress(struct archive_write_disk *a) +{ + uint32_t *block_info; + unsigned int block_count; + uint32_t data_pos, data_size; + ssize_t r; + ssize_t bytes_written, bytes_to_write; + unsigned char *b; + + block_info = (uint32_t *)(a->resource_fork + RSRC_H_SIZE); + block_count = archive_le32dec(block_info++); + while (block_count--) { + data_pos = RSRC_H_SIZE + archive_le32dec(block_info++); + data_size = archive_le32dec(block_info++); + r = fgetxattr(a->fd, XATTR_RESOURCEFORK_NAME, + a->compressed_buffer, data_size, data_pos, 0); + if (r != data_size) { + archive_set_error(&a->archive, + (r < 0)?errno:ARCHIVE_ERRNO_MISC, + "Failed to read resource fork"); + return (ARCHIVE_WARN); + } + if (a->compressed_buffer[0] == 0xff) { + bytes_to_write = data_size -1; + b = a->compressed_buffer + 1; + } else { + uLong dest_len = MAX_DECMPFS_BLOCK_SIZE; + int zr; + + zr = uncompress((Bytef *)a->uncompressed_buffer, + &dest_len, a->compressed_buffer, data_size); + if (zr != Z_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to decompress resource fork"); + return (ARCHIVE_WARN); + } + bytes_to_write = dest_len; + b = (unsigned char *)a->uncompressed_buffer; + } + do { + bytes_written = write(a->fd, b, bytes_to_write); + if (bytes_written < 0) { + archive_set_error(&a->archive, errno, + "Write failed"); + return (ARCHIVE_WARN); + } + bytes_to_write -= bytes_written; + b += bytes_written; + } while (bytes_to_write > 0); + } + r = fremovexattr(a->fd, XATTR_RESOURCEFORK_NAME, 0); + if (r == -1) { + archive_set_error(&a->archive, errno, + "Failed to remove resource fork"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +hfs_drive_compressor(struct archive_write_disk *a, const char *buff, + size_t size) +{ + unsigned char *buffer_compressed; + size_t bytes_compressed; + size_t bytes_used; + int ret; + + ret = hfs_reset_compressor(a); + if (ret != ARCHIVE_OK) + return (ret); + + if (a->compressed_buffer == NULL) { + size_t block_size; + + block_size = COMPRESSED_W_SIZE + RSRC_F_SIZE + + + compressBound(MAX_DECMPFS_BLOCK_SIZE); + a->compressed_buffer = malloc(block_size); + if (a->compressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Resource Fork"); + return (ARCHIVE_FATAL); + } + a->compressed_buffer_size = block_size; + a->compressed_buffer_remaining = block_size; + } + + buffer_compressed = a->compressed_buffer + + a->compressed_buffer_size - a->compressed_buffer_remaining; + a->stream.next_in = (Bytef *)(uintptr_t)(const void *)buff; + a->stream.avail_in = size; + a->stream.next_out = buffer_compressed; + a->stream.avail_out = a->compressed_buffer_remaining; + do { + ret = deflate(&a->stream, Z_FINISH); + switch (ret) { + case Z_OK: + case Z_STREAM_END: + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to compress data"); + return (ARCHIVE_FAILED); + } + } while (ret == Z_OK); + bytes_compressed = a->compressed_buffer_remaining - a->stream.avail_out; + + /* + * If the compressed size is larger than the original size, + * throw away compressed data, use uncompressed data instead. + */ + if (bytes_compressed > size) { + buffer_compressed[0] = 0xFF;/* uncompressed marker. */ + memcpy(buffer_compressed + 1, buff, size); + bytes_compressed = size + 1; + } + a->compressed_buffer_remaining -= bytes_compressed; + + /* + * If the compressed size is smaller than MAX_DECMPFS_XATTR_SIZE + * and the block count in the file is only one, store compressed + * data to decmpfs xattr instead of the resource fork. + */ + if (a->decmpfs_block_count == 1 && + (a->decmpfs_attr_size + bytes_compressed) + <= MAX_DECMPFS_XATTR_SIZE) { + archive_le32enc(&a->decmpfs_header_p[DECMPFS_COMPRESSION_TYPE], + CMP_XATTR); + memcpy(a->decmpfs_header_p + DECMPFS_HEADER_SIZE, + buffer_compressed, bytes_compressed); + a->decmpfs_attr_size += bytes_compressed; + a->compressed_buffer_remaining = a->compressed_buffer_size; + /* + * Finish HFS+ Compression. + * - Write the decmpfs xattr. + * - Set the UF_COMPRESSED file flag. + */ + ret = hfs_write_decmpfs(a); + if (ret == ARCHIVE_OK) + ret = hfs_set_compressed_fflag(a); + return (ret); + } + + /* Update block info. */ + archive_le32enc(a->decmpfs_block_info++, + a->compressed_rsrc_position_v - RSRC_H_SIZE); + archive_le32enc(a->decmpfs_block_info++, bytes_compressed); + a->compressed_rsrc_position_v += bytes_compressed; + + /* + * Write the compressed data to the resource fork. + */ + bytes_used = a->compressed_buffer_size - a->compressed_buffer_remaining; + while (bytes_used >= COMPRESSED_W_SIZE) { + ret = hfs_write_compressed_data(a, COMPRESSED_W_SIZE); + if (ret != ARCHIVE_OK) + return (ret); + bytes_used -= COMPRESSED_W_SIZE; + if (bytes_used > COMPRESSED_W_SIZE) + memmove(a->compressed_buffer, + a->compressed_buffer + COMPRESSED_W_SIZE, + bytes_used); + else + memcpy(a->compressed_buffer, + a->compressed_buffer + COMPRESSED_W_SIZE, + bytes_used); + } + a->compressed_buffer_remaining = a->compressed_buffer_size - bytes_used; + + /* + * If the current block is the last block, write the remaining + * compressed data and the resource fork footer. + */ + if (a->file_remaining_bytes == 0) { + size_t rsrc_size; + int64_t bk; + + /* Append the resource footer. */ + rsrc_size = hfs_set_resource_fork_footer( + a->compressed_buffer + bytes_used, + a->compressed_buffer_remaining); + ret = hfs_write_compressed_data(a, bytes_used + rsrc_size); + a->compressed_buffer_remaining = a->compressed_buffer_size; + + /* If the compressed size is not enouph smaller than + * the uncompressed size. cancel HFS+ compression. + * TODO: study a behavior of ditto utility and improve + * the condition to fall back into no HFS+ compression. */ + bk = HFS_BLOCKS(a->compressed_rsrc_position); + bk += bk >> 7; + if (bk > HFS_BLOCKS(a->filesize)) + return hfs_decompress(a); + /* + * Write the resourcefork header. + */ + if (ret == ARCHIVE_OK) + ret = hfs_write_resource_fork_header(a); + /* + * Finish HFS+ Compression. + * - Write the decmpfs xattr. + * - Set the UF_COMPRESSED file flag. + */ + if (ret == ARCHIVE_OK) + ret = hfs_write_decmpfs(a); + if (ret == ARCHIVE_OK) + ret = hfs_set_compressed_fflag(a); + } + return (ret); +} + +static ssize_t +hfs_write_decmpfs_block(struct archive_write_disk *a, const char *buff, + size_t size) +{ + const char *buffer_to_write; + size_t bytes_to_write; + int ret; + + if (a->decmpfs_block_count == (unsigned)-1) { + void *new_block; + size_t new_size; + unsigned int block_count; + + if (a->decmpfs_header_p == NULL) { + new_block = malloc(MAX_DECMPFS_XATTR_SIZE + + sizeof(uint32_t)); + if (new_block == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for decmpfs"); + return (ARCHIVE_FATAL); + } + a->decmpfs_header_p = new_block; + } + a->decmpfs_attr_size = DECMPFS_HEADER_SIZE; + archive_le32enc(&a->decmpfs_header_p[DECMPFS_COMPRESSION_MAGIC], + DECMPFS_MAGIC); + archive_le32enc(&a->decmpfs_header_p[DECMPFS_COMPRESSION_TYPE], + CMP_RESOURCE_FORK); + archive_le64enc(&a->decmpfs_header_p[DECMPFS_UNCOMPRESSED_SIZE], + a->filesize); + + /* Calculate a block count of the file. */ + block_count = + (a->filesize + MAX_DECMPFS_BLOCK_SIZE -1) / + MAX_DECMPFS_BLOCK_SIZE; + /* + * Allocate buffer for resource fork. + * Set up related pointers; + */ + new_size = + RSRC_H_SIZE + /* header */ + 4 + /* Block count */ + (block_count * sizeof(uint32_t) * 2) + + RSRC_F_SIZE; /* footer */ + if (new_size > a->resource_fork_allocated_size) { + new_block = realloc(a->resource_fork, new_size); + if (new_block == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for ResourceFork"); + return (ARCHIVE_FATAL); + } + a->resource_fork_allocated_size = new_size; + a->resource_fork = new_block; + } + + /* Allocate uncompressed buffer */ + if (a->uncompressed_buffer == NULL) { + new_block = malloc(MAX_DECMPFS_BLOCK_SIZE); + if (new_block == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for decmpfs"); + return (ARCHIVE_FATAL); + } + a->uncompressed_buffer = new_block; + } + a->block_remaining_bytes = MAX_DECMPFS_BLOCK_SIZE; + a->file_remaining_bytes = a->filesize; + a->compressed_buffer_remaining = a->compressed_buffer_size; + + /* + * Set up a resource fork. + */ + a->rsrc_xattr_options = XATTR_CREATE; + /* Get the position where we are going to set a bunch + * of block info. */ + a->decmpfs_block_info = + (uint32_t *)(a->resource_fork + RSRC_H_SIZE); + /* Set the block count to the resource fork. */ + archive_le32enc(a->decmpfs_block_info++, block_count); + /* Get the position where we are goint to set compressed + * data. */ + a->compressed_rsrc_position = + RSRC_H_SIZE + 4 + (block_count * 8); + a->compressed_rsrc_position_v = a->compressed_rsrc_position; + a->decmpfs_block_count = block_count; + } + + /* Ignore redundant bytes. */ + if (a->file_remaining_bytes == 0) + return ((ssize_t)size); + + /* Do not overrun a block size. */ + if (size > a->block_remaining_bytes) + bytes_to_write = a->block_remaining_bytes; + else + bytes_to_write = size; + /* Do not overrun the file size. */ + if (bytes_to_write > a->file_remaining_bytes) + bytes_to_write = a->file_remaining_bytes; + + /* For efficiency, if a copy length is full of the uncompressed + * buffer size, do not copy writing data to it. */ + if (bytes_to_write == MAX_DECMPFS_BLOCK_SIZE) + buffer_to_write = buff; + else { + memcpy(a->uncompressed_buffer + + MAX_DECMPFS_BLOCK_SIZE - a->block_remaining_bytes, + buff, bytes_to_write); + buffer_to_write = a->uncompressed_buffer; + } + a->block_remaining_bytes -= bytes_to_write; + a->file_remaining_bytes -= bytes_to_write; + + if (a->block_remaining_bytes == 0 || a->file_remaining_bytes == 0) { + ret = hfs_drive_compressor(a, buffer_to_write, + MAX_DECMPFS_BLOCK_SIZE - a->block_remaining_bytes); + if (ret < 0) + return (ret); + a->block_remaining_bytes = MAX_DECMPFS_BLOCK_SIZE; + } + /* Ignore redundant bytes. */ + if (a->file_remaining_bytes == 0) + return ((ssize_t)size); + return (bytes_to_write); +} + +static ssize_t +hfs_write_data_block(struct archive_write_disk *a, const char *buff, + size_t size) +{ + uint64_t start_size = size; + ssize_t bytes_written = 0; + ssize_t bytes_to_write; + + if (size == 0) + return (ARCHIVE_OK); + + if (a->filesize == 0 || a->fd < 0) { + archive_set_error(&a->archive, 0, + "Attempt to write to an empty file"); + return (ARCHIVE_WARN); + } + + /* If this write would run beyond the file size, truncate it. */ + if (a->filesize >= 0 && (int64_t)(a->offset + size) > a->filesize) + start_size = size = (size_t)(a->filesize - a->offset); + + /* Write the data. */ + while (size > 0) { + bytes_to_write = size; + /* Seek if necessary to the specified offset. */ + if (a->offset < a->fd_offset) { + /* Can't support backword move. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Seek failed"); + return (ARCHIVE_FATAL); + } else if (a->offset > a->fd_offset) { + int64_t skip = a->offset - a->fd_offset; + char nullblock[1024]; + + memset(nullblock, 0, sizeof(nullblock)); + while (skip > 0) { + if (skip > (int64_t)sizeof(nullblock)) + bytes_written = hfs_write_decmpfs_block( + a, nullblock, sizeof(nullblock)); + else + bytes_written = hfs_write_decmpfs_block( + a, nullblock, skip); + if (bytes_written < 0) { + archive_set_error(&a->archive, errno, + "Write failed"); + return (ARCHIVE_WARN); + } + skip -= bytes_written; + } + + a->fd_offset = a->offset; + } + bytes_written = + hfs_write_decmpfs_block(a, buff, bytes_to_write); + if (bytes_written < 0) + return (bytes_written); + buff += bytes_written; + size -= bytes_written; + a->total_bytes_written += bytes_written; + a->offset += bytes_written; + a->fd_offset = a->offset; + } + return (start_size - size); +} +#else +static ssize_t +hfs_write_data_block(struct archive_write_disk *a, const char *buff, + size_t size) +{ + return (write_data_block(a, buff, size)); +} +#endif + static ssize_t _archive_write_disk_data_block(struct archive *_a, const void *buff, size_t size, int64_t offset) @@ -718,7 +1459,10 @@ _archive_write_disk_data_block(struct archive *_a, ARCHIVE_STATE_DATA, "archive_write_data_block"); a->offset = offset; - r = write_data_block(a, buff, size); + if (a->todo & TODO_HFS_COMPRESSION) + r = hfs_write_data_block(a, buff, size); + else + r = write_data_block(a, buff, size); if (r < ARCHIVE_OK) return (r); if ((size_t)r < size) { @@ -737,6 +1481,8 @@ _archive_write_disk_data(struct archive *_a, const void *buff, size_t size) archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, ARCHIVE_STATE_DATA, "archive_write_data"); + if (a->todo & TODO_HFS_COMPRESSION) + return (hfs_write_data_block(a, buff, size)); return (write_data_block(a, buff, size)); } @@ -761,6 +1507,24 @@ _archive_write_disk_finish_entry(struct archive *_a) } else if (a->fd_offset == a->filesize) { /* Last write ended at exactly the filesize; we're done. */ /* Hopefully, this is the common case. */ +#if defined(__APPLE__) && defined(UF_COMPRESSED) && defined(HAVE_ZLIB_H) + } else if (a->todo & TODO_HFS_COMPRESSION) { + char null_d[1024]; + ssize_t r; + + if (a->file_remaining_bytes) + memset(null_d, 0, sizeof(null_d)); + while (a->file_remaining_bytes) { + if (a->file_remaining_bytes > sizeof(null_d)) + r = hfs_write_data_block( + a, null_d, sizeof(null_d)); + else + r = hfs_write_data_block( + a, null_d, a->file_remaining_bytes); + if (r < 0) + return ((int)r); + } +#endif } else { #if HAVE_FTRUNCATE if (ftruncate(a->fd, a->filesize) == -1 && @@ -799,6 +1563,22 @@ _archive_write_disk_finish_entry(struct archive *_a) /* Restore metadata. */ /* + * This is specific to Mac OS X. + * If the current file is an AppleDouble file, it should be + * linked with the data fork file and remove it. + */ + if (a->todo & TODO_APPLEDOUBLE) { + int r2 = fixup_appledouble(a, a->name); + if (r2 == ARCHIVE_EOF) { + /* The current file has been successfully linked + * with the data fork file and removed. So there + * is nothing to do on the current file. */ + goto finish_metadata; + } + if (r2 < ret) ret = r2; + } + + /* * Look up the "real" UID only if we're going to need it. * TODO: the TODO_SGID condition can be dropped here, can't it? */ @@ -820,8 +1600,10 @@ _archive_write_disk_finish_entry(struct archive *_a) * bits. If we set the owner, we know what it is and can skip * a stat() call to examine the ownership of the file on disk. */ - if (a->todo & TODO_OWNER) - ret = set_ownership(a); + if (a->todo & TODO_OWNER) { + int r2 = set_ownership(a); + if (r2 < ret) ret = r2; + } /* * set_mode must precede ACLs on systems such as Solaris and @@ -868,7 +1650,8 @@ _archive_write_disk_finish_entry(struct archive *_a) size_t metadata_size; metadata = archive_entry_mac_metadata(a->entry, &metadata_size); if (metadata != NULL && metadata_size > 0) { - int r2 = set_mac_metadata(a, archive_entry_pathname(a->entry), metadata, metadata_size); + int r2 = set_mac_metadata(a, archive_entry_pathname( + a->entry), metadata, metadata_size); if (r2 < ret) ret = r2; } } @@ -878,12 +1661,13 @@ _archive_write_disk_finish_entry(struct archive *_a) * ACLs that prevent attribute changes (including time). */ if (a->todo & TODO_ACLS) { - int r2 = set_acls(a, a->fd, + int r2 = archive_write_disk_set_acls(&a->archive, a->fd, archive_entry_pathname(a->entry), archive_entry_acl(a->entry)); if (r2 < ret) ret = r2; } +finish_metadata: /* If there's an fd, we can close it now. */ if (a->fd >= 0) { close(a->fd); @@ -950,12 +1734,12 @@ archive_write_disk_gid(struct archive *_a, const char *name, int64_t id) int64_t archive_write_disk_uid(struct archive *_a, const char *name, int64_t id) { - struct archive_write_disk *a = (struct archive_write_disk *)_a; - archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, - ARCHIVE_STATE_ANY, "archive_write_disk_uid"); - if (a->lookup_uid) - return (a->lookup_uid)(a->lookup_uid_data, name, id); - return (id); + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_uid"); + if (a->lookup_uid) + return (a->lookup_uid)(a->lookup_uid_data, name, id); + return (id); } /* @@ -984,6 +1768,9 @@ archive_write_disk_new(void) free(a); return (NULL); } +#ifdef HAVE_ZLIB_H + a->decmpfs_compression_level = 5; +#endif return (&a->archive); } @@ -1008,7 +1795,8 @@ edit_deep_directories(struct archive_write_disk *a) return; /* Try to record our starting dir. */ - a->restore_pwd = open(".", O_RDONLY | O_BINARY); + a->restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC); + __archive_ensure_cloexec_flag(a->restore_pwd); if (a->restore_pwd < 0) return; @@ -1143,9 +1931,10 @@ restore_entry(struct archive_write_disk *a) /* If it's our archive, we're done. */ if (a->skip_file_set && - a->st.st_dev == a->skip_file_dev && - a->st.st_ino == a->skip_file_ino) { - archive_set_error(&a->archive, 0, "Refusing to overwrite archive"); + a->st.st_dev == (dev_t)a->skip_file_dev && + a->st.st_ino == (ino_t)a->skip_file_ino) { + archive_set_error(&a->archive, 0, + "Refusing to overwrite archive"); return (ARCHIVE_FAILED); } @@ -1163,7 +1952,7 @@ restore_entry(struct archive_write_disk *a) /* A dir is in the way of a non-dir, rmdir it. */ if (rmdir(a->name) != 0) { archive_set_error(&a->archive, errno, - "Can't remove already-existing dir"); + "Can't replace existing directory with non-directory"); return (ARCHIVE_FAILED); } /* Try again. */ @@ -1223,7 +2012,7 @@ create_filesystem_object(struct archive_write_disk *a) * * If the hardlink was successfully created and * the archive doesn't have carry data for it, - * consider it to be non-authoritive for meta data. + * consider it to be non-authoritative for meta data. * This is consistent with GNU tar and BSD pax. * If the hardlink does carry data, let the last * archive entry decide ownership. @@ -1232,7 +2021,9 @@ create_filesystem_object(struct archive_write_disk *a) a->todo = 0; a->deferred = 0; } else if (r == 0 && a->filesize > 0) { - a->fd = open(a->name, O_WRONLY | O_TRUNC | O_BINARY); + a->fd = open(a->name, + O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC); + __archive_ensure_cloexec_flag(a->fd); if (a->fd < 0) r = errno; } @@ -1261,7 +2052,7 @@ create_filesystem_object(struct archive_write_disk *a) * that SUID, SGID, etc, require additional work to ensure * security, so we never restore them at this point. */ - mode = final_mode & 0777 & a->user_umask; + mode = final_mode & 0777 & ~a->user_umask; switch (a->mode & AE_IFMT) { default: @@ -1269,7 +2060,8 @@ create_filesystem_object(struct archive_write_disk *a) /* FALLTHROUGH */ case AE_IFREG: a->fd = open(a->name, - O_WRONLY | O_CREAT | O_EXCL | O_BINARY, mode); + O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, mode); + __archive_ensure_cloexec_flag(a->fd); r = (a->fd < 0); break; case AE_IFCHR: @@ -1380,7 +2172,8 @@ _archive_write_disk_close(struct archive *_a) if (p->fixup & TODO_MODE_BASE) chmod(p->name, p->mode); if (p->fixup & TODO_ACLS) - set_acls(a, -1, p->name, &p->acl); + archive_write_disk_set_acls(&a->archive, + -1, p->name, &p->acl); if (p->fixup & TODO_FFLAGS) set_fflags_platform(a, -1, p->name, p->mode, p->fflags_set, 0); @@ -1418,6 +2211,23 @@ _archive_write_disk_free(struct archive *_a) archive_string_free(&a->path_safe); a->archive.magic = 0; __archive_clean(&a->archive); + free(a->decmpfs_header_p); + free(a->resource_fork); + free(a->compressed_buffer); + free(a->uncompressed_buffer); +#ifdef HAVE_ZLIB_H + if (a->stream_valid) { + switch (deflateEnd(&a->stream)) { + case Z_OK: + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + break; + } + } +#endif free(a); return (ret); } @@ -1499,8 +2309,11 @@ new_fixup(struct archive_write_disk *a, const char *pathname) struct fixup_entry *fe; fe = (struct fixup_entry *)calloc(1, sizeof(struct fixup_entry)); - if (fe == NULL) + if (fe == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for a fixup"); return (NULL); + } fe->next = a->fixup_list; a->fixup_list = fe; fe->fixup = 0; @@ -1672,7 +2485,7 @@ cleanup_pathname_win(struct archive_write_disk *a) p = a->name; while (*p != '\0' && alen) { l = mbtowc(&wc, p, alen); - if (l == -1) { + if (l == (size_t)-1) { while (*p != '\0') { if (*p == '\\') *p = '/'; @@ -1887,6 +2700,8 @@ create_dir(struct archive_write_disk *a, char *path) if (mkdir(path, mode) == 0) { if (mode != mode_final) { le = new_fixup(a, path); + if (le == NULL) + return (ARCHIVE_FATAL); le->fixup |=TODO_MODE_BASE; le->mode = mode_final; } @@ -1979,6 +2794,7 @@ set_time(int fd, int mode, const char *name, * on fds and symlinks. */ struct timespec ts[2]; + (void)mode; /* UNUSED */ ts[0].tv_sec = atime; ts[0].tv_nsec = atime_nsec; ts[1].tv_sec = mtime; @@ -2036,6 +2852,11 @@ set_time(int fd, int mode, const char *name, /* * We don't know how to set the time on this platform. */ + (void)fd; /* UNUSED */ + (void)mode; /* UNUSED */ + (void)name; /* UNUSED */ + (void)atime_nsec; /* UNUSED */ + (void)mtime_nsec; /* UNUSED */ return (ARCHIVE_WARN); #endif } @@ -2068,7 +2889,7 @@ set_times(struct archive_write_disk *a, time_t atime, long atime_nanos, time_t birthtime, long birthtime_nanos, time_t mtime, long mtime_nanos, - time_t ctime, long ctime_nanos) + time_t cctime, long ctime_nanos) { /* Note: set_time doesn't use libarchive return conventions! * It uses syscall conventions. So 0 here instead of ARCHIVE_OK. */ @@ -2083,9 +2904,12 @@ set_times(struct archive_write_disk *a, if (a->user_uid == 0 && set_time_tru64(fd, mode, name, atime, atime_nanos, mtime, - mtime_nanos, ctime, ctime_nanos) == 0) { + mtime_nanos, cctime, ctime_nanos) == 0) { return (ARCHIVE_OK); } +#else /* Tru64 */ + (void)cctime; /* UNUSED */ + (void)ctime_nanos; /* UNUSED */ #endif /* Tru64 */ #ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME @@ -2102,6 +2926,9 @@ set_times(struct archive_write_disk *a, r1 = set_time(fd, mode, name, atime, atime_nanos, birthtime, birthtime_nanos); +#else + (void)birthtime; /* UNUSED */ + (void)birthtime_nanos; /* UNUSED */ #endif r2 = set_time(fd, mode, name, atime, atime_nanos, @@ -2117,11 +2944,11 @@ set_times(struct archive_write_disk *a, static int set_times_from_entry(struct archive_write_disk *a) { - time_t atime, birthtime, mtime, ctime; + time_t atime, birthtime, mtime, cctime; long atime_nsec, birthtime_nsec, mtime_nsec, ctime_nsec; /* Suitable defaults. */ - atime = birthtime = mtime = ctime = a->start_time; + atime = birthtime = mtime = cctime = a->start_time; atime_nsec = birthtime_nsec = mtime_nsec = ctime_nsec = 0; /* If no time was provided, we're done. */ @@ -2145,7 +2972,7 @@ set_times_from_entry(struct archive_write_disk *a) mtime_nsec = archive_entry_mtime_nsec(a->entry); } if (archive_entry_ctime_is_set(a->entry)) { - ctime = archive_entry_ctime(a->entry); + cctime = archive_entry_ctime(a->entry); ctime_nsec = archive_entry_ctime_nsec(a->entry); } @@ -2153,7 +2980,7 @@ set_times_from_entry(struct archive_write_disk *a) atime, atime_nsec, birthtime, birthtime_nsec, mtime, mtime_nsec, - ctime, ctime_nsec); + cctime, ctime_nsec); } static int @@ -2312,6 +3139,8 @@ set_fflags(struct archive_write_disk *a) */ if ((critical_flags != 0) && (set & critical_flags)) { le = current_fixup(a, a->name); + if (le == NULL) + return (ARCHIVE_FATAL); le->fixup |= TODO_FFLAGS; le->fflags_set = set; /* Store the mode if it's not already there. */ @@ -2390,8 +3219,8 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, { int ret; int myfd = fd; - unsigned long newflags, oldflags; - unsigned long sf_mask = 0; + int newflags, oldflags; + int sf_mask = 0; if (set == 0 && clear == 0) return (ARCHIVE_OK); @@ -2400,8 +3229,10 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, return (ARCHIVE_OK); /* If we weren't given an fd, open it ourselves. */ - if (myfd < 0) - myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY); + if (myfd < 0) { + myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY | O_CLOEXEC); + __archive_ensure_cloexec_flag(myfd); + } if (myfd < 0) return (ARCHIVE_OK); @@ -2486,12 +3317,196 @@ set_mac_metadata(struct archive_write_disk *a, const char *pathname, (void)metadata_size; /* UNUSED */ return (ARCHIVE_OK); } + +static int +fixup_appledouble(struct archive_write_disk *a, const char *pathname) +{ + (void)a; /* UNUSED */ + (void)pathname; /* UNUSED */ + return (ARCHIVE_OK); +} #else /* * On Mac OS, we use copyfile() to unpack the metadata and * apply it to the target file. */ + +#if defined(HAVE_SYS_XATTR_H) +static int +copy_xattrs(struct archive_write_disk *a, int tmpfd, int dffd) +{ + ssize_t xattr_size; + char *xattr_names = NULL, *xattr_val = NULL; + int ret = ARCHIVE_OK, xattr_i; + + xattr_size = flistxattr(tmpfd, NULL, 0, 0); + if (xattr_size == -1) { + archive_set_error(&a->archive, errno, + "Failed to read metadata(xattr)"); + ret = ARCHIVE_WARN; + goto exit_xattr; + } + xattr_names = malloc(xattr_size); + if (xattr_names == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for metadata(xattr)"); + ret = ARCHIVE_FATAL; + goto exit_xattr; + } + xattr_size = flistxattr(tmpfd, xattr_names, xattr_size, 0); + if (xattr_size == -1) { + archive_set_error(&a->archive, errno, + "Failed to read metadata(xattr)"); + ret = ARCHIVE_WARN; + goto exit_xattr; + } + for (xattr_i = 0; xattr_i < xattr_size; + xattr_i += strlen(xattr_names + xattr_i) + 1) { + ssize_t s; + int f; + + s = fgetxattr(tmpfd, xattr_names + xattr_i, NULL, 0, 0, 0); + if (s == -1) { + archive_set_error(&a->archive, errno, + "Failed to get metadata(xattr)"); + ret = ARCHIVE_WARN; + goto exit_xattr; + } + xattr_val = realloc(xattr_val, s); + if (xattr_val == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Failed to get metadata(xattr)"); + ret = ARCHIVE_WARN; + goto exit_xattr; + } + s = fgetxattr(tmpfd, xattr_names + xattr_i, xattr_val, s, 0, 0); + if (s == -1) { + archive_set_error(&a->archive, errno, + "Failed to get metadata(xattr)"); + ret = ARCHIVE_WARN; + goto exit_xattr; + } + f = fsetxattr(dffd, xattr_names + xattr_i, xattr_val, s, 0, 0); + if (f == -1) { + archive_set_error(&a->archive, errno, + "Failed to get metadata(xattr)"); + ret = ARCHIVE_WARN; + goto exit_xattr; + } + } +exit_xattr: + free(xattr_names); + free(xattr_val); + return (ret); +} +#endif + +static int +copy_acls(struct archive_write_disk *a, int tmpfd, int dffd) +{ + acl_t acl, dfacl = NULL; + int acl_r, ret = ARCHIVE_OK; + + acl = acl_get_fd(tmpfd); + if (acl == NULL) { + if (errno == ENOENT) + /* There are not any ACLs. */ + return (ret); + archive_set_error(&a->archive, errno, + "Failed to get metadata(acl)"); + ret = ARCHIVE_WARN; + goto exit_acl; + } + dfacl = acl_dup(acl); + acl_r = acl_set_fd(dffd, dfacl); + if (acl_r == -1) { + archive_set_error(&a->archive, errno, + "Failed to get metadata(acl)"); + ret = ARCHIVE_WARN; + goto exit_acl; + } +exit_acl: + if (acl) + acl_free(acl); + if (dfacl) + acl_free(dfacl); + return (ret); +} + +static int +create_tempdatafork(struct archive_write_disk *a, const char *pathname) +{ + struct archive_string tmpdatafork; + int tmpfd; + + archive_string_init(&tmpdatafork); + archive_strcpy(&tmpdatafork, "tar.md.XXXXXX"); + tmpfd = mkstemp(tmpdatafork.s); + if (tmpfd < 0) { + archive_set_error(&a->archive, errno, + "Failed to mkstemp"); + archive_string_free(&tmpdatafork); + return (-1); + } + if (copyfile(pathname, tmpdatafork.s, 0, + COPYFILE_UNPACK | COPYFILE_NOFOLLOW + | COPYFILE_ACL | COPYFILE_XATTR) < 0) { + archive_set_error(&a->archive, errno, + "Failed to restore metadata"); + close(tmpfd); + tmpfd = -1; + } + unlink(tmpdatafork.s); + archive_string_free(&tmpdatafork); + return (tmpfd); +} + +static int +copy_metadata(struct archive_write_disk *a, const char *metadata, + const char *datafork, int datafork_compressed) +{ + int ret = ARCHIVE_OK; + + if (datafork_compressed) { + int dffd, tmpfd; + + tmpfd = create_tempdatafork(a, metadata); + if (tmpfd == -1) + return (ARCHIVE_WARN); + + /* + * Do not open the data fork compressed by HFS+ compression + * with at least a writing mode(O_RDWR or O_WRONLY). it + * makes the data fork uncompressed. + */ + dffd = open(datafork, 0); + if (dffd == -1) { + archive_set_error(&a->archive, errno, + "Failed to open the data fork for metadata"); + close(tmpfd); + return (ARCHIVE_WARN); + } + +#if defined(HAVE_SYS_XATTR_H) + ret = copy_xattrs(a, tmpfd, dffd); + if (ret == ARCHIVE_OK) +#endif + ret = copy_acls(a, tmpfd, dffd); + close(tmpfd); + close(dffd); + } else { + if (copyfile(metadata, datafork, 0, + COPYFILE_UNPACK | COPYFILE_NOFOLLOW + | COPYFILE_ACL | COPYFILE_XATTR) < 0) { + archive_set_error(&a->archive, errno, + "Failed to restore metadata"); + ret = ARCHIVE_WARN; + } + } + return (ret); +} + static int set_mac_metadata(struct archive_write_disk *a, const char *pathname, const void *metadata, size_t metadata_size) @@ -2513,139 +3528,101 @@ set_mac_metadata(struct archive_write_disk *a, const char *pathname, if (fd < 0) { archive_set_error(&a->archive, errno, "Failed to restore metadata"); + archive_string_free(&tmp); return (ARCHIVE_WARN); } written = write(fd, metadata, metadata_size); close(fd); - if (written != metadata_size - || copyfile(tmp.s, pathname, 0, - COPYFILE_UNPACK | COPYFILE_NOFOLLOW - | COPYFILE_ACL | COPYFILE_XATTR)) { + if ((size_t)written != metadata_size) { archive_set_error(&a->archive, errno, "Failed to restore metadata"); ret = ARCHIVE_WARN; + } else { + int compressed; + +#if defined(UF_COMPRESSED) + if ((a->todo & TODO_HFS_COMPRESSION) != 0 && + (ret = lazy_stat(a)) == ARCHIVE_OK) + compressed = a->st.st_flags & UF_COMPRESSED; + else +#endif + compressed = 0; + ret = copy_metadata(a, tmp.s, pathname, compressed); } unlink(tmp.s); + archive_string_free(&tmp); return (ret); } -#endif -#ifndef HAVE_POSIX_ACL -/* Default empty function body to satisfy mainline code. */ static int -set_acls(struct archive_write_disk *a, int fd, const char *name, - struct archive_acl *acl) +fixup_appledouble(struct archive_write_disk *a, const char *pathname) { - (void)a; /* UNUSED */ - (void)fd; /* UNUSED */ - (void)name; /* UNUSED */ - (void)acl; /* UNUSED */ - return (ARCHIVE_OK); -} - -#else - -/* - * XXX TODO: What about ACL types other than ACCESS and DEFAULT? - */ -static int -set_acls(struct archive_write_disk *a, int fd, const char *name, - struct archive_acl *abstract_acl) -{ - int ret; - - ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_ACCESS, - ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access"); - if (ret != ARCHIVE_OK) - return (ret); - ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_DEFAULT, - ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default"); - return (ret); -} - - -static int -set_acl(struct archive_write_disk *a, int fd, const char *name, - struct archive_acl *abstract_acl, - acl_type_t acl_type, int ae_requested_type, const char *tname) -{ - acl_t acl; - acl_entry_t acl_entry; - acl_permset_t acl_permset; - int ret; - int ae_type, ae_permset, ae_tag, ae_id; - uid_t ae_uid; - gid_t ae_gid; - const char *ae_name; - int entries; + char buff[8]; + struct stat st; + const char *p; + struct archive_string datafork; + int fd = -1, ret = ARCHIVE_OK; + + archive_string_init(&datafork); + /* Check if the current file name is a type of the resource + * fork file. */ + p = strrchr(pathname, '/'); + if (p == NULL) + p = pathname; + else + p++; + if (p[0] != '.' || p[1] != '_') + goto skip_appledouble; - ret = ARCHIVE_OK; - entries = archive_acl_reset(abstract_acl, ae_requested_type); - if (entries == 0) - return (ARCHIVE_OK); - acl = acl_init(entries); - while (archive_acl_next(&a->archive, abstract_acl, - ae_requested_type, &ae_type, &ae_permset, &ae_tag, &ae_id, - &ae_name) == ARCHIVE_OK) { - acl_create_entry(&acl, &acl_entry); - - switch (ae_tag) { - case ARCHIVE_ENTRY_ACL_USER: - acl_set_tag_type(acl_entry, ACL_USER); - ae_uid = archive_write_disk_uid(&a->archive, - ae_name, ae_id); - acl_set_qualifier(acl_entry, &ae_uid); - break; - case ARCHIVE_ENTRY_ACL_GROUP: - acl_set_tag_type(acl_entry, ACL_GROUP); - ae_gid = archive_write_disk_gid(&a->archive, - ae_name, ae_id); - acl_set_qualifier(acl_entry, &ae_gid); - break; - case ARCHIVE_ENTRY_ACL_USER_OBJ: - acl_set_tag_type(acl_entry, ACL_USER_OBJ); - break; - case ARCHIVE_ENTRY_ACL_GROUP_OBJ: - acl_set_tag_type(acl_entry, ACL_GROUP_OBJ); - break; - case ARCHIVE_ENTRY_ACL_MASK: - acl_set_tag_type(acl_entry, ACL_MASK); - break; - case ARCHIVE_ENTRY_ACL_OTHER: - acl_set_tag_type(acl_entry, ACL_OTHER); - break; - default: - /* XXX */ - break; - } + /* + * Check if the data fork file exists. + * + * TODO: Check if this write disk object has handled it. + */ + archive_strncpy(&datafork, pathname, p - pathname); + archive_strcat(&datafork, p + 2); + if (lstat(datafork.s, &st) == -1 || + (st.st_mode & AE_IFMT) != AE_IFREG) + goto skip_appledouble; - acl_get_permset(acl_entry, &acl_permset); - acl_clear_perms(acl_permset); - if (ae_permset & ARCHIVE_ENTRY_ACL_EXECUTE) - acl_add_perm(acl_permset, ACL_EXECUTE); - if (ae_permset & ARCHIVE_ENTRY_ACL_WRITE) - acl_add_perm(acl_permset, ACL_WRITE); - if (ae_permset & ARCHIVE_ENTRY_ACL_READ) - acl_add_perm(acl_permset, ACL_READ); + /* + * Check if the file is in the AppleDouble form. + */ + fd = open(pathname, O_RDONLY | O_BINARY | O_CLOEXEC); + __archive_ensure_cloexec_flag(fd); + if (fd == -1) { + archive_set_error(&a->archive, errno, + "Failed to open a restoring file"); + ret = ARCHIVE_WARN; + goto skip_appledouble; } - - /* Try restoring the ACL through 'fd' if we can. */ -#if HAVE_ACL_SET_FD - if (fd >= 0 && acl_type == ACL_TYPE_ACCESS && acl_set_fd(fd, acl) == 0) - ret = ARCHIVE_OK; - else + if (read(fd, buff, 8) == -1) { + archive_set_error(&a->archive, errno, + "Failed to read a restoring file"); + close(fd); + ret = ARCHIVE_WARN; + goto skip_appledouble; + } + close(fd); + /* Check AppleDouble Magic Code. */ + if (archive_be32dec(buff) != 0x00051607) + goto skip_appledouble; + /* Check AppleDouble Version. */ + if (archive_be32dec(buff+4) != 0x00020000) + goto skip_appledouble; + + ret = copy_metadata(a, pathname, datafork.s, +#if defined(UF_COMPRESSED) + st.st_flags & UF_COMPRESSED); #else -#if HAVE_ACL_SET_FD_NP - if (fd >= 0 && acl_set_fd_np(fd, acl, acl_type) == 0) - ret = ARCHIVE_OK; - else -#endif + 0); #endif - if (acl_set_file(name, acl_type, acl) != 0) { - archive_set_error(&a->archive, errno, "Failed to set %s acl", tname); - ret = ARCHIVE_WARN; + if (ret == ARCHIVE_OK) { + unlink(pathname); + ret = ARCHIVE_EOF; } - acl_free(acl); +skip_appledouble: + archive_string_free(&datafork); return (ret); } #endif |