Add lzmainfo for backward compatibility with LZMA Utils.
authorLasse Collin <lasse.collin@tukaani.org>
Thu, 13 Aug 2009 09:55:45 +0000 (12:55 +0300)
committerLasse Collin <lasse.collin@tukaani.org>
Thu, 13 Aug 2009 09:55:45 +0000 (12:55 +0300)
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
src/Makefile.am
src/lzmainfo/Makefile.am [new file with mode: 0644]
src/lzmainfo/lzmainfo.1 [new file with mode: 0644]
src/lzmainfo/lzmainfo.c [new file with mode: 0644]

index d46ebee..6efb12e 100644 (file)
@@ -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
index 8031d61..f03f5a3 100644 (file)
@@ -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 (file)
index 0000000..e04a1d9
--- /dev/null
@@ -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 (file)
index 0000000..ef736a6
--- /dev/null
@@ -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 (file)
index 0000000..d9ae311
--- /dev/null
@@ -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 <stdio.h>
+#include <errno.h>
+
+#ifdef ENABLE_NLS
+#      include <libintl.h>
+#      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);
+}