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