Initial checkin of JTE files from package version 1.4
[jigit.git] / mkimage.c
1 #include <errno.h>
2 #include <math.h>
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <limits.h>
7 #include <zlib.h>
8 #include <stdio.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <sys/mman.h>
14 #include "endian.h"
15 #include "md5.h"
16
17 typedef unsigned long long UINT64;
18 typedef unsigned long      UINT32;
19
20 #define BUF_SIZE 65536
21
22 #ifndef MIN
23 #define MIN(x,y)        ( ((x) < (y)) ? (x) : (y))
24 #endif
25
26 FILE *logfile = NULL;
27 int quick = 0;
28
29 typedef enum state_
30 {
31     STARTING,
32     IN_DATA,
33     IN_DESC,
34     DUMP_DESC,
35     DONE,
36     ERROR
37 } e_state;
38
39 typedef struct match_list_
40 {
41     struct match_list_ *next;
42     char *match;
43     char *mirror_path;
44 } match_list_t;
45
46 match_list_t *match_list_head = NULL;
47 match_list_t *match_list_tail = NULL;
48
49 typedef struct jigdo_list_
50 {
51     struct jigdo_list_ *next;
52     off_t file_size;
53     char *md5;
54     char *full_path;
55 } jigdo_list_t;
56
57 jigdo_list_t *jigdo_list_head = NULL;
58 jigdo_list_t *jigdo_list_tail = NULL;
59
60 struct
61 {
62     char   *data_buf;
63     size_t  buf_size;
64     off_t   curr_offset;
65 } zip_state;
66
67 static int add_match_entry(char *match)
68 {
69     match_list_t *entry = NULL;
70     char *mirror_path = NULL;
71     char *ptr = match;
72
73     /* Split "Foo=/mirror/foo" into its components */
74     while (*ptr)
75     {
76         if ('=' == *ptr)
77         {
78             *ptr = 0;
79             ptr++;
80             mirror_path = ptr;
81             break;
82         }
83         ptr++;
84     }
85
86     if (!mirror_path)
87     {
88         fprintf(logfile, "Could not parse malformed match entry \"%s\"\n", match);
89         return EINVAL;
90     }        
91     
92     entry = calloc(1, sizeof(*entry));
93     if (!entry)
94         return ENOMEM;
95
96     fprintf(logfile, "Adding match entry %s:%s\n", match, mirror_path);
97
98     entry->match = match;
99     entry->mirror_path = mirror_path;
100     
101     if (!match_list_head)
102     {
103         match_list_head = entry;
104         match_list_tail = entry;
105     }
106     else
107     {
108         match_list_tail->next = entry;
109         match_list_tail = entry;
110     }
111     
112     return 0;
113 }
114
115 static int file_exists(char *path, off_t *size)
116 {
117     struct stat sb;
118     int error = 0;
119     
120     error = stat(path, &sb);
121     if (!error && S_ISREG(sb.st_mode))
122     {
123         *size = sb.st_size;
124         return 1;
125     }
126     
127     /* else */
128     return 0;
129 }
130
131 static char *base64_dump(unsigned char *buf, size_t buf_size)
132 {
133     const char *b64_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
134     int value = 0;
135     unsigned int i;
136     int bits = 0;
137     char *out = calloc(1, 30);
138     unsigned int out_pos = 0;
139
140     if (!out)
141         return NULL;
142
143     for (i = 0; i < buf_size ; i++)
144     {
145         value = (value << 8) | buf[i];
146         bits += 2;
147         out[out_pos++] = b64_enc[(value >> bits) & 63U];
148         if (bits >= 8) {
149             bits -= 6;
150             out[out_pos++] = b64_enc[(value >> bits) & 63U];
151         }
152     }
153     if (bits > 0)
154     {
155         value <<= 8 - bits;
156         out[out_pos++] = b64_enc[(value >> bits) & 63U];
157     }
158     return out;
159 }
160
161 static int find_file_in_mirror(char *jigdo_entry, char **mirror_path, char **md5sum, off_t *file_size)
162 {
163     match_list_t *entry = match_list_head;
164     char path[PATH_MAX];
165     char *jigdo_name = NULL;
166     char *match = NULL;
167     char *ptr = jigdo_entry;
168
169     *md5sum = jigdo_entry;
170
171     /* Grab out the component strings from the entry in the jigdo file */
172     while (0 != *ptr)
173     {
174         if ('=' == *ptr)
175         {
176             *ptr = 0;
177             ptr++;
178             match = ptr;
179         }
180         else if (':' == *ptr)
181         {
182             *ptr = 0;
183             ptr++;
184             jigdo_name = ptr;
185         }
186         else if ('\n' == *ptr)
187             *ptr = 0;
188         else
189             ptr++;
190     }
191
192     if (NULL == match || NULL == jigdo_name)
193     {
194         fprintf(logfile, "Could not parse malformed jigdo entry \"%s\"\n", jigdo_entry);
195         return EINVAL;
196     }
197
198     while (entry)
199     {
200         if (!strcmp(entry->match, match))
201         {
202             sprintf(path, "%s/%s", entry->mirror_path, jigdo_name);
203             if (file_exists(path, file_size))
204             {
205                 *mirror_path = strdup(path);
206                 return 0;
207             }
208         }
209         entry = entry->next;
210     }
211     
212     fprintf(logfile, "Could not find file %s:%s in any path\n", match, jigdo_name);
213     return ENOENT;
214 }
215
216 /* DELIBERATELY do not sort these, or do anything clever with
217    insertion. The entries in the jigdo file should be in the same
218    order as the ones we'll want from the template. Simply add to the
219    end of the singly-linked list each time! */
220 static int add_file_entry(char *jigdo_entry)
221 {
222     int error = 0;
223     char *file_name = NULL;
224     char *md5 = NULL;
225     jigdo_list_t *new = NULL;
226     off_t file_size = 0;
227     
228     error = find_file_in_mirror(jigdo_entry, &file_name, &md5, &file_size);
229     if (error)
230         return error;
231     
232     new = calloc(1, sizeof(*new));
233     if (!new)
234         return ENOMEM;
235
236     new->md5 = md5;
237     new->full_path = file_name;
238     new->file_size = file_size;
239     
240     if (!jigdo_list_head)
241     {
242         jigdo_list_head = new;
243         jigdo_list_tail = new;
244     }
245     else
246     {
247         jigdo_list_tail->next = new;
248         jigdo_list_tail = new;
249     }
250     
251     return 0;
252 }
253
254 static int parse_jigdo_file(char *filename)
255 {
256     unsigned char buf[2048];
257     FILE *file = NULL;
258     char *ret = NULL;
259     int error = 0;
260     
261     file = fopen(filename, "rb");
262     if (!file)
263     {
264         fprintf(logfile, "Failed to open jigdo file %s, error %d!\n", filename, errno);
265         return errno;
266     }
267
268     /* Find the [Parts] section of the jigdo file */
269     while (1)
270     {
271         ret = fgets(buf, sizeof(buf), file);
272         if (NULL == ret)
273             break;
274         if (!strncmp(buf, "[Parts]", 7))
275             break;
276     }
277
278     /* Now grab the individual file entries and build a list */
279     while (1)
280     {
281         ret = fgets(buf, sizeof(buf), file);
282         if (NULL == ret || !strcmp(buf, "\n"))
283             break;
284         if (!strcmp(buf, "[") || !strcmp(buf, "#"))
285             continue;
286         error = add_file_entry(strdup(buf));
287         if (error)
288             return error;
289     }
290     
291     return 0;
292 }
293
294 static off_t get_file_size(char *filename)
295 {
296     struct stat sb;
297     int error = 0;
298     
299     error = stat(filename, &sb);
300     if (error)
301         return -1;
302     else
303         return sb.st_size;
304 }
305
306 static int decompress_data_block(char *in_buf, size_t in_len, char *out_buf, size_t out_len)
307 {
308     int error = 0;
309     z_stream uc_stream;
310     
311     uc_stream.zalloc = NULL;
312     uc_stream.zfree = NULL;
313     uc_stream.opaque = NULL;
314     uc_stream.next_in = in_buf;
315     uc_stream.avail_in = in_len;
316
317     error = inflateInit(&uc_stream);
318     if (Z_OK != error)
319     {
320         fprintf(logfile, "decompress_data_block: failed to init, error %d\n", error);
321         return EIO;
322     }
323     
324     uc_stream.next_out = out_buf;
325     uc_stream.avail_out = out_len;
326
327     error = inflate(&uc_stream, Z_FINISH);
328     if (Z_OK != error && Z_STREAM_END != error)
329     {
330         fprintf(logfile, "decompress_data_block: failed to decompress, error %d\n", error);
331         return EIO;
332     }
333     
334     error = inflateEnd(&uc_stream);
335     if (Z_OK != error)
336     {
337         fprintf(logfile, "decompress_data_block: failed to end, error %d\n", error);
338         return EIO;
339     }
340     
341     return 0;
342 }    
343
344 static int read_data_block(FILE *template_file)
345 {
346     char inbuf[1024];
347     off_t i = 0;
348     static off_t template_offset = -1;
349     off_t compressed_len = 0;
350     off_t uncompressed_len = 0;
351     char *comp_buf = NULL;
352     int read_num = 0;
353     int error = 0;
354
355     if (-1 == template_offset)
356     {
357         fseek(template_file, 0, SEEK_SET);
358         fread(inbuf, sizeof(inbuf), 1, template_file);
359         for (i = 0; i < sizeof(inbuf); i++)
360         {
361             if (!strncmp(&inbuf[i], "DATA", 4))
362             {
363                 template_offset = i;
364                 break;
365             }
366         }
367         if (-1 == template_offset)
368         {
369             fprintf(logfile, "Unable to locate DATA block in template (offset %lld)\n",
370                     template_offset);
371             return EINVAL;
372         }    
373     }
374     
375     fseek(template_file, template_offset, SEEK_SET);
376     fread(inbuf, 16, 1, template_file);
377     if (strncmp(inbuf, "DATA", 4))
378     {
379         fprintf(logfile, "Unable to locate DATA block in template (offset %lld)\n",
380                 template_offset);
381         return EINVAL;
382     }    
383     
384     compressed_len = read_le48(&inbuf[4]);
385     uncompressed_len = read_le48(&inbuf[10]);
386
387     comp_buf = calloc(1, compressed_len);
388     if (!comp_buf)
389     {
390         fprintf(logfile, "Unable to locate DATA block in template (offset %lld)\n",
391                 template_offset);
392         return ENOMEM;
393     }
394     
395     zip_state.data_buf = calloc(1, uncompressed_len);
396     if (!zip_state.data_buf)
397     {
398         fprintf(logfile, "Unable to allocate %lld bytes for decompression\n",
399                 uncompressed_len);
400         return ENOMEM;
401     }
402
403     read_num = fread(comp_buf, compressed_len, 1, template_file);
404     if (0 == read_num)
405     {
406         fprintf(logfile, "Unable to read %lld bytes for decompression\n",
407                 uncompressed_len);
408         return EIO;
409     }
410
411     error = decompress_data_block(comp_buf, compressed_len,
412                                   zip_state.data_buf, uncompressed_len);
413     if (error)
414     {
415         fprintf(logfile, "Unable to decompress data block, error %d\n", error);
416         return error;
417     }
418         
419     template_offset += compressed_len;
420     zip_state.buf_size = uncompressed_len;
421     zip_state.curr_offset = 0;
422     free (comp_buf);
423     return 0;
424 }
425
426 static int parse_data_block(size_t data_size, FILE *template_file, struct mk_MD5Context *context)
427 {
428     int error = 0;
429     size_t remaining = data_size;
430     size_t size = 0;
431
432     while (remaining)
433     {
434         if (!zip_state.data_buf)
435         {
436             error = read_data_block(template_file);
437             if (error)
438             {
439                 fprintf(logfile, "Unable to decompress template data, error %d\n",
440                         error);
441                 return error;
442             }
443         }
444         size = MIN((zip_state.buf_size - zip_state.curr_offset), remaining);
445         fwrite(&zip_state.data_buf[zip_state.curr_offset], 1, size, stdout);
446         if (!quick)
447             mk_MD5Update(context, &zip_state.data_buf[zip_state.curr_offset], size);
448         zip_state.curr_offset += size;
449         remaining -= size;
450         
451         if (zip_state.curr_offset == zip_state.buf_size)
452         {
453             free(zip_state.data_buf);
454             zip_state.data_buf = NULL;
455         }
456     }
457     
458     fprintf(logfile, "parse_data_block: wrote %d bytes of unmatched data\n", data_size);
459     return error;
460 }
461
462 static int parse_file_block(off_t file_size, char *md5, struct mk_MD5Context *image_context)
463 {
464     char *base64_md5 = base64_dump(md5, 16);
465     FILE *input_file = NULL;
466     char buf[BUF_SIZE];
467     size_t remaining = file_size;
468     int num_read = 0;
469     struct mk_MD5Context file_context;
470     char file_md5[16];
471     jigdo_list_t *jigdo_list_current = jigdo_list_head;
472     
473     if (!quick)
474         mk_MD5Init(&file_context);
475
476     while (jigdo_list_current)
477     {        
478         if ( (jigdo_list_current->file_size == file_size) &&
479              (!memcmp(jigdo_list_current->md5, base64_md5, 16) ) )
480         {
481             input_file = fopen(jigdo_list_current->full_path, "rb");
482             if (!input_file)
483             {
484                 fprintf(logfile, "Unable to open mirror file %s, error %d\n",
485                         jigdo_list_current->full_path, errno);
486                 return errno;
487             }
488             while (remaining)
489             {
490                 int size = MIN(BUF_SIZE, remaining);
491                 memset(buf, 0, BUF_SIZE);
492
493                 num_read = fread(buf, size, 1, input_file);
494                 if (!num_read)
495                 {
496                     fprintf(logfile, "Unable to open mirror file %s, error %d\n",
497                             jigdo_list_current->full_path, errno);
498                     fclose(input_file);
499                     return errno;
500                 }
501                 if (!quick)
502                 {
503                     mk_MD5Update(image_context, buf, size);
504                     mk_MD5Update(&file_context, buf, size);
505                 }
506             
507                 fwrite(buf, size, 1, stdout);
508             
509                 remaining -= size;
510             }
511             fprintf(logfile, "parse_file_block: wrote %lld bytes of data from %s\n",
512                     file_size, jigdo_list_current->full_path);
513             fclose(input_file);
514
515             if (!quick)
516             {
517                 mk_MD5Final(file_md5, &file_context);
518         
519                 if (memcmp(file_md5, md5, 16))
520                 {
521                     fprintf(logfile, "MD5 MISMATCH for file %s\n", jigdo_list_current->full_path);
522                     fprintf(logfile, "    template looking for %s\n", md5);
523                     fprintf(logfile, "    file in mirror is    %s\n", file_md5);
524                     return EINVAL;
525                 }
526             }
527             return 0;
528         }
529         jigdo_list_current = jigdo_list_current->next;
530     }
531     return ENOENT;
532 }
533
534 static int parse_template_file(char *filename)
535 {
536     off_t offset = 0;
537     off_t bytes = 0;
538     unsigned char *buf = NULL;
539     FILE *file = NULL;
540     off_t file_size = 0;
541     off_t desc_start = 0;
542     off_t image_length = 0;
543     off_t written_length = 0;
544     int i = 0;
545     int error = 0;
546     struct mk_MD5Context template_context;
547     unsigned char image_md5sum[16];
548     
549     file = fopen(filename, "rb");
550     if (!file)
551     {
552         fprintf(logfile, "Failed to open template file %s, error %d!\n", filename, errno);
553         return errno;
554     }
555
556     buf = malloc(BUF_SIZE);
557     if (!buf)
558     {
559         fprintf(logfile, "Failed to malloc %d bytes. Abort!\n", BUF_SIZE);
560         fclose(file);
561         return ENOMEM;
562     }
563
564     /* Find the beginning of the desc block */
565     file_size = get_file_size(filename);
566     fseek(file, file_size - 6, SEEK_SET);
567     fread(buf, 6, 1, file);
568     desc_start = file_size - read_le48(buf);
569
570     /* Now seek back to the beginning image desc block to grab the MD5
571        and image length */
572     fseek(file, file_size - 33, SEEK_SET);
573     fread(buf, BUF_SIZE, 1, file);
574     if (buf[0] != 5) /* image data */
575     {
576         fprintf(logfile, "Failed to find image desc in the template file\n");
577         fclose(file);
578         return EINVAL;
579     }
580     image_length = read_le48(&buf[1]);
581     fprintf(logfile, "Image is %lld bytes\n", image_length);
582     fprintf(logfile, "Image MD5 is ");
583     for (i = 0; i < 16; i++)
584         fprintf(logfile, "%2.2x", buf[i+7]);
585     fprintf(logfile, "\n");
586
587     /* Now seek back to the start of the desc block and start
588        assembling the image */
589     fseek(file, desc_start, SEEK_SET);
590     fread(buf, 10, 1, file);
591     if (strncmp(buf, "DESC", 4))
592     {
593         fprintf(logfile, "Failed to find desc start in the template file\n");
594         fclose(file);
595         return EINVAL;
596     }
597     if ((file_size - desc_start) != read_le48(&buf[4]))
598     {
599         fprintf(logfile, "Inconsistent desc length in the template file!\n");
600         fprintf(logfile, "Final chunk says %lld, first chunk says %lld\n",
601                 file_size - desc_start, read_le48(&buf[4]));
602         fclose(file);
603         return EINVAL;
604     }
605
606     if (!quick)
607         mk_MD5Init(&template_context);
608     offset = desc_start + 10;
609     while (1)
610     {
611         if (offset >= (file_size - 33))
612             break; /* Finished! */
613
614         fseek(file, offset, SEEK_SET);
615         bytes = fread(buf, (MIN (BUF_SIZE, file_size - offset)), 1, file);
616         if (1 != bytes)
617         {
618             fprintf(logfile, "Failed to read template file!\n");
619             fclose(file);
620             return EINVAL;
621         }
622         
623         switch (buf[0])
624         {
625             case 2: /* unmatched data */
626                 error = parse_data_block(read_le48(&buf[1]), file, &template_context);
627                 if (error)
628                 {
629                     fprintf(logfile, "Unable to read data block, error %d\n", error);
630                     fclose(file);
631                     return error;
632                 }
633                 offset += 7;
634                 written_length += read_le48(&buf[1]);
635                 break;
636             case 6:
637                 error = parse_file_block(read_le48(&buf[1]), &buf[15], &template_context);
638                 if (error)
639                 {
640                     fprintf(logfile, "Unable to read file block, error %d\n", error);
641                     fclose(file);
642                     return error;
643                 }
644                 offset += 31;
645                 written_length += read_le48(&buf[1]);
646                 break;
647             default:
648                 fprintf(logfile, "Unknown block type %d!\n", buf[0]);
649                 fclose(file);
650                 return EINVAL;
651         }
652     }
653     
654     fclose(file);
655     if (!quick)
656     {
657         mk_MD5Final (image_md5sum, &template_context);
658         fprintf(logfile, "Output image MD5 is ");
659         for (i = 0; i < 16; i++)
660             fprintf(logfile, "%2.2x", image_md5sum[i]);
661         fprintf(logfile, "\n");
662     }
663     fprintf(logfile, "Output image length is %lld\n", written_length);
664
665     return 0;
666 }
667
668 static void usage(char *progname)
669 {
670     printf("%s [OPTIONS]\n\n", progname);
671     printf(" Options:\n");
672     printf(" -j <jigdo name>     Specify the input jigdo file\n");
673     printf(" -t <template name>  Specify the input template file\n");
674     printf(" -m <item=path>      Map <item> to <path> to find the files in the mirror\n");
675     printf(" -l <logfile>        Specify a logfile to append to.\n");
676     printf("                     If not specified, will log to stderr\n");
677     printf(" -q                  Quick mode. Don't check MD5sums. Dangerous!\n");
678 }
679
680 int main(int argc, char **argv)
681 {
682     char *template_filename = NULL;
683     char *jigdo_filename = NULL;
684     int c = -1;
685     int error = 0;
686
687     logfile = stderr;
688
689     bzero(&zip_state, sizeof(zip_state));
690
691     while(1)
692     {
693         c = getopt(argc, argv, ":ql:j:t:m:h?");
694         if (-1 == c)
695             break;
696         
697         switch(c)
698         {
699             case 'q':
700                 quick = 1;
701                 break;
702             case 'l':
703                 logfile = fopen(optarg, "wb");
704                 if (!logfile)
705                 {
706                     fprintf(logfile, "Unable to open log file %s\n", optarg);
707                     return errno;
708                 }
709                 break;
710             case 'j':
711                 if (jigdo_filename)
712                 {
713                     fprintf(logfile, "Can only specify one jigdo file!\n");
714                     return EINVAL;
715                 }
716                 /* else */
717                 jigdo_filename = optarg;
718                 break;
719             case 't':
720                 if (template_filename)
721                 {
722                     fprintf(logfile, "Can only specify one template file!\n");
723                     return EINVAL;
724                 }
725                 /* else */
726                 template_filename = optarg;
727                 break;
728             case 'm':
729                 error = add_match_entry(strdup(optarg));
730                 if (error)
731                     return error;
732                 break;
733             case ':':
734                 fprintf(logfile, "Missing argument!\n");
735                 return EINVAL;
736                 break;
737             case 'h':
738             case '?':
739                 usage(argv[0]);
740                 return 0;
741                 break;
742             default:
743                 fprintf(logfile, "Unknown option!\n");
744                 return EINVAL;
745         }
746     }
747
748     if (NULL == jigdo_filename)
749     {
750         fprintf(logfile, "No jigdo file specified!\n");
751         usage(argv[0]);
752         return EINVAL;
753     }
754     
755     if (NULL == template_filename)
756     {
757         fprintf(logfile, "No template file specified!\n");
758         usage(argv[0]);
759         return EINVAL;
760     }    
761
762     /* Build up a list of file mappings */
763     error = parse_jigdo_file(jigdo_filename);
764     if (error)
765     {
766         fprintf(logfile, "Unable to parse the jigdo file %s\n", jigdo_filename);
767         return error;
768     }
769
770     /* Read the template file and actually build the image to stdout */
771     error = parse_template_file(template_filename);
772     if (error)
773     {
774         fprintf(logfile, "Unable to recreate image from template file %s\n", template_filename);
775         return error;
776     }        
777
778     return 0;
779 }