diff --git a/libfs.c b/libfs.c index d68f04f..1524cc7 100644 --- a/libfs.c +++ b/libfs.c @@ -1,27 +1,27 @@ -#include #include #include +#include #include -#include #include +#include +#include #include -#include -#include #include -#include -#include +#include +#define FS_NAME "customfs" #define FS_MAGIC 0x20240615 -#define BOOKS_MODE 01777 -#define AUTHORS_MODE 0777 -#define AUTHOR_SUBDIR_MODE 01777 +#define MODE_BOOKS 01777 +#define MODE_AUTHORS 0777 +#define MODE_AUTHOR_SUBDIR 01777 MODULE_LICENSE("GPL"); -MODULE_AUTHOR("user"); -MODULE_DESCRIPTION("Custom FS with books and authors directories"); +MODULE_AUTHOR("you"); +MODULE_DESCRIPTION("Custom FS with books/authors behavior"); +static struct super_block *global_sb; static struct inode *books_inode; -static struct inode *authors_inode; +DEFINE_HASHTABLE(link_table, 6); struct link_entry { struct hlist_node node; @@ -29,109 +29,31 @@ struct link_entry { struct inode *target; }; -DEFINE_HASHTABLE(link_table, 6); - -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) { - printk(KERN_INFO "customfs: removing hardlink to inode %lu\n", inode->i_ino); - 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 int fs_statfs(struct dentry *dentry, struct kstatfs *buf) { - buf->f_type = FS_MAGIC; +static ssize_t dummy_read(struct file *file, char __user *buf, size_t len, loff_t *ppos) { return 0; } -static const struct super_operations fs_ops = { - .evict_inode = fs_evict_inode, - .statfs = fs_statfs, -}; - -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 dummy_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { + return len; } -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 file_operations custom_file_ops = { + .read = dummy_read, + .write = dummy_write, + .llseek = noop_llseek, }; -static const struct inode_operations books_file_iops = { - .getattr = simple_getattr, - .setattr = simple_setattr, -}; - -static const struct inode_operations author_subdir_iops; - -static int author_mkdir(struct user_namespace *ns, struct inode *dir, struct dentry *dentry, umode_t mode) { - struct inode *inode = new_inode(dir->i_sb); - if (!inode) - return -ENOMEM; - inode->i_ino = get_next_ino(); - inode_init_owner(&init_user_ns, inode, dir, S_IFDIR | AUTHOR_SUBDIR_MODE); - inode->i_op = &author_subdir_iops; - inode->i_fop = &simple_dir_operations; - inc_nlink(inode); - inc_nlink(dir); - d_add(dentry, inode); - printk(KERN_INFO "customfs: created author subdir '%s'\n", dentry->d_name.name); - return 0; +static bool is_protected_dentry(struct dentry *d) { + return strcmp(d->d_name.name, "books") == 0 || strcmp(d->d_name.name, "authors") == 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); - printk(KERN_INFO "customfs: created hardlink '%s' to inode %lu\n", - new_dentry->d_name.name, inode->i_ino); - } - } - return err; -} - -static const struct inode_operations author_subdir_iops = { - .lookup = simple_lookup, - .mkdir = author_mkdir, - .rmdir = simple_rmdir, - .link = author_link, -}; - -static struct inode *fs_make_inode(struct super_block *sb, umode_t mode) { +static struct inode *get_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_ino = get_next_ino(); inode->i_sb = sb; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); @@ -140,100 +62,178 @@ static struct inode *fs_make_inode(struct super_block *sb, umode_t mode) { inode->i_fop = &simple_dir_operations; inc_nlink(inode); } 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); + inode->i_fop = &custom_file_ops; } 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; +static int books_create(struct user_namespace *ns, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) { + struct inode *inode = get_inode(dir->i_sb, S_IFREG | mode); + if (!inode) + return -ENOMEM; - sb->s_magic = FS_MAGIC; - sb->s_op = &fs_ops; + inode->i_uid = current_fsuid(); + d_add(dentry, inode); + return 0; +} - root_inode = fs_make_inode(sb, S_IFDIR | 0755); +static const struct inode_operations books_dir_iops = { + .lookup = simple_lookup, + .create = books_create, +}; + +static void remove_links_to_inode(struct inode *inode) { + int bkt; + struct link_entry *entry; + struct hlist_node *tmp; + hash_for_each_safe(link_table, bkt, tmp, entry, node) { + if (entry->target == inode) { + d_delete(entry->link); + dput(entry->link); + hash_del(&entry->node); + kfree(entry); + printk(KERN_INFO "customfs: removed link to inode %lu\n", inode->i_ino); + } + } +} + +static int custom_mkdir(struct user_namespace *ns, struct inode *dir, struct dentry *dentry, umode_t mode) { + if (dir == global_sb->s_root->d_inode) + return -EPERM; + + if (S_ISDIR(dir->i_mode)) { + umode_t mode_only = dir->i_mode & 07777; + if ((mode_only == MODE_AUTHORS || mode_only == MODE_AUTHOR_SUBDIR) && + !uid_eq(current_fsuid(), dir->i_uid)) + return -EACCES; + } + + struct inode *inode = get_inode(dir->i_sb, S_IFDIR | MODE_AUTHOR_SUBDIR); + if (!inode) + return -ENOMEM; + + inode->i_uid = current_fsuid(); + d_add(dentry, inode); + inc_nlink(dir); + return 0; +} + +static int custom_unlink(struct inode *dir, struct dentry *dentry) { + if (dir == global_sb->s_root->d_inode && is_protected_dentry(dentry)) + return -EPERM; + return simple_unlink(dir, dentry); +} + +static int custom_rmdir(struct inode *dir, struct dentry *dentry) { + if (dir == global_sb->s_root->d_inode && is_protected_dentry(dentry)) + return -EPERM; + return simple_rmdir(dir, dentry); +} + +static int custom_rename(struct user_namespace *ns, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) { + if ((old_dir == global_sb->s_root->d_inode || new_dir == global_sb->s_root->d_inode) && + (is_protected_dentry(old_dentry) || is_protected_dentry(new_dentry))) + return -EPERM; + return simple_rename(ns, old_dir, old_dentry, new_dir, new_dentry, flags); +} + +static int custom_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { + if (dir->i_sb != old_dentry->d_inode->i_sb) + return -EXDEV; + if (!S_ISREG(old_dentry->d_inode->i_mode)) + return -EPERM; + + int r = simple_link(old_dentry, dir, new_dentry); + if (r == 0) { + struct link_entry *e = kmalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + e->target = d_inode(old_dentry); + e->link = dget(new_dentry); + hash_add(link_table, &e->node, e->target->i_ino); + } + return r; +} + +static const struct inode_operations fs_dir_iops = { + .lookup = simple_lookup, + .mkdir = custom_mkdir, + .unlink = custom_unlink, + .rmdir = custom_rmdir, + .rename = custom_rename, + .link = custom_link, +}; + +static void evict_inode(struct inode *inode) { + if (inode->i_sb == global_sb && inode->i_ino && S_ISREG(inode->i_mode)) + remove_links_to_inode(inode); + clear_inode(inode); +} + +static const struct super_operations fs_sops = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, + .evict_inode = evict_inode, +}; + +static int fill_super(struct super_block *sb, void *data, int silent) { + struct inode *root_inode = get_inode(sb, S_IFDIR | 0755); if (!root_inode) return -ENOMEM; - root_dentry = d_make_root(root_inode); + struct dentry *root_dentry = d_make_root(root_inode); if (!root_dentry) return -ENOMEM; - books_inode = fs_make_inode(sb, S_IFDIR | BOOKS_MODE); - if (!books_inode) - goto out_root; - books_inode->i_op = &simple_dir_inode_operations; - books_inode->i_fop = &simple_dir_operations; - inc_nlink(books_inode); - books_dentry = d_alloc_name(root_dentry, "books"); - if (!books_dentry) - goto out_books; - d_add(books_dentry, books_inode); - d_rehash(books_dentry); - printk(KERN_INFO "customfs: 'books' directory created\n"); - - authors_inode = fs_make_inode(sb, S_IFDIR | AUTHORS_MODE); - if (!authors_inode) - goto out_books_dentry; - authors_inode->i_op = &author_subdir_iops; - inc_nlink(authors_inode); - authors_dentry = d_alloc_name(root_dentry, "authors"); - if (!authors_dentry) - goto out_authors; - d_add(authors_dentry, authors_inode); - d_rehash(authors_dentry); - printk(KERN_INFO "customfs: 'authors' directory created\n"); - sb->s_root = root_dentry; + sb->s_magic = FS_MAGIC; + sb->s_op = &fs_sops; + global_sb = sb; - printk(KERN_INFO "customfs: fill_super completed\n"); - printk(KERN_INFO "customfs: root inode=%lu\n", root_inode->i_ino); - printk(KERN_INFO "customfs: books inode=%lu\n", books_inode->i_ino); - printk(KERN_INFO "customfs: authors inode=%lu\n", authors_inode->i_ino); + // books + books_inode = get_inode(sb, S_IFDIR | MODE_BOOKS); + if (!books_inode) + return -ENOMEM; + books_inode->i_op = &books_dir_iops; + books_inode->i_fop = &simple_dir_operations; + struct dentry *books = d_alloc_name(root_dentry, "books"); + d_add(books, books_inode); + + // authors + struct inode *authors_inode = get_inode(sb, S_IFDIR | MODE_AUTHORS); + if (!authors_inode) + return -ENOMEM; + authors_inode->i_op = &fs_dir_iops; + authors_inode->i_fop = &simple_dir_operations; + struct dentry *authors = d_alloc_name(root_dentry, "authors"); + d_add(authors, authors_inode); return 0; - -out_authors: - iput(authors_inode); -out_books_dentry: - dput(books_dentry); -out_books: - iput(books_inode); -out_root: - dput(root_dentry); - iput(root_inode); - return -ENOMEM; } -static struct dentry *fs_mount(struct file_system_type *type, int flags, - const char *dev, void *data) { - printk(KERN_INFO "customfs: mounting\n"); - return mount_nodev(type, flags, data, fs_fill_super); +static struct dentry *mount_fs(struct file_system_type *type, int flags, const char *dev, void *data) { + return mount_nodev(type, flags, data, fill_super); } static struct file_system_type fs_type = { .owner = THIS_MODULE, - .name = "customfs", - .mount = fs_mount, + .name = FS_NAME, + .mount = mount_fs, .kill_sb = kill_litter_super, }; static int __init fs_init(void) { hash_init(link_table); - printk(KERN_INFO "customfs: module loaded\n"); return register_filesystem(&fs_type); } static void __exit fs_exit(void) { unregister_filesystem(&fs_type); - printk(KERN_INFO "customfs: module unloaded\n"); } module_init(fs_init); module_exit(fs_exit); - diff --git a/t.bash b/t.bash new file mode 100755 index 0000000..d9edcf6 --- /dev/null +++ b/t.bash @@ -0,0 +1,14 @@ +#!/bin/bash + +# создать файл +echo test > /mnt/customfs/books/hello.txt + +# создать подкаталог +mkdir /mnt/customfs/authors/kirill + +# создать жёсткую ссылку +ln /mnt/customfs/books/hello.txt /mnt/customfs/authors/kirill/hlink + +# удалить исходный файл +rm /mnt/customfs/books/hello.txt +# => ссылка в authors также исчезнет