suffix.c revision 273498
1///////////////////////////////////////////////////////////////////////////////
2//
3/// \file       suffix.c
4/// \brief      Checks filename suffix and creates the destination filename
5//
6//  Author:     Lasse Collin
7//
8//  This file has been put into the public domain.
9//  You can do whatever you want with this file.
10//
11///////////////////////////////////////////////////////////////////////////////
12
13#include "private.h"
14
15// For case-insensitive filename suffix on case-insensitive systems
16#if defined(TUKLIB_DOSLIKE) || defined(__VMS)
17#	define strcmp strcasecmp
18#endif
19
20
21static char *custom_suffix = NULL;
22
23
24/// \brief      Test if the char is a directory separator
25static bool
26is_dir_sep(char c)
27{
28#ifdef TUKLIB_DOSLIKE
29	return c == '/' || c == '\\' || c == ':';
30#else
31	return c == '/';
32#endif
33}
34
35
36/// \brief      Test if the string contains a directory separator
37static bool
38has_dir_sep(const char *str)
39{
40#ifdef TUKLIB_DOSLIKE
41	return strpbrk(str, "/\\:") != NULL;
42#else
43	return strchr(str, '/') != NULL;
44#endif
45}
46
47
48/// \brief      Checks if src_name has given compressed_suffix
49///
50/// \param      suffix      Filename suffix to look for
51/// \param      src_name    Input filename
52/// \param      src_len     strlen(src_name)
53///
54/// \return     If src_name has the suffix, src_len - strlen(suffix) is
55///             returned. It's always a positive integer. Otherwise zero
56///             is returned.
57static size_t
58test_suffix(const char *suffix, const char *src_name, size_t src_len)
59{
60	const size_t suffix_len = strlen(suffix);
61
62	// The filename must have at least one character in addition to
63	// the suffix. src_name may contain path to the filename, so we
64	// need to check for directory separator too.
65	if (src_len <= suffix_len
66			|| is_dir_sep(src_name[src_len - suffix_len - 1]))
67		return 0;
68
69	if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
70		return src_len - suffix_len;
71
72	return 0;
73}
74
75
76/// \brief      Removes the filename suffix of the compressed file
77///
78/// \return     Name of the uncompressed file, or NULL if file has unknown
79///             suffix.
80static char *
81uncompressed_name(const char *src_name, const size_t src_len)
82{
83	static const struct {
84		const char *compressed;
85		const char *uncompressed;
86	} suffixes[] = {
87		{ ".xz",    "" },
88		{ ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
89		{ ".lzma",  "" },
90		{ ".tlz",   ".tar" },
91		// { ".gz",    "" },
92		// { ".tgz",   ".tar" },
93	};
94
95	const char *new_suffix = "";
96	size_t new_len = 0;
97
98	if (opt_format == FORMAT_RAW) {
99		// Don't check for known suffixes when --format=raw was used.
100		if (custom_suffix == NULL) {
101			message_error(_("%s: With --format=raw, "
102					"--suffix=.SUF is required unless "
103					"writing to stdout"), src_name);
104			return NULL;
105		}
106	} else {
107		for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
108			new_len = test_suffix(suffixes[i].compressed,
109					src_name, src_len);
110			if (new_len != 0) {
111				new_suffix = suffixes[i].uncompressed;
112				break;
113			}
114		}
115	}
116
117	if (new_len == 0 && custom_suffix != NULL)
118		new_len = test_suffix(custom_suffix, src_name, src_len);
119
120	if (new_len == 0) {
121		message_warning(_("%s: Filename has an unknown suffix, "
122				"skipping"), src_name);
123		return NULL;
124	}
125
126	const size_t new_suffix_len = strlen(new_suffix);
127	char *dest_name = xmalloc(new_len + new_suffix_len + 1);
128
129	memcpy(dest_name, src_name, new_len);
130	memcpy(dest_name + new_len, new_suffix, new_suffix_len);
131	dest_name[new_len + new_suffix_len] = '\0';
132
133	return dest_name;
134}
135
136
137/// \brief      Appends suffix to src_name
138///
139/// In contrast to uncompressed_name(), we check only suffixes that are valid
140/// for the specified file format.
141static char *
142compressed_name(const char *src_name, const size_t src_len)
143{
144	// The order of these must match the order in args.h.
145	static const char *const all_suffixes[][3] = {
146		{
147			".xz",
148			".txz",
149			NULL
150		}, {
151			".lzma",
152			".tlz",
153			NULL
154/*
155		}, {
156			".gz",
157			".tgz",
158			NULL
159*/
160		}, {
161			// --format=raw requires specifying the suffix
162			// manually or using stdout.
163			NULL
164		}
165	};
166
167	// args.c ensures this.
168	assert(opt_format != FORMAT_AUTO);
169
170	const size_t format = opt_format - 1;
171	const char *const *suffixes = all_suffixes[format];
172
173	for (size_t i = 0; suffixes[i] != NULL; ++i) {
174		if (test_suffix(suffixes[i], src_name, src_len) != 0) {
175			message_warning(_("%s: File already has `%s' "
176					"suffix, skipping"), src_name,
177					suffixes[i]);
178			return NULL;
179		}
180	}
181
182	if (custom_suffix != NULL) {
183		if (test_suffix(custom_suffix, src_name, src_len) != 0) {
184			message_warning(_("%s: File already has `%s' "
185					"suffix, skipping"), src_name,
186					custom_suffix);
187			return NULL;
188		}
189	}
190
191	// TODO: Hmm, maybe it would be better to validate this in args.c,
192	// since the suffix handling when decoding is weird now.
193	if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
194		message_error(_("%s: With --format=raw, "
195				"--suffix=.SUF is required unless "
196				"writing to stdout"), src_name);
197		return NULL;
198	}
199
200	const char *suffix = custom_suffix != NULL
201			? custom_suffix : suffixes[0];
202	const size_t suffix_len = strlen(suffix);
203
204	char *dest_name = xmalloc(src_len + suffix_len + 1);
205
206	memcpy(dest_name, src_name, src_len);
207	memcpy(dest_name + src_len, suffix, suffix_len);
208	dest_name[src_len + suffix_len] = '\0';
209
210	return dest_name;
211}
212
213
214extern char *
215suffix_get_dest_name(const char *src_name)
216{
217	assert(src_name != NULL);
218
219	// Length of the name is needed in all cases to locate the end of
220	// the string to compare the suffix, so calculate the length here.
221	const size_t src_len = strlen(src_name);
222
223	return opt_mode == MODE_COMPRESS
224			? compressed_name(src_name, src_len)
225			: uncompressed_name(src_name, src_len);
226}
227
228
229extern void
230suffix_set(const char *suffix)
231{
232	// Empty suffix and suffixes having a directory separator are
233	// rejected. Such suffixes would break things later.
234	if (suffix[0] == '\0' || has_dir_sep(suffix))
235		message_fatal(_("%s: Invalid filename suffix"), suffix);
236
237	// Replace the old custom_suffix (if any) with the new suffix.
238	free(custom_suffix);
239	custom_suffix = xstrdup(suffix);
240	return;
241}
242