summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2012-10-24 17:02:47 -0700
committerGerrit Code Review <noreply-gerritcodereview@google.com>2012-10-24 17:02:47 -0700
commit0a6d8750c4330ad793f3beae670eb21e0e1d401b (patch)
tree226729f6f6181b6c867fbf6d06e89ca8a1dfcf6d
parent6950a7fcfda2d03446514e315b6bf6bd5c476496 (diff)
parent67fe07b509b35defe3a8c80d65ad5ae859ac354a (diff)
downloadfat32lib-tools_r21.tar.gz
Merge changes I50294fcc,Ie59bda0ctools_r21
* changes: Added LGPL 2.1 NOTICE file and MODULE_LICENSE External library with modifications. fat32-lib. Signed-off-by: Dan Galpin <dgalpin@google.com>
-rw-r--r--Android.mk23
-rw-r--r--CleanSpec.mk49
-rw-r--r--MODULE_LICENSE_LGPL0
-rw-r--r--NOTICE140
-rw-r--r--src/main/java/de/waldheinz/fs/AbstractFileSystem.java92
-rw-r--r--src/main/java/de/waldheinz/fs/AbstractFsObject.java112
-rw-r--r--src/main/java/de/waldheinz/fs/BlockDevice.java105
-rw-r--r--src/main/java/de/waldheinz/fs/FileSystem.java95
-rw-r--r--src/main/java/de/waldheinz/fs/FileSystemFactory.java53
-rw-r--r--src/main/java/de/waldheinz/fs/FsDirectory.java83
-rw-r--r--src/main/java/de/waldheinz/fs/FsDirectoryEntry.java143
-rw-r--r--src/main/java/de/waldheinz/fs/FsFile.java85
-rw-r--r--src/main/java/de/waldheinz/fs/FsObject.java53
-rw-r--r--src/main/java/de/waldheinz/fs/ReadOnlyException.java42
-rw-r--r--src/main/java/de/waldheinz/fs/UnknownFileSystemException.java54
-rw-r--r--src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java384
-rw-r--r--src/main/java/de/waldheinz/fs/fat/BootSector.java517
-rw-r--r--src/main/java/de/waldheinz/fs/fat/ClusterChain.java312
-rw-r--r--src/main/java/de/waldheinz/fs/fat/ClusterChainDirectory.java136
-rw-r--r--src/main/java/de/waldheinz/fs/fat/DirectoryFullException.java69
-rw-r--r--src/main/java/de/waldheinz/fs/fat/DosUtils.java81
-rw-r--r--src/main/java/de/waldheinz/fs/fat/Dummy83BufferGenerator.java180
-rw-r--r--src/main/java/de/waldheinz/fs/fat/Fat.java477
-rw-r--r--src/main/java/de/waldheinz/fs/fat/Fat16BootSector.java233
-rw-r--r--src/main/java/de/waldheinz/fs/fat/Fat16RootDirectory.java113
-rw-r--r--src/main/java/de/waldheinz/fs/fat/Fat32BootSector.java209
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatDirectoryEntry.java431
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatFile.java207
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatFileSystem.java283
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatLfnDirectory.java438
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatLfnDirectoryEntry.java378
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatType.java174
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FatUtils.java90
-rw-r--r--src/main/java/de/waldheinz/fs/fat/FsInfoSector.java187
-rw-r--r--src/main/java/de/waldheinz/fs/fat/LittleEndian.java102
-rw-r--r--src/main/java/de/waldheinz/fs/fat/Sector.java129
-rw-r--r--src/main/java/de/waldheinz/fs/fat/ShortName.java267
-rw-r--r--src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java468
-rw-r--r--src/main/java/de/waldheinz/fs/fat/package-info.java22
-rw-r--r--src/main/java/de/waldheinz/fs/package-info.java24
-rw-r--r--src/main/java/de/waldheinz/fs/util/FileDisk.java181
-rw-r--r--src/main/java/de/waldheinz/fs/util/RamDisk.java199
-rw-r--r--src/main/java/de/waldheinz/fs/util/package-info.java24
43 files changed, 7444 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..45f9615
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+# Build a host-side library
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
+LOCAL_MODULE := fat32lib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..b84e1b6
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/MODULE_LICENSE_LGPL b/MODULE_LICENSE_LGPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_LGPL
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..a759017
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,140 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+Preamble
+
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.
+
+We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library.
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library.
+
+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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.
+c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.
+d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.
+(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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.
+
+If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.
+
+However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.
+
+When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:
+
+a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)
+b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.
+c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.
+d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.
+e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.
+For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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.
+
+It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.
+b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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.
+
+9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License.
+
+11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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.
+
+13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
diff --git a/src/main/java/de/waldheinz/fs/AbstractFileSystem.java b/src/main/java/de/waldheinz/fs/AbstractFileSystem.java
new file mode 100644
index 0000000..85be4db
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/AbstractFileSystem.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+
+/**
+ * Abstract class with common things in different FileSystem implementations.
+ *
+ * @author Fabien DUMINY
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public abstract class AbstractFileSystem implements FileSystem {
+ private final boolean readOnly;
+ private boolean closed;
+
+ /**
+ * Creates a new {@code AbstractFileSystem}.
+ *
+ * @param readOnly if the file system should be read-only
+ */
+ public AbstractFileSystem(boolean readOnly) {
+ this.closed = false;
+ this.readOnly = readOnly;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!isClosed()) {
+ if (!isReadOnly()) {
+ flush();
+ }
+
+ closed = true;
+ }
+ }
+
+ @Override
+ public final boolean isClosed() {
+ return closed;
+ }
+
+ @Override
+ public final boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /**
+ * Checks if this {@code FileSystem} was already closed, and throws an
+ * exception if it was.
+ *
+ * @throws IllegalStateException if this {@code FileSystem} was
+ * already closed
+ * @see #isClosed()
+ * @see #close()
+ */
+ protected final void checkClosed() throws IllegalStateException {
+ if (isClosed()) {
+ throw new IllegalStateException("file system was already closed");
+ }
+ }
+
+ /**
+ * Checks if this {@code FileSystem} is read-only, and throws an
+ * exception if it is.
+ *
+ * @throws ReadOnlyException if this {@code FileSystem} is read-only
+ * @see #isReadOnly()
+ */
+ protected final void checkReadOnly() throws ReadOnlyException {
+ if (isReadOnly()) {
+ throw new ReadOnlyException();
+ }
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/AbstractFsObject.java b/src/main/java/de/waldheinz/fs/AbstractFsObject.java
new file mode 100644
index 0000000..fea022a
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/AbstractFsObject.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+/**
+ * A base class that helps to implement the {@code FsObject} interface.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ * @since 0.6
+ */
+public class AbstractFsObject implements FsObject {
+
+ /**
+ * Holds the read-only state of this object.
+ */
+ private final boolean readOnly;
+
+ /**
+ * Remembers if this object still valid.
+ */
+ private boolean valid;
+
+ /**
+ * Creates a new instance of {@code AbstractFsObject} which will be valid
+ * and have the specified read-only state.
+ *
+ * @param readOnly if the new object will be read-only
+ */
+ protected AbstractFsObject(boolean readOnly) {
+ this.valid = true;
+ this.readOnly = readOnly;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ * @see #checkValid()
+ * @see #invalidate()
+ */
+ @Override
+ public final boolean isValid() {
+ return this.valid;
+ }
+
+ /**
+ * Marks this object as invalid.
+ *
+ * @see #isValid()
+ * @see #checkValid()
+ */
+ protected final void invalidate() {
+ this.valid = false;
+ }
+
+ /**
+ * Convience method to check if this object is still valid and throw an
+ * {@code IllegalStateException} if it is not.
+ *
+ * @throws IllegalStateException if this object was invalidated
+ * @since 0.6
+ * @see #isValid()
+ * @see #invalidate()
+ */
+ protected final void checkValid() throws IllegalStateException {
+ if (!isValid()) throw new IllegalStateException(
+ this + " is not valid");
+ }
+
+ /**
+ * Convience method to check if this object is writable. An object is
+ * writable if it is both, valid and not read-only.
+ *
+ * @throws IllegalStateException if this object was invalidated
+ * @throws ReadOnlyException if this object was created with the read-only
+ * flag set
+ * @since 0.6
+ */
+ protected final void checkWritable()
+ throws IllegalStateException, ReadOnlyException {
+
+ checkValid();
+
+ if (isReadOnly()) {
+ throw new ReadOnlyException();
+ }
+ }
+
+ @Override
+ public final boolean isReadOnly() {
+ return this.readOnly;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/BlockDevice.java b/src/main/java/de/waldheinz/fs/BlockDevice.java
new file mode 100644
index 0000000..88f05c9
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/BlockDevice.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * This is the abstraction used for a device that can hold a {@link FileSystem}.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public interface BlockDevice {
+
+ /**
+ * Gets the total length of this device in bytes.
+ *
+ * @return the total number of bytes on this device
+ * @throws IOException on error getting the size of this device
+ */
+ public abstract long getSize() throws IOException;
+
+ /**
+ * Read a block of data from this device.
+ *
+ * @param devOffset the byte offset where to read the data from
+ * @param dest the destination buffer where to store the data read
+ * @throws IOException on read error
+ */
+ public abstract void read(long devOffset, ByteBuffer dest)
+ throws IOException;
+
+ /**
+ * Writes a block of data to this device.
+ *
+ * @param devOffset the byte offset where to store the data
+ * @param src the source {@code ByteBuffer} to write to the device
+ * @throws ReadOnlyException if this {@code BlockDevice} is read-only
+ * @throws IOException on write error
+ * @throws IllegalArgumentException if the {@code devOffset} is negative
+ * or the write would go beyond the end of the device
+ * @see #isReadOnly()
+ */
+ public abstract void write(long devOffset, ByteBuffer src)
+ throws ReadOnlyException, IOException,
+ IllegalArgumentException;
+
+ /**
+ * Flushes data in caches to the actual storage.
+ *
+ * @throws IOException on write error
+ */
+ public abstract void flush() throws IOException;
+
+ /**
+ * Returns the size of a sector on this device.
+ *
+ * @return the sector size in bytes
+ * @throws IOException on error determining the sector size
+ */
+ public int getSectorSize() throws IOException;
+
+ /**
+ * Closes this {@code BlockDevice}. No methods of this device may be
+ * accesses after this method was called.
+ *
+ * @throws IOException on error closing this device
+ * @see #isClosed()
+ */
+ public void close() throws IOException;
+
+ /**
+ * Checks if this device was already closed. No methods may be called
+ * on a closed device (except this method).
+ *
+ * @return if this device is closed
+ */
+ public boolean isClosed();
+
+ /**
+ * Checks if this {@code BlockDevice} is read-only.
+ *
+ * @return if this {@code BlockDevice} is read-only
+ */
+ public boolean isReadOnly();
+
+}
diff --git a/src/main/java/de/waldheinz/fs/FileSystem.java b/src/main/java/de/waldheinz/fs/FileSystem.java
new file mode 100644
index 0000000..f84baae
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/FileSystem.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+
+/**
+ * The interface common to all file system implementations.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public interface FileSystem {
+
+ /**
+ * Gets the root entry of this filesystem. This is usually a directory, but
+ * this is not required.
+ *
+ * @return the file system's root entry
+ * @throws IOException on read error
+ */
+ public FsDirectory getRoot() throws IOException;
+
+ /**
+ * Returns if this {@code FileSystem} is in read-only mode.
+ *
+ * @return if this {@code FileSystem} is read-only
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Close this file system. After a close, all invocations of methods of
+ * this file system or objects created by this file system will throw an
+ * {@link IllegalStateException}.
+ *
+ * @throws IOException on error closing the file system
+ */
+ public void close() throws IOException;
+
+ /**
+ * Returns {@code true} if this file system is closed. If the file system
+ * is closed, no more operations may be performed on it.
+ *
+ * @return if this file system is closed
+ */
+ public boolean isClosed();
+
+ /**
+ * The total size of this file system.
+ *
+ * @return if -1 this feature is unsupported
+ * @throws IOException if an I/O error occurs
+ */
+ public long getTotalSpace() throws IOException;
+
+ /**
+ * The free space of this file system.
+ *
+ * @return if -1 this feature is unsupported
+ * @throws IOException if an I/O error occurs
+ */
+ public long getFreeSpace() throws IOException;
+
+ /**
+ * The usable space of this file system.
+ *
+ * @return if -1 this feature is unsupported
+ * @throws IOException if an I/O error occurs
+ */
+ public long getUsableSpace() throws IOException;
+
+ /**
+ * Flushes any modified file system structures to the underlying storage.
+ *
+ * @throws IOException
+ */
+ public void flush() throws IOException;
+}
diff --git a/src/main/java/de/waldheinz/fs/FileSystemFactory.java b/src/main/java/de/waldheinz/fs/FileSystemFactory.java
new file mode 100644
index 0000000..47cc438
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/FileSystemFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import de.waldheinz.fs.fat.FatFileSystem;
+import java.io.IOException;
+
+/**
+ * Factory for {@link FileSystem} instances.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public class FileSystemFactory {
+
+ private FileSystemFactory() { }
+
+ /**
+ * <p>
+ * Creates a new {@link FileSystem} for the specified {@code device}. When
+ * using this method, care must be taken that there is only one
+ * {@code FileSystems} accessing the specified {@link BlockDevice}.
+ * Otherwise severe file system corruption may occur.
+ * </p>
+ *
+ * @param device the device to create the file system for
+ * @param readOnly if the file system should be openend read-only
+ * @return a new {@code FileSystem} instance for the specified device
+ * @throws UnknownFileSystemException if the file system type could
+ * not be determined
+ * @throws IOException on read error
+ */
+ public static FileSystem create(BlockDevice device, boolean readOnly)
+ throws UnknownFileSystemException, IOException {
+
+ return FatFileSystem.read(device, readOnly);
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/FsDirectory.java b/src/main/java/de/waldheinz/fs/FsDirectory.java
new file mode 100644
index 0000000..c3d9b94
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/FsDirectory.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ * Base class for all {@link FileSystem} directories.
+ *
+ * @author Ewout Prangsma &lt; epr at jnode.org&gt;
+ * @author Matthias Treydte
+ */
+public interface FsDirectory extends Iterable<FsDirectoryEntry>, FsObject {
+
+ /**
+ * Gets an iterator to iterate over the entries of this directory.
+ *
+ * @return the directory iterator
+ */
+ @Override
+ public Iterator<FsDirectoryEntry> iterator();
+
+ /**
+ * Gets the entry with the given name.
+ *
+ * @param name the name of the entry to get
+ * @return the entry, if it existed
+ * @throws IOException on error retrieving the entry
+ */
+ public FsDirectoryEntry getEntry(String name) throws IOException;
+
+ /**
+ * Add a new file with a given name to this directory.
+ *
+ * @param name the name of the file to add
+ * @return the entry pointing to the new file
+ * @throws IOException on error creating the file
+ */
+ public FsDirectoryEntry addFile(String name) throws IOException;
+
+ /**
+ * Add a new (sub-)directory with a given name to this directory.
+ *
+ * @param name the name of the sub-directory to add
+ * @return the entry pointing to the new directory
+ * @throws IOException on error creating the directory
+ */
+ public FsDirectoryEntry addDirectory(String name) throws IOException;
+
+ /**
+ * Remove the entry with the given name from this directory.
+ *
+ * @param name name of the entry to remove
+ * @throws IOException on error deleting the entry
+ */
+ public void remove(String name) throws IOException;
+
+ /**
+ * Save all dirty (unsaved) data to the device.
+ *
+ * @throws IOException on write error
+ */
+ public void flush() throws IOException;
+
+}
diff --git a/src/main/java/de/waldheinz/fs/FsDirectoryEntry.java b/src/main/java/de/waldheinz/fs/FsDirectoryEntry.java
new file mode 100644
index 0000000..c071bfe
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/FsDirectoryEntry.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+import java.util.Comparator;
+
+/**
+ * Represents one entry in a {@link FsDirectory}.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public interface FsDirectoryEntry extends FsObject {
+
+ /**
+ * Compares directory entries alphabetically, with all directories coming
+ * before all files.
+ */
+ public final static Comparator<FsDirectoryEntry> DIRECTORY_ENTRY_COMPARATOR =
+ new Comparator<FsDirectoryEntry>() {
+
+ @Override
+ public int compare(FsDirectoryEntry e1, FsDirectoryEntry e2) {
+ if (e2.isDirectory() == e1.isDirectory()) {
+ /* compare names */
+ return e1.getName().compareTo(e2.getName());
+ } else {
+ if (e2.isDirectory()) return 1;
+ else return -1;
+ }
+ }
+ };
+
+ /**
+ * Gets the name of this entry.
+ *
+ * @return this entrys name
+ */
+ public String getName();
+
+ /**
+ * Gets the last modification time of this entry.
+ *
+ * @return the last modification time of the entry as milliseconds
+ * since 1970, or {@code 0} if this filesystem does not support
+ * getting the last modification time
+ * @throws IOException if an error occurs retrieving the time stamp
+ */
+ public long getLastModified() throws IOException;
+
+ /**
+ * Returns the time when this entry was created as ms since 1970.
+ *
+ * @return the creation time, or 0 if this feature is not supported
+ * @throws IOException on error retrieving the time stamp
+ */
+ public long getCreated() throws IOException;
+
+ /**
+ * Returns the time when this entry was last accessed as ms since 1970.
+ *
+ * @return the last access time, or 0 if this feature is not supported
+ * @throws IOException on error retrieving the last access time
+ */
+ public long getLastAccessed() throws IOException;
+
+ /**
+ * Is this entry refering to a file?
+ *
+ * @return if this entry refers to a file
+ */
+ public boolean isFile();
+
+ /**
+ * Is this entry refering to a (sub-)directory?
+ *
+ * @return if this entry refers to a directory
+ */
+ public boolean isDirectory();
+
+ /**
+ * Sets the name of this entry.
+ *
+ * @param newName the new name of this entry
+ * @throws IOException on error setting the new name
+ */
+ public void setName(String newName) throws IOException;
+
+ /**
+ * Sets the last modification time of this entry.
+ *
+ * @param lastModified the new last modification time of this entry
+ * @throws IOException on write error
+ */
+ public void setLastModified(long lastModified) throws IOException;
+
+ /**
+ * Gets the file this entry refers to. This method can only be called if
+ * {@code isFile} returns {@code true}.
+ *
+ * @return the file described by this entry
+ * @throws IOException on error accessing the file
+ * @throws UnsupportedOperationException if this entry is a directory
+ */
+ public FsFile getFile()
+ throws IOException, UnsupportedOperationException;
+
+ /**
+ * Gets the directory this entry refers to. This method can only be called
+ * if <code>isDirectory</code> returns true.
+ *
+ * @return The directory described by this entry
+ * @throws IOException on read error
+ * @throws UnsupportedOperationException if this entry is a file
+ */
+ public FsDirectory getDirectory()
+ throws IOException, UnsupportedOperationException;
+
+ /**
+ * Indicate if the entry has been modified in memory (ie need to be saved)
+ *
+ * @return true if the entry needs to be saved
+ */
+ public boolean isDirty();
+}
diff --git a/src/main/java/de/waldheinz/fs/FsFile.java b/src/main/java/de/waldheinz/fs/FsFile.java
new file mode 100644
index 0000000..dfaa998
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/FsFile.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A FsFile is a representation of a single block of bytes on a filesystem. It
+ * is comparable to an inode in Unix.
+ *
+ * An FsFile does not have any knowledge of who is using this file. It is also
+ * possible that the system uses a single FsFile instance to create two
+ * inputstream's for two different principals.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ */
+public interface FsFile extends FsObject {
+
+ /**
+ * Gets the length (in bytes) of this file.
+ *
+ * @return the file size
+ */
+ public long getLength();
+
+ /**
+ * Sets the length of this file.
+ *
+ * @param length the new length of this file
+ * @throws IOException on error updating the file size
+ */
+ public void setLength(long length) throws IOException;
+
+ /**
+ * Reads from this file into the specified {@code ByteBuffer}. The
+ * first byte read will be put into the buffer at it's
+ * {@link ByteBuffer#position() position}, and the number of bytes read
+ * will equal the buffer's {@link ByteBuffer#remaining() remaining} bytes.
+ *
+ * @param offset the offset into the file where to start reading
+ * @param dest the destination buffer where to put the bytes that were read
+ * @throws IOException on read error
+ */
+ public void read(long offset, ByteBuffer dest) throws IOException;
+
+ /**
+ * Writes to this file taking the data to write from the specified
+ * {@code ByteBuffer}. This method will read the buffer's
+ * {@link ByteBuffer#remaining() remaining} bytes starting at it's
+ * {@link ByteBuffer#position() position}.
+ *
+ * @param offset the offset into the file where the first byte will be
+ * written
+ * @param src the source buffer to read the data from
+ * @throws ReadOnlyException if the file is read-only
+ * @throws IOException on write error
+ */
+ public void write(long offset, ByteBuffer src)
+ throws ReadOnlyException, IOException;
+
+ /**
+ * Flush any possibly cached data to the disk.
+ *
+ * @throws IOException on error flushing
+ */
+ public void flush() throws IOException;
+}
diff --git a/src/main/java/de/waldheinz/fs/FsObject.java b/src/main/java/de/waldheinz/fs/FsObject.java
new file mode 100644
index 0000000..72d7d50
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/FsObject.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+/**
+ * This interface is the base interface for objects that are part of a
+ * {@link FileSystem}.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public interface FsObject {
+
+ /**
+ * Checks if this {@code FsObject} is still valid.
+ *
+ * An object is not valid anymore if it has been removed from the
+ * filesystem. All invocations on methods (except this method and the
+ * methods inherited from {@link java.lang.Object}) of
+ * invalid objects must throw an {@link IllegalStateException}.
+ *
+ * @return if this {@code FsObject} is still valid
+ */
+ public boolean isValid();
+
+ /**
+ * Checks if this {@code FsObject} is read-only. Any attempt to modify a
+ * read-only {@code FsObject} must result in a {@link ReadOnlyException}
+ * being thrown, and the modification must not be performed.
+ *
+ * @return if this {@code FsObject} is read-only
+ * @since 0.6
+ */
+ public boolean isReadOnly();
+
+}
diff --git a/src/main/java/de/waldheinz/fs/ReadOnlyException.java b/src/main/java/de/waldheinz/fs/ReadOnlyException.java
new file mode 100644
index 0000000..f786d64
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/ReadOnlyException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+/**
+ * This exception is thrown when an attempt is made to write to a read-only
+ * {@link BlockDevice}, {@link FileSystem} or other file system object. This is
+ * an unchecked exception, as it should always be possible to query the object
+ * about it's read-only state using it's {@code isReadOnly()} method.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ * @see FileSystem#isReadOnly()
+ * @see BlockDevice#isReadOnly()
+ */
+public final class ReadOnlyException extends RuntimeException {
+
+ private final static long serialVersionUID = 1;
+
+ /**
+ * Creates a new instance of {@code ReadOnlyException}.
+ *
+ */
+ public ReadOnlyException() {
+ super("read-only");
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/UnknownFileSystemException.java b/src/main/java/de/waldheinz/fs/UnknownFileSystemException.java
new file mode 100644
index 0000000..0e27baf
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/UnknownFileSystemException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs;
+
+import java.io.IOException;
+
+/**
+ * Indicates that it was not possible to determine the type of the file
+ * system being used on a block device.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public final class UnknownFileSystemException extends IOException {
+ private final static long serialVersionUID = 1;
+
+ private final BlockDevice device;
+
+ /**
+ * Creates a new instance of {@code UnknownFileSystemException}.
+ *
+ * @param device the {@code BlockDevice} whose file system could not
+ * be determined
+ */
+ public UnknownFileSystemException(BlockDevice device) {
+ super("can not determin file system type"); //NOI18N
+ this.device = device;
+ }
+
+ /**
+ * Returns the {@code BlockDevice} whose file system could not be
+ * determined.
+ *
+ * @return the {@code BlockDevice} with an unknown file system
+ */
+ public BlockDevice getDevice() {
+ return this.device;
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java b/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java
new file mode 100644
index 0000000..d3445b7
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/AbstractDirectory.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This is the abstract base class for all directory implementations.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+abstract class AbstractDirectory {
+
+ /**
+ * The maximum length of the volume label.
+ *
+ * @see #setLabel(java.lang.String)
+ */
+ public static final int MAX_LABEL_LENGTH = 11;
+
+ private final List<FatDirectoryEntry> entries;
+ private final boolean readOnly;
+ private final boolean isRoot;
+
+ private boolean dirty;
+ private int capacity;
+ private String volumeLabel;
+
+ /**
+ * Creates a new instance of {@code AbstractDirectory}.
+ *
+ * @param capacity the initial capacity of the new instance
+ * @param readOnly if the instance should be read-only
+ * @param isRoot if the new {@code AbstractDirectory} represents a root
+ * directory
+ */
+ protected AbstractDirectory(
+ int capacity, boolean readOnly, boolean isRoot) {
+
+ this.entries = new ArrayList<FatDirectoryEntry>();
+ this.capacity = capacity;
+ this.readOnly = readOnly;
+ this.isRoot = isRoot;
+ }
+
+ /**
+ * Gets called when the {@code AbstractDirectory} must read it's content
+ * off the backing storage. This method must always fill the buffer's
+ * remaining space with the bytes making up this directory, beginning with
+ * the first byte.
+ *
+ * @param data the {@code ByteBuffer} to fill
+ * @throws IOException on read error
+ */
+ protected abstract void read(ByteBuffer data) throws IOException;
+
+ /**
+ * Gets called when the {@code AbstractDirectory} wants to write it's
+ * contents to the backing storage. This method is expected to write the
+ * buffer's remaining data to the storage, beginning with the first byte.
+ *
+ * @param data the {@code ByteBuffer} to write
+ * @throws IOException on write error
+ */
+ protected abstract void write(ByteBuffer data) throws IOException;
+
+ /**
+ * Returns the number of the cluster where this directory is stored. This
+ * is important when creating the ".." entry in a sub-directory, as this
+ * entry must poing to the storage cluster of it's parent.
+ *
+ * @return this directory's storage cluster
+ */
+ protected abstract long getStorageCluster();
+
+ /**
+ * Gets called by the {@code AbstractDirectory} when it has determined that
+ * it should resize because the number of entries has changed.
+ *
+ * @param entryCount the new number of entries this directory needs to store
+ * @throws IOException on write error
+ * @throws DirectoryFullException if the FAT12/16 root directory is full
+ * @see #sizeChanged(long)
+ * @see #checkEntryCount(int)
+ */
+ protected abstract void changeSize(int entryCount)
+ throws DirectoryFullException, IOException;
+
+ /**
+ * Replaces all entries in this directory.
+ *
+ * @param newEntries the new directory entries
+ */
+ public void setEntries(List<FatDirectoryEntry> newEntries) {
+ if (newEntries.size() > capacity)
+ throw new IllegalArgumentException("too many entries");
+
+ this.entries.clear();
+ this.entries.addAll(newEntries);
+ }
+
+ /**
+ *
+ *
+ * @param newSize the new storage space for the directory in bytes
+ * @see #changeSize(int)
+ */
+ protected final void sizeChanged(long newSize) throws IOException {
+ final long newCount = newSize / FatDirectoryEntry.SIZE;
+ if (newCount > Integer.MAX_VALUE)
+ throw new IOException("directory too large");
+
+ this.capacity = (int) newCount;
+ }
+
+ public final FatDirectoryEntry getEntry(int idx) {
+ return this.entries.get(idx);
+ }
+
+ /**
+ * Returns the current capacity of this {@code AbstractDirectory}.
+ *
+ * @return the number of entries this directory can hold in its current
+ * storage space
+ * @see #changeSize(int)
+ */
+ public final int getCapacity() {
+ return this.capacity;
+ }
+
+ /**
+ * The number of entries that are currently stored in this
+ * {@code AbstractDirectory}.
+ *
+ * @return the current number of directory entries
+ */
+ public final int getEntryCount() {
+ return this.entries.size();
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public final boolean isRoot() {
+ return this.isRoot;
+ }
+
+ /**
+ * Gets the number of directory entries in this directory. This is the
+ * number of "real" entries in this directory, possibly plus one if a
+ * volume label is set.
+ *
+ * @return the number of entries in this directory
+ */
+ public int getSize() {
+ return entries.size() + ((this.volumeLabel != null) ? 1 : 0);
+ }
+
+ /**
+ * Mark this directory as dirty.
+ */
+ protected final void setDirty() {
+ this.dirty = true;
+ }
+
+ /**
+ * Checks if this {@code AbstractDirectory} is a root directory.
+ *
+ * @throws UnsupportedOperationException if this is not a root directory
+ * @see #isRoot()
+ */
+ private void checkRoot() throws UnsupportedOperationException {
+ if (!isRoot()) {
+ throw new UnsupportedOperationException(
+ "only supported on root directories");
+ }
+ }
+
+ /**
+ * Mark this directory as not dirty.
+ */
+ private void resetDirty() {
+ this.dirty = false;
+ }
+
+ /**
+ * Flush the contents of this directory to the persistent storage
+ */
+ public void flush() throws IOException {
+
+ final ByteBuffer data = ByteBuffer.allocate(
+ getCapacity() * FatDirectoryEntry.SIZE);
+
+ for (int i=0; i < entries.size(); i++) {
+ final FatDirectoryEntry entry = entries.get(i);
+
+ if (entry != null) {
+ entry.write(data);
+ }
+ }
+
+ /* TODO: the label could be placed directly the dot entries */
+
+ if (this.volumeLabel != null) {
+ final FatDirectoryEntry labelEntry =
+ FatDirectoryEntry.createVolumeLabel(volumeLabel);
+
+ labelEntry.write(data);
+ }
+
+ if (data.hasRemaining()) {
+ FatDirectoryEntry.writeNullEntry(data);
+ }
+
+ data.flip();
+
+ write(data);
+ resetDirty();
+ }
+
+ protected final void read() throws IOException {
+ final ByteBuffer data = ByteBuffer.allocate(
+ getCapacity() * FatDirectoryEntry.SIZE);
+
+ read(data);
+ data.flip();
+
+ for (int i=0; i < getCapacity(); i++) {
+ final FatDirectoryEntry e =
+ FatDirectoryEntry.read(data, isReadOnly());
+
+ if (e == null) break;
+
+ if (e.isVolumeLabel()) {
+ if (!this.isRoot) throw new IOException(
+ "volume label in non-root directory");
+
+ this.volumeLabel = e.getVolumeLabel();
+ } else {
+ entries.add(e);
+ }
+ }
+ }
+
+ public void addEntry(FatDirectoryEntry e) throws IOException {
+ assert (e != null);
+
+ if (getSize() == getCapacity()) {
+ changeSize(getCapacity() + 1);
+ }
+
+ entries.add(e);
+ }
+
+ public void addEntries(FatDirectoryEntry[] entries)
+ throws IOException {
+
+ if (getSize() + entries.length > getCapacity()) {
+ changeSize(getSize() + entries.length);
+ }
+
+ this.entries.addAll(Arrays.asList(entries));
+ }
+
+ public void removeEntry(FatDirectoryEntry entry) throws IOException {
+ assert (entry != null);
+
+ this.entries.remove(entry);
+ changeSize(getSize());
+ }
+
+ /**
+ * Returns the volume label that is stored in this directory. Reading the
+ * volume label is only supported for the root directory.
+ *
+ * @return the volume label stored in this directory, or {@code null}
+ * @throws UnsupportedOperationException if this is not a root directory
+ * @see #isRoot()
+ */
+ public String getLabel() throws UnsupportedOperationException {
+ checkRoot();
+
+ return volumeLabel;
+ }
+
+ public FatDirectoryEntry createSub(Fat fat) throws IOException {
+ final ClusterChain chain = new ClusterChain(fat, false);
+ chain.setChainLength(1);
+
+ final FatDirectoryEntry entry = FatDirectoryEntry.create(true);
+ entry.setStartCluster(chain.getStartCluster());
+
+ final ClusterChainDirectory dir =
+ new ClusterChainDirectory(chain, false);
+
+ /* add "." entry */
+
+ final FatDirectoryEntry dot = FatDirectoryEntry.create(true);
+ dot.setShortName(ShortName.DOT);
+ dot.setStartCluster(dir.getStorageCluster());
+ copyDateTimeFields(entry, dot);
+ dir.addEntry(dot);
+
+ /* add ".." entry */
+
+ final FatDirectoryEntry dotDot = FatDirectoryEntry.create(true);
+ dotDot.setShortName(ShortName.DOT_DOT);
+ dotDot.setStartCluster(getStorageCluster());
+ copyDateTimeFields(entry, dotDot);
+ dir.addEntry(dotDot);
+
+ dir.flush();
+
+ return entry;
+ }
+
+ private static void copyDateTimeFields(
+ FatDirectoryEntry src, FatDirectoryEntry dst) {
+
+ dst.setCreated(src.getCreated());
+ dst.setLastAccessed(src.getLastAccessed());
+ dst.setLastModified(src.getLastModified());
+ }
+
+ /**
+ * Sets the volume label that is stored in this directory. Setting the
+ * volume label is supported on the root directory only.
+ *
+ * @param label the new volume label
+ * @throws IllegalArgumentException if the label is too long
+ * @throws UnsupportedOperationException if this is not a root directory
+ * @see #isRoot()
+ */
+ public void setLabel(String label) throws IllegalArgumentException,
+ UnsupportedOperationException, IOException {
+
+ checkRoot();
+
+ if (label.length() > MAX_LABEL_LENGTH) throw new
+ IllegalArgumentException("label too long");
+
+ if (this.volumeLabel != null) {
+ if (label == null) {
+ changeSize(getSize() - 1);
+ this.volumeLabel = null;
+ } else {
+ ShortName.checkValidChars(label.toCharArray());
+ this.volumeLabel = label;
+ }
+ } else {
+ if (label != null) {
+ changeSize(getSize() + 1);
+ ShortName.checkValidChars(label.toCharArray());
+ this.volumeLabel = label;
+ }
+ }
+
+ this.dirty = true;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/BootSector.java b/src/main/java/de/waldheinz/fs/fat/BootSector.java
new file mode 100644
index 0000000..d1aa398
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/BootSector.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * The boot sector.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public abstract class BootSector extends Sector {
+
+ /**
+ * Offset to the byte specifying the number of FATs.
+ *
+ * @see #getNrFats()
+ * @see #setNrFats(int)
+ */
+ public static final int FAT_COUNT_OFFSET = 16;
+ public static final int RESERVED_SECTORS_OFFSET = 14;
+
+ public static final int TOTAL_SECTORS_16_OFFSET = 19;
+ public static final int TOTAL_SECTORS_32_OFFSET = 32;
+
+ /**
+ * The length of the file system type string.
+ *
+ * @see #getFileSystemType()
+ */
+ public static final int FILE_SYSTEM_TYPE_LENGTH = 8;
+
+ /**
+ * The offset to the sectors per cluster value stored in a boot sector.
+ *
+ * @see #getSectorsPerCluster()
+ * @see #setSectorsPerCluster(int)
+ */
+ public static final int SECTORS_PER_CLUSTER_OFFSET = 0x0d;
+
+ public static final int EXTENDED_BOOT_SIGNATURE = 0x29;
+
+ /**
+ * The size of a boot sector in bytes.
+ */
+ public final static int SIZE = 512;
+
+ protected BootSector(BlockDevice device) {
+ super(device, 0, SIZE);
+ markDirty();
+ }
+
+ public static BootSector read(BlockDevice device) throws IOException {
+ final ByteBuffer bb = ByteBuffer.allocate(512);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ device.read(0, bb);
+
+ if ((bb.get(510) & 0xff) != 0x55 ||
+ (bb.get(511) & 0xff) != 0xaa) throw new IOException(
+ "missing boot sector signature");
+
+ final byte sectorsPerCluster = bb.get(SECTORS_PER_CLUSTER_OFFSET);
+
+ if (sectorsPerCluster <= 0) throw new IOException(
+ "suspicious sectors per cluster count " + sectorsPerCluster);
+
+ final int rootDirEntries = bb.getShort(
+ Fat16BootSector.ROOT_DIR_ENTRIES_OFFSET);
+ final int rootDirSectors = ((rootDirEntries * 32) +
+ (device.getSectorSize() - 1)) / device.getSectorSize();
+
+ final int total16 =
+ bb.getShort(TOTAL_SECTORS_16_OFFSET) & 0xffff;
+ final long total32 =
+ bb.getInt(TOTAL_SECTORS_32_OFFSET) & 0xffffffffl;
+
+ final long totalSectors = total16 == 0 ? total32 : total16;
+
+ final int fatSz16 =
+ bb.getShort(Fat16BootSector.SECTORS_PER_FAT_OFFSET) & 0xffff;
+ final long fatSz32 =
+ bb.getInt(Fat32BootSector.SECTORS_PER_FAT_OFFSET) & 0xffffffffl;
+
+ final long fatSz = fatSz16 == 0 ? fatSz32 : fatSz16;
+ final int reservedSectors = bb.getShort(RESERVED_SECTORS_OFFSET);
+ final int fatCount = bb.get(FAT_COUNT_OFFSET);
+ final long dataSectors = totalSectors - (reservedSectors +
+ (fatCount * fatSz) + rootDirSectors);
+
+ final long clusterCount = dataSectors / sectorsPerCluster;
+
+ final BootSector result =
+ (clusterCount > Fat16BootSector.MAX_FAT16_CLUSTERS) ?
+ new Fat32BootSector(device) : new Fat16BootSector(device);
+
+ result.read();
+ return result;
+ }
+
+ public abstract FatType getFatType();
+
+ /**
+ * Gets the number of sectors per FAT.
+ *
+ * @return the sectors per FAT
+ */
+ public abstract long getSectorsPerFat();
+
+ /**
+ * Sets the number of sectors/fat
+ *
+ * @param v the new number of sectors per fat
+ */
+ public abstract void setSectorsPerFat(long v);
+
+ public abstract void setSectorCount(long count);
+
+ public abstract int getRootDirEntryCount();
+
+ public abstract long getSectorCount();
+
+ /**
+ * Returns the offset to the file system type label, as this differs
+ * between FAT12/16 and FAT32.
+ *
+ * @return the offset to the file system type label
+ */
+ public abstract int getFileSystemTypeLabelOffset();
+
+ public abstract int getExtendedBootSignatureOffset();
+
+ public void init() throws IOException {
+ setBytesPerSector(getDevice().getSectorSize());
+ setSectorCount(getDevice().getSize() / getDevice().getSectorSize());
+ set8(getExtendedBootSignatureOffset(), EXTENDED_BOOT_SIGNATURE);
+
+ /* magic bytes needed by some windows versions to recognize a boot
+ * sector. these are x86 jump instructions which lead into
+ * nirvana when executed, but we're currently unable to produce really
+ * bootable images anyway. So... */
+ set8(0x00, 0xeb);
+ set8(0x01, 0x3c);
+ set8(0x02, 0x90);
+
+ /* the boot sector signature */
+ set8(0x1fe, 0x55);
+ set8(0x1ff, 0xaa);
+ }
+
+ /**
+ * Returns the file system type label string.
+ *
+ * @return the file system type string
+ * @see #setFileSystemTypeLabel(java.lang.String)
+ * @see #getFileSystemTypeLabelOffset()
+ * @see #FILE_SYSTEM_TYPE_LENGTH
+ */
+ public String getFileSystemTypeLabel() {
+ final StringBuilder sb = new StringBuilder(FILE_SYSTEM_TYPE_LENGTH);
+
+ for (int i=0; i < FILE_SYSTEM_TYPE_LENGTH; i++) {
+ sb.append ((char) get8(getFileSystemTypeLabelOffset() + i));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ *
+ *
+ * @param fsType the
+ * @throws IllegalArgumentException if the length of the specified string
+ * does not equal {@link #FILE_SYSTEM_TYPE_LENGTH}
+ */
+ public void setFileSystemTypeLabel(String fsType)
+ throws IllegalArgumentException {
+
+ if (fsType.length() != FILE_SYSTEM_TYPE_LENGTH) {
+ throw new IllegalArgumentException();
+ }
+
+ for (int i=0; i < FILE_SYSTEM_TYPE_LENGTH; i++) {
+ set8(getFileSystemTypeLabelOffset() + i, fsType.charAt(i));
+ }
+ }
+
+ /**
+ * Returns the number of clusters that are really needed to cover the
+ * data-caontaining portion of the file system.
+ *
+ * @return the number of clusters usable for user data
+ * @see #getDataSize()
+ */
+ public final long getDataClusterCount() {
+ return getDataSize() / getBytesPerCluster();
+ }
+
+ /**
+ * Returns the size of the data-containing portion of the file system.
+ *
+ * @return the number of bytes usable for storing user data
+ */
+ private long getDataSize() {
+ return (getSectorCount() * getBytesPerSector()) -
+ FatUtils.getFilesOffset(this);
+ }
+
+ /**
+ * Gets the OEM name
+ *
+ * @return String
+ */
+ public String getOemName() {
+ StringBuilder b = new StringBuilder(8);
+
+ for (int i = 0; i < 8; i++) {
+ int v = get8(0x3 + i);
+ if (v == 0) break;
+ b.append((char) v);
+ }
+
+ return b.toString();
+ }
+
+
+ /**
+ * Sets the OEM name, must be at most 8 characters long.
+ *
+ * @param name the new OEM name
+ */
+ public void setOemName(String name) {
+ if (name.length() > 8) throw new IllegalArgumentException(
+ "only 8 characters are allowed");
+
+ for (int i = 0; i < 8; i++) {
+ char ch;
+ if (i < name.length()) {
+ ch = name.charAt(i);
+ } else {
+ ch = (char) 0;
+ }
+
+ set8(0x3 + i, ch);
+ }
+ }
+
+ /**
+ * Gets the number of bytes/sector
+ *
+ * @return int
+ */
+ public int getBytesPerSector() {
+ return get16(0x0b);
+ }
+
+ /**
+ * Sets the number of bytes/sector
+ *
+ * @param v the new value for bytes per sector
+ */
+ public void setBytesPerSector(int v) {
+ if (v == getBytesPerSector()) return;
+
+ switch (v) {
+ case 512: case 1024: case 2048: case 4096:
+ set16(0x0b, v);
+ break;
+
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private static boolean isPowerOfTwo(int n) {
+ return ((n!=0) && (n&(n-1))==0);
+ }
+
+ /**
+ * Returns the number of bytes per cluster, which is calculated from the
+ * {@link #getSectorsPerCluster() sectors per cluster} and the
+ * {@link #getBytesPerSector() bytes per sector}.
+ *
+ * @return the number of bytes per cluster
+ */
+ public int getBytesPerCluster() {
+ return this.getSectorsPerCluster() * this.getBytesPerSector();
+ }
+
+ /**
+ * Gets the number of sectors/cluster
+ *
+ * @return int
+ */
+ public int getSectorsPerCluster() {
+ return get8(SECTORS_PER_CLUSTER_OFFSET);
+ }
+
+ /**
+ * Sets the number of sectors/cluster
+ *
+ * @param v the new number of sectors per cluster
+ */
+ public void setSectorsPerCluster(int v) {
+ if (v == getSectorsPerCluster()) return;
+ if (!isPowerOfTwo(v)) throw new IllegalArgumentException(
+ "value must be a power of two");
+
+ set8(SECTORS_PER_CLUSTER_OFFSET, v);
+ }
+
+ /**
+ * Gets the number of reserved (for bootrecord) sectors
+ *
+ * @return int
+ */
+ public int getNrReservedSectors() {
+ return get16(RESERVED_SECTORS_OFFSET);
+ }
+
+ /**
+ * Sets the number of reserved (for bootrecord) sectors
+ *
+ * @param v the new number of reserved sectors
+ */
+ public void setNrReservedSectors(int v) {
+ if (v == getNrReservedSectors()) return;
+ if (v < 1) throw new IllegalArgumentException(
+ "there must be >= 1 reserved sectors");
+ set16(RESERVED_SECTORS_OFFSET, v);
+ }
+
+ /**
+ * Gets the number of fats
+ *
+ * @return int
+ */
+ public final int getNrFats() {
+ return get8(FAT_COUNT_OFFSET);
+ }
+
+ /**
+ * Sets the number of fats
+ *
+ * @param v the new number of fats
+ */
+ public final void setNrFats(int v) {
+ if (v == getNrFats()) return;
+
+ set8(FAT_COUNT_OFFSET, v);
+ }
+
+ /**
+ * Gets the number of logical sectors
+ *
+ * @return int
+ */
+ protected int getNrLogicalSectors() {
+ return get16(TOTAL_SECTORS_16_OFFSET);
+ }
+
+ /**
+ * Sets the number of logical sectors
+ *
+ * @param v the new number of logical sectors
+ */
+ protected void setNrLogicalSectors(int v) {
+ if (v == getNrLogicalSectors()) return;
+
+ set16(TOTAL_SECTORS_16_OFFSET, v);
+ }
+
+ protected void setNrTotalSectors(long v) {
+ set32(TOTAL_SECTORS_32_OFFSET, v);
+ }
+
+ protected long getNrTotalSectors() {
+ return get32(TOTAL_SECTORS_32_OFFSET);
+ }
+
+ /**
+ * Gets the medium descriptor byte
+ *
+ * @return int
+ */
+ public int getMediumDescriptor() {
+ return get8(0x15);
+ }
+
+ /**
+ * Sets the medium descriptor byte
+ *
+ * @param v the new medium descriptor
+ */
+ public void setMediumDescriptor(int v) {
+ set8(0x15, v);
+ }
+
+ /**
+ * Gets the number of sectors/track
+ *
+ * @return int
+ */
+ public int getSectorsPerTrack() {
+ return get16(0x18);
+ }
+
+ /**
+ * Sets the number of sectors/track
+ *
+ * @param v the new number of sectors per track
+ */
+ public void setSectorsPerTrack(int v) {
+ if (v == getSectorsPerTrack()) return;
+
+ set16(0x18, v);
+ }
+
+ /**
+ * Gets the number of heads
+ *
+ * @return int
+ */
+ public int getNrHeads() {
+ return get16(0x1a);
+ }
+
+ /**
+ * Sets the number of heads
+ *
+ * @param v the new number of heads
+ */
+ public void setNrHeads(int v) {
+ if (v == getNrHeads()) return;
+
+ set16(0x1a, v);
+ }
+
+ /**
+ * Gets the number of hidden sectors
+ *
+ * @return int
+ */
+ public long getNrHiddenSectors() {
+ return get32(0x1c);
+ }
+
+ /**
+ * Sets the number of hidden sectors
+ *
+ * @param v the new number of hidden sectors
+ */
+ public void setNrHiddenSectors(long v) {
+ if (v == getNrHiddenSectors()) return;
+
+ set32(0x1c, v);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder res = new StringBuilder(1024);
+ res.append("Bootsector :\n");
+ res.append("oemName=");
+ res.append(getOemName());
+ res.append('\n');
+ res.append("medium descriptor = ");
+ res.append(getMediumDescriptor());
+ res.append('\n');
+ res.append("Nr heads = ");
+ res.append(getNrHeads());
+ res.append('\n');
+ res.append("Sectors per track = ");
+ res.append(getSectorsPerTrack());
+ res.append('\n');
+ res.append("Sector per cluster = ");
+ res.append(getSectorsPerCluster());
+ res.append('\n');
+ res.append("byte per sector = ");
+ res.append(getBytesPerSector());
+ res.append('\n');
+ res.append("Nr fats = ");
+ res.append(getNrFats());
+ res.append('\n');
+ res.append("Nr hidden sectors = ");
+ res.append(getNrHiddenSectors());
+ res.append('\n');
+ res.append("Nr logical sectors = ");
+ res.append(getNrLogicalSectors());
+ res.append('\n');
+ res.append("Nr reserved sector = ");
+ res.append(getNrReservedSectors());
+ res.append('\n');
+
+ return res.toString();
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/ClusterChain.java b/src/main/java/de/waldheinz/fs/fat/ClusterChain.java
new file mode 100644
index 0000000..e296092
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/ClusterChain.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.AbstractFsObject;
+import de.waldheinz.fs.BlockDevice;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A chain of clusters as stored in a {@link Fat}.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+final class ClusterChain extends AbstractFsObject {
+ protected final Fat fat;
+ private final BlockDevice device;
+ private final int clusterSize;
+ protected final long dataOffset;
+
+ private long startCluster;
+
+ /**
+ * Creates a new {@code ClusterChain} that contains no clusters.
+ *
+ * @param fat the {@code Fat} that holds the new chain
+ * @param readOnly if the chain should be created read-only
+ */
+ public ClusterChain(Fat fat, boolean readOnly) {
+ this(fat, 0, readOnly);
+ }
+
+ public ClusterChain(Fat fat, long startCluster, boolean readOnly) {
+ super(readOnly);
+
+ this.fat = fat;
+
+ if (startCluster != 0) {
+ this.fat.testCluster(startCluster);
+
+ if (this.fat.isFreeCluster(startCluster))
+ throw new IllegalArgumentException(
+ "cluster " + startCluster + " is free");
+ }
+
+ this.device = fat.getDevice();
+ this.dataOffset = FatUtils.getFilesOffset(fat.getBootSector());
+ this.startCluster = startCluster;
+ this.clusterSize = fat.getBootSector().getBytesPerCluster();
+ }
+
+ public int getClusterSize() {
+ return clusterSize;
+ }
+
+ public Fat getFat() {
+ return fat;
+ }
+
+ public BlockDevice getDevice() {
+ return device;
+ }
+
+ /**
+ * Returns the first cluster of this chain.
+ *
+ * @return the chain's first cluster, which may be 0 if this chain does
+ * not contain any clusters
+ */
+ public long getStartCluster() {
+ return startCluster;
+ }
+
+ /**
+ * Calculates the device offset (0-based) for the given cluster and offset
+ * within the cluster.
+ *
+ * @param cluster
+ * @param clusterOffset
+ * @return long
+ * @throws FileSystemException
+ */
+ private long getDevOffset(long cluster, int clusterOffset) {
+ return dataOffset + clusterOffset +
+ ((cluster - Fat.FIRST_CLUSTER) * clusterSize);
+ }
+
+ /**
+ * Returns the size this {@code ClusterChain} occupies on the device.
+ *
+ * @return the size this chain occupies on the device in bytes
+ */
+ public long getLengthOnDisk() {
+ if (getStartCluster() == 0) return 0;
+
+ return getChainLength() * clusterSize;
+ }
+
+ /**
+ * Sets the length of this {@code ClusterChain} in bytes. Because a
+ * {@code ClusterChain} can only contain full clusters, the new size
+ * will always be a multiple of the cluster size.
+ *
+ * @param size the desired number of bytes the can be stored in
+ * this {@code ClusterChain}
+ * @return the true number of bytes this {@code ClusterChain} can contain
+ * @throws IOException on error setting the new size
+ * @see #setChainLength(int)
+ */
+ public long setSize(long size) throws IOException {
+ final long nrClusters = ((size + clusterSize - 1) / clusterSize);
+ if (nrClusters > Integer.MAX_VALUE)
+ throw new IOException("too many clusters");
+
+ setChainLength((int) nrClusters);
+
+ return clusterSize * nrClusters;
+ }
+
+ /**
+ * Determines the length of this {@code ClusterChain} in clusters.
+ *
+ * @return the length of this chain
+ */
+ public int getChainLength() {
+ if (getStartCluster() == 0) return 0;
+
+ final long[] chain = getFat().getChain(getStartCluster());
+ return chain.length;
+ }
+
+ /**
+ * Sets the length of this cluster chain in clusters.
+ *
+ * @param nrClusters the new number of clusters this chain should contain,
+ * must be {@code >= 0}
+ * @throws IOException on error updating the chain length
+ * @see #setSize(long)
+ */
+ public void setChainLength(int nrClusters) throws IOException {
+ if (nrClusters < 0) throw new IllegalArgumentException(
+ "negative cluster count"); //NOI18N
+
+ if ((this.startCluster == 0) && (nrClusters == 0)) {
+ /* nothing to do */
+ } else if ((this.startCluster == 0) && (nrClusters > 0)) {
+ final long[] chain = fat.allocNew(nrClusters);
+ this.startCluster = chain[0];
+ } else {
+ final long[] chain = fat.getChain(startCluster);
+
+ if (nrClusters != chain.length) {
+ if (nrClusters > chain.length) {
+ /* grow the chain */
+ int count = nrClusters - chain.length;
+
+ while (count > 0) {
+ fat.allocAppend(getStartCluster());
+ count--;
+ }
+ } else {
+ /* shrink the chain */
+ if (nrClusters > 0) {
+ fat.setEof(chain[nrClusters - 1]);
+ for (int i = nrClusters; i < chain.length; i++) {
+ fat.setFree(chain[i]);
+ }
+ } else {
+ for (int i=0; i < chain.length; i++) {
+ fat.setFree(chain[i]);
+ }
+
+ this.startCluster = 0;
+ }
+ }
+ }
+ }
+ }
+
+ public void readData(long offset, ByteBuffer dest)
+ throws IOException {
+
+ int len = dest.remaining();
+
+ if ((startCluster == 0 && len > 0)) throw new EOFException();
+
+ final long[] chain = getFat().getChain(startCluster);
+ final BlockDevice dev = getDevice();
+
+ int chainIdx = (int) (offset / clusterSize);
+ if (offset % clusterSize != 0) {
+ int clusOfs = (int) (offset % clusterSize);
+ int size = Math.min(len,
+ (int) (clusterSize - (offset % clusterSize) - 1));
+ dest.limit(dest.position() + size);
+
+ dev.read(getDevOffset(chain[chainIdx], clusOfs), dest);
+
+ offset += size;
+ len -= size;
+ chainIdx++;
+ }
+
+ while (len > 0) {
+ int size = Math.min(clusterSize, len);
+ dest.limit(dest.position() + size);
+
+ dev.read(getDevOffset(chain[chainIdx], 0), dest);
+
+ len -= size;
+ chainIdx++;
+ }
+ }
+
+ /**
+ * Writes data to this cluster chain, possibly growing the chain so it
+ * can store the additional data. When this method returns without throwing
+ * an exception, the buffer's {@link ByteBuffer#position() position} will
+ * equal it's {@link ByteBuffer#limit() limit}, and the limit will not
+ * have changed. This is not guaranteed if writing fails.
+ *
+ * @param offset the offset where to write the first byte from the buffer
+ * @param srcBuf the buffer to write to this {@code ClusterChain}
+ * @throws IOException on write error
+ */
+ public void writeData(long offset, ByteBuffer srcBuf) throws IOException {
+
+ int len = srcBuf.remaining();
+
+ if (len == 0) return;
+
+ final long minSize = offset + len;
+ if (getLengthOnDisk() < minSize) {
+ setSize(minSize);
+ }
+
+ final long[] chain = fat.getChain(getStartCluster());
+
+ int chainIdx = (int) (offset / clusterSize);
+ if (offset % clusterSize != 0) {
+ int clusOfs = (int) (offset % clusterSize);
+ int size = Math.min(len,
+ (int) (clusterSize - (offset % clusterSize)));
+ srcBuf.limit(srcBuf.position() + size);
+
+ device.write(getDevOffset(chain[chainIdx], clusOfs), srcBuf);
+
+ offset += size;
+ len -= size;
+ chainIdx++;
+ }
+
+ while (len > 0) {
+ int size = Math.min(clusterSize, len);
+ srcBuf.limit(srcBuf.position() + size);
+
+ device.write(getDevOffset(chain[chainIdx], 0), srcBuf);
+
+ len -= size;
+ chainIdx++;
+ }
+
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (!(obj instanceof ClusterChain)) return false;
+
+ final ClusterChain other = (ClusterChain) obj;
+
+ if (this.fat != other.fat &&
+ (this.fat == null || !this.fat.equals(other.fat))) {
+
+ return false;
+ }
+
+ if (this.startCluster != other.startCluster) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 79 * hash +
+ (this.fat != null ? this.fat.hashCode() : 0);
+ hash = 79 * hash +
+ (int) (this.startCluster ^ (this.startCluster >>> 32));
+ return hash;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/ClusterChainDirectory.java b/src/main/java/de/waldheinz/fs/fat/ClusterChainDirectory.java
new file mode 100644
index 0000000..fa3e9df
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/ClusterChainDirectory.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A directory that is stored in a cluster chain.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+class ClusterChainDirectory extends AbstractDirectory {
+
+ /**
+ * According to the FAT specification, this is the maximum size a FAT
+ * directory may occupy on disk. The {@code ClusterChainDirectory} takes
+ * care not to grow beyond this limit.
+ *
+ * @see #changeSize(int)
+ */
+ public final static int MAX_SIZE = 65536 * 32;
+
+ /**
+ * The {@code ClusterChain} that stores this directory. Package-visible
+ * for testing.
+ */
+ final ClusterChain chain;
+
+ protected ClusterChainDirectory(ClusterChain chain, boolean isRoot) {
+
+ super((int)(chain.getLengthOnDisk() / FatDirectoryEntry.SIZE),
+ chain.isReadOnly(), isRoot);
+
+ this.chain = chain;
+ }
+
+ public static ClusterChainDirectory readRoot(
+ ClusterChain chain) throws IOException {
+
+ final ClusterChainDirectory result =
+ new ClusterChainDirectory(chain, true);
+
+ result.read();
+ return result;
+ }
+
+ public static ClusterChainDirectory createRoot(Fat fat) throws IOException {
+
+ if (fat.getFatType() != FatType.FAT32) {
+ throw new IllegalArgumentException(
+ "only FAT32 stores root directory in a cluster chain");
+ }
+
+ final Fat32BootSector bs = (Fat32BootSector) fat.getBootSector();
+ final ClusterChain cc = new ClusterChain(fat, false);
+ cc.setChainLength(1);
+
+ bs.setRootDirFirstCluster(cc.getStartCluster());
+
+ final ClusterChainDirectory result =
+ new ClusterChainDirectory(cc, true);
+
+ result.flush();
+ return result;
+ }
+
+ @Override
+ protected final void read(ByteBuffer data) throws IOException {
+ this.chain.readData(0, data);
+ }
+
+ @Override
+ protected final void write(ByteBuffer data) throws IOException {
+ final int toWrite = data.remaining();
+ chain.writeData(0, data);
+ final long trueSize = chain.getLengthOnDisk();
+
+ /* TODO: check if the code below is really needed */
+ if (trueSize > toWrite) {
+ final int rest = (int) (trueSize - toWrite);
+ final ByteBuffer fill = ByteBuffer.allocate(rest);
+ chain.writeData(toWrite, fill);
+ }
+ }
+
+ /**
+ * Returns the first cluster of the chain that stores this directory for
+ * non-root instances or 0 if this is the root directory.
+ *
+ * @return the first storage cluster of this directory
+ * @see #isRoot()
+ */
+ @Override
+ protected final long getStorageCluster() {
+ return isRoot() ? 0 : chain.getStartCluster();
+ }
+
+ public final void delete() throws IOException {
+ chain.setChainLength(0);
+ }
+
+ @Override
+ protected final void changeSize(int entryCount)
+ throws IOException, IllegalArgumentException {
+
+ assert (entryCount >= 0);
+
+ final int size = entryCount * FatDirectoryEntry.SIZE;
+
+ if (size > MAX_SIZE) throw new DirectoryFullException(
+ "directory would grow beyond " + MAX_SIZE + " bytes",
+ getCapacity(), entryCount);
+
+ sizeChanged(chain.setSize(Math.max(size, chain.getClusterSize())));
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/DirectoryFullException.java b/src/main/java/de/waldheinz/fs/fat/DirectoryFullException.java
new file mode 100644
index 0000000..5f9770c
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/DirectoryFullException.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.io.IOException;
+
+/**
+ * Gets thrown when either
+ * <ul>
+ * <li>a {@link Fat16RootDirectory} becomes full or</li>
+ * <li>a {@link ClusterChainDirectory} grows beyond it's
+ * {@link ClusterChainDirectory#MAX_SIZE maximum size}
+ * </ul>
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public final class DirectoryFullException extends IOException {
+ private final static long serialVersionUID = 2;
+ private final int currentCapacity;
+ private final int requestedCapacity;
+
+ DirectoryFullException(int currentCapacity, int requestedCapacity) {
+ this("directory is full", currentCapacity, requestedCapacity);
+ }
+
+ DirectoryFullException(String message,
+ int currentCapacity, int requestedCapacity) {
+
+ super(message);
+
+ this.currentCapacity = currentCapacity;
+ this.requestedCapacity = requestedCapacity;
+ }
+
+ /**
+ * Returns the current capacity of the directory.
+ *
+ * @return the current capacity
+ */
+ public int getCurrentCapacity() {
+ return currentCapacity;
+ }
+
+ /**
+ * Returns the capacity the directory tried to grow, which did not succeed.
+ *
+ * @return the requested capacity
+ */
+ public int getRequestedCapacity() {
+ return requestedCapacity;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/DosUtils.java b/src/main/java/de/waldheinz/fs/fat/DosUtils.java
new file mode 100644
index 0000000..09e1ebb
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/DosUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.util.Calendar;
+
+/**
+ * This class contains some methods for date and time conversions between Java
+ * and the format known from DOS filesystems (e.g. fat)
+ *
+ * @author Ewout Prangsma &lt; epr at jnode.org&gt;
+ */
+final class DosUtils {
+
+ private DosUtils() { /* no instances */ }
+
+ /**
+ * Decode a 16-bit encoded DOS date/time into a java date/time.
+ *
+ * @param dosDate
+ * @param dosTime
+ * @return long
+ */
+ public static long decodeDateTime(int dosDate, int dosTime) {
+ final Calendar cal = Calendar.getInstance();
+
+ cal.set(Calendar.MILLISECOND, 0);
+ cal.set(Calendar.SECOND, (dosTime & 0x1f) * 2);
+ cal.set(Calendar.MINUTE, (dosTime >> 5) & 0x3f);
+ cal.set(Calendar.HOUR_OF_DAY, dosTime >> 11);
+
+ cal.set(Calendar.DATE, dosDate & 0x1f);
+ cal.set(Calendar.MONTH, ((dosDate >> 5) & 0x0f) - 1);
+ cal.set(Calendar.YEAR, 1980 + (dosDate >> 9));
+
+ return cal.getTimeInMillis();
+ }
+
+ /**
+ * Encode a java date/time into a 16-bit encoded DOS time
+ *
+ * @param javaDateTime
+ * @return long
+ */
+ public static int encodeTime(long javaDateTime) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(javaDateTime);
+ return 2048 * cal.get(Calendar.HOUR_OF_DAY) + 32 * cal.get(Calendar.MINUTE) +
+ cal.get(Calendar.SECOND) / 2;
+ }
+
+ /**
+ * Encode a java date/time into a 16-bit encoded DOS date
+ *
+ * @param javaDateTime
+ * @return long
+ */
+ public static int encodeDate(long javaDateTime) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(javaDateTime);
+ return 512 * (cal.get(Calendar.YEAR) - 1980) + 32 * (cal.get(Calendar.MONTH) + 1) +
+ cal.get(Calendar.DATE);
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/Dummy83BufferGenerator.java b/src/main/java/de/waldheinz/fs/fat/Dummy83BufferGenerator.java
new file mode 100644
index 0000000..d9848cc
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/Dummy83BufferGenerator.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.security.SecureRandom;
+
+/**
+ * Generates dummy 8.3 buffers that are associated with the long names.
+ *
+ * @author Daniel Galpin &lt;dgalpin@google.com&gt; based upon the work of
+ * Andrew Tridgell &lt;tridge@samba.org&gt;
+ */
+final class Dummy83BufferGenerator {
+ final SecureRandom mRandom;
+
+ /**
+ * Creates a new instance of {@code Dummy83BufferGenerator} that uses
+ * randomness only to avoid short name collisions.
+ */
+ public Dummy83BufferGenerator() {
+ mRandom = new SecureRandom();
+ }
+
+ /*
+ * Its in the DOS manual!(DOS 5: page 72) Valid: A..Z 0..9 _ ^ $ ~ ! # % & - {} () @ ' `
+ *
+ * Invalid: spaces/periods,
+ */
+ public static boolean validChar(char toTest) {
+ if (toTest >= 'A' && toTest <= 'Z') return true;
+ if (toTest >= 'a' && toTest <= 'z') return true;
+ if (toTest >= '0' && toTest <= '9') return true;
+ if (toTest == '_' || toTest == '^' || toTest == '$' || toTest == '~' ||
+ toTest == '!' || toTest == '#' || toTest == '%' || toTest == '&' ||
+ toTest == '-' || toTest == '{' || toTest == '}' || toTest == '(' ||
+ toTest == ')' || toTest == '@' || toTest == '\'' || toTest == '`')
+ return true;
+
+ return false;
+ }
+
+ public static boolean isSkipChar(char c) {
+ return (c == '.') || (c == ' ');
+ }
+
+ public static String tidyString(String dirty) {
+ final StringBuilder result = new StringBuilder();
+
+ /* epurate it from alien characters */
+ for (int src=0; src < dirty.length(); src++) {
+ final char toTest = Character.toUpperCase(dirty.charAt(src));
+ if (isSkipChar(toTest)) continue;
+
+ if (validChar(toTest)) {
+ result.append(toTest);
+ } else {
+ result.append('_');
+ }
+ }
+
+ return result.toString();
+ }
+
+ public static boolean cleanString(String s) {
+ for (int i=0; i < s.length(); i++) {
+ if (isSkipChar(s.charAt(i))) return false;
+ if (!validChar(s.charAt(i))) return false;
+ }
+
+ return true;
+ }
+
+ public static String stripLeadingPeriods(String str) {
+ final StringBuilder sb = new StringBuilder(str.length());
+
+ for (int i=0; i < str.length(); i++) {
+ if (str.charAt(i) != '.') { //NOI18N
+ sb.append(str.substring(i));
+ break;
+ }
+ }
+
+ return sb.toString();
+ } /*
+ * These characters are all invalid in 8.3 names, plus have been shown to be
+ * harmless on all tested devices
+ */
+ static final private char[] invalidchar = {
+ (char) 0x01, (char) 0x02, (char) 0x03, (char) 0x04, (char) 0x05, (char) 0x06,
+ (char) 0x0B,
+ (char) 0x0C, (char) 0x0E, (char) 0x0F, (char) 0x10, (char) 0x11, (char) 0x12,
+ (char) 0x13,
+ (char) 0x14, (char) 0x15, (char) 0x16, (char) 0x17, (char) 0x18, (char) 0x19,
+ (char) 0x1A,
+ (char) 0x1B, (char) 0x1C, (char) 0x1D, (char) 0x1E, (char) 0x1F, (char) 0x22,
+ (char) 0x2a,
+ (char) 0x3a, (char) 0x3c, (char) 0x3e, (char) 0x3f, (char) 0x5b, (char) 0x5d,
+ (char) 0x7c
+ };
+
+ /**
+ * See original C Linux patch by Andrew Tridgell &lt;tridge@samba.org&gt;
+ * build a 11 byte 8.3 buffer which is not a short filename. We want 11
+ * bytes which: - will be seen as a constant string to all APIs on Linux and
+ * Windows - cannot be matched with wildcard patterns - cannot be used to
+ * access the file - has a low probability of collision within a directory -
+ * has an invalid 3 byte extension - contains at least one non-space and
+ * non-nul byte
+ *
+ * @param longFullName the long file name to generate the buffer for
+ * @return the generated 8.3 buffer
+ */
+ public ShortName generate83BufferNew(String longFullName)
+ throws IllegalStateException {
+
+ char[] retBuffer = new char[11];
+
+ boolean hasRealShortName = false;// getRealShortNameInstead(longFullName,
+ // retBuffer);
+ if (!hasRealShortName) {
+ int i, tilde_pos, slash_pos;
+ int randomNumber = Math.abs(mRandom.nextInt());
+
+ /*
+ * the '/' makes sure that even unpatched Linux systems can't get at
+ * files by the 8.3 entry.
+ */
+
+ slash_pos = randomNumber % 8;
+ randomNumber >>= 3;
+
+ /*
+ * fill in the first 8 bytes with invalid characters. Note that we
+ * need to be careful not to run out of randomness. We use the same
+ * extension for all buffers.
+ */
+ for (i = 0; i < 8; i++) {
+ if (i == slash_pos)
+ retBuffer[i] = '/';
+ else {
+ retBuffer[i] =
+ invalidchar[randomNumber % invalidchar.length];
+ randomNumber /= invalidchar.length;
+ if (randomNumber < invalidchar.length)
+ randomNumber = Math.abs(mRandom.nextInt());
+ }
+ }
+
+ for ( i = 0; i < 8; i ++ ) {
+ if (retBuffer[i] == 0xe5) {
+ throw new RuntimeException();
+ }
+ }
+
+ retBuffer[8] = 'i';
+ retBuffer[9] = 'f';
+ retBuffer[10] = 'l';
+ }
+ ShortName retName = new ShortName(retBuffer);
+ retName.setHasShortNameOnly(hasRealShortName);
+ return retName;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/Fat.java b/src/main/java/de/waldheinz/fs/fat/Fat.java
new file mode 100644
index 0000000..3f3ab43
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/Fat.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ *
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public final class Fat {
+
+ /**
+ * The first cluster that really holds user data in a FAT.
+ */
+ public final static int FIRST_CLUSTER = 2;
+
+ private final long[] entries;
+ private final FatType fatType;
+ private final int sectorCount;
+ private final int sectorSize;
+ private final BlockDevice device;
+ private final BootSector bs;
+ private final long offset;
+ private final int lastClusterIndex;
+
+ private int lastAllocatedCluster;
+
+ /**
+ * Reads a {@code Fat} as specified by a {@code BootSector}.
+ *
+ * @param bs the boot sector specifying the {@code Fat} layout
+ * @param fatNr the number of the {@code Fat} to read
+ * @return the {@code Fat} that was read
+ * @throws IOException on read error
+ * @throws IllegalArgumentException if {@code fatNr} is greater than
+ * {@link BootSector#getNrFats()}
+ */
+ public static Fat read(BootSector bs, int fatNr)
+ throws IOException, IllegalArgumentException {
+
+ if (fatNr > bs.getNrFats()) {
+ throw new IllegalArgumentException(
+ "boot sector says there are only " + bs.getNrFats() +
+ " FATs when reading FAT #" + fatNr);
+ }
+
+ final long fatOffset = FatUtils.getFatOffset(bs, fatNr);
+ final Fat result = new Fat(bs, fatOffset);
+ result.read();
+ return result;
+ }
+
+ /**
+ * Creates a new {@code Fat} as specified by a {@code BootSector}.
+ *
+ * @param bs the boot sector specifying the {@code Fat} layout
+ * @param fatNr the number of the {@code Fat} to create
+ * @return the {@code Fat} that was created
+ * @throws IOException on write error
+ * @throws IllegalArgumentException if {@code fatNr} is greater than
+ * {@link BootSector#getNrFats()}
+ */
+ public static Fat create(BootSector bs, int fatNr)
+ throws IOException, IllegalArgumentException {
+
+ if (fatNr > bs.getNrFats()) {
+ throw new IllegalArgumentException(
+ "boot sector says there are only " + bs.getNrFats() +
+ " FATs when creating FAT #" + fatNr);
+ }
+
+ final long fatOffset = FatUtils.getFatOffset(bs, fatNr);
+ final Fat result = new Fat(bs, fatOffset);
+
+ if (bs.getDataClusterCount() > result.entries.length)
+ throw new IOException("FAT too small for device");
+
+ result.init(bs.getMediumDescriptor());
+ result.write();
+ return result;
+ }
+
+ private Fat(BootSector bs, long offset) throws IOException {
+ this.bs = bs;
+ this.fatType = bs.getFatType();
+ if (bs.getSectorsPerFat() > Integer.MAX_VALUE)
+ throw new IllegalArgumentException("FAT too large");
+
+ if (bs.getSectorsPerFat() <= 0) throw new IOException(
+ "boot sector says there are " + bs.getSectorsPerFat() +
+ " sectors per FAT");
+
+ if (bs.getBytesPerSector() <= 0) throw new IOException(
+ "boot sector says there are " + bs.getBytesPerSector() +
+ " bytes per sector");
+
+ this.sectorCount = (int) bs.getSectorsPerFat();
+ this.sectorSize = bs.getBytesPerSector();
+ this.device = bs.getDevice();
+ this.offset = offset;
+ this.lastAllocatedCluster = FIRST_CLUSTER;
+
+ if (bs.getDataClusterCount() > Integer.MAX_VALUE) throw
+ new IOException("too many data clusters");
+
+ if (bs.getDataClusterCount() == 0) throw
+ new IOException("no data clusters");
+
+ this.lastClusterIndex = (int) bs.getDataClusterCount() + FIRST_CLUSTER;
+
+ entries = new long[(int) ((sectorCount * sectorSize) /
+ fatType.getEntrySize())];
+
+ if (lastClusterIndex > entries.length) throw new IOException(
+ "file system has " + lastClusterIndex +
+ "clusters but only " + entries.length + " FAT entries");
+ }
+
+ public FatType getFatType() {
+ return fatType;
+ }
+
+ /**
+ * Returns the {@code BootSector} that specifies this {@code Fat}.
+ *
+ * @return this {@code Fat}'s {@code BootSector}
+ */
+ public BootSector getBootSector() {
+ return this.bs;
+ }
+
+ /**
+ * Returns the {@code BlockDevice} where this {@code Fat} is stored.
+ *
+ * @return the device holding this FAT
+ */
+ public BlockDevice getDevice() {
+ return device;
+ }
+
+ private void init(int mediumDescriptor) {
+ entries[0] =
+ (mediumDescriptor & 0xFF) |
+ (0xFFFFF00L & fatType.getBitMask());
+ entries[1] = fatType.getEofMarker();
+ }
+
+ /**
+ * Read the contents of this FAT from the given device at the given offset.
+ *
+ * @param offset the byte offset where to read the FAT from the device
+ * @throws IOException on read error
+ */
+ private void read() throws IOException {
+ final byte[] data = new byte[sectorCount * sectorSize];
+ device.read(offset, ByteBuffer.wrap(data));
+
+ for (int i = 0; i < entries.length; i++)
+ entries[i] = fatType.readEntry(data, i);
+ }
+
+ public void write() throws IOException {
+ this.writeCopy(offset);
+ }
+
+ /**
+ * Write the contents of this FAT to the given device at the given offset.
+ *
+ * @param offset the device offset where to write the FAT copy
+ * @throws IOException on write error
+ */
+ public void writeCopy(long offset) throws IOException {
+ final byte[] data = new byte[sectorCount * sectorSize];
+
+ for (int index = 0; index < entries.length; index++) {
+ fatType.writeEntry(data, index, entries[index]);
+ }
+
+ device.write(offset, ByteBuffer.wrap(data));
+ }
+
+ /**
+ * Gets the medium descriptor byte
+ *
+ * @return int
+ */
+ public int getMediumDescriptor() {
+ return (int) (entries[0] & 0xFF);
+ }
+
+ /**
+ * Gets the entry at a given offset
+ *
+ * @param index
+ * @return long
+ */
+ public long getEntry(int index) {
+ return entries[index];
+ }
+
+ /**
+ * Returns the last free cluster that was accessed in this FAT.
+ *
+ * @return the last seen free cluster
+ */
+ public int getLastFreeCluster() {
+ return this.lastAllocatedCluster;
+ }
+
+ public long[] getChain(long startCluster) {
+ testCluster(startCluster);
+ // Count the chain first
+ int count = 1;
+ long cluster = startCluster;
+ while (!isEofCluster(entries[(int) cluster])) {
+ count++;
+ cluster = entries[(int) cluster];
+ }
+ // Now create the chain
+ long[] chain = new long[count];
+ chain[0] = startCluster;
+ cluster = startCluster;
+ int i = 0;
+ while (!isEofCluster(entries[(int) cluster])) {
+ cluster = entries[(int) cluster];
+ chain[++i] = cluster;
+ }
+ return chain;
+ }
+
+ /**
+ * Gets the cluster after the given cluster
+ *
+ * @param cluster
+ * @return long The next cluster number or -1 which means eof.
+ */
+ public long getNextCluster(long cluster) {
+ testCluster(cluster);
+ long entry = entries[(int) cluster];
+ if (isEofCluster(entry)) {
+ return -1;
+ } else {
+ return entry;
+ }
+ }
+
+ /**
+ * Allocate a cluster for a new file
+ *
+ * @return long the number of the newly allocated cluster
+ * @throws IOException if there are no free clusters
+ */
+ public long allocNew() throws IOException {
+
+ int i;
+ int entryIndex = -1;
+
+ for (i = lastAllocatedCluster; i < lastClusterIndex; i++) {
+ if (isFreeCluster(i)) {
+ entryIndex = i;
+ break;
+ }
+ }
+
+ if (entryIndex < 0) {
+ for (i = FIRST_CLUSTER; i < lastAllocatedCluster; i++) {
+ if (isFreeCluster(i)) {
+ entryIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (entryIndex < 0) {
+ throw new IOException(
+ "FAT Full (" + (lastClusterIndex - FIRST_CLUSTER)
+ + ", " + i + ")"); //NOI18N
+ }
+
+ entries[entryIndex] = fatType.getEofMarker();
+ lastAllocatedCluster = entryIndex % lastClusterIndex;
+ if (lastAllocatedCluster < FIRST_CLUSTER)
+ lastAllocatedCluster = FIRST_CLUSTER;
+
+ return entryIndex;
+ }
+
+ /**
+ * Returns the number of clusters that are currently not in use by this FAT.
+ * This estimate does only account for clusters that are really available in
+ * the data portion of the file system, not for clusters that might only
+ * theoretically be stored in the {@code Fat}.
+ *
+ * @return the free cluster count
+ * @see FsInfoSector#setFreeClusterCount(long)
+ * @see FsInfoSector#getFreeClusterCount()
+ * @see BootSector#getDataClusterCount()
+ */
+ public int getFreeClusterCount() {
+ int result = 0;
+
+ for (int i=FIRST_CLUSTER; i < lastClusterIndex; i++) {
+ if (isFreeCluster(i)) result++;
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the cluster number that was last allocated in this fat.
+ *
+ * @return
+ */
+ public int getLastAllocatedCluster() {
+ return this.lastAllocatedCluster;
+ }
+
+ /**
+ * Allocate a series of clusters for a new file.
+ *
+ * @param nrClusters when number of clusters to allocate
+ * @return long
+ * @throws IOException if there are no free clusters
+ */
+ public long[] allocNew(int nrClusters) throws IOException {
+ final long rc[] = new long[nrClusters];
+
+ rc[0] = allocNew();
+ for (int i = 1; i < nrClusters; i++) {
+ rc[i] = allocAppend(rc[i - 1]);
+ }
+
+ return rc;
+ }
+
+ /**
+ * Allocate a cluster to append to a new file
+ *
+ * @param cluster a cluster from a chain where the new cluster should be
+ * appended
+ * @return long the newly allocated and appended cluster number
+ * @throws IOException if there are no free clusters
+ */
+ public long allocAppend(long cluster)
+ throws IOException {
+
+ testCluster(cluster);
+
+ while (!isEofCluster(entries[(int) cluster])) {
+ cluster = entries[(int) cluster];
+ }
+
+ long newCluster = allocNew();
+ entries[(int) cluster] = newCluster;
+
+ return newCluster;
+ }
+
+ public void setEof(long cluster) {
+ testCluster(cluster);
+ entries[(int) cluster] = fatType.getEofMarker();
+ }
+
+ public void setFree(long cluster) {
+ testCluster(cluster);
+ entries[(int) cluster] = 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Fat)) return false;
+
+ final Fat other = (Fat) obj;
+ if (this.fatType != other.fatType) return false;
+ if (this.sectorCount != other.sectorCount) return false;
+ if (this.sectorSize != other.sectorSize) return false;
+ if (this.lastClusterIndex != other.lastClusterIndex) return false;
+ if (!Arrays.equals(this.entries, other.entries)) return false;
+ if (this.getMediumDescriptor() != other.getMediumDescriptor())
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 23 * hash + Arrays.hashCode(this.entries);
+ hash = 23 * hash + this.fatType.hashCode();
+ hash = 23 * hash + this.sectorCount;
+ hash = 23 * hash + this.sectorSize;
+ hash = 23 * hash + this.lastClusterIndex;
+ return hash;
+ }
+
+ /**
+ * Is the given entry a free cluster?
+ *
+ * @param entry
+ * @return boolean
+ */
+ protected boolean isFreeCluster(long entry) {
+ if (entry > Integer.MAX_VALUE) throw new IllegalArgumentException();
+ return (entries[(int) entry] == 0);
+ }
+
+ /**
+ * Is the given entry a reserved cluster?
+ *
+ * @param entry
+ * @return boolean
+ */
+ protected boolean isReservedCluster(long entry) {
+ return fatType.isReservedCluster(entry);
+ }
+
+ /**
+ * Is the given entry an EOF marker
+ *
+ * @param entry
+ * @return boolean
+ */
+ protected boolean isEofCluster(long entry) {
+ return fatType.isEofCluster(entry);
+ }
+
+ protected void testCluster(long cluster) throws IllegalArgumentException {
+ if ((cluster < FIRST_CLUSTER) || (cluster >= entries.length)) {
+ throw new IllegalArgumentException(
+ "invalid cluster value " + cluster);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append(this.getClass().getSimpleName());
+ sb.append("[type=");
+ sb.append(fatType);
+ sb.append(", mediumDescriptor=0x");
+ sb.append(Integer.toHexString(getMediumDescriptor()));
+ sb.append(", sectorCount=");
+ sb.append(sectorCount);
+ sb.append(", sectorSize=");
+ sb.append(sectorSize);
+ sb.append(", freeClusters=");
+ sb.append(getFreeClusterCount());
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/Fat16BootSector.java b/src/main/java/de/waldheinz/fs/fat/Fat16BootSector.java
new file mode 100644
index 0000000..38bdc2e
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/Fat16BootSector.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+
+/**
+ * The boot sector layout as used by the FAT12 / FAT16 variants.
+ *
+ * @author Matthias Treydte &lt;matthias.treydte at meetwise.com&gt;
+ */
+final class Fat16BootSector extends BootSector {
+
+ /**
+ * The default number of entries for the root directory.
+ *
+ * @see #getRootDirEntryCount()
+ * @see #setRootDirEntryCount(int)
+ */
+ public static final int DEFAULT_ROOT_DIR_ENTRY_COUNT = 512;
+
+ /**
+ * The default volume label.
+ */
+ public static final String DEFAULT_VOLUME_LABEL = "NO NAME"; //NOI18N
+
+ /**
+ * The maximum number of clusters for a FAT12 file system. This is actually
+ * the number of clusters where mkdosfs stop complaining about a FAT16
+ * partition having not enough sectors, so it would be misinterpreted
+ * as FAT12 without special handling.
+ *
+ * @see #getNrLogicalSectors()
+ */
+ public static final int MAX_FAT12_CLUSTERS = 4084;
+
+ public static final int MAX_FAT16_CLUSTERS = 65524;
+
+ /**
+ * The offset to the sectors per FAT value.
+ */
+ public static final int SECTORS_PER_FAT_OFFSET = 0x16;
+
+ /**
+ * The offset to the root directory entry count value.
+ *
+ * @see #getRootDirEntryCount()
+ * @see #setRootDirEntryCount(int)
+ */
+ public static final int ROOT_DIR_ENTRIES_OFFSET = 0x11;
+
+ /**
+ * The offset to the first byte of the volume label.
+ */
+ public static final int VOLUME_LABEL_OFFSET = 0x2b;
+
+ /**
+ * Offset to the FAT file system type string.
+ *
+ * @see #getFileSystemType()
+ */
+ public static final int FILE_SYSTEM_TYPE_OFFSET = 0x36;
+
+ /**
+ * The maximum length of the volume label.
+ */
+ public static final int MAX_VOLUME_LABEL_LENGTH = 11;
+
+ public static final int EXTENDED_BOOT_SIGNATURE_OFFSET = 0x26;
+
+ /**
+ * Creates a new {@code Fat16BootSector} for the specified device.
+ *
+ * @param device the {@code BlockDevice} holding the boot sector
+ */
+ public Fat16BootSector(BlockDevice device) {
+ super(device);
+ }
+
+ /**
+ * Returns the volume label that is stored in this boot sector.
+ *
+ * @return the volume label
+ */
+ public String getVolumeLabel() {
+ final StringBuilder sb = new StringBuilder();
+
+ for (int i=0; i < MAX_VOLUME_LABEL_LENGTH; i++) {
+ final char c = (char) get8(VOLUME_LABEL_OFFSET + i);
+
+ if (c != 0) {
+ sb.append(c);
+ } else {
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Sets the volume label that is stored in this boot sector.
+ *
+ * @param label the new volume label
+ * @throws IllegalArgumentException if the specified label is longer
+ * than {@link #MAX_VOLUME_LABEL_LENGTH}
+ */
+ public void setVolumeLabel(String label) throws IllegalArgumentException {
+ if (label.length() > MAX_VOLUME_LABEL_LENGTH)
+ throw new IllegalArgumentException("volume label too long");
+
+ for (int i = 0; i < MAX_VOLUME_LABEL_LENGTH; i++) {
+ set8(VOLUME_LABEL_OFFSET + i,
+ i < label.length() ? label.charAt(i) : 0);
+ }
+ }
+
+ /**
+ * Gets the number of sectors/fat for FAT 12/16.
+ *
+ * @return int
+ */
+ @Override
+ public long getSectorsPerFat() {
+ return get16(SECTORS_PER_FAT_OFFSET);
+ }
+
+ /**
+ * Sets the number of sectors/fat
+ *
+ * @param v the new number of sectors per fat
+ */
+ @Override
+ public void setSectorsPerFat(long v) {
+ if (v == getSectorsPerFat()) return;
+ if (v > 0x7FFF) throw new IllegalArgumentException(
+ "too many sectors for a FAT12/16");
+
+ set16(SECTORS_PER_FAT_OFFSET, (int)v);
+ }
+
+ @Override
+ public FatType getFatType() {
+ final long rootDirSectors = ((getRootDirEntryCount() * 32) +
+ (getBytesPerSector() - 1)) / getBytesPerSector();
+ final long dataSectors = getSectorCount() -
+ (getNrReservedSectors() + (getNrFats() * getSectorsPerFat()) +
+ rootDirSectors);
+ final long clusterCount = dataSectors / getSectorsPerCluster();
+
+ if (clusterCount > MAX_FAT16_CLUSTERS) throw new IllegalStateException(
+ "too many clusters for FAT12/16: " + clusterCount);
+
+ return clusterCount > MAX_FAT12_CLUSTERS ?
+ FatType.FAT16 : FatType.FAT12;
+ }
+
+ @Override
+ public void setSectorCount(long count) {
+ if (count > 65535) {
+ setNrLogicalSectors(0);
+ setNrTotalSectors(count);
+ } else {
+ setNrLogicalSectors((int) count);
+ setNrTotalSectors(count);
+ }
+ }
+
+ @Override
+ public long getSectorCount() {
+ if (getNrLogicalSectors() == 0) return getNrTotalSectors();
+ else return getNrLogicalSectors();
+ }
+
+ /**
+ * Gets the number of entries in the root directory.
+ *
+ * @return int the root directory entry count
+ */
+ @Override
+ public int getRootDirEntryCount() {
+ return get16(ROOT_DIR_ENTRIES_OFFSET);
+ }
+
+ /**
+ * Sets the number of entries in the root directory
+ *
+ * @param v the new number of entries in the root directory
+ * @throws IllegalArgumentException for negative values
+ */
+ public void setRootDirEntryCount(int v) throws IllegalArgumentException {
+ if (v < 0) throw new IllegalArgumentException();
+ if (v == getRootDirEntryCount()) return;
+
+ set16(ROOT_DIR_ENTRIES_OFFSET, v);
+ }
+
+ @Override
+ public void init() throws IOException {
+ super.init();
+
+ setRootDirEntryCount(DEFAULT_ROOT_DIR_ENTRY_COUNT);
+ setVolumeLabel(DEFAULT_VOLUME_LABEL);
+ }
+
+ @Override
+ public int getFileSystemTypeLabelOffset() {
+ return FILE_SYSTEM_TYPE_OFFSET;
+ }
+
+ @Override
+ public int getExtendedBootSignatureOffset() {
+ return EXTENDED_BOOT_SIGNATURE_OFFSET;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/Fat16RootDirectory.java b/src/main/java/de/waldheinz/fs/fat/Fat16RootDirectory.java
new file mode 100644
index 0000000..958e8a0
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/Fat16RootDirectory.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * The root directory of a FAT12/16 partition.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+final class Fat16RootDirectory extends AbstractDirectory {
+ private final BlockDevice device;
+ private final long deviceOffset;
+
+ private Fat16RootDirectory(Fat16BootSector bs, boolean readOnly) {
+ super(bs.getRootDirEntryCount(), readOnly, true);
+
+ if (bs.getRootDirEntryCount() <= 0) throw new IllegalArgumentException(
+ "root directory size is " + bs.getRootDirEntryCount());
+
+ this.deviceOffset = FatUtils.getRootDirOffset(bs);
+ this.device = bs.getDevice();
+ }
+
+ /**
+ * Reads a {@code Fat16RootDirectory} as indicated by the specified
+ * {@code Fat16BootSector}.
+ *
+ * @param bs the boot sector that describes the root directory to read
+ * @param readOnly if the directory shold be created read-only
+ * @return the directory that was read
+ * @throws IOException on read error
+ */
+ public static Fat16RootDirectory read(
+ Fat16BootSector bs, boolean readOnly) throws IOException {
+
+ final Fat16RootDirectory result = new Fat16RootDirectory(bs, readOnly);
+ result.read();
+ return result;
+ }
+
+ /**
+ * Creates a new {@code Fat16RootDirectory} as indicated by the specified
+ * {@code Fat16BootSector}. The directory will always be created in
+ * read-write mode.
+ *
+ * @param bs the boot sector that describes the root directory to create
+ * @return the directory that was created
+ * @throws IOException on write error
+ */
+ public static Fat16RootDirectory create(
+ Fat16BootSector bs) throws IOException {
+
+ final Fat16RootDirectory result = new Fat16RootDirectory(bs, false);
+ result.flush();
+ return result;
+ }
+
+ @Override
+ protected void read(ByteBuffer data) throws IOException {
+ this.device.read(deviceOffset, data);
+ }
+
+ @Override
+ protected void write(ByteBuffer data) throws IOException {
+ this.device.write(deviceOffset, data);
+ }
+
+ /**
+ * By convention always returns 0, as the FAT12/16 root directory is not
+ * stored in a cluster chain.
+ *
+ * @return always 0
+ */
+ @Override
+ protected long getStorageCluster() {
+ return 0;
+ }
+
+ /**
+ * As a FAT12/16 root directory can not change it's size, this method
+ * throws a {@code DirectoryFullException} if the requested size is
+ * larger than {@link #getCapacity()} and does nothing else.
+ *
+ * @param entryCount {@inheritDoc}
+ */
+ @Override
+ protected void changeSize(int entryCount) throws DirectoryFullException {
+ if (getCapacity() < entryCount) {
+ throw new DirectoryFullException(getCapacity(), entryCount);
+ }
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/Fat32BootSector.java b/src/main/java/de/waldheinz/fs/fat/Fat32BootSector.java
new file mode 100644
index 0000000..eded7ee
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/Fat32BootSector.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+
+/**
+ * Contains the FAT32 specific parts of the boot sector.
+ *
+ * @author Matthias Treydte &lt;matthias.treydte at meetwise.com&gt;
+ */
+final class Fat32BootSector extends BootSector {
+
+ /**
+ * The offset to the entry specifying the first cluster of the FAT32
+ * root directory.
+ */
+ public final static int ROOT_DIR_FIRST_CLUSTER_OFFSET = 0x2c;
+
+ /**
+ * The offset to the 4 bytes specifying the sectors per FAT value.
+ */
+ public static final int SECTORS_PER_FAT_OFFSET = 0x24;
+
+ /**
+ * Offset to the file system type label.
+ */
+ public static final int FILE_SYSTEM_TYPE_OFFSET = 0x52;
+
+ public static final int VERSION_OFFSET = 0x2a;
+ public static final int VERSION = 0;
+
+ public static final int FS_INFO_SECTOR_OFFSET = 0x30;
+ public static final int BOOT_SECTOR_COPY_OFFSET = 0x32;
+ public static final int EXTENDED_BOOT_SIGNATURE_OFFSET = 0x42;
+
+ /*
+ * TODO: make this constructor private
+ */
+ public Fat32BootSector(BlockDevice device) throws IOException {
+ super(device);
+ }
+
+ @Override
+ public void init() throws IOException {
+ super.init();
+
+ set16(VERSION_OFFSET, VERSION);
+
+ setBootSectorCopySector(6); /* as suggested by M$ */
+ }
+
+ /**
+ * Returns the first cluster in the FAT that contains the root directory.
+ *
+ * @return the root directory's first cluster
+ */
+ public long getRootDirFirstCluster() {
+ return get32(ROOT_DIR_FIRST_CLUSTER_OFFSET);
+ }
+
+ /**
+ * Sets the first cluster of the root directory.
+ *
+ * @param value the root directory's first cluster
+ */
+ public void setRootDirFirstCluster(long value) {
+ if (getRootDirFirstCluster() == value) return;
+
+ set32(ROOT_DIR_FIRST_CLUSTER_OFFSET, value);
+ }
+
+ /**
+ * Sets the sectur number that contains a copy of the boot sector.
+ *
+ * @param sectNr the sector that contains a boot sector copy
+ */
+ public void setBootSectorCopySector(int sectNr) {
+ if (getBootSectorCopySector() == sectNr) return;
+ if (sectNr < 0) throw new IllegalArgumentException(
+ "boot sector copy sector must be >= 0");
+
+ set16(BOOT_SECTOR_COPY_OFFSET, sectNr);
+ }
+
+ /**
+ * Returns the sector that contains a copy of the boot sector, or 0 if
+ * there is no copy.
+ *
+ * @return the sector number of the boot sector copy
+ */
+ public int getBootSectorCopySector() {
+ return get16(BOOT_SECTOR_COPY_OFFSET);
+ }
+
+ /**
+ * Sets the 11-byte volume label stored at offset 0x47.
+ *
+ * @param label the new volume label, may be {@code null}
+ */
+ public void setVolumeLabel(String label) {
+ for (int i=0; i < 11; i++) {
+ final byte c =
+ (label == null) ? 0 :
+ (label.length() > i) ? (byte) label.charAt(i) : 0x20;
+
+ set8(0x47 + i, c);
+ }
+ }
+
+ public int getFsInfoSectorNr() {
+ return get16(FS_INFO_SECTOR_OFFSET);
+ }
+
+ public void setFsInfoSectorNr(int offset) {
+ if (getFsInfoSectorNr() == offset) return;
+
+ set16(FS_INFO_SECTOR_OFFSET, offset);
+ }
+
+ @Override
+ public void setSectorsPerFat(long v) {
+ if (getSectorsPerFat() == v) return;
+
+ set32(SECTORS_PER_FAT_OFFSET, v);
+ }
+
+ @Override
+ public long getSectorsPerFat() {
+ return get32(SECTORS_PER_FAT_OFFSET);
+ }
+
+ @Override
+ public FatType getFatType() {
+ return FatType.FAT32;
+ }
+
+ @Override
+ public void setSectorCount(long count) {
+ super.setNrTotalSectors(count);
+ }
+
+ @Override
+ public long getSectorCount() {
+ return super.getNrTotalSectors();
+ }
+
+ /**
+ * This is always 0 for FAT32.
+ *
+ * @return always 0
+ */
+ @Override
+ public int getRootDirEntryCount() {
+ return 0;
+ }
+
+ public void setFileSystemId(int id) {
+ super.set32(0x43, id);
+ }
+
+ public int getFileSystemId() {
+ return (int) super.get32(0x43);
+ }
+
+ /**
+ * Writes a copy of this boot sector to the specified device, if a copy
+ * is requested.
+ *
+ * @param device the device to write the boot sector copy to
+ * @throws IOException on write error
+ * @see #getBootSectorCopySector()
+ */
+ public void writeCopy(BlockDevice device) throws IOException {
+ if (getBootSectorCopySector() > 0) {
+ final long offset = getBootSectorCopySector() * SIZE;
+ buffer.rewind();
+ buffer.limit(buffer.capacity());
+ device.write(offset, buffer);
+ }
+ }
+
+ @Override
+ public int getFileSystemTypeLabelOffset() {
+ return FILE_SYSTEM_TYPE_OFFSET;
+ }
+
+ @Override
+ public int getExtendedBootSignatureOffset() {
+ return EXTENDED_BOOT_SIGNATURE_OFFSET;
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatDirectoryEntry.java b/src/main/java/de/waldheinz/fs/fat/FatDirectoryEntry.java
new file mode 100644
index 0000000..b7a3611
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatDirectoryEntry.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.AbstractFsObject;
+import java.nio.ByteBuffer;
+
+/**
+ *
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+final class FatDirectoryEntry extends AbstractFsObject {
+
+ /**
+ * The size in bytes of an FAT directory entry.
+ */
+ public final static int SIZE = 32;
+
+ /**
+ * The offset to the attributes byte.
+ */
+ private static final int OFFSET_ATTRIBUTES = 0x0b;
+
+ /**
+ * The offset to the file size dword.
+ */
+ private static final int OFFSET_FILE_SIZE = 0x1c;
+
+ private static final int F_READONLY = 0x01;
+ private static final int F_HIDDEN = 0x02;
+ private static final int F_SYSTEM = 0x04;
+ private static final int F_VOLUME_ID = 0x08;
+ private static final int F_DIRECTORY = 0x10;
+ private static final int F_ARCHIVE = 0x20;
+
+ private static final int MAX_CLUSTER = 0xFFFF;
+
+ /**
+ * The magic byte denoting that this entry was deleted and is free
+ * for reuse.
+ *
+ * @see #isDeleted()
+ */
+ public static final int ENTRY_DELETED_MAGIC = 0xe5;
+
+ private final byte[] data;
+ private boolean dirty;
+ boolean hasShortNameOnly;
+
+ FatDirectoryEntry(byte[] data, boolean readOnly) {
+ super(readOnly);
+
+ this.data = data;
+ }
+
+ private FatDirectoryEntry() {
+ this(new byte[SIZE], false);
+
+ }
+
+ /**
+ * Reads a {@code FatDirectoryEntry} from the specified {@code ByteBuffer}.
+ * The buffer must have at least {@link #SIZE} bytes remaining. The entry
+ * is read from the buffer's current position, and if this method returns
+ * non-null the position will have advanced by {@link #SIZE} bytes,
+ * otherwise the position will remain unchanged.
+ *
+ * @param buff the buffer to read the entry from
+ * @param readOnly if the resulting {@code FatDirecoryEntry} should be
+ * read-only
+ * @return the directory entry that was read from the buffer or {@code null}
+ * if there was no entry to read from the specified position (first
+ * byte was 0)
+ */
+ public static FatDirectoryEntry read(ByteBuffer buff, boolean readOnly) {
+ assert (buff.remaining() >= SIZE);
+
+ /* peek into the buffer to see if we're done with reading */
+
+ if (buff.get(buff.position()) == 0) return null;
+
+ /* read the directory entry */
+
+ final byte[] data = new byte[SIZE];
+ buff.get(data);
+ return new FatDirectoryEntry(data, readOnly);
+ }
+
+ public static void writeNullEntry(ByteBuffer buff) {
+ for (int i=0; i < SIZE; i++) {
+ buff.put((byte) 0);
+ }
+ }
+
+ /**
+ * Decides if this entry is a "volume label" entry according to the FAT
+ * specification.
+ *
+ * @return if this is a volume label entry
+ */
+ public boolean isVolumeLabel() {
+ if (isLfnEntry()) return false;
+ else return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == F_VOLUME_ID);
+ }
+
+ private void setFlag(int mask, boolean set) {
+ final int oldFlags = getFlags();
+
+ if (((oldFlags & mask) != 0) == set) return;
+
+ if (set) {
+ setFlags(oldFlags | mask);
+ } else {
+ setFlags(oldFlags & ~mask);
+ }
+
+ this.dirty = true;
+ }
+
+ public boolean isSystemFlag() {
+ return ((getFlags() & F_SYSTEM) != 0);
+ }
+
+ public void setSystemFlag(boolean isSystem) {
+ setFlag(F_SYSTEM, isSystem);
+ }
+
+ public boolean isArchiveFlag() {
+ return ((getFlags() & F_ARCHIVE) != 0);
+ }
+
+ public void setArchiveFlag(boolean isArchive) {
+ setFlag(F_ARCHIVE, isArchive);
+ }
+
+ public boolean isHiddenFlag() {
+ return ((getFlags() & F_HIDDEN) != 0);
+ }
+
+ public void setHiddenFlag(boolean isHidden) {
+ setFlag(F_HIDDEN, isHidden);
+ }
+
+ public boolean isVolumeIdFlag() {
+ return ((getFlags() & F_VOLUME_ID) != 0);
+ }
+
+ public boolean isLfnEntry() {
+ return isReadonlyFlag() && isSystemFlag() &&
+ isHiddenFlag() && isVolumeIdFlag();
+ }
+
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ private int getFlags() {
+ return LittleEndian.getUInt8(data, OFFSET_ATTRIBUTES);
+ }
+
+ private void setFlags(int flags) {
+ LittleEndian.setInt8(data, OFFSET_ATTRIBUTES, flags);
+ }
+
+ public boolean isDirectory() {
+ return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == F_DIRECTORY);
+ }
+
+ public static FatDirectoryEntry create(boolean directory) {
+ final FatDirectoryEntry result = new FatDirectoryEntry();
+
+ if (directory) {
+ result.setFlags(F_DIRECTORY);
+ }
+
+ /* initialize date and time fields */
+
+ final long now = System.currentTimeMillis();
+ result.setCreated(now);
+ result.setLastAccessed(now);
+ result.setLastModified(now);
+
+ return result;
+ }
+
+ public static FatDirectoryEntry createVolumeLabel(String volumeLabel) {
+ assert(volumeLabel != null);
+
+ final byte[] data = new byte[SIZE];
+
+ System.arraycopy(
+ volumeLabel.getBytes(), 0,
+ data, 0,
+ volumeLabel.length());
+
+ final FatDirectoryEntry result = new FatDirectoryEntry(data, false);
+ result.setFlags(FatDirectoryEntry.F_VOLUME_ID);
+ return result;
+ }
+
+ public String getVolumeLabel() {
+ if (!isVolumeLabel())
+ throw new UnsupportedOperationException("not a volume label");
+
+ final StringBuilder sb = new StringBuilder();
+
+ for (int i=0; i < AbstractDirectory.MAX_LABEL_LENGTH; i++) {
+ final byte b = this.data[i];
+
+ if (b != 0) {
+ sb.append((char) b);
+ } else {
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public long getCreated() {
+ return DosUtils.decodeDateTime(
+ LittleEndian.getUInt16(data, 0x10),
+ LittleEndian.getUInt16(data, 0x0e));
+ }
+
+ public void setCreated(long created) {
+ LittleEndian.setInt16(data, 0x0e,
+ DosUtils.encodeTime(created));
+ LittleEndian.setInt16(data, 0x10,
+ DosUtils.encodeDate(created));
+
+ this.dirty = true;
+ }
+
+ public long getLastModified() {
+ return DosUtils.decodeDateTime(
+ LittleEndian.getUInt16(data, 0x18),
+ LittleEndian.getUInt16(data, 0x16));
+ }
+
+ public void setLastModified(long lastModified) {
+ LittleEndian.setInt16(data, 0x16,
+ DosUtils.encodeTime(lastModified));
+ LittleEndian.setInt16(data, 0x18,
+ DosUtils.encodeDate(lastModified));
+
+ this.dirty = true;
+ }
+
+ public long getLastAccessed() {
+ return DosUtils.decodeDateTime(
+ LittleEndian.getUInt16(data, 0x12),
+ 0); /* time is not recorded */
+ }
+
+ public void setLastAccessed(long lastAccessed) {
+ LittleEndian.setInt16(data, 0x12,
+ DosUtils.encodeDate(lastAccessed));
+
+ this.dirty = true;
+ }
+
+ /**
+ * Returns if this entry has been marked as deleted. A deleted entry has
+ * its first byte set to the magic {@link #ENTRY_DELETED_MAGIC} value.
+ *
+ * @return if this entry is marked as deleted
+ */
+ public boolean isDeleted() {
+ return (LittleEndian.getUInt8(data, 0) == ENTRY_DELETED_MAGIC);
+ }
+
+ /**
+ * Returns the size of this entry as stored at {@link #OFFSET_FILE_SIZE}.
+ *
+ * @return the size of the file represented by this entry
+ */
+ public long getLength() {
+ return LittleEndian.getUInt32(data, OFFSET_FILE_SIZE);
+ }
+
+ /**
+ * Sets the size of this entry stored at {@link #OFFSET_FILE_SIZE}.
+ *
+ * @param length the new size of the file represented by this entry
+ * @throws IllegalArgumentException if {@code length} is out of range
+ */
+ public void setLength(long length) throws IllegalArgumentException {
+ LittleEndian.setInt32(data, OFFSET_FILE_SIZE, length);
+ }
+
+ /**
+ * Returns the {@code ShortName} that is stored in this directory entry or
+ * {@code null} if this entry has not been initialized.
+ *
+ * @return the {@code ShortName} stored in this entry or {@code null}
+ */
+ public ShortName getShortName() {
+ if (this.data[0] == 0) {
+ return null;
+ } else {
+ return ShortName.parse(this.data);
+ }
+ }
+
+ /**
+ * Does this entry refer to a file?
+ *
+ * @return
+ * @see org.jnode.fs.FSDirectoryEntry#isFile()
+ */
+ public boolean isFile() {
+ return ((getFlags() & (F_DIRECTORY | F_VOLUME_ID)) == 0);
+ }
+
+ public void setShortName(ShortName sn) {
+ if (sn.equals(this.getShortName())) return;
+
+ sn.write(this.data);
+ this.hasShortNameOnly = sn.hasShortNameOnly();
+ this.dirty = true;
+ }
+
+ /**
+ * Returns the startCluster.
+ *
+ * @return int
+ */
+ public long getStartCluster() {
+ int lowBytes = LittleEndian.getUInt16(data, 0x1a);
+ long highBytes = LittleEndian.getUInt16(data, 0x14);
+ return ( highBytes << 16 | lowBytes );
+ }
+
+ /**
+ * Sets the startCluster.
+ *
+ * @param startCluster The startCluster to set
+ */
+ void setStartCluster(long startCluster) {
+ if ( startCluster > Integer.MAX_VALUE ) throw new AssertionError();
+
+ LittleEndian.setInt16(data, 0x1a, (int) startCluster);
+ LittleEndian.setInt16(data, 0x14, (int)(startCluster >>> 16));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() +
+ " [name=" + getShortName() + "]"; //NOI18N
+ }
+
+ /**
+ * Writes this directory entry into the specified buffer.
+ *
+ * @param buff the buffer to write this entry to
+ */
+ void write(ByteBuffer buff) {
+ buff.put(data);
+ this.dirty = false;
+ }
+
+ /**
+ * Returns if the read-only flag is set for this entry. Do not confuse
+ * this with {@link #isReadOnly()}.
+ *
+ * @return if the read only file system flag is set on this entry
+ * @see #F_READONLY
+ * @see #setReadonlyFlag(boolean)
+ */
+ public boolean isReadonlyFlag() {
+ return ((getFlags() & F_READONLY) != 0);
+ }
+
+ /**
+ * Updates the read-only file system flag for this entry.
+ *
+ * @param isReadonly the new value for the read-only flag
+ * @see #F_READONLY
+ * @see #isReadonlyFlag()
+ */
+ public void setReadonlyFlag(boolean isReadonly) {
+ setFlag(F_READONLY, isReadonly);
+ }
+
+ String getLfnPart() {
+ final char[] unicodechar = new char[13];
+
+ unicodechar[0] = (char) LittleEndian.getUInt16(data, 1);
+ unicodechar[1] = (char) LittleEndian.getUInt16(data, 3);
+ unicodechar[2] = (char) LittleEndian.getUInt16(data, 5);
+ unicodechar[3] = (char) LittleEndian.getUInt16(data, 7);
+ unicodechar[4] = (char) LittleEndian.getUInt16(data, 9);
+ unicodechar[5] = (char) LittleEndian.getUInt16(data, 14);
+ unicodechar[6] = (char) LittleEndian.getUInt16(data, 16);
+ unicodechar[7] = (char) LittleEndian.getUInt16(data, 18);
+ unicodechar[8] = (char) LittleEndian.getUInt16(data, 20);
+ unicodechar[9] = (char) LittleEndian.getUInt16(data, 22);
+ unicodechar[10] = (char) LittleEndian.getUInt16(data, 24);
+ unicodechar[11] = (char) LittleEndian.getUInt16(data, 28);
+ unicodechar[12] = (char) LittleEndian.getUInt16(data, 30);
+
+ int end = 0;
+
+ while ((end < 13) && (unicodechar[end] != '\0')) {
+ end++;
+ }
+
+ return new String(unicodechar).substring(0, end);
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatFile.java b/src/main/java/de/waldheinz/fs/fat/FatFile.java
new file mode 100644
index 0000000..95942e0
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatFile.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.AbstractFsObject;
+import java.io.IOException;
+import de.waldheinz.fs.FsFile;
+import de.waldheinz.fs.ReadOnlyException;
+import java.io.EOFException;
+import java.nio.ByteBuffer;
+
+/**
+ * The in-memory representation of a single file (chain of clusters) on a
+ * FAT file system.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ * @since 0.6
+ */
+public final class FatFile extends AbstractFsObject implements FsFile {
+ private final FatDirectoryEntry entry;
+ private final ClusterChain chain;
+
+ private FatFile(FatDirectoryEntry myEntry, ClusterChain chain) {
+ super(myEntry.isReadOnly());
+
+ this.entry = myEntry;
+ this.chain = chain;
+ }
+
+ static FatFile get(Fat fat, FatDirectoryEntry entry)
+ throws IOException {
+
+ if (entry.isDirectory())
+ throw new IllegalArgumentException(entry + " is a directory");
+
+ final ClusterChain cc = new ClusterChain(
+ fat, entry.getStartCluster(), entry.isReadonlyFlag());
+
+ if (entry.getLength() > cc.getLengthOnDisk()) throw new IOException(
+ "entry is larger than associated cluster chain");
+
+ return new FatFile(entry, cc);
+ }
+
+ /**
+ * Returns the length of this file in bytes. This is the length that
+ * is stored in the directory entry that is associated with this file.
+ *
+ * @return long the length that is recorded for this file
+ */
+ @Override
+ public long getLength() {
+ checkValid();
+
+ return entry.getLength();
+ }
+
+ /**
+ * Sets the size (in bytes) of this file. Because
+ * {@link #write(long, java.nio.ByteBuffer) writing} to the file will grow
+ * it automatically if needed, this method is mainly usefull for truncating
+ * a file.
+ *
+ * @param length the new length of the file in bytes
+ * @throws ReadOnlyException if this file is read-only
+ * @throws IOException on error updating the file size
+ */
+ @Override
+ public void setLength(long length) throws ReadOnlyException, IOException {
+ checkWritable();
+
+ if (getLength() == length) return;
+
+ updateTimeStamps(true);
+ chain.setSize(length);
+
+ this.entry.setStartCluster(chain.getStartCluster());
+ this.entry.setLength(length);
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * Unless this file is {@link #isReadOnly() read-ony}, this method also
+ * updates the "last accessed" field in the directory entry that is
+ * associated with this file.
+ * </p>
+ *
+ * @param offset {@inheritDoc}
+ * @param dest {@inheritDoc}
+ * @see FatDirectoryEntry#setLastAccessed(long)
+ */
+ @Override
+ public void read(long offset, ByteBuffer dest) throws IOException {
+ checkValid();
+
+ final int len = dest.remaining();
+
+ if (len == 0) return;
+
+ if (offset + len > getLength()) {
+ throw new EOFException();
+ }
+
+ if (!isReadOnly()) {
+ updateTimeStamps(false);
+ }
+
+ chain.readData(offset, dest);
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * If the data to be written extends beyond the current
+ * {@link #getLength() length} of this file, an attempt is made to
+ * {@link #setLength(long) grow} the file so that the data will fit.
+ * Additionally, this method updates the "last accessed" and "last modified"
+ * fields on the directory entry that is associated with this file.
+ * </p>
+ *
+ * @param offset {@inheritDoc}
+ * @param srcBuf {@inheritDoc}
+ */
+ @Override
+ public void write(long offset, ByteBuffer srcBuf)
+ throws ReadOnlyException, IOException {
+
+ checkWritable();
+
+ updateTimeStamps(true);
+
+ final long lastByte = offset + srcBuf.remaining();
+
+ if (lastByte > getLength()) {
+ setLength(lastByte);
+ }
+
+ chain.writeData(offset, srcBuf);
+ }
+
+ private void updateTimeStamps(boolean write) {
+ final long now = System.currentTimeMillis();
+ entry.setLastAccessed(now);
+
+ if (write) {
+ entry.setLastModified(now);
+ }
+ }
+
+ /**
+ * Has no effect besides possibly throwing an {@code ReadOnlyException}. To
+ * make sure that all data is written out to disk use the
+ * {@link FatFileSystem#flush()} method.
+ *
+ * @throws ReadOnlyException if this {@code FatFile} is read-only
+ */
+ @Override
+ public void flush() throws ReadOnlyException {
+ checkWritable();
+
+ /* nothing else to do */
+ }
+
+ /**
+ * Returns the {@code ClusterChain} that holds the contents of
+ * this {@code FatFile}.
+ *
+ * @return the file's {@code ClusterChain}
+ */
+ ClusterChain getChain() {
+ checkValid();
+
+ return chain;
+ }
+
+ /**
+ * Returns a human-readable string representation of this {@code FatFile},
+ * mainly for debugging purposes.
+ *
+ * @return a string describing this {@code FatFile}
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [length=" + getLength() +
+ ", first cluster=" + chain.getStartCluster() + "]";
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatFileSystem.java b/src/main/java/de/waldheinz/fs/fat/FatFileSystem.java
new file mode 100644
index 0000000..db4d3e2
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatFileSystem.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.AbstractFileSystem;
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+import de.waldheinz.fs.ReadOnlyException;
+
+/**
+ * <p>
+ * Implements the {@code FileSystem} interface for the FAT family of file
+ * systems. This class always uses the "long file name" specification when
+ * writing directory entries.
+ * </p><p>
+ * For creating (aka "formatting") FAT file systems please refer to the
+ * {@link SuperFloppyFormatter} class.
+ * </p>
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public final class FatFileSystem extends AbstractFileSystem {
+
+ private final Fat fat;
+ private final FsInfoSector fsiSector;
+ private final BootSector bs;
+ private final FatLfnDirectory rootDir;
+ private final AbstractDirectory rootDirStore;
+ private final FatType fatType;
+ private final long filesOffset;
+
+ FatFileSystem(BlockDevice api, boolean readOnly) throws IOException {
+
+ this(api, readOnly, false);
+ }
+
+ /**
+ * Constructor for FatFileSystem in specified readOnly mode
+ *
+ * @param device the {@code BlockDevice} holding the file system
+ * @param readOnly if this FS should be read-lonly
+ * @param ignoreFatDifferences
+ * @throws IOException on read error
+ */
+ private FatFileSystem(BlockDevice device, boolean readOnly,
+ boolean ignoreFatDifferences)
+ throws IOException {
+
+ super(readOnly);
+
+ this.bs = BootSector.read(device);
+
+ if (bs.getNrFats() <= 0) throw new IOException(
+ "boot sector says there are no FATs");
+
+ this.filesOffset = FatUtils.getFilesOffset(bs);
+ this.fatType = bs.getFatType();
+ this.fat = Fat.read(bs, 0);
+
+ if (!ignoreFatDifferences) {
+ for (int i=1; i < bs.getNrFats(); i++) {
+ final Fat tmpFat = Fat.read(bs, i);
+ if (!fat.equals(tmpFat)) {
+ throw new IOException("FAT " + i + " differs from FAT 0");
+ }
+ }
+ }
+
+ if (fatType == FatType.FAT32) {
+ final Fat32BootSector f32bs = (Fat32BootSector) bs;
+ ClusterChain rootDirFile = new ClusterChain(fat,
+ f32bs.getRootDirFirstCluster(), isReadOnly());
+ this.rootDirStore = ClusterChainDirectory.readRoot(rootDirFile);
+ this.fsiSector = FsInfoSector.read(f32bs);
+
+ if (fsiSector.getFreeClusterCount() != fat.getFreeClusterCount()) {
+ throw new IOException("free cluster count mismatch - fat: " +
+ fat.getFreeClusterCount() + " - fsinfo: " +
+ fsiSector.getFreeClusterCount());
+ }
+ } else {
+ this.rootDirStore =
+ Fat16RootDirectory.read((Fat16BootSector) bs,readOnly);
+ this.fsiSector = null;
+ }
+
+ this.rootDir = new FatLfnDirectory(rootDirStore, fat, isReadOnly());
+
+ }
+
+ /**
+ * Reads the file system structure from the specified {@code BlockDevice}
+ * and returns a fresh {@code FatFileSystem} instance to read or modify
+ * it.
+ *
+ * @param device the {@code BlockDevice} holding the file system
+ * @param readOnly if the {@code FatFileSystem} should be in read-only mode
+ * @return the {@code FatFileSystem} instance for the device
+ * @throws IOException on read error or if the file system structure could
+ * not be parsed
+ */
+ public static FatFileSystem read(BlockDevice device, boolean readOnly)
+ throws IOException {
+
+ return new FatFileSystem(device, readOnly);
+ }
+
+ long getFilesOffset() {
+ checkClosed();
+
+ return filesOffset;
+ }
+
+ /**
+ * Returns the size of the FAT entries of this {@code FatFileSystem}.
+ *
+ * @return the exact type of the FAT used by this file system
+ */
+ public FatType getFatType() {
+ checkClosed();
+
+ return this.fatType;
+ }
+
+ /**
+ * Returns the volume label of this file system.
+ *
+ * @return the volume label
+ */
+ public String getVolumeLabel() {
+ checkClosed();
+
+ final String fromDir = rootDirStore.getLabel();
+
+ if (fromDir == null && fatType != FatType.FAT32) {
+ return ((Fat16BootSector)bs).getVolumeLabel();
+ } else {
+ return fromDir;
+ }
+ }
+
+ /**
+ * Sets the volume label for this file system.
+ *
+ * @param label the new volume label, may be {@code null}
+ * @throws ReadOnlyException if the file system is read-only
+ * @throws IOException on write error
+ */
+ public void setVolumeLabel(String label)
+ throws ReadOnlyException, IOException {
+
+ checkClosed();
+ checkReadOnly();
+
+ rootDirStore.setLabel(label);
+
+ if (fatType != FatType.FAT32) {
+ ((Fat16BootSector)bs).setVolumeLabel(label);
+ }
+ }
+
+ AbstractDirectory getRootDirStore() {
+ checkClosed();
+
+ return rootDirStore;
+ }
+
+ /**
+ * Flush all changed structures to the device.
+ *
+ * @throws IOException on write error
+ */
+ @Override
+ public void flush() throws IOException {
+ checkClosed();
+
+ if (bs.isDirty()) {
+ bs.write();
+ }
+
+ for (int i = 0; i < bs.getNrFats(); i++) {
+ fat.writeCopy(FatUtils.getFatOffset(bs, i));
+ }
+
+ rootDir.flush();
+
+ if (fsiSector != null) {
+ fsiSector.setFreeClusterCount(fat.getFreeClusterCount());
+ fsiSector.setLastAllocatedCluster(fat.getLastAllocatedCluster());
+ fsiSector.write();
+ }
+ }
+
+ @Override
+ public FatLfnDirectory getRoot() {
+ checkClosed();
+
+ return rootDir;
+ }
+
+ /**
+ * Returns the fat.
+ *
+ * @return Fat
+ */
+ public Fat getFat() {
+ return fat;
+ }
+
+ /**
+ * Returns the bootsector.
+ *
+ * @return BootSector
+ */
+ public BootSector getBootSector() {
+ checkClosed();
+
+ return bs;
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * This method shows the free space in terms of available clusters.
+ * </p>
+ *
+ * @return always -1
+ */
+ @Override
+ public long getFreeSpace() {
+ return this.fat.getFreeClusterCount() * this.bs.getBytesPerCluster();
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * This method is currently not implemented for {@code FatFileSystem} and
+ * always returns -1.
+ * </p>
+ *
+ * @return always -1
+ */
+ @Override
+ public long getTotalSpace() {
+ return this.bs.getDataClusterCount() * this.bs.getBytesPerCluster();
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * This method is currently not implemented for {@code FatFileSystem} and
+ * always returns -1.
+ * </p>
+ *
+ * @return always -1
+ */
+ @Override
+ public long getUsableSpace() {
+ // TODO implement me
+ return -1;
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatLfnDirectory.java b/src/main/java/de/waldheinz/fs/fat/FatLfnDirectory.java
new file mode 100644
index 0000000..9f4aa9b
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatLfnDirectory.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.AbstractFsObject;
+import de.waldheinz.fs.FsDirectory;
+import de.waldheinz.fs.FsDirectoryEntry;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The {@link FsDirectory} implementation for FAT file systems. This
+ * implementation aims to fully comply to the FAT specification, including
+ * the quite complex naming system regarding the long file names (LFNs) and
+ * their corresponding 8+3 short file names. This also means that an
+ * {@code FatLfnDirectory} is case-preserving but <em>not</em> case-sensitive.
+ *
+ * @author gbin
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ * @since 0.6
+ */
+public final class FatLfnDirectory
+ extends AbstractFsObject
+ implements FsDirectory {
+
+ /**
+ * This set is used to check if a file name is already in use in this
+ * directory. The FAT specification says that file names must be unique
+ * ignoring the case, so this set contains all names converted to
+ * lower-case, and all checks must be performed using lower-case strings.
+ */
+ private final Set<String> usedNames;
+ private final Fat fat;
+ private final Map<ShortName, FatLfnDirectoryEntry> shortNameIndex;
+ private final Map<String, FatLfnDirectoryEntry> longNameIndex;
+ private final Map<FatDirectoryEntry, FatFile> entryToFile;
+ private final Map<FatDirectoryEntry, FatLfnDirectory> entryToDirectory;
+ private Dummy83BufferGenerator dbg;
+
+ final AbstractDirectory dir;
+
+ FatLfnDirectory(AbstractDirectory dir, Fat fat, boolean readOnly)
+ throws IOException {
+
+ super(readOnly);
+
+ if ((dir == null) || (fat == null)) throw new NullPointerException();
+
+ this.fat = fat;
+ this.dir = dir;
+
+ this.shortNameIndex =
+ new LinkedHashMap<ShortName, FatLfnDirectoryEntry>();
+
+ this.longNameIndex =
+ new LinkedHashMap<String, FatLfnDirectoryEntry>();
+
+ this.entryToFile =
+ new LinkedHashMap<FatDirectoryEntry, FatFile>();
+
+ this.entryToDirectory =
+ new LinkedHashMap<FatDirectoryEntry, FatLfnDirectory>();
+
+ this.usedNames = new HashSet<String>();
+ this.dbg = new Dummy83BufferGenerator();
+
+ parseLfn();
+ }
+
+ FatFile getFile(FatDirectoryEntry entry) throws IOException {
+ FatFile file = entryToFile.get(entry);
+
+ if (file == null) {
+ file = FatFile.get(fat, entry);
+ entryToFile.put(entry, file);
+ }
+
+ return file;
+ }
+
+ FatLfnDirectory getDirectory(FatDirectoryEntry entry) throws IOException {
+ FatLfnDirectory result = entryToDirectory.get(entry);
+
+ if (result == null) {
+ final ClusterChainDirectory storage = read(entry, fat);
+ result = new FatLfnDirectory(storage, fat, isReadOnly());
+ entryToDirectory.put(entry, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * According to the FAT file system specification, leading and trailing
+ * spaces in the {@code name} are ignored by this method.
+ * </p>
+ *
+ * @param name {@inheritDoc}
+ * @return {@inheritDoc}
+ * @throws IOException {@inheritDoc}
+ */
+ @Override
+ public FatLfnDirectoryEntry addFile(String name) throws IOException {
+ checkWritable();
+ checkUniqueName(name);
+
+ name = name.trim();
+ final ShortName sn = makeShortName(name, false);
+
+ final FatLfnDirectoryEntry entry =
+ new FatLfnDirectoryEntry(name, sn, this, false);
+
+ dir.addEntries(entry.compactForm());
+
+ shortNameIndex.put(sn, entry);
+ longNameIndex.put(name.toLowerCase(), entry);
+
+ getFile(entry.realEntry);
+
+ dir.setDirty();
+ return entry;
+ }
+
+ boolean isFreeName(String name) {
+ return true;
+ }
+
+ private void checkUniqueName(String name) throws IOException {
+ }
+
+ private void freeUniqueName(String name) {
+ }
+
+ private ShortName makeShortName(String name, boolean isDirectory) throws IOException {
+ final ShortName result;
+
+ try {
+ result = dbg.generate83BufferNew(name);
+ } catch (IllegalArgumentException ex) {
+ throw new IOException(
+ "could not generate short name for \"" + name + "\"", ex);
+ }
+ return result;
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * According to the FAT file system specification, leading and trailing
+ * spaces in the {@code name} are ignored by this method.
+ * </p>
+ *
+ * @param name {@inheritDoc}
+ * @return {@inheritDoc}
+ * @throws IOException {@inheritDoc}
+ */
+ @Override
+ public FatLfnDirectoryEntry addDirectory(String name) throws IOException {
+ checkWritable();
+ checkUniqueName(name);
+
+ name = name.trim();
+ final ShortName sn = makeShortName(name, true);
+ final FatDirectoryEntry real = dir.createSub(fat);
+ real.setShortName(sn);
+ final FatLfnDirectoryEntry e =
+ new FatLfnDirectoryEntry(this, real, name);
+
+ try {
+ dir.addEntries(e.compactForm());
+ } catch (IOException ex) {
+ final ClusterChain cc =
+ new ClusterChain(fat, real.getStartCluster(), false);
+ cc.setChainLength(0);
+ dir.removeEntry(real);
+ throw ex;
+ }
+
+ shortNameIndex.put(sn, e);
+ longNameIndex.put(name.toLowerCase(), e);
+
+ getDirectory(real);
+
+ flush();
+ return e;
+ }
+
+ /**
+ * <p>
+ * {@inheritDoc}
+ * </p><p>
+ * According to the FAT file system specification, leading and trailing
+ * spaces in the {@code name} are ignored by this method.
+ * </p>
+ *
+ * @param name {@inheritDoc}
+ * @return {@inheritDoc}
+ */
+ @Override
+ public FatLfnDirectoryEntry getEntry(String name) {
+ name = name.trim().toLowerCase();
+
+ final FatLfnDirectoryEntry entry = longNameIndex.get(name);
+
+ if (entry == null) {
+ if (!ShortName.canConvert(name)) return null;
+ return shortNameIndex.get(ShortName.get(name));
+ } else {
+ return entry;
+ }
+ }
+
+ private void parseLfn() throws IOException {
+ int i = 0;
+ final int size = dir.getEntryCount();
+
+ while (i < size) {
+ // jump over empty entries
+ while (i < size && dir.getEntry(i) == null) {
+ i++;
+ }
+
+ if (i >= size) {
+ break;
+ }
+
+ int offset = i; // beginning of the entry
+ // check when we reach a real entry
+ while (dir.getEntry(i).isLfnEntry()) {
+ i++;
+ if (i >= size) {
+ // This is a cutted entry, forgive it
+ break;
+ }
+ }
+
+ if (i >= size) {
+ // This is a cutted entry, forgive it
+ break;
+ }
+
+ final FatLfnDirectoryEntry current =
+ FatLfnDirectoryEntry.extract(this, offset, ++i - offset);
+
+ if (!current.realEntry.isDeleted() && current.isValid()) {
+ checkUniqueName(current.getName());
+
+ shortNameIndex.put(current.realEntry.getShortName(), current);
+ longNameIndex.put(current.getName().toLowerCase(), current);
+ }
+ }
+ }
+
+ private void updateLFN() throws IOException {
+ ArrayList<FatDirectoryEntry> dest =
+ new ArrayList<FatDirectoryEntry>();
+
+ for (FatLfnDirectoryEntry currentEntry : shortNameIndex.values()) {
+ FatDirectoryEntry[] encoded = currentEntry.compactForm();
+ dest.addAll(Arrays.asList(encoded));
+ }
+
+ final int size = dest.size();
+
+ dir.changeSize(size);
+ dir.setEntries(dest);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ checkWritable();
+
+ for (FatFile f : entryToFile.values()) {
+ f.flush();
+ }
+
+ for (FatLfnDirectory d : entryToDirectory.values()) {
+ d.flush();
+ }
+
+ updateLFN();
+ dir.flush();
+ }
+
+ @Override
+ public Iterator<FsDirectoryEntry> iterator() {
+ return new Iterator<FsDirectoryEntry>() {
+
+ final Iterator<FatLfnDirectoryEntry> it =
+ shortNameIndex.values().iterator();
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public FsDirectoryEntry next() {
+ return it.next();
+ }
+
+ /**
+ * @see java.util.Iterator#remove()
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * Remove the entry with the given name from this directory.
+ *
+ * @param name the name of the entry to remove
+ * @throws IOException on error removing the entry
+ * @throws IllegalArgumentException on an attempt to remove the dot entries
+ */
+ @Override
+ public void remove(String name)
+ throws IOException, IllegalArgumentException {
+
+ checkWritable();
+
+ final FatLfnDirectoryEntry entry = getEntry(name);
+ if (entry == null) return;
+
+ unlinkEntry(entry);
+
+ final ClusterChain cc = new ClusterChain(
+ fat, entry.realEntry.getStartCluster(), false);
+
+ cc.setChainLength(0);
+
+ freeUniqueName(name);
+ updateLFN();
+ }
+
+ /**
+ * Unlinks the specified entry from this directory without actually
+ * deleting it.
+ *
+ * @param e the entry to be unlinked
+ * @see #linkEntry(de.waldheinz.fs.fat.FatLfnDirectoryEntry)
+ */
+ void unlinkEntry(FatLfnDirectoryEntry entry) {
+ final ShortName sn = entry.realEntry.getShortName();
+
+ if (sn.equals(ShortName.DOT) || sn.equals(ShortName.DOT_DOT)) throw
+ new IllegalArgumentException(
+ "the dot entries can not be removed");
+
+ final String lowerName = entry.getName().toLowerCase();
+
+ assert (this.longNameIndex.containsKey(lowerName));
+ this.longNameIndex.remove(lowerName);
+
+ assert (this.shortNameIndex.containsKey(sn));
+ this.shortNameIndex.remove(sn);
+
+ if (entry.isFile()) {
+ this.entryToFile.remove(entry.realEntry);
+ } else {
+ this.entryToDirectory.remove(entry.realEntry);
+ }
+ }
+
+ /**
+ * Links the specified entry to this directory, updating the entrie's
+ * short name.
+ *
+ * @param entry the entry to be linked (added) to this directory
+ * @see #unlinkEntry(de.waldheinz.fs.fat.FatLfnDirectoryEntry)
+ */
+ void linkEntry(FatLfnDirectoryEntry entry) throws IOException {
+ checkUniqueName(entry.getName());
+ ShortName name;
+ name = this.dbg.generate83BufferNew(entry.getName());
+ entry.realEntry.setShortName(name);
+
+ this.longNameIndex.put(entry.getName().toLowerCase(), entry);
+ this.shortNameIndex.put(entry.realEntry.getShortName(), entry);
+
+ updateLFN();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() +
+ " [size=" + shortNameIndex.size() + //NOI18N
+ ", dir=" + dir + "]"; //NOI18N
+ }
+
+ private static ClusterChainDirectory read(FatDirectoryEntry entry, Fat fat)
+ throws IOException {
+
+ if (!entry.isDirectory()) throw
+ new IllegalArgumentException(entry + " is no directory");
+
+ final ClusterChain chain = new ClusterChain(
+ fat, entry.getStartCluster(),
+ entry.isReadonlyFlag());
+
+ final ClusterChainDirectory result =
+ new ClusterChainDirectory(chain, false);
+
+ result.read();
+ return result;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatLfnDirectoryEntry.java b/src/main/java/de/waldheinz/fs/fat/FatLfnDirectoryEntry.java
new file mode 100644
index 0000000..0882516
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatLfnDirectoryEntry.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.AbstractFsObject;
+import de.waldheinz.fs.FsDirectoryEntry;
+import de.waldheinz.fs.ReadOnlyException;
+import java.io.IOException;
+
+/**
+ * Represents an entry in a {@link FatLfnDirectory}. Besides implementing the
+ * {@link FsDirectoryEntry} interface for FAT file systems, it allows access
+ * to the {@link #setArchiveFlag(boolean) archive},
+ * {@link #setHiddenFlag(boolean) hidden},
+ * {@link #setReadOnlyFlag(boolean) read-only} and
+ * {@link #setSystemFlag(boolean) system} flags specifed for the FAT file
+ * system.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ * @since 0.6
+ */
+public final class FatLfnDirectoryEntry
+ extends AbstractFsObject
+ implements FsDirectoryEntry {
+
+ final FatDirectoryEntry realEntry;
+
+ private FatLfnDirectory parent;
+ private String fileName;
+
+ FatLfnDirectoryEntry(String name, ShortName sn,
+ FatLfnDirectory parent, boolean directory) {
+
+ super(false);
+
+ this.parent = parent;
+ this.fileName = name;
+
+ final long now = System.currentTimeMillis();
+ this.realEntry = FatDirectoryEntry.create(directory);
+ this.realEntry.setShortName(sn);
+ this.realEntry.setCreated(now);
+ this.realEntry.setLastAccessed(now);
+ }
+
+ FatLfnDirectoryEntry(FatLfnDirectory parent,
+ FatDirectoryEntry realEntry, String fileName) {
+
+ super(parent.isReadOnly());
+
+ this.parent = parent;
+ this.realEntry = realEntry;
+ this.fileName = fileName;
+ }
+
+ static FatLfnDirectoryEntry extract(
+ FatLfnDirectory dir, int offset, int len) {
+
+ final FatDirectoryEntry realEntry = dir.dir.getEntry(offset + len - 1);
+ final String fileName;
+
+ if (len == 1) {
+ /* this is just an old plain 8.3 entry */
+ fileName = realEntry.getShortName().asSimpleString();
+ } else {
+ /* stored in reverse order */
+ final StringBuilder name = new StringBuilder(13 * (len - 1));
+
+ for (int i = len - 2; i >= 0; i--) {
+ FatDirectoryEntry entry = dir.dir.getEntry(i + offset);
+ name.append(entry.getLfnPart());
+ }
+
+ fileName = name.toString().trim();
+ }
+
+ return new FatLfnDirectoryEntry(dir, realEntry, fileName);
+ }
+
+ /**
+ * Returns if this directory entry has the FAT "hidden" flag set.
+ *
+ * @return if this is a hidden directory entry
+ * @see #setHiddenFlag(boolean)
+ */
+ public boolean isHiddenFlag() {
+ return this.realEntry.isHiddenFlag();
+ }
+
+ /**
+ * Sets the "hidden" flag on this {@code FatLfnDirectoryEntry} to the
+ * specified value.
+ *
+ * @param hidden if this entry should have the hidden flag set
+ * @throws ReadOnlyException if this entry is read-only
+ * @see #isHiddenFlag()
+ */
+ public void setHiddenFlag(boolean hidden) throws ReadOnlyException {
+ checkWritable();
+
+ this.realEntry.setHiddenFlag(hidden);
+ }
+
+ /**
+ * Returns if this directory entry has the FAT "system" flag set.
+ *
+ * @return if this is a "system" directory entry
+ * @see #setSystemFlag(boolean)
+ */
+ public boolean isSystemFlag() {
+ return this.realEntry.isSystemFlag();
+ }
+
+ /**
+ * Sets the "system" flag on this {@code FatLfnDirectoryEntry} to the
+ * specified value.
+ *
+ * @param systemEntry if this entry should have the system flag set
+ * @throws ReadOnlyException if this entry is read-only
+ * @see #isSystemFlag()
+ */
+ public void setSystemFlag(boolean systemEntry) throws ReadOnlyException {
+ checkWritable();
+
+ this.realEntry.setSystemFlag(systemEntry);
+ }
+
+ /**
+ * Returns if this directory entry has the FAT "read-only" flag set. This
+ * entry may still modified if {@link #isReadOnly()} returns {@code true}.
+ *
+ * @return if this entry has the read-only flag set
+ * @see #setReadOnlyFlag(boolean)
+ */
+ public boolean isReadOnlyFlag() {
+ return this.realEntry.isReadonlyFlag();
+ }
+
+ /**
+ * Sets the "read only" flag on this {@code FatLfnDirectoryEntry} to the
+ * specified value. This method only modifies the read-only flag as
+ * specified by the FAT file system, which is essentially ignored by the
+ * fat32-lib. The true indicator if it is possible to alter this
+ *
+ * @param readOnly if this entry should be flagged as read only
+ * @throws ReadOnlyException if this entry is read-only as given by
+ * {@link #isReadOnly()} method
+ * @see #isReadOnlyFlag()
+ */
+ public void setReadOnlyFlag(boolean readOnly) throws ReadOnlyException {
+ checkWritable();
+
+ this.realEntry.setReadonlyFlag(readOnly);
+ }
+
+ /**
+ * Returns if this directory entry has the FAT "archive" flag set.
+ *
+ * @return if this entry has the archive flag set
+ */
+ public boolean isArchiveFlag() {
+ return this.realEntry.isArchiveFlag();
+ }
+
+ /**
+ * Sets the "archive" flag on this {@code FatLfnDirectoryEntry} to the
+ * specified value.
+ *
+ * @param archive if this entry should have the archive flag set
+ * @throws ReadOnlyException if this entry is
+ * {@link #isReadOnly() read-only}
+ */
+ public void setArchiveFlag(boolean archive) throws ReadOnlyException {
+ checkWritable();
+
+ this.realEntry.setArchiveFlag(archive);
+ }
+
+ private int totalEntrySize() {
+ int result = (fileName.length() / 13) + 1;
+
+ if ((fileName.length() % 13) != 0) {
+ result++;
+ }
+
+ return result;
+ }
+
+ FatDirectoryEntry[] compactForm() {
+ if (this.realEntry.getShortName().equals(ShortName.DOT) ||
+ this.realEntry.getShortName().equals(ShortName.DOT_DOT) ||
+ this.realEntry.hasShortNameOnly) {
+ /* the dot entries must not have a LFN */
+ return new FatDirectoryEntry[]{this.realEntry};
+ }
+
+ final int totalEntrySize = totalEntrySize();
+
+ final FatDirectoryEntry[] entries =
+ new FatDirectoryEntry[totalEntrySize];
+
+ final byte checkSum = this.realEntry.getShortName().checkSum();
+ int j = 0;
+
+ for (int i = totalEntrySize - 2; i > 0; i--) {
+ entries[i] = createPart(fileName.substring(j * 13, j * 13 + 13),
+ j + 1, checkSum, false);
+ j++;
+ }
+
+ entries[0] = createPart(fileName.substring(j * 13),
+ j + 1, checkSum, true);
+
+ entries[totalEntrySize - 1] = this.realEntry;
+
+ return entries;
+ }
+
+ @Override
+ public String getName() {
+ checkValid();
+
+ return fileName;
+ }
+
+ @Override
+ public void setName(String newName) throws IOException {
+ checkWritable();
+
+ if (!this.parent.isFreeName(newName)) {
+ throw new IOException(
+ "the name \"" + newName + "\" is already in use");
+ }
+
+ this.parent.unlinkEntry(this);
+ this.fileName = newName;
+ this.parent.linkEntry(this);
+ }
+
+ /**
+ * Moves this entry to a new directory under the specified name.
+ *
+ * @param target the direcrory where this entry should be moved to
+ * @param newName the new name under which this entry will be accessible
+ * in the target directory
+ * @throws IOException on error moving this entry
+ * @throws ReadOnlyException if this directory is read-only
+ */
+ public void moveTo(FatLfnDirectory target, String newName)
+ throws IOException, ReadOnlyException {
+
+ checkWritable();
+
+ if (!target.isFreeName(newName)) {
+ throw new IOException(
+ "the name \"" + newName + "\" is already in use");
+ }
+
+ this.parent.unlinkEntry(this);
+ this.parent = target;
+ this.fileName = newName;
+ this.parent.linkEntry(this);
+ }
+
+ @Override
+ public void setLastModified(long lastModified) {
+ checkWritable();
+ realEntry.setLastModified(lastModified);
+ }
+
+ @Override
+ public FatFile getFile() throws IOException {
+ return parent.getFile(realEntry);
+ }
+
+ @Override
+ public FatLfnDirectory getDirectory() throws IOException {
+ return parent.getDirectory(realEntry);
+ }
+
+ @Override
+ public String toString() {
+ return "LFN = " + fileName + " / SFN = " + realEntry.getShortName();
+ }
+
+ private static FatDirectoryEntry createPart(String subName,
+ int ordinal, byte checkSum, boolean isLast) {
+
+ final char[] unicodechar = new char[13];
+ subName.getChars(0, subName.length(), unicodechar, 0);
+
+ for (int i=subName.length(); i < 13; i++) {
+ if (i==subName.length()) {
+ unicodechar[i] = 0x0000;
+ } else {
+ unicodechar[i] = 0xffff;
+ }
+ }
+
+ final byte[] rawData = new byte[FatDirectoryEntry.SIZE];
+
+ if (isLast) {
+ LittleEndian.setInt8(rawData, 0, ordinal + (1 << 6));
+ } else {
+ LittleEndian.setInt8(rawData, 0, ordinal);
+ }
+
+ LittleEndian.setInt16(rawData, 1, unicodechar[0]);
+ LittleEndian.setInt16(rawData, 3, unicodechar[1]);
+ LittleEndian.setInt16(rawData, 5, unicodechar[2]);
+ LittleEndian.setInt16(rawData, 7, unicodechar[3]);
+ LittleEndian.setInt16(rawData, 9, unicodechar[4]);
+ LittleEndian.setInt8(rawData, 11, 0x0f); // this is the hidden
+ // attribute tag for
+ // lfn
+ LittleEndian.setInt8(rawData, 12, 0); // reserved
+ LittleEndian.setInt8(rawData, 13, checkSum); // checksum
+ LittleEndian.setInt16(rawData, 14, unicodechar[5]);
+ LittleEndian.setInt16(rawData, 16, unicodechar[6]);
+ LittleEndian.setInt16(rawData, 18, unicodechar[7]);
+ LittleEndian.setInt16(rawData, 20, unicodechar[8]);
+ LittleEndian.setInt16(rawData, 22, unicodechar[9]);
+ LittleEndian.setInt16(rawData, 24, unicodechar[10]);
+ LittleEndian.setInt16(rawData, 26, 0); // sector... unused
+ LittleEndian.setInt16(rawData, 28, unicodechar[11]);
+ LittleEndian.setInt16(rawData, 30, unicodechar[12]);
+
+ return new FatDirectoryEntry(rawData, false);
+ }
+
+ @Override
+ public long getLastModified() throws IOException {
+ return realEntry.getLastModified();
+ }
+
+ @Override
+ public long getCreated() throws IOException {
+ return realEntry.getCreated();
+ }
+
+ @Override
+ public long getLastAccessed() throws IOException {
+ return realEntry.getLastAccessed();
+ }
+
+ @Override
+ public boolean isFile() {
+ return realEntry.isFile();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return realEntry.isDirectory();
+ }
+
+ @Override
+ public boolean isDirty() {
+ return realEntry.isDirty();
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatType.java b/src/main/java/de/waldheinz/fs/fat/FatType.java
new file mode 100644
index 0000000..1978264
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatType.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+/**
+ * Enumerates the different entry sizes of 12, 16 and 32 bits for the different
+ * FAT flavours.
+ *
+ * @author Ewout Prangsma &lt;epr at jnode.org&gt;
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public enum FatType {
+
+ /**
+ * Represents a 12-bit file allocation table.
+ */
+ FAT12((1 << 12) - 16, 0xFFFL, 1.5f, "FAT12 ") { //NOI18N
+
+ @Override
+ long readEntry(byte[] data, int index) {
+ final int idx = (int) (index * 1.5);
+ final int b1 = data[idx] & 0xFF;
+ final int b2 = data[idx + 1] & 0xFF;
+ final int v = (b2 << 8) | b1;
+
+ if ((index % 2) == 0) {
+ return v & 0xFFF;
+ } else {
+ return v >> 4;
+ }
+ }
+
+ @Override
+ void writeEntry(byte[] data, int index, long entry) {
+ final int idx = (int) (index * 1.5);
+
+ if ((index % 2) == 0) {
+ data[idx] = (byte) (entry & 0xFF);
+ data[idx + 1] = (byte) ((entry >> 8) & 0x0F);
+ } else {
+ data[idx] |= (byte) ((entry & 0x0F) << 4);
+ data[idx + 1] = (byte) ((entry >> 4) & 0xFF);
+ }
+ }
+ },
+
+ /**
+ * Represents a 16-bit file allocation table.
+ */
+ FAT16((1 << 16) - 16, 0xFFFFL, 2.0f, "FAT16 ") { //NOI18N
+
+ @Override
+ long readEntry(byte[] data, int index) {
+ final int idx = index << 1;
+ final int b1 = data[idx] & 0xFF;
+ final int b2 = data[idx + 1] & 0xFF;
+ return (b2 << 8) | b1;
+ }
+
+ @Override
+ void writeEntry(byte[] data, int index, long entry) {
+ final int idx = index << 1;
+ data[idx] = (byte) (entry & 0xFF);
+ data[idx + 1] = (byte) ((entry >> 8) & 0xFF);
+ }
+ },
+
+ /**
+ * Represents a 32-bit file allocation table.
+ */
+ FAT32((1 << 28) - 16, 0xFFFFFFFFL, 4.0f, "FAT32 ") { //NOI18N
+
+ @Override
+ long readEntry(byte[] data, int index) {
+ final int idx = index * 4;
+ final long l1 = data[idx] & 0xFF;
+ final long l2 = data[idx + 1] & 0xFF;
+ final long l3 = data[idx + 2] & 0xFF;
+ final long l4 = data[idx + 3] & 0xFF;
+ return (l4 << 24) | (l3 << 16) | (l2 << 8) | l1;
+ }
+
+ @Override
+ void writeEntry(byte[] data, int index, long entry) {
+ final int idx = index << 2;
+ data[idx] = (byte) (entry & 0xFF);
+ data[idx + 1] = (byte) ((entry >> 8) & 0xFF);
+ data[idx + 2] = (byte) ((entry >> 16) & 0xFF);
+ data[idx + 3] = (byte) ((entry >> 24) & 0xFF);
+ }
+ };
+
+ private final long minReservedEntry;
+ private final long maxReservedEntry;
+ private final long eofCluster;
+ private final long eofMarker;
+ private final long bitMask;
+ private final int maxClusters;
+ private final String label;
+ private final float entrySize;
+
+ private FatType(int maxClusters,
+ long bitMask, float entrySize, String label) {
+
+ this.minReservedEntry = (0xFFFFFF0L & bitMask);
+ this.maxReservedEntry = (0xFFFFFF6L & bitMask);
+ this.eofCluster = (0xFFFFFF8L & bitMask);
+ this.eofMarker = (0xFFFFFFFL & bitMask);
+ this.entrySize = entrySize;
+ this.label = label;
+ this.maxClusters = maxClusters;
+ this.bitMask = bitMask;
+ }
+
+ abstract long readEntry(byte[] data, int index);
+
+ abstract void writeEntry(byte[] data, int index, long entry);
+
+ /**
+ * Returns the maximum number of clusters this file system can address.
+ *
+ * @return the maximum cluster count supported
+ */
+ long maxClusters() {
+ return this.maxClusters;
+ }
+
+ /**
+ * Returns the human-readable FAT name string as written to the
+ * {@link com.meetwise.fs.BootSector}.
+ *
+ * @return the boot sector label for this FAT type
+ */
+ String getLabel() {
+ return this.label;
+ }
+
+ boolean isReservedCluster(long entry) {
+ return ((entry >= minReservedEntry) && (entry <= maxReservedEntry));
+ }
+
+ boolean isEofCluster(long entry) {
+ return (entry >= eofCluster);
+ }
+
+ long getEofMarker() {
+ return eofMarker;
+ }
+
+ float getEntrySize() {
+ return entrySize;
+ }
+
+ long getBitMask() {
+ return bitMask;
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FatUtils.java b/src/main/java/de/waldheinz/fs/fat/FatUtils.java
new file mode 100644
index 0000000..b653f7c
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FatUtils.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.io.IOException;
+
+/**
+ * <description>
+ *
+ * @author Ewout Prangsma &lt; epr at jnode.org&gt;
+ * @author Fabien DUMINY
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public class FatUtils {
+
+ /**
+ * Gets the offset (in bytes) of the fat with the given index
+ *
+ * @param bs
+ * @param fatNr (0..)
+ * @return long
+ * @throws IOException
+ */
+ public static long getFatOffset(BootSector bs, int fatNr) {
+ long sectSize = bs.getBytesPerSector();
+ long sectsPerFat = bs.getSectorsPerFat();
+ long resSects = bs.getNrReservedSectors();
+
+ long offset = resSects * sectSize;
+ long fatSize = sectsPerFat * sectSize;
+
+ offset += fatNr * fatSize;
+
+ return offset;
+ }
+
+ /**
+ * Gets the offset (in bytes) of the root directory with the given index
+ *
+ * @param bs
+ * @return long
+ * @throws IOException
+ */
+ public static long getRootDirOffset(BootSector bs) {
+ long sectSize = bs.getBytesPerSector();
+ long sectsPerFat = bs.getSectorsPerFat();
+ int fats = bs.getNrFats();
+
+ long offset = getFatOffset(bs, 0);
+
+ offset += fats * sectsPerFat * sectSize;
+
+ return offset;
+ }
+
+ /**
+ * Gets the offset of the data (file) area
+ *
+ * @param bs
+ * @return long
+ * @throws IOException
+ */
+ public static long getFilesOffset(BootSector bs) {
+ long offset = getRootDirOffset(bs);
+
+ offset += bs.getRootDirEntryCount() * 32;
+
+ return offset;
+ }
+
+ private FatUtils() { /* no instances */ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/FsInfoSector.java b/src/main/java/de/waldheinz/fs/fat/FsInfoSector.java
new file mode 100644
index 0000000..0399a9f
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/FsInfoSector.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+
+/**
+ * The FAT32 File System Information Sector.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ * @see http://en.wikipedia.org/wiki/File_Allocation_Table#FS_Information_Sector
+ */
+final class FsInfoSector extends Sector {
+
+ /**
+ * The offset to the free cluster count value in the FS info sector.
+ */
+ public static final int FREE_CLUSTERS_OFFSET = 0x1e8;
+
+ /**
+ * The offset to the "last allocated cluster" value in this sector.
+ */
+ public static final int LAST_ALLOCATED_OFFSET = 0x1ec;
+
+ /**
+ * The offset to the signature of this sector.
+ */
+ public static final int SIGNATURE_OFFSET = 0x1fe;
+
+ private FsInfoSector(BlockDevice device, long offset) {
+ super(device, offset, BootSector.SIZE);
+ }
+
+ /**
+ * Reads a {@code FsInfoSector} as specified by the given
+ * {@code Fat32BootSector}.
+ *
+ * @param bs the boot sector that specifies where the FS info sector is
+ * stored
+ * @return the FS info sector that was read
+ * @throws IOException on read error
+ * @see Fat32BootSector#getFsInfoSectorNr()
+ */
+ public static FsInfoSector read(Fat32BootSector bs) throws IOException {
+ final FsInfoSector result =
+ new FsInfoSector(bs.getDevice(), offset(bs));
+
+ result.read();
+ result.verify();
+ return result;
+ }
+
+ /**
+ * Creates an new {@code FsInfoSector} where the specified
+ * {@code Fat32BootSector} indicates it should be.
+ *
+ * @param bs the boot sector specifying the FS info sector storage
+ * @return the FS info sector instance that was created
+ * @throws IOException on write error
+ * @see Fat32BootSector#getFsInfoSectorNr()
+ */
+ public static FsInfoSector create(Fat32BootSector bs) throws IOException {
+ final int offset = offset(bs);
+
+ if (offset == 0) throw new IOException(
+ "creating a FS info sector at offset 0 is strange");
+
+ final FsInfoSector result =
+ new FsInfoSector(bs.getDevice(), offset(bs));
+
+ result.init();
+ result.write();
+ return result;
+ }
+
+ private static int offset(Fat32BootSector bs) {
+ return bs.getFsInfoSectorNr() * bs.getBytesPerSector();
+ }
+
+ /**
+ * Sets the number of free clusters on the file system stored at
+ * {@link #FREE_CLUSTERS_OFFSET}.
+ *
+ * @param value the new free cluster count
+ * @see Fat#getFreeClusterCount()
+ */
+ public void setFreeClusterCount(long value) {
+ if (getFreeClusterCount() == value) return;
+
+ set32(FREE_CLUSTERS_OFFSET, value);
+ }
+
+ /**
+ * Returns the number of free clusters on the file system as sepcified by
+ * the 32-bit value at {@link #FREE_CLUSTERS_OFFSET}.
+ *
+ * @return the number of free clusters
+ * @see Fat#getFreeClusterCount()
+ */
+ public long getFreeClusterCount() {
+ return get32(FREE_CLUSTERS_OFFSET);
+ }
+
+ /**
+ * Sets the last allocated cluster that was used in the {@link Fat}.
+ *
+ * @param value the FAT's last allocated cluster number
+ * @see Fat#getLastAllocatedCluster()
+ */
+ public void setLastAllocatedCluster(long value) {
+ if (getLastAllocatedCluster() == value) return;
+
+ super.set32(LAST_ALLOCATED_OFFSET, value);
+ }
+
+ /**
+ * Returns the last allocated cluster number of the {@link Fat} of the
+ * file system this FS info sector is part of.
+ *
+ * @return the last allocated cluster number
+ * @see Fat#getLastAllocatedCluster()
+ */
+ public long getLastAllocatedCluster() {
+ return super.get32(LAST_ALLOCATED_OFFSET);
+ }
+
+ private void init() {
+ buffer.position(0x00);
+ buffer.put((byte) 0x52);
+ buffer.put((byte) 0x52);
+ buffer.put((byte) 0x61);
+ buffer.put((byte) 0x41);
+
+ /* 480 reserved bytes */
+
+ buffer.position(0x1e4);
+ buffer.put((byte) 0x72);
+ buffer.put((byte) 0x72);
+ buffer.put((byte) 0x41);
+ buffer.put((byte) 0x61);
+
+ setFreeClusterCount(-1);
+ setLastAllocatedCluster(Fat.FIRST_CLUSTER);
+
+ buffer.position(SIGNATURE_OFFSET);
+ buffer.put((byte) 0x55);
+ buffer.put((byte) 0xaa);
+
+ markDirty();
+ }
+
+ private void verify() throws IOException {
+ if (!(get8(SIGNATURE_OFFSET) == 0x55) ||
+ !(get8(SIGNATURE_OFFSET + 1) == 0xaa)) {
+
+ throw new IOException("invalid FS info sector signature");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return FsInfoSector.class.getSimpleName() +
+ " [freeClusterCount=" + getFreeClusterCount() + //NOI18N
+ ", lastAllocatedCluster=" + getLastAllocatedCluster() + //NOI18N
+ ", offset=" + getOffset() + //NOI18N
+ ", dirty=" + isDirty() + //NOI18N
+ "]"; //NOI18N
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/LittleEndian.java b/src/main/java/de/waldheinz/fs/fat/LittleEndian.java
new file mode 100644
index 0000000..a89f7ae
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/LittleEndian.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2003-2009 JNode.org
+ * 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+
+/**
+ * Little endian (LSB first) conversion methods.
+ *
+ * @author Ewout Prangsma &lt;epr at users.sourceforge.net&gt;
+ */
+final class LittleEndian {
+
+ private LittleEndian() { /* no instances */ }
+
+ /**
+ * Gets an 8-bit unsigned integer from the given byte array at
+ * the given offset.
+ *
+ * @param src the byte offset where to read the value from
+ * @param offset the byte array to extract the value from
+ * @return the integer that was read
+ */
+ public static int getUInt8(byte[] src, int offset) {
+ return src[offset] & 0xFF;
+ }
+
+ /**
+ * Gets a 16-bit unsigned integer from the given byte array at the given offset.
+ *
+ * @param src
+ * @param offset
+ */
+ public static int getUInt16(byte[] src, int offset) {
+ final int v0 = src[offset + 0] & 0xFF;
+ final int v1 = src[offset + 1] & 0xFF;
+ return ((v1 << 8) | v0);
+ }
+
+ /**
+ * Gets a 32-bit unsigned integer from the given byte array at the given offset.
+ *
+ * @param src
+ * @param offset
+ */
+ public static long getUInt32(byte[] src, int offset) {
+ final long v0 = src[offset + 0] & 0xFF;
+ final long v1 = src[offset + 1] & 0xFF;
+ final long v2 = src[offset + 2] & 0xFF;
+ final long v3 = src[offset + 3] & 0xFF;
+ return ((v3 << 24) | (v2 << 16) | (v1 << 8) | v0);
+ }
+
+ /**
+ * Sets an 8-bit integer in the given byte array at the given offset.
+ */
+ public static void setInt8(byte[] dst, int offset, int value) {
+ dst[offset] = (byte) value;
+ }
+
+ /**
+ * Sets a 16-bit integer in the given byte array at the given offset.
+ */
+ public static void setInt16(byte[] dst, int offset, int value) {
+ dst[offset + 0] = (byte) (value & 0xFF);
+ dst[offset + 1] = (byte) ((value >>> 8) & 0xFF);
+ }
+
+ /**
+ * Sets a 32-bit integer in the given byte array at the given offset.
+ */
+ public static void setInt32(byte[] dst, int offset, long value)
+ throws IllegalArgumentException {
+
+ if (value > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ value + " can not be represented in a 32bit dword");
+ }
+
+ dst[offset + 0] = (byte) (value & 0xFF);
+ dst[offset + 1] = (byte) ((value >>> 8) & 0xFF);
+ dst[offset + 2] = (byte) ((value >>> 16) & 0xFF);
+ dst[offset + 3] = (byte) ((value >>> 24) & 0xFF);
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/Sector.java b/src/main/java/de/waldheinz/fs/fat/Sector.java
new file mode 100644
index 0000000..670f149
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/Sector.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+class Sector {
+ private final BlockDevice device;
+ private final long offset;
+
+ /**
+ * The buffer holding the contents of this {@code Sector}.
+ */
+ protected final ByteBuffer buffer;
+
+ private boolean dirty;
+
+ protected Sector(BlockDevice device, long offset, int size) {
+ this.offset = offset;
+ this.device = device;
+ this.buffer = ByteBuffer.allocate(size);
+ this.buffer.order(ByteOrder.LITTLE_ENDIAN);
+ this.dirty = true;
+ }
+
+ /**
+ * Reads the contents of this {@code Sector} from the device into the
+ * internal buffer and resets the "dirty" state.
+ *
+ * @throws IOException on read error
+ * @see #isDirty()
+ */
+ protected void read() throws IOException {
+ buffer.rewind();
+ buffer.limit(buffer.capacity());
+ device.read(offset, buffer);
+ this.dirty = false;
+ }
+
+ public final boolean isDirty() {
+ return this.dirty;
+ }
+
+ protected final void markDirty() {
+ this.dirty = true;
+ }
+
+ /**
+ * Returns the {@code BlockDevice} where this {@code Sector} is stored.
+ *
+ * @return this {@code Sector}'s device
+ */
+ public BlockDevice getDevice() {
+ return this.device;
+ }
+
+ public final void write() throws IOException {
+ if (!isDirty()) return;
+
+ buffer.position(0);
+ buffer.limit(buffer.capacity());
+ device.write(offset, buffer);
+ this.dirty = false;
+ }
+
+ protected int get16(int offset) {
+ return buffer.getShort(offset) & 0xffff;
+ }
+
+ protected long get32(int offset) {
+ return buffer.getInt(offset) & 0xffffffff;
+ }
+
+ protected int get8(int offset) {
+ return buffer.get(offset) & 0xff;
+ }
+
+ protected void set16(int offset, int value) {
+ buffer.putShort(offset, (short) (value & 0xffff));
+ dirty = true;
+ }
+
+ protected void set32(int offset, long value) {
+ buffer.putInt(offset, (int) (value & 0xffffffff));
+ dirty = true;
+ }
+
+ protected void set8(int offset, int value) {
+ if ((value & 0xff) != value) {
+ throw new IllegalArgumentException(
+ value + " too big to be stored in a single octet");
+ }
+
+ buffer.put(offset, (byte) (value & 0xff));
+ dirty = true;
+ }
+
+ /**
+ * Returns the device offset to this {@code Sector}.
+ *
+ * @return the {@code Sector}'s device offset
+ */
+ protected long getOffset() {
+ return this.offset;
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/ShortName.java b/src/main/java/de/waldheinz/fs/fat/ShortName.java
new file mode 100644
index 0000000..0fb382a
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/ShortName.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+
+/**
+ * Represents a "short" (8.3) file name as used by DOS.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+final class ShortName {
+
+ /**
+ * These are taken from the FAT specification.
+ */
+ private final static byte[] ILLEGAL_CHARS = {
+ 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B,
+ 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C
+ };
+
+ /**
+ * The name of the "current directory" (".") entry of a FAT directory.
+ */
+ public final static ShortName DOT = new ShortName(".", ""); // NOI18N
+
+ /**
+ * The name of the "parent directory" ("..") entry of a FAT directory.
+ */
+ public final static ShortName DOT_DOT = new ShortName("..", ""); // NOI18N
+
+ private final char[] name;
+ private boolean mShortNameOnly;
+
+ private ShortName(String nameExt) {
+ if (nameExt.length() > 12)
+ throw new IllegalArgumentException("name too long");
+
+ final int i = nameExt.indexOf('.');
+ final String nameString, extString;
+
+ if (i < 0) {
+ nameString = nameExt.toUpperCase();
+ extString = "";
+ } else {
+ nameString = nameExt.substring(0, i).toUpperCase();
+ extString = nameExt.substring(i + 1).toUpperCase();
+ }
+
+ this.name = toCharArray(nameString, extString);
+ checkValidChars(this.name);
+ }
+
+ ShortName(String name, String ext) {
+ this.name = toCharArray(name, ext);
+ }
+
+ ShortName(char[] name) {
+ this.name = name;
+ }
+
+ public ShortName(char[] nameArr, char[] extArr) {
+ char[] result = new char[11];
+ System.arraycopy(nameArr, 0, result, 0, nameArr.length);
+ System.arraycopy(extArr, 0, result, 8, extArr.length);
+ this.name = result;
+ }
+
+ private static char[] toCharArray(String name, String ext) {
+ checkValidName(name);
+ checkValidExt(ext);
+
+ final char[] result = new char[11];
+ Arrays.fill(result, ' ');
+ System.arraycopy(name.toCharArray(), 0, result, 0, name.length());
+ System.arraycopy(ext.toCharArray(), 0, result, 8, ext.length());
+
+ return result;
+ }
+
+ /**
+ * Calculates the checksum that is used to test a long file name for it's
+ * validity.
+ *
+ * @return the {@code ShortName}'s checksum
+ */
+ public byte checkSum() {
+ final byte[] dest = new byte[11];
+ for (int i = 0; i < 11; i++)
+ dest[i] = (byte) name[i];
+
+ int sum = dest[0];
+ for (int i = 1; i < 11; i++) {
+ sum = dest[i] + (((sum & 1) << 7) + ((sum & 0xfe) >> 1));
+ }
+
+ return (byte) (sum & 0xff);
+ }
+
+ /**
+ * Parses the specified string into a {@code ShortName}.
+ *
+ * @param name the name+extension of the {@code ShortName} to get
+ * @return the {@code ShortName} representing the specified name
+ * @throws IllegalArgumentException if the specified name can not be parsed
+ * into a {@code ShortName}
+ * @see #canConvert(java.lang.String)
+ */
+ public static ShortName get(String name) throws IllegalArgumentException {
+ if (name.equals("."))
+ return DOT;
+ else if (name.equals(".."))
+ return DOT_DOT;
+ else
+ return new ShortName(name);
+ }
+
+ /**
+ * Tests if the specified string can be converted to a {@code ShortName}.
+ *
+ * @param nameExt the string to test
+ * @return if the string can be converted
+ * @see #get(java.lang.String)
+ */
+ public static boolean canConvert(String nameExt) {
+ /* TODO: do this without exceptions */
+ try {
+ ShortName.get(nameExt);
+ return true;
+ } catch (IllegalArgumentException ex) {
+ return false;
+ }
+ }
+
+ public static ShortName parse(byte[] data) {
+ final char[] nameArr = new char[8];
+
+ for (int i = 0; i < nameArr.length; i++) {
+ nameArr[i] = (char) LittleEndian.getUInt8(data, i);
+ }
+
+ final char[] extArr = new char[3];
+ for (int i = 0; i < extArr.length; i++) {
+ extArr[i] = (char) LittleEndian.getUInt8(data, 0x08 + i);
+ }
+
+ return new ShortName(nameArr, extArr);
+ }
+
+ public void write(byte[] dest) {
+ for (int i = 0; i < 11; i++) {
+ dest[i] = (byte) name[i];
+ }
+ }
+
+ public String asSimpleString() {
+ return new String(this.name).trim();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < this.name.length; i++) {
+ sb.append(Integer.toHexString(name[i]));
+ sb.append(' ');
+ }
+ return getClass().getSimpleName() +
+ " [" +
+ asSimpleString() + " -- " +
+ sb.toString() + "]"; // NOI18N
+ }
+
+ private static void checkValidName(String name) {
+ checkString(name, "name", 1, 8);
+ }
+
+ private static void checkValidExt(String ext) {
+ checkString(ext, "extension", 0, 3);
+ }
+
+ private static void checkString(String str, String strType,
+ int minLength, int maxLength) {
+
+ if (str == null)
+ throw new IllegalArgumentException(strType +
+ " is null");
+ if (str.length() < minLength)
+ throw new IllegalArgumentException(strType +
+ " must have at least " + minLength +
+ " characters: " + str);
+ if (str.length() > maxLength)
+ throw new IllegalArgumentException(strType +
+ " has more than " + maxLength +
+ " characters: " + str);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ShortName)) {
+ return false;
+ }
+
+ final ShortName other = (ShortName) obj;
+ return Arrays.equals(name, other.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(this.name);
+ }
+
+ public void setHasShortNameOnly(boolean hasShortNameOnly) {
+ mShortNameOnly = hasShortNameOnly;
+ }
+
+ public boolean hasShortNameOnly() {
+ return mShortNameOnly;
+ }
+
+ /**
+ * Checks if the specified char array consists only of "valid" byte values
+ * according to the FAT specification.
+ *
+ * @param chars the char array to test
+ * @throws IllegalArgumentException if invalid chars are contained
+ */
+ public static void checkValidChars(char[] chars)
+ throws IllegalArgumentException {
+
+ if (chars[0] == 0x20)
+ throw new IllegalArgumentException(
+ "0x20 can not be the first character");
+
+ for (int i = 0; i < chars.length; i++) {
+ if ((chars[i] & 0xff) != chars[i])
+ throw new IllegalArgumentException("multi-byte character at " + i);
+
+ final byte toTest = (byte) (chars[i] & 0xff);
+
+ if (toTest < 0x20 && toTest != 0x05)
+ throw new IllegalArgumentException("character < 0x20 at" + i);
+
+ for (int j = 0; j < ILLEGAL_CHARS.length; j++) {
+ if (toTest == ILLEGAL_CHARS[j])
+ throw new IllegalArgumentException("illegal character " +
+ ILLEGAL_CHARS[j] + " at " + i);
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java b/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java
new file mode 100644
index 0000000..5a64639
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/SuperFloppyFormatter.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.fat;
+
+import de.waldheinz.fs.BlockDevice;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * <p>
+ * Allows to create FAT file systems on {@link BlockDevice}s which follow the
+ * "super floppy" standard. This means that the device will be formatted so
+ * that it does not contain a partition table. Instead, the entire device holds
+ * a single FAT file system.
+ * </p><p>
+ * This class follows the "builder" pattern, which means it's methods always
+ * returns the {@code SuperFloppyFormatter} instance they're called on. This
+ * allows to chain the method calls like this:
+ * <pre>
+ * BlockDevice dev = new RamDisk(16700000);
+ * FatFileSystem fs = SuperFloppyFormatter.get(dev).
+ * setFatType(FatType.FAT12).format();
+ * </pre>
+ *
+ * </p>
+ *
+ * @author Matthias Treydte &lt;matthias.treydte at meetwise.com&gt;
+ */
+public final class SuperFloppyFormatter {
+
+ /**
+ * The media descriptor used (hard disk).
+ */
+ public final static int MEDIUM_DESCRIPTOR_HD = 0xf8;
+
+ /**
+ * The default number of FATs.
+ */
+ public final static int DEFAULT_FAT_COUNT = 2;
+
+ /**
+ * The default number of sectors per track.
+ */
+ public final static int DEFAULT_SECTORS_PER_TRACK = 32;
+
+ /**
+ * The default number of heads.
+ *
+ * @since 0.6
+ */
+ public final static int DEFAULT_HEADS = 64;
+
+ /**
+ * The default number of heads.
+ *
+ * @deprecated the name of this constant was mistyped
+ * @see #DEFAULT_HEADS
+ */
+ @Deprecated
+ public final static int DEFULT_HEADS = DEFAULT_HEADS;
+
+ /**
+ * The default OEM name for file systems created by this class.
+ */
+ public final static String DEFAULT_OEM_NAME = "fat32lib"; //NOI18N
+
+ private static final int MAX_DIRECTORY = 512;
+
+ private final BlockDevice device;
+
+ private String label;
+ private String oemName;
+ private FatType fatType;
+ private int sectorsPerCluster;
+ private int reservedSectors;
+ private int fatCount;
+
+ /**
+ * Creates a new {@code SuperFloppyFormatter} for the specified
+ * {@code BlockDevice}.
+ *
+ * @param device
+ * @throws IOException on error accessing the specified {@code device}
+ */
+ private SuperFloppyFormatter(BlockDevice device) throws IOException {
+ this.device = device;
+ this.oemName = DEFAULT_OEM_NAME;
+ this.fatCount = DEFAULT_FAT_COUNT;
+ setFatType(fatTypeFromDevice());
+ }
+
+ /**
+ * Retruns a {@code SuperFloppyFormatter} instance suitable for formatting
+ * the specified device.
+ *
+ * @param dev the device that should be formatted
+ * @return the formatter for the device
+ * @throws IOException on error creating the formatter
+ */
+ public static SuperFloppyFormatter get(BlockDevice dev) throws IOException {
+ return new SuperFloppyFormatter(dev);
+ }
+
+ /**
+ * Returns the OEM name that will be written to the {@link BootSector}.
+ *
+ * @return the OEM name of the new file system
+ */
+ public String getOemName() {
+ return oemName;
+ }
+
+ /**
+ * Sets the OEM name of the boot sector.
+ *
+ * TODO: throw an exception early if name is invalid (too long, ...)
+ *
+ * @param oemName the new OEM name
+ * @return this {@code SuperFloppyFormatter}
+ * @see BootSector#setOemName(java.lang.String)
+ */
+ public SuperFloppyFormatter setOemName(String oemName) {
+ this.oemName = oemName;
+ return this;
+ }
+
+ /**
+ * Sets the volume label of the file system to create.
+ *
+ * TODO: throw an exception early if label is invalid (too long, ...)
+ *
+ * @param label the new file system label, may be {@code null}
+ * @return this {@code SuperFloppyFormatter}
+ * @see FatFileSystem#setVolumeLabel(java.lang.String)
+ */
+ public SuperFloppyFormatter setVolumeLabel(String label) {
+ this.label = label;
+ return this;
+ }
+
+ /**
+ * Returns the volume label that will be given to the new file system.
+ *
+ * @return the file system label, may be {@code null}
+ * @see FatFileSystem#getVolumeLabel()
+ */
+ public String getVolumeLabel() {
+ return label;
+ }
+
+ private void initBootSector(BootSector bs)
+ throws IOException {
+
+ bs.init();
+ bs.setFileSystemTypeLabel(fatType.getLabel());
+ bs.setNrReservedSectors(reservedSectors);
+ bs.setNrFats(fatCount);
+ bs.setSectorsPerCluster(sectorsPerCluster);
+ bs.setMediumDescriptor(MEDIUM_DESCRIPTOR_HD);
+ bs.setSectorsPerTrack(DEFAULT_SECTORS_PER_TRACK);
+ bs.setNrHeads(DEFAULT_HEADS);
+ bs.setOemName(oemName);
+ }
+
+ /**
+ * Initializes the boot sector and file system for the device. The file
+ * system created by this method will always be in read-write mode.
+ *
+ * @return the file system that was created
+ * @throws IOException on write error
+ */
+ public FatFileSystem format() throws IOException {
+ final int sectorSize = device.getSectorSize();
+ final int totalSectors = (int)(device.getSize() / sectorSize);
+
+ final FsInfoSector fsi;
+ final BootSector bs;
+ if (sectorsPerCluster == 0) throw new AssertionError();
+
+ if (fatType == FatType.FAT32) {
+ bs = new Fat32BootSector(device);
+ initBootSector(bs);
+
+ final Fat32BootSector f32bs = (Fat32BootSector) bs;
+
+ f32bs.setFsInfoSectorNr(1);
+
+ f32bs.setSectorsPerFat(sectorsPerFat(0, totalSectors));
+ final Random rnd = new Random(System.currentTimeMillis());
+ f32bs.setFileSystemId(rnd.nextInt());
+
+ f32bs.setVolumeLabel(label);
+
+ /* create FS info sector */
+ fsi = FsInfoSector.create(f32bs);
+ } else {
+ bs = new Fat16BootSector(device);
+ initBootSector(bs);
+
+ final Fat16BootSector f16bs = (Fat16BootSector) bs;
+
+ final int rootDirEntries = rootDirectorySize(
+ device.getSectorSize(), totalSectors);
+
+ f16bs.setRootDirEntryCount(rootDirEntries);
+ f16bs.setSectorsPerFat(sectorsPerFat(rootDirEntries, totalSectors));
+ if (label != null) f16bs.setVolumeLabel(label);
+ fsi = null;
+ }
+
+
+// bs.write();
+
+ if (fatType == FatType.FAT32) {
+ Fat32BootSector f32bs = (Fat32BootSector) bs;
+ /* possibly writes the boot sector copy */
+ f32bs.writeCopy(device);
+ }
+
+ final Fat fat = Fat.create(bs, 0);
+
+ final AbstractDirectory rootDirStore;
+ if (fatType == FatType.FAT32) {
+ rootDirStore = ClusterChainDirectory.createRoot(fat);
+ fsi.setFreeClusterCount(fat.getFreeClusterCount());
+ fsi.setLastAllocatedCluster(fat.getLastAllocatedCluster());
+ fsi.write();
+ } else {
+ rootDirStore = Fat16RootDirectory.create((Fat16BootSector) bs);
+ }
+
+ final FatLfnDirectory rootDir =
+ new FatLfnDirectory(rootDirStore, fat, false);
+
+ rootDir.flush();
+
+ for (int i = 0; i < bs.getNrFats(); i++) {
+ fat.writeCopy(FatUtils.getFatOffset(bs, i));
+ }
+
+ bs.write();
+
+ FatFileSystem fs = FatFileSystem.read(device, false);
+
+ if (label != null) {
+ fs.setVolumeLabel(label);
+ }
+
+ fs.flush();
+ return fs;
+ }
+
+ private int sectorsPerFat(int rootDirEntries, int totalSectors)
+ throws IOException {
+
+ final int bps = device.getSectorSize();
+ final int rootDirSectors =
+ ((rootDirEntries * 32) + (bps - 1)) / bps;
+ final long tmp1 =
+ totalSectors - (this.reservedSectors + rootDirSectors);
+ int tmp2 = (256 * this.sectorsPerCluster) + this.fatCount;
+
+ if (fatType == FatType.FAT32)
+ tmp2 /= 2;
+
+ final int result = (int) ((tmp1 + (tmp2 - 1)) / tmp2);
+
+ return result;
+ }
+
+ /**
+ * Determines a usable FAT type from the {@link #device} by looking at the
+ * {@link BlockDevice#getSize() device size} only.
+ *
+ * @return the suggested FAT type
+ * @throws IOException on error determining the device's size
+ */
+ private FatType fatTypeFromDevice() throws IOException {
+ return fatTypeFromSize(device.getSize());
+ }
+
+ /**
+ * Determines a usable FAT type from the {@link #device} by looking at the
+ * {@link BlockDevice#getSize() device size} only.
+ *
+ * @return the suggested FAT type
+ * @throws IOException on error determining the device's size
+ */
+ public static FatType fatTypeFromSize(long sizeInBytes) {
+ final long sizeInMb = sizeInBytes / (1024 * 1024);
+ if (sizeInMb < 4) return FatType.FAT12;
+ else if (sizeInMb < 512) return FatType.FAT16;
+ else return FatType.FAT32;
+ }
+
+ public static int clusterSizeFromSize(long sizeInBytes, int sectorSize){
+ switch(fatTypeFromSize(sizeInBytes)) {
+ case FAT12:
+ return sectorsPerCluster12(sizeInBytes, sectorSize);
+ case FAT16:
+ return sectorsPerCluster16FromSize(sizeInBytes, sectorSize);
+ case FAT32:
+ return sectorsPerCluster32FromSize(sizeInBytes, sectorSize);
+
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns the exact type of FAT the will be created by this formatter.
+ *
+ * @return the FAT type
+ */
+ public FatType getFatType() {
+ return this.fatType;
+ }
+
+ /**
+ * Sets the type of FAT that will be created by this
+ * {@code SuperFloppyFormatter}.
+ *
+ * @param fatType the desired {@code FatType}
+ * @return this {@code SuperFloppyFormatter}
+ * @throws IOException on error setting the {@code fatType}
+ * @throws IllegalArgumentException if {@code fatType} does not support the
+ * size of the device
+ */
+ public SuperFloppyFormatter setFatType(FatType fatType)
+ throws IOException, IllegalArgumentException {
+
+ if (fatType == null) throw new NullPointerException();
+
+ switch (fatType) {
+ case FAT12: case FAT16:
+ this.reservedSectors = 1;
+ break;
+
+ case FAT32:
+ this.reservedSectors = 32;
+ }
+
+ this.sectorsPerCluster = defaultSectorsPerCluster(fatType);
+ this.fatType = fatType;
+
+ return this;
+ }
+
+ private static int rootDirectorySize(int bps, int nbTotalSectors) {
+ final int totalSize = bps * nbTotalSectors;
+ if (totalSize >= MAX_DIRECTORY * 5 * 32) {
+ return MAX_DIRECTORY;
+ } else {
+ return totalSize / (5 * 32);
+ }
+ }
+
+ static private int MAX_FAT32_CLUSTERS = 0x0FFFFFF5;
+
+ static private int sectorsPerCluster32FromSize(long size, int sectorSize) {
+ final long sectors = size / sectorSize;
+
+ if (sectors <= 66600) throw new IllegalArgumentException(
+ "disk too small for FAT32");
+
+ return
+ sectors > 67108864 ? 64 :
+ sectors > 33554432 ? 32 :
+ sectors > 16777216 ? 16 :
+ sectors > 532480 ? 8 : 1;
+ }
+
+ private int sectorsPerCluster32() throws IOException {
+ if (this.reservedSectors != 32) throw new IllegalStateException(
+ "number of reserved sectors must be 32");
+
+ if (this.fatCount != 2) throw new IllegalStateException(
+ "number of FATs must be 2");
+
+ final long sectors = device.getSize() / device.getSectorSize();
+
+ if (sectors <= 66600) throw new IllegalArgumentException(
+ "disk too small for FAT32");
+
+ return sectorsPerCluster32FromSize(device.getSize(), device.getSectorSize());
+ }
+
+ static private int MAX_FAT16_CLUSTERS = 65524;
+
+ static private int sectorsPerCluster16FromSize(long size, int sectorSize) {
+ final long sectors = size / sectorSize;
+
+ if (sectors <= 8400) throw new IllegalArgumentException(
+ "disk too small for FAT16");
+
+ if (sectors > 4194304) throw new IllegalArgumentException(
+ "disk too large for FAT16");
+
+ return
+ sectors > 2097152 ? 64 :
+ sectors > 1048576 ? 32 :
+ sectors > 524288 ? 16 :
+ sectors > 262144 ? 8 :
+ sectors > 32680 ? 4 : 2;
+ }
+
+ private int sectorsPerCluster16() throws IOException {
+ if (this.reservedSectors != 1) throw new IllegalStateException(
+ "number of reserved sectors must be 1");
+
+ if (this.fatCount != 2) throw new IllegalStateException(
+ "number of FATs must be 2");
+
+ long size = device.getSize();
+ int sectorSize = device.getSectorSize();
+ return sectorsPerCluster16FromSize(size, sectorSize);
+ }
+
+ private int defaultSectorsPerCluster(FatType fatType) throws IOException {
+ long size = device.getSize();
+ int sectorSize = device.getSectorSize();
+
+ switch (fatType) {
+ case FAT12:
+ return sectorsPerCluster12(size, sectorSize);
+
+ case FAT16:
+ return sectorsPerCluster16();
+
+ case FAT32:
+ return sectorsPerCluster32();
+
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ static private int sectorsPerCluster12(long size, int sectorSize) {
+ int result = 1;
+
+ final long sectors = size / sectorSize;
+
+ while (sectors / result > Fat16BootSector.MAX_FAT12_CLUSTERS) {
+ result *= 2;
+ if (result * size > 4096) throw new
+ IllegalArgumentException("disk too large for FAT12");
+ }
+
+ return result;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/fat/package-info.java b/src/main/java/de/waldheinz/fs/fat/package-info.java
new file mode 100644
index 0000000..4baf68f
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/fat/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Contains the classes implementing the FAT(12/16/32) file system.
+ */
+package de.waldheinz.fs.fat;
diff --git a/src/main/java/de/waldheinz/fs/package-info.java b/src/main/java/de/waldheinz/fs/package-info.java
new file mode 100644
index 0000000..eb534b4
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Contains the file-system independent classes and interfaces, as well as
+ * the {@link de.waldheinz.fs.FileSystemFactory} for creating
+ * {@link de.waldheinz.fs.FileSystem} instances.
+ */
+package de.waldheinz.fs;
diff --git a/src/main/java/de/waldheinz/fs/util/FileDisk.java b/src/main/java/de/waldheinz/fs/util/FileDisk.java
new file mode 100644
index 0000000..0f3f0bb
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/util/FileDisk.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.util;
+
+import de.waldheinz.fs.BlockDevice;
+import de.waldheinz.fs.ReadOnlyException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * This is a {@code BlockDevice} that uses a {@link File} as it's backing store.
+ *
+ * @author Matthias Treydte &lt;matthias.treydte at meetwise.com&gt;
+ */
+public final class FileDisk implements BlockDevice {
+
+ /**
+ * The number of bytes per sector for all {@code FileDisk} instances.
+ */
+ public final static int BYTES_PER_SECTOR = 512;
+
+ private final RandomAccessFile raf;
+ private final FileChannel fc;
+ private final boolean readOnly;
+ private boolean closed;
+
+ /**
+ * Creates a new instance of {@code FileDisk} for the specified
+ * {@code File}.
+ *
+ * @param file the file that holds the disk contents
+ * @param readOnly if the file should be opened in read-only mode, which
+ * will result in a read-only {@code FileDisk} instance
+ * @throws FileNotFoundException if the specified file does not exist
+ * @see #isReadOnly()
+ */
+ public FileDisk(File file, boolean readOnly) throws FileNotFoundException {
+ if (!file.exists()) throw new FileNotFoundException();
+
+ this.readOnly = readOnly;
+ this.closed = false;
+ final String modeString = readOnly ? "r" : "rw"; //NOI18N
+ this.raf = new RandomAccessFile(file, modeString);
+ this.fc = raf.getChannel();
+ }
+
+ public FileDisk(RandomAccessFile raf, FileChannel fc, boolean readOnly) {
+ this.closed = false;
+ this.raf = raf;
+ this.fc = fc;
+ this.readOnly = readOnly;
+ }
+
+ private FileDisk(RandomAccessFile raf, boolean readOnly) {
+ this.closed = false;
+ this.raf = raf;
+ this.fc = raf.getChannel();
+ this.readOnly = readOnly;
+ }
+
+ /**
+ * Creates a new {@code FileDisk} of the specified size. The
+ * {@code FileDisk} returned by this method will be writable.
+ *
+ * @param file the file to hold the {@code FileDisk} contents
+ * @param size the size of the new {@code FileDisk}
+ * @return the created {@code FileDisk} instance
+ * @throws IOException on error creating the {@code FileDisk}
+ */
+ public static FileDisk create(File file, long size) throws IOException {
+ try {
+ final RandomAccessFile raf =
+ new RandomAccessFile(file, "rw"); //NOI18N
+ raf.setLength(size);
+
+ return new FileDisk(raf, false);
+ } catch (FileNotFoundException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ checkClosed();
+
+ return raf.length();
+ }
+
+ @Override
+ public void read(long devOffset, ByteBuffer dest) throws IOException {
+ checkClosed();
+
+ int toRead = dest.remaining();
+ if ((devOffset + toRead) > getSize()) throw new IOException(
+ "reading past end of device");
+
+ while (toRead > 0) {
+ final int read = fc.read(dest, devOffset);
+ if (read < 0) throw new IOException();
+ toRead -= read;
+ devOffset += read;
+ }
+ }
+
+ @Override
+ public void write(long devOffset, ByteBuffer src) throws IOException {
+ checkClosed();
+
+ if (this.readOnly) throw new ReadOnlyException();
+
+ int toWrite = src.remaining();
+
+ if ((devOffset + toWrite) > getSize()) throw new IOException(
+ "writing past end of file");
+
+ while (toWrite > 0) {
+ final int written = fc.write(src, devOffset);
+ if (written < 0) throw new IOException();
+ toWrite -= written;
+ devOffset += written;
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ checkClosed();
+ }
+
+ @Override
+ public int getSectorSize() {
+ checkClosed();
+
+ return BYTES_PER_SECTOR;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isClosed()) return;
+
+ this.closed = true;
+ this.fc.close();
+ this.raf.close();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return this.closed;
+ }
+
+ private void checkClosed() {
+ if (closed) throw new IllegalStateException("device already closed");
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ checkClosed();
+
+ return this.readOnly;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/util/RamDisk.java b/src/main/java/de/waldheinz/fs/util/RamDisk.java
new file mode 100644
index 0000000..51a1be1
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/util/RamDisk.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+package de.waldheinz.fs.util;
+
+import de.waldheinz.fs.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * A {@link BlockDevice} that lives entirely in heap memory. This is basically
+ * a RAM disk. A {@code RamDisk} is always writable.
+ *
+ * @author Matthias Treydte &lt;waldheinz at gmail.com&gt;
+ */
+public final class RamDisk implements BlockDevice {
+
+ /**
+ * The default sector size for {@code RamDisk}s.
+ */
+ public final static int DEFAULT_SECTOR_SIZE = 512;
+
+ private final int sectorSize;
+ private final ByteBuffer data;
+ private final int size;
+ private boolean closed;
+
+ /**
+ * Reads a GZIP compressed disk image from the specified input stream and
+ * returns a {@code RamDisk} holding the decompressed image.
+ *
+ * @param in the stream to read the disk image from
+ * @return the decompressed {@code RamDisk}
+ * @throws IOException on read or decompression error
+ */
+ public static RamDisk readGzipped(InputStream in) throws IOException {
+ final GZIPInputStream zis = new GZIPInputStream(in);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ final byte[] buffer = new byte[4096];
+
+ int read = zis.read(buffer);
+ int total = 0;
+
+ while (read >= 0) {
+ total += read;
+ bos.write(buffer, 0, read);
+ read = zis.read(buffer);
+ }
+
+ if (total < DEFAULT_SECTOR_SIZE) throw new IOException(
+ "read only " + total + " bytes"); //NOI18N
+
+ final ByteBuffer bb = ByteBuffer.wrap(bos.toByteArray(), 0, total);
+ return new RamDisk(bb, DEFAULT_SECTOR_SIZE);
+ }
+
+ private RamDisk(ByteBuffer buffer, int sectorSize) {
+ this.size = buffer.limit();
+ this.sectorSize = sectorSize;
+ this.data = buffer;
+ this.closed = false;
+ }
+
+ /**
+ * Creates a new instance of {@code RamDisk} of this specified
+ * size and using the {@link #DEFAULT_SECTOR_SIZE}.
+ *
+ * @param size the size of the new block device
+ */
+ public RamDisk(int size) {
+ this(size, DEFAULT_SECTOR_SIZE);
+ }
+
+ /**
+ * Creates a new instance of {@code RamDisk} of this specified
+ * size and sector size
+ *
+ * @param size the size of the new block device
+ * @param sectorSize the sector size of the new block device
+ */
+ public RamDisk(int size, int sectorSize) {
+ if (sectorSize < 1) throw new IllegalArgumentException(
+ "invalid sector size"); //NOI18N
+
+ this.sectorSize = sectorSize;
+ this.size = size;
+ this.data = ByteBuffer.allocate(size);
+ }
+
+ @Override
+ public long getSize() {
+ checkClosed();
+ return this.size;
+ }
+
+ @Override
+ public void read(long devOffset, ByteBuffer dest) throws IOException {
+ checkClosed();
+
+ if (devOffset > getSize()){
+ final StringBuilder sb = new StringBuilder();
+ sb.append("read at ").append(devOffset);
+ sb.append(" is off size (").append(getSize()).append(")");
+
+ throw new IllegalArgumentException(sb.toString());
+ }
+
+ data.limit((int) (devOffset + dest.remaining()));
+ data.position((int) devOffset);
+
+ dest.put(data);
+ }
+
+ @Override
+ public void write(long devOffset, ByteBuffer src) throws IOException {
+ checkClosed();
+
+ if (devOffset + src.remaining() > getSize()) throw new
+ IllegalArgumentException(
+ "offset=" + devOffset +
+ ", length=" + src.remaining() +
+ ", size=" + getSize());
+
+ data.limit((int) (devOffset + src.remaining()));
+ data.position((int) devOffset);
+
+
+ data.put(src);
+ }
+
+ /**
+ * Returns a slice of the {@code ByteBuffer} that is used by this
+ * {@code RamDisk} as it's backing store. The returned buffer will be
+ * live (reflecting any changes made through the
+ * {@link #write(long, java.nio.ByteBuffer) method}, but read-only.
+ *
+ * @return a buffer holding the contents of this {@code RamDisk}
+ */
+ public ByteBuffer getBuffer() {
+ return this.data.asReadOnlyBuffer();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ checkClosed();
+ }
+
+ @Override
+ public int getSectorSize() {
+ checkClosed();
+ return this.sectorSize;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.closed = true;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return this.closed;
+ }
+
+ private void checkClosed() {
+ if (closed) throw new IllegalStateException("device already closed");
+ }
+
+ /**
+ * Returns always {@code false}, as a {@code RamDisk} is always writable.
+ *
+ * @return always {@code false}
+ */
+ @Override
+ public boolean isReadOnly() {
+ checkClosed();
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/de/waldheinz/fs/util/package-info.java b/src/main/java/de/waldheinz/fs/util/package-info.java
new file mode 100644
index 0000000..afde34e
--- /dev/null
+++ b/src/main/java/de/waldheinz/fs/util/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009,2010 Matthias Treydte <mt@waldheinz.de>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; If not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Contains some utility classes that are useful independent of the file system
+ * type.
+ */
+package de.waldheinz.fs.util;
+