X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/eab1e53cacfb1d877317d5e7b416ddb43858d92e..0b4ee9090e278a46b00b21624ba610552d0106d8:/qemu-img.c diff --git a/qemu-img.c b/qemu-img.c index bbe15741f1..f7ffb79db6 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -28,6 +28,7 @@ #include "qapi/qobject-output-visitor.h" #include "qapi/qmp/qerror.h" #include "qapi/qmp/qjson.h" +#include "qapi/qmp/qbool.h" #include "qemu/cutils.h" #include "qemu/config-file.h" #include "qemu/option.h" @@ -59,6 +60,7 @@ enum { OPTION_PATTERN = 260, OPTION_FLUSH_INTERVAL = 261, OPTION_NO_DRAIN = 262, + OPTION_TARGET_IMAGE_OPTS = 263, }; typedef enum OutputFormat { @@ -283,12 +285,21 @@ static int img_open_password(BlockBackend *blk, const char *filename, static BlockBackend *img_open_opts(const char *optstr, QemuOpts *opts, int flags, bool writethrough, - bool quiet) + bool quiet, bool force_share) { QDict *options; Error *local_err = NULL; BlockBackend *blk; options = qemu_opts_to_qdict(opts, NULL); + if (force_share) { + if (qdict_haskey(options, BDRV_OPT_FORCE_SHARE) + && !qdict_get_bool(options, BDRV_OPT_FORCE_SHARE)) { + error_report("--force-share/-U conflicts with image options"); + QDECREF(options); + return NULL; + } + qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true); + } blk = blk_new_open(NULL, NULL, options, flags, &local_err); if (!blk) { error_reportf_err(local_err, "Could not open '%s': ", optstr); @@ -304,18 +315,24 @@ static BlockBackend *img_open_opts(const char *optstr, } static BlockBackend *img_open_file(const char *filename, + QDict *options, const char *fmt, int flags, - bool writethrough, bool quiet) + bool writethrough, bool quiet, + bool force_share) { BlockBackend *blk; Error *local_err = NULL; - QDict *options = NULL; - if (fmt) { + if (!options) { options = qdict_new(); - qdict_put(options, "driver", qstring_from_str(fmt)); + } + if (fmt) { + qdict_put_str(options, "driver", fmt); } + if (force_share) { + qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true); + } blk = blk_new_open(filename, NULL, options, flags, &local_err); if (!blk) { error_reportf_err(local_err, "Could not open '%s': ", filename); @@ -331,10 +348,39 @@ static BlockBackend *img_open_file(const char *filename, } +static int img_add_key_secrets(void *opaque, + const char *name, const char *value, + Error **errp) +{ + QDict *options = opaque; + + if (g_str_has_suffix(name, "key-secret")) { + qdict_put(options, name, qstring_from_str(value)); + } + + return 0; +} + +static BlockBackend *img_open_new_file(const char *filename, + QemuOpts *create_opts, + const char *fmt, int flags, + bool writethrough, bool quiet, + bool force_share) +{ + QDict *options = NULL; + + options = qdict_new(); + qemu_opt_foreach(create_opts, img_add_key_secrets, options, &error_abort); + + return img_open_file(filename, options, fmt, flags, writethrough, quiet, + force_share); +} + + static BlockBackend *img_open(bool image_opts, const char *filename, const char *fmt, int flags, bool writethrough, - bool quiet) + bool quiet, bool force_share) { BlockBackend *blk; if (image_opts) { @@ -348,9 +394,11 @@ static BlockBackend *img_open(bool image_opts, if (!opts) { return NULL; } - blk = img_open_opts(filename, opts, flags, writethrough, quiet); + blk = img_open_opts(filename, opts, flags, writethrough, quiet, + force_share); } else { - blk = img_open_file(filename, fmt, flags, writethrough, quiet); + blk = img_open_file(filename, NULL, fmt, flags, writethrough, quiet, + force_share); } return blk; } @@ -416,7 +464,7 @@ static int img_create(int argc, char **argv) {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":F:b:f:he6o:q", + c = getopt_long(argc, argv, ":F:b:f:ho:q", long_options, NULL); if (c == -1) { break; @@ -440,14 +488,6 @@ static int img_create(int argc, char **argv) case 'f': fmt = optarg; break; - case 'e': - error_report("option -e is deprecated, please use \'-o " - "encryption\' instead!"); - goto fail; - case '6': - error_report("option -6 is deprecated, please use \'-o " - "compat6\' instead!"); - goto fail; case 'o': if (!is_valid_option_list(optarg)) { error_report("Invalid option list: %s", optarg); @@ -650,6 +690,7 @@ static int img_check(int argc, char **argv) ImageCheck *check; bool quiet = false; bool image_opts = false; + bool force_share = false; fmt = NULL; output = NULL; @@ -664,9 +705,10 @@ static int img_check(int argc, char **argv) {"output", required_argument, 0, OPTION_OUTPUT}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:r:T:q", + c = getopt_long(argc, argv, ":hf:r:T:qU", long_options, &option_index); if (c == -1) { break; @@ -705,6 +747,9 @@ static int img_check(int argc, char **argv) case 'q': quiet = true; break; + case 'U': + force_share = true; + break; case OPTION_OBJECT: { QemuOpts *opts; opts = qemu_opts_parse_noisily(&qemu_object_opts, @@ -744,7 +789,8 @@ static int img_check(int argc, char **argv) return 1; } - blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet); + blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet, + force_share); if (!blk) { return 1; } @@ -833,22 +879,28 @@ static void common_block_job_cb(void *opaque, int ret) static void run_block_job(BlockJob *job, Error **errp) { AioContext *aio_context = blk_get_aio_context(job->blk); + int ret = 0; - /* FIXME In error cases, the job simply goes away and we access a dangling - * pointer below. */ aio_context_acquire(aio_context); + block_job_ref(job); do { aio_poll(aio_context, true); qemu_progress_print(job->len ? ((float)job->offset / job->len * 100.f) : 0.0f, 0); - } while (!job->ready); + } while (!job->ready && !job->completed); - block_job_complete_sync(job, errp); + if (!job->completed) { + ret = block_job_complete_sync(job, errp); + } else { + ret = job->ret; + } + block_job_unref(job); aio_context_release(aio_context); - /* A block job may finish instantaneously without publishing any progress, - * so just signal completion here */ - qemu_progress_print(100.f, 0); + /* publish completion progress only when success */ + if (!ret) { + qemu_progress_print(100.f, 0); + } } static int img_commit(int argc, char **argv) @@ -947,7 +999,8 @@ static int img_commit(int argc, char **argv) return 1; } - blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet); + blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet, + false); if (!blk) { return 1; } @@ -1206,6 +1259,7 @@ static int img_compare(int argc, char **argv) int c, pnum; uint64_t progress_base; bool image_opts = false; + bool force_share = false; cache = BDRV_DEFAULT_CACHE; for (;;) { @@ -1213,9 +1267,10 @@ static int img_compare(int argc, char **argv) {"help", no_argument, 0, 'h'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:F:T:pqs", + c = getopt_long(argc, argv, ":hf:F:T:pqsU", long_options, NULL); if (c == -1) { break; @@ -1248,6 +1303,9 @@ static int img_compare(int argc, char **argv) case 's': strict = true; break; + case 'U': + force_share = true; + break; case OPTION_OBJECT: { QemuOpts *opts; opts = qemu_opts_parse_noisily(&qemu_object_opts, @@ -1293,13 +1351,15 @@ static int img_compare(int argc, char **argv) goto out3; } - blk1 = img_open(image_opts, filename1, fmt1, flags, writethrough, quiet); + blk1 = img_open(image_opts, filename1, fmt1, flags, writethrough, quiet, + force_share); if (!blk1) { ret = 2; goto out3; } - blk2 = img_open(image_opts, filename2, fmt2, flags, writethrough, quiet); + blk2 = img_open(image_opts, filename2, fmt2, flags, writethrough, quiet, + force_share); if (!blk2) { ret = 2; goto out2; @@ -1448,12 +1508,16 @@ static int img_compare(int argc, char **argv) } for (;;) { + int64_t count; + nb_sectors = sectors_to_process(total_sectors_over, sector_num); if (nb_sectors <= 0) { break; } - ret = bdrv_is_allocated_above(blk_bs(blk_over), NULL, sector_num, - nb_sectors, &pnum); + ret = bdrv_is_allocated_above(blk_bs(blk_over), NULL, + sector_num * BDRV_SECTOR_SIZE, + nb_sectors * BDRV_SECTOR_SIZE, + &count); if (ret < 0) { ret = 3; error_report("Sector allocation test failed for %s", @@ -1461,7 +1525,10 @@ static int img_compare(int argc, char **argv) goto out; } - nb_sectors = pnum; + /* TODO relax this once bdrv_is_allocated_above does not enforce + * sector alignment */ + assert(QEMU_IS_ALIGNED(count, BDRV_SECTOR_SIZE)); + nb_sectors = count >> BDRV_SECTOR_BITS; if (ret) { ret = check_empty_sectors(blk_over, sector_num, nb_sectors, filename_over, buf1, quiet); @@ -1522,7 +1589,7 @@ typedef struct ImgConvertState { int min_sparse; size_t cluster_sectors; size_t buf_sectors; - int num_coroutines; + long num_coroutines; int running_coroutines; Coroutine *co[MAX_COROUTINES]; int64_t wait_sector_num[MAX_COROUTINES]; @@ -1554,9 +1621,15 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num) if (s->sector_next_status <= sector_num) { BlockDriverState *file; - ret = bdrv_get_block_status(blk_bs(s->src[src_cur]), - sector_num - src_cur_offset, - n, &n, &file); + if (s->target_has_backing) { + ret = bdrv_get_block_status(blk_bs(s->src[src_cur]), + sector_num - src_cur_offset, + n, &n, &file); + } else { + ret = bdrv_get_block_status_above(blk_bs(s->src[src_cur]), NULL, + sector_num - src_cur_offset, + n, &n, &file); + } if (ret < 0) { return ret; } @@ -1565,26 +1638,8 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num) s->status = BLK_ZERO; } else if (ret & BDRV_BLOCK_DATA) { s->status = BLK_DATA; - } else if (!s->target_has_backing) { - /* Without a target backing file we must copy over the contents of - * the backing file as well. */ - /* Check block status of the backing file chain to avoid - * needlessly reading zeroes and limiting the iteration to the - * buffer size */ - ret = bdrv_get_block_status_above(blk_bs(s->src[src_cur]), NULL, - sector_num - src_cur_offset, - n, &n, &file); - if (ret < 0) { - return ret; - } - - if (ret & BDRV_BLOCK_ZERO) { - s->status = BLK_ZERO; - } else { - s->status = BLK_DATA; - } } else { - s->status = BLK_BACKING_FILE; + s->status = s->target_has_backing ? BLK_BACKING_FILE : BLK_DATA; } s->sector_next_status = sector_num + n; @@ -1661,6 +1716,8 @@ static int coroutine_fn convert_co_write(ImgConvertState *s, int64_t sector_num, while (nb_sectors > 0) { int n = nb_sectors; + BdrvRequestFlags flags = s->compressed ? BDRV_REQ_WRITE_COMPRESSED : 0; + switch (status) { case BLK_BACKING_FILE: /* If we have a backing file, leave clusters unallocated that are @@ -1670,43 +1727,24 @@ static int coroutine_fn convert_co_write(ImgConvertState *s, int64_t sector_num, break; case BLK_DATA: - /* We must always write compressed clusters as a whole, so don't - * try to find zeroed parts in the buffer. We can only save the - * write if the buffer is completely zeroed and we're allowed to - * keep the target sparse. */ - if (s->compressed) { - if (s->has_zero_init && s->min_sparse && - buffer_is_zero(buf, n * BDRV_SECTOR_SIZE)) - { - assert(!s->target_has_backing); - break; - } - - iov.iov_base = buf; - iov.iov_len = n << BDRV_SECTOR_BITS; - qemu_iovec_init_external(&qiov, &iov, 1); - - ret = blk_co_pwritev(s->target, sector_num << BDRV_SECTOR_BITS, - n << BDRV_SECTOR_BITS, &qiov, - BDRV_REQ_WRITE_COMPRESSED); - if (ret < 0) { - return ret; - } - break; - } - - /* If there is real non-zero data or we're told to keep the target - * fully allocated (-S 0), we must write it. Otherwise we can treat - * it as zero sectors. */ + /* If we're told to keep the target fully allocated (-S 0) or there + * is real non-zero data, we must write it. Otherwise we can treat + * it as zero sectors. + * Compressed clusters need to be written as a whole, so in that + * case we can only save the write if the buffer is completely + * zeroed. */ if (!s->min_sparse || - is_allocated_sectors_min(buf, n, &n, s->min_sparse)) + (!s->compressed && + is_allocated_sectors_min(buf, n, &n, s->min_sparse)) || + (s->compressed && + !buffer_is_zero(buf, n * BDRV_SECTOR_SIZE))) { iov.iov_base = buf; iov.iov_len = n << BDRV_SECTOR_BITS; qemu_iovec_init_external(&qiov, &iov, 1); ret = blk_co_pwritev(s->target, sector_num << BDRV_SECTOR_BITS, - n << BDRV_SECTOR_BITS, &qiov, 0); + n << BDRV_SECTOR_BITS, &qiov, flags); if (ret < 0) { return ret; } @@ -1716,6 +1754,7 @@ static int coroutine_fn convert_co_write(ImgConvertState *s, int64_t sector_num, case BLK_ZERO: if (s->has_zero_init) { + assert(!s->target_has_backing); break; } ret = blk_co_pwrite_zeroes(s->target, @@ -1761,13 +1800,13 @@ static void coroutine_fn convert_co_do_copy(void *opaque) qemu_co_mutex_lock(&s->lock); if (s->ret != -EINPROGRESS || s->sector_num >= s->total_sectors) { qemu_co_mutex_unlock(&s->lock); - goto out; + break; } n = convert_iteration_sectors(s, s->sector_num); if (n < 0) { qemu_co_mutex_unlock(&s->lock); s->ret = n; - goto out; + break; } /* save current sector and allocation status to local variables */ sector_num = s->sector_num; @@ -1792,7 +1831,6 @@ static void coroutine_fn convert_co_do_copy(void *opaque) error_report("error while reading sector %" PRId64 ": %s", sector_num, strerror(-ret)); s->ret = ret; - goto out; } } else if (!s->min_sparse && status == BLK_ZERO) { status = BLK_DATA; @@ -1801,22 +1839,20 @@ static void coroutine_fn convert_co_do_copy(void *opaque) if (s->wr_in_order) { /* keep writes in order */ - while (s->wr_offs != sector_num) { - if (s->ret != -EINPROGRESS) { - goto out; - } + while (s->wr_offs != sector_num && s->ret == -EINPROGRESS) { s->wait_sector_num[index] = sector_num; qemu_coroutine_yield(); } s->wait_sector_num[index] = -1; } - ret = convert_co_write(s, sector_num, n, buf, status); - if (ret < 0) { - error_report("error while writing sector %" PRId64 - ": %s", sector_num, strerror(-ret)); - s->ret = ret; - goto out; + if (s->ret == -EINPROGRESS) { + ret = convert_co_write(s, sector_num, n, buf, status); + if (ret < 0) { + error_report("error while writing sector %" PRId64 + ": %s", sector_num, strerror(-ret)); + s->ret = ret; + } } if (s->wr_in_order) { @@ -1837,7 +1873,6 @@ static void coroutine_fn convert_co_do_copy(void *opaque) } } -out: qemu_vfree(buf); s->co[index] = NULL; s->running_coroutines--; @@ -1899,7 +1934,7 @@ static int convert_do_copy(ImgConvertState *s) qemu_coroutine_enter(s->co[i]); } - while (s->ret == -EINPROGRESS) { + while (s->running_coroutines) { main_loop_wait(false); } @@ -1916,47 +1951,40 @@ static int convert_do_copy(ImgConvertState *s) static int img_convert(int argc, char **argv) { - int c, bs_n, bs_i, compress, cluster_sectors, skip_create; - int64_t ret = 0; - int progress = 0, flags, src_flags; - bool writethrough, src_writethrough; - const char *fmt, *out_fmt, *cache, *src_cache, *out_baseimg, *out_filename; - BlockDriver *drv, *proto_drv; - BlockBackend **blk = NULL, *out_blk = NULL; - BlockDriverState **bs = NULL, *out_bs = NULL; - int64_t total_sectors; - int64_t *bs_sectors = NULL; - size_t bufsectors = IO_BUF_SIZE / BDRV_SECTOR_SIZE; + int c, bs_i, flags, src_flags = 0; + const char *fmt = NULL, *out_fmt = NULL, *cache = "unsafe", + *src_cache = BDRV_DEFAULT_CACHE, *out_baseimg = NULL, + *out_filename, *out_baseimg_param, *snapshot_name = NULL; + BlockDriver *drv = NULL, *proto_drv = NULL; BlockDriverInfo bdi; - QemuOpts *opts = NULL; + BlockDriverState *out_bs; + QemuOpts *opts = NULL, *sn_opts = NULL; QemuOptsList *create_opts = NULL; - const char *out_baseimg_param; char *options = NULL; - const char *snapshot_name = NULL; - int min_sparse = 8; /* Need at least 4k of zeros for sparse detection */ - bool quiet = false; Error *local_err = NULL; - QemuOpts *sn_opts = NULL; - ImgConvertState state; - bool image_opts = false; - bool wr_in_order = true; - long num_coroutines = 8; + bool writethrough, src_writethrough, quiet = false, image_opts = false, + skip_create = false, progress = false, tgt_image_opts = false; + int64_t ret = -EINVAL; + bool force_share = false; + + ImgConvertState s = (ImgConvertState) { + /* Need at least 4k of zeros for sparse detection */ + .min_sparse = 8, + .buf_sectors = IO_BUF_SIZE / BDRV_SECTOR_SIZE, + .wr_in_order = true, + .num_coroutines = 8, + }; - fmt = NULL; - out_fmt = "raw"; - cache = "unsafe"; - src_cache = BDRV_DEFAULT_CACHE; - out_baseimg = NULL; - compress = 0; - skip_create = 0; for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, + {"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:O:B:ce6o:s:l:S:pt:T:qnm:W", + c = getopt_long(argc, argv, ":hf:O:B:co:s:l:S:pt:T:qnm:WU", long_options, NULL); if (c == -1) { break; @@ -1981,22 +2009,11 @@ static int img_convert(int argc, char **argv) out_baseimg = optarg; break; case 'c': - compress = 1; + s.compressed = true; break; - case 'e': - error_report("option -e is deprecated, please use \'-o " - "encryption\' instead!"); - ret = -1; - goto fail_getopt; - case '6': - error_report("option -6 is deprecated, please use \'-o " - "compat6\' instead!"); - ret = -1; - goto fail_getopt; case 'o': if (!is_valid_option_list(optarg)) { error_report("Invalid option list: %s", optarg); - ret = -1; goto fail_getopt; } if (!options) { @@ -2017,7 +2034,6 @@ static int img_convert(int argc, char **argv) if (!sn_opts) { error_report("Failed in parsing snapshot param '%s'", optarg); - ret = -1; goto fail_getopt; } } else { @@ -2031,15 +2047,14 @@ static int img_convert(int argc, char **argv) sval = cvtnum(optarg); if (sval < 0) { error_report("Invalid minimum zero buffer size for sparse output specified"); - ret = -1; goto fail_getopt; } - min_sparse = sval / BDRV_SECTOR_SIZE; + s.min_sparse = sval / BDRV_SECTOR_SIZE; break; } case 'p': - progress = 1; + progress = true; break; case 't': cache = optarg; @@ -2051,116 +2066,128 @@ static int img_convert(int argc, char **argv) quiet = true; break; case 'n': - skip_create = 1; + skip_create = true; break; case 'm': - if (qemu_strtol(optarg, NULL, 0, &num_coroutines) || - num_coroutines < 1 || num_coroutines > MAX_COROUTINES) { + if (qemu_strtol(optarg, NULL, 0, &s.num_coroutines) || + s.num_coroutines < 1 || s.num_coroutines > MAX_COROUTINES) { error_report("Invalid number of coroutines. Allowed number of" " coroutines is between 1 and %d", MAX_COROUTINES); - ret = -1; goto fail_getopt; } break; case 'W': - wr_in_order = false; + s.wr_in_order = false; break; - case OPTION_OBJECT: - opts = qemu_opts_parse_noisily(&qemu_object_opts, - optarg, true); - if (!opts) { + case 'U': + force_share = true; + break; + case OPTION_OBJECT: { + QemuOpts *object_opts; + object_opts = qemu_opts_parse_noisily(&qemu_object_opts, + optarg, true); + if (!object_opts) { goto fail_getopt; } break; + } case OPTION_IMAGE_OPTS: image_opts = true; break; + case OPTION_TARGET_IMAGE_OPTS: + tgt_image_opts = true; + break; } } + if (!out_fmt && !tgt_image_opts) { + out_fmt = "raw"; + } + if (qemu_opts_foreach(&qemu_object_opts, user_creatable_add_opts_foreach, NULL, NULL)) { goto fail_getopt; } - if (!wr_in_order && compress) { + if (!s.wr_in_order && s.compressed) { error_report("Out of order write and compress are mutually exclusive"); - ret = -1; goto fail_getopt; } - /* Initialize before goto out */ - if (quiet) { - progress = 0; + if (tgt_image_opts && !skip_create) { + error_report("--target-image-opts requires use of -n flag"); + goto fail_getopt; } - qemu_progress_init(progress, 1.0); - bs_n = argc - optind - 1; - out_filename = bs_n >= 1 ? argv[argc - 1] : NULL; + s.src_num = argc - optind - 1; + out_filename = s.src_num >= 1 ? argv[argc - 1] : NULL; if (options && has_help_option(options)) { - ret = print_block_option_help(out_filename, out_fmt); - goto out; + if (out_fmt) { + ret = print_block_option_help(out_filename, out_fmt); + goto fail_getopt; + } else { + error_report("Option help requires a format be specified"); + goto fail_getopt; + } } - if (bs_n < 1) { - error_exit("Must specify image file name"); + if (s.src_num < 1) { + error_report("Must specify image file name"); + goto fail_getopt; } - if (bs_n > 1 && out_baseimg) { - error_report("-B makes no sense when concatenating multiple input " - "images"); - ret = -1; - goto out; - } - - src_flags = 0; + /* ret is still -EINVAL until here */ ret = bdrv_parse_cache_mode(src_cache, &src_flags, &src_writethrough); if (ret < 0) { error_report("Invalid source cache option: %s", src_cache); - goto out; + goto fail_getopt; } + /* Initialize before goto out */ + if (quiet) { + progress = false; + } + qemu_progress_init(progress, 1.0); qemu_progress_print(0, 100); - blk = g_new0(BlockBackend *, bs_n); - bs = g_new0(BlockDriverState *, bs_n); - bs_sectors = g_new(int64_t, bs_n); + s.src = g_new0(BlockBackend *, s.src_num); + s.src_sectors = g_new(int64_t, s.src_num); - total_sectors = 0; - for (bs_i = 0; bs_i < bs_n; bs_i++) { - blk[bs_i] = img_open(image_opts, argv[optind + bs_i], - fmt, src_flags, src_writethrough, quiet); - if (!blk[bs_i]) { + for (bs_i = 0; bs_i < s.src_num; bs_i++) { + s.src[bs_i] = img_open(image_opts, argv[optind + bs_i], + fmt, src_flags, src_writethrough, quiet, + force_share); + if (!s.src[bs_i]) { ret = -1; goto out; } - bs[bs_i] = blk_bs(blk[bs_i]); - bs_sectors[bs_i] = blk_nb_sectors(blk[bs_i]); - if (bs_sectors[bs_i] < 0) { + s.src_sectors[bs_i] = blk_nb_sectors(s.src[bs_i]); + if (s.src_sectors[bs_i] < 0) { error_report("Could not get size of %s: %s", - argv[optind + bs_i], strerror(-bs_sectors[bs_i])); + argv[optind + bs_i], strerror(-s.src_sectors[bs_i])); ret = -1; goto out; } - total_sectors += bs_sectors[bs_i]; + s.total_sectors += s.src_sectors[bs_i]; } if (sn_opts) { - bdrv_snapshot_load_tmp(bs[0], + bdrv_snapshot_load_tmp(blk_bs(s.src[0]), qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID), qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME), &local_err); } else if (snapshot_name != NULL) { - if (bs_n > 1) { + if (s.src_num > 1) { error_report("No support for concatenating multiple snapshot"); ret = -1; goto out; } - bdrv_snapshot_load_tmp_by_id_or_name(bs[0], snapshot_name, &local_err); + bdrv_snapshot_load_tmp_by_id_or_name(blk_bs(s.src[0]), snapshot_name, + &local_err); } if (local_err) { error_reportf_err(local_err, "Failed to load snapshot: "); @@ -2168,22 +2195,22 @@ static int img_convert(int argc, char **argv) goto out; } - /* Find driver and parse its options */ - drv = bdrv_find_format(out_fmt); - if (!drv) { - error_report("Unknown file format '%s'", out_fmt); - ret = -1; - goto out; - } + if (!skip_create) { + /* Find driver and parse its options */ + drv = bdrv_find_format(out_fmt); + if (!drv) { + error_report("Unknown file format '%s'", out_fmt); + ret = -1; + goto out; + } - proto_drv = bdrv_find_protocol(out_filename, true, &local_err); - if (!proto_drv) { - error_report_err(local_err); - ret = -1; - goto out; - } + proto_drv = bdrv_find_protocol(out_filename, true, &local_err); + if (!proto_drv) { + error_report_err(local_err); + ret = -1; + goto out; + } - if (!skip_create) { if (!drv->create_opts) { error_report("Format driver '%s' does not support image creation", drv->format_name); @@ -2211,7 +2238,7 @@ static int img_convert(int argc, char **argv) } } - qemu_opt_set_number(opts, BLOCK_OPT_SIZE, total_sectors * 512, + qemu_opt_set_number(opts, BLOCK_OPT_SIZE, s.total_sectors * 512, &error_abort); ret = add_old_style_options(out_fmt, opts, out_baseimg, NULL); if (ret < 0) { @@ -2224,15 +2251,23 @@ static int img_convert(int argc, char **argv) if (out_baseimg_param) { out_baseimg = out_baseimg_param; } + s.target_has_backing = (bool) out_baseimg; + + if (s.src_num > 1 && out_baseimg) { + error_report("Having a backing file for the target makes no sense when " + "concatenating multiple input images"); + ret = -1; + goto out; + } /* Check if compression is supported */ - if (compress) { + if (s.compressed) { bool encryption = qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT, false); const char *preallocation = qemu_opt_get(opts, BLOCK_OPT_PREALLOC); - if (!drv->bdrv_co_pwritev_compressed) { + if (drv && !drv->bdrv_co_pwritev_compressed) { error_report("Compression not supported for this file format"); ret = -1; goto out; @@ -2265,76 +2300,72 @@ static int img_convert(int argc, char **argv) } } - flags = min_sparse ? (BDRV_O_RDWR | BDRV_O_UNMAP) : BDRV_O_RDWR; + flags = s.min_sparse ? (BDRV_O_RDWR | BDRV_O_UNMAP) : BDRV_O_RDWR; ret = bdrv_parse_cache_mode(cache, &flags, &writethrough); if (ret < 0) { error_report("Invalid cache option: %s", cache); goto out; } - /* XXX we should allow --image-opts to trigger use of - * img_open() here, but then we have trouble with - * the bdrv_create() call which takes different params. - * Not critical right now, so fix can wait... - */ - out_blk = img_open_file(out_filename, out_fmt, flags, writethrough, quiet); - if (!out_blk) { + if (skip_create) { + s.target = img_open(tgt_image_opts, out_filename, out_fmt, + flags, writethrough, quiet, false); + } else { + /* TODO ultimately we should allow --target-image-opts + * to be used even when -n is not given. + * That has to wait for bdrv_create to be improved + * to allow filenames in option syntax + */ + s.target = img_open_new_file(out_filename, opts, out_fmt, + flags, writethrough, quiet, false); + } + if (!s.target) { + ret = -1; + goto out; + } + out_bs = blk_bs(s.target); + + if (s.compressed && !out_bs->drv->bdrv_co_pwritev_compressed) { + error_report("Compression not supported for this file format"); ret = -1; goto out; } - out_bs = blk_bs(out_blk); /* increase bufsectors from the default 4096 (2M) if opt_transfer * or discard_alignment of the out_bs is greater. Limit to 32768 (16MB) * as maximum. */ - bufsectors = MIN(32768, - MAX(bufsectors, - MAX(out_bs->bl.opt_transfer >> BDRV_SECTOR_BITS, - out_bs->bl.pdiscard_alignment >> - BDRV_SECTOR_BITS))); + s.buf_sectors = MIN(32768, + MAX(s.buf_sectors, + MAX(out_bs->bl.opt_transfer >> BDRV_SECTOR_BITS, + out_bs->bl.pdiscard_alignment >> + BDRV_SECTOR_BITS))); if (skip_create) { - int64_t output_sectors = blk_nb_sectors(out_blk); + int64_t output_sectors = blk_nb_sectors(s.target); if (output_sectors < 0) { error_report("unable to get output image length: %s", strerror(-output_sectors)); ret = -1; goto out; - } else if (output_sectors < total_sectors) { + } else if (output_sectors < s.total_sectors) { error_report("output file is smaller than input file"); ret = -1; goto out; } } - cluster_sectors = 0; ret = bdrv_get_info(out_bs, &bdi); if (ret < 0) { - if (compress) { + if (s.compressed) { error_report("could not get block driver info"); goto out; } } else { - compress = compress || bdi.needs_compressed_writes; - cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE; - } - - state = (ImgConvertState) { - .src = blk, - .src_sectors = bs_sectors, - .src_num = bs_n, - .total_sectors = total_sectors, - .target = out_blk, - .compressed = compress, - .target_has_backing = (bool) out_baseimg, - .min_sparse = min_sparse, - .cluster_sectors = cluster_sectors, - .buf_sectors = bufsectors, - .wr_in_order = wr_in_order, - .num_coroutines = num_coroutines, - }; - ret = convert_do_copy(&state); + s.compressed = s.compressed || bdi.needs_compressed_writes; + s.cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE; + } + ret = convert_do_copy(&s); out: if (!ret) { qemu_progress_print(100, 0); @@ -2343,22 +2374,18 @@ out: qemu_opts_del(opts); qemu_opts_free(create_opts); qemu_opts_del(sn_opts); - blk_unref(out_blk); - g_free(bs); - if (blk) { - for (bs_i = 0; bs_i < bs_n; bs_i++) { - blk_unref(blk[bs_i]); + blk_unref(s.target); + if (s.src) { + for (bs_i = 0; bs_i < s.src_num; bs_i++) { + blk_unref(s.src[bs_i]); } - g_free(blk); + g_free(s.src); } - g_free(bs_sectors); + g_free(s.src_sectors); fail_getopt: g_free(options); - if (ret) { - return 1; - } - return 0; + return !!ret; } @@ -2448,7 +2475,7 @@ static gboolean str_equal_func(gconstpointer a, gconstpointer b) static ImageInfoList *collect_image_info_list(bool image_opts, const char *filename, const char *fmt, - bool chain) + bool chain, bool force_share) { ImageInfoList *head = NULL; ImageInfoList **last = &head; @@ -2471,7 +2498,8 @@ static ImageInfoList *collect_image_info_list(bool image_opts, g_hash_table_insert(filenames, (gpointer)filename, NULL); blk = img_open(image_opts, filename, fmt, - BDRV_O_NO_BACKING | BDRV_O_NO_IO, false, false); + BDRV_O_NO_BACKING | BDRV_O_NO_IO, false, false, + force_share); if (!blk) { goto err; } @@ -2523,6 +2551,7 @@ static int img_info(int argc, char **argv) const char *filename, *fmt, *output; ImageInfoList *list; bool image_opts = false; + bool force_share = false; fmt = NULL; output = NULL; @@ -2535,9 +2564,10 @@ static int img_info(int argc, char **argv) {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:h", + c = getopt_long(argc, argv, ":f:hU", long_options, &option_index); if (c == -1) { break; @@ -2555,6 +2585,9 @@ static int img_info(int argc, char **argv) case 'f': fmt = optarg; break; + case 'U': + force_share = true; + break; case OPTION_OUTPUT: output = optarg; break; @@ -2594,7 +2627,8 @@ static int img_info(int argc, char **argv) return 1; } - list = collect_image_info_list(image_opts, filename, fmt, chain); + list = collect_image_info_list(image_opts, filename, fmt, chain, + force_share); if (!list) { return 1; } @@ -2740,6 +2774,7 @@ static int img_map(int argc, char **argv) MapEntry curr = { .length = 0 }, next; int ret = 0; bool image_opts = false; + bool force_share = false; fmt = NULL; output = NULL; @@ -2751,9 +2786,10 @@ static int img_map(int argc, char **argv) {"output", required_argument, 0, OPTION_OUTPUT}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:h", + c = getopt_long(argc, argv, ":f:hU", long_options, &option_index); if (c == -1) { break; @@ -2771,6 +2807,9 @@ static int img_map(int argc, char **argv) case 'f': fmt = optarg; break; + case 'U': + force_share = true; + break; case OPTION_OUTPUT: output = optarg; break; @@ -2807,7 +2846,7 @@ static int img_map(int argc, char **argv) return 1; } - blk = img_open(image_opts, filename, fmt, 0, false, false); + blk = img_open(image_opts, filename, fmt, 0, false, false, force_share); if (!blk) { return 1; } @@ -2870,6 +2909,7 @@ static int img_snapshot(int argc, char **argv) bool quiet = false; Error *err = NULL; bool image_opts = false; + bool force_share = false; bdrv_oflags = BDRV_O_RDWR; /* Parse commandline parameters */ @@ -2878,9 +2918,10 @@ static int img_snapshot(int argc, char **argv) {"help", no_argument, 0, 'h'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":la:c:d:hq", + c = getopt_long(argc, argv, ":la:c:d:hqU", long_options, NULL); if (c == -1) { break; @@ -2930,6 +2971,9 @@ static int img_snapshot(int argc, char **argv) case 'q': quiet = true; break; + case 'U': + force_share = true; + break; case OPTION_OBJECT: { QemuOpts *opts; opts = qemu_opts_parse_noisily(&qemu_object_opts, @@ -2956,7 +3000,8 @@ static int img_snapshot(int argc, char **argv) } /* Open the image */ - blk = img_open(image_opts, filename, NULL, bdrv_oflags, false, quiet); + blk = img_open(image_opts, filename, NULL, bdrv_oflags, false, quiet, + force_share); if (!blk) { return 1; } @@ -3020,6 +3065,7 @@ static int img_rebase(int argc, char **argv) int c, flags, src_flags, ret; bool writethrough, src_writethrough; int unsafe = 0; + bool force_share = false; int progress = 0; bool quiet = false; Error *local_err = NULL; @@ -3036,9 +3082,10 @@ static int img_rebase(int argc, char **argv) {"help", no_argument, 0, 'h'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:F:b:upt:T:q", + c = getopt_long(argc, argv, ":hf:F:b:upt:T:qU", long_options, NULL); if (c == -1) { break; @@ -3088,6 +3135,9 @@ static int img_rebase(int argc, char **argv) case OPTION_IMAGE_OPTS: image_opts = true; break; + case 'U': + force_share = true; + break; } } @@ -3136,7 +3186,8 @@ static int img_rebase(int argc, char **argv) * Ignore the old backing file for unsafe rebase in case we want to correct * the reference to a renamed or moved backing file. */ - blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet); + blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet, + false); if (!blk) { ret = -1; goto out; @@ -3158,9 +3209,15 @@ static int img_rebase(int argc, char **argv) if (bs->backing_format[0] != '\0') { options = qdict_new(); - qdict_put(options, "driver", qstring_from_str(bs->backing_format)); + qdict_put_str(options, "driver", bs->backing_format); } + if (force_share) { + if (!options) { + options = qdict_new(); + } + qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true); + } bdrv_get_backing_filename(bs, backing_name, sizeof(backing_name)); blk_old_backing = blk_new_open(backing_name, NULL, options, src_flags, &local_err); @@ -3173,11 +3230,12 @@ static int img_rebase(int argc, char **argv) } if (out_baseimg[0]) { + options = qdict_new(); if (out_basefmt) { - options = qdict_new(); - qdict_put(options, "driver", qstring_from_str(out_basefmt)); - } else { - options = NULL; + qdict_put_str(options, "driver", out_basefmt); + } + if (force_share) { + qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true); } blk_new_backing = blk_new_open(out_baseimg, NULL, @@ -3207,6 +3265,7 @@ static int img_rebase(int argc, char **argv) int64_t new_backing_num_sectors = 0; uint64_t sector; int n; + int64_t count; float local_progress = 0; buf_old = blk_blockalign(blk, IO_BUF_SIZE); @@ -3254,12 +3313,17 @@ static int img_rebase(int argc, char **argv) } /* If the cluster is allocated, we don't need to take action */ - ret = bdrv_is_allocated(bs, sector, n, &n); + ret = bdrv_is_allocated(bs, sector << BDRV_SECTOR_BITS, + n << BDRV_SECTOR_BITS, &count); if (ret < 0) { error_report("error while reading image metadata: %s", strerror(-ret)); goto out; } + /* TODO relax this once bdrv_is_allocated does not enforce + * sector alignment */ + assert(QEMU_IS_ALIGNED(count, BDRV_SECTOR_SIZE)); + n = count >> BDRV_SECTOR_BITS; if (ret) { continue; } @@ -3483,7 +3547,8 @@ static int img_resize(int argc, char **argv) qemu_opts_del(param); blk = img_open(image_opts, filename, fmt, - BDRV_O_RDWR | BDRV_O_RESIZE, false, quiet); + BDRV_O_RDWR | BDRV_O_RESIZE, false, quiet, + false); if (!blk) { ret = -1; goto out; @@ -3500,20 +3565,11 @@ static int img_resize(int argc, char **argv) goto out; } - ret = blk_truncate(blk, total_size); - switch (ret) { - case 0: + ret = blk_truncate(blk, total_size, &err); + if (!ret) { qprintf(quiet, "Image resized.\n"); - break; - case -ENOTSUP: - error_report("This image does not support resize"); - break; - case -EACCES: - error_report("Image is read-only"); - break; - default: - error_report("Error resizing image: %s", strerror(-ret)); - break; + } else { + error_report_err(err); } out: blk_unref(blk); @@ -3646,7 +3702,8 @@ static int img_amend(int argc, char **argv) goto out; } - blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet); + blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet, + false); if (!blk) { ret = -1; goto out; @@ -3814,6 +3871,7 @@ static int img_bench(int argc, char **argv) bool writethrough = false; struct timeval t1, t2; int i; + bool force_share = false; for (;;) { static const struct option long_options[] = { @@ -3822,9 +3880,10 @@ static int img_bench(int argc, char **argv) {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"pattern", required_argument, 0, OPTION_PATTERN}, {"no-drain", no_argument, 0, OPTION_NO_DRAIN}, + {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hc:d:f:no:qs:S:t:w", long_options, NULL); + c = getopt_long(argc, argv, ":hc:d:f:no:qs:S:t:wU", long_options, NULL); if (c == -1) { break; } @@ -3918,6 +3977,9 @@ static int img_bench(int argc, char **argv) flags |= BDRV_O_RDWR; is_write = true; break; + case 'U': + force_share = true; + break; case OPTION_PATTERN: { unsigned long res; @@ -3965,7 +4027,8 @@ static int img_bench(int argc, char **argv) goto out; } - blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet); + blk = img_open(image_opts, filename, fmt, flags, writethrough, quiet, + force_share); if (!blk) { ret = -1; goto out; @@ -4132,6 +4195,7 @@ static int img_dd(int argc, char **argv) const char *fmt = NULL; int64_t size = 0; int64_t block_count = 0, out_pos, in_pos; + bool force_share = false; struct DdInfo dd = { .flags = 0, .count = 0, @@ -4159,11 +4223,13 @@ static int img_dd(int argc, char **argv) }; const struct option long_options[] = { { "help", no_argument, 0, 'h'}, + { "object", required_argument, 0, OPTION_OBJECT}, { "image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + { "force-share", no_argument, 0, 'U'}, { 0, 0, 0, 0 } }; - while ((c = getopt_long(argc, argv, ":hf:O:", long_options, NULL))) { + while ((c = getopt_long(argc, argv, ":hf:O:U", long_options, NULL))) { if (c == EOF) { break; } @@ -4183,6 +4249,15 @@ static int img_dd(int argc, char **argv) case 'h': help(); break; + case 'U': + force_share = true; + break; + case OPTION_OBJECT: + if (!qemu_opts_parse_noisily(&qemu_object_opts, optarg, true)) { + ret = -1; + goto out; + } + break; case OPTION_IMAGE_OPTS: image_opts = true; break; @@ -4227,7 +4302,16 @@ static int img_dd(int argc, char **argv) ret = -1; goto out; } - blk1 = img_open(image_opts, in.filename, fmt, 0, false, false); + + if (qemu_opts_foreach(&qemu_object_opts, + user_creatable_add_opts_foreach, + NULL, NULL)) { + ret = -1; + goto out; + } + + blk1 = img_open(image_opts, in.filename, fmt, 0, false, false, + force_share); if (!blk1) { ret = -1; @@ -4294,8 +4378,13 @@ static int img_dd(int argc, char **argv) goto out; } - blk2 = img_open(image_opts, out.filename, out_fmt, BDRV_O_RDWR, - false, false); + /* TODO, we can't honour --image-opts for the target, + * since it needs to be given in a format compatible + * with the bdrv_create() call above which does not + * support image-opts style. + */ + blk2 = img_open_file(out.filename, NULL, out_fmt, BDRV_O_RDWR, + false, false, false); if (!blk2) { ret = -1;