First git repo commit for the libreCMC project
[librecmc/librecmc.git] / package / utils / oseama / src / oseama.c
1 /*
2  * oseama
3  *
4  * Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation; either version 2 of the License, or (at your option)
9  * any later version.
10  */
11
12 #include <byteswap.h>
13 #include <endian.h>
14 #include <errno.h>
15 #include <stdint.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #include "md5.h"
22
23 #if !defined(__BYTE_ORDER)
24 #error "Unknown byte order"
25 #endif
26
27 #if __BYTE_ORDER == __BIG_ENDIAN
28 #define cpu_to_be32(x)  (x)
29 #define be32_to_cpu(x)  (x)
30 #define cpu_to_be16(x)  (x)
31 #define be16_to_cpu(x)  (x)
32 #elif __BYTE_ORDER == __LITTLE_ENDIAN
33 #define cpu_to_be32(x)  bswap_32(x)
34 #define be32_to_cpu(x)  bswap_32(x)
35 #define cpu_to_be16(x)  bswap_16(x)
36 #define be16_to_cpu(x)  bswap_16(x)
37 #else
38 #error "Unsupported endianness"
39 #endif
40
41 #define SEAMA_MAGIC                     0x5ea3a417
42
43 struct seama_seal_header {
44         uint32_t magic;
45         uint16_t reserved;
46         uint16_t metasize;
47         uint32_t imagesize;
48 } __attribute__ ((packed));
49
50 struct seama_entity_header {
51         uint32_t magic;
52         uint16_t reserved;
53         uint16_t metasize;
54         uint32_t imagesize;
55         uint8_t md5[16];
56 } __attribute__ ((packed));
57
58 char *seama_path;
59 int entity_idx = -1;
60 char *out_path;
61
62 static inline size_t oseama_min(size_t x, size_t y) {
63         return x < y ? x : y;
64 }
65
66 /**************************************************
67  * Info
68  **************************************************/
69
70 static void oseama_info_parse_options(int argc, char **argv) {
71         int c;
72
73         while ((c = getopt(argc, argv, "e:")) != -1) {
74                 switch (c) {
75                 case 'e':
76                         entity_idx = atoi(optarg);
77                         break;
78                 }
79         }
80 }
81
82 static int oseama_info_entities(FILE *seama) {
83         struct seama_entity_header hdr;
84         size_t bytes, metasize, imagesize;
85         uint8_t buf[1024];
86         char *end, *tmp;
87         int i = 0;
88         int err = 0;
89
90         while ((bytes = fread(&hdr, 1, sizeof(hdr), seama)) == sizeof(hdr)) {
91                 if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) {
92                         fprintf(stderr, "Invalid Seama magic: 0x%08x\n", be32_to_cpu(hdr.magic));
93                         err =  -EINVAL;
94                         goto err_out;
95                 }
96                 metasize = be16_to_cpu(hdr.metasize);
97                 imagesize = be32_to_cpu(hdr.imagesize);
98
99                 if (entity_idx >= 0 && i != entity_idx) {
100                         fseek(seama, metasize + imagesize, SEEK_CUR);
101                         i++;
102                         continue;
103                 }
104
105                 if (metasize >= sizeof(buf)) {
106                         fprintf(stderr, "Too small buffer (%zu B) to read all meta info (%zd B)\n", sizeof(buf), metasize);
107                         err =  -EINVAL;
108                         goto err_out;
109                 }
110
111                 if (entity_idx < 0)
112                         printf("\n");
113                 printf("Entity offset:\t%ld\n", ftell(seama) - sizeof(hdr));
114                 printf("Entity size:\t%zd\n", sizeof(hdr) + metasize + imagesize);
115                 printf("Meta size:\t%zd\n", metasize);
116                 printf("Image size:\t%zd\n", imagesize);
117
118                 bytes = fread(buf, 1, metasize, seama);
119                 if (bytes != metasize) {
120                         fprintf(stderr, "Couldn't read %zd B of meta\n", metasize);
121                         err =  -EIO;
122                         goto err_out;
123                 }
124
125                 end = (char *)&buf[metasize - 1];
126                 *end = '\0';
127                 for (tmp = (char *)buf; tmp < end && strlen(tmp); tmp += strlen(tmp) + 1) {
128                         printf("Meta entry:\t%s\n", tmp);
129                 }
130
131                 fseek(seama, imagesize, SEEK_CUR);
132                 i++;
133         }
134
135 err_out:
136         return err;
137 }
138
139 static int oseama_info(int argc, char **argv) {
140         FILE *seama;
141         struct seama_seal_header hdr;
142         size_t bytes;
143         uint16_t metasize;
144         uint32_t imagesize;
145         uint8_t buf[1024];
146         int err = 0;
147
148         if (argc < 3) {
149                 fprintf(stderr, "No Seama file passed\n");
150                 err = -EINVAL;
151                 goto out;
152         }
153         seama_path = argv[2];
154
155         optind = 3;
156         oseama_info_parse_options(argc, argv);
157
158         seama = fopen(seama_path, "r");
159         if (!seama) {
160                 fprintf(stderr, "Couldn't open %s\n", seama_path);
161                 err = -EACCES;
162                 goto out;
163         }
164
165         bytes = fread(&hdr, 1, sizeof(hdr), seama);
166         if (bytes != sizeof(hdr)) {
167                 fprintf(stderr, "Couldn't read %s header\n", seama_path);
168                 err =  -EIO;
169                 goto err_close;
170         }
171         metasize = be16_to_cpu(hdr.metasize);
172         imagesize = be32_to_cpu(hdr.imagesize);
173
174         if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) {
175                 fprintf(stderr, "Invalid Seama magic: 0x%08x\n", be32_to_cpu(hdr.magic));
176                 err =  -EINVAL;
177                 goto err_close;
178         }
179
180         if (metasize >= sizeof(buf)) {
181                 fprintf(stderr, "Too small buffer (%zu B) to read all meta info (%d B)\n", sizeof(buf), metasize);
182                 err =  -EINVAL;
183                 goto err_close;
184         }
185
186         if (imagesize) {
187                 fprintf(stderr, "Invalid Seama image size: 0x%08x (should be 0)\n", imagesize);
188                 err =  -EINVAL;
189                 goto err_close;
190         }
191
192         bytes = fread(buf, 1, metasize, seama);
193         if (bytes != metasize) {
194                 fprintf(stderr, "Couldn't read %d B of meta\n", metasize);
195                 err =  -EIO;
196                 goto err_close;
197         }
198
199         if (entity_idx < 0) {
200                 char *end, *tmp;
201
202                 printf("Meta size:\t%d\n", metasize);
203                 printf("Image size:\t%d\n", imagesize);
204
205                 end = (char *)&buf[metasize - 1];
206                 *end = '\0';
207                 for (tmp = (char *)buf; tmp < end && strlen(tmp); tmp += strlen(tmp) + 1) {
208                         printf("Meta entry:\t%s\n", tmp);
209                 }
210         }
211
212         oseama_info_entities(seama);
213
214 err_close:
215         fclose(seama);
216 out:
217         return err;
218 }
219
220 /**************************************************
221  * Create
222  **************************************************/
223
224 static ssize_t oseama_entity_append_file(FILE *seama, const char *in_path) {
225         FILE *in;
226         size_t bytes;
227         ssize_t length = 0;
228         uint8_t buf[128];
229
230         in = fopen(in_path, "r");
231         if (!in) {
232                 fprintf(stderr, "Couldn't open %s\n", in_path);
233                 return -EACCES;
234         }
235
236         while ((bytes = fread(buf, 1, sizeof(buf), in)) > 0) {
237                 if (fwrite(buf, 1, bytes, seama) != bytes) {
238                         fprintf(stderr, "Couldn't write %zu B to %s\n", bytes, seama_path);
239                         length = -EIO;
240                         break;
241                 }
242                 length += bytes;
243         }
244
245         fclose(in);
246
247         return length;
248 }
249
250 static ssize_t oseama_entity_append_zeros(FILE *seama, size_t length) {
251         uint8_t *buf;
252
253         buf = malloc(length);
254         if (!buf)
255                 return -ENOMEM;
256         memset(buf, 0, length);
257
258         if (fwrite(buf, 1, length, seama) != length) {
259                 fprintf(stderr, "Couldn't write %zu B to %s\n", length, seama_path);
260                 return -EIO;
261         }
262
263         return length;
264 }
265
266 static ssize_t oseama_entity_align(FILE *seama, size_t curr_offset, size_t alignment) {
267         if (curr_offset & (alignment - 1)) {
268                 size_t length = alignment - (curr_offset % alignment);
269
270                 return oseama_entity_append_zeros(seama, length);
271         }
272
273         return 0;
274 }
275
276 static int oseama_entity_write_hdr(FILE *seama, size_t metasize, size_t imagesize) {
277         struct seama_entity_header hdr = {};
278         uint8_t buf[128];
279         size_t length = imagesize;
280         size_t bytes;
281         MD5_CTX ctx;
282
283         fseek(seama, sizeof(hdr) + metasize, SEEK_SET);
284         MD5_Init(&ctx);
285         while ((bytes = fread(buf, 1, oseama_min(sizeof(buf), length), seama)) > 0) {
286                 MD5_Update(&ctx, buf, bytes);
287                 length -= bytes;
288         }
289         MD5_Final(hdr.md5, &ctx);
290
291         hdr.magic = cpu_to_be32(SEAMA_MAGIC);
292         hdr.metasize = cpu_to_be16(metasize);
293         hdr.imagesize = cpu_to_be32(imagesize);
294
295         fseek(seama, 0, SEEK_SET);
296         bytes = fwrite(&hdr, 1, sizeof(hdr), seama);
297         if (bytes != sizeof(hdr)) {
298                 fprintf(stderr, "Couldn't write Seama entity header to %s\n", seama_path);
299                 return -EIO;
300         }
301
302         return 0;
303 }
304
305 static int oseama_entity(int argc, char **argv) {
306         FILE *seama;
307         ssize_t sbytes;
308         size_t curr_offset = sizeof(struct seama_entity_header);
309         size_t metasize = 0, imagesize = 0;
310         int c;
311         int err = 0;
312
313         if (argc < 3) {
314                 fprintf(stderr, "No Seama file passed\n");
315                 err = -EINVAL;
316                 goto out;
317         }
318         seama_path = argv[2];
319
320         seama = fopen(seama_path, "w+");
321         if (!seama) {
322                 fprintf(stderr, "Couldn't open %s\n", seama_path);
323                 err = -EACCES;
324                 goto out;
325         }
326         fseek(seama, curr_offset, SEEK_SET);
327
328         optind = 3;
329         while ((c = getopt(argc, argv, "m:f:b:")) != -1) {
330                 switch (c) {
331                 case 'm':
332                         sbytes = fwrite(optarg, 1, strlen(optarg) + 1, seama);
333                         if (sbytes < 0) {
334                                 fprintf(stderr, "Failed to write meta %s\n", optarg);
335                         } else {
336                                 curr_offset += sbytes;
337                                 metasize += sbytes;
338                         }
339
340                         sbytes = oseama_entity_align(seama, curr_offset, 4);
341                         if (sbytes < 0) {
342                                 fprintf(stderr, "Failed to append zeros\n");
343                         } else {
344                                 curr_offset += sbytes;
345                                 metasize += sbytes;
346                         }
347
348                         break;
349                 case 'f':
350                 case 'b':
351                         break;
352                 }
353         }
354
355         optind = 3;
356         while ((c = getopt(argc, argv, "m:f:b:")) != -1) {
357                 switch (c) {
358                 case 'm':
359                         break;
360                 case 'f':
361                         sbytes = oseama_entity_append_file(seama, optarg);
362                         if (sbytes < 0) {
363                                 fprintf(stderr, "Failed to append file %s\n", optarg);
364                         } else {
365                                 curr_offset += sbytes;
366                                 imagesize += sbytes;
367                         }
368                         break;
369                 case 'b':
370                         sbytes = strtol(optarg, NULL, 0) - curr_offset;
371                         if (sbytes < 0) {
372                                 fprintf(stderr, "Current Seama entity length is 0x%zx, can't pad it with zeros to 0x%lx\n", curr_offset, strtol(optarg, NULL, 0));
373                         } else {
374                                 sbytes = oseama_entity_append_zeros(seama, sbytes);
375                                 if (sbytes < 0) {
376                                         fprintf(stderr, "Failed to append zeros\n");
377                                 } else {
378                                         curr_offset += sbytes;
379                                         imagesize += sbytes;
380                                 }
381                         }
382                         break;
383                 }
384                 if (err)
385                         break;
386         }
387
388         oseama_entity_write_hdr(seama, metasize, imagesize);
389
390         fclose(seama);
391 out:
392         return err;
393 }
394
395 /**************************************************
396  * Extract
397  **************************************************/
398
399 static void oseama_extract_parse_options(int argc, char **argv) {
400         int c;
401
402         while ((c = getopt(argc, argv, "e:o:")) != -1) {
403                 switch (c) {
404                 case 'e':
405                         entity_idx = atoi(optarg);
406                         break;
407                 case 'o':
408                         out_path = optarg;
409                         break;
410                 }
411         }
412 }
413
414 static int oseama_extract_entity(FILE *seama, FILE *out) {
415         struct seama_entity_header hdr;
416         size_t bytes, metasize, imagesize, length;
417         uint8_t buf[1024];
418         int i = 0;
419         int err = 0;
420
421         while ((bytes = fread(&hdr, 1, sizeof(hdr), seama)) == sizeof(hdr)) {
422                 if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) {
423                         fprintf(stderr, "Invalid Seama magic: 0x%08x\n", be32_to_cpu(hdr.magic));
424                         err =  -EINVAL;
425                         break;
426                 }
427                 metasize = be16_to_cpu(hdr.metasize);
428                 imagesize = be32_to_cpu(hdr.imagesize);
429
430                 if (i != entity_idx) {
431                         fseek(seama, metasize + imagesize, SEEK_CUR);
432                         i++;
433                         continue;
434                 }
435
436                 fseek(seama, -sizeof(hdr), SEEK_CUR);
437
438                 length = sizeof(hdr) + metasize + imagesize;
439                 while ((bytes = fread(buf, 1, oseama_min(sizeof(buf), length), seama)) > 0) {
440                         if (fwrite(buf, 1, bytes, out) != bytes) {
441                                 fprintf(stderr, "Couldn't write %zu B to %s\n", bytes, out_path);
442                                 err = -EIO;
443                                 break;
444                         }
445                         length -= bytes;
446                 }
447
448                 if (length) {
449                         fprintf(stderr, "Couldn't extract whole entity %d from %s (%zu B left)\n", entity_idx, seama_path, length);
450                         err = -EIO;
451                         break;
452                 }
453
454                 break;
455         }
456
457         return err;
458 }
459
460 static int oseama_extract(int argc, char **argv) {
461         FILE *seama;
462         FILE *out;
463         struct seama_seal_header hdr;
464         size_t bytes;
465         uint16_t metasize;
466         int err = 0;
467
468         if (argc < 3) {
469                 fprintf(stderr, "No Seama file passed\n");
470                 err = -EINVAL;
471                 goto out;
472         }
473         seama_path = argv[2];
474
475         optind = 3;
476         oseama_extract_parse_options(argc, argv);
477         if (entity_idx < 0) {
478                 fprintf(stderr, "No entity specified\n");
479                 err = -EINVAL;
480                 goto out;
481         } else if (!out_path) {
482                 fprintf(stderr, "No output file specified\n");
483                 err = -EINVAL;
484                 goto out;
485         }
486
487         seama = fopen(seama_path, "r");
488         if (!seama) {
489                 fprintf(stderr, "Couldn't open %s\n", seama_path);
490                 err = -EACCES;
491                 goto out;
492         }
493
494         out = fopen(out_path, "w");
495         if (!out) {
496                 fprintf(stderr, "Couldn't open %s\n", out_path);
497                 err = -EACCES;
498                 goto err_close_seama;
499         }
500
501         bytes = fread(&hdr, 1, sizeof(hdr), seama);
502         if (bytes != sizeof(hdr)) {
503                 fprintf(stderr, "Couldn't read %s header\n", seama_path);
504                 err =  -EIO;
505                 goto err_close_out;
506         }
507         metasize = be16_to_cpu(hdr.metasize);
508
509         fseek(seama, metasize, SEEK_CUR);
510
511         oseama_extract_entity(seama, out);
512
513 err_close_out:
514         fclose(out);
515 err_close_seama:
516         fclose(seama);
517 out:
518         return err;
519 }
520
521 /**************************************************
522  * Start
523  **************************************************/
524
525 static void usage() {
526         printf("Usage:\n");
527         printf("\n");
528         printf("Info about Seama seal (container):\n");
529         printf("\toseama info <file> [options]\n");
530         printf("\t-e\t\t\t\tprint info about specified entity only\n");
531         printf("\n");
532         printf("Create Seama entity:\n");
533         printf("\toseama entity <file> [options]\n");
534         printf("\t-m meta\t\t\t\tmeta into to put in header\n");
535         printf("\t-f file\t\t\t\tappend content from file\n");
536         printf("\t-b offset\t\t\tappend zeros till reaching absolute offset\n");
537         printf("\n");
538         printf("Extract from Seama seal (container):\n");
539         printf("\toseama extract <file> [options]\n");
540         printf("\t-e\t\t\t\tindex of entity to extract\n");
541         printf("\t-o file\t\t\t\toutput file\n");
542 }
543
544 int main(int argc, char **argv) {
545         if (argc > 1) {
546                 if (!strcmp(argv[1], "info"))
547                         return oseama_info(argc, argv);
548                 else if (!strcmp(argv[1], "entity"))
549                         return oseama_entity(argc, argv);
550                 else if (!strcmp(argv[1], "extract"))
551                         return oseama_extract(argc, argv);
552         }
553
554         usage();
555         return 0;
556 }