chiark / gitweb /
logind: minor clean-ups
[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         /* Both CPUID and DMI are x86 specific interfaces... */
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
144         /* Both CPUID and DMI are x86 specific interfaces... */
145 #if defined(__i386__) || defined(__x86_64__)
146
147         static const char *const dmi_vendors[] = {
148                 "/sys/class/dmi/id/sys_vendor",
149                 "/sys/class/dmi/id/board_vendor",
150                 "/sys/class/dmi/id/bios_vendor"
151         };
152
153         static const struct {
154                 const char *vendor;
155                 int id;
156         } dmi_vendor_table[] = {
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         /* Try xen capabilities file first, if not found try
267          * high-level hypervisor sysfs file:
268          *
269          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
270
271         r = detect_vm_xen();
272         if (r < 0)
273                 return r;
274         if (r != VIRTUALIZATION_NONE)
275                 goto finish;
276
277         r = detect_vm_dmi();
278         if (r < 0)
279                 return r;
280         if (r != VIRTUALIZATION_NONE)
281                 goto finish;
282
283         r = detect_vm_cpuid();
284         if (r < 0)
285                 return r;
286         if (r != VIRTUALIZATION_NONE)
287                 goto finish;
288
289         r = detect_vm_hypervisor();
290         if (r < 0)
291                 return r;
292         if (r != VIRTUALIZATION_NONE)
293                 goto finish;
294
295         r = detect_vm_device_tree();
296         if (r < 0)
297                 return r;
298         if (r != VIRTUALIZATION_NONE)
299                 goto finish;
300
301         r = detect_vm_uml();
302         if (r < 0)
303                 return r;
304         if (r != VIRTUALIZATION_NONE)
305                         goto finish;
306
307         r = detect_vm_zvm();
308         if (r < 0)
309                 return r;
310
311 finish:
312         cached_found = r;
313         return r;
314 }
315
316 int detect_container(void) {
317
318         static const struct {
319                 const char *value;
320                 int id;
321         } value_table[] = {
322                 { "lxc",            VIRTUALIZATION_LXC            },
323                 { "lxc-libvirt",    VIRTUALIZATION_LXC_LIBVIRT    },
324                 { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN },
325                 { "docker",         VIRTUALIZATION_DOCKER         },
326         };
327
328         static thread_local int cached_found = _VIRTUALIZATION_INVALID;
329         _cleanup_free_ char *m = NULL;
330         const char *e = NULL;
331         unsigned j;
332         int r;
333
334         if (cached_found >= 0)
335                 return cached_found;
336
337         /* /proc/vz exists in container and outside of the container,
338          * /proc/bc only outside of the container. */
339         if (access("/proc/vz", F_OK) >= 0 &&
340             access("/proc/bc", F_OK) < 0) {
341                 r = VIRTUALIZATION_OPENVZ;
342                 goto finish;
343         }
344
345         if (getpid() == 1) {
346                 /* If we are PID 1 we can just check our own
347                  * environment variable */
348
349                 e = getenv("container");
350                 if (isempty(e)) {
351                         r = VIRTUALIZATION_NONE;
352                         goto finish;
353                 }
354         } else {
355
356                 /* Otherwise, PID 1 dropped this information into a
357                  * file in /run. This is better than accessing
358                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
359                  * for that. */
360
361                 r = read_one_line_file("/run/systemd/container", &m);
362                 if (r == -ENOENT) {
363
364                         /* Fallback for cases where PID 1 was not
365                          * systemd (for example, cases where
366                          * init=/bin/sh is used. */
367
368                         r = getenv_for_pid(1, "container", &m);
369                         if (r <= 0) {
370
371                                 /* If that didn't work, give up,
372                                  * assume no container manager.
373                                  *
374                                  * Note: This means we still cannot
375                                  * detect containers if init=/bin/sh
376                                  * is passed but privileges dropped,
377                                  * as /proc/1/environ is only readable
378                                  * with privileges. */
379
380                                 r = VIRTUALIZATION_NONE;
381                                 goto finish;
382                         }
383                 }
384                 if (r < 0)
385                         return r;
386
387                 e = m;
388         }
389
390         for (j = 0; j < ELEMENTSOF(value_table); j++)
391                 if (streq(e, value_table[j].value)) {
392                         r = value_table[j].id;
393                         goto finish;
394                 }
395
396         r = VIRTUALIZATION_NONE;
397
398 finish:
399         cached_found = r;
400         return r;
401 }
402
403 /// UNNEEDED by elogind
404 #if 0
405 int detect_virtualization(void) {
406         int r;
407
408         r = detect_container();
409         if (r != 0)
410                 return r;
411
412         return detect_vm();
413 }
414 #endif // 0
415
416 static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
417         [VIRTUALIZATION_NONE] = "none",
418         [VIRTUALIZATION_KVM] = "kvm",
419         [VIRTUALIZATION_QEMU] = "qemu",
420         [VIRTUALIZATION_BOCHS] = "bochs",
421         [VIRTUALIZATION_XEN] = "xen",
422         [VIRTUALIZATION_UML] = "uml",
423         [VIRTUALIZATION_VMWARE] = "vmware",
424         [VIRTUALIZATION_ORACLE] = "oracle",
425         [VIRTUALIZATION_MICROSOFT] = "microsoft",
426         [VIRTUALIZATION_ZVM] = "zvm",
427         [VIRTUALIZATION_PARALLELS] = "parallels",
428         [VIRTUALIZATION_VM_OTHER] = "vm-other",
429
430         [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
431         [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
432         [VIRTUALIZATION_LXC] = "lxc",
433         [VIRTUALIZATION_OPENVZ] = "openvz",
434         [VIRTUALIZATION_DOCKER] = "docker",
435         [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
436 };
437
438 DEFINE_STRING_TABLE_LOOKUP(virtualization, int);