chiark / gitweb /
Prep v230: Apply missing upstream fixes and updates (2/8) src/basic.
[elogind.git] / src / basic / virt.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2011 Lennart Poettering
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <errno.h>
21 #include <stdint.h>
22 #include <stdlib.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 "macro.h"
31 #include "process-util.h"
32 #include "stat-util.h"
33 #include "string-table.h"
34 #include "string-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                 log_debug("Virtualization found, CPUID=%s", sig.text);
103
104                 for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++)
105                         if (streq(sig.text, cpuid_vendor_table[j].cpuid))
106                                 return cpuid_vendor_table[j].id;
107
108                 return VIRTUALIZATION_VM_OTHER;
109         }
110 #endif
111         log_debug("No virtualization found in CPUID");
112
113         return VIRTUALIZATION_NONE;
114 }
115
116 static int detect_vm_device_tree(void) {
117 #if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
118         _cleanup_free_ char *hvtype = NULL;
119         int r;
120
121         r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
122         if (r == -ENOENT) {
123                 _cleanup_closedir_ DIR *dir = NULL;
124                 struct dirent *dent;
125
126                 dir = opendir("/proc/device-tree");
127                 if (!dir) {
128                         if (errno == ENOENT) {
129                                 log_debug_errno(errno, "/proc/device-tree: %m");
130                                 return VIRTUALIZATION_NONE;
131                         }
132                         return -errno;
133                 }
134
135                 FOREACH_DIRENT(dent, dir, return -errno)
136                         if (strstr(dent->d_name, "fw-cfg")) {
137                                 log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name);
138                                 return VIRTUALIZATION_QEMU;
139                         }
140
141                 log_debug("No virtualization found in /proc/device-tree/*");
142                 return VIRTUALIZATION_NONE;
143         } else if (r < 0)
144                 return r;
145
146         log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype);
147         if (streq(hvtype, "linux,kvm"))
148                 return VIRTUALIZATION_KVM;
149         else if (strstr(hvtype, "xen"))
150                 return VIRTUALIZATION_XEN;
151         else
152                 return VIRTUALIZATION_VM_OTHER;
153 #else
154         log_debug("This platform does not support /proc/device-tree");
155         return VIRTUALIZATION_NONE;
156 #endif
157 }
158
159 static int detect_vm_dmi(void) {
160 #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
161
162         static const char *const dmi_vendors[] = {
163                 "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
164                 "/sys/class/dmi/id/sys_vendor",
165                 "/sys/class/dmi/id/board_vendor",
166                 "/sys/class/dmi/id/bios_vendor"
167         };
168
169         static const struct {
170                 const char *vendor;
171                 int id;
172         } dmi_vendor_table[] = {
173                 { "KVM",           VIRTUALIZATION_KVM       },
174                 { "QEMU",          VIRTUALIZATION_QEMU      },
175                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
176                 { "VMware",        VIRTUALIZATION_VMWARE    },
177                 { "VMW",           VIRTUALIZATION_VMWARE    },
178                 { "innotek GmbH",  VIRTUALIZATION_ORACLE    },
179                 { "Xen",           VIRTUALIZATION_XEN       },
180                 { "Bochs",         VIRTUALIZATION_BOCHS     },
181                 { "Parallels",     VIRTUALIZATION_PARALLELS },
182         };
183         unsigned i;
184         int r;
185
186         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
187                 _cleanup_free_ char *s = NULL;
188                 unsigned j;
189
190                 r = read_one_line_file(dmi_vendors[i], &s);
191                 if (r < 0) {
192                         if (r == -ENOENT)
193                                 continue;
194
195                         return r;
196                 }
197
198
199
200                 for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++)
201                         if (startswith(s, dmi_vendor_table[j].vendor)) {
202                                 log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]);
203                                 return dmi_vendor_table[j].id;
204                         }
205         }
206 #endif
207
208         log_debug("No virtualization found in DMI");
209
210         return VIRTUALIZATION_NONE;
211 }
212
213 static int detect_vm_xen(void) {
214         /* Check for Dom0 will be executed later in detect_vm_xen_dom0
215            Thats why we dont check the content of /proc/xen/capabilities here. */
216         if (access("/proc/xen/capabilities", F_OK) < 0) {
217                 log_debug("Virtualization XEN not found, /proc/xen/capabilities does not exist");
218                 return VIRTUALIZATION_NONE;
219         }
220
221         log_debug("Virtualization XEN found (/proc/xen/capabilities exists)");
222         return  VIRTUALIZATION_XEN;
223
224 }
225
226 static bool detect_vm_xen_dom0(void) {
227         _cleanup_free_ char *domcap = NULL;
228         char *cap, *i;
229         int r;
230
231         r = read_one_line_file("/proc/xen/capabilities", &domcap);
232         if (r == -ENOENT) {
233                 log_debug("Virtualization XEN not found, /proc/xen/capabilities does not exist");
234                 return false;
235         }
236         if (r < 0)
237                 return r;
238
239         i = domcap;
240         while ((cap = strsep(&i, ",")))
241                 if (streq(cap, "control_d"))
242                         break;
243         if (!cap) {
244                 log_debug("Virtualization XEN DomU found (/proc/xen/capabilites)");
245                 return false;
246         }
247
248         log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)");
249         return true;
250 }
251
252 static int detect_vm_hypervisor(void) {
253         _cleanup_free_ char *hvtype = NULL;
254         int r;
255
256         r = read_one_line_file("/sys/hypervisor/type", &hvtype);
257         if (r == -ENOENT)
258                 return VIRTUALIZATION_NONE;
259         if (r < 0)
260                 return r;
261
262         log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype);
263
264         if (streq(hvtype, "xen"))
265                 return VIRTUALIZATION_XEN;
266         else
267                 return VIRTUALIZATION_VM_OTHER;
268 }
269
270 static int detect_vm_uml(void) {
271         _cleanup_free_ char *cpuinfo_contents = NULL;
272         int r;
273
274         /* Detect User-Mode Linux by reading /proc/cpuinfo */
275         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
276         if (r < 0)
277                 return r;
278
279         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
280                 log_debug("UML virtualization found in /proc/cpuinfo");
281                 return VIRTUALIZATION_UML;
282         }
283
284         log_debug("No virtualization found in /proc/cpuinfo.");
285         return VIRTUALIZATION_NONE;
286 }
287
288 static int detect_vm_zvm(void) {
289
290 #if defined(__s390__)
291         _cleanup_free_ char *t = NULL;
292         int r;
293
294         r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t);
295         if (r == -ENOENT)
296                 return VIRTUALIZATION_NONE;
297         if (r < 0)
298                 return r;
299
300         log_debug("Virtualization %s found in /proc/sysinfo", t);
301         if (streq(t, "z/VM"))
302                 return VIRTUALIZATION_ZVM;
303         else
304                 return VIRTUALIZATION_KVM;
305 #else
306         log_debug("This platform does not support /proc/sysinfo");
307         return VIRTUALIZATION_NONE;
308 #endif
309 }
310
311 /* Returns a short identifier for the various VM implementations */
312 int detect_vm(void) {
313         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
314         int r;
315
316         if (cached_found >= 0)
317                 return cached_found;
318
319         /* We have to use the correct order here:
320          * Some virtualization technologies do use KVM hypervisor but are
321          * expected to be detected as something else. So detect DMI first.
322          *
323          * An example is Virtualbox since version 5.0, which uses KVM backend.
324          * Detection via DMI works corretly, the CPU ID would find KVM
325          * only. */
326         r = detect_vm_dmi();
327         if (r < 0)
328                 return r;
329         if (r != VIRTUALIZATION_NONE)
330                 goto finish;
331
332         r = detect_vm_cpuid();
333         if (r < 0)
334                 return r;
335         if (r != VIRTUALIZATION_NONE)
336                 goto finish;
337
338         /* x86 xen will most likely be detected by cpuid. If not (most likely
339          * because we're not an x86 guest), then we should try the xen capabilities
340          * file next. If that's not found, then we check for the high-level
341          * hypervisor sysfs file:
342          *
343          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
344
345         r = detect_vm_xen();
346         if (r < 0)
347                 return r;
348         if (r != VIRTUALIZATION_NONE)
349                 goto finish;
350
351         r = detect_vm_hypervisor();
352         if (r < 0)
353                 return r;
354         if (r != VIRTUALIZATION_NONE)
355                 goto finish;
356
357         r = detect_vm_device_tree();
358         if (r < 0)
359                 return r;
360         if (r != VIRTUALIZATION_NONE)
361                 goto finish;
362
363         r = detect_vm_uml();
364         if (r < 0)
365                 return r;
366         if (r != VIRTUALIZATION_NONE)
367                 goto finish;
368
369         r = detect_vm_zvm();
370         if (r < 0)
371                 return r;
372
373 finish:
374         /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others.
375          * In order to detect the Dom0 as not virtualization we need to
376          * double-check it */
377         if (r == VIRTUALIZATION_XEN && detect_vm_xen_dom0())
378                 r = VIRTUALIZATION_NONE;
379
380         cached_found = r;
381         log_debug("Found VM virtualization %s", virtualization_to_string(r));
382         return r;
383 }
384 #endif // 0
385
386 int detect_container(void) {
387
388         static const struct {
389                 const char *value;
390                 int id;
391         } value_table[] = {
392                 { "lxc",            VIRTUALIZATION_LXC            },
393                 { "lxc-libvirt",    VIRTUALIZATION_LXC_LIBVIRT    },
394                 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
395                 { "docker",         VIRTUALIZATION_DOCKER         },
396                 { "rkt",            VIRTUALIZATION_RKT            },
397         };
398
399         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
400         _cleanup_free_ char *m = NULL;
401         const char *e = NULL;
402         unsigned j;
403         int r;
404
405         if (cached_found >= 0)
406                 return cached_found;
407
408         /* /proc/vz exists in container and outside of the container,
409          * /proc/bc only outside of the container. */
410         if (access("/proc/vz", F_OK) >= 0 &&
411             access("/proc/bc", F_OK) < 0) {
412                 r = VIRTUALIZATION_OPENVZ;
413                 goto finish;
414         }
415
416         if (getpid() == 1) {
417                 /* If we are PID 1 we can just check our own
418                  * environment variable */
419
420                 e = getenv("container");
421                 if (isempty(e)) {
422                         r = VIRTUALIZATION_NONE;
423                         goto finish;
424                 }
425         } else {
426
427                 /* Otherwise, PID 1 dropped this information into a
428                  * file in /run. This is better than accessing
429                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
430                  * for that. */
431
432                 r = read_one_line_file("/run/systemd/container", &m);
433                 if (r == -ENOENT) {
434
435                         /* Fallback for cases where PID 1 was not
436                          * systemd (for example, cases where
437                          * init=/bin/sh is used. */
438
439                         r = getenv_for_pid(1, "container", &m);
440                         if (r <= 0) {
441
442                                 /* If that didn't work, give up,
443                                  * assume no container manager.
444                                  *
445                                  * Note: This means we still cannot
446                                  * detect containers if init=/bin/sh
447                                  * is passed but privileges dropped,
448                                  * as /proc/1/environ is only readable
449                                  * with privileges. */
450
451                                 r = VIRTUALIZATION_NONE;
452                                 goto finish;
453                         }
454                 }
455                 if (r < 0)
456                         return r;
457
458                 e = m;
459         }
460
461         for (j = 0; j < ELEMENTSOF(value_table); j++)
462                 if (streq(e, value_table[j].value)) {
463                         r = value_table[j].id;
464                         goto finish;
465                 }
466
467         r = VIRTUALIZATION_CONTAINER_OTHER;
468
469 finish:
470         log_debug("Found container virtualization %s", virtualization_to_string(r));
471         cached_found = r;
472         return r;
473 }
474
475 #if 0 /// UNNEEDED by elogind
476 int detect_virtualization(void) {
477         int r;
478
479         r = detect_container();
480         if (r == 0)
481                 r = detect_vm();
482
483         return r;
484 }
485 #endif // 0
486
487 int running_in_chroot(void) {
488         int ret;
489
490         ret = files_same("/proc/1/root", "/");
491         if (ret < 0)
492                 return ret;
493
494         return ret == 0;
495 }
496
497 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
498         [VIRTUALIZATION_NONE] = "none",
499         [VIRTUALIZATION_KVM] = "kvm",
500         [VIRTUALIZATION_QEMU] = "qemu",
501         [VIRTUALIZATION_BOCHS] = "bochs",
502         [VIRTUALIZATION_XEN] = "xen",
503         [VIRTUALIZATION_UML] = "uml",
504         [VIRTUALIZATION_VMWARE] = "vmware",
505         [VIRTUALIZATION_ORACLE] = "oracle",
506         [VIRTUALIZATION_MICROSOFT] = "microsoft",
507         [VIRTUALIZATION_ZVM] = "zvm",
508         [VIRTUALIZATION_PARALLELS] = "parallels",
509         [VIRTUALIZATION_VM_OTHER] = "vm-other",
510
511         [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
512         [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
513         [VIRTUALIZATION_LXC] = "lxc",
514         [VIRTUALIZATION_OPENVZ] = "openvz",
515         [VIRTUALIZATION_DOCKER] = "docker",
516         [VIRTUALIZATION_RKT] = "rkt",
517         [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
518 };
519
520 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);