]>
Commit | Line | Data |
---|---|---|
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 | 22 | import hashlib |
b45f0e98 MT |
23 | import json |
24 | import os | |
25 | import string | |
26 | ||
3f70e7fd MT |
27 | import _fireinfo |
28 | ||
b45f0e98 MT |
29 | import cpu |
30 | import device | |
715ba5ac | 31 | import hypervisor |
4cb61965 | 32 | import network |
b45f0e98 | 33 | |
0c5ef738 MT |
34 | PROFILE_VERSION = 0 |
35 | ||
45b74ad5 | 36 | SYS_CLASS_DMI = "/sys/class/dmi/id" |
73014efe | 37 | SECRET_ID_FILE = "/etc/fireinfo-id" |
45b74ad5 | 38 | |
7eadbfba MT |
39 | INVALID_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 |
51 | class 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 |
63 | def 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 | 76 | class 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 | |
400 | if __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) |