Commit 2f6ebd4c authored by Chandan Babu R's avatar Chandan Babu R
Browse files

Merge tag 'inode-refactor-6.11_2024-07-02' of...

Merge tag 'inode-refactor-6.11_2024-07-02' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux

 into xfs-6.11-mergeB

xfs: hoist inode operations to libxfs

This series hoists inode creation, renaming, and deletion operations to
libxfs in anticipation of the metadata inode directory feature, which
maintains a directory tree of metadata inodes.  This will be necessary
for further enhancements to the realtime feature, subvolume support.

There aren't supposed to be any functional changes in this intense
refactoring -- we just split the functions into pieces that are generic
and pieces that are specific to libxfs clients.  As a bonus, we can
remove various open-coded pieces of mkfs.xfs and xfs_repair when this
series gets to xfsprogs.

Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarChandan Babu R <chandanbabu@kernel.org>

* tag 'inode-refactor-6.11_2024-07-02' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux:
  xfs: don't use the incore struct xfs_sb for offsets into struct xfs_dsb
  xfs: get rid of trivial rename helpers
  xfs: move dirent update hooks to xfs_dir2.c
  xfs: create libxfs helper to rename two directory entries
  xfs: create libxfs helper to exchange two directory entries
  xfs: create libxfs helper to remove an existing inode/name from a directory
  xfs: hoist inode free function to libxfs
  xfs: create libxfs helper to link an existing inode into a directory
  xfs: create libxfs helper to link a new inode into a directory
  xfs: separate the icreate logic around INIT_XATTRS
  xfs: hoist xfs_{bump,drop}link to libxfs
  xfs: hoist xfs_iunlink to libxfs
  xfs: wrap inode creation dqalloc calls
  xfs: push xfs_icreate_args creation out of xfs_create*
  xfs: hoist new inode initialization functions to libxfs
  xfs: split new inode creation into two pieces
  xfs: use xfs_trans_ichgtime to set times when allocating inode
  xfs: implement atime updates in xfs_trans_ichgtime
  xfs: pack icreate initialization parameters into a separate structure
  xfs: hoist project id get/set functions to libxfs
  xfs: hoist inode flag conversion functions to libxfs
  xfs: hoist extent size helpers to libxfs
  xfs: move inode copy-on-write predicates to xfs_inode.[ch]
  xfs: use consistent uid/gid when grabbing dquots for inodes
  xfs: verify buffer, inode, and dquot items every tx commit
parents 3ba3ab1f ac3a0275
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -217,6 +217,18 @@ config XFS_DEBUG

	  Say N unless you are an XFS developer, or you play one on TV.

config XFS_DEBUG_EXPENSIVE
	bool "XFS expensive debugging checks"
	depends on XFS_FS && XFS_DEBUG
	help
	  Say Y here to get an XFS build with expensive debugging checks
	  enabled.  These checks may affect performance significantly.

	  Note that the resulting code will be HUGER and SLOWER, and probably
	  not useful unless you are debugging a particular problem.

	  Say N unless you are an XFS developer, or you play one on TV.

config XFS_ASSERT_FATAL
	bool "XFS fatal asserts"
	default y
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ xfs-y += $(addprefix libxfs/, \
				   xfs_iext_tree.o \
				   xfs_inode_fork.o \
				   xfs_inode_buf.o \
				   xfs_inode_util.o \
				   xfs_log_rlimit.o \
				   xfs_ag_resv.o \
				   xfs_parent.o \
+43 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@
#include "xfs_health.h"
#include "xfs_bmap_item.h"
#include "xfs_symlink_remote.h"
#include "xfs_inode_util.h"

struct kmem_cache		*xfs_bmap_intent_cache;

@@ -6454,3 +6455,45 @@ xfs_bmap_query_all(

	return xfs_btree_query_all(cur, xfs_bmap_query_range_helper, &query);
}

/* Helper function to extract extent size hint from inode */
xfs_extlen_t
xfs_get_extsz_hint(
	struct xfs_inode	*ip)
{
	/*
	 * No point in aligning allocations if we need to COW to actually
	 * write to them.
	 */
	if (xfs_is_always_cow_inode(ip))
		return 0;
	if ((ip->i_diflags & XFS_DIFLAG_EXTSIZE) && ip->i_extsize)
		return ip->i_extsize;
	if (XFS_IS_REALTIME_INODE(ip) &&
	    ip->i_mount->m_sb.sb_rextsize > 1)
		return ip->i_mount->m_sb.sb_rextsize;
	return 0;
}

/*
 * Helper function to extract CoW extent size hint from inode.
 * Between the extent size hint and the CoW extent size hint, we
 * return the greater of the two.  If the value is zero (automatic),
 * use the default size.
 */
xfs_extlen_t
xfs_get_cowextsz_hint(
	struct xfs_inode	*ip)
{
	xfs_extlen_t		a, b;

	a = 0;
	if (ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE)
		a = ip->i_cowextsize;
	b = xfs_get_extsz_hint(ip);

	a = max(a, b);
	if (a == 0)
		return XFS_DEFAULT_COWEXTSZ_HINT;
	return a;
}
+3 −0
Original line number Diff line number Diff line
@@ -296,4 +296,7 @@ typedef int (*xfs_bmap_query_range_fn)(
int xfs_bmap_query_all(struct xfs_btree_cur *cur, xfs_bmap_query_range_fn fn,
		void *priv);

xfs_extlen_t	xfs_get_extsz_hint(struct xfs_inode *ip);
xfs_extlen_t	xfs_get_cowextsz_hint(struct xfs_inode *ip);

#endif	/* __XFS_BMAP_H__ */
+658 −3
Original line number Diff line number Diff line
@@ -19,6 +19,11 @@
#include "xfs_error.h"
#include "xfs_trace.h"
#include "xfs_health.h"
#include "xfs_bmap_btree.h"
#include "xfs_trans_space.h"
#include "xfs_parent.h"
#include "xfs_ag.h"
#include "xfs_ialloc.h"

const struct xfs_name xfs_name_dotdot = {
	.name	= (const unsigned char *)"..",
@@ -584,9 +589,9 @@ xfs_dir_replace(
 */
int
xfs_dir_canenter(
	xfs_trans_t	*tp,
	xfs_inode_t	*dp,
	struct xfs_name	*name)		/* name of entry to add */
	struct xfs_trans	*tp,
	struct xfs_inode	*dp,
	const struct xfs_name	*name)		/* name of entry to add */
{
	return xfs_dir_createname(tp, dp, name, 0, 0);
}
@@ -756,3 +761,653 @@ xfs_dir2_compname(
		return xfs_ascii_ci_compname(args, name, len);
	return xfs_da_compname(args, name, len);
}

#ifdef CONFIG_XFS_LIVE_HOOKS
/*
 * Use a static key here to reduce the overhead of directory live update hooks.
 * If the compiler supports jump labels, the static branch will be replaced by
 * a nop sled when there are no hook users.  Online fsck is currently the only
 * caller, so this is a reasonable tradeoff.
 *
 * Note: Patching the kernel code requires taking the cpu hotplug lock.  Other
 * parts of the kernel allocate memory with that lock held, which means that
 * XFS callers cannot hold any locks that might be used by memory reclaim or
 * writeback when calling the static_branch_{inc,dec} functions.
 */
DEFINE_STATIC_XFS_HOOK_SWITCH(xfs_dir_hooks_switch);

void
xfs_dir_hook_disable(void)
{
	xfs_hooks_switch_off(&xfs_dir_hooks_switch);
}

void
xfs_dir_hook_enable(void)
{
	xfs_hooks_switch_on(&xfs_dir_hooks_switch);
}

/* Call hooks for a directory update relating to a child dirent update. */
inline void
xfs_dir_update_hook(
	struct xfs_inode		*dp,
	struct xfs_inode		*ip,
	int				delta,
	const struct xfs_name		*name)
{
	if (xfs_hooks_switched_on(&xfs_dir_hooks_switch)) {
		struct xfs_dir_update_params	p = {
			.dp		= dp,
			.ip		= ip,
			.delta		= delta,
			.name		= name,
		};
		struct xfs_mount	*mp = ip->i_mount;

		xfs_hooks_call(&mp->m_dir_update_hooks, 0, &p);
	}
}

/* Call the specified function during a directory update. */
int
xfs_dir_hook_add(
	struct xfs_mount	*mp,
	struct xfs_dir_hook	*hook)
{
	return xfs_hooks_add(&mp->m_dir_update_hooks, &hook->dirent_hook);
}

/* Stop calling the specified function during a directory update. */
void
xfs_dir_hook_del(
	struct xfs_mount	*mp,
	struct xfs_dir_hook	*hook)
{
	xfs_hooks_del(&mp->m_dir_update_hooks, &hook->dirent_hook);
}

/* Configure directory update hook functions. */
void
xfs_dir_hook_setup(
	struct xfs_dir_hook	*hook,
	notifier_fn_t		mod_fn)
{
	xfs_hook_setup(&hook->dirent_hook, mod_fn);
}
#endif /* CONFIG_XFS_LIVE_HOOKS */

/*
 * Given a directory @dp, a newly allocated inode @ip, and a @name, link @ip
 * into @dp under the given @name.  If @ip is a directory, it will be
 * initialized.  Both inodes must have the ILOCK held and the transaction must
 * have sufficient blocks reserved.
 */
int
xfs_dir_create_child(
	struct xfs_trans	*tp,
	unsigned int		resblks,
	struct xfs_dir_update	*du)
{
	struct xfs_inode	*dp = du->dp;
	const struct xfs_name	*name = du->name;
	struct xfs_inode	*ip = du->ip;
	int			error;

	xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
	xfs_assert_ilocked(dp, XFS_ILOCK_EXCL);

	error = xfs_dir_createname(tp, dp, name, ip->i_ino, resblks);
	if (error) {
		ASSERT(error != -ENOSPC);
		return error;
	}

	xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
	xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);

	if (S_ISDIR(VFS_I(ip)->i_mode)) {
		error = xfs_dir_init(tp, ip, dp);
		if (error)
			return error;

		xfs_bumplink(tp, dp);
	}

	/*
	 * If we have parent pointers, we need to add the attribute containing
	 * the parent information now.
	 */
	if (du->ppargs) {
		error = xfs_parent_addname(tp, du->ppargs, dp, name, ip);
		if (error)
			return error;
	}

	xfs_dir_update_hook(dp, ip, 1, name);
	return 0;
}

/*
 * Given a directory @dp, an existing non-directory inode @ip, and a @name,
 * link @ip into @dp under the given @name.  Both inodes must have the ILOCK
 * held.
 */
int
xfs_dir_add_child(
	struct xfs_trans	*tp,
	unsigned int		resblks,
	struct xfs_dir_update	*du)
{
	struct xfs_inode	*dp = du->dp;
	const struct xfs_name	*name = du->name;
	struct xfs_inode	*ip = du->ip;
	struct xfs_mount	*mp = tp->t_mountp;
	int			error;

	xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
	xfs_assert_ilocked(dp, XFS_ILOCK_EXCL);
	ASSERT(!S_ISDIR(VFS_I(ip)->i_mode));

	if (!resblks) {
		error = xfs_dir_canenter(tp, dp, name);
		if (error)
			return error;
	}

	/*
	 * Handle initial link state of O_TMPFILE inode
	 */
	if (VFS_I(ip)->i_nlink == 0) {
		struct xfs_perag	*pag;

		pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino));
		error = xfs_iunlink_remove(tp, pag, ip);
		xfs_perag_put(pag);
		if (error)
			return error;
	}

	error = xfs_dir_createname(tp, dp, name, ip->i_ino, resblks);
	if (error)
		return error;

	xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
	xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);

	xfs_bumplink(tp, ip);

	/*
	 * If we have parent pointers, we now need to add the parent record to
	 * the attribute fork of the inode. If this is the initial parent
	 * attribute, we need to create it correctly, otherwise we can just add
	 * the parent to the inode.
	 */
	if (du->ppargs) {
		error = xfs_parent_addname(tp, du->ppargs, dp, name, ip);
		if (error)
			return error;
	}

	xfs_dir_update_hook(dp, ip, 1, name);
	return 0;
}

/*
 * Given a directory @dp, a child @ip, and a @name, remove the (@name, @ip)
 * entry from the directory.  Both inodes must have the ILOCK held.
 */
int
xfs_dir_remove_child(
	struct xfs_trans	*tp,
	unsigned int		resblks,
	struct xfs_dir_update	*du)
{
	struct xfs_inode	*dp = du->dp;
	const struct xfs_name	*name = du->name;
	struct xfs_inode	*ip = du->ip;
	int			error;

	xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
	xfs_assert_ilocked(dp, XFS_ILOCK_EXCL);

	/*
	 * If we're removing a directory perform some additional validation.
	 */
	if (S_ISDIR(VFS_I(ip)->i_mode)) {
		ASSERT(VFS_I(ip)->i_nlink >= 2);
		if (VFS_I(ip)->i_nlink != 2)
			return -ENOTEMPTY;
		if (!xfs_dir_isempty(ip))
			return -ENOTEMPTY;

		/* Drop the link from ip's "..".  */
		error = xfs_droplink(tp, dp);
		if (error)
			return error;

		/* Drop the "." link from ip to self.  */
		error = xfs_droplink(tp, ip);
		if (error)
			return error;

		/*
		 * Point the unlinked child directory's ".." entry to the root
		 * directory to eliminate back-references to inodes that may
		 * get freed before the child directory is closed.  If the fs
		 * gets shrunk, this can lead to dirent inode validation errors.
		 */
		if (dp->i_ino != tp->t_mountp->m_sb.sb_rootino) {
			error = xfs_dir_replace(tp, ip, &xfs_name_dotdot,
					tp->t_mountp->m_sb.sb_rootino, 0);
			if (error)
				return error;
		}
	} else {
		/*
		 * When removing a non-directory we need to log the parent
		 * inode here.  For a directory this is done implicitly
		 * by the xfs_droplink call for the ".." entry.
		 */
		xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
	}
	xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);

	/* Drop the link from dp to ip. */
	error = xfs_droplink(tp, ip);
	if (error)
		return error;

	error = xfs_dir_removename(tp, dp, name, ip->i_ino, resblks);
	if (error) {
		ASSERT(error != -ENOENT);
		return error;
	}

	/* Remove parent pointer. */
	if (du->ppargs) {
		error = xfs_parent_removename(tp, du->ppargs, dp, name, ip);
		if (error)
			return error;
	}

	xfs_dir_update_hook(dp, ip, -1, name);
	return 0;
}

/*
 * Exchange the entry (@name1, @ip1) in directory @dp1 with the entry (@name2,
 * @ip2) in directory @dp2, and update '..' @ip1 and @ip2's entries as needed.
 * @ip1 and @ip2 need not be of the same type.
 *
 * All inodes must have the ILOCK held, and both entries must already exist.
 */
int
xfs_dir_exchange_children(
	struct xfs_trans	*tp,
	struct xfs_dir_update	*du1,
	struct xfs_dir_update	*du2,
	unsigned int		spaceres)
{
	struct xfs_inode	*dp1 = du1->dp;
	const struct xfs_name	*name1 = du1->name;
	struct xfs_inode	*ip1 = du1->ip;
	struct xfs_inode	*dp2 = du2->dp;
	const struct xfs_name	*name2 = du2->name;
	struct xfs_inode	*ip2 = du2->ip;
	int			ip1_flags = 0;
	int			ip2_flags = 0;
	int			dp2_flags = 0;
	int			error;

	/* Swap inode number for dirent in first parent */
	error = xfs_dir_replace(tp, dp1, name1, ip2->i_ino, spaceres);
	if (error)
		return error;

	/* Swap inode number for dirent in second parent */
	error = xfs_dir_replace(tp, dp2, name2, ip1->i_ino, spaceres);
	if (error)
		return error;

	/*
	 * If we're renaming one or more directories across different parents,
	 * update the respective ".." entries (and link counts) to match the new
	 * parents.
	 */
	if (dp1 != dp2) {
		dp2_flags = XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;

		if (S_ISDIR(VFS_I(ip2)->i_mode)) {
			error = xfs_dir_replace(tp, ip2, &xfs_name_dotdot,
						dp1->i_ino, spaceres);
			if (error)
				return error;

			/* transfer ip2 ".." reference to dp1 */
			if (!S_ISDIR(VFS_I(ip1)->i_mode)) {
				error = xfs_droplink(tp, dp2);
				if (error)
					return error;
				xfs_bumplink(tp, dp1);
			}

			/*
			 * Although ip1 isn't changed here, userspace needs
			 * to be warned about the change, so that applications
			 * relying on it (like backup ones), will properly
			 * notify the change
			 */
			ip1_flags |= XFS_ICHGTIME_CHG;
			ip2_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
		}

		if (S_ISDIR(VFS_I(ip1)->i_mode)) {
			error = xfs_dir_replace(tp, ip1, &xfs_name_dotdot,
						dp2->i_ino, spaceres);
			if (error)
				return error;

			/* transfer ip1 ".." reference to dp2 */
			if (!S_ISDIR(VFS_I(ip2)->i_mode)) {
				error = xfs_droplink(tp, dp1);
				if (error)
					return error;
				xfs_bumplink(tp, dp2);
			}

			/*
			 * Although ip2 isn't changed here, userspace needs
			 * to be warned about the change, so that applications
			 * relying on it (like backup ones), will properly
			 * notify the change
			 */
			ip1_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
			ip2_flags |= XFS_ICHGTIME_CHG;
		}
	}

	if (ip1_flags) {
		xfs_trans_ichgtime(tp, ip1, ip1_flags);
		xfs_trans_log_inode(tp, ip1, XFS_ILOG_CORE);
	}
	if (ip2_flags) {
		xfs_trans_ichgtime(tp, ip2, ip2_flags);
		xfs_trans_log_inode(tp, ip2, XFS_ILOG_CORE);
	}
	if (dp2_flags) {
		xfs_trans_ichgtime(tp, dp2, dp2_flags);
		xfs_trans_log_inode(tp, dp2, XFS_ILOG_CORE);
	}
	xfs_trans_ichgtime(tp, dp1, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
	xfs_trans_log_inode(tp, dp1, XFS_ILOG_CORE);

	/* Schedule parent pointer replacements */
	if (du1->ppargs) {
		error = xfs_parent_replacename(tp, du1->ppargs, dp1, name1,
				dp2, name2, ip1);
		if (error)
			return error;
	}

	if (du2->ppargs) {
		error = xfs_parent_replacename(tp, du2->ppargs, dp2, name2,
				dp1, name1, ip2);
		if (error)
			return error;
	}

	/*
	 * Inform our hook clients that we've finished an exchange operation as
	 * follows: removed the source and target files from their directories;
	 * added the target to the source directory; and added the source to
	 * the target directory.  All inodes are locked, so it's ok to model a
	 * rename this way so long as we say we deleted entries before we add
	 * new ones.
	 */
	xfs_dir_update_hook(dp1, ip1, -1, name1);
	xfs_dir_update_hook(dp2, ip2, -1, name2);
	xfs_dir_update_hook(dp1, ip2, 1, name1);
	xfs_dir_update_hook(dp2, ip1, 1, name2);
	return 0;
}

/*
 * Given an entry (@src_name, @src_ip) in directory @src_dp, make the entry
 * @target_name in directory @target_dp point to @src_ip and remove the
 * original entry, cleaning up everything left behind.
 *
 * Cleanup involves dropping a link count on @target_ip, and either removing
 * the (@src_name, @src_ip) entry from @src_dp or simply replacing the entry
 * with (@src_name, @wip) if a whiteout inode @wip is supplied.
 *
 * All inodes must have the ILOCK held.  We assume that if @src_ip is a
 * directory then its '..' doesn't already point to @target_dp, and that @wip
 * is a freshly allocated whiteout.
 */
int
xfs_dir_rename_children(
	struct xfs_trans	*tp,
	struct xfs_dir_update	*du_src,
	struct xfs_dir_update	*du_tgt,
	unsigned int		spaceres,
	struct xfs_dir_update	*du_wip)
{
	struct xfs_mount	*mp = tp->t_mountp;
	struct xfs_inode	*src_dp = du_src->dp;
	const struct xfs_name	*src_name = du_src->name;
	struct xfs_inode	*src_ip = du_src->ip;
	struct xfs_inode	*target_dp = du_tgt->dp;
	const struct xfs_name	*target_name = du_tgt->name;
	struct xfs_inode	*target_ip = du_tgt->ip;
	bool			new_parent = (src_dp != target_dp);
	bool			src_is_directory;
	int			error;

	src_is_directory = S_ISDIR(VFS_I(src_ip)->i_mode);

	/*
	 * Check for expected errors before we dirty the transaction
	 * so we can return an error without a transaction abort.
	 */
	if (target_ip == NULL) {
		/*
		 * If there's no space reservation, check the entry will
		 * fit before actually inserting it.
		 */
		if (!spaceres) {
			error = xfs_dir_canenter(tp, target_dp, target_name);
			if (error)
				return error;
		}
	} else {
		/*
		 * If target exists and it's a directory, check that whether
		 * it can be destroyed.
		 */
		if (S_ISDIR(VFS_I(target_ip)->i_mode) &&
		    (!xfs_dir_isempty(target_ip) ||
		     (VFS_I(target_ip)->i_nlink > 2)))
			return -EEXIST;
	}

	/*
	 * Directory entry creation below may acquire the AGF. Remove
	 * the whiteout from the unlinked list first to preserve correct
	 * AGI/AGF locking order. This dirties the transaction so failures
	 * after this point will abort and log recovery will clean up the
	 * mess.
	 *
	 * For whiteouts, we need to bump the link count on the whiteout
	 * inode. After this point, we have a real link, clear the tmpfile
	 * state flag from the inode so it doesn't accidentally get misused
	 * in future.
	 */
	if (du_wip->ip) {
		struct xfs_perag	*pag;

		ASSERT(VFS_I(du_wip->ip)->i_nlink == 0);

		pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, du_wip->ip->i_ino));
		error = xfs_iunlink_remove(tp, pag, du_wip->ip);
		xfs_perag_put(pag);
		if (error)
			return error;

		xfs_bumplink(tp, du_wip->ip);
	}

	/*
	 * Set up the target.
	 */
	if (target_ip == NULL) {
		/*
		 * If target does not exist and the rename crosses
		 * directories, adjust the target directory link count
		 * to account for the ".." reference from the new entry.
		 */
		error = xfs_dir_createname(tp, target_dp, target_name,
					   src_ip->i_ino, spaceres);
		if (error)
			return error;

		xfs_trans_ichgtime(tp, target_dp,
					XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);

		if (new_parent && src_is_directory) {
			xfs_bumplink(tp, target_dp);
		}
	} else { /* target_ip != NULL */
		/*
		 * Link the source inode under the target name.
		 * If the source inode is a directory and we are moving
		 * it across directories, its ".." entry will be
		 * inconsistent until we replace that down below.
		 *
		 * In case there is already an entry with the same
		 * name at the destination directory, remove it first.
		 */
		error = xfs_dir_replace(tp, target_dp, target_name,
					src_ip->i_ino, spaceres);
		if (error)
			return error;

		xfs_trans_ichgtime(tp, target_dp,
					XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);

		/*
		 * Decrement the link count on the target since the target
		 * dir no longer points to it.
		 */
		error = xfs_droplink(tp, target_ip);
		if (error)
			return error;

		if (src_is_directory) {
			/*
			 * Drop the link from the old "." entry.
			 */
			error = xfs_droplink(tp, target_ip);
			if (error)
				return error;
		}
	} /* target_ip != NULL */

	/*
	 * Remove the source.
	 */
	if (new_parent && src_is_directory) {
		/*
		 * Rewrite the ".." entry to point to the new
		 * directory.
		 */
		error = xfs_dir_replace(tp, src_ip, &xfs_name_dotdot,
					target_dp->i_ino, spaceres);
		ASSERT(error != -EEXIST);
		if (error)
			return error;
	}

	/*
	 * We always want to hit the ctime on the source inode.
	 *
	 * This isn't strictly required by the standards since the source
	 * inode isn't really being changed, but old unix file systems did
	 * it and some incremental backup programs won't work without it.
	 */
	xfs_trans_ichgtime(tp, src_ip, XFS_ICHGTIME_CHG);
	xfs_trans_log_inode(tp, src_ip, XFS_ILOG_CORE);

	/*
	 * Adjust the link count on src_dp.  This is necessary when
	 * renaming a directory, either within one parent when
	 * the target existed, or across two parent directories.
	 */
	if (src_is_directory && (new_parent || target_ip != NULL)) {

		/*
		 * Decrement link count on src_directory since the
		 * entry that's moved no longer points to it.
		 */
		error = xfs_droplink(tp, src_dp);
		if (error)
			return error;
	}

	/*
	 * For whiteouts, we only need to update the source dirent with the
	 * inode number of the whiteout inode rather than removing it
	 * altogether.
	 */
	if (du_wip->ip)
		error = xfs_dir_replace(tp, src_dp, src_name, du_wip->ip->i_ino,
					spaceres);
	else
		error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino,
					   spaceres);
	if (error)
		return error;

	xfs_trans_ichgtime(tp, src_dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
	xfs_trans_log_inode(tp, src_dp, XFS_ILOG_CORE);
	if (new_parent)
		xfs_trans_log_inode(tp, target_dp, XFS_ILOG_CORE);

	/* Schedule parent pointer updates. */
	if (du_wip->ppargs) {
		error = xfs_parent_addname(tp, du_wip->ppargs, src_dp,
				src_name, du_wip->ip);
		if (error)
			return error;
	}

	if (du_src->ppargs) {
		error = xfs_parent_replacename(tp, du_src->ppargs, src_dp,
				src_name, target_dp, target_name, src_ip);
		if (error)
			return error;
	}

	if (du_tgt->ppargs) {
		error = xfs_parent_removename(tp, du_tgt->ppargs, target_dp,
				target_name, target_ip);
		if (error)
			return error;
	}

	/*
	 * Inform our hook clients that we've finished a rename operation as
	 * follows: removed the source and target files from their directories;
	 * that we've added the source to the target directory; and finally
	 * that we've added the whiteout, if there was one.  All inodes are
	 * locked, so it's ok to model a rename this way so long as we say we
	 * deleted entries before we add new ones.
	 */
	if (target_ip)
		xfs_dir_update_hook(target_dp, target_ip, -1, target_name);
	xfs_dir_update_hook(src_dp, src_ip, -1, src_name);
	xfs_dir_update_hook(target_dp, src_ip, 1, target_name);
	if (du_wip->ip)
		xfs_dir_update_hook(src_dp, du_wip->ip, 1, src_name);
	return 0;
}
Loading