/* commit COW file into the raw image */ int bdrv_commit(BlockDriverState *bs) { BlockBackend *src, *backing; BlockDriverState *backing_file_bs = NULL; BlockDriverState *commit_top_bs = NULL; BlockDriver *drv = bs->drv; int64_t offset, length, backing_length; int ro; int64_t n; int ret = 0; uint8_t *buf = NULL; Error *local_err = NULL; if (!drv) return -ENOMEDIUM; if (!bs->backing) { return -ENOTSUP; } if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT_SOURCE, NULL) || bdrv_op_is_blocked(bs->backing->bs, BLOCK_OP_TYPE_COMMIT_TARGET, NULL)) { return -EBUSY; } ro = bs->backing->bs->read_only; if (ro) { if (bdrv_reopen_set_read_only(bs->backing->bs, false, NULL)) { return -EACCES; } } src = blk_new(BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL); backing = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); ret = blk_insert_bs(src, bs, &local_err); if (ret < 0) { error_report_err(local_err); goto ro_cleanup; } /* Insert commit_top block node above backing, so we can write to it */ backing_file_bs = backing_bs(bs); commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, NULL, BDRV_O_RDWR, &local_err); if (commit_top_bs == NULL) { error_report_err(local_err); goto ro_cleanup; } bdrv_set_aio_context(commit_top_bs, bdrv_get_aio_context(backing_file_bs)); bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort); bdrv_set_backing_hd(bs, commit_top_bs, &error_abort); ret = blk_insert_bs(backing, backing_file_bs, &local_err); if (ret < 0) { error_report_err(local_err); goto ro_cleanup; } length = blk_getlength(src); if (length < 0) { ret = length; goto ro_cleanup; } backing_length = blk_getlength(backing); if (backing_length < 0) { ret = backing_length; goto ro_cleanup; } /* If our top snapshot is larger than the backing file image, * grow the backing file image if possible. If not possible, * we must return an error */ if (length > backing_length) { ret = blk_truncate(backing, length, PREALLOC_MODE_OFF, &local_err); if (ret < 0) { error_report_err(local_err); goto ro_cleanup; } } /* blk_try_blockalign() for src will choose an alignment that works for * backing as well, so no need to compare the alignment manually. */ buf = blk_try_blockalign(src, COMMIT_BUF_SIZE); if (buf == NULL) { ret = -ENOMEM; goto ro_cleanup; } for (offset = 0; offset < length; offset += n) { ret = bdrv_is_allocated(bs, offset, COMMIT_BUF_SIZE, &n); if (ret < 0) { goto ro_cleanup; } if (ret) { ret = blk_pread(src, offset, buf, n); if (ret < 0) { goto ro_cleanup; } ret = blk_pwrite(backing, offset, buf, n, 0); if (ret < 0) { goto ro_cleanup; } } } if (drv->bdrv_make_empty) { ret = drv->bdrv_make_empty(bs); if (ret < 0) { goto ro_cleanup; } blk_flush(src); } /* * Make sure all data we wrote to the backing device is actually * stable on disk. */ blk_flush(backing); ret = 0; ro_cleanup: qemu_vfree(buf); blk_unref(backing); if (backing_file_bs) { bdrv_set_backing_hd(bs, backing_file_bs, &error_abort); } bdrv_unref(commit_top_bs); blk_unref(src); if (ro) { /* ignoring error return here */ bdrv_reopen_set_read_only(bs->backing->bs, true, NULL); } return ret; }
static void handle_notify(EventNotifier *e) { VirtIOBlockDataPlane *s = container_of(e, VirtIOBlockDataPlane, host_notifier); VirtIOBlock *vblk = VIRTIO_BLK(s->vdev); event_notifier_test_and_clear(&s->host_notifier); bdrv_io_plug(s->blk->conf.bs); for (;;) { MultiReqBuffer mrb = { .num_writes = 0, }; int ret; /* Disable guest->host notifies to avoid unnecessary vmexits */ vring_disable_notification(s->vdev, &s->vring); for (;;) { VirtIOBlockReq *req = virtio_blk_alloc_request(vblk); ret = vring_pop(s->vdev, &s->vring, &req->elem); if (ret < 0) { virtio_blk_free_request(req); break; /* no more requests */ } trace_virtio_blk_data_plane_process_request(s, req->elem.out_num, req->elem.in_num, req->elem.index); virtio_blk_handle_request(req, &mrb); } virtio_submit_multiwrite(s->blk->conf.bs, &mrb); if (likely(ret == -EAGAIN)) { /* vring emptied */ /* Re-enable guest->host notifies and stop processing the vring. * But if the guest has snuck in more descriptors, keep processing. */ if (vring_enable_notification(s->vdev, &s->vring)) { break; } } else { /* fatal error */ break; } } bdrv_io_unplug(s->blk->conf.bs); } /* Context: QEMU global mutex held */ void virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *blk, VirtIOBlockDataPlane **dataplane, Error **errp) { VirtIOBlockDataPlane *s; Error *local_err = NULL; BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); *dataplane = NULL; if (!blk->data_plane && !blk->iothread) { return; } /* Don't try if transport does not support notifiers. */ if (!k->set_guest_notifiers || !k->set_host_notifier) { error_setg(errp, "device is incompatible with x-data-plane " "(transport does not support notifiers)"); return; } /* If dataplane is (re-)enabled while the guest is running there could be * block jobs that can conflict. */ if (bdrv_op_is_blocked(blk->conf.bs, BLOCK_OP_TYPE_DATAPLANE, &local_err)) { error_setg(errp, "cannot start dataplane thread: %s", error_get_pretty(local_err)); error_free(local_err); return; } s = g_new0(VirtIOBlockDataPlane, 1); s->vdev = vdev; s->blk = blk; if (blk->iothread) { s->iothread = blk->iothread; object_ref(OBJECT(s->iothread)); } else { /* Create per-device IOThread if none specified. This is for * x-data-plane option compatibility. If x-data-plane is removed we * can drop this. */ object_initialize(&s->internal_iothread_obj, sizeof(s->internal_iothread_obj), TYPE_IOTHREAD); user_creatable_complete(OBJECT(&s->internal_iothread_obj), &error_abort); s->iothread = &s->internal_iothread_obj; } s->ctx = iothread_get_aio_context(s->iothread); s->bh = aio_bh_new(s->ctx, notify_guest_bh, s); error_setg(&s->blocker, "block device is in use by data plane"); bdrv_op_block_all(blk->conf.bs, s->blocker); bdrv_op_unblock(blk->conf.bs, BLOCK_OP_TYPE_RESIZE, s->blocker); bdrv_op_unblock(blk->conf.bs, BLOCK_OP_TYPE_DRIVE_DEL, s->blocker); *dataplane = s; }