#include "internal.h"
#include <linux/module.h>
#include <linux/magic.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/namei.h>
#include <linux/seq_file.h>
#include <linux/statfs.h>
#include <linux/delay.h>
#include <linux/string.h>
#define CREATE_TRACE_POINTS
#include <trace/events/mfs.h>
* Used for alloc_inode
*/
static struct kmem_cache *mfs_inode_cachep;
* Used for dentry info
*/
static struct kmem_cache *mfs_dentry_cachep;
static void mfs_init_once(void *obj)
{
struct mfs_inode *i = obj;
inode_init_once(&i->vfs_inode);
}
static struct inode *mfs_alloc_inode(struct super_block *sb)
{
struct mfs_inode *vi = alloc_inode_sb(sb, mfs_inode_cachep, GFP_KERNEL);
if (!vi)
return NULL;
memset(vi, 0, offsetof(struct mfs_inode, vfs_inode));
mutex_init(&vi->lock);
return &vi->vfs_inode;
}
static void mfs_free_inode(struct inode *inode)
{
struct mfs_inode *vi = MFS_I(inode);
kmem_cache_free(mfs_inode_cachep, vi);
}
static void mfs_evict_inode(struct inode *inode)
{
struct mfs_inode *vi = MFS_I(inode);
struct inode *lower_inode = vi->lower;
struct inode *cache_inode = vi->cache;
truncate_inode_pages_final(&inode->i_data);
if (inode->i_private)
mfs_free_object(inode->i_private);
clear_inode(inode);
if (lower_inode) {
vi->lower = NULL;
iput(lower_inode);
}
if (cache_inode) {
vi->cache = NULL;
iput(cache_inode);
}
}
int mfs_alloc_dentry_info(struct dentry *dentry)
{
struct mfs_dentry_info *info =
kmem_cache_zalloc(mfs_dentry_cachep, GFP_ATOMIC);
if (!info)
return -ENOMEM;
spin_lock_init(&info->lock);
dentry->d_fsdata = info;
return 0;
}
void mfs_free_dentry_info(struct dentry *dentry)
{
if (!dentry || !dentry->d_fsdata)
return;
kmem_cache_free(mfs_dentry_cachep, dentry->d_fsdata);
dentry->d_fsdata = NULL;
}
static void mfs_d_release(struct dentry *dentry)
{
if (!IS_ROOT(dentry))
mfs_release_path(dentry);
mfs_free_dentry_info(dentry);
}
static const struct dentry_operations mfs_dops = {
.d_release = mfs_d_release,
};
static int mfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct mfs_sb_info *sbi = MFS_SB(dentry->d_sb);
int err = vfs_statfs(&sbi->cache, buf);
buf->f_type = MFS_SUPER_MAGIC;
buf->f_spare[0] = sbi->minor;
return err;
}
static int mfs_show_options(struct seq_file *seq, struct dentry *root)
{
struct mfs_sb_info *sbi = MFS_SB(root->d_sb);
if (sbi->mtree)
seq_show_option(seq, "mtree", sbi->mtree);
if (sbi->cachedir)
seq_show_option(seq, "cachedir", sbi->cachedir);
switch (sbi->mode) {
case MFS_MODE_NONE:
seq_puts(seq, ",mode=none");
break;
case MFS_MODE_LOCAL:
seq_puts(seq, ",mode=local");
break;
case MFS_MODE_REMOTE:
seq_puts(seq, ",mode=remote");
break;
}
return 0;
}
static const struct super_operations mfs_sops = {
.alloc_inode = mfs_alloc_inode,
.free_inode = mfs_free_inode,
.drop_inode = generic_delete_inode,
.evict_inode = mfs_evict_inode,
.statfs = mfs_statfs,
.show_options = mfs_show_options,
};
enum {
Opt_mtree,
Opt_cachedir,
Opt_mode,
};
static const struct constant_table mfs_param_mode[] = {
{"none", MFS_MODE_NONE},
{"local", MFS_MODE_LOCAL},
{"remote", MFS_MODE_REMOTE},
{}
};
static const struct fs_parameter_spec mfs_fs_parameters[] = {
fsparam_string("mtree", Opt_mtree),
fsparam_string("cachedir", Opt_cachedir),
fsparam_enum("mode", Opt_mode, mfs_param_mode),
{}
};
static char *remove_trailing(char *s, char c)
{
size_t size;
char *end;
size = strlen(s);
if (!size)
return s;
end = s + size - 1;
while (end >= s && c == *end)
end--;
*(end + 1) = '\0';
return s;
}
static char *_acquire_set_path(char *inputpath, struct path *target)
{
char *p, *realp, *path;
char *res;
int ret = 0;
p = kstrdup(inputpath, GFP_KERNEL);
if (!p)
return ERR_PTR(-ENOMEM);
realp = remove_trailing(p, '/');
if (strlen(realp) == 0) {
kfree(p);
return ERR_PTR(-EINVAL);
}
ret = kern_path(realp, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, target);
kfree(p);
if (ret)
return ERR_PTR(ret);
path = kzalloc(PATH_MAX, GFP_KERNEL);
if (!path) {
path_put(target);
return ERR_PTR(-ENOMEM);
}
realp = d_path(target, path, PATH_MAX);
if (IS_ERR(realp)) {
path_put(target);
res = realp;
goto free;
}
res = kstrdup(realp, GFP_KERNEL);
if (!res) {
path_put(target);
res = ERR_PTR(-ENOMEM);
}
free:
kfree(path);
return res;
}
static int mfs_fc_parse_param(struct fs_context *fc,
struct fs_parameter *param)
{
struct mfs_sb_info *sbi = fc->s_fs_info;
struct fs_parse_result result;
struct path target;
char *p;
int opt;
opt = fs_parse(fc, mfs_fs_parameters, param, &result);
if (opt < 0)
return opt;
switch (opt) {
case Opt_mtree:
p = _acquire_set_path(param->string, &target);
if (IS_ERR(p))
return PTR_ERR(p);
sbi->mtree = p;
pathcpy(&sbi->lower, &target);
break;
case Opt_cachedir:
p = _acquire_set_path(param->string, &target);
if (IS_ERR(p))
return PTR_ERR(p);
sbi->cachedir = p;
pathcpy(&sbi->cache, &target);
break;
case Opt_mode:
sbi->mode = result.int_32;
break;
default:
return -ENOPARAM;
}
return 0;
}
static int mfs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct mfs_sb_info *sbi = MFS_SB(sb);
struct inode *inode;
int err = 0;
if (!sbi->cachedir || !sbi->mtree) {
pr_err("Lack of mtree or cachedir option.\n");
return -EINVAL;
}
if (sbi->mode != MFS_MODE_REMOTE) {
if (strcmp(sbi->cachedir, sbi->mtree)) {
pr_err("local/none mode require the same mtree and cachedir.\n");
return -EINVAL;
}
} else {
if (!strcmp(sbi->cachedir, sbi->mtree)) {
pr_err("remote mode require different mtree and cachedir.\n");
return -EINVAL;
}
if (strlen(sbi->cachedir) > strlen(sbi->mtree) &&
strncmp(sbi->mtree, sbi->cachedir, strlen(sbi->mtree)) == 0) {
pr_err("remote mode mtree should not be parent of cachedir.\n");
return -EINVAL;
}
}
sb->s_stack_depth = max(sbi->lower.mnt->mnt_sb->s_stack_depth,
sbi->cache.mnt->mnt_sb->s_stack_depth) + 1;
if (sb->s_stack_depth > 1) {
pr_err("cannot be stacked on other stackable file system.\n");
return -EINVAL;
}
sb->s_magic = MFS_SUPER_MAGIC;
sb->s_flags |= SB_RDONLY | SB_NOATIME;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_op = &mfs_sops;
sb->s_d_op = &mfs_dops;
err = super_setup_bdi(sb);
if (err)
return err;
if (support_event(sbi)) {
err = mfs_fs_dev_init(sb);
if (err)
return err;
}
err = mfs_fs_sysfs_init(sb);
if (err)
goto out_exit;
inode = mfs_iget(sb, d_inode(sbi->lower.dentry), &sbi->cache);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto out_sysfs;
}
sb->s_root = d_make_root(inode);
if (!sb->s_root) {
err = -ENOMEM;
goto out_iput;
}
err = mfs_alloc_dentry_info(sb->s_root);
if (err)
goto out_dput;
mfs_install_path(sb->s_root, &sbi->lower, &sbi->cache);
sbi->sb = sb;
set_bit(MFS_MOUNTED, &sbi->flags);
return 0;
out_dput:
dput(sb->s_root);
out_iput:
iput(inode);
out_sysfs:
mfs_fs_sysfs_exit(sb);
out_exit:
if (support_event(sbi))
mfs_fs_dev_exit(sb);
return err;
}
static int mfs_fc_get_tree(struct fs_context *fc)
{
return get_tree_nodev(fc, mfs_fc_fill_super);
}
static int mfs_reconfigure(struct fs_context *fc)
{
return -EOPNOTSUPP;
}
static void mfs_fc_free(struct fs_context *fc)
{
struct mfs_sb_info *sbi = fc->s_fs_info;
if (!sbi)
return;
if (sbi->mtree) {
path_put(&sbi->lower);
kfree(sbi->mtree);
}
if (sbi->cachedir) {
path_put(&sbi->cache);
kfree(sbi->cachedir);
}
kfree(sbi);
}
static const struct fs_context_operations mfs_context_ops = {
.parse_param = mfs_fc_parse_param,
.get_tree = mfs_fc_get_tree,
.reconfigure = mfs_reconfigure,
.free = mfs_fc_free,
};
static int mfs_init_fs_context(struct fs_context *fc)
{
struct mfs_sb_info *sbi;
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
if (!sbi)
return -ENOMEM;
init_waitqueue_head(&sbi->caches.pollwq);
xa_init_flags(&sbi->caches.events, XA_FLAGS_ALLOC);
sbi->minor = -1;
fc->s_fs_info = sbi;
fc->ops = &mfs_context_ops;
return 0;
}
static void mfs_kill_sb(struct super_block *sb)
{
struct mfs_sb_info *sbi = MFS_SB(sb);
struct mfs_caches *caches = &sbi->caches;
smp_mb__before_atomic();
clear_bit(MFS_MOUNTED, &sbi->flags);
smp_mb__after_atomic();
mfs_fs_sysfs_exit(sb);
if (support_event(sbi)) {
smp_mb__before_atomic();
while (test_bit(MFS_CACHE_OPENED, &caches->flags)) {
static DEFINE_RATELIMIT_STATE(busy_open, 30 * HZ, 1);
wake_up_all(&caches->pollwq);
msleep(100);
if (!__ratelimit(&busy_open))
continue;
pr_warn("Pending until close the /dev/mfs%u...\n", sbi->minor);
}
smp_mb__after_atomic();
mfs_fs_dev_exit(sb);
}
mfs_destroy_events(sb);
kill_anon_super(sb);
if (sbi->mtree) {
path_put(&sbi->lower);
kfree(sbi->mtree);
}
if (sbi->cachedir) {
path_put(&sbi->cache);
kfree(sbi->cachedir);
}
kfree(sbi);
sb->s_fs_info = NULL;
}
static struct file_system_type mfs_fs_type = {
.owner = THIS_MODULE,
.name = MFS_NAME,
.init_fs_context = mfs_init_fs_context,
.kill_sb = mfs_kill_sb,
.fs_flags = 0,
};
MODULE_ALIAS_FS(MFS_NAME);
static int __init init_mfs_fs(void)
{
int err;
mfs_inode_cachep =
kmem_cache_create("mfs_inode",
sizeof(struct mfs_inode), 0,
SLAB_RECLAIM_ACCOUNT, mfs_init_once);
if (!mfs_inode_cachep)
return -ENOMEM;
mfs_dentry_cachep =
kmem_cache_create("mfs_dentry",
sizeof(struct mfs_dentry_info), 0,
SLAB_RECLAIM_ACCOUNT, NULL);
if (!mfs_dentry_cachep) {
err = -ENOMEM;
goto err_dentryp;
}
err = mfs_cache_init();
if (err)
goto err_cache;
err = register_filesystem(&mfs_fs_type);
if (err)
goto err_register;
err = mfs_dev_init();
if (err)
goto err_dev;
err = mfs_sysfs_init();
if (err)
goto err_sysfs;
pr_info("MFS module loaded\n");
return 0;
err_sysfs:
mfs_dev_exit();
err_dev:
unregister_filesystem(&mfs_fs_type);
err_register:
mfs_cache_exit();
err_cache:
kmem_cache_destroy(mfs_dentry_cachep);
err_dentryp:
kmem_cache_destroy(mfs_inode_cachep);
return err;
}
static void __exit exit_mfs_fs(void)
{
mfs_sysfs_exit();
mfs_dev_exit();
unregister_filesystem(&mfs_fs_type);
rcu_barrier();
mfs_cache_exit();
kmem_cache_destroy(mfs_dentry_cachep);
kmem_cache_destroy(mfs_inode_cachep);
pr_info("MFS module unload\n");
}
module_init(init_mfs_fs);
module_exit(exit_mfs_fs);
MODULE_AUTHOR("Hongbo Li <lihongbo22@huawei.com>");
MODULE_AUTHOR("Xiaojia Huang <huangxiaojia2@huawei.com>");
MODULE_DESCRIPTION("MFS filesystem for Linux");
MODULE_LICENSE("GPL");