#include "tmpfs.h"
#include "errno.h"
#include "heap.h"
#include "utils.h"
struct tmpfs_node {
fs_node_t vfs;
struct tmpfs_node* parent;
struct tmpfs_node* first_child;
struct tmpfs_node* next_sibling;
uint8_t* data;
uint32_t cap;
};
static uint32_t g_tmpfs_next_inode = 1;
static struct fs_node* tmpfs_finddir_impl(struct fs_node* node, const char* name);
static int tmpfs_readdir_impl(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len);
static uint32_t tmpfs_read_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer);
static uint32_t tmpfs_write_impl(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer);
static int tmpfs_mkdir_impl(struct fs_node* dir, const char* name);
static int tmpfs_unlink_impl(struct fs_node* dir, const char* name);
static int tmpfs_rmdir_impl(struct fs_node* dir, const char* name);
static int tmpfs_create_impl(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out);
static const struct file_operations tmpfs_file_ops = {
.read = tmpfs_read_impl,
.write = tmpfs_write_impl,
};
static const struct file_operations tmpfs_dir_ops = {0};
static const struct inode_operations tmpfs_dir_iops = {
.lookup = tmpfs_finddir_impl,
.readdir = tmpfs_readdir_impl,
.mkdir = tmpfs_mkdir_impl,
.unlink = tmpfs_unlink_impl,
.rmdir = tmpfs_rmdir_impl,
.create = tmpfs_create_impl,
};
static struct tmpfs_node* tmpfs_node_alloc(const char* name, uint32_t flags) {
struct tmpfs_node* n = (struct tmpfs_node*)kmalloc(sizeof(*n));
if (!n) return NULL;
memset(n, 0, sizeof(*n));
if (name) {
strcpy(n->vfs.name, name);
} else {
n->vfs.name[0] = 0;
}
n->vfs.flags = flags;
n->vfs.inode = g_tmpfs_next_inode++;
n->vfs.length = 0;
return n;
}
static struct tmpfs_node* tmpfs_child_find(struct tmpfs_node* dir, const char* name) {
if (!dir || !name) return NULL;
struct tmpfs_node* c = dir->first_child;
while (c) {
if (strcmp(c->vfs.name, name) == 0) return c;
c = c->next_sibling;
}
return NULL;
}
static void tmpfs_child_add(struct tmpfs_node* dir, struct tmpfs_node* child) {
child->parent = dir;
child->next_sibling = dir->first_child;
dir->first_child = child;
}
static struct tmpfs_node* tmpfs_child_ensure_dir(struct tmpfs_node* dir, const char* name) {
if (!dir || !name || name[0] == 0) return NULL;
struct tmpfs_node* existing = tmpfs_child_find(dir, name);
if (existing) {
if (existing->vfs.flags != FS_DIRECTORY) return NULL;
return existing;
}
struct tmpfs_node* nd = tmpfs_node_alloc(name, FS_DIRECTORY);
if (!nd) return NULL;
nd->vfs.f_ops = &tmpfs_dir_ops;
nd->vfs.i_ops = &tmpfs_dir_iops;
tmpfs_child_add(dir, nd);
return nd;
}
static int tmpfs_split_next(const char** p_inout, char* out, size_t out_sz) {
if (!p_inout || !*p_inout || !out || out_sz == 0) return 0;
const char* p = *p_inout;
while (*p == '/') p++;
if (*p == 0) {
*p_inout = p;
out[0] = 0;
return 0;
}
size_t i = 0;
while (*p != 0 && *p != '/') {
if (i + 1 < out_sz) {
out[i++] = *p;
}
p++;
}
out[i] = 0;
while (*p == '/') p++;
*p_inout = p;
return out[0] != 0;
}
static uint32_t tmpfs_read_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
if (!node || !buffer) return 0;
if (node->flags != FS_FILE) return 0;
struct tmpfs_node* tn = (struct tmpfs_node*)node;
if (offset > tn->vfs.length) return 0;
if (offset + size > tn->vfs.length) size = tn->vfs.length - offset;
if (size == 0) return 0;
memcpy(buffer, tn->data + offset, size);
return size;
}
static uint32_t tmpfs_write_impl(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
if (!node || !buffer) return 0;
if (node->flags != FS_FILE) return 0;
struct tmpfs_node* tn = (struct tmpfs_node*)node;
uint64_t end = (uint64_t)offset + (uint64_t)size;
if (end > 0xFFFFFFFFULL) return 0;
if ((uint32_t)end > tn->cap) {
uint32_t new_cap = tn->cap ? tn->cap : 64;
while (new_cap < (uint32_t)end) {
new_cap *= 2;
}
uint8_t* new_data = (uint8_t*)kmalloc(new_cap);
if (!new_data) return 0;
memset(new_data, 0, new_cap);
if (tn->data && tn->vfs.length) {
memcpy(new_data, tn->data, tn->vfs.length);
}
if (tn->data) kfree(tn->data);
tn->data = new_data;
tn->cap = new_cap;
}
memcpy(tn->data + offset, buffer, size);
if ((uint32_t)end > tn->vfs.length) tn->vfs.length = (uint32_t)end;
return size;
}
static struct fs_node* tmpfs_finddir_impl(struct fs_node* node, const char* name) {
if (!node || !name) return 0;
if (node->flags != FS_DIRECTORY) return 0;
struct tmpfs_node* dir = (struct tmpfs_node*)node;
struct tmpfs_node* child = tmpfs_child_find(dir, name);
if (!child) return 0;
return &child->vfs;
}
static int tmpfs_readdir_impl(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len) {
if (!node || !inout_index || !buf) return -1;
if (node->flags != FS_DIRECTORY) return -1;
if (buf_len < sizeof(struct vfs_dirent)) return -1;
struct tmpfs_node* dir = (struct tmpfs_node*)node;
uint32_t idx = *inout_index;
uint32_t cap = buf_len / (uint32_t)sizeof(struct vfs_dirent);
struct vfs_dirent* ents = (struct vfs_dirent*)buf;
uint32_t written = 0;
while (written < cap) {
struct vfs_dirent e;
memset(&e, 0, sizeof(e));
if (idx == 0) {
e.d_ino = node->inode;
e.d_type = FS_DIRECTORY;
strcpy(e.d_name, ".");
} else if (idx == 1) {
e.d_ino = dir->parent ? dir->parent->vfs.inode : node->inode;
e.d_type = FS_DIRECTORY;
strcpy(e.d_name, "..");
} else {
uint32_t skip = idx - 2;
struct tmpfs_node* c = dir->first_child;
while (c && skip > 0) { c = c->next_sibling; skip--; }
if (!c) break;
e.d_ino = c->vfs.inode;
e.d_type = (uint8_t)c->vfs.flags;
strcpy(e.d_name, c->vfs.name);
}
e.d_reclen = (uint16_t)sizeof(e);
ents[written] = e;
written++;
idx++;
}
*inout_index = idx;
return (int)(written * (uint32_t)sizeof(struct vfs_dirent));
}
static int tmpfs_mkdir_impl(struct fs_node* dir, const char* name) {
if (!dir || !name || dir->flags != FS_DIRECTORY) return -EINVAL;
struct tmpfs_node* d = (struct tmpfs_node*)dir;
if (tmpfs_child_find(d, name)) return -EEXIST;
struct tmpfs_node* nd = tmpfs_node_alloc(name, FS_DIRECTORY);
if (!nd) return -ENOMEM;
nd->vfs.f_ops = &tmpfs_dir_ops;
nd->vfs.i_ops = &tmpfs_dir_iops;
tmpfs_child_add(d, nd);
return 0;
}
static int tmpfs_unlink_impl(struct fs_node* dir, const char* name) {
if (!dir || !name || dir->flags != FS_DIRECTORY) return -EINVAL;
struct tmpfs_node* d = (struct tmpfs_node*)dir;
struct tmpfs_node* prev = NULL;
struct tmpfs_node* c = d->first_child;
while (c) {
if (strcmp(c->vfs.name, name) == 0) break;
prev = c;
c = c->next_sibling;
}
if (!c) return -ENOENT;
if (c->vfs.flags == FS_DIRECTORY) return -EISDIR;
if (prev) prev->next_sibling = c->next_sibling;
else d->first_child = c->next_sibling;
if (c->data) kfree(c->data);
kfree(c);
return 0;
}
static int tmpfs_rmdir_impl(struct fs_node* dir, const char* name) {
if (!dir || !name || dir->flags != FS_DIRECTORY) return -EINVAL;
struct tmpfs_node* d = (struct tmpfs_node*)dir;
struct tmpfs_node* prev = NULL;
struct tmpfs_node* c = d->first_child;
while (c) {
if (strcmp(c->vfs.name, name) == 0) break;
prev = c;
c = c->next_sibling;
}
if (!c) return -ENOENT;
if (c->vfs.flags != FS_DIRECTORY) return -ENOTDIR;
if (c->first_child) return -ENOTEMPTY;
if (prev) prev->next_sibling = c->next_sibling;
else d->first_child = c->next_sibling;
kfree(c);
return 0;
}
static int tmpfs_create_impl(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out) {
if (!dir || !name || !out || dir->flags != FS_DIRECTORY) return -EINVAL;
struct tmpfs_node* d = (struct tmpfs_node*)dir;
struct tmpfs_node* existing = tmpfs_child_find(d, name);
if (existing) {
*out = &existing->vfs;
return 0;
}
if (!(flags & 0x40U)) return -ENOENT; /* O_CREAT */
struct tmpfs_node* f = tmpfs_node_alloc(name, FS_FILE);
if (!f) return -ENOMEM;
f->vfs.f_ops = &tmpfs_file_ops;
tmpfs_child_add(d, f);
*out = &f->vfs;
return 0;
}
fs_node_t* tmpfs_create_root(void) {
struct tmpfs_node* root = tmpfs_node_alloc("", FS_DIRECTORY);
if (!root) return NULL;
root->vfs.f_ops = &tmpfs_dir_ops;
root->vfs.i_ops = &tmpfs_dir_iops;
return &root->vfs;
}
int tmpfs_add_file(fs_node_t* root_dir, const char* name, const uint8_t* data, uint32_t len) {
if (!root_dir || root_dir->flags != FS_DIRECTORY) return -ENOTDIR;
if (!name || name[0] == 0) return -EINVAL;
struct tmpfs_node* dir = (struct tmpfs_node*)root_dir;
if (tmpfs_child_find(dir, name)) return -EEXIST;
struct tmpfs_node* f = tmpfs_node_alloc(name, FS_FILE);
if (!f) return -ENOMEM;
f->vfs.f_ops = &tmpfs_file_ops;
if (len) {
f->data = (uint8_t*)kmalloc(len);
if (!f->data) {
kfree(f);
return -ENOMEM;
}
memcpy(f->data, data, len);
f->cap = len;
f->vfs.length = len;
}
tmpfs_child_add(dir, f);
return 0;
}
int tmpfs_mkdir_p(fs_node_t* root_dir, const char* path) {
if (!root_dir || root_dir->flags != FS_DIRECTORY) return -ENOTDIR;
if (!path) return -EINVAL;
struct tmpfs_node* cur = (struct tmpfs_node*)root_dir;
const char* p = path;
char part[128];
while (tmpfs_split_next(&p, part, sizeof(part))) {
struct tmpfs_node* next = tmpfs_child_ensure_dir(cur, part);
if (!next) return -ENOMEM;
cur = next;
}
return 0;
}
fs_node_t* tmpfs_create_file(fs_node_t* root_dir, const char* path, const uint8_t* data, uint32_t len) {
if (!root_dir || root_dir->flags != FS_DIRECTORY) return NULL;
if (!path) return NULL;
struct tmpfs_node* cur = (struct tmpfs_node*)root_dir;
const char* p = path;
char part[128];
char leaf[128];
leaf[0] = 0;
while (tmpfs_split_next(&p, part, sizeof(part))) {
if (*p == 0) {
strcpy(leaf, part);
break;
}
struct tmpfs_node* next = tmpfs_child_ensure_dir(cur, part);
if (!next) return NULL;
cur = next;
}
if (leaf[0] == 0) return NULL;
struct tmpfs_node* existing = tmpfs_child_find(cur, leaf);
if (existing) {
if (existing->vfs.flags != FS_FILE) return NULL;
if (len && data) {
uint8_t* buf = (uint8_t*)kmalloc(len);
if (!buf) return NULL;
memcpy(buf, data, len);
(void)tmpfs_write_impl(&existing->vfs, 0, len, buf);
kfree(buf);
}
return &existing->vfs;
}
struct tmpfs_node* f = tmpfs_node_alloc(leaf, FS_FILE);
if (!f) return NULL;
f->vfs.f_ops = &tmpfs_file_ops;
if (len && data) {
f->data = (uint8_t*)kmalloc(len);
if (!f->data) {
kfree(f);
return NULL;
}
memcpy(f->data, data, len);
f->cap = len;
f->vfs.length = len;
}
tmpfs_child_add(cur, f);
return &f->vfs;
}
int tmpfs_create_symlink(fs_node_t* root_dir, const char* link_path, const char* target) {
if (!root_dir || root_dir->flags != FS_DIRECTORY) return -ENOTDIR;
if (!link_path || !target) return -EINVAL;
struct tmpfs_node* cur = (struct tmpfs_node*)root_dir;
const char* p = link_path;
char part[128];
char leaf[128];
leaf[0] = 0;
while (tmpfs_split_next(&p, part, sizeof(part))) {
if (*p == 0) {
strcpy(leaf, part);
break;
}
struct tmpfs_node* next = tmpfs_child_ensure_dir(cur, part);
if (!next) return -ENOMEM;
cur = next;
}
if (leaf[0] == 0) return -EINVAL;
if (tmpfs_child_find(cur, leaf)) return -EEXIST;
struct tmpfs_node* ln = tmpfs_node_alloc(leaf, FS_SYMLINK);
if (!ln) return -ENOMEM;
strcpy(ln->vfs.symlink_target, target);
ln->vfs.length = (uint32_t)strlen(target);
/* symlinks have no f_ops */
tmpfs_child_add(cur, ln);
return 0;
}