]> git.ipfire.org Git - oddments/fireinfo.git/blame - src/fireinfo/system.py
bogomips: Don't crash when no bogomips are available
[oddments/fireinfo.git] / src / fireinfo / system.py
CommitLineData
b45f0e98 1#!/usr/bin/python
3b5ed4e1
MT
2###############################################################################
3# #
4# Fireinfo #
5# Copyright (C) 2010, 2011 IPFire Team (www.ipfire.org) #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
b45f0e98 21
31a30328 22import hashlib
b45f0e98
MT
23import json
24import os
25import string
26
3f70e7fd
MT
27import _fireinfo
28
e1ca6671 29import bios
b45f0e98
MT
30import cpu
31import device
715ba5ac 32import hypervisor
4cb61965 33import network
b45f0e98 34
0c5ef738
MT
35PROFILE_VERSION = 0
36
45b74ad5 37SYS_CLASS_DMI = "/sys/class/dmi/id"
73014efe 38SECRET_ID_FILE = "/etc/fireinfo-id"
45b74ad5 39
7eadbfba
MT
40INVALID_ID_STRINGS = (
41 "OEM", "O.E.M.", "o.e.m.",
42 "N/A", "n/a",
74ce4c8d 43 "12345", "54321", "202020",
ed491340 44 "Chassis", "chassis",
80365bb3 45 "EVAL",
40cffee0 46 "Not Applicable",
dafc2399 47 "None", "empty",
80365bb3 48 "System Serial Number",
220ffe76 49 "XXXXX",
40cffee0 50 "01010101-0101-0101-0101-010101010101",
ac68f90a 51 "00020003-0004-0005-0006-000700080009",
92b4cddf 52 "03000200-0400-0500-0006-000700080009",
80365bb3 53 "11111111-1111-1111-1111-111111111111",
2689ff71 54 "0000000", "00000000",
7eadbfba
MT
55)
56
220ffe76
MT
57INVALID_ID_STRINGS_EXACT_MATCH = (
58 "NA",
59)
60
32aeec73
MT
61class Singleton(type):
62 def __init__(cls, name, bases, dict):
63 super(Singleton, cls).__init__(name, bases, dict)
64 cls.instance = None
65
66 def __call__(cls, *args, **kw):
67 if cls.instance is None:
68 cls.instance = super(Singleton, cls).__call__(*args, **kw)
69
70 return cls.instance
71
72
45b74ad5
MT
73def read_from_file(filename):
74 """
75 Read all data from filename.
76 """
77 if not os.path.exists(filename):
78 return
79
80 try:
81 with open(filename) as f:
82 return f.read().strip()
83 except IOError:
84 pass
85
b45f0e98 86class System(object):
32aeec73 87 __metaclass__ = Singleton
b45f0e98
MT
88
89 def __init__(self):
e1ca6671
MT
90 self.bios = bios.BIOS(self)
91
b45f0e98
MT
92 # find all devices
93 self.devices = []
94 self.scan()
95 self.cpu = cpu.CPU()
715ba5ac
MT
96 self.hypervisor = hypervisor.Hypervisor()
97
3f4e98ab
MT
98 # Read /proc/cpuinfo for vendor information.
99 self.__cpuinfo = self.cpu.read_cpuinfo()
100
b45f0e98 101 def profile(self):
0c5ef738
MT
102 p = {}
103 p["system"] = {
45b74ad5
MT
104 # System information
105 "model" : self.model,
106 "vendor" : self.vendor,
107
0c5ef738
MT
108 # Indicator if the system is running in a
109 # virtual environment.
d8614fc3 110 "virtual" : self.virtual,
b45f0e98 111
0c5ef738
MT
112 # System language
113 "language" : self.language,
114
115 # Release information
116 "release" : self.release,
117 "kernel_release" : self.kernel_release,
118
119 "memory" : self.memory,
120 "root_size" : self.root_size,
121 }
122
123 p["devices"] = []
b45f0e98 124 for device in self.devices:
97679775 125 d = {
b45f0e98
MT
126 "subsystem" : device.subsystem.lower(),
127 "vendor" : device.vendor.lower(),
128 "model" : device.model.lower(),
7923840b
MT
129 "deviceclass" : device.deviceclass,
130 "driver" : device.driver,
97679775
MT
131 }
132
133 # PCI devices provide subsystem information, USB don't.
134 if d["subsystem"] == "pci":
135 d["sub_model"] = device.sub_model
136 d["sub_vendor"] = device.sub_vendor
137
138 p["devices"].append(d)
139
b45f0e98 140 p["cpu"] = {
0c5ef738 141 "arch" : self.arch,
b45f0e98
MT
142 "vendor" : self.cpu.vendor,
143 "model" : self.cpu.model,
eecf2eea 144 "model_string" : self.cpu.model_string,
b45f0e98
MT
145 "stepping" : self.cpu.stepping,
146 "flags" : self.cpu.flags,
b45f0e98 147 "speed" : self.cpu.speed,
b45f0e98
MT
148 "family" : self.cpu.family,
149 "count" : self.cpu.count
150 }
715ba5ac 151
a9401d95
MT
152 if self.cpu.bogomips:
153 p["bogomips"] = self.cpu.bogomips
154
4cb61965
MT
155 p["network"] = {
156 "green" : self.network.has_green(),
157 "blue" : self.network.has_blue(),
158 "orange" : self.network.has_orange(),
159 "red" : self.network.has_red(),
160 }
161
715ba5ac
MT
162 # Only append hypervisor information if we are virtualized.
163 if self.virtual:
164 p["hypervisor"] = {
715ba5ac
MT
165 "vendor" : self.hypervisor.vendor,
166 }
167
0c5ef738
MT
168 return {
169 # Profile version
170 "profile_version" : PROFILE_VERSION,
171
172 # Identification and authorization codes
173 "public_id" : self.public_id,
174 "private_id" : self.private_id,
175
176 # Actual profile data
177 "profile" : p,
178 }
b45f0e98
MT
179
180
181 @property
182 def arch(self):
183 return os.uname()[4]
184
185 @property
186 def public_id(self):
31a30328
MT
187 """
188 This returns a globally (hopefully) ID to identify the host
189 later (by request) in the database.
190 """
73014efe 191 public_id = self.secret_id
3d10b6d9
MT
192 if not public_id:
193 return "0" * 40
194
195 return hashlib.sha1(public_id).hexdigest()
196
b45f0e98
MT
197 @property
198 def private_id(self):
31a30328
MT
199 """
200 The private ID is built out of the _unique_id and used to
201 permit a host to do changes on the database.
202
203 No one could ever guess this without access to the host.
204 """
5e2ba24e 205 private_id = ""
73014efe 206 for i in reversed(self.secret_id):
5e2ba24e
MT
207 private_id += i
208
3d10b6d9
MT
209 if not private_id:
210 return "0" * 40
211
5e2ba24e 212 return hashlib.sha1(private_id).hexdigest()
31a30328 213
73014efe
MT
214 @property
215 def secret_id(self):
216 """
217 Read a "secret" ID from a file if available
218 or calculate it from the hardware.
219 """
220 if os.path.exists(SECRET_ID_FILE):
221 return read_from_file(SECRET_ID_FILE)
222
223 return hashlib.sha1(self._unique_id).hexdigest()
224
31a30328
MT
225 @property
226 def _unique_id(self):
227 """
228 This is a helper ID which is generated out of some hardware information
229 that is considered to be constant over a PC's lifetime.
230
231 None of the data here is ever sent to the server.
232 """
ec65e266 233 ids = []
3d10b6d9
MT
234
235 # Virtual machines (for example) and some boards have a UUID
236 # which is globally unique.
237 for file in ("product_uuid", "product_serial", "chassis_serial"):
7eadbfba 238 id = read_from_file(os.path.join(SYS_CLASS_DMI, file))
c0ef2eb7 239 ids.append(id)
3d10b6d9 240
7eadbfba
MT
241 # Sort out all bogous or invalid strings from the list.
242 _ids = []
243 for id in ids:
88b0ded5
MT
244 if id is None:
245 continue
246
220ffe76
MT
247 for i in INVALID_ID_STRINGS_EXACT_MATCH:
248 if id == i:
c0ef2eb7 249 id = None
7eadbfba
MT
250 break
251
220ffe76
MT
252 if id:
253 for i in INVALID_ID_STRINGS:
254 if i in id:
255 id = None
256 break
257
7eadbfba
MT
258 if id:
259 _ids.append(id)
260
261 ids = _ids
262
17dc2486
MT
263 # Use serial number from root disk (if available) and if
264 # no other ID was found, yet.
265 if not ids:
266 root_disk_serial = self.root_disk_serial
4ea06541 267 if root_disk_serial and not root_disk_serial.startswith("QM000"):
17dc2486 268 ids.append(root_disk_serial)
121f9f20 269
f05eac0d
MT
270 # As last resort, we use the UUID from pakfire.
271 if not ids:
272 id = read_from_file("/opt/pakfire/db/uuid")
273 ids.append(id)
274
ec65e266 275 return "#".join(ids)
31a30328 276
b45f0e98
MT
277 @property
278 def language(self):
e8221cde
MT
279 """
280 Return the language code of IPFire or "unknown" if we cannot get it.
281 """
b3ea53a7
MT
282 # Return "unknown" if settings file does not exist.
283 filename = "/var/ipfire/main/settings"
284 if not os.path.exists(filename):
285 return "unknown"
286
287 with open(filename, "r") as f:
b45f0e98
MT
288 for line in f.readlines():
289 key, val = line.split("=", 1)
e8221cde 290 if key == "LANGUAGE":
b45f0e98
MT
291 return val.strip()
292
293 @property
294 def release(self):
e8221cde
MT
295 """
296 Return the system release string.
297 """
3f4e98ab 298 return read_from_file("/etc/system-release") or "unknown"
b56bde73
SP
299
300 @property
301 def bios_vendor(self):
e8221cde
MT
302 """
303 Return the bios vendor name.
304 """
770c8841 305 return read_from_file("/sys/class/dmi/id/bios_vendor")
b56bde73 306
3f4e98ab
MT
307 def vendor_model_tuple(self):
308 try:
309 s = self.__cpuinfo["Hardware"]
310 except KeyError:
311 return (None, None)
312
313 if s.startswith("ARM-Versatile"):
314 return ("ARM", s)
315
cf062e0b
MT
316 try:
317 v, m = s.split(" ", 1)
318 except ValueError:
319 if s.startswith("BCM"):
320 v = "Broadcom"
321 m = s
322 else:
323 v = None
324 m = s
3f4e98ab
MT
325
326 return v, m
327
45b74ad5
MT
328 @property
329 def vendor(self):
e8221cde
MT
330 """
331 Return the vendor string of this system (if any).
332 """
45b74ad5 333 ret = None
240fefea 334 for file in ("sys_vendor", "board_vendor", "chassis_vendor",):
45b74ad5
MT
335 ret = read_from_file(os.path.join(SYS_CLASS_DMI, file))
336 if ret:
337 break
338
3f4e98ab
MT
339 if ret is None:
340 v, m = self.vendor_model_tuple()
341 ret = v
342
45b74ad5
MT
343 return ret
344
345 @property
346 def model(self):
e8221cde
MT
347 """
348 Return the model string of this system (if any).
349 """
45b74ad5 350 ret = None
84efbcb4 351 for file in ("product_name", "board_model", "chassis_model",):
45b74ad5
MT
352 ret = read_from_file(os.path.join(SYS_CLASS_DMI, file))
353 if ret:
354 break
355
3f4e98ab
MT
356 if ret is None:
357 v, m = self.vendor_model_tuple()
358 ret = m
359
45b74ad5
MT
360 return ret
361
b45f0e98
MT
362 @property
363 def memory(self):
e8221cde
MT
364 """
365 Return the amount of memory in kilobytes.
366 """
b45f0e98
MT
367 with open("/proc/meminfo", "r") as f:
368 firstline = f.readline().strip()
2ced5413 369 return int(firstline.split()[1])
b45f0e98
MT
370
371 @property
0c5ef738 372 def kernel_release(self):
e8221cde
MT
373 """
374 Return the kernel release string.
375 """
b45f0e98
MT
376 return os.uname()[2]
377
378 @property
379 def root_disk(self):
e8221cde
MT
380 """
381 Return the dev node of the root disk.
382 """
758f624a 383 with open("/etc/mtab", "r") as f:
ca85ac3a
MT
384 dev, mountpoint, fs, rest = f.readline().split(" ", 3)
385 if mountpoint == "/" and not fs == "rootfs":
e8221cde 386 # Cut off /dev
b45f0e98 387 dev = dev[5:]
cf062e0b
MT
388
389 # Handle raids and MMC cards like (mmcblk0p3).
390 if dev[-2] == "p":
391 return dev[:-2]
392
393 # Otherwise cut off all digits at end of string
b45f0e98 394 while dev[-1] in string.digits:
e8221cde
MT
395 dev = dev[:-1]
396
397 return dev
b45f0e98
MT
398
399 @property
400 def root_size(self):
e8221cde
MT
401 """
402 Return the size of the root disk in kilobytes.
403 """
404 path = "/sys/block/%s/size" % self.root_disk
b45f0e98
MT
405 if not os.path.exists(path):
406 return
e8221cde 407
b45f0e98 408 with open(path, "r") as f:
e8221cde 409 return int(f.readline()) * 512 / 1024
3f70e7fd
MT
410
411 @property
412 def root_disk_serial(self):
e8221cde
MT
413 """
414 Return the serial number of the root disk (if any).
415 """
7890c775
MT
416 try:
417 serial = _fireinfo.get_harddisk_serial("/dev/%s" % self.root_disk)
418 except OSError:
419 return
e658b1a0
MT
420
421 if serial:
422 # Strip all spaces
423 return serial.strip()
424
b45f0e98 425 def scan(self):
e8221cde
MT
426 """
427 Scan for all devices (PCI/USB) in the system and append them
428 to our list.
429 """
430 self.devices = []
431
432 toscan = (
433 ("/sys/bus/pci/devices", device.PCIDevice),
434 ("/sys/bus/usb/devices", device.USBDevice)
435 )
b45f0e98 436 for path, cls in toscan:
94700953
MT
437 if not os.path.exists(path):
438 continue
439
b45f0e98
MT
440 dirlist = os.listdir(path)
441 for dir in dirlist:
442 self.devices.append(cls(os.path.join(path, dir)))
d8614fc3
MT
443
444 @property
445 def virtual(self):
446 """
447 Say if the host is running in a virtual environment.
448 """
715ba5ac 449 return self.hypervisor.virtual
b45f0e98 450
4cb61965
MT
451 @property
452 def network(self):
453 """
454 Reference to the network class.
455 """
456 return network.Network()
457
b45f0e98
MT
458
459if __name__ == "__main__":
460 s=System()
461 print s.arch
462 print s.language
463 print s.release
b56bde73 464 print s.bios_vendor
b45f0e98
MT
465 print s.memory
466 print s.kernel
467 print s.root_disk
468 print s.root_size
469 print "------------\n", s.devices, "\n------------\n"
65891720 470 print json.dumps(s.profile(), sort_keys=True, indent=4)