1207753Smm///////////////////////////////////////////////////////////////////////////////
2207753Smm//
3207753Smm/// \file       lzmainfo.c
4207753Smm/// \brief      lzmainfo tool for compatibility with LZMA Utils
5207753Smm//
6207753Smm//  Author:     Lasse Collin
7207753Smm//
8207753Smm//  This file has been put into the public domain.
9207753Smm//  You can do whatever you want with this file.
10207753Smm//
11207753Smm///////////////////////////////////////////////////////////////////////////////
12207753Smm
13207753Smm#include "sysdefs.h"
14207753Smm#include <stdio.h>
15207753Smm#include <errno.h>
16207753Smm
17207753Smm#include "lzma.h"
18207753Smm#include "getopt.h"
19207753Smm#include "tuklib_gettext.h"
20207753Smm#include "tuklib_progname.h"
21207753Smm#include "tuklib_exit.h"
22207753Smm
23213700Smm#ifdef TUKLIB_DOSLIKE
24213700Smm#	include <fcntl.h>
25213700Smm#	include <io.h>
26213700Smm#endif
27207753Smm
28213700Smm
29223935Smmstatic void lzma_attribute((__noreturn__))
30207753Smmhelp(void)
31207753Smm{
32207753Smm	printf(
33207753Smm_("Usage: %s [--help] [--version] [FILE]...\n"
34207753Smm"Show information stored in the .lzma file header"), progname);
35207753Smm
36207753Smm	printf(_(
37207753Smm"\nWith no FILE, or when FILE is -, read standard input.\n"));
38207753Smm	printf("\n");
39207753Smm
40207753Smm	printf(_("Report bugs to <%s> (in English or Finnish).\n"),
41207753Smm			PACKAGE_BUGREPORT);
42207753Smm	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
43207753Smm
44207753Smm	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, true);
45207753Smm}
46207753Smm
47207753Smm
48223935Smmstatic void lzma_attribute((__noreturn__))
49207753Smmversion(void)
50207753Smm{
51213700Smm	puts("lzmainfo (" PACKAGE_NAME ") " LZMA_VERSION_STRING);
52207753Smm	tuklib_exit(EXIT_SUCCESS, EXIT_FAILURE, true);
53207753Smm}
54207753Smm
55207753Smm
56207753Smm/// Parse command line options.
57207753Smmstatic void
58207753Smmparse_args(int argc, char **argv)
59207753Smm{
60207753Smm	enum {
61207753Smm		OPT_HELP,
62207753Smm		OPT_VERSION,
63207753Smm	};
64207753Smm
65207753Smm	static const struct option long_opts[] = {
66207753Smm		{ "help",    no_argument, NULL, OPT_HELP },
67207753Smm		{ "version", no_argument, NULL, OPT_VERSION },
68207753Smm		{ NULL,      0,           NULL, 0 }
69207753Smm	};
70207753Smm
71207753Smm	int c;
72207753Smm	while ((c = getopt_long(argc, argv, "", long_opts, NULL)) != -1) {
73207753Smm		switch (c) {
74207753Smm		case OPT_HELP:
75207753Smm			help();
76207753Smm
77207753Smm		case OPT_VERSION:
78207753Smm			version();
79207753Smm
80207753Smm		default:
81207753Smm			exit(EXIT_FAILURE);
82207753Smm		}
83207753Smm	}
84207753Smm
85207753Smm	return;
86207753Smm}
87207753Smm
88207753Smm
89207753Smm/// Primitive base-2 logarithm for integers
90207753Smmstatic uint32_t
91207753Smmmy_log2(uint32_t n)
92207753Smm{
93207753Smm	uint32_t e;
94207753Smm	for (e = 0; n > 1; ++e, n /= 2) ;
95207753Smm	return e;
96207753Smm}
97207753Smm
98207753Smm
99207753Smm/// Parse the .lzma header and display information about it.
100207753Smmstatic bool
101207753Smmlzmainfo(const char *name, FILE *f)
102207753Smm{
103207753Smm	uint8_t buf[13];
104207753Smm	const size_t size = fread(buf, 1, sizeof(buf), f);
105207753Smm	if (size != 13) {
106207753Smm		fprintf(stderr, "%s: %s: %s\n", progname, name,
107207753Smm				ferror(f) ? strerror(errno)
108207753Smm				: _("File is too small to be a .lzma file"));
109207753Smm		return true;
110207753Smm	}
111207753Smm
112207753Smm	lzma_filter filter = { .id = LZMA_FILTER_LZMA1 };
113207753Smm
114207753Smm	// Parse the first five bytes.
115207753Smm	switch (lzma_properties_decode(&filter, NULL, buf, 5)) {
116207753Smm	case LZMA_OK:
117207753Smm		break;
118207753Smm
119207753Smm	case LZMA_OPTIONS_ERROR:
120207753Smm		fprintf(stderr, "%s: %s: %s\n", progname, name,
121207753Smm				_("Not a .lzma file"));
122207753Smm		return true;
123207753Smm
124207753Smm	case LZMA_MEM_ERROR:
125207753Smm		fprintf(stderr, "%s: %s\n", progname, strerror(ENOMEM));
126207753Smm		exit(EXIT_FAILURE);
127207753Smm
128207753Smm	default:
129207753Smm		fprintf(stderr, "%s: %s\n", progname,
130207753Smm				_("Internal error (bug)"));
131207753Smm		exit(EXIT_FAILURE);
132207753Smm	}
133207753Smm
134207753Smm	// Uncompressed size
135207753Smm	uint64_t uncompressed_size = 0;
136207753Smm	for (size_t i = 0; i < 8; ++i)
137207753Smm		uncompressed_size |= (uint64_t)(buf[5 + i]) << (i * 8);
138207753Smm
139207753Smm	// Display the results. We don't want to translate these and also
140207753Smm	// will use MB instead of MiB, because someone could be parsing
141207753Smm	// this output and we don't want to break that when people move
142207753Smm	// from LZMA Utils to XZ Utils.
143207753Smm	if (f != stdin)
144207753Smm		printf("%s\n", name);
145207753Smm
146207753Smm	printf("Uncompressed size:             ");
147207753Smm	if (uncompressed_size == UINT64_MAX)
148207753Smm		printf("Unknown");
149207753Smm	else
150207753Smm		printf("%" PRIu64 " MB (%" PRIu64 " bytes)",
151207753Smm				(uncompressed_size + 512 * 1024)
152207753Smm					/ (1024 * 1024),
153207753Smm				uncompressed_size);
154207753Smm
155207753Smm	lzma_options_lzma *opt = filter.options;
156207753Smm
157207753Smm	printf("\nDictionary size:               "
158213700Smm			"%" PRIu32 " MB (2^%" PRIu32 " bytes)\n"
159207753Smm			"Literal context bits (lc):     %" PRIu32 "\n"
160207753Smm			"Literal pos bits (lp):         %" PRIu32 "\n"
161207753Smm			"Number of pos bits (pb):       %" PRIu32 "\n",
162207753Smm			(opt->dict_size + 512 * 1024) / (1024 * 1024),
163207753Smm			my_log2(opt->dict_size), opt->lc, opt->lp, opt->pb);
164207753Smm
165207753Smm	free(opt);
166207753Smm
167207753Smm	return false;
168207753Smm}
169207753Smm
170207753Smm
171207753Smmextern int
172207753Smmmain(int argc, char **argv)
173207753Smm{
174207753Smm	tuklib_progname_init(argv);
175207753Smm	tuklib_gettext_init(PACKAGE, LOCALEDIR);
176207753Smm
177207753Smm	parse_args(argc, argv);
178207753Smm
179213700Smm#ifdef TUKLIB_DOSLIKE
180213700Smm	setmode(fileno(stdin), O_BINARY);
181213700Smm#endif
182213700Smm
183207753Smm	int ret = EXIT_SUCCESS;
184207753Smm
185207753Smm	// We print empty lines around the output only when reading from
186207753Smm	// files specified on the command line. This is due to how
187207753Smm	// LZMA Utils did it.
188207753Smm	if (optind == argc) {
189207753Smm		if (lzmainfo("(stdin)", stdin))
190207753Smm			ret = EXIT_FAILURE;
191207753Smm	} else {
192207753Smm		printf("\n");
193207753Smm
194207753Smm		do {
195207753Smm			if (strcmp(argv[optind], "-") == 0) {
196207753Smm				if (lzmainfo("(stdin)", stdin))
197207753Smm					ret = EXIT_FAILURE;
198207753Smm			} else {
199207753Smm				FILE *f = fopen(argv[optind], "r");
200207753Smm				if (f == NULL) {
201207753Smm					ret = EXIT_FAILURE;
202207753Smm					fprintf(stderr, "%s: %s: %s\n",
203207753Smm							progname,
204207753Smm							argv[optind],
205207753Smm							strerror(errno));
206207753Smm					continue;
207207753Smm				}
208207753Smm
209207753Smm				if (lzmainfo(argv[optind], f))
210207753Smm					ret = EXIT_FAILURE;
211207753Smm
212207753Smm				printf("\n");
213207753Smm				fclose(f);
214207753Smm			}
215207753Smm		} while (++optind < argc);
216207753Smm	}
217207753Smm
218207753Smm	tuklib_exit(ret, EXIT_FAILURE, true);
219207753Smm}
220