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