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