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