qemu-img: add a "map" subcommand
This command dumps the metadata of an entire chain, in either tabular or JSON format. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
parent
f0ad5712d5
commit
4c93a13b5d
@ -45,6 +45,12 @@ STEXI
|
|||||||
@item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
|
@item info [-f @var{fmt}] [--output=@var{ofmt}] [--backing-chain] @var{filename}
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
|
DEF("map", img_map,
|
||||||
|
"map [-f fmt] [--output=ofmt] filename")
|
||||||
|
STEXI
|
||||||
|
@item map [-f @var{fmt}] [--output=@var{ofmt}] @var{filename}
|
||||||
|
ETEXI
|
||||||
|
|
||||||
DEF("snapshot", img_snapshot,
|
DEF("snapshot", img_snapshot,
|
||||||
"snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
|
"snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
|
||||||
STEXI
|
STEXI
|
||||||
|
191
qemu-img.c
191
qemu-img.c
@ -1801,6 +1801,197 @@ static int img_info(int argc, char **argv)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct MapEntry {
|
||||||
|
int flags;
|
||||||
|
int depth;
|
||||||
|
int64_t start;
|
||||||
|
int64_t length;
|
||||||
|
int64_t offset;
|
||||||
|
BlockDriverState *bs;
|
||||||
|
} MapEntry;
|
||||||
|
|
||||||
|
static void dump_map_entry(OutputFormat output_format, MapEntry *e,
|
||||||
|
MapEntry *next)
|
||||||
|
{
|
||||||
|
switch (output_format) {
|
||||||
|
case OFORMAT_HUMAN:
|
||||||
|
if ((e->flags & BDRV_BLOCK_DATA) &&
|
||||||
|
!(e->flags & BDRV_BLOCK_OFFSET_VALID)) {
|
||||||
|
error_report("File contains external, encrypted or compressed clusters.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if ((e->flags & (BDRV_BLOCK_DATA|BDRV_BLOCK_ZERO)) == BDRV_BLOCK_DATA) {
|
||||||
|
printf("%#-16"PRIx64"%#-16"PRIx64"%#-16"PRIx64"%s\n",
|
||||||
|
e->start, e->length, e->offset, e->bs->filename);
|
||||||
|
}
|
||||||
|
/* This format ignores the distinction between 0, ZERO and ZERO|DATA.
|
||||||
|
* Modify the flags here to allow more coalescing.
|
||||||
|
*/
|
||||||
|
if (next &&
|
||||||
|
(next->flags & (BDRV_BLOCK_DATA|BDRV_BLOCK_ZERO)) != BDRV_BLOCK_DATA) {
|
||||||
|
next->flags &= ~BDRV_BLOCK_DATA;
|
||||||
|
next->flags |= BDRV_BLOCK_ZERO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OFORMAT_JSON:
|
||||||
|
printf("%s{ \"start\": %"PRId64", \"length\": %"PRId64", \"depth\": %d,"
|
||||||
|
" \"zero\": %s, \"data\": %s",
|
||||||
|
(e->start == 0 ? "[" : ",\n"),
|
||||||
|
e->start, e->length, e->depth,
|
||||||
|
(e->flags & BDRV_BLOCK_ZERO) ? "true" : "false",
|
||||||
|
(e->flags & BDRV_BLOCK_DATA) ? "true" : "false");
|
||||||
|
if (e->flags & BDRV_BLOCK_OFFSET_VALID) {
|
||||||
|
printf(", 'offset': %"PRId64"", e->offset);
|
||||||
|
}
|
||||||
|
putchar('}');
|
||||||
|
|
||||||
|
if (!next) {
|
||||||
|
printf("]\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_block_status(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, MapEntry *e)
|
||||||
|
{
|
||||||
|
int64_t ret;
|
||||||
|
int depth;
|
||||||
|
|
||||||
|
/* As an optimization, we could cache the current range of unallocated
|
||||||
|
* clusters in each file of the chain, and avoid querying the same
|
||||||
|
* range repeatedly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
depth = 0;
|
||||||
|
for (;;) {
|
||||||
|
ret = bdrv_get_block_status(bs, sector_num, nb_sectors, &nb_sectors);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
assert(nb_sectors);
|
||||||
|
if (ret & (BDRV_BLOCK_ZERO|BDRV_BLOCK_DATA)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bs = bs->backing_hd;
|
||||||
|
if (bs == NULL) {
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
e->start = sector_num * BDRV_SECTOR_SIZE;
|
||||||
|
e->length = nb_sectors * BDRV_SECTOR_SIZE;
|
||||||
|
e->flags = ret & ~BDRV_BLOCK_OFFSET_MASK;
|
||||||
|
e->offset = ret & BDRV_BLOCK_OFFSET_MASK;
|
||||||
|
e->depth = depth;
|
||||||
|
e->bs = bs;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int img_map(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
OutputFormat output_format = OFORMAT_HUMAN;
|
||||||
|
BlockDriverState *bs;
|
||||||
|
const char *filename, *fmt, *output;
|
||||||
|
int64_t length;
|
||||||
|
MapEntry curr = { .length = 0 }, next;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
fmt = NULL;
|
||||||
|
output = NULL;
|
||||||
|
for (;;) {
|
||||||
|
int option_index = 0;
|
||||||
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"format", required_argument, 0, 'f'},
|
||||||
|
{"output", required_argument, 0, OPTION_OUTPUT},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
c = getopt_long(argc, argv, "f:h",
|
||||||
|
long_options, &option_index);
|
||||||
|
if (c == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (c) {
|
||||||
|
case '?':
|
||||||
|
case 'h':
|
||||||
|
help();
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
fmt = optarg;
|
||||||
|
break;
|
||||||
|
case OPTION_OUTPUT:
|
||||||
|
output = optarg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (optind >= argc) {
|
||||||
|
help();
|
||||||
|
}
|
||||||
|
filename = argv[optind++];
|
||||||
|
|
||||||
|
if (output && !strcmp(output, "json")) {
|
||||||
|
output_format = OFORMAT_JSON;
|
||||||
|
} else if (output && !strcmp(output, "human")) {
|
||||||
|
output_format = OFORMAT_HUMAN;
|
||||||
|
} else if (output) {
|
||||||
|
error_report("--output must be used with human or json as argument.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS, true, false);
|
||||||
|
if (!bs) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output_format == OFORMAT_HUMAN) {
|
||||||
|
printf("%-16s%-16s%-16s%s\n", "Offset", "Length", "Mapped to", "File");
|
||||||
|
}
|
||||||
|
|
||||||
|
length = bdrv_getlength(bs);
|
||||||
|
while (curr.start + curr.length < length) {
|
||||||
|
int64_t nsectors_left;
|
||||||
|
int64_t sector_num;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
sector_num = (curr.start + curr.length) >> BDRV_SECTOR_BITS;
|
||||||
|
|
||||||
|
/* Probe up to 1 GiB at a time. */
|
||||||
|
nsectors_left = DIV_ROUND_UP(length, BDRV_SECTOR_SIZE) - sector_num;
|
||||||
|
n = MIN(1 << (30 - BDRV_SECTOR_BITS), nsectors_left);
|
||||||
|
ret = get_block_status(bs, sector_num, n, &next);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
error_report("Could not read file metadata: %s", strerror(-ret));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr.length != 0 && curr.flags == next.flags &&
|
||||||
|
curr.depth == next.depth &&
|
||||||
|
((curr.flags & BDRV_BLOCK_OFFSET_VALID) == 0 ||
|
||||||
|
curr.offset + curr.length == next.offset)) {
|
||||||
|
curr.length += next.length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr.length > 0) {
|
||||||
|
dump_map_entry(output_format, &curr, &next);
|
||||||
|
}
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_map_entry(output_format, &curr, NULL);
|
||||||
|
|
||||||
|
out:
|
||||||
|
bdrv_unref(bs);
|
||||||
|
return ret < 0;
|
||||||
|
}
|
||||||
|
|
||||||
#define SNAPSHOT_LIST 1
|
#define SNAPSHOT_LIST 1
|
||||||
#define SNAPSHOT_CREATE 2
|
#define SNAPSHOT_CREATE 2
|
||||||
#define SNAPSHOT_APPLY 3
|
#define SNAPSHOT_APPLY 3
|
||||||
|
Loading…
Reference in New Issue
Block a user