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