chiark / gitweb /
verbs: when invoking the default verb, pass a faked argv array, with just the verb...
[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
297                         /* Fallback for cases where PID 1 was not
298                          * systemd (for example, cases where
299                          * init=/bin/sh is used. */
300
301                         r = getenv_for_pid(1, "container", &m);
302                         if (r <= 0) {
303
304                                 /* If that didn't work, give up,
305                                  * assume no container manager.
306                                  *
307                                  * Note: This means we still cannot
308                                  * detect containers if init=/bin/sh
309                                  * is passed but privileges dropped,
310                                  * as /proc/1/environ is only readable
311                                  * with privileges. */
312
313                                 r = 0;
314                                 goto finish;
315                         }
316                 }
317                 if (r < 0)
318                         return r;
319
320                 e = m;
321         }
322
323         /* We only recognize a selected few here, since we want to
324          * enforce a redacted namespace */
325         if (streq(e, "lxc"))
326                 _id ="lxc";
327         else if (streq(e, "lxc-libvirt"))
328                 _id = "lxc-libvirt";
329         else if (streq(e, "systemd-nspawn"))
330                 _id = "systemd-nspawn";
331         else if (streq(e, "docker"))
332                 _id = "docker";
333         else
334                 _id = "other";
335
336         r = 1;
337
338 finish:
339         cached_found = r;
340
341         cached_id = _id;
342         if (id)
343                 *id = _id;
344
345         return r;
346 }
347
348 /* Returns a short identifier for the various VM/container implementations */
349 int detect_virtualization(const char **id) {
350         int r;
351
352         r = detect_container(id);
353         if (r < 0)
354                 return r;
355         if (r > 0)
356                 return VIRTUALIZATION_CONTAINER;
357
358         r = detect_vm(id);
359         if (r < 0)
360                 return r;
361         if (r > 0)
362                 return VIRTUALIZATION_VM;
363
364         return VIRTUALIZATION_NONE;
365 }