archive_write_set_compression_program.c revision 228759
1/*-
2 * Copyright (c) 2007 Joerg Sonnenberger
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "archive_platform.h"
27
28__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_program.c 201104 2009-12-28 03:14:30Z kientzle $");
29
30/* This capability is only available on POSIX systems. */
31#if (!defined(HAVE_PIPE) || !defined(HAVE_FCNTL) || \
32    !(defined(HAVE_FORK) || defined(HAVE_VFORK))) && (!defined(_WIN32) || defined(__CYGWIN__))
33#include "archive.h"
34
35/*
36 * On non-Posix systems, allow the program to build, but choke if
37 * this function is actually invoked.
38 */
39int
40archive_write_set_compression_program(struct archive *_a, const char *cmd)
41{
42	archive_set_error(_a, -1,
43	    "External compression programs not supported on this platform");
44	return (ARCHIVE_FATAL);
45}
46
47#else
48
49#ifdef HAVE_SYS_WAIT_H
50#  include <sys/wait.h>
51#endif
52#ifdef HAVE_ERRNO_H
53#  include <errno.h>
54#endif
55#ifdef HAVE_FCNTL_H
56#  include <fcntl.h>
57#endif
58#ifdef HAVE_STDLIB_H
59#  include <stdlib.h>
60#endif
61#ifdef HAVE_STRING_H
62#  include <string.h>
63#endif
64
65#include "archive.h"
66#include "archive_private.h"
67#include "archive_write_private.h"
68
69#include "filter_fork.h"
70
71struct private_data {
72	char		*description;
73	pid_t		 child;
74	int		 child_stdin, child_stdout;
75
76	char		*child_buf;
77	size_t		 child_buf_len, child_buf_avail;
78};
79
80static int	archive_compressor_program_finish(struct archive_write *);
81static int	archive_compressor_program_init(struct archive_write *);
82static int	archive_compressor_program_write(struct archive_write *,
83		    const void *, size_t);
84
85/*
86 * Allocate, initialize and return a archive object.
87 */
88int
89archive_write_set_compression_program(struct archive *_a, const char *cmd)
90{
91	struct archive_write *a = (struct archive_write *)_a;
92	__archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC,
93	    ARCHIVE_STATE_NEW, "archive_write_set_compression_program");
94	a->compressor.init = &archive_compressor_program_init;
95	a->compressor.config = strdup(cmd);
96	return (ARCHIVE_OK);
97}
98
99/*
100 * Setup callback.
101 */
102static int
103archive_compressor_program_init(struct archive_write *a)
104{
105	int ret;
106	struct private_data *state;
107	static const char *prefix = "Program: ";
108	char *cmd = a->compressor.config;
109
110	if (a->client_opener != NULL) {
111		ret = (a->client_opener)(&a->archive, a->client_data);
112		if (ret != ARCHIVE_OK)
113			return (ret);
114	}
115
116	state = (struct private_data *)malloc(sizeof(*state));
117	if (state == NULL) {
118		archive_set_error(&a->archive, ENOMEM,
119		    "Can't allocate data for compression");
120		return (ARCHIVE_FATAL);
121	}
122	memset(state, 0, sizeof(*state));
123
124	a->archive.compression_code = ARCHIVE_COMPRESSION_PROGRAM;
125	state->description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1);
126	strcpy(state->description, prefix);
127	strcat(state->description, cmd);
128	a->archive.compression_name = state->description;
129
130	state->child_buf_len = a->bytes_per_block;
131	state->child_buf_avail = 0;
132	state->child_buf = malloc(state->child_buf_len);
133
134	if (state->child_buf == NULL) {
135		archive_set_error(&a->archive, ENOMEM,
136		    "Can't allocate data for compression buffer");
137		free(state);
138		return (ARCHIVE_FATAL);
139	}
140
141	if ((state->child = __archive_create_child(cmd,
142		 &state->child_stdin, &state->child_stdout)) == -1) {
143		archive_set_error(&a->archive, EINVAL,
144		    "Can't initialise filter");
145		free(state->child_buf);
146		free(state);
147		return (ARCHIVE_FATAL);
148	}
149
150	a->compressor.write = archive_compressor_program_write;
151	a->compressor.finish = archive_compressor_program_finish;
152
153	a->compressor.data = state;
154	return (0);
155}
156
157static ssize_t
158child_write(struct archive_write *a, const char *buf, size_t buf_len)
159{
160	struct private_data *state = a->compressor.data;
161	ssize_t ret;
162
163	if (state->child_stdin == -1)
164		return (-1);
165
166	if (buf_len == 0)
167		return (-1);
168
169restart_write:
170	do {
171		ret = write(state->child_stdin, buf, buf_len);
172	} while (ret == -1 && errno == EINTR);
173
174	if (ret > 0)
175		return (ret);
176	if (ret == 0) {
177		close(state->child_stdin);
178		state->child_stdin = -1;
179		fcntl(state->child_stdout, F_SETFL, 0);
180		return (0);
181	}
182	if (ret == -1 && errno != EAGAIN)
183		return (-1);
184
185	if (state->child_stdout == -1) {
186		fcntl(state->child_stdin, F_SETFL, 0);
187		__archive_check_child(state->child_stdin, state->child_stdout);
188		goto restart_write;
189	}
190
191	do {
192		ret = read(state->child_stdout,
193		    state->child_buf + state->child_buf_avail,
194		    state->child_buf_len - state->child_buf_avail);
195	} while (ret == -1 && errno == EINTR);
196
197	if (ret == 0 || (ret == -1 && errno == EPIPE)) {
198		close(state->child_stdout);
199		state->child_stdout = -1;
200		fcntl(state->child_stdin, F_SETFL, 0);
201		goto restart_write;
202	}
203	if (ret == -1 && errno == EAGAIN) {
204		__archive_check_child(state->child_stdin, state->child_stdout);
205		goto restart_write;
206	}
207	if (ret == -1)
208		return (-1);
209
210	state->child_buf_avail += ret;
211
212	ret = (a->client_writer)(&a->archive, a->client_data,
213	    state->child_buf, state->child_buf_avail);
214	if (ret <= 0)
215		return (-1);
216
217	if ((size_t)ret < state->child_buf_avail) {
218		memmove(state->child_buf, state->child_buf + ret,
219		    state->child_buf_avail - ret);
220	}
221	state->child_buf_avail -= ret;
222	a->archive.raw_position += ret;
223	goto restart_write;
224}
225
226/*
227 * Write data to the compressed stream.
228 */
229static int
230archive_compressor_program_write(struct archive_write *a, const void *buff,
231    size_t length)
232{
233	ssize_t ret;
234	const char *buf;
235
236	if (a->client_writer == NULL) {
237		archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
238		    "No write callback is registered?  "
239		    "This is probably an internal programming error.");
240		return (ARCHIVE_FATAL);
241	}
242
243	buf = buff;
244	while (length > 0) {
245		ret = child_write(a, buf, length);
246		if (ret == -1 || ret == 0) {
247			archive_set_error(&a->archive, EIO,
248			    "Can't write to filter");
249			return (ARCHIVE_FATAL);
250		}
251		length -= ret;
252		buf += ret;
253	}
254
255	a->archive.file_position += length;
256	return (ARCHIVE_OK);
257}
258
259
260/*
261 * Finish the compression...
262 */
263static int
264archive_compressor_program_finish(struct archive_write *a)
265{
266	int ret, status;
267	ssize_t bytes_read, bytes_written;
268	struct private_data *state;
269
270	state = (struct private_data *)a->compressor.data;
271	ret = 0;
272	if (a->client_writer == NULL) {
273		archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
274		    "No write callback is registered?  "
275		    "This is probably an internal programming error.");
276		ret = ARCHIVE_FATAL;
277		goto cleanup;
278	}
279
280	/* XXX pad compressed data. */
281
282	close(state->child_stdin);
283	state->child_stdin = -1;
284	fcntl(state->child_stdout, F_SETFL, 0);
285
286	for (;;) {
287		do {
288			bytes_read = read(state->child_stdout,
289			    state->child_buf + state->child_buf_avail,
290			    state->child_buf_len - state->child_buf_avail);
291		} while (bytes_read == -1 && errno == EINTR);
292
293		if (bytes_read == 0 || (bytes_read == -1 && errno == EPIPE))
294			break;
295
296		if (bytes_read == -1) {
297			archive_set_error(&a->archive, errno,
298			    "Read from filter failed unexpectedly.");
299			ret = ARCHIVE_FATAL;
300			goto cleanup;
301		}
302		state->child_buf_avail += bytes_read;
303
304		bytes_written = (a->client_writer)(&a->archive, a->client_data,
305		    state->child_buf, state->child_buf_avail);
306		if (bytes_written <= 0) {
307			ret = ARCHIVE_FATAL;
308			goto cleanup;
309		}
310		if ((size_t)bytes_written < state->child_buf_avail) {
311			memmove(state->child_buf,
312			    state->child_buf + bytes_written,
313			    state->child_buf_avail - bytes_written);
314		}
315		state->child_buf_avail -= bytes_written;
316		a->archive.raw_position += bytes_written;
317	}
318
319	/* XXX pad final compressed block. */
320
321cleanup:
322	/* Shut down the child. */
323	if (state->child_stdin != -1)
324		close(state->child_stdin);
325	if (state->child_stdout != -1)
326		close(state->child_stdout);
327	while (waitpid(state->child, &status, 0) == -1 && errno == EINTR)
328		continue;
329
330	if (status != 0) {
331		archive_set_error(&a->archive, EIO,
332		    "Filter exited with failure.");
333		ret = ARCHIVE_FATAL;
334	}
335
336	/* Release our configuration data. */
337	free(a->compressor.config);
338	a->compressor.config = NULL;
339
340	/* Release our private state data. */
341	free(state->child_buf);
342	free(state->description);
343	free(state);
344	return (ret);
345}
346
347#endif /* !defined(HAVE_PIPE) || !defined(HAVE_VFORK) || !defined(HAVE_FCNTL) */
348