Current File : //usr/lib/python3.6/site-packages/cloudinit/dmi.py |
# This file is part of cloud-init. See LICENSE file for license information.
import os
from collections import namedtuple
from cloudinit import log as logging
from cloudinit import subp
from cloudinit.util import is_container, is_FreeBSD
LOG = logging.getLogger(__name__)
# Path for DMI Data
DMI_SYS_PATH = "/sys/class/dmi/id"
kdmi = namedtuple("KernelNames", ["linux", "freebsd"])
kdmi.__new__.defaults__ = (None, None)
# FreeBSD's kenv(1) and Linux /sys/class/dmi/id/* both use different names from
# dmidecode. The values are the same, and ultimately what we're interested in.
# These tools offer a "cheaper" way to access those values over dmidecode.
# This is our canonical translation table. If we add more tools on other
# platforms to find dmidecode's values, their keys need to be put in here.
DMIDECODE_TO_KERNEL = {
"baseboard-asset-tag": kdmi("board_asset_tag", "smbios.planar.tag"),
"baseboard-manufacturer": kdmi("board_vendor", "smbios.planar.maker"),
"baseboard-product-name": kdmi("board_name", "smbios.planar.product"),
"baseboard-serial-number": kdmi("board_serial", "smbios.planar.serial"),
"baseboard-version": kdmi("board_version", "smbios.planar.version"),
"bios-release-date": kdmi("bios_date", "smbios.bios.reldate"),
"bios-vendor": kdmi("bios_vendor", "smbios.bios.vendor"),
"bios-version": kdmi("bios_version", "smbios.bios.version"),
"chassis-asset-tag": kdmi("chassis_asset_tag", "smbios.chassis.tag"),
"chassis-manufacturer": kdmi("chassis_vendor", "smbios.chassis.maker"),
"chassis-serial-number": kdmi("chassis_serial", "smbios.chassis.serial"),
"chassis-version": kdmi("chassis_version", "smbios.chassis.version"),
"system-manufacturer": kdmi("sys_vendor", "smbios.system.maker"),
"system-product-name": kdmi("product_name", "smbios.system.product"),
"system-serial-number": kdmi("product_serial", "smbios.system.serial"),
"system-uuid": kdmi("product_uuid", "smbios.system.uuid"),
"system-version": kdmi("product_version", "smbios.system.version"),
}
def _read_dmi_syspath(key):
"""
Reads dmi data from /sys/class/dmi/id
"""
kmap = DMIDECODE_TO_KERNEL.get(key)
if kmap is None or kmap.linux is None:
return None
dmi_key_path = "{0}/{1}".format(DMI_SYS_PATH, kmap.linux)
LOG.debug("querying dmi data %s", dmi_key_path)
if not os.path.exists(dmi_key_path):
LOG.debug("did not find %s", dmi_key_path)
return None
try:
with open(dmi_key_path, "rb") as fp:
key_data = fp.read()
except PermissionError:
LOG.debug("Could not read %s", dmi_key_path)
return None
# uninitialized dmi values show as all \xff and /sys appends a '\n'.
# in that event, return empty string.
if key_data == b"\xff" * (len(key_data) - 1) + b"\n":
key_data = b""
try:
return key_data.decode("utf8").strip()
except UnicodeDecodeError as e:
LOG.error(
"utf-8 decode of content (%s) in %s failed: %s",
dmi_key_path,
key_data,
e,
)
return None
def _read_kenv(key):
"""
Reads dmi data from FreeBSD's kenv(1)
"""
kmap = DMIDECODE_TO_KERNEL.get(key)
if kmap is None or kmap.freebsd is None:
return None
LOG.debug("querying dmi data %s", kmap.freebsd)
try:
cmd = ["kenv", "-q", kmap.freebsd]
(result, _err) = subp.subp(cmd)
result = result.strip()
LOG.debug("kenv returned '%s' for '%s'", result, kmap.freebsd)
return result
except subp.ProcessExecutionError as e:
LOG.debug("failed kenv cmd: %s\n%s", cmd, e)
return None
return None
def _call_dmidecode(key, dmidecode_path):
"""
Calls out to dmidecode to get the data out. This is mostly for supporting
OS's without /sys/class/dmi/id support.
"""
try:
cmd = [dmidecode_path, "--string", key]
(result, _err) = subp.subp(cmd)
result = result.strip()
LOG.debug("dmidecode returned '%s' for '%s'", result, key)
if result.replace(".", "") == "":
return ""
return result
except subp.ProcessExecutionError as e:
LOG.debug("failed dmidecode cmd: %s\n%s", cmd, e)
return None
def read_dmi_data(key):
"""
Wrapper for reading DMI data.
If running in a container return None. This is because DMI data is
assumed to be not useful in a container as it does not represent the
container but rather the host.
This will do the following (returning the first that produces a
result):
1) Use a mapping to translate `key` from dmidecode naming to
sysfs naming and look in /sys/class/dmi/... for a value.
2) Use `key` as a sysfs key directly and look in /sys/class/dmi/...
3) Fall-back to passing `key` to `dmidecode --string`.
If all of the above fail to find a value, None will be returned.
"""
if is_container():
return None
if is_FreeBSD():
return _read_kenv(key)
syspath_value = _read_dmi_syspath(key)
if syspath_value is not None:
return syspath_value
def is_x86(arch):
return arch == "x86_64" or (arch[0] == "i" and arch[2:] == "86")
# running dmidecode can be problematic on some arches (LP: #1243287)
uname_arch = os.uname()[4]
if not (is_x86(uname_arch) or uname_arch in ("aarch64", "amd64")):
LOG.debug("dmidata is not supported on %s", uname_arch)
return None
dmidecode_path = subp.which("dmidecode")
if dmidecode_path:
return _call_dmidecode(key, dmidecode_path)
LOG.debug("did not find either path %s or dmidecode command", DMI_SYS_PATH)
return None
# vi: ts=4 expandtab