X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=qemu-img.c;h=85d3740b9ca9af1e536f06fef45835b670218bd0;hb=57105f7480f7e2645dee2806e8152868bde60acb;hp=94a31ad9f0aecaa3761d342e75b8594157808f0f;hpb=20caf0f766b48fbbf718eb20aaddb7ffa356a21f;p=qemu.git diff --git a/qemu-img.c b/qemu-img.c index 94a31ad9f..85d3740b9 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -21,12 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include "qapi-visit.h" +#include "qapi/qmp-output-visitor.h" +#include "qapi/qmp/qjson.h" #include "qemu-common.h" -#include "qemu-option.h" -#include "qemu-error.h" -#include "osdep.h" -#include "sysemu.h" -#include "block_int.h" +#include "qemu/option.h" +#include "qemu/error-report.h" +#include "qemu/osdep.h" +#include "sysemu/sysemu.h" +#include "block/block_int.h" +#include #include #ifdef _WIN32 @@ -84,12 +88,13 @@ static void help(void) " '-p' show progress of command (only certain commands)\n" " '-S' indicates the consecutive number of bytes that must contain only zeros\n" " for qemu-img to create a sparse image during conversion\n" + " '--output' takes the format in which the output must be done (human or json)\n" "\n" "Parameters to check subcommand:\n" " '-r' tries to repair any inconsistencies that are found during the check.\n" " '-r leaks' repairs only cluster leaks, whereas '-r all' fixes all\n" " kinds of errors, with a higher risk of choosing the wrong fix or\n" - " hiding corruption that has already occured.\n" + " hiding corruption that has already occurred.\n" "\n" "Parameters to snapshot subcommand:\n" " 'snapshot' is the name of the snapshot to create, apply or delete\n" @@ -221,7 +226,8 @@ static int print_block_option_help(const char *filename, const char *fmt) static BlockDriverState *bdrv_new_open(const char *filename, const char *fmt, - int flags) + int flags, + bool require_io) { BlockDriverState *bs; BlockDriver *drv; @@ -246,7 +252,7 @@ static BlockDriverState *bdrv_new_open(const char *filename, goto fail; } - if (bdrv_is_encrypted(bs)) { + if (bdrv_is_encrypted(bs) && require_io) { printf("Disk image '%s' is encrypted.\n", filename); if (read_password(password, sizeof(password)) < 0) { error_report("No password given"); @@ -288,13 +294,14 @@ static int add_old_style_options(const char *fmt, QEMUOptionParameter *list, static int img_create(int argc, char **argv) { - int c, ret = 0; + int c; uint64_t img_size = -1; const char *fmt = "raw"; const char *base_fmt = NULL; const char *filename; const char *base_filename = NULL; char *options = NULL; + Error *local_err = NULL; for(;;) { c = getopt(argc, argv, "F:b:f:he6o:"); @@ -341,26 +348,30 @@ static int img_create(int argc, char **argv) char *end; sval = strtosz_suffix(argv[optind++], &end, STRTOSZ_DEFSUFFIX_B); if (sval < 0 || *end) { - error_report("Invalid image size specified! You may use k, M, G or " - "T suffixes for "); - error_report("kilobytes, megabytes, gigabytes and terabytes."); - ret = -1; - goto out; + if (sval == -ERANGE) { + error_report("Image size must be less than 8 EiB!"); + } else { + error_report("Invalid image size specified! You may use k, M, " + "G or T suffixes for "); + error_report("kilobytes, megabytes, gigabytes and terabytes."); + } + return 1; } img_size = (uint64_t)sval; } if (options && is_help_option(options)) { - ret = print_block_option_help(filename, fmt); - goto out; + return print_block_option_help(filename, fmt); } - ret = bdrv_img_create(filename, fmt, base_filename, base_fmt, - options, img_size, BDRV_O_FLAGS); -out: - if (ret) { + bdrv_img_create(filename, fmt, base_filename, base_fmt, + options, img_size, BDRV_O_FLAGS, &local_err); + if (error_is_set(&local_err)) { + error_report("%s", error_get_pretty(local_err)); + error_free(local_err); return 1; } + return 0; } @@ -379,7 +390,7 @@ static int img_check(int argc, char **argv) BlockDriverState *bs; BdrvCheckResult result; int fix = 0; - int flags = BDRV_O_FLAGS; + int flags = BDRV_O_FLAGS | BDRV_O_CHECK; fmt = NULL; for(;;) { @@ -413,7 +424,7 @@ static int img_check(int argc, char **argv) } filename = argv[optind++]; - bs = bdrv_new_open(filename, fmt, flags); + bs = bdrv_new_open(filename, fmt, flags, true); if (!bs) { return 1; } @@ -520,7 +531,7 @@ static int img_commit(int argc, char **argv) return -1; } - bs = bdrv_new_open(filename, fmt, flags); + bs = bdrv_new_open(filename, fmt, flags, true); if (!bs) { return 1; } @@ -668,7 +679,7 @@ static int img_convert(int argc, char **argv) QEMUOptionParameter *out_baseimg_param; char *options = NULL; const char *snapshot_name = NULL; - float local_progress; + float local_progress = 0; int min_sparse = 8; /* Need at least 4k of zeros for sparse detection */ fmt = NULL; @@ -762,7 +773,7 @@ static int img_convert(int argc, char **argv) total_sectors = 0; for (bs_i = 0; bs_i < bs_n; bs_i++) { - bs[bs_i] = bdrv_new_open(argv[optind + bs_i], fmt, BDRV_O_FLAGS); + bs[bs_i] = bdrv_new_open(argv[optind + bs_i], fmt, BDRV_O_FLAGS, true); if (!bs[bs_i]) { error_report("Could not open '%s'", argv[optind + bs_i]); ret = -1; @@ -881,7 +892,7 @@ static int img_convert(int argc, char **argv) return -1; } - out_bs = bdrv_new_open(out_filename, out_fmt, flags); + out_bs = bdrv_new_open(out_filename, out_fmt, flags, true); if (!out_bs) { ret = -1; goto out; @@ -908,8 +919,10 @@ static int img_convert(int argc, char **argv) sector_num = 0; nb_sectors = total_sectors; - local_progress = (float)100 / - (nb_sectors / MIN(nb_sectors, cluster_sectors)); + if (nb_sectors != 0) { + local_progress = (float)100 / + (nb_sectors / MIN(nb_sectors, cluster_sectors)); + } for(;;) { int64_t bs_num; @@ -980,8 +993,10 @@ static int img_convert(int argc, char **argv) sector_num = 0; // total number of sectors converted so far nb_sectors = total_sectors - sector_num; - local_progress = (float)100 / - (nb_sectors / MIN(nb_sectors, IO_BUF_SIZE / 512)); + if (nb_sectors != 0) { + local_progress = (float)100 / + (nb_sectors / MIN(nb_sectors, IO_BUF_SIZE / 512)); + } for(;;) { nb_sectors = total_sectors - sector_num; @@ -1102,21 +1117,312 @@ static void dump_snapshots(BlockDriverState *bs) g_free(sn_tab); } -static int img_info(int argc, char **argv) +static void dump_json_image_info_list(ImageInfoList *list) +{ + Error *errp = NULL; + QString *str; + QmpOutputVisitor *ov = qmp_output_visitor_new(); + QObject *obj; + visit_type_ImageInfoList(qmp_output_get_visitor(ov), + &list, NULL, &errp); + obj = qmp_output_get_qobject(ov); + str = qobject_to_json_pretty(obj); + assert(str != NULL); + printf("%s\n", qstring_get_str(str)); + qobject_decref(obj); + qmp_output_visitor_cleanup(ov); + QDECREF(str); +} + +static void collect_snapshots(BlockDriverState *bs , ImageInfo *info) +{ + int i, sn_count; + QEMUSnapshotInfo *sn_tab = NULL; + SnapshotInfoList *info_list, *cur_item = NULL; + sn_count = bdrv_snapshot_list(bs, &sn_tab); + + for (i = 0; i < sn_count; i++) { + info->has_snapshots = true; + info_list = g_new0(SnapshotInfoList, 1); + + info_list->value = g_new0(SnapshotInfo, 1); + info_list->value->id = g_strdup(sn_tab[i].id_str); + info_list->value->name = g_strdup(sn_tab[i].name); + info_list->value->vm_state_size = sn_tab[i].vm_state_size; + info_list->value->date_sec = sn_tab[i].date_sec; + info_list->value->date_nsec = sn_tab[i].date_nsec; + info_list->value->vm_clock_sec = sn_tab[i].vm_clock_nsec / 1000000000; + info_list->value->vm_clock_nsec = sn_tab[i].vm_clock_nsec % 1000000000; + + /* XXX: waiting for the qapi to support qemu-queue.h types */ + if (!cur_item) { + info->snapshots = cur_item = info_list; + } else { + cur_item->next = info_list; + cur_item = info_list; + } + + } + + g_free(sn_tab); +} + +static void dump_json_image_info(ImageInfo *info) +{ + Error *errp = NULL; + QString *str; + QmpOutputVisitor *ov = qmp_output_visitor_new(); + QObject *obj; + visit_type_ImageInfo(qmp_output_get_visitor(ov), + &info, NULL, &errp); + obj = qmp_output_get_qobject(ov); + str = qobject_to_json_pretty(obj); + assert(str != NULL); + printf("%s\n", qstring_get_str(str)); + qobject_decref(obj); + qmp_output_visitor_cleanup(ov); + QDECREF(str); +} + +static void collect_image_info(BlockDriverState *bs, + ImageInfo *info, + const char *filename, + const char *fmt) { - int c; - const char *filename, *fmt; - BlockDriverState *bs; - char size_buf[128], dsize_buf[128]; uint64_t total_sectors; - int64_t allocated_size; char backing_filename[1024]; char backing_filename2[1024]; BlockDriverInfo bdi; + bdrv_get_geometry(bs, &total_sectors); + + info->filename = g_strdup(filename); + info->format = g_strdup(bdrv_get_format_name(bs)); + info->virtual_size = total_sectors * 512; + info->actual_size = bdrv_get_allocated_file_size(bs); + info->has_actual_size = info->actual_size >= 0; + if (bdrv_is_encrypted(bs)) { + info->encrypted = true; + info->has_encrypted = true; + } + if (bdrv_get_info(bs, &bdi) >= 0) { + if (bdi.cluster_size != 0) { + info->cluster_size = bdi.cluster_size; + info->has_cluster_size = true; + } + info->dirty_flag = bdi.is_dirty; + info->has_dirty_flag = true; + } + bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename)); + if (backing_filename[0] != '\0') { + info->backing_filename = g_strdup(backing_filename); + info->has_backing_filename = true; + bdrv_get_full_backing_filename(bs, backing_filename2, + sizeof(backing_filename2)); + + if (strcmp(backing_filename, backing_filename2) != 0) { + info->full_backing_filename = + g_strdup(backing_filename2); + info->has_full_backing_filename = true; + } + + if (bs->backing_format[0]) { + info->backing_filename_format = g_strdup(bs->backing_format); + info->has_backing_filename_format = true; + } + } +} + +static void dump_human_image_info(ImageInfo *info) +{ + char size_buf[128], dsize_buf[128]; + if (!info->has_actual_size) { + snprintf(dsize_buf, sizeof(dsize_buf), "unavailable"); + } else { + get_human_readable_size(dsize_buf, sizeof(dsize_buf), + info->actual_size); + } + get_human_readable_size(size_buf, sizeof(size_buf), info->virtual_size); + printf("image: %s\n" + "file format: %s\n" + "virtual size: %s (%" PRId64 " bytes)\n" + "disk size: %s\n", + info->filename, info->format, size_buf, + info->virtual_size, + dsize_buf); + + if (info->has_encrypted && info->encrypted) { + printf("encrypted: yes\n"); + } + + if (info->has_cluster_size) { + printf("cluster_size: %" PRId64 "\n", info->cluster_size); + } + + if (info->has_dirty_flag && info->dirty_flag) { + printf("cleanly shut down: no\n"); + } + + if (info->has_backing_filename) { + printf("backing file: %s", info->backing_filename); + if (info->has_full_backing_filename) { + printf(" (actual path: %s)", info->full_backing_filename); + } + putchar('\n'); + if (info->has_backing_filename_format) { + printf("backing file format: %s\n", info->backing_filename_format); + } + } + + if (info->has_snapshots) { + SnapshotInfoList *elem; + char buf[256]; + + printf("Snapshot list:\n"); + printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL)); + + /* Ideally bdrv_snapshot_dump() would operate on SnapshotInfoList but + * we convert to the block layer's native QEMUSnapshotInfo for now. + */ + for (elem = info->snapshots; elem; elem = elem->next) { + QEMUSnapshotInfo sn = { + .vm_state_size = elem->value->vm_state_size, + .date_sec = elem->value->date_sec, + .date_nsec = elem->value->date_nsec, + .vm_clock_nsec = elem->value->vm_clock_sec * 1000000000ULL + + elem->value->vm_clock_nsec, + }; + + pstrcpy(sn.id_str, sizeof(sn.id_str), elem->value->id); + pstrcpy(sn.name, sizeof(sn.name), elem->value->name); + printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), &sn)); + } + } +} + +static void dump_human_image_info_list(ImageInfoList *list) +{ + ImageInfoList *elem; + bool delim = false; + + for (elem = list; elem; elem = elem->next) { + if (delim) { + printf("\n"); + } + delim = true; + + dump_human_image_info(elem->value); + } +} + +static gboolean str_equal_func(gconstpointer a, gconstpointer b) +{ + return strcmp(a, b) == 0; +} + +/** + * Open an image file chain and return an ImageInfoList + * + * @filename: topmost image filename + * @fmt: topmost image format (may be NULL to autodetect) + * @chain: true - enumerate entire backing file chain + * false - only topmost image file + * + * Returns a list of ImageInfo objects or NULL if there was an error opening an + * image file. If there was an error a message will have been printed to + * stderr. + */ +static ImageInfoList *collect_image_info_list(const char *filename, + const char *fmt, + bool chain) +{ + ImageInfoList *head = NULL; + ImageInfoList **last = &head; + GHashTable *filenames; + + filenames = g_hash_table_new_full(g_str_hash, str_equal_func, NULL, NULL); + + while (filename) { + BlockDriverState *bs; + ImageInfo *info; + ImageInfoList *elem; + + if (g_hash_table_lookup_extended(filenames, filename, NULL, NULL)) { + error_report("Backing file '%s' creates an infinite loop.", + filename); + goto err; + } + g_hash_table_insert(filenames, (gpointer)filename, NULL); + + bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_NO_BACKING, + false); + if (!bs) { + goto err; + } + + info = g_new0(ImageInfo, 1); + collect_image_info(bs, info, filename, fmt); + collect_snapshots(bs, info); + + elem = g_new0(ImageInfoList, 1); + elem->value = info; + *last = elem; + last = &elem->next; + + bdrv_delete(bs); + + filename = fmt = NULL; + if (chain) { + if (info->has_full_backing_filename) { + filename = info->full_backing_filename; + } else if (info->has_backing_filename) { + filename = info->backing_filename; + } + if (info->has_backing_filename_format) { + fmt = info->backing_filename_format; + } + } + } + g_hash_table_destroy(filenames); + return head; + +err: + qapi_free_ImageInfoList(head); + g_hash_table_destroy(filenames); + return NULL; +} + +enum { + OPTION_OUTPUT = 256, + OPTION_BACKING_CHAIN = 257, +}; + +typedef enum OutputFormat { + OFORMAT_JSON, + OFORMAT_HUMAN, +} OutputFormat; + +static int img_info(int argc, char **argv) +{ + int c; + OutputFormat output_format = OFORMAT_HUMAN; + bool chain = false; + const char *filename, *fmt, *output; + ImageInfoList *list; + fmt = NULL; + output = NULL; for(;;) { - c = getopt(argc, argv, "f:h"); + 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}, + {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, + {0, 0, 0, 0} + }; + c = getopt_long(argc, argv, "f:h", + long_options, &option_index); if (c == -1) { break; } @@ -1128,6 +1434,12 @@ static int img_info(int argc, char **argv) case 'f': fmt = optarg; break; + case OPTION_OUTPUT: + output = optarg; + break; + case OPTION_BACKING_CHAIN: + chain = true; + break; } } if (optind >= argc) { @@ -1135,49 +1447,34 @@ static int img_info(int argc, char **argv) } filename = argv[optind++]; - bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_NO_BACKING); - if (!bs) { + 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; } - bdrv_get_geometry(bs, &total_sectors); - get_human_readable_size(size_buf, sizeof(size_buf), total_sectors * 512); - allocated_size = bdrv_get_allocated_file_size(bs); - if (allocated_size < 0) { - snprintf(dsize_buf, sizeof(dsize_buf), "unavailable"); - } else { - get_human_readable_size(dsize_buf, sizeof(dsize_buf), - allocated_size); - } - printf("image: %s\n" - "file format: %s\n" - "virtual size: %s (%" PRId64 " bytes)\n" - "disk size: %s\n", - filename, bdrv_get_format_name(bs), size_buf, - (total_sectors * 512), - dsize_buf); - if (bdrv_is_encrypted(bs)) { - printf("encrypted: yes\n"); - } - if (bdrv_get_info(bs, &bdi) >= 0) { - if (bdi.cluster_size != 0) { - printf("cluster_size: %d\n", bdi.cluster_size); - } - if (bdi.is_dirty) { - printf("cleanly shut down: no\n"); - } + + list = collect_image_info_list(filename, fmt, chain); + if (!list) { + return 1; } - bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename)); - if (backing_filename[0] != '\0') { - bdrv_get_full_backing_filename(bs, backing_filename2, - sizeof(backing_filename2)); - printf("backing file: %s", backing_filename); - if (strcmp(backing_filename, backing_filename2) != 0) { - printf(" (actual path: %s)", backing_filename2); + + switch (output_format) { + case OFORMAT_HUMAN: + dump_human_image_info_list(list); + break; + case OFORMAT_JSON: + if (chain) { + dump_json_image_info_list(list); + } else { + dump_json_image_info(list->value); } - putchar('\n'); + break; } - dump_snapshots(bs); - bdrv_delete(bs); + + qapi_free_ImageInfoList(list); return 0; } @@ -1248,7 +1545,7 @@ static int img_snapshot(int argc, char **argv) filename = argv[optind++]; /* Open the image */ - bs = bdrv_new_open(filename, NULL, bdrv_oflags); + bs = bdrv_new_open(filename, NULL, bdrv_oflags, true); if (!bs) { return 1; } @@ -1366,7 +1663,7 @@ static int img_rebase(int argc, char **argv) * Ignore the old backing file for unsafe rebase in case we want to correct * the reference to a renamed or moved backing file. */ - bs = bdrv_new_open(filename, fmt, flags); + bs = bdrv_new_open(filename, fmt, flags, true); if (!bs) { return 1; } @@ -1409,13 +1706,15 @@ static int img_rebase(int argc, char **argv) error_report("Could not open old backing file '%s'", backing_name); goto out; } - - bs_new_backing = bdrv_new("new_backing"); - ret = bdrv_open(bs_new_backing, out_baseimg, BDRV_O_FLAGS, + if (out_baseimg[0]) { + bs_new_backing = bdrv_new("new_backing"); + ret = bdrv_open(bs_new_backing, out_baseimg, BDRV_O_FLAGS, new_backing_drv); - if (ret) { - error_report("Could not open new backing file '%s'", out_baseimg); - goto out; + if (ret) { + error_report("Could not open new backing file '%s'", + out_baseimg); + goto out; + } } } @@ -1431,22 +1730,27 @@ static int img_rebase(int argc, char **argv) if (!unsafe) { uint64_t num_sectors; uint64_t old_backing_num_sectors; - uint64_t new_backing_num_sectors; + uint64_t new_backing_num_sectors = 0; uint64_t sector; int n; uint8_t * buf_old; uint8_t * buf_new; - float local_progress; + float local_progress = 0; buf_old = qemu_blockalign(bs, IO_BUF_SIZE); buf_new = qemu_blockalign(bs, IO_BUF_SIZE); bdrv_get_geometry(bs, &num_sectors); bdrv_get_geometry(bs_old_backing, &old_backing_num_sectors); - bdrv_get_geometry(bs_new_backing, &new_backing_num_sectors); + if (bs_new_backing) { + bdrv_get_geometry(bs_new_backing, &new_backing_num_sectors); + } + + if (num_sectors != 0) { + local_progress = (float)100 / + (num_sectors / MIN(num_sectors, IO_BUF_SIZE / 512)); + } - local_progress = (float)100 / - (num_sectors / MIN(num_sectors, IO_BUF_SIZE / 512)); for (sector = 0; sector < num_sectors; sector += n) { /* How many sectors can we handle with the next read? */ @@ -1480,7 +1784,7 @@ static int img_rebase(int argc, char **argv) } } - if (sector >= new_backing_num_sectors) { + if (sector >= new_backing_num_sectors || !bs_new_backing) { memset(buf_new, 0, n * BDRV_SECTOR_SIZE); } else { if (sector + n > new_backing_num_sectors) { @@ -1526,7 +1830,12 @@ static int img_rebase(int argc, char **argv) * backing file are overwritten in the COW file now, so the visible content * doesn't change when we switch the backing file. */ - ret = bdrv_change_backing_file(bs, out_baseimg, out_basefmt); + if (out_baseimg && *out_baseimg) { + ret = bdrv_change_backing_file(bs, out_baseimg, out_basefmt); + } else { + ret = bdrv_change_backing_file(bs, NULL, NULL); + } + if (ret == -ENOSPC) { error_report("Could not change the backing file to '%s': No " "space left in the file header", out_baseimg); @@ -1629,7 +1938,7 @@ static int img_resize(int argc, char **argv) } /* Parse size */ - param = qemu_opts_create(&resize_options, NULL, 0, NULL); + param = qemu_opts_create_nofail(&resize_options); if (qemu_opt_set(param, BLOCK_OPT_SIZE, size)) { /* Error message already printed when size parsing fails */ ret = -1; @@ -1639,7 +1948,7 @@ static int img_resize(int argc, char **argv) n = qemu_opt_get_size(param, BLOCK_OPT_SIZE, 0); qemu_opts_del(param); - bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR); + bs = bdrv_new_open(filename, fmt, BDRV_O_FLAGS | BDRV_O_RDWR, true); if (!bs) { ret = -1; goto out; @@ -1697,14 +2006,13 @@ int main(int argc, char **argv) error_set_progname(argv[0]); + qemu_init_main_loop(); bdrv_init(); if (argc < 2) help(); cmdname = argv[1]; argc--; argv++; - qemu_init_main_loop(); - /* find the command */ for(cmd = img_cmds; cmd->name != NULL; cmd++) { if (!strcmp(cmdname, cmd->name)) {