btrfs: keep orphans for subvolume deletion
authorArne Jansen <sensille@gmx.net>
Thu, 15 Dec 2011 01:12:02 +0000 (20:12 -0500)
committerChris Mason <chris.mason@oracle.com>
Thu, 15 Dec 2011 15:50:37 +0000 (10:50 -0500)
Since we have the free space caches, btrfs_orphan_cleanup also runs for
the tree_root. Unfortunately this also cleans up the orphans used to mark
subvol deletions in progress.

Currently if a subvol deletion gets interrupted twice by umount/mount, the
deletion will not be continued and the space permanently lost, though it
would be possible to write a tool to recover those lost subvol deletions.
This patch checks if the orphan belongs to a subvol (dead root) and skips
the deletion.

Signed-off-by: Arne Jansen <sensille@gmx.net>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
fs/btrfs/inode.c

index f1c4bceed072b551389057686e2372135e40d221..4a31493d97ac8b5083c93949b7629628584b1bf5 100644 (file)
@@ -2158,6 +2158,38 @@ int btrfs_orphan_cleanup(struct btrfs_root *root)
                if (ret && ret != -ESTALE)
                        goto out;
 
+               if (ret == -ESTALE && root == root->fs_info->tree_root) {
+                       struct btrfs_root *dead_root;
+                       struct btrfs_fs_info *fs_info = root->fs_info;
+                       int is_dead_root = 0;
+
+                       /*
+                        * this is an orphan in the tree root. Currently these
+                        * could come from 2 sources:
+                        *  a) a snapshot deletion in progress
+                        *  b) a free space cache inode
+                        * We need to distinguish those two, as the snapshot
+                        * orphan must not get deleted.
+                        * find_dead_roots already ran before us, so if this
+                        * is a snapshot deletion, we should find the root
+                        * in the dead_roots list
+                        */
+                       spin_lock(&fs_info->trans_lock);
+                       list_for_each_entry(dead_root, &fs_info->dead_roots,
+                                           root_list) {
+                               if (dead_root->root_key.objectid ==
+                                   found_key.objectid) {
+                                       is_dead_root = 1;
+                                       break;
+                               }
+                       }
+                       spin_unlock(&fs_info->trans_lock);
+                       if (is_dead_root) {
+                               /* prevent this orphan from being found again */
+                               key.offset = found_key.objectid - 1;
+                               continue;
+                       }
+               }
                /*
                 * Inode is already gone but the orphan item is still there,
                 * kill the orphan item.