Rizin
unix-like reverse engineering framework and cli tools
suffix.c
Go to the documentation of this file.
1 //
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 //
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 # define strcmp strcasecmp
22 #endif
23 
24 
25 static char *custom_suffix = NULL;
26 
27 
29 static bool
31 {
32 #ifdef TUKLIB_DOSLIKE
33  return c == '/' || c == '\\' || c == ':';
34 #else
35  return c == '/';
36 #endif
37 }
38 
39 
41 static bool
42 has_dir_sep(const char *str)
43 {
44 #ifdef TUKLIB_DOSLIKE
45  return strpbrk(str, "/\\:") != NULL;
46 #else
47  return strchr(str, '/') != NULL;
48 #endif
49 }
50 
51 
52 #ifdef __DJGPP__
57 static bool
58 has_sfn_suffix(const char *str, size_t len)
59 {
60  if (len >= 4 && str[len - 1] == '-' && str[len - 2] != '.'
61  && !is_dir_sep(str[len - 2])) {
62  // *.?-
63  if (str[len - 3] == '.')
64  return !is_dir_sep(str[len - 4]);
65 
66  // *.??-
67  if (len >= 5 && !is_dir_sep(str[len - 3])
68  && str[len - 4] == '.')
69  return !is_dir_sep(str[len - 5]);
70  }
71 
72  return false;
73 }
74 #endif
75 
76 
86 static size_t
87 test_suffix(const char *suffix, const char *src_name, size_t src_len)
88 {
89  const size_t suffix_len = strlen(suffix);
90 
91  // The filename must have at least one character in addition to
92  // the suffix. src_name may contain path to the filename, so we
93  // need to check for directory separator too.
94  if (src_len <= suffix_len
95  || is_dir_sep(src_name[src_len - suffix_len - 1]))
96  return 0;
97 
98  if (strcmp(suffix, src_name + src_len - suffix_len) == 0)
99  return src_len - suffix_len;
100 
101  return 0;
102 }
103 
104 
109 static char *
110 uncompressed_name(const char *src_name, const size_t src_len)
111 {
112  static const struct {
113  const char *compressed;
114  const char *uncompressed;
115  } suffixes[] = {
116  { ".xz", "" },
117  { ".txz", ".tar" }, // .txz abbreviation for .txt.gz is rare.
118  { ".lzma", "" },
119 #ifdef __DJGPP__
120  { ".lzm", "" },
121 #endif
122  { ".tlz", ".tar" },
123  // { ".gz", "" },
124  // { ".tgz", ".tar" },
125  };
126 
127  const char *new_suffix = "";
128  size_t new_len = 0;
129 
130  if (opt_format == FORMAT_RAW) {
131  // Don't check for known suffixes when --format=raw was used.
132  if (custom_suffix == NULL) {
133  message_error(_("%s: With --format=raw, "
134  "--suffix=.SUF is required unless "
135  "writing to stdout"), src_name);
136  return NULL;
137  }
138  } else {
139  for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) {
140  new_len = test_suffix(suffixes[i].compressed,
141  src_name, src_len);
142  if (new_len != 0) {
143  new_suffix = suffixes[i].uncompressed;
144  break;
145  }
146  }
147 
148 #ifdef __DJGPP__
149  // Support also *.?- -> *.? and *.??- -> *.?? on DOS.
150  // This is done also when long filenames are available
151  // to keep it easy to decompress files created when
152  // long filename support wasn't available.
153  if (new_len == 0 && has_sfn_suffix(src_name, src_len)) {
154  new_suffix = "";
155  new_len = src_len - 1;
156  }
157 #endif
158  }
159 
160  if (new_len == 0 && custom_suffix != NULL)
161  new_len = test_suffix(custom_suffix, src_name, src_len);
162 
163  if (new_len == 0) {
164  message_warning(_("%s: Filename has an unknown suffix, "
165  "skipping"), src_name);
166  return NULL;
167  }
168 
169  const size_t new_suffix_len = strlen(new_suffix);
170  char *dest_name = xmalloc(new_len + new_suffix_len + 1);
171 
172  memcpy(dest_name, src_name, new_len);
173  memcpy(dest_name + new_len, new_suffix, new_suffix_len);
174  dest_name[new_len + new_suffix_len] = '\0';
175 
176  return dest_name;
177 }
178 
179 
182 static void
183 msg_suffix(const char *src_name, const char *suffix)
184 {
185  message_warning(_("%s: File already has `%s' suffix, skipping"),
186  src_name, suffix);
187  return;
188 }
189 
190 
195 static char *
196 compressed_name(const char *src_name, size_t src_len)
197 {
198  // The order of these must match the order in args.h.
199  static const char *const all_suffixes[][4] = {
200  {
201  ".xz",
202  ".txz",
203  NULL
204  }, {
205  ".lzma",
206 #ifdef __DJGPP__
207  ".lzm",
208 #endif
209  ".tlz",
210  NULL
211 /*
212  }, {
213  ".gz",
214  ".tgz",
215  NULL
216 */
217  }, {
218  // --format=raw requires specifying the suffix
219  // manually or using stdout.
220  NULL
221  }
222  };
223 
224  // args.c ensures this.
226 
227  const size_t format = opt_format - 1;
228  const char *const *suffixes = all_suffixes[format];
229 
230  // Look for known filename suffixes and refuse to compress them.
231  for (size_t i = 0; suffixes[i] != NULL; ++i) {
232  if (test_suffix(suffixes[i], src_name, src_len) != 0) {
233  msg_suffix(src_name, suffixes[i]);
234  return NULL;
235  }
236  }
237 
238 #ifdef __DJGPP__
239  // Recognize also the special suffix that is used when long
240  // filename (LFN) support isn't available. This suffix is
241  // recognized on LFN systems too.
242  if (opt_format == FORMAT_XZ && has_sfn_suffix(src_name, src_len)) {
243  msg_suffix(src_name, "-");
244  return NULL;
245  }
246 #endif
247 
248  if (custom_suffix != NULL) {
249  if (test_suffix(custom_suffix, src_name, src_len) != 0) {
250  msg_suffix(src_name, custom_suffix);
251  return NULL;
252  }
253  }
254 
255  // TODO: Hmm, maybe it would be better to validate this in args.c,
256  // since the suffix handling when decoding is weird now.
257  if (opt_format == FORMAT_RAW && custom_suffix == NULL) {
258  message_error(_("%s: With --format=raw, "
259  "--suffix=.SUF is required unless "
260  "writing to stdout"), src_name);
261  return NULL;
262  }
263 
264  const char *suffix = custom_suffix != NULL
265  ? custom_suffix : suffixes[0];
266  size_t suffix_len = strlen(suffix);
267 
268 #ifdef __DJGPP__
269  if (!_use_lfn(src_name)) {
270  // Long filename (LFN) support isn't available and we are
271  // limited to 8.3 short filenames (SFN).
272  //
273  // Look for suffix separator from the filename, and make sure
274  // that it is in the filename, not in a directory name.
275  const char *sufsep = strrchr(src_name, '.');
276  if (sufsep == NULL || sufsep[1] == '\0'
277  || has_dir_sep(sufsep)) {
278  // src_name has no filename extension.
279  //
280  // Examples:
281  // xz foo -> foo.xz
282  // xz -F lzma foo -> foo.lzm
283  // xz -S x foo -> foox
284  // xz -S x foo. -> foo.x
285  // xz -S x.y foo -> foox.y
286  // xz -S .x foo -> foo.x
287  // xz -S .x foo. -> foo.x
288  //
289  // Avoid double dots:
290  if (sufsep != NULL && sufsep[1] == '\0'
291  && suffix[0] == '.')
292  --src_len;
293 
294  } else if (custom_suffix == NULL
295  && strcasecmp(sufsep, ".tar") == 0) {
296  // ".tar" is handled specially.
297  //
298  // Examples:
299  // xz foo.tar -> foo.txz
300  // xz -F lzma foo.tar -> foo.tlz
301  static const char *const tar_suffixes[] = {
302  ".txz",
303  ".tlz",
304  // ".tgz",
305  };
306  suffix = tar_suffixes[format];
307  suffix_len = 4;
308  src_len -= 4;
309 
310  } else {
311  if (custom_suffix == NULL && opt_format == FORMAT_XZ) {
312  // Instead of the .xz suffix, use a single
313  // character at the end of the filename
314  // extension. This is to minimize name
315  // conflicts when compressing multiple files
316  // with the same basename. E.g. foo.txt and
317  // foo.exe become foo.tx- and foo.ex-. Dash
318  // is rare as the last character of the
319  // filename extension, so it seems to be
320  // quite safe choice and it stands out better
321  // in directory listings than e.g. x. For
322  // comparison, gzip uses z.
323  suffix = "-";
324  suffix_len = 1;
325  }
326 
327  if (suffix[0] == '.') {
328  // The first character of the suffix is a dot.
329  // Throw away the original filename extension
330  // and replace it with the new suffix.
331  //
332  // Examples:
333  // xz -F lzma foo.txt -> foo.lzm
334  // xz -S .x foo.txt -> foo.x
335  src_len = sufsep - src_name;
336 
337  } else {
338  // The first character of the suffix is not
339  // a dot. Preserve the first 0-2 characters
340  // of the original filename extension.
341  //
342  // Examples:
343  // xz foo.txt -> foo.tx-
344  // xz -S x foo.c -> foo.cx
345  // xz -S ab foo.c -> foo.cab
346  // xz -S ab foo.txt -> foo.tab
347  // xz -S abc foo.txt -> foo.abc
348  //
349  // Truncate the suffix to three chars:
350  if (suffix_len > 3)
351  suffix_len = 3;
352 
353  // If needed, overwrite 1-3 characters.
354  if (strlen(sufsep) > 4 - suffix_len)
355  src_len = sufsep - src_name
356  + 4 - suffix_len;
357  }
358  }
359  }
360 #endif
361 
362  char *dest_name = xmalloc(src_len + suffix_len + 1);
363 
364  memcpy(dest_name, src_name, src_len);
365  memcpy(dest_name + src_len, suffix, suffix_len);
366  dest_name[src_len + suffix_len] = '\0';
367 
368  return dest_name;
369 }
370 
371 
372 extern char *
373 suffix_get_dest_name(const char *src_name)
374 {
375  assert(src_name != NULL);
376 
377  // Length of the name is needed in all cases to locate the end of
378  // the string to compare the suffix, so calculate the length here.
379  const size_t src_len = strlen(src_name);
380 
381  return opt_mode == MODE_COMPRESS
382  ? compressed_name(src_name, src_len)
383  : uncompressed_name(src_name, src_len);
384 }
385 
386 
387 extern void
388 suffix_set(const char *suffix)
389 {
390  // Empty suffix and suffixes having a directory separator are
391  // rejected. Such suffixes would break things later.
392  if (suffix[0] == '\0' || has_dir_sep(suffix))
393  message_fatal(_("%s: Invalid filename suffix"), suffix);
394 
395  // Replace the old custom_suffix (if any) with the new suffix.
398  return;
399 }
size_t len
Definition: 6502dis.c:15
#define ARRAY_SIZE(a)
lzma_index ** i
Definition: index.h:629
enum format_type opt_format
Definition: coder.c:25
enum operation_mode opt_mode
Definition: coder.c:24
@ MODE_COMPRESS
Definition: coder.h:14
@ FORMAT_RAW
Definition: coder.h:27
@ FORMAT_AUTO
Definition: coder.h:23
@ FORMAT_XZ
Definition: coder.h:24
#define NULL
Definition: cris-opc.c:27
#define xmalloc
Definition: disas-asm.h:43
unsigned char suffix[65536]
Definition: gun.c:164
RZ_API void Ht_() free(HtName_(Ht) *ht)
Definition: ht_inc.c:130
memcpy(mem, inblock.get(), min(CONTAINING_RECORD(inblock.get(), MEMBLOCK, data) ->size, size))
char * xstrdup(const char *) ATTRIBUTE_MALLOC
Definition: util.c:48
assert(limit<=UINT32_MAX/2)
void message_error(const char *fmt,...)
Definition: message.c:764
void message_warning(const char *fmt,...)
Definition: message.c:751
void message_fatal(const char *fmt,...)
Definition: message.c:777
#define _(String)
Definition: opintl.h:53
#define c(i)
Definition: sha256.c:43
Common includes, definitions, and prototypes.
static char * compressed_name(const char *src_name, size_t src_len)
Appends suffix to src_name.
Definition: suffix.c:196
static char * custom_suffix
Definition: suffix.c:25
void suffix_set(const char *suffix)
Set a custom filename suffix.
Definition: suffix.c:388
static void msg_suffix(const char *src_name, const char *suffix)
Definition: suffix.c:183
static bool has_dir_sep(const char *str)
Test if the string contains a directory separator.
Definition: suffix.c:42
static size_t test_suffix(const char *suffix, const char *src_name, size_t src_len)
Checks if src_name has given compressed_suffix.
Definition: suffix.c:87
char * suffix_get_dest_name(const char *src_name)
Get the name of the destination file.
Definition: suffix.c:373
static bool is_dir_sep(char c)
Test if the char is a directory separator.
Definition: suffix.c:30
static char * uncompressed_name(const char *src_name, const size_t src_len)
Removes the filename suffix of the compressed file.
Definition: suffix.c:110
static uint8_t compressed[1024]
in[] after compression