/*
 * FAT12/16/32 Filesystem Driver
 * main.c
 *
 * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "fat.h"
#include <syscalls.h>
#include <heap.h>
#include <exception.h>
#include <user.h>

const char driver_name[] = "fatfs";

static dword_t fatfs_mount(const char *device, const char *mountpoint, dword_t flags);
static dword_t fatfs_unmount(mounted_volume_t *volume);
static dword_t fatfs_load_file(file_t **file);
static dword_t fatfs_unload_file(file_t *file);
static dword_t fatfs_open_file(file_instance_t **instance);
static dword_t fatfs_close_file(file_instance_t *instance);
static dword_t fatfs_delete_file(mounted_volume_t *volume, const char *path, bool_t purge);
static dword_t fatfs_read_file(file_instance_t *file, void *buffer, qword_t offset, size_t length, size_t *bytes_read);
static dword_t fatfs_write_file(file_instance_t *file, const void *buffer, qword_t offset, size_t length, size_t *bytes_written);
static dword_t fatfs_list_dir(file_instance_t *directory, char *filename, bool_t continue_scan);
static dword_t fatfs_set_file(file_t *file, dword_t info_type, const void *buffer, size_t size);

static dword_t fatfs_read_internal(object_t *obj, void *buffer, qword_t offset, size_t length, size_t *bytes_read);
static dword_t fatfs_write_internal(object_t *obj, const void *buffer, qword_t offset, size_t length, size_t *bytes_written);

static fs_driver_t fatfs_driver =
{
    .name = "FAT",
    .mount = fatfs_mount,
    .unmount = fatfs_unmount,
    .load_file = fatfs_load_file,
    .unload_file = fatfs_unload_file,
    .open_file = fatfs_open_file,
    .close_file = fatfs_close_file,
    .delete_file = fatfs_delete_file,
    .read_file = fatfs_read_file,
    .write_file = fatfs_write_file,
    .list_dir = fatfs_list_dir,
    .set_file = fatfs_set_file
};

static void fatfs_get_entry_name(fatfs_dirent_t *dirent, char *name)
{
    byte_t *src = dirent->filename;
    char *dest = name;
    byte_t *last_name_ptr = &dirent->filename[7];
    byte_t *last_ext_ptr = &dirent->extension[2];

    while (*last_name_ptr == ' ' && last_name_ptr >= dirent->filename) last_name_ptr--;
    while (*last_ext_ptr == ' ' && last_ext_ptr >= dirent->extension) last_ext_ptr--;
    while (src <= last_name_ptr) *dest++ = *src++;

    src = dirent->extension;
    if (src <= last_ext_ptr) *dest++ = '.';
    while (src <= last_ext_ptr) *dest++ = *src++;
    *dest = '\0';
}

static bool_t fatfs_check_name(const char *name)
{
    const char *illegal = "\"*+,/:;<=>?[\\]|";
    size_t num_dots = 0;
    size_t count_before = 0;
    size_t count_after = 0;

    while (*name)
    {
        if (strrchr(illegal, *name) || (*name >= 'a' && *name <= 'z')) return FALSE;

        if (*name == '.')
        {
            num_dots++;
            if (num_dots > 1) return FALSE;
        }
        else if (num_dots == 0)
        {
            count_before++;
            if (count_before > 8) return FALSE;
        }
        else
        {
            count_after++;
            if (count_after > 3) return FALSE;
        }

        name++;
    }

    return TRUE;
}

static void fatfs_set_entry_name(fatfs_dirent_t *dirent, const char *name)
{
    const char *src = name;
    byte_t *dest = dirent->filename;

    while (*src != '.' && *src != '\0') *dest++ = toupper(*src++);
    while (dest < &dirent->filename[8]) *dest++ = ' ';

    if (*src == '.')
    {
        src++;
        dest = dirent->extension;
        while (*src != '\0') *dest++ = toupper(*src++);
    }
}

static inline void fatfs_pack_file_time(clock_time_t *os_time, word_t *date, word_t *time)
{
    if (date) *date = (os_time->year << 9) | (os_time->month << 5) | os_time->day;
    if (time) *time = (os_time->hours << 11) | (os_time->minutes << 5) | (os_time->seconds >> 1);
}

static inline void fatfs_unpack_file_time(word_t date, word_t time, clock_time_t *os_time)
{
    os_time->day = date & 0x1F;
    os_time->month = (date >> 5) & 0x0F;
    os_time->year = (date >> 9) & 0x7F;
    os_time->seconds = (time & 0x1F) << 1;
    os_time->minutes = (time >> 5) & 0x3F;
    os_time->hours = (time >> 11) & 0x1F;
}

static dword_t fatfs_get_next_cluster(fatfs_volume_t *volume, dword_t cluster)
{
    qword_t entry_offset = (qword_t)(volume->reserved_sectors * volume->bytes_per_sector);

    switch (volume->type)
    {
    case FAT12:
        entry_offset += (qword_t)(cluster + (cluster >> 1));
        break;

    case FAT16:
        entry_offset += (qword_t)(cluster << 1);
        break;

    case FAT32:
        entry_offset += (qword_t)(cluster << 2);
        break;
    }

    dword_t entry;
    dword_t bytes_read;
    dword_t ret = device_read(volume->header.device,
                              (byte_t*)&entry,
                              entry_offset,
                              (volume->type == FAT32) ? sizeof(dword_t) : sizeof(word_t),
                              &bytes_read);
    if (ret != ERR_SUCCESS) return FATFS_INVALID_CLUSTER;

    switch (volume->type)
    {
    case FAT12:
        entry = ((cluster & 1) ? (entry >> 4) : entry) & 0xFFF;
        if (entry >= 0xFF0) entry |= FATFS_INVALID_CLUSTER;
        break;

    case FAT16:
        entry &= 0xFFFF;
        if (entry >= 0xFFF0) entry |= FATFS_INVALID_CLUSTER;
        break;

    case FAT32:
        entry &= 0x0FFFFFFF;
        if (entry >= 0x0FFFFFF0) entry |= FATFS_INVALID_CLUSTER;
        break;
    }

    if (entry == 0) entry = FATFS_INVALID_CLUSTER;
    return entry;
}

static dword_t fatfs_set_next_cluster(fatfs_volume_t *volume, dword_t cluster, dword_t next)
{
    qword_t entry_offset = (qword_t)(volume->reserved_sectors * volume->bytes_per_sector);

    switch (volume->type)
    {
    case FAT12:
        next &= 0xFFF;
        entry_offset += (qword_t)(cluster + (cluster >> 1));
        break;

    case FAT16:
        next &= 0xFFFF;
        entry_offset += (qword_t)(cluster << 1);
        break;

    case FAT32:
        next &= 0xFFFFFFFF;
        entry_offset += (qword_t)(cluster << 2);
        break;
    }

    dword_t entry;

    if (volume->type == FAT12)
    {
        dword_t bytes_read;
        dword_t ret = device_read(volume->header.device, (byte_t*)&entry, entry_offset, sizeof(word_t), &bytes_read);
        if (ret != ERR_SUCCESS) return ret;

        entry &= 0xF000 >> ((cluster & 1) * 12);
        entry |= next << ((cluster & 1) * 4);
    }
    else
    {
        entry = next;
    }

    dword_t bytes_written;
    return device_write(volume->header.device,
                        (byte_t*)&entry,
                        entry_offset,
                        (volume->type == FAT32) ? sizeof(dword_t) : sizeof(word_t),
                        &bytes_written);
}

static qword_t fatfs_get_cluster_offset(fatfs_volume_t *volume, dword_t number)
{
    qword_t sector = (qword_t)(number - volume->root_dir_cluster)
                     * (qword_t)volume->sectors_per_cluster
                     + (qword_t)volume->reserved_sectors
                     + (qword_t)(volume->num_fats * volume->sectors_per_fat);
    qword_t offset = sector * (qword_t)volume->bytes_per_sector;

    if (number != volume->root_dir_cluster)
    {
        offset += (qword_t)(volume->num_root_dirents * sizeof(fatfs_dirent_t));
    }

    return offset;
}

static dword_t fatfs_read_cluster(fatfs_volume_t *volume, dword_t number, void *buffer)
{
    dword_t bytes_read;
    return device_read(volume->header.device,
                       buffer,
                       fatfs_get_cluster_offset(volume, number),
                       volume->sectors_per_cluster * volume->bytes_per_sector,
                       &bytes_read);
}

static dword_t fatfs_zero_cluster(fatfs_volume_t *volume, dword_t number)
{
    byte_t *buffer = __builtin_alloca(volume->sectors_per_cluster * volume->bytes_per_sector);
    memset(buffer, 0, volume->sectors_per_cluster * volume->bytes_per_sector);

    dword_t bytes_written;
    return device_write(volume->header.device,
                        buffer,
                        fatfs_get_cluster_offset(volume, number),
                        volume->sectors_per_cluster * volume->bytes_per_sector,
                        &bytes_written);
}

static dword_t fatfs_get_free_cluster(fatfs_volume_t *volume, dword_t *cluster)
{
    dword_t starting_cluster = volume->last_free_cluster;

    do
    {
        if (fatfs_get_next_cluster(volume, volume->last_free_cluster) == FATFS_FREE_CLUSTER)
        {
            *cluster = volume->last_free_cluster;
            return ERR_SUCCESS;
        }

        volume->last_free_cluster++;
        volume->last_free_cluster %= volume->total_clusters;
        if (volume->last_free_cluster <= volume->root_dir_cluster) volume->last_free_cluster = volume->root_dir_cluster + 1;
    }
    while (volume->last_free_cluster != starting_cluster);

    return ERR_DISKFULL;
}

static dword_t fatfs_find_entry(fatfs_volume_t *volume, const char *path, dword_t *cluster_num, dword_t *entry_num, fatfs_dirent_t *dirent)
{
    dword_t ret = ERR_SUCCESS;
    dword_t i;
    byte_t *cluster_buffer = NULL;
    char path_copy[MAX_PATH];
    fatfs_dirent_t root_dirent =
    {
        .attributes = FATFS_ATTR_DIRECTORY,
        .first_cluster_high = volume->root_dir_cluster >> 16,
        .first_cluster_low = volume->root_dir_cluster & 0xFFFF,
        .size = volume->num_root_dirents * sizeof(fatfs_dirent_t)
    };

    if (path[0] == '\0')
    {
        if (cluster_num) *cluster_num = (dword_t)-1;
        if (entry_num) *entry_num = (dword_t)-1;
        if (dirent) *dirent = root_dirent;
        goto cleanup;
    }

    dword_t cluster_size = volume->sectors_per_cluster * volume->bytes_per_sector;
    cluster_buffer = (byte_t*)malloc(cluster_size);

    if (cluster_buffer == NULL)
    {
        ret = ERR_NOMEMORY;
        goto cleanup;
    }

    dword_t previous_cluster = (dword_t)-1;
    dword_t current_cluster = volume->root_dir_cluster;
    dword_t current_index = 0;
    fatfs_dirent_t current_dirent = root_dirent;

    char *token, *endptr;
    strcpy(path_copy, path);

    for (token = strtok_r(path_copy, PATH_DELIMITER_STRING, &endptr); token != NULL; token = strtok_r(NULL, PATH_DELIMITER_STRING, &endptr))
    {
        if (!(current_dirent.attributes & FATFS_ATTR_DIRECTORY))
        {
            ret = ERR_NOTFOUND;
            goto cleanup;
        }

        while (TRUE)
        {
            ret = fatfs_read_cluster(volume, current_cluster, cluster_buffer);
            if (ret != ERR_SUCCESS) goto cleanup;

            fatfs_dirent_t *entries = (fatfs_dirent_t*)cluster_buffer;
            dword_t num_entries = cluster_size / sizeof(fatfs_dirent_t);

            for (i = 0; i < num_entries; i++)
            {
                if (!(entries[i].attributes & FATFS_ATTR_VOLUME_ID)
                    && entries[i].filename[0] != 0
                    && entries[i].filename[0] != FATFS_DELETED)
                {
                    char filename[13];
                    fatfs_get_entry_name(&entries[i], filename);
                    if (strcmp(token, filename) == 0) break;
                }
            }

            previous_cluster = current_cluster;

            if (i != num_entries)
            {
                current_cluster = entries[i].first_cluster_low | (entries[i].first_cluster_high << 16);
                current_index = i;
                current_dirent = entries[i];
                break;
            }

            current_cluster = fatfs_get_next_cluster(volume, current_cluster);
            if (current_cluster >= FATFS_INVALID_CLUSTER)
            {
                ret = ERR_NOTFOUND;
                break;
            }
        }
    }

    if (cluster_num) *cluster_num = previous_cluster;
    if (entry_num) *entry_num = current_index;
    if (dirent) *dirent = current_dirent;

cleanup:
    if (cluster_buffer) free(cluster_buffer);
    return ret;
}

static dword_t fatfs_resize_file(fatfs_file_t *file, qword_t new_size)
{
    dword_t ret;
    dword_t bytes;
    fatfs_volume_t *volume = CONTAINER_OF(file->header.volume, fatfs_volume_t, header);

    if (new_size == file->header.size) return ERR_SUCCESS;

    dword_t cluster_size = volume->sectors_per_cluster * volume->bytes_per_sector;
    dword_t num_clusters_old = (file->header.size + cluster_size - 1) / cluster_size;
    dword_t num_clusters_new = (new_size + cluster_size - 1) / cluster_size;

    dword_t prev_cluster = FATFS_FREE_CLUSTER;
    dword_t current_cluster = file->first_cluster;

    if (num_clusters_new > num_clusters_old)
    {
        while (current_cluster != FATFS_FREE_CLUSTER && current_cluster < FATFS_INVALID_CLUSTER)
        {
            prev_cluster = current_cluster;
            current_cluster = fatfs_get_next_cluster(volume, current_cluster);
        }

        dword_t new_clusters = num_clusters_new - num_clusters_old;
        while (new_clusters--)
        {
            dword_t new_cluster;
            ret = fatfs_get_free_cluster(volume, &new_cluster);
            if (ret != ERR_SUCCESS) break;

            ret = fatfs_zero_cluster(volume, new_cluster);
            if (ret != ERR_SUCCESS)
            {
                fatfs_set_next_cluster(volume, new_cluster, FATFS_BAD_CLUSTER);
                new_clusters++;
                continue;
            }

            if (prev_cluster == FATFS_FREE_CLUSTER)
            {
                file->first_cluster = new_cluster;
            }
            else
            {
                ret = fatfs_set_next_cluster(volume, prev_cluster, new_cluster);
                if (ret != ERR_SUCCESS) break;
            }

            prev_cluster = new_cluster;
        }

        ret = fatfs_set_next_cluster(volume, prev_cluster, FATFS_LAST_CLUSTER);
    }
    else if (num_clusters_new < num_clusters_old)
    {
        while (num_clusters_new--)
        {
            prev_cluster = current_cluster;
            current_cluster = fatfs_get_next_cluster(volume, current_cluster);
            if (current_cluster >= FATFS_INVALID_CLUSTER) break;
        }

        if (prev_cluster == FATFS_FREE_CLUSTER)
        {
            file->first_cluster = 0;
        }
        else
        {
            ret = fatfs_set_next_cluster(volume, prev_cluster, FATFS_LAST_CLUSTER);
        }

        while (current_cluster < FATFS_INVALID_CLUSTER)
        {
            dword_t next_cluster = fatfs_get_next_cluster(volume, current_cluster);
            ret = fatfs_set_next_cluster(volume, current_cluster, FATFS_FREE_CLUSTER);
            if (ret != ERR_SUCCESS) break;
            current_cluster = next_cluster;
        }
    }

    clock_time_t current_time;
    syscall(SYSCALL_CLOCK_GET_TIME, &current_time);

    file->header.size = new_size;
    fatfs_dirent_t dirent;

    ret = device_read(volume->header.device,
                      &dirent,
                      fatfs_get_cluster_offset(volume, file->dirent_cluster) + (qword_t)file->dirent_index * sizeof(fatfs_dirent_t),
                      sizeof(fatfs_dirent_t),
                      &bytes);
    if (ret != ERR_SUCCESS) return ret;

    dirent.size = file->header.size;
    dirent.first_cluster_low = file->first_cluster & 0xFFFF;
    dirent.first_cluster_high = file->first_cluster >> 16;
    fatfs_pack_file_time(&current_time, &dirent.modification_date, &dirent.modification_time);

    return device_write(volume->header.device,
                        &dirent,
                        fatfs_get_cluster_offset(volume, file->dirent_cluster) + (qword_t)file->dirent_index * sizeof(fatfs_dirent_t),
                        sizeof(fatfs_dirent_t),
                        &bytes);
}

static inline dword_t fatfs_rw_file(object_t *object, byte_t *buffer, qword_t offset, size_t length, size_t *bytes, bool_t write)
{
    dword_t ret;
    dword_t count = 0;
    fatfs_file_t *file;
    fatfs_file_instance_t *instance;

    if (object->type == OBJECT_FILE_INSTANCE)
    {
        instance = CONTAINER_OF(CONTAINER_OF(object, file_instance_t, header), fatfs_file_instance_t, header);
        file = CONTAINER_OF(instance->header.global, fatfs_file_t, header);
    }
    else
    {
        ASSERT(object->type == OBJECT_FILE);
        file = CONTAINER_OF(CONTAINER_OF(object, file_t, header), fatfs_file_t, header);
        instance = NULL;
    }

    fatfs_volume_t *volume = CONTAINER_OF(file->header.volume, fatfs_volume_t, header);

    if (!length)
    {
        if (bytes) *bytes = 0;
        return ERR_SUCCESS;
    }

    if (offset > 0xFFFFFFFFULL)
    {
        if (bytes) *bytes = 0;
        return ERR_BEYOND;
    }

    if (write && (offset + (qword_t)length) < file->header.size)
    {
        ret = fatfs_resize_file(file, offset + (qword_t)length);
        if (ret != ERR_SUCCESS) return ret;
    }

    if (file->header.size == 0)
    {
        if (bytes) *bytes = 0;
        return ERR_BEYOND;
    }

    dword_t offset_low = (dword_t)offset;
    dword_t cluster_size = volume->sectors_per_cluster * volume->bytes_per_sector;
    dword_t current_cluster = file->first_cluster;

    while (offset_low >= cluster_size)
    {
        current_cluster = fatfs_get_next_cluster(volume, current_cluster);
        if (current_cluster >= FATFS_INVALID_CLUSTER)
        {
            if (bytes) *bytes = 0;
            return ERR_BEYOND;
        }

        offset_low -= cluster_size;
    }

    while (TRUE)
    {
        dword_t amount = MIN(length - count, cluster_size - offset_low);
        dword_t actually_rw;
        if (amount == 0) break;

        if (write)
        {
            ret = device_write(volume->header.device,
                               &buffer[count],
                               fatfs_get_cluster_offset(volume, current_cluster) + (qword_t)offset_low,
                               amount,
                               &actually_rw);

        }
        else
        {
            ret = device_read(volume->header.device,
                              &buffer[count],
                              fatfs_get_cluster_offset(volume, current_cluster) + (qword_t)offset_low,
                              amount,
                              &actually_rw);
        }

        if (ret != ERR_SUCCESS) goto cleanup;

        offset_low = 0;
        count += amount;

        current_cluster = fatfs_get_next_cluster(volume, current_cluster);
        if (current_cluster >= FATFS_INVALID_CLUSTER) break;
    }

    ret = (count == length) ? ERR_SUCCESS : ERR_BEYOND;

cleanup:
    if (bytes) *bytes = count;
    return ret;
}

static dword_t fatfs_read_internal(object_t *obj, void *buffer, qword_t offset, size_t length, size_t *bytes_read)
{
    return fatfs_rw_file(obj, buffer, offset, length, bytes_read, FALSE);
}

static dword_t fatfs_write_internal(object_t *obj, const void *buffer, qword_t offset, size_t length, size_t *bytes_written)
{
    return fatfs_rw_file(obj, (void*)buffer, offset, length, bytes_written, TRUE);
}

static dword_t fatfs_mount(const char *device, const char *mountpoint, dword_t flags)
{
    fatfs_bpb_t bpb;
    fat_version_t type;
    device_t *block_device = get_block_device(device);
    if (block_device == NULL) return ERR_NOTFOUND;

    dword_t ret = device_read(block_device, (byte_t*)&bpb, 0ULL, sizeof(fatfs_bpb_t), NULL);
    if (ret != ERR_SUCCESS) return ret;
    if (bpb.sectors_per_cluster == 0) return ERR_INVALID;

    dword_t total_sectors;
    if (bpb.total_sectors) total_sectors = bpb.total_sectors;
    else total_sectors = bpb.total_sectors_32;

    dword_t sectors_per_fat;
    if (bpb.sectors_per_fat) sectors_per_fat = bpb.sectors_per_fat;
    else sectors_per_fat = bpb.ext_bpb_32.sectors_per_fat;

    dword_t root_dir_size = (bpb.num_dirent * sizeof(fatfs_dirent_t) + bpb.bytes_per_sector - 1) / bpb.bytes_per_sector;
    dword_t total_clusters = (total_sectors - bpb.reserved_sectors - bpb.num_fats * sectors_per_fat - root_dir_size) / bpb.sectors_per_cluster;

    if (total_clusters < 4086) type = FAT12;
    else if (total_clusters < 65525) type = FAT16;
    else type = FAT32;

    if ((type != FAT32 && bpb.ext_bpb.signature != 0x28 && bpb.ext_bpb.signature != 0x29)
        || (type == FAT32 && bpb.ext_bpb_32.signature != 0x28 && bpb.ext_bpb_32.signature != 0x29))
    {
        return ERR_INVALID;
    }

    fatfs_volume_t *volume = (fatfs_volume_t*)malloc(sizeof(fatfs_volume_t));
    if (volume == NULL) return ERR_NOMEMORY;

    volume->header.device = get_block_device(device);
    volume->header.mountpoint = strdup(mountpoint);
    volume->header.driver = &fatfs_driver;
    volume->type = type;
    volume->bytes_per_sector = bpb.bytes_per_sector;
    volume->sectors_per_cluster = bpb.sectors_per_cluster;
    volume->reserved_sectors = bpb.reserved_sectors;
    volume->root_dir_cluster = (type == FAT32) ? bpb.ext_bpb_32.root_dir_cluster : 2;
    volume->num_root_dirents = bpb.num_dirent;
    volume->num_fats = bpb.num_fats;
    volume->sectors_per_fat = sectors_per_fat;
    volume->total_clusters = total_clusters;
    volume->last_free_cluster = volume->root_dir_cluster + 1;
    volume->owner_uid = get_current_uid();

    ret = register_mounted_volume(&volume->header);
    if (ret != ERR_SUCCESS) free(volume);

    return ret;
}

static dword_t fatfs_unmount(mounted_volume_t *volume)
{
    dword_t ret = unregister_mounted_volume(volume);
    if (ret != ERR_SUCCESS) return ret;

    free(volume->mountpoint);
    free(volume);
    return ERR_SUCCESS;
}

static dword_t fatfs_load_file(file_t **_file)
{
    fatfs_file_t *file = (fatfs_file_t*)realloc(*_file, sizeof(fatfs_file_t));
    if (file == NULL) return ERR_NOMEMORY;
    *_file = &file->header;

    fatfs_dirent_t dirent;
    dword_t cluster, entry;
    fatfs_volume_t *volume = CONTAINER_OF(file->header.volume, fatfs_volume_t, header);

    dword_t ret = fatfs_find_entry(volume, file->header.path, &cluster, &entry, &dirent);

    if (ret == ERR_NOTFOUND && (file->header.global_mode & FILE_MODE_CREATE))
    {
        dword_t i;

        char *parent_path = __builtin_alloca(strlen(file->header.path) + 1);
        strcpy(parent_path, file->header.path);

        char *file_name = NULL;
        char *last_slash = strrchr(parent_path, '/');
        if (last_slash)
        {
            *last_slash = '\0';
            file_name = last_slash + 1;
        }
        else
        {
            *parent_path = '\0';
            file_name = file->header.path;
        }

        if (!fatfs_check_name(file_name)) return ERR_INVALID;

        fatfs_dirent_t parent_dirent;
        dword_t parent_cluster, parent_entry;

        ret = fatfs_find_entry(volume, parent_path, &parent_cluster, &parent_entry, &parent_dirent);
        if (ret != ERR_SUCCESS) return ret;

        dword_t entries_per_cluster = (volume->sectors_per_cluster * volume->bytes_per_sector) / sizeof(fatfs_dirent_t);
        fatfs_dirent_t *entries = (fatfs_dirent_t*)malloc(volume->sectors_per_cluster * volume->bytes_per_sector);
        if (entries == NULL) return ERR_NOMEMORY;

        dword_t prev_cluster = FATFS_FREE_CLUSTER;
        dword_t current_cluster = parent_dirent.first_cluster_low | (parent_dirent.first_cluster_high << 16);

        while (TRUE)
        {
            if (current_cluster == FATFS_FREE_CLUSTER || (current_cluster >= FATFS_END_CLUSTER))
            {
                dword_t new_cluster;
                ret = fatfs_get_free_cluster(volume, &new_cluster);
                if (ret != ERR_SUCCESS) break;

                ret = fatfs_zero_cluster(volume, new_cluster);
                if (ret != ERR_SUCCESS)
                {
                    fatfs_set_next_cluster(volume, new_cluster, FATFS_BAD_CLUSTER);
                    continue;
                }

                if (prev_cluster == FATFS_FREE_CLUSTER)
                {
                    parent_dirent.first_cluster_low = new_cluster & 0xFFFF;
                    parent_dirent.first_cluster_high = new_cluster >> 16;
                }
                else
                {
                    ret = fatfs_set_next_cluster(volume, prev_cluster, new_cluster);
                    if (ret != ERR_SUCCESS) break;
                }

                current_cluster = new_cluster;
            }

            ret = fatfs_read_cluster(volume, current_cluster, entries);
            if (ret != ERR_SUCCESS) break;

            bool_t found = FALSE;

            for (i = 0; i < entries_per_cluster; i++)
            {
                if (entries[i].filename[0] == '\0' || entries[i].filename[0] == FATFS_DELETED)
                {
                    cluster = current_cluster;
                    entry = i;
                    found = TRUE;
                    break;
                }
            }

            if (found) break;

            prev_cluster = current_cluster;
            current_cluster = fatfs_get_next_cluster(volume, current_cluster);
        }

        free(entries);
        if (ret != ERR_SUCCESS) return ret;

        clock_time_t current_time;
        syscall(SYSCALL_CLOCK_GET_TIME, &current_time);

        dirent.attributes = 0;
        if (!(file->header.attributes & FILE_ATTR_OWNER_WRITABLE)) dirent.attributes |= FATFS_ATTR_READONLY;
        if (file->header.attributes & FILE_ATTR_DIRECTORY) dirent.attributes |= FATFS_ATTR_DIRECTORY;

        fatfs_set_entry_name(&dirent, file_name);
        dirent.duration = 1;
        fatfs_pack_file_time(&current_time, &dirent.creation_date, &dirent.creation_time);
        fatfs_pack_file_time(&current_time, &dirent.modification_date, &dirent.modification_time);
        fatfs_pack_file_time(&current_time, &dirent.last_accessed_date, NULL);
        dirent.first_cluster_high = 0;
        dirent.first_cluster_low = 0;
        dirent.size = 0;

        dword_t actually_written;
        ret = device_write(volume->header.device,
                           &dirent,
                           fatfs_get_cluster_offset(volume, cluster) + (qword_t)(entry * sizeof(fatfs_dirent_t)),
                           sizeof(fatfs_dirent_t),
                           &actually_written);
        if (ret != ERR_SUCCESS) return ret;
    }

    file->header.attributes = FILE_ATTR_OWNER_READABLE;
    if (!(dirent.attributes & FATFS_ATTR_READONLY)) file->header.attributes |= FILE_ATTR_OWNER_WRITABLE;
    if (dirent.attributes & FATFS_ATTR_DIRECTORY) file->header.attributes |= FILE_ATTR_DIRECTORY;

    file->header.size = dirent.size;
    file->first_cluster = dirent.first_cluster_low | (dirent.first_cluster_high << 16);
    file->dirent_cluster = cluster;
    file->dirent_index = entry;

    init_cache(&file->cache,
               0,
               volume->bytes_per_sector,
               (read_write_buffer_proc_t)fatfs_read_internal,
               (read_write_buffer_proc_t)fatfs_write_internal);

    return ret;
}

static dword_t fatfs_purge_file(fatfs_volume_t *volume, dword_t starting_cluster)
{
    dword_t current_cluster = starting_cluster;

    while (TRUE)
    {
        dword_t next_cluster = fatfs_get_next_cluster(volume, current_cluster);
        fatfs_set_next_cluster(volume, current_cluster, 0);
        current_cluster = next_cluster;
    }

    return ERR_SUCCESS;
}

static dword_t fatfs_unload_file(file_t *_file)
{
    fatfs_file_t *file = CONTAINER_OF(_file, fatfs_file_t, header);
    fatfs_volume_t *volume = CONTAINER_OF(file->header.volume, fatfs_volume_t, header);

    if (file->header.attributes & FILE_ATTR_DELETED)
    {
        return fatfs_purge_file(volume, file->first_cluster);
    }
    else
    {
        return flush_cache(&file->cache, &file->header.header);
    }
}

static dword_t fatfs_open_file(file_instance_t **_instance)
{
    fatfs_file_t *file = CONTAINER_OF((*_instance)->global, fatfs_file_t, header);

    fatfs_volume_t *volume = CONTAINER_OF(file->header.volume, fatfs_volume_t, header);
    if (get_current_uid() != volume->owner_uid) return ERR_FORBIDDEN;

    fatfs_file_instance_t *instance = (fatfs_file_instance_t*)realloc(*_instance, sizeof(fatfs_file_instance_t));
    if (instance == NULL) return ERR_NOMEMORY;
    *_instance = &instance->header;

    instance->last_offset = 0;
    instance->last_cluster = file->first_cluster;
    return ERR_SUCCESS;
}

static dword_t fatfs_close_file(file_instance_t *_instance)
{
    return ERR_SUCCESS;
}

static dword_t fatfs_delete_file(mounted_volume_t *_volume, const char *path, bool_t purge)
{
    fatfs_volume_t *volume = CONTAINER_OF(volume, fatfs_volume_t, header);
    if (get_current_uid() != volume->owner_uid) return ERR_FORBIDDEN;

    fatfs_dirent_t dirent;
    dword_t cluster_num;
    dword_t entry_num;

    dword_t ret = fatfs_find_entry(volume, path, &cluster_num, &entry_num, &dirent);
    if (ret != ERR_SUCCESS) return ret;

    byte_t deleted = FATFS_DELETED;
    size_t actually_written = 0;

    ret = device_write(volume->header.device,
                       &deleted,
                       fatfs_get_cluster_offset(volume, cluster_num) + (qword_t)(entry_num * sizeof(fatfs_dirent_t)),
                       sizeof(byte_t),
                       &actually_written);
    if (ret != ERR_SUCCESS) return ret;

    if (cluster_num != volume->root_dir_cluster)
    {
        dword_t cluster_size = volume->sectors_per_cluster * volume->bytes_per_sector;
        dword_t entries_per_cluster = cluster_size / sizeof(fatfs_dirent_t);

        byte_t *cluster_buffer = (byte_t*)malloc(cluster_size);
        if (cluster_buffer == NULL) return ERR_SUCCESS;

        ret = fatfs_read_cluster(volume, cluster_num, cluster_buffer);
        if (ret != ERR_SUCCESS)
        {
            free(cluster_buffer);
            return ERR_SUCCESS;
        }

        dword_t i;
        bool_t in_use = FALSE;
        fatfs_dirent_t *entries = (fatfs_dirent_t*)cluster_buffer;

        for (i = 0; i < entries_per_cluster; i++)
        {
            if (entries[i].filename[0] != FATFS_DELETED && entries[i].filename[0] == 0)
            {
                in_use = TRUE;
                break;
            }
        }

        free(cluster_buffer);
        cluster_buffer = NULL;
        entries = NULL;

        if (!in_use)
        {
            dword_t next_in_directory = fatfs_get_next_cluster(volume, cluster_num);

            dword_t directory_start = volume->root_dir_cluster;
            fatfs_dirent_t parent_dirent;
            dword_t parent_dir_entry_cluster;
            dword_t parent_dir_entry_number;
            char *parent_path = __builtin_alloca(strlen(path) + 1);
            strcpy(parent_path, path);
            char *last_slash = strrchr(parent_path, '/');

            if (last_slash)
            {
                *last_slash = '\0';

                ret = fatfs_find_entry(volume, parent_path, &parent_dir_entry_cluster, &parent_dir_entry_number, &parent_dirent);
                ASSERT(ret == ERR_SUCCESS);

                directory_start = parent_dirent.first_cluster_low | (parent_dirent.first_cluster_high << 16);
            }

            if (directory_start == cluster_num)
            {
                if (next_in_directory >= FATFS_INVALID_CLUSTER) next_in_directory = 0;
                parent_dirent.first_cluster_low = next_in_directory & 0xFFFF;
                parent_dirent.first_cluster_high = next_in_directory >> 16;

                device_write(volume->header.device,
                             &parent_dirent,
                             fatfs_get_cluster_offset(volume, parent_dir_entry_cluster)
                             + (qword_t)(parent_dir_entry_number * sizeof(fatfs_dirent_t)),
                             sizeof(fatfs_dirent_t),
                             &actually_written);
            }
            else
            {
                dword_t current_cluster = directory_start;

                while (TRUE)
                {
                    dword_t next_cluster = fatfs_get_next_cluster(volume, current_cluster);
                    ASSERT(next_cluster < FATFS_INVALID_CLUSTER);
                    if (next_cluster == cluster_num) break;
                }

                fatfs_set_next_cluster(volume, current_cluster, next_in_directory);
            }

            fatfs_set_next_cluster(volume, cluster_num, 0);
        }
    }

    if (purge) return fatfs_purge_file(volume, dirent.first_cluster_low | (dirent.first_cluster_high << 16));
    else return TRUE;
}

static dword_t fatfs_read_file(file_instance_t *instance, void *buffer, qword_t offset, size_t length, size_t *bytes_read)
{
    if (instance->mode & FILE_MODE_NO_CACHE)
    {
        return fatfs_read_internal(&instance->header, buffer, offset, length, bytes_read);
    }
    else
    {
        fatfs_file_t *file = CONTAINER_OF(instance->global, fatfs_file_t, header);
        return read_cache(&file->cache, &instance->header, buffer, offset, length, bytes_read);
    }
}

static dword_t fatfs_write_file(file_instance_t *instance, const void *buffer, qword_t offset, size_t length, size_t *bytes_written)
{
    if (instance->mode & FILE_MODE_NO_CACHE)
    {
        return fatfs_write_internal(&instance->header, buffer, offset, length, bytes_written);
    }
    else
    {
        fatfs_file_t *file = CONTAINER_OF(instance->global, fatfs_file_t, header);
        return write_cache(&file->cache, &instance->header, buffer, offset, length, bytes_written);
    }
}

static dword_t fatfs_list_dir(file_instance_t *_directory, char *filename, bool_t continue_scan)
{
    fatfs_volume_t *volume = CONTAINER_OF(_directory->global->volume, fatfs_volume_t, header);
    fatfs_file_t *directory = CONTAINER_OF(_directory->global, fatfs_file_t, header);
    fatfs_file_instance_t *instance = CONTAINER_OF(_directory, fatfs_file_instance_t, header);

    if (!continue_scan)
    {
        instance->last_offset = 0ULL;
        instance->last_cluster = directory->first_cluster;
    }

    if (instance->last_cluster >= FATFS_INVALID_CLUSTER) return ERR_NOMORE;

    fatfs_dirent_t dirent;
    size_t actually_read;

    dword_t ret = device_read(volume->header.device,
                              &dirent,
                              fatfs_get_cluster_offset(volume, instance->last_cluster) + (instance->last_offset & 0x1FF),
                              sizeof(fatfs_dirent_t),
                              &actually_read);
    if (ret != ERR_SUCCESS) return ret;

    instance->last_offset += sizeof(fatfs_dirent_t);

    if ((instance->last_offset & 0x1FF) == 0)
    {
        instance->last_cluster = fatfs_get_next_cluster(volume, instance->last_cluster);
    }

    fatfs_get_entry_name(&dirent, filename);
    return ERR_SUCCESS;
}

static dword_t fatfs_set_file(file_t *_file, dword_t info_type, const void *buffer, size_t size)
{
    fatfs_file_t *file = CONTAINER_OF(_file, fatfs_file_t, header);

    switch (info_type)
    {
    case FILE_INFO_ATTRIBUTES:
    case FILE_INFO_TIME:
        return ERR_NOSYSCALL; // TODO

    case FILE_INFO_SIZE:
        if (size >= sizeof(qword_t))
        {
            return fatfs_resize_file(file, *(qword_t*)buffer);
        }
        else
        {
            return ERR_SMALLBUF;
        }

    default:
        return ERR_INVALID;
    }
}

dword_t driver_load(const char *parameters)
{
    register_filesystem_driver(&fatfs_driver);
    return ERR_SUCCESS;
}
