From 374899b6d98d4668172ca0123085ec20ce77ee02 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 23 Mar 2017 14:19:39 +0000 Subject: [PATCH] p2v: Use lscpu instead of libvirt to get CPU information. 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 | 345 +++++++++++++++++--------------------------- p2v/dependencies.m4 | 5 - 3 files changed, 135 insertions(+), 226 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 CcpuE> data. + * Find CPU vendor, topology and some CPU flags. * - * If libvirt is available at compile time then this is quite - * simple - libvirt API C provides - * a ChostE> element which has mostly what we need. + * lscpu (from util-linux) provides CPU vendor, topology and flags. * - * Flags C, C, C still have to be parsed out of - * F 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 exists. + * + * 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 * * Note that #vCPUs and amount of RAM is handled by F. - * - * See: L */ #include @@ -40,15 +37,10 @@ #include #include #include +#include #include -#ifdef HAVE_LIBVIRT -#include -#include -#endif - -#include - +#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. + * 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'; + ret = malloc (sizeof (char *)); + if (ret == NULL) error (EXIT_FAILURE, errno, "malloc"); + ret[0] = NULL; - if (STREQ (flag, "acpi")) - cpu->acpi = 1; - else if (STREQ (flag, "apic")) - cpu->apic = 1; - else if (STREQ (flag, "pae")) - cpu->pae = 1; + 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; } -} -#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); + return ret; } /** - * Read the capabilities from libvirt and parse out the fields - * we care about. + * Read a single field from lscpu output. + * + * If the field does not exist, returns C. + */ +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 -libvirt_capabilities (struct cpu_config *cpu) +get_vendor (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; + const char *vendor = get_field (lscpu, "Vendor ID"); - /* Connect to libvirt and get the capabilities XML. */ - conn = virConnectOpenReadOnly (NULL); - if (!conn) { - libvirt_error (_("could not connect to libvirt")); - return; + 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. */ } - - /* 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 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 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 */ - +/** + * Read the CPU topology from lscpu output. + */ static void -libvirt_capabilities (struct cpu_config *cpu) +get_topology (char **lscpu, struct cpu_config *cpu) { - fprintf (stderr, - _("%s: program was compiled without libvirt support\n"), - getprogname ()); + 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)); } -#endif /* !HAVE_LIBVIRT */ +/** + * Read some important flags from lscpu output. + */ +static void +get_flags (char **lscpu, struct cpu_config *cpu) +{ + const char *flags; + + flags = get_field (lscpu, "Flags"); + if (flags) { + cpu->apic = strstr (flags, " apic ") != NULL; + cpu->pae = strstr (flags, " pae ") != NULL; + + /* 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. + */ + } +} + +/** + * Find out if the system uses ACPI. + */ +static void +get_acpi (struct cpu_config *cpu) +{ + cpu->acpi = access ("/sys/firmware/acpi", F_OK) == 0; +} 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