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