summaryrefslogtreecommitdiffstats
path: root/module/zfs/zfs_vfsops.c
diff options
context:
space:
mode:
authorGunnar Beutner <[email protected]>2011-06-20 10:36:58 -0700
committerBrian Behlendorf <[email protected]>2011-06-20 13:47:03 -0700
commitb00131d43ca344d4b205a03ab3eb771a060e5087 (patch)
treeba1b2999ba0bd65c8422cbd85b96b0f5136c57bb /module/zfs/zfs_vfsops.c
parent6f0cf71e0d191abb850a45f6d216cb5af158a6dd (diff)
Fix unlink/xattr deadlock
The problem here is that prune_icache() tries to evict/delete both the xattr directory inode as well as at least one xattr inode contained in that directory. Here's what happens: 1. File is created. 2. xattr is created for that file (behind the scenes a xattr directory and a file in that xattr directory are created) 3. File is deleted. 4. Both the xattr directory inode and at least one xattr inode from that directory are evicted by prune_icache(); prune_icache() acquires a lock on both inodes before it calls ->evict() on the inodes When the xattr directory inode is evicted zfs_zinactive attempts to delete the xattr files contained in that directory. While enumerating these files zfs_zget() is called to obtain a reference to the xattr file znode - which tries to lock the xattr inode. However that very same xattr inode was already locked by prune_icache() further up the call stack, thus leading to a deadlock. This can be reliably reproduced like this: $ touch test $ attr -s a -V b test $ rm test $ echo 3 > /proc/sys/vm/drop_caches This patch fixes the deadlock by moving the zfs_purgedir() call to zfs_unlinked_drain(). Instead zfs_rmnode() now checks whether the xattr dir is empty and leaves the xattr dir in the unlinked set if it finds any xattrs. To ensure zfs_unlinked_drain() never accesses a stale super block zfsvfs_teardown() has been update to block until the iput taskq has been drained. This avoids a potential race where a file with an xattr directory is removed and the file system is immediately unmounted. Signed-off-by: Brian Behlendorf <[email protected]> Closes #266
Diffstat (limited to 'module/zfs/zfs_vfsops.c')
-rw-r--r--module/zfs/zfs_vfsops.c6
1 files changed, 6 insertions, 0 deletions
diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c
index e71aa91e1..257563887 100644
--- a/module/zfs/zfs_vfsops.c
+++ b/module/zfs/zfs_vfsops.c
@@ -1092,6 +1092,12 @@ zfsvfs_teardown(zfs_sb_t *zsb, boolean_t unmounting)
}
/*
+ * Drain the iput_taskq to ensure all active references to the
+ * zfs_sb_t have been handled only then can it be safely destroyed.
+ */
+ taskq_wait(dsl_pool_iput_taskq(dmu_objset_pool(zsb->z_os)));
+
+ /*
* Close the zil. NB: Can't close the zil while zfs_inactive
* threads are blocked as zil_close can call zfs_inactive.
*/