qemu-img: Add salvaging mode to convert

This adds a salvaging mode (--salvage) to qemu-img convert which ignores
read errors and treats the respective areas as containing only zeroes.
This can be used for instance to at least partially recover the data
from terminally corrupted qcow2 images.

Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Message-id: 20190507203508.18026-3-mreitz@redhat.com
Signed-off-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
Max Reitz 2019-05-07 22:35:03 +02:00
parent 3d96cb91d7
commit 8eaac025fb
3 changed files with 76 additions and 24 deletions

View File

@ -44,9 +44,9 @@ STEXI
ETEXI ETEXI
DEF("convert", img_convert, DEF("convert", img_convert,
"convert [--object objectdef] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-m num_coroutines] [-W] filename [filename2 [...]] output_filename") "convert [--object objectdef] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-m num_coroutines] [-W] [--salvage] filename [filename2 [...]] output_filename")
STEXI STEXI
@item convert [--object @var{objectdef}] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-B @var{backing_file}] [-o @var{options}] [-l @var{snapshot_param}] [-S @var{sparse_size}] [-m @var{num_coroutines}] [-W] @var{filename} [@var{filename2} [...]] @var{output_filename} @item convert [--object @var{objectdef}] [--image-opts] [--target-image-opts] [-U] [-C] [-c] [-p] [-q] [-n] [-f @var{fmt}] [-t @var{cache}] [-T @var{src_cache}] [-O @var{output_fmt}] [-B @var{backing_file}] [-o @var{options}] [-l @var{snapshot_param}] [-S @var{sparse_size}] [-m @var{num_coroutines}] [-W] [--salvage] @var{filename} [@var{filename2} [...]] @var{output_filename}
ETEXI ETEXI
DEF("create", img_create, DEF("create", img_create,

View File

@ -69,6 +69,7 @@ enum {
OPTION_SIZE = 264, OPTION_SIZE = 264,
OPTION_PREALLOCATION = 265, OPTION_PREALLOCATION = 265,
OPTION_SHRINK = 266, OPTION_SHRINK = 266,
OPTION_SALVAGE = 267,
}; };
typedef enum OutputFormat { typedef enum OutputFormat {
@ -1581,6 +1582,7 @@ typedef struct ImgConvertState {
int64_t target_backing_sectors; /* negative if unknown */ int64_t target_backing_sectors; /* negative if unknown */
bool wr_in_order; bool wr_in_order;
bool copy_range; bool copy_range;
bool salvage;
bool quiet; bool quiet;
int min_sparse; int min_sparse;
int alignment; int alignment;
@ -1628,25 +1630,44 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
} }
if (s->sector_next_status <= sector_num) { 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;
do {
count = n * BDRV_SECTOR_SIZE;
if (s->target_has_backing) { if (s->target_has_backing) {
ret = bdrv_block_status(blk_bs(s->src[src_cur]), offset,
ret = bdrv_block_status(blk_bs(s->src[src_cur]),
(sector_num - src_cur_offset) *
BDRV_SECTOR_SIZE,
count, &count, NULL, NULL); count, &count, NULL, NULL);
} else { } else {
ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL, ret = bdrv_block_status_above(blk_bs(s->src[src_cur]), NULL,
(sector_num - src_cur_offset) * offset, count, &count, NULL,
BDRV_SECTOR_SIZE, NULL);
count, &count, NULL, NULL);
} }
if (ret < 0) { if (ret < 0) {
error_report("error while reading block status of sector %" PRId64 if (s->salvage) {
": %s", sector_num, strerror(-ret)); 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; return ret;
} }
}
} while (ret < 0);
n = DIV_ROUND_UP(count, BDRV_SECTOR_SIZE); n = DIV_ROUND_UP(count, BDRV_SECTOR_SIZE);
if (ret & BDRV_BLOCK_ZERO) { if (ret & BDRV_BLOCK_ZERO) {
@ -1683,6 +1704,7 @@ static int convert_iteration_sectors(ImgConvertState *s, int64_t sector_num)
static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num, static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num,
int nb_sectors, uint8_t *buf) int nb_sectors, uint8_t *buf)
{ {
uint64_t single_read_until = 0;
int n, ret; int n, ret;
assert(nb_sectors <= s->buf_sectors); assert(nb_sectors <= s->buf_sectors);
@ -1690,6 +1712,7 @@ static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num,
BlockBackend *blk; BlockBackend *blk;
int src_cur; int src_cur;
int64_t bs_sectors, src_cur_offset; int64_t bs_sectors, src_cur_offset;
uint64_t offset;
/* In the case of compression with multiple source files, we can get a /* 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 * nb_sectors that spreads into the next part. So we must be able to
@ -1698,14 +1721,30 @@ static int coroutine_fn convert_co_read(ImgConvertState *s, int64_t sector_num,
blk = s->src[src_cur]; blk = s->src[src_cur];
bs_sectors = s->src_sectors[src_cur]; bs_sectors = s->src_sectors[src_cur];
n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset)); offset = (sector_num - src_cur_offset) << BDRV_SECTOR_BITS;
ret = blk_co_pread( n = MIN(nb_sectors, bs_sectors - (sector_num - src_cur_offset));
blk, (sector_num - src_cur_offset) << BDRV_SECTOR_BITS, if (single_read_until > offset) {
n << BDRV_SECTOR_BITS, buf, 0); n = 1;
}
ret = blk_co_pread(blk, offset, n << BDRV_SECTOR_BITS, buf, 0);
if (ret < 0) { if (ret < 0) {
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; return ret;
} }
}
sector_num += n; sector_num += n;
nb_sectors -= n; nb_sectors -= n;
@ -2035,6 +2074,7 @@ static int img_convert(int argc, char **argv)
{"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
{"force-share", no_argument, 0, 'U'}, {"force-share", no_argument, 0, 'U'},
{"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS}, {"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS},
{"salvage", no_argument, 0, OPTION_SALVAGE},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU", c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WU",
@ -2152,6 +2192,9 @@ static int img_convert(int argc, char **argv)
case OPTION_IMAGE_OPTS: case OPTION_IMAGE_OPTS:
image_opts = true; image_opts = true;
break; break;
case OPTION_SALVAGE:
s.salvage = true;
break;
case OPTION_TARGET_IMAGE_OPTS: case OPTION_TARGET_IMAGE_OPTS:
tgt_image_opts = true; tgt_image_opts = true;
break; break;
@ -2178,6 +2221,11 @@ static int img_convert(int argc, char **argv)
goto fail_getopt; 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) { if (tgt_image_opts && !skip_create) {
error_report("--target-image-opts requires use of -n flag"); error_report("--target-image-opts requires use of -n flag");
goto fail_getopt; goto fail_getopt;

View File

@ -175,6 +175,10 @@ improve performance if the data is remote, such as with NFS or iSCSI backends,
but will not automatically sparsify zero sectors, and may result in a fully but will not automatically sparsify zero sectors, and may result in a fully
allocated target image depending on the host support for getting allocation allocated target image depending on the host support for getting allocation
information. information.
@item --salvage
Try to ignore I/O errors when reading. Unless in quiet mode (@code{-q}), errors
will still be printed. Areas that cannot be read from the source will be
treated as containing only zeroes.
@end table @end table
Parameters to dd subcommand: Parameters to dd subcommand: