Implement a ramdisk filesystem driver.
authorcoderain <coderain@sdf.org>
Sun, 26 Feb 2017 02:57:26 +0000 (03:57 +0100)
committercoderain <coderain@sdf.org>
Sun, 26 Feb 2017 02:57:26 +0000 (03:57 +0100)
kernel/src/drivers/fs/ram.c [new file with mode: 0644]
kernel/src/drivers/fs/ram.h [new file with mode: 0644]
kernel/src/start.c

diff --git a/kernel/src/drivers/fs/ram.c b/kernel/src/drivers/fs/ram.c
new file mode 100644 (file)
index 0000000..5085571
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ * ram.c
+ *
+ * Copyright (C) 2017 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 "ram.h"
+#include <heap.h>
+#include <syscalls.h>
+
+static dword_t ramfs_mount(const char *device, dword_t flags);
+static dword_t ramfs_unmount(mounted_volume_t *volume);
+static dword_t ramfs_load_file(file_t **file);
+static dword_t ramfs_unload_file(file_t *file);
+static dword_t ramfs_open_file(file_instance_t **instance);
+static dword_t ramfs_close_file(file_instance_t *instance);
+static dword_t ramfs_delete_file(mounted_volume_t *volume, const char *path, bool_t purge);
+static dword_t ramfs_read_file(file_instance_t *file, void *buffer, qword_t offset, size_t length, size_t *bytes_read);
+static dword_t ramfs_write_file(file_instance_t *file, const void *buffer, qword_t offset, size_t length, size_t *bytes_written);
+static dword_t ramfs_list_dir(file_instance_t *file, char *filename, bool_t continue_scan);
+static dword_t ramfs_set_file(file_t *file, dword_t info_type, const void *buffer, size_t size);
+
+static fs_driver_t ramfs_driver =
+{
+    .name = "RAM",
+    .mount = ramfs_mount,
+    .unmount = ramfs_unmount,
+    .load_file = ramfs_load_file,
+    .unload_file = ramfs_unload_file,
+    .open_file = ramfs_open_file,
+    .close_file = ramfs_close_file,
+    .delete_file = ramfs_delete_file,
+    .read_file = ramfs_read_file,
+    .write_file = ramfs_write_file,
+    .list_dir = ramfs_list_dir,
+    .set_file = ramfs_set_file,
+};
+
+static ramfs_node_t *ramfs_get_entry(ramfs_volume_t *volume, const char *path)
+{
+    char path_copy[MAX_PATH];
+    char *token, *endptr;
+    strcpy(path_copy, path);
+
+    ramfs_node_t *current = &volume->root;
+
+    for (token = strtok(path_copy, PATH_DELIMITER_STRING, &endptr);
+         token != NULL;
+         token = strtok(NULL, PATH_DELIMITER_STRING, &endptr))
+    {
+        if (*token == '\0' || !(current->attributes & FILE_ATTR_DIRECTORY)) return NULL;
+
+        list_entry_t *ptr;
+        list_entry_t *files = &current->files;
+
+        for (ptr = files->next; ptr != files; ptr = ptr->next)
+        {
+            current = CONTAINER_OF(ptr, ramfs_node_t, list);
+            if (strcmp(current->name, token) == 0) break;
+        }
+
+        if (ptr == files) return NULL;
+    }
+
+    return current;
+}
+
+static void ramfs_purge_file(ramfs_volume_t *volume, ramfs_node_t *node)
+{
+    volume->max_size += strlen(node->name) + 1;
+    free(node->name);
+    node->name = NULL;
+
+    if (!(node->attributes & FILE_ATTR_DIRECTORY))
+    {
+        volume->max_size += node->size;
+        free(node->contents);
+    }
+
+    volume->max_size += sizeof(ramfs_node_t);
+    free(node);
+}
+
+static dword_t ramfs_mount(const char *device, dword_t flags)
+{
+    char *endptr;
+    char *device_name = strdup(device);
+
+    char *requested_size = strchr(device_name, ':');
+    if (requested_size == NULL)
+    {
+        free(device_name);
+        return ERR_INVALID;
+    }
+
+    *requested_size++ = '\0';
+
+    qword_t size = strtoull(requested_size, &endptr, 10);
+    if (!size)
+    {
+        free(device_name);
+        return ERR_INVALID;
+    }
+
+    switch (*endptr)
+    {
+    case 'K':
+        size *= 1024ULL;
+        break;
+
+    case 'M':
+        size *= 1048576ULL;
+        break;
+
+    case 'G':
+        size *= 1073741824ULL;
+        break;
+    }
+
+    device_t *virtual_device = (device_t*)malloc(sizeof(device_t));
+    if (virtual_device == NULL) return ERR_NOMEMORY;
+
+    virtual_device->flags = 0;
+    virtual_device->driver = NULL;
+    strcpy(virtual_device->name, device_name);
+    virtual_device->capacity = size;
+    virtual_device->resource = 0;
+
+    dword_t ret = register_block_device(virtual_device);
+    if (ret != ERR_SUCCESS)
+    {
+        free(virtual_device);
+        free(device_name);
+        return ret;
+    }
+
+    ramfs_volume_t *volume = (ramfs_volume_t*)malloc(sizeof(ramfs_volume_t));
+    if (volume == NULL)
+    {
+        unregister_block_device(virtual_device);
+        free(virtual_device);
+        free(device_name);
+        return ERR_NOMEMORY;
+    }
+
+    volume->header.driver = &ramfs_driver;
+    volume->header.device = NULL;
+    volume->max_size = size;
+    list_init(&volume->root.list);
+    volume->root.name = "";
+    volume->root.attributes = FILE_ATTR_DIRECTORY;
+    list_init(&volume->root.files);
+
+    ret = register_mounted_volume(&volume->header);
+
+    if (ret != ERR_SUCCESS)
+    {
+        free(volume);
+        unregister_block_device(virtual_device);
+        free(virtual_device);
+    }
+
+    free(device_name);
+    return ret;
+}
+
+static void ramfs_release_node_recursive(ramfs_node_t *node)
+{
+    if (node->attributes & FILE_ATTR_DIRECTORY)
+    {
+        while (node->files.next != &node->files)
+        {
+            ramfs_node_t *child_node = CONTAINER_OF(node->files.next, ramfs_node_t, list);
+            ramfs_release_node_recursive(child_node);
+            list_remove(&child_node->list);
+            heap_free(&evictable_heap, child_node);
+        }
+    }
+    else
+    {
+        heap_free(&evictable_heap, node->contents);
+        node->contents = NULL;
+        node->size = 0ULL;
+    }
+
+    heap_free(&evictable_heap, node->name);
+    node->name = NULL;
+}
+
+static dword_t ramfs_unmount(mounted_volume_t *_volume)
+{
+    ramfs_volume_t *volume = CONTAINER_OF(_volume, ramfs_volume_t, header);
+    ramfs_release_node_recursive(&volume->root);
+
+    unregister_mounted_volume(&volume->header);
+    free(volume);
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_load_file(file_t **_file)
+{
+    ramfs_file_t *file = realloc(*_file, sizeof(ramfs_file_t));
+    if (file == NULL) return ERR_NOMEMORY;
+    *_file = &file->header;
+
+    ramfs_volume_t *volume = CONTAINER_OF(file->header.volume, ramfs_volume_t, header);
+    ramfs_node_t *node = ramfs_get_entry(volume, file->header.path);
+
+    if (node == NULL)
+    {
+        if (!(file->header.global_mode & FILE_MODE_CREATE)) return ERR_NOTFOUND;
+        char parent_dir[MAX_PATH];
+
+        strcpy(parent_dir, file->header.path);
+        char *last_delimiter = strrchr(parent_dir, PATH_DELIMITER_CHAR);
+        if (last_delimiter) *last_delimiter = '\0';
+        else *parent_dir = '\0';
+
+        ramfs_node_t *dir = ramfs_get_entry(volume, parent_dir);
+        if (dir == NULL || !(dir->attributes & FILE_ATTR_DIRECTORY)) return ERR_ISNOTDIR;
+
+        node = heap_alloc(&evictable_heap, sizeof(ramfs_node_t));
+        if (node == NULL) return ERR_DISKFULL;
+
+        node->name = heap_alloc(&evictable_heap, strlen(last_delimiter + 1) + 1);
+        strcpy(node->name, last_delimiter + 1);
+
+        node->attributes = file->header.attributes;
+
+        if (node->attributes & FILE_ATTR_DIRECTORY)
+        {
+            list_init(&node->files);
+        }
+        else
+        {
+            node->contents = NULL;
+            node->size = 0;
+        }
+
+        list_append(&dir->files, &node->list);
+    }
+
+    file->header.attributes = node->attributes;
+    file->node = node;
+
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_unload_file(file_t *_file)
+{
+    ramfs_file_t *file = CONTAINER_OF(_file, ramfs_file_t, header);
+    ramfs_volume_t *volume = CONTAINER_OF(file->header.volume, ramfs_volume_t, header);
+
+    if (file->header.attributes & FILE_ATTR_DELETED)
+    {
+        ramfs_purge_file(volume, file->node);
+    }
+
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_open_file(file_instance_t **_instance)
+{
+    ramfs_file_instance_t *instance = realloc(*_instance, sizeof(ramfs_file_instance_t));
+    if (instance == NULL) return ERR_NOMEMORY;
+    *_instance = &instance->header;
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_close_file(file_instance_t *instance)
+{
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_delete_file(mounted_volume_t *_volume, const char *path, bool_t purge)
+{
+    ramfs_volume_t *volume = CONTAINER_OF(volume, ramfs_volume_t, header);
+    ramfs_node_t *node = ramfs_get_entry(volume, path);
+    if (node == NULL) return ERR_NOTFOUND;
+
+    list_remove(&node->list);
+    if (purge) ramfs_purge_file(volume, node);
+
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_read_file(file_instance_t *_instance, void *buffer, qword_t offset, size_t length, size_t *bytes_read)
+{
+    ramfs_file_t *file = CONTAINER_OF(_instance->global, ramfs_file_t, header);
+    if (length == 0) return ERR_SUCCESS;
+    if ((offset + length) > file->node->size) return ERR_BEYOND;
+
+    uintptr_t actual_length = MIN(length, file->node->size - offset);
+    memcpy(buffer, &file->node->contents[(uintptr_t)offset], actual_length);
+    *bytes_read = actual_length;
+
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_write_file(file_instance_t *_instance, const void *buffer, qword_t offset, size_t length, size_t *bytes_written)
+{
+    ramfs_file_t *file = CONTAINER_OF(_instance->global, ramfs_file_t, header);
+    ramfs_volume_t *volume = CONTAINER_OF(file->header.volume, ramfs_volume_t, header);
+    if (length == 0) return ERR_SUCCESS;
+
+    if ((offset + length) > file->node->size)
+    {
+        if ((offset + length - file->node->size) > volume->max_size) return ERR_DISKFULL;
+
+        byte_t *new_contents = heap_realloc(&evictable_heap, file->node->contents, offset + length);
+        if (!new_contents) return ERR_DISKFULL;
+
+        file->node->contents = new_contents;
+        memset(&file->node->contents[file->node->size], 0, offset + length - file->node->size);
+        file->node->size = offset + length;
+        volume->max_size -= offset + length - file->node->size;
+    }
+
+    memcpy(&file->node->contents[(uintptr_t)offset], buffer, length);
+    *bytes_written = length;
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_list_dir(file_instance_t *_instance, char *filename, bool_t continue_scan)
+{
+    ramfs_file_t *directory = CONTAINER_OF(_instance->global, ramfs_file_t, header);
+    ramfs_file_instance_t *instance = CONTAINER_OF(_instance, ramfs_file_instance_t, header);
+    if (!continue_scan) instance->dir_scan_node = directory->node->files.next;
+    if (instance->dir_scan_node == &directory->node->files) return ERR_NOMORE;
+
+    ramfs_node_t *node = CONTAINER_OF(instance->dir_scan_node, ramfs_node_t, list);
+    strcpy(filename, node->name);
+
+    instance->dir_scan_node = instance->dir_scan_node->next;
+    return ERR_SUCCESS;
+}
+
+static dword_t ramfs_set_file(file_t *_file, dword_t info_type, const void *buffer, size_t size)
+{
+    ramfs_volume_t *volume = CONTAINER_OF(_file->volume, ramfs_volume_t, header);
+    ramfs_file_t *file = CONTAINER_OF(_file, ramfs_file_t, header);
+
+    switch (info_type)
+    {
+    case FILE_INFO_ATTRIBUTES:
+    case FILE_INFO_TIME:
+        // TODO
+        return ERR_NOSYSCALL;
+
+    case FILE_INFO_SIZE:
+        if (size >= sizeof(qword_t))
+        {
+            qword_t new_size = *((qword_t*)buffer);
+            if ((new_size - file->node->size) > volume->max_size) return ERR_DISKFULL;
+
+            byte_t *new_contents = heap_realloc(&evictable_heap, file->node->contents, new_size);
+            if (!new_contents) return ERR_DISKFULL;
+
+            if (new_size > file->node->size)
+            {
+                memset(&file->node->contents[file->node->size], 0, new_size - file->node->size);
+            }
+
+            file->node->contents = new_contents;
+            volume->max_size -= new_size - file->node->size;
+            file->node->size = new_size;
+        }
+        else
+        {
+            return ERR_SMALLBUF;
+        }
+
+    default:
+        return ERR_INVALID;
+    }
+}
+
+dword_t ramfs_driver_load(void)
+{
+    register_filesystem_driver(&ramfs_driver);
+    return ERR_SUCCESS;
+}
diff --git a/kernel/src/drivers/fs/ram.h b/kernel/src/drivers/fs/ram.h
new file mode 100644 (file)
index 0000000..cbb7890
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * ram.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef _RAM_H_
+#define _RAM_H_
+
+#include <common.h>
+#include <list.h>
+#include <clock.h>
+#include <filesystem.h>
+
+typedef struct
+{
+    list_entry_t list;
+    char *name;
+    dword_t attributes;
+
+    union
+    {
+        struct
+        {
+            byte_t *contents;
+            qword_t size;
+        };
+
+        list_entry_t files;
+    };
+} ramfs_node_t;
+
+typedef struct
+{
+    file_t header;
+    ramfs_node_t *node;
+} ramfs_file_t;
+
+typedef struct
+{
+    file_instance_t header;
+    list_entry_t *dir_scan_node;
+} ramfs_file_instance_t;
+
+typedef struct
+{
+    mounted_volume_t header;
+    qword_t max_size;
+    ramfs_node_t root;
+} ramfs_volume_t;
+
+dword_t ramfs_driver_load(void);
+
+#endif
index bacc0f373f4fab63af066ee4b79df8be17d72935..e63f54d0af9a37c39fcce78a0976b0121265066b 100644 (file)
@@ -43,6 +43,7 @@ extern dword_t serial_driver_load(void);
 extern dword_t serial_driver_unload(void);
 extern dword_t vesa_driver_load(void);
 extern dword_t fatfs_driver_load(void);
+extern dword_t ramfs_driver_load(void);
 
 typedef dword_t (*driver_proc_t)(void);
 
@@ -57,6 +58,7 @@ static struct
     { .name = "serial", .startup_proc = serial_driver_load, .cleanup_proc = serial_driver_unload },
     { .name = "floppy", .startup_proc = floppy_driver_load },
     { .name = "fatfs", .startup_proc = fatfs_driver_load },
+    { .name = "ramfs", .startup_proc = ramfs_driver_load },
     { .name = "vesa", .startup_proc = vesa_driver_load },
     { .startup_proc = NULL }
 };