/*- * 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: head/lib/libarchive/archive_write_set_compression_program.c 201104 2009-12-28 03:14:30Z kientzle $"); /* 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 #endif #ifdef HAVE_ERRNO_H # include #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #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) { ssize_t ret; const char *buf; 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) */