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