suffix.c revision 207753
1207753Smm/////////////////////////////////////////////////////////////////////////////// 2207753Smm// 3207753Smm/// \file suffix.c 4207753Smm/// \brief Checks filename suffix and creates the destination filename 5207753Smm// 6207753Smm// Author: Lasse Collin 7207753Smm// 8207753Smm// This file has been put into the public domain. 9207753Smm// You can do whatever you want with this file. 10207753Smm// 11207753Smm/////////////////////////////////////////////////////////////////////////////// 12207753Smm 13207753Smm#include "private.h" 14207753Smm 15207753Smm// For case-insensitive filename suffix on case-insensitive systems 16207753Smm#if defined(TUKLIB_DOSLIKE) || defined(__VMS) 17207753Smm# define strcmp strcasecmp 18207753Smm#endif 19207753Smm 20207753Smm 21207753Smmstatic char *custom_suffix = NULL; 22207753Smm 23207753Smm 24207753Smmstruct suffix_pair { 25207753Smm const char *compressed; 26207753Smm const char *uncompressed; 27207753Smm}; 28207753Smm 29207753Smm 30207753Smm/// \brief Checks if src_name has given compressed_suffix 31207753Smm/// 32207753Smm/// \param suffix Filename suffix to look for 33207753Smm/// \param src_name Input filename 34207753Smm/// \param src_len strlen(src_name) 35207753Smm/// 36207753Smm/// \return If src_name has the suffix, src_len - strlen(suffix) is 37207753Smm/// returned. It's always a positive integer. Otherwise zero 38207753Smm/// is returned. 39207753Smmstatic size_t 40207753Smmtest_suffix(const char *suffix, const char *src_name, size_t src_len) 41207753Smm{ 42207753Smm const size_t suffix_len = strlen(suffix); 43207753Smm 44207753Smm // The filename must have at least one character in addition to 45207753Smm // the suffix. src_name may contain path to the filename, so we 46207753Smm // need to check for directory separator too. 47207753Smm if (src_len <= suffix_len || src_name[src_len - suffix_len - 1] == '/') 48207753Smm return 0; 49207753Smm 50207753Smm if (strcmp(suffix, src_name + src_len - suffix_len) == 0) 51207753Smm return src_len - suffix_len; 52207753Smm 53207753Smm return 0; 54207753Smm} 55207753Smm 56207753Smm 57207753Smm/// \brief Removes the filename suffix of the compressed file 58207753Smm/// 59207753Smm/// \return Name of the uncompressed file, or NULL if file has unknown 60207753Smm/// suffix. 61207753Smmstatic char * 62207753Smmuncompressed_name(const char *src_name, const size_t src_len) 63207753Smm{ 64207753Smm static const struct suffix_pair suffixes[] = { 65207753Smm { ".xz", "" }, 66207753Smm { ".txz", ".tar" }, // .txz abbreviation for .txt.gz is rare. 67207753Smm { ".lzma", "" }, 68207753Smm { ".tlz", ".tar" }, 69207753Smm // { ".gz", "" }, 70207753Smm // { ".tgz", ".tar" }, 71207753Smm }; 72207753Smm 73207753Smm const char *new_suffix = ""; 74207753Smm size_t new_len = 0; 75207753Smm 76207753Smm if (opt_format == FORMAT_RAW) { 77207753Smm // Don't check for known suffixes when --format=raw was used. 78207753Smm if (custom_suffix == NULL) { 79207753Smm message_error(_("%s: With --format=raw, " 80207753Smm "--suffix=.SUF is required unless " 81207753Smm "writing to stdout"), src_name); 82207753Smm return NULL; 83207753Smm } 84207753Smm } else { 85207753Smm for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) { 86207753Smm new_len = test_suffix(suffixes[i].compressed, 87207753Smm src_name, src_len); 88207753Smm if (new_len != 0) { 89207753Smm new_suffix = suffixes[i].uncompressed; 90207753Smm break; 91207753Smm } 92207753Smm } 93207753Smm } 94207753Smm 95207753Smm if (new_len == 0 && custom_suffix != NULL) 96207753Smm new_len = test_suffix(custom_suffix, src_name, src_len); 97207753Smm 98207753Smm if (new_len == 0) { 99207753Smm message_warning(_("%s: Filename has an unknown suffix, " 100207753Smm "skipping"), src_name); 101207753Smm return NULL; 102207753Smm } 103207753Smm 104207753Smm const size_t new_suffix_len = strlen(new_suffix); 105207753Smm char *dest_name = xmalloc(new_len + new_suffix_len + 1); 106207753Smm 107207753Smm memcpy(dest_name, src_name, new_len); 108207753Smm memcpy(dest_name + new_len, new_suffix, new_suffix_len); 109207753Smm dest_name[new_len + new_suffix_len] = '\0'; 110207753Smm 111207753Smm return dest_name; 112207753Smm} 113207753Smm 114207753Smm 115207753Smm/// \brief Appends suffix to src_name 116207753Smm/// 117207753Smm/// In contrast to uncompressed_name(), we check only suffixes that are valid 118207753Smm/// for the specified file format. 119207753Smmstatic char * 120207753Smmcompressed_name(const char *src_name, const size_t src_len) 121207753Smm{ 122207753Smm // The order of these must match the order in args.h. 123207753Smm static const struct suffix_pair all_suffixes[][3] = { 124207753Smm { 125207753Smm { ".xz", "" }, 126207753Smm { ".txz", ".tar" }, 127207753Smm { NULL, NULL } 128207753Smm }, { 129207753Smm { ".lzma", "" }, 130207753Smm { ".tlz", ".tar" }, 131207753Smm { NULL, NULL } 132207753Smm/* 133207753Smm }, { 134207753Smm { ".gz", "" }, 135207753Smm { ".tgz", ".tar" }, 136207753Smm { NULL, NULL } 137207753Smm*/ 138207753Smm }, { 139207753Smm // --format=raw requires specifying the suffix 140207753Smm // manually or using stdout. 141207753Smm { NULL, NULL } 142207753Smm } 143207753Smm }; 144207753Smm 145207753Smm // args.c ensures this. 146207753Smm assert(opt_format != FORMAT_AUTO); 147207753Smm 148207753Smm const size_t format = opt_format - 1; 149207753Smm const struct suffix_pair *const suffixes = all_suffixes[format]; 150207753Smm 151207753Smm for (size_t i = 0; suffixes[i].compressed != NULL; ++i) { 152207753Smm if (test_suffix(suffixes[i].compressed, src_name, src_len) 153207753Smm != 0) { 154207753Smm message_warning(_("%s: File already has `%s' " 155207753Smm "suffix, skipping"), src_name, 156207753Smm suffixes[i].compressed); 157207753Smm return NULL; 158207753Smm } 159207753Smm } 160207753Smm 161207753Smm // TODO: Hmm, maybe it would be better to validate this in args.c, 162207753Smm // since the suffix handling when decoding is weird now. 163207753Smm if (opt_format == FORMAT_RAW && custom_suffix == NULL) { 164207753Smm message_error(_("%s: With --format=raw, " 165207753Smm "--suffix=.SUF is required unless " 166207753Smm "writing to stdout"), src_name); 167207753Smm return NULL; 168207753Smm } 169207753Smm 170207753Smm const char *suffix = custom_suffix != NULL 171207753Smm ? custom_suffix : suffixes[0].compressed; 172207753Smm const size_t suffix_len = strlen(suffix); 173207753Smm 174207753Smm char *dest_name = xmalloc(src_len + suffix_len + 1); 175207753Smm 176207753Smm memcpy(dest_name, src_name, src_len); 177207753Smm memcpy(dest_name + src_len, suffix, suffix_len); 178207753Smm dest_name[src_len + suffix_len] = '\0'; 179207753Smm 180207753Smm return dest_name; 181207753Smm} 182207753Smm 183207753Smm 184207753Smmextern char * 185207753Smmsuffix_get_dest_name(const char *src_name) 186207753Smm{ 187207753Smm assert(src_name != NULL); 188207753Smm 189207753Smm // Length of the name is needed in all cases to locate the end of 190207753Smm // the string to compare the suffix, so calculate the length here. 191207753Smm const size_t src_len = strlen(src_name); 192207753Smm 193207753Smm return opt_mode == MODE_COMPRESS 194207753Smm ? compressed_name(src_name, src_len) 195207753Smm : uncompressed_name(src_name, src_len); 196207753Smm} 197207753Smm 198207753Smm 199207753Smmextern void 200207753Smmsuffix_set(const char *suffix) 201207753Smm{ 202207753Smm // Empty suffix and suffixes having a slash are rejected. Such 203207753Smm // suffixes would break things later. 204207753Smm if (suffix[0] == '\0' || strchr(suffix, '/') != NULL) 205207753Smm message_fatal(_("%s: Invalid filename suffix"), optarg); 206207753Smm 207207753Smm // Replace the old custom_suffix (if any) with the new suffix. 208207753Smm free(custom_suffix); 209207753Smm custom_suffix = xstrdup(suffix); 210207753Smm return; 211207753Smm} 212