From da337ee329bb4f493e3ebf6fbd7c794e06969783 Mon Sep 17 00:00:00 2001 From: Joerg Sonnenberger Date: Tue, 3 May 2005 14:46:11 +0000 Subject: [PATCH] Sync with FreeBSD. This adds read-only support for zip and ISO9660. --- contrib/libarchive/README | 7 +- contrib/libarchive/README.DELETED | 1 + contrib/libarchive/README.DRAGONFLY | 2 +- contrib/libarchive/archive.h.in | 7 +- contrib/libarchive/archive_entry.c | 12 +- contrib/libarchive/archive_entry.h | 3 +- contrib/libarchive/archive_platform.h | 25 +- contrib/libarchive/archive_private.h | 4 +- contrib/libarchive/archive_read.3 | 155 ++- contrib/libarchive/archive_read.c | 17 +- contrib/libarchive/archive_read_extract.c | 130 ++- contrib/libarchive/archive_read_open_file.c | 57 +- .../archive_read_support_compression_bzip2.c | 3 +- .../archive_read_support_compression_gzip.c | 7 +- .../archive_read_support_compression_none.c | 144 +-- .../archive_read_support_format_all.c | 4 +- .../archive_read_support_format_cpio.c | 8 +- .../archive_read_support_format_iso9660.c | 1001 +++++++++++++++++ .../archive_read_support_format_tar.c | 22 +- .../archive_read_support_format_zip.c | 793 +++++++++++++ contrib/libarchive/archive_string.c | 17 +- contrib/libarchive/archive_string.h | 7 +- contrib/libarchive/archive_string_sprintf.c | 93 +- contrib/libarchive/archive_util.3 | 30 +- contrib/libarchive/archive_util.c | 4 +- contrib/libarchive/archive_write.3 | 84 +- contrib/libarchive/archive_write.c | 5 +- contrib/libarchive/archive_write_open_file.c | 21 +- .../libarchive/archive_write_set_format_pax.c | 295 +++-- contrib/libarchive/libarchive-formats.5 | 26 +- contrib/libarchive/libarchive.3 | 15 +- contrib/libarchive/tar.5 | 10 +- 32 files changed, 2669 insertions(+), 340 deletions(-) create mode 100644 contrib/libarchive/archive_read_support_format_iso9660.c create mode 100644 contrib/libarchive/archive_read_support_format_zip.c diff --git a/contrib/libarchive/README b/contrib/libarchive/README index 2e2d23f92a..a1b80ec545 100644 --- a/contrib/libarchive/README +++ b/contrib/libarchive/README @@ -1,4 +1,4 @@ -$FreeBSD: src/lib/libarchive/README,v 1.3 2004/08/04 06:19:31 kientzle Exp $ +$FreeBSD: src/lib/libarchive/README,v 1.4 2005/02/12 23:09:44 kientzle Exp $ libarchive: a library for reading and writing streaming archives @@ -15,8 +15,7 @@ Documentation: You should also read the copious comments in "archive.h" and the source code for the sample "bsdtar" program for more details. Please let me know -about any errors or omissions you find. (In particular, I no doubt missed -a few things when researching the tar.5 page.) +about any errors or omissions you find. Currently, the library automatically detects and reads the following: * gzip compression @@ -31,6 +30,8 @@ Currently, the library automatically detects and reads the following: * 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 diff --git a/contrib/libarchive/README.DELETED b/contrib/libarchive/README.DELETED index 50146e641e..55e61ed13b 100644 --- a/contrib/libarchive/README.DELETED +++ b/contrib/libarchive/README.DELETED @@ -1,3 +1,4 @@ CVS +Makefile Makefile.am configure.ac.in diff --git a/contrib/libarchive/README.DRAGONFLY b/contrib/libarchive/README.DRAGONFLY index 9ceb0f163e..c76a5f67d8 100644 --- a/contrib/libarchive/README.DRAGONFLY +++ b/contrib/libarchive/README.DRAGONFLY @@ -1,3 +1,3 @@ This directory is a checkout from src/lib/libarchive of the FreeBSD -CVS repository from Tue Nov 9 22:26:00 CET 2004. See README.DELETED +CVS repository from Tue May 3 16:32:31 CEST 2005. See README.DELETED for files not included here. diff --git a/contrib/libarchive/archive.h.in b/contrib/libarchive/archive.h.in index 3ee8a4e100..52c85638b5 100644 --- a/contrib/libarchive/archive.h.in +++ b/contrib/libarchive/archive.h.in @@ -23,7 +23,7 @@ * (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.19 2004/10/18 05:31:01 kientzle Exp $ + * $FreeBSD: src/lib/libarchive/archive.h.in,v 1.22 2005/01/25 06:07:28 kientzle Exp $ */ #ifndef ARCHIVE_H_INCLUDED @@ -128,6 +128,9 @@ typedef int archive_close_callback(struct archive *, void *_client_data); #define ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE (ARCHIVE_FORMAT_TAR | 2) #define ARCHIVE_FORMAT_TAR_PAX_RESTRICTED (ARCHIVE_FORMAT_TAR | 3) #define ARCHIVE_FORMAT_TAR_GNUTAR (ARCHIVE_FORMAT_TAR | 4) +#define ARCHIVE_FORMAT_ISO9660 0x40000 +#define ARCHIVE_FORMAT_ISO9660_ROCKRIDGE (ARCHIVE_FORMAT_ISO9660 | 1) +#define ARCHIVE_FORMAT_ZIP 0x50000 /*- * Basic outline for reading an archive: @@ -159,7 +162,9 @@ int archive_read_support_compression_none(struct archive *); int archive_read_support_format_all(struct archive *); int archive_read_support_format_cpio(struct archive *); int archive_read_support_format_gnutar(struct archive *); +int archive_read_support_format_iso9660(struct archive *); int archive_read_support_format_tar(struct archive *); +int archive_read_support_format_zip(struct archive *); /* Open the archive using callbacks for archive I/O. */ diff --git a/contrib/libarchive/archive_entry.c b/contrib/libarchive/archive_entry.c index 8d7d09f646..6652b7a20b 100644 --- a/contrib/libarchive/archive_entry.c +++ b/contrib/libarchive/archive_entry.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry.c,v 1.23 2004/08/08 07:39:19 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry.c,v 1.25 2005/03/13 02:53:42 kientzle Exp $"); #include #include @@ -597,6 +597,12 @@ 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) { @@ -609,7 +615,7 @@ archive_entry_set_rdevmajor(struct archive_entry *entry, dev_t m) dev_t d; d = entry->ae_stat.st_rdev; - entry->ae_stat.st_rdev = makedev(m, minor(d)); + entry->ae_stat.st_rdev = makedev(major(m), minor(d)); } void @@ -618,7 +624,7 @@ archive_entry_set_rdevminor(struct archive_entry *entry, dev_t m) dev_t d; d = entry->ae_stat.st_rdev; - entry->ae_stat.st_rdev = makedev( major(d), m); + entry->ae_stat.st_rdev = makedev(major(d), minor(m)); } void diff --git a/contrib/libarchive/archive_entry.h b/contrib/libarchive/archive_entry.h index b8e5084c52..e6641b4f2c 100644 --- a/contrib/libarchive/archive_entry.h +++ b/contrib/libarchive/archive_entry.h @@ -23,7 +23,7 @@ * (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.12 2004/08/08 07:39:19 kientzle Exp $ + * $FreeBSD: src/lib/libarchive/archive_entry.h,v 1.13 2005/03/13 02:53:42 kientzle Exp $ */ #ifndef ARCHIVE_ENTRY_H_INCLUDED @@ -112,6 +112,7 @@ void archive_entry_set_link(struct archive_entry *, const char *); void archive_entry_set_mode(struct archive_entry *, mode_t); void archive_entry_set_mtime(struct archive_entry *, time_t, long); void archive_entry_set_pathname(struct archive_entry *, const char *); +void archive_entry_copy_pathname(struct archive_entry *, const char *); void archive_entry_copy_pathname_w(struct archive_entry *, const wchar_t *); void archive_entry_set_rdevmajor(struct archive_entry *, dev_t); void archive_entry_set_rdevminor(struct archive_entry *, dev_t); diff --git a/contrib/libarchive/archive_platform.h b/contrib/libarchive/archive_platform.h index ecbacc7d35..0ef51a1c2b 100644 --- a/contrib/libarchive/archive_platform.h +++ b/contrib/libarchive/archive_platform.h @@ -23,7 +23,7 @@ * (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.12 2004/08/07 03:09:28 kientzle Exp $ + * $FreeBSD: src/lib/libarchive/archive_platform.h,v 1.14 2005/04/23 17:56:34 kientzle Exp $ */ /* @@ -43,6 +43,11 @@ /* A default configuration for FreeBSD, used if there is no config.h. */ #ifdef __FreeBSD__ +#if __FreeBSD__ > 4 +#define HAVE_ACL_CREATE_ENTRY 1 +#define HAVE_ACL_INIT 1 +#define HAVE_ACL_SET_FILE 1 +#endif #define HAVE_BZLIB_H 1 #define HAVE_CHFLAGS 1 #define HAVE_DECL_STRERROR_R 1 @@ -74,9 +79,7 @@ #define HAVE_STRRCHR 1 #define HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1 #define HAVE_STRUCT_STAT_ST_RDEV 1 -#if __FreeBSD__ > 4 #define HAVE_SYS_ACL_H 1 -#endif #define HAVE_SYS_IOCTL_H 1 #define HAVE_SYS_STAT_H 1 #define HAVE_SYS_TIME_H 1 @@ -105,9 +108,19 @@ #include #endif -/* TODO: Test for the functions we use as well... */ -#if HAVE_SYS_ACL_H -#define HAVE_POSIX_ACLS 1 +/* FreeBSD 4 and earlier lack intmax_t/uintmax_t */ +#if defined(__FreeBSD__) && __FreeBSD__ < 5 +#define intmax_t int64_t +#define uintmax_t uint64_t +#endif + +/* + * If this platform has , acl_create(), acl_init(), and + * acl_set_file(), 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 +#define HAVE_POSIX_ACL 1 #endif /* Set up defaults for internal error codes. */ diff --git a/contrib/libarchive/archive_private.h b/contrib/libarchive/archive_private.h index 759681777d..3e8039b584 100644 --- a/contrib/libarchive/archive_private.h +++ b/contrib/libarchive/archive_private.h @@ -23,7 +23,7 @@ * (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.16 2004/11/06 05:25:53 kientzle Exp $ + * $FreeBSD: src/lib/libarchive/archive_private.h,v 1.17 2005/04/06 04:19:30 kientzle Exp $ */ #ifndef ARCHIVE_PRIVATE_H_INCLUDED @@ -156,6 +156,7 @@ struct archive { int (*bid)(struct archive *); int (*read_header)(struct archive *, struct archive_entry *); int (*read_data)(struct archive *, const void **, size_t *, off_t *); + int (*read_data_skip)(struct archive *); int (*cleanup)(struct archive *); void *format_data; /* Format-specific data for readers. */ } formats[4]; @@ -229,6 +230,7 @@ int __archive_read_register_format(struct archive *a, int (*bid)(struct archive *), int (*read_header)(struct archive *, struct archive_entry *), int (*read_data)(struct archive *, const void **, size_t *, off_t *), + int (*read_data_skip)(struct archive *), int (*cleanup)(struct archive *)); int __archive_read_register_compression(struct archive *a, diff --git a/contrib/libarchive/archive_read.3 b/contrib/libarchive/archive_read.3 index 7dd9e69e33..0277758ead 100644 --- a/contrib/libarchive/archive_read.3 +++ b/contrib/libarchive/archive_read.3 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2003-2004 Tim Kientzle +.\" Copyright (c) 2003-2005 Tim Kientzle .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,9 +22,9 @@ .\" 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.11 2004/08/07 19:22:50 kientzle Exp $ +.\" $FreeBSD: src/lib/libarchive/archive_read.3,v 1.19 2005/02/13 22:25:11 ru Exp $ .\" -.Dd October 1, 2003 +.Dd January 8, 2005 .Dt archive_read 3 .Os .Sh NAME @@ -35,9 +35,11 @@ .Nm archive_read_support_compression_compress , .Nm archive_read_support_compression_gzip , .Nm archive_read_support_compression_none , -.Nm archive_read_support_format_tar , -.Nm archive_read_support_format_cpio , .Nm archive_read_support_format_all , +.Nm archive_read_support_format_cpio , +.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_open_fd , .Nm archive_read_open_file , @@ -49,9 +51,9 @@ .Nm archive_read_data_into_fd , .Nm archive_read_extract , .Nm archive_read_extract_set_progress_callback , -.Nm archive_read_close +.Nm archive_read_close , .Nm archive_read_finish -.Nd functions for reading tar archives +.Nd functions for reading streaming archives .Sh SYNOPSIS .In archive.h .Ft struct archive * @@ -69,17 +71,21 @@ .Ft int .Fn archive_read_support_compression_none "struct archive *" .Ft int -.Fn archive_read_support_format_tar "struct archive *" +.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_all "struct archive *" +.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 -.Fn archive_read_open "struct archive *" "void *client_data" "archive_read_archive_callback *" "archive_open_archive_callback *" "archive_close_archive_callback *" +.Fn archive_read_open "struct archive *" "void *client_data" "archive_open_archive_callback *" "archive_read_archive_callback *" "archive_close_archive_callback *" .Ft int -.Fn archive_read_open_fd "struct archive *" "int fd" +.Fn archive_read_open_fd "struct archive *" "int fd" "size_t block_size" .Ft int -.Fn archive_read_open_file "struct archive *" "const char *filename" +.Fn archive_read_open_file "struct archive *" "const char *filename" "size_t block_size" .Ft int .Fn archive_read_next_header "struct archive *" "struct archive_entry **" .Ft ssize_t @@ -93,7 +99,7 @@ .Ft int .Fn archive_read_data_into_fd "struct archive *" "int fd" .Ft int -.Fn archive_read_extract "struct archive *" "int flags" +.Fn archive_read_extract "struct archive *" "struct archive_entry *" "int flags" .Ft void .Fn archive_read_extract_set_progress_callback "struct archive *" "void (*func)(void *)" "void *user_data" .Ft int @@ -119,7 +125,7 @@ Sets the block size used for reading the archive data. This controls the size that will be used when invoking the read callback function. The default is 20 records or 10240 bytes for tar formats. -.It Fn archive_read_support_compression_XXX +.It 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 Enables auto-detection code and decompression support for the specified compression. Note that @@ -128,10 +134,10 @@ is always enabled by default. For convenience, .Fn archive_read_support_compression_all enables all available decompression code. -.It Fn archive_read_support_format_XXX +.It Fn archive_read_support_format_all , Fn archive_read_support_format_cpio , Fn archive_read_support_format_iso9660 , Fn archive_read_support_format_tar, Fn archive_read_support_format_zip Enables support---including auto-detection code---for the specified archive format. -In particular, +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. @@ -143,7 +149,12 @@ Note that there is no default. Freeze the settings, open the archive, and prepare for reading entries. This is the most generic version of this call, which accepts three callback functions. -The library invokes these client-provided functions to obtain +Most clients will want to use +.Fn archive_read_open_file +or +.Fn archive_read_open_fd +instead. +The library invokes the client-provided functions to obtain raw bytes from the archive. Note: The API permits a decompression method to fork and invoke the callbacks from another process. @@ -156,14 +167,14 @@ communicate between the open/read/close callbacks and the mainline code. .It Fn archive_read_open_fd Like .Fn archive_read_open , -except that it accepts a file descriptor rather than +except that it accepts a file descriptor and block size rather than a trio of function pointers. Note that the file descriptor will not be automatically closed at end-of-archive. .It Fn archive_read_open_file Like .Fn archive_read_open , -except that it accepts a simple filename. +except that it accepts a simple filename and a block size. A NULL filename represents standard input. .It Fn archive_read_next_header Read the header for the next entry and return a pointer to @@ -185,6 +196,9 @@ function avoids copying data and allows you to correctly handle sparse files, as supported by some archive formats. The library gaurantees 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 @@ -201,6 +215,10 @@ to copy the entire entry to the provided file descriptor. .It Fn archive_read_extract A convenience function that recreates the specified object on disk and reads the entry data into that object. +The filename, permissions, and other critical information +are taken from the provided +.Va archive_entry +object. The .Va flags argument modifies how the object is recreated. @@ -218,13 +236,28 @@ 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 files are unlinked before the new entry is written. +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 and recreated from scratch. 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. .El +Note that not all attributes are set immediately; +some attributes are cached in memory and written to disk only +when the archive is closed. +(For example, read-only directories are initially created +writable so that files within those directories can be +restored. +The final permissions are set when the archive is closed.) .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. @@ -239,7 +272,7 @@ Complete the archive and invoke the close callback. .It Fn archive_read_finish Invokes .Fn archive_read_close -if it wasn't invoked maually, then release all resources. +if it was not invoked maually, then release all resources. .El .Pp Note that the library determines most of the relevant information about @@ -251,6 +284,13 @@ or 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 @@ -263,17 +303,46 @@ The callback functions must match the following prototypes: .Ft typedef int .Fn archive_close_callback "struct archive *" "void *client_data" .El -These callback functions are called whenever the library requires -raw bytes from the archive. -Note that it is the client's responsibility to correctly -block the input. .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 . +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 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 +regurn +.Cm ARCHIVE_FATAL. .Sh EXAMPLE The following illustrates basic usage of the library. In this example, @@ -337,9 +406,9 @@ myclose(struct archive *a, void *client_data) Most functions return zero on success, non-zero on error. The possible return codes include: .Cm ARCHIVE_OK -(the operation succeeded) +(the operation succeeded), .Cm ARCHIVE_WARN -(the operation succeeded but a non-critical error was encountered) +(the operation succeeded but a non-critical error was encountered), .Cm ARCHIVE_EOF (end-of-archive was encountered), .Cm ARCHIVE_RETRY @@ -389,6 +458,7 @@ to be returned.) .Sh SEE ALSO .Xr tar 1 , .Xr archive 3 , +.Xr archive_util 3 , .Xr tar 5 .Sh HISTORY The @@ -402,3 +472,22 @@ The 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_read_extract , +but final permissions are not set until +.Fn archive_read_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. diff --git a/contrib/libarchive/archive_read.c b/contrib/libarchive/archive_read.c index bbf0a3e74c..5ae0239db3 100644 --- a/contrib/libarchive/archive_read.c +++ b/contrib/libarchive/archive_read.c @@ -33,7 +33,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read.c,v 1.12 2004/08/14 03:45:45 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read.c,v 1.14 2005/04/06 04:19:30 kientzle Exp $"); #include #include @@ -213,6 +213,7 @@ archive_read_next_header(struct archive *a, struct archive_entry **entryp) *entryp = NULL; entry = a->entry; archive_entry_clear(entry); + archive_string_empty(&a->error_string); /* * If client didn't consume entire data, skip any remainder @@ -225,6 +226,8 @@ archive_read_next_header(struct archive *a, struct archive_entry **entryp) a->state = ARCHIVE_STATE_FATAL; return (ARCHIVE_FATAL); } + if (ret != ARCHIVE_OK) + return (ret); } /* Record start-of-header. */ @@ -404,9 +407,13 @@ archive_read_data_skip(struct archive *a) archive_check_magic(a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA); - while ((r = archive_read_data_block(a, &buff, &size, &offset)) == - ARCHIVE_OK) - ; + if (a->format->read_data_skip != NULL) + r = (a->format->read_data_skip)(a); + else { + while ((r = archive_read_data_block(a, &buff, &size, &offset)) + == ARCHIVE_OK) + ; + } if (r == ARCHIVE_EOF) r = ARCHIVE_OK; @@ -504,6 +511,7 @@ __archive_read_register_format(struct archive *a, int (*bid)(struct archive *), int (*read_header)(struct archive *, struct archive_entry *), int (*read_data)(struct archive *, const void **, size_t *, off_t *), + int (*read_data_skip)(struct archive *), int (*cleanup)(struct archive *)) { int i, number_slots; @@ -519,6 +527,7 @@ __archive_read_register_format(struct archive *a, 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].format_data = format_data; return (ARCHIVE_OK); diff --git a/contrib/libarchive/archive_read_extract.c b/contrib/libarchive/archive_read_extract.c index b4ba5a6cda..521da3172b 100644 --- a/contrib/libarchive/archive_read_extract.c +++ b/contrib/libarchive/archive_read_extract.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_extract.c,v 1.36 2004/11/05 05:16:40 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_extract.c,v 1.39 2005/04/17 22:49:00 kientzle Exp $"); #include #ifdef HAVE_SYS_ACL_H @@ -125,11 +125,11 @@ static int extract_symlink(struct archive *, struct archive_entry *, int); static unsigned int hash(const char *); static gid_t lookup_gid(struct archive *, const char *uname, gid_t); static uid_t lookup_uid(struct archive *, const char *uname, uid_t); +static int create_dir(struct archive *, const char *, int flags); +static int create_dir_mutable(struct archive *, char *, int flags); +static int create_dir_recursive(struct archive *, char *, int flags); static int create_parent_dir(struct archive *, const char *, int flags); -static int create_parent_dir_internal(struct archive *, char *, - int flags); -static int create_parent_dir_recursive(struct archive *, char *, - int flags); +static int create_parent_dir_mutable(struct archive *, char *, int flags); static int restore_metadata(struct archive *, struct archive_entry *, int flags); #ifdef HAVE_POSIX_ACL @@ -164,6 +164,7 @@ archive_read_extract(struct archive *a, struct archive_entry *entry, int flags) struct extract *extract; int ret; int restore_pwd; + char *original_filename; if (a->extract == NULL) { a->extract = malloc(sizeof(*a->extract)); @@ -180,16 +181,54 @@ archive_read_extract(struct archive *a, struct archive_entry *entry, int flags) extract->pst = NULL; extract->current_fixup = NULL; restore_pwd = -1; + original_filename = NULL; /* - * TODO: If pathname is longer than PATH_MAX, record starting - * directory and move to a suitable intermediate dir, which - * might require creating them! + * If pathname is longer than PATH_MAX, record starting directory + * and chdir to a suitable intermediate dir. */ if (strlen(archive_entry_pathname(entry)) > PATH_MAX) { + char *intdir, *tail; + + /* + * Yes, the copy here is necessary because we edit + * the pathname in-place to create intermediate dirnames. + */ + original_filename = strdup(archive_entry_pathname(entry)); + restore_pwd = open(".", O_RDONLY); - /* XXX chdir() to a suitable intermediate dir XXX */ - /* XXX Update pathname in 'entry' XXX */ + /* + * "intdir" points to the initial dir section we're going + * to remove, "tail" points to the remainder of the path. + */ + intdir = tail = original_filename; + while (strlen(tail) > PATH_MAX) { + intdir = tail; + + /* Locate a dir prefix shorter than PATH_MAX. */ + tail = intdir + PATH_MAX - 8; + while (tail > intdir && *tail != '/') + tail--; + if (tail <= intdir) { + archive_set_error(a, EPERM, + "Path element too long"); + ret = ARCHIVE_WARN; + goto cleanup; + } + + /* Create intdir and chdir to it. */ + *tail = '\0'; /* Terminate dir portion */ + ret = create_dir(a, intdir, flags); + if (ret == ARCHIVE_OK && chdir(intdir) != 0) { + archive_set_error(a, errno, "Couldn't chdir"); + ret = ARCHIVE_WARN; + } + *tail = '/'; /* Restore the / we removed. */ + if (ret != ARCHIVE_OK) + goto cleanup; + tail++; + } + archive_entry_set_pathname(entry, tail); } if (stat(archive_entry_pathname(entry), &extract->st) == 0) @@ -228,9 +267,15 @@ archive_read_extract(struct archive *a, struct archive_entry *entry, int flags) } } + +cleanup: /* If we changed directory above, restore it here. */ - if (restore_pwd >= 0) + if (restore_pwd >= 0 && original_filename != NULL) { fchdir(restore_pwd); + close(restore_pwd); + archive_entry_copy_pathname(entry, original_filename); + free(original_filename); + } return (ret); } @@ -364,6 +409,9 @@ sort_dir_list(struct fixup_entry *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 *a, const char *pathname) @@ -434,9 +482,9 @@ extract_file(struct archive *a, struct archive_entry *entry, int flags) return (ARCHIVE_WARN); } r = archive_read_data_into_fd(a, fd); + close(fd); extract->pst = NULL; /* Cached stat data no longer valid. */ r2 = restore_metadata(a, entry, flags); - close(fd); return (err_combine(r, r2)); } @@ -496,7 +544,7 @@ extract_dir(struct archive *a, struct archive_entry *entry, int flags) unlink(path); } else { /* Doesn't already exist; try building the parent path. */ - if (create_parent_dir_internal(a, path, flags) != ARCHIVE_OK) + if (create_parent_dir_mutable(a, path, flags) != ARCHIVE_OK) return (ARCHIVE_WARN); } @@ -533,27 +581,36 @@ success: static int create_parent_dir(struct archive *a, const char *path, int flags) { - struct extract *extract; int r; - extract = a->extract; - /* Copy path to mutable storage. */ - archive_strcpy(&(extract->create_parent_dir), path); + archive_strcpy(&(a->extract->create_parent_dir), path); + r = create_parent_dir_mutable(a, a->extract->create_parent_dir.s, flags); + return (r); +} - r = create_parent_dir_internal(a, extract->create_parent_dir.s, flags); +/* + * Like create_parent_dir, but creates the dir actually requested, not + * the parent. + */ +static int +create_dir(struct archive *a, const char *path, int flags) +{ + int r; + /* Copy path to mutable storage. */ + archive_strcpy(&(a->extract->create_parent_dir), path); + r = create_dir_mutable(a, a->extract->create_parent_dir.s, flags); return (r); } /* - * Handle remaining setup for create_parent_dir_recursive(), assuming - * path is already in mutable storage. + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. */ static int -create_parent_dir_internal(struct archive *a, char *path, int flags) +create_parent_dir_mutable(struct archive *a, char *path, int flags) { char *slash; - mode_t old_umask; int r; /* Remove tail element to obtain parent name. */ @@ -561,10 +618,24 @@ create_parent_dir_internal(struct archive *a, char *path, int flags) if (slash == NULL) return (ARCHIVE_OK); *slash = '\0'; + r = create_dir_mutable(a, path, flags); + *slash = '/'; + return (r); +} + +/* + * Create the specified dir, assuming path is already in + * mutable storage. + */ +static int +create_dir_mutable(struct archive *a, char *path, int flags) +{ + mode_t old_umask; + int r; + old_umask = umask(~SECURE_DIR_MODE); - r = create_parent_dir_recursive(a, path, flags); + r = create_dir_recursive(a, path, flags); umask(old_umask); - *slash = '/'; return (r); } @@ -575,7 +646,7 @@ create_parent_dir_internal(struct archive *a, char *path, int flags) * Otherwise, returns ARCHIVE_WARN. */ static int -create_parent_dir_recursive(struct archive *a, char *path, int flags) +create_dir_recursive(struct archive *a, char *path, int flags) { struct stat st; struct extract *extract; @@ -600,7 +671,7 @@ create_parent_dir_recursive(struct archive *a, char *path, int flags) /* Don't bother trying to create null path, '.', or '..'. */ if (slash != NULL) { *slash = '\0'; - r = create_parent_dir_recursive(a, path, flags); + r = create_dir_recursive(a, path, flags); *slash = '/'; return (r); } @@ -632,7 +703,7 @@ create_parent_dir_recursive(struct archive *a, char *path, int flags) return (ARCHIVE_WARN); } else if (slash != NULL) { *slash = '\0'; - r = create_parent_dir_recursive(a, path, flags); + r = create_dir_recursive(a, path, flags); *slash = '/'; if (r != ARCHIVE_OK) return (r); @@ -1011,9 +1082,12 @@ set_perm(struct archive *a, struct archive_entry *entry, int mode, int flags) le = current_fixup(a, archive_entry_pathname(entry)); le->fixup |= FIXUP_FFLAGS; le->fflags_set = set; + /* Store the mode if it's not already there. */ + if ((le->fixup & FIXUP_MODE) == 0) + le->mode = mode; } else { r = set_fflags(a, archive_entry_pathname(entry), - archive_entry_mode(entry), set, clear); + mode, set, clear); if (r != ARCHIVE_OK) return (r); } diff --git a/contrib/libarchive/archive_read_open_file.c b/contrib/libarchive/archive_read_open_file.c index 139eb8678f..4b519656a0 100644 --- a/contrib/libarchive/archive_read_open_file.c +++ b/contrib/libarchive/archive_read_open_file.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_file.c,v 1.6 2004/06/27 23:36:39 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_file.c,v 1.8 2005/03/13 01:51:16 kientzle Exp $"); #include #include @@ -41,7 +41,8 @@ struct read_file_data { int fd; size_t block_size; void *buffer; - char filename[1]; + mode_t st_mode; /* Mode bits for opened file. */ + char filename[1]; /* Must be last! */ }; static int file_close(struct archive *, void *); @@ -54,13 +55,13 @@ archive_read_open_file(struct archive *a, const char *filename, { struct read_file_data *mine; - if (filename == NULL) { + if (filename == NULL || filename[0] == '\0') { mine = malloc(sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); } - mine->filename[0] = 0; + mine->filename[0] = '\0'; } else { mine = malloc(sizeof(*mine) + strlen(filename)); if (mine == NULL) { @@ -82,21 +83,27 @@ file_open(struct archive *a, void *client_data) struct stat st; mine->buffer = malloc(mine->block_size); - if (*mine->filename != 0) + if (mine->filename[0] != '\0') mine->fd = open(mine->filename, O_RDONLY); else - mine->fd = 0; + 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) { + /* Set dev/ino of archive file so extract won't overwrite. */ a->skip_file_dev = st.st_dev; a->skip_file_ino = st.st_ino; + /* Remember mode so close can decide whether to flush. */ + mine->st_mode = st.st_mode; } else { - archive_set_error(a, errno, "Can't stat '%s'", - mine->filename); + 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); @@ -106,10 +113,19 @@ static ssize_t file_read(struct archive *a, void *client_data, const void **buff) { struct read_file_data *mine = client_data; + ssize_t bytes_read; (void)a; /* UNUSED */ *buff = mine->buffer; - return (read(mine->fd, mine->buffer, mine->block_size)); + 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); } static int @@ -118,7 +134,28 @@ file_close(struct archive *a, void *client_data) struct read_file_data *mine = client_data; (void)a; /* UNUSED */ - if (mine->fd >= 0) + + /* + * 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); diff --git a/contrib/libarchive/archive_read_support_compression_bzip2.c b/contrib/libarchive/archive_read_support_compression_bzip2.c index 3f40503d57..3cb2869c19 100644 --- a/contrib/libarchive/archive_read_support_compression_bzip2.c +++ b/contrib/libarchive/archive_read_support_compression_bzip2.c @@ -26,9 +26,10 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_bzip2.c,v 1.6 2004/08/14 03:45:45 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_bzip2.c,v 1.7 2004/12/22 06:30:14 kientzle Exp $"); #include +#include #include #include #include diff --git a/contrib/libarchive/archive_read_support_compression_gzip.c b/contrib/libarchive/archive_read_support_compression_gzip.c index 053012e643..dec7b1fd7b 100644 --- a/contrib/libarchive/archive_read_support_compression_gzip.c +++ b/contrib/libarchive/archive_read_support_compression_gzip.c @@ -26,7 +26,7 @@ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_gzip.c,v 1.7 2004/08/14 03:45:45 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_gzip.c,v 1.9 2005/03/13 01:48:33 kientzle Exp $"); #include @@ -426,7 +426,7 @@ drive_decompressor(struct archive *a, struct private_data *state) */ case 11: /* Optional Extra: Second byte of Length. */ if ((flags & 4)) { - count = (count << 8) | (255 & (int)b); + count = (0xff00 & ((int)b << 8)) | count; header_state = 12; break; } @@ -512,6 +512,9 @@ drive_decompressor(struct archive *a, struct private_data *state) return (ARCHIVE_OK); default: /* Any other return value is an error. */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "gzip decompression failed (%s)", + state->stream.msg); goto fatal; } } diff --git a/contrib/libarchive/archive_read_support_compression_none.c b/contrib/libarchive/archive_read_support_compression_none.c index ead251f7a2..fbec19e8d1 100644 --- a/contrib/libarchive/archive_read_support_compression_none.c +++ b/contrib/libarchive/archive_read_support_compression_none.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_none.c,v 1.5 2004/04/28 04:41:27 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_compression_none.c,v 1.6 2005/02/13 23:29:54 kientzle Exp $"); #include #include @@ -144,63 +144,83 @@ archive_decompressor_none_read_ahead(struct archive *a, const void **buff, 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 reading until we have accumulated enough data. */ - while (state->avail + state->client_avail < min) { - if (state->next > state->buffer && - state->next + min > state->buffer + state->buffer_size && - state->avail > 0) { - memmove(state->buffer, state->next, state->avail); - state->next = state->buffer; - } - if (state->client_avail > 0) { - memcpy(state->next + state->avail, state->client_next, - state->client_avail); - state->client_next += state->client_avail; - state->avail += state->client_avail; - state->client_avail = 0; - } - /* - * It seems to me that const void ** and const char ** - * should be compatible, but they aren't, hence the cast. - */ - bytes_read = (a->client_reader)(a, a->client_data, - (const void **)&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; - break; - } - a->raw_position += bytes_read; - state->client_total = bytes_read; - state->client_avail = state->client_total; - state->client_next = state->client_buff; - } - - /* Common case: If client buffer suffices, use that. */ - if (state->avail == 0) { + /* + * Try to satisfy the request directly from the client + * buffer. We can do this if all of the data in the copy + * buffer was copied from the current client buffer. This + * also covers the case where the copy buffer is empty and + * the client buffer has all the data we need. + */ + if (state->client_total >= state->client_avail + state->avail + && state->client_avail + state->avail >= min) { + state->client_avail += state->avail; + state->client_next -= state->avail; + state->avail = 0; + state->next = state->buffer; *buff = state->client_next; return (state->client_avail); } - /* Add in bytes from client buffer as necessary to meet the minimum. */ - if (min > state->avail + state->client_avail) - min = state->avail + state->client_avail; - if (state->avail < min) { - memcpy(state->next + state->avail, state->client_next, - min - state->avail); - state->client_next += min - state->avail; - state->client_avail -= min - state->avail; - state->avail = min; + /* + * If we can't use client buffer, we'll have to use copy buffer. + */ + + /* 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; + } + + /* Collect data in copy buffer to fulfill request. */ + while (state->avail < min) { + /* Copy data from client buffer to our copy buffer. */ + if (state->client_avail > 0) { + /* 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); + state->client_next += tocopy; + state->client_avail -= tocopy; + state->avail += tocopy; + } else { + /* There is no more client data: fetch more. */ + /* + * It seems to me that const void ** and const + * char ** should be compatible, but they + * aren't, hence the cast. + */ + bytes_read = (a->client_reader)(a, a->client_data, + (const void **)&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; + break; + } + a->raw_position += bytes_read; + state->client_total = bytes_read; + state->client_avail = state->client_total; + state->client_next = state->client_buff; + } } *buff = state->next; @@ -208,11 +228,9 @@ archive_decompressor_none_read_ahead(struct archive *a, const void **buff, } /* - * Mark the appropriate data as used. Note that the request here could - * be much smaller than the size of the previous read_ahead request, but - * typically it won't be. I make an attempt to go back to reading straight - * from the client buffer in case some end-of-block alignment mismatch forced - * me to combine writes above. + * 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 *a, size_t request) @@ -221,21 +239,11 @@ archive_decompressor_none_read_consume(struct archive *a, size_t request) state = a->compression_data; if (state->avail > 0) { + /* Read came from copy buffer. */ state->next += request; state->avail -= request; - /* - * Rollback state->client_next if we can so that future - * reads come straight from the client buffer and we - * avoid copying more data into our buffer. - */ - if (state->avail <= - (size_t)(state->client_next - state->client_buff)) { - state->client_next -= state->avail; - state->client_avail += state->avail; - state->avail = 0; - state->next = state->buffer; - } } else { + /* Read came from client buffer. */ state->client_next += request; state->client_avail -= request; } diff --git a/contrib/libarchive/archive_read_support_format_all.c b/contrib/libarchive/archive_read_support_format_all.c index c7e0342163..3f09482449 100644 --- a/contrib/libarchive/archive_read_support_format_all.c +++ b/contrib/libarchive/archive_read_support_format_all.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_all.c,v 1.4 2004/05/27 21:27:42 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_all.c,v 1.6 2005/01/25 06:07:28 kientzle Exp $"); #include "archive.h" @@ -33,6 +33,8 @@ int archive_read_support_format_all(struct archive *a) { archive_read_support_format_cpio(a); + archive_read_support_format_iso9660(a); archive_read_support_format_tar(a); + archive_read_support_format_zip(a); return (ARCHIVE_OK); } diff --git a/contrib/libarchive/archive_read_support_format_cpio.c b/contrib/libarchive/archive_read_support_format_cpio.c index 2447cd1e11..60471503b1 100644 --- a/contrib/libarchive/archive_read_support_format_cpio.c +++ b/contrib/libarchive/archive_read_support_format_cpio.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_cpio.c,v 1.11 2004/08/14 03:45:45 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_cpio.c,v 1.13 2005/04/06 04:19:30 kientzle Exp $"); #include @@ -142,6 +142,7 @@ archive_read_support_format_cpio(struct archive *a) 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) @@ -161,8 +162,11 @@ archive_read_format_cpio_bid(struct archive *a) cpio = *(a->pformat_data); bid = 0; bytes_read = (a->compression_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); + return (-1); p = h; if (memcmp(p, "070707", 6) == 0) { diff --git a/contrib/libarchive/archive_read_support_format_iso9660.c b/contrib/libarchive/archive_read_support_format_iso9660.c new file mode 100644 index 0000000000..4cae32bf4f --- /dev/null +++ b/contrib/libarchive/archive_read_support_format_iso9660.c @@ -0,0 +1,1001 @@ +/*- + * Copyright (c) 2003-2004 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_iso9660.c,v 1.8 2005/04/06 04:19:30 kientzle Exp $"); + +#include + +#include +/* #include */ /* See archive_platform.h */ +#include +#include +#include +#include +#include + +#include "archive.h" +#include "archive_entry.h" +#include "archive_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 PVD. */ +struct iso9660_primary_volume_descriptor { + unsigned char type[1]; + char id[5]; + unsigned char version[1]; + char reserved1[1]; + char system_id[32]; + char volume_id[32]; + char reserved2[8]; + char volume_space_size[8]; + char reserved3[32]; + char volume_set_size[4]; + char volume_sequence_number[4]; + char logical_block_size[4]; + char path_table_size[8]; + char type_1_path_table[4]; + char opt_type_1_path_table[4]; + char type_m_path_table[4]; + char opt_type_m_path_table[4]; + char root_directory_record[34]; + char volume_set_id[128]; + char publisher_id[128]; + char preparer_id[128]; + char application_id[128]; + char copyright_file_id[37]; + char abstract_file_id[37]; + char bibliographic_file_id[37]; + char creation_date[17]; + char modification_date[17]; + char expiration_date[17]; + char effective_date[17]; + char file_structure_version[1]; + char reserved4[1]; + char application_data[512]; +}; + +/* Structure of an on-disk directory record. */ +struct iso9660_directory_record { + unsigned char length[1]; + unsigned char ext_attr_length[1]; + unsigned char extent[8]; + unsigned char size[8]; + char date[7]; + unsigned char flags[1]; + unsigned char file_unit_size[1]; + unsigned char interleave[1]; + unsigned char volume_sequence_number[4]; + unsigned char name_len[1]; + char name[1]; +}; + + +/* + * 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. */ + 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 + int bid; /* If non-zero, return this as our bid. */ + 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; + ssize_t entry_bytes_remaining; +}; + +static void add_entry(struct iso9660 *iso9660, struct file_info *file); +static int archive_read_format_iso9660_bid(struct archive *); +static int archive_read_format_iso9660_cleanup(struct archive *); +static int archive_read_format_iso9660_read_data(struct archive *, + const void **, size_t *, off_t *); +static int archive_read_format_iso9660_read_header(struct archive *, + struct archive_entry *); +static const char *build_pathname(struct archive_string *, struct file_info *); +static void dump_isodirrec(FILE *, const struct iso9660_directory_record *); +static time_t isodate17(const void *); +static time_t isodate7(const void *); +static int isPVD(struct iso9660 *, const char *); +static struct file_info *next_entry(struct iso9660 *); +static int next_entry_seek(struct archive *a, struct iso9660 *iso9660, + struct file_info **pfile); +static struct file_info * + parse_file_info(struct iso9660 *iso9660, + struct file_info *parent, + const struct iso9660_directory_record *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 int toi(const void *p, int n); + +int +archive_read_support_format_iso9660(struct archive *a) +{ + struct iso9660 *iso9660; + int r; + + iso9660 = malloc(sizeof(*iso9660)); + memset(iso9660, 0, sizeof(*iso9660)); + iso9660->magic = ISO9660_MAGIC; + iso9660->bid = -1; /* We haven't yet bid. */ + + r = __archive_read_register_format(a, + iso9660, + archive_read_format_iso9660_bid, + archive_read_format_iso9660_read_header, + archive_read_format_iso9660_read_data, + NULL, + 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 *a) +{ + struct iso9660 *iso9660; + ssize_t bytes_read; + const void *h; + const char *p; + + iso9660 = *(a->pformat_data); + + if (iso9660->bid >= 0) + return (iso9660->bid); + + /* + * 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->compression_read_ahead)(a, &h, 32768 + 8*2048); + if (bytes_read < 32768 + 8*2048) + return (iso9660->bid = -1); + p = (const 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) { + iso9660->bid = isPVD(iso9660, p); + if (iso9660->bid > 0) + return (iso9660->bid); + if (*p == '\xff') /* End-of-volume-descriptor marker. */ + break; + } + + /* We didn't find a valid PVD; return a bid of zero. */ + iso9660->bid = 0; + return (iso9660->bid); +} + +static int +isPVD(struct iso9660 *iso9660, const char *h) +{ + const struct iso9660_primary_volume_descriptor *voldesc; + struct file_info *file; + + if (h[0] != 1) + return (0); + if (memcmp(h+1, "CD001", 5) != 0) + return (0); + + + voldesc = (const struct iso9660_primary_volume_descriptor *)h; + iso9660->logical_block_size = toi(&voldesc->logical_block_size, 2); + + /* Store the root directory in the pending list. */ + file = parse_file_info(iso9660, NULL, + (struct iso9660_directory_record *)&voldesc->root_directory_record); + add_entry(iso9660, file); + return (48); +} + +static int +archive_read_format_iso9660_read_header(struct archive *a, + struct archive_entry *entry) +{ + struct stat st; + struct iso9660 *iso9660; + struct file_info *file; + ssize_t bytes_read; + int r; + + iso9660 = *(a->pformat_data); + + if (iso9660->seenRockridge) { + a->archive_format = ARCHIVE_FORMAT_ISO9660_ROCKRIDGE; + a->archive_format_name = "ISO9660 with Rockridge extensions"; + } else { + a->archive_format = ARCHIVE_FORMAT_ISO9660; + a->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. */ + memset(&st, 0, sizeof(st)); + st.st_mode = file->mode; + st.st_uid = file->uid; + st.st_gid = file->gid; + st.st_nlink = file->nlinks; + st.st_ino = file->inode; + st.st_mtime = file->mtime; + st.st_ctime = file->ctime; + st.st_atime = file->atime; + st.st_size = iso9660->entry_bytes_remaining; + archive_entry_copy_stat(entry, &st); + archive_string_empty(&iso9660->pathname); + archive_entry_set_pathname(entry, + build_pathname(&iso9660->pathname, file)); + if (file->symlink.s != NULL) + archive_entry_set_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_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 (S_ISDIR(st.st_mode)) { + 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->compression_read_ahead)(a, &block, step); + if (bytes_read < step) { + archive_set_error(a, 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->compression_read_consume)(a, bytes_read); + iso9660->current_position += bytes_read; + iso9660->entry_bytes_remaining -= bytes_read; + for (p = block; + *p != 0 && p < (const unsigned char *)block + bytes_read; + p += *p) { + const struct iso9660_directory_record *dr + = (const struct iso9660_directory_record *)p; + struct file_info *child; + + /* Skip '.' entry. */ + if (dr->name_len[0] == 1 + && dr->name[0] == '\0') + continue; + /* Skip '..' entry. */ + if (dr->name_len[0] == 1 + && dr->name[0] == '\001') + continue; + child = parse_file_info(iso9660, file, dr); + add_entry(iso9660, child); + } + } + } + + release_file(iso9660, file); + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_read_data(struct archive *a, + const void **buff, size_t *size, off_t *offset) +{ + ssize_t bytes_read; + struct iso9660 *iso9660; + + iso9660 = *(a->pformat_data); + if (iso9660->entry_bytes_remaining <= 0) { + *buff = NULL; + *size = 0; + *offset = iso9660->entry_sparse_offset; + return (ARCHIVE_EOF); + } + + bytes_read = (a->compression_read_ahead)(a, buff, 1); + 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->compression_read_consume)(a, bytes_read); + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_cleanup(struct archive *a) +{ + struct iso9660 *iso9660; + struct file_info *file; + + iso9660 = *(a->pformat_data); + while ((file = next_entry(iso9660)) != NULL) + release_file(iso9660, file); + archive_string_free(&iso9660->pathname); + archive_string_free(&iso9660->previous_pathname); + free(iso9660); + *(a->pformat_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 struct iso9660_directory_record *isodirrec) +{ + struct file_info *file; + + /* 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 = malloc(sizeof(*file)); + memset(file, 0, sizeof(*file)); + file->parent = parent; + if (parent != NULL) + parent->refcount++; + file->offset = toi(isodirrec->extent, 4) + * iso9660->logical_block_size; + file->size = toi(isodirrec->size, 4); + file->mtime = isodate7(isodirrec->date); + file->ctime = file->atime = file->mtime; + file->name = malloc(isodirrec->name_len[0] + 1); + memcpy(file->name, isodirrec->name, isodirrec->name_len[0]); + file->name[(int)isodirrec->name_len[0]] = '\0'; + if (isodirrec->flags[0] & 0x02) + file->mode = S_IFDIR | 0700; + else + file->mode = S_IFREG | 0400; + + /* Rockridge extensions overwrite information from above. */ + { + const unsigned char *rr_start, *rr_end; + rr_end = (const unsigned char *)isodirrec + + isodirrec->length[0]; + rr_start = isodirrec->name + isodirrec->name_len[0]; + if ((isodirrec->name_len[0] & 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 ((isodirrec->flags[0] & ~0x02) != 0) { + fprintf(stderr, "\n ** Unrecognized flag: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (toi(isodirrec->volume_sequence_number, 2) != 1) { + fprintf(stderr, "\n ** Unrecognized sequence number: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (isodirrec->file_unit_size[0] != 0) { + fprintf(stderr, "\n ** Unexpected file unit size: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (isodirrec->interleave[0] != 0) { + fprintf(stderr, "\n ** Unexpected interleave: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (isodirrec->ext_attr_length[0] != 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 = malloc(new_size * sizeof(new_pending_files[0])); + 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 = 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] == '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, 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, 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 *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. */ + while (iso9660->current_position < offset) { + ssize_t step = offset - iso9660->current_position; + ssize_t bytes_read; + const void *buff; + + if (step > iso9660->logical_block_size) + step = iso9660->logical_block_size; + bytes_read = (a->compression_read_ahead)(a, &buff, step); + if (bytes_read <= 0) { + release_file(iso9660, file); + return (ARCHIVE_FATAL); + } + if (bytes_read > step) + bytes_read = step; + iso9660->current_position += bytes_read; + (a->compression_read_consume)(a, bytes_read); + } + + /* 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->compression_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->compression_read_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 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 void *p) +{ + struct tm tm; + const unsigned char *v = (const unsigned char *)p; + int offset; + 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 timezone offset, in 1/4-hour increments. */ + offset = ((const signed char *)p)[6]; + if (offset > -48 && offset < 52) { + tm.tm_hour -= offset / 4; + tm.tm_min -= (offset % 4) * 15; + } + return (timegm(&tm)); +} + +static time_t +isodate17(const void *p) +{ + struct tm tm; + const unsigned char *v = (const unsigned char *)p; + int offset; + 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 timezone offset, in 1/4-hour increments. */ + offset = ((const signed char *)p)[16]; + if (offset > -48 && offset < 52) { + tm.tm_hour -= offset / 4; + tm.tm_min -= (offset % 4) * 15; + } + return (timegm(&tm)); +} + +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 struct iso9660_directory_record *isodirrec) +{ + fprintf(out, " l %d,", isodirrec->length[0]); + fprintf(out, " a %d,", isodirrec->ext_attr_length[0]); + fprintf(out, " ext 0x%x,", toi(isodirrec->extent, 4)); + fprintf(out, " s %d,", toi(isodirrec->size, 4)); + fprintf(out, " f 0x%02x,", isodirrec->flags[0]); + fprintf(out, " u %d,", isodirrec->file_unit_size[0]); + fprintf(out, " ilv %d,", isodirrec->interleave[0]); + fprintf(out, " seq %d,", toi(isodirrec->volume_sequence_number,2)); + fprintf(out, " nl %d:", isodirrec->name_len[0]); + fprintf(out, " `%.*s'", isodirrec->name_len[0], isodirrec->name); +} diff --git a/contrib/libarchive/archive_read_support_format_tar.c b/contrib/libarchive/archive_read_support_format_tar.c index 40eddf10a7..8a83c45db8 100644 --- a/contrib/libarchive/archive_read_support_format_tar.c +++ b/contrib/libarchive/archive_read_support_format_tar.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_tar.c,v 1.28 2004/10/27 05:15:23 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_tar.c,v 1.32 2005/04/06 04:19:30 kientzle Exp $"); #include #include @@ -33,6 +33,7 @@ __FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_tar.c,v 1.28 #include #include #include +#include #include "archive.h" #include "archive_entry.h" @@ -215,6 +216,7 @@ archive_read_support_format_tar(struct archive *a) archive_read_format_tar_bid, archive_read_format_tar_read_header, archive_read_format_tar_read_data, + NULL, archive_read_format_tar_cleanup); if (r != ARCHIVE_OK) @@ -295,8 +297,14 @@ archive_read_format_tar_bid(struct archive *a) } /* If it's an end-of-archive mark, we can handle it. */ - if ((*(const char *)h) == 0 && archive_block_is_null(h)) - return (bid + 1); + if ((*(const char *)h) == 0 && archive_block_is_null(h)) { + /* If it's a known tar file, end-of-archive is definite. */ + if ((a->archive_format & ARCHIVE_FORMAT_BASE_MASK) == + ARCHIVE_FORMAT_TAR) + return (512); + /* Empty archive? */ + return (1); + } /* If it's not an end-of-archive mark, it must have a valid checksum.*/ if (!checksum(a, h)) @@ -321,6 +329,7 @@ archive_read_format_tar_bid(struct archive *a) !( 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]) { @@ -1012,8 +1021,11 @@ pax_header(struct archive *a, struct tar *tar, struct archive_entry *entry, return (-1); line_length *= 10; line_length += *p - '0'; - if (line_length > 999999) - return (-1); + if (line_length > 999999) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Rejecting pax extended attribute > 1MB"); + return (ARCHIVE_WARN); + } p++; l--; } diff --git a/contrib/libarchive/archive_read_support_format_zip.c b/contrib/libarchive/archive_read_support_format_zip.c new file mode 100644 index 0000000000..3a5ef0d3d4 --- /dev/null +++ b/contrib/libarchive/archive_read_support_format_zip.c @@ -0,0 +1,793 @@ +/*- + * 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 + * 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_zip.c,v 1.5 2005/04/06 04:19:30 kientzle Exp $"); + +#include +#include +#include +#include +#include +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" + +struct zip { + /* entry_bytes_remaining is the number of bytes we expect. */ + off_t entry_bytes_remaining; + off_t entry_offset; + + /* These count the number of bytes actually read for the entry. */ + off_t entry_compressed_bytes_read; + off_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; + off_t uncompressed_size; + off_t compressed_size; + + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; +#ifdef HAVE_ZLIB_H + z_stream stream; +#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]; +}; + +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 *); +static int archive_read_format_zip_cleanup(struct archive *); +static int archive_read_format_zip_read_data(struct archive *, + const void **, size_t *, off_t *); +static int archive_read_format_zip_read_data_skip(struct archive *a); +static int archive_read_format_zip_read_header(struct archive *, + struct archive_entry *); +static int i2(const char *); +static int i4(const char *); +static unsigned int u2(const char *); +static unsigned int u4(const char *); +static uint64_t u8(const char *); +static int zip_read_data_deflate(struct archive *a, const void **buff, + size_t *size, off_t *offset); +static int zip_read_data_none(struct archive *a, const void **buff, + size_t *size, off_t *offset); +static int zip_read_file_header(struct archive *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 zip *zip; + int r; + + zip = malloc(sizeof(*zip)); + 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 *a) +{ + int bytes_read; + int bid = 0; + const void *h; + const char *p; + + if (a->archive_format == ARCHIVE_FORMAT_ZIP) + bid += 1; + + bytes_read = (a->compression_read_ahead)(a, &h, 4); + if (bytes_read < 4) + return (-1); + p = h; + + if (p[0] == 'P' && p[1] == 'K') { + bid += 16; + if (p[2] == '\001' && p[3] == '\002') + bid += 16; + else if (p[2] == '\003' && p[3] == '\004') + bid += 16; + else if (p[2] == '\005' && p[3] == '\006') + bid += 16; + else if (p[2] == '\007' && p[3] == '\010') + bid += 16; + } + return (bid); +} + +static int +archive_read_format_zip_read_header(struct archive *a, + struct archive_entry *entry) +{ + int bytes_read; + const void *h; + const char *signature; + struct zip *zip; + + a->archive_format = ARCHIVE_FORMAT_ZIP; + if (a->archive_format_name == NULL) + a->archive_format_name = "ZIP"; + + zip = *(a->pformat_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; + bytes_read = (a->compression_read_ahead)(a, &h, 4); + if (bytes_read < 4) + return (ARCHIVE_FATAL); + + signature = h; + if (signature[0] != 'P' || signature[1] != 'K') { + archive_set_error(a, 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_ERRNO_MISC, + "Bad ZIP file: Unexpected end-of-entry record"); + return (ARCHIVE_FATAL); + } + + archive_set_error(a, 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 *a, struct archive_entry *entry, + struct zip *zip) +{ + const struct zip_file_header *p; + const void *h; + int bytes_read; + struct stat st; + + bytes_read = + (a->compression_read_ahead)(a, &h, sizeof(struct zip_file_header)); + if (bytes_read < (int)sizeof(struct zip_file_header)) { + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + p = h; + + zip->version = p->version[0]; + zip->system = p->version[1]; + zip->flags = i2(p->flags); + zip->compression = i2(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 = i4(p->crc32); + zip->filename_length = i2(p->filename_length); + zip->extra_length = i2(p->extra_length); + zip->uncompressed_size = u4(p->uncompressed_size); + zip->compressed_size = u4(p->compressed_size); + + (a->compression_read_consume)(a, sizeof(struct zip_file_header)); + + + /* Read the filename. */ + bytes_read = (a->compression_read_ahead)(a, &h, zip->filename_length); + if (bytes_read < zip->filename_length) { + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + archive_string_ensure(&zip->pathname, zip->filename_length); + archive_strncpy(&zip->pathname, h, zip->filename_length); + (a->compression_read_consume)(a, zip->filename_length); + archive_entry_set_pathname(entry, zip->pathname.s); + + if (zip->pathname.s[archive_strlen(&zip->pathname) - 1] == '/') + zip->mode = S_IFDIR | 0777; + else + zip->mode = S_IFREG | 0777; + + /* Read the extra data. */ + bytes_read = (a->compression_read_ahead)(a, &h, zip->extra_length); + if (bytes_read < zip->extra_length) { + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + process_extra(h, zip); + (a->compression_read_consume)(a, zip->extra_length); + + /* Populate some additional entry fields: */ + memset(&st, 0, sizeof(st)); + st.st_mode = zip->mode; + st.st_uid = zip->uid; + st.st_gid = zip->gid; + st.st_mtime = zip->mtime; + st.st_ctime = zip->ctime; + st.st_atime = zip->atime; + st.st_size = zip->uncompressed_size; + archive_entry_copy_stat(entry, &st); + + zip->entry_bytes_remaining = zip->compressed_size; + zip->entry_offset = 0; + + /* 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_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 *a, + const void **buff, size_t *size, off_t *offset) +{ + int r; + struct zip *zip; + + zip = *(a->pformat_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 void *h; + const char *p; + int bytes_read = + (a->compression_read_ahead)(a, &h, 16); + if (bytes_read < 16) { + archive_set_error(a, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP end-of-file record"); + return (ARCHIVE_FATAL); + } + p = h; + zip->crc32 = i4(p + 4); + zip->compressed_size = u4(p + 8); + zip->uncompressed_size = u4(p + 12); + bytes_read = (a->compression_read_consume)(a, 16); + } + + /* Check file size, CRC against these values. */ + if (zip->compressed_size != zip->entry_compressed_bytes_read) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "ZIP compressed data is wrong size"); + return (ARCHIVE_WARN); + } + if (zip->uncompressed_size != zip->entry_uncompressed_bytes_read) { + archive_set_error(a, 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_ERRNO_MISC, + "ZIP data CRC error"); + return (ARCHIVE_WARN); + } +*/ + /* End-of-entry cleanup done. */ + zip->end_of_entry_cleanup = 1; + } + 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_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 *a, const void **buff, + size_t *size, off_t *offset) +{ + struct zip *zip; + ssize_t bytes_avail; + + zip = *(a->pformat_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->compression_read_ahead)(a, buff, 1); + if (bytes_avail <= 0) { + archive_set_error(a, 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->compression_read_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 *a, const void **buff, + size_t *size, off_t *offset) +{ + struct zip *zip; + ssize_t bytes_avail; + const void *compressed_buff; + int r; + + zip = *(a->pformat_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 + = malloc(zip->uncompressed_buffer_size); + if (zip->uncompressed_buffer == NULL) { + archive_set_error(a, ENOMEM, + "No memory for ZIP decompression"); + return (ARCHIVE_FATAL); + } + } + + /* If we haven't yet read any data, initialize the decompressor. */ + if (!zip->decompress_init) { + r = inflateInit2(&zip->stream, + -15 /* Don't check for zlib header */); + if (r != Z_OK) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Can't initialize ZIP decompression."); + return (ARCHIVE_FATAL); + } + 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->compression_read_ahead)(a, &compressed_buff, 1); + if (bytes_avail <= 0) { + archive_set_error(a, 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 = (void *)(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, ENOMEM, + "Out of memory for ZIP decompression"); + return (ARCHIVE_FATAL); + default: + archive_set_error(a, 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->compression_read_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 *a, const void **buff, + size_t *size, off_t *offset) +{ + int r; + + *buff = NULL; + *size = 0; + *offset = 0; + archive_set_error(a, 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 *a) +{ + struct zip *zip; + const void *buff = NULL; + ssize_t bytes_avail; + + zip = *(a->pformat_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) { + ssize_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. + */ + while (zip->entry_bytes_remaining > 0) { + bytes_avail = (a->compression_read_ahead)(a, &buff, 1); + if (bytes_avail <= 0) { + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file body"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > zip->entry_bytes_remaining) + bytes_avail = zip->entry_bytes_remaining; + (a->compression_read_consume)(a, bytes_avail); + zip->entry_bytes_remaining -= bytes_avail; + } + /* 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 *a) +{ + struct zip *zip; + + zip = *(a->pformat_data); + if (zip->uncompressed_buffer != NULL) + free(zip->uncompressed_buffer); + archive_string_free(&(zip->pathname)); + archive_string_free(&(zip->extra)); + free(zip); + *(a->pformat_data) = NULL; + return (ARCHIVE_OK); +} + +static int +i2(const char *p) +{ + return ((0xff & (int)p[0]) + 256 * (0xff & (int)p[1])); +} + + +static int +i4(const char *p) +{ + return ((0xffff & i2(p)) + 0x10000 * (0xffff & i2(p+2))); +} + +static unsigned int +u2(const char *p) +{ + return ((0xff & (unsigned int)p[0]) + 256 * (0xff & (unsigned int)p[1])); +} + +static unsigned int +u4(const char *p) +{ + return u2(p) + 0x10000 * u2(p+2); +} + +static uint64_t +u8(const char *p) +{ + return u4(p) + 0x100000000LL * u4(p+4); +} + +/* + * 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 = extra; + while (offset < zip->extra_length - 4) + { + unsigned short headerid = u2(p + offset); + unsigned short datasize = u2(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 = u8(p + offset); + if (datasize >= 16) + zip->compressed_size = u8(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: %d -> %d\n", + zip->mtime, i4(p + offset)); +#endif + if (datasize < 4) + break; + zip->mtime = i4(p + offset); + offset += 4; + datasize -= 4; + } + if (flags & 0x02) + { + if (datasize < 4) + break; + zip->atime = i4(p + offset); + offset += 4; + datasize -= 4; + } + if (flags & 0x04) + { + if (datasize < 4) + break; + zip->ctime = i4(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", + i2(p + offset), i2(p + offset + 2)); +#endif + if (datasize >= 2) + zip->uid = i2(p + offset); + if (datasize >= 4) + zip->gid = i2(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/contrib/libarchive/archive_string.c b/contrib/libarchive/archive_string.c index 72cac6e56f..2227afc148 100644 --- a/contrib/libarchive/archive_string.c +++ b/contrib/libarchive/archive_string.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_string.c,v 1.5 2004/08/14 03:45:45 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_string.c,v 1.6 2004/12/22 06:12:40 kientzle Exp $"); /* * Basic resizable string support, to simplify manipulating arbitrary-sized @@ -96,3 +96,18 @@ __archive_strappend_char(struct archive_string *as, char c) { return (__archive_string_append(as, &c, 1)); } + +struct archive_string * +__archive_strappend_int(struct archive_string *as, int d, int base) +{ + static const char *digits = "0123457890abcdef"; + + if (d < 0) { + __archive_strappend_char(as, '-'); + d = -d; + } + if (d >= base) + __archive_strappend_int(as, d/base, base); + __archive_strappend_char(as, digits[d % base]); + return (as); +} diff --git a/contrib/libarchive/archive_string.h b/contrib/libarchive/archive_string.h index 6bc6e08d05..b8b3b598f2 100644 --- a/contrib/libarchive/archive_string.h +++ b/contrib/libarchive/archive_string.h @@ -23,7 +23,7 @@ * (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.4 2004/11/05 05:32:04 kientzle Exp $ + * $FreeBSD: src/lib/libarchive/archive_string.h,v 1.6 2005/01/16 22:13:51 kientzle Exp $ * */ @@ -64,6 +64,11 @@ 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); diff --git a/contrib/libarchive/archive_string_sprintf.c b/contrib/libarchive/archive_string_sprintf.c index 424093551b..63a06d7248 100644 --- a/contrib/libarchive/archive_string_sprintf.c +++ b/contrib/libarchive/archive_string_sprintf.c @@ -25,13 +25,19 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_string_sprintf.c,v 1.6 2004/11/05 05:32:04 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_string_sprintf.c,v 1.7 2005/01/16 22:13:51 kientzle Exp $"); /* - * This uses 'printf' family functions, which can cause issues - * for size-critical applications. I've separated it out to make - * this issue clear. (Currently, it is called directly from within - * the core code, so it cannot easily be omitted.) + * 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 @@ -46,21 +52,78 @@ void __archive_string_vsprintf(struct archive_string *as, const char *fmt, va_list ap) { - size_t l; - va_list ap1; + char long_flag; + intmax_t s; /* Signed integer temp. */ + uintmax_t u; /* Unsigned integer temp. */ + const char *p, *p2; + + __archive_string_ensure(as, 64); if (fmt == NULL) { as->s[0] = 0; return; } - va_copy(ap1, ap); - l = vsnprintf(as->s, as->buffer_length, fmt, ap); - /* If output is bigger than the buffer, resize and try again. */ - if (l+1 >= as->buffer_length) { - __archive_string_ensure(as, l + 1); - l = vsnprintf(as->s, as->buffer_length, fmt, ap1); + 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); + } } - as->length = l; - va_end(ap1); } diff --git a/contrib/libarchive/archive_util.3 b/contrib/libarchive/archive_util.3 index c00c77ac4b..838ae214d0 100644 --- a/contrib/libarchive/archive_util.3 +++ b/contrib/libarchive/archive_util.3 @@ -22,9 +22,9 @@ .\" 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.2 2004/07/04 21:15:37 ru Exp $ +.\" $FreeBSD: src/lib/libarchive/archive_util.3,v 1.3 2005/01/08 19:51:04 kientzle Exp $ .\" -.Dd October 1, 2003 +.Dd January 8, 2005 .Dt archive_util 3 .Os .Sh NAME @@ -94,12 +94,34 @@ by .Fn archive_errno and .Fn archive_error_string . -This function is sometimes useful within I/O callbacks. +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 libarchive 3 , +.Xr printf 3 .Sh HISTORY The .Nm libarchive diff --git a/contrib/libarchive/archive_util.c b/contrib/libarchive/archive_util.c index 2778fe7cb0..2050bb02b5 100644 --- a/contrib/libarchive/archive_util.c +++ b/contrib/libarchive/archive_util.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_util.c,v 1.8 2004/08/14 03:45:45 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_util.c,v 1.9 2005/02/23 06:57:04 kientzle Exp $"); #include #include @@ -65,7 +65,7 @@ archive_error_string(struct archive *a) if (a->error != NULL && *a->error != '\0') return (a->error); else - return (NULL); + return ("(Empty error message)"); } diff --git a/contrib/libarchive/archive_write.3 b/contrib/libarchive/archive_write.3 index 37242f3cba..a5acc5a0e4 100644 --- a/contrib/libarchive/archive_write.3 +++ b/contrib/libarchive/archive_write.3 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2003-2004 Tim Kientzle +.\" Copyright (c) 2003-2005 Tim Kientzle .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,9 +22,9 @@ .\" 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.8 2004/11/05 05:26:30 kientzle Exp $ +.\" $FreeBSD: src/lib/libarchive/archive_write.3,v 1.11 2005/02/13 22:25:11 ru Exp $ .\" -.Dd October 1, 2003 +.Dd January 8, 2005 .Dt archive_write 3 .Os .Sh NAME @@ -73,7 +73,7 @@ .Ft int .Fn archive_write_set_format_ustar "struct archive *" .Ft int -.Fn archive_write_open "struct archive *" "void *client_data" "archive_write_archive_callback *" "archive_open_archive_callback *" "archive_close_archive_callback *" +.Fn archive_write_open "struct archive *" "void *client_data" "archive_open_archive_callback *" "archive_write_archive_callback *" "archive_close_archive_callback *" .Ft int .Fn archive_write_open_fd "struct archive *" "int fd" .Ft int @@ -196,40 +196,72 @@ Complete the archive and invoke the close callback. .It Fn archive_write_finish Invokes .Fn archive_write_close -if it wasn't invoked manually, then release all resources. +if it was not invoked manually, then release all resources. .El -.Pp -The callback functions are defined as follows: +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 ssize_t -.Fn archive_write_archive_callback "struct archive *" "void *client_data" "void *buffer" "size_t length" -.It .Ft typedef int .Fn archive_open_archive_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 int -.Fn archive_close_archive_callback "struct archive *" "void *client_data" +.Ft typedef ssize_t +.Fn archive_write_archive_callback "struct archive *" "void *client_data" "void *buffer" "size_t length" .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 tar archives to tape drives. -.Pp -More information about tar archive formats and blocking can be found -in the -.Xr tar 5 -manual page. +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_archive_callback "struct archive *" "void *client_data" +.El .Pp -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. +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 +regurn +.Cm ARCHIVE_FATAL. .Sh EXAMPLE The following sketch illustrates basic usage of the library. In this example, diff --git a/contrib/libarchive/archive_write.c b/contrib/libarchive/archive_write.c index bb8308de9b..8929c8133c 100644 --- a/contrib/libarchive/archive_write.c +++ b/contrib/libarchive/archive_write.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_write.c,v 1.13 2004/11/05 05:26:30 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write.c,v 1.14 2005/02/12 23:11:29 kientzle Exp $"); /* * This file contains the "essential" portions of the write API, that @@ -126,6 +126,7 @@ archive_write_open(struct archive *a, void *client_data, ret = ARCHIVE_OK; archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_NEW); + archive_string_empty(&a->error_string); a->state = ARCHIVE_STATE_HEADER; a->client_data = client_data; a->client_writer = writer; @@ -194,6 +195,7 @@ archive_write_header(struct archive *a, struct archive_entry *entry) archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA); + archive_string_empty(&a->error_string); /* Finish last entry. */ if (a->state & ARCHIVE_STATE_DATA) @@ -221,6 +223,7 @@ archive_write_data(struct archive *a, const void *buff, size_t s) { int ret; archive_check_magic(a, ARCHIVE_WRITE_MAGIC, ARCHIVE_STATE_DATA); + archive_string_empty(&a->error_string); ret = (a->format_write_data)(a, buff, s); return (ret == ARCHIVE_OK ? (ssize_t)s : -1); } diff --git a/contrib/libarchive/archive_write_open_file.c b/contrib/libarchive/archive_write_open_file.c index 26eeb80ab0..c12dd800bf 100644 --- a/contrib/libarchive/archive_write_open_file.c +++ b/contrib/libarchive/archive_write_open_file.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_file.c,v 1.8 2004/10/17 23:47:30 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_file.c,v 1.11 2005/03/13 01:47:31 kientzle Exp $"); #include #include @@ -51,13 +51,13 @@ archive_write_open_file(struct archive *a, const char *filename) { struct write_file_data *mine; - if (filename == NULL) { + if (filename == NULL || filename[0] == '\0') { mine = malloc(sizeof(*mine)); if (mine == NULL) { archive_set_error(a, ENOMEM, "No memory"); return (ARCHIVE_FATAL); } - mine->filename[0] = 0; + mine->filename[0] = '\0'; /* Record that we're using stdout. */ } else { mine = malloc(sizeof(*mine) + strlen(filename)); if (mine == NULL) { @@ -82,7 +82,7 @@ file_open(struct archive *a, void *client_data) mine = client_data; flags = O_WRONLY | O_CREAT | O_TRUNC; - if (*mine->filename != 0) { + if (mine->filename[0] != '\0') { mine->fd = open(mine->filename, flags, 0666); /* @@ -125,8 +125,15 @@ file_open(struct archive *a, void *client_data) return (ARCHIVE_FATAL); } - a->skip_file_dev = pst->st_dev; - a->skip_file_ino = pst->st_ino; + /* + * 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(pst->st_mode)) { + a->skip_file_dev = pst->st_dev; + a->skip_file_ino = pst->st_ino; + } return (ARCHIVE_OK); } @@ -152,7 +159,7 @@ file_close(struct archive *a, void *client_data) struct write_file_data *mine = client_data; (void)a; /* UNUSED */ - if (mine->fd >= 0) + if (mine->filename[0] != '\0') close(mine->fd); free(mine); return (ARCHIVE_OK); diff --git a/contrib/libarchive/archive_write_set_format_pax.c b/contrib/libarchive/archive_write_set_format_pax.c index 2ebf171281..bc0c8f5325 100644 --- a/contrib/libarchive/archive_write_set_format_pax.c +++ b/contrib/libarchive/archive_write_set_format_pax.c @@ -25,7 +25,7 @@ */ #include "archive_platform.h" -__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_pax.c,v 1.19 2004/11/05 05:26:30 kientzle Exp $"); +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_set_format_pax.c,v 1.26 2005/04/23 17:46:51 kientzle Exp $"); #include #include @@ -60,9 +60,8 @@ static int archive_write_pax_finish(struct archive *); static int archive_write_pax_finish_entry(struct archive *); static int archive_write_pax_header(struct archive *, struct archive_entry *); -static char *build_pax_attribute_name(const char *abbreviated, - struct archive_string *work); -static char *build_ustar_entry_name(char *dest, const char *src); +static char *build_pax_attribute_name(char *dest, const char *src); +static char *build_ustar_entry_name(char *dest, const char *src, const char *insert); static char *format_int(char *dest, int64_t); static int write_nulls(struct archive *, size_t); @@ -314,17 +313,17 @@ archive_write_pax_header(struct archive *a, struct archive_entry *entry_main; const char *linkname, *p; const char *hardlink; - const wchar_t *wp, *wp2, *wname_start; - int need_extension, oldstate, r, ret; + const wchar_t *wp, *wp2; + const char *suffix_start; + int need_extension, r, ret; struct pax *pax; const struct stat *st_main, *st_original; - struct archive_string pax_entry_name; char paxbuff[512]; char ustarbuff[512]; char ustar_entry_name[256]; + char pax_entry_name[256]; - archive_string_init(&pax_entry_name); need_extension = 0; pax = a->format_data; pax->written = 1; @@ -368,11 +367,11 @@ archive_write_pax_header(struct archive *a, */ wp = archive_entry_pathname_w(entry_main); p = archive_entry_pathname(entry_main); - if (wcslen(wp) <= 100) /* Short enough for just 'name' field */ - wname_start = wp; /* Record a zero-length prefix */ + if (strlen(p) <= 100) /* Short enough for just 'name' field */ + suffix_start = p; /* Record a zero-length prefix */ else /* Find the largest suffix that fits in 'name' field. */ - wname_start = wcschr(wp + wcslen(wp) - 100 - 1, '/'); + suffix_start = strchr(p + strlen(p) - 100 - 1, '/'); /* Find non-ASCII character, if any. */ wp2 = wp; @@ -383,11 +382,10 @@ archive_write_pax_header(struct archive *a, * If name is too long, or has non-ASCII characters, add * 'path' to pax extended attrs. */ - if (wname_start == NULL || wname_start - wp > 155 || - *wp2 != L'\0') { + if (suffix_start == NULL || suffix_start - p > 155 || *wp2 != L'\0') { add_pax_attr_w(&(pax->pax_header), "path", wp); archive_entry_set_pathname(entry_main, - build_ustar_entry_name(ustar_entry_name, p)); + build_ustar_entry_name(ustar_entry_name, p, NULL)); need_extension = 1; } @@ -466,7 +464,17 @@ archive_write_pax_header(struct archive *a, if (rdevmajor >= (1 << 18)) { add_pax_attr_int(&(pax->pax_header), "SCHILY.devmajor", rdevmajor); - archive_entry_set_rdevmajor(entry_main, (1 << 18) - 1); + /* + * 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; } @@ -477,7 +485,9 @@ archive_write_pax_header(struct archive *a, if (rdevminor >= (1 << 18)) { add_pax_attr_int(&(pax->pax_header), "SCHILY.devminor", rdevminor); - archive_entry_set_rdevminor(entry_main, (1 << 18) - 1); + /* Truncation is not necessary here, either. */ + /* archive_entry_set_rdevminor(entry_main, + rdevminor & ((1 << 18) - 1)); */ need_extension = 1; } } @@ -513,7 +523,7 @@ archive_write_pax_header(struct archive *a, * 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 generated an extended header, so + * already set (we're already generating an extended header, so * may as well include these). */ if (a->archive_format != ARCHIVE_FORMAT_TAR_PAX_RESTRICTED || @@ -626,14 +636,12 @@ archive_write_pax_header(struct archive *a, if (archive_strlen(&(pax->pax_header)) > 0) { struct stat st; struct archive_entry *pax_attr_entry; - const char *pax_attr_name; memset(&st, 0, sizeof(st)); pax_attr_entry = archive_entry_new(); p = archive_entry_pathname(entry_main); - pax_attr_name = build_pax_attribute_name(p, &pax_entry_name); - - archive_entry_set_pathname(pax_attr_entry, pax_attr_name); + archive_entry_set_pathname(pax_attr_entry, + build_pax_attribute_name(pax_entry_name, p)); st.st_size = archive_strlen(&(pax->pax_header)); st.st_uid = st_main->st_uid; if (st.st_uid >= 1 << 18) @@ -653,11 +661,10 @@ archive_write_pax_header(struct archive *a, pax_attr_entry, 'x', 1); archive_entry_free(pax_attr_entry); - archive_string_free(&pax_entry_name); /* Note that the 'x' header shouldn't ever fail to format */ if (ret != 0) { - const char *msg = "archive_write_header_pax: " + const char *msg = "archive_write_pax_header: " "'x' header failed?! This can't happen.\n"; write(2, msg, strlen(msg)); exit(1); @@ -672,17 +679,19 @@ archive_write_pax_header(struct archive *a, pax->entry_bytes_remaining = archive_strlen(&(pax->pax_header)); pax->entry_padding = 0x1ff & (- pax->entry_bytes_remaining); - oldstate = a->state; - a->state = ARCHIVE_STATE_DATA; - r = archive_write_data(a, pax->pax_header.s, + r = (a->compression_write)(a, pax->pax_header.s, archive_strlen(&(pax->pax_header))); - a->state = oldstate; if (r != ARCHIVE_OK) { /* If a write fails, we're pretty much toast. */ return (ARCHIVE_FATAL); } - - archive_write_pax_finish_entry(a); + /* 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. */ @@ -706,100 +715,198 @@ archive_write_pax_header(struct archive *a, /* * 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) +build_ustar_entry_name(char *dest, const char *src, const char *insert) { - const char *basename, *break_point, *prefix; - int basename_length, dirname_length, prefix_length; + const char *prefix, *prefix_end; + const char *suffix, *suffix_end; + const char *filename, *filename_end; + char *p; + size_t s; + 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. */ + s = strlen(src); + if (s < 100 && insert == NULL) { + strcpy(dest, src); + return (dest); + } - prefix = src; - basename = strrchr(src, '/'); - if (basename == NULL) { - basename = src; - prefix_length = 0; - basename_length = strlen(basename); - if (basename_length > 100) - basename_length = 100; - } else { - basename_length = strlen(basename); - if (basename_length > 100) - basename_length = 100; - dirname_length = basename - src; - - break_point = - strchr(src + dirname_length + basename_length - 101, '/'); - prefix_length = break_point - prefix - 1; - while (prefix_length > 155) { - prefix = strchr(prefix, '/') + 1; /* Drop 1st dir. */ - prefix_length = break_point - prefix - 1; + /* Step 1: Locate filename and enforce the length restriction. */ + filename_end = src + s; + /* 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. */ - strncpy(dest, prefix, basename - prefix + basename_length); - dest[basename - prefix + basename_length] = '\0'; + 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) { + if (prefix_end > prefix || suffix_end > suffix) + *p++ = '/'; + 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 suggests 'dirname'/PaxHeaders/'basename' + * reasonable name: SUSv3 suggests 'dirname'/PaxHeader/'filename' * * Joerg Schiling 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. - * This is a tempting argument, but I'm not entirely convinced. + * Based on this, 'star' uses /tmp/PaxHeader/'basename' as the ustar header + * name. This is a tempting argument, but I'm not entirely convinced. + * I'm also uncomfortable with the fact that "/tmp" is a Unix-ism. * - * Of course, adding "PaxHeaders/" might force the name to be too big. - * Here, I start from the (possibly already-trimmed) name used in the - * main ustar header and delete some additional early path elements to - * fit in the extra "PaxHeader/" part. + * The following routine implements the SUSv3 recommendation, and is + * much simpler because build_ustar_entry_name() above already does + * most of the work (we just need to give it an extra path element to + * insert and handle a few pathological cases). */ static char * -build_pax_attribute_name(const char *abbreviated, /* ustar-compat name */ - struct archive_string *work) +build_pax_attribute_name(char *dest, const char *src) { - const char *basename, *break_point, *prefix; - int prefix_length, suffix_length; + char *p; - /* - * This is much simpler because I know that "abbreviated" is - * already small enough; I just need to determine if it needs - * any further trimming to fit the "PaxHeader/" portion. - */ + /* Handle the null filename case. */ + if (src == NULL || *src == '\0') { + strcpy(dest, "PaxHeader/blank"); + return (dest); + } - /* Identify the final prefix and suffix portions. */ - prefix = abbreviated; /* First guess: prefix starts at beginning */ - if (strlen(abbreviated) > 100) { - break_point = strchr(prefix + strlen(prefix) - 101, '/'); - prefix_length = break_point - prefix - 1; - suffix_length = strlen(break_point + 1); - /* - * The next loop keeps trimming until "/PaxHeader/" can - * be added to either the prefix or the suffix. - */ - while (prefix_length > 144 && suffix_length > 89) { - prefix = strchr(prefix, '/') + 1; /* Drop 1st dir. */ - prefix_length = break_point - prefix - 1; + /* Prune final '/' and other unwanted final elements. */ + p = dest + strlen(dest); + for (;;) { + /* Ends in "/", remove the '/' */ + if (p > dest && p[-1] == '/') { + *--p = '\0'; + continue; } + /* Ends in "/.", remove the '.' */ + if (p > dest + 1 && p[-1] == '.' + && p[-2] == '/') { + *--p = '\0'; + continue; + } + break; } - archive_string_empty(work); - basename = strrchr(prefix, '/'); - if (basename == NULL) { - archive_strcpy(work, "PaxHeader/"); - archive_strcat(work, prefix); - } else { - basename++; - archive_strncpy(work, prefix, basename - prefix); - archive_strcat(work, "PaxHeader/"); - archive_strcat(work, basename); + /* Pathological case: After above, there was nothing left. */ + if (p == dest) { + strcpy(dest, "/PaxHeader/rootdir"); + return (dest); } - return (work->s); + /* Convert unadorned "." into "dot" */ + if (*src == '.' && src[1] == '\0') { + strcpy(dest, "PaxHeader/currentdir"); + return (dest); + } + + /* General case: build a ustar-compatible name adding "/PaxHeader/". */ + build_ustar_entry_name(dest, src, "PaxHeader"); + + return (dest); } /* Write two null blocks for the end of archive */ diff --git a/contrib/libarchive/libarchive-formats.5 b/contrib/libarchive/libarchive-formats.5 index 8b9adcf2c4..36f490259c 100644 --- a/contrib/libarchive/libarchive-formats.5 +++ b/contrib/libarchive/libarchive-formats.5 @@ -22,7 +22,7 @@ .\" 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.2 2004/07/04 21:15:37 ru Exp $ +.\" $FreeBSD: src/lib/libarchive/libarchive-formats.5,v 1.6 2005/02/23 06:58:09 kientzle Exp $ .\" .Dd April 27, 2004 .Dt libarchive-formats 3 @@ -36,7 +36,7 @@ The 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 filesystem object, such as a file, directory, +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 @@ -179,8 +179,8 @@ The SVR4 format can optionally include a CRC of the file contents, although libarchive does not currently verify this CRC. .El .Pp -Cpio is an old format that was widely used because of it's simplicity -and it's support for very long filenames. +Cpio is an old format that was widely used because of its simplicity +and its support for very long filenames. Unfortunately, it has many limitations that make it unsuitable for widespread use. Only the POSIX format permits files over 4GB, and its 18-bit @@ -196,7 +196,7 @@ implementations. A .Dq shell archive is a shell script that, when executed on a POSIX-compliant -system, will recreate a collection of filesystem objects. +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 @@ -220,8 +220,24 @@ 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. .Sh SEE ALSO .Xr cpio 1 , +.Xr mkisofs 1 , .Xr shar 1 , .Xr tar 1 , +.Xr zip 1 , +.Xr zlib 3 , .Xr tar 5 diff --git a/contrib/libarchive/libarchive.3 b/contrib/libarchive/libarchive.3 index 0438312790..0bba953cad 100644 --- a/contrib/libarchive/libarchive.3 +++ b/contrib/libarchive/libarchive.3 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2003-2004 Tim Kientzle +.\" Copyright (c) 2003-2005 Tim Kientzle .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/lib/libarchive/libarchive.3,v 1.4 2004/07/04 21:15:37 ru Exp $ +.\" $FreeBSD: src/lib/libarchive/libarchive.3,v 1.7 2005/01/21 20:50:38 ru Exp $ .\" -.Dd October 1, 2003 +.Dd January 8, 2005 .Dt LIBARCHIVE 3 .Os .Sh NAME @@ -281,7 +281,7 @@ and functions can be used to obtain more information. .Sh ENVIRONMENT There are character set conversions within the -.Xr archive_entry +.Xr archive_entry 3 functions that are impacted by the currently-selected locale. .Sh SEE ALSO .Xr tar 1 , @@ -305,8 +305,8 @@ library was written by 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, sparse -file information, or the arbitrary key/value pairs that can appear in +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 @@ -315,6 +315,3 @@ stored in an is supported by all formats. For example, cpio formats do not support nanosecond timestamps; old tar formats do not support large device numbers. -.Pp -The library cannot write pre-POSIX tar archives. -The support for GNU tar format is incomplete. diff --git a/contrib/libarchive/tar.5 b/contrib/libarchive/tar.5 index 4aa0a8f388..97ab644f6e 100644 --- a/contrib/libarchive/tar.5 +++ b/contrib/libarchive/tar.5 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/lib/libarchive/tar.5,v 1.9 2004/08/07 17:24:50 kientzle Exp $ +.\" $FreeBSD: src/lib/libarchive/tar.5,v 1.11 2005/02/13 23:45:46 ru Exp $ .\" .Dd May 20, 2004 .Dt TAR 5 @@ -34,7 +34,7 @@ The .Nm archive format collects any number of files, directories, and other -filesystem objects (symbolic links, device nodes, etc.) into a single +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 @@ -43,7 +43,7 @@ a general packaging mechanism. A .Nm archive consists of a series of 512-byte records. -Each filesystem object requires a header record which stores basic metadata +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 @@ -384,7 +384,7 @@ Vendor-specific attributes used by Joerg Schilling's implementation. .It Cm SCHILY.acl.access , Cm SCHILY.acl.default Stores the access and default ACLs as textual strings in a format -that's an extension of the format specified by POSIX.1e draft 17. +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 @@ -570,7 +570,7 @@ 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's no longer used), or +header extensions (an older format that is no longer used), or .Dq sparse extensions. .It "V" -- 2.41.0