Message ID | 1372862071-28225-13-git-send-email-pbonzini@redhat.com |
---|---|
State | New |
Headers | show |
On Wed, 07/03 16:34, Paolo Bonzini wrote: > This command dumps the metadata of an entire chain, in either tabular or JSON > format. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > qemu-img-cmds.hx | 6 ++ > qemu-img.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 192 insertions(+) > > diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx > index 4ca7e95..acc3a1c 100644 > --- a/qemu-img-cmds.hx > +++ b/qemu-img-cmds.hx > @@ -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}] filename > +ETEXI > + > DEF("snapshot", img_snapshot, > "snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") > STEXI > diff --git a/qemu-img.c b/qemu-img.c > index 6461932..97392a5 100644 > --- a/qemu-img.c > +++ b/qemu-img.c > @@ -1768,6 +1768,192 @@ 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; > +} 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|BDRV_BLOCK_ZERO)) == BDRV_BLOCK_DATA) { > + printf("%lld %lld %d %lld\n", > + (long long) e->start, (long long) e->length, > + e->depth, (long long) e->offset); > + } Why %lld and explicit cast, not using PRId64? Is BDRV_BLOCK_DATA and BDRV_BLOCK_ZERO distinguishable here for the user? By offset? > + /* 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{ 'depth': %d, 'start': %lld, 'length': %lld, " > + "'zero': %s, 'data': %s", > + (e->start == 0 ? "[" : ",\n"), > + e->depth, (long long) e->start, (long long) e->length, > + (e->flags & BDRV_BLOCK_ZERO) ? "true" : "false", > + (e->flags & BDRV_BLOCK_DATA) ? "true" : "false"); > + if (e->flags & BDRV_BLOCK_OFFSET_VALID) { > + printf(", 'offset': %lld", (long long) e->offset); > + } > + putchar('}'); No indentation. > + > + if (!next) { > + printf("]\n"); > + } > + break; > + } > +} > + > +static int64_t get_block_status(BlockDriverState *bs, int64_t sector_num, > + int *nb_sectors, int *depth) > +{ > + int64_t ret; > + > + /* 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 (;;) { > + int orig_nb_sectors = *nb_sectors; > + > + if (bs == NULL) { > + return 0; > + } > + ret = bdrv_get_block_status(bs, sector_num, *nb_sectors, nb_sectors); > + if (ret < 0) { > + return ret; > + } > + > + if (ret & (BDRV_BLOCK_ZERO|BDRV_BLOCK_DATA)) { > + return ret; > + } > + if (!*nb_sectors) { > + /* Beyond the end of this image (while presumably within the > + * range of the BlockDriverState above this one). The extra > + * data is read as zeroes. > + */ > + *nb_sectors = orig_nb_sectors; > + return BDRV_BLOCK_ZERO; > + } > + > + bs = bs->backing_hd; > + (*depth)++; > + } > +} > + > +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; > + > + 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; > + } > + > + length = bdrv_getlength(bs); > + while (curr.start + curr.length < length) { > + int64_t nsectors_left, ret; > + int64_t sector_num; > + int n, depth, flags; > + > + /* Probe up to 1 G at a time. */ > + sector_num = (curr.start + curr.length) >> BDRV_SECTOR_BITS; > + nsectors_left = DIV_ROUND_UP(length, BDRV_SECTOR_SIZE) - sector_num; > + n = MAX(1 << (30 - BDRV_SECTOR_BITS), nsectors_left); It's MAX or MIN? > + ret = get_block_status(bs, sector_num, &n, &depth); > + > + if (ret < 0) { > + error_report("Could not read file metadata: %s", strerror(-ret)); > + return 1; > + } > + if ((ret & BDRV_BLOCK_DATA) && > + !(ret & BDRV_BLOCK_OFFSET_VALID)) { > + error_report("File contains external or compressed clusters."); > + return 1; > + } > + > + flags = ret & ~BDRV_BLOCK_OFFSET_MASK; > + ret &= BDRV_BLOCK_OFFSET_MASK; > + if (curr.length == 0 || curr.flags != flags || curr.depth != depth || > + ((curr.flags & BDRV_BLOCK_OFFSET_VALID) && > + curr.offset + curr.length != ret)) { A hard tab here. > + next.flags = flags; > + next.depth = depth; > + next.start = sector_num << BDRV_SECTOR_BITS; > + next.offset = ret; > + next.length = 0; > + if (curr.length > 0) { > + dump_map_entry(output_format, &curr, &next); > + } > + curr = next; > + } > + curr.length += n << BDRV_SECTOR_BITS; > + } > + > + dump_map_entry(output_format, &curr, NULL); > + return 0; > +} > + > #define SNAPSHOT_LIST 1 > #define SNAPSHOT_CREATE 2 > #define SNAPSHOT_APPLY 3 > -- > 1.8.2.1 > > >
Il 04/07/2013 07:34, Fam Zheng ha scritto: >> > + if ((e->flags & (BDRV_BLOCK_DATA|BDRV_BLOCK_ZERO)) == BDRV_BLOCK_DATA) { >> > + printf("%lld %lld %d %lld\n", >> > + (long long) e->start, (long long) e->length, >> > + e->depth, (long long) e->offset); >> > + } > Why %lld and explicit cast, not using PRId64? Will fix. > Is BDRV_BLOCK_DATA and BDRV_BLOCK_ZERO distinguishable here for the > user? By offset? I'm not sure I understand the question. Zero blocks are always omitted in the "human" format. Only non-zero blocks are listed. Paolo
On Thu, 07/04 10:16, Paolo Bonzini wrote: > Il 04/07/2013 07:34, Fam Zheng ha scritto: > >> > + if ((e->flags & (BDRV_BLOCK_DATA|BDRV_BLOCK_ZERO)) == BDRV_BLOCK_DATA) { > >> > + printf("%lld %lld %d %lld\n", > >> > + (long long) e->start, (long long) e->length, > >> > + e->depth, (long long) e->offset); > >> > + } > > Why %lld and explicit cast, not using PRId64? > > Will fix. > > > Is BDRV_BLOCK_DATA and BDRV_BLOCK_ZERO distinguishable here for the > > user? By offset? > > I'm not sure I understand the question. > > Zero blocks are always omitted in the "human" format. Only non-zero > blocks are listed. I missed this.
On Wed, Jul 03, 2013 at 04:34:26PM +0200, Paolo Bonzini wrote: > + if ((ret & BDRV_BLOCK_DATA) && > + !(ret & BDRV_BLOCK_OFFSET_VALID)) { > + error_report("File contains external or compressed clusters."); What does "external clusters" mean? Did you mean encrypted?
Il 16/07/2013 05:31, Stefan Hajnoczi ha scritto: > On Wed, Jul 03, 2013 at 04:34:26PM +0200, Paolo Bonzini wrote: >> > + if ((ret & BDRV_BLOCK_DATA) && >> > + !(ret & BDRV_BLOCK_OFFSET_VALID)) { >> > + error_report("File contains external or compressed clusters."); > What does "external clusters" mean? Did you mean encrypted? It means clusters that come from a separate file, as in vmdk extents. But yes, these are encrypted too. Paolo
On 07/03/2013 08:34 AM, Paolo Bonzini wrote: > This command dumps the metadata of an entire chain, in either tabular or JSON > format. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > qemu-img-cmds.hx | 6 ++ > qemu-img.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 192 insertions(+) > > + case OFORMAT_JSON: > + printf("%s{ 'depth': %d, 'start': %lld, 'length': %lld, " > + "'zero': %s, 'data': %s", > + (e->start == 0 ? "[" : ",\n"), > + e->depth, (long long) e->start, (long long) e->length, > + (e->flags & BDRV_BLOCK_ZERO) ? "true" : "false", > + (e->flags & BDRV_BLOCK_DATA) ? "true" : "false"); > + if (e->flags & BDRV_BLOCK_OFFSET_VALID) { > + printf(", 'offset': %lld", (long long) e->offset); > + } > + putchar('}'); Can we please get this format documented in qapi-schema.json, even if we aren't using qapi to generate it yet?
On Thu, Jul 18, 2013 at 02:04:31PM -0600, Eric Blake wrote: > On 07/03/2013 08:34 AM, Paolo Bonzini wrote: > > This command dumps the metadata of an entire chain, in either tabular or JSON > > format. > > > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > > --- > > qemu-img-cmds.hx | 6 ++ > > qemu-img.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > > 2 files changed, 192 insertions(+) > > > > > + case OFORMAT_JSON: > > + printf("%s{ 'depth': %d, 'start': %lld, 'length': %lld, " > > + "'zero': %s, 'data': %s", > > + (e->start == 0 ? "[" : ",\n"), > > + e->depth, (long long) e->start, (long long) e->length, > > + (e->flags & BDRV_BLOCK_ZERO) ? "true" : "false", > > + (e->flags & BDRV_BLOCK_DATA) ? "true" : "false"); > > + if (e->flags & BDRV_BLOCK_OFFSET_VALID) { > > + printf(", 'offset': %lld", (long long) e->offset); > > + } > > + putchar('}'); > > Can we please get this format documented in qapi-schema.json, even if we > aren't using qapi to generate it yet? Paolo: Please send a follow-up patch documenting the json schema, I've already merged this series. I was although thinking about qemu-iotests for qemu-img map, but it's tricky since the allocation is an internal detail of the image format. Perhaps a test case using raw? Stefan
Il 19/07/2013 06:48, Stefan Hajnoczi ha scritto: > On Thu, Jul 18, 2013 at 02:04:31PM -0600, Eric Blake wrote: >> On 07/03/2013 08:34 AM, Paolo Bonzini wrote: >>> This command dumps the metadata of an entire chain, in either tabular or JSON >>> format. >>> >>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> >>> --- >>> qemu-img-cmds.hx | 6 ++ >>> qemu-img.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ >>> 2 files changed, 192 insertions(+) >>> >> >>> + case OFORMAT_JSON: >>> + printf("%s{ 'depth': %d, 'start': %lld, 'length': %lld, " >>> + "'zero': %s, 'data': %s", >>> + (e->start == 0 ? "[" : ",\n"), >>> + e->depth, (long long) e->start, (long long) e->length, >>> + (e->flags & BDRV_BLOCK_ZERO) ? "true" : "false", >>> + (e->flags & BDRV_BLOCK_DATA) ? "true" : "false"); >>> + if (e->flags & BDRV_BLOCK_OFFSET_VALID) { >>> + printf(", 'offset': %lld", (long long) e->offset); >>> + } >>> + putchar('}'); >> >> Can we please get this format documented in qapi-schema.json, even if we >> aren't using qapi to generate it yet? > > Paolo: Please send a follow-up patch documenting the json schema, I've > already merged this series. > > I was although thinking about qemu-iotests for qemu-img map, but it's > tricky since the allocation is an internal detail of the image format. > Perhaps a test case using raw? If you have interesting cases with sparse images, even raw depends on how the underlying file system splits extents. I can do a simple test that ignores the 'offset' field. Paolo
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 4ca7e95..acc3a1c 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -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}] filename +ETEXI + DEF("snapshot", img_snapshot, "snapshot [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") STEXI diff --git a/qemu-img.c b/qemu-img.c index 6461932..97392a5 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -1768,6 +1768,192 @@ 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; +} 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|BDRV_BLOCK_ZERO)) == BDRV_BLOCK_DATA) { + printf("%lld %lld %d %lld\n", + (long long) e->start, (long long) e->length, + e->depth, (long long) e->offset); + } + /* 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{ 'depth': %d, 'start': %lld, 'length': %lld, " + "'zero': %s, 'data': %s", + (e->start == 0 ? "[" : ",\n"), + e->depth, (long long) e->start, (long long) e->length, + (e->flags & BDRV_BLOCK_ZERO) ? "true" : "false", + (e->flags & BDRV_BLOCK_DATA) ? "true" : "false"); + if (e->flags & BDRV_BLOCK_OFFSET_VALID) { + printf(", 'offset': %lld", (long long) e->offset); + } + putchar('}'); + + if (!next) { + printf("]\n"); + } + break; + } +} + +static int64_t get_block_status(BlockDriverState *bs, int64_t sector_num, + int *nb_sectors, int *depth) +{ + int64_t ret; + + /* 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 (;;) { + int orig_nb_sectors = *nb_sectors; + + if (bs == NULL) { + return 0; + } + ret = bdrv_get_block_status(bs, sector_num, *nb_sectors, nb_sectors); + if (ret < 0) { + return ret; + } + + if (ret & (BDRV_BLOCK_ZERO|BDRV_BLOCK_DATA)) { + return ret; + } + if (!*nb_sectors) { + /* Beyond the end of this image (while presumably within the + * range of the BlockDriverState above this one). The extra + * data is read as zeroes. + */ + *nb_sectors = orig_nb_sectors; + return BDRV_BLOCK_ZERO; + } + + bs = bs->backing_hd; + (*depth)++; + } +} + +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; + + 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; + } + + length = bdrv_getlength(bs); + while (curr.start + curr.length < length) { + int64_t nsectors_left, ret; + int64_t sector_num; + int n, depth, flags; + + /* Probe up to 1 G at a time. */ + sector_num = (curr.start + curr.length) >> BDRV_SECTOR_BITS; + nsectors_left = DIV_ROUND_UP(length, BDRV_SECTOR_SIZE) - sector_num; + n = MAX(1 << (30 - BDRV_SECTOR_BITS), nsectors_left); + ret = get_block_status(bs, sector_num, &n, &depth); + + if (ret < 0) { + error_report("Could not read file metadata: %s", strerror(-ret)); + return 1; + } + if ((ret & BDRV_BLOCK_DATA) && + !(ret & BDRV_BLOCK_OFFSET_VALID)) { + error_report("File contains external or compressed clusters."); + return 1; + } + + flags = ret & ~BDRV_BLOCK_OFFSET_MASK; + ret &= BDRV_BLOCK_OFFSET_MASK; + if (curr.length == 0 || curr.flags != flags || curr.depth != depth || + ((curr.flags & BDRV_BLOCK_OFFSET_VALID) && + curr.offset + curr.length != ret)) { + next.flags = flags; + next.depth = depth; + next.start = sector_num << BDRV_SECTOR_BITS; + next.offset = ret; + next.length = 0; + if (curr.length > 0) { + dump_map_entry(output_format, &curr, &next); + } + curr = next; + } + curr.length += n << BDRV_SECTOR_BITS; + } + + dump_map_entry(output_format, &curr, NULL); + return 0; +} + #define SNAPSHOT_LIST 1 #define SNAPSHOT_CREATE 2 #define SNAPSHOT_APPLY 3
This command dumps the metadata of an entire chain, in either tabular or JSON format. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- qemu-img-cmds.hx | 6 ++ qemu-img.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+)