| /* Copyright 2022 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <getopt.h> |
| |
| #include "flash_helpers.h" |
| #include "futility.h" |
| #include "updater.h" |
| |
| #ifdef USE_FLASHROM |
| |
| /* Command line options */ |
| static struct option const long_opts[] = { |
| SHARED_FLASH_ARGS_LONGOPTS |
| /* name has_arg *flag val */ |
| {"help", 0, NULL, 'h'}, |
| {"debug", 0, NULL, 'd'}, |
| {"region", 1, NULL, 'r'}, |
| {"split-output", 0, NULL, 's'}, |
| {"verbose", 0, NULL, 'v'}, |
| {NULL, 0, NULL, 0}, |
| }; |
| |
| static const char *const short_opts = "hdsr:v" SHARED_FLASH_ARGS_SHORTOPTS; |
| |
| static void print_help(int argc, char *argv[]) |
| { |
| printf("\n" |
| "Usage: " MYNAME " %s [OPTIONS] FILE\n" |
| "\n" |
| "Reads AP firmware to the FILE\n" |
| "-d, --debug \tPrint debugging messages\n" |
| "-r, --region=REGIONS \tComma delimited regions to read (optional)\n" |
| "-v, --verbose \tPrint verbose messages\n" |
| "-s, --split-output \tOutput each comma delimited regions to own {FILE}_{region_name} (optional)\n" |
| SHARED_FLASH_ARGS_HELP, |
| argv[0]); |
| } |
| |
| static int append_to_str_array(const char ***arr, const char *str, size_t *len) |
| { |
| const char **expanded_arr = realloc(*arr, sizeof(char *) * ((*len) + 1)); |
| if (!expanded_arr) { |
| WARN("Failed to allocate memory for string array"); |
| return -1; |
| } |
| |
| *arr = expanded_arr; |
| (*arr)[*len] = str; |
| (*len)++; |
| return 0; |
| } |
| |
| static int parse_region_string(const char ***regions, char *str, size_t *rlen) |
| { |
| if (!str) |
| return -1; /* no regions to parse. */ |
| |
| char *savedptr; |
| const char *delim = ","; |
| char *region = strtok_r(str, delim, &savedptr); |
| |
| while (region) { |
| if (append_to_str_array(regions, region, rlen)) |
| return -1; |
| region = strtok_r(NULL, delim, &savedptr); |
| } |
| |
| return 0; |
| } |
| |
| static int read_flash_regions_to_file(struct updater_config *cfg, |
| const char *path, char *str, |
| bool do_split) |
| { |
| int ret = 0; |
| size_t rlen = 0; |
| const char **regions = NULL; |
| |
| if (parse_region_string(®ions, str, &rlen) || !regions || !rlen) { |
| WARN("No parsable regions to process.\n"); |
| ret = -1; |
| goto out_free; |
| } |
| |
| /* Need to read the FMAP to find regions if we are going to write each |
| * region to a separate file. */ |
| if (do_split) { |
| if (append_to_str_array(®ions, FMAP_RO_FMAP, &rlen)) { |
| ret = -1; |
| goto out_free; |
| } |
| } |
| |
| /* Read only the specified regions */ |
| if (flashrom_read_image(&cfg->image_current, regions, |
| rlen, cfg->verbosity + 1)) { |
| ret = -1; |
| goto out_free; |
| } |
| |
| if (!do_split) { |
| if (write_to_file("Wrote AP firmware region to", path, |
| cfg->image_current.data, |
| cfg->image_current.size)) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * The last element of regions is FMAP_RO_FMAP, stop at (rlen-1) to |
| * only process the user-specified regions. |
| */ |
| for (size_t i = 0; i < rlen - 1; i++) { |
| const char *region = regions[i]; |
| |
| struct firmware_section section; |
| if (find_firmware_section(§ion, &cfg->image_current, region)) { |
| ERROR("Region '%s' not found in image.\n", region); |
| ret = -1; |
| goto out_free; |
| } |
| const char *separator = "_"; |
| const size_t fpath_sz = strlen(path) + |
| strlen(separator) + |
| strlen(region) + |
| 1; /* +1 for null termination. */ |
| char *fpath = calloc(1, fpath_sz); |
| if (!fpath) { |
| ret = -1; |
| goto out_free; |
| } |
| snprintf(fpath, fpath_sz, "%s%s%s", path, separator, region); |
| if (write_to_file("Wrote AP firmware region to", |
| fpath, section.data, section.size)) { |
| free(fpath); |
| ret = -1; |
| goto out_free; |
| } |
| free(fpath); |
| } |
| |
| out_free: |
| free(regions); |
| return ret; |
| } |
| |
| static int do_read(int argc, char *argv[]) |
| { |
| struct updater_config *cfg = NULL; |
| struct updater_config_arguments args = {0}; |
| int i, errorcnt = 0; |
| char *regions = NULL; |
| bool do_split = false; |
| |
| opterr = 0; |
| while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { |
| if (handle_flash_argument(&args, i, optarg)) |
| continue; |
| switch (i) { |
| case 'h': |
| print_help(argc, argv); |
| return 0; |
| case 'd': |
| debugging_enabled = 1; |
| args.verbosity++; |
| break; |
| case 'r': |
| regions = strdup(optarg); |
| if (!regions) { |
| ERROR("strdup() returned NULL\n"); |
| return 1; |
| } |
| break; |
| case 's': |
| do_split = true; |
| break; |
| case 'v': |
| args.verbosity++; |
| break; |
| case '?': |
| errorcnt++; |
| if (optopt) |
| ERROR("Unrecognized option: -%c\n", optopt); |
| else if (argv[optind - 1]) |
| ERROR("Unrecognized option (possibly '%s')\n", |
| argv[optind - 1]); |
| else |
| ERROR("Unrecognized option.\n"); |
| break; |
| default: |
| errorcnt++; |
| ERROR("Failed parsing options.\n"); |
| } |
| } |
| if (argc - optind < 1) { |
| ERROR("Missing output filename\n"); |
| print_help(argc, argv); |
| return 1; |
| } |
| const char *output_file_name = argv[optind++]; |
| if (optind < argc) { |
| ERROR("Unexpected arguments.\n"); |
| print_help(argc, argv); |
| return 1; |
| } |
| if (do_split && !regions) { |
| ERROR("Cannot split read of regions without list of regions.\n"); |
| print_help(argc, argv); |
| return 1; |
| } |
| |
| if (setup_flash(&cfg, &args)) { |
| ERROR("While preparing flash\n"); |
| return 1; |
| } |
| |
| if (!regions) { |
| /* full image read. */ |
| int r = load_system_firmware(cfg, &cfg->image_current); |
| /* |
| * Ignore a parse error as we still want to write the file |
| * out in that case |
| */ |
| if (r && r != IMAGE_PARSE_FAILURE) { |
| errorcnt++; |
| goto err; |
| } |
| if (write_to_file("Wrote AP firmware to", output_file_name, |
| cfg->image_current.data, |
| cfg->image_current.size)) { |
| errorcnt++; |
| goto err; |
| } |
| } else { |
| if (read_flash_regions_to_file(cfg, output_file_name, regions, do_split)) |
| errorcnt++; |
| free(regions); |
| } |
| |
| err: |
| teardown_flash(cfg); |
| return !!errorcnt; |
| } |
| #define CMD_HELP_STR "Read AP firmware" |
| |
| #else /* USE_FLASHROM */ |
| |
| static int do_read(int argc, char *argv[]) |
| { |
| FATAL(MYNAME " was built without flashrom support, `read` command unavailable!\n"); |
| return -1; |
| } |
| #define CMD_HELP_STR "Read system firmware (unavailable in this build)" |
| |
| #endif /* !USE_FLASHROM */ |
| |
| DECLARE_FUTIL_COMMAND(read, do_read, VBOOT_VERSION_ALL, CMD_HELP_STR); |