]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/virt.c
Merge pull request #18007 from fw-strlen/ipv6_masq_and_dnat
[thirdparty/systemd.git] / src / basic / virt.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
b52aae1d 2
d31b0033
MG
3#if defined(__i386__) || defined(__x86_64__)
4#include <cpuid.h>
5#endif
b52aae1d 6#include <errno.h>
11c3a366
TA
7#include <stdint.h>
8#include <stdlib.h>
b52aae1d
LP
9#include <unistd.h>
10
b5efdb8a 11#include "alloc-util.h"
0e13779d 12#include "cgroup-util.h"
ade61d3b 13#include "dirent-util.h"
295ee984 14#include "env-util.h"
ade61d3b 15#include "fd-util.h"
07630cea 16#include "fileio.h"
11c3a366 17#include "macro.h"
93cc7779 18#include "process-util.h"
b5efdb8a 19#include "stat-util.h"
8b43440b 20#include "string-table.h"
07630cea 21#include "string-util.h"
b52aae1d
LP
22#include "virt.h"
23
680120bb 24#if defined(__i386__) || defined(__x86_64__)
735ea55f
YW
25static const char *const vm_table[_VIRTUALIZATION_MAX] = {
26 [VIRTUALIZATION_XEN] = "XenVMMXenVMM",
27 [VIRTUALIZATION_KVM] = "KVMKVMKVM",
28 [VIRTUALIZATION_QEMU] = "TCGTCGTCGTCG",
29 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
30 [VIRTUALIZATION_VMWARE] = "VMwareVMware",
31 /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */
32 [VIRTUALIZATION_MICROSOFT] = "Microsoft Hv",
33 /* https://wiki.freebsd.org/bhyve */
34 [VIRTUALIZATION_BHYVE] = "bhyve bhyve ",
35 [VIRTUALIZATION_QNX] = "QNXQVMBSQG",
36 /* https://projectacrn.org */
37 [VIRTUALIZATION_ACRN] = "ACRNACRNACRN",
38};
39
40DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(vm, int);
680120bb 41#endif
735ea55f 42
75f86906 43static int detect_vm_cpuid(void) {
b52aae1d 44
2ef8a4c4 45 /* CPUID is an x86 specific interface. */
bdb628ee 46#if defined(__i386__) || defined(__x86_64__)
b52aae1d 47
d31b0033 48 uint32_t eax, ebx, ecx, edx;
b52aae1d
LP
49 bool hypervisor;
50
51 /* http://lwn.net/Articles/301888/ */
b52aae1d 52
b52aae1d 53 /* First detect whether there is a hypervisor */
d31b0033
MG
54 if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
55 return VIRTUALIZATION_NONE;
b52aae1d 56
5d904a6a 57 hypervisor = ecx & 0x80000000U;
b52aae1d
LP
58
59 if (hypervisor) {
75f86906
LP
60 union {
61 uint32_t sig32[3];
62 char text[13];
63 } sig = {};
735ea55f 64 int v;
b52aae1d
LP
65
66 /* There is a hypervisor, see what it is */
8481e3e7 67 __cpuid(0x40000000U, eax, ebx, ecx, edx);
d31b0033
MG
68
69 sig.sig32[0] = ebx;
70 sig.sig32[1] = ecx;
71 sig.sig32[2] = edx;
b52aae1d 72
9f63a08d
SS
73 log_debug("Virtualization found, CPUID=%s", sig.text);
74
735ea55f
YW
75 v = vm_from_string(sig.text);
76 if (v < 0)
77 return VIRTUALIZATION_VM_OTHER;
bdb628ee 78
735ea55f 79 return v;
b52aae1d 80 }
bdb628ee 81#endif
9f63a08d 82 log_debug("No virtualization found in CPUID");
bdb628ee 83
75f86906 84 return VIRTUALIZATION_NONE;
bdb628ee
ZJS
85}
86
75f86906 87static int detect_vm_device_tree(void) {
db6a8689 88#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__)
d831deb5
CA
89 _cleanup_free_ char *hvtype = NULL;
90 int r;
91
b8f1df82 92 r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype);
75f86906 93 if (r == -ENOENT) {
ce09c71d
AJ
94 _cleanup_closedir_ DIR *dir = NULL;
95 struct dirent *dent;
96
3224e38b
MS
97 if (access("/proc/device-tree/ibm,partition-name", F_OK) == 0 &&
98 access("/proc/device-tree/hmc-managed?", F_OK) == 0 &&
99 access("/proc/device-tree/chosen/qemu,graphic-width", F_OK) != 0)
100 return VIRTUALIZATION_POWERVM;
101
ce09c71d
AJ
102 dir = opendir("/proc/device-tree");
103 if (!dir) {
9f63a08d
SS
104 if (errno == ENOENT) {
105 log_debug_errno(errno, "/proc/device-tree: %m");
75f86906 106 return VIRTUALIZATION_NONE;
9f63a08d 107 }
ce09c71d
AJ
108 return -errno;
109 }
110
75f86906 111 FOREACH_DIRENT(dent, dir, return -errno)
9f63a08d
SS
112 if (strstr(dent->d_name, "fw-cfg")) {
113 log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", dent->d_name);
75f86906 114 return VIRTUALIZATION_QEMU;
9f63a08d 115 }
75f86906 116
9f63a08d 117 log_debug("No virtualization found in /proc/device-tree/*");
75f86906
LP
118 return VIRTUALIZATION_NONE;
119 } else if (r < 0)
120 return r;
121
9f63a08d 122 log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype);
75f86906
LP
123 if (streq(hvtype, "linux,kvm"))
124 return VIRTUALIZATION_KVM;
125 else if (strstr(hvtype, "xen"))
126 return VIRTUALIZATION_XEN;
4d4ac92c
CL
127 else if (strstr(hvtype, "vmware"))
128 return VIRTUALIZATION_VMWARE;
75f86906
LP
129 else
130 return VIRTUALIZATION_VM_OTHER;
131#else
9f63a08d 132 log_debug("This platform does not support /proc/device-tree");
75f86906 133 return VIRTUALIZATION_NONE;
d831deb5 134#endif
d831deb5
CA
135}
136
75f86906 137static int detect_vm_dmi(void) {
2ef8a4c4 138#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
bdb628ee
ZJS
139
140 static const char *const dmi_vendors[] = {
3728dcde 141 "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */
bdb628ee
ZJS
142 "/sys/class/dmi/id/sys_vendor",
143 "/sys/class/dmi/id/board_vendor",
144 "/sys/class/dmi/id/bios_vendor"
145 };
146
75f86906
LP
147 static const struct {
148 const char *vendor;
149 int id;
150 } dmi_vendor_table[] = {
87bc4b40 151 { "KVM", VIRTUALIZATION_KVM },
25454a0c 152 { "QEMU", VIRTUALIZATION_QEMU },
a86cbb0f 153 { "VMware", VIRTUALIZATION_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */
87bc4b40
JL
154 { "VMW", VIRTUALIZATION_VMWARE },
155 { "innotek GmbH", VIRTUALIZATION_ORACLE },
156 { "Oracle Corporation", VIRTUALIZATION_ORACLE },
157 { "Xen", VIRTUALIZATION_XEN },
158 { "Bochs", VIRTUALIZATION_BOCHS },
159 { "Parallels", VIRTUALIZATION_PARALLELS },
aa0c3427 160 /* https://wiki.freebsd.org/bhyve */
87bc4b40 161 { "BHYVE", VIRTUALIZATION_BHYVE },
75f86906 162 };
75f86906 163 int r;
b52aae1d 164
fe96c0f8 165 for (size_t i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
b1b8e816 166 _cleanup_free_ char *s = NULL;
75f86906 167 unsigned j;
b52aae1d 168
b1b8e816
LP
169 r = read_one_line_file(dmi_vendors[i], &s);
170 if (r < 0) {
75f86906
LP
171 if (r == -ENOENT)
172 continue;
b52aae1d 173
75f86906 174 return r;
b52aae1d
LP
175 }
176
75f86906 177 for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++)
9f63a08d
SS
178 if (startswith(s, dmi_vendor_table[j].vendor)) {
179 log_debug("Virtualization %s found in DMI (%s)", s, dmi_vendors[i]);
75f86906 180 return dmi_vendor_table[j].id;
9f63a08d 181 }
b52aae1d 182 }
bdb628ee
ZJS
183#endif
184
9f63a08d
SS
185 log_debug("No virtualization found in DMI");
186
75f86906 187 return VIRTUALIZATION_NONE;
bdb628ee
ZJS
188}
189
75f86906 190static int detect_vm_xen(void) {
45e1c7d5 191
3f61278b 192 /* Check for Dom0 will be executed later in detect_vm_xen_dom0
87dc723a
OH
193 The presence of /proc/xen indicates some form of a Xen domain */
194 if (access("/proc/xen", F_OK) < 0) {
195 log_debug("Virtualization XEN not found, /proc/xen does not exist");
3f61278b
SS
196 return VIRTUALIZATION_NONE;
197 }
198
87dc723a 199 log_debug("Virtualization XEN found (/proc/xen exists)");
45e1c7d5 200 return VIRTUALIZATION_XEN;
3f61278b
SS
201}
202
575e6588
OH
203#define XENFEAT_dom0 11 /* xen/include/public/features.h */
204#define PATH_FEATURES "/sys/hypervisor/properties/features"
1a8e4148
OH
205/* Returns -errno, or 0 for domU, or 1 for dom0 */
206static int detect_vm_xen_dom0(void) {
75f86906 207 _cleanup_free_ char *domcap = NULL;
bdb628ee 208 int r;
b52aae1d 209
575e6588
OH
210 r = read_one_line_file(PATH_FEATURES, &domcap);
211 if (r < 0 && r != -ENOENT)
212 return r;
d6062e3b 213 if (r >= 0) {
575e6588
OH
214 unsigned long features;
215
47dbb99a
YW
216 /* Here, we need to use sscanf() instead of safe_atoul()
217 * as the string lacks the leading "0x". */
13e0f9fe
OH
218 r = sscanf(domcap, "%lx", &features);
219 if (r == 1) {
575e6588
OH
220 r = !!(features & (1U << XENFEAT_dom0));
221 log_debug("Virtualization XEN, found %s with value %08lx, "
222 "XENFEAT_dom0 (indicating the 'hardware domain') is%s set.",
223 PATH_FEATURES, features, r ? "" : " not");
224 return r;
225 }
226 log_debug("Virtualization XEN, found %s, unhandled content '%s'",
227 PATH_FEATURES, domcap);
228 }
229
75f86906 230 r = read_one_line_file("/proc/xen/capabilities", &domcap);
9f63a08d 231 if (r == -ENOENT) {
1a8e4148
OH
232 log_debug("Virtualization XEN because /proc/xen/capabilities does not exist");
233 return 0;
9f63a08d 234 }
d5b687e7
LP
235 if (r < 0)
236 return r;
bdb628ee 237
31a9be23
YW
238 for (const char *i = domcap;;) {
239 _cleanup_free_ char *cap = NULL;
9f63a08d 240
31a9be23
YW
241 r = extract_first_word(&i, &cap, ",", 0);
242 if (r < 0)
243 return r;
244 if (r == 0) {
245 log_debug("Virtualization XEN DomU found (/proc/xen/capabilities)");
246 return 0;
247 }
248
249 if (streq(cap, "control_d")) {
250 log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)");
251 return 1;
252 }
253 }
75f86906 254}
37287585 255
75f86906
LP
256static int detect_vm_hypervisor(void) {
257 _cleanup_free_ char *hvtype = NULL;
258 int r;
37287585 259
75f86906
LP
260 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
261 if (r == -ENOENT)
262 return VIRTUALIZATION_NONE;
263 if (r < 0)
264 return r;
37287585 265
9f63a08d
SS
266 log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype);
267
75f86906
LP
268 if (streq(hvtype, "xen"))
269 return VIRTUALIZATION_XEN;
270 else
271 return VIRTUALIZATION_VM_OTHER;
272}
37287585 273
75f86906 274static int detect_vm_uml(void) {
6058516a 275 _cleanup_fclose_ FILE *f = NULL;
75f86906 276 int r;
37287585 277
75f86906 278 /* Detect User-Mode Linux by reading /proc/cpuinfo */
6058516a
ZJS
279 f = fopen("/proc/cpuinfo", "re");
280 if (!f) {
281 if (errno == ENOENT) {
282 log_debug("/proc/cpuinfo not found, assuming no UML virtualization.");
283 return VIRTUALIZATION_NONE;
284 }
285 return -errno;
ef2a48aa 286 }
9f63a08d 287
6058516a
ZJS
288 for (;;) {
289 _cleanup_free_ char *line = NULL;
290 const char *t;
291
292 r = read_line(f, LONG_LINE_MAX, &line);
293 if (r < 0)
294 return r;
295 if (r == 0)
296 break;
297
298 t = startswith(line, "vendor_id\t: ");
299 if (t) {
300 if (startswith(t, "User Mode Linux")) {
301 log_debug("UML virtualization found in /proc/cpuinfo");
302 return VIRTUALIZATION_UML;
303 }
304
305 break;
306 }
9f63a08d 307 }
bdb628ee 308
ef2a48aa 309 log_debug("UML virtualization not found in /proc/cpuinfo.");
75f86906
LP
310 return VIRTUALIZATION_NONE;
311}
e32886e0 312
75f86906 313static int detect_vm_zvm(void) {
bdb628ee 314
75f86906
LP
315#if defined(__s390__)
316 _cleanup_free_ char *t = NULL;
317 int r;
e32886e0 318
c4cd1d4d 319 r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t);
75f86906
LP
320 if (r == -ENOENT)
321 return VIRTUALIZATION_NONE;
322 if (r < 0)
323 return r;
e32886e0 324
9f63a08d 325 log_debug("Virtualization %s found in /proc/sysinfo", t);
75f86906
LP
326 if (streq(t, "z/VM"))
327 return VIRTUALIZATION_ZVM;
328 else
329 return VIRTUALIZATION_KVM;
330#else
9f63a08d 331 log_debug("This platform does not support /proc/sysinfo");
75f86906
LP
332 return VIRTUALIZATION_NONE;
333#endif
334}
e32886e0 335
75f86906
LP
336/* Returns a short identifier for the various VM implementations */
337int detect_vm(void) {
338 static thread_local int cached_found = _VIRTUALIZATION_INVALID;
530c1c30 339 bool other = false;
c2b19b3c 340 int r, dmi;
e32886e0 341
75f86906
LP
342 if (cached_found >= 0)
343 return cached_found;
bdb628ee 344
f6875b0a 345 /* We have to use the correct order here:
f6875b0a 346 *
f2fe2865 347 * → First, try to detect Oracle Virtualbox, even if it uses KVM, as well as Xen even if it cloaks as Microsoft
c8037dbf 348 * Hyper-V. Attempt to detect uml at this stage also since it runs as a user-process nested inside other VMs.
f2fe2865
LP
349 *
350 * → Second, try to detect from CPUID, this will report KVM for whatever software is used even if info in DMI is
351 * overwritten.
352 *
353 * → Third, try to detect from DMI. */
5f1c788c 354
28b1a3ea 355 dmi = detect_vm_dmi();
f2fe2865 356 if (IN_SET(dmi, VIRTUALIZATION_ORACLE, VIRTUALIZATION_XEN)) {
2f8e375d
BR
357 r = dmi;
358 goto finish;
359 }
5f1c788c 360
c8037dbf
CO
361 /* Detect UML */
362 r = detect_vm_uml();
363 if (r < 0)
364 return r;
365 if (r == VIRTUALIZATION_VM_OTHER)
366 other = true;
367 else if (r != VIRTUALIZATION_NONE)
368 goto finish;
369
370 /* Detect from CPUID */
28b1a3ea 371 r = detect_vm_cpuid();
75f86906
LP
372 if (r < 0)
373 return r;
c2b19b3c
LP
374 if (r == VIRTUALIZATION_VM_OTHER)
375 other = true;
376 else if (r != VIRTUALIZATION_NONE)
377 goto finish;
d831deb5 378
c2b19b3c
LP
379 /* Now, let's get back to DMI */
380 if (dmi < 0)
381 return dmi;
382 if (dmi == VIRTUALIZATION_VM_OTHER)
383 other = true;
384 else if (dmi != VIRTUALIZATION_NONE) {
385 r = dmi;
386 goto finish;
530c1c30 387 }
b52aae1d 388
42685451 389 /* x86 xen will most likely be detected by cpuid. If not (most likely
87dc723a
OH
390 * because we're not an x86 guest), then we should try the /proc/xen
391 * directory next. If that's not found, then we check for the high-level
392 * hypervisor sysfs file.
2f5fa62b 393 */
42685451
AJ
394
395 r = detect_vm_xen();
7080ea16
RR
396 if (r < 0)
397 return r;
c2b19b3c
LP
398 if (r == VIRTUALIZATION_VM_OTHER)
399 other = true;
400 else if (r != VIRTUALIZATION_NONE)
401 goto finish;
7080ea16 402
75f86906
LP
403 r = detect_vm_hypervisor();
404 if (r < 0)
405 return r;
c2b19b3c
LP
406 if (r == VIRTUALIZATION_VM_OTHER)
407 other = true;
408 else if (r != VIRTUALIZATION_NONE)
409 goto finish;
f41925b4 410
75f86906
LP
411 r = detect_vm_device_tree();
412 if (r < 0)
413 return r;
c2b19b3c
LP
414 if (r == VIRTUALIZATION_VM_OTHER)
415 other = true;
416 else if (r != VIRTUALIZATION_NONE)
417 goto finish;
f41925b4 418
75f86906
LP
419 r = detect_vm_zvm();
420 if (r < 0)
421 return r;
0fb533a5
LP
422
423finish:
3f61278b
SS
424 /* x86 xen Dom0 is detected as XEN in hypervisor and maybe others.
425 * In order to detect the Dom0 as not virtualization we need to
426 * double-check it */
1a8e4148 427 if (r == VIRTUALIZATION_XEN) {
c2b19b3c
LP
428 int dom0;
429
430 dom0 = detect_vm_xen_dom0();
431 if (dom0 < 0)
432 return dom0;
433 if (dom0 > 0)
1a8e4148
OH
434 r = VIRTUALIZATION_NONE;
435 } else if (r == VIRTUALIZATION_NONE && other)
530c1c30 436 r = VIRTUALIZATION_VM_OTHER;
3f61278b 437
0fb533a5 438 cached_found = r;
9f63a08d 439 log_debug("Found VM virtualization %s", virtualization_to_string(r));
0fb533a5 440 return r;
b52aae1d
LP
441}
442
735ea55f
YW
443static const char *const container_table[_VIRTUALIZATION_MAX] = {
444 [VIRTUALIZATION_LXC] = "lxc",
445 [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
446 [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
447 [VIRTUALIZATION_DOCKER] = "docker",
448 [VIRTUALIZATION_PODMAN] = "podman",
449 [VIRTUALIZATION_RKT] = "rkt",
450 [VIRTUALIZATION_WSL] = "wsl",
80cc3e3e 451 [VIRTUALIZATION_PROOT] = "proot",
abac810b 452 [VIRTUALIZATION_POUCH] = "pouch",
735ea55f 453};
0fb533a5 454
735ea55f
YW
455DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(container, int);
456
0e13779d
SB
457static int running_in_cgroupns(void) {
458 int r;
459
460 if (!cg_ns_supported())
461 return false;
462
463 r = cg_all_unified();
464 if (r < 0)
465 return r;
466
467 if (r) {
468 /* cgroup v2 */
469
470 r = access("/sys/fs/cgroup/cgroup.events", F_OK);
471 if (r < 0) {
472 if (errno != ENOENT)
473 return -errno;
474 /* All kernel versions have cgroup.events in nested cgroups. */
475 return false;
476 }
477
478 /* There's no cgroup.type in the root cgroup, and future kernel versions
479 * are unlikely to add it since cgroup.type is something that makes no sense
480 * whatsoever in the root cgroup. */
481 r = access("/sys/fs/cgroup/cgroup.type", F_OK);
482 if (r == 0)
483 return true;
484 if (r < 0 && errno != ENOENT)
485 return -errno;
486
487 /* On older kernel versions, there's no cgroup.type */
488 r = access("/sys/kernel/cgroup/features", F_OK);
489 if (r < 0) {
490 if (errno != ENOENT)
491 return -errno;
492 /* This is an old kernel that we know for sure has cgroup.events
493 * only in nested cgroups. */
494 return true;
495 }
496
497 /* This is a recent kernel, and cgroup.type doesn't exist, so we must be
498 * in the root cgroup. */
499 return false;
500 } else {
501 /* cgroup v1 */
502
503 /* If systemd controller is not mounted, do not even bother. */
504 r = access("/sys/fs/cgroup/systemd", F_OK);
505 if (r < 0) {
506 if (errno != ENOENT)
507 return -errno;
508 return false;
509 }
510
511 /* release_agent only exists in the root cgroup. */
512 r = access("/sys/fs/cgroup/systemd/release_agent", F_OK);
513 if (r < 0) {
514 if (errno != ENOENT)
515 return -errno;
516 return true;
517 }
518
519 return false;
520 }
521}
522
a4a9a6f7
SB
523static int detect_container_files(void) {
524 unsigned i;
525
526 static const struct {
527 const char *file_path;
528 int id;
529 } container_file_table[] = {
530 /* https://github.com/containers/podman/issues/6192 */
531 /* https://github.com/containers/podman/issues/3586#issuecomment-661918679 */
532 { "/run/.containerenv", VIRTUALIZATION_PODMAN },
533 /* https://github.com/moby/moby/issues/18355 */
534 /* Docker must be the last in this table, see below. */
535 { "/.dockerenv", VIRTUALIZATION_DOCKER },
536 };
537
538 for (i = 0; i < ELEMENTSOF(container_file_table); i++) {
539 if (access(container_file_table[i].file_path, F_OK) >= 0)
540 return container_file_table[i].id;
541
542 if (errno != ENOENT)
543 log_debug_errno(errno,
544 "Checking if %s exists failed, ignoring: %m",
545 container_file_table[i].file_path);
546 }
547
548 return VIRTUALIZATION_NONE;
549}
550
735ea55f 551int detect_container(void) {
75f86906 552 static thread_local int cached_found = _VIRTUALIZATION_INVALID;
a7e508f8 553 _cleanup_free_ char *m = NULL, *o = NULL, *p = NULL;
75f86906 554 const char *e = NULL;
ab94af92 555 int r;
b52aae1d 556
75f86906 557 if (cached_found >= 0)
0fb533a5 558 return cached_found;
0fb533a5 559
8d6e8034 560 /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */
b7ec9e71
LP
561 if (access("/proc/vz", F_OK) < 0) {
562 if (errno != ENOENT)
563 log_debug_errno(errno, "Failed to check if /proc/vz exists, ignoring: %m");
564 } else if (access("/proc/bc", F_OK) < 0) {
565 if (errno == ENOENT) {
566 r = VIRTUALIZATION_OPENVZ;
567 goto finish;
568 }
569
570 log_debug_errno(errno, "Failed to check if /proc/bc exists, ignoring: %m");
b52aae1d
LP
571 }
572
4096043f 573 /* "Official" way of detecting WSL https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364 */
6c8a2c67 574 r = read_one_line_file("/proc/sys/kernel/osrelease", &o);
b7ec9e71
LP
575 if (r < 0)
576 log_debug_errno(r, "Failed to read /proc/sys/kernel/osrelease, ignoring: %m");
577 else if (strstr(o, "Microsoft") || strstr(o, "WSL")) {
a2f838d5
ZJS
578 r = VIRTUALIZATION_WSL;
579 goto finish;
6c8a2c67
BR
580 }
581
80cc3e3e
CD
582 /* proot doesn't use PID namespacing, so we can just check if we have a matching tracer for this
583 * invocation without worrying about it being elsewhere.
584 */
585 r = get_proc_field("/proc/self/status", "TracerPid", WHITESPACE, &p);
b7ec9e71
LP
586 if (r < 0)
587 log_debug_errno(r, "Failed to read our own trace PID, ignoring: %m");
588 else if (!streq(p, "0")) {
80cc3e3e 589 pid_t ptrace_pid;
b7ec9e71 590
80cc3e3e 591 r = parse_pid(p, &ptrace_pid);
b7ec9e71
LP
592 if (r < 0)
593 log_debug_errno(r, "Failed to parse our own tracer PID, ignoring: %m");
594 else {
80cc3e3e 595 _cleanup_free_ char *ptrace_comm = NULL;
b7ec9e71
LP
596 const char *pf;
597
598 pf = procfs_file_alloca(ptrace_pid, "comm");
80cc3e3e 599 r = read_one_line_file(pf, &ptrace_comm);
b7ec9e71
LP
600 if (r < 0)
601 log_debug_errno(r, "Failed to read %s, ignoring: %m", pf);
602 else if (startswith(ptrace_comm, "proot")) {
9b4f3fa3
CD
603 r = VIRTUALIZATION_PROOT;
604 goto finish;
605 }
80cc3e3e
CD
606 }
607 }
608
79efcd02 609 /* The container manager might have placed this in the /run/host/ hierarchy for us, which is best
0f48ba7b
LP
610 * because we can be consumed just like that, without special privileges. */
611 r = read_one_line_file("/run/host/container-manager", &m);
612 if (r > 0) {
613 e = m;
614 goto translate_name;
615 }
616 if (!IN_SET(r, -ENOENT, 0))
79efcd02 617 return log_debug_errno(r, "Failed to read /run/host/container-manager: %m");
0f48ba7b 618
df0ff127 619 if (getpid_cached() == 1) {
342bed02
ZJS
620 /* If we are PID 1 we can just check our own environment variable, and that's authoritative.
621 * We distinguish three cases:
622 * - the variable is not defined → we jump to other checks
623 * - the variable is defined to an empty value → we are not in a container
624 * - anything else → some container, either one of the known ones or "container-other"
625 */
fdd25311 626 e = getenv("container");
342bed02 627 if (!e)
a4a9a6f7 628 goto check_files;
fdd25311 629 if (isempty(e)) {
75f86906 630 r = VIRTUALIZATION_NONE;
fdd25311
LP
631 goto finish;
632 }
fdd25311 633
8d6e8034
LP
634 goto translate_name;
635 }
636
637 /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing
638 * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */
639 r = read_one_line_file("/run/systemd/container", &m);
a2176045 640 if (r > 0) {
8d6e8034
LP
641 e = m;
642 goto translate_name;
643 }
a2176045 644 if (!IN_SET(r, -ENOENT, 0))
8d6e8034
LP
645 return log_debug_errno(r, "Failed to read /run/systemd/container: %m");
646
647 /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */
648 r = getenv_for_pid(1, "container", &m);
649 if (r > 0) {
fdd25311 650 e = m;
8d6e8034 651 goto translate_name;
fdd25311 652 }
8d6e8034
LP
653 if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */
654 log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m");
655
a4a9a6f7
SB
656check_files:
657 /* Check for existence of some well-known files. We only do this after checking
658 * for other specific container managers, otherwise we risk mistaking another
659 * container manager for Docker: the /.dockerenv file could inadvertently end up
660 * in a file system image. */
661 r = detect_container_files();
662 if (r)
663 goto finish;
664
0e13779d
SB
665 r = running_in_cgroupns();
666 if (r > 0) {
667 r = VIRTUALIZATION_CONTAINER_OTHER;
668 goto finish;
669 }
670 if (r < 0)
671 log_debug_errno(r, "Failed to detect cgroup namespace: %m");
672
a4a9a6f7 673 /* If none of that worked, give up, assume no container manager. */
8d6e8034
LP
674 r = VIRTUALIZATION_NONE;
675 goto finish;
b52aae1d 676
8d6e8034 677translate_name:
a4a9a6f7
SB
678 if (streq(e, "oci")) {
679 /* Some images hardcode container=oci, but OCI is not a specific container manager.
680 * Try to detect one based on well-known files. */
681 r = detect_container_files();
682 if (!r)
683 r = VIRTUALIZATION_CONTAINER_OTHER;
684 goto finish;
685 }
735ea55f
YW
686 r = container_from_string(e);
687 if (r < 0)
688 r = VIRTUALIZATION_CONTAINER_OTHER;
fdd25311 689
0fb533a5 690finish:
8d6e8034 691 log_debug("Found container virtualization %s.", virtualization_to_string(r));
0fb533a5 692 cached_found = r;
ab94af92 693 return r;
b52aae1d
LP
694}
695
75f86906 696int detect_virtualization(void) {
b52aae1d 697 int r;
b52aae1d 698
75f86906 699 r = detect_container();
9f63a08d
SS
700 if (r == 0)
701 r = detect_vm();
b52aae1d 702
9f63a08d 703 return r;
b52aae1d 704}
75f86906 705
299a34c1
ZJS
706static int userns_has_mapping(const char *name) {
707 _cleanup_fclose_ FILE *f = NULL;
708 _cleanup_free_ char *buf = NULL;
709 size_t n_allocated = 0;
710 ssize_t n;
711 uint32_t a, b, c;
712 int r;
713
714 f = fopen(name, "re");
715 if (!f) {
716 log_debug_errno(errno, "Failed to open %s: %m", name);
abd67ce7 717 return errno == ENOENT ? false : -errno;
299a34c1
ZJS
718 }
719
720 n = getline(&buf, &n_allocated, f);
721 if (n < 0) {
722 if (feof(f)) {
723 log_debug("%s is empty, we're in an uninitialized user namespace", name);
724 return true;
725 }
726
727 return log_debug_errno(errno, "Failed to read %s: %m", name);
728 }
729
730 r = sscanf(buf, "%"PRIu32" %"PRIu32" %"PRIu32, &a, &b, &c);
731 if (r < 3)
732 return log_debug_errno(errno, "Failed to parse %s: %m", name);
733
734 if (a == 0 && b == 0 && c == UINT32_MAX) {
735 /* The kernel calls mappings_overlap() and does not allow overlaps */
736 log_debug("%s has a full 1:1 mapping", name);
737 return false;
738 }
739
740 /* Anything else implies that we are in a user namespace */
741 log_debug("Mapping found in %s, we're in a user namespace", name);
742 return true;
743}
744
745int running_in_userns(void) {
746 _cleanup_free_ char *line = NULL;
747 int r;
748
749 r = userns_has_mapping("/proc/self/uid_map");
750 if (r != 0)
751 return r;
752
753 r = userns_has_mapping("/proc/self/gid_map");
754 if (r != 0)
755 return r;
756
757 /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also
758 * possible to compile a kernel without CONFIG_USER_NS, in which case "setgroups"
759 * also does not exist. We cannot distinguish those two cases, so assume that
760 * we're running on a stripped-down recent kernel, rather than on an old one,
761 * and if the file is not found, return false.
762 */
763 r = read_one_line_file("/proc/self/setgroups", &line);
764 if (r < 0) {
765 log_debug_errno(r, "/proc/self/setgroups: %m");
766 return r == -ENOENT ? false : r;
767 }
768
769 truncate_nl(line);
770 r = streq(line, "deny");
771 /* See user_namespaces(7) for a description of this "setgroups" contents. */
772 log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in");
773 return r;
774}
775
7f4b3c5e 776int running_in_chroot(void) {
ef2a48aa 777 int r;
7f4b3c5e 778
08a28eec
LN
779 if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0)
780 return 0;
781
ef2a48aa
ZJS
782 r = files_same("/proc/1/root", "/", 0);
783 if (r < 0)
784 return r;
7f4b3c5e 785
ef2a48aa 786 return r == 0;
7f4b3c5e
LP
787}
788
75f86906
LP
789static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
790 [VIRTUALIZATION_NONE] = "none",
791 [VIRTUALIZATION_KVM] = "kvm",
792 [VIRTUALIZATION_QEMU] = "qemu",
793 [VIRTUALIZATION_BOCHS] = "bochs",
794 [VIRTUALIZATION_XEN] = "xen",
795 [VIRTUALIZATION_UML] = "uml",
796 [VIRTUALIZATION_VMWARE] = "vmware",
797 [VIRTUALIZATION_ORACLE] = "oracle",
798 [VIRTUALIZATION_MICROSOFT] = "microsoft",
799 [VIRTUALIZATION_ZVM] = "zvm",
800 [VIRTUALIZATION_PARALLELS] = "parallels",
aa0c3427 801 [VIRTUALIZATION_BHYVE] = "bhyve",
1fdf07f5 802 [VIRTUALIZATION_QNX] = "qnx",
095b9cf4 803 [VIRTUALIZATION_ACRN] = "acrn",
3224e38b 804 [VIRTUALIZATION_POWERVM] = "powervm",
75f86906
LP
805 [VIRTUALIZATION_VM_OTHER] = "vm-other",
806
807 [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn",
808 [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt",
809 [VIRTUALIZATION_LXC] = "lxc",
810 [VIRTUALIZATION_OPENVZ] = "openvz",
811 [VIRTUALIZATION_DOCKER] = "docker",
90fb1f09 812 [VIRTUALIZATION_PODMAN] = "podman",
9fb16425 813 [VIRTUALIZATION_RKT] = "rkt",
6c8a2c67 814 [VIRTUALIZATION_WSL] = "wsl",
80cc3e3e 815 [VIRTUALIZATION_PROOT] = "proot",
abac810b 816 [VIRTUALIZATION_POUCH] = "pouch",
75f86906
LP
817 [VIRTUALIZATION_CONTAINER_OTHER] = "container-other",
818};
819
820DEFINE_STRING_TABLE_LOOKUP(virtualization, int);