Skip to content

Instantly share code, notes, and snippets.

@harrisonturton
Last active July 17, 2023 13:51
Show Gist options
  • Save harrisonturton/17d3da3a0360137ca82e277d4d54dc73 to your computer and use it in GitHub Desktop.
Save harrisonturton/17d3da3a0360137ca82e277d4d54dc73 to your computer and use it in GitHub Desktop.
Creating Linux virtual filesystems (LWN, 2003)
/*
* Author: Harrison Turton <[email protected]>
*
* This follows the LWN article "Creating Linux virtual filesystems":
*
* https://lwn.net/Articles/57369/
*
* However, this was published in 2003. The VFS has changed quite a
* bit since then, and so the code had to be tweaked to work. So
* isn't a byte-for-byte copy, I had to go digging through the kernel
* to figure out the specifics.
*
* Maybe this will help someone else :)
*
* To use this, build it as a kernel module, load it, and mount a directory
* as "myfs". There's nothing particularly special about this process. Just
* know that you can't ls the mounted directory, because that's not
* implemented. Instead, run cat /dir/counter and cat/subdir/subcounter a
* few times, and see what happens.
*/
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h> /* libfs */
#include <linux/kernel.h>
#include <linux/time.h>
#include <asm/atomic.h>
MODULE_LICENSE("MIT");
MODULE_AUTHOR("Harrison Turton");
MODULE_DESCRIPTION("A small demo filesystem for learning");
#define MYFS_MAGIC 0xdeadbeef
#define TMPSIZE 20
#define PERM_RX_RWX 0755
#define PERM_R_RWX 0644
static struct super_operations myfs_s_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
};
static ssize_t myfs_read(struct file *filp, char *buf,
size_t count, loff_t *offset)
{
atomic_t *counter = (atomic_t *) filp->private_data;
int v, len;
char tmp[TMPSIZE];
v = atomic_read(counter);
if (*offset > 0) {
v -= 1;
} else {
atomic_inc(counter);
}
len = snprintf(tmp, TMPSIZE, "%d\n", v);
if (*offset > len)
return 0;
if (count > len - *offset)
count = len - *offset;
if (copy_to_user(buf, tmp + *offset, count))
return -EFAULT;
*offset += count;
return count;
}
static ssize_t myfs_write(struct file *filp, const char *buf,
size_t count, loff_t *offset)
{
atomic_t *counter = (atomic_t *) filp->private_data;
char tmp[TMPSIZE];
if (*offset != 0)
return -EINVAL;
if (count >= TMPSIZE)
return -EINVAL;
memset(tmp, 0, TMPSIZE);
if (copy_from_user(tmp, buf, count))
return -EFAULT;
atomic_set(counter, simple_strtol(tmp, NULL, 10));
return count;
}
static int myfs_open(struct inode *inode, struct file *filp)
{
filp->private_data = inode->i_private;
return 0;
}
static struct file_operations myfs_f_ops = {
.open = myfs_open,
.read = myfs_read,
.write = myfs_write,
};
static struct inode *myfs_make_inode(struct super_block *sb, int mode)
{
struct inode *inode = new_inode(sb);
if (inode == NULL)
return NULL;
inode->i_mode = mode;
i_uid_write(inode, 0);
i_gid_write(inode, 0);
inode->i_blocks = 0;
inode->i_atime = current_time(inode);
inode->i_mtime = current_time(inode);
inode->i_ctime = current_time(inode);
return inode;
}
static struct dentry *myfs_create_file(struct super_block *sb,
struct dentry *dir, const char *name, atomic_t *counter)
{
struct dentry *dentry;
struct inode *inode;
struct qstr qname;
qname.name = name;
qname.len = strlen(name);
qname.hash = full_name_hash(dir, name, qname.len);
dentry = d_alloc(dir, &qname);
if (!dentry)
goto out;
inode = myfs_make_inode(sb, S_IFREG | PERM_RX_RWX);
if (!inode)
goto out_dput;
inode->i_fop = &myfs_f_ops;
inode->i_private = counter;
d_add(dentry, inode);
return dentry;
out_dput:
dput(dentry);
out:
return NULL;
}
static struct dentry *myfs_create_dir(struct super_block *sb,
struct dentry *parent, const char *name)
{
struct dentry *dentry;
struct inode *inode;
struct qstr qname;
dentry = d_alloc(parent, &qname);
if (!dentry)
goto out;
qname.name = name;
qname.len = strlen(name);
qname.hash = full_name_hash(parent, name, qname.len);
inode = myfs_make_inode(sb, S_IFDIR | PERM_RX_RWX);
if (!inode)
goto out_dput;
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
d_add(dentry, inode);
return dentry;
out_dput:
dput(dentry);
out:
return NULL;
}
static atomic_t counter, subcounter;
static void myfs_create_files(struct super_block *sb, struct dentry *root)
{
struct dentry *subdir;
// One counter file in the top-level directory
atomic_set(&counter, 0);
myfs_create_file(sb, root, "counter", &counter);
// One in the subdirectory
atomic_set(&subcounter, 0);
subdir = myfs_create_dir(sb, root, "subdir");
if (subdir) {
myfs_create_file(sb, subdir, "subcounter", &subcounter);
} else {
pr_info("Could not create subdir");
}
}
static int myfs_fill_super(struct super_block *sb, void *data, int flags)
{
struct inode *root;
struct dentry *root_dentry;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = MYFS_MAGIC;
sb->s_op = &myfs_s_ops;
root = myfs_make_inode(sb, S_IFDIR | PERM_R_RWX);
if (!root)
goto out;
root->i_op = &simple_dir_inode_operations;
root->i_fop = &simple_dir_operations;
root_dentry = d_make_root(root);
if (!root_dentry)
goto out_iput;
sb->s_root = root_dentry;
myfs_create_files(sb, root_dentry);
return 0;
out_iput:
iput(root);
out:
return -ENOMEM;
}
static struct dentry *myfs_mount(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data)
{
return mount_nodev(fs_type, flags, data, &myfs_fill_super);
}
static struct file_system_type myfs_type = {
.name = "myfs",
.owner = THIS_MODULE,
.mount = myfs_mount,
.kill_sb = kill_litter_super,
};
static int __init myfs_init(void)
{
pr_info("myfs_init: Hello world!\n");
pr_info("myfs_init: Moduled loaded\n");
return register_filesystem(&myfs_type);
}
static void __exit myfs_exit(void)
{
pr_info("myfs_init: Goodbye world :(\n");
pr_info("myfs_exit: Module unloaded\n");
unregister_filesystem(&myfs_type);
}
module_init(myfs_init);
module_exit(myfs_exit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment