working vfs

hardcoded structure with support for
- mount
- list directory
- read a file
This commit is contained in:
nganhkhoa 2024-10-29 13:01:19 +07:00
commit 85951ee78f
3 changed files with 288 additions and 0 deletions

9
Makefile Normal file
View File

@ -0,0 +1,9 @@
obj-m += voidfs.o
all: voidfs
voidfs:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

45
README.md Normal file
View File

@ -0,0 +1,45 @@
# Void Filesystem
Just a dummy filesystem for learning.
## Virtual Filesystem (VFS)
Linux VFS allows us to write customize filesystem. The filesystem must be able to mount, other operations such as file read/write or folder creation/deletion can be customized.
A filesystem can be virtual, without a backed-up device. This is the case for, I guess, network file descriptors. For filesystem with a backed-up device, usually a block device, the underlying block device can be accessed by sb_bread to read a block.
Internally, a filesystem must provide a superblock, that will be used as a pointer to read files.
When a file is read, the inode of the file entry can be accessed. If we also keep the internal data of the file in `inode->i_private`, we can fetch that to read the file system through `sb_read(inode->sb)`.
## Run
```
sudo make voidfs
sudo insmod voidfs.ko
# create a dummy file block device
dd if=/dev/zero of=dummy.iso bs=4096 count=1
mkdir -p drive
sudo mount -t voidfs -o loop dummy.iso drive
sudo ls drive
sudo cat drive/dummyfile
```
## Goal
- Fully working filesystem
- Read from block device
- Encrypted filesystem
## Design
TO BE UPDATED
## References
[simplefs](https://github.com/sysprog21/simplefs)
[Linux Kernel Labs - File system drivers](https://linux-kernel-labs.github.io/refs/heads/master/labs/filesystems_part1.html)
[Linux Kernel Documentation](https://docs.kernel.org/filesystems/vfs.html)

234
voidfs.c Normal file
View File

@ -0,0 +1,234 @@
#include <linux/blkdev.h>
#include <linux/buffer_head.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/namei.h>
#include <linux/module.h>
#include <linux/parser.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/version.h>
struct dentry *voidfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data);
static int voidfs_fill_super(struct super_block *sb, void *data, int silent);
void voidfs_kill_superblock(struct super_block *sb);
struct dentry *voidfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int);
int voidfs_readdir(struct file *fp, struct dir_context *ctx);
int voidfs_open(struct inode *inode, struct file *fp);
ssize_t voidfs_read(struct file *fp, char __user *buf, size_t len, loff_t *ppos);
ssize_t voidfs_write(struct file *fp, const char __user *buf, size_t len, loff_t *ppos);
struct file_system_type voidfs_fs_type = {
.owner = THIS_MODULE,
.name = "voidfs",
.mount = voidfs_mount,
.kill_sb = voidfs_kill_superblock,
.fs_flags = FS_REQUIRES_DEV,
};
struct super_operations voidfs_super_ops = {
// null for now
};
struct inode_operations voidfs_inode_ops = {
.lookup = voidfs_lookup,
};
struct file_operations voidfs_dir_ops = {
.iterate_shared = voidfs_readdir,
};
struct file_operations voidfs_file_ops = {
.open = voidfs_open,
.read = voidfs_read,
.write = voidfs_write,
};
struct dentry *voidfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int) {
struct inode* inode = NULL;
printk(KERN_INFO "listing directory %s %s", dentry->d_parent->d_name.name, dentry->d_name.name);
printk(KERN_INFO " %p %p", dentry->d_inode, dir->i_sb);
// parse an entry path
// right now, let's just create a dummy path
if (strcmp(dentry->d_name.name, "dummyfile") == 0) {
// inode->i_sb = sb;
inode = new_inode(dir->i_sb);
inode->i_op = &voidfs_inode_ops;
inode->i_fop = &voidfs_file_ops;
inode->i_mode = S_IFREG | 0644;
inode->i_ino = 3;
// inode->i_private = (void*)1;
}
d_add(dentry, inode);
return NULL;
}
int voidfs_readdir(struct file *dir, struct dir_context *ctx) {
// populate directory entry maybe
struct inode *inode;
struct super_block *sb;
printk(KERN_INFO "readdir");
if (ctx->pos > 3) {
return -ENOENT;
}
inode = file_inode(dir);
sb = inode->i_sb;
dir_emit_dots(dir, ctx);
dir_emit(ctx, "dummydir", 8, 2, DT_DIR);
ctx->pos += 1;
dir_emit(ctx, "dummyfile", 9, 3, DT_REG);
ctx->pos += 1;
return 0;
}
int voidfs_open(struct inode *inode, struct file *fp) {
printk(KERN_INFO "voidfs open");
return 0;
}
ssize_t voidfs_read(struct file *fp, char __user *buf, size_t len, loff_t *ppos) {
struct inode *inode;
struct super_block *sb;
printk(KERN_INFO "voidfs read");
// we only allow read 1 time, should be at the start
if (*ppos > 0) {
return 0;
}
inode = file_inode(fp);
if (!inode) {
return -EFAULT;
}
sb = inode->i_sb;
// we don't read from the block device for now
// so return a dummy string
char* buffer = "this is a voidfs file";
ssize_t nbytes = 21;
// get file buffer
// move the offset
// get as much as we can
if (copy_to_user(buf, buffer, nbytes)) {
printk(KERN_ERR
"Error copying file content to userspace buffer\n");
return -EFAULT;
}
*ppos += nbytes;
return nbytes;
}
ssize_t voidfs_write(struct file *fp, const char __user *buf, size_t len, loff_t *ppos) {
return 0;
}
struct dentry *voidfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) {
printk(KERN_INFO "voidfs try to mount %s\n", dev_name);
struct dentry *ret;
ret = mount_bdev(fs_type, flags, dev_name, data, voidfs_fill_super);
if (unlikely(IS_ERR(ret))) {
printk(KERN_ERR "Error mounting voidfs.\n");
} else {
printk(KERN_INFO "voidfs is succesfully mounted on: %s\n",
dev_name);
}
return ret;
}
// initialize super block metadata
static int voidfs_fill_super(struct super_block *sb, void *data, int silent) {
struct inode *root_inode;
struct inode *dummy_body;
sb->s_magic = 0x44445555;
sb->s_op = &voidfs_super_ops;
sb_set_blocksize(sb, 1 << 12 /* 4 kilobyte */);
sb->s_maxbytes = (1 << 12) * 10;
// we don't get a read on file for now, because we still have
// to define the internal format
// create a dummy root node that we can access
root_inode = new_inode(sb);
if (!root_inode) {
return -ENOMEM;
}
root_inode->i_mode = S_IFDIR | 0644;
inode_init_owner(&nop_mnt_idmap, root_inode, NULL, root_inode->i_mode);
root_inode->i_sb = sb;
root_inode->i_op = &voidfs_inode_ops;
root_inode->i_fop = &voidfs_dir_ops;
sb->s_root = d_make_root(root_inode);
// create a dummy file entry point
// root_inode->i_fop = &voidfs_file_ops;
// create a child file
return 0;
}
void voidfs_kill_superblock(struct super_block *sb) {
printk(KERN_INFO "voidfs try to unmount\n");
kill_block_super(sb);
}
static int __init voidfs_init(void)
{
int ret;
ret = register_filesystem(&voidfs_fs_type);
if (likely(0 == ret)) {
printk(KERN_INFO "Sucessfully registered voidfs\n");
} else {
printk(KERN_ERR "Failed to register voidfs. Error code: %d\n", ret);
}
return ret;
}
static void __exit voidfs_exit(void)
{
int ret;
ret = unregister_filesystem(&voidfs_fs_type);
// kmem_cache_destroy(voidfs_inode_cache);
if (likely(ret == 0)) {
printk(KERN_INFO "Sucessfully unregistered voidfs\n");
} else {
printk(KERN_ERR "Failed to unregister voidfs. Error code: %d\n",
ret);
}
}
module_init(voidfs_init);
module_exit(voidfs_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("nganhkhoa");