New upstream version 1.0.4 upstream/1.0.4
authorSven Höxter <sven@stormbind.net>
Fri, 31 Jul 2020 08:09:09 +0000 (10:09 +0200)
committerSven Höxter <sven@stormbind.net>
Fri, 31 Jul 2020 08:09:09 +0000 (10:09 +0200)
23 files changed:
Makefile.in
NEWS
README.md
configure
fsck/Android.bp
fsck/Makefile.am
fsck/Makefile.in
fsck/de_iter.c [new file with mode: 0644]
fsck/fsck.c
fsck/fsck.h
fsck/repair.c
fsck/repair.h
include/exfat_ondisk.h
include/libexfat.h
include/version.h
lib/libexfat.c
manpages/fsck.exfat.8
manpages/mkfs.exfat.8
manpages/tune.exfat.8
mkfs/Makefile.am
mkfs/Makefile.in
mkfs/mkfs.c
mkfs/mkfs.h

index 2ad6099e15eb5ab5081b3d3be5e53eae0e7a8254..78cd1e623395abba0204b737a19d6feb10069520 100644 (file)
@@ -198,7 +198,7 @@ am__DIST_COMMON = $(dist_man8_MANS) $(srcdir)/Makefile.in \
        $(top_srcdir)/build-aux/install-sh \
        $(top_srcdir)/build-aux/ltmain.sh \
        $(top_srcdir)/build-aux/missing COPYING NEWS build-aux/compile \
-       build-aux/config.guess build-aux/config.sub build-aux/depcomp \
+       build-aux/config.guess build-aux/config.sub \
        build-aux/install-sh build-aux/ltmain.sh build-aux/missing
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 distdir = $(PACKAGE)-$(VERSION)
diff --git a/NEWS b/NEWS
index 064ba7b1436bd34594e8b5d0df3a38baeecea6fd..dd48c5f55a10dcf30b521b67988c88f974edf57f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,21 @@
+exfatprogs 1.0.4 - released 2020-07-31
+======================================
+
+CHANGES :
+ * fsck.exfat: display sector, cluster, and volume sizes in the human
+   readable format.
+ * fsck.exfat: reduce the elapsed time using read-ahead.
+
+NEW FEATURES :
+ * mkfs.exfat: generate pseudo unique serials while creating filesystems.
+ * mkfs.exfat: add the "-b" option to align the start offset of FAT and
+   data clusters.
+ * fsck.exfat: repair zero-byte files which have the NoFatChain attribute.
+
+BUG FIXES :
+ * Fix memory leaks on error handling paths.
+ * fsck.exfat: fix the bug that cannot access space beyond 2TB.
+
 exfatprogs 1.0.3 - released 2020-05-12
 ======================================
 
index 48861d97aa3721931a147823debc57f34190d63a..af6a32c92d0c36f41bce1b8a2efa2446541ece20 100644 (file)
--- a/README.md
+++ b/README.md
@@ -42,6 +42,10 @@ Usage example:
         mkfs.exfat -f /dev/sda1
     4. For set volume label, use -l option with string user want.
         mkfs.exfat -L "my usb" /dev/sda1
+    5. To change boundary alignment(KB or MB or Byte) user want
+        mkfs.exfat -b 16777216 /dev/sda1
+        mkfs.exfat -b 16384K /dev/sda1
+        mkfs.exfat -b 16M /dev/sda1
 
 - fsck.exfat:
     Check the consistency of your exfat filesystem and optionally repair a corrupted device formatted by exfat.
@@ -61,6 +65,23 @@ Usage example:
         tune.exfat -L "new label" /dev/sda1
 ```
 
+## Benchmarks
+
+Some fsck implementations were tested and compared for Samsung 64GB Pro
+microSDXC UHS-I Class 10 which was filled up to 35GB with 9948 directories
+and 16506 files by fsstress.
+
+The difference in the execution time for each testing is very small.
+
+
+| Implementation       | version         | execution time (seconds) |
+|----------------------|-----------------|--------------------------|
+| **exfatprogs fsck**  | 1.0.4           | 11.561                   |
+| Windows fsck         | Windows 10 1809 | 11.449                   |
+| [exfat-fuse fsck]    | 1.3.0           | 68.977                   |
+
+[exfat-fuse fsck]: https://github.com/relan/exfat
+
 ## Sending feedback
 If you have any issues, please create [issues][1] or contact to [Namjae Jeon](mailto:linkinjeon@kernel.org) and
 [Hyunchul Lee](mailto:hyc.lee@gmail.com).
@@ -68,3 +89,7 @@ If you have any issues, please create [issues][1] or contact to [Namjae Jeon](ma
 
 [1]: https://github.com/exfatprogs/exfatprogs/issues
 [2]: https://github.com/exfatprogs/exfatprogs/pulls
+
+## Contributor information
+* Please base your pull requests on the `exfat-next` branch.
+* Make sure you add 'Signed-Off' information to your commits (e. g. `git commit --signoff`).
index 6cea5d3b0e7ebd37699f45b6dd7f7f8a0efbe0a0..68a3273241c023b3ccf0986de90d9ba1fa2272ec 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for exfatprogs 1.0.3.
+# Generated by GNU Autoconf 2.69 for exfatprogs 1.0.4.
 #
 # Report bugs to <linkinjeon@kernel.org>.
 #
@@ -590,8 +590,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='exfatprogs'
 PACKAGE_TARNAME='exfatprogs'
-PACKAGE_VERSION='1.0.3'
-PACKAGE_STRING='exfatprogs 1.0.3'
+PACKAGE_VERSION='1.0.4'
+PACKAGE_STRING='exfatprogs 1.0.4'
 PACKAGE_BUGREPORT='linkinjeon@kernel.org'
 PACKAGE_URL='https://github.com/exfatprogs/exfatprogs'
 
@@ -1325,7 +1325,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures exfatprogs 1.0.3 to adapt to many kinds of systems.
+\`configure' configures exfatprogs 1.0.4 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1396,7 +1396,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of exfatprogs 1.0.3:";;
+     short | recursive ) echo "Configuration of exfatprogs 1.0.4:";;
    esac
   cat <<\_ACEOF
 
@@ -1508,7 +1508,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-exfatprogs configure 1.0.3
+exfatprogs configure 1.0.4
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1786,7 +1786,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by exfatprogs $as_me 1.0.3, which was
+It was created by exfatprogs $as_me 1.0.4, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2654,7 +2654,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='exfatprogs'
- VERSION='1.0.3'
+ VERSION='1.0.4'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -13253,7 +13253,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by exfatprogs $as_me 1.0.3, which was
+This file was extended by exfatprogs $as_me 1.0.4, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -13320,7 +13320,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-exfatprogs config.status 1.0.3
+exfatprogs config.status 1.0.4
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
index 7473fea6d510025b43dbaf8ce52013954a40947a..f267ad4e94bae1e724022bb93d6b0ea556e1c7aa 100644 (file)
@@ -4,6 +4,7 @@ cc_binary {
     name: "fsck.exfat",
 
     srcs: [
+        "de_iter.c",
         "fsck.c",
         "repair.c",
     ],
index 31b0b700381c2dd942c6e8fb7192c2ad29b64b1c..57a0ede0f438788ed5267b7e2ec5a20127bbef44 100644 (file)
@@ -3,4 +3,4 @@ fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a
 
 sbin_PROGRAMS = fsck.exfat
 
-fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h
+fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h
index a73337f6f23b8f483578aaf7d9d0f329ac967090..c339e11c4462af8d0dbb4630404450353da3c395 100644 (file)
@@ -104,7 +104,8 @@ CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 am__installdirs = "$(DESTDIR)$(sbindir)"
 PROGRAMS = $(sbin_PROGRAMS)
-am_fsck_exfat_OBJECTS = fsck.$(OBJEXT) repair.$(OBJEXT)
+am_fsck_exfat_OBJECTS = fsck.$(OBJEXT) repair.$(OBJEXT) \
+       de_iter.$(OBJEXT)
 fsck_exfat_OBJECTS = $(am_fsck_exfat_OBJECTS)
 fsck_exfat_DEPENDENCIES = $(top_builddir)/lib/libexfat.a
 AM_V_lt = $(am__v_lt_@AM_V@)
@@ -292,7 +293,7 @@ top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
 fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a
-fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h
+fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h
 all: all-am
 
 .SUFFIXES:
@@ -386,6 +387,7 @@ mostlyclean-compile:
 distclean-compile:
        -rm -f *.tab.c
 
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/de_iter.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsck.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/repair.Po@am__quote@
 
diff --git a/fsck/de_iter.c b/fsck/de_iter.c
new file mode 100644 (file)
index 0000000..bc95c49
--- /dev/null
@@ -0,0 +1,313 @@
+// 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 (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 && desc->p_clus == EXFAT_EOF_CLUSTER)
+                               return EOF;
+                       else if (ret)
+                               return ret;
+               }
+       }
+
+       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;
+}
index 2a13f437c010c7601c063b2c148e4c8098dde60f..9732afb2982233a4f8950c481b53d991a1755204 100644 (file)
@@ -38,9 +38,10 @@ typedef __u32        bitmap_t;
 #define EXFAT_BITMAP_SIZE(__c_count)   \
        (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t))
 #define EXFAT_BITMAP_GET(__bmap, __c)  \
-                       ((__bmap)[BIT_ENTRY(__c)] & BIT_MASK(__c))
+                       (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c))
 #define EXFAT_BITMAP_SET(__bmap, __c)  \
-                       ((__bmap)[BIT_ENTRY(__c)] |= BIT_MASK(__c))
+                       (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \
+                        BIT_MASK(__c))
 
 #define FSCK_EXIT_NO_ERRORS            0x00
 #define FSCK_EXIT_CORRECTED            0x01
@@ -54,8 +55,8 @@ typedef __u32 bitmap_t;
 struct exfat_stat {
        long            dir_count;
        long            file_count;
-       long            dir_free_count;
-       long            file_free_count;
+       long            error_count;
+       long            fixed_count;
 };
 
 struct path_resolve_ctx {
@@ -71,6 +72,7 @@ static struct option opts[] = {
        {"repair",      no_argument,    NULL,   'r' },
        {"repair-yes",  no_argument,    NULL,   'y' },
        {"repair-no",   no_argument,    NULL,   'n' },
+       {"repair-auto", no_argument,    NULL,   'p' },
        {"version",     no_argument,    NULL,   'V' },
        {"verbose",     no_argument,    NULL,   'v' },
        {"help",        no_argument,    NULL,   'h' },
@@ -84,6 +86,7 @@ static void usage(char *name)
        fprintf(stderr, "\t-r | --repair        Repair interactively\n");
        fprintf(stderr, "\t-y | --repair-yes    Repair without ask\n");
        fprintf(stderr, "\t-n | --repair-no     No repair\n");
+       fprintf(stderr, "\t-p | --repair-auto   Repair automatically\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");
@@ -91,6 +94,15 @@ static void usage(char *name)
        exit(FSCK_EXIT_SYNTAX_ERROR);
 }
 
+#define fsck_err(parent, inode, fmt, ...)              \
+({                                                     \
+               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;
@@ -119,10 +131,6 @@ static struct exfat_inode *alloc_exfat_inode(__u16 attr)
 
 static void free_exfat_inode(struct exfat_inode *node)
 {
-       if (node->attr & ATTR_SUBDIR)
-               exfat_stat.dir_free_count++;
-       else
-               exfat_stat.file_free_count++;
        free(node);
 }
 
@@ -175,341 +183,88 @@ static void inode_free_ancestors(struct exfat_inode *child)
        return;
 }
 
-static struct exfat *alloc_exfat(struct exfat_blk_dev *bd)
-{
-       struct exfat *exfat;
-
-       exfat = (struct exfat *)calloc(1, sizeof(*exfat));
-       if (!exfat) {
-               exfat_err("failed to allocate exfat\n");
-               return NULL;
-       }
-
-       exfat->blk_dev = bd;
-       INIT_LIST_HEAD(&exfat->dir_list);
-       return exfat;
-}
-
 static void free_exfat(struct exfat *exfat)
 {
+       int i;
+
        if (exfat) {
                if (exfat->bs)
                        free(exfat->bs);
-               if (exfat->de_iter.dentries)
-                       free(exfat->de_iter.dentries);
                if (exfat->alloc_bitmap)
                        free(exfat->alloc_bitmap);
-               free(exfat);
-       }
-}
-
-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);
-       }
-}
-
-static inline bool exfat_invalid_clus(struct exfat *exfat, clus_t clus)
-{
-       return clus < EXFAT_FIRST_CLUSTER ||
-       (clus - EXFAT_FIRST_CLUSTER) > le32_to_cpu(exfat->bs->bsx.clu_count);
-}
-
-static int inode_get_clus_next(struct exfat *exfat, struct exfat_inode *node,
-                               clus_t clus, clus_t *next)
-{
-       off_t offset;
-
-       if (exfat_invalid_clus(exfat, clus))
-               return -EINVAL;
-
-       if (node->is_contiguous) {
-               *next = clus + 1;
-               return 0;
-       }
-
-       offset = 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 bool inode_check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
-{
-       clus_t clus;
-       clus_t clus_count;
-
-       clus = node->first_clus;
-       clus_count = DIV_ROUND_UP(node->size, EXFAT_CLUSTER_SIZE(exfat->bs));
-
-       while (clus_count--) {
-               if (exfat_invalid_clus(exfat, clus)) {
-                       exfat_err("bad cluster. 0x%x\n", clus);
-                       return false;
-               }
-
-               if (!EXFAT_BITMAP_GET(exfat->alloc_bitmap,
-                                       clus - EXFAT_FIRST_CLUSTER)) {
-                       exfat_err(
-                               "cluster allocated, but not in bitmap. 0x%x\n",
-                               clus);
-                       return false;
-               }
-
-               if (inode_get_clus_next(exfat, node, clus, &clus) != 0) {
-                       exfat_err(
-                               "broken cluster chain. (previous cluster 0x%x)\n",
-                               clus);
-                       return false;
-               }
-       }
-       return true;
-}
-
-static bool inode_get_clus_count(struct exfat *exfat, struct exfat_inode *node,
-                                                       clus_t *clus_count)
-{
-       clus_t clus;
-
-       clus = node->first_clus;
-       *clus_count = 0;
-
-       do {
-               if (exfat_invalid_clus(exfat, clus)) {
-                       exfat_err("bad cluster. 0x%x\n", clus);
-                       return false;
-               }
-
-               if (inode_get_clus_next(exfat, node, clus, &clus) != 0) {
-                       exfat_err(
-                               "broken cluster chain. (previous cluster 0x%x)\n",
-                               clus);
-                       return false;
-               }
-
-               (*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;
-}
-
-static 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) +
-                               ((clus - EXFAT_FIRST_CLUSTER) <<
-                                exfat->bs->bsx.sect_per_clus_bits));
-}
-
-static ssize_t exfat_file_read(struct exfat *exfat, struct exfat_inode *node,
-                       void *buf, size_t total_size, off_t file_offset)
-{
-       size_t clus_size;
-       clus_t start_l_clus, l_clus, p_clus;
-       unsigned int clus_offset;
-       int ret;
-       off_t device_offset;
-       ssize_t read_size;
-       size_t remain_size;
-
-       if (file_offset >= (off_t)node->size)
-               return EOF;
-
-       clus_size = EXFAT_CLUSTER_SIZE(exfat->bs);
-       total_size = MIN(total_size, node->size - file_offset);
-       remain_size = total_size;
-
-       if (remain_size == 0)
-               return 0;
-
-       start_l_clus = file_offset / clus_size;
-       clus_offset = file_offset % clus_size;
-       if (start_l_clus >= node->last_lclus &&
-                       node->last_pclus != EXFAT_EOF_CLUSTER) {
-               l_clus = node->last_lclus;
-               p_clus = node->last_pclus;
-       } else {
-               l_clus = 0;
-               p_clus = node->first_clus;
-       }
-
-       while (p_clus != EXFAT_EOF_CLUSTER) {
-               if (exfat_invalid_clus(exfat, p_clus))
-                       return -EINVAL;
-               if (l_clus < start_l_clus)
-                       goto next_clus;
-
-               read_size = MIN(remain_size, clus_size - clus_offset);
-               device_offset = exfat_c2o(exfat, p_clus) + clus_offset;
-               if (exfat_read(exfat->blk_dev->dev_fd, buf, read_size,
-                                       device_offset) != read_size)
-                       return -EIO;
-
-               clus_offset = 0;
-               buf = (char *)buf + read_size;
-               remain_size -= read_size;
-               if (remain_size == 0)
-                       goto out;
-
-next_clus:
-               l_clus++;
-               ret = inode_get_clus_next(exfat, node, p_clus, &p_clus);
-               if (ret)
-                       return ret;
-       }
-out:
-       node->last_lclus = l_clus;
-       node->last_pclus = p_clus;
-       return total_size - remain_size;
-}
-
-static int boot_region_checksum(struct exfat *exfat)
-{
-       __le32 checksum;
-       unsigned short size;
-       void *sect;
-       unsigned int i;
-
-       size = EXFAT_SECTOR_SIZE(exfat->bs);
-       sect = malloc(size);
-       if (!sect)
-               return -ENOMEM;
-
-       checksum = 0;
-
-       boot_calc_checksum((unsigned char *)exfat->bs, size, true, &checksum);
-       for (i = 1; i < 11; i++) {
-               if (exfat_read(exfat->blk_dev->dev_fd, sect, size, i * size) !=
-                               (ssize_t)size) {
-                       free(sect);
-                       return -EIO;
-               }
-               boot_calc_checksum(sect, size, false, &checksum);
-       }
-
-       if (exfat_read(exfat->blk_dev->dev_fd, sect, size, i * size) !=
-                       (ssize_t)size) {
-               free(sect);
-               return -EIO;
-       }
-       for (i = 0; i < size/sizeof(checksum); i++) {
-               if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) {
-                       union exfat_repair_context rctx = {
-                               .bs_checksum.checksum           = checksum,
-                               .bs_checksum.checksum_sect      = sect,
-                       };
-                       if (!exfat_repair(exfat, ER_BS_CHECKSUM, &rctx)) {
-                               exfat_err("invalid checksum. 0x%x\n",
-                                       le32_to_cpu(((__le32 *)sect)[i]));
-                               free(sect);
-                               return -EIO;
-                       }
+               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);
        }
-
-       free(sect);
-       return 0;
 }
 
-static bool exfat_boot_region_check(struct exfat *exfat)
+static struct exfat *alloc_exfat(struct exfat_blk_dev *bd, struct pbr *bs)
 {
-       struct pbr *bs;
-       ssize_t ret;
+       struct exfat *exfat;
+       int i;
 
-       bs = (struct pbr *)malloc(sizeof(struct pbr));
-       if (!bs) {
-               exfat_err("failed to allocate memory\n");
-               return false;
+       exfat = (struct exfat *)calloc(1, sizeof(*exfat));
+       if (!exfat) {
+               free(bs);
+               exfat_err("failed to allocate exfat\n");
+               return NULL;
        }
 
+       INIT_LIST_HEAD(&exfat->dir_list);
+       exfat->blk_dev = bd;
        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);
 
-       ret = exfat_read(exfat->blk_dev->dev_fd, bs, sizeof(*bs), 0);
-       if (ret != sizeof(*bs)) {
-               exfat_err("failed to read a boot sector. %zd\n", ret);
-               goto err;
-       }
-
-       if (memcmp(bs->bpb.oem_name, "EXFAT   ", 8) != 0) {
-               exfat_err("failed to find exfat file system.\n");
-               goto err;
-       }
-
-       if (EXFAT_SECTOR_SIZE(bs) < 512 || EXFAT_SECTOR_SIZE(bs) > 4 * KB) {
-               exfat_err("too small or big sector size: %d\n",
-                               EXFAT_SECTOR_SIZE(bs));
-               goto err;
-       }
-
-       if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) {
-               exfat_err("too big cluster size: %d\n", EXFAT_CLUSTER_SIZE(bs));
-               goto err;
-       }
-
-       ret = boot_region_checksum(exfat);
-       if (ret) {
-               exfat_err("failed to verify the checksum of a boot region. %zd\n",
-                       ret);
-               goto err;
-       }
-
-       if (bs->bsx.fs_version[1] != 1 || bs->bsx.fs_version[0] != 0) {
-               exfat_err("unsupported exfat version: %d.%d\n",
-                               bs->bsx.fs_version[1], bs->bsx.fs_version[0]);
-               goto err;
-       }
-
-       if (bs->bsx.num_fats != 1) {
-               exfat_err("unsupported FAT count: %d\n", bs->bsx.num_fats);
+       /* 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;
        }
 
-       if (le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) >
-                       exfat->blk_dev->size) {
-               exfat_err("too large sector count: %" PRIu64 "\n, expected: %llu\n",
-                               le64_to_cpu(bs->bsx.vol_length),
-                               exfat->blk_dev->num_sectors);
+       exfat->disk_bitmap = (char *)malloc(
+                               EXFAT_BITMAP_SIZE(exfat->clus_count));
+       if (!exfat->disk_bitmap) {
+               exfat_err("failed to allocate bitmap\n");
                goto err;
        }
 
-       if (le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) >
-                       exfat->blk_dev->size) {
-               exfat_err("too large cluster count: %u, expected: %u\n",
-                               le32_to_cpu(bs->bsx.clu_count),
-                               exfat->blk_dev->num_clusters);
-               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 true;
+       return exfat;
 err:
-       free(bs);
-       exfat->bs = NULL;
-       return false;
+       free_exfat(exfat);
+       return NULL;
 }
 
-static size_t utf16_len(const __le16 *str, size_t max_size)
+static void exfat_free_dir_list(struct exfat *exfat)
 {
-       size_t i = 0;
+       struct exfat_inode *dir, *i;
 
-       while (le16_to_cpu(str[i]) && i < max_size)
-               i++;
-       return 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);
+       }
 }
 
 /*
@@ -533,7 +288,7 @@ bool get_ancestors(struct exfat_inode *child,
 
        dir = child;
        while (dir) {
-               name_len = utf16_len(dir->name, NAME_BUFFER_SIZE);
+               name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE);
                if (char_len + name_len > max_char_len)
                        break;
 
@@ -558,7 +313,8 @@ static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
        int depth, i;
        int name_len;
        __le16 *utf16_path;
-       const __le16 utf16_slash = cpu_to_le16(0x002F);
+       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';
@@ -571,7 +327,8 @@ static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
 
        utf16_path = ctx->utf16_path;
        for (i = 0; i < depth; i++) {
-               name_len = utf16_len(ctx->ancestors[i]->name, NAME_BUFFER_SIZE);
+               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;
@@ -581,6 +338,9 @@ static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
 
        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));
@@ -600,157 +360,394 @@ static int resolve_path_parent(struct path_resolve_ctx *ctx,
        return ret;
 }
 
-static int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
-                                               struct exfat_inode *dir)
-{
-       ssize_t ret;
-
-       if (!iter->dentries) {
-               iter->read_size = EXFAT_CLUSTER_SIZE(exfat->bs);
-               iter->dentries = malloc(iter->read_size * 2);
-               if (!iter->dentries) {
-                       exfat_err("failed to allocate memory\n");
-                       return -ENOMEM;
+#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__);                 \
+})
+
+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)
+{
+       struct exfat_dentry *stream_de;
+       clus_t clus, prev, next;
+       uint64_t count, max_count;
+
+       clus = node->first_clus;
+       prev = EXFAT_EOF_CLUSTER;
+       count = 0;
+       max_count = DIV_ROUND_UP(node->size, exfat->clus_size);
+
+       if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER)
+               return 0;
+
+       /* 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"))
+                       goto truncate_file;
+               else
+                       return -EINVAL;
+       }
+
+       while (clus != EXFAT_EOF_CLUSTER) {
+               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))
+                               goto truncate_file;
+                       else
+                               return -EINVAL;
+               }
+
+               /*
+                * 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))
+                               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
+                               return -EINVAL;
+               }
+
+               /* This cluster is allocated or not */
+               if (get_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))
+                                       goto truncate_file;
+
+                               else
+                                       return -EINVAL;
+                       }
+               }
+
+               count++;
+               EXFAT_BITMAP_SET(exfat->alloc_bitmap,
+                               clus - EXFAT_FIRST_CLUSTER);
+               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))
+                       goto truncate_file;
+               else
+                       return -EINVAL;
+       }
+
+       return 0;
+truncate_file:
+       node->size = count * exfat->clus_size;
+       if (!heap_clus(exfat, prev))
+               node->first_clus = EXFAT_FREE_CLUSTER;
+
+       exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de);
+       if (count * exfat->clus_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))
+               stream_de->stream_start_clu = EXFAT_FREE_CLUSTER;
+       stream_de->stream_size = cpu_to_le64(
+                       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);
+       return 1;
+}
+
+static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node,
+                                                       clus_t *clus_count)
+{
+       clus_t clus;
+
+       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 - 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_FIRST_CLUSTER);
+
+               if (get_next_clus(exfat, node, clus, &clus) != 0) {
+                       exfat_err("/: broken cluster chain\n");
+                       return false;
                }
-       }
 
-       ret = exfat_file_read(exfat, dir, iter->dentries, iter->read_size, 0);
-       if (ret != iter->read_size) {
-               exfat_err("failed to read directory entries.\n");
-               return -EIO;
-       }
+               (*clus_count)++;
+       } while (clus != EXFAT_EOF_CLUSTER);
+       return true;
+}
 
-       iter->exfat = exfat;
-       iter->parent = dir;
-       iter->de_file_offset = 0;
-       iter->next_read_offset = iter->read_size;
-       iter->max_skip_dentries = 0;
-       return 0;
+static off_t exfat_s2o(struct exfat *exfat, off_t sect)
+{
+       return sect << exfat->bs->bsx.sect_size_bits;
 }
 
-static int exfat_de_iter_get(struct exfat_de_iter *iter,
-                               int ith, struct exfat_dentry **dentry)
+off_t exfat_c2o(struct exfat *exfat, unsigned int clus)
 {
-       off_t de_next_file_offset;
-       unsigned int de_next_offset;
-       bool need_read_1_clus = false;
-       int ret;
+       if (clus < EXFAT_FIRST_CLUSTER)
+               return ~0L;
 
-       de_next_file_offset = iter->de_file_offset +
-                       ith * sizeof(struct exfat_dentry);
+       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));
+}
 
-       if (de_next_file_offset + sizeof(struct exfat_dentry) >
-               round_down(iter->parent->size, sizeof(struct exfat_dentry)))
-               return EOF;
+static int boot_region_checksum(struct exfat *exfat)
+{
+       void *sect;
+       unsigned int i;
+       uint32_t checksum;
+       int ret = 0;
+       unsigned short size;
 
-       /*
-        * dentry must be in current cluster, or next cluster which
-        * will be read
-        */
-       if (de_next_file_offset -
-               (iter->de_file_offset / iter->read_size) * iter->read_size >=
-               iter->read_size * 2)
-               return -ERANGE;
+       size = EXFAT_SECTOR_SIZE(exfat->bs);
+       sect = malloc(size);
+       if (!sect)
+               return -ENOMEM;
 
-       de_next_offset = de_next_file_offset % (iter->read_size * 2);
+       checksum = 0;
 
-       /* read a cluster if needed */
-       if (de_next_file_offset >= iter->next_read_offset) {
-               void *buf;
+       boot_calc_checksum((unsigned char *)exfat->bs, size, true, &checksum);
+       for (i = 1; i < 11; i++) {
+               if (exfat_read(exfat->blk_dev->dev_fd, sect, size, i * size) !=
+                               (ssize_t)size) {
+                       exfat_err("failed to read boot regions\n");
+                       ret = -EIO;
+                       goto out;
+               }
+               boot_calc_checksum(sect, size, false, &checksum);
+       }
 
-               need_read_1_clus = de_next_offset < iter->read_size;
-               buf = need_read_1_clus ?
-                       iter->dentries : iter->dentries + iter->read_size;
+       if (exfat_read(exfat->blk_dev->dev_fd, sect, size, 11 * size) !=
+                       (ssize_t)size) {
+               exfat_err("failed to read a boot checksum sector\n");
+               ret = -EIO;
+               goto out;
+       }
 
-               ret = exfat_file_read(iter->exfat, iter->parent, buf,
-                       iter->read_size, iter->next_read_offset);
-               if (ret == EOF) {
-                       return EOF;
-               } else if (ret <= 0) {
-                       exfat_err("failed to read a cluster. %d\n", ret);
-                       return ret;
+       for (i = 0; i < size/sizeof(checksum); i++) {
+               if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) {
+                       if (exfat_repair_ask(exfat, ER_BS_CHECKSUM,
+                               "checksums of boot sector are not correct. "
+                               "%#x, but expected %#x",
+                               le32_to_cpu(((__le32 *)sect)[i]), checksum)) {
+                               goto out_write;
+                       } else {
+                               ret = -EINVAL;
+                               goto out;
+                       }
                }
-               iter->next_read_offset += iter->read_size;
        }
+out:
+       free(sect);
+       return ret;
 
-       if (ith + 1 > iter->max_skip_dentries)
-               iter->max_skip_dentries = ith + 1;
+out_write:
+       for (i = 0; i < size/sizeof(checksum); i++)
+               ((__le32 *)sect)[i] = cpu_to_le32(checksum);
 
-       *dentry = (struct exfat_dentry *) (iter->dentries + de_next_offset);
+       if (exfat_write(exfat->blk_dev->dev_fd,
+                       sect, size, size * 11) != size) {
+               exfat_err("failed to write checksum sector\n");
+               free(sect);
+               return -EIO;
+       }
+       free(sect);
        return 0;
 }
 
-/*
- * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get
- * since the last call of exfat_de_iter_advance
- */
-static int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries)
+static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty)
 {
-       if (skip_dentries != iter->max_skip_dentries)
-               return -EINVAL;
+       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;
+       else
+               flags &= ~0x02;
+
+       exfat->bs->bsx.vol_flags = cpu_to_le16(flags);
+       if (exfat_write(exfat->blk_dev->dev_fd, exfat->bs,
+                       sizeof(struct pbr), 0) != (ssize_t)sizeof(struct pbr)) {
+               exfat_err("failed to set VolumeDirty\n");
+               return -EIO;
+       }
 
-       iter->max_skip_dentries = 0;
-       iter->de_file_offset = iter->de_file_offset +
-                               skip_dentries * sizeof(struct exfat_dentry);
+       if (fsync(exfat->blk_dev->dev_fd) != 0) {
+               exfat_err("failed to set VolumeDirty\n");
+               return -EIO;
+       }
        return 0;
 }
 
-static off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter)
+static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr)
 {
-       return iter->de_file_offset;
-}
+       struct pbr *bs;
 
-static bool check_inode(struct exfat *exfat, struct exfat_inode *parent,
-                                               struct exfat_inode *node)
-{
-       bool ret = false;
+       bs = (struct pbr *)malloc(sizeof(struct pbr));
+       if (!bs) {
+               exfat_err("failed to allocate memory\n");
+               return -ENOMEM;
+       }
 
-       if (node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) {
-               resolve_path_parent(&path_resolve_ctx, parent, node);
-               exfat_err("file is empty, but first cluster is %#x: %s\n",
-                               node->first_clus, path_resolve_ctx.local_path);
-               ret = false;
+       if (exfat_read(bd->dev_fd, bs, sizeof(*bs), 0) !=
+                       (ssize_t)sizeof(*bs)) {
+               exfat_err("failed to read a boot sector\n");
+               free(bs);
+               return -EIO;
        }
 
-       if (node->size > 0 && exfat_invalid_clus(exfat, node->first_clus)) {
-               resolve_path_parent(&path_resolve_ctx, parent, node);
-               exfat_err("first cluster %#x is invalid: %s\n",
-                               node->first_clus, path_resolve_ctx.local_path);
-               ret = false;
+       if (memcmp(bs->bpb.oem_name, "EXFAT   ", 8) != 0) {
+               exfat_err("failed to find exfat file system.\n");
+               goto err;
        }
 
-       if (node->size > le32_to_cpu(exfat->bs->bsx.clu_count) *
-                               EXFAT_CLUSTER_SIZE(exfat->bs)) {
-               resolve_path_parent(&path_resolve_ctx, parent, node);
-               exfat_err("size %" PRIu64 " is greater than cluster heap: %s\n",
-                               node->size, path_resolve_ctx.local_path);
-               ret = false;
+       if (EXFAT_SECTOR_SIZE(bs) < 512 || EXFAT_SECTOR_SIZE(bs) > 4 * KB) {
+               exfat_err("too small or big sector size: %d\n",
+                               EXFAT_SECTOR_SIZE(bs));
+               goto err;
        }
 
-       if (node->size == 0 && node->is_contiguous) {
-               resolve_path_parent(&path_resolve_ctx, parent, node);
-               exfat_err("empty, but marked as contiguous: %s\n",
-                                       path_resolve_ctx.local_path);
-               ret = false;
+       if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) {
+               exfat_err("too big cluster size: %d\n", EXFAT_CLUSTER_SIZE(bs));
+               goto err;
        }
 
-       if ((node->attr & ATTR_SUBDIR) &&
-                       node->size % EXFAT_CLUSTER_SIZE(exfat->bs) != 0) {
-               resolve_path_parent(&path_resolve_ctx, parent, node);
-               exfat_err("directory size %" PRIu64 " is not divisible by %d: %s\n",
-                               node->size, EXFAT_CLUSTER_SIZE(exfat->bs),
-                               path_resolve_ctx.local_path);
-               ret = false;
+       if (bs->bsx.fs_version[1] != 1 || bs->bsx.fs_version[0] != 0) {
+               exfat_err("unsupported exfat version: %d.%d\n",
+                               bs->bsx.fs_version[1], bs->bsx.fs_version[0]);
+               goto err;
+       }
+
+       if (bs->bsx.num_fats != 1) {
+               exfat_err("unsupported FAT count: %d\n", bs->bsx.num_fats);
+               goto err;
+       }
+
+       if (le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) >
+                       bd->size) {
+               exfat_err("too large sector count: %" PRIu64 "\n, expected: %llu\n",
+                               le64_to_cpu(bs->bsx.vol_length),
+                               bd->num_sectors);
+               goto err;
        }
 
-       if (!node->is_contiguous && !inode_check_clus_chain(exfat, node)) {
-               resolve_path_parent(&path_resolve_ctx, parent, node);
-               exfat_err("corrupted cluster chain: %s\n",
-                               path_resolve_ctx.local_path);
-               ret = false;
+       if (le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) >
+                       bd->size) {
+               exfat_err("too large cluster count: %u, expected: %u\n",
+                               le32_to_cpu(bs->bsx.clu_count),
+                               bd->num_clusters);
+               goto err;
        }
 
-       return ret;
+       *pbr = bs;
+       return 0;
+err:
+       free(bs);
+       return -EINVAL;
 }
 
 static void dentry_calc_checksum(struct exfat_dentry *dentry,
@@ -788,13 +785,68 @@ static __le16 file_calc_checksum(struct exfat_de_iter *iter)
        return checksum;
 }
 
+/*
+ * return 0 if there are no errors, or 1 if errors are fixed, or
+ * an error code
+ */
+static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
+{
+       struct exfat *exfat = iter->exfat;
+       struct exfat_dentry *dentry;
+       int ret = 0;
+       uint16_t checksum;
+
+       ret = check_clus_chain(exfat, node);
+       if (ret < 0)
+               return ret;
+
+       if (node->size > le32_to_cpu(exfat->bs->bsx.clu_count) *
+                               (uint64_t)exfat->clus_size) {
+               fsck_err(iter->parent, node,
+                       "size %" PRIu64 " is greater than cluster heap\n",
+                       node->size);
+               ret = -EINVAL;
+       }
+
+       if (node->size == 0 && node->is_contiguous) {
+               if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT,
+                               "empty, but has no Fat chain\n")) {
+                       exfat_de_iter_get_dirty(iter, 1, &dentry);
+                       dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS;
+                       ret = 1;
+               } else
+                       ret = -EINVAL;
+       }
+
+       if ((node->attr & ATTR_SUBDIR) &&
+                       node->size % exfat->clus_size != 0) {
+               fsck_err(iter->parent, node,
+                       "directory size %" PRIu64 " is not divisible by %d\n",
+                       node->size, exfat->clus_size);
+               ret = -EINVAL;
+       }
+
+       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
+                       ret = -EINVAL;
+       }
+
+       return ret;
+}
+
 static int read_file_dentries(struct exfat_de_iter *iter,
                        struct exfat_inode **new_node, int *skip_dentries)
 {
        struct exfat_dentry *file_de, *stream_de, *name_de;
        struct exfat_inode *node;
        int i, ret;
-       __le16 checksum;
 
        /* TODO: mtime, atime, ... */
 
@@ -834,27 +886,23 @@ static int read_file_dentries(struct exfat_de_iter *iter,
                        sizeof(name_de->name_unicode));
        }
 
-       checksum = file_calc_checksum(iter);
-       if (le16_to_cpu(file_de->file_checksum) != checksum) {
-               exfat_err("invalid checksum. 0x%x != 0x%x\n",
-                       le16_to_cpu(file_de->file_checksum),
-                       le16_to_cpu(checksum));
-               ret = -EINVAL;
-               goto err;
-       }
-
-       node->size = le64_to_cpu(stream_de->stream_size);
        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);
 
-       if (le64_to_cpu(stream_de->stream_valid_size) > node->size) {
-               resolve_path_parent(&path_resolve_ctx, iter->parent, node);
-               exfat_err("valid size %" PRIu64 " greater than size %" PRIu64 ": %s\n",
-                       le64_to_cpu(stream_de->stream_valid_size), node->size,
-                       path_resolve_ctx.local_path);
-               ret = -EINVAL;
-               goto err;
+       if (node->size < le64_to_cpu(stream_de->stream_valid_size)) {
+               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)) {
+                       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);
@@ -876,21 +924,17 @@ static int read_file(struct exfat_de_iter *de_iter,
        *new_node = NULL;
 
        ret = read_file_dentries(de_iter, &node, dentry_count);
-       if (ret) {
-               exfat_err("corrupted file directory entries.\n");
+       if (ret)
                return ret;
-       }
 
-       ret = check_inode(de_iter->exfat, de_iter->parent, node);
-       if (ret) {
-               exfat_err("corrupted file directory entries.\n");
+       ret = check_inode(de_iter, node);
+       if (ret < 0) {
                free_exfat_inode(node);
-               return ret;
+               return -EINVAL;
        }
 
-       node->dentry_file_offset = exfat_de_iter_file_offset(de_iter);
        *new_node = node;
-       return 0;
+       return ret;
 }
 
 static bool read_volume_label(struct exfat_de_iter *iter)
@@ -922,11 +966,27 @@ static bool read_volume_label(struct exfat_de_iter *iter)
        return true;
 }
 
-static bool read_alloc_bitmap(struct exfat_de_iter *iter)
+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++;
+       }
+}
+
+static bool read_bitmap(struct exfat_de_iter *iter)
 {
        struct exfat_dentry *dentry;
        struct exfat *exfat;
-       ssize_t alloc_bitmap_size;
 
        exfat = iter->exfat;
        if (exfat_de_iter_get(iter, 0, &dentry))
@@ -936,38 +996,30 @@ static bool read_alloc_bitmap(struct exfat_de_iter *iter)
                        le32_to_cpu(dentry->bitmap_start_clu),
                        le64_to_cpu(dentry->bitmap_size));
 
-       exfat->bit_count = le32_to_cpu(exfat->bs->bsx.clu_count);
-
        if (le64_to_cpu(dentry->bitmap_size) <
-                       DIV_ROUND_UP(exfat->bit_count, 8)) {
+                       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;
        }
-       if (exfat_invalid_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
+       if (!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;
        }
 
-       /* TODO: bitmap could be very large. */
-       alloc_bitmap_size = EXFAT_BITMAP_SIZE(exfat->bit_count);
-       exfat->alloc_bitmap = (__u32 *)malloc(alloc_bitmap_size);
-       if (!exfat->alloc_bitmap) {
-               exfat_err("failed to allocate bitmap\n");
-               return false;
-       }
+       exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
+       exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
 
-       if (exfat_read(exfat->blk_dev->dev_fd,
-                       exfat->alloc_bitmap, alloc_bitmap_size,
-                       exfat_c2o(exfat,
-                       le32_to_cpu(dentry->bitmap_start_clu))) !=
-                       alloc_bitmap_size) {
-               exfat_err("failed to read bitmap\n");
-               free(exfat->alloc_bitmap);
-               exfat->alloc_bitmap = NULL;
+       exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu),
+                       DIV_ROUND_UP(exfat->disk_bitmap_size,
+                       exfat->clus_size));
+
+       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 true;
 }
@@ -985,7 +1037,7 @@ static bool read_upcase_table(struct exfat_de_iter *iter)
        if (exfat_de_iter_get(iter, 0, &dentry))
                return false;
 
-       if (exfat_invalid_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
+       if (!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;
@@ -1022,6 +1074,10 @@ static bool read_upcase_table(struct exfat_de_iter *iter)
                return false;
        }
 
+       exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu),
+                       DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
+                       exfat->clus_size));
+
        free(upcase);
        return true;
 }
@@ -1032,11 +1088,8 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
        struct exfat_inode *node = NULL;
        struct exfat_dentry *dentry;
        int dentry_count;
-       struct list_head sub_dir_list;
        struct exfat_de_iter *de_iter;
 
-       INIT_LIST_HEAD(&sub_dir_list);
-
        de_iter = &exfat->de_iter;
        ret = exfat_de_iter_init(de_iter, exfat, dir);
        if (ret == EOF)
@@ -1049,7 +1102,8 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
                if (ret == EOF) {
                        break;
                } else if (ret) {
-                       exfat_err("failed to get a dentry. %d\n", ret);
+                       fsck_err(dir->parent, dir,
+                               "failed to get a dentry. %d\n", ret);
                        goto err;
                }
 
@@ -1058,15 +1112,18 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
                switch (dentry->type) {
                case EXFAT_FILE:
                        ret = read_file(de_iter, &node, &dentry_count);
-                       if (ret) {
-                               exfat_err("failed to verify file. %d\n", ret);
+                       if (ret < 0) {
+                               exfat_stat.error_count++;
                                goto err;
+                       } 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, &sub_dir_list);
+                               list_add_tail(&node->list, &exfat->dir_list);
                        } else
                                free_exfat_inode(node);
                        break;
@@ -1078,7 +1135,7 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
                        }
                        break;
                case EXFAT_BITMAP:
-                       if (!read_alloc_bitmap(de_iter)) {
+                       if (!read_bitmap(de_iter)) {
                                exfat_err(
                                        "failed to verify allocation bitmap\n");
                                ret = -EINVAL;
@@ -1093,9 +1150,10 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
                                goto err;
                        }
                        break;
+               case EXFAT_LAST:
+                       goto out;
                default:
-                       if (IS_EXFAT_DELETED(dentry->type) ||
-                                       (dentry->type == EXFAT_UNUSED))
+                       if (IS_EXFAT_DELETED(dentry->type))
                                break;
                        exfat_err("unknown entry type. 0x%x\n", dentry->type);
                        ret = -EINVAL;
@@ -1104,14 +1162,137 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir)
 
                exfat_de_iter_advance(de_iter, dentry_count);
        }
-       list_splice(&sub_dir_list, &exfat->dir_list);
+out:
+       exfat_de_iter_flush(de_iter);
        return 0;
 err:
        inode_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)
+{
+       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;
+               }
+
+               /* 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;
+}
+
+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)
+                       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;
+       }
+       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;
+}
+
 /*
  * for each directory in @dir_list.
  * 1. read all dentries and allocate exfat_nodes for files and directories.
@@ -1119,14 +1300,14 @@ err:
  * 2. free all of file exfat_nodes.
  * 3. if the directory does not have children, free its exfat_node.
  */
-static bool exfat_filesystem_check(struct exfat *exfat)
+static int exfat_filesystem_check(struct exfat *exfat)
 {
        struct exfat_inode *dir;
-       bool ret = true;
+       int ret = 0, dir_errors;
 
        if (!exfat->root) {
                exfat_err("root is NULL\n");
-               return false;
+               return -ENOENT;
        }
 
        list_add(&exfat->root->list, &exfat->dir_list);
@@ -1135,20 +1316,19 @@ static bool exfat_filesystem_check(struct exfat *exfat)
                dir = list_entry(exfat->dir_list.next, struct exfat_inode, list);
 
                if (!(dir->attr & ATTR_SUBDIR)) {
-                       resolve_path(&path_resolve_ctx, dir);
-                       ret = false;
-                       exfat_err("failed to travel directories. "
-                                       "the node is not directory: %s\n",
-                                       path_resolve_ctx.local_path);
+                       fsck_err(dir->parent, dir,
+                               "failed to travel directories. "
+                               "the node is not directory\n");
+                       ret = -EINVAL;
                        goto out;
                }
 
-               if (read_children(exfat, dir)) {
+               dir_errors = read_children(exfat, dir);
+               if (dir_errors) {
                        resolve_path(&path_resolve_ctx, dir);
-                       ret = false;
-                       exfat_err("failed to check dentries: %s\n",
+                       exfat_debug("failed to check dentries: %s\n",
                                        path_resolve_ctx.local_path);
-                       goto out;
+                       ret = dir_errors;
                }
 
                list_del(&dir->list);
@@ -1158,10 +1338,12 @@ static bool exfat_filesystem_check(struct exfat *exfat)
 out:
        exfat_free_dir_list(exfat);
        exfat->root = NULL;
+       if (exfat->dirty_fat && reclaim_free_clusters(exfat))
+               return -EIO;
        return ret;
 }
 
-static bool exfat_root_dir_check(struct exfat *exfat)
+static int exfat_root_dir_check(struct exfat *exfat)
 {
        struct exfat_inode *root;
        clus_t clus_count;
@@ -1169,55 +1351,73 @@ static bool exfat_root_dir_check(struct exfat *exfat)
        root = alloc_exfat_inode(ATTR_SUBDIR);
        if (!root) {
                exfat_err("failed to allocate memory\n");
-               return false;
+               return -ENOMEM;
        }
 
        root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
-       if (!inode_get_clus_count(exfat, root, &clus_count)) {
+       if (!root_get_clus_count(exfat, root, &clus_count)) {
                exfat_err("failed to follow the cluster chain of root\n");
-               goto err;
+               free_exfat_inode(root);
+               return -EINVAL;
        }
-       root->size = clus_count * EXFAT_CLUSTER_SIZE(exfat->bs);
+       root->size = clus_count * exfat->clus_size;
 
        exfat->root = root;
        exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n",
                root->first_clus, root->size);
-       return true;
-err:
-       free_exfat_inode(root);
-       exfat->root = NULL;
-       return false;
+       return 0;
 }
 
-void exfat_show_info(struct exfat *exfat)
+static char *bytes_to_human_readable(size_t bytes)
 {
-       exfat_info("Bytes per sector: %d\n",
-                       1 << exfat->bs->bsx.sect_size_bits);
-       exfat_info("Sectors per cluster: %d\n",
-                       1 << exfat->bs->bsx.sect_per_clus_bits);
-       exfat_info("Cluster heap count: %d(0x%x)\n",
-                       le32_to_cpu(exfat->bs->bsx.clu_count),
-                       le32_to_cpu(exfat->bs->bsx.clu_count));
-       exfat_info("Cluster heap offset: %#x\n",
-                       le32_to_cpu(exfat->bs->bsx.clu_offset));
+       static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"};
+       static char buf[15*4];
+       unsigned int i, shift, quoti, remain;
+
+       shift = 0;
+       for (i = 0; i < sizeof(units)/sizeof(units[0]); i++) {
+               if (bytes / (1ULL << (shift + 10)) == 0)
+                       break;
+               shift += 10;
+       }
+
+       quoti = (unsigned int)(bytes / (1ULL << shift));
+       remain = 0;
+       if (shift > 0) {
+               remain = (unsigned int)
+                       ((bytes & ((1ULL << shift) - 1)) >> (shift - 10));
+               remain = (remain * 100) / 1024;
+       }
+
+       snprintf(buf, sizeof(buf), "%u.%02u %s", quoti, remain, units[i]);
+       return buf;
 }
 
-void exfat_show_stat(void)
+static void exfat_show_info(struct exfat *exfat, const char *dev_name,
+                       int errors)
 {
-       exfat_debug("Found directories: %ld\n", exfat_stat.dir_count);
-       exfat_debug("Found files: %ld\n", exfat_stat.file_count);
-       exfat_debug("Found leak directories: %ld\n",
-                       exfat_stat.dir_count - exfat_stat.dir_free_count);
-       exfat_debug("Found leak files: %ld\n",
-                       exfat_stat.file_count - exfat_stat.file_free_count);
+       exfat_info("sector size:  %s\n",
+               bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits));
+       exfat_info("cluster size: %s\n",
+               bytes_to_human_readable(exfat->clus_size));
+       exfat_info("volume size:  %s\n",
+               bytes_to_human_readable(exfat->blk_dev->size));
+
+       printf("%s: %s. directories %ld, files %ld\n", dev_name,
+                       errors ? "checking stopped" : "clean",
+                       exfat_stat.dir_count, exfat_stat.file_count);
+       if (errors || exfat->dirty)
+               printf("%s: files corrupted %ld, files fixed %ld\n", dev_name,
+                       exfat_stat.error_count, exfat_stat.fixed_count);
 }
 
 int main(int argc, char * const argv[])
 {
-       int c, ret;
        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;
 
        memset(&ui, 0, sizeof(ui));
@@ -1229,25 +1429,27 @@ int main(int argc, char * const argv[])
                exfat_err("failed to init locale/codeset\n");
 
        opterr = 0;
-       while ((c = getopt_long(argc, argv, "rynVvh", opts, NULL)) != EOF) {
+       while ((c = getopt_long(argc, argv, "rynpVvh", opts, NULL)) != EOF) {
                switch (c) {
                case 'n':
-                       if (ui.options & FSCK_OPTS_REPAIR)
+                       if (ui.options & FSCK_OPTS_REPAIR_ALL)
                                usage(argv[0]);
                        ui.options |= FSCK_OPTS_REPAIR_NO;
-                       ui.ei.writeable = false;
                        break;
                case 'r':
-                       if (ui.options & FSCK_OPTS_REPAIR)
+                       if (ui.options & FSCK_OPTS_REPAIR_ALL)
                                usage(argv[0]);
                        ui.options |= FSCK_OPTS_REPAIR_ASK;
-                       ui.ei.writeable = true;
                        break;
                case 'y':
-                       if (ui.options & FSCK_OPTS_REPAIR)
+                       if (ui.options & FSCK_OPTS_REPAIR_ALL)
                                usage(argv[0]);
                        ui.options |= FSCK_OPTS_REPAIR_YES;
-                       ui.ei.writeable = true;
+                       break;
+               case 'p':
+                       if (ui.options & FSCK_OPTS_REPAIR_ALL)
+                               usage(argv[0]);
+                       ui.options |= FSCK_OPTS_REPAIR_AUTO;
                        break;
                case 'V':
                        version_only = true;
@@ -1264,12 +1466,18 @@ int main(int argc, char * const argv[])
        }
 
        show_version();
-       if (version_only)
-               exit(FSCK_EXIT_SYNTAX_ERROR);
-
        if (optind != argc - 1)
                usage(argv[0]);
 
+       if (version_only)
+               exit(FSCK_EXIT_SYNTAX_ERROR);
+       if (ui.options & FSCK_OPTS_REPAIR_WRITE)
+               ui.ei.writeable = true;
+       else {
+               ui.options |= FSCK_OPTS_REPAIR_NO;
+               ui.ei.writeable = false;
+       }
+
        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) {
@@ -1277,44 +1485,58 @@ int main(int argc, char * const argv[])
                return FSCK_EXIT_OPERATION_ERROR;
        }
 
-       exfat = alloc_exfat(&bd);
+       ret = read_boot_region(&bd, &bs);
+       if (ret)
+               goto err;
+
+       exfat = alloc_exfat(&bd, bs);
        if (!exfat) {
-               ret = FSCK_EXIT_OPERATION_ERROR;
+               ret = -ENOMEM;
                goto err;
        }
        exfat->options = ui.options;
 
-       exfat_debug("verifying boot regions...\n");
-       if (!exfat_boot_region_check(exfat)) {
-               exfat_err("failed to verify boot regions.\n");
-               ret = FSCK_EXIT_ERRORS_LEFT;
+       if (exfat_mark_volume_dirty(exfat, true)) {
+               ret = -EIO;
                goto err;
        }
 
-       exfat_show_info(exfat);
+       ret = boot_region_checksum(exfat);
+       if (ret)
+               goto err;
 
        exfat_debug("verifying root directory...\n");
-       if (!exfat_root_dir_check(exfat)) {
+       ret = exfat_root_dir_check(exfat);
+       if (ret) {
                exfat_err("failed to verify root directory.\n");
-               ret = FSCK_EXIT_ERRORS_LEFT;
                goto out;
        }
 
        exfat_debug("verifying directory entries...\n");
-       if (!exfat_filesystem_check(exfat)) {
-               exfat_err("failed to verify directory entries.\n");
-               ret = FSCK_EXIT_ERRORS_LEFT;
+       ret = exfat_filesystem_check(exfat);
+       if (ret)
+               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 (ui.ei.writeable)
-               fsync(bd.dev_fd);
-       printf("%s: clean\n", ui.ei.dev_name);
-       ret = FSCK_EXIT_NO_ERRORS;
 out:
-       exfat_show_stat();
+       exfat_show_info(exfat, ui.ei.dev_name, ret);
 err:
+       if (ret == -EINVAL)
+               exit_code = FSCK_EXIT_ERRORS_LEFT;
+       else if (ret)
+               exit_code = FSCK_EXIT_OPERATION_ERROR;
+       else if (exfat->dirty)
+               exit_code = FSCK_EXIT_CORRECTED;
+       else
+               exit_code = FSCK_EXIT_NO_ERRORS;
+
        free_exfat(exfat);
        close(bd.dev_fd);
-       return ret;
+       return exit_code;
 }
index ef46fa797d1ce277d2362edea6adf846d444ce8c..6c91face740c25f5ba10dc788d4305cf79338664 100644 (file)
@@ -20,19 +20,29 @@ struct exfat_inode {
        __u16                   attr;
        uint64_t                size;
        bool                    is_contiguous;
-       off_t                   dentry_file_offset;
        __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;
-       unsigned char           *dentries;      /* cluster * 2 allocated */
-       unsigned int            read_size;      /* cluster size */
-       off_t                   de_file_offset; /* offset in dentries buffer */
+       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;
 };
@@ -41,23 +51,49 @@ enum fsck_ui_options {
        FSCK_OPTS_REPAIR_ASK    = 0x01,
        FSCK_OPTS_REPAIR_YES    = 0x02,
        FSCK_OPTS_REPAIR_NO     = 0x04,
-       FSCK_OPTS_REPAIR        = 0x07,
+       FSCK_OPTS_REPAIR_AUTO   = 0x08,
+       FSCK_OPTS_REPAIR_WRITE  = 0x0b,
+       FSCK_OPTS_REPAIR_ALL    = 0x0f,
 };
 
 struct exfat {
        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;
-       __u32                   *alloc_bitmap;
-       __u64                   bit_count;
+       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
index bbfb75d1397aa1128cb3212b43cf91073edf4ac7..19a2b0d79bd4d24d8b932961e71c96a0a1614ef4 100644 (file)
@@ -4,6 +4,7 @@
  */
 #include <stdio.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "exfat_ondisk.h"
 #include "libexfat.h"
 
 struct exfat_repair_problem {
        er_problem_code_t       prcode;
-       const char              *description;
-       bool (*fix_problem)(struct exfat *exfat,
-                       union exfat_repair_context *rctx);
+       unsigned int            flags;
+       unsigned int            prompt_type;
 };
 
-static bool fix_bs_checksum(struct exfat *exfat,
-                       union exfat_repair_context *rctx)
-{
-       unsigned int size;
-       unsigned int i;
+/* Problem flags */
+#define ERF_PREEN_YES          0x00000001
+#define ERF_DEFAULT_YES                0x00000002
+#define ERF_DEFAULT_NO         0x00000004
 
-       size = EXFAT_SECTOR_SIZE(exfat->bs);
-       for (i = 0; i < size/sizeof(__le32); i++) {
-               ((__le32 *)rctx->bs_checksum.checksum_sect)[i] =
-                               rctx->bs_checksum.checksum;
-       }
+/* Prompt types */
+#define ERP_FIX                        0x00000001
+#define ERP_TRUNCATE           0x00000002
 
-       if (exfat_write(exfat->blk_dev->dev_fd,
-                       rctx->bs_checksum.checksum_sect,
-                       size, size * 11) != size) {
-               exfat_err("failed to write checksum sector\n");
-               return false;
-       }
-
-       return true;
-}
+static const char *prompts[] = {
+       "Repair",
+       "Fix",
+       "Truncate",
+};
 
 static struct exfat_repair_problem problems[] = {
-       {ER_BS_CHECKSUM,
-               "the checksum of boot sector is not correct",
-               fix_bs_checksum},
+       {ER_BS_CHECKSUM, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX},
+       {ER_DE_CHECKSUM, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX},
+       {ER_FILE_VALID_SIZE, ERF_DEFAULT_YES | 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_ZERO_NOFAT, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX},
 };
 
 static struct exfat_repair_problem *find_problem(er_problem_code_t prcode)
@@ -59,42 +58,45 @@ static struct exfat_repair_problem *find_problem(er_problem_code_t prcode)
 
 static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr)
 {
+       bool repair = false;
        char answer[8];
 
-       switch (exfat->options & FSCK_OPTS_REPAIR) {
-       case FSCK_OPTS_REPAIR_ASK:
-               do {
-                       printf("%s: Fix (y/N)?", pr->description);
-                       fflush(stdout);
-
-                       if (fgets(answer, sizeof(answer), stdin)) {
-                               if (strcasecmp(answer, "Y\n") == 0)
-                                       return true;
-                               else if (strcasecmp(answer, "\n") == 0 ||
-                                       strcasecmp(answer, "N\n") == 0)
-                                       return false;
-                       }
-               } while (1);
-               return false;
-       case FSCK_OPTS_REPAIR_YES:
-               return true;
-       case FSCK_OPTS_REPAIR_NO:
-       case 0:
-       default:
-               return false;
+       if (exfat->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)
+               repair = true;
+       else {
+               if (exfat->options & FSCK_OPTS_REPAIR_ASK) {
+                       do {
+                               printf(". %s (y/N)? ",
+                                       prompts[pr->prompt_type]);
+                               fflush(stdout);
+
+                               if (fgets(answer, sizeof(answer), stdin)) {
+                                       if (strcasecmp(answer, "Y\n") == 0)
+                                               return true;
+                                       else if (strcasecmp(answer, "\n") == 0
+                                               || strcasecmp(answer, "N\n") == 0)
+                                               return false;
+                               }
+                       } while (1);
+               } else if (exfat->options & FSCK_OPTS_REPAIR_AUTO &&
+                               pr->flags & ERF_PREEN_YES)
+                       repair = true;
        }
-       return false;
+
+       printf(". %s (y/N)? %c\n", prompts[pr->prompt_type],
+               repair ? 'y' : 'n');
+       return repair;
 }
 
-bool exfat_repair(struct exfat *exfat, er_problem_code_t prcode,
-                       union exfat_repair_context *rctx)
+bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
+                       const char *desc, ...)
 {
        struct exfat_repair_problem *pr = NULL;
-       int need_repair;
-
-       need_repair = ask_repair(exfat, pr);
-       if (!need_repair)
-               return false;
+       va_list ap;
 
        pr = find_problem(prcode);
        if (!pr) {
@@ -102,5 +104,15 @@ bool exfat_repair(struct exfat *exfat, er_problem_code_t prcode,
                return false;
        }
 
-       return pr->fix_problem(exfat, rctx);
+       va_start(ap, desc);
+       vprintf(desc, ap);
+       va_end(ap);
+
+       if (ask_repair(exfat, pr)) {
+               if (pr->prompt_type & ERP_TRUNCATE)
+                       exfat->dirty_fat = true;
+               exfat->dirty = true;
+               return true;
+       } else
+               return false;
 }
index 4c5676382ac76ad429316eee0f73b66fba8753d7..6a34b9be985b527c2fcdd74027fdbed34d39d689 100644 (file)
@@ -6,17 +6,18 @@
 #define _REPAIR_H
 
 #define ER_BS_CHECKSUM                 0x00000001
+#define ER_DE_CHECKSUM                 0x00001001
+#define ER_FILE_VALID_SIZE             0x00002001
+#define ER_FILE_INVALID_CLUS           0x00002002
+#define ER_FILE_FIRST_CLUS             0x00002003
+#define ER_FILE_SMALLER_SIZE           0x00002004
+#define ER_FILE_LARGER_SIZE            0x00002005
+#define ER_FILE_DUPLICATED_CLUS                0x00002006
+#define ER_FILE_ZERO_NOFAT             0x00002007
 
 typedef unsigned int er_problem_code_t;
 
-union exfat_repair_context {
-       struct {
-               __le32          checksum;
-               void            *checksum_sect;
-       } bs_checksum;
-};
-
-bool exfat_repair(struct exfat *exfat, er_problem_code_t prcode,
-                       union exfat_repair_context *rctx);
+bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
+               const char *fmt, ...);
 
 #endif
index 70546a321fd8cc066d0a37ac21e9429b794247d2..163bef01c2c4c3eefc2a0f09859341438a9619f0 100644 (file)
@@ -44,7 +44,7 @@
 #define MSDOS_DELETED          0xE5    /* deleted mark */
 #define MSDOS_UNUSED           0x00    /* end of directory */
 
-#define EXFAT_UNUSED           0x00    /* end of directory */
+#define EXFAT_LAST             0x00    /* end of directory */
 #define EXFAT_DELETE           ~(0x80)
 #define IS_EXFAT_DELETED(x)    ((x) < 0x80) /* deleted file (0x01~0x7F) */
 #define EXFAT_INVAL            0x80    /* invalid value */
index 56053b465ccc15810483cd2f681f20cb60c31f05..6a3ecb8a93ce765f8054a9b72c8c4ebdd29749c0 100644 (file)
@@ -28,6 +28,8 @@
 
 #define EXFAT_MAX_NUM_CLUSTER          (0xFFFFFFF5)
 
+#define DEFAULT_BOUNDARY_ALIGNMENT     (1024*1024)
+
 #define DEFAULT_SECTOR_SIZE    (512)
 
 #define VOLUME_LABEL_BUFFER_SIZE       (VOLUME_LABEL_MAX_LEN*MB_LEN_MAX+1)
@@ -59,6 +61,7 @@ struct exfat_user_input {
        bool writeable;
        unsigned int cluster_size;
        unsigned int sec_per_clu;
+       unsigned int boundary_align;
        bool quick;
        __u16 volume_label[VOLUME_LABEL_MAX_LEN];
        int volume_label_len;
@@ -79,6 +82,7 @@ int exfat_get_blk_dev_info(struct exfat_user_input *ui,
 ssize_t exfat_read(int fd, void *buf, size_t size, off_t offset);
 ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset);
 
+size_t exfat_utf16_len(const __le16 *str, size_t max_size);
 ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size);
 ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
                        char *out_str, size_t out_size);
@@ -93,19 +97,19 @@ extern unsigned int print_level;
 #define EXFAT_INFO     (2)
 #define EXFAT_DEBUG    (3)
 
-#define exfat_msg(level, fmt, ...)                                             \
-       do {                                                                    \
-               if (print_level >= level) {                                     \
-                       if (print_level == EXFAT_INFO)                          \
-                               printf(fmt, ##__VA_ARGS__);             \
-                       else                                                    \
-                               printf("[%s:%4d] " fmt,                         \
-                                       __func__, __LINE__, ##__VA_ARGS__);     \
-               }                                                               \
-       } while (0)                                                             \
-
-#define exfat_err(fmt, ...)    exfat_msg(EXFAT_ERROR, fmt, ##__VA_ARGS__)
-#define exfat_info(fmt, ...)   exfat_msg(EXFAT_INFO, fmt, ##__VA_ARGS__)
-#define exfat_debug(fmt, ...)  exfat_msg(EXFAT_DEBUG, fmt, ##__VA_ARGS__)
+#define exfat_msg(level, dir, fmt, ...)                                        \
+       do {                                                            \
+               if (print_level >= level) {                             \
+                       fprintf(dir, fmt, ##__VA_ARGS__);               \
+               }                                                       \
+       } while (0)                                                     \
+
+#define exfat_err(fmt, ...)    exfat_msg(EXFAT_ERROR, stderr,          \
+                                       fmt, ##__VA_ARGS__)
+#define exfat_info(fmt, ...)   exfat_msg(EXFAT_INFO, stdout,           \
+                                       fmt, ##__VA_ARGS__)
+#define exfat_debug(fmt, ...)  exfat_msg(EXFAT_DEBUG, stdout,          \
+                                       "[%s:%4d] " fmt, __func__,      \
+                                       __LINE__, ##__VA_ARGS__)
 
 #endif /* !_LIBEXFAT_H */
index 1ec8a6316397bb1eb924469e4bccd50ba9287937..e4639039c8dfd3e3a6297b8124573e50f1866adc 100644 (file)
@@ -5,6 +5,6 @@
 
 #ifndef _VERSION_H
 
-#define EXFAT_PROGS_VERSION "1.0.3"
+#define EXFAT_PROGS_VERSION "1.0.4"
 
 #endif /* !_VERSION_H */
index 24f682b58673c219c73a46c04ba5a2f19b97e922..1c0bbc010e95a46d2bbb594aade5c4bb56bdcd48 100644 (file)
@@ -165,6 +165,9 @@ int exfat_get_blk_dev_info(struct exfat_user_input *ui,
        if (!ui->cluster_size)
                exfat_set_default_cluster_size(bd, ui);
 
+       if (!ui->boundary_align)
+               ui->boundary_align = DEFAULT_BOUNDARY_ALIGNMENT;
+
        if (ioctl(fd, BLKSSZGET, &bd->sector_size) < 0)
                bd->sector_size = DEFAULT_SECTOR_SIZE;
        bd->sector_size_bits = sector_size_bits(bd->sector_size);
@@ -195,6 +198,15 @@ ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset)
        return pwrite(fd, buf, size, offset);
 }
 
+size_t exfat_utf16_len(const __le16 *str, size_t max_size)
+{
+       size_t i = 0;
+
+       while (le16_to_cpu(str[i]) && i < max_size)
+               i++;
+       return i;
+}
+
 ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size)
 {
        size_t mbs_len, out_len, i;
@@ -215,6 +227,7 @@ ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size)
        if (mbstowcs(wcs, in_str, mbs_len+1) == (size_t)-1) {
                if (errno == EINVAL || errno == EILSEQ)
                        exfat_err("invalid character sequence in current locale\n");
+               free(wcs);
                return -errno;
        }
 
@@ -278,7 +291,7 @@ ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
        memset(&ps, 0, sizeof(ps));
 
        /* And then convert wchar_t* string to multibyte char* string */
-       for (i = 0, out_len = 0, c_len = 0; i < wcs_len; i++) {
+       for (i = 0, out_len = 0, c_len = 0; i <= wcs_len; i++) {
                c_len = wcrtomb(c_str, wcs[i], &ps);
                /*
                 * If character is non-representable in current locale then
@@ -307,7 +320,7 @@ ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
        free(wcs);
 
        /* Last iteration of above loop should have produced null byte */
-       if (c_len == 0 || out_str[out_len] != 0) {
+       if (c_len == 0 || out_str[out_len-1] != 0) {
                exfat_err("invalid UTF-16 sequence\n");
                return -errno;
        }
index b6cf3c831900d5e45646439354ba8e8b935e95aa..b02a05601ce31f37d2a0984d6b0d8f4cfd7247b6 100644 (file)
@@ -19,7 +19,7 @@ fsck.exfat \- check an exFAT filesystem
 .B fsck.exfat \-V
 .SH DESCRIPTION
 .B fsck.exfat
-checks an exFAT filesystem and repairs the filesytem
+checks an exFAT filesystem and repairs the filesystem
 depending on the options passed.
 .PP
 .SH OPTIONS
index bc7ab4db2eec43002bc8ea72cfd505d078e99796..0bd8ffc14093ab6629bb9d61b705d2457e394e05 100644 (file)
@@ -4,6 +4,9 @@ mkfs.exfat \- create an exFAT filesystem
 .SH SYNOPSIS
 .B mkfs.exfat
 [
+.B \-b
+.I boundary_alignment
+] [
 .B \-c
 .I cluster_size
 ] [
@@ -36,6 +39,12 @@ SCSI disk, use:
 .PP
 .SH OPTIONS
 .TP
+.BI \-b " boundary_alignment"
+Specify the alignment for FAT and start of cluster.
+Boundary alignment can be specified in m/M for megabytes
+and k/K for kilobytes. It should be a power of two.
+Some media like sdcard need this.
+.TP
 .BI \-c " cluster_size"
 Specify the cluster size. Cluster size can be specified in m/M for megabytes
 and k/K for kilobytes.
index 12dd6d26f05aa3ae26c07e1016eb5c7e300efa3c..c834ed98ddeb8d74cb8dd9a78d3cd91462b4a062 100644 (file)
@@ -31,4 +31,3 @@ Prints verbose debugging information while extracting or tuning parameters of th
 .TP
 .B \-V
 Prints the version number and exits.
-.TP
index 201ba3b4a34347fed8ca9a04313ce05efd879669..7b3e35b3c6ccdc380990825180de67b52f01c363 100644 (file)
@@ -1,5 +1,4 @@
 AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
-LIBS = -lm
 mkfs_exfat_LDADD = $(top_builddir)/lib/libexfat.a
 
 sbin_PROGRAMS = mkfs.exfat
index b18897aad52c0f71f7e9e9cec3a0b4fe47aead32..d43dece072b5b7ca40ab0b988c405ee50ea028b3 100644 (file)
@@ -208,7 +208,7 @@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBOBJS = @LIBOBJS@
-LIBS = -lm
+LIBS = @LIBS@
 LIBTOOL = @LIBTOOL@
 LIPO = @LIPO@
 LN_S = @LN_S@
index 4cafb3e56c59fba1e7d68306384f7dfc7269f612..9fff97811160e3a5bcce8a01814dabf34bc78ce6 100644 (file)
@@ -15,8 +15,8 @@
 #include <getopt.h>
 #include <inttypes.h>
 #include <errno.h>
-#include <math.h>
 #include <locale.h>
+#include <time.h>
 
 #include "exfat_ondisk.h"
 #include "libexfat.h"
 
 struct exfat_mkfs_info finfo;
 
+/* random serial generator based on current time */
+static unsigned int get_new_serial(void)
+{
+       struct timespec ts;
+
+       if (clock_gettime(CLOCK_REALTIME, &ts)) {
+               /* set 0000-0000 on error */
+               ts.tv_sec = 0;
+               ts.tv_nsec = 0;
+       }
+
+       return (unsigned int)(ts.tv_nsec << 12 | ts.tv_sec);
+}
+
 static void exfat_setup_boot_sector(struct pbr *ppbr,
                struct exfat_blk_dev *bd, struct exfat_user_input *ui)
 {
        struct bpb64 *pbpb = &ppbr->bpb;
        struct bsx64 *pbsx = &ppbr->bsx;
+       unsigned int i;
 
        /* Fill exfat BIOS paramemter block */
        pbpb->jmp_boot[0] = 0xeb;
@@ -45,10 +60,13 @@ static void exfat_setup_boot_sector(struct pbr *ppbr,
        pbsx->clu_offset = cpu_to_le32(finfo.clu_byte_off / bd->sector_size);
        pbsx->clu_count = cpu_to_le32(finfo.total_clu_cnt);
        pbsx->root_cluster = cpu_to_le32(finfo.root_start_clu);
-       pbsx->vol_serial = cpu_to_le32(1234);
+       pbsx->vol_serial = cpu_to_le32(finfo.volume_serial);
        pbsx->vol_flags = 0;
        pbsx->sect_size_bits = bd->sector_size_bits;
-       pbsx->sect_per_clus_bits = log2(ui->cluster_size / bd->sector_size);
+       pbsx->sect_per_clus_bits = 0;
+       /* Compute base 2 logarithm of ui->cluster_size / bd->sector_size */
+       for (i = ui->cluster_size / bd->sector_size; i > 1; i /= 2)
+               pbsx->sect_per_clus_bits++;
        pbsx->num_fats = 1;
        /* fs_version[0] : minor and fs_version[1] : major */
        pbsx->fs_version[0] = 0;
@@ -387,6 +405,7 @@ static void usage(void)
        fprintf(stderr, "Usage: mkfs.exfat\n");
        fprintf(stderr, "\t-L | --volume-label=label                              Set volume label\n");
        fprintf(stderr, "\t-c | --cluster-size=size(or suffixed by 'K' or 'M')    Specify cluster size\n");
+       fprintf(stderr, "\t-b | --boundary-align=size(or suffixed by 'K' or 'M')  Specify boundary alignment\n");
        fprintf(stderr, "\t-f | --full-format                                     Full format\n");
        fprintf(stderr, "\t-V | --version                                         Show version\n");
        fprintf(stderr, "\t-v | --verbose                                         Print debug\n");
@@ -398,6 +417,7 @@ static void usage(void)
 static struct option opts[] = {
        {"volume-label",        required_argument,      NULL,   'L' },
        {"cluster-size",        required_argument,      NULL,   'c' },
+       {"boundary-align",      required_argument,      NULL,   'b' },
        {"full-format",         no_argument,            NULL,   'f' },
        {"version",             no_argument,            NULL,   'V' },
        {"verbose",             no_argument,            NULL,   'v' },
@@ -409,14 +429,23 @@ static struct option opts[] = {
 static int exfat_build_mkfs_info(struct exfat_blk_dev *bd,
                struct exfat_user_input *ui)
 {
-       if (ui->cluster_size > DEFAULT_CLUSTER_SIZE)
-               finfo.fat_byte_off = ui->cluster_size;
-       else
-               finfo.fat_byte_off = DEFAULT_CLUSTER_SIZE;
+       int clu_len;
+
+       if (ui->boundary_align < bd->sector_size) {
+               exfat_err("boundary alignment is too small (min %d)\n",
+                               bd->sector_size);
+               return -1;
+       }
+       finfo.fat_byte_off = round_up(24 * bd->sector_size,
+                       ui->boundary_align);
        finfo.fat_byte_len = round_up((bd->num_clusters * sizeof(int)),
                ui->cluster_size);
        finfo.clu_byte_off = round_up(finfo.fat_byte_off + finfo.fat_byte_len,
-               DEFAULT_CLUSTER_SIZE);
+               ui->boundary_align);
+       if (bd->size <= finfo.clu_byte_off) {
+               exfat_err("boundary alignment is too big\n");
+               return -1;
+       }
        finfo.total_clu_cnt = (bd->size - finfo.clu_byte_off) /
                ui->cluster_size;
        if (finfo.total_clu_cnt > EXFAT_MAX_NUM_CLUSTER) {
@@ -426,17 +455,17 @@ static int exfat_build_mkfs_info(struct exfat_blk_dev *bd,
 
        finfo.bitmap_byte_off = finfo.clu_byte_off;
        finfo.bitmap_byte_len = round_up(finfo.total_clu_cnt, 8) / 8;
-       finfo.ut_start_clu = round_up(EXFAT_REVERVED_CLUSTERS *
-               ui->cluster_size + finfo.bitmap_byte_len, ui->cluster_size) /
-               ui->cluster_size;
-       finfo.ut_byte_off = round_up(finfo.bitmap_byte_off +
-               finfo.bitmap_byte_len, ui->cluster_size);
+       clu_len = round_up(finfo.bitmap_byte_len, ui->cluster_size);
+
+       finfo.ut_start_clu = EXFAT_FIRST_CLUSTER + clu_len / ui->cluster_size;
+       finfo.ut_byte_off = finfo.bitmap_byte_off + clu_len;
        finfo.ut_byte_len = EXFAT_UPCASE_TABLE_SIZE;
-       finfo.root_start_clu = round_up(finfo.ut_start_clu * ui->cluster_size
-               + finfo.ut_byte_len, ui->cluster_size) / ui->cluster_size;
-       finfo.root_byte_off = round_up(finfo.ut_byte_off + finfo.ut_byte_len,
-               ui->cluster_size);
+       clu_len = round_up(finfo.ut_byte_len, ui->cluster_size);
+
+       finfo.root_start_clu = finfo.ut_start_clu + clu_len / ui->cluster_size;
+       finfo.root_byte_off = finfo.ut_byte_off + clu_len;
        finfo.root_byte_len = sizeof(struct exfat_dentry) * 3;
+       finfo.volume_serial = get_new_serial();
 
        return 0;
 }
@@ -525,7 +554,7 @@ static int make_exfat(struct exfat_blk_dev *bd, struct exfat_user_input *ui)
        return 0;
 }
 
-static long long parse_cluster_size(const char *size)
+static long long parse_size(const char *size)
 {
        char *data_unit;
        unsigned long long byte_size = strtoull(size, &data_unit, 0);
@@ -539,8 +568,10 @@ static long long parse_cluster_size(const char *size)
        case 'k':
                byte_size <<= 10;
                break;
+       case '\0':
+               break;
        default:
-               exfat_err("Wrong unit input('%c') for cluster size\n",
+               exfat_err("Wrong unit input('%c') for size\n",
                                *data_unit);
                return -EINVAL;
        }
@@ -562,7 +593,7 @@ int main(int argc, char *argv[])
                exfat_err("failed to init locale/codeset\n");
 
        opterr = 0;
-       while ((c = getopt_long(argc, argv, "n:L:c:fVvh", opts, NULL)) != EOF)
+       while ((c = getopt_long(argc, argv, "n:L:c:b:fVvh", opts, NULL)) != EOF)
                switch (c) {
                /*
                 * Make 'n' option fallthrough to 'L' option for for backward
@@ -580,16 +611,31 @@ int main(int argc, char *argv[])
                        break;
                }
                case 'c':
-                       ret = parse_cluster_size(optarg);
+                       ret = parse_size(optarg);
                        if (ret < 0)
                                goto out;
-                       else if (ret > EXFAT_MAX_CLUSTER_SIZE) {
+                       else if (ret & (ret - 1)) {
+                               exfat_err("cluster size(%d) is not a power of 2)\n",
+                                       ret);
+                               goto out;
+                       } else if (ret > EXFAT_MAX_CLUSTER_SIZE) {
                                exfat_err("cluster size(%d) exceeds max cluster size(%d)\n",
                                        ui.cluster_size, EXFAT_MAX_CLUSTER_SIZE);
                                goto out;
                        }
                        ui.cluster_size = ret;
                        break;
+               case 'b':
+                       ret = parse_size(optarg);
+                       if (ret < 0)
+                               goto out;
+                       else if (ret & (ret - 1)) {
+                               exfat_err("boundary align(%d) is not a power of 2)\n",
+                                       ret);
+                               goto out;
+                       }
+                       ui.boundary_align = ret;
+                       break;
                case 'f':
                        ui.quick = false;
                        break;
@@ -622,23 +668,24 @@ int main(int argc, char *argv[])
 
        ret = exfat_build_mkfs_info(&bd, &ui);
        if (ret)
-               goto out;
+               goto close;
 
        ret = exfat_zero_out_disk(&bd, &ui);
        if (ret)
-               goto out;
+               goto close;
 
        ret = make_exfat(&bd, &ui);
        if (ret)
-               goto out;
+               goto close;
 
        exfat_info("Synchronizing...\n");
        ret = fsync(bd.dev_fd);
+close:
+       close(bd.dev_fd);
 out:
        if (!ret)
                exfat_info("\nexFAT format complete!\n");
        else
                exfat_info("\nexFAT format fail!\n");
-       close(bd.dev_fd);
        return ret;
 }
index df31894d4f87997fd90f2e829ea67ad10538ce6b..ffd56e32683cf5ba9c9e482bccb77665b128928c 100644 (file)
@@ -5,9 +5,8 @@
 
 #ifndef _MKFS_H
 
-#define DEFAULT_CLUSTER_SIZE   (1024*1024)
-#define MIN_NUM_SECTOR         (2048)
-#define EXFAT_MAX_CLUSTER_SIZE (32*1024*1024)
+#define MIN_NUM_SECTOR                 (2048)
+#define EXFAT_MAX_CLUSTER_SIZE         (32*1024*1024)
 
 struct exfat_mkfs_info {
        unsigned int total_clu_cnt;
@@ -24,6 +23,7 @@ struct exfat_mkfs_info {
        unsigned int root_byte_off;
        unsigned int root_byte_len;
        unsigned int root_start_clu;
+       unsigned int volume_serial;
 };
 
 extern struct exfat_mkfs_info finfo;