diff --git a/restart.bash b/restart.bash new file mode 100755 index 0000000..c66d7c5 --- /dev/null +++ b/restart.bash @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +# Убить все процессы, использующие /mnt/test +sudo fuser -km /mnt/test || true + +# Подождать, пока умрут +sleep 0.5 + +# Умонтировать +sudo umount /mnt/test || true + +# Выгрузить модуль +sudo rmmod statsfs || true + +# Подождать, пока освободится +sleep 0.5 + +# Вставить модуль и смонтировать +sudo insmod statsfs.ko +sudo mount -t libfs none /mnt/test diff --git a/statsfs.c b/statsfs.c index cf28340..06229c1 100644 --- a/statsfs.c +++ b/statsfs.c @@ -9,188 +9,332 @@ #include #include #include +#include +#include +#include +#include +#include +#include -#define LIBFS_MAGIC 0xA0B0C0D0 -#define LIBFS_BOOKS_MODE 01777 +#define LIBFS_MAGIC 0xA0B0C0D0 +#define LIBFS_BOOKS_MODE 01777 #define LIBFS_AUTHORS_MODE 0777 -#define LIBFS_SUBDIR_MODE 01777 +#define LIBFS_SUBDIR_MODE 01777 static struct super_block *libfs_sb; -static struct inode *books_inode = NULL; -static struct vfsmount *libfs_mnt; +static struct inode *books_inode; +static struct inode *authors_inode; +static struct dentry *authors_dentry; + +static void libfs_evict_inode(struct inode *inode); + +struct link_entry { + struct hlist_node node; + struct dentry *link; +}; + +/* запрет создания FIFO/устройств */ +static int libfs_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) +{ + return -EPERM; +} + +/* запрет симв. ссылок */ +static int libfs_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *target) +{ + return -EPERM; +} + +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 super_operations libfs_sops = { - .statfs = simple_statfs, - .drop_inode = generic_delete_inode, + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, + .evict_inode = libfs_evict_inode, }; static const struct file_operations libfs_file_ops = { - .llseek = no_llseek, - .read = NULL, - .write = NULL, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .llseek = generic_file_llseek, }; -static struct inode *libfs_get_inode(struct super_block *sb, umode_t mode) { - struct inode *inode = new_inode(sb); - if (!inode) return NULL; +static const struct inode_operations libfs_dir_iops = { + .lookup = simple_lookup, + .mkdir = libfs_mkdir, + .mknod = libfs_mknod, + .symlink = libfs_symlink, + .unlink = libfs_unlink, + .rmdir = libfs_rmdir, + .rename = libfs_rename, + .link = libfs_link, + .create = libfs_create, +}; + +static struct inode *libfs_get_inode(struct super_block *sb, umode_t mode) +{ + struct inode *inode; + + if (!S_ISREG(mode) && !S_ISDIR(mode)) + return NULL; + + inode = new_inode(sb); + if (!inode) + return NULL; + + inode->i_ino = get_next_ino(); + inode->i_sb = sb; + inode->i_mode = mode; - inode->i_ino = get_next_ino(); - inode->i_sb = sb; - inode->i_op = &simple_dir_inode_operations; if (S_ISDIR(mode)) { + inode->i_op = &libfs_dir_iops; inode->i_fop = &simple_dir_operations; } else { - inode->i_fop = &libfs_file_ops; + /* файл — просто generic_file_* */ + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &libfs_file_ops; + /* заводим список жёстких ссылок */ + inode->i_private = kzalloc(sizeof(struct hlist_head), GFP_KERNEL); + if (!inode->i_private) { + iput(inode); + return NULL; + } + INIT_HLIST_HEAD((struct hlist_head *)inode->i_private); } - inode->i_mode = mode; return inode; } -static bool is_protected_dentry(struct dentry *dentry) { - return (strcmp(dentry->d_name.name, "books") == 0 || strcmp(dentry->d_name.name, "authors") == 0); -} - -struct cleanup_ctx { - struct inode *victim; -}; - -static void libfs_cleanup_links(struct inode *victim) +static void cleanup_links(struct inode *victim) { - struct dentry *root = libfs_sb->s_root; + struct hlist_head *list; + struct link_entry *le; + struct hlist_node *tmp; - struct qstr authors_q = QSTR_INIT("authors", 7); - struct dentry *authors = d_lookup(root, &authors_q); - if (!authors || d_really_is_negative(authors)) { - dput(authors); + if (!S_ISREG(victim->i_mode) || !victim->i_private) return; + + 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)) { + hlist_del(&le->node); + dput(link); + kfree(le); + continue; + } + + { + struct dentry *pd = dget_parent(link); + if (pd) { + struct inode *parent = d_inode(pd); + if (parent) { + inode_lock(parent); + vfs_unlink(NULL, parent, link, NULL); + inode_unlock(parent); + } + dput(pd); + } + } + + hlist_del(&le->node); + dput(link); + kfree(le); } - // Здесь конкретный пример: если ты сам знаешь имя линка на victim - // То можешь через d_lookup() найти его и удалить - struct qstr filename = QSTR_INIT("some_symlink", 12); - struct dentry *maybe_link = d_lookup(authors, &filename); - - if (maybe_link && maybe_link->d_inode == victim) { - inode_lock(authors->d_inode); - vfs_unlink(NULL, authors->d_inode, maybe_link, NULL); - inode_unlock(authors->d_inode); - } - - dput(maybe_link); - dput(authors); + kfree(victim->i_private); + victim->i_private = NULL; } -static int libfs_unlink(struct inode *dir, struct dentry *dentry) { - if (dir == libfs_sb->s_root->d_inode && is_protected_dentry(dentry)) +static bool is_protected(struct dentry *dentry) +{ + return strcmp(dentry->d_name.name, "books") == 0 + || strcmp(dentry->d_name.name, "authors") == 0; +} + +static int libfs_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *victim = d_inode(dentry); + int ret; + + if (dir == libfs_sb->s_root->d_inode && is_protected(dentry)) return -EPERM; - struct inode *victim = dentry->d_inode; - int ret = simple_unlink(dir, dentry); - if (ret == 0 && dir == books_inode) { - libfs_cleanup_links(victim); - } + ret = simple_unlink(dir, dentry); + if (ret == 0 && dir == books_inode) + cleanup_links(victim); return ret; } -static int libfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { - if (dir == libfs_sb->s_root->d_inode) { +static int libfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + struct inode *inode; + + if (dir == libfs_sb->s_root->d_inode || dir == books_inode) return -EPERM; + + if (dir == authors_inode) { + 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); + return 0; } - struct inode *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); - return 0; + + return -EPERM; } -static int libfs_rmdir(struct inode *dir, struct dentry *dentry) { - if (dir == libfs_sb->s_root->d_inode && is_protected_dentry(dentry)) +static int libfs_rmdir(struct inode *dir, struct dentry *dentry) +{ + 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 *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { - if ((old_dir == libfs_sb->s_root->d_inode || new_dir == libfs_sb->s_root->d_inode) && - (is_protected_dentry(old_dentry) || is_protected_dentry(new_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) +{ + if ((old_dir == libfs_sb->s_root->d_inode + || new_dir == libfs_sb->s_root->d_inode) + && (is_protected(old_dentry) || is_protected(new_dentry))) + return -EPERM; + if (old_dir == authors_inode || new_dir == authors_inode) return -EPERM; return simple_rename(idmap, old_dir, old_dentry, new_dir, new_dentry, flags); } -static int libfs_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)) +static int libfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + struct inode *target = d_inode(old_dentry); + struct hlist_head *list; + struct link_entry *le; + int ret; + + if (!S_ISREG(target->i_mode)) + return -EPERM; + if (!authors_dentry + || new_dentry->d_parent->d_parent != authors_dentry) return -EPERM; - return simple_link(old_dentry, dir, new_dentry); -} -static const struct inode_operations libfs_dir_iops = { - .lookup = simple_lookup, - .mkdir = libfs_mkdir, - .unlink = libfs_unlink, - .rmdir = libfs_rmdir, - .rename = libfs_rename, - .link = libfs_link, -}; + ret = simple_link(old_dentry, dir, new_dentry); + if (ret) + return ret; -static int libfs_fill_super(struct super_block *sb, void *data, int silent) { - struct inode *inode; - struct dentry *root; - libfs_sb = sb; - sb->s_magic = LIBFS_MAGIC; - sb->s_op = &libfs_sops; - - inode = libfs_get_inode(sb, S_IFDIR | 0755); - if (!inode) + list = (struct hlist_head *)target->i_private; + le = kmalloc(sizeof(*le), GFP_KERNEL); + if (!le) return -ENOMEM; - inode->i_op = &libfs_dir_iops; - inode->i_fop = &simple_dir_operations; - - root = d_make_root(inode); - if (!root) - return -ENOMEM; - sb->s_root = root; - - // /books - { - struct inode *books = libfs_get_inode(sb, S_IFDIR | LIBFS_BOOKS_MODE); - books_inode = books; - struct dentry *d = d_alloc_name(root, "books"); - d_add(d, books); - } - // /authors - { - struct inode *authors = libfs_get_inode(sb, S_IFDIR | LIBFS_AUTHORS_MODE); - struct dentry *d = d_alloc_name(root, "authors"); - d_add(d, authors); - } + le->link = dget(new_dentry); + hlist_add_head(&le->node, list); return 0; } -static struct dentry *libfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { +static int libfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +{ + struct inode *inode; + + 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); + return 0; +} + +static int libfs_fill_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root; + + libfs_sb = sb; + sb->s_magic = LIBFS_MAGIC; + sb->s_op = &libfs_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); + { + struct dentry *d = d_alloc_name(sb->s_root, "books"); + d_add(d, 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); + + return 0; +} + +static struct dentry *libfs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ return mount_nodev(fs_type, flags, data, libfs_fill_super); } +static void libfs_evict_inode(struct inode *inode) +{ + 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, + .owner = THIS_MODULE, + .name = "libfs", + .mount = libfs_mount, .kill_sb = kill_litter_super, }; -static int __init libfs_init(void) { - int ret = register_filesystem(&libfs_type); - if (ret) - return ret; - libfs_mnt = kern_mount(&libfs_type); - return PTR_ERR_OR_ZERO(libfs_mnt); +static int __init libfs_init(void) +{ + return register_filesystem(&libfs_type); } -static void __exit libfs_exit(void) { - kern_unmount(libfs_mnt); +static void __exit libfs_exit(void) +{ + if (authors_dentry) { + dput(authors_dentry); + authors_dentry = NULL; + } + if (books_inode) { + cleanup_links(books_inode); + iput(books_inode); + books_inode = NULL; + } + if (authors_inode) { + iput(authors_inode); + authors_inode = NULL; + } unregister_filesystem(&libfs_type); } @@ -199,4 +343,4 @@ module_exit(libfs_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("you"); -MODULE_DESCRIPTION("Custom library FS with books and authors"); +MODULE_DESCRIPTION("Custom library FS with books and authors"); \ No newline at end of file diff --git a/test.bash b/test.bash new file mode 100755 index 0000000..4f0f8ec --- /dev/null +++ b/test.bash @@ -0,0 +1,85 @@ +#!/bin/bash + +mnt=/mnt/test + +sudo mount -t libfs none "$mnt" + +fail() { + echo "FAIL: $1" >&2 + exit 1 +} + +check_mode() { + [ "$(stat -c '%a' "$1")" = "$2" ] || fail "$1 mode != $2" +} + +check_owner() { + [ "$(stat -c '%U' "$1")" = "$2" ] || fail "$1 owner != $2" +} + +check_fail() { + set +e + "$@" + status=$? + set -e + [ "$status" -ne 0 ] || fail "Expected failure: $*" +} + + +# 2. Права на директории +check_mode "$mnt/books" 1777 +check_mode "$mnt/authors" 777 + +# 3. Удаление/переименование books +check_fail rmdir "$mnt/books" +check_fail mv "$mnt/books" "$mnt/b" + +# 4. Создание файла +sudo touch "$mnt/books/f1" +echo "data" | sudo tee "$mnt/books/f1" > /dev/null +[ "$(cat "$mnt/books/f1")" = "data" ] || fail "f1 content mismatch" + +# 5. mkdir и mkfifo в books +check_fail mkdir "$mnt/books/dir" +check_fail mkfifo "$mnt/books/fifo" + +# 6. mkdir в authors +mkdir "$mnt/authors/u1" +check_mode "$mnt/authors/u1" 1777 + +# 7. Переименование и удаление чужого каталога +check_fail mv "$mnt/authors/u1" "$mnt/authors/u1-renamed" +check_fail rmdir "$mnt/authors/u1-renamed" + +# 8. ссылки в authors +ln "$mnt/books/f1" "$mnt/authors/u1/link1" +check_fail ln -s "$mnt/books/f1" "$mnt/authors/u1/symlink" +check_fail touch "$mnt/authors/u1/new.txt" +check_fail mkdir "$mnt/authors/u1/dir1" + +# 9. удаление файла — должна исчезнуть ссылка +sudo rm "$mnt/books/f1" +[ ! -e "$mnt/authors/u1/link1" ] || fail "link1 not removed after f1 deletion" + +# 10. пересоздание f1 — ссылка не должна появиться +sudo touch "$mnt/books/f1" +[ ! -e "$mnt/authors/u1/link1" ] || fail "link1 appeared after f1 recreated" + +# 11. удалить только ссылку +sudo touch "$mnt/books/f2" +ln "$mnt/books/f2" "$mnt/authors/u1/link2" +rm "$mnt/authors/u1/link2" +[ -e "$mnt/books/f2" ] || fail "f2 removed with link2" + +# 12. удалить файл после удаления ссылки +ln "$mnt/books/f2" "$mnt/authors/u1/link2" +rm "$mnt/authors/u1/link2" +sudo rm "$mnt/books/f2" + +# 13. remount check +sudo umount "$mnt" +sudo mount -t libfs none "$mnt" +check_mode "$mnt/books" 1777 +check_mode "$mnt/authors" 777 + +echo "All tests passed." \ No newline at end of file