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 __FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_program.c 201104 2009-12-28 03:14:30Z kientzle $");
28 
29 #ifdef HAVE_SYS_WAIT_H
30 #  include <sys/wait.h>
31 #endif
32 #ifdef HAVE_ERRNO_H
33 #  include <errno.h>
34 #endif
35 #ifdef HAVE_FCNTL_H
36 #  include <fcntl.h>
37 #endif
38 #ifdef HAVE_STDLIB_H
39 #  include <stdlib.h>
40 #endif
41 #ifdef HAVE_STRING_H
42 #  include <string.h>
43 #endif
44 
45 #include "archive.h"
46 #include "archive_private.h"
47 #include "archive_write_private.h"
48 
49 #if ARCHIVE_VERSION_NUMBER < 4000000
50 int
51 archive_write_set_compression_program(struct archive *a, const char *cmd)
52 {
53 	__archive_write_filters_free(a);
54 	return (archive_write_add_filter_program(a, cmd));
55 }
56 #endif
57 
58 /* This capability is only available on POSIX systems. */
59 #if (!defined(HAVE_PIPE) || !defined(HAVE_FCNTL) || \
60     !(defined(HAVE_FORK) || defined(HAVE_VFORK))) && (!defined(_WIN32) || defined(__CYGWIN__))
61 
62 /*
63  * On non-Posix systems, allow the program to build, but choke if
64  * this function is actually invoked.
65  */
66 int
67 archive_write_add_filter_program(struct archive *_a, const char *cmd)
68 {
69 	archive_set_error(_a, -1,
70 	    "External compression programs not supported on this platform");
71 	return (ARCHIVE_FATAL);
72 }
73 
74 #else
75 
76 #include "filter_fork.h"
77 
78 struct private_data {
79 	char		*cmd;
80 	char		*description;
81 	pid_t		 child;
82 	int		 child_stdin, child_stdout;
83 
84 	char		*child_buf;
85 	size_t		 child_buf_len, child_buf_avail;
86 };
87 
88 static int archive_compressor_program_open(struct archive_write_filter *);
89 static int archive_compressor_program_write(struct archive_write_filter *,
90 		    const void *, size_t);
91 static int archive_compressor_program_close(struct archive_write_filter *);
92 static int archive_compressor_program_free(struct archive_write_filter *);
93 
94 /*
95  * Add a filter to this write handle that passes all data through an
96  * external program.
97  */
98 int
99 archive_write_add_filter_program(struct archive *_a, const char *cmd)
100 {
101 	struct archive_write_filter *f = __archive_write_allocate_filter(_a);
102 	struct archive_write *a = (struct archive_write *)_a;
103 	struct private_data *data;
104 	static const char *prefix = "Program: ";
105 	archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC,
106 	    ARCHIVE_STATE_NEW, "archive_write_add_filter_program");
107 	data = calloc(1, sizeof(*data));
108 	if (data == NULL) {
109 		archive_set_error(&a->archive, ENOMEM, "Out of memory");
110 		return (ARCHIVE_FATAL);
111 	}
112 	data->cmd = strdup(cmd);
113 	data->description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1);
114 	strcpy(data->description, prefix);
115 	strcat(data->description, cmd);
116 
117 	f->name = data->description;
118 	f->data = data;
119 	f->open = &archive_compressor_program_open;
120 	f->code = ARCHIVE_COMPRESSION_PROGRAM;
121 	return (ARCHIVE_OK);
122 }
123 
124 /*
125  * Setup callback.
126  */
127 static int
128 archive_compressor_program_open(struct archive_write_filter *f)
129 {
130 	struct private_data *data = (struct private_data *)f->data;
131 	int ret;
132 
133 	ret = __archive_write_open_filter(f->next_filter);
134 	if (ret != ARCHIVE_OK)
135 		return (ret);
136 
137 	if (data->child_buf == NULL) {
138 		data->child_buf_len = 65536;
139 		data->child_buf_avail = 0;
140 		data->child_buf = malloc(data->child_buf_len);
141 
142 		if (data->child_buf == NULL) {
143 			archive_set_error(f->archive, ENOMEM,
144 			    "Can't allocate compression buffer");
145 			return (ARCHIVE_FATAL);
146 		}
147 	}
148 
149 	if ((data->child = __archive_create_child(data->cmd,
150 		 &data->child_stdin, &data->child_stdout)) == -1) {
151 		archive_set_error(f->archive, EINVAL,
152 		    "Can't initialise filter");
153 		return (ARCHIVE_FATAL);
154 	}
155 
156 	f->write = archive_compressor_program_write;
157 	f->close = archive_compressor_program_close;
158 	f->free = archive_compressor_program_free;
159 	return (0);
160 }
161 
162 static ssize_t
163 child_write(struct archive_write_filter *f, const char *buf, size_t buf_len)
164 {
165 	struct private_data *data = f->data;
166 	ssize_t ret;
167 
168 	if (data->child_stdin == -1)
169 		return (-1);
170 
171 	if (buf_len == 0)
172 		return (-1);
173 
174 restart_write:
175 	do {
176 		ret = write(data->child_stdin, buf, buf_len);
177 	} while (ret == -1 && errno == EINTR);
178 
179 	if (ret > 0)
180 		return (ret);
181 	if (ret == 0) {
182 		close(data->child_stdin);
183 		data->child_stdin = -1;
184 		fcntl(data->child_stdout, F_SETFL, 0);
185 		return (0);
186 	}
187 	if (ret == -1 && errno != EAGAIN)
188 		return (-1);
189 
190 	if (data->child_stdout == -1) {
191 		fcntl(data->child_stdin, F_SETFL, 0);
192 		__archive_check_child(data->child_stdin, data->child_stdout);
193 		goto restart_write;
194 	}
195 
196 	do {
197 		ret = read(data->child_stdout,
198 		    data->child_buf + data->child_buf_avail,
199 		    data->child_buf_len - data->child_buf_avail);
200 	} while (ret == -1 && errno == EINTR);
201 
202 	if (ret == 0 || (ret == -1 && errno == EPIPE)) {
203 		close(data->child_stdout);
204 		data->child_stdout = -1;
205 		fcntl(data->child_stdin, F_SETFL, 0);
206 		goto restart_write;
207 	}
208 	if (ret == -1 && errno == EAGAIN) {
209 		__archive_check_child(data->child_stdin, data->child_stdout);
210 		goto restart_write;
211 	}
212 	if (ret == -1)
213 		return (-1);
214 
215 	data->child_buf_avail += ret;
216 
217 	ret = __archive_write_filter(f->next_filter,
218 	    data->child_buf, data->child_buf_avail);
219 	if (ret <= 0)
220 		return (-1);
221 
222 	if ((size_t)ret < data->child_buf_avail) {
223 		memmove(data->child_buf, data->child_buf + ret,
224 		    data->child_buf_avail - ret);
225 	}
226 	data->child_buf_avail -= ret;
227 	goto restart_write;
228 }
229 
230 /*
231  * Write data to the compressed stream.
232  */
233 static int
234 archive_compressor_program_write(struct archive_write_filter *f,
235     const void *buff, size_t length)
236 {
237 	ssize_t ret;
238 	const char *buf;
239 
240 	buf = buff;
241 	while (length > 0) {
242 		ret = child_write(f, buf, length);
243 		if (ret == -1 || ret == 0) {
244 			archive_set_error(f->archive, EIO,
245 			    "Can't write to filter");
246 			return (ARCHIVE_FATAL);
247 		}
248 		length -= ret;
249 		buf += ret;
250 	}
251 	return (ARCHIVE_OK);
252 }
253 
254 
255 /*
256  * Finish the compression...
257  */
258 static int
259 archive_compressor_program_close(struct archive_write_filter *f)
260 {
261 	struct private_data *data = (struct private_data *)f->data;
262 	int ret, r1, status;
263 	ssize_t bytes_read;
264 
265 	ret = 0;
266 	close(data->child_stdin);
267 	data->child_stdin = -1;
268 	fcntl(data->child_stdout, F_SETFL, 0);
269 
270 	for (;;) {
271 		do {
272 			bytes_read = read(data->child_stdout,
273 			    data->child_buf + data->child_buf_avail,
274 			    data->child_buf_len - data->child_buf_avail);
275 		} while (bytes_read == -1 && errno == EINTR);
276 
277 		if (bytes_read == 0 || (bytes_read == -1 && errno == EPIPE))
278 			break;
279 
280 		if (bytes_read == -1) {
281 			archive_set_error(f->archive, errno,
282 			    "Read from filter failed unexpectedly.");
283 			ret = ARCHIVE_FATAL;
284 			goto cleanup;
285 		}
286 		data->child_buf_avail += bytes_read;
287 
288 		ret = __archive_write_filter(f->next_filter,
289 		    data->child_buf, data->child_buf_avail);
290 		if (ret != ARCHIVE_OK) {
291 			ret = ARCHIVE_FATAL;
292 			goto cleanup;
293 		}
294 		data->child_buf_avail = 0;
295 	}
296 
297 cleanup:
298 	/* Shut down the child. */
299 	if (data->child_stdin != -1)
300 		close(data->child_stdin);
301 	if (data->child_stdout != -1)
302 		close(data->child_stdout);
303 	while (waitpid(data->child, &status, 0) == -1 && errno == EINTR)
304 		continue;
305 
306 	if (status != 0) {
307 		archive_set_error(f->archive, EIO,
308 		    "Filter exited with failure.");
309 		ret = ARCHIVE_FATAL;
310 	}
311 	r1 = __archive_write_close_filter(f->next_filter);
312 	return (r1 < ret ? r1 : ret);
313 }
314 
315 static int
316 archive_compressor_program_free(struct archive_write_filter *f)
317 {
318 	struct private_data *data = (struct private_data *)f->data;
319 	free(data->cmd);
320 	free(data->description);
321 	free(data->child_buf);
322 	free(data);
323 	f->data = NULL;
324 	return (ARCHIVE_OK);
325 }
326 
327 #endif /* !defined(HAVE_PIPE) || !defined(HAVE_VFORK) || !defined(HAVE_FCNTL) */
328