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