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