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