Merge branch 'send_fixes_4.2' of git://git.kernel.org/pub/scm/linux/kernel/git/fdmana...
authorChris Mason <clm@fb.com>
Thu, 4 Jun 2015 02:44:59 +0000 (19:44 -0700)
committerChris Mason <clm@fb.com>
Wed, 10 Jun 2015 14:02:41 +0000 (07:02 -0700)
fs/btrfs/send.c

index 5cf7838..50ebc62 100644 (file)
@@ -243,6 +243,7 @@ struct waiting_dir_move {
         * after this directory is moved, we can try to rmdir the ino rmdir_ino.
         */
        u64 rmdir_ino;
+       bool orphanized;
 };
 
 struct orphan_dir_info {
@@ -1916,8 +1917,13 @@ static int did_overwrite_ref(struct send_ctx *sctx,
                goto out;
        }
 
-       /* we know that it is or will be overwritten. check this now */
-       if (ow_inode < sctx->send_progress)
+       /*
+        * We know that it is or will be overwritten. Check this now.
+        * The current inode being processed might have been the one that caused
+        * inode 'ino' to be orphanized, therefore ow_inode can actually be the
+        * same as sctx->send_progress.
+        */
+       if (ow_inode <= sctx->send_progress)
                ret = 1;
        else
                ret = 0;
@@ -2239,6 +2245,8 @@ static int get_cur_path(struct send_ctx *sctx, u64 ino, u64 gen,
        fs_path_reset(dest);
 
        while (!stop && ino != BTRFS_FIRST_FREE_OBJECTID) {
+               struct waiting_dir_move *wdm;
+
                fs_path_reset(name);
 
                if (is_waiting_for_rm(sctx, ino)) {
@@ -2249,7 +2257,11 @@ static int get_cur_path(struct send_ctx *sctx, u64 ino, u64 gen,
                        break;
                }
 
-               if (is_waiting_for_move(sctx, ino)) {
+               wdm = get_waiting_dir_move(sctx, ino);
+               if (wdm && wdm->orphanized) {
+                       ret = gen_unique_name(sctx, ino, gen, name);
+                       stop = 1;
+               } else if (wdm) {
                        ret = get_first_ref(sctx->parent_root, ino,
                                            &parent_inode, &parent_gen, name);
                } else {
@@ -2939,7 +2951,7 @@ static int is_waiting_for_move(struct send_ctx *sctx, u64 ino)
        return entry != NULL;
 }
 
-static int add_waiting_dir_move(struct send_ctx *sctx, u64 ino)
+static int add_waiting_dir_move(struct send_ctx *sctx, u64 ino, bool orphanized)
 {
        struct rb_node **p = &sctx->waiting_dir_moves.rb_node;
        struct rb_node *parent = NULL;
@@ -2950,6 +2962,7 @@ static int add_waiting_dir_move(struct send_ctx *sctx, u64 ino)
                return -ENOMEM;
        dm->ino = ino;
        dm->rmdir_ino = 0;
+       dm->orphanized = orphanized;
 
        while (*p) {
                parent = *p;
@@ -3046,7 +3059,7 @@ static int add_pending_dir_move(struct send_ctx *sctx,
                        goto out;
        }
 
-       ret = add_waiting_dir_move(sctx, pm->ino);
+       ret = add_waiting_dir_move(sctx, pm->ino, is_orphan);
        if (ret)
                goto out;
 
@@ -3369,8 +3382,40 @@ out:
        return ret;
 }
 
+/*
+ * Check if ino ino1 is an ancestor of inode ino2 in the given root.
+ * Return 1 if true, 0 if false and < 0 on error.
+ */
+static int is_ancestor(struct btrfs_root *root,
+                      const u64 ino1,
+                      const u64 ino1_gen,
+                      const u64 ino2,
+                      struct fs_path *fs_path)
+{
+       u64 ino = ino2;
+
+       while (ino > BTRFS_FIRST_FREE_OBJECTID) {
+               int ret;
+               u64 parent;
+               u64 parent_gen;
+
+               fs_path_reset(fs_path);
+               ret = get_first_ref(root, ino, &parent, &parent_gen, fs_path);
+               if (ret < 0) {
+                       if (ret == -ENOENT && ino == ino2)
+                               ret = 0;
+                       return ret;
+               }
+               if (parent == ino1)
+                       return parent_gen == ino1_gen ? 1 : 0;
+               ino = parent;
+       }
+       return 0;
+}
+
 static int wait_for_parent_move(struct send_ctx *sctx,
-                               struct recorded_ref *parent_ref)
+                               struct recorded_ref *parent_ref,
+                               const bool is_orphan)
 {
        int ret = 0;
        u64 ino = parent_ref->dir;
@@ -3390,11 +3435,24 @@ static int wait_for_parent_move(struct send_ctx *sctx,
         * Our current directory inode may not yet be renamed/moved because some
         * ancestor (immediate or not) has to be renamed/moved first. So find if
         * such ancestor exists and make sure our own rename/move happens after
-        * that ancestor is processed.
+        * that ancestor is processed to avoid path build infinite loops (done
+        * at get_cur_path()).
         */
        while (ino > BTRFS_FIRST_FREE_OBJECTID) {
                if (is_waiting_for_move(sctx, ino)) {
-                       ret = 1;
+                       /*
+                        * If the current inode is an ancestor of ino in the
+                        * parent root, we need to delay the rename of the
+                        * current inode, otherwise don't delayed the rename
+                        * because we can end up with a circular dependency
+                        * of renames, resulting in some directories never
+                        * getting the respective rename operations issued in
+                        * the send stream or getting into infinite path build
+                        * loops.
+                        */
+                       ret = is_ancestor(sctx->parent_root,
+                                         sctx->cur_ino, sctx->cur_inode_gen,
+                                         ino, path_before);
                        break;
                }
 
@@ -3436,7 +3494,7 @@ out:
                                           ino,
                                           &sctx->new_refs,
                                           &sctx->deleted_refs,
-                                          false);
+                                          is_orphan);
                if (!ret)
                        ret = 1;
        }
@@ -3605,6 +3663,17 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
                        }
                }
 
+               if (S_ISDIR(sctx->cur_inode_mode) && sctx->parent_root &&
+                   can_rename) {
+                       ret = wait_for_parent_move(sctx, cur, is_orphan);
+                       if (ret < 0)
+                               goto out;
+                       if (ret == 1) {
+                               can_rename = false;
+                               *pending_move = 1;
+                       }
+               }
+
                /*
                 * link/move the ref to the new place. If we have an orphan
                 * inode, move it and update valid_path. If not, link or move
@@ -3625,18 +3694,11 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
                                 * dirs, we always have one new and one deleted
                                 * ref. The deleted ref is ignored later.
                                 */
-                               ret = wait_for_parent_move(sctx, cur);
-                               if (ret < 0)
-                                       goto out;
-                               if (ret) {
-                                       *pending_move = 1;
-                               } else {
-                                       ret = send_rename(sctx, valid_path,
-                                                         cur->full_path);
-                                       if (!ret)
-                                               ret = fs_path_copy(valid_path,
-                                                              cur->full_path);
-                               }
+                               ret = send_rename(sctx, valid_path,
+                                                 cur->full_path);
+                               if (!ret)
+                                       ret = fs_path_copy(valid_path,
+                                                          cur->full_path);
                                if (ret < 0)
                                        goto out;
                        } else {