0ac0eb9337ddadd5494bb63e2aa1f7ccf0c3fdca
[pxzcat.git] / pxzcat.c
1 /* pxzcat derived from nbdkit
2  * Copyright (C) 2013 Red Hat Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of Red Hat nor the names of its contributors may be
17  * used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
30  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include <config.h>
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <stdint.h>
40 #include <inttypes.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <sys/types.h>
44 #include <error.h>
45 #include <errno.h>
46 #include <getopt.h>
47
48 #include <lzma.h>
49
50 #define DEBUG 1
51
52 #if DEBUG
53 #define debug(fs,...) fprintf (stderr, "pxzcat: debug: " fs, ## __VA_ARGS__)
54 #else
55 #define debug(fs,...) /* nothing */
56 #endif
57
58 #define XZ_HEADER_MAGIC     "\xfd" "7zXZ\0"
59 #define XZ_HEADER_MAGIC_LEN 6
60 #define XZ_FOOTER_MAGIC     "YZ"
61 #define XZ_FOOTER_MAGIC_LEN 2
62
63 static void usage (int exitcode);
64 static void xzfile_uncompress (const char *filename, const char *outputfile);
65 static int check_header_magic (int fd);
66 static lzma_index *parse_indexes (const char *filename, int fd);
67 static void iter_indexes (lzma_index *idx);
68
69 static struct option long_options[] = {
70   { "output",   required_argument,  0, 'o' },
71   { "help",     0,                  0, '?' },
72   { NULL,       0,                  0, 0   }
73 };
74
75 static const char *options = "o:";
76
77 int
78 main (int argc, char *argv[])
79 {
80   int c;
81   int longopt_index;
82   const char *outputfile = NULL;
83
84   for (;;) {
85     c = getopt_long (argc, argv, options, long_options, &longopt_index);
86     if (c == -1)
87       break;
88
89     switch (c) {
90       /* Long option with no short opt equivalent. */
91     case 0:
92       abort ();
93
94     case 'o':
95       outputfile = optarg;
96       break;
97
98     case '?':
99       usage (EXIT_SUCCESS);
100
101     default:
102       usage (EXIT_FAILURE);
103     }
104   }
105
106   if (outputfile == NULL)
107     error (EXIT_FAILURE, 0, "you must give the -o (output file) option\n");
108
109   if (optind != argc - 1)
110     usage (EXIT_FAILURE);
111
112   xzfile_uncompress (argv[optind], outputfile);
113
114   exit (EXIT_SUCCESS);
115 }
116
117 static void
118 usage (int exitcode)
119 {
120   printf ("usage: pxzcat -o output input.xz\n");
121   exit (exitcode);
122 }
123
124 static void
125 xzfile_uncompress (const char *filename, const char *outputfile)
126 {
127   int fd;
128   uint64_t size;
129   lzma_index *idx;
130
131   /* Open the file. */
132   fd = open (filename, O_RDONLY);
133   if (fd == -1)
134     error (EXIT_FAILURE, errno, "open: %s", filename);
135
136   /* Check file magic. */
137   if (!check_header_magic (fd))
138     error (EXIT_FAILURE, 0, "%s: not an xz file", filename);
139
140   /* Read and parse the indexes. */
141   idx = parse_indexes (filename, fd);
142
143   /* Iterate over indexes and uncompress. */
144   iter_indexes (idx);
145
146   close (fd);
147 }
148
149 static int
150 check_header_magic (int fd)
151 {
152   char buf[XZ_HEADER_MAGIC_LEN];
153
154   if (lseek (fd, 0, SEEK_SET) == -1)
155     return 0;
156   if (read (fd, buf, XZ_HEADER_MAGIC_LEN) != XZ_HEADER_MAGIC_LEN)
157     return 0;
158   if (memcmp (buf, XZ_HEADER_MAGIC, XZ_HEADER_MAGIC_LEN) != 0)
159     return 0;
160   return 1;
161 }
162
163 /* For explanation of this function, see src/xz/list.c:parse_indexes
164  * in the xz sources.
165  */
166 static lzma_index *
167 parse_indexes (const char *filename, int fd)
168 {
169   lzma_ret r;
170   off_t pos, index_size;
171   uint8_t footer[LZMA_STREAM_HEADER_SIZE];
172   uint8_t header[LZMA_STREAM_HEADER_SIZE];
173   lzma_stream_flags footer_flags;
174   lzma_stream_flags header_flags;
175   lzma_stream strm = LZMA_STREAM_INIT;
176   ssize_t n;
177   lzma_index *combined_index = NULL;
178   lzma_index *this_index = NULL;
179   lzma_vli stream_padding = 0;
180   size_t nr_streams = 0;
181
182   /* Check file size is a multiple of 4 bytes. */
183   pos = lseek (fd, 0, SEEK_END);
184   if (pos == (off_t) -1)
185     error (EXIT_FAILURE, errno, "%s: lseek", filename);
186
187   if ((pos & 3) != 0)
188     error (EXIT_FAILURE, 0,
189            "%s: not an xz file: size is not a multiple of 4 bytes",
190            filename);
191
192   /* Jump backwards through the file identifying each stream. */
193   while (pos > 0) {
194     debug ("looping through streams: pos = %" PRIu64, (uint64_t) pos);
195
196     if (pos < LZMA_STREAM_HEADER_SIZE)
197       error (EXIT_FAILURE, 0,
198              "%s: corrupted file at %" PRIu64, filename, (uint64_t) pos);
199
200     if (lseek (fd, -LZMA_STREAM_HEADER_SIZE, SEEK_CUR) == -1)
201       error (EXIT_FAILURE, errno, "%s: lseek", filename);
202
203     if (read (fd, footer, LZMA_STREAM_HEADER_SIZE) != LZMA_STREAM_HEADER_SIZE)
204       error (EXIT_FAILURE, errno, "%s: read stream footer", filename);
205
206     /* Skip stream padding. */
207     if (footer[8] == 0 && footer[9] == 0 &&
208         footer[10] == 0 && footer[11] == 0) {
209       stream_padding += 4;
210       pos -= 4;
211       continue;
212     }
213
214     pos -= LZMA_STREAM_HEADER_SIZE;
215     nr_streams++;
216
217     debug ("decode stream footer at pos = %" PRIu64, (uint64_t) pos);
218
219     /* Does the stream footer look reasonable? */
220     r = lzma_stream_footer_decode (&footer_flags, footer);
221     if (r != LZMA_OK)
222       error (EXIT_FAILURE, 0,
223              "%s: invalid stream footer (error %d)", filename, r);
224
225     debug ("backward_size = %" PRIu64, (uint64_t) footer_flags.backward_size);
226     index_size = footer_flags.backward_size;
227     if (pos < index_size + LZMA_STREAM_HEADER_SIZE)
228       error (EXIT_FAILURE, 0, "%s: invalid stream footer", filename);
229
230     pos -= index_size;
231     debug ("decode index at pos = %" PRIu64, (uint64_t) pos);
232
233     /* Seek backwards to the index of this stream. */
234     if (lseek (fd, pos, SEEK_SET) == -1)
235       error (EXIT_FAILURE, errno, "%s: lseek", filename);
236
237     /* Decode the index. */
238     r = lzma_index_decoder (&strm, &this_index, UINT64_MAX);
239     if (r != LZMA_OK)
240       error (EXIT_FAILURE, 0,
241              "%s: invalid stream index (error %d)", filename, r);
242
243     do {
244       uint8_t buf[BUFSIZ];
245
246       strm.avail_in = index_size;
247       if (strm.avail_in > BUFSIZ)
248         strm.avail_in = BUFSIZ;
249
250       n = read (fd, &buf, strm.avail_in);
251       if (n == -1)
252         error (EXIT_FAILURE, errno, "%s: read", filename);
253
254       index_size -= strm.avail_in;
255
256       strm.next_in = buf;
257       r = lzma_code (&strm, LZMA_RUN);
258     } while (r == LZMA_OK);
259
260     if (r != LZMA_STREAM_END)
261       error (EXIT_FAILURE, 0, "%s: could not parse index (error %d)",
262              filename, r);
263
264     pos -= lzma_index_total_size (this_index) + LZMA_STREAM_HEADER_SIZE;
265
266     debug ("decode stream header at pos = %" PRIu64, (uint64_t) pos);
267
268     /* Read and decode the stream header. */
269     if (lseek (fd, pos, SEEK_SET) == -1)
270       error (EXIT_FAILURE, errno, "%s: lseek", filename);
271
272     if (read (fd, header, LZMA_STREAM_HEADER_SIZE) != LZMA_STREAM_HEADER_SIZE)
273       error (EXIT_FAILURE, errno, "%s: read stream header", filename);
274
275     r = lzma_stream_header_decode (&header_flags, header);
276     if (r != LZMA_OK)
277       error (EXIT_FAILURE, 0,
278              "%s: invalid stream header (error %d)", filename, r);
279
280     /* Header and footer of the stream should be equal. */
281     r = lzma_stream_flags_compare (&header_flags, &footer_flags);
282     if (r != LZMA_OK)
283       error (EXIT_FAILURE, 0,
284              "%s: header and footer of stream are not equal (error %d)",
285              filename, r);
286
287     /* Store the decoded stream flags in this_index. */
288     r = lzma_index_stream_flags (this_index, &footer_flags);
289     if (r != LZMA_OK)
290       error (EXIT_FAILURE, 0,
291              "%s: cannot read stream_flags from index (error %d)",
292              filename, r);
293
294     /* Store the amount of stream padding so far.  Needed to calculate
295      * compressed offsets correctly in multi-stream files.
296      */
297     r = lzma_index_stream_padding (this_index, stream_padding);
298     if (r != LZMA_OK)
299       error (EXIT_FAILURE, 0,
300              "%s: cannot set stream_padding in index (error %d)",
301              filename, r);
302
303     if (combined_index != NULL) {
304       r = lzma_index_cat (this_index, combined_index, NULL);
305       if (r != LZMA_OK)
306         error (EXIT_FAILURE, 0, "%s: cannot combine indexes", filename);
307     }
308
309     combined_index = this_index;
310     this_index = NULL;
311   }
312
313   lzma_end (&strm);
314
315   return combined_index;
316 }
317
318 /* Iterate over the indexes and uncompress.
319  */
320 static void
321 iter_indexes (lzma_index *idx)
322 {
323   lzma_index_iter iter;
324
325   lzma_index_iter_init (&iter, idx);
326   while (!lzma_index_iter_next (&iter, LZMA_INDEX_ITER_NONEMPTY_BLOCK)) {
327     abort ();
328
329
330
331
332   }
333 }
334
335 #if 0
336 char *
337 xzfile_read_block (xzfile *xz, uint64_t offset,
338                    uint64_t *start_rtn, uint64_t *size_rtn)
339 {
340   lzma_index_iter iter;
341   uint8_t header[LZMA_BLOCK_HEADER_SIZE_MAX];
342   lzma_block block;
343   lzma_filter filters[LZMA_FILTERS_MAX + 1];
344   lzma_ret r;
345   lzma_stream strm = LZMA_STREAM_INIT;
346   char *data;
347   ssize_t n;
348   size_t i;
349
350   /* Locate the block containing the uncompressed offset. */
351   lzma_index_iter_init (&iter, xz->idx);
352   if (lzma_index_iter_locate (&iter, offset)) {
353     nbdkit_error ("cannot find offset %" PRIu64 " in the xz file", offset);
354     return NULL;
355   }
356
357   *start_rtn = iter.block.uncompressed_file_offset;
358   *size_rtn = iter.block.uncompressed_size;
359
360   nbdkit_debug ("seek: block number %d at file offset %" PRIu64,
361                 (int) iter.block.number_in_file,
362                 (uint64_t) iter.block.compressed_file_offset);
363
364   if (lseek (xz->fd, iter.block.compressed_file_offset, SEEK_SET) == -1) {
365     nbdkit_error ("lseek: %m");
366     return NULL;
367   }
368
369   /* Read the block header.  Start by reading a single byte which
370    * tell us how big the block header is.
371    */
372   n = read (xz->fd, header, 1);
373   if (n == 0) {
374     nbdkit_error ("read: unexpected end of file reading block header byte");
375     return NULL;
376   }
377   if (n == -1) {
378     nbdkit_error ("read: %m");
379     return NULL;
380   }
381
382   if (header[0] == '\0') {
383     nbdkit_error ("read: unexpected invalid block in file, header[0] = 0");
384     return NULL;
385   }
386
387   block.version = 0;
388   block.check = iter.stream.flags->check;
389   block.filters = filters;
390   block.header_size = lzma_block_header_size_decode (header[0]);
391
392   /* Now read and decode the block header. */
393   n = read (xz->fd, &header[1], block.header_size-1);
394   if (n >= 0 && n != block.header_size-1) {
395     nbdkit_error ("read: unexpected end of file reading block header");
396     return NULL;
397   }
398   if (n == -1) {
399     nbdkit_error ("read: %m");
400     return NULL;
401   }
402
403   r = lzma_block_header_decode (&block, NULL, header);
404   if (r != LZMA_OK) {
405     nbdkit_error ("invalid block header (error %d)", r);
406     return NULL;
407   }
408
409   /* What this actually does is it checks that the block header
410    * matches the index.
411    */
412   r = lzma_block_compressed_size (&block, iter.block.unpadded_size);
413   if (r != LZMA_OK) {
414     nbdkit_error ("cannot calculate compressed size (error %d)", r);
415     goto err1;
416   }
417
418   /* Read the block data. */
419   r = lzma_block_decoder (&strm, &block);
420   if (r != LZMA_OK) {
421     nbdkit_error ("invalid block (error %d)", r);
422     goto err1;
423   }
424
425   data = malloc (*size_rtn);
426   if (data == NULL) {
427     nbdkit_error ("malloc (%zu bytes): %m\n"
428                   "NOTE: If this error occurs, you need to recompress your xz files with a smaller block size.  Use: 'xz --block-size=16777216 ...'.",
429                   *size_rtn);
430     goto err1;
431   }
432
433   strm.next_in = NULL;
434   strm.avail_in = 0;
435   strm.next_out = (uint8_t *) data;
436   strm.avail_out = block.uncompressed_size;
437
438   do {
439     uint8_t buf[BUFSIZ];
440     lzma_action action = LZMA_RUN;
441
442     if (strm.avail_in == 0) {
443       strm.next_in = buf;
444       n = read (xz->fd, buf, sizeof buf);
445       if (n == -1) {
446         nbdkit_error ("read: %m");
447         goto err2;
448       }
449       strm.avail_in = n;
450       if (n == 0)
451         action = LZMA_FINISH;
452     }
453
454     strm.avail_in = n;
455     strm.next_in = buf;
456     r = lzma_code (&strm, action);
457   } while (r == LZMA_OK);
458
459   if (r != LZMA_OK && r != LZMA_STREAM_END) {
460     nbdkit_error ("could not parse block data (error %d)", r);
461     goto err2;
462   }
463
464   lzma_end (&strm);
465
466   for (i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i)
467     free (filters[i].options);
468
469   return data;
470
471  err2:
472   free (data);
473   lzma_end (&strm);
474  err1:
475   for (i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i)
476     free (filters[i].options);
477
478   return NULL;
479 }
480 #endif