btrfs: Check qgroup level in kernel qgroup assign.
[cascardo/linux.git] / fs / btrfs / qgroup.c
index 058c79e..4fb9610 100644 (file)
@@ -644,9 +644,8 @@ out:
 }
 
 static int update_qgroup_limit_item(struct btrfs_trans_handle *trans,
-                                   struct btrfs_root *root, u64 qgroupid,
-                                   u64 flags, u64 max_rfer, u64 max_excl,
-                                   u64 rsv_rfer, u64 rsv_excl)
+                                   struct btrfs_root *root,
+                                   struct btrfs_qgroup *qgroup)
 {
        struct btrfs_path *path;
        struct btrfs_key key;
@@ -657,7 +656,7 @@ static int update_qgroup_limit_item(struct btrfs_trans_handle *trans,
 
        key.objectid = 0;
        key.type = BTRFS_QGROUP_LIMIT_KEY;
-       key.offset = qgroupid;
+       key.offset = qgroup->qgroupid;
 
        path = btrfs_alloc_path();
        if (!path)
@@ -673,11 +672,11 @@ static int update_qgroup_limit_item(struct btrfs_trans_handle *trans,
        l = path->nodes[0];
        slot = path->slots[0];
        qgroup_limit = btrfs_item_ptr(l, slot, struct btrfs_qgroup_limit_item);
-       btrfs_set_qgroup_limit_flags(l, qgroup_limit, flags);
-       btrfs_set_qgroup_limit_max_rfer(l, qgroup_limit, max_rfer);
-       btrfs_set_qgroup_limit_max_excl(l, qgroup_limit, max_excl);
-       btrfs_set_qgroup_limit_rsv_rfer(l, qgroup_limit, rsv_rfer);
-       btrfs_set_qgroup_limit_rsv_excl(l, qgroup_limit, rsv_excl);
+       btrfs_set_qgroup_limit_flags(l, qgroup_limit, qgroup->lim_flags);
+       btrfs_set_qgroup_limit_max_rfer(l, qgroup_limit, qgroup->max_rfer);
+       btrfs_set_qgroup_limit_max_excl(l, qgroup_limit, qgroup->max_excl);
+       btrfs_set_qgroup_limit_rsv_rfer(l, qgroup_limit, qgroup->rsv_rfer);
+       btrfs_set_qgroup_limit_rsv_excl(l, qgroup_limit, qgroup->rsv_excl);
 
        btrfs_mark_buffer_dirty(l);
 
@@ -982,7 +981,7 @@ int btrfs_quota_disable(struct btrfs_trans_handle *trans,
        list_del(&quota_root->dirty_list);
 
        btrfs_tree_lock(quota_root->node);
-       clean_tree_block(trans, tree_root, quota_root->node);
+       clean_tree_block(trans, tree_root->fs_info, quota_root->node);
        btrfs_tree_unlock(quota_root->node);
        btrfs_free_tree_block(trans, quota_root, quota_root->node, 0, 1);
 
@@ -1010,6 +1009,10 @@ int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans,
        struct btrfs_qgroup_list *list;
        int ret = 0;
 
+       /* Check the level of src and dst first */
+       if (btrfs_qgroup_level(src) >= btrfs_qgroup_level(dst))
+               return -EINVAL;
+
        mutex_lock(&fs_info->qgroup_ioctl_lock);
        quota_root = fs_info->quota_root;
        if (!quota_root) {
@@ -1049,7 +1052,7 @@ out:
        return ret;
 }
 
-int btrfs_del_qgroup_relation(struct btrfs_trans_handle *trans,
+int __del_qgroup_relation(struct btrfs_trans_handle *trans,
                              struct btrfs_fs_info *fs_info, u64 src, u64 dst)
 {
        struct btrfs_root *quota_root;
@@ -1059,7 +1062,6 @@ int btrfs_del_qgroup_relation(struct btrfs_trans_handle *trans,
        int ret = 0;
        int err;
 
-       mutex_lock(&fs_info->qgroup_ioctl_lock);
        quota_root = fs_info->quota_root;
        if (!quota_root) {
                ret = -EINVAL;
@@ -1090,12 +1092,23 @@ exist:
        del_relation_rb(fs_info, src, dst);
        spin_unlock(&fs_info->qgroup_lock);
 out:
+       return ret;
+}
+
+int btrfs_del_qgroup_relation(struct btrfs_trans_handle *trans,
+                             struct btrfs_fs_info *fs_info, u64 src, u64 dst)
+{
+       int ret = 0;
+
+       mutex_lock(&fs_info->qgroup_ioctl_lock);
+       ret = __del_qgroup_relation(trans, fs_info, src, dst);
        mutex_unlock(&fs_info->qgroup_ioctl_lock);
+
        return ret;
 }
 
 int btrfs_create_qgroup(struct btrfs_trans_handle *trans,
-                       struct btrfs_fs_info *fs_info, u64 qgroupid, char *name)
+                       struct btrfs_fs_info *fs_info, u64 qgroupid)
 {
        struct btrfs_root *quota_root;
        struct btrfs_qgroup *qgroup;
@@ -1133,6 +1146,7 @@ int btrfs_remove_qgroup(struct btrfs_trans_handle *trans,
 {
        struct btrfs_root *quota_root;
        struct btrfs_qgroup *qgroup;
+       struct btrfs_qgroup_list *list;
        int ret = 0;
 
        mutex_lock(&fs_info->qgroup_ioctl_lock);
@@ -1147,15 +1161,24 @@ int btrfs_remove_qgroup(struct btrfs_trans_handle *trans,
                ret = -ENOENT;
                goto out;
        } else {
-               /* check if there are no relations to this qgroup */
-               if (!list_empty(&qgroup->groups) ||
-                   !list_empty(&qgroup->members)) {
+               /* check if there are no children of this qgroup */
+               if (!list_empty(&qgroup->members)) {
                        ret = -EBUSY;
                        goto out;
                }
        }
        ret = del_qgroup_item(trans, quota_root, qgroupid);
 
+       while (!list_empty(&qgroup->groups)) {
+               list = list_first_entry(&qgroup->groups,
+                                       struct btrfs_qgroup_list, next_group);
+               ret = __del_qgroup_relation(trans, fs_info,
+                                          qgroupid,
+                                          list->group->qgroupid);
+               if (ret)
+                       goto out;
+       }
+
        spin_lock(&fs_info->qgroup_lock);
        del_qgroup_rb(quota_root->fs_info, qgroupid);
        spin_unlock(&fs_info->qgroup_lock);
@@ -1184,23 +1207,27 @@ int btrfs_limit_qgroup(struct btrfs_trans_handle *trans,
                ret = -ENOENT;
                goto out;
        }
-       ret = update_qgroup_limit_item(trans, quota_root, qgroupid,
-                                      limit->flags, limit->max_rfer,
-                                      limit->max_excl, limit->rsv_rfer,
-                                      limit->rsv_excl);
+
+       spin_lock(&fs_info->qgroup_lock);
+       if (limit->flags & BTRFS_QGROUP_LIMIT_MAX_RFER)
+               qgroup->max_rfer = limit->max_rfer;
+       if (limit->flags & BTRFS_QGROUP_LIMIT_MAX_EXCL)
+               qgroup->max_excl = limit->max_excl;
+       if (limit->flags & BTRFS_QGROUP_LIMIT_RSV_RFER)
+               qgroup->rsv_rfer = limit->rsv_rfer;
+       if (limit->flags & BTRFS_QGROUP_LIMIT_RSV_EXCL)
+               qgroup->rsv_excl = limit->rsv_excl;
+       qgroup->lim_flags |= limit->flags;
+
+       spin_unlock(&fs_info->qgroup_lock);
+
+       ret = update_qgroup_limit_item(trans, quota_root, qgroup);
        if (ret) {
                fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;
                btrfs_info(fs_info, "unable to update quota limit for %llu",
                       qgroupid);
        }
 
-       spin_lock(&fs_info->qgroup_lock);
-       qgroup->lim_flags = limit->flags;
-       qgroup->max_rfer = limit->max_rfer;
-       qgroup->max_excl = limit->max_excl;
-       qgroup->rsv_rfer = limit->rsv_rfer;
-       qgroup->rsv_excl = limit->rsv_excl;
-       spin_unlock(&fs_info->qgroup_lock);
 out:
        mutex_unlock(&fs_info->qgroup_ioctl_lock);
        return ret;
@@ -1256,14 +1283,14 @@ static int comp_oper(struct btrfs_qgroup_operation *oper1,
                return -1;
        if (oper1->bytenr > oper2->bytenr)
                return 1;
-       if (oper1->seq < oper2->seq)
-               return -1;
-       if (oper1->seq > oper2->seq)
-               return 1;
        if (oper1->ref_root < oper2->ref_root)
                return -1;
        if (oper1->ref_root > oper2->ref_root)
                return 1;
+       if (oper1->seq < oper2->seq)
+               return -1;
+       if (oper1->seq > oper2->seq)
+               return 1;
        if (oper1->type < oper2->type)
                return -1;
        if (oper1->type > oper2->type)
@@ -1414,6 +1441,8 @@ static int qgroup_excl_accounting(struct btrfs_fs_info *fs_info,
        WARN_ON(sign < 0 && qgroup->excl < oper->num_bytes);
        qgroup->excl += sign * oper->num_bytes;
        qgroup->excl_cmpr += sign * oper->num_bytes;
+       if (sign > 0)
+               qgroup->reserved -= oper->num_bytes;
 
        qgroup_dirty(fs_info, qgroup);
 
@@ -1433,6 +1462,8 @@ static int qgroup_excl_accounting(struct btrfs_fs_info *fs_info,
                qgroup->rfer_cmpr += sign * oper->num_bytes;
                WARN_ON(sign < 0 && qgroup->excl < oper->num_bytes);
                qgroup->excl += sign * oper->num_bytes;
+               if (sign > 0)
+                       qgroup->reserved -= oper->num_bytes;
                qgroup->excl_cmpr += sign * oper->num_bytes;
                qgroup_dirty(fs_info, qgroup);
 
@@ -1845,7 +1876,7 @@ static int qgroup_shared_accounting(struct btrfs_trans_handle *trans,
        struct ulist *roots = NULL;
        struct ulist *qgroups, *tmp;
        struct btrfs_qgroup *qgroup;
-       struct seq_list elem = {};
+       struct seq_list elem = SEQ_LIST_INIT(elem);
        u64 seq;
        int old_roots = 0;
        int new_roots = 0;
@@ -1967,7 +1998,7 @@ static int qgroup_subtree_accounting(struct btrfs_trans_handle *trans,
        int err;
        struct btrfs_qgroup *qg;
        u64 root_obj = 0;
-       struct seq_list elem = {};
+       struct seq_list elem = SEQ_LIST_INIT(elem);
 
        parents = ulist_alloc(GFP_NOFS);
        if (!parents)
@@ -2153,6 +2184,10 @@ int btrfs_run_qgroups(struct btrfs_trans_handle *trans,
                list_del_init(&qgroup->dirty);
                spin_unlock(&fs_info->qgroup_lock);
                ret = update_qgroup_info_item(trans, quota_root, qgroup);
+               if (ret)
+                       fs_info->qgroup_flags |=
+                                       BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;
+               ret = update_qgroup_limit_item(trans, quota_root, qgroup);
                if (ret)
                        fs_info->qgroup_flags |=
                                        BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;
@@ -2219,6 +2254,11 @@ int btrfs_qgroup_inherit(struct btrfs_trans_handle *trans,
                                ret = -EINVAL;
                                goto out;
                        }
+
+                       if ((srcgroup->qgroupid >> 48) <= (objectid >> 48)) {
+                               ret = -EINVAL;
+                               goto out;
+                       }
                        ++i_qgroups;
                }
        }
@@ -2230,17 +2270,6 @@ int btrfs_qgroup_inherit(struct btrfs_trans_handle *trans,
        if (ret)
                goto out;
 
-       if (inherit && inherit->flags & BTRFS_QGROUP_INHERIT_SET_LIMITS) {
-               ret = update_qgroup_limit_item(trans, quota_root, objectid,
-                                              inherit->lim.flags,
-                                              inherit->lim.max_rfer,
-                                              inherit->lim.max_excl,
-                                              inherit->lim.rsv_rfer,
-                                              inherit->lim.rsv_excl);
-               if (ret)
-                       goto out;
-       }
-
        if (srcid) {
                struct btrfs_root *srcroot;
                struct btrfs_key srckey;
@@ -2286,6 +2315,22 @@ int btrfs_qgroup_inherit(struct btrfs_trans_handle *trans,
                goto unlock;
        }
 
+       if (inherit && inherit->flags & BTRFS_QGROUP_INHERIT_SET_LIMITS) {
+               dstgroup->lim_flags = inherit->lim.flags;
+               dstgroup->max_rfer = inherit->lim.max_rfer;
+               dstgroup->max_excl = inherit->lim.max_excl;
+               dstgroup->rsv_rfer = inherit->lim.rsv_rfer;
+               dstgroup->rsv_excl = inherit->lim.rsv_excl;
+
+               ret = update_qgroup_limit_item(trans, quota_root, dstgroup);
+               if (ret) {
+                       fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;
+                       btrfs_info(fs_info, "unable to update quota limit for %llu",
+                              dstgroup->qgroupid);
+                       goto unlock;
+               }
+       }
+
        if (srcid) {
                srcgroup = find_qgroup_rb(fs_info, srcid);
                if (!srcgroup)
@@ -2302,6 +2347,14 @@ int btrfs_qgroup_inherit(struct btrfs_trans_handle *trans,
                dstgroup->excl_cmpr = level_size;
                srcgroup->excl = level_size;
                srcgroup->excl_cmpr = level_size;
+
+               /* inherit the limit info */
+               dstgroup->lim_flags = srcgroup->lim_flags;
+               dstgroup->max_rfer = srcgroup->max_rfer;
+               dstgroup->max_excl = srcgroup->max_excl;
+               dstgroup->rsv_rfer = srcgroup->rsv_rfer;
+               dstgroup->rsv_excl = srcgroup->rsv_excl;
+
                qgroup_dirty(fs_info, dstgroup);
                qgroup_dirty(fs_info, srcgroup);
        }
@@ -2358,12 +2411,6 @@ out:
        return ret;
 }
 
-/*
- * reserve some space for a qgroup and all its parents. The reservation takes
- * place with start_transaction or dealloc_reserve, similar to ENOSPC
- * accounting. If not enough space is available, EDQUOT is returned.
- * We assume that the requested space is new for all qgroups.
- */
 int btrfs_qgroup_reserve(struct btrfs_root *root, u64 num_bytes)
 {
        struct btrfs_root *quota_root;
@@ -2522,7 +2569,7 @@ qgroup_rescan_leaf(struct btrfs_fs_info *fs_info, struct btrfs_path *path,
 {
        struct btrfs_key found;
        struct ulist *roots = NULL;
-       struct seq_list tree_mod_seq_elem = {};
+       struct seq_list tree_mod_seq_elem = SEQ_LIST_INIT(tree_mod_seq_elem);
        u64 num_bytes;
        u64 seq;
        int new_roots;