mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	-----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQSQHSd0lITzzeNWNm3h3BK/laaZPAUCYTDKKAAKCRDh3BK/laaZ PG9PAQCUF0fdBlCKudwSEt5PV5xemycL9OCAlYCd7d4XbBIe9wEA6sVJL9J+OwV2 aF0NomiXtJccE+S9+byjVCyqSzQJGQQ= =6L2Y -----END PGP SIGNATURE----- Merge tag 'ovl-update-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs Pull overlayfs update from Miklos Szeredi: - Copy up immutable/append/sync/noatime attributes (Amir Goldstein) - Improve performance by enabling RCU lookup. - Misc fixes and improvements The reason this touches so many files is that the ->get_acl() method now gets a "bool rcu" argument. The ->get_acl() API was updated based on comments from Al and Linus: Link: https://lore.kernel.org/linux-fsdevel/CAJfpeguQxpd6Wgc0Jd3ks77zcsAv_bn0q17L3VNnnmPKu11t8A@mail.gmail.com/ * tag 'ovl-update-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: ovl: enable RCU'd ->get_acl() vfs: add rcu argument to ->get_acl() callback ovl: fix BUG_ON() in may_delete() when called from ovl_cleanup() ovl: use kvalloc in xattr copy-up ovl: update ctime when changing fileattr ovl: skip checking lower file's i_writecount on truncate ovl: relax lookup error on mismatch origin ftype ovl: do not set overlay.opaque for new directories ovl: add ovl_allow_offline_changes() helper ovl: disable decoding null uuid with redirect_dir ovl: consistent behavior for immutable/append-only inodes ovl: copy up sync/noatime fileattr flags ovl: pass ovl_fs to ovl_check_setxattr() fs: add generic helper for filling statx attribute flags
		
			
				
	
	
		
			291 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0
 | 
						|
/*
 | 
						|
 * Copyright (c) 2008, Christoph Hellwig
 | 
						|
 * All Rights Reserved.
 | 
						|
 */
 | 
						|
#include "xfs.h"
 | 
						|
#include "xfs_shared.h"
 | 
						|
#include "xfs_format.h"
 | 
						|
#include "xfs_log_format.h"
 | 
						|
#include "xfs_trans_resv.h"
 | 
						|
#include "xfs_mount.h"
 | 
						|
#include "xfs_inode.h"
 | 
						|
#include "xfs_attr.h"
 | 
						|
#include "xfs_trace.h"
 | 
						|
#include "xfs_error.h"
 | 
						|
#include "xfs_acl.h"
 | 
						|
#include "xfs_da_format.h"
 | 
						|
#include "xfs_da_btree.h"
 | 
						|
#include "xfs_trans.h"
 | 
						|
 | 
						|
#include <linux/posix_acl_xattr.h>
 | 
						|
 | 
						|
/*
 | 
						|
 * Locking scheme:
 | 
						|
 *  - all ACL updates are protected by inode->i_mutex, which is taken before
 | 
						|
 *    calling into this file.
 | 
						|
 */
 | 
						|
 | 
						|
STATIC struct posix_acl *
 | 
						|
xfs_acl_from_disk(
 | 
						|
	struct xfs_mount	*mp,
 | 
						|
	const struct xfs_acl	*aclp,
 | 
						|
	int			len,
 | 
						|
	int			max_entries)
 | 
						|
{
 | 
						|
	struct posix_acl_entry *acl_e;
 | 
						|
	struct posix_acl *acl;
 | 
						|
	const struct xfs_acl_entry *ace;
 | 
						|
	unsigned int count, i;
 | 
						|
 | 
						|
	if (len < sizeof(*aclp)) {
 | 
						|
		XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, aclp,
 | 
						|
				len);
 | 
						|
		return ERR_PTR(-EFSCORRUPTED);
 | 
						|
	}
 | 
						|
 | 
						|
	count = be32_to_cpu(aclp->acl_cnt);
 | 
						|
	if (count > max_entries || XFS_ACL_SIZE(count) != len) {
 | 
						|
		XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, aclp,
 | 
						|
				len);
 | 
						|
		return ERR_PTR(-EFSCORRUPTED);
 | 
						|
	}
 | 
						|
 | 
						|
	acl = posix_acl_alloc(count, GFP_KERNEL);
 | 
						|
	if (!acl)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	for (i = 0; i < count; i++) {
 | 
						|
		acl_e = &acl->a_entries[i];
 | 
						|
		ace = &aclp->acl_entry[i];
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The tag is 32 bits on disk and 16 bits in core.
 | 
						|
		 *
 | 
						|
		 * Because every access to it goes through the core
 | 
						|
		 * format first this is not a problem.
 | 
						|
		 */
 | 
						|
		acl_e->e_tag = be32_to_cpu(ace->ae_tag);
 | 
						|
		acl_e->e_perm = be16_to_cpu(ace->ae_perm);
 | 
						|
 | 
						|
		switch (acl_e->e_tag) {
 | 
						|
		case ACL_USER:
 | 
						|
			acl_e->e_uid = make_kuid(&init_user_ns,
 | 
						|
						 be32_to_cpu(ace->ae_id));
 | 
						|
			break;
 | 
						|
		case ACL_GROUP:
 | 
						|
			acl_e->e_gid = make_kgid(&init_user_ns,
 | 
						|
						 be32_to_cpu(ace->ae_id));
 | 
						|
			break;
 | 
						|
		case ACL_USER_OBJ:
 | 
						|
		case ACL_GROUP_OBJ:
 | 
						|
		case ACL_MASK:
 | 
						|
		case ACL_OTHER:
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			goto fail;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return acl;
 | 
						|
 | 
						|
fail:
 | 
						|
	posix_acl_release(acl);
 | 
						|
	return ERR_PTR(-EINVAL);
 | 
						|
}
 | 
						|
 | 
						|
STATIC void
 | 
						|
xfs_acl_to_disk(struct xfs_acl *aclp, const struct posix_acl *acl)
 | 
						|
{
 | 
						|
	const struct posix_acl_entry *acl_e;
 | 
						|
	struct xfs_acl_entry *ace;
 | 
						|
	int i;
 | 
						|
 | 
						|
	aclp->acl_cnt = cpu_to_be32(acl->a_count);
 | 
						|
	for (i = 0; i < acl->a_count; i++) {
 | 
						|
		ace = &aclp->acl_entry[i];
 | 
						|
		acl_e = &acl->a_entries[i];
 | 
						|
 | 
						|
		ace->ae_tag = cpu_to_be32(acl_e->e_tag);
 | 
						|
		switch (acl_e->e_tag) {
 | 
						|
		case ACL_USER:
 | 
						|
			ace->ae_id = cpu_to_be32(
 | 
						|
					from_kuid(&init_user_ns, acl_e->e_uid));
 | 
						|
			break;
 | 
						|
		case ACL_GROUP:
 | 
						|
			ace->ae_id = cpu_to_be32(
 | 
						|
					from_kgid(&init_user_ns, acl_e->e_gid));
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			ace->ae_id = cpu_to_be32(ACL_UNDEFINED_ID);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		ace->ae_perm = cpu_to_be16(acl_e->e_perm);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
struct posix_acl *
 | 
						|
xfs_get_acl(struct inode *inode, int type, bool rcu)
 | 
						|
{
 | 
						|
	struct xfs_inode	*ip = XFS_I(inode);
 | 
						|
	struct xfs_mount	*mp = ip->i_mount;
 | 
						|
	struct posix_acl	*acl = NULL;
 | 
						|
	struct xfs_da_args	args = {
 | 
						|
		.dp		= ip,
 | 
						|
		.attr_filter	= XFS_ATTR_ROOT,
 | 
						|
		.valuelen	= XFS_ACL_MAX_SIZE(mp),
 | 
						|
	};
 | 
						|
	int			error;
 | 
						|
 | 
						|
	if (rcu)
 | 
						|
		return ERR_PTR(-ECHILD);
 | 
						|
 | 
						|
	trace_xfs_get_acl(ip);
 | 
						|
 | 
						|
	switch (type) {
 | 
						|
	case ACL_TYPE_ACCESS:
 | 
						|
		args.name = SGI_ACL_FILE;
 | 
						|
		break;
 | 
						|
	case ACL_TYPE_DEFAULT:
 | 
						|
		args.name = SGI_ACL_DEFAULT;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		BUG();
 | 
						|
	}
 | 
						|
	args.namelen = strlen(args.name);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the attribute doesn't exist make sure we have a negative cache
 | 
						|
	 * entry, for any other error assume it is transient.
 | 
						|
	 */
 | 
						|
	error = xfs_attr_get(&args);
 | 
						|
	if (!error) {
 | 
						|
		acl = xfs_acl_from_disk(mp, args.value, args.valuelen,
 | 
						|
					XFS_ACL_MAX_ENTRIES(mp));
 | 
						|
	} else if (error != -ENOATTR) {
 | 
						|
		acl = ERR_PTR(error);
 | 
						|
	}
 | 
						|
 | 
						|
	kmem_free(args.value);
 | 
						|
	return acl;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
__xfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
 | 
						|
{
 | 
						|
	struct xfs_inode	*ip = XFS_I(inode);
 | 
						|
	struct xfs_da_args	args = {
 | 
						|
		.dp		= ip,
 | 
						|
		.attr_filter	= XFS_ATTR_ROOT,
 | 
						|
	};
 | 
						|
	int			error;
 | 
						|
 | 
						|
	switch (type) {
 | 
						|
	case ACL_TYPE_ACCESS:
 | 
						|
		args.name = SGI_ACL_FILE;
 | 
						|
		break;
 | 
						|
	case ACL_TYPE_DEFAULT:
 | 
						|
		if (!S_ISDIR(inode->i_mode))
 | 
						|
			return acl ? -EACCES : 0;
 | 
						|
		args.name = SGI_ACL_DEFAULT;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	args.namelen = strlen(args.name);
 | 
						|
 | 
						|
	if (acl) {
 | 
						|
		args.valuelen = XFS_ACL_SIZE(acl->a_count);
 | 
						|
		args.value = kvzalloc(args.valuelen, GFP_KERNEL);
 | 
						|
		if (!args.value)
 | 
						|
			return -ENOMEM;
 | 
						|
		xfs_acl_to_disk(args.value, acl);
 | 
						|
	}
 | 
						|
 | 
						|
	error = xfs_attr_set(&args);
 | 
						|
	kmem_free(args.value);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If the attribute didn't exist to start with that's fine.
 | 
						|
	 */
 | 
						|
	if (!acl && error == -ENOATTR)
 | 
						|
		error = 0;
 | 
						|
	if (!error)
 | 
						|
		set_cached_acl(inode, type, acl);
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
xfs_acl_set_mode(
 | 
						|
	struct inode		*inode,
 | 
						|
	umode_t			mode)
 | 
						|
{
 | 
						|
	struct xfs_inode	*ip = XFS_I(inode);
 | 
						|
	struct xfs_mount	*mp = ip->i_mount;
 | 
						|
	struct xfs_trans	*tp;
 | 
						|
	int			error;
 | 
						|
 | 
						|
	error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
 | 
						|
	if (error)
 | 
						|
		return error;
 | 
						|
 | 
						|
	xfs_ilock(ip, XFS_ILOCK_EXCL);
 | 
						|
	xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
 | 
						|
	inode->i_mode = mode;
 | 
						|
	inode->i_ctime = current_time(inode);
 | 
						|
	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
 | 
						|
 | 
						|
	if (xfs_has_wsync(mp))
 | 
						|
		xfs_trans_set_sync(tp);
 | 
						|
	return xfs_trans_commit(tp);
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
xfs_set_acl(struct user_namespace *mnt_userns, struct inode *inode,
 | 
						|
	    struct posix_acl *acl, int type)
 | 
						|
{
 | 
						|
	umode_t mode;
 | 
						|
	bool set_mode = false;
 | 
						|
	int error = 0;
 | 
						|
 | 
						|
	if (!acl)
 | 
						|
		goto set_acl;
 | 
						|
 | 
						|
	error = -E2BIG;
 | 
						|
	if (acl->a_count > XFS_ACL_MAX_ENTRIES(XFS_M(inode->i_sb)))
 | 
						|
		return error;
 | 
						|
 | 
						|
	if (type == ACL_TYPE_ACCESS) {
 | 
						|
		error = posix_acl_update_mode(mnt_userns, inode, &mode, &acl);
 | 
						|
		if (error)
 | 
						|
			return error;
 | 
						|
		set_mode = true;
 | 
						|
	}
 | 
						|
 | 
						|
 set_acl:
 | 
						|
	/*
 | 
						|
	 * We set the mode after successfully updating the ACL xattr because the
 | 
						|
	 * xattr update can fail at ENOSPC and we don't want to change the mode
 | 
						|
	 * if the ACL update hasn't been applied.
 | 
						|
	 */
 | 
						|
	error =  __xfs_set_acl(inode, acl, type);
 | 
						|
	if (!error && set_mode && mode != inode->i_mode)
 | 
						|
		error = xfs_acl_set_mode(inode, mode);
 | 
						|
	return error;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Invalidate any cached ACLs if the user has bypassed the ACL interface.
 | 
						|
 * We don't validate the content whatsoever so it is caller responsibility to
 | 
						|
 * provide data in valid format and ensure i_mode is consistent.
 | 
						|
 */
 | 
						|
void
 | 
						|
xfs_forget_acl(
 | 
						|
	struct inode		*inode,
 | 
						|
	const char		*name)
 | 
						|
{
 | 
						|
	if (!strcmp(name, SGI_ACL_FILE))
 | 
						|
		forget_cached_acl(inode, ACL_TYPE_ACCESS);
 | 
						|
	else if (!strcmp(name, SGI_ACL_DEFAULT))
 | 
						|
		forget_cached_acl(inode, ACL_TYPE_DEFAULT);
 | 
						|
}
 |