ish/fs/tmp.c
2020-03-01 15:07:36 -08:00

525 lines
14 KiB
C

#include <sys/stat.h>
#include <string.h>
#include "kernel/task.h"
#include "kernel/errno.h"
#include "kernel/fs.h"
#include "fs/path.h"
#include "util/refcount.h"
#include "debug.h"
// ========================
// ======== INODES ========
// ========================
struct tmp_inode {
struct refcount refcount;
lock_t lock;
struct statbuf stat;
union {
void *file_data;
//char *symlink_data;
};
};
static struct tmp_inode *tmp_inode_new(mode_t_ mode) {
struct tmp_inode *node = malloc(sizeof(struct tmp_inode));
if (node == NULL)
return NULL;
refcount_init(node);
lock_init(&node->lock);
node->stat = (struct statbuf) {};
static _Atomic ino_t next_inode = 1;
node->stat.inode = next_inode++;
node->stat.mode = mode;
node->stat.uid = current->euid;
node->stat.gid = current->egid;
if (S_ISREG(mode)) {
node->file_data = malloc(0);
if (node->file_data == NULL) {
free(node);
return NULL;
}
}
return node;
}
DEFINE_REFCOUNT_STATIC(tmp_inode)
static void tmp_inode_cleanup(struct tmp_inode *inode) {
if (S_ISREG(inode->stat.mode)) {
free(inode->file_data);
}
free(inode);
}
// ===================================
// ======== DIRECTORY ENTRIES ========
// ===================================
struct tmp_dirent {
char name[MAX_NAME + 1];
struct tmp_inode *inode;
unsigned long index;
struct tmp_dirent *parent;
struct list children;
unsigned long next_index;
struct refcount refcount;
lock_t lock;
struct list dir;
};
DEFINE_REFCOUNT_STATIC(tmp_dirent)
static void tmp_dirent_cleanup(struct tmp_dirent *dirent) {
list_remove(&dirent->dir); // TODO locking thinking emoji
tmp_inode_release(dirent->inode);
free(dirent);
}
static void tmp_dirent_init(struct tmp_dirent *dirent) {
refcount_init(dirent);
list_init(&dirent->children);
dirent->next_index = 0;
lock_init(&dirent->lock);
}
// Frees the child inode on failure, so you don't need to! But be careful you don't free it yourself.
// In other words: Takes ownership of `child`
static int tmpfs_dir_link(struct tmp_dirent *dir, const char *name, struct tmp_inode *child, struct tmp_dirent **dirent_out) {
if (!S_ISDIR(dir->inode->stat.mode)) {
tmp_inode_release(child);
return _ENOTDIR;
}
struct tmp_dirent *new_dirent = malloc(sizeof(struct tmp_dirent));
if (new_dirent == NULL) {
tmp_inode_release(child);
return _ENOMEM;
}
tmp_dirent_init(new_dirent);
strcpy(new_dirent->name, name);
new_dirent->inode = tmp_inode_retain(child);
new_dirent->index = dir->next_index++;
new_dirent->parent = tmp_dirent_retain(dir);
list_add_tail(&dir->children, &new_dirent->dir);
if (dirent_out)
*dirent_out = tmp_dirent_retain(new_dirent);
return 0;
}
static void tmpfs_fd_seekdir(struct fd *fd, struct tmp_dirent *dirent) {
if (dirent != NULL)
tmp_dirent_retain(dirent);
if (fd->tmpfs.dir_pos != NULL)
tmp_dirent_release(fd->tmpfs.dir_pos);
fd->tmpfs.dir_pos = dirent;
}
static struct tmp_dirent *tmpfs_dir_lookup(struct tmp_dirent *dir, const char *name) {
if (!S_ISDIR(dir->inode->stat.mode))
return ERR_PTR(_ENOTDIR);
struct tmp_dirent *dirent = NULL;
struct tmp_dirent *d;
list_for_each_entry(&dir->children, d, dir) {
if (d->inode == NULL)
continue;
if (strcmp(d->name, name) == 0) {
dirent = d;
break;
}
}
if (dirent == NULL)
return ERR_PTR(_ENOENT);
return tmp_dirent_retain(dirent);
}
// TODO: should this function even exist? can't tmpfs_dir_link check for existence?
static int tmpfs_dir_lookup_existence(struct tmp_dirent *dir, const char *name) {
struct tmp_dirent *dirent = tmpfs_dir_lookup(dir, name);
if (dirent == ERR_PTR(_ENOENT))
return 0;
if (IS_ERR(dirent))
return PTR_ERR(dirent);
tmp_dirent_release(dirent);
return _EEXIST;
}
static struct tmp_dirent *__tmpfs_lookup(struct mount *mount, const char *path, bool parent, const char **filename_out) {
struct tmp_dirent *root = mount->data;
struct tmp_dirent *dirent = tmp_dirent_retain(root); // strong reference
char component[MAX_NAME + 1] = {};
int err = 0;
while (path_next_component(&path, component, &err)) {
if (parent && *path == '\0')
break;
lock(&dirent->lock);
struct tmp_dirent *child = tmpfs_dir_lookup(dirent, component);
unlock(&dirent->lock);
tmp_dirent_release(dirent);
if (IS_ERR(child))
return child;
dirent = child;
}
if (parent && filename_out)
*filename_out = path - strlen(component);
if (err < 0)
return ERR_PTR(err);
return dirent;
}
static struct tmp_dirent *tmpfs_lookup(struct mount *mount, const char *path) {
return __tmpfs_lookup(mount, path, false, NULL);
}
static struct tmp_dirent *tmpfs_lookup_parent(struct mount *mount, const char *path, const char **filename_out) {
if (strcmp(path, "/") == 0)
return NULL;
return __tmpfs_lookup(mount, path, true, filename_out);
}
static int tmpfs_file_resize(struct tmp_inode *file, size_t size) {
assert(S_ISREG(file->stat.mode));
size_t old_size = file->stat.size;
void *new_data = realloc(file->file_data, size);
if (new_data == NULL)
return _ENOMEM;
file->file_data = new_data;
file->stat.size = size;
memset((char *) file->file_data + old_size, 0, file->stat.size - old_size);
return 0;
}
// ========================
// ======== FS OPS ========
// ========================
extern const struct fd_ops tmpfs_fdops;
static int tmpfs_mount(struct mount *mount) {
struct tmp_inode *root_inode = tmp_inode_new(S_IFDIR | 0777);
if (root_inode == NULL)
return _ENOMEM;
struct tmp_dirent *root = malloc(sizeof(struct tmp_dirent));
if (root == NULL) {
free(root_inode);
return _ENOMEM;
}
tmp_dirent_init(root);
strcpy(root->name, "");
root->inode = root_inode;
root->parent = NULL;
mount->data = root;
return 0;
}
#if 0
// This is the only place where a tmpfs directory tree is recursively freed.
static void tmpfs_unmount_tree(struct tmp_inode *tree) {
assert(refcount_get(tree) == 1); // otherwise mount_remove should have returned EBUSY
if (S_ISDIR(tree->stat.mode)) {
struct tmp_dirent *dirent, *tmp;
list_for_each_entry_safe(&tree->dir.entries, dirent, tmp, dir) {
if (dirent->inode != NULL)
tmpfs_unmount_tree(dirent->inode);
tmp_dirent_release(dirent);
}
}
tmp_inode_release(tree);
}
#endif
static int tmpfs_umount(struct mount *mount) {
// big fat fuckin TODO
// struct tmp_inode *root = mount->data;
// tmpfs_unmount_tree(root);
TODO("tmpfs umount");
return 0;
}
static struct fd *tmpfs_open(struct mount *mount, const char *path, int flags, int mode) {
struct tmp_dirent *dirent;
if (flags & O_CREAT_) {
// FIXME: will create a file when given a path that ends with a slash
const char *filename;
struct tmp_dirent *parent = tmpfs_lookup_parent(mount, path, &filename);
if (IS_ERR(parent))
return ERR_PTR(PTR_ERR(parent));
lock(&parent->lock);
int err = 0;
dirent = tmpfs_dir_lookup(parent, filename);
if (flags & O_EXCL_ && !IS_ERR(dirent)) {
err = _EEXIST;
goto out_creat;
}
if (dirent == ERR_PTR(_ENOENT)) {
struct tmp_inode *inode = tmp_inode_new(S_IFREG | mode);
if (inode == NULL) {
err = _ENOMEM;
goto out_creat;
}
err = tmpfs_dir_link(parent, filename, inode, &dirent);
tmp_inode_release(inode);
if (err < 0) {
goto out_creat;
}
}
out_creat:
if (err < 0) {
tmp_dirent_release(dirent);
dirent = ERR_PTR(err);
}
unlock(&parent->lock);
tmp_dirent_release(parent);
} else {
dirent = tmpfs_lookup(mount, path);
}
if (IS_ERR(dirent))
return ERR_PTR(PTR_ERR(dirent));
struct fd *fd = fd_create(&tmpfs_fdops);
if (fd == NULL) {
tmp_dirent_release(dirent);
return ERR_PTR(_ENOMEM);
}
fd->tmpfs.dirent = dirent;
fd->tmpfs.dir_pos = NULL;
lock(&dirent->lock);
if (!list_empty(&dirent->children)) {
tmpfs_fd_seekdir(fd, list_first_entry(&dirent->children, struct tmp_dirent, dir));
}
unlock(&dirent->lock);
return fd;
}
static int tmpfs_stat(struct mount *mount, const char *path, struct statbuf *stat) {
struct tmp_dirent *dirent = tmpfs_lookup(mount, path);
if (IS_ERR(dirent))
return PTR_ERR(dirent);
struct tmp_inode *inode = dirent->inode;
lock(&inode->lock);
*stat = dirent->inode->stat;
unlock(&inode->lock);
tmp_dirent_release(dirent);
return 0;
}
static int tmpfs_close(struct fd *fd) {
// shouldn't need locking as this is the last reference to the fd
tmp_dirent_release(fd->tmpfs.dirent);
fd->tmpfs.dirent = NULL;
return 0;
}
static int tmpfs_mkdir(struct mount *mount, const char *path, mode_t_ mode) {
const char *filename;
struct tmp_dirent *parent = tmpfs_lookup_parent(mount, path, &filename);
if (IS_ERR(parent))
return PTR_ERR(parent);
lock(&parent->lock);
int err = tmpfs_dir_lookup_existence(parent, filename);
if (err < 0)
goto out;
struct tmp_inode *inode = tmp_inode_new(S_IFDIR | mode);
err = _ENOMEM;
if (inode == NULL)
goto out;
err = tmpfs_dir_link(parent, filename, inode, NULL);
out:
unlock(&parent->lock);
tmp_dirent_release(parent);
return err;
}
// ========================
// ======== FD OPS ========
// ========================
static struct tmp_inode *tmpfs_fd_inode(struct fd *fd) {
return fd->tmpfs.dirent->inode;
}
static int tmpfs_getpath(struct fd *fd, char *buf) {
struct tmp_dirent *dirent = fd->tmpfs.dirent;
struct tmp_dirent *root_dirent = fd->mount->data;
char *p = buf + MAX_PATH;
while (dirent != root_dirent) {
size_t name_len = strlen(dirent->name);
p -= name_len + 1;
if (p < buf)
return _ENAMETOOLONG;
p[0] = '/';
memcpy(&p[1], dirent->name, name_len);
}
memmove(buf, p, strlen(p) + 1);
return 0;
}
static int tmpfs_fstat(struct fd *fd, struct statbuf *stat) {
struct tmp_inode *inode = tmpfs_fd_inode(fd);
lock(&inode->lock);
*stat = inode->stat;
unlock(&inode->lock);
return 0;
}
static ssize_t tmpfs_read(struct fd *fd, void *buf, size_t bufsize) {
ssize_t res;
struct tmp_inode *inode = tmpfs_fd_inode(fd);
lock(&inode->lock);
res = _EISDIR;
if (S_ISDIR(inode->stat.mode))
goto out;
assert(S_ISREG(inode->stat.mode));
if (bufsize > inode->stat.size - fd->offset) {
bufsize = inode->stat.size - fd->offset;
if (fd->offset >= inode->stat.size)
bufsize = 0;
}
memcpy(buf, inode->file_data + fd->offset, bufsize);
fd->offset += bufsize;
res = bufsize;
out:
unlock(&inode->lock);
return res;
}
static ssize_t tmpfs_write(struct fd *fd, const void *buf, size_t bufsize) {
ssize_t res;
struct tmp_inode *inode = tmpfs_fd_inode(fd);
lock(&inode->lock);
res = _EISDIR;
if (S_ISDIR(inode->stat.mode))
goto out;
assert(S_ISREG(inode->stat.mode));
if (inode->stat.size < fd->offset + bufsize) {
res = tmpfs_file_resize(inode, fd->offset + bufsize);
if (res < 0)
goto out;
}
memcpy(inode->file_data + fd->offset, buf, bufsize);
fd->offset += bufsize;
res = bufsize;
out:
unlock(&inode->lock);
return res;
}
static off_t_ tmpfs_lseek(struct fd *fd, off_t_ off, int whence) {
qword_t size;
if (whence == LSEEK_END) {
struct tmp_inode *inode = tmpfs_fd_inode(fd);
lock(&inode->lock);
size = inode->stat.size;
}
// TODO: dedupe code with procfs
off_t_ old_off = fd->offset;
if (whence == LSEEK_SET)
fd->offset = off;
else if (whence == LSEEK_CUR)
fd->offset += off;
else if (whence == LSEEK_END)
fd->offset = size + off;
else
return _EINVAL;
if (fd->offset < 0) {
fd->offset = old_off;
return _EINVAL;
}
return fd->offset;
}
static int tmpfs_readdir(struct fd *fd, struct dir_entry *entry) {
struct tmp_dirent *parent = fd->tmpfs.dirent;
int res = _ENOTDIR;
if (!S_ISDIR(parent->inode->stat.mode))
goto out;
lock(&fd->lock);
lock(&parent->lock);
struct tmp_dirent *dirent = fd->tmpfs.dir_pos;
if (dirent == NULL) {
res = 0;
goto out;
}
struct tmp_dirent *next_dirent = list_next_entry(dirent, dir);
if (&next_dirent->dir == &parent->children) // end of list
next_dirent = NULL;
tmpfs_fd_seekdir(fd, next_dirent);
entry->inode = dirent->inode->stat.inode;
strcpy(entry->name, dirent->name);
res = 1;
out:
unlock(&parent->lock);
unlock(&fd->lock);
return res;
}
static unsigned long tmpfs_telldir(struct fd *fd) {
if (fd->tmpfs.dir_pos == NULL)
return (unsigned long) -1;
return fd->tmpfs.dir_pos->index;
}
static void tmpfs_seekdir(struct fd *fd, unsigned long ptr) {
struct tmp_dirent *dir = fd->tmpfs.dirent;
lock(&dir->lock);
assert(S_ISDIR(dir->inode->stat.mode));
struct tmp_dirent *child;
list_for_each_entry(&dir->children, child, dir) {
if (child->index >= ptr)
break;
}
if (&child->dir == &dir->children)
child = NULL;
tmpfs_fd_seekdir(fd, child);
unlock(&dir->lock);
}
const struct fs_ops tmpfs = {
.name = "tmpfs", .magic = 0x01021994,
.mount = tmpfs_mount,
.umount = tmpfs_umount,
.open = tmpfs_open,
.close = tmpfs_close,
.stat = tmpfs_stat,
.fstat = tmpfs_fstat,
.getpath = tmpfs_getpath,
.mkdir = tmpfs_mkdir,
};
const struct fd_ops tmpfs_fdops = {
.read = tmpfs_read,
.write = tmpfs_write,
.lseek = tmpfs_lseek,
.readdir = tmpfs_readdir,
.telldir = tmpfs_telldir,
.seekdir = tmpfs_seekdir,
};