From c26ce774a7209012c0505ee841d54898c6665e20 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Wed, 15 Mar 2017 12:12:12 +0100 Subject: Add lsmtd program This patch adds a program called "lsmtd". The program produces a pretty printed list of the hierarchy of UBI and MTD devices on a system. It tries to imitate the lsblk program from util-linux as closely as possible. A number of command line switches are available to fine tune what information should be exposed and in what output format. The goal is to have a simple way of displaying the complete MTD stack on a system in a human readable form instead of piecing details together from proc files and various UBI utilities. Signed-off-by: David Oberhollenzer --- misc-utils/Makemodule.am | 9 +- misc-utils/lsmtd.c | 793 +++++++++++++++++++++++++++++++++++++++++++++++ misc-utils/lsmtd.h | 67 ++++ misc-utils/lsmtd_scan.c | 224 +++++++++++++ 4 files changed, 1091 insertions(+), 2 deletions(-) create mode 100644 misc-utils/lsmtd.c create mode 100644 misc-utils/lsmtd.h create mode 100644 misc-utils/lsmtd_scan.c (limited to 'misc-utils') diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index ce1c385..775925e 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -33,11 +33,15 @@ flashcp_SOURCES = misc-utils/flashcp.c flash_erase_SOURCES = misc-utils/flash_erase.c flash_erase_LDADD = libmtd.a +lsmtd_SOURCES = misc-utils/lsmtd.c misc-utils/lsmtd_scan.c +lsmtd_LDADD = libmtd.a libubi.a +lsmtd_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/misc-utils + MISC_BINS = \ ftl_format doc_loadbios ftl_check mtd_debug docfdisk \ serve_image recv_image flash_erase flash_lock \ flash_unlock flash_otp_info flash_otp_dump flash_otp_lock \ - flash_otp_write flashcp mtdpart + flash_otp_write flashcp mtdpart lsmtd MISC_SH = \ misc-utils/flash_eraseall @@ -46,7 +50,8 @@ MISC_EXTRA = \ misc-utils/MAKEDEV MISC_HEADER = \ - misc-utils/mcast_image.h + misc-utils/mcast_image.h \ + misc-utils/lsmtd.h EXTRA_DIST += $(MISC_HEADER) $(MISC_EXTRA) $(MISC_SH) diff --git a/misc-utils/lsmtd.c b/misc-utils/lsmtd.c new file mode 100644 index 0000000..4b1de2c --- /dev/null +++ b/misc-utils/lsmtd.c @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2017 David Oberhollenzer - sigma star gmbh + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA. + * + * Author: David Oberhollenzer + */ +#include +#include + +#include "lsmtd.h" + +#define FLAG_SI 0x0001 +#define FLAG_BYTES 0x0002 +#define FLAG_NO_HEADING 0x0004 +#define FLAG_RAW 0x0008 +#define FLAG_PAIRS 0x0010 +#define FLAG_LIST 0x0020 +#define FLAG_JSON 0x0040 +#define FLAG_ASCII 0x0080 +#define FLAG_NO_UBI 0x0100 +#define FLAG_DRYRUN 0x1000 + +static int flags; +static struct column **selected; +static size_t num_selected; +static size_t max_selected; +struct column *sort_by; + +static const struct option long_opts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "si-units", no_argument, NULL, 'u' }, + { "bytes", no_argument, NULL, 'b' }, + { "noheadings", no_argument, NULL, 'n' }, + { "raw", no_argument, NULL, 'r' }, + { "output", required_argument, NULL, 'o' }, + { "output-all", no_argument, NULL, 'O' }, + { "pairs", no_argument, NULL, 'P' }, + { "list", no_argument, NULL, 'l' }, + { "json", no_argument, NULL, 'J' }, + { "sort", required_argument, NULL, 'x' }, + { "ascii", no_argument, NULL, 'i' }, + { "no-ubi", no_argument, NULL, 'm' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "x:o:OPJlibrumnhV"; +static const char *default_cols = "DEVICE,MAJ:MIN,NAME,TYPE,SIZE"; + +static struct column cols[] = { + { "DEVICE", "name of the device node", COL_DEVNAME, COL_DT_STRING, 0 }, + { "MAJ:MIN", "major:minor device number", + COL_DEVNUM, COL_DT_STRING, 0 }, + { "NAME", "device name string", COL_NAME, COL_DT_STRING, 0 }, + { "TYPE", "device type", COL_TYPE, COL_DT_STRING, 0 }, + { "SIZE", "size of the device", COL_SIZE, COL_DT_SIZE, 0 }, + { "EB-SIZE", "erase block size", COL_EBSIZE, COL_DT_SIZE, 0 }, + { "EB-COUNT", "number of erase blocks", COL_EBCOUNT, COL_DT_NUMBER, 0 }, + { "MIN-IO", "minimum I/O size", COL_MINIO, COL_DT_SIZE, 0 }, + { "SUB-SIZE", "subpage size", COL_SUBSIZE, COL_DT_SIZE, 0 }, + { "OOB-SIZE", "out of band data size", COL_OOBSIZE, COL_DT_SIZE, 0 }, + { "RO", "read-only device", COL_RO, COL_DT_BOOL, 0 }, + { "CORRUPTED", "wheather an UBI volume is corrupted", + COL_CORRUPTED, COL_DT_BOOL, 0 }, + { "REGIONS", "number of additional erase regions", + COL_REGION, COL_DT_NUMBER, 0 }, + { "BB", "wheather the MTD device may have bad eraseblocks", + COL_BB, COL_DT_BOOL, 0 }, + { "MAX-EC", "current highest erase counter value on UBI devices", + COL_MAXEC, COL_DT_NUMBER, 0 }, + { "FREE", "available bytes on an UBI device or volume", + COL_FREE, COL_DT_SIZE, 0 }, + { "FREE-LEB", "available LEBs on an UBI device or volume", + COL_FREE_LEB, COL_DT_NUMBER, 0 }, + { "BAD-COUNT", "number of bad physical eraseblocks", + COL_BAD_COUNT, COL_DT_NUMBER, 0 }, + { "BAD-RSVD", "number of reserved eraseblocks for bad block handling", + COL_BAD_RSVD, COL_DT_NUMBER, 0 }, +}; + +static NORETURN void usage(int status) +{ + FILE *outstream = status == EXIT_SUCCESS ? stdout : stderr; + size_t i, len, max_len = 0; + + fputs( +"Usage: "PROGRAM_NAME" [options] [ ...]\n\n" +"List information about memory technology devices.\n\n" +"Options:\n" +" -u, --si-units Scale sizes by factors of 1000 instead of 1024\n" +" -b, --bytes Print sizes in bytes\n" +" -i, --ascii Use ascii characters only\n" +" -l, --list Use list output format (default)\n" +" -n, --noheadings Don't print a heading\n" +" -r, --raw Use raw output format\n" +" -P, --pairs Use key=\"value\" output format\n" +" -J, --json Use JSON output format\n" +" -o, --output Comma seperated list of columns to print\n" +" -O, --output-all Print all columns\n" +" -x, --sort Sort output by \n" +" -m, --no-ubi Do not display information about UBI devices/volumes\n" +"\n" +" -h, --help Display this help text and exit\n" +" -V, --version Output version information and exit\n" +"\n" +"Available columns (for --output, --sort):\n", + outstream); + + for (i = 0; i < sizeof(cols) / sizeof(cols[0]); ++i) { + len = strlen(cols[i].name); + max_len = len > max_len ? len : max_len; + } + + for (i = 0; i < sizeof(cols) / sizeof(cols[0]); ++i) { + fprintf(outstream, " %*s %s\n", (int)max_len, cols[i].name, + cols[i].desc); + } + + exit(status); +} + +static NORETURN void version(int status) +{ + common_print_version(); + fputs( +"Copyright (C) 2017 David Oberhollenzer - sigma star gmbh\n" +"License GPLv2: GNU GPL version 2 .\n" +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n\n" +"Written by David Oberhollenzer.\n", + stdout); + exit(status); +} + +static struct column *column_by_name(const char *name, size_t len) +{ + size_t i; + + for (i = 0; i < sizeof(cols) / sizeof(cols[0]); ++i) { + if (strncmp(cols[i].name, name, len) != 0) + continue; + if (strlen(cols[i].name) == len) + return cols + i; + } + + return NULL; +} + +static int process_col_list(const char *list) +{ + struct column *col; + const char *end; + size_t len; + + if (*list == '+') { + ++list; + } else { + num_selected = 0; + } + + while (*list) { + end = strchrnul(list, ','); + len = end - list; + + col = column_by_name(list, len); + if (!col) { + fprintf(stderr, "Unknown column '%.*s'\n", + (int)len, list); + return -1; + } + + if (num_selected == max_selected) { + max_selected = max_selected ? max_selected * 2 : 10; + selected = xrealloc(selected, max_selected * + sizeof(*selected)); + } + + selected[num_selected++] = col; + list = *end ? end + 1 : end; + } + return 0; +} + +static void select_all(void) +{ + size_t i; + + num_selected = sizeof(cols) / sizeof(cols[0]); + + if (max_selected < num_selected) { + max_selected = num_selected; + selected = xrealloc(selected, max_selected * sizeof(*selected)); + } + + for (i = 0; i < num_selected; ++i) + selected[i] = cols + i; +} + +static void process_args(int argc, char **argv) +{ + int i; + + process_col_list(default_cols); + + while (1) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'x': + sort_by = column_by_name(optarg, strlen(optarg)); + if (!sort_by) { + fprintf(stderr, "Unknown column '%s'\n", + optarg); + goto fail; + } + break; + case 'o': + if (process_col_list(optarg) != 0) + goto fail; + break; + case 'O': + select_all(); + break; + case 'i': flags |= FLAG_ASCII; break; + case 'J': flags |= FLAG_JSON; break; + case 'P': flags |= FLAG_PAIRS; break; + case 'l': flags |= FLAG_LIST; break; + case 'b': flags |= FLAG_BYTES; break; + case 'r': flags |= FLAG_RAW; break; + case 'u': flags |= FLAG_SI; break; + case 'n': flags |= FLAG_NO_HEADING; break; + case 'm': flags |= FLAG_NO_UBI; break; + case 'h': usage(EXIT_SUCCESS); + case 'V': version(EXIT_SUCCESS); + default: usage(EXIT_FAILURE); + } + } + + i = flags & (FLAG_LIST|FLAG_PAIRS|FLAG_RAW|FLAG_JSON); + + if (i & (i - 1)) { + fputs(PROGRAM_NAME": these options are mutually exclusive: " + "--list --pairs --raw --json\n", stderr); + goto fail; + } else if (!i) { + flags |= FLAG_LIST; + } + + /*if (optind < argc) + list_arg = optind;*/ + return; +fail: + fputs("Try `"PROGRAM_NAME" --help` for more information\n\n", stderr); + exit(EXIT_FAILURE); +} + +static const char *tree_prefix(bool is_last) +{ + if (is_last) + return (flags & FLAG_ASCII) ? "`-" : "└─"; + return (flags & FLAG_ASCII) ? "|-" : "├─"; +} + +static size_t count_chars(const char *str) +{ + size_t count = 0; + while (*str) { + if (((*str) & 0xC0) != 0x80) + ++count; + ++str; + } + return count; +} + +static void devno_to_string(char *buffer, int major, int minor) +{ + sprintf(buffer, flags & FLAG_LIST ? "%3d:%d" : "%d:%d", major, minor); +} + +static void bool_to_string(char *buffer, int value) +{ + if (flags & FLAG_JSON) + strcpy(buffer, value ? "true" : "false"); + else + strcpy(buffer, value ? "1" : "0"); +} + +static void size_to_string(char *buffer, long long int size) +{ + static const char *bcdmap = "0112334456678899"; + static const char *suffix = "KMGTPE"; + int scale, idx, i, remainder = 0; + + if (flags & FLAG_BYTES) { + sprintf(buffer, "%lld", size); + return; + } + + scale = flags & FLAG_SI ? 1000 : 1024; + + for (idx = -1; size >= scale && (idx < 0 || suffix[idx]); ++idx) { + if (remainder >= (scale / 2)) { + remainder = 0; + size = (size / scale) + 1; + } else { + remainder = size % scale; + size /= scale; + } + } + + i = sprintf(buffer, "%lld", size); + + remainder = (remainder >> 6) & 0x0F; + if (remainder) { + buffer[i++] = '.'; + buffer[i++] = bcdmap[remainder]; + } + if (idx >= 0) + buffer[i++] = suffix[idx]; + buffer[i] = '\0'; +} + +static void print_json_string(const char *value) +{ + static const char *jsonrepl = "nrtfb", *jsonesc = "\n\r\t\f\b"; + const char *ptr; + + fputc('"', stdout); + for (; *value; ++value) { + ptr = strchr(jsonesc, *value); + if (ptr) { + fputc('\\', stdout); + fputc(jsonrepl[ptr - jsonesc], stdout); + } else if (*value == '\\' || *value == '"') { + fputc('\\', stdout); + fputc(*value, stdout); + } else if (isascii(*value) && + (iscntrl(*value) || !isprint(*value))) { + fprintf(stdout, "\\u%04X", *value); + } else { + fputc(*value, stdout); + } + } + fputc('"', stdout); +} + +static void print_escaped(const char *value) +{ + while (*value) { + if (iscntrl(*value) || !isprint(*value) || + *value == '\\' || *value == '"') { + fprintf(stdout, "\\x%02X", *(value++)); + } else { + fputc(*(value++), stdout); + } + } +} + +static void print_padded(const char *value, bool numeric, size_t width) +{ + size_t i; + + if (numeric) { + fprintf(stdout, "%*s", (int)width, value); + } else { + for (i = 0; i < width && *value; ++i) { + fputc(*(value++), stdout); + while (((*value) & 0xC0) == 0x80) + fputc(*(value++), stdout); + } + + for (; i < width; ++i) + fputc(' ', stdout); + } +} + +static void print_column(struct column *col, const char *value, + bool is_first, int level) +{ + bool numeric = false; + const char *key; + size_t colw; + + if (col->datatype == COL_DT_NUMBER || col->datatype == COL_DT_SIZE || + col->datatype == COL_DT_BOOL) { + numeric = true; + } + + if (flags & FLAG_JSON) { + if ((col->datatype == COL_DT_SIZE) && !(flags & FLAG_BYTES)) + numeric = false; + + if (!is_first) + fputs(",\n", stdout); + + while (level--) + fputc('\t', stdout); + + fputc('"', stdout); + for (key = col->name; *key; ++key) + fputc(isupper(*key) ? tolower(*key) : *key, stdout); + fputs("\": ", stdout); + + if (numeric) { + fputs(value, stdout); + } else { + print_json_string(value); + } + } else if (flags & FLAG_DRYRUN) { + colw = count_chars(value); + col->width = colw > col->width ? colw : col->width; + } else if (flags & FLAG_PAIRS) { + if (!is_first) + fputc(' ', stdout); + fprintf(stdout, "%s=\"", col->name); + print_escaped(value); + fputs("\"", stdout); + } else if (flags & FLAG_RAW) { + if (!is_first) + fputc(' ', stdout); + print_escaped(value); + } else if (flags & FLAG_LIST) { + if (!is_first) + fputc(' ', stdout); + print_padded(value, numeric, col->width); + } +} + +static size_t print_mtd_device(struct mtd_dev_info *info) +{ + size_t i, count = 0; + const char *value; + char buffer[128]; + + for (i = 0; i < num_selected; ++i) { + value = buffer; + switch (selected[i]->type) { + case COL_DEVNAME: + sprintf(buffer, "mtd%d", info->mtd_num); + break; + case COL_DEVNUM: + devno_to_string(buffer, info->major, info->minor); + break; + case COL_TYPE: + value = info->type_str; + break; + case COL_NAME: + value = info->name; + break; + case COL_SIZE: + size_to_string(buffer, info->size); + break; + case COL_EBSIZE: + size_to_string(buffer, info->eb_size); + break; + case COL_EBCOUNT: + sprintf(buffer, "%d", info->eb_cnt); + break; + case COL_MINIO: + size_to_string(buffer, info->min_io_size); + break; + case COL_SUBSIZE: + size_to_string(buffer, info->subpage_size); + break; + case COL_OOBSIZE: + size_to_string(buffer, info->oob_size); + break; + case COL_RO: + bool_to_string(buffer, !info->writable); + break; + case COL_BB: + bool_to_string(buffer, !info->bb_allowed); + break; + case COL_REGION: + sprintf(buffer, "%d", info->region_cnt); + break; + default: + if (flags & FLAG_JSON) + continue; + buffer[0] = '\0'; + break; + } + print_column(selected[i], value, i == 0, 2); + ++count; + } + return count; +} + +static size_t print_ubi_device(struct mtd_dev_info *mtd, + struct ubi_dev_info *info) +{ + size_t i, count = 0; + char value[128]; + + for (i = 0; i < num_selected; ++i) { + switch (selected[i]->type) { + case COL_DEVNAME: + if (flags & FLAG_LIST) { + sprintf(value, "%subi%d", tree_prefix(true), + info->dev_num); + } else { + sprintf(value, "ubi%d", info->dev_num); + } + break; + case COL_DEVNUM: + devno_to_string(value, info->major, info->minor); + break; + case COL_SIZE: + size_to_string(value, info->total_bytes); + break; + case COL_EBSIZE: + size_to_string(value, info->leb_size); + break; + case COL_EBCOUNT: + sprintf(value, "%d", info->total_lebs); + break; + case COL_MINIO: + size_to_string(value, info->min_io_size); + break; + case COL_MAXEC: + sprintf(value, "%lld", info->max_ec); + break; + case COL_FREE: + size_to_string(value, info->avail_bytes); + break; + case COL_FREE_LEB: + sprintf(value, "%d", info->avail_lebs); + break; + case COL_BAD_COUNT: + sprintf(value, "%d", info->bad_count); + break; + case COL_BAD_RSVD: + sprintf(value, "%d", info->bad_rsvd); + break; + case COL_RO: + bool_to_string(value, !mtd->writable); + break; + default: + if (flags & FLAG_JSON) + continue; + value[0] = '\0'; + break; + } + print_column(selected[i], value, i == 0, 3); + ++count; + } + return count; +} + +static size_t print_ubi_vol(struct mtd_dev_info *mtd, struct ubi_dev_info *dev, + struct ubi_vol_info *info, bool is_last) +{ + size_t i, count = 0; + const char *value; + char buffer[128]; + int used; + + for (i = 0; i < num_selected; ++i) { + value = buffer; + switch (selected[i]->type) { + case COL_DEVNAME: + if (flags & FLAG_LIST) { + sprintf(buffer, " %subi%d_%d", + tree_prefix(is_last), + info->dev_num, info->vol_id); + } else { + sprintf(buffer, "ubi%d_%d", info->dev_num, + info->vol_id); + } + break; + case COL_DEVNUM: + devno_to_string(buffer, info->major, info->minor); + break; + case COL_TYPE: + if (info->type == UBI_DYNAMIC_VOLUME) { + value = "dynamic"; + } else { + value = "static"; + } + break; + case COL_NAME: + value = info->name; + break; + case COL_SIZE: + size_to_string(buffer, info->rsvd_bytes); + break; + case COL_EBSIZE: + size_to_string(buffer, info->leb_size); + break; + case COL_EBCOUNT: + sprintf(buffer, "%d", info->rsvd_lebs); + break; + case COL_MINIO: + size_to_string(buffer, dev->min_io_size); + break; + case COL_FREE: + size_to_string(buffer, + info->rsvd_bytes - info->data_bytes); + break; + case COL_FREE_LEB: + used = info->data_bytes / info->leb_size; + sprintf(buffer, "%d", info->rsvd_lebs - used); + break; + case COL_RO: + bool_to_string(buffer, !mtd->writable); + break; + case COL_CORRUPTED: + bool_to_string(buffer, info->corrupted); + break; + default: + if (flags & FLAG_JSON) + continue; + buffer[0] = '\0'; + break; + } + print_column(selected[i], value, i == 0, 4); + ++count; + } + return count; +} + +static void print_list(void) +{ + struct ubi_node *ubi; + bool is_last; + size_t i; + int j; + + if (!(flags & FLAG_NO_HEADING)) { + if (flags & (FLAG_DRYRUN | FLAG_RAW)) { + for (i = 0; i < num_selected; ++i) + selected[i]->width = strlen(selected[i]->name); + } + + if (!(flags & FLAG_DRYRUN)) { + for (i = 0; i < num_selected; ++i) { + fprintf(stdout, "%-*s ", + (int)selected[i]->width, + selected[i]->name); + } + fputc('\n', stdout); + } + } + + for (i = 0; i < num_mtd_devices; ++i) { + print_mtd_device(&mtd_dev[i].info); + if (!(flags & FLAG_DRYRUN)) + fputc('\n', stdout); + + ubi = mtd_dev[i].ubi; + if (!ubi) + continue; + + print_ubi_device(&mtd_dev[i].info, &ubi->info); + if (!(flags & FLAG_DRYRUN)) + fputc('\n', stdout); + + for (j = 0; j < ubi->info.vol_count; ++j) { + is_last = (j == (ubi->info.vol_count - 1)); + print_ubi_vol(&mtd_dev[i].info, &ubi->info, + ubi->vol_info + j, is_last); + if (!(flags & FLAG_DRYRUN)) + fputc('\n', stdout); + } + } +} + +static void print_pairs(void) +{ + struct ubi_node *ubi; + int i, j; + + for (i = 0; i < num_mtd_devices; ++i) { + print_mtd_device(&mtd_dev[i].info); + fputc('\n', stdout); + + ubi = mtd_dev[i].ubi; + if (ubi) { + print_ubi_device(&mtd_dev[i].info, &ubi->info); + fputc('\n', stdout); + + for (j = 0; j < ubi->info.vol_count; ++j) { + print_ubi_vol(&mtd_dev[i].info, &ubi->info, + ubi->vol_info + j, false); + fputc('\n', stdout); + } + } + } +} + +static void print_json(void) +{ + struct ubi_node *ubi; + int i, j; + + fputs("{\n\t\"mtddevices\": [", stdout); + + for (i = 0; i < num_mtd_devices; ++i) { + fputs(i ? ",{\n" : "{\n", stdout); + if (print_mtd_device(&mtd_dev[i].info) > 0) + fputs(",\n", stdout); + + ubi = mtd_dev[i].ubi; + if (ubi) { + fputs("\t\t\"ubi\": {\n", stdout); + if (print_ubi_device(&mtd_dev[i].info, &ubi->info) > 0) + fputs(",\n", stdout); + + fputs("\t\t\t\"volumes\": [", stdout); + + for (j = 0; j < ubi->info.vol_count; ++j) { + fputs(j ? ",{\n" : "{\n", stdout); + print_ubi_vol(&mtd_dev[i].info, &ubi->info, + ubi->vol_info + j, false); + fputs("\n\t\t\t}", stdout); + } + + fputs("]\n\t\t}\n", stdout); + } else if (!(flags & FLAG_NO_UBI)) { + fputs("\t\t\"ubi\": null\n", stdout); + } + fputs("\t}", stdout); + } + + fputs("]\n}\n", stdout); +} + +int main(int argc, char **argv) +{ + int ret, status = EXIT_FAILURE; + libmtd_t lib_mtd; + libubi_t lib_ubi; + + process_args(argc, argv); + + lib_mtd = libmtd_open(); + if (lib_mtd) { + ret = scan_mtd(lib_mtd); + libmtd_close(lib_mtd); + if (ret) + goto out; + } else { + if (errno) { + perror("libmtd_open"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + + if (!(flags & FLAG_NO_UBI)) { + lib_ubi = libubi_open(); + if (lib_ubi) { + ret = scan_ubi(lib_ubi); + libubi_close(lib_ubi); + if (ret) + goto out; + } else if (errno) { + perror("libubi_open"); + goto out; + } + } + + if (flags & FLAG_JSON) { + print_json(); + } else if (flags & FLAG_PAIRS) { + print_pairs(); + } else { + flags |= FLAG_DRYRUN; + print_list(); + flags &= ~FLAG_DRYRUN; + print_list(); + } + + status = EXIT_SUCCESS; +out: + scan_free(); + free(selected); + return status; +} diff --git a/misc-utils/lsmtd.h b/misc-utils/lsmtd.h new file mode 100644 index 0000000..11d219e --- /dev/null +++ b/misc-utils/lsmtd.h @@ -0,0 +1,67 @@ +#ifndef LSMTD_H +#define LSMTD_H + +#define PROGRAM_NAME "lsmtd" +#include "common.h" +#include "xalloc.h" + +#include +#include + +#define COL_DEVNAME 1 +#define COL_DEVNUM 2 +#define COL_TYPE 3 +#define COL_NAME 4 +#define COL_SIZE 5 +#define COL_EBSIZE 6 +#define COL_EBCOUNT 7 +#define COL_MINIO 8 +#define COL_SUBSIZE 9 +#define COL_OOBSIZE 10 +#define COL_MAXEC 11 +#define COL_FREE 12 +#define COL_FREE_LEB 13 +#define COL_BAD_COUNT 14 +#define COL_BAD_RSVD 15 +#define COL_RO 16 +#define COL_BB 17 +#define COL_REGION 18 +#define COL_CORRUPTED 19 + +#define COL_DT_STRING 1 +#define COL_DT_NUMBER 2 +#define COL_DT_SIZE 3 +#define COL_DT_BOOL 4 + +struct ubi_node { + struct ubi_dev_info info; + struct ubi_vol_info *vol_info; +}; + +struct mtd_node { + struct mtd_dev_info info; + struct ubi_node *ubi; +}; + +struct column { + const char *name; + const char *desc; + int type; + int datatype; + size_t width; +}; + +extern struct ubi_node *ubi_dev; +extern int num_ubi_devices; + +extern struct mtd_node *mtd_dev; +extern int num_mtd_devices; + +extern struct column *sort_by; + +int scan_mtd(libmtd_t lib_mtd); +int scan_ubi(libubi_t lib_ubi); +void scan_free(void); + +#endif /* LSMTD_H */ + diff --git a/misc-utils/lsmtd_scan.c b/misc-utils/lsmtd_scan.c new file mode 100644 index 0000000..cec7b5c --- /dev/null +++ b/misc-utils/lsmtd_scan.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2017 David Oberhollenzer - sigma star gmbh + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA. + * + * Author: David Oberhollenzer + */ +#include "lsmtd.h" + +struct ubi_node *ubi_dev; +int num_ubi_devices; + +struct mtd_node *mtd_dev; +int num_mtd_devices; + +static int compare_mtd(const void *l, const void *r) +{ + const struct mtd_node *a = l, *b = r; + + switch (sort_by->type) { + case COL_DEVNAME: + return a->info.mtd_num - b->info.mtd_num; + case COL_DEVNUM: + if (a->info.major == b->info.major) + return a->info.minor - b->info.minor; + return a->info.major - b->info.major; + case COL_TYPE: + return strcmp(a->info.type_str, b->info.type_str); + case COL_NAME: + return strcmp(a->info.name, b->info.name); + case COL_SIZE: + if (a->info.size < b->info.size) + return -1; + if (a->info.size > b->info.size) + return 1; + return 0; + case COL_EBSIZE: + return a->info.eb_size - b->info.eb_size; + case COL_EBCOUNT: + return a->info.eb_cnt - b->info.eb_cnt; + case COL_MINIO: + return a->info.min_io_size - b->info.min_io_size; + case COL_SUBSIZE: + return a->info.subpage_size - b->info.subpage_size; + case COL_OOBSIZE: + return a->info.oob_size - b->info.oob_size; + case COL_RO: + return !a->info.writable - !b->info.writable; + case COL_BB: + return a->info.bb_allowed - b->info.bb_allowed; + case COL_REGION: + return a->info.region_cnt - b->info.region_cnt; + } + return 0; +} + +static int compare_ubi_vol(const void *l, const void *r) +{ + const struct ubi_vol_info *a = l, *b = r; + long long all, bll; + + switch (sort_by->type) { + case COL_DEVNAME: + if (a->dev_num == b->dev_num) + return a->vol_id - b->vol_id; + return a->dev_num - b->dev_num; + case COL_DEVNUM: + if (a->major == b->major) + return a->minor - b->minor; + return a->major - b->major; + case COL_TYPE: + if (a->type == b->type) + return 0; + return a->type == UBI_DYNAMIC_VOLUME ? 1 : -1; + case COL_NAME: + return strcmp(a->name, b->name); + case COL_SIZE: + all = a->rsvd_bytes; + bll = b->rsvd_bytes; + goto out_ll; + case COL_EBSIZE: + return a->leb_size - b->leb_size; + case COL_EBCOUNT: + return a->rsvd_lebs - b->rsvd_lebs; + case COL_FREE: + case COL_FREE_LEB: + all = (a->rsvd_bytes - a->data_bytes); + bll = (b->rsvd_bytes - b->data_bytes); + goto out_ll; + case COL_CORRUPTED: + return a->corrupted - b->corrupted; + } + return 0; +out_ll: + return (all < bll) ? -1 : ((all > bll) ? 1 : 0); +} + +static int scan_ubi_device(libubi_t lib_ubi, struct ubi_node *dev) +{ + int lo = dev->info.lowest_vol_id, hi = dev->info.highest_vol_id; + int i, idx = 0, dev_num = dev->info.dev_num; + struct ubi_vol_info vol_info; + + if (!dev->info.vol_count) + return 0; + + dev->vol_info = xcalloc(dev->info.vol_count, sizeof(dev->vol_info[0])); + + for (i = lo; i <= hi; ++i) { + if (ubi_get_vol_info1(lib_ubi, dev_num, i, &vol_info)) { + if (errno == ENOENT) + continue; + perror("ubi_get_vol_info1"); + return -1; + } + + dev->vol_info[idx++] = vol_info; + } + + if (sort_by) + qsort(dev->vol_info, idx, sizeof(vol_info), compare_ubi_vol); + return 0; +} + +int scan_ubi(libubi_t lib_ubi) +{ + struct ubi_dev_info dev_info; + struct ubi_info info; + int i, j; + + if (ubi_get_info(lib_ubi, &info)) + return -1; + + if (!info.dev_count) + return 0; + + ubi_dev = xcalloc(info.dev_count, sizeof(ubi_dev[0])); + + for (i = info.lowest_dev_num; i <= info.highest_dev_num; ++i) { + if (!ubi_dev_present(lib_ubi, i)) + continue; + + if (ubi_get_dev_info1(lib_ubi, i, &dev_info)) { + perror("ubi_get_dev_info1"); + return -1; + } + + for (j = 0; j < num_mtd_devices; ++j) { + if (mtd_dev[j].info.mtd_num == dev_info.mtd_num) + break; + } + + if (j == num_mtd_devices) { + fprintf(stderr, "Cannot find mtd device %d refered to " + "by ubi device %d\n", dev_info.mtd_num, + dev_info.dev_num); + return -1; + } + + ubi_dev[num_ubi_devices].info = dev_info; + mtd_dev[j].ubi = ubi_dev + num_ubi_devices; + + if (scan_ubi_device(lib_ubi, ubi_dev + num_ubi_devices)) + return -1; + + ++num_ubi_devices; + } + return 0; +} + +int scan_mtd(libmtd_t lib_mtd) +{ + struct mtd_dev_info dev_info; + struct mtd_info info; + int i, idx = 0; + + if (mtd_get_info(lib_mtd, &info)) + return -1; + + if (!info.mtd_dev_cnt) + return 0; + + mtd_dev = xcalloc(info.mtd_dev_cnt, sizeof(mtd_dev[0])); + + for (i = info.lowest_mtd_num; i <= info.highest_mtd_num; ++i) { + if (!mtd_dev_present(lib_mtd, i)) + continue; + + if (mtd_get_dev_info1(lib_mtd, i, &dev_info)) { + perror("mtd_get_dev_info1"); + return -1; + } + + memcpy(&(mtd_dev[idx++].info), &dev_info, sizeof(dev_info)); + } + + num_mtd_devices = idx; + + if (sort_by) + qsort(mtd_dev, num_mtd_devices, sizeof(*mtd_dev), compare_mtd); + return 0; +} + +void scan_free(void) +{ + int i; + + for (i = 0; i < num_ubi_devices; ++i) + free(ubi_dev[i].vol_info); + + free(ubi_dev); + free(mtd_dev); +} -- cgit v1.2.3