diff options
Diffstat (limited to 'src/lzma')
-rw-r--r-- | src/lzma/Makefile.am | 9 | ||||
-rw-r--r-- | src/lzma/alloc.c | 106 | ||||
-rw-r--r-- | src/lzma/alloc.h | 42 | ||||
-rw-r--r-- | src/lzma/args.c | 531 | ||||
-rw-r--r-- | src/lzma/args.h | 42 | ||||
-rw-r--r-- | src/lzma/error.c | 162 | ||||
-rw-r--r-- | src/lzma/error.h | 67 | ||||
-rw-r--r-- | src/lzma/hardware.c | 75 | ||||
-rw-r--r-- | src/lzma/hardware.h | 16 | ||||
-rw-r--r-- | src/lzma/help.c | 170 | ||||
-rw-r--r-- | src/lzma/help.h | 32 | ||||
-rw-r--r-- | src/lzma/io.c | 757 | ||||
-rw-r--r-- | src/lzma/io.h | 51 | ||||
-rw-r--r-- | src/lzma/main.c | 392 | ||||
-rw-r--r-- | src/lzma/main.h | 60 | ||||
-rw-r--r-- | src/lzma/message.c | 892 | ||||
-rw-r--r-- | src/lzma/message.h | 132 | ||||
-rw-r--r-- | src/lzma/options.c | 42 | ||||
-rw-r--r-- | src/lzma/options.h | 6 | ||||
-rw-r--r-- | src/lzma/private.h | 28 | ||||
-rw-r--r-- | src/lzma/process.c | 525 | ||||
-rw-r--r-- | src/lzma/process.h | 40 | ||||
-rw-r--r-- | src/lzma/suffix.c | 52 | ||||
-rw-r--r-- | src/lzma/suffix.h | 17 | ||||
-rw-r--r-- | src/lzma/util.c | 100 | ||||
-rw-r--r-- | src/lzma/util.h | 43 |
26 files changed, 2525 insertions, 1864 deletions
diff --git a/src/lzma/Makefile.am b/src/lzma/Makefile.am index cd8bb771..e5c5c29a 100644 --- a/src/lzma/Makefile.am +++ b/src/lzma/Makefile.am @@ -15,19 +15,16 @@ bin_PROGRAMS = lzma lzma_SOURCES = \ - alloc.c \ - alloc.h \ args.c \ args.h \ - error.c \ - error.h \ hardware.c \ hardware.h \ - help.c \ - help.h \ io.c \ io.h \ main.c \ + main.h \ + message.c \ + message.h \ options.c \ options.h \ private.h \ diff --git a/src/lzma/alloc.c b/src/lzma/alloc.c deleted file mode 100644 index d0fee68b..00000000 --- a/src/lzma/alloc.c +++ /dev/null @@ -1,106 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file alloc.c -/// \brief Memory allocation functions -// -// 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" - - -/// Called when memory allocation fails. Prints and error message and -/// quits the application. -static void lzma_attribute((noreturn)) -xerror(void) -{ - errmsg(V_ERROR, "%s", strerror(errno)); - my_exit(ERROR); -} - - -extern void * -xmalloc(size_t size) -{ - if (size < 1) { - errno = EINVAL; - xerror(); - } - - void *p = malloc(size); - if (p == NULL) - xerror(); - - return p; -} - - -/* -extern void * -xrealloc(void *ptr, size_t size) -{ - if (size < 1) { - errno = EINVAL; - xerror(); - } - - ptr = realloc(ptr, size); - if (ptr == NULL) - xerror(); - - return ptr; -} -*/ - - -extern char * -xstrdup(const char *src) -{ - if (src == NULL) { - errno = EINVAL; - xerror(); - } - - const size_t size = strlen(src) + 1; - char *dest = malloc(size); - if (dest == NULL) - xerror(); - - memcpy(dest, src, size); - - return dest; -} - - -extern void -xstrcpy(char **dest, const char *src) -{ - size_t len = strlen(src) + 1; - - *dest = realloc(*dest, len); - if (*dest == NULL) - xerror(); - - memcpy(*dest, src, len + 1); - - return; -} - - -extern void * -allocator(void *opaque lzma_attribute((unused)), - size_t nmemb lzma_attribute((unused)), size_t size) -{ - return xmalloc(size); -} diff --git a/src/lzma/alloc.h b/src/lzma/alloc.h deleted file mode 100644 index 80317269..00000000 --- a/src/lzma/alloc.h +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file alloc.h -/// \brief Memory allocation functions -// -// 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. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef ALLOC_H -#define ALLOC_H - -#include "private.h" - - -/// Safe malloc() that never returns NULL. -extern void *xmalloc(size_t size); - -/// Safe realloc() that never returns NULL. -extern void *xrealloc(void *ptr, size_t size); - -/// Safe strdup() that never returns NULL. -extern char *xstrdup(const char *src); - -/// xrealloc()s *dest to the size needed by src, and copies src to *dest. -extern void xstrcpy(char **dest, const char *src); - -/// Function for lzma_allocator.alloc. This uses xmalloc(). -extern void *allocator(void *opaque lzma_attribute((unused)), - size_t nmemb lzma_attribute((unused)), size_t size); - -#endif diff --git a/src/lzma/args.c b/src/lzma/args.c index 14ccfb6d..a2efb277 100644 --- a/src/lzma/args.c +++ b/src/lzma/args.c @@ -25,150 +25,90 @@ #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[LZMA_BLOCK_FILTERS_MAX + 1]; // 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; -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) +parse_real(args_info *args, int argc, char **argv) { - 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; - } + 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:hHlLkM:p:qrS:tT:vVz123456789"; + + static const struct option long_opts[] = { + // Operation mode + { "compress", no_argument, NULL, 'z' }, + { "decompress", no_argument, NULL, 'd' }, + { "uncompress", no_argument, NULL, 'd' }, + { "test", no_argument, NULL, 't' }, + { "list", no_argument, NULL, 'l' }, + { "info", no_argument, NULL, 'l' }, + + // Operation modifiers + { "keep", no_argument, NULL, 'k' }, + { "force", no_argument, NULL, 'f' }, + { "stdout", no_argument, NULL, 'c' }, + { "to-stdout", no_argument, NULL, 'c' }, + { "suffix", required_argument, NULL, 'S' }, + // { "recursive", no_argument, NULL, 'r' }, // TODO + { "files", optional_argument, NULL, OPT_FILES }, + { "files0", optional_argument, NULL, OPT_FILES0 }, + + // Basic compression settings + { "format", required_argument, NULL, 'F' }, + { "check", required_argument, NULL, 'C' }, + { "preset", required_argument, NULL, 'p' }, + { "memory", required_argument, NULL, 'M' }, + { "threads", required_argument, NULL, 'T' }, + + { "fast", no_argument, NULL, '1' }, + { "best", no_argument, NULL, '9' }, + + // Filters + { "lzma1", optional_argument, NULL, OPT_LZMA1 }, + { "lzma2", optional_argument, NULL, OPT_LZMA2 }, + { "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 }, + { "subblock", optional_argument, NULL, OPT_SUBBLOCK }, + + // Other options + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { "long-help", no_argument, NULL, 'H' }, + { "version", no_argument, NULL, 'V' }, + + { NULL, 0, NULL, 0 } + }; - ++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)) @@ -178,32 +118,28 @@ parse_real(int argc, char **argv) case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - preset_number = c - '0'; - preset_default = false; + coder_set_preset(c - '0'); break; - // --memory - case 'M': - opt_memory = str_to_uint64("memory", optarg, - 1, SIZE_MAX); + case 'p': { + const uint64_t preset = str_to_uint64( + "preset", optarg, 1, 9); + coder_set_preset(preset); break; + } - case 'N': - opt_preserve_name = true; + // --memory + case 'M': + // On 32-bit systems, SIZE_MAX would make more sense + // than UINT64_MAX. But use UINT64_MAX still so that + // scripts that assume > 4 GiB values don't break. + hardware_memlimit_set(str_to_uint64( + "memory", optarg, 0, UINT64_MAX)); 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); + suffix_set(optarg); break; case 'T': @@ -214,7 +150,7 @@ parse_real(int argc, char **argv) // --version case 'V': // This doesn't return. - show_version(); + message_version(); // --stdout case 'c': @@ -234,7 +170,12 @@ parse_real(int argc, char **argv) // --help case 'h': // This doesn't return. - show_help(); + message_help(false); + + // --long-help + case 'H': + // This doesn't return. + message_help(true); // --list case 'l': @@ -246,15 +187,9 @@ parse_real(int argc, char **argv) opt_keep_original = true; break; - case 'n': - opt_preserve_name = false; - break; - // --quiet case 'q': - if (verbosity > V_SILENT) - --verbosity; - + message_verbosity_decrease(); break; case 't': @@ -263,9 +198,7 @@ parse_real(int argc, char **argv) // --verbose case 'v': - if (verbosity < V_DEBUG) - ++verbosity; - + message_verbosity_increase(); break; case 'z': @@ -275,43 +208,47 @@ parse_real(int argc, char **argv) // Filter setup case OPT_SUBBLOCK: - add_filter(LZMA_FILTER_SUBBLOCK, optarg); + coder_add_filter(LZMA_FILTER_SUBBLOCK, + options_subblock(optarg)); break; case OPT_X86: - add_filter(LZMA_FILTER_X86, NULL); + coder_add_filter(LZMA_FILTER_X86, NULL); break; case OPT_POWERPC: - add_filter(LZMA_FILTER_POWERPC, NULL); + coder_add_filter(LZMA_FILTER_POWERPC, NULL); break; case OPT_IA64: - add_filter(LZMA_FILTER_IA64, NULL); + coder_add_filter(LZMA_FILTER_IA64, NULL); break; case OPT_ARM: - add_filter(LZMA_FILTER_ARM, NULL); + coder_add_filter(LZMA_FILTER_ARM, NULL); break; case OPT_ARMTHUMB: - add_filter(LZMA_FILTER_ARMTHUMB, NULL); + coder_add_filter(LZMA_FILTER_ARMTHUMB, NULL); break; case OPT_SPARC: - add_filter(LZMA_FILTER_SPARC, NULL); + coder_add_filter(LZMA_FILTER_SPARC, NULL); break; case OPT_DELTA: - add_filter(LZMA_FILTER_DELTA, optarg); + coder_add_filter(LZMA_FILTER_DELTA, + options_delta(optarg)); break; case OPT_LZMA1: - add_filter(LZMA_FILTER_LZMA1, optarg); + coder_add_filter(LZMA_FILTER_LZMA1, + options_lzma(optarg)); break; case OPT_LZMA2: - add_filter(LZMA_FILTER_LZMA2, optarg); + coder_add_filter(LZMA_FILTER_LZMA2, + options_lzma(optarg)); break; // Other @@ -335,14 +272,11 @@ parse_real(int argc, char **argv) }; size_t i = 0; - while (strcmp(types[i].str, optarg) != 0) { - if (++i == ARRAY_SIZE(types)) { - errmsg(V_ERROR, _("%s: Unknown file " + while (strcmp(types[i].str, optarg) != 0) + if (++i == ARRAY_SIZE(types)) + message_fatal(_("%s: Unknown file " "format type"), optarg); - my_exit(ERROR); - } - } opt_format = types[i].format; break; @@ -362,50 +296,43 @@ parse_real(int argc, char **argv) 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); - } + if (++i == ARRAY_SIZE(types)) + message_fatal(_("%s: Unknown integrity" + "check type"), optarg); } - opt_check = types[i].check; + coder_set_check(types[i].check); break; } case OPT_FILES: - opt_files_split = '\n'; + args->files_delim = '\n'; // Fall through case OPT_FILES0: - if (opt_files_name != NULL) { - errmsg(V_ERROR, _("Only one file can be " + if (args->files_name != NULL) + message_fatal(_("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; + args->files_name = (char *)stdin_filename; + args->files_file = stdin; } else { - opt_files_name = optarg; - opt_files_file = fopen(optarg, + args->files_name = optarg; + args->files_file = fopen(optarg, c == OPT_FILES ? "r" : "rb"); - if (opt_files_file == NULL) { - errmsg(V_ERROR, "%s: %s", optarg, + if (args->files_file == NULL) + message_fatal("%s: %s", optarg, strerror(errno)); - my_exit(ERROR); - } } break; default: - show_try_help(); - my_exit(ERROR); + message_try_help(); + my_exit(E_ERROR); } } @@ -414,163 +341,124 @@ parse_real(int argc, char **argv) static void -parse_environment(void) +parse_environment(args_info *args, char *argv0) { - char *env = getenv("LZMA_OPT"); + char *env = getenv("XZ_OPT"); if (env == NULL) return; + // We modify the string, so make a copy of it. env = xstrdup(env); - // Calculate the number of arguments in env. - unsigned int argc = 1; + // Calculate the number of arguments in env. argc stats at one + // to include space for the program name. + 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 " + + // Keep argc small enough to fit into a singed int + // and to keep it usable for memory allocation. + if (++argc == MIN(INT_MAX, SIZE_MAX / sizeof(char *))) + message_fatal(_("The environment variable " + "XZ_OPT contains too many " "arguments")); - my_exit(ERROR); - } } } - char **argv = xmalloc((argc + 1) * sizeof(char*)); + // Allocate memory to hold pointers to the arguments. Add one to get + // space for the terminating NULL (if some systems happen to need it). + char **argv = xmalloc(((size_t)(argc) + 1) * sizeof(char *)); argv[0] = argv0; argv[argc] = NULL; + // Go through the string again. Split the arguments using '\0' + // characters and add pointers to the resulting strings to argv. argc = 1; prev_was_space = true; for (size_t i = 0; env[i] != '\0'; ++i) { if (isspace(env[i])) { prev_was_space = true; + env[i] = '\0'; } else if (prev_was_space) { prev_was_space = false; argv[argc++] = env + i; } } - parse_real((int)(argc), argv); + // Parse the argument list we got from the environment. All non-option + // arguments i.e. filenames are ignored. + parse_real(args, argc, argv); + // Reset the state of the getopt_long() so that we can parse the + // command line options too. There are two incompatible ways to + // do it. +#ifdef HAVE_OPTRESET + // BSD + optind = 1; + optreset = 1; +#else + // GNU, Solaris + optind = 0; +#endif + + // We don't need the argument list from environment anymore. + free(argv); free(env); return; } -static void -set_compression_settings(void) +extern void +args_parse(args_info *args, int argc, char **argv) { - 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 == 1) { - 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. + // Initialize those parts of *args that we need later. + args->files_name = NULL; + args->files_file = NULL; + args->files_delim = '\0'; - } 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; -} + // Type of the file format to use when --format=auto or no --format + // was specified. + enum format_type format_compress_auto = FORMAT_XZ; - -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; - } + // Remove the leading path name, if any. + const char *name = strrchr(argv[0], '/'); + if (name == NULL) + name = argv[0]; + else + ++name; + + // NOTE: It's possible that name[0] is now '\0' if argv[0] + // is weird, but it doesn't matter here. + + // The default file format is .lzma if the command name + // contains "lz". + if (strstr(name, "lz") != NULL) + format_compress_auto = FORMAT_LZMA; + + // Operation mode + if (strstr(name, "cat") != NULL) { + // Imply --decompress --stdout + opt_mode = MODE_DECOMPRESS; + opt_stdout = true; + } else if (strstr(name, "un") != NULL) { + // Imply --decompress + opt_mode = MODE_DECOMPRESS; } } // First the flags from environment - parse_environment(); + parse_environment(args, argv[0]); // Then from the command line optind = 1; - parse_real(argc, argv); + parse_real(args, 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 @@ -580,18 +468,33 @@ parse_args(int argc, char **argv) opt_stdout = true; } + // If no --format flag was used, or it was --format=auto, we need to + // decide what is the target file format we are going to use. This + // depends on how we were called (checked earlier in this function). if (opt_mode == MODE_COMPRESS && opt_format == FORMAT_AUTO) opt_format = format_compress_auto; + // Compression settings need to be validated (options themselves and + // their memory usage) when compressing to any file format. It has to + // be done also when uncompressing raw data, since for raw decoding + // the options given on the command line are used to know what kind + // of raw data we are supposed to decode. if (opt_mode == MODE_COMPRESS || opt_format == FORMAT_RAW) - set_compression_settings(); + coder_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; + if (argv[optind] == NULL && args->files_name == NULL) { + // We don't modify or free() the "-" constant. The caller + // modifies this so don't make the struct itself const. + static char *names_stdin[2] = { (char *)"-", NULL }; + args->arg_names = names_stdin; + args->arg_count = 1; + } else { + // We got at least one filename from the command line, or + // --files or --files0 was specified. + args->arg_names = argv + optind; + args->arg_count = argc - optind; } - return argv + optind; + return; } diff --git a/src/lzma/args.h b/src/lzma/args.h index 8d9cd306..6d4e8282 100644 --- a/src/lzma/args.h +++ b/src/lzma/args.h @@ -23,42 +23,34 @@ #include "private.h" -enum tool_mode { - MODE_COMPRESS, - MODE_DECOMPRESS, - MODE_TEST, - MODE_LIST, -}; +typedef struct { + /// Filenames from command line + char **arg_names; -// NOTE: The order of these is significant in suffix.c. -enum format_type { - FORMAT_AUTO, - FORMAT_XZ, - FORMAT_LZMA, - // HEADER_GZIP, - FORMAT_RAW, -}; + /// Number of filenames from command line + size_t arg_count; + /// Name of the file from which to read filenames. This is NULL + /// if --files or --files0 was not used. + char *files_name; -extern char *opt_suffix; + /// File opened for reading from which filenames are read. This is + /// non-NULL only if files_name is non-NULL. + FILE *files_file; + + /// Delimiter for filenames read from files_file + char files_delim; + +} args_info; -extern char *opt_files_name; -extern char opt_files_split; -extern FILE *opt_files_file; extern bool opt_stdout; extern bool opt_force; extern bool opt_keep_original; -extern bool opt_preserve_name; // extern bool opt_recursive; -extern enum tool_mode opt_mode; -extern enum format_type opt_format; - -extern lzma_check opt_check; -extern lzma_filter opt_filters[LZMA_BLOCK_FILTERS_MAX + 1]; extern const char *stdin_filename; -extern char **parse_args(int argc, char **argv); +extern void args_parse(args_info *args, int argc, char **argv); #endif diff --git a/src/lzma/error.c b/src/lzma/error.c deleted file mode 100644 index e66fd140..00000000 --- a/src/lzma/error.c +++ /dev/null @@ -1,162 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file error.c -/// \brief Error message printing -// -// 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 <stdarg.h> - - -exit_status_type exit_status = SUCCESS; -verbosity_type verbosity = V_WARNING; -char *argv0 = NULL; -volatile sig_atomic_t user_abort = 0; - - -extern const char * -str_strm_error(lzma_ret code) -{ - switch (code) { - case LZMA_OK: - return _("Operation successful"); - - case LZMA_STREAM_END: - return _("Operation finished successfully"); - - case LZMA_PROG_ERROR: - return _("Internal error (bug)"); - - case LZMA_DATA_ERROR: - return _("Compressed data is corrupt"); - - case LZMA_MEM_ERROR: - return strerror(ENOMEM); - - case LZMA_BUF_ERROR: - return _("Unexpected end of input"); - - case LZMA_OPTIONS_ERROR: - return _("Unsupported options"); - - case LZMA_UNSUPPORTED_CHECK: - return _("Unsupported integrity check type"); - - case LZMA_MEMLIMIT_ERROR: - return _("Memory usage limit reached"); - - case LZMA_FORMAT_ERROR: - return _("File format not recognized"); - - default: - return NULL; - } -} - - -extern void -set_exit_status(exit_status_type new_status) -{ - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&mutex); - - if (new_status != WARNING || exit_status == SUCCESS) - exit_status = new_status; - - pthread_mutex_unlock(&mutex); - return; -} - - -extern void lzma_attribute((noreturn)) -my_exit(int status) -{ - // Close stdout. If something goes wrong, print an error message - // to stderr. - { - const int ferror_err = ferror(stdout); - const int fclose_err = fclose(stdout); - if (fclose_err) { - errmsg(V_ERROR, _("Writing to standard output " - "failed: %s"), strerror(errno)); - status = ERROR; - } else if (ferror_err) { - // Some error has occurred but we have no clue about - // the reason since fclose() succeeded. - errmsg(V_ERROR, _("Writing to standard output " - "failed: %s"), "Unknown error"); - status = ERROR; - } - } - - // Close stderr. If something goes wrong, there's nothing where we - // could print an error message. Just set the exit status. - { - const int ferror_err = ferror(stderr); - const int fclose_err = fclose(stderr); - if (fclose_err || ferror_err) - status = ERROR; - } - - exit(status); -} - - -extern void lzma_attribute((format(printf, 2, 3))) -errmsg(verbosity_type v, const char *fmt, ...) -{ - va_list ap; - - if (v <= verbosity) { - va_start(ap, fmt); - - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&mutex); - - fprintf(stderr, "%s: ", argv0); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - - pthread_mutex_unlock(&mutex); - - va_end(ap); - } - - if (v == V_ERROR) - set_exit_status(ERROR); - else if (v == V_WARNING) - set_exit_status(WARNING); - - return; -} - - -extern void -out_of_memory(void) -{ - errmsg(V_ERROR, "%s", strerror(ENOMEM)); - user_abort = 1; - return; -} - - -extern void -internal_error(void) -{ - errmsg(V_ERROR, _("Internal error (bug)")); - user_abort = 1; - return; -} diff --git a/src/lzma/error.h b/src/lzma/error.h deleted file mode 100644 index 34ec30e1..00000000 --- a/src/lzma/error.h +++ /dev/null @@ -1,67 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file error.c -/// \brief Error message printing -// -// 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. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef ERROR_H -#define ERROR_H - -#include "private.h" - - -typedef enum { - SUCCESS = 0, - ERROR = 1, - WARNING = 2, -} exit_status_type; - - -typedef enum { - V_SILENT, - V_ERROR, - V_WARNING, - V_VERBOSE, - V_DEBUG, -} verbosity_type; - - -extern exit_status_type exit_status; - -extern verbosity_type verbosity; - -/// Like GNU's program_invocation_name but portable -extern char *argv0; - -/// Once this is non-zero, all threads must shutdown and clean up incomplete -/// output files from the disk. -extern volatile sig_atomic_t user_abort; - - -extern const char * str_strm_error(lzma_ret code); - -extern void errmsg(verbosity_type v, const char *fmt, ...) - lzma_attribute((format(printf, 2, 3))); - -extern void set_exit_status(exit_status_type new_status); - -extern void my_exit(int status) lzma_attribute((noreturn)); - -extern void out_of_memory(void); - -extern void internal_error(void); - -#endif diff --git a/src/lzma/hardware.c b/src/lzma/hardware.c index 6cb3cdfc..63bf0937 100644 --- a/src/lzma/hardware.c +++ b/src/lzma/hardware.c @@ -26,33 +26,15 @@ size_t opt_threads = 1; -/// Number of bytes of memory to use at maximum (only a rough limit). -/// This can be set with the --memory=NUM command line option. -/// If no better value can be determined, the default is 14 MiB, which -/// should be quite safe even for older systems while still allowing -/// reasonable compression ratio. -size_t opt_memory = 14 * 1024 * 1024; +/// Memory usage limit for encoding +static uint64_t memlimit_encoder; +/// Memory usage limit for decoding +static uint64_t memlimit_decoder; -/// Get the amount of physical memory, and set opt_memory to 1/3 of it. -/// User can then override this with --memory command line option. -static void -hardware_memory(void) -{ - uint64_t mem = physmem(); - if (mem != 0) { - mem /= 3; - -#if UINT64_MAX > SIZE_MAX - if (mem > SIZE_MAX) - mem = SIZE_MAX; -#endif - - opt_memory = mem; - } - - return; -} +/// Memory usage limit given on the command line or environment variable. +/// Zero indicates the default (memlimit_encoder or memlimit_decoder). +static uint64_t memlimit_custom = 0; /// Get the number of CPU cores, and set opt_threads to default to that value. @@ -90,10 +72,51 @@ hardware_cores(void) } +static void +hardware_memlimit_init(void) +{ + uint64_t mem = physmem(); + + // If we cannot determine the amount of RAM, assume 32 MiB. Maybe + // even that is too much on some systems. But on most systems it's + // far too little, and can be annoying. + if (mem == 0) + mem = UINT64_C(16) * 1024 * 1024; + + // Use at maximum of 90 % of RAM when encoding and 33 % when decoding. + memlimit_encoder = mem - mem / 10; + memlimit_decoder = mem / 3; + + return; +} + + +extern void +hardware_memlimit_set(uint64_t memlimit) +{ + memlimit_custom = memlimit; + return; +} + + +extern uint64_t +hardware_memlimit_encoder(void) +{ + return memlimit_custom != 0 ? memlimit_custom : memlimit_encoder; +} + + +extern uint64_t +hardware_memlimit_decoder(void) +{ + return memlimit_custom != 0 ? memlimit_custom : memlimit_decoder; +} + + extern void hardware_init(void) { - hardware_memory(); + hardware_memlimit_init(); hardware_cores(); return; } diff --git a/src/lzma/hardware.h b/src/lzma/hardware.h index d47bd29f..f604df20 100644 --- a/src/lzma/hardware.h +++ b/src/lzma/hardware.h @@ -24,8 +24,22 @@ extern size_t opt_threads; -extern size_t opt_memory; + +/// Initialize some hardware-specific variables, which are needed by other +/// hardware_* functions. extern void hardware_init(void); + +/// Set custom memory usage limit. This is used for both encoding and +/// decoding. Zero indicates resetting the limit back to defaults. +extern void hardware_memlimit_set(uint64_t memlimit); + +/// Get the memory usage limit for encoding. By default this is 90 % of RAM. +extern uint64_t hardware_memlimit_encoder(void); + + +/// Get the memory usage limit for decoding. By default this is 30 % of RAM. +extern uint64_t hardware_memlimit_decoder(void); + #endif diff --git a/src/lzma/help.c b/src/lzma/help.c deleted file mode 100644 index 2e59f3b5..00000000 --- a/src/lzma/help.c +++ /dev/null @@ -1,170 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file help.c -/// \brief Help messages -// -// 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" - - -extern void -show_try_help(void) -{ - // Print this with V_WARNING instead of V_ERROR to prevent it from - // showing up when --quiet has been specified. - errmsg(V_WARNING, _("Try `%s --help' for more information."), argv0); - return; -} - - -extern void lzma_attribute((noreturn)) -show_help(void) -{ - printf(_("Usage: %s [OPTION]... [FILE]...\n" - "Compress or decompress FILEs in the .lzma format.\n" - "\n"), argv0); - - puts(_("Mandatory arguments to long options are mandatory for " - "short options too.\n")); - - puts(_( -" Operation mode:\n" -"\n" -" -z, --compress force compression\n" -" -d, --decompress force decompression\n" -" -t, --test test compressed file integrity\n" -" -l, --list list information about files\n" -)); - - puts(_( -" Operation modifiers:\n" -"\n" -" -k, --keep keep (don't delete) input files\n" -" -f, --force force overwrite of output file and (de)compress links\n" -" -c, --stdout write to standard output and don't delete input files\n" -" -S, --suffix=.SUF use the suffix `.SUF' on compressed files\n" -" -F, --format=FMT file format to encode or decode; possible values are\n" -" `auto' (default), `xz', `lzma', and `raw'\n" -" --files=[FILE] read filenames to process from FILE; if FILE is\n" -" omitted, filenames are read from the standard input;\n" -" filenames must be terminated with the newline character\n" -" --files0=[FILE] like --files but use the nul byte as terminator\n" -)); - - puts(_( -" Compression presets and basic compression options:\n" -"\n" -" -1 .. -2 fast compression\n" -" -3 .. -6 good compression\n" -" -7 .. -9 excellent compression, but needs a lot of memory;\n" -" default is -7 if memory limit allows\n" -"\n" -" -C, --check=CHECK integrity check type: `crc32', `crc64' (default),\n" -" or `sha256'\n" -)); - - puts(_( -" Custom filter chain for compression (alternative for using presets):\n" -"\n" -" --lzma1=[OPTS] LZMA1 or LZMA2; OPTS is a comma-separated list of zero or\n" -" --lzma2=[OPTS] more of the following options (valid values; default):\n" -" dict=NUM dictionary size (4KiB - 1536MiB; 8MiB)\n" -" lc=NUM number of literal context bits (0-4; 3)\n" -" lp=NUM number of literal position bits (0-4; 0)\n" -" pb=NUM number of position bits (0-4; 2)\n" -" mode=MODE compression mode (fast, normal; normal)\n" -" nice=NUM nice length of a match (2-273; 64)\n" -" mf=NAME match finder (hc3, hc4, bt2, bt3, bt4; bt4)\n" -" depth=NUM maximum search depth; 0=automatic (default)\n" -"\n" -" --x86 x86 filter (sometimes called BCJ filter)\n" -" --powerpc PowerPC (big endian) filter\n" -" --ia64 IA64 (Itanium) filter\n" -" --arm ARM filter\n" -" --armthumb ARM-Thumb filter\n" -" --sparc SPARC filter\n" -"\n" -" --delta=[OPTS] Delta filter; valid OPTS (valid values; default):\n" -" dist=NUM distance between bytes being subtracted\n" -" from each other (1-256; 1)\n" -"\n" -" --subblock=[OPTS] Subblock filter; valid OPTS (valid values; default):\n" -" size=NUM number of bytes of data per subblock\n" -" (1 - 256Mi; 4Ki)\n" -" rle=NUM run-length encoder chunk size (0-256; 0)\n" -)); - - puts(_( -" Resource usage options:\n" -"\n" -" -M, --memory=NUM use roughly NUM bytes of memory at maximum\n" -" -T, --threads=NUM use a maximum of NUM (de)compression threads\n" -// " --threading=STR threading style; possible values are `auto' (default),\n" -// " `files', and `stream' -)); - - puts(_( -" Other options:\n" -"\n" -" -q, --quiet suppress warnings; specify twice to suppress errors too\n" -" -v, --verbose be verbose; specify twice for even more verbose\n" -"\n" -" -h, --help display this help and exit\n" -" -V, --version display version and license information and exit\n")); - - puts(_("With no FILE, or when FILE is -, read standard input.\n")); - - size_t mem_limit = opt_memory / (1024 * 1024); - if (mem_limit == 0) - mem_limit = 1; - - // We use PRIu64 instead of %zu to support pre-C99 libc. - puts(_("On this system and configuration, the tool will use")); - printf(_(" * roughly %" PRIu64 " MiB of memory at maximum; and\n"), - (uint64_t)(mem_limit)); - printf(N_(" * at maximum of one thread for (de)compression.\n\n", - " * at maximum of %" PRIu64 - " threads for (de)compression.\n\n", - (uint64_t)(opt_threads)), (uint64_t)(opt_threads)); - - printf(_("Report bugs to <%s> (in English or Finnish).\n"), - PACKAGE_BUGREPORT); - - my_exit(SUCCESS); -} - - -extern void lzma_attribute((noreturn)) -show_version(void) -{ - printf( -"lzma (LZMA Utils) " PACKAGE_VERSION "\n" -"\n" -"Copyright (C) 1999-2008 Igor Pavlov\n" -"Copyright (C) 2007-2008 Lasse Collin\n" -"\n" -"This program is free software; you can redistribute it and/or modify\n" -"it under the terms of the GNU General Public License as published by\n" -"the Free Software Foundation; either version 2 of the License, or\n" -"(at your option) any later version.\n" -"\n" -"This program is distributed in the hope that it will be useful,\n" -"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" -"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" -"GNU General Public License for more details.\n" -"\n"); - my_exit(SUCCESS); -} diff --git a/src/lzma/help.h b/src/lzma/help.h deleted file mode 100644 index 659c66a0..00000000 --- a/src/lzma/help.h +++ /dev/null @@ -1,32 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -/// \file help.h -/// \brief Help messages -// -// 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. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef HELP_H -#define HELP_H - -#include "private.h" - - -extern void show_try_help(void); - -extern void show_help(void) lzma_attribute((noreturn)); - -extern void show_version(void) lzma_attribute((noreturn)); - -#endif diff --git a/src/lzma/io.c b/src/lzma/io.c index b972099f..0ec63f03 100644 --- a/src/lzma/io.c +++ b/src/lzma/io.c @@ -19,131 +19,39 @@ #include "private.h" -#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) -# include <sys/time.h> -#endif +#include <fcntl.h> -#ifndef O_SEARCH -# define O_SEARCH O_RDONLY +#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMES) +# include <sys/time.h> +#elif defined(HAVE_UTIME) +# include <utime.h> #endif -/// \brief Number of open file_pairs -/// -/// Once the main() function has requested processing of all files, -/// we wait that open_pairs drops back to zero. Then it is safe to -/// exit from the program. -static size_t open_pairs = 0; - - -/// \brief mutex for file system operations -/// -/// All file system operations are done via the functions in this file. -/// They use fchdir() to avoid some race conditions (more portable than -/// openat() & co.). -/// -/// Synchronizing all file system operations shouldn't affect speed notably, -/// since the actual reading from and writing to files is done in parallel. -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - - -/// This condition is invoked when a file is closed and the value of -/// the open_files variable has dropped to zero. The only listener for -/// this condition is io_finish() which is called from main(). -static pthread_cond_t io_cond = PTHREAD_COND_INITIALIZER; - - -/// True when stdout is being used by some thread -static bool stdout_in_use = false; - - -/// This condition is signalled when a thread releases stdout (no longer -/// writes data to it). -static pthread_cond_t stdout_cond = PTHREAD_COND_INITIALIZER; - - -/// \brief Directory where we were started -/// -/// This is needed when a new file, whose name was given on command line, -/// is opened. -static int start_dir; - - -static uid_t uid; -static gid_t gid; - - -extern void -io_init(void) -{ - start_dir = open(".", O_SEARCH | O_NOCTTY); - if (start_dir == -1) { - errmsg(V_ERROR, _("Cannot get file descriptor of the current " - "directory: %s"), strerror(errno)); - my_exit(ERROR); - } - - uid = getuid(); - gid = getgid(); - - return; -} - - -/// Waits until the number of open file_pairs has dropped to zero. -extern void -io_finish(void) -{ - pthread_mutex_lock(&mutex); - - while (open_pairs != 0) - pthread_cond_wait(&io_cond, &mutex); - - (void)close(start_dir); - - pthread_mutex_unlock(&mutex); - - return; -} - - /// \brief Unlinks a file /// -/// \param dir_fd File descriptor of the directory containing the file -/// \param name Name of the file with or without path -/// -/// \return Zero on success. On error, -1 is returned and errno set. -/// +/// This tries to verify that the file being unlinked really is the file that +/// we want to unlink by verifying device and inode numbers. There's still +/// a small unavoidable race, but this is much better than nothing (the file +/// could have been moved/replaced even hours earlier). static void -io_unlink(int dir_fd, const char *name, ino_t ino) +io_unlink(const char *name, const struct stat *known_st) { - const char *base = str_filename(name); - if (base == NULL) { - // This shouldn't happen. - errmsg(V_ERROR, _("%s: Invalid filename"), name); - return; - } + struct stat new_st; - pthread_mutex_lock(&mutex); - - if (fchdir(dir_fd)) { - errmsg(V_ERROR, _("Cannot change directory: %s"), - strerror(errno)); + if (lstat(name, &new_st) + || new_st.st_dev != known_st->st_dev + || new_st.st_ino != known_st->st_ino) { + message_error(_("%s: File seems to be moved, not removing"), + name); } else { - struct stat st; - if (lstat(base, &st) || st.st_ino != ino) - errmsg(V_ERROR, _("%s: File seems to be moved, " - "not removing"), name); - // There's a race condition between lstat() and unlink() // but at least we have tried to avoid removing wrong file. - else if (unlink(base)) - errmsg(V_ERROR, _("%s: Cannot remove: %s"), + if (unlink(name)) + message_error(_("%s: Cannot remove: %s"), name, strerror(errno)); } - pthread_mutex_unlock(&mutex); - return; } @@ -160,14 +68,31 @@ io_copy_attrs(const file_pair *pair) // destination file who didn't have permission to access the // source file. - if (uid == 0 && fchown(pair->dest_fd, pair->src_st.st_uid, -1)) - errmsg(V_WARNING, _("%s: Cannot set the file owner: %s"), - pair->dest_name, strerror(errno)); + // Simple cache to avoid repeated calls to geteuid(). + static enum { + WARN_FCHOWN_UNKNOWN, + WARN_FCHOWN_NO, + WARN_FCHOWN_YES, + } warn_fchown = WARN_FCHOWN_UNKNOWN; + + // Try changing the owner of the file. If we aren't root or the owner + // isn't already us, fchown() probably doesn't succeed. We warn + // about failing fchown() only if we are root. + if (fchown(pair->dest_fd, pair->src_st.st_uid, -1) + && warn_fchown != WARN_FCHOWN_NO) { + if (warn_fchown == WARN_FCHOWN_UNKNOWN) + warn_fchown = geteuid() == 0 + ? WARN_FCHOWN_YES : WARN_FCHOWN_NO; + + if (warn_fchown == WARN_FCHOWN_YES) + message_warning(_("%s: Cannot set the file owner: %s"), + pair->dest_name, strerror(errno)); + } mode_t mode; if (fchown(pair->dest_fd, -1, pair->src_st.st_gid)) { - errmsg(V_WARNING, _("%s: Cannot set the file group: %s"), + message_warning(_("%s: Cannot set the file group: %s"), pair->dest_name, strerror(errno)); // We can still safely copy some additional permissions: // `group' must be at least as strict as `other' and @@ -186,192 +111,291 @@ io_copy_attrs(const file_pair *pair) } if (fchmod(pair->dest_fd, mode)) - errmsg(V_WARNING, _("%s: Cannot set the file permissions: %s"), + message_warning(_("%s: Cannot set the file permissions: %s"), pair->dest_name, strerror(errno)); - // Copy the timestamps only if we have a secure function to do it. -#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) - struct timeval tv[2]; - tv[0].tv_sec = pair->src_st.st_atime; - tv[1].tv_sec = pair->src_st.st_mtime; + // Copy the timestamps. We have several possible ways to do this, of + // which some are better in both security and precision. + // + // First, get the nanosecond part of the timestamps. As of writing, + // it's not standardized by POSIX, and there are several names for + // the same thing in struct stat. + long atime_nsec; + long mtime_nsec; # if defined(HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC) - tv[0].tv_usec = pair->src_st.st_atim.tv_nsec / 1000; + // GNU and Solaris + atime_nsec = pair->src_st.st_atim.tv_nsec; + mtime_nsec = pair->src_st.st_mtim.tv_nsec; + # elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC) - tv[0].tv_usec = pair->src_st.st_atimespec.tv_nsec / 1000; -# else - tv[0].tv_usec = 0; -# endif + // BSD + atime_nsec = pair->src_st.st_atimespec.tv_nsec; + mtime_nsec = pair->src_st.st_mtimespec.tv_nsec; + +# elif defined(HAVE_STRUCT_STAT_ST_ATIMENSEC) + // GNU and BSD without extensions + atime_nsec = pair->src_st.st_atimensec; + mtime_nsec = pair->src_st.st_mtimensec; + +# elif defined(HAVE_STRUCT_STAT_ST_UATIME) + // Tru64 + atime_nsec = pair->src_st.st_uatime * 1000; + mtime_nsec = pair->src_st.st_umtime * 1000; + +# elif defined(HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC) + // UnixWare + atime_nsec = pair->src_st.st_atim.st__tim.tv_nsec; + mtime_nsec = pair->src_st.st_mtim.st__tim.tv_nsec; -# if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) - tv[1].tv_usec = pair->src_st.st_mtim.tv_nsec / 1000; -# elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC) - tv[1].tv_usec = pair->src_st.st_mtimespec.tv_nsec / 1000; # else - tv[1].tv_usec = 0; + // Safe fallback + atime_nsec = 0; + mtime_nsec = 0; # endif -# ifdef HAVE_FUTIMES + // Construct a structure to hold the timestamps and call appropriate + // function to set the timestamps. +#if defined(HAVE_FUTIMENS) + // Use nanosecond precision. + struct timespec tv[2]; + tv[0].tv_sec = pair->src_st.st_atime; + tv[0].tv_nsec = atime_nsec; + tv[1].tv_sec = pair->src_st.st_mtime; + tv[1].tv_nsec = mtime_nsec; + + (void)futimens(pair->dest_fd, tv); + +#elif defined(HAVE_FUTIMES) || defined(HAVE_FUTIMESAT) || defined(HAVE_UTIMES) + // Use microsecond precision. + struct timeval tv[2]; + tv[0].tv_sec = pair->src_st.st_atime; + tv[0].tv_usec = atime_nsec / 1000; + tv[1].tv_sec = pair->src_st.st_mtime; + tv[1].tv_usec = mtime_nsec / 1000; + +# if defined(HAVE_FUTIMES) (void)futimes(pair->dest_fd, tv); -# else +# elif defined(HAVE_FUTIMESAT) (void)futimesat(pair->dest_fd, NULL, tv); +# else + // Argh, no function to use a file descriptor to set the timestamp. + (void)utimes(pair->src_name, tv); # endif + +#elif defined(HAVE_UTIME) + // Use one-second precision. utime() doesn't support using file + // descriptor either. + const struct utimbuf buf = { + .actime = pair->src_st.st_atime; + .modtime = pair->src_st.st_mtime; + }; + + // Avoid warnings. + (void)atime_nsec; + (void)mtime_nsec; + + (void)utime(pair->src_name, &buf); #endif return; } -/// Opens and changes into the directory containing the source file. -static int -io_open_dir(file_pair *pair) +/// Opens the source file. Returns false on success, true on error. +static bool +io_open_src(file_pair *pair) { - if (pair->src_name == stdin_filename) - return 0; - - if (fchdir(start_dir)) { - errmsg(V_ERROR, _("Cannot change directory: %s"), - strerror(errno)); - return -1; + // There's nothing to open when reading from stdin. + if (pair->src_name == stdin_filename) { + pair->src_fd = STDIN_FILENO; + return false; } - const char *split = strrchr(pair->src_name, '/'); - if (split == NULL) { - pair->dir_fd = start_dir; - } else { - // Copy also the slash. It's needed to support filenames - // like "/foo" (dirname being "/"), and it never hurts anyway. - const size_t dirname_len = split - pair->src_name + 1; - char dirname[dirname_len + 1]; - memcpy(dirname, pair->src_name, dirname_len); - dirname[dirname_len] = '\0'; - - // Open the directory and change into it. - pair->dir_fd = open(dirname, O_SEARCH | O_NOCTTY); - if (pair->dir_fd == -1 || fchdir(pair->dir_fd)) { - errmsg(V_ERROR, _("%s: Cannot open the directory " - "containing the file: %s"), - pair->src_name, strerror(errno)); - (void)close(pair->dir_fd); - return -1; + // We accept only regular files if we are writing the output + // to disk too, and if --force was not given. + const bool reg_files_only = !opt_stdout && !opt_force; + + // Flags for open() + int flags = O_RDONLY | O_NOCTTY; + + // If we accept only regular files, we need to be careful to avoid + // problems with special files like devices and FIFOs. O_NONBLOCK + // prevents blocking when opening such files. When we want to accept + // special files, we must not use O_NONBLOCK, or otherwise we won't + // block waiting e.g. FIFOs to become readable. + if (reg_files_only) + flags |= O_NONBLOCK; + +#ifdef O_NOFOLLOW + if (reg_files_only) + flags |= O_NOFOLLOW; +#else + // Some POSIX-like systems lack O_NOFOLLOW (it's not required + // by POSIX). Check for symlinks with a separate lstat() on + // these systems. + if (reg_files_only) { + struct stat st; + if (lstat(pair->src_name, &st)) { + message_error("%s: %s", pair->src_name, + strerror(errno)); + return true; + + } else if (S_ISLNK(st.st_mode)) { + message_warning(_("%s: Is a symbolic link, " + "skipping"), pair->src_name); + return true; } } +#endif - return 0; -} + // Try to open the file. If we are accepting non-regular files, + // unblock the caught signals so that open() can be interrupted + // if it blocks e.g. due to a FIFO file. + if (!reg_files_only) + signals_unblock(); + + // Maybe this wouldn't need a loop, since all the signal handlers for + // which we don't use SA_RESTART set user_abort to true. But it + // doesn't hurt to have it just in case. + do { + pair->src_fd = open(pair->src_name, flags); + } while (pair->src_fd == -1 && errno == EINTR && !user_abort); + + if (!reg_files_only) + signals_block(); + + if (pair->src_fd == -1) { + // If we were interrupted, don't display any error message. + if (errno == EINTR) { + // All the signals that don't have SA_RESTART + // set user_abort. + assert(user_abort); + return true; + } +#ifdef O_NOFOLLOW + // Give an understandable error message in if reason + // for failing was that the file was a symbolic link. + // + // Note that at least Linux, OpenBSD, Solaris, and Darwin + // use ELOOP to indicate if O_NOFOLLOW was the reason + // that open() failed. Because there may be + // directories in the pathname, ELOOP may occur also + // because of a symlink loop in the directory part. + // So ELOOP doesn't tell us what actually went wrong. + // + // FreeBSD associates EMLINK with O_NOFOLLOW and + // Tru64 uses ENOTSUP. We use these directly here + // and skip the lstat() call and the associated race. + // I want to hear if there are other kernels that + // fail with something else than ELOOP with O_NOFOLLOW. + bool was_symlink = false; -static void -io_close_dir(file_pair *pair) -{ - if (pair->dir_fd != start_dir) - (void)close(pair->dir_fd); +# if defined(__FreeBSD__) || defined(__DragonFly__) + if (errno == EMLINK) + was_symlink = true; - return; -} +# elif defined(__digital__) && defined(__unix__) + if (errno == ENOTSUP) + was_symlink = true; +# else + if (errno == ELOOP && reg_files_only) { + const int saved_errno = errno; + struct stat st; + if (lstat(pair->src_name, &st) == 0 + && S_ISLNK(st.st_mode)) + was_symlink = true; + + errno = saved_errno; + } +# endif -/// Opens the source file. The file is opened using the plain filename without -/// path, thus the file must be in the current working directory. This is -/// ensured because io_open_dir() is always called before this function. -static int -io_open_src(file_pair *pair) -{ - if (pair->src_name == stdin_filename) { - pair->src_fd = STDIN_FILENO; - } else { - // Strip the pathname. Thanks to io_open_dir(), the file - // is now in the current working directory. - const char *filename = str_filename(pair->src_name); - if (filename == NULL) - return -1; - - // Symlinks are followed if --stdout or --force has been - // specified. - const bool follow_symlinks = opt_stdout || opt_force; - pair->src_fd = open(filename, O_RDONLY | O_NOCTTY - | (follow_symlinks ? 0 : O_NOFOLLOW)); - if (pair->src_fd == -1) { - // Give an understandable error message in if reason - // for failing was that the file was a symbolic link. - // - Linux, OpenBSD, Solaris: ELOOP - // - FreeBSD: EMLINK - // - Tru64: ENOTSUP - // It seems to be safe to check for all these, since - // those errno values aren't used for other purporses - // on any of the listed operating system *when* the - // above flags are used with open(). - if (!follow_symlinks - && (errno == ELOOP -#ifdef EMLINK - || errno == EMLINK -#endif -#ifdef ENOTSUP - || errno == ENOTSUP + if (was_symlink) + message_warning(_("%s: Is a symbolic link, " + "skipping"), pair->src_name); + else #endif - )) { - errmsg(V_WARNING, _("%s: Is a symbolic link, " - "skipping"), pair->src_name); - } else { - errmsg(V_ERROR, "%s: %s", pair->src_name, - strerror(errno)); - } + // Something else than O_NOFOLLOW failing + // (assuming that the race conditions didn't + // confuse us). + message_error("%s: %s", pair->src_name, + strerror(errno)); - return -1; - } + return true; + } - if (fstat(pair->src_fd, &pair->src_st)) { - errmsg(V_ERROR, "%s: %s", pair->src_name, - strerror(errno)); + // Drop O_NONBLOCK, which is used only when we are accepting only + // regular files. After the open() call, we want things to block + // instead of giving EAGAIN. + if (reg_files_only) { + flags = fcntl(pair->src_fd, F_GETFL); + if (flags == -1) + goto error_msg; + + flags &= ~O_NONBLOCK; + + if (fcntl(pair->src_fd, F_SETFL, flags)) + goto error_msg; + } + + // Stat the source file. We need the result also when we copy + // the permissions, and when unlinking. + if (fstat(pair->src_fd, &pair->src_st)) + goto error_msg; + + if (S_ISDIR(pair->src_st.st_mode)) { + message_warning(_("%s: Is a directory, skipping"), + pair->src_name); + goto error; + } + + if (reg_files_only) { + if (!S_ISREG(pair->src_st.st_mode)) { + message_warning(_("%s: Not a regular file, " + "skipping"), pair->src_name); goto error; } - if (S_ISDIR(pair->src_st.st_mode)) { - errmsg(V_WARNING, _("%s: Is a directory, skipping"), + if (pair->src_st.st_mode & (S_ISUID | S_ISGID)) { + // gzip rejects setuid and setgid files even + // when --force was used. bzip2 doesn't check + // for them, but calls fchown() after fchmod(), + // and many systems automatically drop setuid + // and setgid bits there. + // + // We accept setuid and setgid files if + // --force was used. We drop these bits + // explicitly in io_copy_attr(). + message_warning(_("%s: File has setuid or " + "setgid bit set, skipping"), pair->src_name); goto error; } - if (!opt_stdout) { - if (!opt_force && !S_ISREG(pair->src_st.st_mode)) { - errmsg(V_WARNING, _("%s: Not a regular file, " - "skipping"), pair->src_name); - goto error; - } - - if (pair->src_st.st_mode & (S_ISUID | S_ISGID)) { - // Setuid and setgid files are rejected even - // with --force. This is good for security - // (hopefully) but it's a bit weird to reject - // file when --force was given. At least this - // matches gzip's behavior. - errmsg(V_WARNING, _("%s: File has setuid or " - "setgid bit set, skipping"), - pair->src_name); - goto error; - } - - if (!opt_force && (pair->src_st.st_mode & S_ISVTX)) { - errmsg(V_WARNING, _("%s: File has sticky bit " - "set, skipping"), - pair->src_name); - goto error; - } + if (pair->src_st.st_mode & S_ISVTX) { + message_warning(_("%s: File has sticky bit " + "set, skipping"), + pair->src_name); + goto error; + } - if (pair->src_st.st_nlink > 1) { - errmsg(V_WARNING, _("%s: Input file has more " - "than one hard link, " - "skipping"), pair->src_name); - goto error; - } + if (pair->src_st.st_nlink > 1) { + message_warning(_("%s: Input file has more " + "than one hard link, " + "skipping"), pair->src_name); + goto error; } } - return 0; + return false; +error_msg: + message_error("%s: %s", pair->src_name, strerror(errno)); error: (void)close(pair->src_fd); - return -1; + return true; } @@ -383,65 +407,73 @@ error: static void io_close_src(file_pair *pair, bool success) { - if (pair->src_fd == STDIN_FILENO || pair->src_fd == -1) - return; - - if (close(pair->src_fd)) { - errmsg(V_ERROR, _("%s: Closing the file failed: %s"), - pair->src_name, strerror(errno)); - } else if (success && !opt_keep_original) { - io_unlink(pair->dir_fd, pair->src_name, pair->src_st.st_ino); + if (pair->src_fd != STDIN_FILENO && pair->src_fd != -1) { + // If we are going to unlink(), do it before closing the file. + // This way there's no risk that someone replaces the file and + // happens to get same inode number, which would make us + // unlink() wrong file. + if (success && !opt_keep_original) + io_unlink(pair->src_name, &pair->src_st); + + (void)close(pair->src_fd); } return; } -static int +static bool io_open_dest(file_pair *pair) { if (opt_stdout || pair->src_fd == STDIN_FILENO) { // We don't modify or free() this. pair->dest_name = (char *)"(stdout)"; pair->dest_fd = STDOUT_FILENO; + return false; + } - // Synchronize the order in which files get written to stdout. - // Unlocking the mutex is safe, because opening the file_pair - // can no longer fail. - while (stdout_in_use) - pthread_cond_wait(&stdout_cond, &mutex); + pair->dest_name = suffix_get_dest_name(pair->src_name); + if (pair->dest_name == NULL) + return true; - stdout_in_use = true; + // If --force was used, unlink the target file first. + if (opt_force && unlink(pair->dest_name) && errno != ENOENT) { + message_error("%s: Cannot unlink: %s", + pair->dest_name, strerror(errno)); + free(pair->dest_name); + return true; + } - } else { - pair->dest_name = get_dest_name(pair->src_name); - if (pair->dest_name == NULL) - return -1; - - // This cannot fail, because get_dest_name() doesn't return - // invalid names. - const char *filename = str_filename(pair->dest_name); - assert(filename != NULL); - - pair->dest_fd = open(filename, O_WRONLY | O_NOCTTY | O_CREAT - | (opt_force ? O_TRUNC : O_EXCL), - S_IRUSR | S_IWUSR); - if (pair->dest_fd == -1) { - errmsg(V_ERROR, "%s: %s", pair->dest_name, + if (opt_force && unlink(pair->dest_name) && errno != ENOENT) { + message_error("%s: Cannot unlink: %s", pair->dest_name, + strerror(errno)); + free(pair->dest_name); + return true; + } + + // Open the file. + const int flags = O_WRONLY | O_NOCTTY | O_CREAT | O_EXCL; + const mode_t mode = S_IRUSR | S_IWUSR; + pair->dest_fd = open(pair->dest_name, flags, mode); + + if (pair->dest_fd == -1) { + // Don't bother with error message if user requested + // us to exit anyway. + if (!user_abort) + message_error("%s: %s", pair->dest_name, strerror(errno)); - free(pair->dest_name); - return -1; - } - // If this really fails... well, we have a safe fallback. - struct stat st; - if (fstat(pair->dest_fd, &st)) - pair->dest_ino = 0; - else - pair->dest_ino = st.st_ino; + free(pair->dest_name); + return true; } - return 0; + // If this really fails... well, we have a safe fallback. + if (fstat(pair->dest_fd, &pair->dest_st)) { + pair->dest_st.st_dev = 0; + pair->dest_st.st_ino = 0; + } + + return false; } @@ -455,22 +487,16 @@ io_open_dest(file_pair *pair) static int io_close_dest(file_pair *pair, bool success) { - if (pair->dest_fd == -1) + if (pair->dest_fd == -1 || pair->dest_fd == STDOUT_FILENO) return 0; - if (pair->dest_fd == STDOUT_FILENO) { - stdout_in_use = false; - pthread_cond_signal(&stdout_cond); - return 0; - } - if (close(pair->dest_fd)) { - errmsg(V_ERROR, _("%s: Closing the file failed: %s"), + message_error(_("%s: Closing the file failed: %s"), pair->dest_name, strerror(errno)); // Closing destination file failed, so we cannot trust its // contents. Get rid of junk: - io_unlink(pair->dir_fd, pair->dest_name, pair->dest_ino); + io_unlink(pair->dest_name, &pair->dest_st); free(pair->dest_name); return -1; } @@ -478,7 +504,7 @@ io_close_dest(file_pair *pair, bool success) // If the operation using this file wasn't successful, we git rid // of the junk file. if (!success) - io_unlink(pair->dir_fd, pair->dest_name, pair->dest_ino); + io_unlink(pair->dest_name, &pair->dest_st); free(pair->dest_name); @@ -492,98 +518,63 @@ io_open(const char *src_name) if (is_empty_filename(src_name)) return NULL; - file_pair *pair = malloc(sizeof(file_pair)); - if (pair == NULL) { - out_of_memory(); - return NULL; - } + // Since we have only one file open at a time, we can use + // a statically allocated structure. + static file_pair pair; - *pair = (file_pair){ + pair = (file_pair){ .src_name = src_name, .dest_name = NULL, - .dir_fd = -1, .src_fd = -1, .dest_fd = -1, .src_eof = false, }; - pthread_mutex_lock(&mutex); - - ++open_pairs; - - if (io_open_dir(pair)) - goto error_dir; - - if (io_open_src(pair)) - goto error_src; - - if (user_abort || io_open_dest(pair)) - goto error_dest; - - pthread_mutex_unlock(&mutex); + // Block the signals, for which we have a custom signal handler, so + // that we don't need to worry about EINTR. + signals_block(); + + file_pair *ret = NULL; + if (!io_open_src(&pair)) { + // io_open_src() may have unblocked the signals temporarily, + // and thus user_abort may have got set even if open() + // succeeded. + if (user_abort || io_open_dest(&pair)) + io_close_src(&pair, false); + else + ret = &pair; + } - return pair; + signals_unblock(); -error_dest: - io_close_src(pair, false); -error_src: - io_close_dir(pair); -error_dir: - --open_pairs; - pthread_mutex_unlock(&mutex); - free(pair); - return NULL; + return ret; } -/// \brief Closes the file descriptors and frees the structure extern void io_close(file_pair *pair, bool success) { + signals_block(); + if (success && pair->dest_fd != STDOUT_FILENO) io_copy_attrs(pair); // Close the destination first. If it fails, we must not remove // the source file! - if (!io_close_dest(pair, success)) { - // Closing destination file succeeded. Remove the source file - // if the operation using this file pair was successful - // and we haven't been requested to keep the source file. - io_close_src(pair, success); - } else { - // We don't care if operation using this file pair was - // successful or not, since closing the destination file - // failed. Don't remove the original file. - io_close_src(pair, false); - } - - io_close_dir(pair); + if (io_close_dest(pair, success)) + success = false; - free(pair); - - pthread_mutex_lock(&mutex); - - if (--open_pairs == 0) - pthread_cond_signal(&io_cond); + // Close the source file, and unlink it if the operation using this + // file pair was successful and we haven't requested to keep the + // source file. + io_close_src(pair, success); - pthread_mutex_unlock(&mutex); + signals_unblock(); return; } -/// \brief Reads from a file to a buffer -/// -/// \param pair File pair having the sourcefile open for reading -/// \param buf Destination buffer to hold the read data -/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX -/// -/// \return On success, number of bytes read is returned. On end of -/// file zero is returned and pair->src_eof set to true. -/// On error, SIZE_MAX is returned and error message printed. -/// -/// \note This does no locking, thus two threads must not read from -/// the same file. This no problem in this program. extern size_t io_read(file_pair *pair, uint8_t *buf, size_t size) { @@ -608,7 +599,7 @@ io_read(file_pair *pair, uint8_t *buf, size_t size) continue; } - errmsg(V_ERROR, _("%s: Read error: %s"), + message_error(_("%s: Read error: %s"), pair->src_name, strerror(errno)); // FIXME Is this needed? @@ -625,18 +616,7 @@ io_read(file_pair *pair, uint8_t *buf, size_t size) } -/// \brief Writes a buffer to a file -/// -/// \param pair File pair having the destination file open for writing -/// \param buf Buffer containing the data to be written -/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX -/// -/// \return On success, zero is returned. On error, -1 is returned -/// and error message printed. -/// -/// \note This does no locking, thus two threads must not write to -/// the same file. This no problem in this program. -extern int +extern bool io_write(const file_pair *pair, const uint8_t *buf, size_t size) { assert(size < SSIZE_MAX); @@ -660,18 +640,19 @@ io_write(const file_pair *pair, const uint8_t *buf, size_t size) // GNU bash). // // We don't do anything special with --quiet, which - // is what bzip2 does too. However, we print a - // message if --verbose was used (or should that - // only be with double --verbose i.e. debugging?). - errmsg(errno == EPIPE ? V_VERBOSE : V_ERROR, - _("%s: Write error: %s"), + // is what bzip2 does too. If we get SIGPIPE, we + // will handle it like other signals by setting + // user_abort, and get EPIPE here. + if (errno != EPIPE) + message_error(_("%s: Write error: %s"), pair->dest_name, strerror(errno)); - return -1; + + return true; } buf += (size_t)(amount); size -= (size_t)(amount); } - return 0; + return false; } diff --git a/src/lzma/io.h b/src/lzma/io.h index d1aa17f4..4d8e61b2 100644 --- a/src/lzma/io.h +++ b/src/lzma/io.h @@ -22,6 +22,8 @@ #include "private.h" + +// Some systems have suboptimal BUFSIZ. Use a bit bigger value on them. #if BUFSIZ <= 1024 # define IO_BUFFER_SIZE 8192 #else @@ -30,31 +32,66 @@ typedef struct { + /// Name of the source filename (as given on the command line) or + /// pointer to static "(stdin)" when reading from standard input. const char *src_name; + + /// Destination filename converted from src_name or pointer to static + /// "(stdout)" when writing to standard output. char *dest_name; - int dir_fd; + /// File descriptor of the source file int src_fd; + + /// File descriptor of the target file int dest_fd; + /// Stat of the source file. struct stat src_st; - ino_t dest_ino; - bool src_eof; -} file_pair; + /// Stat of the destination file. + struct stat dest_st; + /// True once end of the source file has been detected. + bool src_eof; -extern void io_init(void); +} file_pair; -extern void io_finish(void); +/// \brief Opens a file pair extern file_pair *io_open(const char *src_name); + +/// \brief Closes the file descriptors and frees possible allocated memory +/// +/// The success argument determines if source or destination file gets +/// unlinked: +/// - false: The destination file is unlinked. +/// - true: The source file is unlinked unless writing to stdout or --keep +/// was used. extern void io_close(file_pair *pair, bool success); + +/// \brief Reads from the source file to a buffer +/// +/// \param pair File pair having the source file open for reading +/// \param buf Destination buffer to hold the read data +/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX +/// +/// \return On success, number of bytes read is returned. On end of +/// file zero is returned and pair->src_eof set to true. +/// On error, SIZE_MAX is returned and error message printed. extern size_t io_read(file_pair *pair, uint8_t *buf, size_t size); -extern int io_write(const file_pair *pair, const uint8_t *buf, size_t size); +/// \brief Writes a buffer to the destination file +/// +/// \param pair File pair having the destination file open for writing +/// \param buf Buffer containing the data to be written +/// \param size Size of the buffer; assumed be smaller than SSIZE_MAX +/// +/// \return On success, zero is returned. On error, -1 is returned +/// and error message printed. +extern bool io_write(const file_pair *pair, const uint8_t *buf, size_t size); #endif diff --git a/src/lzma/main.c b/src/lzma/main.c index 02891193..4e24b98d 100644 --- a/src/lzma/main.c +++ b/src/lzma/main.c @@ -21,16 +21,30 @@ #include "open_stdxxx.h" #include <ctype.h> -static sig_atomic_t exit_signal = 0; + +volatile sig_atomic_t user_abort = false; + +/// Exit status to use. This can be changed with set_exit_status(). +static enum exit_status_type exit_status = E_SUCCESS; + +/// If we were interrupted by a signal, we store the signal number so that +/// we can raise that signal to kill the program when all cleanups have +/// been done. +static volatile sig_atomic_t exit_signal = 0; + +/// Mask of signals for which have have established a signal handler to set +/// user_abort to true. +static sigset_t hooked_signals; + +/// signals_block() and signals_unblock() can be called recursively. +static size_t signals_block_count = 0; static void signal_handler(int sig) { - // FIXME Is this thread-safe together with main()? exit_signal = sig; - - user_abort = 1; + user_abort = true; return; } @@ -38,116 +52,226 @@ signal_handler(int sig) static void establish_signal_handlers(void) { - struct sigaction sa; - sa.sa_handler = &signal_handler; - sigfillset(&sa.sa_mask); - sa.sa_flags = 0; - + // List of signals for which we establish the signal handler. static const int sigs[] = { - SIGHUP, SIGINT, - SIGPIPE, SIGTERM, +#ifdef SIGHUP + SIGHUP, +#endif +#ifdef SIGPIPE + SIGPIPE, +#endif +#ifdef SIGXCPU SIGXCPU, +#endif +#ifdef SIGXFSZ SIGXFSZ, +#endif }; - for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); ++i) { - if (sigaction(sigs[i], &sa, NULL)) { - errmsg(V_ERROR, _("Cannot establish signal handlers")); - my_exit(ERROR); - } + // Mask of the signals for which we have established a signal handler. + sigemptyset(&hooked_signals); + for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) + sigaddset(&hooked_signals, sigs[i]); + + struct sigaction sa; + + // All the signals that we handle we also blocked while the signal + // handler runs. + sa.sa_mask = hooked_signals; + + // Don't set SA_RESTART, because we want EINTR so that we can check + // for user_abort and cleanup before exiting. We block the signals + // for which we have established a handler when we don't want EINTR. + sa.sa_flags = 0; + sa.sa_handler = &signal_handler; + + for (size_t i = 0; i < ARRAY_SIZE(sigs); ++i) { + // If the parent process has left some signals ignored, + // we don't unignore them. + struct sigaction old; + if (sigaction(sigs[i], NULL, &old) == 0 + && old.sa_handler == SIG_IGN) + continue; + + // Establish the signal handler. + if (sigaction(sigs[i], &sa, NULL)) + message_signal_handler(); } - /* - SIGINFO/SIGUSR1 for status reporting? - */ + return; } -static bool -is_tty_stdin(void) +extern void +signals_block(void) { - const bool ret = isatty(STDIN_FILENO); - if (ret) { - // FIXME: Other threads may print between these lines. - // Maybe that should be fixed. Not a big issue in practice. - errmsg(V_ERROR, _("Compressed data not read from " - "a terminal.")); - errmsg(V_ERROR, _("Use `--force' to force decompression.")); - show_try_help(); + if (signals_block_count++ == 0) { + const int saved_errno = errno; + sigprocmask(SIG_BLOCK, &hooked_signals, NULL); + errno = saved_errno; } - return ret; + return; } -static bool -is_tty_stdout(void) +extern void +signals_unblock(void) { - const bool ret = isatty(STDOUT_FILENO); - if (ret) { - errmsg(V_ERROR, _("Compressed data not written to " - "a terminal.")); - errmsg(V_ERROR, _("Use `--force' to force compression.")); - show_try_help(); + assert(signals_block_count > 0); + + if (--signals_block_count == 0) { + const int saved_errno = errno; + sigprocmask(SIG_UNBLOCK, &hooked_signals, NULL); + errno = saved_errno; } - return ret; + return; } -static char * -read_name(void) +extern void +set_exit_status(enum exit_status_type new_status) { - size_t size = 256; - size_t pos = 0; - char *name = malloc(size); - if (name == NULL) { - out_of_memory(); - return NULL; + assert(new_status == E_WARNING || new_status == E_ERROR); + + if (exit_status != E_ERROR) + exit_status = new_status; + + return; +} + + +extern void +my_exit(enum exit_status_type status) +{ + // Close stdout. If something goes wrong, print an error message + // to stderr. + { + const int ferror_err = ferror(stdout); + const int fclose_err = fclose(stdout); + if (ferror_err || fclose_err) { + // If it was fclose() that failed, we have the reason + // in errno. If only ferror() indicated an error, + // we have no idea what the reason was. + message(V_ERROR, _("Writing to standard output " + "failed: %s"), + fclose_err ? strerror(errno) + : _("Unknown error")); + status = E_ERROR; + } + } + + // Close stderr. If something goes wrong, there's nothing where we + // could print an error message. Just set the exit status. + { + const int ferror_err = ferror(stderr); + const int fclose_err = fclose(stderr); + if (fclose_err || ferror_err) + status = E_ERROR; } - while (true) { - const int c = fgetc(opt_files_file); - if (c == EOF) { - free(name); - - if (ferror(opt_files_file)) - errmsg(V_ERROR, _("%s: Error reading " - "filenames: %s"), - opt_files_name, - strerror(errno)); - else if (pos != 0) - errmsg(V_ERROR, _("%s: Unexpected end of " - "input when reading " - "filenames"), opt_files_name); + // If we have got a signal, raise it to kill the program. + const int sig = exit_signal; + if (sig != 0) { + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigfillset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(sig, &sa, NULL); + raise(exit_signal); + // If, for some weird reason, the signal doesn't kill us, + // we safely fall to the exit below. + } + + exit(status); +} + + +static const char * +read_name(const args_info *args) +{ + // FIXME: Maybe we should have some kind of memory usage limit here + // like the tool has for the actual compression and uncompression. + // Giving some huge text file with --files0 makes us to read the + // whole file in RAM. + static char *name = NULL; + static size_t size = 256; + + // Allocate the initial buffer. This is never freed, since after it + // is no longer needed, the program exits very soon. It is safe to + // use xmalloc() and xrealloc() in this function, because while + // executing this function, no files are open for writing, and thus + // there's no need to cleanup anything before exiting. + if (name == NULL) + name = xmalloc(size); + + // Write position in name + size_t pos = 0; + + // Read one character at a time into name. + while (!user_abort) { + const int c = fgetc(args->files_file); + + if (ferror(args->files_file)) { + // Take care of EINTR since we have established + // the signal handlers already. + if (errno == EINTR) + continue; + + message_error(_("%s: Error reading filenames: %s"), + args->files_name, strerror(errno)); return NULL; } - if (c == '\0' || c == opt_files_split) - break; + if (feof(args->files_file)) { + if (pos != 0) + message_error(_("%s: Unexpected end of input " + "when reading filenames"), + args->files_name); + + return NULL; + } + + if (c == args->files_delim) { + // We allow consecutive newline (--files) or '\0' + // characters (--files0), and ignore such empty + // filenames. + if (pos == 0) + continue; + + // A non-empty name was read. Terminate it with '\0' + // and return it. + name[pos] = '\0'; + return name; + } + + if (c == '\0') { + // A null character was found when using --files, + // which expects plain text input separated with + // newlines. + message_error(_("%s: Null character found when " + "reading filenames; maybe you meant " + "to use `--files0' instead " + "of `--files'?"), args->files_name); + return NULL; + } name[pos++] = c; + // Allocate more memory if needed. There must always be space + // at least for one character to allow terminating the string + // with '\0'. if (pos == size) { size *= 2; - char *tmp = realloc(name, size); - if (tmp == NULL) { - free(name); - out_of_memory(); - return NULL; - } - - name = tmp; + name = xrealloc(name, size); } } - if (name != NULL) - name[pos] = '\0'; - - return name; + return NULL; } @@ -158,35 +282,56 @@ main(int argc, char **argv) // a valid file descriptor. Exit immediatelly with exit code ERROR // if we cannot make the file descriptors valid. Maybe we should // print an error message, but our stderr could be screwed anyway. - open_stdxxx(ERROR); + open_stdxxx(E_ERROR); - // Set the program invocation name used in various messages. - argv0 = argv[0]; + // This has to be done before calling any liblzma functions. + lzma_init(); - setlocale(LC_ALL, "en_US.UTF-8"); + // Set up the locale. + setlocale(LC_ALL, ""); + +#ifdef ENABLE_NLS + // Set up the message translations too. bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); +#endif + + // Set the program invocation name used in various messages, and + // do other message handling related initializations. + message_init(argv[0]); // Set hardware-dependent default values. These can be overriden // on the command line, thus this must be done before parse_args(). hardware_init(); - char **files = parse_args(argc, argv); - - if (opt_mode == MODE_COMPRESS && opt_stdout && is_tty_stdout()) - return ERROR; - - if (opt_mode == MODE_COMPRESS) - lzma_init_encoder(); + // Parse the command line arguments and get an array of filenames. + // This doesn't return if something is wrong with the command line + // arguments. If there are no arguments, one filename ("-") is still + // returned to indicate stdin. + args_info args; + args_parse(&args, argc, argv); + + // Tell the message handling code how many input files there are if + // we know it. This way the progress indicator can show it. + if (args.files_name != NULL) + message_set_files(0); else - lzma_init_decoder(); - - io_init(); - process_init(); + message_set_files(args.arg_count); + + // Refuse to write compressed data to standard output if it is + // a terminal and --force wasn't used. + if (opt_mode == MODE_COMPRESS) { + if (opt_stdout || (args.arg_count == 1 + && strcmp(args.arg_names[0], "-") == 0)) { + if (is_tty_stdout()) { + message_try_help(); + my_exit(E_ERROR); + } + } + } if (opt_mode == MODE_LIST) { - errmsg(V_ERROR, "--list is not implemented yet."); - my_exit(ERROR); + message_fatal("--list is not implemented yet."); } // Hook the signal handlers. We don't need these before we start @@ -194,60 +339,63 @@ main(int argc, char **argv) // line arguments. establish_signal_handlers(); - while (*files != NULL && !user_abort) { - if (strcmp("-", *files) == 0) { + // Process the files given on the command line. Note that if no names + // were given, parse_args() gave us a fake "-" filename. + for (size_t i = 0; i < args.arg_count && !user_abort; ++i) { + if (strcmp("-", args.arg_names[i]) == 0) { + // Processing from stdin to stdout. Unless --force + // was used, check that we aren't writing compressed + // data to a terminal or reading it from terminal. if (!opt_force) { if (opt_mode == MODE_COMPRESS) { - if (is_tty_stdout()) { - ++files; + if (is_tty_stdout()) continue; - } } else if (is_tty_stdin()) { - ++files; continue; } } - if (opt_files_name == stdin_filename) { - errmsg(V_ERROR, _("Cannot read data from " + // It doesn't make sense to compress data from stdin + // if we are supposed to read filenames from stdin + // too (enabled with --files or --files0). + if (args.files_name == stdin_filename) { + message_error(_("Cannot read data from " "standard input when " "reading filenames " "from standard input")); - ++files; continue; } - *files = (char *)stdin_filename; + // Replace the "-" with a special pointer, which is + // recognized by process_file() and other things. + // This way error messages get a proper filename + // string and the code still knows that it is + // handling the special case of stdin. + args.arg_names[i] = (char *)stdin_filename; } - process_file(*files++); + // Do the actual compression or uncompression. + process_file(args.arg_names[i]); } - if (opt_files_name != NULL) { + // If --files or --files0 was used, process the filenames from the + // given file or stdin. Note that here we don't consider "-" to + // indicate stdin like we do with the command line arguments. + if (args.files_name != NULL) { + // read_name() checks for user_abort so we don't need to + // check it as loop termination condition. while (true) { - char *name = read_name(); + const char *name = read_name(&args); if (name == NULL) break; - if (name[0] != '\0') - process_file(name); - - free(name); + // read_name() doesn't return empty names. + assert(name[0] != '\0'); + process_file(name); } - if (opt_files_name != stdin_filename) - (void)fclose(opt_files_file); - } - - io_finish(); - - if (exit_signal != 0) { - struct sigaction sa; - sa.sa_handler = SIG_DFL; - sigfillset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(exit_signal, &sa, NULL); - raise(exit_signal); + if (args.files_name != stdin_filename) + (void)fclose(args.files_file); } my_exit(exit_status); diff --git a/src/lzma/main.h b/src/lzma/main.h new file mode 100644 index 00000000..1e369425 --- /dev/null +++ b/src/lzma/main.h @@ -0,0 +1,60 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file main.h +/// \brief Miscellanous declarations +// +// Copyright (C) 2008 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MAIN_H +#define MAIN_H + +/// Possible exit status values. These are the same as used by gzip and bzip2. +enum exit_status_type { + E_SUCCESS = 0, + E_ERROR = 1, + E_WARNING = 2, +}; + + +/// If this is true, we will clean up the possibly incomplete output file, +/// return to main() as soon as practical. That is, the code needs to poll +/// this variable in various places. +extern volatile sig_atomic_t user_abort; + + +/// Block the signals which don't have SA_RESTART and which would just set +/// user_abort to true. This is handy when we don't want to handle EINTR +/// and don't want SA_RESTART either. +extern void signals_block(void); + + +/// Unblock the signals blocked by signals_block(). +extern void signals_unblock(void); + + +/// Sets the exit status after a warning or error has occurred. If new_status +/// is EX_WARNING and the old exit status was already EX_ERROR, the exit +/// status is not changed. +extern void set_exit_status(enum exit_status_type new_status); + + +/// Exits the program using the given status. This takes care of closing +/// stdin, stdout, and stderr and catches possible errors. If we had got +/// a signal, this function will raise it so that to the parent process it +/// appears that we were killed by the signal sent by the user. +extern void my_exit(enum exit_status_type status) lzma_attribute((noreturn)); + + +#endif diff --git a/src/lzma/message.c b/src/lzma/message.c new file mode 100644 index 00000000..caba9fbc --- /dev/null +++ b/src/lzma/message.c @@ -0,0 +1,892 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file message.c +/// \brief Printing messages to stderr +// +// Copyright (C) 2007-2008 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" + +#if defined(HAVE_SYS_TIME_H) +# include <sys/time.h> +#elif defined(SIGALRM) +// FIXME +#endif + +#include <stdarg.h> + + +/// Name of the program which is prefixed to the error messages. +static const char *argv0; + +/// Number of the current file +static unsigned int files_pos = 0; + +/// Total number of input files; zero if unknown. +static unsigned int files_total; + +/// Verbosity level +static enum message_verbosity verbosity = V_WARNING; + +/// Filename which we will print with the verbose messages +static const char *filename; + +/// True once the a filename has been printed to stderr as part of progress +/// message. If automatic progress updating isn't enabled, this becomes true +/// after the first progress message has been printed due to user sending +/// SIGALRM. Once this variable is true, we will print an empty line before +/// the next filename to make the output more readable. +static bool first_filename_printed = false; + +/// This is set to true when we have printed the current filename to stderr +/// as part of a progress message. This variable is useful only if not +/// updating progress automatically: if user sends many SIGALRM signals, +/// we won't print the name of the same file multiple times. +static bool current_filename_printed = false; + +/// True if we should print progress indicator and update it automatically. +static bool progress_automatic; + +/// This is true when a progress message was printed and the cursor is still +/// on the same line with the progress message. In that case, a newline has +/// to be printed before any error messages. +static bool progress_active = false; + +/// Expected size of the input stream is needed to show completion percentage +/// and estimate remaining time. +static uint64_t expected_in_size; + +/// Time when we started processing the file +static double start_time; + +/// The signal handler for SIGALRM sets this to true. It is set back to false +/// once the progress message has been updated. +static volatile sig_atomic_t progress_needs_updating = false; + + +/// Signal handler for SIGALRM +static void +progress_signal_handler(int sig lzma_attribute((unused))) +{ + progress_needs_updating = true; + return; +} + + +/// Get the current time as double +static double +my_time(void) +{ + struct timeval tv; + + // This really shouldn't fail. I'm not sure what to return if it + // still fails. It doesn't look so useful to check the return value + // everywhere. FIXME? + if (gettimeofday(&tv, NULL)) + return -1.0; + + return (double)(tv.tv_sec) + (double)(tv.tv_usec) / 1.0e9; +} + + +/// Wrapper for snprintf() to help constructing a string in pieces. +static void /* lzma_attribute((format(printf, 3, 4))) */ +my_snprintf(char **pos, size_t *left, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + const int len = vsnprintf(*pos, *left, fmt, ap); + va_end(ap); + + // If an error occurred, we want the caller to think that the whole + // buffer was used. This way no more data will be written to the + // buffer. We don't need better error handling here. + if (len < 0 || (size_t)(len) >= *left) { + *left = 0; + } else { + *pos += len; + *left -= len; + } + + return; +} + + +extern void +message_init(const char *given_argv0) +{ + // Name of the program + argv0 = given_argv0; + + // If --verbose is used, we use a progress indicator if and only + // if stderr is a terminal. If stderr is not a terminal, we print + // verbose information only after finishing the file. As a special + // exception, even if --verbose was not used, user can send SIGALRM + // to make us print progress information once without automatic + // updating. + progress_automatic = isatty(STDERR_FILENO); + +/* + if (progress_automatic) { + // stderr is a terminal. Check the COLUMNS environment + // variable to see if the terminal is wide enough. If COLUMNS + // doesn't exist or it has some unparseable value, we assume + // that the terminal is wide enough. + const char *columns_str = getenv("COLUMNS"); + uint64_t columns; + if (columns_str != NULL + && !str_to_uint64_raw(&columns, columns_str) + && columns < 80) + progress_automatic = false; + } +*/ + +#ifdef SIGALRM + // Establish the signal handler for SIGALRM. Since this signal + // doesn't require any quick action, we set SA_RESTART. + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = &progress_signal_handler; + if (sigaction(SIGALRM, &sa, NULL)) + message_signal_handler(); +#endif + + return; +} + + +extern void +message_verbosity_increase(void) +{ + if (verbosity < V_DEBUG) + ++verbosity; + + return; +} + + +extern void +message_verbosity_decrease(void) +{ + if (verbosity > V_SILENT) + --verbosity; + + return; +} + + +extern void +message_set_files(unsigned int files) +{ + files_total = files; + return; +} + + +/// Prints the name of the current file if it hasn't been printed already, +/// except if we are processing exactly one stream from stdin to stdout. +/// I think it looks nicer to not print "(stdin)" when --verbose is used +/// in a pipe and no other files are processed. +static void +print_filename(void) +{ + if (!current_filename_printed + && (files_total != 1 || filename != stdin_filename)) { + signals_block(); + + // If a file was already processed, put an empty line + // before the next filename to improve readability. + if (first_filename_printed) + fputc('\n', stderr); + + first_filename_printed = true; + current_filename_printed = true; + + // If we don't know how many files there will be due + // to usage of --files or --files0. + if (files_total == 0) + fprintf(stderr, "%s (%u)\n", filename, + files_pos); + else + fprintf(stderr, "%s (%u/%u)\n", filename, + files_pos, files_total); + + signals_unblock(); + } + + return; +} + + +extern void +message_progress_start(const char *src_name, uint64_t in_size) +{ + // Store the processing start time of the file and its expected size. + // If we aren't printing any statistics, then these are unused. But + // since it is possible that the user tells us with SIGALRM to show + // statistics, we need to have these available anyway. + start_time = my_time(); + filename = src_name; + expected_in_size = in_size; + + // Indicate the name of this file hasn't been printed to + // stderr yet. + current_filename_printed = false; + + // Start numbering the files starting from one. + ++files_pos; + + // If progress indicator is wanted, print the filename and possibly + // the file count now. As an exception, if there is exactly one file, + // do not print the filename at all. + if (verbosity >= V_VERBOSE && progress_automatic) { + // Print the filename to stderr if that is appropriate with + // the current settings. + print_filename(); + + // Start the timer to set progress_needs_updating to true + // after about one second. An alternative would to be set + // progress_needs_updating to true here immediatelly, but + // setting the timer looks better to me, since extremely + // early progress info is pretty much useless. + alarm(1); + } + + return; +} + + +/// Make the string indicating completion percentage. +static const char * +progress_percentage(uint64_t in_pos) +{ + // If the size of the input file is unknown or the size told us is + // clearly wrong since we have processed more data than the alleged + // size of the file, show a static string indicating that we have + // no idea of the completion percentage. + if (expected_in_size == 0 || in_pos > expected_in_size) + return "--- %"; + + static char buf[sizeof("99.9 %")]; + + // Never show 100.0 % before we actually are finished (that case is + // handled separately in message_progress_end()). + snprintf(buf, sizeof(buf), "%.1f %%", + (double)(in_pos) / (double)(expected_in_size) * 99.9); + + return buf; +} + + +static void +progress_sizes_helper(char **pos, size_t *left, uint64_t value, bool final) +{ + if (final) { + // At maximum of four digits is allowed for exact byte count. + if (value < 10000) { + my_snprintf(pos, left, "%'" PRIu64 " B", value); + return; + } + +// // At maximum of four significant digits is allowed for KiB. +// if (value < UINT64_C(1023900)) { + // At maximum of five significant digits is allowed for KiB. + if (value < UINT64_C(10239900)) { + my_snprintf(pos, left, "%'.1f KiB", + (double)(value) / 1024.0); + return; + } + } + + // Otherwise we use MiB. + my_snprintf(pos, left, "%'.1f MiB", + (double)(value) / (1024.0 * 1024.0)); + return; +} + + +/// Make the string containing the amount of input processed, amount of +/// output produced, and the compression ratio. +static const char * +progress_sizes(uint64_t compressed_pos, uint64_t uncompressed_pos, bool final) +{ + // This is enough to hold sizes up to about 99 TiB if thousand + // separator is used, or about 1 PiB without thousand separator. + // After that the progress indicator will look a bit silly, since + // the compression ratio no longer fits with three decimal places. + static char buf[44]; + + char *pos = buf; + size_t left = sizeof(buf); + + // Print the sizes. If this the final message, use more reasonable + // units than MiB if the file was small. + progress_sizes_helper(&pos, &left, compressed_pos, final); + my_snprintf(&pos, &left, " / "); + progress_sizes_helper(&pos, &left, uncompressed_pos, final); + + // Avoid division by zero. If we cannot calculate the ratio, set + // it to some nice number greater than 10.0 so that it gets caught + // in the next if-clause. + const double ratio = uncompressed_pos > 0 + ? (double)(compressed_pos) / (double)(uncompressed_pos) + : 16.0; + + // If the ratio is very bad, just indicate that it is greater than + // 9.999. This way the length of the ratio field stays fixed. + if (ratio > 9.999) + snprintf(pos, left, " > %.3f", 9.999); + else + snprintf(pos, left, " = %.3f", ratio); + + return buf; +} + + +/// Make the string containing the processing speed of uncompressed data. +static const char * +progress_speed(uint64_t uncompressed_pos, double elapsed) +{ + // Don't print the speed immediatelly, since the early values look + // like somewhat random. + if (elapsed < 3.0) + return ""; + + static const char unit[][8] = { + "KiB/s", + "MiB/s", + "GiB/s", + }; + + size_t unit_index = 0; + + // Calculate the speed as KiB/s. + double speed = (double)(uncompressed_pos) / (elapsed * 1024.0); + + // Adjust the unit of the speed if needed. + while (speed > 999.9) { + speed /= 1024.0; + if (++unit_index == ARRAY_SIZE(unit)) + return ""; // Way too fast ;-) + } + + static char buf[sizeof("999.9 GiB/s")]; + snprintf(buf, sizeof(buf), "%.1f %s", speed, unit[unit_index]); + return buf; +} + + +/// Make a string indicating elapsed or remaining time. The format is either +/// M:SS or H:MM:SS depending on if the time is an hour or more. +static const char * +progress_time(uint32_t seconds) +{ + // 9999 hours = 416 days + static char buf[sizeof("9999:59:59")]; + + // Don't show anything if the time is zero or ridiculously big. + if (seconds == 0 || seconds > ((UINT32_C(9999) * 60) + 59) * 60 + 59) + return ""; + + uint32_t minutes = seconds / 60; + seconds %= 60; + + if (minutes >= 60) { + const uint32_t hours = minutes / 60; + minutes %= 60; + snprintf(buf, sizeof(buf), + "%" PRIu32 ":%02" PRIu32 ":%02" PRIu32, + hours, minutes, seconds); + } else { + snprintf(buf, sizeof(buf), "%" PRIu32 ":%02" PRIu32, + minutes, seconds); + } + + return buf; +} + + +/// Make the string to contain the estimated remaining time, or if the amount +/// of input isn't known, how much time has elapsed. +static const char * +progress_remaining(uint64_t in_pos, double elapsed) +{ + // If we don't know the size of the input, we indicate the time + // spent so far. + if (expected_in_size == 0 || in_pos > expected_in_size) + return progress_time((uint32_t)(elapsed)); + + // If we are at the very beginning of the file or the file is very + // small, don't give any estimate to avoid far too wrong estimations. + if (in_pos < (UINT64_C(1) << 19) || elapsed < 8.0) + return ""; + + // Calculate the estimate. Don't give an estimate of zero seconds, + // since it is possible that all the input has been already passed + // to the library, but there is still quite a bit of output pending. + uint32_t remaining = (double)(expected_in_size - in_pos) + * elapsed / (double)(in_pos); + if (remaining == 0) + remaining = 1; + + return progress_time(remaining); +} + + +extern void +message_progress_update(uint64_t in_pos, uint64_t out_pos) +{ + // If there's nothing to do, return immediatelly. + if (!progress_needs_updating || in_pos == 0) + return; + + // Print the filename if it hasn't been printed yet. + print_filename(); + + // Calculate how long we have been processing this file. + const double elapsed = my_time() - start_time; + + // Set compressed_pos and uncompressed_pos. + uint64_t compressed_pos; + uint64_t uncompressed_pos; + if (opt_mode == MODE_COMPRESS) { + compressed_pos = out_pos; + uncompressed_pos = in_pos; + } else { + compressed_pos = in_pos; + uncompressed_pos = out_pos; + } + + signals_block(); + + // Print the actual progress message. The idea is that there is at + // least three spaces between the fields in typical situations, but + // even in rare situations there is at least one space. + fprintf(stderr, " %7s %43s %11s %10s\r", + progress_percentage(in_pos), + progress_sizes(compressed_pos, uncompressed_pos, false), + progress_speed(uncompressed_pos, elapsed), + progress_remaining(in_pos, elapsed)); + + // Updating the progress info was finished. Reset + // progress_needs_updating to wait for the next SIGALRM. + // + // NOTE: This has to be done before alarm() call or with (very) bad + // luck we could be setting this to false after the alarm has already + // been triggered. + progress_needs_updating = false; + + if (progress_automatic) { + // Mark that the progress indicator is active, so if an error + // occurs, the error message gets printed cleanly. + progress_active = true; + + // Restart the timer so that progress_needs_updating gets + // set to true after about one second. + alarm(1); + } else { + // The progress message was printed because user had sent us + // SIGALRM. In this case, each progress message is printed + // on its own line. + fputc('\n', stderr); + } + + signals_unblock(); + + return; +} + + +extern void +message_progress_end(uint64_t in_pos, uint64_t out_pos, bool success) +{ + // If we are not in verbose mode, we have nothing to do. + if (verbosity < V_VERBOSE || user_abort) + return; + + // Cancel a pending alarm, if any. + if (progress_automatic) { + alarm(0); + progress_active = false; + } + + const double elapsed = my_time() - start_time; + + uint64_t compressed_pos; + uint64_t uncompressed_pos; + if (opt_mode == MODE_COMPRESS) { + compressed_pos = out_pos; + uncompressed_pos = in_pos; + } else { + compressed_pos = in_pos; + uncompressed_pos = out_pos; + } + + // If it took less than a second, don't display the time. + const char *elapsed_str = progress_time((double)(elapsed)); + + signals_block(); + + // When using the auto-updating progress indicator, the final + // statistics are printed in the same format as the progress + // indicator itself. + if (progress_automatic && in_pos > 0) { + // Using floating point conversion for the percentage instead + // of static "100.0 %" string, because the decimal separator + // isn't a dot in all locales. + fprintf(stderr, " %5.1f %% %43s %11s %10s\n", + 100.0, + progress_sizes(compressed_pos, uncompressed_pos, true), + progress_speed(uncompressed_pos, elapsed), + elapsed_str); + + // When no automatic progress indicator is used, don't print a verbose + // message at all if we something went wrong and we couldn't produce + // any output. If we did produce output, then it is sometimes useful + // to tell that to the user, especially if we detected an error after + // a time-consuming operation. + } else if (success || out_pos > 0) { + // The filename and size information are always printed. + fprintf(stderr, "%s: %s", filename, progress_sizes( + compressed_pos, uncompressed_pos, true)); + + // The speed and elapsed time aren't always shown. + const char *speed = progress_speed(uncompressed_pos, elapsed); + if (speed[0] != '\0') + fprintf(stderr, ", %s", speed); + + if (elapsed_str[0] != '\0') + fprintf(stderr, ", %s", elapsed_str); + + fputc('\n', stderr); + } + + signals_unblock(); + + return; +} + + +static void +vmessage(enum message_verbosity v, const char *fmt, va_list ap) +{ + if (v <= verbosity) { + signals_block(); + + // If there currently is a progress message on the screen, + // print a newline so that the progress message is left + // readable. This is good, because it is nice to be able to + // see where the error occurred. (The alternative would be + // to clear the progress message and replace it with the + // error message.) + if (progress_active) { + progress_active = false; + fputc('\n', stderr); + } + + fprintf(stderr, "%s: ", argv0); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + + signals_unblock(); + } + + return; +} + + +extern void +message(enum message_verbosity v, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vmessage(v, fmt, ap); + va_end(ap); + return; +} + + +extern void +message_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vmessage(V_WARNING, fmt, ap); + va_end(ap); + + set_exit_status(E_WARNING); + return; +} + + +extern void +message_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vmessage(V_ERROR, fmt, ap); + va_end(ap); + + set_exit_status(E_ERROR); + return; +} + + +extern void +message_fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vmessage(V_ERROR, fmt, ap); + va_end(ap); + + my_exit(E_ERROR); +} + + +extern void +message_bug(void) +{ + message_fatal(_("Internal error (bug)")); +} + + +extern void +message_signal_handler(void) +{ + message_fatal(_("Cannot establish signal handlers")); +} + + +extern const char * +message_strm(lzma_ret code) +{ + switch (code) { + case LZMA_NO_CHECK: + return _("No integrity check; not verifying file integrity"); + + case LZMA_UNSUPPORTED_CHECK: + return _("Unsupported type of integrity check; " + "not verifying file integrity"); + + case LZMA_MEM_ERROR: + return strerror(ENOMEM); + + case LZMA_MEMLIMIT_ERROR: + return _("Memory usage limit reached"); + + case LZMA_FORMAT_ERROR: + return _("File format not recognized"); + + case LZMA_OPTIONS_ERROR: + return _("Unsupported options"); + + case LZMA_DATA_ERROR: + return _("Compressed data is corrupt"); + + case LZMA_BUF_ERROR: + return _("Unexpected end of input"); + + case LZMA_OK: + case LZMA_STREAM_END: + case LZMA_GET_CHECK: + case LZMA_PROG_ERROR: + return _("Internal error (bug)"); + } + + return NULL; +} + + +extern void +message_try_help(void) +{ + // Print this with V_WARNING instead of V_ERROR to prevent it from + // showing up when --quiet has been specified. + message(V_WARNING, _("Try `%s --help' for more information."), argv0); + return; +} + + +extern void +message_version(void) +{ + // It is possible that liblzma version is different than the command + // line tool version, so print both. + printf("xz " PACKAGE_VERSION "\n"); + printf("liblzma %s\n", lzma_version_string()); + my_exit(E_SUCCESS); +} + + +extern void +message_help(bool long_help) +{ + printf(_("Usage: %s [OPTION]... [FILE]...\n" + "Compress or decompress FILEs in the .xz format.\n\n"), + argv0); + + puts(_("Mandatory arguments to long options are mandatory for " + "short options too.\n")); + + if (long_help) + puts(_(" Operation mode:\n")); + + puts(_( +" -z, --compress force compression\n" +" -d, --decompress force decompression\n" +" -t, --test test compressed file integrity\n" +" -l, --list list information about files")); + + if (long_help) + puts(_("\n Operation modifiers:\n")); + + puts(_( +" -k, --keep keep (don't delete) input files\n" +" -f, --force force overwrite of output file and (de)compress links\n" +" -c, --stdout write to standard output and don't delete input files")); + + if (long_help) + puts(_( +" -S, --suffix=.SUF use the suffix `.SUF' on compressed files\n" +" --files=[FILE] read filenames to process from FILE; if FILE is\n" +" omitted, filenames are read from the standard input;\n" +" filenames must be terminated with the newline character\n" +" --files0=[FILE] like --files but use the null character as terminator")); + + if (long_help) { + puts(_("\n Basic file format and compression options:\n")); + puts(_( +" -F, --format=FMT file format to encode or decode; possible values are\n" +" `auto' (default), `xz', `lzma', and `raw'\n" +" -C, --check=CHECK integrity check type: `crc32', `crc64' (default),\n" +" or `sha256'")); + } + + puts(_( +" -p, --preset=NUM compression preset: 1-2 fast compression, 3-6 good\n" +" compression, 7-9 excellent compression; default is 7")); + + puts(_( +" -M, --memory=NUM use roughly NUM bytes of memory at maximum; 0 indicates\n" +" the default setting, which depends on the operation mode\n" +" and the amount of physical memory (RAM)")); + + if (long_help) { + puts(_( +"\n Custom filter chain for compression (alternative for using presets):")); + +#if defined(HAVE_ENCODER_LZMA1) || defined(HAVE_DECODER_LZMA1) \ + || defined(HAVE_ENCODER_LZMA2) || defined(HAVE_DECODER_LZMA2) + puts(_( +"\n" +" --lzma1=[OPTS] LZMA1 or LZMA2; OPTS is a comma-separated list of zero or\n" +" --lzma2=[OPTS] more of the following options (valid values; default):\n" +" dict=NUM dictionary size (4KiB - 1536MiB; 8MiB)\n" +" lc=NUM number of literal context bits (0-4; 3)\n" +" lp=NUM number of literal position bits (0-4; 0)\n" +" pb=NUM number of position bits (0-4; 2)\n" +" mode=MODE compression mode (fast, normal; normal)\n" +" nice=NUM nice length of a match (2-273; 64)\n" +" mf=NAME match finder (hc3, hc4, bt2, bt3, bt4; bt4)\n" +" depth=NUM maximum search depth; 0=automatic (default)")); +#endif + + puts(_( +"\n" +" --x86 x86 filter (sometimes called BCJ filter)\n" +" --powerpc PowerPC (big endian) filter\n" +" --ia64 IA64 (Itanium) filter\n" +" --arm ARM filter\n" +" --armthumb ARM-Thumb filter\n" +" --sparc SPARC filter")); + +#if defined(HAVE_ENCODER_DELTA) || defined(HAVE_DECODER_DELTA) + puts(_( +"\n" +" --delta=[OPTS] Delta filter; valid OPTS (valid values; default):\n" +" dist=NUM distance between bytes being subtracted\n" +" from each other (1-256; 1)")); +#endif + +#if defined(HAVE_ENCODER_SUBBLOCK) || defined(HAVE_DECODER_SUBBLOCK) + puts(_( +"\n" +" --subblock=[OPTS] Subblock filter; valid OPTS (valid values; default):\n" +" size=NUM number of bytes of data per subblock\n" +" (1 - 256Mi; 4Ki)\n" +" rle=NUM run-length encoder chunk size (0-256; 0)")); +#endif + } + +/* + if (long_help) + puts(_( +"\n" +" Resource usage options:\n" +"\n" +" -M, --memory=NUM use roughly NUM bytes of memory at maximum; 0 indicates\n" +" the default setting, which depends on the operation mode\n" +" and the amount of physical memory (RAM)\n" +" -T, --threads=NUM use a maximum of NUM (de)compression threads" +// " --threading=STR threading style; possible values are `auto' (default),\n" +// " `files', and `stream' +)); +*/ + if (long_help) + puts(_("\n Other options:\n")); + + puts(_( +" -q, --quiet suppress warnings; specify twice to suppress errors too\n" +" -v, --verbose be verbose; specify twice for even more verbose")); + + if (long_help) + puts(_( +"\n" +" -h, --help display the short help (lists only the basic options)\n" +" -H, --long-help display this long help")); + else + puts(_( +" -h, --help display this short help\n" +" -H, --long-help display the long help (lists also the advanced options)")); + + puts(_( +" -V, --version display the version number")); + + puts(_("\nWith no FILE, or when FILE is -, read standard input.\n")); + + if (long_help) { + // FIXME !!! + size_t mem_limit = hardware_memlimit_encoder() / (1024 * 1024); + if (mem_limit == 0) + mem_limit = 1; + + // We use PRIu64 instead of %zu to support pre-C99 libc. + // FIXME: Use ' but avoid warnings. + puts(_("On this system and configuration, the tool will use")); + printf(_(" * roughly %" PRIu64 " MiB of memory at maximum; and\n"), + (uint64_t)(mem_limit)); + printf(N_(" * at maximum of one thread for (de)compression.\n\n", + " * at maximum of %" PRIu64 + " threads for (de)compression.\n\n", + (uint64_t)(opt_threads)), (uint64_t)(opt_threads)); + } + + printf(_("Report bugs to <%s> (in English or Finnish).\n"), + PACKAGE_BUGREPORT); + + my_exit(E_SUCCESS); +} diff --git a/src/lzma/message.h b/src/lzma/message.h new file mode 100644 index 00000000..7ef9b165 --- /dev/null +++ b/src/lzma/message.h @@ -0,0 +1,132 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file message.h +/// \brief Printing messages to stderr +// +// Copyright (C) 2007-2008 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. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MESSAGE_H +#define MESSAGE_H + + +/// Verbosity levels +enum message_verbosity { + V_SILENT, ///< No messages + V_ERROR, ///< Only error messages + V_WARNING, ///< Errors and warnings + V_VERBOSE, ///< Errors, warnings, and verbose statistics + V_DEBUG, ///< Debugging, FIXME remove? +}; + + +/// \brief Initializes the message functions +/// +/// \param argv0 Name of the program i.e. argv[0] from main() +/// \param verbosity Verbosity level +/// +/// If an error occurs, this function doesn't return. +/// +extern void message_init(const char *argv0); + + +/// Increase verbosity level by one step unless it was at maximum. +extern void message_verbosity_increase(void); + +/// Decrease verbosity level by one step unless it was at minimum. +extern void message_verbosity_decrease(void); + + +/// Set the total number of files to be processed (stdin is counted as a file +/// here). The default is one. +extern void message_set_files(unsigned int files); + + +/// \brief Print a message if verbosity level is at least "verbosity" +/// +/// This doesn't touch the exit status. +extern void message(enum message_verbosity verbosity, const char *fmt, ...) + lzma_attribute((format(printf, 2, 3))); + + +/// \brief Prints a warning and possibly sets exit status +/// +/// The message is printed only if verbosity level is at least V_WARNING. +/// The exit status is set to WARNING unless it was already at ERROR. +extern void message_warning(const char *fmt, ...) + lzma_attribute((format(printf, 1, 2))); + + +/// \brief Prints an error message and sets exit status +/// +/// The message is printed only if verbosity level is at least V_ERROR. +/// The exit status is set to ERROR. +extern void message_error(const char *fmt, ...) + lzma_attribute((format(printf, 1, 2))); + + +/// \brief Prints an error message and exits with EXIT_ERROR +/// +/// The message is printed only if verbosity level is at least V_ERROR. +extern void message_fatal(const char *fmt, ...) + lzma_attribute((format(printf, 1, 2))) + lzma_attribute((noreturn)); + + +/// Print an error message that an internal error occurred and exit with +/// EXIT_ERROR. +extern void message_bug(void) lzma_attribute((noreturn)); + + +/// Print a message that establishing signal handlers failed, and exit with +/// exit status ERROR. +extern void message_signal_handler(void) lzma_attribute((noreturn)); + + +/// Converts lzma_ret to a string. +extern const char *message_strm(lzma_ret code); + + +/// Print a message that user should try --help. +extern void message_try_help(void); + + +/// Prints the version number to stdout and exits with exit status SUCCESS. +extern void message_version(void) lzma_attribute((noreturn)); + + +/// Print the help message. +extern void message_help(bool long_help) lzma_attribute((noreturn)); + + +/// +extern void message_progress_start(const char *filename, uint64_t in_size); + + +/// +extern void message_progress_update(uint64_t in_pos, uint64_t out_pos); + + +/// \brief Finishes the progress message if we were in verbose mode +/// +/// \param in_pos Final input position i.e. how much input there was. +/// \param out_pos Final output position +/// \param success True if the operation was successful. We don't +/// print the final progress message if the operation +/// wasn't successful. +/// +extern void message_progress_end( + uint64_t in_pos, uint64_t out_pos, bool success); + +#endif diff --git a/src/lzma/options.c b/src/lzma/options.c index f5ebdd8e..77ebddd6 100644 --- a/src/lzma/options.c +++ b/src/lzma/options.c @@ -79,11 +79,9 @@ parse_options(const char *str, const option_map *opts, if (value != NULL) *value++ = '\0'; - if (value == NULL || value[0] == '\0') { - errmsg(V_ERROR, _("%s: Options must be `name=value' " + if (value == NULL || value[0] == '\0') + message_fatal(_("%s: Options must be `name=value' " "pairs separated with commas"), str); - my_exit(ERROR); - } // Look for the option name from the option map. bool found = false; @@ -106,11 +104,9 @@ parse_options(const char *str, const option_map *opts, break; } - if (opts[i].map[j].name == NULL) { - errmsg(V_ERROR, _("%s: Invalid option " + if (opts[i].map[j].name == NULL) + message_fatal(_("%s: Invalid option " "value"), value); - my_exit(ERROR); - } set(filter_options, i, opts[i].map[j].id); } @@ -119,10 +115,8 @@ parse_options(const char *str, const option_map *opts, break; } - if (!found) { - errmsg(V_ERROR, _("%s: Invalid option name"), name); - my_exit(ERROR); - } + if (!found) + message_fatal(_("%s: Invalid option name"), name); if (split == NULL) break; @@ -168,7 +162,7 @@ set_subblock(void *options, uint32_t key, uint64_t value) extern lzma_options_subblock * -parse_options_subblock(const char *str) +options_subblock(const char *str) { static const option_map opts[] = { { "size", NULL, LZMA_SUBBLOCK_DATA_SIZE_MIN, @@ -217,7 +211,7 @@ set_delta(void *options, uint32_t key, uint64_t value) extern lzma_options_delta * -parse_options_delta(const char *str) +options_delta(const char *str) { static const option_map opts[] = { { "dist", NULL, LZMA_DELTA_DIST_MIN, @@ -225,7 +219,7 @@ parse_options_delta(const char *str) { NULL, NULL, 0, 0 } }; - lzma_options_delta *options = xmalloc(sizeof(lzma_options_subblock)); + lzma_options_delta *options = xmalloc(sizeof(lzma_options_delta)); *options = (lzma_options_delta){ // It's hard to give a useful default for this. .type = LZMA_DELTA_TYPE_BYTE, @@ -296,7 +290,7 @@ set_lzma(void *options, uint32_t key, uint64_t value) extern lzma_options_lzma * -parse_options_lzma(const char *str) +options_lzma(const char *str) { static const name_id_map modes[] = { { "fast", LZMA_MODE_FAST }, @@ -345,18 +339,14 @@ parse_options_lzma(const char *str) parse_options(str, opts, &set_lzma, options); - if (options->lc + options->lp > LZMA_LCLP_MAX) { - errmsg(V_ERROR, "The sum of lc and lp must be at " - "maximum of 4"); - exit(ERROR); - } + if (options->lc + options->lp > LZMA_LCLP_MAX) + message_fatal(_("The sum of lc and lp must be at " + "maximum of 4")); const uint32_t nice_len_min = options->mf & 0x0F; - if (options->nice_len < nice_len_min) { - errmsg(V_ERROR, "The selected match finder requires at " - "least nice=%" PRIu32, nice_len_min); - exit(ERROR); - } + if (options->nice_len < nice_len_min) + message_fatal(_("The selected match finder requires at " + "least nice=%" PRIu32), nice_len_min); return options; } diff --git a/src/lzma/options.h b/src/lzma/options.h index 885c5969..4253ac3c 100644 --- a/src/lzma/options.h +++ b/src/lzma/options.h @@ -27,20 +27,20 @@ /// /// \return Pointer to allocated options structure. /// Doesn't return on error. -extern lzma_options_subblock *parse_options_subblock(const char *str); +extern lzma_options_subblock *options_subblock(const char *str); /// \brief Parser for Delta options /// /// \return Pointer to allocated options structure. /// Doesn't return on error. -extern lzma_options_delta *parse_options_delta(const char *str); +extern lzma_options_delta *options_delta(const char *str); /// \brief Parser for LZMA options /// /// \return Pointer to allocated options structure. /// Doesn't return on error. -extern lzma_options_lzma *parse_options_lzma(const char *str); +extern lzma_options_lzma *options_lzma(const char *str); #endif diff --git a/src/lzma/private.h b/src/lzma/private.h index f6a75645..b463a08e 100644 --- a/src/lzma/private.h +++ b/src/lzma/private.h @@ -22,32 +22,30 @@ #include "sysdefs.h" -#ifdef HAVE_ERRNO_H -# include <errno.h> -#else -extern int errno; -#endif - +#include <sys/types.h> #include <sys/stat.h> +#include <errno.h> #include <signal.h> -#include <pthread.h> #include <locale.h> #include <stdio.h> -#include <fcntl.h> #include <unistd.h> -#include "gettext.h" -#define _(msgid) gettext(msgid) -#define N_(msgid1, msgid2, n) ngettext(msgid1, msgid2, n) +#ifdef ENABLE_NLS +# include <libintl.h> +# define _(msgid) gettext(msgid) +# define N_(msgid1, msgid2, n) ngettext(msgid1, msgid2, n) +#else +# define _(msgid) (msgid) +# define N_(msgid1, msgid2, n) ((n) == 1 ? (msgid1) : (msgid2)) +#endif -#include "alloc.h" +#include "main.h" +#include "process.h" +#include "message.h" #include "args.h" -#include "error.h" #include "hardware.h" -#include "help.h" #include "io.h" #include "options.h" -#include "process.h" #include "suffix.h" #include "util.h" diff --git a/src/lzma/process.c b/src/lzma/process.c index fc4ef96a..d30878e4 100644 --- a/src/lzma/process.c +++ b/src/lzma/process.c @@ -20,137 +20,158 @@ #include "private.h" -typedef struct { - lzma_stream strm; - void *options; +enum operation_mode opt_mode = MODE_COMPRESS; - file_pair *pair; +enum format_type opt_format = FORMAT_AUTO; - /// We don't need this for *anything* but seems that at least with - /// glibc pthread_create() doesn't allow NULL. - pthread_t thread; - bool in_use; +/// Stream used to communicate with liblzma +static lzma_stream strm = LZMA_STREAM_INIT; -} thread_data; +/// Filters needed for all encoding all formats, and also decoding in raw data +static lzma_filter filters[LZMA_FILTERS_MAX + 1]; +/// Number of filters. Zero indicates that we are using a preset. +static size_t filters_count = 0; -/// Number of available threads -static size_t free_threads; +/// Number of the preset (1-9) +static size_t preset_number = 7; -/// Thread-specific data -static thread_data *threads; +/// Indicate if no preset has been given. In that case, we will auto-adjust +/// the compression preset so that it doesn't use too much RAM. +// FIXME +static bool preset_default = true; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +/// Integrity check type +static lzma_check check = LZMA_CHECK_CRC64; -/// Attributes of new coder threads. They are created in detached state. -/// Coder threads signal to the service thread themselves when they are done. -static pthread_attr_t thread_attr; +extern void +coder_set_check(lzma_check new_check) +{ + check = new_check; + return; +} -////////// -// Init // -////////// extern void -process_init(void) +coder_set_preset(size_t new_preset) { - threads = malloc(sizeof(thread_data) * opt_threads); - if (threads == NULL) { - out_of_memory(); - my_exit(ERROR); - } + preset_number = new_preset; + preset_default = false; + return; +} - for (size_t i = 0; i < opt_threads; ++i) - memzero(&threads[i], sizeof(threads[0])); - if (pthread_attr_init(&thread_attr) - || pthread_attr_setdetachstate( - &thread_attr, PTHREAD_CREATE_DETACHED)) { - out_of_memory(); - my_exit(ERROR); - } +extern void +coder_add_filter(lzma_vli id, void *options) +{ + if (filters_count == LZMA_FILTERS_MAX) + message_fatal(_("Maximum number of filters is four")); - free_threads = opt_threads; + filters[filters_count].id = id; + filters[filters_count].options = options; + ++filters_count; return; } -////////////////////////// -// Thread-specific data // -////////////////////////// - -static thread_data * -get_thread_data(void) +extern void +coder_set_compression_settings(void) { - pthread_mutex_lock(&mutex); + // Options for LZMA1 or LZMA2 in case we are using a preset. + static lzma_options_lzma opt_lzma; + + if (filters_count == 0) { + // We are using a preset. This is not a good idea in raw mode + // except when playing around with things. Different versions + // of this software may use different options in presets, and + // thus make uncompressing the raw data difficult. + if (opt_format == FORMAT_RAW) { + // The message is shown only if warnings are allowed + // but the exit status isn't changed. + message(V_WARNING, _("Using a preset in raw mode " + "is discouraged.")); + message(V_WARNING, _("The exact options of the " + "presets may vary between software " + "versions.")); + } - while (free_threads == 0) { - pthread_cond_wait(&cond, &mutex); + // Get the preset for LZMA1 or LZMA2. + if (lzma_lzma_preset(&opt_lzma, preset_number)) + message_bug(); - if (user_abort) { - pthread_cond_signal(&cond); - pthread_mutex_unlock(&mutex); - return NULL; - } + // Use LZMA2 except with --format=lzma we use LZMA1. + filters[0].id = opt_format == FORMAT_LZMA + ? LZMA_FILTER_LZMA1 : LZMA_FILTER_LZMA2; + filters[0].options = &opt_lzma; + filters_count = 1; } - thread_data *t = threads; - while (t->in_use) - ++t; + // Terminate the filter options array. + filters[filters_count].id = LZMA_VLI_UNKNOWN; - t->in_use = true; - --free_threads; + // If we are using the LZMA_Alone format, allow exactly one filter + // which has to be LZMA. + if (opt_format == FORMAT_LZMA && (filters_count != 1 + || filters[0].id != LZMA_FILTER_LZMA1)) + message_fatal(_("With --format=lzma only the LZMA1 filter " + "is supported")); - pthread_mutex_unlock(&mutex); - - return t; -} + // TODO: liblzma probably needs an API to validate the filter chain. + // If using --format=raw, we can be decoding. + uint64_t memory_usage; + uint64_t memory_limit; + if (opt_mode == MODE_COMPRESS) { + memory_usage = lzma_memusage_encoder(filters); + memory_limit = hardware_memlimit_encoder(); + } else { + memory_usage = lzma_memusage_decoder(filters); + memory_limit = hardware_memlimit_decoder(); + } -static void -release_thread_data(thread_data *t) -{ - pthread_mutex_lock(&mutex); + if (memory_usage == UINT64_MAX) + message_bug(); - t->in_use = false; - ++free_threads; + if (preset_default) { + // When no preset was explicitly requested, we use the default + // preset only if the memory usage limit allows. Otherwise we + // select a lower preset automatically. + while (memory_usage > memory_limit) { + if (preset_number == 1) + message_fatal(_("Memory usage limit is too " + "small for any internal " + "filter preset")); - pthread_cond_signal(&cond); - pthread_mutex_unlock(&mutex); + if (lzma_lzma_preset(&opt_lzma, --preset_number)) + message_bug(); - return; -} - - -static int -create_thread(void *(*func)(thread_data *t), thread_data *t) -{ - if (opt_threads == 1) { - func(t); - } else { - const int err = pthread_create(&t->thread, &thread_attr, - (void *(*)(void *))(func), t); - if (err) { - errmsg(V_ERROR, _("Cannot create a thread: %s"), - strerror(err)); - user_abort = 1; - return -1; + memory_usage = lzma_memusage_encoder(filters); } + } else { + if (memory_usage > memory_limit) + message_fatal(_("Memory usage limit is too small " + "for the given filter setup")); } - return 0; -} + // Limit the number of worked threads so that memory usage + // limit isn't exceeded. + assert(memory_usage > 0); + size_t thread_limit = memory_limit / memory_usage; + if (thread_limit == 0) + thread_limit = 1; + if (opt_threads > thread_limit) + opt_threads = thread_limit; + + return; +} -///////////////////////// -// One thread per file // -///////////////////////// -static int -single_init(thread_data *t) +static bool +coder_init(void) { lzma_ret ret = LZMA_PROG_ERROR; @@ -162,17 +183,15 @@ single_init(thread_data *t) break; case FORMAT_XZ: - ret = lzma_stream_encoder(&t->strm, - opt_filters, opt_check); + ret = lzma_stream_encoder(&strm, filters, check); break; case FORMAT_LZMA: - ret = lzma_alone_encoder(&t->strm, - opt_filters[0].options); + ret = lzma_alone_encoder(&strm, filters[0].options); break; case FORMAT_RAW: - ret = lzma_raw_encoder(&t->strm, opt_filters); + ret = lzma_raw_encoder(&strm, filters); break; } } else { @@ -181,254 +200,192 @@ single_init(thread_data *t) switch (opt_format) { case FORMAT_AUTO: - ret = lzma_auto_decoder(&t->strm, opt_memory, flags); + ret = lzma_auto_decoder(&strm, + hardware_memlimit_decoder(), flags); break; case FORMAT_XZ: - ret = lzma_stream_decoder(&t->strm, opt_memory, flags); + ret = lzma_stream_decoder(&strm, + hardware_memlimit_decoder(), flags); break; case FORMAT_LZMA: - ret = lzma_alone_decoder(&t->strm, opt_memory); + ret = lzma_alone_decoder(&strm, + hardware_memlimit_decoder()); break; case FORMAT_RAW: // Memory usage has already been checked in args.c. - ret = lzma_raw_decoder(&t->strm, opt_filters); + // FIXME Comment + ret = lzma_raw_decoder(&strm, filters); break; } } if (ret != LZMA_OK) { if (ret == LZMA_MEM_ERROR) - out_of_memory(); + message_error("%s", message_strm(LZMA_MEM_ERROR)); else - internal_error(); + message_bug(); - return -1; + return true; } - return 0; + return false; } -static void * -single(thread_data *t) +static bool +coder_run(file_pair *pair) { - if (single_init(t)) { - io_close(t->pair, false); - release_thread_data(t); - return NULL; - } + // Buffers to hold input and output data. + uint8_t in_buf[IO_BUFFER_SIZE]; + uint8_t out_buf[IO_BUFFER_SIZE]; + + // Initialize the progress indicator. + const uint64_t in_size = pair->src_st.st_size <= (off_t)(0) + ? 0 : (uint64_t)(pair->src_st.st_size); + message_progress_start(pair->src_name, in_size); - uint8_t in_buf[BUFSIZ]; - uint8_t out_buf[BUFSIZ]; lzma_action action = LZMA_RUN; lzma_ret ret; - bool success = false; - t->strm.avail_in = 0; - t->strm.next_out = out_buf; - t->strm.avail_out = BUFSIZ; + strm.avail_in = 0; + strm.next_out = out_buf; + strm.avail_out = IO_BUFFER_SIZE; while (!user_abort) { - if (t->strm.avail_in == 0 && !t->pair->src_eof) { - t->strm.next_in = in_buf; - t->strm.avail_in = io_read(t->pair, in_buf, BUFSIZ); + // Fill the input buffer if it is empty and we haven't reached + // end of file yet. + if (strm.avail_in == 0 && !pair->src_eof) { + strm.next_in = in_buf; + strm.avail_in = io_read(pair, in_buf, IO_BUFFER_SIZE); - if (t->strm.avail_in == SIZE_MAX) + if (strm.avail_in == SIZE_MAX) break; - if (t->pair->src_eof) + // Encoder needs to know when we have given all the + // input to it. The decoders need to know it too when + // we are using LZMA_CONCATENATED. + if (pair->src_eof) action = LZMA_FINISH; } - ret = lzma_code(&t->strm, action); + // Let liblzma do the actual work. + ret = lzma_code(&strm, action); - if ((t->strm.avail_out == 0 || ret != LZMA_OK) - && opt_mode != MODE_TEST) { - if (io_write(t->pair, out_buf, - BUFSIZ - t->strm.avail_out)) - break; + // Write out if the output buffer became full. + if (strm.avail_out == 0) { + if (opt_mode != MODE_TEST && io_write(pair, out_buf, + IO_BUFFER_SIZE - strm.avail_out)) + return false; - t->strm.next_out = out_buf; - t->strm.avail_out = BUFSIZ; + strm.next_out = out_buf; + strm.avail_out = IO_BUFFER_SIZE; } if (ret != LZMA_OK) { - // Check that there is no trailing garbage. This is - // needed for LZMA_Alone and raw streams. - if (ret == LZMA_STREAM_END && (t->strm.avail_in != 0 - || (!t->pair->src_eof && io_read( - t->pair, in_buf, 1) != 0))) - ret = LZMA_DATA_ERROR; - - if (ret != LZMA_STREAM_END) { - errmsg(V_ERROR, "%s: %s", t->pair->src_name, - str_strm_error(ret)); - break; + // Determine if the return value indicates that we + // won't continue coding. + const bool stop = ret != LZMA_NO_CHECK + && ret != LZMA_UNSUPPORTED_CHECK; + + if (stop) { + // First print the final progress info. + // This way the user sees more accurately + // where the error occurred. Note that we + // print this *before* the possible error + // message. + // + // FIXME: What if something goes wrong + // after this? + message_progress_end(strm.total_in, + strm.total_out, + ret == LZMA_STREAM_END); + + // Write the remaining bytes even if something + // went wrong, because that way the user gets + // as much data as possible, which can be good + // when trying to get at least some useful + // data out of damaged files. + if (opt_mode != MODE_TEST && io_write(pair, + out_buf, IO_BUFFER_SIZE + - strm.avail_out)) + return false; } - assert(t->pair->src_eof); - success = true; - break; - } - } - - io_close(t->pair, success); - release_thread_data(t); - - return NULL; -} + if (ret == LZMA_STREAM_END) { + // Check that there is no trailing garbage. + // This is needed for LZMA_Alone and raw + // streams. + if (strm.avail_in == 0 && (pair->src_eof + || io_read(pair, in_buf, 1) + == 0)) { + assert(pair->src_eof); + return true; + } + // FIXME: What about io_read() failing? -/////////////////////////////// -// Multiple threads per file // -/////////////////////////////// - -// TODO - -// I'm not sure what would the best way to implement this. Here's one -// possible way: -// - Reader thread would read the input data and control the coders threads. -// - Every coder thread is associated with input and output buffer pools. -// The input buffer pool is filled by reader thread, and the output buffer -// pool is emptied by the writer thread. -// - Writer thread writes the output data of the oldest living coder thread. -// -// The per-file thread started by the application's main thread is used as -// the reader thread. In the beginning, it starts the writer thread and the -// first coder thread. The coder thread would be left waiting for input from -// the reader thread, and the writer thread would be waiting for input from -// the coder thread. -// -// The reader thread reads the input data into a ring buffer, whose size -// depends on the value returned by lzma_chunk_size(). If the ring buffer -// gets full, the buffer is marked "to be finished", which indicates to -// the coder thread that no more input is coming. Then a new coder thread -// would be started. -// -// TODO - -/* -typedef struct { - /// Buffers - uint8_t (*buffers)[BUFSIZ]; - - /// Number of buffers - size_t buffer_count; - - /// buffers[read_pos] is the buffer currently being read. Once finish - /// is true and read_pos == write_pos, end of input has been reached. - size_t read_pos; - - /// buffers[write_pos] is the buffer into which data is currently - /// being written. - size_t write_pos; - - /// This variable matters only when read_pos == write_pos && finish. - /// In that case, this variable will contain the size of the - /// buffers[read_pos]. - size_t last_size; - - /// True once no more data is being written to the buffer. When this - /// is set, the last_size variable must have been set too. - bool finish; - - /// Mutex to protect access to the variables in this structure - pthread_mutex_t mutex; - - /// Condition to indicate when another thread can continue - pthread_cond_t cond; -} mem_pool; - - -static foo -multi_reader(thread_data *t) -{ - bool done = false; - - do { - const size_t size = io_read(t->pair, - m->buffers + m->write_pos, BUFSIZ); - if (size == SIZE_MAX) { - // TODO - } else if (t->pair->src_eof) { - m->last_size = size; - } - - pthread_mutex_lock(&m->mutex); - - if (++m->write_pos == m->buffer_count) - m->write_pos = 0; - - if (m->write_pos == m->read_pos || t->pair->src_eof) - m->finish = true; - - pthread_cond_signal(&m->cond); - pthread_mutex_unlock(&m->mutex); - - } while (!m->finish); - - return done ? 0 : -1; -} - - -static foo -multi_code() -{ - lzma_action = LZMA_RUN; - - while (true) { - pthread_mutex_lock(&m->mutex); + // We hadn't reached the end of the file. + ret = LZMA_DATA_ERROR; + assert(stop); + } - while (m->read_pos == m->write_pos && !m->finish) - pthread_cond_wait(&m->cond, &m->mutex); + // If we get here and stop is true, something went + // wrong and we print an error. Otherwise it's just + // a warning and coding can continue. + if (stop) { + message_error("%s: %s", pair->src_name, + message_strm(ret)); + } else { + message_warning("%s: %s", pair->src_name, + message_strm(ret)); + + // When compressing, all possible errors set + // stop to true. + assert(opt_mode != MODE_COMPRESS); + } - pthread_mutex_unlock(&m->mutex); + if (ret == LZMA_MEMLIMIT_ERROR) { + // Figure out how much memory would have + // actually needed. + // TODO + } - if (m->finish) { - t->strm.avail_in = m->last_size; - if (opt_mode == MODE_COMPRESS) - action = LZMA_FINISH; - } else { - t->strm.avail_in = BUFSIZ; + if (stop) + return false; } - t->strm.next_in = m->buffers + m->read_pos; - - const lzma_ret ret = lzma_code(&t->strm, action); - + // Show progress information if --verbose was specified and + // stderr is a terminal. + message_progress_update(strm.total_in, strm.total_out); } -} - -*/ + return false; +} -/////////////////////// -// Starting new file // -/////////////////////// extern void process_file(const char *filename) { - thread_data *t = get_thread_data(); - if (t == NULL) - return; // User abort - - // If this fails, it shows appropriate error messages too. - t->pair = io_open(filename); - if (t->pair == NULL) { - release_thread_data(t); + // First try initializing the coder. If it fails, it's useless to try + // opening the file. Check also for user_abort just in case if we had + // got a signal while initializing the coder. + if (coder_init() || user_abort) return; - } - // TODO Currently only one-thread-per-file mode is implemented. + // Try to open the input and output files. + file_pair *pair = io_open(filename); + if (pair == NULL) + return; - if (create_thread(&single, t)) { - io_close(t->pair, false); - release_thread_data(t); - } + // Do the actual coding. + const bool success = coder_run(pair); + + // Close the file pair. It needs to know if coding was successful to + // know if the source or target file should be unlinked. + io_close(pair, success); return; } diff --git a/src/lzma/process.h b/src/lzma/process.h index 7fdfbce6..de23eacb 100644 --- a/src/lzma/process.h +++ b/src/lzma/process.h @@ -23,6 +23,46 @@ #include "private.h" +enum operation_mode { + MODE_COMPRESS, + MODE_DECOMPRESS, + MODE_TEST, + MODE_LIST, +}; + + +// NOTE: The order of these is significant in suffix.c. +enum format_type { + FORMAT_AUTO, + FORMAT_XZ, + FORMAT_LZMA, + // HEADER_GZIP, + FORMAT_RAW, +}; + + +/// Operation mode of the command line tool. This is set in args.c and read +/// in several files. +extern enum operation_mode opt_mode; + +/// File format to use when encoding or what format(s) to accept when +/// decoding. This is a global because it's needed also in suffix.c. +/// This is set in args.c. +extern enum format_type opt_format; + + +/// Set the integrity check type used when compressing +extern void coder_set_check(lzma_check check); + +/// Set preset number +extern void coder_set_preset(size_t new_preset); + +/// Add a filter to the custom filter chain +extern void coder_add_filter(lzma_vli id, void *options); + +/// +extern void coder_set_compression_settings(void); + extern void process_init(void); extern void process_file(const char *filename); diff --git a/src/lzma/suffix.c b/src/lzma/suffix.c index 460acee2..0d46855a 100644 --- a/src/lzma/suffix.c +++ b/src/lzma/suffix.c @@ -20,6 +20,9 @@ #include "private.h" +static char *custom_suffix = NULL; + + struct suffix_pair { const char *compressed; const char *uncompressed; @@ -74,8 +77,8 @@ uncompressed_name(const char *src_name, const size_t src_len) if (opt_format == FORMAT_RAW) { // Don't check for known suffixes when --format=raw was used. - if (opt_suffix == NULL) { - errmsg(V_ERROR, _("%s: With --format=raw, " + if (custom_suffix == NULL) { + message_error(_("%s: With --format=raw, " "--suffix=.SUF is required unless " "writing to stdout"), src_name); return NULL; @@ -91,21 +94,17 @@ uncompressed_name(const char *src_name, const size_t src_len) } } - if (new_len == 0 && opt_suffix != NULL) - new_len = test_suffix(opt_suffix, src_name, src_len); + if (new_len == 0 && custom_suffix != NULL) + new_len = test_suffix(custom_suffix, src_name, src_len); if (new_len == 0) { - errmsg(V_WARNING, _("%s: Filename has an unknown suffix, " + message_warning(_("%s: Filename has an unknown suffix, " "skipping"), src_name); return NULL; } const size_t new_suffix_len = strlen(new_suffix); - char *dest_name = malloc(new_len + new_suffix_len + 1); - if (dest_name == NULL) { - out_of_memory(); - return NULL; - } + char *dest_name = xmalloc(new_len + new_suffix_len + 1); memcpy(dest_name, src_name, new_len); memcpy(dest_name + new_len, new_suffix, new_suffix_len); @@ -154,7 +153,7 @@ compressed_name(const char *src_name, const size_t src_len) for (size_t i = 0; suffixes[i].compressed != NULL; ++i) { if (test_suffix(suffixes[i].compressed, src_name, src_len) != 0) { - errmsg(V_WARNING, _("%s: File already has `%s' " + message_warning(_("%s: File already has `%s' " "suffix, skipping"), src_name, suffixes[i].compressed); return NULL; @@ -163,22 +162,18 @@ compressed_name(const char *src_name, const size_t src_len) // TODO: Hmm, maybe it would be better to validate this in args.c, // since the suffix handling when decoding is weird now. - if (opt_format == FORMAT_RAW && opt_suffix == NULL) { - errmsg(V_ERROR, _("%s: With --format=raw, " + if (opt_format == FORMAT_RAW && custom_suffix == NULL) { + message_error(_("%s: With --format=raw, " "--suffix=.SUF is required unless " "writing to stdout"), src_name); return NULL; } - const char *suffix = opt_suffix != NULL - ? opt_suffix : suffixes[0].compressed; + const char *suffix = custom_suffix != NULL + ? custom_suffix : suffixes[0].compressed; const size_t suffix_len = strlen(suffix); - char *dest_name = malloc(src_len + suffix_len + 1); - if (dest_name == NULL) { - out_of_memory(); - return NULL; - } + char *dest_name = xmalloc(src_len + suffix_len + 1); memcpy(dest_name, src_name, src_len); memcpy(dest_name + src_len, suffix, suffix_len); @@ -189,7 +184,7 @@ compressed_name(const char *src_name, const size_t src_len) extern char * -get_dest_name(const char *src_name) +suffix_get_dest_name(const char *src_name) { assert(src_name != NULL); @@ -201,3 +196,18 @@ get_dest_name(const char *src_name) ? compressed_name(src_name, src_len) : uncompressed_name(src_name, src_len); } + + +extern void +suffix_set(const char *suffix) +{ + // Empty suffix and suffixes having a slash are rejected. Such + // suffixes would break things later. + if (suffix[0] == '\0' || strchr(suffix, '/') != NULL) + message_fatal(_("%s: Invalid filename suffix"), optarg); + + // Replace the old custom_suffix (if any) with the new suffix. + free(custom_suffix); + custom_suffix = xstrdup(suffix); + return; +} diff --git a/src/lzma/suffix.h b/src/lzma/suffix.h index 08315659..c92b92dc 100644 --- a/src/lzma/suffix.h +++ b/src/lzma/suffix.h @@ -20,6 +20,21 @@ #ifndef SUFFIX_H #define SUFFIX_H -extern char *get_dest_name(const char *src_name); +/// \brief Get the name of the destination file +/// +/// Depending on the global variable opt_mode, this tries to find a matching +/// counterpart for src_name. If the name can be constructed, it is allocated +/// and returned (caller must free it). On error, a message is printed and +/// NULL is returned. +extern char *suffix_get_dest_name(const char *src_name); + + +/// \brief Set a custom filename suffix +/// +/// This function calls xstrdup() for the given suffix, thus the caller +/// doesn't need to keep the memory allocated. There can be only one custom +/// suffix, thus if this is called multiple times, the old suffixes are freed +/// and forgotten. +extern void suffix_set(const char *suffix); #endif diff --git a/src/lzma/util.c b/src/lzma/util.c index 4bdbf8ec..13b67925 100644 --- a/src/lzma/util.c +++ b/src/lzma/util.c @@ -20,17 +20,29 @@ #include "private.h" -/// \brief Fancy version of strtoull() -/// -/// \param name Name of the option to show in case of an error -/// \param value String containing the number to be parsed; may -/// contain suffixes "k", "M", "G", "Ki", "Mi", or "Gi" -/// \param min Minimum valid value -/// \param max Maximum valid value -/// -/// \return Parsed value that is in the range [min, max]. Does not return -/// if an error occurs. -/// +extern void * +xrealloc(void *ptr, size_t size) +{ + assert(size > 0); + + ptr = realloc(ptr, size); + if (ptr == NULL) + message_fatal("%s", strerror(errno)); + + return ptr; +} + + +extern char * +xstrdup(const char *src) +{ + assert(src != NULL); + const size_t size = strlen(src) + 1; + char *dest = xmalloc(size); + return memcpy(dest, src, size); +} + + extern uint64_t str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max) { @@ -40,12 +52,9 @@ str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max) while (*value == ' ' || *value == '\t') ++value; - if (*value < '0' || *value > '9') { - errmsg(V_ERROR, _("%s: Value is not a non-negative " - "decimal integer"), - value); - my_exit(ERROR); - } + if (*value < '0' || *value > '9') + message_fatal(_("%s: Value is not a non-negative " + "decimal integer"), value); do { // Don't overflow. @@ -86,12 +95,11 @@ str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max) } if (multiplier == 0) { - errmsg(V_ERROR, _("%s: Invalid multiplier suffix. " + message(V_ERROR, _("%s: Invalid multiplier suffix. " "Valid suffixes:"), value); - errmsg(V_ERROR, "`k' (10^3), `M' (10^6), `G' (10^9) " + message_fatal("`k' (10^3), `M' (10^6), `G' (10^9) " "`Ki' (2^10), `Mi' (2^20), " "`Gi' (2^30)"); - my_exit(ERROR); } // Don't overflow here either. @@ -107,32 +115,10 @@ str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max) return result; error: - errmsg(V_ERROR, _("Value of the option `%s' must be in the range " + message_fatal(_("Value of the option `%s' must be in the range " "[%llu, %llu]"), name, (unsigned long long)(min), (unsigned long long)(max)); - my_exit(ERROR); -} - - -/// \brief Gets filename part from pathname+filename -/// -/// \return Pointer in the filename where the actual filename starts. -/// If the last character is a slash, NULL is returned. -/// -extern const char * -str_filename(const char *name) -{ - const char *base = strrchr(name, '/'); - - if (base == NULL) { - base = name; - } else if (*++base == '\0') { - base = NULL; - errmsg(V_ERROR, _("%s: Invalid filename"), name); - } - - return base; } @@ -179,9 +165,35 @@ extern bool is_empty_filename(const char *filename) { if (filename[0] == '\0') { - errmsg(V_WARNING, _("Empty filename, skipping")); + message_error(_("Empty filename, skipping")); return true; } return false; } + + +extern bool +is_tty_stdin(void) +{ + const bool ret = isatty(STDIN_FILENO); + + if (ret) + message_error(_("Compressed data not read from a terminal " + "unless `--force' is used.")); + + return ret; +} + + +extern bool +is_tty_stdout(void) +{ + const bool ret = isatty(STDOUT_FILENO); + + if (ret) + message_error(_("Compressed data not written to a terminal " + "unless `--force' is used.")); + + return ret; +} diff --git a/src/lzma/util.h b/src/lzma/util.h index 91bd9ba3..dca62b26 100644 --- a/src/lzma/util.h +++ b/src/lzma/util.h @@ -20,13 +20,52 @@ #ifndef UTIL_H #define UTIL_H -#include "private.h" +/// \brief Safe malloc() that never returns NULL +/// +/// \note xmalloc(), xrealloc(), and xstrdup() must not be used when +/// there are files open for writing, that should be cleaned up +/// before exiting. +#define xmalloc(size) xrealloc(NULL, size) + +/// \brief Safe realloc() that never returns NULL +extern void *xrealloc(void *ptr, size_t size); + + +/// \brief Safe strdup() that never returns NULL +extern char *xstrdup(const char *src); + + +/// \brief Fancy version of strtoull() +/// +/// \param name Name of the option to show in case of an error +/// \param value String containing the number to be parsed; may +/// contain suffixes "k", "M", "G", "Ki", "Mi", or "Gi" +/// \param min Minimum valid value +/// \param max Maximum valid value +/// +/// \return Parsed value that is in the range [min, max]. Does not return +/// if an error occurs. +/// extern uint64_t str_to_uint64(const char *name, const char *value, uint64_t min, uint64_t max); -extern const char *str_filename(const char *filename); +/// \brief Check if filename is empty and print an error message extern bool is_empty_filename(const char *filename); + +/// \brief Test if stdin is a terminal +/// +/// If stdin is a terminal, an error message is printed and exit status set +/// to EXIT_ERROR. +extern bool is_tty_stdin(void); + + +/// \brief Test if stdout is a terminal +/// +/// If stdout is a terminal, an error message is printed and exit status set +/// to EXIT_ERROR. +extern bool is_tty_stdout(void); + #endif |