diff options
Diffstat (limited to 'fs/btrfs/qgroup.c')
| -rw-r--r-- | fs/btrfs/qgroup.c | 100 | 
1 files changed, 70 insertions, 30 deletions
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 8928275823a1..1866b1f0da01 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -25,18 +25,6 @@  #include "sysfs.h"  #include "tree-mod-log.h" -/* TODO XXX FIXME - *  - subvol delete -> delete when ref goes to 0? delete limits also? - *  - reorganize keys - *  - compressed - *  - sync - *  - copy also limits on subvol creation - *  - limit - *  - caches for ulists - *  - performance benchmarks - *  - check all ioctl parameters - */ -  /*   * Helpers to access qgroup reservation   * @@ -258,16 +246,19 @@ static int del_qgroup_rb(struct btrfs_fs_info *fs_info, u64 qgroupid)  	return 0;  } -/* must be called with qgroup_lock held */ -static int add_relation_rb(struct btrfs_fs_info *fs_info, -			   u64 memberid, u64 parentid) +/* + * Add relation specified by two qgroups. + * + * Must be called with qgroup_lock held. + * + * Return: 0        on success + *         -ENOENT  if one of the qgroups is NULL + *         <0       other errors + */ +static int __add_relation_rb(struct btrfs_qgroup *member, struct btrfs_qgroup *parent)  { -	struct btrfs_qgroup *member; -	struct btrfs_qgroup *parent;  	struct btrfs_qgroup_list *list; -	member = find_qgroup_rb(fs_info, memberid); -	parent = find_qgroup_rb(fs_info, parentid);  	if (!member || !parent)  		return -ENOENT; @@ -283,7 +274,27 @@ static int add_relation_rb(struct btrfs_fs_info *fs_info,  	return 0;  } -/* must be called with qgroup_lock held */ +/* + * Add relation specified by two qgoup ids. + * + * Must be called with qgroup_lock held. + * + * Return: 0        on success + *         -ENOENT  if one of the ids does not exist + *         <0       other errors + */ +static int add_relation_rb(struct btrfs_fs_info *fs_info, u64 memberid, u64 parentid) +{ +	struct btrfs_qgroup *member; +	struct btrfs_qgroup *parent; + +	member = find_qgroup_rb(fs_info, memberid); +	parent = find_qgroup_rb(fs_info, parentid); + +	return __add_relation_rb(member, parent); +} + +/* Must be called with qgroup_lock held */  static int del_relation_rb(struct btrfs_fs_info *fs_info,  			   u64 memberid, u64 parentid)  { @@ -948,6 +959,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info)  	 */  	lockdep_assert_held_write(&fs_info->subvol_sem); +	if (btrfs_fs_incompat(fs_info, EXTENT_TREE_V2)) { +		btrfs_err(fs_info, +			  "qgroups are currently unsupported in extent tree v2"); +		return -EINVAL; +	} +  	mutex_lock(&fs_info->qgroup_ioctl_lock);  	if (fs_info->quota_root)  		goto out; @@ -1185,12 +1202,34 @@ int btrfs_quota_disable(struct btrfs_fs_info *fs_info)  	struct btrfs_trans_handle *trans = NULL;  	int ret = 0; +	/* +	 * We need to have subvol_sem write locked, to prevent races between +	 * concurrent tasks trying to disable quotas, because we will unlock +	 * and relock qgroup_ioctl_lock across BTRFS_FS_QUOTA_ENABLED changes. +	 */ +	lockdep_assert_held_write(&fs_info->subvol_sem); +  	mutex_lock(&fs_info->qgroup_ioctl_lock);  	if (!fs_info->quota_root)  		goto out; + +	/* +	 * Unlock the qgroup_ioctl_lock mutex before waiting for the rescan worker to +	 * complete. Otherwise we can deadlock because btrfs_remove_qgroup() needs +	 * to lock that mutex while holding a transaction handle and the rescan +	 * worker needs to commit a transaction. +	 */  	mutex_unlock(&fs_info->qgroup_ioctl_lock);  	/* +	 * Request qgroup rescan worker to complete and wait for it. This wait +	 * must be done before transaction start for quota disable since it may +	 * deadlock with transaction by the qgroup rescan worker. +	 */ +	clear_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags); +	btrfs_qgroup_wait_for_completion(fs_info, false); + +	/*  	 * 1 For the root item  	 *  	 * We should also reserve enough items for the quota tree deletion in @@ -1205,14 +1244,13 @@ int btrfs_quota_disable(struct btrfs_fs_info *fs_info)  	if (IS_ERR(trans)) {  		ret = PTR_ERR(trans);  		trans = NULL; +		set_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags);  		goto out;  	}  	if (!fs_info->quota_root)  		goto out; -	clear_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags); -	btrfs_qgroup_wait_for_completion(fs_info, false);  	spin_lock(&fs_info->qgroup_lock);  	quota_root = fs_info->quota_root;  	fs_info->quota_root = NULL; @@ -1430,7 +1468,7 @@ int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src,  	}  	spin_lock(&fs_info->qgroup_lock); -	ret = add_relation_rb(fs_info, src, dst); +	ret = __add_relation_rb(member, parent);  	if (ret < 0) {  		spin_unlock(&fs_info->qgroup_lock);  		goto out; @@ -3247,7 +3285,8 @@ out:  static bool rescan_should_stop(struct btrfs_fs_info *fs_info)  {  	return btrfs_fs_closing(fs_info) || -		test_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); +		test_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state) || +		!test_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags);  }  static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) @@ -3277,11 +3316,9 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)  			err = PTR_ERR(trans);  			break;  		} -		if (!test_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags)) { -			err = -EINTR; -		} else { -			err = qgroup_rescan_leaf(trans, path); -		} + +		err = qgroup_rescan_leaf(trans, path); +  		if (err > 0)  			btrfs_commit_transaction(trans);  		else @@ -3295,7 +3332,7 @@ out:  	if (err > 0 &&  	    fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT) {  		fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT; -	} else if (err < 0) { +	} else if (err < 0 || stopped) {  		fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT;  	}  	mutex_unlock(&fs_info->qgroup_rescan_lock); @@ -3383,6 +3420,9 @@ qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid,  			btrfs_warn(fs_info,  			"qgroup rescan init failed, qgroup is not enabled");  			ret = -EINVAL; +		} else if (!test_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags)) { +			/* Quota disable is in progress */ +			ret = -EBUSY;  		}  		if (ret) {  |