chiark / gitweb /
extract_first_word: Refactor EXTRACT_DONT_COALESCE_SEPARATORS handling
[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         unsigned i;
161
162         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
163                 _cleanup_free_ char *s = NULL;
164                 const char *j, *k;
165                 int r;
166
167                 r = read_one_line_file(dmi_vendors[i], &s);
168                 if (r < 0) {
169                         if (r != -ENOENT)
170                                 return r;
171
172                         continue;
173                 }
174
175                 NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
176                         if (startswith(s, j)) {
177                                 *_id = k;
178                                 return 1;
179                         }
180         }
181 #endif
182
183         return 0;
184 }
185
186 /* Returns a short identifier for the various VM implementations */
187 int detect_vm(const char **id) {
188         _cleanup_free_ char *domcap = NULL, *cpuinfo_contents = NULL;
189         static thread_local int cached_found = -1;
190         static thread_local const char *cached_id = NULL;
191         const char *_id = NULL, *_id_cpuid = NULL;
192         int r;
193
194         if (_likely_(cached_found >= 0)) {
195
196                 if (id)
197                         *id = cached_id;
198
199                 return cached_found;
200         }
201
202         /* Try xen capabilities file first, if not found try high-level hypervisor sysfs file:
203          *
204          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
205         r = read_one_line_file("/proc/xen/capabilities", &domcap);
206         if (r >= 0) {
207                 char *cap, *i = domcap;
208
209                 while ((cap = strsep(&i, ",")))
210                         if (streq(cap, "control_d"))
211                                 break;
212
213                 if (!cap)  {
214                         _id = "xen";
215                         r = 1;
216                 }
217
218                 goto finish;
219
220         } else if (r == -ENOENT) {
221                 _cleanup_free_ char *hvtype = NULL;
222
223                 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
224                 if (r >= 0) {
225                         if (streq(hvtype, "xen")) {
226                                 _id = "xen";
227                                 r = 1;
228                                 goto finish;
229                         }
230                 } else if (r != -ENOENT)
231                         return r;
232         } else
233                 return r;
234
235         /* this will set _id to "other" and return 0 for unknown hypervisors */
236         r = detect_vm_cpuid(&_id);
237
238         /* finish when found a known hypervisor other than kvm */
239         if (r < 0 || (r > 0 && !streq(_id, "kvm")))
240                 goto finish;
241
242         _id_cpuid = _id;
243
244         r = detect_vm_dmi(&_id);
245
246         /* kvm with and without Virtualbox */
247         if (streq_ptr(_id_cpuid, "kvm")) {
248                 if (r > 0 && streq(_id, "oracle"))
249                         goto finish;
250
251                 _id = _id_cpuid;
252                 r = 1;
253                 goto finish;
254         }
255
256         /* information from dmi */
257         if (r != 0)
258                 goto finish;
259
260         r = detect_vm_devicetree(&_id);
261         if (r != 0)
262                 goto finish;
263
264         if (_id) {
265                 /* "other" */
266                 r = 1;
267                 goto finish;
268         }
269
270         /* Detect User-Mode Linux by reading /proc/cpuinfo */
271         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
272         if (r < 0)
273                 return r;
274         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
275                 _id = "uml";
276                 r = 1;
277                 goto finish;
278         }
279
280 #if defined(__s390__)
281         {
282                 _cleanup_free_ char *t = NULL;
283
284                 r = get_status_field("/proc/sysinfo", "VM00 Control Program:", &t);
285                 if (r >= 0) {
286                         if (streq(t, "z/VM"))
287                                 _id = "zvm";
288                         else
289                                 _id = "kvm";
290                         r = 1;
291
292                         goto finish;
293                 }
294         }
295 #endif
296
297         r = 0;
298
299 finish:
300         cached_found = r;
301
302         cached_id = _id;
303         if (id)
304                 *id = _id;
305
306         return r;
307 }
308
309 int detect_container(const char **id) {
310
311         static thread_local int cached_found = -1;
312         static thread_local const char *cached_id = NULL;
313
314         _cleanup_free_ char *m = NULL;
315         const char *_id = NULL, *e = NULL;
316         int r;
317
318         if (_likely_(cached_found >= 0)) {
319
320                 if (id)
321                         *id = cached_id;
322
323                 return cached_found;
324         }
325
326         /* /proc/vz exists in container and outside of the container,
327          * /proc/bc only outside of the container. */
328         if (access("/proc/vz", F_OK) >= 0 &&
329             access("/proc/bc", F_OK) < 0) {
330                 _id = "openvz";
331                 r = 1;
332                 goto finish;
333         }
334
335         if (getpid() == 1) {
336                 /* If we are PID 1 we can just check our own
337                  * environment variable */
338
339                 e = getenv("container");
340                 if (isempty(e)) {
341                         r = 0;
342                         goto finish;
343                 }
344         } else {
345
346                 /* Otherwise, PID 1 dropped this information into a
347                  * file in /run. This is better than accessing
348                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
349                  * for that. */
350
351                 r = read_one_line_file("/run/systemd/container", &m);
352                 if (r == -ENOENT) {
353
354                         /* Fallback for cases where PID 1 was not
355                          * systemd (for example, cases where
356                          * init=/bin/sh is used. */
357
358                         r = getenv_for_pid(1, "container", &m);
359                         if (r <= 0) {
360
361                                 /* If that didn't work, give up,
362                                  * assume no container manager.
363                                  *
364                                  * Note: This means we still cannot
365                                  * detect containers if init=/bin/sh
366                                  * is passed but privileges dropped,
367                                  * as /proc/1/environ is only readable
368                                  * with privileges. */
369
370                                 r = 0;
371                                 goto finish;
372                         }
373                 }
374                 if (r < 0)
375                         return r;
376
377                 e = m;
378         }
379
380         /* We only recognize a selected few here, since we want to
381          * enforce a redacted namespace */
382         if (streq(e, "lxc"))
383                 _id ="lxc";
384         else if (streq(e, "lxc-libvirt"))
385                 _id = "lxc-libvirt";
386         else if (streq(e, "systemd-nspawn"))
387                 _id = "systemd-nspawn";
388         else if (streq(e, "docker"))
389                 _id = "docker";
390         else
391                 _id = "other";
392
393         r = 1;
394
395 finish:
396         cached_found = r;
397
398         cached_id = _id;
399         if (id)
400                 *id = _id;
401
402         return r;
403 }
404
405 /// UNNEEDED by elogind
406 #if 0
407 /* Returns a short identifier for the various VM/container implementations */
408 int detect_virtualization(const char **id) {
409         int r;
410
411         r = detect_container(id);
412         if (r < 0)
413                 return r;
414         if (r > 0)
415                 return VIRTUALIZATION_CONTAINER;
416
417         r = detect_vm(id);
418         if (r < 0)
419                 return r;
420         if (r > 0)
421                 return VIRTUALIZATION_VM;
422
423         return VIRTUALIZATION_NONE;
424 }
425 #endif // 0