xz/src/lzma/args.c

598 lines
14 KiB
C
Raw Normal View History

2007-12-08 22:42:33 +00:00
///////////////////////////////////////////////////////////////////////////////
//
/// \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;
2007-12-08 22:42:33 +00:00
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[LZMA_BLOCK_FILTERS_MAX + 1];
2007-12-08 22:42:33 +00:00
// 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;
2007-12-08 22:42:33 +00:00
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;
2007-12-08 22:42:33 +00:00
enum {
OPT_SUBBLOCK = INT_MIN,
2007-12-08 22:42:33 +00:00
OPT_X86,
OPT_POWERPC,
OPT_IA64,
OPT_ARM,
OPT_ARMTHUMB,
OPT_SPARC,
OPT_DELTA,
OPT_LZMA1,
OPT_LZMA2,
2007-12-08 22:42:33 +00:00
OPT_FILES,
OPT_FILES0,
};
static const char short_opts[] = "cC:dfF:hlLkM:qrS:tT:vVz123456789";
2007-12-08 22:42:33 +00:00
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 },
2007-12-08 22:42:33 +00:00
// 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) {
2007-12-08 22:42:33 +00:00
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:
2007-12-08 22:42:33 +00:00
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 - '0';
2007-12-08 22:42:33 +00:00
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);
2007-12-08 22:42:33 +00:00
break;
case OPT_LZMA2:
add_filter(LZMA_FILTER_LZMA2, optarg);
break;
2007-12-08 22:42:33 +00:00
// 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 },
2007-12-08 22:42:33 +00:00
};
size_t i = 0;
while (strcmp(types[i].str, optarg) != 0) {
if (++i == ARRAY_SIZE(types)) {
2007-12-08 22:42:33 +00:00
errmsg(V_ERROR, _("%s: Unknown file "
"format type"),
optarg);
my_exit(ERROR);
}
}
opt_format = types[i].format;
2007-12-08 22:42:33 +00:00
break;
}
// --check
case 'C': {
static const struct {
char str[8];
lzma_check check;
2007-12-08 22:42:33 +00:00
} 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)) {
2007-12-08 22:42:33 +00:00
errmsg(V_ERROR, _("%s: Unknown "
"integrity check "
"type"), optarg);
my_exit(ERROR);
}
}
opt_check = types[i].check;
2007-12-08 22:42:33 +00:00
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;
2007-12-08 22:42:33 +00:00
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;
2007-12-08 22:42:33 +00:00
filter_count = 1;
}
// Terminate the filter options array.
opt_filters[filter_count].id = LZMA_VLI_UNKNOWN;
2007-12-08 22:42:33 +00:00
// 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);
2007-12-08 22:42:33 +00:00
// Don't go over the memory limits when the default
// setting is used.
if (preset_default) {
while (memory_usage > opt_memory) {
if (preset_number == 1) {
2007-12-08 22:42:33 +00:00
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);
2007-12-08 22:42:33 +00:00
}
// 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.
2007-12-08 22:42:33 +00:00
} else {
if (memory_usage > opt_memory) {
2007-12-08 22:42:33 +00:00
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;
2007-12-08 22:42:33 +00:00
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
2007-12-08 22:42:33 +00:00
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)
2007-12-08 22:42:33 +00:00
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;
}