mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	Add a couple of utility functions to set or remove parent pointers from a file. These functions will be used by repair code, hence they skip the xattr logging that regular parent pointer updates use. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
		
			
				
	
	
		
			379 lines
		
	
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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);
 | 
						|
}
 |