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