[Libguestfs] [PATCH] p2v: Use lscpu instead of libvirt to get CPU information.

Richard W.M. Jones rjones at redhat.com
Thu Mar 23 14:31:36 UTC 2017


Don't get the CPU information from libvirt, because including libvirt
and all dependencies in the virt-p2v ISO bloats everything.

Instead get most of the information we need from the util-linux
program 'lscpu'.

Unfortunately the CPU model cannot be retrieved.

Example output:

  $ ./run virt-p2v --cmdline="p2v.dump_config_and_exit"
  [...]
  cpu vendor . . .   Intel
  cpu sockets  . .   2
  cpu cores  . . .   8
  cpu threads  . .   1
  flags  . . . . .   acpi apic pae

This updates commit 963d6c3be7cd91c0373f67cfdd95c4f1dad1452f.
---
 p2v/Makefile.am     |  11 +-
 p2v/cpuid.c         | 331 ++++++++++++++++++++--------------------------------
 p2v/dependencies.m4 |   5 -
 3 files changed, 128 insertions(+), 219 deletions(-)

diff --git a/p2v/Makefile.am b/p2v/Makefile.am
index 726916027..94c649a8e 100644
--- a/p2v/Makefile.am
+++ b/p2v/Makefile.am
@@ -100,7 +100,6 @@ virt_p2v_CPPFLAGS = \
 virt_p2v_CFLAGS = \
 	-pthread \
 	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
-	$(LIBVIRT_CFLAGS) \
 	$(PCRE_CFLAGS) \
 	$(LIBXML2_CFLAGS) \
 	$(GTK_CFLAGS) \
@@ -109,7 +108,6 @@ virt_p2v_CFLAGS = \
 virt_p2v_LDADD = \
 	$(top_builddir)/common/utils/libutils.la \
 	$(top_builddir)/common/miniexpect/libminiexpect.la \
-	$(LIBVIRT_LIBS) \
 	$(PCRE_LIBS) \
 	$(LIBXML2_LIBS) \
 	$(GTK_LIBS) \
@@ -126,16 +124,9 @@ dependencies_files = \
 	dependencies.redhat \
 	dependencies.suse
 
-if HAVE_LIBVIRT
-dependencies_have_libvirt = -DHAVE_LIBVIRT=1
-endif
-
 $(dependencies_files): dependencies.m4
 	define=`echo $@ | $(SED) 's/dependencies.//;y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`; \
-	m4 -D$$define=1 \
-	   -DGTK_VERSION=$(GTK_VERSION) \
-	   $(dependencies_have_libvirt) \
-	   $< > $@-t
+	m4 -D$$define=1 -DGTK_VERSION=$(GTK_VERSION) $< > $@-t
 	mv $@-t $@
 
 # Support files needed by the virt-p2v-make-* scripts.
diff --git a/p2v/cpuid.c b/p2v/cpuid.c
index 13b61b050..12720552d 100644
--- a/p2v/cpuid.c
+++ b/p2v/cpuid.c
@@ -17,20 +17,17 @@
  */
 
 /**
- * Process CPU capabilities into libvirt-compatible C<E<lt>cpuE<gt>> data.
+ * Find CPU vendor, topology and some CPU flags.
  *
- * If libvirt is available at compile time then this is quite
- * simple - libvirt API C<virConnectGetCapabilities> provides
- * a C<E<lt>hostE<ge>> element which has mostly what we need.
+ * lscpu (from util-linux) provides CPU vendor, topology and flags.
  *
- * Flags C<acpi>, C<apic>, C<pae> still have to be parsed out of
- * F</proc/cpuinfo> because these will not necessarily be present in
- * the libvirt capabilities directly (they are implied by the
- * processor model, requiring a complex lookup in the CPU map).
+ * ACPI can be read by seeing if F</sys/firmware/acpi> exists.
  *
- * Note that #vCPUs and amount of RAM is handled by F<main.c>.
+ * CPU model is essentially impossible to get without using libvirt,
+ * but we cannot use libvirt for the reasons outlined in this message:
+ * https://www.redhat.com/archives/libvirt-users/2017-March/msg00071.html
  *
- * See: L<https://libvirt.org/formatdomain.html#elementsCPU>
+ * Note that #vCPUs and amount of RAM is handled by F<main.c>.
  */
 
 #include <config.h>
@@ -40,15 +37,10 @@
 #include <stdarg.h>
 #include <string.h>
 #include <errno.h>
+#include <error.h>
 #include <libintl.h>
 
-#ifdef HAVE_LIBVIRT
-#include <libvirt/libvirt.h>
-#include <libvirt/virterror.h>
-#endif
-
-#include <libxml/xpath.h>
-
+#include "c-ctype.h"
 #include "getprogname.h"
 #include "ignore-value.h"
 
@@ -65,235 +57,166 @@ free_cpu_config (struct cpu_config *cpu)
 }
 
 /**
- * Read flags from F</proc/cpuinfo>.
+ * Get the output of lscpu as a list of (key, value) pairs (as a
+ * flattened list of strings).
  */
-static void
-cpuinfo_flags (struct cpu_config *cpu)
+static char **
+get_lscpu (void)
 {
   const char *cmd;
   CLEANUP_PCLOSE FILE *fp = NULL;
-  CLEANUP_FREE char *flag = NULL;
+  CLEANUP_FREE char *line = NULL;
   ssize_t len;
   size_t buflen = 0;
+  char **ret = NULL;
+  size_t ret_size = 0;
 
-  /* Get the flags, one per line. */
-  cmd = "< /proc/cpuinfo "
-#if defined(__arm__)
-    "grep ^Features"
-#else
-    "grep ^flags"
-#endif
-    " | awk '{ for (i = 3; i <= NF; ++i) { print $i }; exit }'";
+  cmd = "lscpu";
 
   fp = popen (cmd, "re");
   if (fp == NULL) {
-    perror ("/proc/cpuinfo");
-    return;
+    perror (cmd);
+    return NULL;
   }
 
-  while (errno = 0, (len = getline (&flag, &buflen, fp)) != -1) {
-    if (len > 0 && flag[len-1] == '\n')
-      flag[len-1] = '\0';
-
-    if (STREQ (flag, "acpi"))
-      cpu->acpi = 1;
-    else if (STREQ (flag, "apic"))
-      cpu->apic = 1;
-    else if (STREQ (flag, "pae"))
-      cpu->pae = 1;
+  ret = malloc (sizeof (char *));
+  if (ret == NULL) error (EXIT_FAILURE, errno, "malloc");
+  ret[0] = NULL;
+
+  while (errno = 0, (len = getline (&line, &buflen, fp)) != -1) {
+    char *p;
+    char *key, *value;
+
+    if (len > 0 && line[len-1] == '\n')
+      line[len-1] = '\0';
+
+    /* Split the line at the first ':' character. */
+    p = strchr (line, ':');
+    if (p == NULL)
+      continue;
+
+    *p = '\0';
+    key = strdup (line);
+    /* Skip leading whitespace in the value. */
+    for (++p; *p && c_isspace (*p); ++p)
+      ;
+    value = strdup (p);
+
+    /* Add key and value to the list, and trailing NULL pointer. */
+    ret_size += 2;
+    ret = realloc (ret, (ret_size + 1) * sizeof (char *));
+    if (ret == NULL) error (EXIT_FAILURE, errno, "realloc");
+    ret[ret_size-2] = key;
+    ret[ret_size-1] = value;
+    ret[ret_size] = NULL;
   }
 
   if (errno) {
-    perror ("getline");
-    return;
+    perror (cmd);
+    guestfs_int_free_string_list (ret);
+    return NULL;
   }
+
+  return ret;
 }
 
-#ifdef HAVE_LIBVIRT
+/**
+ * Read a single field from lscpu output.
+ *
+ * If the field does not exist, returns C<NULL>.
+ */
+static const char *
+get_field (char **lscpu, const char *key)
+{
+  size_t i;
+
+  for (i = 0; lscpu[i] != NULL; i += 2) {
+    if (STREQ (lscpu[i], key))
+      return lscpu[i+1];
+  }
+
+  return NULL;
+}
 
+/**
+ * Read the CPU vendor from lscpu output.
+ */
 static void
-ignore_errors (void *ignore, virErrorPtr ignore2)
+get_vendor (char **lscpu, struct cpu_config *cpu)
 {
-  /* empty */
+  const char *vendor = get_field (lscpu, "Vendor ID");
+
+  if (vendor) {
+    /* Note this mapping comes from /usr/share/libvirt/cpu_map.xml */
+    if (STREQ (vendor, "GenuineIntel"))
+      cpu->vendor = strdup ("Intel");
+    else if (STREQ (vendor, "AuthenticAMD"))
+      cpu->vendor = strdup ("AMD");
+    /* Currently aarch64 lscpu has no Vendor ID XXX. */
+  }
 }
 
-static void libvirt_error (const char *fs, ...) __attribute__((format (printf,1,2)));
-
+/**
+ * Read the CPU topology from lscpu output.
+ */
 static void
-libvirt_error (const char *fs, ...)
+get_topology (char **lscpu, struct cpu_config *cpu)
 {
-  va_list args;
-  CLEANUP_FREE char *msg = NULL;
-  int len;
-  virErrorPtr err;
-
-  va_start (args, fs);
-  len = vasprintf (&msg, fs, args);
-  va_end (args);
-
-  if (len < 0) goto fallback;
-
-  /* In all recent libvirt, this retrieves the thread-local error. */
-  err = virGetLastError ();
-  if (err)
-    fprintf (stderr,
-             "%s: %s: %s [code=%d int1=%d]\n",
-             getprogname (), msg, err->message, err->code, err->int1);
-  else
-  fallback:
-    fprintf (stderr, "%s: %s\n", getprogname (), msg);
+  const char *v;
+
+  v = get_field (lscpu, "Socket(s)");
+  if (v)
+    ignore_value (sscanf (v, "%u", &cpu->sockets));
+  v = get_field (lscpu, "Core(s) per socket");
+  if (v)
+    ignore_value (sscanf (v, "%u", &cpu->cores));
+  v = get_field (lscpu, "Thread(s) per core");
+  if (v)
+    ignore_value (sscanf (v, "%u", &cpu->threads));
 }
 
 /**
- * Read the capabilities from libvirt and parse out the fields
- * we care about.
+ * Read some important flags from lscpu output.
  */
 static void
-libvirt_capabilities (struct cpu_config *cpu)
+get_flags (char **lscpu, struct cpu_config *cpu)
 {
-  virConnectPtr conn;
-  CLEANUP_FREE char *capabilities_xml = NULL;
-  CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL;
-  CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL;
-  CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL;
-  const char *xpathexpr;
-  xmlNodeSetPtr nodes;
-  size_t nr_nodes, i;
-  xmlNodePtr node;
-
-  /* Connect to libvirt and get the capabilities XML. */
-  conn = virConnectOpenReadOnly (NULL);
-  if (!conn) {
-    libvirt_error (_("could not connect to libvirt"));
-    return;
-  }
+  const char *flags;
 
-  /* Suppress default behaviour of printing errors to stderr.  Note
-   * you can't set this to NULL to ignore errors; setting it to NULL
-   * restores the default error handler ...
-   */
-  virConnSetErrorFunc (conn, NULL, ignore_errors);
-
-  capabilities_xml = virConnectGetCapabilities (conn);
-  if (!capabilities_xml) {
-    libvirt_error (_("could not get libvirt capabilities"));
-    virConnectClose (conn);
-    return;
-  }
-
-  /* Parse the capabilities XML with libxml2. */
-  doc = xmlReadMemory (capabilities_xml, strlen (capabilities_xml),
-                       NULL, NULL, XML_PARSE_NONET);
-  if (doc == NULL) {
-    fprintf (stderr,
-             _("%s: unable to parse capabilities XML returned by libvirt\n"),
-             getprogname ());
-    virConnectClose (conn);
-    return;
-  }
-
-  xpathCtx = xmlXPathNewContext (doc);
-  if (xpathCtx == NULL) {
-    fprintf (stderr, _("%s: unable to create new XPath context\n"),
-             getprogname ());
-    virConnectClose (conn);
-    return;
-  }
-
-  /* Get the CPU vendor. */
-  xpathexpr = "/capabilities/host/cpu/vendor/text()";
-  xpathObj = xmlXPathEvalExpression (BAD_CAST xpathexpr, xpathCtx);
-  if (xpathObj == NULL) {
-    fprintf (stderr, _("%s: unable to evaluate xpath expression: %s\n"),
-             getprogname (), xpathexpr);
-    virConnectClose (conn);
-    return;
-  }
-  nodes = xpathObj->nodesetval;
-  nr_nodes = nodes->nodeNr;
-  if (nr_nodes > 0) {
-    node = nodes->nodeTab[0];
-    cpu->vendor = (char *) xmlNodeGetContent (node);
-  }
-
-  /* Get the CPU model. */
-  xmlXPathFreeObject (xpathObj);
-  xpathexpr = "/capabilities/host/cpu/model/text()";
-  xpathObj = xmlXPathEvalExpression (BAD_CAST xpathexpr, xpathCtx);
-  if (xpathObj == NULL) {
-    fprintf (stderr, _("%s: unable to evaluate xpath expression: %s\n"),
-             getprogname (), xpathexpr);
-    virConnectClose (conn);
-    return;
-  }
-  nodes = xpathObj->nodesetval;
-  nr_nodes = nodes->nodeNr;
-  if (nr_nodes > 0) {
-    node = nodes->nodeTab[0];
-    cpu->model = (char *) xmlNodeGetContent (node);
-  }
+  flags = get_field (lscpu, "Flags");
+  if (flags) {
+    cpu->apic = strstr (flags, " apic ") != NULL;
+    cpu->pae = strstr (flags, " pae ") != NULL;
 
-  /* Get the topology.  Note the XPath expression returns all
-   * attributes of the <topology> node.
-   */
-  xmlXPathFreeObject (xpathObj);
-  xpathexpr = "/capabilities/host/cpu/topology/@*";
-  xpathObj = xmlXPathEvalExpression (BAD_CAST xpathexpr, xpathCtx);
-  if (xpathObj == NULL) {
-    fprintf (stderr, _("%s: unable to evaluate xpath expression: %s\n"),
-             getprogname (), xpathexpr);
-    virConnectClose (conn);
-    return;
+    /* aarch64 /proc/cpuinfo has a "Features" field, but lscpu does
+     * not expose it.  However aarch64 Features does not contain any
+     * of the interesting flags above.
+     */
   }
-  nodes = xpathObj->nodesetval;
-  nr_nodes = nodes->nodeNr;
-  /* Iterate over the attributes of the <topology> node. */
-  for (i = 0; i < nr_nodes; ++i) {
-    node = nodes->nodeTab[i];
-
-    if (node->type == XML_ATTRIBUTE_NODE) {
-      xmlAttrPtr attr = (xmlAttrPtr) node;
-      CLEANUP_FREE char *content = NULL;
-      unsigned *up;
-
-      if (STREQ ((const char *) attr->name, "sockets")) {
-        up = &cpu->sockets;
-      parse_attr:
-        *up = 0;
-        content = (char *) xmlNodeListGetString (doc, attr->children, 1);
-        if (content)
-          ignore_value (sscanf (content, "%u", up));
-      }
-      else if (STREQ ((const char *) attr->name, "cores")) {
-        up = &cpu->cores;
-        goto parse_attr;
-      }
-      else if (STREQ ((const char *) attr->name, "threads")) {
-        up = &cpu->threads;
-        goto parse_attr;
-      }
-    }
-  }
-
-  virConnectClose (conn);
 }
 
-#else /* !HAVE_LIBVIRT */
-
+/**
+ * Find out if the system uses ACPI.
+ */
 static void
-libvirt_capabilities (struct cpu_config *cpu)
+get_acpi (struct cpu_config *cpu)
 {
-  fprintf (stderr,
-           _("%s: program was compiled without libvirt support\n"),
-           getprogname ());
+  cpu->acpi = access ("/sys/firmware/acpi", F_OK) == 0;
 }
 
-#endif /* !HAVE_LIBVIRT */
-
 void
 get_cpu_config (struct cpu_config *cpu)
 {
+  CLEANUP_FREE_STRING_LIST char **lscpu = NULL;
+
   free_cpu_config (cpu);
-  libvirt_capabilities (cpu);
-  cpuinfo_flags (cpu);
+
+  lscpu = get_lscpu ();
+  if (lscpu != NULL) {
+    get_vendor (lscpu, cpu);
+    get_topology (lscpu, cpu);
+    get_flags (lscpu, cpu);
+  }
+
+  get_acpi (cpu);
 }
diff --git a/p2v/dependencies.m4 b/p2v/dependencies.m4
index 0db2f85d4..02ca87c19 100644
--- a/p2v/dependencies.m4
+++ b/p2v/dependencies.m4
@@ -25,8 +25,6 @@ ifelse(REDHAT,1,
   libxml2
   gtk`'GTK_VERSION
   dbus-libs
-  dnl libvirt is optional, used just to parse the host CPU capabilities.
-  ifdef(`HAVE_LIBVIRT', `libvirt-libs')
 
   dnl Run as external programs by the p2v binary.
   /usr/bin/ssh
@@ -66,7 +64,6 @@ ifelse(DEBIAN,1,
   libxml2
   ifelse(GTK_VERSION,2,libgtk`'GTK_VERSION`'.0-0,libgtk-`'GTK_VERSION`'-0)
   libdbus-1-3
-  ifdef(`HAVE_LIBVIRT', `libvirt0')
   openssh-client
   qemu-utils
   debianutils
@@ -87,7 +84,6 @@ ifelse(ARCHLINUX,1,
   libxml2
   gtk`'GTK_VERSION
   dbus
-  ifdef(`HAVE_LIBVIRT', `libvirt')
   openssh
   qemu
   which
@@ -110,7 +106,6 @@ ifelse(SUSE,1,
   libxml2
   gtk`'GTK_VERSION
   libdbus-1-3
-  ifdef(`HAVE_LIBVIRT', `libvirt-libs')
   qemu-tools
   openssh
   dnl /usr/bin/which is in util-linux on SUSE
-- 
2.12.0




More information about the Libguestfs mailing list