block: Framework for reopening files safely

This is based on Supriya Kannery's bdrv_reopen() patch series.

This provides a transactional method to reopen multiple
images files safely.

Image files are queue for reopen via bdrv_reopen_queue(), and the
reopen occurs when bdrv_reopen_multiple() is called.  Changes are
staged in bdrv_reopen_prepare() and in the equivalent driver level
functions.  If any of the staged images fails a prepare, then all
of the images left untouched, and the staged changes for each image
abandoned.

Block drivers are passed a reopen state structure, that contains:
    * BDS to reopen
    * flags for the reopen
    * opaque pointer for any driver-specific data that needs to be
      persistent from _prepare to _commit/_abort
    * reopen queue pointer, if the driver needs to queue additional
      BDS for a reopen

Signed-off-by: Jeff Cody <jcody@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Jeff Cody 2012-09-20 15:13:19 -04:00 committed by Kevin Wolf
parent 55b110f24e
commit e971aa1273
3 changed files with 257 additions and 0 deletions

232
block.c
View File

@ -863,6 +863,238 @@ unlink_and_fail:
return ret;
}
typedef struct BlockReopenQueueEntry {
bool prepared;
BDRVReopenState state;
QSIMPLEQ_ENTRY(BlockReopenQueueEntry) entry;
} BlockReopenQueueEntry;
/*
* Adds a BlockDriverState to a simple queue for an atomic, transactional
* reopen of multiple devices.
*
* bs_queue can either be an existing BlockReopenQueue that has had QSIMPLE_INIT
* already performed, or alternatively may be NULL a new BlockReopenQueue will
* be created and initialized. This newly created BlockReopenQueue should be
* passed back in for subsequent calls that are intended to be of the same
* atomic 'set'.
*
* bs is the BlockDriverState to add to the reopen queue.
*
* flags contains the open flags for the associated bs
*
* returns a pointer to bs_queue, which is either the newly allocated
* bs_queue, or the existing bs_queue being used.
*
*/
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
BlockDriverState *bs, int flags)
{
assert(bs != NULL);
BlockReopenQueueEntry *bs_entry;
if (bs_queue == NULL) {
bs_queue = g_new0(BlockReopenQueue, 1);
QSIMPLEQ_INIT(bs_queue);
}
if (bs->file) {
bdrv_reopen_queue(bs_queue, bs->file, flags);
}
bs_entry = g_new0(BlockReopenQueueEntry, 1);
QSIMPLEQ_INSERT_TAIL(bs_queue, bs_entry, entry);
bs_entry->state.bs = bs;
bs_entry->state.flags = flags;
return bs_queue;
}
/*
* Reopen multiple BlockDriverStates atomically & transactionally.
*
* The queue passed in (bs_queue) must have been built up previous
* via bdrv_reopen_queue().
*
* Reopens all BDS specified in the queue, with the appropriate
* flags. All devices are prepared for reopen, and failure of any
* device will cause all device changes to be abandonded, and intermediate
* data cleaned up.
*
* If all devices prepare successfully, then the changes are committed
* to all devices.
*
*/
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
{
int ret = -1;
BlockReopenQueueEntry *bs_entry, *next;
Error *local_err = NULL;
assert(bs_queue != NULL);
bdrv_drain_all();
QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
if (bdrv_reopen_prepare(&bs_entry->state, bs_queue, &local_err)) {
error_propagate(errp, local_err);
goto cleanup;
}
bs_entry->prepared = true;
}
/* If we reach this point, we have success and just need to apply the
* changes
*/
QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
bdrv_reopen_commit(&bs_entry->state);
}
ret = 0;
cleanup:
QSIMPLEQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
if (ret && bs_entry->prepared) {
bdrv_reopen_abort(&bs_entry->state);
}
g_free(bs_entry);
}
g_free(bs_queue);
return ret;
}
/* Reopen a single BlockDriverState with the specified flags. */
int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp)
{
int ret = -1;
Error *local_err = NULL;
BlockReopenQueue *queue = bdrv_reopen_queue(NULL, bs, bdrv_flags);
ret = bdrv_reopen_multiple(queue, &local_err);
if (local_err != NULL) {
error_propagate(errp, local_err);
}
return ret;
}
/*
* Prepares a BlockDriverState for reopen. All changes are staged in the
* 'opaque' field of the BDRVReopenState, which is used and allocated by
* the block driver layer .bdrv_reopen_prepare()
*
* bs is the BlockDriverState to reopen
* flags are the new open flags
* queue is the reopen queue
*
* Returns 0 on success, non-zero on error. On error errp will be set
* as well.
*
* On failure, bdrv_reopen_abort() will be called to clean up any data.
* It is the responsibility of the caller to then call the abort() or
* commit() for any other BDS that have been left in a prepare() state
*
*/
int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
Error **errp)
{
int ret = -1;
Error *local_err = NULL;
BlockDriver *drv;
assert(reopen_state != NULL);
assert(reopen_state->bs->drv != NULL);
drv = reopen_state->bs->drv;
/* if we are to stay read-only, do not allow permission change
* to r/w */
if (!(reopen_state->bs->open_flags & BDRV_O_ALLOW_RDWR) &&
reopen_state->flags & BDRV_O_RDWR) {
error_set(errp, QERR_DEVICE_IS_READ_ONLY,
reopen_state->bs->device_name);
goto error;
}
ret = bdrv_flush(reopen_state->bs);
if (ret) {
error_set(errp, ERROR_CLASS_GENERIC_ERROR, "Error (%s) flushing drive",
strerror(-ret));
goto error;
}
if (drv->bdrv_reopen_prepare) {
ret = drv->bdrv_reopen_prepare(reopen_state, queue, &local_err);
if (ret) {
if (local_err != NULL) {
error_propagate(errp, local_err);
} else {
error_set(errp, QERR_OPEN_FILE_FAILED,
reopen_state->bs->filename);
}
goto error;
}
} else {
/* It is currently mandatory to have a bdrv_reopen_prepare()
* handler for each supported drv. */
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
drv->format_name, reopen_state->bs->device_name,
"reopening of file");
ret = -1;
goto error;
}
ret = 0;
error:
return ret;
}
/*
* Takes the staged changes for the reopen from bdrv_reopen_prepare(), and
* makes them final by swapping the staging BlockDriverState contents into
* the active BlockDriverState contents.
*/
void bdrv_reopen_commit(BDRVReopenState *reopen_state)
{
BlockDriver *drv;
assert(reopen_state != NULL);
drv = reopen_state->bs->drv;
assert(drv != NULL);
/* If there are any driver level actions to take */
if (drv->bdrv_reopen_commit) {
drv->bdrv_reopen_commit(reopen_state);
}
/* set BDS specific flags now */
reopen_state->bs->open_flags = reopen_state->flags;
reopen_state->bs->enable_write_cache = !!(reopen_state->flags &
BDRV_O_CACHE_WB);
reopen_state->bs->read_only = !(reopen_state->flags & BDRV_O_RDWR);
}
/*
* Abort the reopen, and delete and free the staged changes in
* reopen_state
*/
void bdrv_reopen_abort(BDRVReopenState *reopen_state)
{
BlockDriver *drv;
assert(reopen_state != NULL);
drv = reopen_state->bs->drv;
assert(drv != NULL);
if (drv->bdrv_reopen_abort) {
drv->bdrv_reopen_abort(reopen_state);
}
}
void bdrv_close(BlockDriverState *bs)
{
bdrv_flush(bs);

17
block.h
View File

@ -97,6 +97,15 @@ typedef enum {
BDRV_ACTION_REPORT, BDRV_ACTION_IGNORE, BDRV_ACTION_STOP
} BlockQMPEventAction;
typedef QSIMPLEQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
typedef struct BDRVReopenState {
BlockDriverState *bs;
int flags;
void *opaque;
} BDRVReopenState;
void bdrv_iostatus_enable(BlockDriverState *bs);
void bdrv_iostatus_reset(BlockDriverState *bs);
void bdrv_iostatus_disable(BlockDriverState *bs);
@ -131,6 +140,14 @@ int bdrv_parse_cache_flags(const char *mode, int *flags);
int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags);
int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
BlockDriver *drv);
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
BlockDriverState *bs, int flags);
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp);
int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp);
int bdrv_reopen_prepare(BDRVReopenState *reopen_state,
BlockReopenQueue *queue, Error **errp);
void bdrv_reopen_commit(BDRVReopenState *reopen_state);
void bdrv_reopen_abort(BDRVReopenState *reopen_state);
void bdrv_close(BlockDriverState *bs);
int bdrv_attach_dev(BlockDriverState *bs, void *dev);
void bdrv_attach_dev_nofail(BlockDriverState *bs, void *dev);

View File

@ -139,6 +139,13 @@ struct BlockDriver {
int instance_size;
int (*bdrv_probe)(const uint8_t *buf, int buf_size, const char *filename);
int (*bdrv_probe_device)(const char *filename);
/* For handling image reopen for split or non-split files */
int (*bdrv_reopen_prepare)(BDRVReopenState *reopen_state,
BlockReopenQueue *queue, Error **errp);
void (*bdrv_reopen_commit)(BDRVReopenState *reopen_state);
void (*bdrv_reopen_abort)(BDRVReopenState *reopen_state);
int (*bdrv_open)(BlockDriverState *bs, int flags);
int (*bdrv_file_open)(BlockDriverState *bs, const char *filename, int flags);
int (*bdrv_read)(BlockDriverState *bs, int64_t sector_num,
@ -336,6 +343,7 @@ struct BlockDriverState {
/* long-running background operation */
BlockJob *job;
};
int get_tmp_filename(char *filename, int size);