nbd patches for 2021-03-09
- Add Vladimir as NBD co-maintainer - Fix reporting of holes in NBD_CMD_BLOCK_STATUS - Improve command-line parsing accuracy of large numbers (anything going through qemu_strtosz), including the deprecation of hex+suffix - Improve some error reporting in the block layer -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAmBHlmIACgkQp6FrSiUn Q2q2cQgAqJWNb4J/ShjvzocDDPzJ0iBitFbg0huFPfbt4DScubEZo5wBJG7vOhOW hIHrWCRzGvRgsn0tcSfrgFaegmHKrLgjkibM7ou8ni9NC1kUBd3R/3FBNIMxhYf7 Q8Kfspl0LRfMJDKF9jdCnQ4Gxcd6h2OIYZqiWVg8V4Tc8WdCpIVOah7e7wjuW8bT vgZvfboUWm5AmIF9j/MxuMn+HFZ4ArSuFVL80ZaXlD00vRra7u3HZ8pUfcOlOujg 7HeouM1E5j3NNE6aZSN++x/EQ3sg0zmirbWUCcgAyRfdRkAmB15uh2PUzPxEIJKH UHUIW5LvNtz2+yzOAz2yK29OE523Yg== =blE1 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2021-03-09' into staging nbd patches for 2021-03-09 - Add Vladimir as NBD co-maintainer - Fix reporting of holes in NBD_CMD_BLOCK_STATUS - Improve command-line parsing accuracy of large numbers (anything going through qemu_strtosz), including the deprecation of hex+suffix - Improve some error reporting in the block layer # gpg: Signature made Tue 09 Mar 2021 15:38:10 GMT # gpg: using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A # gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full] # gpg: aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full] # gpg: aka "[jpeg image of size 6874]" [full] # Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2 F3AA A7A1 6B4A 2527 436A * remotes/ericb/tags/pull-nbd-2021-03-09: block/qcow2: refactor qcow2_update_options_prepare error paths block/qed: bdrv_qed_do_open: deal with errp block/qcow2: simplify qcow2_co_invalidate_cache() block/qcow2: read_cache_sizes: return status value block/qcow2-bitmap: return status from qcow2_store_persistent_dirty_bitmaps block/qcow2-bitmap: improve qcow2_load_dirty_bitmaps() interface block/qcow2: qcow2_get_specific_info(): drop error propagation blockjob: return status from block_job_set_speed() block/mirror: drop extra error propagation in commit_active_start() block: drop extra error propagation for bdrv_set_backing_hd blockdev: fix drive_backup_prepare() missed error block: check return value of bdrv_open_child and drop error propagation utils: Deprecate hex-with-suffix sizes utils: Improve qemu_strtosz() to have 64 bits of precision utils: Enhance testsuite for do_strtosz() nbd: server: Report holes for raw images MAINTAINERS: add Vladimir as co-maintainer of NBD Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
9abda42bf2
|
@ -3050,6 +3050,7 @@ F: block/iscsi-opts.c
|
||||||
|
|
||||||
Network Block Device (NBD)
|
Network Block Device (NBD)
|
||||||
M: Eric Blake <eblake@redhat.com>
|
M: Eric Blake <eblake@redhat.com>
|
||||||
|
M: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
|
||||||
L: qemu-block@nongnu.org
|
L: qemu-block@nongnu.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: block/nbd*
|
F: block/nbd*
|
||||||
|
@ -3060,6 +3061,7 @@ F: blockdev-nbd.c
|
||||||
F: docs/interop/nbd.txt
|
F: docs/interop/nbd.txt
|
||||||
F: docs/interop/qemu-nbd.rst
|
F: docs/interop/qemu-nbd.rst
|
||||||
T: git https://repo.or.cz/qemu/ericb.git nbd
|
T: git https://repo.or.cz/qemu/ericb.git nbd
|
||||||
|
T: git https://src.openvz.org/scm/~vsementsov/qemu.git nbd
|
||||||
|
|
||||||
NFS
|
NFS
|
||||||
M: Peter Lieven <pl@kamp.de>
|
M: Peter Lieven <pl@kamp.de>
|
||||||
|
|
6
block.c
6
block.c
|
@ -2995,11 +2995,9 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options,
|
||||||
|
|
||||||
/* Hook up the backing file link; drop our reference, bs owns the
|
/* Hook up the backing file link; drop our reference, bs owns the
|
||||||
* backing_hd reference now */
|
* backing_hd reference now */
|
||||||
bdrv_set_backing_hd(bs, backing_hd, &local_err);
|
ret = bdrv_set_backing_hd(bs, backing_hd, errp);
|
||||||
bdrv_unref(backing_hd);
|
bdrv_unref(backing_hd);
|
||||||
if (local_err) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto free_exit;
|
goto free_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -464,7 +464,6 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
QemuOpts *opts;
|
QemuOpts *opts;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
uint64_t align;
|
uint64_t align;
|
||||||
|
|
||||||
|
@ -494,10 +493,9 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
bs->file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options, "image",
|
bs->file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options, "image",
|
||||||
bs, &child_of_bds,
|
bs, &child_of_bds,
|
||||||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
||||||
false, &local_err);
|
false, errp);
|
||||||
if (local_err) {
|
if (!bs->file) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,19 +157,17 @@ static int blk_log_writes_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
/* Open the file */
|
/* Open the file */
|
||||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
|
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
|
||||||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, false,
|
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, false,
|
||||||
&local_err);
|
errp);
|
||||||
if (local_err) {
|
if (!bs->file) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open the log file */
|
/* Open the log file */
|
||||||
s->log_file = bdrv_open_child(NULL, options, "log", bs, &child_of_bds,
|
s->log_file = bdrv_open_child(NULL, options, "log", bs, &child_of_bds,
|
||||||
BDRV_CHILD_METADATA, false, &local_err);
|
BDRV_CHILD_METADATA, false, errp);
|
||||||
if (local_err) {
|
if (!s->log_file) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,16 +23,14 @@ typedef struct Request {
|
||||||
static int blkreplay_open(BlockDriverState *bs, QDict *options, int flags,
|
static int blkreplay_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Open the image file */
|
/* Open the image file */
|
||||||
bs->file = bdrv_open_child(NULL, options, "image", bs, &child_of_bds,
|
bs->file = bdrv_open_child(NULL, options, "image", bs, &child_of_bds,
|
||||||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
||||||
false, &local_err);
|
false, errp);
|
||||||
if (local_err) {
|
if (!bs->file) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,6 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
QemuOpts *opts;
|
QemuOpts *opts;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
|
@ -125,20 +124,18 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
bs->file = bdrv_open_child(qemu_opt_get(opts, "x-raw"), options, "raw",
|
bs->file = bdrv_open_child(qemu_opt_get(opts, "x-raw"), options, "raw",
|
||||||
bs, &child_of_bds,
|
bs, &child_of_bds,
|
||||||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
||||||
false, &local_err);
|
false, errp);
|
||||||
if (local_err) {
|
if (!bs->file) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open the test file */
|
/* Open the test file */
|
||||||
s->test_file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options,
|
s->test_file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options,
|
||||||
"test", bs, &child_of_bds, BDRV_CHILD_DATA,
|
"test", bs, &child_of_bds, BDRV_CHILD_DATA,
|
||||||
false, &local_err);
|
false, errp);
|
||||||
if (local_err) {
|
if (!s->test_file) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1860,8 +1860,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
|
||||||
bool auto_complete, Error **errp)
|
bool auto_complete, Error **errp)
|
||||||
{
|
{
|
||||||
bool base_read_only;
|
bool base_read_only;
|
||||||
Error *local_err = NULL;
|
BlockJob *job;
|
||||||
BlockJob *ret;
|
|
||||||
|
|
||||||
base_read_only = bdrv_is_read_only(base);
|
base_read_only = bdrv_is_read_only(base);
|
||||||
|
|
||||||
|
@ -1871,19 +1870,18 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = mirror_start_job(
|
job = mirror_start_job(
|
||||||
job_id, bs, creation_flags, base, NULL, speed, 0, 0,
|
job_id, bs, creation_flags, base, NULL, speed, 0, 0,
|
||||||
MIRROR_LEAVE_BACKING_CHAIN, false,
|
MIRROR_LEAVE_BACKING_CHAIN, false,
|
||||||
on_error, on_error, true, cb, opaque,
|
on_error, on_error, true, cb, opaque,
|
||||||
&commit_active_job_driver, false, base, auto_complete,
|
&commit_active_job_driver, false, base, auto_complete,
|
||||||
filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND,
|
filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND,
|
||||||
&local_err);
|
errp);
|
||||||
if (local_err) {
|
if (!job) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto error_restore_flags;
|
goto error_restore_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return job;
|
||||||
|
|
||||||
error_restore_flags:
|
error_restore_flags:
|
||||||
/* ignore error and errp for bdrv_reopen, because we want to propagate
|
/* ignore error and errp for bdrv_reopen, because we want to propagate
|
||||||
|
|
|
@ -950,25 +950,27 @@ static void set_readonly_helper(gpointer bitmap, gpointer value)
|
||||||
bdrv_dirty_bitmap_set_readonly(bitmap, (bool)value);
|
bdrv_dirty_bitmap_set_readonly(bitmap, (bool)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* qcow2_load_dirty_bitmaps()
|
/*
|
||||||
* Return value is a hint for caller: true means that the Qcow2 header was
|
* Return true on success, false on failure.
|
||||||
* updated. (false doesn't mean that the header should be updated by the
|
* If header_updated is not NULL then it is set appropriately regardless of
|
||||||
* caller, it just means that updating was not needed or the image cannot be
|
* the return value.
|
||||||
* written to).
|
|
||||||
* On failure the function returns false.
|
|
||||||
*/
|
*/
|
||||||
bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, bool *header_updated,
|
||||||
|
Error **errp)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
Qcow2BitmapList *bm_list;
|
Qcow2BitmapList *bm_list;
|
||||||
Qcow2Bitmap *bm;
|
Qcow2Bitmap *bm;
|
||||||
GSList *created_dirty_bitmaps = NULL;
|
GSList *created_dirty_bitmaps = NULL;
|
||||||
bool header_updated = false;
|
|
||||||
bool needs_update = false;
|
bool needs_update = false;
|
||||||
|
|
||||||
|
if (header_updated) {
|
||||||
|
*header_updated = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (s->nb_bitmaps == 0) {
|
if (s->nb_bitmaps == 0) {
|
||||||
/* No bitmaps - nothing to do */
|
/* No bitmaps - nothing to do */
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
|
bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
|
||||||
|
@ -1024,7 +1026,9 @@ bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
||||||
error_setg_errno(errp, -ret, "Can't update bitmap directory");
|
error_setg_errno(errp, -ret, "Can't update bitmap directory");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
header_updated = true;
|
if (header_updated) {
|
||||||
|
*header_updated = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!can_write(bs)) {
|
if (!can_write(bs)) {
|
||||||
|
@ -1035,7 +1039,7 @@ bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, Error **errp)
|
||||||
g_slist_free(created_dirty_bitmaps);
|
g_slist_free(created_dirty_bitmaps);
|
||||||
bitmap_list_free(bm_list);
|
bitmap_list_free(bm_list);
|
||||||
|
|
||||||
return header_updated;
|
return true;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
g_slist_foreach(created_dirty_bitmaps, release_dirty_bitmap_helper, bs);
|
g_slist_foreach(created_dirty_bitmaps, release_dirty_bitmap_helper, bs);
|
||||||
|
@ -1077,30 +1081,32 @@ static Qcow2BitmapInfoFlagsList *get_bitmap_info_flags(uint32_t flags)
|
||||||
/*
|
/*
|
||||||
* qcow2_get_bitmap_info_list()
|
* qcow2_get_bitmap_info_list()
|
||||||
* Returns a list of QCOW2 bitmap details.
|
* Returns a list of QCOW2 bitmap details.
|
||||||
* In case of no bitmaps, the function returns NULL and
|
* On success return true with info_list set (note, that if there are no
|
||||||
* the @errp parameter is not set.
|
* bitmaps, info_list is set to NULL).
|
||||||
* When bitmap information can not be obtained, the function returns
|
* On failure return false with errp set.
|
||||||
* NULL and the @errp parameter is set.
|
|
||||||
*/
|
*/
|
||||||
Qcow2BitmapInfoList *qcow2_get_bitmap_info_list(BlockDriverState *bs,
|
bool qcow2_get_bitmap_info_list(BlockDriverState *bs,
|
||||||
Error **errp)
|
Qcow2BitmapInfoList **info_list, Error **errp)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
Qcow2BitmapList *bm_list;
|
Qcow2BitmapList *bm_list;
|
||||||
Qcow2Bitmap *bm;
|
Qcow2Bitmap *bm;
|
||||||
Qcow2BitmapInfoList *list = NULL;
|
Qcow2BitmapInfoList **tail;
|
||||||
Qcow2BitmapInfoList **tail = &list;
|
|
||||||
|
|
||||||
if (s->nb_bitmaps == 0) {
|
if (s->nb_bitmaps == 0) {
|
||||||
return NULL;
|
*info_list = NULL;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
|
bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
|
||||||
s->bitmap_directory_size, errp);
|
s->bitmap_directory_size, errp);
|
||||||
if (bm_list == NULL) {
|
if (!bm_list) {
|
||||||
return NULL;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*info_list = NULL;
|
||||||
|
tail = info_list;
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(bm, bm_list, entry) {
|
QSIMPLEQ_FOREACH(bm, bm_list, entry) {
|
||||||
Qcow2BitmapInfo *info = g_new0(Qcow2BitmapInfo, 1);
|
Qcow2BitmapInfo *info = g_new0(Qcow2BitmapInfo, 1);
|
||||||
info->granularity = 1U << bm->granularity_bits;
|
info->granularity = 1U << bm->granularity_bits;
|
||||||
|
@ -1111,7 +1117,7 @@ Qcow2BitmapInfoList *qcow2_get_bitmap_info_list(BlockDriverState *bs,
|
||||||
|
|
||||||
bitmap_list_free(bm_list);
|
bitmap_list_free(bm_list);
|
||||||
|
|
||||||
return list;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp)
|
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp)
|
||||||
|
@ -1513,9 +1519,10 @@ out:
|
||||||
* readonly to begin with, and whether we opened directly or reopened to that
|
* readonly to begin with, and whether we opened directly or reopened to that
|
||||||
* state shouldn't matter for the state we get afterward.
|
* state shouldn't matter for the state we get afterward.
|
||||||
*/
|
*/
|
||||||
void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs,
|
bool qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs,
|
||||||
bool release_stored, Error **errp)
|
bool release_stored, Error **errp)
|
||||||
{
|
{
|
||||||
|
ERRP_GUARD();
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint32_t new_nb_bitmaps = s->nb_bitmaps;
|
uint32_t new_nb_bitmaps = s->nb_bitmaps;
|
||||||
|
@ -1535,7 +1542,7 @@ void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs,
|
||||||
bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
|
bm_list = bitmap_list_load(bs, s->bitmap_directory_offset,
|
||||||
s->bitmap_directory_size, errp);
|
s->bitmap_directory_size, errp);
|
||||||
if (bm_list == NULL) {
|
if (bm_list == NULL) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1650,7 +1657,7 @@ success:
|
||||||
}
|
}
|
||||||
|
|
||||||
bitmap_list_free(bm_list);
|
bitmap_list_free(bm_list);
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
QSIMPLEQ_FOREACH(bm, bm_list, entry) {
|
QSIMPLEQ_FOREACH(bm, bm_list, entry) {
|
||||||
|
@ -1668,16 +1675,14 @@ fail:
|
||||||
}
|
}
|
||||||
|
|
||||||
bitmap_list_free(bm_list);
|
bitmap_list_free(bm_list);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp)
|
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp)
|
||||||
{
|
{
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
qcow2_store_persistent_dirty_bitmaps(bs, false, &local_err);
|
if (!qcow2_store_persistent_dirty_bitmaps(bs, false, errp)) {
|
||||||
if (local_err != NULL) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -868,7 +868,7 @@ static void qcow2_attach_aio_context(BlockDriverState *bs,
|
||||||
cache_clean_timer_init(bs, new_context);
|
cache_clean_timer_init(bs, new_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
static bool read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
||||||
uint64_t *l2_cache_size,
|
uint64_t *l2_cache_size,
|
||||||
uint64_t *l2_cache_entry_size,
|
uint64_t *l2_cache_entry_size,
|
||||||
uint64_t *refcount_cache_size, Error **errp)
|
uint64_t *refcount_cache_size, Error **errp)
|
||||||
|
@ -906,16 +906,16 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
||||||
error_setg(errp, QCOW2_OPT_CACHE_SIZE ", " QCOW2_OPT_L2_CACHE_SIZE
|
error_setg(errp, QCOW2_OPT_CACHE_SIZE ", " QCOW2_OPT_L2_CACHE_SIZE
|
||||||
" and " QCOW2_OPT_REFCOUNT_CACHE_SIZE " may not be set "
|
" and " QCOW2_OPT_REFCOUNT_CACHE_SIZE " may not be set "
|
||||||
"at the same time");
|
"at the same time");
|
||||||
return;
|
return false;
|
||||||
} else if (l2_cache_size_set &&
|
} else if (l2_cache_size_set &&
|
||||||
(l2_cache_max_setting > combined_cache_size)) {
|
(l2_cache_max_setting > combined_cache_size)) {
|
||||||
error_setg(errp, QCOW2_OPT_L2_CACHE_SIZE " may not exceed "
|
error_setg(errp, QCOW2_OPT_L2_CACHE_SIZE " may not exceed "
|
||||||
QCOW2_OPT_CACHE_SIZE);
|
QCOW2_OPT_CACHE_SIZE);
|
||||||
return;
|
return false;
|
||||||
} else if (*refcount_cache_size > combined_cache_size) {
|
} else if (*refcount_cache_size > combined_cache_size) {
|
||||||
error_setg(errp, QCOW2_OPT_REFCOUNT_CACHE_SIZE " may not exceed "
|
error_setg(errp, QCOW2_OPT_REFCOUNT_CACHE_SIZE " may not exceed "
|
||||||
QCOW2_OPT_CACHE_SIZE);
|
QCOW2_OPT_CACHE_SIZE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l2_cache_size_set) {
|
if (l2_cache_size_set) {
|
||||||
|
@ -954,8 +954,10 @@ static void read_cache_sizes(BlockDriverState *bs, QemuOpts *opts,
|
||||||
error_setg(errp, "L2 cache entry size must be a power of two "
|
error_setg(errp, "L2 cache entry size must be a power of two "
|
||||||
"between %d and the cluster size (%d)",
|
"between %d and the cluster size (%d)",
|
||||||
1 << MIN_CLUSTER_BITS, s->cluster_size);
|
1 << MIN_CLUSTER_BITS, s->cluster_size);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct Qcow2ReopenState {
|
typedef struct Qcow2ReopenState {
|
||||||
|
@ -982,7 +984,6 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
int i;
|
int i;
|
||||||
const char *encryptfmt;
|
const char *encryptfmt;
|
||||||
QDict *encryptopts = NULL;
|
QDict *encryptopts = NULL;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
qdict_extract_subqdict(options, &encryptopts, "encrypt.");
|
qdict_extract_subqdict(options, &encryptopts, "encrypt.");
|
||||||
|
@ -995,10 +996,8 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get L2 table/refcount block cache size from command line options */
|
/* get L2 table/refcount block cache size from command line options */
|
||||||
read_cache_sizes(bs, opts, &l2_cache_size, &l2_cache_entry_size,
|
if (!read_cache_sizes(bs, opts, &l2_cache_size, &l2_cache_entry_size,
|
||||||
&refcount_cache_size, &local_err);
|
&refcount_cache_size, errp)) {
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -1159,6 +1158,10 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
qdict_put_str(encryptopts, "format", "qcow");
|
qdict_put_str(encryptopts, "format", "qcow");
|
||||||
r->crypto_opts = block_crypto_open_opts_init(encryptopts, errp);
|
r->crypto_opts = block_crypto_open_opts_init(encryptopts, errp);
|
||||||
|
if (!r->crypto_opts) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case QCOW_CRYPT_LUKS:
|
case QCOW_CRYPT_LUKS:
|
||||||
|
@ -1171,14 +1174,15 @@ static int qcow2_update_options_prepare(BlockDriverState *bs,
|
||||||
}
|
}
|
||||||
qdict_put_str(encryptopts, "format", "luks");
|
qdict_put_str(encryptopts, "format", "luks");
|
||||||
r->crypto_opts = block_crypto_open_opts_init(encryptopts, errp);
|
r->crypto_opts = block_crypto_open_opts_init(encryptopts, errp);
|
||||||
|
if (!r->crypto_opts) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error_setg(errp, "Unsupported encryption method %d",
|
error_setg(errp, "Unsupported encryption method %d",
|
||||||
s->crypt_method_header);
|
s->crypt_method_header);
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (s->crypt_method_header != QCOW_CRYPT_NONE && !r->crypto_opts) {
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -1292,11 +1296,11 @@ static int validate_compression_type(BDRVQcow2State *s, Error **errp)
|
||||||
static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
|
static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
|
||||||
int flags, Error **errp)
|
int flags, Error **errp)
|
||||||
{
|
{
|
||||||
|
ERRP_GUARD();
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
unsigned int len, i;
|
unsigned int len, i;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
QCowHeader header;
|
QCowHeader header;
|
||||||
Error *local_err = NULL;
|
|
||||||
uint64_t ext_end;
|
uint64_t ext_end;
|
||||||
uint64_t l1_vm_state_index;
|
uint64_t l1_vm_state_index;
|
||||||
bool update_header = false;
|
bool update_header = false;
|
||||||
|
@ -1611,9 +1615,8 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
|
||||||
/* Open external data file */
|
/* Open external data file */
|
||||||
s->data_file = bdrv_open_child(NULL, options, "data-file", bs,
|
s->data_file = bdrv_open_child(NULL, options, "data-file", bs,
|
||||||
&child_of_bds, BDRV_CHILD_DATA,
|
&child_of_bds, BDRV_CHILD_DATA,
|
||||||
true, &local_err);
|
true, errp);
|
||||||
if (local_err) {
|
if (*errp) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -1785,9 +1788,8 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options,
|
||||||
|
|
||||||
if (!(bdrv_get_flags(bs) & BDRV_O_INACTIVE)) {
|
if (!(bdrv_get_flags(bs) & BDRV_O_INACTIVE)) {
|
||||||
/* It's case 1, 2 or 3.2. Or 3.1 which is BUG in management layer. */
|
/* It's case 1, 2 or 3.2. Or 3.1 which is BUG in management layer. */
|
||||||
bool header_updated = qcow2_load_dirty_bitmaps(bs, &local_err);
|
bool header_updated;
|
||||||
if (local_err != NULL) {
|
if (!qcow2_load_dirty_bitmaps(bs, &header_updated, errp)) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -2719,11 +2721,11 @@ static void qcow2_close(BlockDriverState *bs)
|
||||||
static void coroutine_fn qcow2_co_invalidate_cache(BlockDriverState *bs,
|
static void coroutine_fn qcow2_co_invalidate_cache(BlockDriverState *bs,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
|
ERRP_GUARD();
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
int flags = s->flags;
|
int flags = s->flags;
|
||||||
QCryptoBlock *crypto = NULL;
|
QCryptoBlock *crypto = NULL;
|
||||||
QDict *options;
|
QDict *options;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2741,16 +2743,11 @@ static void coroutine_fn qcow2_co_invalidate_cache(BlockDriverState *bs,
|
||||||
|
|
||||||
flags &= ~BDRV_O_INACTIVE;
|
flags &= ~BDRV_O_INACTIVE;
|
||||||
qemu_co_mutex_lock(&s->lock);
|
qemu_co_mutex_lock(&s->lock);
|
||||||
ret = qcow2_do_open(bs, options, flags, &local_err);
|
ret = qcow2_do_open(bs, options, flags, errp);
|
||||||
qemu_co_mutex_unlock(&s->lock);
|
qemu_co_mutex_unlock(&s->lock);
|
||||||
qobject_unref(options);
|
qobject_unref(options);
|
||||||
if (local_err) {
|
if (ret < 0) {
|
||||||
error_propagate_prepend(errp, local_err,
|
error_prepend(errp, "Could not reopen qcow2 layer: ");
|
||||||
"Could not reopen qcow2 layer: ");
|
|
||||||
bs->drv = NULL;
|
|
||||||
return;
|
|
||||||
} else if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Could not reopen qcow2 layer");
|
|
||||||
bs->drv = NULL;
|
bs->drv = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -5066,12 +5063,10 @@ static ImageInfoSpecific *qcow2_get_specific_info(BlockDriverState *bs,
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
ImageInfoSpecific *spec_info;
|
ImageInfoSpecific *spec_info;
|
||||||
QCryptoBlockInfo *encrypt_info = NULL;
|
QCryptoBlockInfo *encrypt_info = NULL;
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
if (s->crypto != NULL) {
|
if (s->crypto != NULL) {
|
||||||
encrypt_info = qcrypto_block_get_info(s->crypto, &local_err);
|
encrypt_info = qcrypto_block_get_info(s->crypto, errp);
|
||||||
if (local_err) {
|
if (!encrypt_info) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5088,9 +5083,7 @@ static ImageInfoSpecific *qcow2_get_specific_info(BlockDriverState *bs,
|
||||||
};
|
};
|
||||||
} else if (s->qcow_version == 3) {
|
} else if (s->qcow_version == 3) {
|
||||||
Qcow2BitmapInfoList *bitmaps;
|
Qcow2BitmapInfoList *bitmaps;
|
||||||
bitmaps = qcow2_get_bitmap_info_list(bs, &local_err);
|
if (!qcow2_get_bitmap_info_list(bs, &bitmaps, errp)) {
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
qapi_free_ImageInfoSpecific(spec_info);
|
qapi_free_ImageInfoSpecific(spec_info);
|
||||||
qapi_free_QCryptoBlockInfo(encrypt_info);
|
qapi_free_QCryptoBlockInfo(encrypt_info);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -978,12 +978,13 @@ void qcow2_cache_discard(Qcow2Cache *c, void *table);
|
||||||
int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
int qcow2_check_bitmaps_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
|
||||||
void **refcount_table,
|
void **refcount_table,
|
||||||
int64_t *refcount_table_size);
|
int64_t *refcount_table_size);
|
||||||
bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, Error **errp);
|
bool qcow2_load_dirty_bitmaps(BlockDriverState *bs, bool *header_updated,
|
||||||
Qcow2BitmapInfoList *qcow2_get_bitmap_info_list(BlockDriverState *bs,
|
Error **errp);
|
||||||
Error **errp);
|
bool qcow2_get_bitmap_info_list(BlockDriverState *bs,
|
||||||
|
Qcow2BitmapInfoList **info_list, Error **errp);
|
||||||
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp);
|
int qcow2_reopen_bitmaps_rw(BlockDriverState *bs, Error **errp);
|
||||||
int qcow2_truncate_bitmaps_check(BlockDriverState *bs, Error **errp);
|
int qcow2_truncate_bitmaps_check(BlockDriverState *bs, Error **errp);
|
||||||
void qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs,
|
bool qcow2_store_persistent_dirty_bitmaps(BlockDriverState *bs,
|
||||||
bool release_stored, Error **errp);
|
bool release_stored, Error **errp);
|
||||||
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp);
|
int qcow2_reopen_bitmaps_ro(BlockDriverState *bs, Error **errp);
|
||||||
bool qcow2_co_can_store_new_dirty_bitmap(BlockDriverState *bs,
|
bool qcow2_co_can_store_new_dirty_bitmap(BlockDriverState *bs,
|
||||||
|
|
24
block/qed.c
24
block/qed.c
|
@ -393,6 +393,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, 0, &le_header, sizeof(le_header));
|
ret = bdrv_pread(bs->file, 0, &le_header, sizeof(le_header));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
error_setg(errp, "Failed to read QED header");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
qed_header_le_to_cpu(&le_header, &s->header);
|
qed_header_le_to_cpu(&le_header, &s->header);
|
||||||
|
@ -408,25 +409,30 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
if (!qed_is_cluster_size_valid(s->header.cluster_size)) {
|
if (!qed_is_cluster_size_valid(s->header.cluster_size)) {
|
||||||
|
error_setg(errp, "QED cluster size is invalid");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Round down file size to the last cluster */
|
/* Round down file size to the last cluster */
|
||||||
file_size = bdrv_getlength(bs->file->bs);
|
file_size = bdrv_getlength(bs->file->bs);
|
||||||
if (file_size < 0) {
|
if (file_size < 0) {
|
||||||
|
error_setg(errp, "Failed to get file length");
|
||||||
return file_size;
|
return file_size;
|
||||||
}
|
}
|
||||||
s->file_size = qed_start_of_cluster(s, file_size);
|
s->file_size = qed_start_of_cluster(s, file_size);
|
||||||
|
|
||||||
if (!qed_is_table_size_valid(s->header.table_size)) {
|
if (!qed_is_table_size_valid(s->header.table_size)) {
|
||||||
|
error_setg(errp, "QED table size is invalid");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
if (!qed_is_image_size_valid(s->header.image_size,
|
if (!qed_is_image_size_valid(s->header.image_size,
|
||||||
s->header.cluster_size,
|
s->header.cluster_size,
|
||||||
s->header.table_size)) {
|
s->header.table_size)) {
|
||||||
|
error_setg(errp, "QED image size is invalid");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
if (!qed_check_table_offset(s, s->header.l1_table_offset)) {
|
if (!qed_check_table_offset(s, s->header.l1_table_offset)) {
|
||||||
|
error_setg(errp, "QED table offset is invalid");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +444,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
|
|
||||||
/* Header size calculation must not overflow uint32_t */
|
/* Header size calculation must not overflow uint32_t */
|
||||||
if (s->header.header_size > UINT32_MAX / s->header.cluster_size) {
|
if (s->header.header_size > UINT32_MAX / s->header.cluster_size) {
|
||||||
|
error_setg(errp, "QED header size is too large");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,6 +452,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
if ((uint64_t)s->header.backing_filename_offset +
|
if ((uint64_t)s->header.backing_filename_offset +
|
||||||
s->header.backing_filename_size >
|
s->header.backing_filename_size >
|
||||||
s->header.cluster_size * s->header.header_size) {
|
s->header.cluster_size * s->header.header_size) {
|
||||||
|
error_setg(errp, "QED backing filename offset is invalid");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,6 +461,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
bs->auto_backing_file,
|
bs->auto_backing_file,
|
||||||
sizeof(bs->auto_backing_file));
|
sizeof(bs->auto_backing_file));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
error_setg(errp, "Failed to read backing filename");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
pstrcpy(bs->backing_file, sizeof(bs->backing_file),
|
pstrcpy(bs->backing_file, sizeof(bs->backing_file),
|
||||||
|
@ -475,6 +484,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
|
|
||||||
ret = qed_write_header_sync(s);
|
ret = qed_write_header_sync(s);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
error_setg(errp, "Failed to update header");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,6 +497,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
|
|
||||||
ret = qed_read_l1_table_sync(s);
|
ret = qed_read_l1_table_sync(s);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
error_setg(errp, "Failed to read L1 table");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,6 +514,7 @@ static int coroutine_fn bdrv_qed_do_open(BlockDriverState *bs, QDict *options,
|
||||||
|
|
||||||
ret = qed_check(s, &result, true);
|
ret = qed_check(s, &result, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
error_setg(errp, "Image corrupted");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1537,22 +1549,16 @@ static void coroutine_fn bdrv_qed_co_invalidate_cache(BlockDriverState *bs,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
BDRVQEDState *s = bs->opaque;
|
BDRVQEDState *s = bs->opaque;
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
bdrv_qed_close(bs);
|
bdrv_qed_close(bs);
|
||||||
|
|
||||||
bdrv_qed_init_state(bs);
|
bdrv_qed_init_state(bs);
|
||||||
qemu_co_mutex_lock(&s->table_lock);
|
qemu_co_mutex_lock(&s->table_lock);
|
||||||
ret = bdrv_qed_do_open(bs, NULL, bs->open_flags, &local_err);
|
ret = bdrv_qed_do_open(bs, NULL, bs->open_flags, errp);
|
||||||
qemu_co_mutex_unlock(&s->table_lock);
|
qemu_co_mutex_unlock(&s->table_lock);
|
||||||
if (local_err) {
|
if (ret < 0) {
|
||||||
error_propagate_prepend(errp, local_err,
|
error_prepend(errp, "Could not reopen qed layer: ");
|
||||||
"Could not reopen qed layer: ");
|
|
||||||
return;
|
|
||||||
} else if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Could not reopen qed layer");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -929,7 +929,6 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
BDRVQuorumState *s = bs->opaque;
|
BDRVQuorumState *s = bs->opaque;
|
||||||
Error *local_err = NULL;
|
|
||||||
QemuOpts *opts = NULL;
|
QemuOpts *opts = NULL;
|
||||||
const char *pattern_str;
|
const char *pattern_str;
|
||||||
bool *opened;
|
bool *opened;
|
||||||
|
@ -1007,9 +1006,8 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
|
|
||||||
s->children[i] = bdrv_open_child(NULL, options, indexstr, bs,
|
s->children[i] = bdrv_open_child(NULL, options, indexstr, bs,
|
||||||
&child_of_bds, BDRV_CHILD_DATA, false,
|
&child_of_bds, BDRV_CHILD_DATA, false,
|
||||||
&local_err);
|
errp);
|
||||||
if (local_err) {
|
if (!s->children[i]) {
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto close_exit;
|
goto close_exit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1824,9 +1824,7 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
|
||||||
aio_context_acquire(aio_context);
|
aio_context_acquire(aio_context);
|
||||||
|
|
||||||
if (set_backing_hd) {
|
if (set_backing_hd) {
|
||||||
bdrv_set_backing_hd(target_bs, source, &local_err);
|
if (bdrv_set_backing_hd(target_bs, source, errp) < 0) {
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto unref;
|
goto unref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
blockjob.c
18
blockjob.c
|
@ -258,18 +258,18 @@ static bool job_timer_pending(Job *job)
|
||||||
return timer_pending(&job->sleep_timer);
|
return timer_pending(&job->sleep_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
bool block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||||
{
|
{
|
||||||
const BlockJobDriver *drv = block_job_driver(job);
|
const BlockJobDriver *drv = block_job_driver(job);
|
||||||
int64_t old_speed = job->speed;
|
int64_t old_speed = job->speed;
|
||||||
|
|
||||||
if (job_apply_verb(&job->job, JOB_VERB_SET_SPEED, errp)) {
|
if (job_apply_verb(&job->job, JOB_VERB_SET_SPEED, errp) < 0) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (speed < 0) {
|
if (speed < 0) {
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "speed",
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "speed",
|
||||||
"a non-negative value");
|
"a non-negative value");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ratelimit_set_speed(&job->limit, speed, BLOCK_JOB_SLICE_TIME);
|
ratelimit_set_speed(&job->limit, speed, BLOCK_JOB_SLICE_TIME);
|
||||||
|
@ -281,11 +281,13 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speed && speed <= old_speed) {
|
if (speed && speed <= old_speed) {
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* kick only if a timer is pending */
|
/* kick only if a timer is pending */
|
||||||
job_enter_cond(&job->job, job_timer_pending);
|
job_enter_cond(&job->job, job_timer_pending);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
|
int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n)
|
||||||
|
@ -462,12 +464,8 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
|
||||||
|
|
||||||
/* Only set speed when necessary to avoid NotSupported error */
|
/* Only set speed when necessary to avoid NotSupported error */
|
||||||
if (speed != 0) {
|
if (speed != 0) {
|
||||||
Error *local_err = NULL;
|
if (!block_job_set_speed(job, speed, errp)) {
|
||||||
|
|
||||||
block_job_set_speed(job, speed, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
job_early_fail(&job->job);
|
job_early_fail(&job->job);
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,14 @@ Using ``-M kernel-irqchip=off`` with x86 machine types that include a local
|
||||||
APIC is deprecated. The ``split`` setting is supported, as is using
|
APIC is deprecated. The ``split`` setting is supported, as is using
|
||||||
``-M kernel-irqchip=off`` with the ISA PC machine type.
|
``-M kernel-irqchip=off`` with the ISA PC machine type.
|
||||||
|
|
||||||
|
hexadecimal sizes with scaling multipliers (since 6.0)
|
||||||
|
''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
Input parameters that take a size value should only use a size suffix
|
||||||
|
(such as 'k' or 'M') when the base is written in decimal, and not when
|
||||||
|
the value is hexadecimal. That is, '0x20M' is deprecated, and should
|
||||||
|
be written either as '32M' or as '0x2000000'.
|
||||||
|
|
||||||
QEMU Machine Protocol (QMP) commands
|
QEMU Machine Protocol (QMP) commands
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ bool block_job_has_bdrv(BlockJob *job, BlockDriverState *bs);
|
||||||
* Set a rate-limiting parameter for the job; the actual meaning may
|
* Set a rate-limiting parameter for the job; the actual meaning may
|
||||||
* vary depending on the job type.
|
* vary depending on the job type.
|
||||||
*/
|
*/
|
||||||
void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
|
bool block_job_set_speed(BlockJob *job, int64_t speed, Error **errp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* block_job_query:
|
* block_job_query:
|
||||||
|
|
|
@ -2087,8 +2087,8 @@ static int blockstatus_to_extents(BlockDriverState *bs, uint64_t offset,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
flags = (ret & BDRV_BLOCK_ALLOCATED ? 0 : NBD_STATE_HOLE) |
|
flags = (ret & BDRV_BLOCK_DATA ? 0 : NBD_STATE_HOLE) |
|
||||||
(ret & BDRV_BLOCK_ZERO ? NBD_STATE_ZERO : 0);
|
(ret & BDRV_BLOCK_ZERO ? NBD_STATE_ZERO : 0);
|
||||||
|
|
||||||
if (nbd_extent_array_add(ea, num, flags) < 0) {
|
if (nbd_extent_array_add(ea, num, flags) < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -92,16 +92,22 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off comp
|
||||||
== 3. Invalid sizes ==
|
== 3. Invalid sizes ==
|
||||||
|
|
||||||
qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1024
|
qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1024
|
||||||
qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
|
qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
|
||||||
|
qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
|
||||||
|
|
||||||
qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2
|
qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2
|
||||||
qemu-img: TEST_DIR/t.qcow2: Value '-1024' is out of range for parameter 'size'
|
qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64
|
||||||
|
Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta-
|
||||||
|
and exabytes, respectively.
|
||||||
|
|
||||||
qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k
|
qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k
|
||||||
qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
|
qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
|
||||||
|
qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
|
||||||
|
|
||||||
qemu-img create -f qcow2 -o size=-1k TEST_DIR/t.qcow2
|
qemu-img create -f qcow2 -o size=-1k TEST_DIR/t.qcow2
|
||||||
qemu-img: TEST_DIR/t.qcow2: Value '-1k' is out of range for parameter 'size'
|
qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64
|
||||||
|
Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta-
|
||||||
|
and exabytes, respectively.
|
||||||
|
|
||||||
qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- 1kilobyte
|
qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- 1kilobyte
|
||||||
qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
|
qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
|
||||||
|
|
|
@ -13,7 +13,8 @@ qemu-img: Invalid option list: ,
|
||||||
qemu-img: Invalid parameter 'snapshot.foo'
|
qemu-img: Invalid parameter 'snapshot.foo'
|
||||||
qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
|
qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
|
||||||
qemu-img: --output must be used with human or json as argument.
|
qemu-img: --output must be used with human or json as argument.
|
||||||
qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
|
qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
|
||||||
|
qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
|
||||||
qemu-img: Unknown file format 'foo'
|
qemu-img: Unknown file format 'foo'
|
||||||
|
|
||||||
== Size calculation for a new file (human) ==
|
== Size calculation for a new file (human) ==
|
||||||
|
|
|
@ -13,7 +13,8 @@ qemu-img: Invalid option list: ,
|
||||||
qemu-img: Invalid parameter 'snapshot.foo'
|
qemu-img: Invalid parameter 'snapshot.foo'
|
||||||
qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
|
qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
|
||||||
qemu-img: --output must be used with human or json as argument.
|
qemu-img: --output must be used with human or json as argument.
|
||||||
qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
|
qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
|
||||||
|
qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
|
||||||
qemu-img: Unknown file format 'foo'
|
qemu-img: Unknown file format 'foo'
|
||||||
|
|
||||||
== Size calculation for a new file (human) ==
|
== Size calculation for a new file (human) ==
|
||||||
|
|
|
@ -5,7 +5,7 @@ QA output created by 241
|
||||||
size: 1024
|
size: 1024
|
||||||
min block: 1
|
min block: 1
|
||||||
[{ "start": 0, "length": 1000, "depth": 0, "zero": false, "data": true, "offset": OFFSET},
|
[{ "start": 0, "length": 1000, "depth": 0, "zero": false, "data": true, "offset": OFFSET},
|
||||||
{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": true, "offset": OFFSET}]
|
{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": false, "offset": OFFSET}]
|
||||||
1 KiB (0x400) bytes allocated at offset 0 bytes (0x0)
|
1 KiB (0x400) bytes allocated at offset 0 bytes (0x0)
|
||||||
|
|
||||||
=== Exporting unaligned raw image, forced server sector alignment ===
|
=== Exporting unaligned raw image, forced server sector alignment ===
|
||||||
|
@ -23,6 +23,6 @@ WARNING: Image format was not specified for 'TEST_DIR/t.raw' and probing guessed
|
||||||
size: 1024
|
size: 1024
|
||||||
min block: 1
|
min block: 1
|
||||||
[{ "start": 0, "length": 1000, "depth": 0, "zero": false, "data": true, "offset": OFFSET},
|
[{ "start": 0, "length": 1000, "depth": 0, "zero": false, "data": true, "offset": OFFSET},
|
||||||
{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": true, "offset": OFFSET}]
|
{ "start": 1000, "length": 24, "depth": 0, "zero": true, "data": false, "offset": OFFSET}]
|
||||||
1 KiB (0x400) bytes allocated at offset 0 bytes (0x0)
|
1 KiB (0x400) bytes allocated at offset 0 bytes (0x0)
|
||||||
*** done
|
*** done
|
||||||
|
|
|
@ -1960,18 +1960,24 @@ static void test_qemu_strtosz_simple(void)
|
||||||
g_assert_cmpint(res, ==, 0);
|
g_assert_cmpint(res, ==, 0);
|
||||||
g_assert(endptr == str + 1);
|
g_assert(endptr == str + 1);
|
||||||
|
|
||||||
str = "12345";
|
/* Leading 0 gives decimal results, not octal */
|
||||||
|
str = "08";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 8);
|
||||||
|
g_assert(endptr == str + 2);
|
||||||
|
|
||||||
|
/* Leading space is ignored */
|
||||||
|
str = " 12345";
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
g_assert_cmpint(res, ==, 12345);
|
g_assert_cmpint(res, ==, 12345);
|
||||||
g_assert(endptr == str + 5);
|
g_assert(endptr == str + 6);
|
||||||
|
|
||||||
err = qemu_strtosz(str, NULL, &res);
|
err = qemu_strtosz(str, NULL, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
g_assert_cmpint(res, ==, 12345);
|
g_assert_cmpint(res, ==, 12345);
|
||||||
|
|
||||||
/* Note: precision is 53 bits since we're parsing with strtod() */
|
|
||||||
|
|
||||||
str = "9007199254740991"; /* 2^53-1 */
|
str = "9007199254740991"; /* 2^53-1 */
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
@ -1987,7 +1993,7 @@ static void test_qemu_strtosz_simple(void)
|
||||||
str = "9007199254740993"; /* 2^53+1 */
|
str = "9007199254740993"; /* 2^53+1 */
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
g_assert_cmpint(res, ==, 0x20000000000000); /* rounded to 53 bits */
|
g_assert_cmpint(res, ==, 0x20000000000001);
|
||||||
g_assert(endptr == str + 16);
|
g_assert(endptr == str + 16);
|
||||||
|
|
||||||
str = "18446744073709549568"; /* 0xfffffffffffff800 (53 msbs set) */
|
str = "18446744073709549568"; /* 0xfffffffffffff800 (53 msbs set) */
|
||||||
|
@ -1999,11 +2005,40 @@ static void test_qemu_strtosz_simple(void)
|
||||||
str = "18446744073709550591"; /* 0xfffffffffffffbff */
|
str = "18446744073709550591"; /* 0xfffffffffffffbff */
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
g_assert_cmpint(res, ==, 0xfffffffffffff800); /* rounded to 53 bits */
|
g_assert_cmpint(res, ==, 0xfffffffffffffbff);
|
||||||
g_assert(endptr == str + 20);
|
g_assert(endptr == str + 20);
|
||||||
|
|
||||||
/* 0x7ffffffffffffe00..0x7fffffffffffffff get rounded to
|
str = "18446744073709551615"; /* 0xffffffffffffffff */
|
||||||
* 0x8000000000000000, thus -ERANGE; see test_qemu_strtosz_erange() */
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 0xffffffffffffffff);
|
||||||
|
g_assert(endptr == str + 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_qemu_strtosz_hex(void)
|
||||||
|
{
|
||||||
|
const char *str;
|
||||||
|
const char *endptr;
|
||||||
|
int err;
|
||||||
|
uint64_t res = 0xbaadf00d;
|
||||||
|
|
||||||
|
str = "0x0";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 0);
|
||||||
|
g_assert(endptr == str + 3);
|
||||||
|
|
||||||
|
str = "0xab";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 171);
|
||||||
|
g_assert(endptr == str + 4);
|
||||||
|
|
||||||
|
str = "0xae";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 174);
|
||||||
|
g_assert(endptr == str + 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_qemu_strtosz_units(void)
|
static void test_qemu_strtosz_units(void)
|
||||||
|
@ -2064,14 +2099,36 @@ static void test_qemu_strtosz_units(void)
|
||||||
|
|
||||||
static void test_qemu_strtosz_float(void)
|
static void test_qemu_strtosz_float(void)
|
||||||
{
|
{
|
||||||
const char *str = "12.345M";
|
const char *str;
|
||||||
int err;
|
int err;
|
||||||
const char *endptr;
|
const char *endptr;
|
||||||
uint64_t res = 0xbaadf00d;
|
uint64_t res = 0xbaadf00d;
|
||||||
|
|
||||||
|
str = "0.5E";
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
g_assert_cmpint(res, ==, 12.345 * MiB);
|
g_assert_cmpint(res, ==, EiB / 2);
|
||||||
|
g_assert(endptr == str + 4);
|
||||||
|
|
||||||
|
/* For convenience, a fraction of 0 is tolerated even on bytes */
|
||||||
|
str = "1.0B";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 1);
|
||||||
|
g_assert(endptr == str + 4);
|
||||||
|
|
||||||
|
/* An empty fraction is tolerated */
|
||||||
|
str = "1.k";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 1024);
|
||||||
|
g_assert(endptr == str + 3);
|
||||||
|
|
||||||
|
/* For convenience, we permit values that are not byte-exact */
|
||||||
|
str = "12.345M";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, (uint64_t) (12.345 * MiB));
|
||||||
g_assert(endptr == str + 7);
|
g_assert(endptr == str + 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2106,6 +2163,45 @@ static void test_qemu_strtosz_invalid(void)
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, -EINVAL);
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
g_assert(endptr == str);
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
/* Fractional values require scale larger than bytes */
|
||||||
|
str = "1.1B";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
str = "1.1";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
/* No floating point exponents */
|
||||||
|
str = "1.5e1k";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
str = "1.5E+0k";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
/* No hex fractions */
|
||||||
|
str = "0x1.8k";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
/* No negative values */
|
||||||
|
str = "-0";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
|
|
||||||
|
str = "-1";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
g_assert(endptr == str);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_qemu_strtosz_trailing(void)
|
static void test_qemu_strtosz_trailing(void)
|
||||||
|
@ -2131,6 +2227,30 @@ static void test_qemu_strtosz_trailing(void)
|
||||||
|
|
||||||
err = qemu_strtosz(str, NULL, &res);
|
err = qemu_strtosz(str, NULL, &res);
|
||||||
g_assert_cmpint(err, ==, -EINVAL);
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
|
||||||
|
str = "0x";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(res, ==, 0);
|
||||||
|
g_assert(endptr == str + 1);
|
||||||
|
|
||||||
|
err = qemu_strtosz(str, NULL, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
|
||||||
|
str = "0.NaN";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert(endptr == str + 2);
|
||||||
|
|
||||||
|
err = qemu_strtosz(str, NULL, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
|
|
||||||
|
str = "123-45";
|
||||||
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(res, ==, 123);
|
||||||
|
g_assert(endptr == str + 3);
|
||||||
|
|
||||||
|
err = qemu_strtosz(str, NULL, &res);
|
||||||
|
g_assert_cmpint(err, ==, -EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_qemu_strtosz_erange(void)
|
static void test_qemu_strtosz_erange(void)
|
||||||
|
@ -2140,22 +2260,7 @@ static void test_qemu_strtosz_erange(void)
|
||||||
int err;
|
int err;
|
||||||
uint64_t res = 0xbaadf00d;
|
uint64_t res = 0xbaadf00d;
|
||||||
|
|
||||||
str = "-1";
|
str = "18446744073709551616"; /* 2^64; see strtosz_simple for 2^64-1 */
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
|
||||||
g_assert_cmpint(err, ==, -ERANGE);
|
|
||||||
g_assert(endptr == str + 2);
|
|
||||||
|
|
||||||
str = "18446744073709550592"; /* 0xfffffffffffffc00 */
|
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
|
||||||
g_assert_cmpint(err, ==, -ERANGE);
|
|
||||||
g_assert(endptr == str + 20);
|
|
||||||
|
|
||||||
str = "18446744073709551615"; /* 2^64-1 */
|
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
|
||||||
g_assert_cmpint(err, ==, -ERANGE);
|
|
||||||
g_assert(endptr == str + 20);
|
|
||||||
|
|
||||||
str = "18446744073709551616"; /* 2^64 */
|
|
||||||
err = qemu_strtosz(str, &endptr, &res);
|
err = qemu_strtosz(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, -ERANGE);
|
g_assert_cmpint(err, ==, -ERANGE);
|
||||||
g_assert(endptr == str + 20);
|
g_assert(endptr == str + 20);
|
||||||
|
@ -2168,15 +2273,22 @@ static void test_qemu_strtosz_erange(void)
|
||||||
|
|
||||||
static void test_qemu_strtosz_metric(void)
|
static void test_qemu_strtosz_metric(void)
|
||||||
{
|
{
|
||||||
const char *str = "12345k";
|
const char *str;
|
||||||
int err;
|
int err;
|
||||||
const char *endptr;
|
const char *endptr;
|
||||||
uint64_t res = 0xbaadf00d;
|
uint64_t res = 0xbaadf00d;
|
||||||
|
|
||||||
|
str = "12345k";
|
||||||
err = qemu_strtosz_metric(str, &endptr, &res);
|
err = qemu_strtosz_metric(str, &endptr, &res);
|
||||||
g_assert_cmpint(err, ==, 0);
|
g_assert_cmpint(err, ==, 0);
|
||||||
g_assert_cmpint(res, ==, 12345000);
|
g_assert_cmpint(res, ==, 12345000);
|
||||||
g_assert(endptr == str + 6);
|
g_assert(endptr == str + 6);
|
||||||
|
|
||||||
|
str = "12.345M";
|
||||||
|
err = qemu_strtosz_metric(str, &endptr, &res);
|
||||||
|
g_assert_cmpint(err, ==, 0);
|
||||||
|
g_assert_cmpint(res, ==, 12345000);
|
||||||
|
g_assert(endptr == str + 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
@ -2443,6 +2555,8 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
g_test_add_func("/cutils/strtosz/simple",
|
g_test_add_func("/cutils/strtosz/simple",
|
||||||
test_qemu_strtosz_simple);
|
test_qemu_strtosz_simple);
|
||||||
|
g_test_add_func("/cutils/strtosz/hex",
|
||||||
|
test_qemu_strtosz_hex);
|
||||||
g_test_add_func("/cutils/strtosz/units",
|
g_test_add_func("/cutils/strtosz/units",
|
||||||
test_qemu_strtosz_units);
|
test_qemu_strtosz_units);
|
||||||
g_test_add_func("/cutils/strtosz/float",
|
g_test_add_func("/cutils/strtosz/float",
|
||||||
|
|
|
@ -445,9 +445,9 @@ static void test_keyval_visit_size(void)
|
||||||
visit_end_struct(v, NULL);
|
visit_end_struct(v, NULL);
|
||||||
visit_free(v);
|
visit_free(v);
|
||||||
|
|
||||||
/* Note: precision is 53 bits since we're parsing with strtod() */
|
/* Note: full 64 bits of precision */
|
||||||
|
|
||||||
/* Around limit of precision: 2^53-1, 2^53, 2^53+1 */
|
/* Around double limit of precision: 2^53-1, 2^53, 2^53+1 */
|
||||||
qdict = keyval_parse("sz1=9007199254740991,"
|
qdict = keyval_parse("sz1=9007199254740991,"
|
||||||
"sz2=9007199254740992,"
|
"sz2=9007199254740992,"
|
||||||
"sz3=9007199254740993",
|
"sz3=9007199254740993",
|
||||||
|
@ -460,22 +460,25 @@ static void test_keyval_visit_size(void)
|
||||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||||
g_assert_cmphex(sz, ==, 0x20000000000000);
|
g_assert_cmphex(sz, ==, 0x20000000000000);
|
||||||
visit_type_size(v, "sz3", &sz, &error_abort);
|
visit_type_size(v, "sz3", &sz, &error_abort);
|
||||||
g_assert_cmphex(sz, ==, 0x20000000000000);
|
g_assert_cmphex(sz, ==, 0x20000000000001);
|
||||||
visit_check_struct(v, &error_abort);
|
visit_check_struct(v, &error_abort);
|
||||||
visit_end_struct(v, NULL);
|
visit_end_struct(v, NULL);
|
||||||
visit_free(v);
|
visit_free(v);
|
||||||
|
|
||||||
/* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */
|
/* Close to signed integer limit 2^63 */
|
||||||
qdict = keyval_parse("sz1=9223372036854774784," /* 7ffffffffffffc00 */
|
qdict = keyval_parse("sz1=9223372036854775807," /* 7fffffffffffffff */
|
||||||
"sz2=9223372036854775295", /* 7ffffffffffffdff */
|
"sz2=9223372036854775808," /* 8000000000000000 */
|
||||||
|
"sz3=9223372036854775809", /* 8000000000000001 */
|
||||||
NULL, NULL, &error_abort);
|
NULL, NULL, &error_abort);
|
||||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||||
qobject_unref(qdict);
|
qobject_unref(qdict);
|
||||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||||
g_assert_cmphex(sz, ==, 0x7ffffffffffffc00);
|
g_assert_cmphex(sz, ==, 0x7fffffffffffffff);
|
||||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||||
g_assert_cmphex(sz, ==, 0x7ffffffffffffc00);
|
g_assert_cmphex(sz, ==, 0x8000000000000000);
|
||||||
|
visit_type_size(v, "sz3", &sz, &error_abort);
|
||||||
|
g_assert_cmphex(sz, ==, 0x8000000000000001);
|
||||||
visit_check_struct(v, &error_abort);
|
visit_check_struct(v, &error_abort);
|
||||||
visit_end_struct(v, NULL);
|
visit_end_struct(v, NULL);
|
||||||
visit_free(v);
|
visit_free(v);
|
||||||
|
@ -490,14 +493,26 @@ static void test_keyval_visit_size(void)
|
||||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||||
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
||||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||||
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
g_assert_cmphex(sz, ==, 0xfffffffffffffbff);
|
||||||
|
visit_check_struct(v, &error_abort);
|
||||||
|
visit_end_struct(v, NULL);
|
||||||
|
visit_free(v);
|
||||||
|
|
||||||
|
/* Actual limit 2^64-1*/
|
||||||
|
qdict = keyval_parse("sz1=18446744073709551615", /* ffffffffffffffff */
|
||||||
|
NULL, NULL, &error_abort);
|
||||||
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||||
|
qobject_unref(qdict);
|
||||||
|
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||||
|
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||||
|
g_assert_cmphex(sz, ==, 0xffffffffffffffff);
|
||||||
visit_check_struct(v, &error_abort);
|
visit_check_struct(v, &error_abort);
|
||||||
visit_end_struct(v, NULL);
|
visit_end_struct(v, NULL);
|
||||||
visit_free(v);
|
visit_free(v);
|
||||||
|
|
||||||
/* Beyond limits */
|
/* Beyond limits */
|
||||||
qdict = keyval_parse("sz1=-1,"
|
qdict = keyval_parse("sz1=-1,"
|
||||||
"sz2=18446744073709550592", /* fffffffffffffc00 */
|
"sz2=18446744073709551616", /* 2^64 */
|
||||||
NULL, NULL, &error_abort);
|
NULL, NULL, &error_abort);
|
||||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||||
qobject_unref(qdict);
|
qobject_unref(qdict);
|
||||||
|
|
|
@ -654,9 +654,9 @@ static void test_opts_parse_size(void)
|
||||||
g_assert_cmpuint(opts_count(opts), ==, 1);
|
g_assert_cmpuint(opts_count(opts), ==, 1);
|
||||||
g_assert_cmpuint(qemu_opt_get_size(opts, "size1", 1), ==, 0);
|
g_assert_cmpuint(qemu_opt_get_size(opts, "size1", 1), ==, 0);
|
||||||
|
|
||||||
/* Note: precision is 53 bits since we're parsing with strtod() */
|
/* Note: full 64 bits of precision */
|
||||||
|
|
||||||
/* Around limit of precision: 2^53-1, 2^53, 2^54 */
|
/* Around double limit of precision: 2^53-1, 2^53, 2^53+1 */
|
||||||
opts = qemu_opts_parse(&opts_list_02,
|
opts = qemu_opts_parse(&opts_list_02,
|
||||||
"size1=9007199254740991,"
|
"size1=9007199254740991,"
|
||||||
"size2=9007199254740992,"
|
"size2=9007199254740992,"
|
||||||
|
@ -668,18 +668,21 @@ static void test_opts_parse_size(void)
|
||||||
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
|
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
|
||||||
==, 0x20000000000000);
|
==, 0x20000000000000);
|
||||||
g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1),
|
g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1),
|
||||||
==, 0x20000000000000);
|
==, 0x20000000000001);
|
||||||
|
|
||||||
/* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */
|
/* Close to signed int limit: 2^63-1, 2^63, 2^63+1 */
|
||||||
opts = qemu_opts_parse(&opts_list_02,
|
opts = qemu_opts_parse(&opts_list_02,
|
||||||
"size1=9223372036854774784," /* 7ffffffffffffc00 */
|
"size1=9223372036854775807," /* 7fffffffffffffff */
|
||||||
"size2=9223372036854775295", /* 7ffffffffffffdff */
|
"size2=9223372036854775808," /* 8000000000000000 */
|
||||||
|
"size3=9223372036854775809", /* 8000000000000001 */
|
||||||
false, &error_abort);
|
false, &error_abort);
|
||||||
g_assert_cmpuint(opts_count(opts), ==, 2);
|
g_assert_cmpuint(opts_count(opts), ==, 3);
|
||||||
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
|
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
|
||||||
==, 0x7ffffffffffffc00);
|
==, 0x7fffffffffffffff);
|
||||||
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
|
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
|
||||||
==, 0x7ffffffffffffc00);
|
==, 0x8000000000000000);
|
||||||
|
g_assert_cmphex(qemu_opt_get_size(opts, "size3", 1),
|
||||||
|
==, 0x8000000000000001);
|
||||||
|
|
||||||
/* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
|
/* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
|
||||||
opts = qemu_opts_parse(&opts_list_02,
|
opts = qemu_opts_parse(&opts_list_02,
|
||||||
|
@ -690,14 +693,22 @@ static void test_opts_parse_size(void)
|
||||||
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
|
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
|
||||||
==, 0xfffffffffffff800);
|
==, 0xfffffffffffff800);
|
||||||
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
|
g_assert_cmphex(qemu_opt_get_size(opts, "size2", 1),
|
||||||
==, 0xfffffffffffff800);
|
==, 0xfffffffffffffbff);
|
||||||
|
|
||||||
|
/* Actual limit, 2^64-1 */
|
||||||
|
opts = qemu_opts_parse(&opts_list_02,
|
||||||
|
"size1=18446744073709551615", /* ffffffffffffffff */
|
||||||
|
false, &error_abort);
|
||||||
|
g_assert_cmpuint(opts_count(opts), ==, 1);
|
||||||
|
g_assert_cmphex(qemu_opt_get_size(opts, "size1", 1),
|
||||||
|
==, 0xffffffffffffffff);
|
||||||
|
|
||||||
/* Beyond limits */
|
/* Beyond limits */
|
||||||
opts = qemu_opts_parse(&opts_list_02, "size1=-1", false, &err);
|
opts = qemu_opts_parse(&opts_list_02, "size1=-1", false, &err);
|
||||||
error_free_or_abort(&err);
|
error_free_or_abort(&err);
|
||||||
g_assert(!opts);
|
g_assert(!opts);
|
||||||
opts = qemu_opts_parse(&opts_list_02,
|
opts = qemu_opts_parse(&opts_list_02,
|
||||||
"size1=18446744073709550592", /* fffffffffffffc00 */
|
"size1=18446744073709551616", /* 2^64 */
|
||||||
false, &err);
|
false, &err);
|
||||||
error_free_or_abort(&err);
|
error_free_or_abort(&err);
|
||||||
g_assert(!opts);
|
g_assert(!opts);
|
||||||
|
|
114
util/cutils.c
114
util/cutils.c
|
@ -241,52 +241,108 @@ static int64_t suffix_mul(char suffix, int64_t unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert string to bytes, allowing either B/b for bytes, K/k for KB,
|
* Convert size string to bytes.
|
||||||
* M/m for MB, G/g for GB or T/t for TB. End pointer will be returned
|
*
|
||||||
* in *end, if not NULL. Return -ERANGE on overflow, and -EINVAL on
|
* The size parsing supports the following syntaxes
|
||||||
* other error.
|
* - 12345 - decimal, scale determined by @default_suffix and @unit
|
||||||
|
* - 12345{bBkKmMgGtTpPeE} - decimal, scale determined by suffix and @unit
|
||||||
|
* - 12345.678{kKmMgGtTpPeE} - decimal, scale determined by suffix, and
|
||||||
|
* fractional portion is truncated to byte
|
||||||
|
* - 0x7fEE - hexadecimal, unit determined by @default_suffix
|
||||||
|
*
|
||||||
|
* The following cause a deprecation warning, and may be removed in the future
|
||||||
|
* - 0xabc{kKmMgGtTpP} - hex with scaling suffix
|
||||||
|
*
|
||||||
|
* The following are intentionally not supported
|
||||||
|
* - octal, such as 08
|
||||||
|
* - fractional hex, such as 0x1.8
|
||||||
|
* - floating point exponents, such as 1e3
|
||||||
|
*
|
||||||
|
* The end pointer will be returned in *end, if not NULL. If there is
|
||||||
|
* no fraction, the input can be decimal or hexadecimal; if there is a
|
||||||
|
* fraction, then the input must be decimal and there must be a suffix
|
||||||
|
* (possibly by @default_suffix) larger than Byte, and the fractional
|
||||||
|
* portion may suffer from precision loss or rounding. The input must
|
||||||
|
* be positive.
|
||||||
|
*
|
||||||
|
* Return -ERANGE on overflow (with *@end advanced), and -EINVAL on
|
||||||
|
* other error (with *@end left unchanged).
|
||||||
*/
|
*/
|
||||||
static int do_strtosz(const char *nptr, const char **end,
|
static int do_strtosz(const char *nptr, const char **end,
|
||||||
const char default_suffix, int64_t unit,
|
const char default_suffix, int64_t unit,
|
||||||
uint64_t *result)
|
uint64_t *result)
|
||||||
{
|
{
|
||||||
int retval;
|
int retval;
|
||||||
const char *endptr;
|
const char *endptr, *f;
|
||||||
unsigned char c;
|
unsigned char c;
|
||||||
int mul_required = 0;
|
bool mul_required = false, hex = false;
|
||||||
double val, mul, integral, fraction;
|
uint64_t val;
|
||||||
|
int64_t mul;
|
||||||
|
double fraction = 0.0;
|
||||||
|
|
||||||
retval = qemu_strtod_finite(nptr, &endptr, &val);
|
/* Parse integral portion as decimal. */
|
||||||
|
retval = qemu_strtou64(nptr, &endptr, 10, &val);
|
||||||
if (retval) {
|
if (retval) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
fraction = modf(val, &integral);
|
if (memchr(nptr, '-', endptr - nptr) != NULL) {
|
||||||
if (fraction != 0) {
|
endptr = nptr;
|
||||||
mul_required = 1;
|
|
||||||
}
|
|
||||||
c = *endptr;
|
|
||||||
mul = suffix_mul(c, unit);
|
|
||||||
if (mul >= 0) {
|
|
||||||
endptr++;
|
|
||||||
} else {
|
|
||||||
mul = suffix_mul(default_suffix, unit);
|
|
||||||
assert(mul >= 0);
|
|
||||||
}
|
|
||||||
if (mul == 1 && mul_required) {
|
|
||||||
retval = -EINVAL;
|
retval = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
/*
|
if (val == 0 && (*endptr == 'x' || *endptr == 'X')) {
|
||||||
* Values near UINT64_MAX overflow to 2**64 when converting to double
|
/* Input looks like hex, reparse, and insist on no fraction. */
|
||||||
* precision. Compare against the maximum representable double precision
|
retval = qemu_strtou64(nptr, &endptr, 16, &val);
|
||||||
* value below 2**64, computed as "the next value after 2**64 (0x1p64) in
|
if (retval) {
|
||||||
* the direction of 0".
|
goto out;
|
||||||
*/
|
}
|
||||||
if ((val * mul > nextafter(0x1p64, 0)) || val < 0) {
|
if (*endptr == '.') {
|
||||||
|
endptr = nptr;
|
||||||
|
retval = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
hex = true;
|
||||||
|
} else if (*endptr == '.') {
|
||||||
|
/*
|
||||||
|
* Input looks like a fraction. Make sure even 1.k works
|
||||||
|
* without fractional digits. If we see an exponent, treat
|
||||||
|
* the entire input as invalid instead.
|
||||||
|
*/
|
||||||
|
f = endptr;
|
||||||
|
retval = qemu_strtod_finite(f, &endptr, &fraction);
|
||||||
|
if (retval) {
|
||||||
|
fraction = 0.0;
|
||||||
|
endptr++;
|
||||||
|
} else if (memchr(f, 'e', endptr - f) || memchr(f, 'E', endptr - f)) {
|
||||||
|
endptr = nptr;
|
||||||
|
retval = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
} else if (fraction != 0) {
|
||||||
|
mul_required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c = *endptr;
|
||||||
|
mul = suffix_mul(c, unit);
|
||||||
|
if (mul > 0) {
|
||||||
|
if (hex) {
|
||||||
|
warn_report("Using a multiplier suffix on hex numbers "
|
||||||
|
"is deprecated: %s", nptr);
|
||||||
|
}
|
||||||
|
endptr++;
|
||||||
|
} else {
|
||||||
|
mul = suffix_mul(default_suffix, unit);
|
||||||
|
assert(mul > 0);
|
||||||
|
}
|
||||||
|
if (mul == 1 && mul_required) {
|
||||||
|
endptr = nptr;
|
||||||
|
retval = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (val > (UINT64_MAX - ((uint64_t) (fraction * mul))) / mul) {
|
||||||
retval = -ERANGE;
|
retval = -ERANGE;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
*result = val * mul;
|
*result = val * mul + (uint64_t) (fraction * mul);
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
|
Loading…
Reference in New Issue