static int img_rebase()

in qemu-img.c [3437:3855]


static int img_rebase(int argc, char **argv)
{
    BlockBackend *blk = NULL, *blk_old_backing = NULL, *blk_new_backing = NULL;
    uint8_t *buf_old = NULL;
    uint8_t *buf_new = NULL;
    BlockDriverState *bs = NULL, *prefix_chain_bs = NULL;
    BlockDriverState *unfiltered_bs;
    char *filename;
    const char *fmt, *cache, *src_cache, *out_basefmt, *out_baseimg;
    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;
    bool image_opts = false;

    /* Parse commandline parameters */
    fmt = NULL;
    cache = BDRV_DEFAULT_CACHE;
    src_cache = BDRV_DEFAULT_CACHE;
    out_baseimg = NULL;
    out_basefmt = NULL;
    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'},
            {0, 0, 0, 0}
        };
        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;
        case 'f':
            fmt = optarg;
            break;
        case 'F':
            out_basefmt = optarg;
            break;
        case 'b':
            out_baseimg = optarg;
            break;
        case 'u':
            unsafe = 1;
            break;
        case 'p':
            progress = 1;
            break;
        case 't':
            cache = optarg;
            break;
        case 'T':
            src_cache = optarg;
            break;
        case 'q':
            quiet = true;
            break;
        case OPTION_OBJECT:
            user_creatable_process_cmdline(optarg);
            break;
        case OPTION_IMAGE_OPTS:
            image_opts = true;
            break;
        case 'U':
            force_share = true;
            break;
        }
    }

    if (quiet) {
        progress = 0;
    }

    if (optind != argc - 1) {
        error_exit("Expecting one image file name");
    }
    if (!unsafe && !out_baseimg) {
        error_exit("Must specify backing file (-b) or use unsafe mode (-u)");
    }
    filename = argv[optind++];

    qemu_progress_init(progress, 2.0);
    qemu_progress_print(0, 100);

    flags = BDRV_O_RDWR | (unsafe ? BDRV_O_NO_BACKING : 0);
    ret = bdrv_parse_cache_mode(cache, &flags, &writethrough);
    if (ret < 0) {
        error_report("Invalid cache option: %s", cache);
        goto out;
    }

    src_flags = 0;
    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;
    }

    /* The source files are opened read-only, don't care about WCE */
    assert((src_flags & BDRV_O_RDWR) == 0);
    (void) src_writethrough;

    /*
     * Open the images.
     *
     * 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,
                   false);
    if (!blk) {
        ret = -1;
        goto out;
    }
    bs = blk_bs(blk);

    unfiltered_bs = bdrv_skip_filters(bs);

    if (out_basefmt != NULL) {
        if (bdrv_find_format(out_basefmt) == NULL) {
            error_report("Invalid format name: '%s'", out_basefmt);
            ret = -1;
            goto out;
        }
    }

    /* For safe rebasing we need to compare old and new backing file */
    if (!unsafe) {
        QDict *options = NULL;
        BlockDriverState *base_bs = bdrv_cow_bs(unfiltered_bs);

        if (base_bs) {
            blk_old_backing = blk_new(qemu_get_aio_context(),
                                      BLK_PERM_CONSISTENT_READ,
                                      BLK_PERM_ALL);
            ret = blk_insert_bs(blk_old_backing, base_bs,
                                &local_err);
            if (ret < 0) {
                error_reportf_err(local_err,
                                  "Could not reuse old backing file '%s': ",
                                  base_bs->filename);
                goto out;
            }
        } else {
            blk_old_backing = NULL;
        }

        if (out_baseimg[0]) {
            const char *overlay_filename;
            char *out_real_path;

            options = qdict_new();
            if (out_basefmt) {
                qdict_put_str(options, "driver", out_basefmt);
            }
            if (force_share) {
                qdict_put_bool(options, BDRV_OPT_FORCE_SHARE, true);
            }

            bdrv_refresh_filename(bs);
            overlay_filename = bs->exact_filename[0] ? bs->exact_filename
                                                     : bs->filename;
            out_real_path =
                bdrv_get_full_backing_filename_from_filename(overlay_filename,
                                                             out_baseimg,
                                                             &local_err);
            if (local_err) {
                qobject_unref(options);
                error_reportf_err(local_err,
                                  "Could not resolve backing filename: ");
                ret = -1;
                goto out;
            }

            /*
             * Find out whether we rebase an image on top of a previous image
             * in its chain.
             */
            prefix_chain_bs = bdrv_find_backing_image(bs, out_real_path);
            if (prefix_chain_bs) {
                qobject_unref(options);
                g_free(out_real_path);

                blk_new_backing = blk_new(qemu_get_aio_context(),
                                          BLK_PERM_CONSISTENT_READ,
                                          BLK_PERM_ALL);
                ret = blk_insert_bs(blk_new_backing, prefix_chain_bs,
                                    &local_err);
                if (ret < 0) {
                    error_reportf_err(local_err,
                                      "Could not reuse backing file '%s': ",
                                      out_baseimg);
                    goto out;
                }
            } else {
                blk_new_backing = blk_new_open(out_real_path, NULL,
                                               options, src_flags, &local_err);
                g_free(out_real_path);
                if (!blk_new_backing) {
                    error_reportf_err(local_err,
                                      "Could not open new backing file '%s': ",
                                      out_baseimg);
                    ret = -1;
                    goto out;
                }
            }
        }
    }

    /*
     * Check each unallocated cluster in the COW file. If it is unallocated,
     * accesses go to the backing file. We must therefore compare this cluster
     * in the old and new backing file, and if they differ we need to copy it
     * from the old backing file into the COW file.
     *
     * If qemu-img crashes during this step, no harm is done. The content of
     * the image is the same as the original one at any time.
     */
    if (!unsafe) {
        int64_t size;
        int64_t old_backing_size = 0;
        int64_t new_backing_size = 0;
        uint64_t offset;
        int64_t n;
        float local_progress = 0;

        buf_old = blk_blockalign(blk, IO_BUF_SIZE);
        buf_new = blk_blockalign(blk, IO_BUF_SIZE);

        size = blk_getlength(blk);
        if (size < 0) {
            error_report("Could not get size of '%s': %s",
                         filename, strerror(-size));
            ret = -1;
            goto out;
        }
        if (blk_old_backing) {
            old_backing_size = blk_getlength(blk_old_backing);
            if (old_backing_size < 0) {
                char backing_name[PATH_MAX];

                bdrv_get_backing_filename(bs, backing_name,
                                          sizeof(backing_name));
                error_report("Could not get size of '%s': %s",
                             backing_name, strerror(-old_backing_size));
                ret = -1;
                goto out;
            }
        }
        if (blk_new_backing) {
            new_backing_size = blk_getlength(blk_new_backing);
            if (new_backing_size < 0) {
                error_report("Could not get size of '%s': %s",
                             out_baseimg, strerror(-new_backing_size));
                ret = -1;
                goto out;
            }
        }

        if (size != 0) {
            local_progress = (float)100 / (size / MIN(size, IO_BUF_SIZE));
        }

        for (offset = 0; offset < size; offset += n) {
            bool buf_old_is_zero = false;

            /* How many bytes can we handle with the next read? */
            n = MIN(IO_BUF_SIZE, size - offset);

            /* If the cluster is allocated, we don't need to take action */
            ret = bdrv_is_allocated(unfiltered_bs, offset, n, &n);
            if (ret < 0) {
                error_report("error while reading image metadata: %s",
                             strerror(-ret));
                goto out;
            }
            if (ret) {
                continue;
            }

            if (prefix_chain_bs) {
                /*
                 * If cluster wasn't changed since prefix_chain, we don't need
                 * to take action
                 */
                ret = bdrv_is_allocated_above(bdrv_cow_bs(unfiltered_bs),
                                              prefix_chain_bs, false,
                                              offset, n, &n);
                if (ret < 0) {
                    error_report("error while reading image metadata: %s",
                                 strerror(-ret));
                    goto out;
                }
                if (!ret) {
                    continue;
                }
            }

            /*
             * Read old and new backing file and take into consideration that
             * backing files may be smaller than the COW image.
             */
            if (offset >= old_backing_size) {
                memset(buf_old, 0, n);
                buf_old_is_zero = true;
            } else {
                if (offset + n > old_backing_size) {
                    n = old_backing_size - offset;
                }

                ret = blk_pread(blk_old_backing, offset, buf_old, n);
                if (ret < 0) {
                    error_report("error while reading from old backing file");
                    goto out;
                }
            }

            if (offset >= new_backing_size || !blk_new_backing) {
                memset(buf_new, 0, n);
            } else {
                if (offset + n > new_backing_size) {
                    n = new_backing_size - offset;
                }

                ret = blk_pread(blk_new_backing, offset, buf_new, n);
                if (ret < 0) {
                    error_report("error while reading from new backing file");
                    goto out;
                }
            }

            /* If they differ, we need to write to the COW file */
            uint64_t written = 0;

            while (written < n) {
                int64_t pnum;

                if (compare_buffers(buf_old + written, buf_new + written,
                                    n - written, &pnum))
                {
                    if (buf_old_is_zero) {
                        ret = blk_pwrite_zeroes(blk, offset + written, pnum, 0);
                    } else {
                        ret = blk_pwrite(blk, offset + written,
                                         buf_old + written, pnum, 0);
                    }
                    if (ret < 0) {
                        error_report("Error while writing to COW image: %s",
                            strerror(-ret));
                        goto out;
                    }
                }

                written += pnum;
            }
            qemu_progress_print(local_progress, 100);
        }
    }

    /*
     * Change the backing file. All clusters that are different from the old
     * backing file are overwritten in the COW file now, so the visible content
     * doesn't change when we switch the backing file.
     */
    if (out_baseimg && *out_baseimg) {
        ret = bdrv_change_backing_file(unfiltered_bs, out_baseimg, out_basefmt,
                                       true);
    } else {
        ret = bdrv_change_backing_file(unfiltered_bs, NULL, NULL, false);
    }

    if (ret == -ENOSPC) {
        error_report("Could not change the backing file to '%s': No "
                     "space left in the file header", out_baseimg);
    } else if (ret == -EINVAL && out_baseimg && !out_basefmt) {
        error_report("Could not change the backing file to '%s': backing "
                     "format must be specified", out_baseimg);
    } else if (ret < 0) {
        error_report("Could not change the backing file to '%s': %s",
            out_baseimg, strerror(-ret));
    }

    qemu_progress_print(100, 0);
    /*
     * TODO At this point it is possible to check if any clusters that are
     * allocated in the COW file are the same in the backing file. If so, they
     * could be dropped from the COW file. Don't do this before switching the
     * backing file, in case of a crash this would lead to corruption.
     */
out:
    qemu_progress_end();
    /* Cleanup */
    if (!unsafe) {
        blk_unref(blk_old_backing);
        blk_unref(blk_new_backing);
    }
    qemu_vfree(buf_old);
    qemu_vfree(buf_new);

    blk_unref(blk);
    if (ret) {
        return 1;
    }
    return 0;
}