summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Galpin <dgalpin@google.com>2012-08-27 14:56:37 -0700
committerDan Galpin <dgalpin@google.com>2012-08-27 14:56:37 -0700
commit8053088ccd55a94d6643a12660e967a5e8dfd5de (patch)
treea77cad7ba45423528ad67b2c0f7b12eeb1e1fc36
parent6950a7fcfda2d03446514e315b6bf6bd5c476496 (diff)
downloadfat32lib-8053088ccd55a94d6643a12660e967a5e8dfd5de.tar.gz
External library with modifications. fat32-lib.
Signed-off-by: Dan Galpin <dgalpin@google.com> Change-Id: Ie59bda0c04d629c4b2723f77478957105761e5cf
-rw-r--r--Android.mk23
-rw-r--r--CleanSpec.mk49
-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
41 files changed, 7304 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/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;
+