suffix.c revision 223935
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 24219001Smm/// \brief Test if the char is a directory separator 25219001Smmstatic bool 26219001Smmis_dir_sep(char c) 27219001Smm{ 28219001Smm#ifdef TUKLIB_DOSLIKE 29219001Smm return c == '/' || c == '\\' || c == ':'; 30219001Smm#else 31219001Smm return c == '/'; 32219001Smm#endif 33219001Smm} 34219001Smm 35219001Smm 36219001Smm/// \brief Test if the string contains a directory separator 37219001Smmstatic bool 38219001Smmhas_dir_sep(const char *str) 39219001Smm{ 40219001Smm#ifdef TUKLIB_DOSLIKE 41219001Smm return strpbrk(str, "/\\:") != NULL; 42219001Smm#else 43219001Smm return strchr(str, '/') != NULL; 44219001Smm#endif 45219001Smm} 46219001Smm 47219001Smm 48207753Smm/// \brief Checks if src_name has given compressed_suffix 49207753Smm/// 50207753Smm/// \param suffix Filename suffix to look for 51207753Smm/// \param src_name Input filename 52207753Smm/// \param src_len strlen(src_name) 53207753Smm/// 54207753Smm/// \return If src_name has the suffix, src_len - strlen(suffix) is 55207753Smm/// returned. It's always a positive integer. Otherwise zero 56207753Smm/// is returned. 57207753Smmstatic size_t 58207753Smmtest_suffix(const char *suffix, const char *src_name, size_t src_len) 59207753Smm{ 60207753Smm const size_t suffix_len = strlen(suffix); 61207753Smm 62207753Smm // The filename must have at least one character in addition to 63207753Smm // the suffix. src_name may contain path to the filename, so we 64207753Smm // need to check for directory separator too. 65219001Smm if (src_len <= suffix_len 66219001Smm || is_dir_sep(src_name[src_len - suffix_len - 1])) 67207753Smm return 0; 68207753Smm 69207753Smm if (strcmp(suffix, src_name + src_len - suffix_len) == 0) 70207753Smm return src_len - suffix_len; 71207753Smm 72207753Smm return 0; 73207753Smm} 74207753Smm 75207753Smm 76207753Smm/// \brief Removes the filename suffix of the compressed file 77207753Smm/// 78207753Smm/// \return Name of the uncompressed file, or NULL if file has unknown 79207753Smm/// suffix. 80207753Smmstatic char * 81207753Smmuncompressed_name(const char *src_name, const size_t src_len) 82207753Smm{ 83223935Smm static const struct { 84223935Smm const char *compressed; 85223935Smm const char *uncompressed; 86223935Smm } suffixes[] = { 87207753Smm { ".xz", "" }, 88207753Smm { ".txz", ".tar" }, // .txz abbreviation for .txt.gz is rare. 89207753Smm { ".lzma", "" }, 90207753Smm { ".tlz", ".tar" }, 91207753Smm // { ".gz", "" }, 92207753Smm // { ".tgz", ".tar" }, 93207753Smm }; 94207753Smm 95207753Smm const char *new_suffix = ""; 96207753Smm size_t new_len = 0; 97207753Smm 98207753Smm if (opt_format == FORMAT_RAW) { 99207753Smm // Don't check for known suffixes when --format=raw was used. 100207753Smm if (custom_suffix == NULL) { 101207753Smm message_error(_("%s: With --format=raw, " 102207753Smm "--suffix=.SUF is required unless " 103207753Smm "writing to stdout"), src_name); 104207753Smm return NULL; 105207753Smm } 106207753Smm } else { 107207753Smm for (size_t i = 0; i < ARRAY_SIZE(suffixes); ++i) { 108207753Smm new_len = test_suffix(suffixes[i].compressed, 109207753Smm src_name, src_len); 110207753Smm if (new_len != 0) { 111207753Smm new_suffix = suffixes[i].uncompressed; 112207753Smm break; 113207753Smm } 114207753Smm } 115207753Smm } 116207753Smm 117207753Smm if (new_len == 0 && custom_suffix != NULL) 118207753Smm new_len = test_suffix(custom_suffix, src_name, src_len); 119207753Smm 120207753Smm if (new_len == 0) { 121207753Smm message_warning(_("%s: Filename has an unknown suffix, " 122207753Smm "skipping"), src_name); 123207753Smm return NULL; 124207753Smm } 125207753Smm 126207753Smm const size_t new_suffix_len = strlen(new_suffix); 127207753Smm char *dest_name = xmalloc(new_len + new_suffix_len + 1); 128207753Smm 129207753Smm memcpy(dest_name, src_name, new_len); 130207753Smm memcpy(dest_name + new_len, new_suffix, new_suffix_len); 131207753Smm dest_name[new_len + new_suffix_len] = '\0'; 132207753Smm 133207753Smm return dest_name; 134207753Smm} 135207753Smm 136207753Smm 137207753Smm/// \brief Appends suffix to src_name 138207753Smm/// 139207753Smm/// In contrast to uncompressed_name(), we check only suffixes that are valid 140207753Smm/// for the specified file format. 141207753Smmstatic char * 142207753Smmcompressed_name(const char *src_name, const size_t src_len) 143207753Smm{ 144207753Smm // The order of these must match the order in args.h. 145223935Smm static const char *const all_suffixes[][3] = { 146207753Smm { 147223935Smm ".xz", 148223935Smm ".txz", 149223935Smm NULL 150207753Smm }, { 151223935Smm ".lzma", 152223935Smm ".tlz", 153223935Smm NULL 154207753Smm/* 155207753Smm }, { 156223935Smm ".gz", 157223935Smm ".tgz", 158223935Smm NULL 159207753Smm*/ 160207753Smm }, { 161207753Smm // --format=raw requires specifying the suffix 162207753Smm // manually or using stdout. 163223935Smm NULL 164207753Smm } 165207753Smm }; 166207753Smm 167207753Smm // args.c ensures this. 168207753Smm assert(opt_format != FORMAT_AUTO); 169207753Smm 170207753Smm const size_t format = opt_format - 1; 171223935Smm const char *const *suffixes = all_suffixes[format]; 172207753Smm 173223935Smm for (size_t i = 0; suffixes[i] != NULL; ++i) { 174223935Smm if (test_suffix(suffixes[i], src_name, src_len) != 0) { 175207753Smm message_warning(_("%s: File already has `%s' " 176207753Smm "suffix, skipping"), src_name, 177223935Smm suffixes[i]); 178207753Smm return NULL; 179207753Smm } 180207753Smm } 181207753Smm 182223935Smm if (custom_suffix != NULL) { 183223935Smm if (test_suffix(custom_suffix, src_name, src_len) != 0) { 184223935Smm message_warning(_("%s: File already has `%s' " 185223935Smm "suffix, skipping"), src_name, 186223935Smm custom_suffix); 187223935Smm return NULL; 188223935Smm } 189223935Smm } 190223935Smm 191207753Smm // TODO: Hmm, maybe it would be better to validate this in args.c, 192207753Smm // since the suffix handling when decoding is weird now. 193207753Smm if (opt_format == FORMAT_RAW && custom_suffix == NULL) { 194207753Smm message_error(_("%s: With --format=raw, " 195207753Smm "--suffix=.SUF is required unless " 196207753Smm "writing to stdout"), src_name); 197207753Smm return NULL; 198207753Smm } 199207753Smm 200207753Smm const char *suffix = custom_suffix != NULL 201223935Smm ? custom_suffix : suffixes[0]; 202207753Smm const size_t suffix_len = strlen(suffix); 203207753Smm 204207753Smm char *dest_name = xmalloc(src_len + suffix_len + 1); 205207753Smm 206207753Smm memcpy(dest_name, src_name, src_len); 207207753Smm memcpy(dest_name + src_len, suffix, suffix_len); 208207753Smm dest_name[src_len + suffix_len] = '\0'; 209207753Smm 210207753Smm return dest_name; 211207753Smm} 212207753Smm 213207753Smm 214207753Smmextern char * 215207753Smmsuffix_get_dest_name(const char *src_name) 216207753Smm{ 217207753Smm assert(src_name != NULL); 218207753Smm 219207753Smm // Length of the name is needed in all cases to locate the end of 220207753Smm // the string to compare the suffix, so calculate the length here. 221207753Smm const size_t src_len = strlen(src_name); 222207753Smm 223207753Smm return opt_mode == MODE_COMPRESS 224207753Smm ? compressed_name(src_name, src_len) 225207753Smm : uncompressed_name(src_name, src_len); 226207753Smm} 227207753Smm 228207753Smm 229207753Smmextern void 230207753Smmsuffix_set(const char *suffix) 231207753Smm{ 232219001Smm // Empty suffix and suffixes having a directory separator are 233219001Smm // rejected. Such suffixes would break things later. 234219001Smm if (suffix[0] == '\0' || has_dir_sep(suffix)) 235207753Smm message_fatal(_("%s: Invalid filename suffix"), optarg); 236207753Smm 237207753Smm // Replace the old custom_suffix (if any) with the new suffix. 238207753Smm free(custom_suffix); 239207753Smm custom_suffix = xstrdup(suffix); 240207753Smm return; 241207753Smm} 242