summaryrefslogtreecommitdiff
path: root/cloudinit/dmi.py
diff options
context:
space:
mode:
authorScott Moser <smoser@brickies.net>2020-11-02 15:41:11 -0500
committerGitHub <noreply@github.com>2020-11-02 15:41:11 -0500
commit0af1ff1eaf593c325b4f53181a572110eb016c50 (patch)
tree2e0dab084dffd4664a6320f15baf374aff008917 /cloudinit/dmi.py
parentd619f5171ac0ce5b626ef4575ad5f4468e94c987 (diff)
downloadcloud-init-git-0af1ff1eaf593c325b4f53181a572110eb016c50.tar.gz
cloudinit: move dmi functions out of util (#622)
This just separates the reading of dmi values into its own file. Some things of note: * left import of util in dmi.py only for 'is_container' It'd be good if is_container was not in util. * just the use of 'util.is_x86' to dmi.py * open() is used directly rather than load_file.
Diffstat (limited to 'cloudinit/dmi.py')
-rw-r--r--cloudinit/dmi.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/cloudinit/dmi.py b/cloudinit/dmi.py
new file mode 100644
index 00000000..96e0e423
--- /dev/null
+++ b/cloudinit/dmi.py
@@ -0,0 +1,131 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+from cloudinit import log as logging
+from cloudinit import subp
+from cloudinit.util import is_container
+
+import os
+
+LOG = logging.getLogger(__name__)
+
+# Path for DMI Data
+DMI_SYS_PATH = "/sys/class/dmi/id"
+
+# dmidecode and /sys/class/dmi/id/* use different names for the same value,
+# this allows us to refer to them by one canonical name
+DMIDECODE_TO_DMI_SYS_MAPPING = {
+ 'baseboard-asset-tag': 'board_asset_tag',
+ 'baseboard-manufacturer': 'board_vendor',
+ 'baseboard-product-name': 'board_name',
+ 'baseboard-serial-number': 'board_serial',
+ 'baseboard-version': 'board_version',
+ 'bios-release-date': 'bios_date',
+ 'bios-vendor': 'bios_vendor',
+ 'bios-version': 'bios_version',
+ 'chassis-asset-tag': 'chassis_asset_tag',
+ 'chassis-manufacturer': 'chassis_vendor',
+ 'chassis-serial-number': 'chassis_serial',
+ 'chassis-version': 'chassis_version',
+ 'system-manufacturer': 'sys_vendor',
+ 'system-product-name': 'product_name',
+ 'system-serial-number': 'product_serial',
+ 'system-uuid': 'product_uuid',
+ 'system-version': 'product_version',
+}
+
+
+def _read_dmi_syspath(key):
+ """
+ Reads dmi data with from /sys/class/dmi/id
+ """
+ if key not in DMIDECODE_TO_DMI_SYS_MAPPING:
+ return None
+ mapped_key = DMIDECODE_TO_DMI_SYS_MAPPING[key]
+ dmi_key_path = "{0}/{1}".format(DMI_SYS_PATH, mapped_key)
+
+ 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 _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 (IOError, OSError) 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
+
+ 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.warning("did not find either path %s or dmidecode command",
+ DMI_SYS_PATH)
+ return None
+
+# vi: ts=4 expandtab