aboutsummaryrefslogtreecommitdiffstats
path: root/doc/examples/11_file_info.c
blob: 9e7b0c8e62bf3e3942d300076c5fa9033c2fb165 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
///////////////////////////////////////////////////////////////////////////////
//
/// \file       11_file_info.c
/// \brief      Get uncompressed size of .xz file(s)
///
/// Usage:      ./11_file_info INFILE1.xz [INFILEn.xz]...
///
/// Example:    ./11_file_info foo.xz
//
//  Author:     Lasse Collin
//
//  This file has been put into the public domain.
//  You can do whatever you want with this file.
//
///////////////////////////////////////////////////////////////////////////////

#include <stdbool.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <lzma.h>


static bool
print_file_size(lzma_stream *strm, FILE *infile, const char *filename)
{
	// Get the file size. In standard C it can be done by seeking to
	// the end of the file and then getting the file position.
	// In POSIX one can use fstat() and then st_size from struct stat.
	// Also note that fseek() and ftell() use long and thus don't support
	// large files on 32-bit systems (POSIX versions fseeko() and
	// ftello() can support large files).
	if (fseek(infile, 0, SEEK_END)) {
		fprintf(stderr, "Error seeking the file `%s': %s\n",
				filename, strerror(errno));
		return false;
	}

	const long file_size = ftell(infile);

	// The decoder wants to start from the beginning of the .xz file.
	rewind(infile);

	// Initialize the decoder.
	lzma_index *i;
	lzma_ret ret = lzma_file_info_decoder(strm, &i, UINT64_MAX,
			(uint64_t)file_size);
	switch (ret) {
	case LZMA_OK:
		// Initialization succeeded.
		break;

	case LZMA_MEM_ERROR:
		fprintf(stderr, "Out of memory when initializing "
				"the .xz file info decoder\n");
		return false;

	case LZMA_PROG_ERROR:
	default:
		fprintf(stderr, "Unknown error, possibly a bug\n");
		return false;
	}

	// This example program reuses the same lzma_stream structure
	// for multiple files, so we need to reset this when starting
	// a new file.
	strm->avail_in = 0;

	// Buffer for input data.
	uint8_t inbuf[BUFSIZ];

	// Pass data to the decoder and seek when needed.
	while (true) {
		if (strm->avail_in == 0) {
			strm->next_in = inbuf;
			strm->avail_in = fread(inbuf, 1, sizeof(inbuf),
					infile);

			if (ferror(infile)) {
				fprintf(stderr,
					"Error reading from `%s': %s\n",
					filename, strerror(errno));
				return false;
			}

			// We don't need to care about hitting the end of
			// the file so no need to check for feof().
		}

		ret = lzma_code(strm, LZMA_RUN);

		switch (ret) {
		case LZMA_OK:
			break;

		case LZMA_SEEK_NEEDED:
			// The cast is safe because liblzma won't ask us to
			// seek past the known size of the input file which
			// did fit into a long.
			//
			// NOTE: Remember to change these to off_t if you
			// switch fseeko() or lseek().
			if (fseek(infile, (long)(strm->seek_pos), SEEK_SET)) {
				fprintf(stderr, "Error seeking the "
						"file `%s': %s\n",
						filename, strerror(errno));
				return false;
			}

			// The old data in the inbuf is useless now. Set
			// avail_in to zero so that we will read new input
			// from the new file position on the next iteration
			// of this loop.
			strm->avail_in = 0;
			break;

		case LZMA_STREAM_END:
			// File information was successfully decoded.
			// See <lzma/index.h> for functions that can be
			// used on it. In this example we just print
			// the uncompressed size (in bytes) of
			// the .xz file followed by its file name.
			printf("%10" PRIu64 " %s\n",
					lzma_index_uncompressed_size(i),
					filename);

			// Free the memory of the lzma_index structure.
			lzma_index_end(i, NULL);

			return true;

		case LZMA_FORMAT_ERROR:
			// .xz magic bytes weren't found.
			fprintf(stderr, "The file `%s' is not "
					"in the .xz format\n", filename);
			return false;

		case LZMA_OPTIONS_ERROR:
			fprintf(stderr, "The file `%s' has .xz headers that "
					"are not supported by this liblzma "
					"version\n", filename);
			return false;

		case LZMA_DATA_ERROR:
			fprintf(stderr, "The file `%s' is corrupt\n",
					filename);
			return false;

		case LZMA_MEM_ERROR:
			fprintf(stderr, "Memory allocation failed when "
					"decoding the file `%s'\n", filename);
			return false;

		// LZMA_MEMLIMIT_ERROR shouldn't happen because we used
		// UINT64_MAX as the limit.
		//
		// LZMA_BUF_ERROR shouldn't happen because we always provide
		// new input when the input buffer is empty. The decoder
		// knows the input file size and thus won't try to read past
		// the end of the file.
		case LZMA_MEMLIMIT_ERROR:
		case LZMA_BUF_ERROR:
		case LZMA_PROG_ERROR:
		default:
			fprintf(stderr, "Unknown error, possibly a bug\n");
			return false;
		}
	}

	// This line is never reached.
}


extern int
main(int argc, char **argv)
{
	bool success = true;
	lzma_stream strm = LZMA_STREAM_INIT;

	for (int i = 1; i < argc; ++i) {
		FILE *infile = fopen(argv[i], "rb");

		if (infile == NULL) {
			fprintf(stderr, "Cannot open the file `%s': %s\n",
					argv[i], strerror(errno));
			success = false;
		}

		success &= print_file_size(&strm, infile, argv[i]);

		(void)fclose(infile);
	}

	lzma_end(&strm);

	// Close stdout to catch possible write errors that can occur
	// when pending data is flushed from the stdio buffers.
	if (fclose(stdout)) {
		fprintf(stderr, "Write error: %s\n", strerror(errno));
		success = false;
	}

	return success ? EXIT_SUCCESS : EXIT_FAILURE;
}