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;
+}