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