Files
kernel/fs/ext2/node.c
T
2020-08-23 13:26:24 +03:00

585 lines
16 KiB
C

#include "fs/ext2/alloc.h"
#include "fs/ext2/block.h"
#include "fs/ext2/dir.h"
#include "fs/ext2/ext2.h"
#include "fs/ext2/node.h"
#include "fs/fs.h"
#include "fs/node.h"
#include "fs/ofile.h"
#include "sys/assert.h"
#include "sys/debug.h"
#include "sys/heap.h"
#include "sys/mem/slab.h"
#include "sys/panic.h"
#include "sys/string.h"
#include "user/errno.h"
#include "user/fcntl.h"
static int ext2_vnode_find(struct vnode *at, const char *name, struct vnode **res);
static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count);
static ssize_t ext2_vnode_write(struct ofile *fd, const void *buf, size_t count);
static off_t ext2_vnode_lseek(struct ofile *fd, off_t pos, int whence);
static int ext2_vnode_open(struct ofile *fd, int opt);
static int ext2_vnode_opendir(struct ofile *fd);
static int ext2_vnode_chmod(struct vnode *vn, mode_t new_mode);
static int ext2_vnode_chown(struct vnode *node, uid_t new_uid, gid_t new_gid);
static ssize_t ext2_vnode_readdir(struct ofile *fd, struct dirent *ent);
static int ext2_vnode_stat(struct vnode *at, struct stat *st);
static int ext2_vnode_truncate(struct vnode *at, size_t new_size);
static int ext2_vnode_creat(struct vnode *at, const char *name, uid_t uid, gid_t gid, mode_t mode);
static int ext2_vnode_mkdir(struct vnode *at, const char *name, uid_t uid, gid_t gid, mode_t mode);
static int ext2_vnode_unlink(struct vnode *node);
////
struct vnode_operations g_ext2_vnode_ops = {
.find = ext2_vnode_find,
.opendir = ext2_vnode_opendir,
.readdir = ext2_vnode_readdir,
.stat = ext2_vnode_stat,
.chmod = ext2_vnode_chmod,
.chown = ext2_vnode_chown,
.creat = ext2_vnode_creat,
.mkdir = ext2_vnode_mkdir,
.unlink = ext2_vnode_unlink,
.open = ext2_vnode_open,
.read = ext2_vnode_read,
.write = ext2_vnode_write,
.truncate = ext2_vnode_truncate,
.lseek = ext2_vnode_lseek,
};
////
static int ext2_vnode_find(struct vnode *at, const char *name, struct vnode **result) {
_assert(at && at->type == VN_DIR);
_assert(name);
struct ext2_inode *at_inode = at->fs_data;
_assert(at_inode);
struct fs *ext2 = at->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
char block_buffer[data->block_size];
uint32_t block_offset;
uint32_t dir_blocks = at_inode->size_lower / data->block_size;
size_t name_length = strlen(name);
int res;
// Because
_assert(name_length < 255);
for (uint32_t block_index = 0; block_index < dir_blocks; ++block_index) {
block_offset = 0;
if ((res = ext2_read_inode_block(ext2, at_inode, block_buffer, block_index)) != 0) {
return res;
}
while (block_offset < data->block_size) {
struct ext2_dirent *dirent = (struct ext2_dirent *) (block_buffer + block_offset);
if (dirent->ino && (dirent->name_length_low == name_length)) {
if (!strncmp(dirent->name, name, name_length)) {
// Found the dirent
if (!result) {
// Just return if no entry needs to be loaded
return 0;
}
struct ext2_inode *res_inode = slab_calloc(data->inode_cache);
_assert(res_inode);
struct vnode *node = vnode_create(VN_DIR, name);
if ((res = ext2_read_inode(ext2, res_inode, dirent->ino)) != 0) {
kfree(res_inode);
return res;
}
node->fs = ext2;
ext2_inode_to_vnode(node, res_inode, dirent->ino);
*result = node;
return 0;
}
}
block_offset += dirent->ent_size;
}
}
return -ENOENT;
}
static int ext2_vnode_open(struct ofile *fd, int opt) {
if ((opt & O_APPEND) && (opt & O_ACCMODE) == O_RDONLY) {
// Impossible, I guess
return -EINVAL;
}
_assert(!(opt & O_DIRECTORY));
fd->file.pos = 0;
return 0;
}
static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count) {
_assert(fd);
_assert(buf);
struct vnode *node = fd->file.vnode;
_assert(node);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
struct fs *ext2 = node->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
// TODO
_assert(!inode->size_upper);
if (fd->file.pos >= inode->size_lower) {
return 0;
}
char block_buffer[data->block_size];
size_t rem = MIN(count, inode->size_lower - fd->file.pos);
size_t bread = 0;
int res;
while (rem) {
size_t block_offset = fd->file.pos % data->block_size;
size_t can_read = MIN(rem, data->block_size - block_offset);
uint32_t block_index = fd->file.pos / data->block_size;
if ((res = ext2_read_inode_block(ext2, inode, block_buffer, block_index)) != 0) {
return res;
}
memcpy(buf, block_buffer + block_offset, can_read);
buf += can_read;
fd->file.pos += can_read;
bread += can_read;
rem -= can_read;
}
return bread;
}
static off_t ext2_vnode_lseek(struct ofile *fd, off_t pos, int whence) {
off_t calc;
struct vnode *node = fd->file.vnode;
_assert(node);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
switch (whence) {
case SEEK_SET:
calc = pos;
break;
case SEEK_CUR:
calc = pos + fd->file.pos;
break;
default:
panic("Invalid seek whence: %d\n", whence);
}
if (calc < 0 || calc >= inode->size_lower) {
return (off_t) -ESPIPE;
}
fd->file.pos = calc;
return fd->file.pos;
}
static int ext2_vnode_opendir(struct ofile *fd) {
_assert(fd && fd->file.vnode);
_assert(fd->file.vnode->type == VN_DIR);
fd->file.pos = 0;
return 0;
}
static ssize_t ext2_vnode_readdir(struct ofile *fd, struct dirent *ent) {
_assert(fd);
_assert(ent);
struct vnode *node = fd->file.vnode;
_assert(node);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
struct fs *ext2 = node->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
if (fd->file.pos >= inode->size_lower) {
return 0;
}
char block_buffer[data->block_size];
uint32_t block_index = fd->file.pos / data->block_size;
struct ext2_dirent *dirent = (struct ext2_dirent *) (block_buffer + fd->file.pos % data->block_size);
int res;
if ((res = ext2_read_inode_block(ext2, inode, block_buffer, block_index)) != 0) {
return res;
}
if (!dirent->ino) {
fd->file.pos += dirent->ent_size;
if (fd->file.pos / data->block_size != block_index) {
// Dirent reclen cannot point beyond block size
_assert(!(fd->file.pos % data->block_size));
// Requires reading a next block for dirent
return ext2_vnode_readdir(fd, ent);
}
// Don't think the scenario of two empty dirents following each other is likely
dirent = (struct ext2_dirent *) (block_buffer + fd->file.pos % data->block_size);
_assert(dirent->ino);
}
uint32_t name_length = dirent->name_length_low;
ent->d_type = DT_UNKNOWN;
// Even if provided that hint, just ignore it. stat() should be sufficient
if (!(data->sb.required_features & EXT2_REQ_ENT_TYPE)) {
name_length += (uint16_t) dirent->name_length_high << 8;
}
strncpy(ent->d_name, dirent->name, name_length);
ent->d_name[name_length] = 0;
ent->d_off = fd->file.pos;
ent->d_reclen = dirent->ent_size;
// Calculate next dirent position
fd->file.pos += dirent->ent_size;
return ent->d_reclen;
}
static int ext2_vnode_stat(struct vnode *node, struct stat *st) {
_assert(node && st);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
struct fs *ext2 = node->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
_assert(inode->uid == node->uid);
_assert(inode->gid == node->gid);
_assert((inode->mode & 0xFFF) == node->mode);
st->st_mode = inode->mode;
st->st_uid = inode->uid;
st->st_gid = inode->gid;
st->st_size = inode->size_lower;
_assert(!inode->size_upper);
st->st_blksize = data->block_size;
st->st_blocks = (st->st_size + data->block_size - 1) / data->block_size;
st->st_dev = 0;
st->st_rdev = 0;
st->st_mtime = inode->mtime;
st->st_atime = inode->atime;
st->st_ctime = inode->ctime;
st->st_nlink = inode->hard_links;
return 0;
}
////
// Map inode parameters to vnode fields
void ext2_inode_to_vnode(struct vnode *vnode, struct ext2_inode *inode, uint32_t ino) {
vnode->ino = ino;
vnode->uid = inode->uid;
vnode->gid = inode->gid;
vnode->mode = inode->mode & 0xFFF;
vnode->fs_data = inode;
vnode->op = &g_ext2_vnode_ops;
switch (inode->mode & 0xF000) {
case EXT2_IFDIR:
vnode->type = VN_DIR;
break;
case EXT2_IFREG:
vnode->type = VN_REG;
break;
default:
panic("Unsupported inode type: %04x\n", inode->mode & 0xF000);
}
}
// Mutation operations
static ssize_t ext2_vnode_write(struct ofile *fd, const void *buf, size_t count) {
_assert(fd);
_assert(buf);
struct vnode *node = fd->file.vnode;
_assert(node);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
struct fs *ext2 = node->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
int res;
// TODO
_assert(!inode->size_upper);
if (fd->file.pos > inode->size_lower) {
return 0;
}
char block_buf[1024];
size_t req_size = MAX(fd->file.pos + count, inode->size_lower);
uint32_t block_index;
uint32_t block_offset;
uint32_t can_write;
size_t rem = count;
size_t off = 0;
if ((res = ext2_file_resize(ext2, node->ino, inode, req_size)) != 0) {
return res;
}
while (rem) {
block_index = fd->file.pos / data->block_size;
block_offset = fd->file.pos % data->block_size;
can_write = MIN(rem, data->block_size - block_offset);
_assert(can_write);
if (can_write != data->block_size) {
if (ext2_read_inode_block(ext2, inode, block_buf, block_index) != 0) {
panic("PANIC\n");
}
memcpy(block_buf + block_offset, buf + off, can_write);
if (ext2_write_inode_block(ext2, inode, block_buf, block_index) != 0) {
panic("PANIC\n");
}
} else {
if (ext2_write_inode_block(ext2, inode, buf + off, block_index) != 0) {
panic("PANIC\n");
}
}
rem -= can_write;
off += can_write;
fd->file.pos += can_write;
}
inode->mtime = time();
if ((res = ext2_write_inode(ext2, inode, node->ino)) != 0) {
return res;
}
return off;
}
static int ext2_vnode_truncate(struct vnode *node, size_t new_size) {
_assert(node);
struct ext2_inode *inode = node->fs_data;
struct fs *ext2 = node->fs;
_assert(ext2);
int res;
if (node->type != VN_REG) {
return -EPERM;
}
if ((res = ext2_file_resize(ext2, node->ino, inode, new_size)) != 0) {
return res;
}
inode->mtime = time();
return ext2_write_inode(ext2, inode, node->ino);
}
static int ext2_vnode_chmod(struct vnode *node, mode_t new_mode) {
_assert(node);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
struct fs *ext2 = node->fs;
_assert(ext2);
// Only rewrite inode if something really changed
if ((inode->mode & 0xFFF) != (new_mode & 0xFFF)) {
// chmod() only updates file mode, don't touch file type
inode->mode &= ~0xFFF;
inode->mode |= new_mode & 0xFFF;
inode->mtime = time();
node->mode = new_mode & 0xFFF;
return ext2_write_inode(ext2, inode, node->ino);
}
return 0;
}
static int ext2_vnode_chown(struct vnode *node, uid_t new_uid, gid_t new_gid) {
_assert(node);
struct ext2_inode *inode = node->fs_data;
_assert(inode);
struct fs *ext2 = node->fs;
_assert(ext2);
if (new_uid != inode->uid || new_gid != inode->gid) {
inode->uid = new_uid;
inode->gid = new_gid;
inode->mtime = time();
node->uid = new_uid;
node->gid = new_gid;
return ext2_write_inode(ext2, inode, node->ino);
}
return 0;
}
static int ext2_vnode_creat(struct vnode *at, const char *name, uid_t uid, gid_t gid, mode_t mode) {
_assert(at);
struct ext2_inode *at_inode = at->fs_data;
_assert(at_inode);
struct fs *ext2 = at->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
if (ext2_vnode_find(at, name, NULL) == 0) {
return -EEXIST;
}
if ((at_inode->mode & 0xF000) != EXT2_IFDIR) {
panic("Not a directory\n");
}
struct ext2_inode *inode = slab_calloc(data->inode_cache);
if (!inode) {
panic("Failed to allocate (host) an inode\n");
}
uint32_t ino = ext2_alloc_inode(ext2, data, 0);
if (!ino) {
panic("Failed to allocate (disk) an inode\n");
}
inode->mtime = time();
inode->atime = inode->mtime;
inode->ctime = inode->mtime;
inode->uid = uid;
inode->gid = gid;
inode->mode = (mode & 0xFFF) | EXT2_IFREG;
inode->hard_links = 1;
_assert(ext2_dir_insert_inode(ext2, at_inode, at->ino, name, ino, VN_REG) == 0);
_assert(ext2_write_inode(ext2, inode, ino) == 0);
return 0;
}
static int ext2_vnode_mkdir(struct vnode *at, const char *name, uid_t uid, gid_t gid, mode_t mode) {
_assert(at);
struct ext2_inode *at_inode = at->fs_data;
_assert(at_inode);
struct fs *ext2 = at->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
if (ext2_vnode_find(at, name, NULL) == 0) {
return -EEXIST;
}
if ((at_inode->mode & 0xF000) != EXT2_IFDIR) {
panic("Not a directory\n");
}
struct ext2_inode *inode = slab_calloc(data->inode_cache);
if (!inode) {
panic("Failed to allocate (host) an inode\n");
}
uint32_t ino = ext2_alloc_inode(ext2, data, 1);
if (!ino) {
panic("Failed to allocate (disk) an inode\n");
}
inode->mtime = time();
inode->atime = inode->mtime;
inode->ctime = inode->mtime;
inode->uid = uid;
inode->gid = gid;
inode->mode = (mode & 0xFFF) | EXT2_IFDIR;
inode->hard_links = 2; // "." and parent dirent
// Setup the first block for this inode
_assert(ext2_dir_insert_inode(ext2, inode, ino, ".", ino, VN_DIR) == 0);
_assert(ext2_dir_insert_inode(ext2, inode, ino, "..", at->ino, VN_DIR) == 0);
// Insert to parent
_assert(ext2_dir_insert_inode(ext2, at_inode, at->ino, name, ino, VN_DIR) == 0);
_assert(ext2_write_inode(ext2, inode, ino) == 0);
// ".." points to the parent, so increment its refcount
++at_inode->hard_links;
_assert(ext2_write_inode(ext2, at_inode, at->ino) == 0);
return 0;
}
static int ext2_vnode_unlink(struct vnode *vn) {
if (vn->ino == 2) {
// Cannot remove root node
return -EPERM;
}
struct vnode *at = vn->parent;
_assert(at);
struct fs *ext2 = vn->fs;
_assert(ext2);
struct ext2_data *data = ext2->fs_private;
_assert(data);
struct ext2_inode *at_inode = at->fs_data;
struct ext2_inode *inode = vn->fs_data;
_assert(at_inode && inode);
if (ext2_vnode_find(at, vn->name, NULL) != 0) {
return -ENOENT;
}
_assert(ext2_dir_del_inode(ext2, at_inode, at->ino, vn) == 0);
if ((inode->mode & 0xF000) == EXT2_IFDIR) {
// `vn`s ".." points to `at`, so decrement refcount
--at_inode->hard_links;
_assert(ext2_write_inode(ext2, at_inode, at->ino) == 0);
}
// Resize the inode to zero to free its blocks
_assert(ext2_file_resize(ext2, vn->ino, inode, 0) == 0);
inode->hard_links = 0;
// Mark it as "deleted" (?)
inode->dtime = time();
_assert(ext2_write_inode(ext2, inode, vn->ino) == 0);
// Free the inode
ext2_free_inode(ext2, data, vn->ino, vn->type == VN_DIR);
return 0;
}