Contributors: 19
Author Tokens Token Proportion Commits Commit Proportion
Lachlan McIlroy 424 35.78% 1 1.35%
Darrick J. Wong 221 18.65% 18 24.32%
Andreas Gruenbacher 154 13.00% 3 4.05%
Christoph Hellwig 151 12.74% 14 18.92%
Allison Henderson 80 6.75% 8 10.81%
David Chinner 39 3.29% 13 17.57%
Eric Sandeen 26 2.19% 2 2.70%
Brian Foster 17 1.43% 1 1.35%
Al Viro 15 1.27% 2 2.70%
Mark Tinguely 10 0.84% 1 1.35%
Niv Sardi 10 0.84% 1 1.35%
James Morris 8 0.68% 1 1.35%
Russell Cattelan 7 0.59% 1 1.35%
Artem Savkov 6 0.51% 1 1.35%
Stephen Hemminger 6 0.51% 2 2.70%
Christian Brauner 5 0.42% 2 2.70%
David Howells 3 0.25% 1 1.35%
Nathan Scott 2 0.17% 1 1.35%
Wedson Almeida Filho 1 0.08% 1 1.35%
Total 1185 74


// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2008 Christoph Hellwig.
 * Portions Copyright (C) 2000-2008 Silicon Graphics, Inc.
 */

#include "xfs.h"
#include "xfs_shared.h"
#include "xfs_format.h"
#include "xfs_log_format.h"
#include "xfs_da_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_inode.h"
#include "xfs_da_btree.h"
#include "xfs_attr.h"
#include "xfs_acl.h"
#include "xfs_log.h"
#include "xfs_xattr.h"
#include "xfs_quota.h"

#include <linux/posix_acl_xattr.h>

/*
 * Get permission to use log-assisted atomic exchange of file extents.
 * Callers must not be running any transactions or hold any ILOCKs.
 */
static inline int
xfs_attr_grab_log_assist(
	struct xfs_mount	*mp)
{
	int			error = 0;

	/* xattr update log intent items are already enabled */
	if (xfs_is_using_logged_xattrs(mp))
		return 0;

	/*
	 * Check if the filesystem featureset is new enough to set this log
	 * incompat feature bit.  Strictly speaking, the minimum requirement is
	 * a V5 filesystem for the superblock field, but we'll require rmap
	 * or reflink to avoid having to deal with really old kernels.
	 */
	if (!xfs_has_reflink(mp) && !xfs_has_rmapbt(mp))
		return -EOPNOTSUPP;

	/* Enable log-assisted xattrs. */
	error = xfs_add_incompat_log_feature(mp,
			XFS_SB_FEAT_INCOMPAT_LOG_XATTRS);
	if (error)
		return error;
	xfs_set_using_logged_xattrs(mp);

	xfs_warn_mount(mp, XFS_OPSTATE_WARNED_LARP,
 "EXPERIMENTAL logged extended attributes feature in use. Use at your own risk!");

	return 0;
}

static inline bool
xfs_attr_want_log_assist(
	struct xfs_mount	*mp)
{
#ifdef DEBUG
	/* Logged xattrs require a V5 super for log_incompat */
	return xfs_has_crc(mp) && xfs_globals.larp;
#else
	return false;
#endif
}

/*
 * Set or remove an xattr, having grabbed the appropriate logging resources
 * prior to calling libxfs.  Callers of this function are only required to
 * initialize the inode, attr_filter, name, namelen, value, and valuelen fields
 * of @args.
 */
int
xfs_attr_change(
	struct xfs_da_args	*args,
	enum xfs_attr_update	op)
{
	struct xfs_mount	*mp = args->dp->i_mount;
	int			error;

	if (xfs_is_shutdown(mp))
		return -EIO;

	error = xfs_qm_dqattach(args->dp);
	if (error)
		return error;

	/*
	 * We have no control over the attribute names that userspace passes us
	 * to remove, so we have to allow the name lookup prior to attribute
	 * removal to fail as well.
	 */
	args->op_flags = XFS_DA_OP_OKNOENT;

	if (xfs_attr_want_log_assist(mp)) {
		error = xfs_attr_grab_log_assist(mp);
		if (error)
			return error;

		args->op_flags |= XFS_DA_OP_LOGGED;
	}

	args->owner = args->dp->i_ino;
	args->geo = mp->m_attr_geo;
	args->whichfork = XFS_ATTR_FORK;
	xfs_attr_sethash(args);

	return xfs_attr_set(args, op, args->attr_filter & XFS_ATTR_ROOT);
}


static int
xfs_xattr_get(const struct xattr_handler *handler, struct dentry *unused,
		struct inode *inode, const char *name, void *value, size_t size)
{
	struct xfs_da_args	args = {
		.dp		= XFS_I(inode),
		.attr_filter	= handler->flags,
		.name		= name,
		.namelen	= strlen(name),
		.value		= value,
		.valuelen	= size,
	};
	int			error;

	if (xfs_ifork_zapped(XFS_I(inode), XFS_ATTR_FORK))
		return -EIO;

	error = xfs_attr_get(&args);
	if (error)
		return error;
	return args.valuelen;
}

static inline enum xfs_attr_update
xfs_xattr_flags_to_op(
	int		flags,
	const void	*value)
{
	if (!value)
		return XFS_ATTRUPDATE_REMOVE;
	if (flags & XATTR_CREATE)
		return XFS_ATTRUPDATE_CREATE;
	if (flags & XATTR_REPLACE)
		return XFS_ATTRUPDATE_REPLACE;
	return XFS_ATTRUPDATE_UPSERT;
}

static int
xfs_xattr_set(const struct xattr_handler *handler,
	      struct mnt_idmap *idmap, struct dentry *unused,
	      struct inode *inode, const char *name, const void *value,
	      size_t size, int flags)
{
	struct xfs_da_args	args = {
		.dp		= XFS_I(inode),
		.attr_filter	= handler->flags,
		.name		= name,
		.namelen	= strlen(name),
		.value		= (void *)value,
		.valuelen	= size,
	};
	int			error;

	error = xfs_attr_change(&args, xfs_xattr_flags_to_op(flags, value));
	if (!error && (handler->flags & XFS_ATTR_ROOT))
		xfs_forget_acl(inode, name);
	return error;
}

static const struct xattr_handler xfs_xattr_user_handler = {
	.prefix	= XATTR_USER_PREFIX,
	.flags	= 0, /* no flags implies user namespace */
	.get	= xfs_xattr_get,
	.set	= xfs_xattr_set,
};

static const struct xattr_handler xfs_xattr_trusted_handler = {
	.prefix	= XATTR_TRUSTED_PREFIX,
	.flags	= XFS_ATTR_ROOT,
	.get	= xfs_xattr_get,
	.set	= xfs_xattr_set,
};

static const struct xattr_handler xfs_xattr_security_handler = {
	.prefix	= XATTR_SECURITY_PREFIX,
	.flags	= XFS_ATTR_SECURE,
	.get	= xfs_xattr_get,
	.set	= xfs_xattr_set,
};

const struct xattr_handler * const xfs_xattr_handlers[] = {
	&xfs_xattr_user_handler,
	&xfs_xattr_trusted_handler,
	&xfs_xattr_security_handler,
	NULL
};

static void
__xfs_xattr_put_listent(
	struct xfs_attr_list_context *context,
	char *prefix,
	int prefix_len,
	unsigned char *name,
	int namelen)
{
	char *offset;
	int arraytop;

	if (context->count < 0 || context->seen_enough)
		return;

	if (!context->buffer)
		goto compute_size;

	arraytop = context->count + prefix_len + namelen + 1;
	if (arraytop > context->firstu) {
		context->count = -1;	/* insufficient space */
		context->seen_enough = 1;
		return;
	}
	offset = context->buffer + context->count;
	memcpy(offset, prefix, prefix_len);
	offset += prefix_len;
	strncpy(offset, (char *)name, namelen);			/* real name */
	offset += namelen;
	*offset = '\0';

compute_size:
	context->count += prefix_len + namelen + 1;
	return;
}

static void
xfs_xattr_put_listent(
	struct xfs_attr_list_context *context,
	int		flags,
	unsigned char	*name,
	int		namelen,
	void		*value,
	int		valuelen)
{
	char *prefix;
	int prefix_len;

	ASSERT(context->count >= 0);

	/* Don't expose private xattr namespaces. */
	if (flags & XFS_ATTR_PRIVATE_NSP_MASK)
		return;

	if (flags & XFS_ATTR_ROOT) {
#ifdef CONFIG_XFS_POSIX_ACL
		if (namelen == SGI_ACL_FILE_SIZE &&
		    strncmp(name, SGI_ACL_FILE,
			    SGI_ACL_FILE_SIZE) == 0) {
			__xfs_xattr_put_listent(
					context, XATTR_SYSTEM_PREFIX,
					XATTR_SYSTEM_PREFIX_LEN,
					XATTR_POSIX_ACL_ACCESS,
					strlen(XATTR_POSIX_ACL_ACCESS));
		} else if (namelen == SGI_ACL_DEFAULT_SIZE &&
			 strncmp(name, SGI_ACL_DEFAULT,
				 SGI_ACL_DEFAULT_SIZE) == 0) {
			__xfs_xattr_put_listent(
					context, XATTR_SYSTEM_PREFIX,
					XATTR_SYSTEM_PREFIX_LEN,
					XATTR_POSIX_ACL_DEFAULT,
					strlen(XATTR_POSIX_ACL_DEFAULT));
		}
#endif

		/*
		 * Only show root namespace entries if we are actually allowed to
		 * see them.
		 */
		if (!capable(CAP_SYS_ADMIN))
			return;

		prefix = XATTR_TRUSTED_PREFIX;
		prefix_len = XATTR_TRUSTED_PREFIX_LEN;
	} else if (flags & XFS_ATTR_SECURE) {
		prefix = XATTR_SECURITY_PREFIX;
		prefix_len = XATTR_SECURITY_PREFIX_LEN;
	} else {
		prefix = XATTR_USER_PREFIX;
		prefix_len = XATTR_USER_PREFIX_LEN;
	}

	__xfs_xattr_put_listent(context, prefix, prefix_len, name,
				namelen);
	return;
}

ssize_t
xfs_vn_listxattr(
	struct dentry	*dentry,
	char		*data,
	size_t		size)
{
	struct xfs_attr_list_context context;
	struct inode	*inode = d_inode(dentry);
	int		error;

	if (xfs_ifork_zapped(XFS_I(inode), XFS_ATTR_FORK))
		return -EIO;

	/*
	 * First read the regular on-disk attributes.
	 */
	memset(&context, 0, sizeof(context));
	context.dp = XFS_I(inode);
	context.resynch = 1;
	context.buffer = size ? data : NULL;
	context.bufsize = size;
	context.firstu = context.bufsize;
	context.put_listent = xfs_xattr_put_listent;

	error = xfs_attr_list(&context);
	if (error)
		return error;
	if (context.count < 0)
		return -ERANGE;

	return context.count;
}