if (file != NULL) {
filename = blk_bs(file)->filename;
} else {
+ /*
+ * Caution: while qdict_get_try_str() is fine, getting
+ * non-string types would require more care. When @options
+ * come from -blockdev or blockdev_add, its members are typed
+ * according to the QAPI schema, but when they come from
+ * -drive, they're all QString.
+ */
filename = qdict_get_try_str(options, "filename");
}
BlockDriver *drv = NULL;
Error *local_err = NULL;
+ /*
+ * Caution: while qdict_get_try_str() is fine, getting non-string
+ * types would require more care. When @options come from
+ * -blockdev or blockdev_add, its members are typed according to
+ * the QAPI schema, but when they come from -drive, they're all
+ * QString.
+ */
drvname = qdict_get_try_str(*options, "driver");
if (drvname) {
drv = bdrv_find_format(drvname);
}
/* Find the right block driver */
+ /* See cautionary note on accessing @options above */
filename = qdict_get_try_str(*options, "filename");
if (!drvname && protocol) {
return 0;
}
+static int bdrv_child_check_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
+ GSList *ignore_children, 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);
+
/*
* Check whether permissions on this node can be changed in a way that
* @cumulative_perms and @cumulative_shared_perms are the new cumulative
* or bdrv_abort_perm_update().
*/
static int bdrv_check_perm(BlockDriverState *bs, uint64_t cumulative_perms,
- uint64_t cumulative_shared_perms, Error **errp)
+ uint64_t cumulative_shared_perms,
+ GSList *ignore_children, Error **errp)
{
BlockDriver *drv = bs->drv;
BdrvChild *c;
drv->bdrv_child_perm(bs, c, c->role,
cumulative_perms, cumulative_shared_perms,
&cur_perm, &cur_shared);
- ret = bdrv_child_check_perm(c, cur_perm, cur_shared, errp);
+ ret = bdrv_child_check_perm(c, cur_perm, cur_shared, ignore_children,
+ errp);
if (ret < 0) {
return ret;
}
/*
* Checks whether a new reference to @bs can be added if the new user requires
- * @new_used_perm/@new_shared_perm as its permissions. If @ignore_child is set,
- * this old reference is ignored in the calculations; this allows checking
- * permission updates for an existing reference.
+ * @new_used_perm/@new_shared_perm as its permissions. If @ignore_children is
+ * set, the BdrvChild objects in this list are ignored in the calculations;
+ * this allows checking permission updates for an existing reference.
*
* 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, uint64_t new_used_perm,
uint64_t new_shared_perm,
- BdrvChild *ignore_child, Error **errp)
+ GSList *ignore_children, Error **errp)
{
BdrvChild *c;
uint64_t cumulative_perms = new_used_perm;
assert(new_shared_perm & BLK_PERM_WRITE_UNCHANGED);
QLIST_FOREACH(c, &bs->parents, next_parent) {
- if (c == ignore_child) {
+ if (g_slist_find(ignore_children, c)) {
continue;
}
cumulative_shared_perms &= c->shared_perm;
}
- return bdrv_check_perm(bs, cumulative_perms, cumulative_shared_perms, errp);
+ return bdrv_check_perm(bs, cumulative_perms, cumulative_shared_perms,
+ ignore_children, errp);
}
/* Needs to be followed by a call to either bdrv_child_set_perm() or
* bdrv_child_abort_perm_update(). */
-int bdrv_child_check_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
- Error **errp)
+static int bdrv_child_check_perm(BdrvChild *c, uint64_t perm, uint64_t shared,
+ GSList *ignore_children, Error **errp)
{
- return bdrv_check_update_perm(c->bs, perm, shared, c, errp);
+ int ret;
+
+ ignore_children = g_slist_prepend(g_slist_copy(ignore_children), c);
+ ret = bdrv_check_update_perm(c->bs, perm, shared, ignore_children, errp);
+ g_slist_free(ignore_children);
+
+ return ret;
}
-void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared)
+static void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared)
{
uint64_t cumulative_perms, cumulative_shared_perms;
bdrv_set_perm(c->bs, cumulative_perms, cumulative_shared_perms);
}
-void bdrv_child_abort_perm_update(BdrvChild *c)
+static void bdrv_child_abort_perm_update(BdrvChild *c)
{
bdrv_abort_perm_update(c->bs);
}
{
int ret;
- ret = bdrv_child_check_perm(c, perm, shared, errp);
+ ret = bdrv_child_check_perm(c, perm, shared, NULL, errp);
if (ret < 0) {
bdrv_child_abort_perm_update(c);
return ret;
*nshared = shared;
}
-static void bdrv_replace_child(BdrvChild *child, BlockDriverState *new_bs,
- bool check_new_perm)
+static void bdrv_replace_child_noperm(BdrvChild *child,
+ BlockDriverState *new_bs)
{
BlockDriverState *old_bs = child->bs;
- uint64_t perm, shared_perm;
+ if (old_bs && new_bs) {
+ assert(bdrv_get_aio_context(old_bs) == bdrv_get_aio_context(new_bs));
+ }
if (old_bs) {
if (old_bs->quiesce_counter && child->role->drained_end) {
child->role->drained_end(child);
child->role->detach(child);
}
QLIST_REMOVE(child, next_parent);
-
- /* Update permissions for old node. This is guaranteed to succeed
- * because we're just taking a parent away, so we're loosening
- * restrictions. */
- bdrv_get_cumulative_perm(old_bs, &perm, &shared_perm);
- bdrv_check_perm(old_bs, perm, shared_perm, &error_abort);
- bdrv_set_perm(old_bs, perm, shared_perm);
}
child->bs = new_bs;
child->role->drained_begin(child);
}
- bdrv_get_cumulative_perm(new_bs, &perm, &shared_perm);
- if (check_new_perm) {
- bdrv_check_perm(new_bs, perm, shared_perm, &error_abort);
- }
- bdrv_set_perm(new_bs, perm, shared_perm);
-
if (child->role->attach) {
child->role->attach(child);
}
}
}
+/*
+ * Updates @child to change its reference to point to @new_bs, including
+ * checking and applying the necessary permisson updates both to the old node
+ * and to @new_bs.
+ *
+ * NULL is passed as @new_bs for removing the reference before freeing @child.
+ *
+ * If @new_bs is not NULL, bdrv_check_perm() must be called beforehand, as this
+ * function uses bdrv_set_perm() to update the permissions according to the new
+ * reference that @new_bs gets.
+ */
+static void bdrv_replace_child(BdrvChild *child, BlockDriverState *new_bs)
+{
+ BlockDriverState *old_bs = child->bs;
+ uint64_t 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. */
+ bdrv_get_cumulative_perm(old_bs, &perm, &shared_perm);
+ bdrv_check_perm(old_bs, perm, shared_perm, NULL, &error_abort);
+ bdrv_set_perm(old_bs, perm, shared_perm);
+ }
+
+ bdrv_replace_child_noperm(child, new_bs);
+
+ if (new_bs) {
+ bdrv_get_cumulative_perm(new_bs, &perm, &shared_perm);
+ bdrv_set_perm(new_bs, perm, shared_perm);
+ }
+}
+
BdrvChild *bdrv_root_attach_child(BlockDriverState *child_bs,
const char *child_name,
const BdrvChildRole *child_role,
};
/* This performs the matching bdrv_set_perm() for the above check. */
- bdrv_replace_child(child, child_bs, false);
+ bdrv_replace_child(child, child_bs);
return child;
}
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));
parent_bs->drv->bdrv_child_perm(parent_bs, NULL, child_role,
perm, shared_perm, &perm, &shared_perm);
child->next.le_prev = NULL;
}
- bdrv_replace_child(child, NULL, false);
+ bdrv_replace_child(child, NULL);
g_free(child->name);
g_free(child);
bdrv_unref(backing_hd);
}
+ bdrv_refresh_filename(bs);
+
out:
bdrv_refresh_limits(bs, NULL);
}
qdict_extract_subqdict(parent_options, &options, bdref_key_dot);
g_free(bdref_key_dot);
+ /*
+ * Caution: while qdict_get_try_str() is fine, getting non-string
+ * types would require more care. When @parent_options come from
+ * -blockdev or blockdev_add, its members are typed according to
+ * the QAPI schema, but when they come from -drive, they're all
+ * QString.
+ */
reference = qdict_get_try_str(parent_options, bdref_key);
if (reference || qdict_haskey(options, "file.filename")) {
backing_filename[0] = '\0';
bdrv_set_backing_hd(bs, backing_hd, &local_err);
bdrv_unref(backing_hd);
if (local_err) {
+ error_propagate(errp, local_err);
ret = -EINVAL;
goto free_exit;
}
qdict_extract_subqdict(options, &image_options, bdref_key_dot);
g_free(bdref_key_dot);
+ /*
+ * Caution: while qdict_get_try_str() is fine, getting non-string
+ * types would require more care. When @options come from
+ * -blockdev or blockdev_add, its members are typed according to
+ * the QAPI schema, but when they come from -drive, they're all
+ * QString.
+ */
reference = qdict_get_try_str(options, bdref_key);
if (!filename && !reference && !qdict_size(image_options)) {
if (!allow_none) {
goto fail;
}
- /* Set the BDRV_O_RDWR and BDRV_O_ALLOW_RDWR flags.
- * FIXME: we're parsing the QDict to avoid having to create a
- * QemuOpts just for this, but neither option is optimal. */
+ /*
+ * Set the BDRV_O_RDWR and BDRV_O_ALLOW_RDWR flags.
+ * Caution: getting a boolean member of @options requires care.
+ * When @options come from -blockdev or blockdev_add, members are
+ * typed according to the QAPI schema, but when they come from
+ * -drive, they're all QString.
+ */
if (g_strcmp0(qdict_get_try_str(options, BDRV_OPT_READ_ONLY), "on") &&
!qdict_get_try_bool(options, BDRV_OPT_READ_ONLY, false)) {
flags |= (BDRV_O_RDWR | BDRV_O_ALLOW_RDWR);
options = qdict_clone_shallow(options);
/* Find the right image format driver */
+ /* See cautionary note on accessing @options above */
drvname = qdict_get_try_str(options, "driver");
if (drvname) {
drv = bdrv_find_format(drvname);
assert(drvname || !(flags & BDRV_O_PROTOCOL));
+ /* See cautionary note on accessing @options above */
backing = qdict_get_try_str(options, "backing");
if (backing && *backing == '\0') {
flags |= BDRV_O_NO_BACKING;
do {
QString *new_obj = qobject_to_qstring(entry->value);
const char *new = qstring_get_str(new_obj);
+ /*
+ * Caution: while qdict_get_try_str() is fine, getting
+ * non-string types would require more care. When
+ * bs->options come from -blockdev or blockdev_add, its
+ * members are typed according to the QAPI schema, but
+ * when they come from -drive, they're all QString.
+ */
const char *old = qdict_get_try_str(reopen_state->bs->options,
entry->key);
assert(QTAILQ_EMPTY(&all_bdrv_states));
}
-static void change_parent_backing_link(BlockDriverState *from,
- BlockDriverState *to)
+static bool should_update_child(BdrvChild *c, BlockDriverState *to)
+{
+ BdrvChild *to_c;
+
+ if (c->role->stay_at_node) {
+ return false;
+ }
+
+ if (c->role == &child_backing) {
+ /* If @from is a backing file of @to, ignore the child to avoid
+ * creating a loop. We only want to change the pointer of other
+ * parents. */
+ QLIST_FOREACH(to_c, &to->children, next) {
+ if (to_c == c) {
+ break;
+ }
+ }
+ if (to_c) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void bdrv_replace_node(BlockDriverState *from, BlockDriverState *to,
+ Error **errp)
{
- BdrvChild *c, *next, *to_c;
+ BdrvChild *c, *next;
+ GSList *list = NULL, *p;
+ uint64_t old_perm, old_shared;
+ 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);
+ /* Put all parents into @list and calculate their cumulative permissions */
QLIST_FOREACH_SAFE(c, &from->parents, next_parent, next) {
- if (c->role->stay_at_node) {
+ if (!should_update_child(c, to)) {
continue;
}
- if (c->role == &child_backing) {
- /* If @from is a backing file of @to, ignore the child to avoid
- * creating a loop. We only want to change the pointer of other
- * parents. */
- QLIST_FOREACH(to_c, &to->children, next) {
- if (to_c == c) {
- break;
- }
- }
- if (to_c) {
- continue;
- }
- }
+ list = g_slist_prepend(list, c);
+ perm |= c->perm;
+ shared &= c->shared_perm;
+ }
+
+ /* 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, perm, shared, list, errp);
+ if (ret < 0) {
+ bdrv_abort_perm_update(to);
+ goto out;
+ }
+
+ /* Now actually perform the change. We performed the permission check for
+ * all elements of @list at once, so set the permissions all at once at the
+ * very end. */
+ for (p = list; p != NULL; p = p->next) {
+ c = p->data;
bdrv_ref(to);
- /* FIXME Are we sure that bdrv_replace_child() can't run into
- * &error_abort because of permissions? */
- bdrv_replace_child(c, to, true);
+ bdrv_replace_child_noperm(c, to);
bdrv_unref(from);
}
+
+ bdrv_get_cumulative_perm(to, &old_perm, &old_shared);
+ bdrv_set_perm(to, old_perm | perm, old_shared | shared);
+
+out:
+ g_slist_free(list);
+ bdrv_unref(from);
}
/*
{
Error *local_err = NULL;
- assert(!atomic_read(&bs_top->in_flight));
- assert(!atomic_read(&bs_new->in_flight));
-
bdrv_set_backing_hd(bs_new, bs_top, &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto out;
}
- change_parent_backing_link(bs_top, bs_new);
+ bdrv_replace_node(bs_top, bs_new, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ bdrv_set_backing_hd(bs_new, NULL, &error_abort);
+ goto out;
+ }
/* bs_new is now referenced by its new parents, we don't need the
* additional reference any more. */
bdrv_unref(bs_new);
}
-void bdrv_replace_in_backing_chain(BlockDriverState *old, BlockDriverState *new)
-{
- assert(!bdrv_requests_pending(old));
- assert(!bdrv_requests_pending(new));
-
- bdrv_ref(old);
-
- change_parent_backing_link(old, new);
-
- bdrv_unref(old);
-}
-
static void bdrv_delete(BlockDriverState *bs)
{
assert(!bs->job);
BlockDriver *drv = bs->drv;
int ret;
- assert(child->perm & BLK_PERM_RESIZE);
+ /* FIXME: Some format block drivers use this function instead of implicitly
+ * growing their file by writing beyond its end.
+ * See bdrv_aligned_pwritev() for an explanation why we currently
+ * cannot assert this permission in that case. */
+ // assert(child->perm & BLK_PERM_RESIZE);
if (!drv)
return -ENOMEDIUM;
return bs->aio_context;
}
+void bdrv_coroutine_enter(BlockDriverState *bs, Coroutine *co)
+{
+ aio_co_enter(bdrv_get_aio_context(bs), co);
+}
+
static void bdrv_do_remove_aio_context_notifier(BdrvAioNotifier *ban)
{
QLIST_REMOVE(ban, list);
void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context)
{
+ AioContext *ctx = bdrv_get_aio_context(bs);
+
+ aio_disable_external(ctx);
+ bdrv_parent_drained_begin(bs);
bdrv_drain(bs); /* ensure there are no in-flight requests */
+ while (aio_poll(ctx, false)) {
+ /* wait for all bottom halves to execute */
+ }
+
bdrv_detach_aio_context(bs);
/* This function executes in the old AioContext so acquire the new one in
*/
aio_context_acquire(new_context);
bdrv_attach_aio_context(bs, new_context);
+ bdrv_parent_drained_end(bs);
+ aio_enable_external(ctx);
aio_context_release(new_context);
}