diff options
Diffstat (limited to 'libarchive/test/test_write_format_zip_large.c')
| -rw-r--r-- | libarchive/test/test_write_format_zip_large.c | 474 | 
1 files changed, 474 insertions, 0 deletions
diff --git a/libarchive/test/test_write_format_zip_large.c b/libarchive/test/test_write_format_zip_large.c new file mode 100644 index 000000000000..d73dd62dee19 --- /dev/null +++ b/libarchive/test/test_write_format_zip_large.c @@ -0,0 +1,474 @@ +/*- + * Copyright (c) 2003-2007,2013 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD$"); + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* + * This is a somewhat tricky test that verifies the ability to + * write and read very large entries to zip archives. + * + * See test_tar_large.c for more information about the machinery + * being used here. + */ + +static size_t nullsize; +static void *nulldata; + +struct fileblock { +	struct fileblock *next; +	int	size; +	void *buff; +	int64_t gap_size; /* Size of following gap */ +}; + +struct fileblocks { +	int64_t filesize; +	int64_t fileposition; +	int64_t gap_remaining; +	void *buff; +	struct fileblock *first; +	struct fileblock *current; +	struct fileblock *last; +}; + +/* The following size definitions simplify things below. */ +#define KB ((int64_t)1024) +#define MB ((int64_t)1024 * KB) +#define GB ((int64_t)1024 * MB) +#define TB ((int64_t)1024 * GB) + +static int64_t	memory_read_skip(struct archive *, void *, int64_t request); +static ssize_t	memory_read(struct archive *, void *, const void **buff); +static ssize_t	memory_write(struct archive *, void *, const void *, size_t); + +static int16_t le16(const void *_p) { +	const uint8_t *p = _p; +	return (0xff & (int16_t)p[0]) | ((0xff & (int16_t)p[1]) << 8); +} + +static int32_t le32(const void *_p) { +	const uint8_t *p = _p; +	int32_t v = 0xffff & (int32_t)le16(_p); +	return v + ((0xffff & (int32_t)le16(p + 2)) << 16); +} + +static int64_t le64(const void *_p) { +	const uint8_t *p = _p; +	int64_t v = 0xffffffff & (int64_t)le32(_p); +	return v + ((0xffffffff & (int64_t)le32(p + 4)) << 32); +} + +static ssize_t +memory_write(struct archive *a, void *_private, const void *buff, size_t size) +{ +	struct fileblocks *private = _private; +	struct fileblock *block; + +	(void)a; + +	if ((const char *)nulldata <= (const char *)buff +	    && (const char *)buff < (const char *)nulldata + nullsize) { +		/* We don't need to store a block of gap data. */ +		private->last->gap_size += (int64_t)size; +	} else { +		/* Yes, we're assuming the very first write is metadata. */ +		/* It's header or metadata, copy and save it. */ +		block = (struct fileblock *)malloc(sizeof(*block)); +		memset(block, 0, sizeof(*block)); +		block->size = (int)size; +		block->buff = malloc(size); +		memcpy(block->buff, buff, size); +		if (private->last == NULL) { +			private->first = private->last = block; +		} else { +			private->last->next = block; +			private->last = block; +		} +		block->next = NULL; +	} +	private->filesize += size; +	return ((long)size); +} + +static ssize_t +memory_read(struct archive *a, void *_private, const void **buff) +{ +	struct fileblocks *private = _private; +	ssize_t size; + +	(void)a; + +	while (private->current != NULL && private->buff == NULL && private->gap_remaining == 0) { +		private->current = private->current->next; +		if (private->current != NULL) { +			private->buff = private->current->buff; +			private->gap_remaining = private->current->gap_size; +		} +	} + +	if (private->current == NULL) +		return (0); + +	/* If there's real data, return that. */ +	if (private->buff != NULL) { +		*buff = private->buff; +		size = ((char *)private->current->buff + private->current->size) +		    - (char *)private->buff; +		private->buff = NULL; +		private->fileposition += size; +		return (size); +	} + +	/* Big gap: too big to return all at once, so just return some. */ +	if (private->gap_remaining > (int64_t)nullsize) { +		private->gap_remaining -= nullsize; +		*buff = nulldata; +		private->fileposition += nullsize; +		return (nullsize); +	} + +	/* Small gap: finish the gap and prep for next block. */ +	if (private->gap_remaining > 0) { +		size = (ssize_t)private->gap_remaining; +		*buff = nulldata; +		private->gap_remaining = 0; +		private->fileposition += size; + +		private->current = private->current->next; +		if (private->current != NULL) { +			private->buff = private->current->buff; +			private->gap_remaining = private->current->gap_size; +		} + +		return (size); +	} +	fprintf(stderr, "\n\n\nInternal failure\n\n\n"); +	exit(1); +} + +static int +memory_read_open(struct archive *a, void *_private) +{ +	struct fileblocks *private = _private; + +	(void)a; /* UNUSED */ + +	private->current = private->first; +	private->fileposition = 0; +	if (private->current != NULL) { +		private->buff = private->current->buff; +		private->gap_remaining = private->current->gap_size; +	} +	return (ARCHIVE_OK); +} + +static int64_t +memory_read_seek(struct archive *a, void *_private, int64_t offset, int whence) +{ +	struct fileblocks *private = _private; + +	(void)a; +	if (whence == SEEK_END) { +		offset = private->filesize + offset; +	} else if (whence == SEEK_CUR) { +		offset = private->fileposition + offset; +	} + +	if (offset < 0) { +		fprintf(stderr, "\n\n\nInternal failure: negative seek\n\n\n"); +		exit(1); +	} + +	/* We've converted the request into a SEEK_SET. */ +	private->fileposition = offset; + +	/* Walk the block list to find the new position. */ +	offset = 0; +	private->current = private->first; +	while (private->current != NULL) { +		if (offset + private->current->size > private->fileposition) { +			/* Position is in this block. */ +			private->buff = (char *)private->current->buff +			    + private->fileposition - offset; +			private->gap_remaining = private->current->gap_size; +			return private->fileposition; +		} +		offset += private->current->size; +		if (offset + private->current->gap_size > private->fileposition) { +			/* Position is in this gap. */ +			private->buff = NULL; +			private->gap_remaining = private->current->gap_size +			    - (private->fileposition - offset); +			return private->fileposition; +		} +		offset += private->current->gap_size; +		/* Skip to next block. */ +		private->current = private->current->next; +	} +	if (private->fileposition == private->filesize) { +		return private->fileposition; +	} +	fprintf(stderr, "\n\n\nInternal failure: over-sized seek\n\n\n"); +	exit(1); +} + +static int64_t +memory_read_skip(struct archive *a, void *_private, int64_t skip) +{ +	struct fileblocks *private = _private; +	int64_t old_position = private->fileposition; +	int64_t new_position = memory_read_seek(a, _private, skip, SEEK_CUR); +	return (new_position - old_position); +} + +static struct fileblocks * +fileblocks_new(void) +{ +	struct fileblocks *fileblocks; + +	fileblocks = calloc(1, sizeof(struct fileblocks)); +	return fileblocks; +} + +static void +fileblocks_free(struct fileblocks *fileblocks) +{ +	while (fileblocks->first != NULL) { +		struct fileblock *b = fileblocks->first; +		fileblocks->first = fileblocks->first->next; +		free(b->buff); +		free(b); +	} +	free(fileblocks); +} + + +/* The sizes of the entries we're going to generate. */ +static int64_t test_sizes[] = { +	/* Test for 32-bit signed overflow. */ +	2 * GB - 1, 2 * GB, 2 * GB + 1, +	/* Test for 32-bit unsigned overflow. */ +	4 * GB - 1, 4 * GB, 4 * GB + 1, +	/* And beyond ... because we can. */ +	16 * GB - 1, 16 * GB, 16 * GB + 1, +	64 * GB - 1, 64 * GB, 64 * GB + 1, +	256 * GB - 1, 256 * GB, 256 * GB + 1, +	1 * TB, +	0 +}; + + +static void +verify_large_zip(struct archive *a, struct fileblocks *fileblocks) +{ +	char namebuff[64]; +	struct archive_entry *ae; +	int i; + +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_set_options(a, "zip:ignorecrc32")); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_set_open_callback(a, memory_read_open)); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_set_read_callback(a, memory_read)); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_set_skip_callback(a, memory_read_skip)); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_set_seek_callback(a, memory_read_seek)); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_set_callback_data(a, fileblocks)); +	assertEqualIntA(a, ARCHIVE_OK, archive_read_open1(a)); + +	/* +	 * Read entries back. +	 */ +	for (i = 0; test_sizes[i] > 0; i++) { +		assertEqualIntA(a, ARCHIVE_OK, +		    archive_read_next_header(a, &ae)); +		sprintf(namebuff, "file_%d", i); +		assertEqualString(namebuff, archive_entry_pathname(ae)); +		assertEqualInt(test_sizes[i], archive_entry_size(ae)); +	} +	assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); +	assertEqualString("lastfile", archive_entry_pathname(ae)); + +	assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + +	/* Close out the archive. */ +	assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); +} + +DEFINE_TEST(test_write_format_zip_large) +{ +	int i; +	char namebuff[64]; +	struct fileblocks *fileblocks = fileblocks_new(); +	struct archive_entry *ae; +	struct archive *a; +	const char *p; +	const char *cd_start, *zip64_eocd, *zip64_locator, *eocd; +	int64_t cd_size; +	char *buff; +	int64_t  filesize; +	size_t writesize, buffsize, s; + +	nullsize = (size_t)(1 * MB); +	nulldata = malloc(nullsize); +	memset(nulldata, 0xAA, nullsize); + +	/* +	 * Open an archive for writing. +	 */ +	a = archive_write_new(); +	archive_write_set_format_zip(a); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_write_set_options(a, "zip:compression=store")); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_write_set_options(a, "zip:fakecrc32")); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_write_set_bytes_per_block(a, 0)); /* No buffering. */ +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_write_open(a, fileblocks, NULL, memory_write, NULL)); + +	/* +	 * Write a series of large files to it. +	 */ +	for (i = 0; test_sizes[i] != 0; i++) { +		assert((ae = archive_entry_new()) != NULL); +		sprintf(namebuff, "file_%d", i); +		archive_entry_copy_pathname(ae, namebuff); +		archive_entry_set_mode(ae, S_IFREG | 0755); +		filesize = test_sizes[i]; +		archive_entry_set_size(ae, filesize); + +		assertEqualIntA(a, ARCHIVE_OK, +		    archive_write_header(a, ae)); +		archive_entry_free(ae); + +		/* +		 * Write the actual data to the archive. +		 */ +		while (filesize > 0) { +			writesize = nullsize; +			if ((int64_t)writesize > filesize) +				writesize = (size_t)filesize; +			assertEqualIntA(a, (int)writesize, +			    (int)archive_write_data(a, nulldata, writesize)); +			filesize -= writesize; +		} +	} + +	assert((ae = archive_entry_new()) != NULL); +	archive_entry_copy_pathname(ae, "lastfile"); +	archive_entry_set_mode(ae, S_IFREG | 0755); +	assertA(0 == archive_write_header(a, ae)); +	archive_entry_free(ae); + +	/* Close out the archive. */ +	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a)); +	assertEqualInt(ARCHIVE_OK, archive_write_free(a)); + +	/* +	 * Read back with seeking reader: +	 */ +	a = archive_read_new(); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_support_format_zip_seekable(a)); +	verify_large_zip(a, fileblocks); +	assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + +	/* +	 * Read back with streaming reader: +	 */ +	a = archive_read_new(); +	assertEqualIntA(a, ARCHIVE_OK, +	    archive_read_support_format_zip_streamable(a)); +	verify_large_zip(a, fileblocks); +	assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + +	/* +	 * Manually verify some of the final bytes of the archives. +	 */ +	/* Collect the final bytes together */ +#define FINAL_SIZE 8192 +	buff = malloc(FINAL_SIZE); +	buffsize = 0; +	memory_read_open(NULL, fileblocks); +	memory_read_seek(NULL, fileblocks, -FINAL_SIZE, SEEK_END); +	while ((s = memory_read(NULL, fileblocks, (const void **)&p)) > 0) { +		memcpy(buff + buffsize, p, s); +		buffsize += s; +	} +	assertEqualInt(buffsize, FINAL_SIZE); + +	p = buff + buffsize; + +	/* Verify regular end-of-central-directory record */ +	eocd = p - 22; +	assertEqualMem(eocd, "PK\005\006\0\0\0\0", 8); +	assertEqualMem(eocd + 8, "\021\0\021\0", 4); /* 17 entries total */ +	cd_size = le32(eocd + 12); +	/* Start of CD offset should be 0xffffffff */ +	assertEqualMem(eocd + 16, "\xff\xff\xff\xff", 4); +	assertEqualMem(eocd + 20, "\0\0", 2);	/* No Zip comment */ + +	/* Verify Zip64 locator */ +	zip64_locator = p - 42; +	assertEqualMem(zip64_locator, "PK\006\007\0\0\0\0", 8); +	zip64_eocd = p - (fileblocks->filesize - le64(zip64_locator + 8)); +	assertEqualMem(zip64_locator + 16, "\001\0\0\0", 4); + +	/* Verify Zip64 end-of-cd record. */ +	assert(zip64_eocd == p - 98); +	assertEqualMem(zip64_eocd, "PK\006\006", 4); +	assertEqualInt(44, le64(zip64_eocd + 4)); // Size of EoCD record - 12 +	assertEqualMem(zip64_eocd + 12, "\055\0", 2);  // Made by version: 45 +	assertEqualMem(zip64_eocd + 14, "\055\0", 2);  // Requires version: 45 +	assertEqualMem(zip64_eocd + 16, "\0\0\0\0", 4); // This disk +	assertEqualMem(zip64_eocd + 20, "\0\0\0\0", 4); // Total disks +	assertEqualInt(17, le64(zip64_eocd + 24));  // Entries on this disk +	assertEqualInt(17, le64(zip64_eocd + 32));  // Total entries +	cd_size = le64(zip64_eocd + 40); +	cd_start = p - (fileblocks->filesize - le64(zip64_eocd + 48)); + +	assert(cd_start + cd_size == zip64_eocd); + +	assertEqualInt(le64(zip64_eocd + 48) // Start of CD +	    + cd_size +	    + 56 // Size of Zip64 EOCD +	    + 20 // Size of Zip64 locator +	    + 22, // Size of EOCD +	    fileblocks->filesize); + +	// TODO: Scan entire Central Directory, sanity-check all data +	assertEqualMem(cd_start, "PK\001\002", 4); + +	fileblocks_free(fileblocks); +	free(nulldata); +}  | 
