On Tue, Aug 28, 2018 at 12:22:36PM -0700, Allison Henderson wrote:
This patch removes the old parent pointer attribute during the
rename operation, and re-adds the updated parent pointer
Signed-off-by: Allison Henderson <allison.henderson@xxxxxxxxxx>
renameat operations in generic/023 trigger an ASSERT failure in the
error handling path.
XFS: Assertion failed: xfs_isilocked(ip, XFS_ILOCK_EXCL), file: fs/xfs/xfs_inode_item.c, line: 576
------------[ cut here ]------------
kernel BUG at fs/xfs/xfs_message.c:102!
invalid opcode: 0000 [#1] PREEMPT SMP
CPU: 0 PID: 10568 Comm: renameat2 Not tainted 4.19.0-rc2-dgc+ #652
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.11.1-1 04/01/2014
RIP: 0010:assfail+0x28/0x30
Code: c3 90 0f 1f 44 00 00 48 89 f1 41 89 d0 48 c7 c6 c8 52 2e 82 48 89 fa 31 ff e8 64 f9 ff ff 80 3d 05 95 0a 01 00 75 03 0f 0b c3 <0f> 0b 66 0f 1f 44 00 00 0f 1f 44 00 00 48 63 f6 49 8a
RSP: 0018:ffffc90000b7bc18 EFLAGS: 00010202
RAX: 0000000000000000 RBX: ffff88002d20a840 RCX: 0000000000000000
RDX: 00000000ffffffc0 RSI: 000000000000000a RDI: ffffffff8227a7dc
RBP: ffff88002b4fb480 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000200 R11: f000000000000000 R12: ffff880034495fc8
R13: ffffffffffffffff R14: 0000000000000000 R15: ffffffff822e6858
FS: 00007f5fc4d4b740(0000) GS:ffff88003ec00000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f5fc3d01170 CR3: 0000000034534000 CR4: 00000000000006f0
Call Trace:
xfs_inode_item_unlock+0x63/0x80
xfs_trans_free_items+0x71/0xf0
xfs_trans_cancel+0xaf/0x1f0
xfs_rename+0x435/0xc50
xfs_vn_rename+0xd5/0x140
vfs_rename+0x384/0x9b0
? lookup_dcache+0x17/0x60
do_renameat2+0x49b/0x550
__x64_sys_renameat2+0x20/0x30
do_syscall_64+0x5a/0x180
entry_SYSCALL_64_after_hwframe+0x49/0xbe
@@ -2985,14 +2987,14 @@ xfs_rename(
* we can rely on either trans_commit or trans_cancel to unlock
* them.
*/
- xfs_trans_ijoin(tp, src_dp, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, src_dp, 0);
if (new_parent)
- xfs_trans_ijoin(tp, target_dp, XFS_ILOCK_EXCL);
- xfs_trans_ijoin(tp, src_ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, target_dp, 0);
+ xfs_trans_ijoin(tp, src_ip, 0);
if (target_ip)
- xfs_trans_ijoin(tp, target_ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, target_ip, 0);
if (wip)
- xfs_trans_ijoin(tp, wip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, wip, 0);
So we change the locking such that failure requires use to unlock
the inodes after the transaction commit or cancel....
/*
* If we are using project inheritance, we only allow renames
@@ -3002,15 +3004,16 @@ xfs_rename(
if (unlikely((target_dp->i_d.di_flags & XFS_DIFLAG_PROJINHERIT) &&
(xfs_get_projid(target_dp) != xfs_get_projid(src_ip)))) {
error = -EXDEV;
- goto out_trans_cancel;
+ goto out_unlock;
This smells wrong - we need to cancel the transaction, then unlock
the items, not the other way around, so we should still be jumping
to a transaction cancel action.
}
/* RENAME_EXCHANGE is unique from here on. */
- if (flags & RENAME_EXCHANGE)
- return xfs_cross_rename(tp, src_dp, src_name, src_ip,
+ if (flags & RENAME_EXCHANGE) {
+ error = xfs_cross_rename(tp, src_dp, src_name, src_ip,
target_dp, target_name, target_ip,
spaceres);
-
+ goto out;
+ }
This looks problematic, too. i.e. On error, xfs_cross_rename() will
have already called xfs_trans_cancel(), but it hasn't unlocked any
of the inodes. If it succeeds, it commits the transaction. Either
way, the transaction has been finished and freed. However, the
code we jump to expects that the transaction is open and still
running.
Also "out" typically means "goto end of function and return". This
is jumping to parent pointer addition, not the function return
processing. Hence the label needs to be changed to
"parent_pointer"....
[....]
@@ -3175,16 +3178,51 @@ xfs_rename(
VFS_I(wip)->i_state &= ~I_LINKABLE;
}
+out:
+ if (xfs_sb_version_hasparent(&mp->m_sb)) {
+ error = xfs_parent_add_deferred(target_dp, tp, src_ip, target_name,
+ new_diroffset);
And so when we try to add the a parent pointers, we haven't checked
if the cross rename failed and aborted the transaction. Or if it
succeeded, it expects to continue using the transaction which
xfs_cross_rename() committed.
+ if (error)
+ goto out_bmap_cancel;
+
+ error = xfs_parent_remove_deferred(src_dp, tp, src_ip,
+ old_diroffset);
+ if (error)
+ goto out_bmap_cancel;
+ }
We don't have "bmaps" in these functions any more. We just need to
cancel the trasaction now, right?
+
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);
error = xfs_finish_rename(tp);
+
if (wip)
xfs_irele(wip);
+ if (wip)
+ xfs_iunlock(wip, XFS_ILOCK_EXCL);
+ if (target_ip)
+ xfs_iunlock(target_ip, XFS_ILOCK_EXCL);
+ xfs_iunlock(src_ip, XFS_ILOCK_EXCL);
+ if (new_parent)
+ xfs_iunlock(target_dp, XFS_ILOCK_EXCL);
+ xfs_iunlock(src_dp, XFS_ILOCK_EXCL);
+
OK, so this unlocks after the final commit. That's fine, but the
rest of the error stack looks wrong.
return error;
+out_bmap_cancel:
+ xfs_defer_cancel(tp);
xfs_trans_cancel() calls xfs_defer_cancel() now, so it's not needed
anymore.
+out_unlock:
+ if (wip)
+ xfs_iunlock(wip, XFS_ILOCK_EXCL);
+ if (target_ip)
+ xfs_iunlock(target_ip, XFS_ILOCK_EXCL);
+ xfs_iunlock(src_ip, XFS_ILOCK_EXCL);
+ if (new_parent)
+ xfs_iunlock(target_dp, XFS_ILOCK_EXCL);
+ xfs_iunlock(src_dp, XFS_ILOCK_EXCL);
And I think this has to happen after calling xfs_trans_cancel().
+
out_trans_cancel:
xfs_trans_cancel(tp);
out_release_wip:
So the return stacking needs work here. I think it should look
something like:
xfs_trans_log_inode(tp, src_dp, XFS_ILOG_CORE);
if (new_parent)
xfs_trans_log_inode(tp, target_dp, XFS_ILOG_CORE);
error = xfs_finish_rename(tp);
out_unlock:
if (wip)
xfs_irele(wip);
if (wip)
xfs_iunlock(wip, XFS_ILOCK_EXCL);
if (target_ip)
xfs_iunlock(target_ip, XFS_ILOCK_EXCL);
xfs_iunlock(src_ip, XFS_ILOCK_EXCL);
if (new_parent)
xfs_iunlock(target_dp, XFS_ILOCK_EXCL);
xfs_iunlock(src_dp, XFS_ILOCK_EXCL);
return error;
out_trans_cancel:
xfs_trans_cancel(tp);
out_release_wip:
if (wip)
xfs_irele(wip);
goto out_unlock;
}
This means almost all the error goto's remain unchanged,
"out_bmap_cancel" should be "out_trans_cancel", xfs_cross_rename()
needs to roll the transaction, not commit it, and it if fails it
needs to jump to out_trans_cancel().
Cheers,
Dave.