chiark / gitweb /
virt: add comment about order in virtualization detection
[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         /* We have to use the correct order here:
274          * Some virtualization technologies do use KVM hypervisor but are
275          * expected to be detected as something else. So detect DMI first.
276          *
277          * An example is Virtualbox since version 5.0, which uses KVM backend.
278          * Detection via DMI works corretly, the CPU ID would find KVM
279          * only. */
280         r = detect_vm_dmi();
281         if (r < 0)
282                 return r;
283         if (r != VIRTUALIZATION_NONE)
284                 goto finish;
285
286         r = detect_vm_cpuid();
287         if (r < 0)
288                 return r;
289         if (r != VIRTUALIZATION_NONE)
290                 goto finish;
291
292         /* x86 xen will most likely be detected by cpuid. If not (most likely
293          * because we're not an x86 guest), then we should try the xen capabilities
294          * file next. If that's not found, then we check for the high-level
295          * hypervisor sysfs file:
296          *
297          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
298
299         r = detect_vm_xen();
300         if (r < 0)
301                 return r;
302         if (r != VIRTUALIZATION_NONE)
303                 goto finish;
304
305         r = detect_vm_hypervisor();
306         if (r < 0)
307                 return r;
308         if (r != VIRTUALIZATION_NONE)
309                 goto finish;
310
311         r = detect_vm_device_tree();
312         if (r < 0)
313                 return r;
314         if (r != VIRTUALIZATION_NONE)
315                 goto finish;
316
317         r = detect_vm_uml();
318         if (r < 0)
319                 return r;
320         if (r != VIRTUALIZATION_NONE)
321                         goto finish;
322
323         r = detect_vm_zvm();
324         if (r < 0)
325                 return r;
326
327 finish:
328         cached_found = r;
329         return r;
330 }
331 #endif // 0
332
333 int detect_container(void) {
334
335         static const struct {
336                 const char *value;
337                 int id;
338         } value_table[] = {
339                 { "lxc",            VIRTUALIZATION_LXC            },
340                 { "lxc-libvirt",    VIRTUALIZATION_LXC_LIBVIRT    },
341                 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
342                 { "docker",         VIRTUALIZATION_DOCKER         },
343                 { "rkt",            VIRTUALIZATION_RKT            },
344         };
345
346         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
347         _cleanup_free_ char *m = NULL;
348         const char *e = NULL;
349         unsigned j;
350         int r;
351
352         if (cached_found >= 0)
353                 return cached_found;
354
355         /* /proc/vz exists in container and outside of the container,
356          * /proc/bc only outside of the container. */
357         if (access("/proc/vz", F_OK) >= 0 &&
358             access("/proc/bc", F_OK) < 0) {
359                 r = VIRTUALIZATION_OPENVZ;
360                 goto finish;
361         }
362
363         if (getpid() == 1) {
364                 /* If we are PID 1 we can just check our own
365                  * environment variable */
366
367                 e = getenv("container");
368                 if (isempty(e)) {
369                         r = VIRTUALIZATION_NONE;
370                         goto finish;
371                 }
372         } else {
373
374                 /* Otherwise, PID 1 dropped this information into a
375                  * file in /run. This is better than accessing
376                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
377                  * for that. */
378
379                 r = read_one_line_file("/run/systemd/container", &m);
380                 if (r == -ENOENT) {
381
382                         /* Fallback for cases where PID 1 was not
383                          * systemd (for example, cases where
384                          * init=/bin/sh is used. */
385
386                         r = getenv_for_pid(1, "container", &m);
387                         if (r <= 0) {
388
389                                 /* If that didn't work, give up,
390                                  * assume no container manager.
391                                  *
392                                  * Note: This means we still cannot
393                                  * detect containers if init=/bin/sh
394                                  * is passed but privileges dropped,
395                                  * as /proc/1/environ is only readable
396                                  * with privileges. */
397
398                                 r = VIRTUALIZATION_NONE;
399                                 goto finish;
400                         }
401                 }
402                 if (r < 0)
403                         return r;
404
405                 e = m;
406         }
407
408         for (j = 0; j < ELEMENTSOF(value_table); j++)
409                 if (streq(e, value_table[j].value)) {
410                         r = value_table[j].id;
411                         goto finish;
412                 }
413
414         r = VIRTUALIZATION_CONTAINER_OTHER;
415
416 finish:
417         cached_found = r;
418         return r;
419 }
420
421 #if 0 /// UNNEEDED by elogind
422 int detect_virtualization(void) {
423         int r;
424
425         r = detect_container();
426         if (r != 0)
427                 return r;
428
429         return detect_vm();
430 }
431 #endif // 0
432
433 int running_in_chroot(void) {
434         int ret;
435
436         ret = files_same("/proc/1/root", "/");
437         if (ret < 0)
438                 return ret;
439
440         return ret == 0;
441 }
442
443 #if 0 /// UNNEEDED by elogind
444 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
445         [VIRTUALIZATION_NONE] = "none",
446         [VIRTUALIZATION_KVM] = "kvm",
447         [VIRTUALIZATION_QEMU] = "qemu",
448         [VIRTUALIZATION_BOCHS] = "bochs",
449         [VIRTUALIZATION_XEN] = "xen",
450         [VIRTUALIZATION_UML] = "uml",
451         [VIRTUALIZATION_VMWARE] = "vmware",
452         [VIRTUALIZATION_ORACLE] = "oracle",
453         [VIRTUALIZATION_MICROSOFT] = "microsoft",
454         [VIRTUALIZATION_ZVM] = "zvm",
455         [VIRTUALIZATION_PARALLELS] = "parallels",
456         [VIRTUALIZATION_VM_OTHER] = "vm-other",
457
458         [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
459         [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
460         [VIRTUALIZATION_LXC] = "lxc",
461         [VIRTUALIZATION_OPENVZ] = "openvz",
462         [VIRTUALIZATION_DOCKER] = "docker",
463         [VIRTUALIZATION_RKT] = "rkt",
464         [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
465 };
466
467 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);
468 #endif // 0