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.c210
1 files changed, 210 insertions, 0 deletions
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index eebdbc55d078..d6eeb59217b4 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -938,6 +938,117 @@ xfs_bumplink(
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
}
+#ifdef CONFIG_XFS_LIVE_HOOKS
+/*
+ * Use a static key here to reduce the overhead of link count live updates. 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_nlinks_hooks_switch);
+
+void
+xfs_nlink_hook_disable(void)
+{
+ xfs_hooks_switch_off(&xfs_nlinks_hooks_switch);
+}
+
+void
+xfs_nlink_hook_enable(void)
+{
+ xfs_hooks_switch_on(&xfs_nlinks_hooks_switch);
+}
+
+/* Call hooks for a link count update relating to a dot dirent update. */
+static inline void
+xfs_nlink_self_delta(
+ struct xfs_inode *dp,
+ int delta)
+{
+ if (xfs_hooks_switched_on(&xfs_nlinks_hooks_switch)) {
+ struct xfs_nlink_delta_params p = {
+ .dp = dp,
+ .ip = dp,
+ .delta = delta,
+ .name = &xfs_name_dot,
+ };
+ struct xfs_mount *mp = dp->i_mount;
+
+ xfs_hooks_call(&mp->m_nlink_delta_hooks, XFS_SELF_NLINK_DELTA,
+ &p);
+ }
+}
+
+/* Call hooks for a link count update relating to a dotdot dirent update. */
+static inline void
+xfs_nlink_backref_delta(
+ struct xfs_inode *dp,
+ struct xfs_inode *ip,
+ int delta)
+{
+ if (xfs_hooks_switched_on(&xfs_nlinks_hooks_switch)) {
+ struct xfs_nlink_delta_params p = {
+ .dp = dp,
+ .ip = ip,
+ .delta = delta,
+ .name = &xfs_name_dotdot,
+ };
+ struct xfs_mount *mp = ip->i_mount;
+
+ xfs_hooks_call(&mp->m_nlink_delta_hooks, XFS_BACKREF_NLINK_DELTA,
+ &p);
+ }
+}
+
+/* Call hooks for a link count update relating to a dirent update. */
+void
+xfs_nlink_dirent_delta(
+ struct xfs_inode *dp,
+ struct xfs_inode *ip,
+ int delta,
+ struct xfs_name *name)
+{
+ if (xfs_hooks_switched_on(&xfs_nlinks_hooks_switch)) {
+ struct xfs_nlink_delta_params p = {
+ .dp = dp,
+ .ip = ip,
+ .delta = delta,
+ .name = name,
+ };
+ struct xfs_mount *mp = ip->i_mount;
+
+ xfs_hooks_call(&mp->m_nlink_delta_hooks, XFS_DIRENT_NLINK_DELTA,
+ &p);
+ }
+}
+
+/* Call the specified function during a link count update. */
+int
+xfs_nlink_hook_add(
+ struct xfs_mount *mp,
+ struct xfs_nlink_hook *hook)
+{
+ return xfs_hooks_add(&mp->m_nlink_delta_hooks, &hook->delta_hook);
+}
+
+/* Stop calling the specified function during a link count update. */
+void
+xfs_nlink_hook_del(
+ struct xfs_mount *mp,
+ struct xfs_nlink_hook *hook)
+{
+ xfs_hooks_del(&mp->m_nlink_delta_hooks, &hook->delta_hook);
+}
+#else
+# define xfs_nlink_self_delta(dp, delta) ((void)0)
+# define xfs_nlink_backref_delta(dp, ip, delta) ((void)0)
+#endif /* CONFIG_XFS_LIVE_HOOKS */
+
int
xfs_create(
struct user_namespace *mnt_userns,
@@ -1047,6 +1158,16 @@ xfs_create(
}
/*
+ * Create ip with a reference from dp, and add '.' and '..' references
+ * if it's a directory.
+ */
+ xfs_nlink_dirent_delta(dp, ip, 1, name);
+ if (is_dir) {
+ xfs_nlink_self_delta(ip, 1);
+ xfs_nlink_backref_delta(dp, ip, 1);
+ }
+
+ /*
* If this is a synchronous mount, make sure that the
* create transaction goes to disk before returning to
* the user.
@@ -1258,6 +1379,7 @@ xfs_link(
xfs_trans_log_inode(tp, tdp, XFS_ILOG_CORE);
xfs_bumplink(tp, sip);
+ xfs_nlink_dirent_delta(tdp, sip, 1, target_name);
/*
* If this is a synchronous mount, make sure that the
@@ -2487,6 +2609,16 @@ xfs_remove(
}
/*
+ * Drop the link from dp to ip, and if ip was a directory, remove the
+ * '.' and '..' references since we freed the directory.
+ */
+ xfs_nlink_dirent_delta(dp, ip, -1, name);
+ if (S_ISDIR(VFS_I(ip)->i_mode)) {
+ xfs_nlink_backref_delta(dp, ip, -1);
+ xfs_nlink_self_delta(ip, -1);
+ }
+
+ /*
* If this is a synchronous mount, make sure that the
* remove transaction goes to disk before returning to
* the user.
@@ -2560,6 +2692,75 @@ xfs_sort_for_rename(
}
}
+#ifdef CONFIG_XFS_LIVE_HOOKS
+static inline void
+xfs_rename_call_nlink_hooks(
+ struct xfs_inode *src_dp,
+ struct xfs_name *src_name,
+ struct xfs_inode *src_ip,
+ struct xfs_inode *target_dp,
+ struct xfs_name *target_name,
+ 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_nlink_dirent_delta(src_dp, wip, 1, src_name);
+
+ /* Move the src_ip forward link from src_dp to target_dp. */
+ xfs_nlink_dirent_delta(src_dp, src_ip, -1, src_name);
+ xfs_nlink_dirent_delta(target_dp, src_ip, 1, target_name);
+
+ /*
+ * If src_ip is a dir, move its '..' back link from src_dp to
+ * target_dp.
+ */
+ if (S_ISDIR(VFS_I(src_ip)->i_mode)) {
+ xfs_nlink_backref_delta(src_dp, src_ip, -1);
+ xfs_nlink_backref_delta(target_dp, src_ip, 1);
+ }
+
+ if (!target_ip)
+ return;
+
+ if (flags & RENAME_EXCHANGE) {
+ /* Move the target_ip forward link from target_dp to src_dp. */
+ xfs_nlink_dirent_delta(target_dp, target_ip, -1, target_name);
+ xfs_nlink_dirent_delta(src_dp, target_ip, 1, target_name);
+
+ /*
+ * If target_ip is a dir, move its '..' back link from
+ * target_dp to src_dp.
+ */
+ if (S_ISDIR(VFS_I(target_ip)->i_mode)) {
+ xfs_nlink_backref_delta(target_dp, target_ip, -1);
+ xfs_nlink_backref_delta(src_dp, target_ip, 1);
+ }
+
+ return;
+ }
+
+ /* Drop target_ip's forward link from target_dp. */
+ xfs_nlink_dirent_delta(target_dp, target_ip, -1, target_name);
+
+ 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_nlink_self_delta(target_ip, -1);
+ xfs_nlink_backref_delta(target_dp, target_ip, -1);
+}
+#else
+# define xfs_rename_call_nlink_hooks(src_dp, src_name, src_ip, target_dp, \
+ target_name, target_ip, wip, flags) \
+ ((void)0)
+#endif /* CONFIG_XFS_LIVE_HOOKS */
+
static int
xfs_finish_rename(
struct xfs_trans *tp)
@@ -2676,6 +2877,11 @@ xfs_cross_rename(
}
xfs_trans_ichgtime(tp, dp1, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
xfs_trans_log_inode(tp, dp1, XFS_ILOG_CORE);
+
+ if (xfs_hooks_switched_on(&xfs_nlinks_hooks_switch))
+ xfs_rename_call_nlink_hooks(dp1, name1, ip1, dp2, name2, ip2,
+ NULL, RENAME_EXCHANGE);
+
return xfs_finish_rename(tp);
out_trans_abort:
@@ -3059,6 +3265,10 @@ retry:
if (new_parent)
xfs_trans_log_inode(tp, target_dp, XFS_ILOG_CORE);
+ if (xfs_hooks_switched_on(&xfs_nlinks_hooks_switch))
+ xfs_rename_call_nlink_hooks(src_dp, src_name, src_ip,
+ target_dp, target_name, target_ip, wip, flags);
+
error = xfs_finish_rename(tp);
if (wip)
xfs_irele(wip);