In the fake <domain type='physical'> libvirt XML that we create to
describe the physical host, we did not accurately pass any information
about the host CPU except the number of cores (<vcpu/>).
This commit extracts detailed information about the vendor, model and
topology of the host CPU and adds that to the libvirt XML for
virt-v2v. Conveniently we can use libvirt capabilities to get this
information without needing to parse /proc/cpuinfo or similar
techniques.
The libvirt XML looks like this:
<domain type="physical">
...
<cpu match="minimum">
<vendor>Intel</vendor>
<model fallback="allow">Broadwell</model>
<topology sockets="1" cores="2" threads="2"/>
</cpu>
...
<features>
<acpi/>
<apic/>
<pae/>
</features>
---
p2v/Makefile.am | 12 ++-
p2v/config.c | 18 +++-
p2v/conversion.c | 35 +++++-
p2v/cpuid.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++++
p2v/dependencies.m4 | 5 +
p2v/main.c | 56 +---------
p2v/p2v.h | 21 +++-
7 files changed, 379 insertions(+), 67 deletions(-)
create mode 100644 p2v/cpuid.c
diff --git a/p2v/Makefile.am b/p2v/Makefile.am
index 9401220..0b8c1c6 100644
--- a/p2v/Makefile.am
+++ b/p2v/Makefile.am
@@ -74,6 +74,7 @@ virt_p2v_SOURCES = \
about-license.c \
config.c \
conversion.c \
+ cpuid.c \
gui.c \
gui-gtk2-compat.h \
gui-gtk3-compat.h \
@@ -97,6 +98,7 @@ virt_p2v_CPPFLAGS = \
virt_p2v_CFLAGS = \
-pthread \
$(WARN_CFLAGS) $(WERROR_CFLAGS) \
+ $(LIBVIRT_CFLAGS) \
$(PCRE_CFLAGS) \
$(LIBXML2_CFLAGS) \
$(GTK_CFLAGS) \
@@ -105,6 +107,7 @@ 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) \
@@ -120,9 +123,16 @@ 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) $< > $@-t
+ m4 -D$$define=1 \
+ -DGTK_VERSION=$(GTK_VERSION) \
+ $(dependencies_have_libvirt) \
+ $< > $@-t
mv $@-t $@
# Support files needed by the virt-p2v-make-* scripts.
diff --git a/p2v/config.c b/p2v/config.c
index 054b07c..f9f610c 100644
--- a/p2v/config.c
+++ b/p2v/config.c
@@ -95,6 +95,8 @@ free_config (struct config *c)
free (c->identity_url);
free (c->identity_file);
free (c->guestname);
+ free (c->cpu.vendor);
+ free (c->cpu.model);
guestfs_int_free_string_list (c->disks);
guestfs_int_free_string_list (c->removable);
guestfs_int_free_string_list (c->interfaces);
@@ -132,10 +134,20 @@ print_config (struct config *config, FILE *fp)
config->guestname ? config->guestname : "none");
fprintf (fp, "vcpus . . . . . %d\n", config->vcpus);
fprintf (fp, "memory . . . . . %" PRIu64 "\n",
config->memory);
+ if (config->cpu.vendor)
+ fprintf (fp, "cpu vendor . . . %s\n", config->cpu.vendor);
+ if (config->cpu.model)
+ fprintf (fp, "cpu model . . . %s\n", config->cpu.model);
+ if (config->cpu.sockets > 0)
+ fprintf (fp, "cpu sockets . . %u\n", config->cpu.sockets);
+ if (config->cpu.cores > 0)
+ fprintf (fp, "cpu cores . . . %u\n", config->cpu.cores);
+ if (config->cpu.threads > 0)
+ fprintf (fp, "cpu threads . . %u\n", config->cpu.threads);
fprintf (fp, "flags . . . . . %s%s%s\n",
- config->flags & FLAG_ACPI ? " acpi" : "",
- config->flags & FLAG_APIC ? " apic" : "",
- config->flags & FLAG_PAE ? " pae" : "");
+ config->cpu.acpi ? " acpi" : "",
+ config->cpu.apic ? " apic" : "",
+ config->cpu.pae ? " pae" : "");
fprintf (fp, "disks . . . . . ");
if (config->disks != NULL) {
for (i = 0; config->disks[i] != NULL; ++i)
diff --git a/p2v/conversion.c b/p2v/conversion.c
index 0c17ef2..55fbfb1 100644
--- a/p2v/conversion.c
+++ b/p2v/conversion.c
@@ -612,6 +612,35 @@ generate_libvirt_xml (struct config *config, struct data_conn
*data_conns,
string_format ("%d", config->vcpus);
} end_element ();
+ if (config->cpu.vendor || config->cpu.model ||
+ config->cpu.sockets || config->cpu.cores || config->cpu.threads) {
+ /*
https://libvirt.org/formatdomain.html#elementsCPU */
+ start_element ("cpu") {
+ attribute ("match", "minimum");
+ if (config->cpu.vendor) {
+ start_element ("vendor") {
+ string (config->cpu.vendor);
+ } end_element ();
+ }
+ if (config->cpu.model) {
+ start_element ("model") {
+ attribute ("fallback", "allow");
+ string (config->cpu.model);
+ } end_element ();
+ }
+ if (config->cpu.sockets || config->cpu.cores || config->cpu.threads) {
+ start_element ("topology") {
+ if (config->cpu.sockets)
+ attribute_format ("sockets", "%u",
config->cpu.sockets);
+ if (config->cpu.cores)
+ attribute_format ("cores", "%u",
config->cpu.cores);
+ if (config->cpu.threads)
+ attribute_format ("threads", "%u",
config->cpu.threads);
+ } end_element ();
+ }
+ } end_element ();
+ }
+
start_element ("os") {
start_element ("type") {
attribute ("arch", host_cpu);
@@ -620,9 +649,9 @@ generate_libvirt_xml (struct config *config, struct data_conn
*data_conns,
} end_element ();
start_element ("features") {
- if (config->flags & FLAG_ACPI) empty_element ("acpi");
- if (config->flags & FLAG_APIC) empty_element ("apic");
- if (config->flags & FLAG_PAE) empty_element ("pae");
+ if (config->cpu.acpi) empty_element ("acpi");
+ if (config->cpu.apic) empty_element ("apic");
+ if (config->cpu.pae) empty_element ("pae");
} end_element ();
start_element ("devices") {
diff --git a/p2v/cpuid.c b/p2v/cpuid.c
new file mode 100644
index 0000000..13b61b0
--- /dev/null
+++ b/p2v/cpuid.c
@@ -0,0 +1,299 @@
+/* virt-p2v
+ * Copyright (C) 2009-2017 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * Process CPU capabilities into libvirt-compatible C<E<lt>cpuE<gt>>
data.
+ *
+ * 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.
+ *
+ * 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).
+ *
+ * Note that #vCPUs and amount of RAM is handled by F<main.c>.
+ *
+ * See:
L<https://libvirt.org/formatdomain.html#elementsCPU>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <libintl.h>
+
+#ifdef HAVE_LIBVIRT
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+#endif
+
+#include <libxml/xpath.h>
+
+#include "getprogname.h"
+#include "ignore-value.h"
+
+#include "p2v.h"
+
+static void
+free_cpu_config (struct cpu_config *cpu)
+{
+ if (cpu->vendor)
+ free (cpu->vendor);
+ if (cpu->model)
+ free (cpu->model);
+ memset (cpu, 0, sizeof *cpu);
+}
+
+/**
+ * Read flags from F</proc/cpuinfo>.
+ */
+static void
+cpuinfo_flags (struct cpu_config *cpu)
+{
+ const char *cmd;
+ CLEANUP_PCLOSE FILE *fp = NULL;
+ CLEANUP_FREE char *flag = NULL;
+ ssize_t len;
+ size_t buflen = 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 }'";
+
+ fp = popen (cmd, "re");
+ if (fp == NULL) {
+ perror ("/proc/cpuinfo");
+ return;
+ }
+
+ 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;
+ }
+
+ if (errno) {
+ perror ("getline");
+ return;
+ }
+}
+
+#ifdef HAVE_LIBVIRT
+
+static void
+ignore_errors (void *ignore, virErrorPtr ignore2)
+{
+ /* empty */
+}
+
+static void libvirt_error (const char *fs, ...) __attribute__((format (printf,1,2)));
+
+static void
+libvirt_error (const char *fs, ...)
+{
+ 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);
+}
+
+/**
+ * Read the capabilities from libvirt and parse out the fields
+ * we care about.
+ */
+static void
+libvirt_capabilities (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;
+ }
+
+ /* 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);
+ }
+
+ /* 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;
+ }
+ 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 */
+
+static void
+libvirt_capabilities (struct cpu_config *cpu)
+{
+ fprintf (stderr,
+ _("%s: program was compiled without libvirt support\n"),
+ getprogname ());
+}
+
+#endif /* !HAVE_LIBVIRT */
+
+void
+get_cpu_config (struct cpu_config *cpu)
+{
+ free_cpu_config (cpu);
+ libvirt_capabilities (cpu);
+ cpuinfo_flags (cpu);
+}
diff --git a/p2v/dependencies.m4 b/p2v/dependencies.m4
index 21541b4..adbac26 100644
--- a/p2v/dependencies.m4
+++ b/p2v/dependencies.m4
@@ -25,6 +25,8 @@ 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
@@ -79,6 +81,7 @@ ifelse(DEBIAN,1,
libxml2
libgtk`'GTK_VERSION`'.0-0
libdbus-1-3
+ ifdef(`HAVE_LIBVIRT', `libvirt0')
openssh-client
qemu-utils
gawk
@@ -112,6 +115,7 @@ ifelse(ARCHLINUX,1,
libxml2
gtk`'GTK_VERSION
dbus
+ ifdef(`HAVE_LIBVIRT', `libvirt')
openssh
qemu
gawk
@@ -146,6 +150,7 @@ ifelse(SUSE,1,
libxml2
gtk`'GTK_VERSION
libdbus-1-3
+ ifdef(`HAVE_LIBVIRT', `libvirt-libs')
qemu-tools
openssh
gawk
diff --git a/p2v/main.c b/p2v/main.c
index c02d309..7f1e1c0 100644
--- a/p2v/main.c
+++ b/p2v/main.c
@@ -65,7 +65,6 @@ static void udevadm_settle (void);
static void set_config_defaults (struct config *config);
static void find_all_disks (void);
static void find_all_interfaces (void);
-static int cpuinfo_flags (void);
enum { HELP_OPTION = CHAR_MAX + 1 };
static const char options[] = "Vv";
@@ -277,7 +276,6 @@ set_config_defaults (struct config *config)
{
long i;
char hostname[257];
- int flags;
/* Default guest name is derived from the source hostname. If we
* assume that the p2v ISO gets its IP address and hostname from
@@ -341,11 +339,7 @@ set_config_defaults (struct config *config)
config->memory |= config->memory >> 32;
config->memory++;
- flags = cpuinfo_flags ();
- if (flags >= 0)
- config->flags = flags;
- else
- config->flags = 0;
+ get_cpu_config (&config->cpu);
/* Find all block devices in the system. */
if (!test_disk)
@@ -586,51 +580,3 @@ find_all_interfaces (void)
if (all_interfaces)
qsort (all_interfaces, nr_interfaces, sizeof (char *), compare);
}
-
-/**
- * Read the list of flags from F</proc/cpuinfo>.
- */
-static int
-cpuinfo_flags (void)
-{
- const char *cmd;
- CLEANUP_PCLOSE FILE *fp = NULL;
- CLEANUP_FREE char *flag = NULL;
- ssize_t len;
- size_t buflen = 0;
- int ret = 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 }'";
-
- fp = popen (cmd, "re");
- if (fp == NULL) {
- perror ("/proc/cpuinfo");
- return -1;
- }
-
- while (errno = 0, (len = getline (&flag, &buflen, fp)) != -1) {
- if (len > 0 && flag[len-1] == '\n')
- flag[len-1] = '\0';
-
- if (STREQ (flag, "acpi"))
- ret |= FLAG_ACPI;
- else if (STREQ (flag, "apic"))
- ret |= FLAG_APIC;
- else if (STREQ (flag, "pae"))
- ret |= FLAG_PAE;
- }
-
- if (errno) {
- perror ("getline");
- return -1;
- }
-
- return ret;
-}
diff --git a/p2v/p2v.h b/p2v/p2v.h
index 5223aa2..6c794f0 100644
--- a/p2v/p2v.h
+++ b/p2v/p2v.h
@@ -20,6 +20,7 @@
#define P2V_H
#include <stdio.h>
+#include <stdbool.h>
/* Send various debug information to stderr. Harmless and useful, so
* can be left enabled in production builds.
@@ -59,6 +60,17 @@ extern int feature_colours_option;
extern int force_colour;
/* config.c */
+struct cpu_config {
+ char *vendor; /* eg. "Intel" */
+ char *model; /* eg. "Broadwell" */
+ unsigned sockets; /* number of sockets */
+ unsigned cores; /* number of cores per socket */
+ unsigned threads; /* number of hyperthreads per core */
+ bool acpi;
+ bool apic;
+ bool pae;
+};
+
struct config {
char *server;
int port;
@@ -71,7 +83,7 @@ struct config {
char *guestname;
int vcpus;
uint64_t memory;
- int flags;
+ struct cpu_config cpu;
char **disks;
char **removable;
char **interfaces;
@@ -83,10 +95,6 @@ struct config {
char *output_storage;
};
-#define FLAG_ACPI 1
-#define FLAG_APIC 2
-#define FLAG_PAE 4
-
#define OUTPUT_ALLOCATION_NONE 0
#define OUTPUT_ALLOCATION_SPARSE 1
#define OUTPUT_ALLOCATION_PREALLOCATED 2
@@ -96,6 +104,9 @@ extern struct config *copy_config (struct config *);
extern void free_config (struct config *);
extern void print_config (struct config *, FILE *);
+/* cpuid.c */
+extern void get_cpu_config (struct cpu_config *);
+
/* kernel-cmdline.c */
extern char **parse_cmdline_string (const char *cmdline);
extern char **parse_proc_cmdline (void);
--
2.10.2