mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 09:00:55 +00:00
Allow contructing a firmware with multiple images
At the moment there are commands to convert one file format to another, but not to 'merge' or alter them. Some firmware files are containers which can store multiple images, each with optional id, idx and addresses. This would allow us to, for instance, create a DfuSe file with two different raw files that are flashed to different addresses on the SPI flash. It would also allow us to create very small complicated container formats for fuzzing. This can be used by writing a `firmware.builder.xml` file like: <?xml version="1.0" encoding="UTF-8"?> <firmware gtype="FuBcm57xxFirmware"> <version>1.2.3</version> <image> <version>4.5.6</version> <id>header</id> <idx>456</idx> <addr>0x456</addr> <filename>header.bin</filename> </image> <image> <version>7.8.9</version> <id>payload</id> <idx>789</idx> <addr>0x789</addr> <data>aGVsbG8=</data> </image> </firmware> ...and then using something like: # fwupdtool firmware-convert firmware.builder.xml firmware.dfu builder dfu
This commit is contained in:
parent
f17db477eb
commit
41400a8cc6
@ -259,6 +259,63 @@ fu_firmware_image_parse (FuFirmwareImage *self,
|
||||
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)
|
||||
{
|
||||
guint64 tmpval;
|
||||
const gchar *tmp;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
tmp = xb_node_query_text (n, "data", NULL);
|
||||
if (tmp != NULL) {
|
||||
gsize bufsz = 0;
|
||||
g_autofree guchar *buf = NULL;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
buf = g_base64_decode (tmp, &bufsz);
|
||||
blob = g_bytes_new (buf, bufsz);
|
||||
fu_firmware_image_set_bytes (self, blob);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_firmware_image_write:
|
||||
* @self: a #FuPlugin
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <fwupd.h>
|
||||
#include <xmlb.h>
|
||||
|
||||
#define FU_TYPE_FIRMWARE_IMAGE (fu_firmware_image_get_type ())
|
||||
G_DECLARE_DERIVABLE_TYPE (FuFirmwareImage, fu_firmware_image, FU, FIRMWARE_IMAGE, GObject)
|
||||
@ -56,6 +57,9 @@ gboolean fu_firmware_image_parse (FuFirmwareImage *self,
|
||||
GBytes *fw,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error);
|
||||
gboolean fu_firmware_image_build (FuFirmwareImage *self,
|
||||
XbNode *n,
|
||||
GError **error);
|
||||
GBytes *fu_firmware_image_write (FuFirmwareImage *self,
|
||||
GError **error);
|
||||
GBytes *fu_firmware_image_write_chunk (FuFirmwareImage *self,
|
||||
|
@ -239,6 +239,56 @@ fu_firmware_parse (FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError
|
||||
return fu_firmware_parse_full (self, fw, 0x0, 0x0, flags, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_firmware_build:
|
||||
* @self: A #FuFirmware
|
||||
* @n: A #XbNode
|
||||
* @error: A #GError, or %NULL
|
||||
*
|
||||
* Builds a firmware from an XML manifest.
|
||||
*
|
||||
* Returns: %TRUE for success
|
||||
*
|
||||
* Since: 1.5.0
|
||||
**/
|
||||
gboolean
|
||||
fu_firmware_build (FuFirmware *self, XbNode *n, GError **error)
|
||||
{
|
||||
FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS (self);
|
||||
const gchar *tmp;
|
||||
g_autoptr(GPtrArray) xb_images = NULL;
|
||||
|
||||
g_return_val_if_fail (FU_IS_FIRMWARE (self), FALSE);
|
||||
g_return_val_if_fail (XB_IS_NODE (n), FALSE);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* set attributes */
|
||||
tmp = xb_node_query_text (n, "version", NULL);
|
||||
if (tmp != NULL)
|
||||
fu_firmware_set_version (self, tmp);
|
||||
|
||||
/* parse images */
|
||||
xb_images = xb_node_query (n, "image", 0, NULL);
|
||||
if (xb_images != NULL) {
|
||||
for (guint i = 0; i < xb_images->len; i++) {
|
||||
XbNode *xb_image = g_ptr_array_index (xb_images, i);
|
||||
g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (NULL);
|
||||
if (!fu_firmware_image_build (img, xb_image, error))
|
||||
return FALSE;
|
||||
fu_firmware_add_image (self, img);
|
||||
}
|
||||
}
|
||||
|
||||
/* subclassed */
|
||||
if (klass->build != NULL) {
|
||||
if (!klass->build (self, n, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_firmware_parse_file:
|
||||
* @self: A #FuFirmware
|
||||
|
@ -32,8 +32,11 @@ struct _FuFirmwareClass
|
||||
GBytes *fw,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error);
|
||||
gboolean (*build) (FuFirmware *self,
|
||||
XbNode *n,
|
||||
GError **error);
|
||||
/*< private >*/
|
||||
gpointer padding[28];
|
||||
gpointer padding[27];
|
||||
};
|
||||
|
||||
/**
|
||||
@ -67,6 +70,9 @@ gboolean fu_firmware_tokenize (FuFirmware *self,
|
||||
GBytes *fw,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error);
|
||||
gboolean fu_firmware_build (FuFirmware *self,
|
||||
XbNode *n,
|
||||
GError **error);
|
||||
gboolean fu_firmware_parse (FuFirmware *self,
|
||||
GBytes *fw,
|
||||
FwupdInstallFlags flags,
|
||||
|
@ -1539,6 +1539,75 @@ fu_firmware_srec_tokenization_func (void)
|
||||
g_assert_cmpint (rcd->buf->data[0], ==, 0x50);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_firmware_build_func (void)
|
||||
{
|
||||
gboolean ret;
|
||||
g_autofree gchar *str = NULL;
|
||||
g_autoptr(FuFirmware) firmware = fu_firmware_new ();
|
||||
g_autoptr(FuFirmwareImage) img = NULL;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
g_autoptr(GBytes) blob2 = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(XbBuilder) builder = xb_builder_new ();
|
||||
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
|
||||
g_autoptr(XbNode) n = NULL;
|
||||
g_autoptr(XbSilo) silo = NULL;
|
||||
const gchar *buf =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<firmware>\n"
|
||||
" <version>1.2.3</version>\n"
|
||||
" <image>\n"
|
||||
" <version>4.5.6</version>\n"
|
||||
" <id>header</id>\n"
|
||||
" <idx>456</idx>\n"
|
||||
" <addr>0x456</addr>\n"
|
||||
" <data>aGVsbG8=</data>\n"
|
||||
" </image>\n"
|
||||
" <image>\n"
|
||||
" <version>7.8.9</version>\n"
|
||||
" <id>header</id>\n"
|
||||
" <idx>789</idx>\n"
|
||||
" <addr>0x789</addr>\n"
|
||||
" </image>\n"
|
||||
"</firmware>\n";
|
||||
blob = g_bytes_new_static (buf, strlen (buf));
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (blob);
|
||||
|
||||
/* parse XML */
|
||||
ret = xb_builder_source_load_bytes (source, blob, XB_BUILDER_SOURCE_FLAG_NONE, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
xb_builder_import_source (builder, source);
|
||||
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (silo);
|
||||
n = xb_silo_query_first (silo, "firmware", &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (n);
|
||||
|
||||
/* build object */
|
||||
ret = fu_firmware_build (firmware, n, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
g_assert_cmpstr (fu_firmware_get_version (firmware), ==, "1.2.3");
|
||||
|
||||
/* verify image */
|
||||
img = fu_firmware_get_image_by_id (firmware, "header", &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (img);
|
||||
g_assert_cmpstr (fu_firmware_image_get_version (img), ==, "4.5.6");
|
||||
g_assert_cmpint (fu_firmware_image_get_idx (img), ==, 456);
|
||||
g_assert_cmpint (fu_firmware_image_get_addr (img), ==, 0x456);
|
||||
blob2 = fu_firmware_image_write (img, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (blob2);
|
||||
g_assert_cmpint (g_bytes_get_size (blob2), ==, 5);
|
||||
str = g_strndup (g_bytes_get_data (blob2, NULL), g_bytes_get_size (blob2));
|
||||
g_assert_cmpstr (str, ==, "hello");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_firmware_dfu_func (void)
|
||||
{
|
||||
@ -1976,6 +2045,7 @@ main (int argc, char **argv)
|
||||
g_test_add_func ("/fwupd/smbios3", fu_smbios3_func);
|
||||
g_test_add_func ("/fwupd/firmware", fu_firmware_func);
|
||||
g_test_add_func ("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func);
|
||||
g_test_add_func ("/fwupd/firmware{build}", fu_firmware_build_func);
|
||||
g_test_add_func ("/fwupd/firmware{ihex}", fu_firmware_ihex_func);
|
||||
g_test_add_func ("/fwupd/firmware{ihex-offset}", fu_firmware_ihex_offset_func);
|
||||
g_test_add_func ("/fwupd/firmware{ihex-signed}", fu_firmware_ihex_signed_func);
|
||||
|
@ -618,9 +618,11 @@ LIBFWUPDPLUGIN_1.5.0 {
|
||||
fu_device_report_metadata_post;
|
||||
fu_device_report_metadata_pre;
|
||||
fu_firmware_add_flag;
|
||||
fu_firmware_build;
|
||||
fu_firmware_flag_from_string;
|
||||
fu_firmware_flag_to_string;
|
||||
fu_firmware_has_flag;
|
||||
fu_firmware_image_build;
|
||||
fu_firmware_image_get_filename;
|
||||
fu_firmware_image_parse;
|
||||
fu_firmware_image_set_filename;
|
||||
|
@ -24,6 +24,7 @@ dfu = static_library(
|
||||
dependencies : [
|
||||
giounix,
|
||||
libm,
|
||||
libxmlb,
|
||||
gusb,
|
||||
gudev,
|
||||
],
|
||||
|
@ -76,6 +76,7 @@ if get_option('tests')
|
||||
],
|
||||
dependencies : [
|
||||
gio,
|
||||
libxmlb,
|
||||
],
|
||||
link_with : [
|
||||
fwupd,
|
||||
|
@ -48,6 +48,7 @@ if get_option('tests')
|
||||
],
|
||||
dependencies : [
|
||||
gio,
|
||||
libxmlb,
|
||||
],
|
||||
link_with : [
|
||||
fwupd,
|
||||
|
@ -1639,12 +1639,16 @@ fu_util_get_firmware_types (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
}
|
||||
|
||||
static gchar *
|
||||
fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, GError **error)
|
||||
fu_util_prompt_for_firmware_type (FuUtilPrivate *priv, gboolean add_builder, GError **error)
|
||||
{
|
||||
g_autoptr(GPtrArray) firmware_types = NULL;
|
||||
guint idx;
|
||||
firmware_types = fu_engine_get_firmware_gtype_ids (priv->engine);
|
||||
|
||||
/* add fake entry */
|
||||
if (add_builder)
|
||||
g_ptr_array_add (firmware_types, g_strdup ("builder"));
|
||||
|
||||
/* TRANSLATORS: get interactive prompt */
|
||||
g_print ("%s\n", _("Choose a firmware type:"));
|
||||
/* TRANSLATORS: this is to abort the interactive prompt */
|
||||
@ -1697,7 +1701,7 @@ fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
|
||||
/* find the GType to use */
|
||||
if (firmware_type == NULL)
|
||||
firmware_type = fu_util_prompt_for_firmware_type (priv, error);
|
||||
firmware_type = fu_util_prompt_for_firmware_type (priv, TRUE, error);
|
||||
if (firmware_type == NULL)
|
||||
return FALSE;
|
||||
gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type);
|
||||
@ -1716,6 +1720,53 @@ fu_util_firmware_parse (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static FuFirmware *
|
||||
fu_util_firmware_builder_new (FuUtilPrivate *priv, GBytes *fw, GError **error)
|
||||
{
|
||||
const gchar *tmp;
|
||||
g_autoptr(FuFirmware) firmware = NULL;
|
||||
g_autoptr(XbBuilder) builder = xb_builder_new ();
|
||||
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
|
||||
g_autoptr(XbNode) n = NULL;
|
||||
g_autoptr(XbSilo) silo = NULL;
|
||||
|
||||
/* parse XML */
|
||||
if (!xb_builder_source_load_bytes (source, fw,
|
||||
XB_BUILDER_SOURCE_FLAG_NONE,
|
||||
error)) {
|
||||
g_prefix_error (error, "could not parse XML: ");
|
||||
return NULL;
|
||||
}
|
||||
xb_builder_import_source (builder, source);
|
||||
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
|
||||
if (silo == NULL)
|
||||
return NULL;
|
||||
|
||||
/* create FuFirmware of specific GType */
|
||||
n = xb_silo_query_first (silo, "firmware", error);
|
||||
if (n == NULL)
|
||||
return FALSE;
|
||||
tmp = xb_node_get_attr (n, "gtype");
|
||||
if (tmp != NULL) {
|
||||
GType gtype = fu_engine_get_firmware_gtype_by_id (priv->engine, tmp);
|
||||
if (gtype == G_TYPE_INVALID) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"GType %s not supported", tmp);
|
||||
return NULL;
|
||||
}
|
||||
firmware = g_object_new (gtype, NULL);
|
||||
} else {
|
||||
firmware = fu_firmware_new ();
|
||||
}
|
||||
if (!fu_firmware_build (firmware, n, error))
|
||||
return NULL;
|
||||
|
||||
/* success */
|
||||
return g_steal_pointer (&firmware);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
@ -1756,20 +1807,29 @@ fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
|
||||
/* find the GType to use */
|
||||
if (firmware_type_src == NULL)
|
||||
firmware_type_src = fu_util_prompt_for_firmware_type (priv, error);
|
||||
firmware_type_src = fu_util_prompt_for_firmware_type (priv, TRUE, error);
|
||||
if (firmware_type_src == NULL)
|
||||
return FALSE;
|
||||
if (firmware_type_dst == NULL)
|
||||
firmware_type_dst = fu_util_prompt_for_firmware_type (priv, error);
|
||||
firmware_type_dst = fu_util_prompt_for_firmware_type (priv, FALSE, error);
|
||||
if (firmware_type_dst == NULL)
|
||||
return FALSE;
|
||||
gtype_src = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_src);
|
||||
if (gtype_src == G_TYPE_INVALID) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"GType %s not supported", firmware_type_src);
|
||||
return FALSE;
|
||||
if (g_strcmp0 (firmware_type_src, "builder") == 0) {
|
||||
firmware_src = fu_util_firmware_builder_new (priv, blob_src, error);
|
||||
if (firmware_src == NULL)
|
||||
return FALSE;
|
||||
} else {
|
||||
gtype_src = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_src);
|
||||
if (gtype_src == G_TYPE_INVALID) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_FOUND,
|
||||
"GType %s not supported", firmware_type_src);
|
||||
return FALSE;
|
||||
}
|
||||
firmware_src = g_object_new (gtype_src, NULL);
|
||||
if (!fu_firmware_parse (firmware_src, blob_src, priv->flags, error))
|
||||
return FALSE;
|
||||
}
|
||||
gtype_dst = fu_engine_get_firmware_gtype_by_id (priv->engine, firmware_type_dst);
|
||||
if (gtype_dst == G_TYPE_INVALID) {
|
||||
@ -1779,9 +1839,6 @@ fu_util_firmware_convert (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
"GType %s not supported", firmware_type_dst);
|
||||
return FALSE;
|
||||
}
|
||||
firmware_src = g_object_new (gtype_src, NULL);
|
||||
if (!fu_firmware_parse (firmware_src, blob_src, priv->flags, error))
|
||||
return FALSE;
|
||||
str_src = fu_firmware_to_string (firmware_src);
|
||||
g_print ("%s", str_src);
|
||||
|
||||
|
@ -345,6 +345,7 @@ if get_option('tests')
|
||||
fwupdplugin_incdir,
|
||||
],
|
||||
dependencies : [
|
||||
libxmlb,
|
||||
gio,
|
||||
],
|
||||
link_with : [
|
||||
|
Loading…
Reference in New Issue
Block a user