cmake: use extra compiler warnings only on gcc6+
[project/fwtool.git] / fwtool.c
1 /*
2 * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13 #include <sys/types.h>
14 #include <stdio.h>
15 #include <getopt.h>
16 #include <stdbool.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #include "fwimage.h"
22 #include "utils.h"
23 #include "crc32.h"
24
25 #define METADATA_MAXLEN 30 * 1024
26 #define SIGNATURE_MAXLEN 1 * 1024
27
28 #define BUFLEN (METADATA_MAXLEN + SIGNATURE_MAXLEN + 1024)
29
30 enum {
31 MODE_DEFAULT = -1,
32 MODE_EXTRACT = 0,
33 MODE_APPEND = 1,
34 };
35
36 struct data_buf {
37 char *cur;
38 char *prev;
39 int cur_len;
40 int file_len;
41 };
42
43 static FILE *signature_file, *metadata_file, *firmware_file;
44 static int file_mode = MODE_DEFAULT;
45 static bool truncate_file;
46 static bool write_truncated;
47 static bool quiet = false;
48
49 static uint32_t crc_table[256];
50
51 #define msg(...) \
52 do { \
53 if (!quiet) \
54 fprintf(stderr, __VA_ARGS__); \
55 } while (0)
56
57 static int
58 usage(const char *progname)
59 {
60 fprintf(stderr, "Usage: %s <options> <firmware>\n"
61 "\n"
62 "Options:\n"
63 " -S <file>: Append signature file to firmware image\n"
64 " -I <file>: Append metadata file to firmware image\n"
65 " -s <file>: Extract signature file from firmware image\n"
66 " -i <file>: Extract metadata file from firmware image\n"
67 " -t: Remove extracted chunks from firmare image (using -s, -i)\n"
68 " -T: Output firmware image without extracted chunks to stdout (using -s, -i)\n"
69 " -q: Quiet (suppress error messages)\n"
70 "\n", progname);
71 return 1;
72 }
73
74 static FILE *
75 open_file(const char *name, bool write)
76 {
77 FILE *ret;
78
79 if (!strcmp(name, "-"))
80 return write ? stdout : stdin;
81
82 ret = fopen(name, write ? "w" : "r+");
83 if (!ret && !write)
84 ret = fopen(name, "r");
85
86 return ret;
87 }
88
89 static int
90 set_file(FILE **file, const char *name, int mode)
91 {
92 if (file_mode < 0)
93 file_mode = mode;
94 else if (file_mode != mode) {
95 msg("Error: mixing appending and extracting data is not supported\n");
96 return 1;
97 }
98
99 if (*file) {
100 msg("Error: the same append/extract option cannot be used multiple times\n");
101 return 1;
102 }
103
104 *file = open_file(name, mode == MODE_EXTRACT);
105 return !*file;
106 }
107
108 static void
109 trailer_update_crc(struct fwimage_trailer *tr, void *buf, int len)
110 {
111 tr->crc32 = cpu_to_be32(crc32_block(be32_to_cpu(tr->crc32), buf, len, crc_table));
112 }
113
114 static int
115 append_data(FILE *in, FILE *out, struct fwimage_trailer *tr, int maxlen)
116 {
117 while (1) {
118 char buf[512];
119 int len;
120
121 len = fread(buf, 1, sizeof(buf), in);
122 if (!len)
123 break;
124
125 maxlen -= len;
126 if (maxlen < 0)
127 return 1;
128
129 tr->size += len;
130 trailer_update_crc(tr, buf, len);
131 fwrite(buf, len, 1, out);
132 }
133
134 return 0;
135 }
136
137 static void
138 append_trailer(FILE *out, struct fwimage_trailer *tr)
139 {
140 tr->size = cpu_to_be32(tr->size);
141 fwrite(tr, sizeof(*tr), 1, out);
142 trailer_update_crc(tr, tr, sizeof(*tr));
143 }
144
145 static int
146 add_metadata(struct fwimage_trailer *tr)
147 {
148 struct fwimage_header hdr;
149
150 tr->type = FWIMAGE_INFO;
151 tr->size = sizeof(hdr) + sizeof(*tr);
152
153 memset(&hdr, 0, sizeof(hdr));
154 trailer_update_crc(tr, &hdr, sizeof(hdr));
155 fwrite(&hdr, sizeof(hdr), 1, firmware_file);
156
157 if (append_data(metadata_file, firmware_file, tr, METADATA_MAXLEN))
158 return 1;
159
160 append_trailer(firmware_file, tr);
161
162 return 0;
163 }
164
165 static int
166 add_signature(struct fwimage_trailer *tr)
167 {
168 if (!signature_file)
169 return 0;
170
171 tr->type = FWIMAGE_SIGNATURE;
172 tr->size = sizeof(*tr);
173
174 if (append_data(signature_file, firmware_file, tr, SIGNATURE_MAXLEN))
175 return 1;
176
177 append_trailer(firmware_file, tr);
178
179 return 0;
180 }
181
182 static int
183 add_data(const char *name)
184 {
185 struct fwimage_trailer tr;
186 int file_len = 0;
187 int ret = 0;
188
189 memset(&tr, 0, sizeof(tr));
190
191 tr.crc32 = ~0;
192 tr.magic = cpu_to_be32(FWIMAGE_MAGIC);
193
194 firmware_file = fopen(name, "r+");
195 if (!firmware_file) {
196 msg("Failed to open firmware file\n");
197 return 1;
198 }
199
200 while (1) {
201 char buf[512];
202 int len;
203
204 len = fread(buf, 1, sizeof(buf), firmware_file);
205 if (!len)
206 break;
207
208 file_len += len;
209 trailer_update_crc(&tr, buf, len);
210 }
211
212 if (metadata_file)
213 ret = add_metadata(&tr);
214 else if (signature_file)
215 ret = add_signature(&tr);
216
217 if (ret) {
218 fflush(firmware_file);
219 ret = ftruncate(fileno(firmware_file), file_len);
220 if (ret < 0)
221 msg("Error during ftruncate: %m\n");
222 }
223
224 return ret;
225 }
226
227 static void
228 remove_tail(struct data_buf *dbuf, int len)
229 {
230 dbuf->cur_len -= len;
231 dbuf->file_len -= len;
232
233 if (dbuf->cur_len)
234 return;
235
236 free(dbuf->cur);
237 dbuf->cur = dbuf->prev;
238 dbuf->prev = NULL;
239 dbuf->cur_len = BUFLEN;
240 }
241
242 static int
243 extract_tail(struct data_buf *dbuf, void *dest, int len)
244 {
245 int cur_len = dbuf->cur_len;
246
247 if (!dbuf->cur)
248 return 1;
249
250 if (cur_len >= len)
251 cur_len = len;
252
253 memcpy(dest + (len - cur_len), dbuf->cur + dbuf->cur_len - cur_len, cur_len);
254 remove_tail(dbuf, cur_len);
255
256 cur_len = len - cur_len;
257 if (cur_len < 0 || !dbuf->cur)
258 return 1;
259
260 memcpy(dest, dbuf->cur + dbuf->cur_len - cur_len, cur_len);
261 remove_tail(dbuf, cur_len);
262
263 return 0;
264 }
265
266 static uint32_t
267 tail_crc32(struct data_buf *dbuf, uint32_t crc32)
268 {
269 if (dbuf->prev)
270 crc32 = crc32_block(crc32, dbuf->prev, BUFLEN, crc_table);
271
272 return crc32_block(crc32, dbuf->cur, dbuf->cur_len, crc_table);
273 }
274
275 static int
276 validate_metadata(struct fwimage_header *hdr, int data_len)
277 {
278 if (hdr->version != 0)
279 return 1;
280 return 0;
281 }
282
283 static int
284 extract_data(const char *name)
285 {
286 struct fwimage_header *hdr;
287 struct fwimage_trailer tr;
288 struct data_buf dbuf = {};
289 uint32_t crc32 = ~0;
290 int data_len = 0;
291 int ret = 1;
292 void *buf;
293 bool metadata_keep = false;
294
295 memset(&tr, 0, sizeof(tr));
296
297 firmware_file = open_file(name, false);
298 if (!firmware_file) {
299 msg("Failed to open firmware file\n");
300 return 1;
301 }
302
303 if (truncate_file && firmware_file == stdin) {
304 msg("Cannot truncate file when reading from stdin\n");
305 return 1;
306 }
307
308 buf = malloc(BUFLEN);
309 if (!buf)
310 return 1;
311
312 do {
313 char *tmp = dbuf.cur;
314
315 if (write_truncated && dbuf.prev)
316 fwrite(dbuf.prev, 1, BUFLEN, stdout);
317
318 dbuf.cur = dbuf.prev;
319 dbuf.prev = tmp;
320
321 if (dbuf.cur)
322 crc32 = crc32_block(crc32, dbuf.cur, BUFLEN, crc_table);
323 else
324 dbuf.cur = malloc(BUFLEN);
325
326 if (!dbuf.cur)
327 goto out;
328
329 dbuf.cur_len = fread(dbuf.cur, 1, BUFLEN, firmware_file);
330 dbuf.file_len += dbuf.cur_len;
331 } while (dbuf.cur_len == BUFLEN);
332
333 while (1) {
334
335 if (extract_tail(&dbuf, &tr, sizeof(tr))) {
336 msg("unable to extract trailer header\n");
337 break;
338 }
339
340 if (tr.magic != cpu_to_be32(FWIMAGE_MAGIC)) {
341 msg("Data not found\n");
342 metadata_keep = true;
343 break;
344 }
345
346 data_len = be32_to_cpu(tr.size) - sizeof(tr);
347
348 if (be32_to_cpu(tr.crc32) != tail_crc32(&dbuf, crc32)) {
349 msg("CRC error\n");
350 break;
351 }
352
353 if (data_len > BUFLEN) {
354 msg("Size error\n");
355 break;
356 }
357
358 if (extract_tail(&dbuf, buf, data_len)) {
359 msg("unable to extract trailer data\n");
360 break;
361 }
362
363 if (tr.type == FWIMAGE_SIGNATURE) {
364 if (!signature_file)
365 continue;
366 fwrite(buf, data_len, 1, signature_file);
367 ret = 0;
368 break;
369 } else if (tr.type == FWIMAGE_INFO) {
370 if (!metadata_file) {
371 dbuf.file_len += data_len + sizeof(tr);
372 metadata_keep = true;
373 break;
374 }
375
376 hdr = buf;
377 data_len -= sizeof(*hdr);
378 if (validate_metadata(hdr, data_len))
379 continue;
380
381 fwrite(hdr + 1, data_len, 1, metadata_file);
382 ret = 0;
383 break;
384 } else {
385 continue;
386 }
387 }
388
389 if (!ret && truncate_file) {
390 ret = ftruncate(fileno(firmware_file), dbuf.file_len);
391 if (ret < 0) {
392 msg("Error during ftruncate: %m\n");
393 goto out;
394 }
395 }
396
397 if (write_truncated) {
398 if (dbuf.prev)
399 fwrite(dbuf.prev, 1, BUFLEN, stdout);
400 if (dbuf.cur)
401 fwrite(dbuf.cur, 1, dbuf.cur_len, stdout);
402 if (metadata_keep) {
403 fwrite(buf, data_len, 1, stdout);
404 fwrite(&tr, sizeof(tr), 1, stdout);
405 }
406 }
407
408 out:
409 free(buf);
410 free(dbuf.cur);
411 free(dbuf.prev);
412 return ret;
413 }
414
415 static void cleanup(void)
416 {
417 if (signature_file)
418 fclose(signature_file);
419 if (metadata_file)
420 fclose(metadata_file);
421 if (firmware_file)
422 fclose(firmware_file);
423 }
424
425 int main(int argc, char **argv)
426 {
427 const char *progname = argv[0];
428 int ret, ch;
429
430 crc32_filltable(crc_table);
431
432 while ((ch = getopt(argc, argv, "i:I:qs:S:tT")) != -1) {
433 ret = 0;
434 switch(ch) {
435 case 'S':
436 ret = set_file(&signature_file, optarg, MODE_APPEND);
437 break;
438 case 'I':
439 ret = set_file(&metadata_file, optarg, MODE_APPEND);
440 break;
441 case 's':
442 ret = set_file(&signature_file, optarg, MODE_EXTRACT);
443 break;
444 case 'i':
445 ret = set_file(&metadata_file, optarg, MODE_EXTRACT);
446 break;
447 case 't':
448 truncate_file = true;
449 break;
450 case 'T':
451 write_truncated = true;
452 break;
453 case 'q':
454 quiet = true;
455 break;
456 }
457
458 if (ret)
459 goto out;
460 }
461
462 if (optind >= argc) {
463 ret = usage(progname);
464 goto out;
465 }
466
467 if (file_mode == MODE_DEFAULT) {
468 ret = usage(progname);
469 goto out;
470 }
471
472 if (signature_file && metadata_file) {
473 msg("Cannot append/extract metadata and signature in one run\n");
474 return 1;
475 }
476
477 if (file_mode)
478 ret = add_data(argv[optind]);
479 else
480 ret = extract_data(argv[optind]);
481
482 out:
483 cleanup();
484 return ret;
485 }