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