2011-09-23 17:00:33 +02:00
|
|
|
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
|
|
|
|
|
|
|
/***
|
|
|
|
This file is part of systemd.
|
|
|
|
|
|
|
|
Copyright 2011 Lennart Poettering
|
|
|
|
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
2012-04-12 00:20:58 +02:00
|
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
2011-09-23 17:00:33 +02:00
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
systemd 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
|
2012-04-12 00:20:58 +02:00
|
|
|
Lesser General Public License for more details.
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2012-04-12 00:20:58 +02:00
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
2011-09-23 17:00:33 +02:00
|
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "util.h"
|
2015-04-10 19:10:00 +02:00
|
|
|
#include "process-util.h"
|
2011-09-23 17:00:33 +02:00
|
|
|
#include "virt.h"
|
2013-02-14 12:26:13 +01:00
|
|
|
#include "fileio.h"
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2013-11-28 19:34:08 +01:00
|
|
|
static int detect_vm_cpuid(const char **_id) {
|
2011-09-23 17:00:33 +02:00
|
|
|
|
|
|
|
/* Both CPUID and DMI are x86 specific interfaces... */
|
2013-11-28 19:34:08 +01:00
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
2011-09-23 17:00:33 +02:00
|
|
|
|
|
|
|
static const char cpuid_vendor_table[] =
|
|
|
|
"XenVMMXenVMM\0" "xen\0"
|
|
|
|
"KVMKVMKVM\0" "kvm\0"
|
|
|
|
/* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
|
|
|
|
"VMwareVMware\0" "vmware\0"
|
|
|
|
/* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
|
|
|
|
"Microsoft Hv\0" "microsoft\0";
|
|
|
|
|
|
|
|
uint32_t eax, ecx;
|
|
|
|
union {
|
|
|
|
uint32_t sig32[3];
|
|
|
|
char text[13];
|
2013-03-25 00:59:00 +01:00
|
|
|
} sig = {};
|
2011-09-23 17:00:33 +02:00
|
|
|
const char *j, *k;
|
|
|
|
bool hypervisor;
|
|
|
|
|
|
|
|
/* http://lwn.net/Articles/301888/ */
|
|
|
|
|
|
|
|
#if defined (__i386__)
|
|
|
|
#define REG_a "eax"
|
|
|
|
#define REG_b "ebx"
|
|
|
|
#elif defined (__amd64__)
|
|
|
|
#define REG_a "rax"
|
|
|
|
#define REG_b "rbx"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* First detect whether there is a hypervisor */
|
|
|
|
eax = 1;
|
|
|
|
__asm__ __volatile__ (
|
|
|
|
/* ebx/rbx is being used for PIC! */
|
|
|
|
" push %%"REG_b" \n\t"
|
|
|
|
" cpuid \n\t"
|
|
|
|
" pop %%"REG_b" \n\t"
|
|
|
|
|
|
|
|
: "=a" (eax), "=c" (ecx)
|
|
|
|
: "0" (eax)
|
|
|
|
);
|
|
|
|
|
|
|
|
hypervisor = !!(ecx & 0x80000000U);
|
|
|
|
|
|
|
|
if (hypervisor) {
|
|
|
|
|
|
|
|
/* There is a hypervisor, see what it is */
|
|
|
|
eax = 0x40000000U;
|
|
|
|
__asm__ __volatile__ (
|
|
|
|
/* ebx/rbx is being used for PIC! */
|
|
|
|
" push %%"REG_b" \n\t"
|
|
|
|
" cpuid \n\t"
|
|
|
|
" mov %%ebx, %1 \n\t"
|
|
|
|
" pop %%"REG_b" \n\t"
|
|
|
|
|
|
|
|
: "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
|
|
|
|
: "0" (eax)
|
|
|
|
);
|
|
|
|
|
|
|
|
NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
|
|
|
|
if (streq(sig.text, j)) {
|
2013-11-28 19:34:08 +01:00
|
|
|
*_id = k;
|
|
|
|
return 1;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
2013-11-28 19:34:08 +01:00
|
|
|
|
|
|
|
*_id = "other";
|
|
|
|
return 0;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
2013-11-28 19:34:08 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-04 00:56:06 +01:00
|
|
|
static int detect_vm_devicetree(const char **_id) {
|
2015-03-31 11:08:12 +02:00
|
|
|
#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
|
2015-02-04 00:56:06 +01:00
|
|
|
_cleanup_free_ char *hvtype = NULL;
|
|
|
|
int r;
|
|
|
|
|
2015-03-31 11:08:11 +02:00
|
|
|
r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
|
2015-02-04 00:56:06 +01:00
|
|
|
if (r >= 0) {
|
|
|
|
if (streq(hvtype, "linux,kvm")) {
|
|
|
|
*_id = "kvm";
|
|
|
|
return 1;
|
2015-03-31 11:08:12 +02:00
|
|
|
} else if (strstr(hvtype, "xen")) {
|
|
|
|
*_id = "xen";
|
|
|
|
return 1;
|
2015-02-04 00:56:06 +01:00
|
|
|
}
|
ARM: detect-virt: detect QEMU/KVM
QEMU/KVM guests do not have hypervisor nodes, but they do have
fw-cfg nodes (since qemu v2.3.0-rc0). fw-cfg nodes are documented,
see kernel doc Documentation/devicetree/bindings/arm/fw-cfg.txt,
and therefore we should be able to rely on it in this detection.
Unfortunately, we currently don't have enough information in the
DT, or elsewhere, to determine if we're using KVM acceleration
with QEMU or not, so we can only report 'qemu' at this time, even
if KVM is in use. This shouldn't really matter in practice though,
because if detect-virt is used interactively it will be clear to
the user whether or not KVM acceleration is present by the overall
speed of the guest. If used by a script, then the script's behavior
should not change whether it's 'qemu' or 'kvm'. QEMU emulated
guests and QEMU/KVM guests of the same type should behave
identically, only the speed at which they run should differ.
2015-03-31 11:08:13 +02:00
|
|
|
} else if (r == -ENOENT) {
|
|
|
|
_cleanup_closedir_ DIR *dir = NULL;
|
|
|
|
struct dirent *dent;
|
|
|
|
|
|
|
|
dir = opendir("/proc/device-tree");
|
|
|
|
if (!dir) {
|
|
|
|
if (errno == ENOENT)
|
|
|
|
return 0;
|
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
FOREACH_DIRENT(dent, dir, return -errno) {
|
|
|
|
if (strstr(dent->d_name, "fw-cfg")) {
|
|
|
|
*_id = "qemu";
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2015-02-04 00:56:06 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-11-28 19:34:08 +01:00
|
|
|
static int detect_vm_dmi(const char **_id) {
|
|
|
|
|
|
|
|
/* Both CPUID and DMI are x86 specific interfaces... */
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
|
|
|
|
|
|
static const char *const dmi_vendors[] = {
|
|
|
|
"/sys/class/dmi/id/sys_vendor",
|
|
|
|
"/sys/class/dmi/id/board_vendor",
|
|
|
|
"/sys/class/dmi/id/bios_vendor"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char dmi_vendor_table[] =
|
|
|
|
"QEMU\0" "qemu\0"
|
|
|
|
/* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
|
|
|
|
"VMware\0" "vmware\0"
|
|
|
|
"VMW\0" "vmware\0"
|
|
|
|
"innotek GmbH\0" "oracle\0"
|
|
|
|
"Xen\0" "xen\0"
|
2015-09-02 03:43:32 +02:00
|
|
|
"Bochs\0" "bochs\0"
|
|
|
|
"Parallels\0" "parallels\0";
|
2013-11-28 19:34:08 +01:00
|
|
|
unsigned i;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
|
|
|
for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
|
2013-03-07 15:59:26 +01:00
|
|
|
_cleanup_free_ char *s = NULL;
|
2013-11-28 19:34:08 +01:00
|
|
|
const char *j, *k;
|
|
|
|
int r;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2013-03-07 15:59:26 +01:00
|
|
|
r = read_one_line_file(dmi_vendors[i], &s);
|
|
|
|
if (r < 0) {
|
2011-09-23 17:00:33 +02:00
|
|
|
if (r != -ENOENT)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
|
2013-10-31 16:23:06 +01:00
|
|
|
if (startswith(s, j)) {
|
2013-11-28 19:34:08 +01:00
|
|
|
*_id = k;
|
|
|
|
return 1;
|
2013-10-31 16:23:06 +01:00
|
|
|
}
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
2013-11-28 19:34:08 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns a short identifier for the various VM implementations */
|
|
|
|
int detect_vm(const char **id) {
|
2014-06-06 16:36:45 +02:00
|
|
|
_cleanup_free_ char *domcap = NULL, *cpuinfo_contents = NULL;
|
2013-12-16 01:24:14 +01:00
|
|
|
static thread_local int cached_found = -1;
|
|
|
|
static thread_local const char *cached_id = NULL;
|
2015-07-23 21:18:36 +02:00
|
|
|
const char *_id = NULL, *_id_cpuid = NULL;
|
2013-11-28 19:34:08 +01:00
|
|
|
int r;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2013-11-28 19:34:08 +01:00
|
|
|
if (_likely_(cached_found >= 0)) {
|
|
|
|
|
|
|
|
if (id)
|
|
|
|
*id = cached_id;
|
|
|
|
|
|
|
|
return cached_found;
|
|
|
|
}
|
|
|
|
|
2014-06-06 16:36:45 +02:00
|
|
|
/* Try xen capabilities file first, if not found try high-level hypervisor sysfs file:
|
2013-11-28 19:34:08 +01:00
|
|
|
*
|
2014-06-06 16:36:45 +02:00
|
|
|
* https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
|
|
|
|
r = read_one_line_file("/proc/xen/capabilities", &domcap);
|
2013-11-28 19:34:08 +01:00
|
|
|
if (r >= 0) {
|
2014-06-06 16:36:45 +02:00
|
|
|
char *cap, *i = domcap;
|
|
|
|
|
|
|
|
while ((cap = strsep(&i, ",")))
|
|
|
|
if (streq(cap, "control_d"))
|
|
|
|
break;
|
|
|
|
|
2014-07-17 11:25:37 +02:00
|
|
|
if (!cap) {
|
2013-11-28 19:34:08 +01:00
|
|
|
_id = "xen";
|
|
|
|
r = 1;
|
|
|
|
}
|
2014-06-06 16:36:45 +02:00
|
|
|
|
|
|
|
goto finish;
|
|
|
|
|
|
|
|
} else if (r == -ENOENT) {
|
|
|
|
_cleanup_free_ char *hvtype = NULL;
|
|
|
|
|
|
|
|
r = read_one_line_file("/sys/hypervisor/type", &hvtype);
|
|
|
|
if (r >= 0) {
|
|
|
|
if (streq(hvtype, "xen")) {
|
|
|
|
_id = "xen";
|
|
|
|
r = 1;
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
} else if (r != -ENOENT)
|
|
|
|
return r;
|
|
|
|
} else
|
2013-11-28 19:34:08 +01:00
|
|
|
return r;
|
|
|
|
|
|
|
|
/* this will set _id to "other" and return 0 for unknown hypervisors */
|
|
|
|
r = detect_vm_cpuid(&_id);
|
2015-07-23 21:18:36 +02:00
|
|
|
|
|
|
|
/* finish when found a known hypervisor other than kvm */
|
|
|
|
if (r < 0 || (r > 0 && !streq(_id, "kvm")))
|
2013-11-28 19:34:08 +01:00
|
|
|
goto finish;
|
|
|
|
|
2015-07-23 21:18:36 +02:00
|
|
|
_id_cpuid = _id;
|
|
|
|
|
2013-11-28 19:34:08 +01:00
|
|
|
r = detect_vm_dmi(&_id);
|
2015-07-23 21:18:36 +02:00
|
|
|
|
|
|
|
/* kvm with and without Virtualbox */
|
2015-09-02 03:43:32 +02:00
|
|
|
/* Parallels exports KVMKVMKVM leaf */
|
2015-07-23 21:18:36 +02:00
|
|
|
if (streq_ptr(_id_cpuid, "kvm")) {
|
2015-09-02 03:43:32 +02:00
|
|
|
if (r > 0 && (streq(_id, "oracle") || streq(_id, "parallels")))
|
2015-07-23 21:18:36 +02:00
|
|
|
goto finish;
|
|
|
|
|
|
|
|
_id = _id_cpuid;
|
|
|
|
r = 1;
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* information from dmi */
|
2013-11-28 19:34:08 +01:00
|
|
|
if (r != 0)
|
|
|
|
goto finish;
|
|
|
|
|
2015-02-04 00:56:06 +01:00
|
|
|
r = detect_vm_devicetree(&_id);
|
|
|
|
if (r != 0)
|
|
|
|
goto finish;
|
|
|
|
|
2013-11-28 19:34:08 +01:00
|
|
|
if (_id) {
|
|
|
|
/* "other" */
|
2013-10-31 16:23:06 +01:00
|
|
|
r = 1;
|
|
|
|
goto finish;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
|
|
|
|
2013-07-16 13:14:40 +02:00
|
|
|
/* Detect User-Mode Linux by reading /proc/cpuinfo */
|
|
|
|
r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
|
2013-10-31 16:23:06 +01:00
|
|
|
_id = "uml";
|
|
|
|
r = 1;
|
|
|
|
goto finish;
|
2013-07-16 13:14:40 +02:00
|
|
|
}
|
|
|
|
|
2014-07-18 15:13:36 +02:00
|
|
|
#if defined(__s390__)
|
|
|
|
{
|
|
|
|
_cleanup_free_ char *t = NULL;
|
|
|
|
|
|
|
|
r = get_status_field("/proc/sysinfo", "VM00 Control Program:", &t);
|
|
|
|
if (r >= 0) {
|
|
|
|
if (streq(t, "z/VM"))
|
|
|
|
_id = "zvm";
|
|
|
|
else
|
|
|
|
_id = "kvm";
|
|
|
|
r = 1;
|
|
|
|
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-10-31 16:23:06 +01:00
|
|
|
r = 0;
|
|
|
|
|
|
|
|
finish:
|
|
|
|
cached_found = r;
|
|
|
|
|
|
|
|
cached_id = _id;
|
|
|
|
if (id)
|
|
|
|
*id = _id;
|
|
|
|
|
|
|
|
return r;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int detect_container(const char **id) {
|
2013-10-31 16:23:06 +01:00
|
|
|
|
2013-12-16 01:24:14 +01:00
|
|
|
static thread_local int cached_found = -1;
|
|
|
|
static thread_local const char *cached_id = NULL;
|
2013-10-31 16:23:06 +01:00
|
|
|
|
2014-05-28 12:37:11 +02:00
|
|
|
_cleanup_free_ char *m = NULL;
|
|
|
|
const char *_id = NULL, *e = NULL;
|
2012-04-22 14:48:46 +02:00
|
|
|
int r;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2013-10-31 16:23:06 +01:00
|
|
|
if (_likely_(cached_found >= 0)) {
|
|
|
|
|
|
|
|
if (id)
|
|
|
|
*id = cached_id;
|
|
|
|
|
|
|
|
return cached_found;
|
|
|
|
}
|
|
|
|
|
2011-09-23 17:00:33 +02:00
|
|
|
/* /proc/vz exists in container and outside of the container,
|
|
|
|
* /proc/bc only outside of the container. */
|
|
|
|
if (access("/proc/vz", F_OK) >= 0 &&
|
|
|
|
access("/proc/bc", F_OK) < 0) {
|
2013-10-31 16:23:06 +01:00
|
|
|
_id = "openvz";
|
|
|
|
r = 1;
|
|
|
|
goto finish;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
|
|
|
|
2014-05-28 12:37:11 +02:00
|
|
|
if (getpid() == 1) {
|
|
|
|
/* If we are PID 1 we can just check our own
|
|
|
|
* environment variable */
|
|
|
|
|
|
|
|
e = getenv("container");
|
|
|
|
if (isempty(e)) {
|
|
|
|
r = 0;
|
|
|
|
goto finish;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* Otherwise, PID 1 dropped this information into a
|
|
|
|
* file in /run. This is better than accessing
|
|
|
|
* /proc/1/environ, since we don't need CAP_SYS_PTRACE
|
|
|
|
* for that. */
|
|
|
|
|
|
|
|
r = read_one_line_file("/run/systemd/container", &m);
|
|
|
|
if (r == -ENOENT) {
|
2014-12-10 13:23:49 +01:00
|
|
|
|
|
|
|
/* Fallback for cases where PID 1 was not
|
|
|
|
* systemd (for example, cases where
|
|
|
|
* init=/bin/sh is used. */
|
|
|
|
|
|
|
|
r = getenv_for_pid(1, "container", &m);
|
|
|
|
if (r <= 0) {
|
|
|
|
|
|
|
|
/* If that didn't work, give up,
|
|
|
|
* assume no container manager.
|
|
|
|
*
|
|
|
|
* Note: This means we still cannot
|
|
|
|
* detect containers if init=/bin/sh
|
|
|
|
* is passed but privileges dropped,
|
|
|
|
* as /proc/1/environ is only readable
|
|
|
|
* with privileges. */
|
|
|
|
|
|
|
|
r = 0;
|
|
|
|
goto finish;
|
|
|
|
}
|
2014-05-28 12:37:11 +02:00
|
|
|
}
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
e = m;
|
|
|
|
}
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2012-04-22 14:48:46 +02:00
|
|
|
/* We only recognize a selected few here, since we want to
|
|
|
|
* enforce a redacted namespace */
|
2013-10-31 16:23:06 +01:00
|
|
|
if (streq(e, "lxc"))
|
|
|
|
_id ="lxc";
|
|
|
|
else if (streq(e, "lxc-libvirt"))
|
|
|
|
_id = "lxc-libvirt";
|
|
|
|
else if (streq(e, "systemd-nspawn"))
|
|
|
|
_id = "systemd-nspawn";
|
2014-09-09 18:14:25 +02:00
|
|
|
else if (streq(e, "docker"))
|
|
|
|
_id = "docker";
|
2013-10-31 16:23:06 +01:00
|
|
|
else
|
|
|
|
_id = "other";
|
|
|
|
|
2014-05-28 12:37:11 +02:00
|
|
|
r = 1;
|
|
|
|
|
2013-10-31 16:23:06 +01:00
|
|
|
finish:
|
|
|
|
cached_found = r;
|
|
|
|
|
|
|
|
cached_id = _id;
|
|
|
|
if (id)
|
|
|
|
*id = _id;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2012-04-22 14:48:46 +02:00
|
|
|
return r;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns a short identifier for the various VM/container implementations */
|
2014-02-23 22:05:18 +01:00
|
|
|
int detect_virtualization(const char **id) {
|
2011-09-23 17:00:33 +02:00
|
|
|
int r;
|
|
|
|
|
2013-10-31 16:23:06 +01:00
|
|
|
r = detect_container(id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
if (r > 0)
|
|
|
|
return VIRTUALIZATION_CONTAINER;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2013-10-31 16:23:06 +01:00
|
|
|
r = detect_vm(id);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
if (r > 0)
|
|
|
|
return VIRTUALIZATION_VM;
|
2011-09-23 17:00:33 +02:00
|
|
|
|
2013-10-31 16:23:06 +01:00
|
|
|
return VIRTUALIZATION_NONE;
|
2011-09-23 17:00:33 +02:00
|
|
|
}
|