chiark / gitweb /
strv: add new STR_IN_SET() macro that operates similar to IN_SET() but for strings
[elogind.git] / src / shared / virt.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <string.h>
23 #include <errno.h>
24 #include <unistd.h>
25
26 #include "util.h"
27 #include "virt.h"
28 #include "fileio.h"
29
30 static int detect_vm_cpuid(const char **_id) {
31
32         /* Both CPUID and DMI are x86 specific interfaces... */
33 #if defined(__i386__) || defined(__x86_64__)
34
35         static const char cpuid_vendor_table[] =
36                 "XenVMMXenVMM\0"          "xen\0"
37                 "KVMKVMKVM\0"             "kvm\0"
38                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
39                 "VMwareVMware\0"          "vmware\0"
40                 /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
41                 "Microsoft Hv\0"          "microsoft\0";
42
43         uint32_t eax, ecx;
44         union {
45                 uint32_t sig32[3];
46                 char text[13];
47         } sig = {};
48         const char *j, *k;
49         bool hypervisor;
50
51         /* http://lwn.net/Articles/301888/ */
52
53 #if defined (__i386__)
54 #define REG_a "eax"
55 #define REG_b "ebx"
56 #elif defined (__amd64__)
57 #define REG_a "rax"
58 #define REG_b "rbx"
59 #endif
60
61         /* First detect whether there is a hypervisor */
62         eax = 1;
63         __asm__ __volatile__ (
64                 /* ebx/rbx is being used for PIC! */
65                 "  push %%"REG_b"         \n\t"
66                 "  cpuid                  \n\t"
67                 "  pop %%"REG_b"          \n\t"
68
69                 : "=a" (eax), "=c" (ecx)
70                 : "0" (eax)
71         );
72
73         hypervisor = !!(ecx & 0x80000000U);
74
75         if (hypervisor) {
76
77                 /* There is a hypervisor, see what it is */
78                 eax = 0x40000000U;
79                 __asm__ __volatile__ (
80                         /* ebx/rbx is being used for PIC! */
81                         "  push %%"REG_b"         \n\t"
82                         "  cpuid                  \n\t"
83                         "  mov %%ebx, %1          \n\t"
84                         "  pop %%"REG_b"          \n\t"
85
86                         : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
87                         : "0" (eax)
88                 );
89
90                 NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
91                         if (streq(sig.text, j)) {
92                                 *_id = k;
93                                 return 1;
94                         }
95
96                 *_id = "other";
97                 return 0;
98         }
99 #endif
100
101         return 0;
102 }
103
104 static int detect_vm_dmi(const char **_id) {
105
106         /* Both CPUID and DMI are x86 specific interfaces... */
107 #if defined(__i386__) || defined(__x86_64__)
108
109         static const char *const dmi_vendors[] = {
110                 "/sys/class/dmi/id/sys_vendor",
111                 "/sys/class/dmi/id/board_vendor",
112                 "/sys/class/dmi/id/bios_vendor"
113         };
114
115         static const char dmi_vendor_table[] =
116                 "QEMU\0"                  "qemu\0"
117                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
118                 "VMware\0"                "vmware\0"
119                 "VMW\0"                   "vmware\0"
120                 "Microsoft Corporation\0" "microsoft\0"
121                 "innotek GmbH\0"          "oracle\0"
122                 "Xen\0"                   "xen\0"
123                 "Bochs\0"                 "bochs\0";
124         unsigned i;
125
126         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
127                 _cleanup_free_ char *s = NULL;
128                 const char *j, *k;
129                 int r;
130
131                 r = read_one_line_file(dmi_vendors[i], &s);
132                 if (r < 0) {
133                         if (r != -ENOENT)
134                                 return r;
135
136                         continue;
137                 }
138
139                 NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
140                         if (startswith(s, j)) {
141                                 *_id = k;
142                                 return 1;
143                         }
144         }
145 #endif
146
147         return 0;
148 }
149
150 /* Returns a short identifier for the various VM implementations */
151 int detect_vm(const char **id) {
152         _cleanup_free_ char *hvtype = NULL, *cpuinfo_contents = NULL;
153         static thread_local int cached_found = -1;
154         static thread_local const char *cached_id = NULL;
155         const char *_id = NULL;
156         int r;
157
158         if (_likely_(cached_found >= 0)) {
159
160                 if (id)
161                         *id = cached_id;
162
163                 return cached_found;
164         }
165
166         /* Try high-level hypervisor sysfs file first:
167          *
168          * https://bugs.freedesktop.org/show_bug.cgi?id=61491 */
169         r = read_one_line_file("/sys/hypervisor/type", &hvtype);
170         if (r >= 0) {
171                 if (streq(hvtype, "xen")) {
172                         _id = "xen";
173                         r = 1;
174                         goto finish;
175                 }
176         } else if (r != -ENOENT)
177                 return r;
178
179         /* this will set _id to "other" and return 0 for unknown hypervisors */
180         r = detect_vm_cpuid(&_id);
181         if (r != 0)
182                 goto finish;
183
184         r = detect_vm_dmi(&_id);
185         if (r != 0)
186                 goto finish;
187
188         if (_id) {
189                 /* "other" */
190                 r = 1;
191                 goto finish;
192         }
193
194         /* Detect User-Mode Linux by reading /proc/cpuinfo */
195         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
196         if (r < 0)
197                 return r;
198         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
199                 _id = "uml";
200                 r = 1;
201                 goto finish;
202         }
203
204         r = 0;
205
206 finish:
207         cached_found = r;
208
209         cached_id = _id;
210         if (id)
211                 *id = _id;
212
213         return r;
214 }
215
216 int detect_container(const char **id) {
217
218         static thread_local int cached_found = -1;
219         static thread_local const char *cached_id = NULL;
220
221         _cleanup_free_ char *e = NULL;
222         const char *_id = NULL;
223         int r;
224
225         if (_likely_(cached_found >= 0)) {
226
227                 if (id)
228                         *id = cached_id;
229
230                 return cached_found;
231         }
232
233         /* Unfortunately many of these operations require root access
234          * in one way or another */
235
236         r = running_in_chroot();
237         if (r < 0)
238                 return r;
239         if (r > 0) {
240                 _id = "chroot";
241                 goto finish;
242         }
243
244         /* /proc/vz exists in container and outside of the container,
245          * /proc/bc only outside of the container. */
246         if (access("/proc/vz", F_OK) >= 0 &&
247             access("/proc/bc", F_OK) < 0) {
248                 _id = "openvz";
249                 r = 1;
250                 goto finish;
251         }
252
253         r = getenv_for_pid(1, "container", &e);
254         if (r < 0)
255                 return r;
256         if (r == 0)
257                 goto finish;
258
259         /* We only recognize a selected few here, since we want to
260          * enforce a redacted namespace */
261         if (streq(e, "lxc"))
262                 _id ="lxc";
263         else if (streq(e, "lxc-libvirt"))
264                 _id = "lxc-libvirt";
265         else if (streq(e, "systemd-nspawn"))
266                 _id = "systemd-nspawn";
267         else
268                 _id = "other";
269
270 finish:
271         cached_found = r;
272
273         cached_id = _id;
274         if (id)
275                 *id = _id;
276
277         return r;
278 }
279
280 /* Returns a short identifier for the various VM/container implementations */
281 int detect_virtualization(const char **id) {
282         int r;
283
284         r = detect_container(id);
285         if (r < 0)
286                 return r;
287         if (r > 0)
288                 return VIRTUALIZATION_CONTAINER;
289
290         r = detect_vm(id);
291         if (r < 0)
292                 return r;
293         if (r > 0)
294                 return VIRTUALIZATION_VM;
295
296         return VIRTUALIZATION_NONE;
297 }