aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNamjae Jeon <linkinjeon@kernel.org>2022-09-09 12:20:29 +0900
committerGitHub <noreply@github.com>2022-09-09 12:20:29 +0900
commit2b841723294ed57b71b3b35f6fa2aa96541872b3 (patch)
tree582742cfae0273214e62c6bf29a4413ba03c43f6
parent322d5fe703ef1c8adbe88974271c53b8129e8bc2 (diff)
parentd45d4da28836db69c2e9dd69191b6771aadbc2e2 (diff)
downloadexfatprogs-2b841723294ed57b71b3b35f6fa2aa96541872b3.tar.gz
Merge pull request #199 from hclee/for-next
For next
-rw-r--r--.travis.yml6
-rw-r--r--Android.bp1
-rw-r--r--Makefile.am6
-rw-r--r--configure.ac1
-rw-r--r--exfat2img/Makefile.am6
-rw-r--r--exfat2img/exfat2img.c1059
-rw-r--r--fsck/Makefile.am4
-rw-r--r--fsck/de_iter.c316
-rw-r--r--fsck/fsck.c1396
-rw-r--r--fsck/fsck.h81
-rw-r--r--fsck/repair.c54
-rw-r--r--fsck/repair.h12
-rw-r--r--include/exfat_dir.h82
-rw-r--r--include/exfat_fs.h86
-rw-r--r--include/exfat_ondisk.h7
-rw-r--r--include/libexfat.h64
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/exfat_dir.c927
-rw-r--r--lib/exfat_fs.c302
-rw-r--r--lib/libexfat.c165
-rw-r--r--manpages/exfat2img.831
-rw-r--r--mkfs/mkfs.c7
-rw-r--r--tests/bad_bitmap/exfat.img.tar.xzbin0 -> 4056 bytes
-rw-r--r--tests/bad_dentries/exfat.img.tar.xzbin0 -> 9628 bytes
-rw-r--r--tests/bad_file_size/exfat.img.tar.xzbin0 -> 4052 bytes
-rw-r--r--tests/bad_first_clu/exfat.img.tar.xzbin0 -> 3232 bytes
-rw-r--r--tests/bad_num_chain/config1
-rw-r--r--tests/bad_num_chain/exfat.img.tar.xzbin0 -> 4064 bytes
-rw-r--r--tests/bad_root/exfat.img.tar.xzbin0 -> 4528 bytes
-rw-r--r--tests/duplicate_clu/exfat.img.tar.xzbin0 -> 4048 bytes
-rw-r--r--tests/file_invalid_clus/exfat.img.expected.xzbin4048 -> 0 bytes
-rw-r--r--tests/large_file_invalid_clus/exfat.img.expected.xzbin48520 -> 0 bytes
-rw-r--r--tests/loop_chain/config1
-rw-r--r--tests/loop_chain/exfat.img.tar.xzbin0 -> 4052 bytes
-rwxr-xr-xtests/test_fsck.sh44
35 files changed, 3429 insertions, 1232 deletions
diff --git a/.travis.yml b/.travis.yml
index 90aff89..2d44bd1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,7 @@ notifications:
before_script:
- sudo apt-get install linux-headers-$(uname -r)
+ - sudo apt-get install xz-utils
- git clone --branch=exfat-next https://github.com/namjaejeon/exfat_oot
- ./.travis_get_mainline_kernel
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
@@ -21,11 +22,16 @@ script:
- ./configure > /dev/null
- make -j$((`nproc`+1)) > /dev/null
- sudo make install > /dev/null
+ # build & install exfat
- cd exfat_oot
- make > /dev/null
- sudo make install > /dev/null
- sudo modprobe exfat
- sudo mkdir -p /mnt/test
+ - cd ..
+ # run fsck repair testcases
+ - cd tests
+ - sudo ./test_fsck.sh
# create file/director test
- truncate -s 10G test.img
- sudo losetup /dev/loop22 test.img
diff --git a/Android.bp b/Android.bp
index 9f8716b..d0e2594 100644
--- a/Android.bp
+++ b/Android.bp
@@ -9,6 +9,7 @@ cc_library_headers {
"tune",
"label",
"dump",
+ "exfat2img",
],
}
diff --git a/Makefile.am b/Makefile.am
index 44f8635..3e36f55 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = lib mkfs fsck tune label dump
+SUBDIRS = lib mkfs fsck tune label dump exfat2img
# manpages
dist_man8_MANS = \
@@ -10,7 +10,8 @@ dist_man8_MANS = \
manpages/tune.exfat.8 \
manpages/mkfs.exfat.8 \
manpages/exfatlabel.8 \
- manpages/dump.exfat.8
+ manpages/dump.exfat.8 \
+ manpages/exfat2img.8
# other stuff
EXTRA_DIST = \
@@ -22,4 +23,5 @@ EXTRA_DIST = \
fsck/Android.bp \
label/Android.bp \
dump/Android.bp \
+ exfat2img/Android.bp \
README.md
diff --git a/configure.ac b/configure.ac
index 0544309..bc20774 100644
--- a/configure.ac
+++ b/configure.ac
@@ -32,6 +32,7 @@ AC_CONFIG_FILES([
tune/Makefile
label/Makefile
dump/Makefile
+ exfat2img/Makefile
])
AC_OUTPUT
diff --git a/exfat2img/Makefile.am b/exfat2img/Makefile.am
new file mode 100644
index 0000000..8b5cee7
--- /dev/null
+++ b/exfat2img/Makefile.am
@@ -0,0 +1,6 @@
+AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+exfat2img_LDADD = $(top_builddir)/lib/libexfat.a
+
+sbin_PROGRAMS = exfat2img
+
+exfat2img_SOURCES = exfat2img.c
diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c
new file mode 100644
index 0000000..bad7639
--- /dev/null
+++ b/exfat2img/exfat2img.c
@@ -0,0 +1,1059 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+
+#define EXFAT_MAX_UPCASE_CHARS 0x10000
+
+struct exfat2img_hdr {
+ __le32 magic;
+ __le32 major_version;
+ __le32 minor_version;
+ __le32 data_offset;
+ __le32 heap_clus_offset;
+ __le32 cluster_size;
+ __le32 cluster_count;
+ __le32 reserved[20];
+} __packed;
+
+#define EI_MAGIC 0xB67598DB
+#define EI_CC_PAYLOAD_LEN 4
+
+enum {
+ EI_CC_INVALID,
+ EI_CC_COPY_1,
+ EI_CC_COPY_2, /* followed by cluster count(4-byte) */
+ EI_CC_SKIP_1,
+ EI_CC_SKIP_2, /* followed by cluster count(4-byte) */
+};
+
+struct exfat2img {
+ int out_fd;
+ bool is_stdout;
+ off_t stdout_offset;
+ bool save_cc;
+ struct exfat_blk_dev bdev;
+ struct exfat *exfat;
+ struct buffer_desc *dump_bdesc;
+ struct buffer_desc *scan_bdesc;
+ struct exfat_de_iter de_iter;
+};
+
+struct exfat_stat {
+ long dir_count;
+ long file_count;
+ long error_count;
+ uint64_t written_bytes;
+};
+
+static struct exfat2img_hdr ei_hdr;
+static struct exfat2img ei;
+static struct exfat_stat exfat_stat;
+static struct path_resolve_ctx path_resolve_ctx;
+
+static struct option opts[] = {
+ {"output", required_argument, NULL, 'o' },
+ {"version", no_argument, NULL, 'V' },
+ {"help", no_argument, NULL, 'h' },
+ {NULL, 0, NULL, 0 }
+};
+
+static void usage(const char *name)
+{
+ fprintf(stderr, "Usage: %s <device> [image-file]\n", name);
+ fprintf(stderr, "\t-o | --output <image-file> Specify destination file\n");
+ fprintf(stderr, "\t-V | --version Show version\n");
+ fprintf(stderr, "\t-h | --help Show help\n");
+ exit(EXIT_FAILURE);
+}
+
+#define ei_err(parent, inode, fmt, ...) \
+({ \
+ exfat_resolve_path_parent(&path_resolve_ctx, \
+ parent, inode); \
+ exfat_err("ERROR: %s: " fmt, \
+ path_resolve_ctx.local_path, \
+ ##__VA_ARGS__); \
+})
+
+static void free_exfat2img(struct exfat2img *ei)
+{
+ if (ei->exfat)
+ exfat_free_exfat(ei->exfat);
+ if (ei->dump_bdesc)
+ exfat_free_buffer(ei->dump_bdesc, 2);
+ if (ei->scan_bdesc)
+ exfat_free_buffer(ei->scan_bdesc, 2);
+ if (ei->out_fd)
+ close(ei->out_fd);
+ if (ei->bdev.dev_fd)
+ close(ei->bdev.dev_fd);
+}
+
+static int create_exfat2img(struct exfat2img *ei,
+ struct pbr *bs,
+ const char *out_path)
+{
+ int err;
+
+ ei->exfat = exfat_alloc_exfat(&ei->bdev, bs);
+ if (!ei->exfat)
+ return -ENOMEM;
+
+ ei->dump_bdesc = exfat_alloc_buffer(2,
+ ei->exfat->clus_size,
+ ei->exfat->sect_size);
+ if (!ei->dump_bdesc) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ ei->scan_bdesc = exfat_alloc_buffer(2,
+ ei->exfat->clus_size,
+ ei->exfat->sect_size);
+ if (!ei->scan_bdesc) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ if (strcmp(out_path, "-")) {
+ ei->out_fd = open(out_path, O_CREAT | O_TRUNC | O_RDWR, 0664);
+ } else {
+ ei->is_stdout = true;
+ ei->out_fd = fileno(stdout);
+ ei->save_cc = true;
+ }
+ if (ei->out_fd < 0) {
+ exfat_err("failed to open %s: %s\n", out_path,
+ strerror(errno));
+ err = -errno;
+ goto err;
+ }
+
+ return 0;
+err:
+ free_exfat2img(ei);
+ return err;
+}
+
+static int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs)
+{
+ struct pbr *pbr;
+ int err = 0;
+ unsigned int sect_size, clu_size;
+
+ pbr = malloc(sizeof(struct pbr));
+
+ if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) !=
+ (ssize_t)sizeof(*pbr)) {
+ exfat_err("failed to read a boot sector\n");
+ err = -EIO;
+ goto err;
+ }
+
+ err = -EINVAL;
+ if (memcmp(pbr->bpb.oem_name, "EXFAT ", 8) != 0) {
+ exfat_err("failed to find exfat file system\n");
+ goto err;
+ }
+
+ sect_size = 1 << pbr->bsx.sect_size_bits;
+ clu_size = 1 << (pbr->bsx.sect_size_bits +
+ pbr->bsx.sect_per_clus_bits);
+
+ if (sect_size < 512 || sect_size > 4 * KB) {
+ exfat_err("too small or big sector size: %d\n",
+ sect_size);
+ goto err;
+ }
+
+ if (clu_size < sect_size || clu_size > 32 * MB) {
+ exfat_err("too small or big cluster size: %d\n",
+ clu_size);
+ goto err;
+ }
+
+ *bs = pbr;
+ return 0;
+err:
+ free(pbr);
+ return err;
+}
+
+/**
+ * @end: excluded.
+ */
+static ssize_t dump_range(struct exfat2img *ei, off_t start, off_t end)
+{
+ struct exfat *exfat = ei->exfat;
+ size_t len, total_len = 0;
+ ssize_t ret;
+
+ if (ei->is_stdout) {
+ unsigned int sc, sc_offset;
+ unsigned int ec, ec_offset;
+
+ if (exfat_o2c(ei->exfat, start, &sc, &sc_offset) < 0)
+ return -ERANGE;
+ if (exfat_o2c(ei->exfat, end - 1, &ec, &ec_offset) < 0)
+ return -ERANGE;
+ exfat_bitmap_set_range(ei->exfat, exfat->alloc_bitmap,
+ sc, ec - sc + 1);
+ return end - start;
+ }
+
+ while (start < end) {
+ len = (size_t)MIN(end - start, exfat->clus_size);
+
+ ret = exfat_read(exfat->blk_dev->dev_fd,
+ ei->dump_bdesc[0].buffer,
+ len, start);
+ if (ret != (ssize_t)len) {
+ exfat_err("failed to read %llu bytes at %llu\n",
+ (unsigned long long)len,
+ (unsigned long long)start);
+ return -EIO;
+ }
+
+ ret = pwrite(ei->out_fd, ei->dump_bdesc[0].buffer,
+ len, start);
+ if (ret != (ssize_t)len) {
+ exfat_err("failed to write %llu bytes at %llu\n",
+ (unsigned long long)len,
+ (unsigned long long)start);
+ return -EIO;
+ }
+
+ start += len;
+ total_len += len;
+ exfat_stat.written_bytes += len;
+ }
+ return total_len;
+}
+
+static int dump_sectors(struct exfat2img *ei,
+ off_t start_sect,
+ off_t end_sect_excl)
+{
+ struct exfat *exfat = ei->exfat;
+ off_t s, e;
+
+ s = exfat_s2o(exfat, start_sect);
+ e = exfat_s2o(exfat, end_sect_excl);
+ return dump_range(ei, s, e) <= 0 ? -EIO : 0;
+}
+
+static int dump_clusters(struct exfat2img *ei,
+ clus_t start_clus,
+ clus_t end_clus_excl)
+{
+ struct exfat *exfat = ei->exfat;
+ off_t s, e;
+
+ s = exfat_c2o(exfat, start_clus);
+ e = exfat_c2o(exfat, end_clus_excl);
+ return dump_range(ei, s, e) <= 0 ? -EIO : 0;
+}
+
+static int dump_directory(struct exfat2img *ei,
+ struct exfat_inode *inode, size_t size,
+ clus_t *out_clus_count)
+{
+ struct exfat *exfat = ei->exfat;
+ clus_t clus, possible_count;
+ uint64_t max_count;
+ size_t dump_size;
+ off_t start_off, end_off;
+
+ if (size == 0)
+ return -EINVAL;
+
+ if (!(inode->attr & ATTR_SUBDIR))
+ return -EINVAL;
+
+ clus = inode->first_clus;
+ *out_clus_count = 0;
+ max_count = DIV_ROUND_UP(inode->size, exfat->clus_size);
+
+ possible_count = (256 * MB) >> (exfat->bs->bsx.sect_per_clus_bits +
+ exfat->bs->bsx.sect_size_bits);
+ possible_count = MIN(possible_count, exfat->clus_count);
+
+ while (exfat_heap_clus(exfat, clus) && *out_clus_count < possible_count) {
+ dump_size = MIN(size, exfat->clus_size);
+ start_off = exfat_c2o(exfat, clus);
+ end_off = start_off + DIV_ROUND_UP(dump_size, 512) * 512;
+
+ if (dump_range(ei, start_off, end_off) < 0)
+ return -EIO;
+
+ *out_clus_count += 1;
+ size -= dump_size;
+ if (size == 0)
+ break;
+
+ if (inode->is_contiguous) {
+ if (*out_clus_count >= max_count)
+ break;
+ }
+ if (exfat_get_inode_next_clus(exfat, inode, clus, &clus))
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int dump_root(struct exfat2img *ei)
+{
+ struct exfat *exfat = ei->exfat;
+ struct exfat_inode *root;
+ clus_t clus_count = 0;
+
+ root = exfat_alloc_inode(ATTR_SUBDIR);
+ if (!root)
+ return -ENOMEM;
+
+ root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
+ dump_directory(ei, root, (size_t)-1, &clus_count);
+ root->size = clus_count * exfat->clus_size;
+
+ ei->exfat->root = root;
+ return 0;
+}
+
+static int read_file_dentry_set(struct exfat_de_iter *iter,
+ struct exfat_inode **new_node, int *skip_dentries)
+{
+ struct exfat_dentry *file_de, *stream_de, *dentry;
+ struct exfat_inode *node = NULL;
+ int i, ret;
+
+ ret = exfat_de_iter_get(iter, 0, &file_de);
+ if (ret || file_de->type != EXFAT_FILE) {
+ exfat_debug("failed to get file dentry\n");
+ return -EINVAL;
+ }
+
+ ret = exfat_de_iter_get(iter, 1, &stream_de);
+ if (ret || stream_de->type != EXFAT_STREAM) {
+ exfat_debug("failed to get stream dentry\n");
+ *skip_dentries = 2;
+ goto skip_dset;
+ }
+
+ *new_node = NULL;
+ node = exfat_alloc_inode(le16_to_cpu(file_de->file_attr));
+ if (!node)
+ return -ENOMEM;
+
+ for (i = 2; i <= file_de->file_num_ext; i++) {
+ ret = exfat_de_iter_get(iter, i, &dentry);
+ if (ret || dentry->type != EXFAT_NAME)
+ break;
+ memcpy(node->name +
+ (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode,
+ sizeof(dentry->name_unicode));
+ }
+
+ node->first_clus = le32_to_cpu(stream_de->stream_start_clu);
+ node->is_contiguous =
+ ((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0);
+ node->size = le64_to_cpu(stream_de->stream_size);
+
+ *skip_dentries = i;
+ *new_node = node;
+ return 0;
+skip_dset:
+ *new_node = NULL;
+ exfat_free_inode(node);
+ return -EINVAL;
+}
+
+static int read_file(struct exfat_de_iter *de_iter,
+ struct exfat_inode **new_node, int *dentry_count)
+{
+ struct exfat_inode *node;
+ int ret;
+
+ *new_node = NULL;
+
+ ret = read_file_dentry_set(de_iter, &node, dentry_count);
+ if (ret)
+ return ret;
+
+ if (node->attr & ATTR_SUBDIR)
+ exfat_stat.dir_count++;
+ else
+ exfat_stat.file_count++;
+ *new_node = node;
+ return ret;
+}
+
+static int read_bitmap(struct exfat2img *ei, struct exfat_de_iter *iter)
+{
+ struct exfat *exfat = ei->exfat;
+ struct exfat_dentry *dentry;
+ int ret;
+
+ ret = exfat_de_iter_get(iter, 0, &dentry);
+ if (ret || dentry->type != EXFAT_BITMAP) {
+ exfat_debug("failed to get bimtap dentry\n");
+ return -EINVAL;
+ }
+
+ exfat_debug("start cluster %#x, size %#" PRIx64 "\n",
+ le32_to_cpu(dentry->bitmap_start_clu),
+ le64_to_cpu(dentry->bitmap_size));
+
+ if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
+ exfat_err("invalid start cluster of allocate bitmap. 0x%x\n",
+ le32_to_cpu(dentry->bitmap_start_clu));
+ return -EINVAL;
+ }
+
+ exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
+ exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
+
+ return dump_clusters(ei,
+ exfat->disk_bitmap_clus,
+ exfat->disk_bitmap_clus +
+ DIV_ROUND_UP(exfat->disk_bitmap_size,
+ exfat->clus_size));
+}
+
+static int read_upcase_table(struct exfat2img *ei,
+ struct exfat_de_iter *iter)
+{
+ struct exfat *exfat = ei->exfat;
+ struct exfat_dentry *dentry = NULL;
+ int retval;
+ ssize_t size;
+
+ retval = exfat_de_iter_get(iter, 0, &dentry);
+ if (retval || dentry->type != EXFAT_UPCASE) {
+ exfat_debug("failed to get upcase dentry\n");
+ return -EINVAL;
+ }
+
+ if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
+ exfat_err("invalid start cluster of upcase table. 0x%x\n",
+ le32_to_cpu(dentry->upcase_start_clu));
+ return -EINVAL;
+ }
+
+ size = EXFAT_MAX_UPCASE_CHARS * sizeof(__le16);
+ return dump_clusters(ei, le32_to_cpu(dentry->upcase_start_clu),
+ le32_to_cpu(dentry->upcase_start_clu) +
+ DIV_ROUND_UP(size, exfat->clus_size));
+}
+
+static int read_children(struct exfat2img *ei, struct exfat_inode *dir,
+ off_t *end_file_offset)
+{
+ struct exfat *exfat = ei->exfat;
+ struct exfat_inode *node = NULL;
+ struct exfat_dentry *dentry;
+ struct exfat_de_iter *de_iter;
+ int dentry_count;
+ int ret;
+
+ *end_file_offset = 0;
+ de_iter = &ei->de_iter;
+ ret = exfat_de_iter_init(de_iter, exfat, dir, ei->scan_bdesc);
+ if (ret == EOF)
+ return 0;
+ else if (ret)
+ return ret;
+
+ while (1) {
+ ret = exfat_de_iter_get(de_iter, 0, &dentry);
+ if (ret == EOF) {
+ break;
+ } else if (ret) {
+ ei_err(dir->parent, dir,
+ "failed to get a dentry. %d\n", ret);
+ goto err;
+ }
+ dentry_count = 1;
+
+ switch (dentry->type) {
+ case EXFAT_FILE:
+ ret = read_file(de_iter, &node, &dentry_count);
+ if (ret < 0) {
+ exfat_stat.error_count++;
+ break;
+ }
+
+ if (node) {
+ if ((node->attr & ATTR_SUBDIR) && node->size) {
+ node->parent = dir;
+ list_add_tail(&node->sibling,
+ &dir->children);
+ list_add_tail(&node->list,
+ &exfat->dir_list);
+ } else {
+ exfat_free_inode(node);
+ }
+ }
+ break;
+ case EXFAT_LAST:
+ goto out;
+ case EXFAT_BITMAP:
+ if (dir == exfat->root) {
+ ret = read_bitmap(ei, de_iter);
+ if (ret)
+ exfat_debug("failed to read bitmap\n");
+ }
+ break;
+ case EXFAT_UPCASE:
+ if (dir == exfat->root) {
+ ret = read_upcase_table(ei, de_iter);
+ if (ret)
+ exfat_debug("failed to upcase table\n");
+ }
+ break;
+ case EXFAT_VOLUME:
+ default:
+ break;
+ }
+
+ ret = exfat_de_iter_advance(de_iter, dentry_count);
+ }
+out:
+ *end_file_offset = exfat_de_iter_file_offset(de_iter);
+ exfat_de_iter_flush(de_iter);
+ return 0;
+err:
+ exfat_free_children(dir, false);
+ INIT_LIST_HEAD(&dir->children);
+ exfat_de_iter_flush(de_iter);
+ return ret;
+}
+
+static int dump_filesystem(struct exfat2img *ei)
+{
+ struct exfat *exfat = ei->exfat;
+ struct exfat_inode *dir;
+ int ret = 0, dir_errors;
+ clus_t clus_count;
+ off_t end_file_offset;
+
+ if (!exfat->root) {
+ exfat_err("root is NULL\n");
+ return -ENOENT;
+ }
+
+ list_add(&exfat->root->list, &exfat->dir_list);
+
+ while (!list_empty(&exfat->dir_list)) {
+ dir = list_entry(exfat->dir_list.next,
+ struct exfat_inode, list);
+ clus_count = 0;
+
+ if (!(dir->attr & ATTR_SUBDIR)) {
+ ei_err(dir->parent, dir,
+ "failed to travel directories. the node is not directory\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dir_errors = read_children(ei, dir, &end_file_offset);
+ if (!dir_errors) {
+ dump_directory(ei, dir, (size_t)end_file_offset,
+ &clus_count);
+ } else if (dir_errors) {
+ dump_directory(ei, dir, (size_t)-1,
+ &clus_count);
+ exfat_resolve_path(&path_resolve_ctx, dir);
+ exfat_debug("failed to check dentries: %s\n",
+ path_resolve_ctx.local_path);
+ ret = dir_errors;
+ }
+
+ list_del(&dir->list);
+ exfat_free_ancestors(dir);
+ }
+out:
+ exfat_free_dir_list(exfat);
+ return ret;
+}
+
+static int dump_bytes_to_stdout(struct exfat2img *ei,
+ off_t start, off_t end_excl, bool fill_zero)
+{
+ struct exfat *exfat = ei->exfat;
+ size_t len;
+ ssize_t ret;
+
+ if (start != ei->stdout_offset) {
+ exfat_err("try to skip for stdout at %llu, expected: %llu\n",
+ (unsigned long long)start,
+ (unsigned long long)ei->stdout_offset);
+ return -EINVAL;
+ }
+
+ while (start < end_excl) {
+ len = (size_t)MIN(end_excl - start, exfat->clus_size);
+ if (!fill_zero) {
+ ret = exfat_read(exfat->blk_dev->dev_fd,
+ ei->dump_bdesc[0].buffer,
+ len, start);
+ if (ret != (ssize_t)len) {
+ exfat_err("failed to read %llu bytes at %llu\n",
+ (unsigned long long)len,
+ (unsigned long long)start);
+ return -EIO;
+ }
+
+ ret = write(ei->out_fd, ei->dump_bdesc[0].buffer, len);
+ if (ret != (ssize_t)len) {
+ exfat_err("failed to write %llu bytes at %llu\n",
+ (unsigned long long)len,
+ (unsigned long long)start);
+ return -EIO;
+ }
+ } else {
+ ret = write(ei->out_fd, exfat->zero_cluster, len);
+ if (ret != (ssize_t)len) {
+ exfat_err("failed to write %llu bytes at %llu\n",
+ (unsigned long long)len,
+ (unsigned long long)start);
+ return -EIO;
+ }
+ }
+
+ start += len;
+ ei->stdout_offset += len;
+ exfat_stat.written_bytes += len;
+ }
+ return 0;
+}
+
+static int dump_clusters_to_stdout(struct exfat2img *ei,
+ unsigned int start_clu, unsigned int end_clu,
+ bool fill_zero)
+{
+ unsigned int clu, clu_count;
+ unsigned char cc;
+ unsigned int cc_clu_count, cc_len;
+ off_t start_off, end_off_excl;
+ char buf[1 + EI_CC_PAYLOAD_LEN];
+
+ clu = start_clu;
+ clu_count = end_clu - start_clu + 1;
+
+ if (ei->save_cc) {
+ /* if the count of clusters is less than 5, use SKIP_1 or COPY_2 */
+ cc_clu_count = clu_count < 5 ? 1 : clu_count;
+ cc_len = cc_clu_count == 1 ? 1 : 1 + EI_CC_PAYLOAD_LEN;
+ if (fill_zero)
+ cc = cc_clu_count == 1 ? EI_CC_SKIP_1 : EI_CC_SKIP_2;
+ else
+ cc = cc_clu_count == 1 ? EI_CC_COPY_1 : EI_CC_COPY_2;
+ } else {
+ cc = EI_CC_INVALID;
+ cc_clu_count = clu_count;
+ }
+
+ while (clu <= end_clu) {
+ if (cc != EI_CC_INVALID) {
+ buf[0] = cc;
+ *((__le32 *)&buf[1]) =
+ cpu_to_le32(cc_clu_count);
+ if (write(ei->out_fd, buf, cc_len) != (ssize_t)cc_len) {
+ exfat_err("failed to write cc %d : %u\n for %u ~ %u clusters\n",
+ cc, cc_clu_count,
+ start_clu, start_clu + cc_clu_count - 1);
+ }
+ }
+
+ if (cc == EI_CC_COPY_1 || cc == EI_CC_COPY_2) {
+ start_off = exfat_c2o(ei->exfat, clu);
+ end_off_excl = exfat_c2o(ei->exfat, clu + cc_clu_count);
+
+ if (dump_bytes_to_stdout(ei, start_off, end_off_excl,
+ false) < 0)
+ return -EIO;
+ } else {
+ ei->stdout_offset += (off_t)cc_clu_count * ei->exfat->clus_size;
+ }
+ clu += cc_clu_count;
+ }
+
+ return 0;
+}
+
+static int dump_to_stdout(struct exfat2img *ei)
+{
+ struct exfat *exfat = ei->exfat;
+ off_t start_off, end_off;
+ unsigned int clu, last_clu, next_clu;
+ unsigned int start_clu, end_clu;
+
+ start_off = 0;
+ end_off = exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset));
+ if (dump_bytes_to_stdout(ei, start_off, end_off, false) < 0) {
+ exfat_err("failed to dump boot sectors and FAT tables\n");
+ return -EIO;
+ }
+
+ clu = EXFAT_FIRST_CLUSTER;
+ last_clu = clu + exfat->clus_count;
+ while (clu < last_clu) {
+ /* read and write clusters for allocated ones */
+ start_clu = 0;
+ while (clu < last_clu &&
+ exfat_bitmap_get(exfat->alloc_bitmap, clu)) {
+ if (!start_clu)
+ start_clu = clu;
+ end_clu = clu;
+ clu++;
+ }
+
+ if (start_clu) {
+ if (dump_clusters_to_stdout(ei, start_clu, end_clu, false) < 0) {
+ start_off = exfat_c2o(exfat, start_clu);
+ end_off = exfat_c2o(exfat, end_clu);
+ exfat_err("failed to dump range from %llx to %llx\n",
+ (unsigned long long)start_off,
+ (unsigned long long)end_off);
+ return -EIO;
+ }
+ }
+
+ /* exit if all of the remaining clusters are free */
+ if (clu >= last_clu)
+ break;
+ if (exfat_bitmap_find_one(exfat, exfat->alloc_bitmap,
+ clu, &next_clu))
+ next_clu = EXFAT_FIRST_CLUSTER + exfat->clus_count;
+
+ /* write zeroes for free clusters */
+ start_clu = clu;
+ end_clu = next_clu - 1;
+ if (dump_clusters_to_stdout(ei, start_clu, end_clu, true) < 0) {
+ start_off = exfat_c2o(exfat, start_clu);
+ end_off = exfat_c2o(exfat, end_clu);
+ exfat_err("failed to dump zero range from %llx to %llx\n",
+ (unsigned long long)start_off,
+ (unsigned long long)end_off);
+ return -EIO;
+ }
+
+ clu = next_clu;
+ }
+
+ return 0;
+}
+
+static int dump_header(struct exfat2img *ei)
+{
+ struct exfat *exfat = ei->exfat;
+
+ ei_hdr.magic = cpu_to_le32(EI_MAGIC);
+ ei_hdr.major_version = cpu_to_le32(1);
+ ei_hdr.minor_version = cpu_to_le32(0);
+ ei_hdr.data_offset = cpu_to_le32(sizeof(struct exfat2img_hdr));
+ ei_hdr.heap_clus_offset =
+ cpu_to_le32(le32_to_cpu(exfat->bs->bsx.clu_offset) *
+ exfat->sect_size);
+ ei_hdr.cluster_size = cpu_to_le32(exfat->clus_size);
+ ei_hdr.cluster_count = cpu_to_le32(exfat->clus_count);
+
+ if (write(ei->out_fd, &ei_hdr, sizeof(ei_hdr)) != (ssize_t)sizeof(ei_hdr)) {
+ exfat_err("failed to write exfat2img header\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static ssize_t read_stream(int fd, void *buf, size_t len)
+{
+ size_t read_len = 0;
+ ssize_t ret;
+
+ while (read_len < len) {
+ ret = read(fd, buf, len - read_len);
+ if (ret < 0) {
+ if (errno != -EAGAIN && errno != -EINTR)
+ return -1;
+ ret = 0;
+ } else if (ret == 0) {
+ return 0;
+ }
+ buf += (size_t)ret;
+ read_len += (size_t)ret;
+ }
+ return read_len;
+}
+
+static int restore_from_stdin(struct exfat2img *ei)
+{
+ int in_fd, ret;
+ unsigned char cc;
+ unsigned int clu, end_clu;
+ unsigned int cc_clu_count;
+ unsigned int clus_size;
+ __le32 t_cc_clu_count;
+ off_t out_start_off, out_end_off_excl;
+ off_t in_start_off;
+ size_t len;
+
+ in_fd = fileno(stdin);
+ if (in_fd < 0) {
+ exfat_err("failed to get fd from stdin\n");
+ return in_fd;
+ }
+
+ if (read_stream(in_fd, &ei_hdr, sizeof(ei_hdr)) != (ssize_t)sizeof(ei_hdr)) {
+ exfat_err("failed to read a header\n");
+ return -EIO;
+ }
+
+ if (le32_to_cpu(ei_hdr.magic) != EI_MAGIC) {
+ exfat_err("header has invalid magic %#x, expected %#x\n",
+ le32_to_cpu(ei_hdr.magic), EI_MAGIC);
+ return -EINVAL;
+ }
+
+ clus_size = le32_to_cpu(ei_hdr.cluster_size);
+
+ ei->out_fd = ei->bdev.dev_fd;
+ ei->dump_bdesc = exfat_alloc_buffer(2, clus_size, 512);
+ if (!ei->dump_bdesc)
+ return -ENOMEM;
+
+ /* restore boot regions, and FAT tables */
+ in_start_off = le32_to_cpu(ei_hdr.data_offset);
+ out_start_off = 0;
+ out_end_off_excl = le32_to_cpu(ei_hdr.heap_clus_offset);
+ while (out_start_off < out_end_off_excl) {
+ len = MIN(out_end_off_excl - out_start_off, clus_size);
+ if (read_stream(in_fd, ei->dump_bdesc[0].buffer, len) != (ssize_t)len) {
+ exfat_err("failed to read first meta region. %llu ~ %llu\n",
+ (unsigned long long)in_start_off,
+ (unsigned long long)in_start_off + len);
+ ret = -EIO;
+ goto out;
+ }
+
+ if (pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, len, out_start_off)
+ != (ssize_t)len) {
+ exfat_err("failed to write first meta region. %llu ~ %llu\n",
+ (unsigned long long)out_start_off,
+ (unsigned long long)out_start_off + len);
+ ret = -EIO;
+ goto out;
+ }
+
+ out_start_off += len;
+ in_start_off += len;
+ }
+
+ /* restore heap clusters */
+ clu = 0;
+ while (clu < le32_to_cpu(ei_hdr.cluster_count)) {
+ if (read_stream(in_fd, &cc, sizeof(cc)) != (ssize_t)sizeof(cc)) {
+ exfat_err("failed to read cc at %llu\n",
+ (unsigned long long)in_start_off);
+ ret = -EIO;
+ goto out;
+ }
+ in_start_off += 1;
+
+ if (cc == EI_CC_COPY_2 || cc == EI_CC_SKIP_2) {
+ if (read_stream(in_fd, &t_cc_clu_count, EI_CC_PAYLOAD_LEN) !=
+ (ssize_t)EI_CC_PAYLOAD_LEN) {
+ exfat_err("failed to read cc cluster count at %llu\n",
+ (unsigned long long)in_start_off);
+ ret = -EIO;
+ goto out;
+ }
+ cc_clu_count = le32_to_cpu(t_cc_clu_count);
+ in_start_off += EI_CC_PAYLOAD_LEN;
+ } else if (cc == EI_CC_COPY_1 || cc == EI_CC_SKIP_1) {
+ cc_clu_count = 1;
+ } else {
+ exfat_err("unexpected cc %d at %llu\n",
+ cc, (unsigned long long)in_start_off);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (cc == EI_CC_COPY_1 || cc == EI_CC_COPY_2) {
+ end_clu = clu + cc_clu_count;
+ while (clu < end_clu) {
+ if (read_stream(in_fd, ei->dump_bdesc[0].buffer,
+ clus_size) != (ssize_t)clus_size) {
+ exfat_err("failed to read range %llu ~ %llu\n",
+ (unsigned long long)in_start_off,
+ (unsigned long long)in_start_off + clus_size);
+ ret = -EIO;
+ goto out;
+ }
+ if (pwrite(ei->out_fd, ei->dump_bdesc[0].buffer,
+ clus_size, out_start_off) != (ssize_t)clus_size) {
+ exfat_err("failed to write range %llu ~ %llu\n",
+ (unsigned long long)out_start_off,
+ (unsigned long long)out_start_off + clus_size);
+ ret = -EIO;
+ goto out;
+ }
+
+ out_start_off += clus_size;
+ in_start_off += clus_size;
+ clu++;
+ }
+ } else {
+ out_start_off += (off_t)cc_clu_count * clus_size;
+ in_start_off += (off_t)cc_clu_count * clus_size;
+ if (lseek(ei->out_fd, out_start_off, SEEK_SET) == (off_t)-1) {
+ exfat_err("failed to seek to %llu\n",
+ (unsigned long long)out_start_off);
+ ret = -EIO;
+ goto out;
+ }
+ clu += cc_clu_count;
+ }
+ }
+out:
+ fsync(ei->out_fd);
+ exfat_free_buffer(ei->dump_bdesc, 2);
+ return ret;
+}
+
+int main(int argc, char * const argv[])
+{
+ int err = 0, c;
+ const char *in_path, *out_path = NULL, *blkdev_path;
+ struct pbr *bs;
+ struct exfat_user_input ui;
+ off_t last_sect;
+ bool restore;
+
+ print_level = EXFAT_ERROR;
+
+ opterr = 0;
+ while ((c = getopt_long(argc, argv, "o:Vh", opts, NULL)) != EOF) {
+ switch (c) {
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'V':
+ show_version();
+ return 0;
+ case 'h':
+ /* Fall through */
+ default:
+ usage(argv[0]);
+ break;
+ }
+ }
+
+ show_version();
+ if (!(optind == argc - 1 && out_path) &&
+ !(optind == argc - 2 && !out_path))
+ usage(argv[0]);
+
+ in_path = argv[optind++];
+ if (!out_path)
+ out_path = argv[optind++];
+
+ if (!strcmp(in_path, "-")) {
+ restore = true;
+ blkdev_path = out_path;
+ } else {
+ restore = false;
+ blkdev_path = in_path;
+ }
+
+ memset(&ui, 0, sizeof(ui));
+ snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", blkdev_path);
+ if (restore)
+ ui.writeable = true;
+ else
+ ui.writeable = false;
+
+ if (exfat_get_blk_dev_info(&ui, &ei.bdev)) {
+ exfat_err("failed to open %s\n", ui.dev_name);
+ return EXIT_FAILURE;
+ }
+
+ if (restore)
+ return restore_from_stdin(&ei);
+
+ err = read_boot_sect(&ei.bdev, &bs);
+ if (err) {
+ close(ei.bdev.dev_fd);
+ return EXIT_FAILURE;
+ }
+
+ err = create_exfat2img(&ei, bs, out_path);
+ if (err)
+ return EXIT_FAILURE;
+
+ if (!ei.is_stdout) {
+ err = dump_sectors(&ei, 0, le32_to_cpu(ei.exfat->bs->bsx.clu_offset));
+ if (err) {
+ exfat_err("failed to dump boot sectors, fat\n");
+ goto out;
+ }
+
+ last_sect = (off_t)le32_to_cpu(ei.exfat->bs->bsx.clu_offset) +
+ (le32_to_cpu(ei.exfat->bs->bsx.clu_count) <<
+ ei.exfat->bs->bsx.sect_per_clus_bits) - 1;
+ err = dump_sectors(&ei, last_sect, last_sect + 1);
+ if (err) {
+ exfat_err("failed to dump last sector\n");
+ goto out;
+ }
+ }
+
+ err = dump_root(&ei);
+ if (err) {
+ exfat_err("failed to dump root\n");
+ goto out;
+ }
+
+ dump_filesystem(&ei);
+
+ if (ei.is_stdout) {
+ err = dump_header(&ei);
+ if (err)
+ goto out;
+ err = dump_to_stdout(&ei);
+ if (err)
+ goto out;
+ } else {
+ err = fsync(ei.out_fd);
+ if (err) {
+ exfat_err("failed to fsync %s. %d\n", out_path, errno);
+ goto out;
+ }
+ close(ei.out_fd);
+ }
+
+ printf("%ld files found, %ld directories dumped, %llu kbytes written\n",
+ exfat_stat.file_count,
+ exfat_stat.dir_count,
+ (unsigned long long)DIV_ROUND_UP(exfat_stat.written_bytes, 1024));
+
+out:
+ free_exfat2img(&ei);
+ return err == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/fsck/Makefile.am b/fsck/Makefile.am
index 57a0ede..519b13a 100644
--- a/fsck/Makefile.am
+++ b/fsck/Makefile.am
@@ -1,6 +1,6 @@
-AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a
sbin_PROGRAMS = fsck.exfat
-fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h
+fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h
diff --git a/fsck/de_iter.c b/fsck/de_iter.c
deleted file mode 100644
index 587b027..0000000
--- a/fsck/de_iter.c
+++ /dev/null
@@ -1,316 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <fcntl.h>
-
-#include "exfat_ondisk.h"
-#include "libexfat.h"
-#include "fsck.h"
-
-static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block)
-{
- off_t device_offset;
- struct exfat *exfat = iter->exfat;
- struct buffer_desc *desc;
- unsigned int i;
-
- desc = &iter->buffer_desc[block & 0x01];
- device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset;
-
- for (i = 0; i < iter->read_size / iter->write_size; i++) {
- if (desc->dirty[i]) {
- if (exfat_write(exfat->blk_dev->dev_fd,
- desc->buffer + i * iter->write_size,
- iter->write_size,
- device_offset + i * iter->write_size)
- != (ssize_t)iter->write_size)
- return -EIO;
- desc->dirty[i] = 0;
- }
- }
- return 0;
-}
-
-static int read_ahead_first_blocks(struct exfat_de_iter *iter)
-{
-#ifdef POSIX_FADV_WILLNEED
- struct exfat *exfat = iter->exfat;
- clus_t clus_count;
- unsigned int size;
-
- clus_count = iter->parent->size / exfat->clus_size;
-
- if (clus_count > 1) {
- iter->ra_begin_offset = 0;
- iter->ra_next_clus = 1;
- size = exfat->clus_size;
- } else {
- iter->ra_begin_offset = 0;
- iter->ra_next_clus = 0;
- size = iter->ra_partial_size;
- }
- return posix_fadvise(exfat->blk_dev->dev_fd,
- exfat_c2o(exfat, iter->parent->first_clus), size,
- POSIX_FADV_WILLNEED);
-#else
- return -ENOTSUP;
-#endif
-}
-
-/**
- * read the next fragment in advance, and assume the fragment
- * which covers @clus is already read.
- */
-static int read_ahead_next_blocks(struct exfat_de_iter *iter,
- clus_t clus, unsigned int offset, clus_t p_clus)
-{
-#ifdef POSIX_FADV_WILLNEED
- struct exfat *exfat = iter->exfat;
- off_t device_offset;
- clus_t clus_count, ra_clus, ra_p_clus;
- unsigned int size;
- int ret = 0;
-
- clus_count = iter->parent->size / exfat->clus_size;
- if (clus + 1 < clus_count) {
- ra_clus = clus + 1;
- if (ra_clus == iter->ra_next_clus &&
- offset >= iter->ra_begin_offset) {
- ret = get_next_clus(exfat, iter->parent,
- p_clus, &ra_p_clus);
- if (ret)
- return ret;
-
- if (ra_p_clus == EXFAT_EOF_CLUSTER)
- return -EIO;
-
- device_offset = exfat_c2o(exfat, ra_p_clus);
- size = ra_clus + 1 < clus_count ?
- exfat->clus_size : iter->ra_partial_size;
- ret = posix_fadvise(exfat->blk_dev->dev_fd,
- device_offset, size,
- POSIX_FADV_WILLNEED);
- iter->ra_next_clus = ra_clus + 1;
- iter->ra_begin_offset = 0;
- }
- } else {
- if (offset >= iter->ra_begin_offset &&
- offset + iter->ra_partial_size <=
- exfat->clus_size) {
- device_offset = exfat_c2o(exfat, p_clus) +
- offset + iter->ra_partial_size;
- ret = posix_fadvise(exfat->blk_dev->dev_fd,
- device_offset, iter->ra_partial_size,
- POSIX_FADV_WILLNEED);
- iter->ra_begin_offset =
- offset + iter->ra_partial_size;
- }
- }
-
- return ret;
-#else
- return -ENOTSUP;
-#endif
-}
-
-static int read_ahead_next_dir_blocks(struct exfat_de_iter *iter)
-{
-#ifdef POSIX_FADV_WILLNEED
- struct exfat *exfat = iter->exfat;
- struct list_head *current;
- struct exfat_inode *next_inode;
- off_t offset;
-
- if (list_empty(&exfat->dir_list))
- return -EINVAL;
-
- current = exfat->dir_list.next;
- if (iter->parent == list_entry(current, struct exfat_inode, list) &&
- current->next != &exfat->dir_list) {
- next_inode = list_entry(current->next, struct exfat_inode,
- list);
- offset = exfat_c2o(exfat, next_inode->first_clus);
- return posix_fadvise(exfat->blk_dev->dev_fd, offset,
- iter->ra_partial_size,
- POSIX_FADV_WILLNEED);
- }
-
- return 0;
-#else
- return -ENOTSUP;
-#endif
-}
-
-static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block)
-{
- struct exfat *exfat = iter->exfat;
- struct buffer_desc *desc, *prev_desc;
- off_t device_offset;
- ssize_t ret;
-
- desc = &iter->buffer_desc[block & 0x01];
- if (block == 0) {
- desc->p_clus = iter->parent->first_clus;
- desc->offset = 0;
- }
-
- /* if the buffer already contains dirty dentries, write it */
- if (write_block(iter, block))
- return -EIO;
-
- if (block > 0) {
- if (block > iter->parent->size / iter->read_size)
- return EOF;
-
- prev_desc = &iter->buffer_desc[(block-1) & 0x01];
- if (prev_desc->offset + 2 * iter->read_size <=
- exfat->clus_size) {
- desc->p_clus = prev_desc->p_clus;
- desc->offset = prev_desc->offset + iter->read_size;
- } else {
- ret = get_next_clus(exfat, iter->parent,
- prev_desc->p_clus, &desc->p_clus);
- desc->offset = 0;
- if (ret)
- return ret;
- else if (desc->p_clus == EXFAT_EOF_CLUSTER)
- return EOF;
- }
- }
-
- device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset;
- ret = exfat_read(exfat->blk_dev->dev_fd, desc->buffer,
- iter->read_size, device_offset);
- if (ret <= 0)
- return ret;
-
- /*
- * if a buffer is filled with dentries, read blocks ahead of time,
- * otherwise read blocks of the next directory in advance.
- */
- if (desc->buffer[iter->read_size - 32] != EXFAT_LAST)
- read_ahead_next_blocks(iter,
- (block * iter->read_size) / exfat->clus_size,
- (block * iter->read_size) % exfat->clus_size,
- desc->p_clus);
- else
- read_ahead_next_dir_blocks(iter);
- return ret;
-}
-
-int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
- struct exfat_inode *dir)
-{
- iter->exfat = exfat;
- iter->parent = dir;
- iter->write_size = exfat->sect_size;
- iter->read_size = exfat->clus_size <= 4*KB ? exfat->clus_size : 4*KB;
- if (exfat->clus_size <= 32 * KB)
- iter->ra_partial_size = MAX(4 * KB, exfat->clus_size / 2);
- else
- iter->ra_partial_size = exfat->clus_size / 4;
- iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB);
-
- if (!iter->buffer_desc)
- iter->buffer_desc = exfat->buffer_desc;
-
- if (iter->parent->size == 0)
- return EOF;
-
- read_ahead_first_blocks(iter);
- if (read_block(iter, 0) != (ssize_t)iter->read_size) {
- exfat_err("failed to read directory entries.\n");
- return -EIO;
- }
-
- iter->de_file_offset = 0;
- iter->next_read_offset = iter->read_size;
- iter->max_skip_dentries = 0;
- return 0;
-}
-
-int exfat_de_iter_get(struct exfat_de_iter *iter,
- int ith, struct exfat_dentry **dentry)
-{
- off_t next_de_file_offset;
- ssize_t ret;
- unsigned int block;
-
- next_de_file_offset = iter->de_file_offset +
- ith * sizeof(struct exfat_dentry);
- block = (unsigned int)(next_de_file_offset / iter->read_size);
-
- if (next_de_file_offset + sizeof(struct exfat_dentry) >
- iter->parent->size)
- return EOF;
- /* the dentry must be in current, or next block which will be read */
- if (block > iter->de_file_offset / iter->read_size + 1)
- return -ERANGE;
-
- /* read next cluster if needed */
- if (next_de_file_offset >= iter->next_read_offset) {
- ret = read_block(iter, block);
- if (ret != (ssize_t)iter->read_size)
- return ret;
- iter->next_read_offset += iter->read_size;
- }
-
- if (ith + 1 > iter->max_skip_dentries)
- iter->max_skip_dentries = ith + 1;
-
- *dentry = (struct exfat_dentry *)
- (iter->buffer_desc[block & 0x01].buffer +
- next_de_file_offset % iter->read_size);
- return 0;
-}
-
-int exfat_de_iter_get_dirty(struct exfat_de_iter *iter,
- int ith, struct exfat_dentry **dentry)
-{
- off_t next_file_offset;
- unsigned int block;
- int ret, sect_idx;
-
- ret = exfat_de_iter_get(iter, ith, dentry);
- if (!ret) {
- next_file_offset = iter->de_file_offset +
- ith * sizeof(struct exfat_dentry);
- block = (unsigned int)(next_file_offset / iter->read_size);
- sect_idx = (int)((next_file_offset % iter->read_size) /
- iter->write_size);
- iter->buffer_desc[block & 0x01].dirty[sect_idx] = 1;
- }
-
- return ret;
-}
-
-int exfat_de_iter_flush(struct exfat_de_iter *iter)
-{
- if (write_block(iter, 0) || write_block(iter, 1))
- return -EIO;
- return 0;
-}
-
-/*
- * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get
- * since the last call of exfat_de_iter_advance
- */
-int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries)
-{
- if (skip_dentries != iter->max_skip_dentries)
- return -EINVAL;
-
- iter->max_skip_dentries = 0;
- iter->de_file_offset = iter->de_file_offset +
- skip_dentries * sizeof(struct exfat_dentry);
- return 0;
-}
-
-off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter)
-{
- return iter->de_file_offset;
-}
diff --git a/fsck/fsck.c b/fsck/fsck.c
index ba454e6..52b3f1b 100644
--- a/fsck/fsck.c
+++ b/fsck/fsck.c
@@ -15,8 +15,10 @@
#include "exfat_ondisk.h"
#include "libexfat.h"
-#include "fsck.h"
#include "repair.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+#include "fsck.h"
struct fsck_user_input {
struct exfat_user_input ei;
@@ -25,24 +27,6 @@ struct fsck_user_input {
#define EXFAT_MAX_UPCASE_CHARS 0x10000
-#ifdef WORDS_BIGENDIAN
-typedef __u8 bitmap_t;
-#else
-typedef __u32 bitmap_t;
-#endif
-
-#define BITS_PER (sizeof(bitmap_t) * 8)
-#define BIT_MASK(__c) (1 << ((__c) % BITS_PER))
-#define BIT_ENTRY(__c) ((__c) / BITS_PER)
-
-#define EXFAT_BITMAP_SIZE(__c_count) \
- (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t))
-#define EXFAT_BITMAP_GET(__bmap, __c) \
- (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c))
-#define EXFAT_BITMAP_SET(__bmap, __c) \
- (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \
- BIT_MASK(__c))
-
#define FSCK_EXIT_NO_ERRORS 0x00
#define FSCK_EXIT_CORRECTED 0x01
#define FSCK_EXIT_NEED_REBOOT 0x02
@@ -59,12 +43,7 @@ struct exfat_stat {
long fixed_count;
};
-struct path_resolve_ctx {
- struct exfat_inode *ancestors[255];
- __le16 utf16_path[PATH_MAX + 2];
- char local_path[PATH_MAX * MB_LEN_MAX + 1];
-};
-
+struct exfat_fsck exfat_fsck;
struct exfat_stat exfat_stat;
struct path_resolve_ctx path_resolve_ctx;
@@ -73,6 +52,7 @@ static struct option opts[] = {
{"repair-yes", no_argument, NULL, 'y' },
{"repair-no", no_argument, NULL, 'n' },
{"repair-auto", no_argument, NULL, 'p' },
+ {"rescue", no_argument, NULL, 's' },
{"version", no_argument, NULL, 'V' },
{"verbose", no_argument, NULL, 'v' },
{"help", no_argument, NULL, 'h' },
@@ -90,6 +70,7 @@ static void usage(char *name)
fprintf(stderr, "\t-p | --repair-auto Repair automatically\n");
fprintf(stderr, "\t-a Repair automatically\n");
fprintf(stderr, "\t-b | --ignore-bad-fs Try to recover even if exfat is not found\n");
+ fprintf(stderr, "\t-s | --rescue Assign orphaned clusters to files\n");
fprintf(stderr, "\t-V | --version Show version\n");
fprintf(stderr, "\t-v | --verbose Print debug\n");
fprintf(stderr, "\t-h | --help Show help\n");
@@ -99,319 +80,32 @@ static void usage(char *name)
#define fsck_err(parent, inode, fmt, ...) \
({ \
- resolve_path_parent(&path_resolve_ctx, \
+ exfat_resolve_path_parent(&path_resolve_ctx, \
parent, inode); \
exfat_err("ERROR: %s: " fmt, \
path_resolve_ctx.local_path, \
##__VA_ARGS__); \
})
-static struct exfat_inode *alloc_exfat_inode(__u16 attr)
-{
- struct exfat_inode *node;
- int size;
-
- size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE;
- node = (struct exfat_inode *)calloc(1, size);
- if (!node) {
- exfat_err("failed to allocate exfat_node\n");
- return NULL;
- }
-
- node->parent = NULL;
- INIT_LIST_HEAD(&node->children);
- INIT_LIST_HEAD(&node->sibling);
- INIT_LIST_HEAD(&node->list);
-
- node->last_pclus = EXFAT_EOF_CLUSTER;
- node->attr = attr;
- if (attr & ATTR_SUBDIR)
- exfat_stat.dir_count++;
- else
- exfat_stat.file_count++;
- return node;
-}
-
-static void free_exfat_inode(struct exfat_inode *node)
-{
- free(node);
-}
-
-static void inode_free_children(struct exfat_inode *dir, bool file_only)
-{
- struct exfat_inode *node, *i;
-
- list_for_each_entry_safe(node, i, &dir->children, sibling) {
- if (file_only) {
- if (!(node->attr & ATTR_SUBDIR)) {
- list_del(&node->sibling);
- free_exfat_inode(node);
- }
- } else {
- list_del(&node->sibling);
- list_del(&node->list);
- free_exfat_inode(node);
- }
- }
-}
-
-static void inode_free_file_children(struct exfat_inode *dir)
-{
- inode_free_children(dir, true);
-}
-
-/* delete @child and all ancestors that does not have
- * children
- */
-static void inode_free_ancestors(struct exfat_inode *child)
-{
- struct exfat_inode *parent;
-
- if (!list_empty(&child->children))
- return;
-
- do {
- if (!(child->attr & ATTR_SUBDIR)) {
- exfat_err("not directory.\n");
- return;
- }
-
- parent = child->parent;
- list_del(&child->sibling);
- free_exfat_inode(child);
-
- child = parent;
- } while (child && list_empty(&child->children));
-
- return;
-}
-
-static void free_exfat(struct exfat *exfat)
-{
- int i;
-
- if (exfat) {
- if (exfat->bs)
- free(exfat->bs);
- if (exfat->alloc_bitmap)
- free(exfat->alloc_bitmap);
- if (exfat->disk_bitmap)
- free(exfat->disk_bitmap);
- for (i = 0; i < 2; i++) {
- if (exfat->buffer_desc[i].buffer)
- free(exfat->buffer_desc[i].buffer);
- if (exfat->buffer_desc[i].dirty)
- free(exfat->buffer_desc[i].dirty);
- }
- free(exfat);
- }
-}
-
-static int init_exfat(struct exfat *exfat, struct pbr *bs)
-{
- int i;
-
- INIT_LIST_HEAD(&exfat->dir_list);
- exfat->bs = bs;
- exfat->clus_count = le32_to_cpu(bs->bsx.clu_count);
- exfat->clus_size = EXFAT_CLUSTER_SIZE(bs);
- exfat->sect_size = EXFAT_SECTOR_SIZE(bs);
-
- /* TODO: bitmap could be very large. */
- exfat->alloc_bitmap = (char *)calloc(1,
- EXFAT_BITMAP_SIZE(exfat->clus_count));
- if (!exfat->alloc_bitmap) {
- exfat_err("failed to allocate bitmap\n");
- goto err;
- }
-
- exfat->disk_bitmap = (char *)malloc(
- EXFAT_BITMAP_SIZE(exfat->clus_count));
- if (!exfat->disk_bitmap) {
- exfat_err("failed to allocate bitmap\n");
- goto err;
- }
-
- /* allocate cluster buffers */
- for (i = 0; i < 2; i++) {
- exfat->buffer_desc[i].buffer =
- (char *)malloc(exfat->clus_size);
- if (!exfat->buffer_desc[i].buffer)
- goto err;
- exfat->buffer_desc[i].dirty =
- (char *)calloc(
- (exfat->clus_size / exfat->sect_size), 1);
- if (!exfat->buffer_desc[i].dirty)
- goto err;
- }
- return 0;
-err:
- free_exfat(exfat);
- return -ENOMEM;
-}
-
-static void exfat_free_dir_list(struct exfat *exfat)
-{
- struct exfat_inode *dir, *i;
-
- list_for_each_entry_safe(dir, i, &exfat->dir_list, list) {
- inode_free_file_children(dir);
- list_del(&dir->list);
- free_exfat_inode(dir);
- }
-}
-
-/*
- * get references of ancestors that include @child until the count of
- * ancestors is not larger than @count and the count of characters of
- * their names is not larger than @max_char_len.
- * return true if root is reached.
- */
-bool get_ancestors(struct exfat_inode *child,
- struct exfat_inode **ancestors, int count,
- int max_char_len,
- int *ancestor_count)
-{
- struct exfat_inode *dir;
- int name_len, char_len;
- int root_depth, depth, i;
-
- root_depth = 0;
- char_len = 0;
- max_char_len += 1;
-
- dir = child;
- while (dir) {
- name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE);
- if (char_len + name_len > max_char_len)
- break;
-
- /* include '/' */
- char_len += name_len + 1;
- root_depth++;
-
- dir = dir->parent;
- }
-
- depth = MIN(root_depth, count);
-
- for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--)
- ancestors[i] = dir;
-
- *ancestor_count = depth;
- return dir == NULL;
-}
-
-static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
-{
- int depth, i;
- int name_len;
- __le16 *utf16_path;
- static const __le16 utf16_slash = cpu_to_le16(0x002F);
- static const __le16 utf16_null = cpu_to_le16(0x0000);
- size_t in_size;
-
- ctx->local_path[0] = '\0';
-
- get_ancestors(child,
- ctx->ancestors,
- sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]),
- PATH_MAX,
- &depth);
-
- utf16_path = ctx->utf16_path;
- for (i = 0; i < depth; i++) {
- name_len = exfat_utf16_len(ctx->ancestors[i]->name,
- NAME_BUFFER_SIZE);
- memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name,
- name_len * 2);
- utf16_path += name_len;
- memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash));
- utf16_path++;
- }
-
- if (depth > 0)
- utf16_path--;
- memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null));
- utf16_path++;
-
- in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16);
- return exfat_utf16_dec(ctx->utf16_path, in_size,
- ctx->local_path, sizeof(ctx->local_path));
-}
-
-static int resolve_path_parent(struct path_resolve_ctx *ctx,
- struct exfat_inode *parent, struct exfat_inode *child)
-{
- int ret;
- struct exfat_inode *old;
-
- old = child->parent;
- child->parent = parent;
-
- ret = resolve_path(ctx, child);
- child->parent = old;
- return ret;
-}
-
#define repair_file_ask(iter, inode, code, fmt, ...) \
({ \
- resolve_path_parent(&path_resolve_ctx, \
- (iter)->parent, inode); \
- exfat_repair_ask((iter)->exfat, code, \
- "ERROR: %s: " fmt, \
- path_resolve_ctx.local_path, \
- ##__VA_ARGS__); \
+ if (inode) \
+ exfat_resolve_path_parent(&path_resolve_ctx, \
+ (iter)->parent, inode); \
+ else \
+ exfat_resolve_path(&path_resolve_ctx, \
+ (iter)->parent); \
+ exfat_repair_ask(&exfat_fsck, code, \
+ "ERROR: %s: " fmt " at %#" PRIx64, \
+ path_resolve_ctx.local_path, \
+ ##__VA_ARGS__, \
+ exfat_de_iter_device_offset(iter)); \
})
-static inline bool heap_clus(struct exfat *exfat, clus_t clus)
-{
- return clus >= EXFAT_FIRST_CLUSTER &&
- (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count;
-}
-
-int get_next_clus(struct exfat *exfat, struct exfat_inode *node,
- clus_t clus, clus_t *next)
-{
- off_t offset;
-
- *next = EXFAT_EOF_CLUSTER;
-
- if (!heap_clus(exfat, clus))
- return -EINVAL;
-
- if (node->is_contiguous) {
- *next = clus + 1;
- return 0;
- }
-
- offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) <<
- exfat->bs->bsx.sect_size_bits;
- offset += sizeof(clus_t) * clus;
-
- if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset)
- != sizeof(*next))
- return -EIO;
- *next = le32_to_cpu(*next);
- return 0;
-}
-
-static int set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus)
-{
- off_t offset;
-
- offset = le32_to_cpu(exfat->bs->bsx.fat_offset) <<
- exfat->bs->bsx.sect_size_bits;
- offset += sizeof(clus_t) * clus;
-
- if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus),
- offset) != sizeof(next_clus))
- return -EIO;
- return 0;
-}
-
-static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
+static int check_clus_chain(struct exfat_de_iter *de_iter,
+ struct exfat_inode *node)
{
+ struct exfat *exfat = de_iter->exfat;
struct exfat_dentry *stream_de;
clus_t clus, prev, next;
uint64_t count, max_count;
@@ -426,9 +120,11 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
/* the first cluster is wrong */
if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) ||
- (node->size > 0 && !heap_clus(exfat, node->first_clus))) {
- if (repair_file_ask(&exfat->de_iter, node,
- ER_FILE_FIRST_CLUS, "first cluster is wrong"))
+ (node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_FIRST_CLUS,
+ "size %#" PRIx64 ", but the first cluster %#x",
+ node->size, node->first_clus))
goto truncate_file;
else
return -EINVAL;
@@ -438,11 +134,11 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
if (count >= max_count) {
if (node->is_contiguous)
break;
- if (repair_file_ask(&exfat->de_iter, node,
- ER_FILE_SMALLER_SIZE,
- "more clusters are allocated. "
- "truncate to %" PRIu64 " bytes",
- count * exfat->clus_size))
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_SMALLER_SIZE,
+ "more clusters are allocated. truncate to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
goto truncate_file;
else
return -EINVAL;
@@ -452,62 +148,67 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
* This cluster is already allocated. it may be shared with
* the other file, or there is a loop in cluster chain.
*/
- if (EXFAT_BITMAP_GET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER)) {
- if (repair_file_ask(&exfat->de_iter, node,
- ER_FILE_DUPLICATED_CLUS,
- "cluster is already allocated for "
- "the other file. truncated to %"
- PRIu64 " bytes",
- count * exfat->clus_size))
+ if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_DUPLICATED_CLUS,
+ "cluster is already allocated for the other file. truncated to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
goto truncate_file;
else
return -EINVAL;
}
- if (!EXFAT_BITMAP_GET(exfat->disk_bitmap,
- clus - EXFAT_FIRST_CLUSTER)) {
- if (repair_file_ask(&exfat->de_iter, node,
- ER_FILE_INVALID_CLUS,
- "cluster is marked as free. truncate to %" PRIu64 " bytes",
- count * exfat->clus_size))
- goto truncate_file;
-
- else
+ if (!exfat_bitmap_get(exfat->disk_bitmap, clus)) {
+ if (!repair_file_ask(de_iter, node,
+ ER_FILE_INVALID_CLUS,
+ "cluster %#x is marked as free",
+ clus))
return -EINVAL;
}
/* This cluster is allocated or not */
- if (get_next_clus(exfat, node, clus, &next))
+ if (exfat_get_inode_next_clus(exfat, node, clus, &next))
goto truncate_file;
- if (!node->is_contiguous) {
- if (!heap_clus(exfat, next) &&
- next != EXFAT_EOF_CLUSTER) {
- if (repair_file_ask(&exfat->de_iter, node,
- ER_FILE_INVALID_CLUS,
- "broken cluster chain. "
- "truncate to %"
- PRIu64 " bytes",
- count * exfat->clus_size))
+ if (next == EXFAT_BAD_CLUSTER) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_INVALID_CLUS,
+ "BAD cluster. truncate to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
+ goto truncate_file;
+ else
+ return -EINVAL;
+ } else if (!node->is_contiguous) {
+ if (next != EXFAT_EOF_CLUSTER &&
+ !exfat_heap_clus(exfat, next)) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_INVALID_CLUS,
+ "broken cluster chain. truncate to %"
+ PRIu64 " bytes",
+ (count + 1) * exfat->clus_size)) {
+ count++;
+ prev = clus;
+ exfat_bitmap_set(exfat->alloc_bitmap,
+ clus);
goto truncate_file;
-
- else
+ } else {
return -EINVAL;
+ }
}
}
count++;
- EXFAT_BITMAP_SET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER);
+ exfat_bitmap_set(exfat->alloc_bitmap, clus);
prev = clus;
clus = next;
}
if (count < max_count) {
- if (repair_file_ask(&exfat->de_iter, node,
- ER_FILE_LARGER_SIZE, "less clusters are allocated. "
- "truncates to %" PRIu64 " bytes",
- count * exfat->clus_size))
+ if (repair_file_ask(de_iter, node, ER_FILE_LARGER_SIZE,
+ "less clusters are allocated. truncates to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
goto truncate_file;
else
return -EINVAL;
@@ -516,74 +217,83 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
return 0;
truncate_file:
node->size = count * exfat->clus_size;
- if (!heap_clus(exfat, prev))
+ if (!exfat_heap_clus(exfat, prev))
node->first_clus = EXFAT_FREE_CLUSTER;
- exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de);
+ exfat_de_iter_get_dirty(de_iter, 1, &stream_de);
if (count * exfat->clus_size <
- le64_to_cpu(stream_de->stream_valid_size))
+ le64_to_cpu(stream_de->stream_valid_size))
stream_de->stream_valid_size = cpu_to_le64(
- count * exfat->clus_size);
- if (!heap_clus(exfat, prev))
+ count * exfat->clus_size);
+ if (!exfat_heap_clus(exfat, prev))
stream_de->stream_start_clu = EXFAT_FREE_CLUSTER;
stream_de->stream_size = cpu_to_le64(
- count * exfat->clus_size);
+ count * exfat->clus_size);
/* remaining clusters will be freed while FAT is compared with
* alloc_bitmap.
*/
- if (!node->is_contiguous && heap_clus(exfat, prev))
- return set_fat(exfat, prev, EXFAT_EOF_CLUSTER);
+ if (!node->is_contiguous && exfat_heap_clus(exfat, prev)) {
+ if (exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER))
+ return -EIO;
+ }
return 1;
}
-static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node,
- clus_t *clus_count)
+static int root_check_clus_chain(struct exfat *exfat,
+ struct exfat_inode *node,
+ clus_t *clus_count)
{
- clus_t clus;
+ clus_t clus, next, prev = EXFAT_EOF_CLUSTER;
+
+ if (!exfat_heap_clus(exfat, node->first_clus))
+ goto out_trunc;
clus = node->first_clus;
*clus_count = 0;
do {
- if (!heap_clus(exfat, clus)) {
- exfat_err("/: bad cluster. 0x%x\n", clus);
- return false;
+ if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) {
+ if (exfat_repair_ask(&exfat_fsck,
+ ER_FILE_DUPLICATED_CLUS,
+ "ERROR: the cluster chain of root is cyclic"))
+ goto out_trunc;
+ return -EINVAL;
}
- if (EXFAT_BITMAP_GET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER)) {
- exfat_err("/: cluster is already allocated, or "
- "there is a loop in cluster chain\n");
- return false;
- }
+ exfat_bitmap_set(exfat->alloc_bitmap, clus);
- EXFAT_BITMAP_SET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER);
+ if (exfat_get_inode_next_clus(exfat, node, clus, &next)) {
+ exfat_err("ERROR: failed to read the fat entry of root");
+ goto out_trunc;
+ }
- if (get_next_clus(exfat, node, clus, &clus) != 0) {
- exfat_err("/: broken cluster chain\n");
- return false;
+ if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next)) {
+ if (exfat_repair_ask(&exfat_fsck,
+ ER_FILE_INVALID_CLUS,
+ "ERROR: the cluster chain of root is broken")) {
+ if (next != EXFAT_BAD_CLUSTER) {
+ prev = clus;
+ (*clus_count)++;
+ }
+ goto out_trunc;
+ }
+ return -EINVAL;
}
+ prev = clus;
+ clus = next;
(*clus_count)++;
} while (clus != EXFAT_EOF_CLUSTER);
- return true;
-}
-
-static off_t exfat_s2o(struct exfat *exfat, off_t sect)
-{
- return sect << exfat->bs->bsx.sect_size_bits;
-}
-
-off_t exfat_c2o(struct exfat *exfat, unsigned int clus)
-{
- if (clus < EXFAT_FIRST_CLUSTER)
- return ~0L;
- return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) +
- ((off_t)(clus - EXFAT_FIRST_CLUSTER) <<
- exfat->bs->bsx.sect_per_clus_bits));
+ return 0;
+out_trunc:
+ if (!exfat_heap_clus(exfat, prev)) {
+ exfat_err("ERROR: the start cluster of root is wrong\n");
+ return -EINVAL;
+ }
+ node->size = *clus_count * exfat->clus_size;
+ return exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER);
}
static int boot_region_checksum(int dev_fd,
@@ -635,9 +345,6 @@ static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty)
{
uint16_t flags;
- if (!(exfat->options & FSCK_OPTS_REPAIR_WRITE))
- return 0;
-
flags = le16_to_cpu(exfat->bs->bsx.vol_flags);
if (dirty)
flags |= 0x02;
@@ -784,7 +491,9 @@ free_sector:
return ret;
}
-static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
+static int exfat_boot_region_check(struct exfat_blk_dev *blkdev,
+ struct pbr **bs,
+ bool ignore_bad_fs_name)
{
struct pbr *boot_sect;
unsigned int sect_size;
@@ -795,7 +504,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
if (boot_sect == NULL)
return -ENOMEM;
- if (exfat_read(exfat->blk_dev->dev_fd, boot_sect,
+ if (exfat_read(blkdev->dev_fd, boot_sect,
sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) {
exfat_err("failed to read Main boot sector\n");
free(boot_sect);
@@ -803,7 +512,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
}
if (memcmp(boot_sect->bpb.oem_name, "EXFAT ", 8) != 0 &&
- !(exfat->options & FSCK_OPTS_IGNORE_BAD_FS_NAME)) {
+ !ignore_bad_fs_name) {
exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n");
free(boot_sect);
return -ENOTSUP;
@@ -813,16 +522,17 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
free(boot_sect);
/* check boot regions */
- ret = read_boot_region(exfat->blk_dev, bs,
+ ret = read_boot_region(blkdev, bs,
BOOT_SEC_IDX, sect_size, true);
- if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION,
- "boot region is corrupted. try to restore the region from backup"
+ if (ret == -EINVAL &&
+ exfat_repair_ask(&exfat_fsck, ER_BS_BOOT_REGION,
+ "boot region is corrupted. try to restore the region from backup"
)) {
const unsigned int sector_sizes[] = {512, 4096, 1024, 2048};
unsigned int i;
if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) {
- ret = read_boot_region(exfat->blk_dev, bs,
+ ret = read_boot_region(blkdev, bs,
BACKUP_BOOT_SEC_IDX, sect_size,
false);
if (!ret)
@@ -833,7 +543,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
if (sector_sizes[i] == sect_size)
continue;
- ret = read_boot_region(exfat->blk_dev, bs,
+ ret = read_boot_region(blkdev, bs,
BACKUP_BOOT_SEC_IDX,
sector_sizes[i], false);
if (!ret) {
@@ -846,7 +556,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
return ret;
restore:
- ret = restore_boot_region(exfat->blk_dev, sect_size);
+ ret = restore_boot_region(blkdev, sect_size);
if (ret) {
exfat_err("failed to restore boot region from backup\n");
free(*bs);
@@ -855,38 +565,20 @@ restore:
return ret;
}
-static void dentry_calc_checksum(struct exfat_dentry *dentry,
- __le16 *checksum, bool primary)
+static uint16_t file_calc_checksum(struct exfat_de_iter *iter)
{
- unsigned int i;
- uint8_t *bytes;
-
- bytes = (uint8_t *)dentry;
-
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0];
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1];
-
- i = primary ? 4 : 2;
- for (; i < sizeof(*dentry); i++) {
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i];
- }
-}
-
-static __le16 file_calc_checksum(struct exfat_de_iter *iter)
-{
- __le16 checksum;
+ uint16_t checksum;
struct exfat_dentry *file_de, *de;
int i;
checksum = 0;
exfat_de_iter_get(iter, 0, &file_de);
- dentry_calc_checksum(file_de, &checksum, true);
+ exfat_calc_dentry_checksum(file_de, &checksum, true);
for (i = 1; i <= file_de->file_num_ext; i++) {
exfat_de_iter_get(iter, i, &de);
- dentry_calc_checksum(de, &checksum, false);
+ exfat_calc_dentry_checksum(de, &checksum, false);
}
-
return checksum;
}
@@ -902,7 +594,7 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
uint16_t checksum;
bool valid = true;
- ret = check_clus_chain(exfat, node);
+ ret = check_clus_chain(iter, node);
if (ret < 0)
return ret;
@@ -916,7 +608,7 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
if (node->size == 0 && node->is_contiguous) {
if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT,
- "empty, but has no Fat chain\n")) {
+ "empty, but has no Fat chain")) {
exfat_de_iter_get_dirty(iter, 1, &dentry);
dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS;
ret = 1;
@@ -935,61 +627,116 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
checksum = file_calc_checksum(iter);
exfat_de_iter_get(iter, 0, &dentry);
if (checksum != le16_to_cpu(dentry->file_checksum)) {
- if (repair_file_ask(iter, node, ER_DE_CHECKSUM,
- "the checksum of a file is wrong")) {
- exfat_de_iter_get_dirty(iter, 0, &dentry);
- dentry->file_checksum = cpu_to_le16(checksum);
- ret = 1;
- } else
- valid = false;
+ exfat_de_iter_get_dirty(iter, 0, &dentry);
+ dentry->file_checksum = cpu_to_le16(checksum);
+ ret = 1;
}
return valid ? ret : -EINVAL;
}
-static int read_file_dentries(struct exfat_de_iter *iter,
- struct exfat_inode **new_node, int *skip_dentries)
+static int check_name_dentry_set(struct exfat_de_iter *iter,
+ struct exfat_inode *inode)
{
- struct exfat_dentry *file_de, *stream_de, *name_de;
- struct exfat_inode *node;
- int i, ret;
+ struct exfat_dentry *stream_de;
+ size_t name_len;
+ __u16 hash;
+
+ exfat_de_iter_get(iter, 1, &stream_de);
+
+ name_len = exfat_utf16_len(inode->name, NAME_BUFFER_SIZE);
+ if (stream_de->stream_name_len != name_len) {
+ if (repair_file_ask(iter, NULL, ER_DE_NAME_LEN,
+ "the name length of a file is wrong")) {
+ exfat_de_iter_get_dirty(iter, 1, &stream_de);
+ stream_de->stream_name_len = (__u8)name_len;
+ } else {
+ return -EINVAL;
+ }
+ }
- /* TODO: mtime, atime, ... */
+ hash = exfat_calc_name_hash(iter->exfat, inode->name, (int)name_len);
+ if (cpu_to_le16(hash) != stream_de->stream_name_hash) {
+ if (repair_file_ask(iter, NULL, ER_DE_NAME_HASH,
+ "the name hash of a file is wrong")) {
+ exfat_de_iter_get_dirty(iter, 1, &stream_de);
+ stream_de->stream_name_hash = cpu_to_le16(hash);
+ } else {
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int read_file_dentry_set(struct exfat_de_iter *iter,
+ struct exfat_inode **new_node, int *skip_dentries)
+{
+ struct exfat_dentry *file_de, *stream_de, *dentry;
+ struct exfat_inode *node = NULL;
+ int i, ret;
+ bool need_delete = false;
+ uint16_t checksum;
ret = exfat_de_iter_get(iter, 0, &file_de);
if (ret || file_de->type != EXFAT_FILE) {
- exfat_err("failed to get file dentry. %d\n", ret);
+ exfat_err("failed to get file dentry\n");
return -EINVAL;
}
+
+ checksum = file_calc_checksum(iter);
+ if (checksum != le16_to_cpu(file_de->file_checksum)) {
+ if (repair_file_ask(iter, NULL, ER_DE_CHECKSUM,
+ "the checksum of a file is wrong"))
+ need_delete = true;
+ *skip_dentries = 1;
+ goto skip_dset;
+ }
+
+ if (file_de->file_num_ext < 2) {
+ if (repair_file_ask(iter, NULL, ER_DE_SECONDARY_COUNT,
+ "a file has too few secondary count. %d",
+ file_de->file_num_ext))
+ need_delete = true;
+ *skip_dentries = 1;
+ goto skip_dset;
+ }
+
ret = exfat_de_iter_get(iter, 1, &stream_de);
if (ret || stream_de->type != EXFAT_STREAM) {
- exfat_err("failed to get stream dentry. %d\n", ret);
- return -EINVAL;
+ if (repair_file_ask(iter, NULL, ER_DE_STREAM,
+ "failed to get stream dentry"))
+ need_delete = true;
+ *skip_dentries = 2;
+ goto skip_dset;
}
*new_node = NULL;
- node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr));
+ node = exfat_alloc_inode(le16_to_cpu(file_de->file_attr));
if (!node)
return -ENOMEM;
- if (file_de->file_num_ext < 2) {
- exfat_err("too few secondary count. %d\n",
- file_de->file_num_ext);
- free_exfat_inode(node);
- return -EINVAL;
- }
-
for (i = 2; i <= file_de->file_num_ext; i++) {
- ret = exfat_de_iter_get(iter, i, &name_de);
- if (ret || name_de->type != EXFAT_NAME) {
- exfat_err("failed to get name dentry. %d\n", ret);
- ret = -EINVAL;
- goto err;
+ ret = exfat_de_iter_get(iter, i, &dentry);
+ if (ret || dentry->type != EXFAT_NAME) {
+ if (i > 2 && repair_file_ask(iter, NULL, ER_DE_NAME,
+ "failed to get name dentry")) {
+ exfat_de_iter_get_dirty(iter, 0, &file_de);
+ file_de->file_num_ext = i - 1;
+ break;
+ }
+ *skip_dentries = i + 1;
+ goto skip_dset;
}
memcpy(node->name +
- (i-2) * ENTRY_NAME_MAX, name_de->name_unicode,
- sizeof(name_de->name_unicode));
+ (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode,
+ sizeof(dentry->name_unicode));
+ }
+
+ ret = check_name_dentry_set(iter, node);
+ if (ret) {
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
}
node->first_clus = le32_to_cpu(stream_de->stream_start_clu);
@@ -998,27 +745,41 @@ static int read_file_dentries(struct exfat_de_iter *iter,
node->size = le64_to_cpu(stream_de->stream_size);
if (node->size < le64_to_cpu(stream_de->stream_valid_size)) {
+ *skip_dentries = file_de->file_num_ext + 1;
if (repair_file_ask(iter, node, ER_FILE_VALID_SIZE,
- "valid size %" PRIu64 " greater than size %" PRIu64,
- le64_to_cpu(stream_de->stream_valid_size),
- node->size)) {
+ "valid size %" PRIu64 " greater than size %" PRIu64,
+ le64_to_cpu(stream_de->stream_valid_size),
+ node->size)) {
exfat_de_iter_get_dirty(iter, 1, &stream_de);
stream_de->stream_valid_size =
stream_de->stream_size;
} else {
- ret = -EINVAL;
- goto err;
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
}
}
*skip_dentries = (file_de->file_num_ext + 1);
*new_node = node;
return 0;
-err:
- *skip_dentries = 0;
+skip_dset:
+ if (need_delete) {
+ exfat_de_iter_get_dirty(iter, 0, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ for (i = 1; i < *skip_dentries; i++) {
+ exfat_de_iter_get(iter, i, &dentry);
+ if (dentry->type == EXFAT_FILE)
+ break;
+ if (need_delete) {
+ exfat_de_iter_get_dirty(iter, i, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ }
+ *skip_dentries = i;
*new_node = NULL;
- free_exfat_inode(node);
- return ret;
+ exfat_free_inode(node);
+ return need_delete ? 1 : -EINVAL;
}
static int read_file(struct exfat_de_iter *de_iter,
@@ -1029,75 +790,78 @@ static int read_file(struct exfat_de_iter *de_iter,
*new_node = NULL;
- ret = read_file_dentries(de_iter, &node, dentry_count);
+ ret = read_file_dentry_set(de_iter, &node, dentry_count);
if (ret)
return ret;
ret = check_inode(de_iter, node);
if (ret < 0) {
- free_exfat_inode(node);
+ exfat_free_inode(node);
return -EINVAL;
}
+ if (node->attr & ATTR_SUBDIR)
+ exfat_stat.dir_count++;
+ else
+ exfat_stat.file_count++;
*new_node = node;
return ret;
}
-static bool read_volume_label(struct exfat_de_iter *iter)
+static int read_volume_label(struct exfat *exfat)
{
- struct exfat *exfat;
struct exfat_dentry *dentry;
+ int err;
__le16 disk_label[VOLUME_LABEL_MAX_LEN];
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_VOLUME,
+ .in.filter = NULL,
+ };
- exfat = iter->exfat;
- if (exfat_de_iter_get(iter, 0, &dentry))
- return false;
+ err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (err)
+ return err;
+
+ dentry = filter.out.dentry_set;
if (dentry->vol_char_cnt == 0)
- return true;
+ goto out;
if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) {
exfat_err("too long label. %d\n", dentry->vol_char_cnt);
- return false;
+ err = -EINVAL;
+ goto out;
}
memcpy(disk_label, dentry->vol_label, sizeof(disk_label));
if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2,
exfat->volume_label, sizeof(exfat->volume_label)) < 0) {
exfat_err("failed to decode volume label\n");
- return false;
+ err = -EINVAL;
+ goto out;
}
exfat_info("volume label [%s]\n", exfat->volume_label);
- return true;
-}
-
-static void exfat_bitmap_set_range(struct exfat *exfat,
- clus_t start_clus, clus_t count)
-{
- clus_t clus;
-
- if (!heap_clus(exfat, start_clus) ||
- !heap_clus(exfat, start_clus + count))
- return;
-
- clus = start_clus;
- while (clus < start_clus + count) {
- EXFAT_BITMAP_SET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER);
- clus++;
- }
+out:
+ free(filter.out.dentry_set);
+ return err;
}
-static bool read_bitmap(struct exfat_de_iter *iter)
+static int read_bitmap(struct exfat *exfat)
{
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_BITMAP,
+ .in.filter = NULL,
+ .in.param = NULL,
+ };
struct exfat_dentry *dentry;
- struct exfat *exfat;
+ int retval;
- exfat = iter->exfat;
- if (exfat_de_iter_get(iter, 0, &dentry))
- return false;
+ retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (retval)
+ return retval;
+ dentry = filter.out.dentry_set;
exfat_debug("start cluster %#x, size %#" PRIx64 "\n",
le32_to_cpu(dentry->bitmap_start_clu),
le64_to_cpu(dentry->bitmap_size));
@@ -1106,47 +870,81 @@ static bool read_bitmap(struct exfat_de_iter *iter)
DIV_ROUND_UP(exfat->clus_count, 8)) {
exfat_err("invalid size of allocation bitmap. 0x%" PRIx64 "\n",
le64_to_cpu(dentry->bitmap_size));
- return false;
+ return -EINVAL;
}
- if (!heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
+ if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
exfat_err("invalid start cluster of allocate bitmap. 0x%x\n",
le32_to_cpu(dentry->bitmap_start_clu));
- return false;
+ return -EINVAL;
}
exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
- exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu),
- DIV_ROUND_UP(exfat->disk_bitmap_size,
- exfat->clus_size));
+ exfat_bitmap_set_range(exfat, exfat->alloc_bitmap,
+ le64_to_cpu(dentry->bitmap_start_clu),
+ DIV_ROUND_UP(exfat->disk_bitmap_size,
+ exfat->clus_size));
+ free(filter.out.dentry_set);
if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap,
exfat->disk_bitmap_size,
exfat_c2o(exfat, exfat->disk_bitmap_clus)) !=
(ssize_t)exfat->disk_bitmap_size)
- return false;
+ return -EIO;
+ return 0;
+}
- return true;
+static int decompress_upcase_table(const __le16 *in_table, size_t in_len,
+ __u16 *out_table, size_t out_len)
+{
+ size_t i, k;
+ uint16_t ch;
+
+ if (in_len > out_len)
+ return -E2BIG;
+
+ for (k = 0; k < out_len; k++)
+ out_table[k] = k;
+
+ for (i = 0, k = 0; i < in_len && k < out_len; i++) {
+ ch = le16_to_cpu(in_table[i]);
+
+ if (ch == 0xFFFF && i + 1 < in_len) {
+ uint16_t len = le16_to_cpu(in_table[++i]);
+
+ k += len;
+ } else {
+ out_table[k++] = ch;
+ }
+ }
+ return 0;
}
-static bool read_upcase_table(struct exfat_de_iter *iter)
+static int read_upcase_table(struct exfat *exfat)
{
- struct exfat_dentry *dentry;
- struct exfat *exfat;
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_UPCASE,
+ .in.filter = NULL,
+ .in.param = NULL,
+ };
+ struct exfat_dentry *dentry = NULL;
+ __le16 *upcase = NULL;
+ int retval;
ssize_t size;
- __le16 *upcase;
__le32 checksum;
- exfat = iter->exfat;
+ retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (retval)
+ return retval;
- if (exfat_de_iter_get(iter, 0, &dentry))
- return false;
+ dentry = filter.out.dentry_set;
- if (!heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
+ if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
exfat_err("invalid start cluster of upcase table. 0x%x\n",
le32_to_cpu(dentry->upcase_start_clu));
- return false;
+ retval = -EINVAL;
+ goto out;
}
size = (ssize_t)le64_to_cpu(dentry->upcase_size);
@@ -1154,21 +952,23 @@ static bool read_upcase_table(struct exfat_de_iter *iter)
size == 0 || size % sizeof(__le16)) {
exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n",
le64_to_cpu(dentry->upcase_size));
- return false;
+ retval = -EINVAL;
+ goto out;
}
upcase = (__le16 *)malloc(size);
if (!upcase) {
exfat_err("failed to allocate upcase table\n");
- return false;
+ retval = -ENOMEM;
+ goto out;
}
if (exfat_read(exfat->blk_dev->dev_fd, upcase, size,
exfat_c2o(exfat,
le32_to_cpu(dentry->upcase_start_clu))) != size) {
exfat_err("failed to read upcase table\n");
- free(upcase);
- return false;
+ retval = -EIO;
+ goto out;
}
checksum = 0;
@@ -1176,28 +976,43 @@ static bool read_upcase_table(struct exfat_de_iter *iter)
if (le32_to_cpu(dentry->upcase_checksum) != checksum) {
exfat_err("corrupted upcase table %#x (expected: %#x)\n",
checksum, le32_to_cpu(dentry->upcase_checksum));
- free(upcase);
- return false;
+ retval = -EINVAL;
+ goto out;
}
- exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu),
- DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
- exfat->clus_size));
+ exfat_bitmap_set_range(exfat, exfat->alloc_bitmap,
+ le32_to_cpu(dentry->upcase_start_clu),
+ DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
+ exfat->clus_size));
- free(upcase);
- return true;
+ exfat->upcase_table = calloc(1,
+ sizeof(uint16_t) * EXFAT_UPCASE_TABLE_CHARS);
+ if (!exfat->upcase_table) {
+ retval = -EIO;
+ goto out;
+ }
+
+ decompress_upcase_table(upcase, size / 2,
+ exfat->upcase_table, EXFAT_UPCASE_TABLE_CHARS);
+out:
+ if (dentry)
+ free(dentry);
+ if (upcase)
+ free(upcase);
+ return retval;
}
-static int read_children(struct exfat *exfat, struct exfat_inode *dir)
+static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir)
{
- int ret;
+ struct exfat *exfat = fsck->exfat;
struct exfat_inode *node = NULL;
struct exfat_dentry *dentry;
- int dentry_count;
struct exfat_de_iter *de_iter;
+ int dentry_count;
+ int ret;
- de_iter = &exfat->de_iter;
- ret = exfat_de_iter_init(de_iter, exfat, dir);
+ de_iter = &fsck->de_iter;
+ ret = exfat_de_iter_init(de_iter, exfat, dir, fsck->buffer_desc);
if (ret == EOF)
return 0;
else if (ret)
@@ -1220,50 +1035,45 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
ret = read_file(de_iter, &node, &dentry_count);
if (ret < 0) {
exfat_stat.error_count++;
- goto err;
+ break;
} else if (ret) {
exfat_stat.error_count++;
exfat_stat.fixed_count++;
}
- if ((node->attr & ATTR_SUBDIR) && node->size) {
- node->parent = dir;
- list_add_tail(&node->sibling, &dir->children);
- list_add_tail(&node->list, &exfat->dir_list);
- } else
- free_exfat_inode(node);
- break;
- case EXFAT_VOLUME:
- if (!read_volume_label(de_iter)) {
- exfat_err("failed to verify volume label\n");
- ret = -EINVAL;
- goto err;
- }
- break;
- case EXFAT_BITMAP:
- if (!read_bitmap(de_iter)) {
- exfat_err(
- "failed to verify allocation bitmap\n");
- ret = -EINVAL;
- goto err;
- }
- break;
- case EXFAT_UPCASE:
- if (!read_upcase_table(de_iter)) {
- exfat_err(
- "failed to verify upcase table\n");
- ret = -EINVAL;
- goto err;
+ if (node) {
+ if ((node->attr & ATTR_SUBDIR) && node->size) {
+ node->parent = dir;
+ list_add_tail(&node->sibling,
+ &dir->children);
+ list_add_tail(&node->list,
+ &exfat->dir_list);
+ } else {
+ exfat_free_inode(node);
+ }
}
break;
case EXFAT_LAST:
goto out;
+ case EXFAT_VOLUME:
+ case EXFAT_BITMAP:
+ case EXFAT_UPCASE:
+ if (dir == exfat->root)
+ break;
+ /* fallthrough */
default:
if (IS_EXFAT_DELETED(dentry->type))
break;
- exfat_err("unknown entry type. 0x%x\n", dentry->type);
- ret = -EINVAL;
- goto err;
+ if (repair_file_ask(de_iter, NULL, ER_DE_UNKNOWN,
+ "unknown entry type %#x at %07" PRIx64,
+ dentry->type,
+ exfat_de_iter_file_offset(de_iter))) {
+ struct exfat_dentry *dentry;
+
+ exfat_de_iter_get_dirty(de_iter, 0, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ break;
}
exfat_de_iter_advance(de_iter, dentry_count);
@@ -1272,131 +1082,51 @@ out:
exfat_de_iter_flush(de_iter);
return 0;
err:
- inode_free_children(dir, false);
+ exfat_free_children(dir, false);
INIT_LIST_HEAD(&dir->children);
exfat_de_iter_flush(de_iter);
return ret;
}
-static int write_dirty_fat(struct exfat *exfat)
+/* write bitmap segments for clusters which are marked
+ * as free, but allocated to files.
+ */
+static int write_bitmap(struct exfat_fsck *fsck)
{
- struct buffer_desc *bd;
- off_t offset;
- ssize_t len;
- size_t read_size, write_size;
- clus_t clus, last_clus, clus_count, i;
- unsigned int idx;
-
- clus = 0;
- last_clus = le32_to_cpu(exfat->bs->bsx.clu_count) + 2;
- bd = exfat->buffer_desc;
- idx = 0;
- offset = le32_to_cpu(exfat->bs->bsx.fat_offset) *
- exfat->sect_size;
- read_size = exfat->clus_size;
- write_size = exfat->sect_size;
-
- while (clus < last_clus) {
- clus_count = MIN(read_size / sizeof(clus_t), last_clus - clus);
- len = exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer,
- clus_count * sizeof(clus_t), offset);
- if (len != (ssize_t)(sizeof(clus_t) * clus_count)) {
- exfat_err("failed to read fat entries, %zd\n", len);
- return -EIO;
+ struct exfat *exfat = fsck->exfat;
+ bitmap_t *disk_b, *alloc_b, *ohead_b;
+ off_t dev_offset;
+ unsigned int i, bitmap_bytes, byte_offset, write_bytes;
+
+ dev_offset = exfat_c2o(exfat, exfat->disk_bitmap_clus);
+ bitmap_bytes = EXFAT_BITMAP_SIZE(le32_to_cpu(exfat->bs->bsx.clu_count));
+
+ disk_b = (bitmap_t *)exfat->disk_bitmap;
+ alloc_b = (bitmap_t *)exfat->alloc_bitmap;
+ ohead_b = (bitmap_t *)exfat->ohead_bitmap;
+
+ for (i = 0; i < bitmap_bytes / sizeof(bitmap_t); i++)
+ ohead_b[i] = alloc_b[i] | disk_b[i];
+
+ i = 0;
+ while (i < bitmap_bytes / sizeof(bitmap_t)) {
+ if (ohead_b[i] == disk_b[i]) {
+ i++;
+ continue;
}
- /* TODO: read ahead */
-
- for (i = clus ? clus : EXFAT_FIRST_CLUSTER;
- i < clus + clus_count; i++) {
- if (!EXFAT_BITMAP_GET(exfat->alloc_bitmap,
- i - EXFAT_FIRST_CLUSTER) &&
- ((clus_t *)bd[idx].buffer)[i - clus] !=
- EXFAT_FREE_CLUSTER) {
- ((clus_t *)bd[idx].buffer)[i - clus] =
- EXFAT_FREE_CLUSTER;
- bd[idx].dirty[(i - clus) /
- (write_size / sizeof(clus_t))] = true;
- }
- }
-
- for (i = 0; i < read_size; i += write_size) {
- if (bd[idx].dirty[i / write_size]) {
- if (exfat_write(exfat->blk_dev->dev_fd,
- &bd[idx].buffer[i], write_size,
- offset + i) !=
- (ssize_t)write_size) {
- exfat_err("failed to write "
- "fat entries\n");
- return -EIO;
-
- }
- bd[idx].dirty[i / write_size] = false;
- }
- }
-
- idx ^= 0x01;
- clus = clus + clus_count;
- offset += len;
- }
- return 0;
-}
+ byte_offset = ((i * sizeof(bitmap_t)) / 512) * 512;
+ write_bytes = MIN(512, bitmap_bytes - byte_offset);
-static int write_dirty_bitmap(struct exfat *exfat)
-{
- struct buffer_desc *bd;
- off_t offset, last_offset, bitmap_offset;
- ssize_t len;
- ssize_t read_size, write_size, i, size;
- int idx;
-
- offset = exfat_c2o(exfat, exfat->disk_bitmap_clus);
- last_offset = offset + exfat->disk_bitmap_size;
- bitmap_offset = 0;
- read_size = exfat->clus_size;
- write_size = exfat->sect_size;
-
- bd = exfat->buffer_desc;
- idx = 0;
-
- while (offset < last_offset) {
- len = MIN(read_size, last_offset - offset);
- if (exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer,
- len, offset) != (ssize_t)len)
+ if (exfat_write(exfat->blk_dev->dev_fd,
+ (char *)ohead_b + byte_offset, write_bytes,
+ dev_offset + byte_offset) != (ssize_t)write_bytes)
return -EIO;
- /* TODO: read-ahead */
-
- for (i = 0; i < len; i += write_size) {
- size = MIN(write_size, len - i);
- if (memcmp(&bd[idx].buffer[i],
- exfat->alloc_bitmap + bitmap_offset + i,
- size)) {
- if (exfat_write(exfat->blk_dev->dev_fd,
- exfat->alloc_bitmap + bitmap_offset + i,
- size, offset + i) != size)
- return -EIO;
- }
- }
-
- idx ^= 0x01;
- offset += len;
- bitmap_offset += len;
+ i = (byte_offset + write_bytes) / sizeof(bitmap_t);
}
return 0;
-}
-static int reclaim_free_clusters(struct exfat *exfat)
-{
- if (write_dirty_fat(exfat)) {
- exfat_err("failed to write fat entries\n");
- return -EIO;
- }
- if (write_dirty_bitmap(exfat)) {
- exfat_err("failed to write bitmap\n");
- return -EIO;
- }
- return 0;
}
/*
@@ -1406,8 +1136,9 @@ static int reclaim_free_clusters(struct exfat *exfat)
* 2. free all of file exfat_nodes.
* 3. if the directory does not have children, free its exfat_node.
*/
-static int exfat_filesystem_check(struct exfat *exfat)
+static int exfat_filesystem_check(struct exfat_fsck *fsck)
{
+ struct exfat *exfat = fsck->exfat;
struct exfat_inode *dir;
int ret = 0, dir_errors;
@@ -1419,7 +1150,8 @@ static int exfat_filesystem_check(struct exfat *exfat)
list_add(&exfat->root->list, &exfat->dir_list);
while (!list_empty(&exfat->dir_list)) {
- dir = list_entry(exfat->dir_list.next, struct exfat_inode, list);
+ dir = list_entry(exfat->dir_list.next,
+ struct exfat_inode, list);
if (!(dir->attr & ATTR_SUBDIR)) {
fsck_err(dir->parent, dir,
@@ -1429,51 +1161,183 @@ static int exfat_filesystem_check(struct exfat *exfat)
goto out;
}
- dir_errors = read_children(exfat, dir);
+ dir_errors = read_children(fsck, dir);
if (dir_errors) {
- resolve_path(&path_resolve_ctx, dir);
+ exfat_resolve_path(&path_resolve_ctx, dir);
exfat_debug("failed to check dentries: %s\n",
path_resolve_ctx.local_path);
ret = dir_errors;
}
list_del(&dir->list);
- inode_free_file_children(dir);
- inode_free_ancestors(dir);
+ exfat_free_file_children(dir);
+ exfat_free_ancestors(dir);
}
out:
exfat_free_dir_list(exfat);
- exfat->root = NULL;
- if (exfat->dirty_fat && reclaim_free_clusters(exfat))
- return -EIO;
return ret;
}
static int exfat_root_dir_check(struct exfat *exfat)
{
struct exfat_inode *root;
- clus_t clus_count;
+ clus_t clus_count = 0;
+ int err;
- root = alloc_exfat_inode(ATTR_SUBDIR);
- if (!root) {
- exfat_err("failed to allocate memory\n");
+ root = exfat_alloc_inode(ATTR_SUBDIR);
+ if (!root)
return -ENOMEM;
- }
+ exfat->root = root;
root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
- if (!root_get_clus_count(exfat, root, &clus_count)) {
+ if (root_check_clus_chain(exfat, root, &clus_count)) {
exfat_err("failed to follow the cluster chain of root\n");
- free_exfat_inode(root);
+ exfat_free_inode(root);
+ exfat->root = NULL;
return -EINVAL;
}
root->size = clus_count * exfat->clus_size;
- exfat->root = root;
+ exfat_stat.dir_count++;
exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n",
root->first_clus, root->size);
+
+ if (read_volume_label(exfat))
+ exfat_err("failed to read volume label\n");
+
+ err = read_bitmap(exfat);
+ if (err) {
+ exfat_err("failed to read bitmap\n");
+ return -EINVAL;
+ }
+
+ err = read_upcase_table(exfat);
+ if (err) {
+ exfat_err("failed to read upcase table\n");
+ return -EINVAL;
+ }
+
+ root->dev_offset = 0;
+ err = exfat_build_file_dentry_set(exfat, " ", ATTR_SUBDIR,
+ &root->dentry_set, &root->dentry_count);
+ if (err) {
+ exfat_free_inode(root);
+ return -ENOMEM;
+ }
return 0;
}
+static int read_lostfound(struct exfat *exfat, struct exfat_inode **lostfound)
+{
+ struct exfat_lookup_filter filter;
+ struct exfat_inode *inode;
+ int err;
+
+ err = exfat_lookup_file(exfat, exfat->root, "LOST+FOUND", &filter);
+ if (err)
+ return err;
+
+ inode = exfat_alloc_inode(ATTR_SUBDIR);
+ if (!inode) {
+ free(filter.out.dentry_set);
+ return -ENOMEM;
+ }
+
+ inode->dentry_set = filter.out.dentry_set;
+ inode->dentry_count = filter.out.dentry_count;
+ inode->dev_offset = filter.out.dev_offset;
+
+ inode->first_clus =
+ le32_to_cpu(filter.out.dentry_set[1].dentry.stream.start_clu);
+ inode->size =
+ le64_to_cpu(filter.out.dentry_set[1].dentry.stream.size);
+
+ *lostfound = inode;
+ return 0;
+}
+
+/* Create temporary files under LOST+FOUND and assign orphan
+ * chains of clusters to these files.
+ */
+static int rescue_orphan_clusters(struct exfat_fsck *fsck)
+{
+ struct exfat *exfat = fsck->exfat;
+ struct exfat_inode *lostfound;
+ bitmap_t *disk_b, *alloc_b, *ohead_b;
+ struct exfat_dentry *dset;
+ clus_t clu_count, clu, s_clu, e_clu;
+ int err, dcount;
+ unsigned int i;
+ char name[] = "FILE0000000.CHK";
+ struct exfat_dentry_loc loc;
+ struct exfat_lookup_filter lf = {
+ .in.type = EXFAT_INVAL,
+ .in.filter = NULL,
+ };
+
+ err = read_lostfound(exfat, &lostfound);
+ if (err) {
+ exfat_err("failed to find LOST+FOUND\n");
+ return err;
+ }
+
+ /* get the last empty region of LOST+FOUND */
+ err = exfat_lookup_dentry_set(exfat, lostfound, &lf);
+ if (err && err != EOF) {
+ exfat_err("failed to find the last empty slot in LOST+FOUND\n");
+ goto out;
+ }
+
+ loc.parent = lostfound;
+ loc.file_offset = lf.out.file_offset;
+ loc.dev_offset = lf.out.dev_offset;
+
+ /* build a template dentry set */
+ err = exfat_build_file_dentry_set(exfat, name, 0, &dset, &dcount);
+ if (err) {
+ exfat_err("failed to create a temporary file in LOST+FOUNDn");
+ goto out;
+ }
+ dset[1].dentry.stream.flags |= EXFAT_SF_CONTIGUOUS;
+
+ clu_count = le32_to_cpu(exfat->bs->bsx.clu_count);
+
+ /* find clusters which are not marked as free, but not allocated to
+ * any files.
+ */
+ disk_b = (bitmap_t *)exfat->disk_bitmap;
+ alloc_b = (bitmap_t *)exfat->alloc_bitmap;
+ ohead_b = (bitmap_t *)exfat->ohead_bitmap;
+ for (i = 0; i < EXFAT_BITMAP_SIZE(clu_count) / sizeof(bitmap_t); i++)
+ ohead_b[i] = disk_b[i] & ~alloc_b[i];
+
+ /* create temporary files and allocate contiguous orphan clusters
+ * to each file.
+ */
+ for (clu = EXFAT_FIRST_CLUSTER; clu < clu_count + EXFAT_FIRST_CLUSTER &&
+ exfat_bitmap_find_one(exfat, exfat->ohead_bitmap, clu, &s_clu) == 0;) {
+ if (exfat_bitmap_find_zero(exfat, exfat->ohead_bitmap, s_clu, &e_clu))
+ e_clu = clu_count + EXFAT_FIRST_CLUSTER;
+ clu = e_clu;
+
+ snprintf(name, sizeof(name), "FILE%07d.CHK",
+ (unsigned int)(loc.file_offset >> 5));
+ err = exfat_update_file_dentry_set(exfat, dset, dcount,
+ name, s_clu, e_clu - s_clu);
+ if (err)
+ continue;
+ err = exfat_add_dentry_set(exfat, &loc, dset, dcount, true);
+ if (err)
+ continue;
+ }
+
+ free(dset);
+ err = 0;
+out:
+ exfat_free_inode(lostfound);
+ return err;
+}
+
static char *bytes_to_human_readable(size_t bytes)
{
static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"};
@@ -1497,9 +1361,11 @@ static char *bytes_to_human_readable(size_t bytes)
return buf;
}
-static void exfat_show_info(struct exfat *exfat, const char *dev_name,
- int errors)
+static void exfat_show_info(struct exfat_fsck *fsck, const char *dev_name)
{
+ struct exfat *exfat = fsck->exfat;
+ bool clean;
+
exfat_info("sector size: %s\n",
bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits));
exfat_info("cluster size: %s\n",
@@ -1507,19 +1373,21 @@ static void exfat_show_info(struct exfat *exfat, const char *dev_name,
exfat_info("volume size: %s\n",
bytes_to_human_readable(exfat->blk_dev->size));
+ clean = exfat_stat.error_count == 0 ||
+ exfat_stat.error_count == exfat_stat.fixed_count;
printf("%s: %s. directories %ld, files %ld\n", dev_name,
- errors ? "checking stopped" : "clean",
+ clean ? "clean" : "corrupted",
exfat_stat.dir_count, exfat_stat.file_count);
- if (errors || exfat->dirty)
+ if (exfat_stat.error_count)
printf("%s: files corrupted %ld, files fixed %ld\n", dev_name,
- exfat_stat.error_count, exfat_stat.fixed_count);
+ exfat_stat.error_count - exfat_stat.fixed_count,
+ exfat_stat.fixed_count);
}
int main(int argc, char * const argv[])
{
struct fsck_user_input ui;
struct exfat_blk_dev bd;
- struct exfat *exfat = NULL;
struct pbr *bs = NULL;
int c, ret, exit_code;
bool version_only = false;
@@ -1533,7 +1401,7 @@ int main(int argc, char * const argv[])
exfat_err("failed to init locale/codeset\n");
opterr = 0;
- while ((c = getopt_long(argc, argv, "arynpbVvh", opts, NULL)) != EOF) {
+ while ((c = getopt_long(argc, argv, "arynpbsVvh", opts, NULL)) != EOF) {
switch (c) {
case 'n':
if (ui.options & FSCK_OPTS_REPAIR_ALL)
@@ -1559,6 +1427,9 @@ int main(int argc, char * const argv[])
case 'b':
ui.options |= FSCK_OPTS_IGNORE_BAD_FS_NAME;
break;
+ case 's':
+ ui.options |= FSCK_OPTS_RESCUE_CLUS;
+ break;
case 'V':
version_only = true;
break;
@@ -1582,12 +1453,15 @@ int main(int argc, char * const argv[])
if (ui.options & FSCK_OPTS_REPAIR_WRITE)
ui.ei.writeable = true;
else {
- if (ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME)
+ if (ui.options & (FSCK_OPTS_IGNORE_BAD_FS_NAME |
+ FSCK_OPTS_RESCUE_CLUS))
usage(argv[0]);
ui.options |= FSCK_OPTS_REPAIR_NO;
ui.ei.writeable = false;
}
+ exfat_fsck.options = ui.options;
+
snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]);
ret = exfat_get_blk_dev_info(&ui.ei, &bd);
if (ret < 0) {
@@ -1595,62 +1469,100 @@ int main(int argc, char * const argv[])
return FSCK_EXIT_OPERATION_ERROR;
}
- exfat = (struct exfat *)calloc(1, sizeof(*exfat));
- if (!exfat) {
- exfat_err("failed to allocate exfat\n");
- ret = -ENOMEM;
+ ret = exfat_boot_region_check(&bd, &bs,
+ ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME ?
+ true : false);
+ if (ret)
goto err;
- }
- exfat->blk_dev = &bd;
- exfat->options = ui.options;
- ret = exfat_boot_region_check(exfat, &bs);
- if (ret)
+ exfat_fsck.exfat = exfat_alloc_exfat(&bd, bs);
+ if (!exfat_fsck.exfat) {
+ ret = -ENOMEM;
goto err;
+ }
- ret = init_exfat(exfat, bs);
- if (ret) {
- exfat = NULL;
+ exfat_fsck.buffer_desc = exfat_alloc_buffer(2,
+ exfat_fsck.exfat->clus_size,
+ exfat_fsck.exfat->sect_size);
+ if (!exfat_fsck.buffer_desc) {
+ ret = -ENOMEM;
goto err;
}
- if (exfat_mark_volume_dirty(exfat, true)) {
+ if ((exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) &&
+ exfat_mark_volume_dirty(exfat_fsck.exfat, true)) {
ret = -EIO;
goto err;
}
exfat_debug("verifying root directory...\n");
- ret = exfat_root_dir_check(exfat);
+ ret = exfat_root_dir_check(exfat_fsck.exfat);
if (ret) {
exfat_err("failed to verify root directory.\n");
goto out;
}
+ if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) {
+ ret = exfat_create_file(exfat_fsck.exfat,
+ exfat_fsck.exfat->root,
+ "LOST+FOUND",
+ ATTR_SUBDIR);
+ if (ret) {
+ exfat_err("failed to create lost+found directory\n");
+ goto out;
+ }
+
+ if (fsync(exfat_fsck.exfat->blk_dev->dev_fd) != 0) {
+ ret = -EIO;
+ exfat_err("failed to sync()\n");
+ goto out;
+ }
+ }
+
exfat_debug("verifying directory entries...\n");
- ret = exfat_filesystem_check(exfat);
+ ret = exfat_filesystem_check(&exfat_fsck);
if (ret)
goto out;
+ if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) {
+ rescue_orphan_clusters(&exfat_fsck);
+ exfat_fsck.dirty = true;
+ exfat_fsck.dirty_fat = true;
+ }
+
+ if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) {
+ ret = write_bitmap(&exfat_fsck);
+ if (ret) {
+ exfat_err("failed to write bitmap\n");
+ goto out;
+ }
+ }
+
if (ui.ei.writeable && fsync(bd.dev_fd)) {
exfat_err("failed to sync\n");
ret = -EIO;
goto out;
}
- exfat_mark_volume_dirty(exfat, false);
+ if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE)
+ exfat_mark_volume_dirty(exfat_fsck.exfat, false);
out:
- exfat_show_info(exfat, ui.ei.dev_name, ret);
+ exfat_show_info(&exfat_fsck, ui.ei.dev_name);
err:
- if (ret == -EINVAL)
- exit_code = FSCK_EXIT_ERRORS_LEFT;
- else if (ret)
+ if (ret && ret != -EINVAL)
exit_code = FSCK_EXIT_OPERATION_ERROR;
- else if (exfat->dirty)
+ else if (ret == -EINVAL ||
+ exfat_stat.error_count != exfat_stat.fixed_count)
+ exit_code = FSCK_EXIT_ERRORS_LEFT;
+ else if (exfat_fsck.dirty)
exit_code = FSCK_EXIT_CORRECTED;
else
exit_code = FSCK_EXIT_NO_ERRORS;
- free_exfat(exfat);
+ if (exfat_fsck.buffer_desc)
+ exfat_free_buffer(exfat_fsck.buffer_desc, 2);
+ if (exfat_fsck.exfat)
+ exfat_free_exfat(exfat_fsck.exfat);
close(bd.dev_fd);
return exit_code;
}
diff --git a/fsck/fsck.h b/fsck/fsck.h
index 56d3b3b..53003f6 100644
--- a/fsck/fsck.h
+++ b/fsck/fsck.h
@@ -7,46 +7,6 @@
#include "list.h"
-typedef __u32 clus_t;
-
-struct exfat_inode {
- struct exfat_inode *parent;
- struct list_head children;
- struct list_head sibling;
- struct list_head list;
- clus_t first_clus;
- clus_t last_lclus;
- clus_t last_pclus;
- __u16 attr;
- uint64_t size;
- bool is_contiguous;
- __le16 name[0]; /* only for directory */
-};
-
-#define EXFAT_NAME_MAX 255
-#define NAME_BUFFER_SIZE ((EXFAT_NAME_MAX+1)*2)
-
-struct buffer_desc {
- clus_t p_clus;
- unsigned int offset;
- char *buffer;
- char *dirty;
-};
-
-struct exfat_de_iter {
- struct exfat *exfat;
- struct exfat_inode *parent;
- struct buffer_desc *buffer_desc; /* cluster * 2 */
- clus_t ra_next_clus;
- unsigned int ra_begin_offset;
- unsigned int ra_partial_size;
- unsigned int read_size; /* cluster size */
- unsigned int write_size; /* sector size */
- off_t de_file_offset;
- off_t next_read_offset;
- int max_skip_dentries;
-};
-
enum fsck_ui_options {
FSCK_OPTS_REPAIR_ASK = 0x01,
FSCK_OPTS_REPAIR_YES = 0x02,
@@ -55,46 +15,21 @@ enum fsck_ui_options {
FSCK_OPTS_REPAIR_WRITE = 0x0b,
FSCK_OPTS_REPAIR_ALL = 0x0f,
FSCK_OPTS_IGNORE_BAD_FS_NAME = 0x10,
+ FSCK_OPTS_RESCUE_CLUS = 0x20,
};
-struct exfat {
+struct exfat;
+struct exfat_inode;
+
+struct exfat_fsck {
+ struct exfat *exfat;
+ struct exfat_de_iter de_iter;
+ struct buffer_desc *buffer_desc; /* cluster * 2 */
enum fsck_ui_options options;
bool dirty:1;
bool dirty_fat:1;
- struct exfat_blk_dev *blk_dev;
- struct pbr *bs;
- char volume_label[VOLUME_LABEL_BUFFER_SIZE];
- struct exfat_inode *root;
- struct list_head dir_list;
- clus_t clus_count;
- unsigned int clus_size;
- unsigned int sect_size;
- struct exfat_de_iter de_iter;
- struct buffer_desc buffer_desc[2]; /* cluster * 2 */
- char *alloc_bitmap;
- char *disk_bitmap;
- clus_t disk_bitmap_clus;
- unsigned int disk_bitmap_size;
};
-#define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits + \
- (pbr)->bsx.sect_per_clus_bits))
-#define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits)
-
-/* fsck.c */
off_t exfat_c2o(struct exfat *exfat, unsigned int clus);
-int get_next_clus(struct exfat *exfat, struct exfat_inode *node,
- clus_t clus, clus_t *next);
-
-/* de_iter.c */
-int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
- struct exfat_inode *dir);
-int exfat_de_iter_get(struct exfat_de_iter *iter,
- int ith, struct exfat_dentry **dentry);
-int exfat_de_iter_get_dirty(struct exfat_de_iter *iter,
- int ith, struct exfat_dentry **dentry);
-int exfat_de_iter_flush(struct exfat_de_iter *iter);
-int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries);
-off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter);
#endif
diff --git a/fsck/repair.c b/fsck/repair.c
index c79d379..65f4a9f 100644
--- a/fsck/repair.c
+++ b/fsck/repair.c
@@ -8,8 +8,10 @@
#include "exfat_ondisk.h"
#include "libexfat.h"
-#include "fsck.h"
#include "repair.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+#include "fsck.h"
struct exfat_repair_problem {
er_problem_code_t prcode;
@@ -25,23 +27,32 @@ struct exfat_repair_problem {
/* Prompt types */
#define ERP_FIX 0x00000001
#define ERP_TRUNCATE 0x00000002
+#define ERP_DELETE 0x00000003
static const char *prompts[] = {
"Repair",
"Fix",
"Truncate",
+ "Delete",
};
static struct exfat_repair_problem problems[] = {
{ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX},
{ER_BS_BOOT_REGION, 0, ERP_FIX},
- {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX},
+ {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE},
+ {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE},
+ {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE},
+ {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_DELETE},
+ {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE},
+ {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE},
+ {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX},
+ {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX},
{ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX},
- {ER_FILE_INVALID_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE},
- {ER_FILE_FIRST_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE},
- {ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE},
- {ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE},
- {ER_FILE_DUPLICATED_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE},
+ {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE},
+ {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE},
+ {ER_FILE_SMALLER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE},
+ {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE},
+ {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE},
{ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX},
};
@@ -57,19 +68,19 @@ static struct exfat_repair_problem *find_problem(er_problem_code_t prcode)
return NULL;
}
-static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr)
+static bool ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr)
{
bool repair = false;
char answer[8];
- if (exfat->options & FSCK_OPTS_REPAIR_NO ||
- pr->flags & ERF_DEFAULT_NO)
+ if (fsck->options & FSCK_OPTS_REPAIR_NO ||
+ pr->flags & ERF_DEFAULT_NO)
repair = false;
- else if (exfat->options & FSCK_OPTS_REPAIR_YES ||
- pr->flags & ERF_DEFAULT_YES)
+ else if (fsck->options & FSCK_OPTS_REPAIR_YES ||
+ pr->flags & ERF_DEFAULT_YES)
repair = true;
else {
- if (exfat->options & FSCK_OPTS_REPAIR_ASK) {
+ if (fsck->options & FSCK_OPTS_REPAIR_ASK) {
do {
printf(". %s (y/N)? ",
prompts[pr->prompt_type]);
@@ -83,8 +94,8 @@ static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr)
return false;
}
} while (1);
- } else if (exfat->options & FSCK_OPTS_REPAIR_AUTO &&
- pr->flags & ERF_PREEN_YES)
+ } else if (fsck->options & FSCK_OPTS_REPAIR_AUTO &&
+ pr->flags & ERF_PREEN_YES)
repair = true;
}
@@ -93,8 +104,8 @@ static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr)
return repair;
}
-bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
- const char *desc, ...)
+bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode,
+ const char *desc, ...)
{
struct exfat_repair_problem *pr = NULL;
va_list ap;
@@ -109,11 +120,12 @@ bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
vprintf(desc, ap);
va_end(ap);
- if (ask_repair(exfat, pr)) {
+ if (ask_repair(fsck, pr)) {
if (pr->prompt_type & ERP_TRUNCATE)
- exfat->dirty_fat = true;
- exfat->dirty = true;
+ fsck->dirty_fat = true;
+ fsck->dirty = true;
return true;
- } else
+ } else {
return false;
+ }
}
diff --git a/fsck/repair.h b/fsck/repair.h
index f7286b9..4e9a6bf 100644
--- a/fsck/repair.h
+++ b/fsck/repair.h
@@ -8,6 +8,13 @@
#define ER_BS_CHECKSUM 0x00000001
#define ER_BS_BOOT_REGION 0x00000002
#define ER_DE_CHECKSUM 0x00001001
+#define ER_DE_UNKNOWN 0x00001002
+#define ER_DE_FILE 0x00001010
+#define ER_DE_SECONDARY_COUNT 0x00001011
+#define ER_DE_STREAM 0x00001020
+#define ER_DE_NAME 0x00001030
+#define ER_DE_NAME_HASH 0x00001031
+#define ER_DE_NAME_LEN 0x00001032
#define ER_FILE_VALID_SIZE 0x00002001
#define ER_FILE_INVALID_CLUS 0x00002002
#define ER_FILE_FIRST_CLUS 0x00002003
@@ -17,8 +24,9 @@
#define ER_FILE_ZERO_NOFAT 0x00002007
typedef unsigned int er_problem_code_t;
+struct exfat_fsck;
-bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
- const char *fmt, ...);
+bool exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode,
+ const char *fmt, ...);
#endif
diff --git a/include/exfat_dir.h b/include/exfat_dir.h
new file mode 100644
index 0000000..8e55000
--- /dev/null
+++ b/include/exfat_dir.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+
+#ifndef _DIR_H_
+#define _DIR_H_
+
+struct exfat;
+struct exfat_inode;
+struct exfat_dentry_loc;
+struct buffer_desc;
+
+struct exfat_de_iter {
+ struct exfat *exfat;
+ struct exfat_inode *parent;
+ struct buffer_desc *buffer_desc; /* cluster * 2 */
+ __u32 ra_next_clus;
+ unsigned int ra_begin_offset;
+ unsigned int ra_partial_size;
+ unsigned int read_size; /* cluster size */
+ unsigned int write_size; /* sector size */
+ off_t de_file_offset;
+ off_t next_read_offset;
+ int max_skip_dentries;
+};
+
+struct exfat_lookup_filter {
+ struct {
+ uint8_t type;
+ /* return 0 if matched, return 1 if not matched,
+ * otherwise return errno
+ */
+ int (*filter)(struct exfat_de_iter *iter,
+ void *param, int *dentry_count);
+ void *param;
+ } in;
+ struct {
+ struct exfat_dentry *dentry_set;
+ int dentry_count;
+ off_t file_offset;
+ /* device offset where the dentry_set locates, or
+ * the empty slot locates or EOF if not found.
+ */
+ off_t dev_offset;
+ } out;
+};
+
+int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
+ struct exfat_inode *dir, struct buffer_desc *bd);
+int exfat_de_iter_get(struct exfat_de_iter *iter,
+ int ith, struct exfat_dentry **dentry);
+int exfat_de_iter_get_dirty(struct exfat_de_iter *iter,
+ int ith, struct exfat_dentry **dentry);
+int exfat_de_iter_flush(struct exfat_de_iter *iter);
+int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries);
+off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter);
+off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter);
+
+int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent,
+ struct exfat_lookup_filter *filter);
+int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent,
+ const char *name, struct exfat_lookup_filter *filter_out);
+
+int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent,
+ const char *name, unsigned short attr);
+int exfat_update_file_dentry_set(struct exfat *exfat,
+ struct exfat_dentry *dset, int dcount,
+ const char *name,
+ clus_t start_clu, clus_t ccount);
+int exfat_build_file_dentry_set(struct exfat *exfat, const char *name,
+ unsigned short attr, struct exfat_dentry **dentry_set,
+ int *dentry_count);
+int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc,
+ struct exfat_dentry *dset, int dcount,
+ bool need_next_loc);
+void exfat_calc_dentry_checksum(struct exfat_dentry *dentry,
+ uint16_t *checksum, bool primary);
+uint16_t exfat_calc_name_hash(struct exfat *exfat,
+ __le16 *name, int len);
+
+#endif
diff --git a/include/exfat_fs.h b/include/exfat_fs.h
new file mode 100644
index 0000000..c1cfff0
--- /dev/null
+++ b/include/exfat_fs.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2021 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#ifndef _EXFAT_FS_H_
+#define _EXFAT_FS_H_
+
+#include "list.h"
+
+struct exfat_dentry;
+
+struct exfat_inode {
+ struct exfat_inode *parent;
+ struct list_head children;
+ struct list_head sibling;
+ struct list_head list;
+ clus_t first_clus;
+ __u16 attr;
+ uint64_t size;
+ bool is_contiguous;
+ struct exfat_dentry *dentry_set;
+ int dentry_count;
+ off_t dev_offset;
+ __le16 name[0]; /* only for directory */
+};
+
+#define EXFAT_NAME_MAX 255
+#define NAME_BUFFER_SIZE ((EXFAT_NAME_MAX + 1) * 2)
+
+struct exfat {
+ struct exfat_blk_dev *blk_dev;
+ struct pbr *bs;
+ char volume_label[VOLUME_LABEL_BUFFER_SIZE];
+ struct exfat_inode *root;
+ struct list_head dir_list;
+ clus_t clus_count;
+ unsigned int clus_size;
+ unsigned int sect_size;
+ char *disk_bitmap;
+ char *alloc_bitmap;
+ char *ohead_bitmap;
+ clus_t disk_bitmap_clus;
+ unsigned int disk_bitmap_size;
+ __u16 *upcase_table;
+ clus_t start_clu;
+ char *zero_cluster;
+};
+
+struct exfat_dentry_loc {
+ struct exfat_inode *parent;
+ off_t file_offset;
+ off_t dev_offset;
+};
+
+struct path_resolve_ctx {
+ struct exfat_inode *ancestors[255];
+ __le16 utf16_path[PATH_MAX + 2];
+ char local_path[PATH_MAX * MB_LEN_MAX + 1];
+};
+
+struct buffer_desc {
+ __u32 p_clus;
+ unsigned int offset;
+ char *buffer;
+ char *dirty;
+};
+
+struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs);
+void exfat_free_exfat(struct exfat *exfat);
+
+struct exfat_inode *exfat_alloc_inode(__u16 attr);
+void exfat_free_inode(struct exfat_inode *node);
+
+void exfat_free_children(struct exfat_inode *dir, bool file_only);
+void exfat_free_file_children(struct exfat_inode *dir);
+void exfat_free_ancestors(struct exfat_inode *child);
+void exfat_free_dir_list(struct exfat *exfat);
+
+int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child);
+int exfat_resolve_path_parent(struct path_resolve_ctx *ctx,
+ struct exfat_inode *parent, struct exfat_inode *child);
+
+struct buffer_desc *exfat_alloc_buffer(int count,
+ unsigned int clu_size, unsigned int sect_size);
+void exfat_free_buffer(struct buffer_desc *bd, int count);
+#endif
diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h
index b3fc1fe..d1786bf 100644
--- a/include/exfat_ondisk.h
+++ b/include/exfat_ondisk.h
@@ -39,6 +39,7 @@
#define DENTRY_SIZE_BITS 5
/* exFAT allows 8388608(256MB) directory entries */
#define MAX_EXFAT_DENTRIES 8388608
+#define MIN_FILE_DENTRIES 3
/* dentry types */
#define MSDOS_DELETED 0xE5 /* deleted mark */
@@ -156,8 +157,10 @@ struct exfat_dentry {
__le16 access_date;
__u8 create_time_ms;
__u8 modify_time_ms;
- __u8 access_time_ms;
- __u8 reserved2[9];
+ __u8 create_tz;
+ __u8 modify_tz;
+ __u8 access_tz;
+ __u8 reserved2[7];
} __attribute__((packed)) file; /* file directory entry */
struct {
__u8 flags;
diff --git a/include/libexfat.h b/include/libexfat.h
index 53a82a1..b98f2b6 100644
--- a/include/libexfat.h
+++ b/include/libexfat.h
@@ -10,6 +10,8 @@
#include <wchar.h>
#include <limits.h>
+typedef __u32 clus_t;
+
#define KB (1024)
#define MB (1024*1024)
#define GB (1024UL*1024UL*1024UL)
@@ -35,6 +37,7 @@
#define VOLUME_LABEL_BUFFER_SIZE (VOLUME_LABEL_MAX_LEN*MB_LEN_MAX+1)
/* Upcase table macro */
+#define EXFAT_UPCASE_TABLE_CHARS (0x10000)
#define EXFAT_UPCASE_TABLE_SIZE (5836)
/* Flags for tune.exfat and exfatlabel */
@@ -45,6 +48,10 @@
#define EXFAT_MAX_SECTOR_SIZE 4096
+#define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits + \
+ (pbr)->bsx.sect_per_clus_bits))
+#define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits)
+
enum {
BOOT_SEC_IDX = 0,
EXBOOT_SEC_IDX,
@@ -79,12 +86,51 @@ struct exfat_user_input {
unsigned int volume_serial;
};
+struct exfat;
+struct exfat_inode;
+
+#ifdef WORDS_BIGENDIAN
+typedef __u8 bitmap_t;
+#else
+typedef __u32 bitmap_t;
+#endif
+
+#define BITS_PER (sizeof(bitmap_t) * 8)
+#define BIT_MASK(__c) (1 << ((__c) % BITS_PER))
+#define BIT_ENTRY(__c) ((__c) / BITS_PER)
+
+#define EXFAT_BITMAP_SIZE(__c_count) \
+ (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t))
+
+static inline bool exfat_bitmap_get(char *bmap, clus_t c)
+{
+ clus_t cc = c - EXFAT_FIRST_CLUSTER;
+
+ return ((bitmap_t *)(bmap))[BIT_ENTRY(cc)] & BIT_MASK(cc);
+}
+
+static inline void exfat_bitmap_set(char *bmap, clus_t c)
+{
+ clus_t cc = c - EXFAT_FIRST_CLUSTER;
+
+ (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] |= BIT_MASK(cc));
+}
+
+static inline void exfat_bitmap_clear(char *bmap, clus_t c)
+{
+ clus_t cc = c - EXFAT_FIRST_CLUSTER;
+ (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] &= ~BIT_MASK(cc));
+}
+
+void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap,
+ clus_t start_clus, clus_t count);
+int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next);
+int exfat_bitmap_find_one(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next);
+
void show_version(void);
-void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap,
- unsigned int clu);
-void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap,
- unsigned int clu);
wchar_t exfat_bad_char(wchar_t w);
void boot_calc_checksum(unsigned char *sector, unsigned short size,
bool is_boot_sec, __le32 *checksum);
@@ -114,7 +160,15 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd,
struct exfat_user_input *ui);
unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd,
unsigned int clu_off, unsigned int clu);
-
+int exfat_get_next_clus(struct exfat *exfat, clus_t clus, clus_t *next);
+int exfat_get_inode_next_clus(struct exfat *exfat, struct exfat_inode *node,
+ clus_t clus, clus_t *next);
+int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus);
+off_t exfat_s2o(struct exfat *exfat, off_t sect);
+off_t exfat_c2o(struct exfat *exfat, unsigned int clus);
+int exfat_o2c(struct exfat *exfat, off_t device_offset,
+ unsigned int *clu, unsigned int *offset);
+bool exfat_heap_clus(struct exfat *exfat, clus_t clus);
/*
* Exfat Print
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 5ea12db..d732cfd 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,4 +1,4 @@
AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
noinst_LIBRARIES = libexfat.a
-libexfat_a_SOURCES = libexfat.c
+libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c
diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c
new file mode 100644
index 0000000..55f1eb6
--- /dev/null
+++ b/lib/exfat_dir.c
@@ -0,0 +1,927 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+
+static struct path_resolve_ctx path_resolve_ctx;
+
+#define fsck_err(parent, inode, fmt, ...) \
+({ \
+ exfat_resolve_path_parent(&path_resolve_ctx, \
+ parent, inode); \
+ exfat_err("ERROR: %s: " fmt, \
+ path_resolve_ctx.local_path, \
+ ##__VA_ARGS__); \
+})
+
+static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block)
+{
+ off_t device_offset;
+ struct exfat *exfat = iter->exfat;
+ struct buffer_desc *desc;
+ unsigned int i;
+
+ desc = &iter->buffer_desc[block & 0x01];
+ device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset;
+
+ for (i = 0; i < iter->read_size / iter->write_size; i++) {
+ if (desc->dirty[i]) {
+ if (exfat_write(exfat->blk_dev->dev_fd,
+ desc->buffer + i * iter->write_size,
+ iter->write_size,
+ device_offset + i * iter->write_size)
+ != (ssize_t)iter->write_size)
+ return -EIO;
+ desc->dirty[i] = 0;
+ }
+ }
+ return 0;
+}
+
+static int read_ahead_first_blocks(struct exfat_de_iter *iter)
+{
+#ifdef POSIX_FADV_WILLNEED
+ struct exfat *exfat = iter->exfat;
+ clus_t clus_count;
+ unsigned int size;
+
+ clus_count = iter->parent->size / exfat->clus_size;
+
+ if (clus_count > 1) {
+ iter->ra_begin_offset = 0;
+ iter->ra_next_clus = 1;
+ size = exfat->clus_size;
+ } else {
+ iter->ra_begin_offset = 0;
+ iter->ra_next_clus = 0;
+ size = iter->ra_partial_size;
+ }
+ return posix_fadvise(exfat->blk_dev->dev_fd,
+ exfat_c2o(exfat, iter->parent->first_clus), size,
+ POSIX_FADV_WILLNEED);
+#else
+ return -ENOTSUP;
+#endif
+}
+
+/**
+ * read the next fragment in advance, and assume the fragment
+ * which covers @clus is already read.
+ */
+static int read_ahead_next_blocks(struct exfat_de_iter *iter,
+ clus_t clus, unsigned int offset, clus_t p_clus)
+{
+#ifdef POSIX_FADV_WILLNEED
+ struct exfat *exfat = iter->exfat;
+ off_t device_offset;
+ clus_t clus_count, ra_clus, ra_p_clus;
+ unsigned int size;
+ int ret = 0;
+
+ clus_count = iter->parent->size / exfat->clus_size;
+ if (clus + 1 < clus_count) {
+ ra_clus = clus + 1;
+ if (ra_clus == iter->ra_next_clus &&
+ offset >= iter->ra_begin_offset) {
+ ret = exfat_get_inode_next_clus(exfat, iter->parent,
+ p_clus, &ra_p_clus);
+ if (ret)
+ return ret;
+
+ if (ra_p_clus == EXFAT_EOF_CLUSTER)
+ return -EIO;
+
+ device_offset = exfat_c2o(exfat, ra_p_clus);
+ size = ra_clus + 1 < clus_count ?
+ exfat->clus_size : iter->ra_partial_size;
+ ret = posix_fadvise(exfat->blk_dev->dev_fd,
+ device_offset, size,
+ POSIX_FADV_WILLNEED);
+ iter->ra_next_clus = ra_clus + 1;
+ iter->ra_begin_offset = 0;
+ }
+ } else {
+ if (offset >= iter->ra_begin_offset &&
+ offset + iter->ra_partial_size <=
+ exfat->clus_size) {
+ device_offset = exfat_c2o(exfat, p_clus) +
+ offset + iter->ra_partial_size;
+ ret = posix_fadvise(exfat->blk_dev->dev_fd,
+ device_offset, iter->ra_partial_size,
+ POSIX_FADV_WILLNEED);
+ iter->ra_begin_offset =
+ offset + iter->ra_partial_size;
+ }
+ }
+
+ return ret;
+#else
+ return -ENOTSUP;
+#endif
+}
+
+static int read_ahead_next_dir_blocks(struct exfat_de_iter *iter)
+{
+#ifdef POSIX_FADV_WILLNEED
+ struct exfat *exfat = iter->exfat;
+ struct list_head *current;
+ struct exfat_inode *next_inode;
+ off_t offset;
+
+ if (list_empty(&exfat->dir_list))
+ return -EINVAL;
+
+ current = exfat->dir_list.next;
+ if (iter->parent == list_entry(current, struct exfat_inode, list) &&
+ current->next != &exfat->dir_list) {
+ next_inode = list_entry(current->next, struct exfat_inode,
+ list);
+ offset = exfat_c2o(exfat, next_inode->first_clus);
+ return posix_fadvise(exfat->blk_dev->dev_fd, offset,
+ iter->ra_partial_size,
+ POSIX_FADV_WILLNEED);
+ }
+
+ return 0;
+#else
+ return -ENOTSUP;
+#endif
+}
+
+static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block)
+{
+ struct exfat *exfat = iter->exfat;
+ struct buffer_desc *desc, *prev_desc;
+ off_t device_offset;
+ ssize_t ret;
+
+ desc = &iter->buffer_desc[block & 0x01];
+ if (block == 0) {
+ desc->p_clus = iter->parent->first_clus;
+ desc->offset = 0;
+ }
+
+ /* if the buffer already contains dirty dentries, write it */
+ if (write_block(iter, block))
+ return -EIO;
+
+ if (block > 0) {
+ if (block > iter->parent->size / iter->read_size)
+ return EOF;
+
+ prev_desc = &iter->buffer_desc[(block-1) & 0x01];
+ if (prev_desc->offset + 2 * iter->read_size <=
+ exfat->clus_size) {
+ desc->p_clus = prev_desc->p_clus;
+ desc->offset = prev_desc->offset + iter->read_size;
+ } else {
+ ret = exfat_get_inode_next_clus(exfat, iter->parent,
+ prev_desc->p_clus, &desc->p_clus);
+ desc->offset = 0;
+ if (ret)
+ return ret;
+ else if (desc->p_clus == EXFAT_EOF_CLUSTER)
+ return EOF;
+ }
+ }
+
+ device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset;
+ ret = exfat_read(exfat->blk_dev->dev_fd, desc->buffer,
+ iter->read_size, device_offset);
+ if (ret <= 0)
+ return ret;
+
+ /*
+ * if a buffer is filled with dentries, read blocks ahead of time,
+ * otherwise read blocks of the next directory in advance.
+ */
+ if (desc->buffer[iter->read_size - 32] != EXFAT_LAST)
+ read_ahead_next_blocks(iter,
+ (block * iter->read_size) / exfat->clus_size,
+ (block * iter->read_size) % exfat->clus_size,
+ desc->p_clus);
+ else
+ read_ahead_next_dir_blocks(iter);
+ return ret;
+}
+
+int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
+ struct exfat_inode *dir, struct buffer_desc *bd)
+{
+ iter->exfat = exfat;
+ iter->parent = dir;
+ iter->write_size = exfat->sect_size;
+ iter->read_size = exfat->clus_size <= 4*KB ? exfat->clus_size : 4*KB;
+ if (exfat->clus_size <= 32 * KB)
+ iter->ra_partial_size = MAX(4 * KB, exfat->clus_size / 2);
+ else
+ iter->ra_partial_size = exfat->clus_size / 4;
+ iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB);
+
+ iter->buffer_desc = bd;
+
+ iter->de_file_offset = 0;
+ iter->next_read_offset = iter->read_size;
+ iter->max_skip_dentries = 0;
+
+ if (iter->parent->size == 0)
+ return EOF;
+
+ read_ahead_first_blocks(iter);
+ if (read_block(iter, 0) != (ssize_t)iter->read_size) {
+ exfat_err("failed to read directory entries.\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int exfat_de_iter_get(struct exfat_de_iter *iter,
+ int ith, struct exfat_dentry **dentry)
+{
+ off_t next_de_file_offset;
+ ssize_t ret;
+ unsigned int block;
+
+ next_de_file_offset = iter->de_file_offset +
+ ith * sizeof(struct exfat_dentry);
+ block = (unsigned int)(next_de_file_offset / iter->read_size);
+
+ if (next_de_file_offset + sizeof(struct exfat_dentry) >
+ iter->parent->size)
+ return EOF;
+ /* the dentry must be in current, or next block which will be read */
+ if (block > iter->de_file_offset / iter->read_size + 1)
+ return -ERANGE;
+
+ /* read next cluster if needed */
+ if (next_de_file_offset >= iter->next_read_offset) {
+ ret = read_block(iter, block);
+ if (ret != (ssize_t)iter->read_size)
+ return ret;
+ iter->next_read_offset += iter->read_size;
+ }
+
+ if (ith + 1 > iter->max_skip_dentries)
+ iter->max_skip_dentries = ith + 1;
+
+ *dentry = (struct exfat_dentry *)
+ (iter->buffer_desc[block & 0x01].buffer +
+ next_de_file_offset % iter->read_size);
+ return 0;
+}
+
+int exfat_de_iter_get_dirty(struct exfat_de_iter *iter,
+ int ith, struct exfat_dentry **dentry)
+{
+ off_t next_file_offset;
+ unsigned int block;
+ int ret, sect_idx;
+
+ ret = exfat_de_iter_get(iter, ith, dentry);
+ if (!ret) {
+ next_file_offset = iter->de_file_offset +
+ ith * sizeof(struct exfat_dentry);
+ block = (unsigned int)(next_file_offset / iter->read_size);
+ sect_idx = (int)((next_file_offset % iter->read_size) /
+ iter->write_size);
+ iter->buffer_desc[block & 0x01].dirty[sect_idx] = 1;
+ }
+
+ return ret;
+}
+
+int exfat_de_iter_flush(struct exfat_de_iter *iter)
+{
+ if (write_block(iter, 0) || write_block(iter, 1))
+ return -EIO;
+ return 0;
+}
+
+int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries)
+{
+ if (skip_dentries > iter->max_skip_dentries)
+ return -EINVAL;
+
+ iter->max_skip_dentries = 0;
+ iter->de_file_offset = iter->de_file_offset +
+ skip_dentries * sizeof(struct exfat_dentry);
+ return 0;
+}
+
+off_t exfat_de_iter_device_offset(struct exfat_de_iter *iter)
+{
+ struct buffer_desc *bd;
+ unsigned int block;
+
+ if ((uint64_t)iter->de_file_offset >= iter->parent->size)
+ return EOF;
+
+ block = iter->de_file_offset / iter->read_size;
+ bd = &iter->buffer_desc[block & 0x01];
+ return exfat_c2o(iter->exfat, bd->p_clus) + bd->offset +
+ iter->de_file_offset % iter->read_size;
+}
+
+off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter)
+{
+ return iter->de_file_offset;
+}
+
+/*
+ * try to find the dentry set matched with @filter. this function
+ * doesn't verify the dentry set.
+ *
+ * if found, return 0. if not found, return EOF. otherwise return errno.
+ */
+int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent,
+ struct exfat_lookup_filter *filter)
+{
+ struct buffer_desc *bd = NULL;
+ struct exfat_dentry *dentry = NULL;
+ off_t free_file_offset = 0, free_dev_offset = 0;
+ struct exfat_de_iter de_iter;
+ int dentry_count;
+ int retval;
+ bool last_is_free = false;
+
+ bd = exfat_alloc_buffer(2, exfat->clus_size, exfat->sect_size);
+ if (!bd)
+ return -ENOMEM;
+
+ retval = exfat_de_iter_init(&de_iter, exfat, parent, bd);
+ if (retval == EOF || retval)
+ goto out;
+
+ filter->out.dentry_set = NULL;
+ while (1) {
+ retval = exfat_de_iter_get(&de_iter, 0, &dentry);
+ if (retval == EOF) {
+ break;
+ } else if (retval) {
+ fsck_err(parent->parent, parent,
+ "failed to get a dentry. %d\n", retval);
+ goto out;
+ }
+
+ dentry_count = 1;
+ if (dentry->type == filter->in.type) {
+ retval = 0;
+ if (filter->in.filter)
+ retval = filter->in.filter(&de_iter,
+ filter->in.param,
+ &dentry_count);
+
+ if (retval == 0) {
+ struct exfat_dentry *d;
+ int i;
+
+ filter->out.dentry_set = calloc(dentry_count,
+ sizeof(struct exfat_dentry));
+ if (!filter->out.dentry_set) {
+ retval = -ENOMEM;
+ goto out;
+ }
+ for (i = 0; i < dentry_count; i++) {
+ exfat_de_iter_get(&de_iter, i, &d);
+ memcpy(filter->out.dentry_set + i, d,
+ sizeof(struct exfat_dentry));
+ }
+ filter->out.dentry_count = dentry_count;
+ goto out;
+ } else if (retval < 0) {
+ goto out;
+ }
+ last_is_free = false;
+ } else if ((dentry->type == EXFAT_LAST ||
+ IS_EXFAT_DELETED(dentry->type))) {
+ if (!last_is_free) {
+ free_file_offset =
+ exfat_de_iter_file_offset(&de_iter);
+ free_dev_offset =
+ exfat_de_iter_device_offset(&de_iter);
+ last_is_free = true;
+ }
+ } else {
+ last_is_free = false;
+ }
+
+ exfat_de_iter_advance(&de_iter, dentry_count);
+ }
+
+out:
+ if (retval == 0) {
+ filter->out.file_offset =
+ exfat_de_iter_file_offset(&de_iter);
+ filter->out.dev_offset =
+ exfat_de_iter_device_offset(&de_iter);
+ } else if (retval == EOF && last_is_free) {
+ filter->out.file_offset = free_file_offset;
+ filter->out.dev_offset = free_dev_offset;
+ } else {
+ filter->out.file_offset = exfat_de_iter_file_offset(&de_iter);
+ filter->out.dev_offset = EOF;
+ }
+ if (bd)
+ exfat_free_buffer(bd, 2);
+ return retval;
+}
+
+static int filter_lookup_file(struct exfat_de_iter *de_iter,
+ void *param, int *dentry_count)
+{
+ struct exfat_dentry *file_de, *stream_de, *name_de;
+ __le16 *name;
+ int retval, name_len;
+ int i;
+
+ retval = exfat_de_iter_get(de_iter, 0, &file_de);
+ if (retval || file_de->type != EXFAT_FILE)
+ return 1;
+
+ retval = exfat_de_iter_get(de_iter, 1, &stream_de);
+ if (retval || stream_de->type != EXFAT_STREAM)
+ return 1;
+
+ name = (__le16 *)param;
+ name_len = (int)exfat_utf16_len(name, PATH_MAX);
+
+ if (file_de->dentry.file.num_ext <
+ 1 + (name_len + ENTRY_NAME_MAX - 1) / ENTRY_NAME_MAX)
+ return 1;
+
+ for (i = 2; i <= file_de->dentry.file.num_ext && name_len > 0; i++) {
+ int len;
+
+ retval = exfat_de_iter_get(de_iter, i, &name_de);
+ if (retval || name_de->type != EXFAT_NAME)
+ return 1;
+
+ len = MIN(name_len, ENTRY_NAME_MAX);
+ if (memcmp(name_de->dentry.name.unicode_0_14,
+ name, len * 2) != 0)
+ return 1;
+
+ name += len;
+ name_len -= len;
+ }
+
+ *dentry_count = i;
+ return 0;
+}
+
+int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent,
+ const char *name, struct exfat_lookup_filter *filter_out)
+{
+ int retval;
+ __le16 utf16_name[PATH_MAX + 2] = {0, };
+
+ retval = (int)exfat_utf16_enc(name, utf16_name, sizeof(utf16_name));
+ if (retval < 0)
+ return retval;
+
+ filter_out->in.type = EXFAT_FILE;
+ filter_out->in.filter = filter_lookup_file;
+ filter_out->in.param = utf16_name;
+
+ retval = exfat_lookup_dentry_set(exfat, parent, filter_out);
+ if (retval < 0)
+ return retval;
+
+ return 0;
+}
+
+void exfat_calc_dentry_checksum(struct exfat_dentry *dentry,
+ uint16_t *checksum, bool primary)
+{
+ unsigned int i;
+ uint8_t *bytes;
+
+ bytes = (uint8_t *)dentry;
+
+ *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0];
+ *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1];
+
+ i = primary ? 4 : 2;
+ for (; i < sizeof(*dentry); i++)
+ *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i];
+}
+
+static uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount)
+{
+ uint16_t checksum;
+ int i;
+
+ if (dcount < MIN_FILE_DENTRIES)
+ return 0;
+
+ checksum = 0;
+ exfat_calc_dentry_checksum(&dset[0], &checksum, true);
+ for (i = 1; i < dcount; i++)
+ exfat_calc_dentry_checksum(&dset[i], &checksum, false);
+ return checksum;
+}
+
+uint16_t exfat_calc_name_hash(struct exfat *exfat,
+ __le16 *name, int len)
+{
+ int i;
+ __le16 ch;
+ uint16_t chksum = 0;
+
+ for (i = 0; i < len; i++) {
+ ch = exfat->upcase_table[le16_to_cpu(name[i])];
+ ch = cpu_to_le16(ch);
+
+ chksum = ((chksum << 15) | (chksum >> 1)) + (ch & 0xFF);
+ chksum = ((chksum << 15) | (chksum >> 1)) + (ch >> 8);
+ }
+ return chksum;
+}
+
+static void unix_time_to_exfat_time(time_t unix_time, __u8 *tz, __le16 *date,
+ __le16 *time, __u8 *time_ms)
+{
+ struct tm tm;
+ __u16 t, d;
+
+ gmtime_r(&unix_time, &tm);
+ d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday;
+ t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1);
+
+ *tz = 0x80;
+ *date = cpu_to_le16(d);
+ *time = cpu_to_le16(t);
+ if (time_ms)
+ *time_ms = (tm.tm_sec & 1) * 100;
+}
+
+int exfat_build_file_dentry_set(struct exfat *exfat, const char *name,
+ unsigned short attr, struct exfat_dentry **dentry_set,
+ int *dentry_count)
+{
+ struct exfat_dentry *dset;
+ __le16 utf16_name[PATH_MAX + 2];
+ int retval;
+ int dcount, name_len, i;
+ __le16 e_date, e_time;
+ __u8 tz, e_time_ms;
+
+ memset(utf16_name, 0, sizeof(utf16_name));
+ retval = exfat_utf16_enc(name, utf16_name, sizeof(utf16_name));
+ if (retval < 0)
+ return retval;
+
+ name_len = retval / 2;
+ dcount = 2 + DIV_ROUND_UP(name_len, ENTRY_NAME_MAX);
+ dset = calloc(1, dcount * DENTRY_SIZE);
+ if (!dset)
+ return -ENOMEM;
+
+ dset[0].type = EXFAT_FILE;
+ dset[0].dentry.file.num_ext = dcount - 1;
+ dset[0].dentry.file.attr = cpu_to_le16(attr);
+
+ unix_time_to_exfat_time(time(NULL), &tz,
+ &e_date, &e_time, &e_time_ms);
+
+ dset[0].dentry.file.create_date = e_date;
+ dset[0].dentry.file.create_time = e_time;
+ dset[0].dentry.file.create_time_ms = e_time_ms;
+ dset[0].dentry.file.create_tz = tz;
+
+ dset[0].dentry.file.modify_date = e_date;
+ dset[0].dentry.file.modify_time = e_time;
+ dset[0].dentry.file.modify_time_ms = e_time_ms;
+ dset[0].dentry.file.modify_tz = tz;
+
+ dset[0].dentry.file.access_date = e_date;
+ dset[0].dentry.file.access_time = e_time;
+ dset[0].dentry.file.access_tz = tz;
+
+ dset[1].type = EXFAT_STREAM;
+ dset[1].dentry.stream.flags = 0x01;
+ dset[1].dentry.stream.name_len = (__u8)name_len;
+ dset[1].dentry.stream.name_hash =
+ cpu_to_le16(exfat_calc_name_hash(exfat, utf16_name, name_len));
+
+ for (i = 2; i < dcount; i++) {
+ dset[i].type = EXFAT_NAME;
+ memcpy(dset[i].dentry.name.unicode_0_14,
+ utf16_name + (i - 2) * ENTRY_NAME_MAX,
+ ENTRY_NAME_MAX * 2);
+ }
+
+ dset[0].dentry.file.checksum =
+ cpu_to_le16(calc_dentry_set_checksum(dset, dcount));
+
+ *dentry_set = dset;
+ *dentry_count = dcount;
+ return 0;
+}
+
+int exfat_update_file_dentry_set(struct exfat *exfat,
+ struct exfat_dentry *dset, int dcount,
+ const char *name,
+ clus_t start_clu, clus_t ccount)
+{
+ int i, name_len;
+ __le16 utf16_name[PATH_MAX + 2];
+
+ if (dset[0].type != EXFAT_FILE || dcount < MIN_FILE_DENTRIES)
+ return -EINVAL;
+
+ if (name) {
+ name_len = (int)exfat_utf16_enc(name,
+ utf16_name, sizeof(utf16_name));
+ if (name_len < 0)
+ return name_len;
+
+ name_len /= 2;
+ if (dcount != 2 + DIV_ROUND_UP(name_len, ENTRY_NAME_MAX))
+ return -EINVAL;
+
+ dset[1].dentry.stream.name_len = (__u8)name_len;
+ dset[1].dentry.stream.name_hash =
+ exfat_calc_name_hash(exfat, utf16_name, name_len);
+
+ for (i = 2; i < dcount; i++) {
+ dset[i].type = EXFAT_NAME;
+ memcpy(dset[i].dentry.name.unicode_0_14,
+ utf16_name + (i - 2) * ENTRY_NAME_MAX,
+ ENTRY_NAME_MAX * 2);
+ }
+ }
+
+ dset[1].dentry.stream.valid_size = cpu_to_le64(ccount * exfat->clus_size);
+ dset[1].dentry.stream.size = cpu_to_le64(ccount * exfat->clus_size);
+ if (start_clu)
+ dset[1].dentry.stream.start_clu = cpu_to_le32(start_clu);
+
+ dset[0].dentry.file.checksum =
+ cpu_to_le16(calc_dentry_set_checksum(dset, dcount));
+ return 0;
+}
+
+static int find_free_cluster(struct exfat *exfat,
+ clus_t start, clus_t *new_clu)
+{
+ clus_t end = le32_to_cpu(exfat->bs->bsx.clu_count) +
+ EXFAT_FIRST_CLUSTER;
+
+ if (!exfat_heap_clus(exfat, start))
+ return -EINVAL;
+
+ while (start < end) {
+ if (exfat_bitmap_find_zero(exfat, exfat->alloc_bitmap,
+ start, new_clu))
+ break;
+ if (!exfat_bitmap_get(exfat->disk_bitmap, *new_clu))
+ return 0;
+ start = *new_clu + 1;
+ }
+
+ end = start;
+ start = EXFAT_FIRST_CLUSTER;
+ while (start < end) {
+ if (exfat_bitmap_find_zero(exfat, exfat->alloc_bitmap,
+ start, new_clu))
+ goto out_nospc;
+ if (!exfat_bitmap_get(exfat->disk_bitmap, *new_clu))
+ return 0;
+ start = *new_clu + 1;
+ }
+
+out_nospc:
+ *new_clu = EXFAT_EOF_CLUSTER;
+ return -ENOSPC;
+}
+
+static int exfat_map_cluster(struct exfat *exfat, struct exfat_inode *inode,
+ off_t file_off, clus_t *mapped_clu)
+{
+ clus_t clu, next, count, last_count;
+
+ if (!exfat_heap_clus(exfat, inode->first_clus))
+ return -EINVAL;
+
+ clu = inode->first_clus;
+ next = EXFAT_EOF_CLUSTER;
+ count = 1;
+ if (file_off == EOF)
+ last_count = DIV_ROUND_UP(inode->size, exfat->clus_size);
+ else
+ last_count = file_off / exfat->clus_size + 1;
+
+ while (true) {
+ if (count * exfat->clus_size > inode->size)
+ return -EINVAL;
+
+ if (count == last_count) {
+ *mapped_clu = clu;
+ return 0;
+ }
+
+ if (exfat_get_inode_next_clus(exfat, inode, clu, &next))
+ return -EINVAL;
+
+ if (!exfat_heap_clus(exfat, clu))
+ return -EINVAL;
+
+ clu = next;
+ count++;
+ }
+ return -EINVAL;
+}
+
+static int exfat_write_dentry_set(struct exfat *exfat,
+ struct exfat_dentry *dset, int dcount,
+ off_t dev_off, off_t *next_dev_off)
+{
+ clus_t clus;
+ unsigned int clus_off, dent_len, first_half_len, sec_half_len;
+ off_t first_half_off, sec_half_off = 0;
+
+ if (exfat_o2c(exfat, dev_off, &clus, &clus_off))
+ return -ERANGE;
+
+ dent_len = dcount * DENTRY_SIZE;
+ first_half_len = MIN(dent_len, exfat->clus_size - clus_off);
+ sec_half_len = dent_len - first_half_len;
+
+ first_half_off = dev_off;
+ if (sec_half_len) {
+ clus_t next_clus;
+
+ if (exfat_get_next_clus(exfat, clus, &next_clus))
+ return -EIO;
+ if (!exfat_heap_clus(exfat, next_clus))
+ return -EINVAL;
+ sec_half_off = exfat_c2o(exfat, next_clus);
+ }
+
+ if (exfat_write(exfat->blk_dev->dev_fd, dset, first_half_len,
+ first_half_off) != (ssize_t)first_half_len)
+ return -EIO;
+
+ if (sec_half_len) {
+ dset = (struct exfat_dentry *)((char *)dset + first_half_len);
+ if (exfat_write(exfat->blk_dev->dev_fd, dset, sec_half_len,
+ sec_half_off) != (ssize_t)sec_half_len)
+ return -EIO;
+ }
+
+ if (next_dev_off) {
+ if (sec_half_len)
+ *next_dev_off = sec_half_off + sec_half_len;
+ else
+ *next_dev_off = first_half_off + first_half_len;
+ }
+ return 0;
+}
+
+static int exfat_alloc_cluster(struct exfat *exfat, struct exfat_inode *inode,
+ clus_t *new_clu)
+{
+ clus_t last_clu;
+ int err;
+ bool need_dset = inode != exfat->root;
+
+ if ((need_dset && !inode->dentry_set) || inode->is_contiguous)
+ return -EINVAL;
+
+ err = find_free_cluster(exfat, exfat->start_clu, new_clu);
+ if (err) {
+ exfat->start_clu = EXFAT_FIRST_CLUSTER;
+ exfat_err("failed to find an free cluster\n");
+ return -ENOSPC;
+ }
+ exfat->start_clu = *new_clu;
+
+ if (exfat_set_fat(exfat, *new_clu, EXFAT_EOF_CLUSTER))
+ return -EIO;
+
+ /* zero out the new cluster */
+ if (exfat_write(exfat->blk_dev->dev_fd, exfat->zero_cluster,
+ exfat->clus_size, exfat_c2o(exfat, *new_clu)) !=
+ (ssize_t)exfat->clus_size) {
+ exfat_err("failed to fill new cluster with zeroes\n");
+ return -EIO;
+ }
+
+ if (inode->size) {
+ err = exfat_map_cluster(exfat, inode, EOF, &last_clu);
+ if (err) {
+ exfat_err("failed to get the last cluster\n");
+ return err;
+ }
+
+ if (exfat_set_fat(exfat, last_clu, *new_clu))
+ return -EIO;
+
+ if (need_dset) {
+ err = exfat_update_file_dentry_set(exfat,
+ inode->dentry_set,
+ inode->dentry_count,
+ NULL, 0,
+ DIV_ROUND_UP(inode->size,
+ exfat->clus_size) + 1);
+ if (err)
+ return -EINVAL;
+ }
+ } else {
+ if (need_dset) {
+ err = exfat_update_file_dentry_set(exfat,
+ inode->dentry_set,
+ inode->dentry_count,
+ NULL, *new_clu, 1);
+ if (err)
+ return -EINVAL;
+ }
+ }
+
+ if (need_dset && exfat_write_dentry_set(exfat, inode->dentry_set,
+ inode->dentry_count,
+ inode->dev_offset, NULL))
+ return -EIO;
+
+ exfat_bitmap_set(exfat->alloc_bitmap, *new_clu);
+ if (inode->size == 0)
+ inode->first_clus = *new_clu;
+ inode->size += exfat->clus_size;
+ return 0;
+}
+
+int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc,
+ struct exfat_dentry *dset, int dcount,
+ bool need_next_loc)
+{
+ struct exfat_inode *parent = loc->parent;
+ off_t dev_off, next_dev_off;
+
+ if (parent->is_contiguous ||
+ (uint64_t)loc->file_offset > parent->size ||
+ (unsigned int)dcount * DENTRY_SIZE > exfat->clus_size)
+ return -EINVAL;
+
+ dev_off = loc->dev_offset;
+ if ((uint64_t)loc->file_offset + dcount * DENTRY_SIZE > parent->size) {
+ clus_t new_clus;
+
+ if (exfat_alloc_cluster(exfat, parent, &new_clus))
+ return -EIO;
+ if ((uint64_t)loc->file_offset == parent->size - exfat->clus_size)
+ dev_off = exfat_c2o(exfat, new_clus);
+ }
+
+ if (exfat_write_dentry_set(exfat, dset, dcount, dev_off, &next_dev_off))
+ return -EIO;
+
+ if (need_next_loc) {
+ loc->file_offset += dcount * DENTRY_SIZE;
+ loc->dev_offset = next_dev_off;
+ }
+ return 0;
+}
+
+int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent,
+ const char *name, unsigned short attr)
+{
+ struct exfat_dentry *dset;
+ int err, dcount;
+ struct exfat_lookup_filter filter;
+ struct exfat_dentry_loc loc;
+
+ err = exfat_lookup_file(exfat, parent, name, &filter);
+ if (err == 0) {
+ dset = filter.out.dentry_set;
+ dcount = filter.out.dentry_count;
+ if ((le16_to_cpu(dset->dentry.file.attr) & attr) != attr)
+ err = -EEXIST;
+ goto out;
+ }
+
+ err = exfat_build_file_dentry_set(exfat, name, attr,
+ &dset, &dcount);
+ if (err)
+ return err;
+
+ loc.parent = parent;
+ loc.file_offset = filter.out.file_offset;
+ loc.dev_offset = filter.out.dev_offset;
+ err = exfat_add_dentry_set(exfat, &loc, dset, dcount, false);
+out:
+ free(dset);
+ return err;
+}
diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c
new file mode 100644
index 0000000..41518b0
--- /dev/null
+++ b/lib/exfat_fs.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2022 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+
+struct exfat_inode *exfat_alloc_inode(__u16 attr)
+{
+ struct exfat_inode *node;
+ int size;
+
+ size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE;
+ node = (struct exfat_inode *)calloc(1, size);
+ if (!node) {
+ exfat_err("failed to allocate exfat_node\n");
+ return NULL;
+ }
+
+ node->parent = NULL;
+ INIT_LIST_HEAD(&node->children);
+ INIT_LIST_HEAD(&node->sibling);
+ INIT_LIST_HEAD(&node->list);
+
+ node->attr = attr;
+ return node;
+}
+
+void exfat_free_inode(struct exfat_inode *node)
+{
+ if (node) {
+ if (node->dentry_set)
+ free(node->dentry_set);
+ free(node);
+ }
+}
+
+void exfat_free_children(struct exfat_inode *dir, bool file_only)
+{
+ struct exfat_inode *node, *i;
+
+ list_for_each_entry_safe(node, i, &dir->children, sibling) {
+ if (file_only) {
+ if (!(node->attr & ATTR_SUBDIR)) {
+ list_del(&node->sibling);
+ exfat_free_inode(node);
+ }
+ } else {
+ list_del(&node->sibling);
+ list_del(&node->list);
+ exfat_free_inode(node);
+ }
+ }
+}
+
+void exfat_free_file_children(struct exfat_inode *dir)
+{
+ exfat_free_children(dir, true);
+}
+
+/* delete @child and all ancestors that does not have
+ * children
+ */
+void exfat_free_ancestors(struct exfat_inode *child)
+{
+ struct exfat_inode *parent;
+
+ while (child && list_empty(&child->children)) {
+ if (!child->parent || !(child->attr & ATTR_SUBDIR))
+ return;
+
+ parent = child->parent;
+ list_del(&child->sibling);
+ exfat_free_inode(child);
+
+ child = parent;
+ }
+ return;
+}
+
+void exfat_free_dir_list(struct exfat *exfat)
+{
+ struct exfat_inode *dir, *i;
+
+ list_for_each_entry_safe(dir, i, &exfat->dir_list, list) {
+ if (!dir->parent)
+ continue;
+ exfat_free_file_children(dir);
+ list_del(&dir->list);
+ exfat_free_inode(dir);
+ }
+}
+
+void exfat_free_exfat(struct exfat *exfat)
+{
+ if (exfat) {
+ if (exfat->bs)
+ free(exfat->bs);
+ if (exfat->alloc_bitmap)
+ free(exfat->alloc_bitmap);
+ if (exfat->disk_bitmap)
+ free(exfat->disk_bitmap);
+ if (exfat->ohead_bitmap)
+ free(exfat->ohead_bitmap);
+ if (exfat->upcase_table)
+ free(exfat->upcase_table);
+ if (exfat->root)
+ exfat_free_inode(exfat->root);
+ if (exfat->zero_cluster)
+ free(exfat->zero_cluster);
+ free(exfat);
+ }
+}
+
+struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs)
+{
+ struct exfat *exfat;
+
+ exfat = (struct exfat *)calloc(1, sizeof(*exfat));
+ if (!exfat)
+ return NULL;
+
+ INIT_LIST_HEAD(&exfat->dir_list);
+ exfat->blk_dev = blk_dev;
+ exfat->bs = bs;
+ exfat->clus_count = le32_to_cpu(bs->bsx.clu_count);
+ exfat->clus_size = EXFAT_CLUSTER_SIZE(bs);
+ exfat->sect_size = EXFAT_SECTOR_SIZE(bs);
+
+ /* TODO: bitmap could be very large. */
+ exfat->alloc_bitmap = (char *)calloc(1,
+ EXFAT_BITMAP_SIZE(exfat->clus_count));
+ if (!exfat->alloc_bitmap) {
+ exfat_err("failed to allocate bitmap\n");
+ goto err;
+ }
+
+ exfat->ohead_bitmap =
+ calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count));
+ if (!exfat->ohead_bitmap) {
+ exfat_err("failed to allocate bitmap\n");
+ goto err;
+ }
+
+ exfat->disk_bitmap =
+ calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count));
+ if (!exfat->disk_bitmap) {
+ exfat_err("failed to allocate bitmap\n");
+ goto err;
+ }
+
+ exfat->zero_cluster = calloc(1, exfat->clus_size);
+ if (!exfat->zero_cluster) {
+ exfat_err("failed to allocate a zero-filled cluster buffer\n");
+ goto err;
+ }
+
+ exfat->start_clu = EXFAT_FIRST_CLUSTER;
+ return exfat;
+err:
+ exfat_free_exfat(exfat);
+ return NULL;
+}
+
+struct buffer_desc *exfat_alloc_buffer(int count,
+ unsigned int clu_size, unsigned int sect_size)
+{
+ struct buffer_desc *bd;
+ int i;
+
+ bd = (struct buffer_desc *)calloc(sizeof(*bd), count);
+ if (!bd)
+ return NULL;
+
+ for (i = 0; i < count; i++) {
+ bd[i].buffer = (char *)malloc(clu_size);
+ if (!bd[i].buffer)
+ goto err;
+ bd[i].dirty = (char *)calloc(clu_size / sect_size, 1);
+ if (!bd[i].dirty)
+ goto err;
+ }
+ return bd;
+err:
+ exfat_free_buffer(bd, count);
+ return NULL;
+}
+
+void exfat_free_buffer(struct buffer_desc *bd, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (bd[i].buffer)
+ free(bd[i].buffer);
+ if (bd[i].dirty)
+ free(bd[i].dirty);
+ }
+ free(bd);
+}
+
+/*
+ * get references of ancestors that include @child until the count of
+ * ancesters is not larger than @count and the count of characters of
+ * their names is not larger than @max_char_len.
+ * return true if root is reached.
+ */
+static bool get_ancestors(struct exfat_inode *child,
+ struct exfat_inode **ancestors, int count,
+ int max_char_len,
+ int *ancestor_count)
+{
+ struct exfat_inode *dir;
+ int name_len, char_len;
+ int root_depth, depth, i;
+
+ root_depth = 0;
+ char_len = 0;
+ max_char_len += 1;
+
+ dir = child;
+ while (dir) {
+ name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE);
+ if (char_len + name_len > max_char_len)
+ break;
+
+ /* include '/' */
+ char_len += name_len + 1;
+ root_depth++;
+
+ dir = dir->parent;
+ }
+
+ depth = MIN(root_depth, count);
+
+ for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--)
+ ancestors[i] = dir;
+
+ *ancestor_count = depth;
+ return !dir;
+}
+
+int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
+{
+ int depth, i;
+ int name_len;
+ __le16 *utf16_path;
+ static const __le16 utf16_slash = cpu_to_le16(0x002F);
+ static const __le16 utf16_null = cpu_to_le16(0x0000);
+ size_t in_size;
+
+ ctx->local_path[0] = '\0';
+
+ get_ancestors(child,
+ ctx->ancestors,
+ sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]),
+ PATH_MAX,
+ &depth);
+
+ utf16_path = ctx->utf16_path;
+ for (i = 0; i < depth; i++) {
+ name_len = exfat_utf16_len(ctx->ancestors[i]->name,
+ NAME_BUFFER_SIZE);
+ memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name,
+ name_len * 2);
+ utf16_path += name_len;
+ memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash));
+ utf16_path++;
+ }
+
+ if (depth > 1)
+ utf16_path--;
+ memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null));
+ utf16_path++;
+
+ in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16);
+ return exfat_utf16_dec(ctx->utf16_path, in_size,
+ ctx->local_path, sizeof(ctx->local_path));
+}
+
+int exfat_resolve_path_parent(struct path_resolve_ctx *ctx,
+ struct exfat_inode *parent, struct exfat_inode *child)
+{
+ int ret;
+ struct exfat_inode *old;
+
+ old = child->parent;
+ child->parent = parent;
+
+ ret = exfat_resolve_path(ctx, child);
+ child->parent = old;
+ return ret;
+}
diff --git a/lib/libexfat.c b/lib/libexfat.c
index ee48d3a..92e5e91 100644
--- a/lib/libexfat.c
+++ b/lib/libexfat.c
@@ -19,64 +19,56 @@
#include "exfat_ondisk.h"
#include "libexfat.h"
#include "version.h"
-
-#define BITS_PER_LONG (sizeof(long) * CHAR_BIT)
-
-#ifdef WORDS_BIGENDIAN
-#define BITOP_LE_SWIZZLE ((BITS_PER_LONG - 1) & ~0x7)
-#else
-#define BITOP_LE_SWIZZLE 0
-#endif
-
-#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
-#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
+#include "exfat_fs.h"
unsigned int print_level = EXFAT_INFO;
-static inline void set_bit(int nr, void *addr)
+void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap,
+ clus_t start_clus, clus_t count)
{
- unsigned long mask = BIT_MASK(nr);
- unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
+ clus_t clus;
- *p |= mask;
-}
-
-static inline void clear_bit(int nr, void *addr)
-{
- unsigned long mask = BIT_MASK(nr);
- unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
+ if (!exfat_heap_clus(exfat, start_clus) ||
+ !exfat_heap_clus(exfat, start_clus + count - 1))
+ return;
- *p &= ~mask;
+ clus = start_clus;
+ while (clus < start_clus + count) {
+ exfat_bitmap_set(bitmap, clus);
+ clus++;
+ }
}
-static inline void set_bit_le(int nr, void *addr)
+static int exfat_bitmap_find_bit(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next,
+ int bit)
{
- set_bit(nr ^ BITOP_LE_SWIZZLE, addr);
+ clus_t last_clu;
+
+ last_clu = le32_to_cpu(exfat->bs->bsx.clu_count) +
+ EXFAT_FIRST_CLUSTER;
+ while (start_clu < last_clu) {
+ if (!!exfat_bitmap_get(bmap, start_clu) == bit) {
+ *next = start_clu;
+ return 0;
+ }
+ start_clu++;
+ }
+ return 1;
}
-static inline void clear_bit_le(int nr, void *addr)
+int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next)
{
- clear_bit(nr ^ BITOP_LE_SWIZZLE, addr);
+ return exfat_bitmap_find_bit(exfat, bmap,
+ start_clu, next, 0);
}
-void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap,
- unsigned int clu)
+int exfat_bitmap_find_one(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next)
{
- int b;
-
- b = clu & ((bd->sector_size << 3) - 1);
-
- set_bit_le(b, bitmap);
-}
-
-void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap,
- unsigned int clu)
-{
- int b;
-
- b = clu & ((bd->sector_size << 3) - 1);
-
- clear_bit_le(b, bitmap);
+ return exfat_bitmap_find_bit(exfat, bmap,
+ start_clu, next, 1);
}
wchar_t exfat_bad_char(wchar_t w)
@@ -681,3 +673,90 @@ unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd,
return clu_off_sectnr * bd->sector_size +
(clu - EXFAT_RESERVED_CLUSTERS) * bd->cluster_size;
}
+
+int exfat_get_next_clus(struct exfat *exfat, clus_t clus, clus_t *next)
+{
+ off_t offset;
+
+ *next = EXFAT_EOF_CLUSTER;
+
+ if (!exfat_heap_clus(exfat, clus))
+ return -EINVAL;
+
+ offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) <<
+ exfat->bs->bsx.sect_size_bits;
+ offset += sizeof(clus_t) * clus;
+
+ if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset)
+ != sizeof(*next))
+ return -EIO;
+ *next = le32_to_cpu(*next);
+ return 0;
+}
+
+int exfat_get_inode_next_clus(struct exfat *exfat, struct exfat_inode *node,
+ clus_t clus, clus_t *next)
+{
+ *next = EXFAT_EOF_CLUSTER;
+
+ if (node->is_contiguous) {
+ if (!exfat_heap_clus(exfat, clus))
+ return -EINVAL;
+ *next = clus + 1;
+ return 0;
+ }
+
+ return exfat_get_next_clus(exfat, clus, next);
+}
+
+int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus)
+{
+ off_t offset;
+
+ offset = le32_to_cpu(exfat->bs->bsx.fat_offset) <<
+ exfat->bs->bsx.sect_size_bits;
+ offset += sizeof(clus_t) * clus;
+
+ if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus),
+ offset) != sizeof(next_clus))
+ return -EIO;
+ return 0;
+}
+
+off_t exfat_s2o(struct exfat *exfat, off_t sect)
+{
+ return sect << exfat->bs->bsx.sect_size_bits;
+}
+
+off_t exfat_c2o(struct exfat *exfat, unsigned int clus)
+{
+ if (clus < EXFAT_FIRST_CLUSTER)
+ return ~0L;
+
+ return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) +
+ ((off_t)(clus - EXFAT_FIRST_CLUSTER) <<
+ exfat->bs->bsx.sect_per_clus_bits));
+}
+
+int exfat_o2c(struct exfat *exfat, off_t device_offset,
+ unsigned int *clu, unsigned int *offset)
+{
+ off_t heap_offset;
+
+ heap_offset = exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset));
+ if (device_offset < heap_offset)
+ return -ERANGE;
+
+ *clu = (unsigned int)((device_offset - heap_offset) /
+ exfat->clus_size) + EXFAT_FIRST_CLUSTER;
+ if (!exfat_heap_clus(exfat, *clu))
+ return -ERANGE;
+ *offset = (device_offset - heap_offset) % exfat->clus_size;
+ return 0;
+}
+
+bool exfat_heap_clus(struct exfat *exfat, clus_t clus)
+{
+ return clus >= EXFAT_FIRST_CLUSTER &&
+ (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count;
+}
diff --git a/manpages/exfat2img.8 b/manpages/exfat2img.8
new file mode 100644
index 0000000..1c7f288
--- /dev/null
+++ b/manpages/exfat2img.8
@@ -0,0 +1,31 @@
+.TH exfat2img 8
+.SH NAME
+exfat2img \- dump metadata of an exFAT filesystem
+.SH SYNOPSIS
+.B exfat2img
+[
+.B \-o \fIpath\fB\
+] [
+.B \-V
+]
+.I device
+.br
+.B exfat2img \-V
+.SH DESCRIPTION
+.B exfat2img
+dump metadata of exFAT filesystems for debugging. \fBexfat2img\fP dump boot sector, File Allcation Table, Bitmap and all metadata which can reach from root directory.
+
+.SH OPTIONS
+.TP
+.BI \-o\ \-\-output
+Specify output result file. If filesystem to which output file is written does not support sparse file, you should use '-' in place of \fIpath\fP.
+When restoring a partition from a dump image generated from stdout, exfat2img should be used. See Examples.
+.TP
+.B \-V
+Prints the version number and exits.
+
+.SH EXAMPLES
+.PP
+.EX
+.RB "$" " exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz2"
+.RB "$" " bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 -"
diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c
index b5b4957..511662b 100644
--- a/mkfs/mkfs.c
+++ b/mkfs/mkfs.c
@@ -314,12 +314,13 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd)
char *bitmap;
unsigned int i, nbytes;
- bitmap = calloc(finfo.bitmap_byte_len, sizeof(*bitmap));
+ bitmap = calloc(round_up(finfo.bitmap_byte_len, sizeof(bitmap_t)),
+ sizeof(*bitmap));
if (!bitmap)
return -1;
- for (i = 0; i < finfo.used_clu_cnt - EXFAT_FIRST_CLUSTER; i++)
- exfat_set_bit(bd, bitmap, i);
+ for (i = EXFAT_FIRST_CLUSTER; i < finfo.used_clu_cnt; i++)
+ exfat_bitmap_set(bitmap, i);
nbytes = pwrite(bd->dev_fd, bitmap, finfo.bitmap_byte_len, finfo.bitmap_byte_off);
if (nbytes != finfo.bitmap_byte_len) {
diff --git a/tests/bad_bitmap/exfat.img.tar.xz b/tests/bad_bitmap/exfat.img.tar.xz
new file mode 100644
index 0000000..df09d10
--- /dev/null
+++ b/tests/bad_bitmap/exfat.img.tar.xz
Binary files differ
diff --git a/tests/bad_dentries/exfat.img.tar.xz b/tests/bad_dentries/exfat.img.tar.xz
new file mode 100644
index 0000000..32643ca
--- /dev/null
+++ b/tests/bad_dentries/exfat.img.tar.xz
Binary files differ
diff --git a/tests/bad_file_size/exfat.img.tar.xz b/tests/bad_file_size/exfat.img.tar.xz
new file mode 100644
index 0000000..df7ff14
--- /dev/null
+++ b/tests/bad_file_size/exfat.img.tar.xz
Binary files differ
diff --git a/tests/bad_first_clu/exfat.img.tar.xz b/tests/bad_first_clu/exfat.img.tar.xz
new file mode 100644
index 0000000..3dc29ec
--- /dev/null
+++ b/tests/bad_first_clu/exfat.img.tar.xz
Binary files differ
diff --git a/tests/bad_num_chain/config b/tests/bad_num_chain/config
new file mode 100644
index 0000000..f62cec1
--- /dev/null
+++ b/tests/bad_num_chain/config
@@ -0,0 +1 @@
+#OPTS: -s
diff --git a/tests/bad_num_chain/exfat.img.tar.xz b/tests/bad_num_chain/exfat.img.tar.xz
new file mode 100644
index 0000000..68b8605
--- /dev/null
+++ b/tests/bad_num_chain/exfat.img.tar.xz
Binary files differ
diff --git a/tests/bad_root/exfat.img.tar.xz b/tests/bad_root/exfat.img.tar.xz
new file mode 100644
index 0000000..de0066f
--- /dev/null
+++ b/tests/bad_root/exfat.img.tar.xz
Binary files differ
diff --git a/tests/duplicate_clu/exfat.img.tar.xz b/tests/duplicate_clu/exfat.img.tar.xz
new file mode 100644
index 0000000..bf3fdd8
--- /dev/null
+++ b/tests/duplicate_clu/exfat.img.tar.xz
Binary files differ
diff --git a/tests/file_invalid_clus/exfat.img.expected.xz b/tests/file_invalid_clus/exfat.img.expected.xz
deleted file mode 100644
index 08e992e..0000000
--- a/tests/file_invalid_clus/exfat.img.expected.xz
+++ /dev/null
Binary files differ
diff --git a/tests/large_file_invalid_clus/exfat.img.expected.xz b/tests/large_file_invalid_clus/exfat.img.expected.xz
deleted file mode 100644
index b31e710..0000000
--- a/tests/large_file_invalid_clus/exfat.img.expected.xz
+++ /dev/null
Binary files differ
diff --git a/tests/loop_chain/config b/tests/loop_chain/config
new file mode 100644
index 0000000..f62cec1
--- /dev/null
+++ b/tests/loop_chain/config
@@ -0,0 +1 @@
+#OPTS: -s
diff --git a/tests/loop_chain/exfat.img.tar.xz b/tests/loop_chain/exfat.img.tar.xz
new file mode 100644
index 0000000..c863cdd
--- /dev/null
+++ b/tests/loop_chain/exfat.img.tar.xz
Binary files differ
diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh
index 35f81e1..c53d8f3 100755
--- a/tests/test_fsck.sh
+++ b/tests/test_fsck.sh
@@ -1,15 +1,21 @@
#!/usr/bin/env bash
TESTCASE_DIR=$1
+NEED_LOOPDEV=$2
IMAGE_FILE=exfat.img
-FSCK_PROG=../build/sbin/fsck.exfat
-FSCK_OPTS=-y
+FSCK_PROG=fsck.exfat
+FSCK_PROG_2=fsck.exfat
+FSCK_OPTS="-y -s"
PASS_COUNT=0
cleanup() {
echo ""
echo "Passed ${PASS_COUNT} of ${TEST_COUNT}"
- exit
+ if [ ${PASS_COUNT} -ne ${TEST_COUNT} ]; then
+ exit 1
+ else
+ exit 0
+ fi
}
if [ $# -eq 0 ]; then
@@ -31,43 +37,41 @@ for TESTCASE_DIR in $TESTCASE_DIRS; do
# Set up image file as loop device
tar -C . -xf "${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz"
- DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show)
+ if [ $NEED_LOOPDEV ]; then
+ DEV_FILE=$(losetup -f "${IMAGE_FILE}" --show)
+ else
+ DEV_FILE=$IMAGE_FILE
+ fi
# Run fsck for repair
$FSCK_PROG $FSCK_OPTS "$DEV_FILE"
- if [ $? -ne 1 ]; then
+ if [ $? -ne 1 ] && [ $? -ne 0 ]; then
echo ""
echo "Failed to repair ${TESTCASE_DIR}"
- losetup -d "${DEV_FILE}"
+ if [ $NEED_LOOPDEV ]; then
+ losetup -d "${DEV_FILE}"
+ fi
cleanup
fi
echo ""
# Run fsck again
- $FSCK_PROG -n "$DEV_FILE"
+ $FSCK_PROG_2 "$DEV_FILE"
if [ $? -ne 0 ]; then
echo ""
echo "Failed, corrupted ${TESTCASE_DIR}"
- losetup -d "${DEV_FILE}"
- cleanup
- fi
-
- if [ -e "${TESTCASE_DIR}/exfat.img.expected.xz" ]; then
- EXPECTED_FILE=${IMAGE_FILE}.expected
- unxz -cfk "${TESTCASE_DIR}/${EXPECTED_FILE}.xz" > "${EXPECTED_FILE}"
- diff <(xxd "${IMAGE_FILE}") <(xxd "${EXPECTED_FILE}")
- if [ $? -ne 0 ]; then
- echo ""
- echo "Failed ${TESTCASE_DIR}"
+ if [ $NEED_LOOPDEV ]; then
losetup -d "${DEV_FILE}"
- cleanup
fi
+ cleanup
fi
echo ""
echo "Passed ${TESTCASE_DIR}"
PASS_COUNT=$((PASS_COUNT + 1))
- losetup -d "${DEV_FILE}"
+ if [ $NEED_LOOPDEV ]; then
+ losetup -d "${DEV_FILE}"
+ fi
done
cleanup