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