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