8314d37f创建于 2024年8月20日历史提交
// SPDX-License-Identifier: GPL-2.0-only
/*
 * 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"

/* The dentry cache is just so we have properly sized dentries */
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;
}

/* allocate new dentry private data */
int new_dentry_private_data(struct dentry *dentry)
{
	struct sharefs_dentry_info *info = SHAREFS_D(dentry);

	/* use zalloc to init dentry_info.lower_path */
	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; /* found a match */
	else
		return 0; /* no match */
}

static int sharefs_inode_set(struct inode *inode, void *lower_inode)
{
	/* we do actual inode initialization in sharefs_iget */
	return 0;
}

struct inode *sharefs_iget(struct super_block *sb, struct inode *lower_inode)
{
	struct inode *inode; /* the new inode to return */

	if (!igrab(lower_inode))
		return ERR_PTR(-ESTALE);
	inode = iget5_locked(sb, /* our superblock */
			     /*
			      * hashval: we use inode number, but we can
			      * also use "(unsigned long)lower_inode"
			      * instead.
			      */
			     lower_inode->i_ino, /* hashval */
			     sharefs_inode_test, /* inode comparison function */
			     sharefs_inode_set, /* inode init function */
			     lower_inode); /* data passed to test+set fxns */
	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 found a cached inode, then just return it (after iput) */
	if (!(inode->i_state & I_NEW)) {
		iput(lower_inode);
		return inode;
	}

	/* initialize new inode */
	inode->i_ino = lower_inode->i_ino;
	sharefs_set_lower_inode(inode, lower_inode);

	atomic64_inc(&inode->i_version);

	/* use different set of inode ops for symlinks & directories */
	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;

	/* use different set of file ops for directories */
	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;

	/* properly initialize special inodes */
	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);

	/* all well, copy inode attributes */
	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
	 */

	/* inherit lower inode number for sharefs's inode */
	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;

	/* must initialize dentry operations */
	d_set_d_op(dentry, &sharefs_dops);

	if (IS_ROOT(dentry))
		goto out;

	name = dentry->d_name.name;

	/* now start the actual lookup procedure */
	lower_dir_dentry = lower_parent_path->dentry;
	lower_dir_mnt = lower_parent_path->mnt;

	/* Use vfs_path_lookup to check if the dentry exists or not */
	err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name, 0,
			      &lower_path);
	/* no error: handle positive dentries */
	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);
			 /* path_put underlying path on error */
			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;

	/* instantiate a new negative dentry */
	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

	/* allocate dentry private data.  We free it in ->d_release */
	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)));
	/* update parent directory's atime */
	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;
}