1/*
2 * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
7 *
8 */
9
10#include <stdio.h>
11#include <stdlib.h>
12#include <stdint.h>
13#include <string.h>
14#include <unistd.h>     /* for unlink() */
15#include <libgen.h>
16#include <getopt.h>     /* for getopt() */
17#include <stdarg.h>
18#include <errno.h>
19#include <sys/stat.h>
20
21#include <arpa/inet.h>
22#include <netinet/in.h>
23
24#define MAX_MODEL_LEN		20
25#define MAX_SIGNATURE_LEN	30
26#define MAX_REGION_LEN		4
27#define MAX_VERSION_LEN		12
28
29#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
30
31struct file_info {
32	char		*file_name;	/* name of the file */
33	uint32_t	file_size;	/* length of the file */
34	uint32_t	write_size;
35};
36
37struct img_header {
38	uint32_t	checksum;
39	uint32_t	image_size;
40	uint32_t	kernel_size;
41	char		model[MAX_MODEL_LEN];
42	char		signature[MAX_SIGNATURE_LEN];
43	char		region[MAX_REGION_LEN];
44	char		version[MAX_VERSION_LEN];
45	unsigned char	header_len;
46	unsigned char	is_tgz;
47	unsigned char	pad[4];
48} __attribute__ ((packed));
49
50/*
51 * Globals
52 */
53static char *ofname;
54static char *progname;
55
56static char *model;
57static char *signature;
58static char *region = "DEF";
59static char *version;
60static struct file_info kernel_info;
61static struct file_info rootfs_info;
62static uint32_t kernel_size;
63static uint32_t image_size;
64static int combined;
65
66/*
67 * Message macros
68 */
69#define ERR(fmt, ...) do { \
70	fflush(0); \
71	fprintf(stderr, "[%s] *** error: " fmt "\n", \
72			progname, ## __VA_ARGS__ ); \
73} while (0)
74
75#define ERRS(fmt, ...) do { \
76	int save = errno; \
77	fflush(0); \
78	fprintf(stderr, "[%s] *** error: " fmt " (%s)\n", \
79			progname, ## __VA_ARGS__, strerror(save)); \
80} while (0)
81
82#define DBG(fmt, ...) do { \
83	fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \
84} while (0)
85
86static void usage(int status)
87{
88	FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
89
90	fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
91	fprintf(stream,
92"\n"
93"Options:\n"
94"  -k <file>       read kernel image from the file <file>\n"
95"  -c              use the kernel image as a combined image\n"
96"  -M <model>      set model to <model>\n"
97"  -o <file>       write output to the file <file>\n"
98"  -r <file>       read rootfs image from the file <file>\n"
99"  -S <signature>  set image signature to <signature>\n"
100"  -R <region>     set image region to <region>\n"
101"  -V <version>    set image version to <version>\n"
102"  -I <size>       set image size to <size>\n"
103"  -K <size>       set kernel size to <size>\n"
104"  -h              show this screen\n"
105	);
106
107	exit(status);
108}
109
110int
111str2u32(char *arg, uint32_t *val)
112{
113	char *err = NULL;
114	uint32_t t;
115
116	errno=0;
117	t = strtoul(arg, &err, 0);
118	if (errno || (err==arg) || ((err != NULL) && *err)) {
119		return -1;
120	}
121
122	*val = t;
123	return 0;
124}
125
126static int get_file_stat(struct file_info *fdata)
127{
128	struct stat st;
129	int res;
130
131	if (fdata->file_name == NULL)
132		return 0;
133
134	res = stat(fdata->file_name, &st);
135	if (res){
136		ERRS("stat failed on %s", fdata->file_name);
137		return res;
138	}
139
140	fdata->file_size = st.st_size;
141	fdata->write_size = fdata->file_size;
142	return 0;
143}
144
145static int read_to_buf(struct file_info *fdata, char *buf)
146{
147	FILE *f;
148	int ret = EXIT_FAILURE;
149
150	f = fopen(fdata->file_name, "r");
151	if (f == NULL) {
152		ERRS("could not open \"%s\" for reading", fdata->file_name);
153		goto out;
154	}
155
156	errno = 0;
157	fread(buf, fdata->file_size, 1, f);
158	if (errno != 0) {
159		ERRS("unable to read from file \"%s\"", fdata->file_name);
160		goto out_close;
161	}
162
163	ret = EXIT_SUCCESS;
164
165out_close:
166	fclose(f);
167out:
168	return ret;
169}
170
171static int check_options(void)
172{
173	int ret;
174
175#define CHKSTR(_name, _msg)				\
176	do {						\
177		if (_name == NULL) {			\
178			ERR("no %s specified", _msg);	\
179			return -1;			\
180		}					\
181	} while (0)
182
183#define CHKSTRLEN(_name, _msg)					\
184	do {							\
185		int field_len;					\
186		CHKSTR(_name, _msg);				\
187		field_len = FIELD_SIZEOF(struct img_header, _name) - 1; \
188		if (strlen(_name) > field_len) { 		\
189			ERR("%s is too long, max length is %d",	\
190			    _msg, field_len);			\
191			return -1;				\
192		}						\
193	} while (0)
194
195	CHKSTRLEN(model, "model");
196	CHKSTRLEN(signature, "signature");
197	CHKSTRLEN(region, "region");
198	CHKSTRLEN(version, "version");
199	CHKSTR(ofname, "output file");
200	CHKSTR(kernel_info.file_name, "kernel image");
201
202	ret = get_file_stat(&kernel_info);
203	if (ret)
204		return ret;
205
206	if (combined) {
207		if (!kernel_size) {
208			ERR("kernel size must be specified for combined images");
209			return -1;				\
210		}
211
212		if (!image_size)
213			image_size = kernel_info.file_size;
214
215		if (kernel_info.file_size > image_size) {
216			ERR("kernel image is too big");
217			return -1;
218		}
219
220		kernel_info.write_size = image_size;
221	} else {
222		CHKSTR(rootfs_info.file_name, "rootfs image");
223
224		ret = get_file_stat(&rootfs_info);
225		if (ret)
226			return ret;
227
228		if (kernel_size) {
229			/* override kernel size */
230			kernel_info.write_size = kernel_size;
231		}
232
233		if (image_size) {
234			if (image_size < kernel_info.write_size)
235				kernel_info.write_size = image_size;
236
237			/* override rootfs size */
238			rootfs_info.write_size = image_size - kernel_info.write_size;
239		}
240
241		if (kernel_info.file_size > kernel_info.write_size) {
242			ERR("kernel image is too big");
243			return -1;
244		}
245
246		if (rootfs_info.file_size > rootfs_info.write_size) {
247			ERR("rootfs image is too big");
248			return -1;
249		}
250	}
251
252	return 0;
253}
254
255static int write_fw(char *data, int len)
256{
257	FILE *f;
258	int ret = EXIT_FAILURE;
259
260	f = fopen(ofname, "w");
261	if (f == NULL) {
262		ERRS("could not open \"%s\" for writing", ofname);
263		goto out;
264	}
265
266	errno = 0;
267	fwrite(data, len, 1, f);
268	if (errno) {
269		ERRS("unable to write output file");
270		goto out_flush;
271	}
272
273	DBG("firmware file \"%s\" completed", ofname);
274
275	ret = EXIT_SUCCESS;
276
277out_flush:
278	fflush(f);
279	fclose(f);
280	if (ret != EXIT_SUCCESS) {
281		unlink(ofname);
282	}
283out:
284	return ret;
285}
286
287static uint32_t get_csum(unsigned char *p, uint32_t len)
288{
289	uint32_t csum = 0;
290
291	while (len--)
292		csum += *p++;
293
294	return csum;
295}
296
297static int build_fw(void)
298{
299	int buflen;
300	char *buf;
301	char *p;
302	uint32_t csum;
303	struct img_header *hdr;
304	int ret = EXIT_FAILURE;
305
306	buflen = sizeof(struct img_header) +
307		 kernel_info.write_size + rootfs_info.write_size;
308
309	buf = malloc(buflen);
310	if (!buf) {
311		ERR("no memory for buffer\n");
312		goto out;
313	}
314
315	memset(buf, 0, buflen);
316
317	p = buf + sizeof(struct img_header);
318
319	/* read kernel data */
320	ret = read_to_buf(&kernel_info, p);
321	if (ret)
322		goto out_free_buf;
323
324	if (!combined) {
325		p += kernel_info.write_size;
326
327		/* read rootfs data */
328		ret = read_to_buf(&rootfs_info, p);
329		if (ret)
330			goto out_free_buf;
331	}
332
333	csum = get_csum((unsigned char *)(buf + sizeof(struct img_header)),
334			buflen - sizeof(struct img_header));
335
336	/* fill firmware header */
337	hdr = (struct img_header *) buf;
338
339	hdr->checksum = htonl(csum);
340	hdr->image_size = htonl(buflen - sizeof(struct img_header));
341	if (!combined)
342		hdr->kernel_size = htonl(kernel_info.write_size);
343	else
344		hdr->kernel_size = htonl(kernel_size);
345	hdr->header_len = sizeof(struct img_header);
346	strncpy(hdr->model, model, sizeof(hdr->model));
347	strncpy(hdr->signature, signature, sizeof(hdr->signature));
348	strncpy(hdr->version, version, sizeof(hdr->version));
349	strncpy(hdr->region, region, sizeof(hdr->region));
350
351	ret = write_fw(buf, buflen);
352	if (ret)
353		goto out_free_buf;
354
355	ret = EXIT_SUCCESS;
356
357out_free_buf:
358	free(buf);
359out:
360	return ret;
361}
362
363int main(int argc, char *argv[])
364{
365	int ret = EXIT_FAILURE;
366
367	progname = basename(argv[0]);
368
369	while (1) {
370		int c;
371
372		c = getopt(argc, argv, "M:S:V:R:k:K:I:r:o:hc");
373		if (c == -1)
374			break;
375
376		switch (c) {
377		case 'M':
378			model = optarg;
379			break;
380		case 'S':
381			signature = optarg;
382			break;
383		case 'V':
384			version = optarg;
385			break;
386		case 'R':
387			region = optarg;
388			break;
389		case 'k':
390			kernel_info.file_name = optarg;
391			break;
392		case 'K':
393			if (str2u32(optarg, &kernel_size)) {
394				ERR("%s is invalid '%s'",
395				    "kernel size", optarg);
396				goto out;
397			}
398			break;
399		case 'I':
400			if (str2u32(optarg, &image_size)) {
401				ERR("%s is invalid '%s'",
402				    "image size", optarg);
403				goto out;
404			}
405			break;
406		case 'r':
407			rootfs_info.file_name = optarg;
408			break;
409		case 'c':
410			combined = 1;
411			break;
412		case 'o':
413			ofname = optarg;
414			break;
415		case 'h':
416			usage(EXIT_SUCCESS);
417			break;
418		default:
419			usage(EXIT_FAILURE);
420			break;
421		}
422	}
423
424	ret = check_options();
425	if (ret)
426		goto out;
427
428	ret = build_fw();
429
430out:
431	return ret;
432}
433
434