diff options
Diffstat (limited to 'fs/xfs/xfs_inode.c')
-rw-r--r-- | fs/xfs/xfs_inode.c | 210 |
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); |