#include "internal.h"
#include <linux/err.h>
#include <linux/fs_stack.h>
#include <linux/namei.h>
#include <trace/events/mfs.h>
static int mfs_inode_eq(struct inode *inode, void *lower_target)
{
return mfs_lower_inode(inode) == (struct inode *)lower_target;
}
static int mfs_inode_set(struct inode *inode, void *lower_target)
{
return 0;
}
static struct inode *_mfs_get_inode(struct super_block *sb,
struct path *lower_path,
struct path *cache_path)
{
struct mfs_sb_info *sbi = MFS_SB(sb);
struct inode *ret, *lower_inode, *cache_inode;
lower_inode = d_inode(lower_path->dentry);
cache_inode = d_inode(cache_path->dentry);
if (lower_inode->i_sb != sbi->lower.dentry->d_sb) {
ret = ERR_PTR(-EXDEV);
goto out;
}
if ((lower_inode->i_mode & S_IFMT) != (cache_inode->i_mode & S_IFMT)) {
ret = ERR_PTR(-EUCLEAN);
goto out;
}
if (S_ISREG(lower_inode->i_mode)
&& lower_inode->i_size != cache_inode->i_size) {
ret = ERR_PTR(-EUCLEAN);
goto out;
}
ret = mfs_iget(sb, lower_inode, cache_path);
out:
return ret;
}
static int _lookup_create(struct path *lpath, struct path *parent_cpath,
const char *name, struct path *cpath)
{
struct dentry *ldentry, *parent_cdentry, *dentry;
struct inode *linode, *cdir;
int ret = 0, _ret;
ldentry = lpath->dentry;
parent_cdentry = parent_cpath->dentry;
linode = d_inode(ldentry);
cdir = d_inode(parent_cpath->dentry);
inode_lock_nested(cdir, I_MUTEX_PARENT);
retry:
dentry = lookup_one_len(name, parent_cdentry, strlen(name));
if (IS_ERR(dentry)) {
ret = PTR_ERR(dentry);
goto out;
}
cpath->mnt = mntget(parent_cpath->mnt);
cpath->dentry = dentry;
if (d_is_positive(dentry))
goto out;
if (d_is_dir(ldentry)) {
ret = vfs_mkdir(&nop_mnt_idmap, cdir, dentry, linode->i_mode);
if (ret)
goto new_err;
* In the event that the filesystem does not use the @dentry
* but leaves it negative or unhashes it.
*/
if (unlikely(d_unhashed(dentry))) {
mntput(parent_cpath->mnt);
dput(dentry);
goto retry;
}
inode_unlock(cdir);
} else {
ret = vfs_create(&nop_mnt_idmap, cdir, dentry, linode->i_mode, true);
if (ret)
goto new_err;
inode_unlock(cdir);
ret = vfs_truncate(cpath, linode->i_size);
if (ret)
goto truncate_err;
}
return ret;
truncate_err:
inode_lock_nested(cdir, I_MUTEX_PARENT);
_ret = vfs_unlink(&nop_mnt_idmap, cdir, dentry, NULL);
if (_ret)
pr_err("cleanup failed for file:%s, err:%d\n", name, _ret);
new_err:
mntput(parent_cpath->mnt);
dput(dentry);
out:
inode_unlock(cdir);
return ret;
}
static struct dentry *mfs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flag)
{
struct path parent_lpath, parent_cpath, lpath, cpath;
struct dentry *ret, *parent;
struct inode *inode;
const char *name;
int err;
trace_mfs_lookup(dir, dentry, flag);
parent = dget_parent(dentry);
mfs_get_path(parent, &parent_lpath, &parent_cpath);
err = mfs_alloc_dentry_info(dentry);
if (err) {
ret = ERR_PTR(err);
goto out;
}
name = dentry->d_name.name;
err = vfs_path_lookup(parent_lpath.dentry,
parent_lpath.mnt,
name, 0, &lpath);
if (err) {
ret = ERR_PTR(err);
mfs_free_dentry_info(dentry);
goto out;
}
err = vfs_path_lookup(parent_cpath.dentry,
parent_cpath.mnt,
name, 0, &cpath);
if (err) {
if (err != -ENOENT) {
cdentry_fail:
ret = ERR_PTR(err);
path_put(&lpath);
mfs_free_dentry_info(dentry);
goto out;
}
err = _lookup_create(&lpath, &parent_cpath, name, &cpath);
if (err)
goto cdentry_fail;
}
inode = _mfs_get_inode(dir->i_sb, &lpath, &cpath);
if (IS_ERR(inode)) {
path_put(&lpath);
path_put(&cpath);
mfs_free_dentry_info(dentry);
ret = ERR_PTR(PTR_ERR(inode));
goto out;
}
mfs_install_path(dentry, &lpath, &cpath);
ret = d_splice_alias(inode, dentry);
if (IS_ERR(ret)) {
path_put(&lpath);
path_put(&cpath);
mfs_free_dentry_info(dentry);
}
out:
mfs_put_path(&parent_lpath, &parent_cpath);
dput(parent);
return ret;
}
static int mfs_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask,
unsigned int query_flags)
{
struct mfs_inode *vi = MFS_I(d_inode(path->dentry));
generic_fillattr(idmap, request_mask, vi->lower, stat);
return 0;
}
static const char *mfs_get_link(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done)
{
struct mfs_sb_info *sbi = MFS_SB(inode->i_sb);
struct path lpath, cpath;
struct dentry *ldentry;
const char *p;
if (!dentry)
return ERR_PTR(-ECHILD);
mfs_get_path(dentry, &lpath, &cpath);
ldentry = lpath.dentry;
p = vfs_get_link(ldentry, done);
mfs_put_path(&lpath, &cpath);
if (IS_ERR(p) || p[0] != '/')
return p;
if (strlen(p) <= strlen(sbi->mtree))
return ERR_PTR(-EXDEV);
if (strncmp(sbi->mtree, p, strlen(sbi->mtree)) != 0)
return ERR_PTR(-EXDEV);
p += strlen(sbi->mtree);
if (p[0] != '/')
return ERR_PTR(-EXDEV);
p += 1;
return p;
}
static const struct inode_operations mfs_dir_iops = {
.lookup = mfs_lookup,
.getattr = mfs_getattr,
};
static const struct inode_operations mfs_symlink_iops = {
.getattr = mfs_getattr,
.get_link = mfs_get_link,
};
static const struct inode_operations mfs_file_iops = {
.getattr = mfs_getattr,
};
struct inode *mfs_iget(struct super_block *sb, struct inode *lower_inode,
struct path *cache_path)
{
struct inode *inode, *cache_inode = d_inode(cache_path->dentry);
struct mfs_inode *vi;
int err;
if (!igrab(lower_inode))
return ERR_PTR(-ESTALE);
if (!igrab(cache_inode)) {
err = -ESTALE;
goto err_put_lower;
}
inode = iget5_locked(sb, lower_inode->i_ino,
mfs_inode_eq,
mfs_inode_set,
lower_inode);
if (!inode) {
err = -ENOMEM;
goto err_put_cache;
}
if (!(inode->i_state & I_NEW)) {
iput(cache_inode);
iput(lower_inode);
return inode;
}
vi = MFS_I(inode);
inode->i_ino = lower_inode->i_ino;
switch (lower_inode->i_mode & S_IFMT) {
case S_IFREG:
inode->i_op = &mfs_file_iops;
inode->i_fop = &mfs_file_fops;
break;
case S_IFDIR:
inode->i_op = &mfs_dir_iops;
inode->i_fop = &mfs_dir_fops;
break;
case S_IFLNK:
inode->i_op = &mfs_symlink_iops;
break;
default:
err = -EOPNOTSUPP;
goto err_inode;
}
inode->i_mapping->a_ops = &mfs_aops;
if (S_ISREG(cache_inode->i_mode)) {
vi->vfs_inode.i_private = mfs_alloc_object(inode, cache_path);
if (IS_ERR(vi->vfs_inode.i_private)) {
err = PTR_ERR(vi->vfs_inode.i_private);
vi->vfs_inode.i_private = NULL;
goto err_inode;
}
}
vi->lower = lower_inode;
vi->cache = cache_inode;
fsstack_copy_attr_all(inode, lower_inode);
fsstack_copy_inode_size(inode, lower_inode);
unlock_new_inode(inode);
return inode;
err_inode:
iget_failed(inode);
err_put_cache:
iput(cache_inode);
err_put_lower:
iput(lower_inode);
return ERR_PTR(err);
}