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