patch genisoimage multi extent
[platform/upstream/cdrkit.git] / genisoimage / jte.c
1 /*
2  * jte.c
3  *
4  * Copyright (c) 2004-2006 Steve McIntyre <steve@einval.com>
5  *
6  * Implementation of the Jigdo Template Engine - make jigdo files
7  * directly when making ISO images
8  *
9  * GNU GPL v2
10  */
11
12 #include <mconfig.h>
13 #include "genisoimage.h"
14 #include <timedefs.h>
15 #include <fctldefs.h>
16 #include <zlib.h>
17 #include <bzlib.h>
18 #include <regex.h>
19 #ifdef SORTING
20 #include "match.h"
21 #endif /* SORTING */
22 #include <errno.h>
23 #include <schily.h>
24 #ifdef DVD_VIDEO
25 #include "dvd_reader.h"
26 #include "dvd_file.h"
27 #include "ifo_read.h"
28 #include "endianconv.h"
29 #include "checksum.h"
30 #endif
31 #ifdef APPLE_HYB
32 #include <ctype.h>
33 #endif
34
35 #ifdef  VMS
36 #include "vms.h"
37 #endif
38
39 /* Different types used in building our state list below */
40 #define JTET_FILE_MATCH 1
41 #define JTET_NOMATCH    2
42
43 #define JTE_VER_MAJOR     0x0001
44 #define JTE_VER_MINOR     0x0013
45 #define JTE_NAME          "JTE"
46 #define JTE_COMMENT       "JTE at http://www.einval.com/~steve/software/JTE/ ; jigdo at http://atterer.org/jigdo/"
47
48 #define JIGDO_TEMPLATE_VERSION "1.1"
49
50 /*      
51         Simple list to hold the results of -jigdo-exclude and
52         -jigdo-force-match command line options. Seems easiest to do this
53         using regexps.
54 */
55 struct path_match
56 {
57     regex_t  match_pattern;
58     char    *match_rule;
59     struct path_match *next;
60 };
61
62 /* List of mappings e.g. Debian=/mirror/debian */
63 struct path_mapping
64 {
65     char                *from;
66     char                *to;
67     struct path_mapping *next;
68 };
69
70 FILE    *jtjigdo = NULL;       /* File handle used throughout for the jigdo file */
71 FILE    *jttemplate = NULL;    /* File handle used throughout for the template file */
72 char    *jjigdo_out = NULL;    /* Output name for jigdo .jigdo file; NULL means don't do it */
73 char    *jtemplate_out = NULL; /* Output name for jigdo template file; NULL means don't do it */
74 char    *jmd5_list = NULL;     /* Name of file to use for MD5 checking */
75 int      jte_min_size = MIN_JIGDO_FILE_SIZE;
76 jtc_t    jte_template_compression = JTE_TEMP_GZIP;
77 struct  path_match *exclude_list = NULL;
78 struct  path_match *include_list = NULL;
79 struct  path_mapping  *map_list = NULL;
80 unsigned long long template_size = 0;
81 unsigned long long image_size = 0;
82 int checksum_algo_iso = (CHECK_MD5_USED | \
83                          CHECK_SHA1_USED | \
84                          CHECK_SHA256_USED | \
85                          CHECK_SHA512_USED);
86 int checksum_algo_tmpl = CHECK_MD5_USED;
87
88 static checksum_context_t *iso_context = NULL;
89 static checksum_context_t *template_context = NULL;
90
91 /* List of files that we've seen, ready to write into the template and
92    jigdo files */
93 typedef struct _file_entry
94 {
95     unsigned char       md5[16];
96     off_t               file_length;
97     unsigned long long  rsyncsum;
98     char               *filename;
99 } file_entry_t;
100
101 typedef struct _unmatched_entry
102 {
103     off_t uncompressed_length;
104 } unmatched_entry_t;    
105
106 typedef struct _entry
107 {
108     int entry_type; /* JTET_TYPE as above */
109     struct _entry *next;
110     union
111     {
112         file_entry_t      file;
113         unmatched_entry_t chunk;
114     } data;
115 } entry_t;
116
117 typedef struct _jigdo_file_entry
118 {
119     unsigned char type;
120     unsigned char fileLen[6];
121     unsigned char fileRsync[8];
122     unsigned char fileMD5[16];
123 } jigdo_file_entry_t;
124
125 typedef struct _jigdo_chunk_entry
126 {
127     unsigned char type;
128     unsigned char skipLen[6];
129 } jigdo_chunk_entry_t;
130
131 typedef struct _jigdo_image_entry
132 {
133     unsigned char type;
134     unsigned char imageLen[6];
135     unsigned char imageMD5[16];
136     unsigned char blockLen[4];
137 } jigdo_image_entry_t;
138
139 typedef struct _md5_list_entry
140 {
141     struct _md5_list_entry *next;
142     unsigned char       MD5[16];
143     unsigned long long  size;
144     char               *filename;
145 } md5_list_entry_t;
146     
147 entry_t *entry_list = NULL;
148 entry_t *entry_last = NULL;
149 FILE    *t_file = NULL;
150 FILE    *j_file = NULL;
151 int      num_matches = 0;
152 int      num_chunks = 0;
153 md5_list_entry_t *md5_list = NULL;
154 md5_list_entry_t *md5_last = NULL;
155
156 /* Grab the file component from a full path */
157 static char *file_base_name(char *path)
158 {
159     char *endptr = path;
160     char *ptr = path;
161     
162     while (*ptr != '\0')
163     {
164         if ('/' == *ptr)
165             endptr = ++ptr;
166         else
167             ++ptr;
168     }
169     return endptr;
170 }
171
172 /* Build the list of exclusion regexps */
173 extern int jte_add_exclude(char *pattern)
174 {
175     struct path_match *new = NULL;
176     
177     new = malloc(sizeof *new);
178     if (!new)
179         return ENOMEM;    
180     
181     regcomp(&new->match_pattern, pattern, REG_NEWLINE);
182         new->match_rule = pattern;
183
184     /* Order on the exclude list doesn't matter! */
185         new->next = exclude_list;
186
187     exclude_list = new;
188     return 0;
189 }
190
191 /* Check if the file should be excluded because of a filename match. 1
192    means exclude, 0 means not */
193 static int check_exclude_by_name(char *filename, char **matched)
194 {
195     struct path_match *ptr = exclude_list;
196     regmatch_t pmatch[1];
197     int i = 0;
198
199     while (ptr)
200     {
201         if (!regexec(&ptr->match_pattern, filename, 1, pmatch, 0))
202         {
203             *matched = ptr->match_rule;
204             return 1;
205         }
206         ptr = ptr->next;
207     }
208     
209     /* Not matched, so return 0 */
210     return 0;
211 }
212
213 /* Build the list of required inclusion regexps */
214 extern int jte_add_include(char *pattern)
215 {
216     struct path_match *new = NULL;
217     
218     new = malloc(sizeof *new);
219     if (!new)
220         return ENOMEM;    
221     
222     regcomp(&new->match_pattern, pattern, REG_NEWLINE);
223         new->match_rule = pattern;
224
225     /* Order on the include list doesn't matter! */
226         new->next = include_list;
227
228     include_list = new;
229     return 0;
230 }
231
232 /* Check if a file has to be MD5-matched to be valid. If we get called
233    here, we've failed to match any of the MD5 entries we were
234    given. If the path to the filename matches one of the paths in our
235    list, clearly it must have been corrupted. Abort with an error. */
236 static void check_md5_file_match(char *filename)
237 {
238     struct path_match *ptr = include_list;
239     regmatch_t pmatch[1];
240     int i = 0;
241
242     while (ptr)
243     {
244         if (!regexec(&ptr->match_pattern, filename, 1, pmatch, 0))
245         {
246 #ifdef  USE_LIBSCHILY
247                         comerr("File %s should have matched an MD5 entry, but didn't! (Rule '%s')\n", filename, ptr->match_rule);
248 #else
249                         fprintf(stderr, "File %s should have matched an MD5 entry, but didn't! (Rule '%s')\n", filename, ptr->match_rule);
250                         exit(1);
251 #endif
252                 }
253         ptr = ptr->next;
254     }
255 }    
256
257 /* Should we list a file separately in the jigdo output, or should we
258    just dump it into the template file as binary data? Three things
259    cases to look for here:
260
261    1. Small files are better simply folded in, as they take less space that way.
262
263    2. Files in /doc (for example) may change in the archive all the
264       time and it's better to not have to fetch snapshot copies if we
265       can avoid it.      
266
267    3. Files living in specified paths *must* match an entry in the
268       md5-list, or they must have been corrupted. If we find a corrupt
269       file, bail out with an error.
270
271 */
272 extern int list_file_in_jigdo(char *filename, off_t size, char **realname, unsigned char md5[16])
273 {
274     char *matched_rule;
275     md5_list_entry_t *entry = md5_list;
276     int md5sum_done = 0;
277     
278     if (!jtemplate_out)
279         return 0;
280
281     memset(md5, 0, sizeof(md5));
282
283     /* Cheaper to check file size first */
284     if (size < jte_min_size)
285     {
286         if (verbose > 1)
287             fprintf(stderr, "Jigdo-ignoring file %s; it's too small\n", filename);
288         return 0;
289     }
290     
291     /* Now check the excluded list by name */
292     if (check_exclude_by_name(filename, &matched_rule))
293     {
294         if (verbose > 1)
295             fprintf(stderr, "Jigdo-ignoring file %s; it's covered in the exclude list by \"%s\"\n", filename, matched_rule);
296         return 0;
297     }
298
299     /* Check to see if the file is in our md5 list. Check three things:
300        
301        1. the size
302        2. the filename
303        3. (only if the first 2 match) the md5sum
304
305        If we get a match for all three, include the file and return
306        the full path to the file that we have gleaned from the mirror.
307     */
308
309     while (entry)
310     {
311         if (size == entry->size)
312         {
313             if (!strcmp(file_base_name(filename), file_base_name(entry->filename)))
314             {
315                 if (!md5sum_done)
316                 {
317                     calculate_md5sum(filename, size, md5);
318                     md5sum_done = 1;
319                 }
320                 if (!memcmp(md5, entry->MD5, sizeof(entry->MD5)))
321                 {
322                     *realname = entry->filename;
323                     return 1;
324                 }
325             }
326         }
327         entry = entry->next;
328     }
329
330     /* We haven't found an entry in our MD5 list to match this
331      * file. If we should have done, complain and bail out. */
332     check_md5_file_match(filename);
333     return 0;
334 }
335
336 /* Add a mapping of pathnames (e.g. Debian=/mirror/debian). We should
337    be passed TO=FROM here */
338 extern int jte_add_mapping(char *arg)
339 {
340     int error = 0;
341     struct path_mapping *new = NULL;
342     struct path_mapping *entry = NULL;
343     char *p = arg;
344     char *from = NULL;
345     char *to = NULL;
346
347     /* Find the "=" in the string passed. Set it to NULL and we can
348        use the string in-place */
349     while (*p)
350     {
351         if ('=' == *p)
352         {
353             *p = 0;
354             p++;
355             to = arg;
356             from = p;
357         }
358         p++;
359     }
360     if (!from || !strlen(from) || !to || !strlen(to))
361         return EINVAL;
362     
363     new = malloc(sizeof(*new));
364     if (!new)
365         return ENOMEM;
366     
367     new->from = from;
368     new->to = to;
369     new->next = NULL;
370
371     if (verbose > 0)
372         fprintf(stderr, "Adding mapping from %s to %s for the jigdo file\n", from, to);
373     if (!map_list)
374         map_list = new;
375     else
376     {
377         /* Order is important; add to the end of the list */
378         entry = map_list;
379         while (NULL != entry->next)
380             entry = entry->next;
381         entry->next = new;
382     }
383     return 0;
384 }
385
386 /* Check if the filename should be remapped; if so map it, otherwise
387    return the original name. */
388 static char *remap_filename(char *filename)
389 {
390     char *new_name = filename;
391     struct path_mapping *entry = map_list;
392     
393     while (entry)
394     {
395         if (!strncmp(filename, entry->from, strlen(entry->from)))
396         {
397             new_name = calloc(1, 2 + strlen(filename) + strlen(entry->to) - strlen(entry->from));
398             if (!new_name)
399             {
400                 fprintf(stderr, "Failed to malloc new filename; abort!\n");
401                 exit(1);
402             }
403             sprintf(new_name, "%s:%s", entry->to, &filename[strlen(entry->from)]);
404             return new_name;
405         }
406         entry = entry->next;
407     }
408
409     /* No mapping in effect */
410     return strdup(filename);
411 }    
412
413 /* Write data to the template file and update the MD5 sum */
414 static size_t template_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
415 {
416     checksum_update(template_context, ptr, size * nmemb);
417     template_size += (unsigned long long)size * nmemb;
418     return fwrite(ptr, size, nmemb, stream);
419 }
420
421 /* Create a new template file and initialise it */
422 static void write_template_header()
423 {
424     char buf[2048];
425     int i = 0;
426     char *p = buf;
427
428     memset(buf, 0, sizeof(buf));
429
430     template_context = checksum_init_context(checksum_algo_tmpl, "template");
431     if (!template_context)
432     {
433 #ifdef  USE_LIBSCHILY
434         comerr("cannot allocate template checksum contexts\n");
435 #else
436         fprintf(stderr, "cannot allocate template checksum contexts\n");
437         exit(1);
438 #endif
439     }
440     
441     i += sprintf(p, "JigsawDownload template %s %s/%d.%d \r\n",
442                  JIGDO_TEMPLATE_VERSION, JTE_NAME, JTE_VER_MAJOR, JTE_VER_MINOR);
443     p = &buf[i];
444
445     i += sprintf(p, "%s \r\n", JTE_COMMENT);
446     p = &buf[i];
447
448     i += sprintf(p, "\r\n");
449     template_fwrite(buf, i, 1, t_file);
450 }
451
452 /* Read the MD5 list and build a list in memory for us to use later */
453 static void add_md5_entry(unsigned char *md5, unsigned long long size, char *filename)
454 {
455     int error = 0;
456     md5_list_entry_t *new = NULL;
457     
458     new = calloc(1, sizeof(md5_list_entry_t));
459     memcpy(new->MD5, md5, sizeof(new->MD5));
460     new->size = size;
461     new->filename = strdup(filename);
462     
463     /* Add to the end of the list */
464     if (NULL == md5_last)
465     {
466         md5_last = new;
467         md5_list = new;
468     }
469     else
470     {
471         md5_last->next = new;
472         md5_last = new;
473     }
474 }
475
476 /* Parse a 12-digit decimal number */
477 static unsigned long long parse_number(unsigned char in[12])
478 {
479     unsigned long long size = 0;
480     int i = 0;
481     
482     for (i = 0; i < 12; i++)
483     {
484         size *= 10;
485         if (isdigit(in[i]))
486             size += (in[i] - '0');
487     }
488
489     return size;
490 }
491     
492 /* Read the MD5 list and build a list in memory for us to use later
493    MD5 list format:
494
495    <---MD5--->  <--Size-->  <--Filename-->
496        32          12          remaining
497 */
498 static void parse_md5_list(void)
499 {
500     FILE *md5_file = NULL;
501     unsigned char buf[1024];
502     unsigned char md5[16];
503     char *filename = NULL;
504     unsigned char *numbuf = NULL;
505     int num_files = 0;
506     unsigned long long size = 0;
507
508     md5_file = fopen(jmd5_list, "rb");
509     if (!md5_file)
510     {
511 #ifdef  USE_LIBSCHILY
512         comerr("cannot read from MD5 list file '%s'\n", jmd5_list);
513 #else
514         fprintf(stderr, "cannot read from MD5 list file '%s'\n", jmd5_list);
515         exit(1);
516 #endif
517     }
518
519     memset(buf, 0, sizeof(buf));
520     while (fgets((char *)buf, sizeof(buf), md5_file))
521     {
522         numbuf = &buf[34];
523         filename = (char *)&buf[48];
524         /* Lose the trailing \n from the fgets() call */
525         if (buf[strlen((char *)buf)-1] == '\n')
526           buf[strlen((char *)buf)-1] = 0;
527
528         if (mk_MD5Parse(buf, md5))
529         {
530 #ifdef  USE_LIBSCHILY
531             comerr("cannot parse MD5 file '%s'\n", jmd5_list);
532 #else
533             fprintf(stderr, "cannot parse MD5 file '%s'\n", jmd5_list);
534             exit(1);
535 #endif
536         }
537         size = parse_number(numbuf);
538         add_md5_entry(md5, size, filename);
539         memset(buf, 0, sizeof(buf));
540         num_files++;
541     }
542     if (verbose > 0)
543         fprintf(stderr, "parse_md5_list: added MD5 checksums for %d files\n", num_files);
544     fclose(md5_file);
545 }
546
547 /* Initialise state and start the jigdo template file */
548 void write_jt_header(FILE *template_file, FILE *jigdo_file)
549 {
550     t_file = template_file;
551     j_file = jigdo_file;
552
553     /* Start checksum work for the image */
554     iso_context = checksum_init_context(checksum_algo_iso, "iso");
555     if (!iso_context)
556     {
557 #ifdef  USE_LIBSCHILY
558         comerr("cannot allocate iso checksum contexts\n");
559 #else
560         fprintf(stderr, "cannot allocate iso checksum contexts\n");
561         exit(1);
562 #endif
563     }
564
565     /* Start the template file */
566     write_template_header();
567
568     /* Load up the MD5 list if we've been given one */
569     if (jmd5_list)
570         parse_md5_list();
571 }
572
573 /* Compress and flush out a buffer full of template data */
574 static void flush_gzip_chunk(void *buffer, off_t size)
575 {
576     z_stream c_stream; /* compression stream */
577     unsigned char comp_size_out[6];
578     unsigned char uncomp_size_out[6];
579     off_t compressed_size_out = 0;
580     int err = 0;
581     unsigned char *comp_buf = NULL;
582
583     c_stream.zalloc = NULL;
584     c_stream.zfree = NULL;
585     c_stream.opaque = NULL;
586
587     err = deflateInit(&c_stream, Z_BEST_COMPRESSION);
588     comp_buf = malloc(2 * size); /* Worst case */
589     c_stream.next_out = comp_buf;
590     c_stream.avail_out = 2 * size;
591     c_stream.next_in = buffer;
592     c_stream.avail_in = size;
593     
594     err = deflate(&c_stream, Z_NO_FLUSH);
595     err = deflate(&c_stream, Z_FINISH);
596     
597     compressed_size_out = c_stream.total_out + 16;
598     err = deflateEnd(&c_stream);
599
600     template_fwrite("DATA", 4, 1, t_file);
601
602     write_le48(compressed_size_out, &comp_size_out[0]);
603     template_fwrite(comp_size_out, sizeof(comp_size_out), 1, t_file);
604
605     write_le48(size, &uncomp_size_out[0]);
606     template_fwrite(uncomp_size_out, sizeof(uncomp_size_out), 1, t_file);
607     
608     template_fwrite(comp_buf, c_stream.total_out, 1, t_file);
609     free(comp_buf);
610 }
611
612 /* Compress and flush out a buffer full of template data */
613 static void flush_bz2_chunk(void *buffer, off_t size)
614 {
615     bz_stream c_stream; /* compression stream */
616     unsigned char comp_size_out[6];
617     unsigned char uncomp_size_out[6];
618     off_t compressed_size_out = 0;
619     int err = 0;
620     unsigned char *comp_buf = NULL;
621
622     c_stream.bzalloc = NULL;
623     c_stream.bzfree = NULL;
624     c_stream.opaque = NULL;
625
626     err = BZ2_bzCompressInit(&c_stream, 9, 0, 0);
627     comp_buf = malloc(2 * size); /* Worst case */
628     c_stream.next_out = comp_buf;
629     c_stream.avail_out = 2 * size;
630     c_stream.next_in = buffer;
631     c_stream.avail_in = size;
632     
633     err = BZ2_bzCompress(&c_stream, BZ_FINISH);
634     
635     compressed_size_out = c_stream.total_out_lo32 + 16;
636     err = BZ2_bzCompressEnd(&c_stream);
637
638     template_fwrite("BZIP", 4, 1, t_file);
639
640     write_le48(compressed_size_out, &comp_size_out[0]);
641     template_fwrite(comp_size_out, sizeof(comp_size_out), 1, t_file);
642
643     write_le48(size, &uncomp_size_out[0]);
644     template_fwrite(uncomp_size_out, sizeof(uncomp_size_out), 1, t_file);
645     
646     template_fwrite(comp_buf, c_stream.total_out_lo32, 1, t_file);
647     free(comp_buf);
648 }
649
650 static void flush_compressed_chunk(void *buffer, off_t size)
651 {
652     if (jte_template_compression == JTE_TEMP_BZIP2)
653         flush_bz2_chunk(buffer, size);
654     else
655         flush_gzip_chunk(buffer, size);
656 }
657
658 /* Append to an existing data buffer, and compress/flush it if
659    necessary */
660 static void write_compressed_chunk(unsigned char *buffer, size_t size)
661 {
662     static unsigned char *uncomp_buf = NULL;
663         static size_t uncomp_size = 0;
664     static size_t uncomp_buf_used = 0;
665
666         if (!uncomp_buf)
667         {
668                 if (jte_template_compression == JTE_TEMP_BZIP2)
669                         uncomp_size = 900 * 1024;
670                 else
671                         uncomp_size = 1024 * 1024;
672                 uncomp_buf = malloc(uncomp_size);
673                 if (!uncomp_buf)
674                 {
675 #ifdef  USE_LIBSCHILY
676             comerr("failed to allocate %d bytes for template compression buffer\n", uncomp_size);
677 #else
678             fprintf(stderr, "failed to allocate %d bytes for template compression buffer\n", uncomp_size);
679             exit(1);
680 #endif
681                 }
682         }
683
684     if ((uncomp_buf_used + size) > uncomp_size)
685     {
686         flush_compressed_chunk(uncomp_buf, uncomp_buf_used);
687         uncomp_buf_used = 0;
688     }
689
690     if (!size) /* Signal a flush before we start writing the DESC entry */
691     {
692         flush_compressed_chunk(uncomp_buf, uncomp_buf_used);
693         return;
694     }
695     
696     if (!uncomp_buf_used)
697         memset(uncomp_buf, 0, uncomp_size);
698
699     while (size > uncomp_size)
700     {
701         flush_compressed_chunk(buffer, uncomp_size);
702         buffer += uncomp_size;
703         size -= uncomp_size;
704     }
705     memcpy(&uncomp_buf[uncomp_buf_used], buffer, size);
706     uncomp_buf_used += size;
707 }
708
709 /* Loop through the list of DESC entries that we've built up and
710    append them to the template file */
711 static void write_template_desc_entries(off_t image_len)
712 {
713     entry_t *entry = entry_list;
714     off_t desc_len = 0;
715     unsigned char out_len[6];
716     jigdo_image_entry_t jimage;
717
718     desc_len = 16 /* DESC + length twice */
719         + (sizeof(jigdo_file_entry_t) * num_matches)
720         + (sizeof(jigdo_chunk_entry_t) * num_chunks)
721         + sizeof(jigdo_image_entry_t);
722
723     write_le48(desc_len, &out_len[0]);
724     write_compressed_chunk(NULL, 0);
725     template_fwrite("DESC", 4, 1, t_file);
726     template_fwrite(out_len, sizeof(out_len), 1, t_file);
727     
728     while (entry)
729     {
730         switch (entry->entry_type)
731         {
732             case JTET_FILE_MATCH:
733             {
734                 jigdo_file_entry_t jfile;
735                 jfile.type = 6; /* Matched file */
736                 write_le48(entry->data.file.file_length, &jfile.fileLen[0]);
737                 write_le64(entry->data.file.rsyncsum, &jfile.fileRsync[0]);
738                 memcpy(jfile.fileMD5, entry->data.file.md5, sizeof(jfile.fileMD5));
739                 template_fwrite(&jfile, sizeof(jfile), 1, t_file);
740                 break;
741             }
742             case JTET_NOMATCH:
743             {
744                 jigdo_chunk_entry_t jchunk;
745                                 jchunk.type = 2; /* Raw data, compressed */
746                 write_le48(entry->data.chunk.uncompressed_length, &jchunk.skipLen[0]);
747                 template_fwrite(&jchunk, sizeof(jchunk), 1, t_file);
748                 break;
749             }
750         }
751         entry = entry->next;
752     }
753
754     jimage.type = 5;
755     write_le48(image_len, &jimage.imageLen[0]);
756     checksum_copy(iso_context, CHECK_MD5, &jimage.imageMD5[0]);
757     write_le32(MIN_JIGDO_FILE_SIZE, &jimage.blockLen[0]);
758     template_fwrite(&jimage, sizeof(jimage), 1, t_file);    
759     template_fwrite(out_len, sizeof(out_len), 1, t_file);
760 }
761
762 /* Dump a buffer in jigdo-style "base64" */
763 static char *base64_dump(unsigned char *buf, size_t buf_size)
764 {
765     const char *b64_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
766     int value = 0;
767     unsigned int i;
768     int bits = 0;
769     static char output_buffer[2048];
770     char *p = output_buffer;
771
772     memset(output_buffer, 0, sizeof(output_buffer));
773     if (buf_size >= (sizeof(output_buffer) * 6/8))
774     {
775         fprintf(stderr, "base64_dump: Buffer too small!\n");
776         exit(1);
777     }
778
779     for (i = 0; i < buf_size ; i++)
780     {
781         value = (value << 8) | buf[i];
782         bits += 2;
783         p += sprintf(p, "%c", b64_enc[(value >> bits) & 63U]);
784         if (bits >= 6) {
785             bits -= 6;
786             p += sprintf(p, "%c", b64_enc[(value >> bits) & 63U]);
787         }
788     }
789     if (bits > 0)
790     {
791         value <<= 6 - bits;
792         p += sprintf(p, "%c", b64_enc[value & 63U]);
793     }
794     return output_buffer;
795 }
796
797 /* Write the .jigdo file to match the .template we've just finished. */
798 static void write_jigdo_file(void)
799 {
800     unsigned char template_md5sum[16];
801     entry_t *entry = entry_list;
802     struct path_mapping *map = map_list;
803     int i = 0;
804     struct checksum_info *info = NULL;
805
806     checksum_final(template_context);
807     checksum_copy(template_context, CHECK_MD5, &template_md5sum[0]);
808
809     fprintf(j_file, "# JigsawDownload\n");
810     fprintf(j_file, "# See <http://atterer.org/jigdo/> for details about jigdo\n");
811     fprintf(j_file, "# See <http://www.einval.com/~steve/software/CD/JTE/> for details about JTE\n\n");
812     
813     fprintf(j_file, "[Jigdo]\n");
814     fprintf(j_file, "Version=%s\n", JIGDO_TEMPLATE_VERSION);
815     fprintf(j_file, "Generator=%s/%d.%d\n\n", JTE_NAME, JTE_VER_MAJOR, JTE_VER_MINOR);
816
817     fprintf(j_file, "[Image]\n");
818     fprintf(j_file, "Filename=%s\n", file_base_name(outfile));
819     fprintf(j_file, "Template=http://localhost/%s\n", jtemplate_out);
820
821     fprintf(j_file, "Template-MD5Sum=%s \n",
822             base64_dump(&template_md5sum[0], sizeof(template_md5sum)));
823
824     for (i = 0; i < NUM_CHECKSUMS; i++)
825     {
826         if (checksum_algo_tmpl & (1 << i))
827         {
828             info = checksum_information(i);
829             fprintf(j_file, "# Template Hex %sSum %s\n", info->name, checksum_hex(template_context, i));
830         }
831     }
832     fprintf(j_file, "# Template size %lld bytes\n", template_size);
833
834     for (i = 0; i < NUM_CHECKSUMS; i++)
835     {
836         if (checksum_algo_iso & (1 << i))
837         {
838             info = checksum_information(i);
839             fprintf(j_file, "# Image Hex %sSum %s\n", info->name, checksum_hex(iso_context, i));
840         }
841     }
842
843     fprintf(j_file, "# Image size %lld bytes\n\n", image_size);
844
845     fprintf(j_file, "[Parts]\n");
846     while (entry)
847     {
848         if (JTET_FILE_MATCH == entry->entry_type)
849         {
850             char *new_name = remap_filename(entry->data.file.filename);
851             fprintf(j_file, "%s=%s\n",
852                     base64_dump(&entry->data.file.md5[0], sizeof(entry->data.file.md5)),
853                     new_name);
854             free(new_name);
855         }
856         entry = entry->next;
857     }
858
859     fprintf(j_file, "\n[Servers]\n");
860     fflush(j_file);
861 }
862
863 /* Finish and flush state; for now:
864    
865    1. Dump the DESC blocks and the footer information in the jigdo template file
866    2. Write the jigdo .jigdo file containing file pointers
867 */
868 void write_jt_footer(void)
869 {
870     /* Finish calculating the image's checksum */
871     checksum_final(iso_context);
872
873     /* And calculate the image size */
874     image_size = (unsigned long long)SECTOR_SIZE * last_extent_written;
875
876     write_template_desc_entries(image_size);
877
878     write_jigdo_file();
879 }
880
881 /* Add a raw data entry to the list of extents; no file to match */
882 static void add_unmatched_entry(int uncompressed_length)
883 {
884     entry_t *new_entry = NULL;
885
886     /* Can we extend a previous non-match entry? */
887     if (entry_last && (JTET_NOMATCH == entry_last->entry_type))
888     {
889         entry_last->data.chunk.uncompressed_length += uncompressed_length;
890         return;
891     }
892
893     new_entry = calloc(1, sizeof(entry_t));
894     new_entry->entry_type = JTET_NOMATCH;
895     new_entry->next = NULL;
896     new_entry->data.chunk.uncompressed_length = uncompressed_length;
897
898     /* Add to the end of the list */
899     if (NULL == entry_last)
900     {
901         entry_last = new_entry;
902         entry_list = new_entry;
903     }
904     else
905     {
906         entry_last->next = new_entry;
907         entry_last = new_entry;
908     }
909     num_chunks++;
910 }
911
912 /* Add a file match entry to the list of extents */
913 static void add_file_entry(char *filename, off_t size, unsigned char *md5,
914                            unsigned long long rsyncsum)
915 {
916     entry_t *new_entry = NULL;
917
918     new_entry = calloc(1, sizeof(entry_t));
919     new_entry->entry_type = JTET_FILE_MATCH;
920     new_entry->next = NULL;
921     memcpy(new_entry->data.file.md5, md5, sizeof(new_entry->data.file.md5));
922     new_entry->data.file.file_length = size;
923     new_entry->data.file.rsyncsum = rsyncsum;
924     new_entry->data.file.filename = strdup(filename);
925
926     /* Add to the end of the list */
927     if (NULL == entry_last)
928     {
929         entry_last = new_entry;
930         entry_list = new_entry;
931     }
932     else
933     {
934         entry_last->next = new_entry;
935         entry_last = new_entry;
936     }
937     num_matches++;
938 }    
939
940 /* Cope with an unmatched block in the .iso file:
941
942    1. Write a compressed data chunk in the jigdo template file
943    2. Add an entry in our list of unmatched chunks for later */
944 void jtwrite(buffer, size, count, submode, islast)
945         void    *buffer;
946         int     size;
947         int     count;
948         int     submode;
949         BOOL    islast;
950 {
951 #ifdef  JTWRITE_DEBUG
952         if (count != 1 || (size % 2048) != 0)
953                 error("Count: %d, size: %d\n", count, size);
954 #endif
955
956     if (!jtemplate_out)
957         return;
958
959     /* Update the global image checksum */
960     checksum_update(iso_context, buffer, size * count);
961 //    mk_MD5Update(&iso_context, buffer, size*count);
962
963     /* Write a compressed version of the data to the template file,
964        and add a reference on the state list so we can write that
965        later. */
966     write_compressed_chunk(buffer, size*count);
967     add_unmatched_entry(size*count);
968 }
969
970 /* Cope with a file entry in the .iso file:
971
972    1. Read the file for the image's md5 checksum
973    2. Add an entry in our list of files to be written into the .jigdo later
974 */
975 void write_jt_match_record(char *filename, char *mirror_name, int sector_size, off_t size, unsigned char md5[16])
976 {
977     unsigned long long  tmp_size = 0;
978     char                buf[32768];
979     off_t               remain = size;
980         FILE               *infile = NULL;
981         int                     use = 0;
982     unsigned long long  rsync64_sum = 0;
983     int first_block = 1;
984
985     memset(buf, 0, sizeof(buf));
986
987     if ((infile = fopen(filename, "rb")) == NULL) {
988 #ifdef  USE_LIBSCHILY
989                 comerr("cannot open '%s'\n", filename);
990 #else
991 #ifndef HAVE_STRERROR
992                 fprintf(stderr, "cannot open '%s': (%d)\n",
993                                 filename, errno);
994 #else
995                 fprintf(stderr, "cannot open '%s': %s\n",
996                                 filename, strerror(errno));
997 #endif
998                 exit(1);
999 #endif
1000         }
1001
1002     while (remain > 0)
1003     {
1004         use = remain;
1005         if (remain > sizeof(buf))
1006             use = sizeof(buf);
1007                 if (fread(buf, 1, use, infile) == 0)
1008         {
1009 #ifdef  USE_LIBSCHILY
1010                         comerr("cannot read from '%s'\n", filename);
1011 #else
1012                         fprintf(stderr, "cannot read from '%s'\n", filename);
1013                         exit(1);
1014 #endif
1015                 }
1016         if (first_block)
1017             rsync64_sum = rsync64(buf, MIN_JIGDO_FILE_SIZE);
1018         checksum_update(iso_context, buf, use);
1019 //        mk_MD5Update(&iso_context, buf, use);
1020         remain -= use;
1021         first_block = 0;
1022     }
1023
1024     fclose(infile);
1025     
1026     /* Update the image checksum with any necessary padding data */
1027     if (size % sector_size)
1028     {
1029         int pad_size = sector_size - (size % sector_size);
1030         memset(buf, 0, pad_size);
1031         checksum_update(iso_context, buf, pad_size);
1032 //        mk_MD5Update(&iso_context, buf, pad_size);
1033     }
1034
1035     add_file_entry(mirror_name, size, &md5[0], rsync64_sum);
1036     if (size % sector_size)
1037     {
1038         int pad_size = sector_size - (size % sector_size);
1039         write_compressed_chunk(buf, pad_size);
1040         add_unmatched_entry(pad_size);
1041     }        
1042 }