freebsd-dev/lib/libarchive/archive_write_set_compression_program.c
Tim Kientzle e2421f7667 Merge from libarchive.googlecode.com: Mostly a bunch of
corrections to the Windows support to reconcile differences
between Visual Studio and Cygwin.  Includes parts of
revisions 757, 774, 787, 815, 817, 819, 820, 844, and 886.

Of particular note, r886 overhauled the UTF-8/Unicode conversions to
work correctly regardless of whether the local system uses 16-bit
or 32-bit wchar_t.  (I assume that systems with 16-bit wchar_t
use UTF-16 and those with 32-bit wchar_t use UCS-4.)  This revision
also added a preference for wcrtomb() (which is thread-safe) on
platforms that support it.
2009-04-12 05:33:34 +00:00

350 lines
9.2 KiB
C

/*-
* Copyright (c) 2007 Joerg Sonnenberger
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "archive_platform.h"
__FBSDID("$FreeBSD$");
/* This capability is only available on POSIX systems. */
#if (!defined(HAVE_PIPE) || !defined(HAVE_FCNTL) || \
!(defined(HAVE_FORK) || defined(HAVE_VFORK))) && (!defined(_WIN32) || defined(__CYGWIN__))
#include "archive.h"
/*
* On non-Posix systems, allow the program to build, but choke if
* this function is actually invoked.
*/
int
archive_write_set_compression_program(struct archive *_a, const char *cmd)
{
archive_set_error(_a, -1,
"External compression programs not supported on this platform");
return (ARCHIVE_FATAL);
}
#else
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#include "archive.h"
#include "archive_private.h"
#include "archive_write_private.h"
#include "filter_fork.h"
struct private_data {
char *description;
pid_t child;
int child_stdin, child_stdout;
char *child_buf;
size_t child_buf_len, child_buf_avail;
};
static int archive_compressor_program_finish(struct archive_write *);
static int archive_compressor_program_init(struct archive_write *);
static int archive_compressor_program_write(struct archive_write *,
const void *, size_t);
/*
* Allocate, initialize and return a archive object.
*/
int
archive_write_set_compression_program(struct archive *_a, const char *cmd)
{
struct archive_write *a = (struct archive_write *)_a;
__archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC,
ARCHIVE_STATE_NEW, "archive_write_set_compression_program");
a->compressor.init = &archive_compressor_program_init;
a->compressor.config = strdup(cmd);
return (ARCHIVE_OK);
}
/*
* Setup callback.
*/
static int
archive_compressor_program_init(struct archive_write *a)
{
int ret;
struct private_data *state;
static const char *prefix = "Program: ";
char *cmd = a->compressor.config;
if (a->client_opener != NULL) {
ret = (a->client_opener)(&a->archive, a->client_data);
if (ret != ARCHIVE_OK)
return (ret);
}
state = (struct private_data *)malloc(sizeof(*state));
if (state == NULL) {
archive_set_error(&a->archive, ENOMEM,
"Can't allocate data for compression");
return (ARCHIVE_FATAL);
}
memset(state, 0, sizeof(*state));
a->archive.compression_code = ARCHIVE_COMPRESSION_PROGRAM;
state->description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1);
strcpy(state->description, prefix);
strcat(state->description, cmd);
a->archive.compression_name = state->description;
state->child_buf_len = a->bytes_per_block;
state->child_buf_avail = 0;
state->child_buf = malloc(state->child_buf_len);
if (state->child_buf == NULL) {
archive_set_error(&a->archive, ENOMEM,
"Can't allocate data for compression buffer");
free(state);
return (ARCHIVE_FATAL);
}
if ((state->child = __archive_create_child(cmd,
&state->child_stdin, &state->child_stdout)) == -1) {
archive_set_error(&a->archive, EINVAL,
"Can't initialise filter");
free(state->child_buf);
free(state);
return (ARCHIVE_FATAL);
}
a->compressor.write = archive_compressor_program_write;
a->compressor.finish = archive_compressor_program_finish;
a->compressor.data = state;
return (0);
}
static ssize_t
child_write(struct archive_write *a, const char *buf, size_t buf_len)
{
struct private_data *state = a->compressor.data;
ssize_t ret;
if (state->child_stdin == -1)
return (-1);
if (buf_len == 0)
return (-1);
restart_write:
do {
ret = write(state->child_stdin, buf, buf_len);
} while (ret == -1 && errno == EINTR);
if (ret > 0)
return (ret);
if (ret == 0) {
close(state->child_stdin);
state->child_stdin = -1;
fcntl(state->child_stdout, F_SETFL, 0);
return (0);
}
if (ret == -1 && errno != EAGAIN)
return (-1);
if (state->child_stdout == -1) {
fcntl(state->child_stdin, F_SETFL, 0);
__archive_check_child(state->child_stdin, state->child_stdout);
goto restart_write;
}
do {
ret = read(state->child_stdout,
state->child_buf + state->child_buf_avail,
state->child_buf_len - state->child_buf_avail);
} while (ret == -1 && errno == EINTR);
if (ret == 0 || (ret == -1 && errno == EPIPE)) {
close(state->child_stdout);
state->child_stdout = -1;
fcntl(state->child_stdin, F_SETFL, 0);
goto restart_write;
}
if (ret == -1 && errno == EAGAIN) {
__archive_check_child(state->child_stdin, state->child_stdout);
goto restart_write;
}
if (ret == -1)
return (-1);
state->child_buf_avail += ret;
ret = (a->client_writer)(&a->archive, a->client_data,
state->child_buf, state->child_buf_avail);
if (ret <= 0)
return (-1);
if ((size_t)ret < state->child_buf_avail) {
memmove(state->child_buf, state->child_buf + ret,
state->child_buf_avail - ret);
}
state->child_buf_avail -= ret;
a->archive.raw_position += ret;
goto restart_write;
}
/*
* Write data to the compressed stream.
*/
static int
archive_compressor_program_write(struct archive_write *a, const void *buff,
size_t length)
{
struct private_data *state;
ssize_t ret;
const char *buf;
state = (struct private_data *)a->compressor.data;
if (a->client_writer == NULL) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
"No write callback is registered? "
"This is probably an internal programming error.");
return (ARCHIVE_FATAL);
}
buf = buff;
while (length > 0) {
ret = child_write(a, buf, length);
if (ret == -1 || ret == 0) {
archive_set_error(&a->archive, EIO,
"Can't write to filter");
return (ARCHIVE_FATAL);
}
length -= ret;
buf += ret;
}
a->archive.file_position += length;
return (ARCHIVE_OK);
}
/*
* Finish the compression...
*/
static int
archive_compressor_program_finish(struct archive_write *a)
{
int ret, status;
ssize_t bytes_read, bytes_written;
struct private_data *state;
state = (struct private_data *)a->compressor.data;
ret = 0;
if (a->client_writer == NULL) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
"No write callback is registered? "
"This is probably an internal programming error.");
ret = ARCHIVE_FATAL;
goto cleanup;
}
/* XXX pad compressed data. */
close(state->child_stdin);
state->child_stdin = -1;
fcntl(state->child_stdout, F_SETFL, 0);
for (;;) {
do {
bytes_read = read(state->child_stdout,
state->child_buf + state->child_buf_avail,
state->child_buf_len - state->child_buf_avail);
} while (bytes_read == -1 && errno == EINTR);
if (bytes_read == 0 || (bytes_read == -1 && errno == EPIPE))
break;
if (bytes_read == -1) {
archive_set_error(&a->archive, errno,
"Read from filter failed unexpectedly.");
ret = ARCHIVE_FATAL;
goto cleanup;
}
state->child_buf_avail += bytes_read;
bytes_written = (a->client_writer)(&a->archive, a->client_data,
state->child_buf, state->child_buf_avail);
if (bytes_written <= 0) {
ret = ARCHIVE_FATAL;
goto cleanup;
}
if ((size_t)bytes_written < state->child_buf_avail) {
memmove(state->child_buf,
state->child_buf + bytes_written,
state->child_buf_avail - bytes_written);
}
state->child_buf_avail -= bytes_written;
a->archive.raw_position += bytes_written;
}
/* XXX pad final compressed block. */
cleanup:
/* Shut down the child. */
if (state->child_stdin != -1)
close(state->child_stdin);
if (state->child_stdout != -1)
close(state->child_stdout);
while (waitpid(state->child, &status, 0) == -1 && errno == EINTR)
continue;
if (status != 0) {
archive_set_error(&a->archive, EIO,
"Filter exited with failure.");
ret = ARCHIVE_FATAL;
}
/* Release our configuration data. */
free(a->compressor.config);
a->compressor.config = NULL;
/* Release our private state data. */
free(state->child_buf);
free(state->description);
free(state);
return (ret);
}
#endif /* !defined(HAVE_PIPE) || !defined(HAVE_VFORK) || !defined(HAVE_FCNTL) */