fwupd/libfwupdplugin/fu-firmware.c
Richard Hughes 6da96cd04a Add FuFirmwareFlags to allow opt-in dedupe of added images
The function fu_firmware_add_image() has the comment text 'If an image with the
same ID is present it is replaced' which has not been true for some time.

This was removed, as the common case of adding two images with no ID would only
leave one. However, some plugins do actually want to dedupe on the ID or IDX,
so provide a flag they can set which enables this functionality without
introducing regressions into other plugins.
2020-09-17 20:49:01 +01:00

654 lines
16 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.h"
#include "fu-firmware-image-private.h"
/**
* SECTION:fu-firmware
* @short_description: a firmware file
*
* An object that represents a firmware file.
* See also: #FuDfuFirmware, #FuIhexFirmware, #FuSrecFirmware
*/
typedef struct {
FuFirmwareFlags flags;
GPtrArray *images; /* FuFirmwareImage */
gchar *version;
} FuFirmwarePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (FuFirmware, fu_firmware, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (fu_firmware_get_instance_private (o))
/**
* fu_firmware_flag_to_string:
* @flag: A #FuFirmwareFlags, e.g. %FU_FIRMWARE_FLAG_DEDUPE_ID
*
* Converts a #FuFirmwareFlags to a string.
*
* Return value: identifier string
*
* Since: 1.5.0
**/
const gchar *
fu_firmware_flag_to_string (FuFirmwareFlags flag)
{
if (flag == FU_FIRMWARE_FLAG_NONE)
return "none";
if (flag == FU_FIRMWARE_FLAG_DEDUPE_ID)
return "dedupe-id";
if (flag == FU_FIRMWARE_FLAG_DEDUPE_IDX)
return "dedupe-idx";
return NULL;
}
/**
* fu_firmware_flag_from_string:
* @flag: A string, e.g. `dedupe-id`
*
* Converts a string to a #FuFirmwareFlags.
*
* Return value: enumerated value
*
* Since: 1.5.0
**/
FuFirmwareFlags
fu_firmware_flag_from_string (const gchar *flag)
{
if (g_strcmp0 (flag, "dedupe-id") == 0)
return FU_FIRMWARE_FLAG_DEDUPE_ID;
if (g_strcmp0 (flag, "dedupe-idx") == 0)
return FU_FIRMWARE_FLAG_DEDUPE_IDX;
return FU_FIRMWARE_FLAG_NONE;
}
/**
* fu_firmware_add_flag:
* @firmware: A #FuFirmware
* @flag: the #FuFirmwareFlags
*
* Adds a specific firmware flag to the firmware.
*
* Since: 1.5.0
**/
void
fu_firmware_add_flag (FuFirmware *firmware, FuFirmwareFlags flag)
{
FuFirmwarePrivate *priv = GET_PRIVATE (firmware);
g_return_if_fail (FU_IS_FIRMWARE (firmware));
priv->flags |= flag;
}
/**
* fu_firmware_has_flag:
* @firmware: A #FuFirmware
* @flag: the #FuFirmwareFlags
*
* Finds if the firmware has a specific firmware flag.
*
* Returns: %TRUE if the flag is set
*
* Since: 1.5.0
**/
gboolean
fu_firmware_has_flag (FuFirmware *firmware, FuFirmwareFlags flag)
{
FuFirmwarePrivate *priv = GET_PRIVATE (firmware);
g_return_val_if_fail (FU_IS_FIRMWARE (firmware), FALSE);
return (priv->flags & flag) > 0;
}
/**
* fu_firmware_get_version:
* @self: A #FuFirmware
*
* Gets an optional version that represents the firmware.
*
* Returns: a string, or %NULL
*
* Since: 1.3.3
**/
const gchar *
fu_firmware_get_version (FuFirmware *self)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL);
return priv->version;
}
/**
* fu_firmware_set_version:
* @self: A #FuFirmware
* @version: A string version, or %NULL
*
* Sets an optional version that represents the firmware.
*
* Since: 1.3.3
**/
void
fu_firmware_set_version (FuFirmware *self, const gchar *version)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE (self));
g_free (priv->version);
priv->version = g_strdup (version);
}
/**
* fu_firmware_tokenize:
* @self: A #FuFirmware
* @fw: A #GBytes
* @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: A #GError, or %NULL
*
* Tokenizes a firmware, typically breaking the firmware into records.
*
* Records can be enumerated using subclass-specific functionality, for example
* using fu_srec_firmware_get_records().
*
* Returns: %TRUE for success
*
* Since: 1.3.2
**/
gboolean
fu_firmware_tokenize (FuFirmware *self, GBytes *fw,
FwupdInstallFlags flags, GError **error)
{
FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self);
g_return_val_if_fail (FU_IS_FIRMWARE (self), FALSE);
g_return_val_if_fail (fw != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* optionally subclassed */
if (klass->tokenize != NULL)
return klass->tokenize (self, fw, flags, error);
return TRUE;
}
/**
* fu_firmware_parse_full:
* @self: A #FuFirmware
* @fw: A #GBytes
* @addr_start: Start address, useful for ignoring a bootloader
* @addr_end: End address, useful for ignoring config bytes
* @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: A #GError, or %NULL
*
* Parses a firmware, typically breaking the firmware into images.
*
* Returns: %TRUE for success
*
* Since: 1.3.1
**/
gboolean
fu_firmware_parse_full (FuFirmware *self,
GBytes *fw,
guint64 addr_start,
guint64 addr_end,
FwupdInstallFlags flags,
GError **error)
{
FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self);
g_autoptr(FuFirmwareImage) img = NULL;
g_return_val_if_fail (FU_IS_FIRMWARE (self), FALSE);
g_return_val_if_fail (fw != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* subclassed */
if (klass->tokenize != NULL) {
if (!klass->tokenize (self, fw, flags, error))
return FALSE;
}
if (klass->parse != NULL)
return klass->parse (self, fw, addr_start, addr_end, flags, error);
/* just add entire blob */
img = fu_firmware_image_new (fw);
fu_firmware_add_image (self, img);
return TRUE;
}
/**
* fu_firmware_parse:
* @self: A #FuFirmware
* @fw: A #GBytes
* @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: A #GError, or %NULL
*
* Parses a firmware, typically breaking the firmware into images.
*
* Returns: %TRUE for success
*
* Since: 1.3.1
**/
gboolean
fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error)
{
return fu_firmware_parse_full (self, fw, 0x0, 0x0, flags, error);
}
/**
* fu_firmware_parse_file:
* @self: A #FuFirmware
* @file: A #GFile
* @flags: some #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: A #GError, or %NULL
*
* Parses a firmware file, typically breaking the firmware into images.
*
* Returns: %TRUE for success
*
* Since: 1.3.3
**/
gboolean
fu_firmware_parse_file (FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error)
{
gchar *buf = NULL;
gsize bufsz = 0;
g_autoptr(GBytes) fw = NULL;
if (!g_file_load_contents (file, NULL, &buf, &bufsz, NULL, error))
return FALSE;
fw = g_bytes_new_take (buf, bufsz);
return fu_firmware_parse (self, fw, flags, error);
}
/**
* fu_firmware_write:
* @self: A #FuFirmware
* @error: A #GError, or %NULL
*
* Writes a firmware, typically packing the images into a binary blob.
*
* Returns: (transfer full): a #GBytes
*
* Since: 1.3.1
**/
GBytes *
fu_firmware_write (FuFirmware *self, GError **error)
{
FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self);
g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* subclassed */
if (klass->write != NULL)
return klass->write (self, error);
/* just add default blob */
return fu_firmware_get_image_default_bytes (self, error);
}
/**
* fu_firmware_write_file:
* @self: A #FuFirmware
* @file: A #GFile
* @error: A #GError, or %NULL
*
* Writes a firmware, typically packing the images into a binary blob.
*
* Returns: %TRUE for success
*
* Since: 1.3.3
**/
gboolean
fu_firmware_write_file (FuFirmware *self, GFile *file, GError **error)
{
g_autoptr(GBytes) blob = NULL;
blob = fu_firmware_write (self, error);
if (blob == NULL)
return FALSE;
return g_file_replace_contents (file,
g_bytes_get_data (blob, NULL),
g_bytes_get_size (blob),
NULL, FALSE,
G_FILE_CREATE_NONE,
NULL, NULL, error);
}
/**
* fu_firmware_add_image:
* @self: a #FuPlugin
* @img: A #FuFirmwareImage
*
* Adds an image to the firmware.
*
* If %FU_FIRMWARE_FLAG_DEDUPE_ID is set, an image with the same ID is already
* present it is replaced.
*
* Since: 1.3.1
**/
void
fu_firmware_add_image (FuFirmware *self, FuFirmwareImage *img)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_FIRMWARE (self));
g_return_if_fail (FU_IS_FIRMWARE_IMAGE (img));
/* dedupe */
for (guint i = 0; i < priv->images->len; i++) {
FuFirmwareImage *img_tmp = g_ptr_array_index (priv->images, i);
if (priv->flags & FU_FIRMWARE_FLAG_DEDUPE_ID) {
if (g_strcmp0 (fu_firmware_image_get_id (img_tmp),
fu_firmware_image_get_id (img)) == 0) {
g_ptr_array_remove_index (priv->images, i);
break;
}
}
if (priv->flags & FU_FIRMWARE_FLAG_DEDUPE_IDX) {
if (fu_firmware_image_get_idx (img_tmp) ==
fu_firmware_image_get_idx (img)) {
g_ptr_array_remove_index (priv->images, i);
break;
}
}
}
g_ptr_array_add (priv->images, g_object_ref (img));
}
/**
* fu_firmware_get_images:
* @self: a #FuFirmware
*
* Returns all the images in the firmware.
*
* Returns: (transfer container) (element-type FuFirmwareImage): images
*
* Since: 1.3.1
**/
GPtrArray *
fu_firmware_get_images (FuFirmware *self)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_autoptr(GPtrArray) imgs = NULL;
g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL);
imgs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
for (guint i = 0; i < priv->images->len; i++) {
FuFirmwareImage *img = g_ptr_array_index (priv->images, i);
g_ptr_array_add (imgs, g_object_ref (img));
}
return g_steal_pointer (&imgs);
}
/**
* fu_firmware_get_image_by_id:
* @self: a #FuPlugin
* @id: (nullable): image ID, e.g. "config"
* @error: A #GError, or %NULL
*
* Gets the firmware image using the image ID.
*
* Returns: (transfer full): a #FuFirmwareImage, or %NULL if the image is not found
*
* Since: 1.3.1
**/
FuFirmwareImage *
fu_firmware_get_image_by_id (FuFirmware *self, const gchar *id, GError **error)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
for (guint i = 0; i < priv->images->len; i++) {
FuFirmwareImage *img = g_ptr_array_index (priv->images, i);
if (g_strcmp0 (fu_firmware_image_get_id (img), id) == 0)
return g_object_ref (img);
}
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no image id %s found in firmware", id);
return NULL;
}
/**
* fu_firmware_get_image_by_id_bytes:
* @self: a #FuPlugin
* @id: (nullable): image ID, e.g. "config"
* @error: A #GError, or %NULL
*
* Gets the firmware image bytes using the image ID.
*
* Returns: (transfer full): a #GBytes of a #FuFirmwareImage, or %NULL if the image is not found
*
* Since: 1.3.1
**/
GBytes *
fu_firmware_get_image_by_id_bytes (FuFirmware *self, const gchar *id, GError **error)
{
g_autoptr(FuFirmwareImage) img = fu_firmware_get_image_by_id (self, id, error);
if (img == NULL)
return NULL;
return fu_firmware_image_write (img, error);
}
/**
* fu_firmware_get_image_by_idx:
* @self: a #FuPlugin
* @idx: image index
* @error: A #GError, or %NULL
*
* Gets the firmware image using the image index.
*
* Returns: (transfer full): a #FuFirmwareImage, or %NULL if the image is not found
*
* Since: 1.3.1
**/
FuFirmwareImage *
fu_firmware_get_image_by_idx (FuFirmware *self, guint64 idx, GError **error)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_FIRMWARE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
for (guint i = 0; i < priv->images->len; i++) {
FuFirmwareImage *img = g_ptr_array_index (priv->images, i);
if (fu_firmware_image_get_idx (img) == idx)
return g_object_ref (img);
}
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no image idx %" G_GUINT64_FORMAT " found in firmware", idx);
return NULL;
}
/**
* fu_firmware_get_image_by_idx_bytes:
* @self: a #FuPlugin
* @idx: image index
* @error: A #GError, or %NULL
*
* Gets the firmware image bytes using the image index.
*
* Returns: (transfer full): a #GBytes of a #FuFirmwareImage, or %NULL if the image is not found
*
* Since: 1.3.1
**/
GBytes *
fu_firmware_get_image_by_idx_bytes (FuFirmware *self, guint64 idx, GError **error)
{
g_autoptr(FuFirmwareImage) img = fu_firmware_get_image_by_idx (self, idx, error);
if (img == NULL)
return NULL;
return fu_firmware_image_write (img, error);
}
/**
* fu_firmware_get_image_default:
* @self: a #FuPlugin
* @error: A #GError, or %NULL
*
* Gets the default firmware image.
*
* NOTE: If the firmware has multiple images included then fu_firmware_get_image_by_id()
* or fu_firmware_get_image_by_idx() must be used rather than this function.
*
* Returns: (transfer full): a #FuFirmwareImage, or %NULL if the image is not found
*
* Since: 1.3.1
**/
FuFirmwareImage *
fu_firmware_get_image_default (FuFirmware *self, GError **error)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
if (priv->images->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no images in firmware");
return NULL;
}
if (priv->images->len > 1) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"multiple images present in firmware");
return NULL;
}
return g_object_ref (FU_FIRMWARE_IMAGE (g_ptr_array_index (priv->images, 0)));
}
/**
* fu_firmware_get_image_default_bytes:
* @self: a #FuPlugin
* @error: A #GError, or %NULL
*
* Gets the default firmware image.
*
* Returns: (transfer full): a #GBytes of the image, or %NULL if the image is not found
*
* Since: 1.3.1
**/
GBytes *
fu_firmware_get_image_default_bytes (FuFirmware *self, GError **error)
{
g_autoptr(FuFirmwareImage) img = fu_firmware_get_image_default (self, error);
if (img == NULL)
return NULL;
return fu_firmware_image_write (img, error);
}
/**
* fu_firmware_to_string:
* @self: A #FuFirmware
*
* This allows us to easily print the object.
*
* Returns: a string value, or %NULL for invalid.
*
* Since: 1.3.1
**/
gchar *
fu_firmware_to_string (FuFirmware *self)
{
FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self);
FuFirmwarePrivate *priv = GET_PRIVATE (self);
GString *str = g_string_new (NULL);
/* subclassed type */
fu_common_string_append_kv (str, 0, G_OBJECT_TYPE_NAME (self), NULL);
if (priv->flags != FU_FIRMWARE_FLAG_NONE) {
g_autoptr(GString) tmp = g_string_new ("");
for (guint i = 0; i < 64; i++) {
if ((priv->flags & ((guint64) 1 << i)) == 0)
continue;
g_string_append_printf (tmp, "%s|",
fu_firmware_flag_to_string ((guint64) 1 << i));
}
if (tmp->len > 0)
g_string_truncate (tmp, tmp->len - 1);
fu_common_string_append_kv (str, 0, "Flags", tmp->str);
}
if (priv->version != NULL)
fu_common_string_append_kv (str, 0, "Version", priv->version);
/* vfunc */
if (klass->to_string != NULL)
klass->to_string (self, 0, str);
for (guint i = 0; i < priv->images->len; i++) {
FuFirmwareImage *img = g_ptr_array_index (priv->images, i);
fu_firmware_image_add_string (img, 1, str);
}
return g_string_free (str, FALSE);
}
static void
fu_firmware_init (FuFirmware *self)
{
FuFirmwarePrivate *priv = GET_PRIVATE (self);
priv->images = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
}
static void
fu_firmware_finalize (GObject *object)
{
FuFirmware *self = FU_FIRMWARE (object);
FuFirmwarePrivate *priv = GET_PRIVATE (self);
g_free (priv->version);
g_ptr_array_unref (priv->images);
G_OBJECT_CLASS (fu_firmware_parent_class)->finalize (object);
}
static void
fu_firmware_class_init (FuFirmwareClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_firmware_finalize;
}
/**
* fu_firmware_new:
*
* Creates an empty firmware object.
*
* Returns: a #FuFirmware
*
* Since: 1.3.1
**/
FuFirmware *
fu_firmware_new (void)
{
FuFirmware *self = g_object_new (FU_TYPE_FIRMWARE, NULL);
return FU_FIRMWARE (self);
}
/**
* fu_firmware_new_from_bytes:
* @fw: A #GBytes image
*
* Creates a firmware object with the provided image set as default.
*
* Returns: a #FuFirmware
*
* Since: 1.3.1
**/
FuFirmware *
fu_firmware_new_from_bytes (GBytes *fw)
{
FuFirmware *self = fu_firmware_new ();
g_autoptr(FuFirmwareImage) img = NULL;
img = fu_firmware_image_new (fw);
fu_firmware_add_image (self, img);
return self;
}