|  | /* | 
|  | * QEMU System Emulator block driver | 
|  | * | 
|  | * Copyright (c) 2011 IBM Corp. | 
|  | * Copyright (c) 2012 Red Hat, Inc. | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | * of this software and associated documentation files (the "Software"), to deal | 
|  | * in the Software without restriction, including without limitation the rights | 
|  | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | * copies of the Software, and to permit persons to whom the Software is | 
|  | * furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | 
|  | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | * THE SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include "config-host.h" | 
|  | #include "qemu-common.h" | 
|  | #include "trace.h" | 
|  | #include "monitor/monitor.h" | 
|  | #include "block/block.h" | 
|  | #include "block/blockjob.h" | 
|  | #include "block/block_int.h" | 
|  | #include "qapi/qmp/qjson.h" | 
|  | #include "block/coroutine.h" | 
|  | #include "qmp-commands.h" | 
|  | #include "qemu/timer.h" | 
|  |  | 
|  | void *block_job_create(const BlockJobType *job_type, BlockDriverState *bs, | 
|  | int64_t speed, BlockDriverCompletionFunc *cb, | 
|  | void *opaque, Error **errp) | 
|  | { | 
|  | BlockJob *job; | 
|  |  | 
|  | if (bs->job || bdrv_in_use(bs)) { | 
|  | error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bs)); | 
|  | return NULL; | 
|  | } | 
|  | bdrv_set_in_use(bs, 1); | 
|  |  | 
|  | job = g_malloc0(job_type->instance_size); | 
|  | job->job_type      = job_type; | 
|  | job->bs            = bs; | 
|  | job->cb            = cb; | 
|  | job->opaque        = opaque; | 
|  | job->busy          = true; | 
|  | bs->job = job; | 
|  |  | 
|  | /* Only set speed when necessary to avoid NotSupported error */ | 
|  | if (speed != 0) { | 
|  | Error *local_err = NULL; | 
|  |  | 
|  | block_job_set_speed(job, speed, &local_err); | 
|  | if (error_is_set(&local_err)) { | 
|  | bs->job = NULL; | 
|  | g_free(job); | 
|  | bdrv_set_in_use(bs, 0); | 
|  | error_propagate(errp, local_err); | 
|  | return NULL; | 
|  | } | 
|  | } | 
|  | return job; | 
|  | } | 
|  |  | 
|  | void block_job_completed(BlockJob *job, int ret) | 
|  | { | 
|  | BlockDriverState *bs = job->bs; | 
|  |  | 
|  | assert(bs->job == job); | 
|  | job->cb(job->opaque, ret); | 
|  | bs->job = NULL; | 
|  | g_free(job); | 
|  | bdrv_set_in_use(bs, 0); | 
|  | } | 
|  |  | 
|  | void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp) | 
|  | { | 
|  | Error *local_err = NULL; | 
|  |  | 
|  | if (!job->job_type->set_speed) { | 
|  | error_set(errp, QERR_NOT_SUPPORTED); | 
|  | return; | 
|  | } | 
|  | job->job_type->set_speed(job, speed, &local_err); | 
|  | if (error_is_set(&local_err)) { | 
|  | error_propagate(errp, local_err); | 
|  | return; | 
|  | } | 
|  |  | 
|  | job->speed = speed; | 
|  | } | 
|  |  | 
|  | void block_job_complete(BlockJob *job, Error **errp) | 
|  | { | 
|  | if (job->paused || job->cancelled || !job->job_type->complete) { | 
|  | error_set(errp, QERR_BLOCK_JOB_NOT_READY, job->bs->device_name); | 
|  | return; | 
|  | } | 
|  |  | 
|  | job->job_type->complete(job, errp); | 
|  | } | 
|  |  | 
|  | void block_job_pause(BlockJob *job) | 
|  | { | 
|  | job->paused = true; | 
|  | } | 
|  |  | 
|  | bool block_job_is_paused(BlockJob *job) | 
|  | { | 
|  | return job->paused; | 
|  | } | 
|  |  | 
|  | void block_job_resume(BlockJob *job) | 
|  | { | 
|  | job->paused = false; | 
|  | block_job_iostatus_reset(job); | 
|  | if (job->co && !job->busy) { | 
|  | qemu_coroutine_enter(job->co, NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | void block_job_cancel(BlockJob *job) | 
|  | { | 
|  | job->cancelled = true; | 
|  | block_job_resume(job); | 
|  | } | 
|  |  | 
|  | bool block_job_is_cancelled(BlockJob *job) | 
|  | { | 
|  | return job->cancelled; | 
|  | } | 
|  |  | 
|  | void block_job_iostatus_reset(BlockJob *job) | 
|  | { | 
|  | job->iostatus = BLOCK_DEVICE_IO_STATUS_OK; | 
|  | if (job->job_type->iostatus_reset) { | 
|  | job->job_type->iostatus_reset(job); | 
|  | } | 
|  | } | 
|  |  | 
|  | struct BlockCancelData { | 
|  | BlockJob *job; | 
|  | BlockDriverCompletionFunc *cb; | 
|  | void *opaque; | 
|  | bool cancelled; | 
|  | int ret; | 
|  | }; | 
|  |  | 
|  | static void block_job_cancel_cb(void *opaque, int ret) | 
|  | { | 
|  | struct BlockCancelData *data = opaque; | 
|  |  | 
|  | data->cancelled = block_job_is_cancelled(data->job); | 
|  | data->ret = ret; | 
|  | data->cb(data->opaque, ret); | 
|  | } | 
|  |  | 
|  | int block_job_cancel_sync(BlockJob *job) | 
|  | { | 
|  | struct BlockCancelData data; | 
|  | BlockDriverState *bs = job->bs; | 
|  |  | 
|  | assert(bs->job == job); | 
|  |  | 
|  | /* Set up our own callback to store the result and chain to | 
|  | * the original callback. | 
|  | */ | 
|  | data.job = job; | 
|  | data.cb = job->cb; | 
|  | data.opaque = job->opaque; | 
|  | data.ret = -EINPROGRESS; | 
|  | job->cb = block_job_cancel_cb; | 
|  | job->opaque = &data; | 
|  | block_job_cancel(job); | 
|  | while (data.ret == -EINPROGRESS) { | 
|  | qemu_aio_wait(); | 
|  | } | 
|  | return (data.cancelled && data.ret == 0) ? -ECANCELED : data.ret; | 
|  | } | 
|  |  | 
|  | void block_job_sleep_ns(BlockJob *job, QEMUClock *clock, int64_t ns) | 
|  | { | 
|  | assert(job->busy); | 
|  |  | 
|  | /* Check cancellation *before* setting busy = false, too!  */ | 
|  | if (block_job_is_cancelled(job)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | job->busy = false; | 
|  | if (block_job_is_paused(job)) { | 
|  | qemu_coroutine_yield(); | 
|  | } else { | 
|  | co_sleep_ns(clock, ns); | 
|  | } | 
|  | job->busy = true; | 
|  | } | 
|  |  | 
|  | BlockJobInfo *block_job_query(BlockJob *job) | 
|  | { | 
|  | BlockJobInfo *info = g_new0(BlockJobInfo, 1); | 
|  | info->type      = g_strdup(job->job_type->job_type); | 
|  | info->device    = g_strdup(bdrv_get_device_name(job->bs)); | 
|  | info->len       = job->len; | 
|  | info->busy      = job->busy; | 
|  | info->paused    = job->paused; | 
|  | info->offset    = job->offset; | 
|  | info->speed     = job->speed; | 
|  | info->io_status = job->iostatus; | 
|  | return info; | 
|  | } | 
|  |  | 
|  | static void block_job_iostatus_set_err(BlockJob *job, int error) | 
|  | { | 
|  | if (job->iostatus == BLOCK_DEVICE_IO_STATUS_OK) { | 
|  | job->iostatus = error == ENOSPC ? BLOCK_DEVICE_IO_STATUS_NOSPACE : | 
|  | BLOCK_DEVICE_IO_STATUS_FAILED; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | QObject *qobject_from_block_job(BlockJob *job) | 
|  | { | 
|  | return qobject_from_jsonf("{ 'type': %s," | 
|  | "'device': %s," | 
|  | "'len': %" PRId64 "," | 
|  | "'offset': %" PRId64 "," | 
|  | "'speed': %" PRId64 " }", | 
|  | job->job_type->job_type, | 
|  | bdrv_get_device_name(job->bs), | 
|  | job->len, | 
|  | job->offset, | 
|  | job->speed); | 
|  | } | 
|  |  | 
|  | void block_job_ready(BlockJob *job) | 
|  | { | 
|  | QObject *data = qobject_from_block_job(job); | 
|  | monitor_protocol_event(QEVENT_BLOCK_JOB_READY, data); | 
|  | qobject_decref(data); | 
|  | } | 
|  |  | 
|  | BlockErrorAction block_job_error_action(BlockJob *job, BlockDriverState *bs, | 
|  | BlockdevOnError on_err, | 
|  | int is_read, int error) | 
|  | { | 
|  | BlockErrorAction action; | 
|  |  | 
|  | switch (on_err) { | 
|  | case BLOCKDEV_ON_ERROR_ENOSPC: | 
|  | action = (error == ENOSPC) ? BDRV_ACTION_STOP : BDRV_ACTION_REPORT; | 
|  | break; | 
|  | case BLOCKDEV_ON_ERROR_STOP: | 
|  | action = BDRV_ACTION_STOP; | 
|  | break; | 
|  | case BLOCKDEV_ON_ERROR_REPORT: | 
|  | action = BDRV_ACTION_REPORT; | 
|  | break; | 
|  | case BLOCKDEV_ON_ERROR_IGNORE: | 
|  | action = BDRV_ACTION_IGNORE; | 
|  | break; | 
|  | default: | 
|  | abort(); | 
|  | } | 
|  | bdrv_emit_qmp_error_event(job->bs, QEVENT_BLOCK_JOB_ERROR, action, is_read); | 
|  | if (action == BDRV_ACTION_STOP) { | 
|  | block_job_pause(job); | 
|  | block_job_iostatus_set_err(job, error); | 
|  | if (bs != job->bs) { | 
|  | bdrv_iostatus_set_err(bs, error); | 
|  | } | 
|  | } | 
|  | return action; | 
|  | } |