From 762eb169dd4e1755f3ca543a260592f975c1bbab Mon Sep 17 00:00:00 2001
Message-Id: <762eb169dd4e1755f3ca543a260592f975c1bbab.1366117835.git.minovotn@redhat.com>
In-Reply-To: <8a8dc925d6cdb62aba736eb1551195551e09271b.1366117835.git.minovotn@redhat.com>
References: <8a8dc925d6cdb62aba736eb1551195551e09271b.1366117835.git.minovotn@redhat.com>
From: Kevin Wolf <kwolf@redhat.com>
Date: Thu, 7 Mar 2013 15:29:24 +0100
Subject: [PATCH 16/19] qemu-img: add json output option to the check command

RH-Author: Kevin Wolf <kwolf@redhat.com>
Message-id: <1362670164-15796-13-git-send-email-kwolf@redhat.com>
Patchwork-id: 49314
O-Subject: [RHEL-6.5 qemu-kvm PATCH 12/12] qemu-img: add json output option to the check command
Bugzilla: 888008
RH-Acked-by: Eric Blake <eblake@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>

From: Federico Simoncelli <fsimonce@redhat.com>

This option --output=[human|json] makes qemu-img check output a human
or JSON representation at the choice of the user.

Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
(cherry picked from commit 8599ea4c42c098d2657ed632ad569f7a665706a4)

Conflicts:
	qapi-schema.json
	qemu-img.c

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
---
 Makefile         |  26 ++++++-
 qapi-schema.json |  47 ++++++++++++
 qemu-img-cmds.hx |   4 +-
 qemu-img.c       | 226 ++++++++++++++++++++++++++++++++++++++++++-------------
 qemu-img.texi    |   5 +-
 5 files changed, 249 insertions(+), 59 deletions(-)

Signed-off-by: Michal Novotny <minovotn@redhat.com>
---
 Makefile         |  26 ++++++-
 qapi-schema.json |  47 ++++++++++++
 qemu-img-cmds.hx |   4 +-
 qemu-img.c       | 226 ++++++++++++++++++++++++++++++++++++++++++-------------
 qemu-img.texi    |   5 +-
 5 files changed, 249 insertions(+), 59 deletions(-)

diff --git a/Makefile b/Makefile
index 9044017..ca1839e 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ GENERATED_HEADERS = config-host.h trace.h config-all-devices.h
 ifeq ($(TRACE_BACKEND),dtrace)
 GENERATED_HEADERS += trace-dtrace.h
 endif
-GENERATED_HEADERS += qmp-commands.h
+GENERATED_HEADERS += qmp-commands.h qapi-visit.h
 
 ifneq ($(wildcard config-host.mak),)
 # Put the all: rule here so that config-host.mak can contain dependencies.
@@ -29,10 +29,16 @@ endif
 
 ifeq ($(CONFIG_LIVE_SNAPSHOTS),y)
 rhel_rhev_qmp_commands.h = rhev-qmp-commands.h
+rhel_rhev_qapi_visit.h = rhev-qapi-visit.h
+rhel_rhev_qapi_visit.o = rhev-qapi-visit.o
+rhel_rhev_qapi_types.o = rhev-qapi-types.o
 GENERATED_HEADERS += rhev-qmp-commands.h rhev-qapi-types.h rhev-qapi-visit.h
 GENERATED_SOURCES += rhev-qmp-marshal.c rhev-qapi-types.c rhev-qapi-visit.c
 else
 rhel_rhev_qmp_commands.h = rhel-qmp-commands.h
+rhel_rhev_qapi_visit.h = rhel-qapi-visit.h
+rhel_rhev_qapi_visit.o = rhel-qapi-visit.o
+rhel_rhev_qapi_types.o = rhel-qapi-types.o
 GENERATED_HEADERS += rhel-qmp-commands.h rhel-qapi-types.h rhel-qapi-visit.h
 GENERATED_SOURCES += rhel-qmp-marshal.c rhel-qapi-types.c rhel-qapi-visit.c
 endif
@@ -189,7 +195,8 @@ qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o: $(GENERATED_HEADERS)
 
 TOOLS_OBJ=qemu-tool.o qerror.o $(shared-obj-y) $(trace-obj-y)
 
-qemu-img$(EXESUF): qemu-img.o $(TOOLS_OBJ)
+qemu-img$(EXESUF): qemu-img.o $(TOOLS_OBJ) $(qapi-obj-y) \
+                              $(rhel_rhev_qapi_visit.o) $(rhel_rhev_qapi_types.o)
 
 qemu-nbd$(EXESUF): qemu-nbd.o $(TOOLS_OBJ)
 
@@ -287,6 +294,21 @@ export QMP_COMMANDS_H
 qmp-commands.h: $(rhel_rhev_qmp_commands.h)
 	$(call quiet-command, echo "$$QMP_COMMANDS_H" > $@, "  GEN   $@")
 
+define QAPI_VISIT_H
+/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */
+
+#ifndef QAPI_VISIT_H
+#define QAPI_VISIT_H
+
+#include "$(rhel_rhev_qapi_visit.h)"
+
+
+#endif
+endef
+
+export QAPI_VISIT_H
+qapi-visit.h: $(rhel_rhev_qapi_visit.h)
+	$(call quiet-command, echo "$$QAPI_VISIT_H" > $@, "  GEN   $@")
 
 test-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y)
 test-visitor: test-visitor.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o
diff --git a/qapi-schema.json b/qapi-schema.json
index d6cbf05..bfcea79 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -224,6 +224,53 @@
 #endif
 
 ##
+# @ImageCheck:
+#
+# Information about a QEMU image file check
+#
+# @filename: name of the image file checked
+#
+# @format: format of the image file checked
+#
+# @check-errors: number of unexpected errors occurred during check
+#
+# @image-end-offset: #optional offset (in bytes) where the image ends, this
+#                    field is present if the driver for the image format
+#                    supports it
+#
+# @corruptions: #optional number of corruptions found during the check if any
+#
+# @leaks: #optional number of leaks found during the check if any
+#
+# @corruptions-fixed: #optional number of corruptions fixed during the check
+#                     if any
+#
+# @leaks-fixed: #optional number of leaks fixed during the check if any
+#
+# @total-clusters: #optional total number of clusters, this field is present
+#                  if the driver for the image format supports it
+#
+# @allocated-clusters: #optional total number of allocated clusters, this
+#                      field is present if the driver for the image format
+#                      supports it
+#
+# @fragmented-clusters: #optional total number of fragmented clusters, this
+#                       field is present if the driver for the image format
+#                       supports it
+#
+# Since: 1.4
+#
+##
+
+{ 'type': 'ImageCheck',
+  'data': {'filename': 'str', 'format': 'str', 'check-errors': 'int',
+           '*image-end-offset': 'int', '*corruptions': 'int', '*leaks': 'int',
+           '*corruptions-fixed': 'int', '*leaks-fixed': 'int',
+           '*total-clusters': 'int', '*allocated-clusters': 'int',
+           '*fragmented-clusters': 'int' } }
+
+
+##
 # @EventInfo:
 #
 # Information about a QMP event
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
index 304d081..084108f 100644
--- a/qemu-img-cmds.hx
+++ b/qemu-img-cmds.hx
@@ -10,9 +10,9 @@ STEXI
 STEXI
 
 DEF("check", img_check,
-    "check [-f fmt] [-r [leaks | all]] filename")
+    "check [-f fmt] [--output=ofmt] [-r [leaks | all]] filename")
 STEXI
-@item check [-f @var{fmt}] [-r [leaks | all]] @var{filename}
+@item check [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] @var{filename}
 ETEXI
 
 DEF("create", img_create,
diff --git a/qemu-img.c b/qemu-img.c
index e60b79c..a83c9f8 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -21,11 +21,15 @@
  * 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 "qjson.h"
 #include "qemu-common.h"
 #include "qemu-option.h"
 #include "qemu-error.h"
 #include "osdep.h"
 #include "block_int.h"
+#include <getopt.h>
 #include <stdio.h>
 
 #ifdef _WIN32
@@ -37,6 +41,16 @@ typedef struct img_cmd_t {
     int (*handler)(int argc, char **argv);
 } img_cmd_t;
 
+enum {
+    OPTION_OUTPUT = 256,
+    OPTION_BACKING_CHAIN = 257,
+};
+
+typedef enum OutputFormat {
+    OFORMAT_JSON,
+    OFORMAT_HUMAN,
+} OutputFormat;
+
 /* Default to cache=writeback as data integrity is not important for qemu-tcg. */
 #define BDRV_O_FLAGS BDRV_O_CACHE_WB
 #define BDRV_DEFAULT_CACHE "writeback"
@@ -382,6 +396,96 @@ out:
     return 0;
 }
 
+static void dump_json_image_check(ImageCheck *check)
+{
+    Error *errp = NULL;
+    QString *str;
+    QmpOutputVisitor *ov = qmp_output_visitor_new();
+    QObject *obj;
+    visit_type_ImageCheck(qmp_output_get_visitor(ov),
+                          &check, NULL, &errp);
+    obj = qmp_output_get_qobject(ov);
+    str = qobject_to_json(obj);
+    assert(str != NULL);
+    printf("%s\n", qstring_get_str(str));
+    qobject_decref(obj);
+    qmp_output_visitor_cleanup(ov);
+    QDECREF(str);
+}
+
+static void dump_human_image_check(ImageCheck *check)
+{
+    if (!(check->corruptions || check->leaks || check->check_errors)) {
+        printf("No errors were found on the image.\n");
+    } else {
+        if (check->corruptions) {
+            printf("\n%" PRId64 " errors were found on the image.\n"
+                "Data may be corrupted, or further writes to the image "
+                "may corrupt it.\n",
+                check->corruptions);
+        }
+
+        if (check->leaks) {
+            printf("\n%" PRId64 " leaked clusters were found on the image.\n"
+                "This means waste of disk space, but no harm to data.\n",
+                check->leaks);
+        }
+
+        if (check->check_errors) {
+            printf("\n%" PRId64 " internal errors have occurred during the check.\n",
+                check->check_errors);
+        }
+    }
+
+    if (check->total_clusters != 0 && check->allocated_clusters != 0) {
+        printf("%" PRId64 "/%" PRId64 "= %0.2f%% allocated, %0.2f%% fragmented\n",
+        check->allocated_clusters, check->total_clusters,
+        check->allocated_clusters * 100.0 / check->total_clusters,
+        check->fragmented_clusters * 100.0 / check->allocated_clusters);
+    }
+
+    if (check->image_end_offset) {
+        printf("Image end offset: %" PRId64 "\n", check->image_end_offset);
+    }
+}
+
+static int collect_image_check(BlockDriverState *bs,
+                   ImageCheck *check,
+                   const char *filename,
+                   const char *fmt,
+                   int fix)
+{
+    int ret;
+    BdrvCheckResult result;
+
+    ret = bdrv_check(bs, &result, fix);
+    if (ret < 0) {
+        return ret;
+    }
+
+    check->filename                 = g_strdup(filename);
+    check->format                   = g_strdup(bdrv_get_format_name(bs));
+    check->check_errors             = result.check_errors;
+    check->corruptions              = result.corruptions;
+    check->has_corruptions          = result.corruptions != 0;
+    check->leaks                    = result.leaks;
+    check->has_leaks                = result.leaks != 0;
+    check->corruptions_fixed        = result.corruptions_fixed;
+    check->has_corruptions_fixed    = result.corruptions != 0;
+    check->leaks_fixed              = result.leaks_fixed;
+    check->has_leaks_fixed          = result.leaks != 0;
+    check->image_end_offset         = result.image_end_offset;
+    check->has_image_end_offset     = result.image_end_offset != 0;
+    check->total_clusters           = result.bfi.total_clusters;
+    check->has_total_clusters       = result.bfi.total_clusters != 0;
+    check->allocated_clusters       = result.bfi.allocated_clusters;
+    check->has_allocated_clusters   = result.bfi.allocated_clusters != 0;
+    check->fragmented_clusters      = result.bfi.fragmented_clusters;
+    check->has_fragmented_clusters  = result.bfi.fragmented_clusters != 0;
+
+    return 0;
+}
+
 /*
  * Checks an image for consistency. Exit codes:
  *
@@ -393,15 +497,26 @@ out:
 static int img_check(int argc, char **argv)
 {
     int c, ret;
-    const char *filename, *fmt;
+    OutputFormat output_format = OFORMAT_HUMAN;
+    const char *filename, *fmt, *output;
     BlockDriverState *bs;
-    BdrvCheckResult result;
     int fix = 0;
     int flags = BDRV_O_FLAGS | BDRV_O_CHECK;
+    ImageCheck *check;
 
     fmt = NULL;
+    output = NULL;
     for(;;) {
-        c = getopt(argc, argv, "f:hr:");
+        int option_index = 0;
+        static const struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"format", required_argument, 0, 'f'},
+            {"repair", no_argument, 0, 'r'},
+            {"output", required_argument, 0, OPTION_OUTPUT},
+            {0, 0, 0, 0}
+        };
+        c = getopt_long(argc, argv, "f:hr:",
+                        long_options, &option_index);
         if (c == -1) {
             break;
         }
@@ -424,6 +539,9 @@ static int img_check(int argc, char **argv)
                 help();
             }
             break;
+        case OPTION_OUTPUT:
+            output = optarg;
+            break;
         }
     }
     if (optind >= argc) {
@@ -431,77 +549,79 @@ static int img_check(int argc, char **argv)
     }
     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, flags, true);
     if (!bs) {
         return 1;
     }
-    ret = bdrv_check(bs, &result, fix);
+
+    check = g_new0(ImageCheck, 1);
+    ret = collect_image_check(bs, check, filename, fmt, fix);
 
     if (ret == -ENOTSUP) {
-        bdrv_delete(bs);
-        error_report("This image format does not support checks");
-        return 1;
+        if (output_format == OFORMAT_HUMAN) {
+            error_report("This image format does not support checks");
+        }
+        ret = 1;
+        goto fail;
     }
 
-    if (result.corruptions_fixed || result.leaks_fixed) {
-        printf("The following inconsistencies were found and repaired:\n\n"
-               "    %d leaked clusters\n"
-               "    %d corruptions\n\n"
-               "Double checking the fixed image now...\n",
-               result.leaks_fixed,
-               result.corruptions_fixed);
-        ret = bdrv_check(bs, &result, 0);
-    }
+    if (check->corruptions_fixed || check->leaks_fixed) {
+        int corruptions_fixed, leaks_fixed;
 
-    if (!(result.corruptions || result.leaks || result.check_errors)) {
-        printf("No errors were found on the image.\n");
-    } else {
-        if (result.corruptions) {
-            printf("\n%d errors were found on the image.\n"
-                "Data may be corrupted, or further writes to the image "
-                "may corrupt it.\n",
-                result.corruptions);
-        }
+        leaks_fixed         = check->leaks_fixed;
+        corruptions_fixed   = check->corruptions_fixed;
 
-        if (result.leaks) {
-            printf("\n%d leaked clusters were found on the image.\n"
-                "This means waste of disk space, but no harm to data.\n",
-                result.leaks);
+        if (output_format == OFORMAT_HUMAN) {
+            printf("The following inconsistencies were found and repaired:\n\n"
+                   "    %" PRId64 " leaked clusters\n"
+                   "    %" PRId64 " corruptions\n\n"
+                   "Double checking the fixed image now...\n",
+                   check->leaks_fixed,
+                   check->corruptions_fixed);
         }
 
-        if (result.check_errors) {
-            printf("\n%d internal errors have occurred during the check.\n",
-                result.check_errors);
-        }
-    }
+        ret = collect_image_check(bs, check, filename, fmt, 0);
 
-    if (result.bfi.total_clusters != 0 && result.bfi.allocated_clusters != 0) {
-        printf("%" PRId64 "/%" PRId64 "= %0.2f%% allocated, %0.2f%% fragmented\n",
-        result.bfi.allocated_clusters, result.bfi.total_clusters,
-        result.bfi.allocated_clusters * 100.0 / result.bfi.total_clusters,
-        result.bfi.fragmented_clusters * 100.0 / result.bfi.allocated_clusters);
+        check->leaks_fixed          = leaks_fixed;
+        check->corruptions_fixed    = corruptions_fixed;
     }
 
-    if (result.image_end_offset > 0) {
-        printf("Image end offset: %" PRId64 "\n", result.image_end_offset);
+    switch (output_format) {
+    case OFORMAT_HUMAN:
+        dump_human_image_check(check);
+        break;
+    case OFORMAT_JSON:
+        dump_json_image_check(check);
+        break;
     }
 
-    bdrv_delete(bs);
-
-    if (ret < 0 || result.check_errors) {
-        printf("\nAn error has occurred during the check: %s\n"
-            "The check is not complete and may have missed error.\n",
-            strerror(-ret));
-        return 1;
+    if (ret || check->check_errors) {
+        ret = 1;
+        goto fail;
     }
 
-    if (result.corruptions) {
-        return 2;
-    } else if (result.leaks) {
-        return 3;
+    if (check->corruptions) {
+        ret = 2;
+    } else if (check->leaks) {
+        ret = 3;
     } else {
-        return 0;
+        ret = 0;
     }
+
+fail:
+    qapi_free_ImageCheck(check);
+    bdrv_delete(bs);
+
+    return ret;
 }
 
 static int img_commit(int argc, char **argv)
diff --git a/qemu-img.texi b/qemu-img.texi
index 20ac0eb..d1340f1 100644
--- a/qemu-img.texi
+++ b/qemu-img.texi
@@ -70,9 +70,10 @@ lists all snapshots in the given image
 Command description:
 
 @table @option
-@item check [-f @var{fmt}] [-r [leaks | all]] @var{filename}
+@item check [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] @var{filename}
 
-Perform a consistency check on the disk image @var{filename}.
+Perform a consistency check on the disk image @var{filename}. The command can
+output in the format @var{ofmt} which is either @code{human} or @code{json}.
 
 If @code{-r} is specified, qemu-img tries to repair any inconsistencies found
 during the check. @code{-r leaks} repairs only cluster leaks, whereas
-- 
1.7.11.7