#include #include #include #include #include #include #include #include #include #include #include #include #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 const struct file_operations libfs_file_ops = { .read_iter = generic_file_read_iter, .write_iter = generic_file_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; struct dentry *pd; struct inode *parent; if (!link || d_unlinked(link)) { pr_info("statsfs: link already gone, freeing entry\n"); hlist_del(&le->node); dput(link); kfree(le); continue; } pd = dget_parent(link); 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 inode #%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 = "libfs", .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)");