mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-09 18:17:54 +00:00
579 lines
14 KiB
C
579 lines
14 KiB
C
/*
|
|
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "FuFirmware"
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-firmware-image-private.h"
|
|
|
|
/**
|
|
* SECTION:fu-firmware-image
|
|
* @short_description: a firmware image section
|
|
*
|
|
* An object that represents an image within the firmware file.
|
|
*/
|
|
|
|
typedef struct {
|
|
gchar *id;
|
|
GBytes *bytes;
|
|
guint64 addr;
|
|
guint64 offset;
|
|
guint64 idx;
|
|
gchar *version;
|
|
gchar *filename;
|
|
} FuFirmwareImagePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (FuFirmwareImage, fu_firmware_image, G_TYPE_OBJECT)
|
|
#define GET_PRIVATE(o) (fu_firmware_image_get_instance_private (o))
|
|
|
|
/**
|
|
* fu_firmware_image_get_version:
|
|
* @self: A #FuFirmwareImage
|
|
*
|
|
* Gets an optional version that represents the firmware image.
|
|
*
|
|
* Returns: a string, or %NULL
|
|
*
|
|
* Since: 1.3.4
|
|
**/
|
|
const gchar *
|
|
fu_firmware_image_get_version (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
|
|
return priv->version;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_version:
|
|
* @self: A #FuFirmwareImage
|
|
* @version: (nullable): A string version, or %NULL
|
|
*
|
|
* Sets an optional version that represents the firmware image.
|
|
*
|
|
* Since: 1.3.4
|
|
**/
|
|
void
|
|
fu_firmware_image_set_version (FuFirmwareImage *self, const gchar *version)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
g_free (priv->version);
|
|
priv->version = g_strdup (version);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_get_filename:
|
|
* @self: A #FuFirmwareImage
|
|
*
|
|
* Gets an optional filename that represents the image source or destination.
|
|
*
|
|
* Returns: a string, or %NULL
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
const gchar *
|
|
fu_firmware_image_get_filename (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
|
|
return priv->filename;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_filename:
|
|
* @self: A #FuFirmwareImage
|
|
* @filename: (nullable): A string filename, or %NULL
|
|
*
|
|
* Sets an optional filename that represents the image source or destination.
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
void
|
|
fu_firmware_image_set_filename (FuFirmwareImage *self, const gchar *filename)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
g_free (priv->filename);
|
|
priv->filename = g_strdup (filename);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_id:
|
|
* @self: a #FuPlugin
|
|
* @id: (nullable): image ID, e.g. "config"
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_firmware_image_set_id (FuFirmwareImage *self, const gchar *id)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
g_free (priv->id);
|
|
priv->id = g_strdup (id);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_get_id:
|
|
* @self: a #FuPlugin
|
|
*
|
|
* Gets the image ID, typically set at construction.
|
|
*
|
|
* Returns: image ID, e.g. "config"
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
const gchar *
|
|
fu_firmware_image_get_id (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
|
|
return priv->id;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_addr:
|
|
* @self: a #FuPlugin
|
|
* @addr: integer
|
|
*
|
|
* Sets the base address of the image.
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_firmware_image_set_addr (FuFirmwareImage *self, guint64 addr)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
priv->addr = addr;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_get_addr:
|
|
* @self: a #FuPlugin
|
|
*
|
|
* Gets the base address of the image.
|
|
*
|
|
* Returns: integer
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
guint64
|
|
fu_firmware_image_get_addr (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64);
|
|
return priv->addr;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_offset:
|
|
* @self: a #FuPlugin
|
|
* @offset: integer
|
|
*
|
|
* Sets the base offset of the image.
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
void
|
|
fu_firmware_image_set_offset (FuFirmwareImage *self, guint64 offset)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
priv->offset = offset;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_get_offset:
|
|
* @self: a #FuPlugin
|
|
*
|
|
* Gets the base offset of the image.
|
|
*
|
|
* Returns: integer
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
guint64
|
|
fu_firmware_image_get_offset (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64);
|
|
return priv->offset;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_idx:
|
|
* @self: a #FuPlugin
|
|
* @idx: integer
|
|
*
|
|
* Sets the index of the image which is used for ordering.
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_firmware_image_set_idx (FuFirmwareImage *self, guint64 idx)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
priv->idx = idx;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_get_idx:
|
|
* @self: a #FuPlugin
|
|
*
|
|
* Gets the index of the image which is used for ordering.
|
|
*
|
|
* Returns: integer
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
guint64
|
|
fu_firmware_image_get_idx (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), G_MAXUINT64);
|
|
return priv->idx;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_set_bytes:
|
|
* @self: a #FuPlugin
|
|
* @bytes: A #GBytes
|
|
*
|
|
* Sets the contents of the image if not created with fu_firmware_image_new().
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_firmware_image_set_bytes (FuFirmwareImage *self, GBytes *bytes)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (self));
|
|
g_return_if_fail (bytes != NULL);
|
|
g_return_if_fail (priv->bytes == NULL);
|
|
priv->bytes = g_bytes_ref (bytes);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_get_bytes:
|
|
* @self: a #FuPlugin
|
|
*
|
|
* Gets the data set using fu_firmware_image_set_bytes().
|
|
*
|
|
* This should only really be used by objects subclassing #FuFirmwareImage as
|
|
* images are normally exported to a file using fu_firmware_image_write().
|
|
*
|
|
* Returns: (transfer full): a #GBytes of the data, or %NULL if the bytes is not set
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
GBytes *
|
|
fu_firmware_image_get_bytes (FuFirmwareImage *self)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
|
|
if (priv->bytes == NULL)
|
|
return NULL;
|
|
return g_bytes_ref (priv->bytes);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_parse:
|
|
* @self: A #FuFirmwareImage
|
|
* @fw: A #GBytes
|
|
* @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
|
|
* @error: A #GError, or %NULL
|
|
*
|
|
* Parses a firmware image, typically checking image CRCs and/or headers.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
gboolean
|
|
fu_firmware_image_parse (FuFirmwareImage *self,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
|
|
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), FALSE);
|
|
g_return_val_if_fail (fw != NULL, FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* subclassed */
|
|
if (klass->parse != NULL)
|
|
return klass->parse (self, fw, flags, error);
|
|
|
|
/* just add entire blob */
|
|
fu_firmware_image_set_bytes (self, fw);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_build:
|
|
* @self: A #FuFirmwareImage
|
|
* @n: A #XbNode
|
|
* @error: A #GError, or %NULL
|
|
*
|
|
* Builds a firmware image from an XML manifest.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
gboolean
|
|
fu_firmware_image_build (FuFirmwareImage *self, XbNode *n, GError **error)
|
|
{
|
|
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
|
|
guint64 tmpval;
|
|
const gchar *tmp;
|
|
g_autoptr(XbNode) data = NULL;
|
|
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), FALSE);
|
|
g_return_val_if_fail (XB_IS_NODE (n), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
tmp = xb_node_query_text (n, "version", NULL);
|
|
if (tmp != NULL)
|
|
fu_firmware_image_set_version (self, tmp);
|
|
tmp = xb_node_query_text (n, "id", NULL);
|
|
if (tmp != NULL)
|
|
fu_firmware_image_set_id (self, tmp);
|
|
tmpval = xb_node_query_text_as_uint (n, "idx", NULL);
|
|
if (tmpval != G_MAXUINT64)
|
|
fu_firmware_image_set_idx (self, tmpval);
|
|
tmpval = xb_node_query_text_as_uint (n, "addr", NULL);
|
|
if (tmpval != G_MAXUINT64)
|
|
fu_firmware_image_set_addr (self, tmpval);
|
|
tmpval = xb_node_query_text_as_uint (n, "offset", NULL);
|
|
if (tmpval != G_MAXUINT64)
|
|
fu_firmware_image_set_offset (self, tmpval);
|
|
tmp = xb_node_query_text (n, "filename", NULL);
|
|
if (tmp != NULL) {
|
|
g_autoptr(GBytes) blob = NULL;
|
|
blob = fu_common_get_contents_bytes (tmp, error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
fu_firmware_image_set_bytes (self, blob);
|
|
fu_firmware_image_set_filename (self, tmp);
|
|
}
|
|
data = xb_node_query_first (n, "data", NULL);
|
|
if (data != NULL && xb_node_get_text (data) != NULL) {
|
|
gsize bufsz = 0;
|
|
g_autofree guchar *buf = NULL;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
buf = g_base64_decode (xb_node_get_text (data), &bufsz);
|
|
blob = g_bytes_new (buf, bufsz);
|
|
fu_firmware_image_set_bytes (self, blob);
|
|
} else if (data != NULL) {
|
|
g_autoptr(GBytes) blob = NULL;
|
|
blob = g_bytes_new (NULL, 0);
|
|
fu_firmware_image_set_bytes (self, blob);
|
|
}
|
|
|
|
/* subclassed */
|
|
if (klass->build != NULL) {
|
|
if (!klass->build (self, n, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_write:
|
|
* @self: a #FuPlugin
|
|
* @error: A #GError, or %NULL
|
|
*
|
|
* Writes the image, which will try to call a superclassed ->write() function.
|
|
*
|
|
* By default (and in most cases) this just provides the value set by the
|
|
* fu_firmware_image_set_bytes() function.
|
|
*
|
|
* Returns: (transfer full): a #GBytes of the bytes, or %NULL if the bytes is not set
|
|
*
|
|
* Since: 1.3.3
|
|
**/
|
|
GBytes *
|
|
fu_firmware_image_write (FuFirmwareImage *self, GError **error)
|
|
{
|
|
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
|
|
g_return_val_if_fail (FU_IS_FIRMWARE_IMAGE (self), NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* optional vfunc */
|
|
if (klass->write != NULL)
|
|
return klass->write (self, error);
|
|
|
|
/* fall back to what was set manually */
|
|
if (priv->bytes == NULL) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"no bytes found in firmware bytes %s", priv->id);
|
|
return NULL;
|
|
}
|
|
return g_bytes_ref (priv->bytes);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_write_chunk:
|
|
* @self: a #FuFirmwareImage
|
|
* @address: an address greater than dfu_element_get_address()
|
|
* @chunk_sz_max: the size of the new chunk
|
|
* @error: a #GError, or %NULL
|
|
*
|
|
* Gets a block of data from the image. If the contents of the image is
|
|
* smaller than the requested chunk size then the #GBytes will be smaller
|
|
* than @chunk_sz_max. Use fu_common_bytes_pad() if padding is required.
|
|
*
|
|
* If the @address is larger than the size of the image then an error is returned.
|
|
*
|
|
* Return value: (transfer full): a #GBytes, or %NULL
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
GBytes *
|
|
fu_firmware_image_write_chunk (FuFirmwareImage *self,
|
|
guint64 address,
|
|
guint64 chunk_sz_max,
|
|
GError **error)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
gsize chunk_left;
|
|
guint64 offset;
|
|
|
|
/* check address requested is larger than base address */
|
|
if (address < priv->addr) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"requested address 0x%x less than base address 0x%x",
|
|
(guint) address, (guint) priv->addr);
|
|
return NULL;
|
|
}
|
|
|
|
/* offset into data */
|
|
offset = address - priv->addr;
|
|
if (offset > g_bytes_get_size (priv->bytes)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
"offset 0x%x larger than data size 0x%x",
|
|
(guint) offset,
|
|
(guint) g_bytes_get_size (priv->bytes));
|
|
return NULL;
|
|
}
|
|
|
|
/* if we have less data than requested */
|
|
chunk_left = g_bytes_get_size (priv->bytes) - offset;
|
|
if (chunk_sz_max > chunk_left)
|
|
return g_bytes_new_from_bytes (priv->bytes, offset, chunk_left);
|
|
|
|
/* check chunk */
|
|
return g_bytes_new_from_bytes (priv->bytes, offset, chunk_sz_max);
|
|
}
|
|
|
|
void
|
|
fu_firmware_image_add_string (FuFirmwareImage *self, guint idt, GString *str)
|
|
{
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
FuFirmwareImageClass *klass = FU_FIRMWARE_IMAGE_GET_CLASS (self);
|
|
|
|
fu_common_string_append_kv (str, idt, G_OBJECT_TYPE_NAME (self), NULL);
|
|
if (priv->id != NULL)
|
|
fu_common_string_append_kv (str, idt, "ID", priv->id);
|
|
if (priv->idx != 0x0)
|
|
fu_common_string_append_kx (str, idt, "Index", priv->idx);
|
|
if (priv->addr != 0x0)
|
|
fu_common_string_append_kx (str, idt, "Address", priv->addr);
|
|
if (priv->offset != 0x0)
|
|
fu_common_string_append_kx (str, idt, "Offset", priv->offset);
|
|
if (priv->version != NULL)
|
|
fu_common_string_append_kv (str, idt, "Version", priv->version);
|
|
if (priv->filename != NULL)
|
|
fu_common_string_append_kv (str, idt, "Filename", priv->filename);
|
|
if (priv->bytes != NULL) {
|
|
fu_common_string_append_kx (str, idt, "Data",
|
|
g_bytes_get_size (priv->bytes));
|
|
}
|
|
|
|
/* vfunc */
|
|
if (klass->to_string != NULL)
|
|
klass->to_string (self, idt, str);
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_to_string:
|
|
* @self: A #FuFirmwareImage
|
|
*
|
|
* This allows us to easily print the object.
|
|
*
|
|
* Returns: a string value, or %NULL for invalid.
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
gchar *
|
|
fu_firmware_image_to_string (FuFirmwareImage *self)
|
|
{
|
|
GString *str = g_string_new (NULL);
|
|
fu_firmware_image_add_string (self, 0, str);
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
static void
|
|
fu_firmware_image_init (FuFirmwareImage *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_firmware_image_finalize (GObject *object)
|
|
{
|
|
FuFirmwareImage *self = FU_FIRMWARE_IMAGE (object);
|
|
FuFirmwareImagePrivate *priv = GET_PRIVATE (self);
|
|
g_free (priv->id);
|
|
g_free (priv->version);
|
|
g_free (priv->filename);
|
|
if (priv->bytes != NULL)
|
|
g_bytes_unref (priv->bytes);
|
|
G_OBJECT_CLASS (fu_firmware_image_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_firmware_image_class_init (FuFirmwareImageClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = fu_firmware_image_finalize;
|
|
}
|
|
|
|
/**
|
|
* fu_firmware_image_new:
|
|
* @bytes: Optional #GBytes
|
|
*
|
|
* Creates an empty firmware_image object.
|
|
*
|
|
* Returns: a #FuFirmwareImage
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
FuFirmwareImage *
|
|
fu_firmware_image_new (GBytes *bytes)
|
|
{
|
|
FuFirmwareImage *self = g_object_new (FU_TYPE_FIRMWARE_IMAGE, NULL);
|
|
if (bytes != NULL)
|
|
fu_firmware_image_set_bytes (self, bytes);
|
|
return self;
|
|
}
|