]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/virt.c
Merge pull request #1112 from poettering/sd-bus-container-fixes
[thirdparty/systemd.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 /* 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 }