| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * FWU Metadata Read/Write Tool |
| * Copyright (c) 2025, Kory Maincent <kory.maincent@bootlin.com> |
| * |
| * Tool to read, display, and modify FWU (Firmware Update) metadata |
| * from Linux userspace. Similar to fw_printenv/fw_setenv for U-Boot |
| * environment, but for FWU metadata. |
| * |
| * Usage: |
| * fwumdata - Print all metadata |
| * fwumdata -u - Print metadata and update it if CRC corrupted |
| * fwumdata -c <config> - Use custom config file |
| * fwumdata -a <bank> - Set active bank |
| * fwumdata -p <bank> - Set previous bank |
| * fwumdata -s <bank> <state> - Set bank state (V2 only) |
| * fwumdata -i <id> -b <bank> -A - Accept image |
| * fwumdata -i <id> -b <bank> -C - Clear image acceptance |
| * fwumdata -i <id> -b <bank> |
| * -B <num_banks> |
| * -I <num_images> -C - Clear image acceptance (V1 only) |
| * fwumdata -l - List detailed info with GUIDs |
| */ |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <mtd/mtd-user.h> |
| #include <sys/ioctl.h> |
| #include <u-boot/crc.h> |
| #include "fwumdata.h" |
| |
| /* Device configuration */ |
| struct fwumdata_device { |
| const char *devname; |
| long long devoff; |
| unsigned long mdata_size; |
| unsigned long erase_size; |
| int fd; |
| bool is_mtd; |
| }; |
| |
| /* Global state */ |
| static struct fwumdata_device devices[2]; /* Primary and secondary */ |
| static struct fwu_mdata *mdata; |
| static int have_redundant; |
| static struct fwu_mdata *valid_mdata; |
| static bool mdata_mod; |
| static const char *config_file; |
| static int nbanks, nimages; /* For V1 only */ |
| static const char * const default_config_files[] = { |
| "./fwumdata.config", |
| "/etc/fwumdata.config", |
| NULL |
| }; |
| |
| /* GUID/UUID utilities */ |
| static void guid_to_string(const struct efi_guid *guid, char *str) |
| { |
| sprintf(str, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
| guid->time_high, guid->time_low, guid->reserved, |
| guid->family, guid->node[0], |
| guid->node[1], guid->node[2], guid->node[3], |
| guid->node[4], guid->node[5], guid->node[6]); |
| } |
| |
| /* Config file parsing */ |
| static int parse_config(const char *fname) |
| { |
| size_t linesize = 0; |
| char *line = NULL; |
| char *devname; |
| int i = 0; |
| FILE *fp; |
| int rc; |
| |
| fp = fopen(fname, "r"); |
| if (!fp) |
| return -ENOENT; |
| |
| while (i < 2 && getline(&line, &linesize, fp) != -1) { |
| /* Skip comments and empty lines */ |
| if (line[0] == '#' || line[0] == '\n') |
| continue; |
| |
| devname = NULL; |
| rc = sscanf(line, "%ms %lli %lx %lx", |
| &devname, |
| &devices[i].devoff, |
| &devices[i].mdata_size, |
| &devices[i].erase_size); |
| |
| if (rc < 3) { |
| free(devname); |
| continue; |
| } |
| |
| if (rc < 4) |
| devices[i].erase_size = devices[i].mdata_size; |
| |
| devices[i].devname = devname; |
| i++; |
| } |
| |
| free(line); |
| fclose(fp); |
| |
| if (i == 2) { |
| have_redundant = true; |
| if (devices[0].mdata_size != devices[1].mdata_size) { |
| fprintf(stderr, |
| "Size mismatch between the two metadata\n"); |
| return -EINVAL; |
| } |
| } |
| |
| if (!i) { |
| fprintf(stderr, |
| "Can't read config %s content\n", fname); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int find_parse_config(void) |
| { |
| int i; |
| |
| if (config_file) |
| return parse_config(config_file); |
| |
| for (i = 0; default_config_files[i]; i++) { |
| int ret; |
| |
| ret = parse_config(default_config_files[i]); |
| if (ret == -ENOENT) |
| continue; |
| if (ret) |
| return ret; |
| |
| config_file = default_config_files[i]; |
| return 0; |
| } |
| |
| fprintf(stderr, "Error: Cannot find config file\n"); |
| return -ENOENT; |
| } |
| |
| static int open_device(struct fwumdata_device *dev) |
| { |
| if (strstr(dev->devname, "/dev/mtd")) |
| dev->is_mtd = true; |
| |
| dev->fd = open(dev->devname, O_RDWR | O_SYNC); |
| if (dev->fd < 0) { |
| fprintf(stderr, "Cannot open %s: %s\n", dev->devname, |
| strerror(errno)); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int mtd_erase(int fd, unsigned long offset, unsigned long size) |
| { |
| struct erase_info_user erase; |
| int ret; |
| |
| erase.start = offset; |
| erase.length = size; |
| |
| ret = ioctl(fd, MEMERASE, &erase); |
| if (ret < 0) { |
| fprintf(stderr, "MTD erase failed: %s\n", strerror(errno)); |
| return -errno; |
| } |
| |
| return 0; |
| } |
| |
| static int read_device(struct fwumdata_device *dev, void *buf, size_t count) |
| { |
| if (lseek(dev->fd, dev->devoff, SEEK_SET) < 0) { |
| fprintf(stderr, "Seek failed: %s\n", strerror(errno)); |
| return -errno; |
| } |
| |
| if (read(dev->fd, buf, count) < 0) { |
| fprintf(stderr, "Read failed: %s\n", strerror(errno)); |
| return -errno; |
| } |
| |
| return 0; |
| } |
| |
| static int write_device(struct fwumdata_device *dev, const void *buf, |
| size_t count) |
| { |
| int ret; |
| |
| /* Erase if MTD device */ |
| if (dev->is_mtd) { |
| ret = mtd_erase(dev->fd, dev->devoff, dev->erase_size); |
| if (ret) |
| return ret; |
| } |
| |
| if (lseek(dev->fd, dev->devoff, SEEK_SET) < 0) { |
| fprintf(stderr, "Seek failed: %s\n", strerror(errno)); |
| return -errno; |
| } |
| |
| if (write(dev->fd, buf, count) < 0) { |
| fprintf(stderr, "Write failed: %s\n", strerror(errno)); |
| return -errno; |
| } |
| |
| return 0; |
| } |
| |
| /* Metadata operations */ |
| static int validate_crc(struct fwu_mdata *mdata, size_t size) |
| { |
| u32 calc_crc, stored_crc; |
| |
| stored_crc = mdata->crc32; |
| calc_crc = crc32(0, (const u8 *)&mdata->version, size - sizeof(u32)); |
| |
| if (calc_crc != stored_crc) { |
| fprintf(stderr, |
| "CRC mismatch: calculated 0x%08x, stored 0x%08x\n", |
| calc_crc, stored_crc); |
| if (mdata->version == 1) |
| fprintf(stderr, |
| "Metadata is V1, this may be size description issue\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void update_crc(struct fwu_mdata *mdata, size_t size) |
| { |
| mdata->crc32 = crc32(0, (const u8 *)&mdata->version, size - sizeof(u32)); |
| } |
| |
| static int read_one_metadata(int mdata_id, size_t size) |
| { |
| int ret; |
| |
| ret = open_device(&devices[mdata_id]); |
| if (ret) |
| return ret; |
| |
| ret = read_device(&devices[mdata_id], &mdata[mdata_id], size); |
| if (ret) |
| return ret; |
| |
| if (mdata[mdata_id].version != 1 && mdata[mdata_id].version != 2) { |
| fprintf(stderr, "Invalid metadata %d version: %u\n", |
| mdata_id, mdata[mdata_id].version); |
| } |
| |
| return 0; |
| } |
| |
| static int read_metadata(bool update) |
| { |
| size_t alloc_size; |
| int ret; |
| |
| /* Allocate initial buffer */ |
| alloc_size = devices[0].mdata_size; |
| mdata = calloc(have_redundant ? 2 : 1, alloc_size); |
| if (!mdata) { |
| fprintf(stderr, "Memory allocation failed\n"); |
| return -ENOMEM; |
| } |
| |
| ret = read_one_metadata(0, alloc_size); |
| if (ret) |
| return ret; |
| |
| if (validate_crc(&mdata[0], alloc_size) < 0) { |
| fprintf(stderr, |
| "Warning: Primary metadata CRC validation failed\n"); |
| mdata_mod = update; |
| } else { |
| valid_mdata = &mdata[0]; |
| } |
| |
| if (have_redundant) { |
| ret = read_one_metadata(1, alloc_size); |
| if (ret) |
| return ret; |
| |
| if (validate_crc(&mdata[1], alloc_size) < 0) { |
| fprintf(stderr, |
| "Warning: Secondary metadata CRC validation failed\n"); |
| mdata_mod = update; |
| } else if (valid_mdata && mdata[0].crc32 != mdata[1].crc32) { |
| fprintf(stderr, |
| "Metadatas valid but not equal, use first one as default\n"); |
| mdata_mod = update; |
| } else { |
| valid_mdata = &mdata[1]; |
| } |
| } |
| |
| if (!valid_mdata) { |
| fprintf(stderr, |
| "No metadata valid, use first one as default\n"); |
| mdata_mod = update; |
| valid_mdata = &mdata[0]; |
| } |
| |
| if (valid_mdata->version == 2) { |
| struct fwu_mdata_ext *mdata_ext; |
| |
| mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); |
| if (mdata_ext->metadata_size != alloc_size) { |
| fprintf(stderr, |
| "Metadata real size 0x%x mismatch with the config 0x%zx\n", |
| mdata_ext->metadata_size, alloc_size); |
| return -EINVAL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int write_metadata(void) |
| { |
| size_t write_size = devices[0].mdata_size; |
| int ret; |
| |
| if (!mdata_mod) |
| return 0; |
| |
| /* Update CRC */ |
| update_crc(valid_mdata, write_size); |
| |
| /* Write primary */ |
| ret = write_device(&devices[0], valid_mdata, write_size); |
| if (ret < 0) { |
| fprintf(stderr, "Failed to write primary metadata\n"); |
| return ret; |
| } |
| |
| /* Write secondary if redundant */ |
| if (have_redundant) { |
| ret = write_device(&devices[1], valid_mdata, write_size); |
| if (ret < 0) { |
| fprintf(stderr, "Failed to write secondary metadata\n"); |
| return -1; |
| } |
| } |
| |
| printf("FWU metadata updated successfully\n"); |
| mdata_mod = 0; |
| |
| return 0; |
| } |
| |
| /* Display functions */ |
| static const char *bank_state_to_string(u8 state) |
| { |
| switch (state) { |
| case FWU_BANK_ACCEPTED: |
| return "accepted"; |
| case FWU_BANK_VALID: |
| return "valid"; |
| case FWU_BANK_INVALID: |
| return "invalid"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static void print_metadata_summary(void) |
| { |
| int i; |
| |
| printf("FWU Metadata:\n"); |
| printf("\tVersion: %u\n", valid_mdata->version); |
| printf("\tActive Index: %u\n", valid_mdata->active_index); |
| printf("\tPrevious Index: %u\n", valid_mdata->previous_active_index); |
| printf("\tCRC32: 0x%08x\n", valid_mdata->crc32); |
| |
| if (valid_mdata->version == 2) { |
| struct fwu_fw_store_desc *fw_desc; |
| struct fwu_mdata_ext *mdata_ext; |
| |
| mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); |
| printf("\tMetadata Size: %u bytes\n", mdata_ext->metadata_size); |
| printf("\tDescriptor Offset: %u\n", mdata_ext->desc_offset); |
| printf("\tBank States:\n"); |
| |
| fw_desc = fwu_get_fw_desc(valid_mdata); |
| for (i = 0; i < fw_desc->num_banks && i < MAX_BANKS_V2; i++) { |
| printf("\t\tBank %d: %s (0x%02x)\n", i, |
| bank_state_to_string(mdata_ext->bank_state[i]), |
| mdata_ext->bank_state[i]); |
| } |
| } |
| } |
| |
| static void print_metadata_detailed(void) |
| { |
| struct fwu_fw_store_desc *fw_desc = NULL; |
| struct fwu_image_bank_info *bank_info; |
| struct fwu_image_entry *img_entry; |
| int num_images, num_banks; |
| char guid_str[64]; |
| int i, j; |
| |
| print_metadata_summary(); |
| |
| if (valid_mdata->version == 1) { |
| num_images = nimages; |
| num_banks = nbanks; |
| } else { |
| fw_desc = fwu_get_fw_desc(valid_mdata); |
| num_images = fw_desc->num_images; |
| num_banks = fw_desc->num_banks; |
| } |
| |
| if (fw_desc) { |
| printf("\n\tFirmware Store Descriptor:\n"); |
| printf("\t\tNumber of Banks: %u\n", num_banks); |
| printf("\t\tNumber of Images: %u\n", num_images); |
| printf("\t\tImage Entry Size: %u\n", fw_desc->img_entry_size); |
| printf("\t\tBank Info Entry Size: %u\n", fw_desc->bank_info_entry_size); |
| } |
| |
| printf("\n\tImages:\n"); |
| for (i = 0; i < num_images; i++) { |
| img_entry = fwu_get_image_entry(valid_mdata, valid_mdata->version, |
| num_banks, i); |
| |
| printf("\t\tImage %d:\n", i); |
| |
| guid_to_string(&img_entry->image_type_guid, guid_str); |
| printf("\t\t\tImage Type GUID: %s\n", guid_str); |
| |
| guid_to_string(&img_entry->location_guid, guid_str); |
| printf("\t\t\tLocation GUID: %s\n", guid_str); |
| |
| printf("\t\t\tBanks:\n"); |
| for (j = 0; j < num_banks; j++) { |
| bank_info = fwu_get_bank_info(valid_mdata, |
| valid_mdata->version, |
| num_banks, i, j); |
| |
| guid_to_string(&bank_info->image_guid, guid_str); |
| printf("\t\t\t\tBank %d:\n", j); |
| printf("\t\t\t\t\tImage GUID: %s\n", guid_str); |
| printf("\t\t\t\t\tAccepted: %s (%u)\n", |
| (bank_info->accepted & FWU_IMAGE_ACCEPTED) ? "yes" : "no", |
| bank_info->accepted); |
| } |
| } |
| } |
| |
| /* Modification functions */ |
| static int set_active_index(int bank) |
| { |
| struct fwu_fw_store_desc *fw_desc; |
| int num_banks; |
| |
| if (valid_mdata->version == 2) { |
| fw_desc = fwu_get_fw_desc(valid_mdata); |
| num_banks = fw_desc->num_banks; |
| } else { |
| num_banks = nbanks; |
| } |
| |
| if (bank < 0 || bank >= num_banks) { |
| fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", |
| bank, num_banks - 1); |
| return -EINVAL; |
| } |
| |
| if (valid_mdata->active_index == bank) |
| return 0; |
| |
| valid_mdata->active_index = bank; |
| mdata_mod = 1; |
| |
| printf("Active bank set to %d\n", bank); |
| return 0; |
| } |
| |
| static int set_previous_index(int bank) |
| { |
| struct fwu_fw_store_desc *fw_desc; |
| int num_banks; |
| |
| if (valid_mdata->version == 2) { |
| fw_desc = fwu_get_fw_desc(valid_mdata); |
| num_banks = fw_desc->num_banks; |
| } else { |
| num_banks = nbanks; |
| } |
| |
| if (bank < 0 || bank >= num_banks) { |
| fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", |
| bank, num_banks - 1); |
| return -EINVAL; |
| } |
| |
| if (valid_mdata->previous_active_index == bank) |
| return 0; |
| |
| valid_mdata->previous_active_index = bank; |
| mdata_mod = 1; |
| |
| printf("Previous bank set to %d\n", bank); |
| return 0; |
| } |
| |
| static int set_image_accepted(int image, int bank, int accept) |
| { |
| struct fwu_image_bank_info *bank_info; |
| int num_images, num_banks; |
| |
| if (valid_mdata->version == 1) { |
| num_images = nimages; |
| num_banks = nbanks; |
| } else { |
| struct fwu_fw_store_desc *fw_desc; |
| |
| fw_desc = fwu_get_fw_desc(valid_mdata); |
| num_images = fw_desc->num_images; |
| num_banks = fw_desc->num_banks; |
| } |
| |
| if (bank < 0 || bank >= num_banks) { |
| fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", |
| bank, num_banks - 1); |
| return -EINVAL; |
| } |
| |
| if (image < 0 || image >= num_images) { |
| fprintf(stderr, "Error: Invalid image %d (must be 0-%d)\n", |
| image, num_images - 1); |
| return -EINVAL; |
| } |
| |
| bank_info = fwu_get_bank_info(valid_mdata, valid_mdata->version, |
| num_banks, image, bank); |
| if (accept == bank_info->accepted) |
| return 0; |
| |
| if (accept) { |
| bank_info->accepted = FWU_IMAGE_ACCEPTED; |
| } else { |
| bank_info->accepted = 0; |
| |
| /* According to the spec: bank_state[index] have to be set |
| * to invalid before any content in the img_bank_info[index] |
| * is overwritten. |
| */ |
| if (valid_mdata->version == 2) { |
| struct fwu_mdata_ext *mdata_ext; |
| |
| mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); |
| mdata_ext->bank_state[bank] = FWU_BANK_INVALID; |
| } |
| } |
| |
| mdata_mod = 1; |
| printf("Image %d in bank %d: acceptance %s\n", |
| image, bank, accept ? "set" : "cleared"); |
| |
| return 0; |
| } |
| |
| static int set_bank_state(int bank, const char *state_str) |
| { |
| struct fwu_fw_store_desc *fw_desc; |
| struct fwu_mdata_ext *mdata_ext; |
| u8 state; |
| int i; |
| |
| if (valid_mdata->version != 2) { |
| fprintf(stderr, |
| "Error: Bank state is only supported in V2 metadata\n"); |
| return -EINVAL; |
| } |
| |
| fw_desc = fwu_get_fw_desc(valid_mdata); |
| mdata_ext = fwu_get_fw_mdata_ext(valid_mdata); |
| |
| if (bank < 0 || bank >= fw_desc->num_banks || bank >= MAX_BANKS_V2) { |
| fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n", |
| bank, fw_desc->num_banks - 1); |
| return -EINVAL; |
| } |
| |
| /* Parse state string */ |
| if (!strcmp(state_str, "accepted")) { |
| state = FWU_BANK_ACCEPTED; |
| } else if (!strcmp(state_str, "valid")) { |
| state = FWU_BANK_VALID; |
| } else if (!strcmp(state_str, "invalid")) { |
| state = FWU_BANK_INVALID; |
| } else { |
| fprintf(stderr, |
| "Error: Invalid state '%s' (must be accepted/valid/invalid)\n", |
| state_str); |
| return -EINVAL; |
| } |
| |
| if (mdata_ext->bank_state[bank] == state) |
| return 0; |
| |
| /* If a bank is set in a accepted state all firmware images in |
| * that bank must be marked as accepted as described in the spec. |
| */ |
| if (state == FWU_BANK_ACCEPTED) { |
| for (i = 0; i < fw_desc->num_images; i++) { |
| int ret; |
| |
| ret = set_image_accepted(i, bank, true); |
| if (ret) |
| return ret; |
| } |
| } |
| mdata_ext->bank_state[bank] = state; |
| mdata_mod = 1; |
| |
| printf("Bank %d state set to %s (0x%02x)\n", bank, state_str, state); |
| return 0; |
| } |
| |
| static int metadata_v1_validate_size(void) |
| { |
| int calc_size; |
| |
| calc_size = sizeof(struct fwu_mdata) + |
| (sizeof(struct fwu_image_entry) + |
| sizeof(struct fwu_image_bank_info) * nbanks) * nimages; |
| |
| if (devices[0].mdata_size != calc_size) { |
| fprintf(stderr, |
| "Metadata calculate size (-B and -I options) 0x%x mismatch with the config 0x%zx\n", |
| calc_size, devices[0].mdata_size); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* Command-line interface */ |
| static void print_usage(void) |
| { |
| fprintf(stderr, "Usage: fwumdata [options]\n\n"); |
| fprintf(stderr, "Options:\n" |
| "\t-c, --config <file> Use custom config file, defaults:\n" |
| "\t ./fwumdata.config or /etc/fwumdata.config\n" |
| "\t-l, --list List detailed metadata with GUIDs\n" |
| "\t-a, --active <bank> Set active bank index\n" |
| "\t-p, --previous <bank> Set previous bank index\n" |
| "\t-s, --state <bank> <state> Set bank state (V2 only)\n" |
| "\t state: accepted|valid|invalid\n" |
| "\t-i, --image <id> Image number (for -A/-C)\n" |
| "\t-b, --bank <bank> Bank number (for -A/-C)\n" |
| "\t-A, --accept Accept image (requires -i and -b)\n" |
| "\t-C, --clear Clear image acceptance (requires -i and -b)\n" |
| "\t-u, --update Update metadata if there is a checksum issue\n" |
| "\t-B, --nbanks <num_banks> Number of banks (required for V1 metadata)\n" |
| "\t-I, --nimages <num_images> Number of images (required for V1 metadata)\n" |
| "\t-h, --help Print this help\n\n"); |
| fprintf(stderr, "Config file format (fwumdata.config):\n" |
| "\t# Device Name Device Offset Metadata Size Erase Size\n" |
| "\t/dev/mtd0 0x0 0x78 0x1000\n" |
| "\t/dev/mtd1 0x0 0x78 0x1000\n\n"); |
| fprintf(stderr, "Examples:\n" |
| "\tfwumdata # Print metadata summary\n" |
| "\tfwumdata -l # Print detailed metadata\n" |
| "\tfwumdata -a 1 # Set active bank to 1\n" |
| "\tfwumdata -s 1 accepted # Set bank 1 to accepted state\n" |
| "\tfwumdata -i 0 -b 0 -A # Accept image in bank 0\n" |
| "\tfwumdata -B 2 -I 2 -i 1 -b 1 -A -l # Accept image 1 in bank 1 with metadata V1\n"); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| char *bank_state_str = NULL; |
| bool list_detailed = false; |
| int bank_state_num = -1; |
| int active_index = -1; |
| int bank_id = -1; |
| int prev_index = -1; |
| bool do_accept = 0; |
| bool do_clear = 0; |
| bool do_update = 0; |
| int image_id = -1; |
| int ret = 0; |
| int opt; |
| |
| static struct option long_options[] = { |
| {"config", required_argument, 0, 'c'}, |
| {"list", no_argument, 0, 'l'}, |
| {"active", required_argument, 0, 'a'}, |
| {"previous", required_argument, 0, 'p'}, |
| {"state", required_argument, 0, 's'}, |
| {"image", required_argument, 0, 'i'}, |
| {"bank", required_argument, 0, 'b'}, |
| {"accept", no_argument, 0, 'A'}, |
| {"clear", no_argument, 0, 'C'}, |
| {"update", no_argument, 0, 'u'}, |
| {"nbanks", required_argument, 0, 'B'}, |
| {"nimages", required_argument, 0, 'I'}, |
| {"help", no_argument, 0, 'h'}, |
| {0, 0, 0, 0} |
| }; |
| |
| /* Parse arguments */ |
| while ((opt = getopt_long(argc, argv, "c:la:p:s:i:b:ACuB:I:h", long_options, NULL)) != -1) { |
| switch (opt) { |
| case 'c': |
| config_file = optarg; |
| break; |
| case 'l': |
| list_detailed = 1; |
| break; |
| case 'a': |
| active_index = atoi(optarg); |
| break; |
| case 'p': |
| prev_index = atoi(optarg); |
| break; |
| case 's': |
| bank_state_num = atoi(optarg); |
| if (optind < argc && argv[optind][0] != '-') { |
| bank_state_str = argv[optind++]; |
| } else { |
| fprintf(stderr, |
| "Error: -s requires bank number and state\n"); |
| return 1; |
| } |
| break; |
| case 'i': |
| image_id = atoi(optarg); |
| break; |
| case 'b': |
| bank_id = atoi(optarg); |
| break; |
| case 'A': |
| do_accept = 1; |
| break; |
| case 'C': |
| do_clear = 1; |
| break; |
| case 'u': |
| do_update = 1; |
| break; |
| case 'B': |
| nbanks = atoi(optarg); |
| break; |
| case 'I': |
| nimages = atoi(optarg); |
| break; |
| case 'h': |
| print_usage(); |
| return 0; |
| default: |
| print_usage(); |
| return 1; |
| } |
| } |
| |
| ret = find_parse_config(); |
| if (ret < 0) { |
| fprintf(stderr, "Error: Cannot read configuration\n"); |
| return ret; |
| } |
| |
| ret = read_metadata(do_update); |
| if (ret < 0) { |
| fprintf(stderr, "Error: Cannot read metadata\n"); |
| goto cleanup; |
| } |
| |
| if (valid_mdata->version == 1) { |
| ret = metadata_v1_validate_size(); |
| if (ret) |
| goto cleanup; |
| } |
| |
| /* Perform operations */ |
| if (active_index >= 0) { |
| ret = set_active_index(active_index); |
| if (ret < 0) |
| goto cleanup; |
| } |
| |
| if (prev_index >= 0) { |
| ret = set_previous_index(prev_index); |
| if (ret < 0) |
| goto cleanup; |
| } |
| |
| if (do_accept || do_clear) { |
| if (image_id < 0 || bank_id < 0) { |
| fprintf(stderr, |
| "Error: -A/-C requires both -i <guid> and -b <bank>\n"); |
| ret = -EINVAL; |
| goto cleanup; |
| } |
| |
| ret = set_image_accepted(image_id, bank_id, do_accept); |
| if (ret < 0) |
| goto cleanup; |
| } |
| |
| if (bank_state_num >= 0 && bank_state_str) { |
| ret = set_bank_state(bank_state_num, bank_state_str); |
| if (ret < 0) |
| goto cleanup; |
| } |
| |
| /* Write back if modified */ |
| if (mdata_mod) { |
| ret = write_metadata(); |
| if (ret) |
| goto cleanup; |
| } |
| |
| /* Display metadata if no modifications or list requested */ |
| if (list_detailed) |
| print_metadata_detailed(); |
| else |
| print_metadata_summary(); |
| |
| cleanup: |
| /* Close devices and free memory */ |
| if (devices[0].fd) |
| close(devices[0].fd); |
| if (devices[1].fd) |
| close(devices[1].fd); |
| |
| free(mdata); |
| |
| for (int i = 0; i < 2; i++) { |
| if (devices[i].devname) |
| free((void *)devices[i].devname); |
| } |
| |
| return ret; |
| } |