suffix.c revision 274261
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