1///////////////////////////////////////////////////////////////////////////////
2//
3/// \file       lzmainfo.c
4/// \brief      lzmainfo tool for compatibility with LZMA Utils
5//
6//  Author:     Lasse Collin
7//
8//  This file has been put into the public domain.
9//  You can do whatever you want with this file.
10//
11///////////////////////////////////////////////////////////////////////////////
12
13#include "sysdefs.h"
14#include <stdio.h>
15#include <errno.h>
16
17#include "lzma.h"
18#include "getopt.h"
19#include "tuklib_gettext.h"
20#include "tuklib_progname.h"
21#include "tuklib_exit.h"
22
23#ifdef TUKLIB_DOSLIKE
24#	include <fcntl.h>
25#	include <io.h>
26#endif
27
28
29static void lzma_attribute((__noreturn__))
30help(void)
31{
32	printf(
33_("Usage: %s [--help] [--version] [FILE]...\n"
34"Show information stored in the .lzma file header"), progname);
35
36	printf(_(
37"\nWith no FILE, or when FILE is -, read standard input.\n"));
38	printf("\n");
39
40	printf(_("Report bugs to <%s> (in English or Finnish).\n"),
41			PACKAGE_BUGREPORT);
42	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
43
44	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, true);
45}
46
47
48static void lzma_attribute((__noreturn__))
49version(void)
50{
51	puts("lzmainfo (" PACKAGE_NAME ") " LZMA_VERSION_STRING);
52	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, true);
53}
54
55
56/// Parse command line options.
57static void
58parse_args(int argc, char **argv)
59{
60	enum {
61		OPT_HELP,
62		OPT_VERSION,
63	};
64
65	static const struct option long_opts[] = {
66		{ "help",    no_argument, NULL, OPT_HELP },
67		{ "version", no_argument, NULL, OPT_VERSION },
68		{ NULL,      0,           NULL, 0 }
69	};
70
71	int c;
72	while ((c = getopt_long(argc, argv, "", long_opts, NULL)) != -1) {
73		switch (c) {
74		case OPT_HELP:
75			help();
76
77		case OPT_VERSION:
78			version();
79
80		default:
81			exit(EXIT_FAILURE);
82		}
83	}
84
85	return;
86}
87
88
89/// Primitive base-2 logarithm for integers
90static uint32_t
91my_log2(uint32_t n)
92{
93	uint32_t e;
94	for (e = 0; n > 1; ++e, n /= 2) ;
95	return e;
96}
97
98
99/// Parse the .lzma header and display information about it.
100static bool
101lzmainfo(const char *name, FILE *f)
102{
103	uint8_t buf[13];
104	const size_t size = fread(buf, 1, sizeof(buf), f);
105	if (size != 13) {
106		fprintf(stderr, "%s: %s: %s\n", progname, name,
107				ferror(f) ? strerror(errno)
108				: _("File is too small to be a .lzma file"));
109		return true;
110	}
111
112	lzma_filter filter = { .id = LZMA_FILTER_LZMA1 };
113
114	// Parse the first five bytes.
115	switch (lzma_properties_decode(&filter, NULL, buf, 5)) {
116	case LZMA_OK:
117		break;
118
119	case LZMA_OPTIONS_ERROR:
120		fprintf(stderr, "%s: %s: %s\n", progname, name,
121				_("Not a .lzma file"));
122		return true;
123
124	case LZMA_MEM_ERROR:
125		fprintf(stderr, "%s: %s\n", progname, strerror(ENOMEM));
126		exit(EXIT_FAILURE);
127
128	default:
129		fprintf(stderr, "%s: %s\n", progname,
130				_("Internal error (bug)"));
131		exit(EXIT_FAILURE);
132	}
133
134	// Uncompressed size
135	uint64_t uncompressed_size = 0;
136	for (size_t i = 0; i < 8; ++i)
137		uncompressed_size |= (uint64_t)(buf[5 + i]) << (i * 8);
138
139	// Display the results. We don't want to translate these and also
140	// will use MB instead of MiB, because someone could be parsing
141	// this output and we don't want to break that when people move
142	// from LZMA Utils to XZ Utils.
143	if (f != stdin)
144		printf("%s\n", name);
145
146	printf("Uncompressed size:             ");
147	if (uncompressed_size == UINT64_MAX)
148		printf("Unknown");
149	else
150		printf("%" PRIu64 " MB (%" PRIu64 " bytes)",
151				(uncompressed_size + 512 * 1024)
152					/ (1024 * 1024),
153				uncompressed_size);
154
155	lzma_options_lzma *opt = filter.options;
156
157	printf("\nDictionary size:               "
158			"%" PRIu32 " MB (2^%" PRIu32 " bytes)\n"
159			"Literal context bits (lc):     %" PRIu32 "\n"
160			"Literal pos bits (lp):         %" PRIu32 "\n"
161			"Number of pos bits (pb):       %" PRIu32 "\n",
162			(opt->dict_size + 512 * 1024) / (1024 * 1024),
163			my_log2(opt->dict_size), opt->lc, opt->lp, opt->pb);
164
165	free(opt);
166
167	return false;
168}
169
170
171extern int
172main(int argc, char **argv)
173{
174	tuklib_progname_init(argv);
175	tuklib_gettext_init(PACKAGE, LOCALEDIR);
176
177	parse_args(argc, argv);
178
179#ifdef TUKLIB_DOSLIKE
180	setmode(fileno(stdin), O_BINARY);
181#endif
182
183	int ret = EXIT_SUCCESS;
184
185	// We print empty lines around the output only when reading from
186	// files specified on the command line. This is due to how
187	// LZMA Utils did it.
188	if (optind == argc) {
189		if (lzmainfo("(stdin)", stdin))
190			ret = EXIT_FAILURE;
191	} else {
192		printf("\n");
193
194		do {
195			if (strcmp(argv[optind], "-") == 0) {
196				if (lzmainfo("(stdin)", stdin))
197					ret = EXIT_FAILURE;
198			} else {
199				FILE *f = fopen(argv[optind], "r");
200				if (f == NULL) {
201					ret = EXIT_FAILURE;
202					fprintf(stderr, "%s: %s: %s\n",
203							progname,
204							argv[optind],
205							strerror(errno));
206					continue;
207				}
208
209				if (lzmainfo(argv[optind], f))
210					ret = EXIT_FAILURE;
211
212				printf("\n");
213				fclose(f);
214			}
215		} while (++optind < argc);
216	}
217
218	tuklib_exit(ret, EXIT_FAILURE, true);
219}
220