btrfs-progs: check: write corrected qgroup info to disk
authorMark Fasheh <mfasheh@suse.de>
Mon, 4 Jul 2016 12:57:38 +0000 (14:57 +0200)
committerDavid Sterba <dsterba@suse.com>
Wed, 13 Jul 2016 16:44:26 +0000 (18:44 +0200)
Now that we can verify all qgroups, we can write the corrected qgroups out
to disk when '--repair' is specified. The qgroup status item is also updated
to clear any out-of-date state. The repair_ functions were modeled after the
inode repair code in cmds-check.c.

I also renamed the 'scan' member of qgroup_status_item to 'rescan' in order
to keep consistency with the kernel.

Testing this was easy, I just reproduced qgroup inconsistencies via the
usual routes and had btrfsck fix them.

Signed-off-by: Mark Fasheh <mfasheh@suse.de>
Signed-off-by: David Sterba <dsterba@suse.com>
cmds-check.c
ctree.h
print-tree.c
qgroup-verify.c
qgroup-verify.h

index 6a748ace077efe8ba1fed330f44c7ccf4b6c807f..fbeb3a4a1f93d41d83ca8d3ac5d68fa7a16d748a 100644 (file)
@@ -9655,6 +9655,7 @@ int cmd_check(int argc, char **argv)
        int init_csum_tree = 0;
        int readonly = 0;
        int qgroup_report = 0;
+       int qgroups_repaired = 0;
        enum btrfs_open_ctree_flags ctree_flags = OPEN_CTREE_EXCLUSIVE;
 
        while(1) {
@@ -9810,7 +9811,7 @@ int cmd_check(int argc, char **argv)
                       uuidbuf);
                ret = qgroup_verify_all(info);
                if (ret == 0)
-                       ret = report_qgroups(1);
+                       report_qgroups(1);
                goto close_out;
        }
        if (subvolid) {
@@ -9964,6 +9965,10 @@ int cmd_check(int argc, char **argv)
                err = qgroup_verify_all(info);
                if (err)
                        goto out;
+               report_qgroups(0);
+               err = repair_qgroups(info, &qgroups_repaired);
+               if (err)
+                       goto out;
        }
 
        if (!list_empty(&root->fs_info->recow_ebs)) {
@@ -9972,10 +9977,9 @@ int cmd_check(int argc, char **argv)
        }
 out:
        /* Don't override original ret */
-       if (ret)
-               report_qgroups(0);
-       else
-               ret = report_qgroups(0);
+       if (!ret && qgroups_repaired)
+               ret = qgroups_repaired;
+
        if (found_old_backref) { /*
                 * there was a disk format change when mixed
                 * backref was in testing tree. The old format
diff --git a/ctree.h b/ctree.h
index 9e3626f1d238acf9f898520967f59738c86d6e9a..5cb5533aecbc0593cc7c89428bbacf62354e29ff 100644 (file)
--- a/ctree.h
+++ b/ctree.h
@@ -898,7 +898,7 @@ struct btrfs_qgroup_status_item {
        __le64 version;
        __le64 generation;
        __le64 flags;
-       __le64 scan;            /* progress during scanning */
+       __le64 rescan;          /* progress during scanning */
 } __attribute__ ((__packed__));
 
 struct btrfs_block_group_item {
@@ -2125,8 +2125,8 @@ BTRFS_SETGET_FUNCS(qgroup_status_generation, struct btrfs_qgroup_status_item,
                   generation, 64);
 BTRFS_SETGET_FUNCS(qgroup_status_flags, struct btrfs_qgroup_status_item,
                   flags, 64);
-BTRFS_SETGET_FUNCS(qgroup_status_scan, struct btrfs_qgroup_status_item,
-                  scan, 64);
+BTRFS_SETGET_FUNCS(qgroup_status_rescan, struct btrfs_qgroup_status_item,
+                  rescan, 64);
 
 BTRFS_SETGET_STACK_FUNCS(stack_qgroup_status_version,
                         struct btrfs_qgroup_status_item, version, 64);
@@ -2134,8 +2134,8 @@ BTRFS_SETGET_STACK_FUNCS(stack_qgroup_status_generation,
                         struct btrfs_qgroup_status_item, generation, 64);
 BTRFS_SETGET_STACK_FUNCS(stack_qgroup_status_flags,
                         struct btrfs_qgroup_status_item, flags, 64);
-BTRFS_SETGET_STACK_FUNCS(stack_qgroup_status_scan,
-                        struct btrfs_qgroup_status_item, scan, 64);
+BTRFS_SETGET_STACK_FUNCS(stack_qgroup_status_rescan,
+                        struct btrfs_qgroup_status_item, rescan, 64);
 
 /* btrfs_qgroup_info_item */
 BTRFS_SETGET_FUNCS(qgroup_info_generation, struct btrfs_qgroup_info_item,
index 746f25be39d3769f3f96afed99da11526ca17e5b..9f9e11e2e235f3bb6e466e650ccefcacb637e820 100644 (file)
@@ -1037,7 +1037,7 @@ void btrfs_print_leaf(struct btrfs_root *root, struct extent_buffer *l)
                                btrfs_qgroup_status_generation(l, qg_status),
                                flags_str,
                                (unsigned long long)
-                               btrfs_qgroup_status_scan(l, qg_status));
+                               btrfs_qgroup_status_rescan(l, qg_status));
                        break;
                case BTRFS_QGROUP_RELATION_KEY:
                        break;
index 43ae11ede959b8db322c12199bed431ff49b4610..3d96f1a555a8d4774690dff2940756fc6e3e7b4a 100644 (file)
@@ -29,6 +29,8 @@
 #include "utils.h"
 #include "ulist.h"
 #include "rbtree-utils.h"
+#include "transaction.h"
+#include "repair.h"
 
 #include "qgroup-verify.h"
 
@@ -66,6 +68,8 @@ struct qgroup_count {
        struct list_head members;
 
        u64 cur_refcnt;
+
+       struct list_head bad_list;
 };
 
 static struct counts_tree {
@@ -75,6 +79,8 @@ static struct counts_tree {
        unsigned int            qgroup_inconsist:1;
 } counts = { .root = RB_ROOT };
 
+static LIST_HEAD(bad_qgroups);
+
 static struct rb_root by_bytenr = RB_ROOT;
 
 /*
@@ -819,6 +825,7 @@ static struct qgroup_count *alloc_count(struct btrfs_disk_key *key,
                        btrfs_qgroup_info_exclusive_compressed(leaf, disk);
                INIT_LIST_HEAD(&c->groups);
                INIT_LIST_HEAD(&c->members);
+               INIT_LIST_HEAD(&c->bad_list);
 
                if (insert_count(c)) {
                        free(c);
@@ -1250,34 +1257,36 @@ static int report_qgroup_difference(struct qgroup_count *count, int verbose)
                        print_fields_signed(excl_diff, excl_diff,
                                            "diff:", "exclusive");
        }
-       return (is_different && count->subvol_exists);
+
+       return is_different;
 }
 
-int report_qgroups(int all)
+void report_qgroups(int all)
 {
        struct rb_node *node;
        struct qgroup_count *c;
-       int ret = 0;
 
-       if (counts.rescan_running) {
+       if (!repair && counts.rescan_running) {
                if (all) {
                        printf(
-       "Qgroup rescan is running, qgroup counts difference is expected\n");
+       "Qgroup rescan is running, a difference in qgroup counts is expected\n");
                } else {
                        printf(
-       "Qgroup rescan is running, ignore qgroup check\n");
-                       return ret;
+       "Qgroup rescan is running, qgroups will not be printed.\n");
+                       return;
                }
        }
        if (counts.qgroup_inconsist && !counts.rescan_running)
-               fprintf(stderr, "Qgroup is already inconsistent before checking\n");
+               fprintf(stderr, "Qgroup are marked as inconsistent.\n");
        node = rb_first(&counts.root);
        while (node) {
                c = rb_entry(node, struct qgroup_count, rb_node);
-               ret |= report_qgroup_difference(c, all);
+
+               if (report_qgroup_difference(c, all))
+                       list_add_tail(&c->bad_list, &bad_qgroups);
+
                node = rb_next(node);
        }
-       return ret;
 }
 
 void free_qgroup_counts(void)
@@ -1290,6 +1299,8 @@ void free_qgroup_counts(void)
        while (node) {
                c = rb_entry(node, struct qgroup_count, rb_node);
 
+               list_del(&c->bad_list);
+
                list_for_each_entry_safe(glist, tmpglist, &c->groups,
                                         next_group) {
                        list_del(&glist->next_group);
@@ -1425,3 +1436,150 @@ out:
        return ret;
 }
 
+static int repair_qgroup_info(struct btrfs_fs_info *info,
+                             struct qgroup_count *count)
+{
+       int ret;
+       struct btrfs_root *root = info->quota_root;
+       struct btrfs_trans_handle *trans;
+       struct btrfs_path *path;
+       struct btrfs_qgroup_info_item *info_item;
+       struct btrfs_key key;
+
+       printf("Repair qgroup %llu/%llu\n", btrfs_qgroup_level(count->qgroupid),
+              btrfs_qgroup_subvid(count->qgroupid));
+
+       path = btrfs_alloc_path();
+       if (!path)
+               return -ENOMEM;
+
+       trans = btrfs_start_transaction(root, 1);
+       if (IS_ERR(trans)) {
+               btrfs_free_path(path);
+               return PTR_ERR(trans);
+       }
+
+       key.objectid = 0;
+       key.type = BTRFS_QGROUP_INFO_KEY;
+       key.offset = count->qgroupid;
+       ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+       if (ret) {
+               error("Could not find disk item for qgroup %llu/%llu.\n",
+                     btrfs_qgroup_level(count->qgroupid),
+                     btrfs_qgroup_subvid(count->qgroupid));
+               if (ret > 0)
+                       ret = -ENOENT;
+               goto out;
+       }
+
+       info_item = btrfs_item_ptr(path->nodes[0], path->slots[0],
+                                  struct btrfs_qgroup_info_item);
+
+       btrfs_set_qgroup_info_generation(path->nodes[0], info_item,
+                                        trans->transid);
+
+       btrfs_set_qgroup_info_referenced(path->nodes[0], info_item,
+                                        count->info.referenced);
+       btrfs_set_qgroup_info_referenced_compressed(path->nodes[0], info_item,
+                                           count->info.referenced_compressed);
+
+       btrfs_set_qgroup_info_exclusive(path->nodes[0], info_item,
+                                       count->info.exclusive);
+       btrfs_set_qgroup_info_exclusive_compressed(path->nodes[0], info_item,
+                                          count->info.exclusive_compressed);
+
+       btrfs_mark_buffer_dirty(path->nodes[0]);
+
+out:
+       btrfs_commit_transaction(trans, root);
+       btrfs_free_path(path);
+
+       return ret;
+}
+
+static int repair_qgroup_status(struct btrfs_fs_info *info)
+{
+       int ret;
+       struct btrfs_root *root = info->quota_root;
+       struct btrfs_trans_handle *trans;
+       struct btrfs_path *path;
+       struct btrfs_key key;
+       struct btrfs_qgroup_status_item *status_item;
+
+       printf("Repair qgroup status item\n");
+
+       path = btrfs_alloc_path();
+       if (!path)
+               return -ENOMEM;
+
+       trans = btrfs_start_transaction(root, 1);
+       if (IS_ERR(trans)) {
+               btrfs_free_path(path);
+               return PTR_ERR(trans);
+       }
+
+       key.objectid = 0;
+       key.type = BTRFS_QGROUP_STATUS_KEY;
+       key.offset = 0;
+       ret = btrfs_search_slot(trans, root, &key, path, 0, 1);
+       if (ret) {
+               error("Could not find qgroup status item\n");
+               if (ret > 0)
+                       ret = -ENOENT;
+               goto out;
+       }
+
+       status_item = btrfs_item_ptr(path->nodes[0], path->slots[0],
+                                    struct btrfs_qgroup_status_item);
+       btrfs_set_qgroup_status_flags(path->nodes[0], status_item,
+                                     BTRFS_QGROUP_STATUS_FLAG_ON);
+       btrfs_set_qgroup_status_rescan(path->nodes[0], status_item, 0);
+       btrfs_set_qgroup_status_generation(path->nodes[0], status_item,
+                                          trans->transid);
+
+       btrfs_mark_buffer_dirty(path->nodes[0]);
+
+out:
+       btrfs_commit_transaction(trans, root);
+       btrfs_free_path(path);
+
+       return ret;
+}
+
+int repair_qgroups(struct btrfs_fs_info *info, int *repaired)
+{
+       int ret;
+       struct qgroup_count *count, *tmpcount;
+
+       *repaired = 0;
+
+       if (!repair)
+               return 0;
+
+       list_for_each_entry_safe(count, tmpcount, &bad_qgroups, bad_list) {
+               ret = repair_qgroup_info(info, count);
+               if (ret) {
+                       goto out;
+               }
+
+               (*repaired)++;
+
+               list_del_init(&count->bad_list);
+       }
+
+       /*
+        * Do this step last as we want the latest transaction id on
+        * our qgroup status to avoid a (useless) warning after
+        * mount.
+        */
+       if (*repaired || counts.qgroup_inconsist || counts.rescan_running) {
+               ret = repair_qgroup_status(info);
+               if (ret)
+                       goto out;
+
+               (*repaired)++;
+       }
+
+out:
+       return ret;
+}
index 0f8ff9b3477e4de541877fe6173973fb129e786f..d7d83a46ed5a03975393d9189d2ef32e744bcac4 100644 (file)
@@ -23,7 +23,8 @@
 #include "ctree.h"
 
 int qgroup_verify_all(struct btrfs_fs_info *info);
-int report_qgroups(int all);
+void report_qgroups(int all);
+int repair_qgroups(struct btrfs_fs_info *info, int *repaired);
 
 int print_extent_state(struct btrfs_fs_info *info, u64 subvol);