| /* |
| * Copyright (c) International Business Machines Corp., 2006, 2007 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| /* |
| * Authors: Drake Dowsett, dowsett@de.ibm.com |
| * Frank Haverkamp, haver@vnet.ibm.com |
| * |
| * 1.2 Removed argp because we want to use uClibc. |
| * 1.3 Minor cleanups. |
| * 1.4 Meanwhile Drake had done a lot of changes, syncing those. |
| * 1.5 Bugfixes, simplifications |
| */ |
| |
| /* |
| * unubi reads an image file containing blocks of UBI headers and data |
| * (such as produced from nand2bin) and rebuilds the volumes within. The |
| * default operation (when no flags are given) is to rebuild all valid |
| * volumes found in the image. unubi can also read straight from the |
| * onboard MTD device (ex. /dev/mtdblock/NAND). |
| */ |
| |
| /* TODO: consideration for dynamic vs. static volumes */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <getopt.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <mtd/ubi-media.h> |
| #include <mtd_swab.h> |
| |
| #include "crc32.h" |
| #include "unubi_analyze.h" |
| |
| #define EXEC "unubi" |
| #define CONTACT "haver@vnet.ibm.com" |
| #define VERSION "1.5" |
| |
| static char doc[] = "\nVersion: " VERSION "\n"; |
| static int debug = 0; |
| |
| static const char *optionsstr = |
| "Extract volumes and/or analysis information from an UBI data file.\n" |
| "When no parameters are flagged or given, the default operation is\n" |
| "to rebuild all valid complete UBI volumes found within the image.\n" |
| "\n" |
| " OPERATIONS\n" |
| " -a, --analyze Analyze image and create gnuplot graphs\n" |
| " -i, --info-table Extract volume information tables\n" |
| " -r, --rebuild=<volume-id> Extract and rebuild volume\n" |
| "\n" |
| " OPTIONS\n" |
| " -b, --blocksize=<block-size> Specify size of eraseblocks in image in bytes\n" |
| " (default 128KiB)\n" |
| " -d, --dir=<output-dir> Specify output directory\n" |
| " -D, --debug Enable debug output\n" |
| " -s, --headersize=<header-size> Specify size reserved for metadata in eraseblock\n" |
| " in bytes (default 2048 Byte)\n" |
| /* the -s option might be insufficient when using different vid |
| offset than what we used when writing this tool ... Better would |
| probably be --vid-hdr-offset or alike */ |
| "\n" |
| " ADVANCED\n" |
| " -e, --eb-split Generate individual eraseblock images (all\n" |
| " eraseblocks)\n" |
| " -v, --vol-split Generate individual eraseblock images (valid\n" |
| " eraseblocks only)\n" |
| " -V, --vol-split! Raw split by eraseblock (valid eraseblocks only)\n" |
| "\n" |
| " -?, --help Give this help list\n" |
| " --usage Give a short usage message\n" |
| " --version Print program version\n" |
| "\n"; |
| |
| static const char *usage = |
| "Usage: unubi [-aievV?] [-r <volume-id>] [-b <block-size>] [-d <output-dir>]\n" |
| " [-s <header-size>] [--analyze] [--info-table]\n" |
| " [--rebuild=<volume-id>] [--blocksize=<block-size>]\n" |
| " [--dir=<output-dir>] [--headersize=<header-size>] [--eb-split]\n" |
| " [--vol-split] [--vol-split!] [--help] [--usage] [--version]\n" |
| " image-file\n"; |
| |
| #define ERR_MSG(fmt...) \ |
| fprintf(stderr, EXEC ": " fmt) |
| |
| #define SPLIT_DATA 1 |
| #define SPLIT_RAW 2 |
| |
| #define DIR_FMT "unubi_%s" |
| #define KIB 1024 |
| #define MIB (KIB * KIB) |
| #define MAXPATH KIB |
| |
| /* filenames */ |
| #define FN_INVAL "%s/eb%04u%s" /* invalid eraseblock */ |
| #define FN_NSURE "%s/eb%04u_%03u_%03u_%03x%s" /* unsure eraseblock */ |
| #define FN_VALID "%s/eb%04u_%03u_%03u_%03x%s" /* valid eraseblock */ |
| #define FN_VOLSP "%s/vol%03u_%03u_%03u_%04zu" /* split volume */ |
| #define FN_VOLWH "%s/volume%03u" /* whole volume */ |
| #define FN_VITBL "%s/vol_info_table%zu" /* vol info table */ |
| |
| static uint32_t crc32_table[256]; |
| |
| /* struct args: |
| * bsize int, blocksize of image blocks |
| * hsize int, eraseblock header size |
| * analyze flag, when non-zero produce analysis |
| * eb_split flag, when non-zero output eb#### |
| * note: SPLIT_DATA vs. SPLIT_RAW |
| * vol_split flag, when non-zero output vol###_#### |
| * note: SPLIT_DATA vs. SPLIT_RAW |
| * odir_path string, directory to place volumes in |
| * img_path string, file to read as ubi image |
| * vols int array of size UBI_MAX_VOLUMES, where a 1 can be |
| * written for each --rebuild flag in the index specified |
| * then the array can be counted and collapsed using |
| * count_set() and collapse() |
| */ |
| struct args { |
| int analyze; |
| int itable; |
| uint32_t *vols; |
| |
| size_t vid_hdr_offset; |
| size_t data_offset; |
| size_t bsize; /* FIXME replace by vid_hdr/data offs? */ |
| size_t hsize; |
| |
| char *odir_path; |
| int eb_split; |
| int vol_split; |
| char *img_path; |
| |
| char **options; |
| }; |
| |
| struct option long_options[] = { |
| { .name = "rebuild", .has_arg = 1, .flag = NULL, .val = 'r' }, |
| { .name = "dir", .has_arg = 1, .flag = NULL, .val = 'd' }, |
| { .name = "analyze", .has_arg = 0, .flag = NULL, .val = 'a' }, |
| { .name = "blocksize", .has_arg = 1, .flag = NULL, .val = 'b' }, |
| { .name = "eb-split", .has_arg = 0, .flag = NULL, .val = 'e' }, |
| { .name = "vol-split", .has_arg = 0, .flag = NULL, .val = 'v' }, |
| { .name = "vol-split!", .has_arg = 0, .flag = NULL, .val = 'e' }, |
| { .name = "help", .has_arg = 0, .flag = NULL, .val = '?' }, |
| { .name = "usage", .has_arg = 0, .flag = NULL, .val = 0 }, |
| { .name = "version", .has_arg = 0, .flag = NULL, .val = 'J' }, |
| { NULL, 0, NULL, 0} |
| }; |
| |
| /** |
| * parses out a numerical value from a string of numbers followed by: |
| * k, K, kib, KiB for kibibyte |
| * m, M, mib, MiB for mebibyte |
| **/ |
| static uint32_t |
| str_to_num(char *str) |
| { |
| char *s; |
| ulong num; |
| |
| s = str; |
| num = strtoul(s, &s, 0); |
| |
| if (*s != '\0') { |
| if ((strcmp(s, "KiB") == 0) || (strcmp(s, "K") == 0) || |
| (strcmp(s, "kib") == 0) || (strcmp(s, "k") == 0)) |
| num *= KIB; |
| else if ((strcmp(s, "MiB") == 0) || (strcmp(s, "M") == 0) || |
| (strcmp(s, "mib") == 0) || (strcmp(s, "m") == 0)) |
| num *= MIB; |
| else |
| ERR_MSG("couldn't parse '%s', assuming %lu\n", |
| s, num); |
| } |
| return num; |
| } |
| |
| static int |
| parse_opt(int argc, char **argv, struct args *args) |
| { |
| uint32_t i; |
| |
| while (1) { |
| int key; |
| |
| key = getopt_long(argc, argv, "ab:s:d:Deir:vV?J", |
| long_options, NULL); |
| if (key == -1) |
| break; |
| |
| switch (key) { |
| case 'a': /* --analyze */ |
| args->analyze = 1; |
| break; |
| case 'b': /* --block-size=<block-size> */ |
| args->bsize = str_to_num(optarg); |
| break; |
| case 's': /* --header-size=<header-size> */ |
| args->hsize = str_to_num(optarg); |
| break; |
| case 'd': /* --dir=<output-dir> */ |
| args->odir_path = optarg; |
| break; |
| case 'D': /* --debug */ |
| /* I wanted to use -v but that was already |
| used ... */ |
| debug = 1; |
| break; |
| case 'e': /* --eb-split */ |
| args->eb_split = SPLIT_RAW; |
| break; |
| case 'i': /* --info-table */ |
| args->itable = 1; |
| break; |
| case 'r': /* --rebuild=<volume-id> */ |
| i = str_to_num(optarg); |
| if (i < UBI_MAX_VOLUMES) |
| args->vols[str_to_num(optarg)] = 1; |
| else { |
| ERR_MSG("volume-id out of bounds\n"); |
| return -1; |
| } |
| break; |
| case 'v': /* --vol-split */ |
| if (args->vol_split != SPLIT_RAW) |
| args->vol_split = SPLIT_DATA; |
| break; |
| case 'V': /* --vol-split! */ |
| args->vol_split = SPLIT_RAW; |
| break; |
| case '?': /* help */ |
| fprintf(stderr, "Usage: unubi [OPTION...] " |
| "image-file\n%s%s\nReport bugs to %s\n", |
| doc, optionsstr, CONTACT); |
| exit(0); |
| break; |
| case 'J': |
| fprintf(stderr, "%s\n", VERSION); |
| exit(0); |
| break; |
| default: |
| fprintf(stderr, "%s", usage); |
| exit(-1); |
| } |
| } |
| |
| /* FIXME I suppose hsize should be replaced! */ |
| args->vid_hdr_offset = args->hsize - UBI_VID_HDR_SIZE; |
| args->data_offset = args->hsize; |
| |
| if (optind < argc) |
| args->img_path = argv[optind++]; |
| return 0; |
| } |
| |
| |
| /** |
| * counts the number of indicies which are flagged in full_array; |
| * full_array is an array of flags (1/0); |
| **/ |
| static size_t |
| count_set(uint32_t *full_array, size_t full_len) |
| { |
| size_t count, i; |
| |
| if (full_array == NULL) |
| return 0; |
| |
| for (i = 0, count = 0; i < full_len; i++) |
| if (full_array[i] != 0) |
| count++; |
| |
| return count; |
| } |
| |
| |
| /** |
| * generates coll_array from full_array; |
| * full_array is an array of flags (1/0); |
| * coll_array is an array of the indicies in full_array which are flagged (1); |
| **/ |
| static size_t |
| collapse(uint32_t *full_array, size_t full_len, |
| uint32_t *coll_array, size_t coll_len) |
| { |
| size_t i, j; |
| |
| if ((full_array == NULL) || (coll_array == NULL)) |
| return 0; |
| |
| for (i = 0, j = 0; (i < full_len) && (j < coll_len); i++) |
| if (full_array[i] != 0) { |
| coll_array[j] = i; |
| j++; |
| } |
| |
| return j; |
| } |
| |
| /** |
| * data_crc: save the FILE* position, calculate the crc over a span, |
| * reset the position |
| * returns non-zero when EOF encountered |
| **/ |
| static int |
| data_crc(FILE* fpin, size_t length, uint32_t *ret_crc) |
| { |
| int rc; |
| size_t i; |
| char buf[length]; |
| uint32_t crc; |
| fpos_t start; |
| |
| rc = fgetpos(fpin, &start); |
| if (rc < 0) |
| return -1; |
| |
| for (i = 0; i < length; i++) { |
| int c = fgetc(fpin); |
| if (c == EOF) { |
| ERR_MSG("unexpected EOF\n"); |
| return -1; |
| } |
| buf[i] = (char)c; |
| } |
| |
| rc = fsetpos(fpin, &start); |
| if (rc < 0) |
| return -1; |
| |
| crc = clc_crc32(crc32_table, UBI_CRC32_INIT, buf, length); |
| *ret_crc = crc; |
| return 0; |
| } |
| |
| |
| /** |
| * reads data of size len from fpin and writes it to path |
| **/ |
| static int |
| extract_data(FILE* fpin, size_t len, const char *path) |
| { |
| int rc; |
| size_t i; |
| FILE* fpout; |
| |
| rc = 0; |
| fpout = NULL; |
| |
| fpout = fopen(path, "wb"); |
| if (fpout == NULL) { |
| ERR_MSG("couldn't open file for writing: %s\n", path); |
| rc = -1; |
| goto err; |
| } |
| |
| for (i = 0; i < len; i++) { |
| int c = fgetc(fpin); |
| if (c == EOF) { |
| ERR_MSG("unexpected EOF while writing: %s\n", path); |
| rc = -2; |
| goto err; |
| } |
| c = fputc(c, fpout); |
| if (c == EOF) { |
| ERR_MSG("couldn't write: %s\n", path); |
| rc = -3; |
| goto err; |
| } |
| } |
| |
| err: |
| if (fpout != NULL) |
| fclose(fpout); |
| return rc; |
| } |
| |
| |
| /** |
| * extract volume information table from block. saves and reloads fpin |
| * position |
| * returns -1 when a fpos set or get fails, otherwise <= -2 on other |
| * failure and 0 on success |
| **/ |
| static int |
| extract_itable(FILE *fpin, struct eb_info *cur, size_t bsize, size_t num, |
| const char *path) |
| { |
| char filename[MAXPATH + 1]; |
| int rc; |
| size_t i, max; |
| fpos_t temp; |
| FILE* fpout = NULL; |
| struct ubi_vtbl_record rec; |
| |
| if (fpin == NULL || cur == NULL || path == NULL) |
| return -2; |
| |
| /* remember position */ |
| rc = fgetpos(fpin, &temp); |
| if (rc < 0) |
| return -1; |
| |
| /* jump to top of eraseblock, skip to data section */ |
| fsetpos(fpin, &cur->eb_top); |
| if (rc < 0) |
| return -1; |
| fseek(fpin, be32_to_cpu(cur->ec.data_offset), SEEK_CUR); |
| |
| /* prepare output file */ |
| if (be32_to_cpu(cur->vid.vol_id) != UBI_LAYOUT_VOLUME_ID) |
| return -2; |
| memset(filename, 0, MAXPATH + 1); |
| snprintf(filename, MAXPATH, FN_VITBL, path, num); |
| fpout = fopen(filename, "w"); |
| if (fpout == NULL) |
| return -2; |
| |
| /* loop through entries */ |
| fprintf(fpout, |
| "index\trpebs\talign\ttype\tcrc\t\tname\n"); |
| max = bsize - be32_to_cpu(cur->ec.data_offset); |
| for (i = 0; i < (max / sizeof(rec)); i++) { |
| int blank = 1; |
| char *ptr, *base; |
| char name[UBI_VOL_NAME_MAX + 1]; |
| const char *type = "unknown\0"; |
| uint32_t crc; |
| |
| /* read record */ |
| rc = fread(&rec, 1, sizeof(rec), fpin); |
| if (rc == 0) |
| break; |
| if (rc != sizeof(rec)) { |
| ERR_MSG("reading volume information " |
| "table record failed\n"); |
| rc = -3; |
| goto exit; |
| } |
| |
| /* check crc */ |
| crc = clc_crc32(crc32_table, UBI_CRC32_INIT, &rec, |
| UBI_VTBL_RECORD_SIZE_CRC); |
| if (crc != be32_to_cpu(rec.crc)) |
| continue; |
| |
| /* check for empty */ |
| base = (char *)&rec; |
| ptr = base; |
| while (blank && |
| ((unsigned)(ptr - base) < UBI_VTBL_RECORD_SIZE_CRC)) { |
| if (*ptr != 0) |
| blank = 0; |
| ptr++; |
| } |
| |
| if (blank) |
| continue; |
| |
| /* prep type string */ |
| if (rec.vol_type == UBI_VID_DYNAMIC) |
| type = "dynamic\0"; |
| else if (rec.vol_type == UBI_VID_STATIC) |
| type = "static\0"; |
| |
| /* prep name string */ |
| rec.name[be16_to_cpu(rec.name_len)] = '\0'; |
| sprintf(name, "%s", rec.name); |
| |
| /* print record line to fpout */ |
| fprintf(fpout, "%zu\t%u\t%u\t%s\t0x%08x\t%s\n", |
| i, |
| be32_to_cpu(rec.reserved_pebs), |
| be32_to_cpu(rec.alignment), |
| type, |
| be32_to_cpu(rec.crc), |
| name); |
| } |
| |
| exit: |
| /* reset position */ |
| if (fsetpos(fpin, &temp) < 0) |
| rc = -1; |
| |
| if (fpout != NULL) |
| fclose(fpout); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * using eb chain, tries to rebuild the data of volume at vol_id, or for all |
| * the known volumes, if vol_id is NULL; |
| **/ |
| static int |
| rebuild_volume(FILE * fpin, uint32_t *vol_id, struct eb_info **head, |
| const char *path, size_t block_size, size_t header_size) |
| { |
| char filename[MAXPATH]; |
| int rc; |
| uint32_t vol, num, data_size; |
| FILE* fpout; |
| struct eb_info *cur; |
| |
| rc = 0; |
| |
| if ((fpin == NULL) || (head == NULL) || (*head == NULL)) |
| return 0; |
| |
| /* when vol_id is null, then do all */ |
| if (vol_id == NULL) { |
| cur = *head; |
| vol = be32_to_cpu(cur->vid.vol_id); |
| } else { |
| vol = *vol_id; |
| eb_chain_position(head, vol, NULL, &cur); |
| if (cur == NULL) { |
| if (debug) |
| ERR_MSG("no valid volume %d was found\n", vol); |
| return -1; |
| } |
| } |
| |
| num = 0; |
| snprintf(filename, MAXPATH, FN_VOLWH, path, vol); |
| fpout = fopen(filename, "wb"); |
| if (fpout == NULL) { |
| ERR_MSG("couldn't open file for writing: %s\n", filename); |
| return -1; |
| } |
| |
| while (cur != NULL) { |
| size_t i; |
| |
| if (be32_to_cpu(cur->vid.vol_id) != vol) { |
| /* close out file */ |
| fclose(fpout); |
| |
| /* only stay around if that was the only volume */ |
| if (vol_id != NULL) |
| goto out; |
| |
| /* begin with next */ |
| vol = be32_to_cpu(cur->vid.vol_id); |
| num = 0; |
| snprintf(filename, MAXPATH, FN_VOLWH, path, vol); |
| fpout = fopen(filename, "wb"); |
| if (fpout == NULL) { |
| ERR_MSG("couldn't open file for writing: %s\n", |
| filename); |
| return -1; |
| } |
| } |
| |
| while (num < be32_to_cpu(cur->vid.lnum)) { |
| /* FIXME haver: I hope an empty block is |
| written out so that the binary has no holes |
| ... */ |
| if (debug) |
| ERR_MSG("missing valid block %d for volume %d\n", |
| num, vol); |
| num++; |
| } |
| |
| rc = fsetpos(fpin, &(cur->eb_top)); |
| if (rc < 0) |
| goto out; |
| fseek(fpin, be32_to_cpu(cur->ec.data_offset), SEEK_CUR); |
| |
| if (cur->vid.vol_type == UBI_VID_DYNAMIC) |
| /* FIXME It might be that alignment has influence */ |
| data_size = block_size - header_size; |
| else |
| data_size = be32_to_cpu(cur->vid.data_size); |
| |
| for (i = 0; i < data_size; i++) { |
| int c = fgetc(fpin); |
| if (c == EOF) { |
| ERR_MSG("unexpected EOF while writing: %s\n", |
| filename); |
| rc = -2; |
| goto out; |
| } |
| c = fputc(c, fpout); |
| if (c == EOF) { |
| ERR_MSG("couldn't write: %s\n", filename); |
| rc = -3; |
| goto out; |
| } |
| } |
| |
| cur = cur->next; |
| num++; |
| } |
| |
| out: |
| if (vol_id == NULL) |
| fclose(fpout); |
| return rc; |
| } |
| |
| |
| /** |
| * traverses FILE* trying to load complete, valid and accurate header data |
| * into the eb chain; |
| **/ |
| static int |
| unubi_volumes(FILE* fpin, uint32_t *vols, size_t vc, struct args *a) |
| { |
| char filename[MAXPATH + 1]; |
| char reason[MAXPATH + 1]; |
| int rc; |
| size_t i, count, itable_num; |
| /* relations: |
| * cur ~ head |
| * next ~ first */ |
| struct eb_info *head, *cur, *first, *next; |
| struct eb_info **next_ptr; |
| |
| rc = 0; |
| count = 0; |
| itable_num = 0; |
| head = NULL; |
| first = NULL; |
| next = NULL; |
| cur = malloc(sizeof(*cur)); |
| if (cur == NULL) { |
| ERR_MSG("out of memory\n"); |
| rc = -ENOMEM; |
| goto err; |
| } |
| memset(cur, 0, sizeof(*cur)); |
| |
| fgetpos(fpin, &(cur->eb_top)); |
| while (1) { |
| const char *raw_path; |
| uint32_t crc; |
| |
| cur->phys_addr = ftell(fpin); |
| cur->phys_block = cur->phys_addr / a->bsize; |
| cur->data_crc_ok = 0; |
| cur->ec_crc_ok = 0; |
| cur->vid_crc_ok = 0; |
| |
| memset(filename, 0, MAXPATH + 1); |
| memset(reason, 0, MAXPATH + 1); |
| |
| /* in case of an incomplete ec header */ |
| raw_path = FN_INVAL; |
| |
| /* read erasecounter header */ |
| rc = fread(&cur->ec, 1, sizeof(cur->ec), fpin); |
| if (rc == 0) |
| goto out; /* EOF */ |
| if (rc != sizeof(cur->ec)) { |
| ERR_MSG("reading ec-hdr failed\n"); |
| rc = -1; |
| goto err; |
| } |
| |
| /* check erasecounter header magic */ |
| if (be32_to_cpu(cur->ec.magic) != UBI_EC_HDR_MAGIC) { |
| snprintf(reason, MAXPATH, ".invalid.ec_magic"); |
| goto invalid; |
| } |
| |
| /* check erasecounter header crc */ |
| crc = clc_crc32(crc32_table, UBI_CRC32_INIT, &(cur->ec), |
| UBI_EC_HDR_SIZE_CRC); |
| if (be32_to_cpu(cur->ec.hdr_crc) != crc) { |
| snprintf(reason, MAXPATH, ".invalid.ec_hdr_crc"); |
| goto invalid; |
| } |
| |
| /* read volume id header */ |
| rc = fsetpos(fpin, &(cur->eb_top)); |
| if (rc != 0) |
| goto err; |
| fseek(fpin, be32_to_cpu(cur->ec.vid_hdr_offset), SEEK_CUR); |
| rc = fread(&cur->vid, 1, sizeof(cur->vid), fpin); |
| if (rc == 0) |
| goto out; /* EOF */ |
| if (rc != sizeof(cur->vid)) { |
| ERR_MSG("reading vid-hdr failed\n"); |
| rc = -1; |
| goto err; |
| } |
| |
| /* if the magic number is 0xFFFFFFFF, then it's very likely |
| * that the volume is empty */ |
| if (be32_to_cpu(cur->vid.magic) == 0xffffffff) { |
| snprintf(reason, MAXPATH, ".empty"); |
| goto invalid; |
| } |
| |
| /* vol_id should be in bounds */ |
| if ((be32_to_cpu(cur->vid.vol_id) >= UBI_MAX_VOLUMES) && |
| (be32_to_cpu(cur->vid.vol_id) < |
| UBI_INTERNAL_VOL_START)) { |
| snprintf(reason, MAXPATH, ".invalid"); |
| goto invalid; |
| } else |
| raw_path = FN_NSURE; |
| |
| /* check volume id header magic */ |
| if (be32_to_cpu(cur->vid.magic) != UBI_VID_HDR_MAGIC) { |
| snprintf(reason, MAXPATH, ".invalid.vid_magic"); |
| goto invalid; |
| } |
| cur->ec_crc_ok = 1; |
| |
| /* check volume id header crc */ |
| crc = clc_crc32(crc32_table, UBI_CRC32_INIT, &(cur->vid), |
| UBI_VID_HDR_SIZE_CRC); |
| if (be32_to_cpu(cur->vid.hdr_crc) != crc) { |
| snprintf(reason, MAXPATH, ".invalid.vid_hdr_crc"); |
| goto invalid; |
| } |
| cur->vid_crc_ok = 1; |
| |
| /* check data crc, but only for a static volume */ |
| if (cur->vid.vol_type == UBI_VID_STATIC) { |
| rc = data_crc(fpin, be32_to_cpu(cur->vid.data_size), |
| &crc); |
| if (rc < 0) |
| goto err; |
| if (be32_to_cpu(cur->vid.data_crc) != crc) { |
| snprintf(reason, MAXPATH, ".invalid.data_crc"); |
| goto invalid; |
| } |
| cur->data_crc_ok = 1; |
| } |
| |
| /* enlist this vol, it's valid */ |
| raw_path = FN_VALID; |
| cur->linear = count; |
| rc = eb_chain_insert(&head, cur); |
| if (rc < 0) { |
| if (rc == -ENOMEM) { |
| ERR_MSG("out of memory\n"); |
| goto err; |
| } |
| ERR_MSG("unknown and unexpected error, please contact " |
| CONTACT "\n"); |
| goto err; |
| } |
| |
| /* extract info-table */ |
| if (a->itable && |
| (be32_to_cpu(cur->vid.vol_id) == UBI_LAYOUT_VOLUME_ID)) { |
| extract_itable(fpin, cur, a->bsize, |
| itable_num, a->odir_path); |
| itable_num++; |
| } |
| |
| /* split volumes */ |
| if (a->vol_split) { |
| size_t size = 0; |
| |
| rc = fsetpos(fpin, &(cur->eb_top)); |
| if (rc != 0) |
| goto err; |
| |
| /* |
| * FIXME For dynamic UBI volumes we must write |
| * the maximum available data. The |
| * vid.data_size field is not used in this |
| * case. The dynamic volume user is |
| * responsible for the content. |
| */ |
| if (a->vol_split == SPLIT_DATA) { |
| /* Write only data section */ |
| if (cur->vid.vol_type == UBI_VID_DYNAMIC) { |
| /* FIXME Formular is not |
| always right ... */ |
| size = a->bsize - a->hsize; |
| } else |
| size = be32_to_cpu(cur->vid.data_size); |
| |
| fseek(fpin, |
| be32_to_cpu(cur->ec.data_offset), |
| SEEK_CUR); |
| } |
| else if (a->vol_split == SPLIT_RAW) |
| /* write entire eraseblock */ |
| size = a->bsize; |
| |
| snprintf(filename, MAXPATH, FN_VOLSP, |
| a->odir_path, |
| be32_to_cpu(cur->vid.vol_id), |
| be32_to_cpu(cur->vid.lnum), |
| be32_to_cpu(cur->vid.leb_ver), count); |
| rc = extract_data(fpin, size, filename); |
| if (rc < 0) |
| goto err; |
| } |
| |
| invalid: |
| /* split eraseblocks */ |
| if (a->eb_split) { |
| /* jump to top of block */ |
| rc = fsetpos(fpin, &(cur->eb_top)); |
| if (rc != 0) |
| goto err; |
| |
| if (strcmp(raw_path, FN_INVAL) == 0) |
| snprintf(filename, MAXPATH, raw_path, |
| a->odir_path, count, reason); |
| else |
| snprintf(filename, MAXPATH, raw_path, |
| a->odir_path, |
| count, |
| be32_to_cpu(cur->vid.vol_id), |
| be32_to_cpu(cur->vid.lnum), |
| be32_to_cpu(cur->vid.leb_ver), |
| reason); |
| |
| rc = extract_data(fpin, a->bsize, filename); |
| if (rc < 0) |
| goto err; |
| } |
| |
| /* append to simple linked list */ |
| if (first == NULL) |
| next_ptr = &first; |
| else |
| next_ptr = &next->next; |
| |
| *next_ptr = malloc(sizeof(**next_ptr)); |
| if (*next_ptr == NULL) { |
| ERR_MSG("out of memory\n"); |
| rc = -ENOMEM; |
| goto err; |
| } |
| memset(*next_ptr, 0, sizeof(**next_ptr)); |
| |
| next = *next_ptr; |
| memcpy(next, cur, sizeof(*next)); |
| next->next = NULL; |
| |
| count++; |
| rc = fsetpos(fpin, &(cur->eb_top)); |
| if (rc != 0) |
| goto err; |
| fseek(fpin, a->bsize, SEEK_CUR); |
| memset(cur, 0, sizeof(*cur)); |
| |
| fgetpos(fpin, &(cur->eb_top)); |
| } |
| |
| out: |
| for (i = 0; i < vc; i++) { |
| rc = rebuild_volume(fpin, &vols[i], &head, a->odir_path, |
| a->bsize, a->hsize); |
| if (rc < 0) |
| goto err; |
| } |
| |
| /* if there were no volumes specified, rebuild them all, |
| * UNLESS eb_ or vol_ split or analyze was specified */ |
| if ((vc == 0) && (!a->eb_split) && (!a->vol_split) && |
| (!a->analyze) && (!a->itable)) { |
| rc = rebuild_volume(fpin, NULL, &head, a->odir_path, a->bsize, |
| a->hsize); |
| if (rc < 0) |
| goto err; |
| } |
| |
| err: |
| free(cur); |
| |
| if (a->analyze) { |
| char fname[PATH_MAX + 1]; |
| FILE *fp; |
| |
| unubi_analyze(&head, first, a->odir_path); |
| |
| /* prepare output files */ |
| memset(fname, 0, PATH_MAX + 1); |
| snprintf(fname, PATH_MAX, "%s/%s", a->odir_path, FN_EH_STAT); |
| fp = fopen(fname, "w"); |
| if (fp != NULL) { |
| eb_chain_print(fp, head); |
| fclose(fp); |
| } |
| } |
| eb_chain_destroy(&head); |
| eb_chain_destroy(&first); |
| |
| return rc; |
| } |
| |
| |
| /** |
| * handles command line arguments, then calls unubi_volumes |
| **/ |
| int |
| main(int argc, char *argv[]) |
| { |
| int rc, free_a_odir; |
| size_t vols_len; |
| uint32_t *vols; |
| FILE* fpin; |
| struct args a; |
| |
| rc = 0; |
| free_a_odir = 0; |
| vols_len = 0; |
| vols = NULL; |
| fpin = NULL; |
| init_crc32_table(crc32_table); |
| |
| /* setup struct args a */ |
| memset(&a, 0, sizeof(a)); |
| a.bsize = 128 * KIB; |
| a.hsize = 2 * KIB; |
| a.vols = malloc(sizeof(*a.vols) * UBI_MAX_VOLUMES); |
| if (a.vols == NULL) { |
| ERR_MSG("out of memory\n"); |
| rc = ENOMEM; |
| goto err; |
| } |
| memset(a.vols, 0, sizeof(*a.vols) * UBI_MAX_VOLUMES); |
| |
| /* parse args and check for validity */ |
| parse_opt(argc, argv, &a); |
| if (a.img_path == NULL) { |
| ERR_MSG("no image file specified\n"); |
| rc = EINVAL; |
| goto err; |
| } |
| else if (a.odir_path == NULL) { |
| char *ptr; |
| int len; |
| |
| ptr = strrchr(a.img_path, '/'); |
| if (ptr == NULL) |
| ptr = a.img_path; |
| else |
| ptr++; |
| |
| len = strlen(DIR_FMT) + strlen(ptr); |
| free_a_odir = 1; |
| a.odir_path = malloc(sizeof(*a.odir_path) * len); |
| if (a.odir_path == NULL) { |
| ERR_MSG("out of memory\n"); |
| rc = ENOMEM; |
| goto err; |
| } |
| snprintf(a.odir_path, len, DIR_FMT, ptr); |
| } |
| |
| fpin = fopen(a.img_path, "rb"); |
| if (fpin == NULL) { |
| ERR_MSG("couldn't open file for reading: " |
| "%s\n", a.img_path); |
| rc = EINVAL; |
| goto err; |
| } |
| |
| rc = mkdir(a.odir_path, 0777); |
| if ((rc < 0) && (errno != EEXIST)) { |
| ERR_MSG("couldn't create ouput directory: " |
| "%s\n", a.odir_path); |
| rc = -rc; |
| goto err; |
| } |
| |
| /* fill in vols array */ |
| vols_len = count_set(a.vols, UBI_MAX_VOLUMES); |
| if (vols_len > 0) { |
| vols = malloc(sizeof(*vols) * vols_len); |
| if (vols == NULL) { |
| ERR_MSG("out of memory\n"); |
| rc = ENOMEM; |
| goto err; |
| } |
| collapse(a.vols, UBI_MAX_VOLUMES, vols, vols_len); |
| } |
| |
| /* unubi volumes */ |
| rc = unubi_volumes(fpin, vols, vols_len, &a); |
| if (rc < 0) { |
| /* ERR_MSG("error encountered while working on image file: " |
| "%s\n", a.img_path); */ |
| rc = -rc; |
| goto err; |
| } |
| |
| err: |
| free(a.vols); |
| if (free_a_odir != 0) |
| free(a.odir_path); |
| if (fpin != NULL) |
| fclose(fpin); |
| if (vols_len > 0) |
| free(vols); |
| return rc; |
| } |