goto out_drop_sem;
root_flags = btrfs_root_flags(&root->root_item);
- if (flags & BTRFS_SUBVOL_RDONLY)
+ if (flags & BTRFS_SUBVOL_RDONLY) {
btrfs_set_root_flags(&root->root_item,
root_flags | BTRFS_ROOT_SUBVOL_RDONLY);
- else
- btrfs_set_root_flags(&root->root_item,
+ } else {
+ /*
+ * Block RO -> RW transition if this subvolume is involved in
+ * send
+ */
+ spin_lock(&root->root_item_lock);
+ if (root->send_in_progress == 0) {
+ btrfs_set_root_flags(&root->root_item,
root_flags & ~BTRFS_ROOT_SUBVOL_RDONLY);
+ spin_unlock(&root->root_item_lock);
+ } else {
+ spin_unlock(&root->root_item_lock);
+ btrfs_warn(root->fs_info,
+ "Attempt to set subvolume %llu read-write during send",
+ root->root_key.objectid);
+ ret = -EPERM;
+ goto out_drop_sem;
+ }
+ }
trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
struct send_ctx *sctx = NULL;
u32 i;
u64 *clone_sources_tmp = NULL;
+ int clone_sources_to_rollback = 0;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
send_root = BTRFS_I(file_inode(mnt_file))->root;
fs_info = send_root->fs_info;
+ /*
+ * The subvolume must remain read-only during send, protect against
+ * making it RW.
+ */
+ spin_lock(&send_root->root_item_lock);
+ send_root->send_in_progress++;
+ spin_unlock(&send_root->root_item_lock);
+
/*
* This is done when we lookup the root, it should already be complete
* by the time we get here.
up_read(&send_root->fs_info->extent_commit_sem);
}
+ /*
+ * Userspace tools do the checks and warn the user if it's
+ * not RO.
+ */
+ if (!btrfs_root_readonly(send_root)) {
+ ret = -EPERM;
+ goto out;
+ }
+
arg = memdup_user(arg_, sizeof(*arg));
if (IS_ERR(arg)) {
ret = PTR_ERR(arg);
ret = PTR_ERR(clone_root);
goto out;
}
+ clone_sources_to_rollback = i + 1;
+ spin_lock(&clone_root->root_item_lock);
+ clone_root->send_in_progress++;
+ if (!btrfs_root_readonly(clone_root)) {
+ spin_unlock(&clone_root->root_item_lock);
+ ret = -EPERM;
+ goto out;
+ }
+ spin_unlock(&clone_root->root_item_lock);
sctx->clone_roots[i].root = clone_root;
}
vfree(clone_sources_tmp);
ret = PTR_ERR(sctx->parent_root);
goto out;
}
+ spin_lock(&sctx->parent_root->root_item_lock);
+ sctx->parent_root->send_in_progress++;
+ if (!btrfs_root_readonly(sctx->parent_root)) {
+ spin_unlock(&sctx->parent_root->root_item_lock);
+ ret = -EPERM;
+ goto out;
+ }
+ spin_unlock(&sctx->parent_root->root_item_lock);
}
/*
}
out:
+ for (i = 0; sctx && i < clone_sources_to_rollback; i++) {
+ struct btrfs_root *r = sctx->clone_roots[i].root;
+
+ spin_lock(&r->root_item_lock);
+ r->send_in_progress--;
+ spin_unlock(&r->root_item_lock);
+ }
+ if (sctx && !IS_ERR_OR_NULL(sctx->parent_root)) {
+ struct btrfs_root *r = sctx->parent_root;
+
+ spin_lock(&r->root_item_lock);
+ r->send_in_progress--;
+ spin_unlock(&r->root_item_lock);
+ }
+
+ spin_lock(&send_root->root_item_lock);
+ send_root->send_in_progress--;
+ spin_unlock(&send_root->root_item_lock);
+
kfree(arg);
vfree(clone_sources_tmp);