1 ###############################################################################
4 # Copyright (C) 2010, 2011 IPFire Team (www.ipfire.org) #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 ###############################################################################
26 from . import _fireinfo
30 from . import hypervisor
35 SYS_CLASS_DMI
= "/sys/class/dmi/id"
36 SECRET_ID_FILE
= "/etc/fireinfo-id"
38 INVALID_ID_STRINGS
= (
39 "OEM", "O.E.M.", "o.e.m.",
41 "12345", "54321", "202020",
47 "Serial", "System Serial Number",
49 "01010101-0101-0101-0101-010101010101",
50 "00020003-0004-0005-0006-000700080009",
51 "03000200-0400-0500-0006-000700080009",
52 "11111111-1111-1111-1111-111111111111",
53 "0000000", "00000000",
56 INVALID_ID_STRINGS_EXACT_MATCH
= (
60 class Singleton(type):
61 def __init__(cls
, name
, bases
, dict):
62 super(Singleton
, cls
).__init
__(name
, bases
, dict)
65 def __call__(cls
, *args
, **kw
):
66 if cls
.instance
is None:
67 cls
.instance
= super(Singleton
, cls
).__call
__(*args
, **kw
)
72 def read_from_file(filename
):
74 Read all data from filename.
76 if not os
.path
.exists(filename
):
80 with
open(filename
) as f
:
81 return f
.read().strip()
85 class System(object, metaclass
=Singleton
):
87 self
.bios
= bios
.BIOS(self
)
93 self
.hypervisor
= hypervisor
.Hypervisor()
95 # Read /proc/cpuinfo for vendor information.
96 self
.__cpuinfo
= self
.cpu
.read_cpuinfo()
102 "model" : self
.model
,
103 "vendor" : self
.vendor
,
105 # Indicator if the system is running in a
106 # virtual environment.
107 "virtual" : self
.virtual
,
110 "language" : self
.language
,
112 # Release information
113 "release" : self
.release
,
114 "kernel_release" : self
.kernel_release
,
116 "memory" : self
.memory
,
117 "root_size" : self
.root_size
,
121 for device
in self
.devices
:
123 "subsystem" : device
.subsystem
.lower(),
124 "vendor" : device
.vendor
.lower(),
125 "model" : device
.model
.lower(),
126 "deviceclass" : device
.deviceclass
,
127 "driver" : device
.driver
,
130 # PCI devices provide subsystem information, USB don't.
131 if d
["subsystem"] == "pci":
132 d
["sub_model"] = device
.sub_model
133 d
["sub_vendor"] = device
.sub_vendor
135 p
["devices"].append(d
)
139 "vendor" : self
.cpu
.vendor
,
140 "model" : self
.cpu
.model
,
141 "model_string" : self
.cpu
.model_string
,
142 "stepping" : self
.cpu
.stepping
,
143 "flags" : self
.cpu
.flags
,
144 "speed" : self
.cpu
.speed
,
145 "family" : self
.cpu
.family
,
146 "count" : self
.cpu
.count
149 if self
.cpu
.bogomips
:
150 p
["bogomips"] = self
.cpu
.bogomips
153 "green" : self
.network
.has_green(),
154 "blue" : self
.network
.has_blue(),
155 "orange" : self
.network
.has_orange(),
156 "red" : self
.network
.has_red(),
159 # Only append hypervisor information if we are virtualized.
162 "vendor" : self
.hypervisor
.vendor
,
167 "profile_version" : PROFILE_VERSION
,
169 # Identification and authorization codes
170 "public_id" : self
.public_id
,
171 "private_id" : self
.private_id
,
173 # Actual profile data
185 This returns a globally (hopefully) ID to identify the host
186 later (by request) in the database.
188 public_id
= self
.secret_id
192 h
= hashlib
.sha1(public_id
.encode())
196 def private_id(self
):
198 The private ID is built out of the _unique_id and used to
199 permit a host to do changes on the database.
201 No one could ever guess this without access to the host.
204 for i
in reversed(self
.secret_id
):
210 h
= hashlib
.sha1(private_id
.encode())
216 Read a "secret" ID from a file if available
217 or calculate it from the hardware.
219 if os
.path
.exists(SECRET_ID_FILE
):
220 return read_from_file(SECRET_ID_FILE
)
222 h
= hashlib
.sha1(self
._unique
_id
.encode())
226 def _unique_id(self
):
228 This is a helper ID which is generated out of some hardware information
229 that is considered to be constant over a PC's lifetime.
231 None of the data here is ever sent to the server.
235 # Virtual machines (for example) and some boards have a UUID
236 # which is globally unique.
237 for file in ("product_uuid", "product_serial", "chassis_serial"):
238 id = read_from_file(os
.path
.join(SYS_CLASS_DMI
, file))
241 # Sort out all bogous or invalid strings from the list.
247 for i
in INVALID_ID_STRINGS_EXACT_MATCH
:
253 for i
in INVALID_ID_STRINGS
:
258 # Check if the string only contains 0xff
259 if id and all((e
== "\xff" for e
in id)):
267 # Use serial number from root disk (if available) and if
268 # no other ID was found, yet.
270 root_disk_serial
= self
.root_disk_serial
271 if root_disk_serial
and not root_disk_serial
.startswith("QM000"):
272 ids
.append(root_disk_serial
)
274 # As last resort, we use the UUID from pakfire.
276 id = read_from_file("/opt/pakfire/db/uuid")
284 Return the language code of IPFire or "unknown" if we cannot get it.
286 # Return "unknown" if settings file does not exist.
287 filename
= "/var/ipfire/main/settings"
288 if not os
.path
.exists(filename
):
291 with
open(filename
, "r") as f
:
292 for line
in f
.readlines():
293 key
, val
= line
.split("=", 1)
294 if key
== "LANGUAGE":
300 Return the system release string.
302 return read_from_file("/etc/system-release") or "unknown"
305 def bios_vendor(self
):
307 Return the bios vendor name.
309 return read_from_file("/sys/class/dmi/id/bios_vendor")
311 def vendor_model_tuple(self
):
313 s
= self
.__cpuinfo
["Hardware"]
317 if s
.startswith("ARM-Versatile"):
321 v
, m
= s
.split(" ", 1)
323 if s
.startswith("BCM"):
333 def escape_string(s
):
335 Will remove all non-printable characters from the given string
340 return "".join([x
for x
in s
if x
in string
.printable
])
345 Return the vendor string of this system (if any).
348 for file in ("sys_vendor", "board_vendor", "chassis_vendor",):
349 ret
= read_from_file(os
.path
.join(SYS_CLASS_DMI
, file))
351 return self
.escape_string(ret
)
353 if os
.path
.exists("/proc/device-tree"):
354 ret
= self
.__cpuinfo
.get("Hardware", None)
356 ret
, m
= self
.vendor_model_tuple()
358 return self
.escape_string(ret
)
363 Return the model string of this system (if any).
366 for file in ("product_name", "board_model", "chassis_model",):
367 ret
= read_from_file(os
.path
.join(SYS_CLASS_DMI
, file))
369 return self
.escape_string(ret
)
371 # Read device-tree model if available
372 ret
= read_from_file("/proc/device-tree/model")
374 # replace the NULL byte with which the DT string ends
375 ret
= ret
.replace("\u0000", "")
377 # Fall back to read /proc/cpuinfo
379 v
, ret
= self
.vendor_model_tuple()
381 return self
.escape_string(ret
)
386 Return the amount of memory in kilobytes.
388 with
open("/proc/meminfo", "r") as f
:
389 firstline
= f
.readline().strip()
390 return int(firstline
.split()[1])
393 def kernel_release(self
):
395 Return the kernel release string.
402 Return the dev node of the root disk.
404 with
open("/proc/mounts", "r") as f
:
405 for line
in f
.readlines():
410 dev
, mountpoint
, fs
, rest
= line
.split(" ", 3)
411 if mountpoint
== "/" and not fs
== "rootfs":
415 # Handle raids and MMC cards like (mmcblk0p3).
419 # Otherwise cut off all digits at end of string
420 while dev
[-1] in string
.digits
:
428 Return the size of the root disk in kilobytes.
430 path
= "/sys/block/%s/size" % self
.root_disk
431 if not os
.path
.exists(path
):
434 with
open(path
, "r") as f
:
435 return int(f
.readline()) * 512 / 1024
438 def root_disk_serial(self
):
440 Return the serial number of the root disk (if any).
443 serial
= _fireinfo
.get_harddisk_serial("/dev/%s" % self
.root_disk
)
449 return serial
.strip()
453 Scan for all devices (PCI/USB) in the system and append them
459 ("/sys/bus/pci/devices", device
.PCIDevice
),
460 ("/sys/bus/usb/devices", device
.USBDevice
)
462 for path
, cls
in toscan
:
463 if not os
.path
.exists(path
):
466 dirlist
= os
.listdir(path
)
468 self
.devices
.append(cls(os
.path
.join(path
, dir)))
473 Say if the host is running in a virtual environment.
475 return self
.hypervisor
.virtual
480 Reference to the network class.
482 return network
.Network()
485 if __name__
== "__main__":
495 print("------------\n", s
.devices
, "\n------------\n")
496 print(json
.dumps(s
.profile(), sort_keys
=True, indent
=4))