Files
kernel/fs/vfs.c
T
2020-07-11 01:20:13 +03:00

565 lines
13 KiB
C

#include "arch/amd64/cpu.h"
#include "user/fcntl.h"
#include "user/errno.h"
#include "sys/block/blk.h"
#include "fs/node.h"
#include "sys/heap.h"
#include "sys/string.h"
#include "sys/assert.h"
#include "fs/vfs.h"
#include "fs/fs.h"
#include "sys/panic.h"
#include "sys/debug.h"
static int vfs_find_internal(struct vfs_ioctx *ctx,
struct vnode *at,
const char *path,
int link_itself,
struct vnode **child);
static struct vnode *vfs_root = NULL;
static struct vfs_ioctx _kernel_ioctx = {0};
struct vfs_ioctx *const kernel_ioctx = &_kernel_ioctx;
mode_t vfs_vnode_to_mode(enum vnode_type type) {
switch (type) {
case VN_MNT:
case VN_DIR:
return S_IFDIR;
case VN_LNK:
return S_IFLNK;
case VN_BLK:
return S_IFBLK;
case VN_CHR:
return S_IFCHR;
case VN_REG:
default:
return S_IFREG;
}
}
int vfs_setcwd(struct vfs_ioctx *ctx, const char *path) {
struct vnode *node;
struct vnode *dst;
int res;
if (path[0] == '/') {
if (path[1] == 0) {
ctx->cwd_vnode = NULL;
return 0;
}
if (!vfs_root) {
ctx->cwd_vnode = NULL;
} else {
if ((res = vfs_find_internal(ctx, vfs_root, path, 0, &node)) != 0) {
return res;
}
// Resolve mounts and symlinks
if ((res = vfs_link_resolve(ctx, node, &dst, 0)) != 0) {
return res;
}
if (dst->type == VN_MNT) {
_assert(dst->target);
dst = dst->target;
}
if (dst->type != VN_DIR) {
return -ENOTDIR;
}
if ((res = vfs_access_node(ctx, dst, X_OK)) != 0) {
return res;
}
ctx->cwd_vnode = dst;
}
return 0;
} else {
if ((res = vfs_find(ctx, ctx->cwd_vnode, path, 0, &node)) != 0) {
return res;
}
if ((res = vfs_link_resolve(ctx, node, &dst, 0)) != 0) {
return res;
}
if (dst->type == VN_MNT) {
_assert(dst->target);
dst = dst->target;
}
if (dst->type != VN_DIR) {
return -ENOTDIR;
}
if ((res = vfs_access_node(ctx, dst, X_OK)) != 0) {
return res;
}
ctx->cwd_vnode = dst;
return 0;
}
}
void vfs_vnode_path(char *path, struct vnode *node) {
size_t c = 0;
size_t off = 0;
struct vnode *backstack[16] = { NULL };
if (!node || !node->parent) {
path[0] = '/';
path[1] = 0;
return;
}
// No need for root node
while (node->parent) {
_assert(c < 16);
backstack[c++] = node;
node = node->parent;
}
for (ssize_t i = c - 1; i >= 0; --i) {
struct vnode *item = backstack[i];
path[off++] = '/';
if (item->type == VN_DIR && item->target) {
// It's a filesystem root
item = item->target;
}
strcpy(path + off, item->name);
off += strlen(item->name);
}
}
static const char *path_element(const char *path, char *element) {
const char *sep = strchr(path, '/');
if (!sep) {
strcpy(element, path);
return NULL;
} else {
_assert(sep - path < NODE_MAXLEN);
strncpy(element, path, sep - path);
element[sep - path] = 0;
while (*sep == '/') {
++sep;
}
if (!*sep) {
return NULL;
}
return sep;
}
}
static int vfs_link_resolve_internal(struct vfs_ioctx *ctx,
struct vnode *lnk,
struct vnode **to,
int link_itself,
int cnt) {
_assert(lnk);
if (lnk->type != VN_LNK) {
*to = lnk;
return 0;
}
if (!lnk->target && !(lnk->flags & VN_MEMORY)) {
// Try to load link contents from storage
_assert(lnk->op);
_assert(lnk->op->readlink);
_assert(lnk->parent);
char path[PATH_MAX];
int res;
if ((res = lnk->op->readlink(lnk, path, PATH_MAX)) != 0) {
return res;
}
// Try to load that path
if ((res = vfs_find(ctx, lnk->parent, path, link_itself, &lnk->target)) != 0) {
lnk->target = NULL;
return res;
}
}
struct vnode *target;
if (lnk->flags & VN_PER_PROCESS) {
_assert(lnk->target_func);
// TODO: pass it as an argument instead of ioctx
target = lnk->target_func(thread_self, lnk);
} else {
target = lnk->target;
}
if (target) {
if (target->type == VN_LNK) {
if (cnt <= 1) {
return -ELOOP;
}
return vfs_link_resolve_internal(ctx, target, to, link_itself, cnt - 1);
} else {
*to = target;
return 0;
}
}
return -ENOENT;
}
int vfs_link_resolve(struct vfs_ioctx *ctx, struct vnode *lnk, struct vnode **to, int link_itself) {
return vfs_link_resolve_internal(ctx, lnk, to, LINK_MAX, link_itself);
}
static int vfs_lookup_or_load(struct vnode *at, const char *name, struct vnode **child) {
int res;
if ((res = vnode_lookup_child(at, name, child)) == 0) {
return 0;
}
if (res != -ENOENT) {
return res;
}
// Try to load vnode if possible
if (!(at->flags & VN_MEMORY)) {
_assert(at->op);
_assert(at->op->find);
struct vnode *node;
if ((res = at->op->find(at, name, &node)) != 0) {
return res;
}
// Attach found node to its parent
vnode_attach(at, node);
*child = node;
return 0;
}
return -ENOENT;
}
static int vfs_find_internal(struct vfs_ioctx *ctx,
struct vnode *at,
const char *path,
int link_itself,
struct vnode **child) {
// FIXME: trailing slash should result in symlink resolution in cases like:
// /dir
// /lnk -> /dir
// And path is /lnk/ should result in /dir
char name[NODE_MAXLEN];
const char *child_path;
struct vnode *node;
int err;
if (!at) {
return -ENOENT;
}
_assert(child);
if (!path || !*path) {
// TODO: if mnt
*child = at;
//kdebug("empty hit-final %s\n", at->name);
return 0;
}
if (at->type == VN_LNK) {
if ((err = vfs_link_resolve(ctx, at, &node, link_itself)) != 0) {
//kdebug("miss: link resolution: %s\n", at->name);
return err;
} else {
at = node;
}
}
// TODO: links to links
_assert(at->type != VN_LNK);
if (at->type == VN_MNT) {
at = at->target;
_assert(at);
}
if (at->type != VN_DIR) {
// Only directories contain files
return -ENOTDIR;
}
// TODO: check access to "at" here
// The paths are always assumed to be relative
while (*path == '/') {
++path;
}
child_path = path;
while (1) {
child_path = path_element(child_path, name);
if (!strcmp(name, ".")) {
// Refers to this node
//kdebug(". hit %s\n", at->name);
if (!child_path || !*child_path) {
*child = at;
return 0;
}
continue;
} else if (!strcmp(name, "..")) {
// Refers to node's parent
// And this won't work for filesystem mountpoints:
// Path like a/mnt/.. would still resolve the same point as
// a/mnt
struct vnode *parent = at->parent;
if (!parent) {
parent = at;
}
//kdebug(".. hit %s\n", parent->name);
return vfs_find_internal(ctx, parent, child_path, link_itself, child);
}
break;
}
if ((err = vfs_lookup_or_load(at, name, &node)) != 0) {
//kdebug("miss %s in %s\n", name, at->name);
return err;
}
_assert(node);
if (!child_path) {
//kdebug("hit-final %s\n", name);
// TODO: only run link resolution here is asked to
struct vnode *r = node;
if (!link_itself) {
if (r->type == VN_LNK) {
if ((err = vfs_link_resolve(ctx, r, &node, 0)) != 0) {
//kdebug("miss: link resolution: %s\n", r->name);
return err;
} else {
r = node;
}
}
// TODO: links to links
_assert(r->type != VN_LNK);
}
*child = r;
return 0;
} else {
//kdebug("hit %s\n", name);
return vfs_find_internal(ctx, node, child_path, link_itself, child);
}
return -ENOENT;
}
void vfs_init(void) {
if (vfs_root) {
vnode_dump_tree(DEBUG_INFO, vfs_root, 0);
}
}
int vfs_umount(struct vfs_ioctx *ctx, const char *dir_name) {
struct vnode *node;
int res;
if ((res = vfs_find(ctx, ctx->cwd_vnode, dir_name, 0, &node)) != 0) {
return res;
}
if (!node->parent) {
kwarn("Tried to umount root\n");
return -EBUSY;
}
if (node->type != VN_DIR) {
return -ENOTDIR;
}
if (!node->target) {
return -EINVAL;
}
struct vnode *mountpoint = node->target;
_assert(mountpoint->target = node);
mountpoint->type = VN_DIR;
mountpoint->target = NULL;
struct fs *fs = node->fs;
_assert(fs);
struct blkdev *blk = fs->blk;
_assert(blk);
struct fs_class *cls = fs->cls;
_assert(cls);
if (cls->destroy) {
cls->destroy(fs);
}
memset(fs, 0, sizeof(struct fs));
blk->flags &= ~BLK_BUSY;
// TODO: destroy filesystem tree
return 0;
}
int vfs_mount_internal(struct vnode *at, void *blk, struct fs_class *cls, uint32_t flags, const char *opt) {
int res;
struct fs *fs;
struct vnode *fs_root;
struct blkdev *dev = blk;
// Device checks
if (blk) {
if (dev->flags & BLK_BUSY) {
return -EBUSY;
}
}
// Mountpoint checks
if (at) {
if (at->target) {
// This directory already has something mounted
return -EBUSY;
}
if (at->type != VN_DIR) {
return -ENOTDIR;
}
}
if (!(fs = fs_create(cls, blk, flags, opt))) {
kwarn("Filesystem creation failed\n");
return -EINVAL;
}
_assert(cls->get_root);
if (!(fs_root = cls->get_root(fs))) {
kwarn("Filesystem has no root\n");
// TODO: cleanup
return -EINVAL;
}
// Attach root to mountpoint
if (!at) {
// Make filesystem root new VFS root
vfs_root = fs_root;
} else {
at->type = VN_MNT;
at->target = fs_root;
fs_root->target = at;
// TODO: this prevents root nodes from being mounted at
// multiple targets
fs_root->parent = at->parent;
}
if (blk) {
dev->flags |= BLK_BUSY;
}
vnode_dump_tree(DEBUG_DEFAULT, vfs_root, 0);
// TODO: call cls->mount
return 0;
}
int vfs_mount(struct vfs_ioctx *ctx, const char *at, void *blk, const char *fs, uint32_t flags, const char *opt) {
int res;
struct fs_class *fs_class;
struct vnode *mountpoint;
// NOTE: Mount uses only absolute paths
if (at[0] != '/') {
kwarn("Non-absolute path used for mounting\n");
return -EINVAL;
}
if (!fs || !strcmp(fs, "auto")) {
fs_class = NULL;
} else {
fs_class = fs_class_by_name(fs);
if (!fs_class) {
kwarn("Unknown fs: %s\n", fs);
return -EINVAL;
}
}
// Find mount point
if (!strcmp(at, "/")) {
mountpoint = NULL;
} else {
if ((res = vfs_find(ctx, ctx->cwd_vnode, at, 0, &mountpoint)) != 0) {
return res;
}
}
if (!fs_class) {
if (!blk) {
return -EINVAL;
}
return blk_mount_auto(mountpoint, blk, flags, opt);
} else {
return vfs_mount_internal(mountpoint, blk, fs_class, flags, opt);
}
}
int vfs_find(struct vfs_ioctx *ctx,
struct vnode *rel,
const char *path,
int link_itself,
struct vnode **node) {
int res;
struct vnode *r;
if (!vfs_root) {
return -ENOENT;
}
if (path[0] == '/') {
// Absolute lookup
while (*path == '/') {
++path;
}
res = vfs_find_internal(ctx, vfs_root, path, link_itself, &r);
} else {
if (!rel) {
rel = ctx->cwd_vnode;
}
if (!rel) {
rel = vfs_root;
}
res = vfs_find_internal(ctx, rel, path, link_itself, &r);
}
if (res) {
return res;
}
if (r->type == VN_MNT) {
r = r->target;
}
*node = r;
return 0;
}