Rewrite bzip2 support, as data blocks rather than "missing" blocks.
[jigit.git] / mkimage.c
1 /*
2  * mkimage
3  *
4  * Tool to create an ISO image from jigdo files
5  *
6  * Copyright (c) 2004 Steve McIntyre <steve@einval.com>
7  *
8  * GPL v2 - see COPYING
9  */
10
11 #define BZ2_SUPPORT
12
13 #include <errno.h>
14 #include <math.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <limits.h>
19 #include <zlib.h>
20 #ifdef BZ2_SUPPORT
21 #   include <bzlib.h>
22 #endif
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <sys/mman.h>
28 #include "endian.h"
29 #include "md5.h"
30 #include "jigdb.h"
31
32 typedef long long INT64;
33 typedef unsigned long long UINT64;
34 typedef unsigned long      UINT32;
35
36 #ifndef LLONG_MAX
37 #   define LLONG_MAX (INT64)INT_MAX * INT_MAX
38 #endif
39
40 #define BUF_SIZE 65536
41 #define MISSING -1
42
43 #ifndef MIN
44 #define MIN(x,y)        ( ((x) < (y)) ? (x) : (y))
45 #endif
46
47 #define COMP_GZIP 2
48 #define COMP_BZIP 8
49
50 FILE *logfile = NULL;
51 FILE *outfile = NULL;
52 FILE *missing_file = NULL;
53 long long start_offset = 0;
54 long long end_offset = 0;
55 int quick = 0;
56 int verbose = 0;
57 UINT64 out_size = 0;
58 char *missing_filename = NULL;
59
60 typedef enum state_
61 {
62     STARTING,
63     IN_DATA,
64     IN_DESC,
65     DUMP_DESC,
66     DONE,
67     ERROR
68 } e_state;
69
70 typedef struct match_list_
71 {
72     struct match_list_ *next;
73     char *match;
74     char *mirror_path;
75 } match_list_t;
76
77 match_list_t *match_list_head = NULL;
78 match_list_t *match_list_tail = NULL;
79
80 typedef struct md5_list_
81 {
82     struct md5_list_ *next;
83     INT64 file_size;
84     char *md5;
85     char *full_path;
86 } md5_list_t;
87
88 md5_list_t *md5_list_head = NULL;
89 md5_list_t *md5_list_tail = NULL;
90
91 typedef enum
92 {
93     CT_GZIP,
94     CT_BZIP2
95 } ctype_e;
96
97 struct
98 {
99     char   *data_buf;
100     ctype_e algorithm;
101     INT64   buf_size;
102     INT64   offset_in_curr_buf;
103     INT64   total_offset;
104 } zip_state;
105
106 /* Grab the file component from a full path */
107 static char *file_base_name(char *path)
108 {
109     char *endptr = path;
110     char *ptr = path;
111     
112     while (*ptr != '\0')
113     {
114         if ('/' == *ptr)
115             endptr = ++ptr;
116         else
117             ++ptr;
118     }
119     return endptr;
120 }
121
122 static void write_missing_entry(char *missing, char *filename)
123 {
124     if (!missing_file)
125     {
126         missing_file = fopen(missing, "wb");
127         if (!missing_file)
128         {
129             fprintf(logfile, "write_missing_entry: Unable to open missing log %s; error %d\n", missing, errno);
130             exit(1);
131         }
132     }
133     fprintf(missing_file, "%s\n", filename);
134 }
135
136 static INT64 get_file_size(char *filename)
137 {
138     struct stat sb;
139     int error = 0;
140     
141     error = stat(filename, &sb);
142     if (error)
143         return MISSING;
144     else
145         return sb.st_size;
146 }
147
148 static void display_progress(FILE *file, char *text)
149 {
150     INT64 written = ftello(file);
151     if (out_size > 0)
152         fprintf(logfile, "\r %5.2f%%  %-60.60s",
153                100.0 * written / out_size, text);
154 }
155
156 static int add_match_entry(char *match)
157 {
158     match_list_t *entry = NULL;
159     char *mirror_path = NULL;
160     char *ptr = match;
161
162     /* Split "Foo=/mirror/foo" into its components */
163     while (*ptr)
164     {
165         if ('=' == *ptr)
166         {
167             *ptr = 0;
168             ptr++;
169             mirror_path = ptr;
170             break;
171         }
172         ptr++;
173     }
174
175     if (!mirror_path)
176     {
177         fprintf(logfile, "Could not parse malformed match entry \"%s\"\n", match);
178         return EINVAL;
179     }        
180     
181     entry = calloc(1, sizeof(*entry));
182     if (!entry)
183         return ENOMEM;
184
185     fprintf(logfile, "Adding match entry %s:%s\n", match, mirror_path);
186
187     entry->match = match;
188     entry->mirror_path = mirror_path;
189     
190     if (!match_list_head)
191     {
192         match_list_head = entry;
193         match_list_tail = entry;
194     }
195     else
196     {
197         match_list_tail->next = entry;
198         match_list_tail = entry;
199     }
200     
201     return 0;
202 }
203
204 static int file_exists(char *path, INT64 *size)
205 {
206     struct stat sb;
207     int error = 0;
208     
209     error = stat(path, &sb);
210     if (!error && S_ISREG(sb.st_mode))
211     {
212         *size = sb.st_size;
213         return 1;
214     }
215     
216     /* else */
217     return 0;
218 }
219
220 static md5_list_t *find_file_in_md5_list(unsigned char *base64_md5)
221 {
222     md5_list_t *md5_list_entry = md5_list_head;
223     
224     while (md5_list_entry)
225     {        
226         if (!memcmp(md5_list_entry->md5, base64_md5, 16))
227             return md5_list_entry;
228         /* else */
229         md5_list_entry = md5_list_entry->next;
230     }
231     return NULL; /* Not found */
232 }
233
234 static int find_file_in_mirror(char *jigdo_match, char *jigdo_name,
235                                char *match, INT64 *file_size, char **mirror_path)
236 {
237     match_list_t *entry = match_list_head;
238     char path[PATH_MAX];
239
240     while (entry)
241     {
242         if (!strcmp(entry->match, match))
243         {
244             sprintf(path, "%s/%s", entry->mirror_path, jigdo_name);
245             if (file_exists(path, file_size))
246             {
247                 *mirror_path = strdup(path);
248                 return 0;
249             }
250         }
251         entry = entry->next;
252     }
253     
254     *mirror_path = jigdo_name;
255     return ENOENT;
256 }
257
258
259 static int add_md5_entry(INT64 size, char *md5, char *path)
260 {
261     md5_list_t *new = NULL;    
262     new = calloc(1, sizeof(*new));
263     if (!new)
264         return ENOMEM;
265
266     new->md5 = md5;
267     new->full_path = path;
268     new->file_size = size;
269     
270     if (!md5_list_head)
271     {
272         md5_list_head = new;
273         md5_list_tail = new;
274     }
275     else
276     {
277         md5_list_tail->next = new;
278         md5_list_tail = new;
279     }
280     
281     return 0;
282 }
283
284 static int parse_md5_entry(char *md5_entry)
285 {
286     int error = 0;
287     char *file_name = NULL;
288     char *md5 = NULL;
289     INT64 file_size = 0;
290
291     md5_entry[22] = 0;
292     md5_entry[23] = 0;
293
294     md5 = md5_entry;
295     file_name = &md5_entry[24];
296
297     if ('\n' == file_name[strlen(file_name) -1])
298         file_name[strlen(file_name) - 1] = 0;
299     
300     file_size = get_file_size(file_name);
301
302     error = add_md5_entry(file_size, md5, file_name);
303     return 0;
304 }
305
306 static int parse_md5_file(char *filename)
307 {
308     char buf[2048];
309     FILE *file = NULL;
310     char *ret = NULL;
311     int error = 0;
312
313     file = fopen(filename, "rb");
314     if (!file)
315     {
316         fprintf(logfile, "Failed to open MD5 file %s, error %d!\n", filename, errno);
317         return errno;
318     }
319     
320     while(1)
321     {
322         ret = fgets(buf, sizeof(buf), file);
323         if (NULL == ret)
324             break;
325         error = parse_md5_entry(strdup(buf));
326     }
327     return 0;
328 }
329
330 /* DELIBERATELY do not sort these, or do anything clever with
331    insertion. The entries in the jigdo file should be in the same
332    order as the ones we'll want from the template. Simply add to the
333    end of the singly-linked list each time! */
334 static int add_file_entry(char *jigdo_entry)
335 {
336     int error = 0;
337     char *file_name = NULL;
338     INT64 file_size = 0;
339     char *ptr = jigdo_entry;
340     char *base64_md5 = NULL;
341     char *match = NULL;
342     char *jigdo_name = NULL;
343     
344     /* Grab out the component strings from the entry in the jigdo file */
345     base64_md5 = jigdo_entry;
346     while (0 != *ptr)
347     {
348         if ('=' == *ptr)
349         {
350             *ptr = 0;
351             ptr++;
352             match = ptr;
353         }
354         else if (':' == *ptr)
355         {
356             *ptr = 0;
357             ptr++;
358             jigdo_name = ptr;
359         }
360         else if ('\n' == *ptr)
361             *ptr = 0;
362         else
363             ptr++;
364     }
365
366     if (find_file_in_md5_list(base64_md5))
367         return 0; /* We already have an entry for this file; don't
368                    * waste any more time on it */
369
370     /* else look for the file in the filesystem */
371     if (NULL == match || NULL == jigdo_name)
372     {
373         fprintf(logfile, "Could not parse malformed jigdo entry \"%s\"\n", jigdo_entry);
374         return EINVAL;
375     }
376     error = find_file_in_mirror(match, jigdo_name, match, &file_size, &file_name);
377
378     if (error)
379         {
380                 if (missing_filename)
381                         add_md5_entry(MISSING, base64_md5, file_name);
382                 else
383                 {
384                         fprintf(logfile, "Unable to find a file to match %s\n", file_name);
385                         fprintf(logfile, "Abort!\n");
386                         exit (ENOENT);
387                 }
388         }
389     else
390         add_md5_entry(file_size, base64_md5, file_name);
391     return 0;
392 }
393
394 static int parse_jigdo_file(char *filename)
395 {
396     char buf[2048];
397     gzFile *file = NULL;
398     char *ret = NULL;
399     int error = 0;
400     
401     file = gzopen(filename, "rb");
402     if (!file)
403     {
404         fprintf(logfile, "Failed to open jigdo file %s, error %d!\n", filename, errno);
405         return errno;
406     }
407
408     /* Find the [Parts] section of the jigdo file */
409     while (1)
410     {
411         ret = gzgets(file, buf, sizeof(buf));
412         if (NULL == ret)
413             break;
414         if (!strncmp(buf, "[Parts]", 7))
415             break;
416     }
417
418     /* Now grab the individual file entries and build a list */
419     while (1)
420     {
421         ret = gzgets(file, buf, sizeof(buf));
422         if (NULL == ret || !strcmp(buf, "\n"))
423             break;
424         if (!strcmp(buf, "[") || !strcmp(buf, "#"))
425             continue;
426         error = add_file_entry(strdup(buf));
427         if (error)
428             break;
429     }
430
431     gzclose(file);
432     return error;
433 }
434
435 static int ungzip_data_block(char *in_buf, INT64 in_len, char *out_buf, INT64 out_len)
436 {
437     int error = 0;
438     z_stream uc_stream;
439     
440     uc_stream.zalloc = NULL;
441     uc_stream.zfree = NULL;
442     uc_stream.opaque = NULL;
443     uc_stream.next_in = (unsigned char *)in_buf;
444     uc_stream.avail_in = in_len;
445
446     error = inflateInit(&uc_stream);
447     if (Z_OK != error)
448     {
449         fprintf(logfile, "ungzip_data_block: failed to init, error %d\n", error);
450         return EIO;
451     }
452     
453     uc_stream.next_out = (unsigned char *)out_buf;
454     uc_stream.avail_out = out_len;
455
456     error = inflate(&uc_stream, Z_FINISH);
457     if (Z_OK != error && Z_STREAM_END != error)
458     {
459         fprintf(logfile, "ungzip_data_block: failed to decompress, error %d\n", error);
460         return EIO;
461     }
462     
463     error = inflateEnd(&uc_stream);
464     if (Z_OK != error)
465     {
466         fprintf(logfile, "ungzip_data_block: failed to end, error %d\n", error);
467         return EIO;
468     }
469     
470     return 0;
471 }    
472
473 #ifdef BZ2_SUPPORT
474 static int unbzip_data_block(char *in_buf, INT64 in_len, char *out_buf, INT64 out_len)
475 {
476     int error = 0;
477     bz_stream uc_stream;
478     
479     uc_stream.bzalloc = NULL;
480     uc_stream.bzfree = NULL;
481     uc_stream.opaque = NULL;
482     uc_stream.next_in = in_buf;
483     uc_stream.avail_in = in_len;
484
485     error = BZ2_bzDecompressInit(&uc_stream, 0, 0);
486     if (BZ_OK != error)
487     {
488         fprintf(logfile, "unbzip_data_block: failed to init, error %d\n", error);
489         return EIO;
490     }
491     
492     uc_stream.next_out = out_buf;
493     uc_stream.avail_out = out_len;
494
495     error = BZ2_bzDecompress(&uc_stream);
496     if (BZ_OK != error && BZ_STREAM_END != error)
497     {
498         fprintf(logfile, "unbzip_data_block: failed to decompress, error %d\n", error);
499         return EIO;
500     }
501     
502     error = BZ2_bzDecompressEnd(&uc_stream);
503     if (BZ_OK != error)
504     {
505         fprintf(logfile, "unbzip_data_block: failed to end, error %d\n", error);
506         return EIO;
507     }
508     
509     return 0;
510 }    
511 #endif
512
513 static int decompress_data_block(char *in_buf, INT64 in_len, char *out_buf,
514                                  INT64 out_len, int compress_type)
515 {
516 #ifdef BZ2_SUPPORT
517     if (CT_BZIP2 == compress_type)
518         return unbzip_data_block(in_buf, in_len, out_buf, out_len);
519     else
520 #endif
521         return ungzip_data_block(in_buf, in_len, out_buf, out_len);
522 }
523
524 static int read_data_block(FILE *template_file)
525 {
526     char inbuf[1024];
527     INT64 i = 0;
528     static INT64 template_offset = -1;
529     INT64 compressed_len = 0;
530     INT64 uncompressed_len = 0;
531     char *comp_buf = NULL;
532     int read_num = 0;
533     int error = 0;
534
535     if (-1 == template_offset)
536     {
537         fseek(template_file, 0, SEEK_SET);
538         fread(inbuf, sizeof(inbuf), 1, template_file);
539         for (i = 0; i < sizeof(inbuf); i++)
540         {
541             if (!strncmp(&inbuf[i], "DATA", 4))
542             {
543                 zip_state.algorithm = CT_GZIP;
544                 template_offset = i;
545                 break;
546             }
547             if (!strncmp(&inbuf[i], "BZIP", 4))
548             {
549                 zip_state.algorithm = CT_BZIP2;
550                 template_offset = i;
551                 break;
552             }
553         }
554         if (-1 == template_offset)
555         {
556             fprintf(logfile, "Unable to locate DATA block in template (offset %lld)\n",
557                     template_offset);
558             return EINVAL;
559         }    
560     }
561     
562     fseek(template_file, template_offset, SEEK_SET);
563     fread(inbuf, 16, 1, template_file);
564     if (strncmp(inbuf, "DATA", 4) && strncmp(inbuf, "BZIP", 4))
565     {
566         fprintf(logfile, "Unable to locate DATA block in template (offset %lld)\n",
567                 template_offset);
568         return EINVAL;
569     }    
570     
571     compressed_len = read_le48((unsigned char *)&inbuf[4]);
572     uncompressed_len = read_le48((unsigned char *)&inbuf[10]);
573
574     comp_buf = calloc(1, compressed_len);
575     if (!comp_buf)
576     {
577         fprintf(logfile, "Unable to locate DATA block in template (offset %lld)\n",
578                 template_offset);
579         return ENOMEM;
580     }
581     
582     zip_state.data_buf = calloc(1, uncompressed_len);
583     if (!zip_state.data_buf)
584     {
585         fprintf(logfile, "Unable to allocate %lld bytes for decompression\n",
586                 uncompressed_len);
587         return ENOMEM;
588     }
589
590     read_num = fread(comp_buf, compressed_len, 1, template_file);
591     if (0 == read_num)
592     {
593         fprintf(logfile, "Unable to read %lld bytes for decompression\n",
594                 uncompressed_len);
595         return EIO;
596     }
597
598     error = decompress_data_block(comp_buf, compressed_len,
599                                   zip_state.data_buf, uncompressed_len, zip_state.algorithm);
600     if (error)
601     {
602         fprintf(logfile, "Unable to decompress data block, error %d\n", error);
603         return error;
604     }
605         
606     template_offset += compressed_len;
607     zip_state.buf_size = uncompressed_len;
608     zip_state.offset_in_curr_buf = 0;
609     free (comp_buf);
610     return 0;
611 }
612
613 static int skip_data_block(INT64 data_size, FILE *template_file)
614 {
615     int error = 0;
616     INT64 remaining = data_size;
617     INT64 size = 0;
618
619     /* If we're coming in in the middle of the image, we'll need to
620        skip through some compressed data */
621     while (remaining)
622     {
623         if (!zip_state.data_buf)
624         {
625             error = read_data_block(template_file);
626             if (error)
627             {
628                 fprintf(logfile, "Unable to decompress template data, error %d\n",
629                         error);
630                 return error;
631             }
632         }
633         size = MIN((zip_state.buf_size - zip_state.offset_in_curr_buf), remaining);
634         zip_state.offset_in_curr_buf += size;
635         remaining -= size;
636         
637         if (zip_state.offset_in_curr_buf == zip_state.buf_size)
638         {
639             free(zip_state.data_buf);
640             zip_state.data_buf = NULL;
641         }
642     }
643     
644     fprintf(logfile, "skip_data_block: skipped %lld bytes of unmatched data\n", data_size);
645     return error;
646 }
647
648 static int parse_data_block(INT64 data_size, FILE *template_file, struct mk_MD5Context *context)
649 {
650     int error = 0;
651     INT64 remaining = data_size;
652     INT64 size = 0;
653     int out_size = 0;
654
655     while (remaining)
656     {
657         if (!zip_state.data_buf)
658         {
659             error = read_data_block(template_file);
660             if (error)
661             {
662                 fprintf(logfile, "Unable to decompress template data, error %d\n",
663                         error);
664                 return error;
665             }
666         }
667         size = MIN((zip_state.buf_size - zip_state.offset_in_curr_buf), remaining);
668         out_size = fwrite(&zip_state.data_buf[zip_state.offset_in_curr_buf], size, 1, outfile);
669         if (!out_size)
670         {
671             fprintf(logfile, "parse_data_block: fwrite %lld failed with error %d; aborting\n", size, ferror(outfile));
672             return ferror(outfile);
673         }
674
675         if (verbose)
676             display_progress(outfile, "template data");
677
678         if (!quick)
679             mk_MD5Update(context,
680                                                  (unsigned char *)&zip_state.data_buf[zip_state.offset_in_curr_buf],
681                                                  size);
682         zip_state.offset_in_curr_buf += size;
683         remaining -= size;
684         
685         if (zip_state.offset_in_curr_buf == zip_state.buf_size)
686         {
687             free(zip_state.data_buf);
688             zip_state.data_buf = NULL;
689         }
690     }
691     if (verbose > 1)
692         fprintf(logfile, "parse_data_block: wrote %lld bytes of unmatched data\n", data_size);
693     return error;
694 }
695
696 static int read_file_data(char *filename, char *missing, INT64 offset, INT64 data_size,
697                           struct mk_MD5Context *file_context, struct mk_MD5Context *image_context)
698 {
699     FILE *input_file = NULL;
700     INT64 remaining = data_size;
701     char buf[BUF_SIZE];
702     int num_read = 0;
703     int out_size = 0;
704
705     input_file = fopen(filename, "rb");
706     if (!input_file)
707     {
708         fprintf(logfile, "Unable to open mirror file %s, error %d\n",
709                 filename, errno);
710         return errno;
711     }
712
713     if (missing)
714     {
715         fclose(input_file);
716         return 0;
717     }
718     
719     fseek(input_file, offset, SEEK_SET);
720     while (remaining)
721     {
722         int size = MIN(BUF_SIZE, remaining);
723         memset(buf, 0, BUF_SIZE);
724         
725         num_read = fread(buf, size, 1, input_file);
726         if (!num_read)
727         {
728             fprintf(logfile, "Unable to read from mirror file %s, error %d (offset %ld, length %d)\n",
729                     filename, errno, ftell(input_file), size);
730             fclose(input_file);
731             return errno;
732         }
733         if (file_context)
734         {
735             mk_MD5Update(image_context, (unsigned char *)buf, size);
736             mk_MD5Update(file_context, (unsigned char *)buf, size);
737         }
738         
739         out_size = fwrite(buf, size, 1, outfile);
740         if (!out_size)
741         {
742             fprintf(logfile, "read_file_data: fwrite %d failed with error %d; aborting\n", size, ferror(outfile));
743             return ferror(outfile);
744         }
745         
746         if (verbose)
747             display_progress(outfile, file_base_name(filename));
748         
749         remaining -= size;
750     }
751     if (verbose > 1)
752         fprintf(logfile, "read_file_data: wrote %lld bytes of data from %s\n",
753                 data_size, filename);
754     fclose(input_file);
755     return 0;
756 }
757
758 static int parse_file_block(INT64 offset, INT64 data_size, INT64 file_size, JIGDB *dbp,
759                             unsigned char *md5, struct mk_MD5Context *image_context,
760                             char *missing)
761 {
762     char *base64_md5 = base64_dump(md5, 16);
763     struct mk_MD5Context file_context;
764     struct mk_MD5Context *use_context = NULL;
765     unsigned char file_md5[16];
766     md5_list_t *md5_list_entry = NULL;
767     db_file_entry_t *db_entry = NULL;
768     int error = 0;
769     char *filename = NULL;
770
771     if (!quick)
772     {
773         use_context = &file_context;
774         mk_MD5Init(use_context);
775     }
776
777     /* Try the DB first if we have one */
778     if (dbp)
779     {
780         error = db_lookup_file_by_md5(dbp, base64_md5, &db_entry);
781         if (!error)
782             filename = db_entry->filename;
783     }
784
785     /* No joy; fall back to the MD5 list */
786     if (!filename)
787     {
788         md5_list_entry = find_file_in_md5_list(base64_md5);
789         if (md5_list_entry && file_size == md5_list_entry->file_size)
790             filename = md5_list_entry->full_path;
791     }
792
793     if (filename)
794     {
795         error = read_file_data(filename, missing, offset, data_size,
796                                use_context, image_context);
797         
798         if (error && (ENOENT != error))
799         {
800             fprintf(logfile, "Failed to read file %s, error %d\n", filename, error);
801             return error;
802         }
803         
804         if (!quick)
805         {
806             mk_MD5Final(file_md5, &file_context);
807             
808             if (memcmp(file_md5, md5, 16))
809             {
810                 fprintf(logfile, "MD5 MISMATCH for file %s\n", filename);
811                 fprintf(logfile, "    template looking for %s\n", md5);
812                 fprintf(logfile, "    file %s is    %s\n", filename, file_md5);
813                 return EINVAL;
814             }
815         }
816         return 0;
817     }
818     
819     /* No file found. Add it to the list of missing files, or complain */
820     if ( missing &&
821          (MISSING == md5_list_entry->file_size) &&
822          (!memcmp(md5_list_entry->md5, base64_md5, 16) ) )
823     {
824         write_missing_entry(missing, md5_list_entry->full_path);
825         return 0;
826     }
827     /* else */
828     return ENOENT;
829 }
830
831 static int parse_template_file(char *filename, int sizeonly,
832                                char *missing, char *output_name, char *db_filename)
833 {
834     INT64 template_offset = 0;
835     INT64 bytes = 0;
836     char *buf = NULL;
837     FILE *file = NULL;
838     INT64 file_size = 0;
839     INT64 desc_start = 0;
840     INT64 written_length = 0;
841     INT64 output_offset = 0;
842     int i = 0;
843     int error = 0;
844     struct mk_MD5Context template_context;
845     unsigned char image_md5sum[16];
846     JIGDB *dbp = NULL;
847
848     zip_state.total_offset = 0;
849     
850     file = fopen(filename, "rb");
851     if (!file)
852     {
853         fprintf(logfile, "Failed to open template file %s, error %d!\n", filename, errno);
854         return errno;
855     }
856
857     if (db_filename)
858     {
859         dbp = db_open(db_filename);
860         if (!dbp)
861         {
862             fprintf(logfile, "Failed to open DB file %s, error %d\n", db_filename, errno);
863             return errno;
864         }
865         /* If we have a DB, then we should cache the template
866          * information in it too. Check and see if there is
867          * information about this template file in the database
868          * already. */
869     }
870
871     buf = malloc(BUF_SIZE);
872     if (!buf)
873     {
874         fprintf(logfile, "Failed to malloc %d bytes. Abort!\n", BUF_SIZE);
875         fclose(file);
876         return ENOMEM;
877     }
878
879     /* Find the beginning of the desc block */
880     file_size = get_file_size(filename);
881     fseek(file, file_size - 6, SEEK_SET);
882     fread(buf, 6, 1, file);
883     desc_start = file_size - read_le48((unsigned char *)buf);
884
885     /* Now seek back to the beginning image desc block to grab the MD5
886        and image length */
887     fseek(file, file_size - 33, SEEK_SET);
888     fread(buf, BUF_SIZE, 1, file);
889     if (buf[0] != 5) /* image data */
890     {
891         fprintf(logfile, "Failed to find image desc in the template file\n");
892         fclose(file);
893         return EINVAL;
894     }
895
896     if (sizeonly)
897     {
898         fclose(file);
899         printf("%lld\n", read_le48((unsigned char *)&buf[1]));
900         return 0;
901     }
902
903     if (verbose)
904     {
905         fprintf(logfile, "Image MD5 should be    ");
906         for (i = 0; i < 16; i++)
907             fprintf(logfile, "%2.2x", (unsigned char)buf[i+7]);
908         fprintf(logfile, "\n");
909         fprintf(logfile, "Image size should be   %lld bytes\n", read_le48((unsigned char *)&buf[1]));
910     }
911
912     out_size = read_le48((unsigned char *)&buf[1]);
913     
914     /* Now seek back to the start of the desc block */
915     fseek(file, desc_start, SEEK_SET);
916     fread(buf, 10, 1, file);
917     if (strncmp(buf, "DESC", 4))
918     {
919         fprintf(logfile, "Failed to find desc start in the template file\n");
920         fclose(file);
921         return EINVAL;
922     }
923     if ((file_size - desc_start) != read_le48((unsigned char *)&buf[4]))
924     {
925         fprintf(logfile, "Inconsistent desc length in the template file!\n");
926         fprintf(logfile, "Final chunk says %lld, first chunk says %lld\n",
927                 file_size - desc_start, read_le48((unsigned char *)&buf[4]));
928         fclose(file);
929         return EINVAL;
930     }
931
932     if (!quick)
933         mk_MD5Init(&template_context);
934     template_offset = desc_start + 10;
935
936     if (1 == verbose)
937         fprintf(logfile, "Creating ISO image %s\n", output_name);
938
939     /* Main loop - walk through the template file and expand each entry we find */
940     while (1)
941     {
942         INT64 extent_size;
943         INT64 skip = 0;
944         INT64 read_length = 0;
945
946         if (template_offset >= (file_size - 33))
947         {
948             if (verbose > 1)
949                 fprintf(logfile, "Reached end of template file\n");
950             break; /* Finished! */
951         }
952         
953         if (output_offset > end_offset) /* Past the range we were asked for */
954         {
955             fprintf(logfile, "Reached end of range requested\n");            
956             break;
957         }
958         
959         fseek(file, template_offset, SEEK_SET);
960         bytes = fread(buf, (MIN (BUF_SIZE, file_size - template_offset)), 1, file);
961         if (1 != bytes)
962         {
963             fprintf(logfile, "Failed to read template file!\n");
964             fclose(file);
965             return EINVAL;
966         }
967         
968         extent_size = read_le48((unsigned char *)&buf[1]);
969         read_length = extent_size;
970         
971         if (start_offset > output_offset)
972             skip = start_offset - output_offset;
973         if ((output_offset + extent_size) > end_offset)
974             read_length -= (output_offset + extent_size - end_offset - 1);
975         read_length -= skip;
976         
977         switch (buf[0])
978         {
979             
980             case 2: /* unmatched data */
981                 template_offset += 7;
982                 if (missing)
983                     break;
984                 if ((output_offset + extent_size) >= start_offset)
985                 {
986                     if (skip)
987                         error = skip_data_block(skip, file);
988                     if (error)
989                     {
990                         fprintf(logfile, "Unable to read data block to skip, error %d\n", error);
991                         fclose(file);
992                         return error;
993                     }
994                     error = parse_data_block(read_length, file, &template_context);
995                     if (error)
996                     {
997                         fprintf(logfile, "Unable to read data block, error %d\n", error);
998                         fclose(file);
999                         return error;
1000                     }
1001                     written_length += read_length;
1002                 }
1003                 else
1004                     error = skip_data_block(extent_size, file);
1005                 break;
1006             case 6:
1007                 template_offset += 31;
1008                 if ((output_offset + extent_size) >= start_offset)
1009                 {
1010                     error = parse_file_block(skip, read_length, extent_size, dbp,
1011                                              (unsigned char *)&buf[15], &template_context, missing);
1012                     if (error)
1013                     {
1014                         fprintf(logfile, "Unable to read file block, error %d\n", error);
1015                         fclose(file);
1016                         return error;
1017                     }
1018                     written_length += read_length;
1019                 }
1020                 break;
1021             default:
1022                 fprintf(logfile, "Unknown block type %d!\n", buf[0]);
1023                 fclose(file);
1024                 return EINVAL;
1025         }
1026         output_offset += extent_size;
1027     }
1028
1029     if (missing && missing_file)
1030         return ENOENT;
1031     
1032     fclose(file);
1033     if (verbose)
1034     {
1035         fprintf(logfile, "\n");
1036         if (!quick)
1037         {
1038             mk_MD5Final (image_md5sum, &template_context);
1039             fprintf(logfile, "Output image MD5 is    ");
1040             for (i = 0; i < 16; i++)
1041                 fprintf(logfile, "%2.2x", image_md5sum[i]);
1042             fprintf(logfile, "\n");
1043         }
1044         fprintf(logfile, "Output image length is %lld bytes\n", written_length);
1045     }
1046     
1047     return 0;
1048 }
1049
1050 static void usage(char *progname)
1051 {
1052     printf("%s [OPTIONS]\n\n", progname);
1053     printf(" Options:\n");
1054         printf(" -M <missing name>   Rather than try to build the image, just check that\n");
1055         printf("                     all the needed files are available. If any are missing,\n");
1056         printf("                     list them in this file.\n");
1057     printf(" -d <DB name>        Specify an input MD5 database file, as created by jigsum\n");
1058     printf(" -e <bytenum>        End byte number; will end at EOF if not specified\n");    
1059     printf(" -f <MD5 name>       Specify an input MD5 file. MD5s must be in jigdo's\n");
1060         printf("                     pseudo-base64 format\n");
1061     printf(" -j <jigdo name>     Specify the input jigdo file\n");
1062     printf(" -l <logfile>        Specify a logfile to append to.\n");
1063     printf("                     If not specified, will log to stderr\n");
1064     printf(" -m <item=path>      Map <item> to <path> to find the files in the mirror\n");
1065     printf(" -o <outfile>        Specify a file to write the ISO image to.\n");
1066     printf("                     If not specified, will write to stdout\n");
1067     printf(" -q                  Quick mode. Don't check MD5sums. Dangerous!\n");
1068     printf(" -s <bytenum>        Start byte number; will start at 0 if not specified\n");
1069     printf(" -t <template name>  Specify the input template file\n");
1070         printf(" -v                  Make the output logging more verbose\n");
1071     printf(" -z                  Don't attempt to rebuild the image; simply print its\n");
1072     printf("                     size in bytes\n");
1073 }
1074
1075 int main(int argc, char **argv)
1076 {
1077     char *template_filename = NULL;
1078     char *jigdo_filename = NULL;
1079     char *md5_filename = NULL;
1080     char *output_name = NULL;
1081     char *db_filename = NULL;
1082     int c = -1;
1083     int error = 0;
1084     int sizeonly = 0;
1085
1086     logfile = stderr;
1087     outfile = stdout;
1088
1089     bzero(&zip_state, sizeof(zip_state));
1090
1091     while(1)
1092     {
1093         c = getopt(argc, argv, ":?M:d:e:f:h:j:l:m:o:qs:t:vz");
1094         if (-1 == c)
1095             break;
1096         
1097         switch(c)
1098         {
1099             case 'v':
1100                 verbose++;
1101                 break;
1102             case 'q':
1103                 quick = 1;
1104                 break;
1105             case 'l':
1106                 logfile = fopen(optarg, "ab");
1107                 if (!logfile)
1108                 {
1109                     fprintf(stderr, "Unable to open log file %s\n", optarg);
1110                     return errno;
1111                 }
1112                 setlinebuf(logfile);
1113                 break;
1114             case 'o':
1115                 output_name = optarg;
1116                 outfile = fopen(output_name, "wb");
1117                 if (!outfile)
1118                 {
1119                     fprintf(stderr, "Unable to open output file %s\n", optarg);
1120                     return errno;
1121                 }
1122                 break;
1123             case 'j':
1124                 if (jigdo_filename)
1125                 {
1126                     fprintf(logfile, "Can only specify one jigdo file!\n");
1127                     return EINVAL;
1128                 }
1129                 /* else */
1130                 jigdo_filename = optarg;
1131                 break;
1132             case 't':
1133                 if (template_filename)
1134                 {
1135                     fprintf(logfile, "Can only specify one template file!\n");
1136                     return EINVAL;
1137                 }
1138                 /* else */
1139                 template_filename = optarg;
1140                 break;
1141             case 'f':
1142                 if (md5_filename)
1143                 {
1144                     fprintf(logfile, "Can only specify one MD5 file!\n");
1145                     return EINVAL;
1146                 }
1147                 /* else */
1148                 md5_filename = optarg;
1149                 break;                
1150             case 'd':
1151                 if (db_filename)
1152                 {
1153                     fprintf(logfile, "Can only specify one db file!\n");
1154                     return EINVAL;
1155                 }
1156                 /* else */
1157                 db_filename = optarg;
1158                 break;                
1159             case 'm':
1160                 error = add_match_entry(strdup(optarg));
1161                 if (error)
1162                     return error;
1163                 break;
1164             case 'M':
1165                 missing_filename = optarg;
1166                 break;
1167             case ':':
1168                 fprintf(logfile, "Missing argument!\n");
1169                 return EINVAL;
1170                 break;
1171             case 'h':
1172             case '?':
1173                 usage(argv[0]);
1174                 return 0;
1175                 break;
1176             case 's':
1177                 start_offset = strtoull(optarg, NULL, 10);
1178                 if (start_offset != 0)
1179                     quick = 1;
1180                 break;
1181             case 'e':
1182                 end_offset = strtoull(optarg, NULL, 10);
1183                 if (end_offset != 0)
1184                     quick = 1;
1185                 break;
1186             case 'z':
1187                 sizeonly = 1;
1188                 break;
1189             default:
1190                 fprintf(logfile, "Unknown option!\n");
1191                 return EINVAL;
1192         }
1193     }
1194
1195     if (0 == end_offset)
1196         end_offset = LLONG_MAX;
1197
1198     if ((NULL == jigdo_filename) &&
1199         (NULL == md5_filename) && 
1200         (NULL == db_filename) && 
1201         !sizeonly)
1202     {
1203         fprintf(logfile, "No jigdo file, DB file or MD5 file specified!\n");
1204         usage(argv[0]);
1205         return EINVAL;
1206     }
1207     
1208     if (NULL == template_filename)
1209     {
1210         fprintf(logfile, "No template file specified!\n");
1211         usage(argv[0]);
1212         return EINVAL;
1213     }    
1214
1215     if (md5_filename)
1216     {
1217         /* Build up a list of the files we've been fed */
1218         error = parse_md5_file(md5_filename);
1219         if (error)
1220         {
1221             fprintf(logfile, "Unable to parse the MD5 file %s\n", md5_filename);
1222             return error;
1223         }
1224     }
1225
1226     if (jigdo_filename)
1227     {
1228         /* Build up a list of file mappings */
1229         error = parse_jigdo_file(jigdo_filename);
1230         if (error)
1231         {
1232             fprintf(logfile, "Unable to parse the jigdo file %s\n", jigdo_filename);
1233             return error;
1234         }
1235     }
1236
1237     if (!output_name)
1238         output_name = "to stdout";
1239     /* Read the template file and actually build the image to <outfile> */
1240     error = parse_template_file(template_filename, sizeonly,
1241                                 missing_filename, output_name, db_filename);
1242     if (error)
1243     {
1244         fprintf(logfile, "Unable to recreate image from template file %s\n", template_filename);
1245         if (missing_filename)
1246             fprintf(logfile, "%s contains the list of missing files\n", missing_filename);
1247         return error;
1248     }        
1249
1250     fclose(logfile);
1251     return 0;
1252 }
1253