constfs.c 9.61 KB
/*
 * Copyright (C) 2016 Eistec AB
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     sys_fs_constfs
 * @{
 *
 * @file
 * @brief       ConstFS implementation
 *
 * @author      Joakim Nohlgård <joakim.nohlgard@eistec.se>
 *
 * @}
 */

/* Required for strnlen in string.h, when building with -std=c99 */
#define _DEFAULT_SOURCE 1
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include "fs/constfs.h"
#include "vfs.h"

#define ENABLE_DEBUG (0)
#include "debug.h"

/* File system operations */
static int constfs_mount(vfs_mount_t *mountp);
static int constfs_umount(vfs_mount_t *mountp);
static int constfs_unlink(vfs_mount_t *mountp, const char *name);
static int constfs_stat(vfs_mount_t *mountp, const char *restrict name, struct stat *restrict buf);
static int constfs_statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf);

/* File operations */
static int constfs_close(vfs_file_t *filp);
static int constfs_fstat(vfs_file_t *filp, struct stat *buf);
static off_t constfs_lseek(vfs_file_t *filp, off_t off, int whence);
static int constfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path);
static ssize_t constfs_read(vfs_file_t *filp, void *dest, size_t nbytes);
static ssize_t constfs_write(vfs_file_t *filp, const void *src, size_t nbytes);

/* Directory operations */
static int constfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path);
static int constfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry);
static int constfs_closedir(vfs_DIR *dirp);

static const vfs_file_system_ops_t constfs_fs_ops = {
    .mount = constfs_mount,
    .umount = constfs_umount,
    .unlink = constfs_unlink,
    .statvfs = constfs_statvfs,
    .stat = constfs_stat,
};

static const vfs_file_ops_t constfs_file_ops = {
    .close = constfs_close,
    .fstat = constfs_fstat,
    .lseek = constfs_lseek,
    .open  = constfs_open,
    .read  = constfs_read,
    .write = constfs_write,
};

static const vfs_dir_ops_t constfs_dir_ops = {
    .opendir = constfs_opendir,
    .readdir = constfs_readdir,
    .closedir = constfs_closedir,
};


const vfs_file_system_t constfs_file_system = {
    .f_op = &constfs_file_ops,
    .fs_op = &constfs_fs_ops,
    .d_op = &constfs_dir_ops,
};

/**
 * @internal
 * @brief Fill a file information struct with information about the file
 * pointed to by @p fp
 *
 * @param[in]  fp     file to query
 * @param[out] buf    output buffer
 */
static void _constfs_write_stat(const constfs_file_t *fp, struct stat *restrict buf);

static int constfs_mount(vfs_mount_t *mountp)
{
    /* perform any extra initialization here */
    (void) mountp; /* prevent warning: unused parameter */
    return 0;
}

static int constfs_umount(vfs_mount_t *mountp)
{
    /* free resources and perform any clean up here */
    (void) mountp; /* prevent warning: unused parameter */
    return 0;
}

static int constfs_unlink(vfs_mount_t *mountp, const char *name)
{
    /* Removing files is prohibited */
    (void) mountp; /* prevent warning: unused parameter */
    (void) name; /* prevent warning: unused parameter */
    return -EROFS;
}

static int constfs_stat(vfs_mount_t *mountp, const char *restrict name, struct stat *restrict buf)
{
    (void) name;
    /* Fill out some information about this file */
    if (buf == NULL) {
        return -EFAULT;
    }
    constfs_t *fs = mountp->private_data;
    /* linear search through the files array */
    for (size_t i = 0; i < fs->nfiles; ++i) {
        DEBUG("constfs_stat ? \"%s\"\n", fs->files[i].path);
        if (strcmp(fs->files[i].path, name) == 0) {
            DEBUG("constfs_stat: Found :)\n");
            _constfs_write_stat(&fs->files[i], buf);
            buf->st_ino = i;
            return 0;
        }
    }
    DEBUG("constfs_stat: Not found :(\n");
    return -ENOENT;
}

static int constfs_statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf)
{
    (void) path;
    /* Fill out some information about this file system */
    if (buf == NULL) {
        return -EFAULT;
    }
    constfs_t *fs = mountp->private_data;
    /* clear out the stat buffer first */
    memset(buf, 0, sizeof(*buf));
    buf->f_bsize = sizeof(uint8_t); /* block size */
    buf->f_frsize = sizeof(uint8_t); /* fundamental block size */
    fsblkcnt_t f_blocks = 0;
    for (size_t i = 0; i < fs->nfiles; ++i) {
        f_blocks += fs->files[i].size;
    }
    buf->f_blocks = f_blocks;  /* Blocks total */
    buf->f_bfree = 0;          /* Blocks free */
    buf->f_bavail = 0;         /* Blocks available to non-privileged processes */
    buf->f_files = fs->nfiles; /* Total number of file serial numbers */
    buf->f_ffree = 0;          /* Total number of free file serial numbers */
    buf->f_favail = 0;         /* Number of file serial numbers available to non-privileged process */
    buf->f_fsid = 0;           /* File system id */
    buf->f_flag = (ST_RDONLY | ST_NOSUID); /* File system flags */
    buf->f_namemax = UINT8_MAX; /* Maximum file name length */
    return 0;
}

static int constfs_close(vfs_file_t *filp)
{
    /* perform any necessary clean ups */
    (void) filp; /* prevent warning: unused parameter */
    return 0;
}

static int constfs_fstat(vfs_file_t *filp, struct stat *buf)
{
    constfs_file_t *fp = filp->private_data.ptr;
    if (buf == NULL) {
        return -EFAULT;
    }
    _constfs_write_stat(fp, buf);
    return 0;
}

static off_t constfs_lseek(vfs_file_t *filp, off_t off, int whence)
{
    constfs_file_t *fp = filp->private_data.ptr;
    switch (whence) {
        case SEEK_SET:
            break;
        case SEEK_CUR:
            off += filp->pos;
            break;
        case SEEK_END:
            off += fp->size;
            break;
        default:
            return -EINVAL;
    }
    if (off < 0) {
        /* the resulting file offset would be negative */
        return -EINVAL;
    }
    /* POSIX allows seeking past the end of the file, even with O_RDONLY */
    filp->pos = off;
    return off;
}

static int constfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path)
{
    (void) mode;
    (void) abs_path;
    constfs_t *fs = filp->mp->private_data;
    DEBUG("constfs_open: %p, \"%s\", 0x%x, 0%03lo, \"%s\"\n", (void *)filp, name, flags, (unsigned long)mode, abs_path);
    /* We only support read access */
    if ((flags & O_ACCMODE) != O_RDONLY) {
        return -EROFS;
    }
    /* linear search through the files array */
    for (size_t i = 0; i < fs->nfiles; ++i) {
        DEBUG("constfs_open ? \"%s\"\n", fs->files[i].path);
        if (strcmp(fs->files[i].path, name) == 0) {
            DEBUG("constfs_open: Found :)\n");
            filp->private_data.ptr = (void *)&fs->files[i];
            return 0;
        }
    }
    DEBUG("constfs_open: Not found :(\n");
    return -ENOENT;
}

static ssize_t constfs_read(vfs_file_t *filp, void *dest, size_t nbytes)
{
    constfs_file_t *fp = filp->private_data.ptr;
    DEBUG("constfs_read: %p, %p, %lu\n", (void *)filp, dest, (unsigned long)nbytes);
    if ((size_t)filp->pos >= fp->size) {
        /* Current offset is at or beyond end of file */
        return 0;
    }

    if (nbytes > (fp->size - filp->pos)) {
        nbytes = fp->size - filp->pos;
    }
    memcpy(dest, fp->data + filp->pos, nbytes);
    DEBUG("constfs_read: read %d bytes\n", nbytes);
    filp->pos += nbytes;
    return nbytes;
}

static ssize_t constfs_write(vfs_file_t *filp, const void *src, size_t nbytes)
{
    DEBUG("constfs_write: %p, %p, %lu\n", (void *)filp, src, (unsigned long)nbytes);
    /* Read only file system */
    DEBUG("constfs_write: read only FS\n");
    /* prevent warning: unused parameter */
    (void) filp;
    (void) src;
    (void) nbytes;
    return -EBADF;
}

static int constfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path)
{
    (void) abs_path;
    DEBUG("constfs_opendir: %p, \"%s\", \"%s\"\n", (void *)dirp, dirname, abs_path);
    if (strncmp(dirname, "/", 2) != 0) {
        /* We keep it simple and only support a flat file system, only a root directory */
        return -ENOENT;
    }
    dirp->private_data.value = 0;
    return 0;
}

static int constfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry)
{
    DEBUG("constfs_readdir: %p, %p\n", (void *)dirp, (void *)entry);
    constfs_t *fs = dirp->mp->private_data;
    int filenum = dirp->private_data.value;
    if ((size_t)filenum >= fs->nfiles) {
        /* End of stream */
        return 0;
    }
    const constfs_file_t *fp = &fs->files[filenum];
    if (fp->path == NULL) {
        return -EIO;
    }
    size_t len = strnlen(fp->path, VFS_NAME_MAX + 1);
    if (len > VFS_NAME_MAX) {
        /* name does not fit in vfs_dirent_t buffer */
        /* skipping past the broken entry */
        ++filenum;
        dirp->private_data.value = filenum;
        return -EAGAIN;
    }
    /* copy the string, including terminating null */
    memcpy(&entry->d_name[0], fp->path, len + 1);
    entry->d_ino = filenum;
    ++filenum;
    dirp->private_data.value = filenum;
    return 1;
}

static int constfs_closedir(vfs_DIR *dirp)
{
    /* Just an example, it's not necessary to define closedir if there is
     * nothing to clean up */
    (void) dirp;
    return 0;
}

static void _constfs_write_stat(const constfs_file_t *fp, struct stat *restrict buf)
{
    /* clear out the stat buffer first */
    memset(buf, 0, sizeof(*buf));
    buf->st_nlink = 1;
    buf->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
    buf->st_size = fp->size;
    buf->st_blocks = fp->size;
    buf->st_blksize = sizeof(uint8_t);
}