Snap for 7785160 from d96ac075601cde5992cac861758473af7718432e to t-keystone-qcom-release

Change-Id: Icd41e4cb628f5160a77652d571ea7654a29258f6
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6ab3cc2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,42 @@
+dist: bionic
+
+language: c
+
+notifications:
+ - email: true
+
+before_script:
+ - sudo apt-get install linux-headers-$(uname -r)
+ - git clone --branch=exfat-next https://github.com/namjaejeon/exfat_oot
+ - ./.travis_get_mainline_kernel
+ - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
+ - export PATH=/usr/local/lib:$PATH
+
+script:
+ # run checkpatch.pl
+ - git format-patch -20
+ - ./linux/scripts/checkpatch.pl *.patch || true
+ # build & install exfatprogs
+ - ./autogen.sh > /dev/null
+ - ./configure > /dev/null
+ - make -j$((`nproc`+1)) > /dev/null
+ - sudo make install > /dev/null
+ - cd exfat_oot
+ - make > /dev/null
+ - sudo make install > /dev/null
+ - sudo modprobe exfat
+ - sudo mkdir -p /mnt/test
+ # create file/director test
+ - truncate -s 10G test.img
+ - sudo losetup /dev/loop22 test.img
+ - sudo mkfs.exfat /dev/loop22
+ - sudo mount -t exfat /dev/loop22 /mnt/test/
+ - cd /mnt/test/
+ - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done
+ - sync
+ - sudo rm -rf *
+ - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done
+ - sync
+ - sudo rm -rf *
+ - sudo fsck.exfat /dev/loop22
+ - cd -
diff --git a/.travis_get_mainline_kernel b/.travis_get_mainline_kernel
new file mode 100755
index 0000000..76b3a3a
--- /dev/null
+++ b/.travis_get_mainline_kernel
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+#
+# A simple script we are using to get the latest mainline kernel
+# tar ball
+#
+
+wget https://www.kernel.org/releases.json
+if [ $? -ne 0 ]; then
+	echo "Could not download kernel.org/releases.json"
+	exit 1
+fi
+
+VER=$(cat releases.json | python2.7 -c "import sys, json; print json.load(sys.stdin)['latest_stable']['version']")
+if [ $? -ne 0 ]; then
+	echo "Could not parse release.json"
+	exit 1
+fi
+
+if [ "z$VER" = "z" ]; then
+	echo "Could not determine latest release version"
+	exit 1
+fi
+
+MVER=$(echo $VER | cut -d. -f1)
+
+wget https://cdn.kernel.org/pub/linux/kernel/v"$MVER".x/linux-"$VER".tar.gz
+if [ $? -ne 0 ]; then
+	echo "Could not download $VER kernel version"
+	exit 1
+fi
+
+tar xf linux-"$VER".tar.gz
+if [ $? -ne 0 ]; then
+	echo "Could not untar kernel tar ball"
+	exit 1
+fi
+
+mv linux-"$VER" linux
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..e4f801b
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,54 @@
+// Copyright 2020 The Android Open Source Project
+
+package {
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "external_exfatprogs_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-GPL",
+        "SPDX-license-identifier-GPL-2.0",
+        "SPDX-license-identifier-LGPL",
+    ],
+    license_text: [
+        "COPYING",
+    ],
+}
+
+cc_library_headers {
+    name: "libexfatprogs-headers",
+    export_include_dirs: [
+        "include",
+        "mkfs",
+        "fsck",
+        "tune",
+        "label",
+        "dump",
+    ],
+}
+
+cc_defaults {
+    name: "exfatprogs-defaults",
+    header_libs: ["libexfatprogs-headers"],
+    export_header_lib_headers: ["libexfatprogs-headers"],
+    cflags: [
+        "-D_FILE_OFFSET_BITS=64",
+        "-Wno-unused-parameter",
+    ],
+}
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..941c87d
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		            GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		            END OF TERMS AND CONDITIONS
+
+	        How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..92f2a9a
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: exfatprogs
+description:
+    As new exfat filesystem is merged into linux-5.7 kernel, exfatprogs is 
+    created as an official userspace utilities that contain all of the 
+    standard 
+    utilities for creating and fixing and debugging exfat filesystem in linux 
+    system. The goal of exfatprogs is to provide high performance and quality 
+    at the level of exfat utilities in windows. And this software is licensed 
+    under the GNU General Public License Version 2.
+
+third_party {
+  url {
+    type: GIT
+    value: https://github.com/exfatprogs/exfatprogs/
+  }
+  version: 1.1.0
+  last_upgrade_date { year: 2021 month: 4 day: 9 }
+  license_type: RESTRICTED
+}
diff --git a/MODULE_LICENSE_GPL b/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_GPL
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..44f8635
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,25 @@
+## Makefile.am
+
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = lib mkfs fsck tune label dump
+
+# manpages
+dist_man8_MANS =		\
+	manpages/fsck.exfat.8	\
+	manpages/tune.exfat.8	\
+	manpages/mkfs.exfat.8	\
+	manpages/exfatlabel.8	\
+	manpages/dump.exfat.8
+
+# other stuff
+EXTRA_DIST =			\
+	include			\
+	Android.bp		\
+	lib/Android.bp		\
+	mkfs/Android.bp		\
+	tune/Android.bp		\
+	fsck/Android.bp		\
+	label/Android.bp	\
+	dump/Android.bp		\
+	README.md
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..9c4e72b
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,93 @@
+exfatprogs 1.1.1 - released 2021-04-21
+======================================
+
+CHANGES :
+ * mkfs.exfat: adjust the boundary alignment calculations to compensate
+   for the volume offset.
+
+NEW FEATURES :
+ * mkfs.exfat: add the "--pack-bitmap" option to relocate the allocation
+   bitmap to allow the FAT and the bitmap to share the same allocation
+   unit on flash media.
+
+BUG FIXES :
+ * Fix wrong bit operations on 64-bit big.
+ * Fix memory leaks in error paths.
+
+exfatprogs 1.1.0 - released 2021-02-09
+======================================
+
+CHANGES :
+ * fsck.exfat: recover corrupted boot region.
+
+NEW FEATURES :
+ * exfatlabel: Print or Set volume label and serial.
+ * dump.exfat: Show the on-disk metadata information and the statistics.
+
+BUG FIXES :
+ * set _FILE_OFFSET_BITS=64 for Android build.
+
+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
+======================================
+
+CHANGES :
+ * Rename label.exfat to tune.exfat.
+ * tune.exfat: change argument style(-l option for print level,
+   -L option for setting label)
+ * mkfs.exfat: harmonize set volume label option with tune.exfat.
+
+NEW FEATURES :
+ * Add man page.
+
+BUG FIXES :
+ * Fix the reported build warnings/errors.
+ * Add memset to clean garbage in allocation.
+ * Fix wrong volume label array size.
+ * Open a device using O_EXCL to avoid formatting it while it is mounted.
+ * Fix incomplete "make dist" generated tarball.
+
+
+exfatprogs 1.0.2 - released 2020-04-23
+======================================
+
+CHANGES :
+ * Rename project name to exfatprogs.
+ * Replace iconv library by standard C functions mbstowcs() and wcrtomb().
+
+NEW FEATURES :
+ * Add support for Android build system.
+ * label.exfat: Add support for label.exfat to set/get exfat volume label.
+
+BUG FIXES :
+ * Fix the build warnings/errors and add warning options.
+ * Fix several bugs(memory leak, wrong endian conversion, zero out beyond
+   end of file) and cleanup codes
+ * Fix issues on big endian system and on 32bit system.
+
+
+exfatprogs 1.0.1 - released 2020-04-09
+======================================
+
+NEW FEATURES :
+ * mkfs.exfat: quick/full format support
+ * mkfs.exfat: specify cluster size
+ * mkfs.exfat: set volume label
+ * fsck.exfat: consistency check support
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..ba1f2c3
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+drosen@google.com
+dvander@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4db98fd
--- /dev/null
+++ b/README.md
@@ -0,0 +1,119 @@
+
+## exfatprogs
+As new exfat filesystem is merged into linux-5.7 kernel, exfatprogs is
+created as an official userspace utilities that contain all of the standard
+utilities for creating and fixing and debugging exfat filesystem in linux
+system. The goal of exfatprogs is to provide high performance and quality
+at the level of exfat utilities in windows. And this software is licensed
+under the GNU General Public License Version 2.
+
+## Building exfatprogs
+Install prerequisite packages:
+```
+For Ubuntu:
+    sudo apt-get install autoconf libtool pkg-config
+
+For Fedora, RHEL:
+    sudo yum install autoconf automake libtool
+```
+
+Build steps:
+```
+    cd into the exfatprogs directory:
+    ./autogen.sh
+    ./configure
+    make
+    make install
+```
+
+## Using exfatprogs
+```
+- mkfs.exfat:
+    Build a exfat filesystem on a device or partition(e.g. /dev/hda1, dev/sda1).
+
+Usage example:
+    1. No option(default) : cluster size adjustment as per device size, quick format.
+        mkfs.exfat /dev/sda1
+    2. To change cluster size(KB or MB or Byte) user want
+        mkfs.exfat -c 1048576 /dev/sda1
+        mkfs.exfat -c 1024K /dev/sda1
+        mkfs.exfat -c 1M /dev/sda1
+    3. For full format(zero out)
+        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.
+
+Usage example:
+    1. check the consistency.
+        fsck.exfat /dev/sda1
+    2. repair and fix.(preparing)
+
+- tune.exfat:
+    Adjust tunable filesystem parameters on an exFAT filesystem
+
+Usage example:
+    1. print current volume label.
+        tune.exfat -l /dev/sda1
+    2. set new volume label.
+        tune.exfat -L "new label" /dev/sda1
+    3. print current volume serial.
+        tune.exfat -i /dev/sda1
+    4. set new volume serial.
+        tune.exfat -I 0x12345678 /dev/sda1
+
+- exfatlabel:
+    Get or Set volume label or serial
+
+Usage example:
+    1. get current volume label.
+        exfatlabel /dev/sda1
+    2. set new volume label.
+        exfatlabel /dev/sda1 "new label"
+    3. get current volume serial.
+        exfatlabel -i /dev/sda1
+    4. set new volume serial.
+        exfatlabel -i /dev/sda1 0x12345678
+
+- dump.exfat:
+    Show on-disk information
+
+Usage example:
+    dump.exfat /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).
+[Contributions][2] are also welcome.
+
+[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`).
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..552165f
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+autoreconf --install --verbose
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..0544309
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,37 @@
+AC_PREREQ([2.68])
+
+m4_define([exfat_progs_version], m4_esyscmd_s(
+	 grep "define EXFAT_PROGS_VERSION " include/version.h | \
+		 awk '{print $3}' | sed 's/\"//g'))
+
+AC_INIT([exfatprogs],
+	exfat_progs_version,
+	[linkinjeon@kernel.org],
+	[exfatprogs],
+	[https://github.com/exfatprogs/exfatprogs])
+
+AC_CONFIG_SRCDIR([config.h.in])
+AC_CONFIG_HEADER([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_AUX_DIR([build-aux])
+AM_INIT_AUTOMAKE([foreign tar-pax dist-xz subdir-objects])
+
+AC_LANG([C])
+AC_PROG_CC
+AC_PROG_CC_STDC
+AM_SILENT_RULES([yes])
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+AC_C_BIGENDIAN
+
+AC_CONFIG_FILES([
+	Makefile
+	lib/Makefile
+	mkfs/Makefile
+	fsck/Makefile
+	tune/Makefile
+	label/Makefile
+	dump/Makefile
+])
+
+AC_OUTPUT
diff --git a/dump/Android.bp b/dump/Android.bp
new file mode 100644
index 0000000..fad2bf6
--- /dev/null
+++ b/dump/Android.bp
@@ -0,0 +1,20 @@
+// Copyright 2021 The Android Open Source Project
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_exfatprogs_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+cc_binary {
+    name: "dump.exfat",
+
+    srcs: [
+        "dump.c",
+    ],
+    defaults: ["exfatprogs-defaults"],
+    static_libs: ["libexfat"],
+}
diff --git a/dump/Makefile.am b/dump/Makefile.am
new file mode 100644
index 0000000..29c966e
--- /dev/null
+++ b/dump/Makefile.am
@@ -0,0 +1,6 @@
+AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+dump_exfat_LDADD = $(top_builddir)/lib/libexfat.a
+
+sbin_PROGRAMS = dump.exfat
+
+dump_exfat_SOURCES = dump.c
diff --git a/dump/dump.c b/dump/dump.c
new file mode 100644
index 0000000..85d5101
--- /dev/null
+++ b/dump/dump.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2021 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <locale.h>
+#include <inttypes.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+
+#define EXFAT_MIN_SECT_SIZE_BITS		9
+#define EXFAT_MAX_SECT_SIZE_BITS		12
+#define BITS_PER_BYTE				8
+#define BITS_PER_BYTE_MASK			0x7
+
+static const unsigned char used_bit[] = {
+	0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/*  0 ~  19*/
+	2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~  39*/
+	2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,/* 40 ~  59*/
+	4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,/* 60 ~  79*/
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,/* 80 ~  99*/
+	3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,/*100 ~ 119*/
+	4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4,/*120 ~ 139*/
+	3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,/*140 ~ 159*/
+	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,/*160 ~ 179*/
+	4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,/*180 ~ 199*/
+	3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,/*200 ~ 219*/
+	5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,/*220 ~ 239*/
+	4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8             /*240 ~ 255*/
+};
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: dump.exfat\n");
+	fprintf(stderr, "\t-V | --version                        Show version\n");
+	fprintf(stderr, "\t-h | --help                           Show help\n");
+
+	exit(EXIT_FAILURE);
+}
+
+static struct option opts[] = {
+	{"version",		no_argument,		NULL,	'V' },
+	{"help",		no_argument,		NULL,	'h' },
+	{"?",			no_argument,		NULL,	'?' },
+	{NULL,			0,			NULL,	 0  }
+};
+
+static unsigned int exfat_count_used_clusters(unsigned char *bitmap,
+		unsigned long long bitmap_len)
+{
+	unsigned int count = 0;
+	unsigned long long i;
+
+	for (i = 0; i < bitmap_len; i++)
+		count += used_bit[bitmap[i]];
+
+	return count;
+}
+
+static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd)
+{
+	struct pbr *ppbr;
+	struct bsx64 *pbsx;
+	struct exfat_dentry *ed;
+	unsigned int root_clu_off, bitmap_clu_off, bitmap_clu;
+	unsigned int total_clus, used_clus, clu_offset, root_clu;
+	unsigned long long bitmap_len;
+	int ret;
+	unsigned char *bitmap;
+	char *volume_label;
+
+	ppbr = malloc(bd->sector_size);
+	if (!ppbr) {
+		exfat_err("Cannot allocate pbr: out of memory\n");
+		return -ENOMEM;
+	}
+
+	/* read main boot sector */
+	ret = exfat_read_sector(bd, (char *)ppbr, BOOT_SEC_IDX);
+	if (ret < 0) {
+		exfat_err("main boot sector read failed\n");
+		ret = -EIO;
+		goto free_ppbr;
+	}
+
+	pbsx = &ppbr->bsx;
+
+	if (pbsx->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS ||
+	    pbsx->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) {
+		exfat_err("bogus sector size bits : %u\n",
+				pbsx->sect_size_bits);
+		ret = -EINVAL;
+		goto free_ppbr;
+	}
+
+	if (pbsx->sect_per_clus_bits > 25 - pbsx->sect_size_bits) {
+		exfat_err("bogus sectors bits per cluster : %u\n",
+				pbsx->sect_per_clus_bits);
+		ret = -EINVAL;
+		goto free_ppbr;
+	}
+
+	if (bd->sector_size != 1 << pbsx->sect_size_bits) {
+		exfat_err("bogus sector size : %u (sector size bits : %u)\n",
+				bd->sector_size, pbsx->sect_size_bits);
+		ret = -EINVAL;
+		goto free_ppbr;
+	}
+
+	clu_offset = le32_to_cpu(pbsx->clu_offset);
+	total_clus = le32_to_cpu(pbsx->clu_count);
+	root_clu = le32_to_cpu(pbsx->root_cluster);
+
+	exfat_info("-------------- Dump Boot sector region --------------\n");
+	exfat_info("Volume Length(sectors): \t\t%" PRIu64 "\n",
+			le64_to_cpu(pbsx->vol_length));
+	exfat_info("FAT Offset(sector offset): \t\t%u\n",
+			le32_to_cpu(pbsx->fat_offset));
+	exfat_info("FAT Length(sectors): \t\t\t%u\n",
+			le32_to_cpu(pbsx->fat_length));
+	exfat_info("Cluster Heap Offset (sector offset): \t%u\n", clu_offset);
+	exfat_info("Cluster Count: \t\t\t\t%u\n", total_clus);
+	exfat_info("Root Cluster (cluster offset): \t\t%u\n", root_clu);
+	exfat_info("Volume Serial: \t\t\t\t0x%x\n", le32_to_cpu(pbsx->vol_serial));
+	exfat_info("Sector Size Bits: \t\t\t%u\n", pbsx->sect_size_bits);
+	exfat_info("Sector per Cluster bits: \t\t%u\n\n", pbsx->sect_per_clus_bits);
+
+	bd->cluster_size =
+		1 << (pbsx->sect_per_clus_bits + pbsx->sect_size_bits);
+	root_clu_off = exfat_clus_to_blk_dev_off(bd, clu_offset, root_clu);
+
+	ed = malloc(sizeof(struct exfat_dentry)*3);
+	if (!ed) {
+		exfat_err("failed to allocate memory\n");
+		ret = -ENOMEM;
+		goto free_ppbr;
+	}
+
+	ret = exfat_read(bd->dev_fd, ed, sizeof(struct exfat_dentry)*3,
+			root_clu_off);
+	if (ret < 0) {
+		exfat_err("bitmap entry read failed: %d\n", errno);
+		ret = -EIO;
+		goto free_entry;
+	}
+
+	volume_label = exfat_conv_volume_label(&ed[0]);
+	if (!volume_label) {
+		ret = -EINVAL;
+		goto free_entry;
+	}
+
+	bitmap_clu = le32_to_cpu(ed[1].bitmap_start_clu);
+	bitmap_clu_off = exfat_clus_to_blk_dev_off(bd, clu_offset,
+			bitmap_clu);
+	bitmap_len = le64_to_cpu(ed[1].bitmap_size);
+
+	exfat_info("----------------- Dump Root entries -----------------\n");
+	exfat_info("Volume entry type: \t\t\t0x%x\n", ed[0].type);
+	exfat_info("Volume label: \t\t\t\t%s\n", volume_label);
+	exfat_info("Volume label character count: \t\t%u\n", ed[0].vol_char_cnt);
+
+	exfat_info("Bitmap entry type: \t\t\t0x%x\n", ed[1].type);
+	exfat_info("Bitmap start cluster: \t\t\t%x\n", bitmap_clu);
+	exfat_info("Bitmap size: \t\t\t\t%llu\n", bitmap_len);
+
+	exfat_info("Upcase table entry type: \t\t0x%x\n", ed[2].type);
+	exfat_info("Upcase table start cluster: \t\t%x\n",
+			le32_to_cpu(ed[2].upcase_start_clu));
+	exfat_info("Upcase table size: \t\t\t%" PRIu64 "\n\n",
+			le64_to_cpu(ed[2].upcase_size));
+
+	bitmap = malloc(bitmap_len);
+	if (!bitmap) {
+		exfat_err("bitmap allocation failed\n");
+		ret = -ENOMEM;
+		goto free_volume_label;
+	}
+
+	ret = exfat_read(bd->dev_fd, bitmap, bitmap_len, bitmap_clu_off);
+	if (ret < 0) {
+		exfat_err("bitmap entry read failed: %d\n", errno);
+		ret = -EIO;
+		free(bitmap);
+		goto free_volume_label;
+	}
+
+	total_clus = le32_to_cpu(pbsx->clu_count);
+	used_clus = exfat_count_used_clusters(bitmap, bitmap_len);
+
+	exfat_info("---------------- Show the statistics ----------------\n");
+	exfat_info("Cluster size:  \t\t\t\t%u\n", bd->cluster_size);
+	exfat_info("Total Clusters: \t\t\t%u\n", total_clus);
+	exfat_info("Free Clusters: \t\t\t\t%u\n", total_clus-used_clus);
+	ret = 0;
+
+	free(bitmap);
+
+free_volume_label:
+	free(volume_label);
+free_entry:
+	free(ed);
+free_ppbr:
+	free(ppbr);
+	return ret;
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+	int ret = EXIT_FAILURE;
+	struct exfat_blk_dev bd;
+	struct exfat_user_input ui;
+	bool version_only = false;
+
+	init_user_input(&ui);
+
+	if (!setlocale(LC_CTYPE, ""))
+		exfat_err("failed to init locale/codeset\n");
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "iVh", opts, NULL)) != EOF)
+		switch (c) {
+		case 'V':
+			version_only = true;
+			break;
+		case '?':
+		case 'h':
+		default:
+			usage();
+	}
+
+	show_version();
+	if (version_only)
+		exit(EXIT_FAILURE);
+
+	if (argc < 2)
+		usage();
+
+	memset(ui.dev_name, 0, sizeof(ui.dev_name));
+	snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[1]);
+
+	ret = exfat_get_blk_dev_info(&ui, &bd);
+	if (ret < 0)
+		goto out;
+
+	ret = exfat_show_ondisk_all_info(&bd);
+	close(bd.dev_fd);
+
+out:
+	return ret;
+}
diff --git a/fsck/Android.bp b/fsck/Android.bp
new file mode 100644
index 0000000..208fc34
--- /dev/null
+++ b/fsck/Android.bp
@@ -0,0 +1,22 @@
+// Copyright 2020 The Android Open Source Project
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_exfatprogs_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+cc_binary {
+    name: "fsck.exfat",
+
+    srcs: [
+        "de_iter.c",
+        "fsck.c",
+        "repair.c",
+    ],
+    defaults: ["exfatprogs-defaults"],
+    static_libs: ["libexfat"],
+}
diff --git a/fsck/Makefile.am b/fsck/Makefile.am
new file mode 100644
index 0000000..57a0ede
--- /dev/null
+++ b/fsck/Makefile.am
@@ -0,0 +1,6 @@
+AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a
+
+sbin_PROGRAMS = fsck.exfat
+
+fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h
diff --git a/fsck/de_iter.c b/fsck/de_iter.c
new file mode 100644
index 0000000..bc95c49
--- /dev/null
+++ b/fsck/de_iter.c
@@ -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;
+}
diff --git a/fsck/fsck.c b/fsck/fsck.c
new file mode 100644
index 0000000..747a771
--- /dev/null
+++ b/fsck/fsck.c
@@ -0,0 +1,1593 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ *   Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "fsck.h"
+#include "repair.h"
+
+struct fsck_user_input {
+	struct exfat_user_input		ei;
+	enum fsck_ui_options		options;
+};
+
+#define EXFAT_MAX_UPCASE_CHARS	0x10000
+
+#ifdef WORDS_BIGENDIAN
+typedef __u8	bitmap_t;
+#else
+typedef __u32	bitmap_t;
+#endif
+
+#define BITS_PER	(sizeof(bitmap_t) * 8)
+#define BIT_MASK(__c)	(1 << ((__c) % BITS_PER))
+#define BIT_ENTRY(__c)	((__c) / BITS_PER)
+
+#define EXFAT_BITMAP_SIZE(__c_count)	\
+	(DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t))
+#define EXFAT_BITMAP_GET(__bmap, __c)	\
+			(((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c))
+#define EXFAT_BITMAP_SET(__bmap, __c)	\
+			(((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \
+			 BIT_MASK(__c))
+
+#define FSCK_EXIT_NO_ERRORS		0x00
+#define FSCK_EXIT_CORRECTED		0x01
+#define FSCK_EXIT_NEED_REBOOT		0x02
+#define FSCK_EXIT_ERRORS_LEFT		0x04
+#define FSCK_EXIT_OPERATION_ERROR	0x08
+#define FSCK_EXIT_SYNTAX_ERROR		0x10
+#define FSCK_EXIT_USER_CANCEL		0x20
+#define FSCK_EXIT_LIBRARY_ERROR		0x80
+
+struct exfat_stat {
+	long		dir_count;
+	long		file_count;
+	long		error_count;
+	long		fixed_count;
+};
+
+struct path_resolve_ctx {
+	struct exfat_inode	*ancestors[255];
+	__le16			utf16_path[PATH_MAX + 2];
+	char			local_path[PATH_MAX * MB_LEN_MAX + 1];
+};
+
+struct exfat_stat exfat_stat;
+struct path_resolve_ctx path_resolve_ctx;
+
+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' },
+	{"?",		no_argument,	NULL,	'?' },
+	{NULL,		0,		NULL,	 0  }
+};
+
+static void usage(char *name)
+{
+	fprintf(stderr, "Usage: %s\n", 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-a                   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");
+
+	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;
+	int size;
+
+	size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE;
+	node = (struct exfat_inode *)calloc(1, size);
+	if (!node) {
+		exfat_err("failed to allocate exfat_node\n");
+		return NULL;
+	}
+
+	node->parent = NULL;
+	INIT_LIST_HEAD(&node->children);
+	INIT_LIST_HEAD(&node->sibling);
+	INIT_LIST_HEAD(&node->list);
+
+	node->last_pclus = EXFAT_EOF_CLUSTER;
+	node->attr = attr;
+	if (attr & ATTR_SUBDIR)
+		exfat_stat.dir_count++;
+	else
+		exfat_stat.file_count++;
+	return node;
+}
+
+static void free_exfat_inode(struct exfat_inode *node)
+{
+	free(node);
+}
+
+static void inode_free_children(struct exfat_inode *dir, bool file_only)
+{
+	struct exfat_inode *node, *i;
+
+	list_for_each_entry_safe(node, i, &dir->children, sibling) {
+		if (file_only) {
+			if (!(node->attr & ATTR_SUBDIR)) {
+				list_del(&node->sibling);
+				free_exfat_inode(node);
+			}
+		} else {
+			list_del(&node->sibling);
+			list_del(&node->list);
+			free_exfat_inode(node);
+		}
+	}
+}
+
+static void inode_free_file_children(struct exfat_inode *dir)
+{
+	inode_free_children(dir, true);
+}
+
+/* delete @child and all ancestors that does not have
+ * children
+ */
+static void inode_free_ancestors(struct exfat_inode *child)
+{
+	struct exfat_inode *parent;
+
+	if (!list_empty(&child->children))
+		return;
+
+	do {
+		if (!(child->attr & ATTR_SUBDIR)) {
+			exfat_err("not directory.\n");
+			return;
+		}
+
+		parent = child->parent;
+		list_del(&child->sibling);
+		free_exfat_inode(child);
+
+		child = parent;
+	} while (child && list_empty(&child->children));
+
+	return;
+}
+
+static void free_exfat(struct exfat *exfat)
+{
+	int i;
+
+	if (exfat) {
+		if (exfat->bs)
+			free(exfat->bs);
+		if (exfat->alloc_bitmap)
+			free(exfat->alloc_bitmap);
+		if (exfat->disk_bitmap)
+			free(exfat->disk_bitmap);
+		for (i = 0; i < 2; i++) {
+			if (exfat->buffer_desc[i].buffer)
+				free(exfat->buffer_desc[i].buffer);
+			if (exfat->buffer_desc[i].dirty)
+				free(exfat->buffer_desc[i].dirty);
+		}
+		free(exfat);
+	}
+}
+
+static int init_exfat(struct exfat *exfat, struct pbr *bs)
+{
+	int i;
+
+	INIT_LIST_HEAD(&exfat->dir_list);
+	exfat->bs = bs;
+	exfat->clus_count = le32_to_cpu(bs->bsx.clu_count);
+	exfat->clus_size = EXFAT_CLUSTER_SIZE(bs);
+	exfat->sect_size = EXFAT_SECTOR_SIZE(bs);
+
+	/* TODO: bitmap could be very large. */
+	exfat->alloc_bitmap = (char *)calloc(1,
+			EXFAT_BITMAP_SIZE(exfat->clus_count));
+	if (!exfat->alloc_bitmap) {
+		exfat_err("failed to allocate bitmap\n");
+		goto err;
+	}
+
+	exfat->disk_bitmap = (char *)malloc(
+				EXFAT_BITMAP_SIZE(exfat->clus_count));
+	if (!exfat->disk_bitmap) {
+		exfat_err("failed to allocate bitmap\n");
+		goto err;
+	}
+
+	/* allocate cluster buffers */
+	for (i = 0; i < 2; i++) {
+		exfat->buffer_desc[i].buffer =
+			(char *)malloc(exfat->clus_size);
+		if (!exfat->buffer_desc[i].buffer)
+			goto err;
+		exfat->buffer_desc[i].dirty =
+			(char *)calloc(
+			(exfat->clus_size / exfat->sect_size), 1);
+		if (!exfat->buffer_desc[i].dirty)
+			goto err;
+	}
+	return 0;
+err:
+	free_exfat(exfat);
+	return -ENOMEM;
+}
+
+static void exfat_free_dir_list(struct exfat *exfat)
+{
+	struct exfat_inode *dir, *i;
+
+	list_for_each_entry_safe(dir, i, &exfat->dir_list, list) {
+		inode_free_file_children(dir);
+		list_del(&dir->list);
+		free_exfat_inode(dir);
+	}
+}
+
+/*
+ * get references of ancestors that include @child until the count of
+ * ancesters is not larger than @count and the count of characters of
+ * their names is not larger than @max_char_len.
+ * return true if root is reached.
+ */
+bool get_ancestors(struct exfat_inode *child,
+		struct exfat_inode **ancestors, int count,
+		int max_char_len,
+		int *ancestor_count)
+{
+	struct exfat_inode *dir;
+	int name_len, char_len;
+	int root_depth, depth, i;
+
+	root_depth = 0;
+	char_len = 0;
+	max_char_len += 1;
+
+	dir = child;
+	while (dir) {
+		name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE);
+		if (char_len + name_len > max_char_len)
+			break;
+
+		/* include '/' */
+		char_len += name_len + 1;
+		root_depth++;
+
+		dir = dir->parent;
+	}
+
+	depth = MIN(root_depth, count);
+
+	for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--)
+		ancestors[i] = dir;
+
+	*ancestor_count = depth;
+	return dir == NULL;
+}
+
+static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
+{
+	int depth, i;
+	int name_len;
+	__le16 *utf16_path;
+	static const __le16 utf16_slash = cpu_to_le16(0x002F);
+	static const __le16 utf16_null = cpu_to_le16(0x0000);
+	size_t in_size;
+
+	ctx->local_path[0] = '\0';
+
+	get_ancestors(child,
+			ctx->ancestors,
+			sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]),
+			PATH_MAX,
+			&depth);
+
+	utf16_path = ctx->utf16_path;
+	for (i = 0; i < depth; i++) {
+		name_len = exfat_utf16_len(ctx->ancestors[i]->name,
+				NAME_BUFFER_SIZE);
+		memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name,
+				name_len * 2);
+		utf16_path += name_len;
+		memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash));
+		utf16_path++;
+	}
+
+	if (depth > 0)
+		utf16_path--;
+	memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null));
+	utf16_path++;
+
+	in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16);
+	return exfat_utf16_dec(ctx->utf16_path, in_size,
+				ctx->local_path, sizeof(ctx->local_path));
+}
+
+static int resolve_path_parent(struct path_resolve_ctx *ctx,
+			struct exfat_inode *parent, struct exfat_inode *child)
+{
+	int ret;
+	struct exfat_inode *old;
+
+	old = child->parent;
+	child->parent = parent;
+
+	ret = resolve_path(ctx, child);
+	child->parent = old;
+	return ret;
+}
+
+#define repair_file_ask(iter, inode, code, fmt, ...)	\
+({							\
+		resolve_path_parent(&path_resolve_ctx,	\
+				(iter)->parent, inode);	\
+		exfat_repair_ask((iter)->exfat, code,	\
+			"ERROR: %s: " fmt,		\
+			path_resolve_ctx.local_path,	\
+			##__VA_ARGS__);			\
+})
+
+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;
+		}
+
+		(*clus_count)++;
+	} while (clus != EXFAT_EOF_CLUSTER);
+	return true;
+}
+
+static off_t exfat_s2o(struct exfat *exfat, off_t sect)
+{
+	return sect << exfat->bs->bsx.sect_size_bits;
+}
+
+off_t exfat_c2o(struct exfat *exfat, unsigned int clus)
+{
+	if (clus < EXFAT_FIRST_CLUSTER)
+		return ~0L;
+
+	return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) +
+				((off_t)(clus - EXFAT_FIRST_CLUSTER) <<
+				 exfat->bs->bsx.sect_per_clus_bits));
+}
+
+static int boot_region_checksum(struct exfat_blk_dev *bd, int bs_offset)
+{
+	void *sect;
+	unsigned int i;
+	uint32_t checksum;
+	int ret = 0;
+	unsigned int size;
+
+	size = bd->sector_size;
+	sect = malloc(size);
+	if (!sect)
+		return -ENOMEM;
+
+	checksum = 0;
+	for (i = 0; i < 11; i++) {
+		if (exfat_read(bd->dev_fd, sect, size,
+				bs_offset * size + i * size) !=
+				(ssize_t)size) {
+			exfat_err("failed to read boot region\n");
+			ret = -EIO;
+			goto out;
+		}
+		boot_calc_checksum(sect, size, i == 0, &checksum);
+	}
+
+	if (exfat_read(bd->dev_fd, sect, size,
+			bs_offset * size + 11 * size) !=
+			(ssize_t)size) {
+		exfat_err("failed to read a boot checksum sector\n");
+		ret = -EIO;
+		goto out;
+	}
+
+	for (i = 0; i < size/sizeof(checksum); i++) {
+		if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) {
+			exfat_err("checksum of boot region is not correct. %#x, but expected %#x\n",
+				le32_to_cpu(((__le32 *)sect)[i]), checksum);
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+out:
+	free(sect);
+	return ret;
+}
+
+static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty)
+{
+	uint16_t flags;
+
+	if (!(exfat->options & FSCK_OPTS_REPAIR_WRITE))
+		return 0;
+
+	flags = le16_to_cpu(exfat->bs->bsx.vol_flags);
+	if (dirty)
+		flags |= 0x02;
+	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;
+	}
+
+	if (fsync(exfat->blk_dev->dev_fd) != 0) {
+		exfat_err("failed to set VolumeDirty\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr,
+		int bs_offset)
+{
+	struct pbr *bs;
+	int ret = -EINVAL;
+
+	*pbr = NULL;
+	bs = (struct pbr *)malloc(sizeof(struct pbr));
+	if (!bs) {
+		exfat_err("failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	if (exfat_read(bd->dev_fd, bs, sizeof(*bs),
+			bs_offset * bd->sector_size) != (ssize_t)sizeof(*bs)) {
+		exfat_err("failed to read a boot sector\n");
+		ret = -EIO;
+		goto err;
+	}
+
+	if (memcmp(bs->bpb.oem_name, "EXFAT   ", 8) != 0) {
+		exfat_err("failed to find exfat file system.\n");
+		goto err;
+	}
+
+	ret = boot_region_checksum(bd, bs_offset);
+	if (ret < 0)
+		goto err;
+
+	ret = -EINVAL;
+	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;
+	}
+
+	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 ", expected: %llu\n",
+				le64_to_cpu(bs->bsx.vol_length),
+				bd->num_sectors);
+		goto err;
+	}
+
+	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;
+	}
+
+	*pbr = bs;
+	return 0;
+err:
+	free(bs);
+	return ret;
+}
+
+static int restore_boot_region(struct exfat_blk_dev *bd)
+{
+	int i;
+	char *sector;
+	int ret;
+
+	sector = malloc(bd->sector_size);
+	if (!sector)
+		return -ENOMEM;
+
+	for (i = 0; i < 12; i++) {
+		if (exfat_read(bd->dev_fd, sector, bd->sector_size,
+				BACKUP_BOOT_SEC_IDX * bd->sector_size +
+				i * bd->sector_size) !=
+				(ssize_t)bd->sector_size) {
+			ret = -EIO;
+			goto free_sector;
+		}
+		if (i == 0)
+			((struct pbr *)sector)->bsx.perc_in_use = 0xff;
+
+		if (exfat_write(bd->dev_fd, sector, bd->sector_size,
+				BOOT_SEC_IDX * bd->sector_size +
+				i * bd->sector_size) !=
+				(ssize_t)bd->sector_size) {
+			ret = -EIO;
+			goto free_sector;
+		}
+	}
+
+	if (fsync(bd->dev_fd)) {
+		ret = -EIO;
+		goto free_sector;
+	}
+	ret = 0;
+
+free_sector:
+	free(sector);
+	return ret;
+}
+
+static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
+{
+	int ret;
+
+	ret = read_boot_region(exfat->blk_dev, bs, BOOT_SEC_IDX);
+	if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION,
+				"boot region is corrupted. try to restore the region from backup"
+				)) {
+		ret = read_boot_region(exfat->blk_dev, bs, BACKUP_BOOT_SEC_IDX);
+		if (ret < 0) {
+			exfat_err("backup boot region is also corrupted\n");
+			return ret;
+		}
+		ret = restore_boot_region(exfat->blk_dev);
+		if (ret < 0) {
+			exfat_err("failed to restore boot region from backup\n");
+			free(*bs);
+			*bs = NULL;
+			return ret;
+		}
+	}
+	return ret;
+}
+
+static void dentry_calc_checksum(struct exfat_dentry *dentry,
+				__le16 *checksum, bool primary)
+{
+	unsigned int i;
+	uint8_t *bytes;
+
+	bytes = (uint8_t *)dentry;
+
+	*checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0];
+	*checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1];
+
+	i = primary ? 4 : 2;
+	for (; i < sizeof(*dentry); i++) {
+		*checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i];
+	}
+}
+
+static __le16 file_calc_checksum(struct exfat_de_iter *iter)
+{
+	__le16 checksum;
+	struct exfat_dentry *file_de, *de;
+	int i;
+
+	checksum = 0;
+	exfat_de_iter_get(iter, 0, &file_de);
+
+	dentry_calc_checksum(file_de, &checksum, true);
+	for (i = 1; i <= file_de->file_num_ext; i++) {
+		exfat_de_iter_get(iter, i, &de);
+		dentry_calc_checksum(de, &checksum, false);
+	}
+
+	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;
+	bool valid = true;
+
+	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);
+		valid = false;
+	}
+
+	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
+			valid = false;
+	}
+
+	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);
+		valid = false;
+	}
+
+	checksum = file_calc_checksum(iter);
+	exfat_de_iter_get(iter, 0, &dentry);
+	if (checksum != le16_to_cpu(dentry->file_checksum)) {
+		if (repair_file_ask(iter, node, ER_DE_CHECKSUM,
+				"the checksum of a file is wrong")) {
+			exfat_de_iter_get_dirty(iter, 0, &dentry);
+			dentry->file_checksum = cpu_to_le16(checksum);
+			ret = 1;
+		} else
+			valid = false;
+	}
+
+	return valid ? ret : -EINVAL;
+}
+
+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;
+
+	/* TODO: mtime, atime, ... */
+
+	ret = exfat_de_iter_get(iter, 0, &file_de);
+	if (ret || file_de->type != EXFAT_FILE) {
+		exfat_err("failed to get file dentry. %d\n", ret);
+		return -EINVAL;
+	}
+	ret = exfat_de_iter_get(iter, 1, &stream_de);
+	if (ret || stream_de->type != EXFAT_STREAM) {
+		exfat_err("failed to get stream dentry. %d\n", ret);
+		return -EINVAL;
+	}
+
+	*new_node = NULL;
+	node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr));
+	if (!node)
+		return -ENOMEM;
+
+	if (file_de->file_num_ext < 2) {
+		exfat_err("too few secondary count. %d\n",
+				file_de->file_num_ext);
+		free_exfat_inode(node);
+		return -EINVAL;
+	}
+
+	for (i = 2; i <= file_de->file_num_ext; i++) {
+		ret = exfat_de_iter_get(iter, i, &name_de);
+		if (ret || name_de->type != EXFAT_NAME) {
+			exfat_err("failed to get name dentry. %d\n", ret);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		memcpy(node->name +
+			(i-2) * ENTRY_NAME_MAX, name_de->name_unicode,
+			sizeof(name_de->name_unicode));
+	}
+
+	node->first_clus = le32_to_cpu(stream_de->stream_start_clu);
+	node->is_contiguous =
+		((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0);
+	node->size = le64_to_cpu(stream_de->stream_size);
+
+	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);
+	*new_node = node;
+	return 0;
+err:
+	*skip_dentries = 0;
+	*new_node = NULL;
+	free_exfat_inode(node);
+	return ret;
+}
+
+static int read_file(struct exfat_de_iter *de_iter,
+		struct exfat_inode **new_node, int *dentry_count)
+{
+	struct exfat_inode *node;
+	int ret;
+
+	*new_node = NULL;
+
+	ret = read_file_dentries(de_iter, &node, dentry_count);
+	if (ret)
+		return ret;
+
+	ret = check_inode(de_iter, node);
+	if (ret < 0) {
+		free_exfat_inode(node);
+		return -EINVAL;
+	}
+
+	*new_node = node;
+	return ret;
+}
+
+static bool read_volume_label(struct exfat_de_iter *iter)
+{
+	struct exfat *exfat;
+	struct exfat_dentry *dentry;
+	__le16 disk_label[VOLUME_LABEL_MAX_LEN];
+
+	exfat = iter->exfat;
+	if (exfat_de_iter_get(iter, 0, &dentry))
+		return false;
+
+	if (dentry->vol_char_cnt == 0)
+		return true;
+
+	if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) {
+		exfat_err("too long label. %d\n", dentry->vol_char_cnt);
+		return false;
+	}
+
+	memcpy(disk_label, dentry->vol_label, sizeof(disk_label));
+	if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2,
+		exfat->volume_label, sizeof(exfat->volume_label)) < 0) {
+		exfat_err("failed to decode volume label\n");
+		return false;
+	}
+
+	exfat_info("volume label [%s]\n", exfat->volume_label);
+	return true;
+}
+
+static void exfat_bitmap_set_range(struct exfat *exfat,
+			clus_t start_clus, clus_t count)
+{
+	clus_t clus;
+
+	if (!heap_clus(exfat, start_clus) ||
+		!heap_clus(exfat, start_clus + count))
+		return;
+
+	clus = start_clus;
+	while (clus < start_clus + count) {
+		EXFAT_BITMAP_SET(exfat->alloc_bitmap,
+				clus - EXFAT_FIRST_CLUSTER);
+		clus++;
+	}
+}
+
+static bool read_bitmap(struct exfat_de_iter *iter)
+{
+	struct exfat_dentry *dentry;
+	struct exfat *exfat;
+
+	exfat = iter->exfat;
+	if (exfat_de_iter_get(iter, 0, &dentry))
+		return false;
+
+	exfat_debug("start cluster %#x, size %#" PRIx64 "\n",
+			le32_to_cpu(dentry->bitmap_start_clu),
+			le64_to_cpu(dentry->bitmap_size));
+
+	if (le64_to_cpu(dentry->bitmap_size) <
+			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 (!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;
+	}
+
+	exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
+	exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
+
+	exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu),
+			DIV_ROUND_UP(exfat->disk_bitmap_size,
+			exfat->clus_size));
+
+	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;
+}
+
+static bool read_upcase_table(struct exfat_de_iter *iter)
+{
+	struct exfat_dentry *dentry;
+	struct exfat *exfat;
+	ssize_t size;
+	__le16 *upcase;
+	__le32 checksum;
+
+	exfat = iter->exfat;
+
+	if (exfat_de_iter_get(iter, 0, &dentry))
+		return false;
+
+	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;
+	}
+
+	size = (ssize_t)le64_to_cpu(dentry->upcase_size);
+	if (size > (ssize_t)(EXFAT_MAX_UPCASE_CHARS * sizeof(__le16)) ||
+			size == 0 || size % sizeof(__le16)) {
+		exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n",
+			le64_to_cpu(dentry->upcase_size));
+		return false;
+	}
+
+	upcase = (__le16 *)malloc(size);
+	if (!upcase) {
+		exfat_err("failed to allocate upcase table\n");
+		return false;
+	}
+
+	if (exfat_read(exfat->blk_dev->dev_fd, upcase, size,
+			exfat_c2o(exfat,
+			le32_to_cpu(dentry->upcase_start_clu))) != size) {
+		exfat_err("failed to read upcase table\n");
+		free(upcase);
+		return false;
+	}
+
+	checksum = 0;
+	boot_calc_checksum((unsigned char *)upcase, size, false, &checksum);
+	if (le32_to_cpu(dentry->upcase_checksum) != checksum) {
+		exfat_err("corrupted upcase table %#x (expected: %#x)\n",
+			checksum, le32_to_cpu(dentry->upcase_checksum));
+		free(upcase);
+		return false;
+	}
+
+	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;
+}
+
+static int read_children(struct exfat *exfat, struct exfat_inode *dir)
+{
+	int ret;
+	struct exfat_inode *node = NULL;
+	struct exfat_dentry *dentry;
+	int dentry_count;
+	struct exfat_de_iter *de_iter;
+
+	de_iter = &exfat->de_iter;
+	ret = exfat_de_iter_init(de_iter, exfat, dir);
+	if (ret == EOF)
+		return 0;
+	else if (ret)
+		return ret;
+
+	while (1) {
+		ret = exfat_de_iter_get(de_iter, 0, &dentry);
+		if (ret == EOF) {
+			break;
+		} else if (ret) {
+			fsck_err(dir->parent, dir,
+				"failed to get a dentry. %d\n", ret);
+			goto err;
+		}
+
+		dentry_count = 1;
+
+		switch (dentry->type) {
+		case EXFAT_FILE:
+			ret = read_file(de_iter, &node, &dentry_count);
+			if (ret < 0) {
+				exfat_stat.error_count++;
+				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, &exfat->dir_list);
+			} else
+				free_exfat_inode(node);
+			break;
+		case EXFAT_VOLUME:
+			if (!read_volume_label(de_iter)) {
+				exfat_err("failed to verify volume label\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case EXFAT_BITMAP:
+			if (!read_bitmap(de_iter)) {
+				exfat_err(
+					"failed to verify allocation bitmap\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case EXFAT_UPCASE:
+			if (!read_upcase_table(de_iter)) {
+				exfat_err(
+					"failed to verify upcase table\n");
+				ret = -EINVAL;
+				goto err;
+			}
+			break;
+		case EXFAT_LAST:
+			goto out;
+		default:
+			if (IS_EXFAT_DELETED(dentry->type))
+				break;
+			exfat_err("unknown entry type. 0x%x\n", dentry->type);
+			ret = -EINVAL;
+			goto err;
+		}
+
+		exfat_de_iter_advance(de_iter, dentry_count);
+	}
+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.
+ *    and append directory exfat_nodes to the head of @dir_list
+ * 2. free all of file exfat_nodes.
+ * 3. if the directory does not have children, free its exfat_node.
+ */
+static int exfat_filesystem_check(struct exfat *exfat)
+{
+	struct exfat_inode *dir;
+	int ret = 0, dir_errors;
+
+	if (!exfat->root) {
+		exfat_err("root is NULL\n");
+		return -ENOENT;
+	}
+
+	list_add(&exfat->root->list, &exfat->dir_list);
+
+	while (!list_empty(&exfat->dir_list)) {
+		dir = list_entry(exfat->dir_list.next, struct exfat_inode, list);
+
+		if (!(dir->attr & ATTR_SUBDIR)) {
+			fsck_err(dir->parent, dir,
+				"failed to travel directories. "
+				"the node is not directory\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		dir_errors = read_children(exfat, dir);
+		if (dir_errors) {
+			resolve_path(&path_resolve_ctx, dir);
+			exfat_debug("failed to check dentries: %s\n",
+					path_resolve_ctx.local_path);
+			ret = dir_errors;
+		}
+
+		list_del(&dir->list);
+		inode_free_file_children(dir);
+		inode_free_ancestors(dir);
+	}
+out:
+	exfat_free_dir_list(exfat);
+	exfat->root = NULL;
+	if (exfat->dirty_fat && reclaim_free_clusters(exfat))
+		return -EIO;
+	return ret;
+}
+
+static int exfat_root_dir_check(struct exfat *exfat)
+{
+	struct exfat_inode *root;
+	clus_t clus_count;
+
+	root = alloc_exfat_inode(ATTR_SUBDIR);
+	if (!root) {
+		exfat_err("failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
+	if (!root_get_clus_count(exfat, root, &clus_count)) {
+		exfat_err("failed to follow the cluster chain of root\n");
+		free_exfat_inode(root);
+		return -EINVAL;
+	}
+	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 0;
+}
+
+static char *bytes_to_human_readable(size_t bytes)
+{
+	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;
+}
+
+static void exfat_show_info(struct exfat *exfat, const char *dev_name,
+			int errors)
+{
+	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[])
+{
+	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));
+	memset(&bd, 0, sizeof(bd));
+
+	print_level = EXFAT_ERROR;
+
+	if (!setlocale(LC_CTYPE, ""))
+		exfat_err("failed to init locale/codeset\n");
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "arynpVvh", opts, NULL)) != EOF) {
+		switch (c) {
+		case 'n':
+			if (ui.options & FSCK_OPTS_REPAIR_ALL)
+				usage(argv[0]);
+			ui.options |= FSCK_OPTS_REPAIR_NO;
+			break;
+		case 'r':
+			if (ui.options & FSCK_OPTS_REPAIR_ALL)
+				usage(argv[0]);
+			ui.options |= FSCK_OPTS_REPAIR_ASK;
+			break;
+		case 'y':
+			if (ui.options & FSCK_OPTS_REPAIR_ALL)
+				usage(argv[0]);
+			ui.options |= FSCK_OPTS_REPAIR_YES;
+			break;
+		case 'a':
+		case 'p':
+			if (ui.options & FSCK_OPTS_REPAIR_ALL)
+				usage(argv[0]);
+			ui.options |= FSCK_OPTS_REPAIR_AUTO;
+			break;
+		case 'V':
+			version_only = true;
+			break;
+		case 'v':
+			if (print_level < EXFAT_DEBUG)
+				print_level++;
+			break;
+		case '?':
+		case 'h':
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	show_version();
+	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) {
+		exfat_err("failed to open %s. %d\n", ui.ei.dev_name, ret);
+		return FSCK_EXIT_OPERATION_ERROR;
+	}
+
+	exfat = (struct exfat *)calloc(1, sizeof(*exfat));
+	if (!exfat) {
+		exfat_err("failed to allocate exfat\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	exfat->blk_dev = &bd;
+	exfat->options = ui.options;
+
+	ret = exfat_boot_region_check(exfat, &bs);
+	if (ret)
+		goto err;
+
+	ret = init_exfat(exfat, bs);
+	if (ret)
+		goto err;
+
+	if (exfat_mark_volume_dirty(exfat, true)) {
+		ret = -EIO;
+		goto err;
+	}
+
+	exfat_debug("verifying root directory...\n");
+	ret = exfat_root_dir_check(exfat);
+	if (ret) {
+		exfat_err("failed to verify root directory.\n");
+		goto out;
+	}
+
+	exfat_debug("verifying directory entries...\n");
+	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);
+
+out:
+	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 exit_code;
+}
diff --git a/fsck/fsck.h b/fsck/fsck.h
new file mode 100644
index 0000000..6c91fac
--- /dev/null
+++ b/fsck/fsck.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#ifndef _FSCK_H
+#define _FSCK_H
+
+#include "list.h"
+
+typedef __u32 clus_t;
+
+struct exfat_inode {
+	struct exfat_inode	*parent;
+	struct list_head	children;
+	struct list_head	sibling;
+	struct list_head	list;
+	clus_t			first_clus;
+	clus_t			last_lclus;
+	clus_t			last_pclus;
+	__u16			attr;
+	uint64_t		size;
+	bool			is_contiguous;
+	__le16			name[0];	/* only for directory */
+};
+
+#define EXFAT_NAME_MAX			255
+#define NAME_BUFFER_SIZE		((EXFAT_NAME_MAX+1)*2)
+
+struct buffer_desc {
+	clus_t		p_clus;
+	unsigned int	offset;
+	char		*buffer;
+	char		*dirty;
+};
+
+struct exfat_de_iter {
+	struct exfat		*exfat;
+	struct exfat_inode	*parent;
+	struct buffer_desc	*buffer_desc;		/* cluster * 2 */
+	clus_t			ra_next_clus;
+	unsigned int		ra_begin_offset;
+	unsigned int		ra_partial_size;
+	unsigned int		read_size;		/* cluster size */
+	unsigned int		write_size;		/* sector size */
+	off_t			de_file_offset;
+	off_t			next_read_offset;
+	int			max_skip_dentries;
+};
+
+enum fsck_ui_options {
+	FSCK_OPTS_REPAIR_ASK	= 0x01,
+	FSCK_OPTS_REPAIR_YES	= 0x02,
+	FSCK_OPTS_REPAIR_NO	= 0x04,
+	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;
+	struct buffer_desc	buffer_desc[2];	/* cluster * 2 */
+	char			*alloc_bitmap;
+	char			*disk_bitmap;
+	clus_t			disk_bitmap_clus;
+	unsigned int		disk_bitmap_size;
+};
+
+#define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits +	\
+					(pbr)->bsx.sect_per_clus_bits))
+#define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits)
+
+/* fsck.c */
+off_t exfat_c2o(struct exfat *exfat, unsigned int clus);
+int get_next_clus(struct exfat *exfat, struct exfat_inode *node,
+				clus_t clus, clus_t *next);
+
+/* de_iter.c */
+int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat,
+				struct exfat_inode *dir);
+int exfat_de_iter_get(struct exfat_de_iter *iter,
+			int ith, struct exfat_dentry **dentry);
+int exfat_de_iter_get_dirty(struct exfat_de_iter *iter,
+			int ith, struct exfat_dentry **dentry);
+int exfat_de_iter_flush(struct exfat_de_iter *iter);
+int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries);
+off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter);
+
+#endif
diff --git a/fsck/repair.c b/fsck/repair.c
new file mode 100644
index 0000000..c79d379
--- /dev/null
+++ b/fsck/repair.c
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "fsck.h"
+#include "repair.h"
+
+struct exfat_repair_problem {
+	er_problem_code_t	prcode;
+	unsigned int		flags;
+	unsigned int		prompt_type;
+};
+
+/* Problem flags */
+#define ERF_PREEN_YES		0x00000001
+#define ERF_DEFAULT_YES		0x00000002
+#define ERF_DEFAULT_NO		0x00000004
+
+/* Prompt types */
+#define ERP_FIX			0x00000001
+#define ERP_TRUNCATE		0x00000002
+
+static const char *prompts[] = {
+	"Repair",
+	"Fix",
+	"Truncate",
+};
+
+static struct exfat_repair_problem problems[] = {
+	{ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX},
+	{ER_BS_BOOT_REGION, 0, ERP_FIX},
+	{ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX},
+	{ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX},
+	{ER_FILE_INVALID_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE},
+	{ER_FILE_FIRST_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE},
+	{ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE},
+	{ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE},
+	{ER_FILE_DUPLICATED_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE},
+	{ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX},
+};
+
+static struct exfat_repair_problem *find_problem(er_problem_code_t prcode)
+{
+	unsigned int i;
+
+	for (i = 0; i < sizeof(problems)/sizeof(problems[0]); i++) {
+		if (problems[i].prcode == prcode) {
+			return &problems[i];
+		}
+	}
+	return NULL;
+}
+
+static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr)
+{
+	bool repair = false;
+	char answer[8];
+
+	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;
+	}
+
+	printf(". %s (y/N)? %c\n", prompts[pr->prompt_type],
+		repair ? 'y' : 'n');
+	return repair;
+}
+
+bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
+			const char *desc, ...)
+{
+	struct exfat_repair_problem *pr = NULL;
+	va_list ap;
+
+	pr = find_problem(prcode);
+	if (!pr) {
+		exfat_err("unknown problem code. %#x\n", prcode);
+		return false;
+	}
+
+	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;
+}
diff --git a/fsck/repair.h b/fsck/repair.h
new file mode 100644
index 0000000..f7286b9
--- /dev/null
+++ b/fsck/repair.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#ifndef _REPAIR_H
+#define _REPAIR_H
+
+#define ER_BS_CHECKSUM			0x00000001
+#define ER_BS_BOOT_REGION		0x00000002
+#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;
+
+bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode,
+		const char *fmt, ...);
+
+#endif
diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h
new file mode 100644
index 0000000..74e3fb3
--- /dev/null
+++ b/include/exfat_ondisk.h
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#ifndef _EXFAT_H
+#define _EXFAT_H
+
+#include <stdint.h>
+#include <linux/fs.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WORDS_BIGENDIAN
+#define cpu_to_le16(x)	((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8))
+#define cpu_to_le32(x)	\
+	((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >>  8) | \
+	 (((x) & 0x0000ff00u) <<  8) | (((x) & 0x000000ffu) << 24))
+#define cpu_to_le64(x)	(cpu_to_le32((uint64_t)(x)) << 32 | \
+			cpu_to_le32((uint64_t)(x) >> 32))
+#else
+#define cpu_to_le16(x)	(x)
+#define cpu_to_le32(x)	(x)
+#define cpu_to_le64(x)	(x)
+#endif
+
+#define le64_to_cpu(x)  ((uint64_t)cpu_to_le64(x))
+#define le32_to_cpu(x)  ((uint32_t)cpu_to_le32(x))
+#define le16_to_cpu(x)  ((uint16_t)cpu_to_le16(x))
+
+#define PBR_SIGNATURE		0xAA55
+
+#define VOL_CLEAN		0x0000
+#define VOL_DIRTY		0x0002
+
+#define DENTRY_SIZE		32 /* directory entry size */
+#define DENTRY_SIZE_BITS	5
+/* exFAT allows 8388608(256MB) directory entries */
+#define MAX_EXFAT_DENTRIES	8388608
+
+/* dentry types */
+#define MSDOS_DELETED		0xE5	/* deleted mark */
+#define MSDOS_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 */
+#define EXFAT_BITMAP		0x81	/* allocation bitmap */
+#define EXFAT_UPCASE		0x82	/* upcase table */
+#define EXFAT_VOLUME		0x83	/* volume label */
+#define EXFAT_FILE		0x85	/* file or dir */
+#define EXFAT_GUID		0xA0
+#define EXFAT_PADDING		0xA1
+#define EXFAT_ACLTAB		0xA2
+#define EXFAT_STREAM		0xC0	/* stream entry */
+#define EXFAT_NAME		0xC1	/* file name entry */
+#define EXFAT_ACL		0xC2	/* stream entry */
+
+/* checksum types */
+#define CS_DIR_ENTRY		0
+#define CS_PBR_SECTOR		1
+#define CS_DEFAULT		2
+
+/* file attributes */
+#define ATTR_READONLY		0x0001
+#define ATTR_HIDDEN		0x0002
+#define ATTR_SYSTEM		0x0004
+#define ATTR_VOLUME		0x0008
+#define ATTR_SUBDIR		0x0010
+#define ATTR_ARCHIVE		0x0020
+#define ATTR_EXTEND		(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | \
+				 ATTR_VOLUME) /* 0x000F */
+
+#define ATTR_EXTEND_MASK	(ATTR_EXTEND | ATTR_SUBDIR | ATTR_ARCHIVE)
+#define ATTR_RWMASK		(ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \
+				 ATTR_SUBDIR | ATTR_ARCHIVE)
+
+#define ATTR_READONLY_LE	cpu_to_le16(0x0001)
+#define ATTR_HIDDEN_LE		cpu_to_le16(0x0002)
+#define ATTR_SYSTEM_LE		cpu_to_le16(0x0004)
+#define ATTR_VOLUME_LE		cpu_to_le16(0x0008)
+#define ATTR_SUBDIR_LE		cpu_to_le16(0x0010)
+#define ATTR_ARCHIVE_LE		cpu_to_le16(0x0020)
+
+/* stream flags */
+#define EXFAT_SF_CONTIGUOUS		0x02
+
+#define CLUSTER_32(x)			((unsigned int)((x) & 0xFFFFFFFFU))
+#define EXFAT_EOF_CLUSTER		CLUSTER_32(~0)
+#define EXFAT_BAD_CLUSTER		(0xFFFFFFF7U)
+#define EXFAT_FREE_CLUSTER		(0)
+#define EXFAT_FIRST_CLUSTER		(2)
+#define EXFAT_RESERVED_CLUSTERS		(2)
+
+
+/* EXFAT BIOS parameter block (64 bytes) */
+struct bpb64 {
+	__u8 jmp_boot[3];
+	__u8 oem_name[8];
+	__u8 res_zero[53];
+};
+
+/* EXFAT EXTEND BIOS parameter block (56 bytes) */
+struct bsx64 {
+	__le64 vol_offset;
+	__le64 vol_length;
+	__le32 fat_offset;
+	__le32 fat_length;
+	__le32 clu_offset;
+	__le32 clu_count;
+	__le32 root_cluster;
+	__le32 vol_serial;
+	__u8 fs_version[2];
+	__le16 vol_flags;
+	__u8 sect_size_bits;
+	__u8 sect_per_clus_bits;
+	__u8 num_fats;
+	__u8 phy_drv_no;
+	__u8 perc_in_use;
+	__u8 reserved2[7];
+};
+
+/* Common PBR[Partition Boot Record] (512 bytes) */
+struct pbr {
+	struct bpb64 bpb;
+	struct bsx64 bsx;
+	__u8 boot_code[390];
+	__le16 signature;
+};
+
+/* Extended Boot Sector */
+struct exbs {
+	__u8 zero[510];
+	__le16 signature;
+};
+
+/* Extended Boot Record (8 sectors) */
+struct expbr {
+	struct exbs eb[8];
+};
+
+#define VOLUME_LABEL_MAX_LEN	11
+#define ENTRY_NAME_MAX		15
+
+struct exfat_dentry {
+	__u8 type;
+	union {
+		struct {
+			__u8 character_count;
+			__le16 volume_label[VOLUME_LABEL_MAX_LEN];
+			__u8 reserved[8];
+		} __attribute__((packed)) vol; /* file directory entry */
+
+		struct {
+			__u8 num_ext;
+			__le16 checksum;
+			__le16 attr;
+			__le16 reserved1;
+			__le16 create_time;
+			__le16 create_date;
+			__le16 modify_time;
+			__le16 modify_date;
+			__le16 access_time;
+			__le16 access_date;
+			__u8 create_time_ms;
+			__u8 modify_time_ms;
+			__u8 access_time_ms;
+			__u8 reserved2[9];
+		} __attribute__((packed)) file; /* file directory entry */
+		struct {
+			__u8 flags;
+			__u8 reserved1;
+			__u8 name_len;
+			__le16 name_hash;
+			__le16 reserved2;
+			__le64 valid_size;
+			__le32 reserved3;
+			__le32 start_clu;
+			__le64 size;
+		} __attribute__((packed)) stream; /* stream extension directory entry */
+		struct {
+			__u8 flags;
+			__le16 unicode_0_14[15];
+		} __attribute__((packed)) name; /* file name directory entry */
+		struct {
+			__u8 flags;
+			__u8 reserved[18];
+			__le32 start_clu;
+			__le64 size;
+		} __attribute__((packed)) bitmap; /* allocation bitmap directory entry */
+		struct {
+			__u8 reserved1[3];
+			__le32 checksum;
+			__u8 reserved2[12];
+			__le32 start_clu;
+			__le64 size;
+		} __attribute__((packed)) upcase; /* up-case table directory entry */
+	} __attribute__((packed)) dentry;
+} __attribute__((packed));
+
+#define vol_char_cnt			dentry.vol.character_count
+#define vol_label			dentry.vol.volume_label
+#define file_num_ext			dentry.file.num_ext
+#define file_checksum			dentry.file.checksum
+#define file_attr			dentry.file.attr
+#define file_create_time		dentry.file.create_time
+#define file_create_date		dentry.file.create_date
+#define file_modify_time		dentry.file.modify_time
+#define file_modify_date		dentry.file.modify_date
+#define file_access_time		dentry.file.access_time
+#define file_access_date		dentry.file.access_date
+#define file_create_time_ms		dentry.file.create_time_ms
+#define file_modify_time_ms		dentry.file.modify_time_ms
+#define file_access_time_ms		dentry.file.access_time_ms
+#define stream_flags			dentry.stream.flags
+#define stream_name_len			dentry.stream.name_len
+#define stream_name_hash		dentry.stream.name_hash
+#define stream_start_clu		dentry.stream.start_clu
+#define stream_valid_size		dentry.stream.valid_size
+#define stream_size			dentry.stream.size
+#define name_flags			dentry.name.flags
+#define name_unicode			dentry.name.unicode_0_14
+#define bitmap_flags			dentry.bitmap.flags
+#define bitmap_start_clu		dentry.bitmap.start_clu
+#define bitmap_size			dentry.bitmap.size
+#define upcase_start_clu		dentry.upcase.start_clu
+#define upcase_size			dentry.upcase.size
+#define upcase_checksum			dentry.upcase.checksum
+
+#endif /* !_EXFAT_H */
diff --git a/include/libexfat.h b/include/libexfat.h
new file mode 100644
index 0000000..0357d77
--- /dev/null
+++ b/include/libexfat.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#ifndef _LIBEXFAT_H
+
+#include <stdbool.h>
+#include <sys/types.h>
+#include <wchar.h>
+#include <limits.h>
+
+#define KB			(1024)
+#define MB			(1024*1024)
+#define GB			(1024UL*1024UL*1024UL)
+
+#define __round_mask(x, y) ((__typeof__(x))((y)-1))
+#define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1)
+#define round_down(x, y) ((x) & ~__round_mask(x, y))
+
+#define MIN(a, b)	((a) < (b) ? (a) : (b))
+#define MAX(a, b)	((a) > (b) ? (a) : (b))
+
+#define DIV_ROUND_UP(__i, __d)	(((__i) + (__d) - 1) / (__d))
+
+#define EXFAT_MIN_NUM_SEC_VOL		(2048)
+#define EXFAT_MAX_NUM_SEC_VOL		((2 << 64) - 1)
+
+#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)
+
+/* Upcase tabel macro */
+#define EXFAT_UPCASE_TABLE_SIZE		(5836)
+
+/* Flags for tune.exfat and exfatlabel */
+#define EXFAT_GET_VOLUME_LABEL		0x01
+#define EXFAT_SET_VOLUME_LABEL		0x02
+#define EXFAT_GET_VOLUME_SERIAL		0x03
+#define EXFAT_SET_VOLUME_SERIAL		0x04
+
+enum {
+	BOOT_SEC_IDX = 0,
+	EXBOOT_SEC_IDX,
+	EXBOOT_SEC_NUM = 8,
+	OEM_SEC_IDX,
+	RESERVED_SEC_IDX,
+	CHECKSUM_SEC_IDX,
+	BACKUP_BOOT_SEC_IDX,
+};
+
+struct exfat_blk_dev {
+	int dev_fd;
+	unsigned long long offset;
+	unsigned long long size;
+	unsigned int sector_size;
+	unsigned int sector_size_bits;
+	unsigned long long num_sectors;
+	unsigned int num_clusters;
+	unsigned int cluster_size;
+};
+
+struct exfat_user_input {
+	char dev_name[255];
+	bool writeable;
+	unsigned int cluster_size;
+	unsigned int sec_per_clu;
+	unsigned int boundary_align;
+	bool pack_bitmap;
+	bool quick;
+	__u16 volume_label[VOLUME_LABEL_MAX_LEN];
+	int volume_label_len;
+	unsigned int volume_serial;
+};
+
+void show_version(void);
+
+void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap,
+		unsigned int clu);
+void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap,
+		unsigned int clu);
+wchar_t exfat_bad_char(wchar_t w);
+void boot_calc_checksum(unsigned char *sector, unsigned short size,
+		bool is_boot_sec, __le32 *checksum);
+void init_user_input(struct exfat_user_input *ui);
+int exfat_get_blk_dev_info(struct exfat_user_input *ui,
+		struct exfat_blk_dev *bd);
+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);
+off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd);
+int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off);
+int exfat_set_volume_label(struct exfat_blk_dev *bd,
+		char *label_input, off_t root_clu_off);
+int exfat_read_sector(struct exfat_blk_dev *bd, void *buf,
+		unsigned int sec_off);
+int exfat_write_sector(struct exfat_blk_dev *bd, void *buf,
+		unsigned int sec_off);
+int exfat_write_checksum_sector(struct exfat_blk_dev *bd,
+		unsigned int checksum, bool is_backup);
+char *exfat_conv_volume_label(struct exfat_dentry *vol_entry);
+int exfat_show_volume_serial(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui);
+int exfat_set_volume_serial(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui);
+unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd,
+		unsigned int clu_off, unsigned int clu);
+
+
+/*
+ * Exfat Print
+ */
+
+extern unsigned int print_level;
+
+#define EXFAT_ERROR	(1)
+#define EXFAT_INFO	(2)
+#define EXFAT_DEBUG	(3)
+
+#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 */
diff --git a/include/list.h b/include/list.h
new file mode 100644
index 0000000..30a32de
--- /dev/null
+++ b/include/list.h
@@ -0,0 +1,336 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#ifndef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+
+#define container_of(ptr, type, member) ({			\
+	const typeof(((type *)0)->member) * __mptr = (ptr);	\
+	(type *)((char *)__mptr - offsetof(type, member));	\
+	})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/**
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head *prev, struct list_head *next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->next = LIST_POISON1;
+	entry->prev = LIST_POISON2;
+}
+
+
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+	__list_del(list->prev, list->next);
+	list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+				  struct list_head *head)
+{
+	__list_del(list->prev, list->next);
+	list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+	return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+				 struct list_head *head)
+{
+	struct list_head *first = list->next;
+	struct list_head *last = list->prev;
+	struct list_head *at = head->next;
+
+	first->prev = head;
+	head->next = first;
+
+	last->next = at;
+	at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+				    struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head);	\
+		pos = pos->next)
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+	for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
+		pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_entry((head)->next, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)			\
+	for (pos = list_entry((head)->prev, typeof(*pos), member);	\
+	     &pos->member != (head);	\
+	     pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use as a start point in
+ * list_for_each_entry_continue
+ * @pos:	the type * to use as a start point
+ * @head:	the head of the list
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_prepare_entry(pos, head, member) \
+	((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - iterate over list of given type
+ * continuing after existing point
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_continue(pos, head, member)			\
+	for (pos = list_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head);	\
+	     pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against
+ * removal of list entry
+ * @pos:	the type * to use as a loop counter.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_entry((head)->next, typeof(*pos), member),	\
+		n = list_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_continue - iterate over list of given type
+ * continuing after existing point safe against removal of list entry
+ * @pos:	the type * to use as a loop counter.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member)		\
+	for (pos = list_entry(pos->member.next, typeof(*pos), member),	\
+		n = list_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list of given
+ * type safe against removal of list entry
+ * @pos:	the type * to use as a loop counter.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member)		\
+	for (pos = list_entry((head)->prev, typeof(*pos), member),	\
+		n = list_entry(pos->member.prev, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = n, n = list_entry(n->member.prev, typeof(*n), member))
+
+#endif
diff --git a/include/version.h b/include/version.h
new file mode 100644
index 0000000..ae6291f
--- /dev/null
+++ b/include/version.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2020 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#ifndef _VERSION_H
+
+#define EXFAT_PROGS_VERSION "1.1.1"
+
+#endif /* !_VERSION_H */
diff --git a/label/Android.bp b/label/Android.bp
new file mode 100644
index 0000000..e7300a8
--- /dev/null
+++ b/label/Android.bp
@@ -0,0 +1,20 @@
+// Copyright 2020 The Android Open Source Project
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_exfatprogs_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+cc_binary {
+    name: "exfatlabel",
+
+    srcs: [
+        "label.c",
+    ],
+    defaults: ["exfatprogs-defaults"],
+    static_libs: ["libexfat"],
+}
diff --git a/label/Makefile.am b/label/Makefile.am
new file mode 100644
index 0000000..3f980bb
--- /dev/null
+++ b/label/Makefile.am
@@ -0,0 +1,6 @@
+AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+exfatlabel_LDADD = $(top_builddir)/lib/libexfat.a
+
+sbin_PROGRAMS = exfatlabel
+
+exfatlabel_SOURCES = label.c
diff --git a/label/label.c b/label/label.c
new file mode 100644
index 0000000..fd77a54
--- /dev/null
+++ b/label/label.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2020 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <locale.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: exfatlabel\n");
+	fprintf(stderr, "\t-i | --volume-serial                  Switch to volume serial mode\n");
+	fprintf(stderr, "\t-V | --version                        Show version\n");
+	fprintf(stderr, "\t-h | --help                           Show help\n");
+
+	exit(EXIT_FAILURE);
+}
+
+static struct option opts[] = {
+	{"volume-serial",	no_argument,		NULL,	'i' },
+	{"version",		no_argument,		NULL,	'V' },
+	{"help",		no_argument,		NULL,	'h' },
+	{"?",			no_argument,		NULL,	'?' },
+	{NULL,			0,			NULL,	 0  }
+};
+
+int main(int argc, char *argv[])
+{
+	int c;
+	int ret = EXIT_FAILURE;
+	struct exfat_blk_dev bd;
+	struct exfat_user_input ui;
+	bool version_only = false;
+	off_t root_clu_off;
+	int serial_mode = 0;
+	int flags = 0;
+
+	init_user_input(&ui);
+
+	if (!setlocale(LC_CTYPE, ""))
+		exfat_err("failed to init locale/codeset\n");
+
+	if (argc == 2)
+		flags = EXFAT_GET_VOLUME_LABEL;
+	else if (argc == 3)
+		flags = EXFAT_SET_VOLUME_LABEL;
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "iVh", opts, NULL)) != EOF)
+		switch (c) {
+		case 'i':
+			serial_mode = true;
+			if (argc == 3)
+				flags = EXFAT_GET_VOLUME_SERIAL;
+			else if (argc == 4)
+				flags = EXFAT_SET_VOLUME_SERIAL;
+
+			break;
+		case 'V':
+			version_only = true;
+			break;
+		case '?':
+		case 'h':
+		default:
+			usage();
+	}
+
+	show_version();
+	if (version_only)
+		exit(EXIT_FAILURE);
+
+	if (argc < 2)
+		usage();
+
+	memset(ui.dev_name, 0, sizeof(ui.dev_name));
+	snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[serial_mode + 1]);
+
+	ret = exfat_get_blk_dev_info(&ui, &bd);
+	if (ret < 0)
+		goto out;
+
+	if (serial_mode) {
+		/* Mode to change or display volume serial */
+		if (flags == EXFAT_GET_VOLUME_SERIAL) {
+			ret = exfat_show_volume_serial(&bd, &ui);
+		} else if (flags == EXFAT_SET_VOLUME_SERIAL) {
+			ui.volume_serial = strtoul(argv[3], NULL, 0);
+			ret = exfat_set_volume_serial(&bd, &ui);
+		}
+	} else {
+		/* Mode to change or display volume label */
+		root_clu_off = exfat_get_root_entry_offset(&bd);
+		if (root_clu_off < 0)
+			goto close_fd_out;
+
+		if (flags == EXFAT_GET_VOLUME_LABEL)
+			ret = exfat_show_volume_label(&bd, root_clu_off);
+		else if (flags == EXFAT_SET_VOLUME_LABEL)
+			ret = exfat_set_volume_label(&bd, argv[2], root_clu_off);
+	}
+
+close_fd_out:
+	close(bd.dev_fd);
+out:
+	return ret;
+}
diff --git a/lib/Android.bp b/lib/Android.bp
new file mode 100644
index 0000000..b90a741
--- /dev/null
+++ b/lib/Android.bp
@@ -0,0 +1,19 @@
+// Copyright 2020 The Android Open Source Project
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_exfatprogs_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+cc_library_static {
+    name: "libexfat",
+
+    srcs: [
+        "libexfat.c",
+    ],
+    defaults: ["exfatprogs-defaults"],
+}
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..5ea12db
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,4 @@
+AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+noinst_LIBRARIES = libexfat.a
+
+libexfat_a_SOURCES = libexfat.c
diff --git a/lib/libexfat.c b/lib/libexfat.c
new file mode 100644
index 0000000..13dfbaf
--- /dev/null
+++ b/lib/libexfat.c
@@ -0,0 +1,662 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <wchar.h>
+#include <limits.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "version.h"
+
+#define BITS_PER_LONG		(sizeof(long) * CHAR_BIT)
+
+#ifdef WORDS_BIGENDIAN
+#define BITOP_LE_SWIZZLE	((BITS_PER_LONG - 1) & ~0x7)
+#else
+#define BITOP_LE_SWIZZLE        0
+#endif
+
+#define BIT_MASK(nr)            (1UL << ((nr) % BITS_PER_LONG))
+#define BIT_WORD(nr)            ((nr) / BITS_PER_LONG)
+
+unsigned int print_level  = EXFAT_INFO;
+
+static inline void set_bit(int nr, void *addr)
+{
+	unsigned long mask = BIT_MASK(nr);
+	unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
+
+	*p  |= mask;
+}
+
+static inline void clear_bit(int nr, void *addr)
+{
+	unsigned long mask = BIT_MASK(nr);
+	unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
+
+	*p &= ~mask;
+}
+
+static inline void set_bit_le(int nr, void *addr)
+{
+	set_bit(nr ^ BITOP_LE_SWIZZLE, addr);
+}
+
+static inline void clear_bit_le(int nr, void *addr)
+{
+	clear_bit(nr ^ BITOP_LE_SWIZZLE, addr);
+}
+
+void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap,
+		unsigned int clu)
+{
+	int b;
+
+	b = clu & ((bd->sector_size << 3) - 1);
+
+	set_bit_le(b, bitmap);
+}
+
+void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap,
+		unsigned int clu)
+{
+	int b;
+
+	b = clu & ((bd->sector_size << 3) - 1);
+
+	clear_bit_le(b, bitmap);
+}
+
+wchar_t exfat_bad_char(wchar_t w)
+{
+	return (w < 0x0020)
+		|| (w == '*') || (w == '?') || (w == '<') || (w == '>')
+		|| (w == '|') || (w == '"') || (w == ':') || (w == '/')
+		|| (w == '\\');
+}
+
+void boot_calc_checksum(unsigned char *sector, unsigned short size,
+		bool is_boot_sec, __le32 *checksum)
+{
+	unsigned int index;
+
+	if (is_boot_sec) {
+		for (index = 0; index < size; index++) {
+			if ((index == 106) || (index == 107) || (index == 112))
+				continue;
+			*checksum = ((*checksum & 1) ? 0x80000000 : 0) +
+				(*checksum >> 1) + sector[index];
+		}
+	} else {
+		for (index = 0; index < size; index++) {
+			*checksum = ((*checksum & 1) ? 0x80000000 : 0) +
+				(*checksum >> 1) + sector[index];
+		}
+	}
+}
+
+void show_version(void)
+{
+	printf("exfatprogs version : %s\n", EXFAT_PROGS_VERSION);
+}
+
+static inline unsigned int sector_size_bits(unsigned int size)
+{
+	unsigned int bits = 8;
+
+	do {
+		bits++;
+		size >>= 1;
+	} while (size > 256);
+
+	return bits;
+}
+
+static void exfat_set_default_cluster_size(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	if (256 * MB >= bd->size)
+		ui->cluster_size = 4 * KB;
+	else if (32 * GB >= bd->size)
+		ui->cluster_size = 32 * KB;
+	else
+		ui->cluster_size = 128 * KB;
+}
+
+void init_user_input(struct exfat_user_input *ui)
+{
+	memset(ui, 0, sizeof(struct exfat_user_input));
+	ui->writeable = true;
+	ui->quick = true;
+}
+
+int exfat_get_blk_dev_info(struct exfat_user_input *ui,
+		struct exfat_blk_dev *bd)
+{
+	int fd, ret = -1;
+	off_t blk_dev_size;
+	struct stat st;
+	unsigned long long blk_dev_offset = 0;
+
+	fd = open(ui->dev_name, ui->writeable ? O_RDWR|O_EXCL : O_RDONLY);
+	if (fd < 0) {
+		exfat_err("open failed : %s, %s\n", ui->dev_name,
+			strerror(errno));
+		return -1;
+	}
+	blk_dev_size = lseek(fd, 0, SEEK_END);
+	if (blk_dev_size <= 0) {
+		exfat_err("invalid block device size(%s)\n",
+			ui->dev_name);
+		ret = blk_dev_size;
+		close(fd);
+		goto out;
+	}
+
+	if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) {
+		char pathname[sizeof("/sys/dev/block/4294967295:4294967295/start")];
+		FILE *fp;
+
+		snprintf(pathname, sizeof(pathname), "/sys/dev/block/%u:%u/start",
+			major(st.st_rdev), minor(st.st_rdev));
+		fp = fopen(pathname, "r");
+		if (fp != NULL) {
+			if (fscanf(fp, "%llu", &blk_dev_offset) == 1) {
+				/*
+				 * Linux kernel always reports partition offset
+				 * in 512-byte units, regardless of sector size
+				 */
+				blk_dev_offset <<= 9;
+			}
+			fclose(fp);
+		}
+	}
+
+	bd->dev_fd = fd;
+	bd->offset = blk_dev_offset;
+	bd->size = blk_dev_size;
+	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);
+	bd->num_sectors = blk_dev_size / DEFAULT_SECTOR_SIZE;
+	bd->num_clusters = blk_dev_size / ui->cluster_size;
+
+	exfat_debug("Block device name : %s\n", ui->dev_name);
+	exfat_debug("Block device offset : %llu\n", bd->offset);
+	exfat_debug("Block device size : %llu\n", bd->size);
+	exfat_debug("Block sector size : %u\n", bd->sector_size);
+	exfat_debug("Number of the sectors : %llu\n",
+		bd->num_sectors);
+	exfat_debug("Number of the clusters : %u\n",
+		bd->num_clusters);
+
+	ret = 0;
+	bd->dev_fd = fd;
+out:
+	return ret;
+}
+
+ssize_t exfat_read(int fd, void *buf, size_t size, off_t offset)
+{
+	return pread(fd, buf, size, offset);
+}
+
+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;
+	wchar_t *wcs;
+
+	mbs_len = mbstowcs(NULL, in_str, 0);
+	if (mbs_len == (size_t)-1) {
+		if (errno == EINVAL || errno == EILSEQ)
+			exfat_err("invalid character sequence in current locale\n");
+		return -errno;
+	}
+
+	wcs = calloc(mbs_len+1, sizeof(wchar_t));
+	if (!wcs)
+		return -ENOMEM;
+
+	/* First convert multibyte char* string to wchar_t* string */
+	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;
+	}
+
+	/* Convert wchar_t* string (sequence of code points) to UTF-16 string */
+	for (i = 0, out_len = 0; i < mbs_len; i++) {
+		if (2*(out_len+1) > out_size ||
+		    (wcs[i] >= 0x10000 && 2*(out_len+2) > out_size)) {
+			exfat_err("input string is too long\n");
+			free(wcs);
+			return -E2BIG;
+		}
+
+		/* Encode code point above Plane0 as UTF-16 surrogate pair */
+		if (wcs[i] >= 0x10000) {
+			out_str[out_len++] =
+			  cpu_to_le16(((wcs[i] - 0x10000) >> 10) + 0xD800);
+			wcs[i] = ((wcs[i] - 0x10000) & 0x3FF) + 0xDC00;
+		}
+
+		out_str[out_len++] = cpu_to_le16(wcs[i]);
+	}
+
+	free(wcs);
+	return 2*out_len;
+}
+
+ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
+			char *out_str, size_t out_size)
+{
+	size_t wcs_len, out_len, c_len, i;
+	char c_str[MB_LEN_MAX];
+	wchar_t *wcs;
+	mbstate_t ps;
+	wchar_t w;
+
+	wcs = calloc(in_len/2+1, sizeof(wchar_t));
+	if (!wcs)
+		return -ENOMEM;
+
+	/* First convert UTF-16 string to wchar_t* string */
+	for (i = 0, wcs_len = 0; i < in_len/2; i++, wcs_len++) {
+		wcs[wcs_len] = le16_to_cpu(in_str[i]);
+		/*
+		 * If wchar_t can store code point above Plane0
+		 * then unpack UTF-16 surrogate pair to code point
+		 */
+#if WCHAR_MAX >= 0x10FFFF
+		if (wcs[wcs_len] >= 0xD800 && wcs[wcs_len] <= 0xDBFF &&
+		    i+1 < in_len/2) {
+			w = le16_to_cpu(in_str[i+1]);
+			if (w >= 0xDC00 && w <= 0xDFFF) {
+				wcs[wcs_len] = 0x10000 +
+					       ((wcs[wcs_len] - 0xD800) << 10) +
+					       (w - 0xDC00);
+				i++;
+			}
+		}
+#endif
+	}
+
+	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++) {
+		c_len = wcrtomb(c_str, wcs[i], &ps);
+		/*
+		 * If character is non-representable in current locale then
+		 * try to store it as Unicode replacement code point U+FFFD
+		 */
+		if (c_len == (size_t)-1 && errno == EILSEQ)
+			c_len = wcrtomb(c_str, 0xFFFD, &ps);
+		/* If U+FFFD is also non-representable, try question mark */
+		if (c_len == (size_t)-1 && errno == EILSEQ)
+			c_len = wcrtomb(c_str, L'?', &ps);
+		/* If also (7bit) question mark fails then we cannot do more */
+		if (c_len == (size_t)-1) {
+			exfat_err("invalid UTF-16 sequence\n");
+			free(wcs);
+			return -errno;
+		}
+		if (out_len+c_len > out_size) {
+			exfat_err("input string is too long\n");
+			free(wcs);
+			return -E2BIG;
+		}
+		memcpy(out_str+out_len, c_str, c_len);
+		out_len += c_len;
+	}
+
+	free(wcs);
+
+	/* Last iteration of above loop should have produced null byte */
+	if (c_len == 0 || out_str[out_len-1] != 0) {
+		exfat_err("invalid UTF-16 sequence\n");
+		return -errno;
+	}
+
+	return out_len-1;
+}
+
+off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd)
+{
+	struct pbr *bs;
+	int nbytes;
+	unsigned int cluster_size;
+	off_t root_clu_off;
+
+	bs = (struct pbr *)malloc(sizeof(struct pbr));
+	if (!bs) {
+		exfat_err("failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	nbytes = exfat_read(bd->dev_fd, bs, sizeof(struct pbr), 0);
+	if (nbytes != sizeof(struct pbr)) {
+		exfat_err("boot sector read failed: %d\n", errno);
+		free(bs);
+		return -1;
+	}
+
+	cluster_size = (1 << bs->bsx.sect_per_clus_bits) * bd->sector_size;
+	root_clu_off = le32_to_cpu(bs->bsx.clu_offset) * bd->sector_size +
+		le32_to_cpu(bs->bsx.root_cluster - EXFAT_RESERVED_CLUSTERS)
+		* cluster_size;
+	free(bs);
+
+	return root_clu_off;
+}
+
+char *exfat_conv_volume_label(struct exfat_dentry *vol_entry)
+{
+	char *volume_label;
+	__le16 disk_label[VOLUME_LABEL_MAX_LEN];
+
+	volume_label = malloc(VOLUME_LABEL_BUFFER_SIZE);
+	if (!volume_label)
+		return NULL;
+
+	memcpy(disk_label, vol_entry->vol_label, sizeof(disk_label));
+	memset(volume_label, 0, VOLUME_LABEL_BUFFER_SIZE);
+	if (exfat_utf16_dec(disk_label, vol_entry->vol_char_cnt*2,
+		volume_label, VOLUME_LABEL_BUFFER_SIZE) < 0) {
+		exfat_err("failed to decode volume label\n");
+		free(volume_label);
+		return NULL;
+	}
+
+	return volume_label;
+}
+
+int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off)
+{
+	struct exfat_dentry *vol_entry;
+	char *volume_label;
+	int nbytes;
+
+	vol_entry = malloc(sizeof(struct exfat_dentry));
+	if (!vol_entry) {
+		exfat_err("failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	nbytes = exfat_read(bd->dev_fd, vol_entry,
+		sizeof(struct exfat_dentry), root_clu_off);
+	if (nbytes != sizeof(struct exfat_dentry)) {
+		exfat_err("volume entry read failed: %d\n", errno);
+		free(vol_entry);
+		return -1;
+	}
+
+	volume_label = exfat_conv_volume_label(vol_entry);
+	if (!volume_label) {
+		free(vol_entry);
+		return -EINVAL;
+	}
+
+	exfat_info("label: %s\n", volume_label);
+
+	free(volume_label);
+	free(vol_entry);
+	return 0;
+}
+
+int exfat_set_volume_label(struct exfat_blk_dev *bd,
+		char *label_input, off_t root_clu_off)
+{
+	struct exfat_dentry vol;
+	int nbytes;
+	__u16 volume_label[VOLUME_LABEL_MAX_LEN];
+	int volume_label_len;
+
+	volume_label_len = exfat_utf16_enc(label_input,
+			volume_label, sizeof(volume_label));
+	if (volume_label_len < 0) {
+		exfat_err("failed to encode volume label\n");
+		return -1;
+	}
+
+	vol.type = EXFAT_VOLUME;
+	memset(vol.vol_label, 0, sizeof(vol.vol_label));
+	memcpy(vol.vol_label, volume_label, volume_label_len);
+	vol.vol_char_cnt = volume_label_len/2;
+
+	nbytes = exfat_write(bd->dev_fd, &vol, sizeof(struct exfat_dentry),
+			root_clu_off);
+	if (nbytes != sizeof(struct exfat_dentry)) {
+		exfat_err("volume entry write failed: %d\n", errno);
+		return -1;
+	}
+	fsync(bd->dev_fd);
+
+	exfat_info("new label: %s\n", label_input);
+	return 0;
+}
+
+int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off)
+{
+	int ret;
+	unsigned long long offset = sec_off * bd->sector_size;
+
+	lseek(bd->dev_fd, offset, SEEK_SET);
+	ret = read(bd->dev_fd, buf, bd->sector_size);
+	if (ret < 0) {
+		exfat_err("read failed, sec_off : %u\n", sec_off);
+		return -1;
+	}
+	return 0;
+}
+
+int exfat_write_sector(struct exfat_blk_dev *bd, void *buf,
+		unsigned int sec_off)
+{
+	int bytes;
+	unsigned long long offset = sec_off * bd->sector_size;
+
+	lseek(bd->dev_fd, offset, SEEK_SET);
+	bytes = write(bd->dev_fd, buf, bd->sector_size);
+	if (bytes != (int)bd->sector_size) {
+		exfat_err("write failed, sec_off : %u, bytes : %d\n", sec_off,
+			bytes);
+		return -1;
+	}
+	return 0;
+}
+
+int exfat_write_checksum_sector(struct exfat_blk_dev *bd,
+		unsigned int checksum, bool is_backup)
+{
+	__le32 *checksum_buf;
+	int ret = 0;
+	unsigned int i;
+	unsigned int sec_idx = CHECKSUM_SEC_IDX;
+
+	checksum_buf = malloc(bd->sector_size);
+	if (!checksum_buf)
+		return -1;
+
+	if (is_backup)
+		sec_idx += BACKUP_BOOT_SEC_IDX;
+
+	for (i = 0; i < bd->sector_size / sizeof(int); i++)
+		checksum_buf[i] = cpu_to_le32(checksum);
+
+	ret = exfat_write_sector(bd, checksum_buf, sec_idx);
+	if (ret) {
+		exfat_err("checksum sector write failed\n");
+		goto free;
+	}
+
+free:
+	free(checksum_buf);
+	return ret;
+}
+
+int exfat_show_volume_serial(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	struct pbr *ppbr;
+	int ret;
+
+	ppbr = malloc(bd->sector_size);
+	if (!ppbr) {
+		exfat_err("Cannot allocate pbr: out of memory\n");
+		return -1;
+	}
+
+	/* read main boot sector */
+	ret = exfat_read_sector(bd, (char *)ppbr, BOOT_SEC_IDX);
+	if (ret < 0) {
+		exfat_err("main boot sector read failed\n");
+		ret = -1;
+		goto free_ppbr;
+	}
+
+	exfat_info("volume serial : 0x%x\n", ppbr->bsx.vol_serial);
+
+free_ppbr:
+	free(ppbr);
+	return ret;
+}
+
+static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, bool is_backup)
+{
+	unsigned int checksum = 0;
+	int ret, sec_idx, backup_sec_idx = 0;
+	int sector_size = bd->sector_size;
+	unsigned char *buf;
+
+	buf = malloc(bd->sector_size);
+	if (!buf) {
+		exfat_err("Cannot allocate pbr: out of memory\n");
+		return -1;
+	}
+
+	if (is_backup)
+		backup_sec_idx = BACKUP_BOOT_SEC_IDX;
+
+	for (sec_idx = BOOT_SEC_IDX; sec_idx < CHECKSUM_SEC_IDX; sec_idx++) {
+		bool is_boot_sec = false;
+
+		ret = exfat_read_sector(bd, buf, sec_idx + backup_sec_idx);
+		if (ret < 0) {
+			exfat_err("sector(%d) read failed\n", sec_idx);
+			ret = -1;
+			goto free_buf;
+		}
+
+		if (sec_idx == BOOT_SEC_IDX) {
+			is_boot_sec = true;
+			sector_size = sizeof(struct pbr);
+		} else if (sec_idx >= EXBOOT_SEC_IDX && sec_idx < OEM_SEC_IDX)
+			sector_size = sizeof(struct exbs);
+
+		boot_calc_checksum(buf, sector_size, is_boot_sec,
+			&checksum);
+	}
+
+	ret = exfat_write_checksum_sector(bd, checksum, is_backup);
+
+free_buf:
+	free(buf);
+
+	return ret;
+}
+
+int exfat_set_volume_serial(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	int ret;
+	struct pbr *ppbr;
+
+	ppbr = malloc(bd->sector_size);
+	if (!ppbr) {
+		exfat_err("Cannot allocate pbr: out of memory\n");
+		return -1;
+	}
+
+	/* read main boot sector */
+	ret = exfat_read_sector(bd, (char *)ppbr, BOOT_SEC_IDX);
+	if (ret < 0) {
+		exfat_err("main boot sector read failed\n");
+		ret = -1;
+		goto free_ppbr;
+	}
+
+	ppbr->bsx.vol_serial = ui->volume_serial;
+
+	/* update main boot sector */
+	ret = exfat_write_sector(bd, (char *)ppbr, BOOT_SEC_IDX);
+	if (ret < 0) {
+		exfat_err("main boot sector write failed\n");
+		ret = -1;
+		goto free_ppbr;
+	}
+
+	/* update backup boot sector */
+	ret = exfat_write_sector(bd, (char *)ppbr, BACKUP_BOOT_SEC_IDX);
+	if (ret < 0) {
+		exfat_err("backup boot sector write failed\n");
+		ret = -1;
+		goto free_ppbr;
+	}
+
+	ret = exfat_update_boot_checksum(bd, 0);
+	if (ret < 0) {
+		exfat_err("main checksum update failed\n");
+		goto free_ppbr;
+	}
+
+	ret = exfat_update_boot_checksum(bd, 1);
+	if (ret < 0)
+		exfat_err("backup checksum update failed\n");
+free_ppbr:
+	free(ppbr);
+
+	exfat_info("New volume serial : 0x%x\n", ui->volume_serial);
+
+	return ret;
+}
+
+unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd,
+		unsigned int clu_off_sectnr, unsigned int clu)
+{
+	return clu_off_sectnr * bd->sector_size +
+		(clu - EXFAT_RESERVED_CLUSTERS) * bd->cluster_size;
+}
diff --git a/manpages/dump.exfat.8 b/manpages/dump.exfat.8
new file mode 100644
index 0000000..4c6f589
--- /dev/null
+++ b/manpages/dump.exfat.8
@@ -0,0 +1,17 @@
+.TH dump.exfat 8
+.SH NAME
+dump.exfat \- Show on-disk information of exfat filesystem
+.SH SYNOPSIS
+.B dump.exfat
+.I device
+.br
+.B dump.exfat \-V
+.SH DESCRIPTION
+.B dump.exfat
+Print on-disk information from given device that formatted by exFAT filesystem.
+
+.PP
+.SH OPTIONS
+.TP
+.B \-V
+Prints the version number and exits.
diff --git a/manpages/exfatlabel.8 b/manpages/exfatlabel.8
new file mode 100644
index 0000000..f3274bb
--- /dev/null
+++ b/manpages/exfatlabel.8
@@ -0,0 +1,36 @@
+.TH exfatlabel 8
+.SH NAME
+exfatlabel \- Get or Set volume label or volume serial of an exFAT filesystem
+.SH SYNOPSIS
+.B exfatlabel
+[
+.B \-i
+.I volume-label
+] [
+.B \-v
+]
+.I device
+[
+.I label_string or serial_value
+]
+.br
+.B exfatlabel \-V
+.SH DESCRIPTION
+.B exfatlabel
+Print or set volume label of an existing exFAT filesystem.
+
+If there is a
+.I label_string
+in argument of exfatlabel, It will be written to volume label
+field on given device. If not, exfatlabel will just print out
+after reading volume label field from given device. If -i or
+--volume-serial is given, It can be switched to volume serial
+mode.
+.PP
+.SH OPTIONS
+.TP
+.BI \-i
+Switch to volume serial mode.
+.TP
+.B \-V
+Prints the version number and exits.
diff --git a/manpages/fsck.exfat.8 b/manpages/fsck.exfat.8
new file mode 100644
index 0000000..83f7815
--- /dev/null
+++ b/manpages/fsck.exfat.8
@@ -0,0 +1,51 @@
+.TH fsck.exfat 8
+.SH NAME
+fsck.exfat \- check an exFAT filesystem
+.SH SYNOPSIS
+.B fsck.exfat
+[
+.B \-a
+] [
+.B \-n
+] [
+.B \-r
+] [
+.B \-v
+] [
+.B \-y
+] [
+.B \-v
+]
+.I device
+.br
+.B fsck.exfat \-V
+.SH DESCRIPTION
+.B fsck.exfat
+checks an exFAT filesystem and repairs the filesystem
+depending on the options passed.
+.PP
+.SH OPTIONS
+.TP
+.BI \-a
+This  option  does  the same thing as the -p option.  It is provided for backwards compatibility only;  it  is  suggested  that people use -p option whenever possible.
+.TP
+.BI \-n
+Check the filesystem but do not attempt to repair the filesystem.
+.TP
+.BI \-p
+Repair the filesystem without user interaction if it can be done safely.
+.TP
+.BI \-r
+Repair the filesystem interactively.
+.TP
+.BI \-v
+Prints verbose debugging information while checking the exFAT filesystem.
+.TP
+.BI \-V
+Prints the version number and exits.
+.TP
+.B \-y
+Repair the filesystem answering yes to all questions.
+.SH SEE ALSO
+.BR fsck (8),
+.BR fstab (5),
diff --git a/manpages/mkfs.exfat.8 b/manpages/mkfs.exfat.8
new file mode 100644
index 0000000..9f867d3
--- /dev/null
+++ b/manpages/mkfs.exfat.8
@@ -0,0 +1,111 @@
+.TH mkfs.exfat 8
+.SH NAME
+mkfs.exfat \- create an exFAT filesystem
+.SH SYNOPSIS
+.B mkfs.exfat
+[
+.B \-b
+.I boundary_alignment
+] [
+.B \-c
+.I cluster_size
+] [
+.B \-f
+] [
+.B \-h
+] [
+.B \-L
+.I volume_label
+] [
+.B \-\-pack\-bitmap
+] [
+.B \-v
+]
+.I device
+.br
+.B mkfs.exfat \-V
+.SH DESCRIPTION
+.B mkfs.exfat
+creates an exFAT filesystem by writing on a special
+file using the values found in the arguments of the command line.
+It is invoked automatically by
+.BR mkfs (8)
+when it is given the
+.B \-t exfat
+option.
+.PP
+As an example, to make a filesystem on the first partition on the first
+SCSI disk, use:
+.IP
+.B mkfs.exfat /dev/sda1
+.PP
+.SH OPTIONS
+.TP
+.BR \-b ", " \-\-boundary\-align =\fIalignment\fR
+Specifies the alignment for the FAT and the start of the cluster heap.
+The \fIalignment\fR argument is specified in bytes or may be specified with
+\fBm\fR/\fBM\fR suffix for mebibytes or \fBk\fR/\fBK\fR suffix for kibibytes
+and should be a power of two.
+Some media like SD cards need this for optimal performance and endurance,
+in which case \fIalignment\fR should be set to half of the card's native
+boundary unit size.
+If the card's native boundary unit size is not known, refer to the following
+table of boundary unit sizes recommended by the SD Card Association.
+.\" source: SD Specifications Part 2: File System Specification Version 3.00
+.TS
+center;
+cb1s6cbcb,nnnn.
+Card Capacity Range	Cluster Size	Boundary Unit
+_
+	\[<=]8 MiB	8 KiB	8 KiB
+>8 MiB	\[<=]64 MiB	16 KiB	16 KiB
+>64 MiB	\[<=]256 MiB	16 KiB	32 KiB
+>256 MiB	\[<=]1 GiB	16 KiB	64 KiB
+>1 GiB	\[<=]2 GiB	32 KiB	64 KiB
+>2 GiB	\[<=]32 GiB	32 KiB	4 MiB
+>32 GiB	\[<=]128 GiB	128 KiB	16 MiB
+>128 GiB	\[<=]512 GiB	256 KiB	32 MiB
+>512 GiB	\[<=]2 TiB	512 KiB	64 MiB
+.TE
+.TP
+.BR \-c ", " \-\-cluster\-size =\fIsize\fR
+Specifies the cluster size of the exFAT file system.
+The \fIsize\fR argument is specified in bytes or may be specified with
+\fBm\fR/\fBM\fR suffix for mebibytes or \fBk\fR/\fBK\fR suffix for kibibytes
+and must be a power of two.
+.TP
+.BR \-f ", " \-\-full\-format
+Performs a full format.
+This zeros the entire disk device while creating the exFAT filesystem.
+.TP
+.BR \-h ", " \-\-help
+Prints the help and exit.
+.TP
+.BR \-L ", " \-\-volume\-label =\fIlabel\fR
+Specifies the volume label to be associated with the exFAT filesystem.
+.TP
+.B \-\-pack\-bitmap
+Attempts to relocate the exFAT allocation bitmap so that it ends at the
+alignment boundary immediately following the FAT rather than beginning at that
+boundary.
+This strictly violates the SD card specification but may improve performance
+and endurance on SD cards and other flash media not designed for use with exFAT
+by allowing file-system metadata updates to touch fewer flash allocation units.
+Furthermore, many SD cards and other flash devices specially optimize the
+allocation unit where the FAT resides so as to support tiny writes with reduced
+write amplification but expect only larger writes in subsequent allocation
+units \[em] where the exFAT bitmap would be placed by default.
+Specifying \fB\-\-pack\-bitmap\fR attempts to avoid the potential problems
+associated with issuing many small writes to the bitmap by making it share an
+allocation unit with the FAT.
+If there is insufficient space for the bitmap there, then this option will have
+no effect, and the bitmap will be aligned at the boundary as by default.
+.TP
+.BR \-v ", " \-\-verbose
+Prints verbose debugging information while creating the exFAT filesystem.
+.TP
+.BR \-V ", " \-\-version
+Prints the version number and exits.
+.SH SEE ALSO
+.BR mkfs (8),
+.BR mount (8),
diff --git a/manpages/tune.exfat.8 b/manpages/tune.exfat.8
new file mode 100644
index 0000000..865dc07
--- /dev/null
+++ b/manpages/tune.exfat.8
@@ -0,0 +1,46 @@
+.TH tune.exfat 8
+.SH NAME
+tune.exfat \- adjust tunable filesystem parameters on an exFAT filesystem
+.SH SYNOPSIS
+.B tune.exfat
+[
+.B \-l
+.I print-label
+] [
+.B \-L
+.I set-label
+] [
+.B \-i
+.I print-serial
+] [
+.B \-I
+.I set-serial
+] [
+.B \-v
+]
+.I device
+.br
+.B tune.exfat \-V
+.SH DESCRIPTION
+.B tune.exfat
+adjust tunable ondisk parameters of an existing exFAT filesystem.
+.PP
+.SH OPTIONS
+.TP
+.BI \-l " print-label"
+Print the volume label of the exFAT filesystem.
+.TP
+.BI \-L " set-label"
+Set the volume label of the filesystem to the provided argument.
+.TP
+.BI \-i " print-serial"
+Print the volume serial of the exFAT filesystem.
+.TP
+.BI \-I " set-serial"
+Set the volume serial of the filesystem to the provided argument.
+.TP
+.BI \-v
+Prints verbose debugging information while extracting or tuning parameters of the exFAT filesystem.
+.TP
+.B \-V
+Prints the version number and exits.
diff --git a/mkfs/Android.bp b/mkfs/Android.bp
new file mode 100644
index 0000000..6833d24
--- /dev/null
+++ b/mkfs/Android.bp
@@ -0,0 +1,21 @@
+// Copyright 2020 The Android Open Source Project
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_exfatprogs_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+cc_binary {
+    name: "mkfs.exfat",
+
+    srcs: [
+        "mkfs.c",
+        "upcase.c",
+    ],
+    defaults: ["exfatprogs-defaults"],
+    static_libs: ["libexfat"],
+}
diff --git a/mkfs/Makefile.am b/mkfs/Makefile.am
new file mode 100644
index 0000000..7b3e35b
--- /dev/null
+++ b/mkfs/Makefile.am
@@ -0,0 +1,6 @@
+AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+mkfs_exfat_LDADD = $(top_builddir)/lib/libexfat.a
+
+sbin_PROGRAMS = mkfs.exfat
+
+mkfs_exfat_SOURCES = mkfs.c upcase.c mkfs.h
diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c
new file mode 100644
index 0000000..1837669
--- /dev/null
+++ b/mkfs/mkfs.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <errno.h>
+#include <locale.h>
+#include <time.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "mkfs.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;
+	pbpb->jmp_boot[1] = 0x76;
+	pbpb->jmp_boot[2] = 0x90;
+	memcpy(pbpb->oem_name, "EXFAT   ", 8);
+	memset(pbpb->res_zero, 0, 53);
+
+	/* Fill exfat extend BIOS paramemter block */
+	pbsx->vol_offset = cpu_to_le64(bd->offset / bd->sector_size);
+	pbsx->vol_length = cpu_to_le64(bd->size / bd->sector_size);
+	pbsx->fat_offset = cpu_to_le32(finfo.fat_byte_off / bd->sector_size);
+	pbsx->fat_length = cpu_to_le32(finfo.fat_byte_len / bd->sector_size);
+	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(finfo.volume_serial);
+	pbsx->vol_flags = 0;
+	pbsx->sect_size_bits = bd->sector_size_bits;
+	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;
+	pbsx->fs_version[1] = 1;
+	memset(pbsx->reserved2, 0, 7);
+
+	memset(ppbr->boot_code, 0, 390);
+	ppbr->signature = cpu_to_le16(PBR_SIGNATURE);
+
+	exfat_debug("Volume Offset(sectors) : %" PRIu64 "\n",
+		le64_to_cpu(pbsx->vol_offset));
+	exfat_debug("Volume Length(sectors) : %" PRIu64 "\n",
+		le64_to_cpu(pbsx->vol_length));
+	exfat_debug("FAT Offset(sector offset) : %u\n",
+		le32_to_cpu(pbsx->fat_offset));
+	exfat_debug("FAT Length(sectors) : %u\n",
+		le32_to_cpu(pbsx->fat_length));
+	exfat_debug("Cluster Heap Offset (sector offset) : %u\n",
+		le32_to_cpu(pbsx->clu_offset));
+	exfat_debug("Cluster Count : %u\n",
+		le32_to_cpu(pbsx->clu_count));
+	exfat_debug("Root Cluster (cluster offset) : %u\n",
+		le32_to_cpu(pbsx->root_cluster));
+	exfat_debug("Volume Serial : 0x%x\n", le32_to_cpu(pbsx->vol_serial));
+	exfat_debug("Sector Size Bits : %u\n",
+		pbsx->sect_size_bits);
+	exfat_debug("Sector per Cluster bits : %u\n",
+		pbsx->sect_per_clus_bits);
+}
+
+static int exfat_write_boot_sector(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui, unsigned int *checksum,
+		bool is_backup)
+{
+	struct pbr *ppbr;
+	unsigned int sec_idx = BOOT_SEC_IDX;
+	int ret = 0;
+
+	if (is_backup)
+		sec_idx += BACKUP_BOOT_SEC_IDX;
+
+	ppbr = malloc(sizeof(struct pbr));
+	if (!ppbr) {
+		exfat_err("Cannot allocate pbr: out of memory\n");
+		return -1;
+	}
+	memset(ppbr, 0, sizeof(struct pbr));
+
+	exfat_setup_boot_sector(ppbr, bd, ui);
+
+	/* write main boot sector */
+	ret = exfat_write_sector(bd, ppbr, sec_idx);
+	if (ret < 0) {
+		exfat_err("main boot sector write failed\n");
+		ret = -1;
+		goto free_ppbr;
+	}
+
+	boot_calc_checksum((unsigned char *)ppbr, sizeof(struct pbr),
+		true, checksum);
+
+free_ppbr:
+	free(ppbr);
+	return ret;
+}
+
+static int exfat_write_extended_boot_sectors(struct exfat_blk_dev *bd,
+		unsigned int *checksum, bool is_backup)
+{
+	struct exbs eb;
+	int i;
+	unsigned int sec_idx = EXBOOT_SEC_IDX;
+
+	if (is_backup)
+		sec_idx += BACKUP_BOOT_SEC_IDX;
+
+	memset(&eb, 0, sizeof(struct exbs));
+	eb.signature = cpu_to_le16(PBR_SIGNATURE);
+	for (i = 0; i < EXBOOT_SEC_NUM; i++) {
+		if (exfat_write_sector(bd, &eb, sec_idx++)) {
+			exfat_err("extended boot sector write failed\n");
+			return -1;
+		}
+
+		boot_calc_checksum((unsigned char *) &eb, sizeof(struct exbs),
+			false, checksum);
+	}
+
+	return 0;
+}
+
+static int exfat_write_oem_sector(struct exfat_blk_dev *bd,
+		unsigned int *checksum, bool is_backup)
+{
+	char *oem;
+	int ret = 0;
+	unsigned int sec_idx = OEM_SEC_IDX;
+
+	oem = malloc(bd->sector_size);
+	if (!oem)
+		return -1;
+
+	if (is_backup)
+		sec_idx += BACKUP_BOOT_SEC_IDX;
+
+	memset(oem, 0xFF, bd->sector_size);
+	ret = exfat_write_sector(bd, oem, sec_idx);
+	if (ret) {
+		exfat_err("oem sector write failed\n");
+		ret = -1;
+		goto free_oem;
+	}
+
+	boot_calc_checksum((unsigned char *)oem, bd->sector_size, false,
+		checksum);
+
+	/* Zero out reserved sector */
+	memset(oem, 0, bd->sector_size);
+	ret = exfat_write_sector(bd, oem, sec_idx + 1);
+	if (ret) {
+		exfat_err("reserved sector write failed\n");
+		ret = -1;
+		goto free_oem;
+	}
+
+	boot_calc_checksum((unsigned char *)oem, bd->sector_size, false,
+		checksum);
+
+free_oem:
+	free(oem);
+	return ret;
+}
+
+static int exfat_create_volume_boot_record(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui, bool is_backup)
+{
+	unsigned int checksum = 0;
+	int ret;
+
+	ret = exfat_write_boot_sector(bd, ui, &checksum, is_backup);
+	if (ret)
+		return ret;
+	ret = exfat_write_extended_boot_sectors(bd, &checksum, is_backup);
+	if (ret)
+		return ret;
+	ret = exfat_write_oem_sector(bd, &checksum, is_backup);
+	if (ret)
+		return ret;
+
+	return exfat_write_checksum_sector(bd, checksum, is_backup);
+}
+
+static int write_fat_entry(int fd, __le32 clu,
+		unsigned long long offset)
+{
+	int nbyte;
+
+	lseek(fd, finfo.fat_byte_off + (offset * sizeof(__le32)), SEEK_SET);
+	nbyte = write(fd, (__u8 *) &clu, sizeof(__le32));
+	if (nbyte != sizeof(int)) {
+		exfat_err("write failed, offset : %llu, clu : %x\n",
+			offset, clu);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int write_fat_entries(struct exfat_user_input *ui, int fd,
+		unsigned int clu, unsigned int length)
+{
+	int ret;
+	unsigned int count;
+
+	count = clu + round_up(length, ui->cluster_size) / ui->cluster_size;
+
+	for (; clu < count - 1; clu++) {
+		ret = write_fat_entry(fd, cpu_to_le32(clu + 1), clu);
+		if (ret)
+			return ret;
+	}
+
+	ret = write_fat_entry(fd, cpu_to_le32(EXFAT_EOF_CLUSTER), clu);
+	if (ret)
+		return ret;
+
+	return clu;
+}
+
+static int exfat_create_fat_table(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	int ret, clu;
+
+	/* fat entry 0 should be media type field(0xF8) */
+	ret = write_fat_entry(bd->dev_fd, cpu_to_le32(0xfffffff8), 0);
+	if (ret) {
+		exfat_err("fat 0 entry write failed\n");
+		return ret;
+	}
+
+	/* fat entry 1 is historical precedence(0xFFFFFFFF) */
+	ret = write_fat_entry(bd->dev_fd, cpu_to_le32(0xffffffff), 1);
+	if (ret) {
+		exfat_err("fat 1 entry write failed\n");
+		return ret;
+	}
+
+	/* write bitmap entries */
+	clu = write_fat_entries(ui, bd->dev_fd, EXFAT_FIRST_CLUSTER,
+		finfo.bitmap_byte_len);
+	if (clu < 0)
+		return ret;
+
+	/* write upcase table entries */
+	clu = write_fat_entries(ui, bd->dev_fd, clu + 1, finfo.ut_byte_len);
+	if (clu < 0)
+		return ret;
+
+	/* write root directory entries */
+	clu = write_fat_entries(ui, bd->dev_fd, clu + 1, finfo.root_byte_len);
+	if (clu < 0)
+		return ret;
+
+	finfo.used_clu_cnt = clu + 1;
+	exfat_debug("Total used cluster count : %d\n", finfo.used_clu_cnt);
+
+	return ret;
+}
+
+static int exfat_create_bitmap(struct exfat_blk_dev *bd)
+{
+	char *bitmap;
+	unsigned int i, nbytes;
+
+	bitmap = calloc(finfo.bitmap_byte_len, sizeof(*bitmap));
+	if (!bitmap)
+		return -1;
+
+	for (i = 0; i < finfo.used_clu_cnt - EXFAT_FIRST_CLUSTER; i++)
+		exfat_set_bit(bd, bitmap, i);
+
+	lseek(bd->dev_fd, finfo.bitmap_byte_off, SEEK_SET);
+	nbytes = write(bd->dev_fd, bitmap, finfo.bitmap_byte_len);
+	if (nbytes != finfo.bitmap_byte_len) {
+		exfat_err("write failed, nbytes : %d, bitmap_len : %d\n",
+			nbytes, finfo.bitmap_byte_len);
+		free(bitmap);
+		return -1;
+	}
+
+	free(bitmap);
+	return 0;
+}
+
+static int exfat_create_root_dir(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	struct exfat_dentry ed[3];
+	int dentries_len = sizeof(struct exfat_dentry) * 3;
+	int nbytes;
+
+	/* Set volume label entry */
+	ed[0].type = EXFAT_VOLUME;
+	memset(ed[0].vol_label, 0, 22);
+	memcpy(ed[0].vol_label, ui->volume_label, ui->volume_label_len);
+	ed[0].vol_char_cnt = ui->volume_label_len/2;
+
+	/* Set bitmap entry */
+	ed[1].type = EXFAT_BITMAP;
+	ed[1].bitmap_flags = 0;
+	ed[1].bitmap_start_clu = cpu_to_le32(EXFAT_FIRST_CLUSTER);
+	ed[1].bitmap_size = cpu_to_le64(finfo.bitmap_byte_len);
+
+	/* Set upcase table entry */
+	ed[2].type = EXFAT_UPCASE;
+	ed[2].upcase_checksum = cpu_to_le32(0xe619d30d);
+	ed[2].upcase_start_clu = cpu_to_le32(finfo.ut_start_clu);
+	ed[2].upcase_size = cpu_to_le64(EXFAT_UPCASE_TABLE_SIZE);
+
+	lseek(bd->dev_fd, finfo.root_byte_off, SEEK_SET);
+	nbytes = write(bd->dev_fd, ed, dentries_len);
+	if (nbytes != dentries_len) {
+		exfat_err("write failed, nbytes : %d, dentries_len : %d\n",
+			nbytes, dentries_len);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void usage(void)
+{
+	fputs("Usage: mkfs.exfat\n"
+		"\t-L | --volume-label=label                              Set volume label\n"
+		"\t-c | --cluster-size=size(or suffixed by 'K' or 'M')    Specify cluster size\n"
+		"\t-b | --boundary-align=size(or suffixed by 'K' or 'M')  Specify boundary alignment\n"
+		"\t     --pack-bitmap                                     Move bitmap into FAT segment\n"
+		"\t-f | --full-format                                     Full format\n"
+		"\t-V | --version                                         Show version\n"
+		"\t-v | --verbose                                         Print debug\n"
+		"\t-h | --help                                            Show help\n",
+		stderr);
+
+	exit(EXIT_FAILURE);
+}
+
+#define PACK_BITMAP (CHAR_MAX + 1)
+
+static const struct option opts[] = {
+	{"volume-label",	required_argument,	NULL,	'L' },
+	{"cluster-size",	required_argument,	NULL,	'c' },
+	{"boundary-align",	required_argument,	NULL,	'b' },
+	{"pack-bitmap",		no_argument,		NULL,	PACK_BITMAP },
+	{"full-format",		no_argument,		NULL,	'f' },
+	{"version",		no_argument,		NULL,	'V' },
+	{"verbose",		no_argument,		NULL,	'v' },
+	{"help",		no_argument,		NULL,	'h' },
+	{"?",			no_argument,		NULL,	'?' },
+	{NULL,			0,			NULL,	 0  }
+};
+
+/*
+ * Moves the bitmap to just before the alignment boundary if there is space
+ * between the boundary and the end of the FAT. This may allow the FAT and the
+ * bitmap to share the same allocation unit on flash media, thereby improving
+ * performance and endurance.
+ */
+static int exfat_pack_bitmap(const struct exfat_user_input *ui)
+{
+	unsigned int fat_byte_end = finfo.fat_byte_off + finfo.fat_byte_len,
+		bitmap_byte_len = finfo.bitmap_byte_len,
+		bitmap_clu_len = round_up(bitmap_byte_len, ui->cluster_size),
+		bitmap_clu_cnt, total_clu_cnt, new_bitmap_clu_len;
+
+	for (;;) {
+		bitmap_clu_cnt = bitmap_clu_len / ui->cluster_size;
+		if (finfo.clu_byte_off - bitmap_clu_len < fat_byte_end ||
+				finfo.total_clu_cnt > EXFAT_MAX_NUM_CLUSTER -
+					bitmap_clu_cnt)
+			return -1;
+		total_clu_cnt = finfo.total_clu_cnt + bitmap_clu_cnt;
+		bitmap_byte_len = round_up(total_clu_cnt, 8) / 8;
+		new_bitmap_clu_len = round_up(bitmap_byte_len, ui->cluster_size);
+		if (new_bitmap_clu_len == bitmap_clu_len) {
+			finfo.clu_byte_off -= bitmap_clu_len;
+			finfo.total_clu_cnt = total_clu_cnt;
+			finfo.bitmap_byte_off -= bitmap_clu_len;
+			finfo.bitmap_byte_len = bitmap_byte_len;
+			return 0;
+		}
+		bitmap_clu_len = new_bitmap_clu_len;
+	}
+}
+
+static int exfat_build_mkfs_info(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	unsigned long long total_clu_cnt;
+	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(bd->offset + 24 * bd->sector_size,
+			ui->boundary_align) - bd->offset;
+	finfo.fat_byte_len = round_up((bd->num_clusters * sizeof(int)),
+		ui->cluster_size);
+	finfo.clu_byte_off = round_up(bd->offset + finfo.fat_byte_off +
+		finfo.fat_byte_len, ui->boundary_align) - bd->offset;
+	if (bd->size <= finfo.clu_byte_off) {
+		exfat_err("boundary alignment is too big\n");
+		return -1;
+	}
+	total_clu_cnt = (bd->size - finfo.clu_byte_off) / ui->cluster_size;
+	if (total_clu_cnt > EXFAT_MAX_NUM_CLUSTER) {
+		exfat_err("cluster size is too small\n");
+		return -1;
+	}
+	finfo.total_clu_cnt = (unsigned int) total_clu_cnt;
+
+	finfo.bitmap_byte_off = finfo.clu_byte_off;
+	finfo.bitmap_byte_len = round_up(finfo.total_clu_cnt, 8) / 8;
+	if (ui->pack_bitmap)
+		exfat_pack_bitmap(ui);
+	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;
+	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;
+}
+
+static int exfat_zero_out_disk(struct exfat_blk_dev *bd,
+		struct exfat_user_input *ui)
+{
+	int nbytes;
+	unsigned long long total_written = 0;
+	char *buf;
+	unsigned int chunk_size = ui->cluster_size;
+	unsigned long long size;
+
+	if (ui->quick)
+		size = finfo.root_byte_off + chunk_size;
+	else
+		size = bd->size;
+
+	buf = malloc(chunk_size);
+	if (!buf)
+		return -1;
+
+	memset(buf, 0, chunk_size);
+	lseek(bd->dev_fd, 0, SEEK_SET);
+	do {
+
+		nbytes = write(bd->dev_fd, buf, chunk_size);
+		if (nbytes <= 0) {
+			if (nbytes < 0)
+				exfat_err("write failed(errno : %d)\n", errno);
+			break;
+		}
+		total_written += nbytes;
+	} while (total_written < size);
+
+	free(buf);
+	exfat_debug("zero out written size : %llu, disk size : %llu\n",
+		total_written, bd->size);
+	return 0;
+}
+
+static int make_exfat(struct exfat_blk_dev *bd, struct exfat_user_input *ui)
+{
+	int ret;
+
+	exfat_info("Creating exFAT filesystem(%s, cluster size=%u)\n\n",
+		ui->dev_name, ui->cluster_size);
+
+	exfat_info("Writing volume boot record: ");
+	ret = exfat_create_volume_boot_record(bd, ui, 0);
+	exfat_info("%s\n", ret ? "failed" : "done");
+	if (ret)
+		return ret;
+
+	exfat_info("Writing backup volume boot record: ");
+	/* backup sector */
+	ret = exfat_create_volume_boot_record(bd, ui, 1);
+	exfat_info("%s\n", ret ? "failed" : "done");
+	if (ret)
+		return ret;
+
+	exfat_info("Fat table creation: ");
+	ret = exfat_create_fat_table(bd, ui);
+	exfat_info("%s\n", ret ? "failed" : "done");
+	if (ret)
+		return ret;
+
+	exfat_info("Allocation bitmap creation: ");
+	ret = exfat_create_bitmap(bd);
+	exfat_info("%s\n", ret ? "failed" : "done");
+	if (ret)
+		return ret;
+
+	exfat_info("Upcase table creation: ");
+	ret = exfat_create_upcase_table(bd);
+	exfat_info("%s\n", ret ? "failed" : "done");
+	if (ret)
+		return ret;
+
+	exfat_info("Writing root directory entry: ");
+	ret = exfat_create_root_dir(bd, ui);
+	exfat_info("%s\n", ret ? "failed" : "done");
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static long long parse_size(const char *size)
+{
+	char *data_unit;
+	unsigned long long byte_size = strtoull(size, &data_unit, 0);
+
+	switch (*data_unit) {
+	case 'M':
+	case 'm':
+		byte_size <<= 20;
+		break;
+	case 'K':
+	case 'k':
+		byte_size <<= 10;
+		break;
+	case '\0':
+		break;
+	default:
+		exfat_err("Wrong unit input('%c') for size\n",
+				*data_unit);
+		return -EINVAL;
+	}
+
+	return byte_size;
+}
+
+int main(int argc, char *argv[])
+{
+	int c;
+	int ret = EXIT_FAILURE;
+	struct exfat_blk_dev bd;
+	struct exfat_user_input ui;
+	bool version_only = false;
+
+	init_user_input(&ui);
+
+	if (!setlocale(LC_CTYPE, ""))
+		exfat_err("failed to init locale/codeset\n");
+
+	opterr = 0;
+	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
+		 * compatibility with old utils.
+		 */
+		case 'n':
+		case 'L':
+		{
+			ret = exfat_utf16_enc(optarg,
+				ui.volume_label, sizeof(ui.volume_label));
+			if (ret < 0)
+				goto out;
+
+			ui.volume_label_len = ret;
+			break;
+		}
+		case 'c':
+			ret = parse_size(optarg);
+			if (ret < 0)
+				goto out;
+			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 PACK_BITMAP:
+			ui.pack_bitmap = true;
+			break;
+		case 'f':
+			ui.quick = false;
+			break;
+		case 'V':
+			version_only = true;
+			break;
+		case 'v':
+			print_level = EXFAT_DEBUG;
+			break;
+		case '?':
+		case 'h':
+		default:
+			usage();
+	}
+
+	show_version();
+	if (version_only)
+		exit(EXIT_FAILURE);
+
+	if (argc - optind != 1) {
+		usage();
+	}
+
+	memset(ui.dev_name, 0, sizeof(ui.dev_name));
+	snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[optind]);
+
+	ret = exfat_get_blk_dev_info(&ui, &bd);
+	if (ret < 0)
+		goto out;
+
+	ret = exfat_build_mkfs_info(&bd, &ui);
+	if (ret)
+		goto close;
+
+	ret = exfat_zero_out_disk(&bd, &ui);
+	if (ret)
+		goto close;
+
+	ret = make_exfat(&bd, &ui);
+	if (ret)
+		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");
+	return ret;
+}
diff --git a/mkfs/mkfs.h b/mkfs/mkfs.h
new file mode 100644
index 0000000..ffd56e3
--- /dev/null
+++ b/mkfs/mkfs.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *  Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#ifndef _MKFS_H
+
+#define MIN_NUM_SECTOR			(2048)
+#define EXFAT_MAX_CLUSTER_SIZE		(32*1024*1024)
+
+struct exfat_mkfs_info {
+	unsigned int total_clu_cnt;
+	unsigned int used_clu_cnt;
+	unsigned int fat_byte_off;
+	unsigned int fat_byte_len;
+	unsigned int clu_byte_off;
+	unsigned int bitmap_byte_off;
+	unsigned int bitmap_byte_len;
+	unsigned int ut_byte_off;
+	unsigned int ut_start_clu;
+	unsigned int ut_clus_off;
+	unsigned int ut_byte_len;
+	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;
+
+int exfat_create_upcase_table(struct exfat_blk_dev *bd);
+
+#endif /* !_MKFS_H */
diff --git a/mkfs/upcase.c b/mkfs/upcase.c
new file mode 100644
index 0000000..8d5ef1a
--- /dev/null
+++ b/mkfs/upcase.c
@@ -0,0 +1,515 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "mkfs.h"
+
+static const unsigned char upcase_table[EXFAT_UPCASE_TABLE_SIZE] = {
+	0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00,
+	0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x0B, 0x00,
+	0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x11, 0x00,
+	0x12, 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x00,
+	0x18, 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, 0x00, 0x1C, 0x00, 0x1D, 0x00,
+	0x1E, 0x00, 0x1F, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, 0x23, 0x00,
+	0x24, 0x00, 0x25, 0x00, 0x26, 0x00, 0x27, 0x00, 0x28, 0x00, 0x29, 0x00,
+	0x2A, 0x00, 0x2B, 0x00, 0x2C, 0x00, 0x2D, 0x00, 0x2E, 0x00, 0x2F, 0x00,
+	0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00,
+	0x36, 0x00, 0x37, 0x00,	0x38, 0x00, 0x39, 0x00, 0x3A, 0x00, 0x3B, 0x00,
+	0x3C, 0x00, 0x3D, 0x00, 0x3E, 0x00, 0x3F, 0x00, 0x40, 0x00, 0x41, 0x00,
+	0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00,
+	0x48, 0x00, 0x49, 0x00, 0x4A, 0x00, 0x4B, 0x00, 0x4C, 0x00, 0x4D, 0x00,
+	0x4E, 0x00, 0x4F, 0x00, 0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00,
+	0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x00, 0x58, 0x00, 0x59, 0x00,
+	0x5A, 0x00, 0x5B, 0x00, 0x5C, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x5F, 0x00,
+	0x60, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45, 0x00,
+	0x46, 0x00, 0x47, 0x00, 0x48, 0x00, 0x49, 0x00, 0x4A, 0x00, 0x4B, 0x00,
+	0x4C, 0x00, 0x4D, 0x00, 0x4E, 0x00, 0x4F, 0x00, 0x50, 0x00, 0x51, 0x00,
+	0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x00,
+	0x58, 0x00, 0x59, 0x00, 0x5A, 0x00, 0x7B, 0x00, 0x7C, 0x00, 0x7D, 0x00,
+	0x7E, 0x00, 0x7F, 0x00, 0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00,
+	0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87, 0x00, 0x88, 0x00, 0x89, 0x00,
+	0x8A, 0x00, 0x8B, 0x00, 0x8C, 0x00, 0x8D, 0x00, 0x8E, 0x00, 0x8F, 0x00,
+	0x90, 0x00, 0x91, 0x00, 0x92, 0x00, 0x93, 0x00, 0x94, 0x00, 0x95, 0x00,
+	0x96, 0x00, 0x97, 0x00, 0x98, 0x00, 0x99, 0x00, 0x9A, 0x00, 0x9B, 0x00,
+	0x9C, 0x00, 0x9D, 0x00, 0x9E, 0x00, 0x9F, 0x00, 0xA0, 0x00, 0xA1, 0x00,
+	0xA2, 0x00, 0xA3, 0x00, 0xA4, 0x00, 0xA5, 0x00, 0xA6, 0x00, 0xA7, 0x00,
+	0xA8, 0x00, 0xA9, 0x00, 0xAA, 0x00, 0xAB, 0x00, 0xAC, 0x00, 0xAD, 0x00,
+	0xAE, 0x00, 0xAF, 0x00, 0xB0, 0x00, 0xB1, 0x00, 0xB2, 0x00, 0xB3, 0x00,
+	0xB4, 0x00, 0xB5, 0x00, 0xB6, 0x00, 0xB7, 0x00, 0xB8, 0x00, 0xB9, 0x00,
+	0xBA, 0x00, 0xBB, 0x00, 0xBC, 0x00, 0xBD, 0x00, 0xBE, 0x00, 0xBF, 0x00,
+	0xC0, 0x00, 0xC1, 0x00, 0xC2, 0x00, 0xC3, 0x00, 0xC4, 0x00, 0xC5, 0x00,
+	0xC6, 0x00, 0xC7, 0x00, 0xC8, 0x00, 0xC9, 0x00, 0xCA, 0x00, 0xCB, 0x00,
+	0xCC, 0x00, 0xCD, 0x00, 0xCE, 0x00, 0xCF, 0x00, 0xD0, 0x00, 0xD1, 0x00,
+	0xD2, 0x00, 0xD3, 0x00, 0xD4, 0x00, 0xD5, 0x00, 0xD6, 0x00, 0xD7, 0x00,
+	0xD8, 0x00, 0xD9, 0x00, 0xDA, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0xDD, 0x00,
+	0xDE, 0x00, 0xDF, 0x00, 0xC0, 0x00, 0xC1, 0x00, 0xC2, 0x00, 0xC3, 0x00,
+	0xC4, 0x00, 0xC5, 0x00, 0xC6, 0x00, 0xC7, 0x00, 0xC8, 0x00, 0xC9, 0x00,
+	0xCA, 0x00, 0xCB, 0x00, 0xCC, 0x00, 0xCD, 0x00, 0xCE, 0x00, 0xCF, 0x00,
+	0xD0, 0x00, 0xD1, 0x00, 0xD2, 0x00, 0xD3, 0x00, 0xD4, 0x00, 0xD5, 0x00,
+	0xD6, 0x00, 0xF7, 0x00, 0xD8, 0x00, 0xD9, 0x00, 0xDA, 0x00, 0xDB, 0x00,
+	0xDC, 0x00, 0xDD, 0x00, 0xDE, 0x00, 0x78, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x02, 0x01, 0x02, 0x01, 0x04, 0x01, 0x04, 0x01, 0x06, 0x01, 0x06, 0x01,
+	0x08, 0x01, 0x08, 0x01, 0x0A, 0x01, 0x0A, 0x01, 0x0C, 0x01, 0x0C, 0x01,
+	0x0E, 0x01, 0x0E, 0x01, 0x10, 0x01, 0x10, 0x01, 0x12, 0x01, 0x12, 0x01,
+	0x14, 0x01, 0x14, 0x01, 0x16, 0x01, 0x16, 0x01, 0x18, 0x01, 0x18, 0x01,
+	0x1A, 0x01, 0x1A, 0x01, 0x1C, 0x01, 0x1C, 0x01, 0x1E, 0x01, 0x1E, 0x01,
+	0x20, 0x01, 0x20, 0x01, 0x22, 0x01, 0x22, 0x01, 0x24, 0x01, 0x24, 0x01,
+	0x26, 0x01, 0x26, 0x01, 0x28, 0x01, 0x28, 0x01, 0x2A, 0x01, 0x2A, 0x01,
+	0x2C, 0x01, 0x2C, 0x01, 0x2E, 0x01, 0x2E, 0x01, 0x30, 0x01, 0x31, 0x01,
+	0x32, 0x01, 0x32, 0x01, 0x34, 0x01, 0x34, 0x01, 0x36, 0x01, 0x36, 0x01,
+	0x38, 0x01, 0x39, 0x01, 0x39, 0x01, 0x3B, 0x01, 0x3B, 0x01, 0x3D, 0x01,
+	0x3D, 0x01, 0x3F, 0x01, 0x3F, 0x01, 0x41, 0x01, 0x41, 0x01, 0x43, 0x01,
+	0x43, 0x01, 0x45, 0x01, 0x45, 0x01, 0x47, 0x01, 0x47, 0x01, 0x49, 0x01,
+	0x4A, 0x01, 0x4A, 0x01, 0x4C, 0x01, 0x4C, 0x01, 0x4E, 0x01, 0x4E, 0x01,
+	0x50, 0x01, 0x50, 0x01, 0x52, 0x01, 0x52, 0x01, 0x54, 0x01, 0x54, 0x01,
+	0x56, 0x01, 0x56, 0x01, 0x58, 0x01, 0x58, 0x01, 0x5A, 0x01, 0x5A, 0x01,
+	0x5C, 0x01, 0x5C, 0x01, 0x5E, 0x01, 0x5E, 0x01, 0x60, 0x01, 0x60, 0x01,
+	0x62, 0x01, 0x62, 0x01, 0x64, 0x01, 0x64, 0x01, 0x66, 0x01, 0x66, 0x01,
+	0x68, 0x01, 0x68, 0x01, 0x6A, 0x01, 0x6A, 0x01, 0x6C, 0x01, 0x6C, 0x01,
+	0x6E, 0x01, 0x6E, 0x01, 0x70, 0x01, 0x70, 0x01, 0x72, 0x01, 0x72, 0x01,
+	0x74, 0x01, 0x74, 0x01, 0x76, 0x01, 0x76, 0x01, 0x78, 0x01, 0x79, 0x01,
+	0x79, 0x01, 0x7B, 0x01, 0x7B, 0x01, 0x7D, 0x01, 0x7D, 0x01, 0x7F, 0x01,
+	0x43, 0x02, 0x81, 0x01, 0x82, 0x01, 0x82, 0x01, 0x84, 0x01, 0x84, 0x01,
+	0x86, 0x01, 0x87, 0x01, 0x87, 0x01, 0x89, 0x01, 0x8A, 0x01, 0x8B, 0x01,
+	0x8B, 0x01, 0x8D, 0x01, 0x8E, 0x01, 0x8F, 0x01, 0x90, 0x01, 0x91, 0x01,
+	0x91, 0x01, 0x93, 0x01, 0x94, 0x01, 0xF6, 0x01, 0x96, 0x01, 0x97, 0x01,
+	0x98, 0x01, 0x98, 0x01, 0x3D, 0x02, 0x9B, 0x01, 0x9C, 0x01, 0x9D, 0x01,
+	0x20, 0x02, 0x9F, 0x01, 0xA0, 0x01, 0xA0, 0x01, 0xA2, 0x01, 0xA2, 0x01,
+	0xA4, 0x01, 0xA4, 0x01, 0xA6, 0x01, 0xA7, 0x01, 0xA7, 0x01, 0xA9, 0x01,
+	0xAA, 0x01, 0xAB, 0x01, 0xAC, 0x01, 0xAC, 0x01, 0xAE, 0x01, 0xAF, 0x01,
+	0xAF, 0x01, 0xB1, 0x01, 0xB2, 0x01, 0xB3, 0x01, 0xB3, 0x01, 0xB5, 0x01,
+	0xB5, 0x01, 0xB7, 0x01, 0xB8, 0x01, 0xB8, 0x01, 0xBA, 0x01, 0xBB, 0x01,
+	0xBC, 0x01, 0xBC, 0x01, 0xBE, 0x01, 0xF7, 0x01, 0xC0, 0x01, 0xC1, 0x01,
+	0xC2, 0x01, 0xC3, 0x01, 0xC4, 0x01, 0xC5, 0x01, 0xC4, 0x01, 0xC7, 0x01,
+	0xC8, 0x01, 0xC7, 0x01, 0xCA, 0x01, 0xCB, 0x01, 0xCA, 0x01, 0xCD, 0x01,
+	0xCD, 0x01, 0xCF, 0x01, 0xCF, 0x01, 0xD1, 0x01, 0xD1, 0x01, 0xD3, 0x01,
+	0xD3, 0x01, 0xD5, 0x01, 0xD5, 0x01, 0xD7, 0x01, 0xD7, 0x01, 0xD9, 0x01,
+	0xD9, 0x01, 0xDB, 0x01, 0xDB, 0x01, 0x8E, 0x01, 0xDE, 0x01, 0xDE, 0x01,
+	0xE0, 0x01, 0xE0, 0x01, 0xE2, 0x01, 0xE2, 0x01, 0xE4, 0x01, 0xE4, 0x01,
+	0xE6, 0x01, 0xE6, 0x01, 0xE8, 0x01, 0xE8, 0x01, 0xEA, 0x01, 0xEA, 0x01,
+	0xEC, 0x01, 0xEC, 0x01, 0xEE, 0x01, 0xEE, 0x01, 0xF0, 0x01, 0xF1, 0x01,
+	0xF2, 0x01, 0xF1, 0x01, 0xF4, 0x01, 0xF4, 0x01, 0xF6, 0x01, 0xF7, 0x01,
+	0xF8, 0x01, 0xF8, 0x01, 0xFA, 0x01, 0xFA, 0x01, 0xFC, 0x01, 0xFC, 0x01,
+	0xFE, 0x01, 0xFE, 0x01, 0x00, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+	0x04, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x02, 0x08, 0x02, 0x08, 0x02,
+	0x0A, 0x02, 0x0A, 0x02, 0x0C, 0x02, 0x0C, 0x02, 0x0E, 0x02, 0x0E, 0x02,
+	0x10, 0x02, 0x10, 0x02, 0x12, 0x02, 0x12, 0x02, 0x14, 0x02, 0x14, 0x02,
+	0x16, 0x02, 0x16, 0x02, 0x18, 0x02, 0x18, 0x02, 0x1A, 0x02, 0x1A, 0x02,
+	0x1C, 0x02, 0x1C, 0x02, 0x1E, 0x02, 0x1E, 0x02, 0x20, 0x02, 0x21, 0x02,
+	0x22, 0x02, 0x22, 0x02, 0x24, 0x02, 0x24, 0x02, 0x26, 0x02, 0x26, 0x02,
+	0x28, 0x02, 0x28, 0x02, 0x2A, 0x02, 0x2A, 0x02, 0x2C, 0x02, 0x2C, 0x02,
+	0x2E, 0x02, 0x2E, 0x02, 0x30, 0x02, 0x30, 0x02, 0x32, 0x02, 0x32, 0x02,
+	0x34, 0x02, 0x35, 0x02, 0x36, 0x02, 0x37, 0x02, 0x38, 0x02, 0x39, 0x02,
+	0x65, 0x2C, 0x3B, 0x02, 0x3B, 0x02, 0x3D, 0x02, 0x66, 0x2C, 0x3F, 0x02,
+	0x40, 0x02, 0x41, 0x02, 0x41, 0x02, 0x43, 0x02, 0x44, 0x02, 0x45, 0x02,
+	0x46, 0x02, 0x46, 0x02, 0x48, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x4A, 0x02,
+	0x4C, 0x02, 0x4C, 0x02, 0x4E, 0x02, 0x4E, 0x02, 0x50, 0x02, 0x51, 0x02,
+	0x52, 0x02, 0x81, 0x01, 0x86, 0x01, 0x55, 0x02, 0x89, 0x01, 0x8A, 0x01,
+	0x58, 0x02, 0x8F, 0x01, 0x5A, 0x02, 0x90, 0x01, 0x5C, 0x02, 0x5D, 0x02,
+	0x5E, 0x02, 0x5F, 0x02, 0x93, 0x01, 0x61, 0x02, 0x62, 0x02, 0x94, 0x01,
+	0x64, 0x02, 0x65, 0x02, 0x66, 0x02, 0x67, 0x02, 0x97, 0x01, 0x96, 0x01,
+	0x6A, 0x02, 0x62, 0x2C, 0x6C, 0x02, 0x6D, 0x02, 0x6E, 0x02, 0x9C, 0x01,
+	0x70, 0x02, 0x71, 0x02, 0x9D, 0x01, 0x73, 0x02, 0x74, 0x02, 0x9F, 0x01,
+	0x76, 0x02, 0x77, 0x02, 0x78, 0x02, 0x79, 0x02, 0x7A, 0x02, 0x7B, 0x02,
+	0x7C, 0x02, 0x64, 0x2C, 0x7E, 0x02, 0x7F, 0x02, 0xA6, 0x01, 0x81, 0x02,
+	0x82, 0x02, 0xA9, 0x01, 0x84, 0x02, 0x85, 0x02, 0x86, 0x02, 0x87, 0x02,
+	0xAE, 0x01, 0x44, 0x02, 0xB1, 0x01, 0xB2, 0x01, 0x45, 0x02, 0x8D, 0x02,
+	0x8E, 0x02, 0x8F, 0x02, 0x90, 0x02, 0x91, 0x02, 0xB7, 0x01, 0x93, 0x02,
+	0x94, 0x02, 0x95, 0x02, 0x96, 0x02, 0x97, 0x02, 0x98, 0x02, 0x99, 0x02,
+	0x9A, 0x02, 0x9B, 0x02, 0x9C, 0x02, 0x9D, 0x02, 0x9E, 0x02, 0x9F, 0x02,
+	0xA0, 0x02, 0xA1, 0x02, 0xA2, 0x02, 0xA3, 0x02, 0xA4, 0x02, 0xA5, 0x02,
+	0xA6, 0x02, 0xA7, 0x02, 0xA8, 0x02, 0xA9, 0x02, 0xAA, 0x02, 0xAB, 0x02,
+	0xAC, 0x02, 0xAD, 0x02, 0xAE, 0x02, 0xAF, 0x02, 0xB0, 0x02, 0xB1, 0x02,
+	0xB2, 0x02, 0xB3, 0x02, 0xB4, 0x02, 0xB5, 0x02, 0xB6, 0x02, 0xB7, 0x02,
+	0xB8, 0x02, 0xB9, 0x02, 0xBA, 0x02, 0xBB, 0x02, 0xBC, 0x02, 0xBD, 0x02,
+	0xBE, 0x02, 0xBF, 0x02, 0xC0, 0x02, 0xC1, 0x02, 0xC2, 0x02, 0xC3, 0x02,
+	0xC4, 0x02, 0xC5, 0x02, 0xC6, 0x02, 0xC7, 0x02, 0xC8, 0x02, 0xC9, 0x02,
+	0xCA, 0x02, 0xCB, 0x02, 0xCC, 0x02, 0xCD, 0x02, 0xCE, 0x02, 0xCF, 0x02,
+	0xD0, 0x02, 0xD1, 0x02, 0xD2, 0x02, 0xD3, 0x02, 0xD4, 0x02, 0xD5, 0x02,
+	0xD6, 0x02, 0xD7, 0x02, 0xD8, 0x02, 0xD9, 0x02, 0xDA, 0x02, 0xDB, 0x02,
+	0xDC, 0x02, 0xDD, 0x02, 0xDE, 0x02, 0xDF, 0x02, 0xE0, 0x02, 0xE1, 0x02,
+	0xE2, 0x02, 0xE3, 0x02, 0xE4, 0x02, 0xE5, 0x02, 0xE6, 0x02, 0xE7, 0x02,
+	0xE8, 0x02, 0xE9, 0x02, 0xEA, 0x02, 0xEB, 0x02, 0xEC, 0x02, 0xED, 0x02,
+	0xEE, 0x02, 0xEF, 0x02, 0xF0, 0x02, 0xF1, 0x02, 0xF2, 0x02, 0xF3, 0x02,
+	0xF4, 0x02, 0xF5, 0x02, 0xF6, 0x02, 0xF7, 0x02, 0xF8, 0x02, 0xF9, 0x02,
+	0xFA, 0x02, 0xFB, 0x02, 0xFC, 0x02, 0xFD, 0x02, 0xFE, 0x02, 0xFF, 0x02,
+	0x00, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x03, 0x04, 0x03, 0x05, 0x03,
+	0x06, 0x03, 0x07, 0x03, 0x08, 0x03, 0x09, 0x03, 0x0A, 0x03, 0x0B, 0x03,
+	0x0C, 0x03, 0x0D, 0x03, 0x0E, 0x03, 0x0F, 0x03, 0x10, 0x03, 0x11, 0x03,
+	0x12, 0x03, 0x13, 0x03, 0x14, 0x03, 0x15, 0x03, 0x16, 0x03, 0x17, 0x03,
+	0x18, 0x03, 0x19, 0x03, 0x1A, 0x03, 0x1B, 0x03, 0x1C, 0x03, 0x1D, 0x03,
+	0x1E, 0x03, 0x1F, 0x03, 0x20, 0x03, 0x21, 0x03, 0x22, 0x03, 0x23, 0x03,
+	0x24, 0x03, 0x25, 0x03, 0x26, 0x03, 0x27, 0x03, 0x28, 0x03, 0x29, 0x03,
+	0x2A, 0x03, 0x2B, 0x03, 0x2C, 0x03, 0x2D, 0x03, 0x2E, 0x03, 0x2F, 0x03,
+	0x30, 0x03, 0x31, 0x03, 0x32, 0x03, 0x33, 0x03, 0x34, 0x03, 0x35, 0x03,
+	0x36, 0x03, 0x37, 0x03, 0x38, 0x03, 0x39, 0x03, 0x3A, 0x03, 0x3B, 0x03,
+	0x3C, 0x03, 0x3D, 0x03, 0x3E, 0x03, 0x3F, 0x03, 0x40, 0x03, 0x41, 0x03,
+	0x42, 0x03, 0x43, 0x03, 0x44, 0x03, 0x45, 0x03, 0x46, 0x03, 0x47, 0x03,
+	0x48, 0x03, 0x49, 0x03, 0x4A, 0x03, 0x4B, 0x03, 0x4C, 0x03, 0x4D, 0x03,
+	0x4E, 0x03, 0x4F, 0x03, 0x50, 0x03, 0x51, 0x03, 0x52, 0x03, 0x53, 0x03,
+	0x54, 0x03, 0x55, 0x03, 0x56, 0x03, 0x57, 0x03, 0x58, 0x03, 0x59, 0x03,
+	0x5A, 0x03, 0x5B, 0x03, 0x5C, 0x03, 0x5D, 0x03, 0x5E, 0x03, 0x5F, 0x03,
+	0x60, 0x03, 0x61, 0x03, 0x62, 0x03, 0x63, 0x03, 0x64, 0x03, 0x65, 0x03,
+	0x66, 0x03, 0x67, 0x03, 0x68, 0x03, 0x69, 0x03, 0x6A, 0x03, 0x6B, 0x03,
+	0x6C, 0x03, 0x6D, 0x03, 0x6E, 0x03, 0x6F, 0x03, 0x70, 0x03, 0x71, 0x03,
+	0x72, 0x03, 0x73, 0x03, 0x74, 0x03, 0x75, 0x03, 0x76, 0x03, 0x77, 0x03,
+	0x78, 0x03, 0x79, 0x03, 0x7A, 0x03, 0xFD, 0x03, 0xFE, 0x03, 0xFF, 0x03,
+	0x7E, 0x03, 0x7F, 0x03, 0x80, 0x03, 0x81, 0x03, 0x82, 0x03, 0x83, 0x03,
+	0x84, 0x03, 0x85, 0x03, 0x86, 0x03, 0x87, 0x03, 0x88, 0x03, 0x89, 0x03,
+	0x8A, 0x03, 0x8B, 0x03, 0x8C, 0x03, 0x8D, 0x03, 0x8E, 0x03, 0x8F, 0x03,
+	0x90, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03, 0x94, 0x03, 0x95, 0x03,
+	0x96, 0x03, 0x97, 0x03, 0x98, 0x03, 0x99, 0x03, 0x9A, 0x03, 0x9B, 0x03,
+	0x9C, 0x03, 0x9D, 0x03, 0x9E, 0x03, 0x9F, 0x03, 0xA0, 0x03, 0xA1, 0x03,
+	0xA2, 0x03, 0xA3, 0x03, 0xA4, 0x03, 0xA5, 0x03, 0xA6, 0x03, 0xA7, 0x03,
+	0xA8, 0x03, 0xA9, 0x03, 0xAA, 0x03, 0xAB, 0x03, 0x86, 0x03, 0x88, 0x03,
+	0x89, 0x03, 0x8A, 0x03, 0xB0, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03,
+	0x94, 0x03, 0x95, 0x03, 0x96, 0x03, 0x97, 0x03, 0x98, 0x03, 0x99, 0x03,
+	0x9A, 0x03, 0x9B, 0x03, 0x9C, 0x03, 0x9D, 0x03, 0x9E, 0x03, 0x9F, 0x03,
+	0xA0, 0x03, 0xA1, 0x03, 0xA3, 0x03, 0xA3, 0x03, 0xA4, 0x03, 0xA5, 0x03,
+	0xA6, 0x03, 0xA7, 0x03, 0xA8, 0x03, 0xA9, 0x03, 0xAA, 0x03, 0xAB, 0x03,
+	0x8C, 0x03, 0x8E, 0x03, 0x8F, 0x03, 0xCF, 0x03, 0xD0, 0x03, 0xD1, 0x03,
+	0xD2, 0x03, 0xD3, 0x03, 0xD4, 0x03, 0xD5, 0x03, 0xD6, 0x03, 0xD7, 0x03,
+	0xD8, 0x03, 0xD8, 0x03, 0xDA, 0x03, 0xDA, 0x03, 0xDC, 0x03, 0xDC, 0x03,
+	0xDE, 0x03, 0xDE, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE2, 0x03, 0xE2, 0x03,
+	0xE4, 0x03, 0xE4, 0x03, 0xE6, 0x03, 0xE6, 0x03, 0xE8, 0x03, 0xE8, 0x03,
+	0xEA, 0x03, 0xEA, 0x03, 0xEC, 0x03, 0xEC, 0x03, 0xEE, 0x03, 0xEE, 0x03,
+	0xF0, 0x03, 0xF1, 0x03, 0xF9, 0x03, 0xF3, 0x03, 0xF4, 0x03, 0xF5, 0x03,
+	0xF6, 0x03, 0xF7, 0x03, 0xF7, 0x03, 0xF9, 0x03, 0xFA, 0x03, 0xFA, 0x03,
+	0xFC, 0x03, 0xFD, 0x03, 0xFE, 0x03, 0xFF, 0x03, 0x00, 0x04, 0x01, 0x04,
+	0x02, 0x04, 0x03, 0x04, 0x04, 0x04, 0x05, 0x04, 0x06, 0x04, 0x07, 0x04,
+	0x08, 0x04, 0x09, 0x04, 0x0A, 0x04, 0x0B, 0x04, 0x0C, 0x04, 0x0D, 0x04,
+	0x0E, 0x04, 0x0F, 0x04, 0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04,
+	0x14, 0x04, 0x15, 0x04, 0x16, 0x04, 0x17, 0x04, 0x18, 0x04, 0x19, 0x04,
+	0x1A, 0x04, 0x1B, 0x04, 0x1C, 0x04, 0x1D, 0x04, 0x1E, 0x04, 0x1F, 0x04,
+	0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04, 0x24, 0x04, 0x25, 0x04,
+	0x26, 0x04, 0x27, 0x04, 0x28, 0x04, 0x29, 0x04, 0x2A, 0x04, 0x2B, 0x04,
+	0x2C, 0x04, 0x2D, 0x04, 0x2E, 0x04, 0x2F, 0x04, 0x10, 0x04, 0x11, 0x04,
+	0x12, 0x04, 0x13, 0x04, 0x14, 0x04, 0x15, 0x04, 0x16, 0x04, 0x17, 0x04,
+	0x18, 0x04, 0x19, 0x04, 0x1A, 0x04, 0x1B, 0x04, 0x1C, 0x04, 0x1D, 0x04,
+	0x1E, 0x04, 0x1F, 0x04, 0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04,
+	0x24, 0x04, 0x25, 0x04, 0x26, 0x04, 0x27, 0x04, 0x28, 0x04, 0x29, 0x04,
+	0x2A, 0x04, 0x2B, 0x04, 0x2C, 0x04, 0x2D, 0x04, 0x2E, 0x04, 0x2F, 0x04,
+	0x00, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x04, 0x04, 0x04, 0x05, 0x04,
+	0x06, 0x04, 0x07, 0x04, 0x08, 0x04, 0x09, 0x04, 0x0A, 0x04, 0x0B, 0x04,
+	0x0C, 0x04, 0x0D, 0x04, 0x0E, 0x04, 0x0F, 0x04, 0x60, 0x04, 0x60, 0x04,
+	0x62, 0x04, 0x62, 0x04, 0x64, 0x04, 0x64, 0x04, 0x66, 0x04, 0x66, 0x04,
+	0x68, 0x04, 0x68, 0x04, 0x6A, 0x04, 0x6A, 0x04, 0x6C, 0x04, 0x6C, 0x04,
+	0x6E, 0x04, 0x6E, 0x04, 0x70, 0x04, 0x70, 0x04, 0x72, 0x04, 0x72, 0x04,
+	0x74, 0x04, 0x74, 0x04, 0x76, 0x04, 0x76, 0x04, 0x78, 0x04, 0x78, 0x04,
+	0x7A, 0x04, 0x7A, 0x04, 0x7C, 0x04, 0x7C, 0x04, 0x7E, 0x04, 0x7E, 0x04,
+	0x80, 0x04, 0x80, 0x04, 0x82, 0x04, 0x83, 0x04, 0x84, 0x04, 0x85, 0x04,
+	0x86, 0x04, 0x87, 0x04, 0x88, 0x04, 0x89, 0x04, 0x8A, 0x04, 0x8A, 0x04,
+	0x8C, 0x04, 0x8C, 0x04, 0x8E, 0x04, 0x8E, 0x04, 0x90, 0x04, 0x90, 0x04,
+	0x92, 0x04, 0x92, 0x04, 0x94, 0x04, 0x94, 0x04, 0x96, 0x04, 0x96, 0x04,
+	0x98, 0x04, 0x98, 0x04, 0x9A, 0x04, 0x9A, 0x04, 0x9C, 0x04, 0x9C, 0x04,
+	0x9E, 0x04, 0x9E, 0x04, 0xA0, 0x04, 0xA0, 0x04, 0xA2, 0x04, 0xA2, 0x04,
+	0xA4, 0x04, 0xA4, 0x04, 0xA6, 0x04, 0xA6, 0x04, 0xA8, 0x04, 0xA8, 0x04,
+	0xAA, 0x04, 0xAA, 0x04, 0xAC, 0x04, 0xAC, 0x04, 0xAE, 0x04, 0xAE, 0x04,
+	0xB0, 0x04, 0xB0, 0x04, 0xB2, 0x04, 0xB2, 0x04, 0xB4, 0x04, 0xB4, 0x04,
+	0xB6, 0x04, 0xB6, 0x04, 0xB8, 0x04, 0xB8, 0x04, 0xBA, 0x04, 0xBA, 0x04,
+	0xBC, 0x04, 0xBC, 0x04, 0xBE, 0x04, 0xBE, 0x04, 0xC0, 0x04, 0xC1, 0x04,
+	0xC1, 0x04, 0xC3, 0x04, 0xC3, 0x04, 0xC5, 0x04, 0xC5, 0x04, 0xC7, 0x04,
+	0xC7, 0x04, 0xC9, 0x04, 0xC9, 0x04, 0xCB, 0x04, 0xCB, 0x04, 0xCD, 0x04,
+	0xCD, 0x04, 0xC0, 0x04, 0xD0, 0x04, 0xD0, 0x04, 0xD2, 0x04, 0xD2, 0x04,
+	0xD4, 0x04, 0xD4, 0x04, 0xD6, 0x04, 0xD6, 0x04, 0xD8, 0x04, 0xD8, 0x04,
+	0xDA, 0x04, 0xDA, 0x04, 0xDC, 0x04, 0xDC, 0x04, 0xDE, 0x04, 0xDE, 0x04,
+	0xE0, 0x04, 0xE0, 0x04, 0xE2, 0x04, 0xE2, 0x04, 0xE4, 0x04, 0xE4, 0x04,
+	0xE6, 0x04, 0xE6, 0x04, 0xE8, 0x04, 0xE8, 0x04, 0xEA, 0x04, 0xEA, 0x04,
+	0xEC, 0x04, 0xEC, 0x04, 0xEE, 0x04, 0xEE, 0x04, 0xF0, 0x04, 0xF0, 0x04,
+	0xF2, 0x04, 0xF2, 0x04, 0xF4, 0x04, 0xF4, 0x04, 0xF6, 0x04, 0xF6, 0x04,
+	0xF8, 0x04, 0xF8, 0x04, 0xFA, 0x04, 0xFA, 0x04, 0xFC, 0x04, 0xFC, 0x04,
+	0xFE, 0x04, 0xFE, 0x04, 0x00, 0x05, 0x00, 0x05, 0x02, 0x05, 0x02, 0x05,
+	0x04, 0x05, 0x04, 0x05, 0x06, 0x05, 0x06, 0x05, 0x08, 0x05, 0x08, 0x05,
+	0x0A, 0x05, 0x0A, 0x05, 0x0C, 0x05, 0x0C, 0x05, 0x0E, 0x05, 0x0E, 0x05,
+	0x10, 0x05, 0x10, 0x05, 0x12, 0x05, 0x12, 0x05, 0x14, 0x05, 0x15, 0x05,
+	0x16, 0x05, 0x17, 0x05, 0x18, 0x05, 0x19, 0x05, 0x1A, 0x05, 0x1B, 0x05,
+	0x1C, 0x05, 0x1D, 0x05, 0x1E, 0x05, 0x1F, 0x05, 0x20, 0x05, 0x21, 0x05,
+	0x22, 0x05, 0x23, 0x05, 0x24, 0x05, 0x25, 0x05, 0x26, 0x05, 0x27, 0x05,
+	0x28, 0x05, 0x29, 0x05, 0x2A, 0x05, 0x2B, 0x05, 0x2C, 0x05, 0x2D, 0x05,
+	0x2E, 0x05, 0x2F, 0x05, 0x30, 0x05, 0x31, 0x05, 0x32, 0x05, 0x33, 0x05,
+	0x34, 0x05, 0x35, 0x05, 0x36, 0x05, 0x37, 0x05, 0x38, 0x05, 0x39, 0x05,
+	0x3A, 0x05, 0x3B, 0x05, 0x3C, 0x05, 0x3D, 0x05, 0x3E, 0x05, 0x3F, 0x05,
+	0x40, 0x05, 0x41, 0x05, 0x42, 0x05, 0x43, 0x05, 0x44, 0x05, 0x45, 0x05,
+	0x46, 0x05, 0x47, 0x05, 0x48, 0x05, 0x49, 0x05, 0x4A, 0x05, 0x4B, 0x05,
+	0x4C, 0x05, 0x4D, 0x05, 0x4E, 0x05, 0x4F, 0x05, 0x50, 0x05, 0x51, 0x05,
+	0x52, 0x05, 0x53, 0x05, 0x54, 0x05, 0x55, 0x05, 0x56, 0x05, 0x57, 0x05,
+	0x58, 0x05, 0x59, 0x05, 0x5A, 0x05, 0x5B, 0x05, 0x5C, 0x05, 0x5D, 0x05,
+	0x5E, 0x05, 0x5F, 0x05, 0x60, 0x05, 0x31, 0x05, 0x32, 0x05, 0x33, 0x05,
+	0x34, 0x05, 0x35, 0x05, 0x36, 0x05, 0x37, 0x05, 0x38, 0x05, 0x39, 0x05,
+	0x3A, 0x05, 0x3B, 0x05, 0x3C, 0x05, 0x3D, 0x05, 0x3E, 0x05, 0x3F, 0x05,
+	0x40, 0x05, 0x41, 0x05, 0x42, 0x05, 0x43, 0x05, 0x44, 0x05, 0x45, 0x05,
+	0x46, 0x05, 0x47, 0x05, 0x48, 0x05, 0x49, 0x05, 0x4A, 0x05, 0x4B, 0x05,
+	0x4C, 0x05, 0x4D, 0x05, 0x4E, 0x05, 0x4F, 0x05, 0x50, 0x05, 0x51, 0x05,
+	0x52, 0x05, 0x53, 0x05, 0x54, 0x05, 0x55, 0x05, 0x56, 0x05, 0xFF, 0xFF,
+	0xF6, 0x17, 0x63, 0x2C, 0x7E, 0x1D, 0x7F, 0x1D, 0x80, 0x1D, 0x81, 0x1D,
+	0x82, 0x1D, 0x83, 0x1D, 0x84, 0x1D, 0x85, 0x1D, 0x86, 0x1D, 0x87, 0x1D,
+	0x88, 0x1D, 0x89, 0x1D, 0x8A, 0x1D, 0x8B, 0x1D, 0x8C, 0x1D, 0x8D, 0x1D,
+	0x8E, 0x1D, 0x8F, 0x1D, 0x90, 0x1D, 0x91, 0x1D, 0x92, 0x1D, 0x93, 0x1D,
+	0x94, 0x1D, 0x95, 0x1D, 0x96, 0x1D, 0x97, 0x1D, 0x98, 0x1D, 0x99, 0x1D,
+	0x9A, 0x1D, 0x9B, 0x1D, 0x9C, 0x1D, 0x9D, 0x1D, 0x9E, 0x1D, 0x9F, 0x1D,
+	0xA0, 0x1D, 0xA1, 0x1D, 0xA2, 0x1D, 0xA3, 0x1D, 0xA4, 0x1D, 0xA5, 0x1D,
+	0xA6, 0x1D, 0xA7, 0x1D, 0xA8, 0x1D, 0xA9, 0x1D, 0xAA, 0x1D, 0xAB, 0x1D,
+	0xAC, 0x1D, 0xAD, 0x1D, 0xAE, 0x1D, 0xAF, 0x1D, 0xB0, 0x1D, 0xB1, 0x1D,
+	0xB2, 0x1D, 0xB3, 0x1D, 0xB4, 0x1D, 0xB5, 0x1D, 0xB6, 0x1D, 0xB7, 0x1D,
+	0xB8, 0x1D, 0xB9, 0x1D, 0xBA, 0x1D, 0xBB, 0x1D, 0xBC, 0x1D, 0xBD, 0x1D,
+	0xBE, 0x1D, 0xBF, 0x1D, 0xC0, 0x1D, 0xC1, 0x1D, 0xC2, 0x1D, 0xC3, 0x1D,
+	0xC4, 0x1D, 0xC5, 0x1D, 0xC6, 0x1D, 0xC7, 0x1D, 0xC8, 0x1D, 0xC9, 0x1D,
+	0xCA, 0x1D, 0xCB, 0x1D, 0xCC, 0x1D, 0xCD, 0x1D, 0xCE, 0x1D, 0xCF, 0x1D,
+	0xD0, 0x1D, 0xD1, 0x1D, 0xD2, 0x1D, 0xD3, 0x1D, 0xD4, 0x1D, 0xD5, 0x1D,
+	0xD6, 0x1D, 0xD7, 0x1D, 0xD8, 0x1D, 0xD9, 0x1D, 0xDA, 0x1D, 0xDB, 0x1D,
+	0xDC, 0x1D, 0xDD, 0x1D, 0xDE, 0x1D, 0xDF, 0x1D, 0xE0, 0x1D, 0xE1, 0x1D,
+	0xE2, 0x1D, 0xE3, 0x1D, 0xE4, 0x1D, 0xE5, 0x1D, 0xE6, 0x1D, 0xE7, 0x1D,
+	0xE8, 0x1D, 0xE9, 0x1D, 0xEA, 0x1D, 0xEB, 0x1D, 0xEC, 0x1D, 0xED, 0x1D,
+	0xEE, 0x1D, 0xEF, 0x1D, 0xF0, 0x1D, 0xF1, 0x1D, 0xF2, 0x1D, 0xF3, 0x1D,
+	0xF4, 0x1D, 0xF5, 0x1D, 0xF6, 0x1D, 0xF7, 0x1D, 0xF8, 0x1D, 0xF9, 0x1D,
+	0xFA, 0x1D, 0xFB, 0x1D, 0xFC, 0x1D, 0xFD, 0x1D, 0xFE, 0x1D, 0xFF, 0x1D,
+	0x00, 0x1E, 0x00, 0x1E, 0x02, 0x1E, 0x02, 0x1E, 0x04, 0x1E, 0x04, 0x1E,
+	0x06, 0x1E, 0x06, 0x1E, 0x08, 0x1E, 0x08, 0x1E, 0x0A, 0x1E, 0x0A, 0x1E,
+	0x0C, 0x1E, 0x0C, 0x1E, 0x0E, 0x1E, 0x0E, 0x1E, 0x10, 0x1E, 0x10, 0x1E,
+	0x12, 0x1E, 0x12, 0x1E, 0x14, 0x1E, 0x14, 0x1E, 0x16, 0x1E, 0x16, 0x1E,
+	0x18, 0x1E, 0x18, 0x1E, 0x1A, 0x1E, 0x1A, 0x1E, 0x1C, 0x1E, 0x1C, 0x1E,
+	0x1E, 0x1E, 0x1E, 0x1E, 0x20, 0x1E, 0x20, 0x1E, 0x22, 0x1E, 0x22, 0x1E,
+	0x24, 0x1E, 0x24, 0x1E, 0x26, 0x1E, 0x26, 0x1E, 0x28, 0x1E, 0x28, 0x1E,
+	0x2A, 0x1E, 0x2A, 0x1E, 0x2C, 0x1E, 0x2C, 0x1E, 0x2E, 0x1E, 0x2E, 0x1E,
+	0x30, 0x1E, 0x30, 0x1E, 0x32, 0x1E, 0x32, 0x1E, 0x34, 0x1E, 0x34, 0x1E,
+	0x36, 0x1E, 0x36, 0x1E, 0x38, 0x1E, 0x38, 0x1E, 0x3A, 0x1E, 0x3A, 0x1E,
+	0x3C, 0x1E, 0x3C, 0x1E, 0x3E, 0x1E, 0x3E, 0x1E, 0x40, 0x1E, 0x40, 0x1E,
+	0x42, 0x1E, 0x42, 0x1E, 0x44, 0x1E, 0x44, 0x1E, 0x46, 0x1E, 0x46, 0x1E,
+	0x48, 0x1E, 0x48, 0x1E, 0x4A, 0x1E, 0x4A, 0x1E, 0x4C, 0x1E, 0x4C, 0x1E,
+	0x4E, 0x1E, 0x4E, 0x1E, 0x50, 0x1E, 0x50, 0x1E, 0x52, 0x1E, 0x52, 0x1E,
+	0x54, 0x1E, 0x54, 0x1E, 0x56, 0x1E, 0x56, 0x1E, 0x58, 0x1E, 0x58, 0x1E,
+	0x5A, 0x1E, 0x5A, 0x1E, 0x5C, 0x1E, 0x5C, 0x1E, 0x5E, 0x1E, 0x5E, 0x1E,
+	0x60, 0x1E, 0x60, 0x1E, 0x62, 0x1E, 0x62, 0x1E, 0x64, 0x1E, 0x64, 0x1E,
+	0x66, 0x1E, 0x66, 0x1E, 0x68, 0x1E, 0x68, 0x1E, 0x6A, 0x1E, 0x6A, 0x1E,
+	0x6C, 0x1E, 0x6C, 0x1E, 0x6E, 0x1E, 0x6E, 0x1E, 0x70, 0x1E, 0x70, 0x1E,
+	0x72, 0x1E, 0x72, 0x1E, 0x74, 0x1E, 0x74, 0x1E, 0x76, 0x1E, 0x76, 0x1E,
+	0x78, 0x1E, 0x78, 0x1E, 0x7A, 0x1E, 0x7A, 0x1E, 0x7C, 0x1E, 0x7C, 0x1E,
+	0x7E, 0x1E, 0x7E, 0x1E, 0x80, 0x1E, 0x80, 0x1E, 0x82, 0x1E, 0x82, 0x1E,
+	0x84, 0x1E, 0x84, 0x1E, 0x86, 0x1E, 0x86, 0x1E, 0x88, 0x1E, 0x88, 0x1E,
+	0x8A, 0x1E, 0x8A, 0x1E, 0x8C, 0x1E, 0x8C, 0x1E, 0x8E, 0x1E, 0x8E, 0x1E,
+	0x90, 0x1E, 0x90, 0x1E, 0x92, 0x1E, 0x92, 0x1E, 0x94, 0x1E, 0x94, 0x1E,
+	0x96, 0x1E, 0x97, 0x1E, 0x98, 0x1E, 0x99, 0x1E, 0x9A, 0x1E, 0x9B, 0x1E,
+	0x9C, 0x1E, 0x9D, 0x1E, 0x9E, 0x1E, 0x9F, 0x1E, 0xA0, 0x1E, 0xA0, 0x1E,
+	0xA2, 0x1E, 0xA2, 0x1E, 0xA4, 0x1E, 0xA4, 0x1E, 0xA6, 0x1E, 0xA6, 0x1E,
+	0xA8, 0x1E, 0xA8, 0x1E, 0xAA, 0x1E, 0xAA, 0x1E, 0xAC, 0x1E, 0xAC, 0x1E,
+	0xAE, 0x1E, 0xAE, 0x1E, 0xB0, 0x1E, 0xB0, 0x1E, 0xB2, 0x1E, 0xB2, 0x1E,
+	0xB4, 0x1E, 0xB4, 0x1E, 0xB6, 0x1E, 0xB6, 0x1E, 0xB8, 0x1E, 0xB8, 0x1E,
+	0xBA, 0x1E, 0xBA, 0x1E, 0xBC, 0x1E, 0xBC, 0x1E, 0xBE, 0x1E, 0xBE, 0x1E,
+	0xC0, 0x1E, 0xC0, 0x1E, 0xC2, 0x1E, 0xC2, 0x1E, 0xC4, 0x1E, 0xC4, 0x1E,
+	0xC6, 0x1E, 0xC6, 0x1E, 0xC8, 0x1E, 0xC8, 0x1E, 0xCA, 0x1E, 0xCA, 0x1E,
+	0xCC, 0x1E, 0xCC, 0x1E, 0xCE, 0x1E, 0xCE, 0x1E, 0xD0, 0x1E, 0xD0, 0x1E,
+	0xD2, 0x1E, 0xD2, 0x1E, 0xD4, 0x1E, 0xD4, 0x1E, 0xD6, 0x1E, 0xD6, 0x1E,
+	0xD8, 0x1E, 0xD8, 0x1E, 0xDA, 0x1E, 0xDA, 0x1E, 0xDC, 0x1E, 0xDC, 0x1E,
+	0xDE, 0x1E, 0xDE, 0x1E, 0xE0, 0x1E, 0xE0, 0x1E, 0xE2, 0x1E, 0xE2, 0x1E,
+	0xE4, 0x1E, 0xE4, 0x1E, 0xE6, 0x1E, 0xE6, 0x1E, 0xE8, 0x1E, 0xE8, 0x1E,
+	0xEA, 0x1E, 0xEA, 0x1E, 0xEC, 0x1E, 0xEC, 0x1E, 0xEE, 0x1E, 0xEE, 0x1E,
+	0xF0, 0x1E, 0xF0, 0x1E, 0xF2, 0x1E, 0xF2, 0x1E, 0xF4, 0x1E, 0xF4, 0x1E,
+	0xF6, 0x1E, 0xF6, 0x1E, 0xF8, 0x1E, 0xF8, 0x1E, 0xFA, 0x1E, 0xFB, 0x1E,
+	0xFC, 0x1E, 0xFD, 0x1E, 0xFE, 0x1E, 0xFF, 0x1E, 0x08, 0x1F, 0x09, 0x1F,
+	0x0A, 0x1F, 0x0B, 0x1F, 0x0C, 0x1F, 0x0D, 0x1F, 0x0E, 0x1F, 0x0F, 0x1F,
+	0x08, 0x1F, 0x09, 0x1F, 0x0A, 0x1F, 0x0B, 0x1F, 0x0C, 0x1F, 0x0D, 0x1F,
+	0x0E, 0x1F, 0x0F, 0x1F, 0x18, 0x1F, 0x19, 0x1F, 0x1A, 0x1F, 0x1B, 0x1F,
+	0x1C, 0x1F, 0x1D, 0x1F, 0x16, 0x1F, 0x17, 0x1F, 0x18, 0x1F, 0x19, 0x1F,
+	0x1A, 0x1F, 0x1B, 0x1F, 0x1C, 0x1F, 0x1D, 0x1F, 0x1E, 0x1F, 0x1F, 0x1F,
+	0x28, 0x1F, 0x29, 0x1F, 0x2A, 0x1F, 0x2B, 0x1F, 0x2C, 0x1F, 0x2D, 0x1F,
+	0x2E, 0x1F, 0x2F, 0x1F, 0x28, 0x1F, 0x29, 0x1F, 0x2A, 0x1F, 0x2B, 0x1F,
+	0x2C, 0x1F, 0x2D, 0x1F, 0x2E, 0x1F, 0x2F, 0x1F, 0x38, 0x1F, 0x39, 0x1F,
+	0x3A, 0x1F, 0x3B, 0x1F, 0x3C, 0x1F, 0x3D, 0x1F, 0x3E, 0x1F, 0x3F, 0x1F,
+	0x38, 0x1F, 0x39, 0x1F, 0x3A, 0x1F, 0x3B, 0x1F, 0x3C, 0x1F, 0x3D, 0x1F,
+	0x3E, 0x1F, 0x3F, 0x1F, 0x48, 0x1F, 0x49, 0x1F, 0x4A, 0x1F, 0x4B, 0x1F,
+	0x4C, 0x1F, 0x4D, 0x1F, 0x46, 0x1F, 0x47, 0x1F, 0x48, 0x1F, 0x49, 0x1F,
+	0x4A, 0x1F, 0x4B, 0x1F, 0x4C, 0x1F, 0x4D, 0x1F, 0x4E, 0x1F, 0x4F, 0x1F,
+	0x50, 0x1F, 0x59, 0x1F, 0x52, 0x1F, 0x5B, 0x1F, 0x54, 0x1F, 0x5D, 0x1F,
+	0x56, 0x1F, 0x5F, 0x1F, 0x58, 0x1F, 0x59, 0x1F, 0x5A, 0x1F, 0x5B, 0x1F,
+	0x5C, 0x1F, 0x5D, 0x1F, 0x5E, 0x1F, 0x5F, 0x1F, 0x68, 0x1F, 0x69, 0x1F,
+	0x6A, 0x1F, 0x6B, 0x1F, 0x6C, 0x1F, 0x6D, 0x1F, 0x6E, 0x1F, 0x6F, 0x1F,
+	0x68, 0x1F, 0x69, 0x1F, 0x6A, 0x1F, 0x6B, 0x1F, 0x6C, 0x1F, 0x6D, 0x1F,
+	0x6E, 0x1F, 0x6F, 0x1F, 0xBA, 0x1F, 0xBB, 0x1F, 0xC8, 0x1F, 0xC9, 0x1F,
+	0xCA, 0x1F, 0xCB, 0x1F, 0xDA, 0x1F, 0xDB, 0x1F, 0xF8, 0x1F, 0xF9, 0x1F,
+	0xEA, 0x1F, 0xEB, 0x1F, 0xFA, 0x1F, 0xFB, 0x1F, 0x7E, 0x1F, 0x7F, 0x1F,
+	0x88, 0x1F, 0x89, 0x1F, 0x8A, 0x1F, 0x8B, 0x1F, 0x8C, 0x1F, 0x8D, 0x1F,
+	0x8E, 0x1F, 0x8F, 0x1F, 0x88, 0x1F, 0x89, 0x1F, 0x8A, 0x1F, 0x8B, 0x1F,
+	0x8C, 0x1F, 0x8D, 0x1F, 0x8E, 0x1F, 0x8F, 0x1F, 0x98, 0x1F, 0x99, 0x1F,
+	0x9A, 0x1F, 0x9B, 0x1F, 0x9C, 0x1F, 0x9D, 0x1F, 0x9E, 0x1F, 0x9F, 0x1F,
+	0x98, 0x1F, 0x99, 0x1F, 0x9A, 0x1F, 0x9B, 0x1F, 0x9C, 0x1F, 0x9D, 0x1F,
+	0x9E, 0x1F, 0x9F, 0x1F, 0xA8, 0x1F, 0xA9, 0x1F, 0xAA, 0x1F, 0xAB, 0x1F,
+	0xAC, 0x1F, 0xAD, 0x1F, 0xAE, 0x1F, 0xAF, 0x1F, 0xA8, 0x1F, 0xA9, 0x1F,
+	0xAA, 0x1F, 0xAB, 0x1F, 0xAC, 0x1F, 0xAD, 0x1F, 0xAE, 0x1F, 0xAF, 0x1F,
+	0xB8, 0x1F, 0xB9, 0x1F, 0xB2, 0x1F, 0xBC, 0x1F, 0xB4, 0x1F, 0xB5, 0x1F,
+	0xB6, 0x1F, 0xB7, 0x1F, 0xB8, 0x1F, 0xB9, 0x1F, 0xBA, 0x1F, 0xBB, 0x1F,
+	0xBC, 0x1F, 0xBD, 0x1F, 0xBE, 0x1F, 0xBF, 0x1F, 0xC0, 0x1F, 0xC1, 0x1F,
+	0xC2, 0x1F, 0xC3, 0x1F, 0xC4, 0x1F, 0xC5, 0x1F, 0xC6, 0x1F, 0xC7, 0x1F,
+	0xC8, 0x1F, 0xC9, 0x1F, 0xCA, 0x1F, 0xCB, 0x1F, 0xC3, 0x1F, 0xCD, 0x1F,
+	0xCE, 0x1F, 0xCF, 0x1F, 0xD8, 0x1F, 0xD9, 0x1F, 0xD2, 0x1F, 0xD3, 0x1F,
+	0xD4, 0x1F, 0xD5, 0x1F, 0xD6, 0x1F, 0xD7, 0x1F, 0xD8, 0x1F, 0xD9, 0x1F,
+	0xDA, 0x1F, 0xDB, 0x1F, 0xDC, 0x1F, 0xDD, 0x1F, 0xDE, 0x1F, 0xDF, 0x1F,
+	0xE8, 0x1F, 0xE9, 0x1F, 0xE2, 0x1F, 0xE3, 0x1F, 0xE4, 0x1F, 0xEC, 0x1F,
+	0xE6, 0x1F, 0xE7, 0x1F, 0xE8, 0x1F, 0xE9, 0x1F, 0xEA, 0x1F, 0xEB, 0x1F,
+	0xEC, 0x1F, 0xED, 0x1F, 0xEE, 0x1F, 0xEF, 0x1F, 0xF0, 0x1F, 0xF1, 0x1F,
+	0xF2, 0x1F, 0xF3, 0x1F, 0xF4, 0x1F, 0xF5, 0x1F, 0xF6, 0x1F, 0xF7, 0x1F,
+	0xF8, 0x1F, 0xF9, 0x1F, 0xFA, 0x1F, 0xFB, 0x1F, 0xF3, 0x1F, 0xFD, 0x1F,
+	0xFE, 0x1F, 0xFF, 0x1F, 0x00, 0x20, 0x01, 0x20, 0x02, 0x20, 0x03, 0x20,
+	0x04, 0x20, 0x05, 0x20, 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x09, 0x20,
+	0x0A, 0x20, 0x0B, 0x20, 0x0C, 0x20, 0x0D, 0x20, 0x0E, 0x20, 0x0F, 0x20,
+	0x10, 0x20, 0x11, 0x20, 0x12, 0x20, 0x13, 0x20, 0x14, 0x20, 0x15, 0x20,
+	0x16, 0x20, 0x17, 0x20, 0x18, 0x20, 0x19, 0x20, 0x1A, 0x20, 0x1B, 0x20,
+	0x1C, 0x20, 0x1D, 0x20, 0x1E, 0x20, 0x1F, 0x20, 0x20, 0x20, 0x21, 0x20,
+	0x22, 0x20, 0x23, 0x20, 0x24, 0x20, 0x25, 0x20, 0x26, 0x20, 0x27, 0x20,
+	0x28, 0x20, 0x29, 0x20, 0x2A, 0x20, 0x2B, 0x20, 0x2C, 0x20, 0x2D, 0x20,
+	0x2E, 0x20, 0x2F, 0x20, 0x30, 0x20, 0x31, 0x20, 0x32, 0x20, 0x33, 0x20,
+	0x34, 0x20, 0x35, 0x20, 0x36, 0x20, 0x37, 0x20, 0x38, 0x20, 0x39, 0x20,
+	0x3A, 0x20, 0x3B, 0x20, 0x3C, 0x20, 0x3D, 0x20, 0x3E, 0x20, 0x3F, 0x20,
+	0x40, 0x20, 0x41, 0x20, 0x42, 0x20, 0x43, 0x20, 0x44, 0x20, 0x45, 0x20,
+	0x46, 0x20, 0x47, 0x20, 0x48, 0x20, 0x49, 0x20, 0x4A, 0x20, 0x4B, 0x20,
+	0x4C, 0x20, 0x4D, 0x20, 0x4E, 0x20, 0x4F, 0x20, 0x50, 0x20, 0x51, 0x20,
+	0x52, 0x20, 0x53, 0x20, 0x54, 0x20, 0x55, 0x20, 0x56, 0x20, 0x57, 0x20,
+	0x58, 0x20, 0x59, 0x20, 0x5A, 0x20, 0x5B, 0x20, 0x5C, 0x20, 0x5D, 0x20,
+	0x5E, 0x20, 0x5F, 0x20, 0x60, 0x20, 0x61, 0x20, 0x62, 0x20, 0x63, 0x20,
+	0x64, 0x20, 0x65, 0x20, 0x66, 0x20, 0x67, 0x20, 0x68, 0x20, 0x69, 0x20,
+	0x6A, 0x20, 0x6B, 0x20, 0x6C, 0x20, 0x6D, 0x20, 0x6E, 0x20, 0x6F, 0x20,
+	0x70, 0x20, 0x71, 0x20, 0x72, 0x20, 0x73, 0x20, 0x74, 0x20, 0x75, 0x20,
+	0x76, 0x20, 0x77, 0x20, 0x78, 0x20, 0x79, 0x20, 0x7A, 0x20, 0x7B, 0x20,
+	0x7C, 0x20, 0x7D, 0x20, 0x7E, 0x20, 0x7F, 0x20, 0x80, 0x20, 0x81, 0x20,
+	0x82, 0x20, 0x83, 0x20, 0x84, 0x20, 0x85, 0x20, 0x86, 0x20, 0x87, 0x20,
+	0x88, 0x20, 0x89, 0x20, 0x8A, 0x20, 0x8B, 0x20, 0x8C, 0x20, 0x8D, 0x20,
+	0x8E, 0x20, 0x8F, 0x20, 0x90, 0x20, 0x91, 0x20, 0x92, 0x20, 0x93, 0x20,
+	0x94, 0x20, 0x95, 0x20, 0x96, 0x20, 0x97, 0x20, 0x98, 0x20, 0x99, 0x20,
+	0x9A, 0x20, 0x9B, 0x20, 0x9C, 0x20, 0x9D, 0x20, 0x9E, 0x20, 0x9F, 0x20,
+	0xA0, 0x20, 0xA1, 0x20, 0xA2, 0x20, 0xA3, 0x20, 0xA4, 0x20, 0xA5, 0x20,
+	0xA6, 0x20, 0xA7, 0x20, 0xA8, 0x20, 0xA9, 0x20, 0xAA, 0x20, 0xAB, 0x20,
+	0xAC, 0x20, 0xAD, 0x20, 0xAE, 0x20, 0xAF, 0x20, 0xB0, 0x20, 0xB1, 0x20,
+	0xB2, 0x20, 0xB3, 0x20, 0xB4, 0x20, 0xB5, 0x20, 0xB6, 0x20, 0xB7, 0x20,
+	0xB8, 0x20, 0xB9, 0x20, 0xBA, 0x20, 0xBB, 0x20, 0xBC, 0x20, 0xBD, 0x20,
+	0xBE, 0x20, 0xBF, 0x20, 0xC0, 0x20, 0xC1, 0x20, 0xC2, 0x20, 0xC3, 0x20,
+	0xC4, 0x20, 0xC5, 0x20, 0xC6, 0x20, 0xC7, 0x20, 0xC8, 0x20, 0xC9, 0x20,
+	0xCA, 0x20, 0xCB, 0x20, 0xCC, 0x20, 0xCD, 0x20, 0xCE, 0x20, 0xCF, 0x20,
+	0xD0, 0x20, 0xD1, 0x20, 0xD2, 0x20, 0xD3, 0x20, 0xD4, 0x20, 0xD5, 0x20,
+	0xD6, 0x20, 0xD7, 0x20, 0xD8, 0x20, 0xD9, 0x20, 0xDA, 0x20, 0xDB, 0x20,
+	0xDC, 0x20, 0xDD, 0x20, 0xDE, 0x20, 0xDF, 0x20, 0xE0, 0x20, 0xE1, 0x20,
+	0xE2, 0x20, 0xE3, 0x20, 0xE4, 0x20, 0xE5, 0x20, 0xE6, 0x20, 0xE7, 0x20,
+	0xE8, 0x20, 0xE9, 0x20, 0xEA, 0x20, 0xEB, 0x20, 0xEC, 0x20, 0xED, 0x20,
+	0xEE, 0x20, 0xEF, 0x20, 0xF0, 0x20, 0xF1, 0x20, 0xF2, 0x20, 0xF3, 0x20,
+	0xF4, 0x20, 0xF5, 0x20, 0xF6, 0x20, 0xF7, 0x20, 0xF8, 0x20, 0xF9, 0x20,
+	0xFA, 0x20, 0xFB, 0x20, 0xFC, 0x20, 0xFD, 0x20, 0xFE, 0x20, 0xFF, 0x20,
+	0x00, 0x21, 0x01, 0x21, 0x02, 0x21, 0x03, 0x21, 0x04, 0x21, 0x05, 0x21,
+	0x06, 0x21, 0x07, 0x21, 0x08, 0x21, 0x09, 0x21, 0x0A, 0x21, 0x0B, 0x21,
+	0x0C, 0x21, 0x0D, 0x21, 0x0E, 0x21, 0x0F, 0x21, 0x10, 0x21, 0x11, 0x21,
+	0x12, 0x21, 0x13, 0x21, 0x14, 0x21, 0x15, 0x21, 0x16, 0x21, 0x17, 0x21,
+	0x18, 0x21, 0x19, 0x21, 0x1A, 0x21, 0x1B, 0x21, 0x1C, 0x21, 0x1D, 0x21,
+	0x1E, 0x21, 0x1F, 0x21, 0x20, 0x21, 0x21, 0x21, 0x22, 0x21, 0x23, 0x21,
+	0x24, 0x21, 0x25, 0x21, 0x26, 0x21, 0x27, 0x21, 0x28, 0x21, 0x29, 0x21,
+	0x2A, 0x21, 0x2B, 0x21, 0x2C, 0x21, 0x2D, 0x21, 0x2E, 0x21, 0x2F, 0x21,
+	0x30, 0x21, 0x31, 0x21, 0x32, 0x21, 0x33, 0x21, 0x34, 0x21, 0x35, 0x21,
+	0x36, 0x21, 0x37, 0x21, 0x38, 0x21, 0x39, 0x21, 0x3A, 0x21, 0x3B, 0x21,
+	0x3C, 0x21, 0x3D, 0x21, 0x3E, 0x21, 0x3F, 0x21, 0x40, 0x21, 0x41, 0x21,
+	0x42, 0x21, 0x43, 0x21, 0x44, 0x21, 0x45, 0x21, 0x46, 0x21, 0x47, 0x21,
+	0x48, 0x21, 0x49, 0x21, 0x4A, 0x21, 0x4B, 0x21, 0x4C, 0x21, 0x4D, 0x21,
+	0x32, 0x21, 0x4F, 0x21, 0x50, 0x21, 0x51, 0x21, 0x52, 0x21, 0x53, 0x21,
+	0x54, 0x21, 0x55, 0x21, 0x56, 0x21, 0x57, 0x21, 0x58, 0x21, 0x59, 0x21,
+	0x5A, 0x21, 0x5B, 0x21, 0x5C, 0x21, 0x5D, 0x21, 0x5E, 0x21, 0x5F, 0x21,
+	0x60, 0x21, 0x61, 0x21, 0x62, 0x21, 0x63, 0x21, 0x64, 0x21, 0x65, 0x21,
+	0x66, 0x21, 0x67, 0x21, 0x68, 0x21, 0x69, 0x21, 0x6A, 0x21, 0x6B, 0x21,
+	0x6C, 0x21, 0x6D, 0x21, 0x6E, 0x21, 0x6F, 0x21, 0x60, 0x21, 0x61, 0x21,
+	0x62, 0x21, 0x63, 0x21, 0x64, 0x21, 0x65, 0x21, 0x66, 0x21, 0x67, 0x21,
+	0x68, 0x21, 0x69, 0x21, 0x6A, 0x21, 0x6B, 0x21, 0x6C, 0x21, 0x6D, 0x21,
+	0x6E, 0x21, 0x6F, 0x21, 0x80, 0x21, 0x81, 0x21, 0x82, 0x21, 0x83, 0x21,
+	0x83, 0x21, 0xFF, 0xFF, 0x4B, 0x03, 0xB6, 0x24, 0xB7, 0x24, 0xB8, 0x24,
+	0xB9, 0x24, 0xBA, 0x24, 0xBB, 0x24, 0xBC, 0x24, 0xBD, 0x24, 0xBE, 0x24,
+	0xBF, 0x24, 0xC0, 0x24, 0xC1, 0x24, 0xC2, 0x24, 0xC3, 0x24, 0xC4, 0x24,
+	0xC5, 0x24, 0xC6, 0x24, 0xC7, 0x24, 0xC8, 0x24, 0xC9, 0x24, 0xCA, 0x24,
+	0xCB, 0x24, 0xCC, 0x24, 0xCD, 0x24, 0xCE, 0x24, 0xCF, 0x24, 0xFF, 0xFF,
+	0x46, 0x07, 0x00, 0x2C, 0x01, 0x2C, 0x02, 0x2C, 0x03, 0x2C, 0x04, 0x2C,
+	0x05, 0x2C, 0x06, 0x2C, 0x07, 0x2C, 0x08, 0x2C, 0x09, 0x2C, 0x0A, 0x2C,
+	0x0B, 0x2C, 0x0C, 0x2C, 0x0D, 0x2C, 0x0E, 0x2C, 0x0F, 0x2C, 0x10, 0x2C,
+	0x11, 0x2C, 0x12, 0x2C, 0x13, 0x2C, 0x14, 0x2C, 0x15, 0x2C, 0x16, 0x2C,
+	0x17, 0x2C, 0x18, 0x2C, 0x19, 0x2C, 0x1A, 0x2C, 0x1B, 0x2C, 0x1C, 0x2C,
+	0x1D, 0x2C, 0x1E, 0x2C, 0x1F, 0x2C, 0x20, 0x2C, 0x21, 0x2C, 0x22, 0x2C,
+	0x23, 0x2C, 0x24, 0x2C, 0x25, 0x2C, 0x26, 0x2C, 0x27, 0x2C, 0x28, 0x2C,
+	0x29, 0x2C, 0x2A, 0x2C, 0x2B, 0x2C, 0x2C, 0x2C, 0x2D, 0x2C, 0x2E, 0x2C,
+	0x5F, 0x2C, 0x60, 0x2C, 0x60, 0x2C, 0x62, 0x2C, 0x63, 0x2C, 0x64, 0x2C,
+	0x65, 0x2C, 0x66, 0x2C, 0x67, 0x2C, 0x67, 0x2C, 0x69, 0x2C, 0x69, 0x2C,
+	0x6B, 0x2C, 0x6B, 0x2C, 0x6D, 0x2C, 0x6E, 0x2C, 0x6F, 0x2C, 0x70, 0x2C,
+	0x71, 0x2C, 0x72, 0x2C, 0x73, 0x2C, 0x74, 0x2C, 0x75, 0x2C, 0x75, 0x2C,
+	0x77, 0x2C, 0x78, 0x2C, 0x79, 0x2C, 0x7A, 0x2C, 0x7B, 0x2C, 0x7C, 0x2C,
+	0x7D, 0x2C, 0x7E, 0x2C, 0x7F, 0x2C, 0x80, 0x2C, 0x80, 0x2C, 0x82, 0x2C,
+	0x82, 0x2C, 0x84, 0x2C, 0x84, 0x2C, 0x86, 0x2C, 0x86, 0x2C, 0x88, 0x2C,
+	0x88, 0x2C, 0x8A, 0x2C, 0x8A, 0x2C, 0x8C, 0x2C, 0x8C, 0x2C, 0x8E, 0x2C,
+	0x8E, 0x2C, 0x90, 0x2C, 0x90, 0x2C, 0x92, 0x2C, 0x92, 0x2C, 0x94, 0x2C,
+	0x94, 0x2C, 0x96, 0x2C, 0x96, 0x2C, 0x98, 0x2C, 0x98, 0x2C, 0x9A, 0x2C,
+	0x9A, 0x2C, 0x9C, 0x2C, 0x9C, 0x2C, 0x9E, 0x2C, 0x9E, 0x2C, 0xA0, 0x2C,
+	0xA0, 0x2C, 0xA2, 0x2C, 0xA2, 0x2C, 0xA4, 0x2C, 0xA4, 0x2C, 0xA6, 0x2C,
+	0xA6, 0x2C, 0xA8, 0x2C, 0xA8, 0x2C, 0xAA, 0x2C, 0xAA, 0x2C, 0xAC, 0x2C,
+	0xAC, 0x2C, 0xAE, 0x2C, 0xAE, 0x2C, 0xB0, 0x2C, 0xB0, 0x2C, 0xB2, 0x2C,
+	0xB2, 0x2C, 0xB4, 0x2C, 0xB4, 0x2C, 0xB6, 0x2C, 0xB6, 0x2C, 0xB8, 0x2C,
+	0xB8, 0x2C, 0xBA, 0x2C, 0xBA, 0x2C, 0xBC, 0x2C, 0xBC, 0x2C, 0xBE, 0x2C,
+	0xBE, 0x2C, 0xC0, 0x2C, 0xC0, 0x2C, 0xC2, 0x2C, 0xC2, 0x2C, 0xC4, 0x2C,
+	0xC4, 0x2C, 0xC6, 0x2C, 0xC6, 0x2C, 0xC8, 0x2C, 0xC8, 0x2C, 0xCA, 0x2C,
+	0xCA, 0x2C, 0xCC, 0x2C, 0xCC, 0x2C, 0xCE, 0x2C, 0xCE, 0x2C, 0xD0, 0x2C,
+	0xD0, 0x2C, 0xD2, 0x2C, 0xD2, 0x2C, 0xD4, 0x2C, 0xD4, 0x2C, 0xD6, 0x2C,
+	0xD6, 0x2C, 0xD8, 0x2C, 0xD8, 0x2C, 0xDA, 0x2C, 0xDA, 0x2C, 0xDC, 0x2C,
+	0xDC, 0x2C, 0xDE, 0x2C, 0xDE, 0x2C, 0xE0, 0x2C, 0xE0, 0x2C, 0xE2, 0x2C,
+	0xE2, 0x2C, 0xE4, 0x2C, 0xE5, 0x2C, 0xE6, 0x2C, 0xE7, 0x2C, 0xE8, 0x2C,
+	0xE9, 0x2C, 0xEA, 0x2C, 0xEB, 0x2C, 0xEC, 0x2C, 0xED, 0x2C, 0xEE, 0x2C,
+	0xEF, 0x2C, 0xF0, 0x2C, 0xF1, 0x2C, 0xF2, 0x2C, 0xF3, 0x2C, 0xF4, 0x2C,
+	0xF5, 0x2C, 0xF6, 0x2C, 0xF7, 0x2C, 0xF8, 0x2C, 0xF9, 0x2C, 0xFA, 0x2C,
+	0xFB, 0x2C, 0xFC, 0x2C, 0xFD, 0x2C, 0xFE, 0x2C, 0xFF, 0x2C, 0xA0, 0x10,
+	0xA1, 0x10, 0xA2, 0x10, 0xA3, 0x10, 0xA4, 0x10, 0xA5, 0x10, 0xA6, 0x10,
+	0xA7, 0x10, 0xA8, 0x10, 0xA9, 0x10, 0xAA, 0x10, 0xAB, 0x10, 0xAC, 0x10,
+	0xAD, 0x10, 0xAE, 0x10, 0xAF, 0x10, 0xB0, 0x10, 0xB1, 0x10, 0xB2, 0x10,
+	0xB3, 0x10, 0xB4, 0x10, 0xB5, 0x10, 0xB6, 0x10, 0xB7, 0x10, 0xB8, 0x10,
+	0xB9, 0x10, 0xBA, 0x10, 0xBB, 0x10, 0xBC, 0x10, 0xBD, 0x10, 0xBE, 0x10,
+	0xBF, 0x10, 0xC0, 0x10, 0xC1, 0x10, 0xC2, 0x10, 0xC3, 0x10, 0xC4, 0x10,
+	0xC5, 0x10, 0xFF, 0xFF, 0x1B, 0xD2, 0x21, 0xFF, 0x22, 0xFF, 0x23, 0xFF,
+	0x24, 0xFF, 0x25, 0xFF, 0x26, 0xFF, 0x27, 0xFF, 0x28, 0xFF, 0x29, 0xFF,
+	0x2A, 0xFF, 0x2B, 0xFF, 0x2C, 0xFF, 0x2D, 0xFF, 0x2E, 0xFF, 0x2F, 0xFF,
+	0x30, 0xFF, 0x31, 0xFF, 0x32, 0xFF, 0x33, 0xFF, 0x34, 0xFF, 0x35, 0xFF,
+	0x36, 0xFF, 0x37, 0xFF, 0x38, 0xFF, 0x39, 0xFF, 0x3A, 0xFF, 0x5B, 0xFF,
+	0x5C, 0xFF, 0x5D, 0xFF, 0x5E, 0xFF, 0x5F, 0xFF, 0x60, 0xFF, 0x61, 0xFF,
+	0x62, 0xFF, 0x63, 0xFF, 0x64, 0xFF, 0x65, 0xFF, 0x66, 0xFF, 0x67, 0xFF,
+	0x68, 0xFF, 0x69, 0xFF, 0x6A, 0xFF, 0x6B, 0xFF, 0x6C, 0xFF, 0x6D, 0xFF,
+	0x6E, 0xFF, 0x6F, 0xFF, 0x70, 0xFF, 0x71, 0xFF, 0x72, 0xFF, 0x73, 0xFF,
+	0x74, 0xFF, 0x75, 0xFF, 0x76, 0xFF, 0x77, 0xFF, 0x78, 0xFF, 0x79, 0xFF,
+	0x7A, 0xFF, 0x7B, 0xFF, 0x7C, 0xFF, 0x7D, 0xFF, 0x7E, 0xFF, 0x7F, 0xFF,
+	0x80, 0xFF, 0x81, 0xFF, 0x82, 0xFF, 0x83, 0xFF, 0x84, 0xFF, 0x85, 0xFF,
+	0x86, 0xFF, 0x87, 0xFF, 0x88, 0xFF, 0x89, 0xFF, 0x8A, 0xFF, 0x8B, 0xFF,
+	0x8C, 0xFF, 0x8D, 0xFF, 0x8E, 0xFF, 0x8F, 0xFF, 0x90, 0xFF, 0x91, 0xFF,
+	0x92, 0xFF, 0x93, 0xFF, 0x94, 0xFF, 0x95, 0xFF, 0x96, 0xFF, 0x97, 0xFF,
+	0x98, 0xFF, 0x99, 0xFF, 0x9A, 0xFF, 0x9B, 0xFF, 0x9C, 0xFF, 0x9D, 0xFF,
+	0x9E, 0xFF, 0x9F, 0xFF, 0xA0, 0xFF, 0xA1, 0xFF, 0xA2, 0xFF, 0xA3, 0xFF,
+	0xA4, 0xFF, 0xA5, 0xFF, 0xA6, 0xFF, 0xA7, 0xFF, 0xA8, 0xFF, 0xA9, 0xFF,
+	0xAA, 0xFF, 0xAB, 0xFF, 0xAC, 0xFF, 0xAD, 0xFF, 0xAE, 0xFF, 0xAF, 0xFF,
+	0xB0, 0xFF, 0xB1, 0xFF, 0xB2, 0xFF, 0xB3, 0xFF, 0xB4, 0xFF, 0xB5, 0xFF,
+	0xB6, 0xFF, 0xB7, 0xFF, 0xB8, 0xFF, 0xB9, 0xFF, 0xBA, 0xFF, 0xBB, 0xFF,
+	0xBC, 0xFF, 0xBD, 0xFF, 0xBE, 0xFF, 0xBF, 0xFF, 0xC0, 0xFF, 0xC1, 0xFF,
+	0xC2, 0xFF, 0xC3, 0xFF, 0xC4, 0xFF, 0xC5, 0xFF, 0xC6, 0xFF, 0xC7, 0xFF,
+	0xC8, 0xFF, 0xC9, 0xFF, 0xCA, 0xFF, 0xCB, 0xFF, 0xCC, 0xFF, 0xCD, 0xFF,
+	0xCE, 0xFF, 0xCF, 0xFF, 0xD0, 0xFF, 0xD1, 0xFF, 0xD2, 0xFF, 0xD3, 0xFF,
+	0xD4, 0xFF, 0xD5, 0xFF, 0xD6, 0xFF, 0xD7, 0xFF, 0xD8, 0xFF, 0xD9, 0xFF,
+	0xDA, 0xFF, 0xDB, 0xFF, 0xDC, 0xFF, 0xDD, 0xFF, 0xDE, 0xFF, 0xDF, 0xFF,
+	0xE0, 0xFF, 0xE1, 0xFF, 0xE2, 0xFF, 0xE3, 0xFF, 0xE4, 0xFF, 0xE5, 0xFF,
+	0xE6, 0xFF, 0xE7, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0xEA, 0xFF, 0xEB, 0xFF,
+	0xEC, 0xFF, 0xED, 0xFF, 0xEE, 0xFF, 0xEF, 0xFF, 0xF0, 0xFF, 0xF1, 0xFF,
+	0xF2, 0xFF, 0xF3, 0xFF, 0xF4, 0xFF, 0xF5, 0xFF, 0xF6, 0xFF, 0xF7, 0xFF,
+	0xF8, 0xFF, 0xF9, 0xFF, 0xFA, 0xFF, 0xFB, 0xFF, 0xFC, 0xFF, 0xFD, 0xFF,
+	0xFE, 0xFF, 0xFF, 0xFF
+};
+
+int exfat_create_upcase_table(struct exfat_blk_dev *bd)
+{
+	int nbytes;
+
+	lseek(bd->dev_fd, finfo.ut_byte_off, SEEK_SET);
+	nbytes = write(bd->dev_fd, upcase_table, EXFAT_UPCASE_TABLE_SIZE);
+	if (nbytes != EXFAT_UPCASE_TABLE_SIZE)
+		return -1;
+
+	return 0;
+}
diff --git a/tests/2tb_disk/exfat.img.tar.xz b/tests/2tb_disk/exfat.img.tar.xz
new file mode 100644
index 0000000..f979bde
--- /dev/null
+++ b/tests/2tb_disk/exfat.img.tar.xz
Binary files differ
diff --git a/tests/bs_bad_csum/exfat.img.tar.xz b/tests/bs_bad_csum/exfat.img.tar.xz
new file mode 100644
index 0000000..cbcc5c0
--- /dev/null
+++ b/tests/bs_bad_csum/exfat.img.tar.xz
Binary files differ
diff --git a/tests/de_bad_csum/exfat.img.tar.xz b/tests/de_bad_csum/exfat.img.tar.xz
new file mode 100644
index 0000000..f753e82
--- /dev/null
+++ b/tests/de_bad_csum/exfat.img.tar.xz
Binary files differ
diff --git a/tests/file_invalid_clus/exfat.img.expected.xz b/tests/file_invalid_clus/exfat.img.expected.xz
new file mode 100644
index 0000000..08e992e
--- /dev/null
+++ b/tests/file_invalid_clus/exfat.img.expected.xz
Binary files differ
diff --git a/tests/file_invalid_clus/exfat.img.tar.xz b/tests/file_invalid_clus/exfat.img.tar.xz
new file mode 100644
index 0000000..830edf2
--- /dev/null
+++ b/tests/file_invalid_clus/exfat.img.tar.xz
Binary files differ
diff --git a/tests/large_file_invalid_clus/exfat.img.expected.xz b/tests/large_file_invalid_clus/exfat.img.expected.xz
new file mode 100644
index 0000000..b31e710
--- /dev/null
+++ b/tests/large_file_invalid_clus/exfat.img.expected.xz
Binary files differ
diff --git a/tests/large_file_invalid_clus/exfat.img.tar.xz b/tests/large_file_invalid_clus/exfat.img.tar.xz
new file mode 100644
index 0000000..5e3dbef
--- /dev/null
+++ b/tests/large_file_invalid_clus/exfat.img.tar.xz
Binary files differ
diff --git a/tests/test_fsck.sh b/tests/test_fsck.sh
new file mode 100755
index 0000000..3a59d7f
--- /dev/null
+++ b/tests/test_fsck.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+TESTCASE_DIR=$1
+IMAGE_FILE=exfat.img
+FSCK_PROG=../build/sbin/fsck.exfat
+FSCK_OPTS=-y
+PASS_COUNT=0
+
+function cleanup {
+	echo ""
+	echo "Passed ${PASS_COUNT} of ${TEST_COUNT}"
+	exit
+}
+
+if [ $# -eq 0 ]; then
+	TESTCASE_DIRS=`find -mindepth 1 -maxdepth 1 -type d`
+	TEST_COUNT=`find -mindepth 1 -maxdepth 1 -type d | wc -l`
+else
+	TESTCASE_DIRS=$@
+	TEST_COUNT=$#
+fi
+
+for TESTCASE_DIR in $TESTCASE_DIRS
+do
+	if [ ! -e ${TESTCASE_DIR}/${IMAGE_FILE}.tar.xz ]; then
+		TEST_COUNT=$((TEST_COUNT - 1))
+		continue
+	fi
+
+	echo "Running $TESTCASE_DIR"
+	echo "-----------------------------------"
+
+	# Set up image file as loop device
+	tar -C . -xf $TESTCASE_DIR/$IMAGE_FILE.tar.xz
+	losetup -f $IMAGE_FILE
+	DEV_FILE=`losetup -j $IMAGE_FILE | awk '{print $1}' | sed 's/://g'`
+
+	# Run fsck for repair
+	$FSCK_PROG $FSCK_OPTS $DEV_FILE
+	if [ "$?" -ne "1" ]; then
+		echo ""
+		echo "Failed to repair $TESTCASE_DIR"
+		losetup -d $DEV_FILE
+		cleanup
+	fi
+
+	echo ""
+	# Run fsck again
+	$FSCK_PROG -n $DEV_FILE
+	if [ "$?" -ne "0" ]; then
+		echo ""
+		echo "Failed, corrupted $TESTCASE_DIR"
+		losetup -d $DEV_FILE
+		cleanup
+	fi
+
+	if [  -e "$TESTCASE_DIR/exfat.img.expected.xz" ]; then
+		EXPECTED_FILE=$IMAGE_FILE.expected
+		unxz -cfk "$TESTCASE_DIR/$EXPECTED_FILE.xz" > $EXPECTED_FILE
+		diff <(xxd $IMAGE_FILE) <(xxd $EXPECTED_FILE)
+		if [ "$?" -ne "0" ]; then
+			echo ""
+			echo "Failed $TESTCASE_DIR"
+			losetup -d $DEV_FILE
+			cleanup
+		fi
+	fi
+
+	echo ""
+	echo "Passed $TESTCASE_DIR"
+	PASS_COUNT=$((PASS_COUNT + 1))
+
+	losetup -d $DEV_FILE
+done
+cleanup
diff --git a/tune/Android.bp b/tune/Android.bp
new file mode 100644
index 0000000..0e82d10
--- /dev/null
+++ b/tune/Android.bp
@@ -0,0 +1,20 @@
+// Copyright 2020 The Android Open Source Project
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_exfatprogs_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-GPL-2.0
+    default_applicable_licenses: ["external_exfatprogs_license"],
+}
+
+cc_binary {
+    name: "tune.exfat",
+
+    srcs: [
+        "tune.c",
+    ],
+    defaults: ["exfatprogs-defaults"],
+    static_libs: ["libexfat"],
+}
diff --git a/tune/Makefile.am b/tune/Makefile.am
new file mode 100644
index 0000000..1b3740b
--- /dev/null
+++ b/tune/Makefile.am
@@ -0,0 +1,6 @@
+AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+tune_exfat_LDADD = $(top_builddir)/lib/libexfat.a
+
+sbin_PROGRAMS = tune.exfat
+
+tune_exfat_SOURCES = tune.c
diff --git a/tune/tune.c b/tune/tune.c
new file mode 100644
index 0000000..ab04e69
--- /dev/null
+++ b/tune/tune.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *   Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <locale.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: tune.exfat\n");
+	fprintf(stderr, "\t-l | --print-label                    Print volume label\n");
+	fprintf(stderr, "\t-L | --set-label=label                Set volume label\n");
+	fprintf(stderr, "\t-i | --print-serial                   Print volume serial\n");
+	fprintf(stderr, "\t-I | --set-serial=value               Set volume serial\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");
+
+	exit(EXIT_FAILURE);
+}
+
+static struct option opts[] = {
+	{"print-label",		no_argument,		NULL,	'l' },
+	{"set-label",		required_argument,	NULL,	'L' },
+	{"print-serial",	no_argument,		NULL,	'i' },
+	{"set-serial",		required_argument,	NULL,	'I' },
+	{"version",		no_argument,		NULL,	'V' },
+	{"verbose",		no_argument,		NULL,	'v' },
+	{"help",		no_argument,		NULL,	'h' },
+	{"?",			no_argument,		NULL,	'?' },
+	{NULL,			0,			NULL,	 0  }
+};
+
+int main(int argc, char *argv[])
+{
+	int c;
+	int ret = EXIT_FAILURE;
+	struct exfat_blk_dev bd;
+	struct exfat_user_input ui;
+	bool version_only = false;
+	int flags = 0;
+	char label_input[VOLUME_LABEL_BUFFER_SIZE];
+	off_t root_clu_off;
+
+	init_user_input(&ui);
+
+	if (!setlocale(LC_CTYPE, ""))
+		exfat_err("failed to init locale/codeset\n");
+
+	opterr = 0;
+	while ((c = getopt_long(argc, argv, "I:iL:lVvh", opts, NULL)) != EOF)
+		switch (c) {
+		case 'l':
+			flags = EXFAT_GET_VOLUME_LABEL;
+			break;
+		case 'L':
+			snprintf(label_input, sizeof(label_input), "%s",
+					optarg);
+			flags = EXFAT_SET_VOLUME_LABEL;
+			break;
+		case 'i':
+			flags = EXFAT_GET_VOLUME_SERIAL;
+			break;
+		case 'I':
+			ui.volume_serial = strtoul(optarg, NULL, 0);
+			flags = EXFAT_SET_VOLUME_SERIAL;
+			break;
+		case 'V':
+			version_only = true;
+			break;
+		case 'v':
+			print_level = EXFAT_DEBUG;
+			break;
+		case '?':
+		case 'h':
+		default:
+			usage();
+	}
+
+	show_version();
+	if (version_only)
+		exit(EXIT_FAILURE);
+
+	if (argc < 3)
+		usage();
+
+	memset(ui.dev_name, 0, sizeof(ui.dev_name));
+	snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", argv[argc - 1]);
+
+	ret = exfat_get_blk_dev_info(&ui, &bd);
+	if (ret < 0)
+		goto out;
+
+	/* Mode to change or display volume serial */
+	if (flags == EXFAT_GET_VOLUME_SERIAL) {
+		ret = exfat_show_volume_serial(&bd, &ui);
+		goto close_fd_out;
+	} else if (flags == EXFAT_SET_VOLUME_SERIAL) {
+		ret = exfat_set_volume_serial(&bd, &ui);
+		goto close_fd_out;
+	}
+
+	root_clu_off = exfat_get_root_entry_offset(&bd);
+	if (root_clu_off < 0)
+		goto close_fd_out;
+
+	if (flags == EXFAT_GET_VOLUME_LABEL)
+		ret = exfat_show_volume_label(&bd, root_clu_off);
+	else if (flags == EXFAT_SET_VOLUME_LABEL)
+		ret = exfat_set_volume_label(&bd, label_input, root_clu_off);
+close_fd_out:
+	close(bd.dev_fd);
+out:
+	return ret;
+}