std1.0.0-beta-7 SafeC docs

Source fs/vfs.h

View in the repository

// SafeC Standard Library — Virtual Filesystem (VFS) Layer
// Minimal inode-based VFS with driver registration. Freestanding-safe.
//
// A filesystem driver is a context struct with methods; the traits below
// name them. Vfs::mount / mount_ro build the VfsOps table from those
// methods at compile time (one thunk per method, instantiated for the
// driver's type), so a driver writes no function table of its own:
//
//   FsRead      open, read, readdir                  FatCtx, Ext2Ctx
//   FsWrite     write, unlink, mkdir
//   FileSystem  FsRead + FsWrite                     TmpfsCtx
//
//   struct TmpfsCtx tmp; tmp.init();
//   vfs_init();
//   vfs_root.mount("/tmp", &tmp);       // read-write: needs a FileSystem
//   vfs_root.mount_ro("/sd", &fat);     // read-only: any FsRead
//
// Paths a driver sees are relative to its mountpoint ("/" for the root).
// A driver need not set VfsNode.fs_ctx: the VFS fills it in.
#pragma once
#include <std/erased.h>

#define VFS_MAX_MOUNTS  8
#define VFS_MAX_PATH    256
#define VFS_MAX_NAME    64

// Node types
#define VFS_TYPE_FILE   0
#define VFS_TYPE_DIR    1

// Open flags
#define VFS_O_READ      0x01
#define VFS_O_WRITE     0x02
#define VFS_O_CREATE    0x04
#define VFS_O_TRUNC     0x08

// Seek origins
#define VFS_SEEK_SET    0
#define VFS_SEEK_CUR    1
#define VFS_SEEK_END    2

namespace std {

struct VfsNode {
    char          name[VFS_MAX_NAME];
    int           type;             // VFS_TYPE_FILE / VFS_TYPE_DIR
    unsigned long size;             // file size in bytes (0 for dirs)
    unsigned long inode;            // driver-specific inode number
    ?&struct VfsMount fs_ctx;       // driver-internal: the owning VfsMount

    // Reads up to buf.len bytes of the file at `offset` into `buf`.
    // Returns bytes read.
    unsigned long read(unsigned long offset, []unsigned char buf);

    // Writes `buf` to the file at `offset`.  Returns bytes written.
    unsigned long write(unsigned long offset, []const unsigned char buf);

    // Calls cb(entry, user) for each child of this directory.
    // Returns the entry count, or -1 on error.
    generic<T> int readdir(fn void(&VfsNode, &T) cb, &T user);

    // Type-erased listing behind readdir() (see std/erased.h).
    int readdir_raw_(fn void(&VfsNode, ?&Erased) cb, ?&Erased user);
};

trait FsRead {
    // Looks up path (VFS_O_CREATE: creates a missing file) and fills
    // node_out's name, type, size and inode. 0 on success.
    int open(const &char path, int flags, &VfsNode node_out);
    // Up to buf.len bytes of file `inode` from offset; returns the count read.
    unsigned long read(unsigned long inode, unsigned long offset, []unsigned char buf);
    // cb(entry, user) per child of directory `inode`; the entry count, or
    // -1. Drivers declare it generic<T>: (fn void(&VfsNode, ?&T), ?&T).
    int readdir(unsigned long inode, fn void(&VfsNode, ?&Erased) cb, ?&Erased user);
}

trait FsWrite {
    unsigned long write(unsigned long inode, unsigned long offset, []const unsigned char buf);
    int unlink(const &char path);   // 0 on success
    int mkdir(const &char path);    // 0 on success
}

trait FileSystem : FsRead + FsWrite {}

// The mounted driver's entry points, type-erased so one Vfs holds drivers
// of different context types. Built by Vfs::mount / mount_ro; a null
// entry (a read-only mount's write/unlink/mkdir) makes that call fail.
// (See std/erased.h: ctx is the driver's &F.)
struct VfsOps {
    ?fn int(&Erased ctx, const &char path, int flags, &VfsNode node_out) open;
    ?fn int(&Erased ctx, const &char path) unlink;
    ?fn int(&Erased ctx, const &char path) mkdir;
    ?fn unsigned long(&Erased ctx, unsigned long inode, unsigned long off, []unsigned char buf) read;
    ?fn unsigned long(&Erased ctx, unsigned long inode, unsigned long off, []const unsigned char buf) write;
    ?fn int(&Erased ctx, unsigned long inode,
            fn void(&VfsNode, ?&Erased) cb, ?&Erased user) readdir;
};

struct VfsMount {
    char            mountpoint[VFS_MAX_NAME];
    struct VfsOps   ops;
    ?&Erased        ctx;      // the mounted driver (see VfsOps)
    int             active;
};

struct Vfs {
    struct VfsMount mounts[VFS_MAX_MOUNTS];
    int             mount_count;

    // Mount fs at `mountpoint` (e.g. "/" or "/tmp"). 0 on success, -1 if
    // every mount slot is taken. fs must outlive the mount.
    generic<F: FileSystem> int mount(const &char mountpoint, &F fs);
    // Read-only: write, unlink and mkdir through this mount fail.
    generic<F: FsRead>     int mount_ro(const &char mountpoint, &F fs);

    // Type-erased registration behind mount() / mount_ro().
    int  mount_raw_(const &char mountpoint, struct VfsOps ops, &Erased ctx);

    // Unmount filesystem at `mountpoint`.
    int  unmount(const &char mountpoint);

    // Open path; fills `node_out`.  Returns 0 on success.
    int  open(const &char path, int flags, &VfsNode node_out);

    // Remove a file.
    int  unlink(const &char path);

    // Create a directory.
    int  mkdir(const &char path);
};

// Global VFS instance (single-root for embedded use).
extern struct Vfs vfs_root;
void vfs_init();

// An all-null table (vfs.sc).
struct VfsOps vfs_ops_none_();

// One thunk per driver method: called through the type-erased VfsOps entry
// with the mount's ctx, which mount() took as an &F.
generic<F: FsRead>
int vfs_open_thunk_(&F fs, const &char path, int flags, &VfsNode node_out) {
    return fs.open(path, flags, node_out);
}
generic<F: FsRead>
unsigned long vfs_read_thunk_(&F fs, unsigned long inode, unsigned long off, []unsigned char buf) {
    return fs.read(inode, off, buf);
}
generic<F: FsRead>
int vfs_readdir_thunk_(&F fs, unsigned long inode, fn void(&VfsNode, ?&Erased) cb, ?&Erased user) {
    return fs.readdir(inode, cb, user);
}
generic<F: FileSystem>
unsigned long vfs_write_thunk_(&F fs, unsigned long inode, unsigned long off, []const unsigned char buf) {
    return fs.write(inode, off, buf);
}
generic<F: FileSystem>
int vfs_unlink_thunk_(&F fs, const &char path) { return fs.unlink(path); }
generic<F: FileSystem>
int vfs_mkdir_thunk_(&F fs, const &char path) { return fs.mkdir(path); }

// The read entries of F's table (fs only selects F).
generic<F: FsRead>
struct VfsOps vfs_ops_read_(&F fs) {
    struct VfsOps ops = vfs_ops_none_();
    fn int(&F, const &char, int, &VfsNode) o = vfs_open_thunk_;
    fn unsigned long(&F, unsigned long, unsigned long, []unsigned char) r = vfs_read_thunk_;
    fn int(&F, unsigned long, fn void(&VfsNode, ?&Erased), ?&Erased) d = vfs_readdir_thunk_;
    unsafe {
        ops.open    = (?fn int(&Erased, const &char, int, &VfsNode))o;
        ops.read    = (?fn unsigned long(&Erased, unsigned long, unsigned long, []unsigned char))r;
        ops.readdir = (?fn int(&Erased, unsigned long, fn void(&VfsNode, ?&Erased), ?&Erased))d;
    }
    return ops;
}

generic<F: FileSystem>
int Vfs::mount(const &char mountpoint, &F fs) {
    struct VfsOps ops = vfs_ops_read_(fs);
    fn unsigned long(&F, unsigned long, unsigned long, []const unsigned char) w = vfs_write_thunk_;
    fn int(&F, const &char) u = vfs_unlink_thunk_;
    fn int(&F, const &char) m = vfs_mkdir_thunk_;
    unsafe {
        ops.write  = (?fn unsigned long(&Erased, unsigned long, unsigned long, []const unsigned char))w;
        ops.unlink = (?fn int(&Erased, const &char))u;
        ops.mkdir  = (?fn int(&Erased, const &char))m;
        return self.mount_raw_(mountpoint, ops, (&Erased)fs);
    }
}

generic<F: FsRead>
int Vfs::mount_ro(const &char mountpoint, &F fs) {
    struct VfsOps ops = vfs_ops_read_(fs);
    unsafe { return self.mount_raw_(mountpoint, ops, (&Erased)fs); }
}

generic<T>
int VfsNode::readdir(fn void(&VfsNode, &T) cb, &T user) {
    unsafe { return self.readdir_raw_((fn void(&VfsNode, ?&Erased))cb, (?&Erased)user); }
}

} // namespace std