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