]>
Commit | Line | Data |
---|---|---|
b45f0e98 MT |
1 | #!/usr/bin/python |
2 | ||
31a30328 | 3 | import hashlib |
b45f0e98 MT |
4 | import json |
5 | import os | |
6 | import string | |
7 | ||
3f70e7fd MT |
8 | import _fireinfo |
9 | ||
b45f0e98 MT |
10 | import cpu |
11 | import device | |
715ba5ac | 12 | import hypervisor |
b45f0e98 | 13 | |
0c5ef738 MT |
14 | PROFILE_VERSION = 0 |
15 | ||
45b74ad5 | 16 | SYS_CLASS_DMI = "/sys/class/dmi/id" |
73014efe | 17 | SECRET_ID_FILE = "/etc/fireinfo-id" |
45b74ad5 | 18 | |
7eadbfba MT |
19 | INVALID_ID_STRINGS = ( |
20 | "OEM", "O.E.M.", "o.e.m.", | |
21 | "N/A", "n/a", | |
74ce4c8d | 22 | "12345", "54321", "202020", |
ed491340 | 23 | "Chassis", "chassis", |
7eadbfba MT |
24 | ) |
25 | ||
32aeec73 MT |
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 | ||
45b74ad5 MT |
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 | ||
b45f0e98 | 51 | class System(object): |
32aeec73 | 52 | __metaclass__ = Singleton |
b45f0e98 MT |
53 | |
54 | def __init__(self): | |
55 | # find all devices | |
56 | self.devices = [] | |
57 | self.scan() | |
58 | self.cpu = cpu.CPU() | |
715ba5ac MT |
59 | self.hypervisor = hypervisor.Hypervisor() |
60 | ||
b45f0e98 | 61 | def profile(self): |
0c5ef738 MT |
62 | p = {} |
63 | p["system"] = { | |
45b74ad5 MT |
64 | # System information |
65 | "model" : self.model, | |
66 | "vendor" : self.vendor, | |
67 | ||
0c5ef738 MT |
68 | # Indicator if the system is running in a |
69 | # virtual environment. | |
d8614fc3 | 70 | "virtual" : self.virtual, |
b45f0e98 | 71 | |
0c5ef738 MT |
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"] = [] | |
b45f0e98 | 84 | for device in self.devices: |
97679775 | 85 | d = { |
b45f0e98 MT |
86 | "subsystem" : device.subsystem.lower(), |
87 | "vendor" : device.vendor.lower(), | |
88 | "model" : device.model.lower(), | |
7923840b MT |
89 | "deviceclass" : device.deviceclass, |
90 | "driver" : device.driver, | |
97679775 MT |
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 | ||
b45f0e98 | 100 | p["cpu"] = { |
0c5ef738 | 101 | "arch" : self.arch, |
b45f0e98 MT |
102 | "vendor" : self.cpu.vendor, |
103 | "model" : self.cpu.model, | |
eecf2eea | 104 | "model_string" : self.cpu.model_string, |
b45f0e98 MT |
105 | "stepping" : self.cpu.stepping, |
106 | "flags" : self.cpu.flags, | |
107 | "bogomips" : self.cpu.bogomips, | |
108 | "speed" : self.cpu.speed, | |
b45f0e98 MT |
109 | "family" : self.cpu.family, |
110 | "count" : self.cpu.count | |
111 | } | |
715ba5ac MT |
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 | ||
0c5ef738 MT |
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 | ||
1beb8e1c MT |
128 | # XXX just for beta |
129 | "secret_id" : self._unique_id, | |
130 | ||
0c5ef738 MT |
131 | # Actual profile data |
132 | "profile" : p, | |
133 | } | |
b45f0e98 MT |
134 | |
135 | ||
136 | @property | |
137 | def arch(self): | |
138 | return os.uname()[4] | |
139 | ||
140 | @property | |
141 | def public_id(self): | |
31a30328 MT |
142 | """ |
143 | This returns a globally (hopefully) ID to identify the host | |
144 | later (by request) in the database. | |
145 | """ | |
73014efe | 146 | public_id = self.secret_id |
3d10b6d9 MT |
147 | if not public_id: |
148 | return "0" * 40 | |
149 | ||
150 | return hashlib.sha1(public_id).hexdigest() | |
151 | ||
b45f0e98 MT |
152 | @property |
153 | def private_id(self): | |
31a30328 MT |
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 | """ | |
5e2ba24e | 160 | private_id = "" |
73014efe | 161 | for i in reversed(self.secret_id): |
5e2ba24e MT |
162 | private_id += i |
163 | ||
3d10b6d9 MT |
164 | if not private_id: |
165 | return "0" * 40 | |
166 | ||
5e2ba24e | 167 | return hashlib.sha1(private_id).hexdigest() |
31a30328 | 168 | |
73014efe MT |
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 | ||
31a30328 MT |
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 | """ | |
ec65e266 | 188 | ids = [] |
3d10b6d9 MT |
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"): | |
7eadbfba | 193 | id = read_from_file(os.path.join(SYS_CLASS_DMI, file)) |
c0ef2eb7 | 194 | ids.append(id) |
3d10b6d9 | 195 | |
7eadbfba MT |
196 | # Sort out all bogous or invalid strings from the list. |
197 | _ids = [] | |
198 | for id in ids: | |
88b0ded5 MT |
199 | if id is None: |
200 | continue | |
201 | ||
7eadbfba | 202 | for i in INVALID_ID_STRINGS: |
c0ef2eb7 MT |
203 | if i in id: |
204 | id = None | |
7eadbfba MT |
205 | break |
206 | ||
207 | if id: | |
208 | _ids.append(id) | |
209 | ||
210 | ids = _ids | |
211 | ||
17dc2486 MT |
212 | # Use serial number from root disk (if available) and if |
213 | # no other ID was found, yet. | |
214 | if not ids: | |
215 | root_disk_serial = self.root_disk_serial | |
4ea06541 | 216 | if root_disk_serial and not root_disk_serial.startswith("QM000"): |
17dc2486 | 217 | ids.append(root_disk_serial) |
121f9f20 | 218 | |
f05eac0d MT |
219 | # As last resort, we use the UUID from pakfire. |
220 | if not ids: | |
221 | id = read_from_file("/opt/pakfire/db/uuid") | |
222 | ids.append(id) | |
223 | ||
ec65e266 | 224 | return "#".join(ids) |
31a30328 | 225 | |
b45f0e98 MT |
226 | @property |
227 | def language(self): | |
b3ea53a7 MT |
228 | # Return "unknown" if settings file does not exist. |
229 | filename = "/var/ipfire/main/settings" | |
230 | if not os.path.exists(filename): | |
231 | return "unknown" | |
232 | ||
233 | with open(filename, "r") as f: | |
b45f0e98 MT |
234 | for line in f.readlines(): |
235 | key, val = line.split("=", 1) | |
236 | if key=="LANGUAGE": | |
237 | return val.strip() | |
238 | ||
239 | @property | |
240 | def release(self): | |
770c8841 | 241 | return read_from_file("/etc/system-release") |
b56bde73 SP |
242 | |
243 | @property | |
244 | def bios_vendor(self): | |
770c8841 | 245 | return read_from_file("/sys/class/dmi/id/bios_vendor") |
b56bde73 | 246 | |
45b74ad5 MT |
247 | @property |
248 | def vendor(self): | |
249 | ret = None | |
240fefea | 250 | for file in ("sys_vendor", "board_vendor", "chassis_vendor",): |
45b74ad5 MT |
251 | ret = read_from_file(os.path.join(SYS_CLASS_DMI, file)) |
252 | if ret: | |
253 | break | |
254 | ||
255 | return ret | |
256 | ||
257 | @property | |
258 | def model(self): | |
259 | ret = None | |
84efbcb4 | 260 | for file in ("product_name", "board_model", "chassis_model",): |
45b74ad5 MT |
261 | ret = read_from_file(os.path.join(SYS_CLASS_DMI, file)) |
262 | if ret: | |
263 | break | |
264 | ||
265 | return ret | |
266 | ||
b45f0e98 MT |
267 | @property |
268 | def memory(self): | |
269 | with open("/proc/meminfo", "r") as f: | |
270 | firstline = f.readline().strip() | |
2ced5413 | 271 | return int(firstline.split()[1]) |
b45f0e98 MT |
272 | |
273 | @property | |
0c5ef738 | 274 | def kernel_release(self): |
b45f0e98 MT |
275 | return os.uname()[2] |
276 | ||
277 | @property | |
278 | def root_disk(self): | |
279 | with open("/etc/mtab", "r") as f: | |
280 | dev, mountpoint, rest = f.readline().split(" ",2) | |
281 | if mountpoint == "/": | |
282 | dev = dev[5:] | |
283 | # cut off all digits at end of string | |
284 | while dev[-1] in string.digits: | |
285 | dev = dev[:-1] | |
286 | return dev | |
287 | ||
288 | @property | |
289 | def root_size(self): | |
290 | path="/sys/block/%s/size" %self.root_disk | |
291 | if not os.path.exists(path): | |
292 | return | |
293 | with open(path, "r") as f: | |
294 | return int(f.readline())*512/1024 | |
3f70e7fd MT |
295 | |
296 | @property | |
297 | def root_disk_serial(self): | |
e658b1a0 MT |
298 | serial = _fireinfo.get_harddisk_serial("/dev/%s" % self.root_disk) |
299 | ||
300 | if serial: | |
301 | # Strip all spaces | |
302 | return serial.strip() | |
303 | ||
b45f0e98 MT |
304 | def scan(self): |
305 | toscan = (("/sys/bus/pci/devices", device.PCIDevice), | |
306 | ("/sys/bus/usb/devices", device.USBDevice)) | |
307 | for path, cls in toscan: | |
94700953 MT |
308 | if not os.path.exists(path): |
309 | continue | |
310 | ||
b45f0e98 MT |
311 | dirlist = os.listdir(path) |
312 | for dir in dirlist: | |
313 | self.devices.append(cls(os.path.join(path, dir))) | |
d8614fc3 MT |
314 | |
315 | @property | |
316 | def virtual(self): | |
317 | """ | |
318 | Say if the host is running in a virtual environment. | |
319 | """ | |
715ba5ac | 320 | return self.hypervisor.virtual |
b45f0e98 MT |
321 | |
322 | ||
323 | ||
324 | if __name__ == "__main__": | |
325 | s=System() | |
326 | print s.arch | |
327 | print s.language | |
328 | print s.release | |
b56bde73 | 329 | print s.bios_vendor |
b45f0e98 MT |
330 | print s.memory |
331 | print s.kernel | |
332 | print s.root_disk | |
333 | print s.root_size | |
334 | print "------------\n", s.devices, "\n------------\n" | |
65891720 | 335 | print json.dumps(s.profile(), sort_keys=True, indent=4) |