From 4cbbd8c7c2b71822a5d5fd230515d6530d69c698 Mon Sep 17 00:00:00 2001 From: Sven Hoexter Date: Tue, 13 May 2025 14:19:35 +0200 Subject: [PATCH] New upstream version 1.2.9 --- NEWS | 12 + configure | 20 +- dump/dump.c | 770 ++++++++++++++++++++++++++++++++++++++--- include/exfat_dir.h | 1 + include/exfat_ondisk.h | 8 + include/version.h | 2 +- lib/exfat_dir.c | 2 +- manpages/dump.exfat.8 | 25 ++ 8 files changed, 780 insertions(+), 60 deletions(-) diff --git a/NEWS b/NEWS index 9a24b9d..9f6bf87 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,15 @@ +exfatprogs 1.2.9 - released 2025-05-12 +====================================== + +NEW FEATURES : + * dump.exfat: support dumping directory entry sets, + which prints all fields of directory entries and + cluster chains. See a man page. + +CHANGES : + * exfatprogs: update the Github action for build test + with Debain + clang + lld. + exfatprogs 1.2.8 - released 2025-03-04 ====================================== diff --git a/configure b/configure index 0f74903..2d7eeea 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for exfatprogs 1.2.8. +# Generated by GNU Autoconf 2.71 for exfatprogs 1.2.9. # # Report bugs to . # @@ -621,8 +621,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='exfatprogs' PACKAGE_TARNAME='exfatprogs' -PACKAGE_VERSION='1.2.8' -PACKAGE_STRING='exfatprogs 1.2.8' +PACKAGE_VERSION='1.2.9' +PACKAGE_STRING='exfatprogs 1.2.9' PACKAGE_BUGREPORT='linkinjeon@kernel.org' PACKAGE_URL='https://github.com/exfatprogs/exfatprogs' @@ -1348,7 +1348,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures exfatprogs 1.2.8 to adapt to many kinds of systems. +\`configure' configures exfatprogs 1.2.9 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1419,7 +1419,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of exfatprogs 1.2.8:";; + short | recursive ) echo "Configuration of exfatprogs 1.2.9:";; esac cat <<\_ACEOF @@ -1531,7 +1531,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -exfatprogs configure 1.2.8 +exfatprogs configure 1.2.9 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1792,7 +1792,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by exfatprogs $as_me 1.2.8, which was +It was created by exfatprogs $as_me 1.2.9, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3072,7 +3072,7 @@ fi # Define the identity of the package. PACKAGE='exfatprogs' - VERSION='1.2.8' + VERSION='1.2.9' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -13558,7 +13558,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by exfatprogs $as_me 1.2.8, which was +This file was extended by exfatprogs $as_me 1.2.9, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -13627,7 +13627,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -exfatprogs config.status 1.2.8 +exfatprogs config.status 1.2.9 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/dump/dump.c b/dump/dump.c index 6fa1636..4a87480 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -22,6 +22,17 @@ #define BITS_PER_BYTE 8 #define BITS_PER_BYTE_MASK 0x7 +#define DUMP_SCAN_DIR (1 << 0) +#define DUMP_SCAN_DIR_RECURSIVE (1 << 1) +#define DUMP_CLUSTER_CHAIN (1 << 2) + +#define dump_field(name, fmt, ...) \ + exfat_info("%-40s " fmt "\n", name ":", ##__VA_ARGS__) +#define dump_dentry_field(name, fmt, ...) \ + exfat_info(" %-30s " fmt "\n", name ":", ##__VA_ARGS__) +#define dump_dentry_field_wrap(fmt, ...) \ + exfat_info(" %-30s " fmt "\n", "", ##__VA_ARGS__) + static const unsigned char used_bit[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/* 0 ~ 19*/ 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~ 39*/ @@ -41,6 +52,12 @@ static const unsigned char used_bit[] = { static void usage(void) { fprintf(stderr, "Usage: dump.exfat\n"); + fprintf(stderr, "\t-d | --dentry-set=path Show directory entry set\n"); + fprintf(stderr, "\t-c | --cluster-chain Show cluster chain\n"); + fprintf(stderr, + "\t-s | --scan-dir=dir-path Scan and show directory entry sets\n"); + fprintf(stderr, + "\t-r | --recursive Scan and show directory entry sets recursively\n"); fprintf(stderr, "\t-V | --version Show version\n"); fprintf(stderr, "\t-h | --help Show help\n"); @@ -48,6 +65,10 @@ static void usage(void) } static struct option opts[] = { + {"dentry-set", required_argument, NULL, 'd' }, + {"scan-dir", required_argument, NULL, 's' }, + {"recursive", no_argument, NULL, 'r' }, + {"cluster-chain", no_argument, NULL, 'c' }, {"version", no_argument, NULL, 'V' }, {"help", no_argument, NULL, 'h' }, {"?", no_argument, NULL, '?' }, @@ -95,28 +116,23 @@ static int exfat_read_dentry(struct exfat *exfat, struct exfat_inode *inode, return 0; } -static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) +static int exfat_show_fs_info(struct exfat *exfat) { struct pbr *ppbr; struct bsx64 *pbsx; + struct exfat_blk_dev *bd = exfat->blk_dev; struct exfat_dentry ed; unsigned int bitmap_clu; unsigned int total_clus, used_clus, clu_offset, root_clu; unsigned long long bitmap_len; int ret; char *volume_label; - struct exfat *exfat; off_t off; - exfat = exfat_alloc_exfat(bd, NULL, NULL); - if (!exfat) - return -ENOMEM; - ppbr = exfat->bs; if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); - ret = -EINVAL; - goto free_exfat; + return -EINVAL; } pbsx = &ppbr->bsx; @@ -125,15 +141,13 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) pbsx->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) { exfat_err("bogus sector size bits : %u\n", pbsx->sect_size_bits); - ret = -EINVAL; - goto free_exfat; + return -EINVAL; } if (pbsx->sect_per_clus_bits > 25 - pbsx->sect_size_bits) { exfat_err("bogus sectors bits per cluster : %u\n", pbsx->sect_per_clus_bits); - ret = -EINVAL; - goto free_exfat; + return -EINVAL; } bd->sector_size_bits = pbsx->sect_size_bits; @@ -144,74 +158,73 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) root_clu = le32_to_cpu(pbsx->root_cluster); exfat_info("-------------- Dump Boot sector region --------------\n"); - exfat_info("Volume Length(sectors): \t\t%" PRIu64 "\n", + dump_field("Volume Length(sectors)", "%" PRIu64, le64_to_cpu(pbsx->vol_length)); - exfat_info("FAT Offset(sector offset): \t\t%u\n", + dump_field("FAT Offset(sector offset)", "%u", le32_to_cpu(pbsx->fat_offset)); - exfat_info("FAT Length(sectors): \t\t\t%u\n", + dump_field("FAT Length(sectors)", "%u", le32_to_cpu(pbsx->fat_length)); - exfat_info("Cluster Heap Offset (sector offset): \t%u\n", clu_offset); - exfat_info("Cluster Count: \t\t\t\t%u\n", total_clus); - exfat_info("Root Cluster (cluster offset): \t\t%u\n", root_clu); - exfat_info("Volume Serial: \t\t\t\t0x%x\n", le32_to_cpu(pbsx->vol_serial)); - exfat_info("Bytes per Sector: \t\t\t%u\n", 1 << pbsx->sect_size_bits); - exfat_info("Sectors per Cluster: \t\t\t%u\n\n", 1 << pbsx->sect_per_clus_bits); + dump_field("Cluster Heap Offset (sector offset)", "%u", clu_offset); + dump_field("Cluster Count", "%u", total_clus); + dump_field("Root Cluster (cluster offset)", "%u", root_clu); + dump_field("Volume Serial", "0x%x", le32_to_cpu(pbsx->vol_serial)); + dump_field("Bytes per Sector", "%u", 1 << pbsx->sect_size_bits); + dump_field("Sectors per Cluster", "%u", 1 << pbsx->sect_per_clus_bits); bd->cluster_size = 1 << (pbsx->sect_per_clus_bits + pbsx->sect_size_bits); - exfat_info("----------------- Dump Root entries -----------------\n"); + exfat_info("\n----------------- Dump Root entries -----------------\n"); ret = exfat_read_dentry(exfat, exfat->root, EXFAT_VOLUME, &ed, &off); if (ret) - goto free_exfat; + return ret; if (ed.type == EXFAT_VOLUME) { - exfat_info("Volume label entry position: \t\t0x%llx\n", (unsigned long long)off); - exfat_info("Volume label character count: \t\t%u\n", ed.vol_char_cnt); + dump_field("Volume label entry position", "0x%llx", (unsigned long long)off); + dump_field("Volume label character count", "%u", ed.vol_char_cnt); volume_label = exfat_conv_volume_label(&ed); if (!volume_label) - exfat_info("Volume label: \t\t\t\t\n"); + dump_field("Volume label", "%s", ""); else - exfat_info("Volume label: \t\t\t\t%s\n", volume_label); + dump_field("Volume label", "%s", volume_label); free(volume_label); } ret = exfat_read_dentry(exfat, exfat->root, EXFAT_UPCASE, &ed, &off); if (ret) - goto free_exfat; + return ret; if (ed.type == EXFAT_UPCASE) { - exfat_info("Upcase table entry position: \t\t0x%llx\n", (unsigned long long)off); - exfat_info("Upcase table start cluster: \t\t%x\n", + dump_field("Upcase table entry position", "0x%llx", (unsigned long long)off); + dump_field("Upcase table start cluster", "%x", le32_to_cpu(ed.upcase_start_clu)); - exfat_info("Upcase table size: \t\t\t%" PRIu64 "\n", + dump_field("Upcase table size", "%" PRIu64, le64_to_cpu(ed.upcase_size)); } ret = exfat_read_dentry(exfat, exfat->root, EXFAT_BITMAP, &ed, &off); if (ret) - goto free_exfat; + return ret; if (ed.type == EXFAT_BITMAP) { bitmap_len = le64_to_cpu(ed.bitmap_size); bitmap_clu = le32_to_cpu(ed.bitmap_start_clu); - exfat_info("Bitmap entry position: \t\t\t0x%llx\n", (unsigned long long)off); - exfat_info("Bitmap start cluster: \t\t\t%x\n", bitmap_clu); - exfat_info("Bitmap size: \t\t\t\t%llu\n", bitmap_len); + dump_field("Bitmap entry position", "0x%llx", (unsigned long long)off); + dump_field("Bitmap start cluster", "%x", bitmap_clu); + dump_field("Bitmap size", "%llu", bitmap_len); if (bitmap_len > EXFAT_BITMAP_SIZE(exfat->clus_count)) { exfat_err("Invalid bitmap size\n"); - ret = -EINVAL; - goto free_exfat; + return -EINVAL; } ret = exfat_read(bd->dev_fd, exfat->disk_bitmap, bitmap_len, exfat_c2o(exfat, bitmap_clu)); if (ret < 0) { exfat_err("bitmap read failed: %d\n", errno); - ret = -EIO; + return -EIO; } used_clus = exfat_count_used_clusters( @@ -219,17 +232,649 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) bitmap_len); exfat_info("\n---------------- Show the statistics ----------------\n"); - exfat_info("Cluster size: \t\t\t\t%u\n", bd->cluster_size); - exfat_info("Total Clusters: \t\t\t%u\n", exfat->clus_count); - exfat_info("Free Clusters: \t\t\t\t%u\n", - exfat->clus_count - used_clus); + dump_field("Cluster size", "%u", bd->cluster_size); + dump_field("Total Clusters", "%u", exfat->clus_count); + dump_field("Free Clusters", "%u", exfat->clus_count - used_clus); } - ret = 0; + return 0; +} -free_exfat: - exfat_free_exfat(exfat); +/* + * Get the first level file name from a given path + * + * Input + * path: The path of the file/directory. + * name_size: the size of 'name'. + * Output + * name: the file name in the first level of the path. + * Return + * The length of the path to jump to the next level. + */ +static int get_name_from_path(const char *path, char *name, size_t name_size) +{ + int i; + int name_len = 0; + int path_len = strlen(path); + + if (path_len == 0) + return 0; + + for (i = 0; i <= path_len && name_len + 1 < name_size; i++, path++) { + if (*path == '/' || *path == '\0') { + if (name_len == 0) + continue; + + name[name_len] = 0; + return i; + } + + name[name_len] = *path; + name_len++; + } + + name[0] = 0; + return 0; +} + +/* + * Create a inode for a given path + * + * Input + * path: the path of the file/directory. + * Output + * new: the new inode is created for the file/directory. + * If path is '/', it is a copy of exfat->root. + * dir_is_contiguous: Whether the clusters of the parent directory are + * contiguous. + * Return + * 0 on success + * -error code on failure + */ +static int exfat_create_inode_by_path(struct exfat *exfat, const char *path, + struct exfat_inode **new, bool *dir_is_contiguous) +{ + int len, ret; + char name[PATH_MAX + 1]; + struct exfat_inode *inode; + struct exfat_dentry *dentry_set; + struct exfat_lookup_filter filter; + const char *p_path = path; + + inode = exfat_alloc_inode(ATTR_SUBDIR); + if (!inode) + return -ENOMEM; + + *inode = *exfat->root; + *dir_is_contiguous = inode->is_contiguous; + + do { + if ((inode->attr & ATTR_SUBDIR) == 0 && *p_path != '\0') { + ret = -ENOENT; + goto free_inode; + } + + len = get_name_from_path(p_path, name, sizeof(name)); + p_path += len; + if (name[0] == '\0' || len == 0) { + *new = inode; + return 0; + } + + ret = exfat_utf16_enc(name, inode->name, NAME_BUFFER_SIZE); + if (ret < 0) + goto free_inode; + + ret = exfat_lookup_file_by_utf16name(exfat, inode, inode->name, + &filter); + if (ret) { + if (ret == EOF) + ret = -ENOENT; + goto free_inode; + } + + dentry_set = filter.out.dentry_set; + if (inode->dentry_set) + free(inode->dentry_set); + inode->dentry_set = dentry_set; + inode->dev_offset = filter.out.dev_offset; + inode->dentry_count = filter.out.dentry_count; + inode->attr = dentry_set[0].file_attr; + inode->first_clus = le32_to_cpu(dentry_set[1].stream_start_clu); + *dir_is_contiguous = inode->is_contiguous; + inode->is_contiguous = + (dentry_set[1].stream_flags & EXFAT_SF_CONTIGUOUS); + inode->size = le64_to_cpu(dentry_set[1].stream_size); + } while (1); + +free_inode: + exfat_free_inode(inode); + + return ret; +} + +/* + * Get the position of the next directory entry + * + * Input + * is_contiguous: Whether the cluster chain where the directory entries are + * located is continuous. + * dentry_off: The position of the current directory entry. + * Output + * dentry_off: The position of the next directory entry. + * Return + * 0 on success + * -error code on failure + */ +static int exfat_get_next_dentry_offset(struct exfat *exfat, bool is_contiguous, + off_t *dentry_off) +{ + int ret; + clus_t clu; + unsigned int offset; + + if (is_contiguous) { + *dentry_off += DENTRY_SIZE; + return 0; + } + + ret = exfat_o2c(exfat, *dentry_off, &clu, &offset); + if (ret) + return ret; + + if (offset + DENTRY_SIZE == exfat->clus_size) { + ret = exfat_get_next_clus(exfat, clu, &clu); + if (ret) { + exfat_err("failed to get next dentry offset 0x%lx\n", + *dentry_off); + return ret; + } + + if (!exfat_heap_clus(exfat, clu)) { + exfat_err("cluster %u is not in cluster heap\n", clu); + return -ERANGE; + } + + *dentry_off = exfat_c2o(exfat, clu); + } else + *dentry_off += DENTRY_SIZE; + + return 0; +} + +/* + * Print the cluster chain in the format. + * + * Cluster Chain: clu_1:nr_clu_1 + * clu_2:nr_clu_2 + * ... ... + * clu_n:nr_clu_n + * + */ +static void exfat_show_cluster_chain(struct exfat *exfat, + struct exfat_dentry *ed) +{ + int ret = 0; + clus_t clu, next_clu; + clus_t count = 0, num_clus; + bool first = true; + + clu = le32_to_cpu(ed->stream_start_clu); + num_clus = DIV_ROUND_UP(le64_to_cpu(ed->stream_size), exfat->clus_size); + if (clu == 0) + return; + + while (clu != EXFAT_EOF_CLUSTER) { + if (!exfat_heap_clus(exfat, clu)) { + if (first) + dump_dentry_field("Cluster Chain", + "%u(invalid)", clu); + else + dump_dentry_field_wrap("%u(invalid)", clu); + + return; + } + + if (exfat_bitmap_get(exfat->alloc_bitmap, clu)) { + dump_dentry_field_wrap("%u(double-link)", clu); + return; + } + + exfat_bitmap_set(exfat->alloc_bitmap, clu); + + count++; + if (ed->stream_flags & EXFAT_SF_CONTIGUOUS) { + if (count == num_clus) + next_clu = EXFAT_EOF_CLUSTER; + else + next_clu = clu + 1; + } else + ret = exfat_get_next_clus(exfat, clu, &next_clu); + + if (clu + 1 != next_clu || ret) { + if (first) + dump_dentry_field("Cluster chain", "%u:%u", + clu - count + 1, count); + else + dump_dentry_field_wrap("%u:%u", clu - count + 1, count); + first = false; + count = 0; + } + + if (ret) + return; + + clu = next_clu; + } +} + +struct show_dentry { + __u8 type; + const char *type_name; + void (*show)(struct exfat_dentry *ed, struct exfat *exfat, + uint32_t flags); +}; + +static void exfat_show_file_dentry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + uint16_t checksum = calc_dentry_set_checksum(ed, ed->file_num_ext + 1); + + dump_dentry_field("SecondaryCount", "%u", ed->file_num_ext); + if (checksum == le16_to_cpu(ed->file_checksum)) + dump_dentry_field("SetChecksum", "0x%04X", checksum); + else + dump_dentry_field("SetChecksum", "0x%04X(expected: 0x%04X)", + le16_to_cpu(ed->file_checksum), checksum); + dump_dentry_field("FileAttributes", "0x%04X", le16_to_cpu(ed->file_attr)); + dump_dentry_field("CreateTimestamp", "0x%08X", le32_to_cpu(ed->file_create_time)); + dump_dentry_field("LastModifiedTimestamp", "0x%08X", le32_to_cpu(ed->file_modify_time)); + dump_dentry_field("LastAccessedTimestamp", "0x%08X", le32_to_cpu(ed->file_access_time)); + dump_dentry_field("Create10msIncrement", "%u", ed->file_create_time_ms); + dump_dentry_field("LastModified10msIncrement", "%u", ed->file_modify_time_ms); + dump_dentry_field("CreateUtcOffset", "%u", ed->file_create_tz); + dump_dentry_field("LastModifiedUtcOffset", "%u", ed->file_modify_tz); + dump_dentry_field("LastAccessedUtcOffset", "%u", ed->file_access_tz); +} + +static void exfat_show_stream_dentry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_dentry_field("NameLength", "%u", ed->stream_name_len); + dump_dentry_field("NameHash", "0x%04X", le16_to_cpu(ed->stream_name_hash)); + dump_dentry_field("ValidDataLength", "%" PRIu64, le64_to_cpu(ed->stream_valid_size)); + dump_dentry_field("FirstCluster", "%u", le32_to_cpu(ed->stream_start_clu)); + dump_dentry_field("DataLength", "%" PRIu64, le64_to_cpu(ed->stream_size)); + if (flags & DUMP_CLUSTER_CHAIN) + exfat_show_cluster_chain(exfat, ed); +} + +static void exfat_show_bytes(const char *name, unsigned char *bytes, int n) +{ + char buf[64]; + int i, len = 0; + + for (i = 0; i < n && len < sizeof(buf); i++) + len += snprintf(buf + len, sizeof(buf) - len, "%02X", bytes[i]); + + exfat_info("%-33s %s\n", name, buf); +} + +#define dump_bytes_field(name, feild) \ + exfat_show_bytes(" " name ":", (unsigned char *)feild, sizeof(feild)) + +static void exfat_show_name_dentry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_bytes_field("FileName", ed->name_unicode); +} + +static void exfat_show_bitmap_entry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_dentry_field("BitmapFlags", "0x%02X", ed->dentry.bitmap.flags); + dump_dentry_field("FirstCluster", "%u", le32_to_cpu(ed->bitmap_start_clu)); + dump_dentry_field("DataLength", "%" PRIu64, le64_to_cpu(ed->bitmap_size)); + if (flags & DUMP_CLUSTER_CHAIN) + exfat_show_cluster_chain(exfat, ed); +} + +static void exfat_show_upcase_entry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_dentry_field("TableChecksum", "0x%08X", le32_to_cpu(ed->upcase_checksum)); + dump_dentry_field("FirstCluster", "%u", le32_to_cpu(ed->upcase_start_clu)); + dump_dentry_field("DataLength", "%" PRIu64, le64_to_cpu(ed->upcase_size)); + if (flags & DUMP_CLUSTER_CHAIN) + exfat_show_cluster_chain(exfat, ed); +} + +static void exfat_show_volume_entry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_dentry_field("CharacterCount", "%u", ed->vol_char_cnt); + dump_bytes_field("VolumeLabel", ed->vol_label); +} + +static void exfat_show_guid_entry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_dentry_field("SecondaryCount", "%u", ed->dentry.guid.num_ext); + dump_dentry_field("SetChecksum", "0x%04X", le16_to_cpu(ed->dentry.guid.checksum)); + dump_dentry_field("GeneralPrimaryFlags", "0x%04X", le16_to_cpu(ed->dentry.guid.flags)); + dump_bytes_field("VolumeGuid", ed->dentry.guid.guid); +} + +static void exfat_show_vendor_ext_dentry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_bytes_field("VendorGuid", ed->dentry.vendor_ext.guid); + dump_bytes_field("VendorDefined", ed->dentry.vendor_ext.vendor_defined); +} + +static void exfat_show_vendor_alloc_dentry(struct exfat_dentry *ed, + struct exfat *exfat, uint32_t flags) +{ + dump_bytes_field("VendorGuid", ed->dentry.vendor_alloc.guid); + dump_bytes_field("VendorDefined", ed->dentry.vendor_alloc.vendor_defined); + dump_dentry_field("FirstCluster", "%u", le32_to_cpu(ed->vendor_alloc_start_clu)); + dump_dentry_field("DataLength", "%" PRIu64, le64_to_cpu(ed->vendor_alloc_size)); + if (flags & DUMP_CLUSTER_CHAIN) + exfat_show_cluster_chain(exfat, ed); +} + +static struct show_dentry show_dentry_array[] = { + {EXFAT_FILE, "File", exfat_show_file_dentry}, + {EXFAT_STREAM, "Stream Extension", exfat_show_stream_dentry}, + {EXFAT_NAME, "File Name", exfat_show_name_dentry}, + {EXFAT_VENDOR_EXT, "Vendor Extension", exfat_show_vendor_ext_dentry}, + {EXFAT_VENDOR_ALLOC, "Vendor Allocation", exfat_show_vendor_alloc_dentry}, + {EXFAT_BITMAP, "Allocation Bitmap", exfat_show_bitmap_entry}, + {EXFAT_UPCASE, "Up-case Table", exfat_show_upcase_entry}, + {EXFAT_VOLUME, "Volume Label", exfat_show_volume_entry}, + {EXFAT_GUID, "Volume GUID", exfat_show_guid_entry}, + {EXFAT_PADDING, "TexFAT Padding", NULL}, + {EXFAT_LAST, "Unused", NULL} +}; + +/* + * Print a directory entry + * + * Input + * ed: The directory entry will be printed. + * index: The entry index in directory entry set. + * dentry_off: The position of the directory entry. + * flags: If DUMP_CLUSTER_CHAIN is set, the cluster chain will be printed. + */ +static void exfat_show_dentry(struct exfat *exfat, struct exfat_dentry *ed, + unsigned int index, off_t dentry_off, uint32_t flags) +{ + int i; + struct show_dentry *sd = NULL; + + for (i = 0; i < sizeof(show_dentry_array) / sizeof(*sd); i++) { + if (show_dentry_array[i].type == ed->type) { + sd = show_dentry_array + i; + break; + } + } + + if (sd) + exfat_info("%d. %s Directory Entry\n", index, + sd->type_name); + else if (IS_EXFAT_DELETED(ed->type)) + exfat_info("%d. Deleted Directory Entry\n", index); + else + exfat_info("%d. Unknown Directory Entry\n", index); + + dump_dentry_field("Position", "0x%llX", (unsigned long long)dentry_off); + dump_dentry_field("Entrytype", "0x%02X", ed->type); + + if (IS_EXFAT_SEC(ed->type)) + dump_dentry_field("GeneralSecondaryFlags", "0x%02X", ed->stream_flags); + + if (sd && sd->show) + sd->show(ed, exfat, flags); +} + +/* + * Print the directory entry set of the file/directory + * + * Input: + * inode: it contains a copy of the directory entry set that will be printed. + * dir_is_contiguous: Whether the clusters of the parent directory are + * contiguous. + * flags: If DUMP_CLUSTER_CHAIN is set, the cluster chain will be printed. + */ +static void exfat_show_dentry_set(struct exfat *exfat, + struct exfat_inode *inode, bool dir_is_contiguous, + uint32_t flags) +{ + int i; + struct exfat_dentry *ed; + off_t dentry_off; + + ed = inode->dentry_set; + dentry_off = inode->dev_offset; + + for (i = 0; i < inode->dentry_count; i++, ed++) { + exfat_show_dentry(exfat, ed, i, dentry_off, flags); + + if (i < inode->dentry_count - 1) + exfat_get_next_dentry_offset(exfat, + dir_is_contiguous, &dentry_off); + } +} + +/* + * Create an inode for a directory entry set + * + * Input: + * de_iter: the directory entry set in the scan buffer + * Output: + * new: the newly created inode. + * Return: + * 0 on success + * -error code on failure + */ +static int exfat_create_inode(struct exfat *exfat, + struct exfat_de_iter *de_iter, struct exfat_inode **new) +{ + int ret, i; + struct exfat_inode *inode; + struct exfat_dentry *dentry, *de_stream; + + exfat_de_iter_get(de_iter, 0, &dentry); + ret = exfat_de_iter_get(de_iter, 1, &de_stream); + if (ret) + return ret; + + inode = exfat_alloc_inode(le16_to_cpu(dentry->file_attr)); + if (!inode) + return -ENOMEM; + + inode->dev_offset = exfat_de_iter_device_offset(de_iter); + inode->dentry_count = DIV_ROUND_UP(de_stream->stream_name_len, + ENTRY_NAME_MAX) + 2; + inode->dentry_count = MAX(dentry->file_num_ext + 1, + inode->dentry_count); + inode->dentry_count = MAX(inode->dentry_count, 3); + inode->attr = le16_to_cpu(dentry->file_attr); + + inode->dentry_set = calloc(inode->dentry_count, sizeof(*dentry)); + if (!inode->dentry_set) { + ret = -ENOMEM; + goto free_inode; + } + + inode->dentry_set[0] = *dentry; + inode->dentry_set[1] = *de_stream; + + for (i = 2; i < inode->dentry_count; i++) { + ret = exfat_de_iter_get(de_iter, i, &dentry); + if (ret) + goto free_inode; + + if (!IS_EXFAT_SEC(dentry->type)) { + if (i == 2) { + ret = -EINVAL; + goto free_inode; + } + + inode->dentry_count = i; + break; + } + + inode->dentry_set[i] = *dentry; + if (dentry->type == EXFAT_NAME) + memcpy(inode->name + (i - 2) * ENTRY_NAME_MAX, + dentry->name_unicode, + sizeof(dentry->name_unicode)); + } + + inode->first_clus = le32_to_cpu(de_stream->stream_start_clu); + inode->is_contiguous = de_stream->stream_flags & EXFAT_SF_CONTIGUOUS; + inode->size = le64_to_cpu(de_stream->stream_size); + + *new = inode; + + return 0; + +free_inode: + exfat_free_inode(inode); + + return ret; +} + +/* + * Scan and print the directory sets in a directory + * + * Input: + * dir: the inode of the directory. + * path: the path of the directory. + * flags: If DUMP_SCAN_DIR_RECURSIVE is set, scan and print directory entry + * sets recursively. + */ +static int exfat_scan_dentry_set(struct exfat *exfat, struct exfat_inode *dir, + const char *path, uint32_t flags) +{ + int ret, dentry_count, len; + struct buffer_desc *scan_bdesc; + struct exfat_de_iter de_iter; + struct exfat_dentry *dentry; + struct exfat_inode *inode; + char new_path[PATH_MAX]; + + scan_bdesc = exfat_alloc_buffer(exfat); + if (!scan_bdesc) + return -ENOMEM; + + ret = exfat_de_iter_init(&de_iter, exfat, dir, scan_bdesc); + if (ret == EOF) { + ret = 0; + goto free_buffer; + } else if (ret) + goto free_buffer; + + while (1) { + ret = exfat_de_iter_get(&de_iter, 0, &dentry); + if (ret == EOF) { + ret = 0; + goto free_buffer; + } else if (ret) + goto free_buffer; + + dentry_count = 1; + if (dentry->type == EXFAT_FILE) { + ret = exfat_create_inode(exfat, &de_iter, &inode); + if (ret == EOF) { + ret = 0; + goto free_buffer; + } else if (ret) + goto free_buffer; + + exfat_info("-----------------------------------------------------\n"); + + len = snprintf(new_path, sizeof(new_path), "%s/", path); + if (dir->first_clus == exfat->root->first_clus) + len = 1; + + ret = exfat_utf16_dec(inode->name, NAME_BUFFER_SIZE, + new_path + len, sizeof(new_path) - len); + if (ret < 0) + snprintf(new_path + len, sizeof(new_path) - len, + ""); + + exfat_info("Path: %s\n", new_path); + exfat_show_dentry_set(exfat, inode, dir->is_contiguous, flags); + if ((inode->attr & ATTR_SUBDIR) && + (flags & DUMP_SCAN_DIR_RECURSIVE)) { + ret = exfat_scan_dentry_set(exfat, inode, + new_path, flags); + if (ret) { + exfat_free_inode(inode); + goto free_buffer; + } + } + dentry_count = inode->dentry_count; + exfat_free_inode(inode); + } else if (dentry->type & EXFAT_INVAL) { + exfat_info("-----------------------------------------------------\n"); + exfat_info("Directory: %s\n", path); + exfat_show_dentry(exfat, dentry, 0, + exfat_de_iter_device_offset(&de_iter), flags); + } + + ret = exfat_de_iter_advance(&de_iter, dentry_count); + if (ret) + goto free_buffer; + } + +free_buffer: + exfat_free_buffer(exfat, scan_bdesc); + + return ret; +} + +static int exfat_show_dentry_set_by_path(struct exfat *exfat, const char *path, + uint32_t flags) +{ + int ret; + bool dir_is_contiguous; + struct exfat_inode *inode; + + ret = exfat_create_inode_by_path(exfat, path, &inode, + &dir_is_contiguous); + if (ret) + return ret; + + if (flags & DUMP_SCAN_DIR) { + if ((inode->attr & ATTR_SUBDIR) == 0) { + ret = -EINVAL; + goto out; + } + + ret = exfat_scan_dentry_set(exfat, inode, path, flags); + goto out; + } + + exfat_info("\n"); + + /* The root has no directory entry, only show the cluster chain */ + if (exfat->root->first_clus == inode->first_clus && + (flags & DUMP_CLUSTER_CHAIN)) { + struct exfat_dentry ed; + + memset(&ed, 0, sizeof(ed)); + ed.stream_start_clu = cpu_to_le32(inode->first_clus); + memset(exfat->alloc_bitmap, 0, EXFAT_BITMAP_SIZE(exfat->clus_count)); + exfat_show_cluster_chain(exfat, &ed); + } else + exfat_show_dentry_set(exfat, inode, dir_is_contiguous, flags); + +out: + exfat_free_inode(inode); return ret; } @@ -240,6 +885,9 @@ int main(int argc, char *argv[]) struct exfat_blk_dev bd; struct exfat_user_input ui; bool version_only = false; + struct exfat *exfat; + const char *path = NULL; + uint32_t flags = 0; init_user_input(&ui); ui.writeable = false; @@ -248,11 +896,24 @@ int main(int argc, char *argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "iVh", opts, NULL)) != EOF) + while ((c = getopt_long(argc, argv, "iVhd:s:rc", opts, NULL)) != EOF) switch (c) { case 'V': version_only = true; break; + case 'd': + path = optarg; + break; + case 's': + path = optarg; + flags |= DUMP_SCAN_DIR; + break; + case 'r': + flags |= DUMP_SCAN_DIR_RECURSIVE; + break; + case 'c': + flags |= DUMP_CLUSTER_CHAIN; + break; case '?': case 'h': default: @@ -266,13 +927,26 @@ int main(int argc, char *argv[]) if (argc - optind != 1) usage(); - ui.dev_name = argv[1]; + ui.dev_name = argv[argc - 1]; ret = exfat_get_blk_dev_info(&ui, &bd); if (ret < 0) goto out; - ret = exfat_show_ondisk_all_info(&bd); + exfat = exfat_alloc_exfat(&bd, NULL, NULL); + if (!exfat) { + ret = -ENOMEM; + goto close_dev_fd; + } + + if (path) + ret = exfat_show_dentry_set_by_path(exfat, path, flags); + else + ret = exfat_show_fs_info(exfat); + + exfat_free_exfat(exfat); + +close_dev_fd: close(bd.dev_fd); out: diff --git a/include/exfat_dir.h b/include/exfat_dir.h index 7a73488..5b6d4c5 100644 --- a/include/exfat_dir.h +++ b/include/exfat_dir.h @@ -94,6 +94,7 @@ int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc, bool need_next_loc); void exfat_calc_dentry_checksum(struct exfat_dentry *dentry, uint16_t *checksum, bool primary); +uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount); uint16_t exfat_calc_name_hash(struct exfat *exfat, __le16 *name, int len); diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h index 636f5ea..6ac92c7 100644 --- a/include/exfat_ondisk.h +++ b/include/exfat_ondisk.h @@ -57,6 +57,7 @@ #define EXFAT_DELETE ~(0x80) #define IS_EXFAT_DELETED(x) ((x) < 0x80) /* deleted file (0x01~0x7F) */ #define EXFAT_INVAL 0x80 /* invalid value */ +#define EXFAT_SEC (EXFAT_INVAL | (1 << 6)) #define EXFAT_BITMAP 0x81 /* allocation bitmap */ #define EXFAT_UPCASE 0x82 /* upcase table */ #define EXFAT_VOLUME 0x83 /* volume label */ @@ -69,6 +70,7 @@ #define EXFAT_ACL 0xC2 /* stream entry */ #define EXFAT_VENDOR_EXT 0xE0 #define EXFAT_VENDOR_ALLOC 0xE1 +#define IS_EXFAT_SEC(x) ((x) >= EXFAT_SEC) /* checksum types */ #define CS_DIR_ENTRY 0 @@ -238,6 +240,9 @@ struct exfat_dentry { #define file_create_time_ms dentry.file.create_time_ms #define file_modify_time_ms dentry.file.modify_time_ms #define file_access_time_ms dentry.file.access_time_ms +#define file_create_tz dentry.file.create_tz +#define file_modify_tz dentry.file.modify_tz +#define file_access_tz dentry.file.access_tz #define stream_flags dentry.stream.flags #define stream_name_len dentry.stream.name_len #define stream_name_hash dentry.stream.name_hash @@ -252,5 +257,8 @@ struct exfat_dentry { #define upcase_start_clu dentry.upcase.start_clu #define upcase_size dentry.upcase.size #define upcase_checksum dentry.upcase.checksum +#define vendor_alloc_flags dentry.vendor_alloc.flags +#define vendor_alloc_start_clu dentry.vendor_alloc.start_clu +#define vendor_alloc_size dentry.vendor_alloc.size #endif /* !_EXFAT_H */ diff --git a/include/version.h b/include/version.h index 846ae59..dc02416 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.2.8" +#define EXFAT_PROGS_VERSION "1.2.9" #endif /* !_VERSION_H */ diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c index 3189f71..44d27f5 100644 --- a/lib/exfat_dir.c +++ b/lib/exfat_dir.c @@ -553,7 +553,7 @@ void exfat_calc_dentry_checksum(struct exfat_dentry *dentry, } } -static uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount) +uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount) { uint16_t checksum; int i; diff --git a/manpages/dump.exfat.8 b/manpages/dump.exfat.8 index 4c6f589..ab260ee 100644 --- a/manpages/dump.exfat.8 +++ b/manpages/dump.exfat.8 @@ -3,6 +3,17 @@ dump.exfat \- Show on-disk information of exfat filesystem .SH SYNOPSIS .B dump.exfat +[ +.B \-d +.I file-path +] +[ +.B \-s +.I dir-path +] +[ +.B \-r +] .I device .br .B dump.exfat \-V @@ -10,8 +21,22 @@ dump.exfat \- Show on-disk information of exfat filesystem .B dump.exfat Print on-disk information from given device that formatted by exFAT filesystem. +Note: For printing information of directory entries, the absence of "double-link" does not indicate the cluster is not doubly allocated if the scan is not started from the root directory with the recursive option. + .PP .SH OPTIONS .TP .B \-V Prints the version number and exits. +.TP +.BR \-d ", " \-\-dentry-set=\fIpath\fR +Print information of directory entries from a given path that exits in the given device. +.TP +.BR \-s ", " \-\-scan-dir=\fIdir-path\fR +Scan and print information of directory entries from a given path that exits in the given device. +.TP +.BR \-r ", " \-\-recursive +Scan and print information of directory entries from a given path recursively that exits in the given device. Only works with -s option. +.TP +.BR \-c ", " \-\-cluster-chain +Print the cluster chain while printing the directory entry information. Only works with -d/-s option. -- 2.47.3