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