]>
Commit | Line | Data |
---|---|---|
5f4159ab MT |
1 | /* |
2 | * fireinfo.c | |
3 | * | |
4 | * Copyright (C) 2010 IPFire. All rights reserved. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <Python.h> | |
21 | ||
22 | #include <sys/utsname.h> | |
23 | ||
24 | /* | |
25 | Big parts of this were taken from | |
26 | http://git.kernel.org/?p=utils/util-linux-ng/util-linux-ng.git;a=blob;f=sys-utils/lscpu.c | |
27 | */ | |
28 | ||
29 | /* /sys paths */ | |
30 | #define _PATH_SYS_SYSTEM "/sys/devices/system" | |
31 | #define _PATH_SYS_CPU _PATH_SYS_SYSTEM "/cpu" | |
32 | #define _PATH_PROC_XEN "/proc/xen" | |
33 | #define _PATH_PROC_XENCAP _PATH_PROC_XEN "/capabilities" | |
34 | #define _PATH_PROC_CPUINFO "/proc/cpuinfo" | |
35 | #define _PATH_PROC_PCIDEVS "/proc/bus/pci/devices" | |
36 | ||
37 | /* virtualization types */ | |
38 | enum { | |
39 | VIRT_NONE = 0, | |
40 | VIRT_PARA, | |
41 | VIRT_FULL | |
42 | }; | |
43 | const char *virt_types[] = { | |
44 | [VIRT_NONE] = "none", | |
45 | [VIRT_PARA] = "para", | |
46 | [VIRT_FULL] = "full" | |
47 | }; | |
48 | ||
49 | /* hypervisor vendors */ | |
50 | enum { | |
51 | HYPER_NONE = 0, | |
52 | HYPER_XEN, | |
53 | HYPER_KVM, | |
284eedb7 MT |
54 | HYPER_MSHV, |
55 | HYPER_VMWARE | |
5f4159ab MT |
56 | }; |
57 | const char *hv_vendors[] = { | |
58 | [HYPER_NONE] = NULL, | |
59 | [HYPER_XEN] = "Xen", | |
60 | [HYPER_KVM] = "KVM", | |
284eedb7 MT |
61 | [HYPER_MSHV] = "Microsoft", |
62 | [HYPER_VMWARE] = "VMWare" | |
5f4159ab MT |
63 | }; |
64 | ||
65 | /* CPU modes (bits) */ | |
66 | enum { | |
67 | MODE_REAL = (1 << 1), | |
68 | MODE_TRANSPARENT = (1 << 2), | |
69 | MODE_LONG = (1 << 3) | |
70 | }; | |
71 | ||
72 | /* global description */ | |
73 | struct lscpu_desc { | |
74 | char *arch; | |
75 | char *vendor; | |
76 | char *family; | |
77 | char *model; | |
78 | char *virtflag; /* virtualization flag (vmx, svm) */ | |
79 | int hyper; /* hypervisor vendor ID */ | |
80 | int virtype; /* VIRT_PARA|FULL|NONE ? */ | |
81 | char *mhz; | |
82 | char *stepping; | |
83 | char *bogomips; | |
84 | char *flags; | |
85 | int mode; /* rm, lm or/and tm */ | |
86 | ||
87 | int ncpus; /* number of CPUs */ | |
88 | }; | |
89 | ||
90 | static size_t sysrootlen; | |
91 | static char pathbuf[PATH_MAX]; | |
92 | static int maxcpus; /* size in bits of kernel cpu mask */ | |
93 | ||
94 | static FILE *path_fopen(const char *mode, int exit_on_err, const char *path, ...) | |
95 | __attribute__ ((__format__ (__printf__, 3, 4))); | |
96 | static int path_exist(const char *path, ...) | |
97 | __attribute__ ((__format__ (__printf__, 1, 2))); | |
98 | ||
99 | static const char * | |
100 | path_vcreate(const char *path, va_list ap) | |
101 | { | |
102 | if (sysrootlen) | |
103 | vsnprintf(pathbuf + sysrootlen, | |
104 | sizeof(pathbuf) - sysrootlen, path, ap); | |
105 | else | |
106 | vsnprintf(pathbuf, sizeof(pathbuf), path, ap); | |
107 | return pathbuf; | |
108 | } | |
109 | ||
110 | static FILE * | |
111 | path_vfopen(const char *mode, int exit_on_error, const char *path, va_list ap) | |
112 | { | |
113 | FILE *f; | |
114 | const char *p = path_vcreate(path, ap); | |
115 | ||
116 | f = fopen(p, mode); | |
117 | if (!f && exit_on_error) | |
118 | PyErr_SetString(PyExc_Exception, | |
119 | (const char *)sprintf("error: cannot open %s", p)); | |
120 | return f; | |
121 | } | |
122 | ||
123 | static FILE * | |
124 | path_fopen(const char *mode, int exit_on_error, const char *path, ...) | |
125 | { | |
126 | FILE *fd; | |
127 | va_list ap; | |
128 | ||
129 | va_start(ap, path); | |
130 | fd = path_vfopen(mode, exit_on_error, path, ap); | |
131 | va_end(ap); | |
132 | ||
133 | return fd; | |
134 | } | |
135 | ||
136 | static int | |
137 | path_getnum(const char *path, ...) | |
138 | { | |
139 | FILE *fd; | |
140 | va_list ap; | |
141 | int result; | |
142 | ||
143 | va_start(ap, path); | |
144 | fd = path_vfopen("r", 1, path, ap); | |
145 | va_end(ap); | |
146 | ||
147 | if (fscanf(fd, "%d", &result) != 1) { | |
148 | if (ferror(fd)) | |
149 | PyErr_SetString(PyExc_Exception, (const char *)sprintf("failed to read: %s", pathbuf)); | |
150 | else | |
151 | PyErr_SetString(PyExc_Exception, (const char *)sprintf("parse error: %s", pathbuf)); | |
152 | } | |
153 | fclose(fd); | |
154 | return result; | |
155 | } | |
156 | ||
157 | static int | |
158 | path_exist(const char *path, ...) | |
159 | { | |
160 | va_list ap; | |
161 | const char *p; | |
162 | ||
163 | va_start(ap, path); | |
164 | p = path_vcreate(path, ap); | |
165 | va_end(ap); | |
166 | ||
167 | return access(p, F_OK) == 0; | |
168 | } | |
169 | ||
170 | static char * | |
171 | xstrdup(const char *str) | |
172 | { | |
173 | char *s = strdup(str); | |
174 | if (!s) | |
175 | PyErr_SetString(PyExc_Exception, "error: strdup failed"); | |
176 | return s; | |
177 | } | |
178 | ||
179 | /* Lookup a pattern and get the value from cpuinfo. | |
180 | * Format is: | |
181 | * | |
182 | * "<pattern> : <key>" | |
183 | */ | |
184 | int lookup(char *line, char *pattern, char **value) | |
185 | { | |
186 | char *p, *v; | |
187 | int len = strlen(pattern); | |
188 | ||
189 | if (!*line) | |
190 | return 0; | |
191 | ||
192 | /* pattern */ | |
193 | if (strncmp(line, pattern, len)) | |
194 | return 0; | |
195 | ||
196 | /* white spaces */ | |
197 | for (p = line + len; isspace(*p); p++); | |
198 | ||
199 | /* separator */ | |
200 | if (*p != ':') | |
201 | return 0; | |
202 | ||
203 | /* white spaces */ | |
204 | for (++p; isspace(*p); p++); | |
205 | ||
206 | /* value */ | |
207 | if (!*p) | |
208 | return 0; | |
209 | v = p; | |
210 | ||
211 | /* end of value */ | |
212 | len = strlen(line) - 1; | |
213 | for (p = line + len; isspace(*(p-1)); p--); | |
214 | *p = '\0'; | |
215 | ||
216 | *value = xstrdup(v); | |
217 | return 1; | |
218 | } | |
219 | ||
220 | static void | |
221 | read_basicinfo(struct lscpu_desc *desc) | |
222 | { | |
223 | FILE *fp = path_fopen("r", 1, _PATH_PROC_CPUINFO); | |
224 | char buf[BUFSIZ]; | |
225 | struct utsname utsbuf; | |
226 | ||
227 | /* architecture */ | |
228 | if (uname(&utsbuf) == -1) | |
229 | PyErr_SetString(PyExc_Exception, "error: uname failed"); | |
230 | desc->arch = xstrdup(utsbuf.machine); | |
231 | ||
232 | /* count CPU(s) */ | |
233 | while(path_exist(_PATH_SYS_SYSTEM "/cpu/cpu%d", desc->ncpus)) | |
234 | desc->ncpus++; | |
235 | ||
236 | /* details */ | |
237 | while (fgets(buf, sizeof(buf), fp) != NULL) { | |
238 | /* IA64 */ | |
239 | if (lookup(buf, "vendor", &desc->vendor)) ; | |
240 | else if (lookup(buf, "vendor_id", &desc->vendor)) ; | |
241 | /* IA64 */ | |
242 | else if (lookup(buf, "family", &desc->family)) ; | |
243 | else if (lookup(buf, "cpu family", &desc->family)) ; | |
244 | else if (lookup(buf, "model", &desc->model)) ; | |
245 | else if (lookup(buf, "stepping", &desc->stepping)) ; | |
246 | else if (lookup(buf, "cpu MHz", &desc->mhz)) ; | |
247 | else if (lookup(buf, "flags", &desc->flags)) ; | |
248 | else if (lookup(buf, "bogomips", &desc->bogomips)) ; | |
249 | else | |
250 | continue; | |
251 | } | |
252 | ||
253 | if (desc->flags) { | |
254 | snprintf(buf, sizeof(buf), " %s ", desc->flags); | |
255 | if (strstr(buf, " svm ")) | |
256 | desc->virtflag = strdup("svm"); | |
257 | else if (strstr(buf, " vmx ")) | |
258 | desc->virtflag = strdup("vmx"); | |
259 | ||
260 | if (strstr(buf, " rm ")) | |
261 | desc->mode |= MODE_REAL; | |
262 | if (strstr(buf, " tm ")) | |
263 | desc->mode |= MODE_TRANSPARENT; | |
264 | if (strstr(buf, " lm ")) | |
265 | desc->mode |= MODE_LONG; | |
266 | } | |
267 | ||
268 | fclose(fp); | |
269 | ||
270 | if (path_exist(_PATH_SYS_SYSTEM "/cpu/kernel_max")) | |
271 | maxcpus = path_getnum(_PATH_SYS_SYSTEM "/cpu/kernel_max"); | |
272 | ||
273 | else | |
274 | /* we are reading some /sys snapshot instead of the real /sys, | |
275 | * let's use any crazy number... */ | |
276 | maxcpus = desc->ncpus > 2048 ? desc->ncpus : 2048; | |
277 | } | |
278 | ||
279 | static int | |
280 | has_pci_device(int vendor, int device) | |
281 | { | |
282 | FILE *f; | |
283 | int num, fn, ven, dev; | |
284 | int res = 1; | |
285 | ||
286 | f = path_fopen("r", 0, _PATH_PROC_PCIDEVS); | |
287 | if (!f) | |
288 | return 0; | |
289 | ||
290 | /* for more details about bus/pci/devices format see | |
291 | * drivers/pci/proc.c in linux kernel | |
292 | */ | |
293 | while(fscanf(f, "%02x%02x\t%04x%04x\t%*[^\n]", | |
294 | &num, &fn, &ven, &dev) == 4) { | |
295 | ||
296 | if (ven == vendor && dev == device) | |
297 | goto found; | |
298 | } | |
299 | ||
300 | res = 0; | |
301 | found: | |
302 | fclose(f); | |
303 | return res; | |
304 | } | |
305 | ||
306 | #if defined(__x86_64__) || defined(__i386__) | |
307 | ||
308 | /* | |
309 | * This CPUID leaf returns the information about the hypervisor. | |
310 | * EAX : maximum input value for CPUID supported by the hypervisor. | |
311 | * EBX, ECX, EDX : Hypervisor vendor ID signature. E.g. VMwareVMware. | |
312 | */ | |
313 | #define HYPERVISOR_INFO_LEAF 0x40000000 | |
314 | ||
315 | static inline void | |
316 | cpuid(unsigned int op, unsigned int *eax, unsigned int *ebx, | |
317 | unsigned int *ecx, unsigned int *edx) | |
318 | { | |
319 | __asm__( | |
320 | #if defined(__PIC__) && defined(__i386__) | |
321 | /* x86 PIC cannot clobber ebx -- gcc bitches */ | |
322 | "pushl %%ebx;" | |
323 | "cpuid;" | |
324 | "movl %%ebx, %%esi;" | |
325 | "popl %%ebx;" | |
326 | : "=S" (*ebx), | |
327 | #else | |
328 | "cpuid;" | |
329 | : "=b" (*ebx), | |
330 | #endif | |
331 | "=a" (*eax), | |
332 | "=c" (*ecx), | |
333 | "=d" (*edx) | |
334 | : "1" (op), "c"(0)); | |
335 | } | |
336 | ||
337 | static void | |
338 | read_hypervisor_cpuid(struct lscpu_desc *desc) | |
339 | { | |
340 | unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0; | |
341 | char hyper_vendor_id[13]; | |
342 | ||
343 | memset(hyper_vendor_id, 0, sizeof(hyper_vendor_id)); | |
344 | ||
345 | cpuid(HYPERVISOR_INFO_LEAF, &eax, &ebx, &ecx, &edx); | |
346 | memcpy(hyper_vendor_id + 0, &ebx, 4); | |
347 | memcpy(hyper_vendor_id + 4, &ecx, 4); | |
348 | memcpy(hyper_vendor_id + 8, &edx, 4); | |
349 | hyper_vendor_id[12] = '\0'; | |
350 | ||
351 | if (!hyper_vendor_id[0]) | |
352 | return; | |
353 | ||
354 | if (!strncmp("XenVMMXenVMM", hyper_vendor_id, 12)) | |
355 | desc->hyper = HYPER_XEN; | |
356 | else if (!strncmp("KVMKVMKVM", hyper_vendor_id, 9)) | |
357 | desc->hyper = HYPER_KVM; | |
358 | else if (!strncmp("Microsoft Hv", hyper_vendor_id, 12)) | |
359 | desc->hyper = HYPER_MSHV; | |
284eedb7 MT |
360 | else if (!strncmp("VMwareVMware", hyper_vendor_id, 12)) |
361 | desc->hyper = HYPER_VMWARE; | |
5f4159ab MT |
362 | } |
363 | ||
364 | #else /* ! __x86_64__ */ | |
365 | static void | |
366 | read_hypervisor_cpuid(struct lscpu_desc *desc) | |
367 | { | |
368 | } | |
369 | #endif | |
370 | ||
371 | static void | |
372 | read_hypervisor(struct lscpu_desc *desc) | |
373 | { | |
374 | read_hypervisor_cpuid(desc); | |
375 | ||
376 | if (desc->hyper) | |
377 | /* hvm */ | |
378 | desc->virtype = VIRT_FULL; | |
379 | ||
380 | else if (path_exist(_PATH_PROC_XEN)) { | |
381 | /* Xen para-virt or dom0 */ | |
382 | FILE *fd = path_fopen("r", 0, _PATH_PROC_XENCAP); | |
383 | int dom0 = 0; | |
384 | ||
385 | if (fd) { | |
386 | char buf[256]; | |
387 | ||
388 | if (fscanf(fd, "%s", buf) == 1 && | |
389 | !strcmp(buf, "control_d")) | |
390 | dom0 = 1; | |
391 | fclose(fd); | |
392 | } | |
393 | desc->virtype = dom0 ? VIRT_NONE : VIRT_PARA; | |
394 | desc->hyper = HYPER_XEN; | |
395 | ||
396 | } else if (has_pci_device(0x5853, 0x0001)) { | |
397 | /* Xen full-virt on non-x86_64 */ | |
398 | desc->hyper = HYPER_XEN; | |
399 | desc->virtype = VIRT_FULL; | |
400 | } | |
401 | } | |
402 | ||
403 | static PyObject * | |
404 | do_cpuinfo() { | |
405 | /* | |
406 | This function collects information about the CPU. | |
407 | */ | |
408 | struct lscpu_desc _desc, *desc = &_desc; | |
409 | memset(desc, 0, sizeof(*desc)); | |
410 | ||
411 | read_basicinfo(desc); | |
412 | read_hypervisor(desc); | |
413 | ||
414 | PyObject *o = NULL; | |
415 | PyObject *d = PyDict_New(); | |
416 | ||
417 | /* Arch */ | |
418 | PyDict_SetItemString(d, "arch", PyString_FromString(desc->arch)); | |
419 | ||
420 | /* Vendor */ | |
421 | PyDict_SetItemString(d, "vendor", PyString_FromString(desc->vendor)); | |
422 | ||
423 | /* Family */ | |
424 | PyDict_SetItemString(d, "family", PyString_FromString(desc->family)); | |
425 | ||
426 | /* Model */ | |
427 | PyDict_SetItemString(d, "model", PyString_FromString(desc->model)); | |
428 | ||
429 | /* Virtualization flag */ | |
a57a7635 MT |
430 | if (desc->virtflag) { |
431 | o = PyString_FromString(desc->virtflag); | |
432 | } else { | |
433 | o = Py_None; | |
434 | } | |
435 | PyDict_SetItemString(d, "virtflag", o); | |
5f4159ab MT |
436 | |
437 | /* Stepping */ | |
438 | PyDict_SetItemString(d, "stepping", PyString_FromString(desc->stepping)); | |
439 | ||
440 | /* MHz */ | |
441 | PyDict_SetItemString(d, "mhz", PyString_FromString(desc->mhz)); | |
442 | ||
443 | /* Bogomips */ | |
444 | PyDict_SetItemString(d, "bogomips", PyString_FromString(desc->bogomips)); | |
445 | ||
446 | /* Flags */ | |
447 | PyDict_SetItemString(d, "flags", PyString_FromString(desc->flags)); | |
448 | ||
449 | /* Mode */ | |
450 | if (desc->mode & (MODE_REAL | MODE_TRANSPARENT | MODE_LONG)) { | |
451 | o = PyList_New(0); | |
452 | ||
453 | if (desc->mode & MODE_REAL) { | |
454 | PyList_Append(o, (PyObject *)PyInt_FromLong(16)); | |
455 | } | |
456 | if (desc->mode & MODE_TRANSPARENT) { | |
457 | PyList_Append(o, (PyObject *)PyInt_FromLong(32)); | |
458 | } | |
459 | if (desc->mode & MODE_LONG) { | |
460 | PyList_Append(o, (PyObject *)PyInt_FromLong(64)); | |
461 | } | |
462 | PyDict_SetItemString(d, "mode", o); | |
463 | } | |
464 | ||
465 | /* Hypervisor */ | |
466 | if (desc->hyper == HYPER_NONE) { | |
467 | o = Py_None; | |
468 | } else { | |
469 | o = PyString_FromString((const char *)hv_vendors[desc->hyper]); | |
470 | } | |
471 | PyDict_SetItemString(d, "hypervisor", o); | |
472 | ||
473 | /* Virtualization type */ | |
474 | if (desc->virtype == VIRT_NONE) { | |
475 | o = Py_None; | |
476 | } else { | |
477 | o = PyString_FromString((const char *)virt_types[desc->virtype]); | |
478 | } | |
479 | PyDict_SetItemString(d, "virtype", o); | |
480 | ||
481 | /* Number of CPUs */ | |
482 | PyDict_SetItemString(d, "ncpus", PyInt_FromLong(desc->ncpus)); | |
483 | ||
484 | return d; | |
485 | } | |
486 | ||
487 | static PyMethodDef fireinfoModuleMethods[] = { | |
488 | { "cpuinfo", (PyCFunction) do_cpuinfo, METH_NOARGS, NULL }, | |
489 | { NULL, NULL, 0, NULL } | |
490 | }; | |
491 | ||
492 | void init_fireinfo(void) { | |
493 | PyObject *m; | |
494 | ||
495 | m = Py_InitModule("_fireinfo", fireinfoModuleMethods); | |
496 | } |