#include "qemu/osdep.h"
#include <getopt.h>
+#include "qemu-common.h"
#include "qemu-version.h"
#include "qapi/error.h"
#include "qapi/qapi-visit-block-core.h"
#include "qemu/option.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
#include "qom/object_interfaces.h"
-#include "sysemu/sysemu.h"
#include "sysemu/block-backend.h"
#include "block/block_int.h"
#include "block/blockjob.h"
OPTION_SIZE = 264,
OPTION_PREALLOCATION = 265,
OPTION_SHRINK = 266,
+ OPTION_SALVAGE = 267,
};
typedef enum OutputFormat {
create_opts = qemu_opts_append(create_opts, proto_drv->create_opts);
}
- printf("Supported options:\n");
+ if (filename) {
+ printf("Supported options:\n");
+ } else {
+ printf("Supported %s options:\n", fmt);
+ }
qemu_opts_print_help(create_opts, false);
qemu_opts_free(create_opts);
+
+ if (!filename) {
+ printf("\n"
+ "The protocol level may support further options.\n"
+ "Specify the target filename to include those options.\n");
+ }
+
return 0;
}
return res;
}
-#define IO_BUF_SIZE (2 * 1024 * 1024)
+#define IO_BUF_SIZE (2 * MiB)
/*
* Check if passed sectors are empty (not allocated or contain only 0 bytes)
int64_t target_backing_sectors; /* negative if unknown */
bool wr_in_order;
bool copy_range;
+ bool salvage;
+ bool quiet;
int min_sparse;
int alignment;
size_t cluster_sectors;
}
if (s->sector_next_status <= sector_num) {
- int64_t count = n * BDRV_SECTOR_SIZE;
+ uint64_t offset = (sector_num - src_cur_offset) * BDRV_SECTOR_SIZE;
+ int64_t count;
- if (s->target_has_backing) {
+ do {
+ count = n * BDRV_SECTOR_SIZE;
+
+ if (s->target_has_backing) {
+ ret = bdrv_block_status(blk_bs(s->src[src_cur]), offset,
+ count, &count, NULL, NULL);
+ } else {
+ ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL,
+ offset, count, &count, NULL,
+ NULL);
+ }
+
+ if (ret < 0) {
+ if (s->salvage) {
+ if (n == 1) {
+ if (!s->quiet) {
+ warn_report("error while reading block status at "
+ "offset %" PRIu64 ": %s", offset,
+ strerror(-ret));
+ }
+ /* Just try to read the data, then */
+ ret = BDRV_BLOCK_DATA;
+ count = BDRV_SECTOR_SIZE;
+ } else {
+ /* Retry on a shorter range */
+ n = DIV_ROUND_UP(n, 4);
+ }
+ } else {
+ error_report("error while reading block status at offset "
+ "%" PRIu64 ": %s", offset, strerror(-ret));
+ return ret;
+ }
+ }
+ } while (ret < 0);
- ret = bdrv_block_status(blk_bs(s->src[src_cur]),
- (sector_num - src_cur_offset) *
- BDRV_SECTOR_SIZE,
- count, &count, NULL, NULL);
- } else {
- ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL,
- (sector_num - src_cur_offset) *
- BDRV_SECTOR_SIZE,
- count, &count, NULL, NULL);
- }
- if (ret < 0) {
- error_report("error while reading block status of sector %" PRId64
- ": %s", sector_num, strerror(-ret));
- return ret;
- }
n = DIV_ROUND_UP(count, BDRV_SECTOR_SIZE);
if (ret & BDRV_BLOCK_ZERO) {
static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num,
int nb_sectors, uint8_t *buf)
{
+ uint64_t single_read_until = 0;
int n, ret;
- QEMUIOVector qiov;
assert(nb_sectors <= s->buf_sectors);
while (nb_sectors > 0) {
BlockBackend *blk;
int src_cur;
int64_t bs_sectors, src_cur_offset;
+ uint64_t 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
blk = s->src[src_cur];
bs_sectors = s->src_sectors[src_cur];
+ offset = (sector_num - src_cur_offset) << BDRV_SECTOR_BITS;
+
n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset));
- qemu_iovec_init_buf(&qiov, buf, n << BDRV_SECTOR_BITS);
+ if (single_read_until > offset) {
+ n = 1;
+ }
- ret = blk_co_preadv(
- blk, (sector_num - src_cur_offset) << BDRV_SECTOR_BITS,
- n << BDRV_SECTOR_BITS, &qiov, 0);
+ ret = blk_co_pread(blk, offset, n << BDRV_SECTOR_BITS, buf, 0);
if (ret < 0) {
- return ret;
+ if (s->salvage) {
+ if (n > 1) {
+ single_read_until = offset + (n << BDRV_SECTOR_BITS);
+ continue;
+ } else {
+ if (!s->quiet) {
+ warn_report("error while reading offset %" PRIu64
+ ": %s", offset, strerror(-ret));
+ }
+ memset(buf, 0, BDRV_SECTOR_SIZE);
+ }
+ } else {
+ return ret;
+ }
}
sector_num += n;
enum ImgConvertBlockStatus status)
{
int ret;
- QEMUIOVector qiov;
while (nb_sectors > 0) {
int n = nb_sectors;
(s->compressed &&
!buffer_is_zero(buf, n * BDRV_SECTOR_SIZE)))
{
- qemu_iovec_init_buf(&qiov, buf, n << BDRV_SECTOR_BITS);
-
- ret = blk_co_pwritev(s->target, sector_num << BDRV_SECTOR_BITS,
- n << BDRV_SECTOR_BITS, &qiov, flags);
+ ret = blk_co_pwrite(s->target, sector_num << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, buf, flags);
if (ret < 0) {
return ret;
}
QDict *open_opts = NULL;
char *options = NULL;
Error *local_err = NULL;
- bool writethrough, src_writethrough, quiet = false, image_opts = false,
+ bool writethrough, src_writethrough, image_opts = false,
skip_create = false, progress = false, tgt_image_opts = false;
int64_t ret = -EINVAL;
bool force_share = false;
{"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},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU",
src_cache = optarg;
break;
case 'q':
- quiet = true;
+ s.quiet = true;
break;
case 'n':
skip_create = true;
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;
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) {
+ warn_report("-o has no effect when skipping image creation");
+ warn_report("This will become an error in future QEMU versions.");
+ }
+
s.src_num = argc - optind - 1;
out_filename = s.src_num >= 1 ? argv[argc - 1] : NULL;
}
/* Initialize before goto out */
- if (quiet) {
+ if (s.quiet) {
progress = false;
}
qemu_progress_init(progress, 1.0);
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,
+ fmt, src_flags, src_writethrough, s.quiet,
force_share);
if (!s.src[bs_i]) {
ret = -1;
if (skip_create) {
s.target = img_open(tgt_image_opts, out_filename, out_fmt,
- flags, writethrough, quiet, false);
+ flags, writethrough, s.quiet, false);
} else {
/* TODO ultimately we should allow --target-image-opts
* to be used even when -n is not given.
* to allow filenames in option syntax
*/
s.target = img_open_file(out_filename, open_opts, out_fmt,
- flags, writethrough, quiet, false);
+ flags, writethrough, s.quiet, false);
open_opts = NULL; /* blk_new_open will have freed it */
}
if (!s.target) {
int64_t n;
/* Probe up to 1 GiB at a time. */
- n = MIN(1 << 30, length - offset);
+ n = MIN(1 * GiB, length - offset);
ret = get_block_status(bs, offset, n, &next);
if (ret < 0) {
BlockBackend *blk = NULL, *blk_old_backing = NULL, *blk_new_backing = NULL;
uint8_t *buf_old = NULL;
uint8_t *buf_new = NULL;
- BlockDriverState *bs = NULL;
+ BlockDriverState *bs = NULL, *prefix_chain_bs = NULL;
char *filename;
const char *fmt, *cache, *src_cache, *out_basefmt, *out_baseimg;
int c, flags, src_flags, ret;
/* For safe rebasing we need to compare old and new backing file */
if (!unsafe) {
- char backing_name[PATH_MAX];
QDict *options = NULL;
-
- if (bs->backing_format[0] != '\0') {
- options = qdict_new();
- qdict_put_str(options, "driver", bs->backing_format);
- }
-
- if (force_share) {
- if (!options) {
- options = qdict_new();
+ BlockDriverState *base_bs = backing_bs(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;
}
- 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);
- if (!blk_old_backing) {
- error_reportf_err(local_err,
- "Could not open old backing file '%s': ",
- backing_name);
- ret = -1;
- goto out;
+ } else {
+ blk_old_backing = NULL;
}
if (out_baseimg[0]) {
out_baseimg,
&local_err);
if (local_err) {
+ qobject_unref(options);
error_reportf_err(local_err,
"Could not resolve backing filename: ");
ret = -1;
goto out;
}
- 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;
+ /*
+ * 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;
+ }
}
}
}
*/
if (!unsafe) {
int64_t size;
- int64_t old_backing_size;
+ int64_t old_backing_size = 0;
int64_t new_backing_size = 0;
uint64_t offset;
int64_t n;
ret = -1;
goto out;
}
- old_backing_size = blk_getlength(blk_old_backing);
- if (old_backing_size < 0) {
- char backing_name[PATH_MAX];
+ 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;
+ 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);
}
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);
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(backing_bs(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;
if (compare_buffers(buf_old + written, buf_new + written,
n - written, &pnum))
{
- ret = blk_pwrite(blk, offset + written,
- buf_old + written, pnum, 0);
+ 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));