External library with modifications.  fat32-lib.
Signed-off-by: Dan Galpin <dgalpin@google.com>

Change-Id: Ie59bda0c04d629c4b2723f77478957105761e5cf
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;
+