static int img_convert()

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;
}