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 */
file_base_name(char * 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 */
jte_add_exclude(char * pattern)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 */
check_exclude_by_name(char * filename,char ** matched)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 */
jte_add_include(char * pattern)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. */
check_md5_file_match(char * filename)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 */
list_file_in_jigdo(char * filename,off_t size,char ** realname,unsigned char md5[16])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 */
jte_add_mapping(char * arg)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. */
remap_filename(char * filename)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 */
template_fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream)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 */
write_template_header()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 */
add_md5_entry(unsigned char * md5,unsigned long long size,char * filename)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 */
parse_number(unsigned char in[12])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 */
parse_md5_list(void)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 */
write_jt_header(FILE * template_file,FILE * jigdo_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 */
flush_gzip_chunk(void * buffer,off_t size)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 */
flush_bz2_chunk(void * buffer,off_t size)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 
flush_compressed_chunk(void * buffer,off_t size)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 */
write_compressed_chunk(unsigned char * buffer,size_t size)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 */
write_template_desc_entries(off_t image_len)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" */
base64_dump(unsigned char * buf,size_t buf_size)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. */
write_jigdo_file(void)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 */
write_jt_footer(void)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 */
add_unmatched_entry(int uncompressed_length)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 */
add_file_entry(char * filename,off_t size,unsigned char * md5,unsigned long long rsyncsum)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 */
jtwrite(buffer,size,count,submode,islast)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 */
write_jt_match_record(char * filename,char * mirror_name,int sector_size,off_t size,unsigned char md5[16])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 }
1043