fwupd/plugins/modem-manager/fu-firehose-updater.c

753 lines
24 KiB
C

/*
* Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es>
* Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd.
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include <sys/ioctl.h>
#include "fu-firehose-updater.h"
/* Maximum amount of non-"response" (e.g. "log") XML messages that can be
* received from the module when expecting a "response". This is just a safe
* upper limit to avoid reading forever. */
#define MAX_RECV_MESSAGES 100
/* When initializing the conversation with the firehose interpreter, the
* first step is to receive and process a bunch of messages sent by the
* module. The initial timeout to receive the first message is longer in case
* the module needs some initialization time itself; all the messages after
* the first one are expected to be received much quicker. The default timeout
* value should not be extremely long because the initialization phase ends
* when we don't receive more messages, so it's expected that the timeout will
* fully elapse after the last message sent by the module. */
#define INITIALIZE_INITIAL_TIMEOUT_MS 3000
#define INITIALIZE_TIMEOUT_MS 250
/* Maximum amount of time to wait for a message from the module. */
#define DEFAULT_RECV_TIMEOUT_MS 15000
/* The first configure attempt sent to the module will include all the defaults
* listed below. If the module replies with a NAK specifying a different
* (shorter) max payload size to use, the second configure attempt will be done
* with that new suggested max payload size value. Only 2 configure attempts are
* therefore expected. */
#define MAX_CONFIGURE_ATTEMPTS 2
/* Defaults for the firehose configuration step. The max payload size to target
* in bytes may end up being a different if the module requests a shorter one.
*/
#define CONFIGURE_MEMORY_NAME "nand"
#define CONFIGURE_VERBOSE 0
#define CONFIGURE_ALWAYS_VALIDATE 0
#define CONFIGURE_MAX_DIGEST_TABLE_SIZE_IN_BYTES 2048
#define CONFIGURE_MAX_PAYLOAD_SIZE_TO_TARGET_IN_BYTES 8192
#define CONFIGURE_ZLP_AWARE_HOST 1
#define CONFIGURE_SKIP_STORAGE_INIT 0
enum {
SIGNAL_WRITE_PERCENTAGE,
SIGNAL_LAST
};
static guint signals [SIGNAL_LAST] = { 0 };
struct _FuFirehoseUpdater {
GObject parent_instance;
gchar *port;
FuIOChannel *io_channel;
};
G_DEFINE_TYPE (FuFirehoseUpdater, fu_firehose_updater, G_TYPE_OBJECT)
static void
fu_firehose_updater_log_message (const gchar *action, GBytes *msg)
{
const gchar *msg_data;
gsize msg_size;
g_autofree gchar *msg_strsafe = NULL;
if (g_getenv ("FWUPD_MODEM_MANAGER_VERBOSE") == NULL)
return;
msg_data = (const gchar *) g_bytes_get_data (msg, &msg_size);
if (msg_size > G_MAXINT)
return;
msg_strsafe = fu_common_strsafe (msg_data, msg_size);
g_debug ("%s: %.*s", action, (gint) msg_size, msg_strsafe);
}
static gboolean
validate_program_action (XbNode *program, FuArchive *archive, GError **error)
{
const gchar *filename_attr;
GBytes *file;
gsize file_size;
guint64 computed_num_partition_sectors;
guint64 num_partition_sectors;
guint64 sector_size_in_bytes;
filename_attr = xb_node_get_attr (program, "filename");
if (filename_attr == NULL) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Missing 'filename' attribute in 'program' action");
return FALSE;
}
/* contents of the CAB file are flat, no subdirectories; look for the
* exact filename */
file = fu_archive_lookup_by_fn (archive, filename_attr, error);
if (file == NULL)
return FALSE;
file_size = g_bytes_get_size (file);
num_partition_sectors = xb_node_get_attr_as_uint (program, "num_partition_sectors");
if (num_partition_sectors == G_MAXUINT64) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Missing 'num_partition_sectors' attribute in 'program' action for filename '%s'",
filename_attr);
return FALSE;
}
sector_size_in_bytes = xb_node_get_attr_as_uint (program, "SECTOR_SIZE_IN_BYTES");
if (sector_size_in_bytes == G_MAXUINT64) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Missing 'SECTOR_SIZE_IN_BYTES' attribute in 'program' action for filename '%s'",
filename_attr);
return FALSE;
}
computed_num_partition_sectors = file_size / sector_size_in_bytes;
if ((file_size % sector_size_in_bytes) != 0)
computed_num_partition_sectors++;
if (computed_num_partition_sectors != num_partition_sectors) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Invalid 'num_partition_sectors' in 'program' action for filename '%s': "
"expected %" G_GUINT64_FORMAT " instead of %" G_GUINT64_FORMAT " bytes",
filename_attr, computed_num_partition_sectors, num_partition_sectors);
return FALSE;
}
xb_node_set_data (program, "fwupd:ProgramFile", file);
return TRUE;
}
gboolean
fu_firehose_validate_rawprogram (GBytes *rawprogram, FuArchive *archive,
XbSilo **out_silo, GPtrArray **out_action_nodes, GError **error)
{
g_autoptr(XbBuilder) builder = xb_builder_new ();
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
g_autoptr(XbSilo) silo = NULL;
g_autoptr(XbNode) data_node = NULL;
g_autoptr(GPtrArray) action_nodes = NULL;
if (!xb_builder_source_load_bytes (source, rawprogram, XB_BUILDER_SOURCE_FLAG_NONE, error))
return FALSE;
xb_builder_import_source (builder, source);
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
if (silo == NULL)
return FALSE;
data_node = xb_silo_get_root (silo);
action_nodes = xb_node_get_children (data_node);
if (action_nodes == NULL || action_nodes->len == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No actions given");
return FALSE;
}
for (guint i = 0; i < action_nodes->len; i++) {
XbNode *n = g_ptr_array_index (action_nodes, i);
if ((g_strcmp0 (xb_node_get_element (n), "program") == 0) &&
!validate_program_action (n, archive, error)) {
return FALSE;
}
}
*out_silo = g_steal_pointer (&silo);
*out_action_nodes = g_steal_pointer (&action_nodes);
return TRUE;
}
gboolean
fu_firehose_updater_open (FuFirehoseUpdater *self, GError **error)
{
g_debug ("opening firehose port...");
self->io_channel = fu_io_channel_new_file (self->port, error);
return (self->io_channel != NULL);
}
gboolean
fu_firehose_updater_close (FuFirehoseUpdater *self, GError **error)
{
g_debug ("closing firehose port...");
if (!fu_io_channel_shutdown (self->io_channel, error))
return FALSE;
g_clear_object (&self->io_channel);
return TRUE;
}
static gboolean
fu_firehose_updater_check_operation_result (XbNode *node, gboolean *out_rawmode)
{
g_warn_if_fail (g_strcmp0 (xb_node_get_element (node), "response") == 0);
if (g_strcmp0 (xb_node_get_attr (node, "value"), "ACK") != 0)
return FALSE;
if (out_rawmode)
*out_rawmode = (g_strcmp0 (xb_node_get_attr (node, "rawmode"), "true") == 0);
return TRUE;
}
static gboolean
fu_firehose_updater_process_response (GBytes *rsp_bytes, XbSilo **out_silo,
XbNode **out_response_node, GError **error)
{
g_autoptr(XbBuilder) builder = xb_builder_new ();
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
g_autoptr(XbSilo) silo = NULL;
g_autoptr(XbNode) data_node = NULL;
g_autoptr(GPtrArray) action_nodes = NULL;
if (!xb_builder_source_load_bytes (source, rsp_bytes, XB_BUILDER_SOURCE_FLAG_NONE, error))
return FALSE;
xb_builder_import_source (builder, source);
silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
if (silo == NULL)
return FALSE;
data_node = xb_silo_get_root (silo);
if (data_node == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Missing root data node");
return FALSE;
}
action_nodes = xb_node_get_children (data_node);
if (action_nodes != NULL) {
for (guint j = 0; j < action_nodes->len; j++) {
XbNode *node = g_ptr_array_index (action_nodes, j);
if (g_strcmp0 (xb_node_get_element (node), "response") == 0) {
if (out_silo)
*out_silo = g_steal_pointer (&silo);
if (out_response_node)
*out_response_node = g_object_ref (node);
return TRUE;
}
if (g_strcmp0 (xb_node_get_element (node), "log") == 0) {
const gchar *value_attr = xb_node_get_attr (node, "value");
if (value_attr)
g_debug ("device log: %s", value_attr);
}
}
}
if (out_silo != NULL)
*out_silo = NULL;
if (out_response_node != NULL)
*out_response_node = NULL;
return TRUE;
}
static gboolean
fu_firehose_updater_send_and_receive (FuFirehoseUpdater *self, GByteArray *take_cmd_bytearray,
XbSilo **out_silo, XbNode **out_response_node, GError **error)
{
if (take_cmd_bytearray) {
const gchar *cmd_header = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<data>\n";
const gchar *cmd_trailer = "</data>";
g_autoptr(GBytes) cmd_bytes = NULL;
g_byte_array_prepend (take_cmd_bytearray, (const guint8 *)cmd_header, strlen (cmd_header));
g_byte_array_append (take_cmd_bytearray, (const guint8 *)cmd_trailer, strlen (cmd_trailer));
cmd_bytes = g_byte_array_free_to_bytes (take_cmd_bytearray);
fu_firehose_updater_log_message ("writing", cmd_bytes);
if (!fu_io_channel_write_bytes (self->io_channel, cmd_bytes, 1500,
FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) {
g_prefix_error (error, "Failed to write command: ");
return FALSE;
}
}
for (guint i = 0; i < MAX_RECV_MESSAGES; i++) {
g_autoptr(GBytes) rsp_bytes = NULL;
g_autoptr(XbSilo) silo = NULL;
g_autoptr(XbNode) response_node = NULL;
rsp_bytes = fu_io_channel_read_bytes (self->io_channel, -1, DEFAULT_RECV_TIMEOUT_MS,
FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error);
if (rsp_bytes == NULL) {
g_prefix_error (error, "Failed to read XML message: ");
return FALSE;
}
fu_firehose_updater_log_message ("reading", rsp_bytes);
if (!fu_firehose_updater_process_response (rsp_bytes, &silo, &response_node, error)) {
g_prefix_error (error, "Failed to parse XML message: ");
return FALSE;
}
if (silo != NULL && response_node != NULL) {
*out_silo = g_steal_pointer (&silo);
*out_response_node = g_steal_pointer (&response_node);
return TRUE;
}
/* continue until we get a 'response_node' */
}
g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
"Didn't get any response in the last %d messages", MAX_RECV_MESSAGES);
return FALSE;
}
static gboolean
fu_firehose_updater_initialize (FuFirehoseUpdater *self, GError **error)
{
guint n_msg = 0;
for (guint i = 0; i < MAX_RECV_MESSAGES; i++) {
g_autoptr(GBytes) rsp_bytes = NULL;
rsp_bytes = fu_io_channel_read_bytes (self->io_channel, -1,
(i == 0 ? INITIALIZE_INITIAL_TIMEOUT_MS : INITIALIZE_TIMEOUT_MS),
FU_IO_CHANNEL_FLAG_SINGLE_SHOT, NULL);
if (rsp_bytes == NULL)
break;
fu_firehose_updater_log_message ("reading", rsp_bytes);
if (!fu_firehose_updater_process_response (rsp_bytes, NULL, NULL, error)) {
g_prefix_error (error, "Failed to parse XML message: ");
return FALSE;
}
n_msg++;
}
if (n_msg == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Couldn't read initial firehose messages from device");
return FALSE;
}
return TRUE;
}
static guint
fu_firehose_updater_configure (FuFirehoseUpdater *self, GError **error)
{
gint max_payload_size = CONFIGURE_MAX_PAYLOAD_SIZE_TO_TARGET_IN_BYTES;
for (guint i = 0; i < MAX_CONFIGURE_ATTEMPTS; i++) {
GByteArray *cmd_bytearray = NULL;
g_autoptr(XbSilo) rsp_silo = NULL;
g_autoptr(XbNode) rsp_node = NULL;
GString *cmd_str = g_string_new (NULL);
g_string_append_printf (cmd_str, "<configure");
g_string_append_printf (cmd_str, " MemoryName=\"%s\"",
CONFIGURE_MEMORY_NAME);
g_string_append_printf (cmd_str, " Verbose=\"%d\"",
CONFIGURE_VERBOSE);
g_string_append_printf (cmd_str, " AlwaysValidate=\"%d\"",
CONFIGURE_ALWAYS_VALIDATE);
g_string_append_printf (cmd_str, " MaxDigestTableSizeInBytes=\"%d\"",
CONFIGURE_MAX_DIGEST_TABLE_SIZE_IN_BYTES);
g_string_append_printf (cmd_str, " MaxPayloadSizeToTargetInBytes=\"%d\"",
max_payload_size);
g_string_append_printf (cmd_str, " ZlpAwareHost=\"%d\"",
CONFIGURE_ZLP_AWARE_HOST);
g_string_append_printf (cmd_str, " SkipStorageInit=\"%d\"",
CONFIGURE_SKIP_STORAGE_INIT);
g_string_append_printf (cmd_str, "/>");
cmd_bytearray = g_bytes_unref_to_array (g_string_free_to_bytes (cmd_str));
if (!fu_firehose_updater_send_and_receive (self, cmd_bytearray, &rsp_silo, &rsp_node, error)) {
g_prefix_error (error, "Failed to run configure command: ");
return 0;
}
/* retry if we're told to use a different max payload size */
if (!fu_firehose_updater_check_operation_result (rsp_node, NULL)) {
guint64 suggested_max_payload_size;
g_autoptr(XbNode) root = NULL;
root = xb_silo_get_root (rsp_silo);
suggested_max_payload_size = xb_node_get_attr_as_uint (root, "MaxPayloadSizeToTargetInBytes");
if ((suggested_max_payload_size > G_MAXINT) ||
((gint)suggested_max_payload_size == max_payload_size)) {
break;
}
suggested_max_payload_size = max_payload_size;
continue;
}
/* if operation is successful, return the max payload size we requested */
return max_payload_size;
}
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Configure operation failed");
return 0;
}
static gboolean
fu_firehose_updater_reset (FuFirehoseUpdater *self, GError **error)
{
guint recv_cnt = 20;
const gchar *cmd_str = "<power value=\"reset\" />";
GByteArray *cmd_bytearray = NULL;
g_autoptr(XbSilo) rsp_silo = NULL;
g_autoptr(XbNode) rsp_node = NULL;
cmd_bytearray = g_byte_array_append (g_byte_array_new (), (const guint8 *)cmd_str, strlen (cmd_str));
if (!fu_firehose_updater_send_and_receive (self, cmd_bytearray, &rsp_silo, &rsp_node, error)) {
g_prefix_error (error, "Failed to run reset command: ");
return FALSE;
}
if (!fu_firehose_updater_check_operation_result (rsp_node, NULL)) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Reset operation failed");
return FALSE;
}
/* read out all of the remaining messages. otherwise modem won't go into reset */
while (--recv_cnt && fu_firehose_updater_send_and_receive (self, NULL, &rsp_silo, &rsp_node, NULL));
g_warn_if_fail (recv_cnt > 0);
return TRUE;
}
static gboolean
fu_firehose_updater_send_program_file (FuFirehoseUpdater *self, const gchar *program_filename,
GBytes *program_file, guint payload_size, guint sector_size,
GError **error)
{
g_autoptr(GPtrArray) chunks = NULL;
FuChunk *chk;
chunks = fu_chunk_array_new_from_bytes (program_file, 0, 0, payload_size);
/* last block needs to be padded to the next payload_size,
* so that we always send full sectors */
chk = g_ptr_array_index (chunks, chunks->len - 1);
if (fu_chunk_get_data_sz (chk) != payload_size) {
g_autoptr(GBytes) padded_bytes = NULL;
g_autofree guint8 *padded_block = g_malloc0 (payload_size);
g_return_val_if_fail (padded_block != NULL, FALSE);
memcpy (padded_block, fu_chunk_get_data (chk), fu_chunk_get_data_sz (chk));
padded_bytes = g_bytes_new (padded_block, payload_size);
fu_chunk_set_bytes (chk, padded_bytes);
g_return_val_if_fail (fu_chunk_get_data_sz (chk) == payload_size, FALSE);
}
for (guint i = 0; i < chunks->len; i++) {
chk = g_ptr_array_index (chunks, i);
/* log only in blocks of 250 plus first/last */
if (i == 0 || i == (chunks->len - 1) || (i + 1) % 250 == 0)
g_debug ("sending %u bytes in block %u/%u of file '%s'",
fu_chunk_get_data_sz (chk), i+1, chunks->len, program_filename);
if (!fu_io_channel_write_bytes (self->io_channel, fu_chunk_get_bytes (chk),
1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) {
g_prefix_error (error, "Failed to write block %u/%u of file '%s': ",
i+1, chunks->len, program_filename);
return FALSE;
}
}
return TRUE;
}
static gboolean
fu_firehose_updater_actions_validate (GPtrArray *action_nodes, guint max_payload_size, GError **error)
{
g_return_val_if_fail (action_nodes != NULL, FALSE);
for (guint i = 0; i < action_nodes->len; i++) {
const gchar *name = NULL;
const gchar *program_filename = NULL;
GBytes *program_file = NULL;
guint64 program_sector_size_in_bytes = 0;
XbNode *node = g_ptr_array_index (action_nodes, i);
const gchar *action = xb_node_get_element (node);
if (g_strcmp0 (action, "program") != 0)
continue;
name = "fwupd:ProgramFile";
program_file = xb_node_get_data (node, name);
if (program_file == NULL) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to validate program file '%s' command: "
"failed to get %s", program_filename, name);
return FALSE;
}
name = "filename";
program_filename = xb_node_get_attr (node, name);
if (program_filename == NULL) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to validate program file '%s' command: "
"failed to get %s", program_filename, name);
return FALSE;
}
name = "SECTOR_SIZE_IN_BYTES";
program_sector_size_in_bytes = xb_node_get_attr_as_uint (node, name);
if (program_sector_size_in_bytes > max_payload_size) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to validate program file '%s' command: "
"requested sector size bigger (%" G_GUINT64_FORMAT " bytes) "
"than maximum payload size agreed with device (%u bytes)",
program_filename, program_sector_size_in_bytes, max_payload_size);
return FALSE;
}
}
return TRUE;
}
static gsize
fu_firehose_updater_actions_get_total_file_size (GPtrArray *action_nodes)
{
gsize total_bytes = 0;
g_return_val_if_fail (action_nodes != NULL, 0);
for (guint i = 0; i < action_nodes->len; i++) {
GBytes *program_file = NULL;
XbNode *node = g_ptr_array_index (action_nodes, i);
const gchar *action = xb_node_get_element (node);
if (g_strcmp0 (action, "program") != 0)
continue;
program_file = xb_node_get_data (node, "fwupd:ProgramFile");
if (program_file != NULL)
total_bytes += g_bytes_get_size (program_file);
}
return total_bytes;
}
static gboolean
fu_firehose_updater_run_action_program (FuFirehoseUpdater *self, XbNode *node, gboolean rawmode,
guint max_payload_size, gsize *sent_bytes, GError **error)
{
GBytes *program_file = NULL;
const gchar *program_filename = NULL;
guint64 program_sector_size = 0;
guint payload_size = 0;
g_autoptr(XbSilo) rsp_silo = NULL;
g_autoptr(XbNode) rsp_node = NULL;
program_file = xb_node_get_data (node, "fwupd:ProgramFile");
if (program_file == NULL)
return FALSE;
program_filename = xb_node_get_attr (node, "filename");
if (program_filename == NULL)
return FALSE;
program_sector_size = xb_node_get_attr_as_uint (node, "SECTOR_SIZE_IN_BYTES");
if (program_sector_size == G_MAXUINT64)
return FALSE;
if (rawmode == FALSE) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to download program file '%s': rawmode not enabled",
program_filename);
return FALSE;
}
while ((payload_size + (guint)program_sector_size) < max_payload_size)
payload_size += (guint)program_sector_size;
g_debug ("sending program file '%s' (%zu bytes)", program_filename, g_bytes_get_size (program_file));
if (!fu_firehose_updater_send_program_file (self, program_filename, program_file,
payload_size, program_sector_size, error)) {
g_prefix_error (error, "Failed to send program file '%s': ",
program_filename);
return FALSE;
}
g_debug ("waiting for program file download confirmation...");
if (!fu_firehose_updater_send_and_receive (self, NULL, &rsp_silo, &rsp_node, error)) {
g_prefix_error (error, "Download confirmation not received for file '%s': ",
program_filename);
return FALSE;
}
if (!fu_firehose_updater_check_operation_result (rsp_node, &rawmode)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Download confirmation failed for file '%s'",
program_filename);
return FALSE;
}
if (rawmode != FALSE) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Download confirmation failed for file '%s': rawmode still enabled",
program_filename);
return FALSE;
}
if (sent_bytes != NULL)
*sent_bytes += g_bytes_get_size (program_file);
return TRUE;
}
static gboolean
fu_firehose_updater_run_action (FuFirehoseUpdater *self, XbNode *node, guint max_payload_size,
gsize *sent_bytes, GError **error)
{
const gchar *action;
gchar *cmd_str = NULL;
gboolean rawmode = FALSE;
GByteArray *cmd_bytearray = NULL;
g_autoptr(XbSilo) rsp_silo = NULL;
g_autoptr(XbNode) rsp_node = NULL;
action = xb_node_get_element (node);
#if LIBXMLB_CHECK_VERSION(0,2,2)
cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, error);
#else
cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_NONE, error);
#endif
if (cmd_str == NULL)
return FALSE;
cmd_bytearray = g_byte_array_new_take ((guint8 *)cmd_str, strlen (cmd_str));
g_debug ("running command '%s'...", action);
if (!fu_firehose_updater_send_and_receive (self, cmd_bytearray, &rsp_silo,
&rsp_node, error)) {
g_prefix_error (error, "Failed to run command '%s': ", action);
return FALSE;
}
if (!fu_firehose_updater_check_operation_result (rsp_node, &rawmode)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Command '%s' failed", action);
return FALSE;
}
if (g_strcmp0 (action, "program") == 0)
return fu_firehose_updater_run_action_program (self, node, rawmode,
max_payload_size, sent_bytes, error);
return TRUE;
}
static gboolean
fu_firehose_updater_run_actions (FuFirehoseUpdater *self, XbSilo *silo, GPtrArray *action_nodes,
guint max_payload_size, GError **error)
{
gsize sent_bytes = 0;
gsize total_bytes = 0;
g_warn_if_fail (action_nodes->len > 0);
if (!fu_firehose_updater_actions_validate (action_nodes, max_payload_size, error))
return FALSE;
total_bytes = fu_firehose_updater_actions_get_total_file_size (action_nodes);
for (guint i = 0; i < action_nodes->len; i++) {
XbNode *node = g_ptr_array_index (action_nodes, i);
if (!fu_firehose_updater_run_action (self, node, max_payload_size,
&sent_bytes, error))
return FALSE;
g_signal_emit (self, signals [SIGNAL_WRITE_PERCENTAGE], 0,
(guint)((100.0 * (gdouble)sent_bytes) / (gdouble)total_bytes));
}
return TRUE;
}
gboolean
fu_firehose_updater_write (FuFirehoseUpdater *self, XbSilo *silo, GPtrArray *action_nodes, GError **error)
{
guint max_payload_size;
gboolean result;
g_autoptr(GError) error_local = NULL;
g_signal_emit (self, signals [SIGNAL_WRITE_PERCENTAGE], 0, 0);
if (!fu_firehose_updater_initialize (self, error))
return FALSE;
max_payload_size = fu_firehose_updater_configure (self, error);
if (max_payload_size == 0)
return FALSE;
result = fu_firehose_updater_run_actions (self, silo, action_nodes, max_payload_size, error);
if (!fu_firehose_updater_reset (self, &error_local)) {
if (result)
g_propagate_error (error, g_steal_pointer (&error_local));
return FALSE;
}
g_signal_emit (self, signals [SIGNAL_WRITE_PERCENTAGE], 0, 100);
return result;
}
static void
fu_firehose_updater_init (FuFirehoseUpdater *self)
{
}
static void
fu_firehose_updater_finalize (GObject *object)
{
FuFirehoseUpdater *self = FU_FIREHOSE_UPDATER (object);
g_warn_if_fail (self->io_channel == NULL);
g_free (self->port);
G_OBJECT_CLASS (fu_firehose_updater_parent_class)->finalize (object);
}
static void
fu_firehose_updater_class_init (FuFirehoseUpdaterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_firehose_updater_finalize;
signals [SIGNAL_WRITE_PERCENTAGE] =
g_signal_new ("write-percentage",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
}
FuFirehoseUpdater *
fu_firehose_updater_new (const gchar *port)
{
FuFirehoseUpdater *self = g_object_new (FU_TYPE_FIREHOSE_UPDATER, NULL);
self->port = g_strdup (port);
return self;
}