585 lines
16 KiB
C
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;
|
|
}
|