Last active
July 17, 2023 13:51
-
-
Save harrisonturton/17d3da3a0360137ca82e277d4d54dc73 to your computer and use it in GitHub Desktop.
Creating Linux virtual filesystems (LWN, 2003)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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