]>
Commit | Line | Data |
---|---|---|
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" |
b2a331f2 | 15 | #include "errno-util.h" |
ade61d3b | 16 | #include "fd-util.h" |
07630cea | 17 | #include "fileio.h" |
11c3a366 | 18 | #include "macro.h" |
5545f336 | 19 | #include "missing_threads.h" |
93cc7779 | 20 | #include "process-util.h" |
b5efdb8a | 21 | #include "stat-util.h" |
8b43440b | 22 | #include "string-table.h" |
07630cea | 23 | #include "string-util.h" |
7312c422 | 24 | #include "uid-range.h" |
b52aae1d LP |
25 | #include "virt.h" |
26 | ||
ce350379 NM |
27 | enum { |
28 | SMBIOS_VM_BIT_SET, | |
29 | SMBIOS_VM_BIT_UNSET, | |
30 | SMBIOS_VM_BIT_UNKNOWN, | |
31 | }; | |
32 | ||
1b86c7c5 | 33 | static Virtualization detect_vm_cpuid(void) { |
b52aae1d | 34 | |
2ef8a4c4 | 35 | /* CPUID is an x86 specific interface. */ |
bdb628ee | 36 | #if defined(__i386__) || defined(__x86_64__) |
b52aae1d | 37 | |
0f534758 LP |
38 | static const struct { |
39 | const char sig[13]; | |
1b86c7c5 | 40 | Virtualization id; |
0f534758 LP |
41 | } vm_table[] = { |
42 | { "XenVMMXenVMM", VIRTUALIZATION_XEN }, | |
43 | { "KVMKVMKVM", VIRTUALIZATION_KVM }, /* qemu with KVM */ | |
44 | { "Linux KVM Hv", VIRTUALIZATION_KVM }, /* qemu with KVM + HyperV Enlightenments */ | |
45 | { "TCGTCGTCGTCG", VIRTUALIZATION_QEMU }, /* qemu without KVM */ | |
46 | /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ | |
47 | { "VMwareVMware", VIRTUALIZATION_VMWARE }, | |
48 | /* https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/reference/tlfs */ | |
49 | { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, | |
50 | /* https://wiki.freebsd.org/bhyve */ | |
51 | { "bhyve bhyve ", VIRTUALIZATION_BHYVE }, | |
52 | { "QNXQVMBSQG", VIRTUALIZATION_QNX }, | |
53 | /* https://projectacrn.org */ | |
54 | { "ACRNACRNACRN", VIRTUALIZATION_ACRN }, | |
d833ed78 NM |
55 | /* https://www.lockheedmartin.com/en-us/products/Hardened-Security-for-Intel-Processors.html */ |
56 | { "SRESRESRESRE", VIRTUALIZATION_SRE }, | |
5a02a9ad | 57 | { "Apple VZ", VIRTUALIZATION_APPLE }, |
0f534758 LP |
58 | }; |
59 | ||
d31b0033 | 60 | uint32_t eax, ebx, ecx, edx; |
b52aae1d LP |
61 | bool hypervisor; |
62 | ||
63 | /* http://lwn.net/Articles/301888/ */ | |
b52aae1d | 64 | |
b52aae1d | 65 | /* First detect whether there is a hypervisor */ |
d31b0033 MG |
66 | if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) |
67 | return VIRTUALIZATION_NONE; | |
b52aae1d | 68 | |
5d904a6a | 69 | hypervisor = ecx & 0x80000000U; |
b52aae1d LP |
70 | |
71 | if (hypervisor) { | |
75f86906 LP |
72 | union { |
73 | uint32_t sig32[3]; | |
74 | char text[13]; | |
75 | } sig = {}; | |
b52aae1d LP |
76 | |
77 | /* There is a hypervisor, see what it is */ | |
8481e3e7 | 78 | __cpuid(0x40000000U, eax, ebx, ecx, edx); |
d31b0033 MG |
79 | |
80 | sig.sig32[0] = ebx; | |
81 | sig.sig32[1] = ecx; | |
82 | sig.sig32[2] = edx; | |
b52aae1d | 83 | |
9f63a08d SS |
84 | log_debug("Virtualization found, CPUID=%s", sig.text); |
85 | ||
0f534758 LP |
86 | for (size_t i = 0; i < ELEMENTSOF(vm_table); i++) |
87 | if (memcmp_nn(sig.text, sizeof(sig.text), | |
88 | vm_table[i].sig, sizeof(vm_table[i].sig)) == 0) | |
89 | return vm_table[i].id; | |
bdb628ee | 90 | |
0f534758 LP |
91 | log_debug("Unknown virtualization with CPUID=%s. Add to vm_table[]?", sig.text); |
92 | return VIRTUALIZATION_VM_OTHER; | |
b52aae1d | 93 | } |
bdb628ee | 94 | #endif |
9f63a08d | 95 | log_debug("No virtualization found in CPUID"); |
bdb628ee | 96 | |
75f86906 | 97 | return VIRTUALIZATION_NONE; |
bdb628ee ZJS |
98 | } |
99 | ||
1b86c7c5 | 100 | static Virtualization detect_vm_device_tree(void) { |
819874ad | 101 | #if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__) || defined(__riscv) |
d831deb5 CA |
102 | _cleanup_free_ char *hvtype = NULL; |
103 | int r; | |
104 | ||
b8f1df82 | 105 | r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype); |
75f86906 | 106 | if (r == -ENOENT) { |
ce09c71d | 107 | _cleanup_closedir_ DIR *dir = NULL; |
8c7a6c74 | 108 | _cleanup_free_ char *compat = NULL; |
ce09c71d | 109 | |
3224e38b MS |
110 | if (access("/proc/device-tree/ibm,partition-name", F_OK) == 0 && |
111 | access("/proc/device-tree/hmc-managed?", F_OK) == 0 && | |
112 | access("/proc/device-tree/chosen/qemu,graphic-width", F_OK) != 0) | |
113 | return VIRTUALIZATION_POWERVM; | |
114 | ||
ce09c71d AJ |
115 | dir = opendir("/proc/device-tree"); |
116 | if (!dir) { | |
9f63a08d SS |
117 | if (errno == ENOENT) { |
118 | log_debug_errno(errno, "/proc/device-tree: %m"); | |
75f86906 | 119 | return VIRTUALIZATION_NONE; |
9f63a08d | 120 | } |
ce09c71d AJ |
121 | return -errno; |
122 | } | |
123 | ||
af3b864d ZJS |
124 | FOREACH_DIRENT(de, dir, return -errno) |
125 | if (strstr(de->d_name, "fw-cfg")) { | |
126 | log_debug("Virtualization QEMU: \"fw-cfg\" present in /proc/device-tree/%s", de->d_name); | |
75f86906 | 127 | return VIRTUALIZATION_QEMU; |
9f63a08d | 128 | } |
75f86906 | 129 | |
8c7a6c74 FS |
130 | r = read_one_line_file("/proc/device-tree/compatible", &compat); |
131 | if (r < 0 && r != -ENOENT) | |
132 | return r; | |
133 | if (r >= 0 && streq(compat, "qemu,pseries")) { | |
134 | log_debug("Virtualization %s found in /proc/device-tree/compatible", compat); | |
135 | return VIRTUALIZATION_QEMU; | |
136 | } | |
137 | ||
9f63a08d | 138 | log_debug("No virtualization found in /proc/device-tree/*"); |
75f86906 LP |
139 | return VIRTUALIZATION_NONE; |
140 | } else if (r < 0) | |
141 | return r; | |
142 | ||
9f63a08d | 143 | log_debug("Virtualization %s found in /proc/device-tree/hypervisor/compatible", hvtype); |
75f86906 LP |
144 | if (streq(hvtype, "linux,kvm")) |
145 | return VIRTUALIZATION_KVM; | |
146 | else if (strstr(hvtype, "xen")) | |
147 | return VIRTUALIZATION_XEN; | |
4d4ac92c CL |
148 | else if (strstr(hvtype, "vmware")) |
149 | return VIRTUALIZATION_VMWARE; | |
75f86906 LP |
150 | else |
151 | return VIRTUALIZATION_VM_OTHER; | |
152 | #else | |
9f63a08d | 153 | log_debug("This platform does not support /proc/device-tree"); |
75f86906 | 154 | return VIRTUALIZATION_NONE; |
d831deb5 | 155 | #endif |
d831deb5 CA |
156 | } |
157 | ||
5041e771 | 158 | #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) || defined(__riscv) |
1b86c7c5 | 159 | static Virtualization detect_vm_dmi_vendor(void) { |
a9d178d2 | 160 | static const char* const dmi_vendors[] = { |
3728dcde | 161 | "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ |
bdb628ee ZJS |
162 | "/sys/class/dmi/id/sys_vendor", |
163 | "/sys/class/dmi/id/board_vendor", | |
76eec064 | 164 | "/sys/class/dmi/id/bios_vendor", |
a9d178d2 ZJS |
165 | "/sys/class/dmi/id/product_version", /* For Hyper-V VMs test */ |
166 | NULL | |
bdb628ee ZJS |
167 | }; |
168 | ||
75f86906 LP |
169 | static const struct { |
170 | const char *vendor; | |
1b86c7c5 | 171 | Virtualization id; |
75f86906 | 172 | } dmi_vendor_table[] = { |
9b0688f4 YW |
173 | { "KVM", VIRTUALIZATION_KVM }, |
174 | { "OpenStack", VIRTUALIZATION_KVM }, /* Detect OpenStack instance as KVM in non x86 architecture */ | |
175 | { "KubeVirt", VIRTUALIZATION_KVM }, /* Detect KubeVirt instance as KVM in non x86 architecture */ | |
176 | { "Amazon EC2", VIRTUALIZATION_AMAZON }, | |
177 | { "QEMU", VIRTUALIZATION_QEMU }, | |
178 | { "VMware", VIRTUALIZATION_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */ | |
179 | { "VMW", VIRTUALIZATION_VMWARE }, | |
180 | { "innotek GmbH", VIRTUALIZATION_ORACLE }, | |
181 | { "VirtualBox", VIRTUALIZATION_ORACLE }, | |
5e3c08d3 | 182 | { "Oracle Corporation", VIRTUALIZATION_ORACLE }, /* Detect VirtualBox on some proprietary systems via the board_vendor */ |
9b0688f4 YW |
183 | { "Xen", VIRTUALIZATION_XEN }, |
184 | { "Bochs", VIRTUALIZATION_BOCHS }, | |
185 | { "Parallels", VIRTUALIZATION_PARALLELS }, | |
aa0c3427 | 186 | /* https://wiki.freebsd.org/bhyve */ |
9b0688f4 YW |
187 | { "BHYVE", VIRTUALIZATION_BHYVE }, |
188 | { "Hyper-V", VIRTUALIZATION_MICROSOFT }, | |
189 | { "Apple Virtualization", VIRTUALIZATION_APPLE }, | |
190 | { "Google Compute Engine", VIRTUALIZATION_GOOGLE }, /* https://cloud.google.com/run/docs/container-contract#sandbox */ | |
75f86906 | 191 | }; |
75f86906 | 192 | int r; |
b52aae1d | 193 | |
a9d178d2 | 194 | STRV_FOREACH(vendor, dmi_vendors) { |
b1b8e816 | 195 | _cleanup_free_ char *s = NULL; |
b52aae1d | 196 | |
a9d178d2 | 197 | r = read_one_line_file(*vendor, &s); |
b1b8e816 | 198 | if (r < 0) { |
75f86906 LP |
199 | if (r == -ENOENT) |
200 | continue; | |
b52aae1d | 201 | |
75f86906 | 202 | return r; |
b52aae1d LP |
203 | } |
204 | ||
a9d178d2 ZJS |
205 | for (size_t i = 0; i < ELEMENTSOF(dmi_vendor_table); i++) |
206 | if (startswith(s, dmi_vendor_table[i].vendor)) { | |
207 | log_debug("Virtualization %s found in DMI (%s)", s, *vendor); | |
208 | return dmi_vendor_table[i].id; | |
9f63a08d | 209 | } |
b52aae1d | 210 | } |
932feb79 | 211 | log_debug("No virtualization found in DMI vendor table."); |
ce350379 NM |
212 | return VIRTUALIZATION_NONE; |
213 | } | |
214 | ||
215 | static int detect_vm_smbios(void) { | |
627cdcc7 | 216 | /* The SMBIOS BIOS Characteristics Extension Byte 2 (Section 2.1.2.2 of |
ce350379 NM |
217 | * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.4.0.pdf), specifies that |
218 | * the 4th bit being set indicates a VM. The BIOS Characteristics table is exposed via the kernel in | |
219 | * /sys/firmware/dmi/entries/0-0. Note that in the general case, this bit being unset should not | |
220 | * imply that the system is running on bare-metal. For example, QEMU 3.1.0 (with or without KVM) | |
221 | * with SeaBIOS does not set this bit. */ | |
222 | _cleanup_free_ char *s = NULL; | |
223 | size_t readsize; | |
224 | int r; | |
225 | ||
226 | r = read_full_virtual_file("/sys/firmware/dmi/entries/0-0/raw", &s, &readsize); | |
227 | if (r < 0) { | |
932feb79 YW |
228 | log_debug_errno(r, "Unable to read /sys/firmware/dmi/entries/0-0/raw, " |
229 | "using the virtualization information found in DMI vendor table, ignoring: %m"); | |
ce350379 NM |
230 | return SMBIOS_VM_BIT_UNKNOWN; |
231 | } | |
232 | if (readsize < 20 || s[1] < 20) { | |
233 | /* The spec indicates that byte 1 contains the size of the table, 0x12 + the number of | |
234 | * extension bytes. The data we're interested in is in extension byte 2, which would be at | |
235 | * 0x13. If we didn't read that much data, or if the BIOS indicates that we don't have that | |
236 | * much data, we don't infer anything from the SMBIOS. */ | |
932feb79 YW |
237 | log_debug("Only read %zu bytes from /sys/firmware/dmi/entries/0-0/raw (expected 20). " |
238 | "Using the virtualization information found in DMI vendor table.", readsize); | |
ce350379 NM |
239 | return SMBIOS_VM_BIT_UNKNOWN; |
240 | } | |
bdb628ee | 241 | |
ce350379 NM |
242 | uint8_t byte = (uint8_t) s[19]; |
243 | if (byte & (1U<<4)) { | |
932feb79 | 244 | log_debug("DMI BIOS Extension table indicates virtualization."); |
ce350379 NM |
245 | return SMBIOS_VM_BIT_SET; |
246 | } | |
932feb79 | 247 | log_debug("DMI BIOS Extension table does not indicate virtualization."); |
ce350379 NM |
248 | return SMBIOS_VM_BIT_UNSET; |
249 | } | |
f106a639 | 250 | #endif /* defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) */ |
ce350379 | 251 | |
1b86c7c5 | 252 | static Virtualization detect_vm_dmi(void) { |
f106a639 | 253 | #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) |
ce350379 NM |
254 | |
255 | int r; | |
256 | r = detect_vm_dmi_vendor(); | |
257 | ||
258 | /* The DMI vendor tables in /sys/class/dmi/id don't help us distinguish between Amazon EC2 | |
259 | * virtual machines and bare-metal instances, so we need to look at SMBIOS. */ | |
f90eea7d BH |
260 | if (r == VIRTUALIZATION_AMAZON) { |
261 | switch (detect_vm_smbios()) { | |
262 | case SMBIOS_VM_BIT_SET: | |
263 | return VIRTUALIZATION_AMAZON; | |
264 | case SMBIOS_VM_BIT_UNSET: | |
265 | return VIRTUALIZATION_NONE; | |
266 | case SMBIOS_VM_BIT_UNKNOWN: { | |
267 | /* The DMI information we are after is only accessible to the root user, | |
268 | * so we fallback to using the product name which is less restricted | |
269 | * to distinguish metal systems from virtualized instances */ | |
270 | _cleanup_free_ char *s = NULL; | |
aab896e2 | 271 | const char *e; |
f90eea7d BH |
272 | |
273 | r = read_full_virtual_file("/sys/class/dmi/id/product_name", &s, NULL); | |
274 | /* In EC2, virtualized is much more common than metal, so if for some reason | |
275 | * we fail to read the DMI data, assume we are virtualized. */ | |
276 | if (r < 0) { | |
277 | log_debug_errno(r, "Can't read /sys/class/dmi/id/product_name," | |
278 | " assuming virtualized: %m"); | |
279 | return VIRTUALIZATION_AMAZON; | |
280 | } | |
aab896e2 BH |
281 | e = strstrafter(truncate_nl(s), ".metal"); |
282 | if (e && IN_SET(*e, 0, '-')) { | |
283 | log_debug("DMI product name has '.metal', assuming no virtualization"); | |
f90eea7d BH |
284 | return VIRTUALIZATION_NONE; |
285 | } else | |
286 | return VIRTUALIZATION_AMAZON; | |
287 | } | |
288 | default: | |
289 | assert_not_reached(); | |
290 | } | |
291 | } | |
9f63a08d | 292 | |
ce350379 NM |
293 | /* If we haven't identified a VM, but the firmware indicates that there is one, indicate as much. We |
294 | * have no further information about what it is. */ | |
295 | if (r == VIRTUALIZATION_NONE && detect_vm_smbios() == SMBIOS_VM_BIT_SET) | |
296 | return VIRTUALIZATION_VM_OTHER; | |
297 | return r; | |
298 | #else | |
75f86906 | 299 | return VIRTUALIZATION_NONE; |
ce350379 | 300 | #endif |
bdb628ee ZJS |
301 | } |
302 | ||
575e6588 OH |
303 | #define XENFEAT_dom0 11 /* xen/include/public/features.h */ |
304 | #define PATH_FEATURES "/sys/hypervisor/properties/features" | |
1a8e4148 OH |
305 | /* Returns -errno, or 0 for domU, or 1 for dom0 */ |
306 | static int detect_vm_xen_dom0(void) { | |
75f86906 | 307 | _cleanup_free_ char *domcap = NULL; |
bdb628ee | 308 | int r; |
b52aae1d | 309 | |
575e6588 OH |
310 | r = read_one_line_file(PATH_FEATURES, &domcap); |
311 | if (r < 0 && r != -ENOENT) | |
312 | return r; | |
d6062e3b | 313 | if (r >= 0) { |
575e6588 OH |
314 | unsigned long features; |
315 | ||
47dbb99a YW |
316 | /* Here, we need to use sscanf() instead of safe_atoul() |
317 | * as the string lacks the leading "0x". */ | |
13e0f9fe OH |
318 | r = sscanf(domcap, "%lx", &features); |
319 | if (r == 1) { | |
575e6588 OH |
320 | r = !!(features & (1U << XENFEAT_dom0)); |
321 | log_debug("Virtualization XEN, found %s with value %08lx, " | |
322 | "XENFEAT_dom0 (indicating the 'hardware domain') is%s set.", | |
323 | PATH_FEATURES, features, r ? "" : " not"); | |
324 | return r; | |
325 | } | |
326 | log_debug("Virtualization XEN, found %s, unhandled content '%s'", | |
327 | PATH_FEATURES, domcap); | |
328 | } | |
329 | ||
75f86906 | 330 | r = read_one_line_file("/proc/xen/capabilities", &domcap); |
9f63a08d | 331 | if (r == -ENOENT) { |
1a8e4148 OH |
332 | log_debug("Virtualization XEN because /proc/xen/capabilities does not exist"); |
333 | return 0; | |
9f63a08d | 334 | } |
d5b687e7 LP |
335 | if (r < 0) |
336 | return r; | |
bdb628ee | 337 | |
31a9be23 YW |
338 | for (const char *i = domcap;;) { |
339 | _cleanup_free_ char *cap = NULL; | |
9f63a08d | 340 | |
31a9be23 YW |
341 | r = extract_first_word(&i, &cap, ",", 0); |
342 | if (r < 0) | |
343 | return r; | |
344 | if (r == 0) { | |
345 | log_debug("Virtualization XEN DomU found (/proc/xen/capabilities)"); | |
346 | return 0; | |
347 | } | |
348 | ||
349 | if (streq(cap, "control_d")) { | |
350 | log_debug("Virtualization XEN Dom0 ignored (/proc/xen/capabilities)"); | |
351 | return 1; | |
352 | } | |
353 | } | |
75f86906 | 354 | } |
37287585 | 355 | |
1b86c7c5 | 356 | static Virtualization detect_vm_xen(void) { |
ea583ed5 RN |
357 | /* The presence of /proc/xen indicates some form of a Xen domain |
358 | The check for Dom0 is handled outside this function */ | |
599be274 BS |
359 | if (access("/proc/xen", F_OK) < 0) { |
360 | log_debug("Virtualization XEN not found, /proc/xen does not exist"); | |
361 | return VIRTUALIZATION_NONE; | |
362 | } | |
363 | log_debug("Virtualization XEN found (/proc/xen exists)"); | |
599be274 BS |
364 | return VIRTUALIZATION_XEN; |
365 | } | |
366 | ||
1b86c7c5 | 367 | static Virtualization detect_vm_hypervisor(void) { |
75f86906 LP |
368 | _cleanup_free_ char *hvtype = NULL; |
369 | int r; | |
37287585 | 370 | |
75f86906 LP |
371 | r = read_one_line_file("/sys/hypervisor/type", &hvtype); |
372 | if (r == -ENOENT) | |
373 | return VIRTUALIZATION_NONE; | |
374 | if (r < 0) | |
375 | return r; | |
37287585 | 376 | |
9f63a08d SS |
377 | log_debug("Virtualization %s found in /sys/hypervisor/type", hvtype); |
378 | ||
75f86906 LP |
379 | if (streq(hvtype, "xen")) |
380 | return VIRTUALIZATION_XEN; | |
381 | else | |
382 | return VIRTUALIZATION_VM_OTHER; | |
383 | } | |
37287585 | 384 | |
1b86c7c5 | 385 | static Virtualization detect_vm_uml(void) { |
6058516a | 386 | _cleanup_fclose_ FILE *f = NULL; |
75f86906 | 387 | int r; |
37287585 | 388 | |
75f86906 | 389 | /* Detect User-Mode Linux by reading /proc/cpuinfo */ |
6058516a ZJS |
390 | f = fopen("/proc/cpuinfo", "re"); |
391 | if (!f) { | |
392 | if (errno == ENOENT) { | |
393 | log_debug("/proc/cpuinfo not found, assuming no UML virtualization."); | |
394 | return VIRTUALIZATION_NONE; | |
395 | } | |
396 | return -errno; | |
ef2a48aa | 397 | } |
9f63a08d | 398 | |
6058516a ZJS |
399 | for (;;) { |
400 | _cleanup_free_ char *line = NULL; | |
401 | const char *t; | |
402 | ||
403 | r = read_line(f, LONG_LINE_MAX, &line); | |
404 | if (r < 0) | |
405 | return r; | |
406 | if (r == 0) | |
407 | break; | |
408 | ||
409 | t = startswith(line, "vendor_id\t: "); | |
410 | if (t) { | |
411 | if (startswith(t, "User Mode Linux")) { | |
412 | log_debug("UML virtualization found in /proc/cpuinfo"); | |
413 | return VIRTUALIZATION_UML; | |
414 | } | |
415 | ||
416 | break; | |
417 | } | |
9f63a08d | 418 | } |
bdb628ee | 419 | |
ef2a48aa | 420 | log_debug("UML virtualization not found in /proc/cpuinfo."); |
75f86906 LP |
421 | return VIRTUALIZATION_NONE; |
422 | } | |
e32886e0 | 423 | |
1b86c7c5 | 424 | static Virtualization detect_vm_zvm(void) { |
bdb628ee | 425 | |
75f86906 LP |
426 | #if defined(__s390__) |
427 | _cleanup_free_ char *t = NULL; | |
428 | int r; | |
e32886e0 | 429 | |
c4cd1d4d | 430 | r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t); |
75f86906 LP |
431 | if (r == -ENOENT) |
432 | return VIRTUALIZATION_NONE; | |
433 | if (r < 0) | |
434 | return r; | |
e32886e0 | 435 | |
9f63a08d | 436 | log_debug("Virtualization %s found in /proc/sysinfo", t); |
75f86906 LP |
437 | if (streq(t, "z/VM")) |
438 | return VIRTUALIZATION_ZVM; | |
439 | else | |
440 | return VIRTUALIZATION_KVM; | |
441 | #else | |
9f63a08d | 442 | log_debug("This platform does not support /proc/sysinfo"); |
75f86906 LP |
443 | return VIRTUALIZATION_NONE; |
444 | #endif | |
445 | } | |
e32886e0 | 446 | |
75f86906 | 447 | /* Returns a short identifier for the various VM implementations */ |
1b86c7c5 LP |
448 | Virtualization detect_vm(void) { |
449 | static thread_local Virtualization cached_found = _VIRTUALIZATION_INVALID; | |
530c1c30 | 450 | bool other = false; |
1b86c7c5 LP |
451 | int xen_dom0 = 0; |
452 | Virtualization v, dmi; | |
e32886e0 | 453 | |
75f86906 LP |
454 | if (cached_found >= 0) |
455 | return cached_found; | |
bdb628ee | 456 | |
f6875b0a | 457 | /* We have to use the correct order here: |
f6875b0a | 458 | * |
2dc65c32 ZJS |
459 | * → First, try to detect Oracle Virtualbox, Amazon EC2 Nitro, Parallels, and Google Compute Engine, |
460 | * even if they use KVM, as well as Xen, even if it cloaks as Microsoft Hyper-V. Attempt to detect | |
461 | * UML at this stage too, since it runs as a user-process nested inside other VMs. Also check for | |
462 | * Xen now, because Xen PV mode does not override CPUID when nested inside another hypervisor. | |
f2fe2865 | 463 | * |
2dc65c32 | 464 | * → Second, try to detect from CPUID. This will report KVM for whatever software is used even if |
840a49f3 | 465 | * info in DMI is overwritten. |
f2fe2865 LP |
466 | * |
467 | * → Third, try to detect from DMI. */ | |
5f1c788c | 468 | |
28b1a3ea | 469 | dmi = detect_vm_dmi(); |
840a49f3 YW |
470 | if (IN_SET(dmi, |
471 | VIRTUALIZATION_ORACLE, | |
472 | VIRTUALIZATION_XEN, | |
473 | VIRTUALIZATION_AMAZON, | |
baa90b4b | 474 | VIRTUALIZATION_PARALLELS, |
475 | VIRTUALIZATION_GOOGLE)) { | |
1b86c7c5 | 476 | v = dmi; |
2f8e375d BR |
477 | goto finish; |
478 | } | |
5f1c788c | 479 | |
c8037dbf | 480 | /* Detect UML */ |
1b86c7c5 LP |
481 | v = detect_vm_uml(); |
482 | if (v < 0) | |
483 | return v; | |
484 | if (v != VIRTUALIZATION_NONE) | |
c8037dbf CO |
485 | goto finish; |
486 | ||
599be274 | 487 | /* Detect Xen */ |
1b86c7c5 LP |
488 | v = detect_vm_xen(); |
489 | if (v < 0) | |
490 | return v; | |
491 | if (v == VIRTUALIZATION_XEN) { | |
ea583ed5 RN |
492 | /* If we are Dom0, then we expect to not report as a VM. However, as we might be nested |
493 | * inside another hypervisor which can be detected via the CPUID check, wait to report this | |
494 | * until after the CPUID check. */ | |
495 | xen_dom0 = detect_vm_xen_dom0(); | |
496 | if (xen_dom0 < 0) | |
497 | return xen_dom0; | |
498 | if (xen_dom0 == 0) | |
499 | goto finish; | |
1b86c7c5 | 500 | } else if (v != VIRTUALIZATION_NONE) |
ea583ed5 | 501 | assert_not_reached(); |
599be274 | 502 | |
c8037dbf | 503 | /* Detect from CPUID */ |
1b86c7c5 LP |
504 | v = detect_vm_cpuid(); |
505 | if (v < 0) | |
506 | return v; | |
507 | if (v == VIRTUALIZATION_VM_OTHER) | |
c2b19b3c | 508 | other = true; |
1b86c7c5 | 509 | else if (v != VIRTUALIZATION_NONE) |
c2b19b3c | 510 | goto finish; |
d831deb5 | 511 | |
ea583ed5 RN |
512 | /* If we are in Dom0 and have not yet finished, finish with the result of detect_vm_cpuid */ |
513 | if (xen_dom0 > 0) | |
514 | goto finish; | |
515 | ||
c2b19b3c LP |
516 | /* Now, let's get back to DMI */ |
517 | if (dmi < 0) | |
518 | return dmi; | |
519 | if (dmi == VIRTUALIZATION_VM_OTHER) | |
520 | other = true; | |
521 | else if (dmi != VIRTUALIZATION_NONE) { | |
1b86c7c5 | 522 | v = dmi; |
c2b19b3c | 523 | goto finish; |
530c1c30 | 524 | } |
b52aae1d | 525 | |
599be274 | 526 | /* Check high-level hypervisor sysfs file */ |
1b86c7c5 LP |
527 | v = detect_vm_hypervisor(); |
528 | if (v < 0) | |
529 | return v; | |
530 | if (v == VIRTUALIZATION_VM_OTHER) | |
c2b19b3c | 531 | other = true; |
1b86c7c5 | 532 | else if (v != VIRTUALIZATION_NONE) |
c2b19b3c | 533 | goto finish; |
f41925b4 | 534 | |
1b86c7c5 LP |
535 | v = detect_vm_device_tree(); |
536 | if (v < 0) | |
537 | return v; | |
538 | if (v == VIRTUALIZATION_VM_OTHER) | |
c2b19b3c | 539 | other = true; |
1b86c7c5 | 540 | else if (v != VIRTUALIZATION_NONE) |
c2b19b3c | 541 | goto finish; |
f41925b4 | 542 | |
1b86c7c5 LP |
543 | v = detect_vm_zvm(); |
544 | if (v < 0) | |
545 | return v; | |
0fb533a5 LP |
546 | |
547 | finish: | |
1b86c7c5 LP |
548 | if (v == VIRTUALIZATION_NONE && other) |
549 | v = VIRTUALIZATION_VM_OTHER; | |
3f61278b | 550 | |
1b86c7c5 LP |
551 | cached_found = v; |
552 | log_debug("Found VM virtualization %s", virtualization_to_string(v)); | |
553 | return v; | |
b52aae1d LP |
554 | } |
555 | ||
735ea55f YW |
556 | static const char *const container_table[_VIRTUALIZATION_MAX] = { |
557 | [VIRTUALIZATION_LXC] = "lxc", | |
558 | [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt", | |
559 | [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", | |
560 | [VIRTUALIZATION_DOCKER] = "docker", | |
561 | [VIRTUALIZATION_PODMAN] = "podman", | |
562 | [VIRTUALIZATION_RKT] = "rkt", | |
563 | [VIRTUALIZATION_WSL] = "wsl", | |
80cc3e3e | 564 | [VIRTUALIZATION_PROOT] = "proot", |
abac810b | 565 | [VIRTUALIZATION_POUCH] = "pouch", |
735ea55f | 566 | }; |
0fb533a5 | 567 | |
735ea55f YW |
568 | DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(container, int); |
569 | ||
0e13779d SB |
570 | static int running_in_cgroupns(void) { |
571 | int r; | |
572 | ||
573 | if (!cg_ns_supported()) | |
574 | return false; | |
575 | ||
576 | r = cg_all_unified(); | |
577 | if (r < 0) | |
578 | return r; | |
579 | ||
580 | if (r) { | |
581 | /* cgroup v2 */ | |
582 | ||
583 | r = access("/sys/fs/cgroup/cgroup.events", F_OK); | |
584 | if (r < 0) { | |
585 | if (errno != ENOENT) | |
586 | return -errno; | |
587 | /* All kernel versions have cgroup.events in nested cgroups. */ | |
588 | return false; | |
589 | } | |
590 | ||
591 | /* There's no cgroup.type in the root cgroup, and future kernel versions | |
592 | * are unlikely to add it since cgroup.type is something that makes no sense | |
593 | * whatsoever in the root cgroup. */ | |
594 | r = access("/sys/fs/cgroup/cgroup.type", F_OK); | |
595 | if (r == 0) | |
596 | return true; | |
597 | if (r < 0 && errno != ENOENT) | |
598 | return -errno; | |
599 | ||
600 | /* On older kernel versions, there's no cgroup.type */ | |
601 | r = access("/sys/kernel/cgroup/features", F_OK); | |
602 | if (r < 0) { | |
603 | if (errno != ENOENT) | |
604 | return -errno; | |
605 | /* This is an old kernel that we know for sure has cgroup.events | |
606 | * only in nested cgroups. */ | |
607 | return true; | |
608 | } | |
609 | ||
610 | /* This is a recent kernel, and cgroup.type doesn't exist, so we must be | |
611 | * in the root cgroup. */ | |
612 | return false; | |
613 | } else { | |
614 | /* cgroup v1 */ | |
615 | ||
616 | /* If systemd controller is not mounted, do not even bother. */ | |
617 | r = access("/sys/fs/cgroup/systemd", F_OK); | |
618 | if (r < 0) { | |
619 | if (errno != ENOENT) | |
620 | return -errno; | |
621 | return false; | |
622 | } | |
623 | ||
624 | /* release_agent only exists in the root cgroup. */ | |
625 | r = access("/sys/fs/cgroup/systemd/release_agent", F_OK); | |
626 | if (r < 0) { | |
627 | if (errno != ENOENT) | |
628 | return -errno; | |
629 | return true; | |
630 | } | |
631 | ||
632 | return false; | |
633 | } | |
634 | } | |
635 | ||
1b86c7c5 | 636 | static Virtualization detect_container_files(void) { |
a4a9a6f7 SB |
637 | static const struct { |
638 | const char *file_path; | |
1b86c7c5 | 639 | Virtualization id; |
a4a9a6f7 SB |
640 | } container_file_table[] = { |
641 | /* https://github.com/containers/podman/issues/6192 */ | |
642 | /* https://github.com/containers/podman/issues/3586#issuecomment-661918679 */ | |
643 | { "/run/.containerenv", VIRTUALIZATION_PODMAN }, | |
644 | /* https://github.com/moby/moby/issues/18355 */ | |
645 | /* Docker must be the last in this table, see below. */ | |
646 | { "/.dockerenv", VIRTUALIZATION_DOCKER }, | |
647 | }; | |
648 | ||
0ee2d5b2 | 649 | for (size_t i = 0; i < ELEMENTSOF(container_file_table); i++) { |
a4a9a6f7 SB |
650 | if (access(container_file_table[i].file_path, F_OK) >= 0) |
651 | return container_file_table[i].id; | |
652 | ||
653 | if (errno != ENOENT) | |
654 | log_debug_errno(errno, | |
655 | "Checking if %s exists failed, ignoring: %m", | |
656 | container_file_table[i].file_path); | |
657 | } | |
658 | ||
659 | return VIRTUALIZATION_NONE; | |
660 | } | |
661 | ||
1b86c7c5 LP |
662 | Virtualization detect_container(void) { |
663 | static thread_local Virtualization cached_found = _VIRTUALIZATION_INVALID; | |
a7e508f8 | 664 | _cleanup_free_ char *m = NULL, *o = NULL, *p = NULL; |
75f86906 | 665 | const char *e = NULL; |
1b86c7c5 | 666 | Virtualization v; |
ab94af92 | 667 | int r; |
b52aae1d | 668 | |
75f86906 | 669 | if (cached_found >= 0) |
0fb533a5 | 670 | return cached_found; |
0fb533a5 | 671 | |
8d6e8034 | 672 | /* /proc/vz exists in container and outside of the container, /proc/bc only outside of the container. */ |
b7ec9e71 LP |
673 | if (access("/proc/vz", F_OK) < 0) { |
674 | if (errno != ENOENT) | |
675 | log_debug_errno(errno, "Failed to check if /proc/vz exists, ignoring: %m"); | |
676 | } else if (access("/proc/bc", F_OK) < 0) { | |
677 | if (errno == ENOENT) { | |
1b86c7c5 | 678 | v = VIRTUALIZATION_OPENVZ; |
b7ec9e71 LP |
679 | goto finish; |
680 | } | |
681 | ||
682 | log_debug_errno(errno, "Failed to check if /proc/bc exists, ignoring: %m"); | |
b52aae1d LP |
683 | } |
684 | ||
4096043f | 685 | /* "Official" way of detecting WSL https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364 */ |
6c8a2c67 | 686 | r = read_one_line_file("/proc/sys/kernel/osrelease", &o); |
b7ec9e71 LP |
687 | if (r < 0) |
688 | log_debug_errno(r, "Failed to read /proc/sys/kernel/osrelease, ignoring: %m"); | |
689 | else if (strstr(o, "Microsoft") || strstr(o, "WSL")) { | |
1b86c7c5 | 690 | v = VIRTUALIZATION_WSL; |
a2f838d5 | 691 | goto finish; |
6c8a2c67 BR |
692 | } |
693 | ||
80cc3e3e CD |
694 | /* proot doesn't use PID namespacing, so we can just check if we have a matching tracer for this |
695 | * invocation without worrying about it being elsewhere. | |
696 | */ | |
697 | r = get_proc_field("/proc/self/status", "TracerPid", WHITESPACE, &p); | |
b7ec9e71 LP |
698 | if (r < 0) |
699 | log_debug_errno(r, "Failed to read our own trace PID, ignoring: %m"); | |
700 | else if (!streq(p, "0")) { | |
80cc3e3e | 701 | pid_t ptrace_pid; |
b7ec9e71 | 702 | |
80cc3e3e | 703 | r = parse_pid(p, &ptrace_pid); |
b7ec9e71 LP |
704 | if (r < 0) |
705 | log_debug_errno(r, "Failed to parse our own tracer PID, ignoring: %m"); | |
706 | else { | |
80cc3e3e | 707 | _cleanup_free_ char *ptrace_comm = NULL; |
b7ec9e71 LP |
708 | const char *pf; |
709 | ||
710 | pf = procfs_file_alloca(ptrace_pid, "comm"); | |
80cc3e3e | 711 | r = read_one_line_file(pf, &ptrace_comm); |
b7ec9e71 LP |
712 | if (r < 0) |
713 | log_debug_errno(r, "Failed to read %s, ignoring: %m", pf); | |
714 | else if (startswith(ptrace_comm, "proot")) { | |
1b86c7c5 | 715 | v = VIRTUALIZATION_PROOT; |
9b4f3fa3 CD |
716 | goto finish; |
717 | } | |
80cc3e3e CD |
718 | } |
719 | } | |
720 | ||
79efcd02 | 721 | /* The container manager might have placed this in the /run/host/ hierarchy for us, which is best |
0f48ba7b LP |
722 | * because we can be consumed just like that, without special privileges. */ |
723 | r = read_one_line_file("/run/host/container-manager", &m); | |
724 | if (r > 0) { | |
725 | e = m; | |
726 | goto translate_name; | |
727 | } | |
728 | if (!IN_SET(r, -ENOENT, 0)) | |
79efcd02 | 729 | return log_debug_errno(r, "Failed to read /run/host/container-manager: %m"); |
0f48ba7b | 730 | |
df0ff127 | 731 | if (getpid_cached() == 1) { |
342bed02 ZJS |
732 | /* If we are PID 1 we can just check our own environment variable, and that's authoritative. |
733 | * We distinguish three cases: | |
734 | * - the variable is not defined → we jump to other checks | |
735 | * - the variable is defined to an empty value → we are not in a container | |
736 | * - anything else → some container, either one of the known ones or "container-other" | |
737 | */ | |
fdd25311 | 738 | e = getenv("container"); |
342bed02 | 739 | if (!e) |
a4a9a6f7 | 740 | goto check_files; |
fdd25311 | 741 | if (isempty(e)) { |
1b86c7c5 | 742 | v = VIRTUALIZATION_NONE; |
fdd25311 LP |
743 | goto finish; |
744 | } | |
fdd25311 | 745 | |
8d6e8034 LP |
746 | goto translate_name; |
747 | } | |
748 | ||
749 | /* Otherwise, PID 1 might have dropped this information into a file in /run. This is better than accessing | |
750 | * /proc/1/environ, since we don't need CAP_SYS_PTRACE for that. */ | |
751 | r = read_one_line_file("/run/systemd/container", &m); | |
a2176045 | 752 | if (r > 0) { |
8d6e8034 LP |
753 | e = m; |
754 | goto translate_name; | |
755 | } | |
a2176045 | 756 | if (!IN_SET(r, -ENOENT, 0)) |
8d6e8034 LP |
757 | return log_debug_errno(r, "Failed to read /run/systemd/container: %m"); |
758 | ||
759 | /* Fallback for cases where PID 1 was not systemd (for example, cases where init=/bin/sh is used. */ | |
760 | r = getenv_for_pid(1, "container", &m); | |
761 | if (r > 0) { | |
fdd25311 | 762 | e = m; |
8d6e8034 | 763 | goto translate_name; |
fdd25311 | 764 | } |
8d6e8034 LP |
765 | if (r < 0) /* This only works if we have CAP_SYS_PTRACE, hence let's better ignore failures here */ |
766 | log_debug_errno(r, "Failed to read $container of PID 1, ignoring: %m"); | |
767 | ||
a4a9a6f7 SB |
768 | check_files: |
769 | /* Check for existence of some well-known files. We only do this after checking | |
770 | * for other specific container managers, otherwise we risk mistaking another | |
771 | * container manager for Docker: the /.dockerenv file could inadvertently end up | |
772 | * in a file system image. */ | |
1b86c7c5 LP |
773 | v = detect_container_files(); |
774 | if (v < 0) | |
775 | return v; | |
776 | if (v != VIRTUALIZATION_NONE) | |
a4a9a6f7 SB |
777 | goto finish; |
778 | ||
0e13779d SB |
779 | r = running_in_cgroupns(); |
780 | if (r > 0) { | |
1b86c7c5 | 781 | v = VIRTUALIZATION_CONTAINER_OTHER; |
0e13779d SB |
782 | goto finish; |
783 | } | |
784 | if (r < 0) | |
785 | log_debug_errno(r, "Failed to detect cgroup namespace: %m"); | |
786 | ||
a4a9a6f7 | 787 | /* If none of that worked, give up, assume no container manager. */ |
1b86c7c5 | 788 | v = VIRTUALIZATION_NONE; |
8d6e8034 | 789 | goto finish; |
b52aae1d | 790 | |
8d6e8034 | 791 | translate_name: |
a4a9a6f7 SB |
792 | if (streq(e, "oci")) { |
793 | /* Some images hardcode container=oci, but OCI is not a specific container manager. | |
794 | * Try to detect one based on well-known files. */ | |
1b86c7c5 | 795 | v = detect_container_files(); |
a91078bc | 796 | if (v == VIRTUALIZATION_NONE) |
1b86c7c5 | 797 | v = VIRTUALIZATION_CONTAINER_OTHER; |
a4a9a6f7 SB |
798 | goto finish; |
799 | } | |
1b86c7c5 LP |
800 | v = container_from_string(e); |
801 | if (v < 0) | |
802 | v = VIRTUALIZATION_CONTAINER_OTHER; | |
fdd25311 | 803 | |
0fb533a5 | 804 | finish: |
1b86c7c5 LP |
805 | log_debug("Found container virtualization %s.", virtualization_to_string(v)); |
806 | cached_found = v; | |
807 | return v; | |
b52aae1d LP |
808 | } |
809 | ||
1b86c7c5 LP |
810 | Virtualization detect_virtualization(void) { |
811 | int v; | |
b52aae1d | 812 | |
1b86c7c5 LP |
813 | v = detect_container(); |
814 | if (v != VIRTUALIZATION_NONE) | |
815 | return v; | |
b52aae1d | 816 | |
1b86c7c5 | 817 | return detect_vm(); |
b52aae1d | 818 | } |
75f86906 | 819 | |
299a34c1 ZJS |
820 | static int userns_has_mapping(const char *name) { |
821 | _cleanup_fclose_ FILE *f = NULL; | |
7312c422 | 822 | uid_t base, shift, range; |
299a34c1 ZJS |
823 | int r; |
824 | ||
825 | f = fopen(name, "re"); | |
826 | if (!f) { | |
827 | log_debug_errno(errno, "Failed to open %s: %m", name); | |
abd67ce7 | 828 | return errno == ENOENT ? false : -errno; |
299a34c1 ZJS |
829 | } |
830 | ||
7312c422 MY |
831 | r = uid_map_read_one(f, &base, &shift, &range); |
832 | if (r == -ENOMSG) { | |
833 | log_debug("%s is empty, we're in an uninitialized user namespace.", name); | |
b2a331f2 | 834 | return true; |
299a34c1 | 835 | } |
7312c422 MY |
836 | if (r < 0) |
837 | return log_debug_errno(r, "Failed to read %s: %m", name); | |
299a34c1 | 838 | |
7312c422 | 839 | if (base == 0 && shift == 0 && range == UINT32_MAX) { |
299a34c1 ZJS |
840 | /* The kernel calls mappings_overlap() and does not allow overlaps */ |
841 | log_debug("%s has a full 1:1 mapping", name); | |
842 | return false; | |
843 | } | |
844 | ||
845 | /* Anything else implies that we are in a user namespace */ | |
7312c422 | 846 | log_debug("Mapping found in %s, we're in a user namespace.", name); |
299a34c1 ZJS |
847 | return true; |
848 | } | |
849 | ||
850 | int running_in_userns(void) { | |
851 | _cleanup_free_ char *line = NULL; | |
852 | int r; | |
853 | ||
854 | r = userns_has_mapping("/proc/self/uid_map"); | |
855 | if (r != 0) | |
856 | return r; | |
857 | ||
858 | r = userns_has_mapping("/proc/self/gid_map"); | |
859 | if (r != 0) | |
860 | return r; | |
861 | ||
16fa4746 LP |
862 | /* "setgroups" file was added in kernel v3.18-rc6-15-g9cc46516dd. It is also possible to compile a |
863 | * kernel without CONFIG_USER_NS, in which case "setgroups" also does not exist. We cannot | |
864 | * distinguish those two cases, so assume that we're running on a stripped-down recent kernel, rather | |
865 | * than on an old one, and if the file is not found, return false. */ | |
866 | r = read_virtual_file("/proc/self/setgroups", SIZE_MAX, &line, NULL); | |
299a34c1 ZJS |
867 | if (r < 0) { |
868 | log_debug_errno(r, "/proc/self/setgroups: %m"); | |
869 | return r == -ENOENT ? false : r; | |
870 | } | |
871 | ||
16fa4746 LP |
872 | strstrip(line); /* remove trailing newline */ |
873 | ||
299a34c1 ZJS |
874 | r = streq(line, "deny"); |
875 | /* See user_namespaces(7) for a description of this "setgroups" contents. */ | |
876 | log_debug("/proc/self/setgroups contains \"%s\", %s user namespace", line, r ? "in" : "not in"); | |
877 | return r; | |
878 | } | |
879 | ||
7f4b3c5e | 880 | int running_in_chroot(void) { |
ef2a48aa | 881 | int r; |
7f4b3c5e | 882 | |
1a25a77f ZJS |
883 | /* If we're PID1, /proc may not be mounted (and most likely we're not in a chroot). But PID1 will |
884 | * mount /proc, so all other programs can assume that if /proc is *not* available, we're in some | |
885 | * chroot. */ | |
886 | ||
08a28eec LN |
887 | if (getenv_bool("SYSTEMD_IGNORE_CHROOT") > 0) |
888 | return 0; | |
889 | ||
563e6846 | 890 | r = inode_same("/proc/1/root", "/", 0); |
1a25a77f ZJS |
891 | if (r == -ENOENT) { |
892 | r = proc_mounted(); | |
893 | if (r == 0) { | |
7636caf5 YW |
894 | if (getpid_cached() == 1) |
895 | return false; /* We will mount /proc, assuming we're not in a chroot. */ | |
896 | ||
1a25a77f | 897 | log_debug("/proc is not mounted, assuming we're in a chroot."); |
7636caf5 | 898 | return true; |
1a25a77f ZJS |
899 | } |
900 | if (r > 0) /* If we have fake /proc/, we can't do the check properly. */ | |
901 | return -ENOSYS; | |
902 | } | |
ef2a48aa ZJS |
903 | if (r < 0) |
904 | return r; | |
7f4b3c5e | 905 | |
ef2a48aa | 906 | return r == 0; |
7f4b3c5e LP |
907 | } |
908 | ||
68337e55 GS |
909 | #if defined(__i386__) || defined(__x86_64__) |
910 | struct cpuid_table_entry { | |
911 | uint32_t flag_bit; | |
912 | const char *name; | |
913 | }; | |
914 | ||
915 | static const struct cpuid_table_entry leaf1_edx[] = { | |
5c86cec1 YW |
916 | { 0, "fpu" }, |
917 | { 1, "vme" }, | |
918 | { 2, "de" }, | |
919 | { 3, "pse" }, | |
920 | { 4, "tsc" }, | |
921 | { 5, "msr" }, | |
922 | { 6, "pae" }, | |
923 | { 7, "mce" }, | |
924 | { 8, "cx8" }, | |
925 | { 9, "apic" }, | |
926 | { 11, "sep" }, | |
927 | { 12, "mtrr" }, | |
928 | { 13, "pge" }, | |
929 | { 14, "mca" }, | |
930 | { 15, "cmov" }, | |
931 | { 16, "pat" }, | |
932 | { 17, "pse36" }, | |
68337e55 | 933 | { 19, "clflush" }, |
5c86cec1 YW |
934 | { 23, "mmx" }, |
935 | { 24, "fxsr" }, | |
936 | { 25, "sse" }, | |
937 | { 26, "sse2" }, | |
938 | { 28, "ht" }, | |
68337e55 GS |
939 | }; |
940 | ||
941 | static const struct cpuid_table_entry leaf1_ecx[] = { | |
5c86cec1 YW |
942 | { 0, "pni" }, |
943 | { 1, "pclmul" }, | |
68337e55 | 944 | { 3, "monitor" }, |
5c86cec1 YW |
945 | { 9, "ssse3" }, |
946 | { 12, "fma3" }, | |
947 | { 13, "cx16" }, | |
948 | { 19, "sse4_1" }, | |
949 | { 20, "sse4_2" }, | |
950 | { 22, "movbe" }, | |
951 | { 23, "popcnt" }, | |
952 | { 25, "aes" }, | |
953 | { 26, "xsave" }, | |
68337e55 | 954 | { 27, "osxsave" }, |
5c86cec1 YW |
955 | { 28, "avx" }, |
956 | { 29, "f16c" }, | |
957 | { 30, "rdrand" }, | |
68337e55 GS |
958 | }; |
959 | ||
960 | static const struct cpuid_table_entry leaf7_ebx[] = { | |
5c86cec1 YW |
961 | { 3, "bmi1" }, |
962 | { 5, "avx2" }, | |
963 | { 8, "bmi2" }, | |
68337e55 | 964 | { 18, "rdseed" }, |
5c86cec1 | 965 | { 19, "adx" }, |
68337e55 GS |
966 | { 29, "sha_ni" }, |
967 | }; | |
968 | ||
969 | static const struct cpuid_table_entry leaf81_edx[] = { | |
970 | { 11, "syscall" }, | |
5c86cec1 YW |
971 | { 27, "rdtscp" }, |
972 | { 29, "lm" }, | |
68337e55 GS |
973 | }; |
974 | ||
975 | static const struct cpuid_table_entry leaf81_ecx[] = { | |
976 | { 0, "lahf_lm" }, | |
5c86cec1 | 977 | { 5, "abm" }, |
68337e55 GS |
978 | }; |
979 | ||
980 | static const struct cpuid_table_entry leaf87_edx[] = { | |
981 | { 8, "constant_tsc" }, | |
982 | }; | |
983 | ||
984 | static bool given_flag_in_set(const char *flag, const struct cpuid_table_entry *set, size_t set_size, uint32_t val) { | |
985 | for (size_t i = 0; i < set_size; i++) { | |
986 | if ((UINT32_C(1) << set[i].flag_bit) & val && | |
987 | streq(flag, set[i].name)) | |
988 | return true; | |
989 | } | |
990 | return false; | |
991 | } | |
992 | ||
993 | static bool real_has_cpu_with_flag(const char *flag) { | |
994 | uint32_t eax, ebx, ecx, edx; | |
995 | ||
996 | if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { | |
997 | if (given_flag_in_set(flag, leaf1_ecx, ELEMENTSOF(leaf1_ecx), ecx)) | |
998 | return true; | |
999 | ||
1000 | if (given_flag_in_set(flag, leaf1_edx, ELEMENTSOF(leaf1_edx), edx)) | |
1001 | return true; | |
1002 | } | |
1003 | ||
e7014399 | 1004 | if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) { |
68337e55 GS |
1005 | if (given_flag_in_set(flag, leaf7_ebx, ELEMENTSOF(leaf7_ebx), ebx)) |
1006 | return true; | |
1007 | } | |
1008 | ||
1009 | if (__get_cpuid(0x80000001U, &eax, &ebx, &ecx, &edx)) { | |
1010 | if (given_flag_in_set(flag, leaf81_ecx, ELEMENTSOF(leaf81_ecx), ecx)) | |
1011 | return true; | |
1012 | ||
1013 | if (given_flag_in_set(flag, leaf81_edx, ELEMENTSOF(leaf81_edx), edx)) | |
1014 | return true; | |
1015 | } | |
1016 | ||
1017 | if (__get_cpuid(0x80000007U, &eax, &ebx, &ecx, &edx)) | |
1018 | if (given_flag_in_set(flag, leaf87_edx, ELEMENTSOF(leaf87_edx), edx)) | |
1019 | return true; | |
1020 | ||
1021 | return false; | |
1022 | } | |
1023 | #endif | |
1024 | ||
1025 | bool has_cpu_with_flag(const char *flag) { | |
1026 | /* CPUID is an x86 specific interface. Assume on all others that no CPUs have those flags. */ | |
1027 | #if defined(__i386__) || defined(__x86_64__) | |
1028 | return real_has_cpu_with_flag(flag); | |
1029 | #else | |
1030 | return false; | |
1031 | #endif | |
1032 | } | |
1033 | ||
75f86906 | 1034 | static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { |
5c86cec1 YW |
1035 | [VIRTUALIZATION_NONE] = "none", |
1036 | [VIRTUALIZATION_KVM] = "kvm", | |
1037 | [VIRTUALIZATION_AMAZON] = "amazon", | |
1038 | [VIRTUALIZATION_QEMU] = "qemu", | |
1039 | [VIRTUALIZATION_BOCHS] = "bochs", | |
1040 | [VIRTUALIZATION_XEN] = "xen", | |
1041 | [VIRTUALIZATION_UML] = "uml", | |
1042 | [VIRTUALIZATION_VMWARE] = "vmware", | |
1043 | [VIRTUALIZATION_ORACLE] = "oracle", | |
1044 | [VIRTUALIZATION_MICROSOFT] = "microsoft", | |
1045 | [VIRTUALIZATION_ZVM] = "zvm", | |
1046 | [VIRTUALIZATION_PARALLELS] = "parallels", | |
1047 | [VIRTUALIZATION_BHYVE] = "bhyve", | |
1048 | [VIRTUALIZATION_QNX] = "qnx", | |
1049 | [VIRTUALIZATION_ACRN] = "acrn", | |
1050 | [VIRTUALIZATION_POWERVM] = "powervm", | |
f5558306 | 1051 | [VIRTUALIZATION_APPLE] = "apple", |
d833ed78 | 1052 | [VIRTUALIZATION_SRE] = "sre", |
9b0688f4 | 1053 | [VIRTUALIZATION_GOOGLE] = "google", |
5c86cec1 YW |
1054 | [VIRTUALIZATION_VM_OTHER] = "vm-other", |
1055 | ||
1056 | [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", | |
1057 | [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt", | |
1058 | [VIRTUALIZATION_LXC] = "lxc", | |
1059 | [VIRTUALIZATION_OPENVZ] = "openvz", | |
1060 | [VIRTUALIZATION_DOCKER] = "docker", | |
1061 | [VIRTUALIZATION_PODMAN] = "podman", | |
1062 | [VIRTUALIZATION_RKT] = "rkt", | |
1063 | [VIRTUALIZATION_WSL] = "wsl", | |
1064 | [VIRTUALIZATION_PROOT] = "proot", | |
1065 | [VIRTUALIZATION_POUCH] = "pouch", | |
75f86906 LP |
1066 | [VIRTUALIZATION_CONTAINER_OTHER] = "container-other", |
1067 | }; | |
1068 | ||
1b86c7c5 | 1069 | DEFINE_STRING_TABLE_LOOKUP(virtualization, Virtualization); |