kernel/modules: kmod-sched-core: add missing dependency, useful module
[oweals/openwrt.git] / package / system / fwtool / src / 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 quiet = false;
47
48 static uint32_t crc_table[256];
49
50 #define msg(...)                                        \
51         do {                                            \
52                 if (!quiet)                             \
53                         fprintf(stderr, __VA_ARGS__);   \
54         } while (0)
55
56 static int
57 usage(const char *progname)
58 {
59         fprintf(stderr, "Usage: %s <options> <firmware>\n"
60                 "\n"
61                 "Options:\n"
62                 "  -S <file>:           Append signature file to firmware image\n"
63                 "  -I <file>:           Append metadata file to firmware image\n"
64                 "  -s <file>:           Extract signature file from firmware image\n"
65                 "  -i <file>:           Extract metadata file from firmware image\n"
66                 "  -t:                  Remove extracted chunks from firmare image (using -s, -i)\n"
67                 "  -q:                  Quiet (suppress error messages)\n"
68                 "\n", progname);
69         return 1;
70 }
71
72 static FILE *
73 open_file(const char *name, bool write)
74 {
75         FILE *ret;
76
77         if (!strcmp(name, "-"))
78                 return write ? stdout : stdin;
79
80         ret = fopen(name, write ? "w" : "r+");
81         if (!ret && !write)
82                 ret = fopen(name, "r");
83
84         return ret;
85 }
86
87 static int
88 set_file(FILE **file, const char *name, int mode)
89 {
90         if (file_mode < 0)
91                 file_mode = mode;
92         else if (file_mode != mode) {
93                 msg("Error: mixing appending and extracting data is not supported\n");
94                 return 1;
95         }
96
97         if (*file) {
98                 msg("Error: the same append/extract option cannot be used multiple times\n");
99                 return 1;
100         }
101
102         *file = open_file(name, mode == MODE_EXTRACT);
103         return !*file;
104 }
105
106 static void
107 trailer_update_crc(struct fwimage_trailer *tr, void *buf, int len)
108 {
109         tr->crc32 = cpu_to_be32(crc32_block(be32_to_cpu(tr->crc32), buf, len, crc_table));
110 }
111
112 static int
113 append_data(FILE *in, FILE *out, struct fwimage_trailer *tr, int maxlen)
114 {
115         while (1) {
116                 char buf[512];
117                 int len;
118
119                 len = fread(buf, 1, sizeof(buf), in);
120                 if (!len)
121                         break;
122
123                 maxlen -= len;
124                 if (maxlen < 0)
125                         return 1;
126
127                 tr->size += len;
128                 trailer_update_crc(tr, buf, len);
129                 fwrite(buf, len, 1, out);
130         }
131
132         return 0;
133 }
134
135 static void
136 append_trailer(FILE *out, struct fwimage_trailer *tr)
137 {
138         tr->size = cpu_to_be32(tr->size);
139         fwrite(tr, sizeof(*tr), 1, out);
140         trailer_update_crc(tr, tr, sizeof(*tr));
141 }
142
143 static int
144 add_metadata(struct fwimage_trailer *tr)
145 {
146         struct fwimage_header hdr = {};
147
148         tr->type = FWIMAGE_INFO;
149         tr->size = sizeof(hdr) + sizeof(*tr);
150
151         trailer_update_crc(tr, &hdr, sizeof(hdr));
152         fwrite(&hdr, sizeof(hdr), 1, firmware_file);
153
154         if (append_data(metadata_file, firmware_file, tr, METADATA_MAXLEN))
155                 return 1;
156
157         append_trailer(firmware_file, tr);
158
159         return 0;
160 }
161
162 static int
163 add_signature(struct fwimage_trailer *tr)
164 {
165         if (!signature_file)
166                 return 0;
167
168         tr->type = FWIMAGE_SIGNATURE;
169         tr->size = sizeof(*tr);
170
171         if (append_data(signature_file, firmware_file, tr, SIGNATURE_MAXLEN))
172                 return 1;
173
174         append_trailer(firmware_file, tr);
175
176         return 0;
177 }
178
179 static int
180 add_data(const char *name)
181 {
182         struct fwimage_trailer tr = {
183                 .magic = cpu_to_be32(FWIMAGE_MAGIC),
184                 .crc32 = ~0,
185         };
186         int file_len = 0;
187         int ret = 0;
188
189         firmware_file = fopen(name, "r+");
190         if (!firmware_file) {
191                 msg("Failed to open firmware file\n");
192                 return 1;
193         }
194
195         while (1) {
196                 char buf[512];
197                 int len;
198
199                 len = fread(buf, 1, sizeof(buf), firmware_file);
200                 if (!len)
201                         break;
202
203                 file_len += len;
204                 trailer_update_crc(&tr, buf, len);
205         }
206
207         if (metadata_file)
208                 ret = add_metadata(&tr);
209         else if (signature_file)
210                 ret = add_signature(&tr);
211
212         if (ret) {
213                 fflush(firmware_file);
214                 ftruncate(fileno(firmware_file), file_len);
215         }
216
217         return ret;
218 }
219
220 static void
221 remove_tail(struct data_buf *dbuf, int len)
222 {
223         dbuf->cur_len -= len;
224         dbuf->file_len -= len;
225
226         if (dbuf->cur_len)
227                 return;
228
229         free(dbuf->cur);
230         dbuf->cur = dbuf->prev;
231         dbuf->prev = NULL;
232         dbuf->cur_len = BUFLEN;
233 }
234
235 static int
236 extract_tail(struct data_buf *dbuf, void *dest, int len)
237 {
238         int cur_len = dbuf->cur_len;
239
240         if (!dbuf->cur)
241                 return 1;
242
243         if (cur_len >= len)
244                 cur_len = len;
245
246         memcpy(dest + (len - cur_len), dbuf->cur + dbuf->cur_len - cur_len, cur_len);
247         remove_tail(dbuf, cur_len);
248
249         cur_len = len - cur_len;
250         if (cur_len && !dbuf->cur)
251                 return 1;
252
253         memcpy(dest, dbuf->cur + dbuf->cur_len - cur_len, cur_len);
254         remove_tail(dbuf, cur_len);
255
256         return 0;
257 }
258
259 static uint32_t
260 tail_crc32(struct data_buf *dbuf, uint32_t crc32)
261 {
262         if (dbuf->prev)
263                 crc32 = crc32_block(crc32, dbuf->prev, BUFLEN, crc_table);
264
265         return crc32_block(crc32, dbuf->cur, dbuf->cur_len, crc_table);
266 }
267
268 static int
269 validate_metadata(struct fwimage_header *hdr, int data_len)
270 {
271          if (hdr->version != 0)
272                  return 1;
273          return 0;
274 }
275
276 static int
277 extract_data(const char *name)
278 {
279         struct fwimage_header *hdr;
280         struct fwimage_trailer tr;
281         struct data_buf dbuf = {};
282         uint32_t crc32 = ~0;
283         int ret = 1;
284         void *buf;
285
286         firmware_file = open_file(name, false);
287         if (!firmware_file) {
288                 msg("Failed to open firmware file\n");
289                 return 1;
290         }
291
292         if (truncate_file && firmware_file == stdin) {
293                 msg("Cannot truncate file when reading from stdin\n");
294                 return 1;
295         }
296
297         buf = malloc(BUFLEN);
298         if (!buf)
299                 return 1;
300
301         do {
302                 char *tmp = dbuf.cur;
303
304                 dbuf.cur = dbuf.prev;
305                 dbuf.prev = tmp;
306
307                 if (dbuf.cur)
308                         crc32 = crc32_block(crc32, dbuf.cur, BUFLEN, crc_table);
309                 else
310                         dbuf.cur = malloc(BUFLEN);
311
312                 if (!dbuf.cur)
313                         goto out;
314
315                 dbuf.cur_len = fread(dbuf.cur, 1, BUFLEN, firmware_file);
316                 dbuf.file_len += dbuf.cur_len;
317         } while (dbuf.cur_len == BUFLEN);
318
319         while (1) {
320                 int data_len;
321
322                 if (extract_tail(&dbuf, &tr, sizeof(tr)))
323                         break;
324
325                 data_len = be32_to_cpu(tr.size) - sizeof(tr);
326                 if (tr.magic != cpu_to_be32(FWIMAGE_MAGIC)) {
327                         msg("Data not found\n");
328                         break;
329                 }
330
331                 if (be32_to_cpu(tr.crc32) != tail_crc32(&dbuf, crc32)) {
332                         msg("CRC error\n");
333                         break;
334                 }
335
336                 if (data_len > BUFLEN) {
337                         msg("Size error\n");
338                         break;
339                 }
340
341                 extract_tail(&dbuf, buf, data_len);
342
343                 if (tr.type == FWIMAGE_SIGNATURE) {
344                         if (!signature_file)
345                                 continue;
346                         fwrite(buf, data_len, 1, signature_file);
347                         ret = 0;
348                         break;
349                 } else if (tr.type == FWIMAGE_INFO) {
350                         if (!metadata_file)
351                                 break;
352
353                         hdr = buf;
354                         data_len -= sizeof(*hdr);
355                         if (validate_metadata(hdr, data_len))
356                                 continue;
357
358                         fwrite(hdr + 1, data_len, 1, metadata_file);
359                         ret = 0;
360                         break;
361                 } else {
362                         continue;
363                 }
364         }
365
366         if (!ret && truncate_file)
367                 ftruncate(fileno(firmware_file), dbuf.file_len);
368
369 out:
370         free(buf);
371         free(dbuf.cur);
372         free(dbuf.prev);
373         return ret;
374 }
375
376 static void cleanup(void)
377 {
378         if (signature_file)
379                 fclose(signature_file);
380         if (metadata_file)
381                 fclose(metadata_file);
382         if (firmware_file)
383                 fclose(firmware_file);
384 }
385
386 int main(int argc, char **argv)
387 {
388         const char *progname = argv[0];
389         int ret, ch;
390
391         crc32_filltable(crc_table);
392
393         while ((ch = getopt(argc, argv, "i:I:qs:S:t")) != -1) {
394                 ret = 0;
395                 switch(ch) {
396                 case 'S':
397                         ret = set_file(&signature_file, optarg, MODE_APPEND);
398                         break;
399                 case 'I':
400                         ret = set_file(&metadata_file, optarg, MODE_APPEND);
401                         break;
402                 case 's':
403                         ret = set_file(&signature_file, optarg, MODE_EXTRACT);
404                         break;
405                 case 'i':
406                         ret = set_file(&metadata_file, optarg, MODE_EXTRACT);
407                         break;
408                 case 't':
409                         truncate_file = true;
410                         break;
411                 case 'q':
412                         quiet = true;
413                         break;
414                 }
415
416                 if (ret)
417                         goto out;
418         }
419
420         if (optind >= argc) {
421                 ret = usage(progname);
422                 goto out;
423         }
424
425         if (file_mode == MODE_DEFAULT) {
426                 ret = usage(progname);
427                 goto out;
428         }
429
430         if (signature_file && metadata_file) {
431                 msg("Cannot append/extract metadata and signature in one run\n");
432                 return 1;
433         }
434
435         if (file_mode)
436                 ret = add_data(argv[optind]);
437         else
438                 ret = extract_data(argv[optind]);
439
440 out:
441         cleanup();
442         return ret;
443 }