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); +}