// SPDX-License-Identifier: GPL-2.0-only
/*
 * fs/sharefs/inode.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 int sharefs_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, 
                          u32 request_mask, unsigned int flags)
{
	struct path lower_path;
	int ret;

	sharefs_get_lower_path(path->dentry, &lower_path);
	ret = vfs_getattr_nosec(&lower_path, stat, request_mask, flags);
	stat->ino = d_inode(path->dentry)->i_ino;
	stat->uid = d_inode(path->dentry)->i_uid;
	stat->gid = d_inode(path->dentry)->i_gid;
	stat->mode = d_inode(path->dentry)->i_mode;
	stat->dev = 0;
	stat->rdev = 0;
	sharefs_put_lower_path(path->dentry, &lower_path);

	return ret;
}

static ssize_t sharefs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size)
{
	int err;
	struct dentry *lower_dentry;
	struct path lower_path;

	sharefs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	if (!(d_inode(lower_dentry)->i_opflags & IOP_XATTR)) {
		err = -EOPNOTSUPP;
		goto out;
	}
	err = vfs_listxattr(lower_dentry, buffer, buffer_size);
	if (err)
		goto out;
	fsstack_copy_attr_atime(d_inode(dentry),
				d_inode(lower_path.dentry));
out:
	sharefs_put_lower_path(dentry, &lower_path);
	return err;
}

static int sharefs_permission(struct mnt_idmap *idmap, struct inode *inode, int mask)
{
	struct sharefs_sb_info *sbi = SHAREFS_SB(inode->i_sb);
	if (sbi->override_support_delete) {
		return 0;
	}
	unsigned short mode = sharefs_lower_inode(inode)->i_mode;
	kuid_t cur_uid = current_fsuid();
	if (uid_eq(cur_uid, ROOT_UID))
		return 0;
	if (uid_eq(cur_uid, inode->i_uid)) {
		mode >>= 6;
	} else if (in_group_p(inode->i_gid)) {
		mode >>= 3;
	}

	if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
		return 0;

	return -EACCES;
}

static int sharefs_override_permission(struct mnt_idmap *idmap, struct inode *inode, int mask)
{
	unsigned short mode = sharefs_lower_inode(inode)->i_mode;
	mode >>= 6;
	kuid_t cur_uid = current_fsuid();
	if (uid_eq(cur_uid, ROOT_UID)) {
		return 0;
	}

	if ((mask & ~mode & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0) {
		return 0;
	}
	return -EACCES;
}

static int sharefs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry,
			 umode_t mode, bool want_excl)
{
	int err;
	struct dentry *lower_dentry;
	struct dentry *lower_parent_dentry = NULL;
	struct path lower_path;
	const struct cred *saved_cred = NULL;
	__u16 child_perm;

	saved_cred = sharefs_override_file_fsids(dir, &child_perm);
	if (!saved_cred) {
		err = -ENOMEM;
		return err;
	}

	sharefs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_parent_dentry = lock_parent(lower_dentry);
	err = vfs_create(&nop_mnt_idmap, d_inode(lower_parent_dentry), lower_dentry, mode,
			 want_excl);
	if (err)
		goto out;
	err = sharefs_interpose(dentry, dir->i_sb, &lower_path);
	if (err)
		goto out;
	fsstack_copy_attr_times(dir, sharefs_lower_inode(dir));
	fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));

out:
	unlock_dir(lower_parent_dentry);
	sharefs_put_lower_path(dentry, &lower_path);
	sharefs_revert_fsids(saved_cred);
	return err;
}

static int sharefs_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode)
{
	int err;
	struct dentry *lower_dentry;
	struct dentry *lower_parent_dentry = NULL;
	struct path lower_path;
	const struct cred *saved_cred = NULL;
	__u16 child_perm;

	saved_cred = sharefs_override_file_fsids(dir, &child_perm);
	if (!saved_cred) {
		err = -ENOMEM;
		return err;
	}

	sharefs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_parent_dentry = lock_parent(lower_dentry);
	err = vfs_mkdir(&nop_mnt_idmap, d_inode(lower_parent_dentry), lower_dentry, mode);
	if (err)
		goto out;

	err = sharefs_interpose(dentry, dir->i_sb, &lower_path);
	if (err)
		goto out;

	fsstack_copy_attr_times(dir, sharefs_lower_inode(dir));
	fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));
	/* update number of links on parent directory */
	set_nlink(dir, sharefs_lower_inode(dir)->i_nlink);

out:
	unlock_dir(lower_parent_dentry);
	sharefs_put_lower_path(dentry, &lower_path);
	sharefs_revert_fsids(saved_cred);
	return err;
}

static int sharefs_unlink(struct inode *dir, struct dentry *dentry)
{
	int err;
	struct dentry *lower_dentry = NULL;
	struct inode *lower_dir_inode = sharefs_lower_inode(dir);
	struct dentry *lower_dir_dentry = NULL;
	struct path lower_path;
	const struct cred *saved_cred = NULL;
	__u16 child_perm;

	saved_cred = sharefs_override_file_fsids(dir, &child_perm);
	if (!saved_cred) {
			err = -ENOMEM;
			return err;
	}

	sharefs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	dget(lower_dentry);
	lower_dir_dentry = lock_parent(lower_dentry);
	err = vfs_unlink(&nop_mnt_idmap, lower_dir_inode, lower_dentry, NULL);
	if (err)
		goto out;
	fsstack_copy_attr_times(dir, lower_dir_inode);
	fsstack_copy_inode_size(dir, lower_dir_inode);
	set_nlink(dentry->d_inode,
		  sharefs_lower_inode(dentry->d_inode)->i_nlink);
	dentry->d_inode->__i_ctime = dir->__i_ctime;
	d_drop(dentry);

out:
	unlock_dir(lower_dir_dentry);
	dput(lower_dentry);
	sharefs_put_lower_path(dentry, &lower_path);
	sharefs_revert_fsids(saved_cred);
	return err;
}

static int sharefs_rmdir(struct inode *dir, struct dentry *dentry)
{
	int err;
	struct dentry *lower_dentry;
	struct dentry *lower_dir_dentry;
	struct path lower_path;
	const struct cred *saved_cred = NULL;
	__u16 child_perm;
	
	saved_cred = sharefs_override_file_fsids(dir, &child_perm);
	if (!saved_cred) {
		err = -ENOMEM;
		return err;
	}

	sharefs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_dir_dentry = lock_parent(lower_dentry);
	err = vfs_rmdir(&nop_mnt_idmap, lower_dir_dentry->d_inode, lower_dentry);
	if (err)
		goto out;

	d_drop(dentry);
	if (dentry->d_inode)
		clear_nlink(dentry->d_inode);
	fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode);
	fsstack_copy_inode_size(dir, lower_dir_dentry->d_inode);
	set_nlink(dir, lower_dir_dentry->d_inode->i_nlink);

out:
	unlock_dir(lower_dir_dentry);
	sharefs_put_lower_path(dentry, &lower_path);
	sharefs_revert_fsids(saved_cred);
	return err;
}

static int sharefs_check_lower_rename(struct super_block *old_sb, struct super_block *new_sb)
{
	if (old_sb->s_id != new_sb->s_id) {
		return -EXDEV;
	} 

	return 0;
}
static int sharefs_rename(struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry,
				struct inode *new_dir, struct dentry *new_dentry,
				unsigned int flags)
{
	int err;
	struct dentry *lower_old_dentry = NULL;
	struct dentry *lower_new_dentry = NULL;
	struct dentry *lower_old_dir_dentry = NULL;
	struct dentry *lower_new_dir_dentry = NULL;
	struct dentry *trap = NULL;
	struct path lower_old_path, lower_new_path;
	struct renamedata rename_data;
	const struct cred *saved_cred = NULL;
	__u16 child_perm;

	if (flags)
		return -EINVAL;

	saved_cred = sharefs_override_file_fsids(old_dir, &child_perm);
	if (!saved_cred) {
		err = -ENOMEM;
		return err;
	}

	sharefs_get_lower_path(old_dentry, &lower_old_path);
	sharefs_get_lower_path(new_dentry, &lower_new_path);
	lower_old_dentry = lower_old_path.dentry;
	lower_new_dentry = lower_new_path.dentry;
	lower_old_dir_dentry = dget_parent(lower_old_dentry);
	lower_new_dir_dentry = dget_parent(lower_new_dentry);
	trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
	/* source should not be ancestor of target */
	if (trap == lower_old_dentry) {
		err = -EINVAL;
		goto out;
	}
	/* target should not be ancestor of source */
	if (trap == lower_new_dentry) {
		err = -ENOTEMPTY;
		goto out;
	}

	rename_data.old_mnt_idmap  = &nop_mnt_idmap;
	rename_data.old_dir = lower_old_dir_dentry->d_inode;
	rename_data.old_dentry = lower_old_dentry;
	rename_data.new_mnt_idmap = &nop_mnt_idmap;
	rename_data.new_dir = lower_new_dir_dentry->d_inode;
	rename_data.new_dentry = lower_new_dentry;
	rename_data.flags = flags;

	err = sharefs_check_lower_rename(lower_old_path.mnt->mnt_sb, lower_new_path.mnt->mnt_sb);
	if (err != 0) {
		goto out;
	}
	err = vfs_rename(&rename_data);
	if (err)
		goto out;

	fsstack_copy_attr_all(new_dir, lower_new_dir_dentry->d_inode);
	fsstack_copy_inode_size(new_dir, lower_new_dir_dentry->d_inode);
	if (new_dir != old_dir) {
		fsstack_copy_attr_all(old_dir,
				    lower_old_dir_dentry->d_inode);
		fsstack_copy_inode_size(old_dir,
					lower_old_dir_dentry->d_inode);
	}

out:
	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
	dput(lower_old_dir_dentry);
	dput(lower_new_dir_dentry);
	sharefs_put_lower_path(old_dentry, &lower_old_path);
	sharefs_put_lower_path(new_dentry, &lower_new_path);
	sharefs_revert_fsids(saved_cred);
	return err;
}

static int sharefs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *ia)
{
	int err;
	struct dentry *lower_dentry;
	struct inode *inode;
	struct inode *lower_inode;
	struct path lower_path;
	struct iattr lower_ia;
	
	inode = dentry->d_inode;
	/*
	 * Check if user has permission to change inode.  We don't check if
	 * this user can change the lower inode: that should happen when
	 * calling notify_change on the lower inode.
	 */
 	const struct cred *saved_cred = NULL;
 	__u16 child_perm;
 	 
	saved_cred = sharefs_override_file_fsids(inode, &child_perm);
	if (!saved_cred) {
		err = -ENOMEM;
		goto out_err;
	}

	err = setattr_prepare(&nop_mnt_idmap, dentry, ia);
	if (err)
		goto out_err;

	sharefs_get_lower_path(dentry, &lower_path);
	lower_dentry = lower_path.dentry;
	lower_inode = sharefs_lower_inode(inode);

	/* prepare our own lower struct iattr (with the lower file) */
	memcpy(&lower_ia, ia, sizeof(lower_ia));
	if (ia->ia_valid & ATTR_FILE)
		lower_ia.ia_file = sharefs_lower_file(ia->ia_file);

	/*
	 * If shrinking, first truncate upper level to cancel writing dirty
	 * pages beyond the new eof; and also if its' maxbytes is more
	 * limiting (fail with -EFBIG before making any change to the lower
	 * level).  There is no need to vmtruncate the upper level
	 * afterwards in the other cases: we fsstack_copy_inode_size from
	 * the lower level.
	 */
	if (ia->ia_valid & ATTR_SIZE) {
		err = inode_newsize_ok(inode, ia->ia_size);
		if (err)
			goto out;
		truncate_setsize(inode, ia->ia_size);
	}

	lower_ia.ia_valid &= ~(ATTR_MODE|ATTR_UID|ATTR_GID);

	/* notify the (possibly copied-up) lower inode */
	/*
	 * Note: we use lower_dentry->d_inode, because lower_inode may be
	 * unlinked (no inode->i_sb and i_ino==0.  This happens if someone
	 * tries to open(), unlink(), then ftruncate() a file.
	 */

	inode_lock(d_inode(lower_dentry));
	err = notify_change(&nop_mnt_idmap, lower_dentry, &lower_ia, /* note: lower_ia */
			    NULL);
	inode_unlock(d_inode(lower_dentry));

	if (err)
		goto out;

	/* get attributes from the lower inode */
	fsstack_copy_attr_all(inode, lower_inode);
	/*
	 * Not running fsstack_copy_inode_size(inode, lower_inode), because
	 * VFS should update our inode size, and notify_change on
	 * lower_inode should update its size.
	 */

out:
	sharefs_put_lower_path(dentry, &lower_path);
	sharefs_revert_fsids(saved_cred);
out_err:
	return err;
}

const struct inode_operations sharefs_symlink_iops = {
	.permission	= sharefs_permission,
	.getattr	= sharefs_getattr,
	.get_link	= NULL,
	.listxattr	= sharefs_listxattr,
};

const struct inode_operations sharefs_dir_iops = {
	.lookup		= sharefs_lookup,
	.permission	= sharefs_permission,
	.getattr	= sharefs_getattr,
	.listxattr	= sharefs_listxattr,
	.setattr	= sharefs_setattr,
};

const struct inode_operations sharefs_dir_override_iops = {
	.lookup		= sharefs_override_lookup,
	.permission	= sharefs_override_permission,
	.getattr	= sharefs_getattr,
	.listxattr	= sharefs_listxattr,
	.setattr	= sharefs_setattr,
	.unlink		= sharefs_unlink,
	.rmdir		= sharefs_rmdir,
	.rename		= sharefs_rename,
	.create		= sharefs_create,
	.mkdir		= sharefs_mkdir,
};

const struct inode_operations sharefs_main_iops = {
	.permission	= sharefs_permission,
	.getattr	= sharefs_getattr,
	.listxattr	= sharefs_listxattr,
	.setattr        = sharefs_setattr,
};