Cleaned up licensing
[jigit.git] / mkimage.c
index f88efa3..d56ad4c 100644 (file)
--- a/mkimage.c
+++ b/mkimage.c
@@ -1,3 +1,15 @@
+/*
+ * mkimage
+ *
+ * Tool to create an ISO image from jigdo files
+ *
+ * Copyright (c) 2004 Steve McIntyre <steve@einval.com>
+ *
+ * GPL v2 - see COPYING
+ */
+
+#undef BZ2_SUPPORT
+
 #include <errno.h>
 #include <math.h>
 #include <stdlib.h>
@@ -5,7 +17,9 @@
 #include <string.h>
 #include <limits.h>
 #include <zlib.h>
-#include <stdio.h>
+#ifdef BZ2_SUPPORT
+#   include <bzlib.h>
+#endif
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include "endian.h"
 #include "md5.h"
 
+typedef long long INT64;
 typedef unsigned long long UINT64;
 typedef unsigned long      UINT32;
 
+#ifndef LLONG_MAX
+#   define LLONG_MAX (INT64)INT_MAX * INT_MAX
+#endif
+
 #define BUF_SIZE 65536
+#define MISSING -1
 
 #ifndef MIN
 #define MIN(x,y)        ( ((x) < (y)) ? (x) : (y))
 #endif
 
+#define COMP_GZIP 2
+#define COMP_BZIP 8
+
 FILE *logfile = NULL;
+FILE *outfile = NULL;
+FILE *missing_file = NULL;
+long long start_offset = 0;
+long long end_offset = 0;
 int quick = 0;
+int verbose = 0;
+UINT64 out_size = 0;
 
 typedef enum state_
 {
@@ -46,24 +75,75 @@ typedef struct match_list_
 match_list_t *match_list_head = NULL;
 match_list_t *match_list_tail = NULL;
 
-typedef struct jigdo_list_
+typedef struct md5_list_
 {
-    struct jigdo_list_ *next;
-    off_t file_size;
+    struct md5_list_ *next;
+    INT64 file_size;
     char *md5;
     char *full_path;
-} jigdo_list_t;
+} md5_list_t;
 
-jigdo_list_t *jigdo_list_head = NULL;
-jigdo_list_t *jigdo_list_tail = NULL;
+md5_list_t *md5_list_head = NULL;
+md5_list_t *md5_list_tail = NULL;
 
 struct
 {
     char   *data_buf;
-    size_t  buf_size;
-    off_t   curr_offset;
+    INT64  buf_size;
+    INT64   offset_in_curr_buf;
+    INT64   total_offset;
 } zip_state;
 
+/* Grab the file component from a full path */
+static char *file_base_name(char *path)
+{
+    char *endptr = path;
+    char *ptr = path;
+    
+    while (*ptr != '\0')
+    {
+        if ('/' == *ptr)
+            endptr = ++ptr;
+        else
+            ++ptr;
+    }
+    return endptr;
+}
+
+static void write_missing_entry(char *missing, char *filename)
+{
+    if (!missing_file)
+    {
+        missing_file = fopen(missing, "wb");
+        if (!missing_file)
+        {
+            fprintf(logfile, "write_missing_entry: Unable to open missing log %s; error %d\n", missing, errno);
+            exit(1);
+        }
+    }
+    fprintf(missing_file, "%s\n", filename);
+}
+
+static INT64 get_file_size(char *filename)
+{
+    struct stat sb;
+    int error = 0;
+    
+    error = stat(filename, &sb);
+    if (error)
+        return MISSING;
+    else
+        return sb.st_size;
+}
+
+static void display_progress(FILE *file, char *text)
+{
+    INT64 written = ftello(file);
+    if (out_size > 0)
+        fprintf(logfile, "\r %5.2f%%  %-60.60s",
+               100.0 * written / out_size, text);
+}
+
 static int add_match_entry(char *match)
 {
     match_list_t *entry = NULL;
@@ -112,7 +192,7 @@ static int add_match_entry(char *match)
     return 0;
 }
 
-static int file_exists(char *path, off_t *size)
+static int file_exists(char *path, INT64 *size)
 {
     struct stat sb;
     int error = 0;
@@ -128,37 +208,7 @@ static int file_exists(char *path, off_t *size)
     return 0;
 }
 
-static char *base64_dump(unsigned char *buf, size_t buf_size)
-{
-    const char *b64_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
-    int value = 0;
-    unsigned int i;
-    int bits = 0;
-    char *out = calloc(1, 30);
-    unsigned int out_pos = 0;
-
-    if (!out)
-        return NULL;
-
-    for (i = 0; i < buf_size ; i++)
-    {
-        value = (value << 8) | buf[i];
-        bits += 2;
-        out[out_pos++] = b64_enc[(value >> bits) & 63U];
-        if (bits >= 8) {
-            bits -= 6;
-            out[out_pos++] = b64_enc[(value >> bits) & 63U];
-        }
-    }
-    if (bits > 0)
-    {
-        value <<= 8 - bits;
-        out[out_pos++] = b64_enc[(value >> bits) & 63U];
-    }
-    return out;
-}
-
-static int find_file_in_mirror(char *jigdo_entry, char **mirror_path, char **md5sum, off_t *file_size)
+static int find_file_in_mirror(char *jigdo_entry, char **mirror_path, char **md5sum, INT64 *file_size)
 {
     match_list_t *entry = match_list_head;
     char path[PATH_MAX];
@@ -209,58 +259,112 @@ static int find_file_in_mirror(char *jigdo_entry, char **mirror_path, char **md5
         entry = entry->next;
     }
     
-    fprintf(logfile, "Could not find file %s:%s in any path\n", match, jigdo_name);
+    *mirror_path = jigdo_name;
     return ENOENT;
 }
 
-/* DELIBERATELY do not sort these, or do anything clever with
-   insertion. The entries in the jigdo file should be in the same
-   order as the ones we'll want from the template. Simply add to the
-   end of the singly-linked list each time! */
-static int add_file_entry(char *jigdo_entry)
+
+static int add_md5_entry(INT64 size, char *md5, char *path)
 {
-    int error = 0;
-    char *file_name = NULL;
-    char *md5 = NULL;
-    jigdo_list_t *new = NULL;
-    off_t file_size = 0;
-    
-    error = find_file_in_mirror(jigdo_entry, &file_name, &md5, &file_size);
-    if (error)
-        return error;
-    
+    md5_list_t *new = NULL;    
     new = calloc(1, sizeof(*new));
     if (!new)
         return ENOMEM;
 
     new->md5 = md5;
-    new->full_path = file_name;
-    new->file_size = file_size;
+    new->full_path = path;
+    new->file_size = size;
     
-    if (!jigdo_list_head)
+    if (!md5_list_head)
     {
-        jigdo_list_head = new;
-        jigdo_list_tail = new;
+        md5_list_head = new;
+        md5_list_tail = new;
     }
     else
     {
-        jigdo_list_tail->next = new;
-        jigdo_list_tail = new;
+        md5_list_tail->next = new;
+        md5_list_tail = new;
     }
     
     return 0;
 }
 
-static int parse_jigdo_file(char *filename)
+static int parse_md5_entry(char *md5_entry)
+{
+    int error = 0;
+    char *file_name = NULL;
+    char *md5 = NULL;
+    INT64 file_size = 0;
+
+    md5_entry[22] = 0;
+    md5_entry[23] = 0;
+
+    md5 = md5_entry;
+    file_name = &md5_entry[24];
+
+    if ('\n' == file_name[strlen(file_name) -1])
+        file_name[strlen(file_name) - 1] = 0;
+    
+    file_size = get_file_size(file_name);
+
+    error = add_md5_entry(file_size, md5, file_name);
+    return 0;
+}
+
+static int parse_md5_file(char *filename)
 {
     unsigned char buf[2048];
     FILE *file = NULL;
     char *ret = NULL;
     int error = 0;
-    
+
     file = fopen(filename, "rb");
     if (!file)
     {
+        fprintf(logfile, "Failed to open MD5 file %s, error %d!\n", filename, errno);
+        return errno;
+    }
+    
+    while(1)
+    {
+        ret = fgets(buf, sizeof(buf), file);
+        if (NULL == ret)
+            break;
+        error = parse_md5_entry(strdup(buf));
+    }
+    return 0;
+}
+
+/* DELIBERATELY do not sort these, or do anything clever with
+   insertion. The entries in the jigdo file should be in the same
+   order as the ones we'll want from the template. Simply add to the
+   end of the singly-linked list each time! */
+static int add_file_entry(char *jigdo_entry)
+{
+    int error = 0;
+    char *file_name = NULL;
+    char *md5 = NULL;
+    INT64 file_size = 0;
+    
+    error = find_file_in_mirror(jigdo_entry, &file_name, &md5, &file_size);
+
+    if (error)
+        add_md5_entry(MISSING, md5, file_name);
+    else
+        add_md5_entry(file_size, md5, file_name);
+    return 0;
+}
+
+static int parse_jigdo_file(char *filename)
+{
+    unsigned char buf[2048];
+    gzFile *file = NULL;
+    char *ret = NULL;
+    int error = 0;
+    
+    file = gzopen(filename, "rb");
+    if (!file)
+    {
         fprintf(logfile, "Failed to open jigdo file %s, error %d!\n", filename, errno);
         return errno;
     }
@@ -268,7 +372,7 @@ static int parse_jigdo_file(char *filename)
     /* Find the [Parts] section of the jigdo file */
     while (1)
     {
-        ret = fgets(buf, sizeof(buf), file);
+        ret = gzgets(file, buf, sizeof(buf));
         if (NULL == ret)
             break;
         if (!strncmp(buf, "[Parts]", 7))
@@ -278,32 +382,21 @@ static int parse_jigdo_file(char *filename)
     /* Now grab the individual file entries and build a list */
     while (1)
     {
-        ret = fgets(buf, sizeof(buf), file);
+        ret = gzgets(file, buf, sizeof(buf));
         if (NULL == ret || !strcmp(buf, "\n"))
             break;
         if (!strcmp(buf, "[") || !strcmp(buf, "#"))
             continue;
         error = add_file_entry(strdup(buf));
         if (error)
-            return error;
+            break;
     }
-    
-    return 0;
-}
 
-static off_t get_file_size(char *filename)
-{
-    struct stat sb;
-    int error = 0;
-    
-    error = stat(filename, &sb);
-    if (error)
-        return -1;
-    else
-        return sb.st_size;
+    gzclose(file);
+    return error;
 }
 
-static int decompress_data_block(char *in_buf, size_t in_len, char *out_buf, size_t out_len)
+static int ungzip_data_block(char *in_buf, INT64 in_len, char *out_buf, INT64 out_len)
 {
     int error = 0;
     z_stream uc_stream;
@@ -317,7 +410,7 @@ static int decompress_data_block(char *in_buf, size_t in_len, char *out_buf, siz
     error = inflateInit(&uc_stream);
     if (Z_OK != error)
     {
-        fprintf(logfile, "decompress_data_block: failed to init, error %d\n", error);
+        fprintf(logfile, "ungzip_data_block: failed to init, error %d\n", error);
         return EIO;
     }
     
@@ -327,27 +420,78 @@ static int decompress_data_block(char *in_buf, size_t in_len, char *out_buf, siz
     error = inflate(&uc_stream, Z_FINISH);
     if (Z_OK != error && Z_STREAM_END != error)
     {
-        fprintf(logfile, "decompress_data_block: failed to decompress, error %d\n", error);
+        fprintf(logfile, "ungzip_data_block: failed to decompress, error %d\n", error);
         return EIO;
     }
     
     error = inflateEnd(&uc_stream);
     if (Z_OK != error)
     {
-        fprintf(logfile, "decompress_data_block: failed to end, error %d\n", error);
+        fprintf(logfile, "ungzip_data_block: failed to end, error %d\n", error);
+        return EIO;
+    }
+    
+    return 0;
+}    
+
+#ifdef BZ2_SUPPORT
+static int unbzip_data_block(char *in_buf, INT64 in_len, char *out_buf, INT64 out_len)
+{
+    int error = 0;
+    bz_stream uc_stream;
+    
+    uc_stream.bzalloc = NULL;
+    uc_stream.bzfree = NULL;
+    uc_stream.opaque = NULL;
+    uc_stream.next_in = in_buf;
+    uc_stream.avail_in = in_len;
+
+    error = BZ2_bzDecompressInit(&uc_stream, 0, 0);
+    if (BZ_OK != error)
+    {
+        fprintf(logfile, "unbzip_data_block: failed to init, error %d\n", error);
+        return EIO;
+    }
+    
+    uc_stream.next_out = out_buf;
+    uc_stream.avail_out = out_len;
+
+    error = BZ2_bzDecompress(&uc_stream);
+    if (BZ_OK != error && BZ_STREAM_END != error)
+    {
+        fprintf(logfile, "unbzip_data_block: failed to decompress, error %d\n", error);
+        return EIO;
+    }
+    
+    error = BZ2_bzDecompressEnd(&uc_stream);
+    if (BZ_OK != error)
+    {
+        fprintf(logfile, "unbzip_data_block: failed to end, error %d\n", error);
         return EIO;
     }
     
     return 0;
 }    
+#endif
+
+static int decompress_data_block(char *in_buf, INT64 in_len, char *out_buf,
+                                 INT64 out_len, int compress_type)
+{
+#ifdef BZ2_SUPPORT
+    if (COMP_BZIP == compress_type)
+        return unbzip_data_block(in_buf, in_len, out_buf, out_len);
+    else
+#endif
+        return ungzip_data_block(in_buf, in_len, out_buf, out_len);
+}
 
-static int read_data_block(FILE *template_file)
+static int read_data_block(FILE *template_file, int compress_type)
 {
     char inbuf[1024];
-    off_t i = 0;
-    static off_t template_offset = -1;
-    off_t compressed_len = 0;
-    off_t uncompressed_len = 0;
+    INT64 i = 0;
+    static INT64 template_offset = -1;
+    INT64 compressed_len = 0;
+    INT64 uncompressed_len = 0;
     char *comp_buf = NULL;
     int read_num = 0;
     int error = 0;
@@ -409,7 +553,7 @@ static int read_data_block(FILE *template_file)
     }
 
     error = decompress_data_block(comp_buf, compressed_len,
-                                  zip_state.data_buf, uncompressed_len);
+                                  zip_state.data_buf, uncompressed_len, compress_type);
     if (error)
     {
         fprintf(logfile, "Unable to decompress data block, error %d\n", error);
@@ -418,22 +562,24 @@ static int read_data_block(FILE *template_file)
         
     template_offset += compressed_len;
     zip_state.buf_size = uncompressed_len;
-    zip_state.curr_offset = 0;
+    zip_state.offset_in_curr_buf = 0;
     free (comp_buf);
     return 0;
 }
 
-static int parse_data_block(size_t data_size, FILE *template_file, struct mk_MD5Context *context)
+static int skip_data_block(INT64 data_size, FILE *template_file, int compress_type)
 {
     int error = 0;
-    size_t remaining = data_size;
-    size_t size = 0;
+    INT64 remaining = data_size;
+    INT64 size = 0;
 
+    /* If we're coming in in the middle of the image, we'll need to
+       skip through some compressed data */
     while (remaining)
     {
         if (!zip_state.data_buf)
         {
-            error = read_data_block(template_file);
+            error = read_data_block(template_file, compress_type);
             if (error)
             {
                 fprintf(logfile, "Unable to decompress template data, error %d\n",
@@ -441,50 +587,105 @@ static int parse_data_block(size_t data_size, FILE *template_file, struct mk_MD5
                 return error;
             }
         }
-        size = MIN((zip_state.buf_size - zip_state.curr_offset), remaining);
-        fwrite(&zip_state.data_buf[zip_state.curr_offset], 1, size, stdout);
-        if (!quick)
-            mk_MD5Update(context, &zip_state.data_buf[zip_state.curr_offset], size);
-        zip_state.curr_offset += size;
+        size = MIN((zip_state.buf_size - zip_state.offset_in_curr_buf), remaining);
+        zip_state.offset_in_curr_buf += size;
         remaining -= size;
         
-        if (zip_state.curr_offset == zip_state.buf_size)
+        if (zip_state.offset_in_curr_buf == zip_state.buf_size)
         {
             free(zip_state.data_buf);
             zip_state.data_buf = NULL;
         }
     }
     
-    fprintf(logfile, "parse_data_block: wrote %d bytes of unmatched data\n", data_size);
+    fprintf(logfile, "skip_data_block: skipped %lld bytes of unmatched data\n", data_size);
     return error;
 }
 
-static int parse_file_block(off_t file_size, char *md5, struct mk_MD5Context *image_context)
+static int parse_data_block(INT64 data_size, FILE *template_file,
+                            struct mk_MD5Context *context, int compress_type)
+{
+    int error = 0;
+    INT64 remaining = data_size;
+    INT64 size = 0;
+    int out_size = 0;
+
+    while (remaining)
+    {
+        if (!zip_state.data_buf)
+        {
+            error = read_data_block(template_file, compress_type);
+            if (error)
+            {
+                fprintf(logfile, "Unable to decompress template data, error %d\n",
+                        error);
+                return error;
+            }
+        }
+        size = MIN((zip_state.buf_size - zip_state.offset_in_curr_buf), remaining);
+        out_size = fwrite(&zip_state.data_buf[zip_state.offset_in_curr_buf], size, 1, outfile);
+        if (!out_size)
+        {
+            fprintf(logfile, "parse_data_block: fwrite %lld failed with error %d; aborting\n", size, ferror(outfile));
+            return ferror(outfile);
+        }
+
+        if (verbose)
+            display_progress(outfile, "template data");
+
+        if (!quick)
+            mk_MD5Update(context, &zip_state.data_buf[zip_state.offset_in_curr_buf], size);
+        zip_state.offset_in_curr_buf += size;
+        remaining -= size;
+        
+        if (zip_state.offset_in_curr_buf == zip_state.buf_size)
+        {
+            free(zip_state.data_buf);
+            zip_state.data_buf = NULL;
+        }
+    }
+    if (verbose > 1)
+        fprintf(logfile, "parse_data_block: wrote %lld bytes of unmatched data\n", data_size);
+    return error;
+}
+
+static int parse_file_block(INT64 offset, INT64 data_size, INT64 file_size, 
+                            char *md5, struct mk_MD5Context *image_context,
+                            char *missing)
 {
     char *base64_md5 = base64_dump(md5, 16);
     FILE *input_file = NULL;
     char buf[BUF_SIZE];
-    size_t remaining = file_size;
+    INT64 remaining = data_size;
     int num_read = 0;
     struct mk_MD5Context file_context;
     char file_md5[16];
-    jigdo_list_t *jigdo_list_current = jigdo_list_head;
+    md5_list_t *md5_list_current = md5_list_head;
+    int out_size = 0;
     
     if (!quick)
         mk_MD5Init(&file_context);
 
-    while (jigdo_list_current)
+    while (md5_list_current)
     {        
-        if ( (jigdo_list_current->file_size == file_size) &&
-             (!memcmp(jigdo_list_current->md5, base64_md5, 16) ) )
+        if ( (md5_list_current->file_size == file_size) &&
+             (!memcmp(md5_list_current->md5, base64_md5, 16) ) )
         {
-            input_file = fopen(jigdo_list_current->full_path, "rb");
+            input_file = fopen(md5_list_current->full_path, "rb");
             if (!input_file)
             {
                 fprintf(logfile, "Unable to open mirror file %s, error %d\n",
-                        jigdo_list_current->full_path, errno);
+                        md5_list_current->full_path, errno);
                 return errno;
             }
+
+            if (missing)
+            {
+                fclose(input_file);
+                return 0;
+            }
+            
+            fseek(input_file, offset, SEEK_SET);
             while (remaining)
             {
                 int size = MIN(BUF_SIZE, remaining);
@@ -493,8 +694,8 @@ static int parse_file_block(off_t file_size, char *md5, struct mk_MD5Context *im
                 num_read = fread(buf, size, 1, input_file);
                 if (!num_read)
                 {
-                    fprintf(logfile, "Unable to open mirror file %s, error %d\n",
-                            jigdo_list_current->full_path, errno);
+                    fprintf(logfile, "Unable to read from mirror file %s, error %d (offset %ld, length %d)\n",
+                            md5_list_current->full_path, errno, ftell(input_file), size);
                     fclose(input_file);
                     return errno;
                 }
@@ -504,12 +705,21 @@ static int parse_file_block(off_t file_size, char *md5, struct mk_MD5Context *im
                     mk_MD5Update(&file_context, buf, size);
                 }
             
-                fwrite(buf, size, 1, stdout);
+                out_size = fwrite(buf, size, 1, outfile);
+                if (!out_size)
+                {
+                    fprintf(logfile, "parse_file_block: fwrite %d failed with error %d; aborting\n", size, ferror(outfile));
+                    return ferror(outfile);
+                }
             
+                if (verbose)
+                    display_progress(outfile, file_base_name(md5_list_current->full_path));
+
                 remaining -= size;
             }
-            fprintf(logfile, "parse_file_block: wrote %lld bytes of data from %s\n",
-                    file_size, jigdo_list_current->full_path);
+            if (verbose > 1)
+                fprintf(logfile, "parse_file_block: wrote %lld bytes of data from %s\n",
+                    file_size, md5_list_current->full_path);
             fclose(input_file);
 
             if (!quick)
@@ -518,7 +728,7 @@ static int parse_file_block(off_t file_size, char *md5, struct mk_MD5Context *im
         
                 if (memcmp(file_md5, md5, 16))
                 {
-                    fprintf(logfile, "MD5 MISMATCH for file %s\n", jigdo_list_current->full_path);
+                    fprintf(logfile, "MD5 MISMATCH for file %s\n", md5_list_current->full_path);
                     fprintf(logfile, "    template looking for %s\n", md5);
                     fprintf(logfile, "    file in mirror is    %s\n", file_md5);
                     return EINVAL;
@@ -526,25 +736,34 @@ static int parse_file_block(off_t file_size, char *md5, struct mk_MD5Context *im
             }
             return 0;
         }
-        jigdo_list_current = jigdo_list_current->next;
+        if ( missing &&
+             (MISSING == md5_list_current->file_size) &&
+             (!memcmp(md5_list_current->md5, base64_md5, 16) ) )
+        {
+            write_missing_entry(missing, md5_list_current->full_path);
+            return 0;
+        }
+        md5_list_current = md5_list_current->next;
     }
     return ENOENT;
 }
 
-static int parse_template_file(char *filename)
+static int parse_template_file(char *filename, int sizeonly, char *missing, char *output_name)
 {
-    off_t offset = 0;
-    off_t bytes = 0;
+    INT64 template_offset = 0;
+    INT64 bytes = 0;
     unsigned char *buf = NULL;
     FILE *file = NULL;
-    off_t file_size = 0;
-    off_t desc_start = 0;
-    off_t image_length = 0;
-    off_t written_length = 0;
+    INT64 file_size = 0;
+    INT64 desc_start = 0;
+    INT64 written_length = 0;
+    INT64 output_offset = 0;
     int i = 0;
     int error = 0;
     struct mk_MD5Context template_context;
     unsigned char image_md5sum[16];
+
+    zip_state.total_offset = 0;
     
     file = fopen(filename, "rb");
     if (!file)
@@ -577,15 +796,26 @@ static int parse_template_file(char *filename)
         fclose(file);
         return EINVAL;
     }
-    image_length = read_le48(&buf[1]);
-    fprintf(logfile, "Image is %lld bytes\n", image_length);
-    fprintf(logfile, "Image MD5 is ");
-    for (i = 0; i < 16; i++)
-        fprintf(logfile, "%2.2x", buf[i+7]);
-    fprintf(logfile, "\n");
 
-    /* Now seek back to the start of the desc block and start
-       assembling the image */
+    if (sizeonly)
+    {
+        fclose(file);
+        printf("%lld\n", read_le48(&buf[1]));
+        return 0;
+    }
+
+    if (verbose)
+    {
+        fprintf(logfile, "Image should be %lld bytes\n", read_le48(&buf[1]));
+        fprintf(logfile, "Image MD5 should be ");
+        for (i = 0; i < 16; i++)
+            fprintf(logfile, "%2.2x", buf[i+7]);
+        fprintf(logfile, "\n");
+    }
+
+    out_size = read_le48(&buf[1]);
+    
+    /* Now seek back to the start of the desc block */
     fseek(file, desc_start, SEEK_SET);
     fread(buf, 10, 1, file);
     if (strncmp(buf, "DESC", 4))
@@ -605,14 +835,33 @@ static int parse_template_file(char *filename)
 
     if (!quick)
         mk_MD5Init(&template_context);
-    offset = desc_start + 10;
+    template_offset = desc_start + 10;
+
+    if (1 == verbose)
+        fprintf(logfile, "Creating ISO image %s\n", output_name);
+
+    /* Main loop - walk through the template file and expand each entry we find */
     while (1)
     {
-        if (offset >= (file_size - 33))
-            break; /* Finished! */
+        INT64 extent_size;
+        INT64 skip = 0;
+        INT64 read_length = 0;
 
-        fseek(file, offset, SEEK_SET);
-        bytes = fread(buf, (MIN (BUF_SIZE, file_size - offset)), 1, file);
+        if (template_offset >= (file_size - 33))
+        {
+            if (verbose > 1)
+                fprintf(logfile, "Reached end of template file\n");
+            break; /* Finished! */
+        }
+        
+        if (output_offset > end_offset) /* Past the range we were asked for */
+        {
+            fprintf(logfile, "Reached end of range requested\n");            
+            break;
+        }
+        
+        fseek(file, template_offset, SEEK_SET);
+        bytes = fread(buf, (MIN (BUF_SIZE, file_size - template_offset)), 1, file);
         if (1 != bytes)
         {
             fprintf(logfile, "Failed to read template file!\n");
@@ -620,48 +869,85 @@ static int parse_template_file(char *filename)
             return EINVAL;
         }
         
+        extent_size = read_le48(&buf[1]);
+        read_length = extent_size;
+        
+        if (start_offset > output_offset)
+            skip = start_offset - output_offset;
+        if ((output_offset + extent_size) > end_offset)
+            read_length -= (output_offset + extent_size - end_offset - 1);
+        read_length -= skip;
+        
         switch (buf[0])
         {
-            case 2: /* unmatched data */
-                error = parse_data_block(read_le48(&buf[1]), file, &template_context);
-                if (error)
+            
+            case 2: /* unmatched data, gzip */
+            case 8: /* unmatched data, bzip2 */
+                template_offset += 7;
+                if (missing)
+                    break;
+                if ((output_offset + extent_size) >= start_offset)
                 {
-                    fprintf(logfile, "Unable to read data block, error %d\n", error);
-                    fclose(file);
-                    return error;
+                    if (skip)
+                        error = skip_data_block(skip, file, buf[0]);
+                    if (error)
+                    {
+                        fprintf(logfile, "Unable to read data block to skip, error %d\n", error);
+                        fclose(file);
+                        return error;
+                    }
+                    error = parse_data_block(read_length, file, &template_context, buf[0]);
+                    if (error)
+                    {
+                        fprintf(logfile, "Unable to read data block, error %d\n", error);
+                        fclose(file);
+                        return error;
+                    }
+                    written_length += read_length;
                 }
-                offset += 7;
-                written_length += read_le48(&buf[1]);
+                else
+                    error = skip_data_block(extent_size, file, buf[0]);
                 break;
             case 6:
-                error = parse_file_block(read_le48(&buf[1]), &buf[15], &template_context);
-                if (error)
+                template_offset += 31;
+                if ((output_offset + extent_size) >= start_offset)
                 {
-                    fprintf(logfile, "Unable to read file block, error %d\n", error);
-                    fclose(file);
-                    return error;
+                    error = parse_file_block(skip, read_length, extent_size, &buf[15], &template_context, missing);
+                    if (error)
+                    {
+                        fprintf(logfile, "Unable to read file block, error %d\n", error);
+                        fclose(file);
+                        return error;
+                    }
+                    written_length += read_length;
                 }
-                offset += 31;
-                written_length += read_le48(&buf[1]);
                 break;
             default:
                 fprintf(logfile, "Unknown block type %d!\n", buf[0]);
                 fclose(file);
                 return EINVAL;
         }
+        output_offset += extent_size;
     }
+
+    if (missing && missing_file)
+        return ENOENT;
     
     fclose(file);
-    if (!quick)
+    if (verbose)
     {
-        mk_MD5Final (image_md5sum, &template_context);
-        fprintf(logfile, "Output image MD5 is ");
-        for (i = 0; i < 16; i++)
-            fprintf(logfile, "%2.2x", image_md5sum[i]);
         fprintf(logfile, "\n");
+        if (!quick)
+        {
+            mk_MD5Final (image_md5sum, &template_context);
+            fprintf(logfile, "Output image MD5 is ");
+            for (i = 0; i < 16; i++)
+                fprintf(logfile, "%2.2x", image_md5sum[i]);
+            fprintf(logfile, "\n");
+        }
+        fprintf(logfile, "Output image length is %lld\n", written_length);
     }
-    fprintf(logfile, "Output image length is %lld\n", written_length);
-
+    
     return 0;
 }
 
@@ -669,41 +955,71 @@ static void usage(char *progname)
 {
     printf("%s [OPTIONS]\n\n", progname);
     printf(" Options:\n");
+    printf(" -f <MD5 name>       Specify an input MD5 file. MD5s must be in jigdo's\n");
+       printf("                     pseudo-base64 format\n");
     printf(" -j <jigdo name>     Specify the input jigdo file\n");
     printf(" -t <template name>  Specify the input template file\n");
     printf(" -m <item=path>      Map <item> to <path> to find the files in the mirror\n");
+       printf(" -M <missing name>   Rather than try to build the image, just check that\n");
+       printf("                     all the needed files are available. If any are missing,\n");
+       printf("                     list them in this file.\n");
+       printf(" -v                  Make the output logging more verbose\n");
     printf(" -l <logfile>        Specify a logfile to append to.\n");
     printf("                     If not specified, will log to stderr\n");
+    printf(" -o <outfile>        Specify a file to write the ISO image to.\n");
+    printf("                     If not specified, will write to stdout\n");
     printf(" -q                  Quick mode. Don't check MD5sums. Dangerous!\n");
+    printf(" -s <bytenum>        Start byte number; will start at 0 if not specified\n");
+    printf(" -e <bytenum>        End byte number; will end at EOF if not specified\n");    
+    printf(" -z                  Don't attempt to rebuild the image; simply print its\n");
+    printf("                     size in bytes\n");
 }
 
 int main(int argc, char **argv)
 {
     char *template_filename = NULL;
     char *jigdo_filename = NULL;
+    char *md5_filename = NULL;
+    char *missing_filename = NULL;
+    char *output_name = NULL;
     int c = -1;
     int error = 0;
+    int sizeonly = 0;
 
     logfile = stderr;
+    outfile = stdout;
 
     bzero(&zip_state, sizeof(zip_state));
 
     while(1)
     {
-        c = getopt(argc, argv, ":ql:j:t:m:h?");
+        c = getopt(argc, argv, ":ql:o:j:t:f:m:M:h?s:e:zv");
         if (-1 == c)
             break;
         
         switch(c)
         {
+            case 'v':
+                verbose++;
+                break;
             case 'q':
                 quick = 1;
                 break;
             case 'l':
-                logfile = fopen(optarg, "wb");
+                logfile = fopen(optarg, "ab");
                 if (!logfile)
                 {
-                    fprintf(logfile, "Unable to open log file %s\n", optarg);
+                    fprintf(stderr, "Unable to open log file %s\n", optarg);
+                    return errno;
+                }
+                setlinebuf(logfile);
+                break;
+            case 'o':
+                output_name = optarg;
+                outfile = fopen(output_name, "wb");
+                if (!outfile)
+                {
+                    fprintf(stderr, "Unable to open output file %s\n", optarg);
                     return errno;
                 }
                 break;
@@ -725,11 +1041,23 @@ int main(int argc, char **argv)
                 /* else */
                 template_filename = optarg;
                 break;
+            case 'f':
+                if (md5_filename)
+                {
+                    fprintf(logfile, "Can only specify one MD5 file!\n");
+                    return EINVAL;
+                }
+                /* else */
+                md5_filename = optarg;
+                break;                
             case 'm':
                 error = add_match_entry(strdup(optarg));
                 if (error)
                     return error;
                 break;
+            case 'M':
+                missing_filename = optarg;
+                break;
             case ':':
                 fprintf(logfile, "Missing argument!\n");
                 return EINVAL;
@@ -739,15 +1067,33 @@ int main(int argc, char **argv)
                 usage(argv[0]);
                 return 0;
                 break;
+            case 's':
+                start_offset = strtoull(optarg, NULL, 10);
+                if (start_offset != 0)
+                    quick = 1;
+                break;
+            case 'e':
+                end_offset = strtoull(optarg, NULL, 10);
+                if (end_offset != 0)
+                    quick = 1;
+                break;
+            case 'z':
+                sizeonly = 1;
+                break;
             default:
                 fprintf(logfile, "Unknown option!\n");
                 return EINVAL;
         }
     }
 
-    if (NULL == jigdo_filename)
+    if (0 == end_offset)
+        end_offset = LLONG_MAX;
+
+    if ((NULL == jigdo_filename) &&
+        (NULL == md5_filename) && 
+        !sizeonly)
     {
-        fprintf(logfile, "No jigdo file specified!\n");
+        fprintf(logfile, "No jigdo file or MD5 file specified!\n");
         usage(argv[0]);
         return EINVAL;
     }
@@ -759,21 +1105,41 @@ int main(int argc, char **argv)
         return EINVAL;
     }    
 
-    /* Build up a list of file mappings */
-    error = parse_jigdo_file(jigdo_filename);
-    if (error)
+    if (md5_filename)
     {
-        fprintf(logfile, "Unable to parse the jigdo file %s\n", jigdo_filename);
-        return error;
+        /* Build up a list of the files we've been fed */
+        error = parse_md5_file(md5_filename);
+        if (error)
+        {
+            fprintf(logfile, "Unable to parse the MD5 file %s\n", md5_filename);
+            return error;
+        }
     }
 
-    /* Read the template file and actually build the image to stdout */
-    error = parse_template_file(template_filename);
+    if (jigdo_filename)
+    {
+        /* Build up a list of file mappings */
+        error = parse_jigdo_file(jigdo_filename);
+        if (error)
+        {
+            fprintf(logfile, "Unable to parse the jigdo file %s\n", jigdo_filename);
+            return error;
+        }
+    }
+
+    if (!output_name)
+        output_name = "to stdout";
+    /* Read the template file and actually build the image to <outfile> */
+    error = parse_template_file(template_filename, sizeonly, missing_filename, output_name);
     if (error)
     {
         fprintf(logfile, "Unable to recreate image from template file %s\n", template_filename);
+        if (missing_filename)
+            fprintf(logfile, "%s contains the list of missing files\n", missing_filename);
         return error;
     }        
 
+    fclose(logfile);
     return 0;
 }
+