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