diff options
author | Tim Kientzle <kientzle@gmail.com> | 2008-04-29 17:56:43 -0400 |
---|---|---|
committer | Tim Kientzle <kientzle@gmail.com> | 2008-04-29 17:56:43 -0400 |
commit | 8f776fd9f7b07842fdb5801af7128808860cbe35 (patch) | |
tree | 4c71e903ceac319a97459ef6648450e3747476db | |
download | libarchive-8f776fd9f7b07842fdb5801af7128808860cbe35.tar.gz |
IFC to populate initial libarchive-portable tree.
SVN-Revision: 1
164 files changed, 46855 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..37bfd1cb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +.git* export-ignore + diff --git a/libarchive/COPYING b/libarchive/COPYING new file mode 100644 index 00000000..90ef9549 --- /dev/null +++ b/libarchive/COPYING @@ -0,0 +1,36 @@ +All of the C source code, header files, and documentation in this +package are covered by the following: + +Copyright (c) 2003-2007 Tim Kientzle +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Shell scripts, makefiles, and certain other files may be covered by +other licenses. In particular, some distributions of this library +contain Makefiles and/or shell scripts that are generated +automatically by GNU autoconf and GNU automake. Those generated files +are controlled by the relevant licenses. + +$FreeBSD: src/lib/libarchive/COPYING,v 1.3 2007/01/09 08:05:54 kientzle Exp $ + diff --git a/libarchive/Makefile b/libarchive/Makefile new file mode 100644 index 00000000..75078d79 --- /dev/null +++ b/libarchive/Makefile @@ -0,0 +1,252 @@ +# $FreeBSD: src/lib/libarchive/Makefile,v 1.83 2008/03/21 11:10:20 kaiw Exp $ + +LIB= archive +DPADD= ${LIBBZ2} ${LIBZ} +LDADD= -lbz2 -lz + +# The libarchive version stamp. +# Version is three numbers: +# Major: Bumped ONLY when API/ABI breakage happens (see SHLIB_MAJOR) +# Minor: Bumped when significant new features are added +# Revision: Bumped on any notable change + +# The useful version number (one integer, easy to compare) +LIBARCHIVE_VERSION= 2004012 +# The pretty version string +LIBARCHIVE_VERSION_STRING!= echo $$((${LIBARCHIVE_VERSION} / 1000000)).$$((${LIBARCHIVE_VERSION} / 1000 % 1000)).$$((${LIBARCHIVE_VERSION} % 1000)) + +# FreeBSD SHLIB_MAJOR value is managed as part of the FreeBSD system. +# It has no real relation to the version number above. +SHLIB_MAJOR= 4 + +CFLAGS+= -DPLATFORM_CONFIG_H=\"config_freebsd.h\" +CFLAGS+= -I${.OBJDIR} + +WARNS?= 6 + +# Headers to be installed in /usr/include +INCS= archive.h archive_entry.h + +# Build archive.h from archive.h.in by substituting version information. +# Note: FreeBSD has inttypes.h, so enable that include in archive.h.in +archive.h: archive.h.in Makefile + cat ${.CURDIR}/archive.h.in | sed \ + -e 's/@LIBARCHIVE_VERSION@/${LIBARCHIVE_VERSION}/g' \ + -e 's/@LIBARCHIVE_VERSION_STRING@/${LIBARCHIVE_VERSION_STRING}/g' \ + -e 's/@SHLIB_MAJOR@/${SHLIB_MAJOR}/g' \ + -e 's|@ARCHIVE_H_INCLUDE_INTTYPES_H@|#include <inttypes.h> /* For int64_t */|g' \ + > archive.h + +# archive.h needs to be cleaned +CLEANFILES+= archive.h + +# Sources to be compiled. +SRCS= archive.h \ + archive_check_magic.c \ + archive_entry.c \ + archive_entry_copy_stat.c \ + archive_entry_stat.c \ + archive_entry_strmode.c \ + archive_entry_link_resolver.c \ + archive_read.c \ + archive_read_data_into_fd.c \ + archive_read_extract.c \ + archive_read_open_fd.c \ + archive_read_open_file.c \ + archive_read_open_filename.c \ + archive_read_open_memory.c \ + archive_read_support_compression_all.c \ + archive_read_support_compression_bzip2.c \ + archive_read_support_compression_compress.c \ + archive_read_support_compression_gzip.c \ + archive_read_support_compression_none.c \ + archive_read_support_compression_program.c \ + archive_read_support_format_all.c \ + archive_read_support_format_ar.c \ + archive_read_support_format_cpio.c \ + archive_read_support_format_empty.c \ + archive_read_support_format_iso9660.c \ + archive_read_support_format_mtree.c \ + archive_read_support_format_tar.c \ + archive_read_support_format_zip.c \ + archive_string.c \ + archive_string_sprintf.c \ + archive_util.c \ + archive_virtual.c \ + archive_write.c \ + archive_write_disk.c \ + archive_write_disk_set_standard_lookup.c \ + archive_write_open_fd.c \ + archive_write_open_file.c \ + archive_write_open_filename.c \ + archive_write_open_memory.c \ + archive_write_set_compression_bzip2.c \ + archive_write_set_compression_compress.c \ + archive_write_set_compression_gzip.c \ + archive_write_set_compression_none.c \ + archive_write_set_compression_program.c \ + archive_write_set_format.c \ + archive_write_set_format_ar.c \ + archive_write_set_format_by_name.c \ + archive_write_set_format_cpio.c \ + archive_write_set_format_cpio_newc.c \ + archive_write_set_format_pax.c \ + archive_write_set_format_shar.c \ + archive_write_set_format_ustar.c \ + filter_fork.c + +# Man pages to be installed. +MAN= archive_entry.3 \ + archive_read.3 \ + archive_util.3 \ + archive_write.3 \ + archive_write_disk.3 \ + cpio.5 \ + libarchive.3 \ + libarchive-formats.5 \ + tar.5 + +# Symlink the man pages under each function name. +MLINKS+= archive_entry.3 archive_entry_acl_add_entry.3 +MLINKS+= archive_entry.3 archive_entry_acl_add_entry_w.3 +MLINKS+= archive_entry.3 archive_entry_acl_clear.3 +MLINKS+= archive_entry.3 archive_entry_acl_count.3 +MLINKS+= archive_entry.3 archive_entry_acl_next.3 +MLINKS+= archive_entry.3 archive_entry_acl_next_w.3 +MLINKS+= archive_entry.3 archive_entry_acl_reset.3 +MLINKS+= archive_entry.3 archive_entry_acl_text_w.3 +MLINKS+= archive_entry.3 archive_entry_clear.3 +MLINKS+= archive_entry.3 archive_entry_clone.3 +MLINKS+= archive_entry.3 archive_entry_copy_fflags_text_w.3 +MLINKS+= archive_entry.3 archive_entry_copy_gname.3 +MLINKS+= archive_entry.3 archive_entry_copy_gname_w.3 +MLINKS+= archive_entry.3 archive_entry_copy_hardlink_w.3 +MLINKS+= archive_entry.3 archive_entry_copy_link.3 +MLINKS+= archive_entry.3 archive_entry_copy_link_w.3 +MLINKS+= archive_entry.3 archive_entry_copy_pathname_w.3 +MLINKS+= archive_entry.3 archive_entry_copy_stat.3 +MLINKS+= archive_entry.3 archive_entry_copy_symlink_w.3 +MLINKS+= archive_entry.3 archive_entry_copy_uname.3 +MLINKS+= archive_entry.3 archive_entry_copy_uname_w.3 +MLINKS+= archive_entry.3 archive_entry_dev.3 +MLINKS+= archive_entry.3 archive_entry_devmajor.3 +MLINKS+= archive_entry.3 archive_entry_devminor.3 +MLINKS+= archive_entry.3 archive_entry_filetype.3 +MLINKS+= archive_entry.3 archive_entry_fflags.3 +MLINKS+= archive_entry.3 archive_entry_fflags_text.3 +MLINKS+= archive_entry.3 archive_entry_free.3 +MLINKS+= archive_entry.3 archive_entry_gid.3 +MLINKS+= archive_entry.3 archive_entry_gname.3 +MLINKS+= archive_entry.3 archive_entry_gname_w.3 +MLINKS+= archive_entry.3 archive_entry_hardlink.3 +MLINKS+= archive_entry.3 archive_entry_ino.3 +MLINKS+= archive_entry.3 archive_entry_mode.3 +MLINKS+= archive_entry.3 archive_entry_mtime.3 +MLINKS+= archive_entry.3 archive_entry_mtime_nsec.3 +MLINKS+= archive_entry.3 archive_entry_nlink.3 +MLINKS+= archive_entry.3 archive_entry_new.3 +MLINKS+= archive_entry.3 archive_entry_pathname.3 +MLINKS+= archive_entry.3 archive_entry_pathname_w.3 +MLINKS+= archive_entry.3 archive_entry_rdev.3 +MLINKS+= archive_entry.3 archive_entry_rdevmajor.3 +MLINKS+= archive_entry.3 archive_entry_rdevminor.3 +MLINKS+= archive_entry.3 archive_entry_set_atime.3 +MLINKS+= archive_entry.3 archive_entry_set_ctime.3 +MLINKS+= archive_entry.3 archive_entry_set_dev.3 +MLINKS+= archive_entry.3 archive_entry_set_devmajor.3 +MLINKS+= archive_entry.3 archive_entry_set_devminor.3 +MLINKS+= archive_entry.3 archive_entry_set_fflags.3 +MLINKS+= archive_entry.3 archive_entry_set_gid.3 +MLINKS+= archive_entry.3 archive_entry_set_gname.3 +MLINKS+= archive_entry.3 archive_entry_set_hardlink.3 +MLINKS+= archive_entry.3 archive_entry_set_link.3 +MLINKS+= archive_entry.3 archive_entry_set_mode.3 +MLINKS+= archive_entry.3 archive_entry_set_mtime.3 +MLINKS+= archive_entry.3 archive_entry_set_nlink.3 +MLINKS+= archive_entry.3 archive_entry_set_pathname.3 +MLINKS+= archive_entry.3 archive_entry_set_rdev.3 +MLINKS+= archive_entry.3 archive_entry_set_rdevmajor.3 +MLINKS+= archive_entry.3 archive_entry_set_rdevminor.3 +MLINKS+= archive_entry.3 archive_entry_set_size.3 +MLINKS+= archive_entry.3 archive_entry_set_symlink.3 +MLINKS+= archive_entry.3 archive_entry_set_uid.3 +MLINKS+= archive_entry.3 archive_entry_set_uname.3 +MLINKS+= archive_entry.3 archive_entry_size.3 +MLINKS+= archive_entry.3 archive_entry_stat.3 +MLINKS+= archive_entry.3 archive_entry_symlink.3 +MLINKS+= archive_entry.3 archive_entry_uid.3 +MLINKS+= archive_entry.3 archive_entry_uname.3 +MLINKS+= archive_entry.3 archive_entry_uname_w.3 +MLINKS+= archive_read.3 archive_read_data.3 +MLINKS+= archive_read.3 archive_read_data_block.3 +MLINKS+= archive_read.3 archive_read_data_into_buffer.3 +MLINKS+= archive_read.3 archive_read_data_into_fd.3 +MLINKS+= archive_read.3 archive_read_data_skip.3 +MLINKS+= archive_read.3 archive_read_extract.3 +MLINKS+= archive_read.3 archive_read_extract_set_progress_callback.3 +MLINKS+= archive_read.3 archive_read_extract_set_skip_file.3 +MLINKS+= archive_read.3 archive_read_finish.3 +MLINKS+= archive_read.3 archive_read_new.3 +MLINKS+= archive_read.3 archive_read_next_header.3 +MLINKS+= archive_read.3 archive_read_open.3 +MLINKS+= archive_read.3 archive_read_open2.3 +MLINKS+= archive_read.3 archive_read_open_FILE.3 +MLINKS+= archive_read.3 archive_read_open_fd.3 +MLINKS+= archive_read.3 archive_read_open_file.3 +MLINKS+= archive_read.3 archive_read_open_filename.3 +MLINKS+= archive_read.3 archive_read_open_memory.3 +MLINKS+= archive_read.3 archive_read_support_compression_all.3 +MLINKS+= archive_read.3 archive_read_support_compression_bzip2.3 +MLINKS+= archive_read.3 archive_read_support_compression_compress.3 +MLINKS+= archive_read.3 archive_read_support_compression_gzip.3 +MLINKS+= archive_read.3 archive_read_support_compression_none.3 +MLINKS+= archive_read.3 archive_read_support_compression_program.3 +MLINKS+= archive_read.3 archive_read_support_format_all.3 +MLINKS+= archive_read.3 archive_read_support_format_cpio.3 +MLINKS+= archive_read.3 archive_read_support_format_iso9660.3 +MLINKS+= archive_read.3 archive_read_support_format_tar.3 +MLINKS+= archive_read.3 archive_read_support_format_zip.3 +MLINKS+= archive_util.3 archive_clear_error.3 +MLINKS+= archive_util.3 archive_compression.3 +MLINKS+= archive_util.3 archive_compression_name.3 +MLINKS+= archive_util.3 archive_errno.3 +MLINKS+= archive_util.3 archive_error_string.3 +MLINKS+= archive_util.3 archive_format.3 +MLINKS+= archive_util.3 archive_format_name.3 +MLINKS+= archive_util.3 archive_set_error.3 +MLINKS+= archive_write.3 archive_write_close.3 +MLINKS+= archive_write.3 archive_write_data.3 +MLINKS+= archive_write.3 archive_write_finish.3 +MLINKS+= archive_write.3 archive_write_finish_entry.3 +MLINKS+= archive_write.3 archive_write_get_bytes_in_last_block.3 +MLINKS+= archive_write.3 archive_write_get_bytes_per_block.3 +MLINKS+= archive_write.3 archive_write_header.3 +MLINKS+= archive_write.3 archive_write_new.3 +MLINKS+= archive_write.3 archive_write_open.3 +MLINKS+= archive_write.3 archive_write_open_FILE.3 +MLINKS+= archive_write.3 archive_write_open_fd.3 +MLINKS+= archive_write.3 archive_write_open_file.3 +MLINKS+= archive_write.3 archive_write_open_filename.3 +MLINKS+= archive_write.3 archive_write_open_memory.3 +MLINKS+= archive_write.3 archive_write_set_bytes_in_last_block.3 +MLINKS+= archive_write.3 archive_write_set_bytes_per_block.3 +MLINKS+= archive_write.3 archive_write_set_callbacks.3 +MLINKS+= archive_write.3 archive_write_set_compression_bzip2.3 +MLINKS+= archive_write.3 archive_write_set_compression_gzip.3 +MLINKS+= archive_write.3 archive_write_set_compression_none.3 +MLINKS+= archive_write.3 archive_write_set_compression_program.3 +MLINKS+= archive_write.3 archive_write_set_format_pax.3 +MLINKS+= archive_write.3 archive_write_set_format_shar.3 +MLINKS+= archive_write.3 archive_write_set_format_ustar.3 +MLINKS+= archive_write_disk.3 archive_write_disk_new.3 +MLINKS+= archive_write_disk.3 archive_write_disk_set_group_lookup.3 +MLINKS+= archive_write_disk.3 archive_write_disk_set_options.3 +MLINKS+= archive_write_disk.3 archive_write_disk_set_skip_file.3 +MLINKS+= archive_write_disk.3 archive_write_disk_set_standard_lookup.3 +MLINKS+= archive_write_disk.3 archive_write_disk_set_user_lookup.3 +MLINKS+= libarchive.3 archive.3 + +check: + cd ${.CURDIR}/test && make test + +.include <bsd.lib.mk> diff --git a/libarchive/README b/libarchive/README new file mode 100644 index 00000000..df62c230 --- /dev/null +++ b/libarchive/README @@ -0,0 +1,93 @@ +$FreeBSD: src/lib/libarchive/README,v 1.5 2007/03/03 07:37:35 kientzle Exp $ + +libarchive: a library for reading and writing streaming archives + +This is all under a BSD license. Use, enjoy, but don't blame me if it breaks! + +Documentation: + * libarchive.3 gives an overview of the library as a whole + * archive_read.3, archive_write.3, and archive_write_disk.3 provide + detailed calling sequences for the read and write APIs + * archive_entry.3 details the "struct archive_entry" utility class + * libarchive-formats.5 documents the file formats supported by the library + * tar.5 provides some detailed information about a variety of different + "tar" formats. + +You should also read the copious comments in "archive.h" and the source +code for the sample "bsdtar" and "minitar" programs for more details. +Please let me know about any errors or omissions you find. + +Currently, the library automatically detects and reads the following: + * gzip compression + * bzip2 compression + * compress/LZW compression + * GNU tar format (including GNU long filenames, long link names, and + sparse files) + * Solaris 9 extended tar format (including ACLs) + * Old V7 tar archives + * POSIX ustar + * POSIX pax interchange format + * POSIX octet-oriented cpio + * SVR4 ASCII cpio + * Binary cpio (big-endian or little-endian) + * ISO9660 CD-ROM images (with optional Rockridge extensions) + * ZIP archives (with uncompressed or "deflate" compressed entries) + +The library can write: + * gzip compression + * bzip2 compression + * POSIX ustar + * POSIX pax interchange format + * "restricted" pax format, which will create ustar archives except for + entries that require pax extensions (for long filenames, ACLs, etc). + * POSIX octet-oriented cpio + * shar archives + +Notes: + * This is a heavily stream-oriented system. There is no direct + support for in-place modification or random access and no intention + of ever adding such support. Adding such support would require + sacrificing a lot of other features, so don't bother asking. + + * The library is designed to be extended with new compression and + archive formats. The only requirement is that the format be + readable or writable as a stream and that each archive entry be + independent. + + * On read, compression and format are always detected automatically. + + * I've attempted to minimize static link pollution. If you don't + explicitly invoke a particular feature (such as support for a + particular compression or format), it won't get pulled in. + In particular, if you don't explicitly enable a particular + compression or decompression support, you won't need to link + against the corresponding compression or decompression libraries. + This also reduces the size of statically-linked binaries in + environments where that matters. + + * On read, the library accepts whatever blocks you hand it. + Your read callback is free to pass the library a byte at a time + or mmap the entire archive and give it to the library at once. + On write, the library always produces correctly-blocked + output. + + * The object-style approach allows you to have multiple archive streams + open at once. bsdtar uses this in its "@archive" extension. + + * The archive itself is read/written using callback functions. + You can read an archive directly from an in-memory buffer or + write it to a socket, if you wish. There are some utility + functions to provide easy-to-use "open file," etc, capabilities. + + * The read/write APIs are designed to allow individual entries + to be read or written to any data source: You can create + a block of data in memory and add it to a tar archive without + first writing a temporary file. You can also read an entry from + an archive and write the data directly to a socket. If you want + to read/write entries to disk, the archive_write_disk interface + treats a directory as if it were an archive so you can copy + from archive->disk using the same code you use for archive->archive + transfers. + + * Note: "pax interchange format" is really an extended tar format, + despite what the name says. diff --git a/libarchive/archive.h.in b/libarchive/archive.h.in new file mode 100644 index 00000000..67bf4d4b --- /dev/null +++ b/libarchive/archive.h.in @@ -0,0 +1,539 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive.h.in,v 1.49 2008/03/14 22:19:50 kientzle Exp $ + */ + +#ifndef ARCHIVE_H_INCLUDED +#define ARCHIVE_H_INCLUDED + +#include <sys/types.h> /* Linux requires this for off_t */ +@ARCHIVE_H_INCLUDE_INTTYPES_H@ +#include <stdio.h> /* For FILE * */ +#ifndef _WIN32 +#include <unistd.h> /* For ssize_t and size_t */ +#else +typedef long ssize_t; +typedef unsigned int uid_t; +typedef unsigned int gid_t; +typedef unsigned short mode_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The version number is provided as both a macro and a function. + * The macro identifies the installed header; the function identifies + * the library version (which may not be the same if you're using a + * dynamically-linked version of the library). + */ + +/* + * The version number is expressed as a single integer that makes it + * easy to compare versions at build time: for version a.b.c, the + * version number is printf("%d%03d%03d",a,b,c). For example, if you + * know your application requires version 2.12.108 or later, you can + * assert that ARCHIVE_VERSION >= 2012108. + * + * This single-number format was introduced with libarchive 1.9.0 in + * the libarchive 1.x family and libarchive 2.2.4 in the libarchive + * 2.x family. The following may be useful if you really want to do + * feature detection for earlier libarchive versions (which defined + * ARCHIVE_API_VERSION and ARCHIVE_API_FEATURE instead): + * + * #ifndef ARCHIVE_VERSION_NUMBER + * #define ARCHIVE_VERSION_NUMBER \ + * (ARCHIVE_API_VERSION * 1000000 + ARCHIVE_API_FEATURE * 1000) + * #endif + */ +#define ARCHIVE_VERSION_NUMBER @LIBARCHIVE_VERSION@ +int archive_version_number(void); + +/* + * Textual name/version of the library, useful for version displays. + */ +const char * archive_version_string(void); + +#if ARCHIVE_VERSION_NUMBER < 3000000 +/* + * Deprecated; these are older names that will be removed in favor of + * the simpler definitions above. + */ +#define ARCHIVE_VERSION_STAMP ARCHIVE_VERSION_NUMBER +int archive_version_stamp(void); +#define ARCHIVE_LIBRARY_VERSION "libarchive @LIBARCHIVE_VERSION_STRING@" +const char * archive_version(void); +#define ARCHIVE_API_VERSION (ARCHIVE_VERSION_NUMBER / 1000000) +int archive_api_version(void); +#define ARCHIVE_API_FEATURE ((ARCHIVE_VERSION_NUMBER / 1000) % 1000) +int archive_api_feature(void); +#endif + +#if ARCHIVE_VERSION_NUMBER < 3000000 +/* This should never have been here in the first place. */ +/* Legacy of old tar assumptions, will be removed in libarchive 3.0. */ +#define ARCHIVE_BYTES_PER_RECORD 512 +#define ARCHIVE_DEFAULT_BYTES_PER_BLOCK 10240 +#endif + +/* 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 success. */ +/* For example, if write_header "fails", then you can't push data. */ +#define ARCHIVE_FAILED (-25) /* Current operation cannot complete. */ +/* But if write_header is "fatal," then this archive is dead and useless. */ +#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/skip/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); +/* Skips at most request bytes from archive and returns the skipped amount */ +#if ARCHIVE_VERSION_NUMBER < 2000000 +typedef ssize_t archive_skip_callback(struct archive *, void *_client_data, + size_t request); +#else +typedef off_t archive_skip_callback(struct archive *, void *_client_data, + off_t request); +#endif +/* Returns size actually written, zero on EOF, -1 on error. */ +typedef ssize_t archive_write_callback(struct archive *, void *_client_data, + const 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 +#define ARCHIVE_COMPRESSION_PROGRAM 4 + +/* + * 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. + * In some cases, this variation occurs as libarchive learns more about + * the archive (for example, later entries might utilize extensions that + * weren't necessary earlier in the archive; in this case, libarchive + * will change the format code to indicate the extended format that + * was used). In other cases, it's because different tools have + * modified the archive and so different parts of the archive + * actually have slightly different formts. (Both tar and cpio store + * format codes in each entry, so it is quite possible for each + * entry to be in a different format.) + */ +#define ARCHIVE_FORMAT_BASE_MASK 0xff0000 +#define ARCHIVE_FORMAT_CPIO 0x10000 +#define ARCHIVE_FORMAT_CPIO_POSIX (ARCHIVE_FORMAT_CPIO | 1) +#define ARCHIVE_FORMAT_CPIO_BIN_LE (ARCHIVE_FORMAT_CPIO | 2) +#define ARCHIVE_FORMAT_CPIO_BIN_BE (ARCHIVE_FORMAT_CPIO | 3) +#define ARCHIVE_FORMAT_CPIO_SVR4_NOCRC (ARCHIVE_FORMAT_CPIO | 4) +#define ARCHIVE_FORMAT_CPIO_SVR4_CRC (ARCHIVE_FORMAT_CPIO | 5) +#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 +#define ARCHIVE_FORMAT_EMPTY 0x60000 +#define ARCHIVE_FORMAT_AR 0x70000 +#define ARCHIVE_FORMAT_AR_GNU (ARCHIVE_FORMAT_AR | 1) +#define ARCHIVE_FORMAT_AR_BSD (ARCHIVE_FORMAT_AR | 2) +#define ARCHIVE_FORMAT_MTREE 0x80000 +#define ARCHIVE_FORMAT_MTREE_V1 (ARCHIVE_FORMAT_MTREE | 1) +#define ARCHIVE_FORMAT_MTREE_V2 (ARCHIVE_FORMAT_MTREE | 2) + +/*- + * 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_compression_program(struct archive *, + const char *command); + +int archive_read_support_format_all(struct archive *); +int archive_read_support_format_ar(struct archive *); +int archive_read_support_format_cpio(struct archive *); +int archive_read_support_format_empty(struct archive *); +int archive_read_support_format_gnutar(struct archive *); +int archive_read_support_format_iso9660(struct archive *); +int archive_read_support_format_mtree(struct archive *); +int archive_read_support_format_tar(struct archive *); +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 *); +int archive_read_open2(struct archive *, void *_client_data, + archive_open_callback *, archive_read_callback *, + archive_skip_callback *, archive_close_callback *); + +/* + * A variety of shortcuts that invoke archive_read_open() with + * canned callbacks suitable for common situations. The ones that + * accept a block size handle tape blocking correctly. + */ +/* Use this if you know the filename. Note: NULL indicates stdin. */ +int archive_read_open_filename(struct archive *, + const char *_filename, size_t _block_size); +/* archive_read_open_file() is a deprecated synonym for ..._open_filename(). */ +int archive_read_open_file(struct archive *, + const char *_filename, size_t _block_size); +/* Read an archive that's stored in memory. */ +int archive_read_open_memory(struct archive *, + void * buff, size_t size); +/* A more involved version that is only used for internal testing. */ +int archive_read_open_memory2(struct archive *a, void *buff, + size_t size, size_t read_size); +/* Read an archive that's already open, using the file descriptor. */ +int archive_read_open_fd(struct archive *, int _fd, + size_t _block_size); +/* Read an archive that's already open, using a FILE *. */ +/* Note: DO NOT use this with tape drives. */ +int archive_read_open_FILE(struct archive *, FILE *_file); + +/* 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 guarantee 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. */ + +/* Default: Do not try to set owner/group. */ +#define ARCHIVE_EXTRACT_OWNER (1) +/* Default: Do obey umask, do not restore SUID/SGID/SVTX bits. */ +#define ARCHIVE_EXTRACT_PERM (2) +/* Default: Do not restore mtime/atime. */ +#define ARCHIVE_EXTRACT_TIME (4) +/* Default: Replace existing files. */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE (8) +/* Default: Try create first, unlink only if create fails with EEXIST. */ +#define ARCHIVE_EXTRACT_UNLINK (16) +/* Default: Do not restore ACLs. */ +#define ARCHIVE_EXTRACT_ACL (32) +/* Default: Do not restore fflags. */ +#define ARCHIVE_EXTRACT_FFLAGS (64) +/* Default: Do not restore xattrs. */ +#define ARCHIVE_EXTRACT_XATTR (128) +/* Default: Do not try to guard against extracts redirected by symlinks. */ +/* Note: With ARCHIVE_EXTRACT_UNLINK, will remove any intermediate symlink. */ +#define ARCHIVE_EXTRACT_SECURE_SYMLINKS (256) +/* Default: Do not reject entries with '..' as path elements. */ +#define ARCHIVE_EXTRACT_SECURE_NODOTDOT (512) +/* Default: Create parent directories as needed. */ +#define ARCHIVE_EXTRACT_NO_AUTODIR (1024) +/* Default: Overwrite files, even if one on disk is newer. */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER (2048) + +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); + +/* Record the dev/ino of a file that will not be written. This is + * generally set to the dev/ino of the archive being read. */ +void archive_read_extract_set_skip_file(struct archive *, + dev_t, ino_t); + +/* 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. */ +#if ARCHIVE_VERSION_NUMBER >= 2000000 +int archive_read_finish(struct archive *); +#else +/* Temporarily allow library to compile with either 1.x or 2.0 API. */ +/* Erroneously declared to return void in libarchive 1.x */ +void archive_read_finish(struct archive *); +#endif + +/*- + * 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); +int archive_write_get_bytes_per_block(struct archive *); +/* 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_get_bytes_in_last_block(struct archive *); + +/* The dev/ino of a file that won't be archived. This is used + * to avoid recursively adding an archive to itself. */ +int archive_write_set_skip_file(struct archive *, dev_t, ino_t); + +int archive_write_set_compression_bzip2(struct archive *); +int archive_write_set_compression_compress(struct archive *); +int archive_write_set_compression_gzip(struct archive *); +int archive_write_set_compression_none(struct archive *); +int archive_write_set_compression_program(struct archive *, + const char *cmd); +/* 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_ar_bsd(struct archive *); +int archive_write_set_format_ar_svr4(struct archive *); +int archive_write_set_format_cpio(struct archive *); +int archive_write_set_format_cpio_newc(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_filename(struct archive *, const char *_file); +/* A deprecated synonym for archive_write_open_filename() */ +int archive_write_open_file(struct archive *, const char *_file); +int archive_write_open_FILE(struct archive *, FILE *); +/* _buffSize is the size of the buffer, _used refers to a variable that + * will be updated after each write into the buffer. */ +int archive_write_open_memory(struct archive *, + void *_buffer, size_t _buffSize, size_t *_used); + +/* + * 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 *); +#if ARCHIVE_VERSION_NUMBER >= 2000000 +ssize_t archive_write_data(struct archive *, const void *, size_t); +#else +/* Temporarily allow library to compile with either 1.x or 2.0 API. */ +/* This was erroneously declared to return "int" in libarchive 1.x. */ +int archive_write_data(struct archive *, const void *, size_t); +#endif +ssize_t archive_write_data_block(struct archive *, const void *, size_t, off_t); +int archive_write_finish_entry(struct archive *); +int archive_write_close(struct archive *); +#if ARCHIVE_VERSION_NUMBER >= 2000000 +int archive_write_finish(struct archive *); +#else +/* Temporarily allow library to compile with either 1.x or 2.0 API. */ +/* Return value was incorrect in libarchive 1.x. */ +void archive_write_finish(struct archive *); +#endif + +/*- + * To create objects on disk: + * 1) Ask archive_write_disk_new for a new archive_write_disk object. + * 2) Set any global properties. In particular, you should set + * the compression and format to use. + * 3) For each entry: + * - construct an appropriate struct archive_entry structure + * - archive_write_header to create the file/dir/etc on disk + * - archive_write_data to write the entry data + * 4) archive_write_finish to cleanup the writer and release resources + * + * In particular, you can use this in conjunction with archive_read() + * to pull entries out of an archive and create them on disk. + */ +struct archive *archive_write_disk_new(void); +/* This file will not be overwritten. */ +int archive_write_disk_set_skip_file(struct archive *, + dev_t, ino_t); +/* Set flags to control how the next item gets created. */ +int archive_write_disk_set_options(struct archive *, + int flags); +/* + * The lookup functions are given uname/uid (or gname/gid) pairs and + * return a uid (gid) suitable for this system. These are used for + * restoring ownership and for setting ACLs. The default functions + * are naive, they just return the uid/gid. These are small, so reasonable + * for applications that don't need to preserve ownership; they + * are probably also appropriate for applications that are doing + * same-system backup and restore. + */ +/* + * The "standard" lookup functions use common system calls to lookup + * the uname/gname, falling back to the uid/gid if the names can't be + * found. They cache lookups and are reasonably fast, but can be very + * large, so they are not used unless you ask for them. In + * particular, these match the specifications of POSIX "pax" and old + * POSIX "tar". + */ +int archive_write_disk_set_standard_lookup(struct archive *); +/* + * If neither the default (naive) nor the standard (big) functions suit + * your needs, you can write your own and register them. Be sure to + * include a cleanup function if you have allocated private data. + */ +int archive_write_disk_set_group_lookup(struct archive *, + void *private_data, + gid_t (*loookup)(void *, const char *gname, gid_t gid), + void (*cleanup)(void *)); +int archive_write_disk_set_user_lookup(struct archive *, + void *private_data, + uid_t (*)(void *, const char *uname, uid_t uid), + void (*cleanup)(void *)); + +/* + * 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_clear_error(struct archive *); +void archive_set_error(struct archive *, int _err, const char *fmt, ...); +void archive_copy_error(struct archive *dest, struct archive *src); + +#ifdef __cplusplus +} +#endif + +#endif /* !ARCHIVE_H_INCLUDED */ diff --git a/libarchive/archive_check_magic.c b/libarchive/archive_check_magic.c new file mode 100644 index 00000000..715486dc --- /dev/null +++ b/libarchive/archive_check_magic.c @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_check_magic.c,v 1.8 2007/04/02 00:15:45 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive_private.h" + +static void +errmsg(const char *m) +{ + write(STDERR_FILENO, m, strlen(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(unsigned int states) +{ + unsigned int 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 int magic, + unsigned int 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(); + } +} diff --git a/libarchive/archive_endian.h b/libarchive/archive_endian.h new file mode 100644 index 00000000..259f5de9 --- /dev/null +++ b/libarchive/archive_endian.h @@ -0,0 +1,142 @@ +/*- + * Copyright (c) 2002 Thomas Moestl <tmm@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + * + * $FreeBSD: src/lib/libarchive/archive_endian.h,v 1.2 2008/02/26 07:17:47 kientzle Exp $ + * + * Borrowed from FreeBSD's <sys/endian.h> + */ + +#ifndef ARCHIVE_ENDIAN_H_INCLUDED +#define ARCHIVE_ENDIAN_H_INCLUDED + +/* Alignment-agnostic encode/decode bytestream to/from little/big endian. */ + +static inline uint16_t +archive_be16dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[0] << 8) | p[1]); +} + +static inline uint32_t +archive_be32dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); +} + +static inline uint64_t +archive_be64dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return (((uint64_t)archive_be32dec(p) << 32) | archive_be32dec(p + 4)); +} + +static inline uint16_t +archive_le16dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[1] << 8) | p[0]); +} + +static inline uint32_t +archive_le32dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]); +} + +static inline uint64_t +archive_le64dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return (((uint64_t)archive_le32dec(p + 4) << 32) | archive_le32dec(p)); +} + +static inline void +archive_be16enc(void *pp, uint16_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = (u >> 8) & 0xff; + p[1] = u & 0xff; +} + +static inline void +archive_be32enc(void *pp, uint32_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = (u >> 24) & 0xff; + p[1] = (u >> 16) & 0xff; + p[2] = (u >> 8) & 0xff; + p[3] = u & 0xff; +} + +static inline void +archive_be64enc(void *pp, uint64_t u) +{ + unsigned char *p = (unsigned char *)pp; + + archive_be32enc(p, u >> 32); + archive_be32enc(p + 4, u & 0xffffffff); +} + +static inline void +archive_le16enc(void *pp, uint16_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = u & 0xff; + p[1] = (u >> 8) & 0xff; +} + +static inline void +archive_le32enc(void *pp, uint32_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = u & 0xff; + p[1] = (u >> 8) & 0xff; + p[2] = (u >> 16) & 0xff; + p[3] = (u >> 24) & 0xff; +} + +static inline void +archive_le64enc(void *pp, uint64_t u) +{ + unsigned char *p = (unsigned char *)pp; + + archive_le32enc(p, u & 0xffffffff); + archive_le32enc(p + 4, u >> 32); +} + +#endif diff --git a/libarchive/archive_entry.3 b/libarchive/archive_entry.3 new file mode 100644 index 00000000..8d96de1f --- /dev/null +++ b/libarchive/archive_entry.3 @@ -0,0 +1,422 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_entry.3,v 1.17 2008/03/14 23:00:53 kientzle Exp $ +.\" +.Dd December 15, 2003 +.Dt archive_entry 3 +.Os +.Sh NAME +.Nm archive_entry_acl_add_entry , +.Nm archive_entry_acl_add_entry_w , +.Nm archive_entry_acl_clear , +.Nm archive_entry_acl_count , +.Nm archive_entry_acl_next , +.Nm archive_entry_acl_next_w , +.Nm archive_entry_acl_reset , +.Nm archive_entry_acl_text_w , +.Nm archive_entry_atime , +.Nm archive_entry_atime_nsec , +.Nm archive_entry_clear , +.Nm archive_entry_clone , +.Nm archive_entry_copy_fflags_text_w , +.Nm archive_entry_copy_gname , +.Nm archive_entry_copy_gname_w , +.Nm archive_entry_copy_hardlink , +.Nm archive_entry_copy_hardlink_w , +.Nm archive_entry_copy_link , +.Nm archive_entry_copy_link_w , +.Nm archive_entry_copy_pathname_w , +.Nm archive_entry_copy_stat , +.Nm archive_entry_copy_symlink , +.Nm archive_entry_copy_symlink_w , +.Nm archive_entry_copy_uname , +.Nm archive_entry_copy_uname_w , +.Nm archive_entry_dev , +.Nm archive_entry_devmajor , +.Nm archive_entry_devminor , +.Nm archive_entry_filetype , +.Nm archive_entry_fflags , +.Nm archive_entry_fflags_text , +.Nm archive_entry_free , +.Nm archive_entry_gid , +.Nm archive_entry_gname , +.Nm archive_entry_hardlink , +.Nm archive_entry_ino , +.Nm archive_entry_mode , +.Nm archive_entry_mtime , +.Nm archive_entry_mtime_nsec , +.Nm archive_entry_nlink , +.Nm archive_entry_new , +.Nm archive_entry_pathname , +.Nm archive_entry_pathname_w , +.Nm archive_entry_rdev , +.Nm archive_entry_rdevmajor , +.Nm archive_entry_rdevminor , +.Nm archive_entry_set_atime , +.Nm archive_entry_set_ctime , +.Nm archive_entry_set_dev , +.Nm archive_entry_set_devmajor , +.Nm archive_entry_set_devminor , +.Nm archive_entry_set_filetype , +.Nm archive_entry_set_fflags , +.Nm archive_entry_set_gid , +.Nm archive_entry_set_gname , +.Nm archive_entry_set_hardlink , +.Nm archive_entry_set_link , +.Nm archive_entry_set_mode , +.Nm archive_entry_set_mtime , +.Nm archive_entry_set_pathname , +.Nm archive_entry_set_rdevmajor , +.Nm archive_entry_set_rdevminor , +.Nm archive_entry_set_size , +.Nm archive_entry_set_symlink , +.Nm archive_entry_set_uid , +.Nm archive_entry_set_uname , +.Nm archive_entry_size , +.Nm archive_entry_stat , +.Nm archive_entry_symlink , +.Nm archive_entry_uid , +.Nm archive_entry_uname +.Nd functions for manipulating archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft void +.Fo archive_entry_acl_add_entry +.Fa "struct archive_entry *" +.Fa "int type" +.Fa "int permset" +.Fa "int tag" +.Fa "int qual" +.Fa "const char *name" +.Fc +.Ft void +.Fo archive_entry_acl_add_entry_w +.Fa "struct archive_entry *" +.Fa "int type" +.Fa "int permset" +.Fa "int tag" +.Fa "int qual" +.Fa "const wchar_t *name" +.Fc +.Ft void +.Fn archive_entry_acl_clear "struct archive_entry *" +.Ft int +.Fn archive_entry_acl_count "struct archive_entry *" "int type" +.Ft int +.Fo archive_entry_acl_next +.Fa "struct archive_entry *" +.Fa "int want_type" +.Fa "int *type" +.Fa "int *permset" +.Fa "int *tag" +.Fa "int *qual" +.Fa "const char **name" +.Fc +.Ft int +.Fo archive_entry_acl_next_w +.Fa "struct archive_entry *" +.Fa "int want_type" +.Fa "int *type" +.Fa "int *permset" +.Fa "int *tag" +.Fa "int *qual" +.Fa "const wchar_t **name" +.Fc +.Ft int +.Fn archive_entry_acl_reset "struct archive_entry *" "int want_type" +.Ft const wchar_t * +.Fn archive_entry_acl_text_w "struct archive_entry *" "int flags" +.Ft time_t +.Fn archive_entry_atime "struct archive_entry *" +.Ft long +.Fn archive_entry_atime_nsec "struct archive_entry *" +.Ft "struct archive_entry *" +.Fn archive_entry_clear "struct archive_entry *" +.Ft struct archive_entry * +.Fn archive_entry_clone "struct archive_entry *" +.Ft const wchar_t * +.Fn archive_entry_copy_fflags_text_w "struct archive_entry *" "const wchar_t *" +.Ft void +.Fn archive_entry_copy_gname "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_copy_gname_w "struct archive_entry *" "const wchar_t *" +.Ft void +.Fn archive_entry_copy_hardlink "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_copy_hardlink_w "struct archive_entry *" "const wchar_t *" +.Ft void +.Fn archive_entry_copy_pathname_w "struct archive_entry *" "const wchar_t *" +.Ft void +.Fn archive_entry_copy_stat "struct archive_entry *" "const struct stat *" +.Ft void +.Fn archive_entry_copy_symlink "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_copy_symlink_w "struct archive_entry *" "const wchar_t *" +.Ft void +.Fn archive_entry_copy_uname "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_copy_uname_w "struct archive_entry *" "const wchar_t *" +.Ft dev_t +.Fn archive_entry_dev "struct archive_entry *" +.Ft dev_t +.Fn archive_entry_devmajor "struct archive_entry *" +.Ft dev_t +.Fn archive_entry_devminor "struct archive_entry *" +.Ft mode_t +.Fn archive_entry_filetype "struct archive_entry *" +.Ft void +.Fo archive_entry_fflags +.Fa "struct archive_entry *" +.Fa "unsigned long *set" +.Fa "unsigned long *clear" +.Fc +.Ft const char * +.Fn archive_entry_fflags_text "struct archive_entry *" +.Ft void +.Fn archive_entry_free "struct archive_entry *" +.Ft const char * +.Fn archive_entry_gname "struct archive_entry *" +.Ft const char * +.Fn archive_entry_hardlink "struct archive_entry *" +.Ft ino_t +.Fn archive_entry_ino "struct archive_entry *" +.Ft mode_t +.Fn archive_entry_mode "struct archive_entry *" +.Ft time_t +.Fn archive_entry_mtime "struct archive_entry *" +.Ft long +.Fn archive_entry_mtime_nsec "struct archive_entry *" +.Ft unsigned int +.Fn archive_entry_nlink "struct archive_entry *" +.Ft struct archive_entry * +.Fn archive_entry_new "void" +.Ft const char * +.Fn archive_entry_pathname "struct archive_entry *" +.Ft const wchar_t * +.Fn archive_entry_pathname_w "struct archive_entry *" +.Ft dev_t +.Fn archive_entry_rdev "struct archive_entry *" +.Ft dev_t +.Fn archive_entry_rdevmajor "struct archive_entry *" +.Ft dev_t +.Fn archive_entry_rdevminor "struct archive_entry *" +.Ft void +.Fn archive_entry_set_dev "struct archive_entry *" "dev_t" +.Ft void +.Fn archive_entry_set_devmajor "struct archive_entry *" "dev_t" +.Ft void +.Fn archive_entry_set_devminor "struct archive_entry *" "dev_t" +.Ft void +.Fn archive_entry_set_filetype "struct archive_entry *" "unsigned int" +.Ft void +.Fo archive_entry_set_fflags +.Fa "struct archive_entry *" +.Fa "unsigned long set" +.Fa "unsigned long clear" +.Fc +.Ft void +.Fn archive_entry_set_gid "struct archive_entry *" "gid_t" +.Ft void +.Fn archive_entry_set_gname "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_set_hardlink "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_set_ino "struct archive_entry *" "unsigned long" +.Ft void +.Fn archive_entry_set_link "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_set_mode "struct archive_entry *" "mode_t" +.Ft void +.Fn archive_entry_set_mtime "struct archive_entry *" "time_t" "long nanos" +.Ft void +.Fn archive_entry_set_nlink "struct archive_entry *" "unsigned int" +.Ft void +.Fn archive_entry_set_pathname "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_set_rdev "struct archive_entry *" "dev_t" +.Ft void +.Fn archive_entry_set_rdevmajor "struct archive_entry *" "dev_t" +.Ft void +.Fn archive_entry_set_rdevminor "struct archive_entry *" "dev_t" +.Ft void +.Fn archive_entry_set_size "struct archive_entry *" "int64_t" +.Ft void +.Fn archive_entry_set_symlink "struct archive_entry *" "const char *" +.Ft void +.Fn archive_entry_set_uid "struct archive_entry *" "uid_t" +.Ft void +.Fn archive_entry_set_uname "struct archive_entry *" "const char *" +.Ft int64_t +.Fn archive_entry_size "struct archive_entry *" +.Ft const struct stat * +.Fn archive_entry_stat "struct archive_entry *" +.Ft const char * +.Fn archive_entry_symlink "struct archive_entry *" +.Ft const char * +.Fn archive_entry_uname "struct archive_entry *" +.Sh DESCRIPTION +These functions create and manipulate data objects that +represent entries within an archive. +You can think of a +.Tn struct archive_entry +as a heavy-duty version of +.Tn struct stat : +it includes everything from +.Tn struct stat +plus associated pathname, textual group and user names, etc. +These objects are used by +.Xr libarchive 3 +to represent the metadata associated with a particular +entry in an archive. +.Ss Create and Destroy +There are functions to allocate, destroy, clear, and copy +.Va archive_entry +objects: +.Bl -tag -compact -width indent +.It Fn archive_entry_clear +Erases the object, resetting all internal fields to the +same state as a newly-created object. +This is provided to allow you to quickly recycle objects +without thrashing the heap. +.It Fn archive_entry_clone +A deep copy operation; all text fields are duplicated. +.It Fn archive_entry_free +Releases the +.Tn struct archive_entry +object. +.It Fn archive_entry_new +Allocate and return a blank +.Tn struct archive_entry +object. +.El +.Ss Set and Get Functions +Most of the functions here set or read entries in an object. +Such functions have one of the following forms: +.Bl -tag -compact -width indent +.It Fn archive_entry_set_XXXX +Stores the provided data in the object. +In particular, for strings, the pointer is stored, +not the referenced string. +.It Fn archive_entry_copy_XXXX +As above, except that the referenced data is copied +into the object. +.It Fn archive_entry_XXXX +Returns the specified data. +In the case of strings, a const-qualified pointer to +the string is returned. +.El +String data can be set or accessed as wide character strings +or normal +.Va char +strings. +The functions that use wide character strings are suffixed with +.Cm _w . +Note that these are different representations of the same data: +For example, if you store a narrow string and read the corresponding +wide string, the object will transparently convert formats +using the current locale. +Similarly, if you store a wide string and then store a +narrow string for the same data, the previously-set wide string will +be discarded in favor of the new data. +.Pp +There are a few set/get functions that merit additional description: +.Bl -tag -compact -width indent +.It Fn archive_entry_set_link +This function sets the symlink field if it is already set. +Otherwise, it sets the hardlink field. +.El +.Ss File Flags +File flags are transparently converted between a bitmap +representation and a textual format. +For example, if you set the bitmap and ask for text, the library +will build a canonical text format. +However, if you set a text format and request a text format, +you will get back the same text, even if it is ill-formed. +If you need to canonicalize a textual flags string, you should first set the +text form, then request the bitmap form, then use that to set the bitmap form. +Setting the bitmap format will clear the internal text representation +and force it to be reconstructed when you next request the text form. +.Pp +The bitmap format consists of two integers, one containing bits +that should be set, the other specifying bits that should be +cleared. +Bits not mentioned in either bitmap will be ignored. +Usually, the bitmap of bits to be cleared will be set to zero. +In unusual circumstances, you can force a fully-specified set +of file flags by setting the bitmap of flags to clear to the complement +of the bitmap of flags to set. +(This differs from +.Xr fflagstostr 3 , +which only includes names for set bits.) +Converting a bitmap to a textual string is a platform-specific +operation; bits that are not meaningful on the current platform +will be ignored. +.Pp +The canonical text format is a comma-separated list of flag names. +The +.Fn archive_entry_copy_fflags_text_w +function parses the provided text and sets the internal bitmap values. +This is a platform-specific operation; names that are not meaningful +on the current platform will be ignored. +The function returns a pointer to the start of the first name that was not +recognized, or NULL if every name was recognized. +Note that every name--including names that follow an unrecognized name--will +be evaluated, and the bitmaps will be set to reflect every name that is +recognized. +(In particular, this differs from +.Xr strtofflags 3 , +which stops parsing at the first unrecognized name.) +.Ss ACL Handling +XXX This needs serious help. +XXX +.Pp +An +.Dq Access Control List +(ACL) is a list of permissions that grant access to particular users or +groups beyond what would normally be provided by standard POSIX mode bits. +The ACL handling here addresses some deficiencies in the POSIX.1e draft 17 ACL +specification. +In particular, POSIX.1e draft 17 specifies several different formats, but +none of those formats include both textual user/group names and numeric +UIDs/GIDs. +.Pp +XXX explain ACL stuff XXX +.\" .Sh EXAMPLE +.\" .Sh RETURN VALUES +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr archive 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.\" .Sh BUGS diff --git a/libarchive/archive_entry.c b/libarchive/archive_entry.c new file mode 100644 index 00000000..5f9e39a6 --- /dev/null +++ b/libarchive/archive_entry.c @@ -0,0 +1,1873 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry.c,v 1.51 2008/03/14 23:19:46 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef MAJOR_IN_MKDEV +#include <sys/mkdev.h> +#else +#ifdef MAJOR_IN_SYSMACROS +#include <sys/sysmacros.h> +#endif +#endif +#ifdef HAVE_EXT2FS_EXT2_FS_H +#include <ext2fs/ext2_fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_LINUX_FS_H +#include <linux/fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_LINUX_EXT2_FS_H +#include <linux/ext2_fs.h> /* for Linux file flags */ +#endif +#include <stddef.h> +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_WCHAR_H +#include <wchar.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_entry_private.h" + +#undef max +#define max(a, b) ((a)>(b)?(a):(b)) + +/* Play games to come up with a suitable makedev() definition. */ +#ifdef __QNXNTO__ +/* QNX. <sigh> */ +#include <sys/netmgr.h> +#define ae_makedev(maj, min) makedev(ND_LOCAL_NODE, (maj), (min)) +#elif defined makedev +/* There's a "makedev" macro. */ +#define ae_makedev(maj, min) makedev((maj), (min)) +#elif defined mkdev || defined _WIN32 || defined __WIN32__ +/* Windows. <sigh> */ +#define ae_makedev(maj, min) mkdev((maj), (min)) +#else +/* There's a "makedev" function. */ +#define ae_makedev(maj, min) makedev((maj), (min)) +#endif + +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 void aes_copy_wcs_len(struct aes *, const wchar_t *wcs, size_t); + +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 int isint_w(const wchar_t *start, const wchar_t *end, int *result); +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); +static void +archive_entry_acl_add_entry_w_len(struct archive_entry *entry, int type, + int permset, int tag, int id, const wchar_t *name, size_t); + + +#ifndef HAVE_WCSCPY +static wchar_t * wcscpy(wchar_t *s1, const wchar_t *s2) +{ + wchar_t *dest = s1; + while ((*s1 = *s2) != L'\0') + ++s1, ++s2; + return dest; +} +#endif +#ifndef HAVE_WCSLEN +static size_t wcslen(const wchar_t *s) +{ + const wchar_t *p = s; + while (*p != L'\0') + ++p; + return p - s; +} +#endif +#ifndef HAVE_WMEMCMP +/* Good enough for simple equality testing, but not for sorting. */ +#define wmemcmp(a,b,i) memcmp((a), (b), (i) * sizeof(wchar_t)) +#endif +#ifndef HAVE_WMEMCPY +#define wmemcpy(a,b,i) (wchar_t *)memcpy((a), (b), (i) * sizeof(wchar_t)) +#endif + + +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 + */ + size_t 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) +{ + int r; + + 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. + */ + size_t 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()"); + r = mbstowcs(aes->aes_wcs_alloc, aes->aes_mbs, wcs_length); + aes->aes_wcs_alloc[wcs_length] = 0; + if (r == -1) { + /* Conversion failed, don't lie to our clients. */ + free(aes->aes_wcs_alloc); + aes->aes_wcs = aes->aes_wcs_alloc = NULL; + } + } + 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) +{ + aes_copy_wcs_len(aes, wcs, wcslen(wcs)); +} + +static void +aes_copy_wcs_len(struct aes *aes, const wchar_t *wcs, size_t len) +{ + 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((len + 1) * sizeof(wchar_t)); + if (aes->aes_wcs_alloc == NULL) + __archive_errx(1, "No memory for aes_copy_wcs()"); + wmemcpy(aes->aes_wcs_alloc, wcs, len); + aes->aes_wcs_alloc[len] = L'\0'; + aes->aes_wcs = aes->aes_wcs_alloc; +} + +struct archive_entry * +archive_entry_clear(struct archive_entry *entry) +{ + if (entry == NULL) + return (NULL); + 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); + archive_entry_xattr_clear(entry); + free(entry->stat); + memset(entry, 0, sizeof(*entry)); + return entry; +} + +struct archive_entry * +archive_entry_clone(struct archive_entry *entry) +{ + struct archive_entry *entry2; + struct ae_acl *ap, *ap2; + struct ae_xattr *xp; + + /* 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); + + /* Copy ACL data over. */ + ap = entry->acl_head; + while (ap != NULL) { + ap2 = acl_new_entry(entry2, + ap->type, ap->permset, ap->tag, ap->id); + if (ap2 != NULL) + aes_copy(&ap2->name, &ap->name); + ap = ap->next; + } + + /* Copy xattr data over. */ + xp = entry->xattr_head; + while (xp != NULL) { + archive_entry_xattr_add_entry(entry2, + xp->name, xp->value, xp->size); + xp = xp->next; + } + + 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.aest_atime); +} + +long +archive_entry_atime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_atime_nsec); +} + +time_t +archive_entry_ctime(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_ctime); +} + +long +archive_entry_ctime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_ctime_nsec); +} + +dev_t +archive_entry_dev(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_dev_is_broken_down) + return ae_makedev(entry->ae_stat.aest_devmajor, + entry->ae_stat.aest_devminor); + else + return (entry->ae_stat.aest_dev); +} + +dev_t +archive_entry_devmajor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_dev_is_broken_down) + return (entry->ae_stat.aest_devmajor); + else + return major(entry->ae_stat.aest_dev); +} + +dev_t +archive_entry_devminor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_dev_is_broken_down) + return (entry->ae_stat.aest_devminor); + else + return minor(entry->ae_stat.aest_dev); +} + +mode_t +archive_entry_filetype(struct archive_entry *entry) +{ + return (AE_IFMT & entry->ae_stat.aest_mode); +} + +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.aest_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.aest_ino); +} + +mode_t +archive_entry_mode(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_mode); +} + +time_t +archive_entry_mtime(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_mtime); +} + +long +archive_entry_mtime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_mtime_nsec); +} + +unsigned int +archive_entry_nlink(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_nlink); +} + +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) +{ + if (entry->ae_stat.aest_rdev_is_broken_down) + return ae_makedev(entry->ae_stat.aest_rdevmajor, + entry->ae_stat.aest_rdevminor); + else + return (entry->ae_stat.aest_rdev); +} + +dev_t +archive_entry_rdevmajor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_rdev_is_broken_down) + return (entry->ae_stat.aest_rdevmajor); + else + return major(entry->ae_stat.aest_rdev); +} + +dev_t +archive_entry_rdevminor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_rdev_is_broken_down) + return (entry->ae_stat.aest_rdevminor); + else + return minor(entry->ae_stat.aest_rdev); +} + +int64_t +archive_entry_size(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_size); +} + +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.aest_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. + */ + +void +archive_entry_set_filetype(struct archive_entry *entry, unsigned int type) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_mode &= ~AE_IFMT; + entry->ae_stat.aest_mode |= AE_IFMT & type; +} + +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->stat_valid = 0; + entry->ae_stat.aest_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(struct archive_entry *entry, const char *name) +{ + aes_copy_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_ino(struct archive_entry *entry, unsigned long ino) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_ino = ino; +} + +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) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_atime = t; + entry->ae_stat.aest_atime_nsec = ns; +} + +void +archive_entry_set_ctime(struct archive_entry *entry, time_t t, long ns) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_ctime = t; + entry->ae_stat.aest_ctime_nsec = ns; +} + +void +archive_entry_set_dev(struct archive_entry *entry, dev_t d) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_dev_is_broken_down = 0; + entry->ae_stat.aest_dev = d; +} + +void +archive_entry_set_devmajor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_dev_is_broken_down = 1; + entry->ae_stat.aest_devmajor = m; +} + +void +archive_entry_set_devminor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_dev_is_broken_down = 1; + entry->ae_stat.aest_devminor = m; +} + +/* 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); +} + +/* Set symlink if symlink is already set, else set hardlink. */ +void +archive_entry_copy_link(struct archive_entry *entry, const char *target) +{ + if (entry->ae_symlink.aes_mbs != NULL || + entry->ae_symlink.aes_wcs != NULL) + aes_copy_mbs(&entry->ae_symlink, target); + else + aes_copy_mbs(&entry->ae_hardlink, target); +} + +/* Set symlink if symlink is already set, else set hardlink. */ +void +archive_entry_copy_link_w(struct archive_entry *entry, const wchar_t *target) +{ + if (entry->ae_symlink.aes_mbs != NULL || + entry->ae_symlink.aes_wcs != NULL) + aes_copy_wcs(&entry->ae_symlink, target); + else + aes_copy_wcs(&entry->ae_hardlink, target); +} + +void +archive_entry_set_mode(struct archive_entry *entry, mode_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_mode = m; +} + +void +archive_entry_set_mtime(struct archive_entry *entry, time_t m, long ns) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_mtime = m; + entry->ae_stat.aest_mtime_nsec = ns; +} + +void +archive_entry_set_nlink(struct archive_entry *entry, unsigned int nlink) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_nlink = nlink; +} + +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_perm(struct archive_entry *entry, mode_t p) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_mode &= AE_IFMT; + entry->ae_stat.aest_mode |= ~AE_IFMT & p; +} + +void +archive_entry_set_rdev(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_rdev = m; + entry->ae_stat.aest_rdev_is_broken_down = 0; +} + +void +archive_entry_set_rdevmajor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_rdev_is_broken_down = 1; + entry->ae_stat.aest_rdevmajor = m; +} + +void +archive_entry_set_rdevminor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_rdev_is_broken_down = 1; + entry->ae_stat.aest_rdevminor = m; +} + +void +archive_entry_set_size(struct archive_entry *entry, int64_t s) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_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(struct archive_entry *entry, const char *linkname) +{ + aes_copy_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->stat_valid = 0; + entry->ae_stat.aest_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(struct archive_entry *entry, const char *name) +{ + aes_copy_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) +{ + archive_entry_acl_add_entry_w_len(entry, type, permset, tag, id, name, wcslen(name)); +} + +void +archive_entry_acl_add_entry_w_len(struct archive_entry *entry, + int type, int permset, int tag, int id, const wchar_t *name, size_t len) +{ + 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' && len > 0) + aes_copy_wcs_len(&ap->name, name, len); + 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.aest_mode &= ~0700; + entry->ae_stat.aest_mode |= (permset & 7) << 6; + return (0); + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + entry->ae_stat.aest_mode &= ~0070; + entry->ae_stat.aest_mode |= (permset & 7) << 3; + return (0); + case ARCHIVE_ENTRY_ACL_OTHER: + entry->ae_stat.aest_mode &= ~0007; + entry->ae_stat.aest_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.aest_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.aest_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.aest_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.aest_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; + *type = 0; + *permset = 0; + *tag = 0; + *id = -1; + *name = NULL; + return (ARCHIVE_EOF); /* End of ACL entries. */ + } + *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; + size_t 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); + else + length += sizeof(uid_t) * 3 + 1; + 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.aest_mode & 0700, -1); + *wp++ = ','; + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_GROUP_OBJ, NULL, + entry->ae_stat.aest_mode & 0070, -1); + *wp++ = ','; + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_OTHER, NULL, + entry->ae_stat.aest_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 < 0) + id = 0; + 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; + /* FALLTHROUGH */ + case ARCHIVE_ENTRY_ACL_USER: + wcscpy(*wp, L"user"); + break; + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + wname = NULL; + id = -1; + /* FALLTHROUGH */ + 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); + } else if (tag == ARCHIVE_ENTRY_ACL_USER + || tag == ARCHIVE_ENTRY_ACL_GROUP) { + append_id_w(wp, id); + id = -1; + } + *(*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) +{ + struct { + const wchar_t *start; + const wchar_t *end; + } field[4]; + + int fields; + int type, tag, permset, id; + const wchar_t *p; + wchar_t sep; + + while (text != NULL && *text != L'\0') { + /* + * Parse the fields out of the next entry, + * advance 'text' to start of next entry. + */ + fields = 0; + do { + const wchar_t *start, *end; + next_field_w(&text, &start, &end, &sep); + if (fields < 4) { + field[fields].start = start; + field[fields].end = end; + } + ++fields; + } while (sep == L':'); + + if (fields < 3) + return (ARCHIVE_WARN); + + /* Check for a numeric ID in field 1 or 3. */ + id = -1; + isint_w(field[1].start, field[1].end, &id); + /* Field 3 is optional. */ + if (id == -1 && fields > 3) + isint_w(field[3].start, field[3].end, &id); + + /* Parse the permissions from field 2. */ + permset = 0; + p = field[2].start; + while (p < field[2].end) { + switch (*p++) { + 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: + return (ARCHIVE_WARN); + } + } + + /* + * Solaris extension: "defaultuser::rwx" is the + * default ACL corresponding to "user::rwx", etc. + */ + if (field[0].end-field[0].start > 7 + && wmemcmp(field[0].start, L"default", 7) == 0) { + type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT; + field[0].start += 7; + } else + type = default_type; + + if (prefix_w(field[0].start, field[0].end, L"user")) { + if (id != -1 || field[1].start < field[1].end) + tag = ARCHIVE_ENTRY_ACL_USER; + else + tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + } else if (prefix_w(field[0].start, field[0].end, L"group")) { + if (id != -1 || field[1].start < field[1].end) + tag = ARCHIVE_ENTRY_ACL_GROUP; + else + tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + } else if (prefix_w(field[0].start, field[0].end, L"other")) { + if (id != -1 || field[1].start < field[1].end) + return (ARCHIVE_WARN); + tag = ARCHIVE_ENTRY_ACL_OTHER; + } else if (prefix_w(field[0].start, field[0].end, L"mask")) { + if (id != -1 || field[1].start < field[1].end) + return (ARCHIVE_WARN); + tag = ARCHIVE_ENTRY_ACL_MASK; + } else + return (ARCHIVE_WARN); + + /* Add entry to the internal list. */ + archive_entry_acl_add_entry_w_len(entry, type, permset, + tag, id, field[1].start, field[1].end - field[1].start); + } + return (ARCHIVE_OK); +} + +/* + * extended attribute handling + */ + +void +archive_entry_xattr_clear(struct archive_entry *entry) +{ + struct ae_xattr *xp; + + while (entry->xattr_head != NULL) { + xp = entry->xattr_head->next; + free(entry->xattr_head->name); + free(entry->xattr_head->value); + free(entry->xattr_head); + entry->xattr_head = xp; + } + + entry->xattr_head = NULL; +} + +void +archive_entry_xattr_add_entry(struct archive_entry *entry, + const char *name, const void *value, size_t size) +{ + struct ae_xattr *xp; + + for (xp = entry->xattr_head; xp != NULL; xp = xp->next) + ; + + if ((xp = (struct ae_xattr *)malloc(sizeof(struct ae_xattr))) == NULL) + /* XXX Error XXX */ + return; + + xp->name = strdup(name); + if ((xp->value = malloc(size)) != NULL) { + memcpy(xp->value, value, size); + xp->size = size; + } else + xp->size = 0; + + xp->next = entry->xattr_head; + entry->xattr_head = xp; +} + + +/* + * returns number of the extended attribute entries + */ +int +archive_entry_xattr_count(struct archive_entry *entry) +{ + struct ae_xattr *xp; + int count = 0; + + for (xp = entry->xattr_head; xp != NULL; xp = xp->next) + count++; + + return count; +} + +int +archive_entry_xattr_reset(struct archive_entry * entry) +{ + entry->xattr_p = entry->xattr_head; + + return archive_entry_xattr_count(entry); +} + +int +archive_entry_xattr_next(struct archive_entry * entry, + const char **name, const void **value, size_t *size) +{ + if (entry->xattr_p) { + *name = entry->xattr_p->name; + *value = entry->xattr_p->value; + *size = entry->xattr_p->size; + + entry->xattr_p = entry->xattr_p->next; + + return (ARCHIVE_OK); + } else { + *name = NULL; + *value = NULL; + *size = (size_t)0; + return (ARCHIVE_WARN); + } +} + +/* + * end of xattr handling + */ + +/* + * Parse a string to a positive decimal integer. Returns true if + * the string is non-empty and consists only of decimal digits, + * false otherwise. + */ +static int +isint_w(const wchar_t *start, const wchar_t *end, int *result) +{ + int n = 0; + if (start >= end) + return (0); + while (start < end) { + if (*start < '0' || *start > '9') + return (0); + if (n > (INT_MAX / 10)) + n = INT_MAX; + else { + n *= 10; + n += *start - '0'; + } + start++; + } + *result = n; + return (1); +} + +/* + * 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)++; +} + +/* + * Return true if the characters [start...end) are a prefix of 'test'. + * This makes it easy to handle the obvious abbreviations: 'u' for 'user', etc. + */ +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; + size_t 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); +} + + +#ifdef TEST +#include <stdio.h> +int +main(int argc, char **argv) +{ + struct archive_entry *entry = archive_entry_new(); + unsigned long set, clear; + const wchar_t *remainder; + + remainder = archive_entry_copy_fflags_text_w(entry, L"nosappnd dump archive,,,,,,,"); + archive_entry_fflags(entry, &set, &clear); + + wprintf(L"set=0x%lX clear=0x%lX remainder='%ls'\n", set, clear, remainder); + + wprintf(L"new flags='%s'\n", archive_entry_fflags_text(entry)); + return (0); +} +#endif diff --git a/libarchive/archive_entry.h b/libarchive/archive_entry.h new file mode 100644 index 00000000..3bfe9e91 --- /dev/null +++ b/libarchive/archive_entry.h @@ -0,0 +1,348 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_entry.h,v 1.26 2008/03/14 23:00:53 kientzle Exp $ + */ + +#ifndef ARCHIVE_ENTRY_H_INCLUDED +#define ARCHIVE_ENTRY_H_INCLUDED + +#include <sys/types.h> +#include <stddef.h> /* for wchar_t */ +#include <time.h> +#include <unistd.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * 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; + +/* + * File-type constants. These are returned from archive_entry_filetype() + * and passed to archive_entry_set_filetype(). + * + * These values match S_XXX defines on every platform I've checked, + * including Windows, AIX, Linux, Solaris, and BSD. They're + * (re)defined here because platforms generally don't define the ones + * they don't support. For example, Windows doesn't define S_IFLNK or + * S_IFBLK. Instead of having a mass of conditional logic and system + * checks to define any S_XXX values that aren't supported locally, + * I've just defined a new set of such constants so that + * libarchive-based applications can manipulate and identify archive + * entries properly even if the hosting platform can't store them on + * disk. + * + * These values are also used directly within some portable formats, + * such as cpio. If you find a platform that varies from these, the + * correct solution is to leave these alone and translate from these + * portable values to platform-native values when entries are read from + * or written to disk. + */ +#define AE_IFMT 0170000 +#define AE_IFREG 0100000 +#define AE_IFLNK 0120000 +#define AE_IFSOCK 0140000 +#define AE_IFCHR 0020000 +#define AE_IFBLK 0060000 +#define AE_IFDIR 0040000 +#define AE_IFIFO 0010000 + +/* + * 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 *); +dev_t archive_entry_devmajor(struct archive_entry *); +dev_t archive_entry_devminor(struct archive_entry *); +mode_t archive_entry_filetype(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 *); +unsigned int archive_entry_nlink(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 *); +dev_t archive_entry_rdevmajor(struct archive_entry *); +dev_t archive_entry_rdevminor(struct archive_entry *); +int64_t archive_entry_size(struct archive_entry *); +const char *archive_entry_strmode(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_set_atime(struct archive_entry *, time_t, long); +void archive_entry_set_ctime(struct archive_entry *, time_t, long); +void archive_entry_set_dev(struct archive_entry *, dev_t); +void archive_entry_set_devmajor(struct archive_entry *, dev_t); +void archive_entry_set_devminor(struct archive_entry *, dev_t); +void archive_entry_set_filetype(struct archive_entry *, unsigned int); +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(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_ino(struct archive_entry *, unsigned long); +void archive_entry_set_link(struct archive_entry *, const char *); +void archive_entry_copy_link(struct archive_entry *, const char *); +void archive_entry_copy_link_w(struct archive_entry *, const wchar_t *); +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_nlink(struct archive_entry *, unsigned int); +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_perm(struct archive_entry *, mode_t); +void archive_entry_set_rdev(struct archive_entry *, dev_t); +void archive_entry_set_rdevmajor(struct archive_entry *, dev_t); +void archive_entry_set_rdevminor(struct archive_entry *, dev_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(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(struct archive_entry *, const char *); +void archive_entry_copy_uname_w(struct archive_entry *, const wchar_t *); + +/* + * Routines to bulk copy fields to/from a platform-native "struct + * stat." Libarchive used to just store a struct stat inside of each + * archive_entry object, but this created issues when trying to + * manipulate archives on systems different than the ones they were + * created on. + * + * TODO: On Linux, provide both stat32 and stat64 versions of these functions. + */ +const struct stat *archive_entry_stat(struct archive_entry *); +void archive_entry_copy_stat(struct archive_entry *, const struct stat *); + +/* + * 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 + * + * This last point, in particular, forces me to implement a reasonably + * complete set of ACL support routines. + * + * TODO: Extend this to support NFSv4/NTFS permissions. That should + * allow full ACL support on Mac OS, in particular, which uses + * POSIX.1e-style interfaces to manipulate NFSv4/NTFS permissions. + */ + +/* + * 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! + * + * TODO: Move this declaration out of the public header and into + * a private header. Warnings above are silly. + */ +int __archive_entry_acl_parse_w(struct archive_entry *, + const wchar_t *, int /* type */); + +/* + * extended attributes + */ + +void archive_entry_xattr_clear(struct archive_entry *); +void archive_entry_xattr_add_entry(struct archive_entry *, + const char * /* name */, const void * /* value */, + size_t /* size */); + +/* + * To retrieve the xattr list, first "reset", then repeatedly ask for the + * "next" entry. + */ + +int archive_entry_xattr_count(struct archive_entry *); +int archive_entry_xattr_reset(struct archive_entry *); +int archive_entry_xattr_next(struct archive_entry *, + const char ** /* name */, const void ** /* value */, size_t *); + +/* + * Utility to detect hardlinks. + * + * The 'struct archive_hardlink_lookup' is a cache of entry + * names and dev/ino numbers. Here's how to use it: + * 1. Create a lookup object with archive_hardlink_lookup_new() + * 2. Hand each archive_entry to archive_hardlink_lookup(). + * That function will return NULL (this is not a hardlink to + * a previous entry) or the pathname of the first entry + * that matched this. + * 3. Use archive_hardlink_lookup_free() to release the cache. + * + * To make things more efficient, be sure that each entry has a valid + * nlinks value. The hardlink cache uses this to track when all links + * have been found. If the nlinks value is zero, it will keep every + * name in the cache indefinitely, which can use a lot of memory. + */ +struct archive_entry_linkresolver; + +struct archive_entry_linkresolver *archive_entry_linkresolver_new(void); +void archive_entry_linkresolver_free(struct archive_entry_linkresolver *); +const char *archive_entry_linkresolve(struct archive_entry_linkresolver *, + struct archive_entry *); + +#ifdef __cplusplus +} +#endif + +#endif /* !ARCHIVE_ENTRY_H_INCLUDED */ diff --git a/libarchive/archive_entry_copy_stat.c b/libarchive/archive_entry_copy_stat.c new file mode 100644 index 00000000..514db027 --- /dev/null +++ b/libarchive/archive_entry_copy_stat.c @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry_copy_stat.c,v 1.1 2007/05/29 01:00:18 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#include "archive_entry.h" + +void +archive_entry_copy_stat(struct archive_entry *entry, const struct stat *st) +{ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + archive_entry_set_atime(entry, st->st_atime, st->st_atimespec.tv_nsec); + archive_entry_set_ctime(entry, st->st_ctime, st->st_ctimespec.tv_nsec); + archive_entry_set_mtime(entry, st->st_mtime, st->st_mtimespec.tv_nsec); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + archive_entry_set_atime(entry, st->st_atime, st->st_atim.tv_nsec); + archive_entry_set_ctime(entry, st->st_ctime, st->st_ctim.tv_nsec); + archive_entry_set_mtime(entry, st->st_mtime, st->st_mtim.tv_nsec); +#else + archive_entry_set_atime(entry, st->st_atime, 0); + archive_entry_set_ctime(entry, st->st_ctime, 0); + archive_entry_set_mtime(entry, st->st_mtime, 0); +#endif + archive_entry_set_dev(entry, st->st_dev); + archive_entry_set_gid(entry, st->st_gid); + archive_entry_set_uid(entry, st->st_uid); + archive_entry_set_ino(entry, st->st_ino); + archive_entry_set_nlink(entry, st->st_nlink); + archive_entry_set_rdev(entry, st->st_rdev); + archive_entry_set_size(entry, st->st_size); + archive_entry_set_mode(entry, st->st_mode); +} diff --git a/libarchive/archive_entry_link_resolver.c b/libarchive/archive_entry_link_resolver.c new file mode 100644 index 00000000..78a3c65d --- /dev/null +++ b/libarchive/archive_entry_link_resolver.c @@ -0,0 +1,222 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry_link_resolver.c,v 1.1 2007/12/30 04:58:21 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive_entry.h" + +/* Initial size of link cache. */ +#define links_cache_initial_size 1024 + +struct archive_entry_linkresolver { + char *last_name; + unsigned long number_entries; + size_t number_buckets; + struct links_entry **buckets; +}; + +struct links_entry { + struct links_entry *next; + struct links_entry *previous; + int links; + dev_t dev; + ino_t ino; + char *name; +}; + +struct archive_entry_linkresolver * +archive_entry_linkresolver_new(void) +{ + struct archive_entry_linkresolver *links_cache; + size_t i; + + links_cache = malloc(sizeof(struct archive_entry_linkresolver)); + if (links_cache == NULL) + return (NULL); + memset(links_cache, 0, sizeof(struct archive_entry_linkresolver)); + links_cache->number_buckets = links_cache_initial_size; + links_cache->buckets = malloc(links_cache->number_buckets * + sizeof(links_cache->buckets[0])); + if (links_cache->buckets == NULL) { + free(links_cache); + return (NULL); + } + for (i = 0; i < links_cache->number_buckets; i++) + links_cache->buckets[i] = NULL; + return (links_cache); +} + +void +archive_entry_linkresolver_free(struct archive_entry_linkresolver *links_cache) +{ + size_t i; + + if (links_cache->buckets == NULL) + return; + + for (i = 0; i < links_cache->number_buckets; i++) { + while (links_cache->buckets[i] != NULL) { + struct links_entry *lp = links_cache->buckets[i]->next; + if (links_cache->buckets[i]->name != NULL) + free(links_cache->buckets[i]->name); + free(links_cache->buckets[i]); + links_cache->buckets[i] = lp; + } + } + free(links_cache->buckets); + links_cache->buckets = NULL; +} + +const char * +archive_entry_linkresolve(struct archive_entry_linkresolver *links_cache, + struct archive_entry *entry) +{ + struct links_entry *le, **new_buckets; + int hash; + size_t i, new_size; + dev_t dev; + ino_t ino; + int nlinks; + + + /* Free a held name. */ + free(links_cache->last_name); + links_cache->last_name = NULL; + + /* If the links cache overflowed and got flushed, don't bother. */ + if (links_cache->buckets == NULL) + return (NULL); + + dev = archive_entry_dev(entry); + ino = archive_entry_ino(entry); + nlinks = archive_entry_nlink(entry); + + /* An entry with one link can't be a hard link. */ + if (nlinks == 1) + return (NULL); + + /* If the links cache is getting too full, enlarge the hash table. */ + if (links_cache->number_entries > links_cache->number_buckets * 2) + { + /* Try to enlarge the bucket list. */ + new_size = links_cache->number_buckets * 2; + new_buckets = malloc(new_size * sizeof(struct links_entry *)); + + if (new_buckets != NULL) { + memset(new_buckets, 0, + new_size * sizeof(struct links_entry *)); + for (i = 0; i < links_cache->number_buckets; i++) { + while (links_cache->buckets[i] != NULL) { + /* Remove entry from old bucket. */ + le = links_cache->buckets[i]; + links_cache->buckets[i] = le->next; + + /* Add entry to new bucket. */ + hash = (le->dev ^ le->ino) % new_size; + + if (new_buckets[hash] != NULL) + new_buckets[hash]->previous = + le; + le->next = new_buckets[hash]; + le->previous = NULL; + new_buckets[hash] = le; + } + } + free(links_cache->buckets); + links_cache->buckets = new_buckets; + links_cache->number_buckets = new_size; + } + } + + /* Try to locate this entry in the links cache. */ + hash = ( dev ^ ino ) % links_cache->number_buckets; + for (le = links_cache->buckets[hash]; le != NULL; le = le->next) { + if (le->dev == dev && le->ino == ino) { + /* + * Decrement link count each time and release + * the entry if it hits zero. This saves + * memory and is necessary for detecting + * missed links. + */ + --le->links; + if (le->links > 0) + return (le->name); + /* + * When we release the entry, save the name + * until the next call. + */ + links_cache->last_name = le->name; + /* + * Release the entry. + */ + if (le->previous != NULL) + le->previous->next = le->next; + if (le->next != NULL) + le->next->previous = le->previous; + if (links_cache->buckets[hash] == le) + links_cache->buckets[hash] = le->next; + links_cache->number_entries--; + free(le); + return (links_cache->last_name); + } + } + + /* Add this entry to the links cache. */ + le = malloc(sizeof(struct links_entry)); + if (le == NULL) + return (NULL); + le->name = strdup(archive_entry_pathname(entry)); + if (le->name == NULL) { + free(le); + return (NULL); + } + + /* If we could allocate the entry, record it. */ + if (links_cache->buckets[hash] != NULL) + links_cache->buckets[hash]->previous = le; + links_cache->number_entries++; + le->next = links_cache->buckets[hash]; + le->previous = NULL; + links_cache->buckets[hash] = le; + le->dev = dev; + le->ino = ino; + le->links = nlinks - 1; + return (NULL); +} diff --git a/libarchive/archive_entry_private.h b/libarchive/archive_entry_private.h new file mode 100644 index 00000000..0d368a4d --- /dev/null +++ b/libarchive/archive_entry_private.h @@ -0,0 +1,157 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_entry_private.h,v 1.3 2008/03/31 06:24:39 kientzle Exp $ + */ + +#ifndef ARCHIVE_ENTRY_PRIVATE_H_INCLUDED +#define ARCHIVE_ENTRY_PRIVATE_H_INCLUDED + +/* + * 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 */ +}; + +struct ae_xattr { + struct ae_xattr *next; + + char *name; + void *value; + size_t size; +}; + +/* + * 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 & AE_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. + */ + + /* + * Read archive_entry_copy_stat.c for an explanation of why I + * don't just use "struct stat" instead of "struct aest" here + * and why I have this odd pointer to a separately-allocated + * struct stat. + */ + void *stat; + int stat_valid; /* Set to 0 whenever a field in aest changes. */ + + struct aest { + int64_t aest_atime; + uint32_t aest_atime_nsec; + int64_t aest_ctime; + uint32_t aest_ctime_nsec; + int64_t aest_mtime; + uint32_t aest_mtime_nsec; + gid_t aest_gid; + ino_t aest_ino; + mode_t aest_mode; + uint32_t aest_nlink; + uint64_t aest_size; + uid_t aest_uid; + /* + * Because converting between device codes and + * major/minor values is platform-specific and + * inherently a bit risky, we only do that conversion + * lazily. That way, we will do a better job of + * preserving information in those cases where no + * conversion is actually required. + */ + int aest_dev_is_broken_down; + dev_t aest_dev; + dev_t aest_devmajor; + dev_t aest_devminor; + int aest_rdev_is_broken_down; + dev_t aest_rdev; + dev_t aest_rdevmajor; + dev_t aest_rdevminor; + } 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; + + struct ae_xattr *xattr_head; + struct ae_xattr *xattr_p; + + char strmode[12]; +}; + + +#endif /* ARCHIVE_ENTRY_PRIVATE_H_INCLUDED */ diff --git a/libarchive/archive_entry_stat.c b/libarchive/archive_entry_stat.c new file mode 100644 index 00000000..6ef5b372 --- /dev/null +++ b/libarchive/archive_entry_stat.c @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry_stat.c,v 1.1 2007/05/29 01:00:18 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#include "archive_entry.h" +#include "archive_entry_private.h" + +const struct stat * +archive_entry_stat(struct archive_entry *entry) +{ + struct stat *st; + if (entry->stat == NULL) { + entry->stat = malloc(sizeof(*st)); + if (entry->stat == NULL) + return (NULL); + entry->stat_valid = 0; + } + + /* + * If none of the underlying fields have been changed, we + * don't need to regenerate. In theory, we could use a bitmap + * here to flag only those items that have changed, but the + * extra complexity probably isn't worth it. It will be very + * rare for anyone to change just one field then request a new + * stat structure. + */ + if (entry->stat_valid) + return (entry->stat); + + st = entry->stat; + /* + * Use the public interfaces to extract items, so that + * the appropriate conversions get invoked. + */ + st->st_atime = archive_entry_atime(entry); + st->st_ctime = archive_entry_ctime(entry); + st->st_mtime = archive_entry_mtime(entry); + st->st_dev = archive_entry_dev(entry); + st->st_gid = archive_entry_gid(entry); + st->st_uid = archive_entry_uid(entry); + st->st_ino = archive_entry_ino(entry); + st->st_nlink = archive_entry_nlink(entry); + st->st_rdev = archive_entry_rdev(entry); + st->st_size = archive_entry_size(entry); + st->st_mode = archive_entry_mode(entry); + + /* + * On systems that support high-res timestamps, copy that + * information into struct stat. + */ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + st->st_atimespec.tv_nsec = archive_entry_atime_nsec(entry); + st->st_ctimespec.tv_nsec = archive_entry_ctime_nsec(entry); + st->st_mtimespec.tv_nsec = archive_entry_mtime_nsec(entry); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + st->st_atim.tv_nsec = archive_entry_atime_nsec(entry); + st->st_ctim.tv_nsec = archive_entry_ctime_nsec(entry); + st->st_mtim.tv_nsec = archive_entry_mtime_nsec(entry); +#endif + + /* + * TODO: On Linux, store 32 or 64 here depending on whether + * the cached stat structure is a stat32 or a stat64. This + * will allow us to support both variants interchangably. + */ + entry->stat_valid = 1; + + return (st); +} diff --git a/libarchive/archive_entry_strmode.c b/libarchive/archive_entry_strmode.c new file mode 100644 index 00000000..dc08d972 --- /dev/null +++ b/libarchive/archive_entry_strmode.c @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry_strmode.c,v 1.2 2008/02/19 05:49:02 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive_entry.h" +#include "archive_entry_private.h" + +const char * +archive_entry_strmode(struct archive_entry *entry) +{ + static const char *perms = "?rwxrwxrwx "; + static const mode_t permbits[] = + { 0400, 0200, 0100, 0040, 0020, 0010, 0004, 0002, 0001 }; + char *bp = entry->strmode; + mode_t mode; + int i; + + /* Fill in a default string, then selectively override. */ + strcpy(bp, perms); + + mode = archive_entry_mode(entry); + switch (archive_entry_filetype(entry)) { + case AE_IFREG: bp[0] = '-'; break; + case AE_IFBLK: bp[0] = 'b'; break; + case AE_IFCHR: bp[0] = 'c'; break; + case AE_IFDIR: bp[0] = 'd'; break; + case AE_IFLNK: bp[0] = 'l'; break; + case AE_IFSOCK: bp[0] = 's'; break; + case AE_IFIFO: bp[0] = 'p'; break; + } + + for (i = 0; i < 9; i++) + if (!(mode & permbits[i])) + bp[i+1] = '-'; + + if (mode & S_ISUID) { + if (mode & 0100) bp[3] = 's'; + else bp[3] = 'S'; + } + if (mode & S_ISGID) { + if (mode & 0010) bp[6] = 's'; + else bp[6] = 'S'; + } + if (mode & S_ISVTX) { + if (mode & 0001) bp[9] = 't'; + else bp[9] = 'T'; + } + if (archive_entry_acl_count(entry, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)) + bp[10] = '+'; + + return (bp); +} diff --git a/libarchive/archive_platform.h b/libarchive/archive_platform.h new file mode 100644 index 00000000..b14ccd82 --- /dev/null +++ b/libarchive/archive_platform.h @@ -0,0 +1,129 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_platform.h,v 1.29 2008/02/19 06:06:13 kientzle Exp $ + */ + +/* + * This header is the first thing included in any of the libarchive + * source files. As far as possible, platform-specific issues should + * be dealt with here and not within individual source files. I'm + * actively trying to minimize #if blocks within the main source, + * since they obfuscate the code. + */ + +#ifndef ARCHIVE_PLATFORM_H_INCLUDED +#define ARCHIVE_PLATFORM_H_INCLUDED + +#ifdef _WIN32 +#include "config_windows.h" +#include "archive_windows.h" +#elif defined(PLATFORM_CONFIG_H) +/* Use hand-built config.h in environments that need it. */ +#include PLATFORM_CONFIG_H +#elif defined(HAVE_CONFIG_H) +/* Most POSIX platforms use the 'configure' script to build config.h */ +#include "../config.h" +#else +/* Warn if the library hasn't been (automatically or manually) configured. */ +#error Oops: No config.h and no pre-built configuration in archive_platform.h. +#endif + +/* + * The config files define a lot of feature macros. The following + * uses those macros to select/define replacements and include key + * headers as required. + */ + +/* No non-FreeBSD platform will have __FBSDID, so just define it here. */ +#ifdef __FreeBSD__ +#include <sys/cdefs.h> /* For __FBSDID */ +#else +/* Just leaving this macro replacement empty leads to a dangling semicolon. */ +#define __FBSDID(a) struct _undefined_hack +#endif + +/* Try to get standard C99-style integer type definitions. */ +#if HAVE_INTTYPES_H +#include <inttypes.h> +#elif HAVE_STDINT_H +#include <stdint.h> +#endif + +/* Some platforms lack the standard *_MAX definitions. */ +#if !HAVE_DECL_SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif +#if !HAVE_DECL_UINT32_MAX +#define UINT32_MAX (~(uint32_t)0) +#endif +#if !HAVE_DECL_UINT64_MAX +#define UINT64_MAX (~(uint64_t)0) +#endif +#if !HAVE_DECL_INT64_MAX +#define INT64_MAX ((int64_t)(UINT64_MAX >> 1)) +#endif +#if !HAVE_DECL_INT64_MIN +#define INT64_MIN ((int64_t)(~INT64_MAX)) +#endif + +/* + * If this platform has <sys/acl.h>, acl_create(), acl_init(), + * acl_set_file(), and ACL_USER, we assume it has the rest of the + * POSIX.1e draft functions used in archive_read_extract.c. + */ +#if HAVE_SYS_ACL_H && HAVE_ACL_CREATE_ENTRY && HAVE_ACL_INIT && HAVE_ACL_SET_FILE && HAVE_ACL_USER +#define HAVE_POSIX_ACL 1 +#endif + +/* + * If we can't restore metadata using a file descriptor, then + * for compatibility's sake, close files before trying to restore metadata. + */ +#if defined(HAVE_FCHMOD) || defined(HAVE_FUTIMES) || defined(HAVE_ACL_SET_FD) || defined(HAVE_ACL_SET_FD_NP) || defined(HAVE_FCHOWN) +#define CAN_RESTORE_METADATA_FD +#endif + +/* 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 + +#endif /* !ARCHIVE_PLATFORM_H_INCLUDED */ diff --git a/libarchive/archive_private.h b/libarchive/archive_private.h new file mode 100644 index 00000000..9ca5893d --- /dev/null +++ b/libarchive/archive_private.h @@ -0,0 +1,99 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_private.h,v 1.29 2007/04/02 00:15:45 kientzle Exp $ + */ + +#ifndef ARCHIVE_PRIVATE_H_INCLUDED +#define ARCHIVE_PRIVATE_H_INCLUDED + +#include "archive.h" +#include "archive_string.h" + +#define ARCHIVE_WRITE_MAGIC (0xb0c5c0deU) +#define ARCHIVE_READ_MAGIC (0xdeb0c5U) +#define ARCHIVE_WRITE_DISK_MAGIC (0xc001b0c5U) + +#define ARCHIVE_STATE_ANY 0xFFFFU +#define ARCHIVE_STATE_NEW 1U +#define ARCHIVE_STATE_HEADER 2U +#define ARCHIVE_STATE_DATA 4U +#define ARCHIVE_STATE_DATA_END 8U +#define ARCHIVE_STATE_EOF 0x10U +#define ARCHIVE_STATE_CLOSED 0x20U +#define ARCHIVE_STATE_FATAL 0x8000U + +struct archive_vtable { + int (*archive_write_close)(struct archive *); + int (*archive_write_finish)(struct archive *); + int (*archive_write_header)(struct archive *, + struct archive_entry *); + int (*archive_write_finish_entry)(struct archive *); + ssize_t (*archive_write_data)(struct archive *, + const void *, size_t); + ssize_t (*archive_write_data_block)(struct archive *, + const void *, size_t, off_t); +}; + +struct archive { + /* + * The magic/state values are used to sanity-check the + * client's usage. If an API function is called at a + * ridiculous time, or the client passes us an invalid + * pointer, these values allow me to catch that. + */ + unsigned int magic; + unsigned int state; + + /* + * Some public API functions depend on the "real" type of the + * archive object. + */ + struct archive_vtable *vtable; + + int archive_format; + const char *archive_format_name; + + int compression_code; /* Currently active compression. */ + const char *compression_name; + + /* Position in UNCOMPRESSED data stream. */ + off_t file_position; + /* Position in COMPRESSED data stream. */ + off_t raw_position; + + int archive_error_number; + const char *error; + struct archive_string error_string; +}; + +/* Check magic value and state; exit if it isn't valid. */ +void __archive_check_magic(struct archive *, unsigned int magic, + unsigned int state, const char *func); + +void __archive_errx(int retvalue, const char *msg); + +#define err_combine(a,b) ((a) < (b) ? (a) : (b)) + +#endif diff --git a/libarchive/archive_read.3 b/libarchive/archive_read.3 new file mode 100644 index 00000000..e15c904a --- /dev/null +++ b/libarchive/archive_read.3 @@ -0,0 +1,582 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_read.3,v 1.36 2008/03/10 14:45:29 jkoshy Exp $ +.\" +.Dd August 19, 2006 +.Dt archive_read 3 +.Os +.Sh NAME +.Nm archive_read_new , +.Nm archive_read_support_compression_all , +.Nm archive_read_support_compression_bzip2 , +.Nm archive_read_support_compression_compress , +.Nm archive_read_support_compression_gzip , +.Nm archive_read_support_compression_none , +.Nm archive_read_support_compression_program , +.Nm archive_read_support_format_all , +.Nm archive_read_support_format_cpio , +.Nm archive_read_support_format_empty , +.Nm archive_read_support_format_iso9660 , +.Nm archive_read_support_format_tar , +.Nm archive_read_support_format_zip , +.Nm archive_read_open , +.Nm archive_read_open2 , +.Nm archive_read_open_fd , +.Nm archive_read_open_FILE , +.Nm archive_read_open_filename , +.Nm archive_read_open_memory , +.Nm archive_read_next_header , +.Nm archive_read_data , +.Nm archive_read_data_block , +.Nm archive_read_data_skip , +.\" #if ARCHIVE_API_VERSION < 3 +.Nm archive_read_data_into_buffer , +.\" #endif +.Nm archive_read_data_into_fd , +.Nm archive_read_extract , +.Nm archive_read_extract_set_progress_callback , +.Nm archive_read_close , +.Nm archive_read_finish +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_read_new "void" +.Ft int +.Fn archive_read_support_compression_all "struct archive *" +.Ft int +.Fn archive_read_support_compression_bzip2 "struct archive *" +.Ft int +.Fn archive_read_support_compression_compress "struct archive *" +.Ft int +.Fn archive_read_support_compression_gzip "struct archive *" +.Ft int +.Fn archive_read_support_compression_none "struct archive *" +.Ft int +.Fo archive_read_support_compression_program +.Fa "struct archive *" +.Fa "const char *cmd" +.Fc +.Ft int +.Fn archive_read_support_format_all "struct archive *" +.Ft int +.Fn archive_read_support_format_cpio "struct archive *" +.Ft int +.Fn archive_read_support_format_empty "struct archive *" +.Ft int +.Fn archive_read_support_format_iso9660 "struct archive *" +.Ft int +.Fn archive_read_support_format_tar "struct archive *" +.Ft int +.Fn archive_read_support_format_zip "struct archive *" +.Ft int +.Fo archive_read_open +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "archive_open_callback *" +.Fa "archive_read_callback *" +.Fa "archive_close_callback *" +.Fc +.Ft int +.Fo archive_read_open2 +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "archive_open_callback *" +.Fa "archive_read_callback *" +.Fa "archive_skip_callback *" +.Fa "archive_close_callback *" +.Fc +.Ft int +.Fn archive_read_open_FILE "struct archive *" "FILE *file" +.Ft int +.Fn archive_read_open_fd "struct archive *" "int fd" "size_t block_size" +.Ft int +.Fo archive_read_open_filename +.Fa "struct archive *" +.Fa "const char *filename" +.Fa "size_t block_size" +.Fc +.Ft int +.Fn archive_read_open_memory "struct archive *" "void *buff" "size_t size" +.Ft int +.Fn archive_read_next_header "struct archive *" "struct archive_entry **" +.Ft ssize_t +.Fn archive_read_data "struct archive *" "void *buff" "size_t len" +.Ft int +.Fo archive_read_data_block +.Fa "struct archive *" +.Fa "const void **buff" +.Fa "size_t *len" +.Fa "off_t *offset" +.Fc +.Ft int +.Fn archive_read_data_skip "struct archive *" +.\" #if ARCHIVE_API_VERSION < 3 +.Ft int +.Fn archive_read_data_into_buffer "struct archive *" "void *" "ssize_t len" +.\" #endif +.Ft int +.Fn archive_read_data_into_fd "struct archive *" "int fd" +.Ft int +.Fo archive_read_extract +.Fa "struct archive *" +.Fa "struct archive_entry *" +.Fa "int flags" +.Fc +.Ft void +.Fo archive_read_extract_set_progress_callback +.Fa "struct archive *" +.Fa "void (*func)(void *)" +.Fa "void *user_data" +.Fc +.Ft int +.Fn archive_read_close "struct archive *" +.Ft int +.Fn archive_read_finish "struct archive *" +.Sh DESCRIPTION +These functions provide a complete API for reading streaming archives. +The general process is to first create the +.Tn struct archive +object, set options, initialize the reader, iterate over the archive +headers and associated data, then close the archive and release all +resources. +The following summary describes the functions in approximately the +order they would be used: +.Bl -tag -compact -width indent +.It Fn archive_read_new +Allocates and initializes a +.Tn struct archive +object suitable for reading from an archive. +.It Xo +.Fn archive_read_support_compression_all , +.Fn archive_read_support_compression_bzip2 , +.Fn archive_read_support_compression_compress , +.Fn archive_read_support_compression_gzip , +.Fn archive_read_support_compression_none +.Xc +Enables auto-detection code and decompression support for the +specified compression. +Note that +.Dq none +is always enabled by default. +For convenience, +.Fn archive_read_support_compression_all +enables all available decompression code. +.It Fn archive_read_support_compression_program +Data is fed through the specified external program before being dearchived. +Note that this disables automatic detection of the compression format, +so it makes no sense to specify this in conjunction with any other +decompression option. +.It Xo +.Fn archive_read_support_format_all , +.Fn archive_read_support_format_cpio , +.Fn archive_read_support_format_empty , +.Fn archive_read_support_format_iso9660 , +.Fn archive_read_support_format_tar , +.Fn archive_read_support_format_zip +.Xc +Enables support---including auto-detection code---for the +specified archive format. +For example, +.Fn archive_read_support_format_tar +enables support for a variety of standard tar formats, old-style tar, +ustar, pax interchange format, and many common variants. +For convenience, +.Fn archive_read_support_format_all +enables support for all available formats. +Only empty archives are supported by default. +.It Fn archive_read_open +The same as +.Fn archive_read_open2 , +except that the skip callback is assumed to be +.Dv NULL . +.It Fn archive_read_open2 +Freeze the settings, open the archive, and prepare for reading entries. +This is the most generic version of this call, which accepts +four callback functions. +Most clients will want to use +.Fn archive_read_open_filename , +.Fn archive_read_open_FILE , +.Fn archive_read_open_fd , +or +.Fn archive_read_open_memory +instead. +The library invokes the client-provided functions to obtain +raw bytes from the archive. +.It Fn archive_read_open_FILE +Like +.Fn archive_read_open , +except that it accepts a +.Ft "FILE *" +pointer. +This function should not be used with tape drives or other devices +that require strict I/O blocking. +.It Fn archive_read_open_fd +Like +.Fn archive_read_open , +except that it accepts a file descriptor and block size rather than +a set of function pointers. +Note that the file descriptor will not be automatically closed at +end-of-archive. +This function is safe for use with tape drives or other blocked devices. +.It Fn archive_read_open_file +This is a deprecated synonym for +.Fn archive_read_open_filename . +.It Fn archive_read_open_filename +Like +.Fn archive_read_open , +except that it accepts a simple filename and a block size. +A NULL filename represents standard input. +This function is safe for use with tape drives or other blocked devices. +.It Fn archive_read_open_memory +Like +.Fn archive_read_open , +except that it accepts a pointer and size of a block of +memory containing the archive data. +.It Fn archive_read_next_header +Read the header for the next entry and return a pointer to +a +.Tn struct archive_entry . +.It Fn archive_read_data +Read data associated with the header just read. +Internally, this is a convenience function that calls +.Fn archive_read_data_block +and fills any gaps with nulls so that callers see a single +continuous stream of data. +.It Fn archive_read_data_block +Return the next available block of data for this entry. +Unlike +.Fn archive_read_data , +the +.Fn archive_read_data_block +function avoids copying data and allows you to correctly handle +sparse files, as supported by some archive formats. +The library guarantees that offsets will increase and that blocks +will not overlap. +Note that the blocks returned from this function can be much larger +than the block size read from disk, due to compression +and internal buffer optimizations. +.It Fn archive_read_data_skip +A convenience function that repeatedly calls +.Fn archive_read_data_block +to skip all of the data for this archive entry. +.\" #if ARCHIVE_API_VERSION < 3 +.It Fn archive_read_data_into_buffer +This function is deprecated and will be removed. +Use +.Fn archive_read_data +instead. +.\" #endif +.It Fn archive_read_data_into_fd +A convenience function that repeatedly calls +.Fn archive_read_data_block +to copy the entire entry to the provided file descriptor. +.It Fn archive_read_extract , Fn archive_read_extract_set_skip_file +A convenience function that wraps the corresponding +.Xr archive_write_disk 3 +interfaces. +The first call to +.Fn archive_read_extract +creates a restore object using +.Xr archive_write_disk_new 3 +and +.Xr archive_write_disk_set_standard_lookup 3 , +then transparently invokes +.Xr archive_write_disk_set_options 3 , +.Xr archive_write_header 3 , +.Xr archive_write_data 3 , +and +.Xr archive_write_finish_entry 3 +to create the entry on disk and copy data into it. +The +.Va flags +argument is passed unmodified to +.Xr archive_write_disk_set_options 3 . +.It Fn archive_read_extract_set_progress_callback +Sets a pointer to a user-defined callback that can be used +for updating progress displays during extraction. +The progress function will be invoked during the extraction of large +regular files. +The progress function will be invoked with the pointer provided to this call. +Generally, the data pointed to should include a reference to the archive +object and the archive_entry object so that various statistics +can be retrieved for the progress display. +.It Fn archive_read_close +Complete the archive and invoke the close callback. +.It Fn archive_read_finish +Invokes +.Fn archive_read_close +if it was not invoked manually, then release all resources. +Note: In libarchive 1.x, this function was declared to return +.Ft void , +which made it impossible to detect certain errors when +.Fn archive_read_close +was invoked implicitly from this function. +The declaration is corrected beginning with libarchive 2.0. +.El +.Pp +Note that the library determines most of the relevant information about +the archive by inspection. +In particular, it automatically detects +.Xr gzip 1 +or +.Xr bzip2 1 +compression and transparently performs the appropriate decompression. +It also automatically detects the archive format. +.Pp +A complete description of the +.Tn struct archive +and +.Tn struct archive_entry +objects can be found in the overview manual page for +.Xr libarchive 3 . +.Sh CLIENT CALLBACKS +The callback functions must match the following prototypes: +.Bl -item -offset indent +.It +.Ft typedef ssize_t +.Fo archive_read_callback +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "const void **buffer" +.Fc +.It +.\" #if ARCHIVE_API_VERSION < 2 +.Ft typedef int +.Fo archive_skip_callback +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "size_t request" +.Fc +.\" #else +.\" .Ft typedef off_t +.\" .Fo archive_skip_callback +.\" .Fa "struct archive *" +.\" .Fa "void *client_data" +.\" .Fa "off_t request" +.\" .Fc +.\" #endif +.It +.Ft typedef int +.Fn archive_open_callback "struct archive *" "void *client_data" +.It +.Ft typedef int +.Fn archive_close_callback "struct archive *" "void *client_data" +.El +.Pp +The open callback is invoked by +.Fn archive_open . +It should return +.Cm ARCHIVE_OK +if the underlying file or data source is successfully +opened. +If the open fails, it should call +.Fn archive_set_error +to register an error code and message and return +.Cm ARCHIVE_FATAL . +.Pp +The read callback is invoked whenever the library +requires raw bytes from the archive. +The read callback should read data into a buffer, +set the +.Li const void **buffer +argument to point to the available data, and +return a count of the number of bytes available. +The library will invoke the read callback again +only after it has consumed this data. +The library imposes no constraints on the size +of the data blocks returned. +On end-of-file, the read callback should +return zero. +On error, the read callback should invoke +.Fn archive_set_error +to register an error code and message and +return -1. +.Pp +The skip callback is invoked when the +library wants to ignore a block of data. +The return value is the number of bytes actually +skipped, which may differ from the request. +If the callback cannot skip data, it should return +zero. +If the skip callback is not provided (the +function pointer is +.Dv NULL ), +the library will invoke the read function +instead and simply discard the result. +A skip callback can provide significant +performance gains when reading uncompressed +archives from slow disk drives or other media +that can skip quickly. +.Pp +The close callback is invoked by archive_close when +the archive processing is complete. +The callback should return +.Cm ARCHIVE_OK +on success. +On failure, the callback should invoke +.Fn archive_set_error +to register an error code and message and +return +.Cm ARCHIVE_FATAL. +.Sh EXAMPLE +The following illustrates basic usage of the library. +In this example, +the callback functions are simply wrappers around the standard +.Xr open 2 , +.Xr read 2 , +and +.Xr close 2 +system calls. +.Bd -literal -offset indent +void +list_archive(const char *name) +{ + struct mydata *mydata; + struct archive *a; + struct archive_entry *entry; + + mydata = malloc(sizeof(struct mydata)); + a = archive_read_new(); + mydata->name = name; + archive_read_support_compression_all(a); + archive_read_support_format_all(a); + archive_read_open(a, mydata, myopen, myread, myclose); + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + printf("%s\\n",archive_entry_pathname(entry)); + archive_read_data_skip(a); + } + archive_read_finish(a); + free(mydata); +} + +ssize_t +myread(struct archive *a, void *client_data, const void **buff) +{ + struct mydata *mydata = client_data; + + *buff = mydata->buff; + return (read(mydata->fd, mydata->buff, 10240)); +} + +int +myopen(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + mydata->fd = open(mydata->name, O_RDONLY); + return (mydata->fd >= 0 ? ARCHIVE_OK : ARCHIVE_FATAL); +} + +int +myclose(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + if (mydata->fd > 0) + close(mydata->fd); + return (ARCHIVE_OK); +} +.Ed +.Sh RETURN VALUES +Most functions return zero on success, non-zero on error. +The possible return codes include: +.Cm ARCHIVE_OK +(the operation succeeded), +.Cm ARCHIVE_WARN +(the operation succeeded but a non-critical error was encountered), +.Cm ARCHIVE_EOF +(end-of-archive was encountered), +.Cm ARCHIVE_RETRY +(the operation failed but can be retried), +and +.Cm ARCHIVE_FATAL +(there was a fatal error; the archive should be closed immediately). +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.Pp +.Fn archive_read_new +returns a pointer to a freshly allocated +.Tn struct archive +object. +It returns +.Dv NULL +on error. +.Pp +.Fn archive_read_data +returns a count of bytes actually read or zero at the end of the entry. +On error, a value of +.Cm ARCHIVE_FATAL , +.Cm ARCHIVE_WARN , +or +.Cm ARCHIVE_RETRY +is returned and an error code and textual description can be retrieved from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.Pp +The library expects the client callbacks to behave similarly. +If there is an error, you can use +.Fn archive_set_error +to set an appropriate error code and description, +then return one of the non-zero values above. +(Note that the value eventually returned to the client may +not be the same; many errors that are not critical at the level +of basic I/O can prevent the archive from being properly read, +thus most I/O errors eventually cause +.Cm ARCHIVE_FATAL +to be returned.) +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr tar 1 , +.Xr archive 3 , +.Xr archive_util 3 , +.Xr tar 5 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +Many traditional archiver programs treat +empty files as valid empty archives. +For example, many implementations of +.Xr tar 1 +allow you to append entries to an empty file. +Of course, it is impossible to determine the format of an empty file +by inspecting the contents, so this library treats empty files as +having a special +.Dq empty +format. diff --git a/libarchive/archive_read.c b/libarchive/archive_read.c new file mode 100644 index 00000000..327969f4 --- /dev/null +++ b/libarchive/archive_read.c @@ -0,0 +1,739 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the "essential" portions of the read API, that + * is, stuff that will probably always be used by any client that + * actually needs to read an archive. Optional pieces have been, as + * far as possible, separated out into separate files to avoid + * needlessly bloating statically-linked clients. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read.c,v 1.38 2008/03/12 04:58:32 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +static void choose_decompressor(struct archive_read *, const void*, size_t); +static int choose_format(struct archive_read *); +static off_t dummy_skip(struct archive_read *, off_t); + +/* + * Allocate, initialize and return a struct archive object. + */ +struct archive * +archive_read_new(void) +{ + struct archive_read *a; + + a = (struct archive_read *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_READ_MAGIC; + + a->archive.state = ARCHIVE_STATE_NEW; + a->entry = archive_entry_new(); + + /* We always support uncompressed archives. */ + archive_read_support_compression_none(&a->archive); + + return (&a->archive); +} + +/* + * Record the do-not-extract-to file. This belongs in archive_read_extract.c. + */ +void +archive_read_extract_set_skip_file(struct archive *_a, dev_t d, ino_t i) +{ + struct archive_read *a = (struct archive_read *)_a; + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_ANY, + "archive_read_extract_set_skip_file"); + a->skip_file_dev = d; + a->skip_file_ino = i; +} + + +/* + * Open the archive + */ +int +archive_read_open(struct archive *a, void *client_data, + archive_open_callback *client_opener, archive_read_callback *client_reader, + archive_close_callback *client_closer) +{ + /* Old archive_read_open() is just a thin shell around + * archive_read_open2. */ + return archive_read_open2(a, client_data, client_opener, + client_reader, NULL, client_closer); +} + +int +archive_read_open2(struct archive *_a, void *client_data, + archive_open_callback *client_opener, + archive_read_callback *client_reader, + archive_skip_callback *client_skipper, + archive_close_callback *client_closer) +{ + struct archive_read *a = (struct archive_read *)_a; + const void *buffer; + ssize_t bytes_read; + int e; + + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, "archive_read_open"); + + if (client_reader == NULL) + __archive_errx(1, + "No reader function provided to archive_read_open"); + + /* + * Set these NULL initially. If the open or initial read fails, + * we'll leave them NULL to indicate that the file is invalid. + * (In particular, this helps ensure that the closer doesn't + * get called more than once.) + */ + a->client_opener = NULL; + a->client_reader = NULL; + a->client_skipper = NULL; + a->client_closer = NULL; + a->client_data = NULL; + + /* Open data source. */ + if (client_opener != NULL) { + e =(client_opener)(&a->archive, client_data); + if (e != 0) { + /* If the open failed, call the closer to clean up. */ + if (client_closer) + (client_closer)(&a->archive, client_data); + return (e); + } + } + + /* Read first block now for compress format detection. */ + bytes_read = (client_reader)(&a->archive, client_data, &buffer); + + if (bytes_read < 0) { + /* If the first read fails, close before returning error. */ + if (client_closer) + (client_closer)(&a->archive, client_data); + /* client_reader should have already set error information. */ + return (ARCHIVE_FATAL); + } + + /* Now that the client callbacks have worked, remember them. */ + a->client_opener = client_opener; /* Do we need to remember this? */ + a->client_reader = client_reader; + a->client_skipper = client_skipper; + a->client_closer = client_closer; + a->client_data = client_data; + + /* Select a decompression routine. */ + choose_decompressor(a, buffer, (size_t)bytes_read); + if (a->decompressor == NULL) + return (ARCHIVE_FATAL); + + /* Initialize decompression routine with the first block of data. */ + e = (a->decompressor->init)(a, buffer, (size_t)bytes_read); + + if (e == ARCHIVE_OK) + a->archive.state = ARCHIVE_STATE_HEADER; + + /* + * If the decompressor didn't register a skip function, provide a + * dummy compression-layer skip function. + */ + if (a->decompressor->skip == NULL) + a->decompressor->skip = dummy_skip; + + return (e); +} + +/* + * Allow each registered decompression routine to bid on whether it + * wants to handle this stream. Return index of winning bidder. + */ +static void +choose_decompressor(struct archive_read *a, + const void *buffer, size_t bytes_read) +{ + int decompression_slots, i, bid, best_bid; + struct decompressor_t *decompressor, *best_decompressor; + + decompression_slots = sizeof(a->decompressors) / + sizeof(a->decompressors[0]); + + best_bid = 0; + a->decompressor = NULL; + best_decompressor = NULL; + + decompressor = a->decompressors; + for (i = 0; i < decompression_slots; i++) { + if (decompressor->bid) { + bid = (decompressor->bid)(buffer, bytes_read); + if (bid > best_bid || best_decompressor == NULL) { + best_bid = bid; + best_decompressor = decompressor; + } + } + decompressor ++; + } + + /* + * There were no bidders; this is a serious programmer error + * and demands a quick and definitive abort. + */ + if (best_decompressor == NULL) + __archive_errx(1, "No decompressors were registered; you " + "must call at least one " + "archive_read_support_compression_XXX function in order " + "to successfully read an archive."); + + /* + * There were bidders, but no non-zero bids; this means we can't + * support this stream. + */ + if (best_bid < 1) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized archive format"); + return; + } + + /* Record the best decompressor for this stream. */ + a->decompressor = best_decompressor; +} + +/* + * Dummy skip function, for use if the compression layer doesn't provide + * one: This code just reads data and discards it. + */ +static off_t +dummy_skip(struct archive_read * a, off_t request) +{ + const void * dummy_buffer; + ssize_t bytes_read; + off_t bytes_skipped; + + for (bytes_skipped = 0; request > 0;) { + bytes_read = (a->decompressor->read_ahead)(a, &dummy_buffer, 1); + if (bytes_read < 0) + return (bytes_read); + if (bytes_read == 0) { + /* Premature EOF. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated input file (need to skip %jd bytes)", + (intmax_t)request); + return (ARCHIVE_FATAL); + } + if (bytes_read > request) + bytes_read = (ssize_t)request; + (a->decompressor->consume)(a, (size_t)bytes_read); + request -= bytes_read; + bytes_skipped += bytes_read; + } + + return (bytes_skipped); +} + +/* + * Read header of next entry. + */ +int +archive_read_next_header(struct archive *_a, struct archive_entry **entryp) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_entry *entry; + int slot, ret; + + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_read_next_header"); + + *entryp = NULL; + entry = a->entry; + archive_entry_clear(entry); + archive_clear_error(&a->archive); + + /* + * If no format has yet been chosen, choose one. + */ + if (a->format == NULL) { + slot = choose_format(a); + if (slot < 0) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + a->format = &(a->formats[slot]); + } + + /* + * If client didn't consume entire data, skip any remainder + * (This is especially important for GNU incremental directories.) + */ + if (a->archive.state == ARCHIVE_STATE_DATA) { + ret = archive_read_data_skip(&a->archive); + if (ret == ARCHIVE_EOF) { + archive_set_error(&a->archive, EIO, "Premature end-of-file."); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + if (ret != ARCHIVE_OK) + return (ret); + } + + /* Record start-of-header. */ + a->header_position = a->archive.file_position; + + ret = (a->format->read_header)(a, entry); + + /* + * EOF and FATAL are persistent at this layer. By + * modifying the state, we guarantee that future calls to + * read a header or read data will fail. + */ + switch (ret) { + case ARCHIVE_EOF: + a->archive.state = ARCHIVE_STATE_EOF; + break; + case ARCHIVE_OK: + a->archive.state = ARCHIVE_STATE_DATA; + break; + case ARCHIVE_WARN: + a->archive.state = ARCHIVE_STATE_DATA; + break; + case ARCHIVE_RETRY: + break; + case ARCHIVE_FATAL: + a->archive.state = ARCHIVE_STATE_FATAL; + break; + } + + *entryp = entry; + a->read_data_output_offset = 0; + a->read_data_remaining = 0; + return (ret); +} + +/* + * Allow each registered format to bid on whether it wants to handle + * the next entry. Return index of winning bidder. + */ +static int +choose_format(struct archive_read *a) +{ + int slots; + int i; + int bid, best_bid; + int best_bid_slot; + + slots = sizeof(a->formats) / sizeof(a->formats[0]); + best_bid = -1; + best_bid_slot = -1; + + /* Set up a->format and a->pformat_data for convenience of bidders. */ + a->format = &(a->formats[0]); + for (i = 0; i < slots; i++, a->format++) { + if (a->format->bid) { + bid = (a->format->bid)(a); + if (bid == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + if ((bid > best_bid) || (best_bid_slot < 0)) { + best_bid = bid; + best_bid_slot = i; + } + } + } + + /* + * There were no bidders; this is a serious programmer error + * and demands a quick and definitive abort. + */ + if (best_bid_slot < 0) + __archive_errx(1, "No formats were registered; you must " + "invoke at least one archive_read_support_format_XXX " + "function in order to successfully read an archive."); + + /* + * There were bidders, but no non-zero bids; this means we + * can't support this stream. + */ + if (best_bid < 1) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized archive format"); + return (ARCHIVE_FATAL); + } + + return (best_bid_slot); +} + +/* + * Return the file offset (within the uncompressed data stream) where + * the last header started. + */ +int64_t +archive_read_header_position(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_header_position"); + return (a->header_position); +} + +/* + * Read data from an archive entry, using a read(2)-style interface. + * This is a convenience routine that just calls + * archive_read_data_block and copies the results into the client + * buffer, filling any gaps with zero bytes. Clients using this + * API can be completely ignorant of sparse-file issues; sparse files + * will simply be padded with nulls. + * + * DO NOT intermingle calls to this function and archive_read_data_block + * to read a single entry body. + */ +ssize_t +archive_read_data(struct archive *_a, void *buff, size_t s) +{ + struct archive_read *a = (struct archive_read *)_a; + char *dest; + const void *read_buf; + size_t bytes_read; + size_t len; + int r; + + bytes_read = 0; + dest = (char *)buff; + + while (s > 0) { + if (a->read_data_remaining == 0) { + read_buf = a->read_data_block; + r = archive_read_data_block(&a->archive, &read_buf, + &a->read_data_remaining, &a->read_data_offset); + a->read_data_block = read_buf; + if (r == ARCHIVE_EOF) + return (bytes_read); + /* + * Error codes are all negative, so the status + * return here cannot be confused with a valid + * byte count. (ARCHIVE_OK is zero.) + */ + if (r < ARCHIVE_OK) + return (r); + } + + if (a->read_data_offset < a->read_data_output_offset) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Encountered out-of-order sparse blocks"); + return (ARCHIVE_RETRY); + } + + /* Compute the amount of zero padding needed. */ + if (a->read_data_output_offset + (off_t)s < + a->read_data_offset) { + len = s; + } else if (a->read_data_output_offset < + a->read_data_offset) { + len = a->read_data_offset - + a->read_data_output_offset; + } else + len = 0; + + /* Add zeroes. */ + memset(dest, 0, len); + s -= len; + a->read_data_output_offset += len; + dest += len; + bytes_read += len; + + /* Copy data if there is any space left. */ + if (s > 0) { + len = a->read_data_remaining; + if (len > s) + len = s; + memcpy(dest, a->read_data_block, len); + s -= len; + a->read_data_block += len; + a->read_data_remaining -= len; + a->read_data_output_offset += len; + a->read_data_offset += len; + dest += len; + bytes_read += len; + } + } + return (bytes_read); +} + +#if ARCHIVE_API_VERSION < 3 +/* + * Obsolete function provided for compatibility only. Note that the API + * of this function doesn't allow the caller to detect if the remaining + * data from the archive entry is shorter than the buffer provided, or + * even if an error occurred while reading data. + */ +int +archive_read_data_into_buffer(struct archive *a, void *d, ssize_t len) +{ + + archive_read_data(a, d, len); + return (ARCHIVE_OK); +} +#endif + +/* + * Skip over all remaining data in this entry. + */ +int +archive_read_data_skip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int r; + const void *buff; + size_t size; + off_t offset; + + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_skip"); + + if (a->format->read_data_skip != NULL) + r = (a->format->read_data_skip)(a); + else { + while ((r = archive_read_data_block(&a->archive, + &buff, &size, &offset)) + == ARCHIVE_OK) + ; + } + + if (r == ARCHIVE_EOF) + r = ARCHIVE_OK; + + a->archive.state = ARCHIVE_STATE_HEADER; + return (r); +} + +/* + * Read the next block of entry data from the archive. + * This is a zero-copy interface; the client receives a pointer, + * size, and file offset of the next available block of data. + * + * Returns ARCHIVE_OK if the operation is successful, ARCHIVE_EOF if + * the end of entry is encountered. + */ +int +archive_read_data_block(struct archive *_a, + const void **buff, size_t *size, off_t *offset) +{ + struct archive_read *a = (struct archive_read *)_a; + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_block"); + + if (a->format->read_data == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "Internal error: " + "No format_read_data_block function registered"); + return (ARCHIVE_FATAL); + } + + return (a->format->read_data)(a, buff, size, offset); +} + +/* + * Close the file and release most resources. + * + * Be careful: client might just call read_new and then read_finish. + * Don't assume we actually read anything or performed any non-trivial + * initialization. + */ +int +archive_read_close(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int r = ARCHIVE_OK, r1 = ARCHIVE_OK; + size_t i, n; + + __archive_check_magic(&a->archive, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_close"); + a->archive.state = ARCHIVE_STATE_CLOSED; + + /* Call cleanup functions registered by optional components. */ + if (a->cleanup_archive_extract != NULL) + r = (a->cleanup_archive_extract)(a); + + /* TODO: Clean up the formatters. */ + + /* Clean up the decompressors. */ + n = sizeof(a->decompressors)/sizeof(a->decompressors[0]); + for (i = 0; i < n; i++) { + if (a->decompressors[i].finish != NULL) { + r1 = (a->decompressors[i].finish)(a); + if (r1 < r) + r = r1; + } + } + + /* Close the client stream. */ + if (a->client_closer != NULL) { + r1 = ((a->client_closer)(&a->archive, a->client_data)); + if (r1 < r) + r = r1; + } + + return (r); +} + +/* + * Release memory and other resources. + */ +#if ARCHIVE_API_VERSION > 1 +int +#else +/* Temporarily allow library to compile with either 1.x or 2.0 API. */ +void +#endif +archive_read_finish(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int i; + int slots; + int r = ARCHIVE_OK; + + __archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_ANY, + "archive_read_finish"); + if (a->archive.state != ARCHIVE_STATE_CLOSED) + r = archive_read_close(&a->archive); + + /* Cleanup format-specific data. */ + slots = sizeof(a->formats) / sizeof(a->formats[0]); + for (i = 0; i < slots; i++) { + a->format = &(a->formats[i]); + if (a->formats[i].cleanup) + (a->formats[i].cleanup)(a); + } + + archive_string_free(&a->archive.error_string); + if (a->entry) + archive_entry_free(a->entry); + a->archive.magic = 0; + free(a); +#if ARCHIVE_API_VERSION > 1 + return (r); +#endif +} + +/* + * Used internally by read format handlers to register their bid and + * initialization functions. + */ +int +__archive_read_register_format(struct archive_read *a, + void *format_data, + int (*bid)(struct archive_read *), + int (*read_header)(struct archive_read *, struct archive_entry *), + int (*read_data)(struct archive_read *, const void **, size_t *, off_t *), + int (*read_data_skip)(struct archive_read *), + int (*cleanup)(struct archive_read *)) +{ + int i, number_slots; + + __archive_check_magic(&a->archive, + ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "__archive_read_register_format"); + + number_slots = sizeof(a->formats) / sizeof(a->formats[0]); + + for (i = 0; i < number_slots; i++) { + if (a->formats[i].bid == bid) + return (ARCHIVE_WARN); /* We've already installed */ + if (a->formats[i].bid == NULL) { + a->formats[i].bid = bid; + a->formats[i].read_header = read_header; + a->formats[i].read_data = read_data; + a->formats[i].read_data_skip = read_data_skip; + a->formats[i].cleanup = cleanup; + a->formats[i].data = format_data; + return (ARCHIVE_OK); + } + } + + __archive_errx(1, "Not enough slots for format registration"); + return (ARCHIVE_FATAL); /* Never actually called. */ +} + +/* + * Used internally by decompression routines to register their bid and + * initialization functions. + */ +struct decompressor_t * +__archive_read_register_compression(struct archive_read *a, + int (*bid)(const void *, size_t), + int (*init)(struct archive_read *, const void *, size_t)) +{ + int i, number_slots; + + __archive_check_magic(&a->archive, + ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "__archive_read_register_compression"); + + number_slots = sizeof(a->decompressors) / sizeof(a->decompressors[0]); + + for (i = 0; i < number_slots; i++) { + if (a->decompressors[i].bid == bid) + return (a->decompressors + i); + if (a->decompressors[i].bid == NULL) { + a->decompressors[i].bid = bid; + a->decompressors[i].init = init; + return (a->decompressors + i); + } + } + + __archive_errx(1, "Not enough slots for compression registration"); + return (NULL); /* Never actually executed. */ +} + +/* used internally to simplify read-ahead */ +const void * +__archive_read_ahead(struct archive_read *a, size_t len) +{ + const void *h; + + if ((a->decompressor->read_ahead)(a, &h, len) < (ssize_t)len) + return (NULL); + return (h); +} diff --git a/libarchive/archive_read_data_into_fd.c b/libarchive/archive_read_data_into_fd.c new file mode 100644 index 00000000..664cc2c3 --- /dev/null +++ b/libarchive/archive_read_data_into_fd.c @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_data_into_fd.c,v 1.15 2007/04/02 00:21:46 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" +#include "archive_private.h" + +/* Maximum amount of data to write at one time. */ +#define MAX_WRITE (1024 * 1024) + +/* + * This implementation minimizes copying of data and is sparse-file aware. + */ +int +archive_read_data_into_fd(struct archive *a, int fd) +{ + int r; + const void *buff; + size_t size, bytes_to_write; + ssize_t bytes_written, total_written; + off_t offset; + off_t output_offset; + + __archive_check_magic(a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA, "archive_read_data_into_fd"); + + total_written = 0; + output_offset = 0; + + while ((r = archive_read_data_block(a, &buff, &size, &offset)) == + ARCHIVE_OK) { + const char *p = buff; + if (offset > output_offset) { + lseek(fd, offset - output_offset, SEEK_CUR); + output_offset = offset; + } + while (size > 0) { + bytes_to_write = size; + if (bytes_to_write > MAX_WRITE) + bytes_to_write = MAX_WRITE; + bytes_written = write(fd, p, bytes_to_write); + if (bytes_written < 0) { + archive_set_error(a, errno, "Write error"); + return (-1); + } + output_offset += bytes_written; + total_written += bytes_written; + p += bytes_written; + size -= bytes_written; + } + } + + if (r != ARCHIVE_EOF) + return (r); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_extract.c b/libarchive/archive_read_extract.c new file mode 100644 index 00000000..bb5add7b --- /dev/null +++ b/libarchive/archive_read_extract.c @@ -0,0 +1,176 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_extract.c,v 1.60 2008/01/18 04:53:45 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_write_disk_private.h" + +struct extract { + struct archive *ad; /* archive_write_disk object */ + + /* Progress function invoked during extract. */ + void (*extract_progress)(void *); + void *extract_progress_user_data; +}; + +static int archive_read_extract_cleanup(struct archive_read *); +static int copy_data(struct archive *ar, struct archive *aw); +static struct extract *get_extract(struct archive_read *); + +static struct extract * +get_extract(struct archive_read *a) +{ + /* If we haven't initialized, do it now. */ + /* This also sets up a lot of global state. */ + if (a->extract == NULL) { + a->extract = (struct extract *)malloc(sizeof(*a->extract)); + if (a->extract == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't extract"); + return (NULL); + } + memset(a->extract, 0, sizeof(*a->extract)); + a->extract->ad = archive_write_disk_new(); + if (a->extract->ad == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't extract"); + return (NULL); + } + archive_write_disk_set_standard_lookup(a->extract->ad); + a->cleanup_archive_extract = archive_read_extract_cleanup; + } + return (a->extract); +} + +int +archive_read_extract(struct archive *_a, struct archive_entry *entry, int flags) +{ + struct archive_read *a = (struct archive_read *)_a; + struct extract *extract; + int r, r2; + + extract = get_extract(a); + if (extract == NULL) + return (ARCHIVE_FATAL); + + /* Set up for this particular entry. */ + extract = a->extract; + archive_write_disk_set_options(a->extract->ad, flags); + archive_write_disk_set_skip_file(a->extract->ad, + a->skip_file_dev, a->skip_file_ino); + r = archive_write_header(a->extract->ad, entry); + if (r < ARCHIVE_WARN) + r = ARCHIVE_WARN; + if (r != ARCHIVE_OK) + /* If _write_header failed, copy the error. */ + archive_copy_error(&a->archive, extract->ad); + else + /* Otherwise, pour data into the entry. */ + r = copy_data(_a, a->extract->ad); + r2 = archive_write_finish_entry(a->extract->ad); + if (r2 < ARCHIVE_WARN) + r2 = ARCHIVE_WARN; + /* Use the first message. */ + if (r2 != ARCHIVE_OK && r == ARCHIVE_OK) + archive_copy_error(&a->archive, extract->ad); + /* Use the worst error return. */ + if (r2 < r) + r = r2; + return (r); +} + +void +archive_read_extract_set_progress_callback(struct archive *_a, + void (*progress_func)(void *), void *user_data) +{ + struct archive_read *a = (struct archive_read *)_a; + struct extract *extract = get_extract(a); + if (extract != NULL) { + extract->extract_progress = progress_func; + extract->extract_progress_user_data = user_data; + } +} + +static int +copy_data(struct archive *ar, struct archive *aw) +{ + off_t offset; + const void *buff; + struct extract *extract; + size_t size; + int r; + + extract = get_extract((struct archive_read *)ar); + for (;;) { + r = archive_read_data_block(ar, &buff, &size, &offset); + if (r == ARCHIVE_EOF) + return (ARCHIVE_OK); + if (r != ARCHIVE_OK) + return (r); + r = archive_write_data_block(aw, buff, size, offset); + if (r < ARCHIVE_WARN) + r = ARCHIVE_WARN; + if (r != ARCHIVE_OK) { + archive_set_error(ar, archive_errno(aw), + "%s", archive_error_string(aw)); + return (r); + } + if (extract->extract_progress) + (extract->extract_progress) + (extract->extract_progress_user_data); + } +} + +/* + * Cleanup function for archive_extract. + */ +static int +archive_read_extract_cleanup(struct archive_read *a) +{ + int ret = ARCHIVE_OK; + +#if ARCHIVE_API_VERSION > 1 + ret = +#endif + archive_write_finish(a->extract->ad); + free(a->extract); + a->extract = NULL; + return (ret); +} diff --git a/libarchive/archive_read_open_fd.c b/libarchive/archive_read_open_fd.c new file mode 100644 index 00000000..2ebd46d6 --- /dev/null +++ b/libarchive/archive_read_open_fd.c @@ -0,0 +1,187 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_fd.c,v 1.13 2007/06/26 03:06:48 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" + +struct read_fd_data { + int fd; + size_t block_size; + char can_skip; + void *buffer; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_read(struct archive *, void *, const void **buff); +#if ARCHIVE_API_VERSION < 2 +static ssize_t file_skip(struct archive *, void *, size_t request); +#else +static off_t file_skip(struct archive *, void *, off_t request); +#endif + +int +archive_read_open_fd(struct archive *a, int fd, size_t block_size) +{ + struct read_fd_data *mine; + + mine = (struct read_fd_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->block_size = block_size; + mine->buffer = malloc(mine->block_size); + if (mine->buffer == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + free(mine); + return (ARCHIVE_FATAL); + } + mine->fd = fd; + /* lseek() hardly ever works, so disable it by default. See below. */ + mine->can_skip = 0; + return (archive_read_open2(a, mine, file_open, file_read, file_skip, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + struct stat st; + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Can't stat fd %d", mine->fd); + return (ARCHIVE_FATAL); + } + + if (S_ISREG(st.st_mode)) { + archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); + /* + * Enabling skip here is a performance optimization for + * anything that supports lseek(). On FreeBSD, only + * regular files and raw disk devices support lseek() and + * there's no portable way to determine if a device is + * a raw disk device, so we only enable this optimization + * for regular files. + */ + mine->can_skip = 1; + } + return (ARCHIVE_OK); +} + +static ssize_t +file_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + ssize_t bytes_read; + + *buff = mine->buffer; + bytes_read = read(mine->fd, mine->buffer, mine->block_size); + if (bytes_read < 0) { + archive_set_error(a, errno, "Error reading fd %d", mine->fd); + } + return (bytes_read); +} + +#if ARCHIVE_API_VERSION < 2 +static ssize_t +file_skip(struct archive *a, void *client_data, size_t request) +#else +static off_t +file_skip(struct archive *a, void *client_data, off_t request) +#endif +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + off_t old_offset, new_offset; + + if (!mine->can_skip) + return (0); + + /* Reduce request to the next smallest multiple of block_size */ + request = (request / mine->block_size) * mine->block_size; + if (request == 0) + return (0); + + /* + * Hurray for lazy evaluation: if the first lseek fails, the second + * one will not be executed. + */ + if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) < 0) || + ((new_offset = lseek(mine->fd, request, SEEK_CUR)) < 0)) + { + /* If seek failed once, it will probably fail again. */ + mine->can_skip = 0; + + if (errno == ESPIPE) + { + /* + * Failure to lseek() can be caused by the file + * descriptor pointing to a pipe, socket or FIFO. + * Return 0 here, so the compression layer will use + * read()s instead to advance the file descriptor. + * It's slower of course, but works as well. + */ + return (0); + } + /* + * There's been an error other than ESPIPE. This is most + * likely caused by a programmer error (too large request) + * or a corrupted archive file. + */ + archive_set_error(a, errno, "Error seeking"); + return (-1); + } + return (new_offset - old_offset); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + + (void)a; /* UNUSED */ + if (mine->buffer != NULL) + free(mine->buffer); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_open_file.c b/libarchive/archive_read_open_file.c new file mode 100644 index 00000000..55c431c7 --- /dev/null +++ b/libarchive/archive_read_open_file.c @@ -0,0 +1,167 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_file.c,v 1.20 2007/06/26 03:06:48 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" + +struct read_FILE_data { + FILE *f; + size_t block_size; + void *buffer; + char can_skip; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_read(struct archive *, void *, const void **buff); +#if ARCHIVE_API_VERSION < 2 +static ssize_t file_skip(struct archive *, void *, size_t request); +#else +static off_t file_skip(struct archive *, void *, off_t request); +#endif + +int +archive_read_open_FILE(struct archive *a, FILE *f) +{ + struct read_FILE_data *mine; + + mine = (struct read_FILE_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->block_size = 128 * 1024; + mine->buffer = malloc(mine->block_size); + if (mine->buffer == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + free(mine); + return (ARCHIVE_FATAL); + } + mine->f = f; + /* Suppress skip by default. See below. */ + mine->can_skip = 0; + return (archive_read_open2(a, mine, file_open, file_read, + file_skip, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; + struct stat st; + + /* + * If we can't fstat() the file, it may just be that + * it's not a file. (FILE * objects can wrap many kinds + * of I/O streams.) + */ + if (fstat(fileno(mine->f), &st) == 0 && S_ISREG(st.st_mode)) { + archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); + /* Enable the seek optimization for regular files. */ + mine->can_skip = 1; + } + + return (ARCHIVE_OK); +} + +static ssize_t +file_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; + ssize_t bytes_read; + + *buff = mine->buffer; + bytes_read = fread(mine->buffer, 1, mine->block_size, mine->f); + if (bytes_read < 0) { + archive_set_error(a, errno, "Error reading file"); + } + return (bytes_read); +} + +#if ARCHIVE_API_VERSION < 2 +static ssize_t +file_skip(struct archive *a, void *client_data, size_t request) +#else +static off_t +file_skip(struct archive *a, void *client_data, off_t request) +#endif +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; + + (void)a; /* UNUSED */ + + /* + * If we can't skip, return 0 as the amount we did step and + * the caller will work around by reading and discarding. + */ + if (!mine->can_skip) + return (0); + if (request == 0) + return (0); + +#if HAVE_FSEEKO + if (fseeko(mine->f, request, SEEK_CUR) != 0) +#else + if (fseek(mine->f, request, SEEK_CUR) != 0) +#endif + { + mine->can_skip = 0; + return (0); + } + return (request); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; + + (void)a; /* UNUSED */ + if (mine->buffer != NULL) + free(mine->buffer); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_open_filename.c b/libarchive/archive_read_open_filename.c new file mode 100644 index 00000000..3d6376fb --- /dev/null +++ b/libarchive/archive_read_open_filename.c @@ -0,0 +1,267 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_filename.c,v 1.21 2008/02/19 06:10:48 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct read_file_data { + int fd; + size_t block_size; + void *buffer; + mode_t st_mode; /* Mode bits for opened file. */ + char can_skip; /* This file supports skipping. */ + char filename[1]; /* Must be last! */ +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_read(struct archive *, void *, const void **buff); +#if ARCHIVE_API_VERSION < 2 +static ssize_t file_skip(struct archive *, void *, size_t request); +#else +static off_t file_skip(struct archive *, void *, off_t request); +#endif + +int +archive_read_open_file(struct archive *a, const char *filename, + size_t block_size) +{ + return (archive_read_open_filename(a, filename, block_size)); +} + +int +archive_read_open_filename(struct archive *a, const char *filename, + size_t block_size) +{ + struct read_file_data *mine; + + if (filename == NULL || filename[0] == '\0') { + mine = (struct read_file_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->filename[0] = '\0'; + } else { + mine = (struct read_file_data *)malloc(sizeof(*mine) + strlen(filename)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + strcpy(mine->filename, filename); + } + mine->block_size = block_size; + mine->buffer = NULL; + mine->fd = -1; + /* lseek() almost never works; disable it by default. See below. */ + mine->can_skip = 0; + return (archive_read_open2(a, mine, file_open, file_read, file_skip, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + struct stat st; + + mine->buffer = malloc(mine->block_size); + if (mine->buffer == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + if (mine->filename[0] != '\0') + mine->fd = open(mine->filename, O_RDONLY | O_BINARY); + else + mine->fd = 0; /* Fake "open" for stdin. */ + if (mine->fd < 0) { + archive_set_error(a, errno, "Failed to open '%s'", + mine->filename); + return (ARCHIVE_FATAL); + } + if (fstat(mine->fd, &st) == 0) { + /* If we're reading a file from disk, ensure that we don't + overwrite it with an extracted file. */ + if (S_ISREG(st.st_mode)) { + archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); + /* + * Enabling skip here is a performance + * optimization for anything that supports + * lseek(). On FreeBSD, only regular files + * and raw disk devices support lseek() and + * there's no portable way to determine if a + * device is a raw disk device, so we only + * enable this optimization for regular files. + */ + mine->can_skip = 1; + } + /* Remember mode so close can decide whether to flush. */ + mine->st_mode = st.st_mode; + } else { + if (mine->filename[0] == '\0') + archive_set_error(a, errno, "Can't stat stdin"); + else + archive_set_error(a, errno, "Can't stat '%s'", + mine->filename); + return (ARCHIVE_FATAL); + } + return (0); +} + +static ssize_t +file_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + ssize_t bytes_read; + + *buff = mine->buffer; + bytes_read = read(mine->fd, mine->buffer, mine->block_size); + if (bytes_read < 0) { + if (mine->filename[0] == '\0') + archive_set_error(a, errno, "Error reading stdin"); + else + archive_set_error(a, errno, "Error reading '%s'", + mine->filename); + } + return (bytes_read); +} + +#if ARCHIVE_API_VERSION < 2 +static ssize_t +file_skip(struct archive *a, void *client_data, size_t request) +#else +static off_t +file_skip(struct archive *a, void *client_data, off_t request) +#endif +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + off_t old_offset, new_offset; + + if (!mine->can_skip) /* We can't skip, so ... */ + return (0); /* ... skip zero bytes. */ + + /* Reduce request to the next smallest multiple of block_size */ + request = (request / mine->block_size) * mine->block_size; + if (request == 0) + return (0); + + /* + * Hurray for lazy evaluation: if the first lseek fails, the second + * one will not be executed. + */ + if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) < 0) || + ((new_offset = lseek(mine->fd, request, SEEK_CUR)) < 0)) + { + /* If skip failed once, it will probably fail again. */ + mine->can_skip = 0; + + if (errno == ESPIPE) + { + /* + * Failure to lseek() can be caused by the file + * descriptor pointing to a pipe, socket or FIFO. + * Return 0 here, so the compression layer will use + * read()s instead to advance the file descriptor. + * It's slower of course, but works as well. + */ + return (0); + } + /* + * There's been an error other than ESPIPE. This is most + * likely caused by a programmer error (too large request) + * or a corrupted archive file. + */ + if (mine->filename[0] == '\0') + /* + * Should never get here, since lseek() on stdin ought + * to return an ESPIPE error. + */ + archive_set_error(a, errno, "Error seeking in stdin"); + else + archive_set_error(a, errno, "Error seeking in '%s'", + mine->filename); + return (-1); + } + return (new_offset - old_offset); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + + (void)a; /* UNUSED */ + + /* + * Sometimes, we should flush the input before closing. + * Regular files: faster to just close without flush. + * Devices: must not flush (user might need to + * read the "next" item on a non-rewind device). + * Pipes and sockets: must flush (otherwise, the + * program feeding the pipe or socket may complain). + * Here, I flush everything except for regular files and + * device nodes. + */ + if (!S_ISREG(mine->st_mode) + && !S_ISCHR(mine->st_mode) + && !S_ISBLK(mine->st_mode)) { + ssize_t bytesRead; + do { + bytesRead = read(mine->fd, mine->buffer, + mine->block_size); + } while (bytesRead > 0); + } + /* If a named file was opened, then it needs to be closed. */ + if (mine->filename[0] != '\0') + close(mine->fd); + if (mine->buffer != NULL) + free(mine->buffer); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_open_memory.c b/libarchive/archive_read_open_memory.c new file mode 100644 index 00000000..61f574fa --- /dev/null +++ b/libarchive/archive_read_open_memory.c @@ -0,0 +1,156 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_memory.c,v 1.6 2007/07/06 15:51:59 kientzle Exp $"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "archive.h" + +/* + * Glue to read an archive from a block of memory. + * + * This is mostly a huge help in building test harnesses; + * test programs can build archives in memory and read them + * back again without having to mess with files on disk. + */ + +struct read_memory_data { + unsigned char *buffer; + unsigned char *end; + ssize_t read_size; +}; + +static int memory_read_close(struct archive *, void *); +static int memory_read_open(struct archive *, void *); +#if ARCHIVE_API_VERSION < 2 +static ssize_t memory_read_skip(struct archive *, void *, size_t request); +#else +static off_t memory_read_skip(struct archive *, void *, off_t request); +#endif +static ssize_t memory_read(struct archive *, void *, const void **buff); + +int +archive_read_open_memory(struct archive *a, void *buff, size_t size) +{ + return archive_read_open_memory2(a, buff, size, size); +} + +/* + * Don't use _open_memory2() in production code; the archive_read_open_memory() + * version is the one you really want. This is just here so that + * test harnesses can exercise block operations inside the library. + */ +int +archive_read_open_memory2(struct archive *a, void *buff, + size_t size, size_t read_size) +{ + struct read_memory_data *mine; + + mine = (struct read_memory_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + memset(mine, 0, sizeof(*mine)); + mine->buffer = (unsigned char *)buff; + mine->end = mine->buffer + size; + mine->read_size = read_size; + return (archive_read_open2(a, mine, memory_read_open, + memory_read, memory_read_skip, memory_read_close)); +} + +/* + * There's nothing to open. + */ +static int +memory_read_open(struct archive *a, void *client_data) +{ + (void)a; /* UNUSED */ + (void)client_data; /* UNUSED */ + return (ARCHIVE_OK); +} + +/* + * This is scary simple: Just advance a pointer. Limiting + * to read_size is not technically necessary, but it exercises + * more of the internal logic when used with a small block size + * in a test harness. Production use should not specify a block + * size; then this is much faster. + */ +static ssize_t +memory_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + ssize_t size; + + (void)a; /* UNUSED */ + *buff = mine->buffer; + size = mine->end - mine->buffer; + if (size > mine->read_size) + size = mine->read_size; + mine->buffer += size; + return (size); +} + +/* + * Advancing is just as simple. Again, this is doing more than + * necessary in order to better exercise internal code when used + * as a test harness. + */ +#if ARCHIVE_API_VERSION < 2 +static ssize_t +memory_read_skip(struct archive *a, void *client_data, size_t skip) +#else +static off_t +memory_read_skip(struct archive *a, void *client_data, off_t skip) +#endif +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + + (void)a; /* UNUSED */ + if ((off_t)skip > (off_t)(mine->end - mine->buffer)) + skip = mine->end - mine->buffer; + /* Round down to block size. */ + skip /= mine->read_size; + skip *= mine->read_size; + mine->buffer += skip; + return (skip); +} + +/* + * Close is just cleaning up our one small bit of data. + */ +static int +memory_read_close(struct archive *a, void *client_data) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + (void)a; /* UNUSED */ + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_private.h b/libarchive/archive_read_private.h new file mode 100644 index 00000000..f4d0274b --- /dev/null +++ b/libarchive/archive_read_private.h @@ -0,0 +1,135 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_read_private.h,v 1.6 2008/03/15 11:09:16 kientzle Exp $ + */ + +#ifndef ARCHIVE_READ_PRIVATE_H_INCLUDED +#define ARCHIVE_READ_PRIVATE_H_INCLUDED + +#include "archive.h" +#include "archive_string.h" +#include "archive_private.h" + +struct archive_read { + struct archive archive; + + struct archive_entry *entry; + + /* Dev/ino of the archive being read/written. */ + dev_t skip_file_dev; + ino_t skip_file_ino; + + /* + * 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_skip_callback *client_skipper; + archive_close_callback *client_closer; + void *client_data; + + /* File offset of beginning of most recently-read header. */ + off_t header_position; + + /* + * Decompressors have a very specific lifecycle: + * public setup function initializes a slot in this table + * 'config' holds minimal configuration data + * bid() examines a block of data and returns a bid [1] + * init() is called for successful bidder + * 'data' is initialized by init() + * read() returns a pointer to the next block of data + * consume() indicates how much data is used + * skip() ignores bytes of data + * finish() cleans up and frees 'data' and 'config' + * + * [1] General guideline: bid the number of bits that you actually + * test, e.g., 16 if you test a 2-byte magic value. + */ + struct decompressor_t { + void *config; + void *data; + int (*bid)(const void *buff, size_t); + int (*init)(struct archive_read *, + const void *buff, size_t); + int (*finish)(struct archive_read *); + ssize_t (*read_ahead)(struct archive_read *, + const void **, size_t); + ssize_t (*consume)(struct archive_read *, size_t); + off_t (*skip)(struct archive_read *, off_t); + } decompressors[4]; + + /* Pointer to current decompressor. */ + struct decompressor_t *decompressor; + + /* + * Format detection is mostly the same as compression + * detection, with one significant difference: 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. + */ + + struct archive_format_descriptor { + void *data; + int (*bid)(struct archive_read *); + int (*read_header)(struct archive_read *, struct archive_entry *); + int (*read_data)(struct archive_read *, const void **, size_t *, off_t *); + int (*read_data_skip)(struct archive_read *); + int (*cleanup)(struct archive_read *); + } formats[8]; + struct archive_format_descriptor *format; /* Active format. */ + + /* + * Various information needed by archive_extract. + */ + struct extract *extract; + int (*cleanup_archive_extract)(struct archive_read *); +}; + +int __archive_read_register_format(struct archive_read *a, + void *format_data, + int (*bid)(struct archive_read *), + int (*read_header)(struct archive_read *, struct archive_entry *), + int (*read_data)(struct archive_read *, const void **, size_t *, off_t *), + int (*read_data_skip)(struct archive_read *), + int (*cleanup)(struct archive_read *)); + +struct decompressor_t + *__archive_read_register_compression(struct archive_read *a, + int (*bid)(const void *, size_t), + int (*init)(struct archive_read *, const void *, size_t)); + +const void + *__archive_read_ahead(struct archive_read *, size_t); + +#endif diff --git a/libarchive/archive_read_support_compression_all.c b/libarchive/archive_read_support_compression_all.c new file mode 100644 index 00000000..da2b246b --- /dev/null +++ b/libarchive/archive_read_support_compression_all.c @@ -0,0 +1,43 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_all.c,v 1.6 2007/01/09 08:05:55 kientzle Exp $"); + +#include "archive.h" + +int +archive_read_support_compression_all(struct archive *a) +{ +#if HAVE_BZLIB_H + archive_read_support_compression_bzip2(a); +#endif + /* The decompress code doesn't use an outside library. */ + archive_read_support_compression_compress(a); +#if HAVE_ZLIB_H + archive_read_support_compression_gzip(a); +#endif + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_compression_bzip2.c b/libarchive/archive_read_support_compression_bzip2.c new file mode 100644 index 00000000..372eff09 --- /dev/null +++ b/libarchive/archive_read_support_compression_bzip2.c @@ -0,0 +1,414 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_bzip2.c,v 1.17 2008/02/19 05:44:59 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#if HAVE_BZLIB_H +struct private_data { + bz_stream stream; + char *uncompressed_buffer; + size_t uncompressed_buffer_size; + char *read_next; + int64_t total_out; + char eof; /* True = found end of compressed data. */ +}; + +static int finish(struct archive_read *); +static ssize_t read_ahead(struct archive_read *, const void **, size_t); +static ssize_t read_consume(struct archive_read *, size_t); +static int drive_decompressor(struct archive_read *a, struct private_data *); +#endif + +/* These two functions are defined even if we lack the library. See below. */ +static int bid(const void *, size_t); +static int init(struct archive_read *, const void *, size_t); + +int +archive_read_support_compression_bzip2(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + if (__archive_read_register_compression(a, bid, init) != NULL) + return (ARCHIVE_OK); + return (ARCHIVE_FATAL); +} + +/* + * Test whether we can handle this data. + * + * This logic returns zero if any part of the signature fails. It + * also tries to Do The Right Thing if a very short buffer prevents us + * from verifying as much as we would like. + */ +static int +bid(const void *buff, size_t len) +{ + const unsigned char *buffer; + int bits_checked; + + if (len < 1) + return (0); + + buffer = (const unsigned char *)buff; + bits_checked = 0; + if (buffer[0] != 'B') /* Verify first ID byte. */ + return (0); + bits_checked += 8; + if (len < 2) + return (bits_checked); + + if (buffer[1] != 'Z') /* Verify second ID byte. */ + return (0); + bits_checked += 8; + if (len < 3) + return (bits_checked); + + if (buffer[2] != 'h') /* Verify third ID byte. */ + return (0); + bits_checked += 8; + if (len < 4) + return (bits_checked); + + if (buffer[3] < '1' || buffer[3] > '9') + return (0); + bits_checked += 5; + + /* + * Research Question: Can we do any more to verify that this + * really is BZip2 format?? For 99.9% of the time, the above + * test is sufficient, but it would be nice to do a more + * thorough check. It's especially troubling that the BZip2 + * signature begins with all ASCII characters; a tar archive + * whose first filename begins with 'BZh3' would potentially + * fool this logic. (It may also be possible to guard against + * such anomalies in archive_read_support_compression_none.) + */ + + return (bits_checked); +} + +#ifndef HAVE_BZLIB_H + +/* + * If we don't have the library on this system, we can't actually do the + * decompression. We can, however, still detect compressed archives + * and emit a useful message. + */ +static int +init(struct archive_read *a, const void *buff, size_t n) +{ + (void)a; /* UNUSED */ + (void)buff; /* UNUSED */ + (void)n; /* UNUSED */ + + archive_set_error(&a->archive, -1, + "This version of libarchive was compiled without bzip2 support"); + return (ARCHIVE_FATAL); +} + + +#else + +/* + * Setup the callbacks. + */ +static int +init(struct archive_read *a, const void *buff, size_t n) +{ + struct private_data *state; + int ret; + + a->archive.compression_code = ARCHIVE_COMPRESSION_BZIP2; + a->archive.compression_name = "bzip2"; + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for %s decompression", + a->archive.compression_name); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + state->uncompressed_buffer_size = 64 * 1024; + state->uncompressed_buffer = (char *)malloc(state->uncompressed_buffer_size); + state->stream.next_out = state->uncompressed_buffer; + state->read_next = state->uncompressed_buffer; + state->stream.avail_out = state->uncompressed_buffer_size; + + if (state->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate %s decompression buffers", + a->archive.compression_name); + free(state); + return (ARCHIVE_FATAL); + } + + /* + * A bug in bzlib.h: stream.next_in should be marked 'const' + * but isn't (the library never alters data through the + * next_in pointer, only reads it). The result: this ugly + * cast to remove 'const'. + */ + state->stream.next_in = (char *)(uintptr_t)(const void *)buff; + state->stream.avail_in = n; + + a->decompressor->read_ahead = read_ahead; + a->decompressor->consume = read_consume; + a->decompressor->skip = NULL; /* not supported */ + a->decompressor->finish = finish; + + /* Initialize compression library. */ + ret = BZ2_bzDecompressInit(&(state->stream), + 0 /* library verbosity */, + 0 /* don't use slow low-mem algorithm */); + + /* If init fails, try using low-memory algorithm instead. */ + if (ret == BZ_MEM_ERROR) { + ret = BZ2_bzDecompressInit(&(state->stream), + 0 /* library verbosity */, + 1 /* do use slow low-mem algorithm */); + } + + if (ret == BZ_OK) { + a->decompressor->data = state; + return (ARCHIVE_OK); + } + + /* Library setup failed: Clean up. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing %s library", + a->archive.compression_name); + free(state->uncompressed_buffer); + free(state); + + /* Override the error message if we know what really went wrong. */ + switch (ret) { + case BZ_PARAM_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid setup parameter"); + break; + case BZ_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Internal error initializing compression library: " + "out of memory"); + break; + case BZ_CONFIG_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "mis-compiled library"); + break; + } + + return (ARCHIVE_FATAL); +} + +/* + * Return a block of data from the decompression buffer. Decompress more + * as necessary. + */ +static ssize_t +read_ahead(struct archive_read *a, const void **p, size_t min) +{ + struct private_data *state; + size_t read_avail, was_avail; + int ret; + + state = (struct private_data *)a->decompressor->data; + if (!a->client_reader) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No read callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + read_avail = state->stream.next_out - state->read_next; + + if (read_avail + state->stream.avail_out < min) { + memmove(state->uncompressed_buffer, state->read_next, + read_avail); + state->read_next = state->uncompressed_buffer; + state->stream.next_out = state->read_next + read_avail; + state->stream.avail_out + = state->uncompressed_buffer_size - read_avail; + } + + while (read_avail < min && /* Haven't satisfied min. */ + read_avail < state->uncompressed_buffer_size) { /* !full */ + was_avail = read_avail; + if ((ret = drive_decompressor(a, state)) < ARCHIVE_OK) + return (ret); + if (ret == ARCHIVE_EOF) + break; /* Break on EOF even if we haven't met min. */ + read_avail = state->stream.next_out - state->read_next; + if (was_avail == read_avail) /* No progress? */ + break; + } + + *p = state->read_next; + return (read_avail); +} + +/* + * Mark a previously-returned block of data as read. + */ +static ssize_t +read_consume(struct archive_read *a, size_t n) +{ + struct private_data *state; + + state = (struct private_data *)a->decompressor->data; + a->archive.file_position += n; + state->read_next += n; + if (state->read_next > state->stream.next_out) + __archive_errx(1, "Request to consume too many " + "bytes from bzip2 decompressor"); + return (n); +} + +/* + * Clean up the decompressor. + */ +static int +finish(struct archive_read *a) +{ + struct private_data *state; + int ret; + + state = (struct private_data *)a->decompressor->data; + ret = ARCHIVE_OK; + switch (BZ2_bzDecompressEnd(&(state->stream))) { + case BZ_OK: + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up %s compressor", + a->archive.compression_name); + ret = ARCHIVE_FATAL; + } + + free(state->uncompressed_buffer); + free(state); + + a->decompressor->data = NULL; + return (ret); +} + +/* + * Utility function to pull data through decompressor, reading input + * blocks as necessary. + */ +static int +drive_decompressor(struct archive_read *a, struct private_data *state) +{ + ssize_t ret; + int decompressed, total_decompressed; + char *output; + const void *read_buf; + + if (state->eof) + return (ARCHIVE_EOF); + total_decompressed = 0; + for (;;) { + if (state->stream.avail_in == 0) { + read_buf = state->stream.next_in; + ret = (a->client_reader)(&a->archive, a->client_data, + &read_buf); + state->stream.next_in = (void *)(uintptr_t)read_buf; + if (ret < 0) { + /* + * TODO: Find a better way to handle + * this read failure. + */ + goto fatal; + } + if (ret == 0 && total_decompressed == 0) { + archive_set_error(&a->archive, EIO, + "Premature end of %s compressed data", + a->archive.compression_name); + return (ARCHIVE_FATAL); + } + a->archive.raw_position += ret; + state->stream.avail_in = ret; + } + + { + output = state->stream.next_out; + + /* Decompress some data. */ + ret = BZ2_bzDecompress(&(state->stream)); + decompressed = state->stream.next_out - output; + + /* Accumulate the total bytes of output. */ + state->total_out += decompressed; + total_decompressed += decompressed; + + switch (ret) { + case BZ_OK: /* Decompressor made some progress. */ + if (decompressed > 0) + return (ARCHIVE_OK); + break; + case BZ_STREAM_END: /* Found end of stream. */ + state->eof = 1; + return (ARCHIVE_OK); + default: + /* Any other return value is an error. */ + goto fatal; + } + } + } + return (ARCHIVE_OK); + + /* Return a fatal error. */ +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s decompression failed", a->archive.compression_name); + return (ARCHIVE_FATAL); +} + +#endif /* HAVE_BZLIB_H */ diff --git a/libarchive/archive_read_support_compression_compress.c b/libarchive/archive_read_support_compression_compress.c new file mode 100644 index 00000000..050099b2 --- /dev/null +++ b/libarchive/archive_read_support_compression_compress.c @@ -0,0 +1,493 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code borrows heavily from "compress" source code, which is + * protected by the following copyright. (Clause 3 dropped by request + * of the Regents.) + */ + +/*- + * Copyright (c) 1985, 1986, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis and James A. Woods, derived from original + * work by Spencer Thomas and Joseph Orost. + * + * 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. + */ + + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_compress.c,v 1.10 2007/05/29 01:00:19 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +/* + * Because LZW decompression is pretty simple, I've just implemented + * the whole decompressor here (cribbing from "compress" source code, + * of course), rather than relying on an external library. I have + * made an effort to clarify and simplify the algorithm, so the + * names and structure here don't exactly match those used by compress. + */ + +struct private_data { + /* Input variables. */ + const unsigned char *next_in; + size_t avail_in; + int bit_buffer; + int bits_avail; + size_t bytes_in_section; + + /* Output variables. */ + size_t uncompressed_buffer_size; + void *uncompressed_buffer; + unsigned char *read_next; /* Data for client. */ + unsigned char *next_out; /* Where to write new data. */ + size_t avail_out; /* Space at end of buffer. */ + + /* Decompression status variables. */ + int use_reset_code; + int end_of_stream; /* EOF status. */ + int maxcode; /* Largest code. */ + int maxcode_bits; /* Length of largest code. */ + int section_end_code; /* When to increase bits. */ + int bits; /* Current code length. */ + int oldcode; /* Previous code. */ + int finbyte; /* Last byte of prev code. */ + + /* Dictionary. */ + int free_ent; /* Next dictionary entry. */ + unsigned char suffix[65536]; + uint16_t prefix[65536]; + + /* + * Scratch area for expanding dictionary entries. Note: + * "worst" case here comes from compressing /dev/zero: the + * last code in the dictionary will code a sequence of + * 65536-256 zero bytes. Thus, we need stack space to expand + * a 65280-byte dictionary entry. (Of course, 32640:1 + * compression could also be considered the "best" case. ;-) + */ + unsigned char *stackp; + unsigned char stack[65300]; +}; + +static int bid(const void *, size_t); +static int finish(struct archive_read *); +static int init(struct archive_read *, const void *, size_t); +static ssize_t read_ahead(struct archive_read *, const void **, size_t); +static ssize_t read_consume(struct archive_read *, size_t); +static int getbits(struct archive_read *, struct private_data *, int n); +static int next_code(struct archive_read *a, struct private_data *state); + +int +archive_read_support_compression_compress(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + if (__archive_read_register_compression(a, bid, init) != NULL) + return (ARCHIVE_OK); + return (ARCHIVE_FATAL); +} + +/* + * Test whether we can handle this data. + * + * This logic returns zero if any part of the signature fails. It + * also tries to Do The Right Thing if a very short buffer prevents us + * from verifying as much as we would like. + */ +static int +bid(const void *buff, size_t len) +{ + const unsigned char *buffer; + int bits_checked; + + if (len < 1) + return (0); + + buffer = (const unsigned char *)buff; + bits_checked = 0; + if (buffer[0] != 037) /* Verify first ID byte. */ + return (0); + bits_checked += 8; + if (len < 2) + return (bits_checked); + + if (buffer[1] != 0235) /* Verify second ID byte. */ + return (0); + bits_checked += 8; + if (len < 3) + return (bits_checked); + + /* + * TODO: Verify more. + */ + + return (bits_checked); +} + +/* + * Setup the callbacks. + */ +static int +init(struct archive_read *a, const void *buff, size_t n) +{ + struct private_data *state; + int code; + + a->archive.compression_code = ARCHIVE_COMPRESSION_COMPRESS; + a->archive.compression_name = "compress (.Z)"; + + a->decompressor->read_ahead = read_ahead; + a->decompressor->consume = read_consume; + a->decompressor->skip = NULL; /* not supported */ + a->decompressor->finish = finish; + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for %s decompression", + a->archive.compression_name); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + a->decompressor->data = state; + + state->uncompressed_buffer_size = 64 * 1024; + state->uncompressed_buffer = malloc(state->uncompressed_buffer_size); + + if (state->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate %s decompression buffers", + a->archive.compression_name); + goto fatal; + } + + state->next_in = (const unsigned char *)buff; + state->avail_in = n; + state->read_next = state->next_out = (unsigned char *)state->uncompressed_buffer; + state->avail_out = state->uncompressed_buffer_size; + + code = getbits(a, state, 8); + if (code != 037) /* This should be impossible. */ + goto fatal; + + code = getbits(a, state, 8); + if (code != 0235) { + /* This can happen if the library is receiving 1-byte + * blocks and gzip and compress are both enabled. + * You can't distinguish gzip and compress only from + * the first byte. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Compress signature did not match."); + goto fatal; + } + + code = getbits(a, state, 8); + state->maxcode_bits = code & 0x1f; + state->maxcode = (1 << state->maxcode_bits); + state->use_reset_code = code & 0x80; + + /* Initialize decompressor. */ + state->free_ent = 256; + state->stackp = state->stack; + if (state->use_reset_code) + state->free_ent++; + state->bits = 9; + state->section_end_code = (1<<state->bits) - 1; + state->oldcode = -1; + for (code = 255; code >= 0; code--) { + state->prefix[code] = 0; + state->suffix[code] = code; + } + next_code(a, state); + return (ARCHIVE_OK); + +fatal: + finish(a); + return (ARCHIVE_FATAL); +} + +/* + * Return a block of data from the decompression buffer. Decompress more + * as necessary. + */ +static ssize_t +read_ahead(struct archive_read *a, const void **p, size_t min) +{ + struct private_data *state; + size_t read_avail; + int ret; + + state = (struct private_data *)a->decompressor->data; + if (!a->client_reader) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No read callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + read_avail = state->next_out - state->read_next; + + if (read_avail < min && state->end_of_stream) { + if (state->end_of_stream == ARCHIVE_EOF) + return (0); + else + return (-1); + } + + if (read_avail < min) { + memmove(state->uncompressed_buffer, state->read_next, + read_avail); + state->read_next = (unsigned char *)state->uncompressed_buffer; + state->next_out = state->read_next + read_avail; + state->avail_out + = state->uncompressed_buffer_size - read_avail; + + while (read_avail < state->uncompressed_buffer_size + && !state->end_of_stream) { + if (state->stackp > state->stack) { + *state->next_out++ = *--state->stackp; + state->avail_out--; + read_avail++; + } else { + ret = next_code(a, state); + if (ret == ARCHIVE_EOF) + state->end_of_stream = ret; + else if (ret != ARCHIVE_OK) + return (ret); + } + } + } + + *p = state->read_next; + return (read_avail); +} + +/* + * Mark a previously-returned block of data as read. + */ +static ssize_t +read_consume(struct archive_read *a, size_t n) +{ + struct private_data *state; + + state = (struct private_data *)a->decompressor->data; + a->archive.file_position += n; + state->read_next += n; + if (state->read_next > state->next_out) + __archive_errx(1, "Request to consume too many " + "bytes from compress decompressor"); + return (n); +} + +/* + * Clean up the decompressor. + */ +static int +finish(struct archive_read *a) +{ + struct private_data *state; + int ret = ARCHIVE_OK; + + state = (struct private_data *)a->decompressor->data; + + if (state != NULL) { + if (state->uncompressed_buffer != NULL) + free(state->uncompressed_buffer); + free(state); + } + + a->decompressor->data = NULL; + return (ret); +} + +/* + * Process the next code and fill the stack with the expansion + * of the code. Returns ARCHIVE_FATAL if there is a fatal I/O or + * format error, ARCHIVE_EOF if we hit end of data, ARCHIVE_OK otherwise. + */ +static int +next_code(struct archive_read *a, struct private_data *state) +{ + int code, newcode; + + static int debug_buff[1024]; + static unsigned debug_index; + + code = newcode = getbits(a, state, state->bits); + if (code < 0) + return (code); + + debug_buff[debug_index++] = code; + if (debug_index >= sizeof(debug_buff)/sizeof(debug_buff[0])) + debug_index = 0; + + /* If it's a reset code, reset the dictionary. */ + if ((code == 256) && state->use_reset_code) { + /* + * The original 'compress' implementation blocked its + * I/O in a manner that resulted in junk bytes being + * inserted after every reset. The next section skips + * this junk. (Yes, the number of *bytes* to skip is + * a function of the current *bit* length.) + */ + int skip_bytes = state->bits - + (state->bytes_in_section % state->bits); + skip_bytes %= state->bits; + state->bits_avail = 0; /* Discard rest of this byte. */ + while (skip_bytes-- > 0) { + code = getbits(a, state, 8); + if (code < 0) + return (code); + } + /* Now, actually do the reset. */ + state->bytes_in_section = 0; + state->bits = 9; + state->section_end_code = (1 << state->bits) - 1; + state->free_ent = 257; + state->oldcode = -1; + return (next_code(a, state)); + } + + if (code > state->free_ent) { + /* An invalid code is a fatal error. */ + archive_set_error(&a->archive, -1, "Invalid compressed data"); + return (ARCHIVE_FATAL); + } + + /* Special case for KwKwK string. */ + if (code >= state->free_ent) { + *state->stackp++ = state->finbyte; + code = state->oldcode; + } + + /* Generate output characters in reverse order. */ + while (code >= 256) { + *state->stackp++ = state->suffix[code]; + code = state->prefix[code]; + } + *state->stackp++ = state->finbyte = code; + + /* Generate the new entry. */ + code = state->free_ent; + if (code < state->maxcode && state->oldcode >= 0) { + state->prefix[code] = state->oldcode; + state->suffix[code] = state->finbyte; + ++state->free_ent; + } + if (state->free_ent > state->section_end_code) { + state->bits++; + state->bytes_in_section = 0; + if (state->bits == state->maxcode_bits) + state->section_end_code = state->maxcode; + else + state->section_end_code = (1 << state->bits) - 1; + } + + /* Remember previous code. */ + state->oldcode = newcode; + return (ARCHIVE_OK); +} + +/* + * Return next 'n' bits from stream. + * + * -1 indicates end of available data. + */ +static int +getbits(struct archive_read *a, struct private_data *state, int n) +{ + int code, ret; + static const int mask[] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff, + 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff + }; + const void *read_buf; + + while (state->bits_avail < n) { + if (state->avail_in <= 0) { + read_buf = state->next_in; + ret = (a->client_reader)(&a->archive, a->client_data, + &read_buf); + state->next_in = read_buf; + if (ret < 0) + return (ARCHIVE_FATAL); + if (ret == 0) + return (ARCHIVE_EOF); + a->archive.raw_position += ret; + state->avail_in = ret; + } + state->bit_buffer |= *state->next_in++ << state->bits_avail; + state->avail_in--; + state->bits_avail += 8; + state->bytes_in_section++; + } + + code = state->bit_buffer; + state->bit_buffer >>= n; + state->bits_avail -= n; + + return (code & mask[n]); +} diff --git a/libarchive/archive_read_support_compression_gzip.c b/libarchive/archive_read_support_compression_gzip.c new file mode 100644 index 00000000..2dac54da --- /dev/null +++ b/libarchive/archive_read_support_compression_gzip.c @@ -0,0 +1,549 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_gzip.c,v 1.16 2008/02/19 05:44:59 kientzle Exp $"); + + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#ifdef HAVE_ZLIB_H +struct private_data { + z_stream stream; + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; + unsigned char *read_next; + int64_t total_out; + unsigned long crc; + char header_done; + char eof; /* True = found end of compressed data. */ +}; + +static int finish(struct archive_read *); +static ssize_t read_ahead(struct archive_read *, const void **, size_t); +static ssize_t read_consume(struct archive_read *, size_t); +static int drive_decompressor(struct archive_read *a, struct private_data *); +#endif + +/* These two functions are defined even if we lack the library. See below. */ +static int bid(const void *, size_t); +static int init(struct archive_read *, const void *, size_t); + +int +archive_read_support_compression_gzip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + if (__archive_read_register_compression(a, bid, init) != NULL) + return (ARCHIVE_OK); + return (ARCHIVE_FATAL); +} + +/* + * Test whether we can handle this data. + * + * This logic returns zero if any part of the signature fails. It + * also tries to Do The Right Thing if a very short buffer prevents us + * from verifying as much as we would like. + */ +static int +bid(const void *buff, size_t len) +{ + const unsigned char *buffer; + int bits_checked; + + if (len < 1) + return (0); + + buffer = (const unsigned char *)buff; + bits_checked = 0; + if (buffer[0] != 037) /* Verify first ID byte. */ + return (0); + bits_checked += 8; + if (len < 2) + return (bits_checked); + + if (buffer[1] != 0213) /* Verify second ID byte. */ + return (0); + bits_checked += 8; + if (len < 3) + return (bits_checked); + + if (buffer[2] != 8) /* Compression must be 'deflate'. */ + return (0); + bits_checked += 8; + if (len < 4) + return (bits_checked); + + if ((buffer[3] & 0xE0)!= 0) /* No reserved flags set. */ + return (0); + bits_checked += 3; + if (len < 5) + return (bits_checked); + + /* + * TODO: Verify more; in particular, gzip has an optional + * header CRC, which would give us 16 more verified bits. We + * may also be able to verify certain constraints on other + * fields. + */ + + return (bits_checked); +} + + +#ifndef HAVE_ZLIB_H + +/* + * If we don't have the library on this system, we can't actually do the + * decompression. We can, however, still detect compressed archives + * and emit a useful message. + */ +static int +init(struct archive_read *a, const void *buff, size_t n) +{ + (void)a; /* UNUSED */ + (void)buff; /* UNUSED */ + (void)n; /* UNUSED */ + + archive_set_error(&a->archive, -1, + "This version of libarchive was compiled without gzip support"); + return (ARCHIVE_FATAL); +} + + +#else + +/* + * Setup the callbacks. + */ +static int +init(struct archive_read *a, const void *buff, size_t n) +{ + struct private_data *state; + int ret; + + a->archive.compression_code = ARCHIVE_COMPRESSION_GZIP; + a->archive.compression_name = "gzip"; + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for %s decompression", + a->archive.compression_name); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + state->crc = crc32(0L, NULL, 0); + state->header_done = 0; /* We've not yet begun to parse header... */ + + state->uncompressed_buffer_size = 64 * 1024; + state->uncompressed_buffer = (unsigned char *)malloc(state->uncompressed_buffer_size); + state->stream.next_out = state->uncompressed_buffer; + state->read_next = state->uncompressed_buffer; + state->stream.avail_out = state->uncompressed_buffer_size; + + if (state->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate %s decompression buffers", + a->archive.compression_name); + free(state); + return (ARCHIVE_FATAL); + } + + /* + * A bug in zlib.h: stream.next_in should be marked 'const' + * but isn't (the library never alters data through the + * next_in pointer, only reads it). The result: this ugly + * cast to remove 'const'. + */ + state->stream.next_in = (Bytef *)(uintptr_t)(const void *)buff; + state->stream.avail_in = n; + + a->decompressor->read_ahead = read_ahead; + a->decompressor->consume = read_consume; + a->decompressor->skip = NULL; /* not supported */ + a->decompressor->finish = finish; + + /* + * TODO: Do I need to parse the gzip header before calling + * inflateInit2()? In particular, one of the header bytes + * marks "best compression" or "fastest", which may be + * appropriate for setting the second parameter here. + * However, I think the only penalty for not setting it + * correctly is wasted memory. If this is necessary, it + * should probably go into drive_decompressor() below. + */ + + /* Initialize compression library. */ + ret = inflateInit2(&(state->stream), + -15 /* Don't check for zlib header */); + if (ret == Z_OK) { + a->decompressor->data = state; + return (ARCHIVE_OK); + } + + /* Library setup failed: Clean up. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing %s library", + a->archive.compression_name); + free(state->uncompressed_buffer); + free(state); + + /* Override the error message if we know what really went wrong. */ + switch (ret) { + case Z_STREAM_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid setup parameter"); + break; + case Z_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Internal error initializing compression library: " + "out of memory"); + break; + case Z_VERSION_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid library version"); + break; + } + + return (ARCHIVE_FATAL); +} + +/* + * Return a block of data from the decompression buffer. Decompress more + * as necessary. + */ +static ssize_t +read_ahead(struct archive_read *a, const void **p, size_t min) +{ + struct private_data *state; + size_t read_avail, was_avail; + int ret; + + state = (struct private_data *)a->decompressor->data; + if (!a->client_reader) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No read callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + read_avail = state->stream.next_out - state->read_next; + + if (read_avail + state->stream.avail_out < min) { + memmove(state->uncompressed_buffer, state->read_next, + read_avail); + state->read_next = state->uncompressed_buffer; + state->stream.next_out = state->read_next + read_avail; + state->stream.avail_out + = state->uncompressed_buffer_size - read_avail; + } + + while (read_avail < min && /* Haven't satisfied min. */ + read_avail < state->uncompressed_buffer_size) { /* !full */ + was_avail = read_avail; + if ((ret = drive_decompressor(a, state)) < ARCHIVE_OK) + return (ret); + if (ret == ARCHIVE_EOF) + break; /* Break on EOF even if we haven't met min. */ + read_avail = state->stream.next_out - state->read_next; + if (was_avail == read_avail) /* No progress? */ + break; + } + + *p = state->read_next; + return (read_avail); +} + +/* + * Mark a previously-returned block of data as read. + */ +static ssize_t +read_consume(struct archive_read *a, size_t n) +{ + struct private_data *state; + + state = (struct private_data *)a->decompressor->data; + a->archive.file_position += n; + state->read_next += n; + if (state->read_next > state->stream.next_out) + __archive_errx(1, "Request to consume too many " + "bytes from gzip decompressor"); + return (n); +} + +/* + * Clean up the decompressor. + */ +static int +finish(struct archive_read *a) +{ + struct private_data *state; + int ret; + + state = (struct private_data *)a->decompressor->data; + ret = ARCHIVE_OK; + switch (inflateEnd(&(state->stream))) { + case Z_OK: + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up %s compressor", + a->archive.compression_name); + ret = ARCHIVE_FATAL; + } + + free(state->uncompressed_buffer); + free(state); + + a->decompressor->data = NULL; + return (ret); +} + +/* + * Utility function to pull data through decompressor, reading input + * blocks as necessary. + */ +static int +drive_decompressor(struct archive_read *a, struct private_data *state) +{ + ssize_t ret; + size_t decompressed, total_decompressed; + int count, flags, header_state; + unsigned char *output; + unsigned char b; + const void *read_buf; + + if (state->eof) + return (ARCHIVE_EOF); + flags = 0; + count = 0; + header_state = 0; + total_decompressed = 0; + for (;;) { + if (state->stream.avail_in == 0) { + read_buf = state->stream.next_in; + ret = (a->client_reader)(&a->archive, a->client_data, + &read_buf); + state->stream.next_in = (unsigned char *)(uintptr_t)read_buf; + if (ret < 0) { + /* + * TODO: Find a better way to handle + * this read failure. + */ + goto fatal; + } + if (ret == 0 && total_decompressed == 0) { + archive_set_error(&a->archive, EIO, + "Premature end of %s compressed data", + a->archive.compression_name); + return (ARCHIVE_FATAL); + } + a->archive.raw_position += ret; + state->stream.avail_in = ret; + } + + if (!state->header_done) { + /* + * If still parsing the header, interpret the + * next byte. + */ + b = *(state->stream.next_in++); + state->stream.avail_in--; + + /* + * Yes, this is somewhat crude, but it works, + * GZip format isn't likely to change anytime + * in the near future, and header parsing is + * certainly not a performance issue, so + * there's little point in making this more + * elegant. Of course, if you see an easy way + * to make this more elegant, please let me + * know.. ;-) + */ + switch (header_state) { + case 0: /* First byte of signature. */ + if (b != 037) + goto fatal; + header_state = 1; + break; + case 1: /* Second byte of signature. */ + if (b != 0213) + goto fatal; + header_state = 2; + break; + case 2: /* Compression type must be 8. */ + if (b != 8) + goto fatal; + header_state = 3; + break; + case 3: /* GZip flags. */ + flags = b; + header_state = 4; + break; + case 4: case 5: case 6: case 7: /* Mod time. */ + header_state++; + break; + case 8: /* Deflate flags. */ + header_state = 9; + break; + case 9: /* OS. */ + header_state = 10; + break; + case 10: /* Optional Extra: First byte of Length. */ + if ((flags & 4)) { + count = 255 & (int)b; + header_state = 11; + break; + } + /* + * Fall through if there is no + * Optional Extra field. + */ + case 11: /* Optional Extra: Second byte of Length. */ + if ((flags & 4)) { + count = (0xff00 & ((int)b << 8)) | count; + header_state = 12; + break; + } + /* + * Fall through if there is no + * Optional Extra field. + */ + case 12: /* Optional Extra Field: counted length. */ + if ((flags & 4)) { + --count; + if (count == 0) header_state = 13; + else header_state = 12; + break; + } + /* + * Fall through if there is no + * Optional Extra field. + */ + case 13: /* Optional Original Filename. */ + if ((flags & 8)) { + if (b == 0) header_state = 14; + else header_state = 13; + break; + } + /* + * Fall through if no Optional + * Original Filename. + */ + case 14: /* Optional Comment. */ + if ((flags & 16)) { + if (b == 0) header_state = 15; + else header_state = 14; + break; + } + /* Fall through if no Optional Comment. */ + case 15: /* Optional Header CRC: First byte. */ + if ((flags & 2)) { + header_state = 16; + break; + } + /* Fall through if no Optional Header CRC. */ + case 16: /* Optional Header CRC: Second byte. */ + if ((flags & 2)) { + header_state = 17; + break; + } + /* Fall through if no Optional Header CRC. */ + case 17: /* First byte of compressed data. */ + state->header_done = 1; /* done with header */ + state->stream.avail_in++; + state->stream.next_in--; + } + + /* + * TODO: Consider moving the inflateInit2 call + * here so it can include the compression type + * from the header? + */ + } else { + output = state->stream.next_out; + + /* Decompress some data. */ + ret = inflate(&(state->stream), 0); + decompressed = state->stream.next_out - output; + + /* Accumulate the CRC of the uncompressed data. */ + state->crc = crc32(state->crc, output, decompressed); + + /* Accumulate the total bytes of output. */ + state->total_out += decompressed; + total_decompressed += decompressed; + + switch (ret) { + case Z_OK: /* Decompressor made some progress. */ + if (decompressed > 0) + return (ARCHIVE_OK); + break; + case Z_STREAM_END: /* Found end of stream. */ + /* + * TODO: Verify gzip trailer + * (uncompressed length and CRC). + */ + state->eof = 1; + return (ARCHIVE_OK); + default: + /* Any other return value is an error. */ + goto fatal; + } + } + } + return (ARCHIVE_OK); + + /* Return a fatal error. */ +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s decompression failed", a->archive.compression_name); + return (ARCHIVE_FATAL); +} + +#endif /* HAVE_ZLIB_H */ diff --git a/libarchive/archive_read_support_compression_none.c b/libarchive/archive_read_support_compression_none.c new file mode 100644 index 00000000..3f17756a --- /dev/null +++ b/libarchive/archive_read_support_compression_none.c @@ -0,0 +1,370 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_none.c,v 1.19 2007/12/30 04:58:21 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct archive_decompress_none { + char *buffer; + size_t buffer_size; + char *next; /* Current read location. */ + size_t avail; /* Bytes in my buffer. */ + const void *client_buff; /* Client buffer information. */ + size_t client_total; + const char *client_next; + size_t client_avail; + char end_of_file; + char fatal; +}; + +/* + * Size of internal buffer used for combining short reads. This is + * also an upper limit on the size of a read request. Recall, + * however, that we can (and will!) return blocks of data larger than + * this. The read semantics are: you ask for a minimum, I give you a + * pointer to my best-effort match and tell you how much data is + * there. It could be less than you asked for, it could be much more. + * For example, a client might use mmap() to "read" the entire file as + * a single block. In that case, I will return that entire block to + * my clients. + */ +#define BUFFER_SIZE 65536 + +#define minimum(a, b) (a < b ? a : b) + +static int archive_decompressor_none_bid(const void *, size_t); +static int archive_decompressor_none_finish(struct archive_read *); +static int archive_decompressor_none_init(struct archive_read *, + const void *, size_t); +static ssize_t archive_decompressor_none_read_ahead(struct archive_read *, + const void **, size_t); +static ssize_t archive_decompressor_none_read_consume(struct archive_read *, + size_t); +static off_t archive_decompressor_none_skip(struct archive_read *, off_t); + +int +archive_read_support_compression_none(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + if (__archive_read_register_compression(a, + archive_decompressor_none_bid, + archive_decompressor_none_init) != NULL) + return (ARCHIVE_OK); + return (ARCHIVE_FATAL); +} + +/* + * Try to detect an "uncompressed" archive. + */ +static int +archive_decompressor_none_bid(const void *buff, size_t len) +{ + (void)buff; + (void)len; + + return (1); /* Default: We'll take it if noone else does. */ +} + +static int +archive_decompressor_none_init(struct archive_read *a, const void *buff, size_t n) +{ + struct archive_decompress_none *state; + + a->archive.compression_code = ARCHIVE_COMPRESSION_NONE; + a->archive.compression_name = "none"; + + state = (struct archive_decompress_none *)malloc(sizeof(*state)); + if (!state) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate input data"); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + state->buffer_size = BUFFER_SIZE; + state->buffer = (char *)malloc(state->buffer_size); + state->next = state->buffer; + if (state->buffer == NULL) { + free(state); + archive_set_error(&a->archive, ENOMEM, "Can't allocate input buffer"); + return (ARCHIVE_FATAL); + } + + /* Save reference to first block of data. */ + state->client_buff = buff; + state->client_total = n; + state->client_next = state->client_buff; + state->client_avail = state->client_total; + + a->decompressor->data = state; + a->decompressor->read_ahead = archive_decompressor_none_read_ahead; + a->decompressor->consume = archive_decompressor_none_read_consume; + a->decompressor->skip = archive_decompressor_none_skip; + a->decompressor->finish = archive_decompressor_none_finish; + + return (ARCHIVE_OK); +} + +/* + * We just pass through pointers to the client buffer if we can. + * If the client buffer is short, then we copy stuff to our internal + * buffer to combine reads. + */ +static ssize_t +archive_decompressor_none_read_ahead(struct archive_read *a, const void **buff, + size_t min) +{ + struct archive_decompress_none *state; + ssize_t bytes_read; + + state = (struct archive_decompress_none *)a->decompressor->data; + if (state->fatal) + return (-1); + + /* + * Don't make special efforts to handle requests larger than + * the copy buffer. + */ + if (min > state->buffer_size) + min = state->buffer_size; + + /* + * Keep pulling more data until we can satisfy the request. + */ + for (;;) { + + /* + * If we can satisfy from the copy buffer, we're done. + */ + if (state->avail >= min) { + *buff = state->next; + return (state->avail); + } + + /* + * We can satisfy directly from client buffer if everything + * currently in the copy buffer is still in the client buffer. + */ + if (state->client_total >= state->client_avail + state->avail + && state->client_avail + state->avail >= min) { + /* "Roll back" to client buffer. */ + state->client_avail += state->avail; + state->client_next -= state->avail; + /* Copy buffer is now empty. */ + state->avail = 0; + state->next = state->buffer; + /* Return data from client buffer. */ + *buff = state->client_next; + return (state->client_avail); + } + + /* Move data forward in copy buffer if necessary. */ + if (state->next > state->buffer && + state->next + min > state->buffer + state->buffer_size) { + if (state->avail > 0) + memmove(state->buffer, state->next, state->avail); + state->next = state->buffer; + } + + /* If we've used up the client data, get more. */ + if (state->client_avail <= 0) { + bytes_read = (a->client_reader)(&a->archive, + a->client_data, &state->client_buff); + if (bytes_read < 0) { /* Read error. */ + state->client_total = state->client_avail = 0; + state->client_next = state->client_buff = NULL; + state->fatal = 1; + return (-1); + } + if (bytes_read == 0) { /* End-of-file. */ + state->client_total = state->client_avail = 0; + state->client_next = state->client_buff = NULL; + state->end_of_file = 1; + /* Return whatever we do have. */ + *buff = state->next; + return (state->avail); + } + a->archive.raw_position += bytes_read; + state->client_total = bytes_read; + state->client_avail = state->client_total; + state->client_next = state->client_buff; + } + else + { + /* We can add client data to copy buffer. */ + /* First estimate: copy to fill rest of buffer. */ + size_t tocopy = (state->buffer + state->buffer_size) + - (state->next + state->avail); + /* Don't copy more than is available. */ + if (tocopy > state->client_avail) + tocopy = state->client_avail; + memcpy(state->next + state->avail, state->client_next, + tocopy); + /* Remove this data from client buffer. */ + state->client_next += tocopy; + state->client_avail -= tocopy; + /* add it to copy buffer. */ + state->avail += tocopy; + } + } +} + +/* + * Mark the appropriate data as used. Note that the request here will + * often be much smaller than the size of the previous read_ahead + * request. + */ +static ssize_t +archive_decompressor_none_read_consume(struct archive_read *a, size_t request) +{ + struct archive_decompress_none *state; + + state = (struct archive_decompress_none *)a->decompressor->data; + if (state->avail > 0) { + /* Read came from copy buffer. */ + state->next += request; + state->avail -= request; + } else { + /* Read came from client buffer. */ + state->client_next += request; + state->client_avail -= request; + } + a->archive.file_position += request; + return (request); +} + +/* + * Skip forward by exactly the requested bytes or else return + * ARCHIVE_FATAL. Note that this differs from the contract for + * read_ahead, which does not guarantee a minimum count. + */ +static off_t +archive_decompressor_none_skip(struct archive_read *a, off_t request) +{ + struct archive_decompress_none *state; + off_t bytes_skipped, total_bytes_skipped = 0; + size_t min; + + state = (struct archive_decompress_none *)a->decompressor->data; + if (state->fatal) + return (-1); + /* + * If there is data in the buffers already, use that first. + */ + if (state->avail > 0) { + min = minimum(request, (off_t)state->avail); + bytes_skipped = archive_decompressor_none_read_consume(a, min); + request -= bytes_skipped; + total_bytes_skipped += bytes_skipped; + } + if (state->client_avail > 0) { + min = minimum(request, (off_t)state->client_avail); + bytes_skipped = archive_decompressor_none_read_consume(a, min); + request -= bytes_skipped; + total_bytes_skipped += bytes_skipped; + } + if (request == 0) + return (total_bytes_skipped); + /* + * If a client_skipper was provided, try that first. + */ +#if ARCHIVE_API_VERSION < 2 + if ((a->client_skipper != NULL) && (request < SSIZE_MAX)) { +#else + if (a->client_skipper != NULL) { +#endif + bytes_skipped = (a->client_skipper)(&a->archive, + a->client_data, request); + if (bytes_skipped < 0) { /* error */ + state->client_total = state->client_avail = 0; + state->client_next = state->client_buff = NULL; + state->fatal = 1; + return (bytes_skipped); + } + total_bytes_skipped += bytes_skipped; + a->archive.file_position += bytes_skipped; + request -= bytes_skipped; + state->client_next = state->client_buff; + a->archive.raw_position += bytes_skipped; + state->client_avail = state->client_total = 0; + } + /* + * Note that client_skipper will usually not satisfy the + * full request (due to low-level blocking concerns), + * so even if client_skipper is provided, we may still + * have to use ordinary reads to finish out the request. + */ + while (request > 0) { + const void* dummy_buffer; + ssize_t bytes_read; + bytes_read = archive_decompressor_none_read_ahead(a, + &dummy_buffer, 1); + if (bytes_read < 0) + return (bytes_read); + if (bytes_read == 0) { + /* We hit EOF before we satisfied the skip request. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated input file (need to skip %jd bytes)", + (intmax_t)request); + return (ARCHIVE_FATAL); + } + min = (size_t)(minimum(bytes_read, request)); + bytes_read = archive_decompressor_none_read_consume(a, min); + total_bytes_skipped += bytes_read; + request -= bytes_read; + } + return (total_bytes_skipped); +} + +static int +archive_decompressor_none_finish(struct archive_read *a) +{ + struct archive_decompress_none *state; + + state = (struct archive_decompress_none *)a->decompressor->data; + free(state->buffer); + free(state); + a->decompressor->data = NULL; + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_compression_program.c b/libarchive/archive_read_support_compression_program.c new file mode 100644 index 00000000..58b4bbdd --- /dev/null +++ b/libarchive/archive_read_support_compression_program.c @@ -0,0 +1,315 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_program.c,v 1.2 2007/07/20 01:28:50 kientzle Exp $"); + +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifdef HAVE_ERRNO_H +# include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +#ifdef HAVE_LIMITS_H +# include <limits.h> +#endif +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +# include <string.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#include "filter_fork.h" + +struct archive_decompress_program { + char *description; + pid_t child; + int child_stdin, child_stdout; + + char *child_out_buf; + char *child_out_buf_next; + size_t child_out_buf_len, child_out_buf_avail; + + const char *child_in_buf; + size_t child_in_buf_avail; +}; + +static int archive_decompressor_program_bid(const void *, size_t); +static int archive_decompressor_program_finish(struct archive_read *); +static int archive_decompressor_program_init(struct archive_read *, + const void *, size_t); +static ssize_t archive_decompressor_program_read_ahead(struct archive_read *, + const void **, size_t); +static ssize_t archive_decompressor_program_read_consume(struct archive_read *, + size_t); + +int +archive_read_support_compression_program(struct archive *_a, const char *cmd) +{ + struct archive_read *a = (struct archive_read *)_a; + struct decompressor_t *decompressor; + + if (cmd == NULL || *cmd == '\0') + return (ARCHIVE_WARN); + + decompressor = __archive_read_register_compression(a, + archive_decompressor_program_bid, + archive_decompressor_program_init); + if (decompressor == NULL) + return (ARCHIVE_WARN); + + decompressor->config = strdup(cmd); + return (ARCHIVE_OK); +} + +/* + * If the user used us to register, they must really want us to + * handle it, so this module always bids INT_MAX. + */ +static int +archive_decompressor_program_bid(const void *buff, size_t len) +{ + (void)buff; /* UNUSED */ + (void)len; /* UNUSED */ + + return (INT_MAX); /* Default: We'll take it. */ +} + +static ssize_t +child_read(struct archive_read *a, char *buf, size_t buf_len) +{ + struct archive_decompress_program *state = a->decompressor->data; + ssize_t ret, requested; + const void *child_buf; + + if (state->child_stdout == -1) + return (-1); + + if (buf_len == 0) + return (-1); + +restart_read: + requested = buf_len > SSIZE_MAX ? SSIZE_MAX : buf_len; + + do { + ret = read(state->child_stdout, buf, requested); + } while (ret == -1 && errno == EINTR); + + if (ret > 0) + return (ret); + if (ret == 0 || (ret == -1 && errno == EPIPE)) { + close(state->child_stdout); + state->child_stdout = -1; + return (0); + } + if (ret == -1 && errno != EAGAIN) + return (-1); + + if (state->child_in_buf_avail == 0) { + child_buf = state->child_in_buf; + ret = (a->client_reader)(&a->archive, + a->client_data,&child_buf); + state->child_in_buf = (const char *)child_buf; + + if (ret < 0) { + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + return (-1); + } + if (ret == 0) { + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + goto restart_read; + } + state->child_in_buf_avail = ret; + } + + do { + ret = write(state->child_stdin, state->child_in_buf, + state->child_in_buf_avail); + } while (ret == -1 && errno == EINTR); + + if (ret > 0) { + state->child_in_buf += ret; + state->child_in_buf_avail -= ret; + goto restart_read; + } else if (ret == -1 && errno == EAGAIN) { + __archive_check_child(state->child_stdin, state->child_stdout); + goto restart_read; + } else if (ret == 0 || (ret == -1 && errno == EPIPE)) { + close(state->child_stdin); + state->child_stdout = -1; + fcntl(state->child_stdout, F_SETFL, 0); + goto restart_read; + } else { + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + return (-1); + } +} + +static int +archive_decompressor_program_init(struct archive_read *a, const void *buff, size_t n) +{ + struct archive_decompress_program *state; + const char *cmd = a->decompressor->config; + const char *prefix = "Program: "; + + + state = (struct archive_decompress_program *)malloc(sizeof(*state)); + if (!state) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate input data"); + return (ARCHIVE_FATAL); + } + + a->archive.compression_code = ARCHIVE_COMPRESSION_PROGRAM; + state->description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1); + strcpy(state->description, prefix); + strcat(state->description, cmd); + a->archive.compression_name = state->description; + + state->child_out_buf_next = state->child_out_buf = malloc(65536); + if (!state->child_out_buf) { + free(state); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate filter buffer"); + return (ARCHIVE_FATAL); + } + state->child_out_buf_len = 65536; + state->child_out_buf_avail = 0; + + state->child_in_buf = buff; + state->child_in_buf_avail = n; + + if ((state->child = __archive_create_child(cmd, + &state->child_stdin, &state->child_stdout)) == -1) { + free(state->child_out_buf); + free(state); + archive_set_error(&a->archive, EINVAL, + "Can't initialise filter"); + return (ARCHIVE_FATAL); + } + + a->decompressor->data = state; + a->decompressor->read_ahead = archive_decompressor_program_read_ahead; + a->decompressor->consume = archive_decompressor_program_read_consume; + a->decompressor->skip = NULL; + a->decompressor->finish = archive_decompressor_program_finish; + + /* XXX Check that we can read at least one byte? */ + return (ARCHIVE_OK); +} + +static ssize_t +archive_decompressor_program_read_ahead(struct archive_read *a, const void **buff, + size_t min) +{ + struct archive_decompress_program *state; + ssize_t bytes_read; + + state = (struct archive_decompress_program *)a->decompressor->data; + + if (min > state->child_out_buf_len) + min = state->child_out_buf_len; + + while (state->child_stdout != -1 && min > state->child_out_buf_avail) { + if (state->child_out_buf != state->child_out_buf_next) { + memmove(state->child_out_buf, state->child_out_buf_next, + state->child_out_buf_avail); + state->child_out_buf_next = state->child_out_buf; + } + + bytes_read = child_read(a, + state->child_out_buf + state->child_out_buf_avail, + state->child_out_buf_len - state->child_out_buf_avail); + if (bytes_read == -1) + return (-1); + if (bytes_read == 0) + break; + state->child_out_buf_avail += bytes_read; + a->archive.raw_position += bytes_read; + } + + *buff = state->child_out_buf_next; + return (state->child_out_buf_avail); +} + +static ssize_t +archive_decompressor_program_read_consume(struct archive_read *a, size_t request) +{ + struct archive_decompress_program *state; + + state = (struct archive_decompress_program *)a->decompressor->data; + + state->child_out_buf_next += request; + state->child_out_buf_avail -= request; + + a->archive.file_position += request; + return (request); +} + +static int +archive_decompressor_program_finish(struct archive_read *a) +{ + struct archive_decompress_program *state; + int status; + + state = (struct archive_decompress_program *)a->decompressor->data; + + /* Release our configuration data. */ + free(a->decompressor->config); + a->decompressor->config = NULL; + + /* Shut down the child. */ + if (state->child_stdin != -1) + close(state->child_stdin); + if (state->child_stdout != -1) + close(state->child_stdout); + while (waitpid(state->child, &status, 0) == -1 && errno == EINTR) + continue; + + /* Release our private data. */ + free(state->child_out_buf); + free(state->description); + free(state); + a->decompressor->data = NULL; + + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_format_all.c b/libarchive/archive_read_support_format_all.c new file mode 100644 index 00000000..24e31ef5 --- /dev/null +++ b/libarchive/archive_read_support_format_all.c @@ -0,0 +1,42 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_all.c,v 1.10 2007/12/30 04:58:21 kientzle Exp $"); + +#include "archive.h" + +int +archive_read_support_format_all(struct archive *a) +{ + archive_read_support_format_ar(a); + archive_read_support_format_cpio(a); + archive_read_support_format_empty(a); + archive_read_support_format_iso9660(a); + archive_read_support_format_mtree(a); + archive_read_support_format_tar(a); + archive_read_support_format_zip(a); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_format_ar.c b/libarchive/archive_read_support_format_ar.c new file mode 100644 index 00000000..f6e4cc7e --- /dev/null +++ b/libarchive/archive_read_support_format_ar.c @@ -0,0 +1,601 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 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. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_ar.c,v 1.9 2008/03/12 21:10:26 kaiw Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct ar { + off_t entry_bytes_remaining; + off_t entry_offset; + off_t entry_padding; + char *strtab; + size_t strtab_size; +}; + +/* + * Define structure of the "ar" header. + */ +#define AR_name_offset 0 +#define AR_name_size 16 +#define AR_date_offset 16 +#define AR_date_size 12 +#define AR_uid_offset 28 +#define AR_uid_size 6 +#define AR_gid_offset 34 +#define AR_gid_size 6 +#define AR_mode_offset 40 +#define AR_mode_size 8 +#define AR_size_offset 48 +#define AR_size_size 10 +#define AR_fmag_offset 58 +#define AR_fmag_size 2 + +#define isdigit(x) (x) >= '0' && (x) <= '9' + +static int archive_read_format_ar_bid(struct archive_read *a); +static int archive_read_format_ar_cleanup(struct archive_read *a); +static int archive_read_format_ar_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset); +static int archive_read_format_ar_skip(struct archive_read *a); +static int archive_read_format_ar_read_header(struct archive_read *a, + struct archive_entry *e); +static uint64_t ar_atol8(const char *p, unsigned char_cnt); +static uint64_t ar_atol10(const char *p, unsigned char_cnt); +static int ar_parse_gnu_filename_table(struct archive_read *a); +static int ar_parse_common_header(struct ar *ar, struct archive_entry *, + const char *h); + +int +archive_read_support_format_ar(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct ar *ar; + int r; + + ar = (struct ar *)malloc(sizeof(*ar)); + if (ar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate ar data"); + return (ARCHIVE_FATAL); + } + memset(ar, 0, sizeof(*ar)); + ar->strtab = NULL; + + r = __archive_read_register_format(a, + ar, + archive_read_format_ar_bid, + archive_read_format_ar_read_header, + archive_read_format_ar_read_data, + archive_read_format_ar_skip, + archive_read_format_ar_cleanup); + + if (r != ARCHIVE_OK) { + free(ar); + return (r); + } + return (ARCHIVE_OK); +} + +static int +archive_read_format_ar_cleanup(struct archive_read *a) +{ + struct ar *ar; + + ar = (struct ar *)(a->format->data); + if (ar->strtab) + free(ar->strtab); + free(ar); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static int +archive_read_format_ar_bid(struct archive_read *a) +{ + struct ar *ar; + ssize_t bytes_read; + const void *h; + + if (a->archive.archive_format != 0 && + (a->archive.archive_format & ARCHIVE_FORMAT_BASE_MASK) != + ARCHIVE_FORMAT_AR) + return(0); + + ar = (struct ar *)(a->format->data); + + /* + * Verify the 8-byte file signature. + * TODO: Do we need to check more than this? + */ + bytes_read = (a->decompressor->read_ahead)(a, &h, 8); + if (bytes_read < 8) + return (-1); + if (strncmp((const char*)h, "!<arch>\n", 8) == 0) { + return (64); + } + return (-1); +} + +static int +archive_read_format_ar_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + char filename[AR_name_size + 1]; + struct ar *ar; + uint64_t number; /* Used to hold parsed numbers before validation. */ + ssize_t bytes_read; + size_t bsd_name_length, entry_size, s; + char *p, *st; + const void *b; + const char *h; + int r; + + ar = (struct ar*)(a->format->data); + + if (a->archive.file_position == 0) { + /* + * We are now at the beginning of the archive, + * so we need first consume the ar global header. + */ + (a->decompressor->consume)(a, 8); + /* Set a default format code for now. */ + a->archive.archive_format = ARCHIVE_FORMAT_AR; + } + + /* Read the header for the next file entry. */ + bytes_read = (a->decompressor->read_ahead)(a, &b, 60); + if (bytes_read < 60) { + /* Broken header. */ + return (ARCHIVE_EOF); + } + (a->decompressor->consume)(a, 60); + h = (const char *)b; + + /* Verify the magic signature on the file header. */ + if (strncmp(h + AR_fmag_offset, "`\n", 2) != 0) { + archive_set_error(&a->archive, EINVAL, + "Consistency check failed"); + return (ARCHIVE_WARN); + } + + /* Copy filename into work buffer. */ + strncpy(filename, h + AR_name_offset, AR_name_size); + filename[AR_name_size] = '\0'; + + /* + * Guess the format variant based on the filename. + */ + if (a->archive.archive_format == ARCHIVE_FORMAT_AR) { + /* We don't already know the variant, so let's guess. */ + /* + * Biggest clue is presence of '/': GNU starts special + * filenames with '/', appends '/' as terminator to + * non-special names, so anything with '/' should be + * GNU except for BSD long filenames. + */ + if (strncmp(filename, "#1/", 3) == 0) + a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; + else if (strchr(filename, '/') != NULL) + a->archive.archive_format = ARCHIVE_FORMAT_AR_GNU; + else if (strncmp(filename, "__.SYMDEF", 9) == 0) + a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; + /* + * XXX Do GNU/SVR4 'ar' programs ever omit trailing '/' + * if name exactly fills 16-byte field? If so, we + * can't assume entries without '/' are BSD. XXX + */ + } + + /* Update format name from the code. */ + if (a->archive.archive_format == ARCHIVE_FORMAT_AR_GNU) + a->archive.archive_format_name = "ar (GNU/SVR4)"; + else if (a->archive.archive_format == ARCHIVE_FORMAT_AR_BSD) + a->archive.archive_format_name = "ar (BSD)"; + else + a->archive.archive_format_name = "ar"; + + /* + * Remove trailing spaces from the filename. GNU and BSD + * variants both pad filename area out with spaces. + * This will only be wrong if GNU/SVR4 'ar' implementations + * omit trailing '/' for 16-char filenames and we have + * a 16-char filename that ends in ' '. + */ + p = filename + AR_name_size - 1; + while (p >= filename && *p == ' ') { + *p = '\0'; + p--; + } + + /* + * Remove trailing slash unless first character is '/'. + * (BSD entries never end in '/', so this will only trim + * GNU-format entries. GNU special entries start with '/' + * and are not terminated in '/', so we don't trim anything + * that starts with '/'.) + */ + if (filename[0] != '/' && *p == '/') + *p = '\0'; + + /* + * '//' is the GNU filename table. + * Later entries can refer to names in this table. + */ + if (strcmp(filename, "//") == 0) { + /* This must come before any call to _read_ahead. */ + ar_parse_common_header(ar, entry, h); + archive_entry_copy_pathname(entry, filename); + archive_entry_set_filetype(entry, AE_IFREG); + /* Get the size of the filename table. */ + number = ar_atol10(h + AR_size_offset, AR_size_size); + if (number > SIZE_MAX) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Filename table too large"); + return (ARCHIVE_FATAL); + } + entry_size = (size_t)number; + if (entry_size == 0) { + archive_set_error(&a->archive, EINVAL, + "Invalid string table"); + return (ARCHIVE_WARN); + } + if (ar->strtab != NULL) { + archive_set_error(&a->archive, EINVAL, + "More than one string tables exist"); + return (ARCHIVE_WARN); + } + + /* Read the filename table into memory. */ + st = malloc(entry_size); + if (st == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate filename table buffer"); + return (ARCHIVE_FATAL); + } + ar->strtab = st; + ar->strtab_size = entry_size; + for (s = entry_size; s > 0; s -= bytes_read) { + bytes_read = (a->decompressor->read_ahead)(a, &b, s); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if (bytes_read > (ssize_t)s) + bytes_read = s; + memcpy(st, b, bytes_read); + st += bytes_read; + (a->decompressor->consume)(a, bytes_read); + } + /* All contents are consumed. */ + ar->entry_bytes_remaining = 0; + archive_entry_set_size(entry, ar->entry_bytes_remaining); + + /* Parse the filename table. */ + return (ar_parse_gnu_filename_table(a)); + } + + /* + * GNU variant handles long filenames by storing /<number> + * to indicate a name stored in the filename table. + */ + if (filename[0] == '/' && isdigit(filename[1])) { + number = ar_atol10(h + AR_name_offset + 1, AR_name_size - 1); + /* + * If we can't look up the real name, warn and return + * the entry with the wrong name. + */ + if (ar->strtab == NULL || number > ar->strtab_size) { + archive_set_error(&a->archive, EINVAL, + "Can't find long filename for entry"); + archive_entry_copy_pathname(entry, filename); + /* Parse the time, owner, mode, size fields. */ + ar_parse_common_header(ar, entry, h); + return (ARCHIVE_WARN); + } + + archive_entry_copy_pathname(entry, &ar->strtab[(size_t)number]); + /* Parse the time, owner, mode, size fields. */ + return (ar_parse_common_header(ar, entry, h)); + } + + /* + * BSD handles long filenames by storing "#1/" followed by the + * length of filename as a decimal number, then prepends the + * the filename to the file contents. + */ + if (strncmp(filename, "#1/", 3) == 0) { + /* Parse the time, owner, mode, size fields. */ + /* This must occur before _read_ahead is called again. */ + ar_parse_common_header(ar, entry, h); + + /* Parse the size of the name, adjust the file size. */ + number = ar_atol10(h + AR_name_offset + 3, AR_name_size - 3); + if ((off_t)number > ar->entry_bytes_remaining) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Bad input file size"); + return (ARCHIVE_FATAL); + } + bsd_name_length = (size_t)number; + ar->entry_bytes_remaining -= bsd_name_length; + /* Adjust file size reported to client. */ + archive_entry_set_size(entry, ar->entry_bytes_remaining); + + /* Read the long name into memory. */ + bytes_read = (a->decompressor->read_ahead)(a, &b, bsd_name_length); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if ((size_t)bytes_read < bsd_name_length) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated input file"); + return (ARCHIVE_FATAL); + } + (a->decompressor->consume)(a, bsd_name_length); + + /* Store it in the entry. */ + p = (char *)malloc(bsd_name_length + 1); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate fname buffer"); + return (ARCHIVE_FATAL); + } + strncpy(p, b, bsd_name_length); + p[bsd_name_length] = '\0'; + archive_entry_copy_pathname(entry, p); + free(p); + return (ARCHIVE_OK); + } + + /* + * "/" is the SVR4/GNU archive symbol table. + */ + if (strcmp(filename, "/") == 0) { + archive_entry_copy_pathname(entry, "/"); + /* Parse the time, owner, mode, size fields. */ + r = ar_parse_common_header(ar, entry, h); + /* Force the file type to a regular file. */ + archive_entry_set_filetype(entry, AE_IFREG); + return (r); + } + + /* + * "__.SYMDEF" is a BSD archive symbol table. + */ + if (strcmp(filename, "__.SYMDEF") == 0) { + archive_entry_copy_pathname(entry, filename); + /* Parse the time, owner, mode, size fields. */ + return (ar_parse_common_header(ar, entry, h)); + } + + /* + * Otherwise, this is a standard entry. The filename + * has already been trimmed as much as possible, based + * on our current knowledge of the format. + */ + archive_entry_copy_pathname(entry, filename); + return (ar_parse_common_header(ar, entry, h)); +} + +static int +ar_parse_common_header(struct ar *ar, struct archive_entry *entry, + const char *h) +{ + uint64_t n; + + /* Copy remaining header */ + archive_entry_set_mtime(entry, + (time_t)ar_atol10(h + AR_date_offset, AR_date_size), 0L); + archive_entry_set_uid(entry, + (uid_t)ar_atol10(h + AR_uid_offset, AR_uid_size)); + archive_entry_set_gid(entry, + (gid_t)ar_atol10(h + AR_gid_offset, AR_gid_size)); + archive_entry_set_mode(entry, + (mode_t)ar_atol8(h + AR_mode_offset, AR_mode_size)); + n = ar_atol10(h + AR_size_offset, AR_size_size); + + ar->entry_offset = 0; + ar->entry_padding = n % 2; + archive_entry_set_size(entry, n); + ar->entry_bytes_remaining = n; + return (ARCHIVE_OK); +} + +static int +archive_read_format_ar_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset) +{ + ssize_t bytes_read; + struct ar *ar; + + ar = (struct ar *)(a->format->data); + + if (ar->entry_bytes_remaining > 0) { + bytes_read = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_read == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated ar archive"); + return (ARCHIVE_FATAL); + } + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read > ar->entry_bytes_remaining) + bytes_read = (ssize_t)ar->entry_bytes_remaining; + *size = bytes_read; + *offset = ar->entry_offset; + ar->entry_offset += bytes_read; + ar->entry_bytes_remaining -= bytes_read; + (a->decompressor->consume)(a, (size_t)bytes_read); + return (ARCHIVE_OK); + } else { + while (ar->entry_padding > 0) { + bytes_read = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if (bytes_read > ar->entry_padding) + bytes_read = (ssize_t)ar->entry_padding; + (a->decompressor->consume)(a, (size_t)bytes_read); + ar->entry_padding -= bytes_read; + } + *buff = NULL; + *size = 0; + *offset = ar->entry_offset; + return (ARCHIVE_EOF); + } +} + +static int +archive_read_format_ar_skip(struct archive_read *a) +{ + off_t bytes_skipped; + struct ar* ar; + int r = ARCHIVE_OK; + const void *b; /* Dummy variables */ + size_t s; + off_t o; + + ar = (struct ar *)(a->format->data); + if (a->decompressor->skip == NULL) { + while (r == ARCHIVE_OK) + r = archive_read_format_ar_read_data(a, &b, &s, &o); + return (r); + } + + bytes_skipped = (a->decompressor->skip)(a, ar->entry_bytes_remaining + + ar->entry_padding); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + ar->entry_bytes_remaining = 0; + ar->entry_padding = 0; + + return (ARCHIVE_OK); +} + +static int +ar_parse_gnu_filename_table(struct archive_read *a) +{ + struct ar *ar; + char *p; + size_t size; + + ar = (struct ar*)(a->format->data); + size = ar->strtab_size; + + for (p = ar->strtab; p < ar->strtab + size - 1; ++p) { + if (*p == '/') { + *p++ = '\0'; + if (*p != '\n') + goto bad_string_table; + *p = '\0'; + } + } + /* + * Sanity check, last two chars must be `/\n' or '\n\n', + * depending on whether the string table is padded by a '\n' + * (string table produced by GNU ar always has a even size). + */ + if (p != ar->strtab + size && *p != '\n') + goto bad_string_table; + + /* Enforce zero termination. */ + ar->strtab[size - 1] = '\0'; + + return (ARCHIVE_OK); + +bad_string_table: + archive_set_error(&a->archive, EINVAL, + "Invalid string table"); + free(ar->strtab); + ar->strtab = NULL; + return (ARCHIVE_WARN); +} + +static uint64_t +ar_atol8(const char *p, unsigned char_cnt) +{ + uint64_t l, limit, last_digit_limit; + unsigned int digit, base; + + base = 8; + limit = UINT64_MAX / base; + last_digit_limit = UINT64_MAX % base; + + while ((*p == ' ' || *p == '\t') && char_cnt-- > 0) + p++; + + l = 0; + digit = *p - '0'; + while (*p >= '0' && digit < base && char_cnt-- > 0) { + if (l>limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (l); +} + +static uint64_t +ar_atol10(const char *p, unsigned char_cnt) +{ + uint64_t l, limit, last_digit_limit; + unsigned int base, digit; + + base = 10; + limit = UINT64_MAX / base; + last_digit_limit = UINT64_MAX % base; + + while ((*p == ' ' || *p == '\t') && char_cnt-- > 0) + p++; + l = 0; + digit = *p - '0'; + while (*p >= '0' && digit < base && char_cnt-- > 0) { + if (l > limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (l); +} diff --git a/libarchive/archive_read_support_format_cpio.c b/libarchive/archive_read_support_format_cpio.c new file mode 100644 index 00000000..2c50abc6 --- /dev/null +++ b/libarchive/archive_read_support_format_cpio.c @@ -0,0 +1,777 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_cpio.c,v 1.26 2008/01/15 04:56:48 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +/* #include <stdint.h> */ /* See archive_platform.h */ +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct cpio_bin_header { + unsigned char c_magic[2]; + unsigned char c_dev[2]; + unsigned char c_ino[2]; + unsigned char c_mode[2]; + unsigned char c_uid[2]; + unsigned char c_gid[2]; + unsigned char c_nlink[2]; + unsigned char c_rdev[2]; + unsigned char c_mtime[4]; + unsigned char c_namesize[2]; + unsigned char c_filesize[4]; +}; + +struct cpio_odc_header { + char c_magic[6]; + char c_dev[6]; + char c_ino[6]; + char c_mode[6]; + char c_uid[6]; + char c_gid[6]; + char c_nlink[6]; + char c_rdev[6]; + char c_mtime[11]; + char c_namesize[6]; + char c_filesize[11]; +}; + +struct cpio_newc_header { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_crc[8]; +}; + +struct links_entry { + struct links_entry *next; + struct links_entry *previous; + int links; + dev_t dev; + ino_t ino; + char *name; +}; + +#define CPIO_MAGIC 0x13141516 +struct cpio { + int magic; + int (*read_header)(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); + struct links_entry *links_head; + struct archive_string entry_name; + struct archive_string entry_linkname; + off_t entry_bytes_remaining; + off_t entry_offset; + off_t entry_padding; +}; + +static int64_t atol16(const char *, unsigned); +static int64_t atol8(const char *, unsigned); +static int archive_read_format_cpio_bid(struct archive_read *); +static int archive_read_format_cpio_cleanup(struct archive_read *); +static int archive_read_format_cpio_read_data(struct archive_read *, + const void **, size_t *, off_t *); +static int archive_read_format_cpio_read_header(struct archive_read *, + struct archive_entry *); +static int be4(const unsigned char *); +static int find_odc_header(struct archive_read *); +static int find_newc_header(struct archive_read *); +static int header_bin_be(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_bin_le(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_newc(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_odc(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int is_octal(const char *, size_t); +static int is_hex(const char *, size_t); +static int le4(const unsigned char *); +static void record_hardlink(struct cpio *cpio, struct archive_entry *entry); + +int +archive_read_support_format_cpio(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct cpio *cpio; + int r; + + cpio = (struct cpio *)malloc(sizeof(*cpio)); + if (cpio == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); + return (ARCHIVE_FATAL); + } + memset(cpio, 0, sizeof(*cpio)); + cpio->magic = CPIO_MAGIC; + + r = __archive_read_register_format(a, + cpio, + archive_read_format_cpio_bid, + archive_read_format_cpio_read_header, + archive_read_format_cpio_read_data, + NULL, + archive_read_format_cpio_cleanup); + + if (r != ARCHIVE_OK) + free(cpio); + return (ARCHIVE_OK); +} + + +static int +archive_read_format_cpio_bid(struct archive_read *a) +{ + int bytes_read; + const void *h; + const unsigned char *p; + struct cpio *cpio; + int bid; + + cpio = (struct cpio *)(a->format->data); + + bytes_read = (a->decompressor->read_ahead)(a, &h, 6); + /* Convert error code into error return. */ + if (bytes_read < 0) + return ((int)bytes_read); + if (bytes_read < 6) + return (-1); + + p = (const unsigned char *)h; + bid = 0; + if (memcmp(p, "070707", 6) == 0) { + /* ASCII cpio archive (odc, POSIX.1) */ + cpio->read_header = header_odc; + bid += 48; + /* + * XXX TODO: More verification; Could check that only octal + * digits appear in appropriate header locations. XXX + */ + } else if (memcmp(p, "070701", 6) == 0) { + /* ASCII cpio archive (SVR4 without CRC) */ + cpio->read_header = header_newc; + bid += 48; + /* + * XXX TODO: More verification; Could check that only hex + * digits appear in appropriate header locations. XXX + */ + } else if (memcmp(p, "070702", 6) == 0) { + /* ASCII cpio archive (SVR4 with CRC) */ + /* XXX TODO: Flag that we should check the CRC. XXX */ + cpio->read_header = header_newc; + bid += 48; + /* + * XXX TODO: More verification; Could check that only hex + * digits appear in appropriate header locations. XXX + */ + } else if (p[0] * 256 + p[1] == 070707) { + /* big-endian binary cpio archives */ + cpio->read_header = header_bin_be; + bid += 16; + /* Is more verification possible here? */ + } else if (p[0] + p[1] * 256 == 070707) { + /* little-endian binary cpio archives */ + cpio->read_header = header_bin_le; + bid += 16; + /* Is more verification possible here? */ + } else + return (ARCHIVE_WARN); + + return (bid); +} + +static int +archive_read_format_cpio_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct cpio *cpio; + size_t bytes; + const void *h; + size_t namelength; + size_t name_pad; + int r; + + cpio = (struct cpio *)(a->format->data); + r = (cpio->read_header(a, cpio, entry, &namelength, &name_pad)); + + if (r < ARCHIVE_WARN) + return (r); + + /* Read name from buffer. */ + bytes = (a->decompressor->read_ahead)(a, &h, namelength + name_pad); + if (bytes < namelength + name_pad) + return (ARCHIVE_FATAL); + (a->decompressor->consume)(a, namelength + name_pad); + archive_strncpy(&cpio->entry_name, (const char *)h, namelength); + archive_entry_set_pathname(entry, cpio->entry_name.s); + cpio->entry_offset = 0; + + /* If this is a symlink, read the link contents. */ + if (archive_entry_filetype(entry) == AE_IFLNK) { + bytes = (a->decompressor->read_ahead)(a, &h, + cpio->entry_bytes_remaining); + if ((off_t)bytes < cpio->entry_bytes_remaining) + return (ARCHIVE_FATAL); + (a->decompressor->consume)(a, cpio->entry_bytes_remaining); + archive_strncpy(&cpio->entry_linkname, (const char *)h, + cpio->entry_bytes_remaining); + archive_entry_set_symlink(entry, cpio->entry_linkname.s); + cpio->entry_bytes_remaining = 0; + } + + /* Compare name to "TRAILER!!!" to test for end-of-archive. */ + if (namelength == 11 && strcmp((const char *)h, "TRAILER!!!") == 0) { + /* TODO: Store file location of start of block. */ + archive_set_error(&a->archive, 0, NULL); + return (ARCHIVE_EOF); + } + + /* Detect and record hardlinks to previously-extracted entries. */ + record_hardlink(cpio, entry); + + return (r); +} + +static int +archive_read_format_cpio_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset) +{ + ssize_t bytes_read; + struct cpio *cpio; + + cpio = (struct cpio *)(a->format->data); + if (cpio->entry_bytes_remaining > 0) { + bytes_read = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if (bytes_read > cpio->entry_bytes_remaining) + bytes_read = cpio->entry_bytes_remaining; + *size = bytes_read; + *offset = cpio->entry_offset; + cpio->entry_offset += bytes_read; + cpio->entry_bytes_remaining -= bytes_read; + (a->decompressor->consume)(a, bytes_read); + return (ARCHIVE_OK); + } else { + while (cpio->entry_padding > 0) { + bytes_read = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if (bytes_read > cpio->entry_padding) + bytes_read = cpio->entry_padding; + (a->decompressor->consume)(a, bytes_read); + cpio->entry_padding -= bytes_read; + } + *buff = NULL; + *size = 0; + *offset = cpio->entry_offset; + return (ARCHIVE_EOF); + } +} + +/* + * Skip forward to the next cpio newc header by searching for the + * 07070[12] string. This should be generalized and merged with + * find_odc_header below. + */ +static int +is_hex(const char *p, size_t len) +{ + while (len-- > 0) { + if ((*p >= '0' && *p <= '9') + || (*p >= 'a' && *p <= 'f') + || (*p >= 'A' && *p <= 'F')) + ++p; + else + return (0); + } + return (1); +} + +static int +find_newc_header(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t skip, bytes, skipped = 0; + + for (;;) { + bytes = (a->decompressor->read_ahead)(a, &h, 2048); + if (bytes < sizeof(struct cpio_newc_header)) + return (ARCHIVE_FATAL); + p = h; + q = p + bytes; + + /* Try the typical case first, then go into the slow search.*/ + if (memcmp("07070", p, 5) == 0 + && (p[5] == '1' || p[5] == '2') + && is_hex(p, sizeof(struct cpio_newc_header))) + return (ARCHIVE_OK); + + /* + * Scan ahead until we find something that looks + * like an odc header. + */ + while (p + sizeof(struct cpio_newc_header) < q) { + switch (p[5]) { + case '1': + case '2': + if (memcmp("07070", p, 5) == 0 + && is_hex(p, sizeof(struct cpio_newc_header))) { + skip = p - (const char *)h; + (a->decompressor->consume)(a, skip); + skipped += skip; + if (skipped > 0) { + archive_set_error(&a->archive, + 0, + "Skipped %d bytes before " + "finding valid header", + (int)skipped); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } + p += 2; + break; + case '0': + p++; + break; + default: + p += 6; + break; + } + } + skip = p - (const char *)h; + (a->decompressor->consume)(a, skip); + skipped += skip; + } +} + +static int +header_newc(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const struct cpio_newc_header *header; + size_t bytes; + int r; + + r = find_newc_header(a); + if (r < ARCHIVE_WARN) + return (r); + + /* Read fixed-size portion of header. */ + bytes = (a->decompressor->read_ahead)(a, &h, sizeof(struct cpio_newc_header)); + if (bytes < sizeof(struct cpio_newc_header)) + return (ARCHIVE_FATAL); + (a->decompressor->consume)(a, sizeof(struct cpio_newc_header)); + + /* Parse out hex fields. */ + header = (const struct cpio_newc_header *)h; + + if (memcmp(header->c_magic, "070701", 6) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC; + a->archive.archive_format_name = "ASCII cpio (SVR4 with no CRC)"; + } else if (memcmp(header->c_magic, "070702", 6) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_SVR4_CRC; + a->archive.archive_format_name = "ASCII cpio (SVR4 with CRC)"; + } else { + /* TODO: Abort here? */ + } + + archive_entry_set_devmajor(entry, atol16(header->c_devmajor, sizeof(header->c_devmajor))); + archive_entry_set_devminor(entry, atol16(header->c_devminor, sizeof(header->c_devminor))); + archive_entry_set_ino(entry, atol16(header->c_ino, sizeof(header->c_ino))); + archive_entry_set_mode(entry, atol16(header->c_mode, sizeof(header->c_mode))); + archive_entry_set_uid(entry, atol16(header->c_uid, sizeof(header->c_uid))); + archive_entry_set_gid(entry, atol16(header->c_gid, sizeof(header->c_gid))); + archive_entry_set_nlink(entry, atol16(header->c_nlink, sizeof(header->c_nlink))); + archive_entry_set_rdevmajor(entry, atol16(header->c_rdevmajor, sizeof(header->c_rdevmajor))); + archive_entry_set_rdevminor(entry, atol16(header->c_rdevminor, sizeof(header->c_rdevminor))); + archive_entry_set_mtime(entry, atol16(header->c_mtime, sizeof(header->c_mtime)), 0); + *namelength = atol16(header->c_namesize, sizeof(header->c_namesize)); + /* Pad name to 2 more than a multiple of 4. */ + *name_pad = (2 - *namelength) & 3; + + /* + * Note: entry_bytes_remaining is at least 64 bits and + * therefore guaranteed to be big enough for a 33-bit file + * size. + */ + cpio->entry_bytes_remaining = + atol16(header->c_filesize, sizeof(header->c_filesize)); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + /* Pad file contents to a multiple of 4. */ + cpio->entry_padding = 3 & -cpio->entry_bytes_remaining; + return (r); +} + +/* + * Skip forward to the next cpio odc header by searching for the + * 070707 string. This is a hand-optimized search that could + * probably be easily generalized to handle all character-based + * cpio variants. + */ +static int +is_octal(const char *p, size_t len) +{ + while (len-- > 0) { + if (*p < '0' || *p > '7') + return (0); + ++p; + } + return (1); +} + +static int +find_odc_header(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t skip, bytes, skipped = 0; + + for (;;) { + bytes = (a->decompressor->read_ahead)(a, &h, 512); + if (bytes < sizeof(struct cpio_odc_header)) + return (ARCHIVE_FATAL); + p = h; + q = p + bytes; + + /* Try the typical case first, then go into the slow search.*/ + if (memcmp("070707", p, 6) == 0 + && is_octal(p, sizeof(struct cpio_odc_header))) + return (ARCHIVE_OK); + + /* + * Scan ahead until we find something that looks + * like an odc header. + */ + while (p + sizeof(struct cpio_odc_header) < q) { + switch (p[5]) { + case '7': + if (memcmp("070707", p, 6) == 0 + && is_octal(p, sizeof(struct cpio_odc_header))) { + skip = p - (const char *)h; + (a->decompressor->consume)(a, skip); + skipped += skip; + if (skipped > 0) { + archive_set_error(&a->archive, + 0, + "Skipped %d bytes before " + "finding valid header", + (int)skipped); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } + p += 2; + break; + case '0': + p++; + break; + default: + p += 6; + break; + } + } + skip = p - (const char *)h; + (a->decompressor->consume)(a, skip); + skipped += skip; + } +} + +static int +header_odc(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + int r; + const struct cpio_odc_header *header; + size_t bytes; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_POSIX; + a->archive.archive_format_name = "POSIX octet-oriented cpio"; + + /* Find the start of the next header. */ + r = find_odc_header(a); + if (r < ARCHIVE_WARN) + return (r); + + /* Read fixed-size portion of header. */ + bytes = (a->decompressor->read_ahead)(a, &h, sizeof(struct cpio_odc_header)); + if (bytes < sizeof(struct cpio_odc_header)) + return (ARCHIVE_FATAL); + (a->decompressor->consume)(a, sizeof(struct cpio_odc_header)); + + /* Parse out octal fields. */ + header = (const struct cpio_odc_header *)h; + + archive_entry_set_dev(entry, atol8(header->c_dev, sizeof(header->c_dev))); + archive_entry_set_ino(entry, atol8(header->c_ino, sizeof(header->c_ino))); + archive_entry_set_mode(entry, atol8(header->c_mode, sizeof(header->c_mode))); + archive_entry_set_uid(entry, atol8(header->c_uid, sizeof(header->c_uid))); + archive_entry_set_gid(entry, atol8(header->c_gid, sizeof(header->c_gid))); + archive_entry_set_nlink(entry, atol8(header->c_nlink, sizeof(header->c_nlink))); + archive_entry_set_rdev(entry, atol8(header->c_rdev, sizeof(header->c_rdev))); + archive_entry_set_mtime(entry, atol8(header->c_mtime, sizeof(header->c_mtime)), 0); + *namelength = atol8(header->c_namesize, sizeof(header->c_namesize)); + *name_pad = 0; /* No padding of filename. */ + + /* + * Note: entry_bytes_remaining is at least 64 bits and + * therefore guaranteed to be big enough for a 33-bit file + * size. + */ + cpio->entry_bytes_remaining = + atol8(header->c_filesize, sizeof(header->c_filesize)); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = 0; + return (r); +} + +static int +header_bin_le(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const struct cpio_bin_header *header; + size_t bytes; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_BIN_LE; + a->archive.archive_format_name = "cpio (little-endian binary)"; + + /* Read fixed-size portion of header. */ + bytes = (a->decompressor->read_ahead)(a, &h, sizeof(struct cpio_bin_header)); + if (bytes < sizeof(struct cpio_bin_header)) + return (ARCHIVE_FATAL); + (a->decompressor->consume)(a, sizeof(struct cpio_bin_header)); + + /* Parse out binary fields. */ + header = (const struct cpio_bin_header *)h; + + archive_entry_set_dev(entry, header->c_dev[0] + header->c_dev[1] * 256); + archive_entry_set_ino(entry, header->c_ino[0] + header->c_ino[1] * 256); + archive_entry_set_mode(entry, header->c_mode[0] + header->c_mode[1] * 256); + archive_entry_set_uid(entry, header->c_uid[0] + header->c_uid[1] * 256); + archive_entry_set_gid(entry, header->c_gid[0] + header->c_gid[1] * 256); + archive_entry_set_nlink(entry, header->c_nlink[0] + header->c_nlink[1] * 256); + archive_entry_set_rdev(entry, header->c_rdev[0] + header->c_rdev[1] * 256); + archive_entry_set_mtime(entry, le4(header->c_mtime), 0); + *namelength = header->c_namesize[0] + header->c_namesize[1] * 256; + *name_pad = *namelength & 1; /* Pad to even. */ + + cpio->entry_bytes_remaining = le4(header->c_filesize); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = cpio->entry_bytes_remaining & 1; /* Pad to even. */ + return (ARCHIVE_OK); +} + +static int +header_bin_be(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const struct cpio_bin_header *header; + size_t bytes; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_BIN_BE; + a->archive.archive_format_name = "cpio (big-endian binary)"; + + /* Read fixed-size portion of header. */ + bytes = (a->decompressor->read_ahead)(a, &h, + sizeof(struct cpio_bin_header)); + if (bytes < sizeof(struct cpio_bin_header)) + return (ARCHIVE_FATAL); + (a->decompressor->consume)(a, sizeof(struct cpio_bin_header)); + + /* Parse out binary fields. */ + header = (const struct cpio_bin_header *)h; + archive_entry_set_dev(entry, header->c_dev[0] * 256 + header->c_dev[1]); + archive_entry_set_ino(entry, header->c_ino[0] * 256 + header->c_ino[1]); + archive_entry_set_mode(entry, header->c_mode[0] * 256 + header->c_mode[1]); + archive_entry_set_uid(entry, header->c_uid[0] * 256 + header->c_uid[1]); + archive_entry_set_gid(entry, header->c_gid[0] * 256 + header->c_gid[1]); + archive_entry_set_nlink(entry, header->c_nlink[0] * 256 + header->c_nlink[1]); + archive_entry_set_rdev(entry, header->c_rdev[0] * 256 + header->c_rdev[1]); + archive_entry_set_mtime(entry, be4(header->c_mtime), 0); + *namelength = header->c_namesize[0] * 256 + header->c_namesize[1]; + *name_pad = *namelength & 1; /* Pad to even. */ + + cpio->entry_bytes_remaining = be4(header->c_filesize); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = cpio->entry_bytes_remaining & 1; /* Pad to even. */ + return (ARCHIVE_OK); +} + +static int +archive_read_format_cpio_cleanup(struct archive_read *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)(a->format->data); + /* Free inode->name map */ + while (cpio->links_head != NULL) { + struct links_entry *lp = cpio->links_head->next; + + if (cpio->links_head->name) + free(cpio->links_head->name); + free(cpio->links_head); + cpio->links_head = lp; + } + archive_string_free(&cpio->entry_name); + free(cpio); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static int +le4(const unsigned char *p) +{ + return ((p[0]<<16) + (p[1]<<24) + (p[2]<<0) + (p[3]<<8)); +} + + +static int +be4(const unsigned char *p) +{ + return (p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24)); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +atol8(const char *p, unsigned char_cnt) +{ + int64_t l; + int digit; + + l = 0; + while (char_cnt-- > 0) { + if (*p >= '0' && *p <= '7') + digit = *p - '0'; + else + return (l); + p++; + l <<= 3; + l |= digit; + } + return (l); +} + +static int64_t +atol16(const char *p, unsigned char_cnt) +{ + int64_t l; + int digit; + + l = 0; + while (char_cnt-- > 0) { + if (*p >= 'a' && *p <= 'f') + digit = *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + digit = *p - 'A' + 10; + else if (*p >= '0' && *p <= '9') + digit = *p - '0'; + else + return (l); + p++; + l <<= 4; + l |= digit; + } + return (l); +} + +static void +record_hardlink(struct cpio *cpio, struct archive_entry *entry) +{ + struct links_entry *le; + dev_t dev; + ino_t ino; + + dev = archive_entry_dev(entry); + ino = archive_entry_ino(entry); + + /* + * First look in the list of multiply-linked files. If we've + * already dumped it, convert this entry to a hard link entry. + */ + for (le = cpio->links_head; le; le = le->next) { + if (le->dev == dev && le->ino == ino) { + archive_entry_copy_hardlink(entry, le->name); + + if (--le->links <= 0) { + if (le->previous != NULL) + le->previous->next = le->next; + if (le->next != NULL) + le->next->previous = le->previous; + if (cpio->links_head == le) + cpio->links_head = le->next; + free(le->name); + free(le); + } + + return; + } + } + + le = (struct links_entry *)malloc(sizeof(struct links_entry)); + if (le == NULL) + __archive_errx(1, "Out of memory adding file to list"); + if (cpio->links_head != NULL) + cpio->links_head->previous = le; + le->next = cpio->links_head; + le->previous = NULL; + cpio->links_head = le; + le->dev = dev; + le->ino = ino; + le->links = archive_entry_nlink(entry) - 1; + le->name = strdup(archive_entry_pathname(entry)); + if (le->name == NULL) + __archive_errx(1, "Out of memory adding file to list"); +} diff --git a/libarchive/archive_read_support_format_empty.c b/libarchive/archive_read_support_format_empty.c new file mode 100644 index 00000000..837fdef9 --- /dev/null +++ b/libarchive/archive_read_support_format_empty.c @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_empty.c,v 1.3 2007/05/29 01:00:19 kientzle Exp $"); + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +static int archive_read_format_empty_bid(struct archive_read *); +static int archive_read_format_empty_read_data(struct archive_read *, + const void **, size_t *, off_t *); +static int archive_read_format_empty_read_header(struct archive_read *, + struct archive_entry *); +int +archive_read_support_format_empty(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int r; + + r = __archive_read_register_format(a, + NULL, + archive_read_format_empty_bid, + archive_read_format_empty_read_header, + archive_read_format_empty_read_data, + NULL, + NULL); + + return (r); +} + + +static int +archive_read_format_empty_bid(struct archive_read *a) +{ + int bytes_read; + const void *h; + + bytes_read = (a->decompressor->read_ahead)(a, &h, 1); + if (bytes_read > 0) + return (-1); + return (1); +} + +static int +archive_read_format_empty_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + (void)a; /* UNUSED */ + (void)entry; /* UNUSED */ + + a->archive.archive_format = ARCHIVE_FORMAT_EMPTY; + a->archive.archive_format_name = "Empty file"; + + return (ARCHIVE_EOF); +} + +static int +archive_read_format_empty_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset) +{ + (void)a; /* UNUSED */ + (void)buff; /* UNUSED */ + (void)size; /* UNUSED */ + (void)offset; /* UNUSED */ + + return (ARCHIVE_EOF); +} diff --git a/libarchive/archive_read_support_format_iso9660.c b/libarchive/archive_read_support_format_iso9660.c new file mode 100644 index 00000000..d333f0cc --- /dev/null +++ b/libarchive/archive_read_support_format_iso9660.c @@ -0,0 +1,1129 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_iso9660.c,v 1.25 2008/02/19 06:02:01 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +/* #include <stdint.h> */ /* See archive_platform.h */ +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#include <time.h> + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_string.h" + +/* + * An overview of ISO 9660 format: + * + * Each disk is laid out as follows: + * * 32k reserved for private use + * * Volume descriptor table. Each volume descriptor + * is 2k and specifies basic format information. + * The "Primary Volume Descriptor" (PVD) is defined by the + * standard and should always be present; other volume + * descriptors include various vendor-specific extensions. + * * Files and directories. Each file/dir is specified by + * an "extent" (starting sector and length in bytes). + * Dirs are just files with directory records packed one + * after another. The PVD contains a single dir entry + * specifying the location of the root directory. Everything + * else follows from there. + * + * This module works by first reading the volume descriptors, then + * building a list of directory entries, sorted by starting + * sector. At each step, I look for the earliest dir entry that + * hasn't yet been read, seek forward to that location and read + * that entry. If it's a dir, I slurp in the new dir entries and + * add them to the heap; if it's a regular file, I return the + * corresponding archive_entry and wait for the client to request + * the file body. This strategy allows us to read most compliant + * CDs with a single pass through the data, as required by libarchive. + */ + +/* Structure of on-disk primary volume descriptor. */ +#define PVD_type_offset 0 +#define PVD_type_size 1 +#define PVD_id_offset (PVD_type_offset + PVD_type_size) +#define PVD_id_size 5 +#define PVD_version_offset (PVD_id_offset + PVD_id_size) +#define PVD_version_size 1 +#define PVD_reserved1_offset (PVD_version_offset + PVD_version_size) +#define PVD_reserved1_size 1 +#define PVD_system_id_offset (PVD_reserved1_offset + PVD_reserved1_size) +#define PVD_system_id_size 32 +#define PVD_volume_id_offset (PVD_system_id_offset + PVD_system_id_size) +#define PVD_volume_id_size 32 +#define PVD_reserved2_offset (PVD_volume_id_offset + PVD_volume_id_size) +#define PVD_reserved2_size 8 +#define PVD_volume_space_size_offset (PVD_reserved2_offset + PVD_reserved2_size) +#define PVD_volume_space_size_size 8 +#define PVD_reserved3_offset (PVD_volume_space_size_offset + PVD_volume_space_size_size) +#define PVD_reserved3_size 32 +#define PVD_volume_set_size_offset (PVD_reserved3_offset + PVD_reserved3_size) +#define PVD_volume_set_size_size 4 +#define PVD_volume_sequence_number_offset (PVD_volume_set_size_offset + PVD_volume_set_size_size) +#define PVD_volume_sequence_number_size 4 +#define PVD_logical_block_size_offset (PVD_volume_sequence_number_offset + PVD_volume_sequence_number_size) +#define PVD_logical_block_size_size 4 +#define PVD_path_table_size_offset (PVD_logical_block_size_offset + PVD_logical_block_size_size) +#define PVD_path_table_size_size 8 +#define PVD_type_1_path_table_offset (PVD_path_table_size_offset + PVD_path_table_size_size) +#define PVD_type_1_path_table_size 4 +#define PVD_opt_type_1_path_table_offset (PVD_type_1_path_table_offset + PVD_type_1_path_table_size) +#define PVD_opt_type_1_path_table_size 4 +#define PVD_type_m_path_table_offset (PVD_opt_type_1_path_table_offset + PVD_opt_type_1_path_table_size) +#define PVD_type_m_path_table_size 4 +#define PVD_opt_type_m_path_table_offset (PVD_type_m_path_table_offset + PVD_type_m_path_table_size) +#define PVD_opt_type_m_path_table_size 4 +#define PVD_root_directory_record_offset (PVD_opt_type_m_path_table_offset + PVD_opt_type_m_path_table_size) +#define PVD_root_directory_record_size 34 +#define PVD_volume_set_id_offset (PVD_root_directory_record_offset + PVD_root_directory_record_size) +#define PVD_volume_set_id_size 128 +#define PVD_publisher_id_offset (PVD_volume_set_id_offset + PVD_volume_set_id_size) +#define PVD_publisher_id_size 128 +#define PVD_preparer_id_offset (PVD_publisher_id_offset + PVD_publisher_id_size) +#define PVD_preparer_id_size 128 +#define PVD_application_id_offset (PVD_preparer_id_offset + PVD_preparer_id_size) +#define PVD_application_id_size 128 +#define PVD_copyright_file_id_offset (PVD_application_id_offset + PVD_application_id_size) +#define PVD_copyright_file_id_size 37 +#define PVD_abstract_file_id_offset (PVD_copyright_file_id_offset + PVD_copyright_file_id_size) +#define PVD_abstract_file_id_size 37 +#define PVD_bibliographic_file_id_offset (PVD_abstract_file_id_offset + PVD_abstract_file_id_size) +#define PVD_bibliographic_file_id_size 37 +#define PVD_creation_date_offset (PVD_bibliographic_file_id_offset + PVD_bibliographic_file_id_size) +#define PVD_creation_date_size 17 +#define PVD_modification_date_offset (PVD_creation_date_offset + PVD_creation_date_size) +#define PVD_modification_date_size 17 +#define PVD_expiration_date_offset (PVD_modification_date_offset + PVD_modification_date_size) +#define PVD_expiration_date_size 17 +#define PVD_effective_date_offset (PVD_expiration_date_offset + PVD_expiration_date_size) +#define PVD_effective_date_size 17 +#define PVD_file_structure_version_offset (PVD_effective_date_offset + PVD_effective_date_size) +#define PVD_file_structure_version_size 1 +#define PVD_reserved4_offset (PVD_file_structure_version_offset + PVD_file_structure_version_size) +#define PVD_reserved4_size 1 +#define PVD_application_data_offset (PVD_reserved4_offset + PVD_reserved4_size) +#define PVD_application_data_size 512 + +/* Structure of an on-disk directory record. */ +/* Note: ISO9660 stores each multi-byte integer twice, once in + * each byte order. The sizes here are the size of just one + * of the two integers. (This is why the offset of a field isn't + * the same as the offset+size of the previous field.) */ +#define DR_length_offset 0 +#define DR_length_size 1 +#define DR_ext_attr_length_offset 1 +#define DR_ext_attr_length_size 1 +#define DR_extent_offset 2 +#define DR_extent_size 4 +#define DR_size_offset 10 +#define DR_size_size 4 +#define DR_date_offset 18 +#define DR_date_size 7 +#define DR_flags_offset 25 +#define DR_flags_size 1 +#define DR_file_unit_size_offset 26 +#define DR_file_unit_size_size 1 +#define DR_interleave_offset 27 +#define DR_interleave_size 1 +#define DR_volume_sequence_number_offset 28 +#define DR_volume_sequence_number_size 2 +#define DR_name_len_offset 32 +#define DR_name_len_size 1 +#define DR_name_offset 33 + +/* + * Our private data. + */ + +/* In-memory storage for a directory record. */ +struct file_info { + struct file_info *parent; + int refcount; + uint64_t offset; /* Offset on disk. */ + uint64_t size; /* File size in bytes. */ + uint64_t ce_offset; /* Offset of CE */ + uint64_t ce_size; /* Size of CE */ + time_t mtime; /* File last modified time. */ + time_t atime; /* File last accessed time. */ + time_t ctime; /* File creation time. */ + uint64_t rdev; /* Device number */ + mode_t mode; + uid_t uid; + gid_t gid; + ino_t inode; + int nlinks; + char *name; /* Null-terminated filename. */ + struct archive_string symlink; +}; + + +struct iso9660 { + int magic; +#define ISO9660_MAGIC 0x96609660 + struct archive_string pathname; + char seenRockridge; /* Set true if RR extensions are used. */ + unsigned char suspOffset; + + uint64_t previous_offset; + uint64_t previous_size; + struct archive_string previous_pathname; + + /* TODO: Make this a heap for fast inserts and deletions. */ + struct file_info **pending_files; + int pending_files_allocated; + int pending_files_used; + + uint64_t current_position; + ssize_t logical_block_size; + + off_t entry_sparse_offset; + int64_t entry_bytes_remaining; +}; + +static void add_entry(struct iso9660 *iso9660, struct file_info *file); +static int archive_read_format_iso9660_bid(struct archive_read *); +static int archive_read_format_iso9660_cleanup(struct archive_read *); +static int archive_read_format_iso9660_read_data(struct archive_read *, + const void **, size_t *, off_t *); +static int archive_read_format_iso9660_read_data_skip(struct archive_read *); +static int archive_read_format_iso9660_read_header(struct archive_read *, + struct archive_entry *); +static const char *build_pathname(struct archive_string *, struct file_info *); +static void dump_isodirrec(FILE *, const unsigned char *isodirrec); +static time_t time_from_tm(struct tm *); +static time_t isodate17(const unsigned char *); +static time_t isodate7(const unsigned char *); +static int isPVD(struct iso9660 *, const unsigned char *); +static struct file_info *next_entry(struct iso9660 *); +static int next_entry_seek(struct archive_read *a, struct iso9660 *iso9660, + struct file_info **pfile); +static struct file_info * + parse_file_info(struct iso9660 *iso9660, + struct file_info *parent, const unsigned char *isodirrec); +static void parse_rockridge(struct iso9660 *iso9660, + struct file_info *file, const unsigned char *start, + const unsigned char *end); +static void release_file(struct iso9660 *, struct file_info *); +static unsigned toi(const void *p, int n); + +int +archive_read_support_format_iso9660(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct iso9660 *iso9660; + int r; + + iso9660 = (struct iso9660 *)malloc(sizeof(*iso9660)); + if (iso9660 == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate iso9660 data"); + return (ARCHIVE_FATAL); + } + memset(iso9660, 0, sizeof(*iso9660)); + iso9660->magic = ISO9660_MAGIC; + + r = __archive_read_register_format(a, + iso9660, + archive_read_format_iso9660_bid, + archive_read_format_iso9660_read_header, + archive_read_format_iso9660_read_data, + archive_read_format_iso9660_read_data_skip, + archive_read_format_iso9660_cleanup); + + if (r != ARCHIVE_OK) { + free(iso9660); + return (r); + } + return (ARCHIVE_OK); +} + + +static int +archive_read_format_iso9660_bid(struct archive_read *a) +{ + struct iso9660 *iso9660; + ssize_t bytes_read; + const void *h; + const unsigned char *p; + int bid; + + iso9660 = (struct iso9660 *)(a->format->data); + + /* + * Skip the first 32k (reserved area) and get the first + * 8 sectors of the volume descriptor table. Of course, + * if the I/O layer gives us more, we'll take it. + */ + bytes_read = (a->decompressor->read_ahead)(a, &h, 32768 + 8*2048); + if (bytes_read < 32768 + 8*2048) + return (-1); + p = (const unsigned char *)h; + + /* Skip the reserved area. */ + bytes_read -= 32768; + p += 32768; + + /* Check each volume descriptor to locate the PVD. */ + for (; bytes_read > 2048; bytes_read -= 2048, p += 2048) { + bid = isPVD(iso9660, p); + if (bid > 0) + return (bid); + if (*p == '\177') /* End-of-volume-descriptor marker. */ + break; + } + + /* We didn't find a valid PVD; return a bid of zero. */ + return (0); +} + +static int +isPVD(struct iso9660 *iso9660, const unsigned char *h) +{ + struct file_info *file; + + if (h[0] != 1) + return (0); + if (memcmp(h+1, "CD001", 5) != 0) + return (0); + + iso9660->logical_block_size = toi(h + PVD_logical_block_size_offset, 2); + + /* Store the root directory in the pending list. */ + file = parse_file_info(iso9660, NULL, h + PVD_root_directory_record_offset); + add_entry(iso9660, file); + return (48); +} + +static int +archive_read_format_iso9660_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct iso9660 *iso9660; + struct file_info *file; + ssize_t bytes_read; + int r; + + iso9660 = (struct iso9660 *)(a->format->data); + + if (!a->archive.archive_format) { + a->archive.archive_format = ARCHIVE_FORMAT_ISO9660; + a->archive.archive_format_name = "ISO9660"; + } + + /* Get the next entry that appears after the current offset. */ + r = next_entry_seek(a, iso9660, &file); + if (r != ARCHIVE_OK) + return (r); + + iso9660->entry_bytes_remaining = file->size; + iso9660->entry_sparse_offset = 0; /* Offset for sparse-file-aware clients. */ + + /* Set up the entry structure with information about this entry. */ + archive_entry_set_mode(entry, file->mode); + archive_entry_set_uid(entry, file->uid); + archive_entry_set_gid(entry, file->gid); + archive_entry_set_nlink(entry, file->nlinks); + archive_entry_set_ino(entry, file->inode); + archive_entry_set_mtime(entry, file->mtime, 0); + archive_entry_set_ctime(entry, file->ctime, 0); + archive_entry_set_atime(entry, file->atime, 0); + /* N.B.: Rock Ridge supports 64-bit device numbers. */ + archive_entry_set_rdev(entry, (dev_t)file->rdev); + archive_entry_set_size(entry, iso9660->entry_bytes_remaining); + archive_string_empty(&iso9660->pathname); + archive_entry_set_pathname(entry, + build_pathname(&iso9660->pathname, file)); + if (file->symlink.s != NULL) + archive_entry_copy_symlink(entry, file->symlink.s); + + /* If this entry points to the same data as the previous + * entry, convert this into a hardlink to that entry. + * But don't bother for zero-length files. */ + if (file->offset == iso9660->previous_offset + && file->size == iso9660->previous_size + && file->size > 0) { + archive_entry_set_hardlink(entry, + iso9660->previous_pathname.s); + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + release_file(iso9660, file); + return (ARCHIVE_OK); + } + + /* If the offset is before our current position, we can't + * seek backwards to extract it, so issue a warning. */ + if (file->offset < iso9660->current_position) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring out-of-order file"); + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + release_file(iso9660, file); + return (ARCHIVE_WARN); + } + + iso9660->previous_size = file->size; + iso9660->previous_offset = file->offset; + archive_strcpy(&iso9660->previous_pathname, iso9660->pathname.s); + + /* If this is a directory, read in all of the entries right now. */ + if (archive_entry_filetype(entry) == AE_IFDIR) { + while (iso9660->entry_bytes_remaining > 0) { + const void *block; + const unsigned char *p; + ssize_t step = iso9660->logical_block_size; + if (step > iso9660->entry_bytes_remaining) + step = iso9660->entry_bytes_remaining; + bytes_read = (a->decompressor->read_ahead)(a, &block, step); + if (bytes_read < step) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to read full block when scanning ISO9660 directory list"); + release_file(iso9660, file); + return (ARCHIVE_FATAL); + } + if (bytes_read > step) + bytes_read = step; + (a->decompressor->consume)(a, bytes_read); + iso9660->current_position += bytes_read; + iso9660->entry_bytes_remaining -= bytes_read; + for (p = (const unsigned char *)block; + *p != 0 && p < (const unsigned char *)block + bytes_read; + p += *p) { + struct file_info *child; + + /* Skip '.' entry. */ + if (*(p + DR_name_len_offset) == 1 + && *(p + DR_name_offset) == '\0') + continue; + /* Skip '..' entry. */ + if (*(p + DR_name_len_offset) == 1 + && *(p + DR_name_offset) == '\001') + continue; + child = parse_file_info(iso9660, file, p); + add_entry(iso9660, child); + if (iso9660->seenRockridge) { + a->archive.archive_format = + ARCHIVE_FORMAT_ISO9660_ROCKRIDGE; + a->archive.archive_format_name = + "ISO9660 with Rockridge extensions"; + } + } + } + } + + release_file(iso9660, file); + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_read_data_skip(struct archive_read *a) +{ + /* Because read_next_header always does an explicit skip + * to the next entry, we don't need to do anything here. */ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset) +{ + ssize_t bytes_read; + struct iso9660 *iso9660; + + iso9660 = (struct iso9660 *)(a->format->data); + if (iso9660->entry_bytes_remaining <= 0) { + *buff = NULL; + *size = 0; + *offset = iso9660->entry_sparse_offset; + return (ARCHIVE_EOF); + } + + bytes_read = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_read == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated input file"); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if (bytes_read > iso9660->entry_bytes_remaining) + bytes_read = iso9660->entry_bytes_remaining; + *size = bytes_read; + *offset = iso9660->entry_sparse_offset; + iso9660->entry_sparse_offset += bytes_read; + iso9660->entry_bytes_remaining -= bytes_read; + iso9660->current_position += bytes_read; + (a->decompressor->consume)(a, bytes_read); + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_cleanup(struct archive_read *a) +{ + struct iso9660 *iso9660; + struct file_info *file; + + iso9660 = (struct iso9660 *)(a->format->data); + while ((file = next_entry(iso9660)) != NULL) + release_file(iso9660, file); + archive_string_free(&iso9660->pathname); + archive_string_free(&iso9660->previous_pathname); + if (iso9660->pending_files) + free(iso9660->pending_files); + free(iso9660); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +/* + * This routine parses a single ISO directory record, makes sense + * of any extensions, and stores the result in memory. + */ +static struct file_info * +parse_file_info(struct iso9660 *iso9660, struct file_info *parent, + const unsigned char *isodirrec) +{ + struct file_info *file; + size_t name_len; + int flags; + + /* TODO: Sanity check that name_len doesn't exceed length, etc. */ + + /* Create a new file entry and copy data from the ISO dir record. */ + file = (struct file_info *)malloc(sizeof(*file)); + if (file == NULL) + return (NULL); + memset(file, 0, sizeof(*file)); + file->parent = parent; + if (parent != NULL) + parent->refcount++; + file->offset = toi(isodirrec + DR_extent_offset, DR_extent_size) + * iso9660->logical_block_size; + file->size = toi(isodirrec + DR_size_offset, DR_size_size); + file->mtime = isodate7(isodirrec + DR_date_offset); + file->ctime = file->atime = file->mtime; + name_len = (size_t)*(const unsigned char *)(isodirrec + DR_name_len_offset); + file->name = (char *)malloc(name_len + 1); + if (file->name == NULL) { + free(file); + return (NULL); + } + memcpy(file->name, isodirrec + DR_name_offset, name_len); + file->name[name_len] = '\0'; + flags = *(isodirrec + DR_flags_offset); + if (flags & 0x02) + file->mode = AE_IFDIR | 0700; + else + file->mode = AE_IFREG | 0400; + + /* Rockridge extensions overwrite information from above. */ + { + const unsigned char *rr_start, *rr_end; + rr_end = (const unsigned char *)isodirrec + + *(isodirrec + DR_length_offset); + rr_start = (const unsigned char *)(isodirrec + DR_name_offset + + name_len); + if ((name_len & 1) == 0) + rr_start++; + rr_start += iso9660->suspOffset; + parse_rockridge(iso9660, file, rr_start, rr_end); + } + + /* DEBUGGING: Warn about attributes I don't yet fully support. */ + if ((flags & ~0x02) != 0) { + fprintf(stderr, "\n ** Unrecognized flag: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (toi(isodirrec + DR_volume_sequence_number_offset, 2) != 1) { + fprintf(stderr, "\n ** Unrecognized sequence number: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (*(isodirrec + DR_file_unit_size_offset) != 0) { + fprintf(stderr, "\n ** Unexpected file unit size: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (*(isodirrec + DR_interleave_offset) != 0) { + fprintf(stderr, "\n ** Unexpected interleave: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (*(isodirrec + DR_ext_attr_length_offset) != 0) { + fprintf(stderr, "\n ** Unexpected extended attribute length: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } + + return (file); +} + +static void +add_entry(struct iso9660 *iso9660, struct file_info *file) +{ + /* Expand our pending files list as necessary. */ + if (iso9660->pending_files_used >= iso9660->pending_files_allocated) { + struct file_info **new_pending_files; + int new_size = iso9660->pending_files_allocated * 2; + + if (new_size < 1024) + new_size = 1024; + new_pending_files = (struct file_info **)malloc(new_size * sizeof(new_pending_files[0])); + if (new_pending_files == NULL) + __archive_errx(1, "Out of memory"); + memcpy(new_pending_files, iso9660->pending_files, + iso9660->pending_files_allocated * sizeof(new_pending_files[0])); + if (iso9660->pending_files != NULL) + free(iso9660->pending_files); + iso9660->pending_files = new_pending_files; + iso9660->pending_files_allocated = new_size; + } + + iso9660->pending_files[iso9660->pending_files_used++] = file; +} + +static void +parse_rockridge(struct iso9660 *iso9660, struct file_info *file, + const unsigned char *p, const unsigned char *end) +{ + (void)iso9660; /* UNUSED */ + + while (p + 4 < end /* Enough space for another entry. */ + && p[0] >= 'A' && p[0] <= 'Z' /* Sanity-check 1st char of name. */ + && p[1] >= 'A' && p[1] <= 'Z' /* Sanity-check 2nd char of name. */ + && p + p[2] <= end) { /* Sanity-check length. */ + const unsigned char *data = p + 4; + int data_length = p[2] - 4; + int version = p[3]; + + /* + * Yes, each 'if' here does test p[0] again. + * Otherwise, the fall-through handling to catch + * unsupported extensions doesn't work. + */ + switch(p[0]) { + case 'C': + if (p[0] == 'C' && p[1] == 'E' && version == 1) { + /* + * CE extension comprises: + * 8 byte sector containing extension + * 8 byte offset w/in above sector + * 8 byte length of continuation + */ + file->ce_offset = toi(data, 4) + * iso9660->logical_block_size + + toi(data + 8, 4); + file->ce_size = toi(data + 16, 4); + break; + } + /* FALLTHROUGH */ + case 'N': + if (p[0] == 'N' && p[1] == 'M' && version == 1 + && *data == 0) { + /* NM extension with flag byte == 0 */ + /* + * NM extension comprises: + * one byte flag + * rest is long name + */ + /* TODO: Obey flags. */ + char *old_name = file->name; + + data++; /* Skip flag byte. */ + data_length--; + file->name = (char *)malloc(data_length + 1); + if (file->name != NULL) { + free(old_name); + memcpy(file->name, data, data_length); + file->name[data_length] = '\0'; + } else + file->name = old_name; + break; + } + /* FALLTHROUGH */ + case 'P': + if (p[0] == 'P' && p[1] == 'D' && version == 1) { + /* + * PD extension is padding; + * contents are always ignored. + */ + break; + } + if (p[0] == 'P' && p[1] == 'N' && version == 1) { + if (data_length == 16) { + file->rdev = toi(data,4); + file->rdev <<= 32; + file->rdev |= toi(data + 8, 4); + } + break; + } + if (p[0] == 'P' && p[1] == 'X' && version == 1) { + /* + * PX extension comprises: + * 8 bytes for mode, + * 8 bytes for nlinks, + * 8 bytes for uid, + * 8 bytes for gid, + * 8 bytes for inode. + */ + if (data_length == 32) { + file->mode = toi(data, 4); + file->nlinks = toi(data + 8, 4); + file->uid = toi(data + 16, 4); + file->gid = toi(data + 24, 4); + file->inode = toi(data + 32, 4); + } + break; + } + /* FALLTHROUGH */ + case 'R': + if (p[0] == 'R' && p[1] == 'R' && version == 1) { + iso9660->seenRockridge = 1; + /* + * RR extension comprises: + * one byte flag value + */ + /* TODO: Handle RR extension. */ + break; + } + /* FALLTHROUGH */ + case 'S': + if (p[0] == 'S' && p[1] == 'L' && version == 1 + && *data == 0) { + int cont = 1; + /* SL extension with flags == 0 */ + /* TODO: handle non-zero flag values. */ + data++; /* Skip flag byte. */ + data_length--; + while (data_length > 0) { + unsigned char flag = *data++; + unsigned char nlen = *data++; + data_length -= 2; + + if (cont == 0) + archive_strcat(&file->symlink, "/"); + cont = 0; + + switch(flag) { + case 0x01: /* Continue */ + archive_strncat(&file->symlink, + (const char *)data, nlen); + cont = 1; + break; + case 0x02: /* Current */ + archive_strcat(&file->symlink, "."); + break; + case 0x04: /* Parent */ + archive_strcat(&file->symlink, ".."); + break; + case 0x08: /* Root */ + case 0x10: /* Volume root */ + archive_string_empty(&file->symlink); + break; + case 0x20: /* Hostname */ + archive_strcat(&file->symlink, "hostname"); + break; + case 0: + archive_strncat(&file->symlink, + (const char *)data, nlen); + break; + default: + /* TODO: issue a warning ? */ + break; + } + data += nlen; + data_length -= nlen; + } + break; + } + if (p[0] == 'S' && p[1] == 'P' + && version == 1 && data_length == 7 + && data[0] == (unsigned char)'\xbe' + && data[1] == (unsigned char)'\xef') { + /* + * SP extension stores the suspOffset + * (Number of bytes to skip between + * filename and SUSP records.) + * It is mandatory by the SUSP standard + * (IEEE 1281). + * + * It allows SUSP to coexist with + * non-SUSP uses of the System + * Use Area by placing non-SUSP data + * before SUSP data. + * + * TODO: Add a check for 'SP' in + * first directory entry, disable all SUSP + * processing if not found. + */ + iso9660->suspOffset = data[2]; + break; + } + if (p[0] == 'S' && p[1] == 'T' + && data_length == 0 && version == 1) { + /* + * ST extension marks end of this + * block of SUSP entries. + * + * It allows SUSP to coexist with + * non-SUSP uses of the System + * Use Area by placing non-SUSP data + * after SUSP data. + */ + return; + } + case 'T': + if (p[0] == 'T' && p[1] == 'F' && version == 1) { + char flag = data[0]; + /* + * TF extension comprises: + * one byte flag + * create time (optional) + * modify time (optional) + * access time (optional) + * attribute time (optional) + * Time format and presence of fields + * is controlled by flag bits. + */ + data++; + if (flag & 0x80) { + /* Use 17-byte time format. */ + if (flag & 1) /* Create time. */ + data += 17; + if (flag & 2) { /* Modify time. */ + file->mtime = isodate17(data); + data += 17; + } + if (flag & 4) { /* Access time. */ + file->atime = isodate17(data); + data += 17; + } + if (flag & 8) { /* Attribute time. */ + file->ctime = isodate17(data); + data += 17; + } + } else { + /* Use 7-byte time format. */ + if (flag & 1) /* Create time. */ + data += 7; + if (flag & 2) { /* Modify time. */ + file->mtime = isodate7(data); + data += 7; + } + if (flag & 4) { /* Access time. */ + file->atime = isodate7(data); + data += 7; + } + if (flag & 8) { /* Attribute time. */ + file->ctime = isodate7(data); + data += 7; + } + } + break; + } + /* FALLTHROUGH */ + default: + /* The FALLTHROUGHs above leave us here for + * any unsupported extension. */ + { + const unsigned char *t; + fprintf(stderr, "\nUnsupported RRIP extension for %s\n", file->name); + fprintf(stderr, " %c%c(%d):", p[0], p[1], data_length); + for (t = data; t < data + data_length && t < data + 16; t++) + fprintf(stderr, " %02x", *t); + fprintf(stderr, "\n"); + } + } + + + + p += p[2]; + } +} + +static void +release_file(struct iso9660 *iso9660, struct file_info *file) +{ + struct file_info *parent; + + if (file->refcount == 0) { + parent = file->parent; + if (file->name) + free(file->name); + archive_string_free(&file->symlink); + free(file); + if (parent != NULL) { + parent->refcount--; + release_file(iso9660, parent); + } + } +} + +static int +next_entry_seek(struct archive_read *a, struct iso9660 *iso9660, + struct file_info **pfile) +{ + struct file_info *file; + uint64_t offset; + + *pfile = NULL; + for (;;) { + *pfile = file = next_entry(iso9660); + if (file == NULL) + return (ARCHIVE_EOF); + + /* CE area precedes actual file data? Ignore it. */ + if (file->ce_offset > file->offset) { +fprintf(stderr, " *** Discarding CE data.\n"); + file->ce_offset = 0; + file->ce_size = 0; + } + + /* If CE exists, find and read it now. */ + if (file->ce_offset > 0) + offset = file->ce_offset; + else + offset = file->offset; + + /* Seek forward to the start of the entry. */ + if (iso9660->current_position < offset) { + off_t step = offset - iso9660->current_position; + off_t bytes_read; + bytes_read = (a->decompressor->skip)(a, step); + if (bytes_read < 0) + return (bytes_read); + iso9660->current_position = offset; + } + + /* We found body of file; handle it now. */ + if (offset == file->offset) + return (ARCHIVE_OK); + + /* Found CE? Process it and push the file back onto list. */ + if (offset == file->ce_offset) { + const void *p; + ssize_t size = file->ce_size; + ssize_t bytes_read; + const unsigned char *rr_start; + + file->ce_offset = 0; + file->ce_size = 0; + bytes_read = (a->decompressor->read_ahead)(a, &p, size); + if (bytes_read > size) + bytes_read = size; + rr_start = (const unsigned char *)p; + parse_rockridge(iso9660, file, rr_start, + rr_start + bytes_read); + (a->decompressor->consume)(a, bytes_read); + iso9660->current_position += bytes_read; + add_entry(iso9660, file); + } + } +} + +static struct file_info * +next_entry(struct iso9660 *iso9660) +{ + int least_index; + uint64_t least_end_offset; + int i; + struct file_info *r; + + if (iso9660->pending_files_used < 1) + return (NULL); + + /* Assume the first file in the list is the earliest on disk. */ + least_index = 0; + least_end_offset = iso9660->pending_files[0]->offset + + iso9660->pending_files[0]->size; + + /* Now, try to find an earlier one. */ + for (i = 0; i < iso9660->pending_files_used; i++) { + /* Use the position of the file *end* as our comparison. */ + uint64_t end_offset = iso9660->pending_files[i]->offset + + iso9660->pending_files[i]->size; + if (iso9660->pending_files[i]->ce_offset > 0 + && iso9660->pending_files[i]->ce_offset < iso9660->pending_files[i]->offset) + end_offset = iso9660->pending_files[i]->ce_offset + + iso9660->pending_files[i]->ce_size; + if (least_end_offset > end_offset) { + least_index = i; + least_end_offset = end_offset; + } + } + r = iso9660->pending_files[least_index]; + iso9660->pending_files[least_index] + = iso9660->pending_files[--iso9660->pending_files_used]; + return (r); +} + +static unsigned int +toi(const void *p, int n) +{ + const unsigned char *v = (const unsigned char *)p; + if (n > 1) + return v[0] + 256 * toi(v + 1, n - 1); + if (n == 1) + return v[0]; + return (0); +} + +static time_t +isodate7(const unsigned char *v) +{ + struct tm tm; + int offset; + memset(&tm, 0, sizeof(tm)); + tm.tm_year = v[0]; + tm.tm_mon = v[1] - 1; + tm.tm_mday = v[2]; + tm.tm_hour = v[3]; + tm.tm_min = v[4]; + tm.tm_sec = v[5]; + /* v[6] is the signed timezone offset, in 1/4-hour increments. */ + offset = ((const signed char *)v)[6]; + if (offset > -48 && offset < 52) { + tm.tm_hour -= offset / 4; + tm.tm_min -= (offset % 4) * 15; + } + return (time_from_tm(&tm)); +} + +static time_t +isodate17(const unsigned char *v) +{ + struct tm tm; + int offset; + memset(&tm, 0, sizeof(tm)); + tm.tm_year = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + + (v[2] - '0') * 10 + (v[3] - '0') + - 1900; + tm.tm_mon = (v[4] - '0') * 10 + (v[5] - '0'); + tm.tm_mday = (v[6] - '0') * 10 + (v[7] - '0'); + tm.tm_hour = (v[8] - '0') * 10 + (v[9] - '0'); + tm.tm_min = (v[10] - '0') * 10 + (v[11] - '0'); + tm.tm_sec = (v[12] - '0') * 10 + (v[13] - '0'); + /* v[16] is the signed timezone offset, in 1/4-hour increments. */ + offset = ((const signed char *)v)[16]; + if (offset > -48 && offset < 52) { + tm.tm_hour -= offset / 4; + tm.tm_min -= (offset % 4) * 15; + } + return (time_from_tm(&tm)); +} + +/* + * timegm() converts a struct tm to a time_t, except it isn't standard, + * so I provide my own function here that (ideally) is just a wrapper + * for timegm(). + */ +static time_t +time_from_tm(struct tm *t) +{ +#if HAVE_TIMEGM + return (timegm(t)); +#elif HAVE_STRUCT_TM_TM_GMTOFF + /* + * Unfortunately, timegm() isn't standard. The standard + * mktime() function is a close match, except that it uses + * local timezone instead of GMT. You can compensate for + * this by adding the timezone and DST offsets back in, at + * the cost of two calls to mktime(). + */ + mktime(t); /* Normalize the time and get the TZ offset. */ + t->tm_sec += t->tm_gmtoff; /* Try to adjust for the timezone and DST.*/ + if (t->tm_isdst) + t->tm_hour -= 1; + return (mktime(t)); /* Re-convert. */ +#elif defined(HAVE_SETENV) && defined(HAVE_UNSETENV) && defined(HAVE_TZSET) + /* No timegm() and no tm_gmtoff, let's try forcing mktime() to UTC. */ + time_t ret; + char *tz; + + /* Reset the timezone, remember the old one. */ + tz = getenv("TZ"); + setenv("TZ", "UTC 0", 1); + tzset(); + + ret = mktime(t); + + /* Restore the previous timezone. */ + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +#else + /* <sigh> We have no choice but to use localtime instead of UTC. */ + return (mktime(t)); +#endif +} + +static const char * +build_pathname(struct archive_string *as, struct file_info *file) +{ + if (file->parent != NULL && file->parent->name[0] != '\0') { + build_pathname(as, file->parent); + archive_strcat(as, "/"); + } + if (file->name[0] == '\0') + archive_strcat(as, "."); + else + archive_strcat(as, file->name); + return (as->s); +} + +static void +dump_isodirrec(FILE *out, const unsigned char *isodirrec) +{ + fprintf(out, " l %d,", + toi(isodirrec + DR_length_offset, DR_length_size)); + fprintf(out, " a %d,", + toi(isodirrec + DR_ext_attr_length_offset, DR_ext_attr_length_size)); + fprintf(out, " ext 0x%x,", + toi(isodirrec + DR_extent_offset, DR_extent_size)); + fprintf(out, " s %d,", + toi(isodirrec + DR_size_offset, DR_extent_size)); + fprintf(out, " f 0x%02x,", + toi(isodirrec + DR_flags_offset, DR_flags_size)); + fprintf(out, " u %d,", + toi(isodirrec + DR_file_unit_size_offset, DR_file_unit_size_size)); + fprintf(out, " ilv %d,", + toi(isodirrec + DR_interleave_offset, DR_interleave_size)); + fprintf(out, " seq %d,", + toi(isodirrec + DR_volume_sequence_number_offset, DR_volume_sequence_number_size)); + fprintf(out, " nl %d:", + toi(isodirrec + DR_name_len_offset, DR_name_len_size)); + fprintf(out, " `%.*s'", + toi(isodirrec + DR_name_len_offset, DR_name_len_size), isodirrec + DR_name_offset); +} diff --git a/libarchive/archive_read_support_format_mtree.c b/libarchive/archive_read_support_format_mtree.c new file mode 100644 index 00000000..f1980e6b --- /dev/null +++ b/libarchive/archive_read_support_format_mtree.c @@ -0,0 +1,777 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_mtree.c,v 1.4 2008/03/15 11:02:47 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <stddef.h> +/* #include <stdint.h> */ /* See archive_platform.h */ +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_string.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct mtree_entry { + struct mtree_entry *next; + char *name; + char *option_start; + char *option_end; + char full; + char used; +}; + +struct mtree { + struct archive_string line; + size_t buffsize; + char *buff; + off_t offset; + int fd; + int filetype; + int archive_format; + const char *archive_format_name; + struct mtree_entry *entries; + struct mtree_entry *this_entry; + struct archive_string current_dir; + struct archive_string contents_name; + + off_t cur_size, cur_offset; +}; + +static int cleanup(struct archive_read *); +static int mtree_bid(struct archive_read *); +static int parse_file(struct archive_read *, struct archive_entry *, + struct mtree *, struct mtree_entry *); +static void parse_escapes(char *, struct mtree_entry *); +static int parse_line(struct archive_read *, struct archive_entry *, + struct mtree *, struct mtree_entry *); +static int parse_keyword(struct archive_read *, struct mtree *, + struct archive_entry *, char *, char *); +static int read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset); +static ssize_t readline(struct archive_read *, struct mtree *, char **, ssize_t); +static int skip(struct archive_read *a); +static int read_header(struct archive_read *, + struct archive_entry *); +static int64_t mtree_atol10(char **); +static int64_t mtree_atol8(char **); + +int +archive_read_support_format_mtree(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct mtree *mtree; + int r; + + mtree = (struct mtree *)malloc(sizeof(*mtree)); + if (mtree == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate mtree data"); + return (ARCHIVE_FATAL); + } + memset(mtree, 0, sizeof(*mtree)); + mtree->fd = -1; + + r = __archive_read_register_format(a, mtree, + mtree_bid, read_header, read_data, skip, cleanup); + + if (r != ARCHIVE_OK) + free(mtree); + return (ARCHIVE_OK); +} + +static int +cleanup(struct archive_read *a) +{ + struct mtree *mtree; + struct mtree_entry *p, *q; + + mtree = (struct mtree *)(a->format->data); + p = mtree->entries; + while (p != NULL) { + q = p->next; + free(p->name); + /* + * Note: option_start, option_end are pointers into + * the block that p->name points to. So we should + * not try to free them! + */ + free(p); + p = q; + } + archive_string_free(&mtree->line); + archive_string_free(&mtree->current_dir); + archive_string_free(&mtree->contents_name); + free(mtree->buff); + free(mtree); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + + +static int +mtree_bid(struct archive_read *a) +{ + struct mtree *mtree; + ssize_t bytes_read; + const void *h; + const char *signature = "#mtree"; + const char *p; + int bid; + + mtree = (struct mtree *)(a->format->data); + + /* Now let's look at the actual header and see if it matches. */ + bytes_read = (a->decompressor->read_ahead)(a, &h, strlen(signature)); + + if (bytes_read <= 0) + return (bytes_read); + + p = h; + bid = 0; + while (bytes_read > 0 && *signature != '\0') { + if (*p != *signature) + return (bid = 0); + bid += 8; + p++; + signature++; + bytes_read--; + } + return (bid); +} + +/* + * The extended mtree format permits multiple lines specifying + * attributes for each file. Practically speaking, that means we have + * to read the entire mtree file into memory up front. + */ +static int +read_mtree(struct archive_read *a, struct mtree *mtree) +{ + ssize_t len; + char *p; + struct mtree_entry *mentry; + struct mtree_entry *last_mentry = NULL; + + mtree->archive_format = ARCHIVE_FORMAT_MTREE_V1; + mtree->archive_format_name = "mtree"; + + for (;;) { + len = readline(a, mtree, &p, 256); + if (len == 0) { + mtree->this_entry = mtree->entries; + return (ARCHIVE_OK); + } + if (len < 0) + return (len); + /* Leading whitespace is never significant, ignore it. */ + while (*p == ' ' || *p == '\t') { + ++p; + --len; + } + /* Skip content lines and blank lines. */ + if (*p == '#') + continue; + if (*p == '\r' || *p == '\n' || *p == '\0') + continue; + mentry = malloc(sizeof(*mentry)); + if (mentry == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + memset(mentry, 0, sizeof(*mentry)); + /* Add this entry to list. */ + if (last_mentry == NULL) { + last_mentry = mtree->entries = mentry; + } else { + last_mentry->next = mentry; + } + last_mentry = mentry; + + /* Copy line over onto heap. */ + mentry->name = malloc(len + 1); + if (mentry->name == NULL) { + free(mentry); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + strcpy(mentry->name, p); + mentry->option_end = mentry->name + len; + /* Find end of name. */ + p = mentry->name; + while (*p != ' ' && *p != '\n' && *p != '\0') + ++p; + *p++ = '\0'; + parse_escapes(mentry->name, mentry); + /* Find start of options and record it. */ + while (p < mentry->option_end && (*p == ' ' || *p == '\t')) + ++p; + mentry->option_start = p; + /* Null terminate each separate option. */ + while (++p < mentry->option_end) + if (*p == ' ' || *p == '\t' || *p == '\n') + *p = '\0'; + } +} + +/* + * Read in the entire mtree file into memory on the first request. + * Then use the next unused file to satisfy each header request. + */ +static int +read_header(struct archive_read *a, struct archive_entry *entry) +{ + struct mtree *mtree; + char *p; + int r; + + mtree = (struct mtree *)(a->format->data); + + if (mtree->fd >= 0) { + close(mtree->fd); + mtree->fd = -1; + } + + if (mtree->entries == NULL) { + r = read_mtree(a, mtree); + if (r != ARCHIVE_OK) + return (r); + } + + a->archive.archive_format = mtree->archive_format; + a->archive.archive_format_name = mtree->archive_format_name; + + for (;;) { + if (mtree->this_entry == NULL) + return (ARCHIVE_EOF); + if (strcmp(mtree->this_entry->name, "..") == 0) { + mtree->this_entry->used = 1; + if (archive_strlen(&mtree->current_dir) > 0) { + /* Roll back current path. */ + p = mtree->current_dir.s + + mtree->current_dir.length - 1; + while (p >= mtree->current_dir.s && *p != '/') + --p; + if (p >= mtree->current_dir.s) + --p; + mtree->current_dir.length + = p - mtree->current_dir.s + 1; + } + } + if (!mtree->this_entry->used) { + r = parse_file(a, entry, mtree, mtree->this_entry); + return (r); + } + mtree->this_entry = mtree->this_entry->next; + } +} + +/* + * A single file can have multiple lines contribute specifications. + * Parse as many lines as necessary, then pull additional information + * from a backing file on disk as necessary. + */ +static int +parse_file(struct archive_read *a, struct archive_entry *entry, + struct mtree *mtree, struct mtree_entry *mentry) +{ + struct stat st; + struct mtree_entry *mp; + int r = ARCHIVE_OK, r1; + + mentry->used = 1; + + /* Initialize reasonable defaults. */ + mtree->filetype = AE_IFREG; + archive_entry_set_size(entry, 0); + + /* Parse options from this line. */ + r = parse_line(a, entry, mtree, mentry); + + if (mentry->full) { + archive_entry_copy_pathname(entry, mentry->name); + /* + * "Full" entries are allowed to have multiple lines + * and those lines aren't required to be adjacent. We + * don't support multiple lines for "relative" entries + * nor do we make any attempt to merge data from + * separate "relative" and "full" entries. (Merging + * "relative" and "full" entries would require dealing + * with pathname canonicalization, which is a very + * tricky subject.) + */ + for (mp = mentry->next; mp != NULL; mp = mp->next) { + if (mp->full && !mp->used + && strcmp(mentry->name, mp->name) == 0) { + /* Later lines override earlier ones. */ + mp->used = 1; + r1 = parse_line(a, entry, mtree, mp); + if (r1 < r) + r = r1; + } + } + } else { + /* + * Relative entries require us to construct + * the full path and possibly update the + * current directory. + */ + size_t n = archive_strlen(&mtree->current_dir); + if (n > 0) + archive_strcat(&mtree->current_dir, "/"); + archive_strcat(&mtree->current_dir, mentry->name); + archive_entry_copy_pathname(entry, mtree->current_dir.s); + if (archive_entry_filetype(entry) != AE_IFDIR) + mtree->current_dir.length = n; + } + + /* + * Try to open and stat the file to get the real size + * and other file info. It would be nice to avoid + * this here so that getting a listing of an mtree + * wouldn't require opening every referenced contents + * file. But then we wouldn't know the actual + * contents size, so I don't see a really viable way + * around this. (Also, we may want to someday pull + * other unspecified info from the contents file on + * disk.) + */ + mtree->fd = -1; + if (archive_strlen(&mtree->contents_name) > 0) { + mtree->fd = open(mtree->contents_name.s, + O_RDONLY | O_BINARY); + if (mtree->fd < 0) { + archive_set_error(&a->archive, errno, + "Can't open content=\"%s\"", + mtree->contents_name.s); + r = ARCHIVE_WARN; + } + } else if (archive_entry_filetype(entry) == AE_IFREG) { + mtree->fd = open(archive_entry_pathname(entry), + O_RDONLY | O_BINARY); + } + + /* + * If there is a contents file on disk, use that size; + * otherwise leave it as-is (it might have been set from + * the mtree size= keyword). + */ + if (mtree->fd >= 0) { + if (fstat(mtree->fd, &st) != 0) { + archive_set_error(&a->archive, errno, + "could not stat %s", + archive_entry_pathname(entry)); + r = ARCHIVE_WARN; + /* If we can't stat it, don't keep it open. */ + close(mtree->fd); + mtree->fd = -1; + } else if ((st.st_mode & S_IFMT) != S_IFREG) { + archive_set_error(&a->archive, errno, + "%s is not a regular file", + archive_entry_pathname(entry)); + r = ARCHIVE_WARN; + /* Don't hold a non-regular file open. */ + close(mtree->fd); + mtree->fd = -1; + } else { + archive_entry_set_size(entry, st.st_size); + archive_entry_set_ino(entry, st.st_ino); + archive_entry_set_dev(entry, st.st_dev); + archive_entry_set_nlink(entry, st.st_nlink); + } + } + mtree->cur_size = archive_entry_size(entry); + mtree->offset = 0; + + return r; +} + +/* + * Each line contains a sequence of keywords. + */ +static int +parse_line(struct archive_read *a, struct archive_entry *entry, + struct mtree *mtree, struct mtree_entry *mp) +{ + char *p, *q; + int r = ARCHIVE_OK, r1; + + p = mp->option_start; + while (p < mp->option_end) { + q = p + strlen(p); + r1 = parse_keyword(a, mtree, entry, p, q); + if (r1 < r) + r = r1; + p = q + 1; + } + return (r); +} + +/* + * Parse a single keyword and its value. + */ +static int +parse_keyword(struct archive_read *a, struct mtree *mtree, + struct archive_entry *entry, char *key, char *end) +{ + char *val; + + if (end == key) + return (ARCHIVE_OK); + if (*key == '\0') + return (ARCHIVE_OK); + + val = strchr(key, '='); + if (val == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Malformed attribute \"%s\" (%d)", key, key[0]); + return (ARCHIVE_WARN); + } + + *val = '\0'; + ++val; + + switch (key[0]) { + case 'c': + if (strcmp(key, "content") == 0 + || strcmp(key, "contents") == 0) { + parse_escapes(val, NULL); + archive_strcpy(&mtree->contents_name, val); + break; + } + case 'g': + if (strcmp(key, "gid") == 0) { + archive_entry_set_gid(entry, mtree_atol10(&val)); + break; + } + if (strcmp(key, "gname") == 0) { + archive_entry_copy_gname(entry, val); + break; + } + case 'l': + if (strcmp(key, "link") == 0) { + archive_entry_set_link(entry, val); + break; + } + case 'm': + if (strcmp(key, "mode") == 0) { + if (val[0] == '0') { + archive_entry_set_perm(entry, + mtree_atol8(&val)); + } else + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Symbolic mode \"%s\" unsupported", val); + break; + } + case 's': + if (strcmp(key, "size") == 0) { + archive_entry_set_size(entry, mtree_atol10(&val)); + break; + } + case 't': + if (strcmp(key, "type") == 0) { + switch (val[0]) { + case 'b': + if (strcmp(val, "block") == 0) { + mtree->filetype = AE_IFBLK; + break; + } + case 'c': + if (strcmp(val, "char") == 0) { + mtree->filetype = AE_IFCHR; + break; + } + case 'd': + if (strcmp(val, "dir") == 0) { + mtree->filetype = AE_IFDIR; + break; + } + case 'f': + if (strcmp(val, "fifo") == 0) { + mtree->filetype = AE_IFIFO; + break; + } + if (strcmp(val, "file") == 0) { + mtree->filetype = AE_IFREG; + break; + } + case 'l': + if (strcmp(val, "link") == 0) { + mtree->filetype = AE_IFLNK; + break; + } + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized file type \"%s\"", val); + return (ARCHIVE_WARN); + } + archive_entry_set_filetype(entry, mtree->filetype); + break; + } + if (strcmp(key, "time") == 0) { + archive_entry_set_mtime(entry, mtree_atol10(&val), 0); + break; + } + case 'u': + if (strcmp(key, "uid") == 0) { + archive_entry_set_uid(entry, mtree_atol10(&val)); + break; + } + if (strcmp(key, "uname") == 0) { + archive_entry_copy_uname(entry, val); + break; + } + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized key %s=%s", key, val); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +read_data(struct archive_read *a, const void **buff, size_t *size, off_t *offset) +{ + size_t bytes_to_read; + ssize_t bytes_read; + struct mtree *mtree; + + mtree = (struct mtree *)(a->format->data); + if (mtree->fd < 0) { + *buff = NULL; + *offset = 0; + *size = 0; + return (ARCHIVE_EOF); + } + if (mtree->buff == NULL) { + mtree->buffsize = 64 * 1024; + mtree->buff = malloc(mtree->buffsize); + if (mtree->buff == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + } + } + + *buff = mtree->buff; + *offset = mtree->offset; + if ((off_t)mtree->buffsize > mtree->cur_size - mtree->offset) + bytes_to_read = mtree->cur_size - mtree->offset; + else + bytes_to_read = mtree->buffsize; + bytes_read = read(mtree->fd, mtree->buff, bytes_to_read); + if (bytes_read < 0) { + archive_set_error(&a->archive, errno, "Can't read"); + return (ARCHIVE_WARN); + } + if (bytes_read == 0) { + *size = 0; + return (ARCHIVE_EOF); + } + mtree->offset += bytes_read; + *size = bytes_read; + return (ARCHIVE_OK); +} + +/* Skip does nothing except possibly close the contents file. */ +static int +skip(struct archive_read *a) +{ + struct mtree *mtree; + + mtree = (struct mtree *)(a->format->data); + if (mtree->fd >= 0) { + close(mtree->fd); + mtree->fd = -1; + } + return (ARCHIVE_OK); +} + +/* + * Since parsing octal escapes always makes strings shorter, + * we can always do this conversion in-place. + */ +static void +parse_escapes(char *src, struct mtree_entry *mentry) +{ + char *dest = src; + char c; + + while (*src != '\0') { + c = *src++; + if (c == '/' && mentry != NULL) + mentry->full = 1; + if (c == '\\') { + if (src[0] >= '0' && src[0] <= '3' + && src[1] >= '0' && src[1] <= '7' + && src[2] >= '0' && src[2] <= '7') { + c = (src[0] - '0') << 6; + c |= (src[1] - '0') << 3; + c |= (src[2] - '0'); + src += 3; + } + } + *dest++ = c; + } + *dest = '\0'; +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +mtree_atol8(char **p) +{ + int64_t l, limit, last_digit_limit; + int digit, base; + + base = 8; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + l = 0; + digit = **p - '0'; + while (digit >= 0 && digit < base) { + if (l>limit || (l == limit && digit > last_digit_limit)) { + l = INT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++(*p) - '0'; + } + return (l); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +mtree_atol10(char **p) +{ + int64_t l, limit, last_digit_limit; + int base, digit, sign; + + base = 10; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + if (**p == '-') { + sign = -1; + ++(*p); + } else + sign = 1; + + l = 0; + digit = **p - '0'; + while (digit >= 0 && digit < base) { + if (l > limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++(*p) - '0'; + } + return (sign < 0) ? -l : l; +} + +/* + * Returns length of line (including trailing newline) + * or negative on error. 'start' argument is updated to + * point to first character of line. + */ +static ssize_t +readline(struct archive_read *a, struct mtree *mtree, char **start, ssize_t limit) +{ + ssize_t bytes_read; + ssize_t total_size = 0; + const void *t; + const char *s; + void *p; + + /* Accumulate line in a line buffer. */ + for (;;) { + /* Read some more. */ + bytes_read = (a->decompressor->read_ahead)(a, &t, 1); + if (bytes_read == 0) + return (0); + if (bytes_read < 0) + return (ARCHIVE_FATAL); + s = t; /* Start of line? */ + p = memchr(t, '\n', bytes_read); + /* If we found '\n', trim the read. */ + if (p != NULL) { + bytes_read = 1 + ((const char *)p) - s; + } + if (total_size + bytes_read + 1 > limit) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Line too long"); + return (ARCHIVE_FATAL); + } + if (archive_string_ensure(&mtree->line, + total_size + bytes_read + 1) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate working buffer"); + return (ARCHIVE_FATAL); + } + memcpy(mtree->line.s + total_size, t, bytes_read); + (a->decompressor->consume)(a, bytes_read); + total_size += bytes_read; + /* Null terminate. */ + mtree->line.s[total_size] = '\0'; + /* If we found '\n', clean up and return. */ + if (p != NULL) { + *start = mtree->line.s; + return (total_size); + } + } +} diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c new file mode 100644 index 00000000..76fda2d6 --- /dev/null +++ b/libarchive/archive_read_support_format_tar.c @@ -0,0 +1,2379 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_tar.c,v 1.67 2008/03/15 01:43:58 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stddef.h> +/* #include <stdint.h> */ /* See archive_platform.h */ +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +/* Obtain suitable wide-character manipulation functions. */ +#ifdef HAVE_WCHAR_H +#include <wchar.h> +#else +/* Good enough for equality testing, which is all we need. */ +static int wcscmp(const wchar_t *s1, const wchar_t *s2) +{ + int diff = *s1 - *s2; + while (*s1 && diff == 0) + diff = (int)*++s1 - (int)*++s2; + return diff; +} +/* Good enough for equality testing, which is all we need. */ +static int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n) +{ + int diff = *s1 - *s2; + while (*s1 && diff == 0 && n-- > 0) + diff = (int)*++s1 - (int)*++s2; + return diff; +} +static size_t wcslen(const wchar_t *s) +{ + const wchar_t *p = s; + while (*p) + p++; + return p - s; +} +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#define tar_min(a,b) ((a) < (b) ? (a) : (b)) + +/* + * Layout of POSIX 'ustar' tar header. + */ +struct archive_entry_header_ustar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; /* "old format" header ends here */ + char magic[6]; /* For POSIX: "ustar\0" */ + char version[2]; /* For POSIX: "00" */ + char uname[32]; + char gname[32]; + char rdevmajor[8]; + char rdevminor[8]; + char prefix[155]; +}; + +/* + * Structure of GNU tar header + */ +struct gnu_sparse { + char offset[12]; + char numbytes[12]; +}; + +struct archive_entry_header_gnutar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[8]; /* "ustar \0" (note blank/blank/null at end) */ + char uname[32]; + char gname[32]; + char rdevmajor[8]; + char rdevminor[8]; + char atime[12]; + char ctime[12]; + char offset[12]; + char longnames[4]; + char unused[1]; + struct gnu_sparse sparse[4]; + char isextended[1]; + char realsize[12]; + /* + * Old GNU format doesn't use POSIX 'prefix' field; they use + * the 'L' (longname) entry instead. + */ +}; + +/* + * Data specific to this format. + */ +struct sparse_block { + struct sparse_block *next; + off_t offset; + off_t remaining; +}; + +struct tar { + struct archive_string acl_text; + struct archive_string entry_pathname; + struct archive_string entry_linkpath; + struct archive_string entry_uname; + struct archive_string entry_gname; + struct archive_string longlink; + struct archive_string longname; + struct archive_string pax_header; + struct archive_string pax_global; + struct archive_string line; + int pax_hdrcharset_binary; + wchar_t *pax_entry; + size_t pax_entry_length; + int header_recursion_depth; + off_t entry_bytes_remaining; + off_t entry_offset; + off_t entry_padding; + off_t realsize; + struct sparse_block *sparse_list; + struct sparse_block *sparse_last; + int64_t sparse_offset; + int64_t sparse_numbytes; + int sparse_gnu_major; + int sparse_gnu_minor; + char sparse_gnu_pending; +}; + +static ssize_t UTF8_mbrtowc(wchar_t *pwc, const char *s, size_t n); +static int archive_block_is_null(const unsigned char *p); +static char *base64_decode(const char *, size_t, size_t *); +static void gnu_add_sparse_entry(struct tar *, + off_t offset, off_t remaining); +static void gnu_clear_sparse_list(struct tar *); +static int gnu_sparse_old_read(struct archive_read *, struct tar *, + const struct archive_entry_header_gnutar *header); +static void gnu_sparse_old_parse(struct tar *, + const struct gnu_sparse *sparse, int length); +static int gnu_sparse_01_parse(struct tar *, const char *); +static ssize_t gnu_sparse_10_read(struct archive_read *, struct tar *); +static int header_Solaris_ACL(struct archive_read *, struct tar *, + struct archive_entry *, const void *); +static int header_common(struct archive_read *, struct tar *, + struct archive_entry *, const void *); +static int header_old_tar(struct archive_read *, struct tar *, + struct archive_entry *, const void *); +static int header_pax_extensions(struct archive_read *, struct tar *, + struct archive_entry *, const void *); +static int header_pax_global(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int header_longlink(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int header_longname(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int header_volume(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int header_ustar(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int header_gnutar(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int archive_read_format_tar_bid(struct archive_read *); +static int archive_read_format_tar_cleanup(struct archive_read *); +static int archive_read_format_tar_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset); +static int archive_read_format_tar_skip(struct archive_read *a); +static int archive_read_format_tar_read_header(struct archive_read *, + struct archive_entry *); +static int checksum(struct archive_read *, const void *); +static int pax_attribute(struct tar *, struct archive_entry *, + char *key, char *value); +static int pax_header(struct archive_read *, struct tar *, + struct archive_entry *, char *attr); +static void pax_time(const char *, int64_t *sec, long *nanos); +static ssize_t readline(struct archive_read *, struct tar *, const char **, + ssize_t limit); +static int read_body_to_string(struct archive_read *, struct tar *, + struct archive_string *, const void *h); +static int64_t tar_atol(const char *, unsigned); +static int64_t tar_atol10(const char *, unsigned); +static int64_t tar_atol256(const char *, unsigned); +static int64_t tar_atol8(const char *, unsigned); +static int tar_read_header(struct archive_read *, struct tar *, + struct archive_entry *); +static int tohex(int c); +static char *url_decode(const char *); +static wchar_t *utf8_decode(struct tar *, const char *, size_t length); + +int +archive_read_support_format_gnutar(struct archive *a) +{ + return (archive_read_support_format_tar(a)); +} + + +int +archive_read_support_format_tar(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct tar *tar; + int r; + + tar = (struct tar *)malloc(sizeof(*tar)); + if (tar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate tar data"); + return (ARCHIVE_FATAL); + } + memset(tar, 0, sizeof(*tar)); + + r = __archive_read_register_format(a, tar, + archive_read_format_tar_bid, + archive_read_format_tar_read_header, + archive_read_format_tar_read_data, + archive_read_format_tar_skip, + archive_read_format_tar_cleanup); + + if (r != ARCHIVE_OK) + free(tar); + return (ARCHIVE_OK); +} + +static int +archive_read_format_tar_cleanup(struct archive_read *a) +{ + struct tar *tar; + + tar = (struct tar *)(a->format->data); + gnu_clear_sparse_list(tar); + archive_string_free(&tar->acl_text); + archive_string_free(&tar->entry_pathname); + archive_string_free(&tar->entry_linkpath); + archive_string_free(&tar->entry_uname); + archive_string_free(&tar->entry_gname); + archive_string_free(&tar->line); + archive_string_free(&tar->pax_global); + archive_string_free(&tar->pax_header); + archive_string_free(&tar->longname); + archive_string_free(&tar->longlink); + free(tar->pax_entry); + free(tar); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + + +static int +archive_read_format_tar_bid(struct archive_read *a) +{ + int bid; + ssize_t bytes_read; + const void *h; + const struct archive_entry_header_ustar *header; + + bid = 0; + + /* Now let's look at the actual header and see if it matches. */ + if (a->decompressor->read_ahead != NULL) + bytes_read = (a->decompressor->read_ahead)(a, &h, 512); + else + bytes_read = 0; /* Empty file. */ + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read < 512) + return (0); + + /* If it's an end-of-archive mark, we can handle it. */ + if ((*(const char *)h) == 0 + && archive_block_is_null((const unsigned char *)h)) { + /* + * Usually, I bid the number of bits verified, but + * in this case, 4096 seems excessive so I picked 10 as + * an arbitrary but reasonable-seeming value. + */ + return (10); + } + + /* If it's not an end-of-archive mark, it must have a valid checksum.*/ + if (!checksum(a, h)) + return (0); + bid += 48; /* Checksum is usually 6 octal digits. */ + + header = (const struct archive_entry_header_ustar *)h; + + /* Recognize POSIX formats. */ + if ((memcmp(header->magic, "ustar\0", 6) == 0) + &&(memcmp(header->version, "00", 2)==0)) + bid += 56; + + /* Recognize GNU tar format. */ + if ((memcmp(header->magic, "ustar ", 6) == 0) + &&(memcmp(header->version, " \0", 2)==0)) + bid += 56; + + /* Type flag must be null, digit or A-Z, a-z. */ + if (header->typeflag[0] != 0 && + !( header->typeflag[0] >= '0' && header->typeflag[0] <= '9') && + !( header->typeflag[0] >= 'A' && header->typeflag[0] <= 'Z') && + !( header->typeflag[0] >= 'a' && header->typeflag[0] <= 'z') ) + return (0); + bid += 2; /* 6 bits of variation in an 8-bit field leaves 2 bits. */ + + /* Sanity check: Look at first byte of mode field. */ + switch (255 & (unsigned)header->mode[0]) { + case 0: case 255: + /* Base-256 value: No further verification possible! */ + break; + case ' ': /* Not recommended, but not illegal, either. */ + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + /* Octal Value. */ + /* TODO: Check format of remainder of this field. */ + break; + default: + /* Not a valid mode; bail out here. */ + return (0); + } + /* TODO: Sanity test uid/gid/size/mtime/rdevmajor/rdevminor fields. */ + + return (bid); +} + +/* + * The function invoked by archive_read_header(). This + * just sets up a few things and then calls the internal + * tar_read_header() function below. + */ +static int +archive_read_format_tar_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + /* + * When converting tar archives to cpio archives, it is + * essential that each distinct file have a distinct inode + * number. To simplify this, we keep a static count here to + * assign fake dev/inode numbers to each tar entry. Note that + * pax format archives may overwrite this with something more + * useful. + * + * Ideally, we would track every file read from the archive so + * that we could assign the same dev/ino pair to hardlinks, + * but the memory required to store a complete lookup table is + * probably not worthwhile just to support the relatively + * obscure tar->cpio conversion case. + */ + static int default_inode; + static int default_dev; + struct tar *tar; + struct sparse_block *sp; + const char *p; + int r; + size_t l; + + /* Assign default device/inode values. */ + archive_entry_set_dev(entry, 1 + default_dev); /* Don't use zero. */ + archive_entry_set_ino(entry, ++default_inode); /* Don't use zero. */ + /* Limit generated st_ino number to 16 bits. */ + if (default_inode >= 0xffff) { + ++default_dev; + default_inode = 0; + } + + tar = (struct tar *)(a->format->data); + tar->entry_offset = 0; + while (tar->sparse_list != NULL) { + sp = tar->sparse_list; + tar->sparse_list = sp->next; + free(sp); + } + tar->sparse_last = NULL; + tar->realsize = -1; /* Mark this as "unset" */ + + r = tar_read_header(a, tar, entry); + + /* + * "non-sparse" files are really just sparse files with + * a single block. + */ + if (tar->sparse_list == NULL) + gnu_add_sparse_entry(tar, 0, tar->entry_bytes_remaining); + + if (r == ARCHIVE_OK) { + /* + * "Regular" entry with trailing '/' is really + * directory: This is needed for certain old tar + * variants and even for some broken newer ones. + */ + p = archive_entry_pathname(entry); + l = strlen(p); + if (archive_entry_filetype(entry) == AE_IFREG + && p[l-1] == '/') + archive_entry_set_filetype(entry, AE_IFDIR); + } + return (r); +} + +static int +archive_read_format_tar_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset) +{ + ssize_t bytes_read; + struct tar *tar; + struct sparse_block *p; + + tar = (struct tar *)(a->format->data); + + if (tar->sparse_gnu_pending) { + if (tar->sparse_gnu_major == 1 && tar->sparse_gnu_minor == 0) { + tar->sparse_gnu_pending = 0; + /* Read initial sparse map. */ + bytes_read = gnu_sparse_10_read(a, tar); + tar->entry_bytes_remaining -= bytes_read; + if (bytes_read < 0) + return (bytes_read); + } else { + *size = 0; + *offset = 0; + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unrecognized GNU sparse file format"); + return (ARCHIVE_WARN); + } + tar->sparse_gnu_pending = 0; + } + + /* Remove exhausted entries from sparse list. */ + while (tar->sparse_list != NULL && + tar->sparse_list->remaining == 0) { + p = tar->sparse_list; + tar->sparse_list = p->next; + free(p); + } + + /* If we're at end of file, return EOF. */ + if (tar->sparse_list == NULL || tar->entry_bytes_remaining == 0) { + if ((a->decompressor->skip)(a, tar->entry_padding) < 0) + return (ARCHIVE_FATAL); + tar->entry_padding = 0; + *buff = NULL; + *size = 0; + *offset = tar->realsize; + return (ARCHIVE_EOF); + } + + bytes_read = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_read == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated tar archive"); + return (ARCHIVE_FATAL); + } + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read > tar->entry_bytes_remaining) + bytes_read = tar->entry_bytes_remaining; + /* Don't read more than is available in the + * current sparse block. */ + if (tar->sparse_list->remaining < bytes_read) + bytes_read = tar->sparse_list->remaining; + *size = bytes_read; + *offset = tar->sparse_list->offset; + tar->sparse_list->remaining -= bytes_read; + tar->sparse_list->offset += bytes_read; + tar->entry_bytes_remaining -= bytes_read; + (a->decompressor->consume)(a, bytes_read); + return (ARCHIVE_OK); +} + +static int +archive_read_format_tar_skip(struct archive_read *a) +{ + off_t bytes_skipped; + struct tar* tar; + + tar = (struct tar *)(a->format->data); + + /* + * Compression layer skip functions are required to either skip the + * length requested or fail, so we can rely upon the entire entry + * plus padding being skipped. + */ + bytes_skipped = (a->decompressor->skip)(a, tar->entry_bytes_remaining + + tar->entry_padding); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + tar->entry_bytes_remaining = 0; + tar->entry_padding = 0; + + /* Free the sparse list. */ + gnu_clear_sparse_list(tar); + + return (ARCHIVE_OK); +} + +/* + * This function recursively interprets all of the headers associated + * with a single entry. + */ +static int +tar_read_header(struct archive_read *a, struct tar *tar, + struct archive_entry *entry) +{ + ssize_t bytes; + int err; + const void *h; + const struct archive_entry_header_ustar *header; + + /* Read 512-byte header record */ + bytes = (a->decompressor->read_ahead)(a, &h, 512); + if (bytes < 0) + return (bytes); + if (bytes == 0) { + /* + * An archive that just ends without a proper + * end-of-archive marker. Yes, there are tar programs + * that do this; hold our nose and accept it. + */ + return (ARCHIVE_EOF); + } + if (bytes < 512) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive"); + return (ARCHIVE_FATAL); + } + (a->decompressor->consume)(a, 512); + + + /* Check for end-of-archive mark. */ + if (((*(const char *)h)==0) && archive_block_is_null((const unsigned char *)h)) { + /* Try to consume a second all-null record, as well. */ + bytes = (a->decompressor->read_ahead)(a, &h, 512); + if (bytes > 0) + (a->decompressor->consume)(a, bytes); + archive_set_error(&a->archive, 0, NULL); + if (a->archive.archive_format_name == NULL) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR; + a->archive.archive_format_name = "tar"; + } + return (ARCHIVE_EOF); + } + + /* + * Note: If the checksum fails and we return ARCHIVE_RETRY, + * then the client is likely to just retry. This is a very + * crude way to search for the next valid header! + * + * TODO: Improve this by implementing a real header scan. + */ + if (!checksum(a, h)) { + archive_set_error(&a->archive, EINVAL, "Damaged tar archive"); + return (ARCHIVE_RETRY); /* Retryable: Invalid header */ + } + + if (++tar->header_recursion_depth > 32) { + archive_set_error(&a->archive, EINVAL, "Too many special headers"); + return (ARCHIVE_WARN); + } + + /* Determine the format variant. */ + header = (const struct archive_entry_header_ustar *)h; + switch(header->typeflag[0]) { + case 'A': /* Solaris tar ACL */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "Solaris tar"; + err = header_Solaris_ACL(a, tar, entry, h); + break; + case 'g': /* POSIX-standard 'g' header. */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange format"; + err = header_pax_global(a, tar, entry, h); + break; + case 'K': /* Long link name (GNU tar, others) */ + err = header_longlink(a, tar, entry, h); + break; + case 'L': /* Long filename (GNU tar, others) */ + err = header_longname(a, tar, entry, h); + break; + case 'V': /* GNU volume header */ + err = header_volume(a, tar, entry, h); + break; + case 'X': /* Used by SUN tar; same as 'x'. */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = + "POSIX pax interchange format (Sun variant)"; + err = header_pax_extensions(a, tar, entry, h); + break; + case 'x': /* POSIX-standard 'x' header. */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange format"; + err = header_pax_extensions(a, tar, entry, h); + break; + default: + if (memcmp(header->magic, "ustar \0", 8) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR; + a->archive.archive_format_name = "GNU tar format"; + err = header_gnutar(a, tar, entry, h); + } else if (memcmp(header->magic, "ustar", 5) == 0) { + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR; + a->archive.archive_format_name = "POSIX ustar format"; + } + err = header_ustar(a, tar, entry, h); + } else { + a->archive.archive_format = ARCHIVE_FORMAT_TAR; + a->archive.archive_format_name = "tar (non-POSIX)"; + err = header_old_tar(a, tar, entry, h); + } + } + --tar->header_recursion_depth; + /* We return warnings or success as-is. Anything else is fatal. */ + if (err == ARCHIVE_WARN || err == ARCHIVE_OK) + return (err); + if (err == ARCHIVE_EOF) + /* EOF when recursively reading a header is bad. */ + archive_set_error(&a->archive, EINVAL, "Damaged tar archive"); + return (ARCHIVE_FATAL); +} + +/* + * Return true if block checksum is correct. + */ +static int +checksum(struct archive_read *a, const void *h) +{ + const unsigned char *bytes; + const struct archive_entry_header_ustar *header; + int check, i, sum; + + (void)a; /* UNUSED */ + bytes = (const unsigned char *)h; + header = (const struct archive_entry_header_ustar *)h; + + /* + * Test the checksum. Note that POSIX specifies _unsigned_ + * bytes for this calculation. + */ + sum = tar_atol(header->checksum, sizeof(header->checksum)); + check = 0; + for (i = 0; i < 148; i++) + check += (unsigned char)bytes[i]; + for (; i < 156; i++) + check += 32; + for (; i < 512; i++) + check += (unsigned char)bytes[i]; + if (sum == check) + return (1); + + /* + * Repeat test with _signed_ bytes, just in case this archive + * was created by an old BSD, Solaris, or HP-UX tar with a + * broken checksum calculation. + */ + check = 0; + for (i = 0; i < 148; i++) + check += (signed char)bytes[i]; + for (; i < 156; i++) + check += 32; + for (; i < 512; i++) + check += (signed char)bytes[i]; + if (sum == check) + return (1); + + return (0); +} + +/* + * Return true if this block contains only nulls. + */ +static int +archive_block_is_null(const unsigned char *p) +{ + unsigned i; + + for (i = 0; i < 512; i++) + if (*p++) + return (0); + return (1); +} + +/* + * Interpret 'A' Solaris ACL header + */ +static int +header_Solaris_ACL(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + size_t size; + int err; + char *acl, *p; + wchar_t *wp; + + /* + * read_body_to_string adds a NUL terminator, but we need a little + * more to make sure that we don't overrun acl_text later. + */ + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + err = read_body_to_string(a, tar, &(tar->acl_text), h); + if (err != ARCHIVE_OK) + return (err); + err = tar_read_header(a, tar, entry); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + + /* Skip leading octal number. */ + /* XXX TODO: Parse the octal number and sanity-check it. */ + p = acl = tar->acl_text.s; + while (*p != '\0' && p < acl + size) + p++; + p++; + + if (p >= acl + size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed Solaris ACL attribute"); + return(ARCHIVE_WARN); + } + + /* Skip leading octal number. */ + size -= (p - acl); + acl = p; + + while (*p != '\0' && p < acl + size) + p++; + + wp = utf8_decode(tar, acl, p - acl); + err = __archive_entry_acl_parse_w(entry, wp, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + return (err); +} + +/* + * Interpret 'K' long linkname header. + */ +static int +header_longlink(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + int err; + + err = read_body_to_string(a, tar, &(tar->longlink), h); + if (err != ARCHIVE_OK) + return (err); + err = tar_read_header(a, tar, entry); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + /* Set symlink if symlink already set, else hardlink. */ + archive_entry_copy_link(entry, tar->longlink.s); + return (ARCHIVE_OK); +} + +/* + * Interpret 'L' long filename header. + */ +static int +header_longname(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + int err; + + err = read_body_to_string(a, tar, &(tar->longname), h); + if (err != ARCHIVE_OK) + return (err); + /* Read and parse "real" header, then override name. */ + err = tar_read_header(a, tar, entry); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + archive_entry_copy_pathname(entry, tar->longname.s); + return (ARCHIVE_OK); +} + + +/* + * Interpret 'V' GNU tar volume header. + */ +static int +header_volume(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + (void)h; + + /* Just skip this and read the next header. */ + return (tar_read_header(a, tar, entry)); +} + +/* + * Read body of an archive entry into an archive_string object. + */ +static int +read_body_to_string(struct archive_read *a, struct tar *tar, + struct archive_string *as, const void *h) +{ + off_t size, padded_size; + ssize_t bytes_read, bytes_to_copy; + const struct archive_entry_header_ustar *header; + const void *src; + char *dest; + + (void)tar; /* UNUSED */ + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + if ((size > 1048576) || (size < 0)) { + archive_set_error(&a->archive, EINVAL, + "Special header too large"); + return (ARCHIVE_FATAL); + } + + /* Fail if we can't make our buffer big enough. */ + if (archive_string_ensure(as, size+1) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory"); + return (ARCHIVE_FATAL); + } + + /* Read the body into the string. */ + padded_size = (size + 511) & ~ 511; + dest = as->s; + while (padded_size > 0) { + bytes_read = (a->decompressor->read_ahead)(a, &src, padded_size); + if (bytes_read == 0) + return (ARCHIVE_EOF); + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read > padded_size) + bytes_read = padded_size; + (a->decompressor->consume)(a, bytes_read); + bytes_to_copy = bytes_read; + if ((off_t)bytes_to_copy > size) + bytes_to_copy = (ssize_t)size; + memcpy(dest, src, bytes_to_copy); + dest += bytes_to_copy; + size -= bytes_to_copy; + padded_size -= bytes_read; + } + *dest = '\0'; + return (ARCHIVE_OK); +} + +/* + * Parse out common header elements. + * + * This would be the same as header_old_tar, except that the + * filename is handled slightly differently for old and POSIX + * entries (POSIX entries support a 'prefix'). This factoring + * allows header_old_tar and header_ustar + * to handle filenames differently, while still putting most of the + * common parsing into one place. + */ +static int +header_common(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + char tartype; + + (void)a; /* UNUSED */ + + header = (const struct archive_entry_header_ustar *)h; + if (header->linkname[0]) + archive_strncpy(&(tar->entry_linkpath), header->linkname, + sizeof(header->linkname)); + else + archive_string_empty(&(tar->entry_linkpath)); + + /* Parse out the numeric fields (all are octal) */ + archive_entry_set_mode(entry, tar_atol(header->mode, sizeof(header->mode))); + archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid))); + archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid))); + tar->entry_bytes_remaining = tar_atol(header->size, sizeof(header->size)); + tar->realsize = tar->entry_bytes_remaining; + archive_entry_set_size(entry, tar->entry_bytes_remaining); + archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0); + + /* Handle the tar type flag appropriately. */ + tartype = header->typeflag[0]; + + switch (tartype) { + case '1': /* Hard link */ + archive_entry_copy_hardlink(entry, tar->entry_linkpath.s); + /* + * The following may seem odd, but: Technically, tar + * does not store the file type for a "hard link" + * entry, only the fact that it is a hard link. So, I + * leave the type zero normally. But, pax interchange + * format allows hard links to have data, which + * implies that the underlying entry is a regular + * file. + */ + if (archive_entry_size(entry) > 0) + archive_entry_set_filetype(entry, AE_IFREG); + + /* + * A tricky point: Traditionally, tar readers have + * ignored the size field when reading hardlink + * entries, and some writers put non-zero sizes even + * though the body is empty. POSIX blessed this + * convention in the 1988 standard, but broke with + * this tradition in 2001 by permitting hardlink + * entries to store valid bodies in pax interchange + * format, but not in ustar format. Since there is no + * hard and fast way to distinguish pax interchange + * from earlier archives (the 'x' and 'g' entries are + * optional, after all), we need a heuristic. + */ + if (archive_entry_size(entry) == 0) { + /* If the size is already zero, we're done. */ + } else if (a->archive.archive_format + == ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) { + /* Definitely pax extended; must obey hardlink size. */ + } else if (a->archive.archive_format == ARCHIVE_FORMAT_TAR + || a->archive.archive_format == ARCHIVE_FORMAT_TAR_GNUTAR) + { + /* Old-style or GNU tar: we must ignore the size. */ + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + } else if (archive_read_format_tar_bid(a) > 50) { + /* + * We don't know if it's pax: If the bid + * function sees a valid ustar header + * immediately following, then let's ignore + * the hardlink size. + */ + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + } + /* + * TODO: There are still two cases I'd like to handle: + * = a ustar non-pax archive with a hardlink entry at + * end-of-archive. (Look for block of nulls following?) + * = a pax archive that has not seen any pax headers + * and has an entry which is a hardlink entry storing + * a body containing an uncompressed tar archive. + * The first is worth addressing; I don't see any reliable + * way to deal with the second possibility. + */ + break; + case '2': /* Symlink */ + archive_entry_set_filetype(entry, AE_IFLNK); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + archive_entry_copy_symlink(entry, tar->entry_linkpath.s); + break; + case '3': /* Character device */ + archive_entry_set_filetype(entry, AE_IFCHR); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case '4': /* Block device */ + archive_entry_set_filetype(entry, AE_IFBLK); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case '5': /* Dir */ + archive_entry_set_filetype(entry, AE_IFDIR); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case '6': /* FIFO device */ + archive_entry_set_filetype(entry, AE_IFIFO); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case 'D': /* GNU incremental directory type */ + /* + * No special handling is actually required here. + * It might be nice someday to preprocess the file list and + * provide it to the client, though. + */ + archive_entry_set_filetype(entry, AE_IFDIR); + break; + case 'M': /* GNU "Multi-volume" (remainder of file from last archive)*/ + /* + * As far as I can tell, this is just like a regular file + * entry, except that the contents should be _appended_ to + * the indicated file at the indicated offset. This may + * require some API work to fully support. + */ + break; + case 'N': /* Old GNU "long filename" entry. */ + /* The body of this entry is a script for renaming + * previously-extracted entries. Ugh. It will never + * be supported by libarchive. */ + archive_entry_set_filetype(entry, AE_IFREG); + break; + case 'S': /* GNU sparse files */ + /* + * Sparse files are really just regular files with + * sparse information in the extended area. + */ + /* FALLTHROUGH */ + default: /* Regular file and non-standard types */ + /* + * Per POSIX: non-recognized types should always be + * treated as regular files. + */ + archive_entry_set_filetype(entry, AE_IFREG); + break; + } + return (0); +} + +/* + * Parse out header elements for "old-style" tar archives. + */ +static int +header_old_tar(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + + /* Copy filename over (to ensure null termination). */ + header = (const struct archive_entry_header_ustar *)h; + archive_strncpy(&(tar->entry_pathname), header->name, sizeof(header->name)); + archive_entry_copy_pathname(entry, tar->entry_pathname.s); + + /* Grab rest of common fields */ + header_common(a, tar, entry, h); + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + return (0); +} + +/* + * Parse a file header for a pax extended archive entry. + */ +static int +header_pax_global(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + int err; + + err = read_body_to_string(a, tar, &(tar->pax_global), h); + if (err != ARCHIVE_OK) + return (err); + err = tar_read_header(a, tar, entry); + return (err); +} + +static int +header_pax_extensions(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + int err, err2; + + err = read_body_to_string(a, tar, &(tar->pax_header), h); + if (err != ARCHIVE_OK) + return (err); + + /* Parse the next header. */ + err = tar_read_header(a, tar, entry); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + + /* + * TODO: Parse global/default options into 'entry' struct here + * before handling file-specific options. + * + * This design (parse standard header, then overwrite with pax + * extended attribute data) usually works well, but isn't ideal; + * it would be better to parse the pax extended attributes first + * and then skip any fields in the standard header that were + * defined in the pax header. + */ + err2 = pax_header(a, tar, entry, tar->pax_header.s); + err = err_combine(err, err2); + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + return (err); +} + + +/* + * Parse a file header for a Posix "ustar" archive entry. This also + * handles "pax" or "extended ustar" entries. + */ +static int +header_ustar(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + struct archive_string *as; + + header = (const struct archive_entry_header_ustar *)h; + + /* Copy name into an internal buffer to ensure null-termination. */ + as = &(tar->entry_pathname); + if (header->prefix[0]) { + archive_strncpy(as, header->prefix, sizeof(header->prefix)); + if (as->s[archive_strlen(as) - 1] != '/') + archive_strappend_char(as, '/'); + archive_strncat(as, header->name, sizeof(header->name)); + } else + archive_strncpy(as, header->name, sizeof(header->name)); + + archive_entry_copy_pathname(entry, as->s); + + /* Handle rest of common fields. */ + header_common(a, tar, entry, h); + + /* Handle POSIX ustar fields. */ + archive_strncpy(&(tar->entry_uname), header->uname, + sizeof(header->uname)); + archive_entry_copy_uname(entry, tar->entry_uname.s); + + archive_strncpy(&(tar->entry_gname), header->gname, + sizeof(header->gname)); + archive_entry_copy_gname(entry, tar->entry_gname.s); + + /* Parse out device numbers only for char and block specials. */ + if (header->typeflag[0] == '3' || header->typeflag[0] == '4') { + archive_entry_set_rdevmajor(entry, + tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); + archive_entry_set_rdevminor(entry, + tar_atol(header->rdevminor, sizeof(header->rdevminor))); + } + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + + return (0); +} + + +/* + * Parse the pax extended attributes record. + * + * Returns non-zero if there's an error in the data. + */ +static int +pax_header(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, char *attr) +{ + size_t attr_length, l, line_length; + char *line, *p; + char *key, *value; + wchar_t *wp; + int err, err2; + + attr_length = strlen(attr); + tar->pax_hdrcharset_binary = 0; + archive_string_empty(&(tar->entry_gname)); + archive_string_empty(&(tar->entry_linkpath)); + archive_string_empty(&(tar->entry_pathname)); + archive_string_empty(&(tar->entry_uname)); + err = ARCHIVE_OK; + while (attr_length > 0) { + /* Parse decimal length field at start of line. */ + line_length = 0; + l = attr_length; + line = p = attr; /* Record start of line. */ + while (l>0) { + if (*p == ' ') { + p++; + l--; + break; + } + if (*p < '0' || *p > '9') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax extended attributes"); + return (ARCHIVE_WARN); + } + line_length *= 10; + line_length += *p - '0'; + if (line_length > 999999) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Rejecting pax extended attribute > 1MB"); + return (ARCHIVE_WARN); + } + p++; + l--; + } + + /* + * Parsed length must be no bigger than available data, + * at least 1, and the last character of the line must + * be '\n'. + */ + if (line_length > attr_length + || line_length < 1 + || attr[line_length - 1] != '\n') + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax extended attribute"); + return (ARCHIVE_WARN); + } + + /* Null-terminate the line. */ + attr[line_length - 1] = '\0'; + + /* Find end of key and null terminate it. */ + key = p; + if (key[0] == '=') + return (-1); + while (*p && *p != '=') + ++p; + if (*p == '\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid pax extended attributes"); + return (ARCHIVE_WARN); + } + *p = '\0'; + + /* Identify null-terminated 'value' portion. */ + value = p + 1; + + /* Identify this attribute and set it in the entry. */ + err2 = pax_attribute(tar, entry, key, value); + err = err_combine(err, err2); + + /* Skip to next line */ + attr += line_length; + attr_length -= line_length; + } + if (archive_strlen(&(tar->entry_gname)) > 0) { + value = tar->entry_gname.s; + if (tar->pax_hdrcharset_binary) + archive_entry_copy_gname(entry, value); + else { + wp = utf8_decode(tar, value, strlen(value)); + if (wp == NULL) { + archive_entry_copy_gname(entry, value); + if (err > ARCHIVE_WARN) + err = ARCHIVE_WARN; + } else + archive_entry_copy_gname_w(entry, wp); + } + } + if (archive_strlen(&(tar->entry_linkpath)) > 0) { + value = tar->entry_linkpath.s; + if (tar->pax_hdrcharset_binary) + archive_entry_copy_link(entry, value); + else { + wp = utf8_decode(tar, value, strlen(value)); + if (wp == NULL) { + archive_entry_copy_link(entry, value); + if (err > ARCHIVE_WARN) + err = ARCHIVE_WARN; + } else + archive_entry_copy_link_w(entry, wp); + } + } + if (archive_strlen(&(tar->entry_pathname)) > 0) { + value = tar->entry_pathname.s; + if (tar->pax_hdrcharset_binary) + archive_entry_copy_pathname(entry, value); + else { + wp = utf8_decode(tar, value, strlen(value)); + if (wp == NULL) { + archive_entry_copy_pathname(entry, value); + if (err > ARCHIVE_WARN) + err = ARCHIVE_WARN; + } else + archive_entry_copy_pathname_w(entry, wp); + } + } + if (archive_strlen(&(tar->entry_uname)) > 0) { + value = tar->entry_uname.s; + if (tar->pax_hdrcharset_binary) + archive_entry_copy_uname(entry, value); + else { + wp = utf8_decode(tar, value, strlen(value)); + if (wp == NULL) { + archive_entry_copy_uname(entry, value); + if (err > ARCHIVE_WARN) + err = ARCHIVE_WARN; + } else + archive_entry_copy_uname_w(entry, wp); + } + } + return (err); +} + +static int +pax_attribute_xattr(struct archive_entry *entry, + char *name, char *value) +{ + char *name_decoded; + void *value_decoded; + size_t value_len; + + if (strlen(name) < 18 || (strncmp(name, "LIBARCHIVE.xattr.", 17)) != 0) + return 3; + + name += 17; + + /* URL-decode name */ + name_decoded = url_decode(name); + if (name_decoded == NULL) + return 2; + + /* Base-64 decode value */ + value_decoded = base64_decode(value, strlen(value), &value_len); + if (value_decoded == NULL) { + free(name_decoded); + return 1; + } + + archive_entry_xattr_add_entry(entry, name_decoded, + value_decoded, value_len); + + free(name_decoded); + free(value_decoded); + return 0; +} + +/* + * Parse a single key=value attribute. key/value pointers are + * assumed to point into reasonably long-lived storage. + * + * Note that POSIX reserves all-lowercase keywords. Vendor-specific + * extensions should always have keywords of the form "VENDOR.attribute" + * In particular, it's quite feasible to support many different + * vendor extensions here. I'm using "LIBARCHIVE" for extensions + * unique to this library. + * + * Investigate other vendor-specific extensions and see if + * any of them look useful. + */ +static int +pax_attribute(struct tar *tar, struct archive_entry *entry, + char *key, char *value) +{ + int64_t s; + long n; + wchar_t *wp; + + switch (key[0]) { + case 'G': + /* GNU "0.0" sparse pax format. */ + if (strcmp(key, "GNU.sparse.numblocks") == 0) { + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + tar->sparse_gnu_major = 0; + tar->sparse_gnu_minor = 0; + } + if (strcmp(key, "GNU.sparse.offset") == 0) { + tar->sparse_offset = tar_atol10(value, strlen(value)); + if (tar->sparse_numbytes != -1) { + gnu_add_sparse_entry(tar, + tar->sparse_offset, tar->sparse_numbytes); + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + } + } + if (strcmp(key, "GNU.sparse.numbytes") == 0) { + tar->sparse_numbytes = tar_atol10(value, strlen(value)); + if (tar->sparse_numbytes != -1) { + gnu_add_sparse_entry(tar, + tar->sparse_offset, tar->sparse_numbytes); + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + } + } + if (strcmp(key, "GNU.sparse.size") == 0) { + tar->realsize = tar_atol10(value, strlen(value)); + archive_entry_set_size(entry, tar->realsize); + } + + /* GNU "0.1" sparse pax format. */ + if (strcmp(key, "GNU.sparse.map") == 0) { + tar->sparse_gnu_major = 0; + tar->sparse_gnu_minor = 1; + if (gnu_sparse_01_parse(tar, value) != ARCHIVE_OK) + return (ARCHIVE_WARN); + } + + /* GNU "1.0" sparse pax format */ + if (strcmp(key, "GNU.sparse.major") == 0) { + tar->sparse_gnu_major = tar_atol10(value, strlen(value)); + tar->sparse_gnu_pending = 1; + } + if (strcmp(key, "GNU.sparse.minor") == 0) { + tar->sparse_gnu_minor = tar_atol10(value, strlen(value)); + tar->sparse_gnu_pending = 1; + } + if (strcmp(key, "GNU.sparse.name") == 0) { + wp = utf8_decode(tar, value, strlen(value)); + if (wp != NULL) + archive_entry_copy_pathname_w(entry, wp); + else + archive_entry_copy_pathname(entry, value); + } + if (strcmp(key, "GNU.sparse.realsize") == 0) { + tar->realsize = tar_atol10(value, strlen(value)); + archive_entry_set_size(entry, tar->realsize); + } + break; + case 'L': + /* Our extensions */ +/* TODO: Handle arbitrary extended attributes... */ +/* + if (strcmp(key, "LIBARCHIVE.xxxxxxx")==0) + archive_entry_set_xxxxxx(entry, value); +*/ + if (strncmp(key, "LIBARCHIVE.xattr.", 17)==0) + pax_attribute_xattr(entry, key, value); + break; + case 'S': + /* We support some keys used by the "star" archiver */ + if (strcmp(key, "SCHILY.acl.access")==0) { + wp = utf8_decode(tar, value, strlen(value)); + /* TODO: if (wp == NULL) */ + __archive_entry_acl_parse_w(entry, wp, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + } else if (strcmp(key, "SCHILY.acl.default")==0) { + wp = utf8_decode(tar, value, strlen(value)); + /* TODO: if (wp == NULL) */ + __archive_entry_acl_parse_w(entry, wp, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); + } else if (strcmp(key, "SCHILY.devmajor")==0) { + archive_entry_set_rdevmajor(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.devminor")==0) { + archive_entry_set_rdevminor(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.fflags")==0) { + wp = utf8_decode(tar, value, strlen(value)); + /* TODO: if (wp == NULL) */ + archive_entry_copy_fflags_text_w(entry, wp); + } else if (strcmp(key, "SCHILY.dev")==0) { + archive_entry_set_dev(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.ino")==0) { + archive_entry_set_ino(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.nlink")==0) { + archive_entry_set_nlink(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.realsize")==0) { + tar->realsize = tar_atol10(value, strlen(value)); + archive_entry_set_size(entry, tar->realsize); + } + break; + case 'a': + if (strcmp(key, "atime")==0) { + pax_time(value, &s, &n); + archive_entry_set_atime(entry, s, n); + } + break; + case 'c': + if (strcmp(key, "ctime")==0) { + pax_time(value, &s, &n); + archive_entry_set_ctime(entry, s, n); + } else if (strcmp(key, "charset")==0) { + /* TODO: Publish charset information in entry. */ + } else if (strcmp(key, "comment")==0) { + /* TODO: Publish comment in entry. */ + } + break; + case 'g': + if (strcmp(key, "gid")==0) { + archive_entry_set_gid(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "gname")==0) { + archive_strcpy(&(tar->entry_gname), value); + } + break; + case 'h': + if (strcmp(key, "hdrcharset") == 0) { + if (strcmp(value, "BINARY") == 0) + tar->pax_hdrcharset_binary = 1; + else if (strcmp(value, "ISO-IR 10646 2000 UTF-8") == 0) + tar->pax_hdrcharset_binary = 0; + else { + /* TODO: Warn about unsupported hdrcharset */ + } + } + break; + case 'l': + /* pax interchange doesn't distinguish hardlink vs. symlink. */ + if (strcmp(key, "linkpath")==0) { + archive_strcpy(&(tar->entry_linkpath), value); + } + break; + case 'm': + if (strcmp(key, "mtime")==0) { + pax_time(value, &s, &n); + archive_entry_set_mtime(entry, s, n); + } + break; + case 'p': + if (strcmp(key, "path")==0) { + archive_strcpy(&(tar->entry_pathname), value); + } + break; + case 'r': + /* POSIX has reserved 'realtime.*' */ + break; + case 's': + /* POSIX has reserved 'security.*' */ + /* Someday: if (strcmp(key, "security.acl")==0) { ... } */ + if (strcmp(key, "size")==0) { + /* "size" is the size of the data in the entry. */ + tar->entry_bytes_remaining + = tar_atol10(value, strlen(value)); + /* + * But, "size" is not necessarily the size of + * the file on disk; if this is a sparse file, + * the disk size may have already been set from + * GNU.sparse.realsize or GNU.sparse.size or + * an old GNU header field or SCHILY.realsize + * or .... + */ + if (tar->realsize < 0) { + archive_entry_set_size(entry, + tar->entry_bytes_remaining); + tar->realsize + = tar->entry_bytes_remaining; + } + } + break; + case 'u': + if (strcmp(key, "uid")==0) { + archive_entry_set_uid(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "uname")==0) { + archive_strcpy(&(tar->entry_uname), value); + } + break; + } + return (0); +} + + + +/* + * parse a decimal time value, which may include a fractional portion + */ +static void +pax_time(const char *p, int64_t *ps, long *pn) +{ + char digit; + int64_t s; + unsigned long l; + int sign; + int64_t limit, last_digit_limit; + + limit = INT64_MAX / 10; + last_digit_limit = INT64_MAX % 10; + + s = 0; + sign = 1; + if (*p == '-') { + sign = -1; + p++; + } + while (*p >= '0' && *p <= '9') { + digit = *p - '0'; + if (s > limit || + (s == limit && digit > last_digit_limit)) { + s = UINT64_MAX; + break; + } + s = (s * 10) + digit; + ++p; + } + + *ps = s * sign; + + /* Calculate nanoseconds. */ + *pn = 0; + + if (*p != '.') + return; + + l = 100000000UL; + do { + ++p; + if (*p >= '0' && *p <= '9') + *pn += (*p - '0') * l; + else + break; + } while (l /= 10); +} + +/* + * Parse GNU tar header + */ +static int +header_gnutar(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_gnutar *header; + + (void)a; + + /* + * GNU header is like POSIX ustar, except 'prefix' is + * replaced with some other fields. This also means the + * filename is stored as in old-style archives. + */ + + /* Grab fields common to all tar variants. */ + header_common(a, tar, entry, h); + + /* Copy filename over (to ensure null termination). */ + header = (const struct archive_entry_header_gnutar *)h; + archive_strncpy(&(tar->entry_pathname), header->name, + sizeof(header->name)); + archive_entry_copy_pathname(entry, tar->entry_pathname.s); + + /* Fields common to ustar and GNU */ + /* XXX Can the following be factored out since it's common + * to ustar and gnu tar? Is it okay to move it down into + * header_common, perhaps? */ + archive_strncpy(&(tar->entry_uname), + header->uname, sizeof(header->uname)); + archive_entry_copy_uname(entry, tar->entry_uname.s); + + archive_strncpy(&(tar->entry_gname), + header->gname, sizeof(header->gname)); + archive_entry_copy_gname(entry, tar->entry_gname.s); + + /* Parse out device numbers only for char and block specials */ + if (header->typeflag[0] == '3' || header->typeflag[0] == '4') { + archive_entry_set_rdevmajor(entry, + tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); + archive_entry_set_rdevminor(entry, + tar_atol(header->rdevminor, sizeof(header->rdevminor))); + } else + archive_entry_set_rdev(entry, 0); + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + + /* Grab GNU-specific fields. */ + archive_entry_set_atime(entry, + tar_atol(header->atime, sizeof(header->atime)), 0); + archive_entry_set_ctime(entry, + tar_atol(header->ctime, sizeof(header->ctime)), 0); + if (header->realsize[0] != 0) { + tar->realsize + = tar_atol(header->realsize, sizeof(header->realsize)); + archive_entry_set_size(entry, tar->realsize); + } + + if (header->sparse[0].offset[0] != 0) { + gnu_sparse_old_read(a, tar, header); + } else { + if (header->isextended[0] != 0) { + /* XXX WTF? XXX */ + } + } + + return (0); +} + +static void +gnu_add_sparse_entry(struct tar *tar, off_t offset, off_t remaining) +{ + struct sparse_block *p; + + p = (struct sparse_block *)malloc(sizeof(*p)); + if (p == NULL) + __archive_errx(1, "Out of memory"); + memset(p, 0, sizeof(*p)); + if (tar->sparse_last != NULL) + tar->sparse_last->next = p; + else + tar->sparse_list = p; + tar->sparse_last = p; + p->offset = offset; + p->remaining = remaining; +} + +static void +gnu_clear_sparse_list(struct tar *tar) +{ + struct sparse_block *p; + + while (tar->sparse_list != NULL) { + p = tar->sparse_list; + tar->sparse_list = p->next; + free(p); + } + tar->sparse_last = NULL; +} + +/* + * GNU tar old-format sparse data. + * + * GNU old-format sparse data is stored in a fixed-field + * format. Offset/size values are 11-byte octal fields (same + * format as 'size' field in ustart header). These are + * stored in the header, allocating subsequent header blocks + * as needed. Extending the header in this way is a pretty + * severe POSIX violation; this design has earned GNU tar a + * lot of criticism. + */ + +static int +gnu_sparse_old_read(struct archive_read *a, struct tar *tar, + const struct archive_entry_header_gnutar *header) +{ + ssize_t bytes_read; + const void *data; + struct extended { + struct gnu_sparse sparse[21]; + char isextended[1]; + char padding[7]; + }; + const struct extended *ext; + + gnu_sparse_old_parse(tar, header->sparse, 4); + if (header->isextended[0] == 0) + return (ARCHIVE_OK); + + do { + bytes_read = (a->decompressor->read_ahead)(a, &data, 512); + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read < 512) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive " + "detected while reading sparse file data"); + return (ARCHIVE_FATAL); + } + (a->decompressor->consume)(a, 512); + ext = (const struct extended *)data; + gnu_sparse_old_parse(tar, ext->sparse, 21); + } while (ext->isextended[0] != 0); + if (tar->sparse_list != NULL) + tar->entry_offset = tar->sparse_list->offset; + return (ARCHIVE_OK); +} + +static void +gnu_sparse_old_parse(struct tar *tar, + const struct gnu_sparse *sparse, int length) +{ + while (length > 0 && sparse->offset[0] != 0) { + gnu_add_sparse_entry(tar, + tar_atol(sparse->offset, sizeof(sparse->offset)), + tar_atol(sparse->numbytes, sizeof(sparse->numbytes))); + sparse++; + length--; + } +} + +/* + * GNU tar sparse format 0.0 + * + * Beginning with GNU tar 1.15, sparse files are stored using + * information in the pax extended header. The GNU tar maintainers + * have gone through a number of variations in the process of working + * out this scheme; furtunately, they're all numbered. + * + * Sparse format 0.0 uses attribute GNU.sparse.numblocks to store the + * number of blocks, and GNU.sparse.offset/GNU.sparse.numbytes to + * store offset/size for each block. The repeated instances of these + * latter fields violate the pax specification (which frowns on + * duplicate keys), so this format was quickly replaced. + */ + +/* + * GNU tar sparse format 0.1 + * + * This version replaced the offset/numbytes attributes with + * a single "map" attribute that stored a list of integers. This + * format had two problems: First, the "map" attribute could be very + * long, which caused problems for some implementations. More + * importantly, the sparse data was lost when extracted by archivers + * that didn't recognize this extension. + */ + +static int +gnu_sparse_01_parse(struct tar *tar, const char *p) +{ + const char *e; + off_t offset = -1, size = -1; + + for (;;) { + e = p; + while (*e != '\0' && *e != ',') { + if (*e < '0' || *e > '9') + return (ARCHIVE_WARN); + e++; + } + if (offset < 0) { + offset = tar_atol10(p, e - p); + if (offset < 0) + return (ARCHIVE_WARN); + } else { + size = tar_atol10(p, e - p); + if (size < 0) + return (ARCHIVE_WARN); + gnu_add_sparse_entry(tar, offset, size); + offset = -1; + } + if (*e == '\0') + return (ARCHIVE_OK); + p = e + 1; + } +} + +/* + * GNU tar sparse format 1.0 + * + * The idea: The offset/size data is stored as a series of base-10 + * ASCII numbers prepended to the file data, so that dearchivers that + * don't support this format will extract the block map along with the + * data and a separate post-process can restore the sparseness. + * + * Unfortunately, GNU tar 1.16 had a bug that added unnecessary + * padding to the body of the file when using this format. GNU tar + * 1.17 corrected this bug without bumping the version number, so + * it's not possible to support both variants. This code supports + * the later variant at the expense of not supporting the former. + * + * This variant also replaced GNU.sparse.size with GNU.sparse.realsize + * and introduced the GNU.sparse.major/GNU.sparse.minor attributes. + */ + +/* + * Read the next line from the input, and parse it as a decimal + * integer followed by '\n'. Returns positive integer value or + * negative on error. + */ +static int64_t +gnu_sparse_10_atol(struct archive_read *a, struct tar *tar, + ssize_t *remaining) +{ + int64_t l, limit, last_digit_limit; + const char *p; + ssize_t bytes_read; + int base, digit; + + base = 10; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + /* + * Skip any lines starting with '#'; GNU tar specs + * don't require this, but they should. + */ + do { + bytes_read = readline(a, tar, &p, tar_min(*remaining, 100)); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + *remaining -= bytes_read; + } while (p[0] == '#'); + + l = 0; + while (bytes_read > 0) { + if (*p == '\n') + return (l); + if (*p < '0' || *p >= '0' + base) + return (ARCHIVE_WARN); + digit = *p - '0'; + if (l > limit || (l == limit && digit > last_digit_limit)) + l = UINT64_MAX; /* Truncate on overflow. */ + else + l = (l * base) + digit; + p++; + bytes_read--; + } + /* TODO: Error message. */ + return (ARCHIVE_WARN); +} + +/* + * Returns length (in bytes) of the sparse data description + * that was read. + */ +static ssize_t +gnu_sparse_10_read(struct archive_read *a, struct tar *tar) +{ + ssize_t remaining, bytes_read; + int entries; + off_t offset, size, to_skip; + + /* Clear out the existing sparse list. */ + gnu_clear_sparse_list(tar); + + remaining = tar->entry_bytes_remaining; + + /* Parse entries. */ + entries = gnu_sparse_10_atol(a, tar, &remaining); + if (entries < 0) + return (ARCHIVE_FATAL); + /* Parse the individual entries. */ + while (entries-- > 0) { + /* Parse offset/size */ + offset = gnu_sparse_10_atol(a, tar, &remaining); + if (offset < 0) + return (ARCHIVE_FATAL); + size = gnu_sparse_10_atol(a, tar, &remaining); + if (size < 0) + return (ARCHIVE_FATAL); + /* Add a new sparse entry. */ + gnu_add_sparse_entry(tar, offset, size); + } + /* Skip rest of block... */ + bytes_read = tar->entry_bytes_remaining - remaining; + to_skip = 0x1ff & -bytes_read; + if (to_skip != (a->decompressor->skip)(a, to_skip)) + return (ARCHIVE_FATAL); + return (bytes_read + to_skip); +} + +/*- + * Convert text->integer. + * + * Traditional tar formats (including POSIX) specify base-8 for + * all of the standard numeric fields. This is a significant limitation + * in practice: + * = file size is limited to 8GB + * = rdevmajor and rdevminor are limited to 21 bits + * = uid/gid are limited to 21 bits + * + * There are two workarounds for this: + * = pax extended headers, which use variable-length string fields + * = GNU tar and STAR both allow either base-8 or base-256 in + * most fields. The high bit is set to indicate base-256. + * + * On read, this implementation supports both extensions. + */ +static int64_t +tar_atol(const char *p, unsigned char_cnt) +{ + /* + * Technically, GNU tar considers a field to be in base-256 + * only if the first byte is 0xff or 0x80. + */ + if (*p & 0x80) + return (tar_atol256(p, char_cnt)); + return (tar_atol8(p, char_cnt)); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +tar_atol8(const char *p, unsigned char_cnt) +{ + int64_t l, limit, last_digit_limit; + int digit, sign, base; + + base = 8; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '-') { + sign = -1; + p++; + } else + sign = 1; + + l = 0; + digit = *p - '0'; + while (digit >= 0 && digit < base && char_cnt-- > 0) { + if (l>limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (sign < 0) ? -l : l; +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +tar_atol10(const char *p, unsigned char_cnt) +{ + int64_t l, limit, last_digit_limit; + int base, digit, sign; + + base = 10; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '-') { + sign = -1; + p++; + } else + sign = 1; + + l = 0; + digit = *p - '0'; + while (digit >= 0 && digit < base && char_cnt-- > 0) { + if (l > limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (sign < 0) ? -l : l; +} + +/* + * Parse a base-256 integer. This is just a straight signed binary + * value in big-endian order, except that the high-order bit is + * ignored. + */ +static int64_t +tar_atol256(const char *_p, unsigned char_cnt) +{ + int64_t l, upper_limit, lower_limit; + const unsigned char *p = (const unsigned char *)_p; + + upper_limit = INT64_MAX / 256; + lower_limit = INT64_MIN / 256; + + /* Pad with 1 or 0 bits, depending on sign. */ + if ((0x40 & *p) == 0x40) + l = (int64_t)-1; + else + l = 0; + l = (l << 6) | (0x3f & *p++); + while (--char_cnt > 0) { + if (l > upper_limit) { + l = INT64_MAX; /* Truncate on overflow */ + break; + } else if (l < lower_limit) { + l = INT64_MIN; + break; + } + l = (l << 8) | (0xff & (int64_t)*p++); + } + return (l); +} + +/* + * Returns length of line (including trailing newline) + * or negative on error. 'start' argument is updated to + * point to first character of line. This avoids copying + * when possible. + */ +static ssize_t +readline(struct archive_read *a, struct tar *tar, const char **start, + ssize_t limit) +{ + ssize_t bytes_read; + ssize_t total_size = 0; + const void *t; + const char *s; + void *p; + + bytes_read = (a->decompressor->read_ahead)(a, &t, 1); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + s = t; /* Start of line? */ + p = memchr(t, '\n', bytes_read); + /* If we found '\n' in the read buffer, return pointer to that. */ + if (p != NULL) { + bytes_read = 1 + ((const char *)p) - s; + if (bytes_read > limit) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Line too long"); + return (ARCHIVE_FATAL); + } + (a->decompressor->consume)(a, bytes_read); + *start = s; + return (bytes_read); + } + /* Otherwise, we need to accumulate in a line buffer. */ + for (;;) { + if (total_size + bytes_read > limit) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Line too long"); + return (ARCHIVE_FATAL); + } + if (archive_string_ensure(&tar->line, total_size + bytes_read) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate working buffer"); + return (ARCHIVE_FATAL); + } + memcpy(tar->line.s + total_size, t, bytes_read); + (a->decompressor->consume)(a, bytes_read); + total_size += bytes_read; + /* If we found '\n', clean up and return. */ + if (p != NULL) { + *start = tar->line.s; + return (total_size); + } + /* Read some more. */ + bytes_read = (a->decompressor->read_ahead)(a, &t, 1); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + s = t; /* Start of line? */ + p = memchr(t, '\n', bytes_read); + /* If we found '\n', trim the read. */ + if (p != NULL) { + bytes_read = 1 + ((const char *)p) - s; + } + } +} + +static wchar_t * +utf8_decode(struct tar *tar, const char *src, size_t length) +{ + wchar_t *dest; + ssize_t n; + int err; + + /* Ensure pax_entry buffer is big enough. */ + if (tar->pax_entry_length <= length) { + wchar_t *old_entry = tar->pax_entry; + + if (tar->pax_entry_length <= 0) + tar->pax_entry_length = 1024; + while (tar->pax_entry_length <= length + 1) + tar->pax_entry_length *= 2; + + old_entry = tar->pax_entry; + tar->pax_entry = (wchar_t *)realloc(tar->pax_entry, + tar->pax_entry_length * sizeof(wchar_t)); + if (tar->pax_entry == NULL) { + free(old_entry); + /* TODO: Handle this error. */ + return (NULL); + } + } + + dest = tar->pax_entry; + err = 0; + while (length > 0) { + n = UTF8_mbrtowc(dest, src, length); + if (n < 0) + return (NULL); + if (n == 0) + break; + dest++; + src += n; + length -= n; + } + *dest++ = L'\0'; + return (tar->pax_entry); +} + +/* + * Copied and simplified from FreeBSD libc/locale. + */ +static ssize_t +UTF8_mbrtowc(wchar_t *pwc, const char *s, size_t n) +{ + int ch, i, len, mask; + unsigned long wch; + + if (s == NULL || n == 0 || pwc == NULL) + return (0); + + /* + * Determine the number of octets that make up this character from + * the first octet, and a mask that extracts the interesting bits of + * the first octet. + */ + ch = (unsigned char)*s; + if ((ch & 0x80) == 0) { + mask = 0x7f; + len = 1; + } else if ((ch & 0xe0) == 0xc0) { + mask = 0x1f; + len = 2; + } else if ((ch & 0xf0) == 0xe0) { + mask = 0x0f; + len = 3; + } else if ((ch & 0xf8) == 0xf0) { + mask = 0x07; + len = 4; + } else { + /* Invalid first byte. */ + return (-1); + } + + if (n < (size_t)len) { + /* Valid first byte but truncated. */ + return (-2); + } + + /* + * Decode the octet sequence representing the character in chunks + * of 6 bits, most significant first. + */ + wch = (unsigned char)*s++ & mask; + i = len; + while (--i != 0) { + if ((*s & 0xc0) != 0x80) { + /* Invalid intermediate byte; consume one byte and + * emit '?' */ + *pwc = '?'; + return (1); + } + wch <<= 6; + wch |= *s++ & 0x3f; + } + + /* Assign the value to the output; out-of-range values + * just get truncated. */ + *pwc = (wchar_t)wch; +#ifdef WCHAR_MAX + /* + * If platform has WCHAR_MAX, we can do something + * more sensible with out-of-range values. + */ + if (wch >= WCHAR_MAX) + *pwc = '?'; +#endif + /* Return number of bytes input consumed: 0 for end-of-string. */ + return (wch == L'\0' ? 0 : len); +} + + +/* + * base64_decode - Base64 decode + * + * This accepts most variations of base-64 encoding, including: + * * with or without line breaks + * * with or without the final group padded with '=' or '_' characters + * (The most economical Base-64 variant does not pad the last group and + * omits line breaks; RFC1341 used for MIME requires both.) + */ +static char * +base64_decode(const char *s, size_t len, size_t *out_len) +{ + static const unsigned char digits[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N', + 'O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b', + 'c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z','0','1','2','3', + '4','5','6','7','8','9','+','/' }; + static unsigned char decode_table[128]; + char *out, *d; + const unsigned char *src = (const unsigned char *)s; + + /* If the decode table is not yet initialized, prepare it. */ + if (decode_table[digits[1]] != 1) { + size_t i; + memset(decode_table, 0xff, sizeof(decode_table)); + for (i = 0; i < sizeof(digits); i++) + decode_table[digits[i]] = i; + } + + /* Allocate enough space to hold the entire output. */ + /* Note that we may not use all of this... */ + out = (char *)malloc((len * 3 + 3) / 4); + if (out == NULL) { + *out_len = 0; + return (NULL); + } + d = out; + + while (len > 0) { + /* Collect the next group of (up to) four characters. */ + int v = 0; + int group_size = 0; + while (group_size < 4 && len > 0) { + /* '=' or '_' padding indicates final group. */ + if (*src == '=' || *src == '_') { + len = 0; + break; + } + /* Skip illegal characters (including line breaks) */ + if (*src > 127 || *src < 32 + || decode_table[*src] == 0xff) { + len--; + src++; + continue; + } + v <<= 6; + v |= decode_table[*src++]; + len --; + group_size++; + } + /* Align a short group properly. */ + v <<= 6 * (4 - group_size); + /* Unpack the group we just collected. */ + switch (group_size) { + case 4: d[2] = v & 0xff; + /* FALLTHROUGH */ + case 3: d[1] = (v >> 8) & 0xff; + /* FALLTHROUGH */ + case 2: d[0] = (v >> 16) & 0xff; + break; + case 1: /* this is invalid! */ + break; + } + d += group_size * 3 / 4; + } + + *out_len = d - out; + return (out); +} + +static char * +url_decode(const char *in) +{ + char *out, *d; + const char *s; + + out = (char *)malloc(strlen(in) + 1); + if (out == NULL) + return (NULL); + for (s = in, d = out; *s != '\0'; ) { + if (*s == '%') { + /* Try to convert % escape */ + int digit1 = tohex(s[1]); + int digit2 = tohex(s[2]); + if (digit1 >= 0 && digit2 >= 0) { + /* Looks good, consume three chars */ + s += 3; + /* Convert output */ + *d++ = ((digit1 << 4) | digit2); + continue; + } + /* Else fall through and treat '%' as normal char */ + } + *d++ = *s++; + } + *d = '\0'; + return (out); +} + +static int +tohex(int c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (-1); +} diff --git a/libarchive/archive_read_support_format_zip.c b/libarchive/archive_read_support_format_zip.c new file mode 100644 index 00000000..3c951313 --- /dev/null +++ b/libarchive/archive_read_support_format_zip.c @@ -0,0 +1,780 @@ +/*- + * Copyright (c) 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_zip.c,v 1.22 2008/02/27 06:05:59 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <time.h> +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + +struct zip { + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_bytes_remaining; + int64_t entry_offset; + + /* These count the number of bytes actually read for the entry. */ + int64_t entry_compressed_bytes_read; + int64_t entry_uncompressed_bytes_read; + + unsigned version; + unsigned system; + unsigned flags; + unsigned compression; + const char * compression_name; + time_t mtime; + time_t ctime; + time_t atime; + mode_t mode; + uid_t uid; + gid_t gid; + + /* Flags to mark progress of decompression. */ + char decompress_init; + char end_of_entry; + char end_of_entry_cleanup; + + long crc32; + ssize_t filename_length; + ssize_t extra_length; + int64_t uncompressed_size; + int64_t compressed_size; + + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; +#ifdef HAVE_ZLIB_H + z_stream stream; + char stream_valid; +#endif + + struct archive_string pathname; + struct archive_string extra; + char format_name[64]; +}; + +#define ZIP_LENGTH_AT_END 8 + +struct zip_file_header { + char signature[4]; + char version[2]; + char flags[2]; + char compression[2]; + char timedate[4]; + char crc32[4]; + char compressed_size[4]; + char uncompressed_size[4]; + char filename_length[2]; + char extra_length[2]; +}; + +static const char *compression_names[] = { + "uncompressed", + "shrinking", + "reduced-1", + "reduced-2", + "reduced-3", + "reduced-4", + "imploded", + "reserved", + "deflation" +}; + +static int archive_read_format_zip_bid(struct archive_read *); +static int archive_read_format_zip_cleanup(struct archive_read *); +static int archive_read_format_zip_read_data(struct archive_read *, + const void **, size_t *, off_t *); +static int archive_read_format_zip_read_data_skip(struct archive_read *a); +static int archive_read_format_zip_read_header(struct archive_read *, + struct archive_entry *); +static int zip_read_data_deflate(struct archive_read *a, const void **buff, + size_t *size, off_t *offset); +static int zip_read_data_none(struct archive_read *a, const void **buff, + size_t *size, off_t *offset); +static int zip_read_file_header(struct archive_read *a, + struct archive_entry *entry, struct zip *zip); +static time_t zip_time(const char *); +static void process_extra(const void* extra, struct zip* zip); + +int +archive_read_support_format_zip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct zip *zip; + int r; + + zip = (struct zip *)malloc(sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data"); + return (ARCHIVE_FATAL); + } + memset(zip, 0, sizeof(*zip)); + + r = __archive_read_register_format(a, + zip, + archive_read_format_zip_bid, + archive_read_format_zip_read_header, + archive_read_format_zip_read_data, + archive_read_format_zip_read_data_skip, + archive_read_format_zip_cleanup); + + if (r != ARCHIVE_OK) + free(zip); + return (ARCHIVE_OK); +} + + +static int +archive_read_format_zip_bid(struct archive_read *a) +{ + int bid = 0; + const char *p; + + if (a->archive.archive_format == ARCHIVE_FORMAT_ZIP) + bid += 1; + + if ((p = __archive_read_ahead(a, 4)) == NULL) + return (-1); + + /* + * Bid of 30 here is: 16 bits for "PK", + * next 16-bit field has four options (-2 bits). + * 16 + 16-2 = 30. + */ + if (p[0] == 'P' && p[1] == 'K') { + if ((p[2] == '\001' && p[3] == '\002') + || (p[2] == '\003' && p[3] == '\004') + || (p[2] == '\005' && p[3] == '\006') + || (p[2] == '\007' && p[3] == '\010') + || (p[2] == '0' && p[3] == '0')) + return (30); + } + return (0); +} + +static int +archive_read_format_zip_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + const void *h; + const char *signature; + struct zip *zip; + + a->archive.archive_format = ARCHIVE_FORMAT_ZIP; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "ZIP"; + + zip = (struct zip *)(a->format->data); + zip->decompress_init = 0; + zip->end_of_entry = 0; + zip->end_of_entry_cleanup = 0; + zip->entry_uncompressed_bytes_read = 0; + zip->entry_compressed_bytes_read = 0; + if ((h = __archive_read_ahead(a, 4)) == NULL) + return (ARCHIVE_FATAL); + + signature = (const char *)h; + if (signature[0] != 'P' || signature[1] != 'K') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad ZIP file"); + return (ARCHIVE_FATAL); + } + + /* + * "PK00" signature is used for "split" archives that + * only have a single segment. This means we can just + * skip the PK00; the first real file header should follow. + */ + if (signature[2] == '0' && signature[3] == '0') { + (a->decompressor->consume)(a, 4); + if ((h = __archive_read_ahead(a, 4)) == NULL) + return (ARCHIVE_FATAL); + signature = (const char *)h; + if (signature[0] != 'P' || signature[1] != 'K') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad ZIP file"); + return (ARCHIVE_FATAL); + } + } + + if (signature[2] == '\001' && signature[3] == '\002') { + /* Beginning of central directory. */ + return (ARCHIVE_EOF); + } + + if (signature[2] == '\003' && signature[3] == '\004') { + /* Regular file entry. */ + return (zip_read_file_header(a, entry, zip)); + } + + if (signature[2] == '\005' && signature[3] == '\006') { + /* End-of-archive record. */ + return (ARCHIVE_EOF); + } + + if (signature[2] == '\007' && signature[3] == '\010') { + /* + * We should never encounter this record here; + * see ZIP_LENGTH_AT_END handling below for details. + */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Bad ZIP file: Unexpected end-of-entry record"); + return (ARCHIVE_FATAL); + } + + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Damaged ZIP file or unsupported format variant (%d,%d)", + signature[2], signature[3]); + return (ARCHIVE_FATAL); +} + +int +zip_read_file_header(struct archive_read *a, struct archive_entry *entry, + struct zip *zip) +{ + const struct zip_file_header *p; + const void *h; + + if ((p = __archive_read_ahead(a, sizeof *p)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + + zip->version = p->version[0]; + zip->system = p->version[1]; + zip->flags = archive_le16dec(p->flags); + zip->compression = archive_le16dec(p->compression); + if (zip->compression < + sizeof(compression_names)/sizeof(compression_names[0])) + zip->compression_name = compression_names[zip->compression]; + else + zip->compression_name = "??"; + zip->mtime = zip_time(p->timedate); + zip->ctime = 0; + zip->atime = 0; + zip->mode = 0; + zip->uid = 0; + zip->gid = 0; + zip->crc32 = archive_le32dec(p->crc32); + zip->filename_length = archive_le16dec(p->filename_length); + zip->extra_length = archive_le16dec(p->extra_length); + zip->uncompressed_size = archive_le32dec(p->uncompressed_size); + zip->compressed_size = archive_le32dec(p->compressed_size); + + (a->decompressor->consume)(a, sizeof(struct zip_file_header)); + + + /* Read the filename. */ + if ((h = __archive_read_ahead(a, zip->filename_length)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + if (archive_string_ensure(&zip->pathname, zip->filename_length) == NULL) + __archive_errx(1, "Out of memory"); + archive_strncpy(&zip->pathname, h, zip->filename_length); + (a->decompressor->consume)(a, zip->filename_length); + archive_entry_set_pathname(entry, zip->pathname.s); + + if (zip->pathname.s[archive_strlen(&zip->pathname) - 1] == '/') + zip->mode = AE_IFDIR | 0777; + else + zip->mode = AE_IFREG | 0777; + + /* Read the extra data. */ + if ((h = __archive_read_ahead(a, zip->extra_length)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + process_extra(h, zip); + (a->decompressor->consume)(a, zip->extra_length); + + /* Populate some additional entry fields: */ + archive_entry_set_mode(entry, zip->mode); + archive_entry_set_uid(entry, zip->uid); + archive_entry_set_gid(entry, zip->gid); + archive_entry_set_mtime(entry, zip->mtime, 0); + archive_entry_set_ctime(entry, zip->ctime, 0); + archive_entry_set_atime(entry, zip->atime, 0); + archive_entry_set_size(entry, zip->uncompressed_size); + + zip->entry_bytes_remaining = zip->compressed_size; + zip->entry_offset = 0; + + /* If there's no body, force read_data() to return EOF immediately. */ + if (0 == (zip->flags & ZIP_LENGTH_AT_END) + && zip->entry_bytes_remaining < 1) + zip->end_of_entry = 1; + + /* Set up a more descriptive format name. */ + sprintf(zip->format_name, "ZIP %d.%d (%s)", + zip->version / 10, zip->version % 10, + zip->compression_name); + a->archive.archive_format_name = zip->format_name; + + return (ARCHIVE_OK); +} + +/* Convert an MSDOS-style date/time into Unix-style time. */ +static time_t +zip_time(const char *p) +{ + int msTime, msDate; + struct tm ts; + + msTime = (0xff & (unsigned)p[0]) + 256 * (0xff & (unsigned)p[1]); + msDate = (0xff & (unsigned)p[2]) + 256 * (0xff & (unsigned)p[3]); + + memset(&ts, 0, sizeof(ts)); + ts.tm_year = ((msDate >> 9) & 0x7f) + 80; /* Years since 1900. */ + ts.tm_mon = ((msDate >> 5) & 0x0f) - 1; /* Month number. */ + ts.tm_mday = msDate & 0x1f; /* Day of month. */ + ts.tm_hour = (msTime >> 11) & 0x1f; + ts.tm_min = (msTime >> 5) & 0x3f; + ts.tm_sec = (msTime << 1) & 0x3e; + ts.tm_isdst = -1; + return mktime(&ts); +} + +static int +archive_read_format_zip_read_data(struct archive_read *a, + const void **buff, size_t *size, off_t *offset) +{ + int r; + struct zip *zip; + + zip = (struct zip *)(a->format->data); + + /* + * If we hit end-of-entry last time, clean up and return + * ARCHIVE_EOF this time. + */ + if (zip->end_of_entry) { + if (!zip->end_of_entry_cleanup) { + if (zip->flags & ZIP_LENGTH_AT_END) { + const char *p; + + if ((p = __archive_read_ahead(a, 16)) == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP end-of-file record"); + return (ARCHIVE_FATAL); + } + zip->crc32 = archive_le32dec(p + 4); + zip->compressed_size = archive_le32dec(p + 8); + zip->uncompressed_size = archive_le32dec(p + 12); + (a->decompressor->consume)(a, 16); + } + + /* Check file size, CRC against these values. */ + if (zip->compressed_size != zip->entry_compressed_bytes_read) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP compressed data is wrong size"); + return (ARCHIVE_WARN); + } + /* Size field only stores the lower 32 bits of the actual size. */ + if ((zip->uncompressed_size & UINT32_MAX) + != (zip->entry_uncompressed_bytes_read & UINT32_MAX)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP uncompressed data is wrong size"); + return (ARCHIVE_WARN); + } +/* TODO: Compute CRC. */ +/* + if (zip->crc32 != zip->entry_crc32_calculated) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP data CRC error"); + return (ARCHIVE_WARN); + } +*/ + /* End-of-entry cleanup done. */ + zip->end_of_entry_cleanup = 1; + } + *offset = zip->entry_uncompressed_bytes_read; + *size = 0; + *buff = NULL; + return (ARCHIVE_EOF); + } + + switch(zip->compression) { + case 0: /* No compression. */ + r = zip_read_data_none(a, buff, size, offset); + break; + case 8: /* Deflate compression. */ + r = zip_read_data_deflate(a, buff, size, offset); + break; + default: /* Unsupported compression. */ + *buff = NULL; + *size = 0; + *offset = 0; + /* Return a warning. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported ZIP compression method (%s)", + zip->compression_name); + if (zip->flags & ZIP_LENGTH_AT_END) { + /* + * ZIP_LENGTH_AT_END requires us to + * decompress the entry in order to + * skip it, but we don't know this + * compression method, so we give up. + */ + r = ARCHIVE_FATAL; + } else { + /* We know compressed size; just skip it. */ + archive_read_format_zip_read_data_skip(a); + r = ARCHIVE_WARN; + } + break; + } + return (r); +} + +/* + * Read "uncompressed" data. According to the current specification, + * if ZIP_LENGTH_AT_END is specified, then the size fields in the + * initial file header are supposed to be set to zero. This would, of + * course, make it impossible for us to read the archive, since we + * couldn't determine the end of the file data. Info-ZIP seems to + * include the real size fields both before and after the data in this + * case (the CRC only appears afterwards), so this works as you would + * expect. + * + * Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets + * zip->end_of_entry if it consumes all of the data. + */ +static int +zip_read_data_none(struct archive_read *a, const void **buff, + size_t *size, off_t *offset) +{ + struct zip *zip; + ssize_t bytes_avail; + + zip = (struct zip *)(a->format->data); + + if (zip->entry_bytes_remaining == 0) { + *buff = NULL; + *size = 0; + *offset = zip->entry_offset; + zip->end_of_entry = 1; + return (ARCHIVE_OK); + } + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + bytes_avail = (a->decompressor->read_ahead)(a, buff, 1); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file data"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > zip->entry_bytes_remaining) + bytes_avail = zip->entry_bytes_remaining; + (a->decompressor->consume)(a, bytes_avail); + *size = bytes_avail; + *offset = zip->entry_offset; + zip->entry_offset += *size; + zip->entry_bytes_remaining -= *size; + zip->entry_uncompressed_bytes_read += *size; + zip->entry_compressed_bytes_read += *size; + return (ARCHIVE_OK); +} + +#ifdef HAVE_ZLIB_H +static int +zip_read_data_deflate(struct archive_read *a, const void **buff, + size_t *size, off_t *offset) +{ + struct zip *zip; + ssize_t bytes_avail; + const void *compressed_buff; + int r; + + zip = (struct zip *)(a->format->data); + + /* If the buffer hasn't been allocated, allocate it now. */ + if (zip->uncompressed_buffer == NULL) { + zip->uncompressed_buffer_size = 32 * 1024; + zip->uncompressed_buffer + = (unsigned char *)malloc(zip->uncompressed_buffer_size); + if (zip->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for ZIP decompression"); + return (ARCHIVE_FATAL); + } + } + + /* If we haven't yet read any data, initialize the decompressor. */ + if (!zip->decompress_init) { + if (zip->stream_valid) + r = inflateReset(&zip->stream); + else + r = inflateInit2(&zip->stream, + -15 /* Don't check for zlib header */); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't initialize ZIP decompression."); + return (ARCHIVE_FATAL); + } + /* Stream structure has been set up. */ + zip->stream_valid = 1; + /* We've initialized decompression for this stream. */ + zip->decompress_init = 1; + } + + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + bytes_avail = (a->decompressor->read_ahead)(a, &compressed_buff, 1); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file body"); + return (ARCHIVE_FATAL); + } + + /* + * A bug in zlib.h: stream.next_in should be marked 'const' + * but isn't (the library never alters data through the + * next_in pointer, only reads it). The result: this ugly + * cast to remove 'const'. + */ + zip->stream.next_in = (Bytef *)(uintptr_t)(const void *)compressed_buff; + zip->stream.avail_in = bytes_avail; + zip->stream.total_in = 0; + zip->stream.next_out = zip->uncompressed_buffer; + zip->stream.avail_out = zip->uncompressed_buffer_size; + zip->stream.total_out = 0; + + r = inflate(&zip->stream, 0); + switch (r) { + case Z_OK: + break; + case Z_STREAM_END: + zip->end_of_entry = 1; + break; + case Z_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Out of memory for ZIP decompression"); + return (ARCHIVE_FATAL); + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP decompression failed (%d)", r); + return (ARCHIVE_FATAL); + } + + /* Consume as much as the compressor actually used. */ + bytes_avail = zip->stream.total_in; + (a->decompressor->consume)(a, bytes_avail); + zip->entry_bytes_remaining -= bytes_avail; + zip->entry_compressed_bytes_read += bytes_avail; + + *offset = zip->entry_offset; + *size = zip->stream.total_out; + zip->entry_uncompressed_bytes_read += *size; + *buff = zip->uncompressed_buffer; + zip->entry_offset += *size; + return (ARCHIVE_OK); +} +#else +static int +zip_read_data_deflate(struct archive_read *a, const void **buff, + size_t *size, off_t *offset) +{ + *buff = NULL; + *size = 0; + *offset = 0; + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "libarchive compiled without deflate support (no libz)"); + return (ARCHIVE_FATAL); +} +#endif + +static int +archive_read_format_zip_read_data_skip(struct archive_read *a) +{ + struct zip *zip; + const void *buff = NULL; + off_t bytes_skipped; + + zip = (struct zip *)(a->format->data); + + /* + * If the length is at the end, we have no choice but + * to decompress all the data to find the end marker. + */ + if (zip->flags & ZIP_LENGTH_AT_END) { + size_t size; + off_t offset; + int r; + do { + r = archive_read_format_zip_read_data(a, &buff, + &size, &offset); + } while (r == ARCHIVE_OK); + return (r); + } + + /* + * If the length is at the beginning, we can skip the + * compressed data much more quickly. + */ + bytes_skipped = (a->decompressor->skip)(a, zip->entry_bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + /* This entry is finished and done. */ + zip->end_of_entry_cleanup = zip->end_of_entry = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_zip_cleanup(struct archive_read *a) +{ + struct zip *zip; + + zip = (struct zip *)(a->format->data); +#ifdef HAVE_ZLIB_H + if (zip->stream_valid) + inflateEnd(&zip->stream); +#endif + free(zip->uncompressed_buffer); + archive_string_free(&(zip->pathname)); + archive_string_free(&(zip->extra)); + free(zip); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +/* + * The extra data is stored as a list of + * id1+size1+data1 + id2+size2+data2 ... + * triplets. id and size are 2 bytes each. + */ +static void +process_extra(const void* extra, struct zip* zip) +{ + int offset = 0; + const char *p = (const char *)extra; + while (offset < zip->extra_length - 4) + { + unsigned short headerid = archive_le16dec(p + offset); + unsigned short datasize = archive_le16dec(p + offset + 2); + offset += 4; + if (offset + datasize > zip->extra_length) + break; +#ifdef DEBUG + fprintf(stderr, "Header id 0x%04x, length %d\n", + headerid, datasize); +#endif + switch (headerid) { + case 0x0001: + /* Zip64 extended information extra field. */ + if (datasize >= 8) + zip->uncompressed_size = archive_le64dec(p + offset); + if (datasize >= 16) + zip->compressed_size = archive_le64dec(p + offset + 8); + break; + case 0x5455: + { + /* Extended time field "UT". */ + int flags = p[offset]; + offset++; + datasize--; + /* Flag bits indicate which dates are present. */ + if (flags & 0x01) + { +#ifdef DEBUG + fprintf(stderr, "mtime: %lld -> %d\n", + (long long)zip->mtime, + archive_le32dec(p + offset)); +#endif + if (datasize < 4) + break; + zip->mtime = archive_le32dec(p + offset); + offset += 4; + datasize -= 4; + } + if (flags & 0x02) + { + if (datasize < 4) + break; + zip->atime = archive_le32dec(p + offset); + offset += 4; + datasize -= 4; + } + if (flags & 0x04) + { + if (datasize < 4) + break; + zip->ctime = archive_le32dec(p + offset); + offset += 4; + datasize -= 4; + } + break; + } + case 0x7855: + /* Info-ZIP Unix Extra Field (type 2) "Ux". */ +#ifdef DEBUG + fprintf(stderr, "uid %d gid %d\n", + archive_le16dec(p + offset), + archive_le16dec(p + offset + 2)); +#endif + if (datasize >= 2) + zip->uid = archive_le16dec(p + offset); + if (datasize >= 4) + zip->gid = archive_le16dec(p + offset + 2); + break; + default: + break; + } + offset += datasize; + } +#ifdef DEBUG + if (offset != zip->extra_length) + { + fprintf(stderr, + "Extra data field contents do not match reported size!"); + } +#endif +} diff --git a/libarchive/archive_string.c b/libarchive/archive_string.c new file mode 100644 index 00000000..7e43b360 --- /dev/null +++ b/libarchive/archive_string.c @@ -0,0 +1,126 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_string.c,v 1.11 2007/07/15 19:13:59 kientzle Exp $"); + +/* + * Basic resizable string support, to simplify manipulating arbitrary-sized + * strings while minimizing heap activity. + */ + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive_private.h" +#include "archive_string.h" + +struct archive_string * +__archive_string_append(struct archive_string *as, const char *p, size_t s) +{ + if (__archive_string_ensure(as, as->length + s + 1) == NULL) + __archive_errx(1, "Out of memory"); + memcpy(as->s + as->length, p, s); + as->s[as->length + s] = 0; + as->length += s; + return (as); +} + +void +__archive_string_copy(struct archive_string *dest, struct archive_string *src) +{ + if (__archive_string_ensure(dest, src->length + 1) == NULL) + __archive_errx(1, "Out of memory"); + memcpy(dest->s, src->s, src->length); + dest->length = src->length; + dest->s[dest->length] = 0; +} + +void +__archive_string_free(struct archive_string *as) +{ + as->length = 0; + as->buffer_length = 0; + if (as->s != NULL) + free(as->s); +} + +/* Returns NULL on any allocation failure. */ +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); + if (as->s == NULL) + return (NULL); + 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 = "0123456789abcdef"; + + 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); +} diff --git a/libarchive/archive_string.h b/libarchive/archive_string.h new file mode 100644 index 00000000..f56c50fe --- /dev/null +++ b/libarchive/archive_string.h @@ -0,0 +1,122 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_string.h,v 1.10 2008/03/14 22:00:09 kientzle Exp $ + * + */ + +#ifndef ARCHIVE_STRING_H_INCLUDED +#define ARCHIVE_STRING_H_INCLUDED + +#include <stdarg.h> +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +/* + * 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); + +/* Copy one archive_string to another */ +void +__archive_string_copy(struct archive_string *dest, struct archive_string *src); +#define archive_string_copy(dest, src) \ + __archive_string_copy(dest, src) + +/* 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 + +void __archive_string_sprintf(struct archive_string *, const char *, ...); +#define archive_string_sprintf __archive_string_sprintf + +#endif diff --git a/libarchive/archive_string_sprintf.c b/libarchive/archive_string_sprintf.c new file mode 100644 index 00000000..6f77a36a --- /dev/null +++ b/libarchive/archive_string_sprintf.c @@ -0,0 +1,140 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_string_sprintf.c,v 1.10 2008/03/14 22:00:09 kientzle Exp $"); + +/* + * The use of printf()-family functions can be troublesome + * for space-constrained applications. In addition, correctly + * implementing this function in terms of vsnprintf() requires + * two calls (one to determine the size, another to format the + * result), which in turn requires duplicating the argument list + * using va_copy, which isn't yet universally available. + * + * So, I've implemented a bare minimum of printf()-like capability + * here. This is only used to format error messages, so doesn't + * require any floating-point support or field-width handling. + */ + +#include <stdio.h> + +#include "archive_string.h" +#include "archive_private.h" + +void +__archive_string_sprintf(struct archive_string *as, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + archive_string_vsprintf(as, fmt, ap); + va_end(ap); +} + +/* + * 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; + + if (__archive_string_ensure(as, 64) == NULL) + __archive_errx(1, "Out of memory"); + + 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); + } + } +} diff --git a/libarchive/archive_util.3 b/libarchive/archive_util.3 new file mode 100644 index 00000000..b315c55e --- /dev/null +++ b/libarchive/archive_util.3 @@ -0,0 +1,151 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_util.3,v 1.8 2008/03/10 14:44:40 jkoshy Exp $ +.\" +.Dd January 8, 2005 +.Dt archive_util 3 +.Os +.Sh NAME +.Nm archive_clear_error , +.Nm archive_compression , +.Nm archive_compression_name , +.Nm archive_copy_error , +.Nm archive_errno , +.Nm archive_error_string , +.Nm archive_format , +.Nm archive_format_name , +.Nm archive_set_error +.Nd libarchive utility functions +.Sh SYNOPSIS +.In archive.h +.Ft void +.Fn archive_clear_error "struct archive *" +.Ft int +.Fn archive_compression "struct archive *" +.Ft const char * +.Fn archive_compression_name "struct archive *" +.Ft void +.Fn archive_copy_error "struct archive *" "struct archive *" +.Ft int +.Fn archive_errno "struct archive *" +.Ft const char * +.Fn archive_error_string "struct archive *" +.Ft int +.Fn archive_format "struct archive *" +.Ft const char * +.Fn archive_format_name "struct archive *" +.Ft void +.Fo archive_set_error +.Fa "struct archive *" +.Fa "int error_code" +.Fa "const char *fmt" +.Fa "..." +.Fc +.Sh DESCRIPTION +These functions provide access to various information about the +.Tn struct archive +object used in the +.Xr libarchive 3 +library. +.Bl -tag -compact -width indent +.It Fn archive_clear_error +Clears any error information left over from a previous call. +Not generally used in client code. +.It Fn archive_compression +Returns a numeric code indicating the current compression. +This value is set by +.Fn archive_read_open . +.It Fn archive_compression_name +Returns a text description of the current compression suitable for display. +.It Fn archive_copy_error +Copies error information from one archive to another. +.It Fn archive_errno +Returns a numeric error code (see +.Xr errno 2 ) +indicating the reason for the most recent error return. +.It Fn archive_error_string +Returns a textual error message suitable for display. +The error message here is usually more specific than that +obtained from passing the result of +.Fn archive_errno +to +.Xr strerror 3 . +.It Fn archive_format +Returns a numeric code indicating the format of the current +archive entry. +This value is set by a successful call to +.Fn archive_read_next_header . +Note that it is common for this value to change from +entry to entry. +For example, a tar archive might have several entries that +utilize GNU tar extensions and several entries that do not. +These entries will have different format codes. +.It Fn archive_format_name +A textual description of the format of the current entry. +.It Fn archive_set_error +Sets the numeric error code and error description that will be returned +by +.Fn archive_errno +and +.Fn archive_error_string . +This function should be used within I/O callbacks to set system-specific +error codes and error descriptions. +This function accepts a printf-like format string and arguments. +However, you should be careful to use only the following printf +format specifiers: +.Dq %c , +.Dq %d , +.Dq %jd , +.Dq %jo , +.Dq %ju , +.Dq %jx , +.Dq %ld , +.Dq %lo , +.Dq %lu , +.Dq %lx , +.Dq %o , +.Dq %u , +.Dq %s , +.Dq %x , +.Dq %% . +Field-width specifiers and other printf features are +not uniformly supported and should not be used. +.El +.Sh SEE ALSO +.Xr archive_read 3 , +.Xr archive_write 3 , +.Xr libarchive 3 , +.Xr printf 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . diff --git a/libarchive/archive_util.c b/libarchive/archive_util.c new file mode 100644 index 00000000..69d69a51 --- /dev/null +++ b/libarchive/archive_util.c @@ -0,0 +1,229 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_util.c,v 1.17 2008/03/14 22:31:57 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_string.h" + +#if ARCHIVE_VERSION_NUMBER < 3000000 +/* These disappear in libarchive 3.0 */ +/* Deprecated. */ +int +archive_api_feature(void) +{ + return (ARCHIVE_API_FEATURE); +} + +/* Deprecated. */ +int +archive_api_version(void) +{ + return (ARCHIVE_API_VERSION); +} + +/* Deprecated synonym for archive_version_number() */ +int +archive_version_stamp(void) +{ + return (archive_version_number()); +} + +/* Deprecated synonym for archive_version_string() */ +const char * +archive_version(void) +{ + return (archive_version_string()); +} +#endif + +int +archive_version_number(void) +{ + return (ARCHIVE_VERSION_NUMBER); +} + +/* + * Format a version string of the form "libarchive x.y.z", where x, y, + * z are the correct parts of the version ID from + * archive_version_number(). + * + * I used to do all of this at build time in shell scripts but that + * proved to be a portability headache. + */ + +const char * +archive_version_string(void) +{ + static char buff[128]; + struct archive_string as; + int n; + + if (buff[0] == '\0') { + n = archive_version_number(); + memset(&as, 0, sizeof(as)); + archive_string_sprintf(&as, "libarchive %d.%d.%d", + n / 1000000, (n / 1000) % 1000, n % 1000); + strncpy(buff, as.s, sizeof(buff)); + buff[sizeof(buff) - 1] = '\0'; + archive_string_free(&as); + } + return (buff); +} + +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_clear_error(struct archive *a) +{ + archive_string_empty(&a->error_string); + a->error = NULL; +} + +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_copy_error(struct archive *dest, struct archive *src) +{ + dest->archive_error_number = src->archive_error_number; + + archive_string_copy(&dest->error_string, &src->error_string); + dest->error = dest->error_string.s; +} + +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); +} diff --git a/libarchive/archive_virtual.c b/libarchive/archive_virtual.c new file mode 100644 index 00000000..a431058c --- /dev/null +++ b/libarchive/archive_virtual.c @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_virtual.c,v 1.1 2007/03/03 07:37:36 kientzle Exp $"); + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" + +int +archive_write_close(struct archive *a) +{ + return ((a->vtable->archive_write_close)(a)); +} + +#if ARCHIVE_API_VERSION > 1 +int +archive_write_finish(struct archive *a) +{ + return ((a->vtable->archive_write_finish)(a)); +} +#else +/* Temporarily allow library to compile with either 1.x or 2.0 API. */ +void +archive_write_finish(struct archive *a) +{ + (void)(a->vtable->archive_write_finish)(a); +} +#endif + +int +archive_write_header(struct archive *a, struct archive_entry *entry) +{ + return ((a->vtable->archive_write_header)(a, entry)); +} + +int +archive_write_finish_entry(struct archive *a) +{ + return ((a->vtable->archive_write_finish_entry)(a)); +} + +#if ARCHIVE_API_VERSION > 1 +ssize_t +#else +/* Temporarily allow library to compile with either 1.x or 2.0 API. */ +int +#endif +archive_write_data(struct archive *a, const void *buff, size_t s) +{ + return ((a->vtable->archive_write_data)(a, buff, s)); +} + +ssize_t +archive_write_data_block(struct archive *a, const void *buff, size_t s, off_t o) +{ + return ((a->vtable->archive_write_data_block)(a, buff, s, o)); +} diff --git a/libarchive/archive_write.3 b/libarchive/archive_write.3 new file mode 100644 index 00000000..253b39e7 --- /dev/null +++ b/libarchive/archive_write.3 @@ -0,0 +1,554 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_write.3,v 1.23 2008/03/10 14:44:41 jkoshy Exp $ +.\" +.Dd August 19, 2006 +.Dt archive_write 3 +.Os +.Sh NAME +.Nm archive_write_new , +.Nm archive_write_set_format_cpio , +.Nm archive_write_set_format_pax , +.Nm archive_write_set_format_pax_restricted , +.Nm archive_write_set_format_shar , +.Nm archive_write_set_format_shar_binary , +.Nm archive_write_set_format_ustar , +.Nm archive_write_get_bytes_per_block , +.Nm archive_write_set_bytes_per_block , +.Nm archive_write_set_bytes_in_last_block , +.Nm archive_write_set_compression_bzip2 , +.Nm archive_write_set_compression_gzip , +.Nm archive_write_set_compression_none , +.Nm archive_write_set_compression_program , +.Nm archive_write_open , +.Nm archive_write_open_fd , +.Nm archive_write_open_FILE , +.Nm archive_write_open_filename , +.Nm archive_write_open_memory , +.Nm archive_write_header , +.Nm archive_write_data , +.Nm archive_write_finish_entry , +.Nm archive_write_close , +.Nm archive_write_finish +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_write_new "void" +.Ft int +.Fn archive_write_get_bytes_per_block "struct archive *" +.Ft int +.Fn archive_write_set_bytes_per_block "struct archive *" "int bytes_per_block" +.Ft int +.Fn archive_write_set_bytes_in_last_block "struct archive *" "int" +.Ft int +.Fn archive_write_set_compression_bzip2 "struct archive *" +.Ft int +.Fn archive_write_set_compression_gzip "struct archive *" +.Ft int +.Fn archive_write_set_compression_none "struct archive *" +.Ft int +.Fo archive_write_set_compression_program +.Fa "struct archive *" +.Fa "const char * cmd" +.Fc +.Ft int +.Fn archive_write_set_format_cpio "struct archive *" +.Ft int +.Fn archive_write_set_format_pax "struct archive *" +.Ft int +.Fn archive_write_set_format_pax_restricted "struct archive *" +.Ft int +.Fn archive_write_set_format_shar "struct archive *" +.Ft int +.Fn archive_write_set_format_shar_binary "struct archive *" +.Ft int +.Fn archive_write_set_format_ustar "struct archive *" +.Ft int +.Fo archive_write_open +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "archive_open_callback *" +.Fa "archive_write_callback *" +.Fa "archive_close_callback *" +.Fc +.Ft int +.Fn archive_write_open_fd "struct archive *" "int fd" +.Ft int +.Fn archive_write_open_FILE "struct archive *" "FILE *file" +.Ft int +.Fn archive_write_open_filename "struct archive *" "const char *filename" +.Ft int +.Fo archive_write_open_memory +.Fa "struct archive *" +.Fa "void *buffer" +.Fa "size_t bufferSize" +.Fa "size_t *outUsed" +.Fc +.Ft int +.Fn archive_write_header "struct archive *" "struct archive_entry *" +.Ft ssize_t +.Fn archive_write_data "struct archive *" "const void *" "size_t" +.Ft int +.Fn archive_write_finish_entry "struct archive *" +.Ft int +.Fn archive_write_close "struct archive *" +.Ft int +.Fn archive_write_finish "struct archive *" +.Sh DESCRIPTION +These functions provide a complete API for creating streaming +archive files. +The general process is to first create the +.Tn struct archive +object, set any desired options, initialize the archive, append entries, then +close the archive and release all resources. +The following summary describes the functions in approximately +the order they are ordinarily used: +.Bl -tag -width indent +.It Fn archive_write_new +Allocates and initializes a +.Tn struct archive +object suitable for writing a tar archive. +.It Fn archive_write_set_bytes_per_block +Sets the block size used for writing the archive data. +Every call to the write callback function, except possibly the last one, will +use this value for the length. +The third parameter is a boolean that specifies whether or not the final block +written will be padded to the full block size. +If it is zero, the last block will not be padded. +If it is non-zero, padding will be added both before and after compression. +The default is to use a block size of 10240 bytes and to pad the last block. +Note that a block size of zero will suppress internal blocking +and cause writes to be sent directly to the write callback as they occur. +.It Fn archive_write_get_bytes_per_block +Retrieve the block size to be used for writing. +A value of -1 here indicates that the library should use default values. +A value of zero indicates that internal blocking is suppressed. +.It Fn archive_write_set_bytes_in_last_block +Sets the block size used for writing the last block. +If this value is zero, the last block will be padded to the same size +as the other blocks. +Otherwise, the final block will be padded to a multiple of this size. +In particular, setting it to 1 will cause the final block to not be padded. +For compressed output, any padding generated by this option +is applied only after the compression. +The uncompressed data is always unpadded. +The default is to pad the last block to the full block size (note that +.Fn archive_write_open_filename +will set this based on the file type). +Unlike the other +.Dq set +functions, this function can be called after the archive is opened. +.It Fn archive_write_get_bytes_in_last_block +Retrieve the currently-set value for last block size. +A value of -1 here indicates that the library should use default values. +.It Xo +.Fn archive_write_set_format_cpio , +.Fn archive_write_set_format_pax , +.Fn archive_write_set_format_pax_restricted , +.Fn archive_write_set_format_shar , +.Fn archive_write_set_format_shar_binary , +.Fn archive_write_set_format_ustar +.Xc +Sets the format that will be used for the archive. +The library can write +POSIX octet-oriented cpio format archives, +POSIX-standard +.Dq pax interchange +format archives, +traditional +.Dq shar +archives, +enhanced +.Dq binary +shar archives that store a variety of file attributes and handle binary files, +and +POSIX-standard +.Dq ustar +archives. +The pax interchange format is a backwards-compatible tar format that +adds key/value attributes to each entry and supports arbitrary +filenames, linknames, uids, sizes, etc. +.Dq Restricted pax interchange format +is the library default; this is the same as pax format, but suppresses +the pax extended header for most normal files. +In most cases, this will result in ordinary ustar archives. +.It Xo +.Fn archive_write_set_compression_bzip2 , +.Fn archive_write_set_compression_gzip , +.Fn archive_write_set_compression_none +.Xc +The resulting archive will be compressed as specified. +Note that the compressed output is always properly blocked. +.It Fn archive_write_set_compression_program +The archive will be fed into the specified compression program. +The output of that program is blocked and written to the client +write callbacks. +.It Fn archive_write_open +Freeze the settings, open the archive, and prepare for writing entries. +This is the most generic form of this function, which accepts +pointers to three callback functions which will be invoked by +the compression layer to write the constructed archive. +.It Fn archive_write_open_fd +A convenience form of +.Fn archive_write_open +that accepts a file descriptor. +The +.Fn archive_write_open_fd +function is safe for use with tape drives or other +block-oriented devices. +.It Fn archive_write_open_FILE +A convenience form of +.Fn archive_write_open +that accepts a +.Ft "FILE *" +pointer. +Note that +.Fn archive_write_open_FILE +is not safe for writing to tape drives or other devices +that require correct blocking. +.It Fn archive_write_open_file +A deprecated synonym for +.Fn archive_write_open_filename . +.It Fn archive_write_open_filename +A convenience form of +.Fn archive_write_open +that accepts a filename. +A NULL argument indicates that the output should be written to standard output; +an argument of +.Dq - +will open a file with that name. +If you have not invoked +.Fn archive_write_set_bytes_in_last_block , +then +.Fn archive_write_open_filename +will adjust the last-block padding depending on the file: +it will enable padding when writing to standard output or +to a character or block device node, it will disable padding otherwise. +You can override this by manually invoking +.Fn archive_write_set_bytes_in_last_block +before calling +.Fn archive_write_open . +The +.Fn archive_write_open_filename +function is safe for use with tape drives or other +block-oriented devices. +.It Fn archive_write_open_memory +A convenience form of +.Fn archive_write_open +that accepts a pointer to a block of memory that will receive +the archive. +The final +.Ft "size_t *" +argument points to a variable that will be updated +after each write to reflect how much of the buffer +is currently in use. +You should be careful to ensure that this variable +remains allocated until after the archive is +closed. +.It Fn archive_write_header +Build and write a header using the data in the provided +.Tn struct archive_entry +structure. +See +.Xr archive_entry 3 +for information on creating and populating +.Tn struct archive_entry +objects. +.It Fn archive_write_data +Write data corresponding to the header just written. +Returns number of bytes written or -1 on error. +.It Fn archive_write_finish_entry +Close out the entry just written. +In particular, this writes out the final padding required by some formats. +Ordinarily, clients never need to call this, as it +is called automatically by +.Fn archive_write_next_header +and +.Fn archive_write_close +as needed. +.It Fn archive_write_close +Complete the archive and invoke the close callback. +.It Fn archive_write_finish +Invokes +.Fn archive_write_close +if it was not invoked manually, then releases all resources. +Note that this function was declared to return +.Ft void +in libarchive 1.x, which made it impossible to detect errors when +.Fn archive_write_close +was invoked implicitly from this function. +This is corrected beginning with libarchive 2.0. +.El +More information about the +.Va struct archive +object and the overall design of the library can be found in the +.Xr libarchive 3 +overview. +.Sh IMPLEMENTATION +Compression support is built-in to libarchive, which uses zlib and bzlib +to handle gzip and bzip2 compression, respectively. +.Sh CLIENT CALLBACKS +To use this library, you will need to define and register +callback functions that will be invoked to write data to the +resulting archive. +These functions are registered by calling +.Fn archive_write_open : +.Bl -item -offset indent +.It +.Ft typedef int +.Fn archive_open_callback "struct archive *" "void *client_data" +.El +.Pp +The open callback is invoked by +.Fn archive_write_open . +It should return +.Cm ARCHIVE_OK +if the underlying file or data source is successfully +opened. +If the open fails, it should call +.Fn archive_set_error +to register an error code and message and return +.Cm ARCHIVE_FATAL . +.Bl -item -offset indent +.It +.Ft typedef ssize_t +.Fo archive_write_callback +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "void *buffer" +.Fa "size_t length" +.Fc +.El +.Pp +The write callback is invoked whenever the library +needs to write raw bytes to the archive. +For correct blocking, each call to the write callback function +should translate into a single +.Xr write 2 +system call. +This is especially critical when writing archives to tape drives. +On success, the write callback should return the +number of bytes actually written. +On error, the callback should invoke +.Fn archive_set_error +to register an error code and message and return -1. +.Bl -item -offset indent +.It +.Ft typedef int +.Fn archive_close_callback "struct archive *" "void *client_data" +.El +.Pp +The close callback is invoked by archive_close when +the archive processing is complete. +The callback should return +.Cm ARCHIVE_OK +on success. +On failure, the callback should invoke +.Fn archive_set_error +to register an error code and message and +return +.Cm ARCHIVE_FATAL. +.Sh EXAMPLE +The following sketch illustrates basic usage of the library. +In this example, +the callback functions are simply wrappers around the standard +.Xr open 2 , +.Xr write 2 , +and +.Xr close 2 +system calls. +.Bd -literal -offset indent +#include <sys/stat.h> +#include <archive.h> +#include <archive_entry.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +struct mydata { + const char *name; + int fd; +}; + +int +myopen(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + mydata->fd = open(mydata->name, O_WRONLY | O_CREAT, 0644); + if (mydata->fd >= 0) + return (ARCHIVE_OK); + else + return (ARCHIVE_FATAL); +} + +ssize_t +mywrite(struct archive *a, void *client_data, void *buff, size_t n) +{ + struct mydata *mydata = client_data; + + return (write(mydata->fd, buff, n)); +} + +int +myclose(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + if (mydata->fd > 0) + close(mydata->fd); + return (0); +} + +void +write_archive(const char *outname, const char **filename) +{ + struct mydata *mydata = malloc(sizeof(struct mydata)); + struct archive *a; + struct archive_entry *entry; + struct stat st; + char buff[8192]; + int len; + int fd; + + a = archive_write_new(); + mydata->name = outname; + archive_write_set_compression_gzip(a); + archive_write_set_format_ustar(a); + archive_write_open(a, mydata, myopen, mywrite, myclose); + while (*filename) { + stat(*filename, &st); + entry = archive_entry_new(); + archive_entry_copy_stat(entry, &st); + archive_entry_set_pathname(entry, *filename); + 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); + filename++; + } + archive_write_finish(a); +} + +int main(int argc, const char **argv) +{ + const char *outname; + argv++; + outname = argv++; + write_archive(outname, argv); + return 0; +} +.Ed +.Sh RETURN VALUES +Most functions return +.Cm ARCHIVE_OK +(zero) on success, or one of several non-zero +error codes for errors. +Specific error codes include: +.Cm ARCHIVE_RETRY +for operations that might succeed if retried, +.Cm ARCHIVE_WARN +for unusual conditions that do not prevent further operations, and +.Cm ARCHIVE_FATAL +for serious errors that make remaining operations impossible. +The +.Fn archive_errno +and +.Fn archive_error_string +functions can be used to retrieve an appropriate error code and a +textual error message. +.Pp +.Fn archive_write_new +returns a pointer to a newly-allocated +.Tn struct archive +object. +.Pp +.Fn archive_write_data +returns a count of the number of bytes actually written. +On error, -1 is returned and the +.Fn archive_errno +and +.Fn archive_error_string +functions will return appropriate values. +Note that if the client-provided write callback function +returns a non-zero value, that error will be propagated back to the caller +through whatever API function resulted in that call, which +may include +.Fn archive_write_header , +.Fn archive_write_data , +.Fn archive_write_close , +or +.Fn archive_write_finish . +The client callback can call +.Fn archive_set_error +to provide values that can then be retrieved by +.Fn archive_errno +and +.Fn archive_error_string . +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr tar 5 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +There are many peculiar bugs in historic tar implementations that may cause +certain programs to reject archives written by this library. +For example, several historic implementations calculated header checksums +incorrectly and will thus reject valid archives; GNU tar does not fully support +pax interchange format; some old tar implementations required specific +field terminations. +.Pp +The default pax interchange format eliminates most of the historic +tar limitations and provides a generic key/value attribute facility +for vendor-defined extensions. +One oversight in POSIX is the failure to provide a standard attribute +for large device numbers. +This library uses +.Dq SCHILY.devminor +and +.Dq SCHILY.devmajor +for device numbers that exceed the range supported by the backwards-compatible +ustar header. +These keys are compatible with Joerg Schilling's +.Nm star +archiver. +Other implementations may not recognize these keys and will thus be unable +to correctly restore device nodes with large device numbers from archives +created by this library. diff --git a/libarchive/archive_write.c b/libarchive/archive_write.c new file mode 100644 index 00000000..1a3ddc9a --- /dev/null +++ b/libarchive/archive_write.c @@ -0,0 +1,358 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write.c,v 1.27 2008/03/14 23:09:02 kientzle Exp $"); + +/* + * This file contains the "essential" portions of the write API, that + * is, stuff that will essentially always be used by any client that + * actually needs to write a archive. Optional pieces have been, as + * far as possible, separated out into separate files to reduce + * needlessly bloating statically-linked clients. + */ + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#include <time.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static struct archive_vtable *archive_write_vtable(void); + +static int _archive_write_close(struct archive *); +static int _archive_write_finish(struct archive *); +static int _archive_write_header(struct archive *, struct archive_entry *); +static int _archive_write_finish_entry(struct archive *); +static ssize_t _archive_write_data(struct archive *, const void *, size_t); + +static struct archive_vtable * +archive_write_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_write_close = _archive_write_close; + av.archive_write_finish = _archive_write_finish; + av.archive_write_header = _archive_write_header; + av.archive_write_finish_entry = _archive_write_finish_entry; + av.archive_write_data = _archive_write_data; + } + return (&av); +} + +/* + * Allocate, initialize and return an archive object. + */ +struct archive * +archive_write_new(void) +{ + struct archive_write *a; + unsigned char *nulls; + + a = (struct archive_write *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_WRITE_MAGIC; + a->archive.state = ARCHIVE_STATE_NEW; + a->archive.vtable = archive_write_vtable(); + /* + * The value 10240 here matches the traditional tar default, + * but is otherwise arbitrary. + * TODO: Set the default block size from the format selected. + */ + a->bytes_per_block = 10240; + a->bytes_in_last_block = -1; /* Default */ + + /* 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->archive); + return (&a->archive); +} + +/* + * Set the block size. Returns 0 if successful. + */ +int +archive_write_set_bytes_per_block(struct archive *_a, int bytes_per_block) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_bytes_per_block"); + a->bytes_per_block = bytes_per_block; + return (ARCHIVE_OK); +} + +/* + * Get the current block size. -1 if it has never been set. + */ +int +archive_write_get_bytes_per_block(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_get_bytes_per_block"); + return (a->bytes_per_block); +} + +/* + * Set the size for the last block. + * Returns 0 if successful. + */ +int +archive_write_set_bytes_in_last_block(struct archive *_a, int bytes) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_set_bytes_in_last_block"); + a->bytes_in_last_block = bytes; + return (ARCHIVE_OK); +} + +/* + * Return the value set above. -1 indicates it has not been set. + */ +int +archive_write_get_bytes_in_last_block(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_get_bytes_in_last_block"); + return (a->bytes_in_last_block); +} + + +/* + * dev/ino of a file to be rejected. Used to prevent adding + * an archive to itself recursively. + */ +int +archive_write_set_skip_file(struct archive *_a, dev_t d, ino_t i) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_set_skip_file"); + a->skip_file_dev = d; + a->skip_file_ino = i; + 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) +{ + struct archive_write *a = (struct archive_write *)_a; + int ret; + + ret = ARCHIVE_OK; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_open"); + archive_clear_error(&a->archive); + a->archive.state = ARCHIVE_STATE_HEADER; + a->client_data = client_data; + a->client_writer = writer; + a->client_opener = opener; + a->client_closer = closer; + ret = (a->compressor.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. + */ +static int +_archive_write_close(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = ARCHIVE_OK, r1 = ARCHIVE_OK; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_close"); + + /* Finish the last entry. */ + if (a->archive.state & ARCHIVE_STATE_DATA) + r = ((a->format_finish_entry)(a)); + + /* Finish off the archive. */ + if (a->format_finish != NULL) { + r1 = (a->format_finish)(a); + if (r1 < r) + r = r1; + } + + /* Release format resources. */ + if (a->format_destroy != NULL) { + r1 = (a->format_destroy)(a); + if (r1 < r) + r = r1; + } + + /* Finish the compression and close the stream. */ + if (a->compressor.finish != NULL) { + r1 = (a->compressor.finish)(a); + if (r1 < r) + r = r1; + } + + /* Close out the client stream. */ + if (a->client_closer != NULL) { + r1 = (a->client_closer)(&a->archive, a->client_data); + if (r1 < r) + r = r1; + } + + a->archive.state = ARCHIVE_STATE_CLOSED; + return (r); +} + +/* + * Destroy the archive structure. + */ +static int +_archive_write_finish(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = ARCHIVE_OK; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_finish"); + if (a->archive.state != ARCHIVE_STATE_CLOSED) + r = archive_write_close(&a->archive); + + /* Release various dynamic buffers. */ + free((void *)(uintptr_t)(const void *)a->nulls); + archive_string_free(&a->archive.error_string); + a->archive.magic = 0; + free(a); + return (r); +} + +/* + * Write the appropriate header. + */ +static int +_archive_write_header(struct archive *_a, struct archive_entry *entry) +{ + struct archive_write *a = (struct archive_write *)_a; + int ret, r2; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_DATA | ARCHIVE_STATE_HEADER, "archive_write_header"); + archive_clear_error(&a->archive); + + /* In particular, "retry" and "fatal" get returned immediately. */ + ret = archive_write_finish_entry(&a->archive); + if (ret < ARCHIVE_OK && ret != ARCHIVE_WARN) + return (ret); + + if (a->skip_file_dev != 0 && + archive_entry_dev(entry) == a->skip_file_dev && + a->skip_file_ino != 0 && + archive_entry_ino(entry) == a->skip_file_ino) { + archive_set_error(&a->archive, 0, + "Can't add archive to itself"); + return (ARCHIVE_FAILED); + } + + /* Format and write header. */ + r2 = ((a->format_write_header)(a, entry)); + if (r2 < ret) + ret = r2; + + a->archive.state = ARCHIVE_STATE_DATA; + return (ret); +} + +static int +_archive_write_finish_entry(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int ret = ARCHIVE_OK; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_finish_entry"); + if (a->archive.state & ARCHIVE_STATE_DATA) + ret = (a->format_finish_entry)(a); + a->archive.state = ARCHIVE_STATE_HEADER; + return (ret); +} + +/* + * Note that the compressor is responsible for blocking. + */ +static ssize_t +_archive_write_data(struct archive *_a, const void *buff, size_t s) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data"); + archive_clear_error(&a->archive); + return ((a->format_write_data)(a, buff, s)); +} diff --git a/libarchive/archive_write_disk.3 b/libarchive/archive_write_disk.3 new file mode 100644 index 00000000..5d836e10 --- /dev/null +++ b/libarchive/archive_write_disk.3 @@ -0,0 +1,371 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_write_disk.3,v 1.2 2008/03/10 14:44:41 jkoshy Exp $ +.\" +.Dd March 2, 2007 +.Dt archive_write_disk 3 +.Os +.Sh NAME +.Nm archive_write_disk_new , +.Nm archive_write_disk_set_options , +.Nm archive_write_disk_set_skip_file , +.Nm archive_write_disk_set_group_lookup , +.Nm archive_write_disk_set_standard_lookup , +.Nm archive_write_disk_set_user_lookup , +.Nm archive_write_header , +.Nm archive_write_data , +.Nm archive_write_finish_entry , +.Nm archive_write_close , +.Nm archive_write_finish +.Nd functions for creating objects on disk +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_write_disk_new "void" +.Ft int +.Fn archive_write_disk_set_options "struct archive *" "int flags" +.Ft int +.Fn archive_write_disk_set_skip_file "struct archive *" "dev_t" "ino_t" +.Ft int +.Fo archive_write_disk_set_group_lookup +.Fa "struct archive *" +.Fa "void *" +.Fa "gid_t (*)(void *, const char *gname, gid_t gid)" +.Fa "void (*cleanup)(void *)" +.Fc +.Ft int +.Fn archive_write_disk_set_standard_lookup "struct archive *" +.Ft int +.Fo archive_write_disk_set_user_lookup +.Fa "struct archive *" +.Fa "void *" +.Fa "uid_t (*)(void *, const char *uname, uid_t uid)" +.Fa "void (*cleanup)(void *)" +.Fc +.Ft int +.Fn archive_write_header "struct archive *" "struct archive_entry *" +.Ft ssize_t +.Fn archive_write_data "struct archive *" "const void *" "size_t" +.Ft int +.Fn archive_write_finish_entry "struct archive *" +.Ft int +.Fn archive_write_close "struct archive *" +.Ft int +.Fn archive_write_finish "struct archive *" +.Sh DESCRIPTION +These functions provide a complete API for creating objects on +disk from +.Tn struct archive_entry +descriptions. +They are most naturally used when extracting objects from an archive +using the +.Fn archive_read +interface. +The general process is to read +.Tn struct archive_entry +objects from an archive, then write those objects to a +.Tn struct archive +object created using the +.Fn archive_write_disk +family functions. +This interface is deliberately very similar to the +.Fn archive_write +interface used to write objects to a streaming archive. +.Bl -tag -width indent +.It Fn archive_write_disk_new +Allocates and initializes a +.Tn struct archive +object suitable for writing objects to disk. +.It Fn archive_write_disk_set_skip_file +Records the device and inode numbers of a file that should not be +overwritten. +This is typically used to ensure that an extraction process does not +overwrite the archive from which objects are being read. +This capability is technically unnecessary but can be a significant +performance optimization in practice. +.It Fn archive_write_disk_set_options +The options field consists of a bitwise OR of one or more of the +following values: +.Bl -tag -compact -width "indent" +.It Cm ARCHIVE_EXTRACT_OWNER +The user and group IDs should be set on the restored file. +By default, the user and group IDs are not restored. +.It Cm ARCHIVE_EXTRACT_PERM +Full permissions (including SGID, SUID, and sticky bits) should +be restored exactly as specified, without obeying the +current umask. +Note that SUID and SGID bits can only be restored if the +user and group ID of the object on disk are correct. +If +.Cm ARCHIVE_EXTRACT_OWNER +is not specified, then SUID and SGID bits will only be restored +if the default user and group IDs of newly-created objects on disk +happen to match those specified in the archive entry. +By default, only basic permissions are restored, and umask is obeyed. +.It Cm ARCHIVE_EXTRACT_TIME +The timestamps (mtime, ctime, and atime) should be restored. +By default, they are ignored. +Note that restoring of atime is not currently supported. +.It Cm ARCHIVE_EXTRACT_NO_OVERWRITE +Existing files on disk will not be overwritten. +By default, existing regular files are truncated and overwritten; +existing directories will have their permissions updated; +other pre-existing objects are unlinked and recreated from scratch. +.It Cm ARCHIVE_EXTRACT_UNLINK +Existing files on disk will be unlinked before any attempt to +create them. +In some cases, this can prove to be a significant performance improvement. +By default, existing files are truncated and rewritten, but +the file is not recreated. +In particular, the default behavior does not break existing hard links. +.It Cm ARCHIVE_EXTRACT_ACL +Attempt to restore ACLs. +By default, extended ACLs are ignored. +.It Cm ARCHIVE_EXTRACT_FFLAGS +Attempt to restore extended file flags. +By default, file flags are ignored. +.It Cm ARCHIVE_EXTRACT_XATTR +Attempt to restore POSIX.1e extended attributes. +By default, they are ignored. +.It Cm ARCHIVE_EXTRACT_SECURE_SYMLINKS +Refuse to extract any object whose final location would be altered +by a symlink on disk. +This is intended to help guard against a variety of mischief +caused by archives that (deliberately or otherwise) extract +files outside of the current directory. +The default is not to perform this check. +If +.Cm ARCHIVE_EXTRACT_UNLINK +is specified together with this option, the library will +remove any intermediate symlinks it finds and return an +error only if such symlink could not be removed. +.It Cm ARCHIVE_EXTRACT_SECURE_NODOTDOT +Refuse to extract a path that contains a +.Pa .. +element anywhere within it. +The default is to not refuse such paths. +Note that paths ending in +.Pa .. +always cause an error, regardless of this flag. +.El +.It Xo +.Fn archive_write_disk_set_group_lookup , +.Fn archive_write_disk_set_user_lookup +.Xc +The +.Tn struct archive_entry +objects contain both names and ids that can be used to identify users +and groups. +These names and ids describe the ownership of the file itself and +also appear in ACL lists. +By default, the library uses the ids and ignores the names, but +this can be overridden by registering user and group lookup functions. +To register, you must provide a lookup function which +accepts both a name and id and returns a suitable id. +You may also provide a +.Tn void * +pointer to a private data structure and a cleanup function for +that data. +The cleanup function will be invoked when the +.Tn struct archive +object is destroyed. +.It Fn archive_write_disk_set_standard_lookup +This convenience function installs a standard set of user +and group lookup functions. +These functions use +.Xr getpwnam 3 +and +.Xr getgrnam 3 +to convert names to ids, defaulting to the ids if the names cannot +be looked up. +These functions also implement a simple memory cache to reduce +the number of calls to +.Xr getpwnam 3 +and +.Xr getgrnam 3 . +.It Fn archive_write_header +Build and write a header using the data in the provided +.Tn struct archive_entry +structure. +See +.Xr archive_entry 3 +for information on creating and populating +.Tn struct archive_entry +objects. +.It Fn archive_write_data +Write data corresponding to the header just written. +Returns number of bytes written or -1 on error. +.It Fn archive_write_finish_entry +Close out the entry just written. +Ordinarily, clients never need to call this, as it +is called automatically by +.Fn archive_write_next_header +and +.Fn archive_write_close +as needed. +.It Fn archive_write_close +Set any attributes that could not be set during the initial restore. +For example, directory timestamps are not restored initially because +restoring a subsequent file would alter that timestamp. +Similarly, non-writable directories are initially created with +write permissions (so that their contents can be restored). +The +.Nm +library maintains a list of all such deferred attributes and +sets them when this function is invoked. +.It Fn archive_write_finish +Invokes +.Fn archive_write_close +if it was not invoked manually, then releases all resources. +.El +More information about the +.Va struct archive +object and the overall design of the library can be found in the +.Xr libarchive 3 +overview. +Many of these functions are also documented under +.Xr archive_write 3 . +.Sh RETURN VALUES +Most functions return +.Cm ARCHIVE_OK +(zero) on success, or one of several non-zero +error codes for errors. +Specific error codes include: +.Cm ARCHIVE_RETRY +for operations that might succeed if retried, +.Cm ARCHIVE_WARN +for unusual conditions that do not prevent further operations, and +.Cm ARCHIVE_FATAL +for serious errors that make remaining operations impossible. +The +.Fn archive_errno +and +.Fn archive_error_string +functions can be used to retrieve an appropriate error code and a +textual error message. +.Pp +.Fn archive_write_disk_new +returns a pointer to a newly-allocated +.Tn struct archive +object. +.Pp +.Fn archive_write_data +returns a count of the number of bytes actually written. +On error, -1 is returned and the +.Fn archive_errno +and +.Fn archive_error_string +functions will return appropriate values. +.Sh SEE ALSO +.Xr archive_read 3 , +.Xr archive_write 3 , +.Xr tar 1 , +.Xr libarchive 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +The +.Nm archive_write_disk +interface was added to +.Nm libarchive 2.0 +and first appeared in +.Fx 6.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +Directories are actually extracted in two distinct phases. +Directories are created during +.Fn archive_write_header , +but final permissions are not set until +.Fn archive_write_close . +This separation is necessary to correctly handle borderline +cases such as a non-writable directory containing +files, but can cause unexpected results. +In particular, directory permissions are not fully +restored until the archive is closed. +If you use +.Xr chdir 2 +to change the current directory between calls to +.Fn archive_read_extract +or before calling +.Fn archive_read_close , +you may confuse the permission-setting logic with +the result that directory permissions are restored +incorrectly. +.Pp +The library attempts to create objects with filenames longer than +.Cm PATH_MAX +by creating prefixes of the full path and changing the current directory. +Currently, this logic is limited in scope; the fixup pass does +not work correctly for such objects and the symlink security check +option disables the support for very long pathnames. +.Pp +Restoring the path +.Pa aa/../bb +does create each intermediate directory. +In particular, the directory +.Pa aa +is created as well as the final object +.Pa bb . +In theory, this can be exploited to create an entire directory heirarchy +with a single request. +Of course, this does not work if the +.Cm ARCHIVE_EXTRACT_NODOTDOT +option is specified. +.Pp +Implicit directories are always created obeying the current umask. +Explicit objects are created obeying the current umask unless +.Cm ARCHIVE_EXTRACT_PERM +is specified, in which case they current umask is ignored. +.Pp +SGID and SUID bits are restored only if the correct user and +group could be set. +If +.Cm ARCHIVE_EXTRACT_OWNER +is not specified, then no attempt is made to set the ownership. +In this case, SGID and SUID bits are restored only if the +user and group of the final object happen to match those specified +in the entry. +.Pp +The +.Dq standard +user-id and group-id lookup functions are not the defaults because +.Xr getgrnam 3 +and +.Xr getpwnam 3 +are sometimes too large for particular applications. +The current design allows the application author to use a more +compact implementation when appropriate. +.Pp +There should be a corresponding +.Nm archive_read_disk +interface that walks a directory heirarchy and returns archive +entry objects.
\ No newline at end of file diff --git a/libarchive/archive_write_disk.c b/libarchive/archive_write_disk.c new file mode 100644 index 00000000..620beac4 --- /dev/null +++ b/libarchive/archive_write_disk.c @@ -0,0 +1,2173 @@ +/*- + * Copyright (c) 2003-2007 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. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_disk.c,v 1.24 2008/03/15 04:20:50 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_ACL_H +#include <sys/acl.h> +#endif +#ifdef HAVE_ATTR_XATTR_H +#include <attr/xattr.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#ifdef HAVE_SYS_UTIME_H +#include <sys/utime.h> +#endif + +#ifdef HAVE_EXT2FS_EXT2_FS_H +#include <ext2fs/ext2_fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_LINUX_FS_H +#include <linux/fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_LINUX_EXT2_FS_H +#include <linux/ext2_fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_UTIME_H +#include <utime.h> +#endif + +#include "archive.h" +#include "archive_string.h" +#include "archive_entry.h" +#include "archive_private.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct fixup_entry { + struct fixup_entry *next; + mode_t mode; + int64_t mtime; + int64_t atime; + unsigned long mtime_nanos; + unsigned long atime_nanos; + unsigned long fflags_set; + int fixup; /* bitmask of what needs fixing */ + char *name; +}; + +/* + * We use a bitmask to track which operations remain to be done for + * this file. In particular, this helps us avoid unnecessary + * operations when it's possible to take care of one step as a + * side-effect of another. For example, mkdir() can specify the mode + * for the newly-created object but symlink() cannot. This means we + * can skip chmod() if mkdir() succeeded, but we must explicitly + * chmod() if we're trying to create a directory that already exists + * (mkdir() failed) or if we're restoring a symlink. Similarly, we + * need to verify UID/GID before trying to restore SUID/SGID bits; + * that verification can occur explicitly through a stat() call or + * implicitly because of a successful chown() call. + */ +#define TODO_MODE_FORCE 0x40000000 +#define TODO_MODE_BASE 0x20000000 +#define TODO_SUID 0x10000000 +#define TODO_SUID_CHECK 0x08000000 +#define TODO_SGID 0x04000000 +#define TODO_SGID_CHECK 0x02000000 +#define TODO_MODE (TODO_MODE_BASE|TODO_SUID|TODO_SGID) +#define TODO_TIMES ARCHIVE_EXTRACT_TIME +#define TODO_OWNER ARCHIVE_EXTRACT_OWNER +#define TODO_FFLAGS ARCHIVE_EXTRACT_FFLAGS +#define TODO_ACLS ARCHIVE_EXTRACT_ACL +#define TODO_XATTR ARCHIVE_EXTRACT_XATTR + +struct archive_write_disk { + struct archive archive; + + mode_t user_umask; + struct fixup_entry *fixup_list; + struct fixup_entry *current_fixup; + uid_t user_uid; + dev_t skip_file_dev; + ino_t skip_file_ino; + + gid_t (*lookup_gid)(void *private, const char *gname, gid_t gid); + void (*cleanup_gid)(void *private); + void *lookup_gid_data; + uid_t (*lookup_uid)(void *private, const char *gname, gid_t gid); + void (*cleanup_uid)(void *private); + void *lookup_uid_data; + + /* + * Full path of last file to satisfy symlink checks. + */ + struct archive_string path_safe; + + /* + * Cached stat data from disk for the current entry. + * If this is valid, pst points to st. Otherwise, + * pst is null. + */ + struct stat st; + struct stat *pst; + + /* Information about the object being restored right now. */ + struct archive_entry *entry; /* Entry being extracted. */ + char *name; /* Name of entry, possibly edited. */ + struct archive_string _name_data; /* backing store for 'name' */ + /* Tasks remaining for this object. */ + int todo; + /* Tasks deferred until end-of-archive. */ + int deferred; + /* Options requested by the client. */ + int flags; + /* Handle for the file we're restoring. */ + int fd; + /* Current offset for writing data to the file. */ + off_t offset; + /* Maximum size of file. */ + off_t filesize; + /* Dir we were in before this restore; only for deep paths. */ + int restore_pwd; + /* Mode we should use for this entry; affected by _PERM and umask. */ + mode_t mode; + /* UID/GID to use in restoring this entry. */ + uid_t uid; + gid_t gid; +}; + +/* + * Default mode for dirs created automatically (will be modified by umask). + * Note that POSIX specifies 0777 for implicity-created dirs, "modified + * by the process' file creation mask." + */ +#define DEFAULT_DIR_MODE 0777 +/* + * Dir modes are restored in two steps: During the extraction, the permissions + * in the archive are modified to match the following limits. During + * the post-extract fixup pass, the permissions from the archive are + * applied. + */ +#define MINIMUM_DIR_MODE 0700 +#define MAXIMUM_DIR_MODE 0775 + +static int check_symlinks(struct archive_write_disk *); +static int create_filesystem_object(struct archive_write_disk *); +static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); +#ifdef HAVE_FCHDIR +static void edit_deep_directories(struct archive_write_disk *ad); +#endif +static int cleanup_pathname(struct archive_write_disk *); +static int create_dir(struct archive_write_disk *, char *); +static int create_parent_dir(struct archive_write_disk *, char *); +static int older(struct stat *, struct archive_entry *); +static int restore_entry(struct archive_write_disk *); +#ifdef HAVE_POSIX_ACL +static int set_acl(struct archive_write_disk *, int fd, struct archive_entry *, + acl_type_t, int archive_entry_acl_type, const char *tn); +#endif +static int set_acls(struct archive_write_disk *); +static int set_xattrs(struct archive_write_disk *); +static int set_fflags(struct archive_write_disk *); +static int set_fflags_platform(struct archive_write_disk *, int fd, + const char *name, mode_t mode, + unsigned long fflags_set, unsigned long fflags_clear); +static int set_ownership(struct archive_write_disk *); +static int set_mode(struct archive_write_disk *, int mode); +static int set_time(struct archive_write_disk *); +static struct fixup_entry *sort_dir_list(struct fixup_entry *p); +static gid_t trivial_lookup_gid(void *, const char *, gid_t); +static uid_t trivial_lookup_uid(void *, const char *, uid_t); + + +static struct archive_vtable *archive_write_disk_vtable(void); + +static int _archive_write_close(struct archive *); +static int _archive_write_finish(struct archive *); +static int _archive_write_header(struct archive *, struct archive_entry *); +static int _archive_write_finish_entry(struct archive *); +static ssize_t _archive_write_data(struct archive *, const void *, size_t); +static ssize_t _archive_write_data_block(struct archive *, const void *, size_t, off_t); + +static struct archive_vtable * +archive_write_disk_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_write_close = _archive_write_close; + av.archive_write_finish = _archive_write_finish; + av.archive_write_header = _archive_write_header; + av.archive_write_finish_entry = _archive_write_finish_entry; + av.archive_write_data = _archive_write_data; + av.archive_write_data_block = _archive_write_data_block; + } + return (&av); +} + + +int +archive_write_disk_set_options(struct archive *_a, int flags) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + + a->flags = flags; + return (ARCHIVE_OK); +} + + +/* + * Extract this entry to disk. + * + * TODO: Validate hardlinks. According to the standards, we're + * supposed to check each extracted hardlink and squawk if it refers + * to a file that we didn't restore. I'm not entirely convinced this + * is a good idea, but more importantly: Is there any way to validate + * hardlinks without keeping a complete list of filenames from the + * entire archive?? Ugh. + * + */ +static int +_archive_write_header(struct archive *_a, struct archive_entry *entry) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + struct fixup_entry *fe; + int ret, r; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_disk_header"); + archive_clear_error(&a->archive); + if (a->archive.state & ARCHIVE_STATE_DATA) { + r = _archive_write_finish_entry(&a->archive); + if (r != ARCHIVE_OK) + return (r); + } + + /* Set up for this particular entry. */ + a->pst = NULL; + a->current_fixup = NULL; + a->deferred = 0; + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->entry = archive_entry_clone(entry); + a->fd = -1; + a->offset = 0; + a->uid = a->user_uid; + a->mode = archive_entry_mode(a->entry); + a->filesize = archive_entry_size(a->entry); + archive_strcpy(&(a->_name_data), archive_entry_pathname(a->entry)); + a->name = a->_name_data.s; + archive_clear_error(&a->archive); + + /* + * Clean up the requested path. This is necessary for correct + * dir restores; the dir restore logic otherwise gets messed + * up by nonsense like "dir/.". + */ + ret = cleanup_pathname(a); + if (ret != ARCHIVE_OK) + return (ret); + + /* + * Set the umask to zero so we get predictable mode settings. + * This gets done on every call to _write_header in case the + * user edits their umask during the extraction for some + * reason. This will be reset before we return. Note that we + * don't need to do this in _finish_entry, as the chmod(), etc, + * system calls don't obey umask. + */ + a->user_umask = umask(0); + /* From here on, early exit requires "goto done" to clean up. */ + + /* Figure out what we need to do for this entry. */ + a->todo = TODO_MODE_BASE; + if (a->flags & ARCHIVE_EXTRACT_PERM) { + a->todo |= TODO_MODE_FORCE; /* Be pushy about permissions. */ + /* + * SGID requires an extra "check" step because we + * cannot easily predict the GID that the system will + * assign. (Different systems assign GIDs to files + * based on a variety of criteria, including process + * credentials and the gid of the enclosing + * directory.) We can only restore the SGID bit if + * the file has the right GID, and we only know the + * GID if we either set it (see set_ownership) or if + * we've actually called stat() on the file after it + * was restored. Since there are several places at + * which we might verify the GID, we need a TODO bit + * to keep track. + */ + if (a->mode & S_ISGID) + a->todo |= TODO_SGID | TODO_SGID_CHECK; + /* + * Verifying the SUID is simpler, but can still be + * done in multiple ways, hence the separate "check" bit. + */ + if (a->mode & S_ISUID) + a->todo |= TODO_SUID | TODO_SUID_CHECK; + } else { + /* + * User didn't request full permissions, so don't + * restore SUID, SGID bits and obey umask. + */ + a->mode &= ~S_ISUID; + a->mode &= ~S_ISGID; + a->mode &= ~S_ISVTX; + a->mode &= ~a->user_umask; + } + if (a->flags & ARCHIVE_EXTRACT_OWNER) + a->todo |= TODO_OWNER; + if (a->flags & ARCHIVE_EXTRACT_TIME) + a->todo |= TODO_TIMES; + if (a->flags & ARCHIVE_EXTRACT_ACL) + a->todo |= TODO_ACLS; + if (a->flags & ARCHIVE_EXTRACT_FFLAGS) + a->todo |= TODO_FFLAGS; + if (a->flags & ARCHIVE_EXTRACT_SECURE_SYMLINKS) { + ret = check_symlinks(a); + if (ret != ARCHIVE_OK) + goto done; + } +#ifdef HAVE_FCHDIR + /* If path exceeds PATH_MAX, shorten the path. */ + edit_deep_directories(a); +#endif + + ret = restore_entry(a); + +#ifdef HAVE_FCHDIR + /* If we changed directory above, restore it here. */ + if (a->restore_pwd >= 0) { + fchdir(a->restore_pwd); + close(a->restore_pwd); + a->restore_pwd = -1; + } +#endif + + /* + * Fixup uses the unedited pathname from archive_entry_pathname(), + * because it is relative to the base dir and the edited path + * might be relative to some intermediate dir as a result of the + * deep restore logic. + */ + if (a->deferred & TODO_MODE) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->fixup |= TODO_MODE_BASE; + fe->mode = a->mode; + } + + if (a->deferred & TODO_TIMES) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->fixup |= TODO_TIMES; + fe->mtime = archive_entry_mtime(entry); + fe->mtime_nanos = archive_entry_mtime_nsec(entry); + fe->atime = archive_entry_atime(entry); + fe->atime_nanos = archive_entry_atime_nsec(entry); + } + + if (a->deferred & TODO_FFLAGS) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->fixup |= TODO_FFLAGS; + /* TODO: Complete this.. defer fflags from below. */ + } + + /* We've created the object and are ready to pour data into it. */ + if (ret == ARCHIVE_OK) + a->archive.state = ARCHIVE_STATE_DATA; + /* + * If it's not open, tell our client not to try writing. + * In particular, dirs, links, etc, don't get written to. + */ + if (a->fd < 0) { + archive_entry_set_size(entry, 0); + a->filesize = 0; + } +done: + /* Restore the user's umask before returning. */ + umask(a->user_umask); + + return (ret); +} + +int +archive_write_disk_set_skip_file(struct archive *_a, dev_t d, ino_t i) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_skip_file"); + a->skip_file_dev = d; + a->skip_file_ino = i; + return (ARCHIVE_OK); +} + +static ssize_t +_archive_write_data_block(struct archive *_a, + const void *buff, size_t size, off_t offset) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + ssize_t bytes_written = 0; + int r = ARCHIVE_OK; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_disk_block"); + if (a->fd < 0) { + archive_set_error(&a->archive, 0, "File not open"); + return (ARCHIVE_WARN); + } + archive_clear_error(&a->archive); + + /* Seek if necessary to the specified offset. */ + if (offset != a->offset) { + if (lseek(a->fd, offset, SEEK_SET) < 0) { + archive_set_error(&a->archive, errno, "Seek failed"); + return (ARCHIVE_WARN); + } + a->offset = offset; + } + + /* Write the data. */ + while (size > 0 && a->offset < a->filesize) { + if ((off_t)(a->offset + size) > a->filesize) { + size = (size_t)(a->filesize - a->offset); + archive_set_error(&a->archive, errno, + "Write request too large"); + r = ARCHIVE_WARN; + } + bytes_written = write(a->fd, buff, size); + if (bytes_written < 0) { + archive_set_error(&a->archive, errno, "Write failed"); + return (ARCHIVE_WARN); + } + size -= bytes_written; + a->offset += bytes_written; + } + return (r); +} + +static ssize_t +_archive_write_data(struct archive *_a, const void *buff, size_t size) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + off_t offset; + int r; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data"); + if (a->fd < 0) + return (ARCHIVE_OK); + + offset = a->offset; + r = _archive_write_data_block(_a, buff, size, a->offset); + if (r < ARCHIVE_OK) + return (r); + return (a->offset - offset); +} + +static int +_archive_write_finish_entry(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + int ret = ARCHIVE_OK; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_finish_entry"); + if (a->archive.state & ARCHIVE_STATE_HEADER) + return (ARCHIVE_OK); + archive_clear_error(&a->archive); + + /* Restore metadata. */ + + /* + * Look up the "real" UID only if we're going to need it. We + * need this for TODO_SGID because chown() requires both. + */ + if (a->todo & (TODO_OWNER | TODO_SUID | TODO_SGID)) { + a->uid = a->lookup_uid(a->lookup_uid_data, + archive_entry_uname(a->entry), + archive_entry_uid(a->entry)); + } + /* Look up the "real" GID only if we're going to need it. */ + if (a->todo & (TODO_OWNER | TODO_SGID | TODO_SUID)) { + a->gid = a->lookup_gid(a->lookup_gid_data, + archive_entry_gname(a->entry), + archive_entry_gid(a->entry)); + } + /* + * If restoring ownership, do it before trying to restore suid/sgid + * bits. If we set the owner, we know what it is and can skip + * a stat() call to examine the ownership of the file on disk. + */ + if (a->todo & TODO_OWNER) + ret = set_ownership(a); + if (a->todo & TODO_MODE) { + int r2 = set_mode(a, a->mode); + if (r2 < ret) ret = r2; + } + if (a->todo & TODO_TIMES) { + int r2 = set_time(a); + if (r2 < ret) ret = r2; + } + if (a->todo & TODO_ACLS) { + int r2 = set_acls(a); + if (r2 < ret) ret = r2; + } + if (a->todo & TODO_XATTR) { + int r2 = set_xattrs(a); + if (r2 < ret) ret = r2; + } + if (a->todo & TODO_FFLAGS) { + int r2 = set_fflags(a); + if (r2 < ret) ret = r2; + } + + /* If there's an fd, we can close it now. */ + if (a->fd >= 0) { + close(a->fd); + a->fd = -1; + } + /* If there's an entry, we can release it now. */ + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->archive.state = ARCHIVE_STATE_HEADER; + return (ret); +} + +int +archive_write_disk_set_group_lookup(struct archive *_a, + void *private_data, + gid_t (*lookup_gid)(void *private, const char *gname, gid_t gid), + void (*cleanup_gid)(void *private)) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_group_lookup"); + + a->lookup_gid = lookup_gid; + a->cleanup_gid = cleanup_gid; + a->lookup_gid_data = private_data; + return (ARCHIVE_OK); +} + +int +archive_write_disk_set_user_lookup(struct archive *_a, + void *private_data, + uid_t (*lookup_uid)(void *private, const char *uname, uid_t uid), + void (*cleanup_uid)(void *private)) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_user_lookup"); + + a->lookup_uid = lookup_uid; + a->cleanup_uid = cleanup_uid; + a->lookup_uid_data = private_data; + return (ARCHIVE_OK); +} + + +/* + * Create a new archive_write_disk object and initialize it with global state. + */ +struct archive * +archive_write_disk_new(void) +{ + struct archive_write_disk *a; + + a = (struct archive_write_disk *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_WRITE_DISK_MAGIC; + /* We're ready to write a header immediately. */ + a->archive.state = ARCHIVE_STATE_HEADER; + a->archive.vtable = archive_write_disk_vtable(); + a->lookup_uid = trivial_lookup_uid; + a->lookup_gid = trivial_lookup_gid; +#ifdef HAVE_GETEUID + a->user_uid = geteuid(); +#endif /* HAVE_GETEUID */ + if (archive_string_ensure(&a->path_safe, 512) == NULL) { + free(a); + return (NULL); + } + return (&a->archive); +} + + +/* + * If pathname is longer than PATH_MAX, chdir to a suitable + * intermediate dir and edit the path down to a shorter suffix. Note + * that this routine never returns an error; if the chdir() attempt + * fails for any reason, we just go ahead with the long pathname. The + * object creation is likely to fail, but any error will get handled + * at that time. + */ +#ifdef HAVE_FCHDIR +static void +edit_deep_directories(struct archive_write_disk *a) +{ + int ret; + char *tail = a->name; + + a->restore_pwd = -1; + + /* If path is short, avoid the open() below. */ + if (strlen(tail) <= PATH_MAX) + return; + + /* Try to record our starting dir. */ + a->restore_pwd = open(".", O_RDONLY | O_BINARY); + if (a->restore_pwd < 0) + return; + + /* As long as the path is too long... */ + while (strlen(tail) > PATH_MAX) { + /* Locate a dir prefix shorter than PATH_MAX. */ + tail += PATH_MAX - 8; + while (tail > a->name && *tail != '/') + tail--; + /* Exit if we find a too-long path component. */ + if (tail <= a->name) + return; + /* Create the intermediate dir and chdir to it. */ + *tail = '\0'; /* Terminate dir portion */ + ret = create_dir(a, a->name); + if (ret == ARCHIVE_OK && chdir(a->name) != 0) + ret = ARCHIVE_WARN; + *tail = '/'; /* Restore the / we removed. */ + if (ret != ARCHIVE_OK) + return; + tail++; + /* The chdir() succeeded; we've now shortened the path. */ + a->name = tail; + } + return; +} +#endif + +/* + * The main restore function. + */ +static int +restore_entry(struct archive_write_disk *a) +{ + int ret = ARCHIVE_OK, en; + + if (a->flags & ARCHIVE_EXTRACT_UNLINK && !S_ISDIR(a->mode)) { + /* + * TODO: Fix this. Apparently, there are platforms + * that still allow root to hose the entire filesystem + * by unlinking a dir. The S_ISDIR() test above + * prevents us from using unlink() here if the new + * object is a dir, but that doesn't mean the old + * object isn't a dir. + */ + if (unlink(a->name) == 0) { + /* We removed it, we're done. */ + } else if (errno == ENOENT) { + /* File didn't exist, that's just as good. */ + } else if (rmdir(a->name) == 0) { + /* It was a dir, but now it's gone. */ + } else { + /* We tried, but couldn't get rid of it. */ + archive_set_error(&a->archive, errno, + "Could not unlink"); + return(ARCHIVE_WARN); + } + } + + /* Try creating it first; if this fails, we'll try to recover. */ + en = create_filesystem_object(a); + + if ((en == ENOTDIR || en == ENOENT) + && !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) { + /* If the parent dir doesn't exist, try creating it. */ + create_parent_dir(a, a->name); + /* Now try to create the object again. */ + en = create_filesystem_object(a); + } + + if ((en == EISDIR || en == EEXIST) + && (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { + /* If we're not overwriting, we're done. */ + archive_set_error(&a->archive, en, "Already exists"); + return (ARCHIVE_WARN); + } + + /* + * Some platforms return EISDIR if you call + * open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some + * return EEXIST. POSIX is ambiguous, requiring EISDIR + * for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT) + * on an existing item. + */ + if (en == EISDIR) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); + return (ARCHIVE_WARN); + } + /* Try again. */ + en = create_filesystem_object(a); + } else if (en == EEXIST) { + /* + * We know something is in the way, but we don't know what; + * we need to find out before we go any further. + */ + if (lstat(a->name, &a->st) != 0) { + archive_set_error(&a->archive, errno, + "Can't stat existing object"); + return (ARCHIVE_WARN); + } + + /* TODO: if it's a symlink... */ + + if (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER) { + if (!older(&(a->st), a->entry)) { + archive_set_error(&a->archive, 0, + "File on disk is not older; skipping."); + return (ARCHIVE_FAILED); + } + } + + /* If it's our archive, we're done. */ + if (a->skip_file_dev > 0 && + a->skip_file_ino > 0 && + a->st.st_dev == a->skip_file_dev && + a->st.st_ino == a->skip_file_ino) { + archive_set_error(&a->archive, 0, "Refusing to overwrite archive"); + return (ARCHIVE_FAILED); + } + + if (!S_ISDIR(a->st.st_mode)) { + /* A non-dir is in the way, unlink it. */ + if (unlink(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't unlink already-existing object"); + return (ARCHIVE_WARN); + } + /* Try again. */ + en = create_filesystem_object(a); + } else if (!S_ISDIR(a->mode)) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); + return (ARCHIVE_WARN); + } + /* Try again. */ + en = create_filesystem_object(a); + } else { + /* + * There's a dir in the way of a dir. Don't + * waste time with rmdir()/mkdir(), just fix + * up the permissions on the existing dir. + * Note that we don't change perms on existing + * dirs unless _EXTRACT_PERM is specified. + */ + if ((a->mode != a->st.st_mode) + && (a->todo & TODO_MODE_FORCE)) + a->deferred |= (a->todo & TODO_MODE); + /* Ownership doesn't need deferred fixup. */ + en = 0; /* Forget the EEXIST. */ + } + } + + if (en) { + /* Everything failed; give up here. */ + archive_set_error(&a->archive, en, "Can't create '%s'", a->name); + return (ARCHIVE_WARN); + } + + a->pst = NULL; /* Cached stat data no longer valid. */ + return (ret); +} + +/* + * Returns 0 if creation succeeds, or else returns errno value from + * the failed system call. Note: This function should only ever perform + * a single system call. + */ +int +create_filesystem_object(struct archive_write_disk *a) +{ + /* Create the entry. */ + const char *linkname; + mode_t final_mode, mode; + int r; + + /* We identify hard/symlinks according to the link names. */ + /* Since link(2) and symlink(2) don't handle modes, we're done here. */ + linkname = archive_entry_hardlink(a->entry); + if (linkname != NULL) { + r = link(linkname, a->name) ? errno : 0; + /* + * New cpio and pax formats allow hardlink entries + * to carry data, so we may have to open the file + * for hardlink entries. + */ + if (r == 0 && a->filesize > 0) { + a->fd = open(a->name, O_WRONLY | O_TRUNC | O_BINARY); + if (a->fd < 0) + r = errno; + } + return (r); + } + linkname = archive_entry_symlink(a->entry); + if (linkname != NULL) + return symlink(linkname, a->name) ? errno : 0; + + /* + * The remaining system calls all set permissions, so let's + * try to take advantage of that to avoid an extra chmod() + * call. (Recall that umask is set to zero right now!) + */ + + /* Mode we want for the final restored object (w/o file type bits). */ + final_mode = a->mode & 07777; + /* + * The mode that will actually be restored in this step. Note + * that SUID, SGID, etc, require additional work to ensure + * security, so we never restore them at this point. + */ + mode = final_mode & 0777; + + switch (a->mode & AE_IFMT) { + default: + /* POSIX requires that we fall through here. */ + /* FALLTHROUGH */ + case AE_IFREG: + a->fd = open(a->name, + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, mode); + r = (a->fd < 0); + break; + case AE_IFCHR: +#ifdef HAVE_MKNOD + /* Note: we use AE_IFCHR for the case label, and + * S_IFCHR for the mknod() call. This is correct. */ + r = mknod(a->name, mode | S_IFCHR, + archive_entry_rdev(a->entry)); +#else + /* TODO: Find a better way to warn about our inability + * to restore a char device node. */ + return (EINVAL); +#endif /* HAVE_MKNOD */ + break; + case AE_IFBLK: +#ifdef HAVE_MKNOD + r = mknod(a->name, mode | S_IFBLK, + archive_entry_rdev(a->entry)); +#else + /* TODO: Find a better way to warn about our inability + * to restore a block device node. */ + return (EINVAL); +#endif /* HAVE_MKNOD */ + break; + case AE_IFDIR: + mode = (mode | MINIMUM_DIR_MODE) & MAXIMUM_DIR_MODE; + r = mkdir(a->name, mode); + if (r == 0) { + /* Defer setting dir times. */ + a->deferred |= (a->todo & TODO_TIMES); + a->todo &= ~TODO_TIMES; + /* Never use an immediate chmod(). */ + if (mode != final_mode) + a->deferred |= (a->todo & TODO_MODE); + a->todo &= ~TODO_MODE; + } + break; + case AE_IFIFO: +#ifdef HAVE_MKFIFO + r = mkfifo(a->name, mode); +#else + /* TODO: Find a better way to warn about our inability + * to restore a fifo. */ + return (EINVAL); +#endif /* HAVE_MKFIFO */ + break; + } + + /* All the system calls above set errno on failure. */ + if (r) + return (errno); + + /* If we managed to set the final mode, we've avoided a chmod(). */ + if (mode == final_mode) + a->todo &= ~TODO_MODE; + return (0); +} + +/* + * Cleanup function for archive_extract. Mostly, this involves processing + * the fixup list, which is used to address a number of problems: + * * Dir permissions might prevent us from restoring a file in that + * dir, so we restore the dir with minimum 0700 permissions first, + * then correct the mode at the end. + * * Similarly, the act of restoring a file touches the directory + * and changes the timestamp on the dir, so we have to touch-up dir + * timestamps at the end as well. + * * Some file flags can interfere with the restore by, for example, + * preventing the creation of hardlinks to those files. + * + * Note that tar/cpio do not require that archives be in a particular + * order; there is no way to know when the last file has been restored + * within a directory, so there's no way to optimize the memory usage + * here by fixing up the directory any earlier than the + * end-of-archive. + * + * XXX TODO: Directory ACLs should be restored here, for the same + * reason we set directory perms here. XXX + */ +static int +_archive_write_close(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + struct fixup_entry *next, *p; + int ret; + + __archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_disk_close"); + ret = _archive_write_finish_entry(&a->archive); + + /* Sort dir list so directories are fixed up in depth-first order. */ + p = sort_dir_list(a->fixup_list); + + while (p != NULL) { + a->pst = NULL; /* Mark stat cache as out-of-date. */ + if (p->fixup & TODO_TIMES) { +#ifdef HAVE_UTIMES + /* {f,l,}utimes() are preferred, when available. */ + struct timeval times[2]; + times[1].tv_sec = p->mtime; + times[1].tv_usec = p->mtime_nanos / 1000; + times[0].tv_sec = p->atime; + times[0].tv_usec = p->atime_nanos / 1000; +#ifdef HAVE_LUTIMES + lutimes(p->name, times); +#else + utimes(p->name, times); +#endif +#else + /* utime() is more portable, but less precise. */ + struct utimbuf times; + times.modtime = p->mtime; + times.actime = p->atime; + + utime(p->name, ×); +#endif + } + if (p->fixup & TODO_MODE_BASE) + chmod(p->name, p->mode); + + if (p->fixup & TODO_FFLAGS) + set_fflags_platform(a, -1, p->name, + p->mode, p->fflags_set, 0); + + next = p->next; + free(p->name); + free(p); + p = next; + } + a->fixup_list = NULL; + return (ret); +} + +static int +_archive_write_finish(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + int ret; + ret = _archive_write_close(&a->archive); + if (a->cleanup_gid != NULL && a->lookup_gid_data != NULL) + (a->cleanup_gid)(a->lookup_gid_data); + if (a->cleanup_uid != NULL && a->lookup_uid_data != NULL) + (a->cleanup_uid)(a->lookup_uid_data); + archive_string_free(&a->_name_data); + archive_string_free(&a->archive.error_string); + archive_string_free(&a->path_safe); + free(a); + return (ret); +} + +/* + * Simple O(n log n) merge sort to order the fixup list. In + * particular, we want to restore dir timestamps depth-first. + */ +static struct fixup_entry * +sort_dir_list(struct fixup_entry *p) +{ + struct fixup_entry *a, *b, *t; + + if (p == NULL) + return (NULL); + /* A one-item list is already sorted. */ + if (p->next == NULL) + return (p); + + /* Step 1: split the list. */ + t = p; + a = p->next->next; + while (a != NULL) { + /* Step a twice, t once. */ + a = a->next; + if (a != NULL) + a = a->next; + t = t->next; + } + /* Now, t is at the mid-point, so break the list here. */ + b = t->next; + t->next = NULL; + a = p; + + /* Step 2: Recursively sort the two sub-lists. */ + a = sort_dir_list(a); + b = sort_dir_list(b); + + /* Step 3: Merge the returned lists. */ + /* Pick the first element for the merged list. */ + if (strcmp(a->name, b->name) > 0) { + t = p = a; + a = a->next; + } else { + t = p = b; + b = b->next; + } + + /* Always put the later element on the list first. */ + while (a != NULL && b != NULL) { + if (strcmp(a->name, b->name) > 0) { + t->next = a; + a = a->next; + } else { + t->next = b; + b = b->next; + } + t = t->next; + } + + /* Only one list is non-empty, so just splice it on. */ + if (a != NULL) + t->next = a; + if (b != NULL) + t->next = b; + + return (p); +} + +/* + * Returns a new, initialized fixup entry. + * + * TODO: Reduce the memory requirements for this list by using a tree + * structure rather than a simple list of names. + */ +static struct fixup_entry * +new_fixup(struct archive_write_disk *a, const char *pathname) +{ + struct fixup_entry *fe; + + fe = (struct fixup_entry *)malloc(sizeof(struct fixup_entry)); + if (fe == NULL) + return (NULL); + fe->next = a->fixup_list; + a->fixup_list = fe; + fe->fixup = 0; + fe->name = strdup(pathname); + return (fe); +} + +/* + * Returns a fixup structure for the current entry. + */ +static struct fixup_entry * +current_fixup(struct archive_write_disk *a, const char *pathname) +{ + if (a->current_fixup == NULL) + a->current_fixup = new_fixup(a, pathname); + return (a->current_fixup); +} + +/* TODO: Make this work. */ +/* + * TODO: The deep-directory support bypasses this; disable deep directory + * support if we're doing symlink checks. + */ +/* + * TODO: Someday, integrate this with the deep dir support; they both + * scan the path and both can be optimized by comparing against other + * recent paths. + */ +static int +check_symlinks(struct archive_write_disk *a) +{ + char *pn, *p; + char c; + int r; + struct stat st; + + /* + * Guard against symlink tricks. Reject any archive entry whose + * destination would be altered by a symlink. + */ + /* Whatever we checked last time doesn't need to be re-checked. */ + pn = a->name; + p = a->path_safe.s; + while ((*pn != '\0') && (*p == *pn)) + ++p, ++pn; + c = pn[0]; + /* Keep going until we've checked the entire name. */ + while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) { + /* Skip the next path element. */ + while (*pn != '\0' && *pn != '/') + ++pn; + c = pn[0]; + pn[0] = '\0'; + /* Check that we haven't hit a symlink. */ + r = lstat(a->name, &st); + if (r != 0) { + /* We've hit a dir that doesn't exist; stop now. */ + if (errno == ENOENT) + break; + } else if (S_ISLNK(st.st_mode)) { + if (c == '\0') { + /* + * Last element is symlink; remove it + * so we can overwrite it with the + * item being extracted. + */ + if (unlink(a->name)) { + archive_set_error(&a->archive, errno, + "Could not remove symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_WARN); + } + /* + * Even if we did remove it, a warning + * is in order. The warning is silly, + * though, if we're just replacing one + * symlink with another symlink. + */ + if (!S_ISLNK(a->mode)) { + archive_set_error(&a->archive, 0, + "Removing symlink %s", + a->name); + } + /* Symlink gone. No more problem! */ + pn[0] = c; + return (0); + } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { + /* User asked us to remove problems. */ + if (unlink(a->name) != 0) { + archive_set_error(&a->archive, 0, + "Cannot remove intervening symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_WARN); + } + } else { + archive_set_error(&a->archive, 0, + "Cannot extract through symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_WARN); + } + } + } + pn[0] = c; + /* We've checked and/or cleaned the whole path, so remember it. */ + archive_strcpy(&a->path_safe, a->name); + return (ARCHIVE_OK); +} + +/* + * Canonicalize the pathname. In particular, this strips duplicate + * '/' characters, '.' elements, and trailing '/'. It also raises an + * error for an empty path, a trailing '..' or (if _SECURE_NODOTDOT is + * set) any '..' in the path. + */ +static int +cleanup_pathname(struct archive_write_disk *a) +{ + char *dest, *src; + char separator = '\0'; + int lastdotdot = 0; /* True if last elt copied was '..' */ + + dest = src = a->name; + if (*src == '\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid empty pathname"); + return (ARCHIVE_WARN); + } + + /* Skip leading '/'. */ + if (*src == '/') + separator = *src++; + + /* Scan the pathname one element at a time. */ + for (;;) { + /* src points to first char after '/' */ + if (src[0] == '\0') { + break; + } else if (src[0] == '/') { + /* Found '//', ignore second one. */ + src++; + continue; + } else if (src[0] == '.') { + if (src[1] == '\0') { + /* Ignore trailing '.' */ + break; + } else if (src[1] == '/') { + /* Skip './'. */ + src += 2; + continue; + } else if (src[1] == '.') { + if (src[2] == '/' || src[2] == '\0') { + /* Conditionally warn about '..' */ + if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Path contains '..'"); + return (ARCHIVE_WARN); + } + lastdotdot = 1; + } else + lastdotdot = 0; + /* + * Note: Under no circumstances do we + * remove '..' elements. In + * particular, restoring + * '/foo/../bar/' should create the + * 'foo' dir as a side-effect. + */ + } else + lastdotdot = 0; + } else + lastdotdot = 0; + + /* Copy current element, including leading '/'. */ + if (separator) + *dest++ = '/'; + while (*src != '\0' && *src != '/') { + *dest++ = *src++; + } + + if (*src == '\0') + break; + + /* Skip '/' separator. */ + separator = *src++; + } + /* + * We've just copied zero or more path elements, not including the + * final '/'. + */ + if (lastdotdot) { + /* Trailing '..' is always wrong. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Path contains trailing '..'"); + return (ARCHIVE_WARN); + } + if (dest == a->name) { + /* + * Nothing got copied. The path must have been something + * like '.' or '/' or './' or '/././././/./'. + */ + if (separator) + *dest++ = '/'; + else + *dest++ = '.'; + } + /* Terminate the result. */ + *dest = '\0'; + return (ARCHIVE_OK); +} + +/* + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. + */ +static int +create_parent_dir(struct archive_write_disk *a, char *path) +{ + char *slash; + int r; + + /* Remove tail element to obtain parent name. */ + slash = strrchr(path, '/'); + if (slash == NULL) + return (ARCHIVE_OK); + *slash = '\0'; + r = create_dir(a, path); + *slash = '/'; + return (r); +} + +/* + * Create the specified dir, recursing to create parents as necessary. + * + * Returns ARCHIVE_OK if the path exists when we're done here. + * Otherwise, returns ARCHIVE_WARN. + * Assumes path is in mutable storage; path is unchanged on exit. + */ +static int +create_dir(struct archive_write_disk *a, char *path) +{ + struct stat st; + struct fixup_entry *le; + char *slash, *base; + mode_t mode_final, mode; + int r; + + r = ARCHIVE_OK; + + /* Check for special names and just skip them. */ + slash = strrchr(path, '/'); + if (slash == NULL) + base = path; + else + base = slash + 1; + + if (base[0] == '\0' || + (base[0] == '.' && base[1] == '\0') || + (base[0] == '.' && base[1] == '.' && base[2] == '\0')) { + /* Don't bother trying to create null path, '.', or '..'. */ + if (slash != NULL) { + *slash = '\0'; + r = create_dir(a, path); + *slash = '/'; + return (r); + } + return (ARCHIVE_OK); + } + + /* + * Yes, this should be stat() and not lstat(). Using lstat() + * here loses the ability to extract through symlinks. Also note + * that this should not use the a->st cache. + */ + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) + return (ARCHIVE_OK); + if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { + archive_set_error(&a->archive, EEXIST, + "Can't create directory '%s'", path); + return (ARCHIVE_WARN); + } + if (unlink(path) != 0) { + archive_set_error(&a->archive, errno, + "Can't create directory '%s': " + "Conflicting file cannot be removed"); + return (ARCHIVE_WARN); + } + } else if (errno != ENOENT && errno != ENOTDIR) { + /* Stat failed? */ + archive_set_error(&a->archive, errno, "Can't test directory '%s'", path); + return (ARCHIVE_WARN); + } else if (slash != NULL) { + *slash = '\0'; + r = create_dir(a, path); + *slash = '/'; + if (r != ARCHIVE_OK) + return (r); + } + + /* + * Mode we want for the final restored directory. Per POSIX, + * implicitly-created dirs must be created obeying the umask. + * There's no mention whether this is different for privileged + * restores (which the rest of this code handles by pretending + * umask=0). I've chosen here to always obey the user's umask for + * implicit dirs, even if _EXTRACT_PERM was specified. + */ + mode_final = DEFAULT_DIR_MODE & ~a->user_umask; + /* Mode we want on disk during the restore process. */ + mode = mode_final; + mode |= MINIMUM_DIR_MODE; + mode &= MAXIMUM_DIR_MODE; + if (mkdir(path, mode) == 0) { + if (mode != mode_final) { + le = new_fixup(a, path); + le->fixup |=TODO_MODE_BASE; + le->mode = mode_final; + } + return (ARCHIVE_OK); + } + + /* + * Without the following check, a/b/../b/c/d fails at the + * second visit to 'b', so 'd' can't be created. Note that we + * don't add it to the fixup list here, as it's already been + * added. + */ + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + return (ARCHIVE_OK); + + archive_set_error(&a->archive, errno, "Failed to create dir '%s'", path); + return (ARCHIVE_WARN); +} + +/* + * Note: Although we can skip setting the user id if the desired user + * id matches the current user, we cannot skip setting the group, as + * many systems set the gid bit based on the containing directory. So + * we have to perform a chown syscall if we want to restore the SGID + * bit. (The alternative is to stat() and then possibly chown(); it's + * more efficient to skip the stat() and just always chown().) Note + * that a successful chown() here clears the TODO_SGID_CHECK bit, which + * allows set_mode to skip the stat() check for the GID. + */ +static int +set_ownership(struct archive_write_disk *a) +{ + /* If we know we can't change it, don't bother trying. */ + if (a->user_uid != 0 && a->user_uid != a->uid) { + archive_set_error(&a->archive, errno, + "Can't set UID=%d", a->uid); + return (ARCHIVE_WARN); + } + +#ifdef HAVE_FCHOWN + /* If we have an fd, we can avoid a race. */ + if (a->fd >= 0 && fchown(a->fd, a->uid, a->gid) == 0) { + /* We've set owner and know uid/gid are correct. */ + a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK); + return (ARCHIVE_OK); + } +#endif + + /* We prefer lchown() but will use chown() if that's all we have. */ + /* Of course, if we have neither, this will always fail. */ +#ifdef HAVE_LCHOWN + if (lchown(a->name, a->uid, a->gid) == 0) { + /* We've set owner and know uid/gid are correct. */ + a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK); + return (ARCHIVE_OK); + } +#elif HAVE_CHOWN + if (!S_ISLNK(a->mode) && chown(a->name, a->uid, a->gid) == 0) { + /* We've set owner and know uid/gid are correct. */ + a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK); + return (ARCHIVE_OK); + } +#endif + + archive_set_error(&a->archive, errno, + "Can't set user=%d/group=%d for %s", a->uid, a->gid, + a->name); + return (ARCHIVE_WARN); +} + +#ifdef HAVE_UTIMES +/* + * The utimes()-family functions provide high resolution and + * a way to set time on an fd or a symlink. We prefer them + * when they're available. + */ +static int +set_time(struct archive_write_disk *a) +{ + struct timeval times[2]; + + times[1].tv_sec = archive_entry_mtime(a->entry); + times[1].tv_usec = archive_entry_mtime_nsec(a->entry) / 1000; + + times[0].tv_sec = archive_entry_atime(a->entry); + times[0].tv_usec = archive_entry_atime_nsec(a->entry) / 1000; + +#ifdef HAVE_FUTIMES + if (a->fd >= 0 && futimes(a->fd, times) == 0) { + return (ARCHIVE_OK); + } +#endif + +#ifdef HAVE_LUTIMES + if (lutimes(a->name, times) != 0) +#else + if (!S_ISLNK(a->mode) && utimes(a->name, times) != 0) +#endif + { + archive_set_error(&a->archive, errno, "Can't update time for %s", + a->name); + return (ARCHIVE_WARN); + } + + /* + * Note: POSIX does not provide a portable way to restore ctime. + * (Apart from resetting the system clock, which is distasteful.) + * So, any restoration of ctime will necessarily be OS-specific. + */ + + /* XXX TODO: Can FreeBSD restore ctime? XXX */ + return (ARCHIVE_OK); +} +#elif defined(HAVE_UTIME) +/* + * utime() is an older, more standard interface that we'll use + * if utimes() isn't available. + */ +static int +set_time(struct archive_write_disk *a) +{ + struct utimbuf times; + + times.modtime = archive_entry_mtime(a->entry); + times.actime = archive_entry_atime(a->entry); + if (!S_ISLNK(a->mode) && utime(a->name, ×) != 0) { + archive_set_error(&a->archive, errno, + "Can't update time for %s", a->name); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} +#else +/* This platform doesn't give us a way to restore the time. */ +static int +set_time(struct archive_write_disk *a) +{ + (void)a; /* UNUSED */ + archive_set_error(&a->archive, errno, + "Can't update time for %s", a->name); + return (ARCHIVE_WARN); +} +#endif + + +static int +set_mode(struct archive_write_disk *a, int mode) +{ + int r = ARCHIVE_OK; + mode &= 07777; /* Strip off file type bits. */ + + if (a->todo & TODO_SGID_CHECK) { + /* + * If we don't know the GID is right, we must stat() + * to verify it. We can't just check the GID of this + * process, since systems sometimes set GID from + * the enclosing dir or based on ACLs. + */ + if (a->pst != NULL) { + /* Already have stat() data available. */ +#ifdef HAVE_FSTAT + } else if (a->fd >= 0 && fstat(a->fd, &a->st) == 0) { + a->pst = &a->st; +#endif + } else if (stat(a->name, &a->st) == 0) { + a->pst = &a->st; + } else { + archive_set_error(&a->archive, errno, + "Couldn't stat file"); + return (ARCHIVE_WARN); + } + if (a->pst->st_gid != a->gid) { + mode &= ~ S_ISGID; + if (a->flags & ARCHIVE_EXTRACT_OWNER) { + /* + * This is only an error if you + * requested owner restore. If you + * didn't, we'll try to restore + * sgid/suid, but won't consider it a + * problem if we can't. + */ + archive_set_error(&a->archive, -1, + "Can't restore SGID bit"); + r = ARCHIVE_WARN; + } + } + /* While we're here, double-check the UID. */ + if (a->pst->st_uid != a->uid + && (a->todo & TODO_SUID)) { + mode &= ~ S_ISUID; + if (a->flags & ARCHIVE_EXTRACT_OWNER) { + archive_set_error(&a->archive, -1, + "Can't restore SUID bit"); + r = ARCHIVE_WARN; + } + } + a->todo &= ~TODO_SGID_CHECK; + a->todo &= ~TODO_SUID_CHECK; + } else if (a->todo & TODO_SUID_CHECK) { + /* + * If we don't know the UID is right, we can just check + * the user, since all systems set the file UID from + * the process UID. + */ + if (a->user_uid != a->uid) { + mode &= ~ S_ISUID; + if (a->flags & ARCHIVE_EXTRACT_OWNER) { + archive_set_error(&a->archive, -1, + "Can't make file SUID"); + r = ARCHIVE_WARN; + } + } + a->todo &= ~TODO_SUID_CHECK; + } + + if (S_ISLNK(a->mode)) { +#ifdef HAVE_LCHMOD + /* + * If this is a symlink, use lchmod(). If the + * platform doesn't support lchmod(), just skip it. A + * platform that doesn't provide a way to set + * permissions on symlinks probably ignores + * permissions on symlinks, so a failure here has no + * impact. + */ + if (lchmod(a->name, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } +#endif + } else if (!S_ISDIR(a->mode)) { + /* + * If it's not a symlink and not a dir, then use + * fchmod() or chmod(), depending on whether we have + * an fd. Dirs get their perms set during the + * post-extract fixup, which is handled elsewhere. + */ +#ifdef HAVE_FCHMOD + if (a->fd >= 0) { + if (fchmod(a->fd, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } + } else +#endif + /* If this platform lacks fchmod(), then + * we'll just use chmod(). */ + if (chmod(a->name, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } + } + return (r); +} + +static int +set_fflags(struct archive_write_disk *a) +{ + struct fixup_entry *le; + unsigned long set, clear; + int r; + int critical_flags; + mode_t mode = archive_entry_mode(a->entry); + + /* + * Make 'critical_flags' hold all file flags that can't be + * immediately restored. For example, on BSD systems, + * SF_IMMUTABLE prevents hardlinks from being created, so + * should not be set until after any hardlinks are created. To + * preserve some semblance of portability, this uses #ifdef + * extensively. Ugly, but it works. + * + * Yes, Virginia, this does create a security race. It's mitigated + * somewhat by the practice of creating dirs 0700 until the extract + * is done, but it would be nice if we could do more than that. + * People restoring critical file systems should be wary of + * other programs that might try to muck with files as they're + * being restored. + */ + /* Hopefully, the compiler will optimize this mess into a constant. */ + critical_flags = 0; +#ifdef SF_IMMUTABLE + critical_flags |= SF_IMMUTABLE; +#endif +#ifdef UF_IMMUTABLE + critical_flags |= UF_IMMUTABLE; +#endif +#ifdef SF_APPEND + critical_flags |= SF_APPEND; +#endif +#ifdef UF_APPEND + critical_flags |= UF_APPEND; +#endif +#ifdef EXT2_APPEND_FL + critical_flags |= EXT2_APPEND_FL; +#endif +#ifdef EXT2_IMMUTABLE_FL + critical_flags |= EXT2_IMMUTABLE_FL; +#endif + + if (a->todo & TODO_FFLAGS) { + archive_entry_fflags(a->entry, &set, &clear); + + /* + * The first test encourages the compiler to eliminate + * all of this if it's not necessary. + */ + if ((critical_flags != 0) && (set & critical_flags)) { + le = current_fixup(a, a->name); + le->fixup |= TODO_FFLAGS; + le->fflags_set = set; + /* Store the mode if it's not already there. */ + if ((le->fixup & TODO_MODE) == 0) + le->mode = mode; + } else { + r = set_fflags_platform(a, a->fd, + a->name, mode, set, clear); + if (r != ARCHIVE_OK) + return (r); + } + } + return (ARCHIVE_OK); +} + + +#if ( defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && !defined(__linux) +static int +set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, + mode_t mode, unsigned long set, unsigned long clear) +{ + (void)mode; /* UNUSED */ + if (set == 0 && clear == 0) + return (ARCHIVE_OK); + + /* + * XXX Is the stat here really necessary? Or can I just use + * the 'set' flags directly? In particular, I'm not sure + * about the correct approach if we're overwriting an existing + * file that already has flags on it. XXX + */ + if (fd >= 0 && fstat(fd, &a->st) == 0) + a->pst = &a->st; + else if (lstat(name, &a->st) == 0) + a->pst = &a->st; + else { + archive_set_error(&a->archive, errno, + "Couldn't stat file"); + return (ARCHIVE_WARN); + } + + a->st.st_flags &= ~clear; + a->st.st_flags |= set; +#ifdef HAVE_FCHFLAGS + /* If platform has fchflags() and we were given an fd, use it. */ + if (fd >= 0 && fchflags(fd, a->st.st_flags) == 0) + return (ARCHIVE_OK); +#endif + /* + * If we can't use the fd to set the flags, we'll use the + * pathname to set flags. We prefer lchflags() but will use + * chflags() if we must. + */ +#ifdef HAVE_LCHFLAGS + if (lchflags(name, a->st.st_flags) == 0) + return (ARCHIVE_OK); +#elif defined(HAVE_CHFLAGS) + if (S_ISLNK(a->st.st_mode)) { + archive_set_error(&a->archive, errno, + "Can't set file flags on symlink."); + return (ARCHIVE_WARN); + } + if (chflags(name, a->st.st_flags) == 0) + return (ARCHIVE_OK); +#endif + archive_set_error(&a->archive, errno, + "Failed to set file flags"); + return (ARCHIVE_WARN); +} + +#elif defined(__linux) && defined(EXT2_IOC_GETFLAGS) && defined(EXT2_IOC_SETFLAGS) + +/* + * Linux has flags too, but uses ioctl() to access them instead of + * having a separate chflags() system call. + */ +static int +set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, + mode_t mode, unsigned long set, unsigned long clear) +{ + int ret; + int myfd = fd; + unsigned long newflags, oldflags; + unsigned long sf_mask = 0; + + if (set == 0 && clear == 0) + return (ARCHIVE_OK); + /* Only regular files and dirs can have flags. */ + if (!S_ISREG(mode) && !S_ISDIR(mode)) + return (ARCHIVE_OK); + + /* If we weren't given an fd, open it ourselves. */ + if (myfd < 0) + myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY); + if (myfd < 0) + return (ARCHIVE_OK); + + /* + * Linux has no define for the flags that are only settable by + * the root user. This code may seem a little complex, but + * there seem to be some Linux systems that lack these + * defines. (?) The code below degrades reasonably gracefully + * if sf_mask is incomplete. + */ +#ifdef EXT2_IMMUTABLE_FL + sf_mask |= EXT2_IMMUTABLE_FL; +#endif +#ifdef EXT2_APPEND_FL + sf_mask |= EXT2_APPEND_FL; +#endif + /* + * XXX As above, this would be way simpler if we didn't have + * to read the current flags from disk. XXX + */ + ret = ARCHIVE_OK; + /* Try setting the flags as given. */ + if (ioctl(myfd, EXT2_IOC_GETFLAGS, &oldflags) >= 0) { + newflags = (oldflags & ~clear) | set; + if (ioctl(myfd, EXT2_IOC_SETFLAGS, &newflags) >= 0) + goto cleanup; + if (errno != EPERM) + goto fail; + } + /* If we couldn't set all the flags, try again with a subset. */ + if (ioctl(myfd, EXT2_IOC_GETFLAGS, &oldflags) >= 0) { + newflags &= ~sf_mask; + oldflags &= sf_mask; + newflags |= oldflags; + if (ioctl(myfd, EXT2_IOC_SETFLAGS, &newflags) >= 0) + goto cleanup; + } + /* We couldn't set the flags, so report the failure. */ +fail: + archive_set_error(&a->archive, errno, + "Failed to set file flags"); + ret = ARCHIVE_WARN; +cleanup: + if (fd < 0) + close(myfd); + return (ret); +} + +#else /* Not HAVE_CHFLAGS && Not __linux */ + +/* + * Of course, some systems have neither BSD chflags() nor Linux' flags + * support through ioctl(). + */ +static int +set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, + mode_t mode, unsigned long set, unsigned long clear) +{ + (void)a; /* UNUSED */ + (void)fd; /* UNUSED */ + (void)name; /* UNUSED */ + (void)mode; /* UNUSED */ + (void)set; /* UNUSED */ + (void)clear; /* UNUSED */ + return (ARCHIVE_OK); +} + +#endif /* __linux */ + +#ifndef HAVE_POSIX_ACL +/* Default empty function body to satisfy mainline code. */ +static int +set_acls(struct archive_write_disk *a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +#else + +/* + * XXX TODO: What about ACL types other than ACCESS and DEFAULT? + */ +static int +set_acls(struct archive_write_disk *a) +{ + int ret; + + ret = set_acl(a, a->fd, a->entry, ACL_TYPE_ACCESS, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access"); + if (ret != ARCHIVE_OK) + return (ret); + ret = set_acl(a, a->fd, a->entry, ACL_TYPE_DEFAULT, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default"); + return (ret); +} + + +static int +set_acl(struct archive_write_disk *a, int fd, struct archive_entry *entry, + acl_type_t acl_type, int ae_requested_type, const char *tname) +{ + acl_t acl; + acl_entry_t acl_entry; + acl_permset_t acl_permset; + int ret; + int ae_type, ae_permset, ae_tag, ae_id; + uid_t ae_uid; + gid_t ae_gid; + const char *ae_name; + int entries; + const char *name; + + ret = ARCHIVE_OK; + entries = archive_entry_acl_reset(entry, ae_requested_type); + if (entries == 0) + return (ARCHIVE_OK); + acl = acl_init(entries); + while (archive_entry_acl_next(entry, ae_requested_type, &ae_type, + &ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) { + acl_create_entry(&acl, &acl_entry); + + switch (ae_tag) { + case ARCHIVE_ENTRY_ACL_USER: + acl_set_tag_type(acl_entry, ACL_USER); + ae_uid = a->lookup_uid(a->lookup_uid_data, + ae_name, ae_id); + acl_set_qualifier(acl_entry, &ae_uid); + break; + case ARCHIVE_ENTRY_ACL_GROUP: + acl_set_tag_type(acl_entry, ACL_GROUP); + ae_gid = a->lookup_gid(a->lookup_gid_data, + ae_name, ae_id); + acl_set_qualifier(acl_entry, &ae_gid); + break; + case ARCHIVE_ENTRY_ACL_USER_OBJ: + acl_set_tag_type(acl_entry, ACL_USER_OBJ); + break; + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + acl_set_tag_type(acl_entry, ACL_GROUP_OBJ); + break; + case ARCHIVE_ENTRY_ACL_MASK: + acl_set_tag_type(acl_entry, ACL_MASK); + break; + case ARCHIVE_ENTRY_ACL_OTHER: + acl_set_tag_type(acl_entry, ACL_OTHER); + break; + default: + /* XXX */ + break; + } + + acl_get_permset(acl_entry, &acl_permset); + acl_clear_perms(acl_permset); + if (ae_permset & ARCHIVE_ENTRY_ACL_EXECUTE) + acl_add_perm(acl_permset, ACL_EXECUTE); + if (ae_permset & ARCHIVE_ENTRY_ACL_WRITE) + acl_add_perm(acl_permset, ACL_WRITE); + if (ae_permset & ARCHIVE_ENTRY_ACL_READ) + acl_add_perm(acl_permset, ACL_READ); + } + + name = archive_entry_pathname(entry); + + /* Try restoring the ACL through 'fd' if we can. */ +#if HAVE_ACL_SET_FD + if (fd >= 0 && acl_type == ACL_TYPE_ACCESS && acl_set_fd(fd, acl) == 0) + ret = ARCHIVE_OK; + else +#else +#if HAVE_ACL_SET_FD_NP + if (fd >= 0 && acl_set_fd_np(fd, acl, acl_type) == 0) + ret = ARCHIVE_OK; + else +#endif +#endif + if (acl_set_file(name, acl_type, acl) != 0) { + archive_set_error(&a->archive, errno, "Failed to set %s acl", tname); + ret = ARCHIVE_WARN; + } + acl_free(acl); + return (ret); +} +#endif + +#if HAVE_LSETXATTR +/* + * Restore extended attributes - Linux implementation + */ +static int +set_xattrs(struct archive_write_disk *a) +{ + struct archive_entry *entry = a->entry; + static int warning_done = 0; + int ret = ARCHIVE_OK; + int i = archive_entry_xattr_reset(entry); + + while (i--) { + const char *name; + const void *value; + size_t size; + archive_entry_xattr_next(entry, &name, &value, &size); + if (name != NULL && + strncmp(name, "xfsroot.", 8) != 0 && + strncmp(name, "system.", 7) != 0) { + int e; +#if HAVE_FSETXATTR + if (a->fd >= 0) + e = fsetxattr(a->fd, name, value, size, 0); + else +#endif + { + e = lsetxattr(archive_entry_pathname(entry), + name, value, size, 0); + } + if (e == -1) { + if (errno == ENOTSUP) { + if (!warning_done) { + warning_done = 1; + archive_set_error(&a->archive, errno, + "Cannot restore extended " + "attributes on this file " + "system"); + } + } else + archive_set_error(&a->archive, errno, + "Failed to set extended attribute"); + ret = ARCHIVE_WARN; + } + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid extended attribute encountered"); + ret = ARCHIVE_WARN; + } + } + return (ret); +} +#else +/* + * Restore extended attributes - stub implementation for unsupported systems + */ +static int +set_xattrs(struct archive_write_disk *a) +{ + static int warning_done = 0; + + /* If there aren't any extended attributes, then it's okay not + * to extract them, otherwise, issue a single warning. */ + if (archive_entry_xattr_count(a->entry) != 0 && !warning_done) { + warning_done = 1; + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Cannot restore extended attributes on this system"); + return (ARCHIVE_WARN); + } + /* Warning was already emitted; suppress further warnings. */ + return (ARCHIVE_OK); +} +#endif + + +/* + * Trivial implementations of gid/uid lookup functions. + * These are normally overridden by the client, but these stub + * versions ensure that we always have something that works. + */ +static gid_t +trivial_lookup_gid(void *private_data, const char *gname, gid_t gid) +{ + (void)private_data; /* UNUSED */ + (void)gname; /* UNUSED */ + return (gid); +} + +static uid_t +trivial_lookup_uid(void *private_data, const char *uname, uid_t uid) +{ + (void)private_data; /* UNUSED */ + (void)uname; /* UNUSED */ + return (uid); +} + +/* + * Test if file on disk is older than entry. + */ +static int +older(struct stat *st, struct archive_entry *entry) +{ + /* First, test the seconds and return if we have a definite answer. */ + /* Definitely older. */ + if (st->st_mtime < archive_entry_mtime(entry)) + return (1); + /* Definitely younger. */ + if (st->st_mtime > archive_entry_mtime(entry)) + return (0); + /* If this platform supports fractional seconds, try those. */ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + /* Definitely older. */ + if (st->st_mtimespec.tv_nsec < archive_entry_mtime_nsec(entry)) + return (1); + /* Definitely younger. */ + if (st->st_mtimespec.tv_nsec > archive_entry_mtime_nsec(entry)) + return (0); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + /* Definitely older. */ + if (st->st_mtim.tv_nsec < archive_entry_mtime_nsec(entry)) + return (1); + /* Definitely older. */ + if (st->st_mtim.tv_nsec > archive_entry_mtime_nsec(entry)) + return (0); +#else + /* This system doesn't have high-res timestamps. */ +#endif + /* Same age, so not older. */ + return (0); +} diff --git a/libarchive/archive_write_disk_private.h b/libarchive/archive_write_disk_private.h new file mode 100644 index 00000000..1e8ad692 --- /dev/null +++ b/libarchive/archive_write_disk_private.h @@ -0,0 +1,34 @@ +/*- + * Copyright (c) 2003-2007 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. + * + * $FreeBSD: src/lib/libarchive/archive_write_disk_private.h,v 1.1 2007/03/03 07:37:36 kientzle Exp $ + */ + +#ifndef ARCHIVE_WRITE_DISK_PRIVATE_H_INCLUDED +#define ARCHIVE_WRITE_DISK_PRIVATE_H_INCLUDED + +struct archive_write_disk; + +#endif diff --git a/libarchive/archive_write_disk_set_standard_lookup.c b/libarchive/archive_write_disk_set_standard_lookup.c new file mode 100644 index 00000000..427b8761 --- /dev/null +++ b/libarchive/archive_write_disk_set_standard_lookup.c @@ -0,0 +1,196 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_disk_set_standard_lookup.c,v 1.4 2007/05/29 01:00:19 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_write_disk_private.h" + +struct bucket { + char *name; + int hash; + id_t id; +}; + +static const size_t cache_size = 127; +static unsigned int hash(const char *); +static gid_t lookup_gid(void *, const char *uname, gid_t); +static uid_t lookup_uid(void *, const char *uname, uid_t); +static void cleanup(void *); + +/* + * Installs functions that use getpwnam()/getgrnam()---along with + * a simple cache to accelerate such lookups---into the archive_write_disk + * object. This is in a separate file because getpwnam()/getgrnam() + * can pull in a LOT of library code (including NIS/LDAP functions, which + * pull in DNS resolveers, etc). This can easily top 500kB, which makes + * it inappropriate for some space-constrained applications. + * + * Applications that are size-sensitive may want to just use the + * real default functions (defined in archive_write_disk.c) that just + * use the uid/gid without the lookup. Or define your own custom functions + * if you prefer. + * + * TODO: Replace these hash tables with simpler move-to-front LRU + * lists with a bounded size (128 items?). The hash is a bit faster, + * but has a bad pathology in which it thrashes a single bucket. Even + * walking a list of 128 items is a lot faster than calling + * getpwnam()! + */ +int +archive_write_disk_set_standard_lookup(struct archive *a) +{ + struct bucket *ucache = malloc(cache_size * sizeof(struct bucket)); + struct bucket *gcache = malloc(cache_size * sizeof(struct bucket)); + memset(ucache, 0, cache_size * sizeof(struct bucket)); + memset(gcache, 0, cache_size * sizeof(struct bucket)); + archive_write_disk_set_group_lookup(a, gcache, lookup_gid, cleanup); + archive_write_disk_set_user_lookup(a, ucache, lookup_uid, cleanup); + return (ARCHIVE_OK); +} + +static gid_t +lookup_gid(void *private_data, const char *gname, gid_t gid) +{ + int h; + struct bucket *b; + struct bucket *gcache = (struct bucket *)private_data; + + /* If no gname, just use the gid provided. */ + if (gname == NULL || *gname == '\0') + return (gid); + + /* Try to find gname in the cache. */ + h = hash(gname); + b = &gcache[h % cache_size ]; + if (b->name != NULL && b->hash == h && strcmp(gname, b->name) == 0) + return ((gid_t)b->id); + + /* Free the cache slot for a new entry. */ + if (b->name != NULL) + free(b->name); + b->name = strdup(gname); + /* Note: If strdup fails, that's okay; we just won't cache. */ + b->hash = h; +#if HAVE_GRP_H + { + struct group *grent = getgrnam(gname); + if (grent != NULL) + gid = grent->gr_gid; + } +#elif _WIN32 + /* TODO: do a gname->gid lookup for Windows. */ +#endif + b->id = gid; + + return (gid); +} + +static uid_t +lookup_uid(void *private_data, const char *uname, uid_t uid) +{ + int h; + struct bucket *b; + struct bucket *ucache = (struct bucket *)private_data; + + /* If no uname, just use the uid provided. */ + if (uname == NULL || *uname == '\0') + return (uid); + + /* Try to find uname in the cache. */ + h = hash(uname); + b = &ucache[h % cache_size ]; + if (b->name != NULL && b->hash == h && strcmp(uname, b->name) == 0) + return ((uid_t)b->id); + + /* Free the cache slot for a new entry. */ + if (b->name != NULL) + free(b->name); + b->name = strdup(uname); + /* Note: If strdup fails, that's okay; we just won't cache. */ + b->hash = h; +#if HAVE_PWD_H + { + struct passwd *pwent = getpwnam(uname); + if (pwent != NULL) + uid = pwent->pw_uid; + } +#elif _WIN32 + /* TODO: do a uname->uid lookup for Windows. */ +#endif + b->id = uid; + + return (uid); +} + +static void +cleanup(void *private) +{ + size_t i; + struct bucket *cache = (struct bucket *)private; + + for (i = 0; i < cache_size; i++) + free(cache[i].name); + free(cache); +} + + +static unsigned int +hash(const char *p) +{ + /* A 32-bit version of Peter Weinberger's (PJW) hash algorithm, + as used by ELF for hashing function names. */ + unsigned g, h = 0; + while (*p != '\0') { + h = ( h << 4 ) + *p++; + if (( g = h & 0xF0000000 )) { + h ^= g >> 24; + h &= 0x0FFFFFFF; + } + } + return h; +} diff --git a/libarchive/archive_write_open_fd.c b/libarchive/archive_write_open_fd.c new file mode 100644 index 00000000..35e258c4 --- /dev/null +++ b/libarchive/archive_write_open_fd.c @@ -0,0 +1,132 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_fd.c,v 1.9 2007/01/09 08:05:56 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" + +struct write_fd_data { + off_t offset; + int fd; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_write(struct archive *, void *, const void *buff, size_t); + +int +archive_write_open_fd(struct archive *a, int fd) +{ + struct write_fd_data *mine; + + mine = (struct write_fd_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->fd = fd; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + struct write_fd_data *mine; + struct stat st; + + mine = (struct write_fd_data *)client_data; + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat fd %d", mine->fd); + return (ARCHIVE_FATAL); + } + + /* + * If this is a regular file, don't add it to itself. + */ + if (S_ISREG(st.st_mode)) + archive_write_set_skip_file(a, st.st_dev, st.st_ino); + + /* + * If client hasn't explicitly set the last block handling, + * then set it here. + */ + if (archive_write_get_bytes_in_last_block(a) < 0) { + /* If the output is a block or character device, fifo, + * or stdout, pad the last block, otherwise leave it + * unpadded. */ + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode) || + S_ISFIFO(st.st_mode) || (mine->fd == 1)) + /* Last block will be fully padded. */ + archive_write_set_bytes_in_last_block(a, 0); + else + archive_write_set_bytes_in_last_block(a, 1); + } + + return (ARCHIVE_OK); +} + +static ssize_t +file_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_fd_data *mine; + ssize_t bytesWritten; + + mine = (struct write_fd_data *)client_data; + bytesWritten = write(mine->fd, buff, length); + if (bytesWritten <= 0) { + archive_set_error(a, errno, "Write error"); + return (-1); + } + return (bytesWritten); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct write_fd_data *mine = (struct write_fd_data *)client_data; + + (void)a; /* UNUSED */ + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_open_file.c b/libarchive/archive_write_open_file.c new file mode 100644 index 00000000..5c0c737f --- /dev/null +++ b/libarchive/archive_write_open_file.c @@ -0,0 +1,105 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_file.c,v 1.19 2007/01/09 08:05:56 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" + +struct write_FILE_data { + FILE *f; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_write(struct archive *, void *, const void *buff, size_t); + +int +archive_write_open_FILE(struct archive *a, FILE *f) +{ + struct write_FILE_data *mine; + + mine = (struct write_FILE_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->f = f; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + (void)a; /* UNUSED */ + (void)client_data; /* UNUSED */ + + return (ARCHIVE_OK); +} + +static ssize_t +file_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_FILE_data *mine; + size_t bytesWritten; + + mine = client_data; + bytesWritten = fwrite(buff, 1, length, mine->f); + if (bytesWritten < length) { + archive_set_error(a, errno, "Write error"); + return (-1); + } + return (bytesWritten); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct write_FILE_data *mine = client_data; + + (void)a; /* UNUSED */ + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_open_filename.c b/libarchive/archive_write_open_filename.c new file mode 100644 index 00000000..72eeb540 --- /dev/null +++ b/libarchive/archive_write_open_filename.c @@ -0,0 +1,179 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_filename.c,v 1.20 2008/02/19 05:46:58 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "archive.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct write_file_data { + int fd; + char filename[1]; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_write(struct archive *, void *, const void *buff, size_t); + +int +archive_write_open_file(struct archive *a, const char *filename) +{ + return (archive_write_open_filename(a, filename)); +} + +int +archive_write_open_filename(struct archive *a, const char *filename) +{ + struct write_file_data *mine; + + if (filename == NULL || filename[0] == '\0') { + mine = (struct write_file_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->filename[0] = '\0'; /* Record that we're using stdout. */ + } else { + mine = (struct write_file_data *)malloc(sizeof(*mine) + strlen(filename)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + strcpy(mine->filename, filename); + } + mine->fd = -1; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + int flags; + struct write_file_data *mine; + struct stat st; + + mine = (struct write_file_data *)client_data; + flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; + + /* + * Open the file. + */ + if (mine->filename[0] != '\0') { + mine->fd = open(mine->filename, flags, 0666); + if (mine->fd < 0) { + archive_set_error(a, errno, "Failed to open '%s'", + mine->filename); + return (ARCHIVE_FATAL); + } + } else { + /* + * NULL filename is stdout. + */ + mine->fd = 1; + /* By default, pad archive when writing to stdout. */ + if (archive_write_get_bytes_in_last_block(a) < 0) + archive_write_set_bytes_in_last_block(a, 0); + } + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat '%s'", + mine->filename); + return (ARCHIVE_FATAL); + } + + /* + * Set up default last block handling. + */ + if (archive_write_get_bytes_in_last_block(a) < 0) { + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode) || + S_ISFIFO(st.st_mode)) + /* Pad last block when writing to device or FIFO. */ + archive_write_set_bytes_in_last_block(a, 0); + else + /* Don't pad last block otherwise. */ + archive_write_set_bytes_in_last_block(a, 1); + } + + /* + * If the output file is a regular file, don't add it to + * itself. If it's a device file, it's okay to add the device + * entry to the output archive. + */ + if (S_ISREG(st.st_mode)) + archive_write_set_skip_file(a, st.st_dev, st.st_ino); + + return (ARCHIVE_OK); +} + +static ssize_t +file_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_file_data *mine; + ssize_t bytesWritten; + + mine = (struct write_file_data *)client_data; + bytesWritten = write(mine->fd, buff, length); + if (bytesWritten <= 0) { + archive_set_error(a, errno, "Write error"); + return (-1); + } + return (bytesWritten); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct write_file_data *mine = (struct write_file_data *)client_data; + + (void)a; /* UNUSED */ + if (mine->filename[0] != '\0') + close(mine->fd); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_open_memory.c b/libarchive/archive_write_open_memory.c new file mode 100644 index 00000000..d235ca01 --- /dev/null +++ b/libarchive/archive_write_open_memory.c @@ -0,0 +1,126 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_memory.c,v 1.3 2007/01/09 08:05:56 kientzle Exp $"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "archive.h" + +/* + * This is a little tricky. I used to allow the + * compression handling layer to fork the compressor, + * which means this write function gets invoked in + * a separate process. That would, of course, make it impossible + * to actually use the data stored into memory here. + * Fortunately, none of the compressors fork today and + * I'm reluctant to use that route in the future but, if + * forking compressors ever do reappear, this will have + * to get a lot more complicated. + */ + +struct write_memory_data { + size_t used; + size_t size; + size_t * client_size; + unsigned char * buff; +}; + +static int memory_write_close(struct archive *, void *); +static int memory_write_open(struct archive *, void *); +static ssize_t memory_write(struct archive *, void *, const void *buff, size_t); + +/* + * Client provides a pointer to a block of memory to receive + * the data. The 'size' param both tells us the size of the + * client buffer and lets us tell the client the final size. + */ +int +archive_write_open_memory(struct archive *a, void *buff, size_t buffSize, size_t *used) +{ + struct write_memory_data *mine; + + mine = (struct write_memory_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + memset(mine, 0, sizeof(*mine)); + mine->buff = buff; + mine->size = buffSize; + mine->client_size = used; + return (archive_write_open(a, mine, + memory_write_open, memory_write, memory_write_close)); +} + +static int +memory_write_open(struct archive *a, void *client_data) +{ + struct write_memory_data *mine; + mine = client_data; + mine->used = 0; + if (mine->client_size != NULL) + *mine->client_size = mine->used; + /* Disable padding if it hasn't been set explicitly. */ + if (-1 == archive_write_get_bytes_in_last_block(a)) + archive_write_set_bytes_in_last_block(a, 1); + return (ARCHIVE_OK); +} + +/* + * Copy the data into the client buffer. + * Note that we update mine->client_size on every write. + * In particular, this means the client can follow exactly + * how much has been written into their buffer at any time. + */ +static ssize_t +memory_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_memory_data *mine; + mine = client_data; + + if (mine->used + length > mine->size) { + archive_set_error(a, ENOMEM, "Buffer exhausted"); + return (ARCHIVE_FATAL); + } + memcpy(mine->buff + mine->used, buff, length); + mine->used += length; + if (mine->client_size != NULL) + *mine->client_size = mine->used; + return (length); +} + +static int +memory_write_close(struct archive *a, void *client_data) +{ + struct write_memory_data *mine; + (void)a; /* UNUSED */ + mine = client_data; + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_private.h b/libarchive/archive_write_private.h new file mode 100644 index 00000000..55c24ad9 --- /dev/null +++ b/libarchive/archive_write_private.h @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/archive_write_private.h,v 1.3 2008/03/15 11:04:45 kientzle Exp $ + */ + +#ifndef ARCHIVE_WRITE_PRIVATE_H_INCLUDED +#define ARCHIVE_WRITE_PRIVATE_H_INCLUDED + +#include "archive.h" +#include "archive_string.h" +#include "archive_private.h" + +struct archive_write { + struct archive archive; + + /* Dev/ino of the archive being 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; + + /* Callbacks to open/read/write/close archive stream. */ + archive_open_callback *client_opener; + 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. */ + + /* + * On write, the client just invokes an archive_write_set function + * which sets up the data here directly. + */ + struct { + void *data; + void *config; + int (*init)(struct archive_write *); + int (*finish)(struct archive_write *); + int (*write)(struct archive_write *, const void *, size_t); + } compressor; + + /* + * Pointers to format-specific functions for writing. They're + * initialized by archive_write_set_format_XXX() calls. + */ + void *format_data; + int (*format_init)(struct archive_write *); + int (*format_finish)(struct archive_write *); + int (*format_destroy)(struct archive_write *); + int (*format_finish_entry)(struct archive_write *); + int (*format_write_header)(struct archive_write *, + struct archive_entry *); + ssize_t (*format_write_data)(struct archive_write *, + const void *buff, size_t); +}; + +/* + * 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_write *, char buff[512], + struct archive_entry *, int tartype, int strict); + +#endif diff --git a/libarchive/archive_write_set_compression_bzip2.c b/libarchive/archive_write_set_compression_bzip2.c new file mode 100644 index 00000000..272ae26e --- /dev/null +++ b/libarchive/archive_write_set_compression_bzip2.c @@ -0,0 +1,354 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +/* Don't compile this if we don't have bzlib. */ +#if HAVE_BZLIB_H + +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_compression_bzip2.c,v 1.13 2007/12/30 04:58:21 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct private_data { + bz_stream stream; + int64_t total_in; + char *compressed; + size_t compressed_buffer_size; +}; + + +/* + * Yuck. bzlib.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 = (char *)(uintptr_t)(const void *)(src) + +static int archive_compressor_bzip2_finish(struct archive_write *); +static int archive_compressor_bzip2_init(struct archive_write *); +static int archive_compressor_bzip2_write(struct archive_write *, + const void *, size_t); +static int drive_compressor(struct archive_write *, struct private_data *, + int finishing); + +/* + * Allocate, initialize and return an archive object. + */ +int +archive_write_set_compression_bzip2(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_compression_bzip2"); + a->compressor.init = &archive_compressor_bzip2_init; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_bzip2_init(struct archive_write *a) +{ + int ret; + struct private_data *state; + + a->archive.compression_code = ARCHIVE_COMPRESSION_BZIP2; + a->archive.compression_name = "bzip2"; + + if (a->client_opener != NULL) { + ret = (a->client_opener)(&a->archive, a->client_data); + if (ret != 0) + return (ret); + } + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, 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 = (char *)malloc(state->compressed_buffer_size); + + if (state->compressed == NULL) { + archive_set_error(&a->archive, 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; + a->compressor.write = archive_compressor_bzip2_write; + a->compressor.finish = archive_compressor_bzip2_finish; + + /* Initialize compression library */ + ret = BZ2_bzCompressInit(&(state->stream), 9, 0, 30); + if (ret == BZ_OK) { + a->compressor.data = state; + return (ARCHIVE_OK); + } + + /* Library setup failed: clean up. */ + archive_set_error(&a->archive, 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 BZ_PARAM_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid setup parameter"); + break; + case BZ_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Internal error initializing compression library: " + "out of memory"); + break; + case BZ_CONFIG_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "mis-compiled library"); + break; + } + + return (ARCHIVE_FATAL); + +} + +/* + * Write data to the compressed stream. + * + * Returns ARCHIVE_OK if all data written, error otherwise. + */ +static int +archive_compressor_bzip2_write(struct archive_write *a, const void *buff, + size_t length) +{ + struct private_data *state; + + state = (struct private_data *)a->compressor.data; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + /* Update statistics */ + state->total_in += length; + + /* Compress input data to output buffer */ + SET_NEXT_IN(state, buff); + state->stream.avail_in = length; + if (drive_compressor(a, state, 0)) + return (ARCHIVE_FATAL); + a->archive.file_position += length; + return (ARCHIVE_OK); +} + + +/* + * Finish the compression. + */ +static int +archive_compressor_bzip2_finish(struct archive_write *a) +{ + ssize_t block_length; + int ret; + struct private_data *state; + ssize_t target_block_length; + ssize_t bytes_written; + unsigned tocopy; + + state = (struct private_data *)a->compressor.data; + ret = ARCHIVE_OK; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered?\n" + "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->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))) + goto cleanup; + + /* 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->archive, a->client_data, + state->compressed, block_length); + + /* TODO: Handle short write of final block. */ + if (bytes_written <= 0) + ret = ARCHIVE_FATAL; + else { + a->archive.raw_position += ret; + ret = ARCHIVE_OK; + } + + /* Cleanup: shut down compressor, release memory, etc. */ +cleanup: + switch (BZ2_bzCompressEnd(&(state->stream))) { + case BZ_OK: + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + + free(state->compressed); + free(state); + 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_write *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->archive, + 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 remainder to + * front and keep filling */ + memmove(state->compressed, + state->compressed + bytes_written, + state->compressed_buffer_size - bytes_written); + } + + a->archive.raw_position += bytes_written; + state->stream.next_out = state->compressed + + state->compressed_buffer_size - bytes_written; + state->stream.avail_out = bytes_written; + } + + /* If there's nothing to do, we're done. */ + if (!finishing && state->stream.avail_in == 0) + return (ARCHIVE_OK); + + ret = BZ2_bzCompress(&(state->stream), + finishing ? BZ_FINISH : BZ_RUN); + + switch (ret) { + case BZ_RUN_OK: + /* In non-finishing case, did compressor + * consume everything? */ + if (!finishing && state->stream.avail_in == 0) + return (ARCHIVE_OK); + break; + case BZ_FINISH_OK: /* Finishing: There's more work to do */ + break; + case BZ_STREAM_END: /* Finishing: all done */ + /* Only occurs in finishing case */ + return (ARCHIVE_OK); + default: + /* Any other return value indicates an error */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_PROGRAMMER, + "Bzip2 compression failed;" + " BZ2_bzCompress() returned %d", + ret); + return (ARCHIVE_FATAL); + } + } +} + +#endif /* HAVE_BZLIB_H */ diff --git a/libarchive/archive_write_set_compression_compress.c b/libarchive/archive_write_set_compression_compress.c new file mode 100644 index 00000000..f913a23c --- /dev/null +++ b/libarchive/archive_write_set_compression_compress.c @@ -0,0 +1,494 @@ +/*- + * Copyright (c) 2008 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1985, 1986, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis and James A. Woods, derived from original + * work by Spencer Thomas and Joseph Orost. + * + * 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. + * 3. 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. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_compression_compress.c,v 1.1 2008/03/14 20:35:37 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#define HSIZE 69001 /* 95% occupancy */ +#define HSHIFT 8 /* 8 - trunc(log2(HSIZE / 65536)) */ +#define CHECK_GAP 10000 /* Ratio check interval. */ + +#define MAXCODE(bits) ((1 << (bits)) - 1) + +/* + * the next two codes should not be changed lightly, as they must not + * lie within the contiguous general code space. + */ +#define FIRST 257 /* First free entry. */ +#define CLEAR 256 /* Table clear output code. */ + +struct private_data { + off_t in_count, out_count, checkpoint; + + int code_len; /* Number of bits/code. */ + int cur_maxcode; /* Maximum code, given n_bits. */ + int max_maxcode; /* Should NEVER generate this code. */ + int hashtab [HSIZE]; + unsigned short codetab [HSIZE]; + int first_free; /* First unused entry. */ + int compress_ratio; + + int cur_code, cur_fcode; + + int bit_offset; + unsigned char bit_buf; + + unsigned char *compressed; + size_t compressed_buffer_size; + size_t compressed_offset; +}; + +static int archive_compressor_compress_finish(struct archive_write *); +static int archive_compressor_compress_init(struct archive_write *); +static int archive_compressor_compress_write(struct archive_write *, + const void *, size_t); + +/* + * Allocate, initialize and return a archive object. + */ +int +archive_write_set_compression_compress(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_compression_compress"); + a->compressor.init = &archive_compressor_compress_init; + a->archive.compression_code = ARCHIVE_COMPRESSION_COMPRESS; + a->archive.compression_name = "compress"; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_compress_init(struct archive_write *a) +{ + int ret; + struct private_data *state; + + a->archive.compression_code = ARCHIVE_COMPRESSION_COMPRESS; + a->archive.compression_name = "compress"; + + if (a->bytes_per_block < 4) { + archive_set_error(&a->archive, EINVAL, + "Can't write Compress header as single block"); + return (ARCHIVE_FATAL); + } + + if (a->client_opener != NULL) { + ret = (a->client_opener)(&a->archive, a->client_data); + if (ret != ARCHIVE_OK) + return (ret); + } + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, 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 = malloc(state->compressed_buffer_size); + + if (state->compressed == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for compression buffer"); + free(state); + return (ARCHIVE_FATAL); + } + + a->compressor.write = archive_compressor_compress_write; + a->compressor.finish = archive_compressor_compress_finish; + + state->max_maxcode = 0x10000; /* Should NEVER generate this code. */ + state->in_count = 0; /* Length of input. */ + state->bit_buf = 0; + state->bit_offset = 0; + state->out_count = 3; /* Includes 3-byte header mojo. */ + state->compress_ratio = 0; + state->checkpoint = CHECK_GAP; + state->code_len = 9; + state->cur_maxcode = MAXCODE(state->code_len); + state->first_free = FIRST; + + memset(state->hashtab, 0xff, sizeof(state->hashtab)); + + /* Prime output buffer with a gzip header. */ + state->compressed[0] = 0x1f; /* Compress */ + state->compressed[1] = 0x9d; + state->compressed[2] = 0x90; /* Block mode, 16bit max */ + state->compressed_offset = 3; + + a->compressor.data = state; + return (0); +} + +/*- + * Output the given code. + * Inputs: + * code: A n_bits-bit integer. If == -1, then EOF. This assumes + * that n_bits =< (long)wordsize - 1. + * Outputs: + * Outputs code to the file. + * Assumptions: + * Chars are 8 bits long. + * Algorithm: + * Maintain a BITS character long buffer (so that 8 codes will + * fit in it exactly). Use the VAX insv instruction to insert each + * code in turn. When the buffer fills up empty it and start over. + */ + +static unsigned char rmask[9] = + {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; + +static int +output_byte(struct archive_write *a, unsigned char c) +{ + struct private_data *state = a->compressor.data; + ssize_t bytes_written; + + state->compressed[state->compressed_offset++] = c; + ++state->out_count; + + if (state->compressed_buffer_size == state->compressed_offset) { + bytes_written = (a->client_writer)(&a->archive, + a->client_data, + state->compressed, state->compressed_buffer_size); + if (bytes_written <= 0) + return ARCHIVE_FATAL; + a->archive.raw_position += bytes_written; + state->compressed_offset = 0; + } + + return ARCHIVE_OK; +} + +static int +output_code(struct archive_write *a, int ocode) +{ + struct private_data *state = a->compressor.data; + int bits, ret, clear_flg, bit_offset; + + clear_flg = ocode == CLEAR; + bits = state->code_len; + + /* + * Since ocode is always >= 8 bits, only need to mask the first + * hunk on the left. + */ + bit_offset = state->bit_offset % 8; + state->bit_buf |= (ocode << bit_offset) & 0xff; + output_byte(a, state->bit_buf); + + bits = state->code_len - (8 - bit_offset); + ocode >>= 8 - bit_offset; + /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */ + if (bits >= 8) { + output_byte(a, ocode & 0xff); + ocode >>= 8; + bits -= 8; + } + /* Last bits. */ + state->bit_offset += state->code_len; + state->bit_buf = ocode & rmask[bits]; + if (state->bit_offset == state->code_len * 8) + state->bit_offset = 0; + + /* + * If the next entry is going to be too big for the ocode size, + * then increase it, if possible. + */ + if (clear_flg || state->first_free > state->cur_maxcode) { + /* + * Write the whole buffer, because the input side won't + * discover the size increase until after it has read it. + */ + if (state->bit_offset > 0) { + while (state->bit_offset < state->code_len * 8) { + ret = output_byte(a, state->bit_buf); + if (ret != ARCHIVE_OK) + return ret; + state->bit_offset += 8; + state->bit_buf = 0; + } + } + state->bit_buf = 0; + state->bit_offset = 0; + + if (clear_flg) { + state->code_len = 9; + state->cur_maxcode = MAXCODE(state->code_len); + } else { + state->code_len++; + if (state->code_len == 16) + state->cur_maxcode = state->max_maxcode; + else + state->cur_maxcode = MAXCODE(state->code_len); + } + } + + return (ARCHIVE_OK); +} + +static int +output_flush(struct archive_write *a) +{ + struct private_data *state = a->compressor.data; + int ret; + + /* At EOF, write the rest of the buffer. */ + if (state->bit_offset % 8) { + state->code_len = (state->bit_offset % 8 + 7) / 8; + ret = output_byte(a, state->bit_buf); + if (ret != ARCHIVE_OK) + return ret; + } + + return (ARCHIVE_OK); +} + +/* + * Write data to the compressed stream. + */ +static int +archive_compressor_compress_write(struct archive_write *a, const void *buff, + size_t length) +{ + struct private_data *state; + int i; + int ratio; + int c, disp, ret; + const unsigned char *bp; + + state = (struct private_data *)a->compressor.data; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + if (length == 0) + return ARCHIVE_OK; + + bp = buff; + + if (state->in_count == 0) { + state->cur_code = *bp++; + ++state->in_count; + --length; + } + + while (length--) { + c = *bp++; + state->in_count++; + state->cur_fcode = (c << 16) + state->cur_code; + i = ((c << HSHIFT) ^ state->cur_code); /* Xor hashing. */ + + if (state->hashtab[i] == state->cur_fcode) { + state->cur_code = state->codetab[i]; + continue; + } + if (state->hashtab[i] < 0) /* Empty slot. */ + goto nomatch; + /* Secondary hash (after G. Knott). */ + if (i == 0) + disp = 1; + else + disp = HSIZE - i; + probe: + if ((i -= disp) < 0) + i += HSIZE; + + if (state->hashtab[i] == state->cur_fcode) { + state->cur_code = state->codetab[i]; + continue; + } + if (state->hashtab[i] >= 0) + goto probe; + nomatch: + ret = output_code(a, state->cur_code); + if (ret != ARCHIVE_OK) + return ret; + state->cur_code = c; + if (state->first_free < state->max_maxcode) { + state->codetab[i] = state->first_free++; /* code -> hashtable */ + state->hashtab[i] = state->cur_fcode; + continue; + } + if (state->in_count < state->checkpoint) + continue; + + state->checkpoint = state->in_count + CHECK_GAP; + + if (state->in_count <= 0x007fffff) + ratio = state->in_count * 256 / state->out_count; + else if ((ratio = state->out_count / 256) == 0) + ratio = 0x7fffffff; + else + ratio = state->in_count / ratio; + + if (ratio > state->compress_ratio) + state->compress_ratio = ratio; + else { + state->compress_ratio = 0; + memset(state->hashtab, 0xff, sizeof(state->hashtab)); + state->first_free = FIRST; + ret = output_code(a, CLEAR); + if (ret != ARCHIVE_OK) + return ret; + } + } + + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ +static int +archive_compressor_compress_finish(struct archive_write *a) +{ + ssize_t block_length, target_block_length, bytes_written; + int ret; + struct private_data *state; + unsigned tocopy; + + state = (struct private_data *)a->compressor.data; + ret = 0; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, 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) { + while (state->in_count % a->bytes_per_block != 0) { + tocopy = a->bytes_per_block - + (state->in_count % a->bytes_per_block); + if (tocopy > a->null_length) + tocopy = a->null_length; + ret = archive_compressor_compress_write(a, a->nulls, + tocopy); + if (ret != ARCHIVE_OK) + goto cleanup; + } + } + + ret = output_code(a, state->cur_code); + if (ret != ARCHIVE_OK) + goto cleanup; + ret = output_flush(a); + if (ret != ARCHIVE_OK) + goto cleanup; + + /* Optionally, pad the final compressed block. */ + block_length = state->compressed_offset; + + /* Tricky calculation to determine size of last block. */ + 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->compressed + state->compressed_offset, 0, + target_block_length - block_length); + block_length = target_block_length; + } + + /* Write the last block */ + bytes_written = (a->client_writer)(&a->archive, a->client_data, + state->compressed, block_length); + if (bytes_written <= 0) + ret = ARCHIVE_FATAL; + else + a->archive.raw_position += bytes_written; + +cleanup: + free(state->compressed); + free(state); + return (ret); +} diff --git a/libarchive/archive_write_set_compression_gzip.c b/libarchive/archive_write_set_compression_gzip.c new file mode 100644 index 00000000..18abbdf6 --- /dev/null +++ b/libarchive/archive_write_set_compression_gzip.c @@ -0,0 +1,430 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +/* Don't compile this if we don't have zlib. */ +#if HAVE_ZLIB_H + +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_compression_gzip.c,v 1.16 2008/02/21 03:21:50 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#include <time.h> +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +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 *)(uintptr_t)(const void *)(src) + +static int archive_compressor_gzip_finish(struct archive_write *); +static int archive_compressor_gzip_init(struct archive_write *); +static int archive_compressor_gzip_write(struct archive_write *, + const void *, size_t); +static int drive_compressor(struct archive_write *, struct private_data *, + int finishing); + + +/* + * Allocate, initialize and return a archive object. + */ +int +archive_write_set_compression_gzip(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_compression_gzip"); + a->compressor.init = &archive_compressor_gzip_init; + a->archive.compression_code = ARCHIVE_COMPRESSION_GZIP; + a->archive.compression_name = "gzip"; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_gzip_init(struct archive_write *a) +{ + int ret; + struct private_data *state; + time_t t; + + a->archive.compression_code = ARCHIVE_COMPRESSION_GZIP; + a->archive.compression_name = "gzip"; + + if (a->client_opener != NULL) { + ret = (a->client_opener)(&a->archive, a->client_data); + if (ret != ARCHIVE_OK) + return (ret); + } + + /* + * The next check is a temporary workaround until the gzip + * code can be overhauled some. The code should not require + * that compressed_buffer_size == bytes_per_block. Removing + * this assumption will allow us to compress larger chunks at + * a time, which should improve overall performance + * marginally. As a minor side-effect, such a cleanup would + * allow us to support truly arbitrary block sizes. + */ + if (a->bytes_per_block < 10) { + archive_set_error(&a->archive, EINVAL, + "GZip compressor requires a minimum 10 byte block size"); + return (ARCHIVE_FATAL); + } + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for compression"); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + /* + * See comment above. We should set compressed_buffer_size to + * max(bytes_per_block, 65536), but the code can't handle that yet. + */ + 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->archive, 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->compressor.write = archive_compressor_gzip_write; + a->compressor.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->compressor.data = state; + return (0); + } + + /* Library setup failed: clean up. */ + archive_set_error(&a->archive, 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, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid setup parameter"); + break; + case Z_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, "Internal error initializing " + "compression library"); + break; + case Z_VERSION_ERROR: + archive_set_error(&a->archive, 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_write *a, const void *buff, + size_t length) +{ + struct private_data *state; + int ret; + + state = (struct private_data *)a->compressor.data; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, 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->archive.file_position += length; + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ +static int +archive_compressor_gzip_finish(struct archive_write *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->compressor.data; + ret = 0; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, 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->archive, a->client_data, + state->compressed, state->compressed_buffer_size); + if (bytes_written <= 0) { + ret = ARCHIVE_FATAL; + goto cleanup; + } + a->archive.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->archive, a->client_data, + state->compressed, block_length); + if (bytes_written <= 0) { + ret = ARCHIVE_FATAL; + goto cleanup; + } + a->archive.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, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + free(state->compressed); + free(state); + 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_write *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->archive, + 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->archive.raw_position += bytes_written; + state->stream.next_out + = state->compressed + + state->compressed_buffer_size - bytes_written; + state->stream.avail_out = bytes_written; + } + + /* If there's nothing to do, we're done. */ + if (!finishing && state->stream.avail_in == 0) + return (ARCHIVE_OK); + + 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, ARCHIVE_ERRNO_MISC, + "GZip compression failed:" + " deflate() call returned status %d", + ret); + return (ARCHIVE_FATAL); + } + } +} + +#endif /* HAVE_ZLIB_H */ diff --git a/libarchive/archive_write_set_compression_none.c b/libarchive/archive_write_set_compression_none.c new file mode 100644 index 00000000..bdecb240 --- /dev/null +++ b/libarchive/archive_write_set_compression_none.c @@ -0,0 +1,259 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_compression_none.c,v 1.16 2007/12/30 04:58:22 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static int archive_compressor_none_finish(struct archive_write *a); +static int archive_compressor_none_init(struct archive_write *); +static int archive_compressor_none_write(struct archive_write *, + 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) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_compression_none"); + a->compressor.init = &archive_compressor_none_init; + return (0); +} + +/* + * Setup callback. + */ +static int +archive_compressor_none_init(struct archive_write *a) +{ + int ret; + struct archive_none *state; + + a->archive.compression_code = ARCHIVE_COMPRESSION_NONE; + a->archive.compression_name = "none"; + + if (a->client_opener != NULL) { + ret = (a->client_opener)(&a->archive, a->client_data); + if (ret != 0) + return (ret); + } + + state = (struct archive_none *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for output buffering"); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + state->buffer_size = a->bytes_per_block; + if (state->buffer_size != 0) { + state->buffer = (char *)malloc(state->buffer_size); + if (state->buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate output buffer"); + free(state); + return (ARCHIVE_FATAL); + } + } + + state->next = state->buffer; + state->avail = state->buffer_size; + + a->compressor.data = state; + a->compressor.write = archive_compressor_none_write; + a->compressor.finish = archive_compressor_none_finish; + return (ARCHIVE_OK); +} + +/* + * Write data to the stream. + */ +static int +archive_compressor_none_write(struct archive_write *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->compressor.data; + buff = (const char *)vbuff; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + remaining = length; + + /* + * If there is no buffer for blocking, just pass the data + * straight through to the client write callback. In + * particular, this supports "no write delay" operation for + * special applications. Just set the block size to zero. + */ + if (state->buffer_size == 0) { + while (remaining > 0) { + bytes_written = (a->client_writer)(&a->archive, + a->client_data, buff, remaining); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + a->archive.raw_position += bytes_written; + remaining -= bytes_written; + buff += bytes_written; + } + a->archive.file_position += length; + return (ARCHIVE_OK); + } + + /* If the copy buffer isn't empty, try to fill it. */ + if (state->avail < state->buffer_size) { + /* If buffer is not empty... */ + /* ... copy data into buffer ... */ + 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; + /* ... if it's full, write it out. */ + if (state->avail == 0) { + bytes_written = (a->client_writer)(&a->archive, + a->client_data, state->buffer, state->buffer_size); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + /* XXX TODO: if bytes_written < state->buffer_size */ + a->archive.raw_position += bytes_written; + state->next = state->buffer; + state->avail = state->buffer_size; + } + } + + while (remaining > state->buffer_size) { + /* Write out full blocks directly to client. */ + bytes_written = (a->client_writer)(&a->archive, + a->client_data, buff, state->buffer_size); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + a->archive.raw_position += bytes_written; + buff += bytes_written; + remaining -= bytes_written; + } + + if (remaining > 0) { + /* Copy last bit into copy buffer. */ + memcpy(state->next, buff, remaining); + state->next += remaining; + state->avail -= remaining; + } + + a->archive.file_position += length; + return (ARCHIVE_OK); +} + + +/* + * Finish the compression. + */ +static int +archive_compressor_none_finish(struct archive_write *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->compressor.data; + ret = ret2 = ARCHIVE_OK; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, 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->archive, + a->client_data, state->buffer, block_length); + if (bytes_written <= 0) + ret = ARCHIVE_FATAL; + else { + a->archive.raw_position += bytes_written; + ret = ARCHIVE_OK; + } + } + if (state->buffer) + free(state->buffer); + free(state); + a->compressor.data = NULL; + + return (ret); +} diff --git a/libarchive/archive_write_set_compression_program.c b/libarchive/archive_write_set_compression_program.c new file mode 100644 index 00000000..fc1481db --- /dev/null +++ b/libarchive/archive_write_set_compression_program.c @@ -0,0 +1,322 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_compression_program.c,v 1.1 2007/05/29 01:00:19 kientzle Exp $"); + +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif +#ifdef HAVE_ERRNO_H +# include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +# include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#include "filter_fork.h" + +struct private_data { + char *description; + pid_t child; + int child_stdin, child_stdout; + + char *child_buf; + size_t child_buf_len, child_buf_avail; +}; + +static int archive_compressor_program_finish(struct archive_write *); +static int archive_compressor_program_init(struct archive_write *); +static int archive_compressor_program_write(struct archive_write *, + const void *, size_t); + +/* + * Allocate, initialize and return a archive object. + */ +int +archive_write_set_compression_program(struct archive *_a, const char *cmd) +{ + struct archive_write *a = (struct archive_write *)_a; + __archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_compression_program"); + a->compressor.init = &archive_compressor_program_init; + a->compressor.config = strdup(cmd); + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_program_init(struct archive_write *a) +{ + int ret; + struct private_data *state; + static const char *prefix = "Program: "; + char *cmd = a->compressor.config; + + if (a->client_opener != NULL) { + ret = (a->client_opener)(&a->archive, a->client_data); + if (ret != ARCHIVE_OK) + return (ret); + } + + state = (struct private_data *)malloc(sizeof(*state)); + if (state == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for compression"); + return (ARCHIVE_FATAL); + } + memset(state, 0, sizeof(*state)); + + a->archive.compression_code = ARCHIVE_COMPRESSION_PROGRAM; + state->description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1); + strcpy(state->description, prefix); + strcat(state->description, cmd); + a->archive.compression_name = state->description; + + state->child_buf_len = a->bytes_per_block; + state->child_buf_avail = 0; + state->child_buf = malloc(state->child_buf_len); + + if (state->child_buf == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data for compression buffer"); + free(state); + return (ARCHIVE_FATAL); + } + + if ((state->child = __archive_create_child(cmd, + &state->child_stdin, &state->child_stdout)) == -1) { + archive_set_error(&a->archive, EINVAL, + "Can't initialise filter"); + free(state->child_buf); + free(state); + return (ARCHIVE_FATAL); + } + + a->compressor.write = archive_compressor_program_write; + a->compressor.finish = archive_compressor_program_finish; + + a->compressor.data = state; + return (0); +} + +static ssize_t +child_write(struct archive_write *a, const char *buf, size_t buf_len) +{ + struct private_data *state = a->compressor.data; + ssize_t ret; + + if (state->child_stdin == -1) + return (-1); + + if (buf_len == 0) + return (-1); + +restart_write: + do { + ret = write(state->child_stdin, buf, buf_len); + } while (ret == -1 && errno == EINTR); + + if (ret > 0) + return (ret); + if (ret == 0) { + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + return (0); + } + if (ret == -1 && errno != EAGAIN) + return (-1); + + do { + ret = read(state->child_stdout, + state->child_buf + state->child_buf_avail, + state->child_buf_len - state->child_buf_avail); + } while (ret == -1 && errno == EINTR); + + if (ret == 0 || (ret == -1 && errno == EPIPE)) { + close(state->child_stdout); + state->child_stdout = -1; + fcntl(state->child_stdin, F_SETFL, 0); + goto restart_write; + } + if (ret == -1 && errno == EAGAIN) { + __archive_check_child(state->child_stdin, state->child_stdout); + goto restart_write; + } + if (ret == -1) + return (-1); + + state->child_buf_avail += ret; + + ret = (a->client_writer)(&a->archive, a->client_data, + state->child_buf, state->child_buf_avail); + if (ret <= 0) + return (-1); + + if ((size_t)ret < state->child_buf_avail) { + memmove(state->child_buf, state->child_buf + ret, + state->child_buf_avail - ret); + } + state->child_buf_avail -= ret; + a->archive.raw_position += ret; + goto restart_write; +} + +/* + * Write data to the compressed stream. + */ +static int +archive_compressor_program_write(struct archive_write *a, const void *buff, + size_t length) +{ + struct private_data *state; + ssize_t ret; + const char *buf; + + state = (struct private_data *)a->compressor.data; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + return (ARCHIVE_FATAL); + } + + buf = buff; + while (length > 0) { + ret = child_write(a, buf, length); + if (ret == -1 || ret == 0) { + archive_set_error(&a->archive, EIO, + "Can't write to filter"); + return (ARCHIVE_FATAL); + } + length -= ret; + buf += ret; + } + + a->archive.file_position += length; + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ +static int +archive_compressor_program_finish(struct archive_write *a) +{ + int ret, status; + ssize_t bytes_read, bytes_written; + struct private_data *state; + + state = (struct private_data *)a->compressor.data; + ret = 0; + if (a->client_writer == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "No write callback is registered? " + "This is probably an internal programming error."); + ret = ARCHIVE_FATAL; + goto cleanup; + } + + /* XXX pad compressed data. */ + + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + + for (;;) { + do { + bytes_read = read(state->child_stdout, + state->child_buf + state->child_buf_avail, + state->child_buf_len - state->child_buf_avail); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0 || (bytes_read == -1 && errno == EPIPE)) + break; + + if (bytes_read == -1) { + archive_set_error(&a->archive, errno, + "Read from filter failed unexpectedly."); + ret = ARCHIVE_FATAL; + goto cleanup; + } + state->child_buf_avail += bytes_read; + + bytes_written = (a->client_writer)(&a->archive, a->client_data, + state->child_buf, state->child_buf_avail); + if (bytes_written <= 0) { + ret = ARCHIVE_FATAL; + goto cleanup; + } + if ((size_t)bytes_written < state->child_buf_avail) { + memmove(state->child_buf, + state->child_buf + bytes_written, + state->child_buf_avail - bytes_written); + } + state->child_buf_avail -= bytes_written; + a->archive.raw_position += bytes_written; + } + + /* XXX pad final compressed block. */ + +cleanup: + /* Shut down the child. */ + if (state->child_stdin != -1) + close(state->child_stdin); + if (state->child_stdout != -1) + close(state->child_stdout); + while (waitpid(state->child, &status, 0) == -1 && errno == EINTR) + continue; + + if (status != 0) { + archive_set_error(&a->archive, EIO, + "Filter exited with failure."); + ret = ARCHIVE_FATAL; + } + + /* Release our configuration data. */ + free(a->compressor.config); + a->compressor.config = NULL; + + /* Release our private state data. */ + free(state->child_buf); + free(state->description); + free(state); + return (ret); +} diff --git a/libarchive/archive_write_set_format.c b/libarchive/archive_write_set_format.c new file mode 100644 index 00000000..e5f89211 --- /dev/null +++ b/libarchive/archive_write_set_format.c @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format.c,v 1.5 2007/06/22 05:47:00 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +#include "archive.h" +#include "archive_private.h" + +/* A table that maps format codes to functions. */ +static +struct { int code; int (*setter)(struct archive *); } codes[] = +{ + { ARCHIVE_FORMAT_CPIO, archive_write_set_format_cpio }, + { ARCHIVE_FORMAT_CPIO_SVR4_NOCRC, archive_write_set_format_cpio_newc }, + { ARCHIVE_FORMAT_CPIO_POSIX, archive_write_set_format_cpio }, + { ARCHIVE_FORMAT_SHAR, archive_write_set_format_shar }, + { ARCHIVE_FORMAT_SHAR_BASE, archive_write_set_format_shar }, + { ARCHIVE_FORMAT_SHAR_DUMP, archive_write_set_format_shar_dump }, + { ARCHIVE_FORMAT_TAR, archive_write_set_format_pax_restricted }, + { ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE, archive_write_set_format_pax }, + { ARCHIVE_FORMAT_TAR_PAX_RESTRICTED, + archive_write_set_format_pax_restricted }, + { ARCHIVE_FORMAT_TAR_USTAR, archive_write_set_format_ustar }, + { 0, NULL } +}; + +int +archive_write_set_format(struct archive *a, int code) +{ + int i; + + for (i = 0; codes[i].code != 0; i++) { + if (code == codes[i].code) + return ((codes[i].setter)(a)); + } + + archive_set_error(a, EINVAL, "No such format"); + return (ARCHIVE_FATAL); +} diff --git a/libarchive/archive_write_set_format_ar.c b/libarchive/archive_write_set_format_ar.c new file mode 100644 index 00000000..313d0b39 --- /dev/null +++ b/libarchive/archive_write_set_format_ar.c @@ -0,0 +1,546 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 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. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_ar.c,v 1.6 2008/03/15 11:04:45 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct ar_w { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + int is_strtab; + int has_strtab; + char *strtab; +}; + +/* + * Define structure of the "ar" header. + */ +#define AR_name_offset 0 +#define AR_name_size 16 +#define AR_date_offset 16 +#define AR_date_size 12 +#define AR_uid_offset 28 +#define AR_uid_size 6 +#define AR_gid_offset 34 +#define AR_gid_size 6 +#define AR_mode_offset 40 +#define AR_mode_size 8 +#define AR_size_offset 48 +#define AR_size_size 10 +#define AR_fmag_offset 58 +#define AR_fmag_size 2 + +static int archive_write_set_format_ar(struct archive_write *); +static int archive_write_ar_header(struct archive_write *, + struct archive_entry *); +static ssize_t archive_write_ar_data(struct archive_write *, + const void *buff, size_t s); +static int archive_write_ar_destroy(struct archive_write *); +static int archive_write_ar_finish(struct archive_write *); +static int archive_write_ar_finish_entry(struct archive_write *); +static const char *ar_basename(const char *path); +static int format_octal(int64_t v, char *p, int s); +static int format_decimal(int64_t v, char *p, int s); + +int +archive_write_set_format_ar_bsd(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = archive_write_set_format_ar(a); + if (r == ARCHIVE_OK) { + a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; + a->archive.archive_format_name = "ar (BSD)"; + } + return (r); +} + +int +archive_write_set_format_ar_svr4(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = archive_write_set_format_ar(a); + if (r == ARCHIVE_OK) { + a->archive.archive_format = ARCHIVE_FORMAT_AR_GNU; + a->archive.archive_format_name = "ar (GNU/SVR4)"; + } + return (r); +} + +/* + * Generic initialization. + */ +static int +archive_write_set_format_ar(struct archive_write *a) +{ + struct ar_w *ar; + + /* If someone else was already registered, unregister them. */ + if (a->format_destroy != NULL) + (a->format_destroy)(a); + + ar = (struct ar_w *)malloc(sizeof(*ar)); + if (ar == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate ar data"); + return (ARCHIVE_FATAL); + } + memset(ar, 0, sizeof(*ar)); + a->format_data = ar; + + a->format_write_header = archive_write_ar_header; + a->format_write_data = archive_write_ar_data; + a->format_finish = archive_write_ar_finish; + a->format_destroy = archive_write_ar_destroy; + a->format_finish_entry = archive_write_ar_finish_entry; + return (ARCHIVE_OK); +} + +static int +archive_write_ar_header(struct archive_write *a, struct archive_entry *entry) +{ + int ret, append_fn; + char buff[60]; + char *ss, *se; + struct ar_w *ar; + const char *pathname; + const char *filename; + + ret = 0; + append_fn = 0; + ar = (struct ar_w *)a->format_data; + ar->is_strtab = 0; + filename = NULL; + + /* + * Reject files with empty name. + */ + pathname = archive_entry_pathname(entry); + if (*pathname == '\0') { + archive_set_error(&a->archive, EINVAL, + "Invalid filename"); + return (ARCHIVE_WARN); + } + + /* + * If we are now at the beginning of the archive, + * we need first write the ar global header. + */ + if (a->archive.file_position == 0) + (a->compressor.write)(a, "!<arch>\n", 8); + + memset(buff, ' ', 60); + strncpy(&buff[AR_fmag_offset], "`\n", 2); + + if (strcmp(pathname, "/") == 0 ) { + /* Entry is archive symbol table in GNU format */ + buff[AR_name_offset] = '/'; + goto stat; + } + if (strcmp(pathname, "__.SYMDEF") == 0) { + /* Entry is archive symbol table in BSD format */ + strncpy(buff + AR_name_offset, "__.SYMDEF", 9); + goto stat; + } + if (strcmp(pathname, "//") == 0) { + /* + * Entry is archive filename table, inform that we should + * collect strtab in next _data call. + */ + ar->is_strtab = 1; + buff[AR_name_offset] = buff[AR_name_offset + 1] = '/'; + /* + * For archive string table, only ar_size filed should + * be set. + */ + goto size; + } + + /* + * Otherwise, entry is a normal archive member. + * Strip leading paths from filenames, if any. + */ + if ((filename = ar_basename(pathname)) == NULL) { + /* Reject filenames with trailing "/" */ + archive_set_error(&a->archive, EINVAL, + "Invalid filename"); + return (ARCHIVE_WARN); + } + + if (a->archive.archive_format == ARCHIVE_FORMAT_AR_GNU) { + /* + * SVR4/GNU variant use a "/" to mark then end of the filename, + * make it possible to have embedded spaces in the filename. + * So, the longest filename here (without extension) is + * actually 15 bytes. + */ + if (strlen(filename) <= 15) { + strncpy(&buff[AR_name_offset], + filename, strlen(filename)); + buff[AR_name_offset + strlen(filename)] = '/'; + } else { + /* + * For filename longer than 15 bytes, GNU variant + * makes use of a string table and instead stores the + * offset of the real filename to in the ar_name field. + * The string table should have been written before. + */ + if (ar->has_strtab <= 0) { + archive_set_error(&a->archive, EINVAL, + "Can't find string table"); + return (ARCHIVE_WARN); + } + + se = (char *)malloc(strlen(filename) + 3); + if (se == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate filename buffer"); + return (ARCHIVE_FATAL); + } + + strncpy(se, filename, strlen(filename)); + strcpy(se + strlen(filename), "/\n"); + + ss = strstr(ar->strtab, se); + free(se); + + if (ss == NULL) { + archive_set_error(&a->archive, EINVAL, + "Invalid string table"); + return (ARCHIVE_WARN); + } + + /* + * GNU variant puts "/" followed by digits into + * ar_name field. These digits indicates the real + * filename string's offset to the string table. + */ + buff[AR_name_offset] = '/'; + if (format_decimal(ss - ar->strtab, + buff + AR_name_offset + 1, + AR_name_size - 1)) { + archive_set_error(&a->archive, ERANGE, + "string table offset too large"); + return (ARCHIVE_WARN); + } + } + } else if (a->archive.archive_format == ARCHIVE_FORMAT_AR_BSD) { + /* + * BSD variant: for any file name which is more than + * 16 chars or contains one or more embedded space(s), the + * string "#1/" followed by the ASCII length of the name is + * put into the ar_name field. The file size (stored in the + * ar_size field) is incremented by the length of the name. + * The name is then written immediately following the + * archive header. + */ + if (strlen(filename) <= 16 && strchr(filename, ' ') == NULL) { + strncpy(&buff[AR_name_offset], filename, strlen(filename)); + buff[AR_name_offset + strlen(filename)] = ' '; + } + else { + strncpy(buff + AR_name_offset, "#1/", 3); + if (format_decimal(strlen(filename), + buff + AR_name_offset + 3, + AR_name_size - 3)) { + archive_set_error(&a->archive, ERANGE, + "File name too long"); + return (ARCHIVE_WARN); + } + append_fn = 1; + archive_entry_set_size(entry, + archive_entry_size(entry) + strlen(filename)); + } + } + +stat: + if (format_decimal(archive_entry_mtime(entry), buff + AR_date_offset, AR_date_size)) { + archive_set_error(&a->archive, ERANGE, + "File modification time too large"); + return (ARCHIVE_WARN); + } + if (format_decimal(archive_entry_uid(entry), buff + AR_uid_offset, AR_uid_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric user ID too large"); + return (ARCHIVE_WARN); + } + if (format_decimal(archive_entry_gid(entry), buff + AR_gid_offset, AR_gid_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric group ID too large"); + return (ARCHIVE_WARN); + } + if (format_octal(archive_entry_mode(entry), buff + AR_mode_offset, AR_mode_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric mode too large"); + return (ARCHIVE_WARN); + } + /* + * Sanity Check: A non-pseudo archive member should always be + * a regular file. + */ + if (filename != NULL && archive_entry_filetype(entry) != AE_IFREG) { + archive_set_error(&a->archive, EINVAL, + "Regular file required for non-pseudo member"); + return (ARCHIVE_WARN); + } + +size: + if (format_decimal(archive_entry_size(entry), buff + AR_size_offset, + AR_size_size)) { + archive_set_error(&a->archive, ERANGE, + "File size out of range"); + return (ARCHIVE_WARN); + } + + ret = (a->compressor.write)(a, buff, 60); + if (ret != ARCHIVE_OK) + return (ret); + + ar->entry_bytes_remaining = archive_entry_size(entry); + ar->entry_padding = ar->entry_bytes_remaining % 2; + + if (append_fn > 0) { + ret = (a->compressor.write)(a, filename, strlen(filename)); + if (ret != ARCHIVE_OK) + return (ret); + ar->entry_bytes_remaining -= strlen(filename); + } + + return (ARCHIVE_OK); +} + +static ssize_t +archive_write_ar_data(struct archive_write *a, const void *buff, size_t s) +{ + struct ar_w *ar; + int ret; + + ar = (struct ar_w *)a->format_data; + if (s > ar->entry_bytes_remaining) + s = ar->entry_bytes_remaining; + + if (ar->is_strtab > 0) { + if (ar->has_strtab > 0) { + archive_set_error(&a->archive, EINVAL, + "More than one string tables exist"); + return (ARCHIVE_WARN); + } + + ar->strtab = (char *)malloc(s); + if (ar->strtab == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate strtab buffer"); + return (ARCHIVE_FATAL); + } + strncpy(ar->strtab, buff, s); + ar->has_strtab = 1; + } + + ret = (a->compressor.write)(a, buff, s); + if (ret != ARCHIVE_OK) + return (ret); + + ar->entry_bytes_remaining -= s; + return (s); +} + +static int +archive_write_ar_destroy(struct archive_write *a) +{ + struct ar_w *ar; + + ar = (struct ar_w *)a->format_data; + + if (ar->has_strtab > 0) { + free(ar->strtab); + ar->strtab = NULL; + } + + free(ar); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_ar_finish(struct archive_write *a) +{ + int ret; + + /* + * If we haven't written anything yet, we need to write + * the ar global header now to make it a valid ar archive. + */ + if (a->archive.file_position == 0) { + ret = (a->compressor.write)(a, "!<arch>\n", 8); + return (ret); + } + + return (ARCHIVE_OK); +} + +static int +archive_write_ar_finish_entry(struct archive_write *a) +{ + struct ar_w *ar; + int ret; + + ar = (struct ar_w *)a->format_data; + + if (ar->entry_bytes_remaining != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Entry remaining bytes larger than 0"); + return (ARCHIVE_WARN); + } + + if (ar->entry_padding == 0) { + return (ARCHIVE_OK); + } + + if (ar->entry_padding != 1) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Padding wrong size: %d should be 1 or 0", + ar->entry_padding); + return (ARCHIVE_WARN); + } + + ret = (a->compressor.write)(a, "\n", 1); + return (ret); +} + +/* + * Format a number into the specified field using base-8. + * NB: This version is slightly different from the one in + * _ustar.c + */ +static int +format_octal(int64_t v, char *p, int s) +{ + int len; + char *h; + + len = s; + h = p; + + /* 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. */ + do { + *--p = (char)('0' + (v & 7)); + v >>= 3; + } while (--s > 0 && v > 0); + + if (v == 0) { + memmove(h, p, len - s); + p = h + len - s; + while (s-- > 0) + *p++ = ' '; + return (0); + } + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '7'; + + return (-1); +} + +/* + * Format a number into the specified field using base-10. + */ +static int +format_decimal(int64_t v, char *p, int s) +{ + int len; + char *h; + + len = s; + h = p; + + /* Negative values in ar header are meaningless , so use 0. */ + if (v < 0) { + while (len-- > 0) + *p++ = '0'; + return (-1); + } + + p += s; + do { + *--p = (char)('0' + (v % 10)); + v /= 10; + } while (--s > 0 && v > 0); + + if (v == 0) { + memmove(h, p, len - s); + p = h + len - s; + while (s-- > 0) + *p++ = ' '; + return (0); + } + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '9'; + + return (-1); +} + +static const char * +ar_basename(const char *path) +{ + const char *endp, *startp; + + endp = path + strlen(path) - 1; + /* + * For filename with trailing slash(es), we return + * NULL indicating an error. + */ + if (*endp == '/') + return (NULL); + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + return (startp); +} diff --git a/libarchive/archive_write_set_format_by_name.c b/libarchive/archive_write_set_format_by_name.c new file mode 100644 index 00000000..0d7cae48 --- /dev/null +++ b/libarchive/archive_write_set_format_by_name.c @@ -0,0 +1,74 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_by_name.c,v 1.7 2007/06/22 05:47:00 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_private.h" + +/* A table that maps names to functions. */ +static +struct { const char *name; int (*setter)(struct archive *); } names[] = +{ + { "arbsd", archive_write_set_format_ar_bsd }, + { "ar", archive_write_set_format_ar_bsd }, + { "argnu", archive_write_set_format_ar_svr4 }, + { "arsvr4", archive_write_set_format_ar_svr4 }, + { "cpio", archive_write_set_format_cpio }, + { "newc", archive_write_set_format_cpio_newc }, + { "odc", archive_write_set_format_cpio }, + { "pax", archive_write_set_format_pax }, + { "posix", archive_write_set_format_pax }, + { "shar", archive_write_set_format_shar }, + { "shardump", archive_write_set_format_shar_dump }, + { "ustar", archive_write_set_format_ustar }, + { NULL, NULL } +}; + +int +archive_write_set_format_by_name(struct archive *a, const char *name) +{ + int i; + + for (i = 0; names[i].name != NULL; i++) { + if (strcmp(name, names[i].name) == 0) + return ((names[i].setter)(a)); + } + + archive_set_error(a, EINVAL, "No such format '%s'", name); + return (ARCHIVE_FATAL); +} diff --git a/libarchive/archive_write_set_format_cpio.c b/libarchive/archive_write_set_format_cpio.c new file mode 100644 index 00000000..61042999 --- /dev/null +++ b/libarchive/archive_write_set_format_cpio.c @@ -0,0 +1,267 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_cpio.c,v 1.14 2008/03/15 11:04:45 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static ssize_t archive_write_cpio_data(struct archive_write *, + const void *buff, size_t s); +static int archive_write_cpio_finish(struct archive_write *); +static int archive_write_cpio_destroy(struct archive_write *); +static int archive_write_cpio_finish_entry(struct archive_write *); +static int archive_write_cpio_header(struct archive_write *, + struct archive_entry *); +static int format_octal(int64_t, void *, int); +static int64_t format_octal_recursive(int64_t, char *, int); + +struct cpio { + uint64_t entry_bytes_remaining; +}; + +struct cpio_header { + char c_magic[6]; + char c_dev[6]; + char c_ino[6]; + char c_mode[6]; + char c_uid[6]; + char c_gid[6]; + char c_nlink[6]; + char c_rdev[6]; + char c_mtime[11]; + char c_namesize[6]; + char c_filesize[11]; +}; + +/* + * Set output format to 'cpio' format. + */ +int +archive_write_set_format_cpio(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct cpio *cpio; + + /* If someone else was already registered, unregister them. */ + if (a->format_destroy != NULL) + (a->format_destroy)(a); + + cpio = (struct cpio *)malloc(sizeof(*cpio)); + if (cpio == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); + return (ARCHIVE_FATAL); + } + memset(cpio, 0, sizeof(*cpio)); + a->format_data = cpio; + + a->pad_uncompressed = 1; + a->format_write_header = archive_write_cpio_header; + a->format_write_data = archive_write_cpio_data; + a->format_finish_entry = archive_write_cpio_finish_entry; + a->format_finish = archive_write_cpio_finish; + a->format_destroy = archive_write_cpio_destroy; + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_POSIX; + a->archive.archive_format_name = "POSIX cpio"; + return (ARCHIVE_OK); +} + +static int +archive_write_cpio_header(struct archive_write *a, struct archive_entry *entry) +{ + struct cpio *cpio; + const char *p, *path; + int pathlength, ret; + struct cpio_header h; + + cpio = (struct cpio *)a->format_data; + ret = 0; + + path = archive_entry_pathname(entry); + pathlength = strlen(path) + 1; /* Include trailing null. */ + + memset(&h, 0, sizeof(h)); + format_octal(070707, &h.c_magic, sizeof(h.c_magic)); + format_octal(archive_entry_dev(entry), &h.c_dev, sizeof(h.c_dev)); + /* + * TODO: Generate artificial inode numbers rather than just + * re-using the ones off the disk. That way, the 18-bit c_ino + * field only limits the number of files in the archive. + */ + if (archive_entry_ino(entry) > 0777777) { + archive_set_error(&a->archive, ERANGE, "large inode number truncated"); + ret = ARCHIVE_WARN; + } + + format_octal(archive_entry_ino(entry) & 0777777, &h.c_ino, sizeof(h.c_ino)); + format_octal(archive_entry_mode(entry), &h.c_mode, sizeof(h.c_mode)); + format_octal(archive_entry_uid(entry), &h.c_uid, sizeof(h.c_uid)); + format_octal(archive_entry_gid(entry), &h.c_gid, sizeof(h.c_gid)); + format_octal(archive_entry_nlink(entry), &h.c_nlink, sizeof(h.c_nlink)); + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) + format_octal(archive_entry_dev(entry), &h.c_rdev, sizeof(h.c_rdev)); + else + format_octal(0, &h.c_rdev, sizeof(h.c_rdev)); + format_octal(archive_entry_mtime(entry), &h.c_mtime, sizeof(h.c_mtime)); + format_octal(pathlength, &h.c_namesize, sizeof(h.c_namesize)); + + /* Non-regular files don't store bodies. */ + if (archive_entry_filetype(entry) != AE_IFREG) + archive_entry_set_size(entry, 0); + + /* Symlinks get the link written as the body of the entry. */ + p = archive_entry_symlink(entry); + if (p != NULL && *p != '\0') + format_octal(strlen(p), &h.c_filesize, sizeof(h.c_filesize)); + else + format_octal(archive_entry_size(entry), + &h.c_filesize, sizeof(h.c_filesize)); + + ret = (a->compressor.write)(a, &h, sizeof(h)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + ret = (a->compressor.write)(a, path, pathlength); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + cpio->entry_bytes_remaining = archive_entry_size(entry); + + /* Write the symlink now. */ + if (p != NULL && *p != '\0') + ret = (a->compressor.write)(a, p, strlen(p)); + + return (ret); +} + +static ssize_t +archive_write_cpio_data(struct archive_write *a, const void *buff, size_t s) +{ + struct cpio *cpio; + int ret; + + cpio = (struct cpio *)a->format_data; + if (s > cpio->entry_bytes_remaining) + s = cpio->entry_bytes_remaining; + + ret = (a->compressor.write)(a, buff, s); + cpio->entry_bytes_remaining -= s; + if (ret >= 0) + return (s); + else + return (ret); +} + +/* + * Format a number into the specified field. + */ +static int +format_octal(int64_t v, void *p, int digits) +{ + int64_t max; + int ret; + + max = (((int64_t)1) << (digits * 3)) - 1; + if (v >= 0 && v <= max) { + format_octal_recursive(v, (char *)p, digits); + ret = 0; + } else { + format_octal_recursive(max, (char *)p, digits); + ret = -1; + } + return (ret); +} + +static int64_t +format_octal_recursive(int64_t v, char *p, int s) +{ + if (s == 0) + return (v); + v = format_octal_recursive(v, p+1, s-1); + *p = '0' + (v & 7); + return (v >>= 3); +} + +static int +archive_write_cpio_finish(struct archive_write *a) +{ + struct cpio *cpio; + int er; + struct archive_entry *trailer; + + cpio = (struct cpio *)a->format_data; + trailer = archive_entry_new(); + /* nlink = 1 here for GNU cpio compat. */ + archive_entry_set_nlink(trailer, 1); + archive_entry_set_pathname(trailer, "TRAILER!!!"); + er = archive_write_cpio_header(a, trailer); + archive_entry_free(trailer); + return (er); +} + +static int +archive_write_cpio_destroy(struct archive_write *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)a->format_data; + free(cpio); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_cpio_finish_entry(struct archive_write *a) +{ + struct cpio *cpio; + int to_write, ret; + + cpio = (struct cpio *)a->format_data; + ret = ARCHIVE_OK; + while (cpio->entry_bytes_remaining > 0) { + to_write = cpio->entry_bytes_remaining < a->null_length ? + cpio->entry_bytes_remaining : a->null_length; + ret = (a->compressor.write)(a, a->nulls, to_write); + if (ret != ARCHIVE_OK) + return (ret); + cpio->entry_bytes_remaining -= to_write; + } + return (ret); +} diff --git a/libarchive/archive_write_set_format_cpio_newc.c b/libarchive/archive_write_set_format_cpio_newc.c new file mode 100644 index 00000000..b5a2a028 --- /dev/null +++ b/libarchive/archive_write_set_format_cpio_newc.c @@ -0,0 +1,285 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2006 Rudolf Marek SYSGO s.r.o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_cpio_newc.c,v 1.4 2008/03/15 11:04:45 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static ssize_t archive_write_newc_data(struct archive_write *, + const void *buff, size_t s); +static int archive_write_newc_finish(struct archive_write *); +static int archive_write_newc_destroy(struct archive_write *); +static int archive_write_newc_finish_entry(struct archive_write *); +static int archive_write_newc_header(struct archive_write *, + struct archive_entry *); +static int format_hex(int64_t, void *, int); +static int64_t format_hex_recursive(int64_t, char *, int); + +struct cpio { + uint64_t entry_bytes_remaining; + int padding; +}; + +struct cpio_header_newc { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_checksum[8]; +}; + +/* + * Set output format to 'cpio' format. + */ +int +archive_write_set_format_cpio_newc(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct cpio *cpio; + + /* If someone else was already registered, unregister them. */ + if (a->format_destroy != NULL) + (a->format_destroy)(a); + + cpio = (struct cpio *)malloc(sizeof(*cpio)); + if (cpio == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); + return (ARCHIVE_FATAL); + } + memset(cpio, 0, sizeof(*cpio)); + a->format_data = cpio; + + a->pad_uncompressed = 1; + a->format_write_header = archive_write_newc_header; + a->format_write_data = archive_write_newc_data; + a->format_finish_entry = archive_write_newc_finish_entry; + a->format_finish = archive_write_newc_finish; + a->format_destroy = archive_write_newc_destroy; + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC; + a->archive.archive_format_name = "SVR4 cpio nocrc"; + return (ARCHIVE_OK); +} + +static int +archive_write_newc_header(struct archive_write *a, struct archive_entry *entry) +{ + struct cpio *cpio; + const char *p, *path; + int pathlength, ret; + struct cpio_header_newc h; + int pad; + + cpio = (struct cpio *)a->format_data; + ret = 0; + + path = archive_entry_pathname(entry); + pathlength = strlen(path) + 1; /* Include trailing null. */ + + memset(&h, 0, sizeof(h)); + format_hex(0x070701, &h.c_magic, sizeof(h.c_magic)); + format_hex(archive_entry_devmajor(entry), &h.c_devmajor, sizeof(h.c_devmajor)); + format_hex(archive_entry_devminor(entry), &h.c_devminor, sizeof(h.c_devminor)); + if (archive_entry_ino(entry) > 0xffffffff) { + archive_set_error(&a->archive, ERANGE, "large inode number truncated"); + ret = ARCHIVE_WARN; + } + + format_hex(archive_entry_ino(entry) & 0xffffffff, &h.c_ino, sizeof(h.c_ino)); + format_hex(archive_entry_mode(entry), &h.c_mode, sizeof(h.c_mode)); + format_hex(archive_entry_uid(entry), &h.c_uid, sizeof(h.c_uid)); + format_hex(archive_entry_gid(entry), &h.c_gid, sizeof(h.c_gid)); + format_hex(archive_entry_nlink(entry), &h.c_nlink, sizeof(h.c_nlink)); + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) { + format_hex(archive_entry_rdevmajor(entry), &h.c_rdevmajor, sizeof(h.c_rdevmajor)); + format_hex(archive_entry_rdevminor(entry), &h.c_rdevminor, sizeof(h.c_rdevminor)); + } else { + format_hex(0, &h.c_rdevmajor, sizeof(h.c_rdevmajor)); + format_hex(0, &h.c_rdevminor, sizeof(h.c_rdevminor)); + } + format_hex(archive_entry_mtime(entry), &h.c_mtime, sizeof(h.c_mtime)); + format_hex(pathlength, &h.c_namesize, sizeof(h.c_namesize)); + format_hex(0, &h.c_checksum, sizeof(h.c_checksum)); + + /* Non-regular files don't store bodies. */ + if (archive_entry_filetype(entry) != AE_IFREG) + archive_entry_set_size(entry, 0); + + /* Symlinks get the link written as the body of the entry. */ + p = archive_entry_symlink(entry); + if (p != NULL && *p != '\0') + format_hex(strlen(p), &h.c_filesize, sizeof(h.c_filesize)); + else + format_hex(archive_entry_size(entry), + &h.c_filesize, sizeof(h.c_filesize)); + + ret = (a->compressor.write)(a, &h, sizeof(h)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Pad pathname to even length. */ + ret = (a->compressor.write)(a, path, pathlength); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + pad = 0x3 & - (pathlength + sizeof(struct cpio_header_newc)); + if (pad) + ret = (a->compressor.write)(a, "\0\0\0", pad); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + cpio->entry_bytes_remaining = archive_entry_size(entry); + cpio->padding = 3 & (-cpio->entry_bytes_remaining); + + /* Write the symlink now. */ + if (p != NULL && *p != '\0') { + ret = (a->compressor.write)(a, p, strlen(p)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + pad = 0x3 & -strlen(p); + ret = (a->compressor.write)(a, "\0\0\0", pad); + } + + return (ret); +} + +static ssize_t +archive_write_newc_data(struct archive_write *a, const void *buff, size_t s) +{ + struct cpio *cpio; + int ret; + + cpio = (struct cpio *)a->format_data; + if (s > cpio->entry_bytes_remaining) + s = cpio->entry_bytes_remaining; + + ret = (a->compressor.write)(a, buff, s); + cpio->entry_bytes_remaining -= s; + if (ret >= 0) + return (s); + else + return (ret); +} + +/* + * Format a number into the specified field. + */ +static int +format_hex(int64_t v, void *p, int digits) +{ + int64_t max; + int ret; + + max = (((int64_t)1) << (digits * 4)) - 1; + if (v >= 0 && v <= max) { + format_hex_recursive(v, (char *)p, digits); + ret = 0; + } else { + format_hex_recursive(max, (char *)p, digits); + ret = -1; + } + return (ret); +} + +static int64_t +format_hex_recursive(int64_t v, char *p, int s) +{ + if (s == 0) + return (v); + v = format_hex_recursive(v, p+1, s-1); + *p = "0123456789abcdef"[v & 0xf]; + return (v >>= 4); +} + +static int +archive_write_newc_finish(struct archive_write *a) +{ + struct cpio *cpio; + int er; + struct archive_entry *trailer; + + cpio = (struct cpio *)a->format_data; + trailer = archive_entry_new(); + archive_entry_set_nlink(trailer, 1); + archive_entry_set_pathname(trailer, "TRAILER!!!"); + er = archive_write_newc_header(a, trailer); + archive_entry_free(trailer); + return (er); +} + +static int +archive_write_newc_destroy(struct archive_write *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)a->format_data; + free(cpio); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_newc_finish_entry(struct archive_write *a) +{ + struct cpio *cpio; + int to_write, ret; + + cpio = (struct cpio *)a->format_data; + ret = ARCHIVE_OK; + while (cpio->entry_bytes_remaining > 0) { + to_write = cpio->entry_bytes_remaining < a->null_length ? + cpio->entry_bytes_remaining : a->null_length; + ret = (a->compressor.write)(a, a->nulls, to_write); + if (ret != ARCHIVE_OK) + return (ret); + cpio->entry_bytes_remaining -= to_write; + } + ret = (a->compressor.write)(a, a->nulls, cpio->padding); + return (ret); +} diff --git a/libarchive/archive_write_set_format_pax.c b/libarchive/archive_write_set_format_pax.c new file mode 100644 index 00000000..d6e3e6c4 --- /dev/null +++ b/libarchive/archive_write_set_format_pax.c @@ -0,0 +1,1316 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_pax.c,v 1.46 2008/03/15 11:04:45 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct pax { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + struct archive_string pax_header; +}; + +static void add_pax_attr(struct archive_string *, const char *key, + const char *value); +static void add_pax_attr_int(struct archive_string *, + const char *key, int64_t value); +static void add_pax_attr_time(struct archive_string *, + const char *key, int64_t sec, + unsigned long nanos); +static void add_pax_attr_w(struct archive_string *, + const char *key, const wchar_t *wvalue); +static ssize_t archive_write_pax_data(struct archive_write *, + const void *, size_t); +static int archive_write_pax_finish(struct archive_write *); +static int archive_write_pax_destroy(struct archive_write *); +static int archive_write_pax_finish_entry(struct archive_write *); +static int archive_write_pax_header(struct archive_write *, + struct archive_entry *); +static char *base64_encode(const char *src, size_t len); +static char *build_pax_attribute_name(char *dest, const char *src); +static char *build_ustar_entry_name(char *dest, const char *src, + size_t src_length, const char *insert); +static char *format_int(char *dest, int64_t); +static int has_non_ASCII(const wchar_t *); +static char *url_encode(const char *in); +static int write_nulls(struct archive_write *, size_t); + +/* + * Set output format to 'restricted pax' format. + * + * This is the same as normal 'pax', but tries to suppress + * the pax header whenever possible. This is the default for + * bsdtar, for instance. + */ +int +archive_write_set_format_pax_restricted(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r; + r = archive_write_set_format_pax(&a->archive); + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; + a->archive.archive_format_name = "restricted POSIX pax interchange"; + return (r); +} + +/* + * Set output format to 'pax' format. + */ +int +archive_write_set_format_pax(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct pax *pax; + + if (a->format_destroy != NULL) + (a->format_destroy)(a); + + pax = (struct pax *)malloc(sizeof(*pax)); + if (pax == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate pax data"); + return (ARCHIVE_FATAL); + } + memset(pax, 0, sizeof(*pax)); + a->format_data = pax; + + a->pad_uncompressed = 1; + a->format_write_header = archive_write_pax_header; + a->format_write_data = archive_write_pax_data; + a->format_finish = archive_write_pax_finish; + a->format_destroy = archive_write_pax_destroy; + a->format_finish_entry = archive_write_pax_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange"; + return (ARCHIVE_OK); +} + +/* + * Note: This code assumes that 'nanos' has the same sign as 'sec', + * which implies that sec=-1, nanos=200000000 represents -1.2 seconds + * and not -0.8 seconds. This is a pretty pedantic point, as we're + * unlikely to encounter many real files created before Jan 1, 1970, + * much less ones with timestamps recorded to sub-second resolution. + */ +static void +add_pax_attr_time(struct archive_string *as, const char *key, + int64_t sec, unsigned long nanos) +{ + int digit, i; + char *t; + /* + * Note that each byte contributes fewer than 3 base-10 + * digits, so this will always be big enough. + */ + char tmp[1 + 3*sizeof(sec) + 1 + 3*sizeof(nanos)]; + + tmp[sizeof(tmp) - 1] = 0; + t = tmp + sizeof(tmp) - 1; + + /* Skip trailing zeros in the fractional part. */ + for (digit = 0, i = 10; i > 0 && digit == 0; i--) { + digit = nanos % 10; + nanos /= 10; + } + + /* Only format the fraction if it's non-zero. */ + if (i > 0) { + while (i > 0) { + *--t = "0123456789"[digit]; + digit = nanos % 10; + nanos /= 10; + i--; + } + *--t = '.'; + } + t = format_int(t, sec); + + add_pax_attr(as, key, t); +} + +static char * +format_int(char *t, int64_t i) +{ + int sign; + + if (i < 0) { + sign = -1; + i = -i; + } else + sign = 1; + + do { + *--t = "0123456789"[i % 10]; + } while (i /= 10); + if (sign < 0) + *--t = '-'; + return (t); +} + +static void +add_pax_attr_int(struct archive_string *as, const char *key, int64_t value) +{ + char tmp[1 + 3 * sizeof(value)]; + + tmp[sizeof(tmp) - 1] = 0; + add_pax_attr(as, key, format_int(tmp + sizeof(tmp) - 1, value)); +} + +static char * +utf8_encode(const wchar_t *wval) +{ + int utf8len; + const wchar_t *wp; + unsigned long wc; + char *utf8_value, *p; + + utf8len = 0; + for (wp = wval; *wp != L'\0'; ) { + wc = *wp++; + if (wc <= 0x7f) + utf8len++; + else if (wc <= 0x7ff) + utf8len += 2; + else if (wc <= 0xffff) + utf8len += 3; + else if (wc <= 0x1fffff) + utf8len += 4; + else if (wc <= 0x3ffffff) + utf8len += 5; + else if (wc <= 0x7fffffff) + utf8len += 6; + /* Ignore larger values; UTF-8 can't encode them. */ + } + + utf8_value = (char *)malloc(utf8len + 1); + if (utf8_value == NULL) { + __archive_errx(1, "Not enough memory for attributes"); + return (NULL); + } + + for (wp = wval, p = utf8_value; *wp != L'\0'; ) { + wc = *wp++; + if (wc <= 0x7f) { + *p++ = (char)wc; + } else if (wc <= 0x7ff) { + p[0] = 0xc0 | ((wc >> 6) & 0x1f); + p[1] = 0x80 | (wc & 0x3f); + p += 2; + } else if (wc <= 0xffff) { + p[0] = 0xe0 | ((wc >> 12) & 0x0f); + p[1] = 0x80 | ((wc >> 6) & 0x3f); + p[2] = 0x80 | (wc & 0x3f); + p += 3; + } else if (wc <= 0x1fffff) { + p[0] = 0xf0 | ((wc >> 18) & 0x07); + p[1] = 0x80 | ((wc >> 12) & 0x3f); + p[2] = 0x80 | ((wc >> 6) & 0x3f); + p[3] = 0x80 | (wc & 0x3f); + p += 4; + } else if (wc <= 0x3ffffff) { + p[0] = 0xf8 | ((wc >> 24) & 0x03); + p[1] = 0x80 | ((wc >> 18) & 0x3f); + p[2] = 0x80 | ((wc >> 12) & 0x3f); + p[3] = 0x80 | ((wc >> 6) & 0x3f); + p[4] = 0x80 | (wc & 0x3f); + p += 5; + } else if (wc <= 0x7fffffff) { + p[0] = 0xfc | ((wc >> 30) & 0x01); + p[1] = 0x80 | ((wc >> 24) & 0x3f); + p[1] = 0x80 | ((wc >> 18) & 0x3f); + p[2] = 0x80 | ((wc >> 12) & 0x3f); + p[3] = 0x80 | ((wc >> 6) & 0x3f); + p[4] = 0x80 | (wc & 0x3f); + p += 6; + } + /* Ignore larger values; UTF-8 can't encode them. */ + } + *p = '\0'; + + return (utf8_value); +} + +static void +add_pax_attr_w(struct archive_string *as, const char *key, const wchar_t *wval) +{ + char *utf8_value = utf8_encode(wval); + if (utf8_value == NULL) + return; + add_pax_attr(as, key, utf8_value); + free(utf8_value); +} + +/* + * Add a key/value attribute to the pax header. This function handles + * the length field and various other syntactic requirements. + */ +static void +add_pax_attr(struct archive_string *as, const char *key, const char *value) +{ + int digits, i, len, next_ten; + char tmp[1 + 3 * sizeof(int)]; /* < 3 base-10 digits per byte */ + + /*- + * PAX attributes have the following layout: + * <len> <space> <key> <=> <value> <nl> + */ + len = 1 + strlen(key) + 1 + strlen(value) + 1; + + /* + * The <len> field includes the length of the <len> field, so + * computing the correct length is tricky. I start by + * counting the number of base-10 digits in 'len' and + * computing the next higher power of 10. + */ + next_ten = 1; + digits = 0; + i = len; + while (i > 0) { + i = i / 10; + digits++; + next_ten = next_ten * 10; + } + /* + * For example, if string without the length field is 99 + * chars, then adding the 2 digit length "99" will force the + * total length past 100, requiring an extra digit. The next + * statement adjusts for this effect. + */ + if (len + digits >= next_ten) + digits++; + + /* Now, we have the right length so we can build the line. */ + tmp[sizeof(tmp) - 1] = 0; /* Null-terminate the work area. */ + archive_strcat(as, format_int(tmp + sizeof(tmp) - 1, len + digits)); + archive_strappend_char(as, ' '); + archive_strcat(as, key); + archive_strappend_char(as, '='); + archive_strcat(as, value); + archive_strappend_char(as, '\n'); +} + +static void +archive_write_pax_header_xattrs(struct pax *pax, struct archive_entry *entry) +{ + struct archive_string s; + int i = archive_entry_xattr_reset(entry); + + while (i--) { + const char *name; + const void *value; + char *encoded_value; + char *url_encoded_name = NULL, *encoded_name = NULL; + wchar_t *wcs_name = NULL; + size_t size; + + archive_entry_xattr_next(entry, &name, &value, &size); + /* Name is URL-encoded, then converted to wchar_t, + * then UTF-8 encoded. */ + url_encoded_name = url_encode(name); + if (url_encoded_name != NULL) { + /* Convert narrow-character to wide-character. */ + int wcs_length = strlen(url_encoded_name); + wcs_name = (wchar_t *)malloc((wcs_length + 1) * sizeof(wchar_t)); + if (wcs_name == NULL) + __archive_errx(1, "No memory for xattr conversion"); + mbstowcs(wcs_name, url_encoded_name, wcs_length); + wcs_name[wcs_length] = 0; + free(url_encoded_name); /* Done with this. */ + } + if (wcs_name != NULL) { + encoded_name = utf8_encode(wcs_name); + free(wcs_name); /* Done with wchar_t name. */ + } + + encoded_value = base64_encode((const char *)value, size); + + if (encoded_name != NULL && encoded_value != NULL) { + archive_string_init(&s); + archive_strcpy(&s, "LIBARCHIVE.xattr."); + archive_strcat(&s, encoded_name); + add_pax_attr(&(pax->pax_header), s.s, encoded_value); + archive_string_free(&s); + } + free(encoded_name); + free(encoded_value); + } +} + +/* + * TODO: Consider adding 'comment' and 'charset' fields to + * archive_entry so that clients can specify them. Also, consider + * adding generic key/value tags so clients can add arbitrary + * key/value data. + */ +static int +archive_write_pax_header(struct archive_write *a, + struct archive_entry *entry_original) +{ + struct archive_entry *entry_main; + const char *p; + char *t; + const wchar_t *wp; + const char *suffix_start; + int need_extension, r, ret; + struct pax *pax; + const char *hdrcharset = NULL; + const char *hardlink; + const char *path = NULL, *linkpath = NULL; + const char *uname = NULL, *gname = NULL; + const wchar_t *path_w = NULL, *linkpath_w = NULL; + const wchar_t *uname_w = NULL, *gname_w = NULL; + + char paxbuff[512]; + char ustarbuff[512]; + char ustar_entry_name[256]; + char pax_entry_name[256]; + + ret = ARCHIVE_OK; + need_extension = 0; + pax = (struct pax *)a->format_data; + + hardlink = archive_entry_hardlink(entry_original); + + /* Make sure this is a type of entry that we can handle here */ + if (hardlink == NULL) { + switch (archive_entry_filetype(entry_original)) { + case AE_IFBLK: + case AE_IFCHR: + case AE_IFIFO: + case AE_IFLNK: + case AE_IFREG: + break; + case AE_IFDIR: + /* + * Ensure a trailing '/'. Modify the original + * entry so the client sees the change. + */ + p = archive_entry_pathname(entry_original); + if (p[strlen(p) - 1] != '/') { + t = (char *)malloc(strlen(p) + 2); + if (t == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate pax data"); + return(ARCHIVE_FATAL); + } + strcpy(t, p); + strcat(t, "/"); + archive_entry_copy_pathname(entry_original, t); + free(t); + } + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive this (type=0%lo)", + (unsigned long)archive_entry_filetype(entry_original)); + return (ARCHIVE_WARN); + } + } + + /* Copy entry so we can modify it as needed. */ + entry_main = archive_entry_clone(entry_original); + archive_string_empty(&(pax->pax_header)); /* Blank our work area. */ + + /* + * First, check the name fields and see if any of them + * require binary coding. If any of them does, then all of + * them do. + */ + hdrcharset = NULL; + path = archive_entry_pathname(entry_main); + path_w = archive_entry_pathname_w(entry_main); + if (path != NULL && path_w == NULL) { + archive_set_error(&a->archive, EILSEQ, + "Can't translate pathname '%s' to UTF-8", path); + ret = ARCHIVE_WARN; + hdrcharset = "BINARY"; + } + uname = archive_entry_uname(entry_main); + uname_w = archive_entry_uname_w(entry_main); + if (uname != NULL && uname_w == NULL) { + archive_set_error(&a->archive, EILSEQ, + "Can't translate uname '%s' to UTF-8", uname); + ret = ARCHIVE_WARN; + hdrcharset = "BINARY"; + } + gname = archive_entry_gname(entry_main); + gname_w = archive_entry_gname_w(entry_main); + if (gname != NULL && gname_w == NULL) { + archive_set_error(&a->archive, EILSEQ, + "Can't translate gname '%s' to UTF-8", gname); + ret = ARCHIVE_WARN; + hdrcharset = "BINARY"; + } + linkpath = hardlink; + if (linkpath != NULL) { + linkpath_w = archive_entry_hardlink_w(entry_main); + } else { + linkpath = archive_entry_symlink(entry_main); + if (linkpath != NULL) + linkpath_w = archive_entry_symlink_w(entry_main); + } + if (linkpath != NULL && linkpath_w == NULL) { + archive_set_error(&a->archive, EILSEQ, + "Can't translate linkpath '%s' to UTF-8", linkpath); + ret = ARCHIVE_WARN; + hdrcharset = "BINARY"; + } + + /* Store the header encoding first, to be nice to readers. */ + if (hdrcharset != NULL) + add_pax_attr(&(pax->pax_header), "hdrcharset", hdrcharset); + + /* + * Determining whether or not the name is too big is ugly + * because of the rules for dividing names between 'name' and + * 'prefix' fields. Here, I pick out the longest possible + * suffix, then test whether the remaining prefix is too long. + */ + if (strlen(path) <= 100) /* Short enough for just 'name' field */ + suffix_start = path; /* Record a zero-length prefix */ + else + /* Find the largest suffix that fits in 'name' field. */ + suffix_start = strchr(path + strlen(path) - 100 - 1, '/'); + + /* + * If name is too long, or has non-ASCII characters, add + * 'path' to pax extended attrs. (Note that an unconvertible + * name must have non-ASCII characters.) + */ + if (suffix_start == NULL || suffix_start - path > 155 + || path_w == NULL || has_non_ASCII(path_w)) { + if (path_w == NULL || hdrcharset != NULL) + /* Can't do UTF-8, so store it raw. */ + add_pax_attr(&(pax->pax_header), "path", path); + else + add_pax_attr_w(&(pax->pax_header), "path", path_w); + archive_entry_set_pathname(entry_main, + build_ustar_entry_name(ustar_entry_name, + path, strlen(path), NULL)); + need_extension = 1; + } + + if (linkpath != NULL) { + /* If link name is too long or has non-ASCII characters, add + * 'linkpath' to pax extended attrs. */ + if (strlen(linkpath) > 100 || linkpath_w == NULL + || linkpath_w == NULL || has_non_ASCII(linkpath_w)) { + if (linkpath_w == NULL || hdrcharset != NULL) + /* If the linkpath is not convertible + * to wide, or we're encoding in + * binary anyway, store it raw. */ + add_pax_attr(&(pax->pax_header), + "linkpath", linkpath); + else + /* If the link is long or has a + * non-ASCII character, store it as a + * pax extended attribute. */ + add_pax_attr_w(&(pax->pax_header), + "linkpath", linkpath_w); + if (strlen(linkpath) > 100) { + if (hardlink != NULL) + archive_entry_set_hardlink(entry_main, + "././@LongHardLink"); + else + archive_entry_set_symlink(entry_main, + "././@LongSymLink"); + } + need_extension = 1; + } + } + + /* If file size is too large, add 'size' to pax extended attrs. */ + if (archive_entry_size(entry_main) >= (((int64_t)1) << 33)) { + add_pax_attr_int(&(pax->pax_header), "size", + archive_entry_size(entry_main)); + need_extension = 1; + } + + /* If numeric GID is too large, add 'gid' to pax extended attrs. */ + if (archive_entry_gid(entry_main) >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "gid", + archive_entry_gid(entry_main)); + need_extension = 1; + } + + /* If group name is too large or has non-ASCII characters, add + * 'gname' to pax extended attrs. */ + if (gname != NULL) { + if (strlen(gname) > 31 + || gname_w == NULL + || has_non_ASCII(gname_w)) + { + if (gname_w == NULL || hdrcharset != NULL) { + add_pax_attr(&(pax->pax_header), + "gname", gname); + } else { + add_pax_attr_w(&(pax->pax_header), + "gname", gname_w); + } + need_extension = 1; + } + } + + /* If numeric UID is too large, add 'uid' to pax extended attrs. */ + if (archive_entry_uid(entry_main) >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "uid", + archive_entry_uid(entry_main)); + need_extension = 1; + } + + /* Add 'uname' to pax extended attrs if necessary. */ + if (uname != NULL) { + if (strlen(uname) > 31 + || uname_w == NULL + || has_non_ASCII(uname_w)) + { + if (uname_w == NULL || hdrcharset != NULL) { + add_pax_attr(&(pax->pax_header), + "uname", uname); + } else { + add_pax_attr_w(&(pax->pax_header), + "uname", uname_w); + } + need_extension = 1; + } + } + + /* + * POSIX/SUSv3 doesn't provide a standard key for large device + * numbers. I use the same keys here that Joerg Schilling + * used for 'star.' (Which, somewhat confusingly, are called + * "devXXX" even though they code "rdev" values.) No doubt, + * other implementations use other keys. Note that there's no + * reason we can't write the same information into a number of + * different keys. + * + * Of course, this is only needed for block or char device entries. + */ + if (archive_entry_filetype(entry_main) == AE_IFBLK + || archive_entry_filetype(entry_main) == AE_IFCHR) { + /* + * If rdevmajor is too large, add 'SCHILY.devmajor' to + * extended attributes. + */ + dev_t rdevmajor, rdevminor; + rdevmajor = archive_entry_rdevmajor(entry_main); + rdevminor = archive_entry_rdevminor(entry_main); + if (rdevmajor >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "SCHILY.devmajor", + rdevmajor); + /* + * Non-strict formatting below means we don't + * have to truncate here. Not truncating improves + * the chance that some more modern tar archivers + * (such as GNU tar 1.13) can restore the full + * value even if they don't understand the pax + * extended attributes. See my rant below about + * file size fields for additional details. + */ + /* archive_entry_set_rdevmajor(entry_main, + rdevmajor & ((1 << 18) - 1)); */ + need_extension = 1; + } + + /* + * If devminor is too large, add 'SCHILY.devminor' to + * extended attributes. + */ + if (rdevminor >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "SCHILY.devminor", + rdevminor); + /* Truncation is not necessary here, either. */ + /* archive_entry_set_rdevminor(entry_main, + rdevminor & ((1 << 18) - 1)); */ + need_extension = 1; + } + } + + /* + * Technically, the mtime field in the ustar header can + * support 33 bits, but many platforms use signed 32-bit time + * values. The cutoff of 0x7fffffff here is a compromise. + * Yes, this check is duplicated just below; this helps to + * avoid writing an mtime attribute just to handle a + * high-resolution timestamp in "restricted pax" mode. + */ + if (!need_extension && + ((archive_entry_mtime(entry_main) < 0) + || (archive_entry_mtime(entry_main) >= 0x7fffffff))) + need_extension = 1; + + /* I use a star-compatible file flag attribute. */ + p = archive_entry_fflags_text(entry_main); + if (!need_extension && p != NULL && *p != '\0') + need_extension = 1; + + /* If there are non-trivial ACL entries, we need an extension. */ + if (!need_extension && archive_entry_acl_count(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS) > 0) + need_extension = 1; + + /* If there are non-trivial ACL entries, we need an extension. */ + if (!need_extension && archive_entry_acl_count(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) > 0) + need_extension = 1; + + /* If there are extended attributes, we need an extension */ + if (!need_extension && archive_entry_xattr_count(entry_original) > 0) + need_extension = 1; + + /* + * The following items are handled differently in "pax + * restricted" format. In particular, in "pax restricted" + * format they won't be added unless need_extension is + * already set (we're already generating an extended header, so + * may as well include these). + */ + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_RESTRICTED || + need_extension) { + + if (archive_entry_mtime(entry_main) < 0 || + archive_entry_mtime(entry_main) >= 0x7fffffff || + archive_entry_mtime_nsec(entry_main) != 0) + add_pax_attr_time(&(pax->pax_header), "mtime", + archive_entry_mtime(entry_main), + archive_entry_mtime_nsec(entry_main)); + + if (archive_entry_ctime(entry_main) != 0 || + archive_entry_ctime_nsec(entry_main) != 0) + add_pax_attr_time(&(pax->pax_header), "ctime", + archive_entry_ctime(entry_main), + archive_entry_ctime_nsec(entry_main)); + + if (archive_entry_atime(entry_main) != 0 || + archive_entry_atime_nsec(entry_main) != 0) + add_pax_attr_time(&(pax->pax_header), "atime", + archive_entry_atime(entry_main), + archive_entry_atime_nsec(entry_main)); + + /* I use a star-compatible file flag attribute. */ + p = archive_entry_fflags_text(entry_main); + if (p != NULL && *p != '\0') + add_pax_attr(&(pax->pax_header), "SCHILY.fflags", p); + + /* I use star-compatible ACL attributes. */ + wp = archive_entry_acl_text_w(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS | + ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID); + if (wp != NULL && *wp != L'\0') + add_pax_attr_w(&(pax->pax_header), + "SCHILY.acl.access", wp); + wp = archive_entry_acl_text_w(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT | + ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID); + if (wp != NULL && *wp != L'\0') + add_pax_attr_w(&(pax->pax_header), + "SCHILY.acl.default", wp); + + /* Include star-compatible metadata info. */ + /* Note: "SCHILY.dev{major,minor}" are NOT the + * major/minor portions of "SCHILY.dev". */ + add_pax_attr_int(&(pax->pax_header), "SCHILY.dev", + archive_entry_dev(entry_main)); + add_pax_attr_int(&(pax->pax_header), "SCHILY.ino", + archive_entry_ino(entry_main)); + add_pax_attr_int(&(pax->pax_header), "SCHILY.nlink", + archive_entry_nlink(entry_main)); + + /* Store extended attributes */ + archive_write_pax_header_xattrs(pax, entry_original); + } + + /* Only regular files have data. */ + if (archive_entry_filetype(entry_main) != AE_IFREG) + archive_entry_set_size(entry_main, 0); + + /* + * Pax-restricted does not store data for hardlinks, in order + * to improve compatibility with ustar. + */ + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE && + hardlink != NULL) + archive_entry_set_size(entry_main, 0); + + /* + * XXX Full pax interchange format does permit a hardlink + * entry to have data associated with it. I'm not supporting + * that here because the client expects me to tell them whether + * or not this format expects data for hardlinks. If I + * don't check here, then every pax archive will end up with + * duplicated data for hardlinks. Someday, there may be + * need to select this behavior, in which case the following + * will need to be revisited. XXX + */ + if (hardlink != NULL) + archive_entry_set_size(entry_main, 0); + + /* Format 'ustar' header for main entry. + * + * The trouble with file size: If the reader can't understand + * the file size, they may not be able to locate the next + * entry and the rest of the archive is toast. Pax-compliant + * readers are supposed to ignore the file size in the main + * header, so the question becomes how to maximize portability + * for readers that don't support pax attribute extensions. + * For maximum compatibility, I permit numeric extensions in + * the main header so that the file size stored will always be + * correct, even if it's in a format that only some + * implementations understand. The technique used here is: + * + * a) If possible, follow the standard exactly. This handles + * files up to 8 gigabytes minus 1. + * + * b) If that fails, try octal but omit the field terminator. + * That handles files up to 64 gigabytes minus 1. + * + * c) Otherwise, use base-256 extensions. That handles files + * up to 2^63 in this implementation, with the potential to + * go up to 2^94. That should hold us for a while. ;-) + * + * The non-strict formatter uses similar logic for other + * numeric fields, though they're less critical. + */ + __archive_write_format_header_ustar(a, ustarbuff, entry_main, -1, 0); + + /* If we built any extended attributes, write that entry first. */ + if (archive_strlen(&(pax->pax_header)) > 0) { + struct archive_entry *pax_attr_entry; + time_t s; + uid_t uid; + gid_t gid; + mode_t mode; + long ns; + + pax_attr_entry = archive_entry_new(); + p = archive_entry_pathname(entry_main); + archive_entry_set_pathname(pax_attr_entry, + build_pax_attribute_name(pax_entry_name, p)); + archive_entry_set_size(pax_attr_entry, + archive_strlen(&(pax->pax_header))); + /* Copy uid/gid (but clip to ustar limits). */ + uid = archive_entry_uid(entry_main); + if (uid >= 1 << 18) + uid = (1 << 18) - 1; + archive_entry_set_uid(pax_attr_entry, uid); + gid = archive_entry_gid(entry_main); + if (gid >= 1 << 18) + gid = (1 << 18) - 1; + archive_entry_set_gid(pax_attr_entry, gid); + /* Copy mode over (but not setuid/setgid bits) */ + mode = archive_entry_mode(entry_main); +#ifdef S_ISUID + mode &= ~S_ISUID; +#endif +#ifdef S_ISGID + mode &= ~S_ISGID; +#endif +#ifdef S_ISVTX + mode &= ~S_ISVTX; +#endif + archive_entry_set_mode(pax_attr_entry, mode); + + /* Copy uname/gname. */ + archive_entry_set_uname(pax_attr_entry, + archive_entry_uname(entry_main)); + archive_entry_set_gname(pax_attr_entry, + archive_entry_gname(entry_main)); + + /* Copy mtime, but clip to ustar limits. */ + s = archive_entry_mtime(entry_main); + ns = archive_entry_mtime_nsec(entry_main); + if (s < 0) { s = 0; ns = 0; } + if (s > 0x7fffffff) { s = 0x7fffffff; ns = 0; } + archive_entry_set_mtime(pax_attr_entry, s, ns); + + /* Ditto for atime. */ + s = archive_entry_atime(entry_main); + ns = archive_entry_atime_nsec(entry_main); + if (s < 0) { s = 0; ns = 0; } + if (s > 0x7fffffff) { s = 0x7fffffff; ns = 0; } + archive_entry_set_atime(pax_attr_entry, s, ns); + + /* Standard ustar doesn't support ctime. */ + archive_entry_set_ctime(pax_attr_entry, 0, 0); + + r = __archive_write_format_header_ustar(a, paxbuff, + pax_attr_entry, 'x', 1); + + archive_entry_free(pax_attr_entry); + + /* Note that the 'x' header shouldn't ever fail to format */ + if (r != 0) { + const char *msg = "archive_write_pax_header: " + "'x' header failed?! This can't happen.\n"; + write(2, msg, strlen(msg)); + exit(1); + } + r = (a->compressor.write)(a, paxbuff, 512); + if (r != ARCHIVE_OK) { + pax->entry_bytes_remaining = 0; + pax->entry_padding = 0; + return (ARCHIVE_FATAL); + } + + pax->entry_bytes_remaining = archive_strlen(&(pax->pax_header)); + pax->entry_padding = 0x1ff & (-(int64_t)pax->entry_bytes_remaining); + + r = (a->compressor.write)(a, pax->pax_header.s, + archive_strlen(&(pax->pax_header))); + if (r != ARCHIVE_OK) { + /* If a write fails, we're pretty much toast. */ + return (ARCHIVE_FATAL); + } + /* Pad out the end of the entry. */ + r = write_nulls(a, pax->entry_padding); + if (r != ARCHIVE_OK) { + /* If a write fails, we're pretty much toast. */ + return (ARCHIVE_FATAL); + } + pax->entry_bytes_remaining = pax->entry_padding = 0; + } + + /* Write the header for main entry. */ + r = (a->compressor.write)(a, ustarbuff, 512); + if (r != ARCHIVE_OK) + return (r); + + /* + * Inform the client of the on-disk size we're using, so + * they can avoid unnecessarily writing a body for something + * that we're just going to ignore. + */ + archive_entry_set_size(entry_original, archive_entry_size(entry_main)); + pax->entry_bytes_remaining = archive_entry_size(entry_main); + pax->entry_padding = 0x1ff & (-(int64_t)pax->entry_bytes_remaining); + archive_entry_free(entry_main); + + return (ret); +} + +/* + * We need a valid name for the regular 'ustar' entry. This routine + * tries to hack something more-or-less reasonable. + * + * The approach here tries to preserve leading dir names. We do so by + * working with four sections: + * 1) "prefix" directory names, + * 2) "suffix" directory names, + * 3) inserted dir name (optional), + * 4) filename. + * + * These sections must satisfy the following requirements: + * * Parts 1 & 2 together form an initial portion of the dir name. + * * Part 3 is specified by the caller. (It should not contain a leading + * or trailing '/'.) + * * Part 4 forms an initial portion of the base filename. + * * The filename must be <= 99 chars to fit the ustar 'name' field. + * * Parts 2, 3, 4 together must be <= 99 chars to fit the ustar 'name' fld. + * * Part 1 must be <= 155 chars to fit the ustar 'prefix' field. + * * If the original name ends in a '/', the new name must also end in a '/' + * * Trailing '/.' sequences may be stripped. + * + * Note: Recall that the ustar format does not store the '/' separating + * parts 1 & 2, but does store the '/' separating parts 2 & 3. + */ +static char * +build_ustar_entry_name(char *dest, const char *src, size_t src_length, + const char *insert) +{ + const char *prefix, *prefix_end; + const char *suffix, *suffix_end; + const char *filename, *filename_end; + char *p; + int need_slash = 0; /* Was there a trailing slash? */ + size_t suffix_length = 99; + int insert_length; + + /* Length of additional dir element to be added. */ + if (insert == NULL) + insert_length = 0; + else + /* +2 here allows for '/' before and after the insert. */ + insert_length = strlen(insert) + 2; + + /* Step 0: Quick bailout in a common case. */ + if (src_length < 100 && insert == NULL) { + strncpy(dest, src, src_length); + dest[src_length] = '\0'; + return (dest); + } + + /* Step 1: Locate filename and enforce the length restriction. */ + filename_end = src + src_length; + /* Remove trailing '/' chars and '/.' pairs. */ + for (;;) { + if (filename_end > src && filename_end[-1] == '/') { + filename_end --; + need_slash = 1; /* Remember to restore trailing '/'. */ + continue; + } + if (filename_end > src + 1 && filename_end[-1] == '.' + && filename_end[-2] == '/') { + filename_end -= 2; + need_slash = 1; /* "foo/." will become "foo/" */ + continue; + } + break; + } + if (need_slash) + suffix_length--; + /* Find start of filename. */ + filename = filename_end - 1; + while ((filename > src) && (*filename != '/')) + filename --; + if ((*filename == '/') && (filename < filename_end - 1)) + filename ++; + /* Adjust filename_end so that filename + insert fits in 99 chars. */ + suffix_length -= insert_length; + if (filename_end > filename + suffix_length) + filename_end = filename + suffix_length; + /* Calculate max size for "suffix" section (#3 above). */ + suffix_length -= filename_end - filename; + + /* Step 2: Locate the "prefix" section of the dirname, including + * trailing '/'. */ + prefix = src; + prefix_end = prefix + 155; + if (prefix_end > filename) + prefix_end = filename; + while (prefix_end > prefix && *prefix_end != '/') + prefix_end--; + if ((prefix_end < filename) && (*prefix_end == '/')) + prefix_end++; + + /* Step 3: Locate the "suffix" section of the dirname, + * including trailing '/'. */ + suffix = prefix_end; + suffix_end = suffix + suffix_length; /* Enforce limit. */ + if (suffix_end > filename) + suffix_end = filename; + if (suffix_end < suffix) + suffix_end = suffix; + while (suffix_end > suffix && *suffix_end != '/') + suffix_end--; + if ((suffix_end < filename) && (*suffix_end == '/')) + suffix_end++; + + /* Step 4: Build the new name. */ + /* The OpenBSD strlcpy function is safer, but less portable. */ + /* Rather than maintain two versions, just use the strncpy version. */ + p = dest; + if (prefix_end > prefix) { + strncpy(p, prefix, prefix_end - prefix); + p += prefix_end - prefix; + } + if (suffix_end > suffix) { + strncpy(p, suffix, suffix_end - suffix); + p += suffix_end - suffix; + } + if (insert != NULL) { + /* Note: assume insert does not have leading or trailing '/' */ + strcpy(p, insert); + p += strlen(insert); + *p++ = '/'; + } + strncpy(p, filename, filename_end - filename); + p += filename_end - filename; + if (need_slash) + *p++ = '/'; + *p++ = '\0'; + + return (dest); +} + +/* + * The ustar header for the pax extended attributes must have a + * reasonable name: SUSv3 requires 'dirname'/PaxHeader.'pid'/'filename' + * where 'pid' is the PID of the archiving process. Unfortunately, + * that makes testing a pain since the output varies for each run, + * so I'm sticking with the simpler 'dirname'/PaxHeader/'filename' + * for now. (Someday, I'll make this settable. Then I can use the + * SUS recommendation as default and test harnesses can override it + * to get predictable results.) + * + * Joerg Schilling has argued that this is unnecessary because, in + * practice, if the pax extended attributes get extracted as regular + * files, noone is going to bother reading those attributes to + * manually restore them. Based on this, 'star' uses + * /tmp/PaxHeader/'basename' as the ustar header name. This is a + * tempting argument, in part because it's simpler than the SUSv3 + * recommendation, but I'm not entirely convinced. I'm also + * uncomfortable with the fact that "/tmp" is a Unix-ism. + * + * The following routine leverages build_ustar_entry_name() above and + * so is simpler than you might think. It just needs to provide the + * additional path element and handle a few pathological cases). + */ +static char * +build_pax_attribute_name(char *dest, const char *src) +{ + char buff[64]; + const char *p; + + /* Handle the null filename case. */ + if (src == NULL || *src == '\0') { + strcpy(dest, "PaxHeader/blank"); + return (dest); + } + + /* Prune final '/' and other unwanted final elements. */ + p = src + strlen(src); + for (;;) { + /* Ends in "/", remove the '/' */ + if (p > src && p[-1] == '/') { + --p; + continue; + } + /* Ends in "/.", remove the '.' */ + if (p > src + 1 && p[-1] == '.' + && p[-2] == '/') { + --p; + continue; + } + break; + } + + /* Pathological case: After above, there was nothing left. + * This includes "/." "/./." "/.//./." etc. */ + if (p == src) { + strcpy(dest, "/PaxHeader/rootdir"); + return (dest); + } + + /* Convert unadorned "." into a suitable filename. */ + if (*src == '.' && p == src + 1) { + strcpy(dest, "PaxHeader/currentdir"); + return (dest); + } + + /* + * TODO: Push this string into the 'pax' structure to avoid + * recomputing it every time. That will also open the door + * to having clients override it. + */ +#if HAVE_GETPID && 0 /* Disable this for now; see above comment. */ + sprintf(buff, "PaxHeader.%d", getpid()); +#else + /* If the platform can't fetch the pid, don't include it. */ + strcpy(buff, "PaxHeader"); +#endif + /* General case: build a ustar-compatible name adding "/PaxHeader/". */ + build_ustar_entry_name(dest, src, p - src, buff); + + return (dest); +} + +/* Write two null blocks for the end of archive */ +static int +archive_write_pax_finish(struct archive_write *a) +{ + struct pax *pax; + int r; + + if (a->compressor.write == NULL) + return (ARCHIVE_OK); + + pax = (struct pax *)a->format_data; + r = write_nulls(a, 512 * 2); + return (r); +} + +static int +archive_write_pax_destroy(struct archive_write *a) +{ + struct pax *pax; + + pax = (struct pax *)a->format_data; + archive_string_free(&pax->pax_header); + free(pax); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_pax_finish_entry(struct archive_write *a) +{ + struct pax *pax; + int ret; + + pax = (struct pax *)a->format_data; + ret = write_nulls(a, pax->entry_bytes_remaining + pax->entry_padding); + pax->entry_bytes_remaining = pax->entry_padding = 0; + return (ret); +} + +static int +write_nulls(struct archive_write *a, size_t padding) +{ + int ret, to_write; + + while (padding > 0) { + to_write = padding < a->null_length ? padding : a->null_length; + ret = (a->compressor.write)(a, a->nulls, to_write); + if (ret != ARCHIVE_OK) + return (ret); + padding -= to_write; + } + return (ARCHIVE_OK); +} + +static ssize_t +archive_write_pax_data(struct archive_write *a, const void *buff, size_t s) +{ + struct pax *pax; + int ret; + + pax = (struct pax *)a->format_data; + if (s > pax->entry_bytes_remaining) + s = pax->entry_bytes_remaining; + + ret = (a->compressor.write)(a, buff, s); + pax->entry_bytes_remaining -= s; + if (ret == ARCHIVE_OK) + return (s); + else + return (ret); +} + +static int +has_non_ASCII(const wchar_t *wp) +{ + while (*wp != L'\0' && *wp < 128) + wp++; + return (*wp != L'\0'); +} + +/* + * Used by extended attribute support; encodes the name + * so that there will be no '=' characters in the result. + */ +static char * +url_encode(const char *in) +{ + const char *s; + char *d; + int out_len = 0; + char *out; + + for (s = in; *s != '\0'; s++) { + if (*s < 33 || *s > 126 || *s == '%' || *s == '=') + out_len += 3; + else + out_len++; + } + + out = (char *)malloc(out_len + 1); + if (out == NULL) + return (NULL); + + for (s = in, d = out; *s != '\0'; s++) { + /* encode any non-printable ASCII character or '%' or '=' */ + if (*s < 33 || *s > 126 || *s == '%' || *s == '=') { + /* URL encoding is '%' followed by two hex digits */ + *d++ = '%'; + *d++ = "0123456789ABCDEF"[0x0f & (*s >> 4)]; + *d++ = "0123456789ABCDEF"[0x0f & *s]; + } else { + *d++ = *s; + } + } + *d = '\0'; + return (out); +} + +/* + * Encode a sequence of bytes into a C string using base-64 encoding. + * + * Returns a null-terminated C string allocated with malloc(); caller + * is responsible for freeing the result. + */ +static char * +base64_encode(const char *s, size_t len) +{ + static const char digits[64] = + { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O', + 'P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d', + 'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s', + 't','u','v','w','x','y','z','0','1','2','3','4','5','6','7', + '8','9','+','/' }; + int v; + char *d, *out; + + /* 3 bytes becomes 4 chars, but round up and allow for trailing NUL */ + out = (char *)malloc((len * 4 + 2) / 3 + 1); + if (out == NULL) + return (NULL); + d = out; + + /* Convert each group of 3 bytes into 4 characters. */ + while (len >= 3) { + v = (((int)s[0] << 16) & 0xff0000) + | (((int)s[1] << 8) & 0xff00) + | (((int)s[2]) & 0x00ff); + s += 3; + len -= 3; + *d++ = digits[(v >> 18) & 0x3f]; + *d++ = digits[(v >> 12) & 0x3f]; + *d++ = digits[(v >> 6) & 0x3f]; + *d++ = digits[(v) & 0x3f]; + } + /* Handle final group of 1 byte (2 chars) or 2 bytes (3 chars). */ + switch (len) { + case 0: break; + case 1: + v = (((int)s[0] << 16) & 0xff0000); + *d++ = digits[(v >> 18) & 0x3f]; + *d++ = digits[(v >> 12) & 0x3f]; + break; + case 2: + v = (((int)s[0] << 16) & 0xff0000) + | (((int)s[1] << 8) & 0xff00); + *d++ = digits[(v >> 18) & 0x3f]; + *d++ = digits[(v >> 12) & 0x3f]; + *d++ = digits[(v >> 6) & 0x3f]; + break; + } + /* Add trailing NUL character so output is a valid C string. */ + *d++ = '\0'; + return (out); +} diff --git a/libarchive/archive_write_set_format_shar.c b/libarchive/archive_write_set_format_shar.c new file mode 100644 index 00000000..b5d16e09 --- /dev/null +++ b/libarchive/archive_write_set_format_shar.c @@ -0,0 +1,560 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_shar.c,v 1.19 2008/03/15 11:04:45 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdarg.h> +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct shar { + int dump; + int end_of_line; + struct archive_entry *entry; + int has_data; + char *last_dir; + char outbuff[1024]; + size_t outbytes; + size_t outpos; + int uuavail; + char uubuffer[3]; + int wrote_header; + struct archive_string work; +}; + +static int archive_write_shar_finish(struct archive_write *); +static int archive_write_shar_destroy(struct archive_write *); +static int archive_write_shar_header(struct archive_write *, + struct archive_entry *); +static ssize_t archive_write_shar_data_sed(struct archive_write *, + const void * buff, size_t); +static ssize_t archive_write_shar_data_uuencode(struct archive_write *, + const void * buff, size_t); +static int archive_write_shar_finish_entry(struct archive_write *); +static int shar_printf(struct archive_write *, const char *fmt, ...); +static void uuencode_group(struct shar *); + +static int +shar_printf(struct archive_write *a, const char *fmt, ...) +{ + struct shar *shar; + va_list ap; + int ret; + + shar = (struct shar *)a->format_data; + va_start(ap, fmt); + archive_string_empty(&(shar->work)); + archive_string_vsprintf(&(shar->work), fmt, ap); + ret = ((a->compressor.write)(a, shar->work.s, strlen(shar->work.s))); + va_end(ap); + return (ret); +} + +/* + * Set output format to 'shar' format. + */ +int +archive_write_set_format_shar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct shar *shar; + + /* If someone else was already registered, unregister them. */ + if (a->format_destroy != NULL) + (a->format_destroy)(a); + + shar = (struct shar *)malloc(sizeof(*shar)); + if (shar == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate shar data"); + return (ARCHIVE_FATAL); + } + memset(shar, 0, sizeof(*shar)); + a->format_data = shar; + + a->pad_uncompressed = 0; + a->format_write_header = archive_write_shar_header; + a->format_finish = archive_write_shar_finish; + a->format_destroy = archive_write_shar_destroy; + a->format_write_data = archive_write_shar_data_sed; + a->format_finish_entry = archive_write_shar_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_SHAR_BASE; + a->archive.archive_format_name = "shar"; + return (ARCHIVE_OK); +} + +/* + * An alternate 'shar' that uses uudecode instead of 'sed' to encode + * file contents and can therefore be used to archive binary files. + * In addition, this variant also attempts to restore ownership, file modes, + * and other extended file information. + */ +int +archive_write_set_format_shar_dump(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct shar *shar; + + archive_write_set_format_shar(&a->archive); + shar = (struct shar *)a->format_data; + shar->dump = 1; + a->format_write_data = archive_write_shar_data_uuencode; + a->archive.archive_format = ARCHIVE_FORMAT_SHAR_DUMP; + a->archive.archive_format_name = "shar dump"; + return (ARCHIVE_OK); +} + +static int +archive_write_shar_header(struct archive_write *a, struct archive_entry *entry) +{ + const char *linkname; + const char *name; + char *p, *pp; + struct shar *shar; + int ret; + + shar = (struct shar *)a->format_data; + if (!shar->wrote_header) { + ret = shar_printf(a, "#!/bin/sh\n"); + if (ret != ARCHIVE_OK) + return (ret); + ret = shar_printf(a, "# This is a shell archive\n"); + if (ret != ARCHIVE_OK) + return (ret); + shar->wrote_header = 1; + } + + /* Save the entry for the closing. */ + if (shar->entry) + archive_entry_free(shar->entry); + shar->entry = archive_entry_clone(entry); + name = archive_entry_pathname(entry); + + /* Handle some preparatory issues. */ + switch(archive_entry_filetype(entry)) { + case AE_IFREG: + /* Only regular files have non-zero size. */ + break; + case AE_IFDIR: + archive_entry_set_size(entry, 0); + /* Don't bother trying to recreate '.' */ + if (strcmp(name, ".") == 0 || strcmp(name, "./") == 0) + return (ARCHIVE_OK); + break; + case AE_IFIFO: + case AE_IFCHR: + case AE_IFBLK: + /* All other file types have zero size in the archive. */ + archive_entry_set_size(entry, 0); + break; + default: + archive_entry_set_size(entry, 0); + if (archive_entry_hardlink(entry) == NULL && + archive_entry_symlink(entry) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "shar format cannot archive this"); + return (ARCHIVE_WARN); + } + } + + /* Stock preparation for all file types. */ + ret = shar_printf(a, "echo x %s\n", name); + if (ret != ARCHIVE_OK) + return (ret); + + if (archive_entry_filetype(entry) != AE_IFDIR) { + /* Try to create the dir. */ + p = strdup(name); + pp = strrchr(p, '/'); + /* If there is a / character, try to create the dir. */ + if (pp != NULL) { + *pp = '\0'; + + /* Try to avoid a lot of redundant mkdir commands. */ + if (strcmp(p, ".") == 0) { + /* Don't try to "mkdir ." */ + free(p); + } else if (shar->last_dir == NULL) { + ret = shar_printf(a, + "mkdir -p %s > /dev/null 2>&1\n", p); + if (ret != ARCHIVE_OK) { + free(p); + return (ret); + } + shar->last_dir = p; + } else if (strcmp(p, shar->last_dir) == 0) { + /* We've already created this exact dir. */ + free(p); + } else if (strlen(p) < strlen(shar->last_dir) && + strncmp(p, shar->last_dir, strlen(p)) == 0) { + /* We've already created a subdir. */ + free(p); + } else { + ret = shar_printf(a, + "mkdir -p %s > /dev/null 2>&1\n", p); + if (ret != ARCHIVE_OK) { + free(p); + return (ret); + } + free(shar->last_dir); + shar->last_dir = p; + } + } else { + free(p); + } + } + + /* Handle file-type specific issues. */ + shar->has_data = 0; + if ((linkname = archive_entry_hardlink(entry)) != NULL) { + ret = shar_printf(a, "ln -f %s %s\n", linkname, name); + if (ret != ARCHIVE_OK) + return (ret); + } else if ((linkname = archive_entry_symlink(entry)) != NULL) { + ret = shar_printf(a, "ln -fs %s %s\n", linkname, name); + if (ret != ARCHIVE_OK) + return (ret); + } else { + switch(archive_entry_filetype(entry)) { + case AE_IFREG: + if (archive_entry_size(entry) == 0) { + /* More portable than "touch." */ + ret = shar_printf(a, "test -e \"%s\" || :> \"%s\"\n", name, name); + if (ret != ARCHIVE_OK) + return (ret); + } else { + if (shar->dump) { + ret = shar_printf(a, + "uudecode -o %s << 'SHAR_END'\n", + name); + if (ret != ARCHIVE_OK) + return (ret); + ret = shar_printf(a, "begin %o %s\n", + archive_entry_mode(entry) & 0777, + name); + if (ret != ARCHIVE_OK) + return (ret); + } else { + ret = shar_printf(a, + "sed 's/^X//' > %s << 'SHAR_END'\n", + name); + if (ret != ARCHIVE_OK) + return (ret); + } + shar->has_data = 1; + shar->end_of_line = 1; + shar->outpos = 0; + shar->outbytes = 0; + } + break; + case AE_IFDIR: + ret = shar_printf(a, "mkdir -p %s > /dev/null 2>&1\n", + name); + if (ret != ARCHIVE_OK) + return (ret); + /* Record that we just created this directory. */ + if (shar->last_dir != NULL) + free(shar->last_dir); + + shar->last_dir = strdup(name); + /* Trim a trailing '/'. */ + pp = strrchr(shar->last_dir, '/'); + if (pp != NULL && pp[1] == '\0') + *pp = '\0'; + /* + * TODO: Put dir name/mode on a list to be fixed + * up at end of archive. + */ + break; + case AE_IFIFO: + ret = shar_printf(a, "mkfifo %s\n", name); + if (ret != ARCHIVE_OK) + return (ret); + break; + case AE_IFCHR: + ret = shar_printf(a, "mknod %s c %d %d\n", name, + archive_entry_rdevmajor(entry), + archive_entry_rdevminor(entry)); + if (ret != ARCHIVE_OK) + return (ret); + break; + case AE_IFBLK: + ret = shar_printf(a, "mknod %s b %d %d\n", name, + archive_entry_rdevmajor(entry), + archive_entry_rdevminor(entry)); + if (ret != ARCHIVE_OK) + return (ret); + break; + default: + return (ARCHIVE_WARN); + } + } + + return (ARCHIVE_OK); +} + +/* XXX TODO: This could be more efficient XXX */ +static ssize_t +archive_write_shar_data_sed(struct archive_write *a, const void *buff, size_t n) +{ + struct shar *shar; + const char *src; + int ret; + size_t written = n; + + shar = (struct shar *)a->format_data; + if (!shar->has_data) + return (0); + + src = (const char *)buff; + ret = ARCHIVE_OK; + shar->outpos = 0; + while (n-- > 0) { + if (shar->end_of_line) { + shar->outbuff[shar->outpos++] = 'X'; + shar->end_of_line = 0; + } + if (*src == '\n') + shar->end_of_line = 1; + shar->outbuff[shar->outpos++] = *src++; + + if (shar->outpos > sizeof(shar->outbuff) - 2) { + ret = (a->compressor.write)(a, shar->outbuff, + shar->outpos); + if (ret != ARCHIVE_OK) + return (ret); + shar->outpos = 0; + } + } + + if (shar->outpos > 0) + ret = (a->compressor.write)(a, shar->outbuff, shar->outpos); + if (ret != ARCHIVE_OK) + return (ret); + return (written); +} + +#define UUENC(c) (((c)!=0) ? ((c) & 077) + ' ': '`') + +/* XXX This could be a lot more efficient. XXX */ +static void +uuencode_group(struct shar *shar) +{ + int t; + + t = 0; + if (shar->uuavail > 0) + t = 0xff0000 & (shar->uubuffer[0] << 16); + if (shar->uuavail > 1) + t |= 0x00ff00 & (shar->uubuffer[1] << 8); + if (shar->uuavail > 2) + t |= 0x0000ff & (shar->uubuffer[2]); + shar->outbuff[shar->outpos++] = UUENC( 0x3f & (t>>18) ); + shar->outbuff[shar->outpos++] = UUENC( 0x3f & (t>>12) ); + shar->outbuff[shar->outpos++] = UUENC( 0x3f & (t>>6) ); + shar->outbuff[shar->outpos++] = UUENC( 0x3f & (t) ); + shar->uuavail = 0; + shar->outbytes += shar->uuavail; + shar->outbuff[shar->outpos] = 0; +} + +static ssize_t +archive_write_shar_data_uuencode(struct archive_write *a, const void *buff, + size_t length) +{ + struct shar *shar; + const char *src; + size_t n; + int ret; + + shar = (struct shar *)a->format_data; + if (!shar->has_data) + return (ARCHIVE_OK); + src = (const char *)buff; + n = length; + while (n-- > 0) { + if (shar->uuavail == 3) + uuencode_group(shar); + if (shar->outpos >= 60) { + ret = shar_printf(a, "%c%s\n", UUENC(shar->outbytes), + shar->outbuff); + if (ret != ARCHIVE_OK) + return (ret); + shar->outpos = 0; + shar->outbytes = 0; + } + + shar->uubuffer[shar->uuavail++] = *src++; + shar->outbytes++; + } + return (length); +} + +static int +archive_write_shar_finish_entry(struct archive_write *a) +{ + const char *g, *p, *u; + struct shar *shar; + int ret; + + shar = (struct shar *)a->format_data; + if (shar->entry == NULL) + return (0); + + if (shar->dump) { + /* Finish uuencoded data. */ + if (shar->has_data) { + if (shar->uuavail > 0) + uuencode_group(shar); + if (shar->outpos > 0) { + ret = shar_printf(a, "%c%s\n", + UUENC(shar->outbytes), shar->outbuff); + if (ret != ARCHIVE_OK) + return (ret); + shar->outpos = 0; + shar->uuavail = 0; + shar->outbytes = 0; + } + ret = shar_printf(a, "%c\n", UUENC(0)); + if (ret != ARCHIVE_OK) + return (ret); + ret = shar_printf(a, "end\n", UUENC(0)); + if (ret != ARCHIVE_OK) + return (ret); + ret = shar_printf(a, "SHAR_END\n"); + if (ret != ARCHIVE_OK) + return (ret); + } + /* Restore file mode, owner, flags. */ + /* + * TODO: Don't immediately restore mode for + * directories; defer that to end of script. + */ + ret = shar_printf(a, "chmod %o %s\n", + archive_entry_mode(shar->entry) & 07777, + archive_entry_pathname(shar->entry)); + if (ret != ARCHIVE_OK) + return (ret); + + u = archive_entry_uname(shar->entry); + g = archive_entry_gname(shar->entry); + if (u != NULL || g != NULL) { + ret = shar_printf(a, "chown %s%s%s %s\n", + (u != NULL) ? u : "", + (g != NULL) ? ":" : "", (g != NULL) ? g : "", + archive_entry_pathname(shar->entry)); + if (ret != ARCHIVE_OK) + return (ret); + } + + if ((p = archive_entry_fflags_text(shar->entry)) != NULL) { + ret = shar_printf(a, "chflags %s %s\n", p, + archive_entry_pathname(shar->entry)); + if (ret != ARCHIVE_OK) + return (ret); + } + + /* TODO: restore ACLs */ + + } else { + if (shar->has_data) { + /* Finish sed-encoded data: ensure last line ends. */ + if (!shar->end_of_line) { + ret = shar_printf(a, "\n"); + if (ret != ARCHIVE_OK) + return (ret); + } + ret = shar_printf(a, "SHAR_END\n"); + if (ret != ARCHIVE_OK) + return (ret); + } + } + + archive_entry_free(shar->entry); + shar->entry = NULL; + return (0); +} + +static int +archive_write_shar_finish(struct archive_write *a) +{ + struct shar *shar; + int ret; + + /* + * TODO: Accumulate list of directory names/modes and + * fix them all up at end-of-archive. + */ + + shar = (struct shar *)a->format_data; + + /* + * Only write the end-of-archive markers if the archive was + * actually started. This avoids problems if someone sets + * shar format, then sets another format (which would invoke + * shar_finish to free the format-specific data). + */ + if (shar->wrote_header) { + ret = shar_printf(a, "exit\n"); + if (ret != ARCHIVE_OK) + return (ret); + /* Shar output is never padded. */ + archive_write_set_bytes_in_last_block(&a->archive, 1); + /* + * TODO: shar should also suppress padding of + * uncompressed data within gzip/bzip2 streams. + */ + } + return (ARCHIVE_OK); +} + +static int +archive_write_shar_destroy(struct archive_write *a) +{ + struct shar *shar; + + shar = (struct shar *)a->format_data; + if (shar->entry != NULL) + archive_entry_free(shar->entry); + if (shar->last_dir != NULL) + free(shar->last_dir); + archive_string_free(&(shar->work)); + free(shar); + a->format_data = NULL; + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_set_format_ustar.c b/libarchive/archive_write_set_format_ustar.c new file mode 100644 index 00000000..c2c0011a --- /dev/null +++ b/libarchive/archive_write_set_format_ustar.c @@ -0,0 +1,572 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_ustar.c,v 1.26 2008/03/15 11:04:45 kientzle Exp $"); + + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct ustar { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; +}; + +/* + * Define structure of POSIX 'ustar' tar header. + */ +#define USTAR_name_offset 0 +#define USTAR_name_size 100 +#define USTAR_mode_offset 100 +#define USTAR_mode_size 6 +#define USTAR_mode_max_size 8 +#define USTAR_uid_offset 108 +#define USTAR_uid_size 6 +#define USTAR_uid_max_size 8 +#define USTAR_gid_offset 116 +#define USTAR_gid_size 6 +#define USTAR_gid_max_size 8 +#define USTAR_size_offset 124 +#define USTAR_size_size 11 +#define USTAR_size_max_size 12 +#define USTAR_mtime_offset 136 +#define USTAR_mtime_size 11 +#define USTAR_mtime_max_size 11 +#define USTAR_checksum_offset 148 +#define USTAR_checksum_size 8 +#define USTAR_typeflag_offset 156 +#define USTAR_typeflag_size 1 +#define USTAR_linkname_offset 157 +#define USTAR_linkname_size 100 +#define USTAR_magic_offset 257 +#define USTAR_magic_size 6 +#define USTAR_version_offset 263 +#define USTAR_version_size 2 +#define USTAR_uname_offset 265 +#define USTAR_uname_size 32 +#define USTAR_gname_offset 297 +#define USTAR_gname_size 32 +#define USTAR_rdevmajor_offset 329 +#define USTAR_rdevmajor_size 6 +#define USTAR_rdevmajor_max_size 8 +#define USTAR_rdevminor_offset 337 +#define USTAR_rdevminor_size 6 +#define USTAR_rdevminor_max_size 8 +#define USTAR_prefix_offset 345 +#define USTAR_prefix_size 155 +#define USTAR_padding_offset 500 +#define USTAR_padding_size 12 + +/* + * A filled-in copy of the header for initialization. + */ +static const char template_header[] = { + /* name: 100 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0, + /* Mode, space-null termination: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* uid, space-null termination: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* gid, space-null termination: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* size, space termation: 12 bytes */ + '0','0','0','0','0','0','0','0','0','0','0', ' ', + /* mtime, space termation: 12 bytes */ + '0','0','0','0','0','0','0','0','0','0','0', ' ', + /* Initial checksum value: 8 spaces */ + ' ',' ',' ',' ',' ',' ',' ',' ', + /* Typeflag: 1 byte */ + '0', /* '0' = regular file */ + /* Linkname: 100 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0, + /* Magic: 6 bytes, Version: 2 bytes */ + 'u','s','t','a','r','\0', '0','0', + /* Uname: 32 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* Gname: 32 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* rdevmajor + space/null padding: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* rdevminor + space/null padding: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* Prefix: 155 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0, + /* Padding: 12 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0 +}; + +static ssize_t archive_write_ustar_data(struct archive_write *a, const void *buff, + size_t s); +static int archive_write_ustar_destroy(struct archive_write *); +static int archive_write_ustar_finish(struct archive_write *); +static int archive_write_ustar_finish_entry(struct archive_write *); +static int archive_write_ustar_header(struct archive_write *, + 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_write *a, size_t); + +/* + * Set output format to 'ustar' format. + */ +int +archive_write_set_format_ustar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct ustar *ustar; + + /* If someone else was already registered, unregister them. */ + if (a->format_destroy != NULL) + (a->format_destroy)(a); + + /* Basic internal sanity test. */ + if (sizeof(template_header) != 512) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Internal: template_header wrong size: %d should be 512", sizeof(template_header)); + return (ARCHIVE_FATAL); + } + + ustar = (struct ustar *)malloc(sizeof(*ustar)); + if (ustar == NULL) { + archive_set_error(&a->archive, 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_destroy = archive_write_ustar_destroy; + a->format_finish_entry = archive_write_ustar_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR; + a->archive.archive_format_name = "POSIX ustar"; + return (ARCHIVE_OK); +} + +static int +archive_write_ustar_header(struct archive_write *a, struct archive_entry *entry) +{ + char buff[512]; + int ret; + struct ustar *ustar; + + ustar = (struct ustar *)a->format_data; + + /* Only regular files (not hardlinks) have data. */ + if (archive_entry_hardlink(entry) != NULL || + archive_entry_symlink(entry) != NULL || + !(archive_entry_filetype(entry) == AE_IFREG)) + archive_entry_set_size(entry, 0); + + if (AE_IFDIR == archive_entry_mode(entry)) { + const char *p; + char *t; + /* + * Ensure a trailing '/'. Modify the entry so + * the client sees the change. + */ + p = archive_entry_pathname(entry); + if (p[strlen(p) - 1] != '/') { + t = (char *)malloc(strlen(p) + 2); + if (t == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate ustar data"); + return(ARCHIVE_FATAL); + } + strcpy(t, p); + strcat(t, "/"); + archive_entry_copy_pathname(entry, t); + free(t); + } + } + + ret = __archive_write_format_header_ustar(a, buff, entry, -1, 1); + if (ret != ARCHIVE_OK) + return (ret); + ret = (a->compressor.write)(a, buff, 512); + if (ret != ARCHIVE_OK) + return (ret); + + ustar->entry_bytes_remaining = archive_entry_size(entry); + ustar->entry_padding = 0x1ff & (-(int64_t)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_write *a, char h[512], + struct archive_entry *entry, int tartype, int strict) +{ + unsigned int checksum; + int i, ret; + size_t copy_length; + const char *p, *pp; + int mytartype; + + ret = 0; + mytartype = -1; + /* + * The "template header" already includes the "ustar" + * signature, various end-of-field markers and other required + * elements. + */ + memcpy(h, &template_header, 512); + + /* + * 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) <= USTAR_name_size) + memcpy(h + USTAR_name_offset, pp, strlen(pp)); + else { + /* Store in two pieces, splitting at a '/'. */ + p = strchr(pp + strlen(pp) - USTAR_name_size - 1, '/'); + /* + * If the separator we found is the first '/', find + * the next one. (This is a pathological case that + * occurs for paths of exactly 101 bytes that start with + * '/'; it occurs because the separating '/' is not + * stored explicitly and the reconstruction assumes that + * an empty prefix means there is no '/' separator.) + */ + if (p == pp) + p = strchr(p + 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->archive, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_WARN; + } else if (p > pp + USTAR_prefix_size) { + archive_set_error(&a->archive, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_WARN; + } else { + /* Copy prefix and remainder to appropriate places */ + memcpy(h + USTAR_prefix_offset, pp, p - pp); + memcpy(h + USTAR_name_offset, 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 > USTAR_linkname_size) { + archive_set_error(&a->archive, ENAMETOOLONG, + "Link contents too long"); + ret = ARCHIVE_WARN; + copy_length = USTAR_linkname_size; + } + memcpy(h + USTAR_linkname_offset, p, copy_length); + } + + p = archive_entry_uname(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (copy_length > USTAR_uname_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Username too long"); + ret = ARCHIVE_WARN; + copy_length = USTAR_uname_size; + } + memcpy(h + USTAR_uname_offset, p, copy_length); + } + + p = archive_entry_gname(entry); + if (p != NULL && p[0] != '\0') { + copy_length = strlen(p); + if (strlen(p) > USTAR_gname_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Group name too long"); + ret = ARCHIVE_WARN; + copy_length = USTAR_gname_size; + } + memcpy(h + USTAR_gname_offset, p, copy_length); + } + + if (format_number(archive_entry_mode(entry) & 07777, h + USTAR_mode_offset, USTAR_mode_size, USTAR_mode_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, "Numeric mode too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(archive_entry_uid(entry), h + USTAR_uid_offset, USTAR_uid_size, USTAR_uid_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, "Numeric user ID too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(archive_entry_gid(entry), h + USTAR_gid_offset, USTAR_gid_size, USTAR_gid_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, "Numeric group ID too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(archive_entry_size(entry), h + USTAR_size_offset, USTAR_size_size, USTAR_size_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, "File size out of range"); + ret = ARCHIVE_WARN; + } + + if (format_number(archive_entry_mtime(entry), h + USTAR_mtime_offset, USTAR_mtime_size, USTAR_mtime_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "File modification time too large"); + ret = ARCHIVE_WARN; + } + + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) { + if (format_number(archive_entry_rdevmajor(entry), h + USTAR_rdevmajor_offset, + USTAR_rdevmajor_size, USTAR_rdevmajor_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Major device number too large"); + ret = ARCHIVE_WARN; + } + + if (format_number(archive_entry_rdevminor(entry), h + USTAR_rdevminor_offset, + USTAR_rdevminor_size, USTAR_rdevminor_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Minor device number too large"); + ret = ARCHIVE_WARN; + } + } + + if (tartype >= 0) { + h[USTAR_typeflag_offset] = tartype; + } else if (mytartype >= 0) { + h[USTAR_typeflag_offset] = mytartype; + } else { + switch (archive_entry_filetype(entry)) { + case AE_IFREG: h[USTAR_typeflag_offset] = '0' ; break; + case AE_IFLNK: h[USTAR_typeflag_offset] = '2' ; break; + case AE_IFCHR: h[USTAR_typeflag_offset] = '3' ; break; + case AE_IFBLK: h[USTAR_typeflag_offset] = '4' ; break; + case AE_IFDIR: h[USTAR_typeflag_offset] = '5' ; break; + case AE_IFIFO: h[USTAR_typeflag_offset] = '6' ; break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive this (mode=0%lo)", + (unsigned long)archive_entry_mode(entry)); + ret = ARCHIVE_WARN; + } + } + + checksum = 0; + for (i = 0; i < 512; i++) + checksum += 255 & (unsigned int)h[i]; + h[USTAR_checksum_offset + 6] = '\0'; /* Can't be pre-set in the template. */ + /* h[USTAR_checksum_offset + 7] = ' '; */ /* This is pre-set in the template. */ + format_octal(checksum, h + USTAR_checksum_offset, 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 = (char)('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_write *a) +{ + int r; + + if (a->compressor.write == NULL) + return (ARCHIVE_OK); + + r = write_nulls(a, 512*2); + return (r); +} + +static int +archive_write_ustar_destroy(struct archive_write *a) +{ + struct ustar *ustar; + + ustar = (struct ustar *)a->format_data; + free(ustar); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_ustar_finish_entry(struct archive_write *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_write *a, size_t padding) +{ + int ret; + size_t to_write; + + while (padding > 0) { + to_write = padding < a->null_length ? padding : a->null_length; + ret = (a->compressor.write)(a, a->nulls, to_write); + if (ret != ARCHIVE_OK) + return (ret); + padding -= to_write; + } + return (ARCHIVE_OK); +} + +static ssize_t +archive_write_ustar_data(struct archive_write *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->compressor.write)(a, buff, s); + ustar->entry_bytes_remaining -= s; + if (ret != ARCHIVE_OK) + return (ret); + return (s); +} diff --git a/libarchive/config_freebsd.h b/libarchive/config_freebsd.h new file mode 100644 index 00000000..97127a19 --- /dev/null +++ b/libarchive/config_freebsd.h @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/config_freebsd.h,v 1.8 2008/03/15 04:20:50 kientzle Exp $ + */ + +/* FreeBSD 5.0 and later have ACL support. */ +#if __FreeBSD__ > 4 +#define HAVE_ACL_CREATE_ENTRY 1 +#define HAVE_ACL_INIT 1 +#define HAVE_ACL_SET_FD 1 +#define HAVE_ACL_SET_FD_NP 1 +#define HAVE_ACL_SET_FILE 1 +#define HAVE_ACL_USER 1 +#endif + +#define HAVE_BZLIB_H 1 +#define HAVE_CHFLAGS 1 +#define HAVE_CHOWN 1 +#define HAVE_DECL_INT64_MAX 1 +#define HAVE_DECL_INT64_MIN 1 +#define HAVE_DECL_SIZE_MAX 1 +#define HAVE_DECL_STRERROR_R 1 +#define HAVE_DECL_UINT32_MAX 1 +#define HAVE_DECL_UINT64_MAX 1 +#define HAVE_EFTYPE 1 +#define HAVE_EILSEQ 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCHDIR 1 +#define HAVE_FCHFLAGS 1 +#define HAVE_FCHMOD 1 +#define HAVE_FCHOWN 1 +#define HAVE_FCNTL_H 1 +#define HAVE_FSEEKO 1 +#define HAVE_FSTAT 1 +#define HAVE_FUTIMES 1 +#define HAVE_GETEUID 1 +#define HAVE_GETPID 1 +#define HAVE_GRP_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LCHFLAGS 1 +#define HAVE_LCHMOD 1 +#define HAVE_LCHOWN 1 +#define HAVE_LIMITS_H 1 +#define HAVE_LUTIMES 1 +#define HAVE_MALLOC 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMSET 1 +#define HAVE_MKDIR 1 +#define HAVE_MKFIFO 1 +#define HAVE_MKNOD 1 +#define HAVE_POLL 1 +#define HAVE_POLL_H 1 +#define HAVE_PWD_H 1 +#define HAVE_SELECT 1 +#define HAVE_SETENV 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRCHR 1 +#define HAVE_STRDUP 1 +#define HAVE_STRERROR 1 +#define HAVE_STRERROR_R 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1 +#define HAVE_STRUCT_STAT_ST_RDEV 1 +#define HAVE_STRUCT_TM_TM_GMTOFF 1 +#define HAVE_SYS_ACL_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TYPES_H 1 +#undef HAVE_SYS_UTIME_H +#define HAVE_SYS_WAIT_H 1 +#define HAVE_TIMEGM 1 +#define HAVE_TZSET 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UNSETENV 1 +#define HAVE_UTIME 1 +#define HAVE_UTIMES 1 +#define HAVE_UTIME_H 1 +#define HAVE_WCHAR_H 1 +#define HAVE_WCSCPY 1 +#define HAVE_WCSLEN 1 +#define HAVE_WMEMCMP 1 +#define HAVE_WMEMCPY 1 +#define HAVE_ZLIB_H 1 +#define STDC_HEADERS 1 +#define TIME_WITH_SYS_TIME 1 + +/* FreeBSD 4 and earlier lack intmax_t/uintmax_t */ +#if __FreeBSD__ < 5 +#define intmax_t int64_t +#define uintmax_t uint64_t +#endif diff --git a/libarchive/cpio.5 b/libarchive/cpio.5 new file mode 100644 index 00000000..9dbdc6d8 --- /dev/null +++ b/libarchive/cpio.5 @@ -0,0 +1,325 @@ +.\" Copyright (c) 2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/cpio.5,v 1.1 2007/12/30 04:58:22 kientzle Exp $ +.\" +.Dd October 5, 2007 +.Dt CPIO 5 +.Os +.Sh NAME +.Nm cpio +.Nd format of cpio archive files +.Sh DESCRIPTION +The +.Nm +archive format collects any number of files, directories, and other +file system objects (symbolic links, device nodes, etc.) into a single +stream of bytes. +.Ss General Format +Each file system object in a +.Nm +archive comprises a header record with basic numeric metadata +followed by the full pathname of the entry and the file data. +The header record stores a series of integer values that generally +follow the fields in +.Va struct stat . +(See +.Xr stat 2 +for details.) +The variants differ primarily in how they store those integers +(binary, octal, or hexadecimal). +The header is followed by the pathname of the +entry (the length of the pathname is stored in the header) +and any file data. +The end of the archive is indicated by a special record with +the pathname +.Dq TRAILER!!! . +.Ss PWB format +XXX Any documentation of the original PWB/UNIX 1.0 format? XXX +.Ss Old Binary Format +The old binary +.Nm +format stores numbers as 2-byte and 4-byte binary values. +Each entry begins with a header in the following format: +.Bd -literal -offset indent +struct header_old_cpio { + unsigned short c_magic; + unsigned short c_dev; + unsigned short c_ino; + unsigned short c_mode; + unsigned short c_uid; + unsigned short c_gid; + unsigned short c_nlink; + unsigned short c_rdev; + unsigned short c_mtime[2]; + unsigned short c_namesize; + unsigned short c_filesize[2]; +}; +.Ed +.Pp +The +.Va unsigned short +fields here are 16-bit integer values; the +.Va unsigned int +fields are 32-bit integer values. +The fields are as follows +.Bl -tag -width indent +.It Va magic +The integer value octal 070707. +This value can be used to determine whether this archive is +written with little-endian or big-endian integers. +.It Va dev , Va ino +The device and inode numbers from the disk. +These are used by programs that read +.Nm +archives to determine when two entries refer to the same file. +Programs that synthesize +.Nm +archives should be careful to set these to distinct values for each entry. +.It Va mode +The mode specifies both the regular permissions and the file type. +It consists of several bit fields as follows: +.Bl -tag -width "MMMMMMM" -compact +.It 0170000 +This masks the file type bits. +.It 0140000 +File type value for sockets. +.It 0120000 +File type value for symbolic links. +For symbolic links, the link body is stored as file data. +.It 0100000 +File type value for regular files. +.It 0060000 +File type value for block special devices. +.It 0040000 +File type value for directories. +.It 0020000 +File type value for character special devices. +.It 0010000 +File type value for named pipes or FIFOs. +.It 0004000 +SUID bit. +.It 0002000 +SGID bit. +.It 0001000 +Sticky bit. +On some systems, this modifies the behavior of executables and/or directories. +.It 0000777 +The lower 9 bits specify read/write/execute permissions +for world, group, and user following standard POSIX conventions. +.El +.It Va uid , Va gid +The numeric user id and group id of the owner. +.It Va nlink +The number of links to this file. +Directories always have a value of at least two here. +Note that hardlinked files include file data with every copy in the archive. +.It Va rdev +For block special and character special entries, +this field contains the associated device number. +For all other entry types, it should be set to zero by writers +and ignored by readers. +.It Va mtime +Modification time of the file, indicated as the number +of seconds since the start of the epoch, +00:00:00 UTC January 1, 1970. +The four-byte integer is stored with the most-significant 16 bits first +followed by the least-significant 16 bits. +Each of the two 16 bit values are stored in machine-native byte order. +.It Va namesize +The number of bytes in the pathname that follows the header. +This count includes the trailing NULL byte. +.It Va filesize +The size of the file. +Note that this archive format is limited to +four gigabyte file sizes. +See +.Va mtime +above for a description of the storage of four-byte integers. +.El +.Pp +The pathname immediately follows the fixed header. +If the +.Cm namesize +is odd, an additional NULL byte is added after the pathname. +The file data is then appended, padded with NULL +bytes to an even length. +.Pp +Hardlinked files are not given special treatment; +the full file contents are included with each copy of the +file. +.Ss Portable ASCII Format +.St -susv2 +standardized an ASCII variant that is portable across all +platforms. +It is commonly known as the +.Dq old character +format or as the +.Dq odc +format. +It stores the same numeric fields as the old binary format, but +represents them as 6-character or 11-character octal values. +.Bd -literal -offset indent +struct cpio_odc_header { + char c_magic[6]; + char c_dev[6]; + char c_ino[6]; + char c_mode[6]; + char c_uid[6]; + char c_gid[6]; + char c_nlink[6]; + char c_rdev[6]; + char c_mtime[11]; + char c_namesize[6]; + char c_filesize[11]; +}; +.Ed +.Pp +The fields are identical to those in the old binary format. +The name and file body follow the fixed header. +Unlike the old binary format, there is no additional padding +after the pathname or file contents. +If the files being archived are themselves entirely ASCII, then +the resulting archive will be entirely ASCII, except for the +NULL byte that terminates the name field. +.Ss New ASCII Format +The "new" ASCII format uses 8-byte hexadecimal fields for +all numbers and separates device numbers into separate fields +for major and minor numbers. +.Bd -literal -offset indent +struct cpio_newc_header { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +}; +.Ed +.Pp +Except as specified below, the fields here match those specified +for the old binary format above. +.Bl -tag -width indent +.It Va magic +The string +.Dq 070701 . +.It Va check +This field is always set to zero by writers and ignored by readers. +See the next section for more details. +.El +.Pp +The pathname is followed by NULL bytes so that the total size +of the fixed header plus pathname is a multiple of four. +Likewise, the file data is padded to a multiple of four bytes. +Note that this format supports only 4 gigabyte files (unlike the +older ASCII format, which supports 8 gigabyte files). +.Pp +In this format, hardlinked files are handled by setting the +filesize to zero for each entry except the last one that +appears in the archive. +.Ss New CRC Format +The CRC format is identical to the new ASCII format described +in the previous section except that the magic field is set +to +.Dq 070702 +and the +.Va check +field is set to the sum of all bytes in the file data. +This sum is computed treating all bytes as unsigned values +and using unsigned arithmetic. +Only the least-significant 32 bits of the sum are stored. +.Ss HP variants +The +.Nm cpio +implementation distributed with HPUX used XXXX but stored +device numbers differently XXX. +.Ss Other Extensions and Variants +Sun Solaris uses additional file types to store extended file +data, including ACLs and extended attributes, as special +entries in cpio archives. +.Pp +XXX Others? XXX +.Sh BUGS +The +.Dq CRC +format is mis-named, as it uses a simple checksum and +not a cyclic redundancy check. +.Pp +The old binary format is limited to 16 bits for user id, +group id, device, and inode numbers. +It is limited to 4 gigabyte file sizes. +.Pp +The old ASCII format is limited to 18 bits for +the user id, group id, device, and inode numbers. +It is limited to 8 gigabyte file sizes. +.Pp +The new ASCII format is limited to 4 gigabyte file sizes. +.Pp +None of the cpio formats store user or group names, +which are essential when moving files between systems with +dissimilar user or group numbering. +.Pp +Especially when writing older cpio variants, it may be necessary +to map actual device/inode values to synthesized values that +fit the available fields. +With very large filesystems, this may be necessary even for +the newer formats. +.Sh SEE ALSO +.Xr cpio 1 , +.Xr tar 5 +.Sh STANDARDS +The +.Nm cpio +utility is no longer a part of POSIX or the Single Unix Standard. +It last appeared in +.St -susv2 . +It has been supplanted in subsequent standards by +.Xr pax 1 . +The portable ASCII format is currently part of the specification for the +.Xr pax 1 +utility. +.Sh HISTORY +The original cpio utility was written by Dick Haight +while working in AT&T's Unix Support Group. +It appeared in 1977 as part of PWB/UNIX 1.0, the +.Dq Programmer's Work Bench +derived from +.At v6 +that was used internally at AT&T. +Both the old binary and old character formats were in use +by 1980, according to the System III source released +by SCO under their +.Dq Ancient Unix +license. +The character format was adopted as part of +.St -p1003.1-88 . +XXX when did "newc" appear? Who invented it? When did HP come out with their variant? When did Sun introduce ACLs and extended attributes? XXX
\ No newline at end of file diff --git a/libarchive/filter_fork.c b/libarchive/filter_fork.c new file mode 100644 index 00000000..8cad9e2f --- /dev/null +++ b/libarchive/filter_fork.c @@ -0,0 +1,139 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: src/lib/libarchive/filter_fork.c,v 1.2 2007/12/30 04:58:22 kientzle Exp $"); + +#if defined(HAVE_POLL) +# if defined(HAVE_POLL_H) +# include <poll.h> +# elif defined(HAVE_SYS_POLL_H) +# include <sys/poll.h> +# endif +#elif defined(HAVE_SELECT) +# if defined(HAVE_SYS_SELECT_H) +# include <sys/select.h> +# elif defined(HAVE_UNISTD_H) +# include <unistd.h> +# endif +#endif +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include "filter_fork.h" + +pid_t +__archive_create_child(const char *path, int *child_stdin, int *child_stdout) +{ + pid_t child; + int stdin_pipe[2], stdout_pipe[2], tmp; + + if (pipe(stdin_pipe) == -1) + goto state_allocated; + if (stdin_pipe[0] == STDOUT_FILENO) { + if ((tmp = dup(stdin_pipe[0])) == -1) + goto stdin_opened; + close(stdin_pipe[0]); + stdin_pipe[0] = tmp; + } + if (pipe(stdout_pipe) == -1) + goto stdin_opened; + if (stdout_pipe[1] == STDIN_FILENO) { + if ((tmp = dup(stdout_pipe[1])) == -1) + goto stdout_opened; + close(stdout_pipe[1]); + stdout_pipe[1] = tmp; + } + + switch ((child = vfork())) { + case -1: + goto stdout_opened; + case 0: + close(stdin_pipe[1]); + close(stdout_pipe[0]); + if (dup2(stdin_pipe[0], STDIN_FILENO) == -1) + _exit(254); + if (stdin_pipe[0] != STDIN_FILENO) + close(stdin_pipe[0]); + if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1) + _exit(254); + if (stdout_pipe[1] != STDOUT_FILENO) + close(stdout_pipe[1]); + execlp(path, path, (char *)NULL); + _exit(254); + default: + close(stdin_pipe[0]); + close(stdout_pipe[1]); + + *child_stdin = stdin_pipe[1]; + fcntl(*child_stdin, F_SETFL, O_NONBLOCK); + *child_stdout = stdout_pipe[0]; + fcntl(*child_stdout, F_SETFL, O_NONBLOCK); + } + + return child; + +stdout_opened: + close(stdout_pipe[0]); + close(stdout_pipe[1]); +stdin_opened: + close(stdin_pipe[0]); + close(stdin_pipe[1]); +state_allocated: + return -1; +} + +void +__archive_check_child(int in, int out) +{ +#if defined(HAVE_POLL) + struct pollfd fds[2]; + + fds[0].fd = in; + fds[0].events = POLLOUT; + fds[1].fd = out; + fds[1].events = POLLIN; + + poll(fds, 2, -1); /* -1 == INFTIM, wait forever */ +#elif defined(HAVE_SELECT) + fd_set fds_in, fds_out, fds_error; + + FD_ZERO(&fds_in); + FD_SET(out, &fds_in); + FD_ZERO(&fds_out); + FD_SET(in, &fds_out); + FD_ZERO(&fds_error); + FD_SET(in, &fds_error); + FD_SET(out, &fds_error); + select(in < out ? out + 1 : in + 1, &fds_in, &fds_out, &fds_error, NULL); +#else + sleep(1); +#endif +} diff --git a/libarchive/filter_fork.h b/libarchive/filter_fork.h new file mode 100644 index 00000000..685efda0 --- /dev/null +++ b/libarchive/filter_fork.h @@ -0,0 +1,37 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/filter_fork.h,v 1.1 2007/05/29 01:00:20 kientzle Exp $ + */ + +#ifndef FILTER_FORK_H +#define FILTER_FORK_H + +pid_t +__archive_create_child(const char *path, int *child_stdin, int *child_stdout); + +void +__archive_check_child(int in, int out); + +#endif diff --git a/libarchive/libarchive-formats.5 b/libarchive/libarchive-formats.5 new file mode 100644 index 00000000..0346d8f5 --- /dev/null +++ b/libarchive/libarchive-formats.5 @@ -0,0 +1,272 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/libarchive-formats.5,v 1.15 2007/12/30 04:58:22 kientzle Exp $ +.\" +.Dd April 27, 2004 +.Dt libarchive-formats 3 +.Os +.Sh NAME +.Nm libarchive-formats +.Nd archive formats supported by the libarchive library +.Sh DESCRIPTION +The +.Xr libarchive 3 +library reads and writes a variety of streaming archive formats. +Generally speaking, all of these archive formats consist of a series of +.Dq entries . +Each entry stores a single file system object, such as a file, directory, +or symbolic link. +.Pp +The following provides a brief description of each format supported +by libarchive, with some information about recognized extensions or +limitations of the current library support. +Note that just because a format is supported by libarchive does not +imply that a program that uses libarchive will support that format. +Applications that use libarchive specify which formats they wish +to support. +.Ss Tar Formats +The +.Xr libarchive 3 +library can read most tar archives. +However, it only writes POSIX-standard +.Dq ustar +and +.Dq pax interchange +formats. +.Pp +All tar formats store each entry in one or more 512-byte records. +The first record is used for file metadata, including filename, +timestamp, and mode information, and the file data is stored in +subsequent records. +Later variants have extended this by either appropriating undefined +areas of the header record, extending the header to multiple records, +or by storing special entries that modify the interpretation of +subsequent entries. +.Pp +.Bl -tag -width indent +.It Cm gnutar +The +.Xr libarchive 3 +library can read GNU-format tar archives. +It currently supports the most popular GNU extensions, including +modern long filename and linkname support, as well as atime and ctime data. +The libarchive library does not support multi-volume +archives, nor the old GNU long filename format. +It can read GNU sparse file entries, including the new POSIX-based +formats, but cannot write GNU sparse file entries. +.It Cm pax +The +.Xr libarchive 3 +library can read and write POSIX-compliant pax interchange format +archives. +Pax interchange format archives are an extension of the older ustar +format that adds a separate entry with additional attributes stored +as key/value pairs. +The presence of this additional entry is the only difference between +pax interchange format and the older ustar format. +The extended attributes are of unlimited length and are stored +as UTF-8 Unicode strings. +Keywords defined in the standard are in all lowercase; vendors are allowed +to define custom keys by preceding them with the vendor name in all uppercase. +When writing pax archives, libarchive uses many of the SCHILY keys +defined by Joerg Schilling's +.Dq star +archiver. +The libarchive library can read most of the SCHILY keys. +It silently ignores any keywords that it does not understand. +.It Cm restricted pax +The libarchive library can also write pax archives in which it +attempts to suppress the extended attributes entry whenever +possible. +The result will be identical to a ustar archive unless the +extended attributes entry is required to store a long file +name, long linkname, extended ACL, file flags, or if any of the standard +ustar data (user name, group name, UID, GID, etc) cannot be fully +represented in the ustar header. +In all cases, the result can be dearchived by any program that +can read POSIX-compliant pax interchange format archives. +Programs that correctly read ustar format (see below) will also be +able to read this format; any extended attributes will be extracted as +separate files stored in +.Pa PaxHeader +directories. +.It Cm ustar +The libarchive library can both read and write this format. +This format has the following limitations: +.Bl -bullet -compact +.It +Device major and minor numbers are limited to 21 bits. +Nodes with larger numbers will not be added to the archive. +.It +Path names in the archive are limited to 255 bytes. +(Shorter if there is no / character in exactly the right place.) +.It +Symbolic links and hard links are stored in the archive with +the name of the referenced file. +This name is limited to 100 bytes. +.It +Extended attributes, file flags, and other extended +security information cannot be stored. +.It +Archive entries are limited to 2 gigabytes in size. +.El +Note that the pax interchange format has none of these restrictions. +.El +.Pp +The libarchive library can also read a variety of commonly-used extensions to +the basic tar format. +In particular, it supports base-256 values in certain numeric fields. +This essentially removes the limitations on file size, modification time, +and device numbers. +.Pp +The first tar program appeared in Seventh Edition Unix in 1979. +The first official standard for the tar file format was the +.Dq ustar +(Unix Standard Tar) format defined by POSIX in 1988. +POSIX.1-2001 extended the ustar format to create the +.Dq pax interchange +format. +.Ss Cpio Formats +The libarchive library can read a number of common cpio variants and can write +.Dq odc +and +.Dq newc +format archives. +A cpio archive stores each entry as a fixed-size header followed +by a variable-length filename and variable-length data. +Unlike tar, cpio does only minimal padding of the header or file data. +There are a variety of cpio formats, which differ primarily in +how they store the initial header: some store the values as +octal or hexadecimal numbers in ASCII, others as binary values of +varying byte order and length. +.Bl -tag -width indent +.It Cm binary +The libarchive library can read both big-endian and little-endian +variants of the original binary cpio format. +This format used 32-bit binary values for file size and mtime, +and 16-bit binary values for the other fields. +.It Cm odc +The libarchive library can both read and write this +POSIX-standard format. +This format stores the header contents as octal values in ASCII. +It is standard, portable, and immune from byte-order confusion. +File sizes and mtime are limited to 33 bits (8GB file size), +other fields are limited to 18 bits. +.It Cm SVR4 +The libarchive library can read both CRC and non-CRC variants of +this format. +The SVR4 format uses eight-digit hexadecimal values for +all header fields. +This limits file size to 4GB, and also limits the mtime and +other fields to 32 bits. +The SVR4 format can optionally include a CRC of the file +contents, although libarchive does not currently verify this CRC. +.El +.Pp +Cpio first appeared in PWB/UNIX 1.0, which was released within +AT&T in 1977. +PWB/UNIX 1.0 formed the basis of System III Unix, released outside +of AT&T in 1981. +This makes cpio older than tar, although cpio was not included +in Version 7 AT&T Unix. +As a result, the tar command became much better known in universities +and research groups that used Version 7. +The combination of the +.Nm find +and +.Nm cpio +utilities provided very precise control over file selection. +Unfortunately, the format has many limitations that make it unsuitable +for widespread use. +Only the POSIX format permits files over 4GB, and its 18-bit +limit for most other fields makes it unsuitable for modern systems. +In addition, cpio formats only store numeric UID/GID values (not +usernames and group names), which can make it very difficult to correctly +transfer archives across systems with dissimilar user numbering. +.Ss Shar Formats +A +.Dq shell archive +is a shell script that, when executed on a POSIX-compliant +system, will recreate a collection of file system objects. +The libarchive library can write two different kinds of shar archives: +.Bl -tag -width indent +.It Cm shar +The traditional shar format uses a limited set of POSIX +commands, including +.Xr echo 1 , +.Xr mkdir 1 , +and +.Xr sed 1 . +It is suitable for portably archiving small collections of plain text files. +However, it is not generally well-suited for large archives +(many implementations of +.Xr sh 1 +have limits on the size of a script) nor should it be used with non-text files. +.It Cm shardump +This format is similar to shar but encodes files using +.Xr uuencode 1 +so that the result will be a plain text file regardless of the file contents. +It also includes additional shell commands that attempt to reproduce as +many file attributes as possible, including owner, mode, and flags. +The additional commands used to restore file attributes make +shardump archives less portable than plain shar archives. +.El +.Ss ISO9660 format +Libarchive can read and extract from files containing ISO9660-compliant +CDROM images. +It also has partial support for Rockridge extensions. +In many cases, this can remove the need to burn a physical CDROM. +It also avoids security and complexity issues that come with +virtual mounts and loopback devices. +.Ss Zip format +Libarchive can extract from most zip format archives. +It currently only supports uncompressed entries and entries +compressed with the +.Dq deflate +algorithm. +Older zip compression algorithms are not supported. +.Ss Archive (library) file format +The Unix archive format (commonly created by the +.Xr ar 1 +archiver) is a general-purpose format which is +used almost exclusively for object files to be +read by the link editor +.Xr ld 1 . +The ar format has never been standardised. +There are two common variants: +the GNU format derived from SVR4, +and the BSD format, which first appeared in 4.4BSD. +Libarchive provides read and write support for both variants. +.Sh SEE ALSO +.Xr ar 1 , +.Xr cpio 1 , +.Xr mkisofs 1 , +.Xr shar 1 , +.Xr tar 1 , +.Xr zip 1 , +.Xr zlib 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/libarchive.3 b/libarchive/libarchive.3 new file mode 100644 index 00000000..8c19d008 --- /dev/null +++ b/libarchive/libarchive.3 @@ -0,0 +1,331 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/libarchive.3,v 1.11 2007/01/09 08:05:56 kientzle Exp $ +.\" +.Dd August 19, 2006 +.Dt LIBARCHIVE 3 +.Os +.Sh NAME +.Nm libarchive +.Nd functions for reading and writing streaming archives +.Sh LIBRARY +.Lb libarchive +.Sh OVERVIEW +The +.Nm +library provides a flexible interface for reading and writing +streaming archive files such as tar and cpio. +The library is inherently stream-oriented; readers serially iterate through +the archive, writers serially add things to the archive. +In particular, note that there is no built-in support for +random access nor for in-place modification. +.Pp +When reading an archive, the library automatically detects the +format and the compression. +The library currently has read support for: +.Bl -bullet -compact +.It +old-style tar archives, +.It +most variants of the POSIX +.Dq ustar +format, +.It +the POSIX +.Dq pax interchange +format, +.It +GNU-format tar archives, +.It +most common cpio archive formats, +.It +ISO9660 CD images (with or without RockRidge extensions), +.It +Zip archives. +.El +The library automatically detects archives compressed with +.Xr gzip 1 , +.Xr bzip2 1 , +or +.Xr compress 1 +and decompresses them transparently. +.Pp +When writing an archive, you can specify the compression +to be used and the format to use. +The library can write +.Bl -bullet -compact +.It +POSIX-standard +.Dq ustar +archives, +.It +POSIX +.Dq pax interchange format +archives, +.It +POSIX octet-oriented cpio archives, +.It +two different variants of shar archives. +.El +Pax interchange format is an extension of the tar archive format that +eliminates essentially all of the limitations of historic tar formats +in a standard fashion that is supported +by POSIX-compliant +.Xr pax 1 +implementations on many systems as well as several newer implementations of +.Xr tar 1 . +Note that the default write format will suppress the pax extended +attributes for most entries; explicitly requesting pax format will +enable those attributes for all entries. +.Pp +The read and write APIs are accessed through the +.Fn archive_read_XXX +functions and the +.Fn archive_write_XXX +functions, respectively, and either can be used independently +of the other. +.Pp +The rest of this manual page provides an overview of the library +operation. +More detailed information can be found in the individual manual +pages for each API or utility function. +.Sh READING AN ARCHIVE +To read an archive, you must first obtain an initialized +.Tn struct archive +object from +.Fn archive_read_new . +You can then modify this object for the desired operations with the +various +.Fn archive_read_set_XXX +and +.Fn archive_read_support_XXX +functions. +In particular, you will need to invoke appropriate +.Fn archive_read_support_XXX +functions to enable the corresponding compression and format +support. +Note that these latter functions perform two distinct operations: +they cause the corresponding support code to be linked into your +program, and they enable the corresponding auto-detect code. +Unless you have specific constraints, you will generally want +to invoke +.Fn archive_read_support_compression_all +and +.Fn archive_read_support_format_all +to enable auto-detect for all formats and compression types +currently supported by the library. +.Pp +Once you have prepared the +.Tn struct archive +object, you call +.Fn archive_read_open +to actually open the archive and prepare it for reading. +There are several variants of this function; +the most basic expects you to provide pointers to several +functions that can provide blocks of bytes from the archive. +There are convenience forms that allow you to +specify a filename, file descriptor, +.Ft "FILE *" +object, or a block of memory from which to read the archive data. +Note that the core library makes no assumptions about the +size of the blocks read; +callback functions are free to read whatever block size is +most appropriate for the medium. +.Pp +Each archive entry consists of a header followed by a certain +amount of data. +You can obtain the next header with +.Fn archive_read_next_header , +which returns a pointer to an +.Tn struct archive_entry +structure with information about the current archive element. +If the entry is a regular file, then the header will be followed +by the file data. +You can use +.Fn archive_read_data +(which works much like the +.Xr read 2 +system call) +to read this data from the archive. +You may prefer to use the higher-level +.Fn archive_read_data_skip , +which reads and discards the data for this entry, +.Fn archive_read_data_to_buffer , +which reads the data into an in-memory buffer, +.Fn archive_read_data_to_file , +which copies the data to the provided file descriptor, or +.Fn archive_read_extract , +which recreates the specified entry on disk and copies data +from the archive. +In particular, note that +.Fn archive_read_extract +uses the +.Tn struct archive_entry +structure that you provide it, which may differ from the +entry just read from the archive. +In particular, many applications will want to override the +pathname, file permissions, or ownership. +.Pp +Once you have finished reading data from the archive, you +should call +.Fn archive_read_close +to close the archive, then call +.Fn archive_read_finish +to release all resources, including all memory allocated by the library. +.Pp +The +.Xr archive_read 3 +manual page provides more detailed calling information for this API. +.Sh WRITING AN ARCHIVE +You use a similar process to write an archive. +The +.Fn archive_write_new +function creates an archive object useful for writing, +the various +.Fn archive_write_set_XXX +functions are used to set parameters for writing the archive, and +.Fn archive_write_open +completes the setup and opens the archive for writing. +.Pp +Individual archive entries are written in a three-step +process: +You first initialize a +.Tn struct archive_entry +structure with information about the new entry. +At a minimum, you should set the pathname of the +entry and provide a +.Va struct stat +with a valid +.Va st_mode +field, which specifies the type of object and +.Va st_size +field, which specifies the size of the data portion of the object. +The +.Fn archive_write_header +function actually writes the header data to the archive. +You can then use +.Fn archive_write_data +to write the actual data. +.Pp +After all entries have been written, use the +.Fn archive_write_finish +function to release all resources. +.Pp +The +.Xr archive_write 3 +manual page provides more detailed calling information for this API. +.Sh DESCRIPTION +Detailed descriptions of each function are provided by the +corresponding manual pages. +.Pp +All of the functions utilize an opaque +.Tn struct archive +datatype that provides access to the archive contents. +.Pp +The +.Tn struct archive_entry +structure contains a complete description of a single archive +entry. +It uses an opaque interface that is fully documented in +.Xr archive_entry 3 . +.Pp +Users familiar with historic formats should be aware that the newer +variants have eliminated most restrictions on the length of textual fields. +Clients should not assume that filenames, link names, user names, or +group names are limited in length. +In particular, pax interchange format can easily accommodate pathnames +in arbitrary character sets that exceed +.Va PATH_MAX . +.Sh RETURN VALUES +Most functions return zero on success, non-zero on error. +The return value indicates the general severity of the error, ranging +from +.Cm ARCHIVE_WARN , +which indicates a minor problem that should probably be reported +to the user, to +.Cm ARCHIVE_FATAL , +which indicates a serious problem that will prevent any further +operations on this archive. +On error, the +.Fn archive_errno +function can be used to retrieve a numeric error code (see +.Xr errno 2 ) . +The +.Fn archive_error_string +returns a textual error message suitable for display. +.Pp +.Fn archive_read_new +and +.Fn archive_write_new +return pointers to an allocated and initialized +.Tn struct archive +object. +.Pp +.Fn archive_read_data +and +.Fn archive_write_data +return a count of the number of bytes actually read or written. +A value of zero indicates the end of the data for this entry. +A negative value indicates an error, in which case the +.Fn archive_errno +and +.Fn archive_error_string +functions can be used to obtain more information. +.Sh ENVIRONMENT +There are character set conversions within the +.Xr archive_entry 3 +functions that are impacted by the currently-selected locale. +.Sh SEE ALSO +.Xr tar 1 , +.Xr archive_entry 3 , +.Xr archive_read 3 , +.Xr archive_util 3 , +.Xr archive_write 3 , +.Xr tar 5 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +Some archive formats support information that is not supported by +.Tn struct archive_entry . +Such information cannot be fully archived or restored using this library. +This includes, for example, comments, character sets, +or the arbitrary key/value pairs that can appear in +pax interchange format archives. +.Pp +Conversely, of course, not all of the information that can be +stored in an +.Tn struct archive_entry +is supported by all formats. +For example, cpio formats do not support nanosecond timestamps; +old tar formats do not support large device numbers. diff --git a/libarchive/libarchive_internals.3 b/libarchive/libarchive_internals.3 new file mode 100644 index 00000000..9a42b76d --- /dev/null +++ b/libarchive/libarchive_internals.3 @@ -0,0 +1,366 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/libarchive_internals.3,v 1.2 2007/12/30 04:58:22 kientzle Exp $ +.\" +.Dd April 16, 2007 +.Dt LIBARCHIVE 3 +.Os +.Sh NAME +.Nm libarchive_internals +.Nd description of libarchive internal interfaces +.Sh OVERVIEW +The +.Nm libarchive +library provides a flexible interface for reading and writing +streaming archive files such as tar and cpio. +Internally, it follows a modular layered design that should +make it easy to add new archive and compression formats. +.Sh GENERAL ARCHITECTURE +Externally, libarchive exposes most operations through an +opaque, object-style interface. +The +.Xr archive_entry 1 +objects store information about a single filesystem object. +The rest of the library provides facilities to write +.Xr archive_entry 1 +objects to archive files, +read them from archive files, +and write them to disk. +(There are plans to add a facility to read +.Xr archive_entry 1 +objects from disk as well.) +.Pp +The read and write APIs each have four layers: a public API +layer, a format layer that understands the archive file format, +a compression layer, and an I/O layer. +The I/O layer is completely exposed to clients who can replace +it entirely with their own functions. +.Pp +In order to provide as much consistency as possible for clients, +some public functions are virtualized. +Eventually, it should be possible for clients to open +an archive or disk writer, and then use a single set of +code to select and write entries, regardless of the target. +.Sh READ ARCHITECTURE +From the outside, clients use the +.Xr archive_read 3 +API to manipulate an +.Nm archive +object to read entries and bodies from an archive stream. +Internally, the +.Nm archive +object is cast to an +.Nm archive_read +object, which holds all read-specific data. +The API has four layers: +The lowest layer is the I/O layer. +This layer can be overridden by clients, but most clients use +the packaged I/O callbacks provided, for example, by +.Xr archive_read_open_memory 3 , +and +.Xr archive_read_open_fd 3 . +The compression layer calls the I/O layer to +read bytes and decompresses them for the format layer. +The format layer unpacks a stream of uncompressed bytes and +creates +.Nm archive_entry +objects from the incoming data. +The API layer tracks overall state +(for example, it prevents clients from reading data before reading a header) +and invokes the format and compression layer operations +through registered function pointers. +In particular, the API layer drives the format-detection process: +When opening the archive, it reads an initial block of data +and offers it to each registered compression handler. +The one with the highest bid is initialized with the first block. +Similarly, the format handlers are polled to see which handler +is the best for each archive. +(Prior to 2.4.0, the format bidders were invoked for each +entry, but this design hindered error recovery.) +.Ss I/O Layer and Client Callbacks +The read API goes to some lengths to be nice to clients. +As a result, there are few restrictions on the behavior of +the client callbacks. +.Pp +The client read callback is expected to provide a block +of data on each call. +A zero-length return does indicate end of file, but otherwise +blocks may be as small as one byte or as large as the entire file. +In particular, blocks may be of different sizes. +.Pp +The client skip callback returns the number of bytes actually +skipped, which may be much smaller than the skip requested. +The only requirement is that the skip not be larger. +In particular, clients are allowed to return zero for any +skip that they don't want to handle. +The skip callback must never be invoked with a negative value. +.Pp +Keep in mind that not all clients are reading from disk: +clients reading from networks may provide different-sized +blocks on every request and cannot skip at all; +advanced clients may use +.Xr mmap 2 +to read the entire file into memory at once and return the +entire file to libarchive as a single block; +other clients may begin asynchronous I/O operations for the +next block on each request. +.Ss Decompresssion Layer +The decompression layer not only handles decompression, +it also buffers data so that the format handlers see a +much nicer I/O model. +The decompression API is a two stage peek/consume model. +A read_ahead request specifies a minimum read amount; +the decompression layer must provide a pointer to at least +that much data. +If more data is immediately available, it should return more: +the format layer handles bulk data reads by asking for a minimum +of one byte and then copying as much data as is available. +.Pp +A subsequent call to the +.Fn consume +function advances the read pointer. +Note that data returned from a +.Fn read_ahead +call is guaranteed to remain in place until +the next call to +.Fn read_ahead . +Intervening calls to +.Fn consume +should not cause the data to move. +.Pp +Skip requests must always be handled exactly. +Decompression handlers that cannot seek forward should +not register a skip handler; +the API layer fills in a generic skip handler that reads and discards data. +.Pp +A decompression handler has a specific lifecycle: +.Bl -tag -compact -width indent +.It Registration/Configuration +When the client invokes the public support function, +the decompression handler invokes the internal +.Fn __archive_read_register_compression +function to provide bid and initialization functions. +This function returns +.Cm NULL +on error or else a pointer to a +.Cm struct decompressor_t . +This structure contains a +.Va void * config +slot that can be used for storing any customization information. +.It Bid +The bid function is invoked with a pointer and size of a block of data. +The decompressor can access its config data +through the +.Va decompressor +element of the +.Cm archive_read +object. +The bid function is otherwise stateless. +In particular, it must not perform any I/O operations. +.Pp +The value returned by the bid function indicates its suitability +for handling this data stream. +A bid of zero will ensure that this decompressor is never invoked. +Return zero if magic number checks fail. +Otherwise, your initial implementation should return the number of bits +actually checked. +For example, if you verify two full bytes and three bits of another +byte, bid 19. +Note that the initial block may be very short; +be careful to only inspect the data you are given. +(The current decompressors require two bytes for correct bidding.) +.It Initialize +The winning bidder will have its init function called. +This function should initialize the remaining slots of the +.Va struct decompressor_t +object pointed to by the +.Va decompressor +element of the +.Va archive_read +object. +In particular, it should allocate any working data it needs +in the +.Va data +slot of that structure. +The init function is called with the block of data that +was used for tasting. +At this point, the decompressor is responsible for all I/O +requests to the client callbacks. +The decompressor is free to read more data as and when +necessary. +.It Satisfy I/O requests +The format handler will invoke the +.Va read_ahead , +.Va consume , +and +.Va skip +functions as needed. +.It Finish +The finish method is called only once when the archive is closed. +It should release anything stored in the +.Va data +and +.Va config +slots of the +.Va decompressor +object. +It should not invoke the client close callback. +.El +.Ss Format Layer +The read formats have a similar lifecycle to the decompression handlers: +.Bl -tag -compact -width indent +.It Registration +Allocate your private data and initialize your pointers. +.It Bid +Formats bid by invoking the +.Fn read_ahead +decompression method but not calling the +.Fn consume +method. +This allows each bidder to look ahead in the input stream. +Bidders should not look further ahead than necessary, as long +look aheads put pressure on the decompression layer to buffer +lots of data. +Most formats only require a few hundred bytes of look ahead; +look aheads of a few kilobytes are reasonable. +(The ISO9660 reader sometimes looks ahead by 48k, which +should be considered an upper limit.) +.It Read header +The header read is usually the most complex part of any format. +There are a few strategies worth mentioning: +For formats such as tar or cpio, reading and parsing the header is +straightforward since headers alternate with data. +For formats that store all header data at the beginning of the file, +the first header read request may have to read all headers into +memory and store that data, sorted by the location of the file +data. +Subsequent header read requests will skip forward to the +beginning of the file data and return the corresponding header. +.It Read Data +The read data interface supports sparse files; this requires that +each call return a block of data specifying the file offset and +size. +This may require you to carefully track the location so that you +can return accurate file offsets for each read. +Remember that the decompressor will return as much data as it has. +Generally, you will want to request one byte, +examine the return value to see how much data is available, and +possibly trim that to the amount you can use. +You should invoke consume for each block just before you return it. +.It Skip All Data +The skip data call should skip over all file data and trailing padding. +This is called automatically by the API layer just before each +header read. +It is also called in response to the client calling the public +.Fn data_skip +function. +.It Cleanup +On cleanup, the format should release all of its allocated memory. +.El +.Ss API Layer +XXX to do XXX +.Sh WRITE ARCHITECTURE +The write API has a similar set of four layers: +an API layer, a format layer, a compression layer, and an I/O layer. +The registration here is much simpler because only +one format and one compression can be registered at a time. +.Ss I/O Layer and Client Callbacks +XXX To be written XXX +.Ss Compression Layer +XXX To be written XXX +.Ss Format Layer +XXX To be written XXX +.Ss API Layer +XXX To be written XXX +.Sh WRITE_DISK ARCHITECTURE +The write_disk API is intended to look just like the write API +to clients. +Since it does not handle multiple formats or compression, it +is not layered internally. +.Sh GENERAL SERVICES +The +.Nm archive_read , +.Nm archive_write , +and +.Nm archive_write_disk +objects all contain an initial +.Nm archive +object which provides common support for a set of standard services. +(Recall that ANSI/ISO C90 guarantees that you can cast freely between +a pointer to a structure and a pointer to the first element of that +structure.) +The +.Nm archive +object has a magic value that indicates which API this object +is associated with, +slots for storing error information, +and function pointers for virtualized API functions. +.Sh MISCELLANEOUS NOTES +Connecting existing archiving libraries into libarchive is generally +quite difficult. +In particular, many existing libraries strongly assume that you +are reading from a file; they seek forwards and backwards as necessary +to locate various pieces of information. +In contrast, libarchive never seeks backwards in its input, which +sometimes requires very different approaches. +.Pp +For example, libarchive's ISO9660 support operates very differently +from most ISO9660 readers. +The libarchive support utilizes a work-queue design that +keeps a list of known entries sorted by their location in the input. +Whenever libarchive's ISO9660 implementation is asked for the next +header, checks this list to find the next item on the disk. +Directories are parsed when they are encountered and new +items are added to the list. +This design relies heavily on the ISO9660 image being optimized so that +directories always occur earlier on the disk than the files they +describe. +.Pp +Depending on the specific format, such approaches may not be possible. +The ZIP format specification, for example, allows archivers to store +key information only at the end of the file. +In theory, it is possible to create ZIP archives that cannot +be read without seeking. +Fortunately, such archives are very rare, and libarchive can read +most ZIP archives, though it cannot always extract as much information +as a dedicated ZIP program. +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry 3 , +.Xr archive_read 3 , +.Xr archive_write 3 , +.Xr archive_write_disk 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS diff --git a/libarchive/mtree.5 b/libarchive/mtree.5 new file mode 100644 index 00000000..fb4c8640 --- /dev/null +++ b/libarchive/mtree.5 @@ -0,0 +1,270 @@ +.\" Copyright (c) 1989, 1990, 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. +.\" +.\" From: @(#)mtree.8 8.2 (Berkeley) 12/11/93 +.\" $FreeBSD: src/usr.sbin/mtree/mtree.5,v 1.1 2008/01/01 06:15:57 kientzle Exp $ +.\" +.Dd December 31, 2007 +.Dt MTREE 5 +.Os +.Sh NAME +.Nm mtree +.Nd format of mtree dir heirarchy files +.Sh DESCRIPTION +The +.Nm +format is a textual format that describes a collection of filesystem objects. +Such files are typically used to create or verify directory heirarchies. +.Ss General Format +An +.Nm +file consists of a series of lines, each providing information +about a single filesystem object. +Leading whitespace is always ignored. +.Pp +When encoding file or pathnames, any backslash character or +character outside of the 95 printable ASCII characters must be +encoded as a a backslash followed by three +octal digits. +When reading mtree files, any appearance of a backslash +followed by three octal digits should be converted into the +corresponding character. +.Pp +Each line is interpreted independently as one of the following types: +.Bl -tag -width Cm +.It Signature +The first line of any mtree file must begin with +.Dq #mtree . +If a file contains any full path entries, the first line should +begin with +.Dq #mtree v2.0 , +otherwise, the first line should begin with +.Dq #mtree v1.0 . +.It Blank +Blank lines are ignored. +.It Comment +Lines beginning with +.Cm # +are ignored. +.It Special +Lines beginning with +.Cm / +are special commands that influence +the interpretation of later lines. +.It Relative +If the first whitespace-delimited word has no +.Cm / +characters, +it is the name of a file in the current directory. +Any relative entry that describes a directory changes the +current directory. +.It dot-dot +As a special case, a relative entry with the filename +.Pa .. +changes the current directory to the parent directory. +Options on dot-dot entries are always ignored. +.It Full +If the first whitespace-delimited word has a +.Cm / +character after +the first character, it is the pathname of a file relative to the +starting directory. +There can be multiple full entries describing the same file. +.El +.Pp +Some tools that process +.Nm +files may require that multiple lines describing the same file +occur consecutively. +It is not permitted for the same file to be mentioned using +both a relative and a full file specification. +.Ss Special commands +Two special commands are currently defined: +.Bl -tag -width Cm +.It Cm /set +This command defines default values for one or more keywords. +It is followed on the same line by one or more whitespace-separated +keyword definitions. +These definitions apply to all following files that do not specify +a value for that keyword. +.It Cm /unset +This command removes any default value set by a previous +.Cm /set +command. +It is followed on the same line by one or more keywords +separated by whitespace. +.El +.Ss Keywords +After the filename, a full or relative entry consists of zero +or more whitespace-separated keyword definitions. +Each such definitions consists of a key from the following +list immediately followed by an '=' sign +and a value. +Software programs reading mtree files should warn about +unrecognized keywords. +.Pp +Currently supported keywords are as follows: +.Bl -tag -width Cm +.It Cm cksum +The checksum of the file using the default algorithm specified by +the +.Xr cksum 1 +utility. +.It Cm contents +The full pathname of a file whose contents should be +compared to the contents of this file. +.It Cm flags +The file flags as a symbolic name. +See +.Xr chflags 1 +for information on these names. +If no flags are to be set the string +.Dq none +may be used to override the current default. +.It Cm ignore +Ignore any file hierarchy below this file. +.It Cm gid +The file group as a numeric value. +.It Cm gname +The file group as a symbolic name. +.It Cm md5 +The MD5 message digest of the file. +.It Cm md5digest +A synonym for +.Cm md5 . +.It Cm sha1 +The +.Tn FIPS +160-1 +.Pq Dq Tn SHA-1 +message digest of the file. +.It Cm sha1digest +A synonym for +.Cm sha1 . +.It Cm sha256 +The +.Tn FIPS +180-2 +.Pq Dq Tn SHA-256 +message digest of the file. +.It Cm sha256digest +A synonym for +.Cm sha256 . +.It Cm ripemd160digest +The +.Tn RIPEMD160 +message digest of the file. +.It Cm rmd160 +A synonym for +.Cm ripemd160digest . +.It Cm rmd160digest +A synonym for +.Cm ripemd160digest . +.It Cm mode +The current file's permissions as a numeric (octal) or symbolic +value. +.It Cm nlink +The number of hard links the file is expected to have. +.It Cm nochange +Make sure this file or directory exists but otherwise ignore all attributes. +.It Cm uid +The file owner as a numeric value. +.It Cm uname +The file owner as a symbolic name. +.It Cm size +The size, in bytes, of the file. +.It Cm link +The file the symbolic link is expected to reference. +.It Cm time +The last modification time of the file. +.It Cm type +The type of the file; may be set to any one of the following: +.Pp +.Bl -tag -width Cm -compact +.It Cm block +block special device +.It Cm char +character special device +.It Cm dir +directory +.It Cm fifo +fifo +.It Cm file +regular file +.It Cm link +symbolic link +.It Cm socket +socket +.El +.El +.Pp +.Sh SEE ALSO +.Xr cksum 1 , +.Xr find 1 , +.Xr mtree 8 +.Sh BUGS +The +.Fx +implementation of mtree does not currently support +the +.Nm +2.0 +format. +The requirement for a +.Dq #mtree +signature line is new and not yet widely implemented. +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.3 Reno . +The +.Tn MD5 +digest capability was added in +.Fx 2.1 , +in response to the widespread use of programs which can spoof +.Xr cksum 1 . +The +.Tn SHA-1 +and +.Tn RIPEMD160 +digests were added in +.Fx 4.0 , +as new attacks have demonstrated weaknesses in +.Tn MD5 . +The +.Tn SHA-256 +digest was added in +.Fx 6.0 . +Support for file flags was added in +.Fx 4.0 , +and mostly comes from +.Nx . +The +.Dq full +entry format was added by +.Nx . diff --git a/libarchive/tar.5 b/libarchive/tar.5 new file mode 100644 index 00000000..ab39df33 --- /dev/null +++ b/libarchive/tar.5 @@ -0,0 +1,730 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/lib/libarchive/tar.5,v 1.17 2007/01/09 08:05:56 kientzle Exp $ +.\" +.Dd May 20, 2004 +.Dt TAR 5 +.Os +.Sh NAME +.Nm tar +.Nd format of tape archive files +.Sh DESCRIPTION +The +.Nm +archive format collects any number of files, directories, and other +file system objects (symbolic links, device nodes, etc.) into a single +stream of bytes. +The format was originally designed to be used with +tape drives that operate with fixed-size blocks, but is widely used as +a general packaging mechanism. +.Ss General Format +A +.Nm +archive consists of a series of 512-byte records. +Each file system object requires a header record which stores basic metadata +(pathname, owner, permissions, etc.) and zero or more records containing any +file data. +The end of the archive is indicated by two records consisting +entirely of zero bytes. +.Pp +For compatibility with tape drives that use fixed block sizes, +programs that read or write tar files always read or write a fixed +number of records with each I/O operation. +These +.Dq blocks +are always a multiple of the record size. +The most common block size\(emand the maximum supported by historic +implementations\(emis 10240 bytes or 20 records. +(Note: the terms +.Dq block +and +.Dq record +here are not entirely standard; this document follows the +convention established by John Gilmore in documenting +.Nm pdtar . ) +.Ss Old-Style Archive Format +The original tar archive format has been extended many times to +include additional information that various implementors found +necessary. +This section describes the variant implemented by the tar command +included in +.At v7 , +which is one of the earliest widely-used versions of the tar program. +.Pp +The header record for an old-style +.Nm +archive consists of the following: +.Bd -literal -offset indent +struct header_old_tar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char linkflag[1]; + char linkname[100]; + char pad[255]; +}; +.Ed +All unused bytes in the header record are filled with nulls. +.Bl -tag -width indent +.It Va name +Pathname, stored as a null-terminated string. +Early tar implementations only stored regular files (including +hardlinks to those files). +One common early convention used a trailing "/" character to indicate +a directory name, allowing directory permissions and owner information +to be archived and restored. +.It Va mode +File mode, stored as an octal number in ASCII. +.It Va uid , Va gid +User id and group id of owner, as octal numbers in ASCII. +.It Va size +Size of file, as octal number in ASCII. +For regular files only, this indicates the amount of data +that follows the header. +In particular, this field was ignored by early tar implementations +when extracting hardlinks. +Modern writers should always store a zero length for hardlink entries. +.It Va mtime +Modification time of file, as an octal number in ASCII. +This indicates the number of seconds since the start of the epoch, +00:00:00 UTC January 1, 1970. +Note that negative values should be avoided +here, as they are handled inconsistently. +.It Va checksum +Header checksum, stored as an octal number in ASCII. +To compute the checksum, set the checksum field to all spaces, +then sum all bytes in the header using unsigned arithmetic. +This field should be stored as six octal digits followed by a null and a space +character. +Note that many early implementations of tar used signed arithmetic +for the checksum field, which can cause interoperability problems +when transferring archives between systems. +Modern robust readers compute the checksum both ways and accept the +header if either computation matches. +.It Va linkflag , Va linkname +In order to preserve hardlinks and conserve tape, a file +with multiple links is only written to the archive the first +time it is encountered. +The next time it is encountered, the +.Va linkflag +is set to an ASCII +.Sq 1 +and the +.Va linkname +field holds the first name under which this file appears. +(Note that regular files have a null value in the +.Va linkflag +field.) +.El +.Pp +Early tar implementations varied in how they terminated these fields. +The tar command in +.At v7 +used the following conventions (this is also documented in early BSD manpages): +the pathname must be null-terminated; +the mode, uid, and gid fields must end in a space and a null byte; +the size and mtime fields must end in a space; +the checksum is terminated by a null and a space. +Early implementations filled the numeric fields with leading spaces. +This seems to have been common practice until the +.St -p1003.1-88 +standard was released. +For best portability, modern implementations should fill the numeric +fields with leading zeros. +.Ss Pre-POSIX Archives +An early draft of +.St -p1003.1-88 +served as the basis for John Gilmore's +.Nm pdtar +program and many system implementations from the late 1980s +and early 1990s. +These archives generally follow the POSIX ustar +format described below with the following variations: +.Bl -bullet -compact -width indent +.It +The magic value is +.Dq ustar\ \& +(note the following space). +The version field contains a space character followed by a null. +.It +The numeric fields are generally filled with leading spaces +(not leading zeros as recommended in the final standard). +.It +The prefix field is often not used, limiting pathnames to +the 100 characters of old-style archives. +.El +.Ss POSIX ustar Archives +.St -p1003.1-88 +defined a standard tar file format to be read and written +by compliant implementations of +.Xr tar 1 . +This format is often called the +.Dq ustar +format, after the magic value used +in the header. +(The name is an acronym for +.Dq Unix Standard TAR . ) +It extends the historic format with new fields: +.Bd -literal -offset indent +struct header_posix_ustar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +}; +.Ed +.Bl -tag -width indent +.It Va typeflag +Type of entry. +POSIX extended the earlier +.Va linkflag +field with several new type values: +.Bl -tag -width indent -compact +.It Dq 0 +Regular file. +NULL should be treated as a synonym, for compatibility purposes. +.It Dq 1 +Hard link. +.It Dq 2 +Symbolic link. +.It Dq 3 +Character device node. +.It Dq 4 +Block device node. +.It Dq 5 +Directory. +.It Dq 6 +FIFO node. +.It Dq 7 +Reserved. +.It Other +A POSIX-compliant implementation must treat any unrecognized typeflag value +as a regular file. +In particular, writers should ensure that all entries +have a valid filename so that they can be restored by readers that do not +support the corresponding extension. +Uppercase letters "A" through "Z" are reserved for custom extensions. +Note that sockets and whiteout entries are not archivable. +.El +It is worth noting that the +.Va size +field, in particular, has different meanings depending on the type. +For regular files, of course, it indicates the amount of data +following the header. +For directories, it may be used to indicate the total size of all +files in the directory, for use by operating systems that pre-allocate +directory space. +For all other types, it should be set to zero by writers and ignored +by readers. +.It Va magic +Contains the magic value +.Dq ustar +followed by a NULL byte to indicate that this is a POSIX standard archive. +Full compliance requires the uname and gname fields be properly set. +.It Va version +Version. +This should be +.Dq 00 +(two copies of the ASCII digit zero) for POSIX standard archives. +.It Va uname , Va gname +User and group names, as null-terminated ASCII strings. +These should be used in preference to the uid/gid values +when they are set and the corresponding names exist on +the system. +.It Va devmajor , Va devminor +Major and minor numbers for character device or block device entry. +.It Va prefix +First part of pathname. +If the pathname is too long to fit in the 100 bytes provided by the standard +format, it can be split at any +.Pa / +character with the first portion going here. +If the prefix field is not empty, the reader will prepend +the prefix value and a +.Pa / +character to the regular name field to obtain the full pathname. +.El +.Pp +Note that all unused bytes must be set to +.Dv NULL . +.Pp +Field termination is specified slightly differently by POSIX +than by previous implementations. +The +.Va magic , +.Va uname , +and +.Va gname +fields must have a trailing +.Dv NULL . +The +.Va pathname , +.Va linkname , +and +.Va prefix +fields must have a trailing +.Dv NULL +unless they fill the entire field. +(In particular, it is possible to store a 256-character pathname if it +happens to have a +.Pa / +as the 156th character.) +POSIX requires numeric fields to be zero-padded in the front, and allows +them to be terminated with either space or +.Dv NULL +characters. +.Pp +Currently, most tar implementations comply with the ustar +format, occasionally extending it by adding new fields to the +blank area at the end of the header record. +.Ss Pax Interchange Format +There are many attributes that cannot be portably stored in a +POSIX ustar archive. +.St -p1003.1-2001 +defined a +.Dq pax interchange format +that uses two new types of entries to hold text-formatted +metadata that applies to following entries. +Note that a pax interchange format archive is a ustar archive in every +respect. +The new data is stored in ustar-compatible archive entries that use the +.Dq x +or +.Dq g +typeflag. +In particular, older implementations that do not fully support these +extensions will extract the metadata into regular files, where the +metadata can be examined as necessary. +.Pp +An entry in a pax interchange format archive consists of one or +two standard ustar entries, each with its own header and data. +The first optional entry stores the extended attributes +for the following entry. +This optional first entry has an "x" typeflag and a size field that +indicates the total size of the extended attributes. +The extended attributes themselves are stored as a series of text-format +lines encoded in the portable UTF-8 encoding. +Each line consists of a decimal number, a space, a key string, an equals +sign, a value string, and a new line. +The decimal number indicates the length of the entire line, including the +initial length field and the trailing newline. +An example of such a field is: +.Dl 25 ctime=1084839148.1212\en +Keys in all lowercase are standard keys. +Vendors can add their own keys by prefixing them with an all uppercase +vendor name and a period. +Note that, unlike the historic header, numeric values are stored using +decimal, not octal. +A description of some common keys follows: +.Bl -tag -width indent +.It Cm atime , Cm ctime , Cm mtime +File access, inode change, and modification times. +These fields can be negative or include a decimal point and a fractional value. +.It Cm uname , Cm uid , Cm gname , Cm gid +User name, group name, and numeric UID and GID values. +The user name and group name stored here are encoded in UTF8 +and can thus include non-ASCII characters. +The UID and GID fields can be of arbitrary length. +.It Cm linkpath +The full path of the linked-to file. +Note that this is encoded in UTF8 and can thus include non-ASCII characters. +.It Cm path +The full pathname of the entry. +Note that this is encoded in UTF8 and can thus include non-ASCII characters. +.It Cm realtime.* , Cm security.* +These keys are reserved and may be used for future standardization. +.It Cm size +The size of the file. +Note that there is no length limit on this field, allowing conforming +archives to store files much larger than the historic 8GB limit. +.It Cm SCHILY.* +Vendor-specific attributes used by Joerg Schilling's +.Nm star +implementation. +.It Cm SCHILY.acl.access , Cm SCHILY.acl.default +Stores the access and default ACLs as textual strings in a format +that is an extension of the format specified by POSIX.1e draft 17. +In particular, each user or group access specification can include a fourth +colon-separated field with the numeric UID or GID. +This allows ACLs to be restored on systems that may not have complete +user or group information available (such as when NIS/YP or LDAP services +are temporarily unavailable). +.It Cm SCHILY.devminor , Cm SCHILY.devmajor +The full minor and major numbers for device nodes. +.It Cm SCHILY.dev, Cm SCHILY.ino , Cm SCHILY.nlinks +The device number, inode number, and link count for the entry. +In particular, note that a pax interchange format archive using Joerg +Schilling's +.Cm SCHILY.* +extensions can store all of the data from +.Va struct stat . +.It Cm LIBARCHIVE.xattr. Ns Ar namespace Ns . Ns Ar key +Libarchive stores POSIX.1e-style extended attributes using +keys of this form. +The +.Ar key +value is URL-encoded: +All non-ASCII characters and the two special characters +.Dq = +and +.Dq % +are encoded as +.Dq % +followed by two uppercase hexadecimal digits. +The value of this key is the extended attribute value +encoded in base 64. +XXX Detail the base-64 format here XXX +.It Cm VENDOR.* +XXX document other vendor-specific extensions XXX +.El +.Pp +Any values stored in an extended attribute override the corresponding +values in the regular tar header. +Note that compliant readers should ignore the regular fields when they +are overridden. +This is important, as existing archivers are known to store non-compliant +values in the standard header fields in this situation. +There are no limits on length for any of these fields. +In particular, numeric fields can be arbitrarily large. +All text fields are encoded in UTF8. +Compliant writers should store only portable 7-bit ASCII characters in +the standard ustar header and use extended +attributes whenever a text value contains non-ASCII characters. +.Pp +In addition to the +.Cm x +entry described above, the pax interchange format +also supports a +.Cm g +entry. +The +.Cm g +entry is identical in format, but specifies attributes that serve as +defaults for all subsequent archive entries. +The +.Cm g +entry is not widely used. +.Pp +Besides the new +.Cm x +and +.Cm g +entries, the pax interchange format has a few other minor variations +from the earlier ustar format. +The most troubling one is that hardlinks are permitted to have +data following them. +This allows readers to restore any hardlink to a file without +having to rewind the archive to find an earlier entry. +However, it creates complications for robust readers, as it is no longer +clear whether or not they should ignore the size field for hardlink entries. +.Ss GNU Tar Archives +The GNU tar program started with a pre-POSIX format similar to that +described earlier and has extended it using several different mechanisms: +It added new fields to the empty space in the header (some of which was later +used by POSIX for conflicting purposes); +it allowed the header to be continued over multiple records; +and it defined new entries that modify following entries +(similar in principle to the +.Cm x +entry described above, but each GNU special entry is single-purpose, +unlike the general-purpose +.Cm x +entry). +As a result, GNU tar archives are not POSIX compatible, although +more lenient POSIX-compliant readers can successfully extract most +GNU tar archives. +.Bd -literal -offset indent +struct header_gnu_tar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char atime[12]; + char ctime[12]; + char offset[12]; + char longnames[4]; + char unused[1]; + struct { + char offset[12]; + char numbytes[12]; + } sparse[4]; + char isextended[1]; + char realsize[12]; + char pad[17]; +}; +.Ed +.Bl -tag -width indent +.It Va typeflag +GNU tar uses the following special entry types, in addition to +those defined by POSIX: +.Bl -tag -width indent +.It "7" +GNU tar treats type "7" records identically to type "0" records, +except on one obscure RTOS where they are used to indicate the +pre-allocation of a contiguous file on disk. +.It "D" +This indicates a directory entry. +Unlike the POSIX-standard "5" +typeflag, the header is followed by data records listing the names +of files in this directory. +Each name is preceded by an ASCII "Y" +if the file is stored in this archive or "N" if the file is not +stored in this archive. +Each name is terminated with a null, and +an extra null marks the end of the name list. +The purpose of this +entry is to support incremental backups; a program restoring from +such an archive may wish to delete files on disk that did not exist +in the directory when the archive was made. +.Pp +Note that the "D" typeflag specifically violates POSIX, which requires +that unrecognized typeflags be restored as normal files. +In this case, restoring the "D" entry as a file could interfere +with subsequent creation of the like-named directory. +.It "K" +The data for this entry is a long linkname for the following regular entry. +.It "L" +The data for this entry is a long pathname for the following regular entry. +.It "M" +This is a continuation of the last file on the previous volume. +GNU multi-volume archives guarantee that each volume begins with a valid +entry header. +To ensure this, a file may be split, with part stored at the end of one volume, +and part stored at the beginning of the next volume. +The "M" typeflag indicates that this entry continues an existing file. +Such entries can only occur as the first or second entry +in an archive (the latter only if the first entry is a volume label). +The +.Va size +field specifies the size of this entry. +The +.Va offset +field at bytes 369-380 specifies the offset where this file fragment +begins. +The +.Va realsize +field specifies the total size of the file (which must equal +.Va size +plus +.Va offset ) . +When extracting, GNU tar checks that the header file name is the one it is +expecting, that the header offset is in the correct sequence, and that +the sum of offset and size is equal to realsize. +FreeBSD's version of GNU tar does not handle the corner case of an +archive's being continued in the middle of a long name or other +extension header. +.It "N" +Type "N" records are no longer generated by GNU tar. +They contained a +list of files to be renamed or symlinked after extraction; this was +originally used to support long names. +The contents of this record +are a text description of the operations to be done, in the form +.Dq Rename %s to %s\en +or +.Dq Symlink %s to %s\en ; +in either case, both +filenames are escaped using K&R C syntax. +.It "S" +This is a +.Dq sparse +regular file. +Sparse files are stored as a series of fragments. +The header contains a list of fragment offset/length pairs. +If more than four such entries are required, the header is +extended as necessary with +.Dq extra +header extensions (an older format that is no longer used), or +.Dq sparse +extensions. +.It "V" +The +.Va name +field should be interpreted as a tape/volume header name. +This entry should generally be ignored on extraction. +.El +.It Va magic +The magic field holds the five characters +.Dq ustar +followed by a space. +Note that POSIX ustar archives have a trailing null. +.It Va version +The version field holds a space character followed by a null. +Note that POSIX ustar archives use two copies of the ASCII digit +.Dq 0 . +.It Va atime , Va ctime +The time the file was last accessed and the time of +last change of file information, stored in octal as with +.Va mtime . +.It Va longnames +This field is apparently no longer used. +.It Sparse Va offset / Va numbytes +Each such structure specifies a single fragment of a sparse +file. +The two fields store values as octal numbers. +The fragments are each padded to a multiple of 512 bytes +in the archive. +On extraction, the list of fragments is collected from the +header (including any extension headers), and the data +is then read and written to the file at appropriate offsets. +.It Va isextended +If this is set to non-zero, the header will be followed by additional +.Dq sparse header +records. +Each such record contains information about as many as 21 additional +sparse blocks as shown here: +.Bd -literal -offset indent +struct gnu_sparse_header { + struct { + char offset[12]; + char numbytes[12]; + } sparse[21]; + char isextended[1]; + char padding[7]; +}; +.Ed +.It Va realsize +A binary representation of the file's complete size, with a much larger range +than the POSIX file size. +In particular, with +.Cm M +type files, the current entry is only a portion of the file. +In that case, the POSIX size field will indicate the size of this +entry; the +.Va realsize +field will indicate the total size of the file. +.El +.Ss Solaris Tar +XXX More Details Needed XXX +.Pp +Solaris tar (beginning with SunOS XXX 5.7 ?? XXX) supports an +.Dq extended +format that is fundamentally similar to pax interchange format, +with the following differences: +.Bl -bullet -compact -width indent +.It +Extended attributes are stored in an entry whose type is +.Cm X , +not +.Cm x , +as used by pax interchange format. +The detailed format of this entry appears to be the same +as detailed above for the +.Cm x +entry. +.It +An additional +.Cm A +entry is used to store an ACL for the following regular entry. +The body of this entry contains a seven-digit octal number +(whose value is 01000000 plus the number of ACL entries) +followed by a zero byte, followed by the +textual ACL description. +.El +.Ss Other Extensions +One common extension, utilized by GNU tar, star, and other newer +.Nm +implementations, permits binary numbers in the standard numeric +fields. +This is flagged by setting the high bit of the first character. +This permits 95-bit values for the length and time fields +and 63-bit values for the uid, gid, and device numbers. +GNU tar supports this extension for the +length, mtime, ctime, and atime fields. +Joerg Schilling's star program supports this extension for +all numeric fields. +Note that this extension is largely obsoleted by the extended attribute +record provided by the pax interchange format. +.Pp +Another early GNU extension allowed base-64 values rather +than octal. +This extension was short-lived and such archives are almost never seen. +However, there is still code in GNU tar to support them; this code is +responsible for a very cryptic warning message that is sometimes seen when +GNU tar encounters a damaged archive. +.Sh SEE ALSO +.Xr ar 1 , +.Xr pax 1 , +.Xr tar 1 +.Sh STANDARDS +The +.Nm tar +utility is no longer a part of POSIX or the Single Unix Standard. +It last appeared in +.St -susv2 . +It has been supplanted in subsequent standards by +.Xr pax 1 . +The ustar format is currently part of the specification for the +.Xr pax 1 +utility. +The pax interchange file format is new with +.St -p1003.1-2001 . +.Sh HISTORY +A +.Nm tar +command appeared in Seventh Edition Unix, which was released in January, 1979. +It replaced the +.Nm tp +program from Fourth Edition Unix which in turn replaced the +.Nm tap +program from First Edition Unix. +John Gilmore's +.Nm pdtar +public-domain implementation (circa 1987) was highly influential +and formed the basis of +.Nm GNU tar . +Joerg Shilling's +.Nm star +archiver is another open-source (GPL) archiver (originally developed +circa 1985) which features complete support for pax interchange +format. diff --git a/libarchive/test/.cvsignore b/libarchive/test/.cvsignore new file mode 100644 index 00000000..b71f5a0d --- /dev/null +++ b/libarchive/test/.cvsignore @@ -0,0 +1,10 @@ +*.tar +*.tar.gz +*.tgz +*.zip +.depend +.deps +.dirstamp +archive.h +libarchive_test +list.h diff --git a/libarchive/test/Makefile b/libarchive/test/Makefile new file mode 100644 index 00000000..9d04b089 --- /dev/null +++ b/libarchive/test/Makefile @@ -0,0 +1,118 @@ +# $FreeBSD: src/lib/libarchive/test/Makefile,v 1.18 2008/03/15 02:22:08 kientzle Exp $ + +# Where to find the libarchive sources +LA_SRCDIR=${.CURDIR}/.. +.PATH: ${LA_SRCDIR} + +# Get a list of all libarchive source files +LA_SRCS!=make -f ${LA_SRCDIR}/Makefile -V SRCS + +TESTS= \ + test_acl_basic.c \ + test_acl_pax.c \ + test_archive_api_feature.c \ + test_bad_fd.c \ + test_compat_gtar.c \ + test_compat_tar_hardlink.c \ + test_compat_zip.c \ + test_empty_write.c \ + test_entry.c \ + test_entry_strmode.c \ + test_pax_filename_encoding.c \ + test_read_compress_program.c \ + test_read_data_large.c \ + test_read_extract.c \ + test_read_format_ar.c \ + test_read_format_cpio_bin.c \ + test_read_format_cpio_bin_Z.c \ + test_read_format_cpio_bin_bz2.c \ + test_read_format_cpio_bin_gz.c \ + test_read_format_cpio_odc.c \ + test_read_format_cpio_svr4_gzip.c \ + test_read_format_cpio_svr4c_Z.c \ + test_read_format_empty.c \ + test_read_format_gtar_gz.c \ + test_read_format_gtar_sparse.c \ + test_read_format_iso_gz.c \ + test_read_format_isorr_bz2.c \ + test_read_format_mtree.c \ + test_read_format_pax_bz2.c \ + test_read_format_tar.c \ + test_read_format_tbz.c \ + test_read_format_tgz.c \ + test_read_format_tz.c \ + test_read_format_zip.c \ + test_read_large.c \ + test_read_pax_truncated.c \ + test_read_position.c \ + test_read_truncated.c \ + test_tar_filenames.c \ + test_tar_large.c \ + test_write_compress_program.c \ + test_write_compress.c \ + test_write_disk.c \ + test_write_disk_hardlink.c \ + test_write_disk_perms.c \ + test_write_disk_secure.c \ + test_write_format_ar.c \ + test_write_format_cpio.c \ + test_write_format_cpio_odc.c \ + test_write_format_cpio_newc.c \ + test_write_format_cpio_empty.c \ + test_write_format_shar_empty.c \ + test_write_format_tar.c \ + test_write_format_tar_empty.c \ + test_write_open_memory.c + + +# Build the test program using all libarchive sources + the test sources. +SRCS= ${LA_SRCS} \ + ${TESTS} \ + list.h \ + main.c \ + read_open_memory.c + +CLEANFILES+= list.h archive.h + +NO_MAN=yes + +PROG=libarchive_test +INTERNALPROG=yes # Don't install this; it's just for testing +DPADD=${LIBBZ2} ${LIBZ} +CFLAGS+= -DPLATFORM_CONFIG_H=\"config_freebsd.h\" +LDADD= -lz -lbz2 +CFLAGS+= -static -g +CFLAGS+= -I${.OBJDIR} +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${LA_SRCDIR} +# Without this, libarchive source files find archive.h in LA_SRCDIR, +# which may not be the same as archive.h in the test dir. +CFLAGS+= -I- + +# Uncomment to link against dmalloc +LDADD+= -L/usr/local/lib -ldmalloc +CFLAGS+= -I/usr/local/include -DUSE_DMALLOC +WARNS=6 + +# Build libarchive_test and run it. +check test: libarchive_test + ./libarchive_test -k -r ${.CURDIR} + +INCS=archive.h list.h + +# Build archive.h, but in our .OBJDIR, not libarchive's +# This keeps libarchive_test and libarchive builds completely separate. +archive.h: ${LA_SRCDIR}/archive.h.in ${LA_SRCDIR}/Makefile + cd ${LA_SRCDIR} && unset MAKEOBJDIRPREFIX && MAKEOBJDIR=${.OBJDIR} make archive.h + +# list.h is just a list of all tests, as indicated by DEFINE_TEST macro lines +list.h: ${TESTS} Makefile + (cd ${.CURDIR}; cat ${TESTS}) | grep DEFINE_TEST > list.h + +CLEANFILES += *.out *.o *.core *~ list.h archive.h + +cleantest: + -chmod -R +w /tmp/libarchive_test.* + rm -rf /tmp/libarchive_test.* + +.include <bsd.prog.mk> diff --git a/libarchive/test/README b/libarchive/test/README new file mode 100644 index 00000000..235a70b0 --- /dev/null +++ b/libarchive/test/README @@ -0,0 +1,63 @@ +$FreeBSD: src/lib/libarchive/test/README,v 1.3 2008/01/01 22:28:04 kientzle Exp $ + +This is the test harness for libarchive. + +It compiles into a single program "libarchive_test" that is intended +to exercise as much of the library as possible. It is, of course, +very much a work in progress. + +Each test is a function named test_foo in a file named test_foo.c. +Note that the file name is the same as the function name. +Each file must start with this line: + + #include "test.h" + +The test function must be declared with a line of this form + + DEFINE_TEST(test_foo) + +Nothing else should appear on that line. + +When you add a test, please update the Makefile to add your +file to the list of tests. The Makefile and main.c use various +macro trickery to automatically collect a list of test functions +to be invoked. + +Each test function can rely on the following: + + * The current directory will be a freshly-created empty directory + suitable for that test. (The top-level main() creates a + directory for each separate test and chdir()s to that directory + before running the test.) + + * The test function should use assert(), assertA() and similar macros + defined in test.h. If you need to add new macros of this form, feel + free to do so. The current macro set includes assertEqualInt() and + assertEqualString() that print out additional detail about their + arguments if the assertion does fail. 'A' versions also accept + a struct archive * and display any error message from there on + failure. + + * You are encouraged to document each assertion with a failure() call + just before the assert. The failure() function is a printf-like + function whose text is displayed only if the assertion fails. It + can be used to display additional information relevant to the failure: + + failure("The data read from file %s did not match the data written to that file.", filename); + assert(strcmp(buff1, buff2) == 0); + + * Tests are encouraged to be economical with their memory and disk usage, + though this is not essential. The test is occasionally run under + a memory debugger to try to locate memory leaks in the library; + as a result, tests should be careful to release any memory they + allocate. + + * Disable tests on specific platforms as necessary. Please don't + use config.h to adjust feature requirements, as I want the tests + to also serve as a check on the configure process. The following + form is appropriate: + +#if !defined(__PLATFORM) && !defined(__Platform2__) + assert(xxxx) +#endif + diff --git a/libarchive/test/main.c b/libarchive/test/main.c new file mode 100644 index 00000000..39778032 --- /dev/null +++ b/libarchive/test/main.c @@ -0,0 +1,882 @@ +/* + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Various utility routines useful for test programs. + * Each test program is linked against this file. + */ +#include <errno.h> +#include <locale.h> +#include <stdarg.h> +#include <time.h> + +#include "test.h" + +/* + * This same file is used pretty much verbatim for all test harnesses. + * + * The next few lines are the only differences. + */ +#undef PROGRAM /* Testing a library, not a program. */ +#define ENVBASE "LIBARCHIVE" /* Prefix for environment variables. */ +#define EXTRA_DUMP(x) archive_error_string((struct archive *)(x)) +#define EXTRA_VERSION archive_version() +__FBSDID("$FreeBSD: src/lib/libarchive/test/main.c,v 1.11 2008/03/12 05:12:23 kientzle Exp $"); + +/* + * "list.h" is simply created by "grep DEFINE_TEST"; it has + * a line like + * DEFINE_TEST(test_function) + * for each test. + * Include it here with a suitable DEFINE_TEST to declare all of the + * test functions. + */ +#undef DEFINE_TEST +#define DEFINE_TEST(name) void name(void); +#include "list.h" + +/* Interix doesn't define these in a standard header. */ +#if __INTERIX__ +extern char *optarg; +extern int optind; +#endif + +/* Default is to crash and try to force a core dump on failure. */ +static int dump_on_failure = 1; +/* Default is to print some basic information about each test. */ +static int quiet_flag = 0; +/* Cumulative count of component failures. */ +static int failures = 0; +/* Cumulative count of skipped component tests. */ +static int skips = 0; +/* Cumulative count of assertions. */ +static int assertions = 0; + +/* Directory where uuencoded reference files can be found. */ +static char *refdir; + +/* + * My own implementation of the standard assert() macro emits the + * message in the same format as GCC (file:line: message). + * It also includes some additional useful information. + * This makes it a lot easier to skim through test failures in + * Emacs. ;-) + * + * It also supports a few special features specifically to simplify + * test harnesses: + * failure(fmt, args) -- Stores a text string that gets + * printed if the following assertion fails, good for + * explaining subtle tests. + */ +static char msg[4096]; + +/* + * For each test source file, we remember how many times each + * failure was reported. + */ +static const char *failed_filename = NULL; +static struct line { + int line; + int count; +} failed_lines[1000]; + +/* + * Count this failure; return the number of previous failures. + */ +static int +previous_failures(const char *filename, int line) +{ + unsigned int i; + int count; + + if (failed_filename == NULL || strcmp(failed_filename, filename) != 0) + memset(failed_lines, 0, sizeof(failed_lines)); + failed_filename = filename; + + for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) { + if (failed_lines[i].line == line) { + count = failed_lines[i].count; + failed_lines[i].count++; + return (count); + } + if (failed_lines[i].line == 0) { + failed_lines[i].line = line; + failed_lines[i].count = 1; + return (0); + } + } + return (0); +} + +/* + * Copy arguments into file-local variables. + */ +static const char *test_filename; +static int test_line; +static void *test_extra; +void test_setup(const char *filename, int line) +{ + test_filename = filename; + test_line = line; +} + +/* + * Inform user that we're skipping a test. + */ +void +test_skipping(const char *fmt, ...) +{ + va_list ap; + + if (previous_failures(test_filename, test_line)) + return; + + va_start(ap, fmt); + fprintf(stderr, " *** SKIPPING: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + ++skips; +} + +/* Common handling of failed tests. */ +static void +report_failure(void *extra) +{ + if (msg[0] != '\0') { + fprintf(stderr, " Description: %s\n", msg); + msg[0] = '\0'; + } + +#ifdef EXTRA_DUMP + if (extra != NULL) + fprintf(stderr, " detail: %s\n", EXTRA_DUMP(extra)); +#else + (void)extra; /* UNUSED */ +#endif + + if (dump_on_failure) { + fprintf(stderr, + " *** forcing core dump so failure can be debugged ***\n"); + *(char *)(NULL) = 0; + exit(1); + } +} + +/* + * Summarize repeated failures in the just-completed test file. + * The reports above suppress multiple failures from the same source + * line; this reports on any tests that did fail multiple times. + */ +static int +summarize_comparator(const void *a0, const void *b0) +{ + const struct line *a = a0, *b = b0; + if (a->line == 0 && b->line == 0) + return (0); + if (a->line == 0) + return (1); + if (b->line == 0) + return (-1); + return (a->line - b->line); +} + +static void +summarize(void) +{ + unsigned int i; + + qsort(failed_lines, sizeof(failed_lines)/sizeof(failed_lines[0]), + sizeof(failed_lines[0]), summarize_comparator); + for (i = 0; i < sizeof(failed_lines)/sizeof(failed_lines[0]); i++) { + if (failed_lines[i].line == 0) + break; + if (failed_lines[i].count > 1) + fprintf(stderr, "%s:%d: Failed %d times\n", + failed_filename, failed_lines[i].line, + failed_lines[i].count); + } + /* Clear the failure history for the next file. */ + memset(failed_lines, 0, sizeof(failed_lines)); +} + +/* Set up a message to display only after a test fails. */ +void +failure(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsprintf(msg, fmt, ap); + va_end(ap); +} + +/* Generic assert() just displays the failed condition. */ +int +test_assert(const char *file, int line, int value, const char *condition, void *extra) +{ + ++assertions; + if (value) { + msg[0] = '\0'; + return (value); + } + failures ++; + if (previous_failures(file, line)) + return (value); + fprintf(stderr, "%s:%d: Assertion failed\n", file, line); + fprintf(stderr, " Condition: %s\n", condition); + report_failure(extra); + return (value); +} + +/* assertEqualInt() displays the values of the two integers. */ +int +test_assert_equal_int(const char *file, int line, + int v1, const char *e1, int v2, const char *e2, void *extra) +{ + ++assertions; + if (v1 == v2) { + msg[0] = '\0'; + return (1); + } + failures ++; + if (previous_failures(file, line)) + return (0); + fprintf(stderr, "%s:%d: Assertion failed: Ints not equal\n", + file, line); + fprintf(stderr, " %s=%d\n", e1, v1); + fprintf(stderr, " %s=%d\n", e2, v2); + report_failure(extra); + return (0); +} + +/* assertEqualString() displays the values of the two strings. */ +int +test_assert_equal_string(const char *file, int line, + const char *v1, const char *e1, + const char *v2, const char *e2, + void *extra) +{ + ++assertions; + if (v1 == NULL || v2 == NULL) { + if (v1 == v2) { + msg[0] = '\0'; + return (1); + } + } else if (strcmp(v1, v2) == 0) { + msg[0] = '\0'; + return (1); + } + failures ++; + if (previous_failures(file, line)) + return (0); + fprintf(stderr, "%s:%d: Assertion failed: Strings not equal\n", + file, line); + fprintf(stderr, " %s = \"%s\"\n", e1, v1); + fprintf(stderr, " %s = \"%s\"\n", e2, v2); + report_failure(extra); + return (0); +} + +/* assertEqualWString() displays the values of the two strings. */ +int +test_assert_equal_wstring(const char *file, int line, + const wchar_t *v1, const char *e1, + const wchar_t *v2, const char *e2, + void *extra) +{ + ++assertions; + if (wcscmp(v1, v2) == 0) { + msg[0] = '\0'; + return (1); + } + failures ++; + if (previous_failures(file, line)) + return (0); + fprintf(stderr, "%s:%d: Assertion failed: Unicode strings not equal\n", + file, line); + fwprintf(stderr, L" %s = \"%ls\"\n", e1, v1); + fwprintf(stderr, L" %s = \"%ls\"\n", e2, v2); + report_failure(extra); + return (0); +} + +/* + * Pretty standard hexdump routine. As a bonus, if ref != NULL, then + * any bytes in p that differ from ref will be highlighted with '_' + * before and after the hex value. + */ +static void +hexdump(const char *p, const char *ref, size_t l, size_t offset) +{ + size_t i, j; + char sep; + + for(i=0; i < l; i+=16) { + fprintf(stderr, "%04x", i + offset); + sep = ' '; + for (j = 0; j < 16 && i + j < l; j++) { + if (ref != NULL && p[i + j] != ref[i + j]) + sep = '_'; + fprintf(stderr, "%c%02x", sep, 0xff & (int)p[i+j]); + if (ref != NULL && p[i + j] == ref[i + j]) + sep = ' '; + } + for (; j < 16; j++) { + fprintf(stderr, "%c ", sep); + sep = ' '; + } + fprintf(stderr, "%c", sep); + for (j=0; j < 16 && i + j < l; j++) { + int c = p[i + j]; + if (c >= ' ' && c <= 126) + fprintf(stderr, "%c", c); + else + fprintf(stderr, "."); + } + fprintf(stderr, "\n"); + } +} + +/* assertEqualMem() displays the values of the two memory blocks. */ +/* TODO: For long blocks, hexdump the first bytes that actually differ. */ +int +test_assert_equal_mem(const char *file, int line, + const char *v1, const char *e1, + const char *v2, const char *e2, + size_t l, const char *ld, void *extra) +{ + ++assertions; + if (v1 == NULL || v2 == NULL) { + if (v1 == v2) { + msg[0] = '\0'; + return (1); + } + } else if (memcmp(v1, v2, l) == 0) { + msg[0] = '\0'; + return (1); + } + failures ++; + if (previous_failures(file, line)) + return (0); + fprintf(stderr, "%s:%d: Assertion failed: memory not equal\n", + file, line); + fprintf(stderr, " size %s = %d\n", ld, (int)l); + fprintf(stderr, " Dump of %s\n", e1); + hexdump(v1, v2, l < 32 ? l : 32, 0); + fprintf(stderr, " Dump of %s\n", e2); + hexdump(v2, v1, l < 32 ? l : 32, 0); + fprintf(stderr, "\n"); + report_failure(extra); + return (0); +} + +int +test_assert_empty_file(const char *f1fmt, ...) +{ + char buff[1024]; + char f1[1024]; + struct stat st; + va_list ap; + ssize_t s; + int fd; + + + va_start(ap, f1fmt); + vsprintf(f1, f1fmt, ap); + va_end(ap); + + if (stat(f1, &st) != 0) { + fprintf(stderr, "%s:%d: Could not stat: %s\n", test_filename, test_line, f1); + report_failure(NULL); + } + if (st.st_size == 0) + return (1); + + failures ++; + if (previous_failures(test_filename, test_line)) + return (0); + + fprintf(stderr, "%s:%d: File not empty: %s\n", test_filename, test_line, f1); + fprintf(stderr, " File size: %d\n", (int)st.st_size); + fprintf(stderr, " Contents:\n"); + fd = open(f1, O_RDONLY); + if (fd < 0) { + fprintf(stderr, " Unable to open %s\n", f1); + } else { + s = sizeof(buff) < st.st_size ? sizeof(buff) : st.st_size; + s = read(fd, buff, s); + hexdump(buff, NULL, s, 0); + } + report_failure(NULL); + return (0); +} + +/* assertEqualFile() asserts that two files have the same contents. */ +/* TODO: hexdump the first bytes that actually differ. */ +int +test_assert_equal_file(const char *f1, const char *f2pattern, ...) +{ + char f2[1024]; + va_list ap; + char buff1[1024]; + char buff2[1024]; + int fd1, fd2; + int n1, n2; + + va_start(ap, f2pattern); + vsprintf(f2, f2pattern, ap); + va_end(ap); + + fd1 = open(f1, O_RDONLY); + fd2 = open(f2, O_RDONLY); + for (;;) { + n1 = read(fd1, buff1, sizeof(buff1)); + n2 = read(fd2, buff2, sizeof(buff2)); + if (n1 != n2) + break; + if (n1 == 0 && n2 == 0) + return (1); + if (memcmp(buff1, buff2, n1) != 0) + break; + } + failures ++; + if (previous_failures(test_filename, test_line)) + return (0); + fprintf(stderr, "%s:%d: Files are not identical\n", + test_filename, test_line); + fprintf(stderr, " file1=\"%s\"\n", f1); + fprintf(stderr, " file2=\"%s\"\n", f2); + report_failure(test_extra); + return (0); +} + +/* assertFileContents() asserts the contents of a file. */ +int +test_assert_file_contents(const void *buff, int s, const char *fpattern, ...) +{ + char f[1024]; + va_list ap; + char *contents; + int fd; + int n; + + va_start(ap, fpattern); + vsprintf(f, fpattern, ap); + va_end(ap); + + fd = open(f, O_RDONLY); + contents = malloc(s * 2); + n = read(fd, contents, s * 2); + if (n == s && memcmp(buff, contents, s) == 0) { + free(contents); + return (1); + } + failures ++; + if (!previous_failures(test_filename, test_line)) { + fprintf(stderr, "%s:%d: File contents don't match\n", + test_filename, test_line); + fprintf(stderr, " file=\"%s\"\n", f); + if (n > 0) + hexdump(contents, buff, n, 0); + else { + fprintf(stderr, " File empty, contents should be:\n"); + hexdump(buff, NULL, s, 0); + } + report_failure(test_extra); + } + free(contents); + return (0); +} + +/* + * Call standard system() call, but build up the command line using + * sprintf() conventions. + */ +int +systemf(const char *fmt, ...) +{ + char buff[8192]; + va_list ap; + int r; + + va_start(ap, fmt); + vsprintf(buff, fmt, ap); + r = system(buff); + va_end(ap); + return (r); +} + +/* + * Slurp a file into memory for ease of comparison and testing. + * Returns size of file in 'sizep' if non-NULL, null-terminates + * data in memory for ease of use. + */ +char * +slurpfile(size_t * sizep, const char *fmt, ...) +{ + char filename[8192]; + struct stat st; + va_list ap; + char *p; + ssize_t bytes_read; + int fd; + int r; + + va_start(ap, fmt); + vsprintf(filename, fmt, ap); + va_end(ap); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + /* Note: No error; non-existent file is okay here. */ + return (NULL); + } + r = fstat(fd, &st); + if (r != 0) { + fprintf(stderr, "Can't stat file %s\n", filename); + close(fd); + return (NULL); + } + p = malloc(st.st_size + 1); + if (p == NULL) { + fprintf(stderr, "Can't allocate %ld bytes of memory to read file %s\n", (long int)st.st_size, filename); + close(fd); + return (NULL); + } + bytes_read = read(fd, p, st.st_size); + if (bytes_read < st.st_size) { + fprintf(stderr, "Can't read file %s\n", filename); + close(fd); + free(p); + return (NULL); + } + p[st.st_size] = '\0'; + if (sizep != NULL) + *sizep = (size_t)st.st_size; + close(fd); + return (p); +} + +/* + * "list.h" is automatically generated; it just has a lot of lines like: + * DEFINE_TEST(function_name) + * It's used above to declare all of the test functions. + * We reuse it here to define a list of all tests (functions and names). + */ +#undef DEFINE_TEST +#define DEFINE_TEST(n) { n, #n }, +struct { void (*func)(void); const char *name; } tests[] = { + #include "list.h" +}; + +/* + * Each test is run in a private work dir. Those work dirs + * do have consistent and predictable names, in case a group + * of tests need to collaborate. However, there is no provision + * for requiring that tests run in a certain order. + */ +static int test_run(int i, const char *tmpdir) +{ + int failures_before = failures; + + if (!quiet_flag) + printf("%d: %s\n", i, tests[i].name); + /* + * Always explicitly chdir() in case the last test moved us to + * a strange place. + */ + if (chdir(tmpdir)) { + fprintf(stderr, + "ERROR: Couldn't chdir to temp dir %s\n", + tmpdir); + exit(1); + } + /* Create a temp directory for this specific test. */ + if (mkdir(tests[i].name, 0755)) { + fprintf(stderr, + "ERROR: Couldn't create temp dir ``%s''\n", + tests[i].name); + exit(1); + } + /* Chdir() to that work directory. */ + if (chdir(tests[i].name)) { + fprintf(stderr, + "ERROR: Couldn't chdir to temp dir ``%s''\n", + tests[i].name); + exit(1); + } + /* Explicitly reset the locale before each test. */ + setlocale(LC_ALL, "C"); + /* Run the actual test. */ + (*tests[i].func)(); + /* Summarize the results of this test. */ + summarize(); + /* Return appropriate status. */ + return (failures == failures_before ? 0 : 1); +} + +static void usage(const char *program) +{ + static const int limit = sizeof(tests) / sizeof(tests[0]); + int i; + + printf("Usage: %s [options] <test> <test> ...\n", program); + printf("Default is to run all tests.\n"); + printf("Otherwise, specify the numbers of the tests you wish to run.\n"); + printf("Options:\n"); + printf(" -k Keep running after failures.\n"); + printf(" Default: Core dump after any failure.\n"); +#ifdef PROGRAM + printf(" -p <path> Path to executable to be tested.\n"); + printf(" Default: path taken from " ENVBASE " environment variable.\n"); +#endif + printf(" -q Quiet.\n"); + printf(" -r <dir> Path to dir containing reference files.\n"); + printf(" Default: Current directory.\n"); + printf("Available tests:\n"); + for (i = 0; i < limit; i++) + printf(" %d: %s\n", i, tests[i].name); + exit(1); +} + +#define UUDECODE(c) (((c) - 0x20) & 0x3f) + +void +extract_reference_file(const char *name) +{ + char buff[1024]; + FILE *in, *out; + + sprintf(buff, "%s/%s.uu", refdir, name); + in = fopen(buff, "r"); + failure("Couldn't open reference file %s", buff); + assert(in != NULL); + if (in == NULL) + return; + /* Read up to and including the 'begin' line. */ + for (;;) { + if (fgets(buff, sizeof(buff), in) == NULL) { + /* TODO: This is a failure. */ + return; + } + if (memcmp(buff, "begin ", 6) == 0) + break; + } + /* Now, decode the rest and write it. */ + /* Not a lot of error checking here; the input better be right. */ + out = fopen(name, "w"); + while (fgets(buff, sizeof(buff), in) != NULL) { + char *p = buff; + int bytes; + + if (memcmp(buff, "end", 3) == 0) + break; + + bytes = UUDECODE(*p++); + while (bytes > 0) { + int n = 0; + /* Write out 1-3 bytes from that. */ + if (bytes > 0) { + n = UUDECODE(*p++) << 18; + n |= UUDECODE(*p++) << 12; + fputc(n >> 16, out); + --bytes; + } + if (bytes > 0) { + n |= UUDECODE(*p++) << 6; + fputc((n >> 8) & 0xFF, out); + --bytes; + } + if (bytes > 0) { + n |= UUDECODE(*p++); + fputc(n & 0xFF, out); + --bytes; + } + } + } + fclose(out); + fclose(in); +} + + +int main(int argc, char **argv) +{ + static const int limit = sizeof(tests) / sizeof(tests[0]); + int i, tests_run = 0, tests_failed = 0, opt; + time_t now; + char *refdir_alloc = NULL; + char *progname, *p; + char tmpdir[256]; + char tmpdir_timestamp[256]; + + /* + * Name of this program, used to build root of our temp directory + * tree. + */ + progname = p = argv[0]; + while (*p != '\0') { + if (*p == '/') + progname = p + 1; + ++p; + } + +#ifdef PROGRAM + /* Get the target program from environment, if available. */ + testprog = getenv(ENVBASE); +#endif + + /* Allow -k to be controlled through the environment. */ + if (getenv(ENVBASE "_KEEP_GOING") != NULL) + dump_on_failure = 0; + + /* Get the directory holding test files from environment. */ + refdir = getenv(ENVBASE "_TEST_FILES"); + + /* + * Parse options. + */ + while ((opt = getopt(argc, argv, "kp:qr:")) != -1) { + switch (opt) { + case 'k': + dump_on_failure = 0; + break; + case 'p': +#ifdef PROGRAM + testprog = optarg; +#else + usage(progname); +#endif + break; + case 'q': + quiet_flag++; + break; + case 'r': + refdir = optarg; + break; + case '?': + default: + usage(progname); + } + } + argc -= optind; + argv += optind; + + /* + * Sanity-check that our options make sense. + */ +#ifdef PROGRAM + if (testprog == NULL) + usage(progname); +#endif + + /* + * Create a temp directory for the following tests. + * Include the time the tests started as part of the name, + * to make it easier to track the results of multiple tests. + */ + now = time(NULL); + for (i = 0; i < 1000; i++) { + strftime(tmpdir_timestamp, sizeof(tmpdir_timestamp), + "%Y-%m-%dT%H.%M.%S", + localtime(&now)); + sprintf(tmpdir, "/tmp/%s.%s-%03d", progname, tmpdir_timestamp, i); + if (mkdir(tmpdir,0755) == 0) + break; + if (errno == EEXIST) + continue; + fprintf(stderr, "ERROR: Unable to create temp directory %s\n", + tmpdir); + exit(1); + } + + /* + * If the user didn't specify a directory for locating + * reference files, use the current directory for that. + */ + if (refdir == NULL) { + systemf("/bin/pwd > %s/refdir", tmpdir); + refdir = refdir_alloc = slurpfile(NULL, "%s/refdir", tmpdir); + p = refdir + strlen(refdir); + while (p[-1] == '\n') { + --p; + *p = '\0'; + } + } + + /* + * Banner with basic information. + */ + if (!quiet_flag) { + printf("Running tests in: %s\n", tmpdir); + printf("Reference files will be read from: %s\n", refdir); +#ifdef PROGRAM + printf("Running tests on: %s\n", testprog); +#endif + printf("Exercising: "); + fflush(stdout); + printf("%s\n", EXTRA_VERSION); + } + + /* + * Run some or all of the individual tests. + */ + if (argc == 0) { + /* Default: Run all tests. */ + for (i = 0; i < limit; i++) { + if (test_run(i, tmpdir)) + tests_failed++; + tests_run++; + } + } else { + while (*(argv) != NULL) { + i = atoi(*argv); + if (**argv < '0' || **argv > '9' || i < 0 || i >= limit) { + printf("*** INVALID Test %s\n", *argv); + usage(progname); + } else { + if (test_run(i, tmpdir)) + tests_failed++; + tests_run++; + } + argv++; + } + } + + /* + * Report summary statistics. + */ + if (!quiet_flag) { + printf("\n"); + printf("%d of %d tests reported failures\n", + tests_failed, tests_run); + printf(" Total of %d assertions checked.\n", assertions); + printf(" Total of %d assertions failed.\n", failures); + printf(" Total of %d assertions skipped.\n", skips); + } + + free(refdir_alloc); + + return (tests_failed); +} diff --git a/libarchive/test/read_open_memory.c b/libarchive/test/read_open_memory.c new file mode 100644 index 00000000..aacb0123 --- /dev/null +++ b/libarchive/test/read_open_memory.c @@ -0,0 +1,148 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/read_open_memory.c,v 1.2 2008/01/01 22:28:04 kientzle Exp $"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* + * Read an archive from a block of memory. + * + * This is identical to archive_read_open_memory(), except + * that it goes out of its way to be a little bit unpleasant, + * in order to better test the libarchive internals. + */ + +struct read_memory_data { + unsigned char *buffer; + unsigned char *end; + size_t read_size; + size_t copy_buff_size; + char *copy_buff; +}; + +static int memory_read_close(struct archive *, void *); +static int memory_read_open(struct archive *, void *); +#if ARCHIVE_API_VERSION < 2 +static ssize_t memory_read_skip(struct archive *, void *, size_t request); +#else +static off_t memory_read_skip(struct archive *, void *, off_t request); +#endif +static ssize_t memory_read(struct archive *, void *, const void **buff); + +int +read_open_memory(struct archive *a, void *buff, size_t size, size_t read_size) +{ + struct read_memory_data *mine; + + mine = (struct read_memory_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + memset(mine, 0, sizeof(*mine)); + mine->buffer = (unsigned char *)buff; + mine->end = mine->buffer + size; + mine->read_size = read_size; + mine->copy_buff_size = read_size + 64; + mine->copy_buff = malloc(mine->copy_buff_size); + return (archive_read_open2(a, mine, memory_read_open, + memory_read, memory_read_skip, memory_read_close)); +} + +/* + * There's nothing to open. + */ +static int +memory_read_open(struct archive *a, void *client_data) +{ + (void)a; /* UNUSED */ + (void)client_data; /* UNUSED */ + return (ARCHIVE_OK); +} + +/* + * In order to exercise libarchive's internal read-combining logic, + * we deliberately copy data for each read to a separate buffer. + * That way, code that runs off the end of the provided data + * will screw up. + */ +static ssize_t +memory_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + size_t size; + + (void)a; /* UNUSED */ + size = mine->end - mine->buffer; + if (size > mine->read_size) + size = mine->read_size; + memset(mine->copy_buff, 0xA5, mine->copy_buff_size); + memcpy(mine->copy_buff, mine->buffer, size); + *buff = mine->copy_buff; + + mine->buffer += size; + return (size); +} + +/* + * How mean can a skip() routine be? Let's try to find out. + */ +#if ARCHIVE_API_VERSION < 2 +static ssize_t +memory_read_skip(struct archive *a, void *client_data, size_t skip) +#else +static off_t +memory_read_skip(struct archive *a, void *client_data, off_t skip) +#endif +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + + (void)a; /* UNUSED */ + /* We can't skip by more than is available. */ + if ((off_t)skip > (off_t)(mine->end - mine->buffer)) + skip = mine->end - mine->buffer; + /* Always do small skips by prime amounts. */ + if (skip > 71) + skip = 71; + mine->buffer += skip; + return (skip); +} + +/* + * Close is just cleaning up our one small bit of data. + */ +static int +memory_read_close(struct archive *a, void *client_data) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + (void)a; /* UNUSED */ + free(mine->copy_buff); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/test/test.h b/libarchive/test/test.h new file mode 100644 index 00000000..dcb190ff --- /dev/null +++ b/libarchive/test/test.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2003-2006 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/lib/libarchive/test/test.h,v 1.9 2008/03/12 05:12:23 kientzle Exp $ + */ + +/* Every test program should #include "test.h" as the first thing. */ + +/* + * The goal of this file (and the matching test.c) is to + * simplify the very repetitive test-*.c test programs. + */ +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#ifndef _WIN32 +#include <unistd.h> +#endif +#include <wchar.h> + +#ifdef USE_DMALLOC +#include <dmalloc.h> +#endif + +#if defined(HAVE_CONFIG_H) +/* Most POSIX platforms use the 'configure' script to build config.h */ +#include "../../config.h" +#elif defined(__FreeBSD__) +/* Building as part of FreeBSD system requires a pre-built config.h. */ +#include "../config_freebsd.h" +#elif defined(_WIN32) +/* Win32 can't run the 'configure' script. */ +#include "../config_windows.h" +#else +/* Warn if the library hasn't been (automatically or manually) configured. */ +#error Oops: No config.h and no pre-built configuration in test.h. +#endif + +/* No non-FreeBSD platform will have __FBSDID, so just define it here. */ +#ifdef __FreeBSD__ +#include <sys/cdefs.h> /* For __FBSDID */ +#else +#define __FBSDID(a) /* null */ +#endif + +/* + * Redefine DEFINE_TEST for use in defining the test functions. + */ +#undef DEFINE_TEST +#define DEFINE_TEST(name) void name(void); void name(void) + +/* An implementation of the standard assert() macro */ +#define assert(e) test_assert(__FILE__, __LINE__, (e), #e, NULL) + +/* Assert two integers are the same. Reports value of each one if not. */ +#define assertEqualInt(v1,v2) \ + test_assert_equal_int(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL) + +/* Assert two strings are the same. Reports value of each one if not. */ +#define assertEqualString(v1,v2) \ + test_assert_equal_string(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL) +/* As above, but v1 and v2 are wchar_t * */ +#define assertEqualWString(v1,v2) \ + test_assert_equal_wstring(__FILE__, __LINE__, (v1), #v1, (v2), #v2, NULL) +/* As above, but raw blocks of bytes. */ +#define assertEqualMem(v1, v2, l) \ + test_assert_equal_mem(__FILE__, __LINE__, (v1), #v1, (v2), #v2, (l), #l, NULL) +/* Assert two files are the same; allow printf-style expansion of second name. + * See below for comments about variable arguments here... + */ +#define assertEqualFile \ + test_setup(__FILE__, __LINE__);test_assert_equal_file +/* Assert that a file is empty; supports printf-style arguments. */ +#define assertEmptyFile \ + test_setup(__FILE__, __LINE__);test_assert_empty_file + +/* + * This would be simple with C99 variadic macros, but I don't want to + * require that. Instead, I insert a function call before each + * skipping() call to pass the file and line information down. Crude, + * but effective. + */ +#define skipping \ + test_setup(__FILE__, __LINE__);test_skipping + +/* Function declarations. These are defined in test_utility.c. */ +void failure(const char *fmt, ...); +void test_setup(const char *, int); +void test_skipping(const char *fmt, ...); +int test_assert(const char *, int, int, const char *, void *); +int test_assert_empty_file(const char *, ...); +int test_assert_equal_file(const char *, const char *, ...); +int test_assert_equal_int(const char *, int, int, const char *, int, const char *, void *); +int test_assert_equal_string(const char *, int, const char *v1, const char *, const char *v2, const char *, void *); +int test_assert_equal_wstring(const char *, int, const wchar_t *v1, const char *, const wchar_t *v2, const char *, void *); +int test_assert_equal_mem(const char *, int, const char *, const char *, const char *, const char *, size_t, const char *, void *); +int test_assert_file_contents(const void *, int, const char *, ...); + +/* Like sprintf, then system() */ +int systemf(const char * fmt, ...); + +/* Suck file into string allocated via malloc(). Call free() when done. */ +/* Supports printf-style args: slurpfile(NULL, "%s/myfile", refdir); */ +char *slurpfile(size_t *, const char *fmt, ...); + +/* Extracts named reference file to the current directory. */ +void extract_reference_file(const char *); + +/* + * Special interfaces for libarchive test harness. + */ + +#include "archive.h" +#include "archive_entry.h" + +/* Special customized read-from-memory interface. */ +int read_open_memory(struct archive *, void *, size_t, size_t); + +/* + * ARCHIVE_VERSION_STAMP first appeared in 1.9 and libarchive 2.2.4. + * We can approximate it for earlier versions, though. + * This is used to disable tests of features not present in the current + * version. + */ +#ifndef ARCHIVE_VERSION_STAMP +#define ARCHIVE_VERSION_STAMP \ + (ARCHIVE_API_VERSION * 1000000 + ARCHIVE_API_FEATURE * 1000) +#endif + +/* Versions of above that accept an archive argument for additional info. */ +#define assertA(e) test_assert(__FILE__, __LINE__, (e), #e, (a)) +#define assertEqualIntA(a,v1,v2) \ + test_assert_equal_int(__FILE__, __LINE__, (v1), #v1, (v2), #v2, (a)) +#define assertEqualStringA(a,v1,v2) \ + test_assert_equal_string(__FILE__, __LINE__, (v1), #v1, (v2), #v2, (a)) diff --git a/libarchive/test/test_acl_basic.c b/libarchive/test/test_acl_basic.c new file mode 100644 index 00000000..effcfb78 --- /dev/null +++ b/libarchive/test/test_acl_basic.c @@ -0,0 +1,230 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_acl_basic.c,v 1.4 2007/07/06 15:43:11 kientzle Exp $"); + +/* + * Exercise the system-independent portion of the ACL support. + * Check that archive_entry objects can save and restore ACL data + * and that pax archive can save and restore ACL data. + * + * This should work on all systems, regardless of whether local + * filesystems support ACLs or not. + */ + +struct acl_t { + int type; /* Type of ACL: "access" or "default" */ + int permset; /* Permissions for this class of users. */ + int tag; /* Owner, User, Owning group, group, other, etc. */ + int qual; /* GID or UID of user/group, depending on tag. */ + const char *name; /* Name of user/group, depending on tag. */ +}; + +struct acl_t acls0[] = { + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE, + ARCHIVE_ENTRY_ACL_USER_OBJ, 0, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_GROUP_OBJ, 0, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_WRITE, + ARCHIVE_ENTRY_ACL_OTHER, 0, "" }, +}; + +struct acl_t acls1[] = { + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE, + ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_USER, 77, "user77" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_WRITE, + ARCHIVE_ENTRY_ACL_OTHER, -1, "" }, +}; + +struct acl_t acls2[] = { + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE | ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_USER, 77, "user77" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0, + ARCHIVE_ENTRY_ACL_USER, 78, "user78" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0007, + ARCHIVE_ENTRY_ACL_GROUP, 78, "group78" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_WRITE | ARCHIVE_ENTRY_ACL_EXECUTE, + ARCHIVE_ENTRY_ACL_OTHER, -1, "" }, +}; + +static void +set_acls(struct archive_entry *ae, struct acl_t *acls, int n) +{ + int i; + + archive_entry_acl_clear(ae); + for (i = 0; i < n; i++) { + archive_entry_acl_add_entry(ae, + acls[i].type, acls[i].permset, acls[i].tag, acls[i].qual, + acls[i].name); + } +} + +static int +acl_match(struct acl_t *acl, int type, int permset, int tag, int qual, const char *name) +{ + if (type != acl->type) + return (0); + if (permset != acl->permset) + return (0); + if (tag != acl->tag) + return (0); + if (tag == ARCHIVE_ENTRY_ACL_USER_OBJ) + return (1); + if (tag == ARCHIVE_ENTRY_ACL_GROUP_OBJ) + return (1); + if (tag == ARCHIVE_ENTRY_ACL_OTHER) + return (1); + if (qual != acl->qual) + return (0); + if (name == NULL) { + if (acl->name == NULL || acl->name[0] == '\0') + return (1); + } + if (acl->name == NULL) { + if (name[0] == '\0') + return (1); + } + return (0 == strcmp(name, acl->name)); +} + +static void +compare_acls(struct archive_entry *ae, struct acl_t *acls, int n, int mode) +{ + int *marker = malloc(sizeof(marker[0]) * n); + int i; + int r; + int type, permset, tag, qual; + int matched; + const char *name; + + for (i = 0; i < n; i++) + marker[i] = i; + + while (0 == (r = archive_entry_acl_next(ae, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name))) { + for (i = 0, matched = 0; i < n && !matched; i++) { + if (acl_match(&acls[marker[i]], type, permset, + tag, qual, name)) { + /* We found a match; remove it. */ + marker[i] = marker[n - 1]; + n--; + matched = 1; + } + } + if (tag == ARCHIVE_ENTRY_ACL_USER_OBJ) { + if (!matched) printf("No match for user_obj perm\n"); + failure("USER_OBJ permset (%02o) != user mode (%02o)", + permset, 07 & (mode >> 6)); + assert((permset << 6) == (mode & 0700)); + } else if (tag == ARCHIVE_ENTRY_ACL_GROUP_OBJ) { + if (!matched) printf("No match for group_obj perm\n"); + failure("GROUP_OBJ permset %02o != group mode %02o", + permset, 07 & (mode >> 3)); + assert((permset << 3) == (mode & 0070)); + } else if (tag == ARCHIVE_ENTRY_ACL_OTHER) { + if (!matched) printf("No match for other perm\n"); + failure("OTHER permset (%02o) != other mode (%02o)", + permset, mode & 07); + assert((permset << 0) == (mode & 0007)); + } else { + failure("Could not find match for ACL " + "(type=%d,permset=%d,tag=%d,qual=%d,name=``%s'')", + type, permset, tag, qual, name); + assert(matched == 1); + } + } +#if ARCHIVE_VERSION_STAMP < 1009000 + /* Known broken before 1.9.0. */ + skipping("archive_entry_acl_next() exits with ARCHIVE_EOF"); +#else + assertEqualInt(ARCHIVE_EOF, r); +#endif + assert((mode & 0777) == (archive_entry_mode(ae) & 0777)); + failure("Could not find match for ACL " + "(type=%d,permset=%d,tag=%d,qual=%d,name=``%s'')", + acls[marker[0]].type, acls[marker[0]].permset, + acls[marker[0]].tag, acls[marker[0]].qual, acls[marker[0]].name); + assert(n == 0); /* Number of ACLs not matched should == 0 */ + free(marker); +} + +DEFINE_TEST(test_acl_basic) +{ + struct archive_entry *ae; + + /* Create a simple archive_entry. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0777); + + /* Basic owner/owning group should just update mode bits. */ + set_acls(ae, acls0, sizeof(acls0)/sizeof(acls0[0])); + failure("Basic ACLs shouldn't be stored as extended ACLs"); + assert(0 == archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + failure("Basic ACLs should set mode to 0142, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0142); + + + /* With any extended ACL entry, we should read back a full set. */ + set_acls(ae, acls1, sizeof(acls1)/sizeof(acls1[0])); + failure("One extended ACL should flag all ACLs to be returned."); + assert(4 == archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + compare_acls(ae, acls1, sizeof(acls1)/sizeof(acls1[0]), 0142); + failure("Basic ACLs should set mode to 0142, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0142); + + + /* A more extensive set of ACLs. */ + set_acls(ae, acls2, sizeof(acls2)/sizeof(acls2[0])); + assertEqualInt(6, archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + compare_acls(ae, acls2, sizeof(acls2)/sizeof(acls2[0]), 0543); + failure("Basic ACLs should set mode to 0543, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0543); + + /* + * Check that clearing ACLs gets rid of them all by repeating + * the first test. + */ + set_acls(ae, acls0, sizeof(acls0)/sizeof(acls0[0])); + failure("Basic ACLs shouldn't be stored as extended ACLs"); + assert(0 == archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + failure("Basic ACLs should set mode to 0142, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0142); + archive_entry_free(ae); +} diff --git a/libarchive/test/test_acl_pax.c b/libarchive/test/test_acl_pax.c new file mode 100644 index 00000000..abf74694 --- /dev/null +++ b/libarchive/test/test_acl_pax.c @@ -0,0 +1,521 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_acl_pax.c,v 1.4 2007/07/06 15:43:11 kientzle Exp $"); + +/* + * Exercise the system-independent portion of the ACL support. + * Check that pax archive can save and restore ACL data. + * + * This should work on all systems, regardless of whether local + * filesystems support ACLs or not. + */ + +static unsigned char buff[16384]; + +static unsigned char reference[] = { +'P','a','x','H','e','a','d','e','r','/','f','i','l','e',0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,'0','0','0','1','4','2',' ',0,'0','0','0','0','0','0',' ',0,'0','0', +'0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','0','6','2',' ','0', +'0','0','0','0','0','0','0','0','0','0',' ','0','1','1','7','6','7',0,' ', +'x',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r', +0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0', +'0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,'1','6',' ','S','C','H','I','L','Y','.','d','e','v','=','0',10, +'1','6',' ','S','C','H','I','L','Y','.','i','n','o','=','0',10,'1','8',' ', +'S','C','H','I','L','Y','.','n','l','i','n','k','=','0',10,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,'f','i','l','e',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,'0','0','0','1','4','2',' ',0,'0','0','0','0','0','0',' ',0,'0','0', +'0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','0','0','0',' ','0', +'0','0','0','0','0','0','0','0','0','0',' ','0','1','0','0','0','6',0,' ', +'0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r', +0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0', +'0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,'P','a','x','H','e','a','d','e','r','/','f','i','l','e',0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,'0','0','0','1','4','2',' ',0,'0','0','0','0','0','0',' ', +0,'0','0','0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','1','7','2', +' ','0','0','0','0','0','0','0','0','0','0','0',' ','0','1','1','7','7','1', +0,' ','x',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t', +'a','r',0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0', +'0','0','0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,'7','2',' ','S','C','H','I','L','Y','.','a','c','l','.', +'a','c','c','e','s','s','=','u','s','e','r',':',':','-','-','x',',','g','r', +'o','u','p',':',':','r','-','-',',','o','t','h','e','r',':',':','-','w','-', +',','u','s','e','r',':','u','s','e','r','7','7',':','r','-','-',':','7','7', +10,'1','6',' ','S','C','H','I','L','Y','.','d','e','v','=','0',10,'1','6', +' ','S','C','H','I','L','Y','.','i','n','o','=','0',10,'1','8',' ','S','C', +'H','I','L','Y','.','n','l','i','n','k','=','0',10,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,'f','i','l','e',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'0','0','0','1','4','2',' ',0,'0','0','0','0','0','0',' ',0,'0','0','0', +'0','0','0',' ',0,'0','0','0','0','0','0','0','0','0','0','0',' ','0','0', +'0','0','0','0','0','0','0','0','0',' ','0','1','0','0','0','6',0,' ','0', +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0, +'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0', +'0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,'P','a','x','H','e','a','d','e','r','/','f','i','l','e',0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,'0','0','0','5','4','3',' ',0,'0','0','0','0','0','0',' ', +0,'0','0','0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','2','4','3', +' ','0','0','0','0','0','0','0','0','0','0','0',' ','0','1','1','7','7','5', +0,' ','x',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t', +'a','r',0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0', +'0','0','0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,'1','1','3',' ','S','C','H','I','L','Y','.','a','c','l', +'.','a','c','c','e','s','s','=','u','s','e','r',':',':','r','-','x',',','g', +'r','o','u','p',':',':','r','-','-',',','o','t','h','e','r',':',':','-','w', +'x',',','g','r','o','u','p',':','g','r','o','u','p','7','8',':','r','w','x', +':','7','8',',','u','s','e','r',':','u','s','e','r','7','8',':','-','-','-', +':','7','8',',','u','s','e','r',':','u','s','e','r','7','7',':','r','-','-', +':','7','7',10,'1','6',' ','S','C','H','I','L','Y','.','d','e','v','=','0', +10,'1','6',' ','S','C','H','I','L','Y','.','i','n','o','=','0',10,'1','8', +' ','S','C','H','I','L','Y','.','n','l','i','n','k','=','0',10,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,'f','i','l','e',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,'0','0','0','5','4','3',' ',0,'0','0','0','0','0','0',' ',0,'0','0', +'0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','0','0','0',' ','0', +'0','0','0','0','0','0','0','0','0','0',' ','0','1','0','0','1','3',0,' ', +'0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r', +0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0', +'0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,'P','a','x','H','e','a','d','e','r','/','f','i','l','e',0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,'0','0','0','1','4','2',' ',0,'0','0','0','0','0','0',' ', +0,'0','0','0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','0','6','2', +' ','0','0','0','0','0','0','0','0','0','0','0',' ','0','1','1','7','6','7', +0,' ','x',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t', +'a','r',0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0', +'0','0','0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,'1','6',' ','S','C','H','I','L','Y','.','d','e','v','=', +'0',10,'1','6',' ','S','C','H','I','L','Y','.','i','n','o','=','0',10,'1', +'8',' ','S','C','H','I','L','Y','.','n','l','i','n','k','=','0',10,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'f','i','l','e',0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,'0','0','0','1','4','2',' ',0,'0','0','0','0','0','0',' ', +0,'0','0','0','0','0','0',' ',0,'0','0','0','0','0','0','0','0','0','0','0', +' ','0','0','0','0','0','0','0','0','0','0','0',' ','0','1','0','0','0','6', +0,' ','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t', +'a','r',0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0', +'0','0','0','0','0',' ',0,'0','0','0','0','0','0',' ',0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + +struct acl_t { + int type; /* Type of ACL: "access" or "default" */ + int permset; /* Permissions for this class of users. */ + int tag; /* Owner, User, Owning group, group, other, etc. */ + int qual; /* GID or UID of user/group, depending on tag. */ + const char *name; /* Name of user/group, depending on tag. */ +}; + +static struct acl_t acls0[] = { + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE, + ARCHIVE_ENTRY_ACL_USER_OBJ, 0, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_GROUP_OBJ, 0, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_WRITE, + ARCHIVE_ENTRY_ACL_OTHER, 0, "" }, +}; + +static struct acl_t acls1[] = { + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE, + ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_USER, 77, "user77" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_WRITE, + ARCHIVE_ENTRY_ACL_OTHER, -1, "" }, +}; + +static struct acl_t acls2[] = { + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_EXECUTE | ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_USER_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_USER, 77, "user77" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0, + ARCHIVE_ENTRY_ACL_USER, 78, "user78" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_READ, + ARCHIVE_ENTRY_ACL_GROUP_OBJ, -1, "" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, 0007, + ARCHIVE_ENTRY_ACL_GROUP, 78, "group78" }, + { ARCHIVE_ENTRY_ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_WRITE | ARCHIVE_ENTRY_ACL_EXECUTE, + ARCHIVE_ENTRY_ACL_OTHER, -1, "" }, +}; + +static void +set_acls(struct archive_entry *ae, struct acl_t *acls, int n) +{ + int i; + + archive_entry_acl_clear(ae); + for (i = 0; i < n; i++) { + archive_entry_acl_add_entry(ae, + acls[i].type, acls[i].permset, acls[i].tag, acls[i].qual, + acls[i].name); + } +} + +static int +acl_match(struct acl_t *acl, int type, int permset, int tag, int qual, const char *name) +{ + if (type != acl->type) + return (0); + if (permset != acl->permset) + return (0); + if (tag != acl->tag) + return (0); + if (tag == ARCHIVE_ENTRY_ACL_USER_OBJ) + return (1); + if (tag == ARCHIVE_ENTRY_ACL_GROUP_OBJ) + return (1); + if (tag == ARCHIVE_ENTRY_ACL_OTHER) + return (1); + if (qual != acl->qual) + return (0); + if (name == NULL) { + if (acl->name == NULL || acl->name[0] == '\0') + return (1); + } + if (acl->name == NULL) { + if (name[0] == '\0') + return (1); + } + return (0 == strcmp(name, acl->name)); +} + +static void +compare_acls(struct archive_entry *ae, struct acl_t *acls, int n, int mode) +{ + int *marker = malloc(sizeof(marker[0]) * n); + int i; + int r; + int type, permset, tag, qual; + int matched; + const char *name; + + for (i = 0; i < n; i++) + marker[i] = i; + + while (0 == (r = archive_entry_acl_next(ae, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name))) { + for (i = 0, matched = 0; i < n && !matched; i++) { + if (acl_match(&acls[marker[i]], type, permset, + tag, qual, name)) { + /* We found a match; remove it. */ + marker[i] = marker[n - 1]; + n--; + matched = 1; + } + } + if (tag == ARCHIVE_ENTRY_ACL_USER_OBJ) { + if (!matched) printf("No match for user_obj perm\n"); + failure("USER_OBJ permset (%02o) != user mode (%02o)", + permset, 07 & (mode >> 6)); + assert((permset << 6) == (mode & 0700)); + } else if (tag == ARCHIVE_ENTRY_ACL_GROUP_OBJ) { + if (!matched) printf("No match for group_obj perm\n"); + failure("GROUP_OBJ permset %02o != group mode %02o", + permset, 07 & (mode >> 3)); + assert((permset << 3) == (mode & 0070)); + } else if (tag == ARCHIVE_ENTRY_ACL_OTHER) { + if (!matched) printf("No match for other perm\n"); + failure("OTHER permset (%02o) != other mode (%02o)", + permset, mode & 07); + assert((permset << 0) == (mode & 0007)); + } else { + failure("Could not find match for ACL " + "(type=%d,permset=%d,tag=%d,qual=%d,name=``%s'')", + type, permset, tag, qual, name); + assert(matched == 1); + } + } +#if ARCHIVE_VERSION_STAMP < 1009000 + /* Known broken before 1.9.0. */ + skipping("archive_entry_acl_next() exits with ARCHIVE_EOF"); +#else + assertEqualInt(ARCHIVE_EOF, r); +#endif + assert((mode & 0777) == (archive_entry_mode(ae) & 0777)); + failure("Could not find match for ACL " + "(type=%d,permset=%d,tag=%d,qual=%d,name=``%s'')", + acls[marker[0]].type, acls[marker[0]].permset, + acls[marker[0]].tag, acls[marker[0]].qual, acls[marker[0]].name); + assert(n == 0); /* Number of ACLs not matched should == 0 */ + free(marker); +} + +DEFINE_TEST(test_acl_pax) +{ + struct archive *a; + struct archive_entry *ae; + size_t used; + int fd; + + /* Write an archive to memory. */ + assert(NULL != (a = archive_write_new())); + assertA(0 == archive_write_set_format_pax(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_set_bytes_per_block(a, 1)); + assertA(0 == archive_write_set_bytes_in_last_block(a, 1)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* Write a series of files to the archive with different ACL info. */ + + /* Create a simple archive_entry. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0777); + + /* Basic owner/owning group should just update mode bits. */ + set_acls(ae, acls0, sizeof(acls0)/sizeof(acls0[0])); + assertA(0 == archive_write_header(a, ae)); + + /* With any extended ACL entry, we should read back a full set. */ + set_acls(ae, acls1, sizeof(acls1)/sizeof(acls1[0])); + assertA(0 == archive_write_header(a, ae)); + + + /* A more extensive set of ACLs. */ + set_acls(ae, acls2, sizeof(acls2)/sizeof(acls2[0])); + assertA(0 == archive_write_header(a, ae)); + + /* + * Check that clearing ACLs gets rid of them all by repeating + * the first test. + */ + set_acls(ae, acls0, sizeof(acls0)/sizeof(acls0[0])); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Write out the data we generated to a file for manual inspection. */ + assert(-1 < (fd = open("testout", O_WRONLY | O_CREAT | O_TRUNC, 0775))); + assert(used == (size_t)write(fd, buff, used)); + close(fd); + + /* Write out the reference data to a file for manual inspection. */ + assert(-1 < (fd = open("reference", O_WRONLY | O_CREAT | O_TRUNC, 0775))); + assert(sizeof(reference) == write(fd, reference, sizeof(reference))); + close(fd); + + /* Assert that the generated data matches the built-in reference data.*/ + failure("Generated pax archive does not match reference; check 'testout' and 'reference' files."); + assert(0 == memcmp(buff, reference, sizeof(reference))); + failure("Generated pax archive does not match reference; check 'testout' and 'reference' files."); + assertEqualInt(used, sizeof(reference)); + + /* Read back each entry and check that the ACL data is right. */ + assert(NULL != (a = archive_read_new())); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + /* First item has no ACLs */ + assertA(0 == archive_read_next_header(a, &ae)); + failure("Basic ACLs shouldn't be stored as extended ACLs"); + assert(0 == archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + failure("Basic ACLs should set mode to 0142, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0142); + + /* Second item has a few ACLs */ + assertA(0 == archive_read_next_header(a, &ae)); + failure("One extended ACL should flag all ACLs to be returned."); + assert(4 == archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + compare_acls(ae, acls1, sizeof(acls1)/sizeof(acls1[0]), 0142); + failure("Basic ACLs should set mode to 0142, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0142); + + /* Third item has pretty extensive ACLs */ + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualInt(6, archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + compare_acls(ae, acls2, sizeof(acls2)/sizeof(acls2[0]), 0543); + failure("Basic ACLs should set mode to 0543, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0543); + + /* Fourth item has no ACLs */ + assertA(0 == archive_read_next_header(a, &ae)); + failure("Basic ACLs shouldn't be stored as extended ACLs"); + assert(0 == archive_entry_acl_reset(ae, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + failure("Basic ACLs should set mode to 0142, not %04o", + archive_entry_mode(ae)&0777); + assert((archive_entry_mode(ae) & 0777) == 0142); + + /* Close the archive. */ + assertA(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} diff --git a/libarchive/test/test_archive_api_feature.c b/libarchive/test/test_archive_api_feature.c new file mode 100644 index 00000000..cfc0b841 --- /dev/null +++ b/libarchive/test/test_archive_api_feature.c @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_archive_api_feature.c,v 1.4 2008/03/14 22:31:57 kientzle Exp $"); + +DEFINE_TEST(test_archive_api_feature) +{ + char buff[128]; + + /* This is the (hopefully) final versioning API. */ + assertEqualInt(ARCHIVE_VERSION_NUMBER, archive_version_number()); + sprintf(buff, "libarchive %d.%d.%d", + archive_version_number() / 1000000, + (archive_version_number() / 1000) % 1000, + archive_version_number() % 1000); + assertEqualString(buff, archive_version_string()); + +/* This is all scheduled to disappear in libarchive 3.0 */ +#if ARCHIVE_VERSION_NUMBER < 3000000 + assertEqualInt(ARCHIVE_VERSION_STAMP, ARCHIVE_VERSION_NUMBER); + assertEqualInt(ARCHIVE_API_FEATURE, archive_api_feature()); + assertEqualInt(ARCHIVE_API_VERSION, archive_api_version()); + /* + * Even though ARCHIVE_VERSION_STAMP only appears in + * archive.h after 1.9.0 and 2.2.3, the macro is synthesized + * in test.h, so this test is always valid. + */ + assertEqualInt(ARCHIVE_VERSION_STAMP / 1000, ARCHIVE_API_VERSION * 1000 + ARCHIVE_API_FEATURE); + /* + * The function, however, isn't always available. It appeared + * sometime in the middle of 2.2.3, but the synthesized value + * never has a release version, so the following conditional + * exactly determines whether the current library has the + * function. + */ +#if ARCHIVE_VERSION_STAMP / 1000 == 1009 || ARCHIVE_VERSION_STAMP > 2002000 + assertEqualInt(ARCHIVE_VERSION_STAMP, archive_version_stamp()); +#else + skipping("archive_version_stamp()"); +#endif + assertEqualString(ARCHIVE_LIBRARY_VERSION, archive_version()); +#endif +} diff --git a/libarchive/test/test_bad_fd.c b/libarchive/test/test_bad_fd.c new file mode 100644 index 00000000..1903cd07 --- /dev/null +++ b/libarchive/test/test_bad_fd.c @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_bad_fd.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +/* Verify that attempting to open an invalid fd returns correct error. */ +DEFINE_TEST(test_bad_fd) +{ + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(ARCHIVE_FATAL == archive_read_open_fd(a, -1, 1024)); + assertA(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} diff --git a/libarchive/test/test_compat_gtar.c b/libarchive/test/test_compat_gtar.c new file mode 100644 index 00000000..66cd7b02 --- /dev/null +++ b/libarchive/test/test_compat_gtar.c @@ -0,0 +1,110 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_compat_gtar.c,v 1.2 2008/03/12 05:12:23 kientzle Exp $"); + +/* + * Verify our ability to read sample files created by GNU tar. + * It should be easy to add any new sample files sent in by users + * to this collection of tests. + */ + +/* Copy this function for each test file and adjust it accordingly. */ + +/* + * test_compat_gtar_1.tgz exercises reading long filenames and + * symlink targets stored in the GNU tar format. + */ +static void +test_compat_gtar_1(void) +{ + char name[] = "test_compat_gtar_1.tgz"; + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 10240)); + + /* Read first entry. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString( + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890", + archive_entry_pathname(ae)); + assertEqualInt(1197179003, archive_entry_mtime(ae)); + assertEqualInt(1000, archive_entry_uid(ae)); + assertEqualString("tim", archive_entry_uname(ae)); + assertEqualInt(1000, archive_entry_gid(ae)); + assertEqualString("tim", archive_entry_gname(ae)); + assertEqualInt(0100644, archive_entry_mode(ae)); + + /* Read second entry. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString( + "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij" + "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij" + "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij" + "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij", + archive_entry_pathname(ae)); + assertEqualString( + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890", + archive_entry_symlink(ae)); + assertEqualInt(1197179043, archive_entry_mtime(ae)); + assertEqualInt(1000, archive_entry_uid(ae)); + assertEqualString("tim", archive_entry_uname(ae)); + assertEqualInt(1000, archive_entry_gid(ae)); + assertEqualString("tim", archive_entry_gname(ae)); + assertEqualInt(0120755, archive_entry_mode(ae)); + + /* Verify the end-of-archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify that the format detection worked. */ + assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_GZIP); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_TAR_GNUTAR); + + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + +DEFINE_TEST(test_compat_gtar) +{ + test_compat_gtar_1(); +} + + diff --git a/libarchive/test/test_compat_gtar_1.tgz.uu b/libarchive/test/test_compat_gtar_1.tgz.uu new file mode 100644 index 00000000..f088a4a5 --- /dev/null +++ b/libarchive/test/test_compat_gtar_1.tgz.uu @@ -0,0 +1,9 @@ +begin 644 test_compat_gtar_1.tgz +M'XL(`,N`6T<``^W62PZ",!`&X!YE3@`SI:6Z<R^7\(&*+Q+%>'W+PJB)43=4 +MJO^W:1.Z:#KYATG2)!T5]7Y95/N-Z@:UF)ZO7B9"-TPD[%@4%1W=Y\'IV$P. +M1.I0U\VK<^=566Y#7"@LT9FQN1L,.>[=M]\Q5@%JHX0Y-Z;-NSC+]^LM\S[R +M.G?,XC(B+:Q949"B7O/?5+N7Y]Y]CU32U_[OZS_NZ#X/T/][T\/1_\/K;?XQ +M_P4QF<[FY6*YJM9Q[[[]CK$*4!O_CV%G[6?SGS9^_C/&:I]_'6(X_?/Y#P`` +4````````````?L\%KFMT6@`H```` +` +end diff --git a/libarchive/test/test_compat_tar_hardlink.c b/libarchive/test/test_compat_tar_hardlink.c new file mode 100644 index 00000000..ae71e923 --- /dev/null +++ b/libarchive/test/test_compat_tar_hardlink.c @@ -0,0 +1,104 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_compat_tar_hardlink.c,v 1.2 2008/03/12 05:12:23 kientzle Exp $"); + +/* + * Background: There are two written standards for the tar file format. + * The first is the POSIX 1988 "ustar" format, the second is the 2001 + * "pax extended" format that builds on the "ustar" format by adding + * support for generic additional attributes. Buried in the details + * is one frustrating incompatibility: The 1988 standard says that + * tar readers MUST ignore the size field on hardlink entries; the + * 2001 standard says that tar readers MUST obey the size field on + * hardlink entries. libarchive tries to navigate this particular + * minefield by using auto-detect logic to guess whether it should + * or should not obey the size field. + * + * This test tries to probe the boundaries of such handling; the test + * archives here were adapted from real archives created by real + * tar implementations that are (as of early 2008) apparently still + * in use. + */ + +static void +test_compat_tar_hardlink_1(void) +{ + char name[] = "test_compat_tar_hardlink_1.tar"; + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 10240)); + + /* Read first entry, which is a regular file. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("xmcd-3.3.2/docs_d/READMf", + archive_entry_pathname(ae)); + assertEqualString(NULL, archive_entry_hardlink(ae)); + assertEqualInt(321, archive_entry_size(ae)); + assertEqualInt(1082575645, archive_entry_mtime(ae)); + assertEqualInt(1851, archive_entry_uid(ae)); + assertEqualInt(3, archive_entry_gid(ae)); + assertEqualInt(0100444, archive_entry_mode(ae)); + + /* Read second entry, which is a hard link at the end of archive. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("xmcd-3.3.2/README", + archive_entry_pathname(ae)); + assertEqualString( + "xmcd-3.3.2/docs_d/READMf", + archive_entry_hardlink(ae)); + assertEqualInt(0, archive_entry_size(ae)); + assertEqualInt(1082575645, archive_entry_mtime(ae)); + assertEqualInt(1851, archive_entry_uid(ae)); + assertEqualInt(3, archive_entry_gid(ae)); + assertEqualInt(0100444, archive_entry_mode(ae)); + + /* Verify the end-of-archive. */ + /* + * This failed in libarchive 2.4.12 because the tar reader + * tried to obey the size field for the hard link and ended + * up running past the end of the file. + */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify that the format detection worked. */ + assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_NONE); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_TAR); + + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); +} + +DEFINE_TEST(test_compat_tar_hardlink) +{ + test_compat_tar_hardlink_1(); +} + + diff --git a/libarchive/test/test_compat_tar_hardlink_1.tar.uu b/libarchive/test/test_compat_tar_hardlink_1.tar.uu new file mode 100644 index 00000000..95dba54c --- /dev/null +++ b/libarchive/test/test_compat_tar_hardlink_1.tar.uu @@ -0,0 +1,39 @@ +$FreeBSD: src/lib/libarchive/test/test_compat_tar_hardlink_1.tar.uu,v 1.1 2008/01/31 07:47:38 kientzle Exp $ +begin 644 test_compat_tar_hardlink_1.tar +M>&UC9"TS+C,N,B]D;V-S7V0O4D5!1$UF```````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````"`@(#0T-"``("`S-#<S(``@("`@(#,@`"`@("`@("`@-3`Q +M(#$P,#0Q-30U-#,U("`@-S8U-``@```````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````!X>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX +M>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'@````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````'AM8V0M,RXS+C(O +M4D5!1$U%```````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````@ +M("`T-#0@`"`@,S0W,R``("`@("`S(``@("`@("`@(#4P,2`Q,#`T,34T-30S +M-2`@,3(R,#<`(#%X;6-D+3,N,RXR+V1O8W-?9"]214%$368````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +&```````` +` +end diff --git a/libarchive/test/test_compat_zip.c b/libarchive/test/test_compat_zip.c new file mode 100644 index 00000000..fdeb9c64 --- /dev/null +++ b/libarchive/test/test_compat_zip.c @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_compat_zip.c,v 1.2 2008/03/12 05:12:23 kientzle Exp $"); + +/* Copy this function for each test file and adjust it accordingly. */ +static void +test_compat_zip_1(void) +{ + char name[] = "test_compat_zip_1.zip"; + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 10240)); + + /* Read first entry. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("META-INF/MANIFEST.MF", archive_entry_pathname(ae)); + + /* Read second entry. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("tmp.class", archive_entry_pathname(ae)); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_NONE); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_ZIP); + + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + +DEFINE_TEST(test_compat_zip) +{ + test_compat_zip_1(); +} + + diff --git a/libarchive/test/test_compat_zip_1.zip.uu b/libarchive/test/test_compat_zip_1.zip.uu new file mode 100644 index 00000000..e13a6cac --- /dev/null +++ b/libarchive/test/test_compat_zip_1.zip.uu @@ -0,0 +1,14 @@ +begin 644 test_compat_zip_1.zip +M4$L#!!0`"``(``B$@S<````````````````4````345402U)3D8O34%.249% +M4U0N34;S3<S+3$LM+M$-2RTJSLS/LU(PU#/@Y7+,0Q)Q+$A,SDA5`(H!)<U! +MTLY%J8DEJ2FZ3I56"BF9B4DY^;J&>J9Z!O$&YKI)!H8*&L&E>0J^F<E%^<65 +MQ26IN<4*GGG)>IJ\7+Q<`%!+!PAHTY\490```'$```!02P,$%``(``@`"(2# +M-P````````````````D```!T;7`N8VQA<W,[]6_7/@8&!D,&+G8&#G8&3BX& +M1@86'@8V!E9&!F8-S3!&!C:;S+S,$CN@L'-^2BHC@T!68EFB?DYB7KJ^?U)6 +M:G()4&%);@&#(@,34"\(,`(AT``@R0[D"8+Y#`RL6ML9F#>"%3```%!+!P@+ +M(*8V:````'8```!02P$"%``4``@`"``(A(,W:-.?%&4```!Q````%``````` +M````````````````345402U)3D8O34%.249%4U0N34902P$"%``4``@`"``( +MA(,W"R"F-F@```!V````"0````````````````"G````=&UP+F-L87-S4$L% +J!@`````"``(`>0```$8!```7`%!R;T=U87)D+"!V97)S:6]N(#0N,"XQ +` +end diff --git a/libarchive/test/test_empty_write.c b/libarchive/test/test_empty_write.c new file mode 100644 index 00000000..6a9f3f9c --- /dev/null +++ b/libarchive/test/test_empty_write.c @@ -0,0 +1,121 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_empty_write.c,v 1.2 2008/03/15 11:06:15 kientzle Exp $"); + +DEFINE_TEST(test_empty_write) +{ + char buff[32768]; + struct archive_entry *ae; + struct archive *a; + size_t used; + + /* + * Exercise a zero-byte write to a gzip-compressed archive. + */ + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_gzip(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + /* Write a file to it. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 0); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* THE TEST: write zero bytes to this entry. */ + /* This used to crash. */ + assertEqualIntA(a, 0, archive_write_data(a, "", 0)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + + /* + * Again, with bzip2 compression. + */ + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_bzip2(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + /* Write a file to it. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 0); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* THE TEST: write zero bytes to this entry. */ + assertEqualIntA(a, 0, archive_write_data(a, "", 0)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + + /* + * For good measure, one more time with no compression. + */ + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + /* Write a file to it. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 0); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* THE TEST: write zero bytes to this entry. */ + assertEqualIntA(a, 0, archive_write_data(a, "", 0)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif +} diff --git a/libarchive/test/test_entry.c b/libarchive/test/test_entry.c new file mode 100644 index 00000000..29edae7f --- /dev/null +++ b/libarchive/test/test_entry.c @@ -0,0 +1,761 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_entry.c,v 1.5 2008/03/14 23:19:46 kientzle Exp $"); + +#include <locale.h> + +/* + * Most of these tests are system-independent, though a few depend on + * features of the local system. Such tests are conditionalized on + * the platform name. On unsupported platforms, only the + * system-independent features will be tested. + * + * No, I don't want to use config.h in the test files because I want + * the tests to also serve as a check on the correctness of config.h. + * A mis-configured library build should cause tests to fail. + */ + +DEFINE_TEST(test_entry) +{ + char buff[128]; + wchar_t wbuff[128]; + struct stat st; + struct archive_entry *e, *e2; + const struct stat *pst; + unsigned long set, clear; /* For fflag testing. */ + int type, permset, tag, qual; /* For ACL testing. */ + const char *name; /* For ACL testing. */ + const char *xname; /* For xattr tests. */ + const void *xval; /* For xattr tests. */ + size_t xsize; /* For xattr tests. */ + int c; + + assert((e = archive_entry_new()) != NULL); + + /* + * Basic set/read tests for all fields. + * We should be able to set any field and read + * back the same value. + * + * For methods that "copy" a string, we should be able + * to overwrite the original passed-in string without + * changing the value in the entry. + * + * The following tests are ordered alphabetically by the + * name of the field. + */ + /* atime */ + archive_entry_set_atime(e, 13579, 24680); + assertEqualInt(archive_entry_atime(e), 13579); + assertEqualInt(archive_entry_atime_nsec(e), 24680); + /* ctime */ + archive_entry_set_ctime(e, 13580, 24681); + assertEqualInt(archive_entry_ctime(e), 13580); + assertEqualInt(archive_entry_ctime_nsec(e), 24681); +#if ARCHIVE_VERSION_STAMP >= 1009000 + /* dev */ + archive_entry_set_dev(e, 235); + assertEqualInt(archive_entry_dev(e), 235); +#else + skipping("archive_entry_dev()"); +#endif + /* devmajor/devminor are tested specially below. */ +#if ARCHIVE_VERSION_STAMP >= 1009000 + /* filetype */ + archive_entry_set_filetype(e, AE_IFREG); + assertEqualInt(archive_entry_filetype(e), AE_IFREG); +#else + skipping("archive_entry_filetype()"); +#endif + /* fflags are tested specially below */ + /* gid */ + archive_entry_set_gid(e, 204); + assertEqualInt(archive_entry_gid(e), 204); + /* gname */ + archive_entry_set_gname(e, "group"); + assertEqualString(archive_entry_gname(e), "group"); + wcscpy(wbuff, L"wgroup"); + archive_entry_copy_gname_w(e, wbuff); + assertEqualWString(archive_entry_gname_w(e), L"wgroup"); + memset(wbuff, 0, sizeof(wbuff)); + assertEqualWString(archive_entry_gname_w(e), L"wgroup"); + /* hardlink */ + archive_entry_set_hardlink(e, "hardlinkname"); + assertEqualString(archive_entry_hardlink(e), "hardlinkname"); + strcpy(buff, "hardlinkname2"); + archive_entry_copy_hardlink(e, buff); + assertEqualString(archive_entry_hardlink(e), "hardlinkname2"); + memset(buff, 0, sizeof(buff)); + assertEqualString(archive_entry_hardlink(e), "hardlinkname2"); + wcscpy(wbuff, L"whardlink"); + archive_entry_copy_hardlink_w(e, wbuff); + assertEqualWString(archive_entry_hardlink_w(e), L"whardlink"); + memset(wbuff, 0, sizeof(wbuff)); + assertEqualWString(archive_entry_hardlink_w(e), L"whardlink"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + /* ino */ + archive_entry_set_ino(e, 8593); + assertEqualInt(archive_entry_ino(e), 8593); +#else + skipping("archive_entry_ino()"); +#endif + + /* link */ + archive_entry_set_hardlink(e, "hardlinkname"); + archive_entry_set_symlink(e, NULL); + archive_entry_set_link(e, "link"); + assertEqualString(archive_entry_hardlink(e), "link"); + assertEqualString(archive_entry_symlink(e), NULL); + archive_entry_copy_link(e, "link2"); + assertEqualString(archive_entry_hardlink(e), "link2"); + assertEqualString(archive_entry_symlink(e), NULL); + archive_entry_copy_link_w(e, L"link3"); + assertEqualString(archive_entry_hardlink(e), "link3"); + assertEqualString(archive_entry_symlink(e), NULL); + archive_entry_set_hardlink(e, NULL); + archive_entry_set_symlink(e, "symlink"); + archive_entry_set_link(e, "link"); + assertEqualString(archive_entry_hardlink(e), NULL); + assertEqualString(archive_entry_symlink(e), "link"); + archive_entry_copy_link(e, "link2"); + assertEqualString(archive_entry_hardlink(e), NULL); + assertEqualString(archive_entry_symlink(e), "link2"); + archive_entry_copy_link_w(e, L"link3"); + assertEqualString(archive_entry_hardlink(e), NULL); + assertEqualString(archive_entry_symlink(e), "link3"); + /* Arbitrarily override hardlink if both hardlink and symlink set. */ + archive_entry_set_hardlink(e, "hardlink"); + archive_entry_set_symlink(e, "symlink"); + archive_entry_set_link(e, "link"); + assertEqualString(archive_entry_hardlink(e), "hardlink"); + assertEqualString(archive_entry_symlink(e), "link"); + + /* mode */ + archive_entry_set_mode(e, 0123456); + assertEqualInt(archive_entry_mode(e), 0123456); + /* mtime */ + archive_entry_set_mtime(e, 13581, 24682); + assertEqualInt(archive_entry_mtime(e), 13581); + assertEqualInt(archive_entry_mtime_nsec(e), 24682); +#if ARCHIVE_VERSION_STAMP >= 1009000 + /* nlink */ + archive_entry_set_nlink(e, 736); + assertEqualInt(archive_entry_nlink(e), 736); +#else + skipping("archive_entry_nlink()"); +#endif + /* pathname */ + archive_entry_set_pathname(e, "path"); + assertEqualString(archive_entry_pathname(e), "path"); + archive_entry_set_pathname(e, "path"); + assertEqualString(archive_entry_pathname(e), "path"); + strcpy(buff, "path2"); + archive_entry_copy_pathname(e, buff); + assertEqualString(archive_entry_pathname(e), "path2"); + memset(buff, 0, sizeof(buff)); + assertEqualString(archive_entry_pathname(e), "path2"); + wcscpy(wbuff, L"wpath"); + archive_entry_copy_pathname_w(e, wbuff); + assertEqualWString(archive_entry_pathname_w(e), L"wpath"); + memset(wbuff, 0, sizeof(wbuff)); + assertEqualWString(archive_entry_pathname_w(e), L"wpath"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + /* rdev */ + archive_entry_set_rdev(e, 532); + assertEqualInt(archive_entry_rdev(e), 532); +#else + skipping("archive_entry_rdev()"); +#endif + /* rdevmajor/rdevminor are tested specially below. */ + /* size */ + archive_entry_set_size(e, 987654321); + assertEqualInt(archive_entry_size(e), 987654321); + /* symlink */ + archive_entry_set_symlink(e, "symlinkname"); + assertEqualString(archive_entry_symlink(e), "symlinkname"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + strcpy(buff, "symlinkname2"); + archive_entry_copy_symlink(e, buff); + assertEqualString(archive_entry_symlink(e), "symlinkname2"); + memset(buff, 0, sizeof(buff)); + assertEqualString(archive_entry_symlink(e), "symlinkname2"); +#endif + archive_entry_copy_symlink_w(e, L"wsymlink"); + assertEqualWString(archive_entry_symlink_w(e), L"wsymlink"); + /* uid */ + archive_entry_set_uid(e, 83); + assertEqualInt(archive_entry_uid(e), 83); + /* uname */ + archive_entry_set_uname(e, "user"); + assertEqualString(archive_entry_uname(e), "user"); + wcscpy(wbuff, L"wuser"); + archive_entry_copy_gname_w(e, wbuff); + assertEqualWString(archive_entry_gname_w(e), L"wuser"); + memset(wbuff, 0, sizeof(wbuff)); + assertEqualWString(archive_entry_gname_w(e), L"wuser"); + + /* Test fflags interface. */ + archive_entry_set_fflags(e, 0x55, 0xAA); + archive_entry_fflags(e, &set, &clear); + failure("Testing set/get of fflags data."); + assertEqualInt(set, 0x55); + failure("Testing set/get of fflags data."); + assertEqualInt(clear, 0xAA); +#ifdef __FreeBSD__ + /* Converting fflags bitmap to string is currently system-dependent. */ + /* TODO: Make this system-independent. */ + assertEqualString(archive_entry_fflags_text(e), + "uappnd,nouchg,nodump,noopaque,uunlnk"); + /* TODO: Test archive_entry_copy_fflags_text_w() */ +#endif + + /* See test_acl_basic.c for tests of ACL set/get consistency. */ + + /* Test xattrs set/get consistency. */ + archive_entry_xattr_add_entry(e, "xattr1", "xattrvalue1", 12); + assertEqualInt(1, archive_entry_xattr_reset(e)); + assertEqualInt(0, archive_entry_xattr_next(e, &xname, &xval, &xsize)); + assertEqualString(xname, "xattr1"); + assertEqualString(xval, "xattrvalue1"); + assertEqualInt(xsize, 12); + assertEqualInt(1, archive_entry_xattr_count(e)); + assertEqualInt(ARCHIVE_WARN, + archive_entry_xattr_next(e, &xname, &xval, &xsize)); + assertEqualString(xname, NULL); + assertEqualString(xval, NULL); + assertEqualInt(xsize, 0); + archive_entry_xattr_clear(e); + assertEqualInt(0, archive_entry_xattr_reset(e)); + assertEqualInt(ARCHIVE_WARN, + archive_entry_xattr_next(e, &xname, &xval, &xsize)); + assertEqualString(xname, NULL); + assertEqualString(xval, NULL); + assertEqualInt(xsize, 0); + archive_entry_xattr_add_entry(e, "xattr1", "xattrvalue1", 12); + assertEqualInt(1, archive_entry_xattr_reset(e)); + archive_entry_xattr_add_entry(e, "xattr2", "xattrvalue2", 12); + assertEqualInt(2, archive_entry_xattr_reset(e)); + assertEqualInt(0, archive_entry_xattr_next(e, &xname, &xval, &xsize)); + assertEqualInt(0, archive_entry_xattr_next(e, &xname, &xval, &xsize)); + assertEqualInt(ARCHIVE_WARN, + archive_entry_xattr_next(e, &xname, &xval, &xsize)); + assertEqualString(xname, NULL); + assertEqualString(xval, NULL); + assertEqualInt(xsize, 0); + + + /* + * Test clone() implementation. + */ + + /* Set values in 'e' */ + archive_entry_clear(e); + archive_entry_set_atime(e, 13579, 24680); + archive_entry_set_ctime(e, 13580, 24681); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_dev(e, 235); +#endif + archive_entry_set_fflags(e, 0x55, 0xAA); + archive_entry_set_gid(e, 204); + archive_entry_set_gname(e, "group"); + archive_entry_set_hardlink(e, "hardlinkname"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_ino(e, 8593); +#endif + archive_entry_set_mode(e, 0123456); + archive_entry_set_mtime(e, 13581, 24682); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_nlink(e, 736); +#endif + archive_entry_set_pathname(e, "path"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_rdev(e, 532); +#endif + archive_entry_set_size(e, 987654321); + archive_entry_set_symlink(e, "symlinkname"); + archive_entry_set_uid(e, 83); + archive_entry_set_uname(e, "user"); + /* Add an ACL entry. */ + archive_entry_acl_add_entry(e, ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + ARCHIVE_ENTRY_ACL_READ, ARCHIVE_ENTRY_ACL_USER, 77, "user77"); + /* Add an extended attribute. */ + archive_entry_xattr_add_entry(e, "xattr1", "xattrvalue", 11); + + /* Make a clone. */ + e2 = archive_entry_clone(e); + + /* Clone should have same contents. */ + assertEqualInt(archive_entry_atime(e2), 13579); + assertEqualInt(archive_entry_atime_nsec(e2), 24680); + assertEqualInt(archive_entry_ctime(e2), 13580); + assertEqualInt(archive_entry_ctime_nsec(e2), 24681); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_dev(e2), 235); +#endif + archive_entry_fflags(e, &set, &clear); + assertEqualInt(clear, 0xAA); + assertEqualInt(set, 0x55); + assertEqualInt(archive_entry_gid(e2), 204); + assertEqualString(archive_entry_gname(e2), "group"); + assertEqualString(archive_entry_hardlink(e2), "hardlinkname"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_ino(e2), 8593); +#endif + assertEqualInt(archive_entry_mode(e2), 0123456); + assertEqualInt(archive_entry_mtime(e2), 13581); + assertEqualInt(archive_entry_mtime_nsec(e2), 24682); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_nlink(e2), 736); +#endif + assertEqualString(archive_entry_pathname(e2), "path"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_rdev(e2), 532); +#endif + assertEqualInt(archive_entry_size(e2), 987654321); + assertEqualString(archive_entry_symlink(e2), "symlinkname"); + assertEqualInt(archive_entry_uid(e2), 83); + assertEqualString(archive_entry_uname(e2), "user"); +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("ACL preserved by archive_entry_clone()"); +#else + /* Verify ACL was copied. */ + assertEqualInt(4, c = archive_entry_acl_reset(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + /* First three are standard permission bits. */ + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, 4); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_USER_OBJ); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, 5); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_GROUP_OBJ); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, 6); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_OTHER); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); + /* Fourth is custom one. */ + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, ARCHIVE_ENTRY_ACL_READ); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_USER); + assertEqualInt(qual, 77); + assertEqualString(name, "user77"); +#endif +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("xattr data preserved by archive_entry_clone"); +#else + /* Verify xattr was copied. */ + assertEqualInt(1, c = archive_entry_xattr_reset(e2)); + assertEqualInt(0, archive_entry_xattr_next(e2, &xname, &xval, &xsize)); + assertEqualString(xname, "xattr1"); + assertEqualString(xval, "xattrvalue"); + assertEqualInt(xsize, 11); + assertEqualInt(ARCHIVE_WARN, + archive_entry_xattr_next(e2, &xname, &xval, &xsize)); + assertEqualString(xname, NULL); + assertEqualString(xval, NULL); + assertEqualInt(xsize, 0); +#endif + + /* Change the original */ + archive_entry_set_atime(e, 13580, 24690); + archive_entry_set_ctime(e, 13590, 24691); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_dev(e, 245); +#endif + archive_entry_set_fflags(e, 0x85, 0xDA); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_filetype(e, AE_IFLNK); +#endif + archive_entry_set_gid(e, 214); + archive_entry_set_gname(e, "grouper"); + archive_entry_set_hardlink(e, "hardlinkpath"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_ino(e, 8763); +#endif + archive_entry_set_mode(e, 0123654); + archive_entry_set_mtime(e, 18351, 28642); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_nlink(e, 73); +#endif + archive_entry_set_pathname(e, "pathest"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_rdev(e, 132); +#endif + archive_entry_set_size(e, 987456321); + archive_entry_set_symlink(e, "symlinkpath"); + archive_entry_set_uid(e, 93); + archive_entry_set_uname(e, "username"); + archive_entry_acl_clear(e); + archive_entry_xattr_clear(e); + + /* Clone should still have same contents. */ + assertEqualInt(archive_entry_atime(e2), 13579); + assertEqualInt(archive_entry_atime_nsec(e2), 24680); + assertEqualInt(archive_entry_ctime(e2), 13580); + assertEqualInt(archive_entry_ctime_nsec(e2), 24681); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_dev(e2), 235); +#endif + archive_entry_fflags(e2, &set, &clear); + assertEqualInt(clear, 0xAA); + assertEqualInt(set, 0x55); + assertEqualInt(archive_entry_gid(e2), 204); + assertEqualString(archive_entry_gname(e2), "group"); + assertEqualString(archive_entry_hardlink(e2), "hardlinkname"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_ino(e2), 8593); +#endif + assertEqualInt(archive_entry_mode(e2), 0123456); + assertEqualInt(archive_entry_mtime(e2), 13581); + assertEqualInt(archive_entry_mtime_nsec(e2), 24682); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_nlink(e2), 736); +#endif + assertEqualString(archive_entry_pathname(e2), "path"); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_rdev(e2), 532); +#endif + assertEqualInt(archive_entry_size(e2), 987654321); + assertEqualString(archive_entry_symlink(e2), "symlinkname"); + assertEqualInt(archive_entry_uid(e2), 83); + assertEqualString(archive_entry_uname(e2), "user"); +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("ACL held by clone of archive_entry"); +#else + /* Verify ACL was unchanged. */ + assertEqualInt(4, c = archive_entry_acl_reset(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS)); + /* First three are standard permission bits. */ + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, 4); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_USER_OBJ); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, 5); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_GROUP_OBJ); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, 6); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_OTHER); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); + /* Fourth is custom one. */ + assertEqualInt(0, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + assertEqualInt(permset, ARCHIVE_ENTRY_ACL_READ); + assertEqualInt(tag, ARCHIVE_ENTRY_ACL_USER); + assertEqualInt(qual, 77); + assertEqualString(name, "user77"); + assertEqualInt(1, archive_entry_acl_next(e2, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + &type, &permset, &tag, &qual, &name)); + assertEqualInt(type, 0); + assertEqualInt(permset, 0); + assertEqualInt(tag, 0); + assertEqualInt(qual, -1); + assertEqualString(name, NULL); +#endif +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("xattr preserved in archive_entry copy"); +#else + /* Verify xattr was unchanged. */ + assertEqualInt(1, archive_entry_xattr_reset(e2)); +#endif + + /* Release clone. */ + archive_entry_free(e2); + + /* + * Test clear() implementation. + */ + archive_entry_clear(e); + assertEqualInt(archive_entry_atime(e), 0); + assertEqualInt(archive_entry_atime_nsec(e), 0); + assertEqualInt(archive_entry_ctime(e), 0); + assertEqualInt(archive_entry_ctime_nsec(e), 0); + assertEqualInt(archive_entry_dev(e), 0); + archive_entry_fflags(e, &set, &clear); + assertEqualInt(clear, 0); + assertEqualInt(set, 0); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_filetype(e), 0); +#endif + assertEqualInt(archive_entry_gid(e), 0); + assertEqualString(archive_entry_gname(e), NULL); + assertEqualString(archive_entry_hardlink(e), NULL); + assertEqualInt(archive_entry_ino(e), 0); + assertEqualInt(archive_entry_mode(e), 0); + assertEqualInt(archive_entry_mtime(e), 0); + assertEqualInt(archive_entry_mtime_nsec(e), 0); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_nlink(e), 0); +#endif + assertEqualString(archive_entry_pathname(e), NULL); + assertEqualInt(archive_entry_rdev(e), 0); + assertEqualInt(archive_entry_size(e), 0); + assertEqualString(archive_entry_symlink(e), NULL); + assertEqualInt(archive_entry_uid(e), 0); + assertEqualString(archive_entry_uname(e), NULL); + /* ACLs should be cleared. */ + assertEqualInt(archive_entry_acl_count(e, ARCHIVE_ENTRY_ACL_TYPE_ACCESS), 0); + assertEqualInt(archive_entry_acl_count(e, ARCHIVE_ENTRY_ACL_TYPE_DEFAULT), 0); + /* Extended attributes should be cleared. */ + assertEqualInt(archive_entry_xattr_count(e), 0); + + /* + * Test archive_entry_copy_stat(). + */ + memset(&st, 0, sizeof(st)); + /* Set all of the standard 'struct stat' fields. */ + st.st_atime = 456789; + st.st_ctime = 345678; + st.st_dev = 123; + st.st_gid = 34; + st.st_ino = 234; + st.st_mode = 077777; + st.st_mtime = 234567; + st.st_nlink = 345; + st.st_size = 123456789; + st.st_uid = 23; +#ifdef __FreeBSD__ + /* On FreeBSD, high-res timestamp data should come through. */ + st.st_atimespec.tv_nsec = 6543210; + st.st_ctimespec.tv_nsec = 5432109; + st.st_mtimespec.tv_nsec = 3210987; +#endif + /* Copy them into the entry. */ + archive_entry_copy_stat(e, &st); + /* Read each one back separately and compare. */ + assertEqualInt(archive_entry_atime(e), 456789); + assertEqualInt(archive_entry_ctime(e), 345678); + assertEqualInt(archive_entry_dev(e), 123); + assertEqualInt(archive_entry_gid(e), 34); + assertEqualInt(archive_entry_ino(e), 234); + assertEqualInt(archive_entry_mode(e), 077777); + assertEqualInt(archive_entry_mtime(e), 234567); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(archive_entry_nlink(e), 345); +#endif + assertEqualInt(archive_entry_size(e), 123456789); + assertEqualInt(archive_entry_uid(e), 23); +#if __FreeBSD__ + /* On FreeBSD, high-res timestamp data should come through. */ + assertEqualInt(archive_entry_atime_nsec(e), 6543210); + assertEqualInt(archive_entry_ctime_nsec(e), 5432109); + assertEqualInt(archive_entry_mtime_nsec(e), 3210987); +#endif + + /* + * Test archive_entry_stat(). + */ + /* First, clear out any existing stat data. */ + memset(&st, 0, sizeof(st)); + archive_entry_copy_stat(e, &st); + /* Set a bunch of fields individually. */ + archive_entry_set_atime(e, 456789, 321); + archive_entry_set_ctime(e, 345678, 432); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_dev(e, 123); +#endif + archive_entry_set_gid(e, 34); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_ino(e, 234); +#endif + archive_entry_set_mode(e, 012345); + archive_entry_set_mode(e, 012345); + archive_entry_set_mtime(e, 234567, 543); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_nlink(e, 345); +#endif + archive_entry_set_size(e, 123456789); + archive_entry_set_uid(e, 23); + /* Retrieve a stat structure. */ + assert((pst = archive_entry_stat(e)) != NULL); + /* Check that the values match. */ + assertEqualInt(pst->st_atime, 456789); + assertEqualInt(pst->st_ctime, 345678); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(pst->st_dev, 123); +#endif + assertEqualInt(pst->st_gid, 34); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(pst->st_ino, 234); +#endif + assertEqualInt(pst->st_mode, 012345); + assertEqualInt(pst->st_mtime, 234567); +#if ARCHIVE_VERSION_STAMP >= 1009000 + assertEqualInt(pst->st_nlink, 345); +#endif + assertEqualInt(pst->st_size, 123456789); + assertEqualInt(pst->st_uid, 23); +#ifdef __FreeBSD__ + /* On FreeBSD, high-res timestamp data should come through. */ + assertEqualInt(pst->st_atimespec.tv_nsec, 321); + assertEqualInt(pst->st_ctimespec.tv_nsec, 432); + assertEqualInt(pst->st_mtimespec.tv_nsec, 543); +#endif + + /* Changing any one value should update struct stat. */ + archive_entry_set_atime(e, 456788, 0); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_atime, 456788); + archive_entry_set_ctime(e, 345677, 431); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_ctime, 345677); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_dev(e, 122); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_dev, 122); +#endif + archive_entry_set_gid(e, 33); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_gid, 33); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_ino(e, 233); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_ino, 233); +#endif + archive_entry_set_mode(e, 012344); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_mode, 012344); + archive_entry_set_mtime(e, 234566, 542); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_mtime, 234566); +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_nlink(e, 344); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_nlink, 344); +#endif + archive_entry_set_size(e, 123456788); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_size, 123456788); + archive_entry_set_uid(e, 22); + assert((pst = archive_entry_stat(e)) != NULL); + assertEqualInt(pst->st_uid, 22); + /* We don't need to check high-res fields here. */ + + /* + * Test dev/major/minor interfaces. Setting 'dev' or 'rdev' + * should change the corresponding major/minor values, and + * vice versa. + * + * The test here is system-specific because it assumes that + * makedev(), major(), and minor() are defined in sys/stat.h. + * I'm not too worried about it, though, because the code is + * simple. If it works on FreeBSD, it's unlikely to be broken + * anywhere else. Note: The functionality is present on every + * platform even if these tests only run some places; + * libarchive's more extensive configuration logic should find + * the necessary definitions on every platform. + */ +#if __FreeBSD__ +#if ARCHIVE_VERSION_STAMP >= 1009000 + archive_entry_set_dev(e, 0x12345678); + assertEqualInt(archive_entry_devmajor(e), major(0x12345678)); + assertEqualInt(archive_entry_devminor(e), minor(0x12345678)); + assertEqualInt(archive_entry_dev(e), 0x12345678); + archive_entry_set_devmajor(e, 0xfe); + archive_entry_set_devminor(e, 0xdcba98); + assertEqualInt(archive_entry_devmajor(e), 0xfe); + assertEqualInt(archive_entry_devminor(e), 0xdcba98); + assertEqualInt(archive_entry_dev(e), makedev(0xfe, 0xdcba98)); + archive_entry_set_rdev(e, 0x12345678); + assertEqualInt(archive_entry_rdevmajor(e), major(0x12345678)); + assertEqualInt(archive_entry_rdevminor(e), minor(0x12345678)); + assertEqualInt(archive_entry_rdev(e), 0x12345678); + archive_entry_set_rdevmajor(e, 0xfe); + archive_entry_set_rdevminor(e, 0xdcba98); + assertEqualInt(archive_entry_rdevmajor(e), 0xfe); + assertEqualInt(archive_entry_rdevminor(e), 0xdcba98); + assertEqualInt(archive_entry_rdev(e), makedev(0xfe, 0xdcba98)); +#endif +#endif + + /* + * Exercise the character-conversion logic, if we can. + */ + failure("Can't exercise charset-conversion logic."); + if (assert(NULL != setlocale(LC_ALL, "de_DE.UTF-8"))) { + /* A filename that cannot be converted to wide characters. */ + archive_entry_copy_pathname(e, "abc\314\214mno\374xyz"); + failure("Converting invalid chars to Unicode should fail."); + assert(NULL == archive_entry_pathname_w(e)); + //failure("Converting invalid chars to UTF-8 should fail."); + //assert(NULL == archive_entry_pathname_utf8(e)); + + /* A group name that cannot be converted. */ + archive_entry_copy_gname(e, "abc\314\214mno\374xyz"); + failure("Converting invalid chars to Unicode should fail."); + assert(NULL == archive_entry_gname_w(e)); + + /* A user name that cannot be converted. */ + archive_entry_copy_uname(e, "abc\314\214mno\374xyz"); + failure("Converting invalid chars to Unicode should fail."); + assert(NULL == archive_entry_uname_w(e)); + + /* A hardlink target that cannot be converted. */ + archive_entry_copy_hardlink(e, "abc\314\214mno\374xyz"); + failure("Converting invalid chars to Unicode should fail."); + assert(NULL == archive_entry_hardlink_w(e)); + + /* A symlink target that cannot be converted. */ + archive_entry_copy_symlink(e, "abc\314\214mno\374xyz"); + failure("Converting invalid chars to Unicode should fail."); + assert(NULL == archive_entry_symlink_w(e)); + } + + /* Release the experimental entry. */ + archive_entry_free(e); +} diff --git a/libarchive/test/test_entry_strmode.c b/libarchive/test/test_entry_strmode.c new file mode 100644 index 00000000..2941c4a4 --- /dev/null +++ b/libarchive/test/test_entry_strmode.c @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_entry_strmode.c,v 1.1 2008/01/01 22:28:04 kientzle Exp $"); + +DEFINE_TEST(test_entry_strmode) +{ + struct archive_entry *entry; + + assert((entry = archive_entry_new()) != NULL); + + archive_entry_set_mode(entry, S_IFREG | 0642); + assertEqualString(archive_entry_strmode(entry), "-rw-r---w- "); + + archive_entry_set_mode(entry, S_IFBLK | 03642); + assertEqualString(archive_entry_strmode(entry), "brw-r-S-wT "); + + archive_entry_set_mode(entry, S_IFCHR | 05777); + assertEqualString(archive_entry_strmode(entry), "crwsrwxrwt "); + + archive_entry_set_mode(entry, S_IFLNK | 04000); + assertEqualString(archive_entry_strmode(entry), "l--S------ "); + + /* Release the experimental entry. */ + archive_entry_free(entry); +} diff --git a/libarchive/test/test_pax_filename_encoding.c b/libarchive/test/test_pax_filename_encoding.c new file mode 100644 index 00000000..b11be58a --- /dev/null +++ b/libarchive/test/test_pax_filename_encoding.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_pax_filename_encoding.c,v 1.1 2008/03/15 01:43:59 kientzle Exp $"); + +#include <locale.h> + +/* + * Pax interchange is supposed to encode filenames into + * UTF-8. Of course, that's not always possible. This + * test is intended to verify that filenames always get + * stored and restored correctly, regardless of the encodings. + */ + +DEFINE_TEST(test_pax_filename_encoding) +{ + static const char testname[] = "test_pax_filename_encoding.tar.gz"; + char buff[65536]; + /* + * \314\214 is a valid 2-byte UTF-8 sequence. + * \374 is invalid in UTF-8. + */ + char filename[] = "abc\314\214mno\374xyz"; + char longname[] = "abc\314\214mno\374xyz" + "/abc\314\214mno\374xyz/abcdefghijklmnopqrstuvwxyz" + "/abc\314\214mno\374xyz/abcdefghijklmnopqrstuvwxyz" + "/abc\314\214mno\374xyz/abcdefghijklmnopqrstuvwxyz" + "/abc\314\214mno\374xyz/abcdefghijklmnopqrstuvwxyz" + "/abc\314\214mno\374xyz/abcdefghijklmnopqrstuvwxyz" + "/abc\314\214mno\374xyz/abcdefghijklmnopqrstuvwxyz" + ; + size_t used; + struct archive *a; + struct archive_entry *entry; + + /* + * Read an archive that has non-UTF8 pax filenames in it. + */ + extract_reference_file(testname); + a = archive_read_new(); + assertEqualInt(ARCHIVE_OK, archive_read_support_format_tar(a)); + assertEqualInt(ARCHIVE_OK, archive_read_support_compression_gzip(a)); + assertEqualInt(ARCHIVE_OK, + archive_read_open_filename(a, testname, 10240)); + /* + * First entry in this test archive has an invalid UTF-8 sequence + * in it, but the header is not marked as hdrcharset=BINARY, so that + * requires a warning. + */ + failure("An invalid UTF8 pathname in a pax archive should be read\n" + " without conversion but with a warning"); + assertEqualInt(ARCHIVE_WARN, archive_read_next_header(a, &entry)); + assertEqualString(filename, archive_entry_pathname(entry)); + /* + * Second entry is identical except that it does have + * hdrcharset=BINARY, so no warning should be generated. + */ + failure("A pathname with hdrcharset=BINARY can have invalid UTF8\n" + " characters in it without generating a warning"); + assertEqualInt(ARCHIVE_OK, archive_read_next_header(a, &entry)); + assertEqualString(filename, archive_entry_pathname(entry)); + archive_read_finish(a); + + /* + * We need a starting locale which has invalid sequences. + * de_DE.UTF-8 seems to be commonly supported. + */ + /* If it doesn't exist, just warn and return. */ + failure("We need a suitable locale for the encoding tests."); + if (!assert(NULL != setlocale(LC_ALL, "de_DE.UTF-8"))) + return; + + assert((a = archive_write_new()) != NULL); + assertEqualIntA(a, 0, archive_write_set_format_pax(a)); + assertEqualIntA(a, 0, archive_write_set_compression_none(a)); + assertEqualIntA(a, 0, archive_write_set_bytes_per_block(a, 0)); + assertEqualInt(0, + archive_write_open_memory(a, buff, sizeof(buff), &used)); + + assert((entry = archive_entry_new()) != NULL); + /* Set pathname, gname, uname, hardlink to nonconvertible values. */ + archive_entry_copy_pathname(entry, filename); + archive_entry_copy_gname(entry, filename); + archive_entry_copy_uname(entry, filename); + archive_entry_copy_hardlink(entry, filename); + archive_entry_set_filetype(entry, AE_IFREG); + failure("This should generate a warning for nonconvertible names."); + assertEqualInt(ARCHIVE_WARN, archive_write_header(a, entry)); + archive_entry_free(entry); + + assert((entry = archive_entry_new()) != NULL); + /* Set path, gname, uname, and symlink to nonconvertible values. */ + archive_entry_copy_pathname(entry, filename); + archive_entry_copy_gname(entry, filename); + archive_entry_copy_uname(entry, filename); + archive_entry_copy_symlink(entry, filename); + archive_entry_set_filetype(entry, AE_IFLNK); + failure("This should generate a warning for nonconvertible names."); + assertEqualInt(ARCHIVE_WARN, archive_write_header(a, entry)); + archive_entry_free(entry); + + assert((entry = archive_entry_new()) != NULL); + /* Set pathname to a very long nonconvertible value. */ + archive_entry_copy_pathname(entry, longname); + archive_entry_set_filetype(entry, AE_IFREG); + failure("This should generate a warning for nonconvertible names."); + assertEqualInt(ARCHIVE_WARN, archive_write_header(a, entry)); + archive_entry_free(entry); + + assertEqualInt(0, archive_write_close(a)); + assertEqualInt(0, archive_write_finish(a)); + + /* + * Now read the entries back. + */ + + assert((a = archive_read_new()) != NULL); + assertEqualInt(0, archive_read_support_format_tar(a)); + assertEqualInt(0, archive_read_open_memory(a, buff, used)); + + assertEqualInt(0, archive_read_next_header(a, &entry)); + assertEqualString(filename, archive_entry_pathname(entry)); + assertEqualString(filename, archive_entry_gname(entry)); + assertEqualString(filename, archive_entry_uname(entry)); + assertEqualString(filename, archive_entry_hardlink(entry)); + + assertEqualInt(0, archive_read_next_header(a, &entry)); + assertEqualString(filename, archive_entry_pathname(entry)); + assertEqualString(filename, archive_entry_gname(entry)); + assertEqualString(filename, archive_entry_uname(entry)); + assertEqualString(filename, archive_entry_symlink(entry)); + + assertEqualInt(0, archive_read_next_header(a, &entry)); + assertEqualString(longname, archive_entry_pathname(entry)); + + assertEqualInt(0, archive_read_close(a)); + assertEqualInt(0, archive_read_finish(a)); +} + diff --git a/libarchive/test/test_pax_filename_encoding.tar.gz.uu b/libarchive/test/test_pax_filename_encoding.tar.gz.uu new file mode 100644 index 00000000..7191aac5 --- /dev/null +++ b/libarchive/test/test_pax_filename_encoding.tar.gz.uu @@ -0,0 +1,10 @@ +begin 644 test_pax_filename_encoding.tar.gz +M'XL(`)4;VT<``^V6STK#0!#&<\Y3[!/HS/Z-ASVHEQ1$BE[L<4T6$DP32:-$ +MG\%'\Y%Z,*7$UEJLE"91NK_+P.P>OF'X^&9LZM":V):GYCYZ?YOFQ5W]\NH= +M%`"0G).FHA*P7I>@E`1!22E!`9,$4#"0'JD/*V,[3[/*E(V4*IW^^&_7^W(4 +M\EG_"13)HZD2W6Y_WFS?IT"B9EZKD8(0+)"!6/3,EQZ5/BIR>QF.KB8GL7W6 +M0>!3VC;2O-"<H>#\S,>@[>99FC]H](>>VM'2G>M7[/(_@-CP/V-4>`2Z$K3. +MD?L_M%E6#"W",1CC;_D_[SW_*;+-_!><N?SO@R;_D[B,$E/.;*4O1M?G-Q/_ +L%T<!1\7V/@IP\<T=!7^![ER_8H_\%PI=_O>!RW^'P^$X3CX`98.>C@`4```` +` +end diff --git a/libarchive/test/test_read_compress_program.c b/libarchive/test/test_read_compress_program.c new file mode 100644 index 00000000..ec688e62 --- /dev/null +++ b/libarchive/test/test_read_compress_program.c @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_compress_program.c,v 1.2 2007/07/06 15:43:11 kientzle Exp $"); + +static unsigned char archive[] = { +31,139,8,0,222,'C','p','C',0,3,211,'c',160,'=','0','0','0','0','7','5','U', +0,210,134,230,166,6,200,'4',28,'(',24,26,24,27,155,24,152,24,154,27,155,')', +24,24,26,152,154,25,'2','(',152,210,193,'m',12,165,197,'%',137,'E','@',167, +148,'d',230,226,'U','G','H',30,234,15,'8','=',10,'F',193,'(',24,5,131,28, +0,0,29,172,5,240,0,6,0,0}; + +DEFINE_TEST(test_read_compress_program) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("archive_read_support_compression_program()"); +#else + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, 0, archive_read_support_compression_none(a)); + assertEqualIntA(a, 0, archive_read_support_compression_program(a, "gunzip")); + assert(0 == archive_read_support_format_all(a)); + assertEqualIntA(a, 0, archive_read_open_memory(a, archive, sizeof(archive))); + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_PROGRAM); + assert(archive_format(a) == ARCHIVE_FORMAT_TAR_USTAR); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +#endif +} + + diff --git a/libarchive/test/test_read_data_large.c b/libarchive/test/test_read_data_large.c new file mode 100644 index 00000000..a690b7b0 --- /dev/null +++ b/libarchive/test/test_read_data_large.c @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_data_large.c,v 1.3 2007/05/29 01:00:20 kientzle Exp $"); + +/* + * Test read/write of a 10M block of data in a single operation. + * Uses an in-memory archive with a single 10M entry. Exercises + * archive_read_data() to ensure it can handle large blocks like + * this and also exercises archive_read_data_into_fd() (which + * had a bug relating to this, fixed in Nov 2006). + */ + +char buff1[11000000]; +char buff2[10000000]; +char buff3[10000000]; + +DEFINE_TEST(test_read_data_large) +{ + struct archive_entry *ae; + struct archive *a; + char tmpfilename[] = "largefile"; + int tmpfilefd; + unsigned int i; + size_t used; + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff1, sizeof(buff1), &used)); + + /* + * Write a file (with random contents) to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + for (i = 0; i < sizeof(buff2); i++) + buff2[i] = (unsigned char)rand(); + archive_entry_set_size(ae, sizeof(buff2)); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(sizeof(buff2) == archive_write_data(a, buff2, sizeof(buff2))); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Check that archive_read_data can handle 10*10^6 at a pop. */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff1, sizeof(buff1))); + assertA(0 == archive_read_next_header(a, &ae)); + failure("Wrote 10MB, but didn't read the same amount"); + assertEqualIntA(a, sizeof(buff2),archive_read_data(a, buff3, sizeof(buff3))); + failure("Read expected 10MB, but data read didn't match what was written"); + assert(0 == memcmp(buff2, buff3, sizeof(buff3))); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Check archive_read_data_into_fd */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff1, sizeof(buff1))); + assertA(0 == archive_read_next_header(a, &ae)); + tmpfilefd = open(tmpfilename, O_WRONLY | O_CREAT, 0777); + assert(tmpfilefd != 0); + assertEqualIntA(a, 0, archive_read_data_into_fd(a, tmpfilefd)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + close(tmpfilefd); + + tmpfilefd = open(tmpfilename, O_RDONLY); + assert(tmpfilefd != 0); + assertEqualIntA(NULL, sizeof(buff3), read(tmpfilefd, buff3, sizeof(buff3))); + close(tmpfilefd); + assert(0 == memcmp(buff2, buff3, sizeof(buff3))); + + unlink(tmpfilename); +} diff --git a/libarchive/test/test_read_extract.c b/libarchive/test/test_read_extract.c new file mode 100644 index 00000000..bd42fd0c --- /dev/null +++ b/libarchive/test/test_read_extract.c @@ -0,0 +1,184 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_extract.c,v 1.3 2007/05/29 01:00:20 kientzle Exp $"); + +#define BUFF_SIZE 1000000 +#define FILE_BUFF_SIZE 100000 + +DEFINE_TEST(test_read_extract) +{ + struct archive_entry *ae; + struct archive *a; + struct stat st; + size_t used; + int i; + char *buff, *file_buff; + int fd; + ssize_t bytes_read; + + buff = malloc(BUFF_SIZE); + file_buff = malloc(FILE_BUFF_SIZE); + + /* Force the umask to something predictable. */ + umask(022); + + /* Create a new archive in memory containing various types of entries. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff, BUFF_SIZE, &used)); + /* A directory to be restored with EXTRACT_PERM. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir_0775"); + archive_entry_set_mode(ae, S_IFDIR | 0775); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* A regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + for (i = 0; i < FILE_BUFF_SIZE; i++) + file_buff[i] = (unsigned char)rand(); + archive_entry_set_size(ae, FILE_BUFF_SIZE); + assertA(0 == archive_write_header(a, ae)); + assertA(FILE_BUFF_SIZE == archive_write_data(a, file_buff, FILE_BUFF_SIZE)); + archive_entry_free(ae); + /* A directory that should obey umask when restored. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir"); + archive_entry_set_mode(ae, S_IFDIR | 0777); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* A file in the directory. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir/file"); + archive_entry_set_mode(ae, S_IFREG | 0700); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* A file in a dir that is not already in the archive. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir2/file"); + archive_entry_set_mode(ae, S_IFREG | 0000); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* A dir with a trailing /. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir3/."); + archive_entry_set_mode(ae, S_IFDIR | 0710); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* Multiple dirs with a single entry. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir4/a/../b/../c/"); + archive_entry_set_mode(ae, S_IFDIR | 0711); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* A symlink. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "symlink"); + archive_entry_set_mode(ae, S_IFLNK | 0755); + archive_entry_set_symlink(ae, "file"); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Extract the entries to disk. */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, BUFF_SIZE)); + /* Restore first entry with _EXTRACT_PERM. */ + failure("Error reading first entry", i); + assertA(0 == archive_read_next_header(a, &ae)); + assertA(0 == archive_read_extract(a, ae, ARCHIVE_EXTRACT_PERM)); + /* Rest of entries get restored with no flags. */ + for (i = 0; i < 7; i++) { + failure("Error reading entry %d", i+1); + assertA(0 == archive_read_next_header(a, &ae)); + assertA(0 == archive_read_extract(a, ae, 0)); + } + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Test the entries on disk. */ + assert(0 == stat("dir_0775", &st)); + failure("This was 0775 in archive, and should be 0775 on disk"); + assert(st.st_mode == (S_IFDIR | 0775)); + assert(0 == stat("file", &st)); + failure("st.st_mode=%o should be %o", st.st_mode, S_IFREG | 0755); + assert(st.st_mode == (S_IFREG | 0755)); + failure("The file extracted to disk is the wrong size."); + assert(st.st_size == FILE_BUFF_SIZE); + fd = open("file", O_RDONLY); + failure("The file on disk could not be opened."); + assert(fd != 0); + bytes_read = read(fd, buff, FILE_BUFF_SIZE); + failure("The file contents read from disk are the wrong size"); + assert(bytes_read == FILE_BUFF_SIZE); + failure("The file contents on disk do not match the file contents that were put into the archive."); + assert(memcmp(buff, file_buff, FILE_BUFF_SIZE) == 0); + assert(0 == stat("dir", &st)); + failure("This was 0777 in archive, but umask should make it 0755"); + assert(st.st_mode == (S_IFDIR | 0755)); + assert(0 == stat("dir/file", &st)); + assert(st.st_mode == (S_IFREG | 0700)); + assert(0 == stat("dir2", &st)); + assert(st.st_mode == (S_IFDIR | 0755)); + assert(0 == stat("dir2/file", &st)); + assert(st.st_mode == (S_IFREG | 0000)); + assert(0 == stat("dir3", &st)); + assert(st.st_mode == (S_IFDIR | 0710)); + assert(0 == stat("dir4", &st)); + assert(st.st_mode == (S_IFDIR | 0755)); + assert(0 == stat("dir4/a", &st)); + assert(st.st_mode == (S_IFDIR | 0755)); + assert(0 == stat("dir4/b", &st)); + assert(st.st_mode == (S_IFDIR | 0755)); + assert(0 == stat("dir4/c", &st)); + assert(st.st_mode == (S_IFDIR | 0711)); + assert(0 == lstat("symlink", &st)); + assert(S_ISLNK(st.st_mode)); +#if HAVE_LCHMOD + /* Systems that lack lchmod() can't set symlink perms, so skip this. */ + assert((st.st_mode & 07777) == 0755); +#endif + assert(0 == stat("symlink", &st)); + assert(st.st_mode == (S_IFREG | 0755)); + + free(buff); + free(file_buff); +} diff --git a/libarchive/test/test_read_format_ar.c b/libarchive/test/test_read_format_ar.c new file mode 100644 index 00000000..0c01a2bb --- /dev/null +++ b/libarchive/test/test_read_format_ar.c @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 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. + */ + +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_ar.c,v 1.5 2008/03/12 21:10:26 kaiw Exp $"); + +#if ARCHIVE_VERSION_STAMP >= 1009000 +/* + * This "archive" is created by "GNU ar". Here we try to verify + * our GNU format handling functionality. + */ +static unsigned char archive[] = { +'!','<','a','r','c','h','>',10,'/','/',' ',' ',' ',' ',' ',' ',' ', +' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', +' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', +' ',' ',' ',' ',' ','4','0',' ',' ',' ',' ',' ',' ',' ',' ','`',10, +'y','y','y','t','t','t','s','s','s','a','a','a','f','f','f','.','o', +'/',10,'h','h','h','h','j','j','j','j','k','k','k','k','l','l','l', +'l','.','o','/',10,10,'/','0',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', +' ',' ',' ',' ','1','1','7','5','4','6','5','6','5','2',' ',' ','1', +'0','0','1',' ',' ','0',' ',' ',' ',' ',' ','1','0','0','6','4','4', +' ',' ','8',' ',' ',' ',' ',' ',' ',' ',' ',' ','`',10,'5','5','6', +'6','7','7','8','8','g','g','h','h','.','o','/',' ',' ',' ',' ',' ', +' ',' ',' ',' ','1','1','7','5','4','6','5','6','6','8',' ',' ','1', +'0','0','1',' ',' ','0',' ',' ',' ',' ',' ','1','0','0','6','4','4', +' ',' ','4',' ',' ',' ',' ',' ',' ',' ',' ',' ','`',10,'3','3','3', +'3','/','1','9',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ', +'1','1','7','5','4','6','5','7','1','3',' ',' ','1','0','0','1',' ', +' ','0',' ',' ',' ',' ',' ','1','0','0','6','4','4',' ',' ','9',' ', +' ',' ',' ',' ',' ',' ',' ',' ','`',10,'9','8','7','6','5','4','3', +'2','1',10}; + +char buff[64]; +#endif + +DEFINE_TEST(test_read_format_ar) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("test_read_support_format_ar()"); +#else + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + + /* Filename table. */ + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("//", archive_entry_pathname(ae)); + assertEqualInt(0, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_uid(ae)); + assertEqualInt(0, archive_entry_gid(ae)); + assertEqualInt(0, archive_entry_size(ae)); + + /* First Entry */ + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("yyytttsssaaafff.o", archive_entry_pathname(ae)); + assertEqualInt(1175465652, archive_entry_mtime(ae)); + assertEqualInt(1001, archive_entry_uid(ae)); + assertEqualInt(0, archive_entry_gid(ae)); + assert(8 == archive_entry_size(ae)); + assertA(8 == archive_read_data(a, buff, 10)); + assert(0 == memcmp(buff, "55667788", 8)); + + /* Second Entry */ + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("gghh.o", archive_entry_pathname(ae)); + assertEqualInt(1175465668, archive_entry_mtime(ae)); + assertEqualInt(1001, archive_entry_uid(ae)); + assertEqualInt(0, archive_entry_gid(ae)); + assert(4 == archive_entry_size(ae)); + assertA(4 == archive_read_data(a, buff, 10)); + assert(0 == memcmp(buff, "3333", 4)); + + /* Third Entry */ + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("hhhhjjjjkkkkllll.o", archive_entry_pathname(ae)); + assertEqualInt(1175465713, archive_entry_mtime(ae)); + assertEqualInt(1001, archive_entry_uid(ae)); + assertEqualInt(0, archive_entry_gid(ae)); + assert(9 == archive_entry_size(ae)); + assertA(9 == archive_read_data(a, buff, 9)); + assert(0 == memcmp(buff, "987654321", 9)); + + /* Test EOF */ + assertA(1 == archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +#endif +} diff --git a/libarchive/test/test_read_format_cpio_bin.c b/libarchive/test/test_read_format_cpio_bin.c new file mode 100644 index 00000000..b188228f --- /dev/null +++ b/libarchive/test/test_read_format_cpio_bin.c @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_bin.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +199,'q',21,4,177,'y',237,'A',232,3,232,3,2,0,0,0,'p','C',244,'M',2,0,0,0, +0,0,'.',0,199,'q',0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,11,0,0,0,0,0,'T','R', +'A','I','L','E','R','!','!','!',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +DEFINE_TEST(test_read_format_cpio_bin) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertA(0 == archive_read_next_header(a, &ae)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_NONE); + assertA(archive_format(a) == ARCHIVE_FORMAT_CPIO_BIN_LE); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_cpio_bin_Z.c b/libarchive/test/test_read_format_cpio_bin_Z.c new file mode 100644 index 00000000..1dcebd33 --- /dev/null +++ b/libarchive/test/test_read_format_cpio_bin_Z.c @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_bin_Z.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,157,144,199,226,'T',' ',16,'+','O',187,' ',232,6,'$',20,0,160,'!',156, +'!',244,154,'0','l',216,208,5,128,128,20,'3','R',12,160,177,225,2,141,'T', +164,4,'I',194,164,136,148,16,'(',';',170,'\\',201,178,165,203,151,'0','c', +202,156,'I',179,166,205,155,'8','s',234,220,201,179,167,207,159,'@',127,2}; + +DEFINE_TEST(test_read_format_cpio_bin_Z) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertA(0 == archive_read_next_header(a, &ae)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_COMPRESS); + assertA(archive_format(a) == ARCHIVE_FORMAT_CPIO_BIN_LE); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_cpio_bin_bz2.c b/libarchive/test/test_read_format_cpio_bin_bz2.c new file mode 100644 index 00000000..e2841a7c --- /dev/null +++ b/libarchive/test/test_read_format_cpio_bin_bz2.c @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_bin_bz2.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +'B','Z','h','9','1','A','Y','&','S','Y',134,'J',208,'4',0,0,30,246,141,253, +8,2,0,' ',1,'*','&',20,0,'`',' ',' ',2,0,128,0,'B',4,8,' ',0,'T','P',0,'4', +0,13,6,137,168,245,27,'Q',160,'a',25,169,5,'I',187,'(',10,'d','E',177,177, +142,218,232,'r',130,'4','D',247,'<','Z',190,'U',237,236,'d',227,31,' ','z', +192,'E','_',23,'r','E','8','P',144,134,'J',208,'4'}; + +DEFINE_TEST(test_read_format_cpio_bin_bz2) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_BZIP2); + assert(archive_format(a) == ARCHIVE_FORMAT_CPIO_BIN_LE); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_cpio_bin_gz.c b/libarchive/test/test_read_format_cpio_bin_gz.c new file mode 100644 index 00000000..efabf319 --- /dev/null +++ b/libarchive/test/test_read_format_cpio_bin_gz.c @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_bin_gz.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,139,8,0,244,'M','p','C',0,3,';','^','(',202,178,177,242,173,227,11,230, +23,204,'L',12,12,12,5,206,'_','|','A','4',3,131,30,195,241,'B',6,'8','`', +132,210,220,'`','2','$',200,209,211,199,'5','H','Q','Q',145,'a',20,12,'i', +0,0,170,199,228,195,0,2,0,0}; + +DEFINE_TEST(test_read_format_cpio_bin_gz) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_GZIP); + assert(archive_format(a) == ARCHIVE_FORMAT_CPIO_BIN_LE); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_cpio_odc.c b/libarchive/test/test_read_format_cpio_odc.c new file mode 100644 index 00000000..1d80ef96 --- /dev/null +++ b/libarchive/test/test_read_format_cpio_odc.c @@ -0,0 +1,68 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_odc.c,v 1.2 2008/01/01 22:28:04 kientzle Exp $"); + +static unsigned char archive[] = { +'0','7','0','7','0','7','0','0','2','0','2','5','0','7','4','6','6','1','0', +'4','0','7','5','5','0','0','1','7','5','0','0','0','1','7','5','0','0','0', +'0','0','0','2','0','0','0','0','0','0','1','0','3','3','4','0','5','0','0', +'5','3','0','0','0','0','0','2','0','0','0','0','0','0','0','0','0','0','0', +'.',0,'0','7','0','7','0','7','0','0','0','0','0','0','0','0','0','0','0', +'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0', +'0','0','0','0','0','1','0','0','0','0','0','0','0','0','0','0','0','0','0', +'0','0','0','0','0','0','0','0','1','3','0','0','0','0','0','0','0','0','0', +'0','0','T','R','A','I','L','E','R','!','!','!',0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0}; + +DEFINE_TEST(test_read_format_cpio_odc) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_NONE); + assertA(archive_format(a) == ARCHIVE_FORMAT_CPIO_POSIX); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_cpio_svr4_gzip.c b/libarchive/test/test_read_format_cpio_svr4_gzip.c new file mode 100644 index 00000000..0bd8296d --- /dev/null +++ b/libarchive/test/test_read_format_cpio_svr4_gzip.c @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_svr4_gzip.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,139,8,0,236,'c',217,'D',0,3,'3','0','7','0','7','0','4','0','0',181,'0', +183,'L',2,210,6,6,'&',134,169,')',' ',218,192,'8',213,2,133,'6','0','0','2', +'1','6','7','0','5','0','N','6','@',5,'&',16,202,208,212,0,';','0',130,'1', +244,24,12,160,246,17,5,136,'U',135,14,146,'`',140,144,' ','G','O',31,215, +' ','E','E','E',134,'Q',128,21,0,0,'%',215,202,221,0,2,0,0}; + +DEFINE_TEST(test_read_format_cpio_svr4_gzip) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_GZIP); + assert(archive_format(a) == ARCHIVE_FORMAT_CPIO_SVR4_NOCRC); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_cpio_svr4c_Z.c b/libarchive/test/test_read_format_cpio_svr4c_Z.c new file mode 100644 index 00000000..d6158a8c --- /dev/null +++ b/libarchive/test/test_read_format_cpio_svr4c_Z.c @@ -0,0 +1,56 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_cpio_svr4c_Z.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,157,144,'0','n',4,132,'!',3,6,140,26,'8','n',228,16,19,195,160,'A',26, +'1',202,144,'q','h','p','F',25,28,20,'a','X',196,152,145,' ',141,25,2,'k', +192,160,'A',163,163,201,135,29,'c',136,'<',201,'2','c','A',147,'.',0,12,20, +248,178,165,205,155,20,27,226,220,201,243,166,152,147,'T',164,4,'I',194,164, +136,148,16,'H',1,'(',']',202,180,169,211,167,'P',163,'J',157,'J',181,170, +213,171,'X',179,'j',221,202,181,171,215,175,'L',1}; + +DEFINE_TEST(test_read_format_cpio_svr4c_Z) +{ + struct archive_entry *ae; + struct archive *a; +/* printf("Archive address: start=%X, end=%X\n", archive, archive+sizeof(archive)); */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertA(0 == archive_read_next_header(a, &ae)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_COMPRESS); + assertA(archive_format(a) == ARCHIVE_FORMAT_CPIO_SVR4_CRC); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_empty.c b/libarchive/test/test_read_format_empty.c new file mode 100644 index 00000000..f6f3e237 --- /dev/null +++ b/libarchive/test/test_read_format_empty.c @@ -0,0 +1,49 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_empty.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { }; + +DEFINE_TEST(test_read_format_empty) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_NONE); + assertA(archive_format(a) == ARCHIVE_FORMAT_EMPTY); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_gtar_gz.c b/libarchive/test/test_read_format_gtar_gz.c new file mode 100644 index 00000000..307d1d8c --- /dev/null +++ b/libarchive/test/test_read_format_gtar_gz.c @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_gtar_gz.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,139,8,0,'+','e',217,'D',0,3,211,211,'g',160,'9','0',0,2,'s','S','S',16, +'m','h','n','j',128,'L',195,0,131,161,129,177,177,137,129,137,185,185,161, +'!',131,129,161,129,153,161,'9',131,130,')',237,157,198,192,'P','Z','\\', +146,'X',164,160,192,'P',146,153,139,'W',29,'!','y',152,'G','`',244,'(',24, +5,163,'`',20,12,'r',0,0,226,234,'6',162,0,6,0,0}; + +DEFINE_TEST(test_read_format_gtar_gz) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_GZIP); + assert(archive_format(a) == ARCHIVE_FORMAT_TAR_GNUTAR); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_gtar_sparse.c b/libarchive/test/test_read_format_gtar_sparse.c new file mode 100644 index 00000000..b7efea5b --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse.c @@ -0,0 +1,321 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_gtar_sparse.c,v 1.8 2008/03/12 05:12:23 kientzle Exp $"); + + +struct contents { + off_t o; + size_t s; + const char *d; +}; + +struct contents archive_contents_sparse[] = { + { 1000000, 1, "a" }, + { 2000000, 1, "a" }, + { 3145728, 0, NULL } +}; + +struct contents archive_contents_sparse2[] = { + { 1000000, 1, "a" }, + { 2000000, 1, "a" }, + { 3000000, 1, "a" }, + { 4000000, 1, "a" }, + { 5000000, 1, "a" }, + { 6000000, 1, "a" }, + { 7000000, 1, "a" }, + { 8000000, 1, "a" }, + { 9000000, 1, "a" }, + { 10000000, 1, "a" }, + { 11000000, 1, "a" }, + { 12000000, 1, "a" }, + { 13000000, 1, "a" }, + { 14000000, 1, "a" }, + { 15000000, 1, "a" }, + { 16000000, 1, "a" }, + { 17000000, 1, "a" }, + { 18000000, 1, "a" }, + { 19000000, 1, "a" }, + { 20000000, 1, "a" }, + { 21000000, 1, "a" }, + { 22000000, 1, "a" }, + { 23000000, 1, "a" }, + { 24000000, 1, "a" }, + { 25000000, 1, "a" }, + { 26000000, 1, "a" }, + { 27000000, 1, "a" }, + { 28000000, 1, "a" }, + { 29000000, 1, "a" }, + { 30000000, 1, "a" }, + { 31000000, 1, "a" }, + { 32000000, 1, "a" }, + { 33000000, 1, "a" }, + { 34000000, 1, "a" }, + { 35000000, 1, "a" }, + { 36000000, 1, "a" }, + { 37000000, 1, "a" }, + { 38000000, 1, "a" }, + { 39000000, 1, "a" }, + { 40000000, 1, "a" }, + { 41000000, 1, "a" }, + { 42000000, 1, "a" }, + { 43000000, 1, "a" }, + { 44000000, 1, "a" }, + { 45000000, 1, "a" }, + { 46000000, 1, "a" }, + { 47000000, 1, "a" }, + { 48000000, 1, "a" }, + { 49000000, 1, "a" }, + { 50000000, 1, "a" }, + { 51000000, 1, "a" }, + { 52000000, 1, "a" }, + { 53000000, 1, "a" }, + { 54000000, 1, "a" }, + { 55000000, 1, "a" }, + { 56000000, 1, "a" }, + { 57000000, 1, "a" }, + { 58000000, 1, "a" }, + { 59000000, 1, "a" }, + { 60000000, 1, "a" }, + { 61000000, 1, "a" }, + { 62000000, 1, "a" }, + { 63000000, 1, "a" }, + { 64000000, 1, "a" }, + { 65000000, 1, "a" }, + { 66000000, 1, "a" }, + { 67000000, 1, "a" }, + { 68000000, 1, "a" }, + { 69000000, 1, "a" }, + { 70000000, 1, "a" }, + { 71000000, 1, "a" }, + { 72000000, 1, "a" }, + { 73000000, 1, "a" }, + { 74000000, 1, "a" }, + { 75000000, 1, "a" }, + { 76000000, 1, "a" }, + { 77000000, 1, "a" }, + { 78000000, 1, "a" }, + { 79000000, 1, "a" }, + { 80000000, 1, "a" }, + { 81000000, 1, "a" }, + { 82000000, 1, "a" }, + { 83000000, 1, "a" }, + { 84000000, 1, "a" }, + { 85000000, 1, "a" }, + { 86000000, 1, "a" }, + { 87000000, 1, "a" }, + { 88000000, 1, "a" }, + { 89000000, 1, "a" }, + { 90000000, 1, "a" }, + { 91000000, 1, "a" }, + { 92000000, 1, "a" }, + { 93000000, 1, "a" }, + { 94000000, 1, "a" }, + { 95000000, 1, "a" }, + { 96000000, 1, "a" }, + { 97000000, 1, "a" }, + { 98000000, 1, "a" }, + { 99000000, 1, "a" }, + { 99000001, 0, NULL } +}; + +struct contents archive_contents_nonsparse[] = { + { 0, 1, "a" }, + { 1, 0, NULL } +}; + +/* + * Describe an archive with three entries: + * + * File 1: named "sparse" + * * a length of 3145728 bytes (3MiB) + * * a single 'a' byte at offset 1000000 + * * a single 'a' byte at offset 2000000 + * File 2: named "sparse2" + * * a single 'a' byte at offset 1,000,000, 2,000,000, ..., 99,000,000 + * * length of 99,000,001 + * File 3: named 'non-sparse' + * * length of 1 byte + * * contains a single byte 'a' + */ + +struct archive_contents { + const char *filename; + struct contents *contents; +} files[] = { + { "sparse", archive_contents_sparse }, + { "sparse2", archive_contents_sparse2 }, + { "non-sparse", archive_contents_nonsparse }, + { NULL, NULL } +}; + +/* + * A tricky piece of code that verifies the contents of a sparse + * archive entry against a description as defined at the top of this + * source file. + */ +#define min(a,b) ((a) < (b) ? (a) : (b)) + +static void +verify_archive_file(const char *name, struct archive_contents *ac) +{ + struct archive_entry *ae; + int err; + /* data, size, offset of next expected block. */ + struct contents expect; + /* data, size, offset of block read from archive. */ + struct contents actual; + struct archive *a; + + extract_reference_file(name); + + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_tar(a)); + failure("Can't open %s", name); + assert(0 == archive_read_open_filename(a, name, 3)); + + while (ac->filename != NULL) { + struct contents *cts = ac->contents; + + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + failure("Name mismatch in archive %s", name); + assertEqualString(ac->filename, archive_entry_pathname(ae)); + + expect = *cts++; + while (0 == (err = archive_read_data_block(a, + (const void **)&actual.d, + &actual.s, &actual.o))) { + while (actual.s > 0) { + char c = *(const char *)actual.d; + if(actual.o < expect.o) { + /* + * Any byte before the expected + * data must be NULL. + */ + failure("%s: pad at offset %d " + "should be zero", name, actual.o); + assertEqualInt(c, 0); + } else if (actual.o == expect.o) { + /* + * Data at matching offsets must match. + */ + assertEqualInt(c, *expect.d); + expect.d++; + expect.o++; + expect.s--; + /* End of expected? step to next expected. */ + if (expect.s <= 0) + expect = *cts++; + } else { + /* + * We found data beyond that expected. + */ + failure("%s: Unexpected trailing data", + name); + assert(actual.o <= expect.o); + archive_read_finish(a); + return; + } + actual.d++; + actual.o++; + actual.s--; + } + } + failure("%s: should be end of entry", name); + assertEqualIntA(a, err, ARCHIVE_EOF); + failure("%s: Size returned at EOF must be zero", name); + assertEqualInt(actual.s, 0); +#if ARCHIVE_VERSION_STAMP < 1009000 + /* libarchive < 1.9 doesn't get this right */ + skipping("offset of final sparse chunk"); +#else + failure("%s: Offset of final empty chunk must be same as file size", name); + assertEqualInt(actual.o, expect.o); +#endif + /* Step to next file description. */ + ++ac; + } + + err = archive_read_next_header(a, &ae); + assertEqualIntA(a, ARCHIVE_EOF, err); + + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + +DEFINE_TEST(test_read_format_gtar_sparse) +{ + /* Two archives that use the "GNU tar sparse format". */ + verify_archive_file("test_read_format_gtar_sparse_1_13.tgz", files); + verify_archive_file("test_read_format_gtar_sparse_1_17.tgz", files); + + /* + * libarchive < 1.9 doesn't support the newer --posix sparse formats + * from GNU tar 1.15 and later. + */ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("read support for GNUtar --posix sparse formats"); +#else + /* + * An archive created by GNU tar 1.17 using --posix --sparse-format=0.1 + */ + verify_archive_file( + "test_read_format_gtar_sparse_1_17_posix00.tgz", + files); + /* + * An archive created by GNU tar 1.17 using --posix --sparse-format=0.1 + */ + verify_archive_file( + "test_read_format_gtar_sparse_1_17_posix01.tgz", + files); + /* + * An archive created by GNU tar 1.17 using --posix --sparse-format=1.0 + */ + verify_archive_file( + "test_read_format_gtar_sparse_1_17_posix10.tgz", + files); + /* + * The last test archive here is a little odd. First, it's + * uncompressed, because that exercises some of the block + * reassembly code a little harder. Second, it includes some + * leading comments prior to the sparse block description. + * GNU tar doesn't do this, but I think it should, so I want + * to ensure that libarchive correctly ignores such comments. + * Dump the file, looking for "#!gnu-sparse-format" starting + * at byte 0x600. + */ + verify_archive_file( + "test_read_format_gtar_sparse_1_17_posix10_modified.tar", + files); +#endif +} + + diff --git a/libarchive/test/test_read_format_gtar_sparse_1_13.tgz.uu b/libarchive/test/test_read_format_gtar_sparse_1_13.tgz.uu new file mode 100644 index 00000000..a298e59f --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse_1_13.tgz.uu @@ -0,0 +1,26 @@ +begin 644 test_read_format_gtar_sparse_1_13.tgz +M'XL(`&&";$<``^W72VX;1Q2%X<Y.N($`=>NYD*Q``P\\L&.(\OYSNP/)LGE` +M!SDF.D#^SP,G94&\7?VS']<O3\_7#]M#E2AE]KZ54F*-\O[O7<W_W:*LUJ)$ +M]%R/M>;:+G\\=JR_?;V^/#U?+MO+QT]W?^YG__ZO[5O09L\]>MN1M__.7:IB +M/=HZO*[O2W<_('?U\.NG?_KUOQ+_(_0#G.=ZW/_K0S_C[OT_>FW?W?]C*[5& +M:]S_[]S_6V]J?=72?US_K92Q8L1JZB%A?_YJI0_QV^I<98KU5D=?:CT/0AU% +MKW6HH^@S0AU%CCINCF)?'S/4G#/Z4'/.T4+-N2)F4>NYV6+./=XIYHS<M2KF +MS#UH4\R9:=<JYHS\S"GFC-I6$W-&77D^Q?H>A9JSK;INJW@[_]%;-#5OG_D8 +M+M9'G4W-.XZCOEV?M:FZ8\ZJZCXB5ON:AUW$G#4_N8LY:\F*Q)PUH@XQ9\WO +MB.HWFUBJWUHS8+'?Q]=)S9FG)]2</0]`S9F;4-6<?<VIYAQMJ'[K6.VVWV_G +M/\]F51T?7W\U[]K_J/5Y',:/ZRTG;F+>5F;^(K$>^]5?K8]UV_%^.8JI^CTN +M4V+.O,VTKN;,[2MJSHRWJSE[7@75G"/#4W/NJVK.F1LD]OGU<GJ[OM90<^9I +M#C5G[MJX<_[[OB-BWOSRA.JX9_2JXYY?:-7Q<?D7\_8,;XIY>^98Q;[VW*BI +MYNQYQ5!SYK=0]=M'7HC5G&-4U>]QFU)SSLQ+S9E?FZ;F7!F,F'/LZ8DY1^ZH +MZG<_]ZK?D1=ZU>]Q.[US_D=>F&Z?&O;U5=5SP=B?T]2\&:9Z+AAYHE7'>='N +MJN,\#4UU?-S^U9QSOP.H]?V/6#\>AMZM%_E@A'^$]W<XZ`<.^H&#?N"@'SCH +M!PZW'_J#@W[@H!\XZ`<.^H&#?N"@'SAX_\>9Z`<.^H&#?N"@'SCH!P[Z@8/W +M?YR)?N"@'SCH!P[Z@8-^X*`?.'C_QYGH!P[Z@8-^X*`?..@'#OJ!@_=_G(E^ +MX*`?..@'#OJ!@W[@H!\X>/_'F>@'#OJ!@W[@H!\XZ`<.^H&#]W^<B7[@H!\X +MZ`<.^H&#?N"@'SAX_\>9Z`<.^H&#?N"@'SCH!P[Z@8/W?YR)?N"@'SCH!P[Z +M@8-^X*`?.'C_QYGH!P[Z@8-^X*`?..@'#OJ!@_=_G(E^X*`?..@'#OJ!@W[@ +MH!\X>/_'F>@'#OJ!@WX\G__\_/OUR]/S]</C/J-$*;/WK902:Y3W?[_:HJS6 +MHD3TR)^/&F6[E,>-],W7Z\O3\^6RO7S\=/?G?O;O`````````/`?\Q>.)E`. +$`/`````` +` +end diff --git a/libarchive/test/test_read_format_gtar_sparse_1_17.tgz.uu b/libarchive/test/test_read_format_gtar_sparse_1_17.tgz.uu new file mode 100644 index 00000000..3f312712 --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse_1_17.tgz.uu @@ -0,0 +1,26 @@ +begin 644 test_read_format_gtar_sparse_1_17.tgz +M'XL(`&&";$<``^W776X3611%83,33Z"E>^[O0'H$?N"!!V@4A_GWJ8*$".\V +MK=Y8U1+KB\#H)L2G;BV77=?/EZ?K^]-#E31[WQYCC?+V<5/SSRG*:BU*1,_U +M6"O:Z?SG8\?ZZLOU^?)T/I^>/WR\^W,_^_Y_MFU!FSVV7?BV(Z__SEVJ8CTW +M:?>ROO_[WA.\_H=?[O+K?R5^(_1SK.M^_:\/?8Z[U__HM95X<_V/4ZDU6N7Z +M_\_7_]9Z4^NKEO[C^KM2QHH1:]MF]>RY_WV(WU;G*E.LMSKZ4NMY$.HH>JU# +M'46?$>HH<M1Q<Q3;^IBAYIS1AYISCA9JSA4QBUK/S19SYL.<8L[(7:MBSMR# +M-L6<F7:M8L[(YYQBSJAM-3%GU)7G4ZQO4:@YVZKKMHK7\Q^]15/S]KF6FG?4 +MV=2\8S_JV_59FZH[YJRJ[CUBM:]YV$7,6?.9NYBSEJQ(S%DCZA!SUGR-J'ZS +MB:7ZK34#%ON]OYS4G'EZ0LW9\P#4G+D)5<W9UYQJSM&&ZK>.U6[[_7[^\VQ6 +MU?'^\E?SKNU+K<_],'Y<;SEQ$_.V,O,7B?7(J[_8UY:[=-OQ=CF*J?K=+U-B +MSGR;:5W-F=M7U)P9;U=S]KP*JCE'AJ?FW%;5G#,W2.SSR^7T=GVMH>;,TQQJ +MSMRU<>?\]VU'Q+SYX@G5<<_H5<<]7]"JX_WR+^;M&=X4\_;,L8I][;E14\W9 +M\XJAYLQ7H>JWC[P0JSG'J*K?_6U*S3DS+S5GOFR:FG-E,&+.L:4GYARYHZK? +M[=RK?D=>Z%6_^]OIG?,_\L)T^ZEA6U]5?2X8V^<T-6^&J3X7C#S1JN.\:'?5 +M<9Z&ICK>W_[5G'-[!U#KVY=8WS\,O5DO\H,1_A7NW^"@'SCH!P[Z@8-^X*`? +M.-Q^Z`\.^H&#?N"@'SCH!P[Z@8-^X.#^'T>B'SCH!P[Z@8-^X*`?..@'#N[_ +M<23Z@8-^X*`?..@'#OJ!@W[@X/X?1Z(?..@'#OJ!@W[@H!\XZ`<.[O]Q)/J! +M@W[@H!\XZ`<.^H&#?N#@_A]'HA\XZ`<.^H&#?N"@'SCH!P[N_W$D^H&#?N"@ +M'SCH!P[Z@8-^X.#^'T>B'SCH!P[Z@8-^X*`?..@'#N[_<23Z@8-^X*`?..@' +M#OJ!@W[@X/X?1Z(?..@'#OJ!@W[@H!\XZ`<.[O]Q)/J!@W[@H!\XZ`<.^H&# +M?N#@_A]'HA\XZ`<.^O%\^NO3']?/EZ?K^\<]1TFS]^TQUBAO'U^<HJS6HD3T +M..7?M:S3N3QNI.^^7)\O3^?SZ?G#Q[L_][/O`P````````#P/_(W91GI)`#P +"```` +` +end diff --git a/libarchive/test/test_read_format_gtar_sparse_1_17_posix00.tgz.uu b/libarchive/test/test_read_format_gtar_sparse_1_17_posix00.tgz.uu new file mode 100644 index 00000000..41038960 --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse_1_17_posix00.tgz.uu @@ -0,0 +1,29 @@ +begin 644 test_read_format_gtar_sparse_1_17_posix00.tgz +M'XL(`&*";$<``^W9S6[;1A2&8:UU%;Z!RO/#^5MHW:R*;'H!K,,`06L[$&7` +M[=5W%#FN'(Q-Z1S5K-#W642!E&/3/M\$'\'5]<?^\</0?QHVX\KG&,KU^+7? +MC,/B?$P5NV[W:E,PAZ_?=,8MK$G>6V-MYQ;&^BZ9Q=7C&:_A50_CMM_42]%^ +MG:>?Y?GU0KAT]?,OOZ[V.U^-7_X:UMYV(;F\=/'PH[N'V]_^N+_Y?5S[I<N' +MG]Q__CP.VW6I?%R^_(*[J3^WP[@.UBU=:8S9.I:3.WGN^2I#<\XLG;GJMU]N +MA[6U);MZ:<;NWKMY^9Y9SKV!>9W]L#=,G'^W.R[_G/_ZOK7)U?/_+H?H^_FO +ML7CSWTU]?J'G7ZN?^P)PT<C/O%:O]3]WON_Q]O__UEN??NQ_(7KZWWMX6>6^ +M];]2]GMI5+)]`2SE/]$`W6[.Y-.;8YU+77?R7%?G8C8GSX7=G#W]]Q+K7`BG +M_U[R/H&GSPGW5Z;WYTUK[V9Z@>U!.[W!]J";7F%[T$_OL#W832^Q/1BGM_C* +MX/0:VX-)NL<LW6,1[M$9X1Z=%>[1.>$>72?<H^N$>W1!N$<7A7MT2;K'+-UC +M$>[1&^$>O1/NT3OA'KT7[M%WPCWZ(-RCC\(]^B3=8Q;N\>DXGGZIG1'NL3NB +MW[0'CR@X[<$C&DY[\(B*TQX\HN.T!X\H.>W!(UK.*X/2/4I[3I#VG"#M.4': +M<X*TYP1ISPG2GA.D/2=(>TZ0]IP@[3E1VG.BM.=$:<^)TIX3I3TG2GM.E/:< +M*.TY4=ISHK3G)&G/2=*>DZ0])TE[3I+VG"3M.4G:<Y*TYR1IS\G2GI.E/2=+ +M>TZ6]IPL[3E9VG.RM.=D:<_)TIZ3I3TG2WM.D?:<(NTY1=ISBK3G%&G/*=*> +M4Z0]ITA[3IGH.;;XQL-UUWBX;O_G#]<OP'ZY9WS8T_#F\Q_;.6_LP?,?NS#6 +MU;=Y_G\)>'X+#?(##?(##?(##?(##?(##6U^R!\TR`\TR`\TR`\TR`\TR`\T +MR`\TN/_'G,@/-,@/-,@/-,@/-,@/-,@/-+C_QYS(#S3(#S3(#S3(#S3(#S3( +M#S2X_\><R`\TR`\TR`\TR`\TR`\TR`\TN/_'G,@/-,@/-,@/-,@/-,@/-,@/ +M-+C_QYS(#S3(#S3(#S3(#S3(#S3(#S2X_\><R`\TR`\TR`\TR`\TR`\TR`\T +MN/_'G,@/-,@/-,@/-,@/-,@/-,@/-+C_QYS(#S3(#S3(#S3(#S3(#S3(#S2X +M_\><R`\TR`\TR`\TR`\TR`\TR`\TN/_'G,@/-,@/-,@/-,@/-,@/-,@/-+C_ +MQYS(#S3(#S3(C\[J^F/_^&'H/PV;<>5S#.7Z[O[NI_%KOQF',WT/4\6NV[W: +M%,SAZU[]NS7)>VNL[=S"U#]"6EP]GNG[O^EAW/:;>BG:K_/THSR_7@AGKOKM +ME]MA;6W)KOAH[+*^=_/C>W-?)_X=9S_L#9/GWQR>?UO/OPNFGO]W.43?SW\- +B_)O_;NKS"SW_`````````````````(#+]#?B%\M:`!@!```` +` +end diff --git a/libarchive/test/test_read_format_gtar_sparse_1_17_posix01.tgz.uu b/libarchive/test/test_read_format_gtar_sparse_1_17_posix01.tgz.uu new file mode 100644 index 00000000..6d7963ed --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse_1_17_posix01.tgz.uu @@ -0,0 +1,27 @@ +begin 644 test_read_format_gtar_sparse_1_17_posix01.tgz +M'XL(`&*";$<``^W7WVX:1QB&<8ZY"B[`Q?/-OYT]X+3-416IZ@5L'0ZLQHX% +MMF3UZKN`';]V&SO21[PB>GXG;,#F`_,,F5F>?QSN/ZR'3^O-=IE:K7:^O1DV +MV_7L>,*HYKR[M:X$O=V+)<XL="E9,,MQ%BSEFF>+^R.^AF^ZV]X.F_&E>)_G +MX;U\O3T1L5O\]ON?R\-GOMQ>_K->)<NEBVT>JSYT?7?UU^<O%W]O5^GE(\/5 +M>G6XGA?31ZZ&FU4_2O6L6#RS\;)U<7_],.0LS&-8#+>7XU.8]2V./QOB[KZ+ +MY_>%^=1_J9_3\GS\O/[8?UR_7GY>_Y"O@#?6?]PMEZ?U/]YOQ<:OA,6[+*+' +M]3_F]NK/O?7XB:Y_KV'J%X"31C_36GYK_Q>/-^.M[_]JNO]+N_U?*<;^[SW$ +M]I_]7]_OWX'-G^\-GS:`??_RH:<=8)Q;[.MW;@+C[CJTPX9PO.YRWE_G\;JV +ML+\NNVL[_&X=KTLY_&X[_)T/U_+\O3R_!1E@)A,LR@A+,L.R#+$J4ZSJV^AT +M3M,YO<R)0>9$DSDQRIR894[,,B<6_7M5F1,[G=-T3B]S4I`Y*<J<%&5.2KHY +MS_K!%)F3JLQ)G<YI,N?A[82'?\B<K)]_U@#RLP(T@:P-9(T@:P59,\C:0=$. +MBG90M(.B'13MH&@'13LHVD'1#HIV4+6#JAU4[:!J!U4[J-I!U0ZJ=E"U@ZH= +M=-I!IQUTVD&G'73:0:<==-I!IQUTVD'3#IIVT+2#IATT[:!I!TT[:,^^#)Y] +M&V@'33OHM8->.^BU@UX[Z+6#7COHM8->.^@?.[`^?>?YUB8ZW[YV_CO6%N#5 +M__\MQQ1,_O^WW?DOYLKY[Q2P?X<'_<"#?N!!/_"@'WC0#SR\_=`?/.@''O0# +M#_J!!_W`@W[@03_PX/R/*=$//.@''O0##_J!!_W`@W[@P?D?4Z(?>-`//.@' +M'O0##_J!!_W`@_,_ID0_\*`?>-`//.@''O0##_J!!^=_3(E^X$$_\*`?>-`/ +M/.@''O0##\[_F!+]P(-^X$$_\*`?>-`//.@''IS_,27Z@0?]P(-^X$$_\*`? +M>-`//#C_8TKT`P_Z@0?]P(-^X$$_\*`?>'#^QY3H!Q[T`P_Z@0?]P(-^X$$_ +M\.#\CRG1#SSH!Q[T`P_Z@0?]P(-^X,'Y'U.B'WC0#SSH!Q[T`P_Z@0?]P(/S +M/Z9$/_"@'WC0C\_R_.-P_V$]?%IOMLO4:K7SZR_7OVQOALUV?:09851SWMU: +M5X+>'HS7%KJ4+)CE-`N68['9XOY(\U]UM[T=-N-+\3[/PUOY>GLB8E@,MY=7 +MZY59WV*?:K#Y>-_%R_NF?IWX,8Z^V/_'F^L_Z/JW<?W'$KK9XET6T>/Z'X-_ +9]>?>>OQ$US\``````/CY_0O4#S!&`/`````` +` +end diff --git a/libarchive/test/test_read_format_gtar_sparse_1_17_posix10.tgz.uu b/libarchive/test/test_read_format_gtar_sparse_1_17_posix10.tgz.uu new file mode 100644 index 00000000..c74c08fb --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse_1_17_posix10.tgz.uu @@ -0,0 +1,27 @@ +begin 644 test_read_format_gtar_sparse_1_17_posix10.tgz +M'XL(`&.";$<``^W7RV[;5A1&88WU%'J!RN=^&7B:9E0$*/H`1,*!B]@))`<P +M\O2A)#O];=@RBJV8$+*^"1G*T=9E'8%G??%AN'L_#I_&S78=6RGQ8OMUV&S' +MQ>FX24EI=_0U.SWN!9\7WM48O?,^Q87S,96R6-V=\#6\Z-OV=MA,+\7Z//?O +MY>?Q3(2P^O.O?]:'[WQ]/?S[97/IET^N7MU,5]TR%+UZ,UR/EX?S9?3ZR&8< +M/F^OOH^7T:=<0UL&MQINKZ8_][ZWT&-Q87?MX^-K;CGWA_$;6E],7]S?^^_M +MW=7G\9?\!+RR_N-NN?RW_J?K/ON<%ZLW640/ZW]J\>C?O?;XF:[_N.R36);9 +MAZ6?3EL-^_.'M<NJQ!'#W"\`9XU^YK5^Z?X_G&[&Z_?_]>G]?\Z!^_^W\+_N +M_^L+]__3W4)X=@/0^_[#\,_L`.(S.P#/O<9;.W;_?ZJ?@*/K?[K-C,[+^O>[ +M^_^0(_?_;Z'WES8`87?NVF$S,)W7E/;G:3HOS>W/\^[<'_YOF<ZGG^W]>3M\ +M"(=S>?XNS^^=#/!>)O@@(WR4&3[)$%]DBB_Z-JK.:3JGRYS@9$[P,B<$F1.2 +MS`E)YH2LGU>1.:'JG*9SNLR)3N;$('-BD#DQZL8LZ1>394XL,B=6G=-DSOW; +M<??_D#E)O_^D`:1'!6@"21M(&D'2"I)FD+2#K!UD[2!K!UD[R-I!U@ZR=I"U +M@ZP=9.V@:`=%.RC:0=$.BG90M(.B'13MH&@'13NHVD'5#JIV4+6#JAU4[:!J +M!U4[J-I!TPZ:=M"T@Z8=-.V@:0=-.VB/?@P>_1IH!TT[Z-I!UPZZ=M"U@ZX= +M=.V@:P==.^@/'?@>N=^P8O\&"_J!!?W`@GY@03^PH!]86/NA/UC0#RSH!Q;T +M`POZ@07]P()^8,'^'W.B'UC0#RSH!Q;T`POZ@07]P(+]/^9$/["@'UC0#RSH +M!Q;T`POZ@07[?\R)?F!!/["@'UC0#RSH!Q;T`POV_Y@3_<""?F!!/["@'UC0 +M#RSH!Q;L_S$G^H$%_<""?F!!/["@'UC0#RS8_V-.]`,+^H$%_<""?F!!/["@ +M'UBP_\><Z`<6]`,+^H$%_<""?F!!/[!@_X\YT0\LZ`<6]`,+^H$%_<""?F#! +M_A]SHA]8T`\LZ`<6]`,+^H$%_<""_3_F1#^PH!]8T`\LZ`<6]`,+^H$%^W_, +MB7Y@03^PH!^;]<6'X>[].'P:-]MU;*7$BYLO-W]LOPZ;[7BB&6Y24MH=?<U. +MCP?3N7<U1N^\3W'A?`HY+E9W)YI_U+?M[;"97HKU>>[?RL_CF0AN-=Q>78^7 +MWO<6>BS.+Z=K'Y]>F_MUXM<X^6)_QJOKW^GZ]]/Z#]G5Q>I-%M'#^I^"/_IW +5KSU^INL?````P._C!\JB`&$`\``` +` +end diff --git a/libarchive/test/test_read_format_gtar_sparse_1_17_posix10_modified.tar.uu b/libarchive/test/test_read_format_gtar_sparse_1_17_posix10_modified.tar.uu new file mode 100644 index 00000000..cf8fd77c --- /dev/null +++ b/libarchive/test/test_read_format_gtar_sparse_1_17_posix10_modified.tar.uu @@ -0,0 +1,1369 @@ +begin 644 test_read_format_gtar_sparse_1_17_posix10_modified.tar +M+B]087A(96%D97)S+C,X-C8S+W-P87)S90`````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````#`P,#`V-#0`,#`P,3<U,``P,#`Q-S4P`#`P,#`P,#`P,C$U +M`#$P-S,S,3`Q,30S`#`Q,S0V-@`@>``````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````!U<W1A<@`P,``````` +M```````````````````````````````````````````````````````````` +M```````````````````P,#`P,#`P`#`P,#`P,#`````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````R,B!'3E4N<W!A<G-E+FUA:F]R/3$*,C(@1TY5 +M+G-P87)S92YM:6YO<CTP"C(V($=.52YS<&%R<V4N;F%M93US<&%R<V4*,S$@ +M1TY5+G-P87)S92YR96%L<VEZ93TS,30U-S(X"C(P(&%T:6UE/3$Q.3@R.3,V +M,#(*,C`@8W1I;64],3$Y.#(Y,S8P,`H````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````"XO1TY54W!A<G-E +M1FEL92XS.#8V,R]S<&%R<V4````````````````````````````````````` +M```````````````````````````````````````````````````````````P +M,#`P-C0T`#`P,#$W-3``,#`P,3<U,``P,#`P,#`P,S`P,``Q,#<S,S$P,3$T +M,``P,34Q-34`(#`````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````=7-T87(`,#!T:6T````````````````` +M`````````````````````'1I;0`````````````````````````````````` +M````,#`P,#`P,``P,#`P,#`P```````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````(R%G;G4M<W!A<G-E+69O<FUA=`HC9F]R;6%T.C$N,`HS"CDY.3DS +M-@HU,3(*,3DY.3@W,@HU,3(*,S$T-3<R.`HP"@`````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````````````80`` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````80`````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````+B]087A(96%D97)S+C,X-C8S+W-P87)S93(````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````#`P,#`V-#0`,#`P,3<U,``P,#`Q-S4P +M`#`P,#`P,#`P,C$W`#$P-S,S,3`Q,30S`#`Q,S4U,@`@>``````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````````!U +M<W1A<@`P,``````````````````````````````````````````````````` +M```````````````````````````````````P,#`P,#`P`#`P,#`P,#`````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````R,B!'3E4N<W!A<G-E+FUA +M:F]R/3$*,C(@1TY5+G-P87)S92YM:6YO<CTP"C(W($=.52YS<&%R<V4N;F%M +M93US<&%R<V4R"C,R($=.52YS<&%R<V4N<F5A;'-I>F4].3DP,#`P,#$*,C`@ +M871I;64],3$Y.#(Y,S8P,PHR,"!C=&EM93TQ,3DX,CDS-C`Q"@`````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`"XO1TY54W!A<G-E1FEL92XS.#8V,R]S<&%R<V4R```````````````````` +M```````````````````````````````````````````````````````````` +M```````````````P,#`P-C0T`#`P,#$W-3``,#`P,3<U,``P,#`P,#$T-3,P +M,0`Q,#<S,S$P,3$T,0`P,34R-3,`(#`````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````=7-T87(`,#!T:6T` +M`````````````````````````````````````'1I;0`````````````````` +M````````````````````,#`P,#`P,``P,#`P,#`P```````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````.3D*.3DY.3,V"C4Q,@HQ.3DY.#<R"C4Q,@HR +M.3DY.#`X"C4Q,@HS.3DY-S0T"C4Q,@HT.3DY-C@P"C4Q,@HU.3DY-C$V"C4Q +M,@HV.3DY-34R"C4Q,@HX,#`P,#`P"C4Q,@HX.3DY.3,V"C4Q,@HY.3DY.#<R +M"C4Q,@HQ,#DY.3@P.`HU,3(*,3$Y.3DW-#0*-3$R"C$R.3DY-C@P"C4Q,@HQ +M,SDY.38Q-@HU,3(*,30Y.3DU-3(*-3$R"C$V,#`P,#`P"C4Q,@HQ-CDY.3DS +M-@HU,3(*,3<Y.3DX-S(*-3$R"C$X.3DY.#`X"C4Q,@HQ.3DY.3<T-`HU,3(* +M,C`Y.3DV.#`*-3$R"C(Q.3DY-C$V"C4Q,@HR,CDY.34U,@HU,3(*,C0P,#`P +M,#`*-3$R"C(T.3DY.3,V"C4Q,@HR-3DY.3@W,@HU,3(*,C8Y.3DX,#@*-3$R +M"C(W.3DY-S0T"C4Q,@HR.#DY.38X,`HU,3(*,CDY.3DV,38*-3$R"C,P.3DY +M-34R"C4Q,@HS,C`P,#`P,`HU,3(*,S(Y.3DY,S8*-3$R"C,S.3DY.#<R"C4Q +M,@HS-#DY.3@P.`HU,3(*,S4Y.3DW-#0*-3$R"C,V.3DY-C@P"C4Q,@HS-SDY +M.38Q-@HU,3(*,S@Y.3DU-3(*-3$R"C0P,#`P,#`P"C4Q,@HT,#DY.3DS-@HU +M,3(*-#$Y.3DX-S(*-3$R"C0R.3DY.#`X"C4Q,@HT,SDY.3<T-`HU,3(*-#0Y +M.3DV.#`*-3$R"C0U.3DY-C$V"C4Q,@HT-CDY.34U,@HU,3(*-#@P,#`P,#`* +M-3$R"C0X.3DY.3,V"C4Q,@HT.3DY.3@W,@HU,3(*-3`Y.3DX,#@*-3$R"C4Q +M.3DY-S0T"C4Q,@HU,CDY.38X,`HU,3(*-3,Y.3DV,38*-3$R"C4T.3DY-34R +M"C4Q,@HU-C`P,#`P,`HU,3(*-38Y.3DY,S8*-3$R"C4W.3DY.#<R"C4Q,@HU +M.#DY.3@P.`HU,3(*-3DY.3DW-#0*-3$R"C8P.3DY-C@P"C4Q,@HV,3DY.38Q +M-@HU,3(*-C(Y.3DU-3(*-3$R"C8T,#`P,#`P"C4Q,@HV-#DY.3DS-@HU,3(* +M-C4Y.3DX-S(*-3$R"C8V.3DY.#`X"C4Q,@HV-SDY.3<T-`HU,3(*-C@Y.3DV +M.#`*-3$R"C8Y.3DY-C$V"C4Q,@HW,#DY.34U,@HU,3(*-S(P,#`P,#`*-3$R +M"C<R.3DY.3,V"C4Q,@HW,SDY.3@W,@HU,3(*-S0Y.3DX,#@*-3$R"C<U.3DY +M-S0T"C4Q,@HW-CDY.38X,`HU,3(*-S<Y.3DV,38*-3$R"C<X.3DY-34R"C4Q +M,@HX,#`P,#`P,`HU,3(*.#`Y.3DY,S8*-3$R"C@Q.3DY.#<R"C4Q,@HX,CDY +M.3@P.`HU,3(*.#,Y.3DW-#0*-3$R"C@T.3DY-C@P"C4Q,@HX-3DY.38Q-@HU +M,3(*.#8Y.3DU-3(*-3$R"C@X,#`P,#`P"C4Q,@HX.#DY.3DS-@HU,3(*.#DY +M.3DX-S(*-3$R"CDP.3DY.#`X"C4Q,@HY,3DY.3<T-`HU,3(*.3(Y.3DV.#`* +M-3$R"CDS.3DY-C$V"C4Q,@HY-#DY.34U,@HU,3(*.38P,#`P,#`*-3$R"CDV +M.3DY.3,V"C4Q,@HY-SDY.3@W,@HU,3(*.3@Y.3DX,#@*,3DS"@`````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````````````&$` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````&$````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````&$````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````&$````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````&$````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````````````&$` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````&$````````````` +M```````````````````````````````````````````````````````````` +M``````````!A```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````````!A +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````!A```````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````!A```````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````!A```````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````!A```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````````!A +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````!A```````````` +M```````````````````````````````````````````````````````````` +M````````````80`````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M80`````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````80`````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````80`````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````80`````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````80`````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M80`````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````80`````````` +M```````````````````````````````````````````````````````````` +M`````````````&$````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`&$````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````&$````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````&$````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````&$````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````&$````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`&$````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````&$````````` +M```````````````````````````````````````````````````````````` +M``````````````!A```````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``!A```````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````!A```````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````!A```````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````!A```````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````!A```````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``!A```````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````!A```````` +M```````````````````````````````````````````````````````````` +M````````````````80`````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````80`````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````````80`````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````80`````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````80`````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````80`````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````80`````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````````80`````` +M```````````````````````````````````````````````````````````` +M`````````````````&$````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````&$````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````````&$````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````&$````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````&$````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````&$````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````&$````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````````&$````` +M```````````````````````````````````````````````````````````` +M``````````````````!A```````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````!A```````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````!A```` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````!A```````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````!A```````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````!A```````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````!A```````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````!A```` +M```````````````````````````````````````````````````````````` +M````````````````````80`````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````80`````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````````````80`` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````80`````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````80`````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````80`````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````80`````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````````````80`` +M```````````````````````````````````````````````````````````` +M`````````````````````&$````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````&$````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````````````&$` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````&$````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````&$````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````&$````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````&$````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````````````&$` +M```````````````````````````````````````````````````````````` +M``````````````````````!A```````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````!A```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````````!A +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````!A```````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````!A```````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````!A```````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````!A```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````````````````````!A +M```````````````````````````````````````````````````````````` +M````````````````````````80`````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````80`````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M80`````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````80`````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````80`````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````80`````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````80`````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M80`````````````````````````````````````````````````````````` +M`````````````````````````&$````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````&$````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`&$````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````````````&$````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````````````````````````````````````+B]0 +M87A(96%D97)S+C,X-C8S+VYO;BUS<&%R<V4````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````#`P,#`V-#0`,#`P,3<U,``P,#`Q-S4P`#`P,#`P,#`P,#4P`#$P +M-S,S,3`Q,30S`#`Q-#(U,P`@>``````````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````!U<W1A<@`P,``````````` +M```````````````````````````````````````````````````````````` +M```````````````P,#`P,#`P`#`P,#`P,#`````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````R,"!A=&EM93TQ,3DX,CDS-C`Q"C(P(&-T:6UE/3$Q +M.3@R.3,V,#$*```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````````````````````&YO;BUS<&%R<V4````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````P,#`P +M-C0T`#`P,#$W-3``,#`P,3<U,``P,#`P,#`P,#`P,``Q,#<S,S$P,3$T,0`P +M,3(U,#<`(#`````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M````````````````````````=7-T87(`,#!T:6T````````````````````` +M`````````````````'1I;0`````````````````````````````````````` +M,#`P,#`P,``P,#`P,#`P```````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +/```````````````````` +` +end diff --git a/libarchive/test/test_read_format_iso_gz.c b/libarchive/test/test_read_format_iso_gz.c new file mode 100644 index 00000000..92ada131 --- /dev/null +++ b/libarchive/test/test_read_format_iso_gz.c @@ -0,0 +1,73 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_iso_gz.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,139,8,8,201,'R','p','C',0,3,'t','e','s','t','-','r','e','a','d','_','f', +'o','r','m','a','t','.','i','s','o',0,237,219,223,'k',211,'@',28,0,240,212, +23,'K','}',20,169,143,135,15,162,224,218,180,']','W',186,183,173,'I',183, +206,254,144,'d',19,246,'$',5,';',24,'2',11,235,240,239,221,127,162,233,'f', +17,29,219,24,12,'\'',243,243,'!',185,187,220,']',146,';',8,9,223,131,'D', +17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'P',234, +'%','q',220,'(','E',253,211,217,'l',';','O',194,'u','z','I','6',25,']',219, +26,194,234,'z',241,'o',217,13,247,'-',182,229,30,149,203,'Q',229,178,170, +242,252,'W',243,139,'e',242,'*',170,'^',30,'U',163,242,'2','+','G',199,207, +158,'V','_',190,';',127,178,':',255,134,1,241,23,140,222,15,242,'I','?',15, +'E',26,186,27,27,'q','}',183,'8',232,15,134,'i','~',152,239,167,163,176,'}', +'0',24,'&','i',22,'^','/',159,159,180,'7',201,146,162,176,150,213,147,143, +'E','!','K',183,246,'\'','Y','x',211,'{',27,26,221,'n','+',164,181,195,201, +193,'x','\'',217,26,166,171,202,'N',216,171,'}','H',183,178,'|','2',174,239, +213,242,222,238,'`','8',28,140,'w',30,'j',186,205,'8','n','7',26,'q',167, +217,'j',174,183,';','q','|','~',165,'"',254,'C','t',165,'G',')','z',168,209, +243,'o',184,143,215,'6',220,139,239,'?',191,255,0,192,255,163,136,224,194, +'h',254,'5',140,231,223,'B',232,132,'f','k',179,185,190,217,238,'\\',132, +':',149,147,'/',199,139,249,209,'"','4','k','q',173,21,214,230,225,'l',182, +'8','[',';',157,'M','?',127,':',154,159,158,'L',207,'j','E','{',152,'>',244, +28,0,128,187,')',']',172,177,139,255,1,0,0,224,'1',187,136,252,171,22,0,0, +0,0,224,'1',187,253,31,187,'[','{','X',';',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,206, +'~',0,137,'#',195,182,0,128,1,0}; + +DEFINE_TEST(test_read_format_iso_gz) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_GZIP); + assert(archive_format(a) == ARCHIVE_FORMAT_ISO9660); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_isorr_bz2.c b/libarchive/test/test_read_format_isorr_bz2.c new file mode 100644 index 00000000..107024f5 --- /dev/null +++ b/libarchive/test/test_read_format_isorr_bz2.c @@ -0,0 +1,183 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_isorr_bz2.c,v 1.3 2008/01/01 22:28:04 kientzle Exp $"); + +/* +Execute the following to rebuild the data for this program: + tail -n +5 test-read_format-isorr_bz2.c | /bin/sh + +rm -rf /tmp/iso +mkdir /tmp/iso +mkdir /tmp/iso/dir +echo "hello" >/tmp/iso/file +ln /tmp/iso/file /tmp/iso/hardlink +(cd /tmp/iso; ln -s file symlink) +TZ=utc touch -afhm -t 197001010000.01 /tmp/iso /tmp/iso/file /tmp/iso/dir +TZ=utc touch -afhm -t 196912312359.58 /tmp/iso/symlink +mkhybrid -R -uid 1 -gid 2 /tmp/iso | bzip2 > data.iso.bz2 +cat data.iso.bz2 | ./maketest.pl > data.c +exit 1 + */ + +static unsigned char archive[] = { +'B','Z','h','9','1','A','Y','&','S','Y','G',11,4,'c',0,0,199,255,221,255, +255,203,252,221,'c',251,248,'?',255,223,224,167,255,222,'&','!',234,'$',0, +'0',1,' ',0,'D',2,129,8,192,3,14,'2','3','$',19,184,'J',' ','F',168,244,201, +149,'6','Q',226,155,'S',212,209,160,'h','4','i',160,26,13,0,244,134,212,0, +218,'O',212,153,1,144,244,128,148,' ',147,13,' ',213,'=','1','\'',169,166, +128,'=','!',233,0,208,0,26,0,0,30,160,'h',0,'4','z',130,180,163,'@',0,0,4, +211,0,0,0,2,'b','`',0,0,0,0,0,8,146,133,'F',154,'y','A',163,'A',161,163,'@', +'z',134,'C','C','F',131,'F','@',0,0,0,0,6,154,26,'Q',24,234,180,'P',172,251, +'=',2,'P','H','&','Y','o',130,28,'"',229,210,247,227,248,200,'?','6',161, +'?',170,'H',172,'"','H','I',16,'2','"','&',148,'G',133,'T','z',224,1,215, +' ',0,191,184,10,160,24,248,180,183,244,156,'K',202,133,208,'U',5,'6','C', +26,144,'H',168,'H','H','(','"',151,'@','m',223,'(','P',169,'e',145,148,'6', +237,235,7,227,204,']','k','{',241,187,227,244,251,':','a','L',138,'#','R', +'"',221,'_',239,')',140,'*','*',172,'Q',16,1,16,207,166,251,233,'Z',169,'4', +'_',195,'a',14,18,231,'}',14,139,137,'e',213,185,'T',194,'D','`',25,'$',187, +208,'%','c',162,'~',181,'@',204,'2',238,'P',161,213,127,'I',169,3,' ','o', +6,161,16,128,'F',214,'S','m',6,244,11,229,'Z','y','.',176,'q',' ',248,167, +204,26,193,'q',211,241,214,133,221,212,'I','`',28,244,'N','N','f','H','9', +'w',245,209,'*',20,26,208,'h','(',194,156,192,'l',';',192,'X','T',151,177, +209,'0',156,16,'=',20,'k',184,144,'z',26,'j',133,194,'9',227,'<','[','^', +17,'w','p',225,220,248,'>',205,'>','[',19,'5',155,17,175,28,28,168,175,'n', +'\'','c','w',27,222,204,'k','n','x','I',23,237,'c',145,11,184,'A','(',1,169, +'0',180,189,134,'\\','Y','x',187,'C',151,'d','k','y','-','L',218,138,'s', +'*','(',12,'h',242,'*',17,'E','L',202,146,138,'l','0',217,160,'9','.','S', +214,198,143,'3','&',237,'=','t','P',168,214,210,'`','p','J',181,'H',138,149, +'1','B',206,22,164,'[','O','A',172,134,224,179,219,166,184,'X',185,'W',154, +219,19,161,'Y',184,220,237,147,'9',191,237,'&','i','_',226,146,205,160,'@', +'b',182,';',3,'!',183,'J','t',161,160,178,173,'S',235,':','2',159,':',245, +'{','U',174,'P',142,'G','(',')',9,168,185,'A','U',231,193,'g',213,'e',12, +'X',223,22,249,')',152,237,'G',150,156,3,201,245,212,'2',218,209,177,196, +235,'_','~',137,24,31,196,232,'B',172,'w',159,24,'n',156,150,225,'1','y', +22,'#',138,193,227,232,169,170,166,179,1,11,182,'i',')',160,180,198,175,128, +249,167,5,194,142,183,'f',134,206,180,'&','E','!','[',31,195,':',192,'s', +232,187,'N',131,'Y',137,243,15,'y',12,'J',163,'-',242,'5',197,151,130,163, +240,220,'T',161,'L',159,141,159,152,'4',18,128,'.','^',250,168,200,163,'P', +231,'Y','w','F','U',186,'x',190,16,'0',228,22,'9','F','t',168,157,'i',190, +'+',246,141,142,18,' ','M',174,197,'O',165,'m',224,27,'b',150,'|','W','H', +196,'.','*','Q','$',225,'I','-',148,169,'F',7,197,'m','-',130,153,0,158,21, +'(',221,221,226,206,'g',13,159,163,'y',176,'~',158,'k','4','q','d','s',177, +'7',14,217,'1',173,206,228,'t',250,200,170,162,'d','2','Z','$','e',168,224, +223,129,174,229,165,187,252,203,'-',28,'`',207,183,'-','/',127,196,230,131, +'B',30,237,' ',8,26,194,'O',132,'L','K','\\',144,'L','c',1,10,176,192,'c', +0,244,2,168,3,0,'+',233,186,16,17,'P',17,129,252,'2',0,2,154,247,255,166, +'.',228,138,'p',161,' ',142,22,8,198}; + +DEFINE_TEST(test_read_format_isorr_bz2) +{ + struct archive_entry *ae; + struct archive *a; + const void *p; + size_t size; + off_t offset; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + + /* First entry is '.' root directory. */ + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString(".", archive_entry_pathname(ae)); + assert(S_ISDIR(archive_entry_stat(ae)->st_mode)); + assertEqualInt(2048, archive_entry_size(ae)); + assertEqualInt(1, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_mtime_nsec(ae)); + assertEqualInt(1, archive_entry_ctime(ae)); + assertEqualInt(0, archive_entry_stat(ae)->st_nlink); + assertEqualInt(0, archive_entry_uid(ae)); + assertEqualIntA(a, ARCHIVE_EOF, + archive_read_data_block(a, &p, &size, &offset)); + assertEqualInt(size, 0); + + /* A directory. */ + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("dir", archive_entry_pathname(ae)); + assert(S_ISDIR(archive_entry_stat(ae)->st_mode)); + assert(2048 == archive_entry_size(ae)); + assert(1 == archive_entry_mtime(ae)); + assert(1 == archive_entry_atime(ae)); + assert(2 == archive_entry_stat(ae)->st_nlink); + assert(1 == archive_entry_uid(ae)); + assert(2 == archive_entry_gid(ae)); + + /* A regular file. */ + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("file", archive_entry_pathname(ae)); + assert(S_ISREG(archive_entry_stat(ae)->st_mode)); + assert(6 == archive_entry_size(ae)); + assert(0 == archive_read_data_block(a, &p, &size, &offset)); + assert(6 == size); + assert(0 == offset); + assert(0 == memcmp(p, "hello\n", 6)); + assert(1 == archive_entry_mtime(ae)); + assert(1 == archive_entry_atime(ae)); + assert(2 == archive_entry_stat(ae)->st_nlink); + assert(1 == archive_entry_uid(ae)); + assert(2 == archive_entry_gid(ae)); + + /* A hardlink to the regular file. */ + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("hardlink", archive_entry_pathname(ae)); + assert(S_ISREG(archive_entry_stat(ae)->st_mode)); + assertEqualString("file", archive_entry_hardlink(ae)); + assert(6 == archive_entry_size(ae)); + assert(1 == archive_entry_mtime(ae)); + assert(1 == archive_entry_atime(ae)); + assert(2 == archive_entry_stat(ae)->st_nlink); + assert(1 == archive_entry_uid(ae)); + assert(2 == archive_entry_gid(ae)); + + /* A symlink to the regular file. */ + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("symlink", archive_entry_pathname(ae)); + assert(S_ISLNK(archive_entry_stat(ae)->st_mode)); + assertEqualString("file", archive_entry_symlink(ae)); + assert(0 == archive_entry_size(ae)); + assert(-2 == archive_entry_mtime(ae)); + assert(-2 == archive_entry_atime(ae)); + assert(1 == archive_entry_stat(ae)->st_nlink); + assert(1 == archive_entry_uid(ae)); + assert(2 == archive_entry_gid(ae)); + + /* End of archive. */ + assert(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + + /* Verify archive format. */ + assert(archive_compression(a) == ARCHIVE_COMPRESSION_BZIP2); + assert(archive_format(a) == ARCHIVE_FORMAT_ISO9660_ROCKRIDGE); + + /* Close the archive. */ + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_mtree.c b/libarchive/test/test_read_format_mtree.c new file mode 100644 index 00000000..e5165d31 --- /dev/null +++ b/libarchive/test/test_read_format_mtree.c @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_mtree.c,v 1.1 2008/01/01 22:28:04 kientzle Exp $"); + +/* Single entry with a hardlink. */ +static unsigned char archive[] = { + "#mtree\n" + "file type=file uid=18 mode=0123\n" + "dir type=dir\n" + " file\\040with\\040space type=file uid=18\n" + " ..\n" + "file\\04with\\040space\n" + "dir2 type=dir\n" + " dir3a type=dir\n" + " indir3a\n" + "dir2/fullindir2 type=file mode=0777\n" + " ..\n" + " indir2\n" + " dir3b type=dir\n" + " indir3b\n" + " ..\n" + " ..\n" + "notindir\n" + "dir2/fullindir2 mode=0644\n" +}; + +DEFINE_TEST(test_read_format_mtree) +{ + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_open_memory(a, archive, sizeof(archive))); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_MTREE_V1); + assertEqualString(archive_entry_pathname(ae), "file"); + assertEqualInt(archive_entry_uid(ae), 18); + assert(S_ISREG(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0123); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir"); + assert(S_ISDIR(archive_entry_mode(ae))); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir/file with space"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "file\\04with space"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3a"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3a/indir3a"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/fullindir2"); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/indir2"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3b"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3b/indir3b"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "notindir"); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_pax_bz2.c b/libarchive/test/test_read_format_pax_bz2.c new file mode 100644 index 00000000..186d5f9d --- /dev/null +++ b/libarchive/test/test_read_format_pax_bz2.c @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_pax_bz2.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +'B','Z','h','9','1','A','Y','&','S','Y',152,180,30,185,0,0,140,127,176,212, +144,0,' ','@',1,255,226,8,'d','H',' ',238,'/',159,'@',0,16,4,'@',0,8,'0', +0,216,'A',164,167,147,'Q',147,'!',180,'#',0,'L',153,162,'i',181,'?','P',192, +26,'h','h',209,136,200,6,128,13,12,18,132,202,'5','O',209,'5','=',26,'2', +154,7,168,12,2,'d',252,13,254,29,'4',247,181,'l','T','i',130,5,195,1,'2', +'@',146,18,251,245,'c','J',130,224,172,'$','l','4',235,170,186,'c','1',255, +179,'K',188,136,18,208,152,192,149,153,10,'{','|','0','8',166,3,6,9,128,172, +'(',164,220,244,149,6,' ',243,212,'B',25,17,'6',237,13,'I',152,'L',129,209, +'G','J','<',137,'Y',16,'b',21,18,'a','Y','l','t','r',160,128,147,'l','f', +'~',219,206,'=','?','S',233,'3',251,'L','~',17,176,169,'%',23,'_',225,'M', +'C','u','k',218,8,'q',216,'(',22,235,'K',131,136,146,136,147,202,0,158,134, +'F',23,160,184,'s','0','a',246,'*','P',7,2,238,'H',167,10,18,19,22,131,215, +' '}; + +DEFINE_TEST(test_read_format_pax_bz2) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_BZIP2); + assert(archive_format(a) == ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_tar.c b/libarchive/test/test_read_format_tar.c new file mode 100644 index 00000000..1de08495 --- /dev/null +++ b/libarchive/test/test_read_format_tar.c @@ -0,0 +1,479 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_tar.c,v 1.3 2008/01/13 23:50:30 kientzle Exp $"); + +/* + * Each of these archives is a short archive with a single entry. The + * corresponding verify function verifies the entry structure returned + * from libarchive is what it should be. The support functions pad with + * lots of zeros, so we can trim trailing zero bytes from each hardcoded + * archive to save space. + * + * The naming here follows the tar file type flags. E.g. '1' is a hardlink, + * '2' is a symlink, '5' is a dir, etc. + */ + +/* Empty archive. */ +static unsigned char archiveEmpty[] = { + /* 512 zero bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +}; + +static void verifyEmpty(void) +{ + struct archive_entry *ae; + struct archive *a; + + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archiveEmpty, 512)); + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_NONE); + failure("512 zero bytes should be recognized as a tar archive."); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_TAR); + + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + +/* Single entry with a hardlink. */ +static unsigned char archive1[] = { +'h','a','r','d','l','i','n','k',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0', +'0','6','4','4',' ',0,'0','0','1','7','5','0',' ',0,'0','0','1','7','5','0', +' ',0,'0','0','0','0','0','0','0','0','0','0','0',' ','1','0','6','4','6', +'0','5','2','6','6','2',' ','0','1','3','0','5','7',0,' ','1','f','i','l', +'e',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0,'0', +'0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0', +'0','0','0','0','0',' ',0,'0','0','0','0','0','0',' '}; + +static void verify1(struct archive_entry *ae) +{ + /* A hardlink is not a symlink. */ + assert(!S_ISLNK(archive_entry_mode(ae))); + /* Nor is it a directory. */ + assert(!S_ISDIR(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0644); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "hardlink"); + assertEqualString(archive_entry_hardlink(ae), "file"); + assert(archive_entry_symlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184388530); +} + +/* Verify that symlinks are read correctly. */ +static unsigned char archive2[] = { +'s','y','m','l','i','n','k',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0', +'0','0','7','5','5',' ','0','0','0','1','7','5','0',' ','0','0','0','1','7', +'5','0',' ','0','0','0','0','0','0','0','0','0','0','0',' ','1','0','6','4', +'6','0','5','4','1','0','1',' ','0','0','1','3','3','2','3',' ','2','f','i', +'l','e',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0, +'0','0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'0','0','0','0','0','0','0',' ','0','0','0','0','0','0','0',' '}; + +static void verify2(struct archive_entry *ae) +{ + assert(S_ISLNK(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "symlink"); + assertEqualString(archive_entry_symlink(ae), "file"); + assert(archive_entry_hardlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184389185); +} + +/* Character device node. */ +static unsigned char archive3[] = { +'d','e','v','c','h','a','r',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0', +'0','0','7','5','5',' ','0','0','0','1','7','5','0',' ','0','0','0','1','7', +'5','0',' ','0','0','0','0','0','0','0','0','0','0','0',' ','1','0','6','4', +'6','0','5','4','1','0','1',' ','0','0','1','2','4','1','2',' ','3',0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0, +'0','0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'0','0','0','0','0','0','0',' ','0','0','0','0','0','0','0',' '}; + +static void verify3(struct archive_entry *ae) +{ + assert(S_ISCHR(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "devchar"); + assert(archive_entry_symlink(ae) == NULL); + assert(archive_entry_hardlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184389185); +} + +/* Block device node. */ +static unsigned char archive4[] = { +'d','e','v','b','l','o','c','k',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0', +'0','0','7','5','5',' ','0','0','0','1','7','5','0',' ','0','0','0','1','7', +'5','0',' ','0','0','0','0','0','0','0','0','0','0','0',' ','1','0','6','4', +'6','0','5','4','1','0','1',' ','0','0','1','2','5','7','0',' ','4',0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0, +'0','0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'0','0','0','0','0','0','0',' ','0','0','0','0','0','0','0',' '}; + +static void verify4(struct archive_entry *ae) +{ + assert(S_ISBLK(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "devblock"); + assert(archive_entry_symlink(ae) == NULL); + assert(archive_entry_hardlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184389185); +} + +/* Directory. */ +static unsigned char archive5[] = { +'.',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0', +'7','5','5',' ',0,'0','0','1','7','5','0',' ',0,'0','0','1','7','5','0', +' ',0,'0','0','0','0','0','0','0','0','0','0','0',' ','1','0','3','3', +'4','0','4','1','7','3','6',' ','0','1','0','5','6','1',0,' ','5',0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0, +'0','0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'0','0','0','0','0','0',' ',0,'0','0','0','0','0','0',' '}; + +static void verify5(struct archive_entry *ae) +{ + assert(S_ISDIR(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mtime(ae), 1131430878); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); +} + +/* fifo */ +static unsigned char archive6[] = { +'f','i','f','o',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0', +'0','0','7','5','5',' ','0','0','0','1','7','5','0',' ','0','0','0','1','7', +'5','0',' ','0','0','0','0','0','0','0','0','0','0','0',' ','1','0','6','4', +'6','0','5','4','1','0','1',' ','0','0','1','1','7','2','4',' ','6',0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',0, +'0','0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'0','0','0','0','0','0','0',' ','0','0','0','0','0','0','0',' '}; + +static void verify6(struct archive_entry *ae) +{ + assert(S_ISFIFO(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "fifo"); + assert(archive_entry_symlink(ae) == NULL); + assert(archive_entry_hardlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184389185); +} + +/* GNU long link name */ +static unsigned char archiveK[] = { +'.','/','.','/','@','L','o','n','g','L','i','n','k',0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,'0','0','0','0','0','0','0',0,'0','0','0','0','0','0','0',0,'0','0','0', +'0','0','0','0',0,'0','0','0','0','0','0','0','0','6','6','6',0,'0','0','0', +'0','0','0','0','0','0','0','0',0,'0','1','1','7','1','5',0,' ','K',0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u','s','t','a','r',' ',' ', +0,'r','o','o','t',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'w','h','e','e','l',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'t', +'h','i','s','_','i','s','_','a','_','v','e','r','y','_','l','o','n','g','_', +'s','y','m','l','i','n','k','_','b','o','d','y','_','a','b','c','d','e','f', +'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y', +'z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', +'r','s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g','h','i', +'j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a', +'b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t', +'u','v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l', +'m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d', +'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w', +'x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', +'p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g', +'h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', +'_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r', +'s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j', +'k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b', +'c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u', +'v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m', +'n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d','e', +'f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x', +'y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', +'q','r','s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g','h', +'i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +'s','y','m','l','i','n','k',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','1', +'2','0','7','5','5',0,'0','0','0','1','7','5','0',0,'0','0','0','1','7','5', +'0',0,'0','0','0','0','0','0','0','0','0','0','0',0,'1','0','6','4','6','0', +'5','6','7','7','0',0,'0','3','5','4','4','7',0,' ','2','t','h','i','s','_', +'i','s','_','a','_','v','e','r','y','_','l','o','n','g','_','s','y','m','l', +'i','n','k','_','b','o','d','y','_','a','b','c','d','e','f','g','h','i','j', +'k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b', +'c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u', +'v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l',0, +'u','s','t','a','r',' ',' ',0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,'t','i','m'}; + +static void verifyK(struct archive_entry *ae) +{ + assert(S_ISLNK(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "symlink"); + assertEqualString(archive_entry_symlink(ae), + "this_is_a_very_long_symlink_body_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz"); + assert(archive_entry_hardlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184390648); +} + +/* TODO: GNU long name */ + +/* TODO: Solaris ACL */ + +/* Pax extended long link name */ +static unsigned char archivexL[] = { +'.','/','P','a','x','H','e','a','d','e','r','s','.','8','6','9','7','5','/', +'s','y','m','l','i','n','k',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0','0','6','4','4',0,'0','0','0','1', +'7','5','0',0,'0','0','0','1','7','5','0',0,'0','0','0','0','0','0','0','0', +'7','5','3',0,'1','0','6','4','6','0','5','7','6','1','1',0,'0','1','3','7', +'1','4',0,' ','x',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'u', +'s','t','a','r',0,'0','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,'0','0','0','0','0','0','0',0,'0','0','0','0','0','0','0',0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'4','5','1',' ','l','i','n','k','p','a','t', +'h','=','t','h','i','s','_','i','s','_','a','_','v','e','r','y','_','l','o', +'n','g','_','s','y','m','l','i','n','k','_','b','o','d','y','_','a','b','c', +'d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', +'w','x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','n', +'o','p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d','e','f', +'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y', +'z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q', +'r','s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g','h','i', +'j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a', +'b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t', +'u','v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l', +'m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d', +'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w', +'x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', +'p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g', +'h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', +'_','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r', +'s','t','u','v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j', +'k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b', +'c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u', +'v','w','x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m', +'n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d','e', +'f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x', +'y','z',10,'2','0',' ','a','t','i','m','e','=','1','1','8','4','3','9','1', +'0','2','5',10,'2','0',' ','c','t','i','m','e','=','1','1','8','4','3','9', +'0','6','4','8',10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'s','y','m', +'l','i','n','k',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,'0','0','0','0','7', +'5','5',0,'0','0','0','1','7','5','0',0,'0','0','0','1','7','5','0',0,'0', +'0','0','0','0','0','0','0','0','0','0',0,'1','0','6','4','6','0','5','6', +'7','7','0',0,'0','3','7','1','2','1',0,' ','2','t','h','i','s','_','i','s', +'_','a','_','v','e','r','y','_','l','o','n','g','_','s','y','m','l','i','n', +'k','_','b','o','d','y','_','a','b','c','d','e','f','g','h','i','j','k','l', +'m','n','o','p','q','r','s','t','u','v','w','x','y','z','_','a','b','c','d', +'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w', +'x','y','z','_','a','b','c','d','e','f','g','h','i','j','k','l','m','u','s', +'t','a','r',0,'0','0','t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,'t','i','m',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,'0','0','0','0','0','0','0',0,'0','0','0','0','0','0','0'}; + +static void verifyxL(struct archive_entry *ae) +{ + assert(S_ISLNK(archive_entry_mode(ae))); + assertEqualInt(archive_entry_mode(ae) & 0777, 0755); + assertEqualInt(archive_entry_uid(ae), 1000); + assertEqualInt(archive_entry_gid(ae), 1000); + assertEqualString(archive_entry_uname(ae), "tim"); + assertEqualString(archive_entry_gname(ae), "tim"); + assertEqualString(archive_entry_pathname(ae), "symlink"); + assertEqualString(archive_entry_symlink(ae), + "this_is_a_very_long_symlink_body_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_" + "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz"); + assert(archive_entry_hardlink(ae) == NULL); + assertEqualInt(archive_entry_mtime(ae), 1184390648); +} + + +/* TODO: Any other types of headers? */ + +static void verify(unsigned char *d, size_t s, + void (*f)(struct archive_entry *), + int compression, int format) +{ + struct archive_entry *ae; + struct archive *a; + unsigned char *buff = malloc(100000); + + memcpy(buff, d, s); + memset(buff + s, 0, 2048); + + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, buff, s + 1024)); + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualInt(archive_compression(a), compression); + assertEqualInt(archive_format(a), format); + + /* Verify the only entry. */ + f(ae); + + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + free(buff); +} + +DEFINE_TEST(test_read_format_tar) +{ + verifyEmpty(); + verify(archive1, sizeof(archive1), verify1, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_USTAR); + verify(archive2, sizeof(archive2), verify2, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_USTAR); + verify(archive3, sizeof(archive3), verify3, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_USTAR); + verify(archive4, sizeof(archive4), verify4, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_USTAR); + verify(archive5, sizeof(archive5), verify5, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_USTAR); + verify(archive6, sizeof(archive6), verify6, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_USTAR); + verify(archiveK, sizeof(archiveK), verifyK, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_GNUTAR); + verify(archivexL, sizeof(archivexL), verifyxL, + ARCHIVE_COMPRESSION_NONE, ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE); +} + + diff --git a/libarchive/test/test_read_format_tbz.c b/libarchive/test/test_read_format_tbz.c new file mode 100644 index 00000000..3d135eb6 --- /dev/null +++ b/libarchive/test/test_read_format_tbz.c @@ -0,0 +1,55 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_tbz.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +'B','Z','h','9','1','A','Y','&','S','Y',237,7,140,'W',0,0,27,251,144,208, +128,0,' ','@',1,'o',128,0,0,224,'"',30,0,0,'@',0,8,' ',0,'T','2',26,163,'&', +129,160,211,212,18,'I',169,234,13,168,26,6,150,'1',155,134,'p',8,173,3,183, +'J','S',26,20,'2',222,'b',240,160,'a','>',205,'f',29,170,227,'[',179,139, +'\'','L','o',211,':',178,'0',162,134,'*','>','8',24,153,230,147,'R','?',23, +'r','E','8','P',144,237,7,140,'W'}; + +DEFINE_TEST(test_read_format_tbz) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_BZIP2); + assert(archive_format(a) == ARCHIVE_FORMAT_TAR_USTAR); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_tgz.c b/libarchive/test/test_read_format_tgz.c new file mode 100644 index 00000000..ce61aa67 --- /dev/null +++ b/libarchive/test/test_read_format_tgz.c @@ -0,0 +1,54 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_tgz.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,139,8,0,222,'C','p','C',0,3,211,'c',160,'=','0','0','0','0','7','5','U', +0,210,134,230,166,6,200,'4',28,'(',24,26,24,27,155,24,152,24,154,27,155,')', +24,24,26,152,154,25,'2','(',152,210,193,'m',12,165,197,'%',137,'E','@',167, +148,'d',230,226,'U','G','H',30,234,15,'8','=',10,'F',193,'(',24,5,131,28, +0,0,29,172,5,240,0,6,0,0}; + +DEFINE_TEST(test_read_format_tgz) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assert(0 == archive_read_support_compression_all(a)); + assert(0 == archive_read_support_format_all(a)); + assert(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assert(0 == archive_read_next_header(a, &ae)); + assert(archive_compression(a) == ARCHIVE_COMPRESSION_GZIP); + assert(archive_format(a) == ARCHIVE_FORMAT_TAR_USTAR); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_tz.c b/libarchive/test/test_read_format_tz.c new file mode 100644 index 00000000..337b96df --- /dev/null +++ b/libarchive/test/test_read_format_tz.c @@ -0,0 +1,56 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_tz.c,v 1.1 2007/03/03 07:37:37 kientzle Exp $"); + +static unsigned char archive[] = { +31,157,144,'.',0,8,28,'H',176,160,193,131,8,19,'*','\\',200,176,'!','B',24, +16,'o',212,168,1,2,0,196,24,18,'a','T',188,152,'q','#',196,143,' ','5',198, +128,'1','c',6,13,24,'4','0',206,176,1,2,198,200,26,'6','b',0,0,'Q',195,161, +205,155,'8','s',234,4,'P','g',14,157,'0','r',',',194,160,147,166,205,206, +132,'D',141,30,'=',24,'R',163,'P',144,21,151,'J',157,'J',181,170,213,171, +'X',179,'j',221,202,181,171,215,175,'`',195,138,29,'K',182,172,217,179,'h', +211,170,']',203,182,173,219,183,'g',1}; + +DEFINE_TEST(test_read_format_tz) +{ + struct archive_entry *ae; + struct archive *a; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertA(0 == archive_read_next_header(a, &ae)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_COMPRESS); + assertA(archive_format(a) == ARCHIVE_FORMAT_TAR_USTAR); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_format_zip.c b/libarchive/test/test_read_format_zip.c new file mode 100644 index 00000000..9829217f --- /dev/null +++ b/libarchive/test/test_read_format_zip.c @@ -0,0 +1,88 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_format_zip.c,v 1.3 2008/01/01 22:28:04 kientzle Exp $"); + +static unsigned char archive[] = { +'P','K',3,4,10,0,0,0,0,0,'Y','f',179,'6',0,0,0,0,0,0,0,0,0,0,0,0,4,0,21,0, +'d','i','r','/','U','T',9,0,3,25,'U','O','F',25,'U','O','F','U','x',4,0,232, +3,232,3,'P','K',3,4,20,0,0,0,8,0,'o','f',179,'6',':','7','f','=',10,0,0,0, +18,0,0,0,5,0,21,0,'f','i','l','e','1','U','T',9,0,3,'A','U','O','F',172,'[', +'O','F','U','x',4,0,232,3,232,3,203,'H',205,201,201,231,202,'@','"',1,'P', +'K',3,4,20,0,0,0,8,0,'Z','j',179,'6',':','7','f','=',10,0,0,0,18,0,0,0,5, +0,21,0,'f','i','l','e','2','U','T',9,0,3,172,'[','O','F',172,'[','O','F', +'U','x',4,0,232,3,232,3,203,'H',205,201,201,231,202,'@','"',1,'P','K',1,2, +23,3,10,0,0,0,0,0,'Y','f',179,'6',0,0,0,0,0,0,0,0,0,0,0,0,4,0,13,0,0,0,0, +0,0,0,16,0,237,'A',0,0,0,0,'d','i','r','/','U','T',5,0,3,25,'U','O','F','U', +'x',0,0,'P','K',1,2,23,3,20,0,0,0,8,0,'o','f',179,'6',':','7','f','=',10, +0,0,0,18,0,0,0,5,0,13,0,0,0,0,0,1,0,0,0,164,129,'7',0,0,0,'f','i','l','e', +'1','U','T',5,0,3,'A','U','O','F','U','x',0,0,'P','K',1,2,23,3,20,0,0,0,8, +0,'Z','j',179,'6',':','7','f','=',10,0,0,0,18,0,0,0,5,0,13,0,0,0,0,0,1,0, +0,0,164,129,'y',0,0,0,'f','i','l','e','2','U','T',5,0,3,172,'[','O','F','U', +'x',0,0,'P','K',5,6,0,0,0,0,3,0,3,0,191,0,0,0,187,0,0,0,0,0}; + +DEFINE_TEST(test_read_format_zip) +{ + struct archive_entry *ae; + struct archive *a; + char *buff[128]; + const void *pv; + size_t s; + off_t o; + + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_open_memory(a, archive, sizeof(archive))); + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("dir/", archive_entry_pathname(ae)); + assertEqualInt(1179604249, archive_entry_mtime(ae)); + assertEqualInt(0, archive_entry_size(ae)); + assertEqualIntA(a, ARCHIVE_EOF, + archive_read_data_block(a, &pv, &s, &o)); + assertEqualInt(s, 0); + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("file1", archive_entry_pathname(ae)); + assertEqualInt(1179604289, archive_entry_mtime(ae)); + assertEqualInt(18, archive_entry_size(ae)); + assertEqualInt(18, archive_read_data(a, buff, 18)); + assert(0 == memcmp(buff, "hello\nhello\nhello\n", 18)); + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("file2", archive_entry_pathname(ae)); + assertEqualInt(1179605932, archive_entry_mtime(ae)); + assertEqualInt(18, archive_entry_size(ae)); + assertEqualInt(18, archive_read_data(a, buff, 18)); + assert(0 == memcmp(buff, "hello\nhello\nhello\n", 18)); + assertA(archive_compression(a) == ARCHIVE_COMPRESSION_NONE); + assertA(archive_format(a) == ARCHIVE_FORMAT_ZIP); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + + diff --git a/libarchive/test/test_read_large.c b/libarchive/test/test_read_large.c new file mode 100644 index 00000000..d0342407 --- /dev/null +++ b/libarchive/test/test_read_large.c @@ -0,0 +1,94 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_large.c,v 1.3 2007/05/29 01:00:21 kientzle Exp $"); + +static unsigned char testdata[10 * 1024 * 1024]; +static unsigned char testdatacopy[10 * 1024 * 1024]; +static unsigned char buff[11 * 1024 * 1024]; + +/* Check correct behavior on large reads. */ +DEFINE_TEST(test_read_large) +{ + unsigned int i; + int tmpfilefd; + char tmpfilename[] = "/tmp/test-read_large.XXXXXX"; + size_t used; + struct archive *a; + struct archive_entry *entry; + + for (i = 0; i < sizeof(testdata); i++) + testdata[i] = (unsigned char)(rand()); + + assert(NULL != (a = archive_write_new())); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + assert(NULL != (entry = archive_entry_new())); + archive_entry_set_size(entry, sizeof(testdata)); + archive_entry_set_mode(entry, S_IFREG | 0777); + archive_entry_set_pathname(entry, "test"); + assertA(0 == archive_write_header(a, entry)); + archive_entry_free(entry); + assertA(sizeof(testdata) == archive_write_data(a, testdata, sizeof(testdata))); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + assert(NULL != (a = archive_read_new())); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, sizeof(buff))); + assertA(0 == archive_read_next_header(a, &entry)); + assertA(0 == archive_read_data_into_buffer(a, testdatacopy, sizeof(testdatacopy))); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + assert(0 == memcmp(testdata, testdatacopy, sizeof(testdata))); + + + assert(NULL != (a = archive_read_new())); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, sizeof(buff))); + assertA(0 == archive_read_next_header(a, &entry)); + assert(0 < (tmpfilefd = mkstemp(tmpfilename))); + assertA(0 == archive_read_data_into_fd(a, tmpfilefd)); + close(tmpfilefd); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + tmpfilefd = open(tmpfilename, O_RDONLY); + read(tmpfilefd, testdatacopy, sizeof(testdatacopy)); + close(tmpfilefd); + assert(0 == memcmp(testdata, testdatacopy, sizeof(testdata))); + + unlink(tmpfilename); +} diff --git a/libarchive/test/test_read_pax_truncated.c b/libarchive/test/test_read_pax_truncated.c new file mode 100644 index 00000000..5e2c9c53 --- /dev/null +++ b/libarchive/test/test_read_pax_truncated.c @@ -0,0 +1,281 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_pax_truncated.c,v 1.2 2008/01/01 22:28:04 kientzle Exp $"); + +DEFINE_TEST(test_read_pax_truncated) +{ + struct archive_entry *ae; + struct archive *a; + ssize_t used, i; + size_t buff_size = 1000000; + ssize_t filedata_size = 100000; + char *buff = malloc(buff_size); + char *buff2 = malloc(buff_size); + char *filedata = malloc(filedata_size); + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_pax(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff, buff_size, &used)); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + for (i = 0; i < filedata_size; i++) + filedata[i] = (unsigned char)rand(); + archive_entry_set_atime(ae, 1, 2); + archive_entry_set_ctime(ae, 3, 4); + archive_entry_set_mtime(ae, 5, 6); + archive_entry_set_size(ae, filedata_size); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(filedata_size == archive_write_data(a, filedata, filedata_size)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Now, read back a truncated version of the archive and + * verify that we get an appropriate error. */ + for (i = 1; i < used + 100; i += 100) { + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == read_open_memory(a, buff, i, 13)); + + if (i < 1536) { + assertEqualIntA(a, ARCHIVE_FATAL, archive_read_next_header(a, &ae)); + goto wrap_up; + } else { + failure("Archive truncated to %d bytes", i); + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + } + + if (i < 1536 + filedata_size) { + assertA(ARCHIVE_FATAL == archive_read_data(a, filedata, filedata_size)); + goto wrap_up; + } else { + failure("Archive truncated to %d bytes", i); + assertEqualIntA(a, filedata_size, + archive_read_data(a, filedata, filedata_size)); + } + + /* Verify the end of the archive. */ + /* Archive must be long enough to capture a 512-byte + * block of zeroes after the entry. (POSIX requires a + * second block of zeros to be written but libarchive + * does not return an error if it can't consume + * it.) */ + if (i < 1536 + 512*((filedata_size + 511)/512) + 512) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + } else { + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + } + wrap_up: + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + } + + + + /* Same as above, except skip the body instead of reading it. */ + for (i = 1; i < used + 100; i += 100) { + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == read_open_memory(a, buff, i, 7)); + + if (i < 1536) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + goto wrap_up2; + } else { + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + } + + if (i < 1536 + 512*((filedata_size+511)/512)) { + assertA(ARCHIVE_FATAL == archive_read_data_skip(a)); + goto wrap_up2; + } else { + assertA(ARCHIVE_OK == archive_read_data_skip(a)); + } + + /* Verify the end of the archive. */ + /* Archive must be long enough to capture a 512-byte + * block of zeroes after the entry. (POSIX requires a + * second block of zeros to be written but libarchive + * does not return an error if it can't consume + * it.) */ + if (i < 1536 + 512*((filedata_size + 511)/512) + 512) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + } else { + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + } + wrap_up2: + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + } + + /* Now, damage the archive in various ways and test the responses. */ + + /* Damage the first size field in the pax attributes. */ + memcpy(buff2, buff, buff_size); + buff2[512] = '9'; + buff2[513] = '9'; + buff2[514] = 'A'; /* Non-digit in size. */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Damage the size field in the pax attributes. */ + memcpy(buff2, buff, buff_size); + buff2[512] = 'A'; /* First character not a digit. */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Damage the size field in the pax attributes. */ + memcpy(buff2, buff, buff_size); + for (i = 512; i < 520; i++) /* Size over 999999. */ + buff2[i] = '9'; + buff2[i] = ' '; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Damage the size field in the pax attributes. */ + memcpy(buff2, buff, buff_size); + buff2[512] = '9'; /* Valid format, but larger than attribute area. */ + buff2[513] = '9'; + buff2[514] = '9'; + buff2[515] = ' '; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Damage the size field in the pax attributes. */ + memcpy(buff2, buff, buff_size); + buff2[512] = '1'; /* Too small. */ + buff2[513] = ' '; + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Damage the size field in the pax attributes. */ + memcpy(buff2, buff, buff_size); + buff2[512] = ' '; /* No size given. */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_WARN, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* Damage the ustar header. */ + memcpy(buff2, buff, buff_size); + buff2[1024]++; /* Break the checksum. */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff2, used)); + assertEqualIntA(a, ARCHIVE_FATAL, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* + * TODO: Damage the ustar header in various ways and fixup the + * checksum in order to test boundary cases in the innermost + * ustar header parsing. + */ + + free(buff); + free(buff2); + free(filedata); +} diff --git a/libarchive/test/test_read_position.c b/libarchive/test/test_read_position.c new file mode 100644 index 00000000..3675e11a --- /dev/null +++ b/libarchive/test/test_read_position.c @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_position.c,v 1.3 2007/05/29 01:00:21 kientzle Exp $"); + +static unsigned char nulls[10000000]; +static unsigned char buff[10000000]; + +/* Check that header_position tracks correctly on read. */ +DEFINE_TEST(test_read_position) +{ + struct archive *a; + struct archive_entry *ae; + size_t write_pos; + const size_t data_size = 1000000; + + /* Create a simple archive_entry. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_pathname(ae, "testfile"); + archive_entry_set_mode(ae, S_IFREG); + archive_entry_set_size(ae, data_size); + + assert(NULL != (a = archive_write_new())); + assertA(0 == archive_write_set_format_pax_restricted(a)); + assertA(0 == archive_write_set_bytes_per_block(a, 512)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &write_pos)); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(data_size == (size_t)archive_write_data(a, nulls, sizeof(nulls))); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + assertA(0 == archive_write_close(a)); + archive_write_finish(a); +#endif + /* 512-byte header + data_size (rounded up) + 1024 end-of-archive */ + assert(write_pos == ((512 + data_size + 1024 + 511)/512)*512); + + /* Read the archive back. */ + assert(NULL != (a = archive_read_new())); + assertA(0 == archive_read_support_format_tar(a)); + assertA(0 == archive_read_open_memory2(a, buff, sizeof(buff), 512)); + assert((intmax_t)0 == (intmax_t)archive_read_header_position(a)); + assertA(0 == archive_read_next_header(a, &ae)); + assert((intmax_t)0 == (intmax_t)archive_read_header_position(a)); + assertA(0 == archive_read_data_skip(a)); + assert((intmax_t)0 == (intmax_t)archive_read_header_position(a)); + assertA(1 == archive_read_next_header(a, &ae)); + assert((intmax_t)((data_size + 511 + 512)/512)*512 == (intmax_t)archive_read_header_position(a)); + assertA(0 == archive_read_close(a)); + assert((intmax_t)((data_size + 511 + 512)/512)*512 == (intmax_t)archive_read_header_position(a)); + archive_read_finish(a); +} diff --git a/libarchive/test/test_read_truncated.c b/libarchive/test/test_read_truncated.c new file mode 100644 index 00000000..726984c5 --- /dev/null +++ b/libarchive/test/test_read_truncated.c @@ -0,0 +1,149 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_read_truncated.c,v 1.3 2007/05/29 01:00:21 kientzle Exp $"); + +char buff[1000000]; +char buff2[100000]; + +DEFINE_TEST(test_read_truncated) +{ + struct archive_entry *ae; + struct archive *a; + unsigned int i; + size_t used; + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + for (i = 0; i < sizeof(buff2); i++) + buff2[i] = (unsigned char)rand(); + archive_entry_set_size(ae, sizeof(buff2)); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(sizeof(buff2) == archive_write_data(a, buff2, sizeof(buff2))); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Now, read back a truncated version of the archive and + * verify that we get an appropriate error. */ + for (i = 1; i < used + 100; i += 100) { + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, i)); + + if (i < 512) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + goto wrap_up; + } else { + assertA(0 == archive_read_next_header(a, &ae)); + } + + if (i < 512 + sizeof(buff2)) { + assertA(ARCHIVE_FATAL == archive_read_data(a, buff2, sizeof(buff2))); + goto wrap_up; + } else { + assertA(sizeof(buff2) == archive_read_data(a, buff2, sizeof(buff2))); + } + + /* Verify the end of the archive. */ + /* Archive must be long enough to capture a 512-byte + * block of zeroes after the entry. (POSIX requires a + * second block of zeros to be written but libarchive + * does not return an error if it can't consume + * it.) */ + if (i < 512 + 512*((sizeof(buff2) + 511)/512) + 512) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + } else { + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + } + wrap_up: + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + } + + + + /* Same as above, except skip the body instead of reading it. */ + for (i = 1; i < used + 100; i += 100) { + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, i)); + + if (i < 512) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + goto wrap_up2; + } else { + assertA(0 == archive_read_next_header(a, &ae)); + } + + if (i < 512 + 512*((sizeof(buff2)+511)/512)) { + assertA(ARCHIVE_FATAL == archive_read_data_skip(a)); + goto wrap_up2; + } else { + assertA(ARCHIVE_OK == archive_read_data_skip(a)); + } + + /* Verify the end of the archive. */ + /* Archive must be long enough to capture a 512-byte + * block of zeroes after the entry. (POSIX requires a + * second block of zeros to be written but libarchive + * does not return an error if it can't consume + * it.) */ + if (i < 512 + 512*((sizeof(buff2) + 511)/512) + 512) { + assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae)); + } else { + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + } + wrap_up2: + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + } +} diff --git a/libarchive/test/test_tar_filenames.c b/libarchive/test/test_tar_filenames.c new file mode 100644 index 00000000..8b83b527 --- /dev/null +++ b/libarchive/test/test_tar_filenames.c @@ -0,0 +1,176 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_tar_filenames.c,v 1.8 2008/01/01 22:28:04 kientzle Exp $"); + +/* + * Exercise various lengths of filenames in tar archives, + * especially around the magic sizes where ustar breaks + * filenames into prefix/suffix. + */ + +static void +test_filename(const char *prefix, int dlen, int flen) +{ + char buff[8192]; + char filename[400]; + char dirname[400]; + struct archive_entry *ae; + struct archive *a; + size_t used; + size_t prefix_length = 0; + unsigned i = 0; + + if (prefix) { + strcpy(filename, prefix); + i = prefix_length = strlen(prefix); + } + for (; i < prefix_length + dlen; i++) + filename[i] = 'a'; + filename[i++] = '/'; + for (; i < prefix_length + dlen + flen + 1; i++) + filename[i] = 'b'; + filename[i++] = '\0'; + + strcpy(dirname, filename); + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_pax_restricted(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_set_bytes_per_block(a,0)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, filename); + archive_entry_set_mode(ae, S_IFREG | 0755); + failure("Pathname %d/%d", dlen, flen); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* + * Write a dir to it (without trailing '/'). + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, dirname); + archive_entry_set_mode(ae, S_IFDIR | 0755); + failure("Dirname %d/%d", dlen, flen); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* Tar adds a '/' to directory names. */ + strcat(dirname, "/"); + + /* + * Write a dir to it (with trailing '/'). + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, dirname); + archive_entry_set_mode(ae, S_IFDIR | 0755); + failure("Dirname %d/%d", dlen, flen); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Now, read the data back. + */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + /* Read the file and check the filename. */ + assertA(0 == archive_read_next_header(a, &ae)); +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("Leading '/' preserved on long filenames"); +#else + assertEqualString(filename, archive_entry_pathname(ae)); +#endif + assertEqualInt((S_IFREG | 0755), archive_entry_mode(ae)); + + /* + * Read the two dirs and check the names. + * + * Both dirs should read back with the same name, since + * tar should add a trailing '/' to any dir that doesn't + * already have one. We only report the first such failure + * here. + */ + assertA(0 == archive_read_next_header(a, &ae)); +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("Trailing '/' preserved on dirnames"); +#else + assertEqualString(dirname, archive_entry_pathname(ae)); +#endif + assert((S_IFDIR | 0755) == archive_entry_mode(ae)); + + assertA(0 == archive_read_next_header(a, &ae)); +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("Trailing '/' added to dir names"); +#else + assertEqualString(dirname, archive_entry_pathname(ae)); +#endif + assert((S_IFDIR | 0755) == archive_entry_mode(ae)); + + /* Verify the end of the archive. */ + assert(1 == archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +} + +DEFINE_TEST(test_tar_filenames) +{ + int dlen, flen; + + /* Repeat the following for a variety of dir/file lengths. */ + for (dlen = 40; dlen < 60; dlen++) { + for (flen = 40; flen < 60; flen++) { + test_filename(NULL, dlen, flen); + test_filename("/", dlen, flen); + } + } + + for (dlen = 140; dlen < 160; dlen++) { + for (flen = 90; flen < 110; flen++) { + test_filename(NULL, dlen, flen); + test_filename("/", dlen, flen); + } + } +} diff --git a/libarchive/test/test_tar_large.c b/libarchive/test/test_tar_large.c new file mode 100644 index 00000000..c675ac1e --- /dev/null +++ b/libarchive/test/test_tar_large.c @@ -0,0 +1,309 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_tar_large.c,v 1.1 2008/01/01 22:28:04 kientzle Exp $"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* + * This is a somewhat tricky test that verifies the ability to + * write and read very large entries to tar archives. It + * writes entries from 2GB up to 1TB to an archive in memory. + * The memory storage here carefully avoids actually storing + * any part of the file bodies, so it runs very quickly and requires + * very little memory. If you're willing to wait a few minutes, + * you should be able to exercise petabyte entries with this code. + */ + +/* + * Each file is built up by duplicating the following block. + */ +static size_t filedatasize; +static void *filedata; + +/* + * We store the archive as blocks of data generated by libarchive, + * each possibly followed by bytes of file data. + */ +struct memblock { + struct memblock *next; + size_t size; + void *buff; + off_t filebytes; +}; + +/* + * The total memory store is just a list of memblocks plus + * some accounting overhead. + */ +struct memdata { + off_t filebytes; + void *buff; + struct memblock *first; + struct memblock *last; +}; + +/* The following size definitions simplify things below. */ +#define KB ((off_t)1024) +#define MB ((off_t)1024 * KB) +#define GB ((off_t)1024 * MB) +#define TB ((off_t)1024 * GB) + +#if ARCHIVE_API_VERSION < 2 +static ssize_t memory_read_skip(struct archive *, void *, size_t request); +#else +static off_t memory_read_skip(struct archive *, void *, off_t request); +#endif +static ssize_t memory_read(struct archive *, void *, const void **buff); +static ssize_t memory_write(struct archive *, void *, const void *, size_t); + + +static ssize_t +memory_write(struct archive *a, void *_private, const void *buff, size_t size) +{ + struct memdata *private = _private; + struct memblock *block; + + (void)a; + + /* + * Since libarchive tries to behave in a zero-copy manner, if + * you give a pointer to filedata to the library, a pointer + * into that data will (usually) pop out here. This way, we + * can tell the difference between filedata and library header + * and metadata. + */ + if ((const char *)filedata <= (const char *)buff + && (const char *)buff < (const char *)filedata + filedatasize) { + /* We don't need to store a block of file data. */ + private->last->filebytes += size; + } else { + /* Yes, we're assuming the very first write is metadata. */ + /* It's header or metadata, copy and save it. */ + block = (struct memblock *)malloc(sizeof(*block)); + memset(block, 0, sizeof(*block)); + block->size = size; + block->buff = malloc(size); + memcpy(block->buff, buff, size); + if (private->last == NULL) { + private->first = private->last = block; + } else { + private->last->next = block; + private->last = block; + } + block->next = NULL; + } + return (size); +} + +static ssize_t +memory_read(struct archive *a, void *_private, const void **buff) +{ + struct memdata *private = _private; + struct memblock *block; + ssize_t size; + + (void)a; + + free(private->buff); + private->buff = NULL; + if (private->first == NULL) { + private->last = NULL; + return (ARCHIVE_EOF); + } + if (private->filebytes > 0) { + /* + * We're returning file bytes, simulate it by + * passing blocks from the template data. + */ + if (private->filebytes > (off_t)filedatasize) + size = filedatasize; + else + size = (ssize_t)private->filebytes; + private->filebytes -= size; + *buff = filedata; + } else { + /* + * We need to get some real data to return. + */ + block = private->first; + private->first = block->next; + size = block->size; + if (block->buff != NULL) { + private->buff = block->buff; + *buff = block->buff; + } else { + private->buff = NULL; + *buff = filedata; + } + private->filebytes = block->filebytes; + free(block); + } + return (size); +} + + +#if ARCHIVE_API_VERSION < 2 +static ssize_t +memory_read_skip(struct archive *a, void *private, size_t skip) +{ + (void)a; /* UNUSED */ + (void)private; /* UNUSED */ + (void)skip; /* UNUSED */ + return (0); +} +#else +static off_t +memory_read_skip(struct archive *a, void *_private, off_t skip) +#endif +{ + struct memdata *private = _private; + + (void)a; + + if (private->first == NULL) { + private->last = NULL; + return (0); + } + if (private->filebytes > 0) { + if (private->filebytes < skip) + skip = private->filebytes; + private->filebytes -= skip; + } else { + skip = 0; + } + return (skip); +} + +DEFINE_TEST(test_tar_large) +{ + /* The sizes of the entries we're going to generate. */ + static off_t tests[] = { + /* Test for 32-bit signed overflow. */ + 2 * GB - 1, 2 * GB, 2 * GB + 1, + /* Test for 32-bit unsigned overflow. */ + 4 * GB - 1, 4 * GB, 4 * GB + 1, + /* 8GB is the "official" max for ustar. */ + 8 * GB - 1, 8 * GB, 8 * GB + 1, + /* Bend ustar a tad and you can get 64GB (12 octal digits). */ + 64 * GB - 1, 64 * GB, + /* And larger entries that require non-ustar extensions. */ + 256 * GB, 1 * TB, 0 }; + int i; + char namebuff[64]; + struct memdata memdata; + struct archive_entry *ae; + struct archive *a; + off_t filesize, writesize; + + filedatasize = 1 * MB; + filedata = malloc(filedatasize); + memset(filedata, 0xAA, filedatasize); + memset(&memdata, 0, sizeof(memdata)); + + /* + * Open an archive for writing. + */ + a = archive_write_new(); + archive_write_set_format_pax_restricted(a); + archive_write_set_bytes_per_block(a, 0); /* No buffering. */ + archive_write_open(a, &memdata, NULL, memory_write, NULL); + + /* + * Write a series of large files to it. + */ + for (i = 0; tests[i] > 0; i++) { + assert((ae = archive_entry_new()) != NULL); + sprintf(namebuff, "file_%d", i); + archive_entry_copy_pathname(ae, namebuff); + archive_entry_set_mode(ae, S_IFREG | 0755); + filesize = tests[i]; + archive_entry_set_size(ae, filesize); + + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + /* + * Write the actual data to the archive. + */ + while (filesize > 0) { + writesize = filedatasize; + if (writesize > filesize) + writesize = filesize; + assertA(writesize == archive_write_data(a, filedata, writesize)); + filesize -= writesize; + } + } + + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "lastfile"); + archive_entry_set_mode(ae, S_IFREG | 0755); + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Open the same archive for reading. + */ + a = archive_read_new(); + archive_read_support_format_tar(a); + archive_read_open2(a, &memdata, NULL, + memory_read, memory_read_skip, NULL); + + /* + * Read entries back. + */ + for (i = 0; tests[i] > 0; i++) { + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + sprintf(namebuff, "file_%d", i); + assertEqualString(namebuff, archive_entry_pathname(ae)); + assert(tests[i] == archive_entry_size(ae)); + } + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assertEqualString("lastfile", archive_entry_pathname(ae)); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Close out the archive. */ + assertA(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + free(memdata.buff); + free(filedata); +} diff --git a/libarchive/test/test_write_compress.c b/libarchive/test/test_write_compress.c new file mode 100644 index 00000000..6f8ef356 --- /dev/null +++ b/libarchive/test/test_write_compress.c @@ -0,0 +1,102 @@ +/*- + * Copyright (c) 2007 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. + */ + +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_compress.c,v 1.2 2008/03/15 11:05:49 kientzle Exp $"); + +/* + * A basic exercise of compress reading and writing. + * + * TODO: Add a reference file and make sure we can decompress that. + */ + +DEFINE_TEST(test_write_compress) +{ + struct archive_entry *ae; + struct archive* a; + char *buff, *data; + size_t buffsize, datasize; + char path[16]; + size_t used; + int i; + + buffsize = 1000000; + assert(NULL != (buff = (char *)malloc(buffsize))); + + datasize = 10000; + assert(NULL != (data = (char *)malloc(datasize))); + memset(data, 0, datasize); + + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_compress(a)); + assertA(0 == archive_write_open_memory(a, buff, buffsize, &used)); + + for (i = 0; i < 100; i++) { + sprintf(path, "file%03d", i); + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, path); + archive_entry_set_size(ae, datasize); + archive_entry_set_filetype(ae, AE_IFREG); + assertA(0 == archive_write_header(a, ae)); + assertA(datasize == (size_t)archive_write_data(a, data, datasize)); + archive_entry_free(ae); + } + + + archive_write_close(a); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Now, read the data back. + */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + + for (i = 0; i < 100; i++) { + sprintf(path, "file%03d", i); + assertEqualInt(0, archive_read_next_header(a, &ae)); + assertEqualString(path, archive_entry_pathname(ae)); + assertEqualInt(datasize, archive_entry_size(ae)); + } + + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + free(data); + free(buff); +} diff --git a/libarchive/test/test_write_compress_program.c b/libarchive/test/test_write_compress_program.c new file mode 100644 index 00000000..666bbf92 --- /dev/null +++ b/libarchive/test/test_write_compress_program.c @@ -0,0 +1,101 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_compress_program.c,v 1.2 2007/07/06 15:43:11 kientzle Exp $"); + +char buff[1000000]; +char buff2[64]; + +DEFINE_TEST(test_write_compress_program) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("archive_write_set_compress_program()"); +#else + struct archive_entry *ae; + struct archive *a; + size_t used; + int blocksize = 1024; + + /* Create a new archive in memory. */ + /* Write it through an external "gzip" program. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_program(a, "gzip")); + assertA(0 == archive_write_set_bytes_per_block(a, blocksize)); + assertA(0 == archive_write_set_bytes_in_last_block(a, blocksize)); + assertA(blocksize == archive_write_get_bytes_in_last_block(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + assertA(blocksize == archive_write_get_bytes_in_last_block(a)); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 1, 10); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 8); + + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(8 == archive_write_data(a, "12345678", 9)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Now, read the data back through the built-in gzip support. + */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + assertA(0 == archive_read_next_header(a, &ae)); + + assert(1 == archive_entry_mtime(ae)); + assert(0 == archive_entry_atime(ae)); + assert(0 == archive_entry_ctime(ae)); + assertEqualString("file", archive_entry_pathname(ae)); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + assert(8 == archive_entry_size(ae)); + assertA(8 == archive_read_data(a, buff2, 10)); + assert(0 == memcmp(buff2, "12345678", 8)); + + /* Verify the end of the archive. */ + assert(1 == archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +#endif +} diff --git a/libarchive/test/test_write_disk.c b/libarchive/test/test_write_disk.c new file mode 100644 index 00000000..480cf29b --- /dev/null +++ b/libarchive/test/test_write_disk.c @@ -0,0 +1,198 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_disk.c,v 1.8 2008/01/23 05:47:08 kientzle Exp $"); + +#if ARCHIVE_VERSION_STAMP >= 1009000 + +#define UMASK 022 + +static void create(struct archive_entry *ae, const char *msg) +{ + struct archive *ad; + struct stat st; + + /* Write the entry to disk. */ + assert((ad = archive_write_disk_new()) != NULL); + failure("%s", msg); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); +#if ARCHIVE_API_VERSION > 1 + assertEqualInt(0, archive_write_finish(ad)); +#else + archive_write_finish(ad); +#endif + /* Test the entries on disk. */ + assert(0 == stat(archive_entry_pathname(ae), &st)); + failure("st.st_mode=%o archive_entry_mode(ae)=%o", + st.st_mode, archive_entry_mode(ae)); + assert(st.st_mode == (archive_entry_mode(ae) & ~UMASK)); +} + +static void create_reg_file(struct archive_entry *ae, const char *msg) +{ + static const char data[]="abcdefghijklmnopqrstuvwxyz"; + struct archive *ad; + struct stat st; + + /* Write the entry to disk. */ + assert((ad = archive_write_disk_new()) != NULL); + failure("%s", msg); + /* + * A touchy API design issue: archive_write_data() does (as of + * 2.4.12) enforce the entry size as a limit on the data + * written to the file. This was not enforced prior to + * 2.4.12. The change was prompted by the refined + * hardlink-restore semantics introduced at that time. In + * short, libarchive needs to know whether a "hardlink entry" + * is going to overwrite the contents so that it can know + * whether or not to open the file for writing. This implies + * that there is a fundamental semantic difference between an + * entry with a zero size and one with a non-zero size in the + * case of hardlinks and treating the hardlink case + * differently from the regular file case is just asking for + * trouble. So, a zero size must always mean that no data + * will be accepted, which is consistent with the file size in + * the entry being a maximum size. + */ + archive_entry_set_size(ae, sizeof(data)); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); +#if ARCHIVE_API_VERSION > 1 + assertEqualInt(0, archive_write_finish(ad)); +#else + archive_write_finish(ad); +#endif + /* Test the entries on disk. */ + assert(0 == stat(archive_entry_pathname(ae), &st)); + failure("st.st_mode=%o archive_entry_mode(ae)=%o", + st.st_mode, archive_entry_mode(ae)); + assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK)); + assertEqualInt(st.st_size, sizeof(data)); +} + +static void create_reg_file2(struct archive_entry *ae, const char *msg) +{ + const int datasize = 100000; + char *data; + char *compare; + struct archive *ad; + struct stat st; + int i, fd; + + data = malloc(datasize); + for (i = 0; i < datasize; i++) + data[i] = (char)(i % 256); + + /* Write the entry to disk. */ + assert((ad = archive_write_disk_new()) != NULL); + failure("%s", msg); + /* + * See above for an explanation why this next call + * is necessary. + */ + archive_entry_set_size(ae, datasize); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + for (i = 0; i < datasize - 999; i += 1000) { + assertEqualIntA(ad, ARCHIVE_OK, + archive_write_data_block(ad, data + i, 1000, i)); + } + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); +#if ARCHIVE_API_VERSION > 1 + assertEqualInt(0, archive_write_finish(ad)); +#else + archive_write_finish(ad); +#endif + /* Test the entries on disk. */ + assert(0 == stat(archive_entry_pathname(ae), &st)); + failure("st.st_mode=%o archive_entry_mode(ae)=%o", + st.st_mode, archive_entry_mode(ae)); + assertEqualInt(st.st_mode, (archive_entry_mode(ae) & ~UMASK)); + assertEqualInt(st.st_size, i); + + compare = malloc(datasize); + fd = open(archive_entry_pathname(ae), O_RDONLY); + assertEqualInt(datasize, read(fd, compare, datasize)); + close(fd); + assert(memcmp(compare, data, datasize) == 0); + free(compare); + free(data); +} +#endif + +DEFINE_TEST(test_write_disk) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("archive_write_disk interface"); +#else + struct archive_entry *ae; + + /* Force the umask to something predictable. */ + umask(UMASK); + + /* A regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0755); + create_reg_file(ae, "Test creating a regular file"); + archive_entry_free(ae); + + /* Another regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file2"); + archive_entry_set_mode(ae, S_IFREG | 0755); + create_reg_file2(ae, "Test creating another regular file"); + archive_entry_free(ae); + + /* A regular file over an existing file */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0724); + create(ae, "Test creating a file over an existing file."); + archive_entry_free(ae); + + /* A directory. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir"); + archive_entry_set_mode(ae, S_IFDIR | 0555); + create(ae, "Test creating a regular dir."); + archive_entry_free(ae); + + /* A directory over an existing file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFDIR | 0742); + create(ae, "Test creating a dir over an existing file."); + archive_entry_free(ae); + + /* A file over an existing dir. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file"); + archive_entry_set_mode(ae, S_IFREG | 0744); + create(ae, "Test creating a file over an existing dir."); + archive_entry_free(ae); +#endif +} diff --git a/libarchive/test/test_write_disk_hardlink.c b/libarchive/test/test_write_disk_hardlink.c new file mode 100644 index 00000000..b21c44b2 --- /dev/null +++ b/libarchive/test/test_write_disk_hardlink.c @@ -0,0 +1,165 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_disk_hardlink.c,v 1.1 2008/01/18 05:05:58 kientzle Exp $"); + +#define UMASK 022 + +/* + * Exercise hardlink recreation. + */ +DEFINE_TEST(test_write_disk_hardlink) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("archive_write_disk_hardlink tests"); +#else + static const char data[]="abcdefghijklmnopqrstuvwxyz"; + struct archive *ad; + struct archive_entry *ae; + struct stat st, st2; + + /* Force the umask to something predictable. */ + umask(UMASK); + + /* Write entries to disk. */ + assert((ad = archive_write_disk_new()) != NULL); + + /* + * First, use a tar-like approach; a regular file, then + * a separate "hardlink" entry. + */ + + /* Regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link1a"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, sizeof(data)); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* Link. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link1b"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 0); + archive_entry_copy_hardlink(ae, "link1a"); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(0, archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* + * Second, try an old-cpio-like approach; a regular file, then + * another identical one (which has been marked hardlink). + */ + + /* Regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link2a"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, sizeof(data)); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* Link. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link2b"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, sizeof(data)); + archive_entry_copy_hardlink(ae, "link2a"); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* + * Finally, try a new-cpio-like approach, where the initial + * regular file is empty and the hardlink has the data. + */ + + /* Regular file. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link3a"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 0); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(0, archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + + /* Link. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link3b"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, sizeof(data)); + archive_entry_copy_hardlink(ae, "link3a"); + assertEqualIntA(ad, 0, archive_write_header(ad, ae)); + assertEqualInt(sizeof(data), archive_write_data(ad, data, sizeof(data))); + assertEqualIntA(ad, 0, archive_write_finish_entry(ad)); + archive_entry_free(ae); + assertEqualInt(0, archive_write_finish(ad)); + + /* Test the entries on disk. */ + assert(0 == stat("link1a", &st)); + assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st.st_size, sizeof(data)); + assertEqualInt(st.st_nlink, 2); + + assert(0 == stat("link1b", &st2)); + assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st2.st_size, sizeof(data)); + assertEqualInt(st2.st_nlink, 2); + assertEqualInt(st.st_ino, st2.st_ino); + assertEqualInt(st.st_dev, st2.st_dev); + + assert(0 == stat("link2a", &st)); + assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st.st_size, sizeof(data)); + assertEqualInt(st.st_nlink, 2); + + assert(0 == stat("link2b", &st2)); + assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st2.st_size, sizeof(data)); + assertEqualInt(st2.st_nlink, 2); + assertEqualInt(st.st_ino, st2.st_ino); + assertEqualInt(st.st_dev, st2.st_dev); + + assert(0 == stat("link3a", &st)); + assertEqualInt(st.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st.st_size, sizeof(data)); + assertEqualInt(st.st_nlink, 2); + + assert(0 == stat("link3b", &st2)); + assertEqualInt(st2.st_mode, (S_IFREG | 0755) & ~UMASK); + assertEqualInt(st2.st_size, sizeof(data)); + assertEqualInt(st2.st_nlink, 2); + assertEqualInt(st.st_ino, st2.st_ino); + assertEqualInt(st.st_dev, st2.st_dev); +#endif +} diff --git a/libarchive/test/test_write_disk_perms.c b/libarchive/test/test_write_disk_perms.c new file mode 100644 index 00000000..4c03c75b --- /dev/null +++ b/libarchive/test/test_write_disk_perms.c @@ -0,0 +1,455 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_disk_perms.c,v 1.8 2008/01/01 22:28:04 kientzle Exp $"); + +#if ARCHIVE_VERSION_STAMP >= 1009000 + +#define UMASK 022 + +static long _default_gid = -1; +static long _invalid_gid = -1; +static long _alt_gid = -1; + +/* + * To fully test SGID restores, we need three distinct GIDs to work + * with: + * * the GID that files are created with by default (for the + * current user in the current directory) + * * An "alt gid" that this user can create files with + * * An "invalid gid" that this user is not permitted to create + * files with. + * The second fails if this user doesn't belong to at least two groups; + * the third fails if the current user is root. + */ +static void +searchgid(void) +{ + static int _searched = 0; + uid_t uid = getuid(); + gid_t gid = 0; + unsigned int n; + struct stat st; + int fd; + + /* If we've already looked this up, we're done. */ + if (_searched) + return; + _searched = 1; + + /* Create a file on disk in the current default dir. */ + fd = open("test_gid", O_CREAT, 0664); + failure("Couldn't create a file for gid testing."); + assert(fd > 0); + + /* See what GID it ended up with. This is our "valid" GID. */ + assert(fstat(fd, &st) == 0); + _default_gid = st.st_gid; + + /* Find a GID for which fchown() fails. This is our "invalid" GID. */ + _invalid_gid = -1; + /* This loop stops when we wrap the gid or examine 10,000 gids. */ + for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) { + if (fchown(fd, uid, gid) != 0) { + _invalid_gid = gid; + break; + } + } + + /* + * Find a GID for which fchown() succeeds, but which isn't the + * default. This is the "alternate" gid. + */ + _alt_gid = -1; + for (gid = 0, n = 0; gid == n && n < 10000 ; n++, gid++) { + /* _alt_gid must be different than _default_gid */ + if (gid == (gid_t)_default_gid) + continue; + if (fchown(fd, uid, gid) == 0) { + _alt_gid = gid; + break; + } + } + close(fd); +} + +static int +altgid(void) +{ + searchgid(); + return (_alt_gid); +} + +static int +invalidgid(void) +{ + searchgid(); + return (_invalid_gid); +} + +static int +defaultgid(void) +{ + searchgid(); + return (_default_gid); +} +#endif + +/* + * Exercise permission and ownership restores. + * In particular, try to exercise a bunch of border cases related + * to files/dirs that already exist, SUID/SGID bits, etc. + */ + +DEFINE_TEST(test_write_disk_perms) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("archive_write_disk interface"); +#else + struct archive *a; + struct archive_entry *ae; + struct stat st; + + /* + * Set ownership of the current directory to the group of this + * process. Otherwise, the SGID tests below fail if the + * /tmp directory is owned by a group to which we don't belong + * and we're on a system where group ownership is inherited. + * (Because we're not allowed to SGID files with defaultgid().) + */ + assertEqualInt(0, chown(".", getuid(), getgid())); + + /* Create an archive_write_disk object. */ + assert((a = archive_write_disk_new()) != NULL); + + /* Write a regular file to it. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file_0755"); + archive_entry_set_mode(ae, S_IFREG | 0777); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + archive_entry_free(ae); + + /* Write a regular file, then write over it. */ + /* For files, the perms should get updated. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file_overwrite_0144"); + archive_entry_set_mode(ae, S_IFREG | 0777); + assert(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + /* Check that file was created with different perms. */ + assert(0 == stat("file_overwrite_0144", &st)); + failure("file_overwrite_0144: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) != 0144); + /* Overwrite, this should change the perms. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file_overwrite_0144"); + archive_entry_set_mode(ae, S_IFREG | 0144); + assert(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + + /* Write a regular dir. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir_0514"); + archive_entry_set_mode(ae, S_IFDIR | 0514); + assert(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + + /* Overwrite an existing dir. */ + /* For dir, the first perms should get left. */ + assert(mkdir("dir_overwrite_0744", 0744) == 0); + /* Check original perms. */ + assert(0 == stat("dir_overwrite_0744", &st)); + failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0744); + /* Overwrite shouldn't edit perms. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir_overwrite_0744"); + archive_entry_set_mode(ae, S_IFDIR | 0777); + assert(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + /* Make sure they're unchanged. */ + assert(0 == stat("dir_overwrite_0744", &st)); + failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0744); + + /* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "file_no_suid"); + archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777); + archive_write_disk_set_options(a, 0); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + + /* Write a regular file with ARCHIVE_EXTRACT_PERM. */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_0777"); + archive_entry_set_mode(ae, S_IFREG | 0777); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + + /* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_4742"); + archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); + archive_entry_set_uid(ae, getuid()); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + + /* + * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit, + * but wrong uid. POSIX says you shouldn't restore SUID bit + * unless the UID could be restored. + */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_bad_suid"); + archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); + archive_entry_set_uid(ae, getuid() + 1); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); + assertA(0 == archive_write_header(a, ae)); + /* + * Because we didn't ask for owner, the failure to + * restore SUID shouldn't return a failure. + * We check below to make sure SUID really wasn't set. + * See more detailed comments below. + */ + failure("Opportunistic SUID failure shouldn't return error."); + assertEqualInt(0, archive_write_finish_entry(a)); + + if (getuid() != 0) { + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_bad_suid2"); + archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); + archive_entry_set_uid(ae, getuid() + 1); + archive_write_disk_set_options(a, + ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); + assertA(0 == archive_write_header(a, ae)); + /* Owner change should fail here. */ + failure("Non-opportunistic SUID failure should return error."); + assertEqualInt(ARCHIVE_WARN, archive_write_finish_entry(a)); + } + + /* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_perm_sgid"); + archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); + archive_entry_set_gid(ae, defaultgid()); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); + assert(0 == archive_write_header(a, ae)); + failure("Setting SGID bit should succeed here."); + assertEqualIntA(a, 0, archive_write_finish_entry(a)); + + if (altgid() == -1) { + /* + * Current user must belong to at least two groups or + * else we can't test setting the GID to another group. + */ + printf("Current user can't test gid restore: must belong to more than one group.\n"); + } else { + /* + * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit + * but without ARCHIVE_EXTRACT_OWNER. + */ + /* + * This is a weird case: The user has asked for permissions to + * be restored but not asked for ownership to be restored. As + * a result, the default file creation will create a file with + * the wrong group. There are several possible behaviors for + * libarchive in this scenario: + * = Set the SGID bit. It is wrong and a security hole to + * set SGID with the wrong group. Even POSIX thinks so. + * = Implicitly set the group. I don't like this. + * = drop the SGID bit and warn (the old libarchive behavior) + * = drop the SGID bit and don't warn (the current libarchive + * behavior). + * The current behavior sees SGID/SUID restore when you + * don't ask for owner restore as an "opportunistic" + * action. That is, libarchive should do it if it can, + * but if it can't, it's not an error. + */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_alt_sgid"); + archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); + archive_entry_set_uid(ae, getuid()); + archive_entry_set_gid(ae, altgid()); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); + assert(0 == archive_write_header(a, ae)); + failure("Setting SGID bit should fail because of group mismatch but the failure should be silent because we didn't ask for the group to be set."); + assertEqualIntA(a, 0, archive_write_finish_entry(a)); + + /* + * As above, but add _EXTRACT_OWNER to verify that it + * does succeed. + */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_alt_sgid_owner"); + archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); + archive_entry_set_uid(ae, getuid()); + archive_entry_set_gid(ae, altgid()); + archive_write_disk_set_options(a, + ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); + assert(0 == archive_write_header(a, ae)); + failure("Setting SGID bit should succeed here."); + assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a)); + } + + /* + * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit, + * but wrong GID. POSIX says you shouldn't restore SGID bit + * unless the GID could be restored. + */ + if (invalidgid() == -1) { + /* This test always fails for root. */ + printf("Running as root: Can't test SGID failures.\n"); + } else { + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_bad_sgid"); + archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); + archive_entry_set_gid(ae, invalidgid()); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); + assertA(0 == archive_write_header(a, ae)); + failure("This SGID restore should fail without an error."); + assertEqualIntA(a, 0, archive_write_finish_entry(a)); + + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_bad_sgid2"); + archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); + archive_entry_set_gid(ae, invalidgid()); + archive_write_disk_set_options(a, + ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); + assertA(0 == archive_write_header(a, ae)); + failure("This SGID restore should fail with an error."); + assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a)); + } + + /* Set ownership should fail if we're not root. */ + if (getuid() == 0) { + printf("Running as root: Can't test setuid failures.\n"); + } else { + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "file_bad_owner"); + archive_entry_set_mode(ae, S_IFREG | 0744); + archive_entry_set_uid(ae, getuid() + 1); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER); + assertA(0 == archive_write_header(a, ae)); + assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a)); + } + +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + archive_entry_free(ae); + + /* Test the entries on disk. */ + assert(0 == stat("file_0755", &st)); + failure("file_0755: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); + + assert(0 == stat("file_overwrite_0144", &st)); + failure("file_overwrite_0144: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0144); + + assert(0 == stat("dir_0514", &st)); + failure("dir_0514: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0514); + + assert(0 == stat("dir_overwrite_0744", &st)); + failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0744); + + assert(0 == stat("file_no_suid", &st)); + failure("file_0755: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); + + assert(0 == stat("file_0777", &st)); + failure("file_0777: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0777); + + /* SUID bit should get set here. */ + assert(0 == stat("file_4742", &st)); + failure("file_4742: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (S_ISUID | 0742)); + + /* SUID bit should NOT have been set here. */ + assert(0 == stat("file_bad_suid", &st)); + failure("file_bad_suid: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (0742)); + + /* Some things don't fail if you're root, so suppress this. */ + if (getuid() != 0) { + /* SUID bit should NOT have been set here. */ + assert(0 == stat("file_bad_suid2", &st)); + failure("file_bad_suid2: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (0742)); + } + + /* SGID should be set here. */ + assert(0 == stat("file_perm_sgid", &st)); + failure("file_perm_sgid: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (S_ISGID | 0742)); + + if (altgid() != -1) { + /* SGID should not be set here. */ + assert(0 == stat("file_alt_sgid", &st)); + failure("file_alt_sgid: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (0742)); + + /* SGID should be set here. */ + assert(0 == stat("file_alt_sgid_owner", &st)); + failure("file_alt_sgid: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (S_ISGID | 0742)); + } + + if (invalidgid() != -1) { + /* SGID should NOT be set here. */ + assert(0 == stat("file_bad_sgid", &st)); + failure("file_bad_sgid: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (0742)); + /* SGID should NOT be set here. */ + assert(0 == stat("file_bad_sgid2", &st)); + failure("file_bad_sgid2: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (0742)); + } + + if (getuid() != 0) { + assert(0 == stat("file_bad_owner", &st)); + failure("file_bad_owner: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == (0744)); + failure("file_bad_owner: st.st_uid=%d getuid()=%d", + st.st_uid, getuid()); + /* The entry had getuid()+1, but because we're + * not root, we should not have been able to set that. */ + assert(st.st_uid == getuid()); + } +#endif +} diff --git a/libarchive/test/test_write_disk_secure.c b/libarchive/test/test_write_disk_secure.c new file mode 100644 index 00000000..80876b46 --- /dev/null +++ b/libarchive/test/test_write_disk_secure.c @@ -0,0 +1,147 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_disk_secure.c,v 1.3 2007/07/06 15:43:11 kientzle Exp $"); + +#define UMASK 022 + +/* + * Exercise security checks that should prevent certain + * writes. + */ + +DEFINE_TEST(test_write_disk_secure) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("archive_write_disk interface"); +#else + struct archive *a; + struct archive_entry *ae; + struct stat st; + + /* Start with a known umask. */ + umask(UMASK); + + /* Create an archive_write_disk object. */ + assert((a = archive_write_disk_new()) != NULL); + + /* Write a regular dir to it. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "dir"); + archive_entry_set_mode(ae, S_IFDIR | 0777); + assert(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + + /* Write a symlink to the dir above. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link_to_dir"); + archive_entry_set_mode(ae, S_IFLNK | 0777); + archive_entry_set_symlink(ae, "dir"); + archive_write_disk_set_options(a, 0); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + + /* + * Without security checks, we should be able to + * extract a file through the link. + */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "link_to_dir/filea"); + archive_entry_set_mode(ae, S_IFREG | 0777); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + + /* But with security checks enabled, this should fail. */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "link_to_dir/fileb"); + archive_entry_set_mode(ae, S_IFREG | 0777); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); + failure("Extracting a file through a symlink should fail here."); + assertEqualInt(ARCHIVE_WARN, archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + + /* Create another link. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "link_to_dir2"); + archive_entry_set_mode(ae, S_IFLNK | 0777); + archive_entry_set_symlink(ae, "dir"); + archive_write_disk_set_options(a, 0); + assert(0 == archive_write_header(a, ae)); + assert(0 == archive_write_finish_entry(a)); + + /* + * With symlink check and unlink option, it should remove + * the link and create the dir. + */ + assert(archive_entry_clear(ae) != NULL); + archive_entry_copy_pathname(ae, "link_to_dir2/filec"); + archive_entry_set_mode(ae, S_IFREG | 0777); + archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_UNLINK); + assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); + archive_entry_free(ae); + assert(0 == archive_write_finish_entry(a)); + + +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Test the entries on disk. */ + assert(0 == lstat("dir", &st)); + failure("dir: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); + + assert(0 == lstat("link_to_dir", &st)); + failure("link_to_dir: st.st_mode=%o", st.st_mode); + assert(S_ISLNK(st.st_mode)); +#if HAVE_LCHMOD + /* Systems that lack lchmod() can't set symlink perms, so skip this. */ + failure("link_to_dir: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); +#endif + + assert(0 == lstat("dir/filea", &st)); + failure("dir/filea: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); + + failure("dir/fileb: This file should not have been created"); + assert(0 != lstat("dir/fileb", &st)); + + assert(0 == lstat("link_to_dir2", &st)); + failure("link_to_dir2 should have been re-created as a true dir"); + assert(S_ISDIR(st.st_mode)); + failure("link_to_dir2: Implicit dir creation should obey umask, but st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); + + assert(0 == lstat("link_to_dir2/filec", &st)); + assert(S_ISREG(st.st_mode)); + failure("link_to_dir2/filec: st.st_mode=%o", st.st_mode); + assert((st.st_mode & 07777) == 0755); +#endif +} diff --git a/libarchive/test/test_write_format_ar.c b/libarchive/test/test_write_format_ar.c new file mode 100644 index 00000000..6c7a4462 --- /dev/null +++ b/libarchive/test/test_write_format_ar.c @@ -0,0 +1,210 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 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. + */ + +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_ar.c,v 1.6 2008/03/12 21:10:26 kaiw Exp $"); + +char buff[4096]; +char buff2[64]; +static unsigned char strtab[] = "abcdefghijklmn.o/\nggghhhjjjrrrttt.o/\niiijjjdddsssppp.o/\n"; + +DEFINE_TEST(test_write_format_ar) +{ +#if ARCHIVE_VERSION_STAMP < 1009000 + skipping("ar write support"); +#else + struct archive_entry *ae; + struct archive* a; + size_t used; + + /* + * First we try to create a SVR4/GNU format archive. + */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ar_svr4(a)); + assertA(0 == archive_write_set_compression_gzip(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* write the filename table */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "//"); + archive_entry_set_size(ae, strlen(strtab)); + assertA(0 == archive_write_header(a, ae)); + assertA(strlen(strtab) == (size_t)archive_write_data(a, strtab, strlen(strtab))); + archive_entry_free(ae); + + /* write entries */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 1, 0); + assert(1 == archive_entry_mtime(ae)); + archive_entry_set_mode(ae, S_IFREG | 0755); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + archive_entry_copy_pathname(ae, "abcdefghijklmn.o"); + archive_entry_set_size(ae, 8); + assertA(0 == archive_write_header(a, ae)); + assertA(8 == archive_write_data(a, "87654321", 15)); + archive_entry_free(ae); + + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "ggghhhjjjrrrttt.o"); + archive_entry_set_filetype(ae, AE_IFREG); + archive_entry_set_size(ae, 7); + assertA(0 == archive_write_header(a, ae)); + assertA(7 == archive_write_data(a, "7777777", 7)); + archive_entry_free(ae); + + /* test full pathname */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "/usr/home/xx/iiijjjdddsssppp.o"); + archive_entry_set_mode(ae, S_IFREG | 0755); + archive_entry_set_size(ae, 8); + assertA(0 == archive_write_header(a, ae)); + assertA(8 == archive_write_data(a, "88877766", 8)); + archive_entry_free(ae); + + /* trailing "/" should be rejected */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "/usr/home/xx/iiijjj/"); + archive_entry_set_size(ae, 8); + assertA(0 != archive_write_header(a, ae)); + archive_entry_free(ae); + + /* Non regular file should be rejected */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "gfgh.o"); + archive_entry_set_mode(ae, S_IFDIR | 0755); + archive_entry_set_size(ae, 6); + assertA(0 != archive_write_header(a, ae)); + archive_entry_free(ae); + + archive_write_close(a); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Now, read the data back. + */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualInt(0, archive_entry_mtime(ae)); + assertEqualString("//", archive_entry_pathname(ae)); + assertEqualInt(0, archive_entry_size(ae)); + + assertA(0 == archive_read_next_header(a, &ae)); + assert(1 == archive_entry_mtime(ae)); + assertEqualString("abcdefghijklmn.o", archive_entry_pathname(ae)); + assert(8 == archive_entry_size(ae)); + assertA(8 == archive_read_data(a, buff2, 10)); + assert(0 == memcmp(buff2, "87654321", 8)); + + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("ggghhhjjjrrrttt.o", archive_entry_pathname(ae)); + assert(7 == archive_entry_size(ae)); + assertA(7 == archive_read_data(a, buff2, 11)); + assert(0 == memcmp(buff2, "7777777", 7)); + + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("iiijjjdddsssppp.o", archive_entry_pathname(ae)); + assert(8 == archive_entry_size(ae)); + assertA(8 == archive_read_data(a, buff2, 17)); + assert(0 == memcmp(buff2, "88877766", 8)); + + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + /* + * Then, we try to create a BSD format archive. + */ + memset(buff, 0, sizeof(buff)); + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ar_bsd(a)); + assertA(0 == archive_write_set_compression_bzip2(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* write a entry need long name extension */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "ttttyyyyuuuuiiii.o"); + archive_entry_set_filetype(ae, AE_IFREG); + archive_entry_set_size(ae, 5); + assertA(0 == archive_write_header(a, ae)); + assertA(5 == archive_write_data(a, "12345", 7)); + archive_entry_free(ae); + + /* write a entry with a short name */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_copy_pathname(ae, "ttyy.o"); + archive_entry_set_filetype(ae, AE_IFREG); + archive_entry_set_size(ae, 6); + assertA(0 == archive_write_header(a, ae)); + assertA(6 == archive_write_data(a, "555555", 7)); + archive_entry_free(ae); + archive_write_close(a); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* Now, Read the data back */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + assertEqualString("ttttyyyyuuuuiiii.o", archive_entry_pathname(ae)); + assertEqualInt(5, archive_entry_size(ae)); + assertA(5 == archive_read_data(a, buff2, 10)); + assert(0 == memcmp(buff2, "12345", 5)); + + assert(0 == archive_read_next_header(a, &ae)); + assertEqualString("ttyy.o", archive_entry_pathname(ae)); + assert(6 == archive_entry_size(ae)); + assertA(6 == archive_read_data(a, buff2, 10)); + assert(0 == memcmp(buff2, "555555", 6)); + + /* Test EOF */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif +#endif +} diff --git a/libarchive/test/test_write_format_cpio.c b/libarchive/test/test_write_format_cpio.c new file mode 100644 index 00000000..dbb97755 --- /dev/null +++ b/libarchive/test/test_write_format_cpio.c @@ -0,0 +1,203 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_cpio.c,v 1.4 2008/01/01 22:28:04 kientzle Exp $"); + +/* The version stamp macro was introduced after cpio write support. */ +#if ARCHIVE_VERSION_STAMP >= 1009000 +static void +test_format(int (*set_format)(struct archive *)) +{ + char filedata[64]; + struct archive_entry *ae; + struct archive *a; + char *p; + size_t used; + size_t buffsize = 1000000; + char *buff; + int damaged = 0; + + buff = malloc(buffsize); + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == (*set_format)(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_open_memory(a, buff, buffsize, &used)); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 1, 10); + assert(1 == archive_entry_mtime(ae)); + assert(10 == archive_entry_mtime_nsec(ae)); + p = strdup("file"); + archive_entry_copy_pathname(ae, p); + strcpy(p, "XXXX"); + free(p); + assertEqualString("file", archive_entry_pathname(ae)); + archive_entry_set_mode(ae, S_IFREG | 0755); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + archive_entry_set_size(ae, 8); + + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(8 == archive_write_data(a, "12345678", 9)); + + /* + * Write another file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 1, 10); + assert(1 == archive_entry_mtime(ae)); + assert(10 == archive_entry_mtime_nsec(ae)); + p = strdup("file2"); + archive_entry_copy_pathname(ae, p); + strcpy(p, "XXXX"); + free(p); + assertEqualString("file2", archive_entry_pathname(ae)); + archive_entry_set_mode(ae, S_IFREG | 0755); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + archive_entry_set_size(ae, 4); + + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(4 == archive_write_data(a, "1234", 5)); + + /* + * Write a directory to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 11, 110); + archive_entry_copy_pathname(ae, "dir"); + archive_entry_set_mode(ae, S_IFDIR | 0755); + archive_entry_set_size(ae, 512); + + assertA(0 == archive_write_header(a, ae)); + assertEqualInt(0, archive_entry_size(ae)); + archive_entry_free(ae); + assertEqualIntA(a, 0, archive_write_data(a, "12345678", 9)); + + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Damage the second entry to test the search-ahead recovery. + */ + { + int i; + for (i = 80; i < 150; i++) { + if (memcmp(buff + i, "07070", 5) == 0) { + damaged = 1; + buff[i] = 'X'; + break; + } + } + } + failure("Unable to locate the second header for damage-recovery test."); + assert(damaged = 1); + + /* + * Now, read the data back. + */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); + + assertEqualInt(1, archive_entry_mtime(ae)); + /* Not the same as above: cpio doesn't store hi-res times. */ + assert(0 == archive_entry_mtime_nsec(ae)); + assert(0 == archive_entry_atime(ae)); + assert(0 == archive_entry_ctime(ae)); + assertEqualString("file", archive_entry_pathname(ae)); + assertEqualInt((S_IFREG | 0755), archive_entry_mode(ae)); + assertEqualInt(8, archive_entry_size(ae)); + assertA(8 == archive_read_data(a, filedata, 10)); + assert(0 == memcmp(filedata, "12345678", 8)); + + /* + * Read the second file back. + */ + if (!damaged) { + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt(1, archive_entry_mtime(ae)); + /* Not the same as above: cpio doesn't store hi-res times. */ + assert(0 == archive_entry_mtime_nsec(ae)); + assert(0 == archive_entry_atime(ae)); + assert(0 == archive_entry_ctime(ae)); + assertEqualString("file2", archive_entry_pathname(ae)); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + assertEqualInt(4, archive_entry_size(ae)); + assertEqualIntA(a, 4, archive_read_data(a, filedata, 10)); + assert(0 == memcmp(filedata, "1234", 4)); + } + + /* + * Read the dir entry back. + */ + assertEqualIntA(a, + damaged ? ARCHIVE_WARN : ARCHIVE_OK, + archive_read_next_header(a, &ae)); + assertEqualInt(11, archive_entry_mtime(ae)); + assert(0 == archive_entry_mtime_nsec(ae)); + assert(0 == archive_entry_atime(ae)); + assert(0 == archive_entry_ctime(ae)); + assertEqualString("dir", archive_entry_pathname(ae)); + assertEqualInt((S_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualInt(0, archive_entry_size(ae)); + assertEqualIntA(a, 0, archive_read_data(a, filedata, 10)); + + /* Verify the end of the archive. */ + assertEqualIntA(a, 1, archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + + free(buff); +} +#endif + +DEFINE_TEST(test_write_format_cpio) +{ +#if ARCHIVE_VERSION_STAMP >= 1009000 + test_format(archive_write_set_format_cpio); + test_format(archive_write_set_format_cpio_newc); +#else + skipping("cpio write support"); +#endif +} diff --git a/libarchive/test/test_write_format_cpio_empty.c b/libarchive/test/test_write_format_cpio_empty.c new file mode 100644 index 00000000..61c49211 --- /dev/null +++ b/libarchive/test/test_write_format_cpio_empty.c @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_cpio_empty.c,v 1.2 2007/05/29 01:00:21 kientzle Exp $"); + +/* + * Check that an "empty" cpio archive is correctly created. + */ + +/* Here's what an empty cpio archive should look like. */ +static char ref[] = +"070707" /* Magic number */ +"000000" /* Dev = 0 */ +"000000" /* ino = 0 */ +"000000" /* mode = 0 */ +"000000" /* uid = 0 */ +"000000" /* gid = 0 */ +"000001" /* nlink = 1 */ +"000000" /* rdev = 0 */ +"00000000000" /* mtime = 0 */ +"000013" /* Namesize = 11 */ +"00000000000" /* filesize = 0 */ +"TRAILER!!!\0"; /* Name */ + +DEFINE_TEST(test_write_format_cpio_empty) +{ + struct archive *a; + char buff[2048]; + size_t used; + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_cpio(a)); + assertA(0 == archive_write_set_compression_none(a)); + /* 1-byte block size ensures we see only the required bytes. */ + /* We're not testing the padding here. */ + assertA(0 == archive_write_set_bytes_per_block(a, 1)); + assertA(0 == archive_write_set_bytes_in_last_block(a, 1)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + failure("Empty cpio archive should be exactly 87 bytes, was %d.", used); + assert(used == 87); + failure("Empty cpio archive is incorrectly formatted."); + assert(memcmp(buff, ref, 87) == 0); +} diff --git a/libarchive/test/test_write_format_cpio_newc.c b/libarchive/test/test_write_format_cpio_newc.c new file mode 100644 index 00000000..431097b2 --- /dev/null +++ b/libarchive/test/test_write_format_cpio_newc.c @@ -0,0 +1,211 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_cpio_newc.c,v 1.2 2008/01/23 05:43:26 kientzle Exp $"); + + +static int +is_hex(const char *p, size_t l) +{ + while (l > 0) { + if (*p >= 0 && *p <= '9') { + /* Ascii digit */ + } else if (*p >= 'a' && *p <= 'f') { + /* lowercase letter a-f */ + } else { + /* Not hex. */ + return (0); + } + --l; + ++p; + } + return (1); +} + +/* + * Detailed verification that cpio 'newc' archives are written with + * the correct format. + */ +DEFINE_TEST(test_write_format_cpio_newc) +{ + struct archive *a; + struct archive_entry *entry; + char *buff, *e; + size_t buffsize = 100000; + size_t used; + + buff = malloc(buffsize); + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertEqualIntA(a, 0, archive_write_set_format_cpio_newc(a)); + assertEqualIntA(a, 0, archive_write_set_compression_none(a)); + assertEqualIntA(a, 0, archive_write_open_memory(a, buff, buffsize, &used)); + + /* + * Add various files to it. + * TODO: Extend this to cover more filetypes. + */ + + /* Regular file */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 1, 10); + archive_entry_set_pathname(entry, "file"); + archive_entry_set_mode(entry, S_IFREG | 0664); + archive_entry_set_size(entry, 10); + archive_entry_set_uid(entry, 80); + archive_entry_set_gid(entry, 90); + archive_entry_set_dev(entry, 12); + archive_entry_set_ino(entry, 89); + archive_entry_set_nlink(entry, 1); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + assertEqualIntA(a, 10, archive_write_data(a, "1234567890", 10)); + + /* Directory */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 2, 20); + archive_entry_set_pathname(entry, "dir"); + archive_entry_set_mode(entry, S_IFDIR | 0775); + archive_entry_set_size(entry, 10); + archive_entry_set_nlink(entry, 2); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + assertEqualIntA(a, 0, archive_write_data(a, "1234567890", 10)); + + /* Symlink */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 3, 30); + archive_entry_set_pathname(entry, "lnk"); + archive_entry_set_mode(entry, S_IFLNK | 0664); + archive_entry_set_size(entry, 0); + archive_entry_set_uid(entry, 83); + archive_entry_set_gid(entry, 93); + archive_entry_set_dev(entry, 13); + archive_entry_set_ino(entry, 88); + archive_entry_set_nlink(entry, 1); + archive_entry_set_symlink(entry,"a"); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + + +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Verify the archive format. + */ + e = buff; + + /* First entry is "file" */ + assert(is_hex(e, 110)); /* Entire header is hex digits. */ + assertEqualMem(e + 0, "070701", 6); /* Magic */ + assertEqualMem(e + 6, "00000059", 8); /* ino */ + assertEqualMem(e + 14, "000081b4", 8); /* Mode */ + assertEqualMem(e + 22, "00000050", 8); /* uid */ + assertEqualMem(e + 30, "0000005a", 8); /* gid */ + assertEqualMem(e + 38, "00000001", 8); /* nlink */ + assertEqualMem(e + 46, "00000001", 8); /* mtime */ + assertEqualMem(e + 54, "0000000a", 8); /* File size */ + assertEqualMem(e + 62, "00000000", 8); /* devmajor */ + assertEqualMem(e + 70, "0000000c", 8); /* devminor */ + assertEqualMem(e + 78, "00000000", 8); /* rdevmajor */ + assertEqualMem(e + 86, "00000000", 8); /* rdevminor */ + assertEqualMem(e + 94, "00000005", 8); /* Name size */ + assertEqualMem(e + 102, "00000000", 8); /* CRC */ + assertEqualMem(e + 110, "file\0\0", 6); /* Name contents */ + assertEqualMem(e + 116, "1234567890", 10); /* File body */ + assertEqualMem(e + 126, "\0\0", 2); /* Pad to multiple of 4 */ + e += 128; /* Must be multiple of four here! */ + + /* Second entry is "dir" */ + assert(is_hex(e, 110)); + assertEqualMem(e + 0, "070701", 6); /* Magic */ + assertEqualMem(e + 6, "00000000", 8); /* ino */ + assertEqualMem(e + 14, "000041fd", 8); /* Mode */ + assertEqualMem(e + 22, "00000000", 8); /* uid */ + assertEqualMem(e + 30, "00000000", 8); /* gid */ + assertEqualMem(e + 38, "00000002", 8); /* nlink */ + assertEqualMem(e + 46, "00000002", 8); /* mtime */ + assertEqualMem(e + 54, "00000000", 8); /* File size */ + assertEqualMem(e + 62, "00000000", 8); /* devmajor */ + assertEqualMem(e + 70, "00000000", 8); /* devminor */ + assertEqualMem(e + 78, "00000000", 8); /* rdevmajor */ + assertEqualMem(e + 86, "00000000", 8); /* rdevminor */ + assertEqualMem(e + 94, "00000004", 8); /* Name size */ + assertEqualMem(e + 102, "00000000", 8); /* CRC */ + assertEqualMem(e + 110, "dir\0", 4); /* name */ + assertEqualMem(e + 114, "\0\0", 2); /* Pad to multiple of 4 */ + e += 116; /* Must be multiple of four here! */ + + /* Third entry is "lnk" */ + assert(is_hex(e, 110)); /* Entire header is hex digits. */ + assertEqualMem(e + 0, "070701", 6); /* Magic */ + assertEqualMem(e + 6, "00000058", 8); /* ino */ + assertEqualMem(e + 14, "0000a1b4", 8); /* Mode */ + assertEqualMem(e + 22, "00000053", 8); /* uid */ + assertEqualMem(e + 30, "0000005d", 8); /* gid */ + assertEqualMem(e + 38, "00000001", 8); /* nlink */ + assertEqualMem(e + 46, "00000003", 8); /* mtime */ + assertEqualMem(e + 54, "00000001", 8); /* File size */ + assertEqualMem(e + 62, "00000000", 8); /* devmajor */ + assertEqualMem(e + 70, "0000000d", 8); /* devminor */ + assertEqualMem(e + 78, "00000000", 8); /* rdevmajor */ + assertEqualMem(e + 86, "00000000", 8); /* rdevminor */ + assertEqualMem(e + 94, "00000004", 8); /* Name size */ + assertEqualMem(e + 102, "00000000", 8); /* CRC */ + assertEqualMem(e + 110, "lnk\0\0\0", 6); /* Name contents */ + assertEqualMem(e + 116, "a\0\0\0", 4); /* File body + pad */ + e += 120; /* Must be multiple of four here! */ + + /* TODO: Verify other types of entries. */ + + /* Last entry is end-of-archive marker. */ + assert(is_hex(e, 76)); + assertEqualMem(e + 0, "070701", 6); /* Magic */ + assertEqualMem(e + 6, "00000000", 8); /* ino */ + assertEqualMem(e + 14, "00000000", 8); /* Mode */ + assertEqualMem(e + 22, "00000000", 8); /* uid */ + assertEqualMem(e + 30, "00000000", 8); /* gid */ + assertEqualMem(e + 38, "00000001", 8); /* nlink */ + assertEqualMem(e + 46, "00000000", 8); /* mtime */ + assertEqualMem(e + 54, "00000000", 8); /* File size */ + assertEqualMem(e + 62, "00000000", 8); /* devmajor */ + assertEqualMem(e + 70, "00000000", 8); /* devminor */ + assertEqualMem(e + 78, "00000000", 8); /* rdevmajor */ + assertEqualMem(e + 86, "00000000", 8); /* rdevminor */ + assertEqualMem(e + 94, "0000000b", 8); /* Name size */ + assertEqualMem(e + 102, "00000000", 8); /* CRC */ + assertEqualMem(e + 110, "TRAILER!!!\0", 11); /* Name */ + assertEqualMem(e + 121, "\0\0\0", 3); /* Pad to multiple of 4 bytes */ + e += 124; /* Must be multiple of four here! */ + + assertEqualInt(used, e - buff); + + free(buff); +} diff --git a/libarchive/test/test_write_format_cpio_odc.c b/libarchive/test/test_write_format_cpio_odc.c new file mode 100644 index 00000000..e6ed7eed --- /dev/null +++ b/libarchive/test/test_write_format_cpio_odc.c @@ -0,0 +1,224 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_cpio_odc.c,v 1.1 2008/01/01 22:28:04 kientzle Exp $"); + + +static int +is_octal(const char *p, size_t l) +{ + while (l > 0) { + if (*p < '0' || *p > '7') + return (0); + --l; + ++p; + } + return (1); +} + +/* + * Detailed verification that cpio 'odc' archives are written with + * the correct format. + */ +DEFINE_TEST(test_write_format_cpio_odc) +{ + struct archive *a; + struct archive_entry *entry; + char *buff, *e; + size_t buffsize = 100000; + size_t used; + + buff = malloc(buffsize); + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertEqualIntA(a, 0, archive_write_set_format_cpio(a)); + assertEqualIntA(a, 0, archive_write_set_compression_none(a)); + assertEqualIntA(a, 0, archive_write_open_memory(a, buff, buffsize, &used)); + + /* + * Add various files to it. + * TODO: Extend this to cover more filetypes. + */ + + /* "file" with 10 bytes of content */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 1, 10); + archive_entry_set_pathname(entry, "file"); + archive_entry_set_mode(entry, S_IFREG | 0664); + archive_entry_set_size(entry, 10); + archive_entry_set_uid(entry, 80); + archive_entry_set_gid(entry, 90); + archive_entry_set_dev(entry, 12); + archive_entry_set_ino(entry, 89); + archive_entry_set_nlink(entry, 2); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + assertEqualIntA(a, 10, archive_write_data(a, "1234567890", 10)); + + /* Hardlink to "file" with 10 bytes of content */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 1, 10); + archive_entry_set_pathname(entry, "linkfile"); + archive_entry_set_mode(entry, S_IFREG | 0664); + archive_entry_set_size(entry, 10); + archive_entry_set_uid(entry, 80); + archive_entry_set_gid(entry, 90); + archive_entry_set_dev(entry, 12); + archive_entry_set_ino(entry, 89); + archive_entry_set_nlink(entry, 2); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + assertEqualIntA(a, 10, archive_write_data(a, "1234567890", 10)); + + /* "dir" */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 2, 20); + archive_entry_set_pathname(entry, "dir"); + archive_entry_set_mode(entry, S_IFDIR | 0775); + archive_entry_set_size(entry, 10); + archive_entry_set_nlink(entry, 2); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + /* Write of data to dir should fail == zero bytes get written. */ + assertEqualIntA(a, 0, archive_write_data(a, "1234567890", 10)); + + /* "symlink" pointing to "file" */ + assert((entry = archive_entry_new()) != NULL); + archive_entry_set_mtime(entry, 3, 30); + archive_entry_set_pathname(entry, "symlink"); + archive_entry_set_mode(entry, S_IFLNK | 0664); + archive_entry_set_symlink(entry,"file"); + archive_entry_set_size(entry, 0); + archive_entry_set_uid(entry, 88); + archive_entry_set_gid(entry, 98); + archive_entry_set_dev(entry, 12); + archive_entry_set_ino(entry, 90); + archive_entry_set_nlink(entry, 1); + assertEqualIntA(a, 0, archive_write_header(a, entry)); + archive_entry_free(entry); + /* Write of data to symlink should fail == zero bytes get written. */ + assertEqualIntA(a, 0, archive_write_data(a, "1234567890", 10)); + +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + /* + * Verify the archive format. + */ + e = buff; + + /* "file" */ + assert(is_octal(e, 76)); /* Entire header is octal digits. */ + assertEqualMem(e + 0, "070707", 6); /* Magic */ + assertEqualMem(e + 6, "000014", 6); /* dev */ + assertEqualMem(e + 12, "000131", 6); /* ino */ + assertEqualMem(e + 18, "100664", 6); /* Mode */ + assertEqualMem(e + 24, "000120", 6); /* uid */ + assertEqualMem(e + 30, "000132", 6); /* gid */ + assertEqualMem(e + 36, "000002", 6); /* nlink */ + assertEqualMem(e + 42, "000000", 6); /* rdev */ + assertEqualMem(e + 48, "00000000001", 11); /* mtime */ + assertEqualMem(e + 59, "000005", 6); /* Name size */ + assertEqualMem(e + 65, "00000000012", 11); /* File size */ + assertEqualMem(e + 76, "file\0", 5); /* Name contents */ + assertEqualMem(e + 81, "1234567890", 10); /* File contents */ + e += 91; + + /* hardlink to "file" */ + assert(is_octal(e, 76)); /* Entire header is octal digits. */ + assertEqualMem(e + 0, "070707", 6); /* Magic */ + assertEqualMem(e + 6, "000014", 6); /* dev */ + assertEqualMem(e + 12, "000131", 6); /* ino */ + assertEqualMem(e + 18, "100664", 6); /* Mode */ + assertEqualMem(e + 24, "000120", 6); /* uid */ + assertEqualMem(e + 30, "000132", 6); /* gid */ + assertEqualMem(e + 36, "000002", 6); /* nlink */ + assertEqualMem(e + 42, "000000", 6); /* rdev */ + assertEqualMem(e + 48, "00000000001", 11); /* mtime */ + assertEqualMem(e + 59, "000011", 6); /* Name size */ + assertEqualMem(e + 65, "00000000012", 11); /* File size */ + assertEqualMem(e + 76, "linkfile\0", 9); /* Name contents */ + assertEqualMem(e + 85, "1234567890", 10); /* File contents */ + e += 95; + + /* "dir" */ + assert(is_octal(e, 76)); + assertEqualMem(e + 0, "070707", 6); /* Magic */ + assertEqualMem(e + 6, "000000", 6); /* dev */ + assertEqualMem(e + 12, "000000", 6); /* ino */ + assertEqualMem(e + 18, "040775", 6); /* Mode */ + assertEqualMem(e + 24, "000000", 6); /* uid */ + assertEqualMem(e + 30, "000000", 6); /* gid */ + assertEqualMem(e + 36, "000002", 6); /* Nlink */ + assertEqualMem(e + 42, "000000", 6); /* rdev */ + assertEqualMem(e + 48, "00000000002", 11); /* mtime */ + assertEqualMem(e + 59, "000004", 6); /* Name size */ + assertEqualMem(e + 65, "00000000000", 11); /* File size */ + assertEqualMem(e + 76, "dir\0", 4); /* name */ + e += 80; + + /* "symlink" pointing to "file" */ + assert(is_octal(e, 76)); /* Entire header is octal digits. */ + assertEqualMem(e + 0, "070707", 6); /* Magic */ + assertEqualMem(e + 6, "000014", 6); /* dev */ + assertEqualMem(e + 12, "000132", 6); /* ino */ + assertEqualMem(e + 18, "120664", 6); /* Mode */ + assertEqualMem(e + 24, "000130", 6); /* uid */ + assertEqualMem(e + 30, "000142", 6); /* gid */ + assertEqualMem(e + 36, "000001", 6); /* nlink */ + assertEqualMem(e + 42, "000000", 6); /* rdev */ + assertEqualMem(e + 48, "00000000003", 11); /* mtime */ + assertEqualMem(e + 59, "000010", 6); /* Name size */ + assertEqualMem(e + 65, "00000000004", 11); /* File size */ + assertEqualMem(e + 76, "symlink\0", 8); /* Name contents */ + assertEqualMem(e + 84, "file", 4); /* File contents == link target */ + e += 88; + + /* TODO: Verify other types of entries. */ + + /* Last entry is end-of-archive marker. */ + assert(is_octal(e, 76)); + assertEqualMem(e + 0, "070707", 6); /* Magic */ + assertEqualMem(e + 6, "000000", 6); /* dev */ + assertEqualMem(e + 12, "000000", 6); /* ino */ + assertEqualMem(e + 18, "000000", 6); /* Mode */ + assertEqualMem(e + 24, "000000", 6); /* uid */ + assertEqualMem(e + 30, "000000", 6); /* gid */ + assertEqualMem(e + 36, "000001", 6); /* Nlink */ + assertEqualMem(e + 42, "000000", 6); /* rdev */ + assertEqualMem(e + 48, "00000000000", 11); /* mtime */ + assertEqualMem(e + 59, "000013", 6); /* Name size */ + assertEqualMem(e + 65, "00000000000", 11); /* File size */ + assertEqualMem(e + 76, "TRAILER!!!\0", 11); /* Name */ + e += 87; + + assertEqualInt(used, e - buff); + + free(buff); +} diff --git a/libarchive/test/test_write_format_shar_empty.c b/libarchive/test/test_write_format_shar_empty.c new file mode 100644 index 00000000..c43e23b3 --- /dev/null +++ b/libarchive/test/test_write_format_shar_empty.c @@ -0,0 +1,58 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_shar_empty.c,v 1.2 2007/05/29 01:00:21 kientzle Exp $"); + +/* + * Check that an "empty" shar archive is correctly created as an empty file. + */ + +DEFINE_TEST(test_write_format_shar_empty) +{ + struct archive *a; + char buff[2048]; + size_t used; + + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_shar(a)); + assertA(0 == archive_write_set_compression_none(a)); + /* 1-byte block size ensures we see only the required bytes. */ + /* We're not testing the padding here. */ + assertA(0 == archive_write_set_bytes_per_block(a, 1)); + assertA(0 == archive_write_set_bytes_in_last_block(a, 1)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + + failure("Empty shar archive should be exactly 0 bytes, was %d.", used); + assert(used == 0); +} diff --git a/libarchive/test/test_write_format_tar.c b/libarchive/test/test_write_format_tar.c new file mode 100644 index 00000000..a23d05ca --- /dev/null +++ b/libarchive/test/test_write_format_tar.c @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_tar.c,v 1.3 2007/05/29 01:00:21 kientzle Exp $"); + +char buff[1000000]; +char buff2[64]; + +DEFINE_TEST(test_write_format_tar) +{ + struct archive_entry *ae; + struct archive *a; + char *p; + size_t used; + size_t blocksize; + + /* Repeat the following for a variety of odd blocksizes. */ + for (blocksize = 1; blocksize < 100000; blocksize += blocksize + 3) { + /* Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_set_bytes_per_block(a, blocksize)); + assertA(0 == archive_write_set_bytes_in_last_block(a, blocksize)); + assertA(blocksize == (size_t)archive_write_get_bytes_in_last_block(a)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + assertA(blocksize == (size_t)archive_write_get_bytes_in_last_block(a)); + + /* + * Write a file to it. + */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_mtime(ae, 1, 10); + assert(1 == archive_entry_mtime(ae)); +#if !defined(__INTERIX) + assert(10 == archive_entry_mtime_nsec(ae)); +#endif + p = strdup("file"); + archive_entry_copy_pathname(ae, p); + strcpy(p, "XXXX"); + free(p); + assertEqualString("file", archive_entry_pathname(ae)); + archive_entry_set_mode(ae, S_IFREG | 0755); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + archive_entry_set_size(ae, 8); + + assertA(0 == archive_write_header(a, ae)); + archive_entry_free(ae); + assertA(8 == archive_write_data(a, "12345678", 9)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + /* This calculation gives "the smallest multiple of + * the block size that is at least 2048 bytes". */ + assert(((2048 - 1)/blocksize+1)*blocksize == used); + + /* + * Now, read the data back. + */ + assert((a = archive_read_new()) != NULL); + assertA(0 == archive_read_support_format_all(a)); + assertA(0 == archive_read_support_compression_all(a)); + assertA(0 == archive_read_open_memory(a, buff, used)); + + assertA(0 == archive_read_next_header(a, &ae)); + + assert(1 == archive_entry_mtime(ae)); + /* Not the same as above: ustar doesn't store hi-res times. */ + assert(0 == archive_entry_mtime_nsec(ae)); + assert(0 == archive_entry_atime(ae)); + assert(0 == archive_entry_ctime(ae)); + assertEqualString("file", archive_entry_pathname(ae)); + assert((S_IFREG | 0755) == archive_entry_mode(ae)); + assert(8 == archive_entry_size(ae)); + assertA(8 == archive_read_data(a, buff2, 10)); + assert(0 == memcmp(buff2, "12345678", 8)); + + /* Verify the end of the archive. */ + assert(1 == archive_read_next_header(a, &ae)); + assert(0 == archive_read_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_read_finish(a)); +#else + archive_read_finish(a); +#endif + } +} diff --git a/libarchive/test/test_write_format_tar_empty.c b/libarchive/test/test_write_format_tar_empty.c new file mode 100644 index 00000000..1d4d3544 --- /dev/null +++ b/libarchive/test/test_write_format_tar_empty.c @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_format_tar_empty.c,v 1.3 2007/07/06 15:43:11 kientzle Exp $"); + +/* + * Check that an "empty" tar archive is correctly created. + */ + +DEFINE_TEST(test_write_format_tar_empty) +{ + struct archive *a; + char buff[2048]; + size_t used; + unsigned int i; + + /* USTAR format: Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_set_bytes_per_block(a, 512)); + assertA(0 == archive_write_set_bytes_in_last_block(a, 512)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + +#if ARCHIVE_VERSION_STAMP < 1009000 + /* Earlier versions wrote 0-length files for empty tar archives. */ + skipping("empty tar archive size"); +#else + assert(used == 1024); +#endif + for (i = 0; i < used; i++) { + failure("Empty tar archive should be all nulls."); + assert(buff[i] == 0); + } + + /* PAX format: Create a new archive in memory. */ + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_pax(a)); + assertA(0 == archive_write_set_compression_none(a)); + assertA(0 == archive_write_set_bytes_per_block(a, 512)); + assertA(0 == archive_write_set_bytes_in_last_block(a, 512)); + assertA(0 == archive_write_open_memory(a, buff, sizeof(buff), &used)); + + /* Close out the archive. */ + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assertA(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + +#if ARCHIVE_VERSION_STAMP < 1009000 + /* Earlier versions wrote 0-length files for empty tar archives. */ + skipping("empty tar archive size"); +#else + assertEqualInt(used, 1024); +#endif + for (i = 0; i < used; i++) { + failure("Empty tar archive should be all nulls."); + assert(buff[i] == 0); + } +} diff --git a/libarchive/test/test_write_open_memory.c b/libarchive/test/test_write_open_memory.c new file mode 100644 index 00000000..2e58fff4 --- /dev/null +++ b/libarchive/test/test_write_open_memory.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD: src/lib/libarchive/test/test_write_open_memory.c,v 1.3 2007/05/29 01:00:21 kientzle Exp $"); + +/* Try to force archive_write_open_memory.c to write past the end of an array. */ +static unsigned char buff[16384]; + +DEFINE_TEST(test_write_open_memory) +{ + unsigned int i; + struct archive *a; + struct archive_entry *ae; + const char *name="/tmp/test"; + + /* Create a simple archive_entry. */ + assert((ae = archive_entry_new()) != NULL); + archive_entry_set_pathname(ae, name); + archive_entry_set_mode(ae, S_IFREG); + assertEqualString(archive_entry_pathname(ae), name); + + /* Try writing with different buffer sizes. */ + /* Make sure that we get failure on too-small buffers, success on + * large enough ones. */ + for (i = 100; i < 1600; i++) { + size_t s; + size_t blocksize = 94; + assert((a = archive_write_new()) != NULL); + assertA(0 == archive_write_set_format_ustar(a)); + assertA(0 == archive_write_set_bytes_in_last_block(a, 1)); + assertA(0 == archive_write_set_bytes_per_block(a, blocksize)); + buff[i] = 0xAE; + assertA(0 == archive_write_open_memory(a, buff, i, &s)); + /* If buffer is smaller than a tar header, this should fail. */ + if (i < (511/blocksize)*blocksize) + assertA(ARCHIVE_FATAL == archive_write_header(a,ae)); + else + assertA(0 == archive_write_header(a, ae)); + /* If buffer is smaller than a tar header plus 1024 byte + * end-of-archive marker, then this should fail. */ + if (i < 1536) + assertA(ARCHIVE_FATAL == archive_write_close(a)); + else + assertA(0 == archive_write_close(a)); +#if ARCHIVE_API_VERSION > 1 + assert(0 == archive_write_finish(a)); +#else + archive_write_finish(a); +#endif + assert(buff[i] == 0xAE); + assert(s <= i); + } + archive_entry_free(ae); +} diff --git a/tar/COPYING b/tar/COPYING new file mode 100644 index 00000000..9c14fd20 --- /dev/null +++ b/tar/COPYING @@ -0,0 +1,62 @@ +$FreeBSD: src/usr.bin/tar/COPYING,v 1.3 2008/01/02 00:19:49 kientzle Exp $ + +All of the C source code and documentation in this package is subject +to the following: + +Copyright (c) 2003-2007 Tim Kientzle +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Some of the filename pattern matching code is based on code subject +to the following license: + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * 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. + */ diff --git a/tar/Makefile b/tar/Makefile new file mode 100644 index 00000000..fa325429 --- /dev/null +++ b/tar/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD: src/usr.bin/tar/Makefile,v 1.34 2008/03/18 06:18:49 kientzle Exp $ + +PROG= bsdtar +BSDTAR_VERSION_STRING=2.5.0b +SRCS= bsdtar.c getdate.y matching.c read.c tree.c util.c write.c +WARNS?= 5 +DPADD= ${LIBARCHIVE} ${LIBBZ2} ${LIBZ} +LDADD= -larchive -lbz2 -lz +CFLAGS+= -DBSDTAR_VERSION_STRING=\"${BSDTAR_VERSION_STRING}\" +CFLAGS+= -DPLATFORM_CONFIG_H=\"config_freebsd.h\" +CFLAGS+= -I${.CURDIR} +SYMLINKS= bsdtar ${BINDIR}/tar +MLINKS= bsdtar.1 tar.1 + +check: $(PROG) + cd ${.CURDIR}/test && make clean test + +.include <bsd.prog.mk> diff --git a/tar/bsdtar.1 b/tar/bsdtar.1 new file mode 100644 index 00000000..433f5b36 --- /dev/null +++ b/tar/bsdtar.1 @@ -0,0 +1,776 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD: src/usr.bin/tar/bsdtar.1,v 1.40 2008/03/15 03:25:26 kientzle Exp $ +.\" +.Dd April 13, 2004 +.Dt BSDTAR 1 +.Os +.Sh NAME +.Nm tar +.Nd manipulate tape archives +.Sh SYNOPSIS +.Nm +.Op Ar bundled-flags Ao args Ac +.Op Ao Ar file Ac | Ao Ar pattern Ac ... +.Nm +.Brq Fl c +.Op Ar options +.Op Ar files | directories +.Nm +.Brq Fl r | Fl u +.Fl f Ar archive-file +.Op Ar options +.Op Ar files | directories +.Nm +.Brq Fl t | Fl x +.Op Ar options +.Op Ar patterns +.Sh DESCRIPTION +.Nm +creates and manipulates streaming archive files. +This implementation can extract from tar, pax, cpio, zip, jar, ar, +and ISO 9660 cdrom images and can create tar, pax, cpio, ar, +and shar archives. +.Pp +The first synopsis form shows a +.Dq bundled +option word. +This usage is provided for compatibility with historical implementations. +See COMPATIBILITY below for details. +.Pp +The other synopsis forms show the preferred usage. +The first option to +.Nm +is a mode indicator from the following list: +.Bl -tag -compact -width indent +.It Fl c +Create a new archive containing the specified items. +.It Fl r +Like +.Fl c , +but new entries are appended to the archive. +Note that this only works on uncompressed archives stored in regular files. +The +.Fl f +option is required. +.It Fl t +List archive contents to stdout. +.It Fl u +Like +.Fl r , +but new entries are added only if they have a modification date +newer than the corresponding entry in the archive. +Note that this only works on uncompressed archives stored in regular files. +The +.Fl f +option is required. +.It Fl x +Extract to disk from the archive. +If a file with the same name appears more than once in the archive, +each copy will be extracted, with later copies overwriting (replacing) +earlier copies. +.El +.Pp +In +.Fl c , +.Fl r , +or +.Fl u +mode, each specified file or directory is added to the +archive in the order specified on the command line. +By default, the contents of each directory are also archived. +.Pp +In extract or list mode, the entire command line +is read and parsed before the archive is opened. +The pathnames or patterns on the command line indicate +which items in the archive should be processed. +Patterns are shell-style globbing patterns as +documented in +.Xr tcsh 1 . +.Sh OPTIONS +Unless specifically stated otherwise, options are applicable in +all operating modes. +.Bl -tag -width indent +.It Cm @ Ns Pa archive +(c and r mode only) +The specified archive is opened and the entries +in it will be appended to the current archive. +As a simple example, +.Dl Nm Fl c Fl f Pa - Pa newfile Cm @ Ns Pa original.tar +writes a new archive to standard output containing a file +.Pa newfile +and all of the entries from +.Pa original.tar . +In contrast, +.Dl Nm Fl c Fl f Pa - Pa newfile Pa original.tar +creates a new archive with only two entries. +Similarly, +.Dl Nm Fl czf Pa - Fl -format Cm pax Cm @ Ns Pa - +reads an archive from standard input (whose format will be determined +automatically) and converts it into a gzip-compressed +pax-format archive on stdout. +In this way, +.Nm +can be used to convert archives from one format to another. +.It Fl b Ar blocksize +Specify the block size, in 512-byte records, for tape drive I/O. +As a rule, this argument is only needed when reading from or writing +to tape drives, and usually not even then as the default block size of +20 records (10240 bytes) is very common. +.It Fl C Ar directory +In c and r mode, this changes the directory before adding +the following files. +In x mode, change directories after opening the archive +but before extracting entries from the archive. +.It Fl -check-links ( Fl W Cm check-links ) +(c and r modes only) +Issue a warning message unless all links to each file are archived. +.It Fl -chroot ( Fl W Cm chroot ) +(x mode only) +.Fn chroot +to the current directory after processing any +.Fl C +options and before extracting any files. +.It Fl -exclude Ar pattern ( Fl W Cm exclude Ns = Ns Ar pattern ) +Do not process files or directories that match the +specified pattern. +Note that exclusions take precedence over patterns or filenames +specified on the command line. +.It Fl -format Ar format ( Fl W Cm format Ns = Ns Ar format ) +(c mode only) +Use the specified format for the created archive. +Supported formats include +.Dq cpio , +.Dq pax , +.Dq shar , +and +.Dq ustar . +Other formats may also be supported; see +.Xr libarchive-formats 5 +for more information about currently-supported formats. +.It Fl f Ar file +Read the archive from or write the archive to the specified file. +The filename can be +.Pa - +for standard input or standard output. +If not specified, the default tape device will be used. +(On +.Fx , +the default tape device is +.Pa /dev/sa0 . ) +.It Fl H +(c and r mode only) +Symbolic links named on the command line will be followed; the +target of the link will be archived, not the link itself. +.It Fl h +(c and r mode only) +Synonym for +.Fl L . +.It Fl I +Synonym for +.Fl T . +.It Fl -include Ar pattern ( Fl W Cm include Ns = Ns Ar pattern ) +Process only files or directories that match the specified pattern. +Note that exclusions specified with +.Fl -exclude +take precedence over inclusions. +If no inclusions are explicitly specified, all entries are processed by +default. +The +.Fl -include +option is especially useful when filtering archives. +For example, the command +.Dl Nm Fl c Fl f Pa new.tar Fl -include='*foo*' Cm @ Ns Pa old.tgz +creates a new archive +.Pa new.tar +containing only the entries from +.Pa old.tgz +containing the string +.Sq foo . +.It Fl j +(c mode only) +Compress the resulting archive with +.Xr bzip2 1 . +In extract or list modes, this option is ignored. +Note that, unlike other +.Nm tar +implementations, this implementation recognizes bzip2 compression +automatically when reading archives. +.It Fl k +(x mode only) +Do not overwrite existing files. +In particular, if a file appears more than once in an archive, +later copies will not overwrite earlier copies. +.It Fl L +(c and r mode only) +All symbolic links will be followed. +Normally, symbolic links are archived as such. +With this option, the target of the link will be archived instead. +.It Fl l +This is a synonym for the +.Fl -check-links +option. +.It Fl m +(x mode only) +Do not extract modification time. +By default, the modification time is set to the time stored in the archive. +.It Fl n +(c, r, u modes only) +Do not recursively archive the contents of directories. +.It Fl -newer Ar date ( Fl W Cm newer Ns = Ns Ar date ) +(c, r, u modes only) +Only include files and directories newer than the specified date. +This compares ctime entries. +.It Fl -newer-mtime Ar date ( Fl W Cm newer-mtime Ns = Ns Ar date ) +(c, r, u modes only) +Like +.Fl -newer , +except it compares mtime entries instead of ctime entries. +.It Fl -newer-than Pa file ( Fl W Cm newer-than Ns = Ns Pa file ) +(c, r, u modes only) +Only include files and directories newer than the specified file. +This compares ctime entries. +.It Fl -newer-mtime-than Pa file ( Fl W Cm newer-mtime-than Ns = Ns Pa file ) +(c, r, u modes only) +Like +.Fl -newer-than , +except it compares mtime entries instead of ctime entries. +.It Fl -nodump ( Fl W Cm nodump ) +(c and r modes only) +Honor the nodump file flag by skipping this file. +.It Fl -null ( Fl W Cm null ) +(use with +.Fl I , +.Fl T , +or +.Fl X ) +Filenames or patterns are separated by null characters, +not by newlines. +This is often used to read filenames output by the +.Fl print0 +option to +.Xr find 1 . +.It Fl O +(x, t modes only) +In extract (-x) mode, files will be written to standard out rather than +being extracted to disk. +In list (-t) mode, the file listing will be written to stderr rather than +the usual stdout. +.It Fl o +(x mode only) +Use the user and group of the user running the program rather +than those specified in the archive. +Note that this has no significance unless +.Fl p +is specified, and the program is being run by the root user. +In this case, the file modes and flags from +the archive will be restored, but ACLs or owner information in +the archive will be discarded. +.It Fl -one-file-system ( Fl W Cm one-file-system ) +(c, r, and u modes) +Do not cross mount points. +.It Fl P +Preserve pathnames. +By default, absolute pathnames (those that begin with a / +character) have the leading slash removed both when creating archives +and extracting from them. +Also, +.Nm +will refuse to extract archive entries whose pathnames contain +.Pa .. +or whose target directory would be altered by a symlink. +This option suppresses these behaviors. +.It Fl p +(x mode only) +Preserve file permissions. +Attempt to restore the full permissions, including owner, file modes, file +flags and ACLs, if available, for each item extracted from the archive. +By default, newly-created files are owned by the user running +.Nm , +the file mode is restored for newly-created regular files, and +all other types of entries receive default permissions. +If +.Nm +is being run by root, the default is to restore the owner unless the +.Fl o +option is also specified. +.It Fl q ( Fl -fast-read ) +(x and t mode only) +Extract or list only the first archive entry that matches each pattern +or filename operand. +Exit as soon as each specified pattern or filename has been matched. +By default, the archive is always read to the very end, since +there can be multiple entries with the same name and, by convention, +later entries overwrite earlier entries. +This option is provided as a performance optimization. +.It Fl -strip-components Ar count ( Fl W Cm strip-components Ns = Ns Ar count ) +(x and t mode only) +Remove the specified number of leading path elements. +Pathnames with fewer elements will be silently skipped. +Note that the pathname is edited after checking inclusion/exclusion patterns +but before security checks. +.It Fl T Ar filename +In x or t mode, +.Nm +will read the list of names to be extracted from +.Pa filename . +In c mode, +.Nm +will read names to be archived from +.Pa filename . +The special name +.Dq -C +on a line by itself will cause the current directory to be changed to +the directory specified on the following line. +Names are terminated by newlines unless +.Fl -null +is specified. +Note that +.Fl -null +also disables the special handling of lines containing +.Dq -C . +.It Fl U +(x mode only) +Unlink files before creating them. +Without this option, +.Nm +overwrites existing files, which preserves existing hardlinks. +With this option, existing hardlinks will be broken, as will any +symlink that would affect the location of an extracted file. +.It Fl -use-compress-program Ar program +Pipe the input (in x or t mode) or the output (in c mode) through +.Pa program +instead of using the builtin compression support. +.It Fl v +Produce verbose output. +In create and extract modes, +.Nm +will list each file name as it is read from or written to +the archive. +In list mode, +.Nm +will produce output similar to that of +.Xr ls 1 . +Additional +.Fl v +options will provide additional detail. +.It Fl W Ar longopt=value +Long options (preceded by +.Fl - ) +are only supported directly on systems that have the +.Xr getopt_long 3 +function. +The +.Fl W +option can be used to access long options on systems that +do not support this function. +.It Fl w +Ask for confirmation for every action. +.It Fl X Ar filename +Read a list of exclusion patterns from the specified file. +See +.Fl -exclude +for more information about the handling of exclusions. +.It Fl y +(c mode only) +Compress the resulting archive with +.Xr bzip2 1 . +In extract or list modes, this option is ignored. +Note that, unlike other +.Nm tar +implementations, this implementation recognizes bzip2 compression +automatically when reading archives. +.It Fl z +(c mode only) +Compress the resulting archive with +.Xr gzip 1 . +In extract or list modes, this option is ignored. +Note that, unlike other +.Nm tar +implementations, this implementation recognizes gzip compression +automatically when reading archives. +.It Fl Z +(c mode only) +Compress the resulting archive with +.Xr compress 1 . +In extract or list modes, this option is ignored. +Note that, unlike other +.Nm tar +implementations, this implementation recognizes compress compression +automatically when reading archives. +.El +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +.Bl -tag -width ".Ev BLOCKSIZE" +.It Ev LANG +The locale to use. +See +.Xr environ 7 +for more information. +.It Ev TAPE +The default tape device. +The +.Fl f +option overrides this. +.It Ev TZ +The timezone to use when displaying dates. +See +.Xr environ 7 +for more information. +.El +.Sh FILES +.Bl -tag -width ".Ev BLOCKSIZE" +.It Pa /dev/sa0 +The default tape device, if not overridden by the +.Ev TAPE +environment variable or the +.Fl f +option. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +The following creates a new archive +called +.Ar file.tar.gz +that contains two files +.Ar source.c +and +.Ar source.h : +.Dl Nm Fl czf Pa file.tar.gz Pa source.c Pa source.h +.Pp +To view a detailed table of contents for this +archive: +.Dl Nm Fl tvf Pa file.tar.gz +.Pp +To extract all entries from the archive on +the default tape drive: +.Dl Nm Fl x +.Pp +To examine the contents of an ISO 9660 cdrom image: +.Dl Nm Fl tf Pa image.iso +.Pp +To move file hierarchies, invoke +.Nm +as +.Dl Nm Fl cf Pa - Fl C Pa srcdir\ . | Nm Fl xpf Pa - Fl C Pa destdir +or more traditionally +.Dl cd srcdir \&; Nm Fl cf Pa -\ . | ( cd destdir \&; Nm Fl xpf Pa - ) +.Pp +In create mode, the list of files and directories to be archived +can also include directory change instructions of the form +.Cm -C Ns Pa foo/baz +and archive inclusions of the form +.Cm @ Ns Pa archive-file . +For example, the command line +.Dl Nm Fl c Fl f Pa new.tar Pa foo1 Cm @ Ns Pa old.tgz Cm -C Ns Pa /tmp Pa foo2 +will create a new archive +.Pa new.tar . +.Nm +will read the file +.Pa foo1 +from the current directory and add it to the output archive. +It will then read each entry from +.Pa old.tgz +and add those entries to the output archive. +Finally, it will switch to the +.Pa /tmp +directory and add +.Pa foo2 +to the output archive. +.Pp +The +.Fl -newer +and +.Fl -newer-mtime +switches accept a variety of common date and time specifications, including +.Dq 12 Mar 2005 7:14:29pm , +.Dq 2005-03-12 19:14 , +.Dq 5 minutes ago , +and +.Dq 19:14 PST May 1 . +.Sh COMPATIBILITY +The bundled-arguments format is supported for compatibility +with historic implementations. +It consists of an initial word (with no leading - character) in which +each character indicates an option. +Arguments follow as separate words. +The order of the arguments must match the order +of the corresponding characters in the bundled command word. +For example, +.Dl Nm Cm tbf 32 Pa file.tar +specifies three flags +.Cm t , +.Cm b , +and +.Cm f . +The +.Cm b +and +.Cm f +flags both require arguments, +so there must be two additional items +on the command line. +The +.Ar 32 +is the argument to the +.Cm b +flag, and +.Ar file.tar +is the argument to the +.Cm f +flag. +.Pp +The mode options c, r, t, u, and x and the options +b, f, l, m, o, v, and w comply with SUSv2. +.Pp +For maximum portability, scripts that invoke +.Nm tar +should use the bundled-argument format above, should limit +themselves to the +.Cm c , +.Cm t , +and +.Cm x +modes, and the +.Cm b , +.Cm f , +.Cm m , +.Cm v , +and +.Cm w +options. +.Pp +On systems that support getopt_long(), additional long options +are available to improve compatibility with other tar implementations. +.Sh SECURITY +Certain security issues are common to many archiving programs, including +.Nm . +In particular, carefully-crafted archives can request that +.Nm +extract files to locations outside of the target directory. +This can potentially be used to cause unwitting users to overwrite +files they did not intend to overwrite. +If the archive is being extracted by the superuser, any file +on the system can potentially be overwritten. +There are three ways this can happen. +Although +.Nm +has mechanisms to protect against each one, +savvy users should be aware of the implications: +.Bl -bullet -width indent +.It +Archive entries can have absolute pathnames. +By default, +.Nm +removes the leading +.Pa / +character from filenames before restoring them to guard against this problem. +.It +Archive entries can have pathnames that include +.Pa .. +components. +By default, +.Nm +will not extract files containing +.Pa .. +components in their pathname. +.It +Archive entries can exploit symbolic links to restore +files to other directories. +An archive can restore a symbolic link to another directory, +then use that link to restore a file into that directory. +To guard against this, +.Nm +checks each extracted path for symlinks. +If the final path element is a symlink, it will be removed +and replaced with the archive entry. +If +.Fl U +is specified, any intermediate symlink will also be unconditionally removed. +If neither +.Fl U +nor +.Fl P +is specified, +.Nm +will refuse to extract the entry. +.El +To protect yourself, you should be wary of any archives that +come from untrusted sources. +You should examine the contents of an archive with +.Dl Nm Fl tf Pa filename +before extraction. +You should use the +.Fl k +option to ensure that +.Nm +will not overwrite any existing files or the +.Fl U +option to remove any pre-existing files. +You should generally not extract archives while running with super-user +privileges. +Note that the +.Fl P +option to +.Nm +disables the security checks above and allows you to extract +an archive while preserving any absolute pathnames, +.Pa .. +components, or symlinks to other directories. +.Sh SEE ALSO +.Xr bzip2 1 , +.Xr compress 1 , +.Xr cpio 1 , +.Xr gzip 1 , +.Xr mt 1 , +.Xr pax 1 , +.Xr shar 1 , +.Xr libarchive 3 , +.Xr libarchive-formats 5 , +.Xr tar 5 +.Sh STANDARDS +There is no current POSIX standard for the tar command; it appeared +in +.St -p1003.1-96 +but was dropped from +.St -p1003.1-2001 . +The options used by this implementation were developed by surveying a +number of existing tar implementations as well as the old POSIX specification +for tar and the current POSIX specification for pax. +.Pp +The ustar and pax interchange file formats are defined by +.St -p1003.1-2001 +for the pax command. +.Sh HISTORY +A +.Nm tar +command appeared in Seventh Edition Unix, which was released in January, 1979. +There have been numerous other implementations, +many of which extended the file format. +John Gilmore's +.Nm pdtar +public-domain implementation (circa November, 1987) +was quite influential, and formed the basis of GNU tar. +GNU tar was included as the standard system tar +in +.Fx +beginning with +.Fx 1.0 . +.Pp +This is a complete re-implementation based on the +.Xr libarchive 3 +library. +.Sh BUGS +This program follows +.St -p1003.1-96 +for the definition of the +.Fl l +option. +Note that GNU tar prior to version 1.15 treated +.Fl l +as a synonym for the +.Fl -one-file-system +option. +.Pp +The +.Fl C Pa dir +option may differ from historic implementations. +.Pp +All archive output is written in correctly-sized blocks, even +if the output is being compressed. +Whether or not the last output block is padded to a full +block size varies depending on the format and the +output device. +For tar and cpio formats, the last block of output is padded +to a full block size if the output is being +written to standard output or to a character or block device such as +a tape drive. +If the output is being written to a regular file, the last block +will not be padded. +Many compressors, including +.Xr gzip 1 +and +.Xr bzip2 1 , +complain about the null padding when decompressing an archive created by +.Nm , +although they still extract it correctly. +.Pp +The compression and decompression is implemented internally, so +there may be insignificant differences between the compressed output +generated by +.Dl Nm Fl czf Pa - file +and that generated by +.Dl Nm Fl cf Pa - file | Nm gzip +.Pp +The default should be to read and write archives to the standard I/O paths, +but tradition (and POSIX) dictates otherwise. +.Pp +The +.Cm r +and +.Cm u +modes require that the archive be uncompressed +and located in a regular file on disk. +Other archives can be modified using +.Cm c +mode with the +.Pa @archive-file +extension. +.Pp +To archive a file called +.Pa @foo +or +.Pa -foo +you must specify it as +.Pa ./@foo +or +.Pa ./-foo , +respectively. +.Pp +In create mode, a leading +.Pa ./ +is always removed. +A leading +.Pa / +is stripped unless the +.Fl P +option is specified. +.Pp +There needs to be better support for file selection on both create +and extract. +.Pp +There is not yet any support for multi-volume archives or for archiving +sparse files. +.Pp +Converting between dissimilar archive formats (such as tar and cpio) using the +.Cm @ Ns Pa - +convention can cause hard link information to be lost. +(This is a consequence of the incompatible ways that different archive +formats store hardlink information.) +.Pp +There are alternative long options for many of the short options that +are deliberately not documented. diff --git a/tar/bsdtar.c b/tar/bsdtar.c new file mode 100644 index 00000000..81d9841b --- /dev/null +++ b/tar/bsdtar.c @@ -0,0 +1,934 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/bsdtar.c,v 1.86 2008/03/15 05:08:21 kientzle Exp $"); + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_GETOPT_LONG +#include <getopt.h> +#else +struct option { + const char *name; + int has_arg; + int *flag; + int val; +}; +#define no_argument 0 +#define required_argument 1 +#endif +#ifdef HAVE_LANGINFO_H +#include <langinfo.h> +#endif +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_ZLIB_H +#include <zlib.h> +#endif + +#include "bsdtar.h" + +#if !HAVE_DECL_OPTARG +extern int optarg; +#endif + +#if !HAVE_DECL_OPTIND +extern int optind; +#endif + +/* + * Per POSIX.1-1988, tar defaults to reading/writing archives to/from + * the default tape device for the system. Pick something reasonable here. + */ +#ifdef __linux +#define _PATH_DEFTAPE "/dev/st0" +#endif + +#ifndef _PATH_DEFTAPE +#define _PATH_DEFTAPE "/dev/tape" +#endif + +/* External function to parse a date/time string (from getdate.y) */ +time_t get_date(const char *); + +static int bsdtar_getopt(struct bsdtar *, const char *optstring, + const struct option **poption); +static void long_help(struct bsdtar *); +static void only_mode(struct bsdtar *, const char *opt, + const char *valid); +static char ** rewrite_argv(struct bsdtar *, + int *argc, char ** src_argv, + const char *optstring); +static void set_mode(struct bsdtar *, char opt); +static void version(void); + +/* + * The leading '+' here forces the GNU version of getopt() (as well as + * both the GNU and BSD versions of getopt_long) to stop at the first + * non-option. Otherwise, GNU getopt() permutes the arguments and + * screws up -C processing. + */ +static const char *tar_opts = "+Bb:C:cf:HhI:jkLlmnOoPprtT:UuvW:wX:xyZz"; + +/* + * Most of these long options are deliberately not documented. They + * are provided only to make life easier for people who also use GNU tar. + * The only long options documented in the manual page are the ones + * with no corresponding short option, such as --exclude, --nodump, + * and --fast-read. + * + * On systems that lack getopt_long, long options can be specified + * using -W longopt and -W longopt=value, e.g. "-W nodump" is the same + * as "--nodump" and "-W exclude=pattern" is the same as "--exclude + * pattern". This does not rely the GNU getopt() "W;" extension, so + * should work correctly on any system with a POSIX-compliant getopt(). + */ + +/* Fake short equivalents for long options that otherwise lack them. */ +enum { + OPTION_CHECK_LINKS = 1, + OPTION_CHROOT, + OPTION_EXCLUDE, + OPTION_FORMAT, + OPTION_HELP, + OPTION_INCLUDE, + OPTION_NEWER_CTIME, + OPTION_NEWER_CTIME_THAN, + OPTION_NEWER_MTIME, + OPTION_NEWER_MTIME_THAN, + OPTION_NODUMP, + OPTION_NO_SAME_OWNER, + OPTION_NO_SAME_PERMISSIONS, + OPTION_NULL, + OPTION_ONE_FILE_SYSTEM, + OPTION_POSIX, + OPTION_STRIP_COMPONENTS, + OPTION_TOTALS, + OPTION_USE_COMPRESS_PROGRAM, + OPTION_VERSION +}; + +/* + * If you add anything, be very careful to keep this list properly + * sorted, as the -W logic relies on it. + */ +static const struct option tar_longopts[] = { + { "absolute-paths", no_argument, NULL, 'P' }, + { "append", no_argument, NULL, 'r' }, + { "block-size", required_argument, NULL, 'b' }, + { "bunzip2", no_argument, NULL, 'j' }, + { "bzip", no_argument, NULL, 'j' }, + { "bzip2", no_argument, NULL, 'j' }, + { "cd", required_argument, NULL, 'C' }, + { "check-links", no_argument, NULL, OPTION_CHECK_LINKS }, + { "chroot", no_argument, NULL, OPTION_CHROOT }, + { "compress", no_argument, NULL, 'Z' }, + { "confirmation", no_argument, NULL, 'w' }, + { "create", no_argument, NULL, 'c' }, + { "dereference", no_argument, NULL, 'L' }, + { "directory", required_argument, NULL, 'C' }, + { "exclude", required_argument, NULL, OPTION_EXCLUDE }, + { "exclude-from", required_argument, NULL, 'X' }, + { "extract", no_argument, NULL, 'x' }, + { "fast-read", no_argument, NULL, 'q' }, + { "file", required_argument, NULL, 'f' }, + { "files-from", required_argument, NULL, 'T' }, + { "format", required_argument, NULL, OPTION_FORMAT }, + { "gunzip", no_argument, NULL, 'z' }, + { "gzip", no_argument, NULL, 'z' }, + { "help", no_argument, NULL, OPTION_HELP }, + { "include", required_argument, NULL, OPTION_INCLUDE }, + { "interactive", no_argument, NULL, 'w' }, + { "insecure", no_argument, NULL, 'P' }, + { "keep-old-files", no_argument, NULL, 'k' }, + { "list", no_argument, NULL, 't' }, + { "modification-time", no_argument, NULL, 'm' }, + { "newer", required_argument, NULL, OPTION_NEWER_CTIME }, + { "newer-ctime", required_argument, NULL, OPTION_NEWER_CTIME }, + { "newer-ctime-than", required_argument, NULL, OPTION_NEWER_CTIME_THAN }, + { "newer-mtime", required_argument, NULL, OPTION_NEWER_MTIME }, + { "newer-mtime-than", required_argument, NULL, OPTION_NEWER_MTIME_THAN }, + { "newer-than", required_argument, NULL, OPTION_NEWER_CTIME_THAN }, + { "nodump", no_argument, NULL, OPTION_NODUMP }, + { "norecurse", no_argument, NULL, 'n' }, + { "no-recursion", no_argument, NULL, 'n' }, + { "no-same-owner", no_argument, NULL, OPTION_NO_SAME_OWNER }, + { "no-same-permissions",no_argument, NULL, OPTION_NO_SAME_PERMISSIONS }, + { "null", no_argument, NULL, OPTION_NULL }, + { "one-file-system", no_argument, NULL, OPTION_ONE_FILE_SYSTEM }, + { "posix", no_argument, NULL, OPTION_POSIX }, + { "preserve-permissions", no_argument, NULL, 'p' }, + { "read-full-blocks", no_argument, NULL, 'B' }, + { "same-permissions", no_argument, NULL, 'p' }, + { "strip-components", required_argument, NULL, OPTION_STRIP_COMPONENTS }, + { "to-stdout", no_argument, NULL, 'O' }, + { "totals", no_argument, NULL, OPTION_TOTALS }, + { "uncompress", no_argument, NULL, 'Z' }, + { "unlink", no_argument, NULL, 'U' }, + { "unlink-first", no_argument, NULL, 'U' }, + { "update", no_argument, NULL, 'u' }, + { "use-compress-program", + required_argument, NULL, OPTION_USE_COMPRESS_PROGRAM }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, OPTION_VERSION }, + { NULL, 0, NULL, 0 } +}; + +/* A basic set of security flags to request from libarchive. */ +#define SECURITY \ + (ARCHIVE_EXTRACT_SECURE_SYMLINKS \ + | ARCHIVE_EXTRACT_SECURE_NODOTDOT) + +int +main(int argc, char **argv) +{ + struct bsdtar *bsdtar, bsdtar_storage; + const struct option *option; + int opt, t; + char option_o; + char possible_help_request; + char buff[16]; + + /* + * Use a pointer for consistency, but stack-allocated storage + * for ease of cleanup. + */ + bsdtar = &bsdtar_storage; + memset(bsdtar, 0, sizeof(*bsdtar)); + bsdtar->fd = -1; /* Mark as "unused" */ + option_o = 0; + + /* Need bsdtar->progname before calling bsdtar_warnc. */ + if (*argv == NULL) + bsdtar->progname = "bsdtar"; + else { + bsdtar->progname = strrchr(*argv, '/'); + if (bsdtar->progname != NULL) + bsdtar->progname++; + else + bsdtar->progname = *argv; + } + + if (setlocale(LC_ALL, "") == NULL) + bsdtar_warnc(bsdtar, 0, "Failed to set default locale"); +#if defined(HAVE_NL_LANGINFO) && defined(HAVE_D_MD_ORDER) + bsdtar->day_first = (*nl_langinfo(D_MD_ORDER) == 'd'); +#endif + possible_help_request = 0; + + /* Look up uid of current user for future reference */ + bsdtar->user_uid = geteuid(); + + /* Default: open tape drive. */ + bsdtar->filename = getenv("TAPE"); + if (bsdtar->filename == NULL) + bsdtar->filename = _PATH_DEFTAPE; + + /* Default: preserve mod time on extract */ + bsdtar->extract_flags = ARCHIVE_EXTRACT_TIME; + + /* Default: Perform basic security checks. */ + bsdtar->extract_flags |= SECURITY; + + /* Defaults for root user: */ + if (bsdtar->user_uid == 0) { + /* --same-owner */ + bsdtar->extract_flags |= ARCHIVE_EXTRACT_OWNER; + /* -p */ + bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM; + bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL; + bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR; + bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS; + } + + /* Rewrite traditional-style tar arguments, if used. */ + argv = rewrite_argv(bsdtar, &argc, argv, tar_opts); + + bsdtar->argv = argv; + bsdtar->argc = argc; + + /* Process all remaining arguments now. */ + /* + * Comments following each option indicate where that option + * originated: SUSv2, POSIX, GNU tar, star, etc. If there's + * no such comment, then I don't know of anyone else who + * implements that option. + */ + while ((opt = bsdtar_getopt(bsdtar, tar_opts, &option)) != -1) { + switch (opt) { + case 'B': /* GNU tar */ + /* libarchive doesn't need this; just ignore it. */ + break; + case 'b': /* SUSv2 */ + t = atoi(optarg); + if (t <= 0 || t > 1024) + bsdtar_errc(bsdtar, 1, 0, + "Argument to -b is out of range (1..1024)"); + bsdtar->bytes_per_block = 512 * t; + break; + case 'C': /* GNU tar */ + set_chdir(bsdtar, optarg); + break; + case 'c': /* SUSv2 */ + set_mode(bsdtar, opt); + break; + case OPTION_CHECK_LINKS: /* GNU tar */ + bsdtar->option_warn_links = 1; + break; + case OPTION_CHROOT: /* NetBSD */ + bsdtar->option_chroot = 1; + break; + case OPTION_EXCLUDE: /* GNU tar */ + if (exclude(bsdtar, optarg)) + bsdtar_errc(bsdtar, 1, 0, + "Couldn't exclude %s\n", optarg); + break; + case OPTION_FORMAT: /* GNU tar, others */ + bsdtar->create_format = optarg; + break; + case 'f': /* SUSv2 */ + bsdtar->filename = optarg; + if (strcmp(bsdtar->filename, "-") == 0) + bsdtar->filename = NULL; + break; + case 'H': /* BSD convention */ + bsdtar->symlink_mode = 'H'; + break; + case 'h': /* Linux Standards Base, gtar; synonym for -L */ + bsdtar->symlink_mode = 'L'; + /* Hack: -h by itself is the "help" command. */ + possible_help_request = 1; + break; + case OPTION_HELP: /* GNU tar, others */ + long_help(bsdtar); + exit(0); + break; + case 'I': /* GNU tar */ + /* + * TODO: Allow 'names' to come from an archive, + * not just a text file. Design a good UI for + * allowing names and mode/owner to be read + * from an archive, with contents coming from + * disk. This can be used to "refresh" an + * archive or to design archives with special + * permissions without having to create those + * permissions on disk. + */ + bsdtar->names_from_file = optarg; + break; + case OPTION_INCLUDE: + /* + * Noone else has the @archive extension, so + * noone else needs this to filter entries + * when transforming archives. + */ + if (include(bsdtar, optarg)) + bsdtar_errc(bsdtar, 1, 0, + "Failed to add %s to inclusion list", + optarg); + break; + case 'j': /* GNU tar */ +#if HAVE_LIBBZ2 + if (bsdtar->create_compression != '\0') + bsdtar_errc(bsdtar, 1, 0, + "Can't specify both -%c and -%c", opt, + bsdtar->create_compression); + bsdtar->create_compression = opt; +#else + bsdtar_warnc(bsdtar, 0, "-j compression not supported by this version of bsdtar"); + usage(bsdtar); +#endif + break; + case 'k': /* GNU tar */ + bsdtar->extract_flags |= ARCHIVE_EXTRACT_NO_OVERWRITE; + break; + case 'L': /* BSD convention */ + bsdtar->symlink_mode = 'L'; + break; + case 'l': /* SUSv2 and GNU tar beginning with 1.16 */ + /* GNU tar 1.13 used -l for --one-file-system */ + bsdtar->option_warn_links = 1; + break; + case 'm': /* SUSv2 */ + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_TIME; + break; + case 'n': /* GNU tar */ + bsdtar->option_no_subdirs = 1; + break; + /* + * Selecting files by time: + * --newer-?time='date' Only files newer than 'date' + * --newer-?time-than='file' Only files newer than time + * on specified file (useful for incremental backups) + * TODO: Add corresponding "older" options to reverse these. + */ + case OPTION_NEWER_CTIME: /* GNU tar */ + bsdtar->newer_ctime_sec = get_date(optarg); + break; + case OPTION_NEWER_CTIME_THAN: + { + struct stat st; + if (stat(optarg, &st) != 0) + bsdtar_errc(bsdtar, 1, 0, + "Can't open file %s", optarg); + bsdtar->newer_ctime_sec = st.st_ctime; + bsdtar->newer_ctime_nsec = + ARCHIVE_STAT_CTIME_NANOS(&st); + } + break; + case OPTION_NEWER_MTIME: /* GNU tar */ + bsdtar->newer_mtime_sec = get_date(optarg); + break; + case OPTION_NEWER_MTIME_THAN: + { + struct stat st; + if (stat(optarg, &st) != 0) + bsdtar_errc(bsdtar, 1, 0, + "Can't open file %s", optarg); + bsdtar->newer_mtime_sec = st.st_mtime; + bsdtar->newer_mtime_nsec = + ARCHIVE_STAT_MTIME_NANOS(&st); + } + break; + case OPTION_NODUMP: /* star */ + bsdtar->option_honor_nodump = 1; + break; + case OPTION_NO_SAME_OWNER: /* GNU tar */ + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER; + break; + case OPTION_NO_SAME_PERMISSIONS: /* GNU tar */ + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_PERM; + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_ACL; + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_XATTR; + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_FFLAGS; + break; + case OPTION_NULL: /* GNU tar */ + bsdtar->option_null++; + break; + case 'O': /* GNU tar */ + bsdtar->option_stdout = 1; + break; + case 'o': /* SUSv2 and GNU conflict here, but not fatally */ + option_o = 1; /* Record it and resolve it later. */ + break; + case OPTION_ONE_FILE_SYSTEM: /* GNU tar */ + bsdtar->option_dont_traverse_mounts = 1; + break; +#if 0 + /* + * The common BSD -P option is not necessary, since + * our default is to archive symlinks, not follow + * them. This is convenient, as -P conflicts with GNU + * tar anyway. + */ + case 'P': /* BSD convention */ + /* Default behavior, no option necessary. */ + break; +#endif + case 'P': /* GNU tar */ + bsdtar->extract_flags &= ~SECURITY; + bsdtar->option_absolute_paths = 1; + break; + case 'p': /* GNU tar, star */ + bsdtar->extract_flags |= ARCHIVE_EXTRACT_PERM; + bsdtar->extract_flags |= ARCHIVE_EXTRACT_ACL; + bsdtar->extract_flags |= ARCHIVE_EXTRACT_XATTR; + bsdtar->extract_flags |= ARCHIVE_EXTRACT_FFLAGS; + break; + case OPTION_POSIX: /* GNU tar */ + bsdtar->create_format = "pax"; + break; + case 'q': /* FreeBSD GNU tar --fast-read, NetBSD -q */ + bsdtar->option_fast_read = 1; + break; + case 'r': /* SUSv2 */ + set_mode(bsdtar, opt); + break; + case OPTION_STRIP_COMPONENTS: /* GNU tar 1.15 */ + bsdtar->strip_components = atoi(optarg); + break; + case 'T': /* GNU tar */ + bsdtar->names_from_file = optarg; + break; + case 't': /* SUSv2 */ + set_mode(bsdtar, opt); + bsdtar->verbose++; + break; + case OPTION_TOTALS: /* GNU tar */ + bsdtar->option_totals++; + break; + case 'U': /* GNU tar */ + bsdtar->extract_flags |= ARCHIVE_EXTRACT_UNLINK; + bsdtar->option_unlink_first = 1; + break; + case 'u': /* SUSv2 */ + set_mode(bsdtar, opt); + break; + case 'v': /* SUSv2 */ + bsdtar->verbose++; + break; + case OPTION_VERSION: /* GNU convention */ + version(); + break; +#if 0 + /* + * The -W longopt feature is handled inside of + * bsdtar_getop(), so -W is not available here. + */ + case 'W': /* Obscure, but useful GNU convention. */ + break; +#endif + case 'w': /* SUSv2 */ + bsdtar->option_interactive = 1; + break; + case 'X': /* GNU tar */ + if (exclude_from_file(bsdtar, optarg)) + bsdtar_errc(bsdtar, 1, 0, + "failed to process exclusions from file %s", + optarg); + break; + case 'x': /* SUSv2 */ + set_mode(bsdtar, opt); + break; + case 'y': /* FreeBSD version of GNU tar */ +#if HAVE_LIBBZ2 + if (bsdtar->create_compression != '\0') + bsdtar_errc(bsdtar, 1, 0, + "Can't specify both -%c and -%c", opt, + bsdtar->create_compression); + bsdtar->create_compression = opt; +#else + bsdtar_warnc(bsdtar, 0, "-y compression not supported by this version of bsdtar"); + usage(bsdtar); +#endif + break; + case 'Z': /* GNU tar */ + if (bsdtar->create_compression != '\0') + bsdtar_errc(bsdtar, 1, 0, + "Can't specify both -%c and -%c", opt, + bsdtar->create_compression); + bsdtar->create_compression = opt; + break; + case 'z': /* GNU tar, star, many others */ +#if HAVE_LIBZ + if (bsdtar->create_compression != '\0') + bsdtar_errc(bsdtar, 1, 0, + "Can't specify both -%c and -%c", opt, + bsdtar->create_compression); + bsdtar->create_compression = opt; +#else + bsdtar_warnc(bsdtar, 0, "-z compression not supported by this version of bsdtar"); + usage(bsdtar); +#endif + break; + case OPTION_USE_COMPRESS_PROGRAM: + bsdtar->compress_program = optarg; + break; + default: + usage(bsdtar); + } + } + + /* + * Sanity-check options. + */ + + /* If no "real" mode was specified, treat -h as --help. */ + if ((bsdtar->mode == '\0') && possible_help_request) { + long_help(bsdtar); + exit(0); + } + + /* Otherwise, a mode is required. */ + if (bsdtar->mode == '\0') + bsdtar_errc(bsdtar, 1, 0, + "Must specify one of -c, -r, -t, -u, -x"); + + /* Check boolean options only permitted in certain modes. */ + if (bsdtar->option_dont_traverse_mounts) + only_mode(bsdtar, "--one-file-system", "cru"); + if (bsdtar->option_fast_read) + only_mode(bsdtar, "--fast-read", "xt"); + if (bsdtar->option_honor_nodump) + only_mode(bsdtar, "--nodump", "cru"); + if (option_o > 0) { + switch (bsdtar->mode) { + case 'c': + /* + * In GNU tar, -o means "old format." The + * "ustar" format is the closest thing + * supported by libarchive. + */ + bsdtar->create_format = "ustar"; + /* TODO: bsdtar->create_format = "v7"; */ + break; + case 'x': + /* POSIX-compatible behavior. */ + bsdtar->option_no_owner = 1; + bsdtar->extract_flags &= ~ARCHIVE_EXTRACT_OWNER; + break; + default: + only_mode(bsdtar, "-o", "xc"); + break; + } + } + if (bsdtar->option_no_subdirs) + only_mode(bsdtar, "-n", "cru"); + if (bsdtar->option_stdout) + only_mode(bsdtar, "-O", "xt"); + if (bsdtar->option_unlink_first) + only_mode(bsdtar, "-U", "x"); + if (bsdtar->option_warn_links) + only_mode(bsdtar, "--check-links", "cr"); + + /* Check other parameters only permitted in certain modes. */ + if (bsdtar->create_compression != '\0') { + strcpy(buff, "-?"); + buff[1] = bsdtar->create_compression; + only_mode(bsdtar, buff, "cxt"); + } + if (bsdtar->create_format != NULL) + only_mode(bsdtar, "--format", "c"); + if (bsdtar->symlink_mode != '\0') { + strcpy(buff, "-?"); + buff[1] = bsdtar->symlink_mode; + only_mode(bsdtar, buff, "cru"); + } + if (bsdtar->strip_components != 0) + only_mode(bsdtar, "--strip-components", "xt"); + + bsdtar->argc -= optind; + bsdtar->argv += optind; + + switch(bsdtar->mode) { + case 'c': + tar_mode_c(bsdtar); + break; + case 'r': + tar_mode_r(bsdtar); + break; + case 't': + tar_mode_t(bsdtar); + break; + case 'u': + tar_mode_u(bsdtar); + break; + case 'x': + tar_mode_x(bsdtar); + break; + } + + cleanup_exclusions(bsdtar); + if (bsdtar->return_value != 0) + bsdtar_warnc(bsdtar, 0, + "Error exit delayed from previous errors."); + return (bsdtar->return_value); +} + +static void +set_mode(struct bsdtar *bsdtar, char opt) +{ + if (bsdtar->mode != '\0' && bsdtar->mode != opt) + bsdtar_errc(bsdtar, 1, 0, + "Can't specify both -%c and -%c", opt, bsdtar->mode); + bsdtar->mode = opt; +} + +/* + * Verify that the mode is correct. + */ +static void +only_mode(struct bsdtar *bsdtar, const char *opt, const char *valid_modes) +{ + if (strchr(valid_modes, bsdtar->mode) == NULL) + bsdtar_errc(bsdtar, 1, 0, + "Option %s is not permitted in mode -%c", + opt, bsdtar->mode); +} + + +/*- + * Convert traditional tar arguments into new-style. + * For example, + * tar tvfb file.tar 32 --exclude FOO + * will be converted to + * tar -t -v -f file.tar -b 32 --exclude FOO + * + * This requires building a new argv array. The initial bundled word + * gets expanded into a new string that looks like "-t\0-v\0-f\0-b\0". + * The new argv array has pointers into this string intermingled with + * pointers to the existing arguments. Arguments are moved to + * immediately follow their options. + * + * The optstring argument here is the same one passed to getopt(3). + * It is used to determine which option letters have trailing arguments. + */ +char ** +rewrite_argv(struct bsdtar *bsdtar, int *argc, char **src_argv, + const char *optstring) +{ + char **new_argv, **dest_argv; + const char *p; + char *src, *dest; + + if (src_argv[0] == NULL || + src_argv[1] == NULL || src_argv[1][0] == '-') + return (src_argv); + + *argc += strlen(src_argv[1]) - 1; + new_argv = malloc((*argc + 1) * sizeof(new_argv[0])); + if (new_argv == NULL) + bsdtar_errc(bsdtar, 1, errno, "No Memory"); + + dest_argv = new_argv; + *dest_argv++ = *src_argv++; + + dest = malloc(strlen(*src_argv) * 3); + if (dest == NULL) + bsdtar_errc(bsdtar, 1, errno, "No memory"); + for (src = *src_argv++; *src != '\0'; src++) { + *dest_argv++ = dest; + *dest++ = '-'; + *dest++ = *src; + *dest++ = '\0'; + /* If option takes an argument, insert that into the list. */ + for (p = optstring; p != NULL && *p != '\0'; p++) { + if (*p != *src) + continue; + if (p[1] != ':') /* No arg required, done. */ + break; + if (*src_argv == NULL) /* No arg available? Error. */ + bsdtar_errc(bsdtar, 1, 0, + "Option %c requires an argument", + *src); + *dest_argv++ = *src_argv++; + break; + } + } + + /* Copy remaining arguments, including trailing NULL. */ + while ((*dest_argv++ = *src_argv++) != NULL) + ; + + return (new_argv); +} + +void +usage(struct bsdtar *bsdtar) +{ + const char *p; + + p = bsdtar->progname; + + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " List: %s -tf <archive-filename>\n", p); + fprintf(stderr, " Extract: %s -xf <archive-filename>\n", p); + fprintf(stderr, " Create: %s -cf <archive-filename> [filenames...]\n", p); +#ifdef HAVE_GETOPT_LONG + fprintf(stderr, " Help: %s --help\n", p); +#else + fprintf(stderr, " Help: %s -h\n", p); +#endif + exit(1); +} + +static void +version(void) +{ + printf("bsdtar %s - %s\n", + BSDTAR_VERSION_STRING, + archive_version()); + exit(1); +} + +static const char *long_help_msg = + "First option must be a mode specifier:\n" + " -c Create -r Add/Replace -t List -u Update -x Extract\n" + "Common Options:\n" + " -b # Use # 512-byte records per I/O block\n" + " -f <filename> Location of archive (default " _PATH_DEFTAPE ")\n" + " -v Verbose\n" + " -w Interactive\n" + "Create: %p -c [options] [<file> | <dir> | @<archive> | -C <dir> ]\n" + " <file>, <dir> add these items to archive\n" + " -z, -j Compress archive with gzip/bzip2\n" + " --format {ustar|pax|cpio|shar} Select archive format\n" +#ifdef HAVE_GETOPT_LONG + " --exclude <pattern> Skip files that match pattern\n" +#else + " -W exclude=<pattern> Skip files that match pattern\n" +#endif + " -C <dir> Change to <dir> before processing remaining files\n" + " @<archive> Add entries from <archive> to output\n" + "List: %p -t [options] [<patterns>]\n" + " <patterns> If specified, list only entries that match\n" + "Extract: %p -x [options] [<patterns>]\n" + " <patterns> If specified, extract only entries that match\n" + " -k Keep (don't overwrite) existing files\n" + " -m Don't restore modification times\n" + " -O Write entries to stdout, don't restore to disk\n" + " -p Restore permissions (including ACLs, owner, file flags)\n"; + + +/* + * Note that the word 'bsdtar' will always appear in the first line + * of output. + * + * In particular, /bin/sh scripts that need to test for the presence + * of bsdtar can use the following template: + * + * if (tar --help 2>&1 | grep bsdtar >/dev/null 2>&1 ) then \ + * echo bsdtar; else echo not bsdtar; fi + */ +static void +long_help(struct bsdtar *bsdtar) +{ + const char *prog; + const char *p; + + prog = bsdtar->progname; + + fflush(stderr); + + p = (strcmp(prog,"bsdtar") != 0) ? "(bsdtar)" : ""; + printf("%s%s: manipulate archive files\n", prog, p); + + for (p = long_help_msg; *p != '\0'; p++) { + if (*p == '%') { + if (p[1] == 'p') { + fputs(prog, stdout); + p++; + } else + putchar('%'); + } else + putchar(*p); + } + version(); +} + +static int +bsdtar_getopt(struct bsdtar *bsdtar, const char *optstring, + const struct option **poption) +{ + char *p, *q; + const struct option *option; + int opt; + int option_index; + size_t option_length; + + option_index = -1; + *poption = NULL; + +#ifdef HAVE_GETOPT_LONG + opt = getopt_long(bsdtar->argc, bsdtar->argv, optstring, + tar_longopts, &option_index); + if (option_index > -1) + *poption = tar_longopts + option_index; +#else + opt = getopt(bsdtar->argc, bsdtar->argv, optstring); +#endif + + /* Support long options through -W longopt=value */ + if (opt == 'W') { + p = optarg; + q = strchr(optarg, '='); + if (q != NULL) { + option_length = (size_t)(q - p); + optarg = q + 1; + } else { + option_length = strlen(p); + optarg = NULL; + } + option = tar_longopts; + while (option->name != NULL && + (strlen(option->name) < option_length || + strncmp(p, option->name, option_length) != 0 )) { + option++; + } + + if (option->name != NULL) { + *poption = option; + opt = option->val; + + /* If the first match was exact, we're done. */ + if (strncmp(p, option->name, strlen(option->name)) == 0) { + while (option->name != NULL) + option++; + } else { + /* Check if there's another match. */ + option++; + while (option->name != NULL && + (strlen(option->name) < option_length || + strncmp(p, option->name, option_length) != 0)) { + option++; + } + } + if (option->name != NULL) + bsdtar_errc(bsdtar, 1, 0, + "Ambiguous option %s " + "(matches both %s and %s)", + p, (*poption)->name, option->name); + + if ((*poption)->has_arg == required_argument + && optarg == NULL) + bsdtar_errc(bsdtar, 1, 0, + "Option \"%s\" requires argument", p); + } else { + opt = '?'; + /* TODO: Set up a fake 'struct option' for + * error reporting... ? ? ? */ + } + } + + return (opt); +} diff --git a/tar/bsdtar.h b/tar/bsdtar.h new file mode 100644 index 00000000..167b5523 --- /dev/null +++ b/tar/bsdtar.h @@ -0,0 +1,125 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/tar/bsdtar.h,v 1.30 2008/03/15 03:06:46 kientzle Exp $ + */ + +#include "bsdtar_platform.h" +#include <stdio.h> + +#define DEFAULT_BYTES_PER_BLOCK (20*512) + +/* + * The internal state for the "bsdtar" program. + * + * Keeping all of the state in a structure like this simplifies memory + * leak testing (at exit, anything left on the heap is suspect). A + * pointer to this structure is passed to most bsdtar internal + * functions. + */ +struct bsdtar { + /* Options */ + const char *filename; /* -f filename */ + const char *create_format; /* -F format */ + char *pending_chdir; /* -C dir */ + const char *names_from_file; /* -T file */ + time_t newer_ctime_sec; /* --newer/--newer-than */ + long newer_ctime_nsec; /* --newer/--newer-than */ + time_t newer_mtime_sec; /* --newer-mtime */ + long newer_mtime_nsec; /* --newer-mtime-than */ + int bytes_per_block; /* -b block_size */ + int verbose; /* -v */ + int extract_flags; /* Flags for extract operation */ + int strip_components; /* Remove this many leading dirs */ + char mode; /* Program mode: 'c', 't', 'r', 'u', 'x' */ + char symlink_mode; /* H or L, per BSD conventions */ + char create_compression; /* j, y, or z */ + const char *compress_program; + char option_absolute_paths; /* -P */ + char option_chroot; /* --chroot */ + char option_dont_traverse_mounts; /* --one-file-system */ + char option_fast_read; /* --fast-read */ + char option_honor_nodump; /* --nodump */ + char option_interactive; /* -w */ + char option_no_owner; /* -o */ + char option_no_subdirs; /* -n */ + char option_null; /* --null */ + char option_stdout; /* -O */ + char option_totals; /* --totals */ + char option_unlink_first; /* -U */ + char option_warn_links; /* --check-links */ + char day_first; /* show day before month in -tv output */ + + /* If >= 0, then close this when done. */ + int fd; + + /* Miscellaneous state information */ + struct archive *archive; + const char *progname; + int argc; + char **argv; + size_t gs_width; /* For 'list_item' in read.c */ + size_t u_width; /* for 'list_item' in read.c */ + uid_t user_uid; /* UID running this program */ + int return_value; /* Value returned by main() */ + char warned_lead_slash; /* Already displayed warning */ + char next_line_is_dir; /* Used for -C parsing in -cT */ + + /* + * Data for various subsystems. Full definitions are located in + * the file where they are used. + */ + struct archive_dir *archive_dir; /* for write.c */ + struct name_cache *gname_cache; /* for write.c */ + struct links_cache *links_cache; /* for write.c */ + struct matching *matching; /* for matching.c */ + struct security *security; /* for read.c */ + struct name_cache *uname_cache; /* for write.c */ +}; + +void bsdtar_errc(struct bsdtar *, int _eval, int _code, + const char *fmt, ...); +void bsdtar_warnc(struct bsdtar *, int _code, const char *fmt, ...); +void cleanup_exclusions(struct bsdtar *); +void do_chdir(struct bsdtar *); +int edit_pathname(struct bsdtar *, struct archive_entry *); +int exclude(struct bsdtar *, const char *pattern); +int exclude_from_file(struct bsdtar *, const char *pathname); +int excluded(struct bsdtar *, const char *pathname); +int include(struct bsdtar *, const char *pattern); +int include_from_file(struct bsdtar *, const char *pathname); +int pathcmp(const char *a, const char *b); +int process_lines(struct bsdtar *bsdtar, const char *pathname, + int (*process)(struct bsdtar *, const char *)); +void safe_fprintf(FILE *, const char *fmt, ...); +void set_chdir(struct bsdtar *, const char *newdir); +void tar_mode_c(struct bsdtar *bsdtar); +void tar_mode_r(struct bsdtar *bsdtar); +void tar_mode_t(struct bsdtar *bsdtar); +void tar_mode_u(struct bsdtar *bsdtar); +void tar_mode_x(struct bsdtar *bsdtar); +int unmatched_inclusions(struct bsdtar *bsdtar); +void usage(struct bsdtar *); +int yes(const char *fmt, ...); + diff --git a/tar/bsdtar_platform.h b/tar/bsdtar_platform.h new file mode 100644 index 00000000..ccb9d3c0 --- /dev/null +++ b/tar/bsdtar_platform.h @@ -0,0 +1,150 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/tar/bsdtar_platform.h,v 1.25 2008/01/02 00:23:00 kientzle Exp $ + */ + +/* + * This header is the first thing included in any of the bsdtar + * source files. As far as possible, platform-specific issues should + * be dealt with here and not within individual source files. + */ + +#ifndef BSDTAR_PLATFORM_H_INCLUDED +#define BSDTAR_PLATFORM_H_INCLUDED + +#if defined(PLATFORM_CONFIG_H) +/* Use hand-built config.h in environments that need it. */ +#include PLATFORM_CONFIG_H +#elif defined(HAVE_CONFIG_H) +/* Most POSIX platforms use the 'configure' script to build config.h */ +#include "../config.h" +#else +/* Warn if bsdtar hasn't been (automatically or manually) configured. */ +#error Oops: No config.h and no built-in configuration in bsdtar_platform.h. +#endif /* !HAVE_CONFIG_H */ + +/* No non-FreeBSD platform will have __FBSDID, so just define it here. */ +#ifdef __FreeBSD__ +#include <sys/cdefs.h> /* For __FBSDID */ +#else +/* Just leaving this macro replacement empty leads to a dangling semicolon. */ +#define __FBSDID(a) struct _undefined_hack +#endif + +#ifdef HAVE_LIBARCHIVE +/* If we're using the platform libarchive, include system headers. */ +#include <archive.h> +#include <archive_entry.h> +#else +/* Otherwise, include user headers. */ +#include "archive.h" +#include "archive_entry.h" +#endif + +/* + * Does this platform have complete-looking POSIX-style ACL support, + * including some variant of the acl_get_perm() function (which was + * omitted from the POSIX.1e draft)? + */ +#if HAVE_SYS_ACL_H && HAVE_ACL_PERMSET_T && HAVE_ACL_USER +#if HAVE_ACL_GET_PERM || HAVE_ACL_GET_PERM_NP +#define HAVE_POSIX_ACL 1 +#endif +#endif + +#ifdef HAVE_LIBACL +#include <acl/libacl.h> +#endif + +#if HAVE_ACL_GET_PERM +#define ACL_GET_PERM acl_get_perm +#else +#if HAVE_ACL_GET_PERM_NP +#define ACL_GET_PERM acl_get_perm_np +#endif +#endif + +/* + * Include "dirent.h" (or it's equivalent on several different platforms). + * + * This is slightly modified from the GNU autoconf recipe. + * In particular, FreeBSD includes d_namlen in it's dirent structure, + * so my configure script includes an explicit test for the d_namlen + * field. + */ +#if HAVE_DIRENT_H +# include <dirent.h> +# if HAVE_DIRENT_D_NAMLEN +# define DIRENT_NAMLEN(dirent) (dirent)->d_namlen +# else +# define DIRENT_NAMLEN(dirent) strlen((dirent)->d_name) +# endif +#else +# define dirent direct +# define DIRENT_NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + + +/* + * We need to be able to display a filesize using printf(). The type + * and format string here must be compatible with one another and + * large enough for any file. + */ +#if HAVE_UINTMAX_T +#define BSDTAR_FILESIZE_TYPE uintmax_t +#define BSDTAR_FILESIZE_PRINTF "%ju" +#else +#if HAVE_UNSIGNED_LONG_LONG +#define BSDTAR_FILESIZE_TYPE unsigned long long +#define BSDTAR_FILESIZE_PRINTF "%llu" +#else +#define BSDTAR_FILESIZE_TYPE unsigned long +#define BSDTAR_FILESIZE_PRINTF "%lu" +#endif +#endif + +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC +#define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctimespec.tv_nsec +#define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtimespec.tv_nsec +#else +#if HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC +#define ARCHIVE_STAT_CTIME_NANOS(st) (st)->st_ctim.tv_nsec +#define ARCHIVE_STAT_MTIME_NANOS(st) (st)->st_mtim.tv_nsec +#else +#define ARCHIVE_STAT_CTIME_NANOS(st) (0) +#define ARCHIVE_STAT_MTIME_NANOS(st) (0) +#endif +#endif + +#endif /* !BSDTAR_PLATFORM_H_INCLUDED */ diff --git a/tar/config_freebsd.h b/tar/config_freebsd.h new file mode 100644 index 00000000..8704f05c --- /dev/null +++ b/tar/config_freebsd.h @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/tar/config_freebsd.h,v 1.3 2008/03/15 03:06:46 kientzle Exp $ + */ + +/* A default configuration for FreeBSD, used if there is no config.h. */ + +#include <sys/param.h> /* __FreeBSD_version */ + +#if __FreeBSD__ > 4 +#define HAVE_ACL_GET_PERM 0 +#define HAVE_ACL_GET_PERM_NP 1 +#define HAVE_ACL_PERMSET_T 1 +#define HAVE_ACL_USER 1 +#endif +#undef HAVE_ATTR_XATTR_H +#define HAVE_BZLIB_H 1 +#define HAVE_CHFLAGS 1 +#define HAVE_CHROOT 1 +#define HAVE_DECL_OPTARG 1 +#define HAVE_DECL_OPTIND 1 +#define HAVE_DIRENT_D_NAMLEN 1 +#define HAVE_DIRENT_H 1 +#define HAVE_D_MD_ORDER 1 +#define HAVE_ERRNO_H 1 +#undef HAVE_EXT2FS_EXT2_FS_H +#define HAVE_FCHDIR 1 +#define HAVE_FCNTL_H 1 +#define HAVE_FNMATCH 1 +#define HAVE_FNMATCH_H 1 +#define HAVE_FNM_LEADING_DIR 1 +#define HAVE_FTRUNCATE 1 +#define HAVE_GETOPT_LONG 1 +#undef HAVE_GETXATTR +#define HAVE_GRP_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LANGINFO_H 1 +#undef HAVE_LGETXATTR +#undef HAVE_LIBACL +#define HAVE_LIBARCHIVE 1 +#define HAVE_LIBBZ2 1 +#define HAVE_LIBZ 1 +#define HAVE_LIMITS_H 1 +#undef HAVE_LINUX_EXT2_FS_H +#undef HAVE_LINUX_FS_H +#undef HAVE_LISTXATTR +#undef HAVE_LLISTXATTR +#define HAVE_LOCALE_H 1 +#define HAVE_MALLOC 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMORY_H 1 +#define HAVE_MEMSET 1 +#if __FreeBSD_version >= 450002 /* nl_langinfo introduced */ +#define HAVE_NL_LANGINFO 1 +#endif +#define HAVE_PATHS_H 1 +#define HAVE_PWD_H 1 +#define HAVE_SETLOCALE 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRCHR 1 +#define HAVE_STRDUP 1 +#define HAVE_STRERROR 1 +#define HAVE_STRFTIME 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRRCHR 1 +#undef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC +#define HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1 +#define HAVE_STRUCT_STAT_ST_RDEV 1 +#define HAVE_SYS_ACL_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_TIME_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_UINTMAX_T 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UNSIGNED_LONG_LONG +#define HAVE_VPRINTF 1 +#define HAVE_ZLIB_H 1 +#undef MAJOR_IN_MKDEV +#define STDC_HEADERS 1 + diff --git a/tar/getdate.y b/tar/getdate.y new file mode 100644 index 00000000..3253f6d6 --- /dev/null +++ b/tar/getdate.y @@ -0,0 +1,811 @@ +%{ +/* + * March 2005: Further modified and simplified by Tim Kientzle: + * Eliminate minutes-based calculations (just do everything in + * seconds), have lexer only recognize unsigned integers (handle '+' + * and '-' characters in grammar), combine tables into one table with + * explicit abbreviation notes, do am/pm adjustments in the grammar + * (eliminate some state variables and post-processing). Among other + * things, these changes eliminated two shift/reduce conflicts. (Went + * from 10 to 8.) + * All of Tim Kientzle's changes to this file are public domain. + */ + +/* +** Originally written by Steven M. Bellovin <smb@research.att.com> while +** at the University of North Carolina at Chapel Hill. Later tweaked by +** a couple of people on Usenet. Completely overhauled by Rich $alz +** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990; +** +** This grammar has 10 shift/reduce conflicts. +** +** This code is in the public domain and has no copyright. +*/ +/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */ +/* SUPPRESS 288 on yyerrlab *//* Label unused */ + +#ifdef __FreeBSD__ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: src/usr.bin/tar/getdate.y,v 1.9 2007/07/20 01:27:50 kientzle Exp $"); +#endif + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define yyparse getdate_yyparse +#define yylex getdate_yylex +#define yyerror getdate_yyerror + +static int yyparse(void); +static int yylex(void); +static int yyerror(const char *); + +time_t get_date(char *); + +#define EPOCH 1970 +#define HOUR(x) ((time_t)(x) * 60) +#define SECSPERDAY (24L * 60L * 60L) + +/* +** Daylight-savings mode: on, off, or not yet known. +*/ +typedef enum _DSTMODE { + DSTon, DSToff, DSTmaybe +} DSTMODE; + +/* +** Meridian: am or pm. +*/ +enum { tAM, tPM }; + +/* +** Global variables. We could get rid of most of these by using a good +** union as the yacc stack. (This routine was originally written before +** yacc had the %union construct.) Maybe someday; right now we only use +** the %union very rarely. +*/ +static char *yyInput; + +static DSTMODE yyDSTmode; +static time_t yyDayOrdinal; +static time_t yyDayNumber; +static int yyHaveDate; +static int yyHaveDay; +static int yyHaveRel; +static int yyHaveTime; +static int yyHaveZone; +static time_t yyTimezone; +static time_t yyDay; +static time_t yyHour; +static time_t yyMinutes; +static time_t yyMonth; +static time_t yySeconds; +static time_t yyYear; +static time_t yyRelMonth; +static time_t yyRelSeconds; + +%} + +%union { + time_t Number; +} + +%token tAGO tDAY tDAYZONE tAMPM tMONTH tMONTH_UNIT tSEC_UNIT tUNUMBER +%token tZONE tDST + +%type <Number> tDAY tDAYZONE tMONTH tMONTH_UNIT +%type <Number> tSEC_UNIT tUNUMBER tZONE tAMPM + +%% + +spec : /* NULL */ + | spec item + ; + +item : time { yyHaveTime++; } + | zone { yyHaveZone++; } + | date { yyHaveDate++; } + | day { yyHaveDay++; } + | rel { yyHaveRel++; } + | number + ; + +time : tUNUMBER tAMPM { + /* "7am" */ + yyHour = $1; + if (yyHour == 12) + yyHour = 0; + yyMinutes = 0; + yySeconds = 0; + if ($2 == tPM) + yyHour += 12; + } + | bare_time { + /* "7:12:18" "19:17" */ + } + | bare_time tAMPM { + /* "7:12pm", "12:20:13am" */ + if (yyHour == 12) + yyHour = 0; + if ($2 == tPM) + yyHour += 12; + } + | bare_time '+' tUNUMBER { + /* "7:14+0700" */ + yyDSTmode = DSToff; + yyTimezone = - ($3 % 100 + ($3 / 100) * 60); + } + | bare_time '-' tUNUMBER { + /* "19:14:12-0530" */ + yyDSTmode = DSToff; + yyTimezone = + ($3 % 100 + ($3 / 100) * 60); + } + ; + +bare_time : tUNUMBER ':' tUNUMBER { + yyHour = $1; + yyMinutes = $3; + yySeconds = 0; + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER { + yyHour = $1; + yyMinutes = $3; + yySeconds = $5; + } + ; + +zone : tZONE { + yyTimezone = $1; + yyDSTmode = DSToff; + } + | tDAYZONE { + yyTimezone = $1; + yyDSTmode = DSTon; + } + | tZONE tDST { + yyTimezone = $1; + yyDSTmode = DSTon; + } + ; + +day : tDAY { + yyDayOrdinal = 1; + yyDayNumber = $1; + } + | tDAY ',' { + /* "tue," "wednesday," */ + yyDayOrdinal = 1; + yyDayNumber = $1; + } + | tUNUMBER tDAY { + /* "second tues" "3 wed" */ + yyDayOrdinal = $1; + yyDayNumber = $2; + } + ; + +date : tUNUMBER '/' tUNUMBER { + /* "1/15" */ + yyMonth = $1; + yyDay = $3; + } + | tUNUMBER '/' tUNUMBER '/' tUNUMBER { + if ($1 >= 13) { + /* First number is big: 2004/01/29, 99/02/17 */ + yyYear = $1; + yyMonth = $3; + yyDay = $5; + } else if (($5 >= 13) || ($3 >= 13)) { + /* Last number is big: 01/07/98 */ + /* Middle number is big: 01/29/04 */ + yyMonth = $1; + yyDay = $3; + yyYear = $5; + } else { + /* No significant clues: 02/03/04 */ + yyMonth = $1; + yyDay = $3; + yyYear = $5; + } + } + | tUNUMBER '-' tUNUMBER '-' tUNUMBER { + /* ISO 8601 format. yyyy-mm-dd. */ + yyYear = $1; + yyMonth = $3; + yyDay = $5; + } + | tUNUMBER '-' tMONTH '-' tUNUMBER { + if ($1 > 31) { + /* e.g. 1992-Jun-17 */ + yyYear = $1; + yyMonth = $3; + yyDay = $5; + } else { + /* e.g. 17-JUN-1992. */ + yyDay = $1; + yyMonth = $3; + yyYear = $5; + } + } + | tMONTH tUNUMBER { + /* "May 3" */ + yyMonth = $1; + yyDay = $2; + } + | tMONTH tUNUMBER ',' tUNUMBER { + /* "June 17, 2001" */ + yyMonth = $1; + yyDay = $2; + yyYear = $4; + } + | tUNUMBER tMONTH { + /* "12 Sept" */ + yyDay = $1; + yyMonth = $2; + } + | tUNUMBER tMONTH tUNUMBER { + /* "12 Sept 1997" */ + yyDay = $1; + yyMonth = $2; + yyYear = $3; + } + ; + +rel : relunit tAGO { + yyRelSeconds = -yyRelSeconds; + yyRelMonth = -yyRelMonth; + } + | relunit + ; + +relunit : '-' tUNUMBER tSEC_UNIT { + /* "-3 hours" */ + yyRelSeconds -= $2 * $3; + } + | '+' tUNUMBER tSEC_UNIT { + /* "+1 minute" */ + yyRelSeconds += $2 * $3; + } + | tUNUMBER tSEC_UNIT { + /* "1 day" */ + yyRelSeconds += $1 * $2; + } + | tSEC_UNIT { + /* "hour" */ + yyRelSeconds += $1; + } + | '-' tUNUMBER tMONTH_UNIT { + /* "-3 months" */ + yyRelMonth -= $2 * $3; + } + | '+' tUNUMBER tMONTH_UNIT { + /* "+5 years" */ + yyRelMonth += $2 * $3; + } + | tUNUMBER tMONTH_UNIT { + /* "2 years" */ + yyRelMonth += $1 * $2; + } + | tMONTH_UNIT { + /* "6 months" */ + yyRelMonth += $1; + } + ; + +number : tUNUMBER { + if (yyHaveTime && yyHaveDate && !yyHaveRel) + yyYear = $1; + else { + if($1>10000) { + /* "20040301" */ + yyHaveDate++; + yyDay= ($1)%100; + yyMonth= ($1/100)%100; + yyYear = $1/10000; + } + else { + /* "513" is same as "5:13" */ + yyHaveTime++; + if ($1 < 100) { + yyHour = $1; + yyMinutes = 0; + } + else { + yyHour = $1 / 100; + yyMinutes = $1 % 100; + } + yySeconds = 0; + } + } + } + ; + + +%% + +static struct TABLE { + size_t abbrev; + const char *name; + int type; + time_t value; +} const TimeWords[] = { + /* am/pm */ + { 0, "am", tAMPM, tAM }, + { 0, "pm", tAMPM, tPM }, + + /* Month names. */ + { 3, "january", tMONTH, 1 }, + { 3, "february", tMONTH, 2 }, + { 3, "march", tMONTH, 3 }, + { 3, "april", tMONTH, 4 }, + { 3, "may", tMONTH, 5 }, + { 3, "june", tMONTH, 6 }, + { 3, "july", tMONTH, 7 }, + { 3, "august", tMONTH, 8 }, + { 3, "september", tMONTH, 9 }, + { 3, "october", tMONTH, 10 }, + { 3, "november", tMONTH, 11 }, + { 3, "december", tMONTH, 12 }, + + /* Days of the week. */ + { 2, "sunday", tDAY, 0 }, + { 3, "monday", tDAY, 1 }, + { 2, "tuesday", tDAY, 2 }, + { 3, "wednesday", tDAY, 3 }, + { 2, "thursday", tDAY, 4 }, + { 2, "friday", tDAY, 5 }, + { 2, "saturday", tDAY, 6 }, + + /* Timezones: Offsets are in minutes. */ + { 0, "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ + { 0, "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ + { 0, "utc", tZONE, HOUR( 0) }, + { 0, "wet", tZONE, HOUR( 0) }, /* Western European */ + { 0, "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ + { 0, "wat", tZONE, HOUR( 1) }, /* West Africa */ + { 0, "at", tZONE, HOUR( 2) }, /* Azores */ + /* { 0, "bst", tZONE, HOUR( 3) }, */ /* Brazil Standard: Conflict */ + /* { 0, "gst", tZONE, HOUR( 3) }, */ /* Greenland Standard: Conflict*/ + { 0, "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */ + { 0, "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */ + { 0, "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */ + { 0, "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ + { 0, "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ + { 0, "est", tZONE, HOUR( 5) }, /* Eastern Standard */ + { 0, "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ + { 0, "cst", tZONE, HOUR( 6) }, /* Central Standard */ + { 0, "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ + { 0, "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ + { 0, "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ + { 0, "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ + { 0, "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ + { 0, "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ + { 0, "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ + { 0, "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ + { 0, "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ + { 0, "cat", tZONE, HOUR(10) }, /* Central Alaska */ + { 0, "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ + { 0, "nt", tZONE, HOUR(11) }, /* Nome */ + { 0, "idlw", tZONE, HOUR(12) }, /* Intl Date Line West */ + { 0, "cet", tZONE, -HOUR(1) }, /* Central European */ + { 0, "met", tZONE, -HOUR(1) }, /* Middle European */ + { 0, "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ + { 0, "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ + { 0, "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ + { 0, "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ + { 0, "fwt", tZONE, -HOUR(1) }, /* French Winter */ + { 0, "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ + { 0, "eet", tZONE, -HOUR(2) }, /* Eastern Eur, USSR Zone 1 */ + { 0, "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ + { 0, "it", tZONE, -HOUR(3)-30 },/* Iran */ + { 0, "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ + { 0, "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ + { 0, "ist", tZONE, -HOUR(5)-30 },/* Indian Standard */ + { 0, "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ + /* { 0, "nst", tZONE, -HOUR(6.5) }, */ /* North Sumatra: Conflict */ + /* { 0, "sst", tZONE, -HOUR(7) }, */ /* So Sumatra, USSR 6: Conflict */ + { 0, "wast", tZONE, -HOUR(7) }, /* West Australian Standard */ + { 0, "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */ + { 0, "jt", tZONE, -HOUR(7)-30 },/* Java (3pm in Cronusland!)*/ + { 0, "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ + { 0, "jst", tZONE, -HOUR(9) }, /* Japan Std, USSR Zone 8 */ + { 0, "cast", tZONE, -HOUR(9)-30 },/* Central Australian Std */ + { 0, "cadt", tDAYZONE, -HOUR(9)-30 },/* Central Australian Daylt */ + { 0, "east", tZONE, -HOUR(10) }, /* Eastern Australian Std */ + { 0, "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylt */ + { 0, "gst", tZONE, -HOUR(10) }, /* Guam Std, USSR Zone 9 */ + { 0, "nzt", tZONE, -HOUR(12) }, /* New Zealand */ + { 0, "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ + { 0, "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ + { 0, "idle", tZONE, -HOUR(12) }, /* Intl Date Line East */ + + { 0, "dst", tDST, 0 }, + + /* Time units. */ + { 4, "years", tMONTH_UNIT, 12 }, + { 5, "months", tMONTH_UNIT, 1 }, + { 9, "fortnights", tSEC_UNIT, 14 * 24 * 60 * 60 }, + { 4, "weeks", tSEC_UNIT, 7 * 24 * 60 * 60 }, + { 3, "days", tSEC_UNIT, 1 * 24 * 60 * 60 }, + { 4, "hours", tSEC_UNIT, 60 * 60 }, + { 3, "minutes", tSEC_UNIT, 60 }, + { 3, "seconds", tSEC_UNIT, 1 }, + + /* Relative-time words. */ + { 0, "tomorrow", tSEC_UNIT, 1 * 24 * 60 * 60 }, + { 0, "yesterday", tSEC_UNIT, -1 * 24 * 60 * 60 }, + { 0, "today", tSEC_UNIT, 0 }, + { 0, "now", tSEC_UNIT, 0 }, + { 0, "last", tUNUMBER, -1 }, + { 0, "this", tSEC_UNIT, 0 }, + { 0, "next", tUNUMBER, 2 }, + { 0, "first", tUNUMBER, 1 }, + { 0, "1st", tUNUMBER, 1 }, +/* { 0, "second", tUNUMBER, 2 }, */ + { 0, "2nd", tUNUMBER, 2 }, + { 0, "third", tUNUMBER, 3 }, + { 0, "3rd", tUNUMBER, 3 }, + { 0, "fourth", tUNUMBER, 4 }, + { 0, "4th", tUNUMBER, 4 }, + { 0, "fifth", tUNUMBER, 5 }, + { 0, "5th", tUNUMBER, 5 }, + { 0, "sixth", tUNUMBER, 6 }, + { 0, "seventh", tUNUMBER, 7 }, + { 0, "eighth", tUNUMBER, 8 }, + { 0, "ninth", tUNUMBER, 9 }, + { 0, "tenth", tUNUMBER, 10 }, + { 0, "eleventh", tUNUMBER, 11 }, + { 0, "twelfth", tUNUMBER, 12 }, + { 0, "ago", tAGO, 1 }, + + /* Military timezones. */ + { 0, "a", tZONE, HOUR( 1) }, + { 0, "b", tZONE, HOUR( 2) }, + { 0, "c", tZONE, HOUR( 3) }, + { 0, "d", tZONE, HOUR( 4) }, + { 0, "e", tZONE, HOUR( 5) }, + { 0, "f", tZONE, HOUR( 6) }, + { 0, "g", tZONE, HOUR( 7) }, + { 0, "h", tZONE, HOUR( 8) }, + { 0, "i", tZONE, HOUR( 9) }, + { 0, "k", tZONE, HOUR( 10) }, + { 0, "l", tZONE, HOUR( 11) }, + { 0, "m", tZONE, HOUR( 12) }, + { 0, "n", tZONE, HOUR(- 1) }, + { 0, "o", tZONE, HOUR(- 2) }, + { 0, "p", tZONE, HOUR(- 3) }, + { 0, "q", tZONE, HOUR(- 4) }, + { 0, "r", tZONE, HOUR(- 5) }, + { 0, "s", tZONE, HOUR(- 6) }, + { 0, "t", tZONE, HOUR(- 7) }, + { 0, "u", tZONE, HOUR(- 8) }, + { 0, "v", tZONE, HOUR(- 9) }, + { 0, "w", tZONE, HOUR(-10) }, + { 0, "x", tZONE, HOUR(-11) }, + { 0, "y", tZONE, HOUR(-12) }, + { 0, "z", tZONE, HOUR( 0) }, + + /* End of table. */ + { 0, NULL, 0, 0 } +}; + + + + +/* ARGSUSED */ +static int +yyerror(const char *s) +{ + (void)s; + return 0; +} + +static time_t +ToSeconds(time_t Hours, time_t Minutes, time_t Seconds) +{ + if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) + return -1; + if (Hours < 0 || Hours > 23) + return -1; + return (Hours * 60L + Minutes) * 60L + Seconds; +} + + +/* Year is either + * A number from 0 to 99, which means a year from 1970 to 2069, or + * The actual year (>=100). */ +static time_t +Convert(time_t Month, time_t Day, time_t Year, + time_t Hours, time_t Minutes, time_t Seconds, DSTMODE DSTmode) +{ + static int DaysInMonth[12] = { + 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + time_t tod; + time_t Julian; + int i; + + if (Year < 69) + Year += 2000; + else if (Year < 100) + Year += 1900; + DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) + ? 29 : 28; + /* Checking for 2038 bogusly assumes that time_t is 32 bits. But + I'm too lazy to try to check for time_t overflow in another way. */ + if (Year < EPOCH || Year > 2038 + || Month < 1 || Month > 12 + /* Lint fluff: "conversion from long may lose accuracy" */ + || Day < 1 || Day > DaysInMonth[(int)--Month]) + return -1; + + Julian = Day - 1; + for (i = 0; i < Month; i++) + Julian += DaysInMonth[i]; + for (i = EPOCH; i < Year; i++) + Julian += 365 + (i % 4 == 0); + Julian *= SECSPERDAY; + Julian += yyTimezone * 60L; + if ((tod = ToSeconds(Hours, Minutes, Seconds)) < 0) + return -1; + Julian += tod; + if (DSTmode == DSTon + || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) + Julian -= 60 * 60; + return Julian; +} + + +static time_t +DSTcorrect(time_t Start, time_t Future) +{ + time_t StartDay; + time_t FutureDay; + + StartDay = (localtime(&Start)->tm_hour + 1) % 24; + FutureDay = (localtime(&Future)->tm_hour + 1) % 24; + return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; +} + + +static time_t +RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber) +{ + struct tm *tm; + time_t now; + + now = Start; + tm = localtime(&now); + now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); + now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); + return DSTcorrect(Start, now); +} + + +static time_t +RelativeMonth(time_t Start, time_t RelMonth) +{ + struct tm *tm; + time_t Month; + time_t Year; + + if (RelMonth == 0) + return 0; + tm = localtime(&Start); + Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth; + Year = Month / 12; + Month = Month % 12 + 1; + return DSTcorrect(Start, + Convert(Month, (time_t)tm->tm_mday, Year, + (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, + DSTmaybe)); +} + +static int +yylex(void) +{ + char c; + char buff[64]; + + for ( ; ; ) { + while (isspace((unsigned char)*yyInput)) + yyInput++; + + /* Skip parenthesized comments. */ + if (*yyInput == '(') { + int Count = 0; + do { + c = *yyInput++; + if (c == '\0') + return c; + if (c == '(') + Count++; + else if (c == ')') + Count--; + } while (Count > 0); + continue; + } + + /* Try the next token in the word table first. */ + /* This allows us to match "2nd", for example. */ + { + char *src = yyInput; + const struct TABLE *tp; + unsigned i = 0; + + /* Force to lowercase and strip '.' characters. */ + while (*src != '\0' + && (isalnum((unsigned char)*src) || *src == '.') + && i < sizeof(buff)-1) { + if (*src != '.') { + if (isupper((unsigned char)*src)) + buff[i++] = tolower((unsigned char)*src); + else + buff[i++] = *src; + } + src++; + } + buff[i++] = '\0'; + + /* + * Find the first match. If the word can be + * abbreviated, make sure we match at least + * the minimum abbreviation. + */ + for (tp = TimeWords; tp->name; tp++) { + size_t abbrev = tp->abbrev; + if (abbrev == 0) + abbrev = strlen(tp->name); + if (strlen(buff) >= abbrev + && strncmp(tp->name, buff, strlen(buff)) + == 0) { + /* Skip over token. */ + yyInput = src; + /* Return the match. */ + yylval.Number = tp->value; + return tp->type; + } + } + } + + /* + * Not in the word table, maybe it's a number. Note: + * Because '-' and '+' have other special meanings, I + * don't deal with signed numbers here. + */ + if (isdigit((unsigned char)(c = *yyInput))) { + for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); ) + yylval.Number = 10 * yylval.Number + c - '0'; + yyInput--; + return (tUNUMBER); + } + + return (*yyInput++); + } +} + +#define TM_YEAR_ORIGIN 1900 + +/* Yield A - B, measured in seconds. */ +static long +difftm (struct tm *a, struct tm *b) +{ + int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); + int by = b->tm_year + (TM_YEAR_ORIGIN - 1); + int days = ( + /* difference in day of year */ + a->tm_yday - b->tm_yday + /* + intervening leap days */ + + ((ay >> 2) - (by >> 2)) + - (ay/100 - by/100) + + ((ay/100 >> 2) - (by/100 >> 2)) + /* + difference in years * 365 */ + + (long)(ay-by) * 365 + ); + return (60*(60*(24*days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} + +time_t +get_date(char *p) +{ + struct tm *tm; + struct tm gmt, *gmt_ptr; + time_t Start; + time_t tod; + time_t nowtime; + long tzone; + + memset(&gmt, 0, sizeof(gmt)); + yyInput = p; + + (void)time (&nowtime); + + gmt_ptr = gmtime (&nowtime); + if (gmt_ptr != NULL) { + /* Copy, in case localtime and gmtime use the same buffer. */ + gmt = *gmt_ptr; + } + + if (! (tm = localtime (&nowtime))) + return -1; + + if (gmt_ptr != NULL) + tzone = difftm (&gmt, tm) / 60; + else + /* This system doesn't understand timezones; fake it. */ + tzone = 0; + if(tm->tm_isdst) + tzone += 60; + + yyYear = tm->tm_year + 1900; + yyMonth = tm->tm_mon + 1; + yyDay = tm->tm_mday; + yyTimezone = tzone; + yyDSTmode = DSTmaybe; + yyHour = 0; + yyMinutes = 0; + yySeconds = 0; + yyRelSeconds = 0; + yyRelMonth = 0; + yyHaveDate = 0; + yyHaveDay = 0; + yyHaveRel = 0; + yyHaveTime = 0; + yyHaveZone = 0; + + if (yyparse() + || yyHaveTime > 1 || yyHaveZone > 1 + || yyHaveDate > 1 || yyHaveDay > 1) + return -1; + + if (yyHaveDate || yyHaveTime || yyHaveDay) { + Start = Convert(yyMonth, yyDay, yyYear, + yyHour, yyMinutes, yySeconds, yyDSTmode); + if (Start < 0) + return -1; + } else { + Start = nowtime; + if (!yyHaveRel) + Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; + } + + Start += yyRelSeconds; + Start += RelativeMonth(Start, yyRelMonth); + + if (yyHaveDay && !yyHaveDate) { + tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber); + Start += tod; + } + + /* Have to do *something* with a legitimate -1 so it's + * distinguishable from the error return value. (Alternately + * could set errno on error.) */ + return Start == -1 ? 0 : Start; +} + + +#if defined(TEST) + +/* ARGSUSED */ +int +main(int argc, char **argv) +{ + time_t d; + + while (*++argv != NULL) { + (void)printf("Input: %s\n", *argv); + d = get_date(*argv); + if (d == -1) + (void)printf("Bad format - couldn't convert.\n"); + else + (void)printf("Output: %s\n", ctime(&d)); + } + exit(0); + /* NOTREACHED */ +} +#endif /* defined(TEST) */ diff --git a/tar/matching.c b/tar/matching.c new file mode 100644 index 00000000..9a6b2795 --- /dev/null +++ b/tar/matching.c @@ -0,0 +1,421 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/matching.c,v 1.12 2008/03/18 06:18:49 kientzle Exp $"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "bsdtar.h" + +struct match { + struct match *next; + int matches; + char pattern[1]; +}; + +struct matching { + struct match *exclusions; + int exclusions_count; + struct match *inclusions; + int inclusions_count; + int inclusions_unmatched_count; +}; + + +static void add_pattern(struct bsdtar *, struct match **list, + const char *pattern); +static int bsdtar_fnmatch(const char *p, const char *s); +static void initialize_matching(struct bsdtar *); +static int match_exclusion(struct match *, const char *pathname); +static int match_inclusion(struct match *, const char *pathname); + +/* + * The matching logic here needs to be re-thought. I started out to + * try to mimic gtar's matching logic, but it's not entirely + * consistent. In particular 'tar -t' and 'tar -x' interpret patterns + * on the command line as anchored, but --exclude doesn't. + */ + +/* + * Utility functions to manage exclusion/inclusion patterns + */ + +int +exclude(struct bsdtar *bsdtar, const char *pattern) +{ + struct matching *matching; + + if (bsdtar->matching == NULL) + initialize_matching(bsdtar); + matching = bsdtar->matching; + add_pattern(bsdtar, &(matching->exclusions), pattern); + matching->exclusions_count++; + return (0); +} + +int +exclude_from_file(struct bsdtar *bsdtar, const char *pathname) +{ + return (process_lines(bsdtar, pathname, &exclude)); +} + +int +include(struct bsdtar *bsdtar, const char *pattern) +{ + struct matching *matching; + + if (bsdtar->matching == NULL) + initialize_matching(bsdtar); + matching = bsdtar->matching; + add_pattern(bsdtar, &(matching->inclusions), pattern); + matching->inclusions_count++; + matching->inclusions_unmatched_count++; + return (0); +} + +int +include_from_file(struct bsdtar *bsdtar, const char *pathname) +{ + return (process_lines(bsdtar, pathname, &include)); +} + +static void +add_pattern(struct bsdtar *bsdtar, struct match **list, const char *pattern) +{ + struct match *match; + + match = malloc(sizeof(*match) + strlen(pattern) + 1); + if (match == NULL) + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + if (pattern[0] == '/') + pattern++; + strcpy(match->pattern, pattern); + /* Both "foo/" and "foo" should match "foo/bar". */ + if (match->pattern[strlen(match->pattern)-1] == '/') + match->pattern[strlen(match->pattern)-1] = '\0'; + match->next = *list; + *list = match; + match->matches = 0; +} + + +int +excluded(struct bsdtar *bsdtar, const char *pathname) +{ + struct matching *matching; + struct match *match; + struct match *matched; + + matching = bsdtar->matching; + if (matching == NULL) + return (0); + + /* Exclusions take priority */ + for (match = matching->exclusions; match != NULL; match = match->next){ + if (match_exclusion(match, pathname)) + return (1); + } + + /* Then check for inclusions */ + matched = NULL; + for (match = matching->inclusions; match != NULL; match = match->next){ + if (match_inclusion(match, pathname)) { + /* + * If this pattern has never been matched, + * then we're done. + */ + if (match->matches == 0) { + match->matches++; + matching->inclusions_unmatched_count--; + return (0); + } + /* + * Otherwise, remember the match but keep checking + * in case we can tick off an unmatched pattern. + */ + matched = match; + } + } + /* + * We didn't find a pattern that had never been matched, but + * we did find a match, so count it and exit. + */ + if (matched != NULL) { + matched->matches++; + return (0); + } + + /* If there were inclusions, default is to exclude. */ + if (matching->inclusions != NULL) + return (1); + + /* No explicit inclusions, default is to match. */ + return (0); +} + +/* + * This is a little odd, but it matches the default behavior of + * gtar. In particular, 'a*b' will match 'foo/a1111/222b/bar' + * + */ +int +match_exclusion(struct match *match, const char *pathname) +{ + const char *p; + + if (*match->pattern == '*' || *match->pattern == '/') + return (bsdtar_fnmatch(match->pattern, pathname) == 0); + + for (p = pathname; p != NULL; p = strchr(p, '/')) { + if (*p == '/') + p++; + if (bsdtar_fnmatch(match->pattern, p) == 0) + return (1); + } + return (0); +} + +/* + * Again, mimic gtar: inclusions are always anchored (have to match + * the beginning of the path) even though exclusions are not anchored. + */ +int +match_inclusion(struct match *match, const char *pathname) +{ + return (bsdtar_fnmatch(match->pattern, pathname) == 0); +} + +void +cleanup_exclusions(struct bsdtar *bsdtar) +{ + struct match *p, *q; + + if (bsdtar->matching) { + p = bsdtar->matching->inclusions; + while (p != NULL) { + q = p; + p = p->next; + free(q); + } + p = bsdtar->matching->exclusions; + while (p != NULL) { + q = p; + p = p->next; + free(q); + } + free(bsdtar->matching); + } +} + +static void +initialize_matching(struct bsdtar *bsdtar) +{ + bsdtar->matching = malloc(sizeof(*bsdtar->matching)); + if (bsdtar->matching == NULL) + bsdtar_errc(bsdtar, 1, errno, "No memory"); + memset(bsdtar->matching, 0, sizeof(*bsdtar->matching)); +} + +int +unmatched_inclusions(struct bsdtar *bsdtar) +{ + struct matching *matching; + + matching = bsdtar->matching; + if (matching == NULL) + return (0); + return (matching->inclusions_unmatched_count); +} + + + +#if defined(HAVE_FNMATCH) && defined(HAVE_FNM_LEADING_DIR) + +/* Use system fnmatch() if it suits our needs. */ +/* On Linux, _GNU_SOURCE must be defined to get FNM_LEADING_DIR. */ +#define _GNU_SOURCE +#include <fnmatch.h> +static int +bsdtar_fnmatch(const char *pattern, const char *string) +{ + return (fnmatch(pattern, string, FNM_LEADING_DIR)); +} + +#else +/* + * The following was hacked from BSD C library + * code: src/lib/libc/gen/fnmatch.c,v 1.15 2002/02/01 + * + * In particular, most of the flags were ripped out: this always + * behaves like FNM_LEADING_DIR is set and other flags specified + * by POSIX are unset. + * + * Normally, I would not conditionally compile something like this: If + * I have to support it anyway, everyone may as well use it. ;-) + * However, the full POSIX spec for fnmatch() includes a lot of + * advanced character handling that I'm not ready to put in here, so + * it's probably best if people use a local version when it's available. + */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * 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 int +bsdtar_fnmatch(const char *pattern, const char *string) +{ + const char *saved_pattern; + int negate, matched; + char c; + + for (;;) { + switch (c = *pattern++) { + case '\0': + if (*string == '/' || *string == '\0') + return (0); + return (1); + case '?': + if (*string == '\0') + return (1); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + /* Optimize for pattern with * at end. */ + if (c == '\0') + return (0); + + /* General case, use recursion. */ + while (*string != '\0') { + if (!bsdtar_fnmatch(pattern, string)) + return (0); + ++string; + } + return (1); + case '[': + if (*string == '\0') + return (1); + saved_pattern = pattern; + if (*pattern == '!' || *pattern == '^') { + negate = 1; + ++pattern; + } else + negate = 0; + matched = 0; + c = *pattern++; + do { + if (c == '\\') + c = *pattern++; + if (c == '\0') { + pattern = saved_pattern; + c = '['; + goto norm; + } + if (*pattern == '-') { + char c2 = *(pattern + 1); + if (c2 == '\0') { + pattern = saved_pattern; + c = '['; + goto norm; + } + if (c2 == ']') { + /* [a-] is not a range. */ + if (c == *string + || '-' == *string) + matched = 1; + pattern ++; + } else { + if (c <= *string + && *string <= c2) + matched = 1; + pattern += 2; + } + } else if (c == *string) + matched = 1; + c = *pattern++; + } while (c != ']'); + if (matched == negate) + return (1); + ++string; + break; + case '\\': + if ((c = *pattern++) == '\0') { + c = '\\'; + --pattern; + } + /* FALLTHROUGH */ + default: + norm: + if (c != *string) + return (1); + string++; + break; + } + } + /* NOTREACHED */ +} + +#endif diff --git a/tar/read.c b/tar/read.c new file mode 100644 index 00000000..279a1054 --- /dev/null +++ b/tar/read.c @@ -0,0 +1,369 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/read.c,v 1.36 2008/03/15 03:06:46 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef MAJOR_IN_MKDEV +#include <sys/mkdev.h> +#elif defined(MAJOR_IN_SYSMACROS) +#include <sys/sysmacros.h> +#endif +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_TIME_H +#include <time.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "bsdtar.h" + +static void list_item_verbose(struct bsdtar *, FILE *, + struct archive_entry *); +static void read_archive(struct bsdtar *bsdtar, char mode); + +void +tar_mode_t(struct bsdtar *bsdtar) +{ + read_archive(bsdtar, 't'); +} + +void +tar_mode_x(struct bsdtar *bsdtar) +{ + read_archive(bsdtar, 'x'); +} + +/* + * Handle 'x' and 't' modes. + */ +static void +read_archive(struct bsdtar *bsdtar, char mode) +{ + FILE *out; + struct archive *a; + struct archive_entry *entry; + const struct stat *st; + int r; + + while (*bsdtar->argv) { + include(bsdtar, *bsdtar->argv); + bsdtar->argv++; + } + + if (bsdtar->names_from_file != NULL) + include_from_file(bsdtar, bsdtar->names_from_file); + + a = archive_read_new(); + if (bsdtar->compress_program != NULL) + archive_read_support_compression_program(a, bsdtar->compress_program); + else + archive_read_support_compression_all(a); + archive_read_support_format_all(a); + if (archive_read_open_file(a, bsdtar->filename, + bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block : + DEFAULT_BYTES_PER_BLOCK)) + bsdtar_errc(bsdtar, 1, 0, "Error opening archive: %s", + archive_error_string(a)); + + do_chdir(bsdtar); + + if (mode == 'x' && bsdtar->option_chroot) { +#if HAVE_CHROOT + if (chroot(".") != 0) + bsdtar_errc(bsdtar, 1, errno, "Can't chroot to \".\""); +#else + bsdtar_errc(bsdtar, 1, 0, + "chroot isn't supported on this platform"); +#endif + } + + for (;;) { + /* Support --fast-read option */ + if (bsdtar->option_fast_read && + unmatched_inclusions(bsdtar) == 0) + break; + + r = archive_read_next_header(a, &entry); + if (r == ARCHIVE_EOF) + break; + if (r < ARCHIVE_OK) + bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); + if (r <= ARCHIVE_WARN) + bsdtar->return_value = 1; + if (r == ARCHIVE_RETRY) { + /* Retryable error: try again */ + bsdtar_warnc(bsdtar, 0, "Retrying..."); + continue; + } + if (r == ARCHIVE_FATAL) + break; + + /* + * Exclude entries that are too old. + */ + st = archive_entry_stat(entry); + if (bsdtar->newer_ctime_sec > 0) { + if (st->st_ctime < bsdtar->newer_ctime_sec) + continue; /* Too old, skip it. */ + if (st->st_ctime == bsdtar->newer_ctime_sec + && ARCHIVE_STAT_CTIME_NANOS(st) + <= bsdtar->newer_ctime_nsec) + continue; /* Too old, skip it. */ + } + if (bsdtar->newer_mtime_sec > 0) { + if (st->st_mtime < bsdtar->newer_mtime_sec) + continue; /* Too old, skip it. */ + if (st->st_mtime == bsdtar->newer_mtime_sec + && ARCHIVE_STAT_MTIME_NANOS(st) + <= bsdtar->newer_mtime_nsec) + continue; /* Too old, skip it. */ + } + + /* + * Note that pattern exclusions are checked before + * pathname rewrites are handled. This gives more + * control over exclusions, since rewrites always lose + * information. (For example, consider a rewrite + * s/foo[0-9]/foo/. If we check exclusions after the + * rewrite, there would be no way to exclude foo1/bar + * while allowing foo2/bar.) + */ + if (excluded(bsdtar, archive_entry_pathname(entry))) + continue; /* Excluded by a pattern test. */ + + /* + * Modify the pathname as requested by the user. We + * do this for -t as well to give users a way to + * preview the effects of their rewrites. We also do + * this before extraction security checks (including + * leading '/' removal). Note that some rewrite + * failures prevent extraction. + */ + if (edit_pathname(bsdtar, entry)) + continue; /* Excluded by a rewrite failure. */ + + if (mode == 't') { + /* Perversely, gtar uses -O to mean "send to stderr" + * when used with -t. */ + out = bsdtar->option_stdout ? stderr : stdout; + + if (bsdtar->verbose < 2) + safe_fprintf(out, "%s", + archive_entry_pathname(entry)); + else + list_item_verbose(bsdtar, out, entry); + fflush(out); + r = archive_read_data_skip(a); + if (r == ARCHIVE_WARN) { + fprintf(out, "\n"); + bsdtar_warnc(bsdtar, 0, "%s", + archive_error_string(a)); + } + if (r == ARCHIVE_RETRY) { + fprintf(out, "\n"); + bsdtar_warnc(bsdtar, 0, "%s", + archive_error_string(a)); + } + if (r == ARCHIVE_FATAL) { + fprintf(out, "\n"); + bsdtar_warnc(bsdtar, 0, "%s", + archive_error_string(a)); + bsdtar->return_value = 1; + break; + } + fprintf(out, "\n"); + } else { + if (bsdtar->option_interactive && + !yes("extract '%s'", archive_entry_pathname(entry))) + continue; + + /* + * Format here is from SUSv2, including the + * deferred '\n'. + */ + if (bsdtar->verbose) { + safe_fprintf(stderr, "x %s", + archive_entry_pathname(entry)); + fflush(stderr); + } + if (bsdtar->option_stdout) + r = archive_read_data_into_fd(a, 1); + else + r = archive_read_extract(a, entry, + bsdtar->extract_flags); + if (r != ARCHIVE_OK) { + if (!bsdtar->verbose) + safe_fprintf(stderr, "%s", + archive_entry_pathname(entry)); + safe_fprintf(stderr, ": %s", + archive_error_string(a)); + if (!bsdtar->verbose) + fprintf(stderr, "\n"); + bsdtar->return_value = 1; + } + if (bsdtar->verbose) + fprintf(stderr, "\n"); + if (r == ARCHIVE_FATAL) + break; + } + } + + if (bsdtar->verbose > 2) + fprintf(stdout, "Archive Format: %s, Compression: %s\n", + archive_format_name(a), archive_compression_name(a)); + + archive_read_finish(a); +} + + +/* + * Display information about the current file. + * + * The format here roughly duplicates the output of 'ls -l'. + * This is based on SUSv2, where 'tar tv' is documented as + * listing additional information in an "unspecified format," + * and 'pax -l' is documented as using the same format as 'ls -l'. + */ +static void +list_item_verbose(struct bsdtar *bsdtar, FILE *out, struct archive_entry *entry) +{ + const struct stat *st; + char tmp[100]; + size_t w; + const char *p; + const char *fmt; + time_t tim; + static time_t now; + + st = archive_entry_stat(entry); + + /* + * We avoid collecting the entire list in memory at once by + * listing things as we see them. However, that also means we can't + * just pre-compute the field widths. Instead, we start with guesses + * and just widen them as necessary. These numbers are completely + * arbitrary. + */ + if (!bsdtar->u_width) { + bsdtar->u_width = 6; + bsdtar->gs_width = 13; + } + if (!now) + time(&now); + fprintf(out, "%s %d ", + archive_entry_strmode(entry), + (int)(st->st_nlink)); + + /* Use uname if it's present, else uid. */ + p = archive_entry_uname(entry); + if ((p == NULL) || (*p == '\0')) { + sprintf(tmp, "%lu ", (unsigned long)st->st_uid); + p = tmp; + } + w = strlen(p); + if (w > bsdtar->u_width) + bsdtar->u_width = w; + fprintf(out, "%-*s ", (int)bsdtar->u_width, p); + + /* Use gname if it's present, else gid. */ + p = archive_entry_gname(entry); + if (p != NULL && p[0] != '\0') { + fprintf(out, "%s", p); + w = strlen(p); + } else { + sprintf(tmp, "%lu", (unsigned long)st->st_gid); + w = strlen(tmp); + fprintf(out, "%s", tmp); + } + + /* + * Print device number or file size, right-aligned so as to make + * total width of group and devnum/filesize fields be gs_width. + * If gs_width is too small, grow it. + */ + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) { + sprintf(tmp, "%lu,%lu", + (unsigned long)major(st->st_rdev), + (unsigned long)minor(st->st_rdev)); /* ls(1) also casts here. */ + } else { + /* + * Note the use of platform-dependent macros to format + * the filesize here. We need the format string and the + * corresponding type for the cast. + */ + sprintf(tmp, BSDTAR_FILESIZE_PRINTF, + (BSDTAR_FILESIZE_TYPE)st->st_size); + } + if (w + strlen(tmp) >= bsdtar->gs_width) + bsdtar->gs_width = w+strlen(tmp)+1; + fprintf(out, "%*s", (int)(bsdtar->gs_width - w), tmp); + + /* Format the time using 'ls -l' conventions. */ + tim = (time_t)st->st_mtime; + if (abs(tim - now) > (365/2)*86400) + fmt = bsdtar->day_first ? "%e %b %Y" : "%b %e %Y"; + else + fmt = bsdtar->day_first ? "%e %b %R" : "%b %e %R"; + strftime(tmp, sizeof(tmp), fmt, localtime(&tim)); + fprintf(out, " %s ", tmp); + safe_fprintf(out, "%s", archive_entry_pathname(entry)); + + /* Extra information for links. */ + if (archive_entry_hardlink(entry)) /* Hard link */ + safe_fprintf(out, " link to %s", + archive_entry_hardlink(entry)); + else if (S_ISLNK(st->st_mode)) /* Symbolic link */ + safe_fprintf(out, " -> %s", archive_entry_symlink(entry)); +} diff --git a/tar/tree.c b/tar/tree.c new file mode 100644 index 00000000..23e64e3d --- /dev/null +++ b/tar/tree.c @@ -0,0 +1,542 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * This is a new directory-walking system that addresses a number + * of problems I've had with fts(3). In particular, it has no + * pathname-length limits (other than the size of 'int'), handles + * deep logical traversals, uses considerably less memory, and has + * an opaque interface (easier to modify in the future). + * + * Internally, it keeps a single list of "tree_entry" items that + * represent filesystem objects that require further attention. + * Non-directories are not kept in memory: they are pulled from + * readdir(), returned to the client, then freed as soon as possible. + * Any directory entry to be traversed gets pushed onto the stack. + * + * There is surprisingly little information that needs to be kept for + * each item on the stack. Just the name, depth (represented here as the + * string length of the parent directory's pathname), and some markers + * indicating how to get back to the parent (via chdir("..") for a + * regular dir or via fchdir(2) for a symlink). + */ +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/tree.c,v 1.8 2007/03/11 10:36:42 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "tree.h" + +/* + * TODO: + * 1) Loop checking. + * 3) Arbitrary logical traversals by closing/reopening intermediate fds. + */ + +struct tree_entry { + struct tree_entry *next; + struct tree_entry *parent; + char *name; + size_t dirname_length; + dev_t dev; + ino_t ino; + int fd; + int flags; +}; + +/* Definitions for tree_entry.flags bitmap. */ +#define isDir 1 /* This entry is a regular directory. */ +#define isDirLink 2 /* This entry is a symbolic link to a directory. */ +#define needsPreVisit 4 /* This entry needs to be previsited. */ +#define needsPostVisit 8 /* This entry needs to be postvisited. */ + +/* + * Local data for this package. + */ +struct tree { + struct tree_entry *stack; + struct tree_entry *current; + DIR *d; + int initialDirFd; + int flags; + int visit_type; + int tree_errno; /* Error code from last failed operation. */ + + char *buff; + const char *basename; + size_t buff_length; + size_t path_length; + size_t dirname_length; + + int depth; + int openCount; + int maxOpenCount; + + struct stat lst; + struct stat st; +}; + +/* Definitions for tree.flags bitmap. */ +#define needsReturn 8 /* Marks first entry as not having been returned yet. */ +#define hasStat 16 /* The st entry is set. */ +#define hasLstat 32 /* The lst entry is set. */ + + +#ifdef HAVE_DIRENT_D_NAMLEN +/* BSD extension; avoids need for a strlen() call. */ +#define D_NAMELEN(dp) (dp)->d_namlen +#else +#define D_NAMELEN(dp) (strlen((dp)->d_name)) +#endif + +#if 0 +#include <stdio.h> +void +tree_dump(struct tree *t, FILE *out) +{ + struct tree_entry *te; + + fprintf(out, "\tdepth: %d\n", t->depth); + fprintf(out, "\tbuff: %s\n", t->buff); + fprintf(out, "\tpwd: "); fflush(stdout); system("pwd"); + fprintf(out, "\taccess: %s\n", t->basename); + fprintf(out, "\tstack:\n"); + for (te = t->stack; te != NULL; te = te->next) { + fprintf(out, "\t\tte->name: %s%s%s\n", te->name, + te->flags & needsPreVisit ? "" : " *", + t->current == te ? " (current)" : ""); + } +} +#endif + +/* + * Add a directory path to the current stack. + */ +static void +tree_push(struct tree *t, const char *path) +{ + struct tree_entry *te; + + te = malloc(sizeof(*te)); + memset(te, 0, sizeof(*te)); + te->next = t->stack; + t->stack = te; + te->fd = -1; + te->name = strdup(path); + te->flags = needsPreVisit | needsPostVisit; + te->dirname_length = t->dirname_length; +} + +/* + * Append a name to the current path. + */ +static void +tree_append(struct tree *t, const char *name, size_t name_length) +{ + char *p; + + if (t->buff != NULL) + t->buff[t->dirname_length] = '\0'; + /* Strip trailing '/' from name, unless entire name is "/". */ + while (name_length > 1 && name[name_length - 1] == '/') + name_length--; + + /* Resize pathname buffer as needed. */ + while (name_length + 1 + t->dirname_length >= t->buff_length) { + t->buff_length *= 2; + if (t->buff_length < 1024) + t->buff_length = 1024; + t->buff = realloc(t->buff, t->buff_length); + } + p = t->buff + t->dirname_length; + t->path_length = t->dirname_length + name_length; + /* Add a separating '/' if it's needed. */ + if (t->dirname_length > 0 && p[-1] != '/') { + *p++ = '/'; + t->path_length ++; + } + strncpy(p, name, name_length); + p[name_length] = '\0'; + t->basename = p; +} + +/* + * Open a directory tree for traversal. + */ +struct tree * +tree_open(const char *path) +{ + struct tree *t; + + t = malloc(sizeof(*t)); + memset(t, 0, sizeof(*t)); + tree_append(t, path, strlen(path)); + t->initialDirFd = open(".", O_RDONLY); + /* + * During most of the traversal, items are set up and then + * returned immediately from tree_next(). That doesn't work + * for the very first entry, so we set a flag for this special + * case. + */ + t->flags = needsReturn; + return (t); +} + +/* + * We've finished a directory; ascend back to the parent. + */ +static void +tree_ascend(struct tree *t) +{ + struct tree_entry *te; + + te = t->stack; + t->depth--; + if (te->flags & isDirLink) { + fchdir(te->fd); + close(te->fd); + t->openCount--; + } else { + chdir(".."); + } +} + +/* + * Pop the working stack. + */ +static void +tree_pop(struct tree *t) +{ + struct tree_entry *te; + + t->buff[t->dirname_length] = '\0'; + if (t->stack == t->current && t->current != NULL) + t->current = t->current->parent; + te = t->stack; + t->stack = te->next; + t->dirname_length = te->dirname_length; + t->basename = t->buff + t->dirname_length; + /* Special case: starting dir doesn't skip leading '/'. */ + if (t->dirname_length > 0) + t->basename++; + free(te->name); + free(te); +} + +/* + * Get the next item in the tree traversal. + */ +int +tree_next(struct tree *t) +{ + struct dirent *de = NULL; + + /* Handle the startup case by returning the initial entry. */ + if (t->flags & needsReturn) { + t->flags &= ~needsReturn; + return (t->visit_type = TREE_REGULAR); + } + + while (t->stack != NULL) { + /* If there's an open dir, get the next entry from there. */ + while (t->d != NULL) { + de = readdir(t->d); + if (de == NULL) { + closedir(t->d); + t->d = NULL; + } else if (de->d_name[0] == '.' + && de->d_name[1] == '\0') { + /* Skip '.' */ + } else if (de->d_name[0] == '.' + && de->d_name[1] == '.' + && de->d_name[2] == '\0') { + /* Skip '..' */ + } else { + /* + * Append the path to the current path + * and return it. + */ + tree_append(t, de->d_name, D_NAMELEN(de)); + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + return (t->visit_type = TREE_REGULAR); + } + } + + /* If the current dir needs to be visited, set it up. */ + if (t->stack->flags & needsPreVisit) { + t->current = t->stack; + tree_append(t, t->stack->name, strlen(t->stack->name)); + t->stack->flags &= ~needsPreVisit; + /* If it is a link, set up fd for the ascent. */ + if (t->stack->flags & isDirLink) { + t->stack->fd = open(".", O_RDONLY); + t->openCount++; + if (t->openCount > t->maxOpenCount) + t->maxOpenCount = t->openCount; + } + t->dirname_length = t->path_length; + if (chdir(t->stack->name) != 0) { + /* chdir() failed; return error */ + tree_pop(t); + t->tree_errno = errno; + return (t->visit_type = TREE_ERROR_DIR); + } + t->depth++; + t->d = opendir("."); + if (t->d == NULL) { + tree_ascend(t); /* Undo "chdir" */ + tree_pop(t); + t->tree_errno = errno; + return (t->visit_type = TREE_ERROR_DIR); + } + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + t->basename = "."; + return (t->visit_type = TREE_POSTDESCENT); + } + + /* We've done everything necessary for the top stack entry. */ + if (t->stack->flags & needsPostVisit) { + tree_ascend(t); + tree_pop(t); + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + return (t->visit_type = TREE_POSTASCENT); + } + } + return (t->visit_type = 0); +} + +/* + * Return error code. + */ +int +tree_errno(struct tree *t) +{ + return (t->tree_errno); +} + +/* + * Called by the client to mark the directory just returned from + * tree_next() as needing to be visited. + */ +void +tree_descend(struct tree *t) +{ + if (t->visit_type != TREE_REGULAR) + return; + + if (tree_current_is_physical_dir(t)) { + tree_push(t, t->basename); + t->stack->flags |= isDir; + } else if (tree_current_is_dir(t)) { + tree_push(t, t->basename); + t->stack->flags |= isDirLink; + } +} + +/* + * Get the stat() data for the entry just returned from tree_next(). + */ +const struct stat * +tree_current_stat(struct tree *t) +{ + if (!(t->flags & hasStat)) { + if (stat(t->basename, &t->st) != 0) + return NULL; + t->flags |= hasStat; + } + return (&t->st); +} + +/* + * Get the lstat() data for the entry just returned from tree_next(). + */ +const struct stat * +tree_current_lstat(struct tree *t) +{ + if (!(t->flags & hasLstat)) { + if (lstat(t->basename, &t->lst) != 0) + return NULL; + t->flags |= hasLstat; + } + return (&t->lst); +} + +/* + * Test whether current entry is a dir or link to a dir. + */ +int +tree_current_is_dir(struct tree *t) +{ + const struct stat *st; + + /* + * If we already have lstat() info, then try some + * cheap tests to determine if this is a dir. + */ + if (t->flags & hasLstat) { + /* If lstat() says it's a dir, it must be a dir. */ + if (S_ISDIR(tree_current_lstat(t)->st_mode)) + return 1; + /* Not a dir; might be a link to a dir. */ + /* If it's not a link, then it's not a link to a dir. */ + if (!S_ISLNK(tree_current_lstat(t)->st_mode)) + return 0; + /* + * It's a link, but we don't know what it's a link to, + * so we'll have to use stat(). + */ + } + + st = tree_current_stat(t); + /* If we can't stat it, it's not a dir. */ + if (st == NULL) + return 0; + /* Use the definitive test. Hopefully this is cached. */ + return (S_ISDIR(st->st_mode)); +} + +/* + * Test whether current entry is a physical directory. Usually, we + * already have at least one of stat() or lstat() in memory, so we + * use tricks to try to avoid an extra trip to the disk. + */ +int +tree_current_is_physical_dir(struct tree *t) +{ + const struct stat *st; + + /* + * If stat() says it isn't a dir, then it's not a dir. + * If stat() data is cached, this check is free, so do it first. + */ + if ((t->flags & hasStat) + && (!S_ISDIR(tree_current_stat(t)->st_mode))) + return 0; + + /* + * Either stat() said it was a dir (in which case, we have + * to determine whether it's really a link to a dir) or + * stat() info wasn't available. So we use lstat(), which + * hopefully is already cached. + */ + + st = tree_current_lstat(t); + /* If we can't stat it, it's not a dir. */ + if (st == NULL) + return 0; + /* Use the definitive test. Hopefully this is cached. */ + return (S_ISDIR(st->st_mode)); +} + +/* + * Test whether current entry is a symbolic link. + */ +int +tree_current_is_physical_link(struct tree *t) +{ + const struct stat *st = tree_current_lstat(t); + if (st == NULL) + return 0; + return (S_ISLNK(st->st_mode)); +} + +/* + * Return the access path for the entry just returned from tree_next(). + */ +const char * +tree_current_access_path(struct tree *t) +{ + return (t->basename); +} + +/* + * Return the full path for the entry just returned from tree_next(). + */ +const char * +tree_current_path(struct tree *t) +{ + return (t->buff); +} + +/* + * Return the length of the path for the entry just returned from tree_next(). + */ +size_t +tree_current_pathlen(struct tree *t) +{ + return (t->path_length); +} + +/* + * Return the nesting depth of the entry just returned from tree_next(). + */ +int +tree_current_depth(struct tree *t) +{ + return (t->depth); +} + +/* + * Terminate the traversal and release any resources. + */ +void +tree_close(struct tree *t) +{ + /* Release anything remaining in the stack. */ + while (t->stack != NULL) + tree_pop(t); + if (t->buff) + free(t->buff); + /* chdir() back to where we started. */ + if (t->initialDirFd >= 0) { + fchdir(t->initialDirFd); + close(t->initialDirFd); + t->initialDirFd = -1; + } + free(t); +} diff --git a/tar/tree.h b/tar/tree.h new file mode 100644 index 00000000..a32a15f8 --- /dev/null +++ b/tar/tree.h @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/tar/tree.h,v 1.3 2007/01/09 08:12:17 kientzle Exp $ + */ + +/*- + * A set of routines for traversing directory trees. + * Similar in concept to the fts library, but with a few + * important differences: + * * Uses less memory. In particular, fts stores an entire directory + * in memory at a time. This package only keeps enough subdirectory + * information in memory to track the traversal. Information + * about non-directories is discarded as soon as possible. + * * Supports very deep logical traversals. The fts package + * uses "non-chdir" approach for logical traversals. This + * package does use a chdir approach for logical traversals + * and can therefore handle pathnames much longer than + * PATH_MAX. + * * Supports deep physical traversals "out of the box." + * Due to the memory optimizations above, there's no need to + * limit dir names to 32k. + */ + +#include <sys/stat.h> +#include <stdio.h> + +struct tree; + +/* Initiate/terminate a tree traversal. */ +struct tree *tree_open(const char * /* pathname */); +void tree_close(struct tree *); + +/* + * tree_next() returns Zero if there is no next entry, non-zero if there is. + * Note that directories are potentially visited three times. The first + * time as "regular" file. If tree_descend() is invoked at that time, + * the directory is added to a work list and will be visited two more + * times: once just after descending into the directory and again + * just after ascending back to the parent. + * + * TREE_ERROR is returned if the descent failed (because the + * directory couldn't be opened, for instance). This is returned + * instead of TREE_PREVISIT/TREE_POSTVISIT. + */ +#define TREE_REGULAR 1 +#define TREE_POSTDESCENT 2 +#define TREE_POSTASCENT 3 +#define TREE_ERROR_DIR -1 +int tree_next(struct tree *); + +int tree_errno(struct tree *); + +/* + * Request that current entry be visited. If you invoke it on every + * directory, you'll get a physical traversal. This is ignored if the + * current entry isn't a directory or a link to a directory. So, if + * you invoke this on every returned path, you'll get a full logical + * traversal. + */ +void tree_descend(struct tree *); + +/* + * Return information about the current entry. + */ + +int tree_current_depth(struct tree *); +/* + * The current full pathname, length of the full pathname, + * and a name that can be used to access the file. + * Because tree does use chdir extensively, the access path is + * almost never the same as the full current path. + */ +const char *tree_current_path(struct tree *); +size_t tree_current_pathlen(struct tree *); +const char *tree_current_access_path(struct tree *); +/* + * Request the lstat() or stat() data for the current path. Since the + * tree package needs to do some of this anyway, and caches the + * results, you should take advantage of it here if you need it rather + * than make a redundant stat() or lstat() call of your own. + */ +const struct stat *tree_current_stat(struct tree *); +const struct stat *tree_current_lstat(struct tree *); +/* The following tests may use mechanisms much faster than stat()/lstat(). */ +/* "is_physical_dir" is equivalent to S_ISDIR(tree_current_lstat()->st_mode) */ +int tree_current_is_physical_dir(struct tree *); +/* "is_physical_link" is equivalent to S_ISLNK(tree_current_lstat()->st_mode) */ +int tree_current_is_physical_link(struct tree *); +/* "is_dir" is equivalent to S_ISDIR(tree_current_stat()->st_mode) */ +int tree_current_is_dir(struct tree *); + +/* For testing/debugging: Dump the internal status to the given filehandle. */ +void tree_dump(struct tree *, FILE *); diff --git a/tar/util.c b/tar/util.c new file mode 100644 index 00000000..68ff8d8f --- /dev/null +++ b/tar/util.c @@ -0,0 +1,437 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.18 2008/01/02 00:21:27 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> /* Linux doesn't define mode_t, etc. in sys/stat.h. */ +#endif +#include <ctype.h> +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDARG_H +#include <stdarg.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#include "bsdtar.h" + +static void bsdtar_vwarnc(struct bsdtar *, int code, + const char *fmt, va_list ap); + +/* + * Print a string, taking care with any non-printable characters. + */ + +void +safe_fprintf(FILE *f, const char *fmt, ...) +{ + char *buff; + char *buff_heap; + int buff_length; + int length; + va_list ap; + char *p; + unsigned i; + char buff_stack[256]; + char copy_buff[256]; + + /* Use a stack-allocated buffer if we can, for speed and safety. */ + buff_heap = NULL; + buff_length = sizeof(buff_stack); + buff = buff_stack; + + va_start(ap, fmt); + length = vsnprintf(buff, buff_length, fmt, ap); + va_end(ap); + /* If the result is too large, allocate a buffer on the heap. */ + if (length >= buff_length) { + buff_length = length+1; + buff_heap = malloc(buff_length); + /* Failsafe: use the truncated string if malloc fails. */ + if (buff_heap != NULL) { + buff = buff_heap; + va_start(ap, fmt); + length = vsnprintf(buff, buff_length, fmt, ap); + va_end(ap); + } + } + + /* Write data, expanding unprintable characters. */ + p = buff; + i = 0; + while (*p != '\0') { + unsigned char c = *p++; + + if (isprint(c) && c != '\\') + copy_buff[i++] = c; + else { + copy_buff[i++] = '\\'; + switch (c) { + case '\a': copy_buff[i++] = 'a'; break; + case '\b': copy_buff[i++] = 'b'; break; + case '\f': copy_buff[i++] = 'f'; break; + case '\n': copy_buff[i++] = 'n'; break; +#if '\r' != '\n' + /* On some platforms, \n and \r are the same. */ + case '\r': copy_buff[i++] = 'r'; break; +#endif + case '\t': copy_buff[i++] = 't'; break; + case '\v': copy_buff[i++] = 'v'; break; + case '\\': copy_buff[i++] = '\\'; break; + default: + sprintf(copy_buff + i, "%03o", c); + i += 3; + } + } + + /* If our temp buffer is full, dump it and keep going. */ + if (i > (sizeof(copy_buff) - 8)) { + copy_buff[i++] = '\0'; + fprintf(f, "%s", copy_buff); + i = 0; + } + } + copy_buff[i++] = '\0'; + fprintf(f, "%s", copy_buff); + + /* If we allocated a heap-based buffer, free it now. */ + if (buff_heap != NULL) + free(buff_heap); +} + +static void +bsdtar_vwarnc(struct bsdtar *bsdtar, int code, const char *fmt, va_list ap) +{ + fprintf(stderr, "%s: ", bsdtar->progname); + vfprintf(stderr, fmt, ap); + if (code != 0) + fprintf(stderr, ": %s", strerror(code)); + fprintf(stderr, "\n"); +} + +void +bsdtar_warnc(struct bsdtar *bsdtar, int code, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + bsdtar_vwarnc(bsdtar, code, fmt, ap); + va_end(ap); +} + +void +bsdtar_errc(struct bsdtar *bsdtar, int eval, int code, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + bsdtar_vwarnc(bsdtar, code, fmt, ap); + va_end(ap); + exit(eval); +} + +int +yes(const char *fmt, ...) +{ + char buff[32]; + char *p; + ssize_t l; + + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, " (y/N)? "); + fflush(stderr); + + l = read(2, buff, sizeof(buff)); + if (l <= 0) + return (0); + buff[l] = 0; + + for (p = buff; *p != '\0'; p++) { + if (isspace(0xff & (int)*p)) + continue; + switch(*p) { + case 'y': case 'Y': + return (1); + case 'n': case 'N': + return (0); + default: + return (0); + } + } + + return (0); +} + +/* + * Read lines from file and do something with each one. If option_null + * is set, lines are terminated with zero bytes; otherwise, they're + * terminated with newlines. + * + * This uses a self-sizing buffer to handle arbitrarily-long lines. + * If the "process" function returns non-zero for any line, this + * function will return non-zero after attempting to process all + * remaining lines. + */ +int +process_lines(struct bsdtar *bsdtar, const char *pathname, + int (*process)(struct bsdtar *, const char *)) +{ + FILE *f; + char *buff, *buff_end, *line_start, *line_end, *p; + size_t buff_length, bytes_read, bytes_wanted; + int separator; + int ret; + + separator = bsdtar->option_null ? '\0' : '\n'; + ret = 0; + + if (strcmp(pathname, "-") == 0) + f = stdin; + else + f = fopen(pathname, "r"); + if (f == NULL) + bsdtar_errc(bsdtar, 1, errno, "Couldn't open %s", pathname); + buff_length = 8192; + buff = malloc(buff_length); + if (buff == NULL) + bsdtar_errc(bsdtar, 1, ENOMEM, "Can't read %s", pathname); + line_start = line_end = buff_end = buff; + for (;;) { + /* Get some more data into the buffer. */ + bytes_wanted = buff + buff_length - buff_end; + bytes_read = fread(buff_end, 1, bytes_wanted, f); + buff_end += bytes_read; + /* Process all complete lines in the buffer. */ + while (line_end < buff_end) { + if (*line_end == separator) { + *line_end = '\0'; + if ((*process)(bsdtar, line_start) != 0) + ret = -1; + line_start = line_end + 1; + line_end = line_start; + } else + line_end++; + } + if (feof(f)) + break; + if (ferror(f)) + bsdtar_errc(bsdtar, 1, errno, + "Can't read %s", pathname); + if (line_start > buff) { + /* Move a leftover fractional line to the beginning. */ + memmove(buff, line_start, buff_end - line_start); + buff_end -= line_start - buff; + line_end -= line_start - buff; + line_start = buff; + } else { + /* Line is too big; enlarge the buffer. */ + p = realloc(buff, buff_length *= 2); + if (p == NULL) + bsdtar_errc(bsdtar, 1, ENOMEM, + "Line too long in %s", pathname); + buff_end = p + (buff_end - buff); + line_end = p + (line_end - buff); + line_start = buff = p; + } + } + /* At end-of-file, handle the final line. */ + if (line_end > line_start) { + *line_end = '\0'; + if ((*process)(bsdtar, line_start) != 0) + ret = -1; + } + free(buff); + if (f != stdin) + fclose(f); + return (ret); +} + +/*- + * The logic here for -C <dir> attempts to avoid + * chdir() as long as possible. For example: + * "-C /foo -C /bar file" needs chdir("/bar") but not chdir("/foo") + * "-C /foo -C bar file" needs chdir("/foo/bar") + * "-C /foo -C bar /file1" does not need chdir() + * "-C /foo -C bar /file1 file2" needs chdir("/foo/bar") before file2 + * + * The only correct way to handle this is to record a "pending" chdir + * request and combine multiple requests intelligently until we + * need to process a non-absolute file. set_chdir() adds the new dir + * to the pending list; do_chdir() actually executes any pending chdir. + * + * This way, programs that build tar command lines don't have to worry + * about -C with non-existent directories; such requests will only + * fail if the directory must be accessed. + */ +void +set_chdir(struct bsdtar *bsdtar, const char *newdir) +{ + if (newdir[0] == '/') { + /* The -C /foo -C /bar case; dump first one. */ + free(bsdtar->pending_chdir); + bsdtar->pending_chdir = NULL; + } + if (bsdtar->pending_chdir == NULL) + /* Easy case: no previously-saved dir. */ + bsdtar->pending_chdir = strdup(newdir); + else { + /* The -C /foo -C bar case; concatenate */ + char *old_pending = bsdtar->pending_chdir; + size_t old_len = strlen(old_pending); + bsdtar->pending_chdir = malloc(old_len + strlen(newdir) + 2); + if (old_pending[old_len - 1] == '/') + old_pending[old_len - 1] = '\0'; + if (bsdtar->pending_chdir != NULL) + sprintf(bsdtar->pending_chdir, "%s/%s", + old_pending, newdir); + free(old_pending); + } + if (bsdtar->pending_chdir == NULL) + bsdtar_errc(bsdtar, 1, errno, "No memory"); +} + +void +do_chdir(struct bsdtar *bsdtar) +{ + if (bsdtar->pending_chdir == NULL) + return; + + if (chdir(bsdtar->pending_chdir) != 0) { + bsdtar_errc(bsdtar, 1, 0, "could not chdir to '%s'\n", + bsdtar->pending_chdir); + } + free(bsdtar->pending_chdir); + bsdtar->pending_chdir = NULL; +} + +/* + * Handle --strip-components and any future path-rewriting options. + * Returns non-zero if the pathname should not be extracted. + * + * TODO: Support pax-style regex path rewrites. + */ +int +edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) +{ + const char *name = archive_entry_pathname(entry); + + /* Strip leading dir names as per --strip-components option. */ + if (bsdtar->strip_components > 0) { + int r = bsdtar->strip_components; + const char *p = name; + + while (r > 0) { + switch (*p++) { + case '/': + r--; + name = p; + break; + case '\0': + /* Path is too short, skip it. */ + return (1); + } + } + } + + /* Strip redundant leading '/' characters. */ + while (name[0] == '/' && name[1] == '/') + name++; + + /* Strip leading '/' unless user has asked us not to. */ + if (name[0] == '/' && !bsdtar->option_absolute_paths) { + /* Generate a warning the first time this happens. */ + if (!bsdtar->warned_lead_slash) { + bsdtar_warnc(bsdtar, 0, + "Removing leading '/' from member names"); + bsdtar->warned_lead_slash = 1; + } + name++; + /* Special case: Stripping leading '/' from "/" yields ".". */ + if (*name == '\0') + name = "."; + } + + /* Safely replace name in archive_entry. */ + if (name != archive_entry_pathname(entry)) { + char *q = strdup(name); + archive_entry_copy_pathname(entry, q); + free(q); + } + return (0); +} + +/* + * Like strcmp(), but try to be a little more aware of the fact that + * we're comparing two paths. Right now, it just handles leading + * "./" and trailing '/' specially, so that "a/b/" == "./a/b" + * + * TODO: Make this better, so that "./a//b/./c/" == "a/b/c" + * TODO: After this works, push it down into libarchive. + * TODO: Publish the path normalization routines in libarchive so + * that bsdtar can normalize paths and use fast strcmp() instead + * of this. + */ + +int +pathcmp(const char *a, const char *b) +{ + /* Skip leading './' */ + if (a[0] == '.' && a[1] == '/' && a[2] != '\0') + a += 2; + if (b[0] == '.' && b[1] == '/' && b[2] != '\0') + b += 2; + /* Find the first difference, or return (0) if none. */ + while (*a == *b) { + if (*a == '\0') + return (0); + a++; + b++; + } + /* + * If one ends in '/' and the other one doesn't, + * they're the same. + */ + if (a[0] == '/' && a[1] == '\0' && b[0] == '\0') + return (0); + if (a[0] == '\0' && b[0] == '/' && b[1] == '\0') + return (0); + /* They're really different, return the correct sign. */ + return (*(const unsigned char *)a - *(const unsigned char *)b); +} diff --git a/tar/write.c b/tar/write.c new file mode 100644 index 00000000..82736afc --- /dev/null +++ b/tar/write.c @@ -0,0 +1,1553 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "bsdtar_platform.h" +__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.65 2008/03/15 02:41:44 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_ACL_H +#include <sys/acl.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#ifdef HAVE_ATTR_XATTR_H +#include <attr/xattr.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_EXT2FS_EXT2_FS_H +#include <ext2fs/ext2_fs.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_FNMATCH_H +#include <fnmatch.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_LINUX_FS_H +#include <linux/fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_LINUX_EXT2_FS_H +#include <linux/ext2_fs.h> /* for Linux file flags */ +#endif +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "bsdtar.h" +#include "tree.h" + +/* Fixed size of uname/gname caches. */ +#define name_cache_size 101 + +static const char * const NO_NAME = "(noname)"; + +/* Initial size of link cache. */ +#define links_cache_initial_size 1024 + +struct archive_dir_entry { + struct archive_dir_entry *next; + time_t mtime_sec; + int mtime_nsec; + char *name; +}; + +struct archive_dir { + struct archive_dir_entry *head, *tail; +}; + +struct links_cache { + unsigned long number_entries; + size_t number_buckets; + struct links_entry **buckets; +}; + +struct links_entry { + struct links_entry *next; + struct links_entry *previous; + int links; + dev_t dev; + ino_t ino; + char *name; +}; + +struct name_cache { + int probes; + int hits; + size_t size; + struct { + id_t id; + const char *name; + } cache[name_cache_size]; +}; + +static void add_dir_list(struct bsdtar *bsdtar, const char *path, + time_t mtime_sec, int mtime_nsec); +static int append_archive(struct bsdtar *, struct archive *, + struct archive *ina); +static int append_archive_filename(struct bsdtar *, + struct archive *, const char *fname); +static void archive_names_from_file(struct bsdtar *bsdtar, + struct archive *a); +static int archive_names_from_file_helper(struct bsdtar *bsdtar, + const char *line); +static int copy_file_data(struct bsdtar *bsdtar, + struct archive *a, struct archive *ina); +static void create_cleanup(struct bsdtar *); +static void free_buckets(struct bsdtar *, struct links_cache *); +static void free_cache(struct name_cache *cache); +static const char * lookup_gname(struct bsdtar *bsdtar, gid_t gid); +static int lookup_gname_helper(struct bsdtar *bsdtar, + const char **name, id_t gid); +static void lookup_hardlink(struct bsdtar *, + struct archive_entry *entry, const struct stat *); +static const char * lookup_uname(struct bsdtar *bsdtar, uid_t uid); +static int lookup_uname_helper(struct bsdtar *bsdtar, + const char **name, id_t uid); +static int new_enough(struct bsdtar *, const char *path, + const struct stat *); +static void setup_acls(struct bsdtar *, struct archive_entry *, + const char *path); +static void setup_xattrs(struct bsdtar *, struct archive_entry *, + const char *path); +static void test_for_append(struct bsdtar *); +static void write_archive(struct archive *, struct bsdtar *); +static void write_entry(struct bsdtar *, struct archive *, + const struct stat *, const char *pathname, + const char *accpath); +static int write_file_data(struct bsdtar *, struct archive *, + int fd); +static void write_hierarchy(struct bsdtar *, struct archive *, + const char *); + +void +tar_mode_c(struct bsdtar *bsdtar) +{ + struct archive *a; + int r; + + if (*bsdtar->argv == NULL && bsdtar->names_from_file == NULL) + bsdtar_errc(bsdtar, 1, 0, "no files or directories specified"); + + a = archive_write_new(); + + /* Support any format that the library supports. */ + if (bsdtar->create_format == NULL) { + r = archive_write_set_format_pax_restricted(a); + bsdtar->create_format = "pax restricted"; + } else { + r = archive_write_set_format_by_name(a, bsdtar->create_format); + } + if (r != ARCHIVE_OK) { + fprintf(stderr, "Can't use format %s: %s\n", + bsdtar->create_format, + archive_error_string(a)); + usage(bsdtar); + } + + /* + * If user explicitly set the block size, then assume they + * want the last block padded as well. Otherwise, use the + * default block size and accept archive_write_open_file()'s + * default padding decisions. + */ + if (bsdtar->bytes_per_block != 0) { + archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block); + archive_write_set_bytes_in_last_block(a, + bsdtar->bytes_per_block); + } else + archive_write_set_bytes_per_block(a, DEFAULT_BYTES_PER_BLOCK); + + if (bsdtar->compress_program) { + archive_write_set_compression_program(a, bsdtar->compress_program); + } else { + switch (bsdtar->create_compression) { + case 0: + archive_write_set_compression_none(a); + break; +#ifdef HAVE_LIBBZ2 + case 'j': case 'y': + archive_write_set_compression_bzip2(a); + break; +#endif +#ifdef HAVE_LIBZ + case 'z': + archive_write_set_compression_gzip(a); + break; +#endif + case 'Z': + archive_write_set_compression_compress(a); + break; + default: + bsdtar_errc(bsdtar, 1, 0, + "Unrecognized compression option -%c", + bsdtar->create_compression); + } + } + + r = archive_write_open_file(a, bsdtar->filename); + if (r != ARCHIVE_OK) + bsdtar_errc(bsdtar, 1, 0, archive_error_string(a)); + + write_archive(a, bsdtar); + + if (bsdtar->option_totals) { + fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n", + (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a)); + } + + archive_write_finish(a); +} + +/* + * Same as 'c', except we only support tar or empty formats in + * uncompressed files on disk. + */ +void +tar_mode_r(struct bsdtar *bsdtar) +{ + off_t end_offset; + int format; + struct archive *a; + struct archive_entry *entry; + int r; + + /* Sanity-test some arguments and the file. */ + test_for_append(bsdtar); + + format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; + + bsdtar->fd = open(bsdtar->filename, O_RDWR | O_CREAT, 0666); + if (bsdtar->fd < 0) + bsdtar_errc(bsdtar, 1, errno, + "Cannot open %s", bsdtar->filename); + + a = archive_read_new(); + archive_read_support_compression_all(a); + archive_read_support_format_tar(a); + archive_read_support_format_gnutar(a); + r = archive_read_open_fd(a, bsdtar->fd, 10240); + if (r != ARCHIVE_OK) + bsdtar_errc(bsdtar, 1, archive_errno(a), + "Can't read archive %s: %s", bsdtar->filename, + archive_error_string(a)); + while (0 == archive_read_next_header(a, &entry)) { + if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) { + archive_read_finish(a); + close(bsdtar->fd); + bsdtar_errc(bsdtar, 1, 0, + "Cannot append to compressed archive."); + } + /* Keep going until we hit end-of-archive */ + format = archive_format(a); + } + + end_offset = archive_read_header_position(a); + archive_read_finish(a); + + /* Re-open archive for writing */ + a = archive_write_new(); + archive_write_set_compression_none(a); + /* + * Set the format to be used for writing. To allow people to + * extend empty files, we need to allow them to specify the format, + * which opens the possibility that they will specify a format that + * doesn't match the existing format. Hence, the following bit + * of arcane ugliness. + */ + + if (bsdtar->create_format != NULL) { + /* If the user requested a format, use that, but ... */ + archive_write_set_format_by_name(a, + bsdtar->create_format); + /* ... complain if it's not compatible. */ + format &= ARCHIVE_FORMAT_BASE_MASK; + if (format != (int)(archive_format(a) & ARCHIVE_FORMAT_BASE_MASK) + && format != ARCHIVE_FORMAT_EMPTY) { + bsdtar_errc(bsdtar, 1, 0, + "Format %s is incompatible with the archive %s.", + bsdtar->create_format, bsdtar->filename); + } + } else { + /* + * Just preserve the current format, with a little care + * for formats that libarchive can't write. + */ + if (format == ARCHIVE_FORMAT_TAR_GNUTAR) + /* TODO: When gtar supports pax, use pax restricted. */ + format = ARCHIVE_FORMAT_TAR_USTAR; + if (format == ARCHIVE_FORMAT_EMPTY) + format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; + archive_write_set_format(a, format); + } + lseek(bsdtar->fd, end_offset, SEEK_SET); /* XXX check return val XXX */ + archive_write_open_fd(a, bsdtar->fd); /* XXX check return val XXX */ + + write_archive(a, bsdtar); /* XXX check return val XXX */ + + if (bsdtar->option_totals) { + fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n", + (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a)); + } + + archive_write_finish(a); + close(bsdtar->fd); + bsdtar->fd = -1; +} + +void +tar_mode_u(struct bsdtar *bsdtar) +{ + off_t end_offset; + struct archive *a; + struct archive_entry *entry; + int format; + struct archive_dir_entry *p; + struct archive_dir archive_dir; + + bsdtar->archive_dir = &archive_dir; + memset(&archive_dir, 0, sizeof(archive_dir)); + + format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; + + /* Sanity-test some arguments and the file. */ + test_for_append(bsdtar); + + bsdtar->fd = open(bsdtar->filename, O_RDWR); + if (bsdtar->fd < 0) + bsdtar_errc(bsdtar, 1, errno, + "Cannot open %s", bsdtar->filename); + + a = archive_read_new(); + archive_read_support_compression_all(a); + archive_read_support_format_tar(a); + archive_read_support_format_gnutar(a); + if (archive_read_open_fd(a, bsdtar->fd, + bsdtar->bytes_per_block != 0 ? bsdtar->bytes_per_block : + DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) { + bsdtar_errc(bsdtar, 1, 0, + "Can't open %s: %s", bsdtar->filename, + archive_error_string(a)); + } + + /* Build a list of all entries and their recorded mod times. */ + while (0 == archive_read_next_header(a, &entry)) { + if (archive_compression(a) != ARCHIVE_COMPRESSION_NONE) { + archive_read_finish(a); + close(bsdtar->fd); + bsdtar_errc(bsdtar, 1, 0, + "Cannot append to compressed archive."); + } + add_dir_list(bsdtar, archive_entry_pathname(entry), + archive_entry_mtime(entry), + archive_entry_mtime_nsec(entry)); + /* Record the last format determination we see */ + format = archive_format(a); + /* Keep going until we hit end-of-archive */ + } + + end_offset = archive_read_header_position(a); + archive_read_finish(a); + + /* Re-open archive for writing. */ + a = archive_write_new(); + archive_write_set_compression_none(a); + /* + * Set format to same one auto-detected above, except that + * we don't write GNU tar format, so use ustar instead. + */ + if (format == ARCHIVE_FORMAT_TAR_GNUTAR) + format = ARCHIVE_FORMAT_TAR_USTAR; + archive_write_set_format(a, format); + if (bsdtar->bytes_per_block != 0) { + archive_write_set_bytes_per_block(a, bsdtar->bytes_per_block); + archive_write_set_bytes_in_last_block(a, + bsdtar->bytes_per_block); + } else + archive_write_set_bytes_per_block(a, DEFAULT_BYTES_PER_BLOCK); + lseek(bsdtar->fd, end_offset, SEEK_SET); + ftruncate(bsdtar->fd, end_offset); + archive_write_open_fd(a, bsdtar->fd); + + write_archive(a, bsdtar); + + if (bsdtar->option_totals) { + fprintf(stderr, "Total bytes written: " BSDTAR_FILESIZE_PRINTF "\n", + (BSDTAR_FILESIZE_TYPE)archive_position_compressed(a)); + } + + archive_write_finish(a); + close(bsdtar->fd); + bsdtar->fd = -1; + + while (bsdtar->archive_dir->head != NULL) { + p = bsdtar->archive_dir->head->next; + free(bsdtar->archive_dir->head->name); + free(bsdtar->archive_dir->head); + bsdtar->archive_dir->head = p; + } + bsdtar->archive_dir->tail = NULL; +} + + +/* + * Write user-specified files/dirs to opened archive. + */ +static void +write_archive(struct archive *a, struct bsdtar *bsdtar) +{ + const char *arg; + + if (bsdtar->names_from_file != NULL) + archive_names_from_file(bsdtar, a); + + while (*bsdtar->argv) { + arg = *bsdtar->argv; + if (arg[0] == '-' && arg[1] == 'C') { + arg += 2; + if (*arg == '\0') { + bsdtar->argv++; + arg = *bsdtar->argv; + if (arg == NULL) { + bsdtar_warnc(bsdtar, 1, 0, + "Missing argument for -C"); + bsdtar->return_value = 1; + return; + } + } + set_chdir(bsdtar, arg); + } else { + if (*arg != '/' && (arg[0] != '@' || arg[1] != '/')) + do_chdir(bsdtar); /* Handle a deferred -C */ + if (*arg == '@') { + if (append_archive_filename(bsdtar, a, + arg + 1) != 0) + break; + } else + write_hierarchy(bsdtar, a, arg); + } + bsdtar->argv++; + } + + create_cleanup(bsdtar); + if (archive_write_close(a)) { + bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); + bsdtar->return_value = 1; + } +} + +/* + * Archive names specified in file. + * + * Unless --null was specified, a line containing exactly "-C" will + * cause the next line to be a directory to pass to chdir(). If + * --null is specified, then a line "-C" is just another filename. + */ +void +archive_names_from_file(struct bsdtar *bsdtar, struct archive *a) +{ + bsdtar->archive = a; + + bsdtar->next_line_is_dir = 0; + process_lines(bsdtar, bsdtar->names_from_file, + archive_names_from_file_helper); + if (bsdtar->next_line_is_dir) + bsdtar_errc(bsdtar, 1, errno, + "Unexpected end of filename list; " + "directory expected after -C"); +} + +static int +archive_names_from_file_helper(struct bsdtar *bsdtar, const char *line) +{ + if (bsdtar->next_line_is_dir) { + set_chdir(bsdtar, line); + bsdtar->next_line_is_dir = 0; + } else if (!bsdtar->option_null && strcmp(line, "-C") == 0) + bsdtar->next_line_is_dir = 1; + else { + if (*line != '/') + do_chdir(bsdtar); /* Handle a deferred -C */ + write_hierarchy(bsdtar, bsdtar->archive, line); + } + return (0); +} + +/* + * Copy from specified archive to current archive. Returns non-zero + * for write errors (which force us to terminate the entire archiving + * operation). If there are errors reading the input archive, we set + * bsdtar->return_value but return zero, so the overall archiving + * operation will complete and return non-zero. + */ +static int +append_archive_filename(struct bsdtar *bsdtar, struct archive *a, + const char *filename) +{ + struct archive *ina; + int rc; + + if (strcmp(filename, "-") == 0) + filename = NULL; /* Library uses NULL for stdio. */ + + ina = archive_read_new(); + archive_read_support_format_all(ina); + archive_read_support_compression_all(ina); + if (archive_read_open_file(ina, filename, 10240)) { + bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(ina)); + bsdtar->return_value = 1; + return (0); + } + + rc = append_archive(bsdtar, a, ina); + + if (archive_errno(ina)) { + bsdtar_warnc(bsdtar, 0, "Error reading archive %s: %s", + filename, archive_error_string(ina)); + bsdtar->return_value = 1; + } + archive_read_finish(ina); + + return (rc); +} + +static int +append_archive(struct bsdtar *bsdtar, struct archive *a, struct archive *ina) +{ + struct archive_entry *in_entry; + int e; + + while (0 == archive_read_next_header(ina, &in_entry)) { + if (!new_enough(bsdtar, archive_entry_pathname(in_entry), + archive_entry_stat(in_entry))) + continue; + if (excluded(bsdtar, archive_entry_pathname(in_entry))) + continue; + if (bsdtar->option_interactive && + !yes("copy '%s'", archive_entry_pathname(in_entry))) + continue; + if (bsdtar->verbose) + safe_fprintf(stderr, "a %s", + archive_entry_pathname(in_entry)); + + e = archive_write_header(a, in_entry); + if (e != ARCHIVE_OK) { + if (!bsdtar->verbose) + bsdtar_warnc(bsdtar, 0, "%s: %s", + archive_entry_pathname(in_entry), + archive_error_string(a)); + else + fprintf(stderr, ": %s", archive_error_string(a)); + } + if (e == ARCHIVE_FATAL) + exit(1); + + if (e >= ARCHIVE_WARN) + if (copy_file_data(bsdtar, a, ina)) + exit(1); + + if (bsdtar->verbose) + fprintf(stderr, "\n"); + } + + /* Note: If we got here, we saw no write errors, so return success. */ + return (0); +} + +/* Helper function to copy data between archives. */ +static int +copy_file_data(struct bsdtar *bsdtar, struct archive *a, struct archive *ina) +{ + char buff[64*1024]; + ssize_t bytes_read; + ssize_t bytes_written; + + bytes_read = archive_read_data(ina, buff, sizeof(buff)); + while (bytes_read > 0) { + bytes_written = archive_write_data(a, buff, bytes_read); + if (bytes_written < bytes_read) { + bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); + return (-1); + } + bytes_read = archive_read_data(ina, buff, sizeof(buff)); + } + + return (0); +} + +/* + * Add the file or dir hierarchy named by 'path' to the archive + */ +static void +write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path) +{ + struct tree *tree; + char symlink_mode = bsdtar->symlink_mode; + dev_t first_dev = 0; + int dev_recorded = 0; + int tree_ret; +#ifdef __linux + int fd, r; + unsigned long fflags; +#endif + + tree = tree_open(path); + + if (!tree) { + bsdtar_warnc(bsdtar, errno, "%s: Cannot open", path); + bsdtar->return_value = 1; + return; + } + + while ((tree_ret = tree_next(tree))) { + const char *name = tree_current_path(tree); + const struct stat *st = NULL, *lst = NULL; + int descend; + + if (tree_ret == TREE_ERROR_DIR) + bsdtar_warnc(bsdtar, errno, "%s: Couldn't visit directory", name); + if (tree_ret != TREE_REGULAR) + continue; + lst = tree_current_lstat(tree); + if (lst == NULL) { + /* Couldn't lstat(); must not exist. */ + bsdtar_warnc(bsdtar, errno, "%s: Cannot stat", name); + + /* + * Report an error via the exit code if the failed + * path is a prefix of what the user provided via + * the command line. (Testing for string equality + * here won't work due to trailing '/' characters.) + */ + if (memcmp(name, path, strlen(name)) == 0) + bsdtar->return_value = 1; + + continue; + } + if (S_ISLNK(lst->st_mode)) + st = tree_current_stat(tree); + /* Default: descend into any dir or symlink to dir. */ + /* We'll adjust this later on. */ + descend = 0; + if ((st != NULL) && S_ISDIR(st->st_mode)) + descend = 1; + if ((lst != NULL) && S_ISDIR(lst->st_mode)) + descend = 1; + + /* + * If user has asked us not to cross mount points, + * then don't descend into into a dir on a different + * device. + */ + if (!dev_recorded) { + first_dev = lst->st_dev; + dev_recorded = 1; + } + if (bsdtar->option_dont_traverse_mounts) { + if (lst != NULL && lst->st_dev != first_dev) + descend = 0; + } + + /* + * If this file/dir is flagged "nodump" and we're + * honoring such flags, skip this file/dir. + */ +#ifdef HAVE_CHFLAGS + if (bsdtar->option_honor_nodump && + (lst->st_flags & UF_NODUMP)) + continue; +#endif + +#ifdef __linux + /* + * Linux has a nodump flag too but to read it + * we have to open() the file/dir and do an ioctl on it... + */ + if (bsdtar->option_honor_nodump && + ((fd = open(name, O_RDONLY|O_NONBLOCK)) >= 0) && + ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &fflags)), + close(fd), r) >= 0 && + (fflags & EXT2_NODUMP_FL)) + continue; +#endif + + /* + * If this file/dir is excluded by a filename + * pattern, skip it. + */ + if (excluded(bsdtar, name)) + continue; + + /* + * If the user vetoes this file/directory, skip it. + */ + if (bsdtar->option_interactive && + !yes("add '%s'", name)) + continue; + + /* + * If this is a dir, decide whether or not to recurse. + */ + if (bsdtar->option_no_subdirs) + descend = 0; + + /* + * Distinguish 'L'/'P'/'H' symlink following. + */ + switch(symlink_mode) { + case 'H': + /* 'H': After the first item, rest like 'P'. */ + symlink_mode = 'P'; + /* 'H': First item (from command line) like 'L'. */ + /* FALLTHROUGH */ + case 'L': + /* 'L': Do descend through a symlink to dir. */ + /* 'L': Archive symlink to file as file. */ + lst = tree_current_stat(tree); + /* If stat fails, we have a broken symlink; + * in that case, archive the link as such. */ + if (lst == NULL) + lst = tree_current_lstat(tree); + break; + default: + /* 'P': Don't descend through a symlink to dir. */ + if (!S_ISDIR(lst->st_mode)) + descend = 0; + /* 'P': Archive symlink to file as symlink. */ + /* lst = tree_current_lstat(tree); */ + break; + } + + if (descend) + tree_descend(tree); + + /* + * Write the entry. Note that write_entry() handles + * pathname editing and newness testing. + */ + write_entry(bsdtar, a, lst, name, + tree_current_access_path(tree)); + } + tree_close(tree); +} + +/* + * Add a single filesystem object to the archive. + */ +static void +write_entry(struct bsdtar *bsdtar, struct archive *a, const struct stat *st, + const char *pathname, const char *accpath) +{ + struct archive_entry *entry; + int e; + int fd; +#ifdef __linux + int r; + unsigned long stflags; +#endif + static char linkbuffer[PATH_MAX+1]; + + fd = -1; + entry = archive_entry_new(); + + archive_entry_set_pathname(entry, pathname); + + /* + * Rewrite the pathname to be archived. If rewrite + * fails, skip the entry. + */ + if (edit_pathname(bsdtar, entry)) + goto abort; + + /* + * In -u mode, check that the file is newer than what's + * already in the archive; in all modes, obey --newerXXX flags. + */ + if (!new_enough(bsdtar, archive_entry_pathname(entry), st)) + goto abort; + + if (!S_ISDIR(st->st_mode) && (st->st_nlink > 1)) + lookup_hardlink(bsdtar, entry, st); + + /* Display entry as we process it. This format is required by SUSv2. */ + if (bsdtar->verbose) + safe_fprintf(stderr, "a %s", archive_entry_pathname(entry)); + + /* Read symbolic link information. */ + if ((st->st_mode & S_IFMT) == S_IFLNK) { + int lnklen; + + lnklen = readlink(accpath, linkbuffer, PATH_MAX); + if (lnklen < 0) { + if (!bsdtar->verbose) + bsdtar_warnc(bsdtar, errno, + "%s: Couldn't read symbolic link", + pathname); + else + safe_fprintf(stderr, + ": Couldn't read symbolic link: %s", + strerror(errno)); + goto cleanup; + } + linkbuffer[lnklen] = 0; + archive_entry_set_symlink(entry, linkbuffer); + } + + /* Look up username and group name. */ + archive_entry_set_uname(entry, lookup_uname(bsdtar, st->st_uid)); + archive_entry_set_gname(entry, lookup_gname(bsdtar, st->st_gid)); + +#ifdef HAVE_CHFLAGS + if (st->st_flags != 0) + archive_entry_set_fflags(entry, st->st_flags, 0); +#endif + +#ifdef __linux + if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode)) && + ((fd = open(accpath, O_RDONLY|O_NONBLOCK)) >= 0) && + ((r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags)), close(fd), (fd = -1), r) >= 0 && + stflags) { + archive_entry_set_fflags(entry, stflags, 0); + } +#endif + + archive_entry_copy_stat(entry, st); + setup_acls(bsdtar, entry, accpath); + setup_xattrs(bsdtar, entry, accpath); + + /* + * If it's a regular file (and non-zero in size) make sure we + * can open it before we start to write. In particular, note + * that we can always archive a zero-length file, even if we + * can't read it. + */ + if (S_ISREG(st->st_mode) && st->st_size > 0) { + fd = open(accpath, O_RDONLY); + if (fd < 0) { + if (!bsdtar->verbose) + bsdtar_warnc(bsdtar, errno, "%s: could not open file", pathname); + else + fprintf(stderr, ": %s", strerror(errno)); + goto cleanup; + } + } + + /* Non-regular files get archived with zero size. */ + if (!S_ISREG(st->st_mode)) + archive_entry_set_size(entry, 0); + + e = archive_write_header(a, entry); + if (e != ARCHIVE_OK) { + if (!bsdtar->verbose) + bsdtar_warnc(bsdtar, 0, "%s: %s", pathname, + archive_error_string(a)); + else + fprintf(stderr, ": %s", archive_error_string(a)); + } + + if (e == ARCHIVE_FATAL) + exit(1); + + /* + * If we opened a file earlier, write it out now. Note that + * the format handler might have reset the size field to zero + * to inform us that the archive body won't get stored. In + * that case, just skip the write. + */ + if (e >= ARCHIVE_WARN && fd >= 0 && archive_entry_size(entry) > 0) + if (write_file_data(bsdtar, a, fd)) + exit(1); + +cleanup: + if (bsdtar->verbose) + fprintf(stderr, "\n"); + +abort: + if (fd >= 0) + close(fd); + + if (entry != NULL) + archive_entry_free(entry); +} + + +/* Helper function to copy file to archive, with stack-allocated buffer. */ +static int +write_file_data(struct bsdtar *bsdtar, struct archive *a, int fd) +{ + char buff[64*1024]; + ssize_t bytes_read; + ssize_t bytes_written; + + /* XXX TODO: Allocate buffer on heap and store pointer to + * it in bsdtar structure; arrange cleanup as well. XXX */ + + bytes_read = read(fd, buff, sizeof(buff)); + while (bytes_read > 0) { + bytes_written = archive_write_data(a, buff, bytes_read); + if (bytes_written < 0) { + /* Write failed; this is bad */ + bsdtar_warnc(bsdtar, 0, "%s", archive_error_string(a)); + return (-1); + } + if (bytes_written < bytes_read) { + /* Write was truncated; warn but continue. */ + bsdtar_warnc(bsdtar, 0, + "Truncated write; file may have grown while being archived."); + return (0); + } + bytes_read = read(fd, buff, sizeof(buff)); + } + return 0; +} + + +static void +create_cleanup(struct bsdtar *bsdtar) +{ + /* Free inode->pathname map used for hardlink detection. */ + if (bsdtar->links_cache != NULL) { + free_buckets(bsdtar, bsdtar->links_cache); + free(bsdtar->links_cache); + bsdtar->links_cache = NULL; + } + + free_cache(bsdtar->uname_cache); + bsdtar->uname_cache = NULL; + free_cache(bsdtar->gname_cache); + bsdtar->gname_cache = NULL; +} + + +static void +free_buckets(struct bsdtar *bsdtar, struct links_cache *links_cache) +{ + size_t i; + + if (links_cache->buckets == NULL) + return; + + for (i = 0; i < links_cache->number_buckets; i++) { + while (links_cache->buckets[i] != NULL) { + struct links_entry *lp = links_cache->buckets[i]->next; + if (bsdtar->option_warn_links) + bsdtar_warnc(bsdtar, 0, "Missing links to %s", + links_cache->buckets[i]->name); + if (links_cache->buckets[i]->name != NULL) + free(links_cache->buckets[i]->name); + free(links_cache->buckets[i]); + links_cache->buckets[i] = lp; + } + } + free(links_cache->buckets); + links_cache->buckets = NULL; +} + +static void +lookup_hardlink(struct bsdtar *bsdtar, struct archive_entry *entry, + const struct stat *st) +{ + struct links_cache *links_cache; + struct links_entry *le, **new_buckets; + int hash; + size_t i, new_size; + + /* If necessary, initialize the links cache. */ + links_cache = bsdtar->links_cache; + if (links_cache == NULL) { + bsdtar->links_cache = malloc(sizeof(struct links_cache)); + if (bsdtar->links_cache == NULL) + bsdtar_errc(bsdtar, 1, ENOMEM, + "No memory for hardlink detection."); + links_cache = bsdtar->links_cache; + memset(links_cache, 0, sizeof(struct links_cache)); + links_cache->number_buckets = links_cache_initial_size; + links_cache->buckets = malloc(links_cache->number_buckets * + sizeof(links_cache->buckets[0])); + if (links_cache->buckets == NULL) { + bsdtar_errc(bsdtar, 1, ENOMEM, + "No memory for hardlink detection."); + } + for (i = 0; i < links_cache->number_buckets; i++) + links_cache->buckets[i] = NULL; + } + + /* If the links cache overflowed and got flushed, don't bother. */ + if (links_cache->buckets == NULL) + return; + + /* If the links cache is getting too full, enlarge the hash table. */ + if (links_cache->number_entries > links_cache->number_buckets * 2) + { + new_size = links_cache->number_buckets * 2; + new_buckets = malloc(new_size * sizeof(struct links_entry *)); + + if (new_buckets != NULL) { + memset(new_buckets, 0, + new_size * sizeof(struct links_entry *)); + for (i = 0; i < links_cache->number_buckets; i++) { + while (links_cache->buckets[i] != NULL) { + /* Remove entry from old bucket. */ + le = links_cache->buckets[i]; + links_cache->buckets[i] = le->next; + + /* Add entry to new bucket. */ + hash = (le->dev ^ le->ino) % new_size; + + if (new_buckets[hash] != NULL) + new_buckets[hash]->previous = + le; + le->next = new_buckets[hash]; + le->previous = NULL; + new_buckets[hash] = le; + } + } + free(links_cache->buckets); + links_cache->buckets = new_buckets; + links_cache->number_buckets = new_size; + } else { + free_buckets(bsdtar, links_cache); + bsdtar_warnc(bsdtar, ENOMEM, + "No more memory for recording hard links"); + bsdtar_warnc(bsdtar, 0, + "Remaining links will be dumped as full files"); + } + } + + /* Try to locate this entry in the links cache. */ + hash = ( st->st_dev ^ st->st_ino ) % links_cache->number_buckets; + for (le = links_cache->buckets[hash]; le != NULL; le = le->next) { + if (le->dev == st->st_dev && le->ino == st->st_ino) { + archive_entry_copy_hardlink(entry, le->name); + + /* + * Decrement link count each time and release + * the entry if it hits zero. This saves + * memory and is necessary for proper -l + * implementation. + */ + if (--le->links <= 0) { + if (le->previous != NULL) + le->previous->next = le->next; + if (le->next != NULL) + le->next->previous = le->previous; + if (le->name != NULL) + free(le->name); + if (links_cache->buckets[hash] == le) + links_cache->buckets[hash] = le->next; + links_cache->number_entries--; + free(le); + } + + return; + } + } + + /* Add this entry to the links cache. */ + le = malloc(sizeof(struct links_entry)); + if (le != NULL) + le->name = strdup(archive_entry_pathname(entry)); + if ((le == NULL) || (le->name == NULL)) { + free_buckets(bsdtar, links_cache); + bsdtar_warnc(bsdtar, ENOMEM, + "No more memory for recording hard links"); + bsdtar_warnc(bsdtar, 0, + "Remaining hard links will be dumped as full files"); + if (le != NULL) + free(le); + return; + } + if (links_cache->buckets[hash] != NULL) + links_cache->buckets[hash]->previous = le; + links_cache->number_entries++; + le->next = links_cache->buckets[hash]; + le->previous = NULL; + links_cache->buckets[hash] = le; + le->dev = st->st_dev; + le->ino = st->st_ino; + le->links = st->st_nlink - 1; +} + +#ifdef HAVE_POSIX_ACL +static void setup_acl(struct bsdtar *bsdtar, + struct archive_entry *entry, const char *accpath, + int acl_type, int archive_entry_acl_type); + +static void +setup_acls(struct bsdtar *bsdtar, struct archive_entry *entry, + const char *accpath) +{ + archive_entry_acl_clear(entry); + + setup_acl(bsdtar, entry, accpath, + ACL_TYPE_ACCESS, ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + /* Only directories can have default ACLs. */ + if (S_ISDIR(archive_entry_mode(entry))) + setup_acl(bsdtar, entry, accpath, + ACL_TYPE_DEFAULT, ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); +} + +static void +setup_acl(struct bsdtar *bsdtar, struct archive_entry *entry, + const char *accpath, int acl_type, int archive_entry_acl_type) +{ + acl_t acl; + acl_tag_t acl_tag; + acl_entry_t acl_entry; + acl_permset_t acl_permset; + int s, ae_id, ae_tag, ae_perm; + const char *ae_name; + + /* Retrieve access ACL from file. */ + acl = acl_get_file(accpath, acl_type); + if (acl != NULL) { + s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry); + while (s == 1) { + ae_id = -1; + ae_name = NULL; + + acl_get_tag_type(acl_entry, &acl_tag); + if (acl_tag == ACL_USER) { + ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry); + ae_name = lookup_uname(bsdtar, ae_id); + ae_tag = ARCHIVE_ENTRY_ACL_USER; + } else if (acl_tag == ACL_GROUP) { + ae_id = (int)*(gid_t *)acl_get_qualifier(acl_entry); + ae_name = lookup_gname(bsdtar, ae_id); + ae_tag = ARCHIVE_ENTRY_ACL_GROUP; + } else if (acl_tag == ACL_MASK) { + ae_tag = ARCHIVE_ENTRY_ACL_MASK; + } else if (acl_tag == ACL_USER_OBJ) { + ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + } else if (acl_tag == ACL_GROUP_OBJ) { + ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + } else if (acl_tag == ACL_OTHER) { + ae_tag = ARCHIVE_ENTRY_ACL_OTHER; + } else { + /* Skip types that libarchive can't support. */ + continue; + } + + acl_get_permset(acl_entry, &acl_permset); + ae_perm = 0; + /* + * acl_get_perm() is spelled differently on different + * platforms; see bsdtar_platform.h for details. + */ + if (ACL_GET_PERM(acl_permset, ACL_EXECUTE)) + ae_perm |= ARCHIVE_ENTRY_ACL_EXECUTE; + if (ACL_GET_PERM(acl_permset, ACL_READ)) + ae_perm |= ARCHIVE_ENTRY_ACL_READ; + if (ACL_GET_PERM(acl_permset, ACL_WRITE)) + ae_perm |= ARCHIVE_ENTRY_ACL_WRITE; + + archive_entry_acl_add_entry(entry, + archive_entry_acl_type, ae_perm, ae_tag, + ae_id, ae_name); + + s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); + } + acl_free(acl); + } +} +#else +static void +setup_acls(struct bsdtar *bsdtar, struct archive_entry *entry, + const char *accpath) +{ + (void)bsdtar; + (void)entry; + (void)accpath; +} +#endif + +#if HAVE_LISTXATTR && HAVE_LLISTXATTR && HAVE_GETXATTR && HAVE_LGETXATTR + +static void +setup_xattr(struct bsdtar *bsdtar, struct archive_entry *entry, + const char *accpath, const char *name) +{ + size_t size; + void *value = NULL; + char symlink_mode = bsdtar->symlink_mode; + + if (symlink_mode == 'H') + size = getxattr(accpath, name, NULL, 0); + else + size = lgetxattr(accpath, name, NULL, 0); + + if (size == -1) { + bsdtar_warnc(bsdtar, errno, "Couldn't get extended attribute"); + return; + } + + if (size > 0 && (value = malloc(size)) == NULL) { + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + return; + } + + if (symlink_mode == 'H') + size = getxattr(accpath, name, value, size); + else + size = lgetxattr(accpath, name, value, size); + + if (size == -1) { + bsdtar_warnc(bsdtar, errno, "Couldn't get extended attribute"); + return; + } + + archive_entry_xattr_add_entry(entry, name, value, size); + + free(value); +} + +/* + * Linux extended attribute support + */ +static void +setup_xattrs(struct bsdtar *bsdtar, struct archive_entry *entry, + const char *accpath) +{ + char *list, *p; + size_t list_size; + char symlink_mode = bsdtar->symlink_mode; + + if (symlink_mode == 'H') + list_size = listxattr(accpath, NULL, 0); + else + list_size = llistxattr(accpath, NULL, 0); + + if (list_size == -1) { + bsdtar_warnc(bsdtar, errno, + "Couldn't list extended attributes"); + return; + } else if (list_size == 0) + return; + + if ((list = malloc(list_size)) == NULL) { + bsdtar_errc(bsdtar, 1, errno, "Out of memory"); + return; + } + + if (symlink_mode == 'H') + list_size = listxattr(accpath, list, list_size); + else + list_size = llistxattr(accpath, list, list_size); + + if (list_size == -1) { + bsdtar_warnc(bsdtar, errno, + "Couldn't list extended attributes"); + free(list); + return; + } + + for (p = list; (p - list) < list_size; p += strlen(p) + 1) { + if (strncmp(p, "system.", 7) == 0 || + strncmp(p, "xfsroot.", 8) == 0) + continue; + + setup_xattr(bsdtar, entry, accpath, p); + } + + free(list); +} + +#else + +/* + * Generic (stub) extended attribute support. + */ +static void +setup_xattrs(struct bsdtar *bsdtar, struct archive_entry *entry, + const char *accpath) +{ + (void)bsdtar; /* UNUSED */ + (void)entry; /* UNUSED */ + (void)accpath; /* UNUSED */ +} + +#endif + +static void +free_cache(struct name_cache *cache) +{ + size_t i; + + if (cache != NULL) { + for (i = 0; i < cache->size; i++) { + if (cache->cache[i].name != NULL && + cache->cache[i].name != NO_NAME) + free((void *)(uintptr_t)cache->cache[i].name); + } + free(cache); + } +} + +/* + * Lookup uid/gid from uname/gname, return NULL if no match. + */ +static const char * +lookup_name(struct bsdtar *bsdtar, struct name_cache **name_cache_variable, + int (*lookup_fn)(struct bsdtar *, const char **, id_t), id_t id) +{ + struct name_cache *cache; + const char *name; + int slot; + + + if (*name_cache_variable == NULL) { + *name_cache_variable = malloc(sizeof(struct name_cache)); + if (*name_cache_variable == NULL) + bsdtar_errc(bsdtar, 1, ENOMEM, "No more memory"); + memset(*name_cache_variable, 0, sizeof(struct name_cache)); + (*name_cache_variable)->size = name_cache_size; + } + + cache = *name_cache_variable; + cache->probes++; + + slot = id % cache->size; + if (cache->cache[slot].name != NULL) { + if (cache->cache[slot].id == id) { + cache->hits++; + if (cache->cache[slot].name == NO_NAME) + return (NULL); + return (cache->cache[slot].name); + } + if (cache->cache[slot].name != NO_NAME) + free((void *)(uintptr_t)cache->cache[slot].name); + cache->cache[slot].name = NULL; + } + + if (lookup_fn(bsdtar, &name, id) == 0) { + if (name == NULL || name[0] == '\0') { + /* Cache the negative response. */ + cache->cache[slot].name = NO_NAME; + cache->cache[slot].id = id; + } else { + cache->cache[slot].name = strdup(name); + if (cache->cache[slot].name != NULL) { + cache->cache[slot].id = id; + return (cache->cache[slot].name); + } + /* + * Conveniently, NULL marks an empty slot, so + * if the strdup() fails, we've just failed to + * cache it. No recovery necessary. + */ + } + } + return (NULL); +} + +static const char * +lookup_uname(struct bsdtar *bsdtar, uid_t uid) +{ + return (lookup_name(bsdtar, &bsdtar->uname_cache, + &lookup_uname_helper, (id_t)uid)); +} + +static int +lookup_uname_helper(struct bsdtar *bsdtar, const char **name, id_t id) +{ + struct passwd *pwent; + + (void)bsdtar; /* UNUSED */ + + errno = 0; + pwent = getpwuid((uid_t)id); + if (pwent == NULL) { + *name = NULL; + if (errno != 0) + bsdtar_warnc(bsdtar, errno, "getpwuid(%d) failed", id); + return (errno); + } + + *name = pwent->pw_name; + return (0); +} + +static const char * +lookup_gname(struct bsdtar *bsdtar, gid_t gid) +{ + return (lookup_name(bsdtar, &bsdtar->gname_cache, + &lookup_gname_helper, (id_t)gid)); +} + +static int +lookup_gname_helper(struct bsdtar *bsdtar, const char **name, id_t id) +{ + struct group *grent; + + (void)bsdtar; /* UNUSED */ + + errno = 0; + grent = getgrgid((gid_t)id); + if (grent == NULL) { + *name = NULL; + if (errno != 0) + bsdtar_warnc(bsdtar, errno, "getgrgid(%d) failed", id); + return (errno); + } + + *name = grent->gr_name; + return (0); +} + +/* + * Test if the specified file is new enough to include in the archive. + */ +int +new_enough(struct bsdtar *bsdtar, const char *path, const struct stat *st) +{ + struct archive_dir_entry *p; + + /* + * If this file/dir is excluded by a time comparison, skip it. + */ + if (bsdtar->newer_ctime_sec > 0) { + if (st->st_ctime < bsdtar->newer_ctime_sec) + return (0); /* Too old, skip it. */ + if (st->st_ctime == bsdtar->newer_ctime_sec + && ARCHIVE_STAT_CTIME_NANOS(st) + <= bsdtar->newer_ctime_nsec) + return (0); /* Too old, skip it. */ + } + if (bsdtar->newer_mtime_sec > 0) { + if (st->st_mtime < bsdtar->newer_mtime_sec) + return (0); /* Too old, skip it. */ + if (st->st_mtime == bsdtar->newer_mtime_sec + && ARCHIVE_STAT_MTIME_NANOS(st) + <= bsdtar->newer_mtime_nsec) + return (0); /* Too old, skip it. */ + } + + /* + * In -u mode, we only write an entry if it's newer than + * what was already in the archive. + */ + if (bsdtar->archive_dir != NULL && + bsdtar->archive_dir->head != NULL) { + for (p = bsdtar->archive_dir->head; p != NULL; p = p->next) { + if (pathcmp(path, p->name)==0) + return (p->mtime_sec < st->st_mtime || + (p->mtime_sec == st->st_mtime && + p->mtime_nsec + < ARCHIVE_STAT_MTIME_NANOS(st))); + } + } + + /* If the file wasn't rejected, include it. */ + return (1); +} + +/* + * Add an entry to the dir list for 'u' mode. + * + * XXX TODO: Make this fast. + */ +static void +add_dir_list(struct bsdtar *bsdtar, const char *path, + time_t mtime_sec, int mtime_nsec) +{ + struct archive_dir_entry *p; + + /* + * Search entire list to see if this file has appeared before. + * If it has, override the timestamp data. + */ + p = bsdtar->archive_dir->head; + while (p != NULL) { + if (strcmp(path, p->name)==0) { + p->mtime_sec = mtime_sec; + p->mtime_nsec = mtime_nsec; + return; + } + p = p->next; + } + + p = malloc(sizeof(*p)); + if (p == NULL) + bsdtar_errc(bsdtar, 1, ENOMEM, "Can't read archive directory"); + + p->name = strdup(path); + if (p->name == NULL) + bsdtar_errc(bsdtar, 1, ENOMEM, "Can't read archive directory"); + p->mtime_sec = mtime_sec; + p->mtime_nsec = mtime_nsec; + p->next = NULL; + if (bsdtar->archive_dir->tail == NULL) { + bsdtar->archive_dir->head = bsdtar->archive_dir->tail = p; + } else { + bsdtar->archive_dir->tail->next = p; + bsdtar->archive_dir->tail = p; + } +} + +void +test_for_append(struct bsdtar *bsdtar) +{ + struct stat s; + + if (*bsdtar->argv == NULL) + bsdtar_errc(bsdtar, 1, 0, "no files or directories specified"); + if (bsdtar->filename == NULL) + bsdtar_errc(bsdtar, 1, 0, "Cannot append to stdout."); + + if (bsdtar->create_compression != 0) + bsdtar_errc(bsdtar, 1, 0, + "Cannot append to %s with compression", bsdtar->filename); + + if (stat(bsdtar->filename, &s) != 0) + return; + + if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode)) + bsdtar_errc(bsdtar, 1, 0, + "Cannot append to %s: not a regular file.", + bsdtar->filename); +} |