in qemu-img.c [2181:2760]
static int img_convert(int argc, char **argv)
{
int c, bs_i, flags, src_flags = BDRV_O_NO_SHARE;
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,
*backing_fmt = NULL;
BlockDriver *drv = NULL, *proto_drv = NULL;
BlockDriverInfo bdi;
BlockDriverState *out_bs;
QemuOpts *opts = NULL, *sn_opts = NULL;
QemuOptsList *create_opts = NULL;
QDict *open_opts = NULL;
char *options = NULL;
Error *local_err = NULL;
bool writethrough, src_writethrough, image_opts = false,
skip_create = false, progress = false, tgt_image_opts = false;
int64_t ret = -EINVAL;
bool force_share = false;
bool explict_min_sparse = false;
bool bitmaps = false;
bool skip_broken = false;
int64_t rate_limit = 0;
ImgConvertState s = (ImgConvertState) {
/* Need at least 4k of zeros for sparse detection */
.min_sparse = 8,
.copy_range = false,
.buf_sectors = IO_BUF_SIZE / BDRV_SECTOR_SIZE,
.wr_in_order = true,
.num_coroutines = 8,
};
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},
{"salvage", no_argument, 0, OPTION_SALVAGE},
{"target-is-zero", no_argument, 0, OPTION_TARGET_IS_ZERO},
{"bitmaps", no_argument, 0, OPTION_BITMAPS},
{"skip-broken-bitmaps", no_argument, 0, OPTION_SKIP_BROKEN},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, ":hf:O:B:CcF:o:l:S:pt:T:qnm:WUr:",
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;
case 'f':
fmt = optarg;
break;
case 'O':
out_fmt = optarg;
break;
case 'B':
out_baseimg = optarg;
break;
case 'C':
s.copy_range = true;
break;
case 'c':
s.compressed = true;
break;
case 'F':
backing_fmt = optarg;
break;
case 'o':
if (accumulate_options(&options, optarg) < 0) {
goto fail_getopt;
}
break;
case 'l':
if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) {
sn_opts = qemu_opts_parse_noisily(&internal_snapshot_opts,
optarg, false);
if (!sn_opts) {
error_report("Failed in parsing snapshot param '%s'",
optarg);
goto fail_getopt;
}
} else {
snapshot_name = optarg;
}
break;
case 'S':
{
int64_t sval;
sval = cvtnum("buffer size for sparse output", optarg);
if (sval < 0) {
goto fail_getopt;
} else if (!QEMU_IS_ALIGNED(sval, BDRV_SECTOR_SIZE) ||
sval / BDRV_SECTOR_SIZE > MAX_BUF_SECTORS) {
error_report("Invalid buffer size for sparse output specified. "
"Valid sizes are multiples of %llu up to %llu. Select "
"0 to disable sparse detection (fully allocates output).",
BDRV_SECTOR_SIZE, MAX_BUF_SECTORS * BDRV_SECTOR_SIZE);
goto fail_getopt;
}
s.min_sparse = sval / BDRV_SECTOR_SIZE;
explict_min_sparse = true;
break;
}
case 'p':
progress = true;
break;
case 't':
cache = optarg;
break;
case 'T':
src_cache = optarg;
break;
case 'q':
s.quiet = true;
break;
case 'n':
skip_create = true;
break;
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 'r':
rate_limit = cvtnum("rate limit", optarg);
if (rate_limit < 0) {
goto fail_getopt;
}
break;
case OPTION_OBJECT:
user_creatable_process_cmdline(optarg);
break;
case OPTION_IMAGE_OPTS:
image_opts = true;
break;
case OPTION_SALVAGE:
s.salvage = true;
break;
case OPTION_TARGET_IMAGE_OPTS:
tgt_image_opts = true;
break;
case OPTION_TARGET_IS_ZERO:
/*
* The user asserting that the target is blank has the
* same effect as the target driver supporting zero
* initialisation.
*/
s.has_zero_init = true;
break;
case OPTION_BITMAPS:
bitmaps = true;
break;
case OPTION_SKIP_BROKEN:
skip_broken = true;
break;
}
}
if (!out_fmt && !tgt_image_opts) {
out_fmt = "raw";
}
if (skip_broken && !bitmaps) {
error_report("Use of --skip-broken-bitmaps requires --bitmaps");
goto fail_getopt;
}
if (s.compressed && s.copy_range) {
error_report("Cannot enable copy offloading when -c is used");
goto fail_getopt;
}
if (explict_min_sparse && s.copy_range) {
error_report("Cannot enable copy offloading when -S is used");
goto fail_getopt;
}
if (s.copy_range && s.salvage) {
error_report("Cannot use copy offloading in salvaging mode");
goto fail_getopt;
}
if (tgt_image_opts && !skip_create) {
error_report("--target-image-opts requires use of -n flag");
goto fail_getopt;
}
if (skip_create && options) {
error_report("-o has no effect when skipping image creation");
goto fail_getopt;
}
if (s.has_zero_init && !skip_create) {
error_report("--target-is-zero requires use of -n flag");
goto fail_getopt;
}
s.src_num = argc - optind - 1;
out_filename = s.src_num >= 1 ? argv[argc - 1] : NULL;
if (options && has_help_option(options)) {
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 (s.src_num < 1) {
error_report("Must specify image file name");
goto fail_getopt;
}
/* 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 fail_getopt;
}
/* Initialize before goto out */
if (s.quiet) {
progress = false;
}
qemu_progress_init(progress, 1.0);
qemu_progress_print(0, 100);
s.src = g_new0(BlockBackend *, s.src_num);
s.src_sectors = g_new(int64_t, s.src_num);
s.src_alignment = g_new(int, s.src_num);
for (bs_i = 0; bs_i < s.src_num; bs_i++) {
BlockDriverState *src_bs;
s.src[bs_i] = img_open(image_opts, argv[optind + bs_i],
fmt, src_flags, src_writethrough, s.quiet,
force_share);
if (!s.src[bs_i]) {
ret = -1;
goto out;
}
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(-s.src_sectors[bs_i]));
ret = -1;
goto out;
}
src_bs = blk_bs(s.src[bs_i]);
s.src_alignment[bs_i] = DIV_ROUND_UP(src_bs->bl.request_alignment,
BDRV_SECTOR_SIZE);
if (!bdrv_get_info(src_bs, &bdi)) {
s.src_alignment[bs_i] = MAX(s.src_alignment[bs_i],
bdi.cluster_size / BDRV_SECTOR_SIZE);
}
s.total_sectors += s.src_sectors[bs_i];
}
if (sn_opts) {
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 (s.src_num > 1) {
error_report("No support for concatenating multiple snapshot");
ret = -1;
goto out;
}
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: ");
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;
}
if (!drv->create_opts) {
error_report("Format driver '%s' does not support image creation",
drv->format_name);
ret = -1;
goto out;
}
if (!proto_drv->create_opts) {
error_report("Protocol driver '%s' does not support image creation",
proto_drv->format_name);
ret = -1;
goto out;
}
create_opts = qemu_opts_append(create_opts, drv->create_opts);
create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
opts = qemu_opts_create(create_opts, NULL, 0, &error_abort);
if (options) {
if (!qemu_opts_do_parse(opts, options, NULL, &local_err)) {
error_report_err(local_err);
ret = -1;
goto out;
}
}
qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
s.total_sectors * BDRV_SECTOR_SIZE, &error_abort);
ret = add_old_style_options(out_fmt, opts, out_baseimg, backing_fmt);
if (ret < 0) {
goto out;
}
}
/* Get backing file name if -o backing_file was used */
out_baseimg_param = qemu_opt_get(opts, BLOCK_OPT_BACKING_FILE);
if (out_baseimg_param) {
out_baseimg = out_baseimg_param;
}
s.target_has_backing = (bool) out_baseimg;
if (s.has_zero_init && s.target_has_backing) {
error_report("Cannot use --target-is-zero when the destination "
"image has a backing file");
goto out;
}
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;
}
if (out_baseimg_param) {
if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) {
error_report("Use of backing file requires explicit "
"backing format");
ret = -1;
goto out;
}
}
/* Check if compression is supported */
if (s.compressed) {
bool encryption =
qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT, false);
const char *encryptfmt =
qemu_opt_get(opts, BLOCK_OPT_ENCRYPT_FORMAT);
const char *preallocation =
qemu_opt_get(opts, BLOCK_OPT_PREALLOC);
if (drv && !block_driver_can_compress(drv)) {
error_report("Compression not supported for this file format");
ret = -1;
goto out;
}
if (encryption || encryptfmt) {
error_report("Compression and encryption not supported at "
"the same time");
ret = -1;
goto out;
}
if (preallocation
&& strcmp(preallocation, "off"))
{
error_report("Compression and preallocation not supported at "
"the same time");
ret = -1;
goto out;
}
}
/* Determine if bitmaps need copying */
if (bitmaps) {
if (s.src_num > 1) {
error_report("Copying bitmaps only possible with single source");
ret = -1;
goto out;
}
ret = convert_check_bitmaps(blk_bs(s.src[0]), skip_broken);
if (ret < 0) {
goto out;
}
}
/*
* The later open call will need any decryption secrets, and
* bdrv_create() will purge "opts", so extract them now before
* they are lost.
*/
if (!skip_create) {
open_opts = qdict_new();
qemu_opt_foreach(opts, img_add_key_secrets, open_opts, &error_abort);
/* Create the new image */
ret = bdrv_create(drv, out_filename, opts, &local_err);
if (ret < 0) {
error_reportf_err(local_err, "%s: error while converting %s: ",
out_filename, out_fmt);
goto out;
}
}
s.target_is_new = !skip_create;
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;
}
if (flags & BDRV_O_NOCACHE) {
/*
* If we open the target with O_DIRECT, it may be necessary to
* extend its size to align to the physical sector size.
*/
flags |= BDRV_O_RESIZE;
}
if (skip_create) {
s.target = img_open(tgt_image_opts, out_filename, out_fmt,
flags, writethrough, s.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_file(out_filename, open_opts, out_fmt,
flags, writethrough, s.quiet, false);
open_opts = NULL; /* blk_new_open will have freed it */
}
if (!s.target) {
ret = -1;
goto out;
}
out_bs = blk_bs(s.target);
if (bitmaps && !bdrv_supports_persistent_dirty_bitmap(out_bs)) {
error_report("Format driver '%s' does not support bitmaps",
out_bs->drv->format_name);
ret = -1;
goto out;
}
if (s.compressed && !block_driver_can_compress(out_bs->drv)) {
error_report("Compression not supported for this file format");
ret = -1;
goto out;
}
/* increase bufsectors from the default 4096 (2M) if opt_transfer
* or discard_alignment of the out_bs is greater. Limit to
* MAX_BUF_SECTORS as maximum which is currently 32768 (16MB). */
s.buf_sectors = MIN(MAX_BUF_SECTORS,
MAX(s.buf_sectors,
MAX(out_bs->bl.opt_transfer >> BDRV_SECTOR_BITS,
out_bs->bl.pdiscard_alignment >>
BDRV_SECTOR_BITS)));
/* try to align the write requests to the destination to avoid unnecessary
* RMW cycles. */
s.alignment = MAX(pow2floor(s.min_sparse),
DIV_ROUND_UP(out_bs->bl.request_alignment,
BDRV_SECTOR_SIZE));
assert(is_power_of_2(s.alignment));
if (skip_create) {
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 < s.total_sectors) {
error_report("output file is smaller than input file");
ret = -1;
goto out;
}
}
if (s.target_has_backing && s.target_is_new) {
/* Errors are treated as "backing length unknown" (which means
* s.target_backing_sectors has to be negative, which it will
* be automatically). The backing file length is used only
* for optimizations, so such a case is not fatal. */
s.target_backing_sectors =
bdrv_nb_sectors(bdrv_backing_chain_next(out_bs));
} else {
s.target_backing_sectors = -1;
}
ret = bdrv_get_info(out_bs, &bdi);
if (ret < 0) {
if (s.compressed) {
error_report("could not get block driver info");
goto out;
}
} else {
s.compressed = s.compressed || bdi.needs_compressed_writes;
s.cluster_sectors = bdi.cluster_size / BDRV_SECTOR_SIZE;
}
if (rate_limit) {
set_rate_limit(s.target, rate_limit);
}
ret = convert_do_copy(&s);
/* Now copy the bitmaps */
if (bitmaps && ret == 0) {
ret = convert_copy_bitmaps(blk_bs(s.src[0]), out_bs, skip_broken);
}
out:
if (!ret) {
qemu_progress_print(100, 0);
}
qemu_progress_end();
qemu_opts_del(opts);
qemu_opts_free(create_opts);
qobject_unref(open_opts);
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(s.src);
}
g_free(s.src_sectors);
g_free(s.src_alignment);
fail_getopt:
qemu_opts_del(sn_opts);
g_free(options);
return !!ret;
}