]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/xz/src/xz/suffix.c
MFV: xz 5.4.2.
[FreeBSD/FreeBSD.git] / contrib / xz / src / xz / suffix.c
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 #ifdef __DJGPP__
16 #       include <fcntl.h>
17 #endif
18
19 // For case-insensitive filename suffix on case-insensitive systems
20 #if defined(TUKLIB_DOSLIKE) || defined(__VMS)
21 #       ifdef HAVE_STRINGS_H
22 #               include <strings.h>
23 #       endif
24 #       define strcmp strcasecmp
25 #endif
26
27
28 static char *custom_suffix = NULL;
29
30
31 /// \brief      Test if the char is a directory separator
32 static bool
33 is_dir_sep(char c)
34 {
35 #ifdef TUKLIB_DOSLIKE
36         return c == '/' || c == '\\' || c == ':';
37 #else
38         return c == '/';
39 #endif
40 }
41
42
43 /// \brief      Test if the string contains a directory separator
44 static bool
45 has_dir_sep(const char *str)
46 {
47 #ifdef TUKLIB_DOSLIKE
48         return strpbrk(str, "/\\:") != NULL;
49 #else
50         return strchr(str, '/') != NULL;
51 #endif
52 }
53
54
55 #ifdef __DJGPP__
56 /// \brief      Test for special suffix used for 8.3 short filenames (SFN)
57 ///
58 /// \return     If str matches *.?- or *.??-, true is returned. Otherwise
59 ///             false is returned.
60 static bool
61 has_sfn_suffix(const char *str, size_t len)
62 {
63         if (len >= 4 && str[len - 1] == '-' && str[len - 2] != '.'
64                         && !is_dir_sep(str[len - 2])) {
65                 // *.?-
66                 if (str[len - 3] == '.')
67                         return !is_dir_sep(str[len - 4]);
68
69                 // *.??-
70                 if (len >= 5 && !is_dir_sep(str[len - 3])
71                                 && str[len - 4] == '.')
72                         return !is_dir_sep(str[len - 5]);
73         }
74
75         return false;
76 }
77 #endif
78
79
80 /// \brief      Checks if src_name has given compressed_suffix
81 ///
82 /// \param      suffix      Filename suffix to look for
83 /// \param      src_name    Input filename
84 /// \param      src_len     strlen(src_name)
85 ///
86 /// \return     If src_name has the suffix, src_len - strlen(suffix) is
87 ///             returned. It's always a positive integer. Otherwise zero
88 ///             is returned.
89 static size_t
90 test_suffix(const char *suffix, const char *src_name, size_t src_len)
91 {
92         const size_t suffix_len = strlen(suffix);
93
94         // The filename must have at least one character in addition to
95         // the suffix. src_name may contain path to the filename, so we
96         // need to check for directory separator too.
97         if (src_len <= suffix_len
98                         || is_dir_sep(src_name[src_len - suffix_len - 1]))
99                 return 0;
100
101         if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
102                 return src_len - suffix_len;
103
104         return 0;
105 }
106
107
108 /// \brief      Removes the filename suffix of the compressed file
109 ///
110 /// \return     Name of the uncompressed file, or NULL if file has unknown
111 ///             suffix.
112 static char *
113 uncompressed_name(const char *src_name, const size_t src_len)
114 {
115         static const struct {
116                 const char *compressed;
117                 const char *uncompressed;
118         } suffixes[] = {
119                 { ".xz",    "" },
120                 { ".txz",   ".tar" }, // .txz abbreviation for .txt.gz is rare.
121                 { ".lzma",  "" },
122 #ifdef __DJGPP__
123                 { ".lzm",   "" },
124 #endif
125                 { ".tlz",   ".tar" }, // Both .tar.lzma and .tar.lz
126 #ifdef HAVE_LZIP_DECODER
127                 { ".lz",    "" },
128 #endif
129         };
130
131         const char *new_suffix = "";
132         size_t new_len = 0;
133
134         if (opt_format != FORMAT_RAW) {
135                 for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
136                         new_len = test_suffix(suffixes[i].compressed,
137                                         src_name, src_len);
138                         if (new_len != 0) {
139                                 new_suffix = suffixes[i].uncompressed;
140                                 break;
141                         }
142                 }
143
144 #ifdef __DJGPP__
145                 // Support also *.?- -> *.? and *.??- -> *.?? on DOS.
146                 // This is done also when long filenames are available
147                 // to keep it easy to decompress files created when
148                 // long filename support wasn't available.
149                 if (new_len == 0 && has_sfn_suffix(src_name, src_len)) {
150                         new_suffix = "";
151                         new_len = src_len - 1;
152                 }
153 #endif
154         }
155
156         if (new_len == 0 && custom_suffix != NULL)
157                 new_len = test_suffix(custom_suffix, src_name, src_len);
158
159         if (new_len == 0) {
160                 message_warning(_("%s: Filename has an unknown suffix, "
161                                 "skipping"), src_name);
162                 return NULL;
163         }
164
165         const size_t new_suffix_len = strlen(new_suffix);
166         char *dest_name = xmalloc(new_len + new_suffix_len + 1);
167
168         memcpy(dest_name, src_name, new_len);
169         memcpy(dest_name + new_len, new_suffix, new_suffix_len);
170         dest_name[new_len + new_suffix_len] = '\0';
171
172         return dest_name;
173 }
174
175
176 /// This message is needed in multiple places in compressed_name(),
177 /// so the message has been put into its own function.
178 static void
179 msg_suffix(const char *src_name, const char *suffix)
180 {
181         message_warning(_("%s: File already has `%s' suffix, skipping"),
182                         src_name, suffix);
183         return;
184 }
185
186
187 /// \brief      Appends suffix to src_name
188 ///
189 /// In contrast to uncompressed_name(), we check only suffixes that are valid
190 /// for the specified file format.
191 static char *
192 compressed_name(const char *src_name, size_t src_len)
193 {
194         // The order of these must match the order in args.h.
195         static const char *const all_suffixes[][4] = {
196                 {
197                         ".xz",
198                         ".txz",
199                         NULL
200                 }, {
201                         ".lzma",
202 #ifdef __DJGPP__
203                         ".lzm",
204 #endif
205                         ".tlz",
206                         NULL
207 #ifdef HAVE_LZIP_DECODER
208                 // This is needed to keep the table indexing in sync with
209                 // enum format_type from coder.h.
210                 }, {
211 /*
212                         ".lz",
213 */
214                         NULL
215 #endif
216                 }, {
217                         // --format=raw requires specifying the suffix
218                         // manually or using stdout.
219                         NULL
220                 }
221         };
222
223         // args.c ensures these.
224         assert(opt_format != FORMAT_AUTO);
225 #ifdef HAVE_LZIP_DECODER
226         assert(opt_format != FORMAT_LZIP);
227 #endif
228
229         const size_t format = opt_format - 1;
230         const char *const *suffixes = all_suffixes[format];
231
232         // Look for known filename suffixes and refuse to compress them.
233         for (size_t i = 0; suffixes[i] != NULL; ++i) {
234                 if (test_suffix(suffixes[i], src_name, src_len) != 0) {
235                         msg_suffix(src_name, suffixes[i]);
236                         return NULL;
237                 }
238         }
239
240 #ifdef __DJGPP__
241         // Recognize also the special suffix that is used when long
242         // filename (LFN) support isn't available. This suffix is
243         // recognized on LFN systems too.
244         if (opt_format == FORMAT_XZ && has_sfn_suffix(src_name, src_len)) {
245                 msg_suffix(src_name, "-");
246                 return NULL;
247         }
248 #endif
249
250         if (custom_suffix != NULL) {
251                 if (test_suffix(custom_suffix, src_name, src_len) != 0) {
252                         msg_suffix(src_name, custom_suffix);
253                         return NULL;
254                 }
255         }
256
257         const char *suffix = custom_suffix != NULL
258                         ? custom_suffix : suffixes[0];
259         size_t suffix_len = strlen(suffix);
260
261 #ifdef __DJGPP__
262         if (!_use_lfn(src_name)) {
263                 // Long filename (LFN) support isn't available and we are
264                 // limited to 8.3 short filenames (SFN).
265                 //
266                 // Look for suffix separator from the filename, and make sure
267                 // that it is in the filename, not in a directory name.
268                 const char *sufsep = strrchr(src_name, '.');
269                 if (sufsep == NULL || sufsep[1] == '\0'
270                                 || has_dir_sep(sufsep)) {
271                         // src_name has no filename extension.
272                         //
273                         // Examples:
274                         // xz foo         -> foo.xz
275                         // xz -F lzma foo -> foo.lzm
276                         // xz -S x foo    -> foox
277                         // xz -S x foo.   -> foo.x
278                         // xz -S x.y foo  -> foox.y
279                         // xz -S .x foo   -> foo.x
280                         // xz -S .x foo.  -> foo.x
281                         //
282                         // Avoid double dots:
283                         if (sufsep != NULL && sufsep[1] == '\0'
284                                         && suffix[0] == '.')
285                                 --src_len;
286
287                 } else if (custom_suffix == NULL
288                                 && strcasecmp(sufsep, ".tar") == 0) {
289                         // ".tar" is handled specially.
290                         //
291                         // Examples:
292                         // xz foo.tar          -> foo.txz
293                         // xz -F lzma foo.tar  -> foo.tlz
294                         static const char *const tar_suffixes[] = {
295                                 ".txz", // .tar.xz
296                                 ".tlz", // .tar.lzma
297 /*
298                                 ".tlz", // .tar.lz
299 */
300                         };
301                         suffix = tar_suffixes[format];
302                         suffix_len = 4;
303                         src_len -= 4;
304
305                 } else {
306                         if (custom_suffix == NULL && opt_format == FORMAT_XZ) {
307                                 // Instead of the .xz suffix, use a single
308                                 // character at the end of the filename
309                                 // extension. This is to minimize name
310                                 // conflicts when compressing multiple files
311                                 // with the same basename. E.g. foo.txt and
312                                 // foo.exe become foo.tx- and foo.ex-. Dash
313                                 // is rare as the last character of the
314                                 // filename extension, so it seems to be
315                                 // quite safe choice and it stands out better
316                                 // in directory listings than e.g. x. For
317                                 // comparison, gzip uses z.
318                                 suffix = "-";
319                                 suffix_len = 1;
320                         }
321
322                         if (suffix[0] == '.') {
323                                 // The first character of the suffix is a dot.
324                                 // Throw away the original filename extension
325                                 // and replace it with the new suffix.
326                                 //
327                                 // Examples:
328                                 // xz -F lzma foo.txt  -> foo.lzm
329                                 // xz -S .x  foo.txt   -> foo.x
330                                 src_len = sufsep - src_name;
331
332                         } else {
333                                 // The first character of the suffix is not
334                                 // a dot. Preserve the first 0-2 characters
335                                 // of the original filename extension.
336                                 //
337                                 // Examples:
338                                 // xz foo.txt         -> foo.tx-
339                                 // xz -S x  foo.c     -> foo.cx
340                                 // xz -S ab foo.c     -> foo.cab
341                                 // xz -S ab foo.txt   -> foo.tab
342                                 // xz -S abc foo.txt  -> foo.abc
343                                 //
344                                 // Truncate the suffix to three chars:
345                                 if (suffix_len > 3)
346                                         suffix_len = 3;
347
348                                 // If needed, overwrite 1-3 characters.
349                                 if (strlen(sufsep) > 4 - suffix_len)
350                                         src_len = sufsep - src_name
351                                                         + 4 - suffix_len;
352                         }
353                 }
354         }
355 #endif
356
357         char *dest_name = xmalloc(src_len + suffix_len + 1);
358
359         memcpy(dest_name, src_name, src_len);
360         memcpy(dest_name + src_len, suffix, suffix_len);
361         dest_name[src_len + suffix_len] = '\0';
362
363         return dest_name;
364 }
365
366
367 extern char *
368 suffix_get_dest_name(const char *src_name)
369 {
370         assert(src_name != NULL);
371
372         // Length of the name is needed in all cases to locate the end of
373         // the string to compare the suffix, so calculate the length here.
374         const size_t src_len = strlen(src_name);
375
376         return opt_mode == MODE_COMPRESS
377                         ? compressed_name(src_name, src_len)
378                         : uncompressed_name(src_name, src_len);
379 }
380
381
382 extern void
383 suffix_set(const char *suffix)
384 {
385         // Empty suffix and suffixes having a directory separator are
386         // rejected. Such suffixes would break things later.
387         if (suffix[0] == '\0' || has_dir_sep(suffix))
388                 message_fatal(_("%s: Invalid filename suffix"), suffix);
389
390         // Replace the old custom_suffix (if any) with the new suffix.
391         free(custom_suffix);
392         custom_suffix = xstrdup(suffix);
393         return;
394 }
395
396
397 extern bool
398 suffix_is_set(void)
399 {
400         return custom_suffix != NULL;
401 }