return ret;
}
+static void update_disk_balance_vrange(struct btrfs_disk_balance_args *disk,
+ u64 vstart, u64 vend)
+{
+ disk->flags = cpu_to_le64(le64_to_cpu(disk->flags) |
+ BTRFS_BALANCE_ARGS_VRANGE);
+ disk->vstart = cpu_to_le64(vstart);
+ disk->vend = cpu_to_le64(vend);
+}
+
+/* Write balance progress with the same transaction as other persistent state. */
+int btrfs_run_balance(struct btrfs_trans_handle *trans)
+{
+ BTRFS_PATH_AUTO_FREE(path);
+ struct btrfs_fs_info *fs_info = trans->fs_info;
+ struct btrfs_balance_control *bctl;
+ struct btrfs_root *root = fs_info->tree_root;
+ struct btrfs_disk_balance_args disk_bargs;
+ struct btrfs_balance_item *item;
+ struct extent_buffer *leaf;
+ struct btrfs_key key;
+ u64 flags, data_start, data_end, meta_start, meta_end, sys_start, sys_end;
+ int ret;
+
+ spin_lock(&fs_info->balance_lock);
+ bctl = fs_info->balance_ctl;
+ if (!bctl || !bctl->item_needs_writeback) {
+ spin_unlock(&fs_info->balance_lock);
+ return 0;
+ }
+ flags = bctl->flags;
+ data_start = bctl->data.vstart;
+ data_end = bctl->data.vend;
+ meta_start = bctl->meta.vstart;
+ meta_end = bctl->meta.vend;
+ sys_start = bctl->sys.vstart;
+ sys_end = bctl->sys.vend;
+ bctl->item_needs_writeback = false;
+ spin_unlock(&fs_info->balance_lock);
+
+ path = btrfs_alloc_path();
+ if (!path) {
+ ret = -ENOMEM;
+ goto restore_writeback;
+ }
+
+ key.objectid = BTRFS_BALANCE_OBJECTID;
+ key.type = BTRFS_TEMPORARY_ITEM_KEY;
+ key.offset = 0;
+
+ ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
+ if (ret > 0)
+ ret = -ENOENT;
+ if (ret)
+ goto restore_writeback;
+
+ leaf = path->nodes[0];
+ item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_balance_item);
+
+ /* Keep the original limit and other filters, which may change in memory. */
+ if (flags & BTRFS_BALANCE_DATA) {
+ btrfs_balance_data(leaf, item, &disk_bargs);
+ update_disk_balance_vrange(&disk_bargs, data_start, data_end);
+ btrfs_set_balance_data(leaf, item, &disk_bargs);
+ }
+ if (flags & BTRFS_BALANCE_METADATA) {
+ btrfs_balance_meta(leaf, item, &disk_bargs);
+ update_disk_balance_vrange(&disk_bargs, meta_start, meta_end);
+ btrfs_set_balance_meta(leaf, item, &disk_bargs);
+ }
+ if (flags & BTRFS_BALANCE_SYSTEM) {
+ btrfs_balance_sys(leaf, item, &disk_bargs);
+ update_disk_balance_vrange(&disk_bargs, sys_start, sys_end);
+ btrfs_set_balance_sys(leaf, item, &disk_bargs);
+ }
+ return 0;
+
+restore_writeback:
+ spin_lock(&fs_info->balance_lock);
+ if (fs_info->balance_ctl == bctl)
+ bctl->item_needs_writeback = true;
+ spin_unlock(&fs_info->balance_lock);
+ return ret;
+}
+
static int del_balance_item(struct btrfs_fs_info *fs_info)
{
struct btrfs_root *root = fs_info->tree_root;
return ret;
}
-/*
- * This is a heuristic used to reduce the number of chunks balanced on
- * resume after balance was interrupted.
- */
+/* Update balance arguments when resuming an interrupted balance. */
static void update_balance_args(struct btrfs_balance_control *bctl)
{
/*
bctl->sys.flags |= BTRFS_BALANCE_ARGS_SOFT;
if (bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT)
bctl->meta.flags |= BTRFS_BALANCE_ARGS_SOFT;
+}
- /*
- * Turn on usage filter if is not already used. The idea is
- * that chunks that we have already balanced should be
- * reasonably full. Don't do it for chunks that are being
- * converted - that will keep us from relocating unconverted
- * (albeit full) chunks.
- */
- if (!(bctl->data.flags & BTRFS_BALANCE_ARGS_USAGE) &&
- !(bctl->data.flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
- !(bctl->data.flags & BTRFS_BALANCE_ARGS_CONVERT)) {
- bctl->data.flags |= BTRFS_BALANCE_ARGS_USAGE;
- bctl->data.usage = 90;
- }
- if (!(bctl->sys.flags & BTRFS_BALANCE_ARGS_USAGE) &&
- !(bctl->sys.flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
- !(bctl->sys.flags & BTRFS_BALANCE_ARGS_CONVERT)) {
- bctl->sys.flags |= BTRFS_BALANCE_ARGS_USAGE;
- bctl->sys.usage = 90;
- }
- if (!(bctl->meta.flags & BTRFS_BALANCE_ARGS_USAGE) &&
- !(bctl->meta.flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
- !(bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT)) {
- bctl->meta.flags |= BTRFS_BALANCE_ARGS_USAGE;
- bctl->meta.usage = 90;
+static bool update_balance_vrange(struct btrfs_balance_args *bargs, u64 offset)
+{
+ if (bargs->flags & BTRFS_BALANCE_ARGS_VRANGE) {
+ u64 vend = max(bargs->vstart, min(bargs->vend, offset));
+
+ if (bargs->vend == vend)
+ return false;
+ bargs->vend = vend;
+ } else {
+ bargs->flags |= BTRFS_BALANCE_ARGS_VRANGE;
+ bargs->vstart = 0;
+ bargs->vend = offset;
}
+ return true;
+}
+
+static void update_balance_vranges(struct btrfs_balance_control *bctl, u64 offset)
+{
+ bool changed = false;
+
+ if (bctl->flags & BTRFS_BALANCE_DATA)
+ changed |= update_balance_vrange(&bctl->data, offset);
+ if (bctl->flags & BTRFS_BALANCE_METADATA)
+ changed |= update_balance_vrange(&bctl->meta, offset);
+ if (bctl->flags & BTRFS_BALANCE_SYSTEM)
+ changed |= update_balance_vrange(&bctl->sys, offset);
+ if (changed)
+ bctl->item_needs_writeback = true;
}
/*
spin_unlock(&fs_info->balance_lock);
}
loop:
+ if (!counting) {
+ spin_lock(&fs_info->balance_lock);
+ update_balance_vranges(bctl, found_key.offset);
+ spin_unlock(&fs_info->balance_lock);
+ }
if (found_key.offset == 0)
break;
key.offset = found_key.offset - 1;
kfree(buf);
}
+static int commit_balance_progress(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_trans_handle *trans;
+
+ trans = btrfs_start_transaction_fallback_global_rsv(fs_info->tree_root, 0);
+ if (IS_ERR(trans))
+ return PTR_ERR(trans);
+ return btrfs_commit_transaction(trans);
+}
+
/*
* Should be called with balance mutex held
*/
mutex_lock(&fs_info->balance_mutex);
if (ret == -ECANCELED && atomic_read(&fs_info->balance_pause_req)) {
+ int err;
+
+ /* No other transaction is guaranteed before unmount or remount-ro. */
+ err = commit_balance_progress(fs_info);
+
+ if (err) {
+ btrfs_err(fs_info, "balance: failed to persist progress: %d", err);
+ ret = err;
+ }
btrfs_info(fs_info, "balance: paused");
btrfs_exclop_balance(fs_info, BTRFS_EXCLOP_BALANCE_PAUSED);
paused = true;