fwupd/libfwupdplugin/fu-fdt-image.c

663 lines
17 KiB
C

/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuFirmware"
#include "config.h"
#include "fu-byte-array.h"
#include "fu-common.h"
#include "fu-fdt-image.h"
#include "fu-mem.h"
#include "fu-string.h"
/**
* FuFdtImage:
*
* A Flattened DeviceTree firmware image.
*
* See also: [class@FuFdtFirmware]
*/
typedef struct {
GHashTable *hash_attrs;
GHashTable *hash_attrs_format;
} FuFdtImagePrivate;
G_DEFINE_TYPE_WITH_PRIVATE(FuFdtImage, fu_fdt_image, FU_TYPE_FIRMWARE)
#define GET_PRIVATE(o) (fu_fdt_image_get_instance_private(o))
#define FU_FDT_IMAGE_FORMAT_STR "str"
#define FU_FDT_IMAGE_FORMAT_STRLIST "strlist"
#define FU_FDT_IMAGE_FORMAT_UINT32 "uint32"
#define FU_FDT_IMAGE_FORMAT_UINT64 "uint64"
#define FU_FDT_IMAGE_FORMAT_DATA "data"
static const gchar *
fu_fdt_image_guess_format_from_key(const gchar *key)
{
struct {
const gchar *key;
const gchar *format;
} key_format_map[] = {{"#address-cells", FU_FDT_IMAGE_FORMAT_UINT32},
{"algo", FU_FDT_IMAGE_FORMAT_STR},
{"arch", FU_FDT_IMAGE_FORMAT_STR},
{"compatible", FU_FDT_IMAGE_FORMAT_STRLIST},
{"compression", FU_FDT_IMAGE_FORMAT_STR},
{"creator", FU_FDT_IMAGE_FORMAT_STR},
{"data-offset", FU_FDT_IMAGE_FORMAT_UINT32},
{"data-size", FU_FDT_IMAGE_FORMAT_UINT32},
{"default", FU_FDT_IMAGE_FORMAT_STR},
{"description", FU_FDT_IMAGE_FORMAT_STR},
{"entry", FU_FDT_IMAGE_FORMAT_STR},
{"firmware", FU_FDT_IMAGE_FORMAT_STR},
{"load", FU_FDT_IMAGE_FORMAT_UINT32},
{"os", FU_FDT_IMAGE_FORMAT_STR},
{"timestamp", FU_FDT_IMAGE_FORMAT_UINT32},
{"type", FU_FDT_IMAGE_FORMAT_STR},
{"version", FU_FDT_IMAGE_FORMAT_STR},
{NULL, NULL}};
for (guint i = 0; key_format_map[i].key != NULL; i++) {
if (g_strcmp0(key, key_format_map[i].key) == 0)
return key_format_map[i].format;
}
return NULL;
}
static gchar **
fu_fdt_image_strlist_from_blob(GBytes *blob)
{
gchar **val;
gsize bufsz = 0;
const guint8 *buf = g_bytes_get_data(blob, &bufsz);
g_autoptr(GPtrArray) strs = g_ptr_array_new();
/* delimit by NUL */
for (gsize i = 0; i < bufsz; i++) {
const gchar *tmp = (const gchar *)buf + i;
g_ptr_array_add(strs, (gpointer)tmp);
i += strlen(tmp);
}
/* copy to GStrv */
val = g_new0(gchar *, strs->len + 1);
for (guint i = 0; i < strs->len; i++)
val[i] = g_strdup(g_ptr_array_index(strs, i));
return val;
}
static void
fu_fdt_image_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
{
FuFdtImage *self = FU_FDT_IMAGE(firmware);
FuFdtImagePrivate *priv = GET_PRIVATE(self);
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, priv->hash_attrs);
while (g_hash_table_iter_next(&iter, &key, &value)) {
gsize bufsz = 0;
const guint8 *buf = g_bytes_get_data(value, &bufsz);
const gchar *format = g_hash_table_lookup(priv->hash_attrs_format, key);
g_autofree gchar *str = NULL;
g_autoptr(XbBuilderNode) bc = NULL;
/* guess format based on key name to improve debugging experience */
if (format == NULL)
format = fu_fdt_image_guess_format_from_key(key);
if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT32) == 0 && bufsz == 4) {
guint64 tmp = fu_memread_uint32(buf, G_BIG_ENDIAN);
str = g_strdup_printf("0x%x", (guint)tmp);
} else if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT64) == 0 && bufsz == 8) {
guint64 tmp = fu_memread_uint64(buf, G_BIG_ENDIAN);
str = g_strdup_printf("0x%x", (guint)tmp);
} else if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STR) == 0 && bufsz > 0) {
str = g_strndup((const gchar *)buf, bufsz);
} else if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STRLIST) == 0 && bufsz > 0) {
g_auto(GStrv) tmp = fu_fdt_image_strlist_from_blob(value);
str = g_strjoinv(":", tmp);
} else {
str = g_base64_encode(buf, bufsz);
}
bc = xb_builder_node_insert(bn, "metadata", "key", key, NULL);
if (str != NULL)
xb_builder_node_set_text(bc, str, -1);
if (format != NULL)
xb_builder_node_set_attr(bc, "format", format);
}
}
/**
* fu_fdt_image_get_attrs:
* @self: a #FuFdtImage
*
* Gets all the attributes stored on the image.
*
* Returns: (transfer container) (element-type utf8): keys
*
* Since: 1.8.2
**/
GPtrArray *
fu_fdt_image_get_attrs(FuFdtImage *self)
{
FuFdtImagePrivate *priv = GET_PRIVATE(self);
GPtrArray *array = g_ptr_array_new_with_free_func(g_free);
g_autoptr(GList) keys = NULL;
g_return_val_if_fail(FU_IS_FDT_IMAGE(self), NULL);
keys = g_hash_table_get_keys(priv->hash_attrs);
for (GList *l = keys; l != NULL; l = l->next) {
const gchar *key = l->data;
g_ptr_array_add(array, g_strdup(key));
}
return array;
}
/**
* fu_fdt_image_get_attr:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @error: (nullable): optional return location for an error
*
* Gets a attribute from the image.
*
* Returns: (transfer full): blob
*
* Since: 1.8.2
**/
GBytes *
fu_fdt_image_get_attr(FuFdtImage *self, const gchar *key, GError **error)
{
FuFdtImagePrivate *priv = GET_PRIVATE(self);
GBytes *blob;
g_return_val_if_fail(FU_IS_FDT_IMAGE(self), NULL);
g_return_val_if_fail(key != NULL, NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
blob = g_hash_table_lookup(priv->hash_attrs, key);
if (blob == NULL) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no data for %s", key);
return NULL;
}
/* success */
return g_bytes_ref(blob);
}
/**
* fu_fdt_image_get_attr_u32:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @val: (out) (nullable): value
* @error: (nullable): optional return location for an error
*
* Gets a uint32 attribute from the image.
*
* Returns: %TRUE if @val was set.
*
* Since: 1.8.2
**/
gboolean
fu_fdt_image_get_attr_u32(FuFdtImage *self, const gchar *key, guint32 *val, GError **error)
{
g_autoptr(GBytes) blob = NULL;
g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
blob = fu_fdt_image_get_attr(self, key, error);
if (blob == NULL)
return FALSE;
if (g_bytes_get_size(blob) != sizeof(guint32)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid data size for %s, got 0x%x, expected 0x%x",
key,
(guint)g_bytes_get_size(blob),
(guint)sizeof(guint32));
return FALSE;
}
if (val != NULL)
*val = fu_memread_uint32(g_bytes_get_data(blob, NULL), G_BIG_ENDIAN);
return TRUE;
}
/**
* fu_fdt_image_get_attr_u64:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @val: (out) (nullable): value
* @error: (nullable): optional return location for an error
*
* Gets a uint64 attribute from the image.
*
* Returns: %TRUE if @val was set.
*
* Since: 1.8.2
**/
gboolean
fu_fdt_image_get_attr_u64(FuFdtImage *self, const gchar *key, guint64 *val, GError **error)
{
g_autoptr(GBytes) blob = NULL;
g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
blob = fu_fdt_image_get_attr(self, key, error);
if (blob == NULL)
return FALSE;
if (g_bytes_get_size(blob) != sizeof(guint64)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid data size for %s, got 0x%x, expected 0x%x",
key,
(guint)g_bytes_get_size(blob),
(guint)sizeof(guint64));
return FALSE;
}
if (val != NULL)
*val = fu_memread_uint64(g_bytes_get_data(blob, NULL), G_BIG_ENDIAN);
return TRUE;
}
/**
* fu_fdt_image_get_attr_strlist:
* @self: a #FuFdtImage
* @key: string, e.g. `compatible`
* @val: (out) (nullable) (transfer full): values
* @error: (nullable): optional return location for an error
*
* Gets a stringlist attribute from the image. @val is always `NUL` terminated.
*
* Returns: %TRUE if @val was set.
*
* Since: 1.8.2
**/
gboolean
fu_fdt_image_get_attr_strlist(FuFdtImage *self, const gchar *key, gchar ***val, GError **error)
{
g_autoptr(GBytes) blob = NULL;
const guint8 *buf;
gsize bufsz = 0;
g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
blob = fu_fdt_image_get_attr(self, key, error);
if (blob == NULL)
return FALSE;
if (g_bytes_get_size(blob) == 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid data size for %s, got 0x%x",
key,
(guint)g_bytes_get_size(blob));
return FALSE;
}
/* sanity check */
buf = g_bytes_get_data(blob, &bufsz);
for (gsize i = 0; i < bufsz; i++) {
if (buf[i] != 0x0 && !g_ascii_isprint((gchar)buf[i])) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"nonprintable character 0x%02x at offset 0x%x in %s",
buf[i],
(guint)i,
key);
return FALSE;
}
}
/* success */
if (val != NULL)
*val = fu_fdt_image_strlist_from_blob(blob);
return TRUE;
}
/**
* fu_fdt_image_get_attr_str:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @val: (out) (nullable) (transfer full): value
* @error: (nullable): optional return location for an error
*
* Gets a string attribute from the image. @val is always `NUL` terminated.
*
* Returns: %TRUE if @val was set.
*
* Since: 1.8.2
**/
gboolean
fu_fdt_image_get_attr_str(FuFdtImage *self, const gchar *key, gchar **val, GError **error)
{
g_autoptr(GBytes) blob = NULL;
const guint8 *buf;
gsize bufsz = 0;
g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
blob = fu_fdt_image_get_attr(self, key, error);
if (blob == NULL)
return FALSE;
if (g_bytes_get_size(blob) == 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"invalid data size for %s, got 0x%x",
key,
(guint)g_bytes_get_size(blob));
return FALSE;
}
/* sanity check */
buf = g_bytes_get_data(blob, &bufsz);
for (gsize i = 0; i < bufsz; i++) {
if (buf[i] != 0x0 && !g_ascii_isprint((gchar)buf[i])) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"nonprintable character 0x%02x at offset 0x%x in %s",
buf[i],
(guint)i,
key);
return FALSE;
}
}
/* success */
if (val != NULL)
*val = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob));
return TRUE;
}
/**
* fu_fdt_image_set_attr:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @blob: a #GBytes
*
* Sets a attribute for the image.
*
* Since: 1.8.2
**/
void
fu_fdt_image_set_attr(FuFdtImage *self, const gchar *key, GBytes *blob)
{
FuFdtImagePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_FDT_IMAGE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->hash_attrs, g_strdup(key), g_bytes_ref(blob));
}
static void
fu_fdt_image_set_attr_format(FuFdtImage *self, const gchar *key, const gchar *format)
{
FuFdtImagePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_FDT_IMAGE(self));
g_return_if_fail(format != NULL);
g_hash_table_insert(priv->hash_attrs_format, g_strdup(key), strdup(format));
}
/**
* fu_fdt_image_set_attr_uint32:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @value: value to store
*
* Sets a uint32 attribute for the image.
*
* Since: 1.8.2
**/
void
fu_fdt_image_set_attr_uint32(FuFdtImage *self, const gchar *key, guint32 value)
{
guint8 buf[4] = {0x0};
g_autoptr(GBytes) blob = NULL;
g_return_if_fail(FU_IS_FDT_IMAGE(self));
g_return_if_fail(key != NULL);
fu_memwrite_uint32(buf, value, G_BIG_ENDIAN);
blob = g_bytes_new(buf, sizeof(buf));
fu_fdt_image_set_attr(self, key, blob);
fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_UINT32);
}
/**
* fu_fdt_image_set_attr_uint64:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @value: value to store
*
* Sets a uint64 attribute for the image.
*
* Since: 1.8.2
**/
void
fu_fdt_image_set_attr_uint64(FuFdtImage *self, const gchar *key, guint64 value)
{
guint8 buf[8] = {0x0};
g_autoptr(GBytes) blob = NULL;
g_return_if_fail(FU_IS_FDT_IMAGE(self));
g_return_if_fail(key != NULL);
fu_memwrite_uint64(buf, value, G_BIG_ENDIAN);
blob = g_bytes_new(buf, sizeof(buf));
fu_fdt_image_set_attr(self, key, blob);
fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_UINT64);
}
/**
* fu_fdt_image_set_attr_str:
* @self: a #FuFdtImage
* @key: string, e.g. `creator`
* @value: value to store
*
* Sets a string attribute for the image.
*
* Since: 1.8.2
**/
void
fu_fdt_image_set_attr_str(FuFdtImage *self, const gchar *key, const gchar *value)
{
g_autoptr(GBytes) blob = NULL;
g_return_if_fail(FU_IS_FDT_IMAGE(self));
g_return_if_fail(key != NULL);
g_return_if_fail(value != NULL);
blob = g_bytes_new((const guint8 *)value, strlen(value) + 1);
fu_fdt_image_set_attr(self, key, blob);
fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_STR);
}
/**
* fu_fdt_image_set_attr_strlist:
* @self: a #FuFdtImage
* @key: string, e.g. `compatible`
* @value: values to store
*
* Sets a stringlist attribute for the image.
*
* Since: 1.8.2
**/
void
fu_fdt_image_set_attr_strlist(FuFdtImage *self, const gchar *key, gchar **value)
{
g_autoptr(GByteArray) buf = g_byte_array_new();
g_autoptr(GBytes) blob = NULL;
g_return_if_fail(FU_IS_FDT_IMAGE(self));
g_return_if_fail(key != NULL);
g_return_if_fail(value != NULL);
g_return_if_fail(value[0] != NULL);
for (guint i = 0; value[i] != NULL; i++) {
g_byte_array_append(buf, (const guint8 *)value[i], strlen(value[i]));
fu_byte_array_append_uint8(buf, 0x0);
}
blob = g_bytes_new(buf->data, buf->len);
fu_fdt_image_set_attr(self, key, blob);
fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_STRLIST);
}
static gboolean
fu_fdt_image_build_metadata_node(FuFdtImage *self, XbNode *n, GError **error)
{
const gchar *key;
const gchar *format;
const gchar *value = xb_node_get_text(n);
key = xb_node_get_attr(n, "key");
if (key == NULL) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "key invalid");
return FALSE;
}
format = xb_node_get_attr(n, "format");
if (format == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"format unspecified for %s, expected uint64|uint32|str|strlist|data",
key);
return FALSE;
}
fu_fdt_image_set_attr_format(self, key, format);
/* actually parse values */
if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT32) == 0) {
guint64 tmp = 0;
if (value != NULL) {
if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT32, error))
return FALSE;
}
fu_fdt_image_set_attr_uint32(self, key, tmp);
return TRUE;
}
if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT64) == 0) {
guint64 tmp = 0;
if (value != NULL) {
if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT64, error))
return FALSE;
}
fu_fdt_image_set_attr_uint64(self, key, tmp);
return TRUE;
}
if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STR) == 0) {
if (value != NULL) {
fu_fdt_image_set_attr_str(self, key, value);
} else {
g_autoptr(GBytes) blob = g_bytes_new(NULL, 0);
fu_fdt_image_set_attr(self, key, blob);
}
return TRUE;
}
if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STRLIST) == 0) {
if (value != NULL) {
g_auto(GStrv) split = g_strsplit(value, ":", -1);
fu_fdt_image_set_attr_strlist(self, key, split);
} else {
g_autoptr(GBytes) blob = g_bytes_new(NULL, 0);
fu_fdt_image_set_attr(self, key, blob);
}
return TRUE;
}
if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_DATA) == 0) {
g_autoptr(GBytes) blob = NULL;
if (value != NULL) {
gsize bufsz = 0;
g_autofree guchar *buf = g_base64_decode(value, &bufsz);
blob = g_bytes_new(buf, bufsz);
} else {
blob = g_bytes_new(NULL, 0);
}
fu_fdt_image_set_attr(self, key, blob);
return TRUE;
}
/* failed */
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"format for %s invalid, expected uint64|uint32|str|strlist|data",
key);
return FALSE;
}
static gboolean
fu_fdt_image_build(FuFirmware *firmware, XbNode *n, GError **error)
{
FuFdtImage *self = FU_FDT_IMAGE(firmware);
g_autoptr(GPtrArray) metadata = NULL;
metadata = xb_node_query(n, "metadata", 0, NULL);
if (metadata != NULL) {
for (guint i = 0; i < metadata->len; i++) {
XbNode *c = g_ptr_array_index(metadata, i);
if (!fu_fdt_image_build_metadata_node(self, c, error))
return FALSE;
}
}
/* success */
return TRUE;
}
static void
fu_fdt_image_init(FuFdtImage *self)
{
FuFdtImagePrivate *priv = GET_PRIVATE(self);
priv->hash_attrs =
g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref);
priv->hash_attrs_format = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
}
static void
fu_fdt_image_finalize(GObject *object)
{
FuFdtImage *self = FU_FDT_IMAGE(object);
FuFdtImagePrivate *priv = GET_PRIVATE(self);
g_hash_table_unref(priv->hash_attrs);
g_hash_table_unref(priv->hash_attrs_format);
G_OBJECT_CLASS(fu_fdt_image_parent_class)->finalize(object);
}
static void
fu_fdt_image_class_init(FuFdtImageClass *klass)
{
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass);
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fu_fdt_image_finalize;
klass_firmware->export = fu_fdt_image_export;
klass_firmware->build = fu_fdt_image_build;
}
/**
* fu_fdt_image_new:
*
* Creates a new #FuFirmware of sub type FDT image
*
* Since: 1.8.2
**/
FuFirmware *
fu_fdt_image_new(void)
{
return FU_FIRMWARE(g_object_new(FU_TYPE_FDT_IMAGE, NULL));
}