]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lscpu: detect more hypervisor vendors
authorOndrej Oprala <ooprala@redhat.com>
Mon, 22 Apr 2013 12:18:24 +0000 (08:18 -0400)
committerKarel Zak <kzak@redhat.com>
Mon, 20 May 2013 14:30:23 +0000 (16:30 +0200)
[kzak@redhat.com: - cleanup coding style,
                  - use path_exist()]

Signed-off-by: Ondrej Oprala <ooprala@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
include/pathnames.h
sys-utils/Makemodule.am
sys-utils/lscpu-dmi.c [new file with mode: 0644]
sys-utils/lscpu.c
sys-utils/lscpu.h [new file with mode: 0644]

index d7b36efcdaf8bd84313d062e7adf87dc18543816..cc8a20b16e0d89dd8c1ddef9e3b61ad80e16762c 100644 (file)
 # define _PATH_DEV             "/dev/"
 #endif
 
+#define _PATH_DEV_MEM          "/dev/mem"
+
 #define _PATH_DEV_LOOP         "/dev/loop"
 #define _PATH_DEV_LOOPCTL      "/dev/loop-control"
 #define _PATH_DEV_TTY          "/dev/tty"
index 4139fcf659675c597c00c277b6bdf7a57da4c870..977be5aff73055cc99caab97cd51c772e8b0eb59 100644 (file)
@@ -239,7 +239,10 @@ endif
 
 if BUILD_LSCPU
 usrbin_exec_PROGRAMS += lscpu
-lscpu_SOURCES = sys-utils/lscpu.c
+lscpu_SOURCES = \
+       sys-utils/lscpu.c \
+       sys-utils/lscpu.h \
+       sys-utils/lscpu-dmi.c
 lscpu_LDADD = $(LDADD) libcommon.la
 dist_man_MANS += sys-utils/lscpu.1
 endif
diff --git a/sys-utils/lscpu-dmi.c b/sys-utils/lscpu-dmi.c
new file mode 100644 (file)
index 0000000..21e024b
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * lscpu-dmi - Module to parse SMBIOS information
+ *
+ * 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 would 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.
+ *
+ * Code originally taken from the dmidecode utility and slightly rewritten
+ * to suite the needs of lscpu
+ */
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "c.h"
+#include "pathnames.h"
+#include "all-io.h"
+#include "lscpu.h"
+
+#define WORD(x) (uint16_t)(*(const uint16_t *)(x))
+#define DWORD(x) (uint32_t)(*(const uint32_t *)(x))
+
+struct dmi_header
+{
+       uint8_t type;
+       uint8_t length;
+       uint16_t handle;
+       uint8_t *data;
+};
+
+static int checksum(const uint8_t *buf, size_t len)
+{
+       uint8_t sum = 0;
+       size_t a;
+
+       for (a = 0; a < len; a++)
+               sum += buf[a];
+       return (sum == 0);
+}
+
+static void *get_mem_chunk(size_t base, size_t len, const char *devmem)
+{
+       void *p;
+       int fd;
+
+       if ((fd = open(devmem, O_RDONLY)) == -1)
+               return NULL;
+       if ((p = malloc(len)) == NULL)
+               return NULL;
+       if (lseek(fd, base, SEEK_SET) == -1) {
+               free(p);
+               return NULL;
+       }
+       if (read_all(fd, p, len) == -1) {
+               free(p);
+               return NULL;
+       }
+
+       close(fd);
+       return p;
+}
+
+static void to_dmi_header(struct dmi_header *h, uint8_t *data)
+{
+       h->type = data[0];
+       h->length = data[1];
+       h->handle = WORD(data + 2);
+       h->data = data;
+}
+
+static char *dmi_string(const struct dmi_header *dm, uint8_t s)
+{
+       char *bp = (char *)dm->data;
+
+       if (s == 0)
+               return NULL;
+
+       bp += dm->length;
+       while (s > 1 && *bp)
+       {
+               bp += strlen(bp);
+               bp++;
+               s--;
+       }
+
+       if (!*bp)
+               return NULL;
+
+       return bp;
+}
+
+static int hypervisor_from_dmi_table(uint32_t base, uint16_t len,
+                               uint16_t num, const char *devmem)
+{
+       uint8_t *buf = NULL;
+       uint8_t *data;
+       int i = 0;
+       char *vendor = NULL;
+       char *product = NULL;
+       char *manufacturer = NULL;
+
+       if ((buf = get_mem_chunk(base, len, devmem)) == NULL)
+               return HYPER_NONE;
+       data = buf;
+
+        /* 4 is the length of an SMBIOS structure header */
+       while (i < num && data + 4 <= buf + len) {
+               uint8_t *next;
+               struct dmi_header h;
+
+               to_dmi_header(&h, data);
+
+               /*
+                * If a short entry is found (less than 4 bytes), not only it
+                * is invalid, but we cannot reliably locate the next entry.
+                * Better stop at this point.
+                */
+               if (h.length < 4)
+                       return HYPER_NONE;
+
+               /* look for the next handle */
+               next = data + h.length;
+               while (next - buf + 1 < len && (next[0] != 0 || next[1] != 0))
+                       next++;
+               next += 2;
+               switch (h.type) {
+                       case 0:
+                               vendor = dmi_string(&h, data[0x04]);
+                               break;
+                       case 1:
+                               manufacturer = dmi_string(&h, data[0x04]);
+                               product = dmi_string(&h, data[0x05]);
+                               break;
+                       default:
+                               break;
+               }
+
+               data = next;
+               i++;
+       }
+       if (!strcmp(manufacturer, "innotek GmbH"))
+               return HYPER_INNOTEK;
+       else if (strstr(manufacturer, "HITACHI") && strstr(product, "LPAR"))
+               return HYPER_HITACHI;
+       else if (!strcmp(vendor, "Parallels"))
+               return HYPER_PARALLELS;
+
+       free(buf);
+       return HYPER_NONE;
+}
+
+static int hypervisor_decode_legacy(uint8_t *buf, const char *devmem)
+{
+       if (!checksum(buf, 0x0F))
+               return HYPER_NONE;
+
+       return hypervisor_from_dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06),
+                        WORD(buf + 0x0C),
+               devmem);
+}
+
+static int hypervisor_decode_smbios(uint8_t *buf, const char *devmem)
+{
+       if (!checksum(buf, buf[0x05])
+           || memcmp(buf + 0x10, "_DMI_", 5) != 0
+           || !checksum(buf + 0x10, 0x0F))
+               return -1;
+
+       return hypervisor_from_dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16),
+                        WORD(buf + 0x1C),
+               devmem);
+}
+
+/*
+ * Probe for EFI interface
+ */
+#define EFI_NOT_FOUND   (-1)
+#define EFI_NO_SMBIOS   (-2)
+static int address_from_efi(size_t *address)
+{
+       FILE *tab;
+       char linebuf[64];
+       int ret;
+
+       *address = 0; /* Prevent compiler warning */
+
+       /*
+        * Linux up to 2.6.6: /proc/efi/systab
+        * Linux 2.6.7 and up: /sys/firmware/efi/systab
+        */
+       if (!(tab = fopen("/sys/firmware/efi/systab", "r")) &&
+           !(tab = fopen("/proc/efi/systab", "r")))
+               return EFI_NOT_FOUND;           /* No EFI interface */
+
+       ret = EFI_NO_SMBIOS;
+       while ((fgets(linebuf, sizeof(linebuf) - 1, tab)) != NULL) {
+               char *addrp = strchr(linebuf, '=');
+               *(addrp++) = '\0';
+               if (strcmp(linebuf, "SMBIOS") == 0) {
+                       *address = strtoul(addrp, NULL, 0);
+                       ret = 0;
+                       break;
+               }
+       }
+
+       fclose(tab);
+       return ret;
+}
+
+int read_hypervisor_dmi(void)
+{
+       int ret = HYPER_NONE;
+       uint8_t *buf = NULL;
+       size_t fp = 0;
+
+       if (sizeof(uint8_t) != 1
+           || sizeof(uint16_t) != 2
+           || sizeof(uint32_t) != 4
+           || '\0' != 0)
+               return ret;
+
+       /* First try EFI (ia64, Intel-based Mac) */
+       switch (address_from_efi(&fp)) {
+               case EFI_NOT_FOUND:
+                       goto memory_scan;
+               case EFI_NO_SMBIOS:
+                       goto exit_free;
+       }
+
+       buf = get_mem_chunk(fp, 0x20, _PATH_DEV_MEM);
+       if (!buf)
+               goto exit_free;
+
+       if (hypervisor_decode_smbios(buf, _PATH_DEV_MEM))
+               goto done;
+
+memory_scan:
+       /* Fallback to memory scan (x86, x86_64) */
+       buf = get_mem_chunk(0xF0000, 0x10000, _PATH_DEV_MEM);
+       if (!buf)
+               goto exit_free;
+
+       for (fp = 0; fp <= 0xFFF0; fp += 16) {
+               if (memcmp(buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) {
+                       if ((ret = hypervisor_decode_smbios(buf + fp,
+                                                _PATH_DEV_MEM)) == -1)
+                               fp += 16;
+
+               } else if (memcmp(buf + fp, "_DMI_", 5) == 0)
+                       ret = hypervisor_decode_legacy(buf + fp, _PATH_DEV_MEM);
+       }
+
+done:
+       free(buf);
+exit_free:
+       return ret;
+}
index 2e08f66a9c7be03444d601754330c2fb67fbd011..58f3248e9086593fa3a21375cbabeb1b9afc73ec 100644 (file)
@@ -43,6 +43,7 @@
 #include "path.h"
 #include "closestream.h"
 #include "optutils.h"
+#include "lscpu.h"
 
 #define CACHE_MAX 100
 
 #define _PATH_PROC_CPUINFO     "/proc/cpuinfo"
 #define _PATH_PROC_PCIDEVS     "/proc/bus/pci/devices"
 #define _PATH_PROC_SYSINFO     "/proc/sysinfo"
+#define _PATH_PROC_STATUS      "/proc/self/status"
+#define _PATH_PROC_VZ  "/proc/vz"
+#define _PATH_PROC_BC  "/proc/bc"
+#define _PATH_DEV_MEM          "/dev/mem"
 
 /* virtualization types */
 enum {
@@ -67,22 +72,18 @@ const char *virt_types[] = {
        [VIRT_FULL]     = N_("full")
 };
 
-/* hypervisor vendors */
-enum {
-       HYPER_NONE      = 0,
-       HYPER_XEN,
-       HYPER_KVM,
-       HYPER_MSHV,
-       HYPER_VMWARE,
-       HYPER_IBM
-};
 const char *hv_vendors[] = {
        [HYPER_NONE]    = NULL,
        [HYPER_XEN]     = "Xen",
        [HYPER_KVM]     = "KVM",
        [HYPER_MSHV]    = "Microsoft",
        [HYPER_VMWARE]  = "VMware",
-       [HYPER_IBM]     = "IBM"
+       [HYPER_IBM]     = "IBM",
+       [HYPER_VSERVER] = "Linux-VServer",
+       [HYPER_UML]     = "User-mode Linux",
+       [HYPER_INNOTEK] = "Innotek GmbH",
+       [HYPER_HITACHI] = "Hitachi",
+       [HYPER_PARALLELS]       = "Parallels"
 };
 
 /* CPU modes */
@@ -529,17 +530,21 @@ read_hypervisor_cpuid(struct lscpu_desc *desc __attribute__((__unused__)))
 static void
 read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod)
 {
-       if (mod->system != SYSTEM_SNAPSHOT)
+       FILE *fd;
+
+       if (mod->system != SYSTEM_SNAPSHOT) {
                read_hypervisor_cpuid(desc);
+               if (!desc->hyper)
+                       desc->hyper = read_hypervisor_dmi();
+       }
 
        if (desc->hyper)
-               /* hvm */
                desc->virtype = VIRT_FULL;
 
+       /* Xen para-virt or dom0 */
        else if (path_exist(_PATH_PROC_XEN)) {
-               /* Xen para-virt or dom0 */
-               FILE *fd = path_fopen("r", 0, _PATH_PROC_XENCAP);
                int dom0 = 0;
+               fd = path_fopen("r", 0, _PATH_PROC_XENCAP);
 
                if (fd) {
                        char buf[256];
@@ -552,10 +557,12 @@ read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod)
                desc->virtype = dom0 ? VIRT_NONE : VIRT_PARA;
                desc->hyper = HYPER_XEN;
 
+       /* Xen full-virt on non-x86_64 */
        } else if (has_pci_device(0x5853, 0x0001)) {
-               /* Xen full-virt on non-x86_64 */
                desc->hyper = HYPER_XEN;
                desc->virtype = VIRT_FULL;
+
+       /* IBM PR/SM */
        } else if (path_exist(_PATH_PROC_SYSINFO)) {
                FILE *fd = path_fopen("r", 0, _PATH_PROC_SYSINFO);
                char buf[BUFSIZ];
@@ -591,6 +598,41 @@ read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod)
                }
                fclose(fd);
        }
+
+       /* OpenVZ/Virtuozzo - /proc/vz dir should exist
+        *                    /proc/bc should not */
+       else if (path_exist(_PATH_PROC_VZ) && !path_exist(_PATH_PROC_BC))
+               desc->hyper = HYPER_PARALLELS;
+
+       /* IBM */
+       else if (desc->vendor &&
+                (strcmp(desc->vendor, "PowerVM Lx86") == 0 ||
+                 strcmp(desc->vendor, "IBM/S390") == 0))
+               desc->hyper = HYPER_IBM;
+
+       /* User-mode-linux */
+       else if (desc->modelname && strstr(desc->modelname, "UML"))
+               desc->hyper = HYPER_UML;
+
+       /* Linux-VServer */
+       else if (path_exist(_PATH_PROC_STATUS)) {
+               char buf[BUFSIZ];
+               char *val = NULL;
+
+               fd = path_fopen("r", 0, _PATH_PROC_STATUS);
+               while (fgets(buf, sizeof(buf), fd) != NULL) {
+                       if (lookup(buf, "VxID", &val))
+                               break;
+               }
+               fclose(fd);
+
+               if (val) {
+                       while (isdigit(*val))
+                               ++val;
+                       if (!*val)
+                               desc->hyper = HYPER_VSERVER;
+               }
+       }
 }
 
 /* add @set to the @ary, unnecessary set is deallocated. */
diff --git a/sys-utils/lscpu.h b/sys-utils/lscpu.h
new file mode 100644 (file)
index 0000000..312038f
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef LSCPU_H
+#define LSCPU_H
+
+/* hypervisor vendors */
+enum {
+       HYPER_NONE      = 0,
+       HYPER_XEN,
+       HYPER_KVM,
+       HYPER_MSHV,
+       HYPER_VMWARE,
+       HYPER_IBM,              /* sys-z powervm */
+       HYPER_VSERVER,
+       HYPER_UML,
+       HYPER_INNOTEK,          /* VBOX */
+       HYPER_HITACHI,
+       HYPER_PARALLELS         /* OpenVZ/VIrtuozzo */
+};
+
+extern int read_hypervisor_dmi(void);
+
+#endif /* LSCPU_H */