[Libguestfs] [PATCH nbdinfo v2] info: Add a --map option for displaying allocation metadata.

Richard W.M. Jones rjones at redhat.com
Sat Sep 26 16:36:23 UTC 2020


---
 info/Makefile.am                      |   4 +
 info/info-map-base-allocation-json.sh |  52 ++++++++++
 info/info-map-base-allocation.sh      |  49 ++++++++++
 info/nbdinfo.c                        | 134 ++++++++++++++++++++++++--
 info/nbdinfo.pod                      |  39 +++++++-
 5 files changed, 271 insertions(+), 7 deletions(-)

diff --git a/info/Makefile.am b/info/Makefile.am
index 5d0d288..496692a 100644
--- a/info/Makefile.am
+++ b/info/Makefile.am
@@ -25,6 +25,8 @@ EXTRA_DIST = \
 	info-size.sh \
 	info-text.sh \
 	info-description.sh \
+	info-map-base-allocation.sh \
+	info-map-base-allocation-json.sh \
 	nbdinfo.pod \
 	$(NULL)
 
@@ -67,6 +69,8 @@ TESTS += \
 	info-size.sh \
 	info-text.sh \
 	info-description.sh \
+	info-map-base-allocation.sh \
+	info-map-base-allocation-json.sh \
 	$(NULL)
 
 check-valgrind:
diff --git a/info/info-map-base-allocation-json.sh b/info/info-map-base-allocation-json.sh
new file mode 100755
index 0000000..7927178
--- /dev/null
+++ b/info/info-map-base-allocation-json.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2020 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires nbdkit --version
+requires nbdsh --version
+requires jq --version
+
+out=info-base-allocation-json.out
+cleanup_fn rm -f $out
+rm -f $out
+
+# Note the memory plugin uses a 32K page size, and extents
+# are always aligned with this.
+nbdkit -U - memory 1M --run '
+    nbdsh -u "$uri" \
+          -c "h.pwrite(b\"\\x01\"*131072, 0)" \
+          -c "h.pwrite(b\"\\x02\"*131072, 320*1024)" &&
+    nbdinfo --map --json "$uri"
+' > $out
+
+cat $out
+jq < $out
+
+test $( jq -r '.[0].offset' < $out ) -eq 0
+test $( jq -r '.[0].length' < $out ) -eq 131072
+test $( jq -r '.[0].type' < $out ) -eq 0
+test $( jq -r '.[0].description' < $out ) = "allocated"
+
+test $( jq -r '.[3].offset' < $out ) -eq 458752
+test $( jq -r '.[3].length' < $out ) -eq 589824
+test $( jq -r '.[3].type' < $out ) -eq 3
+test $( jq -r '.[3].description' < $out ) = "hole,zero"
diff --git a/info/info-map-base-allocation.sh b/info/info-map-base-allocation.sh
new file mode 100755
index 0000000..104ed7c
--- /dev/null
+++ b/info/info-map-base-allocation.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2020 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+. ../tests/functions.sh
+
+set -e
+set -x
+
+requires nbdkit --version
+requires nbdsh --version
+requires tr --version
+
+out=info-base-allocation.out
+cleanup_fn rm -f $out
+rm -f $out
+
+# Note the memory plugin uses a 32K page size, and extents
+# are always aligned with this.
+nbdkit -U - memory 1M --run '
+    nbdsh -u "$uri" \
+          -c "h.pwrite(b\"\\x01\"*131072, 0)" \
+          -c "h.pwrite(b\"\\x02\"*131072, 320*1024)" &&
+    nbdinfo --map "$uri"
+' > $out
+
+cat $out
+
+if [ "$(tr -s ' ' < $out)" != " 0 131072 0 allocated
+ 131072 196608 3 hole,zero
+ 327680 131072 0 allocated
+ 458752 589824 3 hole,zero" ]; then
+    echo "$0: unexpected output from nbdinfo --map"
+    exit 1
+fi
diff --git a/info/nbdinfo.c b/info/nbdinfo.c
index 647a24c..ff060aa 100644
--- a/info/nbdinfo.c
+++ b/info/nbdinfo.c
@@ -34,6 +34,7 @@
 static bool list_all = false;
 static bool probe_content, content_flag, no_content_flag;
 static bool json_output = false;
+static const char *map = NULL;
 static bool size_only = false;
 
 static struct export_list {
@@ -49,6 +50,10 @@ static void list_one_export (struct nbd_handle *nbd, const char *desc,
 static void list_all_exports (struct nbd_handle *nbd1, const char *uri);
 static void print_json_string (const char *);
 static char *get_content (struct nbd_handle *, int64_t size);
+static int extent_callback (void *user_data, const char *metacontext,
+                            uint64_t offset,
+                            uint32_t *entries, size_t nr_entries,
+                            int *error);
 
 static void __attribute__((noreturn))
 usage (FILE *fp, int exitcode)
@@ -60,6 +65,7 @@ usage (FILE *fp, int exitcode)
 "    nbdinfo nbd://localhost\n"
 "    nbdinfo \"nbd+unix:///?socket=/tmp/unixsock\"\n"
 "    nbdinfo --size nbd://example.com\n"
+"    nbdinfo --map nbd://example.com\n"
 "    nbdinfo --json nbd://example.com\n"
 "    nbdinfo --list nbd://example.com\n"
 "\n"
@@ -85,6 +91,7 @@ main (int argc, char *argv[])
     CONTENT_OPTION,
     NO_CONTENT_OPTION,
     JSON_OPTION,
+    MAP_OPTION,
     SIZE_OPTION,
   };
   const char *short_options = "LV";
@@ -95,6 +102,7 @@ main (int argc, char *argv[])
     { "json",               no_argument,       NULL, JSON_OPTION },
     { "list",               no_argument,       NULL, 'L' },
     { "long-options",       no_argument,       NULL, LONG_OPTIONS },
+    { "map",                optional_argument, NULL, MAP_OPTION },
     { "short-options",      no_argument,       NULL, SHORT_OPTIONS },
     { "size",               no_argument,       NULL, SIZE_OPTION },
     { "version",            no_argument,       NULL, 'V' },
@@ -143,6 +151,10 @@ main (int argc, char *argv[])
       no_content_flag = true;
       break;
 
+    case MAP_OPTION:
+      map = optarg ? optarg : "base:allocation";
+      break;
+
     case SIZE_OPTION:
       size_only = true;
       break;
@@ -164,10 +176,11 @@ main (int argc, char *argv[])
   if (argc - optind != 1)
     usage (stderr, EXIT_FAILURE);
 
-  /* You can combine certain options. */
-  if (list_all && size_only) {
-    fprintf (stderr, "%s: you cannot use %s and %s together.\n",
-             argv[0], "--list", "--size");
+  /* You cannot combine certain options. */
+  if (!!list_all + !!map + !!size_only > 1) {
+    fprintf (stderr,
+             "%s: you cannot use --list, --map and --size together.\n",
+             argv[0]);
     exit (EXIT_FAILURE);
   }
   if (content_flag && no_content_flag) {
@@ -182,6 +195,8 @@ main (int argc, char *argv[])
     probe_content = true;
   if (no_content_flag)
     probe_content = false;
+  if (map)
+    probe_content = false;
 
   /* Open the NBD side. */
   nbd = nbd_create ();
@@ -191,11 +206,13 @@ main (int argc, char *argv[])
   }
   nbd_set_uri_allow_local_file (nbd, true); /* Allow ?tls-psk-file. */
 
-  /* If using --list then we need opt mode in the handle. */
+  /* Set optional modes in the handle. */
   if (list_all)
     nbd_set_opt_mode (nbd, true);
-  if (!size_only)
+  if (!map && !size_only)
     nbd_set_full_info (nbd, true);
+  if (map)
+    nbd_add_meta_context (nbd, map);
 
   if (nbd_connect_uri (nbd, argv[optind]) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
@@ -225,6 +242,44 @@ main (int argc, char *argv[])
 
     printf ("%" PRIi64 "\n", size);
   }
+  else if (map) {
+    uint64_t offset, prev_offset;
+
+    /* Did we get the requested map? */
+    if (!nbd_can_meta_context (nbd, map)) {
+      fprintf (stderr,
+               "%s: --map: server does not support metadata context \"%s\"\n",
+               argv[0], map);
+      exit (EXIT_FAILURE);
+    }
+
+    size = nbd_get_size (nbd);
+    if (size == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+
+    if (json_output) printf ("[\n");
+    for (offset = 0; offset < size;) {
+      prev_offset = offset;
+      if (nbd_block_status (nbd, size - offset, offset,
+                            (nbd_extent_callback) { .callback = extent_callback,
+                                                    .user_data = &offset },
+                            0) == -1) {
+        fprintf (stderr, "%s\n", nbd_get_error ());
+        exit (EXIT_FAILURE);
+      }
+      /* We expect extent_callback to increment the offset.  If it did
+       * not then probably the server is not returning any extents.
+       */
+      if (offset <= prev_offset) {
+        fprintf (stderr, "%s: --map: server did not return any extents\n",
+                 argv[0]);
+        exit (EXIT_FAILURE);
+      }
+    }
+    if (json_output) printf ("\n]\n");
+  }
   else {
     /* Print per-connection fields. */
     protocol = nbd_get_protocol (nbd);
@@ -591,3 +646,70 @@ get_content (struct nbd_handle *nbd, int64_t size)
   free (cmd);
   return ret;                   /* caller frees */
 }
+
+/* Callback handling --map. */
+static const char *
+extent_description (const char *metacontext, uint32_t type)
+{
+  if (strcmp (metacontext, "base:allocation") == 0) {
+    switch (type) {
+    case 0: return "allocated";
+    case 1: return "zero";
+    case 2: return "hole";
+    case 3: return "hole,zero";
+    }
+  }
+  else if (strcmp (metacontext, "qemu:dirty-bitmap") == 0) {
+    switch (type) {
+    case 0: return "clean";
+    case 1: return "dirty";
+    }
+  }
+
+  return "unknown";
+}
+
+static int
+extent_callback (void *user_data, const char *metacontext,
+                 uint64_t offset,
+                 uint32_t *entries, size_t nr_entries,
+                 int *error)
+{
+  size_t i;
+  uint64_t *ret_offset = user_data;
+  static bool comma = false;
+
+  if (strcmp (metacontext, map) != 0)
+    return 0;
+
+  /* Print the entries received. */
+  for (i = 0; i < nr_entries; i += 2) {
+    const char *descr = extent_description (map, entries[i+1]);
+
+    if (!json_output) {
+      printf ("%10" PRIu64 "  "
+              "%10" PRIu32 "  "
+              "%3" PRIu32 "  "
+              "%s\n",
+              offset, entries[i], entries[i+1], descr);
+    }
+    else {
+      if (comma)
+        printf (",\n");
+
+      printf ("{ \"offset\": %" PRIu64 ", "
+              "\"length\": %" PRIu32 ", "
+              "\"type\": %" PRIu32 ", ",
+              offset, entries[i], entries[i+1]);
+      printf ("\"description\": ");
+      print_json_string (descr);
+      printf ("}");
+      comma = true;
+    }
+
+    offset += entries[i];
+  }
+
+  *ret_offset = offset;
+  return 0;
+}
diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod
index 19305bf..0e65f1f 100644
--- a/info/nbdinfo.pod
+++ b/info/nbdinfo.pod
@@ -4,7 +4,7 @@ nbdinfo - display information and metadata about NBD servers and exports
 
 =head1 SYNOPSIS
 
- nbdinfo [--json] [--size] NBD-URI
+ nbdinfo [--json] [--map] [--size] NBD-URI
 
  nbdinfo -L|--list NBD-URI
 
@@ -20,6 +20,8 @@ nbdinfo - display information and metadata about NBD servers and exports
 
  nbdinfo --size nbd://example.com
 
+ nbdinfo --map nbd://example.com
+
  nbdinfo --json nbd://example.com
 
  nbdinfo --list nbd://example.com
@@ -84,6 +86,32 @@ the I<--json> parameter:
    ]
  }
 
+=head3 Map
+
+To show a map which areas of the disk are allocated and sparse, use
+the I<--map> option:
+
+ $ nbdinfo --map nbd://localhost/
+ 0        1048576  0  allocated
+ 1048576  1048576  3  hole,zero
+
+The fields are: start size type description.
+
+The type field is an integer showing the raw value from the NBD
+protocol.  For some maps nbdinfo knows how to translate the type into
+a printable description.
+
+By default this shows the C<"base:allocation"> map, but you can show
+other maps too:
+
+ $ nbdinfo --map=qemu:dirty-bitmap nbd://localhost/
+ 0  1048576  1  dirty
+
+For more information on NBD maps, see I<Metadata querying> in the NBD
+protocol.  I<--json> can also be used here for parsable JSON output.
+
+=head3 List of exports
+
 To list all the exports available on an NBD server use the I<--list>
 (I<-L>) option.
 
@@ -122,6 +150,15 @@ use I<--list --content>.
 
 The output is displayed in JSON format.
 
+=item B<--map>
+
+=item B<--map=>MAP
+
+Display the map (usually whether parts of the disk are allocated or
+sparse) of the given export.  This displays the C<"base:allocation">
+map by default, you can choose a different map with the optional
+parameter.
+
 =item B<-L>
 
 =item B<--list>
-- 
2.27.0




More information about the Libguestfs mailing list