suffix.c revision 207753
1207753Smm///////////////////////////////////////////////////////////////////////////////
2207753Smm//
3207753Smm/// \file       suffix.c
4207753Smm/// \brief      Checks filename suffix and creates the destination filename
5207753Smm//
6207753Smm//  Author:     Lasse Collin
7207753Smm//
8207753Smm//  This file has been put into the public domain.
9207753Smm//  You can do whatever you want with this file.
10207753Smm//
11207753Smm///////////////////////////////////////////////////////////////////////////////
12207753Smm
13207753Smm#include "private.h"
14207753Smm
15207753Smm// For case-insensitive filename suffix on case-insensitive systems
16207753Smm#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
17207753Smm#	define strcmp strcasecmp
18207753Smm#endif
19207753Smm
20207753Smm
21207753Smmstatic char *custom_suffix = NULL;
22207753Smm
23207753Smm
24207753Smmstruct suffix_pair {
25207753Smm	const char *compressed;
26207753Smm	const char *uncompressed;
27207753Smm};
28207753Smm
29207753Smm
30207753Smm/// \brief      Checks if src_name has given compressed_suffix
31207753Smm///
32207753Smm/// \param      suffix      Filename suffix to look for
33207753Smm/// \param      src_name    Input filename
34207753Smm/// \param      src_len     strlen(src_name)
35207753Smm///
36207753Smm/// \return     If src_name has the suffix, src_len - strlen(suffix) is
37207753Smm///             returned. It's always a positive integer. Otherwise zero
38207753Smm///             is returned.
39207753Smmstatic size_t
40207753Smmtest_suffix(const char *suffix, const char *src_name, size_t src_len)
41207753Smm{
42207753Smm	const size_t suffix_len = strlen(suffix);
43207753Smm
44207753Smm	// The filename must have at least one character in addition to
45207753Smm	// the suffix. src_name may contain path to the filename, so we
46207753Smm	// need to check for directory separator too.
47207753Smm	if (src_len <= suffix_len || src_name[src_len - suffix_len - 1] == '/')
48207753Smm		return 0;
49207753Smm
50207753Smm	if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
51207753Smm		return src_len - suffix_len;
52207753Smm
53207753Smm	return 0;
54207753Smm}
55207753Smm
56207753Smm
57207753Smm/// \brief      Removes the filename suffix of the compressed file
58207753Smm///
59207753Smm/// \return     Name of the uncompressed file, or NULL if file has unknown
60207753Smm///             suffix.
61207753Smmstatic char *
62207753Smmuncompressed_name(const char *src_name, const size_t src_len)
63207753Smm{
64207753Smm	static const struct suffix_pair suffixes[] = {
65207753Smm		{ ".xz",    "" },
66207753Smm		{ ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
67207753Smm		{ ".lzma",  "" },
68207753Smm		{ ".tlz",   ".tar" },
69207753Smm		// { ".gz",    "" },
70207753Smm		// { ".tgz",   ".tar" },
71207753Smm	};
72207753Smm
73207753Smm	const char *new_suffix = "";
74207753Smm	size_t new_len = 0;
75207753Smm
76207753Smm	if (opt_format == FORMAT_RAW) {
77207753Smm		// Don't check for known suffixes when --format=raw was used.
78207753Smm		if (custom_suffix == NULL) {
79207753Smm			message_error(_("%s: With --format=raw, "
80207753Smm					"--suffix=.SUF is required unless "
81207753Smm					"writing to stdout"), src_name);
82207753Smm			return NULL;
83207753Smm		}
84207753Smm	} else {
85207753Smm		for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
86207753Smm			new_len = test_suffix(suffixes[i].compressed,
87207753Smm					src_name, src_len);
88207753Smm			if (new_len != 0) {
89207753Smm				new_suffix = suffixes[i].uncompressed;
90207753Smm				break;
91207753Smm			}
92207753Smm		}
93207753Smm	}
94207753Smm
95207753Smm	if (new_len == 0 && custom_suffix != NULL)
96207753Smm		new_len = test_suffix(custom_suffix, src_name, src_len);
97207753Smm
98207753Smm	if (new_len == 0) {
99207753Smm		message_warning(_("%s: Filename has an unknown suffix, "
100207753Smm				"skipping"), src_name);
101207753Smm		return NULL;
102207753Smm	}
103207753Smm
104207753Smm	const size_t new_suffix_len = strlen(new_suffix);
105207753Smm	char *dest_name = xmalloc(new_len + new_suffix_len + 1);
106207753Smm
107207753Smm	memcpy(dest_name, src_name, new_len);
108207753Smm	memcpy(dest_name + new_len, new_suffix, new_suffix_len);
109207753Smm	dest_name[new_len + new_suffix_len] = '\0';
110207753Smm
111207753Smm	return dest_name;
112207753Smm}
113207753Smm
114207753Smm
115207753Smm/// \brief      Appends suffix to src_name
116207753Smm///
117207753Smm/// In contrast to uncompressed_name(), we check only suffixes that are valid
118207753Smm/// for the specified file format.
119207753Smmstatic char *
120207753Smmcompressed_name(const char *src_name, const size_t src_len)
121207753Smm{
122207753Smm	// The order of these must match the order in args.h.
123207753Smm	static const struct suffix_pair all_suffixes[][3] = {
124207753Smm		{
125207753Smm			{ ".xz",    "" },
126207753Smm			{ ".txz",   ".tar" },
127207753Smm			{ NULL, NULL }
128207753Smm		}, {
129207753Smm			{ ".lzma",  "" },
130207753Smm			{ ".tlz",   ".tar" },
131207753Smm			{ NULL,     NULL }
132207753Smm/*
133207753Smm		}, {
134207753Smm			{ ".gz",    "" },
135207753Smm			{ ".tgz",   ".tar" },
136207753Smm			{ NULL,     NULL }
137207753Smm*/
138207753Smm		}, {
139207753Smm			// --format=raw requires specifying the suffix
140207753Smm			// manually or using stdout.
141207753Smm			{ NULL,     NULL }
142207753Smm		}
143207753Smm	};
144207753Smm
145207753Smm	// args.c ensures this.
146207753Smm	assert(opt_format != FORMAT_AUTO);
147207753Smm
148207753Smm	const size_t format = opt_format - 1;
149207753Smm	const struct suffix_pair *const suffixes = all_suffixes[format];
150207753Smm
151207753Smm	for (size_t i = 0; suffixes[i].compressed != NULL; ++i) {
152207753Smm		if (test_suffix(suffixes[i].compressed, src_name, src_len)
153207753Smm				!= 0) {
154207753Smm			message_warning(_("%s: File already has `%s' "
155207753Smm					"suffix, skipping"), src_name,
156207753Smm					suffixes[i].compressed);
157207753Smm			return NULL;
158207753Smm		}
159207753Smm	}
160207753Smm
161207753Smm	// TODO: Hmm, maybe it would be better to validate this in args.c,
162207753Smm	// since the suffix handling when decoding is weird now.
163207753Smm	if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
164207753Smm		message_error(_("%s: With --format=raw, "
165207753Smm				"--suffix=.SUF is required unless "
166207753Smm				"writing to stdout"), src_name);
167207753Smm		return NULL;
168207753Smm	}
169207753Smm
170207753Smm	const char *suffix = custom_suffix != NULL
171207753Smm			? custom_suffix : suffixes[0].compressed;
172207753Smm	const size_t suffix_len = strlen(suffix);
173207753Smm
174207753Smm	char *dest_name = xmalloc(src_len + suffix_len + 1);
175207753Smm
176207753Smm	memcpy(dest_name, src_name, src_len);
177207753Smm	memcpy(dest_name + src_len, suffix, suffix_len);
178207753Smm	dest_name[src_len + suffix_len] = '\0';
179207753Smm
180207753Smm	return dest_name;
181207753Smm}
182207753Smm
183207753Smm
184207753Smmextern char *
185207753Smmsuffix_get_dest_name(const char *src_name)
186207753Smm{
187207753Smm	assert(src_name != NULL);
188207753Smm
189207753Smm	// Length of the name is needed in all cases to locate the end of
190207753Smm	// the string to compare the suffix, so calculate the length here.
191207753Smm	const size_t src_len = strlen(src_name);
192207753Smm
193207753Smm	return opt_mode == MODE_COMPRESS
194207753Smm			? compressed_name(src_name, src_len)
195207753Smm			: uncompressed_name(src_name, src_len);
196207753Smm}
197207753Smm
198207753Smm
199207753Smmextern void
200207753Smmsuffix_set(const char *suffix)
201207753Smm{
202207753Smm	// Empty suffix and suffixes having a slash are rejected. Such
203207753Smm	// suffixes would break things later.
204207753Smm	if (suffix[0] == '\0' || strchr(suffix, '/') != NULL)
205207753Smm		message_fatal(_("%s: Invalid filename suffix"), optarg);
206207753Smm
207207753Smm	// Replace the old custom_suffix (if any) with the new suffix.
208207753Smm	free(custom_suffix);
209207753Smm	custom_suffix = xstrdup(suffix);
210207753Smm	return;
211207753Smm}
212