From e51b4e49e800bd84e6d589dca2964d3985e88139 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Thu, 13 Aug 2009 12:55:45 +0300 Subject: [PATCH] Add lzmainfo for backward compatibility with LZMA Utils. lzmainfo now links against static liblzma. In contrast to other command line tools in XZ Utils, linking lzmainfo against static liblzma by default is dumb. This will be fixed once I have fixed some related issues in configure.ac. --- configure.ac | 1 + src/Makefile.am | 2 +- src/lzmainfo/Makefile.am | 29 +++++ src/lzmainfo/lzmainfo.1 | 55 +++++++++ src/lzmainfo/lzmainfo.c | 242 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 src/lzmainfo/Makefile.am create mode 100644 src/lzmainfo/lzmainfo.1 create mode 100644 src/lzmainfo/lzmainfo.c diff --git a/configure.ac b/configure.ac index d46ebee1..6efb12ed 100644 --- a/configure.ac +++ b/configure.ac @@ -663,6 +663,7 @@ AC_CONFIG_FILES([ src/liblzma/api/Makefile src/xz/Makefile src/xzdec/Makefile + src/lzmainfo/Makefile src/scripts/Makefile src/scripts/xzdiff src/scripts/xzgrep diff --git a/src/Makefile.am b/src/Makefile.am index 8031d61d..f03f5a3d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,5 +5,5 @@ ## You can do whatever you want with this file. ## -SUBDIRS = liblzma xz xzdec scripts +SUBDIRS = liblzma xz xzdec lzmainfo scripts EXTRA_DIST = common diff --git a/src/lzmainfo/Makefile.am b/src/lzmainfo/Makefile.am new file mode 100644 index 00000000..e04a1d99 --- /dev/null +++ b/src/lzmainfo/Makefile.am @@ -0,0 +1,29 @@ +## +## Author: Lasse Collin +## +## This file has been put into the public domain. +## You can do whatever you want with this file. +## + +bin_PROGRAMS = lzmainfo + +lzmainfo_SOURCES = lzmainfo.c + +lzmainfo_CPPFLAGS = \ + -DLOCALEDIR=\"$(localedir)\" \ + -I$(top_srcdir)/src/common \ + -I$(top_srcdir)/src/liblzma/api \ + -I$(top_builddir)/lib \ + $(STATIC_CPPFLAGS) + +lzmainfo_LDFLAGS = $(STATIC_LDFLAGS) +lzmainfo_LDADD = $(top_builddir)/src/liblzma/liblzma.la + +if COND_GNULIB +lzmainfo_LDADD += $(top_builddir)/lib/libgnu.a +endif + +lzmainfo_LDADD += $(LTLIBINTL) + + +dist_man_MANS = lzmainfo.1 diff --git a/src/lzmainfo/lzmainfo.1 b/src/lzmainfo/lzmainfo.1 new file mode 100644 index 00000000..ef736a6c --- /dev/null +++ b/src/lzmainfo/lzmainfo.1 @@ -0,0 +1,55 @@ +.\" +.\" Author: Lasse Collin +.\" +.\" This file has been put into the public domain. +.\" You can do whatever you want with this file. +.\" +.TH LZMAINFO 1 "2009-08-13" "Tukaani" "XZ Utils" +.SH NAME +lzmainfo \- show infomation stored in the .lzma file header +.SH SYNOPSIS +.B lzmainfo +.RB [ \-\-help ] +.RB [ \-\-version ] +.RI [ file ]... +.SH DESCRIPTION +.B lzmainfo +shows information stored in the +.B .lzma +file header. It reads the first 13 bytes from the specified +.IR file , +decodes the header, and prints it to standard output in human +readable format. If no +.I files +are given or +.I file +is +.BR \- , +standard input is read. +.PP +Usually the most interesting information is the uncompressed size and +the dictionary size. Uncompressed size can be shown only if the file is +in the non-streamed +.B .lzma +format variant. The amount of memory required to decompress the file is +a few dozen kilobytes plus the dictionary size. +.PP +.B lzmainfo +is included in XZ Utils primarily for backward compatibility with LZMA Utils. +.SH EXIT STATUS +.TP +.B 0 +All is good. +.TP +.B 1 +An error occurred. +.SH BUGS +.B lzmainfo +uses +.B MB +while the correct suffix would be +.B MiB +(2^20 bytes). +This is to keep the output compatible with LZMA Utils. +.SH SEE ALSO +.BR xz (1) diff --git a/src/lzmainfo/lzmainfo.c b/src/lzmainfo/lzmainfo.c new file mode 100644 index 00000000..d9ae311a --- /dev/null +++ b/src/lzmainfo/lzmainfo.c @@ -0,0 +1,242 @@ +/////////////////////////////////////////////////////////////////////////////// +// +/// \file lzmainfo.c +/// \brief lzmainfo tool for compatibility with LZMA Utils +// +// Author: Lasse Collin +// +// This file has been put into the public domain. +// You can do whatever you want with this file. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "sysdefs.h" +#include +#include + +#ifdef ENABLE_NLS +# include +# define _(msgid) gettext(msgid) +#else +# define _(msgid) msgid +#endif + +#include "lzma.h" +#include "getopt.h" + + +/// Name of the program from argv[0] +static const char *argv0; + + +/// Close stdout unless we are already going to exit with EXIT_FAILURE. +/// If closing stdout fails, set exit status to EXIT_FAILURE and print +/// an error message to stderr. We don't care about closing stderr, +/// because we don't print anything to stderr unless we are going to +/// use EXIT_FAILURE anyway. +static void lzma_attribute((noreturn)) +my_exit(int status) +{ + if (status != EXIT_FAILURE) { + 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. + fprintf(stderr, "%s: %s: %s\n", argv0, + _("Writing to standard output " + "failed"), + fclose_err ? strerror(errno) + : _("Unknown error")); + status = EXIT_FAILURE; + } + } + + exit(status); +} + + +static void lzma_attribute((noreturn)) +help(void) +{ + printf( +_("Usage: %s [--help] [--version] [FILE]...\n" +"Show information stored in the .lzma file header"), argv0); + + printf(_( +"\nWith no FILE, or when FILE is -, read standard input.\n")); + printf("\n"); + + printf(_("Report bugs to <%s> (in English or Finnish).\n"), + PACKAGE_BUGREPORT); + printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_HOMEPAGE); + + my_exit(EXIT_SUCCESS); +} + + +static void lzma_attribute((noreturn)) +version(void) +{ + puts("lzmainfo (" PACKAGE_NAME ") " PACKAGE_VERSION); + my_exit(EXIT_SUCCESS); +} + + +/// Parse command line options. +static void +parse_args(int argc, char **argv) +{ + enum { + OPT_HELP, + OPT_VERSION, + }; + + static const struct option long_opts[] = { + { "help", no_argument, NULL, OPT_HELP }, + { "version", no_argument, NULL, OPT_VERSION }, + { NULL, 0, NULL, 0 } + }; + + int c; + while ((c = getopt_long(argc, argv, "", long_opts, NULL)) != -1) { + switch (c) { + case OPT_HELP: + help(); + + case OPT_VERSION: + version(); + + default: + exit(EXIT_FAILURE); + } + } + + return; +} + + +/// Primitive base-2 logarithm for integers +static uint32_t +my_log2(uint32_t n) +{ + uint32_t e; + for (e = 0; n > 1; ++e, n /= 2) ; + return e; +} + + +/// Parse the .lzma header and display information about it. +static bool +lzmainfo(const char *name, FILE *f) +{ + uint8_t buf[13]; + const size_t size = fread(buf, 1, sizeof(buf), f); + if (size != 13) { + fprintf(stderr, "%s: %s: %s\n", argv0, name, + ferror(f) ? strerror(errno) + : _("File is too small to be a .lzma file")); + return true; + } + + lzma_filter filter = { .id = LZMA_FILTER_LZMA1 }; + + // Parse the first five bytes. + switch (lzma_properties_decode(&filter, NULL, buf, 5)) { + case LZMA_OK: + break; + + case LZMA_OPTIONS_ERROR: + fprintf(stderr, "%s: %s: %s\n", argv0, name, + _("Not a .lzma file")); + return true; + + case LZMA_MEM_ERROR: + fprintf(stderr, "%s: %s\n", argv0, strerror(ENOMEM)); + exit(EXIT_FAILURE); + + default: + fprintf(stderr, "%s: %s\n", argv0, _("Internal error (bug)")); + exit(EXIT_FAILURE); + } + + // Uncompressed size + uint64_t uncompressed_size = 0; + for (size_t i = 0; i < 8; ++i) + uncompressed_size |= (uint64_t)(buf[5 + i]) << (i * 8); + + // Display the results. We don't want to translate these and also + // will use MB instead of MiB, because someone could be parsing + // this output and we don't want to break that when people move + // from LZMA Utils to XZ Utils. + if (f != stdin) + printf("%s\n", name); + + printf("Uncompressed size: "); + if (uncompressed_size == UINT64_MAX) + printf("Unknown"); + else + printf("%" PRIu64 " MB (%" PRIu64 " bytes)", + (uncompressed_size + 512 * 1024) + / (1024 * 1024), + uncompressed_size); + + lzma_options_lzma *opt = filter.options; + + printf("\nDictionary size: " + "%u MB (2^%u bytes)\n" + "Literal context bits (lc): %" PRIu32 "\n" + "Literal pos bits (lp): %" PRIu32 "\n" + "Number of pos bits (pb): %" PRIu32 "\n", + (opt->dict_size + 512 * 1024) / (1024 * 1024), + my_log2(opt->dict_size), opt->lc, opt->lp, opt->pb); + + free(opt); + + return false; +} + + +extern int +main(int argc, char **argv) +{ + int ret = EXIT_SUCCESS; + argv0 = argv[0]; + + parse_args(argc, argv); + + // We print empty lines around the output only when reading from + // files specified on the command line. This is due to how + // LZMA Utils did it. + if (optind == argc) { + lzmainfo("(stdin)", stdin); + } else { + printf("\n"); + + do { + if (strcmp(argv[optind], "-") == 0) { + if (lzmainfo("(stdin)", stdin)) + ret = EXIT_FAILURE; + } else { + FILE *f = fopen(argv[optind], "r"); + if (f == NULL) { + ret = EXIT_FAILURE; + fprintf(stderr, "%s: %s: %s\n", + argv0, argv[optind], + strerror(errno)); + continue; + } + + if (lzmainfo(argv[optind], f)) + ret = EXIT_FAILURE; + + printf("\n"); + fclose(f); + } + } while (++optind < argc); + } + + my_exit(ret); +}