chiark / gitweb /
detect-virt: detect in best-heuristic order
[elogind.git] / src / basic / 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 "process-util.h"
28 #include "virt.h"
29 #include "fileio.h"
30
31 static int detect_vm_cpuid(void) {
32
33         /* CPUID is an x86 specific interface. */
34 #if defined(__i386__) || defined(__x86_64__)
35
36         static const struct {
37                 const char *cpuid;
38                 int id;
39         } cpuid_vendor_table[] = {
40                 { "XenVMMXenVMM", VIRTUALIZATION_XEN       },
41                 { "KVMKVMKVM",    VIRTUALIZATION_KVM       },
42                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
43                 { "VMwareVMware", VIRTUALIZATION_VMWARE    },
44                 /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
45                 { "Microsoft Hv", VIRTUALIZATION_MICROSOFT },
46         };
47
48         uint32_t eax, ecx;
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                 union {
77                         uint32_t sig32[3];
78                         char text[13];
79                 } sig = {};
80                 unsigned j;
81
82                 /* There is a hypervisor, see what it is */
83                 eax = 0x40000000U;
84                 __asm__ __volatile__ (
85                         /* ebx/rbx is being used for PIC! */
86                         "  push %%"REG_b"         \n\t"
87                         "  cpuid                  \n\t"
88                         "  mov %%ebx, %1          \n\t"
89                         "  pop %%"REG_b"          \n\t"
90
91                         : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
92                         : "0" (eax)
93                 );
94
95                 for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++)
96                         if (streq(sig.text, cpuid_vendor_table[j].cpuid))
97                                 return cpuid_vendor_table[j].id;
98
99                 return VIRTUALIZATION_VM_OTHER;
100         }
101 #endif
102
103         return VIRTUALIZATION_NONE;
104 }
105
106 static int detect_vm_device_tree(void) {
107 #if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
108         _cleanup_free_ char *hvtype = NULL;
109         int r;
110
111         r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
112         if (r == -ENOENT) {
113                 _cleanup_closedir_ DIR *dir = NULL;
114                 struct dirent *dent;
115
116                 dir = opendir("/proc/device-tree");
117                 if (!dir) {
118                         if (errno == ENOENT)
119                                 return VIRTUALIZATION_NONE;
120                         return -errno;
121                 }
122
123                 FOREACH_DIRENT(dent, dir, return -errno)
124                         if (strstr(dent->d_name, "fw-cfg"))
125                                 return VIRTUALIZATION_QEMU;
126
127                 return VIRTUALIZATION_NONE;
128         } else if (r < 0)
129                 return r;
130
131         if (streq(hvtype, "linux,kvm"))
132                 return VIRTUALIZATION_KVM;
133         else if (strstr(hvtype, "xen"))
134                 return VIRTUALIZATION_XEN;
135         else
136                 return VIRTUALIZATION_VM_OTHER;
137 #else
138         return VIRTUALIZATION_NONE;
139 #endif
140 }
141
142 static int detect_vm_dmi(void) {
143 #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
144
145         static const char *const dmi_vendors[] = {
146                 "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
147                 "/sys/class/dmi/id/sys_vendor",
148                 "/sys/class/dmi/id/board_vendor",
149                 "/sys/class/dmi/id/bios_vendor"
150         };
151
152         static const struct {
153                 const char *vendor;
154                 int id;
155         } dmi_vendor_table[] = {
156                 { "KVM",           VIRTUALIZATION_KVM       },
157                 { "QEMU",          VIRTUALIZATION_QEMU      },
158                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
159                 { "VMware",        VIRTUALIZATION_VMWARE    },
160                 { "VMW",           VIRTUALIZATION_VMWARE    },
161                 { "innotek GmbH",  VIRTUALIZATION_ORACLE    },
162                 { "Xen",           VIRTUALIZATION_XEN       },
163                 { "Bochs",         VIRTUALIZATION_BOCHS     },
164                 { "Parallels",     VIRTUALIZATION_PARALLELS },
165         };
166         unsigned i;
167         int r;
168
169         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
170                 _cleanup_free_ char *s = NULL;
171                 unsigned j;
172
173                 r = read_one_line_file(dmi_vendors[i], &s);
174                 if (r < 0) {
175                         if (r == -ENOENT)
176                         continue;
177
178                         return r;
179                         }
180
181                 for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++)
182                         if (startswith(s, dmi_vendor_table[j].vendor))
183                                 return dmi_vendor_table[j].id;
184         }
185 #endif
186
187         return VIRTUALIZATION_NONE;
188 }
189
190 static int detect_vm_xen(void) {
191         _cleanup_free_ char *domcap = NULL;
192         char *cap, *i;
193         int r;
194
195         r = read_one_line_file("/proc/xen/capabilities", &domcap);
196         if (r == -ENOENT)
197                 return VIRTUALIZATION_NONE;
198
199         i = domcap;
200                 while ((cap = strsep(&i, ",")))
201                         if (streq(cap, "control_d"))
202                                 break;
203
204         return cap ? VIRTUALIZATION_NONE : VIRTUALIZATION_XEN;
205                 }
206
207 static int detect_vm_hypervisor(void) {
208                 _cleanup_free_ char *hvtype = NULL;
209         int r;
210
211                 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
212         if (r == -ENOENT)
213                 return VIRTUALIZATION_NONE;
214         if (r < 0)
215                 return r;
216
217         if (streq(hvtype, "xen"))
218                 return VIRTUALIZATION_XEN;
219         else
220                 return VIRTUALIZATION_VM_OTHER;
221 }
222
223 static int detect_vm_uml(void) {
224         _cleanup_free_ char *cpuinfo_contents = NULL;
225         int r;
226
227         /* Detect User-Mode Linux by reading /proc/cpuinfo */
228         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
229         if (r < 0)
230                 return r;
231         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n"))
232                 return VIRTUALIZATION_UML;
233
234         return VIRTUALIZATION_NONE;
235 }
236
237 static int detect_vm_zvm(void) {
238
239 #if defined(__s390__)
240         _cleanup_free_ char *t = NULL;
241         int r;
242
243         r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t);
244         if (r == -ENOENT)
245                 return VIRTUALIZATION_NONE;
246         if (r < 0)
247                 return r;
248
249         if (streq(t, "z/VM"))
250                 return VIRTUALIZATION_ZVM;
251         else
252                 return VIRTUALIZATION_KVM;
253 #else
254         return VIRTUALIZATION_NONE;
255 #endif
256         }
257
258 /* Returns a short identifier for the various VM implementations */
259 int detect_vm(void) {
260         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
261         int r;
262
263         if (cached_found >= 0)
264                 return cached_found;
265
266         r = detect_vm_cpuid();
267         if (r < 0)
268                 return r;
269         if (r != VIRTUALIZATION_NONE)
270                 goto finish;
271
272         r = detect_vm_dmi();
273         if (r < 0)
274                 return r;
275         if (r != VIRTUALIZATION_NONE)
276                 goto finish;
277
278         /* x86 xen will most likely be detected by cpuid. If not (most likely
279          * because we're not an x86 guest), then we should try the xen capabilities
280          * file next. If that's not found, then we check for the high-level
281          * hypervisor sysfs file:
282          *
283          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
284
285         r = detect_vm_xen();
286         if (r < 0)
287                 return r;
288         if (r != VIRTUALIZATION_NONE)
289                 goto finish;
290
291         r = detect_vm_hypervisor();
292         if (r < 0)
293                 return r;
294         if (r != VIRTUALIZATION_NONE)
295                 goto finish;
296
297         r = detect_vm_device_tree();
298         if (r < 0)
299                 return r;
300         if (r != VIRTUALIZATION_NONE)
301                 goto finish;
302
303         r = detect_vm_uml();
304         if (r < 0)
305                 return r;
306         if (r != VIRTUALIZATION_NONE)
307                         goto finish;
308
309         r = detect_vm_zvm();
310         if (r < 0)
311                 return r;
312
313 finish:
314         cached_found = r;
315         return r;
316 }
317
318 int detect_container(void) {
319
320         static const struct {
321                 const char *value;
322                 int id;
323         } value_table[] = {
324                 { "lxc",            VIRTUALIZATION_LXC            },
325                 { "lxc-libvirt",    VIRTUALIZATION_LXC_LIBVIRT    },
326                 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
327                 { "docker",         VIRTUALIZATION_DOCKER         },
328         };
329
330         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
331         _cleanup_free_ char *m = NULL;
332         const char *e = NULL;
333         unsigned j;
334         int r;
335
336         if (cached_found >= 0)
337                 return cached_found;
338
339         /* /proc/vz exists in container and outside of the container,
340          * /proc/bc only outside of the container. */
341         if (access("/proc/vz", F_OK) >= 0 &&
342             access("/proc/bc", F_OK) < 0) {
343                 r = VIRTUALIZATION_OPENVZ;
344                 goto finish;
345         }
346
347         if (getpid() == 1) {
348                 /* If we are PID 1 we can just check our own
349                  * environment variable */
350
351                 e = getenv("container");
352                 if (isempty(e)) {
353                         r = VIRTUALIZATION_NONE;
354                         goto finish;
355                 }
356         } else {
357
358                 /* Otherwise, PID 1 dropped this information into a
359                  * file in /run. This is better than accessing
360                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
361                  * for that. */
362
363                 r = read_one_line_file("/run/systemd/container", &m);
364                 if (r == -ENOENT) {
365
366                         /* Fallback for cases where PID 1 was not
367                          * systemd (for example, cases where
368                          * init=/bin/sh is used. */
369
370                         r = getenv_for_pid(1, "container", &m);
371                         if (r <= 0) {
372
373                                 /* If that didn't work, give up,
374                                  * assume no container manager.
375                                  *
376                                  * Note: This means we still cannot
377                                  * detect containers if init=/bin/sh
378                                  * is passed but privileges dropped,
379                                  * as /proc/1/environ is only readable
380                                  * with privileges. */
381
382                                 r = VIRTUALIZATION_NONE;
383                                 goto finish;
384                         }
385                 }
386                 if (r < 0)
387                         return r;
388
389                 e = m;
390         }
391
392         for (j = 0; j < ELEMENTSOF(value_table); j++)
393                 if (streq(e, value_table[j].value)) {
394                         r = value_table[j].id;
395                         goto finish;
396                 }
397
398         r = VIRTUALIZATION_NONE;
399
400 finish:
401         cached_found = r;
402         return r;
403 }
404
405 /// UNNEEDED by elogind
406 #if 0
407 int detect_virtualization(void) {
408         int r;
409
410         r = detect_container();
411         if (r != 0)
412                 return r;
413
414         return detect_vm();
415 }
416 #endif // 0
417
418 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
419         [VIRTUALIZATION_NONE] = "none",
420         [VIRTUALIZATION_KVM] = "kvm",
421         [VIRTUALIZATION_QEMU] = "qemu",
422         [VIRTUALIZATION_BOCHS] = "bochs",
423         [VIRTUALIZATION_XEN] = "xen",
424         [VIRTUALIZATION_UML] = "uml",
425         [VIRTUALIZATION_VMWARE] = "vmware",
426         [VIRTUALIZATION_ORACLE] = "oracle",
427         [VIRTUALIZATION_MICROSOFT] = "microsoft",
428         [VIRTUALIZATION_ZVM] = "zvm",
429         [VIRTUALIZATION_PARALLELS] = "parallels",
430         [VIRTUALIZATION_VM_OTHER] = "vm-other",
431
432         [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
433         [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
434         [VIRTUALIZATION_LXC] = "lxc",
435         [VIRTUALIZATION_OPENVZ] = "openvz",
436         [VIRTUALIZATION_DOCKER] = "docker",
437         [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
438 };
439
440 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);