]> git.ipfire.org Git - oddments/fireinfo.git/blame - fireinfo/system.py
Bump version 2.0.2.
[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",
7eadbfba
MT
44)
45
32aeec73
MT
46class 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
45b74ad5
MT
58def 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
b45f0e98 71class System(object):
32aeec73 72 __metaclass__ = Singleton
b45f0e98
MT
73
74 def __init__(self):
75 # find all devices
76 self.devices = []
77 self.scan()
78 self.cpu = cpu.CPU()
715ba5ac
MT
79 self.hypervisor = hypervisor.Hypervisor()
80
b45f0e98 81 def profile(self):
0c5ef738
MT
82 p = {}
83 p["system"] = {
45b74ad5
MT
84 # System information
85 "model" : self.model,
86 "vendor" : self.vendor,
87
0c5ef738
MT
88 # Indicator if the system is running in a
89 # virtual environment.
d8614fc3 90 "virtual" : self.virtual,
b45f0e98 91
0c5ef738
MT
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"] = []
b45f0e98 104 for device in self.devices:
97679775 105 d = {
b45f0e98
MT
106 "subsystem" : device.subsystem.lower(),
107 "vendor" : device.vendor.lower(),
108 "model" : device.model.lower(),
7923840b
MT
109 "deviceclass" : device.deviceclass,
110 "driver" : device.driver,
97679775
MT
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
b45f0e98 120 p["cpu"] = {
0c5ef738 121 "arch" : self.arch,
b45f0e98
MT
122 "vendor" : self.cpu.vendor,
123 "model" : self.cpu.model,
eecf2eea 124 "model_string" : self.cpu.model_string,
b45f0e98
MT
125 "stepping" : self.cpu.stepping,
126 "flags" : self.cpu.flags,
127 "bogomips" : self.cpu.bogomips,
128 "speed" : self.cpu.speed,
b45f0e98
MT
129 "family" : self.cpu.family,
130 "count" : self.cpu.count
131 }
715ba5ac 132
4cb61965
MT
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
715ba5ac
MT
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
0c5ef738
MT
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 }
b45f0e98
MT
158
159
160 @property
161 def arch(self):
162 return os.uname()[4]
163
164 @property
165 def public_id(self):
31a30328
MT
166 """
167 This returns a globally (hopefully) ID to identify the host
168 later (by request) in the database.
169 """
73014efe 170 public_id = self.secret_id
3d10b6d9
MT
171 if not public_id:
172 return "0" * 40
173
174 return hashlib.sha1(public_id).hexdigest()
175
b45f0e98
MT
176 @property
177 def private_id(self):
31a30328
MT
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 """
5e2ba24e 184 private_id = ""
73014efe 185 for i in reversed(self.secret_id):
5e2ba24e
MT
186 private_id += i
187
3d10b6d9
MT
188 if not private_id:
189 return "0" * 40
190
5e2ba24e 191 return hashlib.sha1(private_id).hexdigest()
31a30328 192
73014efe
MT
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
31a30328
MT
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 """
ec65e266 212 ids = []
3d10b6d9
MT
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"):
7eadbfba 217 id = read_from_file(os.path.join(SYS_CLASS_DMI, file))
c0ef2eb7 218 ids.append(id)
3d10b6d9 219
7eadbfba
MT
220 # Sort out all bogous or invalid strings from the list.
221 _ids = []
222 for id in ids:
88b0ded5
MT
223 if id is None:
224 continue
225
7eadbfba 226 for i in INVALID_ID_STRINGS:
c0ef2eb7
MT
227 if i in id:
228 id = None
7eadbfba
MT
229 break
230
231 if id:
232 _ids.append(id)
233
234 ids = _ids
235
17dc2486
MT
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
4ea06541 240 if root_disk_serial and not root_disk_serial.startswith("QM000"):
17dc2486 241 ids.append(root_disk_serial)
121f9f20 242
f05eac0d
MT
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
ec65e266 248 return "#".join(ids)
31a30328 249
b45f0e98
MT
250 @property
251 def language(self):
e8221cde
MT
252 """
253 Return the language code of IPFire or "unknown" if we cannot get it.
254 """
b3ea53a7
MT
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:
b45f0e98
MT
261 for line in f.readlines():
262 key, val = line.split("=", 1)
e8221cde 263 if key == "LANGUAGE":
b45f0e98
MT
264 return val.strip()
265
266 @property
267 def release(self):
e8221cde
MT
268 """
269 Return the system release string.
270 """
770c8841 271 return read_from_file("/etc/system-release")
b56bde73
SP
272
273 @property
274 def bios_vendor(self):
e8221cde
MT
275 """
276 Return the bios vendor name.
277 """
770c8841 278 return read_from_file("/sys/class/dmi/id/bios_vendor")
b56bde73 279
45b74ad5
MT
280 @property
281 def vendor(self):
e8221cde
MT
282 """
283 Return the vendor string of this system (if any).
284 """
45b74ad5 285 ret = None
240fefea 286 for file in ("sys_vendor", "board_vendor", "chassis_vendor",):
45b74ad5
MT
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):
e8221cde
MT
295 """
296 Return the model string of this system (if any).
297 """
45b74ad5 298 ret = None
84efbcb4 299 for file in ("product_name", "board_model", "chassis_model",):
45b74ad5
MT
300 ret = read_from_file(os.path.join(SYS_CLASS_DMI, file))
301 if ret:
302 break
303
304 return ret
305
b45f0e98
MT
306 @property
307 def memory(self):
e8221cde
MT
308 """
309 Return the amount of memory in kilobytes.
310 """
b45f0e98
MT
311 with open("/proc/meminfo", "r") as f:
312 firstline = f.readline().strip()
2ced5413 313 return int(firstline.split()[1])
b45f0e98
MT
314
315 @property
0c5ef738 316 def kernel_release(self):
e8221cde
MT
317 """
318 Return the kernel release string.
319 """
b45f0e98
MT
320 return os.uname()[2]
321
322 @property
323 def root_disk(self):
e8221cde
MT
324 """
325 Return the dev node of the root disk.
326 """
b45f0e98 327 with open("/etc/mtab", "r") as f:
e8221cde 328 dev, mountpoint, rest = f.readline().split(" ", 2)
b45f0e98 329 if mountpoint == "/":
e8221cde 330 # Cut off /dev
b45f0e98 331 dev = dev[5:]
e8221cde 332 # Cut off all digits at end of string
b45f0e98 333 while dev[-1] in string.digits:
e8221cde
MT
334 dev = dev[:-1]
335
336 return dev
b45f0e98
MT
337
338 @property
339 def root_size(self):
e8221cde
MT
340 """
341 Return the size of the root disk in kilobytes.
342 """
343 path = "/sys/block/%s/size" % self.root_disk
b45f0e98
MT
344 if not os.path.exists(path):
345 return
e8221cde 346
b45f0e98 347 with open(path, "r") as f:
e8221cde 348 return int(f.readline()) * 512 / 1024
3f70e7fd
MT
349
350 @property
351 def root_disk_serial(self):
e8221cde
MT
352 """
353 Return the serial number of the root disk (if any).
354 """
e658b1a0
MT
355 serial = _fireinfo.get_harddisk_serial("/dev/%s" % self.root_disk)
356
357 if serial:
358 # Strip all spaces
359 return serial.strip()
360
b45f0e98 361 def scan(self):
e8221cde
MT
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 )
b45f0e98 372 for path, cls in toscan:
94700953
MT
373 if not os.path.exists(path):
374 continue
375
b45f0e98
MT
376 dirlist = os.listdir(path)
377 for dir in dirlist:
378 self.devices.append(cls(os.path.join(path, dir)))
d8614fc3
MT
379
380 @property
381 def virtual(self):
382 """
383 Say if the host is running in a virtual environment.
384 """
715ba5ac 385 return self.hypervisor.virtual
b45f0e98 386
4cb61965
MT
387 @property
388 def network(self):
389 """
390 Reference to the network class.
391 """
392 return network.Network()
393
b45f0e98
MT
394
395if __name__ == "__main__":
396 s=System()
397 print s.arch
398 print s.language
399 print s.release
b56bde73 400 print s.bios_vendor
b45f0e98
MT
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"
65891720 406 print json.dumps(s.profile(), sort_keys=True, indent=4)