New hooks for reading/writing archives to/from a FILE * or

an in-memory buffer.

PR: bin/86742
This commit is contained in:
Tim Kientzle 2006-11-24 02:00:48 +00:00
parent ac39496f20
commit 13e2d5bcd5
5 changed files with 354 additions and 211 deletions

View file

@ -37,6 +37,7 @@
#include <sys/types.h> /* Linux requires this for off_t */
@ARCHIVE_H_INCLUDE_INTTYPES_H@
#include <stdio.h> /* For FILE * */
#include <unistd.h> /* For ssize_t and size_t */
#ifdef __cplusplus
@ -58,6 +59,7 @@ extern "C" {
*
* 1 - Version tests are available.
* 2 - archive_{read,write}_close available separately from _finish.
* 3 - open_memory, open_memory2, open_FILE, open_fd available
*/
#define ARCHIVE_API_VERSION @ARCHIVE_API_MAJOR@
int archive_api_version(void);
@ -206,9 +208,18 @@ int archive_read_open_filename(struct archive *,
/* archive_read_open_file() is a deprecated synonym for ..._open_filename(). */
int archive_read_open_file(struct archive *,
const char *_filename, size_t _block_size);
/* Read an archive that's stored in memory. */
int archive_read_open_memory(struct archive *,
void * buff, size_t size);
/* A more involved version that is only used for internal testing. */
int archive_read_open_memory2(struct archive *a, void *buff,
size_t size, size_t read_size);
/* Read an archive that's already open, using the file descriptor. */
int archive_read_open_fd(struct archive *, int _fd,
size_t _block_size);
/* Read an archive that's already open, using a FILE *. */
/* Note: DO NOT use this with tape drives. */
int archive_read_open_FILE(struct archive *, FILE *_file);
/* Parses and returns next entry header. */
int archive_read_next_header(struct archive *,
@ -333,6 +344,11 @@ int archive_write_open_fd(struct archive *, int _fd);
int archive_write_open_filename(struct archive *, const char *_file);
/* A deprecated synonym for archive_write_open_filename() */
int archive_write_open_file(struct archive *, const char *_file);
int archive_write_open_FILE(struct archive *, FILE *);
/* _buffSize is the size of the buffer, _used refers to a variable that
* will be updated after each write into the buffer. */
int archive_write_open_memory(struct archive *,
void *_buffer, size_t _buffSize, size_t *_used);
/*
* Note that the library will truncate writes beyond the size provided

View file

@ -1,5 +1,5 @@
/*-
* Copyright (c) 2003-2004 Tim Kientzle
* Copyright (c) 2003-2006 Tim Kientzle
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -48,12 +48,10 @@ __FBSDID("$FreeBSD$");
#include "archive.h"
struct read_file_data {
int fd;
struct read_FILE_data {
FILE *f;
size_t block_size;
void *buffer;
mode_t st_mode; /* Mode bits for opened file. */
char filename[1]; /* Must be last! */
};
static int file_close(struct archive *, void *);
@ -62,91 +60,54 @@ static ssize_t file_read(struct archive *, void *, const void **buff);
static ssize_t file_skip(struct archive *, void *, size_t request);
int
archive_read_open_file(struct archive *a, const char *filename,
size_t block_size)
archive_read_open_FILE(struct archive *a, FILE *f)
{
return (archive_read_open_filename(a, filename, block_size));
}
struct read_FILE_data *mine;
int
archive_read_open_filename(struct archive *a, const char *filename,
size_t block_size)
{
struct read_file_data *mine;
if (filename == NULL || filename[0] == '\0') {
mine = (struct read_file_data *)malloc(sizeof(*mine));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
mine->filename[0] = '\0';
} else {
mine = (struct read_file_data *)malloc(sizeof(*mine) + strlen(filename));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
strcpy(mine->filename, filename);
mine = (struct read_FILE_data *)malloc(sizeof(*mine));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
mine->block_size = block_size;
mine->buffer = NULL;
mine->fd = -1;
return (archive_read_open2(a, mine, file_open, file_read, file_skip, file_close));
mine->block_size = 128 * 1024;
mine->buffer = malloc(mine->block_size);
if (mine->buffer == NULL) {
archive_set_error(a, ENOMEM, "No memory");
free(mine);
return (ARCHIVE_FATAL);
}
mine->f = f;
return (archive_read_open2(a, mine, file_open, file_read,
file_skip, file_close));
}
static int
file_open(struct archive *a, void *client_data)
{
struct read_file_data *mine = (struct read_file_data *)client_data;
struct read_FILE_data *mine = (struct read_FILE_data *)client_data;
struct stat st;
mine->buffer = malloc(mine->block_size);
if (mine->buffer == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
if (mine->filename[0] != '\0')
mine->fd = open(mine->filename, O_RDONLY);
else
mine->fd = 0; /* Fake "open" for stdin. */
if (mine->fd < 0) {
archive_set_error(a, errno, "Failed to open '%s'",
mine->filename);
return (ARCHIVE_FATAL);
}
if (fstat(mine->fd, &st) == 0) {
/* If we're reading a file from disk, ensure that we don't
overwrite it with an extracted file. */
if (S_ISREG(st.st_mode))
archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino);
/* Remember mode so close can decide whether to flush. */
mine->st_mode = st.st_mode;
} else {
if (mine->filename[0] == '\0')
archive_set_error(a, errno, "Can't stat stdin");
else
archive_set_error(a, errno, "Can't stat '%s'",
mine->filename);
return (ARCHIVE_FATAL);
}
return (0);
/*
* If we can't fstat() the file, it may just be that
* it's not a file. (FILE * objects can wrap many kinds
* of I/O streams.)
*/
if (fstat(fileno(mine->f), &st) == 0 && S_ISREG(st.st_mode))
archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino);
return (ARCHIVE_OK);
}
static ssize_t
file_read(struct archive *a, void *client_data, const void **buff)
{
struct read_file_data *mine = (struct read_file_data *)client_data;
struct read_FILE_data *mine = (struct read_FILE_data *)client_data;
ssize_t bytes_read;
*buff = mine->buffer;
bytes_read = read(mine->fd, mine->buffer, mine->block_size);
bytes_read = fread(mine->buffer, 1, mine->block_size, mine->f);
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);
archive_set_error(a, errno, "Error reading file");
}
return (bytes_read);
}
@ -154,77 +115,33 @@ file_read(struct archive *a, void *client_data, const void **buff)
static ssize_t
file_skip(struct archive *a, void *client_data, size_t request)
{
struct read_file_data *mine = (struct read_file_data *)client_data;
off_t old_offset, new_offset;
struct read_FILE_data *mine = (struct read_FILE_data *)client_data;
/* Reduce request to the next smallest multiple of block_size */
request = (request / mine->block_size) * mine->block_size;
/*
* Hurray for lazy evaluation: if the first lseek fails, the second
* one will not be executed.
* Note: the 'fd' and 'filename' versions round the request
* down to a multiple of the block size to ensure proper
* operation on block-oriented media such as tapes. But stdio
* doesn't work with such media (it doesn't ensure blocking),
* so we don't need to bother.
*/
if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) < 0) ||
((new_offset = lseek(mine->fd, request, SEEK_CUR)) < 0))
#if HAVE_FSEEKO
if (fseeko(mine->f, request, SEEK_CUR) != 0)
#else
if (fseek(mine->f, request, SEEK_CUR) != 0)
#endif
{
if (errno == ESPIPE)
{
/*
* Failure to lseek() can be caused by the file
* descriptor pointing to a pipe, socket or FIFO.
* Return 0 here, so the compression layer will use
* read()s instead to advance the file descriptor.
* It's slower of course, but works as well.
*/
return (0);
}
/*
* There's been an error other than ESPIPE. This is most
* likely caused by a programmer error (too large request)
* or a corrupted archive file.
*/
if (mine->filename[0] == '\0')
/*
* Should never get here, since lseek() on stdin ought
* to return an ESPIPE error.
*/
archive_set_error(a, errno, "Error seeking in stdin");
else
archive_set_error(a, errno, "Error seeking in '%s'",
mine->filename);
return (-1);
archive_set_error(a, errno, "Error skipping forward");
return (ARCHIVE_FATAL);
}
return (new_offset - old_offset);
return (request);
}
static int
file_close(struct archive *a, void *client_data)
{
struct read_file_data *mine = (struct read_file_data *)client_data;
struct read_FILE_data *mine = (struct read_FILE_data *)client_data;
(void)a; /* UNUSED */
/*
* Sometimes, we should flush the input before closing.
* Regular files: faster to just close without flush.
* Devices: must not flush (user might need to
* read the "next" item on a non-rewind device).
* Pipes and sockets: must flush (otherwise, the
* program feeding the pipe or socket may complain).
* Here, I flush everything except for regular files and
* device nodes.
*/
if (!S_ISREG(mine->st_mode)
&& !S_ISCHR(mine->st_mode)
&& !S_ISBLK(mine->st_mode)) {
ssize_t bytesRead;
do {
bytesRead = read(mine->fd, mine->buffer,
mine->block_size);
} while (bytesRead > 0);
}
/* If a named file was opened, then it needs to be closed. */
if (mine->filename[0] != '\0')
close(mine->fd);
if (mine->buffer != NULL)
free(mine->buffer);
free(mine);

View file

@ -0,0 +1,147 @@
/*-
* Copyright (c) 2003-2006 Tim Kientzle
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "archive_platform.h"
__FBSDID("$FreeBSD$");
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "archive.h"
/*
* Glue to read an archive from a block of memory.
*
* This is mostly a huge help in building test harnesses;
* test programs can build archives in memory and read them
* back again without having to mess with files on disk.
*/
struct read_memory_data {
unsigned char *buffer;
unsigned char *end;
ssize_t read_size;
};
static int memory_read_close(struct archive *, void *);
static int memory_read_open(struct archive *, void *);
static ssize_t memory_read_skip(struct archive *, void *, size_t request);
static ssize_t memory_read(struct archive *, void *, const void **buff);
int
archive_read_open_memory(struct archive *a, void *buff, size_t size)
{
return archive_read_open_memory2(a, buff, size, size);
}
/*
* Don't use _open_memory2() in production code; the archive_read_open_memory()
* version is the one you really want. This is just here so that
* test harnesses can exercise block operations inside the library.
*/
int
archive_read_open_memory2(struct archive *a, void *buff,
size_t size, size_t read_size)
{
struct read_memory_data *mine;
mine = (struct read_memory_data *)malloc(sizeof(*mine));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
memset(mine, 0, sizeof(*mine));
mine->buffer = (unsigned char *)buff;
mine->end = mine->buffer + size;
mine->read_size = read_size;
return (archive_read_open2(a, mine, memory_read_open,
memory_read, memory_read_skip, memory_read_close));
}
/*
* There's nothing to open.
*/
static int
memory_read_open(struct archive *a, void *client_data)
{
(void)a; /* UNUSED */
(void)client_data; /* UNUSED */
return (ARCHIVE_OK);
}
/*
* This is scary simple: Just advance a pointer. Limiting
* to read_size is not technically necessary, but it exercises
* more of the internal logic when used with a small block size
* in a test harness. Production use should not specify a block
* size; then this is much faster.
*/
static ssize_t
memory_read(struct archive *a, void *client_data, const void **buff)
{
struct read_memory_data *mine = (struct read_memory_data *)client_data;
ssize_t size;
(void)a; /* UNUSED */
*buff = mine->buffer;
size = mine->end - mine->buffer;
if (size > mine->read_size)
size = mine->read_size;
mine->buffer += size;
return (size);
}
/*
* Advancing is just as simple. Again, this is doing more than
* necessary in order to better exercise internal code when used
* as a test harness.
*/
static ssize_t
memory_read_skip(struct archive *a, void *client_data, size_t skip)
{
struct read_memory_data *mine = (struct read_memory_data *)client_data;
(void)a; /* UNUSED */
if (mine->buffer + skip > mine->end)
skip = mine->end - mine->buffer;
/* Round down to block size. */
skip /= mine->read_size;
skip *= mine->read_size;
mine->buffer += skip;
return (skip);
}
/*
* Close is just cleaning up our one small bit of data.
*/
static int
memory_read_close(struct archive *a, void *client_data)
{
struct read_memory_data *mine = (struct read_memory_data *)client_data;
(void)a; /* UNUSED */
free(mine);
return (ARCHIVE_OK);
}

View file

@ -48,9 +48,8 @@ __FBSDID("$FreeBSD$");
#include "archive.h"
struct write_file_data {
int fd;
char filename[1];
struct write_FILE_data {
FILE *f;
};
static int file_close(struct archive *, void *);
@ -58,32 +57,16 @@ static int file_open(struct archive *, void *);
static ssize_t file_write(struct archive *, void *, void *buff, size_t);
int
archive_write_open_file(struct archive *a, const char *filename)
archive_write_open_FILE(struct archive *a, FILE *f)
{
return (archive_write_open_filename(a, filename));
}
struct write_FILE_data *mine;
int
archive_write_open_filename(struct archive *a, const char *filename)
{
struct write_file_data *mine;
if (filename == NULL || filename[0] == '\0') {
mine = (struct write_file_data *)malloc(sizeof(*mine));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
mine->filename[0] = '\0'; /* Record that we're using stdout. */
} else {
mine = (struct write_file_data *)malloc(sizeof(*mine) + strlen(filename));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
strcpy(mine->filename, filename);
mine = (struct write_FILE_data *)malloc(sizeof(*mine));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
mine->fd = -1;
mine->f = f;
return (archive_write_open(a, mine,
file_open, file_write, file_close));
}
@ -91,53 +74,8 @@ archive_write_open_filename(struct archive *a, const char *filename)
static int
file_open(struct archive *a, void *client_data)
{
int flags;
struct write_file_data *mine;
struct stat st;
mine = (struct write_file_data *)client_data;
flags = O_WRONLY | O_CREAT | O_TRUNC;
/*
* Open the file.
*/
if (mine->filename[0] != '\0') {
mine->fd = open(mine->filename, flags, 0666);
if (mine->fd < 0) {
archive_set_error(a, errno, "Failed to open '%s'",
mine->filename);
return (ARCHIVE_FATAL);
}
} else {
/*
* NULL filename is stdout.
*/
mine->fd = 1;
/* By default, pad archive when writing to stdout. */
if (archive_write_get_bytes_in_last_block(a) < 0)
archive_write_set_bytes_in_last_block(a, 0);
}
/*
* Set up default last block handling.
*/
if (archive_write_get_bytes_in_last_block(a) < 0) {
if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode) ||
S_ISFIFO(st.st_mode))
/* Pad last block when writing to device or FIFO. */
archive_write_set_bytes_in_last_block(a, 0);
else
/* Don't pad last block otherwise. */
archive_write_set_bytes_in_last_block(a, 1);
}
/*
* If the output file is a regular file, don't add it to
* itself. If it's a device file, it's okay to add the device
* entry to the output archive.
*/
if (S_ISREG(st.st_mode))
archive_write_set_skip_file(a, st.st_dev, st.st_ino);
(void)a; /* UNUSED */
(void)client_data; /* UNUSED */
return (ARCHIVE_OK);
}
@ -145,12 +83,12 @@ file_open(struct archive *a, void *client_data)
static ssize_t
file_write(struct archive *a, void *client_data, void *buff, size_t length)
{
struct write_file_data *mine;
ssize_t bytesWritten;
struct write_FILE_data *mine;
size_t bytesWritten;
mine = (struct write_file_data *)client_data;
bytesWritten = write(mine->fd, buff, length);
if (bytesWritten <= 0) {
mine = client_data;
bytesWritten = fwrite(buff, 1, length, mine->f);
if (bytesWritten < length) {
archive_set_error(a, errno, "Write error");
return (-1);
}
@ -160,11 +98,9 @@ file_write(struct archive *a, void *client_data, void *buff, size_t length)
static int
file_close(struct archive *a, void *client_data)
{
struct write_file_data *mine = (struct write_file_data *)client_data;
struct write_FILE_data *mine = client_data;
(void)a; /* UNUSED */
if (mine->filename[0] != '\0')
close(mine->fd);
free(mine);
return (ARCHIVE_OK);
}

View file

@ -0,0 +1,127 @@
/*-
* Copyright (c) 2003-2006 Tim Kientzle
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer
* 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$");
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "archive.h"
/*
* This is a little tricky. I used to allow the
* compression handling layer to fork the compressor,
* which means this write function gets invoked in
* a separate process. That would, of course, make it impossible
* to actually use the data stored into memory here.
* Fortunately, none of the compressors fork today and
* I'm reluctant to use that route in the future but, if
* forking compressors ever do reappear, this will have
* to get a lot more complicated.
*/
struct write_memory_data {
size_t used;
size_t size;
size_t * client_size;
unsigned char * buff;
};
static int memory_write_close(struct archive *, void *);
static int memory_write_open(struct archive *, void *);
static ssize_t memory_write(struct archive *, void *, void *buff, size_t);
/*
* Client provides a pointer to a block of memory to receive
* the data. The 'size' param both tells us the size of the
* client buffer and lets us tell the client the final size.
*/
int
archive_write_open_memory(struct archive *a, void *buff, size_t buffSize, size_t *used)
{
struct write_memory_data *mine;
mine = (struct write_memory_data *)malloc(sizeof(*mine));
if (mine == NULL) {
archive_set_error(a, ENOMEM, "No memory");
return (ARCHIVE_FATAL);
}
memset(mine, 0, sizeof(*mine));
mine->buff = buff;
mine->size = buffSize;
mine->client_size = used;
return (archive_write_open(a, mine,
memory_write_open, memory_write, memory_write_close));
}
static int
memory_write_open(struct archive *a, void *client_data)
{
struct write_memory_data *mine;
mine = client_data;
mine->used = 0;
if (mine->client_size != NULL)
*mine->client_size = mine->used;
/* Disable padding if it hasn't been set explicitly. */
if (-1 == archive_write_get_bytes_in_last_block(a))
archive_write_set_bytes_in_last_block(a, 1);
return (ARCHIVE_OK);
}
/*
* Copy the data into the client buffer.
* Note that we update mine->client_size on every write.
* In particular, this means the client can follow exactly
* how much has been written into their buffer at any time.
*/
static ssize_t
memory_write(struct archive *a, void *client_data, void *buff, size_t length)
{
struct write_memory_data *mine;
mine = client_data;
if (mine->used + length > mine->size) {
archive_set_error(a, ENOMEM, "Buffer exhausted");
return (ARCHIVE_FATAL);
}
memcpy(mine->buff + mine->used, buff, length);
mine->used += length;
if (mine->client_size != NULL)
*mine->client_size = mine->used;
return (length);
}
static int
memory_write_close(struct archive *a, void *client_data)
{
struct write_memory_data *mine;
(void)a; /* UNUSED */
mine = client_data;
free(mine);
return (ARCHIVE_OK);
}