Contributors: 7
Author Tokens Token Proportion Commits Commit Proportion
Allison Henderson 659 49.44% 6 14.63%
Darrick J. Wong 583 43.74% 18 43.90%
David Chinner 45 3.38% 9 21.95%
Russell Cattelan 25 1.88% 1 2.44%
Christoph Hellwig 11 0.83% 4 9.76%
Nathan Scott 8 0.60% 2 4.88%
Brian Foster 2 0.15% 1 2.44%
Total 1333 41


// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2022-2024 Oracle.
 * All rights reserved.
 */
#include "xfs.h"
#include "xfs_fs.h"
#include "xfs_format.h"
#include "xfs_da_format.h"
#include "xfs_log_format.h"
#include "xfs_shared.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_bmap_btree.h"
#include "xfs_inode.h"
#include "xfs_error.h"
#include "xfs_trace.h"
#include "xfs_trans.h"
#include "xfs_da_btree.h"
#include "xfs_attr.h"
#include "xfs_dir2.h"
#include "xfs_dir2_priv.h"
#include "xfs_attr_sf.h"
#include "xfs_bmap.h"
#include "xfs_defer.h"
#include "xfs_log.h"
#include "xfs_xattr.h"
#include "xfs_parent.h"
#include "xfs_trans_space.h"
#include "xfs_attr_item.h"
#include "xfs_health.h"

struct kmem_cache		*xfs_parent_args_cache;

/*
 * Parent pointer attribute handling.
 *
 * Because the attribute name is a filename component, it will never be longer
 * than 255 bytes and must not contain nulls or slashes.  These are roughly the
 * same constraints that apply to attribute names.
 *
 * The attribute value must always be a struct xfs_parent_rec.  This means the
 * attribute will never be in remote format because 12 bytes is nowhere near
 * xfs_attr_leaf_entsize_local_max() (~75% of block size).
 *
 * Creating a new parent attribute will always create a new attribute - there
 * should never, ever be an existing attribute in the tree for a new inode.
 * ENOSPC behavior is problematic - creating the inode without the parent
 * pointer is effectively a corruption, so we allow parent attribute creation
 * to dip into the reserve block pool to avoid unexpected ENOSPC errors from
 * occurring.
 */

/* Return true if parent pointer attr name is valid. */
bool
xfs_parent_namecheck(
	unsigned int			attr_flags,
	const void			*name,
	size_t				length)
{
	/*
	 * Parent pointers always use logged operations, so there should never
	 * be incomplete xattrs.
	 */
	if (attr_flags & XFS_ATTR_INCOMPLETE)
		return false;

	return xfs_dir2_namecheck(name, length);
}

/* Return true if parent pointer attr value is valid. */
bool
xfs_parent_valuecheck(
	struct xfs_mount		*mp,
	const void			*value,
	size_t				valuelen)
{
	const struct xfs_parent_rec	*rec = value;

	if (!xfs_has_parent(mp))
		return false;

	/* The xattr value must be a parent record. */
	if (valuelen != sizeof(struct xfs_parent_rec))
		return false;

	/* The parent record must be local. */
	if (value == NULL)
		return false;

	/* The parent inumber must be valid. */
	if (!xfs_verify_dir_ino(mp, be64_to_cpu(rec->p_ino)))
		return false;

	return true;
}

/* Compute the attribute name hash for a parent pointer. */
xfs_dahash_t
xfs_parent_hashval(
	struct xfs_mount		*mp,
	const uint8_t			*name,
	int				namelen,
	xfs_ino_t			parent_ino)
{
	struct xfs_name			xname = {
		.name			= name,
		.len			= namelen,
	};

	/*
	 * Use the same dirent name hash as would be used on the directory, but
	 * mix in the parent inode number to avoid collisions on hardlinked
	 * files with identical names but different parents.
	 */
	return xfs_dir2_hashname(mp, &xname) ^
		upper_32_bits(parent_ino) ^ lower_32_bits(parent_ino);
}

/* Compute the attribute name hash from the xattr components. */
xfs_dahash_t
xfs_parent_hashattr(
	struct xfs_mount		*mp,
	const uint8_t			*name,
	int				namelen,
	const void			*value,
	int				valuelen)
{
	const struct xfs_parent_rec	*rec = value;

	/* Requires a local attr value in xfs_parent_rec format */
	if (valuelen != sizeof(struct xfs_parent_rec)) {
		ASSERT(valuelen == sizeof(struct xfs_parent_rec));
		return 0;
	}

	if (!value) {
		ASSERT(value != NULL);
		return 0;
	}

	return xfs_parent_hashval(mp, name, namelen, be64_to_cpu(rec->p_ino));
}

/*
 * Initialize the parent pointer arguments structure.  Caller must have zeroed
 * the contents of @args.  @tp is only required for updates.
 */
static void
xfs_parent_da_args_init(
	struct xfs_da_args	*args,
	struct xfs_trans	*tp,
	struct xfs_parent_rec	*rec,
	struct xfs_inode	*child,
	xfs_ino_t		owner,
	const struct xfs_name	*parent_name)
{
	args->geo = child->i_mount->m_attr_geo;
	args->whichfork = XFS_ATTR_FORK;
	args->attr_filter = XFS_ATTR_PARENT;
	args->op_flags = XFS_DA_OP_LOGGED | XFS_DA_OP_OKNOENT;
	args->trans = tp;
	args->dp = child;
	args->owner = owner;
	args->name = parent_name->name;
	args->namelen = parent_name->len;
	args->value = rec;
	args->valuelen = sizeof(struct xfs_parent_rec);
	xfs_attr_sethash(args);
}

/* Make sure the incore state is ready for a parent pointer query/update. */
static inline int
xfs_parent_iread_extents(
	struct xfs_trans	*tp,
	struct xfs_inode	*child)
{
	/* Parent pointers require that the attr fork must exist. */
	if (XFS_IS_CORRUPT(child->i_mount, !xfs_inode_has_attr_fork(child))) {
		xfs_inode_mark_sick(child, XFS_SICK_INO_PARENT);
		return -EFSCORRUPTED;
	}

	return xfs_iread_extents(tp, child, XFS_ATTR_FORK);
}

/* Add a parent pointer to reflect a dirent addition. */
int
xfs_parent_addname(
	struct xfs_trans	*tp,
	struct xfs_parent_args	*ppargs,
	struct xfs_inode	*dp,
	const struct xfs_name	*parent_name,
	struct xfs_inode	*child)
{
	int			error;

	error = xfs_parent_iread_extents(tp, child);
	if (error)
		return error;

	xfs_inode_to_parent_rec(&ppargs->rec, dp);
	xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
			child->i_ino, parent_name);
	xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_SET);
	return 0;
}

/* Remove a parent pointer to reflect a dirent removal. */
int
xfs_parent_removename(
	struct xfs_trans	*tp,
	struct xfs_parent_args	*ppargs,
	struct xfs_inode	*dp,
	const struct xfs_name	*parent_name,
	struct xfs_inode	*child)
{
	int			error;

	error = xfs_parent_iread_extents(tp, child);
	if (error)
		return error;

	xfs_inode_to_parent_rec(&ppargs->rec, dp);
	xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
			child->i_ino, parent_name);
	xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_REMOVE);
	return 0;
}

/* Replace one parent pointer with another to reflect a rename. */
int
xfs_parent_replacename(
	struct xfs_trans	*tp,
	struct xfs_parent_args	*ppargs,
	struct xfs_inode	*old_dp,
	const struct xfs_name	*old_name,
	struct xfs_inode	*new_dp,
	const struct xfs_name	*new_name,
	struct xfs_inode	*child)
{
	int			error;

	error = xfs_parent_iread_extents(tp, child);
	if (error)
		return error;

	xfs_inode_to_parent_rec(&ppargs->rec, old_dp);
	xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
			child->i_ino, old_name);

	xfs_inode_to_parent_rec(&ppargs->new_rec, new_dp);
	ppargs->args.new_name = new_name->name;
	ppargs->args.new_namelen = new_name->len;
	ppargs->args.new_value = &ppargs->new_rec;
	ppargs->args.new_valuelen = sizeof(struct xfs_parent_rec);
	xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_REPLACE);
	return 0;
}

/*
 * Extract parent pointer information from any parent pointer xattr into
 * @parent_ino/gen.  The last two parameters can be NULL pointers.
 *
 * Returns 0 if this is not a parent pointer xattr at all; or -EFSCORRUPTED for
 * garbage.
 */
int
xfs_parent_from_attr(
	struct xfs_mount	*mp,
	unsigned int		attr_flags,
	const unsigned char	*name,
	unsigned int		namelen,
	const void		*value,
	unsigned int		valuelen,
	xfs_ino_t		*parent_ino,
	uint32_t		*parent_gen)
{
	const struct xfs_parent_rec	*rec = value;

	ASSERT(attr_flags & XFS_ATTR_PARENT);

	if (!xfs_parent_namecheck(attr_flags, name, namelen))
		return -EFSCORRUPTED;
	if (!xfs_parent_valuecheck(mp, value, valuelen))
		return -EFSCORRUPTED;

	if (parent_ino)
		*parent_ino = be64_to_cpu(rec->p_ino);
	if (parent_gen)
		*parent_gen = be32_to_cpu(rec->p_gen);
	return 0;
}

/*
 * Look up a parent pointer record (@parent_name -> @pptr) of @ip.
 *
 * Caller must hold at least ILOCK_SHARED.  The scratchpad need not be
 * initialized.
 *
 * Returns 0 if the pointer is found, -ENOATTR if there is no match, or a
 * negative errno.
 */
int
xfs_parent_lookup(
	struct xfs_trans		*tp,
	struct xfs_inode		*ip,
	const struct xfs_name		*parent_name,
	struct xfs_parent_rec		*pptr,
	struct xfs_da_args		*scratch)
{
	memset(scratch, 0, sizeof(struct xfs_da_args));
	xfs_parent_da_args_init(scratch, tp, pptr, ip, ip->i_ino, parent_name);
	return xfs_attr_get_ilocked(scratch);
}

/* Sanity-check a parent pointer before we try to perform repairs. */
static inline bool
xfs_parent_sanity_check(
	struct xfs_mount		*mp,
	const struct xfs_name		*parent_name,
	const struct xfs_parent_rec	*pptr)
{
	if (!xfs_parent_namecheck(XFS_ATTR_PARENT, parent_name->name,
				parent_name->len))
		return false;

	if (!xfs_parent_valuecheck(mp, pptr, sizeof(*pptr)))
		return false;

	return true;
}


/*
 * Attach the parent pointer (@parent_name -> @pptr) to @ip immediately.
 * Caller must not have a transaction or hold the ILOCK.  This is for
 * specialized repair functions only.  The scratchpad need not be initialized.
 */
int
xfs_parent_set(
	struct xfs_inode	*ip,
	xfs_ino_t		owner,
	const struct xfs_name	*parent_name,
	struct xfs_parent_rec	*pptr,
	struct xfs_da_args	*scratch)
{
	if (!xfs_parent_sanity_check(ip->i_mount, parent_name, pptr)) {
		ASSERT(0);
		return -EFSCORRUPTED;
	}

	memset(scratch, 0, sizeof(struct xfs_da_args));
	xfs_parent_da_args_init(scratch, NULL, pptr, ip, owner, parent_name);
	return xfs_attr_set(scratch, XFS_ATTRUPDATE_CREATE, false);
}

/*
 * Remove the parent pointer (@parent_name -> @pptr) from @ip immediately.
 * Caller must not have a transaction or hold the ILOCK.  This is for
 * specialized repair functions only.  The scratchpad need not be initialized.
 */
int
xfs_parent_unset(
	struct xfs_inode		*ip,
	xfs_ino_t			owner,
	const struct xfs_name		*parent_name,
	struct xfs_parent_rec		*pptr,
	struct xfs_da_args		*scratch)
{
	if (!xfs_parent_sanity_check(ip->i_mount, parent_name, pptr)) {
		ASSERT(0);
		return -EFSCORRUPTED;
	}

	memset(scratch, 0, sizeof(struct xfs_da_args));
	xfs_parent_da_args_init(scratch, NULL, pptr, ip, owner, parent_name);
	return xfs_attr_set(scratch, XFS_ATTRUPDATE_REMOVE, false);
}