[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cdi-devel] [RFC][PATCH v2] fat: Beginnings of a FAT file system driver



This adds a FAT driver for FAT12/16/32. Long file names are
not yet supported.

Signed-off-by: Kevin Wolf <kevin@xxxxxxxxxx>
---
So we're getting closer... This one adds some basic write support.
Before merging it we still need to add:

* Error handling in a lot of places
* Support for yet unimplemented cdi/fs functions (e.g. truncate)
* Documentation! Currently the only documented functions are those of
  the CDI interface (and the documentation is a straight copy of the
  German-only cdi/fs.h comments)
* Some additional tests, especially for FAT 32

Anyway, if you handle it carefully this version of the driver actually
works r/w on tyndur.


 fat/include              |    1 +
 fat/libfat/dir.c         |  536 ++++++++++++++++++++++++++++++++++++++
 fat/libfat/file.c        |  641 ++++++++++++++++++++++++++++++++++++++++++++++
 fat/libfat/fs.c          |   80 ++++++
 fat/libfat/include/fat.h |  287 +++++++++++++++++++++
 fat/main.c               |  498 +++++++++++++++++++++++++++++++++++
 6 files changed, 2043 insertions(+), 0 deletions(-)
 create mode 120000 fat/include
 create mode 100644 fat/libfat/dir.c
 create mode 100644 fat/libfat/file.c
 create mode 100644 fat/libfat/fs.c
 create mode 100644 fat/libfat/include/fat.h
 create mode 100644 fat/main.c

diff --git a/fat/include b/fat/include
new file mode 120000
index 0000000..6f62628
--- /dev/null
+++ b/fat/include
@@ -0,0 +1 @@
+libfat/include/
\ No newline at end of file
diff --git a/fat/libfat/dir.c b/fat/libfat/dir.c
new file mode 100644
index 0000000..28926d2
--- /dev/null
+++ b/fat/libfat/dir.c
@@ -0,0 +1,536 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include "fat.h"
+
+static int fat_rootdir_open(struct fat_fs* fs, struct fat_dir* dir)
+{
+    int64_t rootdir_offset = fs->bpb.reserved_sectors +
+        (fs->bpb.num_fats * fs->bpb.fat_sectors);
+
+    uint32_t rootdir_sectors =
+        (fs->bpb.rootdir_length * sizeof(struct fat_disk_dir_entry) + 511)
+        / 512;
+
+    void* buf = malloc(512 * rootdir_sectors);
+
+    // Wurzelverzeichnis einlesen
+    if (!fs->dev_read(rootdir_offset * 512, rootdir_sectors * 512, buf,
+        fs->dev_private))
+    {
+        goto fail;
+    }
+
+    // Rueckgabestruktur befuellen
+    dir->entries = buf;
+    dir->fs = fs;
+    dir->i = 0;
+    dir->first_cluster = 0;
+    dir->num = rootdir_sectors * 512 / sizeof(struct fat_disk_dir_entry);
+
+    return 0;
+
+fail:
+    free(buf);
+    return -1;
+}
+
+static int fat_rootdir32_open(struct fat_fs* fs, struct fat_dir* dir)
+{
+    struct fat_file file;
+    struct fat32_extended_bpb* ext = &fs->bpb.extended.fat32;
+    struct fat_dir_entry dentry = {
+        .size = 0,
+        .first_cluster = ext->rootdir_cluster,
+    };
+    int ret;
+    void* buf;
+
+    ret = fat_file_open_by_dir_entry(fs, &dentry, &file);
+    if (ret < 0) {
+        return ret;
+    }
+
+    buf = malloc(file.size);
+
+    ret = fat_file_read(&file, buf, 0, file.size);
+    fat_file_close(&file);
+    if (ret < 0) {
+        free(buf);
+        return ret;
+    }
+
+    /* Rueckgabestruktur befuellen */
+    dir->entries = buf;
+    dir->fs = fs;
+    dir->i = 0;
+    dir->first_cluster = 0;
+    dir->num = file.size / sizeof(struct fat_disk_dir_entry);
+
+    return 0;
+}
+
+static int fat_subdir_open_by_dir_entry(struct fat_fs* fs,
+    struct fat_dir_entry* dentry, struct fat_dir* subdir)
+{
+    struct fat_file file;
+    void* buf;
+    int first_cluster;
+    int ret;
+
+    ret = fat_file_open_by_dir_entry(fs, dentry, &file);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /*
+     * Ein Verzeichnis ohne Cluster ist ein korruptes FS, fehlschlagen ist also
+     * in Ordnung. Wir sollten nur nicht auf file.blocklist[0] zugreifen.
+     */
+    if (file.blocklist == NULL) {
+        fat_file_close(&file);
+        return -FAT_ENOENT;
+    }
+
+    buf = malloc(file.size);
+    first_cluster = file.blocklist[0];
+
+    ret = fat_file_read(&file, buf, 0, file.size);
+    fat_file_close(&file);
+    if (ret < 0) {
+        free(buf);
+        return ret;
+    }
+
+    /* Rueckgabestruktur befuellen */
+    subdir->entries = buf;
+    subdir->fs = fs;
+    subdir->i = 0;
+    subdir->first_cluster = first_cluster;
+    subdir->num = file.size / sizeof(struct fat_disk_dir_entry);
+
+    return 0;
+}
+
+int fat_subdir_open(struct fat_fs* fs, struct fat_dir* dir, const char* path,
+    struct fat_dir* subdir)
+{
+    struct fat_dir_entry dentry;
+    int ret;
+
+    /* Vaterverzeichnis nach Unterverzeichnis durchsuchen */
+    do {
+        ret = fat_dir_read(dir, &dentry);
+        if (ret < 0) {
+            return ret;
+        }
+
+        if ((ret >= 0) && !strcmp(dentry.name, path)) {
+            goto found;
+        }
+    } while (ret >= 0);
+
+    return -FAT_ENOENT;
+
+    /* Verzeichnis als Datei oeffnen und Eintraege laden */
+found:
+    return fat_subdir_open_by_dir_entry(fs, &dentry, subdir);
+}
+
+int fat_dir_open(struct fat_fs* fs, const char* _path, struct fat_dir* dir)
+{
+    int ret;
+    char* path = strdup(_path);
+    char* p;
+    struct fat_dir subdir;
+
+    if (fs->type == FS_TYPE_FAT32) {
+        ret = fat_rootdir32_open(fs, dir);
+    } else {
+        ret = fat_rootdir_open(fs, dir);
+    }
+
+    if (ret < 0) {
+        goto out;
+    }
+
+    /* Pfade muessen mit einem Slash anfangen */
+    if (*path != '/') {
+        ret = -FAT_EINVAL;
+        goto out;
+    }
+
+    /* Jetzt die Unterverzeichnisse parsen */
+    p = strtok(path + 1, "/");
+    while (p != NULL) {
+        ret = fat_subdir_open(fs, dir, p, &subdir);
+        fat_dir_close(dir);
+        if (ret < 0) {
+            goto out;
+        }
+        *dir = subdir;
+        p = strtok(NULL, "/");
+    }
+
+    ret = 0;
+out:
+    free(path);
+    return ret;
+}
+
+static void short_fn_to_string(struct fat_disk_dir_entry* entry, char* buf)
+{
+    int i;
+    int pos = -1;
+    int ext_start;
+
+    // 8 Zeichen Name in Kleinbuchstaben
+    for (i = 0; i < 8; i++) {
+        buf[i] = tolower(entry->name[i]);
+        if (!isspace(buf[i])) {
+            pos = i;
+        }
+    }
+
+    // Punkt
+    buf[++pos] = '.';
+
+    // 3 Zeichen Erweiterung in Kleinbuchstaben
+    ext_start = pos + 1;
+    for (i = 0; i < 3; i++) {
+        buf[ext_start + i] = tolower(entry->ext[i]);
+        if (!isspace(buf[ext_start + i])) {
+            pos = ext_start + i + 1;
+        }
+    }
+
+    // Nullterminieren
+    buf[pos] = '\0';
+}
+
+static void string_to_short_fn(struct fat_disk_dir_entry* entry,
+    const char* buf)
+{
+    int i;
+    const char* p;
+
+    for (i = 0; i < 8; i++) {
+        switch (buf[i]) {
+            case '.':
+                memset(&entry->name[i], ' ', 8 - i);
+                p = &buf[i + 1];
+                goto parse_ext;
+            case '\0':
+                memset(&entry->name[i], ' ', 8 - i);
+                p = &buf[i];
+                goto parse_ext;
+            default:
+                entry->name[i] = toupper(buf[i]);
+                break;
+        }
+    }
+
+    p = strchr(&buf[8], '.');
+parse_ext:
+    if (!p || !*p) {
+        memset(entry->ext, ' ', 3);
+        return;
+    }
+
+    for (i = 0; i < 3; i++) {
+        if (p[i] == '\0') {
+            memset(&entry->ext[i], ' ', 3 - i);
+            return;
+        }
+        entry->ext[i] = toupper(p[i]);
+    }
+}
+
+static int is_free_entry(struct fat_disk_dir_entry* entry)
+{
+    // Die Datei existiert nicht
+    if (entry->name[0] == '\0') {
+        return 1;
+    }
+
+    // Die Datei ist geloescht
+    if (entry->name[0] == (char) 0xE5) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int is_valid_entry(struct fat_disk_dir_entry* entry)
+{
+    // Die Datei existiert nicht oder ist geloescht
+    if (is_free_entry(entry)) {
+        return 0;
+    }
+
+    // Die Datentraegerbezeichnung interessiert uns nicht
+    if (entry->attrib & FAT_ATTRIB_VOLUME) {
+        return 0;
+    }
+
+    // . und .. werden ignoriert
+    if (entry->name[0] == '.') {
+        return 0;
+    }
+
+    return 1;
+}
+
+static void dir_entry_from_disk(struct fat_dir_entry* entry,
+    struct fat_disk_dir_entry* disk_entry)
+{
+    short_fn_to_string(disk_entry, entry->name);
+
+    entry->first_cluster        = disk_entry->first_cluster;
+    entry->size                 = disk_entry->size;
+    entry->attrib               = disk_entry->attrib;
+}
+
+static void dir_entry_to_disk(struct fat_file* file,
+    struct fat_disk_dir_entry* disk_entry)
+{
+    string_to_short_fn(disk_entry, file->dir_entry.name);
+
+    disk_entry->first_cluster   = file->dir_entry.first_cluster;
+    disk_entry->size            = file->size;
+    disk_entry->attrib          = file->dir_entry.attrib;
+}
+
+int fat_dir_read(struct fat_dir* dir, struct fat_dir_entry* entry)
+{
+    if (dir->i < 0 || dir->i >= dir->num) {
+        return -1;
+    }
+
+    // Ungueltige Eintraege ueberspringen
+    while (dir->i < dir->num && !is_valid_entry(&dir->entries[dir->i])) {
+        short_fn_to_string(&dir->entries[dir->i], entry->name);
+        dir->i++;
+    }
+
+    // Fertig?
+    if (dir->i >= dir->num) {
+        return -1;
+    }
+
+    dir_entry_from_disk(entry, &dir->entries[dir->i]);
+    entry->dir_first_cluster    = dir->first_cluster;
+    entry->index_in_dir         = dir->i;
+
+    dir->i++;
+
+    return 0;
+}
+
+int fat_dir_update_rootdir_entry(struct fat_file* file)
+{
+    struct fat_fs* fs = file->fs;
+    struct fat_disk_dir_entry dentry;
+
+#if 0
+    if (i < 0 ||i >= dir->num) {
+        return -FAT_EINVAL;
+    }
+#endif
+
+    int64_t rootdir_offset = fs->bpb.reserved_sectors +
+        (fs->bpb.num_fats * fs->bpb.fat_sectors);
+    uint32_t offset = rootdir_offset * 512 +
+        file->dir_entry.index_in_dir * sizeof(struct fat_disk_dir_entry);
+
+    if (!fs->dev_read(offset, sizeof(struct fat_disk_dir_entry), &dentry,
+        fs->dev_private))
+    {
+        return -FAT_EIO;
+    }
+
+    dir_entry_to_disk(file, &dentry);
+
+    if (!fs->dev_write(offset, sizeof(struct fat_disk_dir_entry), &dentry,
+        fs->dev_private))
+    {
+        return -FAT_EIO;
+    }
+
+    return 0;
+}
+
+int fat_dir_update_subdir_entry(struct fat_file* file)
+{
+    struct fat_fs* fs = file->fs;
+    struct fat_dir_entry dentry = {
+        .first_cluster = file->dir_entry.dir_first_cluster,
+        .size = 0,
+        .attrib = FAT_ATTRIB_DIR,
+    };
+    struct fat_disk_dir_entry file_dentry;
+    struct fat_file dir;
+    int ret;
+
+#if 0
+    if (i < 0 ||i >= dir->num) {
+        return -FAT_EINVAL;
+    }
+#endif
+
+    uint32_t offset =
+        file->dir_entry.index_in_dir * sizeof(struct fat_disk_dir_entry);
+
+
+    ret = fat_file_open_by_dir_entry(fs, &dentry, &dir);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* Bisherigen Verzeichniseintrag laden, falls vorhanden */
+    if (offset < file->size) {
+        ret = fat_file_read(&dir, &file_dentry, offset,
+             sizeof(struct fat_disk_dir_entry));
+        if (ret < 0) {
+            return ret;
+        }
+    } else {
+        memset(&file_dentry, 0, sizeof(file_dentry));
+    }
+
+    /* Felder aktualisieren */
+    dir_entry_to_disk(file, &file_dentry);
+
+    ret = fat_file_write(&dir, &file_dentry, offset,
+        sizeof(struct fat_disk_dir_entry));
+    if (ret < 0) {
+        return ret;
+    }
+
+    fat_file_close(&dir);
+
+    return 0;
+}
+
+int fat_dir_update_entry(struct fat_file* file)
+{
+    if (file->dir_entry.dir_first_cluster == 0) {
+        return fat_dir_update_rootdir_entry(file);
+    } else {
+        return fat_dir_update_subdir_entry(file);
+    }
+}
+
+/* FIXME Verzeichnis lesen nach create_entry
+ * FIXME Zwei vergroessernde Aufrufe ueberschreiben sich gegenseitig
+ * FIXME Verzeichnis erweitern auf neuen nicht genullten Cluster */
+int fat_dir_create_entry(struct fat_dir* dir, const char* name, int attr,
+    struct fat_dir_entry* dir_entry)
+{
+    int i;
+    int ret;
+
+    /* Dateistruktur faken, deren Verzeichniseintrag "geaendert" werden soll */
+    struct fat_file file = {
+        .fs = dir->fs,
+        .size = 0,
+        .dir_entry = {
+            .dir_first_cluster = dir->first_cluster,
+            .index_in_dir = 0,
+            .first_cluster = 0,
+            .attrib = attr,
+        },
+    };
+
+    /* Freien Index im Verzeichnis suchen */
+    for (i = 0; i < dir->num; i++) {
+        if (is_free_entry(&dir->entries[i])) {
+            goto found;
+        }
+    }
+
+    /* Ausser dem Wurzelverzeichnis koennen wir alles vergroessern */
+    if (dir->first_cluster != 0) {
+        i = dir->num;
+        goto found;
+    }
+
+    return -FAT_ENOENT;
+
+found:
+    strncpy(file.dir_entry.name, name, sizeof(file.dir_entry.name));
+    file.dir_entry.name[sizeof(file.dir_entry.name) - 1] = '\0';
+    file.dir_entry.index_in_dir = i;
+
+    if (attr & FAT_ATTRIB_DIR) {
+        fat_file_alloc_first_cluster(&file);
+    }
+
+    /* Ins Verzeichnis eintragen */
+    ret = fat_dir_update_entry(&file);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (dir_entry) {
+        *dir_entry = file.dir_entry;
+    }
+
+    return 0;
+}
+
+int fat_dir_create(struct fat_dir* dir, const char* name,
+    struct fat_dir_entry* dir_entry)
+{
+    struct fat_file dir_file;
+    struct fat_dir_entry dentry;
+    int ret;
+    uint32_t cluster_size = dir->fs->bpb.cluster_sectors * 512;
+    uint8_t buf[cluster_size];
+    struct fat_disk_dir_entry* disk_dentry;
+
+    /* Verzeichnis anlegen */
+    ret = fat_dir_create_entry(dir, name, FAT_ATTRIB_DIR, &dentry);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* Und einen genullten Cluster reservieren */
+    ret = fat_file_open_by_dir_entry(dir->fs, &dentry, &dir_file);
+    if (ret < 0) {
+        return ret;
+    }
+
+    disk_dentry = (struct fat_disk_dir_entry*) buf;
+    memset(buf, 0, cluster_size);
+    disk_dentry[0] = (struct fat_disk_dir_entry) {
+        .name           = ".       ",
+        .ext            = "   ",
+        .attrib         = FAT_ATTRIB_DIR,
+        .first_cluster  = dentry.first_cluster,
+        .size           = 0,
+    };
+    disk_dentry[1] = (struct fat_disk_dir_entry) {
+        .name           = "..      ",
+        .ext            = "   ",
+        .attrib         = FAT_ATTRIB_DIR,
+        .first_cluster  = dir->first_cluster,
+        .size           = 0,
+    };
+
+    ret = fat_file_write(&dir_file, buf, 0, cluster_size);
+    fat_file_close(&dir_file);
+
+    if (dir_entry) {
+        *dir_entry = dentry;
+    }
+
+    return ret;
+}
+
+int fat_dir_close(struct fat_dir* dir)
+{
+    free(dir->entries);
+    return 0;
+}
diff --git a/fat/libfat/file.c b/fat/libfat/file.c
new file mode 100644
index 0000000..84644f5
--- /dev/null
+++ b/fat/libfat/file.c
@@ -0,0 +1,641 @@
+#include <string.h>
+#include <assert.h>
+
+#include "fat.h"
+
+#define NUM_CLUSTERS(x, cluster_size) \
+    (((x) + (cluster_size) - 1) / (cluster_size))
+#define SECTOR_BITS (512 * 8)
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+// FIXME Sektorgroesse != 512
+
+static uint8_t fat_buf[1024];
+static struct fat_fs* fat_buf_fs = NULL;
+static uint32_t fat_buf_sector = 0;
+static int fat_buf_changed = 0;
+
+static uint32_t get_next_cluster(struct fat_fs* fs, uint32_t cluster);
+
+
+static uint8_t* get_fat_buffer(struct fat_fs* fs, uint32_t sector)
+{
+    sector += fs->bpb.reserved_sectors;
+
+    if (fs == fat_buf_fs && sector == fat_buf_sector) {
+        return fat_buf;
+    }
+
+    /* FIXME Write back changed entries before overwriting them */
+    /* TODO Evtl. reicht memcpy und lesen nur eines Sektors */
+    if (!fs->dev_read(sector * 512, 1024, fat_buf, fs->dev_private)) {
+        fat_buf_fs = NULL;
+        return NULL;
+    }
+
+    fat_buf_fs = fs;
+    fat_buf_sector = sector;
+    fat_buf_changed = 0;
+
+    return fat_buf;
+}
+
+static int commit_fat_buffer(struct fat_fs* fs)
+{
+    int i;
+    uint32_t sector;
+
+    if (fs != fat_buf_fs) {
+        return 0;
+    }
+
+    sector = fat_buf_sector;
+    for (i = 0; i < fs->bpb.num_fats; i++) {
+        /* TODO Evtl. reicht schreiben nur eines Sektors */
+        if (!fs->dev_write(sector * 512, 1024, fat_buf, fs->dev_private)) {
+            return -FAT_EIO;
+        }
+
+        if (fs->type == FS_TYPE_FAT32) {
+            sector += fs->bpb.extended.fat32.fat_sectors;
+        } else {
+            sector += fs->bpb.fat_sectors;
+        }
+    }
+
+    fat_buf_changed = 0;
+
+    return 0;
+}
+
+static uint32_t get_next_cluster_fat12(struct fat_fs* fs, uint32_t cluster)
+{
+    uint8_t* buf;
+    uint32_t fat_bit_offset = (cluster * 12ULL);
+    uint32_t fat_sector = fat_bit_offset / SECTOR_BITS;
+    uint32_t value;
+
+    fat_bit_offset %= SECTOR_BITS;
+
+    buf = get_fat_buffer(fs, fat_sector);
+    if (buf == NULL) {
+        return 0;
+    }
+
+    value = *((uint16_t*) &buf[fat_bit_offset / 8]);
+
+    if ((fat_bit_offset & 7) == 0) {
+        value &= 0xFFF;
+    } else {
+        value >>= 4;
+    }
+
+    if (value > 0xff5) {
+        value |= 0xfffff000;
+    }
+
+    return value;
+}
+
+static uint32_t get_next_cluster_fat16(struct fat_fs* fs, uint32_t cluster)
+{
+    uint8_t* buf;
+    uint32_t fat_sector = (cluster * sizeof(uint16_t)) / 512;
+    uint32_t fat_offset = (cluster * sizeof(uint16_t)) % 512;
+    uint32_t value;
+
+    buf = get_fat_buffer(fs, fat_sector);
+    if (buf == NULL) {
+        return 0;
+    }
+
+    value = *((uint16_t*) &buf[fat_offset]);
+
+    if (value > 0xfff5) {
+        value |= 0xffff0000;
+    }
+
+    return value;
+}
+
+static uint32_t get_next_cluster_fat32(struct fat_fs* fs, uint32_t cluster)
+{
+    uint8_t* buf;
+    uint32_t fat_sector = (cluster * sizeof(uint32_t)) / 512;
+    uint32_t fat_offset = (cluster * sizeof(uint32_t)) % 512;
+    uint32_t value;
+
+    buf = get_fat_buffer(fs, fat_sector);
+    if (buf == NULL) {
+        return 0;
+    }
+
+    value = *((uint32_t*) &buf[fat_offset]);
+
+    if (value > 0xffffff5) {
+        value |= 0xf0000000;
+    }
+
+    return value;
+}
+
+static int set_next_cluster_fat12(struct fat_fs* fs, uint32_t cluster,
+    uint32_t value)
+{
+    uint8_t* buf;
+    uint32_t fat_bit_offset = (cluster * 12ULL);
+    uint32_t fat_sector = fat_bit_offset / SECTOR_BITS;
+    uint16_t* pvalue;
+
+    fat_bit_offset %= SECTOR_BITS;
+
+    buf = get_fat_buffer(fs, fat_sector);
+    if (buf == NULL) {
+        return 0;
+    }
+
+    pvalue = ((uint16_t*) &buf[fat_bit_offset / 8]);
+    value &= 0xFFF;
+
+    if ((fat_bit_offset & 7) == 0) {
+        *pvalue &= ~0xFFF;
+        *pvalue |= value;
+    } else {
+        *pvalue &= ~0xFFF0;
+        *pvalue |= (value << 4);
+    }
+    fat_buf_changed = 1;
+
+    get_next_cluster_fat12(fs, cluster);
+
+    return 0;
+}
+
+static int set_next_cluster_fat16(struct fat_fs* fs, uint32_t cluster,
+    uint32_t value)
+{
+    uint8_t* buf;
+    uint32_t fat_sector = (cluster * sizeof(uint16_t)) / 512;
+    uint32_t fat_offset = (cluster * sizeof(uint16_t)) % 512;
+
+    buf = get_fat_buffer(fs, fat_sector);
+    if (buf == NULL) {
+        return -FAT_EIO;
+    }
+
+    *((uint16_t*) &buf[fat_offset]) = value;
+    fat_buf_changed = 1;
+
+    return 0;
+}
+
+static int set_next_cluster(struct fat_fs* fs, uint32_t cluster,
+    uint32_t value)
+{
+    if (fs->type == FS_TYPE_FAT12) {
+        return set_next_cluster_fat12(fs, cluster, value);
+    } else if (fs->type == FS_TYPE_FAT16) {
+        return set_next_cluster_fat16(fs, cluster, value);
+    } else {
+        return -FAT_EINVAL;
+    }
+}
+
+int fat_file_alloc_first_cluster(struct fat_file* file)
+{
+    struct fat_fs* fs = file->fs;
+    uint32_t num_data_clusters = NUM_CLUSTERS(
+        fs->total_sectors - fs->first_data_cluster,
+        fs->bpb.cluster_sectors);
+    uint32_t j;
+    int ret;
+
+    /* Freien Cluster suchen und einhaengen */
+    for (j = 0; j < num_data_clusters; j++) {
+        if (get_next_cluster(fs, j) == 0) {
+            goto found;
+        }
+    }
+
+    return -FAT_ENOSPC;
+
+    /* EOF-Markierung in die FAT eintragen */
+found:
+    ret = set_next_cluster(fs, j, 0xffffffff);
+    if (ret < 0) {
+        return ret;
+    }
+
+    commit_fat_buffer(fs);
+
+    /* Cluster der Datei zuordnen */
+    file->dir_entry.first_cluster = j;
+
+    return 0;
+}
+
+static int fat_extend_file(struct fat_file* file, uint32_t new_size)
+{
+    struct fat_fs* fs = file->fs;
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    size_t old_blocklist_size = file->blocklist_size;
+    size_t new_blocklist_size = NUM_CLUSTERS(new_size, cluster_size);
+    int ret;
+
+    /* Ersten Cluster der Kette allozieren */
+    if (file->blocklist == NULL) {
+        fat_file_alloc_first_cluster(file);
+
+        old_blocklist_size = 1;
+        file->blocklist_size = 1;
+        file->blocklist = malloc(sizeof(*file->blocklist));
+        file->blocklist[0] = file->dir_entry.first_cluster;
+    }
+
+    /* Wenn noetig, neue Cluster allozieren */
+    if (new_blocklist_size != old_blocklist_size) {
+        uint32_t i;
+        uint32_t num_data_clusters = NUM_CLUSTERS(
+            fs->total_sectors - fs->first_data_cluster,
+            fs->bpb.cluster_sectors);
+        uint32_t free_index = 0;
+
+        /* Blockliste vergroessern */
+        file->blocklist = realloc(file->blocklist,
+            new_blocklist_size * sizeof(uint32_t));
+        file->blocklist_size = new_blocklist_size;
+
+        /* Neue Eintraege der Blockliste befuellen */
+        for (i = old_blocklist_size; i < new_blocklist_size; i++) {
+            uint32_t j;
+
+            /* Freien Cluster suchen und einhaengen */
+            for (j = free_index; j < num_data_clusters; j++) {
+                if (get_next_cluster(fs, j) == 0) {
+
+                    /* Neue EOF-Markierung */
+                    file->blocklist[i] = j;
+                    ret = set_next_cluster(fs, file->blocklist[i], 0xffffffff);
+                    if (ret < 0) {
+                        return ret;
+                    }
+
+                    /* In die Clusterkette einhaengen */
+                    ret = set_next_cluster(fs, file->blocklist[i - 1], j);
+                    if (ret < 0) {
+                        return ret;
+                    }
+
+                    free_index = j;
+                    goto found;
+                }
+            }
+
+            return -FAT_ENOSPC;
+        found:
+            /* Weiter mit naechstem Cluster */;
+        }
+
+        /* Und zuletzt veraenderte Eintraege zurueckschreiben */
+        commit_fat_buffer(fs);
+    }
+
+    /* Dateigroesse anpassen und ggf. neuen ersten Cluster eintragen */
+    file->size = new_size;
+    if ((file->dir_entry.attrib & FAT_ATTRIB_DIR) == 0) {
+        fat_dir_update_entry(file);
+    }
+
+    return 0;
+}
+
+static uint32_t get_next_cluster(struct fat_fs* fs, uint32_t cluster)
+{
+    switch (fs->type) {
+        case FS_TYPE_FAT12:
+            return get_next_cluster_fat12(fs, cluster);
+
+        case FS_TYPE_FAT16:
+            return get_next_cluster_fat16(fs, cluster);
+
+        case FS_TYPE_FAT32:
+            return get_next_cluster_fat32(fs, cluster);
+
+        default:
+            return -FAT_EINVAL;
+    }
+}
+
+
+int fat_file_open_by_dir_entry(struct fat_fs* fs, struct fat_dir_entry* entry,
+    struct fat_file* file)
+{
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    uint32_t cluster;
+    uint32_t i;
+
+    /* Dateistruktur anlegen */
+    file->fs = fs;
+    file->size = entry->size;
+    file->dir_entry = *entry;
+
+    /* Eine leere Datei braucht keine Blockliste */
+    if (entry->first_cluster == 0) {
+        file->blocklist = NULL;
+        file->blocklist_size = 0;
+        return 0;
+    }
+
+    /*
+     * Blockliste anlegen und einlesen. Bei Groesse 0 (z.B. Verzeichnisse gehen
+     * wir von unbekannter Dateigroesse aus und lassen die Blockliste dynamisch
+     * wachsen.
+     */
+    if (file->size > 0) {
+        file->blocklist_size = NUM_CLUSTERS(file->size, cluster_size);
+    } else {
+        file->blocklist_size = 1;
+    }
+
+    file->blocklist = malloc(file->blocklist_size * sizeof(uint32_t));
+    file->blocklist[0] = entry->first_cluster;
+
+    cluster = 0;
+    i = 1;
+    while (1) {
+        cluster = get_next_cluster(fs, file->blocklist[i - 1]);
+
+        /* Fehler und Dateiende */
+        if (cluster == 0) {
+            goto fail;
+        } else if (cluster >= 0xfffffff8) {
+            break;
+        }
+
+        /* Blocklist vergroessern, falls noetig */
+        if (file->blocklist_size <= i) {
+            file->blocklist_size *= 2;
+            file->blocklist = realloc(file->blocklist,
+                file->blocklist_size * sizeof(uint32_t));
+        }
+
+        /* In die Blockliste eintragen */
+        file->blocklist[i] = cluster;
+
+        i++;
+    }
+
+    /* Dateigroesse anpassen, wenn sie unbekannt war */
+    if (file->size == 0) {
+        file->size = i * cluster_size;
+    }
+
+    return 0;
+
+fail:
+    free(file->blocklist);
+    return -FAT_EIO;
+}
+
+int fat_file_open(struct fat_fs* fs, const char* path, struct fat_file* file)
+{
+    struct fat_dir dir;
+    struct fat_dir_entry dentry;
+    int ret;
+    char* p;
+
+    /* Letzten Pfadtrenner suchen */
+    p = strrchr(path, '/');
+    if (p == NULL) {
+        return -FAT_EINVAL;
+    }
+
+    /* Verzeichnis oeffnen */
+    int len = p - path + 1;
+    char dirname[len + 1];
+
+    memcpy(dirname, path, len);
+    dirname[len] = '\0';
+
+    ret = fat_dir_open(fs, dirname, &dir);
+    if (ret< 0) {
+        return ret;
+    }
+
+    /* Alles vor dem Dateinamen wegwerfen */
+    path = p + 1;
+
+    /* Verzeichnis nach Dateinamen durchsuchen */
+    do {
+        ret = fat_dir_read(&dir, &dentry);
+        if (ret >= 0 && !strcmp(dentry.name, path)) {
+            break;
+        }
+    } while (ret >= 0);
+
+    /* Datei oeffnen oder -ENOENT zurueckgeben, wenn sie nicht existiert */
+    if (ret < 0) {
+        return -FAT_ENOENT;
+    }
+
+    /* Verzeichnis wieder schliessen */
+    ret = fat_dir_close(&dir);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return fat_file_open_by_dir_entry(fs, &dentry, file);
+}
+
+static uint32_t file_get_cluster(struct fat_file* file, uint32_t file_cluster)
+{
+    if (file_cluster >= file->blocklist_size) {
+        return 0;
+    }
+
+    return file->blocklist[file_cluster];
+}
+
+static int64_t get_data_cluster_offset(struct fat_fs* fs, uint32_t cluster_idx)
+{
+    uint32_t sector = fs->bpb.cluster_sectors * (cluster_idx - 2);
+
+    return (fs->first_data_cluster + sector) * 512;
+}
+
+static int read_cluster(struct fat_fs* fs, void* buf, uint32_t cluster_idx)
+{
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    uint32_t cluster_offset = get_data_cluster_offset(fs, cluster_idx);
+
+    if (!fs->dev_read(cluster_offset, cluster_size, buf, fs->dev_private)) {
+        return -FAT_EIO;
+    }
+
+    return 0;
+}
+
+static int read_cluster_part(struct fat_fs* fs, void* buf,
+    uint32_t cluster_idx,  size_t offset, size_t len)
+{
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    uint8_t cluster[cluster_size];
+    int ret;
+
+    assert (offset + len <= cluster_size);
+
+    ret = read_cluster(fs, cluster, cluster_idx);
+    if (ret < 0) {
+        return ret;
+    }
+
+    memcpy(buf, cluster + offset, len);
+    return 0;
+}
+
+static int write_cluster(struct fat_fs* fs, void* buf, uint32_t cluster_idx)
+{
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    uint32_t cluster_offset = get_data_cluster_offset(fs, cluster_idx);
+
+    if (!fs->dev_write(cluster_offset, cluster_size, buf, fs->dev_private)) {
+        return -FAT_EIO;
+    }
+
+    return 0;
+}
+
+static int write_cluster_part(struct fat_fs* fs, void* buf,
+    uint32_t cluster_idx,  size_t offset, size_t len)
+{
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    uint8_t cluster[cluster_size];
+    int ret;
+
+    assert (offset + len <= cluster_size);
+
+    ret = read_cluster(fs, cluster, cluster_idx);
+    if (ret < 0) {
+        return ret;
+    }
+
+    memcpy(cluster + offset, buf, len);
+
+    ret = write_cluster(fs, cluster, cluster_idx);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return 0;
+}
+
+int32_t fat_file_rw(struct fat_file* file, void* buf, uint32_t offset,
+    size_t len, int is_write)
+{
+    struct fat_fs* fs = file->fs;
+    uint32_t cluster_size = fs->bpb.cluster_sectors * 512;
+    uint8_t* p = buf;
+    size_t n = len;
+    int ret;
+    uint32_t file_cluster = offset / cluster_size;
+    uint32_t disk_cluster;
+    uint32_t offset_in_cluster;
+
+    /* Funktionspointer fuer Lesen/Schreiben von Clustern */
+    int (*rw_cluster)(struct fat_fs* fs, void* buf, uint32_t cluster_idx);
+    int (*rw_cluster_part)(struct fat_fs* fs, void* buf, uint32_t cluster_idx,
+        size_t offset, size_t len);
+
+    if (is_write) {
+        rw_cluster      = write_cluster;
+        rw_cluster_part = write_cluster_part;
+    } else {
+        rw_cluster      = read_cluster;
+        rw_cluster_part = read_cluster_part;
+    }
+
+    /* Angefangener Cluster am Anfang */
+    offset_in_cluster = offset & (cluster_size - 1);
+    if (offset_in_cluster) {
+        uint32_t count = MIN(n, cluster_size - offset_in_cluster);
+
+        disk_cluster = file_get_cluster(file, file_cluster++);
+        if (disk_cluster == 0) {
+            return -FAT_EIO;
+        }
+
+        ret = rw_cluster_part(fs, p, disk_cluster, offset_in_cluster, count);
+        if (ret < 0) {
+            return ret;
+        }
+
+        p += count;
+        n -= count;
+    }
+
+    /* Eine beliebige Anzahl von ganzen Clustern in der Mitte */
+    while (n >= cluster_size) {
+        disk_cluster = file_get_cluster(file, file_cluster++);
+        if (disk_cluster == 0) {
+            return -FAT_EIO;
+        }
+
+        ret = rw_cluster(fs, p, disk_cluster);
+        if (ret < 0) {
+            return ret;
+        }
+
+        p += cluster_size;
+        n -= cluster_size;
+    }
+
+    /* Und wieder ein angefangener Cluster am Ende */
+    if (n) {
+        disk_cluster = file_get_cluster(file, file_cluster);
+        if (disk_cluster == 0) {
+            return -FAT_EIO;
+        }
+
+        ret = rw_cluster_part(fs, p, disk_cluster, 0, n);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+int32_t fat_file_read(struct fat_file* file, void* buf, uint32_t offset,
+    size_t len)
+{
+
+    /* Bei Ueberlaenge Fehler zurueckgeben */
+    if (offset + len > file->size) {
+        return -FAT_EIO;
+    }
+
+    return fat_file_rw(file, buf, offset, len, 0);
+}
+
+int32_t fat_file_write(struct fat_file* file, const void* buf, uint32_t offset,
+    size_t len)
+{
+    int ret;
+
+    /* Datei erweitern, falls noetig */
+    if (offset + len > file->size) {
+        ret = fat_extend_file(file, offset + len);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    return fat_file_rw(file, (void*) buf, offset, len, 1);
+}
+
+int fat_file_close(struct fat_file* file)
+{
+    free(file->blocklist);
+    return 0;
+}
diff --git a/fat/libfat/fs.c b/fat/libfat/fs.c
new file mode 100644
index 0000000..5fcf583
--- /dev/null
+++ b/fat/libfat/fs.c
@@ -0,0 +1,80 @@
+#include <stdio.h>
+#include "fat.h"
+
+/**
+ * fat-Dateisystem einbinden. Dafuer muessen dev_read, dev_write und
+ * dev_private (falls notwendig) initialisiert sein.
+ *
+ * @return 1 bei Erfolg, im Fehlerfall 0
+ */
+int fat_fs_mount(struct fat_fs* fs)
+{
+    // BPB einlesen
+    if (!fs->dev_read(0, 512, &fs->bpb, fs->dev_private)) {
+        return 0;
+    }
+
+    // Magic pruefen
+    if (fs->bpb.magic != 0xaa55) {
+        return 0;
+    }
+
+    // FAT-Variante bestimmen
+    uint32_t total_sectors;
+    uint32_t rootdir_sectors;
+    uint32_t data_sectors;
+    uint32_t data_clusters;
+
+    if (fs->bpb.total_sectors != 0) {
+        total_sectors = fs->bpb.total_sectors;
+    } else {
+        // TODO Hier ist auf jeden Fall FAT32, egal wie viele Cluster
+        total_sectors = fs->bpb.total_sectors_large;
+    }
+
+    rootdir_sectors =
+        (fs->bpb.rootdir_length * sizeof(struct fat_disk_dir_entry) + 511)
+        / 512;
+
+    // TODO fat_sectors ungueltig fuer FAT32
+    data_sectors = total_sectors
+        - fs->bpb.reserved_sectors
+        - (fs->bpb.num_fats * fs->bpb.fat_sectors)
+        - rootdir_sectors;
+    data_clusters = data_sectors / fs->bpb.cluster_sectors;
+
+    if (data_clusters < 4085) {
+        fs->type = FS_TYPE_FAT12;
+    } else if (data_clusters < 65525) {
+        fs->type = FS_TYPE_FAT16;
+    } else {
+        fs->type = FS_TYPE_FAT32;
+        data_sectors -=
+            (fs->bpb.num_fats * fs->bpb.extended.fat32.fat_sectors);
+    }
+
+
+    fs->total_sectors = total_sectors;
+    fs->first_data_cluster = total_sectors - data_sectors;
+
+    return 1;
+}
+
+/**
+ * fat-Dateisystem aushaengen. Dabei werden saemtliche gecachten Daten auf die
+ * Platte geschrieben. Die Member sb und cache_handle in der fs-Struktur sind
+ * danach NULL.
+ *
+ * @return 1 bei Erfolg, im Fehlerfall 0
+ */
+int fat_fs_unmount(struct fat_fs* fs)
+{
+    return 0;
+}
+
+/**
+ * Saetmliche gecachten Daten auf die Platte schreiben
+ */
+void fat_fs_sync(struct fat_fs* fs)
+{
+}
diff --git a/fat/libfat/include/fat.h b/fat/libfat/include/fat.h
new file mode 100644
index 0000000..5b62a10
--- /dev/null
+++ b/fat/libfat/include/fat.h
@@ -0,0 +1,287 @@
+#ifndef _FAT_H_
+#define _FAT_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef CDI_HAS_ERRNO
+
+#include <errno.h>
+#define FAT_ENOENT  ENOENT
+#define FAT_EIO     EIO
+#define FAT_EINVAL  EINVAL
+#define FAT_ENOSPC  ENOSPC
+
+#else
+
+#define FAT_ENOENT  1
+#define FAT_EIO     2
+#define FAT_EINVAL  3
+#define FAT_ENOSPC  4
+
+#endif
+
+// TODO Nur fuer Debugging - spaeter entfernen
+#include <stdio.h>
+
+struct fat32_extended_bpb {
+    uint32_t    fat_sectors;
+    uint16_t    flags;
+    uint16_t    version;
+    uint32_t    rootdir_cluster;
+} __attribute__((packed));
+
+/**
+ * BIOS Parameter Block
+ */
+struct fat_bpb {
+    uint8_t     jmp_code[3];
+    char        os_name[8];
+    uint16_t    sector_size;
+    uint8_t     cluster_sectors;
+    uint16_t    reserved_sectors;
+    uint8_t     num_fats;
+    uint16_t    rootdir_length;
+    uint16_t    total_sectors;
+    uint8_t     media_type;
+
+    /* Nur fuer FAT12/16; FAT32 benutzt fat32_extended_bpb.fat_sectors */
+    uint16_t    fat_sectors;
+
+    uint16_t    sectors_per_track;
+    uint16_t    heads;
+    uint32_t    hidden_sectors;
+    uint32_t    total_sectors_large;
+    union {
+        struct fat32_extended_bpb   fat32;
+        uint8_t                     bytes[474];
+    } extended;
+    uint16_t    magic;
+} __attribute__((packed));
+
+enum fat_type {
+    FS_TYPE_FAT12,
+    FS_TYPE_FAT16,
+    FS_TYPE_FAT32,
+};
+
+/**
+ * Zentrale Struktur zur Verwaltung eines eingebundenen Dateisystemes
+ */
+struct fat_fs {
+    /**
+     * Funktionspointer der von Aufrufer gesetzt werden muss. Diese Funktion
+     * liest Daten vom Datentraeger ein, auf dem sich das Dateisystem befindet.
+     *
+     * @param start Position von der an gelesen werden soll
+     * @param size  Anzahl der zu lesenden Bytes
+     * @param dest  Pointer auf den Puffer in dem die Daten abgelegt werden
+     *              sollen
+     * @param prv   Private Daten zum Zugriff auf den Datentraeger (z.B.
+     *              Dateideskriptor), aus dev_private zu entnehmen
+     *
+     * @return 1 wenn das Lesen geklappt hat, 0 sonst
+     */
+    int (*dev_read)(uint64_t start, size_t size, void* dest, void* prv);
+
+    /**
+     * Funktionspointer der von Aufrufer gesetzt werden muss. Diese Funktion
+     * schreibt Daten auf den Datentraeger, auf dem sich das Dateisystem
+     * befindet.
+     *
+     * @param start Position von der an gelesen werden soll
+     * @param size  Anzahl der zu lesenden Bytes
+     * @param dest  Pointer auf den Puffer, aus dem die Daten gelesen werden
+     *              sollen.
+     * @param prv   Private Daten zum Zugriff auf den Datentraeger (z.B.
+     *              Dateideskriptor), aus dev_private zu entnehmen
+     *
+     * @return 1 wenn das Schreiben geklappt hat, 0 sonst
+     */
+    int (*dev_write)(uint64_t start, size_t size, const void* source,
+        void* prv);
+
+    /**
+     * Funktionspointer der vom Aufrufer gesetzt werden muss. Diese Funktion
+     * erstellt einen neuen Cache und gibt ein Handle darauf zurueck.
+     *
+     * @param fs            Pointer auf das Dateisystem fuer das der Cache
+     *                      erstellt werden soll.
+     * @param block_size    Blockgroesse im Cache
+     *
+     * @return Handle oder NULL im Fehlerfall
+     */
+    void* (*cache_create)(struct fat_fs* fs, size_t block_size);
+
+    /**
+     * Funktionspointer der vom Aufrufer gesetzt werden muss. Diese Funktion
+     * zerstoert einen mit cache_create erstellten Cache
+     *
+     * @param cache Handle
+     */
+    void (*cache_destroy)(void* handle);
+
+    /**
+     * Funktionspointer der vom Aufrufer gesetzt werden muss. Diese Funktion
+     * schreibt alle veraenderten Cache-Blocks auf die Platte
+     *
+     * @param cache Handle
+     */
+    void (*cache_sync)(void* handle);
+
+    /**
+     * Funktionspointer der vom Aufrufer gesetzt werden muss. Diese Funktion
+     * holt einen Block aus dem Cache und gibt einen Pointer auf ein
+     * Block-Handle zrueck. Dieser Pointer ist mindestens solange gueltig, wie
+     * das Handle nicht freigegeben wird.
+     *
+     * @param cache     Cache-Handle
+     * @param block     Blocknummer
+     * @param noread    Wenn dieser Parameter != 0 ist, wird der Block nicht
+     *                  eingelesen. Das kann benutzt werden, wenn er eh
+     *                  vollstaendig ueberschrieben wird.
+     *
+     * @return Block-Handle
+     */
+    struct fat_cache_block* (*cache_block)(void* cache, uint64_t block,
+        int noread);
+
+    /**
+     * Funktionspointer der vom Aufrufer gesetzt werden muss. Diese Funktion
+     * markiert den angegebenen Block als veraendert, damit er geschrieben wird,
+     * bevor er aus dem Cache entfernt wird.
+     *
+     * @param handle Block-Handle
+     */
+    void (*cache_block_dirty)(struct fat_cache_block* handle);
+
+    /**
+     * Funktionspointer der vom Aufrufer gesetzt werden muss. Diese Funktion
+     * gibt einen mit cache_block alloziierten Block wieder frei.
+     *
+     * @param handle    Block-Handle
+     * @param dirty     Wenn != 0 wird der Block auch als Dirty markiert
+     */
+    void (*cache_block_free)(struct fat_cache_block* handle, int dirty);
+
+
+    /// Private Daten zum Zugriff auf den Datentraeger
+    void* dev_private;
+
+    /// Handle fuer Blockcache
+    void* cache_handle;
+
+    /// Darf vom Aufrufer benutzt werden
+    void* opaque;
+
+    /// BIOS Parameter Block
+    struct fat_bpb bpb;
+
+    enum fat_type type;
+    uint32_t total_sectors;
+    uint32_t first_data_cluster;
+};
+
+/**
+ * Cache-Block-Handle
+ */
+struct fat_cache_block {
+    /// Blocknummer
+    uint64_t number;
+
+    /// Daten
+    void* data;
+
+    /// Cache-Handle
+    void* cache;
+
+    /// Daten fuer die Implementierung
+    void* opaque;
+};
+
+/**
+ * fat-Dateisystem einbinden. Dafuer muessen dev_read, dev_write und
+ * dev_private (falls notwendig) initialisiert sein.
+ *
+ * @return 1 bei Erfolg, im Fehlerfall 0
+ */
+int fat_fs_mount(struct fat_fs* fs);
+
+/**
+ * fat-Dateisystem aushaengen. Dabei werden saemtliche gecachten Daten auf die
+ * Platte geschrieben. Die Member sb und cache_handle in der fs-Struktur sind
+ * danach NULL.
+ *
+ * @return 1 bei Erfolg, im Fehlerfall 0
+ */
+int fat_fs_unmount(struct fat_fs* fs);
+
+/**
+ * Saetmliche gecachten Daten auf die Platte schreiben
+ */
+void fat_fs_sync(struct fat_fs* fs);
+
+#define FAT_ATTRIB_READONLY 0x01
+#define FAT_ATTRIB_HIDDEN   0x02
+#define FAT_ATTRIB_SYSTEM   0x04
+#define FAT_ATTRIB_VOLUME   0x08
+#define FAT_ATTRIB_DIR      0x10
+#define FAT_ATTRIB_ARCHIVE  0x20
+
+struct fat_disk_dir_entry {
+    char name[8];
+    char ext[3];
+    uint8_t attrib;
+    uint8_t data[14];
+    uint16_t first_cluster;
+    uint32_t size;
+} __attribute__((packed));
+
+
+struct fat_dir_entry {
+    int index_in_dir;
+    int dir_first_cluster;
+    char name[13]; /* 8.3 plus \0 */
+    int first_cluster;
+    int attrib;
+    uint32_t size;
+};
+
+struct fat_file;
+
+struct fat_dir {
+    struct fat_fs* fs;
+    struct fat_disk_dir_entry* entries;
+    int first_cluster;
+    int i;
+    int num;
+};
+
+int fat_dir_open(struct fat_fs* fs, const char* path, struct fat_dir* dir);
+int fat_dir_read(struct fat_dir* dir, struct fat_dir_entry* entry);
+int fat_dir_create_entry(struct fat_dir* dir, const char* name, int attr,
+    struct fat_dir_entry* dir_entry);
+int fat_dir_create(struct fat_dir* dir, const char* name,
+    struct fat_dir_entry* dir_entry);
+int fat_dir_update_entry(struct fat_file* file);
+int fat_dir_close(struct fat_dir* dir);
+
+struct fat_file {
+    struct fat_fs* fs;
+    uint32_t size;
+    size_t blocklist_size;
+    uint32_t* blocklist;
+    struct fat_dir_entry dir_entry;
+};
+
+int fat_file_open(struct fat_fs* fs, const char* path, struct fat_file* file);
+int fat_file_open_by_dir_entry(struct fat_fs* fs, struct fat_dir_entry* entry,
+    struct fat_file* file);
+int fat_file_alloc_first_cluster(struct fat_file* file);
+int32_t fat_file_read(struct fat_file* file, void* buf, uint32_t offset,
+    size_t len);
+int32_t fat_file_write(struct fat_file* file, const void* buf, uint32_t offset,
+    size_t len);
+int fat_file_close(struct fat_file* file);
+
+#endif
diff --git a/fat/main.c b/fat/main.c
new file mode 100644
index 0000000..c329ce4
--- /dev/null
+++ b/fat/main.c
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 2010 The tyndur Project. All rights reserved.
+ *
+ * This code is derived from software contributed to the tyndur Project
+ * by Kevin Wolf.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cdi.h>
+#include <cdi/fs.h>
+
+#include "fat.h"
+
+#define DRIVER_NAME "fat"
+
+struct fat_res {
+    struct cdi_fs_res       cdi;
+    char*                   path; /* TODO Remove this */
+    struct fat_dir_entry    dir_entry;
+    struct fat_file         file;
+};
+
+static int fat_res_load(struct cdi_fs_stream* s);
+static int64_t fat_res_meta_read(struct cdi_fs_stream* s, cdi_fs_meta_t meta);
+static cdi_list_t fat_dir_list(struct cdi_fs_stream* s);
+static size_t fat_file_op_read(struct cdi_fs_stream* s, uint64_t start,
+    size_t size, void* buffer);
+static size_t fat_file_op_write(struct cdi_fs_stream* stream, uint64_t start,
+    size_t size, const void* buffer);
+static int fat_dir_create_child(struct cdi_fs_stream* stream,
+    const char* name, struct cdi_fs_res* parent);
+static int fat_res_assign_class(struct cdi_fs_stream* s,
+    cdi_fs_res_class_t class);
+static int fat_file_op_truncate(struct cdi_fs_stream* s, uint64_t size);
+
+static struct cdi_fs_driver fat_driver;
+
+static struct cdi_fs_res_res fat_res_ops = {
+    .load           = fat_res_load,
+    .meta_read      = fat_res_meta_read,
+    .assign_class   = fat_res_assign_class,
+};
+
+static struct cdi_fs_res_dir fat_dir_ops = {
+    .list           = fat_dir_list,
+    .create_child   = fat_dir_create_child,
+};
+
+static struct cdi_fs_res_file fat_file_ops = {
+    .read           = fat_file_op_read,
+    .write          = fat_file_op_write,
+    .truncate       = fat_file_op_truncate,
+};
+
+static int fat_data_read(uint64_t start, size_t size, void* dest, void* prv)
+{
+    int ret;
+    ret = cdi_fs_data_read(prv, start, size, dest);
+    return ret == size;
+}
+
+static int fat_data_write(uint64_t start, size_t size, const void* source,
+    void* prv)
+{
+    int ret;
+    ret = cdi_fs_data_write(prv, start, size, source);
+    return ret == size;
+}
+
+/**
+ * Initialisiert die Datenstrukturen fuer den fat-Treiber
+ */
+static int fat_driver_init(void)
+{
+    cdi_fs_driver_init(&fat_driver);
+    return 0;
+}
+
+/**
+ * Deinitialisiert die Datenstrukturen fuer den fat-Treiber
+ */
+static int fat_driver_destroy(void)
+{
+    cdi_fs_driver_destroy(&fat_driver);
+    return 0;
+}
+
+/**
+ * Neues Dateisystem initialisieren; Diese Funktion muss das root_object in
+ * der Dateisystemstruktur eintragen.
+ *
+ * @return Wenn das Dateisystem erfolgreich initialisiert wurde 1, sonst
+ *         0. Falls ein Fehler auftritt, muss das error-Feld in der
+ *         Dateisystemstruktur gesetzt werden.
+ */
+static int fat_fs_init(struct cdi_fs_filesystem* fs)
+{
+    struct fat_res* root;
+    struct fat_fs* fat_fs;
+
+    /* Initialize root node */
+    root = malloc(sizeof(*root));
+    *root = (struct fat_res) {
+        .cdi = {
+            .name = "/",
+            .res = &fat_res_ops,
+        },
+        .path = "/",
+        .dir_entry = {
+            .attrib = FAT_ATTRIB_DIR,
+        },
+    };
+
+    /* Mount file system */
+    fat_fs = malloc(sizeof(*fat_fs));
+    fat_fs->dev_read    = fat_data_read;
+    fat_fs->dev_write   = fat_data_write;
+    fat_fs->dev_private = fs;
+
+    if (!fat_fs_mount(fat_fs)) {
+        goto fail;
+    }
+
+    /* Initialize fs structure */
+    fs->root_res = &root->cdi;
+    fs->opaque = fat_fs;
+
+    return 1;
+
+fail:
+    free(root);
+    free(fat_fs);
+    return 0;
+}
+
+/**
+ * Dateisystem deinitialisieren
+ *
+ * @return Wenn das Dateisystem erfolgreich deinitialisiert wurde 1, 0
+ *         sonst. Falls ein Fehler auftritt, muss das error-Feld in der
+ *         Dateisystemstruktur gesetzt werden.
+ */
+static int fat_fs_destroy(struct cdi_fs_filesystem* fs)
+{
+    return 1;
+}
+
+static int fat_res_load_dir(struct fat_fs* fs, struct fat_res* res)
+{
+    struct fat_dir dir;
+    struct fat_dir_entry dentry;
+    struct fat_res* child;
+    int ret;
+
+    res->cdi.children = cdi_list_create();
+
+    ret = fat_dir_open(fs, res->path, &dir);
+    if (ret < 0) {
+        goto fail;
+    }
+
+    while(1) {
+        ret = fat_dir_read(&dir, &dentry);
+        if (ret < 0) {
+            break;
+        }
+
+        child = calloc(1, sizeof(*child));
+        child->cdi.res = &fat_res_ops;
+        child->cdi.name = strdup(dentry.name);
+        asprintf(&child->path, "%s/%s", res->path, dentry.name);
+        child->dir_entry = dentry;
+
+        cdi_list_push(res->cdi.children, child);
+    }
+
+    fat_dir_close(&dir);
+
+    res->cdi.dir = &fat_dir_ops;
+    res->cdi.flags.browse = 1;
+    res->cdi.flags.create_child = 1;
+
+    return 1;
+
+fail:
+    cdi_list_destroy(res->cdi.children);
+    return 0;
+}
+
+static int fat_res_load_file(struct fat_fs* fs, struct fat_res* res)
+{
+    int ret;
+
+    ret  = fat_file_open_by_dir_entry(fs, &res->dir_entry, &res->file);
+    if (ret < 0) {
+        return 0;
+    }
+
+    res->cdi.file = &fat_file_ops;
+    res->cdi.flags.read = 1;
+    res->cdi.flags.write = 1;
+    res->cdi.flags.execute = 1;
+
+    return 1;
+}
+
+/**
+ * Ressource laden
+ *
+ * @param stream Stream
+ *
+ * @return Falls die Ressource erfolgreich geladen wurde 1, sonst 0
+ */
+static int fat_res_load(struct cdi_fs_stream* s)
+{
+    struct fat_res* res = (struct fat_res*) s->res;
+    int ret;
+
+    if (res->dir_entry.attrib & FAT_ATTRIB_DIR) {
+        ret = fat_res_load_dir(s->fs->opaque, res);
+    } else {
+        ret = fat_res_load_file(s->fs->opaque, res);
+    }
+
+    if (!ret) {
+        goto fail;
+    }
+
+    s->res->loaded = 1;
+
+    return 1;
+
+fail:
+    return 0;
+}
+
+/**
+ * Metaeigenschaft lesen
+ *
+ * @param stream Stream
+ * @param meta Konstante fuer die gewuenschte Metaeigenschaft
+ *
+ * @return Wert der Metaeigenschaft
+ */
+static int64_t fat_res_meta_read(struct cdi_fs_stream* s, cdi_fs_meta_t meta)
+{
+    struct fat_res* res = (struct fat_res*) s->res;
+    struct fat_fs* fs = s->fs->opaque;
+
+    switch (meta) {
+        case CDI_FS_META_SIZE:
+            return res->dir_entry.size;
+
+        case CDI_FS_META_BLOCKSZ:
+        case CDI_FS_META_BESTBLOCKSZ:
+            return fs->bpb.sector_size * fs->bpb.cluster_sectors;
+
+        default:
+            return -1;
+    }
+}
+
+/**
+ * Diese Ressource einer neuen Klasse zuweisen. Diese Funktion wird nur
+ * aufgerufen, wenn die Ressource nicht bereits dieser Klasse zugewiesen
+ * ist.
+ *
+ * @param stream Stream
+ * @param class Konstante fuer den Typ der klasse, der die Ressource
+ *              zugewiesen werden soll.
+ *
+ * @return 1 falls die Ressource erfolgreich der Klasse zugewiesen wurde, 0
+ *         sonst
+ */
+static int fat_res_assign_class(struct cdi_fs_stream* s,
+    cdi_fs_res_class_t class)
+{
+    struct fat_res* res = (struct fat_res*) s->res;
+    struct fat_res* parent_res = (struct fat_res*) res->cdi.parent;
+    int ret;
+
+    /* Allow only one class */
+    if (res->cdi.file || res->cdi.dir || res->cdi.link || res->cdi.special) {
+        s->error = CDI_FS_ERROR_ONS;
+        return 0;
+    }
+
+    /* Create the dir entry */
+    struct fat_dir dir;
+
+    ret = fat_dir_open(s->fs->opaque, parent_res->path, &dir);
+    if (ret < 0) {
+        return 0;
+    }
+
+    s->error = CDI_FS_ERROR_NONE;
+    switch (class) {
+        case CDI_FS_CLASS_FILE:
+            ret = fat_dir_create_entry(&dir, res->cdi.name, 0,
+                &res->dir_entry);
+            if (ret < 0) {
+                s->error = CDI_FS_ERROR_UNKNOWN;
+                goto out;
+            }
+            res->cdi.file = &fat_file_ops;
+            break;
+
+        case CDI_FS_CLASS_DIR:
+            ret = fat_dir_create(&dir, res->cdi.name, &res->dir_entry);
+            if (ret < 0) {
+                s->error = CDI_FS_ERROR_UNKNOWN;
+                goto out;
+            }
+            res->cdi.dir = &fat_dir_ops;
+            break;
+
+        default:
+            s->error = CDI_FS_ERROR_NOT_IMPLEMENTED;
+            goto out;
+    }
+
+    if (!fat_res_load(s)) {
+        s->error = CDI_FS_ERROR_UNKNOWN;
+    }
+
+out:
+    fat_dir_close(&dir);
+    return s->error == CDI_FS_ERROR_NONE ? 1 : 0;
+}
+
+/**
+ * Diese Funktion gibt einen Pointer auf die Liste mit den Eintraegen
+ * zurueck. Hier wird nicht einfach fix der Pointer in fs_res genommen,
+ * damit dort auch "versteckte" Eintraege vorhanden sein koennten. (Ich
+ * meine hier nicht irgend ein versteckt-Flag dass die Dateien vor dem
+ * Benutzer Verstecken soll, sondern eher von fuer den Internen gebrauch
+ * angelegten Eintraegen.
+ * Diese Liste muss nicht vom Aufrufer freigegeben werden, da einige
+ * Treiber hier direkt children aus fs_res benutzen, und andere dafuer eine
+ * eigene Liste erstellen, die sie intern abspeichern.
+ *
+ * @param stream Stream
+ *
+ * @return Pointer auf eine Liste mit den Untereintraegen vom Typ
+ * cdi_fs_res.
+ */
+static cdi_list_t fat_dir_list(struct cdi_fs_stream* s)
+{
+    return s->res->children;
+}
+
+/**
+ * Neue Ressource in der Aktuellen erstellen. Diese wird erstmal noch
+ * keiner Klasse zugewiesen. Diese Funktion wird mit einem NULL-Pointer als
+ * Ressource im Stream aufgerufen. Dieser NULL-Pointer muss bei
+ * Erfolgreichem Beenden durch einen Pointer auf die neue Ressource ersetzt
+ * worden sein.
+ *
+ * @param stream Mit NULL-Pointer als Ressource
+ * @param name Name der neuen Ressource
+ * @param parent Ressource, der die neue Ressource als Kindressource
+ *               zugeordnet werden soll.
+ *
+ * @return Falls die Ressource erfolgreich erstellt wurde 1, sonst 0
+ */
+static int fat_dir_create_child(struct cdi_fs_stream* stream,
+    const char* name, struct cdi_fs_res* _parent)
+{
+    struct fat_res* parent = (struct fat_res*) _parent;
+    struct fat_res* res;
+
+    res = malloc(sizeof(*res));
+    *res = (struct fat_res) {
+        .cdi = {
+            .name = strdup(name),
+            .res = &fat_res_ops,
+            .parent = &parent->cdi,
+            .loaded = 1,
+        },
+    };
+
+    asprintf(&res->path, "%s/%s", parent->path, name);
+
+    stream->res = &res->cdi;
+    return 1;
+}
+
+/**
+ * Daten aus dieser Datei lesen. Wird nur aufgerufen, wenn es durch die
+ * Flags oder Berechtigungen nicht verhindert wird.
+ *
+ * Im Fehlerfall wird je nach Fehler die Fehlernummer im Handle und die im
+ * Device gesetzt.
+ *
+ * @param stream Stream
+ * @param start Position von der an gelesen werden soll
+ * @param size Groesse der zu lesenden Daten
+ * @param buffer Puffer in den die Daten gelsen werden sollen
+ *
+ * @return Gelesene Bytes, oder 0 im Fehlerfall
+ */
+static size_t fat_file_op_read(struct cdi_fs_stream* s, uint64_t start,
+    size_t size, void* buffer)
+{
+    struct fat_res* res = (struct fat_res*) s->res;
+    int32_t ret;
+
+    ret = fat_file_read(&res->file, buffer, start, size);
+    if (ret < 0) {
+        /* TODO Set error */
+        return 0;
+    } else {
+        return size;
+    }
+}
+
+/**
+ * Daten in diese Datei schreiben. Wird nur aufgerufen, wenn es durch die
+ * Flags oder Berechtigungen nicht verhindert wird.
+ *
+ * Im Fehlerfall wird je nach Fehler die Fehlernummer im Handle und die im
+ * Device gesetzt.
+ *
+ * @param stream Stream
+ * @param start Position an die geschrieben werden soll
+ * @param size Groesse der zu schreibenden Daten
+ * @param buffer Puffer aus dem die Daten gelesen werden sollen
+ *
+ * @return Geschriebene Bytes oder 0 im Fehlerfall
+ */
+static size_t fat_file_op_write(struct cdi_fs_stream* s, uint64_t start,
+    size_t size, const void* buffer)
+{
+    struct fat_res* res = (struct fat_res*) s->res;
+    int32_t ret;
+
+    ret = fat_file_write(&res->file, buffer, start, size);
+    if (ret < 0) {
+        /* TODO Set error */
+        return 0;
+    } else {
+        return size;
+    }
+}
+
+/**
+ * Groesse der Datei anpassen. Diese Funktion kann sowohl fuers
+ * Vergroessern, als auch fuers Verkleinern benutzt werden.
+ *
+ * Im Fehlerfall wird je nach Fehler die Fehlernummer im Handle und die im
+ * Device gesetzt.
+ *
+ * @param stream Stream
+ * @param size Neue Groesse der Datei
+ *
+ * @return 1 bei Erfolg, im Fehlerfall 0
+ */
+static int fat_file_op_truncate(struct cdi_fs_stream* s, uint64_t size)
+{
+    /* TODO */
+    return 1;
+}
+
+static struct cdi_fs_driver fat_driver = {
+    .drv = {
+        .type           = CDI_FILESYSTEM,
+        .name           = DRIVER_NAME,
+        .init           = fat_driver_init,
+        .destroy        = fat_driver_destroy,
+    },
+    .fs_init            = fat_fs_init,
+    .fs_destroy         = fat_fs_destroy,
+};
+
+CDI_DRIVER(DRIVER_NAME, fat_driver)
-- 
1.6.0.2