X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/6d3f4c6d1d8eb1187dc13713c49e3986eab39e7a..0b4ee9090e278a46b00b21624ba610552d0106d8:/qemu-img.c diff --git a/qemu-img.c b/qemu-img.c index df3aefd35a..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 { @@ -88,6 +90,16 @@ static void QEMU_NORETURN GCC_FMT_ATTR(1, 2) error_exit(const char *fmt, ...) exit(EXIT_FAILURE); } +static void QEMU_NORETURN missing_argument(const char *option) +{ + error_exit("missing argument for option '%s'", option); +} + +static void QEMU_NORETURN unrecognized_option(const char *option) +{ + error_exit("unrecognized option '%s'", option); +} + /* Please keep in synch with qemu-img.texi */ static void QEMU_NORETURN help(void) { @@ -156,6 +168,11 @@ static void QEMU_NORETURN help(void) " kinds of errors, with a higher risk of choosing the wrong fix or\n" " hiding corruption that has already occurred.\n" "\n" + "Parameters to convert subcommand:\n" + " '-m' specifies how many coroutines work in parallel during the convert\n" + " process (defaults to 8)\n" + " '-W' allow to write to the target out of order rather than sequential\n" + "\n" "Parameters to snapshot subcommand:\n" " 'snapshot' is the name of the snapshot to create, apply or delete\n" " '-a' applies a snapshot (revert disk to saved state)\n" @@ -268,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); @@ -289,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); @@ -316,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) { @@ -333,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; } @@ -401,13 +464,18 @@ 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; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; @@ -420,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); @@ -496,7 +556,7 @@ static int img_create(int argc, char **argv) } bdrv_img_create(filename, fmt, base_filename, base_fmt, - options, img_size, 0, &local_err, quiet); + options, img_size, 0, quiet, &local_err); if (local_err) { error_reportf_err(local_err, "%s: ", filename); goto fail; @@ -630,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; @@ -644,15 +705,21 @@ 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; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; @@ -680,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, @@ -719,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; } @@ -808,20 +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; 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) @@ -830,6 +909,7 @@ static int img_commit(int argc, char **argv) const char *filename, *fmt, *cache, *base; BlockBackend *blk; BlockDriverState *bs, *base_bs; + BlockJob *job; bool progress = false, quiet = false, drop = false; bool writethrough; Error *local_err = NULL; @@ -847,13 +927,18 @@ static int img_commit(int argc, char **argv) {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "f:ht:b:dpq", + c = getopt_long(argc, argv, ":f:ht:b:dpq", long_options, NULL); if (c == -1) { break; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; @@ -914,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; } @@ -950,8 +1036,8 @@ static int img_commit(int argc, char **argv) aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); commit_active_start("commit", bs, base_bs, BLOCK_JOB_DEFAULT, 0, - BLOCKDEV_ON_ERROR_REPORT, common_block_job_cb, &cbi, - &local_err, false); + BLOCKDEV_ON_ERROR_REPORT, NULL, common_block_job_cb, + &cbi, false, &local_err); aio_context_release(aio_context); if (local_err) { goto done; @@ -965,7 +1051,8 @@ static int img_commit(int argc, char **argv) bdrv_ref(bs); } - run_block_job(bs->job, &local_err); + job = block_job_get("commit"); + run_block_job(job, &local_err); if (local_err) { goto unref_backing; } @@ -1172,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 (;;) { @@ -1179,15 +1267,21 @@ 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; } switch (c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; @@ -1209,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, @@ -1254,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; @@ -1409,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", @@ -1422,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); @@ -1462,49 +1568,68 @@ enum ImgConvertBlockStatus { BLK_BACKING_FILE, }; +#define MAX_COROUTINES 16 + typedef struct ImgConvertState { BlockBackend **src; int64_t *src_sectors; - int src_cur, src_num; - int64_t src_cur_offset; + int src_num; int64_t total_sectors; int64_t allocated_sectors; + int64_t allocated_done; + int64_t sector_num; + int64_t wr_offs; enum ImgConvertBlockStatus status; int64_t sector_next_status; BlockBackend *target; bool has_zero_init; bool compressed; bool target_has_backing; + bool wr_in_order; int min_sparse; size_t cluster_sectors; size_t buf_sectors; + long num_coroutines; + int running_coroutines; + Coroutine *co[MAX_COROUTINES]; + int64_t wait_sector_num[MAX_COROUTINES]; + CoMutex lock; + int ret; } ImgConvertState; -static void convert_select_part(ImgConvertState *s, int64_t sector_num) +static void convert_select_part(ImgConvertState *s, int64_t sector_num, + int *src_cur, int64_t *src_cur_offset) { - assert(sector_num >= s->src_cur_offset); - while (sector_num - s->src_cur_offset >= s->src_sectors[s->src_cur]) { - s->src_cur_offset += s->src_sectors[s->src_cur]; - s->src_cur++; - assert(s->src_cur < s->src_num); + *src_cur = 0; + *src_cur_offset = 0; + while (sector_num - *src_cur_offset >= s->src_sectors[*src_cur]) { + *src_cur_offset += s->src_sectors[*src_cur]; + (*src_cur)++; + assert(*src_cur < s->src_num); } } static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num) { - int64_t ret; - int n; + int64_t ret, src_cur_offset; + int n, src_cur; - convert_select_part(s, sector_num); + convert_select_part(s, sector_num, &src_cur, &src_cur_offset); assert(s->total_sectors > sector_num); n = MIN(s->total_sectors - sector_num, BDRV_REQUEST_MAX_SECTORS); if (s->sector_next_status <= sector_num) { BlockDriverState *file; - ret = bdrv_get_block_status(blk_bs(s->src[s->src_cur]), - sector_num - s->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; } @@ -1513,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[s->src_cur]), NULL, - sector_num - s->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; @@ -1558,28 +1665,34 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num) return n; } -static int convert_read(ImgConvertState *s, int64_t sector_num, int nb_sectors, - uint8_t *buf) +static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num, + int nb_sectors, uint8_t *buf) { - int n; - int ret; + int n, ret; + QEMUIOVector qiov; + struct iovec iov; assert(nb_sectors <= s->buf_sectors); while (nb_sectors > 0) { BlockBackend *blk; - int64_t bs_sectors; + int src_cur; + int64_t bs_sectors, src_cur_offset; /* In the case of compression with multiple source files, we can get a * nb_sectors that spreads into the next part. So we must be able to * read across multiple BDSes for one convert_read() call. */ - convert_select_part(s, sector_num); - blk = s->src[s->src_cur]; - bs_sectors = s->src_sectors[s->src_cur]; - - n = MIN(nb_sectors, bs_sectors - (sector_num - s->src_cur_offset)); - ret = blk_pread(blk, - (sector_num - s->src_cur_offset) << BDRV_SECTOR_BITS, - buf, n << BDRV_SECTOR_BITS); + convert_select_part(s, sector_num, &src_cur, &src_cur_offset); + blk = s->src[src_cur]; + bs_sectors = s->src_sectors[src_cur]; + + n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset)); + iov.iov_base = buf; + iov.iov_len = n << BDRV_SECTOR_BITS; + qemu_iovec_init_external(&qiov, &iov, 1); + + ret = blk_co_preadv( + blk, (sector_num - src_cur_offset) << BDRV_SECTOR_BITS, + n << BDRV_SECTOR_BITS, &qiov, 0); if (ret < 0) { return ret; } @@ -1592,15 +1705,20 @@ static int convert_read(ImgConvertState *s, int64_t sector_num, int nb_sectors, return 0; } -static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors, - const uint8_t *buf) + +static int coroutine_fn convert_co_write(ImgConvertState *s, int64_t sector_num, + int nb_sectors, uint8_t *buf, + enum ImgConvertBlockStatus status) { int ret; + QEMUIOVector qiov; + struct iovec iov; while (nb_sectors > 0) { int n = nb_sectors; + BdrvRequestFlags flags = s->compressed ? BDRV_REQ_WRITE_COMPRESSED : 0; - switch (s->status) { + switch (status) { case BLK_BACKING_FILE: /* If we have a backing file, leave clusters unallocated that are * unallocated in the source image, so that the backing file is @@ -1609,35 +1727,24 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors, 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; - } - - ret = blk_pwrite_compressed(s->target, - sector_num << BDRV_SECTOR_BITS, - buf, n << BDRV_SECTOR_BITS); - 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))) { - ret = blk_pwrite(s->target, sector_num << BDRV_SECTOR_BITS, - buf, n << BDRV_SECTOR_BITS, 0); + 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, flags); if (ret < 0) { return ret; } @@ -1647,10 +1754,12 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors, case BLK_ZERO: if (s->has_zero_init) { + assert(!s->target_has_backing); break; } - ret = blk_pwrite_zeroes(s->target, sector_num << BDRV_SECTOR_BITS, - n << BDRV_SECTOR_BITS, 0); + ret = blk_co_pwrite_zeroes(s->target, + sector_num << BDRV_SECTOR_BITS, + n << BDRV_SECTOR_BITS, 0); if (ret < 0) { return ret; } @@ -1665,12 +1774,118 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors, return 0; } -static int convert_do_copy(ImgConvertState *s) +static void coroutine_fn convert_co_do_copy(void *opaque) { + ImgConvertState *s = opaque; uint8_t *buf = NULL; - int64_t sector_num, allocated_done; - int ret; - int n; + int ret, i; + int index = -1; + + for (i = 0; i < s->num_coroutines; i++) { + if (s->co[i] == qemu_coroutine_self()) { + index = i; + break; + } + } + assert(index >= 0); + + s->running_coroutines++; + buf = blk_blockalign(s->target, s->buf_sectors * BDRV_SECTOR_SIZE); + + while (1) { + int n; + int64_t sector_num; + enum ImgConvertBlockStatus status; + + qemu_co_mutex_lock(&s->lock); + if (s->ret != -EINPROGRESS || s->sector_num >= s->total_sectors) { + qemu_co_mutex_unlock(&s->lock); + break; + } + n = convert_iteration_sectors(s, s->sector_num); + if (n < 0) { + qemu_co_mutex_unlock(&s->lock); + s->ret = n; + break; + } + /* save current sector and allocation status to local variables */ + sector_num = s->sector_num; + status = s->status; + if (!s->min_sparse && s->status == BLK_ZERO) { + n = MIN(n, s->buf_sectors); + } + /* increment global sector counter so that other coroutines can + * already continue reading beyond this request */ + s->sector_num += n; + qemu_co_mutex_unlock(&s->lock); + + if (status == BLK_DATA || (!s->min_sparse && status == BLK_ZERO)) { + s->allocated_done += n; + qemu_progress_print(100.0 * s->allocated_done / + s->allocated_sectors, 0); + } + + if (status == BLK_DATA) { + ret = convert_co_read(s, sector_num, n, buf); + if (ret < 0) { + error_report("error while reading sector %" PRId64 + ": %s", sector_num, strerror(-ret)); + s->ret = ret; + } + } else if (!s->min_sparse && status == BLK_ZERO) { + status = BLK_DATA; + memset(buf, 0x00, n * BDRV_SECTOR_SIZE); + } + + if (s->wr_in_order) { + /* keep writes in order */ + 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; + } + + 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) { + /* reenter the coroutine that might have waited + * for this write to complete */ + s->wr_offs = sector_num + n; + for (i = 0; i < s->num_coroutines; i++) { + if (s->co[i] && s->wait_sector_num[i] == s->wr_offs) { + /* + * A -> B -> A cannot occur because A has + * s->wait_sector_num[i] == -1 during A -> B. Therefore + * B will never enter A during this time window. + */ + qemu_coroutine_enter(s->co[i]); + break; + } + } + } + } + + qemu_vfree(buf); + s->co[index] = NULL; + s->running_coroutines--; + if (!s->running_coroutines && s->ret == -EINPROGRESS) { + /* the convert job finished successfully */ + s->ret = 0; + } +} + +static int convert_do_copy(ImgConvertState *s) +{ + int ret, i, n; + int64_t sector_num = 0; /* Check whether we have zero initialisation or can get it efficiently */ s->has_zero_init = s->min_sparse && !s->target_has_backing @@ -1691,21 +1906,15 @@ static int convert_do_copy(ImgConvertState *s) if (s->compressed) { if (s->cluster_sectors <= 0 || s->cluster_sectors > s->buf_sectors) { error_report("invalid cluster size"); - ret = -EINVAL; - goto fail; + return -EINVAL; } s->buf_sectors = s->cluster_sectors; } - buf = blk_blockalign(s->target, s->buf_sectors * BDRV_SECTOR_SIZE); - /* Calculate allocated sectors for progress */ - s->allocated_sectors = 0; - sector_num = 0; while (sector_num < s->total_sectors) { n = convert_iteration_sectors(s, sector_num); if (n < 0) { - ret = n; - goto fail; + return n; } if (s->status == BLK_DATA || (!s->min_sparse && s->status == BLK_ZERO)) { @@ -1715,110 +1924,78 @@ static int convert_do_copy(ImgConvertState *s) } /* Do the copy */ - s->src_cur = 0; - s->src_cur_offset = 0; s->sector_next_status = 0; + s->ret = -EINPROGRESS; - sector_num = 0; - allocated_done = 0; - - while (sector_num < s->total_sectors) { - n = convert_iteration_sectors(s, sector_num); - if (n < 0) { - ret = n; - goto fail; - } - if (s->status == BLK_DATA || (!s->min_sparse && s->status == BLK_ZERO)) - { - allocated_done += n; - qemu_progress_print(100.0 * allocated_done / s->allocated_sectors, - 0); - } - - if (s->status == BLK_DATA) { - ret = convert_read(s, sector_num, n, buf); - if (ret < 0) { - error_report("error while reading sector %" PRId64 - ": %s", sector_num, strerror(-ret)); - goto fail; - } - } else if (!s->min_sparse && s->status == BLK_ZERO) { - n = MIN(n, s->buf_sectors); - memset(buf, 0, n * BDRV_SECTOR_SIZE); - s->status = BLK_DATA; - } - - ret = convert_write(s, sector_num, n, buf); - if (ret < 0) { - error_report("error while writing sector %" PRId64 - ": %s", sector_num, strerror(-ret)); - goto fail; - } + qemu_co_mutex_init(&s->lock); + for (i = 0; i < s->num_coroutines; i++) { + s->co[i] = qemu_coroutine_create(convert_co_do_copy, s); + s->wait_sector_num[i] = -1; + qemu_coroutine_enter(s->co[i]); + } - sector_num += n; + while (s->running_coroutines) { + main_loop_wait(false); } - if (s->compressed) { + if (s->compressed && !s->ret) { /* signal EOF to align */ ret = blk_pwrite_compressed(s->target, 0, NULL, 0); if (ret < 0) { - goto fail; + return ret; } } - ret = 0; -fail: - qemu_vfree(buf); - return ret; + return s->ret; } 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 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:qn", + c = getopt_long(argc, argv, ":hf:O:B:co:s:l:S:pt:T:qnm:WU", long_options, NULL); if (c == -1) { break; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; @@ -1832,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) { @@ -1868,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 { @@ -1882,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; @@ -1902,98 +2066,128 @@ static int img_convert(int argc, char **argv) quiet = true; break; case 'n': - skip_create = 1; + skip_create = true; break; - case OPTION_OBJECT: - opts = qemu_opts_parse_noisily(&qemu_object_opts, - optarg, true); - if (!opts) { + case 'm': + 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); goto fail_getopt; } break; + case 'W': + s.wr_in_order = false; + break; + 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; } - /* Initialize before goto out */ - if (quiet) { - progress = 0; + if (!s.wr_in_order && s.compressed) { + error_report("Out of order write and compress are mutually exclusive"); + goto fail_getopt; + } + + 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: "); @@ -2001,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); @@ -2044,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) { @@ -2057,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; @@ -2098,74 +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, - }; - 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); @@ -2174,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; } @@ -2279,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; @@ -2302,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; } @@ -2354,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; @@ -2366,21 +2564,30 @@ 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; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; case 'f': fmt = optarg; break; + case 'U': + force_share = true; + break; case OPTION_OUTPUT: output = optarg; break; @@ -2420,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; } @@ -2566,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; @@ -2577,21 +2786,30 @@ 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; } switch (c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; case 'f': fmt = optarg; break; + case 'U': + force_share = true; + break; case OPTION_OUTPUT: output = optarg; break; @@ -2628,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; } @@ -2691,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 */ @@ -2699,15 +2918,21 @@ 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; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); return 0; @@ -2746,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, @@ -2772,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; } @@ -2836,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; @@ -2852,15 +3082,21 @@ 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; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); return 0; @@ -2899,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; } } @@ -2947,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; @@ -2969,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); @@ -2984,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, @@ -3018,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); @@ -3065,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; } @@ -3221,13 +3474,18 @@ static int img_resize(int argc, char **argv) {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "f:hq", + c = getopt_long(argc, argv, ":f:hq", long_options, NULL); if (c == -1) { break; } switch(c) { + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; case 'h': help(); break; @@ -3289,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, false, quiet); + BDRV_O_RDWR | BDRV_O_RESIZE, false, quiet, + false); if (!blk) { ret = -1; goto out; @@ -3306,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); @@ -3359,54 +3609,59 @@ static int img_amend(int argc, char **argv) {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "ho:f:t:pq", + c = getopt_long(argc, argv, ":ho:f:t:pq", long_options, NULL); if (c == -1) { break; } switch (c) { - case 'h': - case '?': - help(); - break; - case 'o': - if (!is_valid_option_list(optarg)) { - error_report("Invalid option list: %s", optarg); - ret = -1; - goto out_no_progress; - } - if (!options) { - options = g_strdup(optarg); - } else { - char *old_options = options; - options = g_strdup_printf("%s,%s", options, optarg); - g_free(old_options); - } - break; - case 'f': - fmt = optarg; - break; - case 't': - cache = optarg; - break; - case 'p': - progress = true; - break; - case 'q': - quiet = true; - break; - case OPTION_OBJECT: - opts = qemu_opts_parse_noisily(&qemu_object_opts, - optarg, true); - if (!opts) { - ret = -1; - goto out_no_progress; - } - break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + case ':': + missing_argument(argv[optind - 1]); + break; + case '?': + unrecognized_option(argv[optind - 1]); + break; + case 'h': + help(); + break; + case 'o': + if (!is_valid_option_list(optarg)) { + error_report("Invalid option list: %s", optarg); + ret = -1; + goto out_no_progress; + } + if (!options) { + options = g_strdup(optarg); + } else { + char *old_options = options; + options = g_strdup_printf("%s,%s", options, optarg); + g_free(old_options); + } + break; + case 'f': + fmt = optarg; + break; + case 't': + cache = optarg; + break; + case 'p': + progress = true; + break; + case 'q': + quiet = true; + break; + case OPTION_OBJECT: + opts = qemu_opts_parse_noisily(&qemu_object_opts, + optarg, true); + if (!opts) { + ret = -1; + goto out_no_progress; + } + break; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; } } @@ -3447,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; @@ -3615,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[] = { @@ -3623,16 +3880,22 @@ 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; } switch (c) { - case 'h': + case ':': + missing_argument(argv[optind - 1]); + break; case '?': + unrecognized_option(argv[optind - 1]); + break; + case 'h': help(); break; case 'c': @@ -3714,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; @@ -3761,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; @@ -3928,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, @@ -3955,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; } @@ -3970,13 +4240,24 @@ static int img_dd(int argc, char **argv) case 'f': fmt = optarg; break; + case ':': + missing_argument(argv[optind - 1]); + break; case '?': - error_report("Try 'qemu-img --help' for more information."); - ret = -1; - goto out; + unrecognized_option(argv[optind - 1]); + break; 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; @@ -4021,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; @@ -4088,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; @@ -4202,8 +4497,14 @@ int main(int argc, char **argv) qemu_add_opts(&qemu_source_opts); qemu_add_opts(&qemu_trace_opts); - while ((c = getopt_long(argc, argv, "+hVT:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "+:hVT:", long_options, NULL)) != -1) { switch (c) { + case ':': + missing_argument(argv[optind - 1]); + return 0; + case '?': + unrecognized_option(argv[optind - 1]); + return 0; case 'h': help(); return 0;