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