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}
|
||||
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,
|
||||
"snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename")
|
||||
STEXI
|
||||
|
191
qemu-img.c
191
qemu-img.c
@ -1801,6 +1801,197 @@ static int img_info(int argc, char **argv)
|
||||
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_CREATE 2
|
||||
#define SNAPSHOT_APPLY 3
|
||||
|
Loading…
Reference in New Issue
Block a user