chiark / gitweb /
detect-virt: use /proc/device-tree
[elogind.git] / src / shared / 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 "virt.h"
28 #include "fileio.h"
29
30 static int detect_vm_cpuid(const char **_id) {
31
32         /* Both CPUID and DMI are x86 specific interfaces... */
33 #if defined(__i386__) || defined(__x86_64__)
34
35         static const char cpuid_vendor_table[] =
36                 "XenVMMXenVMM\0"          "xen\0"
37                 "KVMKVMKVM\0"             "kvm\0"
38                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
39                 "VMwareVMware\0"          "vmware\0"
40                 /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
41                 "Microsoft Hv\0"          "microsoft\0";
42
43         uint32_t eax, ecx;
44         union {
45                 uint32_t sig32[3];
46                 char text[13];
47         } sig = {};
48         const char *j, *k;
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
77                 /* There is a hypervisor, see what it is */
78                 eax = 0x40000000U;
79                 __asm__ __volatile__ (
80                         /* ebx/rbx is being used for PIC! */
81                         "  push %%"REG_b"         \n\t"
82                         "  cpuid                  \n\t"
83                         "  mov %%ebx, %1          \n\t"
84                         "  pop %%"REG_b"          \n\t"
85
86                         : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
87                         : "0" (eax)
88                 );
89
90                 NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
91                         if (streq(sig.text, j)) {
92                                 *_id = k;
93                                 return 1;
94                         }
95
96                 *_id = "other";
97                 return 0;
98         }
99 #endif
100
101         return 0;
102 }
103
104 static int detect_vm_devicetree(const char **_id) {
105 #if defined(__powerpc__) || defined(__powerpc64__)
106         _cleanup_free_ char *hvtype = NULL;
107         int r;
108
109         r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
110         if (r >= 0) {
111                 if (streq(hvtype, "linux,kvm")) {
112                         *_id = "kvm";
113                         return 1;
114                 }
115         }
116 #endif
117         return 0;
118 }
119
120 static int detect_vm_dmi(const char **_id) {
121
122         /* Both CPUID and DMI are x86 specific interfaces... */
123 #if defined(__i386__) || defined(__x86_64__)
124
125         static const char *const dmi_vendors[] = {
126                 "/sys/class/dmi/id/sys_vendor",
127                 "/sys/class/dmi/id/board_vendor",
128                 "/sys/class/dmi/id/bios_vendor"
129         };
130
131         static const char dmi_vendor_table[] =
132                 "QEMU\0"                  "qemu\0"
133                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
134                 "VMware\0"                "vmware\0"
135                 "VMW\0"                   "vmware\0"
136                 "innotek GmbH\0"          "oracle\0"
137                 "Xen\0"                   "xen\0"
138                 "Bochs\0"                 "bochs\0";
139         unsigned i;
140
141         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
142                 _cleanup_free_ char *s = NULL;
143                 const char *j, *k;
144                 int r;
145
146                 r = read_one_line_file(dmi_vendors[i], &s);
147                 if (r < 0) {
148                         if (r != -ENOENT)
149                                 return r;
150
151                         continue;
152                 }
153
154                 NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
155                         if (startswith(s, j)) {
156                                 *_id = k;
157                                 return 1;
158                         }
159         }
160 #endif
161
162         return 0;
163 }
164
165 /* Returns a short identifier for the various VM implementations */
166 int detect_vm(const char **id) {
167         _cleanup_free_ char *domcap = NULL, *cpuinfo_contents = NULL;
168         static thread_local int cached_found = -1;
169         static thread_local const char *cached_id = NULL;
170         const char *_id = NULL;
171         int r;
172
173         if (_likely_(cached_found >= 0)) {
174
175                 if (id)
176                         *id = cached_id;
177
178                 return cached_found;
179         }
180
181         /* Try xen capabilities file first, if not found try high-level hypervisor sysfs file:
182          *
183          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
184         r = read_one_line_file("/proc/xen/capabilities", &domcap);
185         if (r >= 0) {
186                 char *cap, *i = domcap;
187
188                 while ((cap = strsep(&i, ",")))
189                         if (streq(cap, "control_d"))
190                                 break;
191
192                 if (!cap)  {
193                         _id = "xen";
194                         r = 1;
195                 }
196
197                 goto finish;
198
199         } else if (r == -ENOENT) {
200                 _cleanup_free_ char *hvtype = NULL;
201
202                 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
203                 if (r >= 0) {
204                         if (streq(hvtype, "xen")) {
205                                 _id = "xen";
206                                 r = 1;
207                                 goto finish;
208                         }
209                 } else if (r != -ENOENT)
210                         return r;
211         } else
212                 return r;
213
214         /* this will set _id to "other" and return 0 for unknown hypervisors */
215         r = detect_vm_cpuid(&_id);
216         if (r != 0)
217                 goto finish;
218
219         r = detect_vm_dmi(&_id);
220         if (r != 0)
221                 goto finish;
222
223         r = detect_vm_devicetree(&_id);
224         if (r != 0)
225                 goto finish;
226
227         if (_id) {
228                 /* "other" */
229                 r = 1;
230                 goto finish;
231         }
232
233         /* Detect User-Mode Linux by reading /proc/cpuinfo */
234         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
235         if (r < 0)
236                 return r;
237         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
238                 _id = "uml";
239                 r = 1;
240                 goto finish;
241         }
242
243 #if defined(__s390__)
244         {
245                 _cleanup_free_ char *t = NULL;
246
247                 r = get_status_field("/proc/sysinfo", "VM00 Control Program:", &t);
248                 if (r >= 0) {
249                         if (streq(t, "z/VM"))
250                                 _id = "zvm";
251                         else
252                                 _id = "kvm";
253                         r = 1;
254
255                         goto finish;
256                 }
257         }
258 #endif
259
260         r = 0;
261
262 finish:
263         cached_found = r;
264
265         cached_id = _id;
266         if (id)
267                 *id = _id;
268
269         return r;
270 }
271
272 int detect_container(const char **id) {
273
274         static thread_local int cached_found = -1;
275         static thread_local const char *cached_id = NULL;
276
277         _cleanup_free_ char *m = NULL;
278         const char *_id = NULL, *e = NULL;
279         int r;
280
281         if (_likely_(cached_found >= 0)) {
282
283                 if (id)
284                         *id = cached_id;
285
286                 return cached_found;
287         }
288
289         /* /proc/vz exists in container and outside of the container,
290          * /proc/bc only outside of the container. */
291         if (access("/proc/vz", F_OK) >= 0 &&
292             access("/proc/bc", F_OK) < 0) {
293                 _id = "openvz";
294                 r = 1;
295                 goto finish;
296         }
297
298         if (getpid() == 1) {
299                 /* If we are PID 1 we can just check our own
300                  * environment variable */
301
302                 e = getenv("container");
303                 if (isempty(e)) {
304                         r = 0;
305                         goto finish;
306                 }
307         } else {
308
309                 /* Otherwise, PID 1 dropped this information into a
310                  * file in /run. This is better than accessing
311                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
312                  * for that. */
313
314                 r = read_one_line_file("/run/systemd/container", &m);
315                 if (r == -ENOENT) {
316
317                         /* Fallback for cases where PID 1 was not
318                          * systemd (for example, cases where
319                          * init=/bin/sh is used. */
320
321                         r = getenv_for_pid(1, "container", &m);
322                         if (r <= 0) {
323
324                                 /* If that didn't work, give up,
325                                  * assume no container manager.
326                                  *
327                                  * Note: This means we still cannot
328                                  * detect containers if init=/bin/sh
329                                  * is passed but privileges dropped,
330                                  * as /proc/1/environ is only readable
331                                  * with privileges. */
332
333                                 r = 0;
334                                 goto finish;
335                         }
336                 }
337                 if (r < 0)
338                         return r;
339
340                 e = m;
341         }
342
343         /* We only recognize a selected few here, since we want to
344          * enforce a redacted namespace */
345         if (streq(e, "lxc"))
346                 _id ="lxc";
347         else if (streq(e, "lxc-libvirt"))
348                 _id = "lxc-libvirt";
349         else if (streq(e, "systemd-nspawn"))
350                 _id = "systemd-nspawn";
351         else if (streq(e, "docker"))
352                 _id = "docker";
353         else
354                 _id = "other";
355
356         r = 1;
357
358 finish:
359         cached_found = r;
360
361         cached_id = _id;
362         if (id)
363                 *id = _id;
364
365         return r;
366 }
367
368 /* Returns a short identifier for the various VM/container implementations */
369 int detect_virtualization(const char **id) {
370         int r;
371
372         r = detect_container(id);
373         if (r < 0)
374                 return r;
375         if (r > 0)
376                 return VIRTUALIZATION_CONTAINER;
377
378         r = detect_vm(id);
379         if (r < 0)
380                 return r;
381         if (r > 0)
382                 return VIRTUALIZATION_VM;
383
384         return VIRTUALIZATION_NONE;
385 }