diff --git a/fs/exfat/.gitignore b/fs/exfat/.gitignore new file mode 100644 index 000000000000..810b0062282a --- /dev/null +++ b/fs/exfat/.gitignore @@ -0,0 +1,142 @@ +# +# NOTE! Don't add files that are generated in specific +# subdirectories here. Add them in the ".gitignore" file +# in that subdirectory instead. +# +# NOTE! Please use 'git ls-files -i --exclude-standard' +# command after changing this file, to see if there are +# any tracked files which get ignored after the change. +# +# Normal rules (sorted alphabetically) +# +.* +*.a +*.asn1.[ch] +*.bin +*.bz2 +*.c.[012]*.* +*.dt.yaml +*.dtb +*.dtb.S +*.dwo +*.elf +*.gcno +*.gz +*.i +*.ko +*.lex.c +*.ll +*.lst +*.lz4 +*.lzma +*.lzo +*.mod +*.mod.c +*.o +*.o.* +*.order +*.patch +*.s +*.so +*.so.dbg +*.su +*.symtypes +*.tab.[ch] +*.tar +*.xz +Module.symvers +modules.builtin + +# +# Top-level generic files +# +/tags +/TAGS +/linux +/vmlinux +/vmlinux.32 +/vmlinux-gdb.py +/vmlinuz +/System.map +/Module.markers +/modules.builtin.modinfo + +# +# RPM spec file (make rpm-pkg) +# +/*.spec + +# +# Snap directory (make snap-pkg) +# +/snap/ + +# +# tar directory (make tar*-pkg) +# +/tar-install/ + +# +# We don't want to ignore the following even if they are dot-files +# +!.clang-format +!.cocciconfig +!.get_maintainer.ignore +!.gitattributes +!.gitignore +!.mailmap + +# +# Generated include files +# +/include/config/ +/include/generated/ +/include/ksym/ +/arch/*/include/generated/ + +# stgit generated dirs +patches-* + +# quilt's files +patches +series + +# cscope files +cscope.* +ncscope.* + +# gnu global files +GPATH +GRTAGS +GSYMS +GTAGS + +# id-utils files +ID + +*.orig +*~ +\#*# + +# +# Leavings from module signing +# +extra_certificates +signing_key.pem +signing_key.priv +signing_key.x509 +x509.genkey + +# Kconfig presets +/all.config +/alldef.config +/allmod.config +/allno.config +/allrandom.config +/allyes.config + +# Kdevelop4 +*.kdev4 + +# Clang's compilation database file +/compile_commands.json diff --git a/fs/exfat/Kconfig b/fs/exfat/Kconfig new file mode 100644 index 000000000000..27e4d3330ba8 --- /dev/null +++ b/fs/exfat/Kconfig @@ -0,0 +1,66 @@ +config EXFAT_FS + tristate "exFAT fs support" + depends on BLOCK + select NLS + select NLS_UTF8 + select NLS_CODEPAGE_437 + select NLS_ISO8859_1 + help + If you want to use the exFAT file system, then you must say Y or M + here to inlucde exFAT support. exFAT supports winnt short-name rule. + (winnt: emulate the Windows NT rule for display/create.) + + To compile this as a module, choose M here: the module will be called + exfat. + +if EXFAT_FS +config EXFAT_DEFAULT_CODEPAGE + int "Default codepage for exFAT" + default 437 + help + This option should be set to the codepage of your exFAT filesystems. + +config EXFAT_DEFAULT_IOCHARSET + string "Default iocharset for exFAT" + default "utf8" + help + Set this to the default input/output character set you'd + like exFAT to use. It should probably match the character set + that most of your exFAT filesystems use, and can be overridden + with the "iocharset" mount option for exFAT filesystems. + +config EXFAT_VIRTUAL_XATTR + bool "Virtual xattr support for exFAT" + default y + help + To support virtual xattr. + +config EXFAT_VIRTUAL_XATTR_SELINUX_LABEL + string "Default string for SELinux label" + default "u:object_r:exfat:s0" + depends on EXFAT_VIRTUAL_XATTR + help + Set this to the default string for SELinux label. + Support for "u:object_r:exfat:s0" was added in Android Pie, + if you're running Oreo or lower, use "u:object_r:vfat:s0" instead. + +config EXFAT_DEBUG + bool "enable debug features" + +if EXFAT_DEBUG + +config EXFAT_DBG_MSG + bool "enable debug messages" + +config EXFAT_DBG_BUGON + bool "enable strict BUG_ON() for debugging" + +config EXFAT_DBG_WARNON + bool "enable strict WARN_ON() for debugging" + +endif # if EXFAT_DEBUG + +config EXFAT_UEVENT + bool "enable uevent" + +endif # if EXFAT_FS diff --git a/fs/exfat/LICENSE b/fs/exfat/LICENSE new file mode 100644 index 000000000000..ff0812fd89cc --- /dev/null +++ b/fs/exfat/LICENSE @@ -0,0 +1,359 @@ +Valid-License-Identifier: GPL-2.0 +Valid-License-Identifier: GPL-2.0-only +Valid-License-Identifier: GPL-2.0+ +Valid-License-Identifier: GPL-2.0-or-later +SPDX-URL: https://spdx.org/licenses/GPL-2.0.html +Usage-Guide: + To use this license in source code, put one of the following SPDX + tag/value pairs into a comment according to the placement + guidelines in the licensing rules documentation. + For 'GNU General Public License (GPL) version 2 only' use: + SPDX-License-Identifier: GPL-2.0 + or + SPDX-License-Identifier: GPL-2.0-only + For 'GNU General Public License (GPL) version 2 or any later version' use: + SPDX-License-Identifier: GPL-2.0+ + or + SPDX-License-Identifier: GPL-2.0-or-later +License-Text: + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, 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 Library 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. + + + Copyright (C) + + 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 St, 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. + + , 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 Library General +Public License instead of this License. diff --git a/fs/exfat/Makefile b/fs/exfat/Makefile new file mode 100644 index 000000000000..4e7c7c27d770 --- /dev/null +++ b/fs/exfat/Makefile @@ -0,0 +1,42 @@ +# +# Makefile for the linux FAT12/16/32(VFAT)/64(exFAT) filesystem driver. +# + +ifneq ($(KERNELRELEASE),) +# Called from inline kernel build +# DKMS_DEFINE +obj-$(CONFIG_EXFAT_FS) += exfat.o + +exfat-objs := super.o core.o core_exfat.o blkdev.o fatent.o cache.o \ + nls.o misc.o extent.o xattr.o +else +# Called from external kernel module build + +KERNELRELEASE ?= $(shell uname -r) +KDIR ?= /lib/modules/${KERNELRELEASE}/build +MDIR ?= /lib/modules/${KERNELRELEASE} +PWD := $(shell pwd) + +export CONFIG_EXFAT_FS := m + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + +help: + $(MAKE) -C $(KDIR) M=$(PWD) help + +install: exfat.ko + rm -f ${MDIR}/kernel/fs/exfat/exfat.ko + install -m644 -b -D exfat.ko ${MDIR}/kernel/fs/exfat/exfat.ko + depmod -aq + +uninstall: + rm -rf ${MDIR}/kernel/fs/exfat + depmod -aq + +endif + +.PHONY : all clean install uninstall diff --git a/fs/exfat/README.md b/fs/exfat/README.md new file mode 100644 index 000000000000..ee6929e36ebd --- /dev/null +++ b/fs/exfat/README.md @@ -0,0 +1,202 @@ +# exfat-linux + +This __exFAT filesystem module for Linux kernel__ is based on `sdFAT` drivers by Samsung, which is used with their smartphone lineups. + +The main objective of **exfat-linux** is to provide the best generic kernel drivers for exFAT. That means Samsung-specific modifications such as fat12/16/32 handlings, defrag and etc has been removed to make the code portable. + +This project can be used for everyday Linux users by simply doing `make && make install`. Ubuntu users can simply add a PPA and start using it, without even downloading the code. This can also be directly dropped-in to an existing Linux kernel source for building the filesystem drivers inline, which should be useful for Android kernel developers. + +--------------------------------------- + +[exfat-nofuse]'s development has been stale for more than a year with no clear maintainership. It's also been lacking critical upstream(*which is Samsung*) changes. + +[exfat-nofuse]: https://github.com/dorimanx/exfat-nofuse + +**exfat-linux** is: + + * Based on a totally different and newer base provided by Samsung + * Intended to keep upstream changes constantly merged + * Intended to fix breakage from newer kernels as-soon-as-possible + +**exfat-linux** has been tested with all major LTS kernels ranging from 3.4 to 4.19 and the ones Canonical uses for Ubuntu: `3.4`, `3.10`, `3.18`, `4.1`, `4.4`, `4.9`, `4.14`, `4.19` and `4.15`, `5.0`, `5.2`, and `5.3-rc`. + +It's also been tested with `x86(i386)`, `x86_64(amd64)`, `arm32(AArch32)` and `arm64(AArch64)`. + +## Disclaimer + +#### ● Original authorship and copyright: Samsung + +#### ● Maintainer of exfat-linux: Park Ju Hyung([arter97](https://twitter.com/arter97)) + +## Using exfat-linux + +### ● Ubuntu PPA + +If you're an Ubuntu user, you can simply add a [PPA repository](https://launchpad.net/~arter97/+archive/ubuntu/exfat-linux) and start using the exFAT module. + +Ubuntu will handle upgrades automatically as well. + +1. Add the exfat-linux repository + + ``` + sudo add-apt-repository ppa:arter97/exfat-linux + sudo apt update + ``` + +2. Install the module + + `sudo apt install exfat-dkms` + +This will use DKMS(Dynamic Kernel Module Support) and automatically build exFAT module for your current Ubuntu installation. + +### ● Manually installing the module + +1. Download the code + + ``` + git clone https://github.com/arter97/exfat-linux + cd exfat-linux + ``` + +2. Build + + `make` + +3. Install + + `sudo make install` + +This will install the module to your __currently running kernel__. + +4. And finally load + + `sudo modprobe exfat` + +If you upgrade the kernel, you'll have to repeat this process. + +If you want to update **exfat-linux** to the latest version, you'll have to repeat this process. + +### ● Merging the drivers to existing Linux kernel source + +If you're using `git`, using `git subtree` or `git submodule` is highly recommended. + +1. Add this repository to `fs/exfat` + +2. Modify `fs/Kconfig` + +``` + menu "DOS/FAT/NT Filesystems" + + source "fs/fat/Kconfig" ++source "fs/exfat/Kconfig" + source "fs/ntfs/Kconfig" + endmenu +``` + +3. Modify `fs/Makefile` + +``` + obj-$(CONFIG_FAT_FS) += fat/ ++obj-$(CONFIG_EXFAT_FS) += exfat/ + obj-$(CONFIG_BFS_FS) += bfs/ +``` + +And you're good to go! + +## Benchmarks + +For reference, existing exFAT implementations were tested and compared on a server running Ubuntu 16.04 with Linux kernel 4.14 under a contained virtual machine. + +Linux 4.14 was used as higher LTS kernels don't work with [exfat-nofuse] at the time of testing. + +### ● Ramdisk + +#### fio sequential I/O + +| Implementation | Base | Read | Write | +| --------------- | ------ | ------------ | ------------ | +| **exfat-linux** | 2.2.0 | 7042 MB/s | 2173 MB/s | +| [exfat-nofuse] | 1.2.9 | 6849 MB/s | 1961 MB/s | +| [exfat-fuse] | N/A | 3097 MB/s | 1710 MB/s | +| ext4 | N/A | 7352 MB/s | 3333 MB/s | + +#### fio random I/O + +| Implementation | Base | Read | Write | +| --------------- | ------ | ------------ | ------------ | +| **exfat-linux** | 2.2.0 | 760 MB/s | 2222 MB/s | +| [exfat-nofuse] | 1.2.9 | 760 MB/s | 2160 MB/s | +| [exfat-fuse] | N/A | 1.7 MB/s | 1.6 MB/s | +| ext4 | N/A | 747 MB/s | 2816 MB/s | + +### ● NVMe device + +#### fio sequential I/O + +| Implementation | Base | Read | Write | +| --------------- | ------ | ------------ | ------------ | +| **exfat-linux** | 2.2.0 | 1283 MB/s | 1832 MB/s | +| [exfat-nofuse] | 1.2.9 | 1285 MB/s | 1678 MB/s | +| [exfat-fuse] | N/A | 751 MB/s | 1464 MB/s | +| ext4 | N/A | 1283 MB/s | 3356 MB/s | + +#### fio random I/O + +| Implementation | Base | Read | Write | +| --------------- | ------ | ------------ | ------------ | +| **exfat-linux** | 2.2.0 | 26 MB/s | 1885 MB/s | +| [exfat-nofuse] | 1.2.9 | 24 MB/s | 1827 MB/s | +| [exfat-fuse] | N/A | 1.6 MB/s | 1.6 MB/s | +| ext4 | N/A | 29 MB/s | 2821 MB/s | + +[exfat-fuse]: https://github.com/relan/exfat + +## Mount options + +* uid +* gid +* umask +* dmask +* fmask +* allow_utime +* codepage +* iocharset +* quiet +* utf8 +* tz + + * Please refer to the [vfat](https://github.com/torvalds/linux/blob/master/Documentation/filesystems/vfat.txt)'s documentation. + +* namecase + + * Passing `namecase=1` as a mount option will make exFAT operate in a case-sensitive mode. + + * Default is insensitive mode. + +* symlink + + * Allow a symlink to be created under exFAT. + +* errors=continue + + * Keep going on a filesystem error. + +* errors=panic + + * Panic and halt the machine if an error occurs. + +* errors=remount-ro + + * Remount the filesystem read-only on an error. + +* discard + + * Enable the use of discard/TRIM commands to ensure flash storage doesn't run out of free blocks. This option may introduce latency penalty on file removal operations. + +* delayed_meta + + * Delay flushing metadata, hence improving performance. + + * This is enabled by default, please pass `nodelayed_meta` to disable it. + +## Enjoy! diff --git a/fs/exfat/api.h b/fs/exfat/api.h new file mode 100644 index 000000000000..5b13c0a1aab1 --- /dev/null +++ b/fs/exfat/api.h @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + */ + +#ifndef _EXFAT_API_H +#define _EXFAT_API_H + +#include "config.h" +#include "exfat_fs.h" + +/* cache size (in number of sectors) */ +/* (should be an exponential value of 2) */ +#define FAT_CACHE_SIZE 128 +#define FAT_CACHE_HASH_SIZE 64 +#define BUF_CACHE_SIZE 256 +#define BUF_CACHE_HASH_SIZE 64 + +/* Read-ahead related */ +/* First config vars. should be pow of 2 */ +#define FCACHE_MAX_RA_SIZE (PAGE_SIZE) +#define DCACHE_MAX_RA_SIZE (128*1024) + +/* type values */ +#define TYPE_UNUSED 0x0000 +#define TYPE_DELETED 0x0001 +#define TYPE_INVALID 0x0002 +#define TYPE_CRITICAL_PRI 0x0100 +#define TYPE_BITMAP 0x0101 +#define TYPE_UPCASE 0x0102 +#define TYPE_VOLUME 0x0103 +#define TYPE_DIR 0x0104 +#define TYPE_FILE 0x011F +#define TYPE_SYMLINK 0x015F +#define TYPE_CRITICAL_SEC 0x0200 +#define TYPE_STREAM 0x0201 +#define TYPE_EXTEND 0x0202 +#define TYPE_ACL 0x0203 +#define TYPE_BENIGN_PRI 0x0400 +#define TYPE_GUID 0x0401 +#define TYPE_PADDING 0x0402 +#define TYPE_ACLTAB 0x0403 +#define TYPE_BENIGN_SEC 0x0800 +#define TYPE_ALL 0x0FFF + +/* eio values */ +#define EXFAT_EIO_NONE (0x00000000) +#define EXFAT_EIO_READ (0x00000001) +#define EXFAT_EIO_WRITE (0x00000002) +#define EXFAT_EIO_BDI (0x00000004) + +/* modes for volume allocation unit status */ +#define VOL_AU_STAT_TOTAL (0) +#define VOL_AU_STAT_CLEAN (1) +#define VOL_AU_STAT_FULL (2) + +/* DOS name structure */ +typedef struct { + u8 name[DOS_NAME_LENGTH]; + u8 name_case; +} DOS_NAME_T; + +/* unicode name structure */ +typedef struct { + u16 name[MAX_NAME_LENGTH+3]; /* +3 for null and for converting */ + u16 name_hash; + u8 name_len; +} UNI_NAME_T; + +/* should be merged it to DATE_TIME_T */ +typedef struct { + u16 sec; /* 0 ~ 59 */ + u16 min; /* 0 ~ 59 */ + u16 hour; /* 0 ~ 23 */ + u16 day; /* 1 ~ 31 */ + u16 mon; /* 1 ~ 12 */ + u16 year; /* 0 ~ 127 (since 1980) */ +} TIMESTAMP_T; + + +typedef struct { + u16 Year; + u16 Month; + u16 Day; + u16 Hour; + u16 Minute; + u16 Second; + u16 MilliSecond; +} DATE_TIME_T; + +typedef struct { + u64 Offset; // start sector number of the partition + u64 Size; // in sectors +} PART_INFO_T; + +typedef struct { + u32 SecSize; // sector size in bytes + u64 DevSize; // block device size in sectors +} DEV_INFO_T; + +typedef struct { + u32 ClusterSize; + u32 NumClusters; + u32 FreeClusters; + u32 UsedClusters; +} VOL_INFO_T; + +/* directory structure */ +typedef struct { + u32 dir; + u32 size; + u8 flags; +} CHAIN_T; + +/* hint structure */ +typedef struct { + u32 clu; + union { + u32 off; // cluster offset + s32 eidx; // entry index + }; +} HINT_T; + +typedef struct { + spinlock_t cache_lru_lock; + struct list_head cache_lru; + s32 nr_caches; + u32 cache_valid_id; // for avoiding the race between alloc and free +} EXTENT_T; + +/* first empty entry hint information */ +typedef struct { + s32 eidx; // entry index of a directory + s32 count; // count of continuous empty entry + CHAIN_T cur; // the cluster that first empty slot exists in +} HINT_FEMP_T; + +/* file id structure */ +typedef struct { + CHAIN_T dir; + s32 entry; + u32 type; + u32 attr; + u32 start_clu; + u64 size; + u8 flags; + u8 reserved[3]; // padding + u32 version; // the copy of low 32bit of i_version to check the validation of hint_stat + s64 rwoffset; // file offset or dentry index for readdir + EXTENT_T extent; // extent cache for a file + HINT_T hint_bmap; // hint for cluster last accessed + HINT_T hint_stat; // hint for entry index we try to lookup next time + HINT_FEMP_T hint_femp; // hint for first empty entry +} FILE_ID_T; + +typedef struct { + s8 *lfn; + s8 *sfn; + s32 lfnbuf_len; //usally MAX_UNINAME_BUF_SIZE + s32 sfnbuf_len; //usally MAX_DOSNAME_BUF_SIZE, used only for vfat, not for exfat +} DENTRY_NAMEBUF_T; + +typedef struct { + u32 Attr; + u64 Size; + u32 NumSubdirs; + DATE_TIME_T CreateTimestamp; + DATE_TIME_T ModifyTimestamp; + DATE_TIME_T AccessTimestamp; + DENTRY_NAMEBUF_T NameBuf; +} DIR_ENTRY_T; + +/* cache information */ +typedef struct __cache_entry { + struct __cache_entry *next; + struct __cache_entry *prev; + struct { + struct __cache_entry *next; + struct __cache_entry *prev; + } hash; + u64 sec; + u32 flag; + struct buffer_head *bh; +} cache_ent_t; + +typedef struct { + s32 (*alloc_cluster)(struct super_block *, u32, CHAIN_T *, s32); + s32 (*free_cluster)(struct super_block *, CHAIN_T *, s32); + s32 (*count_used_clusters)(struct super_block *, u32 *); + s32 (*init_dir_entry)(struct super_block *, CHAIN_T *, s32, u32, u32, u64); + s32 (*init_ext_entry)(struct super_block *, CHAIN_T *, s32, s32, UNI_NAME_T *, DOS_NAME_T *); + s32 (*find_dir_entry)(struct super_block *, FILE_ID_T *, CHAIN_T *, UNI_NAME_T *, s32, DOS_NAME_T *, u32); + s32 (*delete_dir_entry)(struct super_block *, CHAIN_T *, s32, s32, s32); + void (*get_uniname_from_ext_entry)(struct super_block *, CHAIN_T *, s32, u16 *); + s32 (*count_ext_entries)(struct super_block *, CHAIN_T *, s32, DENTRY_T *); + s32 (*calc_num_entries)(UNI_NAME_T *); + s32 (*check_max_dentries)(FILE_ID_T *); + u32 (*get_entry_type)(DENTRY_T *); + void (*set_entry_type)(DENTRY_T *, u32); + u32 (*get_entry_attr)(DENTRY_T *); + void (*set_entry_attr)(DENTRY_T *, u32); + u8 (*get_entry_flag)(DENTRY_T *); + void (*set_entry_flag)(DENTRY_T *, u8); + u32 (*get_entry_clu0)(DENTRY_T *); + void (*set_entry_clu0)(DENTRY_T *, u32); + u64 (*get_entry_size)(DENTRY_T *); + void (*set_entry_size)(DENTRY_T *, u64); + void (*get_entry_time)(DENTRY_T *, TIMESTAMP_T *, u8); + void (*set_entry_time)(DENTRY_T *, TIMESTAMP_T *, u8); + u32 (*get_au_stat)(struct super_block *, s32); +} FS_FUNC_T; + +typedef struct __FS_INFO_T { + s32 bd_opened; // opened or not + u32 vol_id; // volume serial number + u64 num_sectors; // num of sectors in volume + u32 num_clusters; // num of clusters in volume + u32 cluster_size; // cluster size in bytes + u32 cluster_size_bits; + u32 sect_per_clus; // cluster size in sectors + u32 sect_per_clus_bits; + u64 FAT1_start_sector; // FAT1 start sector + u64 FAT2_start_sector; // FAT2 start sector + u64 root_start_sector; // root dir start sector + u64 data_start_sector; // data area start sector + u32 num_FAT_sectors; // num of FAT sectors + u32 root_dir; // root dir cluster + u32 dentries_in_root; // num of dentries in root dir + u32 dentries_per_clu; // num of dentries per cluster + u32 vol_flag; // volume dirty flag + struct buffer_head *pbr_bh; // buffer_head of PBR sector + + u32 map_clu; // allocation bitmap start cluster + u32 map_sectors; // num of allocation bitmap sectors + struct buffer_head **vol_amap; // allocation bitmap + + u16 **vol_utbl; // upcase table + + u32 clu_srch_ptr; // cluster search pointer + u32 used_clusters; // number of used clusters + + u32 prev_eio; // block device operation error flag + + FS_FUNC_T *fs_func; + + s32 reserved_clusters; // # of reserved clusters (DA) + void *amap; // AU Allocation Map + + /* fat cache */ + struct { + cache_ent_t pool[FAT_CACHE_SIZE]; + cache_ent_t lru_list; + cache_ent_t hash_list[FAT_CACHE_HASH_SIZE]; + } fcache; + + /* meta cache */ + struct { + cache_ent_t pool[BUF_CACHE_SIZE]; + cache_ent_t lru_list; + cache_ent_t keep_list; // CACHEs in this list will not be kicked by normal lru operations + cache_ent_t hash_list[BUF_CACHE_HASH_SIZE]; + } dcache; +} FS_INFO_T; + +#endif /* _EXFAT_API_H */ diff --git a/fs/exfat/blkdev.c b/fs/exfat/blkdev.c new file mode 100644 index 000000000000..fe3a085eab13 --- /dev/null +++ b/fs/exfat/blkdev.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * blkdev.c: exFAT Block Device Driver Glue Layer + */ + +#include +#include +#include +#include "exfat.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) */ +static struct backing_dev_info *inode_to_bdi(struct inode *bd_inode) +{ + return bd_inode->i_mapping->backing_dev_info; +} +#endif + +s32 exfat_bdev_open_dev(struct super_block *sb) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (fsi->bd_opened) + return 0; + + fsi->bd_opened = true; + return 0; +} + +s32 exfat_bdev_close_dev(struct super_block *sb) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + fsi->bd_opened = false; + return 0; +} + +static inline s32 block_device_ejected(struct super_block *sb) +{ + struct inode *bd_inode = sb->s_bdev->bd_inode; + struct backing_dev_info *bdi = inode_to_bdi(bd_inode); + + return (bdi->dev == NULL); +} + +s32 exfat_bdev_check_bdi_valid(struct super_block *sb) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (block_device_ejected(sb)) { + if (!(fsi->prev_eio & EXFAT_EIO_BDI)) { + fsi->prev_eio |= EXFAT_EIO_BDI; + exfat_log_msg(sb, KERN_ERR, "%s: block device is " + "eliminated.(bdi:%p)", __func__, sb->s_bdi); + exfat_debug_warn_on(1); + } + return -ENXIO; + } + + return 0; +} + + +/* Make a readahead request */ +s32 exfat_bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 sects_per_page = (PAGE_SIZE >> sb->s_blocksize_bits); + struct blk_plug plug; + u64 i; + + if (!fsi->bd_opened) + return -EIO; + + blk_start_plug(&plug); + for (i = 0; i < num_secs; i++) { + if (i && !(i & (sects_per_page - 1))) { +#ifdef MODULE + /* TODO: fix this by using proper APIs */ + blk_finish_plug(&plug); + blk_start_plug(&plug); +#else + blk_flush_plug(current); +#endif + } + sb_breadahead(sb, (sector_t)(secno + i)); + } + blk_finish_plug(&plug); + + return 0; +} + +s32 exfat_bdev_mread(struct super_block *sb, u64 secno, struct buffer_head **bh, u64 num_secs, s32 read) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u8 blksize_bits = sb->s_blocksize_bits; + + if (!fsi->bd_opened) + return -EIO; + + brelse(*bh); + + if (read) + *bh = __bread(sb->s_bdev, (sector_t)secno, num_secs << blksize_bits); + else + *bh = __getblk(sb->s_bdev, (sector_t)secno, num_secs << blksize_bits); + + /* read successfully */ + if (*bh) + return 0; + + /* + * patch 1.2.4 : reset ONCE warning message per volume. + */ + if (!(fsi->prev_eio & EXFAT_EIO_READ)) { + fsi->prev_eio |= EXFAT_EIO_READ; + exfat_log_msg(sb, KERN_ERR, "%s: No bh. I/O error.", __func__); + exfat_debug_warn_on(1); + } + + return -EIO; +} + +s32 exfat_bdev_mwrite(struct super_block *sb, u64 secno, struct buffer_head *bh, u64 num_secs, s32 sync) +{ + u64 count; + struct buffer_head *bh2; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (!fsi->bd_opened) + return -EIO; + + if (secno == bh->b_blocknr) { + set_buffer_uptodate(bh); + mark_buffer_dirty(bh); + if (sync && (sync_dirty_buffer(bh) != 0)) + return -EIO; + } else { + count = num_secs << sb->s_blocksize_bits; + + bh2 = __getblk(sb->s_bdev, (sector_t)secno, count); + + if (!bh2) + goto no_bh; + + lock_buffer(bh2); + memcpy(bh2->b_data, bh->b_data, count); + set_buffer_uptodate(bh2); + mark_buffer_dirty(bh2); + unlock_buffer(bh2); + if (sync && (sync_dirty_buffer(bh2) != 0)) { + __brelse(bh2); + goto no_bh; + } + __brelse(bh2); + } + return 0; +no_bh: + /* + * patch 1.2.4 : reset ONCE warning message per volume. + */ + if (!(fsi->prev_eio & EXFAT_EIO_WRITE)) { + fsi->prev_eio |= EXFAT_EIO_WRITE; + exfat_log_msg(sb, KERN_ERR, "%s: No bh. I/O error.", __func__); + exfat_debug_warn_on(1); + } + + return -EIO; +} + +s32 exfat_bdev_sync_all(struct super_block *sb) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (!fsi->bd_opened) + return -EIO; + + return sync_blockdev(sb->s_bdev); +} + +/* + * Sector Read/Write Functions + */ +s32 exfat_read_sect(struct super_block *sb, u64 sec, struct buffer_head **bh, s32 read) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + BUG_ON(!bh); + if ((sec >= fsi->num_sectors) && (fsi->num_sectors > 0)) { + exfat_fs_error_ratelimit(sb, + "%s: out of range (sect:%llu)", __func__, sec); + return -EIO; + } + + if (exfat_bdev_mread(sb, sec, bh, 1, read)) { + exfat_fs_error_ratelimit(sb, + "%s: I/O error (sect:%llu)", __func__, sec); + return -EIO; + } + + return 0; +} + +s32 exfat_write_sect(struct super_block *sb, u64 sec, struct buffer_head *bh, s32 sync) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + BUG_ON(!bh); + if ((sec >= fsi->num_sectors) && (fsi->num_sectors > 0)) { + exfat_fs_error_ratelimit(sb, + "%s: out of range (sect:%llu)", __func__, sec); + return -EIO; + } + + if (exfat_bdev_mwrite(sb, sec, bh, 1, sync)) { + exfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu)", + __func__, sec); + return -EIO; + } + + return 0; +} + +static inline void __blkdev_write_bhs(struct buffer_head **bhs, s32 nr_bhs) +{ + s32 i; + + for (i = 0; i < nr_bhs; i++) + write_dirty_buffer(bhs[i], WRITE); +} + +static inline s32 __blkdev_sync_bhs(struct buffer_head **bhs, s32 nr_bhs) +{ + s32 i, err = 0; + + for (i = 0; i < nr_bhs; i++) { + wait_on_buffer(bhs[i]); + if (!err && !buffer_uptodate(bhs[i])) + err = -EIO; + } + return err; +} + +static inline s32 __buffer_zeroed(struct super_block *sb, u64 blknr, u64 num_secs) +{ + struct buffer_head *bhs[MAX_BUF_PER_PAGE]; + s32 nr_bhs = MAX_BUF_PER_PAGE; + u64 last_blknr = blknr + num_secs; + s32 err, i, n; + struct blk_plug plug; + + /* Zeroing the unused blocks on this cluster */ + n = 0; + blk_start_plug(&plug); + while (blknr < last_blknr) { + bhs[n] = sb_getblk(sb, (sector_t)blknr); + if (!bhs[n]) { + err = -ENOMEM; + blk_finish_plug(&plug); + goto error; + } + memset(bhs[n]->b_data, 0, sb->s_blocksize); + set_buffer_uptodate(bhs[n]); + mark_buffer_dirty(bhs[n]); + + n++; + blknr++; + + if (blknr == last_blknr) + break; + + if (n == nr_bhs) { + __blkdev_write_bhs(bhs, n); + + for (i = 0; i < n; i++) + brelse(bhs[i]); + n = 0; + } + } + __blkdev_write_bhs(bhs, n); + blk_finish_plug(&plug); + + err = __blkdev_sync_bhs(bhs, n); + if (err) + goto error; + + for (i = 0; i < n; i++) + brelse(bhs[i]); + + return 0; + +error: + EMSG("%s: failed zeroed sect %llu\n", __func__, blknr); + for (i = 0; i < n; i++) + bforget(bhs[i]); + + return err; +} + +s32 exfat_write_msect_zero(struct super_block *sb, u64 sec, u64 num_secs) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { + exfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", + __func__, sec, num_secs); + return -EIO; + } + + /* Just return -EAGAIN if it is failed */ + if (__buffer_zeroed(sb, sec, num_secs)) + return -EAGAIN; + + return 0; +} diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c new file mode 100644 index 000000000000..2bd94633137e --- /dev/null +++ b/fs/exfat/cache.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * cache.c: exFAT Cache Manager + */ + +#include /* for mark_page_accessed() */ +#include +#include "exfat.h" +#include "core.h" + +#define DEBUG_HASH_LIST +#define DEBUG_HASH_PREV (0xAAAA5555) +#define DEBUG_HASH_NEXT (0x5555AAAA) + +#define LOCKBIT (0x01) +#define DIRTYBIT (0x02) +#define KEEPBIT (0x04) + +static cache_ent_t *__fcache_find(struct super_block *sb, u64 sec); +static cache_ent_t *__fcache_get(struct super_block *sb); +static void __fcache_insert_hash(struct super_block *sb, cache_ent_t *bp); +static void __fcache_remove_hash(cache_ent_t *bp); + +static cache_ent_t *__dcache_find(struct super_block *sb, u64 sec); +static cache_ent_t *__dcache_get(struct super_block *sb); +static void __dcache_insert_hash(struct super_block *sb, cache_ent_t *bp); +static void __dcache_remove_hash(cache_ent_t *bp); + +static void push_to_mru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->next = list->next; + bp->prev = list; + list->next->prev = bp; + list->next = bp; +} + +static void push_to_lru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->prev = list->prev; + bp->next = list; + list->prev->next = bp; + list->prev = bp; +} + +static void move_to_mru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->prev->next = bp->next; + bp->next->prev = bp->prev; + push_to_mru(bp, list); +} + +static void move_to_lru(cache_ent_t *bp, cache_ent_t *list) +{ + bp->prev->next = bp->next; + bp->next->prev = bp->prev; + push_to_lru(bp, list); +} + +static inline s32 __check_hash_valid(cache_ent_t *bp) +{ +#ifdef DEBUG_HASH_LIST + if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) || + (bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) { + return -EINVAL; + } +#endif + if ((bp->hash.next == bp) || (bp->hash.prev == bp)) + return -EINVAL; + + return 0; +} + +static inline void __remove_from_hash(cache_ent_t *bp) +{ + (bp->hash.prev)->hash.next = bp->hash.next; + (bp->hash.next)->hash.prev = bp->hash.prev; + bp->hash.next = bp; + bp->hash.prev = bp; +#ifdef DEBUG_HASH_LIST + bp->hash.next = (cache_ent_t *)DEBUG_HASH_NEXT; + bp->hash.prev = (cache_ent_t *)DEBUG_HASH_PREV; +#endif +} + +/* + * Do FAT mirroring (don't sync) + * sec: sector No. in FAT1 + * bh: bh of sec. + */ +static inline s32 __fat_copy(struct super_block *sb, u64 sec, struct buffer_head *bh, int sync) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u64 sec2; + + if (fsi->FAT2_start_sector != fsi->FAT1_start_sector) { + sec2 = sec - fsi->FAT1_start_sector + fsi->FAT2_start_sector; + BUG_ON(sec2 != (sec + (u64)fsi->num_FAT_sectors)); + + MMSG("BD: fat mirroring (%llu in FAT1, %llu in FAT2)\n", sec, sec2); + if (exfat_write_sect(sb, sec2, bh, sync)) + return -EIO; + } + + return 0; +} + +/* + * returns 1, if bp is flushed + * returns 0, if bp is not dirty + * returns -1, if error occurs + */ +static s32 __fcache_ent_flush(struct super_block *sb, cache_ent_t *bp, u32 sync) +{ + struct exfat_sb_info *sbi; + + if (!(bp->flag & DIRTYBIT)) + return 0; + + sbi = EXFAT_SB(sb); + if (sbi->options.delayed_meta) { + // Make buffer dirty (XXX: Naive impl.) + if (exfat_write_sect(sb, bp->sec, bp->bh, 0)) + return -EIO; + + if (__fat_copy(sb, bp->sec, bp->bh, 0)) + return -EIO; + } + + bp->flag &= ~(DIRTYBIT); + + if (sync) + sync_dirty_buffer(bp->bh); + + return 1; +} + +static s32 __fcache_ent_discard(struct super_block *sb, cache_ent_t *bp) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + __fcache_remove_hash(bp); + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + move_to_lru(bp, &fsi->fcache.lru_list); + return 0; +} + +u8 *exfat_fcache_getblk(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 page_ra_count = FCACHE_MAX_RA_SIZE >> sb->s_blocksize_bits; + + bp = __fcache_find(sb, sec); + if (bp) { + if (exfat_bdev_check_bdi_valid(sb)) { + __fcache_ent_flush(sb, bp, 0); + __fcache_ent_discard(sb, bp); + return NULL; + } + move_to_mru(bp, &fsi->fcache.lru_list); + return bp->bh->b_data; + } + + bp = __fcache_get(sb); + if (!__check_hash_valid(bp)) + __fcache_remove_hash(bp); + + bp->sec = sec; + bp->flag = 0; + __fcache_insert_hash(sb, bp); + + /* Naive FAT read-ahead (increase I/O unit to page_ra_count) */ + if ((sec & (page_ra_count - 1)) == 0) + exfat_bdev_readahead(sb, sec, (u64)page_ra_count); + + /* + * patch 1.2.4 : buffer_head null pointer exception problem. + * + * When exfat_read_sect is failed, fcache should be moved to + * EMPTY hash_list and the first of lru_list. + */ + if (exfat_read_sect(sb, sec, &(bp->bh), 1)) { + __fcache_ent_discard(sb, bp); + return NULL; + } + + return bp->bh->b_data; +} + +s32 exfat_fcache_modify(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + + bp = __fcache_find(sb, sec); + if (!bp) { + exfat_fs_error(sb, "Can`t find fcache (sec 0x%016llx)", sec); + return -EIO; + } + + if (exfat_write_sect(sb, sec, bp->bh, 0)) + return -EIO; + + if (__fat_copy(sb, sec, bp->bh, 0)) + return -EIO; + + return 0; +} + +s32 exfat_meta_cache_init(struct super_block *sb) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + s32 i; + + /* LRU list */ + fsi->fcache.lru_list.next = &fsi->fcache.lru_list; + fsi->fcache.lru_list.prev = fsi->fcache.lru_list.next; + + for (i = 0; i < FAT_CACHE_SIZE; i++) { + fsi->fcache.pool[i].sec = ~0; + fsi->fcache.pool[i].flag = 0; + fsi->fcache.pool[i].bh = NULL; + fsi->fcache.pool[i].prev = NULL; + fsi->fcache.pool[i].next = NULL; + push_to_mru(&(fsi->fcache.pool[i]), &fsi->fcache.lru_list); + } + + fsi->dcache.lru_list.next = &fsi->dcache.lru_list; + fsi->dcache.lru_list.prev = fsi->dcache.lru_list.next; + fsi->dcache.keep_list.next = &fsi->dcache.keep_list; + fsi->dcache.keep_list.prev = fsi->dcache.keep_list.next; + + // Initially, all the BUF_CACHEs are in the LRU list + for (i = 0; i < BUF_CACHE_SIZE; i++) { + fsi->dcache.pool[i].sec = ~0; + fsi->dcache.pool[i].flag = 0; + fsi->dcache.pool[i].bh = NULL; + fsi->dcache.pool[i].prev = NULL; + fsi->dcache.pool[i].next = NULL; + push_to_mru(&(fsi->dcache.pool[i]), &fsi->dcache.lru_list); + } + + /* HASH list */ + for (i = 0; i < FAT_CACHE_HASH_SIZE; i++) { + fsi->fcache.hash_list[i].sec = ~0; + fsi->fcache.hash_list[i].hash.next = &(fsi->fcache.hash_list[i]); +; + fsi->fcache.hash_list[i].hash.prev = fsi->fcache.hash_list[i].hash.next; + } + + for (i = 0; i < FAT_CACHE_SIZE; i++) + __fcache_insert_hash(sb, &(fsi->fcache.pool[i])); + + for (i = 0; i < BUF_CACHE_HASH_SIZE; i++) { + fsi->dcache.hash_list[i].sec = ~0; + fsi->dcache.hash_list[i].hash.next = &(fsi->dcache.hash_list[i]); + + fsi->dcache.hash_list[i].hash.prev = fsi->dcache.hash_list[i].hash.next; + } + + for (i = 0; i < BUF_CACHE_SIZE; i++) + __dcache_insert_hash(sb, &(fsi->dcache.pool[i])); + + return 0; +} + +s32 exfat_meta_cache_shutdown(struct super_block *sb) +{ + return 0; +} + +s32 exfat_fcache_release_all(struct super_block *sb) +{ + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + s32 dirtycnt = 0; + + bp = fsi->fcache.lru_list.next; + while (bp != &fsi->fcache.lru_list) { + s32 ret_tmp = __fcache_ent_flush(sb, bp, 0); + + if (ret_tmp < 0) + ret = ret_tmp; + else + dirtycnt += ret_tmp; + + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + bp = bp->next; + } + + DMSG("BD:Release / dirty fat cache: %d (err:%d)\n", dirtycnt, ret); + return ret; +} + + +/* internal DIRTYBIT marked => bh dirty */ +s32 exfat_fcache_flush(struct super_block *sb, u32 sync) +{ + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + s32 dirtycnt = 0; + + bp = fsi->fcache.lru_list.next; + while (bp != &fsi->fcache.lru_list) { + ret = __fcache_ent_flush(sb, bp, sync); + if (ret < 0) + break; + + dirtycnt += ret; + bp = bp->next; + } + + MMSG("BD: flush / dirty fat cache: %d (err:%d)\n", dirtycnt, ret); + return ret; +} + +static cache_ent_t *__fcache_find(struct super_block *sb, u64 sec) +{ + s32 off; + cache_ent_t *bp, *hp; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + off = (sec + (sec >> fsi->sect_per_clus_bits)) & (FAT_CACHE_HASH_SIZE - 1); + hp = &(fsi->fcache.hash_list[off]); + for (bp = hp->hash.next; bp != hp; bp = bp->hash.next) { + if (bp->sec == sec) { + /* + * patch 1.2.4 : for debugging + */ + WARN(!bp->bh, "exFAT: fcache has no bh. " + "It will make system panic.\n"); + + touch_buffer(bp->bh); + return bp; + } + } + return NULL; +} + +static cache_ent_t *__fcache_get(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + cache_ent_t *bp; + + bp = fsi->fcache.lru_list.prev; + if (sbi->options.delayed_meta) { + while (bp->flag & DIRTYBIT) { + cache_ent_t *bp_prev = bp->prev; + + bp = bp_prev; + if (bp == &fsi->fcache.lru_list) { + DMSG("BD: fat cache flooding\n"); + exfat_fcache_flush(sb, 0); // flush all dirty FAT caches + bp = fsi->fcache.lru_list.prev; + } + } + } +// if (bp->flag & DIRTYBIT) +// sync_dirty_buffer(bp->bh); + + move_to_mru(bp, &fsi->fcache.lru_list); + return bp; +} + +static void __fcache_insert_hash(struct super_block *sb, cache_ent_t *bp) +{ + s32 off; + cache_ent_t *hp; + FS_INFO_T *fsi; + + fsi = &(EXFAT_SB(sb)->fsi); + off = (bp->sec + (bp->sec >> fsi->sect_per_clus_bits)) & (FAT_CACHE_HASH_SIZE-1); + + hp = &(fsi->fcache.hash_list[off]); + bp->hash.next = hp->hash.next; + bp->hash.prev = hp; + hp->hash.next->hash.prev = bp; + hp->hash.next = bp; +} + + +static void __fcache_remove_hash(cache_ent_t *bp) +{ +#ifdef DEBUG_HASH_LIST + if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) || + (bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) { + EMSG("%s: FATAL: tried to remove already-removed-cache-entry" + "(bp:%p)\n", __func__, bp); + return; + } +#endif + WARN_ON(bp->flag & DIRTYBIT); + __remove_from_hash(bp); +} + +/* Read-ahead a cluster */ +s32 exfat_dcache_readahead(struct super_block *sb, u64 sec) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + struct buffer_head *bh; + u32 max_ra_count = DCACHE_MAX_RA_SIZE >> sb->s_blocksize_bits; + u32 page_ra_count = PAGE_SIZE >> sb->s_blocksize_bits; + u32 adj_ra_count = max(fsi->sect_per_clus, page_ra_count); + u32 ra_count = min(adj_ra_count, max_ra_count); + + /* Read-ahead is not required */ + if (fsi->sect_per_clus == 1) + return 0; + + if (sec < fsi->data_start_sector) { + EMSG("BD: %s: requested sector is invalid(sect:%llu, root:%llu)\n", + __func__, sec, fsi->data_start_sector); + return -EIO; + } + + /* Not sector aligned with ra_count, resize ra_count to page size */ + if ((sec - fsi->data_start_sector) & (ra_count - 1)) + ra_count = page_ra_count; + + bh = sb_find_get_block(sb, sec); + if (!bh || !buffer_uptodate(bh)) + exfat_bdev_readahead(sb, sec, (u64)ra_count); + + brelse(bh); + + return 0; +} + +/* + * returns 1, if bp is flushed + * returns 0, if bp is not dirty + * returns -1, if error occurs + */ +static s32 __dcache_ent_flush(struct super_block *sb, cache_ent_t *bp, u32 sync) +{ + struct exfat_sb_info *sbi; + + if (!(bp->flag & DIRTYBIT)) + return 0; + + sbi = EXFAT_SB(sb); + if (sbi->options.delayed_meta) { + // Make buffer dirty (XXX: Naive impl.) + if (exfat_write_sect(sb, bp->sec, bp->bh, 0)) + return -EIO; + } + bp->flag &= ~(DIRTYBIT); + + if (sync) + sync_dirty_buffer(bp->bh); + + return 1; +} + +static s32 __dcache_ent_discard(struct super_block *sb, cache_ent_t *bp) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + MMSG("%s : bp[%p] (sec:%016llx flag:%08x bh:%p) list(prev:%p next:%p) " + "hash(prev:%p next:%p)\n", __func__, + bp, bp->sec, bp->flag, bp->bh, bp->prev, bp->next, + bp->hash.prev, bp->hash.next); + + __dcache_remove_hash(bp); + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + + move_to_lru(bp, &fsi->dcache.lru_list); + return 0; +} + +u8 *exfat_dcache_getblk(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + bp = __dcache_find(sb, sec); + if (bp) { + if (exfat_bdev_check_bdi_valid(sb)) { + MMSG("%s: found cache(%p, sect:%llu). But invalid BDI\n" + , __func__, bp, sec); + __dcache_ent_flush(sb, bp, 0); + __dcache_ent_discard(sb, bp); + return NULL; + } + + if (!(bp->flag & KEEPBIT)) // already in keep list + move_to_mru(bp, &fsi->dcache.lru_list); + + return bp->bh->b_data; + } + + bp = __dcache_get(sb); + + if (!__check_hash_valid(bp)) + __dcache_remove_hash(bp); + + bp->sec = sec; + bp->flag = 0; + __dcache_insert_hash(sb, bp); + + if (exfat_read_sect(sb, sec, &(bp->bh), 1)) { + __dcache_ent_discard(sb, bp); + return NULL; + } + + return bp->bh->b_data; + +} + +s32 exfat_dcache_modify(struct super_block *sb, u64 sec) +{ + s32 ret = -EIO; + cache_ent_t *bp; + + exfat_set_sb_dirty(sb); + + bp = __dcache_find(sb, sec); + if (unlikely(!bp)) { + exfat_fs_error(sb, "Can`t find dcache (sec 0x%016llx)", sec); + return -EIO; + } + + ret = exfat_write_sect(sb, sec, bp->bh, 0); + + if (ret) { + DMSG("%s : failed to modify buffer(err:%d, sec:%llu, bp:0x%p)\n", + __func__, ret, sec, bp); + } + + return ret; +} + +s32 exfat_dcache_lock(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + + bp = __dcache_find(sb, sec); + if (likely(bp)) { + bp->flag |= LOCKBIT; + return 0; + } + + EMSG("%s : failed to lock buffer(sec:%llu, bp:0x%p)\n", __func__, sec, bp); + return -EIO; +} + +s32 exfat_dcache_unlock(struct super_block *sb, u64 sec) +{ + cache_ent_t *bp; + + bp = __dcache_find(sb, sec); + if (likely(bp)) { + bp->flag &= ~(LOCKBIT); + return 0; + } + + EMSG("%s : failed to unlock buffer (sec:%llu, bp:0x%p)\n", __func__, sec, bp); + return -EIO; +} + +s32 exfat_dcache_release(struct super_block *sb, u64 sec) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + cache_ent_t *bp; + FS_INFO_T *fsi = &(sbi->fsi); + + bp = __dcache_find(sb, sec); + if (unlikely(!bp)) + return -ENOENT; + + if (sbi->options.delayed_meta) { + if (bp->flag & DIRTYBIT) { + if (exfat_write_sect(sb, bp->sec, bp->bh, 0)) + return -EIO; + } + } + + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + + move_to_lru(bp, &fsi->dcache.lru_list); + return 0; +} + +s32 exfat_dcache_release_all(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(sbi->fsi); + s32 dirtycnt = 0; + + /* Connect list elements: + * LRU list : (A - B - ... - bp_front) + (bp_first + ... + bp_last) + */ + while (fsi->dcache.keep_list.prev != &fsi->dcache.keep_list) { + cache_ent_t *bp_keep = fsi->dcache.keep_list.prev; + // bp_keep->flag &= ~(KEEPBIT); // Will be 0-ed later + move_to_mru(bp_keep, &fsi->dcache.lru_list); + } + + bp = fsi->dcache.lru_list.next; + while (bp != &fsi->dcache.lru_list) { + if (sbi->options.delayed_meta) { + if (bp->flag & DIRTYBIT) { + dirtycnt++; + if (exfat_write_sect(sb, bp->sec, bp->bh, 0)) + ret = -EIO; + } + } + + bp->sec = ~0; + bp->flag = 0; + + if (bp->bh) { + __brelse(bp->bh); + bp->bh = NULL; + } + bp = bp->next; + } + + DMSG("BD:Release / dirty buf cache: %d (err:%d)", dirtycnt, ret); + return ret; +} + + +s32 exfat_dcache_flush(struct super_block *sb, u32 sync) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + s32 ret = 0; + cache_ent_t *bp; + FS_INFO_T *fsi = &(sbi->fsi); + s32 dirtycnt = 0; + s32 keepcnt = 0; + + /* Connect list elements: + * LRU list : (A - B - ... - bp_front) + (bp_first + ... + bp_last) + */ + while (fsi->dcache.keep_list.prev != &fsi->dcache.keep_list) { + cache_ent_t *bp_keep = fsi->dcache.keep_list.prev; + + bp_keep->flag &= ~(KEEPBIT); // Will be 0-ed later + move_to_mru(bp_keep, &fsi->dcache.lru_list); + keepcnt++; + } + + bp = fsi->dcache.lru_list.next; + while (bp != &fsi->dcache.lru_list) { + if (bp->flag & DIRTYBIT) { + if (sbi->options.delayed_meta) { + // Make buffer dirty (XXX: Naive impl.) + if (exfat_write_sect(sb, bp->sec, bp->bh, 0)) { + ret = -EIO; + break; + } + } + + bp->flag &= ~(DIRTYBIT); + dirtycnt++; + + if (sync != 0) + sync_dirty_buffer(bp->bh); + } + bp = bp->next; + } + + MMSG("BD: flush / dirty dentry cache: %d (%d from keeplist, err:%d)\n", + dirtycnt, keepcnt, ret); + return ret; +} + +static cache_ent_t *__dcache_find(struct super_block *sb, u64 sec) +{ + s32 off; + cache_ent_t *bp, *hp; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + off = (sec + (sec >> fsi->sect_per_clus_bits)) & (BUF_CACHE_HASH_SIZE - 1); + + hp = &(fsi->dcache.hash_list[off]); + for (bp = hp->hash.next; bp != hp; bp = bp->hash.next) { + if (bp->sec == sec) { + touch_buffer(bp->bh); + return bp; + } + } + return NULL; +} + +static cache_ent_t *__dcache_get(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + cache_ent_t *bp; + FS_INFO_T *fsi = &(sbi->fsi); + + bp = fsi->dcache.lru_list.prev; + + if (sbi->options.delayed_meta) { + while (bp->flag & (DIRTYBIT | LOCKBIT)) { + cache_ent_t *bp_prev = bp->prev; // hold prev + + if (bp->flag & DIRTYBIT) { + MMSG("BD: Buf cache => Keep list\n"); + bp->flag |= KEEPBIT; + move_to_mru(bp, &fsi->dcache.keep_list); + } + bp = bp_prev; + + /* If all dcaches are dirty */ + if (bp == &fsi->dcache.lru_list) { + DMSG("BD: buf cache flooding\n"); + exfat_dcache_flush(sb, 0); + bp = fsi->dcache.lru_list.prev; + } + } + } else { + while (bp->flag & LOCKBIT) + bp = bp->prev; + } +// if (bp->flag & DIRTYBIT) +// sync_dirty_buffer(bp->bh); + + move_to_mru(bp, &fsi->dcache.lru_list); + return bp; +} + +static void __dcache_insert_hash(struct super_block *sb, cache_ent_t *bp) +{ + s32 off; + cache_ent_t *hp; + FS_INFO_T *fsi; + + fsi = &(EXFAT_SB(sb)->fsi); + off = (bp->sec + (bp->sec >> fsi->sect_per_clus_bits)) & (BUF_CACHE_HASH_SIZE-1); + + hp = &(fsi->dcache.hash_list[off]); + bp->hash.next = hp->hash.next; + bp->hash.prev = hp; + hp->hash.next->hash.prev = bp; + hp->hash.next = bp; +} + +static void __dcache_remove_hash(cache_ent_t *bp) +{ +#ifdef DEBUG_HASH_LIST + if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) || + (bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) { + EMSG("%s: FATAL: tried to remove already-removed-cache-entry" + "(bp:%p)\n", __func__, bp); + return; + } +#endif + WARN_ON(bp->flag & DIRTYBIT); + __remove_from_hash(bp); +} diff --git a/fs/exfat/config.h b/fs/exfat/config.h new file mode 100644 index 000000000000..4cad85e1adc3 --- /dev/null +++ b/fs/exfat/config.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + */ + +#ifndef _EXFAT_CONFIG_H +#define _EXFAT_CONFIG_H + +#ifndef CONFIG_EXFAT_WRITE_SB_INTERVAL_CSECS +#define CONFIG_EXFAT_WRITE_SB_INTERVAL_CSECS (dirty_writeback_interval) +#endif + +#ifndef CONFIG_EXFAT_DEFAULT_CODEPAGE /* if Kconfig lacked codepage */ +#define CONFIG_EXFAT_DEFAULT_CODEPAGE 437 +#endif + +#ifndef CONFIG_EXFAT_DEFAULT_IOCHARSET /* if Kconfig lacked iocharset */ +#define CONFIG_EXFAT_DEFAULT_IOCHARSET "utf8" +#endif + +#ifndef CONFIG_EXFAT_VIRTUAL_XATTR +#define CONFIG_EXFAT_VIRTUAL_XATTR +#endif + +#endif /* _EXFAT_CONFIG_H */ diff --git a/fs/exfat/core.c b/fs/exfat/core.c new file mode 100644 index 000000000000..d6d49d414a01 --- /dev/null +++ b/fs/exfat/core.c @@ -0,0 +1,3171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * core.c: FAT-common core code + */ + +#include +#include +#include +#include +#include +#include + +#include "exfat.h" +#include "core.h" +#include +#include + +void exfat_set_sb_dirty(struct super_block *sb) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) + sb->s_dirt = 1; +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) */ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + sbi->s_dirt = 1; + /* Insert work */ + spin_lock(&sbi->work_lock); + if (!sbi->write_super_queued) { + unsigned long delay; + + delay = msecs_to_jiffies(CONFIG_EXFAT_WRITE_SB_INTERVAL_CSECS * 10); + queue_delayed_work(system_long_wq, &sbi->write_super_work, delay); + sbi->write_super_queued = 1; + } + spin_unlock(&sbi->work_lock); +#endif +} + +static s32 check_type_size(void) +{ + /* critical check for system requirement on size of DENTRY_T structure */ + if (sizeof(DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(DOS_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(EXT_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(FILE_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(STRM_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(NAME_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(BMAP_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(CASE_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + if (sizeof(VOLM_DENTRY_T) != DENTRY_SIZE) + return -EINVAL; + + return 0; +} + +static s32 __fs_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + pbr64_t *bpb; + s32 err; + s32 sync = 0; + + /* flags are not changed */ + if (fsi->vol_flag == new_flag) + return 0; + + fsi->vol_flag = new_flag; + + /* skip updating volume dirty flag, + * if this volume has been mounted with read-only + */ + if (EXFAT_IS_SB_RDONLY(sb)) + return 0; + + if (!fsi->pbr_bh) { + err = exfat_read_sect(sb, 0, &(fsi->pbr_bh), 1); + if (err) { + EMSG("%s : failed to read boot sector\n", __func__); + return err; + } + } + + bpb = (pbr64_t *)fsi->pbr_bh->b_data; + bpb->bsx.vol_flags = cpu_to_le16(new_flag); + + if (always_sync) + sync = 1; + else if ((new_flag == VOL_DIRTY) && (!buffer_dirty(fsi->pbr_bh))) + sync = 1; + else + sync = 0; + + err = exfat_write_sect(sb, 0, fsi->pbr_bh, sync); + if (err) + EMSG("%s : failed to modify volume flag\n", __func__); + + return err; +} + +static s32 fs_set_vol_flags(struct super_block *sb, u16 new_flag) +{ + return __fs_set_vol_flags(sb, new_flag, 0); +} + +s32 exfat_fscore_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) +{ + return __fs_set_vol_flags(sb, new_flag, always_sync); +} + +static s32 fs_sync(struct super_block *sb, s32 do_sync) +{ + s32 err; + + if (!do_sync) + return 0; + + err = exfat_bdev_sync_all(sb); + if (err) + EMSG("%s : failed to sync. (err:%d)\n", __func__, err); + + return err; +} + +/* + * Cluster Management Functions + */ + +static s32 __clear_cluster(struct inode *inode, u32 clu) +{ + u64 s, n; + struct super_block *sb = inode->i_sb; + u32 sect_size = (u32)sb->s_blocksize; + s32 ret = 0; + struct buffer_head *tmp_bh = NULL; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(clu)) { /* FAT16 root_dir */ + s = fsi->root_start_sector; + n = fsi->data_start_sector; + } else { + s = CLUS_TO_SECT(fsi, clu); + n = s + fsi->sect_per_clus; + } + + if (IS_DIRSYNC(inode)) { + ret = exfat_write_msect_zero(sb, s, (u64)fsi->sect_per_clus); + if (ret != -EAGAIN) + return ret; + } + + /* Trying buffered zero writes + * if it doesn't have DIRSYNC or exfat_write_msect_zero() returned -EAGAIN + */ + for ( ; s < n; s++) { +#if 0 + exfat_dcache_release(sb, s); +#endif + ret = exfat_read_sect(sb, s, &tmp_bh, 0); + if (ret) + goto out; + + memset((u8 *)tmp_bh->b_data, 0x0, sect_size); + ret = exfat_write_sect(sb, s, tmp_bh, 0); + if (ret) + goto out; + } +out: + brelse(tmp_bh); + return ret; +} + +static s32 __find_last_cluster(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_clu) +{ + u32 clu, next; + u32 count = 0; + + next = p_chain->dir; + if (p_chain->flags == 0x03) { + *ret_clu = next + p_chain->size - 1; + return 0; + } + + do { + count++; + clu = next; + if (exfat_ent_get_safe(sb, clu, &next)) + return -EIO; + } while (!IS_CLUS_EOF(next)); + + if (p_chain->size != count) { + exfat_fs_error(sb, "bogus directory size " + "(clus : ondisk(%d) != counted(%d))", + p_chain->size, count); + exfat_debug_bug_on(1); + return -EIO; + } + + *ret_clu = clu; + return 0; +} + + +static s32 __count_num_clusters(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_count) +{ + u32 i, count; + u32 clu; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (!p_chain->dir || IS_CLUS_EOF(p_chain->dir)) { + *ret_count = 0; + return 0; + } + + if (p_chain->flags == 0x03) { + *ret_count = p_chain->size; + return 0; + } + + clu = p_chain->dir; + count = 0; + for (i = CLUS_BASE; i < fsi->num_clusters; i++) { + count++; + if (exfat_ent_get_safe(sb, clu, &clu)) + return -EIO; + if (IS_CLUS_EOF(clu)) + break; + } + + *ret_count = count; + return 0; +} + +/* + * Upcase table Management Functions + */ +static void free_upcase_table(struct super_block *sb) +{ + u32 i; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u16 **upcase_table; + + upcase_table = fsi->vol_utbl; + for (i = 0 ; i < UTBL_COL_COUNT ; i++) { + /* kfree(NULL) is safe */ + kfree(upcase_table[i]); + upcase_table[i] = NULL; + } + + /* kfree(NULL) is safe */ + kfree(fsi->vol_utbl); + fsi->vol_utbl = NULL; +} + +static s32 __load_upcase_table(struct super_block *sb, u64 sector, u64 num_sectors, u32 utbl_checksum) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + struct buffer_head *tmp_bh = NULL; + u32 sect_size = (u32)sb->s_blocksize; + s32 ret = -EIO; + u32 i, j; + + u8 skip = false; + u32 index = 0; + u32 checksum = 0; + u16 **upcase_table = kzalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL); + + if (!upcase_table) + return -ENOMEM; + /* thanks for kzalloc + * memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *)); + */ + + fsi->vol_utbl = upcase_table; + num_sectors += sector; + + while (sector < num_sectors) { + ret = exfat_read_sect(sb, sector, &tmp_bh, 1); + if (ret) { + EMSG("%s: failed to read sector(0x%llx)\n", + __func__, sector); + goto error; + } + sector++; + + for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { + /* FIXME : is __le16 ok? */ + //u16 uni = le16_to_cpu(((__le16*)(tmp_bh->b_data))[i]); + u16 uni = get_unaligned_le16((u8 *)tmp_bh->b_data+i); + + checksum = ((checksum & 1) ? 0x80000000 : 0) + + (checksum >> 1) + *(((u8 *)tmp_bh->b_data)+i); + checksum = ((checksum & 1) ? 0x80000000 : 0) + + (checksum >> 1) + *(((u8 *)tmp_bh->b_data)+(i+1)); + + if (skip) { + MMSG("skip from 0x%X to 0x%X(amount of 0x%X)\n", + index, index+uni, uni); + index += uni; + skip = false; + } else if (uni == index) { + index++; + } else if (uni == 0xFFFF) { + skip = true; + } else { /* uni != index , uni != 0xFFFF */ + u16 col_index = exfat_get_col_index(index); + + if (!upcase_table[col_index]) { + upcase_table[col_index] = + kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL); + if (!upcase_table[col_index]) { + EMSG("failed to allocate memory" + " for column 0x%X\n", + col_index); + ret = -ENOMEM; + goto error; + } + + for (j = 0; j < UTBL_ROW_COUNT; j++) + upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j; + } + + upcase_table[col_index][exfat_get_row_index(index)] = uni; + index++; + } + } + } + if (index >= 0xFFFF && utbl_checksum == checksum) { + DMSG("%s: load upcase table successfully" + "(idx:0x%08x, utbl_chksum:0x%08x)\n", + __func__, index, utbl_checksum); + if (tmp_bh) + brelse(tmp_bh); + return 0; + } + + EMSG("%s: failed to load upcase table" + "(idx:0x%08x, chksum:0x%08x, utbl_chksum:0x%08x)\n", + __func__, index, checksum, utbl_checksum); + + ret = -EINVAL; +error: + if (tmp_bh) + brelse(tmp_bh); + free_upcase_table(sb); + return ret; +} + +static s32 __load_default_upcase_table(struct super_block *sb) +{ + s32 i, ret = -EIO; + u32 j; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + u8 skip = false; + u32 index = 0; + u16 uni = 0; + u16 **upcase_table; + + upcase_table = kmalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL); + if (!upcase_table) + return -ENOMEM; + + fsi->vol_utbl = upcase_table; + memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *)); + + for (i = 0; index <= 0xFFFF && i < EXFAT_NUM_UPCASE*2; i += 2) { + /* FIXME : is __le16 ok? */ + //uni = le16_to_cpu(((__le16*)uni_def_upcase)[i>>1]); + uni = get_unaligned_le16((u8 *)uni_def_upcase+i); + if (skip) { + MMSG("skip from 0x%x ", index); + index += uni; + MMSG("to 0x%x (amount of 0x%x)\n", index, uni); + skip = false; + } else if (uni == index) { + index++; + } else if (uni == 0xFFFF) { + skip = true; + } else { /* uni != index , uni != 0xFFFF */ + u16 col_index = exfat_get_col_index(index); + + if (!upcase_table[col_index]) { + upcase_table[col_index] = kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL); + if (!upcase_table[col_index]) { + EMSG("failed to allocate memory for " + "new column 0x%x\n", col_index); + ret = -ENOMEM; + goto error; + } + + for (j = 0; j < UTBL_ROW_COUNT; j++) + upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j; + } + + upcase_table[col_index][exfat_get_row_index(index)] = uni; + index++; + } + } + + if (index >= 0xFFFF) + return 0; + +error: + /* FATAL error: default upcase table has error */ + free_upcase_table(sb); + return ret; +} + +static s32 load_upcase_table(struct super_block *sb) +{ + s32 i, ret; + u32 tbl_clu, type; + u64 sector, tbl_size, num_sectors; + u8 blksize_bits = sb->s_blocksize_bits; + CHAIN_T clu; + CASE_DENTRY_T *ep; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + clu.dir = fsi->root_dir; + clu.flags = 0x01; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < fsi->dentries_per_clu; i++) { + ep = (CASE_DENTRY_T *) exfat_get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type((DENTRY_T *) ep); + + if (type == TYPE_UNUSED) + break; + if (type != TYPE_UPCASE) + continue; + + tbl_clu = le32_to_cpu(ep->start_clu); + tbl_size = le64_to_cpu(ep->size); + + sector = CLUS_TO_SECT(fsi, tbl_clu); + num_sectors = ((tbl_size - 1) >> blksize_bits) + 1; + ret = __load_upcase_table(sb, sector, num_sectors, + le32_to_cpu(ep->checksum)); + + if (ret && (ret != -EIO)) + goto load_default; + + /* load successfully */ + return ret; + } + + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + +load_default: + exfat_log_msg(sb, KERN_INFO, "trying to load default upcase table"); + /* load default upcase table */ + return __load_default_upcase_table(sb); +} + + +/* + * Directory Entry Management Functions + */ +s32 exfat_walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, u32 byte_offset, u32 *clu) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 clu_offset; + u32 cur_clu; + + clu_offset = byte_offset >> fsi->cluster_size_bits; + cur_clu = p_dir->dir; + + if (p_dir->flags == 0x03) { + cur_clu += clu_offset; + } else { + while (clu_offset > 0) { + if (get_next_clus_safe(sb, &cur_clu)) + return -EIO; + if (IS_CLUS_EOF(cur_clu)) { + exfat_fs_error(sb, "invalid dentry access " + "beyond EOF (clu : %u, eidx : %d)", + p_dir->dir, + byte_offset >> DENTRY_SIZE_BITS); + return -EIO; + } + clu_offset--; + } + } + + if (clu) + *clu = cur_clu; + return 0; +} + +static s32 find_location(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector, s32 *offset) +{ + s32 ret; + u32 off, clu = 0; + u32 blksize_mask = (u32)(sb->s_blocksize-1); + u8 blksize_bits = sb->s_blocksize_bits; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + off = entry << DENTRY_SIZE_BITS; + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) { + *offset = off & blksize_mask; + *sector = off >> blksize_bits; + *sector += fsi->root_start_sector; + return 0; + } + + ret = exfat_walk_fat_chain(sb, p_dir, off, &clu); + if (ret) + return ret; + + /* byte offset in cluster */ + off &= (fsi->cluster_size - 1); + + /* byte offset in sector */ + *offset = off & blksize_mask; + + /* sector offset in cluster */ + *sector = off >> blksize_bits; + *sector += CLUS_TO_SECT(fsi, clu); + return 0; +} + +DENTRY_T *exfat_get_dentry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 dentries_per_page = PAGE_SIZE >> DENTRY_SIZE_BITS; + s32 off; + u64 sec; + u8 *buf; + + if (p_dir->dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + BUG_ON(!fsi->prev_eio); + return NULL; + } + + if (find_location(sb, p_dir, entry, &sec, &off)) + return NULL; + + /* DIRECTORY READAHEAD : + * Try to read ahead per a page except root directory of fat12/16 + */ + if ((!IS_CLUS_FREE(p_dir->dir)) && + !(entry & (dentries_per_page - 1))) + exfat_dcache_readahead(sb, sec); + + buf = exfat_dcache_getblk(sb, sec); + if (!buf) + return NULL; + + if (sector) + *sector = sec; + return (DENTRY_T *)(buf + off); +} + +/* used only in search empty_slot() */ +#define CNT_UNUSED_NOHIT (-1) +#define CNT_UNUSED_HIT (-2) +/* search EMPTY CONTINUOUS "num_entries" entries */ +static s32 search_empty_slot(struct super_block *sb, HINT_FEMP_T *hint_femp, CHAIN_T *p_dir, s32 num_entries) +{ + s32 i, dentry, num_empty = 0; + s32 dentries_per_clu; + u32 type; + CHAIN_T clu; + DENTRY_T *ep; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + ASSERT(-1 <= hint_femp->eidx); + + if (hint_femp->eidx != -1) { + clu.dir = hint_femp->cur.dir; + clu.size = hint_femp->cur.size; + clu.flags = hint_femp->cur.flags; + + dentry = hint_femp->eidx; + + if (num_entries <= hint_femp->count) { + MMSG("%s: empty slot(HIT) - found " + "(clu : 0x%08x eidx : %d)\n", + __func__, hint_femp->cur.dir, hint_femp->eidx); + hint_femp->eidx = -1; + + return dentry; + } + MMSG("%s: empty slot(HIT) - search from " + "(clu : 0x%08x eidx : %d)\n", + __func__, hint_femp->cur.dir, hint_femp->eidx); + } else { + MMSG("%s: empty slot(MISS) - search from " + "(clu:0x%08x eidx : 0)\n", + __func__, p_dir->dir); + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + dentry = 0; + } + + while (!IS_CLUS_EOF(clu.dir)) { + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + i = dentry % dentries_per_clu; + else + i = dentry & (dentries_per_clu-1); + + for ( ; i < dentries_per_clu; i++, dentry++) { + ep = exfat_get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type(ep); + + if ((type == TYPE_UNUSED) || (type == TYPE_DELETED)) { + num_empty++; + if (hint_femp->eidx == -1) { + hint_femp->eidx = dentry; + hint_femp->count = CNT_UNUSED_NOHIT; + + hint_femp->cur.dir = clu.dir; + hint_femp->cur.size = clu.size; + hint_femp->cur.flags = clu.flags; + } + + if ((type == TYPE_UNUSED) && + (hint_femp->count != CNT_UNUSED_HIT)) { + hint_femp->count = CNT_UNUSED_HIT; + } + } else { + if ((hint_femp->eidx != -1) && + (hint_femp->count == CNT_UNUSED_HIT)) { + /* unused empty group means + * an empty group which includes + * unused dentry + */ + exfat_fs_error(sb, + "found bogus dentry(%d) " + "beyond unused empty group(%d) " + "(start_clu : %u, cur_clu : %u)", + dentry, hint_femp->eidx, p_dir->dir, + clu.dir); + return -EIO; + } + + num_empty = 0; + hint_femp->eidx = -1; + } + + if (num_empty >= num_entries) { + /* found and invalidate hint_femp */ + hint_femp->eidx = -1; + + return (dentry - (num_entries-1)); + } + } + + if (IS_CLUS_FREE(p_dir->dir)) + break; /* FAT16 root_dir */ + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + } + + return -ENOSPC; +} + +/* + * find empty directory entry. + * if there isn't any empty slot, expand cluster chain. + */ +static s32 find_empty_entry(struct inode *inode, CHAIN_T *p_dir, s32 num_entries) +{ + s32 dentry; + u32 ret, last_clu; + u64 sector; + u64 size = 0; + CHAIN_T clu; + DENTRY_T *ep = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + HINT_FEMP_T hint_femp; + + hint_femp.eidx = -1; + + ASSERT(-1 <= fid->hint_femp.eidx); + + if (fid->hint_femp.eidx != -1) { + memcpy(&hint_femp, &fid->hint_femp, sizeof(HINT_FEMP_T)); + fid->hint_femp.eidx = -1; + } + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + return search_empty_slot(sb, &hint_femp, p_dir, num_entries); + + while ((dentry = search_empty_slot(sb, &hint_femp, p_dir, num_entries)) < 0) { + if (dentry == -EIO) + break; + + if (fsi->fs_func->check_max_dentries(fid)) + return -ENOSPC; + + /* we trust p_dir->size regardless of FAT type */ + if (__find_last_cluster(sb, p_dir, &last_clu)) + return -EIO; + + /* + * Allocate new cluster to this directory + */ + clu.dir = last_clu + 1; + clu.size = 0; /* UNUSED */ + clu.flags = p_dir->flags; + + /* (0) check if there are reserved clusters + * Refer to create_dir's comments + */ + if (!IS_CLUS_EOF(fsi->used_clusters) && + ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - 2))) + return -ENOSPC; + + /* (1) allocate a cluster */ + ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT); + if (ret) + return ret; + + if (__clear_cluster(inode, clu.dir)) + return -EIO; + + /* (2) append to the FAT chain */ + if (clu.flags != p_dir->flags) { + /* no-fat-chain bit is disabled, + * so fat-chain should be synced with alloc-bmp + */ + exfat_chain_cont_cluster(sb, p_dir->dir, p_dir->size); + p_dir->flags = 0x01; + hint_femp.cur.flags = 0x01; + } + + if (clu.flags == 0x01) + if (exfat_ent_set(sb, last_clu, clu.dir)) + return -EIO; + + if (hint_femp.eidx == -1) { + /* the special case that new dentry + * should be allocated from the start of new cluster + */ + hint_femp.eidx = (s32)(p_dir->size << + (fsi->cluster_size_bits - DENTRY_SIZE_BITS)); + hint_femp.count = fsi->dentries_per_clu; + + hint_femp.cur.dir = clu.dir; + hint_femp.cur.size = 0; + hint_femp.cur.flags = clu.flags; + } + hint_femp.cur.size++; + p_dir->size++; + size = (p_dir->size << fsi->cluster_size_bits); + + /* (3) update the directory entry */ + if (p_dir->dir != fsi->root_dir) { + ep = exfat_get_dentry_in_dir(sb, + &(fid->dir), fid->entry+1, §or); + if (!ep) + return -EIO; + fsi->fs_func->set_entry_size(ep, size); + fsi->fs_func->set_entry_flag(ep, p_dir->flags); + if (exfat_dcache_modify(sb, sector)) + return -EIO; + + if (exfat_update_dir_chksum(sb, &(fid->dir), fid->entry)) + return -EIO; + } + + /* directory inode should be updated in here */ + i_size_write(inode, (loff_t)size); + EXFAT_I(inode)->i_size_ondisk += fsi->cluster_size; + EXFAT_I(inode)->i_size_aligned += fsi->cluster_size; + EXFAT_I(inode)->fid.size = size; + EXFAT_I(inode)->fid.flags = p_dir->flags; + inode->i_blocks += 1 << (fsi->cluster_size_bits - sb->s_blocksize_bits); + } + + return dentry; +} + +#define EXFAT_MIN_SUBDIR (2) + +static s32 __count_dos_name_entries(struct super_block *sb, CHAIN_T *p_dir, u32 type, u32 *dotcnt) +{ + s32 i, count = 0, check_dot = 0; + s32 dentries_per_clu; + u32 entry_type; + CHAIN_T clu; + DENTRY_T *ep; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + if (dotcnt) + *dotcnt = 0; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < dentries_per_clu; i++) { + ep = exfat_get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + entry_type = fsi->fs_func->get_entry_type(ep); + + if (entry_type == TYPE_UNUSED) + return count; + if (!(type & TYPE_CRITICAL_PRI) && !(type & TYPE_BENIGN_PRI)) + continue; + + if ((type != TYPE_ALL) && (type != entry_type)) + continue; + + count++; + } + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + break; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + + check_dot = 0; + } + + return count; +} + +s32 check_dir_empty(struct super_block *sb, CHAIN_T *p_dir) +{ + s32 i; + s32 dentries_per_clu; + u32 type; + CHAIN_T clu; + DENTRY_T *ep; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + else + dentries_per_clu = fsi->dentries_per_clu; + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < dentries_per_clu; i++) { + ep = exfat_get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type(ep); + + if (type == TYPE_UNUSED) + return 0; + + if ((type != TYPE_FILE) && (type != TYPE_DIR)) + continue; + + return -ENOTEMPTY; + } + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(p_dir->dir)) + return -ENOTEMPTY; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + } + + return 0; +} + +/* input : dir, uni_name + * output : num_of_entry, dos_name(format : aaaaaa~1.bbb) + */ +static s32 get_num_entries_and_dos_name(struct super_block *sb, CHAIN_T *p_dir, + UNI_NAME_T *p_uniname, s32 *entries, + DOS_NAME_T *p_dosname, s32 lookup) +{ + s32 num_entries; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + /* Init null char. */ + p_dosname->name[0] = '\0'; + + num_entries = fsi->fs_func->calc_num_entries(p_uniname); + if (num_entries == 0) + return -EINVAL; + + *entries = num_entries; + return 0; +} + +void exfat_get_uniname_from_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, UNI_NAME_T *p_uniname, u8 mode) +{ + DOS_NAME_T dos_name; + + if (mode == 0x0) + dos_name.name_case = 0x0; + else + dos_name.name_case = ep->lcase; + + memcpy(dos_name.name, ep->name, DOS_NAME_LENGTH); + exfat_nls_sfn_to_uni16s(sb, &dos_name, p_uniname); +} + +/* returns the length of a struct qstr, ignoring trailing dots */ +static inline unsigned int __striptail_len(unsigned int len, const char *name) +{ + while (len && name[len - 1] == '.') + len--; + return len; +} + +/* + * Name Resolution Functions : + * Zero if it was successful; otherwise nonzero. + */ +static s32 __resolve_path(struct inode *inode, const u8 *path, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, int lookup) +{ + s32 namelen; + s32 lossy = NLS_NAME_NO_LOSSY; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + + /* DOT and DOTDOT are handled by VFS layer */ + + /* strip all trailing spaces */ + /* DO NOTHING : Is needed? */ + + /* strip all trailing periods */ + namelen = __striptail_len(strlen(path), path); + if (!namelen) + return -ENOENT; + + /* the limitation of linux? */ + if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE)) + return -ENAMETOOLONG; + + /* + * strip all leading spaces : + * "MS windows 7" supports leading spaces. + * So we should skip this preprocessing for compatibility. + */ + + /* file name conversion : + * If lookup case, we allow bad-name for compatibility. + */ + namelen = exfat_nls_vfsname_to_uni16s(sb, path, namelen, p_uniname, &lossy); + if (namelen < 0) + return namelen; /* return error value */ + + if ((lossy && !lookup) || !namelen) + return -EINVAL; + + exfat_debug_bug_on(fid->size != i_size_read(inode)); +// fid->size = i_size_read(inode); + + p_dir->dir = fid->start_clu; + p_dir->size = (u32)(fid->size >> fsi->cluster_size_bits); + p_dir->flags = fid->flags; + + return 0; +} + +static inline s32 resolve_path(struct inode *inode, const u8 *path, CHAIN_T *dir, UNI_NAME_T *uni) +{ + return __resolve_path(inode, path, dir, uni, 0); +} + +static inline s32 resolve_path_for_lookup(struct inode *inode, const u8 *path, CHAIN_T *dir, UNI_NAME_T *uni) +{ + return __resolve_path(inode, path, dir, uni, 1); +} + +static s32 create_dir(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, FILE_ID_T *fid) +{ + s32 dentry, num_entries; + u64 ret; + u64 size; + CHAIN_T clu; + DOS_NAME_T dos_name; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name, 0); + if (ret) + return ret; + + /* find_empty_entry must be called before alloc_cluster */ + dentry = find_empty_entry(inode, p_dir, num_entries); + if (dentry < 0) + return dentry; /* -EIO or -ENOSPC */ + + clu.dir = CLUS_EOF; + clu.size = 0; + clu.flags = 0x03; + + /* (0) Check if there are reserved clusters up to max. */ + if ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - CLUS_BASE)) + return -ENOSPC; + + /* (1) allocate a cluster */ + ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT); + if (ret) + return ret; + + ret = __clear_cluster(inode, clu.dir); + if (ret) + return ret; + + size = fsi->cluster_size; + + /* (2) update the directory entry */ + /* make sub-dir entry in parent directory */ + ret = fsi->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_DIR, clu.dir, size); + if (ret) + return ret; + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fid->dir.dir = p_dir->dir; + fid->dir.size = p_dir->size; + fid->dir.flags = p_dir->flags; + fid->entry = dentry; + + fid->attr = ATTR_SUBDIR; + fid->flags = 0x03; + fid->size = size; + fid->start_clu = clu.dir; + + fid->type = TYPE_DIR; + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + + /* hint_stat will be used if this is directory. */ + fid->version = 0; + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + return 0; +} + +static s32 create_file(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, u8 mode, FILE_ID_T *fid) +{ + s32 ret, dentry, num_entries; + DOS_NAME_T dos_name; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name, 0); + if (ret) + return ret; + + /* find_empty_entry must be called before alloc_cluster() */ + dentry = find_empty_entry(inode, p_dir, num_entries); + if (dentry < 0) + return dentry; /* -EIO or -ENOSPC */ + + /* (1) update the directory entry */ + /* fill the dos name directory entry information of the created file. + * the first cluster is not determined yet. (0) + */ + ret = fsi->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_FILE | mode, CLUS_FREE, 0); + if (ret) + return ret; + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fid->dir.dir = p_dir->dir; + fid->dir.size = p_dir->size; + fid->dir.flags = p_dir->flags; + fid->entry = dentry; + + fid->attr = ATTR_ARCHIVE | mode; + fid->flags = 0x03; + fid->size = 0; + fid->start_clu = CLUS_EOF; + + fid->type = TYPE_FILE; + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + + /* hint_stat will be used if this is directory. */ + fid->version = 0; + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + return 0; +} + +static s32 remove_file(struct inode *inode, CHAIN_T *p_dir, s32 entry) +{ + s32 num_entries; + u64 sector; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + ep = exfat_get_dentry_in_dir(sb, p_dir, entry, §or); + if (!ep) + return -EIO; + + exfat_dcache_lock(sb, sector); + + /* exfat_dcache_lock() before call count_ext_entries() */ + num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, entry, ep); + if (num_entries < 0) { + exfat_dcache_unlock(sb, sector); + return -EIO; + } + num_entries++; + + exfat_dcache_unlock(sb, sector); + + /* (1) update the directory entry */ + return fsi->fs_func->delete_dir_entry(sb, p_dir, entry, 0, num_entries); +} + +static s32 rename_file(struct inode *inode, CHAIN_T *p_dir, s32 oldentry, UNI_NAME_T *p_uniname, FILE_ID_T *fid) +{ + s32 ret, newentry = -1, num_old_entries, num_new_entries; + u64 sector_old, sector_new; + DOS_NAME_T dos_name; + DENTRY_T *epold, *epnew; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + epold = exfat_get_dentry_in_dir(sb, p_dir, oldentry, §or_old); + if (!epold) + return -EIO; + + exfat_dcache_lock(sb, sector_old); + + /* exfat_dcache_lock() before call count_ext_entries() */ + num_old_entries = fsi->fs_func->count_ext_entries(sb, p_dir, oldentry, epold); + if (num_old_entries < 0) { + exfat_dcache_unlock(sb, sector_old); + return -EIO; + } + num_old_entries++; + + ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_new_entries, &dos_name, 0); + if (ret) { + exfat_dcache_unlock(sb, sector_old); + return ret; + } + + if (num_old_entries < num_new_entries) { + newentry = find_empty_entry(inode, p_dir, num_new_entries); + if (newentry < 0) { + exfat_dcache_unlock(sb, sector_old); + return newentry; /* -EIO or -ENOSPC */ + } + + epnew = exfat_get_dentry_in_dir(sb, p_dir, newentry, §or_new); + if (!epnew) { + exfat_dcache_unlock(sb, sector_old); + return -EIO; + } + + memcpy((void *) epnew, (void *) epold, DENTRY_SIZE); + if (fsi->fs_func->get_entry_type(epnew) == TYPE_FILE) { + fsi->fs_func->set_entry_attr(epnew, fsi->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE); + fid->attr |= ATTR_ARCHIVE; + } + exfat_dcache_modify(sb, sector_new); + exfat_dcache_unlock(sb, sector_old); + + epold = exfat_get_dentry_in_dir(sb, p_dir, oldentry+1, §or_old); + exfat_dcache_lock(sb, sector_old); + epnew = exfat_get_dentry_in_dir(sb, p_dir, newentry+1, §or_new); + + if (!epold || !epnew) { + exfat_dcache_unlock(sb, sector_old); + return -EIO; + } + + memcpy((void *) epnew, (void *) epold, DENTRY_SIZE); + exfat_dcache_modify(sb, sector_new); + exfat_dcache_unlock(sb, sector_old); + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, newentry, num_new_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fsi->fs_func->delete_dir_entry(sb, p_dir, oldentry, 0, num_old_entries); + fid->entry = newentry; + } else { + if (fsi->fs_func->get_entry_type(epold) == TYPE_FILE) { + fsi->fs_func->set_entry_attr(epold, fsi->fs_func->get_entry_attr(epold) | ATTR_ARCHIVE); + fid->attr |= ATTR_ARCHIVE; + } + exfat_dcache_modify(sb, sector_old); + exfat_dcache_unlock(sb, sector_old); + + ret = fsi->fs_func->init_ext_entry(sb, p_dir, oldentry, num_new_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fsi->fs_func->delete_dir_entry(sb, p_dir, oldentry, num_new_entries, num_old_entries); + } + + return 0; +} + +static s32 move_file(struct inode *inode, CHAIN_T *p_olddir, s32 oldentry, + CHAIN_T *p_newdir, UNI_NAME_T *p_uniname, FILE_ID_T *fid) +{ + s32 ret, newentry, num_new_entries, num_old_entries; + u64 sector_mov, sector_new; + DOS_NAME_T dos_name; + DENTRY_T *epmov, *epnew; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + epmov = exfat_get_dentry_in_dir(sb, p_olddir, oldentry, §or_mov); + if (!epmov) + return -EIO; + + /* check if the source and target directory is the same */ + if (fsi->fs_func->get_entry_type(epmov) == TYPE_DIR && + fsi->fs_func->get_entry_clu0(epmov) == p_newdir->dir) + return -EINVAL; + + exfat_dcache_lock(sb, sector_mov); + + /* exfat_dcache_lock() before call count_ext_entries() */ + num_old_entries = fsi->fs_func->count_ext_entries(sb, p_olddir, oldentry, epmov); + if (num_old_entries < 0) { + exfat_dcache_unlock(sb, sector_mov); + return -EIO; + } + num_old_entries++; + + ret = get_num_entries_and_dos_name(sb, p_newdir, p_uniname, &num_new_entries, &dos_name, 0); + if (ret) { + exfat_dcache_unlock(sb, sector_mov); + return ret; + } + + newentry = find_empty_entry(inode, p_newdir, num_new_entries); + if (newentry < 0) { + exfat_dcache_unlock(sb, sector_mov); + return newentry; /* -EIO or -ENOSPC */ + } + + epnew = exfat_get_dentry_in_dir(sb, p_newdir, newentry, §or_new); + if (!epnew) { + exfat_dcache_unlock(sb, sector_mov); + return -EIO; + } + + memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE); + if (fsi->fs_func->get_entry_type(epnew) == TYPE_FILE) { + fsi->fs_func->set_entry_attr(epnew, fsi->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE); + fid->attr |= ATTR_ARCHIVE; + } + exfat_dcache_modify(sb, sector_new); + exfat_dcache_unlock(sb, sector_mov); + + epmov = exfat_get_dentry_in_dir(sb, p_olddir, oldentry+1, §or_mov); + exfat_dcache_lock(sb, sector_mov); + epnew = exfat_get_dentry_in_dir(sb, p_newdir, newentry+1, §or_new); + if (!epmov || !epnew) { + exfat_dcache_unlock(sb, sector_mov); + return -EIO; + } + + memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE); + exfat_dcache_modify(sb, sector_new); + exfat_dcache_unlock(sb, sector_mov); + + ret = fsi->fs_func->init_ext_entry(sb, p_newdir, newentry, num_new_entries, p_uniname, &dos_name); + if (ret) + return ret; + + fsi->fs_func->delete_dir_entry(sb, p_olddir, oldentry, 0, num_old_entries); + + fid->dir.dir = p_newdir->dir; + fid->dir.size = p_newdir->size; + fid->dir.flags = p_newdir->flags; + + fid->entry = newentry; + + return 0; +} + +/* roll back to the initial state of the file system */ +s32 exfat_fscore_init(void) +{ + s32 ret; + + ret = check_type_size(); + if (ret) + return ret; + + return exfat_extent_cache_init(); +} + +/* make free all memory-alloced global buffers */ +s32 exfat_fscore_shutdown(void) +{ + exfat_extent_cache_shutdown(); + return 0; +} + +/* check device is ejected */ +s32 exfat_fscore_check_bdi_valid(struct super_block *sb) +{ + return exfat_bdev_check_bdi_valid(sb); +} + +static bool is_exfat(pbr_t *pbr) +{ + int i = 53; + + do { + if (pbr->bpb.f64.res_zero[i-1]) + break; + } while (--i); + return i ? false : true; +} + +inline pbr_t *read_pbr_with_logical_sector(struct super_block *sb, struct buffer_head **prev_bh) +{ + pbr_t *p_pbr = (pbr_t *) (*prev_bh)->b_data; + u16 logical_sect = 0; + + if (is_exfat(p_pbr)) + logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; + else + logical_sect = get_unaligned_le16(&p_pbr->bpb.f16.sect_size); + + /* is x a power of 2? + * (x) != 0 && (((x) & ((x) - 1)) == 0) + */ + if (!is_power_of_2(logical_sect) + || (logical_sect < 512) + || (logical_sect > 4096)) { + exfat_log_msg(sb, KERN_ERR, "bogus logical sector size %u", + logical_sect); + return NULL; + } + + if (logical_sect < sb->s_blocksize) { + exfat_log_msg(sb, KERN_ERR, + "logical sector size too small for device" + " (logical sector size = %u)", logical_sect); + return NULL; + } + + if (logical_sect > sb->s_blocksize) { + struct buffer_head *bh = NULL; + + __brelse(*prev_bh); + *prev_bh = NULL; + + if (!sb_set_blocksize(sb, logical_sect)) { + exfat_log_msg(sb, KERN_ERR, + "unable to set blocksize %u", logical_sect); + return NULL; + } + bh = sb_bread(sb, 0); + if (!bh) { + exfat_log_msg(sb, KERN_ERR, + "unable to read boot sector " + "(logical sector size = %lu)", sb->s_blocksize); + return NULL; + } + + *prev_bh = bh; + p_pbr = (pbr_t *) bh->b_data; + } + + exfat_log_msg(sb, KERN_INFO, + "set logical sector size : %lu", sb->s_blocksize); + + return p_pbr; +} + +/* mount the file system volume */ +s32 exfat_fscore_mount(struct super_block *sb) +{ + s32 ret; + pbr_t *p_pbr; + struct buffer_head *tmp_bh = NULL; + struct gendisk *disk = sb->s_bdev->bd_disk; + struct hd_struct *part = sb->s_bdev->bd_part; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + /* initialize previous I/O error */ + fsi->prev_eio = 0; + + /* open the block device */ + if (exfat_bdev_open_dev(sb)) + return -EIO; + + /* set block size to read super block */ + sb_min_blocksize(sb, 512); + + /* read boot sector */ + ret = exfat_read_sect(sb, 0, &tmp_bh, 1); + if (ret) { + exfat_log_msg(sb, KERN_ERR, "unable to read boot sector"); + ret = -EIO; + goto bd_close; + } + + /* PRB is read */ + p_pbr = (pbr_t *) tmp_bh->b_data; + + /* check the validity of PBR */ + if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { + exfat_log_msg(sb, KERN_ERR, "invalid boot record signature"); + brelse(tmp_bh); + ret = -EINVAL; + goto bd_close; + } + + /* check logical sector size */ + p_pbr = read_pbr_with_logical_sector(sb, &tmp_bh); + if (!p_pbr) { + brelse(tmp_bh); + ret = -EIO; + goto bd_close; + } + + /* fill fs_struct */ + if (!is_exfat(p_pbr)) { + ret = -EINVAL; + goto free_bh; + } + + /* set maximum file size for exFAT */ + sb->s_maxbytes = 0x7fffffffffffffffLL; + ret = mount_exfat(sb, p_pbr); + +free_bh: + brelse(tmp_bh); + if (ret) { + exfat_log_msg(sb, KERN_ERR, "failed to mount fs-core"); + goto bd_close; + } + + /* warn misaligned data data start sector must be a multiple of clu_size */ + exfat_log_msg(sb, KERN_INFO, + "(bps : %lu, spc : %u, data start : %llu, %s)", + sb->s_blocksize, fsi->sect_per_clus, fsi->data_start_sector, + (fsi->data_start_sector & (fsi->sect_per_clus - 1)) ? + "misaligned" : "aligned"); + + exfat_log_msg(sb, KERN_INFO, + "detected volume size : %llu KB (disk : %llu KB, " + "part : %llu KB)", + (fsi->num_sectors * (sb->s_blocksize >> SECTOR_SIZE_BITS)) >> 1, + disk ? (u64)((disk->part0.nr_sects) >> 1) : 0, + part ? (u64)((part->nr_sects) >> 1) : 0); + + ret = load_upcase_table(sb); + if (ret) { + exfat_log_msg(sb, KERN_ERR, "failed to load upcase table"); + goto bd_close; + } + + /* allocate-bitmap is only for exFAT */ + ret = exfat_load_alloc_bmp(sb); + if (ret) { + exfat_log_msg(sb, KERN_ERR, "failed to load alloc-bitmap"); + goto free_upcase; + } + + if (fsi->used_clusters == UINT_MAX) { + ret = fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters); + if (ret) { + exfat_log_msg(sb, KERN_ERR, "failed to scan clusters"); + goto exfat_free_alloc_bmp; + } + } + + return 0; +exfat_free_alloc_bmp: + exfat_free_alloc_bmp(sb); +free_upcase: + free_upcase_table(sb); +bd_close: + exfat_bdev_close_dev(sb); + return ret; +} + +/* umount the file system volume */ +s32 exfat_fscore_umount(struct super_block *sb) +{ + s32 ret = 0; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (fs_sync(sb, 0)) + ret = -EIO; + + if (fs_set_vol_flags(sb, VOL_CLEAN)) + ret = -EIO; + + free_upcase_table(sb); + + exfat_free_alloc_bmp(sb); + + if (exfat_fcache_release_all(sb)) + ret = -EIO; + + if (exfat_dcache_release_all(sb)) + ret = -EIO; + + if (fsi->prev_eio) + ret = -EIO; + /* close the block device */ + exfat_bdev_close_dev(sb); + return ret; +} + +/* get the information of a file system volume */ +s32 exfat_fscore_statfs(struct super_block *sb, VOL_INFO_T *info) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (fsi->used_clusters == UINT_MAX) { + if (fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters)) + return -EIO; + } + + info->ClusterSize = fsi->cluster_size; + info->NumClusters = fsi->num_clusters - 2; /* clu 0 & 1 */ + info->UsedClusters = fsi->used_clusters + fsi->reserved_clusters; + info->FreeClusters = info->NumClusters - info->UsedClusters; + + return 0; +} + +/* synchronize all file system volumes */ +s32 exfat_fscore_sync_fs(struct super_block *sb, s32 do_sync) +{ + /* synchronize the file system */ + if (fs_sync(sb, do_sync)) + return -EIO; + + if (fs_set_vol_flags(sb, VOL_CLEAN)) + return -EIO; + + return 0; +} + +/* stat allocation unit of a file system volume */ +u32 exfat_fscore_get_au_stat(struct super_block *sb, s32 mode) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (fsi->fs_func->get_au_stat) + return fsi->fs_func->get_au_stat(sb, mode); + + /* No error, just returns 0 */ + return 0; +} + +/* lookup a file */ +s32 exfat_fscore_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid) +{ + s32 ret, dentry, num_entries; + CHAIN_T dir; + UNI_NAME_T uni_name; + DOS_NAME_T dos_name; + DENTRY_T *ep, *ep2; + ENTRY_SET_CACHE_T *es = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *dir_fid = &(EXFAT_I(inode)->fid); + + /* check the validity of directory name in the given pathname */ + ret = resolve_path_for_lookup(inode, path, &dir, &uni_name); + if (ret) + return ret; + + ret = get_num_entries_and_dos_name(sb, &dir, &uni_name, &num_entries, &dos_name, 1); + if (ret) + return ret; + + /* check the validation of hint_stat and initialize it if required */ + if (dir_fid->version != (u32)(GET_IVERSION(inode) & 0xffffffff)) { + dir_fid->hint_stat.clu = dir.dir; + dir_fid->hint_stat.eidx = 0; + dir_fid->version = (u32)(GET_IVERSION(inode) & 0xffffffff); + dir_fid->hint_femp.eidx = -1; + } + + /* search the file name for directories */ + dentry = fsi->fs_func->find_dir_entry(sb, dir_fid, &dir, &uni_name, + num_entries, &dos_name, TYPE_ALL); + + if ((dentry < 0) && (dentry != -EEXIST)) + return dentry; /* -error value */ + + fid->dir.dir = dir.dir; + fid->dir.size = dir.size; + fid->dir.flags = dir.flags; + fid->entry = dentry; + + /* root directory itself */ + if (unlikely(dentry == -EEXIST)) { + fid->type = TYPE_DIR; + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + + fid->attr = ATTR_SUBDIR; + fid->flags = 0x01; + fid->size = 0; + fid->start_clu = fsi->root_dir; + } else { + es = exfat_get_dentry_set_in_dir(sb, &dir, dentry, ES_2_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + + fid->type = fsi->fs_func->get_entry_type(ep); + fid->rwoffset = 0; + fid->hint_bmap.off = CLUS_EOF; + fid->attr = fsi->fs_func->get_entry_attr(ep); + + fid->size = fsi->fs_func->get_entry_size(ep2); + if ((fid->type == TYPE_FILE) && (fid->size == 0)) { + fid->flags = 0x03; + fid->start_clu = CLUS_EOF; + } else { + fid->flags = fsi->fs_func->get_entry_flag(ep2); + fid->start_clu = fsi->fs_func->get_entry_clu0(ep2); + } + + /* FOR GRACEFUL ERROR HANDLING */ + if (IS_CLUS_FREE(fid->start_clu)) { + exfat_fs_error(sb, + "non-zero size file starts with zero cluster " + "(size : %llu, p_dir : %u, entry : 0x%08x)", + fid->size, fid->dir.dir, fid->entry); + exfat_debug_bug_on(1); + return -EIO; + } + + exfat_release_dentry_set(es); + } + + /* hint_stat will be used if this is directory. */ + fid->version = 0; + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + return 0; +} + +/* create a file */ +s32 exfat_fscore_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid) +{ + s32 ret/*, dentry*/; + CHAIN_T dir; + UNI_NAME_T uni_name; + struct super_block *sb = inode->i_sb; + + /* check the validity of directory name in the given pathname */ + ret = resolve_path(inode, path, &dir, &uni_name); + if (ret) + return ret; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* create a new file */ + ret = create_file(inode, &dir, &uni_name, mode, fid); + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} + +/* read data from a opened file */ +s32 exfat_fscore_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount) +{ + s32 ret = 0; + s32 offset, sec_offset; + u32 clu_offset; + u32 clu; + u64 logsector, oneblkread, read_bytes; + struct buffer_head *tmp_bh = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + /* check if the given file ID is opened */ + if (fid->type != TYPE_FILE) + return -EPERM; + + if (fid->rwoffset > fid->size) + fid->rwoffset = fid->size; + + if (count > (fid->size - fid->rwoffset)) + count = fid->size - fid->rwoffset; + + if (count == 0) { + if (rcount) + *rcount = 0; + return 0; + } + + read_bytes = 0; + + while (count > 0) { + clu_offset = fid->rwoffset >> fsi->cluster_size_bits; + clu = fid->start_clu; + + if (fid->flags == 0x03) { + clu += clu_offset; + } else { + /* hint information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + clu = fid->hint_bmap.clu; + } + + while (clu_offset > 0) { + ret = get_next_clus_safe(sb, &clu); + if (ret) + goto err_out; + + clu_offset--; + } + } + + /* hint information */ + fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits; + fid->hint_bmap.clu = clu; + + offset = (s32)(fid->rwoffset & (fsi->cluster_size - 1)); /* byte offset in cluster */ + sec_offset = offset >> sb->s_blocksize_bits; /* sector offset in cluster */ + offset &= (sb->s_blocksize - 1); /* byte offset in sector */ + + logsector = CLUS_TO_SECT(fsi, clu) + sec_offset; + + oneblkread = (u64)(sb->s_blocksize - offset); + if (oneblkread > count) + oneblkread = count; + + if ((offset == 0) && (oneblkread == sb->s_blocksize)) { + ret = exfat_read_sect(sb, logsector, &tmp_bh, 1); + if (ret) + goto err_out; + memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data), (s32) oneblkread); + } else { + ret = exfat_read_sect(sb, logsector, &tmp_bh, 1); + if (ret) + goto err_out; + memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data)+offset, (s32) oneblkread); + } + count -= oneblkread; + read_bytes += oneblkread; + fid->rwoffset += oneblkread; + } + +err_out: + brelse(tmp_bh); + + /* set the size of read bytes */ + if (rcount != NULL) + *rcount = read_bytes; + + return ret; +} + +/* write data into a opened file */ +s32 exfat_fscore_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount) +{ + s32 ret = 0; + s32 modified = false, offset, sec_offset; + u32 clu_offset, num_clusters, num_alloc; + u32 clu, last_clu; + u64 logsector, oneblkwrite, write_bytes; + CHAIN_T new_clu; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + ENTRY_SET_CACHE_T *es = NULL; + struct buffer_head *tmp_bh = NULL; + struct super_block *sb = inode->i_sb; + u32 blksize = (u32)sb->s_blocksize; + u32 blksize_mask = (u32)(sb->s_blocksize-1); + u8 blksize_bits = sb->s_blocksize_bits; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + /* check if the given file ID is opened */ + if (fid->type != TYPE_FILE) + return -EPERM; + + if (fid->rwoffset > fid->size) + fid->rwoffset = fid->size; + + if (count == 0) { + if (wcount) + *wcount = 0; + return 0; + } + + fs_set_vol_flags(sb, VOL_DIRTY); + + if (fid->size == 0) + num_clusters = 0; + else + num_clusters = ((fid->size-1) >> fsi->cluster_size_bits) + 1; + + write_bytes = 0; + + while (count > 0) { + clu_offset = (fid->rwoffset >> fsi->cluster_size_bits); + clu = last_clu = fid->start_clu; + + if (fid->flags == 0x03) { + if ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) { + last_clu += clu_offset - 1; + + if (clu_offset == num_clusters) + clu = CLUS_EOF; + else + clu += clu_offset; + } + } else { + /* hint information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + clu = fid->hint_bmap.clu; + } + + while ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) { + last_clu = clu; + ret = get_next_clus_safe(sb, &clu); + if (ret) + goto err_out; + + clu_offset--; + } + } + + if (IS_CLUS_EOF(clu)) { + num_alloc = ((count-1) >> fsi->cluster_size_bits) + 1; + new_clu.dir = IS_CLUS_EOF(last_clu) ? CLUS_EOF : last_clu+1; + new_clu.size = 0; + new_clu.flags = fid->flags; + + /* (1) allocate a chain of clusters */ + ret = fsi->fs_func->alloc_cluster(sb, num_alloc, &new_clu, ALLOC_COLD); + if (ret) + goto err_out; + + /* (2) append to the FAT chain */ + if (IS_CLUS_EOF(last_clu)) { + if (new_clu.flags == 0x01) + fid->flags = 0x01; + fid->start_clu = new_clu.dir; + modified = true; + } else { + if (new_clu.flags != fid->flags) { + /* no-fat-chain bit is disabled, + * so fat-chain should be synced with + * alloc-bmp + */ + exfat_chain_cont_cluster(sb, fid->start_clu, num_clusters); + fid->flags = 0x01; + modified = true; + } + if (new_clu.flags == 0x01) { + ret = exfat_ent_set(sb, last_clu, new_clu.dir); + if (ret) + goto err_out; + } + } + + num_clusters += num_alloc; + clu = new_clu.dir; + } + + /* hint information */ + fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits; + fid->hint_bmap.clu = clu; + + /* byte offset in cluster */ + offset = (s32)(fid->rwoffset & (fsi->cluster_size-1)); + /* sector offset in cluster */ + sec_offset = offset >> blksize_bits; + /* byte offset in sector */ + offset &= blksize_mask; + logsector = CLUS_TO_SECT(fsi, clu) + sec_offset; + + oneblkwrite = (u64)(blksize - offset); + if (oneblkwrite > count) + oneblkwrite = count; + + if ((offset == 0) && (oneblkwrite == blksize)) { + ret = exfat_read_sect(sb, logsector, &tmp_bh, 0); + if (ret) + goto err_out; + + memcpy(((s8 *)tmp_bh->b_data), + ((s8 *)buffer)+write_bytes, + (s32)oneblkwrite); + + ret = exfat_write_sect(sb, logsector, tmp_bh, 0); + if (ret) { + brelse(tmp_bh); + goto err_out; + } + } else { + if ((offset > 0) || ((fid->rwoffset+oneblkwrite) < fid->size)) { + ret = exfat_read_sect(sb, logsector, &tmp_bh, 1); + if (ret) + goto err_out; + } else { + ret = exfat_read_sect(sb, logsector, &tmp_bh, 0); + if (ret) + goto err_out; + } + + memcpy(((s8 *) tmp_bh->b_data)+offset, ((s8 *) buffer)+write_bytes, (s32) oneblkwrite); + ret = exfat_write_sect(sb, logsector, tmp_bh, 0); + if (ret) { + brelse(tmp_bh); + goto err_out; + } + } + + count -= oneblkwrite; + write_bytes += oneblkwrite; + fid->rwoffset += oneblkwrite; + + fid->attr |= ATTR_ARCHIVE; + + if (fid->size < fid->rwoffset) { + fid->size = fid->rwoffset; + modified = true; + } + } + + brelse(tmp_bh); + + /* (3) update the direcoty entry */ + /* get_entry_(set_)in_dir shoulb be check DIR_DELETED flag. */ + es = exfat_get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) { + ret = -EIO; + goto err_out; + } + ep2 = ep+1; + + fsi->fs_func->set_entry_time(ep, exfat_tm_now(EXFAT_SB(sb), &tm), TM_MODIFY); + fsi->fs_func->set_entry_attr(ep, fid->attr); + + if (modified) { + if (fsi->fs_func->get_entry_flag(ep2) != fid->flags) + fsi->fs_func->set_entry_flag(ep2, fid->flags); + + if (fsi->fs_func->get_entry_size(ep2) != fid->size) + fsi->fs_func->set_entry_size(ep2, fid->size); + + if (fsi->fs_func->get_entry_clu0(ep2) != fid->start_clu) + fsi->fs_func->set_entry_clu0(ep2, fid->start_clu); + } + + if (exfat_update_dir_chksum_with_entry_set(sb, es)) { + ret = -EIO; + goto err_out; + } + exfat_release_dentry_set(es); + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + +err_out: + /* set the size of written bytes */ + if (wcount) + *wcount = write_bytes; + + return ret; +} + +/* resize the file length */ +s32 exfat_fscore_truncate(struct inode *inode, u64 old_size, u64 new_size) +{ + u32 num_clusters_new, num_clusters_da, num_clusters_phys; + u32 last_clu = CLUS_FREE; + CHAIN_T clu; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + ENTRY_SET_CACHE_T *es = NULL; + s32 evict = (fid->dir.dir == DIR_DELETED) ? 1 : 0; + + /* check if the given file ID is opened */ + if ((fid->type != TYPE_FILE) && (fid->type != TYPE_DIR)) + return -EPERM; + + /* TO CHECK inode type and size */ + MMSG("%s: inode(%p) type(%s) size:%lld->%lld\n", __func__, inode, + (fid->type == TYPE_FILE) ? "file" : "dir", old_size, new_size); + + /* XXX : This is for debugging. */ + + /* It can be when write failed */ +#if 0 + if (fid->size != old_size) { + DMSG("%s: inode(%p) size-mismatch(old:%lld != fid:%lld)\n", + __func__, inode, old_size, fid->size); + WARN_ON(1); + } +#endif + /* + * There is no lock to protect fid->size. + * So, we should get old_size and use it. + */ + if (old_size <= new_size) + return 0; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* Reserved count update */ + #define num_clusters(v) ((v) ? (u32)(((v) - 1) >> fsi->cluster_size_bits) + 1 : 0) + num_clusters_da = num_clusters(EXFAT_I(inode)->i_size_aligned); + num_clusters_new = num_clusters(i_size_read(inode)); + num_clusters_phys = num_clusters(EXFAT_I(inode)->i_size_ondisk); + + /* num_clusters(i_size_old) should be equal to num_clusters_da */ + BUG_ON((num_clusters(old_size)) != (num_clusters(EXFAT_I(inode)->i_size_aligned))); + + /* for debugging (FIXME: is okay on no-da case?) */ + BUG_ON(num_clusters_da < num_clusters_phys); + + if ((num_clusters_da != num_clusters_phys) && + (num_clusters_new < num_clusters_da)) { + /* Decrement reserved clusters + * n_reserved = num_clusters_da - max(new,phys) + */ + int n_reserved = (num_clusters_new > num_clusters_phys) ? + (num_clusters_da - num_clusters_new) : + (num_clusters_da - num_clusters_phys); + + fsi->reserved_clusters -= n_reserved; + BUG_ON(fsi->reserved_clusters < 0); + } + + clu.dir = fid->start_clu; + /* In no-da case, num_clusters_phys is equal to below value + * clu.size = (u32)((old_size-1) >> fsi->cluster_size_bits) + 1; + */ + clu.size = num_clusters_phys; + clu.flags = fid->flags; + + if (new_size > 0) { + /* Truncate FAT chain num_clusters after the first cluster + * num_clusters = min(new, phys); + */ + u32 num_clusters = (num_clusters_new < num_clusters_phys) ? + num_clusters_new : num_clusters_phys; + + /* Follow FAT chain + * (defensive coding - works fine even with corrupted FAT table + */ + if (clu.flags == 0x03) { + clu.dir += num_clusters; + clu.size -= num_clusters; +#if 0 + /* exfat_extent_get_clus can`t know last_cluster + * when find target cluster in cache. + */ + } else if (fid->type == TYPE_FILE) { + u32 fclus = 0; + s32 err = exfat_extent_get_clus(inode, num_clusters, + &fclus, &(clu.dir), &last_clu, 0); + if (err) + return -EIO; + ASSERT(fclus == num_clusters); + + if ((num_clusters > 1) && (last_clu == fid->start_clu)) { + u32 fclus_tmp = 0; + u32 temp = 0; + + err = exfat_extent_get_clus(inode, num_clusters - 1, + &fclus_tmp, &last_clu, &temp, 0); + if (err) + return -EIO; + ASSERT(fclus_tmp == (num_clusters - 1)); + } + + num_clusters -= fclus; + clu.size -= fclus; +#endif + } else { + while (num_clusters > 0) { + last_clu = clu.dir; + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + + num_clusters--; + clu.size--; + } + } + + /* Optimization avialable: */ +#if 0 + if (num_clusters_new < num_clusters) { + < loop > + } else { + // num_clusters_new >= num_clusters_phys + // FAT truncation is not necessary + + clu.dir = CLUS_EOF; + clu.size = 0; + } +#endif + } else if (new_size == 0) { + fid->flags = 0x03; + fid->start_clu = CLUS_EOF; + } + fid->size = new_size; + + if (fid->type == TYPE_FILE) + fid->attr |= ATTR_ARCHIVE; + + /* + * clu.dir: free from + * clu.size: # of clusters to free (exFAT, 0x03 only), no fat_free if 0 + * clu.flags: fid->flags (exFAT only) + */ + + /* (1) update the directory entry */ + if (!evict) { + es = exfat_get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + + fsi->fs_func->set_entry_time(ep, exfat_tm_now(EXFAT_SB(sb), &tm), TM_MODIFY); + fsi->fs_func->set_entry_attr(ep, fid->attr); + + /* File size should be zero if there is no cluster allocated */ + if (IS_CLUS_EOF(fid->start_clu)) + fsi->fs_func->set_entry_size(ep2, 0); + else + fsi->fs_func->set_entry_size(ep2, new_size); + + if (new_size == 0) { + /* Any directory can not be truncated to zero */ + BUG_ON(fid->type != TYPE_FILE); + + fsi->fs_func->set_entry_flag(ep2, 0x01); + fsi->fs_func->set_entry_clu0(ep2, CLUS_FREE); + } + + if (exfat_update_dir_chksum_with_entry_set(sb, es)) + return -EIO; + exfat_release_dentry_set(es); + } + + /* (2) cut off from the FAT chain */ + if ((fid->flags == 0x01) && + (!IS_CLUS_FREE(last_clu)) && (!IS_CLUS_EOF(last_clu))) { + if (exfat_ent_set(sb, last_clu, CLUS_EOF)) + return -EIO; + } + + /* (3) invalidate cache and free the clusters */ + /* clear extent cache */ + exfat_extent_cache_inval_inode(inode); + + /* hint information */ + fid->hint_bmap.off = CLUS_EOF; + fid->hint_bmap.clu = CLUS_EOF; + if (fid->rwoffset > fid->size) + fid->rwoffset = fid->size; + + /* hint_stat will be used if this is directory. */ + fid->hint_stat.eidx = 0; + fid->hint_stat.clu = fid->start_clu; + fid->hint_femp.eidx = -1; + + /* free the clusters */ + if (fsi->fs_func->free_cluster(sb, &clu, evict)) + return -EIO; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return 0; +} + +static void update_parent_info(FILE_ID_T *fid, struct inode *parent_inode) +{ + FS_INFO_T *fsi = &(EXFAT_SB(parent_inode->i_sb)->fsi); + FILE_ID_T *parent_fid = &(EXFAT_I(parent_inode)->fid); + + /* + * the problem that FILE_ID_T caches wrong parent info. + * + * because of flag-mismatch of fid->dir, + * there is abnormal traversing cluster chain. + */ + if (unlikely((parent_fid->flags != fid->dir.flags) + || (parent_fid->size != (fid->dir.size<cluster_size_bits)) + || (parent_fid->start_clu != fid->dir.dir))) { + + fid->dir.dir = parent_fid->start_clu; + fid->dir.flags = parent_fid->flags; + fid->dir.size = ((parent_fid->size + (fsi->cluster_size-1)) + >> fsi->cluster_size_bits); + } +} + +/* rename or move a old file into a new file */ +s32 exfat_fscore_rename(struct inode *old_parent_inode, FILE_ID_T *fid, + struct inode *new_parent_inode, struct dentry *new_dentry) +{ + s32 ret; + s32 dentry; + CHAIN_T olddir, newdir; + CHAIN_T *p_dir = NULL; + UNI_NAME_T uni_name; + DENTRY_T *ep; + struct super_block *sb = old_parent_inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u8 *new_path = (u8 *) new_dentry->d_name.name; + struct inode *new_inode = new_dentry->d_inode; + int num_entries; + FILE_ID_T *new_fid = NULL; + u32 new_entry_type = TYPE_UNUSED; + s32 new_entry = 0; + + /* check the validity of pointer parameters */ + if ((new_path == NULL) || (strlen(new_path) == 0)) + return -EINVAL; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted source dentry\n", __func__); + return -ENOENT; + } + + /* patch 1.2.4 : the problem that FILE_ID_T caches wrong parent info. */ + update_parent_info(fid, old_parent_inode); + + olddir.dir = fid->dir.dir; + olddir.size = fid->dir.size; + olddir.flags = fid->dir.flags; + + dentry = fid->entry; + + ep = exfat_get_dentry_in_dir(sb, &olddir, dentry, NULL); + if (!ep) + return -EIO; + + if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; + + /* check whether new dir is existing directory and empty */ + if (new_inode) { + ret = -EIO; + new_fid = &EXFAT_I(new_inode)->fid; + + if (new_fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted target dentry\n", __func__); + goto out; + } + + /* patch 1.2.4 : + * the problem that FILE_ID_T caches wrong parent info. + * + * FIXME : is needed? + */ + update_parent_info(new_fid, new_parent_inode); + + p_dir = &(new_fid->dir); + new_entry = new_fid->entry; + ep = exfat_get_dentry_in_dir(sb, p_dir, new_entry, NULL); + if (!ep) + goto out; + + new_entry_type = fsi->fs_func->get_entry_type(ep); + + /* if new_inode exists, update fid */ + new_fid->size = i_size_read(new_inode); + + if (new_entry_type == TYPE_DIR) { + CHAIN_T new_clu; + + new_clu.dir = new_fid->start_clu; + new_clu.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1; + new_clu.flags = new_fid->flags; + + ret = check_dir_empty(sb, &new_clu); + if (ret) + return ret; + } + } + + /* check the validity of directory name in the given new pathname */ + ret = resolve_path(new_parent_inode, new_path, &newdir, &uni_name); + if (ret) + return ret; + + fs_set_vol_flags(sb, VOL_DIRTY); + + if (olddir.dir == newdir.dir) + ret = rename_file(new_parent_inode, &olddir, dentry, &uni_name, fid); + else + ret = move_file(new_parent_inode, &olddir, dentry, &newdir, &uni_name, fid); + + if ((!ret) && new_inode) { + /* delete entries of new_dir */ + ep = exfat_get_dentry_in_dir(sb, p_dir, new_entry, NULL); + if (!ep) { + ret = -EIO; + goto del_out; + } + + num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, new_entry, ep); + if (num_entries < 0) { + ret = -EIO; + goto del_out; + } + + + if (fsi->fs_func->delete_dir_entry(sb, p_dir, new_entry, 0, num_entries+1)) { + ret = -EIO; + goto del_out; + } + + /* Free the clusters if new_inode is a dir(as if exfat_fscore_rmdir) */ + if (new_entry_type == TYPE_DIR) { + /* new_fid, new_clu_to_free */ + CHAIN_T new_clu_to_free; + + new_clu_to_free.dir = new_fid->start_clu; + new_clu_to_free.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1; + new_clu_to_free.flags = new_fid->flags; + + if (fsi->fs_func->free_cluster(sb, &new_clu_to_free, 1)) { + /* just set I/O error only */ + ret = -EIO; + } + + new_fid->size = 0; + new_fid->start_clu = CLUS_EOF; + new_fid->flags = 0x03; + } +del_out: + /* Update new_inode fid + * Prevent syncing removed new_inode + * (new_fid is already initialized above code ("if (new_inode)") + */ + new_fid->dir.dir = DIR_DELETED; + } +out: + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} + +/* remove a file */ +s32 exfat_fscore_remove(struct inode *inode, FILE_ID_T *fid) +{ + s32 ret; + s32 dentry; + CHAIN_T dir, clu_to_free; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + dir.dir = fid->dir.dir; + dir.size = fid->dir.size; + dir.flags = fid->dir.flags; + + dentry = fid->entry; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + return -ENOENT; + } + + ep = exfat_get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + + if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* (1) update the directory entry */ + ret = remove_file(inode, &dir, dentry); + if (ret) + goto out; + + clu_to_free.dir = fid->start_clu; + clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1; + clu_to_free.flags = fid->flags; + + /* (2) invalidate extent cache and free the clusters + */ + /* clear extent cache */ + exfat_extent_cache_inval_inode(inode); + ret = fsi->fs_func->free_cluster(sb, &clu_to_free, 0); + /* WARN : DO NOT RETURN ERROR IN HERE */ + + /* (3) update FILE_ID_T */ + fid->size = 0; + fid->start_clu = CLUS_EOF; + fid->flags = 0x03; + fid->dir.dir = DIR_DELETED; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); +out: + return ret; +} + + +/* + * Get the information of a given file + * REMARK : This function does not need any file name on linux + * + * info.Size means the value saved on disk. + * But root directory doesn`t have real dentry, + * so the size of root directory returns calculated one exceptively. + */ +s32 exfat_fscore_read_inode(struct inode *inode, DIR_ENTRY_T *info) +{ + s32 count; + CHAIN_T dir; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + ENTRY_SET_CACHE_T *es = NULL; + u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0; + + exfat_extent_cache_init_inode(inode); + + /* if root directory */ + if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1)) { + info->Attr = ATTR_SUBDIR; + memset((s8 *) &info->CreateTimestamp, 0, sizeof(DATE_TIME_T)); + memset((s8 *) &info->ModifyTimestamp, 0, sizeof(DATE_TIME_T)); + memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T)); + //strcpy(info->NameBuf.sfn, "."); + //strcpy(info->NameBuf.lfn, "."); + + dir.dir = fsi->root_dir; + dir.flags = 0x01; + dir.size = 0; /* UNUSED */ + + /* FAT16 root_dir */ + if (IS_CLUS_FREE(fsi->root_dir)) { + info->Size = fsi->dentries_in_root << DENTRY_SIZE_BITS; + } else { + u32 num_clu; + + if (__count_num_clusters(sb, &dir, &num_clu)) + return -EIO; + info->Size = (u64)num_clu << fsi->cluster_size_bits; + } + + count = __count_dos_name_entries(sb, &dir, TYPE_DIR, NULL); + if (count < 0) + return -EIO; + info->NumSubdirs = count; + + return 0; + } + + /* get the directory entry of given file or directory */ + /* es should be released */ + es = exfat_get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_2_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + + /* set FILE_INFO structure using the acquired DENTRY_T */ + info->Attr = fsi->fs_func->get_entry_attr(ep); + + fsi->fs_func->get_entry_time(ep, &tm, TM_CREATE); + info->CreateTimestamp.Year = tm.year; + info->CreateTimestamp.Month = tm.mon; + info->CreateTimestamp.Day = tm.day; + info->CreateTimestamp.Hour = tm.hour; + info->CreateTimestamp.Minute = tm.min; + info->CreateTimestamp.Second = tm.sec; + info->CreateTimestamp.MilliSecond = 0; + + fsi->fs_func->get_entry_time(ep, &tm, TM_MODIFY); + info->ModifyTimestamp.Year = tm.year; + info->ModifyTimestamp.Month = tm.mon; + info->ModifyTimestamp.Day = tm.day; + info->ModifyTimestamp.Hour = tm.hour; + info->ModifyTimestamp.Minute = tm.min; + info->ModifyTimestamp.Second = tm.sec; + info->ModifyTimestamp.MilliSecond = 0; + + memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T)); + + info->NumSubdirs = 0; + info->Size = fsi->fs_func->get_entry_size(ep2); + + exfat_release_dentry_set(es); + + if (is_dir) { + u32 dotcnt = 0; + + dir.dir = fid->start_clu; + dir.flags = fid->flags; + dir.size = fid->size >> fsi->cluster_size_bits; + /* + * NOTE : + * If "dir.flags" has 0x01, "dir.size" is meaningless. + */ +#if 0 + if (info->Size == 0) { + s32 num_clu; + + if (__count_num_clusters(sb, &dir, &num_clu)) + return -EIO; + info->Size = (u64)num_clu << fsi->cluster_size_bits; + } +#endif + count = __count_dos_name_entries(sb, &dir, TYPE_DIR, &dotcnt); + if (count < 0) + return -EIO; + + count += EXFAT_MIN_SUBDIR; + info->NumSubdirs = count; + } + + return 0; +} + +/* set the information of a given file + * REMARK : This function does not need any file name on linux + */ +s32 exfat_fscore_write_inode(struct inode *inode, DIR_ENTRY_T *info, s32 sync) +{ + s32 ret = -EIO; + TIMESTAMP_T tm; + DENTRY_T *ep, *ep2; + ENTRY_SET_CACHE_T *es = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0; + u64 on_disk_size; + + /* SKIP WRITING INODE : + * if the indoe is already unlinked, + * there is no need for updating inode + */ + if (fid->dir.dir == DIR_DELETED) + return 0; + + if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1)) + return 0; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* get the directory entry of given file or directory */ + es = exfat_get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) + return -EIO; + ep2 = ep+1; + + fsi->fs_func->set_entry_attr(ep, info->Attr); + + /* set FILE_INFO structure using the acquired DENTRY_T */ + tm.sec = info->CreateTimestamp.Second; + tm.min = info->CreateTimestamp.Minute; + tm.hour = info->CreateTimestamp.Hour; + tm.day = info->CreateTimestamp.Day; + tm.mon = info->CreateTimestamp.Month; + tm.year = info->CreateTimestamp.Year; + fsi->fs_func->set_entry_time(ep, &tm, TM_CREATE); + + tm.sec = info->ModifyTimestamp.Second; + tm.min = info->ModifyTimestamp.Minute; + tm.hour = info->ModifyTimestamp.Hour; + tm.day = info->ModifyTimestamp.Day; + tm.mon = info->ModifyTimestamp.Month; + tm.year = info->ModifyTimestamp.Year; + fsi->fs_func->set_entry_time(ep, &tm, TM_MODIFY); + + /* File size should be zero if there is no cluster allocated */ + on_disk_size = info->Size; + + if (IS_CLUS_EOF(fid->start_clu)) + on_disk_size = 0; + + fsi->fs_func->set_entry_size(ep2, on_disk_size); + + ret = exfat_update_dir_chksum_with_entry_set(sb, es); + exfat_release_dentry_set(es); + + fs_sync(sb, sync); + /* Comment below code to prevent super block update frequently */ + //fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} + + +/* + * Input: inode, (logical) clu_offset, target allocation area + * Output: errcode, cluster number + * *clu = (~0), if it's unable to allocate a new cluster + */ +s32 exfat_fscore_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest) +{ + s32 ret, modified = false; + u32 last_clu; + CHAIN_T new_clu; + DENTRY_T *ep; + ENTRY_SET_CACHE_T *es = NULL; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + u32 local_clu_offset = clu_offset; + s32 reserved_clusters = fsi->reserved_clusters; + u32 num_to_be_allocated = 0, num_clusters = 0; + + fid->rwoffset = (s64)(clu_offset) << fsi->cluster_size_bits; + + if (EXFAT_I(inode)->i_size_ondisk > 0) + num_clusters = (u32)((EXFAT_I(inode)->i_size_ondisk-1) >> fsi->cluster_size_bits) + 1; + + if (clu_offset >= num_clusters) + num_to_be_allocated = clu_offset - num_clusters + 1; + + if ((dest == ALLOC_NOWHERE) && (num_to_be_allocated > 0)) { + *clu = CLUS_EOF; + return 0; + } + + /* check always request cluster is 1 */ + //ASSERT(num_to_be_allocated == 1); + + *clu = last_clu = fid->start_clu; + + /* XXX: Defensive code needed. + * what if i_size_ondisk != # of allocated clusters + */ + if (fid->flags == 0x03) { + if ((clu_offset > 0) && (!IS_CLUS_EOF(*clu))) { + last_clu += clu_offset - 1; + + if (clu_offset == num_clusters) + *clu = CLUS_EOF; + else + *clu += clu_offset; + } + } else if (fid->type == TYPE_FILE) { + u32 fclus = 0; + s32 err = exfat_extent_get_clus(inode, clu_offset, + &fclus, clu, &last_clu, 1); + if (err) + return -EIO; + + clu_offset -= fclus; + } else { + /* hint information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + /* hint_bmap.clu should be valid */ + ASSERT(fid->hint_bmap.clu >= 2); + *clu = fid->hint_bmap.clu; + } + + while ((clu_offset > 0) && (!IS_CLUS_EOF(*clu))) { + last_clu = *clu; + if (get_next_clus_safe(sb, clu)) + return -EIO; + clu_offset--; + } + } + + if (IS_CLUS_EOF(*clu)) { + fs_set_vol_flags(sb, VOL_DIRTY); + + new_clu.dir = (IS_CLUS_EOF(last_clu)) ? CLUS_EOF : last_clu + 1; + new_clu.size = 0; + new_clu.flags = fid->flags; + + /* (1) allocate a cluster */ + if (num_to_be_allocated < 1) { + /* Broken FAT (i_sze > allocated FAT) */ + EMSG("%s: invalid fat chain : inode(%p) " + "num_to_be_allocated(%d) " + "i_size_ondisk(%lld) fid->flags(%02x) " + "fid->start(%08x) fid->hint_off(%u) " + "fid->hint_clu(%u) fid->rwoffset(%llu) " + "modified_clu_off(%d) last_clu(%08x) " + "new_clu(%08x)", __func__, inode, + num_to_be_allocated, + (EXFAT_I(inode)->i_size_ondisk), + fid->flags, fid->start_clu, + fid->hint_bmap.off, fid->hint_bmap.clu, + fid->rwoffset, clu_offset, + last_clu, new_clu.dir); + exfat_fs_error(sb, "broken FAT chain."); + return -EIO; + } + + ret = fsi->fs_func->alloc_cluster(sb, num_to_be_allocated, &new_clu, ALLOC_COLD); + if (ret) + return ret; + + if (IS_CLUS_EOF(new_clu.dir) || IS_CLUS_FREE(new_clu.dir)) { + exfat_fs_error(sb, "bogus cluster new allocated" + "(last_clu : %u, new_clu : %u)", + last_clu, new_clu.dir); + ASSERT(0); + return -EIO; + } + + /* (2) append to the FAT chain */ + if (IS_CLUS_EOF(last_clu)) { + if (new_clu.flags == 0x01) + fid->flags = 0x01; + fid->start_clu = new_clu.dir; + modified = true; + } else { + if (new_clu.flags != fid->flags) { + /* no-fat-chain bit is disabled, + * so fat-chain should be synced with alloc-bmp + */ + exfat_chain_cont_cluster(sb, fid->start_clu, num_clusters); + fid->flags = 0x01; + modified = true; + } + if (new_clu.flags == 0x01) + if (exfat_ent_set(sb, last_clu, new_clu.dir)) + return -EIO; + } + + num_clusters += num_to_be_allocated; + *clu = new_clu.dir; + + if (fid->dir.dir != DIR_DELETED) { + es = exfat_get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); + if (!es) + return -EIO; + /* get stream entry */ + ep++; + + /* (3) update directory entry */ + if (modified) { + if (fsi->fs_func->get_entry_flag(ep) != fid->flags) + fsi->fs_func->set_entry_flag(ep, fid->flags); + + if (fsi->fs_func->get_entry_clu0(ep) != fid->start_clu) + fsi->fs_func->set_entry_clu0(ep, fid->start_clu); + + fsi->fs_func->set_entry_size(ep, fid->size); + } + + if (exfat_update_dir_chksum_with_entry_set(sb, es)) + return -EIO; + exfat_release_dentry_set(es); + } + + /* add number of new blocks to inode */ + inode->i_blocks += num_to_be_allocated << (fsi->cluster_size_bits - sb->s_blocksize_bits); +#if 0 + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); +#endif + /* (4) Move *clu pointer along FAT chains (hole care) + * because the caller of this function expect *clu to be the last cluster. + * This only works when num_to_be_allocated >= 2, + * *clu = (the first cluster of the allocated chain) => (the last cluster of ...) + */ + if (fid->flags == 0x03) { + *clu += num_to_be_allocated - 1; + } else { + while (num_to_be_allocated > 1) { + if (get_next_clus_safe(sb, clu)) + return -EIO; + num_to_be_allocated--; + } + } + + } + + /* update reserved_clusters */ + fsi->reserved_clusters = reserved_clusters; + + /* hint information */ + fid->hint_bmap.off = local_clu_offset; + fid->hint_bmap.clu = *clu; + + return 0; +} + +/* remove an entry, BUT don't truncate */ +s32 exfat_fscore_unlink(struct inode *inode, FILE_ID_T *fid) +{ + s32 dentry; + CHAIN_T dir; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + + dir.dir = fid->dir.dir; + dir.size = fid->dir.size; + dir.flags = fid->dir.flags; + + dentry = fid->entry; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + return -ENOENT; + } + + ep = exfat_get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + + if (EXFAT_SB(sb)->fsi.fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* (1) update the directory entry */ + if (remove_file(inode, &dir, dentry)) + return -EIO; + + /* This doesn't modify fid */ + fid->dir.dir = DIR_DELETED; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return 0; +} + +/* create a directory */ +s32 exfat_fscore_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid) +{ + s32 ret/*, dentry*/; + CHAIN_T dir; + UNI_NAME_T uni_name; + struct super_block *sb = inode->i_sb; + + /* check the validity of directory name in the given old pathname */ + ret = resolve_path(inode, path, &dir, &uni_name); + if (ret) + goto out; + + fs_set_vol_flags(sb, VOL_DIRTY); + + ret = create_dir(inode, &dir, &uni_name, fid); + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); +out: + return ret; +} + +/* read a directory entry from the opened directory */ +s32 exfat_fscore_readdir(struct inode *inode, DIR_ENTRY_T *dir_entry) +{ + s32 i; + s32 dentries_per_clu, dentries_per_clu_bits = 0; + u32 type, clu_offset; + u64 sector; + CHAIN_T dir, clu; + UNI_NAME_T uni_name; + TIMESTAMP_T tm; + DENTRY_T *ep; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + u32 dentry = (u32)(fid->rwoffset & 0xFFFFFFFF); /* u32 is enough for directory */ + + /* check if the given file ID is opened */ + if (fid->type != TYPE_DIR) + return -EPERM; + + if (fid->entry == -1) { + dir.dir = fsi->root_dir; + dir.size = 0; /* just initialize, but will not use */ + dir.flags = 0x01; + } else { + dir.dir = fid->start_clu; + dir.size = fid->size >> fsi->cluster_size_bits; + dir.flags = fid->flags; + exfat_debug_bug_on(dentry >= (dir.size * fsi->dentries_per_clu)); + } + + if (IS_CLUS_FREE(dir.dir)) { /* FAT16 root_dir */ + dentries_per_clu = fsi->dentries_in_root; + + /* Prevent readdir over directory size */ + if (dentry >= dentries_per_clu) { + clu.dir = CLUS_EOF; + } else { + clu.dir = dir.dir; + clu.size = dir.size; + clu.flags = dir.flags; + } + } else { + dentries_per_clu = fsi->dentries_per_clu; + dentries_per_clu_bits = ilog2(dentries_per_clu); + + clu_offset = dentry >> dentries_per_clu_bits; + clu.dir = dir.dir; + clu.size = dir.size; + clu.flags = dir.flags; + + if (clu.flags == 0x03) { + clu.dir += clu_offset; + clu.size -= clu_offset; + } else { + /* hint_information */ + if ((clu_offset > 0) && + ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && + (clu_offset >= fid->hint_bmap.off)) { + clu_offset -= fid->hint_bmap.off; + clu.dir = fid->hint_bmap.clu; + } + + while (clu_offset > 0) { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + + clu_offset--; + } + } + } + + while (!IS_CLUS_EOF(clu.dir)) { + if (IS_CLUS_FREE(dir.dir)) /* FAT16 root_dir */ + i = dentry % dentries_per_clu; + else + i = dentry & (dentries_per_clu-1); + + for ( ; i < dentries_per_clu; i++, dentry++) { + ep = exfat_get_dentry_in_dir(sb, &clu, i, §or); + if (!ep) + return -EIO; + + type = fsi->fs_func->get_entry_type(ep); + + if (type == TYPE_UNUSED) + break; + + if ((type != TYPE_FILE) && (type != TYPE_DIR)) + continue; + + exfat_dcache_lock(sb, sector); + dir_entry->Attr = fsi->fs_func->get_entry_attr(ep); + + fsi->fs_func->get_entry_time(ep, &tm, TM_CREATE); + dir_entry->CreateTimestamp.Year = tm.year; + dir_entry->CreateTimestamp.Month = tm.mon; + dir_entry->CreateTimestamp.Day = tm.day; + dir_entry->CreateTimestamp.Hour = tm.hour; + dir_entry->CreateTimestamp.Minute = tm.min; + dir_entry->CreateTimestamp.Second = tm.sec; + dir_entry->CreateTimestamp.MilliSecond = 0; + + fsi->fs_func->get_entry_time(ep, &tm, TM_MODIFY); + dir_entry->ModifyTimestamp.Year = tm.year; + dir_entry->ModifyTimestamp.Month = tm.mon; + dir_entry->ModifyTimestamp.Day = tm.day; + dir_entry->ModifyTimestamp.Hour = tm.hour; + dir_entry->ModifyTimestamp.Minute = tm.min; + dir_entry->ModifyTimestamp.Second = tm.sec; + dir_entry->ModifyTimestamp.MilliSecond = 0; + + memset((s8 *) &dir_entry->AccessTimestamp, 0, sizeof(DATE_TIME_T)); + + *(uni_name.name) = 0x0; + fsi->fs_func->get_uniname_from_ext_entry(sb, &dir, dentry, uni_name.name); + if (*(uni_name.name) == 0x0) + exfat_get_uniname_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x1); + exfat_nls_uni16s_to_vfsname(sb, &uni_name, + dir_entry->NameBuf.lfn, + dir_entry->NameBuf.lfnbuf_len); + exfat_dcache_unlock(sb, sector); + + ep = exfat_get_dentry_in_dir(sb, &clu, i+1, NULL); + if (!ep) + return -EIO; + + dir_entry->Size = fsi->fs_func->get_entry_size(ep); + + /* + * Update hint information : + * fat16 root directory does not need it. + */ + if (!IS_CLUS_FREE(dir.dir)) { + fid->hint_bmap.off = dentry >> dentries_per_clu_bits; + fid->hint_bmap.clu = clu.dir; + } + + fid->rwoffset = (s64) ++dentry; + + return 0; + } + + /* fat16 root directory */ + if (IS_CLUS_FREE(dir.dir)) + break; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &(clu.dir))) + return -EIO; + } + } + + dir_entry->NameBuf.lfn[0] = '\0'; + + fid->rwoffset = (s64)dentry; + + return 0; +} + +/* remove a directory */ +s32 exfat_fscore_rmdir(struct inode *inode, FILE_ID_T *fid) +{ + s32 ret; + s32 dentry; + DENTRY_T *ep; + CHAIN_T dir, clu_to_free; + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + dir.dir = fid->dir.dir; + dir.size = fid->dir.size; + dir.flags = fid->dir.flags; + + dentry = fid->entry; + + if (fid->dir.dir == DIR_DELETED) { + EMSG("%s : abnormal access to deleted dentry\n", __func__); + return -ENOENT; + } + + ep = exfat_get_dentry_in_dir(sb, &dir, dentry, NULL); + if (!ep) + return -EIO; + + if (EXFAT_SB(sb)->fsi.fs_func->get_entry_attr(ep) & ATTR_READONLY) + return -EPERM; + + clu_to_free.dir = fid->start_clu; + clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1; + clu_to_free.flags = fid->flags; + + ret = check_dir_empty(sb, &clu_to_free); + if (ret) { + if (ret == -EIO) + EMSG("%s : failed to check_dir_empty : err(%d)\n", + __func__, ret); + return ret; + } + + fs_set_vol_flags(sb, VOL_DIRTY); + + /* (1) update the directory entry */ + ret = remove_file(inode, &dir, dentry); + if (ret) { + EMSG("%s : failed to remove_file : err(%d)\n", __func__, ret); + return ret; + } + + fid->dir.dir = DIR_DELETED; + + fs_sync(sb, 0); + fs_set_vol_flags(sb, VOL_CLEAN); + + return ret; +} diff --git a/fs/exfat/core.h b/fs/exfat/core.h new file mode 100644 index 000000000000..258bb490c96e --- /dev/null +++ b/fs/exfat/core.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + */ + +#ifndef _EXFAT_CORE_H +#define _EXFAT_CORE_H + +#include + +#include "config.h" +#include "api.h" +#include "upcase.h" + +#define get_next_clus(sb, pclu) exfat_ent_get(sb, *(pclu), pclu) +#define get_next_clus_safe(sb, pclu) exfat_ent_get_safe(sb, *(pclu), pclu) + +/* file status */ +/* this prevents + * exfat_fscore_write_inode, exfat_fscore_map_clus, ... with the unlinked inodes + * from corrupting on-disk dentry data. + * + * The fid->dir value of unlinked inode will be DIR_DELETED + * and those functions must check if fid->dir is valid prior to + * the calling of exfat_get_dentry_in_dir() + */ +#define DIR_DELETED 0xFFFF0321 + +#define ES_2_ENTRIES 2 +#define ES_3_ENTRIES 3 +#define ES_ALL_ENTRIES 0 + +typedef struct { + u64 sector; // sector number that contains file_entry + u32 offset; // byte offset in the sector + s32 alloc_flag; // flag in stream entry. 01 for cluster chain, 03 for contig. clusters. + u32 num_entries; + void *__buf; // __buf should be the last member +} ENTRY_SET_CACHE_T; + +/* file system initialization & shutdown functions */ +s32 exfat_fscore_init(void); +s32 exfat_fscore_shutdown(void); + +/* bdev management */ +s32 exfat_fscore_check_bdi_valid(struct super_block *sb); + +/* chain management */ +s32 exfat_chain_cont_cluster(struct super_block *sb, u32 chain, u32 len); + +/* volume management functions */ +s32 exfat_fscore_mount(struct super_block *sb); +s32 exfat_fscore_umount(struct super_block *sb); +s32 exfat_fscore_statfs(struct super_block *sb, VOL_INFO_T *info); +s32 exfat_fscore_sync_fs(struct super_block *sb, s32 do_sync); +s32 exfat_fscore_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync); +u32 exfat_fscore_get_au_stat(struct super_block *sb, s32 mode); + +/* file management functions */ +s32 exfat_fscore_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid); +s32 exfat_fscore_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid); +s32 exfat_fscore_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount); +s32 exfat_fscore_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount); +s32 exfat_fscore_truncate(struct inode *inode, u64 old_size, u64 new_size); +s32 exfat_fscore_rename(struct inode *old_parent_inode, FILE_ID_T *fid, + struct inode *new_parent_inode, struct dentry *new_dentry); +s32 exfat_fscore_remove(struct inode *inode, FILE_ID_T *fid); +s32 exfat_fscore_read_inode(struct inode *inode, DIR_ENTRY_T *info); +s32 exfat_fscore_write_inode(struct inode *inode, DIR_ENTRY_T *info, int sync); +s32 exfat_fscore_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest); +s32 exfat_fscore_unlink(struct inode *inode, FILE_ID_T *fid); + +/* directory management functions */ +s32 exfat_fscore_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid); +s32 exfat_fscore_readdir(struct inode *inode, DIR_ENTRY_T *dir_ent); +s32 exfat_fscore_rmdir(struct inode *inode, FILE_ID_T *fid); + +/* core.c : core code for common */ +/* dir entry management functions */ +DENTRY_T *exfat_get_dentry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector); + +/* name conversion functions */ +void exfat_get_uniname_from_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, UNI_NAME_T *p_uniname, u8 mode); + +/* file operation functions */ +s32 exfat_walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, u32 byte_offset, u32 *clu); + +/* exfat/cache.c */ +s32 exfat_meta_cache_init(struct super_block *sb); +s32 exfat_meta_cache_shutdown(struct super_block *sb); +u8 *exfat_fcache_getblk(struct super_block *sb, u64 sec); +s32 exfat_fcache_modify(struct super_block *sb, u64 sec); +s32 exfat_fcache_release_all(struct super_block *sb); +s32 exfat_fcache_flush(struct super_block *sb, u32 sync); + +u8 *exfat_dcache_getblk(struct super_block *sb, u64 sec); +s32 exfat_dcache_modify(struct super_block *sb, u64 sec); +s32 exfat_dcache_lock(struct super_block *sb, u64 sec); +s32 exfat_dcache_unlock(struct super_block *sb, u64 sec); +s32 exfat_dcache_release(struct super_block *sb, u64 sec); +s32 exfat_dcache_release_all(struct super_block *sb); +s32 exfat_dcache_flush(struct super_block *sb, u32 sync); +s32 exfat_dcache_readahead(struct super_block *sb, u64 sec); + + +/* fatent.c */ +s32 exfat_ent_get(struct super_block *sb, u32 loc, u32 *content); +s32 exfat_ent_set(struct super_block *sb, u32 loc, u32 content); +s32 exfat_ent_get_safe(struct super_block *sb, u32 loc, u32 *content); + +/* core_exfat.c : core code for exfat */ + +s32 exfat_load_alloc_bmp(struct super_block *sb); +void exfat_free_alloc_bmp(struct super_block *sb); +ENTRY_SET_CACHE_T *exfat_get_dentry_set_in_dir(struct super_block *sb, + CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep); +void exfat_release_dentry_set(ENTRY_SET_CACHE_T *es); +s32 exfat_update_dir_chksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry); +s32 exfat_update_dir_chksum_with_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es); +s32 mount_exfat(struct super_block *sb, pbr_t *p_pbr); + +/* blkdev.c */ +s32 exfat_bdev_open_dev(struct super_block *sb); +s32 exfat_bdev_close_dev(struct super_block *sb); +s32 exfat_bdev_check_bdi_valid(struct super_block *sb); +s32 exfat_bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs); +s32 exfat_bdev_mread(struct super_block *sb, u64 secno, struct buffer_head **bh, u64 num_secs, s32 read); +s32 exfat_bdev_mwrite(struct super_block *sb, u64 secno, struct buffer_head *bh, u64 num_secs, s32 sync); +s32 exfat_bdev_sync_all(struct super_block *sb); + +/* blkdev.c : sector read/write functions */ +s32 exfat_read_sect(struct super_block *sb, u64 sec, struct buffer_head **bh, s32 read); +s32 exfat_write_sect(struct super_block *sb, u64 sec, struct buffer_head *bh, s32 sync); +s32 exfat_write_msect_zero(struct super_block *sb, u64 sec, u64 num_secs); + +/* misc.c */ +u16 exfat_calc_chksum_2byte(void *data, s32 len, u16 chksum, s32 type); + +/* extent.c */ +s32 exfat_extent_cache_init(void); +void exfat_extent_cache_shutdown(void); +void exfat_extent_cache_init_inode(struct inode *inode); +void exfat_extent_cache_inval_inode(struct inode *inode); +s32 exfat_extent_get_clus(struct inode *inode, u32 cluster, u32 *fclus, + u32 *dclus, u32 *last_dclus, s32 allow_eof); + +void exfat_set_sb_dirty(struct super_block *sb); + +#endif /* _EXFAT_CORE_H */ diff --git a/fs/exfat/core_exfat.c b/fs/exfat/core_exfat.c new file mode 100644 index 000000000000..28eba2f213a9 --- /dev/null +++ b/fs/exfat/core_exfat.c @@ -0,0 +1,1485 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * core_exfat.c: exFAT core code + */ + +#include +#include +#include +#include +#include + +#include "exfat.h" +#include "core.h" +#include +#include + +static u8 free_bit[] = { + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/* 0 ~ 19*/ + 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~ 39*/ + 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/* 40 ~ 59*/ + 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/* 60 ~ 79*/ + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,/* 80 ~ 99*/ + 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,/*100 ~ 119*/ + 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*120 ~ 139*/ + 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,/*140 ~ 159*/ + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*160 ~ 179*/ + 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,/*180 ~ 199*/ + 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*200 ~ 219*/ + 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/*220 ~ 239*/ + 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /*240 ~ 254*/ +}; + +static u8 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*/ +}; + +/* + * Directory Entry Management Functions + */ +static u32 exfat_get_entry_type(DENTRY_T *p_entry) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry; + + if (ep->type == EXFAT_UNUSED) + return TYPE_UNUSED; + if (ep->type < 0x80) + return TYPE_DELETED; + if (ep->type == 0x80) + return TYPE_INVALID; + if (ep->type < 0xA0) { + if (ep->type == 0x81) + return TYPE_BITMAP; + if (ep->type == 0x82) + return TYPE_UPCASE; + if (ep->type == 0x83) + return TYPE_VOLUME; + if (ep->type == 0x85) { + if (le16_to_cpu(ep->attr) & ATTR_SUBDIR) + return TYPE_DIR; + return TYPE_FILE; + } + return TYPE_CRITICAL_PRI; + } + if (ep->type < 0xC0) { + if (ep->type == 0xA0) + return TYPE_GUID; + if (ep->type == 0xA1) + return TYPE_PADDING; + if (ep->type == 0xA2) + return TYPE_ACLTAB; + return TYPE_BENIGN_PRI; + } + if (ep->type < 0xE0) { + if (ep->type == 0xC0) + return TYPE_STREAM; + if (ep->type == 0xC1) + return TYPE_EXTEND; + if (ep->type == 0xC2) + return TYPE_ACL; + return TYPE_CRITICAL_SEC; + } + return TYPE_BENIGN_SEC; +} + +static void exfat_set_entry_type(DENTRY_T *p_entry, u32 type) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry; + + if (type == TYPE_UNUSED) { + ep->type = 0x0; + } else if (type == TYPE_DELETED) { + ep->type &= ~0x80; + } else if (type == TYPE_STREAM) { + ep->type = 0xC0; + } else if (type == TYPE_EXTEND) { + ep->type = 0xC1; + } else if (type == TYPE_BITMAP) { + ep->type = 0x81; + } else if (type == TYPE_UPCASE) { + ep->type = 0x82; + } else if (type == TYPE_VOLUME) { + ep->type = 0x83; + } else if (type == TYPE_DIR) { + ep->type = 0x85; + ep->attr = cpu_to_le16(ATTR_SUBDIR); + } else if (type == TYPE_FILE) { + ep->type = 0x85; + ep->attr = cpu_to_le16(ATTR_ARCHIVE); + } else if (type == TYPE_SYMLINK) { + ep->type = 0x85; + ep->attr = cpu_to_le16(ATTR_ARCHIVE | ATTR_SYMLINK); + } +} + +static u32 exfat_get_entry_attr(DENTRY_T *p_entry) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + return (u32)le16_to_cpu(ep->attr); +} + +static void exfat_set_entry_attr(DENTRY_T *p_entry, u32 attr) +{ + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + ep->attr = cpu_to_le16((u16) attr); +} + +static u8 exfat_get_entry_flag(DENTRY_T *p_entry) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + return ep->flags; +} + +static void exfat_set_entry_flag(DENTRY_T *p_entry, u8 flags) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + ep->flags = flags; +} + +static u32 exfat_get_entry_clu0(DENTRY_T *p_entry) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + return (u32)le32_to_cpu(ep->start_clu); +} + +static void exfat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + ep->start_clu = cpu_to_le32(start_clu); +} + +static u64 exfat_get_entry_size(DENTRY_T *p_entry) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + return le64_to_cpu(ep->valid_size); +} + +static void exfat_set_entry_size(DENTRY_T *p_entry, u64 size) +{ + STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry; + + ep->valid_size = cpu_to_le64(size); + ep->size = cpu_to_le64(size); +} + +static void exfat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode) +{ + u16 t = 0x00, d = 0x21; + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + switch (mode) { + case TM_CREATE: + t = le16_to_cpu(ep->create_time); + d = le16_to_cpu(ep->create_date); + break; + case TM_MODIFY: + t = le16_to_cpu(ep->modify_time); + d = le16_to_cpu(ep->modify_date); + break; + case TM_ACCESS: + t = le16_to_cpu(ep->access_time); + d = le16_to_cpu(ep->access_date); + break; + } + + tp->sec = (t & 0x001F) << 1; + tp->min = (t >> 5) & 0x003F; + tp->hour = (t >> 11); + tp->day = (d & 0x001F); + tp->mon = (d >> 5) & 0x000F; + tp->year = (d >> 9); +} + +static void exfat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode) +{ + u16 t, d; + FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry; + + t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1); + d = (tp->year << 9) | (tp->mon << 5) | tp->day; + + switch (mode) { + case TM_CREATE: + ep->create_time = cpu_to_le16(t); + ep->create_date = cpu_to_le16(d); + break; + case TM_MODIFY: + ep->modify_time = cpu_to_le16(t); + ep->modify_date = cpu_to_le16(d); + break; + case TM_ACCESS: + ep->access_time = cpu_to_le16(t); + ep->access_date = cpu_to_le16(d); + break; + } +} + + +static void __init_file_entry(struct super_block *sb, FILE_DENTRY_T *ep, u32 type) +{ + TIMESTAMP_T tm, *tp; + + exfat_set_entry_type((DENTRY_T *) ep, type); + + tp = exfat_tm_now(EXFAT_SB(sb), &tm); + exfat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE); + exfat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY); + exfat_set_entry_time((DENTRY_T *) ep, tp, TM_ACCESS); + ep->create_time_ms = 0; + ep->modify_time_ms = 0; + ep->access_time_ms = 0; +} + +static void __init_strm_entry(STRM_DENTRY_T *ep, u8 flags, u32 start_clu, u64 size) +{ + exfat_set_entry_type((DENTRY_T *) ep, TYPE_STREAM); + ep->flags = flags; + ep->start_clu = cpu_to_le32(start_clu); + ep->valid_size = cpu_to_le64(size); + ep->size = cpu_to_le64(size); +} + +static void __init_name_entry(NAME_DENTRY_T *ep, u16 *uniname) +{ + s32 i; + + exfat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND); + ep->flags = 0x0; + + for (i = 0; i < 15; i++) { + ep->unicode_0_14[i] = cpu_to_le16(*uniname); + if (*uniname == 0x0) + break; + uniname++; + } +} + +static s32 exfat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type, u32 start_clu, u64 size) +{ + u64 sector; + u8 flags; + FILE_DENTRY_T *file_ep; + STRM_DENTRY_T *strm_ep; + + flags = (type == TYPE_FILE) ? 0x01 : 0x03; + + /* we cannot use exfat_get_dentry_set_in_dir here because file ep is not initialized yet */ + file_ep = (FILE_DENTRY_T *)exfat_get_dentry_in_dir(sb, p_dir, entry, §or); + if (!file_ep) + return -EIO; + + strm_ep = (STRM_DENTRY_T *)exfat_get_dentry_in_dir(sb, p_dir, entry+1, §or); + if (!strm_ep) + return -EIO; + + __init_file_entry(sb, file_ep, type); + if (exfat_dcache_modify(sb, sector)) + return -EIO; + + __init_strm_entry(strm_ep, flags, start_clu, size); + if (exfat_dcache_modify(sb, sector)) + return -EIO; + + return 0; +} + +s32 exfat_update_dir_chksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry) +{ + s32 ret = -EIO; + s32 i, num_entries; + u64 sector; + u16 chksum; + FILE_DENTRY_T *file_ep; + DENTRY_T *ep; + + file_ep = (FILE_DENTRY_T *)exfat_get_dentry_in_dir(sb, p_dir, entry, §or); + if (!file_ep) + return -EIO; + + exfat_dcache_lock(sb, sector); + + num_entries = (s32) file_ep->num_ext + 1; + chksum = exfat_calc_chksum_2byte((void *) file_ep, DENTRY_SIZE, 0, CS_DIR_ENTRY); + + for (i = 1; i < num_entries; i++) { + ep = exfat_get_dentry_in_dir(sb, p_dir, entry+i, NULL); + if (!ep) + goto out_unlock; + + chksum = exfat_calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, CS_DEFAULT); + } + + file_ep->checksum = cpu_to_le16(chksum); + ret = exfat_dcache_modify(sb, sector); +out_unlock: + exfat_dcache_unlock(sb, sector); + return ret; + +} + + +static s32 exfat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries, + UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname) +{ + s32 i; + u64 sector; + u16 *uniname = p_uniname->name; + FILE_DENTRY_T *file_ep; + STRM_DENTRY_T *strm_ep; + NAME_DENTRY_T *name_ep; + + file_ep = (FILE_DENTRY_T *)exfat_get_dentry_in_dir(sb, p_dir, entry, §or); + if (!file_ep) + return -EIO; + + file_ep->num_ext = (u8)(num_entries - 1); + exfat_dcache_modify(sb, sector); + + strm_ep = (STRM_DENTRY_T *)exfat_get_dentry_in_dir(sb, p_dir, entry+1, §or); + if (!strm_ep) + return -EIO; + + strm_ep->name_len = p_uniname->name_len; + strm_ep->name_hash = cpu_to_le16(p_uniname->name_hash); + exfat_dcache_modify(sb, sector); + + for (i = 2; i < num_entries; i++) { + name_ep = (NAME_DENTRY_T *)exfat_get_dentry_in_dir(sb, p_dir, entry+i, §or); + if (!name_ep) + return -EIO; + + __init_name_entry(name_ep, uniname); + exfat_dcache_modify(sb, sector); + uniname += 15; + } + + exfat_update_dir_chksum(sb, p_dir, entry); + + return 0; +} + + +static s32 exfat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries) +{ + s32 i; + u64 sector; + DENTRY_T *ep; + + for (i = order; i < num_entries; i++) { + ep = exfat_get_dentry_in_dir(sb, p_dir, entry+i, §or); + if (!ep) + return -EIO; + + exfat_set_entry_type(ep, TYPE_DELETED); + if (exfat_dcache_modify(sb, sector)) + return -EIO; + } + + return 0; +} + +static s32 __write_partial_entries_in_entry_set(struct super_block *sb, + ENTRY_SET_CACHE_T *es, u64 sec, u32 off, u32 count) +{ + s32 num_entries; + u32 buf_off = (off - es->offset); + u32 remaining_byte_in_sector, copy_entries; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 clu; + u8 *buf, *esbuf = (u8 *)&(es->__buf); + + MMSG("%s: es %p sec %llu off %u cnt %d\n", __func__, es, sec, off, count); + num_entries = count; + + while (num_entries) { + /* write per sector base */ + remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off; + copy_entries = min((s32)(remaining_byte_in_sector >> DENTRY_SIZE_BITS), num_entries); + buf = exfat_dcache_getblk(sb, sec); + if (!buf) + goto err_out; + MMSG("es->buf %p buf_off %u\n", esbuf, buf_off); + MMSG("copying %d entries from %p to sector %llu\n", copy_entries, (esbuf + buf_off), sec); + memcpy(buf + off, esbuf + buf_off, copy_entries << DENTRY_SIZE_BITS); + exfat_dcache_modify(sb, sec); + num_entries -= copy_entries; + + if (num_entries) { + // get next sector + if (IS_LAST_SECT_IN_CLUS(fsi, sec)) { + clu = SECT_TO_CLUS(fsi, sec); + if (es->alloc_flag == 0x03) + clu++; + else if (get_next_clus_safe(sb, &clu)) + goto err_out; + sec = CLUS_TO_SECT(fsi, clu); + } else { + sec++; + } + off = 0; + buf_off += copy_entries << DENTRY_SIZE_BITS; + } + } + + return 0; +err_out: + return -EIO; +} + +/* write back all entries in entry set */ +static s32 __write_whole_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es) +{ + return __write_partial_entries_in_entry_set(sb, es, es->sector, es->offset, es->num_entries); +} + +s32 exfat_update_dir_chksum_with_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es) +{ + DENTRY_T *ep; + u16 chksum = 0; + s32 chksum_type = CS_DIR_ENTRY, i; + + ep = (DENTRY_T *)&(es->__buf); + for (i = 0; i < es->num_entries; i++) { + MMSG("%s %p\n", __func__, ep); + chksum = exfat_calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, chksum_type); + ep++; + chksum_type = CS_DEFAULT; + } + + ep = (DENTRY_T *)&(es->__buf); + ((FILE_DENTRY_T *)ep)->checksum = cpu_to_le16(chksum); + return __write_whole_entry_set(sb, es); +} + +/* returns a set of dentries for a file or dir. + * Note that this is a copy (dump) of dentries so that user should call write_entry_set() + * to apply changes made in this entry set to the real device. + * in: + * sb+p_dir+entry: indicates a file/dir + * type: specifies how many dentries should be included. + * out: + * file_ep: will point the first dentry(= file dentry) on success + * return: + * pointer of entry set on success, + * NULL on failure. + */ + +#define ES_MODE_STARTED 0 +#define ES_MODE_GET_FILE_ENTRY 1 +#define ES_MODE_GET_STRM_ENTRY 2 +#define ES_MODE_GET_NAME_ENTRY 3 +#define ES_MODE_GET_CRITICAL_SEC_ENTRY 4 +ENTRY_SET_CACHE_T *exfat_get_dentry_set_in_dir(struct super_block *sb, + CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep) +{ + s32 ret; + u32 off, byte_offset, clu = 0; + u32 entry_type; + u64 sec; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + ENTRY_SET_CACHE_T *es = NULL; + DENTRY_T *ep, *pos; + u8 *buf; + u8 num_entries; + s32 mode = ES_MODE_STARTED; + + /* FIXME : is available in error case? */ + if (p_dir->dir == DIR_DELETED) { + EMSG("%s : access to deleted dentry\n", __func__); + BUG_ON(!fsi->prev_eio); + return NULL; + } + + MMSG("p_dir dir %u flags %x size %d\n", p_dir->dir, p_dir->flags, p_dir->size); + MMSG("entry %d type %d\n", entry, type); + + byte_offset = entry << DENTRY_SIZE_BITS; + ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu); + if (ret) + return NULL; + + /* byte offset in cluster */ + byte_offset &= fsi->cluster_size - 1; + + /* byte offset in sector */ + off = byte_offset & (u32)(sb->s_blocksize - 1); + + /* sector offset in cluster */ + sec = byte_offset >> (sb->s_blocksize_bits); + sec += CLUS_TO_SECT(fsi, clu); + + buf = exfat_dcache_getblk(sb, sec); + if (!buf) + goto err_out; + + ep = (DENTRY_T *)(buf + off); + entry_type = exfat_get_entry_type(ep); + + if ((entry_type != TYPE_FILE) + && (entry_type != TYPE_DIR)) + goto err_out; + + if (type == ES_ALL_ENTRIES) + num_entries = ((FILE_DENTRY_T *)ep)->num_ext+1; + else + num_entries = type; + + MMSG("trying to malloc %lx bytes for %d entries\n", + (unsigned long)(offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries) * sizeof(DENTRY_T)), num_entries); + es = kmalloc((offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries) * sizeof(DENTRY_T)), GFP_KERNEL); + if (!es) { + EMSG("%s: failed to alloc entryset\n", __func__); + goto err_out; + } + + es->num_entries = num_entries; + es->sector = sec; + es->offset = off; + es->alloc_flag = p_dir->flags; + + pos = (DENTRY_T *) &(es->__buf); + + while (num_entries) { + // instead of copying whole sector, we will check every entry. + // this will provide minimum stablity and consistency. + entry_type = exfat_get_entry_type(ep); + + if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) + goto err_out; + + switch (mode) { + case ES_MODE_STARTED: + if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) + mode = ES_MODE_GET_FILE_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_FILE_ENTRY: + if (entry_type == TYPE_STREAM) + mode = ES_MODE_GET_STRM_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_STRM_ENTRY: + if (entry_type == TYPE_EXTEND) + mode = ES_MODE_GET_NAME_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_NAME_ENTRY: + if (entry_type == TYPE_EXTEND) + break; + else if (entry_type == TYPE_STREAM) + goto err_out; + else if (entry_type & TYPE_CRITICAL_SEC) + mode = ES_MODE_GET_CRITICAL_SEC_ENTRY; + else + goto err_out; + break; + case ES_MODE_GET_CRITICAL_SEC_ENTRY: + if ((entry_type == TYPE_EXTEND) || (entry_type == TYPE_STREAM)) + goto err_out; + else if ((entry_type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC) + goto err_out; + break; + } + + /* copy dentry */ + memcpy(pos, ep, sizeof(DENTRY_T)); + + if (--num_entries == 0) + break; + + if (((off + DENTRY_SIZE) & (u32)(sb->s_blocksize - 1)) < + (off & (u32)(sb->s_blocksize - 1))) { + // get the next sector + if (IS_LAST_SECT_IN_CLUS(fsi, sec)) { + if (es->alloc_flag == 0x03) + clu++; + else if (get_next_clus_safe(sb, &clu)) + goto err_out; + sec = CLUS_TO_SECT(fsi, clu); + } else { + sec++; + } + buf = exfat_dcache_getblk(sb, sec); + if (!buf) + goto err_out; + off = 0; + ep = (DENTRY_T *)(buf); + } else { + ep++; + off += DENTRY_SIZE; + } + pos++; + } + + if (file_ep) + *file_ep = (DENTRY_T *)&(es->__buf); + + MMSG("es sec %llu offset %u flags %d, num_entries %u buf ptr %p\n", + es->sector, es->offset, es->alloc_flag, es->num_entries, &(es->__buf)); + return es; + +err_out: + /* kfree(NULL) is safe */ + kfree(es); + es = NULL; + return NULL; +} + +void exfat_release_dentry_set(ENTRY_SET_CACHE_T *es) +{ + /* kfree(NULL) is safe */ + kfree(es); + es = NULL; +} + +static s32 __extract_uni_name_from_name_entry(NAME_DENTRY_T *ep, u16 *uniname, s32 order) +{ + s32 i, len = 0; + + for (i = 0; i < 15; i++) { + /* FIXME : unaligned? */ + *uniname = le16_to_cpu(ep->unicode_0_14[i]); + if (*uniname == 0x0) + return len; + uniname++; + len++; + } + + *uniname = 0x0; + return len; + +} + +#define DIRENT_STEP_FILE (0) +#define DIRENT_STEP_STRM (1) +#define DIRENT_STEP_NAME (2) +#define DIRENT_STEP_SECD (3) + +/* return values of exfat_find_dir_entry() + * >= 0 : return dir entiry position with the name in dir + * -EEXIST : (root dir, ".") it is the root dir itself + * -ENOENT : entry with the name does not exist + * -EIO : I/O error + */ +static s32 exfat_find_dir_entry(struct super_block *sb, FILE_ID_T *fid, + CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *unused, u32 type) +{ + s32 i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; + s32 order, step, name_len = 0; + s32 dentries_per_clu, num_empty = 0; + u32 entry_type; + u16 entry_uniname[16], *uniname = NULL, unichar; + CHAIN_T clu; + DENTRY_T *ep; + HINT_T *hint_stat = &fid->hint_stat; + HINT_FEMP_T candi_empty; + FILE_DENTRY_T *file_ep; + STRM_DENTRY_T *strm_ep; + NAME_DENTRY_T *name_ep; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + /* + * REMARK: + * DOT and DOTDOT are handled by VFS layer + */ + + if (IS_CLUS_FREE(p_dir->dir)) + return -EIO; + + dentries_per_clu = fsi->dentries_per_clu; + + clu.dir = p_dir->dir; + clu.size = p_dir->size; + clu.flags = p_dir->flags; + + if (hint_stat->eidx) { + clu.dir = hint_stat->clu; + dentry = hint_stat->eidx; + end_eidx = dentry; + } + + candi_empty.eidx = -1; +rewind: + order = 0; + step = DIRENT_STEP_FILE; + while (!IS_CLUS_EOF(clu.dir)) { + i = dentry & (dentries_per_clu - 1); + for (; i < dentries_per_clu; i++, dentry++) { + if (rewind && (dentry == end_eidx)) + goto not_found; + + ep = exfat_get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + entry_type = exfat_get_entry_type(ep); + + if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) { + step = DIRENT_STEP_FILE; + + num_empty++; + if (candi_empty.eidx == -1) { + if (num_empty == 1) { + candi_empty.cur.dir = clu.dir; + candi_empty.cur.size = clu.size; + candi_empty.cur.flags = clu.flags; + } + + if (num_empty >= num_entries) { + candi_empty.eidx = dentry - (num_empty - 1); + ASSERT(0 <= candi_empty.eidx); + candi_empty.count = num_empty; + + if ((fid->hint_femp.eidx == -1) || + (candi_empty.eidx <= fid->hint_femp.eidx)) { + memcpy(&fid->hint_femp, + &candi_empty, + sizeof(HINT_FEMP_T)); + } + } + } + + if (entry_type == TYPE_UNUSED) + goto not_found; + continue; + } + + num_empty = 0; + candi_empty.eidx = -1; + + if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) { + step = DIRENT_STEP_FILE; + if ((type == TYPE_ALL) || (type == entry_type)) { + file_ep = (FILE_DENTRY_T *) ep; + num_ext = file_ep->num_ext; + step = DIRENT_STEP_STRM; + } + continue; + } + + if (entry_type == TYPE_STREAM) { + if (step != DIRENT_STEP_STRM) { + step = DIRENT_STEP_FILE; + continue; + } + step = DIRENT_STEP_FILE; + strm_ep = (STRM_DENTRY_T *) ep; + if ((p_uniname->name_hash == le16_to_cpu(strm_ep->name_hash)) && + (p_uniname->name_len == strm_ep->name_len)) { + step = DIRENT_STEP_NAME; + order = 1; + name_len = 0; + } + continue; + } + + if (entry_type == TYPE_EXTEND) { + if (step != DIRENT_STEP_NAME) { + step = DIRENT_STEP_FILE; + continue; + } + name_ep = (NAME_DENTRY_T *) ep; + + if ((++order) == 2) + uniname = p_uniname->name; + else + uniname += 15; + + len = __extract_uni_name_from_name_entry(name_ep, entry_uniname, order); + name_len += len; + + unichar = *(uniname+len); + *(uniname+len) = 0x0; + + if (exfat_nls_cmp_uniname(sb, uniname, entry_uniname)) { + step = DIRENT_STEP_FILE; + } else if (name_len == p_uniname->name_len) { + if (order == num_ext) { + //fid->hint_femp.eidx = -1; + goto found; + } + step = DIRENT_STEP_SECD; + } + + *(uniname+len) = unichar; + continue; + } + + if (entry_type & (TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) { + if (step == DIRENT_STEP_SECD) { + if (++order == num_ext) + goto found; + continue; + } + } + step = DIRENT_STEP_FILE; + } + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + if (get_next_clus_safe(sb, &clu.dir)) + return -EIO; + } + } + +not_found: + /* we started at not 0 index,so we should try to find target + * from 0 index to the index we started at. + */ + if (!rewind && end_eidx) { + rewind = 1; + dentry = 0; + clu.dir = p_dir->dir; + /* reset empty hint */ + num_empty = 0; + candi_empty.eidx = -1; + goto rewind; + } + + /* initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; + return -ENOENT; + +found: + /* next dentry we'll find is out of this cluster */ + if (!((dentry + 1) & (dentries_per_clu-1))) { + int ret = 0; + + if (clu.flags == 0x03) { + if ((--clu.size) > 0) + clu.dir++; + else + clu.dir = CLUS_EOF; + } else { + ret = get_next_clus_safe(sb, &clu.dir); + } + + if (ret || IS_CLUS_EOF(clu.dir)) { + /* just initialized hint_stat */ + hint_stat->clu = p_dir->dir; + hint_stat->eidx = 0; + return (dentry - num_ext); + } + } + + hint_stat->clu = clu.dir; + hint_stat->eidx = dentry + 1; + return (dentry - num_ext); +} + +/* returns -EIO on error */ +static s32 exfat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry) +{ + s32 i, count = 0; + u32 type; + FILE_DENTRY_T *file_ep = (FILE_DENTRY_T *) p_entry; + DENTRY_T *ext_ep; + + for (i = 0, entry++; i < file_ep->num_ext; i++, entry++) { + ext_ep = exfat_get_dentry_in_dir(sb, p_dir, entry, NULL); + if (!ext_ep) + return -EIO; + + type = exfat_get_entry_type(ext_ep); + if ((type == TYPE_EXTEND) || (type == TYPE_STREAM)) + count++; + else + return count; + } + + return count; +} + + +/* + * Name Conversion Functions + */ +static void exfat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname) +{ + s32 i; + DENTRY_T *ep; + ENTRY_SET_CACHE_T *es; + + es = exfat_get_dentry_set_in_dir(sb, p_dir, entry, ES_ALL_ENTRIES, &ep); + if (!es) + return; + + if (es->num_entries < 3) + goto out; + + ep += 2; + + /* + * First entry : file entry + * Second entry : stream-extension entry + * Third entry : first file-name entry + * So, the index of first file-name dentry should start from 2. + */ + for (i = 2; i < es->num_entries; i++, ep++) { + if (exfat_get_entry_type(ep) != TYPE_EXTEND) + goto out; + + __extract_uni_name_from_name_entry((NAME_DENTRY_T *)ep, uniname, i); + uniname += 15; + } + +out: + exfat_release_dentry_set(es); +} + +static s32 exfat_calc_num_entries(UNI_NAME_T *p_uniname) +{ + s32 len; + + len = p_uniname->name_len; + if (len == 0) + return 0; + + /* 1 file entry + 1 stream entry + name entries */ + return((len-1) / 15 + 3); + +} + +static s32 exfat_check_max_dentries(FILE_ID_T *fid) +{ + if ((fid->size >> DENTRY_SIZE_BITS) >= MAX_EXFAT_DENTRIES) { + /* exFAT spec allows a dir to grow upto 8388608(256MB) dentries */ + return -ENOSPC; + } + return 0; +} + +/* + * Allocation Bitmap Management Functions + */ +s32 exfat_load_alloc_bmp(struct super_block *sb) +{ + s32 ret; + u32 i, j, map_size, type, need_map_size; + u64 sector; + CHAIN_T clu; + BMAP_DENTRY_T *ep; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + clu.dir = fsi->root_dir; + clu.flags = 0x01; + + while (!IS_CLUS_EOF(clu.dir)) { + for (i = 0; i < fsi->dentries_per_clu; i++) { + ep = (BMAP_DENTRY_T *) exfat_get_dentry_in_dir(sb, &clu, i, NULL); + if (!ep) + return -EIO; + + type = exfat_get_entry_type((DENTRY_T *) ep); + + if (type == TYPE_UNUSED) + break; + if (type != TYPE_BITMAP) + continue; + + if (ep->flags == 0x0) { + fsi->map_clu = le32_to_cpu(ep->start_clu); + map_size = (u32) le64_to_cpu(ep->size); + + need_map_size = (((fsi->num_clusters - CLUS_BASE) - 1) >> 3) + 1; + if (need_map_size != map_size) { + exfat_log_msg(sb, KERN_ERR, + "bogus allocation bitmap size(need : %u, cur : %u)", + need_map_size, map_size); + /* Only allowed when bogus allocation bitmap size is large */ + if (need_map_size > map_size) + return -EIO; + } + fsi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1; + fsi->vol_amap = + kmalloc((sizeof(struct buffer_head *) * fsi->map_sectors), GFP_KERNEL); + if (!fsi->vol_amap) + return -ENOMEM; + + sector = CLUS_TO_SECT(fsi, fsi->map_clu); + + for (j = 0; j < fsi->map_sectors; j++) { + fsi->vol_amap[j] = NULL; + ret = exfat_read_sect(sb, sector+j, &(fsi->vol_amap[j]), 1); + if (ret) { + /* release all buffers and free vol_amap */ + i = 0; + while (i < j) + brelse(fsi->vol_amap[i++]); + + /* kfree(NULL) is safe */ + kfree(fsi->vol_amap); + fsi->vol_amap = NULL; + return ret; + } + } + + fsi->pbr_bh = NULL; + return 0; + } + } + + if (get_next_clus_safe(sb, &clu.dir)) + return -EIO; + } + + return -EINVAL; +} + +void exfat_free_alloc_bmp(struct super_block *sb) +{ + s32 i; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + brelse(fsi->pbr_bh); + + for (i = 0; i < fsi->map_sectors; i++) + __brelse(fsi->vol_amap[i]); + + /* kfree(NULL) is safe */ + kfree(fsi->vol_amap); + fsi->vol_amap = NULL; +} + +/* WARN : + * If the value of "clu" is 0, it means cluster 2 which is + * the first cluster of cluster heap. + */ +static s32 set_alloc_bitmap(struct super_block *sb, u32 clu) +{ + s32 i, b; + u64 sector; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + i = clu >> (sb->s_blocksize_bits + 3); + b = clu & (u32)((sb->s_blocksize << 3) - 1); + + sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i; + bitmap_set((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1); + + return exfat_write_sect(sb, sector, fsi->vol_amap[i], 0); +} + +/* WARN : + * If the value of "clu" is 0, it means cluster 2 which is + * the first cluster of cluster heap. + */ +static s32 clr_alloc_bitmap(struct super_block *sb, u32 clu) +{ + s32 ret; + s32 i, b; + u64 sector; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_mount_options *opts = &sbi->options; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + i = clu >> (sb->s_blocksize_bits + 3); + b = clu & (u32)((sb->s_blocksize << 3) - 1); + + sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i; + + bitmap_clear((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1); + + ret = exfat_write_sect(sb, sector, fsi->vol_amap[i], 0); + + if (opts->discard) { + s32 ret_discard; + + ret_discard = sb_issue_discard(sb, CLUS_TO_SECT(fsi, clu+2), + (1 << fsi->sect_per_clus_bits), GFP_NOFS, 0); + + if (ret_discard == -EOPNOTSUPP) { + exfat_msg(sb, KERN_ERR, + "discard not supported by device, disabling"); + opts->discard = 0; + } + } + + return ret; +} + +/* WARN : + * If the value of "clu" is 0, it means cluster 2 which is + * the first cluster of cluster heap. + */ +static u32 test_alloc_bitmap(struct super_block *sb, u32 clu) +{ + u32 i, map_i, map_b; + u32 clu_base, clu_free; + u8 k, clu_mask; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + clu_base = (clu & ~(0x7)) + 2; + clu_mask = (1 << (clu - clu_base + 2)) - 1; + + map_i = clu >> (sb->s_blocksize_bits + 3); + map_b = (clu >> 3) & (u32)(sb->s_blocksize - 1); + + for (i = 2; i < fsi->num_clusters; i += 8) { + k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b); + if (clu_mask > 0) { + k |= clu_mask; + clu_mask = 0; + } + if (k < 0xFF) { + clu_free = clu_base + free_bit[k]; + if (clu_free < fsi->num_clusters) + return clu_free; + } + clu_base += 8; + + if (((++map_b) >= (u32)sb->s_blocksize) || + (clu_base >= fsi->num_clusters)) { + if ((++map_i) >= fsi->map_sectors) { + clu_base = 2; + map_i = 0; + } + map_b = 0; + } + } + + return CLUS_EOF; +} + +s32 exfat_chain_cont_cluster(struct super_block *sb, u32 chain, u32 len) +{ + if (!len) + return 0; + + while (len > 1) { + if (exfat_ent_set(sb, chain, chain+1)) + return -EIO; + chain++; + len--; + } + + if (exfat_ent_set(sb, chain, CLUS_EOF)) + return -EIO; + return 0; +} + +static s32 exfat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse) +{ + s32 ret = -EIO; + u32 num_clusters = 0; + u32 clu; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + s32 i; + u64 sector; + + /* invalid cluster number */ + if (IS_CLUS_FREE(p_chain->dir) || IS_CLUS_EOF(p_chain->dir)) + return 0; + + /* no cluster to truncate */ + if (p_chain->size == 0) { + DMSG("%s: cluster(%u) truncation is not required.", + __func__, p_chain->dir); + return 0; + } + + /* check cluster validation */ + if ((p_chain->dir < 2) && (p_chain->dir >= fsi->num_clusters)) { + EMSG("%s: invalid start cluster (%u)\n", __func__, p_chain->dir); + exfat_debug_bug_on(1); + return -EIO; + } + + exfat_set_sb_dirty(sb); + clu = p_chain->dir; + + if (p_chain->flags == 0x03) { + do { + if (do_relse) { + sector = CLUS_TO_SECT(fsi, clu); + for (i = 0; i < fsi->sect_per_clus; i++) { + if (exfat_dcache_release(sb, sector+i) == -EIO) + goto out; + } + } + + if (clr_alloc_bitmap(sb, clu-2)) + goto out; + clu++; + + num_clusters++; + } while (num_clusters < p_chain->size); + } else { + do { + if (do_relse) { + sector = CLUS_TO_SECT(fsi, clu); + for (i = 0; i < fsi->sect_per_clus; i++) { + if (exfat_dcache_release(sb, sector+i) == -EIO) + goto out; + } + } + + if (clr_alloc_bitmap(sb, (clu - CLUS_BASE))) + goto out; + + if (get_next_clus_safe(sb, &clu)) + goto out; + + num_clusters++; + } while (!IS_CLUS_EOF(clu)); + } + + /* success */ + ret = 0; +out: + + fsi->used_clusters -= num_clusters; + return ret; +} + +static s32 exfat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest) +{ + s32 ret = -ENOSPC; + u32 num_clusters = 0, total_cnt; + u32 hint_clu, new_clu, last_clu = CLUS_EOF; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + total_cnt = fsi->num_clusters - CLUS_BASE; + + if (unlikely(total_cnt < fsi->used_clusters)) { + exfat_fs_error_ratelimit(sb, + "%s: invalid used clusters(t:%u,u:%u)\n", + __func__, total_cnt, fsi->used_clusters); + return -EIO; + } + + if (num_alloc > total_cnt - fsi->used_clusters) + return -ENOSPC; + + hint_clu = p_chain->dir; + /* find new cluster */ + if (IS_CLUS_EOF(hint_clu)) { + if (fsi->clu_srch_ptr < CLUS_BASE) { + EMSG("%s: fsi->clu_srch_ptr is invalid (%u)\n", + __func__, fsi->clu_srch_ptr); + ASSERT(0); + fsi->clu_srch_ptr = CLUS_BASE; + } + + hint_clu = test_alloc_bitmap(sb, fsi->clu_srch_ptr - CLUS_BASE); + if (IS_CLUS_EOF(hint_clu)) + return -ENOSPC; + } + + /* check cluster validation */ + if ((hint_clu < CLUS_BASE) && (hint_clu >= fsi->num_clusters)) { + EMSG("%s: hint_cluster is invalid (%u)\n", __func__, hint_clu); + ASSERT(0); + hint_clu = CLUS_BASE; + if (p_chain->flags == 0x03) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) + return -EIO; + p_chain->flags = 0x01; + } + } + + exfat_set_sb_dirty(sb); + + p_chain->dir = CLUS_EOF; + + while ((new_clu = test_alloc_bitmap(sb, hint_clu - CLUS_BASE)) != CLUS_EOF) { + if ((new_clu != hint_clu) && (p_chain->flags == 0x03)) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) { + ret = -EIO; + goto error; + } + p_chain->flags = 0x01; + } + + /* update allocation bitmap */ + if (set_alloc_bitmap(sb, new_clu - CLUS_BASE)) { + ret = -EIO; + goto error; + } + + num_clusters++; + + /* update FAT table */ + if (p_chain->flags == 0x01) { + if (exfat_ent_set(sb, new_clu, CLUS_EOF)) { + ret = -EIO; + goto error; + } + } + + if (IS_CLUS_EOF(p_chain->dir)) { + p_chain->dir = new_clu; + } else if (p_chain->flags == 0x01) { + if (exfat_ent_set(sb, last_clu, new_clu)) { + ret = -EIO; + goto error; + } + } + last_clu = new_clu; + + if ((--num_alloc) == 0) { + fsi->clu_srch_ptr = hint_clu; + fsi->used_clusters += num_clusters; + + p_chain->size += num_clusters; + return 0; + } + + hint_clu = new_clu + 1; + if (hint_clu >= fsi->num_clusters) { + hint_clu = CLUS_BASE; + + if (p_chain->flags == 0x03) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) { + ret = -EIO; + goto error; + } + p_chain->flags = 0x01; + } + } + } +error: + if (num_clusters) + exfat_free_cluster(sb, p_chain, 0); + return ret; +} + +static s32 exfat_count_used_clusters(struct super_block *sb, u32 *ret_count) +{ + u32 count = 0; + u32 i, map_i, map_b; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 total_clus = fsi->num_clusters - 2; + + map_i = map_b = 0; + + for (i = 0; i < total_clus; i += 8) { + u8 k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b); + + count += used_bit[k]; + if ((++map_b) >= (u32)sb->s_blocksize) { + map_i++; + map_b = 0; + } + } + + /* FIXME : abnormal bitmap count should be handled as more smart */ + if (total_clus < count) + count = total_clus; + + *ret_count = count; + return 0; +} + + +/* + * File Operation Functions + */ +static FS_FUNC_T exfat_fs_func = { + .alloc_cluster = exfat_alloc_cluster, + .free_cluster = exfat_free_cluster, + .count_used_clusters = exfat_count_used_clusters, + + .init_dir_entry = exfat_init_dir_entry, + .init_ext_entry = exfat_init_ext_entry, + .find_dir_entry = exfat_find_dir_entry, + .delete_dir_entry = exfat_delete_dir_entry, + .get_uniname_from_ext_entry = exfat_get_uniname_from_ext_entry, + .count_ext_entries = exfat_count_ext_entries, + .calc_num_entries = exfat_calc_num_entries, + .check_max_dentries = exfat_check_max_dentries, + + .get_entry_type = exfat_get_entry_type, + .set_entry_type = exfat_set_entry_type, + .get_entry_attr = exfat_get_entry_attr, + .set_entry_attr = exfat_set_entry_attr, + .get_entry_flag = exfat_get_entry_flag, + .set_entry_flag = exfat_set_entry_flag, + .get_entry_clu0 = exfat_get_entry_clu0, + .set_entry_clu0 = exfat_set_entry_clu0, + .get_entry_size = exfat_get_entry_size, + .set_entry_size = exfat_set_entry_size, + .get_entry_time = exfat_get_entry_time, + .set_entry_time = exfat_set_entry_time, +}; + +s32 mount_exfat(struct super_block *sb, pbr_t *p_pbr) +{ + pbr64_t *p_bpb = (pbr64_t *)p_pbr; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (!p_bpb->bsx.num_fats) { + exfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); + return -EINVAL; + } + + fsi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits; + fsi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits; + fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits; + fsi->cluster_size = 1 << fsi->cluster_size_bits; + + fsi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length); + + fsi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset); + if (p_bpb->bsx.num_fats == 1) + fsi->FAT2_start_sector = fsi->FAT1_start_sector; + else + fsi->FAT2_start_sector = fsi->FAT1_start_sector + fsi->num_FAT_sectors; + + fsi->root_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset); + fsi->data_start_sector = fsi->root_start_sector; + + fsi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length); + fsi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) + 2; + /* because the cluster index starts with 2 */ + + fsi->vol_id = le32_to_cpu(p_bpb->bsx.vol_serial); + + fsi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster); + fsi->dentries_in_root = 0; + fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS); + + fsi->vol_flag = (u32) le16_to_cpu(p_bpb->bsx.vol_flags); + fsi->clu_srch_ptr = CLUS_BASE; + fsi->used_clusters = UINT_MAX; + + fsi->fs_func = &exfat_fs_func; + + if (p_bpb->bsx.vol_flags & VOL_DIRTY) { + fsi->vol_flag |= VOL_DIRTY; + exfat_log_msg(sb, KERN_WARNING, "Volume was not properly " + "unmounted. Some data may be corrupt. " + "Please run fsck."); + } + + return 0; +} diff --git a/fs/exfat/debian/changelog b/fs/exfat/debian/changelog new file mode 100644 index 000000000000..1da1548e291d --- /dev/null +++ b/fs/exfat/debian/changelog @@ -0,0 +1,19 @@ +exfat-dkms (2.2.0-3arter97) UNRELEASED; urgency=medium + + * Added "quiet" mount option. + + -- Park Ju Hyung Tue, 29 Oct 2019 17:34:32 +0900 + +exfat-dkms (2.2.0-2arter97) UNRELEASED; urgency=medium + + * Fix xattr support. + * Merged upstream exFAT changes. + * More code clean-ups. + + -- Park Ju Hyung Wed, 9 Oct 2019 23:16:22 +0900 + +exfat-dkms (2.2.0-1arter97) UNRELEASED; urgency=medium + + * Initial release. + + -- Park Ju Hyung Tue, 13 Aug 2019 08:53:31 +0900 diff --git a/fs/exfat/debian/compat b/fs/exfat/debian/compat new file mode 100644 index 000000000000..f599e28b8ab0 --- /dev/null +++ b/fs/exfat/debian/compat @@ -0,0 +1 @@ +10 diff --git a/fs/exfat/debian/control b/fs/exfat/debian/control new file mode 100644 index 000000000000..d6ccaabb25b1 --- /dev/null +++ b/fs/exfat/debian/control @@ -0,0 +1,11 @@ +Source: exfat-dkms +Section: misc +Priority: optional +Maintainer: Park Ju Hyung +Build-Depends: debhelper (>= 9), dkms + +Package: exfat-dkms +Architecture: all +Depends: ${misc:Depends}, dkms +Conflicts: exfat-fuse +Description: exFAT filesystem driver for Linux kernel diff --git a/fs/exfat/debian/copyright b/fs/exfat/debian/copyright new file mode 100644 index 000000000000..ac85309b07f4 --- /dev/null +++ b/fs/exfat/debian/copyright @@ -0,0 +1,6 @@ +Author: Park Ju Hyung + +License: + +GNU General Public License version 2, see /usr/share/common-licenses/GPL-2 +for more information. diff --git a/fs/exfat/debian/exfat-dkms.dkms b/fs/exfat/debian/exfat-dkms.dkms new file mode 100644 index 000000000000..bdb443c8d685 --- /dev/null +++ b/fs/exfat/debian/exfat-dkms.dkms @@ -0,0 +1,5 @@ +PACKAGE_NAME="exfat" +PACKAGE_VERSION="#MODULE_VERSION#" +BUILT_MODULE_NAME[0]="$PACKAGE_NAME" +DEST_MODULE_LOCATION[0]="/extra/dkms" +AUTOINSTALL="YES" diff --git a/fs/exfat/debian/exfat-dkms.install b/fs/exfat/debian/exfat-dkms.install new file mode 100644 index 000000000000..b601f22c481a --- /dev/null +++ b/fs/exfat/debian/exfat-dkms.install @@ -0,0 +1 @@ +usr/src diff --git a/fs/exfat/debian/local/print_rule.mk b/fs/exfat/debian/local/print_rule.mk new file mode 100644 index 000000000000..43f1c668629a --- /dev/null +++ b/fs/exfat/debian/local/print_rule.mk @@ -0,0 +1,2 @@ +writeKbuild: + @printf 'obj-m += exfat.o\nexfat-objs := %s\n' "$(strip $(exfat-objs))" > $(KBUILD_PATH) diff --git a/fs/exfat/debian/rules b/fs/exfat/debian/rules new file mode 100755 index 000000000000..5c9a58b14e9f --- /dev/null +++ b/fs/exfat/debian/rules @@ -0,0 +1,23 @@ +#!/usr/bin/make -f + +include /usr/share/dpkg/pkg-info.mk + +version := $(shell dpkg-parsechangelog | grep '^Version:' | cut -d' ' -f2) +targetdir := debian/tmp/usr/src/exfat-$(version) + +%: + dh $@ --with dkms + +override_dh_auto_install: + mkdir -p $(targetdir) + install -m644 *.c *.h Kconfig LICENSE Makefile $(targetdir) + # create a Kbuild file instead of patching the Makefile + $(MAKE) KERNELRELEASE=1 KBUILD_PATH=$(targetdir)/Kbuild -s -f Makefile -f debian/local/print_rule.mk writeKbuild + +override_dh_dkms: + dh_dkms -V $(version) + +override_dh_auto_configure: +override_dh_auto_build: +override_dh_auto_test: +override_dh_auto_clean: diff --git a/fs/exfat/exfat.h b/fs/exfat/exfat.h new file mode 100644 index 000000000000..e1b6be50328f --- /dev/null +++ b/fs/exfat/exfat.h @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + */ + +#ifndef _EXFAT_H +#define _EXFAT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "api.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) +#include +#define INC_IVERSION(x) (inode_inc_iversion(x)) +#define GET_IVERSION(x) (inode_peek_iversion_raw(x)) +#define SET_IVERSION(x,y) (inode_set_iversion(x, y)) +#else +#define INC_IVERSION(x) (x->i_version++) +#define GET_IVERSION(x) (x->i_version) +#define SET_IVERSION(x,y) (x->i_version = y) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) +#define timespec_compat timespec64 +#define KTIME_GET_REAL_TS ktime_get_real_ts64 +#else +#define timespec_compat timespec +#define KTIME_GET_REAL_TS ktime_get_real_ts +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +#define EXFAT_IS_SB_RDONLY(sb) ((sb)->s_flags & MS_RDONLY) +#else +#define EXFAT_IS_SB_RDONLY(sb) ((sb)->s_flags & SB_RDONLY) +#endif + +/* + * exfat error flags + */ +#define EXFAT_ERRORS_CONT (1) /* ignore error and continue */ +#define EXFAT_ERRORS_PANIC (2) /* panic on error */ +#define EXFAT_ERRORS_RO (3) /* remount r/o on error */ + +/* + * exfat allocator destination for smart allocation + */ +#define ALLOC_NOWHERE (0) +#define ALLOC_COLD (1) +#define ALLOC_HOT (16) +#define ALLOC_COLD_ALIGNED (1) +#define ALLOC_COLD_PACKING (2) +#define ALLOC_COLD_SEQ (4) + +/* + * exfat nls lossy flag + */ +#define NLS_NAME_NO_LOSSY (0x00) /* no lossy */ +#define NLS_NAME_LOSSY (0x01) /* just detected incorrect filename(s) */ +#define NLS_NAME_OVERLEN (0x02) /* the length is over than its limit */ + +/* + * exfat common MACRO + */ +#define CLUSTER_16(x) ((u16)((x) & 0xFFFFU)) +#define CLUSTER_32(x) ((u32)((x) & 0xFFFFFFFFU)) +#define CLUS_EOF CLUSTER_32(~0) +#define CLUS_BAD (0xFFFFFFF7U) +#define CLUS_FREE (0) +#define CLUS_BASE (2) +#define IS_CLUS_EOF(x) ((x) == CLUS_EOF) +#define IS_CLUS_BAD(x) ((x) == CLUS_BAD) +#define IS_CLUS_FREE(x) ((x) == CLUS_FREE) +#define IS_LAST_SECT_IN_CLUS(fsi, sec) \ + ((((sec) - (fsi)->data_start_sector + 1) \ + & ((1 << (fsi)->sect_per_clus_bits) - 1)) == 0) + +#define CLUS_TO_SECT(fsi, x) \ + ((((unsigned long long)(x) - CLUS_BASE) << (fsi)->sect_per_clus_bits) + (fsi)->data_start_sector) + +#define SECT_TO_CLUS(fsi, sec) \ + ((u32)((((sec) - (fsi)->data_start_sector) >> (fsi)->sect_per_clus_bits) + CLUS_BASE)) + +/* + * exfat mount in-memory data + */ +struct exfat_mount_options { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + kuid_t fs_uid; + kgid_t fs_gid; +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) */ + uid_t fs_uid; + gid_t fs_gid; +#endif + unsigned short fs_fmask; + unsigned short fs_dmask; + unsigned short allow_utime; /* permission for setting the [am]time */ + unsigned short codepage; /* codepage for shortname conversions */ + char *iocharset; /* charset for filename input/display */ + unsigned char quiet; /* fake return success on setattr(e.g. chmods/chowns) */ + + unsigned char utf8; + unsigned char casesensitive; + unsigned char tz_utc; + unsigned char symlink; /* support symlink operation */ + unsigned char errors; /* on error: continue, panic, remount-ro */ + unsigned char discard; /* flag on if -o dicard specified and device support discard() */ + unsigned char delayed_meta; /* delay flushing dirty metadata */ +}; + +#define EXFAT_HASH_BITS 8 +#define EXFAT_HASH_SIZE (1UL << EXFAT_HASH_BITS) + +/* + * EXFAT file system superblock in-memory data + */ +struct exfat_sb_info { + FS_INFO_T fsi; /* private filesystem info */ + + struct mutex s_vlock; /* volume lock */ + int use_vmalloc; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) + int s_dirt; + struct mutex s_lock; /* superblock lock */ + int write_super_queued; /* Write_super work is pending? */ + struct delayed_work write_super_work; /* Work_queue data structrue for write_super() */ + spinlock_t work_lock; /* Lock for WQ */ +#endif + struct super_block *host_sb; /* sb pointer */ + struct exfat_mount_options options; + struct nls_table *nls_disk; /* Codepage used on disk */ + struct nls_table *nls_io; /* Charset used for input and display */ + struct ratelimit_state ratelimit; + + spinlock_t inode_hash_lock; + struct hlist_head inode_hashtable[EXFAT_HASH_SIZE]; + struct kobject sb_kobj; + + atomic_t stat_n_pages_queued; /* # of pages in the request queue (approx.) */ +}; + +/* + * EXFAT file system inode in-memory data + */ +struct exfat_inode_info { + FILE_ID_T fid; + char *target; + /* NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access */ + loff_t i_size_ondisk; /* physically allocated size */ + loff_t i_size_aligned; /* block-aligned i_size (used in cont_write_begin) */ + loff_t i_pos; /* on-disk position of directory entry or 0 */ + struct hlist_node i_hash_fat; /* hash by i_location */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + struct rw_semaphore truncate_lock; /* protect bmap against truncate */ +#endif + struct inode vfs_inode; +}; + +/* + * FIXME : needs on-disk-slot in-memory data + */ + +static inline struct exfat_sb_info *EXFAT_SB(struct super_block *sb) +{ + return (struct exfat_sb_info *)sb->s_fs_info; +} + +static inline struct exfat_inode_info *EXFAT_I(struct inode *inode) +{ + return container_of(inode, struct exfat_inode_info, vfs_inode); +} + +/* + * If ->i_mode can't hold S_IWUGO (i.e. ATTR_RO), we use ->i_attrs to + * save ATTR_RO instead of ->i_mode. + * + * If it's directory and !sbi->options.rodir, ATTR_RO isn't read-only + * bit, it's just used as flag for app. + */ +static inline int exfat_mode_can_hold_ro(struct inode *inode) +{ + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + + if (S_ISDIR(inode->i_mode)) + return 0; + + if ((~sbi->options.fs_fmask) & S_IWUGO) + return 1; + return 0; +} + +/* + * FIXME : needs to check symlink option. + */ +/* Convert attribute bits and a mask to the UNIX mode. */ +static inline mode_t exfat_make_mode(struct exfat_sb_info *sbi, + u32 attr, mode_t mode) +{ + if ((attr & ATTR_READONLY) && !(attr & ATTR_SUBDIR)) + mode &= ~S_IWUGO; + + if (attr & ATTR_SUBDIR) + return (mode & ~sbi->options.fs_dmask) | S_IFDIR; + else if (attr & ATTR_SYMLINK) + return (mode & ~sbi->options.fs_dmask) | S_IFLNK; + else + return (mode & ~sbi->options.fs_fmask) | S_IFREG; +} + +/* Return the FAT attribute byte for this inode */ +static inline u32 exfat_make_attr(struct inode *inode) +{ + u32 attrs = EXFAT_I(inode)->fid.attr; + + if (S_ISDIR(inode->i_mode)) + attrs |= ATTR_SUBDIR; + if (exfat_mode_can_hold_ro(inode) && !(inode->i_mode & S_IWUGO)) + attrs |= ATTR_READONLY; + return attrs; +} + +static inline void exfat_save_attr(struct inode *inode, u32 attr) +{ + if (exfat_mode_can_hold_ro(inode)) + EXFAT_I(inode)->fid.attr = attr & ATTR_RWMASK; + else + EXFAT_I(inode)->fid.attr = attr & (ATTR_RWMASK | ATTR_READONLY); +} + +/* exfat/nls.c */ +/* NLS management function */ +s32 exfat_nls_cmp_uniname(struct super_block *sb, u16 *a, u16 *b); +s32 exfat_nls_sfn_to_uni16s(struct super_block *sb, DOS_NAME_T *p_dosname, UNI_NAME_T *p_uniname); +s32 exfat_nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *uniname, u8 *p_cstring, s32 len); +s32 exfat_nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring, + const s32 len, UNI_NAME_T *uniname, s32 *p_lossy); + +/* exfat/xattr.c */ +#ifdef CONFIG_EXFAT_VIRTUAL_XATTR +void setup_exfat_xattr_handler(struct super_block *sb); +extern int exfat_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); +extern ssize_t exfat_getxattr(struct dentry *dentry, const char *name, void *value, size_t size); +extern ssize_t exfat_listxattr(struct dentry *dentry, char *list, size_t size); +extern int exfat_removexattr(struct dentry *dentry, const char *name); +#else +static inline void setup_exfat_xattr_handler(struct super_block *sb) {}; +#endif + +/* exfat/misc.c */ +#ifdef CONFIG_EXFAT_UEVENT +extern int exfat_uevent_init(struct kset *exfat_kset); +extern void exfat_uevent_uninit(void); +extern void exfat_uevent_ro_remount(struct super_block *sb); +#else +static inline int exfat_uevent_init(struct kset *exfat_kset) +{ + return 0; +} +static inline void exfat_uevent_uninit(void) {}; +static inline void exfat_uevent_ro_remount(struct super_block *sb) {}; +#endif +extern void +__exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) + __printf(3, 4) __cold; +#define exfat_fs_error(sb, fmt, args...) \ + __exfat_fs_error(sb, 1, fmt, ## args) +#define exfat_fs_error_ratelimit(sb, fmt, args...) \ + __exfat_fs_error(sb, __ratelimit(&EXFAT_SB(sb)->ratelimit), fmt, ## args) +extern void +__exfat_msg(struct super_block *sb, const char *lv, int st, const char *fmt, ...) + __printf(4, 5) __cold; +#define exfat_msg(sb, lv, fmt, args...) \ + __exfat_msg(sb, lv, 0, fmt, ## args) +#define exfat_log_msg(sb, lv, fmt, args...) \ + __exfat_msg(sb, lv, 1, fmt, ## args) +extern void exfat_log_version(void); +extern void exfat_time_fat2unix(struct exfat_sb_info *sbi, struct timespec_compat *ts, + DATE_TIME_T *tp); +extern void exfat_time_unix2fat(struct exfat_sb_info *sbi, struct timespec_compat *ts, + DATE_TIME_T *tp); +extern TIMESTAMP_T *exfat_tm_now(struct exfat_sb_info *sbi, TIMESTAMP_T *tm); + +#ifdef CONFIG_EXFAT_DEBUG + +#ifdef CONFIG_EXFAT_DBG_BUGON +#define exfat_debug_bug_on(expr) BUG_ON(expr) +#else +#define exfat_debug_bug_on(expr) +#endif + +#ifdef CONFIG_EXFAT_DBG_WARNON +#define exfat_debug_warn_on(expr) WARN_ON(expr) +#else +#define exfat_debug_warn_on(expr) +#endif + +#else /* CONFIG_EXFAT_DEBUG */ + +#define exfat_debug_bug_on(expr) +#define exfat_debug_warn_on(expr) + +#endif /* CONFIG_EXFAT_DEBUG */ + +#define EXFAT_MSG_LV_NONE (0x00000000) +#define EXFAT_MSG_LV_ERR (0x00000001) +#define EXFAT_MSG_LV_INFO (0x00000002) +#define EXFAT_MSG_LV_DBG (0x00000003) +#define EXFAT_MSG_LV_MORE (0x00000004) +#define EXFAT_MSG_LV_TRACE (0x00000005) +#define EXFAT_MSG_LV_ALL (0x00000006) + +#define EXFAT_MSG_LEVEL EXFAT_MSG_LV_INFO + +#define EXFAT_TAG_NAME "EXFAT" +#define __S(x) #x +#define _S(x) __S(x) + +extern void __exfat_dmsg(int level, const char *fmt, ...) __printf(2, 3) __cold; + +#define EXFAT_EMSG_T(level, ...) \ + __exfat_dmsg(level, KERN_ERR "[" EXFAT_TAG_NAME "] [" _S(__FILE__) "(" _S(__LINE__) ")] " __VA_ARGS__) +#define EXFAT_DMSG_T(level, ...) \ + __exfat_dmsg(level, KERN_INFO "[" EXFAT_TAG_NAME "] " __VA_ARGS__) + +#define EXFAT_EMSG(...) EXFAT_EMSG_T(EXFAT_MSG_LV_ERR, __VA_ARGS__) +#define EXFAT_IMSG(...) EXFAT_DMSG_T(EXFAT_MSG_LV_INFO, __VA_ARGS__) +#define EXFAT_DMSG(...) EXFAT_DMSG_T(EXFAT_MSG_LV_DBG, __VA_ARGS__) +#define EXFAT_MMSG(...) EXFAT_DMSG_T(EXFAT_MSG_LV_MORE, __VA_ARGS__) + +#define EMSG(...) +#define IMSG(...) +#define DMSG(...) +#define MMSG(...) + +#define EMSG_VAR(exp) +#define IMSG_VAR(exp) +#define DMSG_VAR(exp) +#define MMSG_VAR(exp) + +#ifdef CONFIG_EXFAT_DBG_MSG + + +#if (EXFAT_MSG_LEVEL >= EXFAT_MSG_LV_ERR) +#undef EMSG +#undef EMSG_VAR +#define EMSG(...) EXFAT_EMSG(__VA_ARGS__) +#define EMSG_VAR(exp) exp +#endif + +#if (EXFAT_MSG_LEVEL >= EXFAT_MSG_LV_INFO) +#undef IMSG +#undef IMSG_VAR +#define IMSG(...) EXFAT_IMSG(__VA_ARGS__) +#define IMSG_VAR(exp) exp +#endif + +#if (EXFAT_MSG_LEVEL >= EXFAT_MSG_LV_DBG) +#undef DMSG +#undef DMSG_VAR +#define DMSG(...) EXFAT_DMSG(__VA_ARGS__) +#define DMSG_VAR(exp) exp +#endif + +#if (EXFAT_MSG_LEVEL >= EXFAT_MSG_LV_MORE) +#undef MMSG +#undef MMSG_VAR +#define MMSG(...) EXFAT_MMSG(__VA_ARGS__) +#define MMSG_VAR(exp) exp +#endif + +#endif /* CONFIG_EXFAT_DBG_MSG */ + +#define ASSERT(expr) { \ + if (!(expr)) { \ + pr_err("exFAT: Assertion failed! %s\n", #expr); \ + BUG_ON(1); \ + } \ +} + +#endif /* !_EXFAT_H */ + diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h new file mode 100644 index 000000000000..522b8076c55f --- /dev/null +++ b/fs/exfat/exfat_fs.h @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + */ + +#ifndef _EXFAT_FS_H +#define _EXFAT_FS_H + +#include +#include +#include + +#ifndef MSDOS_SUPER_MAGIC +#define MSDOS_SUPER_MAGIC 0x4d44 /* MD */ +#endif + +#ifndef EXFAT_SUPER_MAGIC +#define EXFAT_SUPER_MAGIC (0x2011BAB0UL) +#endif /* EXFAT_SUPER_MAGIC */ + +#ifndef EXFAT_SUPER_MAGIC +#define EXFAT_SUPER_MAGIC (0x5EC5DFA4UL) +#endif /* EXFAT_SUPER_MAGIC */ + +#define EXFAT_ROOT_INO 1 + +/* FAT types */ +#define FAT12 0x01 // FAT12 +#define FAT16 0x0E // Win95 FAT16 (LBA) +#define FAT32 0x0C // Win95 FAT32 (LBA) +#define EXFAT 0x07 // exFAT + +/* directory file name */ +#define DOS_CUR_DIR_NAME ". " +#define DOS_PAR_DIR_NAME ".. " + +#ifdef __LITTLE_ENDIAN +#define UNI_CUR_DIR_NAME ".\0" +#define UNI_PAR_DIR_NAME ".\0.\0" +#else +#define UNI_CUR_DIR_NAME "\0." +#define UNI_PAR_DIR_NAME "\0.\0." +#endif + +/* file name lengths */ +/* NOTE : + * The maximum length of input or output is limited to 256 including NULL, + * But we allocate 4 extra bytes for utf8 translation reside in last position, + * because utf8 can uses memory upto 6 bytes per one character. + * Therefore, MAX_CHARSET_SIZE supports upto 6 bytes for utf8 + */ +#define MAX_UNINAME_BUF_SIZE (((MAX_NAME_LENGTH+1)*2)+4) +#define MAX_DOSNAME_BUF_SIZE ((DOS_NAME_LENGTH+2)+6) +#define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH+1)*MAX_CHARSET_SIZE) +#define MAX_CHARSET_SIZE 6 // max size of multi-byte character +#define MAX_NAME_LENGTH 255 // max len of file name excluding NULL +#define DOS_NAME_LENGTH 11 // DOS file name length excluding NULL + +#define SECTOR_SIZE_BITS 9 /* VFS sector size is 512 bytes */ + +#define DENTRY_SIZE 32 /* directory entry size */ +#define DENTRY_SIZE_BITS 5 + +#define MAX_FAT_DENTRIES 65536 /* FAT allows 65536 directory entries */ +#define MAX_EXFAT_DENTRIES 8388608 /* exFAT allows 8388608(256MB) directory entries */ + +/* PBR entries */ +#define PBR_SIGNATURE 0xAA55 +#define EXT_SIGNATURE 0xAA550000 +#define VOL_LABEL "NO NAME " /* size should be 11 */ +#define OEM_NAME "MSWIN4.1" /* size should be 8 */ +#define STR_FAT12 "FAT12 " /* size should be 8 */ +#define STR_FAT16 "FAT16 " /* size should be 8 */ +#define STR_FAT32 "FAT32 " /* size should be 8 */ +#define STR_EXFAT "EXFAT " /* size should be 8 */ + +#define VOL_CLEAN 0x0000 +#define VOL_DIRTY 0x0002 + +#define FAT_VOL_DIRTY 0x01 + +/* max number of clusters */ +#define FAT12_THRESHOLD 4087 // 2^12 - 1 + 2 (clu 0 & 1) +#define FAT16_THRESHOLD 65527 // 2^16 - 1 + 2 +#define FAT32_THRESHOLD 268435457 // 2^28 - 1 + 2 +#define EXFAT_THRESHOLD 268435457 // 2^28 - 1 + 2 + +/* dentry types */ +#define MSDOS_DELETED 0xE5 /* deleted mark */ +#define MSDOS_UNUSED 0x00 /* end of directory */ + +#define EXFAT_UNUSED 0x00 /* end of directory */ +#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_STREAM 0xC0 /* stream entry */ +#define EXFAT_NAME 0xC1 /* file name entry */ +#define EXFAT_ACL 0xC2 /* stream entry */ + +/* specific flag */ +#define MSDOS_LAST_LFN 0x40 + +/* file attributes */ +#define ATTR_NORMAL 0x0000 +#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_SYMLINK 0x0040 +#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 | ATTR_SYMLINK)/* 0x007E */ + +/* file creation modes */ +#define FM_REGULAR 0x00 +#define FM_SYMLINK 0x40 + +/* time modes */ +#define TM_CREATE 0 +#define TM_MODIFY 1 +#define TM_ACCESS 2 + +/* checksum types */ +#define CS_DIR_ENTRY 0 +#define CS_PBR_SECTOR 1 +#define CS_DEFAULT 2 + +/* + * ioctl command + */ +#define EXFAT_IOCTL_GET_VOLUME_ID _IOR('r', 0x12, __u32) + +/* FAT12/16 BIOS parameter block (64 bytes) */ +typedef struct { + __u8 jmp_boot[3]; + __u8 oem_name[8]; + + __u8 sect_size[2]; /* unaligned */ + __u8 sect_per_clus; + __le16 num_reserved; /* . */ + __u8 num_fats; + __u8 num_root_entries[2]; /* unaligned */ + __u8 num_sectors[2]; /* unaligned */ + __u8 media_type; + __le16 num_fat_sectors; + __le16 sectors_in_track; + __le16 num_heads; + __le32 num_hid_sectors; /* . */ + __le32 num_huge_sectors; + + __u8 phy_drv_no; + __u8 state; /* used by WindowsNT for mount state */ + __u8 ext_signature; + __u8 vol_serial[4]; + __u8 vol_label[11]; + __u8 vol_type[8]; + __le16 dummy; +} bpb16_t; + +/* FAT32 BIOS parameter block (64 bytes) */ +typedef struct { + __u8 jmp_boot[3]; + __u8 oem_name[8]; + + __u8 sect_size[2]; /* unaligned */ + __u8 sect_per_clus; + __le16 num_reserved; + __u8 num_fats; + __u8 num_root_entries[2]; /* unaligned */ + __u8 num_sectors[2]; /* unaligned */ + __u8 media_type; + __le16 num_fat_sectors; /* zero */ + __le16 sectors_in_track; + __le16 num_heads; + __le32 num_hid_sectors; /* . */ + __le32 num_huge_sectors; + + __le32 num_fat32_sectors; + __le16 ext_flags; + __u8 fs_version[2]; + __le32 root_cluster; /* . */ + __le16 fsinfo_sector; + __le16 backup_sector; + __le16 reserved[6]; /* . */ +} bpb32_t; + +/* FAT32 EXTEND BIOS parameter block (32 bytes) */ +typedef struct { + __u8 phy_drv_no; + __u8 state; /* used by WindowsNT for mount state */ + __u8 ext_signature; + __u8 vol_serial[4]; + __u8 vol_label[11]; + __u8 vol_type[8]; + __le16 dummy[3]; +} bsx32_t; + +/* EXFAT BIOS parameter block (64 bytes) */ +typedef struct { + __u8 jmp_boot[3]; + __u8 oem_name[8]; + __u8 res_zero[53]; +} bpb64_t; + +/* EXFAT EXTEND BIOS parameter block (56 bytes) */ +typedef struct { + __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]; +} bsx64_t; + +/* FAT32 PBR (64 bytes) */ +typedef struct { + bpb16_t bpb; +} pbr16_t; + +/* FAT32 PBR[BPB+BSX] (96 bytes) */ +typedef struct { + bpb32_t bpb; + bsx32_t bsx; +} pbr32_t; + +/* EXFAT PBR[BPB+BSX] (120 bytes) */ +typedef struct { + bpb64_t bpb; + bsx64_t bsx; +} pbr64_t; + +/* Common PBR[Partition Boot Record] (512 bytes) */ +typedef struct { + union { + __u8 raw[64]; + bpb16_t f16; + bpb32_t f32; + bpb64_t f64; + } bpb; + union { + __u8 raw[56]; + bsx32_t f32; + bsx64_t f64; + } bsx; + __u8 boot_code[390]; + __le16 signature; +} pbr_t; + +/* FAT32 filesystem information sector (512 bytes) */ +typedef struct { + __le32 signature1; // aligned + __u8 reserved1[480]; + __le32 signature2; // aligned + __le32 free_cluster; // aligned + __le32 next_cluster; // aligned + __u8 reserved2[14]; + __le16 signature3[2]; +} fat32_fsi_t; + +/* FAT directory entry (32 bytes) */ +typedef struct { + __u8 dummy[32]; +} DENTRY_T; + +typedef struct { + __u8 name[DOS_NAME_LENGTH]; /* 11 chars */ + __u8 attr; + __u8 lcase; + __u8 create_time_ms; + __le16 create_time; // aligned + __le16 create_date; // aligned + __le16 access_date; // aligned + __le16 start_clu_hi; // aligned + __le16 modify_time; // aligned + __le16 modify_date; // aligned + __le16 start_clu_lo; // aligned + __le32 size; // aligned +} DOS_DENTRY_T; + +/* FAT extended directory entry (32 bytes) */ +typedef struct { + __u8 order; + __u8 unicode_0_4[10]; + __u8 attr; + __u8 sysid; + __u8 checksum; + __le16 unicode_5_10[6]; // aligned + __le16 start_clu; // aligned + __le16 unicode_11_12[2]; // aligned +} EXT_DENTRY_T; + +/* EXFAT file directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 num_ext; + __le16 checksum; // aligned + __le16 attr; // aligned + __le16 reserved1; + __le16 create_time; // aligned + __le16 create_date; // aligned + __le16 modify_time; // aligned + __le16 modify_date; // aligned + __le16 access_time; // aligned + __le16 access_date; // aligned + __u8 create_time_ms; + __u8 modify_time_ms; + __u8 access_time_ms; + __u8 reserved2[9]; +} FILE_DENTRY_T; + +/* EXFAT stream extension directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 flags; + __u8 reserved1; + __u8 name_len; + __le16 name_hash; // aligned + __le16 reserved2; + __le64 valid_size; // aligned + __le32 reserved3; // aligned + __le32 start_clu; // aligned + __le64 size; // aligned +} STRM_DENTRY_T; + +/* EXFAT file name directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 flags; + __le16 unicode_0_14[15]; // aligned +} NAME_DENTRY_T; + +/* EXFAT allocation bitmap directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 flags; + __u8 reserved[18]; + __le32 start_clu; // aligned + __le64 size; // aligned +} BMAP_DENTRY_T; + +/* EXFAT up-case table directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 reserved1[3]; + __le32 checksum; // aligned + __u8 reserved2[12]; + __le32 start_clu; // aligned + __le64 size; // aligned +} CASE_DENTRY_T; + +/* EXFAT volume label directory entry (32 bytes) */ +typedef struct { + __u8 type; + __u8 label_len; + __le16 unicode_0_10[11]; // aligned + __u8 reserved[8]; +} VOLM_DENTRY_T; + +#endif /* _EXFAT_FS_H */ diff --git a/fs/exfat/extent.c b/fs/exfat/extent.c new file mode 100644 index 000000000000..99d116cb09bb --- /dev/null +++ b/fs/exfat/extent.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * extent.c: Improve the performance of traversing fat chain + */ + +/* + * linux/fs/fat/cache.c + * + * Written 1992,1993 by Werner Almesberger + * + * Mar 1999. AV. Changed cache, so that it uses the starting cluster instead + * of inode number. + * May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers. + */ + +#include +#include "exfat.h" +#include "core.h" + +#define EXTENT_CACHE_VALID 0 +/* this must be > 0. */ +#define EXTENT_MAX_CACHE 16 + +struct extent_cache { + struct list_head cache_list; + u32 nr_contig; /* number of contiguous clusters */ + u32 fcluster; /* cluster number in the file. */ + u32 dcluster; /* cluster number on disk. */ +}; + +struct extent_cache_id { + u32 id; + u32 nr_contig; + u32 fcluster; + u32 dcluster; +}; + +static struct kmem_cache *extent_cache_cachep; + +static void init_once(void *c) +{ + struct extent_cache *cache = (struct extent_cache *)c; + + INIT_LIST_HEAD(&cache->cache_list); +} + +s32 exfat_extent_cache_init(void) +{ + extent_cache_cachep = kmem_cache_create("exfat_extent_cache", + sizeof(struct extent_cache), + 0, SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, + init_once); + if (!extent_cache_cachep) + return -ENOMEM; + return 0; +} + +void exfat_extent_cache_shutdown(void) +{ + if (!extent_cache_cachep) + return; + kmem_cache_destroy(extent_cache_cachep); +} + +void exfat_extent_cache_init_inode(struct inode *inode) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + + spin_lock_init(&extent->cache_lru_lock); + extent->nr_caches = 0; + extent->cache_valid_id = EXTENT_CACHE_VALID + 1; + INIT_LIST_HEAD(&extent->cache_lru); +} + +static inline struct extent_cache *extent_cache_alloc(void) +{ + return kmem_cache_alloc(extent_cache_cachep, GFP_NOFS); +} + +static inline void extent_cache_free(struct extent_cache *cache) +{ + BUG_ON(!list_empty(&cache->cache_list)); + kmem_cache_free(extent_cache_cachep, cache); +} + +static inline void extent_cache_update_lru(struct inode *inode, + struct extent_cache *cache) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + + if (extent->cache_lru.next != &cache->cache_list) + list_move(&cache->cache_list, &extent->cache_lru); +} + +static u32 extent_cache_lookup(struct inode *inode, u32 fclus, + struct extent_cache_id *cid, + u32 *cached_fclus, u32 *cached_dclus) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + + static struct extent_cache nohit = { .fcluster = 0, }; + + struct extent_cache *hit = &nohit, *p; + u32 offset = CLUS_EOF; + + spin_lock(&extent->cache_lru_lock); + list_for_each_entry(p, &extent->cache_lru, cache_list) { + /* Find the cache of "fclus" or nearest cache. */ + if (p->fcluster <= fclus && hit->fcluster < p->fcluster) { + hit = p; + if ((hit->fcluster + hit->nr_contig) < fclus) { + offset = hit->nr_contig; + } else { + offset = fclus - hit->fcluster; + break; + } + } + } + if (hit != &nohit) { + extent_cache_update_lru(inode, hit); + + cid->id = extent->cache_valid_id; + cid->nr_contig = hit->nr_contig; + cid->fcluster = hit->fcluster; + cid->dcluster = hit->dcluster; + *cached_fclus = cid->fcluster + offset; + *cached_dclus = cid->dcluster + offset; + } + spin_unlock(&extent->cache_lru_lock); + + return offset; +} + +static struct extent_cache *extent_cache_merge(struct inode *inode, + struct extent_cache_id *new) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + + struct extent_cache *p; + + list_for_each_entry(p, &extent->cache_lru, cache_list) { + /* Find the same part as "new" in cluster-chain. */ + if (p->fcluster == new->fcluster) { + ASSERT(p->dcluster == new->dcluster); + if (new->nr_contig > p->nr_contig) + p->nr_contig = new->nr_contig; + return p; + } + } + return NULL; +} + +static void extent_cache_add(struct inode *inode, struct extent_cache_id *new) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + + struct extent_cache *cache, *tmp; + + if (new->fcluster == -1) /* dummy cache */ + return; + + spin_lock(&extent->cache_lru_lock); + if (new->id != EXTENT_CACHE_VALID && + new->id != extent->cache_valid_id) + goto out; /* this cache was invalidated */ + + cache = extent_cache_merge(inode, new); + if (cache == NULL) { + if (extent->nr_caches < EXTENT_MAX_CACHE) { + extent->nr_caches++; + spin_unlock(&extent->cache_lru_lock); + + tmp = extent_cache_alloc(); + if (!tmp) { + spin_lock(&extent->cache_lru_lock); + extent->nr_caches--; + spin_unlock(&extent->cache_lru_lock); + return; + } + + spin_lock(&extent->cache_lru_lock); + cache = extent_cache_merge(inode, new); + if (cache != NULL) { + extent->nr_caches--; + extent_cache_free(tmp); + goto out_update_lru; + } + cache = tmp; + } else { + struct list_head *p = extent->cache_lru.prev; + cache = list_entry(p, struct extent_cache, cache_list); + } + cache->fcluster = new->fcluster; + cache->dcluster = new->dcluster; + cache->nr_contig = new->nr_contig; + } +out_update_lru: + extent_cache_update_lru(inode, cache); +out: + spin_unlock(&extent->cache_lru_lock); +} + +/* + * Cache invalidation occurs rarely, thus the LRU chain is not updated. It + * fixes itself after a while. + */ +static void __exfat_extent_cache_inval_inode(struct inode *inode) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + struct extent_cache *cache; + + while (!list_empty(&extent->cache_lru)) { + cache = list_entry(extent->cache_lru.next, + struct extent_cache, cache_list); + list_del_init(&cache->cache_list); + extent->nr_caches--; + extent_cache_free(cache); + } + /* Update. The copy of caches before this id is discarded. */ + extent->cache_valid_id++; + if (extent->cache_valid_id == EXTENT_CACHE_VALID) + extent->cache_valid_id++; +} + +void exfat_extent_cache_inval_inode(struct inode *inode) +{ + EXTENT_T *extent = &(EXFAT_I(inode)->fid.extent); + + spin_lock(&extent->cache_lru_lock); + __exfat_extent_cache_inval_inode(inode); + spin_unlock(&extent->cache_lru_lock); +} + +static inline s32 cache_contiguous(struct extent_cache_id *cid, u32 dclus) +{ + cid->nr_contig++; + return ((cid->dcluster + cid->nr_contig) == dclus); +} + +static inline void cache_init(struct extent_cache_id *cid, u32 fclus, u32 dclus) +{ + cid->id = EXTENT_CACHE_VALID; + cid->fcluster = fclus; + cid->dcluster = dclus; + cid->nr_contig = 0; +} + +s32 exfat_extent_get_clus(struct inode *inode, u32 cluster, u32 *fclus, + u32 *dclus, u32 *last_dclus, s32 allow_eof) +{ + struct super_block *sb = inode->i_sb; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 limit = fsi->num_clusters; + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + struct extent_cache_id cid; + u32 content; + + /* FOR GRACEFUL ERROR HANDLING */ + if (IS_CLUS_FREE(fid->start_clu)) { + exfat_fs_error(sb, "invalid access to " + "extent cache (entry 0x%08x)", fid->start_clu); + ASSERT(0); + return -EIO; + } + + *fclus = 0; + *dclus = fid->start_clu; + *last_dclus = *dclus; + + /* + * Don`t use extent_cache if zero offset or non-cluster allocation + */ + if ((cluster == 0) || IS_CLUS_EOF(*dclus)) + return 0; + + cache_init(&cid, CLUS_EOF, CLUS_EOF); + + if (extent_cache_lookup(inode, cluster, &cid, fclus, dclus) == CLUS_EOF) { + /* + * dummy, always not contiguous + * This is reinitialized by cache_init(), later. + */ + ASSERT((cid.id == EXTENT_CACHE_VALID) + && (cid.fcluster == CLUS_EOF) + && (cid.dcluster == CLUS_EOF) + && (cid.nr_contig == 0)); + } + + if (*fclus == cluster) + return 0; + + while (*fclus < cluster) { + /* prevent the infinite loop of cluster chain */ + if (*fclus > limit) { + exfat_fs_error(sb, + "%s: detected the cluster chain loop" + " (i_pos %u)", __func__, + (*fclus)); + return -EIO; + } + + if (exfat_ent_get_safe(sb, *dclus, &content)) + return -EIO; + + *last_dclus = *dclus; + *dclus = content; + (*fclus)++; + + if (IS_CLUS_EOF(content)) { + if (!allow_eof) { + exfat_fs_error(sb, + "%s: invalid cluster chain (i_pos %u," + "last_clus 0x%08x is EOF)", + __func__, *fclus, (*last_dclus)); + return -EIO; + } + + break; + } + + if (!cache_contiguous(&cid, *dclus)) + cache_init(&cid, *fclus, *dclus); + } + + extent_cache_add(inode, &cid); + return 0; +} diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c new file mode 100644 index 000000000000..8b87d03c5569 --- /dev/null +++ b/fs/exfat/fatent.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * fatent.c: exFAT FAT entry manager + */ + +#include + +#include "exfat.h" +#include "core.h" + +/* All buffer structures are protected w/ fsi->v_sem */ + +static inline bool is_reserved_clus(u32 clus) +{ + if (IS_CLUS_FREE(clus)) + return true; + if (IS_CLUS_EOF(clus)) + return true; + if (IS_CLUS_BAD(clus)) + return true; + return false; +} + +static inline bool is_valid_clus(FS_INFO_T *fsi, u32 clus) +{ + if (clus < CLUS_BASE || fsi->num_clusters <= clus) + return false; + return true; +} + +s32 exfat_ent_get(struct super_block *sb, u32 loc, u32 *content) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + u32 off, _content; + u64 sec; + u8 *fat_sector; + + if (!is_valid_clus(fsi, loc)) { + exfat_fs_error(sb, "invalid access to FAT (entry 0x%08x)", loc); + return -EIO; + } + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-2)); + off = (loc << 2) & (u32)(sb->s_blocksize - 1); + + fat_sector = exfat_fcache_getblk(sb, sec); + if (!fat_sector) { + exfat_fs_error(sb, "failed to access to FAT " + "(entry 0x%08x)", loc); + return -EIO; + } + + _content = le32_to_cpu(*(__le32 *)(&fat_sector[off])); + + /* remap reserved clusters to simplify code */ + if (_content >= CLUSTER_32(0xFFFFFFF8)) + _content = CLUS_EOF; + + *content = CLUSTER_32(_content); + + if (!is_reserved_clus(*content) && !is_valid_clus(fsi, *content)) { + exfat_fs_error(sb, "invalid access to FAT (entry 0x%08x) " + "bogus content (0x%08x)", loc, *content); + return -EIO; + } + + return 0; +} + +s32 exfat_ent_set(struct super_block *sb, u32 loc, u32 content) +{ + u32 off; + u64 sec; + u8 *fat_sector; + __le32 *fat_entry; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + sec = fsi->FAT1_start_sector + (loc >> (sb->s_blocksize_bits-2)); + off = (loc << 2) & (u32)(sb->s_blocksize - 1); + + fat_sector = exfat_fcache_getblk(sb, sec); + if (!fat_sector) + return -EIO; + + fat_entry = (__le32 *)&(fat_sector[off]); + *fat_entry = cpu_to_le32(content); + + return exfat_fcache_modify(sb, sec); +} + +s32 exfat_ent_get_safe(struct super_block *sb, u32 loc, u32 *content) +{ + s32 err = exfat_ent_get(sb, loc, content); + + if (err) + return err; + + if (IS_CLUS_FREE(*content)) { + exfat_fs_error(sb, "invalid access to FAT free cluster " + "(entry 0x%08x)", loc); + return -EIO; + } + + if (IS_CLUS_BAD(*content)) { + exfat_fs_error(sb, "invalid access to FAT bad cluster " + "(entry 0x%08x)", loc); + return -EIO; + } + + return 0; +} diff --git a/fs/exfat/misc.c b/fs/exfat/misc.c new file mode 100644 index 000000000000..9cc95e9d82d0 --- /dev/null +++ b/fs/exfat/misc.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * misc.c: Helper function for checksum and handling exFAT errors + */ + +/* + * linux/fs/fat/misc.c + * + * Written 1992,1993 by Werner Almesberger + * 22/11/2000 - Fixed fat_date_unix2dos for dates earlier than 01/01/1980 + * and date_dos2unix for date==0 by Igor Zhbanov(bsg@uniyar.ac.ru) + */ + +#include +#include +#include +#include +#include "exfat.h" +#include "version.h" + +#ifdef CONFIG_EXFAT_UEVENT +static struct kobject exfat_uevent_kobj; + +int exfat_uevent_init(struct kset *exfat_kset) +{ + int err; + struct kobj_type *ktype = get_ktype(&exfat_kset->kobj); + + exfat_uevent_kobj.kset = exfat_kset; + err = kobject_init_and_add(&exfat_uevent_kobj, ktype, NULL, "uevent"); + if (err) + pr_err("[EXFAT] Unable to create exfat uevent kobj\n"); + + return err; +} + +void exfat_uevent_uninit(void) +{ + kobject_del(&exfat_uevent_kobj); + memset(&exfat_uevent_kobj, 0, sizeof(struct kobject)); +} + +void exfat_uevent_ro_remount(struct super_block *sb) +{ + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + char major[16], minor[16]; + char *envp[] = { major, minor, NULL }; + + snprintf(major, sizeof(major), "MAJOR=%d", MAJOR(bd_dev)); + snprintf(minor, sizeof(minor), "MINOR=%d", MINOR(bd_dev)); + + kobject_uevent_env(&exfat_uevent_kobj, KOBJ_CHANGE, envp); +} +#endif + +/* + * exfat_fs_error reports a file system problem that might indicate fa data + * corruption/inconsistency. Depending on 'errors' mount option the + * panic() is called, or error message is printed FAT and nothing is done, + * or filesystem is remounted read-only (default behavior). + * In case the file system is remounted read-only, it can be made writable + * again by remounting it. + */ +void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) +{ + struct exfat_mount_options *opts = &EXFAT_SB(sb)->options; + va_list args; + struct va_format vaf; + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + if (report) { + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + pr_err("exFAT-fs (%s[%d:%d]): ERR: %pV\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf); + va_end(args); + } + + if (opts->errors == EXFAT_ERRORS_PANIC) { + panic("exFAT-fs (%s[%d:%d]): fs panic from previous error\n", + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); + } else if (opts->errors == EXFAT_ERRORS_RO && !EXFAT_IS_SB_RDONLY(sb)) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + sb->s_flags |= MS_RDONLY; +#else + sb->s_flags |= SB_RDONLY; +#endif + pr_err("exFAT-fs (%s[%d:%d]): file-system has been set to " + "read-only\n", sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); + exfat_uevent_ro_remount(sb); + } +} +EXPORT_SYMBOL(__exfat_fs_error); + +/** + * __exfat_msg() - print preformated EXFAT specific messages. + * All logs except what uses exfat_fs_error() should be written by __exfat_msg() + */ +void __exfat_msg(struct super_block *sb, const char *level, int st, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + /* level means KERN_ pacility level */ + printk("%sexFAT-fs (%s[%d:%d]): %pV\n", level, + sb->s_id, MAJOR(bd_dev), MINOR(bd_dev), &vaf); + va_end(args); +} +EXPORT_SYMBOL(__exfat_msg); + +void exfat_log_version(void) +{ + pr_info("exFAT: file-system version %s\n", EXFAT_VERSION); +} +EXPORT_SYMBOL(exfat_log_version); + +/* externs sys_tz + * extern struct timezone sys_tz; + */ +#define UNIX_SECS_1980 315532800L + +#if BITS_PER_LONG == 64 +#define UNIX_SECS_2108 4354819200L +#endif + +/* days between 1970/01/01 and 1980/01/01 (2 leap days) */ +#define DAYS_DELTA_DECADE (365 * 10 + 2) +/* 120 (2100 - 1980) isn't leap year */ +#define NO_LEAP_YEAR_2100 (120) +#define IS_LEAP_YEAR(y) (!((y) & 0x3) && (y) != NO_LEAP_YEAR_2100) + +#define SECS_PER_MIN (60) +#define SECS_PER_HOUR (60 * SECS_PER_MIN) +#define SECS_PER_DAY (24 * SECS_PER_HOUR) + +#define MAKE_LEAP_YEAR(leap_year, year) \ + do { \ + /* 2100 isn't leap year */ \ + if (unlikely(year > NO_LEAP_YEAR_2100)) \ + leap_year = ((year + 3) / 4) - 1; \ + else \ + leap_year = ((year + 3) / 4); \ + } while (0) + +/* Linear day numbers of the respective 1sts in non-leap years. */ +static time_t accum_days_in_year[] = { + /* Month : N 01 02 03 04 05 06 07 08 09 10 11 12 */ + 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, +}; + +/* Convert a FAT time/date pair to a UNIX date (seconds since 1 1 70). */ +void exfat_time_fat2unix(struct exfat_sb_info *sbi, struct timespec_compat *ts, + DATE_TIME_T *tp) +{ + time_t year = tp->Year; + time_t ld; /* leap day */ + + MAKE_LEAP_YEAR(ld, year); + + if (IS_LEAP_YEAR(year) && (tp->Month) > 2) + ld++; + + ts->tv_sec = tp->Second + tp->Minute * SECS_PER_MIN + + tp->Hour * SECS_PER_HOUR + + (year * 365 + ld + accum_days_in_year[tp->Month] + + (tp->Day - 1) + DAYS_DELTA_DECADE) * SECS_PER_DAY; + + if (!sbi->options.tz_utc) + ts->tv_sec += sys_tz.tz_minuteswest * SECS_PER_MIN; + + ts->tv_nsec = 0; +} + +/* Convert linear UNIX date to a FAT time/date pair. */ +void exfat_time_unix2fat(struct exfat_sb_info *sbi, struct timespec_compat *ts, + DATE_TIME_T *tp) +{ + time_t second = ts->tv_sec; + time_t day, month, year; + time_t ld; /* leap day */ + + if (!sbi->options.tz_utc) + second -= sys_tz.tz_minuteswest * SECS_PER_MIN; + + /* Jan 1 GMT 00:00:00 1980. But what about another time zone? */ + if (second < UNIX_SECS_1980) { + tp->Second = 0; + tp->Minute = 0; + tp->Hour = 0; + tp->Day = 1; + tp->Month = 1; + tp->Year = 0; + return; + } +#if (BITS_PER_LONG == 64) + if (second >= UNIX_SECS_2108) { + tp->Second = 59; + tp->Minute = 59; + tp->Hour = 23; + tp->Day = 31; + tp->Month = 12; + tp->Year = 127; + return; + } +#endif + + day = second / SECS_PER_DAY - DAYS_DELTA_DECADE; + year = day / 365; + + MAKE_LEAP_YEAR(ld, year); + if (year * 365 + ld > day) + year--; + + MAKE_LEAP_YEAR(ld, year); + day -= year * 365 + ld; + + if (IS_LEAP_YEAR(year) && day == accum_days_in_year[3]) { + month = 2; + } else { + if (IS_LEAP_YEAR(year) && day > accum_days_in_year[3]) + day--; + for (month = 1; month < 12; month++) { + if (accum_days_in_year[month + 1] > day) + break; + } + } + day -= accum_days_in_year[month]; + + tp->Second = second % SECS_PER_MIN; + tp->Minute = (second / SECS_PER_MIN) % 60; + tp->Hour = (second / SECS_PER_HOUR) % 24; + tp->Day = day + 1; + tp->Month = month; + tp->Year = year; +} + +TIMESTAMP_T *exfat_tm_now(struct exfat_sb_info *sbi, TIMESTAMP_T *tp) +{ + struct timespec_compat ts; + DATE_TIME_T dt; + + KTIME_GET_REAL_TS(&ts); + exfat_time_unix2fat(sbi, &ts, &dt); + + tp->year = dt.Year; + tp->mon = dt.Month; + tp->day = dt.Day; + tp->hour = dt.Hour; + tp->min = dt.Minute; + tp->sec = dt.Second; + + return tp; +} + +u16 exfat_calc_chksum_2byte(void *data, s32 len, u16 chksum, s32 type) +{ + s32 i; + u8 *c = (u8 *) data; + + for (i = 0; i < len; i++, c++) { + if (((i == 2) || (i == 3)) && (type == CS_DIR_ENTRY)) + continue; + chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + (u16) *c; + } + return chksum; +} + +#ifdef CONFIG_EXFAT_DBG_MSG +void __exfat_dmsg(int level, const char *fmt, ...) +{ + va_list args; + + /* should check type */ + if (level > EXFAT_MSG_LEVEL) + return; + + va_start(args, fmt); + /* fmt already includes KERN_ pacility level */ + vprintk(fmt, args); + va_end(args); +} +#endif diff --git a/fs/exfat/nls.c b/fs/exfat/nls.c new file mode 100644 index 000000000000..b367b4d902a7 --- /dev/null +++ b/fs/exfat/nls.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * nls.c: exFAT NLS manager + */ + +#include +#include + +#include "exfat.h" +#include "core.h" + +/* + * Allow full-width illegal characters : + * "MS windows 7" supports full-width-invalid-name-characters. + * So we should check half-width-invalid-name-characters(ASCII) only + * for compatibility. + * + * " * / : < > ? \ | + * + * patch 1.2.0 + */ +static u16 bad_uni_chars[] = { + 0x0022, 0x002A, 0x002F, 0x003A, + 0x003C, 0x003E, 0x003F, 0x005C, 0x007C, +#if 0 /* allow full-width characters */ + 0x201C, 0x201D, 0xFF0A, 0xFF0F, 0xFF1A, + 0xFF1C, 0xFF1E, 0xFF1F, 0xFF3C, 0xFF5C, +#endif + 0 +}; + +static s32 convert_uni_to_ch(struct nls_table *nls, u16 uni, u8 *ch, s32 *lossy); +static s32 convert_ch_to_uni(struct nls_table *nls, u8 *ch, u16 *uni, s32 *lossy); + +static u16 nls_upper(struct super_block *sb, u16 a) +{ + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + + if (EXFAT_SB(sb)->options.casesensitive) + return a; + if ((fsi->vol_utbl)[exfat_get_col_index(a)] != NULL) + return (fsi->vol_utbl)[exfat_get_col_index(a)][exfat_get_row_index(a)]; + else + return a; +} + +u16 *nls_wstrchr(u16 *str, u16 wchar) +{ + while (*str) { + if (*(str++) == wchar) + return str; + } + + return 0; +} + +s32 exfat_nls_cmp_uniname(struct super_block *sb, u16 *a, u16 *b) +{ + s32 i; + + for (i = 0; i < MAX_NAME_LENGTH; i++, a++, b++) { + if (nls_upper(sb, *a) != nls_upper(sb, *b)) + return 1; + if (*a == 0x0) + return 0; + } + return 0; +} + +#define CASE_LOWER_BASE (0x08) /* base is lower case */ +#define CASE_LOWER_EXT (0x10) /* extension is lower case */ + +s32 exfat_nls_sfn_to_uni16s(struct super_block *sb, DOS_NAME_T *p_dosname, UNI_NAME_T *p_uniname) +{ + s32 i = 0, j, n = 0; + u8 buf[MAX_DOSNAME_BUF_SIZE]; + u8 *dosname = p_dosname->name; + u16 *uniname = p_uniname->name; + struct nls_table *nls = EXFAT_SB(sb)->nls_disk; + + if (*dosname == 0x05) { + *buf = 0xE5; + i++; + n++; + } + + for ( ; i < 8; i++, n++) { + if (*(dosname+i) == ' ') + break; + + if ((*(dosname+i) >= 'A') && (*(dosname+i) <= 'Z') && + (p_dosname->name_case & CASE_LOWER_BASE)) + *(buf+n) = *(dosname+i) + ('a' - 'A'); + else + *(buf+n) = *(dosname+i); + } + if (*(dosname+8) != ' ') { + *(buf+n) = '.'; + n++; + } + + for (i = 8; i < DOS_NAME_LENGTH; i++, n++) { + if (*(dosname+i) == ' ') + break; + + if ((*(dosname+i) >= 'A') && (*(dosname+i) <= 'Z') && + (p_dosname->name_case & CASE_LOWER_EXT)) + *(buf+n) = *(dosname+i) + ('a' - 'A'); + else + *(buf+n) = *(dosname+i); + } + *(buf+n) = '\0'; + + i = j = 0; + while (j < MAX_NAME_LENGTH) { + if (*(buf+i) == '\0') + break; + + i += convert_ch_to_uni(nls, (buf+i), uniname, NULL); + + uniname++; + j++; + } + + *uniname = (u16) '\0'; + return j; +} + +static s32 __nls_utf16s_to_vfsname(struct super_block *sb, UNI_NAME_T *p_uniname, u8 *p_cstring, s32 buflen) +{ + s32 len; + const u16 *uniname = p_uniname->name; + + /* always len >= 0 */ + len = utf16s_to_utf8s(uniname, MAX_NAME_LENGTH, UTF16_HOST_ENDIAN, + p_cstring, buflen); + p_cstring[len] = '\0'; + return len; +} + +static s32 __nls_vfsname_to_utf16s(struct super_block *sb, const u8 *p_cstring, + const s32 len, UNI_NAME_T *p_uniname, s32 *p_lossy) +{ + s32 i, unilen, lossy = NLS_NAME_NO_LOSSY; + u16 upname[MAX_NAME_LENGTH+1]; + u16 *uniname = p_uniname->name; + + BUG_ON(!len); + + unilen = utf8s_to_utf16s(p_cstring, len, UTF16_HOST_ENDIAN, + (wchar_t *)uniname, MAX_NAME_LENGTH+2); + if (unilen < 0) { + MMSG("%s: failed to vfsname_to_utf16(err:%d) " + "vfsnamelen:%d", __func__, unilen, len); + return unilen; + } + + if (unilen > MAX_NAME_LENGTH) { + MMSG("%s: failed to vfsname_to_utf16(estr:ENAMETOOLONG) " + "vfsnamelen:%d, unilen:%d>%d", + __func__, len, unilen, MAX_NAME_LENGTH); + return -ENAMETOOLONG; + } + + p_uniname->name_len = (u8)(unilen & 0xFF); + + for (i = 0; i < unilen; i++) { + if ((*uniname < 0x0020) || nls_wstrchr(bad_uni_chars, *uniname)) + lossy |= NLS_NAME_LOSSY; + + *(upname+i) = nls_upper(sb, *uniname); + uniname++; + } + + *uniname = (u16)'\0'; + p_uniname->name_len = unilen; + p_uniname->name_hash = exfat_calc_chksum_2byte((void *) upname, + unilen << 1, 0, CS_DEFAULT); + + if (p_lossy) + *p_lossy = lossy; + + return unilen; +} + +static s32 __exfat_nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *p_uniname, u8 *p_cstring, s32 buflen) +{ + s32 i, j, len, out_len = 0; + u8 buf[MAX_CHARSET_SIZE]; + const u16 *uniname = p_uniname->name; + struct nls_table *nls = EXFAT_SB(sb)->nls_io; + + i = 0; + while ((i < MAX_NAME_LENGTH) && (out_len < (buflen-1))) { + if (*uniname == (u16)'\0') + break; + + len = convert_uni_to_ch(nls, *uniname, buf, NULL); + + if (out_len + len >= buflen) + len = (buflen - 1) - out_len; + + out_len += len; + + if (len > 1) { + for (j = 0; j < len; j++) + *p_cstring++ = (s8) *(buf+j); + } else { /* len == 1 */ + *p_cstring++ = (s8) *buf; + } + + uniname++; + i++; + } + + *p_cstring = '\0'; + return out_len; +} + +static s32 __exfat_nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring, + const s32 len, UNI_NAME_T *p_uniname, s32 *p_lossy) +{ + s32 i, unilen, lossy = NLS_NAME_NO_LOSSY; + u16 upname[MAX_NAME_LENGTH+1]; + u16 *uniname = p_uniname->name; + struct nls_table *nls = EXFAT_SB(sb)->nls_io; + + BUG_ON(!len); + + i = unilen = 0; + while ((unilen < MAX_NAME_LENGTH) && (i < len)) { + i += convert_ch_to_uni(nls, (u8 *)(p_cstring+i), uniname, &lossy); + + if ((*uniname < 0x0020) || nls_wstrchr(bad_uni_chars, *uniname)) + lossy |= NLS_NAME_LOSSY; + + *(upname+unilen) = nls_upper(sb, *uniname); + + uniname++; + unilen++; + } + + if (*(p_cstring+i) != '\0') + lossy |= NLS_NAME_OVERLEN; + + *uniname = (u16)'\0'; + p_uniname->name_len = unilen; + p_uniname->name_hash = + exfat_calc_chksum_2byte((void *) upname, unilen<<1, 0, CS_DEFAULT); + + if (p_lossy) + *p_lossy = lossy; + + return unilen; +} + +s32 exfat_nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *uniname, u8 *p_cstring, s32 buflen) +{ + if (EXFAT_SB(sb)->options.utf8) + return __nls_utf16s_to_vfsname(sb, uniname, p_cstring, buflen); + + return __exfat_nls_uni16s_to_vfsname(sb, uniname, p_cstring, buflen); +} + +s32 exfat_nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring, const s32 len, UNI_NAME_T *uniname, s32 *p_lossy) +{ + if (EXFAT_SB(sb)->options.utf8) + return __nls_vfsname_to_utf16s(sb, p_cstring, len, uniname, p_lossy); + return __exfat_nls_vfsname_to_uni16s(sb, p_cstring, len, uniname, p_lossy); +} + +static s32 convert_ch_to_uni(struct nls_table *nls, u8 *ch, u16 *uni, s32 *lossy) +{ + int len; + + *uni = 0x0; + + if (ch[0] < 0x80) { + *uni = (u16) ch[0]; + return 1; + } + + len = nls->char2uni(ch, MAX_CHARSET_SIZE, uni); + if (len < 0) { + /* conversion failed */ + DMSG("%s: fail to use nls\n", __func__); + if (lossy != NULL) + *lossy |= NLS_NAME_LOSSY; + *uni = (u16) '_'; + if (!strcmp(nls->charset, "utf8")) + return 1; + return 2; + } + + return len; +} + +static s32 convert_uni_to_ch(struct nls_table *nls, u16 uni, u8 *ch, s32 *lossy) +{ + int len; + + ch[0] = 0x0; + + if (uni < 0x0080) { + ch[0] = (u8) uni; + return 1; + } + + len = nls->uni2char(uni, ch, MAX_CHARSET_SIZE); + if (len < 0) { + /* conversion failed */ + DMSG("%s: fail to use nls\n", __func__); + if (lossy != NULL) + *lossy |= NLS_NAME_LOSSY; + ch[0] = '_'; + return 1; + } + + return len; + +} diff --git a/fs/exfat/super.c b/fs/exfat/super.c new file mode 100644 index 000000000000..aa02b8f5af03 --- /dev/null +++ b/fs/exfat/super.c @@ -0,0 +1,3891 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * super.c: exFAT glue layer for supporting VFS + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for mark_page_accessed() */ +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0) +#error EXFAT only supports linux kernel version 3.0 or higher +#endif + +#include "version.h" +#include "config.h" + +#include "exfat.h" +#include "core.h" + +/* skip iterating emit_dots when dir is empty */ +#define ITER_POS_FILLED_DOTS (2) + +static struct kset *exfat_kset; +static struct kmem_cache *exfat_inode_cachep; + +static DEFINE_MUTEX(_lock_core); + +static int exfat_default_codepage = CONFIG_EXFAT_DEFAULT_CODEPAGE; +static char exfat_default_iocharset[] = CONFIG_EXFAT_DEFAULT_IOCHARSET; +static const char exfat_iocharset_with_utf8[] = "iso8859-1"; + +static void exfat_truncate(struct inode *inode, loff_t old_size); +static int exfat_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create); + +static struct inode *exfat_iget(struct super_block *sb, loff_t i_pos); +static struct inode *exfat_build_inode(struct super_block *sb, const FILE_ID_T *fid, loff_t i_pos); +static void exfat_detach(struct inode *inode); +static void exfat_attach(struct inode *inode, loff_t i_pos); +static inline unsigned long exfat_hash(loff_t i_pos); +static s32 __exfat_sync_fs(struct super_block *sb, s32 do_sync); +static int __exfat_write_inode(struct inode *inode, int sync); +static int exfat_sync_inode(struct inode *inode); +static int exfat_write_inode(struct inode *inode, struct writeback_control *wbc); +static void exfat_write_super(struct super_block *sb); +static void exfat_write_failed(struct address_space *mapping, loff_t to); + +static void exfat_init_namebuf(DENTRY_NAMEBUF_T *nb); +static int exfat_alloc_namebuf(DENTRY_NAMEBUF_T *nb); +static void exfat_free_namebuf(DENTRY_NAMEBUF_T *nb); + +static int __exfat_getattr(struct inode *inode, struct kstat *stat); +static void __exfat_writepage_end_io(struct bio *bio, int err); +static inline void lock_super(struct super_block *sb); +static inline void unlock_super(struct super_block *sb); +static int exfat_create_compat(struct inode *dir, struct dentry *dentry); +static int __exfat_revalidate(struct dentry *dentry); +static int __exfat_revalidate_ci(struct dentry *dentry, unsigned int flags); +static int __exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync); +static struct dentry *__exfat_lookup(struct inode *dir, struct dentry *dentry); +static int __exfat_mkdir(struct inode *dir, struct dentry *dentry); +static int __exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); +static int __exfat_show_options(struct seq_file *m, struct super_block *sb); +static inline ssize_t __exfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs); +static inline ssize_t __exfat_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + loff_t count, unsigned long nr_segs); +static int __exfat_d_hash(const struct dentry *dentry, struct qstr *qstr); +static int __exfat_d_hashi(const struct dentry *dentry, struct qstr *qstr); +static int __exfat_cmp(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name); +static int __exfat_cmpi(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name); + +/* mount the file system volume */ +static s32 exfat_mount(struct super_block *sb) +{ + s32 err; + + /* acquire the core lock for file system ccritical section */ + mutex_lock(&_lock_core); + + err = exfat_meta_cache_init(sb); + if (err) + goto out; + + err = exfat_fscore_mount(sb); +out: + if (err) + exfat_meta_cache_shutdown(sb); + + /* release the core lock for file system critical section */ + mutex_unlock(&_lock_core); + + return err; +} + +/* unmount the file system volume */ +static s32 exfat_umount(struct super_block *sb) +{ + s32 err; + + /* acquire the core lock for file system ccritical section */ + mutex_lock(&_lock_core); + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_umount(sb); + exfat_meta_cache_shutdown(sb); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + + /* release the core lock for file system critical section */ + mutex_unlock(&_lock_core); + + return err; +} + +static s32 exfat_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) +{ + s32 err; + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_set_vol_flags(sb, new_flag, always_sync); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* read the target string of symlink */ +static s32 exfat_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && buffer); + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_read_link(inode, fid, buffer, count, rcount); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* write the target string of symlink */ +static s32 exfat_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && buffer); + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_write_link(inode, fid, buffer, count, wcount); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* remove a file */ +static s32 exfat_remove(struct inode *inode, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid); + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_remove(inode, fid); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* get the information of a given file */ +static s32 exfat_read_inode(struct inode *inode, DIR_ENTRY_T *info) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_read_inode(inode, info); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* return the cluster number in the given cluster offset */ +static s32 exfat_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(clu); + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_map_clus(inode, clu_offset, clu, dest); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* read a directory entry from the opened directory */ +static s32 __exfat_readdir(struct inode *inode, DIR_ENTRY_T *dir_entry) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(dir_entry); + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_readdir(inode, dir_entry); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +/* reflect the internal dirty flags to VFS bh dirty flags */ +static s32 exfat_cache_flush(struct super_block *sb, int do_sync) +{ + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + exfat_fcache_flush(sb, do_sync); + exfat_dcache_flush(sb, do_sync); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) */ +static inline void bio_set_dev(struct bio *bio, struct block_device *bdev) +{ + bio->bi_bdev = bdev; +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,9,0) +#define current_time(x) CURRENT_TIME_SEC +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +static int exfat_getattr(const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int query_flags) +{ + struct inode *inode = d_backing_inode(path->dentry); + + return __exfat_getattr(inode, stat); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) */ +static int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) +{ + struct inode *inode = dentry->d_inode; + + return __exfat_getattr(inode, stat); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +static inline void __exfat_clean_bdev_aliases(struct block_device *bdev, sector_t block) +{ + clean_bdev_aliases(bdev, block, 1); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) */ +static inline void __exfat_clean_bdev_aliases(struct block_device *bdev, sector_t block) +{ + unmap_underlying_metadata(bdev, block); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + /* + * The VFS already checks for existence, so for local filesystems + * the RENAME_NOREPLACE implementation is equivalent to plain rename. + * Don't support any other flags + */ + if (flags & ~RENAME_NOREPLACE) + return -EINVAL; + return __exfat_rename(old_dir, old_dentry, new_dir, new_dentry); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) */ +static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + return __exfat_rename(old_dir, old_dentry, new_dir, new_dentry); +} + +// setattr_prepare() was backported to several LTS kernels +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 39) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) +#define SETATTR_PREPARE_AVAILABLE +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37) && \ + LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) +#define SETATTR_PREPARE_AVAILABLE +#endif + +#ifndef SETATTR_PREPARE_AVAILABLE +static int setattr_prepare(struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = dentry->d_inode; + + return inode_change_ok(inode, attr); +} +#endif +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) +static inline void __exfat_submit_bio_write(struct bio *bio) +{ + bio_set_op_attrs(bio, REQ_OP_WRITE, 0); + submit_bio(bio); +} + +static inline unsigned int __exfat_full_name_hash(const struct dentry *dentry, const char *name, unsigned int len) +{ + return full_name_hash(dentry, name, len); +} + +static inline unsigned long __exfat_init_name_hash(const struct dentry *dentry) +{ + return init_name_hash(dentry); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) */ +static inline void __exfat_submit_bio_write(struct bio *bio) +{ + submit_bio(WRITE, bio); +} + +static inline unsigned int __exfat_full_name_hash(const struct dentry *unused, const char *name, unsigned int len) +{ + return full_name_hash(name, len); +} + +static inline unsigned long __exfat_init_name_hash(const struct dentry *unused) +{ + return init_name_hash(); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 21) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 21) */ +static inline void inode_lock(struct inode *inode) +{ + mutex_lock(&inode->i_mutex); +} + +static inline void inode_unlock(struct inode *inode) +{ + mutex_unlock(&inode->i_mutex); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static inline int exfat_remount_syncfs(struct super_block *sb) +{ + sync_filesystem(sb); + return 0; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) */ +static inline int exfat_remount_syncfs(struct super_block *sb) +{ + /* + * We don`t need to call sync_filesystem(sb), + * Because VFS calls it. + */ + return 0; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) +static inline sector_t __exfat_bio_sector(struct bio *bio) +{ + return bio->bi_iter.bi_sector; +} + +static inline void __exfat_set_bio_iterate(struct bio *bio, sector_t sector, + unsigned int size, unsigned int idx, unsigned int done) +{ + struct bvec_iter *iter = &(bio->bi_iter); + + iter->bi_sector = sector; + iter->bi_size = size; + iter->bi_idx = idx; + iter->bi_bvec_done = done; +} + +static void __exfat_truncate_pagecache(struct inode *inode, + loff_t to, loff_t newsize) +{ + truncate_pagecache(inode, newsize); +} + +static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) +{ + return __exfat_d_hash(dentry, qstr); +} + +static int exfat_d_hashi(const struct dentry *dentry, struct qstr *qstr) +{ + return __exfat_d_hashi(dentry, qstr); +} + +//instead of exfat_readdir +static int exfat_iterate(struct file *filp, struct dir_context *ctx) +{ + struct inode *inode = filp->f_path.dentry->d_inode; + struct super_block *sb = inode->i_sb; + DIR_ENTRY_T de; + DENTRY_NAMEBUF_T *nb = &(de.NameBuf); + unsigned long inum; + loff_t cpos; + int err = 0, fake_offset = 0; + + exfat_init_namebuf(nb); + lock_super(sb); + + cpos = ctx->pos; + if (!dir_emit_dots(filp, ctx)) + goto out; + if (ctx->pos == ITER_POS_FILLED_DOTS) { + cpos = 0; + fake_offset = 1; + } + if (cpos & (DENTRY_SIZE - 1)) { + err = -ENOENT; + goto out; + } + + /* name buffer should be allocated before use */ + err = exfat_alloc_namebuf(nb); + if (err) + goto out; +get_new: + EXFAT_I(inode)->fid.size = i_size_read(inode); + EXFAT_I(inode)->fid.rwoffset = cpos >> DENTRY_SIZE_BITS; + + if (cpos >= EXFAT_I(inode)->fid.size) + goto end_of_dir; + + err = __exfat_readdir(inode, &de); + if (err) { + // at least we tried to read a sector + // move cpos to next sector position (should be aligned) + if (err == -EIO) { + cpos += 1 << (sb->s_blocksize_bits); + cpos &= ~((u32)sb->s_blocksize-1); + } + + err = -EIO; + goto end_of_dir; + } + + cpos = EXFAT_I(inode)->fid.rwoffset << DENTRY_SIZE_BITS; + + if (!nb->lfn[0]) + goto end_of_dir; + + if (!memcmp(nb->sfn, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = inode->i_ino; + } else if (!memcmp(nb->sfn, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = parent_ino(filp->f_path.dentry); + } else { + loff_t i_pos = ((loff_t) EXFAT_I(inode)->fid.start_clu << 32) | + ((EXFAT_I(inode)->fid.rwoffset-1) & 0xffffffff); + struct inode *tmp = exfat_iget(sb, i_pos); + + if (tmp) { + inum = tmp->i_ino; + iput(tmp); + } else { + inum = iunique(sb, EXFAT_ROOT_INO); + } + } + + /* Before calling dir_emit(), sb_lock should be released. + * Because page fault can occur in dir_emit() when the size of buffer given + * from user is larger than one page size + */ + unlock_super(sb); + if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, + (de.Attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) + goto out_unlocked; + lock_super(sb); + + ctx->pos = cpos; + goto get_new; + +end_of_dir: + if (!cpos && fake_offset) + cpos = ITER_POS_FILLED_DOTS; + ctx->pos = cpos; +out: + unlock_super(sb); +out_unlocked: + /* + * To improve performance, free namebuf after unlock sb_lock. + * If namebuf is not allocated, this function do nothing + */ + exfat_free_namebuf(nb); + return err; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) */ +static inline sector_t __exfat_bio_sector(struct bio *bio) +{ + return bio->bi_sector; +} + +static inline void __exfat_set_bio_iterate(struct bio *bio, sector_t sector, + unsigned int size, unsigned int idx, unsigned int done) +{ + bio->bi_sector = sector; + bio->bi_idx = idx; + bio->bi_size = size; //PAGE_SIZE; +} + +static void __exfat_truncate_pagecache(struct inode *inode, + loff_t to, loff_t newsize) +{ + truncate_pagecache(inode, to, newsize); +} + +static int exfat_d_hash(const struct dentry *dentry, + const struct inode *inode, struct qstr *qstr) +{ + return __exfat_d_hash(dentry, qstr); +} + +static int exfat_d_hashi(const struct dentry *dentry, + const struct inode *inode, struct qstr *qstr) +{ + return __exfat_d_hashi(dentry, qstr); +} + +static int exfat_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + struct inode *inode = filp->f_path.dentry->d_inode; + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T de; + DENTRY_NAMEBUF_T *nb = &(de.NameBuf); + unsigned long inum; + loff_t cpos; + int err = 0, fake_offset = 0; + + exfat_init_namebuf(nb); + lock_super(sb); + + cpos = filp->f_pos; + /* Fake . and .. for the root directory. */ + while (cpos < ITER_POS_FILLED_DOTS) { + if (inode->i_ino == EXFAT_ROOT_INO) + inum = EXFAT_ROOT_INO; + else if (cpos == 0) + inum = inode->i_ino; + else /* (cpos == 1) */ + inum = parent_ino(filp->f_path.dentry); + + if (filldir(dirent, "..", cpos+1, cpos, inum, DT_DIR) < 0) + goto out; + cpos++; + filp->f_pos++; + } + if (cpos == ITER_POS_FILLED_DOTS) { + cpos = 0; + fake_offset = 1; + } + if (cpos & (DENTRY_SIZE - 1)) { + err = -ENOENT; + goto out; + } + + /* name buffer should be allocated before use */ + err = exfat_alloc_namebuf(nb); + if (err) + goto out; +get_new: + EXFAT_I(inode)->fid.size = i_size_read(inode); + EXFAT_I(inode)->fid.rwoffset = cpos >> DENTRY_SIZE_BITS; + + if (cpos >= EXFAT_I(inode)->fid.size) + goto end_of_dir; + + err = __exfat_readdir(inode, &de); + if (err) { + // at least we tried to read a sector + // move cpos to next sector position (should be aligned) + if (err == -EIO) { + cpos += 1 << (sb->s_blocksize_bits); + cpos &= ~((u32)sb->s_blocksize-1); + } + + err = -EIO; + goto end_of_dir; + } + + cpos = EXFAT_I(inode)->fid.rwoffset << DENTRY_SIZE_BITS; + + if (!nb->lfn[0]) + goto end_of_dir; + + if (!memcmp(nb->sfn, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = inode->i_ino; + } else if (!memcmp(nb->sfn, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH)) { + inum = parent_ino(filp->f_path.dentry); + } else { + loff_t i_pos = ((loff_t) EXFAT_I(inode)->fid.start_clu << 32) | + ((EXFAT_I(inode)->fid.rwoffset-1) & 0xffffffff); + struct inode *tmp = exfat_iget(sb, i_pos); + + if (tmp) { + inum = tmp->i_ino; + iput(tmp); + } else { + inum = iunique(sb, EXFAT_ROOT_INO); + } + } + + /* Before calling dir_emit(), sb_lock should be released. + * Because page fault can occur in dir_emit() when the size of buffer given + * from user is larger than one page size + */ + unlock_super(sb); + if (filldir(dirent, nb->lfn, strlen(nb->lfn), cpos, inum, + (de.Attr & ATTR_SUBDIR) ? DT_DIR : DT_REG) < 0) + goto out_unlocked; + lock_super(sb); + + filp->f_pos = cpos; + goto get_new; + +end_of_dir: + if (!cpos && fake_offset) + cpos = ITER_POS_FILLED_DOTS; + filp->f_pos = cpos; +out: + unlock_super(sb); +out_unlocked: + /* + * To improve performance, free namebuf after unlock sb_lock. + * If namebuf is not allocated, this function do nothing + */ + exfat_free_namebuf(nb); + return err; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) + /* EMPTY */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0) */ +static inline struct inode *file_inode(const struct file *f) +{ + return f->f_dentry->d_inode; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) +static inline int __is_sb_dirty(struct super_block *sb) +{ + return EXFAT_SB(sb)->s_dirt; +} + +static inline void __set_sb_clean(struct super_block *sb) +{ + EXFAT_SB(sb)->s_dirt = 0; +} + +/* Workqueue wrapper for exfat_write_super () */ +static void __write_super_delayed(struct work_struct *work) +{ + struct exfat_sb_info *sbi; + struct super_block *sb; + + sbi = container_of(work, struct exfat_sb_info, write_super_work.work); + sb = sbi->host_sb; + + /* XXX: Is this needed? */ + if (!sb || !down_read_trylock(&sb->s_umount)) { + DMSG("%s: skip delayed work(write_super).\n", __func__); + return; + } + + DMSG("%s: do delayed_work(write_super).\n", __func__); + + spin_lock(&sbi->work_lock); + sbi->write_super_queued = 0; + spin_unlock(&sbi->work_lock); + + exfat_write_super(sb); + + up_read(&sb->s_umount); +} + +static void setup_exfat_sync_super_wq(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + mutex_init(&sbi->s_lock); + spin_lock_init(&sbi->work_lock); + INIT_DELAYED_WORK(&sbi->write_super_work, __write_super_delayed); + sbi->host_sb = sb; +} + +static inline bool __cancel_delayed_work_sync(struct exfat_sb_info *sbi) +{ + return cancel_delayed_work_sync(&sbi->write_super_work); +} + +static inline void lock_super(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + mutex_lock(&sbi->s_lock); +} + +static inline void unlock_super(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + mutex_unlock(&sbi->s_lock); +} + +static int exfat_revalidate(struct dentry *dentry, unsigned int flags) +{ + if (flags & LOOKUP_RCU) + return -ECHILD; + + return __exfat_revalidate(dentry); +} + +static int exfat_revalidate_ci(struct dentry *dentry, unsigned int flags) +{ + if (flags & LOOKUP_RCU) + return -ECHILD; + + return __exfat_revalidate_ci(dentry, flags); +} + +static struct inode *exfat_iget(struct super_block *sb, loff_t i_pos) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *info; + struct hlist_head *head = sbi->inode_hashtable + exfat_hash(i_pos); + struct inode *inode = NULL; + + spin_lock(&sbi->inode_hash_lock); + hlist_for_each_entry(info, head, i_hash_fat) { + BUG_ON(info->vfs_inode.i_sb != sb); + + if (i_pos != info->i_pos) + continue; + inode = igrab(&info->vfs_inode); + if (inode) + break; + } + spin_unlock(&sbi->inode_hash_lock); + return inode; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) */ +static inline int __is_sb_dirty(struct super_block *sb) +{ + return sb->s_dirt; +} + +static inline void __set_sb_clean(struct super_block *sb) +{ + sb->s_dirt = 0; +} + +static void setup_exfat_sync_super_wq(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + sbi->host_sb = sb; +} + +static inline bool __cancel_delayed_work_sync(struct exfat_sb_info *sbi) +{ + /* DO NOTHING */ + return 0; +} + +static inline void clear_inode(struct inode *inode) +{ + end_writeback(inode); +} + +static int exfat_revalidate(struct dentry *dentry, struct nameidata *nd) +{ + if (nd && nd->flags & LOOKUP_RCU) + return -ECHILD; + + return __exfat_revalidate(dentry); +} + +static int exfat_revalidate_ci(struct dentry *dentry, struct nameidata *nd) +{ + if (nd && nd->flags & LOOKUP_RCU) + return -ECHILD; + + return __exfat_revalidate_ci(dentry, nd ? nd->flags : 0); + +} + +static struct inode *exfat_iget(struct super_block *sb, loff_t i_pos) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_inode_info *info; + struct hlist_node *node; + struct hlist_head *head = sbi->inode_hashtable + exfat_hash(i_pos); + struct inode *inode = NULL; + + spin_lock(&sbi->inode_hash_lock); + hlist_for_each_entry(info, node, head, i_hash_fat) { + BUG_ON(info->vfs_inode.i_sb != sb); + + if (i_pos != info->i_pos) + continue; + inode = igrab(&info->vfs_inode); + if (inode) + break; + } + spin_unlock(&sbi->inode_hash_lock); + return inode; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + return __exfat_lookup(dir, dentry); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0) */ +static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + return __exfat_lookup(dir, dentry); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + /* NOTHING NOW */ +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) */ +#define GLOBAL_ROOT_UID (0) +#define GLOBAL_ROOT_GID (0) + +static inline bool uid_eq(uid_t left, uid_t right) +{ + return left == right; +} + +static inline bool gid_eq(gid_t left, gid_t right) +{ + return left == right; +} + +static inline uid_t from_kuid_munged(struct user_namespace *to, uid_t kuid) +{ + return kuid; +} + +static inline gid_t from_kgid_munged(struct user_namespace *to, gid_t kgid) +{ + return kgid; +} + +static inline uid_t make_kuid(struct user_namespace *from, uid_t uid) +{ + return uid; +} + +static inline gid_t make_kgid(struct user_namespace *from, gid_t gid) +{ + return gid; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +static struct dentry *__d_make_root(struct inode *root_inode) +{ + return d_make_root(root_inode); +} + +static void __exfat_do_truncate(struct inode *inode, loff_t old, loff_t new) +{ + down_write(&EXFAT_I(inode)->truncate_lock); + truncate_setsize(inode, new); + exfat_truncate(inode, old); + up_write(&EXFAT_I(inode)->truncate_lock); +} + +static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block) +{ + sector_t blocknr; + + /* exfat_get_cluster() assumes the requested blocknr isn't truncated. */ + down_read(&EXFAT_I(mapping->host)->truncate_lock); + blocknr = generic_block_bmap(mapping, block, exfat_get_block); + up_read(&EXFAT_I(mapping->host)->truncate_lock); + return blocknr; +} + +static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + return __exfat_mkdir(dir, dentry); +} + +static int exfat_show_options(struct seq_file *m, struct dentry *root) +{ + return __exfat_show_options(m, root->d_sb); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) */ +static inline void set_nlink(struct inode *inode, unsigned int nlink) +{ + inode->i_nlink = nlink; +} + +static struct dentry *__d_make_root(struct inode *root_inode) +{ + return d_alloc_root(root_inode); +} + +static void __exfat_do_truncate(struct inode *inode, loff_t old, loff_t new) +{ + truncate_setsize(inode, new); + exfat_truncate(inode, old); +} + +static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block) +{ + sector_t blocknr; + + /* exfat_get_cluster() assumes the requested blocknr isn't truncated. */ + down_read(&mapping->host->i_alloc_sem); + blocknr = generic_block_bmap(mapping, block, exfat_get_block); + up_read(&mapping->host->i_alloc_sem); + return blocknr; +} + +static int exfat_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + return __exfat_mkdir(dir, dentry); +} + +static int exfat_show_options(struct seq_file *m, struct vfsmount *mnt) +{ + return __exfat_show_options(m, mnt->mnt_sb); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0) +#define __exfat_generic_file_fsync(filp, start, end, datasync) \ + generic_file_fsync(filp, start, end, datasync) + +static int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) +{ + return __exfat_file_fsync(filp, start, end, datasync); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) */ +#define __exfat_generic_file_fsync(filp, start, end, datasync) \ + generic_file_fsync(filp, datasync) +static int exfat_file_fsync(struct file *filp, int datasync) +{ + return __exfat_file_fsync(filp, 0, 0, datasync); +} +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0) +static void exfat_writepage_end_io(struct bio *bio) +{ + __exfat_writepage_end_io(bio, blk_status_to_errno(bio->bi_status)); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) +static void exfat_writepage_end_io(struct bio *bio) +{ + __exfat_writepage_end_io(bio, bio->bi_error); +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) */ +static void exfat_writepage_end_io(struct bio *bio, int err) +{ + if (test_bit(BIO_UPTODATE, &bio->bi_flags)) + err = 0; + __exfat_writepage_end_io(bio, err); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) +static int exfat_cmp(const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __exfat_cmp(dentry, len, str, name); +} + +static int exfat_cmpi(const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __exfat_cmpi(dentry, len, str, name); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) +static int exfat_cmp(const struct dentry *parent, const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __exfat_cmp(dentry, len, str, name); +} + +static int exfat_cmpi(const struct dentry *parent, const struct dentry *dentry, + unsigned int len, const char *str, const struct qstr *name) +{ + return __exfat_cmpi(dentry, len, str, name); +} +#else +static int exfat_cmp(const struct dentry *parent, const struct inode *pinode, + const struct dentry *dentry, const struct inode *inode, + unsigned int len, const char *str, const struct qstr *name) +{ + return __exfat_cmp(dentry, len, str, name); +} + +static int exfat_cmpi(const struct dentry *parent, const struct inode *pinode, + const struct dentry *dentry, const struct inode *inode, + unsigned int len, const char *str, const struct qstr *name) +{ + return __exfat_cmpi(dentry, len, str, name); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) +static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); + int rw = iov_iter_rw(iter); + loff_t offset = iocb->ki_pos; + + return __exfat_direct_IO(rw, iocb, inode, + (void *)iter, offset, count, 0 /* UNUSED */); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +static ssize_t exfat_direct_IO(struct kiocb *iocb, + struct iov_iter *iter, + loff_t offset) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); + int rw = iov_iter_rw(iter); + + return __exfat_direct_IO(rw, iocb, inode, + (void *)iter, offset, count, 0 /* UNUSED */); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static ssize_t exfat_direct_IO(int rw, struct kiocb *iocb, + struct iov_iter *iter, + loff_t offset) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_iter_count(iter); + + return __exfat_direct_IO(rw, iocb, inode, + (void *)iter, offset, count, 0 /* UNUSED */); +} +#else +static ssize_t exfat_direct_IO(int rw, struct kiocb *iocb, + const struct iovec *iov, loff_t offset, unsigned long nr_segs) +{ + struct file *file = iocb->ki_filp; + struct address_space *mapping = file->f_mapping; + struct inode *inode = mapping->host; + size_t count = iov_length(iov, nr_segs); + + return __exfat_direct_IO(rw, iocb, inode, + (void *)iov, offset, count, nr_segs); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) +static inline ssize_t __exfat_blkdev_direct_IO(int unused, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t unused_1, + unsigned long nr_segs) +{ + struct iov_iter *iter = (struct iov_iter *)iov_u; + + return blockdev_direct_IO(iocb, inode, iter, exfat_get_block); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +static inline ssize_t __exfat_blkdev_direct_IO(int unused, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + struct iov_iter *iter = (struct iov_iter *)iov_u; + + return blockdev_direct_IO(iocb, inode, iter, offset, exfat_get_block); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) +static inline ssize_t __exfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + struct iov_iter *iter = (struct iov_iter *)iov_u; + + return blockdev_direct_IO(rw, iocb, inode, iter, + offset, exfat_get_block); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +static inline ssize_t __exfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + const struct iovec *iov = (const struct iovec *)iov_u; + + return blockdev_direct_IO(rw, iocb, inode, iov, + offset, nr_segs, exfat_get_block); +} +#else +static inline ssize_t __exfat_blkdev_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + unsigned long nr_segs) +{ + const struct iovec *iov = (const struct iovec *)iov_u; + + return blockdev_direct_IO(rw, iocb, inode, inode->i_sb->s_bdev, iov, + offset, nr_segs, exfat_get_block, NULL); +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) +static const char *exfat_follow_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done) +{ + struct exfat_inode_info *ei = EXFAT_I(inode); + + return (char *)(ei->target); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) +static const char *exfat_follow_link(struct dentry *dentry, void **cookie) +{ + struct exfat_inode_info *ei = EXFAT_I(dentry->d_inode); + + return *cookie = (char *)(ei->target); +} +#else +static void *exfat_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + struct exfat_inode_info *ei = EXFAT_I(dentry->d_inode); + + nd_set_link(nd, (char *)(ei->target)); + return NULL; +} +#endif + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + bool excl) +{ + return exfat_create_compat(dir, dentry); +} +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) +static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, + struct nameidata *nd) +{ + return exfat_create_compat(dir, dentry); +} +#else +static int exfat_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + return exfat_create_compat(dir, dentry); +} +#endif + +static inline loff_t exfat_make_i_pos(FILE_ID_T *fid) +{ + return ((loff_t) fid->dir.dir << 32) | (fid->entry & 0xffffffff); +} + +static void exfat_init_namebuf(DENTRY_NAMEBUF_T *nb) +{ + nb->lfn = NULL; + nb->sfn = NULL; + nb->lfnbuf_len = 0; + nb->sfnbuf_len = 0; +} + +static int exfat_alloc_namebuf(DENTRY_NAMEBUF_T *nb) +{ + nb->lfn = __getname(); + if (!nb->lfn) + return -ENOMEM; + nb->sfn = nb->lfn + MAX_VFSNAME_BUF_SIZE; + nb->lfnbuf_len = MAX_VFSNAME_BUF_SIZE; + nb->sfnbuf_len = MAX_VFSNAME_BUF_SIZE; + return 0; +} + +static void exfat_free_namebuf(DENTRY_NAMEBUF_T *nb) +{ + if (!nb->lfn) + return; + + __putname(nb->lfn); + exfat_init_namebuf(nb); +} + +#define EXFAT_DSTATE_LOCKED (void *)(0xCAFE2016) +#define EXFAT_DSTATE_UNLOCKED (void *)(0x00000000) + +static inline void __lock_d_revalidate(struct dentry *dentry) +{ + spin_lock(&dentry->d_lock); + dentry->d_fsdata = EXFAT_DSTATE_LOCKED; + spin_unlock(&dentry->d_lock); +} + +static inline void __unlock_d_revalidate(struct dentry *dentry) +{ + spin_lock(&dentry->d_lock); + dentry->d_fsdata = EXFAT_DSTATE_UNLOCKED; + spin_unlock(&dentry->d_lock); +} + +/* __check_dstate_locked requires dentry->d_lock */ +static inline int __check_dstate_locked(struct dentry *dentry) +{ + if (dentry->d_fsdata == EXFAT_DSTATE_LOCKED) + return 1; + + return 0; +} + +/* + * If new entry was created in the parent, it could create the 8.3 + * alias (the shortname of logname). So, the parent may have the + * negative-dentry which matches the created 8.3 alias. + * + * If it happened, the negative dentry isn't actually negative + * anymore. So, drop it. + */ +static int __exfat_revalidate_common(struct dentry *dentry) +{ + int ret = 1; + + spin_lock(&dentry->d_lock); + if ((!dentry->d_inode) && (!__check_dstate_locked(dentry) && + (dentry->d_time != GET_IVERSION(dentry->d_parent->d_inode)))) { + ret = 0; + } + spin_unlock(&dentry->d_lock); + return ret; +} + +static int __exfat_revalidate(struct dentry *dentry) +{ + /* This is not negative dentry. Always valid. */ + if (dentry->d_inode) + return 1; + return __exfat_revalidate_common(dentry); +} + +static int __exfat_revalidate_ci(struct dentry *dentry, unsigned int flags) +{ + /* + * This is not negative dentry. Always valid. + * + * Note, rename() to existing directory entry will have ->d_inode, + * and will use existing name which isn't specified name by user. + * + * We may be able to drop this positive dentry here. But dropping + * positive dentry isn't good idea. So it's unsupported like + * rename("filename", "FILENAME") for now. + */ + if (dentry->d_inode) + return 1; +#if 0 /* Blocked below code for lookup_one_len() called by stackable FS */ + /* + * This may be nfsd (or something), anyway, we can't see the + * intent of this. So, since this can be for creation, drop it. + */ + if (!flags) + return 0; +#endif + /* + * Drop the negative dentry, in order to make sure to use the + * case sensitive name which is specified by user if this is + * for creation. + */ + if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)) + return 0; + return __exfat_revalidate_common(dentry); +} + + +/* returns the length of a struct qstr, ignoring trailing dots */ +static unsigned int __exfat_striptail_len(unsigned int len, const char *name) +{ + while (len && name[len - 1] == '.') + len--; + return len; +} + +static unsigned int exfat_striptail_len(const struct qstr *qstr) +{ + return __exfat_striptail_len(qstr->len, qstr->name); +} + +/* + * Compute the hash for the exfat name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The exfat fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int __exfat_d_hash(const struct dentry *dentry, struct qstr *qstr) +{ + unsigned int len = exfat_striptail_len(qstr); + + qstr->hash = __exfat_full_name_hash(dentry, qstr->name, len); + return 0; +} + +/* + * Compute the hash for the exfat name corresponding to the dentry. + * Note: if the name is invalid, we leave the hash code unchanged so + * that the existing dentry can be used. The exfat fs routines will + * return ENOENT or EINVAL as appropriate. + */ +static int __exfat_d_hashi(const struct dentry *dentry, struct qstr *qstr) +{ + struct nls_table *t = EXFAT_SB(dentry->d_sb)->nls_io; + const unsigned char *name; + unsigned int len; + unsigned long hash; + + name = qstr->name; + len = exfat_striptail_len(qstr); + + hash = __exfat_init_name_hash(dentry); + while (len--) + hash = partial_name_hash(nls_tolower(t, *name++), hash); + qstr->hash = end_name_hash(hash); + + return 0; +} + +/* + * Case sensitive compare of two exfat names. + */ +static int __exfat_cmp(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name) +{ + unsigned int alen, blen; + + /* A filename cannot end in '.' or we treat it like it has none */ + alen = exfat_striptail_len(name); + blen = __exfat_striptail_len(len, str); + if (alen == blen) { + if (strncmp(name->name, str, alen) == 0) + return 0; + } + return 1; +} + +/* + * Case insensitive compare of two exfat names. + */ +static int __exfat_cmpi(const struct dentry *dentry, unsigned int len, + const char *str, const struct qstr *name) +{ + struct nls_table *t = EXFAT_SB(dentry->d_sb)->nls_io; + unsigned int alen, blen; + + /* A filename cannot end in '.' or we treat it like it has none */ + alen = exfat_striptail_len(name); + blen = __exfat_striptail_len(len, str); + if (alen == blen) { + if (nls_strnicmp(t, name->name, str, alen) == 0) + return 0; + } + return 1; +} + +static const struct dentry_operations exfat_dentry_ops = { + .d_revalidate = exfat_revalidate, + .d_hash = exfat_d_hash, + .d_compare = exfat_cmp, +}; + +static const struct dentry_operations exfat_ci_dentry_ops = { + .d_revalidate = exfat_revalidate_ci, + .d_hash = exfat_d_hashi, + .d_compare = exfat_cmpi, +}; + +static int exfat_file_mmap(struct file *file, struct vm_area_struct *vm_struct) +{ + return generic_file_mmap(file, vm_struct); +} + +static int exfat_ioctl_volume_id(struct inode *dir) +{ + struct exfat_sb_info *sbi = EXFAT_SB(dir->i_sb); + FS_INFO_T *fsi = &(sbi->fsi); + + return fsi->vol_id; +} + +static long exfat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + + if (cmd != EXFAT_IOCTL_GET_VOLUME_ID) + return -ENOTSUPP; + + return exfat_ioctl_volume_id(inode); +} + +static int __exfat_getattr(struct inode *inode, struct kstat *stat) +{ + generic_fillattr(inode, stat); + stat->blksize = EXFAT_SB(inode->i_sb)->fsi.cluster_size; + + return 0; +} + +static void __exfat_writepage_end_io(struct bio *bio, int err) +{ + struct page *page = bio->bi_io_vec->bv_page; + struct super_block *sb = page->mapping->host->i_sb; + + ASSERT(bio->bi_vcnt == 1); /* Single page endio */ + ASSERT(bio_data_dir(bio)); /* Write */ + + if (err) { + SetPageError(page); + mapping_set_error(page->mapping, err); + } + + end_page_writeback(page); + bio_put(bio); + + // Update trace info. + atomic_dec(&EXFAT_SB(sb)->stat_n_pages_queued); +} + +static int __exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) +{ + return __exfat_generic_file_fsync(filp, start, end, datasync); +} + +static const struct file_operations exfat_dir_operations = { + .llseek = generic_file_llseek, + .read = generic_read_dir, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) + .iterate = exfat_iterate, +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) */ + .readdir = exfat_readdir, +#endif + .fsync = exfat_file_fsync, + .unlocked_ioctl = exfat_generic_ioctl, +}; + +/* create a file */ +static s32 __exfat_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + /* check the validity of pointer parameters */ + ASSERT(fid && path); + + if (unlikely(!strlen(path))) + return -EINVAL; + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_create(inode, path, mode, fid); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +static int exfat_create_compat(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + FILE_ID_T fid; + loff_t i_pos; + int err; + + lock_super(sb); + + err = __exfat_create(dir, (u8 *) dentry->d_name.name, FM_REGULAR, &fid); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + INC_IVERSION(dir); + dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) exfat_sync_inode(dir); + else + mark_inode_dirty(dir); + + i_pos = exfat_make_i_pos(&fid); + inode = exfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + INC_IVERSION(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + d_instantiate(dentry, inode); +out: + __unlock_d_revalidate(dentry); + unlock_super(sb); + + return err; +} + + +static int exfat_find(struct inode *dir, struct qstr *qname, FILE_ID_T *fid) +{ + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + u8 *path = (u8 *)qname->name; + int err; + + if (qname->len == 0) + return -ENOENT; + + /* check the validity of pointer parameters */ + ASSERT(fid && path); + + if (unlikely(!strlen(path))) + return -EINVAL; + + mutex_lock(&(sbi->s_vlock)); + err = exfat_fscore_lookup(dir, path, fid); + mutex_unlock(&(sbi->s_vlock)); + if (err) + return -ENOENT; + + return 0; +} + +static int exfat_d_anon_disconn(struct dentry *dentry) +{ + return IS_ROOT(dentry) && (dentry->d_flags & DCACHE_DISCONNECTED); +} + +static struct dentry *__exfat_lookup(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + struct dentry *alias; + int err; + FILE_ID_T fid; + loff_t i_pos; + u64 ret; + mode_t i_mode; + + lock_super(sb); + err = exfat_find(dir, &dentry->d_name, &fid); + if (err) { + if (err == -ENOENT) { + inode = NULL; + goto out; + } + goto error; + } + + i_pos = exfat_make_i_pos(&fid); + inode = exfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto error; + } + + i_mode = inode->i_mode; + if (S_ISLNK(i_mode) && !EXFAT_I(inode)->target) { + EXFAT_I(inode)->target = kmalloc((i_size_read(inode)+1), GFP_KERNEL); + if (!EXFAT_I(inode)->target) { + err = -ENOMEM; + goto error; + } + exfat_read_link(dir, &fid, EXFAT_I(inode)->target, i_size_read(inode), &ret); + *(EXFAT_I(inode)->target + i_size_read(inode)) = '\0'; + } + + alias = d_find_alias(inode); + + /* + * Checking "alias->d_parent == dentry->d_parent" to make sure + * FS is not corrupted (especially double linked dir). + */ + if (alias && alias->d_parent == dentry->d_parent && + !exfat_d_anon_disconn(alias)) { + + /* + * Unhashed alias is able to exist because of revalidate() + * called by lookup_fast. You can easily make this status + * by calling create and lookup concurrently + * In such case, we reuse an alias instead of new dentry + */ + if (d_unhashed(alias)) { + exfat_msg(sb, KERN_INFO, "rehashed a dentry(%p) " + "in read lookup", alias); + d_drop(dentry); + d_rehash(alias); + } else if (!S_ISDIR(i_mode)) { + /* + * This inode has non anonymous-DCACHE_DISCONNECTED + * dentry. This means, the user did ->lookup() by an + * another name (longname vs 8.3 alias of it) in past. + * + * Switch to new one for reason of locality if possible. + */ + d_move(alias, dentry); + } + iput(inode); + unlock_super(sb); + return alias; + } + dput(alias); +out: + /* initialize d_time even though it is positive dentry */ + dentry->d_time = GET_IVERSION(dir); + unlock_super(sb); + + dentry = d_splice_alias(inode, dentry); + + return dentry; + +error: + unlock_super(sb); + return ERR_PTR(err); +} + + +static int exfat_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + int err; + + lock_super(sb); + + EXFAT_I(inode)->fid.size = i_size_read(inode); + + /* check the validity of pointer parameters */ + ASSERT(fid); + + mutex_lock(&(sbi->s_vlock)); + err = exfat_fscore_unlink(dir, fid); + mutex_unlock(&(sbi->s_vlock)); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + INC_IVERSION(dir); + dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) exfat_sync_inode(dir); + else + mark_inode_dirty(dir); + + clear_nlink(inode); + inode->i_mtime = inode->i_atime = current_time(inode); + exfat_detach(inode); + dentry->d_time = GET_IVERSION(dir); +out: + __unlock_d_revalidate(dentry); + unlock_super(sb); + return err; +} + +static int exfat_symlink(struct inode *dir, struct dentry *dentry, const char *target) +{ + struct super_block *sb = dir->i_sb; + struct inode *inode; + FILE_ID_T fid; + loff_t i_pos; + int err; + u64 len = (u64) strlen(target); + u64 ret; + + /* symlink option check */ + if (!EXFAT_SB(sb)->options.symlink) + return -ENOTSUPP; + + lock_super(sb); + + err = __exfat_create(dir, (u8 *) dentry->d_name.name, FM_SYMLINK, &fid); + if (err) + goto out; + + err = exfat_write_link(dir, &fid, (char *) target, len, &ret); + + if (err) { + exfat_remove(dir, &fid); + goto out; + } + + __lock_d_revalidate(dentry); + + INC_IVERSION(dir); + dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) exfat_sync_inode(dir); + else + mark_inode_dirty(dir); + + i_pos = exfat_make_i_pos(&fid); + inode = exfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + INC_IVERSION(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + EXFAT_I(inode)->target = kmemdup(target, len + 1, GFP_KERNEL); + if (!EXFAT_I(inode)->target) { + err = -ENOMEM; + goto out; + } + + d_instantiate(dentry, inode); +out: + __unlock_d_revalidate(dentry); + unlock_super(sb); + return err; +} + +static int __exfat_mkdir(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct inode *inode; + FILE_ID_T fid; + loff_t i_pos; + u8 *path; + int err; + + lock_super(sb); + + path = (u8 *) dentry->d_name.name; + + /* check the validity of pointer parameters */ + ASSERT(path); + + if (unlikely(!strlen(path))) { + err = -EINVAL; + goto out; + } + + mutex_lock(&(sbi->s_vlock)); + err = exfat_fscore_mkdir(dir, path, &fid); + mutex_unlock(&(sbi->s_vlock)); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + INC_IVERSION(dir); + dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) exfat_sync_inode(dir); + else + mark_inode_dirty(dir); + inc_nlink(dir); + + i_pos = exfat_make_i_pos(&fid); + inode = exfat_build_inode(sb, &fid, i_pos); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto out; + } + INC_IVERSION(inode); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + /* timestamp is already written, so mark_inode_dirty() is unneeded. */ + + d_instantiate(dentry, inode); + +out: + __unlock_d_revalidate(dentry); + unlock_super(sb); + + return err; +} + + +static int exfat_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct super_block *sb = dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + int err; + + lock_super(sb); + + EXFAT_I(inode)->fid.size = i_size_read(inode); + + /* check the validity of pointer parameters */ + ASSERT(fid); + + mutex_lock(&(sbi->s_vlock)); + err = exfat_fscore_rmdir(inode, fid); + mutex_unlock(&(sbi->s_vlock)); + if (err) + goto out; + + __lock_d_revalidate(dentry); + + INC_IVERSION(dir); + dir->i_mtime = dir->i_atime = current_time(dir); + if (IS_DIRSYNC(dir)) + (void) exfat_sync_inode(dir); + else + mark_inode_dirty(dir); + drop_nlink(dir); + + clear_nlink(inode); + inode->i_mtime = inode->i_atime = current_time(inode); + exfat_detach(inode); + dentry->d_time = GET_IVERSION(dir); +out: + __unlock_d_revalidate(dentry); + unlock_super(sb); + return err; +} + +static int __exfat_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + struct inode *old_inode, *new_inode; + struct super_block *sb = old_dir->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + loff_t i_pos; + int err; + + lock_super(sb); + + old_inode = old_dentry->d_inode; + new_inode = new_dentry->d_inode; + + EXFAT_I(old_inode)->fid.size = i_size_read(old_inode); + + /* check the validity of pointer parameters */ + ASSERT(&(EXFAT_I(old_inode)->fid)); + + mutex_lock(&(sbi->s_vlock)); + err = exfat_fscore_rename(old_dir, &(EXFAT_I(old_inode)->fid), + new_dir, new_dentry); + mutex_unlock(&(sbi->s_vlock)); + if (err) + goto out; + + __lock_d_revalidate(old_dentry); + __lock_d_revalidate(new_dentry); + + INC_IVERSION(new_dir); + new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = current_time(new_dir); + if (IS_DIRSYNC(new_dir)) + (void) exfat_sync_inode(new_dir); + else + mark_inode_dirty(new_dir); + + i_pos = exfat_make_i_pos(&(EXFAT_I(old_inode)->fid)); + exfat_detach(old_inode); + exfat_attach(old_inode, i_pos); + if (IS_DIRSYNC(new_dir)) + (void) exfat_sync_inode(old_inode); + else + mark_inode_dirty(old_inode); + + if ((S_ISDIR(old_inode->i_mode)) && (old_dir != new_dir)) { + drop_nlink(old_dir); + if (!new_inode) + inc_nlink(new_dir); + } + + INC_IVERSION(old_dir); + old_dir->i_ctime = old_dir->i_mtime = current_time(old_dir); + if (IS_DIRSYNC(old_dir)) + (void) exfat_sync_inode(old_dir); + else + mark_inode_dirty(old_dir); + + if (new_inode) { + exfat_detach(new_inode); + + /* skip drop_nlink if new_inode already has been dropped */ + if (new_inode->i_nlink) { + drop_nlink(new_inode); + if (S_ISDIR(new_inode->i_mode)) + drop_nlink(new_inode); + } else { + EMSG("%s : abnormal access to an inode dropped\n", + __func__); + WARN_ON(new_inode->i_nlink == 0); + } + new_inode->i_ctime = current_time(new_inode); +#if 0 + (void) exfat_sync_inode(new_inode); +#endif + } + +out: + __unlock_d_revalidate(old_dentry); + __unlock_d_revalidate(new_dentry); + unlock_super(sb); + return err; +} + +static int exfat_cont_expand(struct inode *inode, loff_t size) +{ + struct address_space *mapping = inode->i_mapping; + loff_t start = i_size_read(inode), count = size - i_size_read(inode); + int err, err2; + + err = generic_cont_expand_simple(inode, size); + if (err) + return err; + + inode->i_ctime = inode->i_mtime = current_time(inode); + mark_inode_dirty(inode); + + if (!IS_SYNC(inode)) + return 0; + + err = filemap_fdatawrite_range(mapping, start, start + count - 1); + err2 = sync_mapping_buffers(mapping); + err = (err)?(err):(err2); + err2 = write_inode_now(inode, 1); + err = (err)?(err):(err2); + if (err) + return err; + + return filemap_fdatawait_range(mapping, start, start + count - 1); +} + +static int exfat_allow_set_time(struct exfat_sb_info *sbi, struct inode *inode) +{ + mode_t allow_utime = sbi->options.allow_utime; + + if (!uid_eq(current_fsuid(), inode->i_uid)) { + if (in_group_p(inode->i_gid)) + allow_utime >>= 3; + if (allow_utime & MAY_WRITE) + return 1; + } + + /* use a default check */ + return 0; +} + +static int exfat_sanitize_mode(const struct exfat_sb_info *sbi, + struct inode *inode, umode_t *mode_ptr) +{ + mode_t i_mode, mask, perm; + + i_mode = inode->i_mode; + + if (S_ISREG(i_mode) || S_ISLNK(i_mode)) + mask = sbi->options.fs_fmask; + else + mask = sbi->options.fs_dmask; + + perm = *mode_ptr & ~(S_IFMT | mask); + + /* Of the r and x bits, all (subject to umask) must be present.*/ + if ((perm & (S_IRUGO | S_IXUGO)) != (i_mode & (S_IRUGO | S_IXUGO))) + return -EPERM; + + if (exfat_mode_can_hold_ro(inode)) { + /* Of the w bits, either all (subject to umask) or none must be present. */ + if ((perm & S_IWUGO) && ((perm & S_IWUGO) != (S_IWUGO & ~mask))) + return -EPERM; + } else { + /* If exfat_mode_can_hold_ro(inode) is false, can't change w bits. */ + if ((perm & S_IWUGO) != (S_IWUGO & ~mask)) + return -EPERM; + } + + *mode_ptr &= S_IFMT | perm; + + return 0; +} + +static int exfat_setattr(struct dentry *dentry, struct iattr *attr) +{ + + struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); + struct inode *inode = dentry->d_inode; + unsigned int ia_valid; + int error; + loff_t old_size; + + if ((attr->ia_valid & ATTR_SIZE) + && (attr->ia_size > i_size_read(inode))) { + error = exfat_cont_expand(inode, attr->ia_size); + if (error || attr->ia_valid == ATTR_SIZE) + goto out; + attr->ia_valid &= ~ATTR_SIZE; + } + + /* Check for setting the inode time. */ + ia_valid = attr->ia_valid; + if ((ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) + && exfat_allow_set_time(sbi, inode)) { + attr->ia_valid &= ~(ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET); + } + + error = setattr_prepare(dentry, attr); + attr->ia_valid = ia_valid; + if (error) { + if (sbi->options.quiet) + error = 0; + goto out; + } + + if (((attr->ia_valid & ATTR_UID) && + (!uid_eq(attr->ia_uid, sbi->options.fs_uid))) || + ((attr->ia_valid & ATTR_GID) && + (!gid_eq(attr->ia_gid, sbi->options.fs_gid))) || + ((attr->ia_valid & ATTR_MODE) && + (attr->ia_mode & ~(S_IFREG | S_IFLNK | S_IFDIR | S_IRWXUGO)))) { + error = -EPERM; + } + + if (error) { + if (sbi->options.quiet) + error = 0; + goto out; + } + + /* + * We don't return -EPERM here. Yes, strange, but this is too + * old behavior. + */ + if (attr->ia_valid & ATTR_MODE) { + if (exfat_sanitize_mode(sbi, inode, &attr->ia_mode) < 0) + attr->ia_valid &= ~ATTR_MODE; + } + + EXFAT_I(inode)->fid.size = i_size_read(inode); + + /* patch 1.2.0 : fixed the problem of size mismatch. */ + if (attr->ia_valid & ATTR_SIZE) { + old_size = i_size_read(inode); + + /* TO CHECK evicting directory works correctly */ + MMSG("%s: inode(%p) truncate size (%llu->%llu)\n", __func__, + inode, (u64)old_size, (u64)attr->ia_size); + __exfat_do_truncate(inode, old_size, attr->ia_size); + } + setattr_copy(inode, attr); + mark_inode_dirty(inode); + +out: + return error; +} + +static const struct inode_operations exfat_dir_inode_operations = { + .create = exfat_create, + .lookup = exfat_lookup, + .unlink = exfat_unlink, + .symlink = exfat_symlink, + .mkdir = exfat_mkdir, + .rmdir = exfat_rmdir, + .rename = exfat_rename, + .setattr = exfat_setattr, + .getattr = exfat_getattr, +#ifdef CONFIG_EXFAT_VIRTUAL_XATTR + .listxattr = exfat_listxattr, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + .setxattr = exfat_setxattr, + .getxattr = exfat_getxattr, + .removexattr = exfat_removexattr, +#endif +#endif +}; + +static const struct inode_operations exfat_symlink_inode_operations = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) + .readlink = generic_readlink, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) + .get_link = exfat_follow_link, +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) */ + .follow_link = exfat_follow_link, +#endif +#ifdef CONFIG_EXFAT_VIRTUAL_XATTR + .listxattr = exfat_listxattr, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + .setxattr = exfat_setxattr, + .getxattr = exfat_getxattr, + .removexattr = exfat_removexattr, +#endif +#endif +}; + +static int exfat_file_release(struct inode *inode, struct file *filp) +{ + struct super_block *sb = inode->i_sb; + + /* Moved below code from exfat_write_inode + * TO FIX size-mismatch problem. + */ + /* FIXME : Added bug_on to confirm that there is no size mismatch */ + exfat_debug_bug_on(EXFAT_I(inode)->fid.size != i_size_read(inode)); + EXFAT_I(inode)->fid.size = i_size_read(inode); + __exfat_sync_fs(sb, 0); + return 0; +} + +static const struct file_operations exfat_file_operations = { + .llseek = generic_file_llseek, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) + .read = new_sync_read, + .write = new_sync_write, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0) */ + .read = do_sync_read, + .write = do_sync_write, + .aio_read = generic_file_aio_read, + .aio_write = generic_file_aio_write, +#endif + .mmap = exfat_file_mmap, + .release = exfat_file_release, + .unlocked_ioctl = exfat_generic_ioctl, + .fsync = exfat_file_fsync, + .splice_read = generic_file_splice_read, +}; + +static const struct address_space_operations exfat_aops; + +/* resize the file length */ +static s32 __exfat_truncate(struct inode *inode, u64 old_size, u64 new_size) +{ + s32 err; + struct super_block *sb = inode->i_sb; + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_truncate(inode, old_size, new_size); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +static void exfat_truncate(struct inode *inode, loff_t old_size) +{ + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + unsigned int blocksize = 1 << inode->i_blkbits; + loff_t aligned_size; + int err; + + lock_super(sb); + + if (EXFAT_I(inode)->fid.start_clu == 0) { + /* Stange statement: + * Empty start_clu != ~0 (not allocated) + */ + exfat_fs_error(sb, "tried to truncate zeroed cluster."); + goto out; + } + + err = __exfat_truncate(inode, old_size, i_size_read(inode)); + if (err) + goto out; + + inode->i_ctime = inode->i_mtime = current_time(inode); + if (IS_DIRSYNC(inode)) + (void) exfat_sync_inode(inode); + else + mark_inode_dirty(inode); + + // FIXME: Please check + // inode->i_blocks = ((EXFAT_I(inode)->i_size_ondisk + (fsi->cluster_size - 1)) + inode->i_blocks = ((i_size_read(inode) + (fsi->cluster_size - 1)) & + ~((loff_t)fsi->cluster_size - 1)) >> inode->i_blkbits; +out: + /* + * This protects against truncating a file bigger than it was then + * trying to write into the hole. + * + * comment by sh.hong: + * This seems to mean 'intra page/block' truncate and writing. + * I couldn't find a reason to change the values prior to __exfat_truncate + * Therefore, I switched the order of operations + * so that it's possible to utilize i_size_ondisk in __exfat_truncate + */ + + aligned_size = i_size_read(inode); + if (aligned_size & (blocksize - 1)) { + aligned_size |= (blocksize - 1); + aligned_size++; + } + + if (EXFAT_I(inode)->i_size_ondisk > i_size_read(inode)) + EXFAT_I(inode)->i_size_ondisk = aligned_size; + + if (EXFAT_I(inode)->i_size_aligned > i_size_read(inode)) + EXFAT_I(inode)->i_size_aligned = aligned_size; + + /* After truncation : + * 1) Delayed allocation is OFF + * i_size = i_size_ondisk <= i_size_aligned + * (useless size var.) + * (block-aligned) + * 2) Delayed allocation is ON + * i_size = i_size_ondisk = i_size_aligned + * (will be block-aligned after write) + * or + * i_size_ondisk < i_size <= i_size_aligned (block_aligned) + * (will be block-aligned after write) + */ + + unlock_super(sb); +} + +static const struct inode_operations exfat_file_inode_operations = { + .setattr = exfat_setattr, + .getattr = exfat_getattr, +#ifdef CONFIG_EXFAT_VIRTUAL_XATTR + .listxattr = exfat_listxattr, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) + .setxattr = exfat_setxattr, + .getxattr = exfat_getxattr, + .removexattr = exfat_removexattr, +#endif +#endif +}; + +/* 2-level option flag */ +#define BMAP_NOT_CREATE 0 +#define BMAP_ADD_BLOCK 1 +#define BMAP_ADD_CLUSTER 2 +#define BLOCK_ADDED(bmap_ops) (bmap_ops) +static int exfat_bmap(struct inode *inode, sector_t sector, sector_t *phys, + unsigned long *mapped_blocks, int *create) +{ + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + const unsigned long blocksize = sb->s_blocksize; + const unsigned char blocksize_bits = sb->s_blocksize_bits; + sector_t last_block; + unsigned int cluster, clu_offset, sec_offset; + int err = 0; + + *phys = 0; + *mapped_blocks = 0; + + /* core code should handle EIO */ +#if 0 + if (fsi->prev_eio && BLOCK_ADDED(*create)) + return -EIO; +#endif + + last_block = (i_size_read(inode) + (blocksize - 1)) >> blocksize_bits; + if ((sector >= last_block) && (*create == BMAP_NOT_CREATE)) + return 0; + + /* Is this block already allocated? */ + clu_offset = sector >> fsi->sect_per_clus_bits; /* cluster offset */ + + EXFAT_I(inode)->fid.size = i_size_read(inode); + + if (*create & BMAP_ADD_CLUSTER) + err = exfat_map_clus(inode, clu_offset, &cluster, 1); + else + err = exfat_map_clus(inode, clu_offset, &cluster, ALLOC_NOWHERE); + + if (err) { + if (err != -ENOSPC) + return -EIO; + return err; + } + + if (!IS_CLUS_EOF(cluster)) { + /* sector offset in cluster */ + sec_offset = sector & (fsi->sect_per_clus - 1); + + *phys = CLUS_TO_SECT(fsi, cluster) + sec_offset; + *mapped_blocks = fsi->sect_per_clus - sec_offset; + } +#if 0 + else { + /* Debug purpose (new clu needed) */ + ASSERT((*create & BMAP_ADD_CLUSTER) == 0); + ASSERT(sector >= last_block); + } +#endif + + if (sector < last_block) + *create = BMAP_NOT_CREATE; +#if 0 + else if (sector >= last_block) + *create = non-zero; + + if (iblock <= last mapped-block) + *phys != 0 + *create = BMAP_NOT_CREATE + else if (iblock <= last cluster) + *phys != 0 + *create = non-zero +#endif + return 0; +} + +static int exfat_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct super_block *sb = inode->i_sb; + unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits; + int err = 0; + unsigned long mapped_blocks; + sector_t phys; + loff_t pos; + int bmap_create = create ? BMAP_ADD_CLUSTER : BMAP_NOT_CREATE; + + lock_super(sb); + err = exfat_bmap(inode, iblock, &phys, &mapped_blocks, &bmap_create); + if (err) { + if (err != -ENOSPC) + exfat_fs_error_ratelimit(sb, "%s: failed to bmap " + "(inode:%p iblock:%u, err:%d)", + __func__, inode, (u32)iblock, err); + goto unlock_ret; + } + + if (phys) { + max_blocks = min(mapped_blocks, max_blocks); + + /* Treat newly added block / cluster */ + if (BLOCK_ADDED(bmap_create) || buffer_delay(bh_result)) { + + /* Update i_size_ondisk */ + pos = (iblock + 1) << sb->s_blocksize_bits; + if (EXFAT_I(inode)->i_size_ondisk < pos) { + /* Debug purpose */ + if ((pos - EXFAT_I(inode)->i_size_ondisk) > bh_result->b_size) { + /* This never happens without DA */ + MMSG("Jumping get_block\n"); + } + + EXFAT_I(inode)->i_size_ondisk = pos; + } + + if (BLOCK_ADDED(bmap_create)) { + /* Old way (w/o DA) + * create == 1 only if iblock > i_size + * (in block unit) + */ + + /* 20130723 CHECK + * If happened concurrently with truncation, + * buffer_delay() can be left on whilst + * i_size < (position of i_block). + * + * It's not a big deal as we're re-writing + * previously allocated blocks, but in such cases, + * it has to be an area with i_size_aligned expanded + * beforehand. + */ + + /* FOR GRACEFUL ERROR HANDLING */ + if (buffer_delay(bh_result) && + (pos > EXFAT_I(inode)->i_size_aligned)) { + exfat_fs_error(sb, "requested for bmap " + "out of range(pos:(%llu)>i_size_aligned(%llu)\n", + pos, EXFAT_I(inode)->i_size_aligned); + exfat_debug_bug_on(1); + err = -EIO; + goto unlock_ret; + } + set_buffer_new(bh_result); + + /* + * adjust i_size_aligned if i_size_ondisk is + * bigger than it. (i.e. non-DA) + */ + if (EXFAT_I(inode)->i_size_ondisk > + EXFAT_I(inode)->i_size_aligned) { + EXFAT_I(inode)->i_size_aligned = + EXFAT_I(inode)->i_size_ondisk; + } + } + + if (buffer_delay(bh_result)) + clear_buffer_delay(bh_result); + +#if 0 + /* Debug purpose */ + if (EXFAT_I(inode)->i_size_ondisk > + EXFAT_I(inode)->i_size_aligned) { + /* Only after truncate + * and the two size variables should indicate + * same i_block + */ + unsigned int blocksize = 1 << inode->i_blkbits; + BUG_ON(EXFAT_I(inode)->i_size_ondisk - + EXFAT_I(inode)->i_size_aligned >= blocksize); + } +#endif + } + map_bh(bh_result, sb, phys); + } + + bh_result->b_size = max_blocks << sb->s_blocksize_bits; +unlock_ret: + unlock_super(sb); + return err; +} + +static int exfat_readpage(struct file *file, struct page *page) +{ + int ret; + + ret = mpage_readpage(page, exfat_get_block); + return ret; +} + +static int exfat_readpages(struct file *file, struct address_space *mapping, + struct list_head *pages, unsigned int nr_pages) +{ + int ret; + + ret = mpage_readpages(mapping, pages, nr_pages, exfat_get_block); + return ret; +} + +static inline void exfat_submit_fullpage_bio(struct block_device *bdev, + sector_t sector, unsigned int length, struct page *page) +{ + /* Single page bio submit */ + struct bio *bio; + + BUG_ON((length > PAGE_SIZE) || (length == 0)); + + /* + * If __GFP_WAIT is set, then bio_alloc will always be able to allocate + * a bio. This is due to the mempool guarantees. To make this work, callers + * must never allocate more than 1 bio at a time from this pool. + * + * #define GFP_NOIO (__GFP_WAIT) + */ + bio = bio_alloc(GFP_NOIO, 1); + + bio_set_dev(bio, bdev); + bio->bi_vcnt = 1; + bio->bi_io_vec[0].bv_page = page; /* Inline vec */ + bio->bi_io_vec[0].bv_len = length; /* PAGE_SIZE */ + bio->bi_io_vec[0].bv_offset = 0; + __exfat_set_bio_iterate(bio, sector, length, 0, 0); + + bio->bi_end_io = exfat_writepage_end_io; + __exfat_submit_bio_write(bio); +} + +static int exfat_writepage(struct page *page, struct writeback_control *wbc) +{ + struct inode * const inode = page->mapping->host; + struct super_block *sb = inode->i_sb; + loff_t i_size = i_size_read(inode); + const pgoff_t end_index = i_size >> PAGE_SHIFT; + const unsigned int blocks_per_page = PAGE_SIZE >> inode->i_blkbits; + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + struct buffer_head *bh, *head; + sector_t block, block_0, last_phys; + int ret; + unsigned int nr_blocks_towrite = blocks_per_page; + + /* Don't distinguish 0-filled/clean block. + * Just write back the whole page + */ + if (fsi->cluster_size < PAGE_SIZE) + goto confused; + + if (!PageUptodate(page)) { + MMSG("%s: Not up-to-date page -> block_write_full_page\n", + __func__); + goto confused; + } + + if (page->index >= end_index) { + /* last page or outside i_size */ + unsigned int offset = i_size & (PAGE_SIZE-1); + + /* If a truncation is in progress */ + if (page->index > end_index || !offset) + goto confused; + + /* 0-fill after i_size */ + zero_user_segment(page, offset, PAGE_SIZE); + } + + if (!page_has_buffers(page)) { + MMSG("WP: No buffers -> block_write_full_page\n"); + goto confused; + } + + block = (sector_t)page->index << (PAGE_SHIFT - inode->i_blkbits); + block_0 = block; /* first block */ + head = page_buffers(page); + bh = head; + + last_phys = 0; + do { + BUG_ON(buffer_locked(bh)); + + if (!buffer_dirty(bh) || !buffer_uptodate(bh)) { + if (nr_blocks_towrite == blocks_per_page) + nr_blocks_towrite = (unsigned int) (block - block_0); + + BUG_ON(nr_blocks_towrite >= blocks_per_page); + + // !uptodate but dirty?? + if (buffer_dirty(bh)) + goto confused; + + // Nothing to writeback in this block + bh = bh->b_this_page; + block++; + continue; + } + + if (nr_blocks_towrite != blocks_per_page) + // Dirty -> Non-dirty -> Dirty again case + goto confused; + + /* Map if needed */ + if (!buffer_mapped(bh) || buffer_delay(bh)) { + BUG_ON(bh->b_size != (1 << (inode->i_blkbits))); + ret = exfat_get_block(inode, block, bh, 1); + if (ret) + goto confused; + + if (buffer_new(bh)) { + clear_buffer_new(bh); + __exfat_clean_bdev_aliases(bh->b_bdev, bh->b_blocknr); + } + } + + /* continuity check */ + if (((last_phys + 1) != bh->b_blocknr) && (last_phys != 0)) { + DMSG("Non-contiguous block mapping in single page"); + goto confused; + } + + last_phys = bh->b_blocknr; + bh = bh->b_this_page; + block++; + } while (bh != head); + + if (nr_blocks_towrite == 0) { + DMSG("Page dirty but no dirty bh? alloc_208\n"); + goto confused; + } + + + /* Write-back */ + do { + clear_buffer_dirty(bh); + bh = bh->b_this_page; + } while (bh != head); + + BUG_ON(PageWriteback(page)); + set_page_writeback(page); + + // Trace # of pages queued (Approx.) + atomic_inc(&EXFAT_SB(sb)->stat_n_pages_queued); + + exfat_submit_fullpage_bio(head->b_bdev, + head->b_blocknr << (sb->s_blocksize_bits - SECTOR_SIZE_BITS), + nr_blocks_towrite << inode->i_blkbits, + page); + + unlock_page(page); + + return 0; + +confused: + ret = block_write_full_page(page, exfat_get_block, wbc); + return ret; +} + +static int exfat_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + MMSG("%s(inode:%p) with nr_to_write = 0x%08lx " + "(ku %d, bg %d, tag %d, rc %d )\n", + __func__, mapping->host, wbc->nr_to_write, + wbc->for_kupdate, wbc->for_background, wbc->tagged_writepages, + wbc->for_reclaim); + + ASSERT(mapping->a_ops == &exfat_aops); + + return mpage_writepages(mapping, wbc, exfat_get_block); +} + +static void exfat_write_failed(struct address_space *mapping, loff_t to) +{ + struct inode *inode = mapping->host; + + if (to > i_size_read(inode)) { + __exfat_truncate_pagecache(inode, to, i_size_read(inode)); + exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); + } +} + +static int exfat_check_writable(struct super_block *sb) +{ + if (exfat_fscore_check_bdi_valid(sb)) + return -EIO; + + if (EXFAT_IS_SB_RDONLY(sb)) + return -EROFS; + + return 0; +} + +static int __exfat_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, + unsigned int flags, struct page **pagep, + void **fsdata, get_block_t *get_block, + loff_t *bytes, const char *fname) +{ + struct super_block *sb = mapping->host->i_sb; + int ret; + + ret = exfat_check_writable(sb); + if (unlikely(ret < 0)) + return ret; + + *pagep = NULL; + ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, + get_block, bytes); + + if (ret < 0) + exfat_write_failed(mapping, pos+len); + + return ret; +} + +static int exfat_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int flags, + struct page **pagep, void **fsdata) +{ + return __exfat_write_begin(file, mapping, pos, len, flags, + pagep, fsdata, exfat_get_block, + &EXFAT_I(mapping->host)->i_size_ondisk, + __func__); +} + +static int exfat_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, unsigned int copied, + struct page *pagep, void *fsdata) +{ + struct inode *inode = mapping->host; + FILE_ID_T *fid = &(EXFAT_I(inode)->fid); + int err; + + err = generic_write_end(file, mapping, pos, len, copied, pagep, fsdata); + + /* FOR GRACEFUL ERROR HANDLING */ + if (EXFAT_I(inode)->i_size_aligned < i_size_read(inode)) { + exfat_fs_error(inode->i_sb, "invalid size(size(%llu) " + "> aligned(%llu)\n", i_size_read(inode), + EXFAT_I(inode)->i_size_aligned); + exfat_debug_bug_on(1); + } + + if (err < len) + exfat_write_failed(mapping, pos+len); + + if (!(err < 0) && !(fid->attr & ATTR_ARCHIVE)) { + inode->i_mtime = inode->i_ctime = current_time(inode); + fid->attr |= ATTR_ARCHIVE; + mark_inode_dirty(inode); + } + + return err; +} + +static inline ssize_t __exfat_direct_IO(int rw, struct kiocb *iocb, + struct inode *inode, void *iov_u, loff_t offset, + loff_t count, unsigned long nr_segs) +{ + struct address_space *mapping = inode->i_mapping; + loff_t size = offset + count; + ssize_t ret; + + if (rw == WRITE) { + /* + * FIXME: blockdev_direct_IO() doesn't use ->write_begin(), + * so we need to update the ->i_size_aligned to block boundary. + * + * But we must fill the remaining area or hole by nul for + * updating ->i_size_aligned + * + * Return 0, and fallback to normal buffered write. + */ + if (EXFAT_I(inode)->i_size_aligned < size) + return 0; + } + + /* + * exFAT need to use the DIO_LOCKING for avoiding the race + * condition of exfat_get_block() and ->truncate(). + */ + ret = __exfat_blkdev_direct_IO(rw, iocb, inode, iov_u, offset, nr_segs); + if (ret < 0 && (rw & WRITE)) + exfat_write_failed(mapping, size); + + return ret; +} + +static const struct address_space_operations exfat_aops = { + .readpage = exfat_readpage, + .readpages = exfat_readpages, + .writepage = exfat_writepage, + .writepages = exfat_writepages, + .write_begin = exfat_write_begin, + .write_end = exfat_write_end, + .direct_IO = exfat_direct_IO, + .bmap = exfat_aop_bmap +}; + +static inline unsigned long exfat_hash(loff_t i_pos) +{ + return hash_32(i_pos, EXFAT_HASH_BITS); +} + +static void exfat_attach(struct inode *inode, loff_t i_pos) +{ + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + struct hlist_head *head = sbi->inode_hashtable + exfat_hash(i_pos); + + spin_lock(&sbi->inode_hash_lock); + EXFAT_I(inode)->i_pos = i_pos; + hlist_add_head(&EXFAT_I(inode)->i_hash_fat, head); + spin_unlock(&sbi->inode_hash_lock); +} + +static void exfat_detach(struct inode *inode) +{ + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + + spin_lock(&sbi->inode_hash_lock); + hlist_del_init(&EXFAT_I(inode)->i_hash_fat); + EXFAT_I(inode)->i_pos = 0; + spin_unlock(&sbi->inode_hash_lock); +} + + +/* doesn't deal with root inode */ +static int exfat_fill_inode(struct inode *inode, const FILE_ID_T *fid) +{ + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T info; + u64 size = fid->size; + + memcpy(&(EXFAT_I(inode)->fid), fid, sizeof(FILE_ID_T)); + + EXFAT_I(inode)->i_pos = 0; + EXFAT_I(inode)->target = NULL; + inode->i_uid = sbi->options.fs_uid; + inode->i_gid = sbi->options.fs_gid; + INC_IVERSION(inode); + inode->i_generation = get_seconds(); + + if (exfat_read_inode(inode, &info) < 0) { + MMSG("%s: failed to read stat!\n", __func__); + return -EIO; + } + + if (info.Attr & ATTR_SUBDIR) { /* directory */ + inode->i_generation &= ~1; + inode->i_mode = exfat_make_mode(sbi, info.Attr, S_IRWXUGO); + inode->i_op = &exfat_dir_inode_operations; + inode->i_fop = &exfat_dir_operations; + + set_nlink(inode, info.NumSubdirs); + } else if (info.Attr & ATTR_SYMLINK) { /* symbolic link */ + inode->i_op = &exfat_symlink_inode_operations; + inode->i_generation |= 1; + inode->i_mode = exfat_make_mode(sbi, info.Attr, S_IRWXUGO); + } else { /* regular file */ + inode->i_generation |= 1; + inode->i_mode = exfat_make_mode(sbi, info.Attr, S_IRWXUGO); + inode->i_op = &exfat_file_inode_operations; + inode->i_fop = &exfat_file_operations; + inode->i_mapping->a_ops = &exfat_aops; + + inode->i_mapping->nrpages = 0; + + } + + /* + * Use fid->size instead of info.Size + * because info.Size means the value saved on disk + */ + i_size_write(inode, size); + + /* ondisk and aligned size should be aligned with block size */ + if (size & (inode->i_sb->s_blocksize - 1)) { + size |= (inode->i_sb->s_blocksize - 1); + size++; + } + + EXFAT_I(inode)->i_size_aligned = size; + EXFAT_I(inode)->i_size_ondisk = size; + + exfat_save_attr(inode, info.Attr); + + inode->i_blocks = ((i_size_read(inode) + (fsi->cluster_size - 1)) + & ~((loff_t)fsi->cluster_size - 1)) >> inode->i_blkbits; + + exfat_time_fat2unix(sbi, &inode->i_mtime, &info.ModifyTimestamp); + exfat_time_fat2unix(sbi, &inode->i_ctime, &info.CreateTimestamp); + exfat_time_fat2unix(sbi, &inode->i_atime, &info.AccessTimestamp); + + return 0; +} + +static struct inode *exfat_build_inode(struct super_block *sb, + const FILE_ID_T *fid, loff_t i_pos) +{ + struct inode *inode; + int err; + + inode = exfat_iget(sb, i_pos); + if (inode) + goto out; + inode = new_inode(sb); + if (!inode) { + inode = ERR_PTR(-ENOMEM); + goto out; + } + inode->i_ino = iunique(sb, EXFAT_ROOT_INO); + SET_IVERSION(inode, 1); + err = exfat_fill_inode(inode, fid); + if (err) { + iput(inode); + inode = ERR_PTR(err); + goto out; + } + exfat_attach(inode, i_pos); + insert_inode_hash(inode); +out: + return inode; +} + +static struct inode *exfat_alloc_inode(struct super_block *sb) +{ + struct exfat_inode_info *ei; + + ei = kmem_cache_alloc(exfat_inode_cachep, GFP_NOFS); + if (!ei) + return NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + init_rwsem(&ei->truncate_lock); +#endif + return &ei->vfs_inode; +} + +static void exfat_destroy_inode(struct inode *inode) +{ + kfree(EXFAT_I(inode)->target); + EXFAT_I(inode)->target = NULL; + + kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode)); +} + +static int __exfat_write_inode(struct inode *inode, int sync) +{ + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + DIR_ENTRY_T info; + s32 err; + + if (inode->i_ino == EXFAT_ROOT_INO) + return 0; + + info.Attr = exfat_make_attr(inode); + info.Size = i_size_read(inode); + + exfat_time_unix2fat(sbi, &inode->i_mtime, &info.ModifyTimestamp); + exfat_time_unix2fat(sbi, &inode->i_ctime, &info.CreateTimestamp); + exfat_time_unix2fat(sbi, &inode->i_atime, &info.AccessTimestamp); + + /* FIXME : Do we need handling error? */ + mutex_lock(&(sbi->s_vlock)); + err = exfat_fscore_write_inode(inode, &info, sync); + mutex_unlock(&(sbi->s_vlock)); + + return err; +} + +static int exfat_sync_inode(struct inode *inode) +{ + return __exfat_write_inode(inode, 1); +} + +static int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) +{ + return __exfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL); +} + +static void exfat_evict_inode(struct inode *inode) +{ + truncate_inode_pages(&inode->i_data, 0); + + if (!inode->i_nlink) { + loff_t old_size = i_size_read(inode); + + i_size_write(inode, 0); + + EXFAT_I(inode)->fid.size = old_size; + + /* TO CHECK evicting directory works correctly */ + MMSG("%s: inode(%p) evict %s (size(%llu) to zero)\n", + __func__, inode, + S_ISDIR(inode->i_mode) ? "directory" : "file", + (u64)old_size); + __exfat_truncate(inode, old_size, 0); + } + + invalidate_inode_buffers(inode); + clear_inode(inode); + exfat_extent_cache_inval_inode(inode); + exfat_detach(inode); + + /* after end of this function, caller will remove inode hash */ + /* remove_inode_hash(inode); */ +} + + + +static void exfat_put_super(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + int err; + + exfat_log_msg(sb, KERN_INFO, "trying to unmount..."); + + __cancel_delayed_work_sync(sbi); + + if (__is_sb_dirty(sb)) + exfat_write_super(sb); + + err = exfat_umount(sb); + + if (sbi->nls_disk) { + unload_nls(sbi->nls_disk); + sbi->nls_disk = NULL; + sbi->options.codepage = exfat_default_codepage; + } + if (sbi->nls_io) { + unload_nls(sbi->nls_io); + sbi->nls_io = NULL; + } + if (sbi->options.iocharset != exfat_default_iocharset) { + kfree(sbi->options.iocharset); + sbi->options.iocharset = exfat_default_iocharset; + } + + sb->s_fs_info = NULL; + + kobject_del(&sbi->sb_kobj); + kobject_put(&sbi->sb_kobj); + if (!sbi->use_vmalloc) + kfree(sbi); + else + vfree(sbi); + + exfat_log_msg(sb, KERN_INFO, "unmounted successfully! %s", + err ? "(with previous I/O errors)" : ""); +} + +static inline void __flush_delayed_meta(struct super_block *sb, s32 sync) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + if (sbi->options.delayed_meta) + exfat_cache_flush(sb, sync); +} + +static void exfat_write_super(struct super_block *sb) +{ + int time = 0; + + lock_super(sb); + + __set_sb_clean(sb); + + /* flush delayed FAT/DIR dirty */ + __flush_delayed_meta(sb, 0); + + if (!EXFAT_IS_SB_RDONLY(sb)) + __exfat_sync_fs(sb, 0); + + unlock_super(sb); + + time = jiffies; + + /* Issuing bdev requests is needed + * to guarantee DIR updates in time + * whether w/ or w/o delayed DIR dirty feature. + * (otherwise DIR updates could be delayed for 5 + 5 secs at max.) + */ + sync_blockdev(sb->s_bdev); + + MMSG("BD: exfat_write_super (bdev_sync for %ld ms)\n", + (jiffies - time) * 1000 / HZ); +} + +/* synchronize a file system volume */ +static s32 __exfat_sync_fs(struct super_block *sb, s32 do_sync) +{ + s32 err; + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_sync_fs(sb, do_sync); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + return err; +} + +static int exfat_sync_fs(struct super_block *sb, int wait) +{ + int err = 0; + + /* If there are some dirty buffers in the bdev inode */ + if (__is_sb_dirty(sb)) { + lock_super(sb); + __set_sb_clean(sb); + + err = __exfat_sync_fs(sb, 1); + + unlock_super(sb); + } + + return err; +} + +static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + /* + * patch 1.2.2 : + * fixed the slow-call problem because of volume-lock contention. + */ + struct super_block *sb = dentry->d_sb; + u64 id = huge_encode_dev(sb->s_bdev->bd_dev); + FS_INFO_T *fsi = &(EXFAT_SB(sb)->fsi); + VOL_INFO_T info; + + /* exfat_statfs will try to get a volume lock if needed */ + if (fsi->used_clusters == UINT_MAX) { + s32 err; + + mutex_lock(&(EXFAT_SB(sb)->s_vlock)); + err = exfat_fscore_statfs(sb, &info); + mutex_unlock(&(EXFAT_SB(sb)->s_vlock)); + + return -EIO; + } + + info.ClusterSize = fsi->cluster_size; + info.NumClusters = fsi->num_clusters - 2; /* clu 0 & 1 */ + info.UsedClusters = fsi->used_clusters + fsi->reserved_clusters; + info.FreeClusters = info.NumClusters - info.UsedClusters; + + if (fsi->prev_eio) + exfat_msg(sb, KERN_INFO, "called statfs with previous" + " I/O error(0x%02X).", fsi->prev_eio); + + buf->f_type = sb->s_magic; + buf->f_bsize = info.ClusterSize; + buf->f_blocks = info.NumClusters; + buf->f_bfree = info.FreeClusters; + buf->f_bavail = info.FreeClusters; + buf->f_fsid.val[0] = (u32)id; + buf->f_fsid.val[1] = (u32)(id >> 32); + buf->f_namelen = 260; + + return 0; +} + +static int exfat_remount(struct super_block *sb, int *flags, char *data) +{ + unsigned long prev_sb_flags; + char *orig_data = kstrdup(data, GFP_KERNEL); + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + *flags |= MS_NODIRATIME; +#else + *flags |= SB_NODIRATIME; +#endif + + prev_sb_flags = sb->s_flags; + + exfat_remount_syncfs(sb); + + exfat_set_vol_flags(sb, VOL_CLEAN, 1); + + exfat_log_msg(sb, KERN_INFO, "re-mounted(%s->%s), eio=0x%x, Opts: %s", +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + (prev_sb_flags & MS_RDONLY) ? "ro" : "rw", + (*flags & MS_RDONLY) ? "ro" : "rw", +#else + (prev_sb_flags & SB_RDONLY) ? "ro" : "rw", + (*flags & SB_RDONLY) ? "ro" : "rw", +#endif + fsi->prev_eio, orig_data); + kfree(orig_data); + return 0; +} + +static int __exfat_show_options(struct seq_file *m, struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct exfat_mount_options *opts = &sbi->options; + FS_INFO_T *fsi = &(sbi->fsi); + + /* Show partition info */ + if (fsi->prev_eio) + seq_printf(m, ",eio=0x%x", fsi->prev_eio); + if (!uid_eq(opts->fs_uid, GLOBAL_ROOT_UID)) + seq_printf(m, ",uid=%u", + from_kuid_munged(&init_user_ns, opts->fs_uid)); + if (!gid_eq(opts->fs_gid, GLOBAL_ROOT_GID)) + seq_printf(m, ",gid=%u", + from_kgid_munged(&init_user_ns, opts->fs_gid)); + seq_printf(m, ",fmask=%04o", opts->fs_fmask); + seq_printf(m, ",dmask=%04o", opts->fs_dmask); + if (opts->allow_utime) + seq_printf(m, ",allow_utime=%04o", opts->allow_utime); + if (sbi->nls_disk) + seq_printf(m, ",codepage=%s", sbi->nls_disk->charset); + if (sbi->nls_io) + seq_printf(m, ",iocharset=%s", sbi->nls_io->charset); + if (opts->quiet) + seq_puts(m, ",quiet"); + if (opts->utf8) + seq_puts(m, ",utf8"); + seq_printf(m, ",namecase=%u", opts->casesensitive); + if (opts->tz_utc) + seq_puts(m, ",tz=UTC"); + seq_printf(m, ",symlink=%u", opts->symlink); + seq_printf(m, ",bps=%ld", sb->s_blocksize); + if (opts->errors == EXFAT_ERRORS_CONT) + seq_puts(m, ",errors=continue"); + else if (opts->errors == EXFAT_ERRORS_PANIC) + seq_puts(m, ",errors=panic"); + else + seq_puts(m, ",errors=remount-ro"); + if (opts->discard) + seq_puts(m, ",discard"); + if (opts->delayed_meta) + seq_puts(m, ",delayed_meta"); + + return 0; +} + +static const struct super_operations exfat_sops = { + .alloc_inode = exfat_alloc_inode, + .destroy_inode = exfat_destroy_inode, + .write_inode = exfat_write_inode, + .evict_inode = exfat_evict_inode, + .put_super = exfat_put_super, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) + .write_super = exfat_write_super, +#endif + .sync_fs = exfat_sync_fs, + .statfs = exfat_statfs, + .remount_fs = exfat_remount, + .show_options = exfat_show_options, +}; + +#define EXFAT_ATTR(name, mode, show, store) \ +static struct exfat_attr exfat_attr_##name = __ATTR(name, mode, show, store) + +struct exfat_attr { + struct attribute attr; + ssize_t (*show)(struct exfat_sb_info *, char *); + ssize_t (*store)(struct exfat_sb_info *, const char *, size_t); +}; + +static ssize_t exfat_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct exfat_sb_info *sbi = container_of(kobj, struct exfat_sb_info, sb_kobj); + struct exfat_attr *a = container_of(attr, struct exfat_attr, attr); + + return a->show ? a->show(sbi, buf) : 0; +} + +static ssize_t exfat_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t len) +{ + struct exfat_sb_info *sbi = container_of(kobj, struct exfat_sb_info, sb_kobj); + struct exfat_attr *a = container_of(attr, struct exfat_attr, attr); + + return a->store ? a->store(sbi, buf, len) : len; +} + +static const struct sysfs_ops exfat_attr_ops = { + .show = exfat_attr_show, + .store = exfat_attr_store, +}; + +static ssize_t eio_show(struct exfat_sb_info *sbi, char *buf) +{ + FS_INFO_T *fsi = &(sbi->fsi); + + return snprintf(buf, PAGE_SIZE, "0x%x\n", fsi->prev_eio); +} +EXFAT_ATTR(eio, 0444, eio_show, NULL); + +static ssize_t fratio_show(struct exfat_sb_info *sbi, char *buf) +{ + unsigned int n_total_au = 0; + unsigned int n_clean_au = 0; + unsigned int n_full_au = 0; + unsigned int n_dirty_au = 0; + unsigned int fr = 0; + + n_total_au = exfat_fscore_get_au_stat(sbi->host_sb, VOL_AU_STAT_TOTAL); + n_clean_au = exfat_fscore_get_au_stat(sbi->host_sb, VOL_AU_STAT_CLEAN); + n_full_au = exfat_fscore_get_au_stat(sbi->host_sb, VOL_AU_STAT_FULL); + n_dirty_au = n_total_au - (n_full_au + n_clean_au); + + if (!n_dirty_au) + fr = 0; + else if (!n_clean_au) + fr = 100; + else + fr = (n_dirty_au * 100) / (n_clean_au + n_dirty_au); + + return snprintf(buf, PAGE_SIZE, "%u\n", fr); +} +EXFAT_ATTR(fratio, 0444, fratio_show, NULL); + +static ssize_t totalau_show(struct exfat_sb_info *sbi, char *buf) +{ + unsigned int n_au = 0; + + n_au = exfat_fscore_get_au_stat(sbi->host_sb, VOL_AU_STAT_TOTAL); + return snprintf(buf, PAGE_SIZE, "%u\n", n_au); +} +EXFAT_ATTR(totalau, 0444, totalau_show, NULL); + +static ssize_t cleanau_show(struct exfat_sb_info *sbi, char *buf) +{ + unsigned int n_clean_au = 0; + + n_clean_au = exfat_fscore_get_au_stat(sbi->host_sb, VOL_AU_STAT_CLEAN); + return snprintf(buf, PAGE_SIZE, "%u\n", n_clean_au); +} +EXFAT_ATTR(cleanau, 0444, cleanau_show, NULL); + +static ssize_t fullau_show(struct exfat_sb_info *sbi, char *buf) +{ + unsigned int n_full_au = 0; + + n_full_au = exfat_fscore_get_au_stat(sbi->host_sb, VOL_AU_STAT_FULL); + return snprintf(buf, PAGE_SIZE, "%u\n", n_full_au); +} +EXFAT_ATTR(fullau, 0444, fullau_show, NULL); + +static struct attribute *exfat_attrs[] = { + &exfat_attr_eio.attr, + &exfat_attr_fratio.attr, + &exfat_attr_totalau.attr, + &exfat_attr_cleanau.attr, + &exfat_attr_fullau.attr, + NULL, +}; + +static struct kobj_type exfat_ktype = { + .default_attrs = exfat_attrs, + .sysfs_ops = &exfat_attr_ops, +}; + +static ssize_t version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buff) +{ + return snprintf(buff, PAGE_SIZE, "FS Version %s\n", EXFAT_VERSION); +} + +static struct kobj_attribute version_attr = __ATTR_RO(version); + +static struct attribute *attributes[] = { + &version_attr.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attributes, +}; + +enum { + Opt_uid, + Opt_gid, + Opt_umask, + Opt_dmask, + Opt_fmask, + Opt_allow_utime, + Opt_codepage, + Opt_charset, + Opt_quiet, + Opt_utf8, + Opt_namecase, + Opt_tz_utc, + Opt_symlink, + Opt_err_cont, + Opt_err_panic, + Opt_err_ro, + Opt_err, + Opt_discard, + Opt_delayed_meta, + Opt_nodelayed_meta, +}; + +static const match_table_t exfat_tokens = { + {Opt_uid, "uid=%u"}, + {Opt_gid, "gid=%u"}, + {Opt_umask, "umask=%o"}, + {Opt_dmask, "dmask=%o"}, + {Opt_fmask, "fmask=%o"}, + {Opt_allow_utime, "allow_utime=%o"}, + {Opt_codepage, "codepage=%u"}, + {Opt_charset, "iocharset=%s"}, + {Opt_quiet, "quiet"}, + {Opt_utf8, "utf8"}, + {Opt_namecase, "namecase=%u"}, + {Opt_tz_utc, "tz=UTC"}, + {Opt_symlink, "symlink=%u"}, + {Opt_err_cont, "errors=continue"}, + {Opt_err_panic, "errors=panic"}, + {Opt_err_ro, "errors=remount-ro"}, + {Opt_discard, "discard"}, + {Opt_delayed_meta, "delayed_meta"}, + {Opt_nodelayed_meta, "nodelayed_meta"}, + {Opt_err, NULL} +}; + +static int parse_options(struct super_block *sb, char *options, int silent, + struct exfat_mount_options *opts) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + int option; + char *tmpstr; + + opts->fs_uid = current_uid(); + opts->fs_gid = current_gid(); + opts->fs_fmask = opts->fs_dmask = current->fs->umask; + opts->allow_utime = U16_MAX; + opts->codepage = exfat_default_codepage; + opts->iocharset = exfat_default_iocharset; + opts->quiet = 0; + opts->casesensitive = 0; + opts->utf8 = 0; + opts->tz_utc = 0; + opts->symlink = 0; + opts->errors = EXFAT_ERRORS_RO; + opts->discard = 0; + opts->delayed_meta = 1; + + if (!options) + goto out; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + + if (!*p) + continue; + token = match_token(p, exfat_tokens, args); + switch (token) { + case Opt_uid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_uid = make_kuid(current_user_ns(), option); + break; + case Opt_gid: + if (match_int(&args[0], &option)) + return 0; + opts->fs_gid = make_kgid(current_user_ns(), option); + break; + case Opt_umask: + case Opt_dmask: + case Opt_fmask: + if (match_octal(&args[0], &option)) + return 0; + if (token != Opt_dmask) + opts->fs_fmask = option; + if (token != Opt_fmask) + opts->fs_dmask = option; + break; + case Opt_allow_utime: + if (match_octal(&args[0], &option)) + return 0; + opts->allow_utime = option & (S_IWGRP | S_IWOTH); + break; + case Opt_codepage: + if (match_int(&args[0], &option)) + return 0; + opts->codepage = option; + break; + case Opt_charset: + if (opts->iocharset != exfat_default_iocharset) + kfree(opts->iocharset); + tmpstr = match_strdup(&args[0]); + if (!tmpstr) + return -ENOMEM; + opts->iocharset = tmpstr; + break; + case Opt_quiet: + opts->quiet = 1; + break; + case Opt_namecase: + if (match_int(&args[0], &option)) + return 0; + opts->casesensitive = (option > 0) ? 1:0; + break; + case Opt_utf8: + opts->utf8 = 1; + break; + case Opt_tz_utc: + opts->tz_utc = 1; + break; + case Opt_symlink: + if (match_int(&args[0], &option)) + return 0; + opts->symlink = option > 0 ? 1 : 0; + break; + case Opt_err_cont: + opts->errors = EXFAT_ERRORS_CONT; + break; + case Opt_err_panic: + opts->errors = EXFAT_ERRORS_PANIC; + break; + case Opt_err_ro: + opts->errors = EXFAT_ERRORS_RO; + break; + case Opt_discard: + opts->discard = 1; + break; + case Opt_nodelayed_meta: + opts->delayed_meta = 0; + break; + default: + if (!silent) { + exfat_msg(sb, KERN_ERR, + "unrecognized mount option \"%s\" " + "or missing value", p); + } + return -EINVAL; + } + } + +out: + if (opts->allow_utime == U16_MAX) + opts->allow_utime = ~opts->fs_dmask & (S_IWGRP | S_IWOTH); + + if (opts->utf8 && strcmp(opts->iocharset, exfat_iocharset_with_utf8)) { + exfat_msg(sb, KERN_WARNING, + "utf8 enabled, \"iocharset=%s\" is recommended", + exfat_iocharset_with_utf8); + } + + if (opts->discard) { + struct request_queue *q = bdev_get_queue(sb->s_bdev); + + if (!blk_queue_discard(q)) + exfat_msg(sb, KERN_WARNING, + "mounting with \"discard\" option, but " + "the device does not support discard"); + opts->discard = 0; + } + + return 0; +} + +static void exfat_hash_init(struct super_block *sb) +{ + struct exfat_sb_info *sbi = EXFAT_SB(sb); + int i; + + spin_lock_init(&sbi->inode_hash_lock); + for (i = 0; i < EXFAT_HASH_SIZE; i++) + INIT_HLIST_HEAD(&sbi->inode_hashtable[i]); +} + +static int exfat_read_root(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + FS_INFO_T *fsi = &(sbi->fsi); + DIR_ENTRY_T info; + + EXFAT_I(inode)->fid.dir.dir = fsi->root_dir; + EXFAT_I(inode)->fid.dir.flags = 0x01; + EXFAT_I(inode)->fid.entry = -1; + EXFAT_I(inode)->fid.start_clu = fsi->root_dir; + EXFAT_I(inode)->fid.flags = 0x01; + EXFAT_I(inode)->fid.type = TYPE_DIR; + EXFAT_I(inode)->fid.version = 0; + EXFAT_I(inode)->fid.rwoffset = 0; + EXFAT_I(inode)->fid.hint_bmap.off = CLUS_EOF; + EXFAT_I(inode)->fid.hint_stat.eidx = 0; + EXFAT_I(inode)->fid.hint_stat.clu = fsi->root_dir; + EXFAT_I(inode)->fid.hint_femp.eidx = -1; + + EXFAT_I(inode)->target = NULL; + + if (exfat_read_inode(inode, &info) < 0) + return -EIO; + + inode->i_uid = sbi->options.fs_uid; + inode->i_gid = sbi->options.fs_gid; + INC_IVERSION(inode); + inode->i_generation = 0; + inode->i_mode = exfat_make_mode(sbi, ATTR_SUBDIR, S_IRWXUGO); + inode->i_op = &exfat_dir_inode_operations; + inode->i_fop = &exfat_dir_operations; + + i_size_write(inode, info.Size); + EXFAT_I(inode)->fid.size = info.Size; + inode->i_blocks = ((i_size_read(inode) + (fsi->cluster_size - 1)) + & ~((loff_t)fsi->cluster_size - 1)) >> inode->i_blkbits; + EXFAT_I(inode)->i_pos = ((loff_t) fsi->root_dir << 32) | 0xffffffff; + EXFAT_I(inode)->i_size_aligned = i_size_read(inode); + EXFAT_I(inode)->i_size_ondisk = i_size_read(inode); + + exfat_save_attr(inode, ATTR_SUBDIR); + inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); + set_nlink(inode, info.NumSubdirs + 2); + return 0; +} + +static void setup_dops(struct super_block *sb) +{ + if (EXFAT_SB(sb)->options.casesensitive == 0) + sb->s_d_op = &exfat_ci_dentry_ops; + else + sb->s_d_op = &exfat_dentry_ops; +} + +static int exfat_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root_inode = NULL; + struct exfat_sb_info *sbi; + int err; + char buf[50]; + struct block_device *bdev = sb->s_bdev; + dev_t bd_dev = bdev ? bdev->bd_dev : 0; + + exfat_log_msg(sb, KERN_INFO, "trying to mount..."); + + /* + * GFP_KERNEL is ok here, because while we do hold the + * supeblock lock, memory pressure can't call back into + * the filesystem, since we're only just about to mount + * it and have no inodes etc active! + */ + sbi = kzalloc(sizeof(struct exfat_sb_info), GFP_KERNEL); + if (!sbi) { + exfat_log_msg(sb, KERN_INFO, + "trying to alloc sbi with vzalloc()"); + sbi = vzalloc(sizeof(struct exfat_sb_info)); + if (!sbi) { + exfat_log_msg(sb, KERN_ERR, "failed to mount! (ENOMEM)"); + return -ENOMEM; + } + sbi->use_vmalloc = 1; + } + + mutex_init(&sbi->s_vlock); + sb->s_fs_info = sbi; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) + sb->s_flags |= MS_NODIRATIME; +#else + sb->s_flags |= SB_NODIRATIME; +#endif + sb->s_magic = EXFAT_SUPER_MAGIC; + sb->s_op = &exfat_sops; + ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, + DEFAULT_RATELIMIT_BURST); + err = parse_options(sb, data, silent, &sbi->options); + if (err) { + exfat_log_msg(sb, KERN_ERR, "failed to parse options"); + goto failed_mount; + } + + setup_exfat_xattr_handler(sb); + setup_exfat_sync_super_wq(sb); + setup_dops(sb); + + err = exfat_mount(sb); + if (err) { + exfat_log_msg(sb, KERN_ERR, "failed to recognize fat type"); + goto failed_mount; + } + + /* set up enough so that it can read an inode */ + exfat_hash_init(sb); + + /* + * The low byte of FAT's first entry must have same value with + * media-field. But in real world, too many devices is + * writing wrong value. So, removed that validity check. + * + * if (FAT_FIRST_ENT(sb, media) != first) + */ + + err = -EINVAL; + sprintf(buf, "cp%d", sbi->options.codepage); + sbi->nls_disk = load_nls(buf); + if (!sbi->nls_disk) { + exfat_log_msg(sb, KERN_ERR, "codepage %s not found", buf); + goto failed_mount2; + } + + sbi->nls_io = load_nls(sbi->options.iocharset); + if (!sbi->nls_io) { + exfat_log_msg(sb, KERN_ERR, "IO charset %s not found", + sbi->options.iocharset); + goto failed_mount2; + } + + err = -ENOMEM; + root_inode = new_inode(sb); + if (!root_inode) { + exfat_log_msg(sb, KERN_ERR, "failed to allocate root inode."); + goto failed_mount2; + } + + root_inode->i_ino = EXFAT_ROOT_INO; + SET_IVERSION(root_inode, 1); + + err = exfat_read_root(root_inode); + if (err) { + exfat_log_msg(sb, KERN_ERR, "failed to initialize root inode."); + goto failed_mount2; + } + + exfat_attach(root_inode, EXFAT_I(root_inode)->i_pos); + insert_inode_hash(root_inode); + + err = -ENOMEM; + sb->s_root = __d_make_root(root_inode); + if (!sb->s_root) { + exfat_msg(sb, KERN_ERR, "failed to get the root dentry"); + goto failed_mount2; + } + + /* + * Initialize filesystem attributes (for sysfs) + * ex: /sys/fs/exfat/mmcblk1[179:17] + */ + sbi->sb_kobj.kset = exfat_kset; + err = kobject_init_and_add(&sbi->sb_kobj, &exfat_ktype, NULL, + "%s[%d:%d]", sb->s_id, MAJOR(bd_dev), MINOR(bd_dev)); + if (err) { + exfat_msg(sb, KERN_ERR, "Unable to create exfat attributes for" + " %s[%d:%d](%d)", sb->s_id, + MAJOR(bd_dev), MINOR(bd_dev), err); + goto failed_mount2; + } + + exfat_log_msg(sb, KERN_INFO, "mounted successfully!"); + + return 0; + +failed_mount2: + exfat_umount(sb); +failed_mount: + exfat_log_msg(sb, KERN_INFO, "failed to mount! (%d)", err); + + if (root_inode) + iput(root_inode); + sb->s_root = NULL; + + if (sbi->nls_io) + unload_nls(sbi->nls_io); + if (sbi->nls_disk) + unload_nls(sbi->nls_disk); + if (sbi->options.iocharset != exfat_default_iocharset) + kfree(sbi->options.iocharset); + sb->s_fs_info = NULL; + if (!sbi->use_vmalloc) + kfree(sbi); + else + vfree(sbi); + return err; +} + +static struct dentry *exfat_fs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_bdev(fs_type, flags, dev_name, data, exfat_fill_super); +} + +static void init_once(void *foo) +{ + struct exfat_inode_info *ei = (struct exfat_inode_info *)foo; + + INIT_HLIST_NODE(&ei->i_hash_fat); + inode_init_once(&ei->vfs_inode); +} + +static int __init exfat_init_inodecache(void) +{ + exfat_inode_cachep = kmem_cache_create("exfat_inode_cache", + sizeof(struct exfat_inode_info), + 0, (SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD), + init_once); + if (!exfat_inode_cachep) + return -ENOMEM; + return 0; +} + +static void exfat_destroy_inodecache(void) +{ + /* + * Make sure all delayed rcu free inodes are flushed before we + * destroy cache. + */ + rcu_barrier(); + kmem_cache_destroy(exfat_inode_cachep); +} + +static struct file_system_type exfat_fs_type = { + .owner = THIS_MODULE, + .name = "exfat", + .mount = exfat_fs_mount, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, +}; + +static int __init init_exfat_fs(void) +{ + int err; + + exfat_log_version(); + err = exfat_fscore_init(); + if (err) + goto error; + + exfat_kset = kset_create_and_add("exfat", NULL, fs_kobj); + if (!exfat_kset) { + pr_err("exFAT: failed to create fs_kobj\n"); + err = -ENOMEM; + goto error; + } + + err = sysfs_create_group(&exfat_kset->kobj, &attr_group); + if (err) { + pr_err("exFAT: failed to create exfat version attributes\n"); + goto error; + } + + err = exfat_uevent_init(exfat_kset); + if (err) + goto error; + + err = exfat_init_inodecache(); + if (err) { + pr_err("exFAT: failed to initialize inode cache\n"); + goto error; + } + + err = register_filesystem(&exfat_fs_type); + if (err) { + pr_err("exFAT: failed to register filesystem\n"); + goto error; + } + + return 0; +error: + exfat_uevent_uninit(); + + if (exfat_kset) { + sysfs_remove_group(&exfat_kset->kobj, &attr_group); + kset_unregister(exfat_kset); + exfat_kset = NULL; + } + + exfat_destroy_inodecache(); + exfat_fscore_shutdown(); + + pr_err("exFAT: failed to initialize FS driver(err:%d)\n", err); + return err; +} + +static void __exit exit_exfat_fs(void) +{ + exfat_uevent_uninit(); + + if (exfat_kset) { + sysfs_remove_group(&exfat_kset->kobj, &attr_group); + kset_unregister(exfat_kset); + exfat_kset = NULL; + } + + exfat_destroy_inodecache(); + unregister_filesystem(&exfat_fs_type); + + exfat_fscore_shutdown(); +} + +module_init(init_exfat_fs); +module_exit(exit_exfat_fs); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 9, 0) +MODULE_ALIAS_FS("exfat"); +#endif +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("FAT/exFAT filesystem support"); +MODULE_AUTHOR("Samsung Electronics Co., Ltd."); diff --git a/fs/exfat/upcase.h b/fs/exfat/upcase.h new file mode 100644 index 000000000000..f86356329ca5 --- /dev/null +++ b/fs/exfat/upcase.h @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + */ + +#ifndef _UPCASE_H +#define _UPCASE_H + +/* Upcase tabel macro */ +#define EXFAT_NUM_UPCASE 2918 +#define HIGH_INDEX_BIT (8) +#define HIGH_INDEX_MASK (0xFF00) +#define LOW_INDEX_BIT (16-HIGH_INDEX_BIT) +#define UTBL_ROW_COUNT (1<> LOW_INDEX_BIT; +} +static inline u16 exfat_get_row_index(u16 i) +{ + return i & ~HIGH_INDEX_MASK; +} + +static const u8 uni_def_upcase[EXFAT_NUM_UPCASE<<1] = { + 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 +}; + +#endif /* _UPCASE_H */ diff --git a/fs/exfat/version.h b/fs/exfat/version.h new file mode 100644 index 000000000000..def916cf1ff4 --- /dev/null +++ b/fs/exfat/version.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * version.h: exFAT version definition + */ + +#define EXFAT_BASE_VERSION "2.2.0" +#define EXFAT_EXTRAVERSION "3" +#define EXFAT_VARIANT "arter97" +#define EXFAT_VERSION EXFAT_BASE_VERSION "-" EXFAT_EXTRAVERSION EXFAT_VARIANT diff --git a/fs/exfat/xattr.c b/fs/exfat/xattr.c new file mode 100644 index 000000000000..ab3bb1717889 --- /dev/null +++ b/fs/exfat/xattr.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. + * + * xattr.c: exFAT code for supporting xattr(Extended File Attributes) + */ + +#include "config.h" + +#ifdef CONFIG_EXFAT_VIRTUAL_XATTR + +#include +#include +#include +#include +#include "exfat.h" + +#ifndef CONFIG_EXFAT_VIRTUAL_XATTR_SELINUX_LABEL +#define CONFIG_EXFAT_VIRTUAL_XATTR_SELINUX_LABEL ("undefined") +#endif + +static const char default_xattr[] = CONFIG_EXFAT_VIRTUAL_XATTR_SELINUX_LABEL; + +static int can_support(const char *name) +{ + if (!name || strcmp(name, "security.selinux")) + return -1; + return 0; +} + +ssize_t exfat_listxattr(struct dentry *dentry, char *list, size_t size) +{ + return 0; +} + +static int __exfat_xattr_check_support(const char *name) +{ + if (can_support(name)) + return -EOPNOTSUPP; + + return 0; +} + +ssize_t __exfat_getxattr(const char *name, void *value, size_t size) +{ + if (can_support(name)) + return -EOPNOTSUPP; + + if ((size > strlen(default_xattr)+1) && value) + strcpy(value, default_xattr); + + return strlen(default_xattr); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) +static int exfat_xattr_get(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, void *buffer, size_t size) +{ + return __exfat_getxattr(name, buffer, size); +} + +static int exfat_xattr_set(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, const void *value, size_t size, + int flags) +{ + return __exfat_xattr_check_support(name); +} + +const struct xattr_handler exfat_xattr_handler = { + .prefix = "", /* match anything */ + .get = exfat_xattr_get, + .set = exfat_xattr_set, +}; + +const struct xattr_handler *exfat_xattr_handlers[] = { + &exfat_xattr_handler, + NULL +}; + +void setup_exfat_xattr_handler(struct super_block *sb) +{ + sb->s_xattr = exfat_xattr_handlers; +} +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) */ +int exfat_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) +{ + return __exfat_xattr_check_support(name); +} + +ssize_t exfat_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) +{ + return __exfat_getxattr(name, value, size); +} + +int exfat_removexattr(struct dentry *dentry, const char *name) +{ + return __exfat_xattr_check_support(name); +} + +void setup_exfat_xattr_handler(struct super_block *sb) +{ + /* DO NOTHING */ +} +#endif + +#endif /* CONFIG_EXFAT_VIRTUAL_XATTR */