diff --git a/Makefile b/Makefile index 999f44f..940b175 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -obj-m := statsfs.o +obj-m := libfs.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) @@ -7,4 +7,4 @@ all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: - $(MAKE) -C $(KDIR) M=$(PWD) clean \ No newline at end of file + $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/libfs.c b/libfs.c new file mode 100644 index 0000000..166253b --- /dev/null +++ b/libfs.c @@ -0,0 +1,210 @@ +/* + * Custom Filesystem Module - Implements the described behavior for "books" and "authors" directories. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FS_MAGIC 0x20240615 +#define BOOKS_MODE 01777 +#define AUTHORS_MODE 0777 +#define AUTHOR_SUBDIR_MODE 01777 + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("user"); +MODULE_DESCRIPTION("Custom FS with books and authors directories"); + +static struct inode *books_inode; +static struct inode *authors_inode; + +struct link_entry { + struct hlist_node node; + struct dentry *link; + struct inode *target; +}; + +DEFINE_HASHTABLE(link_table, 6); // 64 buckets + +static void remove_links_to_inode(struct inode *inode) { + struct link_entry *entry; + struct hlist_node *tmp; + int bkt; + hash_for_each_safe(link_table, bkt, tmp, entry, node) { + if (entry->target == inode) { + d_delete(entry->link); + hash_del(&entry->node); + kfree(entry); + } + } +} + +static void fs_evict_inode(struct inode *inode) { + if (inode->i_sb && inode->i_sb->s_magic == FS_MAGIC) { + if (S_ISREG(inode->i_mode)) { + remove_links_to_inode(inode); + } + } + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); +} + +static ssize_t books_file_read(struct file *file, char __user *buf, size_t len, loff_t *ppos) { + return simple_read_from_buffer(buf, len, ppos, file->private_data, PAGE_SIZE); +} + +static ssize_t books_file_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { + return simple_write_to_buffer(file->private_data, PAGE_SIZE, ppos, buf, len); +} + +static const struct file_operations books_file_fops = { + .read = books_file_read, + .write = books_file_write, + .llseek = generic_file_llseek, +}; + +static const struct inode_operations books_file_iops = { + .getattr = simple_getattr, + .setattr = simple_setattr, +}; + +static const struct inode_operations author_subdir_iops; + +static struct dentry *author_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { + return simple_lookup(dir, dentry, flags); +} + +static int author_mkdir(struct user_namespace *ns, struct inode *dir, struct dentry *dentry, umode_t mode) { + struct inode *inode; + inode = new_inode(dir->i_sb); + if (!inode) + return -ENOMEM; + inode->i_ino = get_next_ino(); + inode_init_owner(ns, inode, dir, S_IFDIR | AUTHOR_SUBDIR_MODE); + inode->i_op = &author_subdir_iops; + inode->i_fop = &simple_dir_operations; + d_add(dentry, inode); + return 0; +} + +static int author_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { + int err; + struct inode *inode = d_inode(old_dentry); + if (!S_ISREG(inode->i_mode)) return -EPERM; + err = simple_link(old_dentry, dir, new_dentry); + if (err == 0) { + struct link_entry *entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->link = dget(new_dentry); + entry->target = inode; + hash_add(link_table, &entry->node, inode->i_ino); + } + } + return err; +} + +static const struct inode_operations author_subdir_iops = { + .lookup = author_lookup, + .mkdir = author_mkdir, + .rmdir = simple_rmdir, + .link = author_link, +}; + +static const struct super_operations fs_ops = { + .evict_inode = fs_evict_inode, +}; + +static struct inode *fs_make_inode(struct super_block *sb, umode_t mode) { + struct inode *inode = new_inode(sb); + if (!inode) + return NULL; + inode->i_ino = get_next_ino(); + inode_init_owner(&init_user_ns, inode, NULL, mode); + inode->i_sb = sb; + inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); + + if (S_ISDIR(mode) && !inode->i_op) { + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + } else if (S_ISREG(mode)) { + inode->i_fop = &books_file_fops; + inode->i_op = &books_file_iops; + inode->i_private = kzalloc(PAGE_SIZE, GFP_KERNEL); + } + return inode; +} + +static int fs_fill_super(struct super_block *sb, void *data, int silent) { + struct inode *root_inode; + struct dentry *root_dentry; + struct dentry *books_dentry; + struct dentry *authors_dentry; + + sb->s_magic = FS_MAGIC; + sb->s_op = &fs_ops; + + root_inode = fs_make_inode(sb, S_IFDIR | 0755); + if (!root_inode) + return -ENOMEM; + + root_dentry = d_make_root(root_inode); + if (!root_dentry) + return -ENOMEM; + + sb->s_root = root_dentry; + + books_inode = fs_make_inode(sb, S_IFDIR | BOOKS_MODE); + if (!books_inode) + return -ENOMEM; + books_inode->i_op = &simple_dir_inode_operations; + books_inode->i_fop = &simple_dir_operations; + books_inode->i_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + books_dentry = d_alloc_name(root_dentry, "books"); + if (!books_dentry) + return -ENOMEM; + d_add(books_dentry, books_inode); + + authors_inode = fs_make_inode(sb, S_IFDIR | AUTHORS_MODE); + if (!authors_inode) + return -ENOMEM; + authors_dentry = d_alloc_name(root_dentry, "authors"); + if (!authors_dentry) + return -ENOMEM; + authors_inode->i_op = &author_subdir_iops; + d_add(authors_dentry, authors_inode); + + return 0; +} + +static struct dentry *fs_mount(struct file_system_type *type, int flags, + const char *dev, void *data) { + return mount_nodev(type, flags, data, fs_fill_super); +} + +static struct file_system_type fs_type = { + .owner = THIS_MODULE, + .name = "customfs", + .mount = fs_mount, + .kill_sb = kill_litter_super, +}; + +static int __init fs_init(void) { + hash_init(link_table); + return register_filesystem(&fs_type); +} + +static void __exit fs_exit(void) { + unregister_filesystem(&fs_type); +} + +module_init(fs_init); +module_exit(fs_exit); + diff --git a/statsfs.c b/statsfs.c deleted file mode 100644 index 0fb926b..0000000 --- a/statsfs.c +++ /dev/null @@ -1,360 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include /* для iov_iter_count() */ - -#define LIBFS_MAGIC 0xA0B0C0D0 -#define LIBFS_BOOKS_MODE 01777 -#define LIBFS_AUTHORS_MODE 0777 -#define LIBFS_SUBDIR_MODE 01777 - -static struct super_block *libfs_sb; -static struct inode *books_inode; -static struct inode *authors_inode; -static struct dentry *authors_dentry; - -struct link_entry { - struct hlist_node node; - struct dentry *link; -}; - -static void libfs_evict_inode(struct inode *inode); -static int libfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl); -static int libfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode); -static int libfs_unlink(struct inode *dir, struct dentry *dentry); -static int libfs_rmdir(struct inode *dir, struct dentry *dentry); -static int libfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, - struct dentry *old_dentry, struct inode *new_dir, - struct dentry *new_dentry, unsigned int flags); -static int libfs_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry); - -/* Обёртки с логированием */ -static ssize_t libfs_read_iter(struct kiocb *iocb, struct iov_iter *iter) -{ - struct inode *inode = file_inode(iocb->ki_filp); - pr_info("statsfs: READ ino#%lu pos=%lld len=%zu\n", - inode->i_ino, iocb->ki_pos, iov_iter_count(iter)); - return generic_file_read_iter(iocb, iter); -} - -static ssize_t libfs_write_iter(struct kiocb *iocb, struct iov_iter *iter) -{ - struct inode *inode = file_inode(iocb->ki_filp); - pr_info("statsfs: WRITE ino#%lu pos=%lld len=%zu\n", - inode->i_ino, iocb->ki_pos, iov_iter_count(iter)); - return generic_file_write_iter(iocb, iter); -} - -static const struct file_operations libfs_file_ops = { - .read_iter = libfs_read_iter, - .write_iter = libfs_write_iter, - .llseek = generic_file_llseek, -}; - -static const struct inode_operations libfs_file_iops = { - .setattr = simple_setattr, - .getattr = simple_getattr, -}; - -static const struct inode_operations libfs_dir_iops = { - .lookup = simple_lookup, - .create = libfs_create, - .unlink = libfs_unlink, - .mkdir = libfs_mkdir, - .rmdir = libfs_rmdir, - .rename = libfs_rename, - .link = libfs_link, - .mknod = NULL, - .symlink = NULL, -}; - -static const struct super_operations statsfs_sops = { - .statfs = simple_statfs, - .drop_inode = generic_delete_inode, - .evict_inode = libfs_evict_inode, -}; - -static struct inode *libfs_get_inode(struct super_block *sb, umode_t mode) -{ - struct inode *inode; - pr_info("statsfs: libfs_get_inode(sb=%p, mode=%o)\n", sb, mode); - - inode = new_inode(sb); - if (!inode) { - pr_err("statsfs: new_inode failed for mode %o\n", mode); - return NULL; - } - inode->i_ino = get_next_ino(); - inode->i_sb = sb; - inode->i_mode = mode; - - if (S_ISDIR(mode)) { - inode->i_op = &libfs_dir_iops; - inode->i_fop = &simple_dir_operations; - pr_info("statsfs: created dir inode #%lu\n", inode->i_ino); - } else { - inode->i_op = &libfs_file_iops; - inode->i_fop = &libfs_file_ops; - inode->i_private = kzalloc(sizeof(struct hlist_head), GFP_KERNEL); - if (!inode->i_private) { - pr_err("statsfs: kzalloc for inode #%lu failed\n", inode->i_ino); - iput(inode); - return NULL; - } - INIT_HLIST_HEAD((struct hlist_head *)inode->i_private); - pr_info("statsfs: created file inode #%lu with private list\n", - inode->i_ino); - } - return inode; -} - -static void cleanup_links(struct inode *victim) -{ - struct hlist_head *list; - struct link_entry *le; - struct hlist_node *tmp; - - if (!S_ISREG(victim->i_mode) || !victim->i_private) - return; - - pr_info("statsfs: cleanup_links for inode #%lu\n", victim->i_ino); - list = (struct hlist_head *)victim->i_private; - - hlist_for_each_entry_safe(le, tmp, list, node) { - struct dentry *link = le->link; - if (!link || d_unlinked(link)) { - pr_info("statsfs: link gone, freeing entry\n"); - hlist_del(&le->node); - dput(link); - kfree(le); - continue; - } - - { - struct dentry *pd = dget_parent(link); - struct inode *parent; - if (pd) { - parent = d_inode(pd); - if (WARN_ON(!parent)) - goto skip_unlink; - inode_lock(parent); - vfs_unlink(NULL, parent, link, NULL); - inode_unlock(parent); - pr_info("statsfs: unlinked %pd from parent inode #%lu\n", - link, parent->i_ino); - dput(pd); - } - } - skip_unlink: - hlist_del(&le->node); - dput(link); - kfree(le); - } - - kfree(victim->i_private); - victim->i_private = NULL; -} - -static bool is_protected(const struct dentry *d) -{ - return !strcmp(d->d_name.name, "books") || - !strcmp(d->d_name.name, "authors"); -} - -static int libfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode, bool excl) -{ - struct inode *inode; - pr_info("statsfs: CREATE %pd in dir inode #%lu mode=%o\n", - dentry, dir->i_ino, mode); - if (dir != books_inode) - return -EPERM; - - inode = libfs_get_inode(dir->i_sb, S_IFREG | mode); - if (!inode) - return -ENOMEM; - - inode->i_uid = current_fsuid(); - inode->i_gid = current_fsgid(); - d_add(dentry, inode); - pr_info("statsfs: added file %pd as inode #%lu\n", - dentry, inode->i_ino); - return 0; -} - -static int libfs_unlink(struct inode *dir, struct dentry *dentry) -{ - struct inode *victim = d_inode(dentry); - int err; - pr_info("statsfs: UNLINK %pd from dir inode #%lu\n", - dentry, dir->i_ino); - if (dir == libfs_sb->s_root->d_inode && is_protected(dentry)) - return -EPERM; - - err = simple_unlink(dir, dentry); - if (!err && dir == books_inode) - cleanup_links(victim); - return err; -} - -static int libfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode) -{ - struct inode *inode; - pr_info("statsfs: MKDIR %pd in dir inode #%lu\n", - dentry, dir->i_ino); - if (dir == libfs_sb->s_root->d_inode || dir == books_inode) - return -EPERM; - if (dir != authors_inode) - return -EPERM; - - inode = libfs_get_inode(dir->i_sb, S_IFDIR | LIBFS_SUBDIR_MODE); - if (!inode) - return -ENOMEM; - - inode->i_uid = current_fsuid(); - d_add(dentry, inode); - inc_nlink(dir); - pr_info("statsfs: created subdir %pd as inode #%lu\n", - dentry, inode->i_ino); - return 0; -} - -static int libfs_rmdir(struct inode *dir, struct dentry *dentry) -{ - pr_info("statsfs: RMDIR %pd in dir inode #%lu\n", - dentry, dir->i_ino); - if (dir == libfs_sb->s_root->d_inode && is_protected(dentry)) - return -EPERM; - if (dir == authors_inode) - return -EPERM; - return simple_rmdir(dir, dentry); -} - -static int libfs_rename(struct mnt_idmap *idmap, struct inode *odir, - struct dentry *odent, struct inode *ndir, - struct dentry *ndent, unsigned int flags) -{ - pr_info("statsfs: RENAME %pd(ino#%lu) -> %pd(ino#%lu)\n", - odent, odir->i_ino, ndent, ndir->i_ino); - if ((odir == libfs_sb->s_root->d_inode || ndir == libfs_sb->s_root->d_inode) && - (is_protected(odent) || is_protected(ndent))) - return -EPERM; - if (odir == authors_inode || ndir == authors_inode) - return -EPERM; - return simple_rename(idmap, odir, odent, ndir, ndent, flags); -} - -static int libfs_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) -{ - struct inode *t = d_inode(old_dentry); - struct link_entry *le; - int err; - pr_info("statsfs: LINK %pd -> %pd in dir inode #%lu\n", - old_dentry, new_dentry, dir->i_ino); - if (!S_ISREG(t->i_mode)) - return -EPERM; - if (!authors_dentry || - new_dentry->d_parent->d_parent != authors_dentry) - return -EPERM; - - err = simple_link(old_dentry, dir, new_dentry); - if (err) - return err; - - le = kmalloc(sizeof(*le), GFP_KERNEL); - if (!le) - return -ENOMEM; - - le->link = dget(new_dentry); - hlist_add_head(&le->node, (struct hlist_head *)t->i_private); - pr_info("statsfs: recorded link entry for inode #%lu\n", t->i_ino); - return 0; -} - -static int libfs_fill_super(struct super_block *sb, void *data, int silent) -{ - struct inode *root; - pr_info("statsfs: fill_super\n"); - libfs_sb = sb; - sb->s_magic = LIBFS_MAGIC; - sb->s_op = &statsfs_sops; - - root = libfs_get_inode(sb, S_IFDIR | 0755); - if (!root) - return -ENOMEM; - - root->i_op = &libfs_dir_iops; - root->i_fop = &simple_dir_operations; - sb->s_root = d_make_root(root); - if (!sb->s_root) - return -ENOMEM; - - books_inode = libfs_get_inode(sb, S_IFDIR | LIBFS_BOOKS_MODE); - d_add(d_alloc_name(sb->s_root, "books"), books_inode); - - authors_inode = libfs_get_inode(sb, S_IFDIR | LIBFS_AUTHORS_MODE); - authors_dentry = d_alloc_name(sb->s_root, "authors"); - d_add(authors_dentry, authors_inode); - - pr_info("statsfs: superblock initialized\n"); - return 0; -} - -static struct dentry *libfs_mount(struct file_system_type *fs, - int flags, const char *dev, void *data) -{ - pr_info("statsfs: mounting\n"); - return mount_nodev(fs, flags, data, libfs_fill_super); -} - -static void libfs_evict_inode(struct inode *inode) -{ - pr_info("statsfs: evict_inode ino#%lu\n", inode->i_ino); - truncate_inode_pages_final(&inode->i_data); - cleanup_links(inode); - clear_inode(inode); -} - -static struct file_system_type libfs_type = { - .owner = THIS_MODULE, - .name = "statsfs", - .mount = libfs_mount, - .kill_sb = kill_litter_super, -}; - -static int __init libfs_init(void) -{ - pr_info("statsfs: module init\n"); - return register_filesystem(&libfs_type); -} - -static void __exit libfs_exit(void) -{ - pr_info("statsfs: module exit\n"); - dput(authors_dentry); - cleanup_links(books_inode); - iput(books_inode); - iput(authors_inode); - unregister_filesystem(&libfs_type); -} - -module_init(libfs_init); -module_exit(libfs_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("you"); -MODULE_DESCRIPTION("Custom library FS with books and authors (with debug logs)"); \ No newline at end of file