diff options
author | Andy Cedilnik <andy.cedilnik@kitware.com> | 2006-01-02 04:21:05 (GMT) |
---|---|---|
committer | Andy Cedilnik <andy.cedilnik@kitware.com> | 2006-01-02 04:21:05 (GMT) |
commit | d0329919dacdbbfd518340340c0918b0a95e15a5 (patch) | |
tree | bbf4091d46d3e847fb76a0432d612d089e637920 /Source/CPack/cmCPackTGZGenerator.cxx | |
parent | 74680f1bf427bde968a2836baf1932760b7591cb (diff) | |
download | CMake-d0329919dacdbbfd518340340c0918b0a95e15a5.zip CMake-d0329919dacdbbfd518340340c0918b0a95e15a5.tar.gz CMake-d0329919dacdbbfd518340340c0918b0a95e15a5.tar.bz2 |
ENH: Merge from the cpack branch
Diffstat (limited to 'Source/CPack/cmCPackTGZGenerator.cxx')
-rw-r--r-- | Source/CPack/cmCPackTGZGenerator.cxx | 4067 |
1 files changed, 4067 insertions, 0 deletions
diff --git a/Source/CPack/cmCPackTGZGenerator.cxx b/Source/CPack/cmCPackTGZGenerator.cxx new file mode 100644 index 0000000..594e414 --- /dev/null +++ b/Source/CPack/cmCPackTGZGenerator.cxx @@ -0,0 +1,4067 @@ +/*========================================================================= + + Program: CMake - Cross-Platform Makefile Generator + Module: $RCSfile$ + Language: C++ + Date: $Date$ + Version: $Revision$ + + Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved. + See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + +#include "cmCPackTGZGenerator.h" + +#include "cmake.h" +#include "cmGlobalGenerator.h" +#include "cmLocalGenerator.h" +#include "cmSystemTools.h" +#include "cmMakefile.h" +#include "cmGeneratedFileStream.h" + +#include <cmsys/SystemTools.hxx> +#include <cmzlib/zlib.h> + +#include <memory> // auto_ptr +#include <fcntl.h> // O_WRONLY, O_CREAT + +//---------------------------------------------------------------------- +cmCPackTGZGenerator::cmCPackTGZGenerator() +{ +} + +//---------------------------------------------------------------------- +cmCPackTGZGenerator::~cmCPackTGZGenerator() +{ +} + +//---------------------------------------------------------------------- +int cmCPackTGZGenerator::ProcessGenerator() +{ + return this->Superclass::ProcessGenerator(); +} + +//---------------------------------------------------------------------- +int cmCPackTGZGenerator::Initialize(const char* name) +{ + return this->Superclass::Initialize(name); +} + +//---------------------------------------------------------------------- +// The following code is modified version of bsdtar +/*- +* Copyright (c) 2003-2004 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 +* in this position and unchanged. +* 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. + */ + +#if defined(HAVE_IO_H) +# include <io.h> +#endif +#if defined(HAVE_UNISTD_H) +# include <unistd.h> // for geteuid +#endif +#if defined(HAVE_INTTYPES_H) +# include <inttypes.h> // for uintptr_t +#endif +#include <sys/types.h> +#include <errno.h> // for ENOMEM +#include <sys/stat.h> // for struct stat +#include <time.h> // for time + +#if defined(WIN32) && !defined(__CYGWIN__) +# ifndef S_ISREG +# define S_ISREG(x) ((x) & _S_IFREG) +# endif +# ifndef S_ISLNK +# define S_ISLNK(x) (0) +# endif +#endif + + //--- archive_plarform.h + // + /* Set up defaults for internal error codes. */ +#ifndef ARCHIVE_ERRNO_FILE_FORMAT +#if HAVE_EFTYPE +#define ARCHIVE_ERRNO_FILE_FORMAT EFTYPE +#else +#if HAVE_EILSEQ +#define ARCHIVE_ERRNO_FILE_FORMAT EILSEQ +#else +#define ARCHIVE_ERRNO_FILE_FORMAT EINVAL +#endif +#endif +#endif + +#ifndef ARCHIVE_ERRNO_PROGRAMMER +#define ARCHIVE_ERRNO_PROGRAMMER EINVAL +#endif + +#ifndef ARCHIVE_ERRNO_MISC +#define ARCHIVE_ERRNO_MISC (-1) +#endif + + /* Select the best way to set/get hi-res timestamps. */ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + /* FreeBSD uses "timespec" members. */ +#define ARCHIVE_STAT_ATIME_NANOS(st) (st)->st_atimespec.tv_nsec +#define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctimespec.tv_nsec +#define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtimespec.tv_nsec +#define ARCHIVE_STAT_SET_ATIME_NANOS(st, n) (st)->st_atimespec.tv_nsec = (n) +#define ARCHIVE_STAT_SET_CTIME_NANOS(st, n) (st)->st_ctimespec.tv_nsec = (n) +#define ARCHIVE_STAT_SET_MTIME_NANOS(st, n) (st)->st_mtimespec.tv_nsec = (n) +#else +#if HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + /* Linux uses "tim" members. */ +#define ARCHIVE_STAT_ATIME_NANOS(pstat) (pstat)->st_atim.tv_nsec +#define ARCHIVE_STAT_CTIME_NANOS(pstat) (pstat)->st_ctim.tv_nsec +#define ARCHIVE_STAT_MTIME_NANOS(pstat) (pstat)->st_mtim.tv_nsec +#define ARCHIVE_STAT_SET_ATIME_NANOS(st, n) (st)->st_atim.tv_nsec = (n) +#define ARCHIVE_STAT_SET_CTIME_NANOS(st, n) (st)->st_ctim.tv_nsec = (n) +#define ARCHIVE_STAT_SET_MTIME_NANOS(st, n) (st)->st_mtim.tv_nsec = (n) +#else + /* If we can't find a better way, just use stubs. */ +#define ARCHIVE_STAT_ATIME_NANOS(pstat) 0 +#define ARCHIVE_STAT_CTIME_NANOS(pstat) 0 +#define ARCHIVE_STAT_MTIME_NANOS(pstat) 0 +#define ARCHIVE_STAT_SET_ATIME_NANOS(st, n) +#define ARCHIVE_STAT_SET_CTIME_NANOS(st, n) +#define ARCHIVE_STAT_SET_MTIME_NANOS(st, n) +#endif +#endif + + //--- archive.h + +#define ARCHIVE_BYTES_PER_RECORD 512 +#define ARCHIVE_DEFAULT_BYTES_PER_BLOCK 10240 + + /* Declare our basic types. */ + struct archive; + struct archive_entry; + + /* + * Error codes: Use archive_errno() and archive_error_string() + * to retrieve details. Unless specified otherwise, all functions + * that return 'int' use these codes. + */ +#define ARCHIVE_EOF 1 /* Found end of archive. */ +#define ARCHIVE_OK 0 /* Operation was successful. */ +#define ARCHIVE_RETRY (-10) /* Retry might succeed. */ +#define ARCHIVE_WARN (-20) /* Partial sucess. */ +#define ARCHIVE_FATAL (-30) /* No more operations are possible. */ + + /* + * As far as possible, archive_errno returns standard platform errno codes. + * Of course, the details vary by platform, so the actual definitions + * here are stored in "archive_platform.h". The symbols are listed here + * for reference; as a rule, clients should not need to know the exact + * platform-dependent error code. + */ + /* Unrecognized or invalid file format. */ + /* #define ARCHIVE_ERRNO_FILE_FORMAT */ + /* Illegal usage of the library. */ + /* #define ARCHIVE_ERRNO_PROGRAMMER_ERROR */ + /* Unknown or unclassified error. */ + /* #define ARCHIVE_ERRNO_MISC */ + + /* + * Callbacks are invoked to automatically read/write/open/close the archive. + * You can provide your own for complex tasks (like breaking archives + * across multiple tapes) or use standard ones built into the library. + */ + + /* Returns pointer and size of next block of data from archive. */ + typedef ssize_t archive_read_callback(struct archive *, void *_client_data, + const void **_buffer); +/* Returns size actually written, zero on EOF, -1 on error. */ +typedef ssize_t archive_write_callback(struct archive *, void *_client_data, + void *_buffer, size_t _length); +typedef int archive_open_callback(struct archive *, void *_client_data); +typedef int archive_close_callback(struct archive *, void *_client_data); + +/* + * Codes for archive_compression. + */ +#define ARCHIVE_COMPRESSION_NONE 0 +#define ARCHIVE_COMPRESSION_GZIP 1 +#define ARCHIVE_COMPRESSION_BZIP2 2 +#define ARCHIVE_COMPRESSION_COMPRESS 3 + +/* + * Codes returned by archive_format. + * + * Top 16 bits identifies the format family (e.g., "tar"); lower + * 16 bits indicate the variant. This is updated by read_next_header. + * Note that the lower 16 bits will often vary from entry to entry. + */ +#define ARCHIVE_FORMAT_BASE_MASK 0xff0000U +#define ARCHIVE_FORMAT_CPIO 0x10000 +#define ARCHIVE_FORMAT_CPIO_POSIX (ARCHIVE_FORMAT_CPIO | 1) +#define ARCHIVE_FORMAT_SHAR 0x20000 +#define ARCHIVE_FORMAT_SHAR_BASE (ARCHIVE_FORMAT_SHAR | 1) +#define ARCHIVE_FORMAT_SHAR_DUMP (ARCHIVE_FORMAT_SHAR | 2) +#define ARCHIVE_FORMAT_TAR 0x30000 +#define ARCHIVE_FORMAT_TAR_USTAR (ARCHIVE_FORMAT_TAR | 1) +#define ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE (ARCHIVE_FORMAT_TAR | 2) +#define ARCHIVE_FORMAT_TAR_PAX_RESTRICTED (ARCHIVE_FORMAT_TAR | 3) +#define ARCHIVE_FORMAT_TAR_GNUTAR (ARCHIVE_FORMAT_TAR | 4) +#define ARCHIVE_FORMAT_ISO9660 0x40000 +#define ARCHIVE_FORMAT_ISO9660_ROCKRIDGE (ARCHIVE_FORMAT_ISO9660 | 1) +#define ARCHIVE_FORMAT_ZIP 0x50000 + +/*- + * Basic outline for reading an archive: + * 1) Ask archive_read_new for an archive reader object. + * 2) Update any global properties as appropriate. + * In particular, you'll certainly want to call appropriate + * archive_read_support_XXX functions. + * 3) Call archive_read_open_XXX to open the archive + * 4) Repeatedly call archive_read_next_header to get information about + * successive archive entries. Call archive_read_data to extract + * data for entries of interest. + * 5) Call archive_read_finish to end processing. + */ +struct archive *archive_read_new(void); + +/* + * The archive_read_support_XXX calls enable auto-detect for this + * archive handle. They also link in the necessary support code. + * For example, if you don't want bzlib linked in, don't invoke + * support_compression_bzip2(). The "all" functions provide the + * obvious shorthand. + */ +int archive_read_support_compression_all(struct archive *); +int archive_read_support_compression_bzip2(struct archive *); +int archive_read_support_compression_compress(struct archive *); +int archive_read_support_compression_gzip(struct archive *); +int archive_read_support_compression_none(struct archive *); + +int archive_read_support_format_all(struct archive *); +int archive_read_support_format_cpio(struct archive *); +int archive_read_support_format_gnutar(struct archive *); +int archive_read_support_format_iso9660(struct archive *); +int archive_read_support_format_tar(struct archive *); +int archive_read_support_format_tp(struct archive *a); +int archive_read_support_format_zip(struct archive *); + + +/* Open the archive using callbacks for archive I/O. */ +int archive_read_open(struct archive *, void *_client_data, + archive_open_callback *, archive_read_callback *, + archive_close_callback *); + +/* + * The archive_read_open_file function is a convenience function built + * on archive_read_open that uses a canned callback suitable for + * common situations. Note that a NULL filename indicates stdin. + */ +int archive_read_open_file(struct archive *, const char *_file, + size_t _block_size); +int archive_read_open_fd(struct archive *, int _fd, + size_t _block_size); + +/* Parses and returns next entry header. */ +int archive_read_next_header(struct archive *, + struct archive_entry **); + +/* + * Retrieve the byte offset in UNCOMPRESSED data where last-read + * header started. + */ +int64_t archive_read_header_position(struct archive *); + +/* Read data from the body of an entry. Similar to read(2). */ +ssize_t archive_read_data(struct archive *, void *, size_t); +/* + * A zero-copy version of archive_read_data that also exposes the file offset + * of each returned block. Note that the client has no way to specify + * the desired size of the block. The API does gaurantee that offsets will + * be strictly increasing and that returned blocks will not overlap. + */ +int archive_read_data_block(struct archive *a, + const void **buff, size_t *size, off_t *offset); + +/*- + * Some convenience functions that are built on archive_read_data: + * 'skip': skips entire entry + * 'into_buffer': writes data into memory buffer that you provide + * 'into_fd': writes data to specified filedes + */ +int archive_read_data_skip(struct archive *); +int archive_read_data_into_buffer(struct archive *, void *buffer, + ssize_t len); +int archive_read_data_into_fd(struct archive *, int fd); + +/*- + * Convenience function to recreate the current entry (whose header + * has just been read) on disk. + * + * This does quite a bit more than just copy data to disk. It also: + * - Creates intermediate directories as required. + * - Manages directory permissions: non-writable directories will + * be initially created with write permission enabled; when the + * archive is closed, dir permissions are edited to the values specified + * in the archive. + * - Checks hardlinks: hardlinks will not be extracted unless the + * linked-to file was also extracted within the same session. (TODO) + */ + +/* The "flags" argument selects optional behavior, 'OR' the flags you want. */ +/* TODO: The 'Default' comments here are not quite correct; clean this up. */ +#define ARCHIVE_EXTRACT_OWNER (1) /* Default: owner/group not restored */ +#define ARCHIVE_EXTRACT_PERM (2) /* Default: restore perm only for reg file*/ +#define ARCHIVE_EXTRACT_TIME (4) /* Default: mod time not restored */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE (8) /* Default: Replace files on disk */ +#define ARCHIVE_EXTRACT_UNLINK (16) /* Default: don't unlink existing files */ +#define ARCHIVE_EXTRACT_ACL (32) /* Default: don't restore ACLs */ +#define ARCHIVE_EXTRACT_FFLAGS (64) /* Default: don't restore fflags */ + +int archive_read_extract(struct archive *, struct archive_entry *, + int flags); +void archive_read_extract_set_progress_callback(struct archive *, + void (*_progress_func)(void *), void *_user_data); + +/* Close the file and release most resources. */ +int archive_read_close(struct archive *); +/* Release all resources and destroy the object. */ +/* Note that archive_read_finish will call archive_read_close for you. */ +void archive_read_finish(struct archive *); + +/*- + * To create an archive: + * 1) Ask archive_write_new for a archive writer object. + * 2) Set any global properties. In particular, you should set + * the compression and format to use. + * 3) Call archive_write_open to open the file (most people + * will use archive_write_open_file or archive_write_open_fd, + * which provide convenient canned I/O callbacks for you). + * 4) For each entry: + * - construct an appropriate struct archive_entry structure + * - archive_write_header to write the header + * - archive_write_data to write the entry data + * 5) archive_write_close to close the output + * 6) archive_write_finish to cleanup the writer and release resources + */ +struct archive *archive_write_new(void); +int archive_write_set_bytes_per_block(struct archive *, + int bytes_per_block); +/* XXX This is badly misnamed; suggestions appreciated. XXX */ +int archive_write_set_bytes_in_last_block(struct archive *, + int bytes_in_last_block); + +int archive_write_set_compression_bzip2(struct archive *); +int archive_write_set_compression_gzip(struct archive *); +int archive_write_set_compression_none(struct archive *); +/* A convenience function to set the format based on the code or name. */ +int archive_write_set_format(struct archive *, int format_code); +int archive_write_set_format_by_name(struct archive *, + const char *name); +/* To minimize link pollution, use one or more of the following. */ +int archive_write_set_format_cpio(struct archive *); +/* TODO: int archive_write_set_format_old_tar(struct archive *); */ +int archive_write_set_format_pax(struct archive *); +int archive_write_set_format_pax_restricted(struct archive *); +int archive_write_set_format_shar(struct archive *); +int archive_write_set_format_shar_dump(struct archive *); +int archive_write_set_format_ustar(struct archive *); +int archive_write_open(struct archive *, void *, + archive_open_callback *, archive_write_callback *, + archive_close_callback *); +int archive_write_open_fd(struct archive *, int _fd); +int archive_write_open_file(struct archive *, const char *_file); + +/* + * Note that the library will truncate writes beyond the size provided + * to archive_write_header or pad if the provided data is short. + */ +int archive_write_header(struct archive *, + struct archive_entry *); +/* TODO: should be ssize_t, but that might require .so version bump? */ +int archive_write_data(struct archive *, const void *, size_t); +int archive_write_close(struct archive *); +void archive_write_finish(struct archive *); + +/* + * Accessor functions to read/set various information in + * the struct archive object: + */ +/* Bytes written after compression or read before decompression. */ +int64_t archive_position_compressed(struct archive *); +/* Bytes written to compressor or read from decompressor. */ +int64_t archive_position_uncompressed(struct archive *); + +const char *archive_compression_name(struct archive *); +int archive_compression(struct archive *); +int archive_errno(struct archive *); +const char *archive_error_string(struct archive *); +const char *archive_format_name(struct archive *); +int archive_format(struct archive *); +void archive_set_error(struct archive *, int _err, const char *fmt, ...); + +//--- archive_string.h +/* + * Basic resizable/reusable string support a la Java's "StringBuffer." + * + * Unlike sbuf(9), the buffers here are fully reusable and track the + * length throughout. + * + * Note that all visible symbols here begin with "__archive" as they + * are internal symbols not intended for anyone outside of this library + * to see or use. + */ + +struct archive_string { + char *s; /* Pointer to the storage */ + size_t length; /* Length of 's' */ + size_t buffer_length; /* Length of malloc-ed storage */ +}; + +/* Initialize an archive_string object on the stack or elsewhere. */ +#define archive_string_init(a) \ + do { (a)->s = NULL; (a)->length = 0; (a)->buffer_length = 0; } while(0) + +/* Append a C char to an archive_string, resizing as necessary. */ +struct archive_string * +__archive_strappend_char(struct archive_string *, char); +#define archive_strappend_char __archive_strappend_char + +/* Append a char to an archive_string using UTF8. */ +struct archive_string * +__archive_strappend_char_UTF8(struct archive_string *, int); +#define archive_strappend_char_UTF8 __archive_strappend_char_UTF8 + +/* Append an integer in the specified base (2 <= base <= 16). */ +struct archive_string * +__archive_strappend_int(struct archive_string *as, int d, int base); +#define archive_strappend_int __archive_strappend_int + +/* Basic append operation. */ +struct archive_string * +__archive_string_append(struct archive_string *as, const char *p, size_t s); + +/* Ensure that the underlying buffer is at least as large as the request. */ +struct archive_string * +__archive_string_ensure(struct archive_string *, size_t); +#define archive_string_ensure __archive_string_ensure + +/* Append C string, which may lack trailing \0. */ +struct archive_string * +__archive_strncat(struct archive_string *, const char *, size_t); +#define archive_strncat __archive_strncat + +/* Append a C string to an archive_string, resizing as necessary. */ +#define archive_strcat(as,p) __archive_string_append((as),(p),strlen(p)) + +/* Copy a C string to an archive_string, resizing as necessary. */ +#define archive_strcpy(as,p) \ + ((as)->length = 0, __archive_string_append((as), (p), strlen(p))) + +/* Copy a C string to an archive_string with limit, resizing as necessary. */ +#define archive_strncpy(as,p,l) \ + ((as)->length=0, archive_strncat((as), (p), (l))) + +/* Return length of string. */ +#define archive_strlen(a) ((a)->length) + +/* Set string length to zero. */ +#define archive_string_empty(a) ((a)->length = 0) + +/* Release any allocated storage resources. */ +void __archive_string_free(struct archive_string *); +#define archive_string_free __archive_string_free + +/* Like 'vsprintf', but resizes the underlying string as necessary. */ +void __archive_string_vsprintf(struct archive_string *, const char *, + va_list); +#define archive_string_vsprintf __archive_string_vsprintf +//--- archive_private.h + +#define ARCHIVE_WRITE_MAGIC (0xb0c5c0deU) +#define ARCHIVE_READ_MAGIC (0xdeb0c5U) + +struct archive { + /* + * The magic/state values are used to sanity-check the + * client's usage. If an API function is called at a + * rediculous time, or the client passes us an invalid + * pointer, these values allow me to catch that. + */ + unsigned magic; + unsigned state; + + struct archive_entry *entry; + uid_t user_uid; /* UID of current user. */ + + /* Dev/ino of the archive being read/written. */ + dev_t skip_file_dev; + ino_t skip_file_ino; + + /* Utility: Pointer to a block of nulls. */ + const unsigned char *nulls; + size_t null_length; + + /* + * Used by archive_read_data() to track blocks and copy + * data to client buffers, filling gaps with zero bytes. + */ + const char *read_data_block; + off_t read_data_offset; + off_t read_data_output_offset; + size_t read_data_remaining; + + /* Callbacks to open/read/write/close archive stream. */ + archive_open_callback *client_opener; + archive_read_callback *client_reader; + archive_write_callback *client_writer; + archive_close_callback *client_closer; + void *client_data; + + /* + * Blocking information. Note that bytes_in_last_block is + * misleadingly named; I should find a better name. These + * control the final output from all compressors, including + * compression_none. + */ + int bytes_per_block; + int bytes_in_last_block; + + /* + * These control whether data within a gzip/bzip2 compressed + * stream gets padded or not. If pad_uncompressed is set, + * the data will be padded to a full block before being + * compressed. The pad_uncompressed_byte determines the value + * that will be used for padding. Note that these have no + * effect on compression "none." + */ + int pad_uncompressed; + int pad_uncompressed_byte; /* TODO: Support this. */ + + /* Position in UNCOMPRESSED data stream. */ + off_t file_position; + /* Position in COMPRESSED data stream. */ + off_t raw_position; + /* File offset of beginning of most recently-read header. */ + off_t header_position; + + /* + * Detection functions for decompression: bid functions are + * given a block of data from the beginning of the stream and + * can bid on whether or not they support the data stream. + * General guideline: bid the number of bits that you actually + * test, e.g., 16 if you test a 2-byte magic value. The + * highest bidder will have their init function invoked, which + * can set up pointers to specific handlers. + * + * On write, the client just invokes an archive_write_set function + * which sets up the data here directly. + */ + int compression_code; /* Currently active compression. */ + const char *compression_name; + struct { + int (*bid)(const void *buff, size_t); + int (*init)(struct archive *, const void *buff, size_t); + } decompressors[4]; + /* Read/write data stream (with compression). */ + void *compression_data; /* Data for (de)compressor. */ + int (*compression_init)(struct archive *); /* Initialize. */ + int (*compression_finish)(struct archive *); + int (*compression_write)(struct archive *, const void *, size_t); + /* + * Read uses a peek/consume I/O model: the decompression code + * returns a pointer to the requested block and advances the + * file position only when requested by a consume call. This + * reduces copying and also simplifies look-ahead for format + * detection. + */ + ssize_t (*compression_read_ahead)(struct archive *, + const void **, size_t request); + ssize_t (*compression_read_consume)(struct archive *, size_t); + + /* + * Format detection is mostly the same as compression + * detection, with two significant differences: The bidders + * use the read_ahead calls above to examine the stream rather + * than having the supervisor hand them a block of data to + * examine, and the auction is repeated for every header. + * Winning bidders should set the archive_format and + * archive_format_name appropriately. Bid routines should + * check archive_format and decline to bid if the format of + * the last header was incompatible. + * + * Again, write support is considerably simpler because there's + * no need for an auction. + */ + int archive_format; + const char *archive_format_name; + + struct archive_format_descriptor { + int (*bid)(struct archive *); + int (*read_header)(struct archive *, struct archive_entry *); + int (*read_data)(struct archive *, const void **, size_t *, off_t *); + int (*read_data_skip)(struct archive *); + int (*cleanup)(struct archive *); + void *format_data; /* Format-specific data for readers. */ + } formats[8]; + struct archive_format_descriptor *format; /* Active format. */ + + /* + * Storage for format-specific data. Note that there can be + * multiple format readers active at one time, so we need to + * allow for multiple format readers to have their data + * available. The pformat_data slot here is the solution: on + * read, it is gauranteed to always point to a void* variable + * that the format can use. + */ + void **pformat_data; /* Pointer to current format_data. */ + void *format_data; /* Used by writers. */ + + /* + * Pointers to format-specific functions for writing. They're + * initialized by archive_write_set_format_XXX() calls. + */ + int (*format_init)(struct archive *); /* Only used on write. */ + int (*format_finish)(struct archive *); + int (*format_finish_entry)(struct archive *); + int (*format_write_header)(struct archive *, + struct archive_entry *); + int (*format_write_data)(struct archive *, + const void *buff, size_t); + + /* + * Various information needed by archive_extract. + */ + struct extract *extract; + void (*extract_progress)(void *); + void *extract_progress_user_data; + void (*cleanup_archive_extract)(struct archive *); + + int archive_error_number; + const char *error; + struct archive_string error_string; +}; + + +#define ARCHIVE_STATE_ANY 0xFFFFU +#define ARCHIVE_STATE_NEW 1U +#define ARCHIVE_STATE_HEADER 2U +#define ARCHIVE_STATE_DATA 4U +#define ARCHIVE_STATE_EOF 8U +#define ARCHIVE_STATE_CLOSED 0x10U +#define ARCHIVE_STATE_FATAL 0x8000U + +/* Check magic value and state; exit if it isn't valid. */ +void __archive_check_magic(struct archive *, unsigned magic, + unsigned state, const char *func); + + +int __archive_read_register_format(struct archive *a, + void *format_data, + int (*bid)(struct archive *), + int (*read_header)(struct archive *, struct archive_entry *), + int (*read_data)(struct archive *, const void **, size_t *, off_t *), + int (*read_data_skip)(struct archive *), + int (*cleanup)(struct archive *)); + +int __archive_read_register_compression(struct archive *a, + int (*bid)(const void *, size_t), + int (*init)(struct archive *, const void *, size_t)); + +void __archive_errx(int retvalue, const char *msg); + +#define err_combine(a,b) ((a) < (b) ? (a) : (b)) + + + /* + * Utility function to format a USTAR header into a buffer. If + * "strict" is set, this tries to create the absolutely most portable + * version of a ustar header. If "strict" is set to 0, then it will + * relax certain requirements. + * + * Generally, format-specific declarations don't belong in this + * header; this is a rare example of a function that is shared by + * two very similar formats (ustar and pax). + */ + int + __archive_write_format_header_ustar(struct archive *, char buff[512], + struct archive_entry *, int tartype, int strict); + +//--- archive_entry.h + +/* + * Description of an archive entry. + * + * Basically, a "struct stat" with a few text fields added in. + * + * TODO: Add "comment", "charset", and possibly other entries that are + * supported by "pax interchange" format. However, GNU, ustar, cpio, + * and other variants don't support these features, so they're not an + * excruciatingly high priority right now. + * + * TODO: "pax interchange" format allows essentially arbitrary + * key/value attributes to be attached to any entry. Supporting + * such extensions may make this library useful for special + * applications (e.g., a package manager could attach special + * package-management attributes to each entry). + */ +struct archive_entry; + +/* + * Basic object manipulation + */ + +struct archive_entry *archive_entry_clear(struct archive_entry *); +/* The 'clone' function does a deep copy; all of the strings are copied too. */ +struct archive_entry *archive_entry_clone(struct archive_entry *); +void archive_entry_free(struct archive_entry *); +struct archive_entry *archive_entry_new(void); + +/* + * Retrieve fields from an archive_entry. + */ + +time_t archive_entry_atime(struct archive_entry *); +long archive_entry_atime_nsec(struct archive_entry *); +time_t archive_entry_ctime(struct archive_entry *); +long archive_entry_ctime_nsec(struct archive_entry *); +dev_t archive_entry_dev(struct archive_entry *); +void archive_entry_fflags(struct archive_entry *, + unsigned long *set, unsigned long *clear); +const char *archive_entry_fflags_text(struct archive_entry *); +gid_t archive_entry_gid(struct archive_entry *); +const char *archive_entry_gname(struct archive_entry *); +const wchar_t *archive_entry_gname_w(struct archive_entry *); +const char *archive_entry_hardlink(struct archive_entry *); +const wchar_t *archive_entry_hardlink_w(struct archive_entry *); +ino_t archive_entry_ino(struct archive_entry *); +mode_t archive_entry_mode(struct archive_entry *); +time_t archive_entry_mtime(struct archive_entry *); +long archive_entry_mtime_nsec(struct archive_entry *); +const char *archive_entry_pathname(struct archive_entry *); +const wchar_t *archive_entry_pathname_w(struct archive_entry *); +dev_t archive_entry_rdev(struct archive_entry *); +int64_t archive_entry_size(struct archive_entry *); +const struct stat *archive_entry_stat(struct archive_entry *); +const char *archive_entry_symlink(struct archive_entry *); +const wchar_t *archive_entry_symlink_w(struct archive_entry *); +uid_t archive_entry_uid(struct archive_entry *); +const char *archive_entry_uname(struct archive_entry *); +const wchar_t *archive_entry_uname_w(struct archive_entry *); + +/* + * Set fields in an archive_entry. + * + * Note that string 'set' functions do not copy the string, only the pointer. + * In contrast, 'copy' functions do copy the object pointed to. + */ + +void archive_entry_copy_stat(struct archive_entry *, const struct stat *); +void archive_entry_set_atime(struct archive_entry *, time_t, long); +void archive_entry_set_ctime(struct archive_entry *, time_t, long); +void archive_entry_set_fflags(struct archive_entry *, + unsigned long set, unsigned long clear); +/* Returns pointer to start of first invalid token, or NULL if none. */ +/* Note that all recognized tokens are processed, regardless. */ +const wchar_t *archive_entry_copy_fflags_text_w(struct archive_entry *, + const wchar_t *); +void archive_entry_set_gid(struct archive_entry *, gid_t); +void archive_entry_set_gname(struct archive_entry *, const char *); +void archive_entry_copy_gname_w(struct archive_entry *, const wchar_t *); +void archive_entry_set_hardlink(struct archive_entry *, const char *); +void archive_entry_copy_hardlink(struct archive_entry *, const char *); +void archive_entry_copy_hardlink_w(struct archive_entry *, const wchar_t *); +void archive_entry_set_link(struct archive_entry *, const char *); +void archive_entry_set_mode(struct archive_entry *, mode_t); +void archive_entry_set_mtime(struct archive_entry *, time_t, long); +void archive_entry_set_pathname(struct archive_entry *, const char *); +void archive_entry_copy_pathname(struct archive_entry *, const char *); +void archive_entry_copy_pathname_w(struct archive_entry *, const wchar_t *); +void archive_entry_set_size(struct archive_entry *, int64_t); +void archive_entry_set_symlink(struct archive_entry *, const char *); +void archive_entry_copy_symlink_w(struct archive_entry *, const wchar_t *); +void archive_entry_set_uid(struct archive_entry *, uid_t); +void archive_entry_set_uname(struct archive_entry *, const char *); +void archive_entry_copy_uname_w(struct archive_entry *, const wchar_t *); + +/* + * ACL routines. This used to simply store and return text-format ACL + * strings, but that proved insufficient for a number of reasons: + * = clients need control over uname/uid and gname/gid mappings + * = there are many different ACL text formats + * = would like to be able to read/convert archives containing ACLs + * on platforms that lack ACL libraries + */ + +/* + * Permission bits mimic POSIX.1e. Note that I've not followed POSIX.1e's + * "permset"/"perm" abstract type nonsense. A permset is just a simple + * bitmap, following long-standing Unix tradition. + */ +#define ARCHIVE_ENTRY_ACL_EXECUTE 1 +#define ARCHIVE_ENTRY_ACL_WRITE 2 +#define ARCHIVE_ENTRY_ACL_READ 4 + +/* We need to be able to specify either or both of these. */ +#define ARCHIVE_ENTRY_ACL_TYPE_ACCESS 256 +#define ARCHIVE_ENTRY_ACL_TYPE_DEFAULT 512 + +/* Tag values mimic POSIX.1e */ +#define ARCHIVE_ENTRY_ACL_USER 10001 /* Specified user. */ +#define ARCHIVE_ENTRY_ACL_USER_OBJ 10002 /* User who owns the file. */ +#define ARCHIVE_ENTRY_ACL_GROUP 10003 /* Specified group. */ +#define ARCHIVE_ENTRY_ACL_GROUP_OBJ 10004 /* Group who owns the file. */ +#define ARCHIVE_ENTRY_ACL_MASK 10005 /* Modify group access. */ +#define ARCHIVE_ENTRY_ACL_OTHER 10006 /* Public. */ + +/* + * Set the ACL by clearing it and adding entries one at a time. + * Unlike the POSIX.1e ACL routines, you must specify the type + * (access/default) for each entry. Internally, the ACL data is just + * a soup of entries. API calls here allow you to retrieve just the + * entries of interest. This design (which goes against the spirit of + * POSIX.1e) is useful for handling archive formats that combine + * default and access information in a single ACL list. + */ +void archive_entry_acl_clear(struct archive_entry *); +void archive_entry_acl_add_entry(struct archive_entry *, + int type, int permset, int tag, int qual, const char *name); +void archive_entry_acl_add_entry_w(struct archive_entry *, + int type, int permset, int tag, int qual, const wchar_t *name); + +/* + * To retrieve the ACL, first "reset", then repeatedly ask for the + * "next" entry. The want_type parameter allows you to request only + * access entries or only default entries. + */ +int archive_entry_acl_reset(struct archive_entry *, int want_type); +int archive_entry_acl_next(struct archive_entry *, int want_type, + int *type, int *permset, int *tag, int *qual, const char **name); +int archive_entry_acl_next_w(struct archive_entry *, int want_type, + int *type, int *permset, int *tag, int *qual, + const wchar_t **name); + +/* + * Construct a text-format ACL. The flags argument is a bitmask that + * can include any of the following: + * + * ARCHIVE_ENTRY_ACL_TYPE_ACCESS - Include access entries. + * ARCHIVE_ENTRY_ACL_TYPE_DEFAULT - Include default entries. + * ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID - Include extra numeric ID field in + * each ACL entry. (As used by 'star'.) + * ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT - Include "default:" before each + * default ACL entry. + */ +#define ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID 1024 +#define ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT 2048 +const wchar_t *archive_entry_acl_text_w(struct archive_entry *, int flags); + +/* Return a count of entries matching 'want_type' */ +int archive_entry_acl_count(struct archive_entry *, int want_type); + +/* + * Private ACL parser. This is private because it handles some + * very weird formats that clients should not be messing with. + * Clients should only deal with their platform-native formats. + * Because of the need to support many formats cleanly, new arguments + * are likely to get added on a regular basis. Clients who try to use + * this interface are likely to be surprised when it changes. + * + * You were warned! + */ +int __archive_entry_acl_parse_w(struct archive_entry *, + const wchar_t *, int type); +//--- archive_write.h + +/* + * Allocate, initialize and return an archive object. + */ + struct archive * +archive_write_new(void) +{ + struct archive *a; + unsigned char *nulls; + + a = (struct archive*)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->magic = ARCHIVE_WRITE_MAGIC; +#ifdef HAVE_GETEUID + a->user_uid = geteuid(); +#endif + a->bytes_per_block = ARCHIVE_DEFAULT_BYTES_PER_BLOCK; + a->bytes_in_last_block = -1; /* Default */ + a->state = ARCHIVE_STATE_NEW; + a->pformat_data = &(a->format_data); + + /* Initialize a block of nulls for padding purposes. */ + a->null_length = 1024; + nulls = (unsigned char*)malloc(a->null_length); + if (nulls == NULL) { + free(a); + return (NULL); + } + memset(nulls, 0, a->null_length); + a->nulls = nulls; + /* + * Set default compression, but don't set a default format. + * Were we to set a default format here, we would force every + * client to link in support for that format, even if they didn't + * ever use it. + */ + archive_write_set_compression_none(a); + return (a); +} + + +/* + * Set the block size. Returns 0 if successful. + */ + int +archive_write_set_bytes_per_block(struct archive *a, int bytes_per_block) +{ + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, "archive_write_set_bytes_per_block"); + a->bytes_per_block = bytes_per_block; + return (ARCHIVE_OK); +} + + +/* + * Set the size for the last block. + * Returns 0 if successful. + */ + int +archive_write_set_bytes_in_last_block(struct archive *a, int bytes) +{ + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_ANY, "archive_write_set_bytes_in_last_block"); + a->bytes_in_last_block = bytes; + return (ARCHIVE_OK); +} + + +/* + * Open the archive using the current settings. + */ + int +archive_write_open(struct archive *a, void *client_data, + archive_open_callback *opener, archive_write_callback *writer, + archive_close_callback *closer) +{ + int ret; + + ret = ARCHIVE_OK; + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, "archive_write_open"); + archive_string_empty(&a->error_string); + a->state = ARCHIVE_STATE_HEADER; + a->client_data = client_data; + a->client_writer = writer; + a->client_opener = opener; + a->client_closer = closer; + ret = (a->compression_init)(a); + if (a->format_init && ret == ARCHIVE_OK) + ret = (a->format_init)(a); + return (ret); +} + + +/* + * Close out the archive. + * + * Be careful: user might just call write_new and then write_finish. + * Don't assume we actually wrote anything or performed any non-trivial + * initialization. + */ + int +archive_write_close(struct archive *a) +{ + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_ANY, "archive_write_close"); + + /* Finish the last entry. */ + if (a->state & ARCHIVE_STATE_DATA) + ((a->format_finish_entry)(a)); + + /* Finish off the archive. */ + if (a->format_finish != NULL) + (a->format_finish)(a); + + /* Finish the compression and close the stream. */ + if (a->compression_finish != NULL) + (a->compression_finish)(a); + + a->state = ARCHIVE_STATE_CLOSED; + return (ARCHIVE_OK); +} + +/* + * Destroy the archive structure. + */ + void +archive_write_finish(struct archive *a) +{ + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_ANY, "archive_write_finish"); + if (a->state != ARCHIVE_STATE_CLOSED) + archive_write_close(a); + + /* Release various dynamic buffers. */ + free((void *)(uintptr_t)(const void *)a->nulls); + archive_string_free(&a->error_string); + a->magic = 0; + free(a); +} + + +/* + * Write the appropriate header. + */ + int +archive_write_header(struct archive *a, struct archive_entry *entry) +{ + int ret; + + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, "archive_write_header"); + archive_string_empty(&a->error_string); + + /* Finish last entry. */ + if (a->state & ARCHIVE_STATE_DATA) + ((a->format_finish_entry)(a)); + + if (archive_entry_dev(entry) == a->skip_file_dev && + archive_entry_ino(entry) == a->skip_file_ino) { + archive_set_error(a, 0, "Can't add archive to itself"); + return (ARCHIVE_WARN); + } + + /* Format and write header. */ + ret = ((a->format_write_header)(a, entry)); + + a->state = ARCHIVE_STATE_DATA; + return (ret); +} + +/* + * Note that the compressor is responsible for blocking. + */ +/* Should be "ssize_t", but that breaks the ABI. <sigh> */ + int +archive_write_data(struct archive *a, const void *buff, size_t s) +{ + int ret; + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_DATA, "archive_write_data"); + archive_string_empty(&a->error_string); + ret = (a->format_write_data)(a, buff, s); + return (ret == ARCHIVE_OK ? (ssize_t)s : -1); +} + +//--- archive_write_set_compression_none.c + +static int archive_compressor_none_finish(struct archive *a); +static int archive_compressor_none_init(struct archive *); +static int archive_compressor_none_write(struct archive *, const void *, + size_t); + +struct archive_none { + char *buffer; + ssize_t buffer_size; + char *next; /* Current insert location */ + ssize_t avail; /* Free space left in buffer */ +}; + + int +archive_write_set_compression_none(struct archive *a) +{ + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, "archive_write_set_compression_none"); + a->compression_init = &archive_compressor_none_init; + a->compression_code = ARCHIVE_COMPRESSION_NONE; + a->compression_name = "none"; + return (0); +} + +/* + * Setup callback. + */ + static int +archive_compressor_none_init(struct archive *a) +{ + int ret; + struct archive_none *state; + + a->compression_code = ARCHIVE_COMPRESSION_NONE; + a->compression_name = "none"; + + if (a->client_opener != NULL) { + ret = (a->client_opener)(a, a->client_data); + if (ret != 0) + return (ret); + } + + state = (struct archive_none *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate data for output buffering"); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + state->buffer_size = a->bytes_per_block; + state->buffer = (char*)malloc(state->buffer_size); + + if (state->buffer == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate output buffer"); + free(state); + return (ARCHIVE_FATAL); + } + + state->next = state->buffer; + state->avail = state->buffer_size; + + a->compression_data = state; + a->compression_write = archive_compressor_none_write; + a->compression_finish = archive_compressor_none_finish; + return (ARCHIVE_OK); +} + +/* + * Write data to the stream. + */ + static int +archive_compressor_none_write(struct archive *a, const void *vbuff, + size_t length) +{ + const char *buff; + ssize_t remaining, to_copy; + ssize_t bytes_written; + struct archive_none *state; + + state = (struct archive_none*)a->compression_data; + buff = (const char*)vbuff; + if (a->client_writer == NULL) { + archive_set_error(a, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + remaining = length; + while (remaining > 0) { + /* + * If we have a full output block, write it and reset the + * output buffer. + */ + if (state->avail == 0) { + bytes_written = (a->client_writer)(a, a->client_data, + state->buffer, state->buffer_size); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + /* XXX TODO: if bytes_written < state->buffer_size */ + a->raw_position += bytes_written; + state->next = state->buffer; + state->avail = state->buffer_size; + } + + /* Now we have space in the buffer; copy new data into it. */ + to_copy = (remaining > state->avail) ? + state->avail : remaining; + memcpy(state->next, buff, to_copy); + state->next += to_copy; + state->avail -= to_copy; + buff += to_copy; + remaining -= to_copy; + } + a->file_position += length; + return (ARCHIVE_OK); +} + + +/* + * Finish the compression. + */ + static int +archive_compressor_none_finish(struct archive *a) +{ + ssize_t block_length; + ssize_t target_block_length; + ssize_t bytes_written; + int ret; + int ret2; + struct archive_none *state; + + state = (struct archive_none*)a->compression_data; + ret = ret2 = ARCHIVE_OK; + if (a->client_writer == NULL) { + archive_set_error(a, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + /* If there's pending data, pad and write the last block */ + if (state->next != state->buffer) { + block_length = state->buffer_size - state->avail; + + /* Tricky calculation to determine size of last block */ + target_block_length = block_length; + if (a->bytes_in_last_block <= 0) + /* Default or Zero: pad to full block */ + target_block_length = a->bytes_per_block; + else + /* Round to next multiple of bytes_in_last_block. */ + target_block_length = a->bytes_in_last_block * + ( (block_length + a->bytes_in_last_block - 1) / + a->bytes_in_last_block); + if (target_block_length > a->bytes_per_block) + target_block_length = a->bytes_per_block; + if (block_length < target_block_length) { + memset(state->next, 0, + target_block_length - block_length); + block_length = target_block_length; + } + bytes_written = (a->client_writer)(a, a->client_data, + state->buffer, block_length); + if (bytes_written <= 0) + ret = ARCHIVE_FATAL; + else { + a->raw_position += bytes_written; + ret = ARCHIVE_OK; + } + } + + /* Close the output */ + if (a->client_closer != NULL) + ret2 = (a->client_closer)(a, a->client_data); + + free(state->buffer); + free(state); + a->compression_data = NULL; + + return (ret != ARCHIVE_OK ? ret : ret2); +} + +//--- archive_check_magic.c + + static void +errmsg(const char *m) +{ + cmSystemTools::Error("CPack error: ", m); +} + + static void +diediedie(void) +{ + *(char *)0 = 1; /* Deliberately segfault and force a coredump. */ + _exit(1); /* If that didn't work, just exit with an error. */ +} + + static const char * +state_name(unsigned s) +{ + switch (s) { + case ARCHIVE_STATE_NEW: return ("new"); + case ARCHIVE_STATE_HEADER: return ("header"); + case ARCHIVE_STATE_DATA: return ("data"); + case ARCHIVE_STATE_EOF: return ("eof"); + case ARCHIVE_STATE_CLOSED: return ("closed"); + case ARCHIVE_STATE_FATAL: return ("fatal"); + default: return ("??"); + } +} + + + static void +write_all_states(int states) +{ + unsigned lowbit; + + /* A trick for computing the lowest set bit. */ + while ((lowbit = states & (-states)) != 0) { + states &= ~lowbit; /* Clear the low bit. */ + errmsg(state_name(lowbit)); + if (states != 0) + errmsg("/"); + } +} + +/* + * Check magic value and current state; bail if it isn't valid. + * + * This is designed to catch serious programming errors that violate + * the libarchive API. + */ + void +__archive_check_magic(struct archive *a, unsigned magic, unsigned state, + const char *function) +{ + if (a->magic != magic) { + errmsg("INTERNAL ERROR: Function "); + errmsg(function); + errmsg(" invoked with invalid struct archive structure.\n"); + diediedie(); + } + + if (state == ARCHIVE_STATE_ANY) + return; + + if ((a->state & state) == 0) { + errmsg("INTERNAL ERROR: Function '"); + errmsg(function); + errmsg("' invoked with archive structure in state '"); + write_all_states(a->state); + errmsg("', should be in state '"); + write_all_states(state); + errmsg("'\n"); + diediedie(); + } +} + +//--- archive_util.c + int +archive_errno(struct archive *a) +{ + return (a->archive_error_number); +} + + const char * +archive_error_string(struct archive *a) +{ + + if (a->error != NULL && *a->error != '\0') + return (a->error); + else + return ("(Empty error message)"); +} + + + int +archive_format(struct archive *a) +{ + return (a->archive_format); +} + + const char * +archive_format_name(struct archive *a) +{ + return (a->archive_format_name); +} + + + int +archive_compression(struct archive *a) +{ + return (a->compression_code); +} + + const char * +archive_compression_name(struct archive *a) +{ + return (a->compression_name); +} + + +/* + * Return a count of the number of compressed bytes processed. + */ + int64_t +archive_position_compressed(struct archive *a) +{ + return (a->raw_position); +} + +/* + * Return a count of the number of uncompressed bytes processed. + */ + int64_t +archive_position_uncompressed(struct archive *a) +{ + return (a->file_position); +} + + + void +archive_set_error(struct archive *a, int error_number, const char *fmt, ...) +{ + va_list ap; +#ifdef HAVE_STRERROR_R + char errbuff[512]; +#endif + char *errp; + + a->archive_error_number = error_number; + if (fmt == NULL) { + a->error = NULL; + return; + } + + va_start(ap, fmt); + archive_string_vsprintf(&(a->error_string), fmt, ap); + if (error_number > 0) { + archive_strcat(&(a->error_string), ": "); +#ifdef HAVE_STRERROR_R +#ifdef STRERROR_R_CHAR_P + errp = strerror_r(error_number, errbuff, sizeof(errbuff)); +#else + strerror_r(error_number, errbuff, sizeof(errbuff)); + errp = errbuff; +#endif +#else + /* Note: this is not threadsafe! */ + errp = strerror(error_number); +#endif + archive_strcat(&(a->error_string), errp); + } + a->error = a->error_string.s; + va_end(ap); +} + + void +__archive_errx(int retvalue, const char *msg) +{ + static const char *msg1 = "Fatal Internal Error in libarchive: "; + write(2, msg1, strlen(msg1)); + write(2, msg, strlen(msg)); + write(2, "\n", 1); + exit(retvalue); +} + +//--- archive_string.c + struct archive_string * +__archive_string_append(struct archive_string *as, const char *p, size_t s) +{ + __archive_string_ensure(as, as->length + s + 1); + memcpy(as->s + as->length, p, s); + as->s[as->length + s] = 0; + as->length += s; + return (as); +} + + void +__archive_string_free(struct archive_string *as) +{ + as->length = 0; + as->buffer_length = 0; + if (as->s != NULL) + free(as->s); +} + + struct archive_string * +__archive_string_ensure(struct archive_string *as, size_t s) +{ + if (as->s && (s <= as->buffer_length)) + return (as); + + if (as->buffer_length < 32) + as->buffer_length = 32; + while (as->buffer_length < s) + as->buffer_length *= 2; + as->s = (char*)realloc(as->s, as->buffer_length); + /* TODO: Return null instead and fix up all of our callers to + * handle this correctly. */ + if (as->s == NULL) + __archive_errx(1, "Out of memory"); + return (as); +} + + struct archive_string * +__archive_strncat(struct archive_string *as, const char *p, size_t n) +{ + size_t s; + const char *pp; + + /* Like strlen(p), except won't examine positions beyond p[n]. */ + s = 0; + pp = p; + while (*pp && s < n) { + pp++; + s++; + } + return (__archive_string_append(as, p, s)); +} + + struct archive_string * +__archive_strappend_char(struct archive_string *as, char c) +{ + return (__archive_string_append(as, &c, 1)); +} + + struct archive_string * +__archive_strappend_int(struct archive_string *as, int d, int base) +{ + static const char *digits = "0123457890abcdef"; + + if (d < 0) { + __archive_strappend_char(as, '-'); + d = -d; + } + if (d >= base) + __archive_strappend_int(as, d/base, base); + __archive_strappend_char(as, digits[d % base]); + return (as); +} + +//--- archive_entry.c +/* Obtain suitable wide-character manipulation functions. */ +#ifdef HAVE_WCHAR_H +#include <wchar.h> +#else +static size_t wcslen(const wchar_t *s) +{ + const wchar_t *p = s; + while (*p != L'\0') + ++p; + return p - s; +} +static wchar_t * wcscpy(wchar_t *s1, const wchar_t *s2) +{ + wchar_t *dest = s1; + while((*s1 = *s2) != L'\0') + ++s1, ++s2; + return dest; +} +#define wmemcpy(a,b,i) (wchar_t *)memcpy((a),(b),(i)*sizeof(wchar_t)) +/* Good enough for simple equality testing, but not for sorting. */ +#define wmemcmp(a,b,i) memcmp((a),(b),(i)*sizeof(wchar_t)) +#endif + +#undef max +#define max(a, b) ((a)>(b)?(a):(b)) + + /* + * Handle wide character (i.e., Unicode) and non-wide character + * strings transparently. + * + */ + + struct aes { + const char *aes_mbs; + char *aes_mbs_alloc; + const wchar_t *aes_wcs; + wchar_t *aes_wcs_alloc; + }; + +struct ae_acl { + struct ae_acl *next; + int type; /* E.g., access or default */ + int tag; /* E.g., user/group/other/mask */ + int permset; /* r/w/x bits */ + int id; /* uid/gid for user/group */ + struct aes name; /* uname/gname */ +}; + +static void aes_clean(struct aes *); +static void aes_copy(struct aes *dest, struct aes *src); +static const char * aes_get_mbs(struct aes *); +static const wchar_t * aes_get_wcs(struct aes *); +static void aes_set_mbs(struct aes *, const char *mbs); +static void aes_copy_mbs(struct aes *, const char *mbs); +/* static void aes_set_wcs(struct aes *, const wchar_t *wcs); */ +static void aes_copy_wcs(struct aes *, const wchar_t *wcs); + +static char * ae_fflagstostr(unsigned long bitset, unsigned long bitclear); +static const wchar_t *ae_wcstofflags(const wchar_t *stringp, + unsigned long *setp, unsigned long *clrp); +static void append_entry_w(wchar_t **wp, const wchar_t *prefix, int tag, + const wchar_t *wname, int perm, int id); +static void append_id_w(wchar_t **wp, int id); + +static int acl_special(struct archive_entry *entry, + int type, int permset, int tag); +static struct ae_acl *acl_new_entry(struct archive_entry *entry, + int type, int permset, int tag, int id); +static void next_field_w(const wchar_t **wp, const wchar_t **start, + const wchar_t **end, wchar_t *sep); +static int prefix_w(const wchar_t *start, const wchar_t *end, + const wchar_t *test); + + +/* + * Description of an archive entry. + * + * Basically, this is a "struct stat" with a few text fields added in. + * + * TODO: Add "comment", "charset", and possibly other entries + * that are supported by "pax interchange" format. However, GNU, ustar, + * cpio, and other variants don't support these features, so they're not an + * excruciatingly high priority right now. + * + * TODO: "pax interchange" format allows essentially arbitrary + * key/value attributes to be attached to any entry. Supporting + * such extensions may make this library useful for special + * applications (e.g., a package manager could attach special + * package-management attributes to each entry). There are tricky + * API issues involved, so this is not going to happen until + * there's a real demand for it. + * + * TODO: Design a good API for handling sparse files. + */ +struct archive_entry { + /* + * Note that ae_stat.st_mode & S_IFMT can be 0! + * + * This occurs when the actual file type of the object is not + * in the archive. For example, 'tar' archives store + * hardlinks without marking the type of the underlying + * object. + */ + struct stat ae_stat; + + /* + * Use aes here so that we get transparent mbs<->wcs conversions. + */ + struct aes ae_fflags_text; /* Text fflags per fflagstostr(3) */ + unsigned long ae_fflags_set; /* Bitmap fflags */ + unsigned long ae_fflags_clear; + struct aes ae_gname; /* Name of owning group */ + struct aes ae_hardlink; /* Name of target for hardlink */ + struct aes ae_pathname; /* Name of entry */ + struct aes ae_symlink; /* symlink contents */ + struct aes ae_uname; /* Name of owner */ + + struct ae_acl *acl_head; + struct ae_acl *acl_p; + int acl_state; /* See acl_next for details. */ + wchar_t *acl_text_w; +}; + + static void +aes_clean(struct aes *aes) +{ + if (aes->aes_mbs_alloc) { + free(aes->aes_mbs_alloc); + aes->aes_mbs_alloc = NULL; + } + if (aes->aes_wcs_alloc) { + free(aes->aes_wcs_alloc); + aes->aes_wcs_alloc = NULL; + } + memset(aes, 0, sizeof(*aes)); +} + + static void +aes_copy(struct aes *dest, struct aes *src) +{ + *dest = *src; + if (src->aes_mbs != NULL) { + dest->aes_mbs_alloc = strdup(src->aes_mbs); + dest->aes_mbs = dest->aes_mbs_alloc; + if (dest->aes_mbs == NULL) + __archive_errx(1, "No memory for aes_copy()"); + } + + if (src->aes_wcs != NULL) { + dest->aes_wcs_alloc = (wchar_t*)malloc((wcslen(src->aes_wcs) + 1) + * sizeof(wchar_t)); + dest->aes_wcs = dest->aes_wcs_alloc; + if (dest->aes_wcs == NULL) + __archive_errx(1, "No memory for aes_copy()"); + wcscpy(dest->aes_wcs_alloc, src->aes_wcs); + } +} + + static const char * +aes_get_mbs(struct aes *aes) +{ + if (aes->aes_mbs == NULL && aes->aes_wcs == NULL) + return NULL; + if (aes->aes_mbs == NULL && aes->aes_wcs != NULL) { + /* + * XXX Need to estimate the number of byte in the + * multi-byte form. Assume that, on average, wcs + * chars encode to no more than 3 bytes. There must + * be a better way... XXX + */ + int mbs_length = wcslen(aes->aes_wcs) * 3 + 64; + aes->aes_mbs_alloc = (char*)malloc(mbs_length); + aes->aes_mbs = aes->aes_mbs_alloc; + if (aes->aes_mbs == NULL) + __archive_errx(1, "No memory for aes_get_mbs()"); + wcstombs(aes->aes_mbs_alloc, aes->aes_wcs, mbs_length - 1); + aes->aes_mbs_alloc[mbs_length - 1] = 0; + } + return (aes->aes_mbs); +} + + static const wchar_t * +aes_get_wcs(struct aes *aes) +{ + if (aes->aes_wcs == NULL && aes->aes_mbs == NULL) + return NULL; + if (aes->aes_wcs == NULL && aes->aes_mbs != NULL) { + /* + * No single byte will be more than one wide character, + * so this length estimate will always be big enough. + */ + int wcs_length = strlen(aes->aes_mbs); + aes->aes_wcs_alloc + = (wchar_t*)malloc((wcs_length + 1) * sizeof(wchar_t)); + aes->aes_wcs = aes->aes_wcs_alloc; + if (aes->aes_wcs == NULL) + __archive_errx(1, "No memory for aes_get_wcs()"); + mbstowcs(aes->aes_wcs_alloc, aes->aes_mbs, wcs_length); + aes->aes_wcs_alloc[wcs_length] = 0; + } + return (aes->aes_wcs); +} + + static void +aes_set_mbs(struct aes *aes, const char *mbs) +{ + if (aes->aes_mbs_alloc) { + free(aes->aes_mbs_alloc); + aes->aes_mbs_alloc = NULL; + } + if (aes->aes_wcs_alloc) { + free(aes->aes_wcs_alloc); + aes->aes_wcs_alloc = NULL; + } + aes->aes_mbs = mbs; + aes->aes_wcs = NULL; +} + + static void +aes_copy_mbs(struct aes *aes, const char *mbs) +{ + if (aes->aes_mbs_alloc) { + free(aes->aes_mbs_alloc); + aes->aes_mbs_alloc = NULL; + } + if (aes->aes_wcs_alloc) { + free(aes->aes_wcs_alloc); + aes->aes_wcs_alloc = NULL; + } + aes->aes_mbs_alloc = (char*)malloc((strlen(mbs) + 1) * sizeof(char)); + if (aes->aes_mbs_alloc == NULL) + __archive_errx(1, "No memory for aes_copy_mbs()"); + strcpy(aes->aes_mbs_alloc, mbs); + aes->aes_mbs = aes->aes_mbs_alloc; + aes->aes_wcs = NULL; +} + +#if 0 + static void +aes_set_wcs(struct aes *aes, const wchar_t *wcs) +{ + if (aes->aes_mbs_alloc) { + free(aes->aes_mbs_alloc); + aes->aes_mbs_alloc = NULL; + } + if (aes->aes_wcs_alloc) { + free(aes->aes_wcs_alloc); + aes->aes_wcs_alloc = NULL; + } + aes->aes_mbs = NULL; + aes->aes_wcs = wcs; +} +#endif + + static void +aes_copy_wcs(struct aes *aes, const wchar_t *wcs) +{ + if (aes->aes_mbs_alloc) { + free(aes->aes_mbs_alloc); + aes->aes_mbs_alloc = NULL; + } + if (aes->aes_wcs_alloc) { + free(aes->aes_wcs_alloc); + aes->aes_wcs_alloc = NULL; + } + aes->aes_mbs = NULL; + aes->aes_wcs_alloc = (wchar_t*)malloc((wcslen(wcs) + 1) * sizeof(wchar_t)); + if (aes->aes_wcs_alloc == NULL) + __archive_errx(1, "No memory for aes_copy_wcs()"); + wcscpy(aes->aes_wcs_alloc, wcs); + aes->aes_wcs = aes->aes_wcs_alloc; +} + + struct archive_entry * +archive_entry_clear(struct archive_entry *entry) +{ + aes_clean(&entry->ae_fflags_text); + aes_clean(&entry->ae_gname); + aes_clean(&entry->ae_hardlink); + aes_clean(&entry->ae_pathname); + aes_clean(&entry->ae_symlink); + aes_clean(&entry->ae_uname); + archive_entry_acl_clear(entry); + memset(entry, 0, sizeof(*entry)); + return entry; +} + + struct archive_entry * +archive_entry_clone(struct archive_entry *entry) +{ + struct archive_entry *entry2; + + /* Allocate new structure and copy over all of the fields. */ + entry2 = (struct archive_entry*)malloc(sizeof(*entry2)); + if (entry2 == NULL) + return (NULL); + memset(entry2, 0, sizeof(*entry2)); + entry2->ae_stat = entry->ae_stat; + entry2->ae_fflags_set = entry->ae_fflags_set; + entry2->ae_fflags_clear = entry->ae_fflags_clear; + + aes_copy(&entry2->ae_fflags_text, &entry->ae_fflags_text); + aes_copy(&entry2->ae_gname, &entry->ae_gname); + aes_copy(&entry2->ae_hardlink, &entry->ae_hardlink); + aes_copy(&entry2->ae_pathname, &entry->ae_pathname); + aes_copy(&entry2->ae_symlink, &entry->ae_symlink); + aes_copy(&entry2->ae_uname, &entry->ae_uname); + + /* XXX TODO: Copy ACL data over as well. XXX */ + return (entry2); +} + + void +archive_entry_free(struct archive_entry *entry) +{ + archive_entry_clear(entry); + free(entry); +} + + struct archive_entry * +archive_entry_new(void) +{ + struct archive_entry *entry; + + entry = (struct archive_entry*)malloc(sizeof(*entry)); + if (entry == NULL) + return (NULL); + memset(entry, 0, sizeof(*entry)); + return (entry); +} + +/* + * Functions for reading fields from an archive_entry. + */ + + time_t +archive_entry_atime(struct archive_entry *entry) +{ + return (entry->ae_stat.st_atime); +} + + long +archive_entry_atime_nsec(struct archive_entry *entry) +{ + (void)entry; /* entry can be unused here. */ + return (ARCHIVE_STAT_ATIME_NANOS(&entry->ae_stat)); +} + + time_t +archive_entry_ctime(struct archive_entry *entry) +{ + return (entry->ae_stat.st_ctime); +} + + long +archive_entry_ctime_nsec(struct archive_entry *entry) +{ + (void)entry; /* entry can be unused here. */ + return (ARCHIVE_STAT_CTIME_NANOS(&entry->ae_stat)); +} + + dev_t +archive_entry_dev(struct archive_entry *entry) +{ + return (entry->ae_stat.st_dev); +} + + void +archive_entry_fflags(struct archive_entry *entry, + unsigned long *set, unsigned long *clear) +{ + *set = entry->ae_fflags_set; + *clear = entry->ae_fflags_clear; +} + +/* + * Note: if text was provided, this just returns that text. If you + * really need the text to be rebuilt in a canonical form, set the + * text, ask for the bitmaps, then set the bitmaps. (Setting the + * bitmaps clears any stored text.) This design is deliberate: if + * we're editing archives, we don't want to discard flags just because + * they aren't supported on the current system. The bitmap<->text + * conversions are platform-specific (see below). + */ + const char * +archive_entry_fflags_text(struct archive_entry *entry) +{ + const char *f; + char *p; + + f = aes_get_mbs(&entry->ae_fflags_text); + if (f != NULL) + return (f); + + if (entry->ae_fflags_set == 0 && entry->ae_fflags_clear == 0) + return (NULL); + + p = ae_fflagstostr(entry->ae_fflags_set, entry->ae_fflags_clear); + if (p == NULL) + return (NULL); + + aes_copy_mbs(&entry->ae_fflags_text, p); + free(p); + f = aes_get_mbs(&entry->ae_fflags_text); + return (f); +} + + gid_t +archive_entry_gid(struct archive_entry *entry) +{ + return (entry->ae_stat.st_gid); +} + + const char * +archive_entry_gname(struct archive_entry *entry) +{ + return (aes_get_mbs(&entry->ae_gname)); +} + + const wchar_t * +archive_entry_gname_w(struct archive_entry *entry) +{ + return (aes_get_wcs(&entry->ae_gname)); +} + + const char * +archive_entry_hardlink(struct archive_entry *entry) +{ + return (aes_get_mbs(&entry->ae_hardlink)); +} + + const wchar_t * +archive_entry_hardlink_w(struct archive_entry *entry) +{ + return (aes_get_wcs(&entry->ae_hardlink)); +} + + ino_t +archive_entry_ino(struct archive_entry *entry) +{ + return (entry->ae_stat.st_ino); +} + + mode_t +archive_entry_mode(struct archive_entry *entry) +{ + return (entry->ae_stat.st_mode); +} + + time_t +archive_entry_mtime(struct archive_entry *entry) +{ + return (entry->ae_stat.st_mtime); +} + + long +archive_entry_mtime_nsec(struct archive_entry *entry) +{ + (void)entry; /* entry can be unused here. */ + return (ARCHIVE_STAT_MTIME_NANOS(&entry->ae_stat)); +} + + const char * +archive_entry_pathname(struct archive_entry *entry) +{ + return (aes_get_mbs(&entry->ae_pathname)); +} + + const wchar_t * +archive_entry_pathname_w(struct archive_entry *entry) +{ + return (aes_get_wcs(&entry->ae_pathname)); +} + + dev_t +archive_entry_rdev(struct archive_entry *entry) +{ + return (entry->ae_stat.st_rdev); +} + + int64_t +archive_entry_size(struct archive_entry *entry) +{ + return (entry->ae_stat.st_size); +} + + const struct stat * +archive_entry_stat(struct archive_entry *entry) +{ + return (&entry->ae_stat); +} + + const char * +archive_entry_symlink(struct archive_entry *entry) +{ + return (aes_get_mbs(&entry->ae_symlink)); +} + + const wchar_t * +archive_entry_symlink_w(struct archive_entry *entry) +{ + return (aes_get_wcs(&entry->ae_symlink)); +} + + uid_t +archive_entry_uid(struct archive_entry *entry) +{ + return (entry->ae_stat.st_uid); +} + + const char * +archive_entry_uname(struct archive_entry *entry) +{ + return (aes_get_mbs(&entry->ae_uname)); +} + + const wchar_t * +archive_entry_uname_w(struct archive_entry *entry) +{ + return (aes_get_wcs(&entry->ae_uname)); +} + +/* + * Functions to set archive_entry properties. + */ + +/* + * Note "copy" not "set" here. The "set" functions that accept a pointer + * only store the pointer; they don't copy the underlying object. + */ + void +archive_entry_copy_stat(struct archive_entry *entry, const struct stat *st) +{ + entry->ae_stat = *st; +} + + void +archive_entry_set_fflags(struct archive_entry *entry, + unsigned long set, unsigned long clear) +{ + aes_clean(&entry->ae_fflags_text); + entry->ae_fflags_set = set; + entry->ae_fflags_clear = clear; +} + + const wchar_t * +archive_entry_copy_fflags_text_w(struct archive_entry *entry, + const wchar_t *flags) +{ + aes_copy_wcs(&entry->ae_fflags_text, flags); + return (ae_wcstofflags(flags, + &entry->ae_fflags_set, &entry->ae_fflags_clear)); +} + + void +archive_entry_set_gid(struct archive_entry *entry, gid_t g) +{ + entry->ae_stat.st_gid = g; +} + + void +archive_entry_set_gname(struct archive_entry *entry, const char *name) +{ + aes_set_mbs(&entry->ae_gname, name); +} + + void +archive_entry_copy_gname_w(struct archive_entry *entry, const wchar_t *name) +{ + aes_copy_wcs(&entry->ae_gname, name); +} + + void +archive_entry_set_hardlink(struct archive_entry *entry, const char *target) +{ + aes_set_mbs(&entry->ae_hardlink, target); +} + + void +archive_entry_copy_hardlink(struct archive_entry *entry, const char *target) +{ + aes_copy_mbs(&entry->ae_hardlink, target); +} + + void +archive_entry_copy_hardlink_w(struct archive_entry *entry, const wchar_t *target) +{ + aes_copy_wcs(&entry->ae_hardlink, target); +} + + void +archive_entry_set_atime(struct archive_entry *entry, time_t t, long ns) +{ + (void)ns; + entry->ae_stat.st_atime = t; + ARCHIVE_STAT_SET_ATIME_NANOS(&entry->ae_stat, ns); +} + + void +archive_entry_set_ctime(struct archive_entry *entry, time_t t, long ns) +{ + (void)ns; + entry->ae_stat.st_ctime = t; + ARCHIVE_STAT_SET_CTIME_NANOS(&entry->ae_stat, ns); +} + +/* Set symlink if symlink is already set, else set hardlink. */ + void +archive_entry_set_link(struct archive_entry *entry, const char *target) +{ + if (entry->ae_symlink.aes_mbs != NULL || + entry->ae_symlink.aes_wcs != NULL) + aes_set_mbs(&entry->ae_symlink, target); + else + aes_set_mbs(&entry->ae_hardlink, target); +} + + void +archive_entry_set_mode(struct archive_entry *entry, mode_t m) +{ + entry->ae_stat.st_mode = m; +} + + void +archive_entry_set_mtime(struct archive_entry *entry, time_t m, long ns) +{ + (void)ns; + entry->ae_stat.st_mtime = m; + ARCHIVE_STAT_SET_MTIME_NANOS(&entry->ae_stat, ns); +} + + void +archive_entry_set_pathname(struct archive_entry *entry, const char *name) +{ + aes_set_mbs(&entry->ae_pathname, name); +} + + void +archive_entry_copy_pathname(struct archive_entry *entry, const char *name) +{ + aes_copy_mbs(&entry->ae_pathname, name); +} + + void +archive_entry_copy_pathname_w(struct archive_entry *entry, const wchar_t *name) +{ + aes_copy_wcs(&entry->ae_pathname, name); +} + + void +archive_entry_set_size(struct archive_entry *entry, int64_t s) +{ + entry->ae_stat.st_size = s; +} + + void +archive_entry_set_symlink(struct archive_entry *entry, const char *linkname) +{ + aes_set_mbs(&entry->ae_symlink, linkname); +} + + void +archive_entry_copy_symlink_w(struct archive_entry *entry, const wchar_t *linkname) +{ + aes_copy_wcs(&entry->ae_symlink, linkname); +} + + void +archive_entry_set_uid(struct archive_entry *entry, uid_t u) +{ + entry->ae_stat.st_uid = u; +} + + void +archive_entry_set_uname(struct archive_entry *entry, const char *name) +{ + aes_set_mbs(&entry->ae_uname, name); +} + + void +archive_entry_copy_uname_w(struct archive_entry *entry, const wchar_t *name) +{ + aes_copy_wcs(&entry->ae_uname, name); +} + +/* + * ACL management. The following would, of course, be a lot simpler + * if: 1) the last draft of POSIX.1e were a really thorough and + * complete standard that addressed the needs of ACL archiving and 2) + * everyone followed it faithfully. Alas, neither is true, so the + * following is a lot more complex than might seem necessary to the + * uninitiated. + */ + + void +archive_entry_acl_clear(struct archive_entry *entry) +{ + struct ae_acl *ap; + + while (entry->acl_head != NULL) { + ap = entry->acl_head->next; + aes_clean(&entry->acl_head->name); + free(entry->acl_head); + entry->acl_head = ap; + } + if (entry->acl_text_w != NULL) { + free(entry->acl_text_w); + entry->acl_text_w = NULL; + } + entry->acl_p = NULL; + entry->acl_state = 0; /* Not counting. */ +} + +/* + * Add a single ACL entry to the internal list of ACL data. + */ + void +archive_entry_acl_add_entry(struct archive_entry *entry, + int type, int permset, int tag, int id, const char *name) +{ + struct ae_acl *ap; + + if (acl_special(entry, type, permset, tag) == 0) + return; + ap = acl_new_entry(entry, type, permset, tag, id); + if (ap == NULL) { + /* XXX Error XXX */ + return; + } + if (name != NULL && *name != '\0') + aes_copy_mbs(&ap->name, name); + else + aes_clean(&ap->name); +} + +/* + * As above, but with a wide-character name. + */ + void +archive_entry_acl_add_entry_w(struct archive_entry *entry, + int type, int permset, int tag, int id, const wchar_t *name) +{ + struct ae_acl *ap; + + if (acl_special(entry, type, permset, tag) == 0) + return; + ap = acl_new_entry(entry, type, permset, tag, id); + if (ap == NULL) { + /* XXX Error XXX */ + return; + } + if (name != NULL && *name != L'\0') + aes_copy_wcs(&ap->name, name); + else + aes_clean(&ap->name); +} + +/* + * If this ACL entry is part of the standard POSIX permissions set, + * store the permissions in the stat structure and return zero. + */ + static int +acl_special(struct archive_entry *entry, int type, int permset, int tag) +{ + if (type == ARCHIVE_ENTRY_ACL_TYPE_ACCESS) { + switch (tag) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + entry->ae_stat.st_mode &= ~0700; + entry->ae_stat.st_mode |= (permset & 7) << 6; + return (0); + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + entry->ae_stat.st_mode &= ~0070; + entry->ae_stat.st_mode |= (permset & 7) << 3; + return (0); + case ARCHIVE_ENTRY_ACL_OTHER: + entry->ae_stat.st_mode &= ~0007; + entry->ae_stat.st_mode |= permset & 7; + return (0); + } + } + return (1); +} + +/* + * Allocate and populate a new ACL entry with everything but the + * name. + */ + static struct ae_acl * +acl_new_entry(struct archive_entry *entry, + int type, int permset, int tag, int id) +{ + struct ae_acl *ap; + + if (type != ARCHIVE_ENTRY_ACL_TYPE_ACCESS && + type != ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) + return (NULL); + if (entry->acl_text_w != NULL) { + free(entry->acl_text_w); + entry->acl_text_w = NULL; + } + + /* XXX TODO: More sanity-checks on the arguments XXX */ + + /* If there's a matching entry already in the list, overwrite it. */ + for (ap = entry->acl_head; ap != NULL; ap = ap->next) { + if (ap->type == type && ap->tag == tag && ap->id == id) { + ap->permset = permset; + return (ap); + } + } + + /* Add a new entry to the list. */ + ap = (struct ae_acl *)malloc(sizeof(*ap)); + if (ap == NULL) + return (NULL); + memset(ap, 0, sizeof(*ap)); + ap->next = entry->acl_head; + entry->acl_head = ap; + ap->type = type; + ap->tag = tag; + ap->id = id; + ap->permset = permset; + return (ap); +} + +/* + * Return a count of entries matching "want_type". + */ + int +archive_entry_acl_count(struct archive_entry *entry, int want_type) +{ + int count; + struct ae_acl *ap; + + count = 0; + ap = entry->acl_head; + while (ap != NULL) { + if ((ap->type & want_type) != 0) + count++; + ap = ap->next; + } + + if (count > 0 && ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)) + count += 3; + return (count); +} + +/* + * Prepare for reading entries from the ACL data. Returns a count + * of entries matching "want_type", or zero if there are no + * non-extended ACL entries of that type. + */ + int +archive_entry_acl_reset(struct archive_entry *entry, int want_type) +{ + int count, cutoff; + + count = archive_entry_acl_count(entry, want_type); + + /* + * If the only entries are the three standard ones, + * then don't return any ACL data. (In this case, + * client can just use chmod(2) to set permissions.) + */ + if ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) + cutoff = 3; + else + cutoff = 0; + + if (count > cutoff) + entry->acl_state = ARCHIVE_ENTRY_ACL_USER_OBJ; + else + entry->acl_state = 0; + entry->acl_p = entry->acl_head; + return (count); +} + +/* + * Return the next ACL entry in the list. Fake entries for the + * standard permissions and include them in the returned list. + */ + + int +archive_entry_acl_next(struct archive_entry *entry, int want_type, int *type, + int *permset, int *tag, int *id, const char **name) +{ + *name = NULL; + *id = -1; + + /* + * The acl_state is either zero (no entries available), -1 + * (reading from list), or an entry type (retrieve that type + * from ae_stat.st_mode). + */ + if (entry->acl_state == 0) + return (ARCHIVE_WARN); + + /* The first three access entries are special. */ + if ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) { + switch (entry->acl_state) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + *permset = (entry->ae_stat.st_mode >> 6) & 7; + *type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + *tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + entry->acl_state = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + return (ARCHIVE_OK); + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + *permset = (entry->ae_stat.st_mode >> 3) & 7; + *type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + *tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + entry->acl_state = ARCHIVE_ENTRY_ACL_OTHER; + return (ARCHIVE_OK); + case ARCHIVE_ENTRY_ACL_OTHER: + *permset = entry->ae_stat.st_mode & 7; + *type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + *tag = ARCHIVE_ENTRY_ACL_OTHER; + entry->acl_state = -1; + entry->acl_p = entry->acl_head; + return (ARCHIVE_OK); + default: + break; + } + } + + while (entry->acl_p != NULL && (entry->acl_p->type & want_type) == 0) + entry->acl_p = entry->acl_p->next; + if (entry->acl_p == NULL) { + entry->acl_state = 0; + return (ARCHIVE_WARN); + } + *type = entry->acl_p->type; + *permset = entry->acl_p->permset; + *tag = entry->acl_p->tag; + *id = entry->acl_p->id; + *name = aes_get_mbs(&entry->acl_p->name); + entry->acl_p = entry->acl_p->next; + return (ARCHIVE_OK); +} + +/* + * Generate a text version of the ACL. The flags parameter controls + * the style of the generated ACL. + */ + const wchar_t * +archive_entry_acl_text_w(struct archive_entry *entry, int flags) +{ + int count; + int length; + const wchar_t *wname; + const wchar_t *prefix; + wchar_t separator; + struct ae_acl *ap; + int id; + wchar_t *wp; + + if (entry->acl_text_w != NULL) { + free (entry->acl_text_w); + entry->acl_text_w = NULL; + } + + separator = L','; + count = 0; + length = 0; + ap = entry->acl_head; + while (ap != NULL) { + if ((ap->type & flags) != 0) { + count++; + if ((flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) && + (ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT)) + length += 8; /* "default:" */ + length += 5; /* tag name */ + length += 1; /* colon */ + wname = aes_get_wcs(&ap->name); + if (wname != NULL) + length += wcslen(wname); + length ++; /* colon */ + length += 3; /* rwx */ + length += 1; /* colon */ + length += max(sizeof(uid_t),sizeof(gid_t)) * 3 + 1; + length ++; /* newline */ + } + ap = ap->next; + } + + if (count > 0 && ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)) { + length += 10; /* "user::rwx\n" */ + length += 11; /* "group::rwx\n" */ + length += 11; /* "other::rwx\n" */ + } + + if (count == 0) + return (NULL); + + /* Now, allocate the string and actually populate it. */ + wp = entry->acl_text_w = (wchar_t*)malloc(length * sizeof(wchar_t)); + if (wp == NULL) + __archive_errx(1, "No memory to generate the text version of the ACL"); + count = 0; + if ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) { + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_USER_OBJ, NULL, + entry->ae_stat.st_mode & 0700, -1); + *wp++ = ','; + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_GROUP_OBJ, NULL, + entry->ae_stat.st_mode & 0070, -1); + *wp++ = ','; + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_OTHER, NULL, + entry->ae_stat.st_mode & 0007, -1); + count += 3; + + ap = entry->acl_head; + while (ap != NULL) { + if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) { + wname = aes_get_wcs(&ap->name); + *wp++ = separator; + if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID) + id = ap->id; + else + id = -1; + append_entry_w(&wp, NULL, ap->tag, wname, + ap->permset, id); + count++; + } + ap = ap->next; + } + } + + + if ((flags & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0) { + if (flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) + prefix = L"default:"; + else + prefix = NULL; + ap = entry->acl_head; + count = 0; + while (ap != NULL) { + if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0) { + wname = aes_get_wcs(&ap->name); + if (count > 0) + *wp++ = separator; + if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID) + id = ap->id; + else + id = -1; + append_entry_w(&wp, prefix, ap->tag, + wname, ap->permset, id); + count ++; + } + ap = ap->next; + } + } + + return (entry->acl_text_w); +} + + static void +append_id_w(wchar_t **wp, int id) +{ + if (id > 9) + append_id_w(wp, id / 10); + *(*wp)++ = L"0123456789"[id % 10]; +} + + static void +append_entry_w(wchar_t **wp, const wchar_t *prefix, int tag, + const wchar_t *wname, int perm, int id) +{ + if (prefix != NULL) { + wcscpy(*wp, prefix); + *wp += wcslen(*wp); + } + switch (tag) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + wname = NULL; + id = -1; + /* FALL THROUGH */ + case ARCHIVE_ENTRY_ACL_USER: + wcscpy(*wp, L"user"); + break; + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + wname = NULL; + id = -1; + /* FALL THROUGH */ + case ARCHIVE_ENTRY_ACL_GROUP: + wcscpy(*wp, L"group"); + break; + case ARCHIVE_ENTRY_ACL_MASK: + wcscpy(*wp, L"mask"); + wname = NULL; + id = -1; + break; + case ARCHIVE_ENTRY_ACL_OTHER: + wcscpy(*wp, L"other"); + wname = NULL; + id = -1; + break; + } + *wp += wcslen(*wp); + *(*wp)++ = L':'; + if (wname != NULL) { + wcscpy(*wp, wname); + *wp += wcslen(*wp); + } + *(*wp)++ = L':'; + *(*wp)++ = (perm & 0444) ? L'r' : L'-'; + *(*wp)++ = (perm & 0222) ? L'w' : L'-'; + *(*wp)++ = (perm & 0111) ? L'x' : L'-'; + if (id != -1) { + *(*wp)++ = L':'; + append_id_w(wp, id); + } + **wp = L'\0'; +} + +/* + * Parse a textual ACL. This automatically recognizes and supports + * extensions described above. The 'type' argument is used to + * indicate the type that should be used for any entries not + * explicitly marked as "default:". + */ + int +__archive_entry_acl_parse_w(struct archive_entry *entry, + const wchar_t *text, int default_type) +{ + int type, tag, permset, id; + const wchar_t *start, *end; + const wchar_t *name_start, *name_end; + wchar_t sep; + wchar_t *namebuff; + int namebuff_length; + + name_start = name_end = NULL; + namebuff = NULL; + namebuff_length = 0; + + while (text != NULL && *text != L'\0') { + next_field_w(&text, &start, &end, &sep); + if (sep != L':') + goto fail; + + /* + * Solaris extension: "defaultuser::rwx" is the + * default ACL corresponding to "user::rwx", etc. + */ + if (end-start > 7 && wmemcmp(start, L"default", 7) == 0) { + type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT; + start += 7; + } else + type = default_type; + + if (prefix_w(start, end, L"user")) { + next_field_w(&text, &start, &end, &sep); + if (sep != L':') + goto fail; + if (end > start) { + tag = ARCHIVE_ENTRY_ACL_USER; + name_start = start; + name_end = end; + } else + tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + } else if (prefix_w(start, end, L"group")) { + next_field_w(&text, &start, &end, &sep); + if (sep != L':') + goto fail; + if (end > start) { + tag = ARCHIVE_ENTRY_ACL_GROUP; + name_start = start; + name_end = end; + } else + tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + } else if (prefix_w(start, end, L"other")) { + next_field_w(&text, &start, &end, &sep); + if (sep != L':') + goto fail; + if (end > start) + goto fail; + tag = ARCHIVE_ENTRY_ACL_OTHER; + } else if (prefix_w(start, end, L"mask")) { + next_field_w(&text, &start, &end, &sep); + if (sep != L':') + goto fail; + if (end > start) + goto fail; + tag = ARCHIVE_ENTRY_ACL_MASK; + } else + goto fail; + + next_field_w(&text, &start, &end, &sep); + permset = 0; + while (start < end) { + switch (*start++) { + case 'r': case 'R': + permset |= ARCHIVE_ENTRY_ACL_READ; + break; + case 'w': case 'W': + permset |= ARCHIVE_ENTRY_ACL_WRITE; + break; + case 'x': case 'X': + permset |= ARCHIVE_ENTRY_ACL_EXECUTE; + break; + case '-': + break; + default: + goto fail; + } + } + + /* + * Support star-compatible numeric UID/GID extension. + * This extension adds a ":" followed by the numeric + * ID so that "group:groupname:rwx", for example, + * becomes "group:groupname:rwx:999", where 999 is the + * numeric GID. This extension makes it possible, for + * example, to correctly restore ACLs on a system that + * might have a damaged passwd file or be disconnected + * from a central NIS server. This extension is compatible + * with POSIX.1e draft 17. + */ + if (sep == L':' && (tag == ARCHIVE_ENTRY_ACL_USER || + tag == ARCHIVE_ENTRY_ACL_GROUP)) { + next_field_w(&text, &start, &end, &sep); + + id = 0; + while (start < end && *start >= '0' && *start <= '9') { + if (id > (INT_MAX / 10)) + id = INT_MAX; + else { + id *= 10; + id += *start - '0'; + start++; + } + } + } else + id = -1; /* No id specified. */ + + /* Skip any additional entries. */ + while (sep == L':') { + next_field_w(&text, &start, &end, &sep); + } + + /* Add entry to the internal list. */ + if (name_end == name_start) { + archive_entry_acl_add_entry_w(entry, type, permset, + tag, id, NULL); + } else { + if (namebuff_length <= name_end - name_start) { + if (namebuff != NULL) + free(namebuff); + namebuff_length = name_end - name_start + 256; + namebuff = + (wchar_t*)malloc(namebuff_length * sizeof(wchar_t)); + if (namebuff == NULL) + goto fail; + } + wmemcpy(namebuff, name_start, name_end - name_start); + namebuff[name_end - name_start] = L'\0'; + archive_entry_acl_add_entry_w(entry, type, + permset, tag, id, namebuff); + } + } + if (namebuff != NULL) + free(namebuff); + return (ARCHIVE_OK); + +fail: + if (namebuff != NULL) + free(namebuff); + return (ARCHIVE_WARN); +} + +/* + * Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *wp is updated + * to point to just after the separator. *start points to the first + * character of the matched text and *end just after the last + * character of the matched identifier. In particular *end - *start + * is the length of the field body, not including leading or trailing + * whitespace. + */ + static void +next_field_w(const wchar_t **wp, const wchar_t **start, + const wchar_t **end, wchar_t *sep) +{ + /* Skip leading whitespace to find start of field. */ + while (**wp == L' ' || **wp == L'\t' || **wp == L'\n') { + (*wp)++; + } + *start = *wp; + + /* Scan for the separator. */ + while (**wp != L'\0' && **wp != L',' && **wp != L':' && + **wp != L'\n') { + (*wp)++; + } + *sep = **wp; + + /* Trim trailing whitespace to locate end of field. */ + *end = *wp - 1; + while (**end == L' ' || **end == L'\t' || **end == L'\n') { + (*end)--; + } + (*end)++; + + /* Adjust scanner location. */ + if (**wp != L'\0') + (*wp)++; +} + + static int +prefix_w(const wchar_t *start, const wchar_t *end, const wchar_t *test) +{ + if (start == end) + return (0); + + if (*start++ != *test++) + return (0); + + while (start < end && *start++ == *test++) + ; + + if (start < end) + return (0); + + return (1); +} + + +/* + * Following code is modified from UC Berkeley sources, and + * is subject to the following copyright notice. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + */ + +static struct flag { + const char *name; + const wchar_t *wname; + unsigned long set; + unsigned long clear; +} flags[] = { + /* Preferred (shorter) names per flag first, all prefixed by "no" */ +#ifdef SF_APPEND + { "nosappnd", L"nosappnd", SF_APPEND, 0 }, + { "nosappend", L"nosappend", SF_APPEND, 0 }, +#endif +#ifdef EXT2_APPEND_FL /* 'a' */ + { "nosappnd", L"nosappnd", EXT2_APPEND_FL, 0 }, + { "nosappend", L"nosappend", EXT2_APPEND_FL, 0 }, +#endif +#ifdef SF_ARCHIVED + { "noarch", L"noarch", SF_ARCHIVED, 0 }, + { "noarchived", L"noarchived", SF_ARCHIVED, 0 }, +#endif +#ifdef SF_IMMUTABLE + { "noschg", L"noschg", SF_IMMUTABLE, 0 }, + { "noschange", L"noschange", SF_IMMUTABLE, 0 }, + { "nosimmutable", L"nosimmutable", SF_IMMUTABLE, 0 }, +#endif +#ifdef EXT2_IMMUTABLE_FL /* 'i' */ + { "noschg", L"noschg", EXT2_IMMUTABLE_FL, 0 }, + { "noschange", L"noschange", EXT2_IMMUTABLE_FL, 0 }, + { "nosimmutable", L"nosimmutable", EXT2_IMMUTABLE_FL, 0 }, +#endif +#ifdef SF_NOUNLINK + { "nosunlnk", L"nosunlnk", SF_NOUNLINK, 0 }, + { "nosunlink", L"nosunlink", SF_NOUNLINK, 0 }, +#endif +#ifdef SF_SNAPSHOT + { "nosnapshot", L"nosnapshot", SF_SNAPSHOT, 0 }, +#endif +#ifdef UF_APPEND + { "nouappnd", L"nouappnd", UF_APPEND, 0 }, + { "nouappend", L"nouappend", UF_APPEND, 0 }, +#endif +#ifdef UF_IMMUTABLE + { "nouchg", L"nouchg", UF_IMMUTABLE, 0 }, + { "nouchange", L"nouchange", UF_IMMUTABLE, 0 }, + { "nouimmutable", L"nouimmutable", UF_IMMUTABLE, 0 }, +#endif +#ifdef UF_NODUMP + { "nodump", L"nodump", 0, UF_NODUMP}, +#endif +#ifdef EXT2_NODUMP_FL /* 'd' */ + { "nodump", L"nodump", 0, EXT2_NODUMP_FL}, +#endif +#ifdef UF_OPAQUE + { "noopaque", L"noopaque", UF_OPAQUE, 0 }, +#endif +#ifdef UF_NOUNLINK + { "nouunlnk", L"nouunlnk", UF_NOUNLINK, 0 }, + { "nouunlink", L"nouunlink", UF_NOUNLINK, 0 }, +#endif +#ifdef EXT2_COMPR_FL /* 'c' */ + { "nocompress", L"nocompress", EXT2_COMPR_FL, 0 }, +#endif + +#ifdef EXT2_NOATIME_FL /* 'A' */ + { "noatime", L"noatime", 0, EXT2_NOATIME_FL}, +#endif + { NULL, NULL, 0, 0 } +}; + +/* + * fflagstostr -- + * Convert file flags to a comma-separated string. If no flags + * are set, return the empty string. + */ + char * +ae_fflagstostr(unsigned long bitset, unsigned long bitclear) +{ + char *string, *dp; + const char *sp; + unsigned long bits; + struct flag *flag; + int length; + + bits = bitset | bitclear; + length = 0; + for (flag = flags; flag->name != NULL; flag++) + if (bits & (flag->set | flag->clear)) { + length += strlen(flag->name) + 1; + bits &= ~(flag->set | flag->clear); + } + + if (length == 0) + return (NULL); + string = (char*)malloc(length); + if (string == NULL) + return (NULL); + + dp = string; + for (flag = flags; flag->name != NULL; flag++) { + if (bitset & flag->set || bitclear & flag->clear) { + sp = flag->name + 2; + } else if (bitset & flag->clear || bitclear & flag->set) { + sp = flag->name; + } else + continue; + bitset &= ~(flag->set | flag->clear); + bitclear &= ~(flag->set | flag->clear); + if (dp > string) + *dp++ = ','; + while ((*dp++ = *sp++) != '\0') + ; + dp--; + } + + *dp = '\0'; + return (string); +} + +/* + * wcstofflags -- + * Take string of arguments and return file flags. This + * version works a little differently than strtofflags(3). + * In particular, it always tests every token, skipping any + * unrecognized tokens. It returns a pointer to the first + * unrecognized token, or NULL if every token was recognized. + * This version is also const-correct and does not modify the + * provided string. + */ + const wchar_t * +ae_wcstofflags(const wchar_t *s, unsigned long *setp, unsigned long *clrp) +{ + const wchar_t *start, *end; + struct flag *flag; + unsigned long set, clear; + const wchar_t *failed; + + set = clear = 0; + start = s; + failed = NULL; + /* Find start of first token. */ + while (*start == L'\t' || *start == L' ' || *start == L',') + start++; + while (*start != L'\0') { + /* Locate end of token. */ + end = start; + while (*end != L'\0' && *end != L'\t' && + *end != L' ' && *end != L',') + end++; + for (flag = flags; flag->wname != NULL; flag++) { + if (wmemcmp(start, flag->wname, end - start) == 0) { + /* Matched "noXXXX", so reverse the sense. */ + clear |= flag->set; + set |= flag->clear; + break; + } else if (wmemcmp(start, flag->wname + 2, end - start) + == 0) { + /* Matched "XXXX", so don't reverse. */ + set |= flag->set; + clear |= flag->clear; + break; + } + } + /* Ignore unknown flag names. */ + if (flag->wname == NULL && failed == NULL) + failed = start; + + /* Find start of next token. */ + start = end; + while (*start == L'\t' || *start == L' ' || *start == L',') + start++; + + } + + if (setp) + *setp = set; + if (clrp) + *clrp = clear; + + /* Return location of first failure. */ + return (failed); +} + +//--- archive_string_sprintf.c +/* + * Like 'vsprintf', but ensures the target is big enough, resizing if + * necessary. + */ + void +__archive_string_vsprintf(struct archive_string *as, const char *fmt, + va_list ap) +{ + char long_flag; + intmax_t s; /* Signed integer temp. */ + uintmax_t u; /* Unsigned integer temp. */ + const char *p, *p2; + + __archive_string_ensure(as, 64); + + if (fmt == NULL) { + as->s[0] = 0; + return; + } + + long_flag = '\0'; + for (p = fmt; *p != '\0'; p++) { + const char *saved_p = p; + + if (*p != '%') { + archive_strappend_char(as, *p); + continue; + } + + p++; + + switch(*p) { + case 'j': + long_flag = 'j'; + p++; + break; + case 'l': + long_flag = 'l'; + p++; + break; + } + + switch (*p) { + case '%': + __archive_strappend_char(as, '%'); + break; + case 'c': + s = va_arg(ap, int); + __archive_strappend_char(as, s); + break; + case 'd': + switch(long_flag) { + case 'j': s = va_arg(ap, intmax_t); break; + case 'l': s = va_arg(ap, long); break; + default: s = va_arg(ap, int); break; + } + archive_strappend_int(as, s, 10); + break; + case 's': + p2 = va_arg(ap, char *); + archive_strcat(as, p2); + break; + case 'o': case 'u': case 'x': case 'X': + /* Common handling for unsigned integer formats. */ + switch(long_flag) { + case 'j': u = va_arg(ap, uintmax_t); break; + case 'l': u = va_arg(ap, unsigned long); break; + default: u = va_arg(ap, unsigned int); break; + } + /* Format it in the correct base. */ + switch (*p) { + case 'o': archive_strappend_int(as, u, 8); break; + case 'u': archive_strappend_int(as, u, 10); break; + default: archive_strappend_int(as, u, 16); break; + } + break; + default: + /* Rewind and print the initial '%' literally. */ + p = saved_p; + archive_strappend_char(as, *p); + } + } +} + +//---------------------------------------------------------------------- +class cmCPackTGZ_Data +{ +public: + cmCPackTGZ_Data(cmCPackTGZGenerator* gen) : + Name(0), OutputStream(0), Generator(gen) {} + const char *Name; + std::ostream* OutputStream; + cmCPackTGZGenerator* Generator; +}; + +//---------------------------------------------------------------------- +int cmCPackTGZGenerator::TGZ_Open(struct archive *a, void *client_data) +{ + cmCPackTGZ_Data *mydata = (cmCPackTGZ_Data*)client_data; + + (void)a; + mydata->OutputStream = new cmGeneratedFileStream(mydata->Name); + if ( *mydata->OutputStream && + mydata->Generator->GenerateHeader(mydata->OutputStream)) + { + return (ARCHIVE_OK); + } + else + { + return (ARCHIVE_FATAL); + } +} + +//---------------------------------------------------------------------- +ssize_t cmCPackTGZGenerator::TGZ_Write(struct archive *a, void *client_data, void *buff, size_t n) +{ + cmCPackTGZ_Data *mydata = (cmCPackTGZ_Data*)client_data; + + (void)a; + mydata->OutputStream->write(reinterpret_cast<const char*>(buff), n); + if ( !*mydata->OutputStream ) + { + return 0; + } + return n; +} + +//---------------------------------------------------------------------- +int cmCPackTGZGenerator::TGZ_Close(struct archive *a, void *client_data) +{ + cmCPackTGZ_Data *mydata = (cmCPackTGZ_Data*)client_data; + + (void)a; + delete mydata->OutputStream; + return (0); +} + +//---------------------------------------------------------------------- +int cmCPackTGZGenerator::CompressFiles(const char* outFileName, const char* toplevel, + const std::vector<std::string>& files) +{ + std::cout << "Toplevel: " << toplevel << std::endl; + cmCPackTGZ_Data mydata(this); + struct archive *a; + struct archive_entry *entry; + struct stat st; + char buff[8192]; + int len; + int fd; + + a = archive_write_new(); + mydata.Name = outFileName; + archive_write_set_compression_gzip(a); + archive_write_set_format_ustar(a); + archive_write_open(a, &mydata, cmCPackTGZGenerator::TGZ_Open, + cmCPackTGZGenerator::TGZ_Write, cmCPackTGZGenerator::TGZ_Close); + std::vector<std::string>::const_iterator fileIt; + for ( fileIt = files.begin(); fileIt != files.end(); ++ fileIt ) + { + std::string fname = cmSystemTools::RelativePath(toplevel, fileIt->c_str()); + const char* filename = fileIt->c_str(); + stat(filename, &st); + entry = archive_entry_new(); + archive_entry_copy_stat(entry, &st); + archive_entry_set_pathname(entry, fname.c_str()); + archive_write_header(a, entry); + fd = open(filename, O_RDONLY); + len = read(fd, buff, sizeof(buff)); + while ( len > 0 ) + { + archive_write_data(a, buff, len); + len = read(fd, buff, sizeof(buff)); + } + archive_entry_free(entry); + } + archive_write_finish(a); + return 1; +} + +//--- archive_write_set_format_ustar.c +struct ustar { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + char written; +}; + +/* + * Define structure of POSIX 'ustar' tar header. + */ +struct archive_entry_header_ustar { + char name[100]; + char mode[6]; + char mode_padding[2]; + char uid[6]; + char uid_padding[2]; + char gid[6]; + char gid_padding[2]; + char size[11]; + char size_padding[1]; + char mtime[11]; + char mtime_padding[1]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; /* For POSIX: "ustar\0" */ + char version[2]; /* For POSIX: "00" */ + char uname[32]; + char gname[32]; + char rdevmajor[6]; + char rdevmajor_padding[2]; + char rdevminor[6]; + char rdevminor_padding[2]; + char prefix[155]; + char padding[12]; +}; + +/* + * A filled-in copy of the header for initialization. + */ +static const struct archive_entry_header_ustar template_header = { + { "" }, /* name */ + { '0','0','0','0','0','0' }, { ' ', '\0' }, /* mode, space-null termination. */ + { '0','0','0','0','0','0' }, { ' ', '\0' }, /* uid, space-null termination. */ + { '0','0','0','0','0','0' }, { ' ', '\0' }, /* gid, space-null termination. */ + { '0','0','0','0','0','0','0','0','0','0','0' }, { ' ' }, /* size, space termination. */ + { '0','0','0','0','0','0','0','0','0','0','0' }, { ' ' }, /* mtime, space termination. */ + { ' ',' ',' ',' ',' ',' ',' ',' ' }, /* Initial checksum value. */ + { '0' }, /* default: regular file */ + { "" }, /* linkname */ + { 'u','s','t','a','r' }, /* magic */ + { '0', '0' }, /* version */ + { "" }, /* uname */ + { "" }, /* gname */ + {'0','0','0','0','0','0'}, { ' ', '\0' }, /* rdevmajor, space-null termination */ + {'0','0','0','0','0','0'}, { ' ', '\0' }, /* rdevminor, space-null termination */ + { "" }, /* prefix */ + { "" } /* padding */ +}; + +static int archive_write_ustar_data(struct archive *a, const void *buff, + size_t s); +static int archive_write_ustar_finish(struct archive *); +static int archive_write_ustar_finish_entry(struct archive *); +static int archive_write_ustar_header(struct archive *, + struct archive_entry *entry); +static int format_256(int64_t, char *, int); +static int format_number(int64_t, char *, int size, int max, int strict); +static int format_octal(int64_t, char *, int); +static int write_nulls(struct archive *a, size_t); + +/* + * Set output format to 'ustar' format. + */ + int +archive_write_set_format_ustar(struct archive *a) +{ + struct ustar *ustar; + + /* If someone else was already registered, unregister them. */ + if (a->format_finish != NULL) + (a->format_finish)(a); + + ustar = (struct ustar*)malloc(sizeof(*ustar)); + if (ustar == NULL) { + archive_set_error(a, ENOMEM, "Can't allocate ustar data"); + return (ARCHIVE_FATAL); + } + memset(ustar, 0, sizeof(*ustar)); + a->format_data = ustar; + + a->pad_uncompressed = 1; /* Mimic gtar in this respect. */ + a->format_write_header = archive_write_ustar_header; + a->format_write_data = archive_write_ustar_data; + a->format_finish = archive_write_ustar_finish; + a->format_finish_entry = archive_write_ustar_finish_entry; + a->archive_format = ARCHIVE_FORMAT_TAR_USTAR; + a->archive_format_name = "POSIX ustar"; + return (ARCHIVE_OK); +} + + static int +archive_write_ustar_header(struct archive *a, struct archive_entry *entry) +{ + char buff[512]; + int ret; + struct ustar *ustar; + + ustar = (struct ustar*)a->format_data; + ustar->written = 1; + + /* Only regular files (not hardlinks) have data. */ + if (archive_entry_hardlink(entry) != NULL || + archive_entry_symlink(entry) != NULL || + !S_ISREG(archive_entry_mode(entry))) + archive_entry_set_size(entry, 0); + + ret = __archive_write_format_header_ustar(a, buff, entry, -1, 1); + if (ret != ARCHIVE_OK) + return (ret); + ret = (a->compression_write)(a, buff, 512); + if (ret != ARCHIVE_OK) + return (ret); + + ustar->entry_bytes_remaining = archive_entry_size(entry); + ustar->entry_padding = 0x1ff & (- ustar->entry_bytes_remaining); + return (ARCHIVE_OK); +} + +/* + * Format a basic 512-byte "ustar" header. + * + * Returns -1 if format failed (due to field overflow). + * Note that this always formats as much of the header as possible. + * If "strict" is set to zero, it will extend numeric fields as + * necessary (overwriting terminators or using base-256 extensions). + * + * This is exported so that other 'tar' formats can use it. + */ + int +__archive_write_format_header_ustar(struct archive *a, char buff[512], + struct archive_entry *entry, int tartype, int strict) +{ + unsigned int checksum; + struct archive_entry_header_ustar *h; + int i, ret; + size_t copy_length; + const char *p, *pp; + const struct stat *st; + int mytartype; + + ret = 0; + mytartype = -1; + /* + * The "template header" already includes the "ustar" + * signature, various end-of-field markers and other required + * elements. + */ + memcpy(buff, &template_header, 512); + + h = (struct archive_entry_header_ustar *)buff; + + /* + * Because the block is already null-filled, and strings + * are allowed to exactly fill their destination (without null), + * I use memcpy(dest, src, strlen()) here a lot to copy strings. + */ + + pp = archive_entry_pathname(entry); + if (strlen(pp) <= sizeof(h->name)) + memcpy(h->name, pp, strlen(pp)); + else { + /* Store in two pieces, splitting at a '/'. */ + p = strchr(pp + strlen(pp) - sizeof(h->name) - 1, '/'); + /* + * If there is no path separator, or the prefix or + * remaining name are too large, return an error. + */ + if (!p) { + archive_set_error(a, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_WARN; + } else if (p > pp + sizeof(h->prefix)) { + archive_set_error(a, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_WARN; + } else { + /* Copy prefix and remainder to appropriate places */ + memcpy(h->prefix, pp, p - pp); + memcpy(h->name, p + 1, pp + strlen(pp) - p - 1); + } + } + + p = archive_entry_hardlink(entry); + if (p != NULL) + mytartype = '1'; + else + p = archive_entry_symlink(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (copy_length > sizeof(h->linkname)) { + archive_set_error(a, ENAMETOOLONG, + "Link contents too long"); + ret = ARCHIVE_WARN; + copy_length = sizeof(h->linkname); + } + memcpy(h->linkname, p, copy_length); + } + + p = archive_entry_uname(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (copy_length > sizeof(h->uname)) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Username too long"); + ret = ARCHIVE_WARN; + copy_length = sizeof(h->uname); + } + memcpy(h->uname, p, copy_length); + } + + p = archive_entry_gname(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (strlen(p) > sizeof(h->gname)) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Group name too long"); + ret = ARCHIVE_WARN; + copy_length = sizeof(h->gname); + } + memcpy(h->gname, p, copy_length); + } + + st = archive_entry_stat(entry); + + if (format_number(st->st_mode & 07777, h->mode, sizeof(h->mode), 8, strict)) { + archive_set_error(a, ERANGE, "Numeric mode too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(st->st_uid, h->uid, sizeof(h->uid), 8, strict)) { + archive_set_error(a, ERANGE, "Numeric user ID too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(st->st_gid, h->gid, sizeof(h->gid), 8, strict)) { + archive_set_error(a, ERANGE, "Numeric group ID too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(st->st_size, h->size, sizeof(h->size), 12, strict)) { + archive_set_error(a, ERANGE, "File size out of range"); + ret = ARCHIVE_WARN; + } + + if (format_number(st->st_mtime, h->mtime, sizeof(h->mtime), 12, strict)) { + archive_set_error(a, ERANGE, + "File modification time too large"); + ret = ARCHIVE_WARN; + } + +#if defined(S_ISBLK) + if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { + if (format_number(major(st->st_rdev), h->rdevmajor, + sizeof(h->rdevmajor), 8, strict)) { + archive_set_error(a, ERANGE, + "Major device number too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(minor(st->st_rdev), h->rdevminor, + sizeof(h->rdevminor), 8, strict)) { + archive_set_error(a, ERANGE, + "Minor device number too large"); + ret = ARCHIVE_WARN; + } + } +#endif + + if (tartype >= 0) { + h->typeflag[0] = tartype; + } else if (mytartype >= 0) { + h->typeflag[0] = mytartype; + } else { + switch (st->st_mode & S_IFMT) { + case S_IFREG: h->typeflag[0] = '0' ; break; +#if defined(S_IFLNK) + case S_IFLNK: h->typeflag[0] = '2' ; break; +#endif + case S_IFCHR: h->typeflag[0] = '3' ; break; +#if defined(S_IFBLK) + case S_IFBLK: h->typeflag[0] = '4' ; break; +#endif + case S_IFDIR: h->typeflag[0] = '5' ; break; +#if defined(S_IFIFO) + case S_IFIFO: h->typeflag[0] = '6' ; break; +#endif +#if defined(S_IFSOCK) + case S_IFSOCK: + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive socket"); + ret = ARCHIVE_WARN; + break; +#endif + default: + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive this (mode=0%lo)", + (unsigned long)st->st_mode); + ret = ARCHIVE_WARN; + } + } + + checksum = 0; + for (i = 0; i < 512; i++) + checksum += 255 & (unsigned int)buff[i]; + h->checksum[6] = '\0'; /* Can't be pre-set in the template. */ + /* h->checksum[7] = ' '; */ /* This is pre-set in the template. */ + format_octal(checksum, h->checksum, 6); + return (ret); +} + +/* + * Format a number into a field, with some intelligence. + */ + static int +format_number(int64_t v, char *p, int s, int maxsize, int strict) +{ + int64_t limit; + + limit = ((int64_t)1 << (s*3)); + + /* "Strict" only permits octal values with proper termination. */ + if (strict) + return (format_octal(v, p, s)); + + /* + * In non-strict mode, we allow the number to overwrite one or + * more bytes of the field termination. Even old tar + * implementations should be able to handle this with no + * problem. + */ + if (v >= 0) { + while (s <= maxsize) { + if (v < limit) + return (format_octal(v, p, s)); + s++; + limit <<= 3; + } + } + + /* Base-256 can handle any number, positive or negative. */ + return (format_256(v, p, maxsize)); +} + +/* + * Format a number into the specified field using base-256. + */ + static int +format_256(int64_t v, char *p, int s) +{ + p += s; + while (s-- > 0) { + *--p = (char)(v & 0xff); + v >>= 8; + } + *p |= 0x80; /* Set the base-256 marker bit. */ + return (0); +} + +/* + * Format a number into the specified field. + */ + static int +format_octal(int64_t v, char *p, int s) +{ + int len; + + len = s; + + /* Octal values can't be negative, so use 0. */ + if (v < 0) { + while (len-- > 0) + *p++ = '0'; + return (-1); + } + + p += s; /* Start at the end and work backwards. */ + while (s-- > 0) { + *--p = '0' + (v & 7); + v >>= 3; + } + + if (v == 0) + return (0); + + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '7'; + + return (-1); +} + + static int +archive_write_ustar_finish(struct archive *a) +{ + struct ustar *ustar; + int r; + + r = ARCHIVE_OK; + ustar = (struct ustar*)a->format_data; + /* + * Suppress end-of-archive if nothing else was ever written. + * This fixes a problem where setting one format, then another + * ends up writing a gratuitous end-of-archive marker. + */ + if (ustar->written && a->compression_write != NULL) + r = write_nulls(a, 512*2); + free(ustar); + a->format_data = NULL; + return (r); +} + + static int +archive_write_ustar_finish_entry(struct archive *a) +{ + struct ustar *ustar; + int ret; + + ustar = (struct ustar*) a->format_data; + ret = write_nulls(a, + ustar->entry_bytes_remaining + ustar->entry_padding); + ustar->entry_bytes_remaining = ustar->entry_padding = 0; + return (ret); +} + + static int +write_nulls(struct archive *a, size_t padding) +{ + int ret, to_write; + + while (padding > 0) { + to_write = padding < a->null_length ? padding : a->null_length; + ret = (a->compression_write)(a, a->nulls, to_write); + if (ret != ARCHIVE_OK) + return (ret); + padding -= to_write; + } + return (ARCHIVE_OK); +} + + static int +archive_write_ustar_data(struct archive *a, const void *buff, size_t s) +{ + struct ustar *ustar; + int ret; + + ustar = (struct ustar*)a->format_data; + if (s > ustar->entry_bytes_remaining) + s = ustar->entry_bytes_remaining; + ret = (a->compression_write)(a, buff, s); + ustar->entry_bytes_remaining -= s; + return (ret); +} + +//--- archive_write_set_compression_gzip.c + +struct private_data { + z_stream stream; + int64_t total_in; + unsigned char *compressed; + size_t compressed_buffer_size; + unsigned long crc; +}; + + +/* + * Yuck. zlib.h is not const-correct, so I need this one bit + * of ugly hackery to convert a const * pointer to a non-const pointer. + */ +#define SET_NEXT_IN(st,src) \ + (st)->stream.next_in = (Bytef*)(void *)(uintptr_t)(const void *)(src) + +static int archive_compressor_gzip_finish(struct archive *); +static int archive_compressor_gzip_init(struct archive *); +static int archive_compressor_gzip_write(struct archive *, const void *, + size_t); +static int drive_compressor(struct archive *, struct private_data *, + int finishing); + + +/* + * Allocate, initialize and return a archive object. + */ + int +archive_write_set_compression_gzip(struct archive *a) +{ + __archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW, "archive_write_set_compression_gzip"); + a->compression_init = &archive_compressor_gzip_init; + a->compression_code = ARCHIVE_COMPRESSION_GZIP; + a->compression_name = "gzip"; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ + static int +archive_compressor_gzip_init(struct archive *a) +{ + int ret; + struct private_data *state; + time_t t; + + a->compression_code = ARCHIVE_COMPRESSION_GZIP; + a->compression_name = "gzip"; + + if (a->client_opener != NULL) { + ret = (a->client_opener)(a, a->client_data); + if (ret != ARCHIVE_OK) + return (ret); + } + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate data for compression"); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + state->compressed_buffer_size = a->bytes_per_block; + state->compressed = (unsigned char*)malloc(state->compressed_buffer_size); + state->crc = crc32(0L, NULL, 0); + + if (state->compressed == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate data for compression buffer"); + free(state); + return (ARCHIVE_FATAL); + } + + state->stream.next_out = state->compressed; + state->stream.avail_out = state->compressed_buffer_size; + + /* Prime output buffer with a gzip header. */ + t = time(NULL); + state->compressed[0] = 0x1f; /* GZip signature bytes */ + state->compressed[1] = 0x8b; + state->compressed[2] = 0x08; /* "Deflate" compression */ + state->compressed[3] = 0; /* No options */ + state->compressed[4] = (t)&0xff; /* Timestamp */ + state->compressed[5] = (t>>8)&0xff; + state->compressed[6] = (t>>16)&0xff; + state->compressed[7] = (t>>24)&0xff; + state->compressed[8] = 0; /* No deflate options */ + state->compressed[9] = 3; /* OS=Unix */ + state->stream.next_out += 10; + state->stream.avail_out -= 10; + + a->compression_write = archive_compressor_gzip_write; + a->compression_finish = archive_compressor_gzip_finish; + + /* Initialize compression library. */ + ret = deflateInit2(&(state->stream), + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + -15 /* < 0 to suppress zlib header */, + 8, + Z_DEFAULT_STRATEGY); + + if (ret == Z_OK) { + a->compression_data = state; + return (0); + } + + /* Library setup failed: clean up. */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, "Internal error " + "initializing compression library"); + free(state->compressed); + free(state); + + /* Override the error message if we know what really went wrong. */ + switch (ret) { + case Z_STREAM_ERROR: + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid setup parameter"); + break; + case Z_MEM_ERROR: + archive_set_error(a, ENOMEM, "Internal error initializing " + "compression library"); + break; + case Z_VERSION_ERROR: + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid library version"); + break; + } + + return (ARCHIVE_FATAL); +} + +/* + * Write data to the compressed stream. + */ + static int +archive_compressor_gzip_write(struct archive *a, const void *buff, + size_t length) +{ + struct private_data *state; + int ret; + + state = (struct private_data*)a->compression_data; + if (a->client_writer == NULL) { + archive_set_error(a, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + /* Update statistics */ + state->crc = crc32(state->crc, (const Bytef*)buff, length); + state->total_in += length; + + /* Compress input data to output buffer */ + SET_NEXT_IN(state, buff); + state->stream.avail_in = length; + if ((ret = drive_compressor(a, state, 0)) != ARCHIVE_OK) + return (ret); + + a->file_position += length; + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ + static int +archive_compressor_gzip_finish(struct archive *a) +{ + ssize_t block_length, target_block_length, bytes_written; + int ret; + struct private_data *state; + unsigned tocopy; + unsigned char trailer[8]; + + state = (struct private_data*)a->compression_data; + ret = 0; + if (a->client_writer == NULL) { + archive_set_error(a, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + ret = ARCHIVE_FATAL; + goto cleanup; + } + + /* By default, always pad the uncompressed data. */ + if (a->pad_uncompressed) { + tocopy = a->bytes_per_block - + (state->total_in % a->bytes_per_block); + while (tocopy > 0 && tocopy < (unsigned)a->bytes_per_block) { + SET_NEXT_IN(state, a->nulls); + state->stream.avail_in = tocopy < a->null_length ? + tocopy : a->null_length; + state->crc = crc32(state->crc, a->nulls, + state->stream.avail_in); + state->total_in += state->stream.avail_in; + tocopy -= state->stream.avail_in; + ret = drive_compressor(a, state, 0); + if (ret != ARCHIVE_OK) + goto cleanup; + } + } + + /* Finish compression cycle */ + if (((ret = drive_compressor(a, state, 1))) != ARCHIVE_OK) + goto cleanup; + + /* Build trailer: 4-byte CRC and 4-byte length. */ + trailer[0] = (state->crc)&0xff; + trailer[1] = (state->crc >> 8)&0xff; + trailer[2] = (state->crc >> 16)&0xff; + trailer[3] = (state->crc >> 24)&0xff; + trailer[4] = (state->total_in)&0xff; + trailer[5] = (state->total_in >> 8)&0xff; + trailer[6] = (state->total_in >> 16)&0xff; + trailer[7] = (state->total_in >> 24)&0xff; + + /* Add trailer to current block. */ + tocopy = 8; + if (tocopy > state->stream.avail_out) + tocopy = state->stream.avail_out; + memcpy(state->stream.next_out, trailer, tocopy); + state->stream.next_out += tocopy; + state->stream.avail_out -= tocopy; + + /* If it overflowed, flush and start a new block. */ + if (tocopy < 8) { + bytes_written = (a->client_writer)(a, a->client_data, + state->compressed, state->compressed_buffer_size); + if (bytes_written <= 0) { + ret = ARCHIVE_FATAL; + goto cleanup; + } + a->raw_position += bytes_written; + state->stream.next_out = state->compressed; + state->stream.avail_out = state->compressed_buffer_size; + memcpy(state->stream.next_out, trailer + tocopy, 8-tocopy); + state->stream.next_out += 8-tocopy; + state->stream.avail_out -= 8-tocopy; + } + + /* Optionally, pad the final compressed block. */ + block_length = state->stream.next_out - state->compressed; + + + /* Tricky calculation to determine size of last block. */ + target_block_length = block_length; + if (a->bytes_in_last_block <= 0) + /* Default or Zero: pad to full block */ + target_block_length = a->bytes_per_block; + else + /* Round length to next multiple of bytes_in_last_block. */ + target_block_length = a->bytes_in_last_block * + ( (block_length + a->bytes_in_last_block - 1) / + a->bytes_in_last_block); + if (target_block_length > a->bytes_per_block) + target_block_length = a->bytes_per_block; + if (block_length < target_block_length) { + memset(state->stream.next_out, 0, + target_block_length - block_length); + block_length = target_block_length; + } + + /* Write the last block */ + bytes_written = (a->client_writer)(a, a->client_data, + state->compressed, block_length); + if (bytes_written <= 0) { + ret = ARCHIVE_FATAL; + goto cleanup; + } + a->raw_position += bytes_written; + + /* Cleanup: shut down compressor, release memory, etc. */ +cleanup: + switch (deflateEnd(&(state->stream))) { + case Z_OK: + break; + default: + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + free(state->compressed); + free(state); + + /* Close the output */ + if (a->client_closer != NULL) + (a->client_closer)(a, a->client_data); + + return (ret); +} + +/* + * Utility function to push input data through compressor, + * writing full output blocks as necessary. + * + * Note that this handles both the regular write case (finishing == + * false) and the end-of-archive case (finishing == true). + */ + static int +drive_compressor(struct archive *a, struct private_data *state, int finishing) +{ + ssize_t bytes_written; + int ret; + + for (;;) { + if (state->stream.avail_out == 0) { + bytes_written = (a->client_writer)(a, a->client_data, + state->compressed, state->compressed_buffer_size); + if (bytes_written <= 0) { + /* TODO: Handle this write failure */ + return (ARCHIVE_FATAL); + } else if ((size_t)bytes_written < state->compressed_buffer_size) { + /* Short write: Move remaining to + * front of block and keep filling */ + memmove(state->compressed, + state->compressed + bytes_written, + state->compressed_buffer_size - bytes_written); + } + a->raw_position += bytes_written; + state->stream.next_out + = state->compressed + + state->compressed_buffer_size - bytes_written; + state->stream.avail_out = bytes_written; + } + + ret = deflate(&(state->stream), + finishing ? Z_FINISH : Z_NO_FLUSH ); + + switch (ret) { + case Z_OK: + /* In non-finishing case, check if compressor + * consumed everything */ + if (!finishing && state->stream.avail_in == 0) + return (ARCHIVE_OK); + /* In finishing case, this return always means + * there's more work */ + break; + case Z_STREAM_END: + /* This return can only occur in finishing case. */ + return (ARCHIVE_OK); + default: + /* Any other return value indicates an error. */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "GZip compression failed"); + return (ARCHIVE_FATAL); + } + } +} + |