Skip to content

File syn_vfs.c

File List > src > syntropic > storage > syn_vfs.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_VFS) || SYN_USE_VFS

#include "../util/syn_assert.h"
#include "syn_vfs.h"

#include <string.h>

static SYN_VfsMount g_mounts[SYN_VFS_MAX_MOUNTS]; 
static size_t g_mount_count = 0;                  
static SYN_VfsFile g_files[SYN_VFS_MAX_OPEN_FILES]; 
static SYN_VfsDir g_dirs[SYN_VFS_MAX_OPEN_DIRS];    
void syn_vfs_init(void)
{
    g_mount_count = 0;
    memset(g_mounts, 0, sizeof(g_mounts));
    memset(g_files, 0, sizeof(g_files));
    memset(g_dirs, 0, sizeof(g_dirs));
}

SYN_Status syn_vfs_mount(const char *prefix, const SYN_VfsOps *ops, void *fs_data)
{
    SYN_ASSERT(prefix != NULL);
    SYN_ASSERT(ops != NULL);

    if (prefix == NULL || ops == NULL) {
        return SYN_INVALID_PARAM;
    }

    if (g_mount_count >= SYN_VFS_MAX_MOUNTS) {
        return SYN_ERROR;
    }

    /* Verify if prefix already exists to prevent duplicate mounts */
    for (size_t i = 0; i < g_mount_count; i++) {
        if (strcmp(g_mounts[i].prefix, prefix) == 0) {
            return SYN_ERROR;
        }
    }

    g_mounts[g_mount_count].prefix = prefix;
    g_mounts[g_mount_count].prefix_len = strlen(prefix);
    g_mounts[g_mount_count].ops = ops;
    g_mounts[g_mount_count].fs_data = fs_data;
    g_mount_count++;

    return SYN_OK;
}

SYN_Status syn_vfs_unmount(const char *prefix)
{
    SYN_ASSERT(prefix != NULL);
    if (prefix == NULL) {
        return SYN_INVALID_PARAM;
    }

    for (size_t i = 0; i < g_mount_count; i++) {
        if (strcmp(g_mounts[i].prefix, prefix) == 0) {
            /* Shift down remaining mounts */
            for (size_t j = i; j < g_mount_count - 1; j++) {
                g_mounts[j] = g_mounts[j + 1];
            }
            g_mount_count--;
            memset(&g_mounts[g_mount_count], 0, sizeof(SYN_VfsMount));
            return SYN_OK;
        }
    }

    return SYN_ERROR;
}

static const SYN_VfsMount *find_mount(const char *path, const char **rel_path)
{
    size_t longest_match = 0;
    const SYN_VfsMount *matched = NULL;

    for (size_t i = 0; i < g_mount_count; i++) {
        size_t len = g_mounts[i].prefix_len;
        if (strncmp(path, g_mounts[i].prefix, len) == 0) {
            /* Prefix match. Check if boundary is slash or end of string */
            if (path[len] == '\0' || path[len] == '/') {
                if (len > longest_match) {
                    longest_match = len;
                    matched = &g_mounts[i];
                }
            }
        }
    }

    if (matched) {
        *rel_path = path + longest_match;
        if (**rel_path == '\0') {
            *rel_path = "/";
        }
    }

    return matched;
}

int syn_vfs_stat(const char *path, SYN_VfsDirEnt *ent)
{
    SYN_ASSERT(path != NULL && ent != NULL);
    const char *rel_path = NULL;
    const SYN_VfsMount *m = find_mount(path, &rel_path);
    if (!m || !m->ops->stat) {
        return -1;
    }
    return m->ops->stat(rel_path, ent, m->fs_data);
}

int syn_vfs_rename(const char *old_path, const char *new_path)
{
    SYN_ASSERT(old_path != NULL && new_path != NULL);
    const char *rel_old = NULL;
    const char *rel_new = NULL;
    const SYN_VfsMount *m_old = find_mount(old_path, &rel_old);
    const SYN_VfsMount *m_new = find_mount(new_path, &rel_new);

    if (!m_old || !m_new || m_old != m_new || !m_old->ops->rename) {
        return -1; /* Rename requires same filesystem mount */
    }

    return m_old->ops->rename(rel_old, rel_new, m_old->fs_data);
}

int syn_vfs_open(const char *path, int flags)
{
    SYN_ASSERT(path != NULL);

    const char *rel_path = NULL;
    const SYN_VfsMount *m = find_mount(path, &rel_path);
    if (!m || !m->ops->open) {
        return -1; /* Mount not found or open not supported */
    }

    /* Find free file descriptor */
    int fd = -1;
    for (int i = 0; i < SYN_VFS_MAX_OPEN_FILES; i++) {
        if (!g_files[i].is_open) {
            fd = i;
            break;
        }
    }

    if (fd < 0) {
        return -2; /* No free file descriptors */
    }

    g_files[fd].ops = m->ops;
    g_files[fd].fs_file = NULL;
    g_files[fd].is_open = true;

    int ret = m->ops->open(&g_files[fd], rel_path, flags, m->fs_data);
    if (ret < 0) {
        g_files[fd].is_open = false;
        return ret;
    }

    return fd;
}

int syn_vfs_close(int fd)
{
    if (fd < 0 || fd >= SYN_VFS_MAX_OPEN_FILES || !g_files[fd].is_open) {
        return -1;
    }

    int ret = 0;
    if (g_files[fd].ops->close) {
        ret = g_files[fd].ops->close(&g_files[fd]);
    }

    g_files[fd].is_open = false;
    return ret;
}

int syn_vfs_read(int fd, void *buf, size_t len)
{
    if (fd < 0 || fd >= SYN_VFS_MAX_OPEN_FILES || !g_files[fd].is_open) {
        return -1;
    }
    if (!g_files[fd].ops->read) {
        return -2;
    }
    return g_files[fd].ops->read(&g_files[fd], buf, len);
}

int syn_vfs_write(int fd, const void *buf, size_t len)
{
    if (fd < 0 || fd >= SYN_VFS_MAX_OPEN_FILES || !g_files[fd].is_open) {
        return -1;
    }
    if (!g_files[fd].ops->write) {
        return -2;
    }
    return g_files[fd].ops->write(&g_files[fd], buf, len);
}

int32_t syn_vfs_seek(int fd, int32_t offset, int whence)
{
    if (fd < 0 || fd >= SYN_VFS_MAX_OPEN_FILES || !g_files[fd].is_open) {
        return -1;
    }
    if (!g_files[fd].ops->seek) {
        return -2;
    }
    return g_files[fd].ops->seek(&g_files[fd], offset, whence);
}

int32_t syn_vfs_tell(int fd)
{
    if (fd < 0 || fd >= SYN_VFS_MAX_OPEN_FILES || !g_files[fd].is_open) {
        return -1;
    }
    if (!g_files[fd].ops->tell) {
        return -2;
    }
    return g_files[fd].ops->tell(&g_files[fd]);
}

int syn_vfs_unlink(const char *path)
{
    SYN_ASSERT(path != NULL);
    const char *rel_path = NULL;
    const SYN_VfsMount *m = find_mount(path, &rel_path);
    if (!m || !m->ops->unlink) {
        return -1;
    }
    return m->ops->unlink(rel_path, m->fs_data);
}

int syn_vfs_mkdir(const char *path)
{
    SYN_ASSERT(path != NULL);
    const char *rel_path = NULL;
    const SYN_VfsMount *m = find_mount(path, &rel_path);
    if (!m || !m->ops->mkdir) {
        return -1;
    }
    return m->ops->mkdir(rel_path, m->fs_data);
}

int syn_vfs_opendir(const char *path)
{
    SYN_ASSERT(path != NULL);
    const char *rel_path = NULL;
    const SYN_VfsMount *m = find_mount(path, &rel_path);
    if (!m || !m->ops->opendir) {
        return -1;
    }

    /* Find free dir descriptor */
    int dd = -1;
    for (int i = 0; i < SYN_VFS_MAX_OPEN_DIRS; i++) {
        if (!g_dirs[i].is_open) {
            dd = i;
            break;
        }
    }

    if (dd < 0) {
        return -2; /* No free dir slots */
    }

    g_dirs[dd].ops = m->ops;
    g_dirs[dd].fs_dir = NULL;
    g_dirs[dd].is_open = true;

    int ret = m->ops->opendir(&g_dirs[dd], rel_path, m->fs_data);
    if (ret < 0) {
        g_dirs[dd].is_open = false;
        return ret;
    }

    return dd;
}

int syn_vfs_readdir(int dd, SYN_VfsDirEnt *ent)
{
    if (dd < 0 || dd >= SYN_VFS_MAX_OPEN_DIRS || !g_dirs[dd].is_open) {
        return -1;
    }
    if (!g_dirs[dd].ops->readdir) {
        return -2;
    }
    return g_dirs[dd].ops->readdir(&g_dirs[dd], ent);
}

int syn_vfs_closedir(int dd)
{
    if (dd < 0 || dd >= SYN_VFS_MAX_OPEN_DIRS || !g_dirs[dd].is_open) {
        return -1;
    }

    int ret = 0;
    if (g_dirs[dd].ops->closedir) {
        ret = g_dirs[dd].ops->closedir(&g_dirs[dd]);
    }

    g_dirs[dd].is_open = false;
    return ret;
}

#endif /* SYN_USE_VFS */