summaryrefslogtreecommitdiff
path: root/fs/xfs/xfs_inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/xfs_inode.c')
-rw-r--r--fs/xfs/xfs_inode.c104
1 files changed, 104 insertions, 0 deletions
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 92f136917047..e2ada2e86abf 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -765,6 +765,16 @@ xfs_create(
goto out_trans_cancel;
/*
+ * Create ip with a reference from dp, and add '.' and '..' references
+ * if it's a directory.
+ */
+ xfs_inode_nlink_delta(dp, ip, 1);
+ if (is_dir) {
+ xfs_inode_nlink_delta(ip, ip, 1);
+ xfs_inode_nlink_delta(ip, dp, 1);
+ }
+
+ /*
* If this is a synchronous mount, make sure that the
* create transaction goes to disk before returning to
* the user.
@@ -1056,6 +1066,7 @@ xfs_link(
error = xfs_dir_link_existing_child(tp, resblks, tdp, target_name, sip);
if (error)
goto error_return;
+ xfs_inode_nlink_delta(tdp, sip, 1);
/*
* If this is a synchronous mount, make sure that the
@@ -2176,6 +2187,16 @@ xfs_remove(
goto out_trans_cancel;
/*
+ * Drop the link from dp to ip, and if ip was a directory, remove the
+ * '.' and '..' references since we freed the directory.
+ */
+ xfs_inode_nlink_delta(dp, ip, -1);
+ if (S_ISDIR(VFS_I(ip)->i_mode)) {
+ xfs_inode_nlink_delta(ip, dp, -1);
+ xfs_inode_nlink_delta(ip, ip, -1);
+ }
+
+ /*
* If this is a synchronous mount, make sure that the
* remove transaction goes to disk before returning to
* the user.
@@ -2303,6 +2324,67 @@ xfs_rename_alloc_whiteout(
return 0;
}
+static inline void
+xfs_rename_call_nlink_hooks(
+ struct xfs_inode *src_dp,
+ struct xfs_inode *src_ip,
+ struct xfs_inode *target_dp,
+ struct xfs_inode *target_ip,
+ struct xfs_inode *wip,
+ unsigned int flags)
+{
+ /* If we added a whiteout, add the reference from src_dp. */
+ if (wip)
+ xfs_inode_nlink_delta(src_dp, wip, 1);
+
+ /* Move the src_ip reference from src_dp to target_dp. */
+ xfs_inode_nlink_delta(src_dp, src_ip, -1);
+ xfs_inode_nlink_delta(target_dp, src_ip, 1);
+
+ /*
+ * If src_ip is a dir, move its '..' reference from src_dp to
+ * target_dp.
+ */
+ if (S_ISDIR(VFS_I(src_ip)->i_mode)) {
+ xfs_inode_nlink_delta(src_ip, src_dp, -1);
+ xfs_inode_nlink_delta(src_ip, target_dp, 1);
+ }
+
+ if (!target_ip)
+ return;
+
+ if (flags & RENAME_EXCHANGE) {
+ /* Move the target_ip reference from target_dp to src_dp. */
+ xfs_inode_nlink_delta(target_dp, target_ip, -1);
+ xfs_inode_nlink_delta(src_dp, target_ip, 1);
+
+ /*
+ * If target_ip is a dir, move its '..' reference from
+ * target_dp to src_dp.
+ */
+ if (S_ISDIR(VFS_I(target_ip)->i_mode)) {
+ xfs_inode_nlink_delta(target_ip, target_dp, -1);
+ xfs_inode_nlink_delta(target_ip, src_dp, 1);
+ }
+
+ return;
+ }
+
+ /* Drop target_ip's reference from target_dp. */
+ xfs_inode_nlink_delta(target_dp, target_ip, -1);
+
+ if (!S_ISDIR(VFS_I(target_ip)->i_mode))
+ return;
+
+ /*
+ * If target_ip was a dir, drop the '.' and '..' references since that
+ * was the last reference.
+ */
+ ASSERT(VFS_I(target_ip)->i_nlink == 0);
+ xfs_inode_nlink_delta(target_ip, target_dp, -1);
+ xfs_inode_nlink_delta(target_ip, target_ip, -1);
+}
+
/*
* xfs_rename
*/
@@ -2443,6 +2525,9 @@ xfs_rename(
VFS_I(wip)->i_state &= ~I_LINKABLE;
}
+ xfs_rename_call_nlink_hooks(src_dp, src_ip, target_dp, target_ip, wip,
+ flags);
+
error = xfs_finish_rename(tp);
if (wip)
xfs_irele(wip);
@@ -2905,3 +2990,22 @@ xfs_is_always_cow_inode(
return ip->i_mount->m_always_cow &&
xfs_sb_version_hasreflink(&ip->i_mount->m_sb);
}
+
+/* Call a hook to capture nlink updates in real time. */
+#if IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB)
+void
+xfs_inode_nlink_delta(
+ struct xfs_inode *dp,
+ struct xfs_inode *ip,
+ int32_t delta)
+{
+ struct xfs_nlink_mod_params p;
+ struct xfs_mount *mp = ip->i_mount;
+
+ p.dir = dp->i_ino;
+ p.ino = ip->i_ino;
+ p.delta = delta;
+
+ xfs_hook_call(&mp->m_nlink_mod_hooks, 0, &p);
+}
+#endif