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 	for (;;) {
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,
193 				data->child_stdout);
194 			continue;
195 		}
196 
197 		do {
198 			ret = read(data->child_stdout,
199 			    data->child_buf + data->child_buf_avail,
200 			    data->child_buf_len - data->child_buf_avail);
201 		} while (ret == -1 && errno == EINTR);
202 
203 		if (ret == 0 || (ret == -1 && errno == EPIPE)) {
204 			close(data->child_stdout);
205 			data->child_stdout = -1;
206 			fcntl(data->child_stdin, F_SETFL, 0);
207 			continue;
208 		}
209 		if (ret == -1 && errno == EAGAIN) {
210 			__archive_check_child(data->child_stdin,
211 				data->child_stdout);
212 			continue;
213 		}
214 		if (ret == -1)
215 			return (-1);
216 
217 		data->child_buf_avail += ret;
218 
219 		ret = __archive_write_filter(f->next_filter,
220 		    data->child_buf, data->child_buf_avail);
221 		if (ret <= 0)
222 			return (-1);
223 
224 		if ((size_t)ret < data->child_buf_avail) {
225 			memmove(data->child_buf, data->child_buf + ret,
226 			    data->child_buf_avail - ret);
227 		}
228 		data->child_buf_avail -= ret;
229 	}
230 }
231 
232 /*
233  * Write data to the compressed stream.
234  */
235 static int
236 archive_compressor_program_write(struct archive_write_filter *f,
237     const void *buff, size_t length)
238 {
239 	ssize_t ret;
240 	const char *buf;
241 
242 	buf = buff;
243 	while (length > 0) {
244 		ret = child_write(f, buf, length);
245 		if (ret == -1 || ret == 0) {
246 			archive_set_error(f->archive, EIO,
247 			    "Can't write to filter");
248 			return (ARCHIVE_FATAL);
249 		}
250 		length -= ret;
251 		buf += ret;
252 	}
253 	return (ARCHIVE_OK);
254 }
255 
256 
257 /*
258  * Finish the compression...
259  */
260 static int
261 archive_compressor_program_close(struct archive_write_filter *f)
262 {
263 	struct private_data *data = (struct private_data *)f->data;
264 	int ret, r1, status;
265 	ssize_t bytes_read;
266 
267 	ret = 0;
268 	close(data->child_stdin);
269 	data->child_stdin = -1;
270 	fcntl(data->child_stdout, F_SETFL, 0);
271 
272 	for (;;) {
273 		do {
274 			bytes_read = read(data->child_stdout,
275 			    data->child_buf + data->child_buf_avail,
276 			    data->child_buf_len - data->child_buf_avail);
277 		} while (bytes_read == -1 && errno == EINTR);
278 
279 		if (bytes_read == 0 || (bytes_read == -1 && errno == EPIPE))
280 			break;
281 
282 		if (bytes_read == -1) {
283 			archive_set_error(f->archive, errno,
284 			    "Read from filter failed unexpectedly.");
285 			ret = ARCHIVE_FATAL;
286 			goto cleanup;
287 		}
288 		data->child_buf_avail += bytes_read;
289 
290 		ret = __archive_write_filter(f->next_filter,
291 		    data->child_buf, data->child_buf_avail);
292 		if (ret != ARCHIVE_OK) {
293 			ret = ARCHIVE_FATAL;
294 			goto cleanup;
295 		}
296 		data->child_buf_avail = 0;
297 	}
298 
299 cleanup:
300 	/* Shut down the child. */
301 	if (data->child_stdin != -1)
302 		close(data->child_stdin);
303 	if (data->child_stdout != -1)
304 		close(data->child_stdout);
305 	while (waitpid(data->child, &status, 0) == -1 && errno == EINTR)
306 		continue;
307 
308 	if (status != 0) {
309 		archive_set_error(f->archive, EIO,
310 		    "Filter exited with failure.");
311 		ret = ARCHIVE_FATAL;
312 	}
313 	r1 = __archive_write_close_filter(f->next_filter);
314 	return (r1 < ret ? r1 : ret);
315 }
316 
317 static int
318 archive_compressor_program_free(struct archive_write_filter *f)
319 {
320 	struct private_data *data = (struct private_data *)f->data;
321 	free(data->cmd);
322 	free(data->description);
323 	free(data->child_buf);
324 	free(data);
325 	f->data = NULL;
326 	return (ARCHIVE_OK);
327 }
328 
329 #endif /* !defined(HAVE_PIPE) || !defined(HAVE_VFORK) || !defined(HAVE_FCNTL) */
330