FS/statsfs.c
2025-06-17 18:58:40 +00:00

360 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/namei.h>
#include <linux/mount.h>
#include <linux/dcache.h>
#include <linux/list.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/printk.h>
#include <linux/uio.h> /* для 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)");