static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q,
uint64_t perm, uint64_t shared,
- GSList *ignore_children, Error **errp);
+ GSList *ignore_children,
+ bool *tighten_restrictions, Error **errp);
static void bdrv_child_abort_perm_update(BdrvChild *c);
static void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared);
static void bdrv_get_cumulative_perm(BlockDriverState *bs, uint64_t *perm,
* permissions of all its parents. This involves checking whether all necessary
* permission changes to child nodes can be performed.
*
+ * Will set *tighten_restrictions to true if and only if new permissions have to
+ * be taken or currently shared permissions are to be unshared. Otherwise,
+ * errors are not fatal as long as the caller accepts that the restrictions
+ * remain tighter than they need to be. The caller still has to abort the
+ * transaction.
+ * @tighten_restrictions cannot be used together with @q: When reopening, we may
+ * encounter fatal errors even though no restrictions are to be tightened. For
+ * example, changing a node from RW to RO will fail if the WRITE permission is
+ * to be kept.
+ *
* A call to this function must always be followed by a call to bdrv_set_perm()
* or bdrv_abort_perm_update().
*/
static int bdrv_check_perm(BlockDriverState *bs, BlockReopenQueue *q,
uint64_t cumulative_perms,
uint64_t cumulative_shared_perms,
- GSList *ignore_children, Error **errp)
+ GSList *ignore_children,
+ bool *tighten_restrictions, Error **errp)
{
BlockDriver *drv = bs->drv;
BdrvChild *c;
int ret;
+ assert(!q || !tighten_restrictions);
+
+ if (tighten_restrictions) {
+ uint64_t current_perms, current_shared;
+ uint64_t added_perms, removed_shared_perms;
+
+ bdrv_get_cumulative_perm(bs, ¤t_perms, ¤t_shared);
+
+ added_perms = cumulative_perms & ~current_perms;
+ removed_shared_perms = current_shared & ~cumulative_shared_perms;
+
+ *tighten_restrictions = added_perms || removed_shared_perms;
+ }
+
/* Write permissions never work with read-only images */
if ((cumulative_perms & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED)) &&
!bdrv_is_writable_after_reopen(bs, q))
/* Check all children */
QLIST_FOREACH(c, &bs->children, next) {
uint64_t cur_perm, cur_shared;
+ bool child_tighten_restr;
+
bdrv_child_perm(bs, c->bs, c, c->role, q,
cumulative_perms, cumulative_shared_perms,
&cur_perm, &cur_shared);
- ret = bdrv_child_check_perm(c, q, cur_perm, cur_shared,
- ignore_children, errp);
+ ret = bdrv_child_check_perm(c, q, cur_perm, cur_shared, ignore_children,
+ tighten_restrictions ? &child_tighten_restr
+ : NULL,
+ errp);
+ if (tighten_restrictions) {
+ *tighten_restrictions |= child_tighten_restr;
+ }
if (ret < 0) {
return ret;
}
* set, the BdrvChild objects in this list are ignored in the calculations;
* this allows checking permission updates for an existing reference.
*
+ * See bdrv_check_perm() for the semantics of @tighten_restrictions.
+ *
* Needs to be followed by a call to either bdrv_set_perm() or
* bdrv_abort_perm_update(). */
static int bdrv_check_update_perm(BlockDriverState *bs, BlockReopenQueue *q,
uint64_t new_used_perm,
uint64_t new_shared_perm,
- GSList *ignore_children, Error **errp)
+ GSList *ignore_children,
+ bool *tighten_restrictions,
+ Error **errp)
{
BdrvChild *c;
uint64_t cumulative_perms = new_used_perm;
uint64_t cumulative_shared_perms = new_shared_perm;
+ assert(!q || !tighten_restrictions);
+
/* There is no reason why anyone couldn't tolerate write_unchanged */
assert(new_shared_perm & BLK_PERM_WRITE_UNCHANGED);
if ((new_used_perm & c->shared_perm) != new_used_perm) {
char *user = bdrv_child_user_desc(c);
char *perm_names = bdrv_perm_names(new_used_perm & ~c->shared_perm);
+
+ if (tighten_restrictions) {
+ *tighten_restrictions = true;
+ }
+
error_setg(errp, "Conflicts with use by %s as '%s', which does not "
"allow '%s' on %s",
user, c->name, perm_names, bdrv_get_node_name(c->bs));
if ((c->perm & new_shared_perm) != c->perm) {
char *user = bdrv_child_user_desc(c);
char *perm_names = bdrv_perm_names(c->perm & ~new_shared_perm);
+
+ if (tighten_restrictions) {
+ *tighten_restrictions = true;
+ }
+
error_setg(errp, "Conflicts with use by %s as '%s', which uses "
"'%s' on %s",
user, c->name, perm_names, bdrv_get_node_name(c->bs));
}
return bdrv_check_perm(bs, q, cumulative_perms, cumulative_shared_perms,
- ignore_children, errp);
+ ignore_children, tighten_restrictions, errp);
}
/* Needs to be followed by a call to either bdrv_child_set_perm() or
* bdrv_child_abort_perm_update(). */
static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q,
uint64_t perm, uint64_t shared,
- GSList *ignore_children, Error **errp)
+ GSList *ignore_children,
+ bool *tighten_restrictions, Error **errp)
{
int ret;
ignore_children = g_slist_prepend(g_slist_copy(ignore_children), c);
- ret = bdrv_check_update_perm(c->bs, q, perm, shared, ignore_children, errp);
+ ret = bdrv_check_update_perm(c->bs, q, perm, shared, ignore_children,
+ tighten_restrictions, errp);
g_slist_free(ignore_children);
if (ret < 0) {
int bdrv_child_try_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
Error **errp)
{
+ Error *local_err = NULL;
int ret;
+ bool tighten_restrictions;
- ret = bdrv_child_check_perm(c, NULL, perm, shared, NULL, errp);
+ ret = bdrv_child_check_perm(c, NULL, perm, shared, NULL,
+ &tighten_restrictions, &local_err);
if (ret < 0) {
bdrv_child_abort_perm_update(c);
+ if (tighten_restrictions) {
+ error_propagate(errp, local_err);
+ } else {
+ /*
+ * Our caller may intend to only loosen restrictions and
+ * does not expect this function to fail. Errors are not
+ * fatal in such a case, so we can just hide them from our
+ * caller.
+ */
+ error_free(local_err);
+ ret = 0;
+ }
return ret;
}
return 0;
}
+int bdrv_child_refresh_perms(BlockDriverState *bs, BdrvChild *c, Error **errp)
+{
+ uint64_t parent_perms, parent_shared;
+ uint64_t perms, shared;
+
+ bdrv_get_cumulative_perm(bs, &parent_perms, &parent_shared);
+ bdrv_child_perm(bs, c->bs, c, c->role, NULL, parent_perms, parent_shared,
+ &perms, &shared);
+
+ return bdrv_child_try_set_perm(c, perms, shared, errp);
+}
+
void bdrv_filter_default_perms(BlockDriverState *bs, BdrvChild *c,
const BdrvChildRole *role,
BlockReopenQueue *reopen_queue,
bdrv_replace_child_noperm(child, new_bs);
+ /*
+ * Start with the new node's permissions. If @new_bs is a (direct
+ * or indirect) child of @old_bs, we must complete the permission
+ * update on @new_bs before we loosen the restrictions on @old_bs.
+ * Otherwise, bdrv_check_perm() on @old_bs would re-initiate
+ * updating the permissions of @new_bs, and thus not purely loosen
+ * restrictions.
+ */
+ if (new_bs) {
+ bdrv_get_cumulative_perm(new_bs, &perm, &shared_perm);
+ bdrv_set_perm(new_bs, perm, shared_perm);
+ }
+
if (old_bs) {
/* Update permissions for old node. This is guaranteed to succeed
* because we're just taking a parent away, so we're loosening
* restrictions. */
+ bool tighten_restrictions;
+ int ret;
+
bdrv_get_cumulative_perm(old_bs, &perm, &shared_perm);
- bdrv_check_perm(old_bs, NULL, perm, shared_perm, NULL, &error_abort);
- bdrv_set_perm(old_bs, perm, shared_perm);
- }
+ ret = bdrv_check_perm(old_bs, NULL, perm, shared_perm, NULL,
+ &tighten_restrictions, NULL);
+ assert(tighten_restrictions == false);
+ if (ret < 0) {
+ /* We only tried to loosen restrictions, so errors are not fatal */
+ bdrv_abort_perm_update(old_bs);
+ } else {
+ bdrv_set_perm(old_bs, perm, shared_perm);
+ }
- if (new_bs) {
- bdrv_get_cumulative_perm(new_bs, &perm, &shared_perm);
- bdrv_set_perm(new_bs, perm, shared_perm);
+ /* When the parent requiring a non-default AioContext is removed, the
+ * node moves back to the main AioContext */
+ bdrv_try_set_aio_context(old_bs, qemu_get_aio_context(), NULL);
}
}
*
* On failure NULL is returned, errp is set and the reference to
* child_bs is also dropped.
+ *
+ * The caller must hold the AioContext lock @child_bs, but not that of @ctx
+ * (unless @child_bs is already in @ctx).
*/
BdrvChild *bdrv_root_attach_child(BlockDriverState *child_bs,
const char *child_name,
const BdrvChildRole *child_role,
+ AioContext *ctx,
uint64_t perm, uint64_t shared_perm,
void *opaque, Error **errp)
{
BdrvChild *child;
+ Error *local_err = NULL;
int ret;
- ret = bdrv_check_update_perm(child_bs, NULL, perm, shared_perm, NULL, errp);
+ ret = bdrv_check_update_perm(child_bs, NULL, perm, shared_perm, NULL, NULL,
+ errp);
if (ret < 0) {
bdrv_abort_perm_update(child_bs);
bdrv_unref(child_bs);
.opaque = opaque,
};
+ /* If the AioContexts don't match, first try to move the subtree of
+ * child_bs into the AioContext of the new parent. If this doesn't work,
+ * try moving the parent into the AioContext of child_bs instead. */
+ if (bdrv_get_aio_context(child_bs) != ctx) {
+ ret = bdrv_try_set_aio_context(child_bs, ctx, &local_err);
+ if (ret < 0 && child_role->can_set_aio_ctx) {
+ GSList *ignore = g_slist_prepend(NULL, child);;
+ ctx = bdrv_get_aio_context(child_bs);
+ if (child_role->can_set_aio_ctx(child, ctx, &ignore, NULL)) {
+ error_free(local_err);
+ ret = 0;
+ g_slist_free(ignore);
+ ignore = g_slist_prepend(NULL, child);;
+ child_role->set_aio_ctx(child, ctx, &ignore);
+ }
+ g_slist_free(ignore);
+ }
+ if (ret < 0) {
+ error_propagate(errp, local_err);
+ g_free(child);
+ bdrv_abort_perm_update(child_bs);
+ return NULL;
+ }
+ }
+
/* This performs the matching bdrv_set_perm() for the above check. */
bdrv_replace_child(child, child_bs);
*
* On failure NULL is returned, errp is set and the reference to
* child_bs is also dropped.
+ *
+ * If @parent_bs and @child_bs are in different AioContexts, the caller must
+ * hold the AioContext lock for @child_bs, but not for @parent_bs.
*/
BdrvChild *bdrv_attach_child(BlockDriverState *parent_bs,
BlockDriverState *child_bs,
bdrv_get_cumulative_perm(parent_bs, &perm, &shared_perm);
assert(parent_bs->drv);
- assert(bdrv_get_aio_context(parent_bs) == bdrv_get_aio_context(child_bs));
bdrv_child_perm(parent_bs, child_bs, NULL, child_role, NULL,
perm, shared_perm, &perm, &shared_perm);
child = bdrv_root_attach_child(child_bs, child_name, child_role,
+ bdrv_get_aio_context(parent_bs),
perm, shared_perm, parent_bs, errp);
if (child == NULL) {
return NULL;
ret = -EINVAL;
goto free_exit;
}
- bdrv_set_aio_context(backing_hd, bdrv_get_aio_context(bs));
if (implicit_backing) {
bdrv_refresh_filename(backing_hd);
/* Not requesting BLK_PERM_CONSISTENT_READ because we're only
* looking at the header to guess the image format. This works even
* in cases where a guest would not see a consistent state. */
- file = blk_new(0, BLK_PERM_ALL);
+ file = blk_new(bdrv_get_aio_context(file_bs), 0, BLK_PERM_ALL);
blk_insert_bs(file, file_bs, &local_err);
bdrv_unref(file_bs);
if (local_err) {
QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
BDRVReopenState *state = &bs_entry->state;
ret = bdrv_check_perm(state->bs, bs_queue, state->perm,
- state->shared_perm, NULL, errp);
+ state->shared_perm, NULL, NULL, errp);
if (ret < 0) {
goto cleanup_perm;
}
state->perm, state->shared_perm,
&nperm, &nshared);
ret = bdrv_check_update_perm(state->new_backing_bs, NULL,
- nperm, nshared, NULL, errp);
+ nperm, nshared, NULL, NULL, errp);
if (ret < 0) {
goto cleanup_perm;
}
BdrvAioNotifier *ban, *ban_next;
BdrvChild *child, *next;
- assert(!bs->job);
assert(!bs->refcnt);
bdrv_drained_begin(bs); /* complete I/O */
uint64_t perm = 0, shared = BLK_PERM_ALL;
int ret;
- assert(!atomic_read(&from->in_flight));
- assert(!atomic_read(&to->in_flight));
-
/* Make sure that @from doesn't go away until we have successfully attached
* all of its parents to @to. */
bdrv_ref(from);
+ assert(qemu_get_current_aio_context() == qemu_get_aio_context());
+ bdrv_drained_begin(from);
+
/* Put all parents into @list and calculate their cumulative permissions */
QLIST_FOREACH_SAFE(c, &from->parents, next_parent, next) {
assert(c->bs == from);
/* Check whether the required permissions can be granted on @to, ignoring
* all BdrvChild in @list so that they can't block themselves. */
- ret = bdrv_check_update_perm(to, NULL, perm, shared, list, errp);
+ ret = bdrv_check_update_perm(to, NULL, perm, shared, list, NULL, errp);
if (ret < 0) {
bdrv_abort_perm_update(to);
goto out;
out:
g_slist_free(list);
+ bdrv_drained_end(from);
bdrv_unref(from);
}
static void bdrv_delete(BlockDriverState *bs)
{
- assert(!bs->job);
assert(bdrv_op_blocker_is_empty(bs));
assert(!bs->refcnt);
/* Check whether we are allowed to switch c from top to base */
GSList *ignore_children = g_slist_prepend(NULL, c);
ret = bdrv_check_update_perm(base, NULL, c->perm, c->shared_perm,
- ignore_children, &local_err);
+ ignore_children, NULL, &local_err);
g_slist_free(ignore_children);
if (ret < 0) {
error_report_err(local_err);
*/
bs->open_flags &= ~BDRV_O_INACTIVE;
bdrv_get_cumulative_perm(bs, &perm, &shared_perm);
- ret = bdrv_check_perm(bs, NULL, perm, shared_perm, NULL, &local_err);
+ ret = bdrv_check_perm(bs, NULL, perm, shared_perm, NULL, NULL, &local_err);
if (ret < 0) {
bs->open_flags |= BDRV_O_INACTIVE;
error_propagate(errp, local_err);
static int bdrv_inactivate_recurse(BlockDriverState *bs)
{
BdrvChild *child, *parent;
+ bool tighten_restrictions;
uint64_t perm, shared_perm;
int ret;
/* Update permissions, they may differ for inactive nodes */
bdrv_get_cumulative_perm(bs, &perm, &shared_perm);
- bdrv_check_perm(bs, NULL, perm, shared_perm, NULL, &error_abort);
- bdrv_set_perm(bs, perm, shared_perm);
+ ret = bdrv_check_perm(bs, NULL, perm, shared_perm, NULL,
+ &tighten_restrictions, NULL);
+ assert(tighten_restrictions == false);
+ if (ret < 0) {
+ /* We only tried to loosen restrictions, so errors are not fatal */
+ bdrv_abort_perm_update(bs);
+ } else {
+ bdrv_set_perm(bs, perm, shared_perm);
+ }
/* Recursively inactivate children */
bs->walking_aio_notifiers = false;
}
-/* @ignore will accumulate all visited BdrvChild object. The caller is
- * responsible for freeing the list afterwards. */
+/*
+ * Changes the AioContext used for fd handlers, timers, and BHs by this
+ * BlockDriverState and all its children and parents.
+ *
+ * The caller must own the AioContext lock for the old AioContext of bs, but it
+ * must not own the AioContext lock for new_context (unless new_context is the
+ * same as the current context of bs).
+ *
+ * @ignore will accumulate all visited BdrvChild object. The caller is
+ * responsible for freeing the list afterwards.
+ */
void bdrv_set_aio_context_ignore(BlockDriverState *bs,
AioContext *new_context, GSList **ignore)
{
if (g_slist_find(*ignore, child)) {
continue;
}
- if (child->role->set_aio_ctx) {
- *ignore = g_slist_prepend(*ignore, child);
- child->role->set_aio_ctx(child, new_context, ignore);
- }
+ assert(child->role->set_aio_ctx);
+ *ignore = g_slist_prepend(*ignore, child);
+ child->role->set_aio_ctx(child, new_context, ignore);
}
bdrv_detach_aio_context(bs);
aio_context_release(new_context);
}
-/* The caller must own the AioContext lock for the old AioContext of bs, but it
- * must not own the AioContext lock for new_context (unless new_context is
- * the same as the current context of bs). */
-void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context)
-{
- GSList *ignore_list = NULL;
- bdrv_set_aio_context_ignore(bs, new_context, &ignore_list);
- g_slist_free(ignore_list);
-}
-
static bool bdrv_parent_can_set_aio_context(BdrvChild *c, AioContext *ctx,
GSList **ignore, Error **errp)
{