chiark / gitweb /
b4368952ffac3d8f3dde431caff786e39f11a5b8
[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 *domcap = 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 xen capabilities file first, if not found try high-level hypervisor sysfs file:
166          *
167          * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */
168         r = read_one_line_file("/proc/xen/capabilities", &domcap);
169         if (r >= 0) {
170                 char *cap, *i = domcap;
171
172                 while ((cap = strsep(&i, ",")))
173                         if (streq(cap, "control_d"))
174                                 break;
175
176                 if (!cap)  {
177                         _id = "xen";
178                         r = 1;
179                 }
180
181                 goto finish;
182
183         } else if (r == -ENOENT) {
184                 _cleanup_free_ char *hvtype = NULL;
185
186                 r = read_one_line_file("/sys/hypervisor/type", &hvtype);
187                 if (r >= 0) {
188                         if (streq(hvtype, "xen")) {
189                                 _id = "xen";
190                                 r = 1;
191                                 goto finish;
192                         }
193                 } else if (r != -ENOENT)
194                         return r;
195         } else
196                 return r;
197
198         /* this will set _id to "other" and return 0 for unknown hypervisors */
199         r = detect_vm_cpuid(&_id);
200         if (r != 0)
201                 goto finish;
202
203         r = detect_vm_dmi(&_id);
204         if (r != 0)
205                 goto finish;
206
207         if (_id) {
208                 /* "other" */
209                 r = 1;
210                 goto finish;
211         }
212
213         /* Detect User-Mode Linux by reading /proc/cpuinfo */
214         r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL);
215         if (r < 0)
216                 return r;
217         if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) {
218                 _id = "uml";
219                 r = 1;
220                 goto finish;
221         }
222
223 #if defined(__s390__)
224         {
225                 _cleanup_free_ char *t = NULL;
226
227                 r = get_status_field("/proc/sysinfo", "VM00 Control Program:", &t);
228                 if (r >= 0) {
229                         if (streq(t, "z/VM"))
230                                 _id = "zvm";
231                         else
232                                 _id = "kvm";
233                         r = 1;
234
235                         goto finish;
236                 }
237         }
238 #endif
239
240         r = 0;
241
242 finish:
243         cached_found = r;
244
245         cached_id = _id;
246         if (id)
247                 *id = _id;
248
249         return r;
250 }
251
252 int detect_container(const char **id) {
253
254         static thread_local int cached_found = -1;
255         static thread_local const char *cached_id = NULL;
256
257         _cleanup_free_ char *m = NULL;
258         const char *_id = NULL, *e = NULL;
259         int r;
260
261         if (_likely_(cached_found >= 0)) {
262
263                 if (id)
264                         *id = cached_id;
265
266                 return cached_found;
267         }
268
269         /* /proc/vz exists in container and outside of the container,
270          * /proc/bc only outside of the container. */
271         if (access("/proc/vz", F_OK) >= 0 &&
272             access("/proc/bc", F_OK) < 0) {
273                 _id = "openvz";
274                 r = 1;
275                 goto finish;
276         }
277
278         if (getpid() == 1) {
279                 /* If we are PID 1 we can just check our own
280                  * environment variable */
281
282                 e = getenv("container");
283                 if (isempty(e)) {
284                         r = 0;
285                         goto finish;
286                 }
287         } else {
288
289                 /* Otherwise, PID 1 dropped this information into a
290                  * file in /run. This is better than accessing
291                  * /proc/1/environ, since we don't need CAP_SYS_PTRACE
292                  * for that. */
293
294                 r = read_one_line_file("/run/systemd/container", &m);
295                 if (r == -ENOENT) {
296                         r = 0;
297                         goto finish;
298                 }
299                 if (r < 0)
300                         return r;
301
302                 e = m;
303         }
304
305         /* We only recognize a selected few here, since we want to
306          * enforce a redacted namespace */
307         if (streq(e, "lxc"))
308                 _id ="lxc";
309         else if (streq(e, "lxc-libvirt"))
310                 _id = "lxc-libvirt";
311         else if (streq(e, "systemd-nspawn"))
312                 _id = "systemd-nspawn";
313         else
314                 _id = "other";
315
316         r = 1;
317
318 finish:
319         cached_found = r;
320
321         cached_id = _id;
322         if (id)
323                 *id = _id;
324
325         return r;
326 }
327
328 /* Returns a short identifier for the various VM/container implementations */
329 int detect_virtualization(const char **id) {
330         int r;
331
332         r = detect_container(id);
333         if (r < 0)
334                 return r;
335         if (r > 0)
336                 return VIRTUALIZATION_CONTAINER;
337
338         r = detect_vm(id);
339         if (r < 0)
340                 return r;
341         if (r > 0)
342                 return VIRTUALIZATION_VM;
343
344         return VIRTUALIZATION_NONE;
345 }