]> icculus.org git repositories - icculus/xz.git/blob - src/xz/list.c
Remove --force from xzdec.
[icculus/xz.git] / src / xz / list.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       list.c
4 /// \brief      Listing information about .lzma files
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 "private.h"
14
15
16 /*
17
18 1. Check the file type: native, alone, unknown
19
20 Alone:
21 1. Show info about header. Don't look for concatenated parts.
22
23 Native:
24 1. Check that Stream Header is valid.
25 2. Seek to the end of the file.
26 3. Skip padding.
27 4. Reverse decode Stream Footer.
28 5. Seek Backward Size bytes.
29 6.
30
31 */
32
33
34 static void
35 unsupported_file(file_handle *handle)
36 {
37         errmsg(V_ERROR, "%s: Unsupported file type", handle->name);
38         set_exit_status(ERROR);
39         (void)io_close(handle);
40         return;
41 }
42
43
44 /// Primitive escaping function, that escapes only ASCII control characters.
45 static void
46 print_escaped(const uint8_t *str)
47 {
48         while (*str != '\0') {
49                 if (*str <= 0x1F || *str == 0x7F)
50                         printf("\\x%02X", *str);
51                 else
52                         putchar(*str);
53
54                 ++str;
55         }
56
57         return;
58 }
59
60
61 static void
62 list_native(file_handle *handle)
63 {
64         lzma_stream strm = LZMA_STREAM_INIT;
65         lzma_stream_flags flags;
66         lzma_ret ret = lzma_stream_header_decoder(&strm, &flags);
67
68 }
69
70
71 static void
72 list_alone(const listing_handle *handle)
73 {
74         if (handle->buffer[0] > (4 * 5 + 4) * 9 + 8) {
75                 unsupported_file(handle);
76                 return;
77         }
78
79         const unsigned int pb = handle->buffer[0] / (9 * 5);
80         handle->buffer[0] -= pb * 9 * 5;
81         const unsigned int lp = handle->buffer[0] / 9;
82         const unsigned int lc = handle->buffer[0] - lp * 9;
83
84         uint32_t dict = 0;
85         for (size_t i = 1; i < 5; ++i) {
86                 dict <<= 8;
87                 dict |= header[i];
88         }
89
90         if (dict > LZMA_DICTIONARY_SIZE_MAX) {
91                 unsupported_file(handle);
92                 return;
93         }
94
95         uint64_t uncompressed_size = 0;
96         for (size_t i = 5; i < 13; ++i) {
97                 uncompressed_size <<= 8;
98                 uncompressed_size |= header[i];
99         }
100
101         // Reject files with uncompressed size of 256 GiB or more. It's
102         // an arbitrary limit trying to avoid at least some false positives.
103         if (uncompressed_size != UINT64_MAX
104                         && uncompressed_size >= (UINT64_C(1) << 38)) {
105                 unsupported_file(handle);
106                 return;
107         }
108
109         if (verbosity < V_WARNING) {
110                 printf("name=");
111                 print_escaped(handle->name);
112                 printf("\nformat=alone\n");
113
114                 if (uncompressed_size == UINT64_MAX)
115                         printf("uncompressed_size=unknown\n");
116                 else
117                         printf("uncompressed_size=%" PRIu64 "\n",
118                                         uncompressed_size);
119
120                 printf("dict=%" PRIu32 "\n", dict);
121
122                 printf("lc=%u\nlp=%u\npb=%u\n\n", lc, lp, pb);
123
124         } else {
125                 printf("File name:                   ");
126                 print_escaped(handle->name);
127                 printf("\nFile format:                 LZMA_Alone\n")
128
129                 printf("Uncompressed size:           ");
130                 if (uncompressed_size == UINT64_MAX)
131                         printf("unknown\n");
132                 else
133                         printf("%," PRIu64 " bytes (%" PRIu64 " MiB)\n",
134                                         uncompressed_size,
135                                         (uncompressed_size + 1024 * 512)
136                                                 / (1024 * 1024));
137
138                 printf("Dictionary size:             %," PRIu32 " bytes "
139                                 "(%" PRIu32 " MiB)\n",
140                                 dict, (dict + 1024 * 512) / (1024 * 1024));
141
142                 printf("Literal context bits (lc):   %u\n", lc);
143                 printf("Literal position bits (lc):  %u\n", lp);
144                 printf("Position bits (pb):          %u\n", pb);
145         }
146
147         return;
148 }
149
150
151
152
153 typedef struct {
154         const char *filename;
155         struct stat st;
156         int fd;
157
158         lzma_stream strm;
159         lzma_stream_flags stream_flags;
160         lzma_info *info;
161
162         lzma_vli backward_size;
163         lzma_vli uncompressed_size;
164
165         size_t buffer_size;
166         uint8_t buffer[IO_BUFFER_SIZE];
167 } listing_handle;
168
169
170 static bool
171 listing_pread(listing_handle *handle, uint64_t offset)
172 {
173         if (offset >= (uint64_t)(handle->st.st_size)) {
174                 errmsg(V_ERROR, "%s: Trying to read past the end of "
175                                 "the file.", handle->filename);
176                 return true;
177         }
178
179 #ifdef HAVE_PREAD
180         const ssize_t ret = pread(handle->fd, handle->buffer, IO_BUFFER_SIZE,
181                         (off_t)(offset));
182 #else
183         // Use lseek() + read() since we don't have pread(). We don't care
184         // to which offset the reading position is left.
185         if (lseek(handle->fd, (off_t)(offset), SEEK_SET) == -1) {
186                 errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno));
187                 return true;
188         }
189
190         const ssize_t ret = read(handle->fd, handle->buffer, IO_BUFFER_SIZE);
191 #endif
192
193         if (ret == -1) {
194                 errmsg(V_ERROR, "%s: %s", handle->filename, strerror(errno));
195                 return true;
196         }
197
198         if (ret == 0) {
199                 errmsg(V_ERROR, "%s: Trying to read past the end of "
200                                 "the file.", handle->filename);
201                 return true;
202         }
203
204         handle->buffer_size = (size_t)(ret);
205         return false;
206 }
207
208
209
210 static bool
211 parse_stream_header(listing_handle *handle)
212 {
213         if (listing_pread(handle, 0))
214                 return true;
215
216         // TODO Got enough input?
217
218         lzma_ret ret = lzma_stream_header_decoder(
219                         &handle->strm, &handle->stream_flags);
220         if (ret != LZMA_OK) {
221                 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
222                 return true;
223         }
224
225         handle->strm.next_in = handle->buffer;
226         handle->strm.avail_in = handle->buffer_size;
227         ret = lzma_code(&handle->strm, LZMA_RUN);
228         if (ret != LZMA_STREAM_END) {
229                 assert(ret != LZMA_OK);
230                 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
231                 return true;
232         }
233
234         return false;
235 }
236
237
238 static bool
239 parse_stream_tail(listing_handle *handle)
240 {
241         uint64_t offset = (uint64_t)(handle->st.st_size);
242
243         // Skip padding
244         do {
245                 if (offset == 0) {
246                         errmsg(V_ERROR, "%s: %s", handle->name,
247                                         str_strm_error(LZMA_DATA_ERROR));
248                         return true;
249                 }
250
251                 if (offset < IO_BUFFER_SIZE)
252                         offset = 0;
253                 else
254                         offset -= IO_BUFFER_SIZE;
255
256                 if (listing_pread(handle, offset))
257                         return true;
258
259                 while (handle->buffer_size > 0
260                                 && handle->buffer[handle->buffer_size - 1]
261                                         == '\0')
262                         --handle->buffer_size;
263
264         } while (handle->buffer_size == 0);
265
266         if (handle->buffer_size < LZMA_STREAM_TAIL_SIZE) {
267                 // TODO
268         }
269
270         lzma_stream_flags stream_flags;
271         lzma_ret ret = lzma_stream_tail_decoder(&handle->strm, &stream_flags);
272         if (ret != LZMA_OK) {
273                 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
274                 return true;
275         }
276
277         handle->strm.next_in = handle->buffer + handle->buffer_size
278                         - LZMA_STREAM_TAIL_SIZE;
279         handle->strm.avail_in = LZMA_STREAM_TAIL_SIZE;
280         handle->buffer_size -= LZMA_STREAM_TAIL_SIZE;
281         ret = lzma_code(&handle->strm, LZMA_RUN);
282         if (ret != LZMA_OK) {
283                 assert(ret != LZMA_OK);
284                 errmsg(V_ERROR, "%s: %s", handle->name, str_strm_error(ret));
285                 return true;
286         }
287
288         if (!lzma_stream_flags_is_equal(handle->stream_flags, stream_flags)) {
289                 // TODO
290                 // Possibly corrupt, possibly concatenated file.
291         }
292
293         handle->backward_size = 0;
294         ret = lzma_vli_reverse_decode(&handle->backward_size, handle->buffer,
295                         &handle->buffer_size);
296         if (ret != LZMA_OK) {
297                 // It may be LZMA_BUF_ERROR too, but it doesn't make sense
298                 // as an error message displayed to the user.
299                 errmsg(V_ERROR, "%s: %s", handle->name,
300                                 str_strm_error(LZMA_DATA_ERROR));
301                 return true;
302         }
303
304         if (!stream_flags.is_multi) {
305                 handle->uncompressed_size = 0;
306                 size_t tmp = handle->buffer_size;
307                 ret = lzma_vli_reverse_decode(&handle->uncompressed_size,
308                                 handle->buffer, &tmp);
309                 if (ret != LZMA_OK)
310                         handle->uncompressed_size = LZMA_VLI_UNKNOWN;
311         }
312
313         // Calculate the Header Metadata Block start offset.
314
315
316         return false;
317 }
318
319
320
321 static void
322 list_native(listing_handle *handle)
323 {
324         lzma_memory_limiter *limiter
325                         = lzma_memory_limiter_create(opt_memory);
326         if (limiter == NULL) {
327                 errmsg(V_ERROR,
328         }
329         lzma_info *info =
330
331
332         // Parse Stream Header
333         //
334         // Single-Block Stream:
335         //  - Parse Block Header
336         //  - Parse Stream Footer
337         //  - If Backward Size doesn't match, error out
338         //
339         // Multi-Block Stream:
340         //  - Parse Header Metadata Block, if any
341         //  - Parse Footer Metadata Block
342         //  - Parse Stream Footer
343         //  - If Footer Metadata Block doesn't match the Stream, error out
344         //
345         // In other words, we don't support concatened files.
346         if (parse_stream_header(handle))
347                 return;
348
349         if (parse_block_header(handle))
350                 return;
351
352         if (handle->stream_flags.is_multi) {
353                 if (handle->block_options.is_metadata) {
354                         if (parse_metadata(handle)
355                                 return;
356                 }
357
358                 if (my_seek(handle,
359
360         } else {
361                 if (handle->block_options.is_metadata) {
362                         FILE_IS_CORRUPT();
363                         return;
364                 }
365
366                 if (parse_stream_footer(handle))
367                         return;
368
369                 // If Uncompressed Size isn't present in Block Header,
370                 // it must be present in Stream Footer.
371                 if (handle->block_options.uncompressed_size
372                                         == LZMA_VLI_UNKNOWN
373                                 && handle->stream_flags.uncompressed_size
374                                         == LZMA_VLI_UNKNOWN) {
375                         FILE_IS_CORRUPT();
376                         return;
377                 }
378
379                 // Construct a single-Record Index.
380                 lzma_index *index = malloc(sizeof(lzma_index));
381                 if (index == NULL) {
382                         out_of_memory();
383                         return;
384                 }
385
386                 // Pohdintaa:
387                 // Jos Block coder hoitaisi Uncompressed ja Backward Sizet,
388                 // voisi index->total_sizeksi laittaa suoraan Backward Sizen.
389                 index->total_size =
390
391                 if () {
392
393                 }
394         }
395
396
397         if (handle->block_options.is_metadata) {
398                 if (!handle->stream_flags.is_multi) {
399                         FILE_IS_CORRUPT();
400                         return;
401                 }
402
403                 if (parse_metadata(handle))
404                         return;
405
406         }
407 }
408
409
410
411 extern void
412 list(const char *filename)
413 {
414         if (strcmp(filename, "-") == 0) {
415                 errmsg(V_ERROR, "%s: --list does not support reading from "
416                                 "standard input", filename);
417                 return;
418         }
419
420         if (is_empty_filename(filename))
421                 return;
422
423         listing_handle handle;
424         handle.filename = filename;
425
426         handle.fd = open(filename, O_RDONLY | O_NOCTTY);
427         if (handle.fd == -1) {
428                 errmsg(V_ERROR, "%s: %s", filename, strerror(errno));
429                 return;
430         }
431
432         if (fstat(handle.fd, &handle.st)) {
433                 errmsg(V_ERROR, "%s: %s", filename, strerror(errno));
434                 goto out;
435         }
436
437         if (!S_ISREG(handle.st.st_mode)) {
438                 errmsg(V_WARNING, _("%s: Not a regular file, skipping"),
439                                 filename);
440                 goto out;
441         }
442
443         if (handle.st.st_size <= 0) {
444                 errmsg(V_ERROR, _("%s: File is empty"), filename);
445                 goto out;
446         }
447
448         if (listing_pread(&handle, 0))
449                 goto out;
450
451         if (handle.buffer[0] == 0xFF) {
452                 if (opt_header == HEADER_ALONE) {
453                         errmsg(V_ERROR, "%s: FIXME", filename); // FIXME
454                         goto out;
455                 }
456
457                 list_native(&handle);
458         } else {
459                 if (opt_header != HEADER_AUTO && opt_header != HEADER_ALONE) {
460                         errmsg(V_ERROR, "%s: FIXME", filename); // FIXME
461                         goto out;
462                 }
463
464                 list_alone(&handle);
465         }
466
467 out:
468         (void)close(fd);
469         return;
470 }