* fs/sharefs/lookup.c
*
* Copyright (c) 1998-2022 Erez Zadok
* Copyright (c) 2009 Shrikar Archak
* Copyright (c) 2003-2022 Stony Brook University
* Copyright (c) 2003-2022 The Research Foundation of SUNY
* Copyright (c) 2023 Huawei Device Co., Ltd.
*/
#include "sharefs.h"
#include "authentication.h"
static struct kmem_cache *sharefs_dentry_cachep;
int sharefs_init_dentry_cache(void)
{
sharefs_dentry_cachep =
kmem_cache_create("sharefs_dentry",
sizeof(struct sharefs_dentry_info),
0, SLAB_RECLAIM_ACCOUNT, NULL);
return sharefs_dentry_cachep ? 0 : -ENOMEM;
}
void sharefs_destroy_dentry_cache(void)
{
if (sharefs_dentry_cachep)
kmem_cache_destroy(sharefs_dentry_cachep);
}
void free_dentry_private_data(struct dentry *dentry)
{
if (!dentry || !dentry->d_fsdata)
return;
kmem_cache_free(sharefs_dentry_cachep, dentry->d_fsdata);
dentry->d_fsdata = NULL;
}
int new_dentry_private_data(struct dentry *dentry)
{
struct sharefs_dentry_info *info = SHAREFS_D(dentry);
info = kmem_cache_zalloc(sharefs_dentry_cachep, GFP_ATOMIC);
if (!info)
return -ENOMEM;
spin_lock_init(&info->lock);
dentry->d_fsdata = info;
return 0;
}
static int sharefs_inode_test(struct inode *inode, void *candidate_lower_inode)
{
struct inode *current_lower_inode = sharefs_lower_inode(inode);
if (current_lower_inode == (struct inode *)candidate_lower_inode)
return 1;
else
return 0;
}
static int sharefs_inode_set(struct inode *inode, void *lower_inode)
{
return 0;
}
struct inode *sharefs_iget(struct super_block *sb, struct inode *lower_inode)
{
struct inode *inode;
if (!igrab(lower_inode))
return ERR_PTR(-ESTALE);
inode = iget5_locked(sb,
* hashval: we use inode number, but we can
* also use "(unsigned long)lower_inode"
* instead.
*/
lower_inode->i_ino,
sharefs_inode_test,
sharefs_inode_set,
lower_inode);
if (!inode) {
iput(lower_inode);
return ERR_PTR(-ENOMEM);
}
if (lower_inode->i_nlink == 0) {
iput(lower_inode);
iput(inode);
return ERR_PTR(-ENOENT);
}
if (!(inode->i_state & I_NEW)) {
iput(lower_inode);
return inode;
}
inode->i_ino = lower_inode->i_ino;
sharefs_set_lower_inode(inode, lower_inode);
atomic64_inc(&inode->i_version);
if (S_ISDIR(lower_inode->i_mode))
inode->i_op = &sharefs_dir_iops;
else if (S_ISLNK(lower_inode->i_mode))
inode->i_op = &sharefs_symlink_iops;
else
inode->i_op = &sharefs_main_iops;
if (S_ISDIR(lower_inode->i_mode))
inode->i_fop = &sharefs_dir_fops;
else
inode->i_fop = &sharefs_main_fops;
inode->i_atime.tv_sec = 0;
inode->i_atime.tv_nsec = 0;
inode->i_mtime.tv_sec = 0;
inode->i_mtime.tv_nsec = 0;
inode->i_ctime.tv_sec = 0;
inode->i_ctime.tv_nsec = 0;
if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) ||
S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode))
init_special_inode(inode, lower_inode->i_mode,
lower_inode->i_rdev);
fsstack_copy_attr_all(inode, lower_inode);
fsstack_copy_inode_size(inode, lower_inode);
unlock_new_inode(inode);
return inode;
}
* Helper interpose routine, called directly by ->lookup to handle
* spliced dentries.
*/
static struct dentry *__sharefs_interpose(struct dentry *dentry,
struct super_block *sb,
struct path *lower_path)
{
struct inode *inode;
struct inode *lower_inode = d_inode(lower_path->dentry);
struct dentry *ret_dentry;
* We allocate our new inode below by calling sharefs_iget,
* which will initialize some of the new inode's fields
*/
inode = sharefs_iget(sb, lower_inode);
if (IS_ERR(inode)) {
ret_dentry = ERR_PTR(PTR_ERR(inode));
goto out;
}
ret_dentry = d_splice_alias(inode, dentry);
out:
return ret_dentry;
}
* Connect a sharefs inode dentry/inode with several lower ones. This is
* the classic stackable file system "vnode interposition" action.
*
* @dentry: sharefs's dentry which interposes on lower one
* @sb: sharefs's super_block
* @lower_path: the lower path (caller does path_get/put)
*/
int sharefs_interpose(struct dentry *dentry, struct super_block *sb,
struct path *lower_path)
{
struct dentry *ret_dentry;
ret_dentry = __sharefs_interpose(dentry, sb, lower_path);
return PTR_ERR(ret_dentry);
}
* Main driver function for sharefs's lookup.
*
* Returns: NULL (ok), ERR_PTR if an error occurred.
* Fills in lower_parent_path with <dentry,mnt> on success.
*/
static struct dentry *__sharefs_lookup(struct dentry *dentry,
unsigned int flags,
struct path *lower_parent_path)
{
int err = 0;
struct vfsmount *lower_dir_mnt;
struct dentry *lower_dir_dentry = NULL;
struct dentry *lower_dentry;
const char *name;
struct path lower_path;
struct qstr this;
struct dentry *ret_dentry = NULL;
d_set_d_op(dentry, &sharefs_dops);
if (IS_ROOT(dentry))
goto out;
name = dentry->d_name.name;
lower_dir_dentry = lower_parent_path->dentry;
lower_dir_mnt = lower_parent_path->mnt;
err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name, 0,
&lower_path);
if (!err) {
sharefs_set_lower_path(dentry, &lower_path);
ret_dentry =
__sharefs_interpose(dentry, dentry->d_sb, &lower_path);
if (IS_ERR(ret_dentry)) {
err = PTR_ERR(ret_dentry);
sharefs_put_reset_lower_path(dentry);
}
goto out;
}
* We don't consider ENOENT an error, and we want to return a
* negative dentry.
*/
if (err && err != -ENOENT)
goto out;
this.name = name;
this.len = strlen(name);
this.hash = full_name_hash(lower_dir_dentry, this.name, this.len);
lower_dentry = d_lookup(lower_dir_dentry, &this);
if (lower_dentry)
goto setup_lower;
lower_dentry = d_alloc(lower_dir_dentry, &this);
if (!lower_dentry) {
err = -ENOMEM;
goto out;
}
* Calling ->lookup instead of d_add will give the lower fs a chance
* to allocate the d_fsdata field but will still instantiate and hash the
* lower_dentry. Without this, sharefs could not stack on top of itself.
*/
d_inode(lower_dir_dentry)->i_op->lookup(d_inode(lower_dir_dentry),
lower_dentry, flags);
setup_lower:
lower_path.dentry = lower_dentry;
lower_path.mnt = mntget(lower_dir_mnt);
sharefs_set_lower_path(dentry, &lower_path);
* If the intent is to create a file, then don't return an error, so
* the VFS will continue the process of making this negative dentry
* into a positive one.
*/
if (err == -ENOENT || (flags & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET)))
err = 0;
out:
if (err)
return ERR_PTR(err);
return ret_dentry;
}
struct dentry *sharefs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
int err;
struct dentry *ret, *parent;
struct path lower_parent_path;
#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE
const struct cred *saved_cred = NULL;
__u16 permission;
#endif
parent = dget_parent(dentry);
sharefs_get_lower_path(parent, &lower_parent_path);
#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE
saved_cred = sharefs_override_file_fsids(dir, &permission);
if (!saved_cred) {
ret = ERR_PTR(-ENOMEM);
goto out_err;
}
#endif
err = new_dentry_private_data(dentry);
if (err) {
ret = ERR_PTR(err);
goto out;
}
ret = __sharefs_lookup(dentry, flags, &lower_parent_path);
if (IS_ERR(ret)) {
sharefs_err("sharefs_lookup error!");
goto out;
}
if (ret)
dentry = ret;
if (d_inode(dentry))
fsstack_copy_attr_times(d_inode(dentry),
sharefs_lower_inode(d_inode(dentry)));
fsstack_copy_attr_atime(d_inode(parent),
sharefs_lower_inode(d_inode(parent)));
fixup_perm_from_level(d_inode(parent), dentry);
out:
#ifdef CONFIG_SHAREFS_SUPPORT_OVERRIDE
sharefs_revert_fsids(saved_cred);
out_err:
#endif
sharefs_put_lower_path(parent, &lower_parent_path);
dput(parent);
return ret;
}