resetting manifest requested domain to floor
[platform/upstream/btrfs-progs.git] / qgroup.c
index dafde12..11659e8 100644 (file)
--- a/qgroup.c
+++ b/qgroup.c
  */
 
 #include "qgroup.h"
+#include <sys/ioctl.h>
 #include "ctree.h"
+#include "ioctl.h"
+#include "utils.h"
+#include <errno.h>
 
-u64 parse_qgroupid(char *p)
+#define BTRFS_QGROUP_NFILTERS_INCREASE (2 * BTRFS_QGROUP_FILTER_MAX)
+#define BTRFS_QGROUP_NCOMPS_INCREASE (2 * BTRFS_QGROUP_COMP_MAX)
+
+struct qgroup_lookup {
+       struct rb_root root;
+};
+
+struct btrfs_qgroup {
+       struct rb_node rb_node;
+       struct rb_node sort_node;
+       /*
+        *all_parent_node is used to
+        *filter a qgroup's all parent
+        */
+       struct rb_node all_parent_node;
+       u64 qgroupid;
+
+       /*
+        * info_item
+        */
+       u64 generation;
+       u64 rfer;       /*referenced*/
+       u64 rfer_cmpr;  /*referenced compressed*/
+       u64 excl;       /*exclusive*/
+       u64 excl_cmpr;  /*exclusive compressed*/
+
+       /*
+        *limit_item
+        */
+       u64 flags;      /*which limits are set*/
+       u64 max_rfer;
+       u64 max_excl;
+       u64 rsv_rfer;
+       u64 rsv_excl;
+
+       /*qgroups this group is member of*/
+       struct list_head qgroups;
+       /*qgroups that are members of this group*/
+       struct list_head members;
+};
+
+/*
+ * glue structure to represent the relations
+ * between qgroups
+ */
+struct btrfs_qgroup_list {
+       struct list_head next_qgroup;
+       struct list_head next_member;
+       struct btrfs_qgroup *qgroup;
+       struct btrfs_qgroup *member;
+};
+
+/*
+ * qgroupid,rfer,excl default to set
+ */
+static struct {
+       char *name;
+       char *column_name;
+       int need_print;
+       unsigned unit_mode;
+       int max_len;
+} btrfs_qgroup_columns[] = {
+       {
+               .name           = "qgroupid",
+               .column_name    = "Qgroupid",
+               .need_print     = 1,
+               .unit_mode      = 0,
+               .max_len        = 8,
+       },
+       {
+               .name           = "rfer",
+               .column_name    = "Rfer",
+               .need_print     = 1,
+               .unit_mode      = UNITS_DEFAULT,
+               .max_len        = 12,
+       },
+       {
+               .name           = "excl",
+               .column_name    = "Excl",
+               .need_print     = 1,
+               .unit_mode      = UNITS_DEFAULT,
+               .max_len        = 12,
+       },
+       {       .name           = "max_rfer",
+               .column_name    = "Max_rfer",
+               .need_print     = 0,
+               .unit_mode      = UNITS_DEFAULT,
+               .max_len        = 12,
+       },
+       {
+               .name           = "max_excl",
+               .column_name    = "Max_excl",
+               .need_print     = 0,
+               .unit_mode      = UNITS_DEFAULT,
+               .max_len        = 12,
+       },
+       {
+               .name           = "parent",
+               .column_name    = "Parent",
+               .need_print     = 0,
+               .unit_mode      = 0,
+               .max_len        = 7,
+       },
+       {
+               .name           = "child",
+               .column_name    = "Child",
+               .need_print     = 0,
+               .unit_mode      = 0,
+               .max_len        = 5,
+       },
+       {
+               .name           = NULL,
+               .column_name    = NULL,
+               .need_print     = 0,
+               .unit_mode      = 0,
+       },
+};
+
+static btrfs_qgroup_filter_func all_filter_funcs[];
+static btrfs_qgroup_comp_func all_comp_funcs[];
+
+void btrfs_qgroup_setup_print_column(enum btrfs_qgroup_column_enum column)
+{
+       int i;
+
+       ASSERT(0 <= column && column <= BTRFS_QGROUP_ALL);
+
+       if (column < BTRFS_QGROUP_ALL) {
+               btrfs_qgroup_columns[column].need_print = 1;
+               return;
+       }
+       for (i = 0; i < BTRFS_QGROUP_ALL; i++)
+               btrfs_qgroup_columns[i].need_print = 1;
+}
+
+void btrfs_qgroup_setup_units(unsigned unit_mode)
+{
+       btrfs_qgroup_columns[BTRFS_QGROUP_RFER].unit_mode = unit_mode;
+       btrfs_qgroup_columns[BTRFS_QGROUP_EXCL].unit_mode = unit_mode;
+       btrfs_qgroup_columns[BTRFS_QGROUP_MAX_RFER].unit_mode = unit_mode;
+       btrfs_qgroup_columns[BTRFS_QGROUP_MAX_EXCL].unit_mode = unit_mode;
+}
+
+static int print_parent_column(struct btrfs_qgroup *qgroup)
+{
+       struct btrfs_qgroup_list *list = NULL;
+       int len = 0;
+
+       list_for_each_entry(list, &qgroup->qgroups, next_qgroup) {
+               len += printf("%llu/%llu",
+                             btrfs_qgroup_level(list->qgroup->qgroupid),
+                             btrfs_qgroup_subvid(list->qgroup->qgroupid));
+               if (!list_is_last(&list->next_qgroup, &qgroup->qgroups))
+                       len += printf(",");
+       }
+       if (list_empty(&qgroup->qgroups))
+               len += printf("---");
+
+       return len;
+}
+
+static int print_child_column(struct btrfs_qgroup *qgroup)
+{
+       struct btrfs_qgroup_list *list = NULL;
+       int len = 0;
+
+       list_for_each_entry(list, &qgroup->members, next_member) {
+               len += printf("%llu/%llu",
+                             btrfs_qgroup_level(list->member->qgroupid),
+                             btrfs_qgroup_subvid(list->member->qgroupid));
+               if (!list_is_last(&list->next_member, &qgroup->members))
+                       len += printf(",");
+       }
+       if (list_empty(&qgroup->members))
+               len += printf("---");
+
+       return len;
+}
+
+static void print_qgroup_column_add_blank(enum btrfs_qgroup_column_enum column,
+                                         int len)
+{
+       len = btrfs_qgroup_columns[column].max_len - len;
+       while (len--)
+               printf(" ");
+}
+
+static void print_qgroup_column(struct btrfs_qgroup *qgroup,
+                               enum btrfs_qgroup_column_enum column)
+{
+       int len;
+       int unit_mode = btrfs_qgroup_columns[column].unit_mode;
+       int max_len = btrfs_qgroup_columns[column].max_len;
+
+       ASSERT(0 <= column && column < BTRFS_QGROUP_ALL);
+
+       switch (column) {
+
+       case BTRFS_QGROUP_QGROUPID:
+               len = printf("%llu/%llu",
+                            btrfs_qgroup_level(qgroup->qgroupid),
+                            btrfs_qgroup_subvid(qgroup->qgroupid));
+               print_qgroup_column_add_blank(BTRFS_QGROUP_QGROUPID, len);
+               break;
+       case BTRFS_QGROUP_RFER:
+               len = printf("%*s", max_len, pretty_size_mode(qgroup->rfer, unit_mode));
+               break;
+       case BTRFS_QGROUP_EXCL:
+               len = printf("%*s", max_len, pretty_size_mode(qgroup->excl, unit_mode));
+               break;
+       case BTRFS_QGROUP_PARENT:
+               len = print_parent_column(qgroup);
+               print_qgroup_column_add_blank(BTRFS_QGROUP_PARENT, len);
+               break;
+       case BTRFS_QGROUP_MAX_RFER:
+               if (qgroup->flags & BTRFS_QGROUP_LIMIT_MAX_RFER)
+                       len = printf("%*s", max_len, pretty_size_mode(qgroup->max_rfer, unit_mode));
+               else
+                       len = printf("%*s", max_len, "none");
+               break;
+       case BTRFS_QGROUP_MAX_EXCL:
+               if (qgroup->flags & BTRFS_QGROUP_LIMIT_MAX_EXCL)
+                       len = printf("%*s", max_len, pretty_size_mode(qgroup->max_excl, unit_mode));
+               else
+                       len = printf("%*s", max_len, "none");
+               break;
+       case BTRFS_QGROUP_CHILD:
+               len = print_child_column(qgroup);
+               print_qgroup_column_add_blank(BTRFS_QGROUP_CHILD, len);
+               break;
+       default:
+               break;
+       }
+}
+
+static void print_single_qgroup_table(struct btrfs_qgroup *qgroup)
+{
+       int i;
+
+       for (i = 0; i < BTRFS_QGROUP_ALL; i++) {
+               if (!btrfs_qgroup_columns[i].need_print)
+                       continue;
+               print_qgroup_column(qgroup, i);
+
+               if (i != BTRFS_QGROUP_CHILD)
+                       printf(" ");
+       }
+       printf("\n");
+}
+
+static void print_table_head(void)
+{
+       int i;
+       int len;
+       int max_len;
+
+       for (i = 0; i < BTRFS_QGROUP_ALL; i++) {
+               max_len = btrfs_qgroup_columns[i].max_len;
+               if (!btrfs_qgroup_columns[i].need_print)
+                       continue;
+               if ((i == BTRFS_QGROUP_QGROUPID) | (i == BTRFS_QGROUP_PARENT) |
+                       (i == BTRFS_QGROUP_CHILD))
+                       printf("%-*s", max_len, btrfs_qgroup_columns[i].name);
+               else
+                       printf("%*s", max_len, btrfs_qgroup_columns[i].name);
+               printf(" ");
+       }
+       printf("\n");
+       for (i = 0; i < BTRFS_QGROUP_ALL; i++) {
+               max_len = btrfs_qgroup_columns[i].max_len;
+               if (!btrfs_qgroup_columns[i].need_print)
+                       continue;
+               if ((i == BTRFS_QGROUP_QGROUPID) | (i == BTRFS_QGROUP_PARENT) |
+                       (i == BTRFS_QGROUP_CHILD)) {
+                       len = strlen(btrfs_qgroup_columns[i].name);
+                       while (len--)
+                               printf("-");
+                       len = max_len - strlen(btrfs_qgroup_columns[i].name);
+                       while (len--)
+                               printf(" ");
+               } else {
+                       len = max_len - strlen(btrfs_qgroup_columns[i].name);
+                       while (len--)
+                               printf(" ");
+                       len = strlen(btrfs_qgroup_columns[i].name);
+                       while (len--)
+                               printf("-");
+               }
+               printf(" ");
+       }
+       printf("\n");
+}
+
+static void qgroup_lookup_init(struct qgroup_lookup *tree)
+{
+       tree->root.rb_node = NULL;
+}
+
+static int comp_entry_with_qgroupid(struct btrfs_qgroup *entry1,
+                                   struct btrfs_qgroup *entry2,
+                                   int is_descending)
+{
+
+       int ret;
+
+       if (entry1->qgroupid > entry2->qgroupid)
+               ret = 1;
+       else if (entry1->qgroupid < entry2->qgroupid)
+               ret = -1;
+       else
+               ret = 0;
+
+       return is_descending ? -ret : ret;
+}
+
+static int comp_entry_with_rfer(struct btrfs_qgroup *entry1,
+                               struct btrfs_qgroup *entry2,
+                               int is_descending)
+{
+       int ret;
+
+       if (entry1->rfer > entry2->rfer)
+               ret = 1;
+       else if (entry1->rfer < entry2->rfer)
+               ret = -1;
+       else
+               ret = 0;
+
+       return is_descending ? -ret : ret;
+}
+
+static int comp_entry_with_excl(struct btrfs_qgroup *entry1,
+                               struct btrfs_qgroup *entry2,
+                               int is_descending)
+{
+       int ret;
+
+       if (entry1->excl > entry2->excl)
+               ret = 1;
+       else if (entry1->excl < entry2->excl)
+               ret = -1;
+       else
+               ret = 0;
+
+       return is_descending ? -ret : ret;
+}
+
+static int comp_entry_with_max_rfer(struct btrfs_qgroup *entry1,
+                                   struct btrfs_qgroup *entry2,
+                                   int is_descending)
+{
+       int ret;
+
+       if (entry1->max_rfer > entry2->max_rfer)
+               ret = 1;
+       else if (entry1->max_rfer < entry2->max_rfer)
+               ret = -1;
+       else
+               ret = 0;
+
+       return is_descending ? -ret : ret;
+}
+
+static int comp_entry_with_max_excl(struct btrfs_qgroup *entry1,
+                                   struct btrfs_qgroup *entry2,
+                                   int is_descending)
+{
+       int ret;
+
+       if (entry1->max_excl > entry2->max_excl)
+               ret = 1;
+       else if (entry1->max_excl < entry2->max_excl)
+               ret = -1;
+       else
+               ret = 0;
+
+       return is_descending ? -ret : ret;
+}
+
+static btrfs_qgroup_comp_func all_comp_funcs[] = {
+       [BTRFS_QGROUP_COMP_QGROUPID]    = comp_entry_with_qgroupid,
+       [BTRFS_QGROUP_COMP_RFER]        = comp_entry_with_rfer,
+       [BTRFS_QGROUP_COMP_EXCL]        = comp_entry_with_excl,
+       [BTRFS_QGROUP_COMP_MAX_RFER]    = comp_entry_with_max_rfer,
+       [BTRFS_QGROUP_COMP_MAX_EXCL]    = comp_entry_with_max_excl
+};
+
+static char *all_sort_items[] = {
+       [BTRFS_QGROUP_COMP_QGROUPID]    = "qgroupid",
+       [BTRFS_QGROUP_COMP_RFER]        = "rfer",
+       [BTRFS_QGROUP_COMP_EXCL]        = "excl",
+       [BTRFS_QGROUP_COMP_MAX_RFER]    = "max_rfer",
+       [BTRFS_QGROUP_COMP_MAX_EXCL]    = "max_excl",
+       [BTRFS_QGROUP_COMP_MAX]         = NULL,
+};
+
+static int  btrfs_qgroup_get_sort_item(char *sort_name)
+{
+       int i;
+
+       for (i = 0; i < BTRFS_QGROUP_COMP_MAX; i++) {
+               if (strcmp(sort_name, all_sort_items[i]) == 0)
+                       return i;
+       }
+       return -1;
+}
+
+struct btrfs_qgroup_comparer_set *btrfs_qgroup_alloc_comparer_set(void)
+{
+       struct btrfs_qgroup_comparer_set *set;
+       int size;
+       size = sizeof(struct btrfs_qgroup_comparer_set) +
+              BTRFS_QGROUP_NCOMPS_INCREASE *
+              sizeof(struct btrfs_qgroup_comparer);
+       set = calloc(1, size);
+       if (!set) {
+               error("memory allocation failed");
+               exit(1);
+       }
+
+       set->total = BTRFS_QGROUP_NCOMPS_INCREASE;
+
+       return set;
+}
+
+int btrfs_qgroup_setup_comparer(struct btrfs_qgroup_comparer_set  **comp_set,
+                               enum btrfs_qgroup_comp_enum comparer,
+                               int is_descending)
+{
+       struct btrfs_qgroup_comparer_set *set = *comp_set;
+       int size;
+
+       ASSERT(set != NULL);
+       ASSERT(comparer < BTRFS_QGROUP_COMP_MAX);
+       ASSERT(set->ncomps <= set->total);
+
+       if (set->ncomps == set->total) {
+               void *tmp;
+
+               size = set->total + BTRFS_QGROUP_NCOMPS_INCREASE;
+               size = sizeof(*set) +
+                      size * sizeof(struct btrfs_qgroup_comparer);
+               tmp = set;
+               set = realloc(set, size);
+               if (!set) {
+                       error("memory allocation failed");
+                       free(tmp);
+                       exit(1);
+               }
+
+               memset(&set->comps[set->total], 0,
+                      BTRFS_QGROUP_NCOMPS_INCREASE *
+                      sizeof(struct btrfs_qgroup_comparer));
+               set->total += BTRFS_QGROUP_NCOMPS_INCREASE;
+               *comp_set = set;
+       }
+
+       ASSERT(set->comps[set->ncomps].comp_func == NULL);
+
+       set->comps[set->ncomps].comp_func = all_comp_funcs[comparer];
+       set->comps[set->ncomps].is_descending = is_descending;
+       set->ncomps++;
+       return 0;
+}
+
+static int sort_comp(struct btrfs_qgroup *entry1, struct btrfs_qgroup *entry2,
+                    struct btrfs_qgroup_comparer_set *set)
+{
+       int qgroupid_compared = 0;
+       int i, ret = 0;
+
+       if (!set || !set->ncomps)
+               goto comp_qgroupid;
+
+       for (i = 0; i < set->ncomps; i++) {
+               if (!set->comps[i].comp_func)
+                       break;
+
+               ret = set->comps[i].comp_func(entry1, entry2,
+                                             set->comps[i].is_descending);
+               if (ret)
+                       return ret;
+
+               if (set->comps[i].comp_func == comp_entry_with_qgroupid)
+                       qgroupid_compared = 1;
+       }
+
+       if (!qgroupid_compared) {
+comp_qgroupid:
+               ret = comp_entry_with_qgroupid(entry1, entry2, 0);
+       }
+
+       return ret;
+}
+
+/*
+ * insert a new root into the tree.  returns the existing root entry
+ * if one is already there.  qgroupid is used
+ * as the key
+ */
+static int qgroup_tree_insert(struct qgroup_lookup *root_tree,
+                             struct btrfs_qgroup *ins)
 {
-       char *s = strchr(p, '/');
-       char *ptr_src_end = p + strlen(p);
-       char *ptr_parse_end = NULL;
-       u64 level;
-       u64 id;
 
-       if (!s) {
-               id = strtoull(p, &ptr_parse_end, 10);
-               if (ptr_parse_end != ptr_src_end)
-                       goto err;
-               return id;
+       struct rb_node **p = &root_tree->root.rb_node;
+       struct rb_node *parent = NULL;
+       struct btrfs_qgroup *curr;
+       int ret;
+
+       while (*p) {
+               parent = *p;
+               curr = rb_entry(parent, struct btrfs_qgroup, rb_node);
+
+               ret = comp_entry_with_qgroupid(ins, curr, 0);
+               if (ret < 0)
+                       p = &(*p)->rb_left;
+               else if (ret > 0)
+                       p = &(*p)->rb_right;
+               else
+                       return -EEXIST;
        }
-       level = strtoull(p, &ptr_parse_end, 10);
-       if (ptr_parse_end != s)
-               goto err;
+       rb_link_node(&ins->rb_node, parent, p);
+       rb_insert_color(&ins->rb_node, &root_tree->root);
+       return 0;
+}
+
+/*
+ *find a given qgroupid in the tree. We return the smallest one,
+ *rb_next can be used to move forward looking for more if required
+ */
+static struct btrfs_qgroup *qgroup_tree_search(struct qgroup_lookup *root_tree,
+                                              u64 qgroupid)
+{
+       struct rb_node *n = root_tree->root.rb_node;
+       struct btrfs_qgroup *entry;
+       struct btrfs_qgroup tmp;
+       int ret;
+
+       tmp.qgroupid = qgroupid;
+
+       while (n) {
+               entry = rb_entry(n, struct btrfs_qgroup, rb_node);
 
-       id = strtoull(s+1, &ptr_parse_end, 10);
-       if (ptr_parse_end != ptr_src_end)
-               goto  err;
+               ret = comp_entry_with_qgroupid(&tmp, entry, 0);
+               if (ret < 0)
+                       n = n->rb_left;
+               else if (ret > 0)
+                       n = n->rb_right;
+               else
+                       return entry;
+
+       }
+       return NULL;
+}
+
+/*
+ * Lookup or insert btrfs_qgroup into qgroup_lookup.
+ *
+ * Search a btrfs_qgroup with @qgroupid from the @qgroup_lookup. If not found,
+ * initialize a btrfs_qgroup with the given qgroupid and insert it to the
+ * @qgroup_lookup.
+ *
+ * Return the pointer to the btrfs_qgroup if found or if inserted successfully.
+ * Return ERR_PTR if any error occurred.
+ */
+static struct btrfs_qgroup *get_or_add_qgroup(
+               struct qgroup_lookup *qgroup_lookup, u64 qgroupid)
+{
+       struct btrfs_qgroup *bq;
+       int ret;
+
+       bq = qgroup_tree_search(qgroup_lookup, qgroupid);
+       if (bq)
+               return bq;
+
+       bq = calloc(1, sizeof(*bq));
+       if (!bq) {
+               error("memory allocation failed");
+               return ERR_PTR(-ENOMEM);
+       }
+
+       bq->qgroupid = qgroupid;
+       INIT_LIST_HEAD(&bq->qgroups);
+       INIT_LIST_HEAD(&bq->members);
+
+       ret = qgroup_tree_insert(qgroup_lookup, bq);
+       if (ret) {
+               error("failed to insert %llu into tree: %s",
+                      (unsigned long long)bq->qgroupid, strerror(-ret));
+               free(bq);
+               return ERR_PTR(ret);
+       }
+
+       return bq;
+}
+
+static int update_qgroup_info(struct qgroup_lookup *qgroup_lookup, u64 qgroupid,
+                             struct btrfs_qgroup_info_item *info)
+{
+       struct btrfs_qgroup *bq;
+
+       bq = get_or_add_qgroup(qgroup_lookup, qgroupid);
+       if (IS_ERR_OR_NULL(bq))
+               return PTR_ERR(bq);
+
+       bq->generation = btrfs_stack_qgroup_info_generation(info);
+       bq->rfer = btrfs_stack_qgroup_info_referenced(info);
+       bq->rfer_cmpr = btrfs_stack_qgroup_info_referenced_compressed(info);
+       bq->excl = btrfs_stack_qgroup_info_exclusive(info);
+       bq->excl_cmpr = btrfs_stack_qgroup_info_exclusive_compressed(info);
+
+       return 0;
+}
+
+static int update_qgroup_limit(struct qgroup_lookup *qgroup_lookup,
+                              u64 qgroupid,
+                              struct btrfs_qgroup_limit_item *limit)
+{
+       struct btrfs_qgroup *bq;
+
+       bq = get_or_add_qgroup(qgroup_lookup, qgroupid);
+       if (IS_ERR_OR_NULL(bq))
+               return PTR_ERR(bq);
+
+       bq->flags = btrfs_stack_qgroup_limit_flags(limit);
+       bq->max_rfer = btrfs_stack_qgroup_limit_max_referenced(limit);
+       bq->max_excl = btrfs_stack_qgroup_limit_max_exclusive(limit);
+       bq->rsv_rfer = btrfs_stack_qgroup_limit_rsv_referenced(limit);
+       bq->rsv_excl = btrfs_stack_qgroup_limit_rsv_exclusive(limit);
+
+       return 0;
+}
+
+static int update_qgroup_relation(struct qgroup_lookup *qgroup_lookup,
+                                 u64 child_id, u64 parent_id)
+{
+       struct btrfs_qgroup *child;
+       struct btrfs_qgroup *parent;
+       struct btrfs_qgroup_list *list;
+
+       child = qgroup_tree_search(qgroup_lookup, child_id);
+       if (!child) {
+               error("cannot find the qgroup %llu/%llu",
+                     btrfs_qgroup_level(child_id),
+                     btrfs_qgroup_subvid(child_id));
+               return -ENOENT;
+       }
+
+       parent = qgroup_tree_search(qgroup_lookup, parent_id);
+       if (!parent) {
+               error("cannot find the qgroup %llu/%llu",
+                     btrfs_qgroup_level(parent_id),
+                     btrfs_qgroup_subvid(parent_id));
+               return -ENOENT;
+       }
+
+       list = malloc(sizeof(*list));
+       if (!list) {
+               error("memory allocation failed");
+               return -ENOMEM;
+       }
+
+       list->qgroup = parent;
+       list->member = child;
+       list_add_tail(&list->next_qgroup, &child->qgroups);
+       list_add_tail(&list->next_member, &parent->members);
+
+       return 0;
+}
+
+static void __free_btrfs_qgroup(struct btrfs_qgroup *bq)
+{
+       struct btrfs_qgroup_list *list;
+       while (!list_empty(&bq->qgroups)) {
+               list = list_entry((&bq->qgroups)->next,
+                                 struct btrfs_qgroup_list,
+                                 next_qgroup);
+               list_del(&list->next_qgroup);
+               list_del(&list->next_member);
+               free(list);
+       }
+       while (!list_empty(&bq->members)) {
+               list = list_entry((&bq->members)->next,
+                                 struct btrfs_qgroup_list,
+                                 next_member);
+               list_del(&list->next_qgroup);
+               list_del(&list->next_member);
+               free(list);
+       }
+       free(bq);
+}
+
+static void __free_all_qgroups(struct qgroup_lookup *root_tree)
+{
+       struct btrfs_qgroup *entry;
+       struct rb_node *n;
+
+       n = rb_first(&root_tree->root);
+       while (n) {
+               entry = rb_entry(n, struct btrfs_qgroup, rb_node);
+               rb_erase(n, &root_tree->root);
+               __free_btrfs_qgroup(entry);
+
+               n = rb_first(&root_tree->root);
+       }
+}
+
+static int filter_all_parent_insert(struct qgroup_lookup *sort_tree,
+                                   struct btrfs_qgroup *bq)
+{
+       struct rb_node **p = &sort_tree->root.rb_node;
+       struct rb_node *parent = NULL;
+       struct btrfs_qgroup *curr;
+       int ret;
+
+       while (*p) {
+               parent = *p;
+               curr = rb_entry(parent, struct btrfs_qgroup, all_parent_node);
+
+               ret = comp_entry_with_qgroupid(bq, curr, 0);
+               if (ret < 0)
+                       p = &(*p)->rb_left;
+               else if (ret > 0)
+                       p = &(*p)->rb_right;
+               else
+                       return -EEXIST;
+       }
+       rb_link_node(&bq->all_parent_node, parent, p);
+       rb_insert_color(&bq->all_parent_node, &sort_tree->root);
+       return 0;
+}
+
+static int filter_by_parent(struct btrfs_qgroup *bq, u64 data)
+{
+       struct btrfs_qgroup *qgroup =
+               (struct btrfs_qgroup *)(unsigned long)data;
+
+       if (data == 0)
+               return 0;
+       if (qgroup->qgroupid == bq->qgroupid)
+               return 1;
+       return 0;
+}
+
+static int filter_by_all_parent(struct btrfs_qgroup *bq, u64 data)
+{
+       struct qgroup_lookup lookup;
+       struct qgroup_lookup *ql = &lookup;
+       struct btrfs_qgroup_list *list;
+       struct rb_node *n;
+       struct btrfs_qgroup *qgroup =
+                        (struct btrfs_qgroup *)(unsigned long)data;
+
+       if (data == 0)
+               return 0;
+       if (bq->qgroupid == qgroup->qgroupid)
+               return 1;
+
+       qgroup_lookup_init(ql);
+       filter_all_parent_insert(ql, qgroup);
+       n = rb_first(&ql->root);
+       while (n) {
+               qgroup = rb_entry(n, struct btrfs_qgroup, all_parent_node);
+               if (!list_empty(&qgroup->qgroups)) {
+                       list_for_each_entry(list, &qgroup->qgroups,
+                                           next_qgroup) {
+                               if ((list->qgroup)->qgroupid == bq->qgroupid)
+                                       return 1;
+                               filter_all_parent_insert(ql, list->qgroup);
+                       }
+               }
+               rb_erase(n, &ql->root);
+               n = rb_first(&ql->root);
+       }
+       return 0;
+}
+
+static btrfs_qgroup_filter_func all_filter_funcs[] = {
+       [BTRFS_QGROUP_FILTER_PARENT]            = filter_by_parent,
+       [BTRFS_QGROUP_FILTER_ALL_PARENT]        = filter_by_all_parent,
+};
+
+struct btrfs_qgroup_filter_set *btrfs_qgroup_alloc_filter_set(void)
+{
+       struct btrfs_qgroup_filter_set *set;
+       int size;
+
+       size = sizeof(struct btrfs_qgroup_filter_set) +
+              BTRFS_QGROUP_NFILTERS_INCREASE *
+              sizeof(struct btrfs_qgroup_filter);
+       set = calloc(1, size);
+       if (!set) {
+               error("memory allocation failed");
+               exit(1);
+       }
+       set->total = BTRFS_QGROUP_NFILTERS_INCREASE;
+
+       return set;
+}
+
+int btrfs_qgroup_setup_filter(struct btrfs_qgroup_filter_set **filter_set,
+                             enum btrfs_qgroup_filter_enum filter, u64 data)
+{
+       struct btrfs_qgroup_filter_set *set = *filter_set;
+       int size;
+
+       ASSERT(set != NULL);
+       ASSERT(filter < BTRFS_QGROUP_FILTER_MAX);
+       ASSERT(set->nfilters <= set->total);
+
+       if (set->nfilters == set->total) {
+               void *tmp;
+
+               size = set->total + BTRFS_QGROUP_NFILTERS_INCREASE;
+               size = sizeof(*set) + size * sizeof(struct btrfs_qgroup_filter);
+
+               tmp = set;
+               set = realloc(set, size);
+               if (!set) {
+                       error("memory allocation failed");
+                       free(tmp);
+                       exit(1);
+               }
+               memset(&set->filters[set->total], 0,
+                      BTRFS_QGROUP_NFILTERS_INCREASE *
+                      sizeof(struct btrfs_qgroup_filter));
+               set->total += BTRFS_QGROUP_NFILTERS_INCREASE;
+               *filter_set = set;
+       }
+
+       ASSERT(set->filters[set->nfilters].filter_func == NULL);
+       set->filters[set->nfilters].filter_func = all_filter_funcs[filter];
+       set->filters[set->nfilters].data = data;
+       set->nfilters++;
+       return 0;
+}
+
+static int filter_qgroup(struct btrfs_qgroup *bq,
+                        struct btrfs_qgroup_filter_set *set)
+{
+       int i, ret;
+
+       if (!set || !set->nfilters)
+               return 1;
+       for (i = 0; i < set->nfilters; i++) {
+               if (!set->filters[i].filter_func)
+                       break;
+               ret = set->filters[i].filter_func(bq, set->filters[i].data);
+               if (!ret)
+                       return 0;
+       }
+       return 1;
+}
+
+static void pre_process_filter_set(struct qgroup_lookup *lookup,
+                                  struct btrfs_qgroup_filter_set *set)
+{
+       int i;
+       struct btrfs_qgroup *qgroup_for_filter = NULL;
+
+       for (i = 0; i < set->nfilters; i++) {
+
+               if (set->filters[i].filter_func == filter_by_all_parent
+                   || set->filters[i].filter_func == filter_by_parent) {
+                       qgroup_for_filter = qgroup_tree_search(lookup,
+                                           set->filters[i].data);
+                       set->filters[i].data =
+                                (u64)(unsigned long)qgroup_for_filter;
+               }
+       }
+}
+
+static int sort_tree_insert(struct qgroup_lookup *sort_tree,
+                           struct btrfs_qgroup *bq,
+                           struct btrfs_qgroup_comparer_set *comp_set)
+{
+       struct rb_node **p = &sort_tree->root.rb_node;
+       struct rb_node *parent = NULL;
+       struct btrfs_qgroup *curr;
+       int ret;
+
+       while (*p) {
+               parent = *p;
+               curr = rb_entry(parent, struct btrfs_qgroup, sort_node);
+
+               ret = sort_comp(bq, curr, comp_set);
+               if (ret < 0)
+                       p = &(*p)->rb_left;
+               else if (ret > 0)
+                       p = &(*p)->rb_right;
+               else
+                       return -EEXIST;
+       }
+       rb_link_node(&bq->sort_node, parent, p);
+       rb_insert_color(&bq->sort_node, &sort_tree->root);
+       return 0;
+}
+
+static void __update_columns_max_len(struct btrfs_qgroup *bq,
+                                    enum btrfs_qgroup_column_enum column)
+{
+       struct btrfs_qgroup_list *list = NULL;
+       char tmp[100];
+       int len;
+       unsigned unit_mode = btrfs_qgroup_columns[column].unit_mode;
+
+       ASSERT(0 <= column && column < BTRFS_QGROUP_ALL);
+
+       switch (column) {
+
+       case BTRFS_QGROUP_QGROUPID:
+               sprintf(tmp, "%llu/%llu",
+                       btrfs_qgroup_level(bq->qgroupid),
+                       btrfs_qgroup_subvid(bq->qgroupid));
+               len = strlen(tmp);
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       case BTRFS_QGROUP_RFER:
+               len = strlen(pretty_size_mode(bq->rfer, unit_mode));
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       case BTRFS_QGROUP_EXCL:
+               len = strlen(pretty_size_mode(bq->excl, unit_mode));
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       case BTRFS_QGROUP_MAX_RFER:
+               len = strlen(pretty_size_mode(bq->max_rfer, unit_mode));
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       case BTRFS_QGROUP_MAX_EXCL:
+               len = strlen(pretty_size_mode(bq->max_excl, unit_mode));
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       case BTRFS_QGROUP_PARENT:
+               len = 0;
+               list_for_each_entry(list, &bq->qgroups, next_qgroup) {
+                       len += sprintf(tmp, "%llu/%llu",
+                               btrfs_qgroup_level(list->qgroup->qgroupid),
+                               btrfs_qgroup_subvid(list->qgroup->qgroupid));
+                       if (!list_is_last(&list->next_qgroup, &bq->qgroups))
+                               len += 1;
+               }
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       case BTRFS_QGROUP_CHILD:
+               len = 0;
+               list_for_each_entry(list, &bq->members, next_member) {
+                       len += sprintf(tmp, "%llu/%llu",
+                               btrfs_qgroup_level(list->member->qgroupid),
+                               btrfs_qgroup_subvid(list->member->qgroupid));
+                       if (!list_is_last(&list->next_member, &bq->members))
+                               len += 1;
+               }
+               if (btrfs_qgroup_columns[column].max_len < len)
+                       btrfs_qgroup_columns[column].max_len = len;
+               break;
+       default:
+               break;
+       }
+
+}
+
+static void update_columns_max_len(struct btrfs_qgroup *bq)
+{
+       int i;
+
+       for (i = 0; i < BTRFS_QGROUP_ALL; i++) {
+               if (!btrfs_qgroup_columns[i].need_print)
+                       continue;
+               __update_columns_max_len(bq, i);
+       }
+}
+
+static void __filter_and_sort_qgroups(struct qgroup_lookup *all_qgroups,
+                                struct qgroup_lookup *sort_tree,
+                                struct btrfs_qgroup_filter_set *filter_set,
+                                struct btrfs_qgroup_comparer_set *comp_set)
+{
+       struct rb_node *n;
+       struct btrfs_qgroup *entry;
+       int ret;
+
+       qgroup_lookup_init(sort_tree);
+       pre_process_filter_set(all_qgroups, filter_set);
+
+       n = rb_last(&all_qgroups->root);
+       while (n) {
+               entry = rb_entry(n, struct btrfs_qgroup, rb_node);
+
+               ret = filter_qgroup(entry, filter_set);
+               if (ret) {
+                       sort_tree_insert(sort_tree, entry, comp_set);
+
+                       update_columns_max_len(entry);
+               }
+               n = rb_prev(n);
+       }
+}
+
+static inline void print_status_flag_warning(u64 flags)
+{
+       if (!(flags & BTRFS_QGROUP_STATUS_FLAG_ON))
+               warning("quota disabled, qgroup data may be out of date");
+       else if (flags & BTRFS_QGROUP_STATUS_FLAG_RESCAN)
+               warning("rescan is running, qgroup data may be incorrect");
+       else if (flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT)
+               warning("qgroup data inconsistent, rescan recommended");
+}
+
+static int __qgroups_search(int fd, struct qgroup_lookup *qgroup_lookup)
+{
+       int ret;
+       struct btrfs_ioctl_search_args args;
+       struct btrfs_ioctl_search_key *sk = &args.key;
+       struct btrfs_ioctl_search_header *sh;
+       unsigned long off = 0;
+       unsigned int i;
+       struct btrfs_qgroup_status_item *si;
+       struct btrfs_qgroup_info_item *info;
+       struct btrfs_qgroup_limit_item *limit;
+       u64 flags;
+       u64 qgroupid;
+       u64 qgroupid1;
+
+       memset(&args, 0, sizeof(args));
+
+       sk->tree_id = BTRFS_QUOTA_TREE_OBJECTID;
+       sk->max_type = BTRFS_QGROUP_RELATION_KEY;
+       sk->min_type = BTRFS_QGROUP_STATUS_KEY;
+       sk->max_objectid = (u64)-1;
+       sk->max_offset = (u64)-1;
+       sk->max_transid = (u64)-1;
+       sk->nr_items = 4096;
+
+       qgroup_lookup_init(qgroup_lookup);
+
+       while (1) {
+               ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+               if (ret < 0) {
+                       if (errno == ENOENT) {
+                               error("can't list qgroups: quotas not enabled");
+                               ret = -ENOTTY;
+                       } else {
+                               error("can't list qgroups: %s",
+                                      strerror(errno));
+                               ret = -errno;
+                       }
+
+                       break;
+               }
+
+               /* the ioctl returns the number of item it found in nr_items */
+               if (sk->nr_items == 0)
+                       break;
+
+               off = 0;
+               /*
+                * for each item, pull the key out of the header and then
+                * read the root_ref item it contains
+                */
+               for (i = 0; i < sk->nr_items; i++) {
+                       sh = (struct btrfs_ioctl_search_header *)(args.buf +
+                                                                 off);
+                       off += sizeof(*sh);
+
+                       switch (btrfs_search_header_type(sh)) {
+                       case BTRFS_QGROUP_STATUS_KEY:
+                               si = (struct btrfs_qgroup_status_item *)
+                                    (args.buf + off);
+                               flags = btrfs_stack_qgroup_status_flags(si);
+
+                               print_status_flag_warning(flags);
+                               break;
+                       case BTRFS_QGROUP_INFO_KEY:
+                               qgroupid = btrfs_search_header_offset(sh);
+                               info = (struct btrfs_qgroup_info_item *)
+                                      (args.buf + off);
+
+                               ret = update_qgroup_info(qgroup_lookup,
+                                                        qgroupid, info);
+                               break;
+                       case BTRFS_QGROUP_LIMIT_KEY:
+                               qgroupid = btrfs_search_header_offset(sh);
+                               limit = (struct btrfs_qgroup_limit_item *)
+                                       (args.buf + off);
+
+                               ret = update_qgroup_limit(qgroup_lookup,
+                                                         qgroupid, limit);
+                               break;
+                       case BTRFS_QGROUP_RELATION_KEY:
+                               qgroupid = btrfs_search_header_offset(sh);
+                               qgroupid1 = btrfs_search_header_objectid(sh);
+
+                               if (qgroupid < qgroupid1)
+                                       break;
+
+                               ret = update_qgroup_relation(qgroup_lookup,
+                                                       qgroupid, qgroupid1);
+                               break;
+                       default:
+                               return ret;
+                       }
+
+                       if (ret)
+                               return ret;
+
+                       off += btrfs_search_header_len(sh);
+
+                       /*
+                        * record the mins in sk so we can make sure the
+                        * next search doesn't repeat this root
+                        */
+                       sk->min_type = btrfs_search_header_type(sh);
+                       sk->min_offset = btrfs_search_header_offset(sh);
+                       sk->min_objectid = btrfs_search_header_objectid(sh);
+               }
+               sk->nr_items = 4096;
+               /*
+                * this iteration is done, step forward one qgroup for the next
+                * ioctl
+                */
+               if (sk->min_offset < (u64)-1)
+                       sk->min_offset++;
+               else
+                       break;
+       }
+
+       return ret;
+}
+
+static void print_all_qgroups(struct qgroup_lookup *qgroup_lookup)
+{
+
+       struct rb_node *n;
+       struct btrfs_qgroup *entry;
+
+       print_table_head();
+
+       n = rb_first(&qgroup_lookup->root);
+       while (n) {
+               entry = rb_entry(n, struct btrfs_qgroup, sort_node);
+               print_single_qgroup_table(entry);
+               n = rb_next(n);
+       }
+}
+
+int btrfs_show_qgroups(int fd,
+                      struct btrfs_qgroup_filter_set *filter_set,
+                      struct btrfs_qgroup_comparer_set *comp_set)
+{
+
+       struct qgroup_lookup qgroup_lookup;
+       struct qgroup_lookup sort_tree;
+       int ret;
+
+       ret = __qgroups_search(fd, &qgroup_lookup);
+       if (ret)
+               return ret;
+       __filter_and_sort_qgroups(&qgroup_lookup, &sort_tree,
+                                 filter_set, comp_set);
+       print_all_qgroups(&sort_tree);
+
+       __free_all_qgroups(&qgroup_lookup);
+       return ret;
+}
+
+int btrfs_qgroup_parse_sort_string(const char *opt_arg,
+                                  struct btrfs_qgroup_comparer_set **comps)
+{
+       int order;
+       int flag;
+       char *p;
+       char **ptr_argv;
+       int what_to_sort;
+       char *opt_tmp;
+       int ret = 0;
+
+       opt_tmp = strdup(opt_arg);
+       if (!opt_tmp)
+               return -ENOMEM;
+
+       p = strtok(opt_tmp, ",");
+       while (p) {
+               flag = 0;
+               ptr_argv = all_sort_items;
+
+               while (*ptr_argv) {
+                       if (strcmp(*ptr_argv, p) == 0) {
+                               flag = 1;
+                               break;
+                       } else {
+                               p++;
+                               if (strcmp(*ptr_argv, p) == 0) {
+                                       flag = 1;
+                                       p--;
+                                       break;
+                               }
+                               p--;
+                       }
+                       ptr_argv++;
+               }
+
+               if (flag == 0) {
+                       ret = -1;
+                       goto out;
+               } else {
+                       if (*p == '+') {
+                               order = 0;
+                               p++;
+                       } else if (*p == '-') {
+                               order = 1;
+                               p++;
+                       } else
+                               order = 0;
+
+                       what_to_sort = btrfs_qgroup_get_sort_item(p);
+                       if (what_to_sort < 0) {
+                               ret = -1;
+                               goto out;
+                       }
+                       btrfs_qgroup_setup_comparer(comps, what_to_sort, order);
+               }
+               p = strtok(NULL, ",");
+       }
 
-       return (level << 48) | id;
-err:
-       fprintf(stderr, "ERROR:invalid qgroupid\n");
-       exit(-1);
+out:
+       free(opt_tmp);
+       return ret;
 }
 
 int qgroup_inherit_size(struct btrfs_qgroup_inherit *p)
@@ -54,8 +1266,8 @@ int qgroup_inherit_size(struct btrfs_qgroup_inherit *p)
                             2 * p->num_excl_copies);
 }
 
-int qgroup_inherit_realloc(struct btrfs_qgroup_inherit **inherit, int n,
-                          int pos)
+static int
+qgroup_inherit_realloc(struct btrfs_qgroup_inherit **inherit, int n, int pos)
 {
        struct btrfs_qgroup_inherit *out;
        int nitems = 0;
@@ -68,13 +1280,13 @@ int qgroup_inherit_realloc(struct btrfs_qgroup_inherit **inherit, int n,
 
        out = calloc(sizeof(*out) + sizeof(out->qgroups[0]) * (nitems + n), 1);
        if (out == NULL) {
-               fprintf(stderr, "ERROR: Not enough memory\n");
-               return 13;
+               error("not enough memory");
+               return -ENOMEM;
        }
 
        if (*inherit) {
                struct btrfs_qgroup_inherit *i = *inherit;
-               int s = sizeof(out->qgroups);
+               int s = sizeof(out->qgroups[0]);
 
                out->num_qgroups = i->num_qgroups;
                out->num_ref_copies = i->num_ref_copies;
@@ -96,8 +1308,8 @@ int qgroup_inherit_add_group(struct btrfs_qgroup_inherit **inherit, char *arg)
        int pos = 0;
 
        if (qgroupid == 0) {
-               fprintf(stderr, "ERROR: bad qgroup specification\n");
-               return 12;
+               error("invalid qgroup specification, qgroupid must not 0");
+               return -EINVAL;
        }
 
        if (*inherit)
@@ -123,8 +1335,8 @@ int qgroup_inherit_add_copy(struct btrfs_qgroup_inherit **inherit, char *arg,
        p = strchr(arg, ':');
        if (!p) {
 bad:
-               fprintf(stderr, "ERROR: bad copy specification\n");
-               return 12;
+               error("invalid copy specification, missing separator :");
+               return -EINVAL;
        }
        *p = 0;
        qgroup_src = parse_qgroupid(arg);