]> git.sven.stormbind.net Git - sven/exfatprogs.git/commitdiff
New upstream version 1.2.9 upstream/1.2.9
authorSven Hoexter <sven@stormbind.net>
Tue, 13 May 2025 12:19:35 +0000 (14:19 +0200)
committerSven Hoexter <sven@stormbind.net>
Tue, 13 May 2025 12:19:35 +0000 (14:19 +0200)
NEWS
configure
dump/dump.c
include/exfat_dir.h
include/exfat_ondisk.h
include/version.h
lib/exfat_dir.c
manpages/dump.exfat.8

diff --git a/NEWS b/NEWS
index 9a24b9dca1efe0a252de964badd6172fe50372e8..9f6bf879b8bc7a68809ed72cc9d602c872a6fcac 100644 (file)
--- 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
 ======================================
 
index 0f74903b6a9c5a09647f39920f7221b5213b62f9..2d7eeea286d4eae82f8ac76eff6d2c09e5b1bb0b 100755 (executable)
--- 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 <linkinjeon@kernel.org>.
 #
@@ -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\\"
 
index 6fa1636da2c2d0ffef6aadfae49b68ecf82d517d..4a8748091983c991935f7cf8feecf4dfd59cdf47 100644 (file)
 #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<invalid>\n");
+                       dump_field("Volume label", "%s", "<invalid>");
                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,
+                                               "<invalid>");
+
+                       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:
index 7a7348817a7475c342d43e6ad8095dd1ed1641bd..5b6d4c502ac6a81d4e3b74136bbb9b36b316ca28 100644 (file)
@@ -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);
 
index 636f5ea59307f920d5a87bcc22da8921d06ebd64..6ac92c7d20feafebf7aa6e128cca781e7149b2c3 100644 (file)
@@ -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 */
index 846ae59f2367ef3ffa8f1b983f71ab36a39df2c0..dc02416db69f56e06b3c93951c2937446f5f79e4 100644 (file)
@@ -5,6 +5,6 @@
 
 #ifndef _VERSION_H
 
-#define EXFAT_PROGS_VERSION "1.2.8"
+#define EXFAT_PROGS_VERSION "1.2.9"
 
 #endif /* !_VERSION_H */
index 3189f71e39687ecf6119b54b12f92dd4fb23a395..44d27f56976536f5107d27912fa2eb5ac6aef2ea 100644 (file)
@@ -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;
index 4c6f5897e6075673ddc803aec74ec3ccf44c4552..ab260eeb0a663a256d97aa5e75ccee72f27c6a8c 100644 (file)
@@ -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.