aboutsummaryrefslogblamecommitdiff
path: root/src/lzma/args.c
blob: 47ae766ae28ccba0559744a869500f0894bcc2ec (plain) (tree)



























                                                                               
                                          











                               

                                        








                                                                   





                                                                              

      
                               






                     
                  
                  





                   
                                                                    



























                                                                        










                                                                          
                                                                       
                                                                       













                                                                        
                                                     
















                                                                         
                               
                               



























































































































                                                                               































                                                                 
                               
                                                              

                              



                                                              



                           













                                                                              

                          


                                                                   






                                                                             
                                                     





                                             

                                                 




                                                                



                                                                   
                                                               






                                                                          
                                                   





























































































                                                                             

                                          
                                




                                                                   
                                                             

                                                                        



                                              
                                                        
 

                                                                          
                                                           
                                                                     
                                                                             



                                                 

                                                                            

                                                         
                                                            
                                                             



                                                           
                                                   






                                                                              




                                                                           
                                                                          
                 





                                                                            
                
                                                







                                                                            

                                                        
















                                                         




                                                                   























                                                                            



                                                                   










                                                                   
///////////////////////////////////////////////////////////////////////////////
//
/// \file       args.c
/// \brief      Argument parsing
///
/// \note       Filter-specific options parsing is in options.c.
//
//  Copyright (C) 2007 Lasse Collin
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
///////////////////////////////////////////////////////////////////////////////

#include "private.h"

#include "getopt.h"
#include <ctype.h>


enum tool_mode opt_mode = MODE_COMPRESS;
enum format_type opt_format = FORMAT_AUTO;

char *opt_suffix = NULL;

char *opt_files_name = NULL;
char opt_files_split = '\0';
FILE *opt_files_file = NULL;

bool opt_stdout = false;
bool opt_force = false;
bool opt_keep_original = false;
bool opt_preserve_name = false;

lzma_check opt_check = LZMA_CHECK_CRC64;
lzma_filter opt_filters[8];

// We don't modify or free() this, but we need to assign it in some
// non-const pointers.
const char *stdin_filename = "(stdin)";

static size_t preset_number = 7 - 1;
static bool preset_default = true;
static size_t filter_count = 0;

/// When compressing, which file format to use if --format=auto or no --format
/// at all has been specified. We need a variable because this depends on
/// with which name we are called. All names with "lz" in them makes us to
/// use the legacy .lzma format.
static enum format_type format_compress_auto = FORMAT_XZ;


enum {
	OPT_SUBBLOCK = INT_MIN,
	OPT_X86,
	OPT_POWERPC,
	OPT_IA64,
	OPT_ARM,
	OPT_ARMTHUMB,
	OPT_SPARC,
	OPT_DELTA,
	OPT_LZMA1,
	OPT_LZMA2,

	OPT_FILES,
	OPT_FILES0,
};


static const char short_opts[] = "cC:dfF:hlLkM:qrS:tT:vVz123456789";


static const struct option long_opts[] = {
	// gzip-like options
	{ "fast",               no_argument,       NULL,  '1' },
	{ "best",               no_argument,       NULL,  '9' },
	{ "memory",             required_argument, NULL,  'M' },
	{ "name",               no_argument,       NULL,  'N' },
	{ "suffix",             required_argument, NULL,  'S' },
	{ "threads",            required_argument, NULL,  'T' },
	{ "version",            no_argument,       NULL,  'V' },
	{ "stdout",             no_argument,       NULL,  'c' },
	{ "to-stdout",          no_argument,       NULL,  'c' },
	{ "decompress",         no_argument,       NULL,  'd' },
	{ "uncompress",         no_argument,       NULL,  'd' },
	{ "force",              no_argument,       NULL,  'f' },
	{ "help",               no_argument,       NULL,  'h' },
	{ "list",               no_argument,       NULL,  'l' },
	{ "info",               no_argument,       NULL,  'l' },
	{ "keep",               no_argument,       NULL,  'k' },
	{ "no-name",            no_argument,       NULL,  'n' },
	{ "quiet",              no_argument,       NULL,  'q' },
//	{ "recursive",          no_argument,       NULL,  'r' }, // TODO
	{ "test",               no_argument,       NULL,  't' },
	{ "verbose",            no_argument,       NULL,  'v' },
	{ "compress",           no_argument,       NULL,  'z' },

	// Filters
	{ "subblock",           optional_argument, NULL,   OPT_SUBBLOCK },
	{ "x86",                no_argument,       NULL,   OPT_X86 },
	{ "bcj",                no_argument,       NULL,   OPT_X86 },
	{ "powerpc",            no_argument,       NULL,   OPT_POWERPC },
	{ "ppc",                no_argument,       NULL,   OPT_POWERPC },
	{ "ia64",               no_argument,       NULL,   OPT_IA64 },
	{ "itanium",            no_argument,       NULL,   OPT_IA64 },
	{ "arm",                no_argument,       NULL,   OPT_ARM },
	{ "armthumb",           no_argument,       NULL,   OPT_ARMTHUMB },
	{ "sparc",              no_argument,       NULL,   OPT_SPARC },
	{ "delta",              optional_argument, NULL,   OPT_DELTA },
	{ "lzma1",              optional_argument, NULL,   OPT_LZMA1 },
	{ "lzma2",              optional_argument, NULL,   OPT_LZMA2 },

	// Other
	{ "format",             required_argument, NULL,   'F' },
	{ "check",              required_argument, NULL,   'C' },
	{ "files",              optional_argument, NULL,   OPT_FILES },
	{ "files0",             optional_argument, NULL,   OPT_FILES0 },

	{ NULL,                 0,                 NULL,   0 }
};


static void
add_filter(lzma_vli id, const char *opt_str)
{
	if (filter_count == LZMA_BLOCK_FILTERS_MAX) {
		errmsg(V_ERROR, _("Maximum number of filters is seven"));
		my_exit(ERROR);
	}

	opt_filters[filter_count].id = id;

	switch (id) {
	case LZMA_FILTER_SUBBLOCK:
		opt_filters[filter_count].options
				= parse_options_subblock(opt_str);
		break;

	case LZMA_FILTER_DELTA:
		opt_filters[filter_count].options
				= parse_options_delta(opt_str);
		break;

	case LZMA_FILTER_LZMA1:
	case LZMA_FILTER_LZMA2:
		opt_filters[filter_count].options
				= parse_options_lzma(opt_str);
		break;

	default:
		assert(opt_str == NULL);
		opt_filters[filter_count].options = NULL;
		break;
	}

	++filter_count;
	preset_default = false;
	return;
}


static void
parse_real(int argc, char **argv)
{
	int c;

	while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
			!= -1) {
		switch (c) {
		// gzip-like options

		case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			preset_number = c - '1';
			preset_default = false;
			break;

		// --memory
		case 'M':
			opt_memory = str_to_uint64("memory", optarg,
					1, SIZE_MAX);
			break;

		case 'N':
			opt_preserve_name = true;
			break;

		// --suffix
		case 'S':
			// Empty suffix and suffixes having a slash are
			// rejected. Such suffixes would break things later.
			if (optarg[0] == '\0' || strchr(optarg, '/') != NULL) {
				errmsg(V_ERROR, _("%s: Invalid filename "
						"suffix"), optarg);
				my_exit(ERROR);
			}

			free(opt_suffix);
			opt_suffix = xstrdup(optarg);
			break;

		case 'T':
			opt_threads = str_to_uint64("threads", optarg,
					1, SIZE_MAX);
			break;

		// --version
		case 'V':
			// This doesn't return.
			show_version();

		// --stdout
		case 'c':
			opt_stdout = true;
			break;

		// --decompress
		case 'd':
			opt_mode = MODE_DECOMPRESS;
			break;

		// --force
		case 'f':
			opt_force = true;
			break;

		// --help
		case 'h':
			// This doesn't return.
			show_help();

		// --list
		case 'l':
			opt_mode = MODE_LIST;
			break;

		// --keep
		case 'k':
			opt_keep_original = true;
			break;

		case 'n':
			opt_preserve_name = false;
			break;

		// --quiet
		case 'q':
			if (verbosity > V_SILENT)
				--verbosity;

			break;

		case 't':
			opt_mode = MODE_TEST;
			break;

		// --verbose
		case 'v':
			if (verbosity < V_DEBUG)
				++verbosity;

			break;

		case 'z':
			opt_mode = MODE_COMPRESS;
			break;

		// Filter setup

		case OPT_SUBBLOCK:
			add_filter(LZMA_FILTER_SUBBLOCK, optarg);
			break;

		case OPT_X86:
			add_filter(LZMA_FILTER_X86, NULL);
			break;

		case OPT_POWERPC:
			add_filter(LZMA_FILTER_POWERPC, NULL);
			break;

		case OPT_IA64:
			add_filter(LZMA_FILTER_IA64, NULL);
			break;

		case OPT_ARM:
			add_filter(LZMA_FILTER_ARM, NULL);
			break;

		case OPT_ARMTHUMB:
			add_filter(LZMA_FILTER_ARMTHUMB, NULL);
			break;

		case OPT_SPARC:
			add_filter(LZMA_FILTER_SPARC, NULL);
			break;

		case OPT_DELTA:
			add_filter(LZMA_FILTER_DELTA, optarg);
			break;

		case OPT_LZMA1:
			add_filter(LZMA_FILTER_LZMA1, optarg);
			break;

		case OPT_LZMA2:
			add_filter(LZMA_FILTER_LZMA2, optarg);
			break;

		// Other

		// --format
		case 'F': {
			// Just in case, support both "lzma" and "alone" since
			// the latter was used for forward compatibility in
			// LZMA Utils 4.32.x.
			static const struct {
				char str[8];
				enum format_type format;
			} types[] = {
				{ "auto",   FORMAT_AUTO },
				{ "xz",     FORMAT_XZ },
				{ "lzma",   FORMAT_LZMA },
				{ "alone",  FORMAT_LZMA },
				// { "gzip",   FORMAT_GZIP },
				// { "gz",     FORMAT_GZIP },
				{ "raw",    FORMAT_RAW },
			};

			size_t i = 0;
			while (strcmp(types[i].str, optarg) != 0) {
				if (++i == ARRAY_SIZE(types)) {
					errmsg(V_ERROR, _("%s: Unknown file "
							"format type"),
							optarg);
					my_exit(ERROR);
				}
			}

			opt_format = types[i].format;
			break;
		}

		// --check
		case 'C': {
			static const struct {
				char str[8];
				lzma_check check;
			} types[] = {
				{ "none",   LZMA_CHECK_NONE },
				{ "crc32",  LZMA_CHECK_CRC32 },
				{ "crc64",  LZMA_CHECK_CRC64 },
				{ "sha256", LZMA_CHECK_SHA256 },
			};

			size_t i = 0;
			while (strcmp(types[i].str, optarg) != 0) {
				if (++i == ARRAY_SIZE(types)) {
					errmsg(V_ERROR, _("%s: Unknown "
							"integrity check "
							"type"), optarg);
					my_exit(ERROR);
				}
			}

			opt_check = types[i].check;
			break;
		}

		case OPT_FILES:
			opt_files_split = '\n';

		// Fall through

		case OPT_FILES0:
			if (opt_files_name != NULL) {
				errmsg(V_ERROR, _("Only one file can be "
						"specified with `--files'"
						"or `--files0'."));
				my_exit(ERROR);
			}

			if (optarg == NULL) {
				opt_files_name = (char *)stdin_filename;
				opt_files_file = stdin;
			} else {
				opt_files_name = optarg;
				opt_files_file = fopen(optarg,
						c == OPT_FILES ? "r" : "rb");
				if (opt_files_file == NULL) {
					errmsg(V_ERROR, "%s: %s", optarg,
							strerror(errno));
					my_exit(ERROR);
				}
			}

			break;

		default:
			show_try_help();
			my_exit(ERROR);
		}
	}

	return;
}


static void
parse_environment(void)
{
	char *env = getenv("LZMA_OPT");
	if (env == NULL)
		return;

	env = xstrdup(env);

	// Calculate the number of arguments in env.
	unsigned int argc = 1;
	bool prev_was_space = true;
	for (size_t i = 0; env[i] != '\0'; ++i) {
		if (isspace(env[i])) {
			prev_was_space = true;
		} else if (prev_was_space) {
			prev_was_space = false;
			if (++argc > (unsigned int)(INT_MAX)) {
				errmsg(V_ERROR, _("The environment variable "
						"LZMA_OPT contains too many "
						"arguments"));
				my_exit(ERROR);
			}
		}
	}

	char **argv = xmalloc((argc + 1) * sizeof(char*));
	argv[0] = argv0;
	argv[argc] = NULL;

	argc = 1;
	prev_was_space = true;
	for (size_t i = 0; env[i] != '\0'; ++i) {
		if (isspace(env[i])) {
			prev_was_space = true;
		} else if (prev_was_space) {
			prev_was_space = false;
			argv[argc++] = env + i;
		}
	}

	parse_real((int)(argc), argv);

	free(env);

	return;
}


static void
set_compression_settings(void)
{
	static lzma_options_lzma opt_lzma;

	if (filter_count == 0) {
		if (lzma_lzma_preset(&opt_lzma, preset_number)) {
			errmsg(V_ERROR, _("Internal error (bug)"));
			my_exit(ERROR);
		}

		opt_filters[0].id = opt_format == FORMAT_LZMA
				? LZMA_FILTER_LZMA1 : LZMA_FILTER_LZMA2;
		opt_filters[0].options = &opt_lzma;
		filter_count = 1;
	}

	// Terminate the filter options array.
	opt_filters[filter_count].id = LZMA_VLI_UNKNOWN;

	// If we are using the LZMA_Alone format, allow exactly one filter
	// which has to be LZMA.
	if (opt_format == FORMAT_LZMA && (filter_count != 1
			|| opt_filters[0].id != LZMA_FILTER_LZMA1)) {
		errmsg(V_ERROR, _("With --format=lzma only the LZMA1 filter "
				"is supported"));
		my_exit(ERROR);
	}

	// TODO: liblzma probably needs an API to validate the filter chain.

	// If using --format=raw, we can be decoding.
	uint64_t memory_usage = opt_mode == MODE_COMPRESS
			? lzma_memusage_encoder(opt_filters)
			: lzma_memusage_decoder(opt_filters);

	// Don't go over the memory limits when the default
	// setting is used.
	if (preset_default) {
		while (memory_usage > opt_memory) {
			if (preset_number == 0) {
				errmsg(V_ERROR, _("Memory usage limit is too "
						"small for any internal "
						"filter preset"));
				my_exit(ERROR);
			}

			if (lzma_lzma_preset(&opt_lzma, --preset_number)) {
				errmsg(V_ERROR, _("Internal error (bug)"));
				my_exit(ERROR);
			}

			memory_usage = lzma_memusage_encoder(opt_filters);
		}

		// TODO: With --format=raw, we should print a warning since
		// the presets may change and thus the next version may not
		// be able to uncompress the raw stream with the same preset
		// number.

	} else {
		if (memory_usage > opt_memory) {
			errmsg(V_ERROR, _("Memory usage limit is too small "
					"for the given filter setup"));
			my_exit(ERROR);
		}
	}

	// Limit the number of worked threads so that memory usage
	// limit isn't exceeded.
	assert(memory_usage > 0);
	size_t thread_limit = opt_memory / memory_usage;
	if (thread_limit == 0)
		thread_limit = 1;

	if (opt_threads > thread_limit)
		opt_threads = thread_limit;

	return;
}


extern char **
parse_args(int argc, char **argv)
{
	// Check how we were called.
	{
		const char *name = str_filename(argv[0]);
		if (name != NULL) {
			// Default file format
			if (strstr(name, "lz") != NULL)
				format_compress_auto = FORMAT_LZMA;

			// Operation mode
			if (strstr(name, "cat") != NULL) {
				opt_mode = MODE_DECOMPRESS;
				opt_stdout = true;
			} else if (strstr(name, "un") != NULL) {
				opt_mode = MODE_DECOMPRESS;
			}
		}
	}

	// First the flags from environment
	parse_environment();

	// Then from the command line
	optind = 1;
	parse_real(argc, argv);

	// Never remove the source file when the destination is not on disk.
	// In test mode the data is written nowhere, but setting opt_stdout
	// will make the rest of the code behave well.
	if (opt_stdout || opt_mode == MODE_TEST) {
		opt_keep_original = true;
		opt_stdout = true;
	}

	if (opt_mode == MODE_COMPRESS && opt_format == FORMAT_AUTO)
		opt_format = format_compress_auto;

	if (opt_mode == MODE_COMPRESS || opt_format == FORMAT_RAW)
		set_compression_settings();

	// If no filenames are given, use stdin.
	if (argv[optind] == NULL && opt_files_name == NULL) {
		// We don't modify or free() the "-" constant.
		static char *argv_stdin[2] = { (char *)"-", NULL };
		return argv_stdin;
	}

	return argv + optind;
}