/*
 * Copyright (C) 2012 STRATO.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License v2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 021110-1307, USA.
 */

#include "kerncompat.h"
#include <sys/ioctl.h>
#include <getopt.h>
#include <errno.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include "libbtrfsutil/btrfsutil.h"
#include "kernel-lib/list.h"
#include "kernel-lib/rbtree.h"
#include "kernel-lib/rbtree_types.h"
#include "kernel-shared/accessors.h"
#include "kernel-shared/uapi/btrfs_tree.h"
#include "kernel-shared/uapi/btrfs.h"
#include "kernel-shared/ctree.h"
#include "common/open-utils.h"
#include "common/sysfs-utils.h"
#include "common/utils.h"
#include "common/help.h"
#include "common/units.h"
#include "common/parse-utils.h"
#include "common/string-utils.h"
#include "common/format-output.h"
#include "common/messages.h"
#include "common/tree-search.h"
#include "cmds/commands.h"
#include "cmds/qgroup.h"

enum btrfs_qgroup_comp_enum;
enum btrfs_qgroup_filter_enum;

#define BTRFS_QGROUP_NFILTERS_INCREASE (2 * BTRFS_QGROUP_FILTER_MAX)
#define BTRFS_QGROUP_NCOMPS_INCREASE (2 * BTRFS_QGROUP_COMP_MAX)

/*
 * 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;
};

struct qgroup_lookup {
	/* This matches btrfs_qgroup_status_item::flags. */
	u64 flags;
	struct rb_root root;
};

struct btrfs_qgroup {
	struct qgroup_lookup *lookup;

	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;

	/*
	 * NULL for qgroups with level > 0 or the subvolume is deleted but not
	 * yet fully cleaned.
	 *
	 * A deleted subvolume means it hasn't been fully cleaned, so callers
	 * should not rely on this to determine if a qgroup is stale.
	 *
	 * This member is only to help locating the path of the corresponding
	 * subvolume.
	 */
	const char *path;

	/*
	 * This is only true if the qgroup is level 0 qgroup, and there is
	 * no subvolume tree for the qgroup at all.
	 */
	bool stale;

	/*
	 * info_item
	 */
	struct btrfs_qgroup_info info;

	/*
	 *limit_item
	 */
	struct btrfs_qgroup_limit limit;

	/*qgroups this group is member of*/
	struct list_head qgroups;
	/*qgroups that are members of this group*/
	struct list_head members;
};

typedef int (*btrfs_qgroup_filter_func)(struct btrfs_qgroup *, u64);
typedef int (*btrfs_qgroup_comp_func)(struct btrfs_qgroup *,
				      struct btrfs_qgroup *, int);

struct btrfs_qgroup_filter {
	btrfs_qgroup_filter_func filter_func;
	u64 data;
};

struct btrfs_qgroup_comparer {
	btrfs_qgroup_comp_func comp_func;
	int is_descending;
};

struct btrfs_qgroup_filter_set {
	int total;
	int nfilters;
	struct btrfs_qgroup_filter filters[0];
};

struct btrfs_qgroup_comparer_set {
	int total;
	int ncomps;
	struct btrfs_qgroup_comparer comps[0];
};

enum btrfs_qgroup_column_enum {
	BTRFS_QGROUP_QGROUPID,
	BTRFS_QGROUP_RFER,
	BTRFS_QGROUP_EXCL,
	BTRFS_QGROUP_MAX_RFER,
	BTRFS_QGROUP_MAX_EXCL,
	BTRFS_QGROUP_PARENT,
	BTRFS_QGROUP_CHILD,
	BTRFS_QGROUP_PATH,
	BTRFS_QGROUP_ALL,
};

enum btrfs_qgroup_comp_enum {
	BTRFS_QGROUP_COMP_QGROUPID,
	BTRFS_QGROUP_COMP_PATH,
	BTRFS_QGROUP_COMP_RFER,
	BTRFS_QGROUP_COMP_EXCL,
	BTRFS_QGROUP_COMP_MAX_RFER,
	BTRFS_QGROUP_COMP_MAX_EXCL,
	BTRFS_QGROUP_COMP_MAX
};

enum btrfs_qgroup_filter_enum {
	BTRFS_QGROUP_FILTER_PARENT,
	BTRFS_QGROUP_FILTER_ALL_PARENT,
	BTRFS_QGROUP_FILTER_MAX,
};

/*
 * 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	= 9,
	},
	{
		.name		= "rfer",
		.column_name	= "Referenced",
		.need_print	= 1,
		.unit_mode	= UNITS_DEFAULT,
		.max_len	= 12,
	},
	{
		.name		= "excl",
		.column_name	= "Exclusive",
		.need_print	= 1,
		.unit_mode	= UNITS_DEFAULT,
		.max_len	= 12,
	},
	{	.name		= "max_rfer",
		.column_name	= "Max referenced",
		.need_print	= 0,
		.unit_mode	= UNITS_DEFAULT,
		.max_len	= 15,
	},
	{
		.name		= "max_excl",
		.column_name	= "Max exclusive",
		.need_print	= 0,
		.unit_mode	= UNITS_DEFAULT,
		.max_len	= 14,
	},
	{
		.name		= "parent",
		.column_name	= "Parent",
		.need_print	= 0,
		.unit_mode	= 0,
		.max_len	= 8,
	},
	{
		.name		= "child",
		.column_name	= "Child",
		.need_print	= 0,
		.unit_mode	= 0,
		.max_len	= 5,
	},
	{
		.name		= "path",
		.column_name	= "Path",
		.need_print	= 1,
		.unit_mode	= 0,
		.max_len	= 6,
	},
	{
		.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[];

static bool is_qgroup_empty(const struct btrfs_qgroup *qg)
{
	return !(qg->info.referenced || qg->info.exclusive ||
		 qg->info.referenced_compressed ||
		 qg->info.exclusive_compressed);
}

static void qgroup_setup_print_column(enum btrfs_qgroup_column_enum column)
{
	int i;

	UASSERT(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;
}

static void 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("%u/%llu",
			      btrfs_qgroup_level(list->qgroup->qgroupid),
			      btrfs_qgroup_subvolid(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("%u/%llu",
			      btrfs_qgroup_level(list->member->qgroupid),
			      btrfs_qgroup_subvolid(list->member->qgroupid));
		if (!list_is_last(&list->next_member, &qgroup->members))
			len += printf(",");
	}
	if (list_empty(&qgroup->members))
		len += printf("-");

	return len;
}

static int print_u64(u64 value, int unit_mode, int max_len)
{
	return printf("%*s", max_len, pretty_size_mode(value, unit_mode));
}

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 const char *get_qgroup_path(struct btrfs_qgroup *qgroup)
{
	if (qgroup->path)
		return qgroup->path;

	/* No path but not stale either, the qgroup is being deleted. */
	if (!qgroup->stale)
		return "<under deletion>";
	/*
	 * Squota mode stale qgroup, but not empty.
	 * This is fully deleted but still necessary.
	 */
	if (qgroup->stale &&
	    (qgroup->lookup->flags & BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE) &&
	    !is_qgroup_empty(qgroup))
		return "<squota space holder>";

	return "<stale>";
}

static void print_path_column(struct btrfs_qgroup *qgroup)
{
	struct btrfs_qgroup_list *list = NULL;

	pr_verbose(LOG_DEFAULT, "  ");
	if (btrfs_qgroup_level(qgroup->qgroupid) > 0) {
		int count = 0;

		list_for_each_entry(list, &qgroup->qgroups, next_qgroup) {
			struct btrfs_qgroup *member = list->qgroup;
			u64 qgroupid = member->qgroupid;
			u64 level = btrfs_qgroup_level(qgroupid);
			u64 sid = btrfs_qgroup_subvolid(qgroupid);

			if (count)
				pr_verbose(LOG_DEFAULT, " ");
			if (level == 0) {
				pr_verbose(LOG_DEFAULT, "%s",
					   get_qgroup_path(qgroup));
			} else {
				pr_verbose(LOG_DEFAULT, "%llu/%llu", level, sid);
			}
			count++;
		}
		pr_verbose(LOG_DEFAULT, "<%u member qgroup%c>", count,
			       (count != 1 ? 's' : '\0'));
	} else if (qgroup->path) {
		pr_verbose(LOG_DEFAULT, "%s%s", (*qgroup->path ? "" : "<toplevel>"), qgroup->path);
	} else {
		pr_verbose(LOG_DEFAULT, "%s", get_qgroup_path(qgroup));
	}
}

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;

	UASSERT(0 <= column && column < BTRFS_QGROUP_ALL);

	switch (column) {

	case BTRFS_QGROUP_QGROUPID:
		len = printf("%u/%llu",
			     btrfs_qgroup_level(qgroup->qgroupid),
			     btrfs_qgroup_subvolid(qgroup->qgroupid));
		print_qgroup_column_add_blank(BTRFS_QGROUP_QGROUPID, len);
		break;
	case BTRFS_QGROUP_RFER:
		len = print_u64(qgroup->info.referenced, unit_mode, max_len);
		break;
	case BTRFS_QGROUP_EXCL:
		len = print_u64(qgroup->info.exclusive, unit_mode, max_len);
		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->limit.flags & BTRFS_QGROUP_LIMIT_MAX_RFER)
			len = print_u64(qgroup->limit.max_referenced,
					unit_mode, max_len);
		else
			len = printf("%*s", max_len, "none");
		break;
	case BTRFS_QGROUP_MAX_EXCL:
		if (qgroup->limit.flags & BTRFS_QGROUP_LIMIT_MAX_EXCL)
			len = print_u64(qgroup->limit.max_exclusive,
					unit_mode, max_len);
		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;
	case BTRFS_QGROUP_PATH:
		print_path_column(qgroup);
		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_ALL - 1)
			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].column_name);
		else
			printf("%*s", max_len, btrfs_qgroup_columns[i].column_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].column_name);
			while (len--)
				printf("-");
			len = max_len - strlen(btrfs_qgroup_columns[i].column_name);
			while (len--)
				printf(" ");
		} else {
			len = max_len - strlen(btrfs_qgroup_columns[i].column_name);
			while (len--)
				printf(" ");
			len = strlen(btrfs_qgroup_columns[i].column_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;
}

/* Sorts first-level qgroups by path and nested qgroups by qgroupid */
static int comp_entry_with_path(struct btrfs_qgroup *entry1,
				    struct btrfs_qgroup *entry2,
				    int is_descending)
{
	int ret = 0;
	const char *p1 = entry1->path;
	const char *p2 = entry2->path;
	u64 level1 = btrfs_qgroup_level(entry1->qgroupid);
	u64 level2 = btrfs_qgroup_level(entry2->qgroupid);

	if (level1 != level2) {
		if (entry1->qgroupid > entry2->qgroupid)
			ret = 1;
		else if (entry1->qgroupid < entry2->qgroupid)
			ret = -1;
	}

	if (ret)
		goto out;

	if (!p1) {
		ret = p2 ? 1 : 0;
		goto out;
	} else if (!p2) {
		ret = -1;
		goto out;
	}

	while (*p1 && *p2) {
		if (*p1 != *p2)
			break;
		p1++;
		p2++;
	}

	if (*p1 == '/')
		ret = 1;
	else if (*p2 == '/')
		ret = -1;
	else if (*p1 > *p2)
		ret = 1;
	else if (*p1 < *p2)
		ret = -1;
out:
	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->info.referenced > entry2->info.referenced)
		ret = 1;
	else if (entry1->info.referenced < entry2->info.referenced)
		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->info.exclusive > entry2->info.exclusive)
		ret = 1;
	else if (entry1->info.exclusive < entry2->info.exclusive)
		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->limit.max_referenced > entry2->limit.max_referenced)
		ret = 1;
	else if (entry1->limit.max_referenced < entry2->limit.max_referenced)
		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->limit.max_exclusive > entry2->limit.max_exclusive)
		ret = 1;
	else if (entry1->limit.max_exclusive < entry2->limit.max_exclusive)
		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_PATH]	= comp_entry_with_path,
	[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_PATH]	= "path",
	[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 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;
}

static struct btrfs_qgroup_comparer_set *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_msg(ERROR_MSG_MEMORY, NULL);
		exit(1);
	}

	set->total = BTRFS_QGROUP_NCOMPS_INCREASE;

	return set;
}

static int 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;

	UASSERT(set != NULL);
	UASSERT(comparer < BTRFS_QGROUP_COMP_MAX);
	UASSERT(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) {
			free(tmp);
			return -ENOMEM;
		}

		memset(&set->comps[set->total], 0,
		       BTRFS_QGROUP_NCOMPS_INCREASE *
		       sizeof(struct btrfs_qgroup_comparer));
		set->total += BTRFS_QGROUP_NCOMPS_INCREASE;
		*comp_set = set;
	}

	UASSERT(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)
{
	bool qgroupid_compared = false;
	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 = true;
	}

	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)
{

	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;
	}
	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);

		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(int fd,
		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_msg(ERROR_MSG_MEMORY, NULL);
		return ERR_PTR(-ENOMEM);
	}

	bq->lookup = qgroup_lookup;
	bq->qgroupid = qgroupid;
	INIT_LIST_HEAD(&bq->qgroups);
	INIT_LIST_HEAD(&bq->members);

	if (btrfs_qgroup_level(qgroupid) == 0) {
		enum btrfs_util_error uret;
		char *path;

		uret = btrfs_util_subvolume_get_path_fd(fd, qgroupid, &path);
		if (uret == BTRFS_UTIL_OK)
			bq->path = path;
		else if (uret != BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) {
			error("%s", btrfs_util_strerror(uret));
			if (uret == BTRFS_UTIL_ERROR_NO_MEMORY)
				return ERR_PTR(-ENOMEM);
			else
				return ERR_PTR(-EIO);
		}
		/*
		 * Do a correct stale detection by searching for the ROOT_ITEM of
		 * the subvolume.
		 *
		 * Passing @subvol as NULL will force the search to only search
		 * for the ROOT_ITEM.
		 */
		uret = btrfs_util_subvolume_get_info_fd(fd, qgroupid, NULL);
		if (uret == BTRFS_UTIL_OK) {
			bq->stale = false;
		} else if (uret == BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) {
			bq->stale = true;
		} else {
			warning("failed to search root item for qgroup %u/%llu, assuming it not stale",
				btrfs_qgroup_level(qgroupid),
				btrfs_qgroup_subvolid(qgroupid));
			bq->stale = false;
		}
	}

	ret = qgroup_tree_insert(qgroup_lookup, bq);
	if (ret) {
		errno = -ret;
		error("failed to insert %llu into tree: %m", bq->qgroupid);
		free(bq);
		return ERR_PTR(ret);
	}

	return bq;
}

static int update_qgroup_info(int fd, struct qgroup_lookup *qgroup_lookup, u64 qgroupid,
			      struct btrfs_qgroup_info_item *info)
{
	struct btrfs_qgroup *bq;

	bq = get_or_add_qgroup(fd, qgroup_lookup, qgroupid);
	if (IS_ERR_OR_NULL(bq))
		return PTR_ERR(bq);

	bq->info.generation = btrfs_stack_qgroup_info_generation(info);
	bq->info.referenced = btrfs_stack_qgroup_info_rfer(info);
	bq->info.referenced_compressed =
		btrfs_stack_qgroup_info_rfer_cmpr(info);
	bq->info.exclusive = btrfs_stack_qgroup_info_excl(info);
	bq->info.exclusive_compressed = btrfs_stack_qgroup_info_excl_cmpr(info);

	return 0;
}

static int update_qgroup_limit(int fd, struct qgroup_lookup *qgroup_lookup,
			       u64 qgroupid,
			       struct btrfs_qgroup_limit_item *limit)
{
	struct btrfs_qgroup *bq;

	bq = get_or_add_qgroup(fd, qgroup_lookup, qgroupid);
	if (IS_ERR_OR_NULL(bq))
		return PTR_ERR(bq);

	bq->limit.flags = btrfs_stack_qgroup_limit_flags(limit);
	bq->limit.max_referenced = btrfs_stack_qgroup_limit_max_rfer(limit);
	bq->limit.max_exclusive = btrfs_stack_qgroup_limit_max_excl(limit);
	bq->limit.rsv_referenced = btrfs_stack_qgroup_limit_rsv_rfer(limit);
	bq->limit.rsv_exclusive = btrfs_stack_qgroup_limit_rsv_excl(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 %u/%llu",
		      btrfs_qgroup_level(child_id),
		      btrfs_qgroup_subvolid(child_id));
		return -ENOENT;
	}

	parent = qgroup_tree_search(qgroup_lookup, parent_id);
	if (!parent) {
		error("cannot find the qgroup %u/%llu",
		      btrfs_qgroup_level(parent_id),
		      btrfs_qgroup_subvolid(parent_id));
		return -ENOENT;
	}

	list = malloc(sizeof(*list));
	if (!list) {
		error_msg(ERROR_MSG_MEMORY, NULL);
		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);
	}
	if (bq->path)
		free((void *)bq->path);
	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,
};

static struct btrfs_qgroup_filter_set *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_msg(ERROR_MSG_MEMORY, NULL);
		exit(1);
	}
	set->total = BTRFS_QGROUP_NFILTERS_INCREASE;

	return set;
}

static int 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;

	UASSERT(set != NULL);
	UASSERT(filter < BTRFS_QGROUP_FILTER_MAX);
	UASSERT(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_msg(ERROR_MSG_MEMORY, NULL);
			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;
	}

	UASSERT(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;

	UASSERT(0 <= column && column < BTRFS_QGROUP_ALL);

	switch (column) {

	case BTRFS_QGROUP_QGROUPID:
		sprintf(tmp, "%u/%llu",
			btrfs_qgroup_level(bq->qgroupid),
			btrfs_qgroup_subvolid(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->info.referenced, 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->info.exclusive, 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->limit.max_referenced,
			     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->limit.max_exclusive,
			     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, "%u/%llu",
				btrfs_qgroup_level(list->qgroup->qgroupid),
				btrfs_qgroup_subvolid(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, "%u/%llu",
				btrfs_qgroup_level(list->member->qgroupid),
				btrfs_qgroup_subvolid(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 bool key_in_range(const struct btrfs_key *key,
			 const struct btrfs_ioctl_search_key *sk)
{
	if (key->objectid < sk->min_objectid ||
	    key->objectid > sk->max_objectid)
		return false;

	if (key->type < sk->min_type ||
	    key->type > sk->max_type)
		return false;

	if (key->offset < sk->min_offset ||
	    key->offset > sk->max_offset)
		return false;

	return true;
}

static int quota_enabled(int fd) {
	struct btrfs_tree_search_args args = { 0 };
	struct btrfs_ioctl_search_key *sk;
	int ret;

	sk = btrfs_tree_search_sk(&args);
	sk->tree_id = BTRFS_QUOTA_TREE_OBJECTID;
	sk->min_type = 0;
	sk->max_type = (u8)-1;
	sk->max_objectid = (u64)-1;
	sk->max_offset = (u64)-1;
	sk->max_transid = (u64)-1;
	sk->nr_items = 1;

	ret = btrfs_tree_search_ioctl(fd, &args);
	if (ret < 0)
		ret = -errno;
	return ret;
}

static int __qgroups_search(int fd, struct btrfs_tree_search_args *args,
			    struct qgroup_lookup *qgroup_lookup)
{
	int ret;
	struct btrfs_ioctl_search_key *sk;
	struct btrfs_ioctl_search_key filter_key;
	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 child, parent;

	sk = btrfs_tree_search_sk(args);
	filter_key = *sk;

	qgroup_lookup_init(qgroup_lookup);

	while (1) {
		ret = btrfs_tree_search_ioctl(fd, args);
		if (ret < 0) {
			if (errno == ENOENT)
				ret = -ENOTTY;
			else
				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++) {
			struct btrfs_key key;
			struct btrfs_ioctl_search_header sh;

			memcpy(&sh, btrfs_tree_search_data(args, off), sizeof(sh));
			off += sizeof(sh);

			key.objectid = sh.objectid;
			key.type = sh.type;
			key.offset = sh.offset;

			if (!key_in_range(&key, &filter_key))
				goto next;

			switch (key.type) {
			case BTRFS_QGROUP_STATUS_KEY:
				si = btrfs_tree_search_data(args, off);
				flags = btrfs_stack_qgroup_status_flags(si);
				qgroup_lookup->flags = flags;

				print_status_flag_warning(flags);
				break;
			case BTRFS_QGROUP_INFO_KEY:
				qgroupid = key.offset;
				info = btrfs_tree_search_data(args, off);

				ret = update_qgroup_info(fd, qgroup_lookup,
							 qgroupid, info);
				break;
			case BTRFS_QGROUP_LIMIT_KEY:
				qgroupid = key.offset;
				limit = btrfs_tree_search_data(args, off);

				ret = update_qgroup_limit(fd, qgroup_lookup,
							  qgroupid, limit);
				break;
			case BTRFS_QGROUP_RELATION_KEY:
				child = key.offset;
				parent = key.objectid;

				if (parent <= child)
					break;

				ret = update_qgroup_relation(qgroup_lookup,
							     child, parent);
				break;
			default:
				return ret;
			}

			if (ret)
				return ret;

next:
			off += sh.len;

			/*
			 * record the mins in sk so we can make sure the
			 * next search doesn't repeat this root
			 */
			sk->min_objectid = key.objectid;
			sk->min_type = key.type;
			sk->min_offset = key.offset;
		}
		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 int qgroups_search_all(int fd, struct qgroup_lookup *qgroup_lookup)
{
	struct btrfs_tree_search_args args;
	struct btrfs_ioctl_search_key *sk;
	int ret;

	memset(&args, 0, sizeof(args));
	sk = btrfs_tree_search_sk(&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;

	ret = __qgroups_search(fd, &args, qgroup_lookup);
	if (ret == -ENOTTY) {
		error("can't list qgroups: quotas not enabled");
	} else if (ret < 0) {
		errno = -ret;
		error("can't list qgroups: %m");
	}
	return ret;
}

int btrfs_qgroup_query(int fd, u64 qgroupid, struct btrfs_qgroup_stats *stats)
{
	struct btrfs_tree_search_args args;
	struct btrfs_ioctl_search_key *sk;
	struct qgroup_lookup qgroup_lookup;
	struct btrfs_qgroup *qgroup;
	struct rb_node *n;
	int ret;

	memset(&args, 0, sizeof(args));
	sk = btrfs_tree_search_sk(&args);
	sk->tree_id = BTRFS_QUOTA_TREE_OBJECTID;
	sk->min_objectid = 0;
	sk->min_type = BTRFS_QGROUP_INFO_KEY;
	sk->min_offset = qgroupid;
	sk->max_objectid = 0;
	sk->max_type = BTRFS_QGROUP_LIMIT_KEY;
	sk->max_offset = qgroupid;
	sk->max_transid = (u64)-1;
	sk->nr_items = 4096;

	ret = __qgroups_search(fd, &args, &qgroup_lookup);
	if (ret < 0)
		return ret;

	ret = -ENODATA;
	n = rb_first(&qgroup_lookup.root);
	if (n) {
		qgroup = rb_entry(n, struct btrfs_qgroup, rb_node);
		stats->qgroupid = qgroup->qgroupid;
		stats->info = qgroup->info;
		stats->limit = qgroup->limit;

		ret = 0;
	}

	__free_all_qgroups(&qgroup_lookup);
	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);
	}
}

static const struct rowspec qgroup_show_rowspec[] = {
	{ .key = "qgroupid", .fmt = "qgroupid", .out_json = "qgroupid" },
	{ .key = "referenced", .fmt = "%llu", .out_json = "referenced" },
	{ .key = "exclusive", .fmt = "%llu", .out_json = "exclusive" },
	{ .key = "max_referenced", .fmt = "%llu", .out_json = "max_referenced" },
	/* Special value if limits not set. */
	{ .key = "max_referenced-none", .fmt = "%s", .out_json = "max_referenced" },
	{ .key = "max_exclusive", .fmt = "%llu", .out_json = "max_exclusive" },
	/* Special value if limits not set. */
	{ .key = "max_exclusive-none", .fmt = "%s", .out_json = "max_exclusive" },
	{ .key = "path", .fmt = "str", .out_json = "path" },
	{ .key = "parents", .fmt = "list", .out_json = "parents" },
	{ .key = "children", .fmt = "list", .out_json = "children" },
	/* Workaround for printing qgroupid in the list as a plain value */
	{ .key = "qgroupid-list", .fmt = "qgroupid", .out_json = NULL },
	ROWSPEC_END
};

static void print_all_qgroups_json(struct qgroup_lookup *qgroup_lookup)
{
	struct rb_node *n;
	struct btrfs_qgroup *qgroup;
	struct format_ctx fctx;

	fmt_start(&fctx, qgroup_show_rowspec, 24, 0);
	fmt_print_start_group(&fctx, "qgroup-show", JSON_TYPE_ARRAY);

	n = rb_first(&qgroup_lookup->root);
	while (n) {
		struct btrfs_qgroup_list *list = NULL;

		qgroup = rb_entry(n, struct btrfs_qgroup, sort_node);

		fmt_print_start_group(&fctx, NULL, JSON_TYPE_MAP);

		fmt_print(&fctx, "qgroupid",
				(int)btrfs_qgroup_level(qgroup->qgroupid),
				btrfs_qgroup_subvolid(qgroup->qgroupid));
		fmt_print(&fctx, "referenced", qgroup->info.referenced);
		if (qgroup->limit.flags & BTRFS_QGROUP_LIMIT_MAX_RFER)
			fmt_print(&fctx, "max_referenced", qgroup->limit.max_referenced);
		else
			fmt_print(&fctx, "max_referenced-none", "none");
		fmt_print(&fctx, "exclusive", qgroup->info.exclusive);
		if (qgroup->limit.flags & BTRFS_QGROUP_LIMIT_MAX_EXCL)
			fmt_print(&fctx, "max_exclusive", qgroup->limit.max_exclusive);
		else
			fmt_print(&fctx, "max_exclusive-none", "none");
		fmt_print(&fctx, "path", qgroup->path ?: "");

		fmt_print_start_group(&fctx, "parents", JSON_TYPE_ARRAY);
		list_for_each_entry(list, &qgroup->qgroups, next_qgroup) {
			fmt_print(&fctx, "qgroupid-list",
				btrfs_qgroup_level(list->qgroup->qgroupid),
				btrfs_qgroup_subvolid(list->qgroup->qgroupid));
		}
		fmt_print_end_group(&fctx, "parents");

		fmt_print_start_group(&fctx, "children", JSON_TYPE_ARRAY);
		list_for_each_entry(list, &qgroup->members, next_member) {
			fmt_print(&fctx, "qgroupid-list",
				btrfs_qgroup_level(list->member->qgroupid),
				btrfs_qgroup_subvolid(list->member->qgroupid));
		}
		fmt_print_end_group(&fctx, "children");

		fmt_print_end_group(&fctx, NULL);

		n = rb_next(n);
	}
	fmt_print_end_group(&fctx, "qgroup-show");
	fmt_end(&fctx);
}

/*
 * Tree search based 'inconsistent' flag is only updated at transaction commit
 * time.  Thus even if the qgroup_status flag shows consistent, the qgroup may
 * already be in an inconsistent state.
 */
static void check_qgroup_sysfs_inconsistent(int fd, const struct qgroup_lookup *qgroup_lookup)
{
	u64 value;
	int sysfs_fd;
	int ret;

	if (qgroup_lookup->flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT)
		return;
	sysfs_fd = sysfs_open_fsid_file(fd, "qgroups/inconsistent");
	if (fd < 0)
		return;
	ret = sysfs_read_fsid_file_u64(fd, "qgroups/inconsistent", &value);
	if (ret < 0)
		goto out;
	if (value)
		warning("qgroup data inconsistent, rescan recommended");
out:
	close(sysfs_fd);
}

static int 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_all(fd, &qgroup_lookup);
	if (ret)
		return ret;

	check_qgroup_sysfs_inconsistent(fd, &qgroup_lookup);
	__filter_and_sort_qgroups(&qgroup_lookup, &sort_tree,
				  filter_set, comp_set);
	if (bconf.output_format == CMD_FORMAT_JSON)
		print_all_qgroups_json(&sort_tree);
	else
		print_all_qgroups(&sort_tree);

	__free_all_qgroups(&qgroup_lookup);
	return ret;
}

/*
 * Parse sort string and allocate new comparator.
 *
 * Return: 0 no errors while parsing
 *         1 parse error
 *        <0 other errors
 */
static int qgroup_parse_sort_string(const char *opt_arg,
				   struct btrfs_qgroup_comparer_set **comps)
{
	bool order;
	bool flag;
	char *p;
	char **ptr_argv;
	int what_to_sort;
	char *opt_tmp;
	int ret = 0, ret2;

	opt_tmp = strdup(opt_arg);
	if (!opt_tmp)
		return -ENOMEM;

	p = strtok(opt_tmp, ",");
	while (p) {
		flag = false;
		ptr_argv = all_sort_items;

		while (*ptr_argv) {
			if (strcmp(*ptr_argv, p) == 0) {
				flag = true;
				break;
			} else {
				p++;
				if (strcmp(*ptr_argv, p) == 0) {
					flag = true;
					p--;
					break;
				}
				p--;
			}
			ptr_argv++;
		}

		if (!flag) {
			ret = 1;
			goto out;
		} else {
			if (*p == '+') {
				order = false;
				p++;
			} else if (*p == '-') {
				order = true;
				p++;
			} else
				order = false;

			what_to_sort = qgroup_get_sort_item(p);
			if (what_to_sort < 0) {
				ret = 1;
				goto out;
			}
			ret2 = qgroup_setup_comparer(comps, what_to_sort, order);
			if (ret2 < 0) {
				ret = ret2;
				goto out;
			}
		}
		p = strtok(NULL, ",");
	}

out:
	free(opt_tmp);
	return ret;
}

static const char * const qgroup_cmd_group_usage[] = {
	"btrfs qgroup <command> [options] <path>",
	NULL
};

static int _cmd_qgroup_assign(const struct cmd_struct *cmd, int assign,
			      int argc, char **argv)
{
	int ret = 0;
	int fd;
	bool rescan = true;
	char *path;
	struct btrfs_ioctl_qgroup_assign_args args;

	optind = 0;
	while (1) {
		enum { GETOPT_VAL_RESCAN = GETOPT_VAL_FIRST, GETOPT_VAL_NO_RESCAN };
		static const struct option long_options[] = {
			{ "rescan", no_argument, NULL, GETOPT_VAL_RESCAN },
			{ "no-rescan", no_argument, NULL, GETOPT_VAL_NO_RESCAN },
			{ NULL, 0, NULL, 0 }
		};
		int c;

		c = getopt_long(argc, argv, "", long_options, NULL);
		if (c < 0)
			break;

		switch (c) {
		case GETOPT_VAL_RESCAN:
			rescan = true;
			break;
		case GETOPT_VAL_NO_RESCAN:
			rescan = false;
			break;
		default:
			usage_unknown_option(cmd, argv);
		}
	}

	if (check_argc_exact(argc - optind, 3))
		return 1;

	memset(&args, 0, sizeof(args));
	args.assign = assign;
	args.src = parse_qgroupid_or_path(argv[optind]);
	args.dst = parse_qgroupid_or_path(argv[optind + 1]);

	path = argv[optind + 2];

	/*
	 * FIXME src should accept subvol path
	 */
	if (btrfs_qgroup_level(args.src) >= btrfs_qgroup_level(args.dst)) {
		error("bad relation requested: %s", path);
		return 1;
	}
	fd = btrfs_open_dir(path);
	if (fd < 0)
		return 1;

	ret = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args);
	if (ret < 0) {
		error("unable to assign quota group: %s",
				errno == ENOTCONN ? "quota not enabled"
						: strerror(errno));
		close(fd);
		return 1;
	}

	/*
	 * If ret > 0, it means assign caused qgroup data inconsistent state.
	 * Schedule a quota rescan if requested.
	 *
	 * The return value change only happens in newer kernel. But will not
	 * cause problem since old kernel has a bug that will never clear
	 * INCONSISTENT bit.
	 */
	if (ret > 0) {
		if (rescan) {
			struct btrfs_ioctl_quota_rescan_args qargs;

			printf("Quota data changed, rescan scheduled\n");
			memset(&qargs, 0, sizeof(qargs));
			ret = ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &qargs);
			if (ret < 0)
				error("quota rescan failed: %m");
		} else {
			warning("quotas may be inconsistent, rescan needed");
			ret = 0;
		}
	}
	close(fd);
	return ret;
}

static int _cmd_qgroup_create(int create, int argc, char **argv)
{
	int ret = 0;
	int fd;
	char *path;
	struct btrfs_ioctl_qgroup_create_args args;

	if (check_argc_exact(argc - optind, 2))
		return 1;

	memset(&args, 0, sizeof(args));
	args.create = create;
	ret = parse_qgroupid(argv[optind], &args.qgroupid);
	if (ret < 0) {
		error("invalid qgroupid %s", argv[optind]);
		return 1;
	}
	path = argv[optind + 1];

	fd = btrfs_open_dir(path);
	if (fd < 0)
		return 1;

	ret = ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args);
	close(fd);
	if (ret < 0) {
		error("unable to %s quota group: %s",
			create ? "create":"destroy",
				errno == ENOTCONN ? "quota not enabled"
						: strerror(errno));
		return 1;
	}
	return 0;
}

static const char * const cmd_qgroup_assign_usage[] = {
	"btrfs qgroup assign [options] <src> <dst> <path>",
	"Assign SRC as the child qgroup of DST.",
	"Assign SRC qgroup as the child qgroup of DST, where the level of DST",
	"must be higher than SRC. The quota accounting will be inconsistent",
	"until the next rescan.",
	"",
	OPTLINE("--rescan", "schedule quota rescan if needed"),
	OPTLINE("--no-rescan", "don't schedule quota rescan"),
	NULL
};

static int cmd_qgroup_assign(const struct cmd_struct *cmd,
			     int argc, char **argv)
{
	return _cmd_qgroup_assign(cmd, 1, argc, argv);
}
static DEFINE_SIMPLE_COMMAND(qgroup_assign, "assign");

static const char * const cmd_qgroup_remove_usage[] = {
	"btrfs qgroup remove [options] <src> <dst> <path>",
	"Remove the relation between child qgroup SRC from DST.",
	"Remove the relation between SRC and DST qgroups. The quota accounting",
	"will be inconsistent until the next rescan.",
	"",
	OPTLINE("--rescan", "schedule quota rescan if needed"),
	OPTLINE("--no-rescan", "don't schedule quota rescan"),
	NULL
};

static int cmd_qgroup_remove(const struct cmd_struct *cmd,
			     int argc, char **argv)
{
	return _cmd_qgroup_assign(cmd, 0, argc, argv);
}
static DEFINE_SIMPLE_COMMAND(qgroup_remove, "remove");

static const char * const cmd_qgroup_create_usage[] = {
	"btrfs qgroup create <qgroupid> <path>",
	"Create a subvolume quota group.",
	"Create a subvolume quota group. The level can't be 0 as such qgroup is",
	"created automatically for a subvolume. Higher level qgroups are supposed",
	"to provide accounting for qgroups in a tree structure.",
	NULL
};

static int cmd_qgroup_create(const struct cmd_struct *cmd,
			     int argc, char **argv)
{
	clean_args_no_options(cmd, argc, argv);

	return _cmd_qgroup_create(1, argc, argv);
}
static DEFINE_SIMPLE_COMMAND(qgroup_create, "create");

static const char * const cmd_qgroup_destroy_usage[] = {
	"btrfs qgroup destroy <qgroupid> <path>",
	"Destroy a quota group.",
	NULL
};

static int cmd_qgroup_destroy(const struct cmd_struct *cmd,
			      int argc, char **argv)
{
	clean_args_no_options(cmd, argc, argv);

	return _cmd_qgroup_create(0, argc, argv);
}
static DEFINE_SIMPLE_COMMAND(qgroup_destroy, "destroy");

static const char * const cmd_qgroup_show_usage[] = {
	"btrfs qgroup show [options] <path>",
	"List subvolume quota groups.",
	"List subvolume quota groups, accounted size, limits and path.",
	"",
	OPTLINE("-p", "print parent qgroup id"),
	OPTLINE("-c", "print child qgroup id"),
	OPTLINE("-r", "print limit of referenced size of qgroup"),
	OPTLINE("-e", "print limit of exclusive size of qgroup"),
	OPTLINE("-F", "list all qgroups which impact the given path (including ancestral qgroups)"),
	OPTLINE("-f", "list all qgroups which impact the given path (excluding ancestral qgroups)"),
	HELPINFO_UNITS_LONG,
	OPTLINE("--sort=qgroupid,rfer,excl,max_rfer,max_excl,path",
		"list qgroups sorted by specified items "
		"you can use '+' or '-' in front of each item. "
		"(+:ascending, -:descending, ascending default)"),
	OPTLINE("--sync", "force sync of the filesystem before getting info"),
	HELPINFO_INSERT_GLOBALS,
	HELPINFO_INSERT_FORMAT,
	NULL
};

static int cmd_qgroup_show(const struct cmd_struct *cmd, int argc, char **argv)
{
	char *path;
	int ret = 0;
	int fd;
	u64 qgroupid;
	int filter_flag = 0;
	unsigned unit_mode;
	bool sync = false;
	enum btrfs_util_error err;
	struct btrfs_qgroup_comparer_set *comparer_set;
	struct btrfs_qgroup_filter_set *filter_set;
	filter_set = qgroup_alloc_filter_set();
	comparer_set = qgroup_alloc_comparer_set();

	unit_mode = get_unit_mode_from_arg(&argc, argv, 0);

	optind = 0;
	while (1) {
		int c;
		enum {
			GETOPT_VAL_SORT = GETOPT_VAL_FIRST,
			GETOPT_VAL_SYNC
		};
		static const struct option long_options[] = {
			{"sort", required_argument, NULL, GETOPT_VAL_SORT},
			{"sync", no_argument, NULL, GETOPT_VAL_SYNC},
			{ NULL, 0, NULL, 0 }
		};

		c = getopt_long(argc, argv, "pcreFf", long_options, NULL);
		if (c < 0)
			break;
		switch (c) {
		case 'p':
			qgroup_setup_print_column(BTRFS_QGROUP_PARENT);
			break;
		case 'c':
			qgroup_setup_print_column(BTRFS_QGROUP_CHILD);
			break;
		case 'r':
			qgroup_setup_print_column(BTRFS_QGROUP_MAX_RFER);
			break;
		case 'e':
			qgroup_setup_print_column(BTRFS_QGROUP_MAX_EXCL);
			break;
		case 'F':
			filter_flag |= 0x1;
			break;
		case 'f':
			filter_flag |= 0x2;
			break;
		case GETOPT_VAL_SORT:
			ret = qgroup_parse_sort_string(optarg, &comparer_set);
			if (ret < 0) {
				errno = -ret;
				error("cannot parse sort string: %m");
				return 1;
			}
			if (ret > 0) {
				error("unrecognized format of sort string");
				return 1;
			}
			break;
		case GETOPT_VAL_SYNC:
			sync = true;
			break;
		default:
			usage_unknown_option(cmd, argv);
		}
	}
	qgroup_setup_units(unit_mode);

	if (check_argc_exact(argc - optind, 1))
		return 1;

	path = argv[optind];
	fd = btrfs_open_dir(path);
	if (fd < 0) {
		free(filter_set);
		free(comparer_set);
		return 1;
	}

	if (sync) {
		err = btrfs_util_fs_sync_fd(fd);
		if (err)
			warning("sync ioctl failed on '%s': %m", path);
	}

	if (filter_flag) {
		ret = lookup_path_rootid(fd, &qgroupid);
		if (ret < 0) {
			errno = -ret;
			error("cannot resolve rootid for %s: %m", path);
			close(fd);
			goto out;
		}
		if (filter_flag & 0x1)
			qgroup_setup_filter(&filter_set,
					BTRFS_QGROUP_FILTER_ALL_PARENT,
					qgroupid);
		if (filter_flag & 0x2)
			qgroup_setup_filter(&filter_set,
					BTRFS_QGROUP_FILTER_PARENT,
					qgroupid);
	}
	ret = show_qgroups(fd, filter_set, comparer_set);
	close(fd);
	free(filter_set);
	free(comparer_set);

out:
	return !!ret;
}
static DEFINE_COMMAND_WITH_FLAGS(qgroup_show, "show", CMD_FORMAT_JSON);

static const char * const cmd_qgroup_limit_usage[] = {
	"btrfs qgroup limit [options] <size>|none [<qgroupid>] <path>",
	"Set the limits a subvolume quota group.",
	"",
	OPTLINE("-c", "limit amount of data after compression. This is the default, it is currently not possible to turn off this option"),
	OPTLINE("-e", "limit space exclusively assigned to this qgroup"),
	NULL
};

static int cmd_qgroup_limit(const struct cmd_struct *cmd, int argc, char **argv)
{
	int ret = 0;
	int fd;
	char *path = NULL;
	struct btrfs_ioctl_qgroup_limit_args args;
	unsigned long long size;
	bool compressed = false;
	bool exclusive = false;
	enum btrfs_util_error err;

	optind = 0;
	while (1) {
		int c = getopt(argc, argv, "ce");
		if (c < 0)
			break;
		switch (c) {
		case 'c':
			compressed = true;
			break;
		case 'e':
			exclusive = true;
			break;
		default:
			usage_unknown_option(cmd, argv);
		}
	}

	if (check_argc_min(argc - optind, 2))
		return 1;

	if (!strcasecmp(argv[optind], "none"))
		size = -1ULL;
	else
		size = arg_strtou64_with_suffix(argv[optind]);

	memset(&args, 0, sizeof(args));
	if (compressed)
		args.lim.flags |= BTRFS_QGROUP_LIMIT_RFER_CMPR |
				  BTRFS_QGROUP_LIMIT_EXCL_CMPR;
	if (exclusive) {
		args.lim.flags |= BTRFS_QGROUP_LIMIT_MAX_EXCL;
		args.lim.max_exclusive = size;
	} else {
		args.lim.flags |= BTRFS_QGROUP_LIMIT_MAX_RFER;
		args.lim.max_referenced = size;
	}

	if (argc - optind == 2) {
		args.qgroupid = 0;
		path = argv[optind + 1];
		err = btrfs_util_subvolume_is_valid(path);
		if (err) {
			error_btrfs_util(err);
			return 1;
		}
		/*
		 * keep qgroupid at 0, this indicates that the subvolume the
		 * fd refers to is to be limited
		 */
	} else if (argc - optind == 3) {
		args.qgroupid = parse_qgroupid_or_path(argv[optind + 1]);
		path = argv[optind + 2];
	} else
		usage(cmd, 1);

	fd = btrfs_open_dir(path);
	if (fd < 0)
		return 1;

	ret = ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args);
	close(fd);
	if (ret < 0) {
		error("unable to limit requested quota group: %s",
				errno == ENOTCONN ? "quota not enabled"
						: strerror(errno));

		return 1;
	}
	return 0;
}
static DEFINE_SIMPLE_COMMAND(qgroup_limit, "limit");

static const char * const cmd_qgroup_clear_stale_usage[] = {
	"btrfs qgroup clear-stale <path>",
	"Clear all stale qgroups (level 0/subvolid), without a subvolume.",
	"Clear all stale qgroups whose subvolume does not exist anymore, this is the",
	"level 0 qgroup like 0/subvolid. Higher level qgroups are not deleted even",
	"if they don't have any child qgroups.",
	HELPINFO_INSERT_GLOBALS,
	HELPINFO_INSERT_QUIET,
	NULL
};

/*
 * Return >0 if the qgroup should or can not be deleted.
 * Return 0 if the qgroup is deleted.
 * Return <0 for critical error.
 */
static int delete_one_stale_qgroup(struct qgroup_lookup *lookup,
				   struct btrfs_qgroup *qg, int fd)
{
	u16 level = btrfs_qgroup_level(qg->qgroupid);
	struct btrfs_ioctl_qgroup_create_args args = { .create = false };
	const bool inconsistent = (lookup->flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT);
	const bool squota = (lookup->flags & BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE);
	const bool empty = is_qgroup_empty(qg);
	bool attempt = false;
	int ret;

	if (level || !qg->stale)
		return 1;

	/*
	 * By design, squota can have a subvolume fully dropped but its qgroup
	 * numbers are not zero.  Such qgroup is still needed for future
	 * accounting, thus can not be deleted.
	 */
	if (squota && !empty)
		return 1;

	/*
	 * We can have inconsistent qgroup numbers, in that case a really stale
	 * qgroup can exist while its numbers are not zero.
	 *
	 * In this case we only attempt to delete the qgroup, depending on the
	 * kernel implementation, we may or may not be able to delete it.
	 */
	if (inconsistent && !empty)
		attempt = true;

	if (attempt)
		pr_verbose(LOG_DEFAULT,
		"Attempt to delete stale but non-empty qgroup %u/%llu\n",
			   level, btrfs_qgroup_subvolid(qg->qgroupid));
	else
		pr_verbose(LOG_DEFAULT, "Delete stale qgroup %u/%llu\n",
			   level, btrfs_qgroup_subvolid(qg->qgroupid));
	args.qgroupid = qg->qgroupid;
	ret = ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args);
	if (ret < 0) {
		if (attempt) {
			warning("not possible to delete non-empty stale qgroup %u/%llu",
				level, btrfs_qgroup_subvolid(qg->qgroupid));
			ret = 1;
		} else {
			error("failed to delete qgroup %u/%llu",
			      level, btrfs_qgroup_subvolid(qg->qgroupid));
		}
	}
	return ret;
}

static int cmd_qgroup_clear_stale(const struct cmd_struct *cmd, int argc, char **argv)
{
	enum btrfs_util_error err;
	int ret = 0;
	int fd;
	char *path = NULL;
	struct qgroup_lookup qgroup_lookup;
	struct rb_node *node;
	struct btrfs_qgroup *entry;

	if (check_argc_exact(argc - optind, 1))
		return 1;

	path = argv[optind];

	fd = btrfs_open_dir(path);
	if (fd < 0)
		return 1;

	/* Do the check first so the sync is not done if quotas are not enabled. */
	ret = quota_enabled(fd);
	if (ret == -ENOENT) {
		warning("qgroups not enabled");
		ret = 0;
		goto out_close;
	} else if (ret < 0) {
		errno = -ret;
		error("cannot check qgroup status: %m");
		goto out_close;
	}

	/* Sync the fs so that the qgroup numbers are uptodate. */
	err = btrfs_util_fs_sync_fd(fd);
	if (err)
		warning("syncing filesystem failed: %m");

	ret = qgroups_search_all(fd, &qgroup_lookup);
	if (ret == -ENOTTY) {
		error("can't list qgroups: quotas not enabled");
		goto out_close;
	} else if (ret < 0) {
		errno = -ret;
		error("can't list qgroups: %m");
		goto out_close;
	}

	node = rb_first(&qgroup_lookup.root);
	while (node) {
		int ret2;

		entry = rb_entry(node, struct btrfs_qgroup, rb_node);
		ret2 = delete_one_stale_qgroup(&qgroup_lookup, entry, fd);
		if (ret2 < 0 && !ret)
			ret = ret2;
		node = rb_next(node);
	}

	__free_all_qgroups(&qgroup_lookup);
out_close:
	close(fd);
	return !!ret;
}
static DEFINE_SIMPLE_COMMAND(qgroup_clear_stale, "clear-stale");

static const char qgroup_cmd_group_info[] =
"manage quota groups";

static const struct cmd_group qgroup_cmd_group = {
	qgroup_cmd_group_usage, qgroup_cmd_group_info, {
		&cmd_struct_qgroup_assign,
		&cmd_struct_qgroup_remove,
		&cmd_struct_qgroup_create,
		&cmd_struct_qgroup_clear_stale,
		&cmd_struct_qgroup_destroy,
		&cmd_struct_qgroup_show,
		&cmd_struct_qgroup_limit,
		NULL
	}
};

DEFINE_GROUP_COMMAND_TOKEN(qgroup);
