mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2025-09-24 09:03:32 +02:00
379 lines
11 KiB
C
379 lines
11 KiB
C
#include "../neonucleus.h"
|
|
|
|
// TODO: finish
|
|
|
|
// Data structures
|
|
|
|
typedef struct nn_vfnode {
|
|
struct nn_vfilesystem *fs;
|
|
char name[NN_MAX_PATH];
|
|
struct nn_vfnode *parent;
|
|
nn_bool_t isDirectory;
|
|
union {
|
|
// if directory
|
|
struct nn_vfnode **entries;
|
|
// if file
|
|
char *data;
|
|
};
|
|
nn_size_t len;
|
|
nn_size_t cap;
|
|
nn_size_t lastModified;
|
|
// this is used to block deleting
|
|
nn_refc handleCount;
|
|
} nn_vfnode;
|
|
|
|
typedef enum nn_vfmode {
|
|
NN_VFMODE_READ,
|
|
NN_VFMODE_WRITE,
|
|
NN_VFMODE_APPEND,
|
|
} nn_vfmode;
|
|
|
|
typedef struct nn_vfhandle {
|
|
nn_vfnode *node;
|
|
nn_intptr_t position;
|
|
nn_vfmode mode;
|
|
} nn_vfhandle;
|
|
|
|
typedef struct nn_vfilesystem {
|
|
nn_Context ctx;
|
|
nn_vfilesystemOptions opts;
|
|
double birthday;
|
|
nn_vfnode *root;
|
|
} nn_vfilesystem;
|
|
|
|
// virtual node helpers
|
|
|
|
nn_size_t nn_vf_now(nn_vfilesystem *fs) {
|
|
nn_Clock c = fs->ctx.clock;
|
|
double time = c.proc(c.userdata);
|
|
double elapsed = time - fs->birthday;
|
|
nn_size_t elapsedMS = elapsed * 1000;
|
|
return fs->opts.creationTime + elapsedMS;
|
|
}
|
|
|
|
nn_vfnode *nn_vf_allocFile(nn_vfilesystem *fs, const char *name) {
|
|
nn_Alloc *alloc = &fs->ctx.allocator;
|
|
nn_vfnode *node = nn_alloc(alloc, sizeof(nn_vfnode));
|
|
if(node == NULL) return NULL;
|
|
*node = (nn_vfnode) {
|
|
.fs = fs,
|
|
.lastModified = nn_vf_now(fs),
|
|
.parent = NULL,
|
|
.isDirectory = false,
|
|
.data = NULL,
|
|
.len = 0,
|
|
.cap = 0,
|
|
.handleCount = 0,
|
|
};
|
|
// we pray
|
|
nn_strcpy(node->name, name);
|
|
return node;
|
|
}
|
|
|
|
nn_vfnode *nn_vf_allocDirectory(nn_vfilesystem *fs, const char *name) {
|
|
nn_Alloc *alloc = &fs->ctx.allocator;
|
|
nn_vfnode *node = nn_alloc(alloc, sizeof(nn_vfnode));
|
|
if(node == NULL) return NULL;
|
|
nn_vfnode **buffer = nn_alloc(alloc, sizeof(nn_vfnode *));
|
|
if(buffer == NULL) {
|
|
nn_dealloc(alloc, node, sizeof(nn_vfnode));
|
|
return NULL;
|
|
}
|
|
*node = (nn_vfnode) {
|
|
.fs = fs,
|
|
.lastModified = nn_vf_now(fs),
|
|
.parent = NULL,
|
|
.isDirectory = false,
|
|
.entries = buffer,
|
|
.len = 0,
|
|
.cap = fs->opts.maxDirEntries,
|
|
.handleCount = 0,
|
|
};
|
|
// we pray
|
|
nn_strcpy(node->name, name);
|
|
return node;
|
|
}
|
|
|
|
void nn_vf_freeNode(nn_vfnode *node) {
|
|
nn_Alloc *alloc = &node->fs->ctx.allocator;
|
|
|
|
if(node->isDirectory) {
|
|
for(nn_size_t i = 0; i < node->len; i++) {
|
|
nn_vf_freeNode(node->entries[i]);
|
|
}
|
|
nn_dealloc(alloc, node->entries, sizeof(nn_vfnode *) * node->cap);
|
|
} else {
|
|
nn_dealloc(alloc, node->data, node->cap);
|
|
}
|
|
|
|
nn_dealloc(alloc, node, sizeof(nn_vfnode));
|
|
}
|
|
|
|
nn_size_t nn_vf_spaceUsedByNode(nn_vfnode *node) {
|
|
if(node->isDirectory) {
|
|
nn_size_t sum = 0;
|
|
for(nn_size_t i = 0; i < node->len; i++) {
|
|
sum = nn_vf_spaceUsedByNode(node->entries[i]);
|
|
}
|
|
return sum;
|
|
} else {
|
|
return node->len;
|
|
}
|
|
}
|
|
|
|
nn_vfnode *nn_vf_find(nn_vfnode *parent, const char *name) {
|
|
if(parent->isDirectory) return NULL;
|
|
for(nn_size_t i = 0; i < parent->len; i++) {
|
|
nn_vfnode *entry = parent->entries[i];
|
|
|
|
if(nn_strcmp(entry->name, name) == 0) {
|
|
return entry;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
nn_bool_t nn_vf_ensureFileCapacity(nn_vfnode *file, nn_size_t capacity) {
|
|
if(file->isDirectory) return false;
|
|
nn_Alloc *alloc = &file->fs->ctx.allocator;
|
|
|
|
if(file->cap >= capacity) return true; // already at that point
|
|
|
|
char *newData = nn_resize(alloc, file->data, file->cap, capacity);
|
|
if(newData == NULL) {
|
|
return false; // OOM
|
|
}
|
|
file->data = newData;
|
|
file->cap = capacity;
|
|
|
|
return true;
|
|
}
|
|
|
|
// this is used to compute exponential backoff
|
|
// TODO: add an option to select either a slower growth rate or
|
|
// linear backoff to reduce memory usage at the cost of speed
|
|
nn_size_t nn_vf_getIdealCapacity(nn_vfnode *file, nn_size_t spaceNeeded) {
|
|
nn_size_t cap = file->cap;
|
|
if(cap == 0) cap = 1;
|
|
while(cap < spaceNeeded) cap *= 2; // this would mean a file with 1,048,577 bytes takes up 2,097,152 bytes, potentially wasting 1,048,575 bytes
|
|
return cap;
|
|
}
|
|
|
|
void nn_vf_clampHandlePosition(nn_vfhandle *handle) {
|
|
nn_size_t len = handle->node->len;
|
|
if(handle->position < 0) handle->position = 0;
|
|
if(handle->position > len) handle->position = len;
|
|
}
|
|
|
|
nn_vfnode *nn_vf_resolvePathFromNode(nn_vfnode *node, const char *path) {
|
|
if(path[0] == 0) return node;
|
|
char firstDirectory[NN_MAX_PATH];
|
|
char subpath[NN_MAX_PATH];
|
|
if(nn_path_firstName(path, firstDirectory, subpath)) {
|
|
return nn_vf_find(node, firstDirectory);
|
|
}
|
|
|
|
nn_vfnode *dir = nn_vf_find(node, firstDirectory);
|
|
return nn_vf_resolvePathFromNode(dir, subpath);
|
|
}
|
|
|
|
nn_vfnode *nn_vf_resolvePath(nn_vfilesystem *fs, const char *path) {
|
|
return nn_vf_resolvePathFromNode(fs->root, path);
|
|
}
|
|
|
|
nn_size_t nn_vf_countTree(nn_vfnode *node) {
|
|
if(!node->isDirectory) return 1;
|
|
nn_size_t n = 1;
|
|
for(nn_size_t i = 0; i < node->len; i++) {
|
|
n += nn_vf_countTree(node->entries[i]);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// methods
|
|
|
|
void nn_vfs_deinit(nn_vfilesystem *fs) {
|
|
nn_Context ctx = fs->ctx;
|
|
|
|
nn_vf_freeNode(fs->root);
|
|
nn_dealloc(&ctx.allocator, fs, sizeof(nn_vfilesystem));
|
|
}
|
|
|
|
void nn_vfs_getLabel(nn_vfilesystem *fs, char *buf, nn_size_t *buflen) {
|
|
*buflen = fs->opts.labelLen;
|
|
nn_memcpy(buf, fs->opts.label, fs->opts.labelLen);
|
|
}
|
|
|
|
void nn_vfs_setLabel(nn_vfilesystem *fs, const char *buf, nn_size_t buflen) {
|
|
nn_memcpy(fs->opts.label, buf, buflen);
|
|
fs->opts.labelLen = buflen;
|
|
}
|
|
|
|
nn_size_t nn_vfs_spaceUsed(nn_vfilesystem *fs) {
|
|
return nn_vf_spaceUsedByNode(fs->root);
|
|
}
|
|
|
|
nn_bool_t nn_vfs_isReadOnly(nn_vfilesystem *fs) {
|
|
return fs->opts.isReadOnly;
|
|
}
|
|
|
|
nn_size_t nn_vfs_size(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
if(node == NULL) {
|
|
nn_error_write(err, "No such file");
|
|
return 0;
|
|
}
|
|
if(node->isDirectory) return 0;
|
|
return node->len;
|
|
}
|
|
|
|
nn_size_t nn_vfs_remove(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
if(node == NULL) {
|
|
nn_error_write(err, "No such file");
|
|
return 0;
|
|
}
|
|
nn_vfnode *parent = node->parent;
|
|
if(parent == NULL) {
|
|
// root, can't delete
|
|
nn_error_write(err, "Unable to delete root");
|
|
return 0;
|
|
}
|
|
nn_size_t removed = nn_vf_countTree(node);
|
|
// it is super easy to delete a tree
|
|
nn_size_t j = 0;
|
|
for(nn_size_t i = 0; i < parent->len; i++) {
|
|
if(parent->entries[i] != node) {
|
|
parent->entries[j] = parent->entries[i];
|
|
j++;
|
|
}
|
|
}
|
|
parent->len = j;
|
|
parent->lastModified = nn_vf_now(fs);
|
|
nn_vf_freeNode(node);
|
|
return removed;
|
|
}
|
|
|
|
nn_size_t nn_vfs_lastModified(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
if(node == NULL) {
|
|
nn_error_write(err, "No such file");
|
|
return 0;
|
|
}
|
|
return node->lastModified;
|
|
}
|
|
|
|
nn_size_t nn_vfs_rename(nn_vfilesystem *fs, const char *from, const char *to, nn_errorbuf_t err) {
|
|
// TODO: implement rename
|
|
nn_error_write(err, "Unsupported operation");
|
|
return 0;
|
|
}
|
|
|
|
nn_bool_t nn_vfs_exists(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
return node != NULL;
|
|
}
|
|
|
|
nn_bool_t nn_vfs_isDirectory(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
if(node == NULL) {
|
|
nn_error_write(err, "No such file");
|
|
return 0;
|
|
}
|
|
return node->isDirectory;
|
|
}
|
|
|
|
nn_bool_t nn_vfs_makeDirectory(nn_vfilesystem *fs, const char *path, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
if(node != NULL) {
|
|
nn_error_write(err, "File already exists");
|
|
return 0;
|
|
}
|
|
char name[NN_MAX_PATH];
|
|
char parentPath[NN_MAX_PATH];
|
|
nn_path_lastName(path, name, parentPath);
|
|
nn_vfnode *parent = nn_vf_resolvePath(fs, path);
|
|
return false;
|
|
}
|
|
|
|
char **nn_vfs_list(nn_Alloc *alloc, nn_vfilesystem *fs, const char *path, nn_size_t *len, nn_errorbuf_t err) {
|
|
nn_vfnode *node = nn_vf_resolvePath(fs, path);
|
|
if(node == NULL) {
|
|
nn_error_write(err, "No such file");
|
|
return NULL;
|
|
}
|
|
if(!node->isDirectory) {
|
|
nn_error_write(err, "Not a directory");
|
|
return NULL;
|
|
}
|
|
nn_size_t count = node->len;
|
|
*len = count;
|
|
char **buf = nn_alloc(alloc, sizeof(char *) * count);
|
|
if(buf == NULL) {
|
|
nn_error_write(err, "Out of memory");
|
|
return NULL;
|
|
}
|
|
for(nn_size_t i = 0; i < count; i++) {
|
|
nn_vfnode *entry = node->entries[i];
|
|
char *s = NULL;
|
|
if(entry->isDirectory) {
|
|
nn_size_t l = nn_strlen(entry->name);
|
|
s = nn_alloc(alloc, l + 2);
|
|
if(s != NULL) {
|
|
nn_memcpy(s, entry->name, l);
|
|
entry->name[l] = '/';
|
|
entry->name[l+1] = 0;
|
|
}
|
|
} else {
|
|
s = nn_strdup(alloc, entry->name);
|
|
}
|
|
if(s == NULL) {
|
|
for(nn_size_t j = 0; j < i; j++) {
|
|
nn_deallocStr(alloc, buf[j]);
|
|
}
|
|
nn_error_write(err, "Out of memory");
|
|
return NULL;
|
|
}
|
|
buf[i] = s;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
void *nn_vfs_open(nn_vfilesystem *fs, const char *path, const char *mode, nn_errorbuf_t err) {
|
|
// TODO: complete
|
|
char m = mode[0];
|
|
nn_vfmode fmode = NN_VFMODE_READ;
|
|
if(m == 'w') fmode = NN_VFMODE_WRITE;
|
|
if(m == 'a') fmode = NN_VFMODE_APPEND;
|
|
return NULL;
|
|
}
|
|
|
|
// main funciton
|
|
|
|
nn_filesystem *nn_volatileFilesystem(nn_Context *context, nn_vfilesystemOptions opts, nn_filesystemControl control) {
|
|
// TODO: handle OOM
|
|
nn_vfilesystem *fs = nn_alloc(&context->allocator, sizeof(nn_vfilesystem));
|
|
nn_Clock c = fs->ctx.clock;
|
|
double time = c.proc(c.userdata);
|
|
fs->ctx = *context;
|
|
fs->birthday = time;
|
|
fs->opts = opts;
|
|
fs->root = nn_vf_allocDirectory(fs, "/");
|
|
nn_filesystemTable table = {
|
|
.userdata = fs,
|
|
.deinit = (void *)nn_vfs_deinit,
|
|
.getLabel = (void *)nn_vfs_getLabel,
|
|
.setLabel = (void *)nn_vfs_setLabel,
|
|
.spaceUsed = (void *)nn_vfs_spaceUsed,
|
|
.spaceTotal = opts.capacity,
|
|
.isReadOnly = (void *)nn_vfs_isReadOnly,
|
|
.size = (void *)nn_vfs_size,
|
|
.remove = (void *)nn_vfs_remove,
|
|
.exists = (void *)nn_vfs_exists,
|
|
.isDirectory = (void *)nn_vfs_isDirectory,
|
|
.makeDirectory = (void *)nn_vfs_makeDirectory,
|
|
.list = (void *)nn_vfs_list,
|
|
};
|
|
return nn_newFilesystem(context, table, control);
|
|
}
|