1 /*-
2  * Copyright (c) 2012 Michihiro NAKAJIMA
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 "bsdtar_platform.h"
27 __FBSDID("$FreeBSD$");
28 
29 #ifdef HAVE_STDLIB_H
30 #include <stdlib.h>
31 #endif
32 #ifdef HAVE_STRING_H
33 #include <string.h>
34 #endif
35 
36 #include "bsdtar.h"
37 #include "err.h"
38 
39 struct creation_set {
40 	char		 *create_format;
41 	struct filter_set {
42 		int	  program;	/* Set 1 if filter is a program name */
43 		char	 *filter_name;
44 	}		 *filters;
45 	int		  filter_count;
46 };
47 
48 struct suffix_code_t {
49 	const char *suffix;
50 	const char *form;
51 };
52 
53 static const char *
54 get_suffix_code(const struct suffix_code_t *tbl, const char *suffix)
55 {
56 	int i;
57 
58 	if (suffix == NULL)
59 		return (NULL);
60 	for (i = 0; tbl[i].suffix != NULL; i++) {
61 		if (strcmp(tbl[i].suffix, suffix) == 0)
62 			return (tbl[i].form);
63 	}
64 	return (NULL);
65 }
66 
67 static const char *
68 get_filter_code(const char *suffix)
69 {
70 	/* A pair of suffix and compression/filter. */
71 	static const struct suffix_code_t filters[] = {
72 		{ ".Z",		"compress" },
73 		{ ".bz2",	"bzip2" },
74 		{ ".gz",	"gzip" },
75 		{ ".grz",	"grzip" },
76 		{ ".lrz",	"lrzip" },
77 		{ ".lz",	"lzip" },
78 		{ ".lzo",	"lzop" },
79 		{ ".lzma",	"lzma" },
80 		{ ".uu",	"uuencode" },
81 		{ ".xz",	"xz" },
82 		{ NULL,		NULL }
83 	};
84 
85 	return get_suffix_code(filters, suffix);
86 }
87 
88 static const char *
89 get_format_code(const char *suffix)
90 {
91 	/* A pair of suffix and format. */
92 	static const struct suffix_code_t formats[] = {
93 		{ ".7z",	"7zip" },
94 		{ ".ar",	"arbsd" },
95 		{ ".cpio",	"cpio" },
96 		{ ".iso",	"iso9960" },
97 		{ ".mtree",	"mtree" },
98 		{ ".shar",	"shar" },
99 		{ ".tar",	"paxr" },
100 		{ ".xar",	"xar" },
101 		{ ".zip",	"zip" },
102 		{ NULL,		NULL }
103 	};
104 
105 	return get_suffix_code(formats, suffix);
106 }
107 
108 static const char *
109 decompose_alias(const char *suffix)
110 {
111 	static const struct suffix_code_t alias[] = {
112 		{ ".taz",	".tar.gz" },
113 		{ ".tgz",	".tar.gz" },
114 		{ ".tbz",	".tar.bz2" },
115 		{ ".tbz2",	".tar.bz2" },
116 		{ ".tz2",	".tar.bz2" },
117 		{ ".tlz",	".tar.lzma" },
118 		{ ".txz",	".tar.xz" },
119 		{ ".tzo",	".tar.lzo" },
120 		{ ".taZ",	".tar.Z" },
121 		{ ".tZ",	".tar.Z" },
122 		{ NULL,		NULL }
123 	};
124 
125 	return get_suffix_code(alias, suffix);
126 }
127 
128 static void
129 _cset_add_filter(struct creation_set *cset, int program, const char *filter)
130 {
131 	struct filter_set *new_ptr;
132 	char *new_filter;
133 
134 	new_ptr = (struct filter_set *)realloc(cset->filters,
135 	    sizeof(*cset->filters) * (cset->filter_count + 1));
136 	if (new_ptr == NULL)
137 		lafe_errc(1, 0, "No memory");
138 	new_filter = strdup(filter);
139 	if (new_filter == NULL)
140 		lafe_errc(1, 0, "No memory");
141 	cset->filters = new_ptr;
142 	cset->filters[cset->filter_count].program = program;
143 	cset->filters[cset->filter_count].filter_name = new_filter;
144 	cset->filter_count++;
145 }
146 
147 void
148 cset_add_filter(struct creation_set *cset, const char *filter)
149 {
150 	_cset_add_filter(cset, 0, filter);
151 }
152 
153 void
154 cset_add_filter_program(struct creation_set *cset, const char *filter)
155 {
156 	_cset_add_filter(cset, 1, filter);
157 }
158 
159 int
160 cset_read_support_filter_program(struct creation_set *cset, struct archive *a)
161 {
162 	int cnt = 0, i;
163 
164 	for (i = 0; i < cset->filter_count; i++) {
165 		if (cset->filters[i].program) {
166 			archive_read_support_filter_program(a,
167 			    cset->filters[i].filter_name);
168 			++cnt;
169 		}
170 	}
171 	return (cnt);
172 }
173 
174 int
175 cset_write_add_filters(struct creation_set *cset, struct archive *a,
176     const void **filter_name)
177 {
178 	int cnt = 0, i, r;
179 
180 	for (i = 0; i < cset->filter_count; i++) {
181 		if (cset->filters[i].program)
182 			r = archive_write_add_filter_program(a,
183 				cset->filters[i].filter_name);
184 		else
185 			r = archive_write_add_filter_by_name(a,
186 				cset->filters[i].filter_name);
187 		if (r < ARCHIVE_WARN) {
188 			*filter_name = cset->filters[i].filter_name;
189 			return (r);
190 		}
191 		++cnt;
192 	}
193 	return (cnt);
194 }
195 
196 void
197 cset_set_format(struct creation_set *cset, const char *format)
198 {
199 	char *f;
200 
201 	f = strdup(format);
202 	if (f == NULL)
203 		lafe_errc(1, 0, "No memory");
204 	free(cset->create_format);
205 	cset->create_format = f;
206 }
207 
208 const char *
209 cset_get_format(struct creation_set *cset)
210 {
211 	return (cset->create_format);
212 }
213 
214 static void
215 _cleanup_filters(struct filter_set *filters, int count)
216 {
217 	int i;
218 
219 	for (i = 0; i < count; i++)
220 		free(filters[i].filter_name);
221 	free(filters);
222 }
223 
224 /*
225  * Clean up a creation set.
226  */
227 void
228 cset_free(struct creation_set *cset)
229 {
230 	_cleanup_filters(cset->filters, cset->filter_count);
231 	free(cset->create_format);
232 	free(cset);
233 }
234 
235 struct creation_set *
236 cset_new(void)
237 {
238 	return calloc(1, sizeof(struct creation_set));
239 }
240 
241 /*
242  * Build a creation set by a file name suffix.
243  */
244 int
245 cset_auto_compress(struct creation_set *cset, const char *filename)
246 {
247 	struct filter_set *old_filters;
248 	char *name, *p;
249 	const char *code;
250 	int old_filter_count;
251 
252 	name = strdup(filename);
253 	if (name == NULL)
254 		lafe_errc(1, 0, "No memory");
255 	/* Save previous filters. */
256 	old_filters = cset->filters;
257 	old_filter_count = cset->filter_count;
258 	cset->filters = NULL;
259 	cset->filter_count = 0;
260 
261 	for (;;) {
262 		/* Get the suffix. */
263 		p = strrchr(name, '.');
264 		if (p == NULL)
265 			break;
266 		/* Suppose it indicates compression/filter type
267 		 * such as ".gz". */
268 		code = get_filter_code(p);
269 		if (code != NULL) {
270 			cset_add_filter(cset, code);
271 			*p = '\0';
272 			continue;
273 		}
274 		/* Suppose it indicates format type such as ".tar". */
275 		code = get_format_code(p);
276 		if (code != NULL) {
277 			cset_set_format(cset, code);
278 			break;
279 		}
280 		/* Suppose it indicates alias such as ".tgz". */
281 		code = decompose_alias(p);
282 		if (code == NULL)
283 			break;
284 		/* Replace the suffix. */
285 		*p = '\0';
286 		name = realloc(name, strlen(name) + strlen(code) + 1);
287 		if (name == NULL)
288 			lafe_errc(1, 0, "No memory");
289 		strcat(name, code);
290 	}
291 	free(name);
292 	if (cset->filters) {
293 		struct filter_set *v;
294 		int i, r;
295 
296 		/* Release previos filters. */
297 		_cleanup_filters(old_filters, old_filter_count);
298 
299 		v = malloc(sizeof(*v) * cset->filter_count);
300 		if (v == NULL)
301 			lafe_errc(1, 0, "No memory");
302 		/* Reverse filter sequence. */
303 		for (i = 0, r = cset->filter_count; r > 0; )
304 			v[i++] = cset->filters[--r];
305 		free(cset->filters);
306 		cset->filters = v;
307 		return (1);
308 	} else {
309 		/* Put previos filters back. */
310 		cset->filters = old_filters;
311 		cset->filter_count = old_filter_count;
312 		return (0);
313 	}
314 }
315