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
43struct seama_seal_header {
44	uint32_t magic;
45	uint16_t reserved;
46	uint16_t metasize;
47	uint32_t imagesize;
48} __attribute__ ((packed));
49
50struct 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
58char *seama_path;
59int entity_idx = -1;
60char *out_path;
61
62static inline size_t oseama_min(size_t x, size_t y) {
63	return x < y ? x : y;
64}
65
66/**************************************************
67 * Info
68 **************************************************/
69
70static 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
82static 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
135err_out:
136	return err;
137}
138
139static 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
214err_close:
215	fclose(seama);
216out:
217	return err;
218}
219
220/**************************************************
221 * Create
222 **************************************************/
223
224static 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
250static 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
266static 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
276static 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
305static 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);
391out:
392	return err;
393}
394
395/**************************************************
396 * Extract
397 **************************************************/
398
399static 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
414static 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
460static 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
513err_close_out:
514	fclose(out);
515err_close_seama:
516	fclose(seama);
517out:
518	return err;
519}
520
521/**************************************************
522 * Start
523 **************************************************/
524
525static 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
544int 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}
557