#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/module.h"
+#include "qemu/units.h"
#include "qom/object_interfaces.h"
#include "sysemu/sysemu.h"
#include "sysemu/block-backend.h"
OPTION_SIZE = 264,
OPTION_PREALLOCATION = 265,
OPTION_SHRINK = 266,
+ OPTION_SALVAGE = 267,
};
typedef enum OutputFormat {
{
va_list ap;
- error_printf("qemu-img: ");
-
va_start(ap, fmt);
- error_vprintf(fmt, ap);
+ error_vreport(fmt, ap);
va_end(ap);
- error_printf("\nTry 'qemu-img --help' for more information\n");
+ error_printf("Try 'qemu-img --help' for more information\n");
exit(EXIT_FAILURE);
}
" 'skip=N' skip N bs-sized blocks at the start of input\n";
printf("%s\nSupported formats:", help_msg);
- bdrv_iterate_format(format_print, NULL);
+ bdrv_iterate_format(format_print, NULL, false);
printf("\n\n" QEMU_HELP_BOTTOM "\n");
exit(EXIT_SUCCESS);
}
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) {
- 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;
- struct iovec iov;
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));
- iov.iov_base = buf;
- iov.iov_len = n << BDRV_SECTOR_BITS;
- qemu_iovec_init_external(&qiov, &iov, 1);
+ 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;
- struct iovec iov;
while (nb_sectors > 0) {
int n = nb_sectors;
(s->compressed &&
!buffer_is_zero(buf, n * BDRV_SECTOR_SIZE)))
{
- 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);
+ ret = blk_co_pwrite(s->target, sector_num << BDRV_SECTOR_BITS,
+ n << BDRV_SECTOR_BITS, buf, flags);
if (ret < 0) {
return ret;
}
}
ret = blk_co_pwrite_zeroes(s->target,
sector_num << BDRV_SECTOR_BITS,
- n << BDRV_SECTOR_BITS, 0);
+ n << BDRV_SECTOR_BITS,
+ BDRV_REQ_MAY_UNMAP);
if (ret < 0) {
return ret;
}
if (!s->has_zero_init && !s->target_has_backing &&
bdrv_can_write_zeroes_with_unmap(blk_bs(s->target)))
{
- ret = blk_make_zero(s->target, BDRV_REQ_MAY_UNMAP);
+ ret = blk_make_zero(s->target, BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK);
if (ret == 0) {
s->has_zero_init = true;
}
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;
}
/* 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) {
if (nb_sns <= 0)
return;
printf("Snapshot list:\n");
- bdrv_snapshot_dump(fprintf, stdout, NULL);
+ bdrv_snapshot_dump(NULL);
printf("\n");
for(i = 0; i < nb_sns; i++) {
sn = &sn_tab[i];
- bdrv_snapshot_dump(fprintf, stdout, sn);
+ bdrv_snapshot_dump(sn);
printf("\n");
}
g_free(sn_tab);
}
delim = true;
- bdrv_image_info_dump(fprintf, stdout, elem->value);
+ bdrv_image_info_dump(elem->value);
}
}
return 0;
}
-static void dump_map_entry(OutputFormat output_format, MapEntry *e,
- MapEntry *next)
+static int dump_map_entry(OutputFormat output_format, MapEntry *e,
+ MapEntry *next)
{
switch (output_format) {
case OFORMAT_HUMAN:
if (e->data && !e->has_offset) {
error_report("File contains external, encrypted or compressed clusters.");
- exit(1);
+ return -1;
}
if (e->data && !e->zero) {
printf("%#-16"PRIx64"%#-16"PRIx64"%#-16"PRIx64"%s\n",
}
break;
}
+ return 0;
}
static int get_block_status(BlockDriverState *bs, int64_t offset,
BlockDriverState *file;
bool has_offset;
int64_t map;
+ char *filename = NULL;
/* As an optimization, we could cache the current range of unallocated
* clusters in each file of the chain, and avoid querying the same
has_offset = !!(ret & BDRV_BLOCK_OFFSET_VALID);
+ if (file && has_offset) {
+ bdrv_refresh_filename(file);
+ filename = file->filename;
+ }
+
*e = (MapEntry) {
.start = offset,
.length = bytes,
.offset = map,
.has_offset = has_offset,
.depth = depth,
- .has_filename = file && has_offset,
- .filename = file && has_offset ? file->filename : NULL,
+ .has_filename = filename,
+ .filename = filename,
};
return 0;
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) {
}
if (curr.length > 0) {
- dump_map_entry(output_format, &curr, &next);
+ ret = dump_map_entry(output_format, &curr, &next);
+ if (ret < 0) {
+ goto out;
+ }
}
curr = next;
}
- dump_map_entry(output_format, &curr, NULL);
+ ret = dump_map_entry(output_format, &curr, NULL);
out:
blk_unref(blk);
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]) {
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 = g_malloc(PATH_MAX);
-
- bdrv_get_full_backing_filename_from_filename(overlay_filename,
- out_baseimg,
- out_real_path,
- PATH_MAX,
- &local_err);
+ 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;
- g_free(out_real_path);
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,
+ 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));
signal(SIGPIPE, SIG_IGN);
#endif
+ error_init(argv[0]);
module_call_init(MODULE_INIT_TRACE);
- error_set_progname(argv[0]);
qemu_init_exec_dir(argv[0]);
if (qemu_init_main_loop(&local_error)) {