Orion
Barry Importing existing Orion kernel d41a53c (3 years, 2 months ago)
/*
* This file controls the Virtual File System for the Kernel. It implements a
* generic File System, which can hook other File Systems. This forms a key
* part of the Kernel, and is used for loading programs and some higher-level
* IPC. This system will dispatch messages to the actual File System tasks in
* user-space.
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "tmpfs/fs.h"
#include "devfs/fs.h"
#include "procfs/fs.h"
#include "ext2fs/fs.h"
#include "vfs.h"
#include "cache.h"
#include "inode.h"
#include "../mem/heap.h"
#include "../task/task.h"
FileSystemType *fsTypes;
/* Register a File System Type */
void
register_fstype(FileSystemType *fstype)
{
if (!fsTypes) {
fsTypes = fstype;
return;
}
FileSystemType *prev;
for (prev = fsTypes; prev->next; prev = prev->next);
prev->next = fstype;
}
/* Initialise the Virtual File System */
void
init_vfs(void)
{
register_fstype(&tmpfsType);
register_fstype(&devfsType);
register_fstype(&procfsType);
register_fstype(&ext2fsType);
mount("tmpfs", NULL, "TmpFS", 0, NULL);
mkdir("dev", 0);
mount("devfs", "/dev", "DevFS", 0, NULL);
mkdir("proc", 0);
mount("procfs", "/proc", "ProcFS", 0, NULL);
}
/* Check if a process has permission for a file */
int
permission(Inode *inode, int mask)
{
if (!inode)
return 0;
int mode = inode->mode;
if (current->euid == inode->uid)
mode >>= 6;
else if (current->egid == inode->gid)
mode >>= 3;
if (((mode & mask & 0007) == mask) || super_user())
return 1;
return 0;
}
/* Lookup a file path */
Inode *
lookup(const char *path, CustodyChain *newchain)
{
if (!current->fs->root)
return NULL;
char buf[PATH_MAX], *p = buf;
strncpy(buf, path, PATH_MAX);
Inode *ino;
DirEntry *de = NULL;
CustodyChain *chain = create_custody_chain();
/* Resolve to absolute/relative root */
if (*p == '/') {
ino = current->fs->root;
while (*++p == '/');
} else {
ino = current->fs->cwd;
copy_custody_chain(¤t->fs->cwdCustody, chain);
}
/* Drop through directories */
char *curr, *prev = p;
for (curr = p; *curr; curr++) {
if (*curr != '/')
continue;
*curr = '\0';
if (!strcmp(prev, ".")) {
de = NULL;
} else if (!strcmp(prev, "..")) {
de = NULL;
if (ino != current->fs->root) {
de = remove_custody(chain);
if (!de)
ino = current->fs->root;
}
} else {
de = inode_lookup(ino, prev);
if (!de) {
ino = NULL;
goto end;
}
add_custody(chain, de);
entry_put(de);
}
if (de) {
ino = de->inode;
if (de->mnt)
ino = de->mnt;
}
while (*++curr == '/');
prev = curr;
}
/* Basename */
if (!strcmp(prev, ".") || !strcmp(prev, "")) {
if (!S_ISDIR(ino->mode)) {
ino = NULL;
de = NULL;
}
} else if (!strcmp(prev, "..")) {
if (ino != current->fs->root) {
de = remove_custody(chain);
if (!de)
ino = current->fs->root;
}
} else {
de = inode_lookup(ino, prev);
if (!de) {
de = kmalloc(sizeof(DirEntry));
init_lock(&de->lock);
strncpy(de->name, prev, NAME_MAX);
de->super = ino->super;
de->usage = 1;
}
add_custody(chain, de);
entry_put(de);
}
if (de) {
ino = de->inode;
if (de->mnt)
ino = de->mnt;
if (ino)
inode_get(ino);
}
/* Copy chain */
if (newchain)
copy_custody_chain(chain, newchain);
end:
destroy_custody_chain(chain);
return ino;
}
/* Mount a file system */
int
mount(const char *src, const char *target, const char *type,
unsigned long flags, void *data)
{
if (!verify_access(src, strnlen(src, PATH_MAX), PROT_READ))
return -EFAULT;
if (!verify_access(target, strnlen(target, PATH_MAX), PROT_READ))
return -EFAULT;
if (!verify_access(type, strnlen(type, 16), PROT_READ))
return -EFAULT;
FileSystemType *fsType;
for (fsType = fsTypes; fsType; fsType = fsType->next)
if (!strcmp(fsType->name, type))
break;
if (!fsType)
return -ENODEV;
Inode *mnt = fsType->mount(fsType, flags, src, data);
if ((int) mnt < 0)
return (int) mnt;
if (!target && !current->fs->root) {
current->fs->root = inode_get(mnt);
current->fs->cwd = inode_get(mnt);
return 0;
}
/* Find target's DirEntry */
CustodyChain tmp;
init_custody_chain(&tmp);
Inode *root = lookup(target, &tmp);
if (!root) {
clean_custody_chain(&tmp);
return -ENOENT;
}
if (!S_ISDIR(root->mode)) {
inode_put(root);
clean_custody_chain(&tmp);
return -ENOTDIR;
}
DirEntry *de = entry_get(tmp.end->entry);
de->mnt = inode_get(mnt);
inode_put(root);
clean_custody_chain(&tmp);
return 0;
}
/* Open a file */
int
open(const char *name, int flags, ...)
{
if (!verify_access(name, strnlen(name, PATH_MAX), PROT_READ))
return -EFAULT;
/* Allocate file descriptor */
int fd;
for (fd = 0; fd < NFILES; fd++)
if (!current->files->fd[fd])
break;
if (fd == NFILES)
return -EMFILE;
/* Find inode */
CustodyChain chain;
init_custody_chain(&chain);
Inode *ino = lookup(name, &chain);
va_list args;
va_start(args, flags);
if (!ino) {
if (!(flags & O_CREATE)) {
clean_custody_chain(&chain);
return -ENOENT;
}
/* Create file */
if (!chain.size) {
clean_custody_chain(&chain);
return -ENOENT;
}
if (chain.end->prev->entry) {
ino = chain.end->prev->entry->inode;
if (chain.end->prev->entry->mnt)
ino = chain.end->prev->entry->mnt;
} else {
ino = current->fs->root;
}
if (!permission(ino, MAY_WRITE)) {
clean_custody_chain(&chain);
return -EACCES;
}
inode_create(ino, chain.end->entry, va_arg(args, mode_t));
ino = lookup(name, NULL);
}
va_end(args);
/* Check permissions */
int perm = 0;
if ((flags & O_ACCMODE) == O_RDONLY)
perm = MAY_READ;
else if ((flags & O_ACCMODE) == O_WRONLY)
perm = MAY_WRITE;
else if ((flags & O_ACCMODE) == O_RDWR)
perm = MAY_READ | MAY_WRITE;
if (!permission(ino, perm)) {
inode_put(ino);
clean_custody_chain(&chain);
return -EACCES;
}
/* Open file */
File *file = kmalloc(sizeof(File));
file->inode = inode_get(ino);
file->uid = ino->uid;
file->gid = ino->gid;
file->flags = flags;
file->mode = ino->mode;
file->pos = 0;
init_lock(&file->lock);
file->ops = ino->fileOps;
file->path = create_custody_chain();
file->usage = 1;
copy_custody_chain(&chain, file->path);
clean_custody_chain(&chain);
int err = file_open(file);
if (err) {
file_put(file);
inode_put(ino);
return err;
}
current->files->fd[fd] = file;
inode_put(ino);
return fd;
}
/* Close a file */
int
close(int fd)
{
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
current->files->fd[fd] = NULL;
if (!file)
return -EBADF;
file_put(file);
return 0;
}
/* Read a file */
size_t
read(int fd, void *buf, size_t count)
{
if (!verify_access(buf, count, PROT_WRITE))
return -EFAULT;
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
if (S_ISDIR(file->mode))
return -EISDIR;
return file_read(file, buf, count);
}
/* Write a file */
size_t
write(int fd, void *buf, size_t count)
{
if (!verify_access(buf, count, PROT_READ))
return -EFAULT;
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
if (S_ISDIR(file->mode))
return -EISDIR;
return file_write(file, buf, count);
}
/* I/O Control */
int
ioctl(int fd, unsigned long request, ...)
{
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
va_list args;
va_start(args, request);
uintptr_t argp = va_arg(args, uintptr_t);
va_end(args);
return file_ioctl(file, request, argp);
}
/* Move a file's position */
off_t
lseek(int fd, off_t offset, int whence)
{
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
acquire(&file->lock);
switch (whence) {
case SEEK_SET:
file->pos = offset;
break;
case SEEK_CUR:
file->pos += offset;
break;
case SEEK_END:
file->pos = file->inode->size + offset;
break;
default:
release(&file->lock);
return -EINVAL;
}
release(&file->lock);
return file->pos;
}
/* Get file status */
int
stat(const char *pathname, struct stat *statbuf)
{
if (!verify_access(pathname, strnlen(pathname, PATH_MAX), PROT_READ))
return -EFAULT;
if (!verify_access(statbuf, sizeof(struct stat), PROT_WRITE))
return -EFAULT;
Inode *inode = lookup(pathname, NULL);
if (!inode)
return -ENOENT;
if (statbuf) {
statbuf->inode = inode->ino;
statbuf->mode = inode->mode;
statbuf->nlink = inode->nlink;
statbuf->uid = inode->uid;
statbuf->gid = inode->gid;
statbuf->size = inode->size;
}
inode_put(inode);
return 0;
}
/* Get file status by file descriptor */
int
fstat(int fd, struct stat *statbuf)
{
if (!verify_access(statbuf, sizeof(struct stat), PROT_WRITE))
return -EFAULT;
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
Inode *inode = file->inode;
statbuf->inode = inode->ino;
statbuf->mode = inode->mode;
statbuf->size = inode->size;
return 0;
}
/* Get directory entries */
size_t
getdents(int fd, void *buf, size_t count)
{
if (!verify_access(buf, count, PROT_WRITE))
return -EFAULT;
if (count < sizeof(DirEnt))
return -EINVAL;
int err;
size_t i, size = 0;
DirEnt *dent = buf;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
if (!S_ISDIR(file->inode->mode))
return -ENOTDIR;
/* Read as many entries as possible */
for (i = 0; i < count / sizeof(DirEnt); i++) {
err = file_readdir(file, dent, i);
if (err < 0)
goto out;
size += sizeof(DirEnt) + dent->namelen;
*((char *) buf + size - 1) = '\0';
dent = (void *) ((char *) buf + size);
}
out:
if (!i)
return err;
return size;
}
/* Make a directory */
int
mkdir(const char *pathname, mode_t mode)
{
if (!verify_access(pathname, strnlen(pathname, PATH_MAX), PROT_READ))
return -EFAULT;
int err;
CustodyChain chain;
init_custody_chain(&chain);
Inode *inode = lookup(pathname, &chain);
if (inode) {
err = -EEXIST;
goto end;
}
if (!chain.size) {
err = -ENOENT;
goto end;
}
/* Check write permission */
if (chain.end->prev->entry) {
inode = chain.end->prev->entry->inode;
if (chain.end->prev->entry->mnt)
inode = chain.end->prev->entry->mnt;
} else {
inode = current->fs->root;
}
if (!permission(inode, MAY_WRITE)) {
err = -EACCES;
goto end;
}
/* Create directory */
err = inode_mkdir(inode, entry_get(chain.end->entry), mode);
end:
clean_custody_chain(&chain);
// if (inode)
// inode_put(inode);
return err;
}
/* Remove a directory */
int
rmdir(const char *pathname)
{
return 0;
}
/* Make a special node */
int
mknod(const char *pathname, mode_t mode, dev_t dev)
{
if (!verify_access(pathname, strnlen(pathname, PATH_MAX), PROT_READ))
return -EFAULT;
int err;
CustodyChain chain;
init_custody_chain(&chain);
Inode *inode = lookup(pathname, &chain);
if (inode) {
err = -EEXIST;
goto end;
}
if (!chain.size) {
err = -ENOENT;
goto end;
}
/* Check write permission */
if (chain.end->prev->entry) {
inode = chain.end->prev->entry->inode;
if (chain.end->prev->entry->mnt)
inode = chain.end->prev->entry->mnt;
} else {
inode = current->fs->root;
}
if (!permission(inode, MAY_WRITE)) {
err = -EACCES;
goto end;
}
/* Create node */
err = inode_mknod(inode, entry_get(chain.end->entry), mode, dev);
end:
clean_custody_chain(&chain);
// if (inode)
// inode_put(inode);
return err;
}
/* Rename/move a file */
int
rename(const char *oldpath, const char *newpath)
{
return 0;
}
/* Duplicate a file descriptor */
int
dup(int oldfd)
{
/* Check file descriptor */
if (oldfd < 0 || oldfd >= NFILES)
return -EBADF;
if (!current->files->fd[oldfd])
return -EBADF;
/* Allocate file descriptor */
int fd;
for (fd = 0; fd < NFILES; fd++)
if (!current->files->fd[fd])
break;
if (fd == NFILES)
return -EMFILE;
dup2(oldfd, fd);
}
/* Duplicate a file descriptor to a new file descriptor */
int
dup2(int oldfd, int newfd)
{
/* Check file descriptors */
if (oldfd < 0 || oldfd >= NFILES)
return -EBADF;
if (!current->files->fd[oldfd])
return -EBADF;
if (newfd < 0 || newfd >= NFILES)
return -EBADF;
if (newfd == oldfd)
return newfd;
/* Close old file in newfd */
if (current->files->fd[newfd])
close(newfd);
current->files->fd[newfd] = file_get(current->files->fd[oldfd]);
return newfd;
}
/* Check if a file descriptor refers to a terminal */
int
isatty(int fd)
{
if (fd < 0 || fd >= NFILES)
return -EBADF;
File *file = current->files->fd[fd];
if (!file)
return -EBADF;
if (S_ISCHR(file->mode))
return 1;
return 0;
}
/* Change the current working directory */
int
chdir(const char *path)
{
if (!verify_access(path, strnlen(path, PATH_MAX), PROT_READ))
return -EFAULT;
CustodyChain tmp;
init_custody_chain(&tmp);
Inode *node = lookup(path, &tmp);
if (!node)
return -ENOENT;
if (!S_ISDIR(node->mode))
return -ENOTDIR;
if (!permission(node, MAY_READ))
return -EACCES;
inode_put(current->fs->cwd);
current->fs->cwd = inode_get(node);
while (current->fs->cwdCustody.size)
remove_custody(¤t->fs->cwdCustody);
copy_custody_chain(&tmp, ¤t->fs->cwdCustody);
clean_custody_chain(&tmp);
// inode_put(node);
return 0;
}
/* Change the current root */
int
chroot(const char *path)
{
if (!verify_access(path, strnlen(path, PATH_MAX), PROT_READ))
return -EFAULT;
Inode *node = lookup(path, NULL);
if (!node)
return -ENOENT;
if (!S_ISDIR(node->mode))
return -ENOTDIR;
if (!permission(node, MAY_READ))
return -EACCES;
inode_put(current->fs->root);
current->fs->root = inode_get(node);
// inode_put(node);
return 0;
}
/* Get the current working directory's path */
char *
getcwd(char *buf, size_t size)
{
if (!verify_access(buf, size, PROT_WRITE))
return (char *) -EFAULT;
return custody_path(¤t->fs->cwdCustody, buf, size);
}