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