chiark / gitweb /
virt: rework container detection logic
[elogind.git] / src / shared / virt.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <string.h>
23 #include <errno.h>
24 #include <unistd.h>
25
26 #include "util.h"
27 #include "virt.h"
28 #include "fileio.h"
29
30 static int detect_vm_cpuid(const char **_id) {
31
32         /* Both CPUID and DMI are x86 specific interfaces... */
33 #if defined(__i386__) || defined(__x86_64__)
34
35         static const char cpuid_vendor_table[] =
36                 "XenVMMXenVMM\0"          "xen\0"
37                 "KVMKVMKVM\0"             "kvm\0"
38                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
39                 "VMwareVMware\0"          "vmware\0"
40                 /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */
41                 "Microsoft Hv\0"          "microsoft\0";
42
43         uint32_t eax, ecx;
44         union {
45                 uint32_t sig32[3];
46                 char text[13];
47         } sig = {};
48         const char *j, *k;
49         bool hypervisor;
50
51         /* http://lwn.net/Articles/301888/ */
52
53 #if defined (__i386__)
54 #define REG_a "eax"
55 #define REG_b "ebx"
56 #elif defined (__amd64__)
57 #define REG_a "rax"
58 #define REG_b "rbx"
59 #endif
60
61         /* First detect whether there is a hypervisor */
62         eax = 1;
63         __asm__ __volatile__ (
64                 /* ebx/rbx is being used for PIC! */
65                 "  push %%"REG_b"         \n\t"
66                 "  cpuid                  \n\t"
67                 "  pop %%"REG_b"          \n\t"
68
69                 : "=a" (eax), "=c" (ecx)
70                 : "0" (eax)
71         );
72
73         hypervisor = !!(ecx & 0x80000000U);
74
75         if (hypervisor) {
76
77                 /* There is a hypervisor, see what it is */
78                 eax = 0x40000000U;
79                 __asm__ __volatile__ (
80                         /* ebx/rbx is being used for PIC! */
81                         "  push %%"REG_b"         \n\t"
82                         "  cpuid                  \n\t"
83                         "  mov %%ebx, %1          \n\t"
84                         "  pop %%"REG_b"          \n\t"
85
86                         : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
87                         : "0" (eax)
88                 );
89
90                 NULSTR_FOREACH_PAIR(j, k, cpuid_vendor_table)
91                         if (streq(sig.text, j)) {
92                                 *_id = k;
93                                 return 1;
94                         }
95
96                 *_id = "other";
97                 return 0;
98         }
99 #endif
100
101         return 0;
102 }
103
104 static int detect_vm_dmi(const char **_id) {
105
106         /* Both CPUID and DMI are x86 specific interfaces... */
107 #if defined(__i386__) || defined(__x86_64__)
108
109         static const char *const dmi_vendors[] = {
110                 "/sys/class/dmi/id/sys_vendor",
111                 "/sys/class/dmi/id/board_vendor",
112                 "/sys/class/dmi/id/bios_vendor"
113         };
114
115         static const char dmi_vendor_table[] =
116                 "QEMU\0"                  "qemu\0"
117                 /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
118                 "VMware\0"                "vmware\0"
119                 "VMW\0"                   "vmware\0"
120                 "innotek GmbH\0"          "oracle\0"
121                 "Xen\0"                   "xen\0"
122                 "Bochs\0"                 "bochs\0";
123         unsigned i;
124
125         for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
126                 _cleanup_free_ char *s = NULL;
127                 const char *j, *k;
128                 int r;
129
130                 r = read_one_line_file(dmi_vendors[i], &s);
131                 if (r < 0) {
132                         if (r != -ENOENT)
133                                 return r;
134
135                         continue;
136                 }
137
138                 NULSTR_FOREACH_PAIR(j, k, dmi_vendor_table)
139                         if (startswith(s, j)) {
140                                 *_id = k;
141                                 return 1;
142                         }
143         }
144 #endif
145
146         return 0;
147 }
148
149 /* Returns a short identifier for the various VM implementations */
150 int detect_vm(const char **id) {
151         _cleanup_free_ char *hvtype = NULL, *cpuinfo_contents = NULL;
152         static thread_local int cached_found = -1;
153         static thread_local const char *cached_id = NULL;
154         const char *_id = NULL;
155         int r;
156
157         if (_likely_(cached_found >= 0)) {
158
159                 if (id)
160                         *id = cached_id;
161
162                 return cached_found;
163         }
164
165         /* Try high-level hypervisor sysfs file first:
166          *
167          * https://bugs.freedesktop.org/show_bug.cgi?id=61491 */
168         r = read_one_line_file("/sys/hypervisor/type", &hvtype);
169         if (r >= 0) {
170                 if (streq(hvtype, "xen")) {
171                         _id = "xen";
172                         r = 1;
173                         goto finish;
174                 }
175         } else if (r != -ENOENT)
176                 return r;
177
178         /* this will set _id to "other" and return 0 for unknown hypervisors */
179         r = detect_vm_cpuid(&_id);
180         if (r != 0)
181                 goto finish;
182
183         r = detect_vm_dmi(&_id);
184         if (r != 0)
185                 goto finish;
186
187         if (_id) {
188                 /* "other" */
189                 r = 1;
190                 goto finish;
191         }
192
193         /* Detect User-Mode Linux by reading /proc/cpuinfo */
194         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
195         if (r < 0)
196                 return r;
197         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
198                 _id = "uml";
199                 r = 1;
200                 goto finish;
201         }
202
203         r = 0;
204
205 finish:
206         cached_found = r;
207
208         cached_id = _id;
209         if (id)
210                 *id = _id;
211
212         return r;
213 }
214
215 int detect_container(const char **id) {
216
217         static thread_local int cached_found = -1;
218         static thread_local const char *cached_id = NULL;
219
220         _cleanup_free_ char *m = NULL;
221         const char *_id = NULL, *e = NULL;
222         int r;
223
224         if (_likely_(cached_found >= 0)) {
225
226                 if (id)
227                         *id = cached_id;
228
229                 return cached_found;
230         }
231
232         /* /proc/vz exists in container and outside of the container,
233          * /proc/bc only outside of the container. */
234         if (access("/proc/vz", F_OK) >= 0 &&
235             access("/proc/bc", F_OK) < 0) {
236                 _id = "openvz";
237                 r = 1;
238                 goto finish;
239         }
240
241         if (getpid() == 1) {
242                 /* If we are PID 1 we can just check our own
243                  * environment variable */
244
245                 e = getenv("container");
246                 if (isempty(e)) {
247                         r = 0;
248                         goto finish;
249                 }
250         } else {
251
252                 /* Otherwise, PID 1 dropped this information into a
253                  * file in /run. This is better than accessing
254                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
255                  * for that. */
256
257                 r = read_one_line_file("/run/systemd/container", &m);
258                 if (r == -ENOENT) {
259                         r = 0;
260                         goto finish;
261                 }
262                 if (r < 0)
263                         return r;
264
265                 e = m;
266         }
267
268         /* We only recognize a selected few here, since we want to
269          * enforce a redacted namespace */
270         if (streq(e, "lxc"))
271                 _id ="lxc";
272         else if (streq(e, "lxc-libvirt"))
273                 _id = "lxc-libvirt";
274         else if (streq(e, "systemd-nspawn"))
275                 _id = "systemd-nspawn";
276         else
277                 _id = "other";
278
279         r = 1;
280
281 finish:
282         cached_found = r;
283
284         cached_id = _id;
285         if (id)
286                 *id = _id;
287
288         return r;
289 }
290
291 /* Returns a short identifier for the various VM/container implementations */
292 int detect_virtualization(const char **id) {
293         int r;
294
295         r = detect_container(id);
296         if (r < 0)
297                 return r;
298         if (r > 0)
299                 return VIRTUALIZATION_CONTAINER;
300
301         r = detect_vm(id);
302         if (r < 0)
303                 return r;
304         if (r > 0)
305                 return VIRTUALIZATION_VM;
306
307         return VIRTUALIZATION_NONE;
308 }