| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2021-2022 HUAWEI, Inc. |
| * http://www.huawei.com/ |
| * Created by Wang Qi <mpiglet@outlook.com> |
| * Guo Xuenan <guoxuenan@huawei.com> |
| */ |
| #include <stdlib.h> |
| #include <getopt.h> |
| #include <time.h> |
| #include "erofs/print.h" |
| #include "erofs/inode.h" |
| #include "erofs/io.h" |
| |
| #ifdef HAVE_LIBUUID |
| #include <uuid.h> |
| #endif |
| |
| struct erofsdump_cfg { |
| unsigned int totalshow; |
| bool show_inode; |
| bool show_extent; |
| bool show_superblock; |
| bool show_statistics; |
| erofs_nid_t nid; |
| const char *inode_path; |
| }; |
| static struct erofsdump_cfg dumpcfg; |
| |
| static const char chart_format[] = "%-16s %-11d %8.2f%% |%-50s|\n"; |
| static const char header_format[] = "%-16s %11s %16s |%-50s|\n"; |
| static char *file_types[] = { |
| ".txt", ".so", ".xml", ".apk", |
| ".odex", ".vdex", ".oat", ".rc", |
| ".otf", ".txt", "others", |
| }; |
| #define OTHERFILETYPE ARRAY_SIZE(file_types) |
| /* (1 << FILE_MAX_SIZE_BITS)KB */ |
| #define FILE_MAX_SIZE_BITS 16 |
| |
| static const char * const file_category_types[] = { |
| [EROFS_FT_UNKNOWN] = "unknown type", |
| [EROFS_FT_REG_FILE] = "regular file", |
| [EROFS_FT_DIR] = "directory", |
| [EROFS_FT_CHRDEV] = "char dev", |
| [EROFS_FT_BLKDEV] = "block dev", |
| [EROFS_FT_FIFO] = "FIFO file", |
| [EROFS_FT_SOCK] = "SOCK file", |
| [EROFS_FT_SYMLINK] = "symlink file", |
| }; |
| |
| struct erofs_statistics { |
| unsigned long files; |
| unsigned long compressed_files; |
| unsigned long uncompressed_files; |
| unsigned long files_total_size; |
| unsigned long files_total_origin_size; |
| double compress_rate; |
| |
| /* [statistics] # of files based on inode_info->flags */ |
| unsigned long file_category_stat[EROFS_FT_MAX]; |
| /* [statistics] # of files based on file name extensions */ |
| unsigned int file_type_stat[OTHERFILETYPE]; |
| /* [statistics] # of files based on the original size of files */ |
| unsigned int file_original_size[FILE_MAX_SIZE_BITS + 1]; |
| /* [statistics] # of files based on the compressed size of files */ |
| unsigned int file_comp_size[FILE_MAX_SIZE_BITS + 1]; |
| }; |
| static struct erofs_statistics stats; |
| |
| static struct option long_options[] = { |
| {"help", no_argument, NULL, 1}, |
| {"nid", required_argument, NULL, 2}, |
| {"device", required_argument, NULL, 3}, |
| {"path", required_argument, NULL, 4}, |
| {0, 0, 0, 0}, |
| }; |
| |
| struct erofsdump_feature { |
| bool compat; |
| u32 flag; |
| const char *name; |
| }; |
| |
| static struct erofsdump_feature feature_lists[] = { |
| { true, EROFS_FEATURE_COMPAT_SB_CHKSUM, "sb_csum" }, |
| { false, EROFS_FEATURE_INCOMPAT_LZ4_0PADDING, "0padding" }, |
| { false, EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER, "big_pcluster" }, |
| { false, EROFS_FEATURE_INCOMPAT_CHUNKED_FILE, "chunked_file" }, |
| { false, EROFS_FEATURE_INCOMPAT_DEVICE_TABLE, "device_table" }, |
| }; |
| |
| static int erofs_read_dir(erofs_nid_t nid, erofs_nid_t parent_nid); |
| static inline int erofs_checkdirent(struct erofs_dirent *de, |
| struct erofs_dirent *last_de, |
| u32 maxsize, const char *dname); |
| |
| static void usage(void) |
| { |
| fputs("usage: [options] IMAGE\n\n" |
| "Dump erofs layout from IMAGE, and [options] are:\n" |
| " -S show statistic information of the image\n" |
| " -V print the version number of dump.erofs and exit.\n" |
| " -e show extent info (--nid is required)\n" |
| " -s show information about superblock\n" |
| " --device=X specify an extra device to be used together\n" |
| " --nid=# show the target inode info of nid #\n" |
| " --path=X show the target inode info of path X\n" |
| " --help display this help and exit.\n", |
| stderr); |
| } |
| |
| static void erofsdump_print_version(void) |
| { |
| fprintf(stderr, "dump.erofs %s\n", cfg.c_version); |
| } |
| |
| static int erofsdump_parse_options_cfg(int argc, char **argv) |
| { |
| int opt, err; |
| |
| while ((opt = getopt_long(argc, argv, "SVes", |
| long_options, NULL)) != -1) { |
| switch (opt) { |
| case 'e': |
| dumpcfg.show_extent = true; |
| ++dumpcfg.totalshow; |
| break; |
| case 's': |
| dumpcfg.show_superblock = true; |
| ++dumpcfg.totalshow; |
| break; |
| case 'S': |
| dumpcfg.show_statistics = true; |
| ++dumpcfg.totalshow; |
| break; |
| case 'V': |
| erofsdump_print_version(); |
| exit(0); |
| case 2: |
| dumpcfg.show_inode = true; |
| dumpcfg.nid = (erofs_nid_t)atoll(optarg); |
| ++dumpcfg.totalshow; |
| break; |
| case 1: |
| usage(); |
| exit(0); |
| case 3: |
| err = blob_open_ro(optarg); |
| if (err) |
| return err; |
| ++sbi.extra_devices; |
| break; |
| case 4: |
| dumpcfg.inode_path = optarg; |
| dumpcfg.show_inode = true; |
| ++dumpcfg.totalshow; |
| break; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| if (optind >= argc) { |
| erofs_err("missing argument: IMAGE"); |
| return -EINVAL; |
| } |
| |
| cfg.c_img_path = strdup(argv[optind++]); |
| if (!cfg.c_img_path) |
| return -ENOMEM; |
| |
| if (optind < argc) { |
| erofs_err("unexpected argument: %s\n", argv[optind]); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int erofs_get_occupied_size(struct erofs_inode *inode, |
| erofs_off_t *size) |
| { |
| *size = 0; |
| switch (inode->datalayout) { |
| case EROFS_INODE_FLAT_INLINE: |
| case EROFS_INODE_FLAT_PLAIN: |
| case EROFS_INODE_CHUNK_BASED: |
| stats.uncompressed_files++; |
| *size = inode->i_size; |
| break; |
| case EROFS_INODE_FLAT_COMPRESSION_LEGACY: |
| case EROFS_INODE_FLAT_COMPRESSION: |
| stats.compressed_files++; |
| *size = inode->u.i_blocks * EROFS_BLKSIZ; |
| break; |
| default: |
| erofs_err("unknown datalayout"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int erofs_getfile_extension(const char *filename) |
| { |
| char *postfix = strrchr(filename, '.'); |
| int type = 0; |
| |
| if (!postfix) |
| return OTHERFILETYPE - 1; |
| for (type = 0; type < OTHERFILETYPE - 1; ++type) { |
| if (strcmp(postfix, file_types[type]) == 0) |
| break; |
| } |
| return type; |
| } |
| |
| static void update_file_size_statatics(erofs_off_t occupied_size, |
| erofs_off_t original_size) |
| { |
| int occupied_size_mark, original_size_mark; |
| |
| original_size_mark = 0; |
| occupied_size_mark = 0; |
| occupied_size >>= 10; |
| original_size >>= 10; |
| |
| while (occupied_size || original_size) { |
| if (occupied_size) { |
| occupied_size >>= 1; |
| occupied_size_mark++; |
| } |
| if (original_size) { |
| original_size >>= 1; |
| original_size_mark++; |
| } |
| } |
| |
| if (original_size_mark >= FILE_MAX_SIZE_BITS) |
| stats.file_original_size[FILE_MAX_SIZE_BITS]++; |
| else |
| stats.file_original_size[original_size_mark]++; |
| |
| if (occupied_size_mark >= FILE_MAX_SIZE_BITS) |
| stats.file_comp_size[FILE_MAX_SIZE_BITS]++; |
| else |
| stats.file_comp_size[occupied_size_mark]++; |
| } |
| |
| static inline int erofs_checkdirent(struct erofs_dirent *de, |
| struct erofs_dirent *last_de, |
| u32 maxsize, const char *dname) |
| { |
| int dname_len; |
| unsigned int nameoff = le16_to_cpu(de->nameoff); |
| |
| if (nameoff < sizeof(struct erofs_dirent) || |
| nameoff >= PAGE_SIZE) { |
| erofs_err("invalid de[0].nameoff %u @ nid %llu", |
| nameoff, de->nid | 0ULL); |
| return -EFSCORRUPTED; |
| } |
| |
| dname_len = (de + 1 >= last_de) ? strnlen(dname, maxsize - nameoff) : |
| le16_to_cpu(de[1].nameoff) - nameoff; |
| /* a corrupted entry is found */ |
| if (nameoff + dname_len > maxsize || |
| dname_len > EROFS_NAME_LEN) { |
| erofs_err("bogus dirent @ nid %llu", |
| le64_to_cpu(de->nid) | 0ULL); |
| DBG_BUGON(1); |
| return -EFSCORRUPTED; |
| } |
| if (de->file_type >= EROFS_FT_MAX) { |
| erofs_err("invalid file type %llu", de->nid); |
| return -EFSCORRUPTED; |
| } |
| return dname_len; |
| } |
| |
| static int erofs_read_dirent(struct erofs_dirent *de, |
| erofs_nid_t nid, erofs_nid_t parent_nid, |
| const char *dname) |
| { |
| int err; |
| erofs_off_t occupied_size = 0; |
| struct erofs_inode inode = { .nid = de->nid }; |
| |
| stats.files++; |
| stats.file_category_stat[de->file_type]++; |
| err = erofs_read_inode_from_disk(&inode); |
| if (err) { |
| erofs_err("read file inode from disk failed!"); |
| return err; |
| } |
| |
| err = erofs_get_occupied_size(&inode, &occupied_size); |
| if (err) { |
| erofs_err("get file size failed\n"); |
| return err; |
| } |
| |
| if (de->file_type == EROFS_FT_REG_FILE) { |
| stats.files_total_origin_size += inode.i_size; |
| stats.file_type_stat[erofs_getfile_extension(dname)]++; |
| stats.files_total_size += occupied_size; |
| update_file_size_statatics(occupied_size, inode.i_size); |
| } |
| |
| if ((de->file_type == EROFS_FT_DIR) |
| && de->nid != nid && de->nid != parent_nid) { |
| err = erofs_read_dir(de->nid, nid); |
| if (err) { |
| erofs_err("parse dir nid %llu error occurred\n", |
| de->nid); |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| static int erofs_read_dir(erofs_nid_t nid, erofs_nid_t parent_nid) |
| { |
| int err; |
| erofs_off_t offset; |
| char buf[EROFS_BLKSIZ]; |
| struct erofs_inode vi = { .nid = nid }; |
| |
| err = erofs_read_inode_from_disk(&vi); |
| if (err) |
| return err; |
| |
| offset = 0; |
| while (offset < vi.i_size) { |
| erofs_off_t maxsize = min_t(erofs_off_t, |
| vi.i_size - offset, EROFS_BLKSIZ); |
| struct erofs_dirent *de = (void *)buf; |
| struct erofs_dirent *end; |
| unsigned int nameoff; |
| |
| err = erofs_pread(&vi, buf, maxsize, offset); |
| if (err) |
| return err; |
| |
| nameoff = le16_to_cpu(de->nameoff); |
| end = (void *)buf + nameoff; |
| while (de < end) { |
| const char *dname; |
| int ret; |
| |
| /* skip "." and ".." dentry */ |
| if (de->nid == nid || de->nid == parent_nid) { |
| de++; |
| continue; |
| } |
| |
| dname = (char *)buf + nameoff; |
| ret = erofs_checkdirent(de, end, maxsize, dname); |
| if (ret < 0) |
| return ret; |
| ret = erofs_read_dirent(de, nid, parent_nid, dname); |
| if (ret < 0) |
| return ret; |
| ++de; |
| } |
| offset += maxsize; |
| } |
| return 0; |
| } |
| |
| static int erofs_get_pathname(erofs_nid_t nid, erofs_nid_t parent_nid, |
| erofs_nid_t target, char *path, unsigned int pos) |
| { |
| int err; |
| erofs_off_t offset; |
| char buf[EROFS_BLKSIZ]; |
| struct erofs_inode inode = { .nid = nid }; |
| |
| path[pos++] = '/'; |
| if (target == sbi.root_nid) |
| return 0; |
| |
| err = erofs_read_inode_from_disk(&inode); |
| if (err) { |
| erofs_err("read inode failed @ nid %llu", nid | 0ULL); |
| return err; |
| } |
| |
| offset = 0; |
| while (offset < inode.i_size) { |
| erofs_off_t maxsize = min_t(erofs_off_t, |
| inode.i_size - offset, EROFS_BLKSIZ); |
| struct erofs_dirent *de = (void *)buf; |
| struct erofs_dirent *end; |
| unsigned int nameoff; |
| |
| err = erofs_pread(&inode, buf, maxsize, offset); |
| if (err) |
| return err; |
| |
| nameoff = le16_to_cpu(de->nameoff); |
| end = (void *)buf + nameoff; |
| while (de < end) { |
| const char *dname; |
| int len; |
| |
| nameoff = le16_to_cpu(de->nameoff); |
| dname = (char *)buf + nameoff; |
| len = erofs_checkdirent(de, end, maxsize, dname); |
| if (len < 0) |
| return len; |
| |
| if (de->nid == target) { |
| memcpy(path + pos, dname, len); |
| path[pos + len] = '\0'; |
| return 0; |
| } |
| |
| if (de->file_type == EROFS_FT_DIR && |
| de->nid != parent_nid && |
| de->nid != nid) { |
| memcpy(path + pos, dname, len); |
| err = erofs_get_pathname(de->nid, nid, |
| target, path, pos + len); |
| if (!err) |
| return 0; |
| memset(path + pos, 0, len); |
| } |
| ++de; |
| } |
| offset += maxsize; |
| } |
| return -1; |
| } |
| |
| static int erofsdump_map_blocks(struct erofs_inode *inode, |
| struct erofs_map_blocks *map, int flags) |
| { |
| if (erofs_inode_is_data_compressed(inode->datalayout)) |
| return z_erofs_map_blocks_iter(inode, map, flags); |
| return erofs_map_blocks(inode, map, flags); |
| } |
| |
| static void erofsdump_show_fileinfo(bool show_extent) |
| { |
| const char *ext_fmt[] = { |
| "%4d: %8" PRIu64 "..%8" PRIu64 " | %7" PRIu64 " : %10" PRIu64 "..%10" PRIu64 " | %7" PRIu64 "\n", |
| "%4d: %8" PRIu64 "..%8" PRIu64 " | %7" PRIu64 " : %10" PRIu64 "..%10" PRIu64 " | %7" PRIu64 " # device %u\n" |
| }; |
| int err, i; |
| erofs_off_t size; |
| u16 access_mode; |
| struct erofs_inode inode = { .nid = dumpcfg.nid }; |
| char path[PATH_MAX + 1] = {0}; |
| char access_mode_str[] = "rwxrwxrwx"; |
| char timebuf[128] = {0}; |
| unsigned int extent_count = 0; |
| struct erofs_map_blocks map = { |
| .index = UINT_MAX, |
| .m_la = 0, |
| }; |
| |
| if (dumpcfg.inode_path) { |
| err = erofs_ilookup(dumpcfg.inode_path, &inode); |
| if (err) { |
| erofs_err("read inode failed @ %s", dumpcfg.inode_path); |
| return; |
| } |
| } else { |
| err = erofs_read_inode_from_disk(&inode); |
| if (err) { |
| erofs_err("read inode failed @ nid %llu", |
| inode.nid | 0ULL); |
| return; |
| } |
| } |
| |
| err = erofs_get_occupied_size(&inode, &size); |
| if (err) { |
| erofs_err("get file size failed @ nid %llu", inode.nid | 0ULL); |
| return; |
| } |
| |
| err = erofs_get_pathname(sbi.root_nid, sbi.root_nid, |
| inode.nid, path, 0); |
| if (err < 0) { |
| erofs_err("file path not found @ nid %llu", inode.nid | 0ULL); |
| return; |
| } |
| |
| strftime(timebuf, sizeof(timebuf), |
| "%Y-%m-%d %H:%M:%S", localtime((time_t *)&inode.i_ctime)); |
| access_mode = inode.i_mode & 0777; |
| for (i = 8; i >= 0; i--) |
| if (((access_mode >> i) & 1) == 0) |
| access_mode_str[8 - i] = '-'; |
| fprintf(stdout, "File : %s\n", path); |
| fprintf(stdout, "Size: %" PRIu64" On-disk size: %" PRIu64 " %s\n", |
| inode.i_size, size, |
| file_category_types[erofs_mode_to_ftype(inode.i_mode)]); |
| fprintf(stdout, "NID: %" PRIu64 " ", inode.nid); |
| fprintf(stdout, "Links: %u ", inode.i_nlink); |
| fprintf(stdout, "Layout: %d Compression ratio: %.2f%%\n", |
| inode.datalayout, |
| (double)(100 * size) / (double)(inode.i_size)); |
| fprintf(stdout, "Inode size: %d ", inode.inode_isize); |
| fprintf(stdout, "Extent size: %u ", inode.extent_isize); |
| fprintf(stdout, "Xattr size: %u\n", inode.xattr_isize); |
| fprintf(stdout, "Uid: %u Gid: %u ", inode.i_uid, inode.i_gid); |
| fprintf(stdout, "Access: %04o/%s\n", access_mode, access_mode_str); |
| fprintf(stdout, "Timestamp: %s.%09d\n", timebuf, inode.i_ctime_nsec); |
| |
| if (!dumpcfg.show_extent) |
| return; |
| |
| fprintf(stdout, "\n Ext: logical offset | length : physical offset | length \n"); |
| while (map.m_la < inode.i_size) { |
| struct erofs_map_dev mdev; |
| |
| err = erofsdump_map_blocks(&inode, &map, |
| EROFS_GET_BLOCKS_FIEMAP); |
| if (err) { |
| erofs_err("failed to get file blocks range"); |
| return; |
| } |
| |
| mdev = (struct erofs_map_dev) { |
| .m_deviceid = map.m_deviceid, |
| .m_pa = map.m_pa, |
| }; |
| err = erofs_map_dev(&sbi, &mdev); |
| if (err) { |
| erofs_err("failed to map device"); |
| return; |
| } |
| |
| fprintf(stdout, ext_fmt[!!mdev.m_deviceid], extent_count++, |
| map.m_la, map.m_la + map.m_llen, map.m_llen, |
| mdev.m_pa, mdev.m_pa + map.m_plen, map.m_plen, |
| mdev.m_deviceid); |
| map.m_la += map.m_llen; |
| } |
| fprintf(stdout, "%s: %d extents found\n", path, extent_count); |
| } |
| |
| static void erofsdump_filesize_distribution(const char *title, |
| unsigned int *file_counts, unsigned int len) |
| { |
| char col1[30]; |
| unsigned int col2, i, lowerbound, upperbound; |
| double col3; |
| char col4[400]; |
| |
| lowerbound = 0; |
| upperbound = 1; |
| fprintf(stdout, "\n%s file size distribution:\n", title); |
| fprintf(stdout, header_format, ">=(KB) .. <(KB) ", "count", |
| "ratio", "distribution"); |
| for (i = 0; i < len; i++) { |
| memset(col1, 0, sizeof(col1)); |
| memset(col4, 0, sizeof(col4)); |
| if (i == len - 1) |
| sprintf(col1, "%6d ..", lowerbound); |
| else if (i <= 6) |
| sprintf(col1, "%6d .. %-6d", lowerbound, upperbound); |
| else |
| |
| sprintf(col1, "%6d .. %-6d", lowerbound, upperbound); |
| col2 = file_counts[i]; |
| if (stats.file_category_stat[EROFS_FT_REG_FILE]) |
| col3 = (double)(100 * col2) / |
| stats.file_category_stat[EROFS_FT_REG_FILE]; |
| else |
| col3 = 0.0; |
| memset(col4, '#', col3 / 2); |
| fprintf(stdout, chart_format, col1, col2, col3, col4); |
| lowerbound = upperbound; |
| upperbound <<= 1; |
| } |
| } |
| |
| static void erofsdump_filetype_distribution(char **file_types, unsigned int len) |
| { |
| char col1[30]; |
| unsigned int col2, i; |
| double col3; |
| char col4[401]; |
| |
| fprintf(stdout, "\nFile type distribution:\n"); |
| fprintf(stdout, header_format, "type", "count", "ratio", |
| "distribution"); |
| for (i = 0; i < len; i++) { |
| memset(col1, 0, sizeof(col1)); |
| memset(col4, 0, sizeof(col4)); |
| sprintf(col1, "%-17s", file_types[i]); |
| col2 = stats.file_type_stat[i]; |
| if (stats.file_category_stat[EROFS_FT_REG_FILE]) |
| col3 = (double)(100 * col2) / |
| stats.file_category_stat[EROFS_FT_REG_FILE]; |
| else |
| col3 = 0.0; |
| memset(col4, '#', col3 / 2); |
| fprintf(stdout, chart_format, col1, col2, col3, col4); |
| } |
| } |
| |
| static void erofsdump_file_statistic(void) |
| { |
| unsigned int i; |
| |
| fprintf(stdout, "Filesystem total file count: %lu\n", |
| stats.files); |
| for (i = 0; i < EROFS_FT_MAX; i++) |
| fprintf(stdout, "Filesystem %s count: %lu\n", |
| file_category_types[i], stats.file_category_stat[i]); |
| |
| stats.compress_rate = (double)(100 * stats.files_total_size) / |
| (double)(stats.files_total_origin_size); |
| fprintf(stdout, "Filesystem compressed files: %lu\n", |
| stats.compressed_files); |
| fprintf(stdout, "Filesystem uncompressed files: %lu\n", |
| stats.uncompressed_files); |
| fprintf(stdout, "Filesystem total original file size: %lu Bytes\n", |
| stats.files_total_origin_size); |
| fprintf(stdout, "Filesystem total file size: %lu Bytes\n", |
| stats.files_total_size); |
| fprintf(stdout, "Filesystem compress rate: %.2f%%\n", |
| stats.compress_rate); |
| } |
| |
| static void erofsdump_print_statistic(void) |
| { |
| int err; |
| |
| err = erofs_read_dir(sbi.root_nid, sbi.root_nid); |
| if (err) { |
| erofs_err("read dir failed"); |
| return; |
| } |
| |
| erofsdump_file_statistic(); |
| erofsdump_filesize_distribution("Original", |
| stats.file_original_size, |
| ARRAY_SIZE(stats.file_original_size)); |
| erofsdump_filesize_distribution("On-disk", |
| stats.file_comp_size, |
| ARRAY_SIZE(stats.file_comp_size)); |
| erofsdump_filetype_distribution(file_types, OTHERFILETYPE); |
| } |
| |
| static void erofsdump_show_superblock(void) |
| { |
| time_t time = sbi.build_time; |
| char uuid_str[37] = "not available"; |
| int i = 0; |
| |
| fprintf(stdout, "Filesystem magic number: 0x%04X\n", |
| EROFS_SUPER_MAGIC_V1); |
| fprintf(stdout, "Filesystem blocks: %llu\n", |
| sbi.total_blocks | 0ULL); |
| fprintf(stdout, "Filesystem inode metadata start block: %u\n", |
| sbi.meta_blkaddr); |
| fprintf(stdout, "Filesystem shared xattr metadata start block: %u\n", |
| sbi.xattr_blkaddr); |
| fprintf(stdout, "Filesystem root nid: %llu\n", |
| sbi.root_nid | 0ULL); |
| fprintf(stdout, "Filesystem inode count: %llu\n", |
| sbi.inos | 0ULL); |
| fprintf(stdout, "Filesystem created: %s", |
| ctime(&time)); |
| fprintf(stdout, "Filesystem features: "); |
| for (; i < ARRAY_SIZE(feature_lists); i++) { |
| u32 feat = le32_to_cpu(feature_lists[i].compat ? |
| sbi.feature_compat : |
| sbi.feature_incompat); |
| if (feat & feature_lists[i].flag) |
| fprintf(stdout, "%s ", feature_lists[i].name); |
| } |
| #ifdef HAVE_LIBUUID |
| uuid_unparse_lower(sbi.uuid, uuid_str); |
| #endif |
| fprintf(stdout, "\nFilesystem UUID: %s\n", |
| uuid_str); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int err; |
| |
| erofs_init_configure(); |
| err = erofsdump_parse_options_cfg(argc, argv); |
| if (err) { |
| if (err == -EINVAL) |
| usage(); |
| goto exit; |
| } |
| |
| err = dev_open_ro(cfg.c_img_path); |
| if (err) { |
| erofs_err("failed to open image file"); |
| goto exit; |
| } |
| |
| err = erofs_read_superblock(); |
| if (err) { |
| erofs_err("failed to read superblock"); |
| goto exit_dev_close; |
| } |
| |
| if (!dumpcfg.totalshow) { |
| dumpcfg.show_superblock = true; |
| dumpcfg.totalshow = 1; |
| } |
| if (dumpcfg.show_superblock) |
| erofsdump_show_superblock(); |
| |
| if (dumpcfg.show_statistics) |
| erofsdump_print_statistic(); |
| |
| if (dumpcfg.show_extent && !dumpcfg.show_inode) { |
| usage(); |
| goto exit_dev_close; |
| } |
| |
| if (dumpcfg.show_inode) |
| erofsdump_show_fileinfo(dumpcfg.show_extent); |
| |
| exit_dev_close: |
| dev_close(); |
| exit: |
| blob_closeall(); |
| erofs_exit_configure(); |
| return err; |
| } |