diff options
Diffstat (limited to 'package/system/fwtool/src/fwtool.c')
-rw-r--r-- | package/system/fwtool/src/fwtool.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/package/system/fwtool/src/fwtool.c b/package/system/fwtool/src/fwtool.c new file mode 100644 index 0000000000..e77b8b58e1 --- /dev/null +++ b/package/system/fwtool/src/fwtool.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2016 Felix Fietkau <nbd@nbd.name> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <sys/types.h> +#include <stdio.h> +#include <getopt.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "fwimage.h" +#include "utils.h" +#include "crc32.h" + +#define METADATA_MAXLEN 30 * 1024 +#define SIGNATURE_MAXLEN 1 * 1024 + +#define BUFLEN (METADATA_MAXLEN + SIGNATURE_MAXLEN + 1024) + +enum { + MODE_DEFAULT = -1, + MODE_EXTRACT = 0, + MODE_APPEND = 1, +}; + +struct data_buf { + char *cur; + char *prev; + int cur_len; + int file_len; +}; + +static FILE *signature_file, *metadata_file, *firmware_file; +static int file_mode = MODE_DEFAULT; +static bool truncate_file; +static bool quiet = false; + +static uint32_t crc_table[256]; + +#define msg(...) \ + do { \ + if (!quiet) \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) + +static int +usage(const char *progname) +{ + fprintf(stderr, "Usage: %s <options> <firmware>\n" + "\n" + "Options:\n" + " -S <file>: Append signature file to firmware image\n" + " -I <file>: Append metadata file to firmware image\n" + " -s <file>: Extract signature file from firmware image\n" + " -i <file>: Extract metadata file from firmware image\n" + " -t: Remove extracted chunks from firmare image (using -s, -i)\n" + " -q: Quiet (suppress error messages)\n" + "\n", progname); + return 1; +} + +static FILE * +open_file(const char *name, bool write) +{ + FILE *ret; + + if (!strcmp(name, "-")) + return write ? stdout : stdin; + + ret = fopen(name, write ? "w" : "r+"); + if (!ret && !write) + ret = fopen(name, "r"); + + return ret; +} + +static int +set_file(FILE **file, const char *name, int mode) +{ + if (file_mode < 0) + file_mode = mode; + else if (file_mode != mode) { + msg("Error: mixing appending and extracting data is not supported\n"); + return 1; + } + + if (*file) { + msg("Error: the same append/extract option cannot be used multiple times\n"); + return 1; + } + + *file = open_file(name, mode == MODE_EXTRACT); + return !*file; +} + +static void +trailer_update_crc(struct fwimage_trailer *tr, void *buf, int len) +{ + tr->crc32 = cpu_to_be32(crc32_block(be32_to_cpu(tr->crc32), buf, len, crc_table)); +} + +static int +append_data(FILE *in, FILE *out, struct fwimage_trailer *tr, int maxlen) +{ + while (1) { + char buf[512]; + int len; + + len = fread(buf, 1, sizeof(buf), in); + if (!len) + break; + + maxlen -= len; + if (maxlen < 0) + return 1; + + tr->size += len; + trailer_update_crc(tr, buf, len); + fwrite(buf, len, 1, out); + } + + return 0; +} + +static void +append_trailer(FILE *out, struct fwimage_trailer *tr) +{ + tr->size = cpu_to_be32(tr->size); + fwrite(tr, sizeof(*tr), 1, out); + trailer_update_crc(tr, tr, sizeof(*tr)); +} + +static int +add_metadata(struct fwimage_trailer *tr) +{ + struct fwimage_header hdr = {}; + + tr->type = FWIMAGE_INFO; + tr->size = sizeof(hdr) + sizeof(*tr); + + trailer_update_crc(tr, &hdr, sizeof(hdr)); + fwrite(&hdr, sizeof(hdr), 1, firmware_file); + + if (append_data(metadata_file, firmware_file, tr, METADATA_MAXLEN)) + return 1; + + append_trailer(firmware_file, tr); + + return 0; +} + +static int +add_signature(struct fwimage_trailer *tr) +{ + if (!signature_file) + return 0; + + tr->type = FWIMAGE_SIGNATURE; + tr->size = sizeof(*tr); + + if (append_data(signature_file, firmware_file, tr, SIGNATURE_MAXLEN)) + return 1; + + append_trailer(firmware_file, tr); + + return 0; +} + +static int +add_data(const char *name) +{ + struct fwimage_trailer tr = { + .magic = cpu_to_be32(FWIMAGE_MAGIC), + .crc32 = ~0, + }; + int file_len = 0; + int ret = 0; + + firmware_file = fopen(name, "r+"); + if (!firmware_file) { + msg("Failed to open firmware file\n"); + return 1; + } + + while (1) { + char buf[512]; + int len; + + len = fread(buf, 1, sizeof(buf), firmware_file); + if (!len) + break; + + file_len += len; + trailer_update_crc(&tr, buf, len); + } + + if (metadata_file) + ret = add_metadata(&tr); + else if (signature_file) + ret = add_signature(&tr); + + if (ret) { + fflush(firmware_file); + ftruncate(fileno(firmware_file), file_len); + } + + return ret; +} + +static void +remove_tail(struct data_buf *dbuf, int len) +{ + dbuf->cur_len -= len; + dbuf->file_len -= len; + + if (dbuf->cur_len) + return; + + free(dbuf->cur); + dbuf->cur = dbuf->prev; + dbuf->prev = NULL; + dbuf->cur_len = BUFLEN; +} + +static int +extract_tail(struct data_buf *dbuf, void *dest, int len) +{ + int cur_len = dbuf->cur_len; + + if (!dbuf->cur) + return 1; + + if (cur_len >= len) + cur_len = len; + + memcpy(dest + (len - cur_len), dbuf->cur + dbuf->cur_len - cur_len, cur_len); + remove_tail(dbuf, cur_len); + + cur_len = len - cur_len; + if (cur_len && !dbuf->cur) + return 1; + + memcpy(dest, dbuf->cur + dbuf->cur_len - cur_len, cur_len); + remove_tail(dbuf, cur_len); + + return 0; +} + +static uint32_t +tail_crc32(struct data_buf *dbuf, uint32_t crc32) +{ + if (dbuf->prev) + crc32 = crc32_block(crc32, dbuf->prev, BUFLEN, crc_table); + + return crc32_block(crc32, dbuf->cur, dbuf->cur_len, crc_table); +} + +static int +validate_metadata(struct fwimage_header *hdr, int data_len) +{ + if (hdr->version != 0) + return 1; + return 0; +} + +static int +extract_data(const char *name) +{ + struct fwimage_header *hdr; + struct fwimage_trailer tr; + struct data_buf dbuf = {}; + uint32_t crc32 = ~0; + int ret = 1; + void *buf; + + firmware_file = open_file(name, false); + if (!firmware_file) { + msg("Failed to open firmware file\n"); + return 1; + } + + if (truncate_file && firmware_file == stdin) { + msg("Cannot truncate file when reading from stdin\n"); + return 1; + } + + buf = malloc(BUFLEN); + if (!buf) + return 1; + + do { + char *tmp = dbuf.cur; + + dbuf.cur = dbuf.prev; + dbuf.prev = tmp; + + if (dbuf.cur) + crc32 = crc32_block(crc32, dbuf.cur, BUFLEN, crc_table); + else + dbuf.cur = malloc(BUFLEN); + + if (!dbuf.cur) + goto out; + + dbuf.cur_len = fread(dbuf.cur, 1, BUFLEN, firmware_file); + dbuf.file_len += dbuf.cur_len; + } while (dbuf.cur_len == BUFLEN); + + while (1) { + int data_len; + + if (extract_tail(&dbuf, &tr, sizeof(tr))) + break; + + data_len = be32_to_cpu(tr.size) - sizeof(tr); + if (tr.magic != cpu_to_be32(FWIMAGE_MAGIC)) { + msg("Data not found\n"); + break; + } + + if (be32_to_cpu(tr.crc32) != tail_crc32(&dbuf, crc32)) { + msg("CRC error\n"); + break; + } + + if (data_len > BUFLEN) { + msg("Size error\n"); + break; + } + + extract_tail(&dbuf, buf, data_len); + + if (tr.type == FWIMAGE_SIGNATURE) { + if (!signature_file) + continue; + fwrite(buf, data_len, 1, signature_file); + ret = 0; + break; + } else if (tr.type == FWIMAGE_INFO) { + if (!metadata_file) + break; + + hdr = buf; + data_len -= sizeof(*hdr); + if (validate_metadata(hdr, data_len)) + continue; + + fwrite(hdr + 1, data_len, 1, metadata_file); + ret = 0; + break; + } else { + continue; + } + } + + if (!ret && truncate_file) + ftruncate(fileno(firmware_file), dbuf.file_len); + +out: + free(buf); + free(dbuf.cur); + free(dbuf.prev); + return ret; +} + +static void cleanup(void) +{ + if (signature_file) + fclose(signature_file); + if (metadata_file) + fclose(metadata_file); + if (firmware_file) + fclose(firmware_file); +} + +int main(int argc, char **argv) +{ + const char *progname = argv[0]; + int ret, ch; + + crc32_filltable(crc_table); + + while ((ch = getopt(argc, argv, "i:I:qs:S:t")) != -1) { + ret = 0; + switch(ch) { + case 'S': + ret = set_file(&signature_file, optarg, MODE_APPEND); + break; + case 'I': + ret = set_file(&metadata_file, optarg, MODE_APPEND); + break; + case 's': + ret = set_file(&signature_file, optarg, MODE_EXTRACT); + break; + case 'i': + ret = set_file(&metadata_file, optarg, MODE_EXTRACT); + break; + case 't': + truncate_file = true; + break; + case 'q': + quiet = true; + break; + } + + if (ret) + goto out; + } + + if (optind >= argc) { + ret = usage(progname); + goto out; + } + + if (file_mode == MODE_DEFAULT) { + ret = usage(progname); + goto out; + } + + if (signature_file && metadata_file) { + msg("Cannot append/extract metadata and signature in one run\n"); + return 1; + } + + if (file_mode) + ret = add_data(argv[optind]); + else + ret = extract_data(argv[optind]); + +out: + cleanup(); + return ret; +} |