#include "extent_io.h"
 #include "qgroup.h"
 #include "block-group.h"
+#include "sysfs.h"
 
 /* TODO XXX FIXME
  *  - subvol delete -> delete when ref goes to 0? delete limits also?
        return qgroup;
 }
 
-static void __del_qgroup_rb(struct btrfs_qgroup *qgroup)
+static void __del_qgroup_rb(struct btrfs_fs_info *fs_info,
+                           struct btrfs_qgroup *qgroup)
 {
        struct btrfs_qgroup_list *list;
 
+       btrfs_sysfs_del_one_qgroup(fs_info, qgroup);
        list_del(&qgroup->dirty);
        while (!list_empty(&qgroup->groups)) {
                list = list_first_entry(&qgroup->groups,
                return -ENOENT;
 
        rb_erase(&qgroup->node, &fs_info->qgroup_tree);
-       __del_qgroup_rb(qgroup);
+       __del_qgroup_rb(fs_info, qgroup);
        return 0;
 }
 
                goto out;
        }
 
+       ret = btrfs_sysfs_add_qgroups(fs_info);
+       if (ret < 0)
+               goto out;
        /* default this to quota off, in case no status key is found */
        fs_info->qgroup_flags = 0;
 
                                goto out;
                        }
                }
+               ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup);
+               if (ret < 0)
+                       goto out;
+
                switch (found_key.type) {
                case BTRFS_QGROUP_INFO_KEY: {
                        struct btrfs_qgroup_info_item *ptr;
                ulist_free(fs_info->qgroup_ulist);
                fs_info->qgroup_ulist = NULL;
                fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_RESCAN;
+               btrfs_sysfs_del_qgroups(fs_info);
        }
 
        return ret < 0 ? ret : 0;
 }
 
-static u64 btrfs_qgroup_subvolid(u64 qgroupid)
-{
-       return (qgroupid & ((1ULL << BTRFS_QGROUP_LEVEL_SHIFT) - 1));
-}
-
 /*
  * Called in close_ctree() when quota is still enabled.  This verifies we don't
  * leak some reserved space.
        while ((n = rb_first(&fs_info->qgroup_tree))) {
                qgroup = rb_entry(n, struct btrfs_qgroup, node);
                rb_erase(n, &fs_info->qgroup_tree);
-               __del_qgroup_rb(qgroup);
+               __del_qgroup_rb(fs_info, qgroup);
        }
        /*
         * We call btrfs_free_qgroup_config() when unmounting
         */
        ulist_free(fs_info->qgroup_ulist);
        fs_info->qgroup_ulist = NULL;
+       btrfs_sysfs_del_qgroups(fs_info);
 }
 
 static int add_qgroup_relation_item(struct btrfs_trans_handle *trans, u64 src,
                goto out;
        }
 
+       ret = btrfs_sysfs_add_qgroups(fs_info);
+       if (ret < 0)
+               goto out;
        /*
         * 1 for quota root item
         * 1 for BTRFS_QGROUP_STATUS item
                                btrfs_abort_transaction(trans, ret);
                                goto out_free_path;
                        }
+                       ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup);
+                       if (ret < 0) {
+                               btrfs_abort_transaction(trans, ret);
+                               goto out_free_path;
+                       }
                }
                ret = btrfs_next_item(tree_root, path);
                if (ret < 0) {
                btrfs_abort_transaction(trans, ret);
                goto out_free_path;
        }
+       ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup);
+       if (ret < 0) {
+               btrfs_abort_transaction(trans, ret);
+               goto out_free_path;
+       }
 
        ret = btrfs_commit_transaction(trans);
        trans = NULL;
                fs_info->qgroup_ulist = NULL;
                if (trans)
                        btrfs_end_transaction(trans);
+               btrfs_sysfs_del_qgroups(fs_info);
        }
        mutex_unlock(&fs_info->qgroup_ioctl_lock);
        return ret;
        qgroup = add_qgroup_rb(fs_info, qgroupid);
        spin_unlock(&fs_info->qgroup_lock);
 
-       if (IS_ERR(qgroup))
+       if (IS_ERR(qgroup)) {
                ret = PTR_ERR(qgroup);
+               goto out;
+       }
+       ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup);
 out:
        mutex_unlock(&fs_info->qgroup_ioctl_lock);
        return ret;
 
 unlock:
        spin_unlock(&fs_info->qgroup_lock);
+       if (!ret)
+               ret = btrfs_sysfs_add_one_qgroup(fs_info, dstgroup);
 out:
        if (!committing)
                mutex_unlock(&fs_info->qgroup_ioctl_lock);
 
 #include "volumes.h"
 #include "space-info.h"
 #include "block-group.h"
+#include "qgroup.h"
 
 struct btrfs_feature_attr {
        struct kobj_attribute kobj_attr;
        return error;
 }
 
+static inline struct btrfs_fs_info *qgroup_kobj_to_fs_info(struct kobject *kobj)
+{
+       return to_fs_info(kobj->parent->parent);
+}
+
+#define QGROUP_ATTR(_member, _show_name)                                       \
+static ssize_t btrfs_qgroup_show_##_member(struct kobject *qgroup_kobj,                \
+                                          struct kobj_attribute *a,            \
+                                          char *buf)                           \
+{                                                                              \
+       struct btrfs_fs_info *fs_info = qgroup_kobj_to_fs_info(qgroup_kobj);    \
+       struct btrfs_qgroup *qgroup = container_of(qgroup_kobj,                 \
+                       struct btrfs_qgroup, kobj);                             \
+       return btrfs_show_u64(&qgroup->_member, &fs_info->qgroup_lock, buf);    \
+}                                                                              \
+BTRFS_ATTR(qgroup, _show_name, btrfs_qgroup_show_##_member)
+
+#define QGROUP_RSV_ATTR(_name, _type)                                          \
+static ssize_t btrfs_qgroup_rsv_show_##_name(struct kobject *qgroup_kobj,      \
+                                            struct kobj_attribute *a,          \
+                                            char *buf)                         \
+{                                                                              \
+       struct btrfs_fs_info *fs_info = qgroup_kobj_to_fs_info(qgroup_kobj);    \
+       struct btrfs_qgroup *qgroup = container_of(qgroup_kobj,                 \
+                       struct btrfs_qgroup, kobj);                             \
+       return btrfs_show_u64(&qgroup->rsv.values[_type],                       \
+                       &fs_info->qgroup_lock, buf);                            \
+}                                                                              \
+BTRFS_ATTR(qgroup, rsv_##_name, btrfs_qgroup_rsv_show_##_name)
+
+QGROUP_ATTR(rfer, referenced);
+QGROUP_ATTR(excl, exclusive);
+QGROUP_ATTR(max_rfer, max_referenced);
+QGROUP_ATTR(max_excl, max_exclusive);
+QGROUP_ATTR(lim_flags, limit_flags);
+QGROUP_RSV_ATTR(data, BTRFS_QGROUP_RSV_DATA);
+QGROUP_RSV_ATTR(meta_pertrans, BTRFS_QGROUP_RSV_META_PERTRANS);
+QGROUP_RSV_ATTR(meta_prealloc, BTRFS_QGROUP_RSV_META_PREALLOC);
+
+static struct attribute *qgroup_attrs[] = {
+       BTRFS_ATTR_PTR(qgroup, referenced),
+       BTRFS_ATTR_PTR(qgroup, exclusive),
+       BTRFS_ATTR_PTR(qgroup, max_referenced),
+       BTRFS_ATTR_PTR(qgroup, max_exclusive),
+       BTRFS_ATTR_PTR(qgroup, limit_flags),
+       BTRFS_ATTR_PTR(qgroup, rsv_data),
+       BTRFS_ATTR_PTR(qgroup, rsv_meta_pertrans),
+       BTRFS_ATTR_PTR(qgroup, rsv_meta_prealloc),
+       NULL
+};
+ATTRIBUTE_GROUPS(qgroup);
+
+static void qgroup_release(struct kobject *kobj)
+{
+       struct btrfs_qgroup *qgroup = container_of(kobj, struct btrfs_qgroup, kobj);
+
+       memset(&qgroup->kobj, 0, sizeof(*kobj));
+}
+
+static struct kobj_type qgroup_ktype = {
+       .sysfs_ops = &kobj_sysfs_ops,
+       .release = qgroup_release,
+       .default_groups = qgroup_groups,
+};
+
+int btrfs_sysfs_add_one_qgroup(struct btrfs_fs_info *fs_info,
+                               struct btrfs_qgroup *qgroup)
+{
+       struct kobject *qgroups_kobj = fs_info->qgroups_kobj;
+       int ret;
+
+       if (test_bit(BTRFS_FS_STATE_DUMMY_FS_INFO, &fs_info->fs_state))
+               return 0;
+       if (qgroup->kobj.state_initialized)
+               return 0;
+       if (!qgroups_kobj)
+               return -EINVAL;
+
+       ret = kobject_init_and_add(&qgroup->kobj, &qgroup_ktype, qgroups_kobj,
+                       "%hu_%llu", btrfs_qgroup_level(qgroup->qgroupid),
+                       btrfs_qgroup_subvolid(qgroup->qgroupid));
+       if (ret < 0)
+               kobject_put(&qgroup->kobj);
+
+       return ret;
+}
+
+void btrfs_sysfs_del_qgroups(struct btrfs_fs_info *fs_info)
+{
+       struct btrfs_qgroup *qgroup;
+       struct btrfs_qgroup *next;
+
+       if (test_bit(BTRFS_FS_STATE_DUMMY_FS_INFO, &fs_info->fs_state))
+               return;
+
+       rbtree_postorder_for_each_entry_safe(qgroup, next,
+                                            &fs_info->qgroup_tree, node)
+               btrfs_sysfs_del_one_qgroup(fs_info, qgroup);
+       kobject_del(fs_info->qgroups_kobj);
+       kobject_put(fs_info->qgroups_kobj);
+       fs_info->qgroups_kobj = NULL;
+}
+
+/* Called when qgroups get initialized, thus there is no need for locking */
+int btrfs_sysfs_add_qgroups(struct btrfs_fs_info *fs_info)
+{
+       struct kobject *fsid_kobj = &fs_info->fs_devices->fsid_kobj;
+       struct btrfs_qgroup *qgroup;
+       struct btrfs_qgroup *next;
+       int ret = 0;
+
+       if (test_bit(BTRFS_FS_STATE_DUMMY_FS_INFO, &fs_info->fs_state))
+               return 0;
+
+       ASSERT(fsid_kobj);
+       if (fs_info->qgroups_kobj)
+               return 0;
+
+       fs_info->qgroups_kobj = kobject_create_and_add("qgroups", fsid_kobj);
+       if (!fs_info->qgroups_kobj) {
+               ret = -ENOMEM;
+               goto out;
+       }
+       rbtree_postorder_for_each_entry_safe(qgroup, next,
+                                            &fs_info->qgroup_tree, node) {
+               ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup);
+               if (ret < 0)
+                       goto out;
+       }
+
+out:
+       if (ret < 0)
+               btrfs_sysfs_del_qgroups(fs_info);
+       return ret;
+}
+
+void btrfs_sysfs_del_one_qgroup(struct btrfs_fs_info *fs_info,
+                               struct btrfs_qgroup *qgroup)
+{
+       if (test_bit(BTRFS_FS_STATE_DUMMY_FS_INFO, &fs_info->fs_state))
+               return;
+
+       if (qgroup->kobj.state_initialized) {
+               kobject_del(&qgroup->kobj);
+               kobject_put(&qgroup->kobj);
+       }
+}
 
 /*
  * Change per-fs features in /sys/fs/btrfs/UUID/features to match current