Add virt-inspector --xpath to run XPath queries directly.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 3 Nov 2011 13:06:25 +0000 (13:06 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 3 Nov 2011 13:06:25 +0000 (13:06 +0000)
xmlstarlet is good, but not available in Red Hat Enterprise Linux.

Build a simple but sane XPath query parser into virt-inspector
directly so that we don't need any external tools.

inspector/virt-inspector.c
inspector/virt-inspector.pod

index 4afce0e..c505fa4 100644 (file)
 
 #include <libxml/xmlIO.h>
 #include <libxml/xmlwriter.h>
+#include <libxml/xpath.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xmlsave.h>
 
 #include "progname.h"
 #include "c-ctype.h"
@@ -47,6 +51,7 @@ int keys_from_stdin = 0;
 int echo_keys = 0;
 const char *libvirt_uri = NULL;
 int inspector = 1;
+static const char *xpath = NULL;
 
 static void output (char **roots);
 static void output_roots (xmlTextWriterPtr xo, char **roots);
@@ -58,6 +63,7 @@ static void output_applications (xmlTextWriterPtr xo, char *root);
 static void canonicalize (char *dev);
 static void free_strings (char **argv);
 static int count_strings (char *const*argv);
+static void do_xpath (const char *query);
 
 static inline char *
 bad_cast (char const *s)
@@ -89,6 +95,7 @@ usage (int status)
              "  -v|--verbose         Verbose messages\n"
              "  -V|--version         Display version and exit\n"
              "  -x                   Trace libguestfs API calls\n"
+             "  --xpath query        Perform an XPath query\n"
              "For more information, see the manpage %s(1).\n"),
              program_name, program_name, program_name,
              program_name);
@@ -119,6 +126,7 @@ main (int argc, char *argv[])
     { "keys-from-stdin", 0, 0, 0 },
     { "verbose", 0, 0, 'v' },
     { "version", 0, 0, 'V' },
+    { "xpath", 1, 0, 0 },
     { 0, 0, 0, 0 }
   };
   struct drv *drvs = NULL;
@@ -150,6 +158,8 @@ main (int argc, char *argv[])
           format = NULL;
         else
           format = optarg;
+      } else if (STREQ (long_options[option_index].name, "xpath")) {
+        xpath = optarg;
       } else {
         fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
                  program_name, long_options[option_index].name, option_index);
@@ -237,6 +247,21 @@ main (int argc, char *argv[])
   if (optind != argc)
     usage (EXIT_FAILURE);
 
+  /* XPath is modal: no drives should be specified.  There must be
+   * one extra parameter on the command line.
+   */
+  if (xpath) {
+    if (drvs != NULL) {
+      fprintf (stderr, _("%s: cannot use --xpath together with other options.\n"),
+               program_name);
+      exit (EXIT_FAILURE);
+    }
+
+    do_xpath (xpath);
+
+    exit (EXIT_SUCCESS);
+  }
+
   /* User must have specified some drives. */
   if (drvs == NULL)
     usage (EXIT_FAILURE);
@@ -782,3 +807,102 @@ count_strings (char *const *argv)
     ;
   return c;
 }
+
+/* Run an XPath query on XML on stdin, print results to stdout. */
+static void
+do_xpath (const char *query)
+{
+  xmlDocPtr doc;
+  xmlXPathContextPtr xpathCtx;
+  xmlXPathObjectPtr xpathObj;
+  xmlNodeSetPtr nodes;
+  char *r;
+  size_t i;
+  xmlSaveCtxtPtr saveCtx;
+  xmlDocPtr wrdoc;
+  xmlNodePtr wrnode;
+
+  doc = xmlReadFd (STDIN_FILENO, NULL, "utf8", 0);
+  if (doc == NULL) {
+    fprintf (stderr, _("%s: unable to parse XML from stdin\n"), program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  xpathCtx = xmlXPathNewContext (doc);
+  if (xpathCtx == NULL) {
+    fprintf (stderr, _("%s: unable to create new XPath context\n"),
+             program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  xpathObj = xmlXPathEvalExpression (BAD_CAST query, xpathCtx);
+  if (xpathObj == NULL) {
+    fprintf (stderr, _("%s: unable to evaluate XPath expression\n"),
+             program_name);
+    exit (EXIT_FAILURE);
+  }
+
+  switch (xpathObj->type) {
+  case XPATH_NODESET:
+    nodes = xpathObj->nodesetval;
+
+    saveCtx = xmlSaveToFd (STDOUT_FILENO, NULL, XML_SAVE_NO_DECL);
+    if (saveCtx == NULL) {
+      fprintf (stderr, _("%s: xmlSaveToFd failed\n"), program_name);
+      exit (EXIT_FAILURE);
+    }
+
+    for (i = 0; i < (size_t) nodes->nodeNr; ++i) {
+      wrdoc = xmlNewDoc (BAD_CAST "1.0");
+      if (wrdoc == NULL) {
+        fprintf (stderr, _("%s: xmlNewDoc failed\n"), program_name);
+        exit (EXIT_FAILURE);
+      }
+      wrnode = xmlCopyNode (nodes->nodeTab[i], 1);
+      if (wrnode == NULL) {
+        fprintf (stderr, _("%s: xmlCopyNode failed\n"), program_name);
+        exit (EXIT_FAILURE);
+      }
+
+      xmlDocSetRootElement (wrdoc, wrnode);
+
+      if (xmlSaveDoc (saveCtx, wrdoc) == -1) {
+        fprintf (stderr, _("%s: xmlSaveDoc failed\n"), program_name);
+        exit (EXIT_FAILURE);
+      }
+
+      xmlFreeDoc (wrdoc);
+    }
+
+    xmlSaveClose (saveCtx);
+
+    break;
+
+  case XPATH_STRING:
+    r = (char *) xpathObj->stringval;
+    printf ("%s", r);
+    i = strlen (r);
+    if (i > 0 && r[i-1] != '\n')
+      printf ("\n");
+    break;
+
+  case XPATH_UNDEFINED: /* grrrrr ... switch-enum is a useless warning */
+  case XPATH_BOOLEAN:
+  case XPATH_NUMBER:
+  case XPATH_POINT:
+  case XPATH_RANGE:
+  case XPATH_LOCATIONSET:
+  case XPATH_USERS:
+  case XPATH_XSLT_TREE:
+  default:
+    r = (char *) xmlXPathCastToString (xpathObj);
+    printf ("%s\n", r);
+    free (r);
+  }
+
+  xmlXPathFreeObject (xpathObj);
+  xmlXPathFreeContext (xpathCtx);
+  xmlFreeDoc (doc);
+
+  exit (EXIT_SUCCESS);
+}
index df9dfda..9a43e94 100755 (executable)
@@ -132,6 +132,13 @@ Display version number and exit.
 
 Enable tracing of libguestfs API calls.
 
+=item B<--xpath> query
+
+Perform an XPath query on the XML on stdin, and print the result on
+stdout.  In this mode virt-inspector simply runs an XPath query; all
+other inspection functions are disabled.  See L</XPATH QUERIES> below
+for some examples.
+
 =back
 
 =head1 OLD-STYLE COMMAND LINE ARGUMENTS
@@ -327,26 +334,34 @@ installer, or one part of a multipart CD.  For example:
      <format>installer</format>
      <live/>
 
-=head1 USING XPATH
+=head1 XPATH QUERIES
+
+Virt-inspector includes built in support for running XPath queries.
+The reason for including XPath support directly in virt-inspector is
+simply that there are no good and widely available command line
+programs that can do XPath queries.  The only good one is
+L<xmlstarlet(1)> and that is not available on Red Hat Enterprise
+Linux.
 
-You can use the XPath query language to select parts of the XML.  We
-recommend using C<xmlstarlet> to perform XPath queries from the
-command line.
+To perform an XPath query, use the I<--xpath> option.  Note that in
+this mode, virt-inspector simply reads XML from stdin and outputs the
+query result on stdout.  All other inspection features are disabled in
+this mode.
 
 For example:
 
- $ virt-inspector -d Guest | xmlstarlet sel -t -c '//filesystems'
+ $ virt-inspector -d Guest | virt-inspector --xpath '//filesystems'
  <filesystems>
       <filesystem dev="/dev/vg_f13x64/lv_root">
         <type>ext4</type>
  [...]
 
  $ virt-inspector -d Guest | \
-     xmlstarlet sel -t -c "string(//filesystem[@dev='/dev/sda1']/type)"
+     virt-inspector --xpath "string(//filesystem[@dev='/dev/sda1']/type)"
  ext4
 
  $ virt-inspector -d Guest | \
-    xmlstarlet sel -t -v '//icon' | base64 -i -d | display -
+     virt-inspector --xpath 'string(//icon)' | base64 -i -d | display -
  [displays the guest icon, if there is one]
 
 =head1 SHELL QUOTING