Block layer core and image format patches

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJVevYFAAoJEH8JsnLIjy/W3jEP/0hiQ3rCRZ/he8s5maTdT+TR
 YSeHkB5rKpz0Uopn1DMn1QrIbUVzX7dyb+uf9zQ0/xRQIzf6k8uxqU/NWrdoF3NK
 qx91dGWedwnG+TEBIMbcR7nMrw4dP6kH7uPz/VWMXDHVLz0HIcD95qhKgs0mSY6J
 dWqex6ACjXM68zJU5IioagU9evV80WZE1S8z7zfixxtTBx5hCaTVbwalkaCxcrXw
 PbZle55rjI8B10+OzgBw0fq10nias+NTndU9CwNBboxmEtAjq8/mQ663vcWlmiFo
 9a/hkda27Z5ut/0Tqk1v4uLHauylp++rrAabPBAuCFMKes6cdkddP15Q/r52aJ29
 5meodQtbet1rGrM+Aq4vuSuWId71PGypEI/3URDdNfYFNISoeLLsk4lcQUu7VrDD
 sRX3Jt8SI3nkIgOnhPyi7NDPmafxFt8yRt5vM8MyR5ynF8NS/2hiAc3wqnbXGjUj
 a5GqDCefb1yM0R5HvksuFFt3OnXlKJQ3J+ksXNUJf9DSAZPauqWD696pcTeg8wyy
 3PIGkczgUuKTVfFWd3THZxJLAo7ZuqvXBHHV8o1SeMBDxwh4FhTd8Kjvm3rUNFfl
 VDox4qwZ1AcxLrxgqazKU7sD9iWBDHURRcpOoUsBys7oxQZnhcmMp1fRlEkTOyrD
 HNiSNByqBrtkfeVzHlSe
 =QgYk
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging

Block layer core and image format patches

# gpg: Signature made Fri Jun 12 16:08:53 2015 BST using RSA key ID C88F2FD6
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>"

* remotes/kevin/tags/for-upstream: (25 commits)
  block: Fix reopen flag inheritance
  block: Add BlockDriverState.inherits_from
  block: Add list of children to BlockDriverState
  queue.h: Add QLIST_FIX_HEAD_PTR()
  block: Drain requests before swapping nodes in bdrv_swap()
  block: Move flag inheritance to bdrv_open_inherit()
  block: Use QemuOpts in bdrv_open_common()
  block: Use macro for cache option names
  vmdk: Use bdrv_open_image()
  quorum: Use bdrv_open_image()
  check-qdict: Test cases for new functions
  qdict: Add qdict_{set,copy}_default()
  qdict: Add qdict_array_entries()
  iotests: Add tests for overriding BDRV_O_PROTOCOL
  block: driver should override flags in bdrv_open()
  block: Change bitmap truncate conditional to assertion
  block: record new size in bdrv_dirty_bitmap_truncate
  raw-posix: Fix .bdrv_co_get_block_status() for unaligned image size
  vmdk: Use vmdk_find_index_in_cluster everywhere
  vmdk: Fix index_in_cluster calculation in vmdk_co_get_block_status
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2015-06-15 10:43:06 +01:00
commit f3e3b083d4
25 changed files with 658 additions and 159 deletions

239
block.c
View File

@ -80,6 +80,12 @@ static QTAILQ_HEAD(, BlockDriverState) graph_bdrv_states =
static QLIST_HEAD(, BlockDriver) bdrv_drivers = static QLIST_HEAD(, BlockDriver) bdrv_drivers =
QLIST_HEAD_INITIALIZER(bdrv_drivers); QLIST_HEAD_INITIALIZER(bdrv_drivers);
static int bdrv_open_inherit(BlockDriverState **pbs, const char *filename,
const char *reference, QDict *options, int flags,
BlockDriverState *parent,
const BdrvChildRole *child_role,
BlockDriver *drv, Error **errp);
static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs); static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);
/* If non-zero, use only whitelisted block drivers */ /* If non-zero, use only whitelisted block drivers */
static int use_bdrv_whitelist; static int use_bdrv_whitelist;
@ -683,8 +689,8 @@ static int bdrv_temp_snapshot_flags(int flags)
} }
/* /*
* Returns the flags that bs->file should get, based on the given flags for * Returns the flags that bs->file should get if a protocol driver is expected,
* the parent BDS * based on the given flags for the parent BDS
*/ */
static int bdrv_inherited_flags(int flags) static int bdrv_inherited_flags(int flags)
{ {
@ -701,6 +707,25 @@ static int bdrv_inherited_flags(int flags)
return flags; return flags;
} }
const BdrvChildRole child_file = {
.inherit_flags = bdrv_inherited_flags,
};
/*
* Returns the flags that bs->file should get if the use of formats (and not
* only protocols) is permitted for it, based on the given flags for the parent
* BDS
*/
static int bdrv_inherited_fmt_flags(int parent_flags)
{
int flags = child_file.inherit_flags(parent_flags);
return flags & ~BDRV_O_PROTOCOL;
}
const BdrvChildRole child_format = {
.inherit_flags = bdrv_inherited_fmt_flags,
};
/* /*
* Returns the flags that bs->backing_hd should get, based on the given flags * Returns the flags that bs->backing_hd should get, based on the given flags
* for the parent BDS * for the parent BDS
@ -716,6 +741,10 @@ static int bdrv_backing_flags(int flags)
return flags; return flags;
} }
static const BdrvChildRole child_backing = {
.inherit_flags = bdrv_backing_flags,
};
static int bdrv_open_flags(BlockDriverState *bs, int flags) static int bdrv_open_flags(BlockDriverState *bs, int flags)
{ {
int open_flags = flags | BDRV_O_CACHE_WB; int open_flags = flags | BDRV_O_CACHE_WB;
@ -768,6 +797,19 @@ static void bdrv_assign_node_name(BlockDriverState *bs,
QTAILQ_INSERT_TAIL(&graph_bdrv_states, bs, node_list); QTAILQ_INSERT_TAIL(&graph_bdrv_states, bs, node_list);
} }
static QemuOptsList bdrv_runtime_opts = {
.name = "bdrv_common",
.head = QTAILQ_HEAD_INITIALIZER(bdrv_runtime_opts.head),
.desc = {
{
.name = "node-name",
.type = QEMU_OPT_STRING,
.help = "Node name of the block device node",
},
{ /* end of list */ }
},
};
/* /*
* Common part for opening disk images and files * Common part for opening disk images and files
* *
@ -779,6 +821,7 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
int ret, open_flags; int ret, open_flags;
const char *filename; const char *filename;
const char *node_name = NULL; const char *node_name = NULL;
QemuOpts *opts;
Error *local_err = NULL; Error *local_err = NULL;
assert(drv != NULL); assert(drv != NULL);
@ -799,23 +842,22 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
trace_bdrv_open_common(bs, filename ?: "", flags, drv->format_name); trace_bdrv_open_common(bs, filename ?: "", flags, drv->format_name);
node_name = qdict_get_try_str(options, "node-name"); opts = qemu_opts_create(&bdrv_runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, options, &local_err);
if (local_err) {
error_propagate(errp, local_err);
ret = -EINVAL;
goto fail_opts;
}
node_name = qemu_opt_get(opts, "node-name");
bdrv_assign_node_name(bs, node_name, &local_err); bdrv_assign_node_name(bs, node_name, &local_err);
if (local_err) { if (local_err) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
return -EINVAL; ret = -EINVAL;
} goto fail_opts;
qdict_del(options, "node-name");
/* bdrv_open() with directly using a protocol as drv. This layer is already
* opened, so assign it to bs (while file becomes a closed BlockDriverState)
* and return immediately. */
if (file != NULL && drv->bdrv_file_open) {
bdrv_swap(file, bs);
return 0;
} }
bs->open_flags = flags;
bs->guest_block_size = 512; bs->guest_block_size = 512;
bs->request_alignment = 512; bs->request_alignment = 512;
bs->zero_beyond_eof = true; bs->zero_beyond_eof = true;
@ -828,7 +870,8 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
? "Driver '%s' can only be used for read-only devices" ? "Driver '%s' can only be used for read-only devices"
: "Driver '%s' is not whitelisted", : "Driver '%s' is not whitelisted",
drv->format_name); drv->format_name);
return -ENOTSUP; ret = -ENOTSUP;
goto fail_opts;
} }
assert(bs->copy_on_read == 0); /* bdrv_new() and bdrv_close() make it so */ assert(bs->copy_on_read == 0); /* bdrv_new() and bdrv_close() make it so */
@ -837,7 +880,8 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
bdrv_enable_copy_on_read(bs); bdrv_enable_copy_on_read(bs);
} else { } else {
error_setg(errp, "Can't use copy-on-read on read-only device"); error_setg(errp, "Can't use copy-on-read on read-only device");
return -EINVAL; ret = -EINVAL;
goto fail_opts;
} }
} }
@ -903,6 +947,8 @@ static int bdrv_open_common(BlockDriverState *bs, BlockDriverState *file,
assert(bdrv_opt_mem_align(bs) != 0); assert(bdrv_opt_mem_align(bs) != 0);
assert(bdrv_min_mem_align(bs) != 0); assert(bdrv_min_mem_align(bs) != 0);
assert((bs->request_alignment != 0) || bs->sg); assert((bs->request_alignment != 0) || bs->sg);
qemu_opts_del(opts);
return 0; return 0;
free_and_fail: free_and_fail:
@ -910,6 +956,8 @@ free_and_fail:
g_free(bs->opaque); g_free(bs->opaque);
bs->opaque = NULL; bs->opaque = NULL;
bs->drv = NULL; bs->drv = NULL;
fail_opts:
qemu_opts_del(opts);
return ret; return ret;
} }
@ -943,14 +991,17 @@ static QDict *parse_json_filename(const char *filename, Error **errp)
/* /*
* Fills in default options for opening images and converts the legacy * Fills in default options for opening images and converts the legacy
* filename/flags pair to option QDict entries. * filename/flags pair to option QDict entries.
* The BDRV_O_PROTOCOL flag in *flags will be set or cleared accordingly if a
* block driver has been specified explicitly.
*/ */
static int bdrv_fill_options(QDict **options, const char **pfilename, int flags, static int bdrv_fill_options(QDict **options, const char **pfilename,
BlockDriver *drv, Error **errp) int *flags, BlockDriver *drv, Error **errp)
{ {
const char *filename = *pfilename; const char *filename = *pfilename;
const char *drvname; const char *drvname;
bool protocol = flags & BDRV_O_PROTOCOL; bool protocol = *flags & BDRV_O_PROTOCOL;
bool parse_filename = false; bool parse_filename = false;
BlockDriver *tmp_drv;
Error *local_err = NULL; Error *local_err = NULL;
/* Parse json: pseudo-protocol */ /* Parse json: pseudo-protocol */
@ -968,6 +1019,24 @@ static int bdrv_fill_options(QDict **options, const char **pfilename, int flags,
*pfilename = filename = NULL; *pfilename = filename = NULL;
} }
drvname = qdict_get_try_str(*options, "driver");
/* If the user has explicitly specified the driver, this choice should
* override the BDRV_O_PROTOCOL flag */
tmp_drv = drv;
if (!tmp_drv && drvname) {
tmp_drv = bdrv_find_format(drvname);
}
if (tmp_drv) {
protocol = tmp_drv->bdrv_file_open;
}
if (protocol) {
*flags |= BDRV_O_PROTOCOL;
} else {
*flags &= ~BDRV_O_PROTOCOL;
}
/* Fetch the file name from the options QDict if necessary */ /* Fetch the file name from the options QDict if necessary */
if (protocol && filename) { if (protocol && filename) {
if (!qdict_haskey(*options, "filename")) { if (!qdict_haskey(*options, "filename")) {
@ -982,7 +1051,6 @@ static int bdrv_fill_options(QDict **options, const char **pfilename, int flags,
/* Find the right block driver */ /* Find the right block driver */
filename = qdict_get_try_str(*options, "filename"); filename = qdict_get_try_str(*options, "filename");
drvname = qdict_get_try_str(*options, "driver");
if (drv) { if (drv) {
if (drvname) { if (drvname) {
@ -1119,9 +1187,10 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp)
} }
assert(bs->backing_hd == NULL); assert(bs->backing_hd == NULL);
ret = bdrv_open(&backing_hd, ret = bdrv_open_inherit(&backing_hd,
*backing_filename ? backing_filename : NULL, NULL, options, *backing_filename ? backing_filename : NULL,
bdrv_backing_flags(bs->open_flags), NULL, &local_err); NULL, options, 0, bs, &child_backing,
NULL, &local_err);
if (ret < 0) { if (ret < 0) {
bdrv_unref(backing_hd); bdrv_unref(backing_hd);
backing_hd = NULL; backing_hd = NULL;
@ -1155,7 +1224,8 @@ free_exit:
* To conform with the behavior of bdrv_open(), *pbs has to be NULL. * To conform with the behavior of bdrv_open(), *pbs has to be NULL.
*/ */
int bdrv_open_image(BlockDriverState **pbs, const char *filename, int bdrv_open_image(BlockDriverState **pbs, const char *filename,
QDict *options, const char *bdref_key, int flags, QDict *options, const char *bdref_key,
BlockDriverState* parent, const BdrvChildRole *child_role,
bool allow_none, Error **errp) bool allow_none, Error **errp)
{ {
QDict *image_options; QDict *image_options;
@ -1183,7 +1253,8 @@ int bdrv_open_image(BlockDriverState **pbs, const char *filename,
goto done; goto done;
} }
ret = bdrv_open(pbs, filename, reference, image_options, flags, NULL, errp); ret = bdrv_open_inherit(pbs, filename, reference, image_options, 0,
parent, child_role, NULL, errp);
done: done:
qdict_del(options, bdref_key); qdict_del(options, bdref_key);
@ -1255,6 +1326,19 @@ out:
return ret; return ret;
} }
static void bdrv_attach_child(BlockDriverState *parent_bs,
BlockDriverState *child_bs,
const BdrvChildRole *child_role)
{
BdrvChild *child = g_new(BdrvChild, 1);
*child = (BdrvChild) {
.bs = child_bs,
.role = child_role,
};
QLIST_INSERT_HEAD(&parent_bs->children, child, next);
}
/* /*
* Opens a disk image (raw, qcow2, vmdk, ...) * Opens a disk image (raw, qcow2, vmdk, ...)
* *
@ -1270,9 +1354,11 @@ out:
* should be opened. If specified, neither options nor a filename may be given, * should be opened. If specified, neither options nor a filename may be given,
* nor can an existing BDS be reused (that is, *pbs has to be NULL). * nor can an existing BDS be reused (that is, *pbs has to be NULL).
*/ */
int bdrv_open(BlockDriverState **pbs, const char *filename, static int bdrv_open_inherit(BlockDriverState **pbs, const char *filename,
const char *reference, QDict *options, int flags, const char *reference, QDict *options, int flags,
BlockDriver *drv, Error **errp) BlockDriverState *parent,
const BdrvChildRole *child_role,
BlockDriver *drv, Error **errp)
{ {
int ret; int ret;
BlockDriverState *file = NULL, *bs; BlockDriverState *file = NULL, *bs;
@ -1281,6 +1367,8 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
int snapshot_flags = 0; int snapshot_flags = 0;
assert(pbs); assert(pbs);
assert(!child_role || !flags);
assert(!child_role == !parent);
if (reference) { if (reference) {
bool options_non_empty = options ? qdict_size(options) : false; bool options_non_empty = options ? qdict_size(options) : false;
@ -1303,6 +1391,9 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
return -ENODEV; return -ENODEV;
} }
bdrv_ref(bs); bdrv_ref(bs);
if (child_role) {
bdrv_attach_child(parent, bs, child_role);
}
*pbs = bs; *pbs = bs;
return 0; return 0;
} }
@ -1318,7 +1409,12 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
options = qdict_new(); options = qdict_new();
} }
ret = bdrv_fill_options(&options, &filename, flags, drv, &local_err); if (child_role) {
bs->inherits_from = parent;
flags = child_role->inherit_flags(parent->open_flags);
}
ret = bdrv_fill_options(&options, &filename, &flags, drv, &local_err);
if (local_err) { if (local_err) {
goto fail; goto fail;
} }
@ -1337,12 +1433,8 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
} }
assert(drvname || !(flags & BDRV_O_PROTOCOL)); assert(drvname || !(flags & BDRV_O_PROTOCOL));
if (drv && !drv->bdrv_file_open) {
/* If the user explicitly wants a format driver here, we'll need to add
* another layer for the protocol in bs->file */
flags &= ~BDRV_O_PROTOCOL;
}
bs->open_flags = flags;
bs->options = options; bs->options = options;
options = qdict_clone_shallow(options); options = qdict_clone_shallow(options);
@ -1357,9 +1449,9 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
} }
assert(file == NULL); assert(file == NULL);
bs->open_flags = flags;
ret = bdrv_open_image(&file, filename, options, "file", ret = bdrv_open_image(&file, filename, options, "file",
bdrv_inherited_flags(flags), bs, &child_file, true, &local_err);
true, &local_err);
if (ret < 0) { if (ret < 0) {
goto fail; goto fail;
} }
@ -1378,6 +1470,12 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
goto fail; goto fail;
} }
/* BDRV_O_PROTOCOL must be set iff a protocol BDS is about to be created */
assert(!!(flags & BDRV_O_PROTOCOL) == !!drv->bdrv_file_open);
/* file must be NULL if a protocol BDS is about to be created
* (the inverse results in an error message from bdrv_open_common()) */
assert(!(flags & BDRV_O_PROTOCOL) || !file);
/* Open the image */ /* Open the image */
ret = bdrv_open_common(bs, file, options, flags, drv, &local_err); ret = bdrv_open_common(bs, file, options, flags, drv, &local_err);
if (ret < 0) { if (ret < 0) {
@ -1440,6 +1538,10 @@ int bdrv_open(BlockDriverState **pbs, const char *filename,
goto close_and_fail; goto close_and_fail;
} }
if (child_role) {
bdrv_attach_child(parent, bs, child_role);
}
QDECREF(options); QDECREF(options);
*pbs = bs; *pbs = bs;
return 0; return 0;
@ -1476,6 +1578,14 @@ close_and_fail:
return ret; return ret;
} }
int bdrv_open(BlockDriverState **pbs, const char *filename,
const char *reference, QDict *options, int flags,
BlockDriver *drv, Error **errp)
{
return bdrv_open_inherit(pbs, filename, reference, options, flags, NULL,
NULL, drv, errp);
}
typedef struct BlockReopenQueueEntry { typedef struct BlockReopenQueueEntry {
bool prepared; bool prepared;
BDRVReopenState state; BDRVReopenState state;
@ -1506,6 +1616,8 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
assert(bs != NULL); assert(bs != NULL);
BlockReopenQueueEntry *bs_entry; BlockReopenQueueEntry *bs_entry;
BdrvChild *child;
if (bs_queue == NULL) { if (bs_queue == NULL) {
bs_queue = g_new0(BlockReopenQueue, 1); bs_queue = g_new0(BlockReopenQueue, 1);
QSIMPLEQ_INIT(bs_queue); QSIMPLEQ_INIT(bs_queue);
@ -1514,8 +1626,15 @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
/* bdrv_open() masks this flag out */ /* bdrv_open() masks this flag out */
flags &= ~BDRV_O_PROTOCOL; flags &= ~BDRV_O_PROTOCOL;
if (bs->file) { QLIST_FOREACH(child, &bs->children, next) {
bdrv_reopen_queue(bs_queue, bs->file, bdrv_inherited_flags(flags)); int child_flags;
if (child->bs->inherits_from != bs) {
continue;
}
child_flags = child->role->inherit_flags(flags);
bdrv_reopen_queue(bs_queue, child->bs, child_flags);
} }
bs_entry = g_new0(BlockReopenQueueEntry, 1); bs_entry = g_new0(BlockReopenQueueEntry, 1);
@ -1726,6 +1845,16 @@ void bdrv_close(BlockDriverState *bs)
notifier_list_notify(&bs->close_notifiers, bs); notifier_list_notify(&bs->close_notifiers, bs);
if (bs->drv) { if (bs->drv) {
BdrvChild *child, *next;
QLIST_FOREACH_SAFE(child, &bs->children, next, next) {
if (child->bs->inherits_from == bs) {
child->bs->inherits_from = NULL;
}
QLIST_REMOVE(child, next);
g_free(child);
}
if (bs->backing_hd) { if (bs->backing_hd) {
BlockDriverState *backing_hd = bs->backing_hd; BlockDriverState *backing_hd = bs->backing_hd;
bdrv_set_backing_hd(bs, NULL); bdrv_set_backing_hd(bs, NULL);
@ -1876,6 +2005,10 @@ static void bdrv_move_feature_fields(BlockDriverState *bs_dest,
void bdrv_swap(BlockDriverState *bs_new, BlockDriverState *bs_old) void bdrv_swap(BlockDriverState *bs_new, BlockDriverState *bs_old)
{ {
BlockDriverState tmp; BlockDriverState tmp;
BdrvChild *child;
bdrv_drain(bs_new);
bdrv_drain(bs_old);
/* The code needs to swap the node_name but simply swapping node_list won't /* The code needs to swap the node_name but simply swapping node_list won't
* work so first remove the nodes from the graph list, do the swap then * work so first remove the nodes from the graph list, do the swap then
@ -1935,6 +2068,30 @@ void bdrv_swap(BlockDriverState *bs_new, BlockDriverState *bs_old)
QTAILQ_INSERT_TAIL(&graph_bdrv_states, bs_old, node_list); QTAILQ_INSERT_TAIL(&graph_bdrv_states, bs_old, node_list);
} }
/*
* Update lh_first.le_prev for non-empty lists.
*
* The head of the op blocker list doesn't change because it is moved back
* in bdrv_move_feature_fields().
*/
assert(QLIST_EMPTY(&bs_old->tracked_requests));
assert(QLIST_EMPTY(&bs_new->tracked_requests));
QLIST_FIX_HEAD_PTR(&bs_new->children, next);
QLIST_FIX_HEAD_PTR(&bs_old->children, next);
/* Update references in bs->opaque and children */
QLIST_FOREACH(child, &bs_old->children, next) {
if (child->bs->inherits_from == bs_new) {
child->bs->inherits_from = bs_old;
}
}
QLIST_FOREACH(child, &bs_new->children, next) {
if (child->bs->inherits_from == bs_old) {
child->bs->inherits_from = bs_new;
}
}
bdrv_rebind(bs_new); bdrv_rebind(bs_new);
bdrv_rebind(bs_old); bdrv_rebind(bs_old);
} }
@ -1957,6 +2114,7 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top)
/* The contents of 'tmp' will become bs_top, as we are /* The contents of 'tmp' will become bs_top, as we are
* swapping bs_new and bs_top contents. */ * swapping bs_new and bs_top contents. */
bdrv_set_backing_hd(bs_top, bs_new); bdrv_set_backing_hd(bs_top, bs_new);
bdrv_attach_child(bs_top, bs_new, &child_backing);
} }
static void bdrv_delete(BlockDriverState *bs) static void bdrv_delete(BlockDriverState *bs)
@ -3242,10 +3400,9 @@ static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs)
uint64_t size = bdrv_nb_sectors(bs); uint64_t size = bdrv_nb_sectors(bs);
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) { QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
if (bdrv_dirty_bitmap_frozen(bitmap)) { assert(!bdrv_dirty_bitmap_frozen(bitmap));
continue;
}
hbitmap_truncate(bitmap->bitmap, size); hbitmap_truncate(bitmap->bitmap, size);
bitmap->size = size;
} }
} }

View File

@ -429,7 +429,7 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
/* Open the backing file */ /* Open the backing file */
assert(bs->file == NULL); assert(bs->file == NULL);
ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-image"), options, "image", ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-image"), options, "image",
flags | BDRV_O_PROTOCOL, false, &local_err); bs, &child_file, false, &local_err);
if (ret < 0) { if (ret < 0) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
goto out; goto out;

View File

@ -125,7 +125,7 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
/* Open the raw file */ /* Open the raw file */
assert(bs->file == NULL); assert(bs->file == NULL);
ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-raw"), options, ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-raw"), options,
"raw", flags | BDRV_O_PROTOCOL, false, &local_err); "raw", bs, &child_file, false, &local_err);
if (ret < 0) { if (ret < 0) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
goto fail; goto fail;
@ -134,7 +134,7 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
/* Open the test file */ /* Open the test file */
assert(s->test_file == NULL); assert(s->test_file == NULL);
ret = bdrv_open_image(&s->test_file, qemu_opt_get(opts, "x-image"), options, ret = bdrv_open_image(&s->test_file, qemu_opt_get(opts, "x-image"), options,
"test", flags, false, &local_err); "test", bs, &child_format, false, &local_err);
if (ret < 0) { if (ret < 0) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
s->test_file = NULL; s->test_file = NULL;

View File

@ -483,9 +483,11 @@ static const char *overlap_bool_option_names[QCOW2_OL_MAX_BITNR] = {
[QCOW2_OL_INACTIVE_L2_BITNR] = QCOW2_OPT_OVERLAP_INACTIVE_L2, [QCOW2_OL_INACTIVE_L2_BITNR] = QCOW2_OPT_OVERLAP_INACTIVE_L2,
}; };
static void read_cache_sizes(QemuOpts *opts, uint64_t *l2_cache_size, static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
uint64_t *l2_cache_size,
uint64_t *refcount_cache_size, Error **errp) uint64_t *refcount_cache_size, Error **errp)
{ {
BDRVQcowState *s = bs->opaque;
uint64_t combined_cache_size; uint64_t combined_cache_size;
bool l2_cache_size_set, refcount_cache_size_set, combined_cache_size_set; bool l2_cache_size_set, refcount_cache_size_set, combined_cache_size_set;
@ -525,7 +527,9 @@ static void read_cache_sizes(QemuOpts *opts, uint64_t *l2_cache_size,
} }
} else { } else {
if (!l2_cache_size_set && !refcount_cache_size_set) { if (!l2_cache_size_set && !refcount_cache_size_set) {
*l2_cache_size = DEFAULT_L2_CACHE_BYTE_SIZE; *l2_cache_size = MAX(DEFAULT_L2_CACHE_BYTE_SIZE,
(uint64_t)DEFAULT_L2_CACHE_CLUSTERS
* s->cluster_size);
*refcount_cache_size = *l2_cache_size *refcount_cache_size = *l2_cache_size
/ DEFAULT_L2_REFCOUNT_SIZE_RATIO; / DEFAULT_L2_REFCOUNT_SIZE_RATIO;
} else if (!l2_cache_size_set) { } else if (!l2_cache_size_set) {
@ -803,7 +807,8 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
goto fail; goto fail;
} }
read_cache_sizes(opts, &l2_cache_size, &refcount_cache_size, &local_err); read_cache_sizes(bs, opts, &l2_cache_size, &refcount_cache_size,
&local_err);
if (local_err) { if (local_err) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
ret = -EINVAL; ret = -EINVAL;

View File

@ -62,11 +62,14 @@
#define MIN_CLUSTER_BITS 9 #define MIN_CLUSTER_BITS 9
#define MAX_CLUSTER_BITS 21 #define MAX_CLUSTER_BITS 21
#define MIN_L2_CACHE_SIZE 1 /* cluster */ /* Must be at least 2 to cover COW */
#define MIN_L2_CACHE_SIZE 2 /* clusters */
/* Must be at least 4 to cover all cases of refcount table growth */ /* Must be at least 4 to cover all cases of refcount table growth */
#define MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */ #define MIN_REFCOUNT_CACHE_SIZE 4 /* clusters */
/* Whichever is more */
#define DEFAULT_L2_CACHE_CLUSTERS 8 /* clusters */
#define DEFAULT_L2_CACHE_BYTE_SIZE 1048576 /* bytes */ #define DEFAULT_L2_CACHE_BYTE_SIZE 1048576 /* bytes */
/* The refblock cache needs only a fourth of the L2 cache size to cover as many /* The refblock cache needs only a fourth of the L2 cache size to cover as many

View File

@ -866,25 +866,18 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
Error *local_err = NULL; Error *local_err = NULL;
QemuOpts *opts = NULL; QemuOpts *opts = NULL;
bool *opened; bool *opened;
QDict *sub = NULL;
QList *list = NULL;
const QListEntry *lentry;
int i; int i;
int ret = 0; int ret = 0;
qdict_flatten(options); qdict_flatten(options);
qdict_extract_subqdict(options, &sub, "children.");
qdict_array_split(sub, &list);
if (qdict_size(sub)) { /* count how many different children are present */
error_setg(&local_err, "Invalid option children.%s", s->num_children = qdict_array_entries(options, "children.");
qdict_first(sub)->key); if (s->num_children < 0) {
error_setg(&local_err, "Option children is not a valid array");
ret = -EINVAL; ret = -EINVAL;
goto exit; goto exit;
} }
/* count how many different children are present */
s->num_children = qlist_size(list);
if (s->num_children < 2) { if (s->num_children < 2) {
error_setg(&local_err, error_setg(&local_err,
"Number of provided children must be greater than 1"); "Number of provided children must be greater than 1");
@ -937,37 +930,17 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
s->bs = g_new0(BlockDriverState *, s->num_children); s->bs = g_new0(BlockDriverState *, s->num_children);
opened = g_new0(bool, s->num_children); opened = g_new0(bool, s->num_children);
for (i = 0, lentry = qlist_first(list); lentry; for (i = 0; i < s->num_children; i++) {
lentry = qlist_next(lentry), i++) { char indexstr[32];
QDict *d; ret = snprintf(indexstr, 32, "children.%d", i);
QString *string; assert(ret < 32);
switch (qobject_type(lentry->value))
{
/* List of options */
case QTYPE_QDICT:
d = qobject_to_qdict(lentry->value);
QINCREF(d);
ret = bdrv_open(&s->bs[i], NULL, NULL, d, flags, NULL,
&local_err);
break;
/* QMP reference */
case QTYPE_QSTRING:
string = qobject_to_qstring(lentry->value);
ret = bdrv_open(&s->bs[i], NULL, qstring_get_str(string), NULL,
flags, NULL, &local_err);
break;
default:
error_setg(&local_err, "Specification of child block device %i "
"is invalid", i);
ret = -EINVAL;
}
ret = bdrv_open_image(&s->bs[i], NULL, options, indexstr, bs,
&child_format, false, &local_err);
if (ret < 0) { if (ret < 0) {
goto close_exit; goto close_exit;
} }
opened[i] = true; opened[i] = true;
} }
@ -990,8 +963,6 @@ exit:
if (local_err) { if (local_err) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
} }
QDECREF(list);
QDECREF(sub);
return ret; return ret;
} }

View File

@ -321,37 +321,13 @@ static int vmdk_is_cid_valid(BlockDriverState *bs)
return 1; return 1;
} }
/* Queue extents, if any, for reopen() */ /* We have nothing to do for VMDK reopen, stubs just return success */
static int vmdk_reopen_prepare(BDRVReopenState *state, static int vmdk_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp) BlockReopenQueue *queue, Error **errp)
{ {
BDRVVmdkState *s;
int ret = -1;
int i;
VmdkExtent *e;
assert(state != NULL); assert(state != NULL);
assert(state->bs != NULL); assert(state->bs != NULL);
return 0;
if (queue == NULL) {
error_setg(errp, "No reopen queue for VMDK extents");
goto exit;
}
s = state->bs->opaque;
assert(s != NULL);
for (i = 0; i < s->num_extents; i++) {
e = &s->extents[i];
if (e->file != state->bs->file) {
bdrv_reopen_queue(queue, e->file, state->flags);
}
}
ret = 0;
exit:
return ret;
} }
static int vmdk_parent_open(BlockDriverState *bs) static int vmdk_parent_open(BlockDriverState *bs)
@ -543,7 +519,7 @@ static int vmdk_open_vmfs_sparse(BlockDriverState *bs,
} }
static int vmdk_open_desc_file(BlockDriverState *bs, int flags, char *buf, static int vmdk_open_desc_file(BlockDriverState *bs, int flags, char *buf,
Error **errp); QDict *options, Error **errp);
static char *vmdk_read_desc(BlockDriverState *file, uint64_t desc_offset, static char *vmdk_read_desc(BlockDriverState *file, uint64_t desc_offset,
Error **errp) Error **errp)
@ -582,7 +558,7 @@ static char *vmdk_read_desc(BlockDriverState *file, uint64_t desc_offset,
static int vmdk_open_vmdk4(BlockDriverState *bs, static int vmdk_open_vmdk4(BlockDriverState *bs,
BlockDriverState *file, BlockDriverState *file,
int flags, Error **errp) int flags, QDict *options, Error **errp)
{ {
int ret; int ret;
uint32_t magic; uint32_t magic;
@ -606,7 +582,7 @@ static int vmdk_open_vmdk4(BlockDriverState *bs,
if (!buf) { if (!buf) {
return -EINVAL; return -EINVAL;
} }
ret = vmdk_open_desc_file(bs, flags, buf, errp); ret = vmdk_open_desc_file(bs, flags, buf, options, errp);
g_free(buf); g_free(buf);
return ret; return ret;
} }
@ -763,7 +739,7 @@ static int vmdk_parse_description(const char *desc, const char *opt_name,
/* Open an extent file and append to bs array */ /* Open an extent file and append to bs array */
static int vmdk_open_sparse(BlockDriverState *bs, static int vmdk_open_sparse(BlockDriverState *bs,
BlockDriverState *file, int flags, BlockDriverState *file, int flags,
char *buf, Error **errp) char *buf, QDict *options, Error **errp)
{ {
uint32_t magic; uint32_t magic;
@ -773,7 +749,7 @@ static int vmdk_open_sparse(BlockDriverState *bs,
return vmdk_open_vmfs_sparse(bs, file, flags, errp); return vmdk_open_vmfs_sparse(bs, file, flags, errp);
break; break;
case VMDK4_MAGIC: case VMDK4_MAGIC:
return vmdk_open_vmdk4(bs, file, flags, errp); return vmdk_open_vmdk4(bs, file, flags, options, errp);
break; break;
default: default:
error_setg(errp, "Image not in VMDK format"); error_setg(errp, "Image not in VMDK format");
@ -783,7 +759,8 @@ static int vmdk_open_sparse(BlockDriverState *bs,
} }
static int vmdk_parse_extents(const char *desc, BlockDriverState *bs, static int vmdk_parse_extents(const char *desc, BlockDriverState *bs,
const char *desc_file_path, Error **errp) const char *desc_file_path, QDict *options,
Error **errp)
{ {
int ret; int ret;
int matches; int matches;
@ -797,6 +774,7 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs,
BlockDriverState *extent_file; BlockDriverState *extent_file;
BDRVVmdkState *s = bs->opaque; BDRVVmdkState *s = bs->opaque;
VmdkExtent *extent; VmdkExtent *extent;
char extent_opt_prefix[32];
while (*p) { while (*p) {
/* parse extent line in one of below formats: /* parse extent line in one of below formats:
@ -846,8 +824,12 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs,
extent_path = g_malloc0(PATH_MAX); extent_path = g_malloc0(PATH_MAX);
path_combine(extent_path, PATH_MAX, desc_file_path, fname); path_combine(extent_path, PATH_MAX, desc_file_path, fname);
extent_file = NULL; extent_file = NULL;
ret = bdrv_open(&extent_file, extent_path, NULL, NULL,
bs->open_flags | BDRV_O_PROTOCOL, NULL, errp); ret = snprintf(extent_opt_prefix, 32, "extents.%d", s->num_extents);
assert(ret < 32);
ret = bdrv_open_image(&extent_file, extent_path, options,
extent_opt_prefix, bs, &child_file, false, errp);
g_free(extent_path); g_free(extent_path);
if (ret) { if (ret) {
return ret; return ret;
@ -870,7 +852,8 @@ static int vmdk_parse_extents(const char *desc, BlockDriverState *bs,
if (!buf) { if (!buf) {
ret = -EINVAL; ret = -EINVAL;
} else { } else {
ret = vmdk_open_sparse(bs, extent_file, bs->open_flags, buf, errp); ret = vmdk_open_sparse(bs, extent_file, bs->open_flags, buf,
options, errp);
} }
g_free(buf); g_free(buf);
if (ret) { if (ret) {
@ -898,7 +881,7 @@ next_line:
} }
static int vmdk_open_desc_file(BlockDriverState *bs, int flags, char *buf, static int vmdk_open_desc_file(BlockDriverState *bs, int flags, char *buf,
Error **errp) QDict *options, Error **errp)
{ {
int ret; int ret;
char ct[128]; char ct[128];
@ -920,7 +903,7 @@ static int vmdk_open_desc_file(BlockDriverState *bs, int flags, char *buf,
} }
s->create_type = g_strdup(ct); s->create_type = g_strdup(ct);
s->desc_offset = 0; s->desc_offset = 0;
ret = vmdk_parse_extents(buf, bs, bs->file->exact_filename, errp); ret = vmdk_parse_extents(buf, bs, bs->file->exact_filename, options, errp);
exit: exit:
return ret; return ret;
} }
@ -942,11 +925,11 @@ static int vmdk_open(BlockDriverState *bs, QDict *options, int flags,
switch (magic) { switch (magic) {
case VMDK3_MAGIC: case VMDK3_MAGIC:
case VMDK4_MAGIC: case VMDK4_MAGIC:
ret = vmdk_open_sparse(bs, bs->file, flags, buf, errp); ret = vmdk_open_sparse(bs, bs->file, flags, buf, options, errp);
s->desc_offset = 0x200; s->desc_offset = 0x200;
break; break;
default: default:
ret = vmdk_open_desc_file(bs, flags, buf, errp); ret = vmdk_open_desc_file(bs, flags, buf, options, errp);
break; break;
} }
if (ret) { if (ret) {
@ -1248,6 +1231,17 @@ static VmdkExtent *find_extent(BDRVVmdkState *s,
return NULL; return NULL;
} }
static inline uint64_t vmdk_find_index_in_cluster(VmdkExtent *extent,
int64_t sector_num)
{
uint64_t index_in_cluster, extent_begin_sector, extent_relative_sector_num;
extent_begin_sector = extent->end_sector - extent->sectors;
extent_relative_sector_num = sector_num - extent_begin_sector;
index_in_cluster = extent_relative_sector_num % extent->cluster_sectors;
return index_in_cluster;
}
static int64_t coroutine_fn vmdk_co_get_block_status(BlockDriverState *bs, static int64_t coroutine_fn vmdk_co_get_block_status(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, int *pnum) int64_t sector_num, int nb_sectors, int *pnum)
{ {
@ -1285,7 +1279,7 @@ static int64_t coroutine_fn vmdk_co_get_block_status(BlockDriverState *bs,
break; break;
} }
index_in_cluster = sector_num % extent->cluster_sectors; index_in_cluster = vmdk_find_index_in_cluster(extent, sector_num);
n = extent->cluster_sectors - index_in_cluster; n = extent->cluster_sectors - index_in_cluster;
if (n > nb_sectors) { if (n > nb_sectors) {
n = nb_sectors; n = nb_sectors;
@ -1413,7 +1407,6 @@ static int vmdk_read(BlockDriverState *bs, int64_t sector_num,
BDRVVmdkState *s = bs->opaque; BDRVVmdkState *s = bs->opaque;
int ret; int ret;
uint64_t n, index_in_cluster; uint64_t n, index_in_cluster;
uint64_t extent_begin_sector, extent_relative_sector_num;
VmdkExtent *extent = NULL; VmdkExtent *extent = NULL;
uint64_t cluster_offset; uint64_t cluster_offset;
@ -1425,9 +1418,7 @@ static int vmdk_read(BlockDriverState *bs, int64_t sector_num,
ret = get_cluster_offset(bs, extent, NULL, ret = get_cluster_offset(bs, extent, NULL,
sector_num << 9, false, &cluster_offset, sector_num << 9, false, &cluster_offset,
0, 0); 0, 0);
extent_begin_sector = extent->end_sector - extent->sectors; index_in_cluster = vmdk_find_index_in_cluster(extent, sector_num);
extent_relative_sector_num = sector_num - extent_begin_sector;
index_in_cluster = extent_relative_sector_num % extent->cluster_sectors;
n = extent->cluster_sectors - index_in_cluster; n = extent->cluster_sectors - index_in_cluster;
if (n > nb_sectors) { if (n > nb_sectors) {
n = nb_sectors; n = nb_sectors;
@ -1489,7 +1480,6 @@ static int vmdk_write(BlockDriverState *bs, int64_t sector_num,
VmdkExtent *extent = NULL; VmdkExtent *extent = NULL;
int ret; int ret;
int64_t index_in_cluster, n; int64_t index_in_cluster, n;
uint64_t extent_begin_sector, extent_relative_sector_num;
uint64_t cluster_offset; uint64_t cluster_offset;
VmdkMetaData m_data; VmdkMetaData m_data;
@ -1505,9 +1495,7 @@ static int vmdk_write(BlockDriverState *bs, int64_t sector_num,
if (!extent) { if (!extent) {
return -EIO; return -EIO;
} }
extent_begin_sector = extent->end_sector - extent->sectors; index_in_cluster = vmdk_find_index_in_cluster(extent, sector_num);
extent_relative_sector_num = sector_num - extent_begin_sector;
index_in_cluster = extent_relative_sector_num % extent->cluster_sectors;
n = extent->cluster_sectors - index_in_cluster; n = extent->cluster_sectors - index_in_cluster;
if (n > nb_sectors) { if (n > nb_sectors) {
n = nb_sectors; n = nb_sectors;

View File

@ -393,13 +393,13 @@ static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,
} }
} }
if (qemu_opt_get_bool(opts, "cache.writeback", true)) { if (qemu_opt_get_bool(opts, BDRV_OPT_CACHE_WB, true)) {
bdrv_flags |= BDRV_O_CACHE_WB; bdrv_flags |= BDRV_O_CACHE_WB;
} }
if (qemu_opt_get_bool(opts, "cache.direct", false)) { if (qemu_opt_get_bool(opts, BDRV_OPT_CACHE_DIRECT, false)) {
bdrv_flags |= BDRV_O_NOCACHE; bdrv_flags |= BDRV_O_NOCACHE;
} }
if (qemu_opt_get_bool(opts, "cache.no-flush", false)) { if (qemu_opt_get_bool(opts, BDRV_OPT_CACHE_NO_FLUSH, false)) {
bdrv_flags |= BDRV_O_NO_FLUSH; bdrv_flags |= BDRV_O_NO_FLUSH;
} }
@ -742,16 +742,16 @@ DriveInfo *drive_new(QemuOpts *all_opts, BlockInterfaceType block_default_type)
} }
/* Specific options take precedence */ /* Specific options take precedence */
if (!qemu_opt_get(all_opts, "cache.writeback")) { if (!qemu_opt_get(all_opts, BDRV_OPT_CACHE_WB)) {
qemu_opt_set_bool(all_opts, "cache.writeback", qemu_opt_set_bool(all_opts, BDRV_OPT_CACHE_WB,
!!(flags & BDRV_O_CACHE_WB), &error_abort); !!(flags & BDRV_O_CACHE_WB), &error_abort);
} }
if (!qemu_opt_get(all_opts, "cache.direct")) { if (!qemu_opt_get(all_opts, BDRV_OPT_CACHE_DIRECT)) {
qemu_opt_set_bool(all_opts, "cache.direct", qemu_opt_set_bool(all_opts, BDRV_OPT_CACHE_DIRECT,
!!(flags & BDRV_O_NOCACHE), &error_abort); !!(flags & BDRV_O_NOCACHE), &error_abort);
} }
if (!qemu_opt_get(all_opts, "cache.no-flush")) { if (!qemu_opt_get(all_opts, BDRV_OPT_CACHE_NO_FLUSH)) {
qemu_opt_set_bool(all_opts, "cache.no-flush", qemu_opt_set_bool(all_opts, BDRV_OPT_CACHE_NO_FLUSH,
!!(flags & BDRV_O_NO_FLUSH), &error_abort); !!(flags & BDRV_O_NO_FLUSH), &error_abort);
} }
qemu_opt_unset(all_opts, "cache"); qemu_opt_unset(all_opts, "cache");
@ -3121,15 +3121,15 @@ QemuOptsList qemu_common_drive_opts = {
.type = QEMU_OPT_STRING, .type = QEMU_OPT_STRING,
.help = "discard operation (ignore/off, unmap/on)", .help = "discard operation (ignore/off, unmap/on)",
},{ },{
.name = "cache.writeback", .name = BDRV_OPT_CACHE_WB,
.type = QEMU_OPT_BOOL, .type = QEMU_OPT_BOOL,
.help = "enables writeback mode for any caches", .help = "enables writeback mode for any caches",
},{ },{
.name = "cache.direct", .name = BDRV_OPT_CACHE_DIRECT,
.type = QEMU_OPT_BOOL, .type = QEMU_OPT_BOOL,
.help = "enables use of O_DIRECT (bypass the host page cache)", .help = "enables use of O_DIRECT (bypass the host page cache)",
},{ },{
.name = "cache.no-flush", .name = BDRV_OPT_CACHE_NO_FLUSH,
.type = QEMU_OPT_BOOL, .type = QEMU_OPT_BOOL,
.help = "ignore any flush requests for the device", .help = "ignore any flush requests for the device",
},{ },{

View File

@ -12,6 +12,7 @@
/* block.c */ /* block.c */
typedef struct BlockDriver BlockDriver; typedef struct BlockDriver BlockDriver;
typedef struct BlockJob BlockJob; typedef struct BlockJob BlockJob;
typedef struct BdrvChildRole BdrvChildRole;
typedef struct BlockDriverInfo { typedef struct BlockDriverInfo {
/* in bytes, 0 if irrelevant */ /* in bytes, 0 if irrelevant */
@ -90,6 +91,14 @@ typedef struct HDGeometry {
#define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH) #define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH)
/* Option names of options parsed by the block layer */
#define BDRV_OPT_CACHE_WB "cache.writeback"
#define BDRV_OPT_CACHE_DIRECT "cache.direct"
#define BDRV_OPT_CACHE_NO_FLUSH "cache.no-flush"
#define BDRV_SECTOR_BITS 9 #define BDRV_SECTOR_BITS 9
#define BDRV_SECTOR_SIZE (1ULL << BDRV_SECTOR_BITS) #define BDRV_SECTOR_SIZE (1ULL << BDRV_SECTOR_BITS)
#define BDRV_SECTOR_MASK ~(BDRV_SECTOR_SIZE - 1) #define BDRV_SECTOR_MASK ~(BDRV_SECTOR_SIZE - 1)
@ -196,7 +205,8 @@ void bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top);
int bdrv_parse_cache_flags(const char *mode, int *flags); int bdrv_parse_cache_flags(const char *mode, int *flags);
int bdrv_parse_discard_flags(const char *mode, int *flags); int bdrv_parse_discard_flags(const char *mode, int *flags);
int bdrv_open_image(BlockDriverState **pbs, const char *filename, int bdrv_open_image(BlockDriverState **pbs, const char *filename,
QDict *options, const char *bdref_key, int flags, QDict *options, const char *bdref_key,
BlockDriverState* parent, const BdrvChildRole *child_role,
bool allow_none, Error **errp); bool allow_none, Error **errp);
void bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd); void bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd);
int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp); int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp);

View File

@ -330,6 +330,19 @@ typedef struct BdrvAioNotifier {
QLIST_ENTRY(BdrvAioNotifier) list; QLIST_ENTRY(BdrvAioNotifier) list;
} BdrvAioNotifier; } BdrvAioNotifier;
struct BdrvChildRole {
int (*inherit_flags)(int parent_flags);
};
extern const BdrvChildRole child_file;
extern const BdrvChildRole child_format;
typedef struct BdrvChild {
BlockDriverState *bs;
const BdrvChildRole *role;
QLIST_ENTRY(BdrvChild) next;
} BdrvChild;
/* /*
* Note: the function bdrv_append() copies and swaps contents of * Note: the function bdrv_append() copies and swaps contents of
* BlockDriverStates, so if you add new fields to this struct, please * BlockDriverStates, so if you add new fields to this struct, please
@ -429,6 +442,12 @@ struct BlockDriverState {
/* long-running background operation */ /* long-running background operation */
BlockJob *job; BlockJob *job;
/* The node that this node inherited default options from (and a reopen on
* which can affect this node by changing these defaults). This is always a
* parent node of this node. */
BlockDriverState *inherits_from;
QLIST_HEAD(, BdrvChild) children;
QDict *options; QDict *options;
BlockdevDetectZeroesOptions detect_zeroes; BlockdevDetectZeroesOptions detect_zeroes;

View File

@ -65,11 +65,15 @@ int64_t qdict_get_try_int(const QDict *qdict, const char *key,
int qdict_get_try_bool(const QDict *qdict, const char *key, int def_value); int qdict_get_try_bool(const QDict *qdict, const char *key, int def_value);
const char *qdict_get_try_str(const QDict *qdict, const char *key); const char *qdict_get_try_str(const QDict *qdict, const char *key);
void qdict_copy_default(QDict *dst, QDict *src, const char *key);
void qdict_set_default_str(QDict *dst, const char *key, const char *val);
QDict *qdict_clone_shallow(const QDict *src); QDict *qdict_clone_shallow(const QDict *src);
void qdict_flatten(QDict *qdict); void qdict_flatten(QDict *qdict);
void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start); void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start);
void qdict_array_split(QDict *src, QList **dst); void qdict_array_split(QDict *src, QList **dst);
int qdict_array_entries(QDict *src, const char *subqdict);
void qdict_join(QDict *dest, QDict *src, bool overwrite); void qdict_join(QDict *dest, QDict *src, bool overwrite);

View File

@ -117,6 +117,12 @@ struct { \
} \ } \
} while (/*CONSTCOND*/0) } while (/*CONSTCOND*/0)
#define QLIST_FIX_HEAD_PTR(head, field) do { \
if ((head)->lh_first != NULL) { \
(head)->lh_first->field.le_prev = &(head)->lh_first; \
} \
} while (/*CONSTCOND*/0)
#define QLIST_INSERT_AFTER(listelm, elm, field) do { \ #define QLIST_INSERT_AFTER(listelm, elm, field) do { \
if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \
(listelm)->field.le_next->field.le_prev = \ (listelm)->field.le_next->field.le_prev = \

View File

@ -477,6 +477,39 @@ static void qdict_destroy_obj(QObject *obj)
g_free(qdict); g_free(qdict);
} }
/**
* qdict_copy_default(): If no entry mapped by 'key' exists in 'dst' yet, the
* value of 'key' in 'src' is copied there (and the refcount increased
* accordingly).
*/
void qdict_copy_default(QDict *dst, QDict *src, const char *key)
{
QObject *val;
if (qdict_haskey(dst, key)) {
return;
}
val = qdict_get(src, key);
if (val) {
qobject_incref(val);
qdict_put_obj(dst, key, val);
}
}
/**
* qdict_set_default_str(): If no entry mapped by 'key' exists in 'dst' yet, a
* new QString initialised by 'val' is put there.
*/
void qdict_set_default_str(QDict *dst, const char *key, const char *val)
{
if (qdict_haskey(dst, key)) {
return;
}
qdict_put(dst, key, qstring_from_str(val));
}
static void qdict_flatten_qdict(QDict *qdict, QDict *target, static void qdict_flatten_qdict(QDict *qdict, QDict *target,
const char *prefix); const char *prefix);
@ -597,17 +630,21 @@ void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start)
} }
} }
static bool qdict_has_prefixed_entries(const QDict *src, const char *start) static int qdict_count_prefixed_entries(const QDict *src, const char *start)
{ {
const QDictEntry *entry; const QDictEntry *entry;
int count = 0;
for (entry = qdict_first(src); entry; entry = qdict_next(src, entry)) { for (entry = qdict_first(src); entry; entry = qdict_next(src, entry)) {
if (strstart(entry->key, start, NULL)) { if (strstart(entry->key, start, NULL)) {
return true; if (count == INT_MAX) {
return -ERANGE;
}
count++;
} }
} }
return false; return count;
} }
/** /**
@ -646,7 +683,8 @@ void qdict_array_split(QDict *src, QList **dst)
snprintf_ret = snprintf(prefix, 32, "%u.", i); snprintf_ret = snprintf(prefix, 32, "%u.", i);
assert(snprintf_ret < 32); assert(snprintf_ret < 32);
is_subqdict = qdict_has_prefixed_entries(src, prefix); /* Overflow is the same as positive non-zero results */
is_subqdict = qdict_count_prefixed_entries(src, prefix);
// There may be either a single subordinate object (named "%u") or // There may be either a single subordinate object (named "%u") or
// multiple objects (each with a key prefixed "%u."), but not both. // multiple objects (each with a key prefixed "%u."), but not both.
@ -666,6 +704,71 @@ void qdict_array_split(QDict *src, QList **dst)
} }
} }
/**
* qdict_array_entries(): Returns the number of direct array entries if the
* sub-QDict of src specified by the prefix in subqdict (or src itself for
* prefix == "") is valid as an array, i.e. the length of the created list if
* the sub-QDict would become empty after calling qdict_array_split() on it. If
* the array is not valid, -EINVAL is returned.
*/
int qdict_array_entries(QDict *src, const char *subqdict)
{
const QDictEntry *entry;
unsigned i;
unsigned entries = 0;
size_t subqdict_len = strlen(subqdict);
assert(!subqdict_len || subqdict[subqdict_len - 1] == '.');
/* qdict_array_split() loops until UINT_MAX, but as we want to return
* negative errors, we only have a signed return value here. Any additional
* entries will lead to -EINVAL. */
for (i = 0; i < INT_MAX; i++) {
QObject *subqobj;
int subqdict_entries;
size_t slen = 32 + subqdict_len;
char indexstr[slen], prefix[slen];
size_t snprintf_ret;
snprintf_ret = snprintf(indexstr, slen, "%s%u", subqdict, i);
assert(snprintf_ret < slen);
subqobj = qdict_get(src, indexstr);
snprintf_ret = snprintf(prefix, slen, "%s%u.", subqdict, i);
assert(snprintf_ret < slen);
subqdict_entries = qdict_count_prefixed_entries(src, prefix);
if (subqdict_entries < 0) {
return subqdict_entries;
}
/* There may be either a single subordinate object (named "%u") or
* multiple objects (each with a key prefixed "%u."), but not both. */
if (subqobj && subqdict_entries) {
return -EINVAL;
} else if (!subqobj && !subqdict_entries) {
break;
}
entries += subqdict_entries ? subqdict_entries : 1;
}
/* Consider everything handled that isn't part of the given sub-QDict */
for (entry = qdict_first(src); entry; entry = qdict_next(src, entry)) {
if (!strstart(qdict_entry_key(entry), subqdict, NULL)) {
entries++;
}
}
/* Anything left in the sub-QDict that wasn't handled? */
if (qdict_size(src) != entries) {
return -EINVAL;
}
return i;
}
/** /**
* qdict_join(): Absorb the src QDict into the dest QDict, that is, move all * qdict_join(): Absorb the src QDict into the dest QDict, that is, move all
* elements from src to dest. * elements from src to dest.

View File

@ -152,6 +152,28 @@ static void qdict_get_try_str_test(void)
QDECREF(tests_dict); QDECREF(tests_dict);
} }
static void qdict_defaults_test(void)
{
QDict *dict, *copy;
dict = qdict_new();
copy = qdict_new();
qdict_set_default_str(dict, "foo", "abc");
qdict_set_default_str(dict, "foo", "def");
g_assert_cmpstr(qdict_get_str(dict, "foo"), ==, "abc");
qdict_set_default_str(dict, "bar", "ghi");
qdict_copy_default(copy, dict, "foo");
g_assert_cmpstr(qdict_get_str(copy, "foo"), ==, "abc");
qdict_set_default_str(copy, "bar", "xyz");
qdict_copy_default(copy, dict, "bar");
g_assert_cmpstr(qdict_get_str(copy, "bar"), ==, "xyz");
QDECREF(copy);
QDECREF(dict);
}
static void qdict_haskey_not_test(void) static void qdict_haskey_not_test(void)
{ {
QDict *tests_dict = qdict_new(); QDict *tests_dict = qdict_new();
@ -444,6 +466,49 @@ static void qdict_array_split_test(void)
QDECREF(test_dict); QDECREF(test_dict);
} }
static void qdict_array_entries_test(void)
{
QDict *dict = qdict_new();
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 0);
qdict_put(dict, "bar", qint_from_int(0));
qdict_put(dict, "baz.0", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 0);
qdict_put(dict, "foo.1", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, -EINVAL);
qdict_put(dict, "foo.0", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 2);
qdict_put(dict, "foo.bar", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, -EINVAL);
qdict_del(dict, "foo.bar");
qdict_put(dict, "foo.2.a", qint_from_int(0));
qdict_put(dict, "foo.2.b", qint_from_int(0));
qdict_put(dict, "foo.2.c", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 3);
g_assert_cmpint(qdict_array_entries(dict, ""), ==, -EINVAL);
QDECREF(dict);
dict = qdict_new();
qdict_put(dict, "1", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, ""), ==, -EINVAL);
qdict_put(dict, "0", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, ""), ==, 2);
qdict_put(dict, "bar", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, ""), ==, -EINVAL);
qdict_del(dict, "bar");
qdict_put(dict, "2.a", qint_from_int(0));
qdict_put(dict, "2.b", qint_from_int(0));
qdict_put(dict, "2.c", qint_from_int(0));
g_assert_cmpint(qdict_array_entries(dict, ""), ==, 3);
QDECREF(dict);
}
static void qdict_join_test(void) static void qdict_join_test(void)
{ {
QDict *dict1, *dict2; QDict *dict1, *dict2;
@ -663,6 +728,7 @@ int main(int argc, char **argv)
g_test_add_func("/public/get_try_int", qdict_get_try_int_test); g_test_add_func("/public/get_try_int", qdict_get_try_int_test);
g_test_add_func("/public/get_str", qdict_get_str_test); g_test_add_func("/public/get_str", qdict_get_str_test);
g_test_add_func("/public/get_try_str", qdict_get_try_str_test); g_test_add_func("/public/get_try_str", qdict_get_try_str_test);
g_test_add_func("/public/defaults", qdict_defaults_test);
g_test_add_func("/public/haskey_not", qdict_haskey_not_test); g_test_add_func("/public/haskey_not", qdict_haskey_not_test);
g_test_add_func("/public/haskey", qdict_haskey_test); g_test_add_func("/public/haskey", qdict_haskey_test);
g_test_add_func("/public/del", qdict_del_test); g_test_add_func("/public/del", qdict_del_test);
@ -670,6 +736,7 @@ int main(int argc, char **argv)
g_test_add_func("/public/iterapi", qdict_iterapi_test); g_test_add_func("/public/iterapi", qdict_iterapi_test);
g_test_add_func("/public/flatten", qdict_flatten_test); g_test_add_func("/public/flatten", qdict_flatten_test);
g_test_add_func("/public/array_split", qdict_array_split_test); g_test_add_func("/public/array_split", qdict_array_split_test);
g_test_add_func("/public/array_entries", qdict_array_entries_test);
g_test_add_func("/public/join", qdict_join_test); g_test_add_func("/public/join", qdict_join_test);
g_test_add_func("/errors/put_exists", qdict_put_exists_test); g_test_add_func("/errors/put_exists", qdict_put_exists_test);

View File

@ -194,7 +194,6 @@ echo === Specifying the protocol layer ===
echo echo
run_qemu -drive file="$TEST_IMG",file.driver=file run_qemu -drive file="$TEST_IMG",file.driver=file
run_qemu -drive file="$TEST_IMG",file.driver=qcow2
echo echo
echo === Leaving out required options === echo === Leaving out required options ===

View File

@ -253,9 +253,6 @@ Testing: -drive file=TEST_DIR/t.qcow2,file.driver=file
QEMU X.Y.Z monitor - type 'help' for more information QEMU X.Y.Z monitor - type 'help' for more information
(qemu) qququiquit (qemu) qququiquit
Testing: -drive file=TEST_DIR/t.qcow2,file.driver=qcow2
QEMU_PROG: -drive file=TEST_DIR/t.qcow2,file.driver=qcow2: Block format 'qcow2' used by device '' doesn't support the option 'filename'
=== Leaving out required options === === Leaving out required options ===

View File

@ -93,6 +93,16 @@ $QEMU_IO -c "open -o l2-cache-size=1M,refcount-cache-size=0.25M $TEST_IMG" \
-c 'read -P 42 0 64k' \ -c 'read -P 42 0 64k' \
| _filter_qemu_io | _filter_qemu_io
echo
echo '=== Testing minimal L2 cache and COW ==='
echo
$QEMU_IMG snapshot -c foo "$TEST_IMG"
# This requires a COW operation, which accesses two L2 tables simultaneously
# (COW source and destination), so there must be enough space in the cache to
# place both tables there (and qemu should not crash)
$QEMU_IO -c "open -o cache-size=0 $TEST_IMG" -c 'write 0 64k' | _filter_qemu_io
# success, all done # success, all done
echo '*** done' echo '*** done'
rm -f $seq.full rm -f $seq.full

View File

@ -26,4 +26,9 @@ read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 65536/65536 bytes at offset 0 read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
=== Testing minimal L2 cache and COW ===
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
*** done *** done

60
tests/qemu-iotests/119 Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
#
# NBD test case for overriding BDRV_O_PROTOCOL by explicitly specifying
# a driver
#
# Copyright (C) 2015 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# creator
owner=mreitz@redhat.com
seq="$(basename $0)"
echo "QA output created by $seq"
here="$PWD"
tmp=/tmp/$$
status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
}
trap "_cleanup; exit \$status" 0 1 2 3 15
# get standard environment, filters and checks
. ./common.rc
. ./common.filter
_supported_fmt raw
_supported_proto nbd
_supported_os Linux
_make_test_img 64M
# This should not crash
echo "{'execute': 'qmp_capabilities'}
{'execute': 'human-monitor-command',
'arguments': {'command-line': 'qemu-io drv \"read -P 0 0 64k\"'}}
{'execute': 'quit'}" \
| $QEMU -drive id=drv,if=none,file="$TEST_IMG",driver=nbd \
-qmp stdio -nodefaults \
| _filter_qmp | _filter_qemu_io
# success, all done
echo
echo '*** done'
rm -f $seq.full
status=0

View File

@ -0,0 +1,11 @@
QA output created by 119
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
QMP_VERSION
{"return": {}}
read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{"return": ""}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
*** done

65
tests/qemu-iotests/120 Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
#
# Non-NBD test cases for overriding BDRV_O_PROTOCOL by explicitly
# specifying a driver
#
# Copyright (C) 2015 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# creator
owner=mreitz@redhat.com
seq="$(basename $0)"
echo "QA output created by $seq"
here="$PWD"
tmp=/tmp/$$
status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
}
trap "_cleanup; exit \$status" 0 1 2 3 15
# get standard environment, filters and checks
. ./common.rc
. ./common.filter
_supported_fmt generic
_supported_proto file
_supported_os Linux
_make_test_img 64M
echo "{'execute': 'qmp_capabilities'}
{'execute': 'human-monitor-command',
'arguments': {'command-line': 'qemu-io drv \"write -P 42 0 64k\"'}}
{'execute': 'quit'}" \
| $QEMU -qmp stdio -nodefaults \
-drive id=drv,if=none,file="$TEST_IMG",driver=raw,file.driver=$IMGFMT \
| _filter_qmp | _filter_qemu_io
$QEMU_IO -c 'read -P 42 0 64k' "$TEST_IMG" | _filter_qemu_io
$QEMU_IO_PROG -c 'read -P 42 0 64k' \
"json:{'driver': 'raw', 'file': {'driver': '$IMGFMT', 'file': {'filename': '$TEST_IMG'}}}" \
| _filter_qemu_io
# success, all done
echo
echo '*** done'
rm -f $seq.full
status=0

View File

@ -0,0 +1,15 @@
QA output created by 120
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
QMP_VERSION
{"return": {}}
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{"return": ""}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
*** done

View File

@ -125,7 +125,7 @@ class TestIncrementalBackup(iotests.QMPTestCase):
event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
match={'data': {'device': kwargs['device']}}) match={'data': {'device': kwargs['device']}})
self.assertIsNotNone(event) self.assertNotEqual(event, None)
try: try:
failure = self.dictpath(event, 'data/error') failure = self.dictpath(event, 'data/error')

View File

@ -29,6 +29,7 @@ tmp=/tmp/$$
status=1 # failure is the default! status=1 # failure is the default!
devname="eiodev$$" devname="eiodev$$"
sudo=""
_setup_eiodev() _setup_eiodev()
{ {
@ -37,6 +38,7 @@ _setup_eiodev()
echo "0 $((1024 * 1024 * 1024 / 512)) error" | \ echo "0 $((1024 * 1024 * 1024 / 512)) error" | \
$cmd dmsetup create "$devname" 2>/dev/null $cmd dmsetup create "$devname" 2>/dev/null
if [ "$?" -eq 0 ]; then if [ "$?" -eq 0 ]; then
sudo="$cmd"
return return
fi fi
done done
@ -74,7 +76,7 @@ TEST_IMG="/dev/mapper/$devname"
echo echo
echo "== reading from error device ==" echo "== reading from error device =="
# Opening image should succeed but the read operation should fail # Opening image should succeed but the read operation should fail
$QEMU_IO --format "$IMGFMT" --nocache -c "read 0 65536" "$TEST_IMG" | _filter_qemu_io $sudo $QEMU_IO --format "$IMGFMT" --nocache -c "read 0 65536" "$TEST_IMG" | _filter_qemu_io
# success, all done # success, all done
echo "*** done" echo "*** done"

View File

@ -121,6 +121,8 @@
114 rw auto quick 114 rw auto quick
115 rw auto 115 rw auto
116 rw auto quick 116 rw auto quick
119 rw auto quick
120 rw auto quick
121 rw auto 121 rw auto
122 rw auto 122 rw auto
123 rw auto quick 123 rw auto quick