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