[[RFC PATCH] v2 07/14] Add generic support for platform updates
Samuel Mendoza-Jonas
sam at mendozajonas.com
Thu Jan 18 16:05:10 AEDT 2018
This adds support for updating platform firmware from within Petitboot
based on downloaded version metadata.
The process involves
- collecting a 'metadata' file which details the version of the new
update and the update's location.
- comparing the new version metadata to the known versions of the
current firmware.
- downloading the update described in the metadata file.
- passing the update file to platform-specific code to perform the
update.
Update progress is also provided by platform-specific code, which uses
a status update callback provided by device-handler.
Since the update is run asynchronously, several actions are blocked from
occurring during an update, particularly reinit and booting.
Signed-off-by: Samuel Mendoza-Jonas <sam at mendozajonas.com>
---
discover/device-handler.c | 216 +++++++++++++++++++++++++++++++++++++++++-
discover/device-handler.h | 2 +
discover/discover-server.c | 9 ++
discover/sysinfo.c | 22 +++++
discover/sysinfo.h | 5 +
lib/pb-config/pb-config.c | 8 ++
lib/pb-protocol/pb-protocol.c | 39 ++++++++
lib/pb-protocol/pb-protocol.h | 2 +
lib/types/types.c | 16 ++++
lib/types/types.h | 15 +++
test/parser/handler.c | 10 ++
11 files changed, 341 insertions(+), 3 deletions(-)
diff --git a/discover/device-handler.c b/discover/device-handler.c
index 7a570f9..4d45e27 100644
--- a/discover/device-handler.c
+++ b/discover/device-handler.c
@@ -19,6 +19,8 @@
#include <process/process.h>
#include <url/url.h>
#include <i18n/i18n.h>
+#include <version/version.h>
+#include <file/file.h>
#include <pb-config/pb-config.h>
#include <sys/sysmacros.h>
@@ -100,6 +102,9 @@ static void device_handler_reinit_sources(struct device_handler *handler);
static void device_handler_update_lang(const char *lang);
+static int device_handler_load_update_data(struct device_handler *handler,
+ const char *url);
+
void discover_context_add_boot_option(struct discover_context *ctx,
struct discover_boot_option *boot_option)
{
@@ -359,6 +364,7 @@ static void set_env_variables(const struct config *config)
struct device_handler *device_handler_init(struct discover_server *server,
struct waitset *waitset, int dry_run)
{
+ const struct config *config = config_get();
struct device_handler *handler;
int rc;
@@ -366,7 +372,7 @@ struct device_handler *device_handler_init(struct discover_server *server,
handler->server = server;
handler->waitset = waitset;
handler->dry_run = dry_run;
- handler->autoboot_enabled = config_autoboot_active(config_get());
+ handler->autoboot_enabled = config_autoboot_active(config);
list_init(&handler->unresolved_boot_options);
@@ -377,7 +383,7 @@ struct device_handler *device_handler_init(struct discover_server *server,
parser_init();
- if (config_get()->safe_mode)
+ if (config->safe_mode)
return handler;
set_env_variables(config_get());
@@ -388,6 +394,9 @@ struct device_handler *device_handler_init(struct discover_server *server,
return NULL;
}
+ if (config->metadata_source)
+ device_handler_load_update_data(handler, config->metadata_source);
+
return handler;
}
@@ -406,6 +415,11 @@ void device_handler_reinit(struct device_handler *handler)
handler->pending_boot_is_default = false;
}
+ if (system_info_update_status() == UPDATE_RUNNING) {
+ pb_log("%s: Update Pending\n", __func__);
+ return;
+ }
+
/* Cancel any remaining async jobs */
process_stop_async_all();
pending_network_jobs_cancel();
@@ -614,7 +628,7 @@ void device_handler_status_download(struct device_handler *handler,
}
if (i >= strlen(units)) {
- pb_log("Couldn't recognise suffix '%c'\n", suffix);
+ pb_log("%s: Couldn't recognise suffix '%c'\n", __func__, suffix);
size_bytes = 0;
} else {
while (i--)
@@ -691,6 +705,7 @@ void device_handler_status_download_remove(struct device_handler *handler,
list_for_each_entry_safe(&handler->progress, p, tmp, list)
if (p->procinfo == procinfo) {
+ pb_debug("progress_info member removed\n");
list_remove(&p->list);
talloc_free(p);
handler->n_progress--;
@@ -1207,6 +1222,11 @@ void device_handler_boot(struct device_handler *handler,
{
struct discover_boot_option *opt = NULL;
+ if (system_info_update_status() == UPDATE_RUNNING) {
+ pb_log("%s: Update Pending\n", __func__);
+ return;
+ }
+
if (cmd->option_id && strlen(cmd->option_id))
opt = find_boot_option_by_id(handler, cmd->option_id);
@@ -1250,6 +1270,11 @@ void device_handler_update_config(struct device_handler *handler,
{
int rc;
+ if (system_info_update_status() == UPDATE_RUNNING) {
+ pb_log("%s: Update Pending\n", __func__);
+ return;
+ }
+
rc = config_set(config);
if (rc)
return;
@@ -1257,6 +1282,8 @@ void device_handler_update_config(struct device_handler *handler,
discover_server_notify_config(handler->server, config);
device_handler_update_lang(config->lang);
device_handler_reinit(handler);
+ device_handler_load_update_data(handler, config->metadata_source);
+
}
static char *device_from_addr(void *ctx, struct pb_url *url)
@@ -1495,6 +1522,93 @@ void device_handler_install_plugin(struct device_handler *handler,
#ifndef PETITBOOT_TEST
+static void platform_update_status_cb(void *arg, struct status *status,
+ enum update_status state)
+{
+ if (status && arg)
+ device_handler_status(arg, status);
+ system_info_register_update_status(state);
+}
+
+/* Check load success before passing to platform-specific code */
+static void device_handler_firmware_update_cb(struct load_url_result *result,
+ void *data)
+{
+ struct device_handler *handler = data;
+ int rc;
+
+ if (!result || result->status != LOAD_OK) {
+ device_handler_status_err(handler,
+ _("Failed to retrieve firmware update"));
+ return;
+ }
+
+ device_handler_status_info(handler,
+ _("Platform update starting - please wait"));
+
+ rc = platform_update(result->url, result->local,
+ platform_update_status_cb, handler, system_info_get());
+
+ if (rc) {
+ device_handler_status_err(handler,
+ _("Platform update failed - see log"));
+ system_info_register_update_status(UPDATE_ERROR);
+ } else {
+ device_handler_status_info(handler,
+ _("Platform update running.."));
+ system_info_register_update_status(UPDATE_RUNNING);
+ }
+
+ talloc_free(result->url);
+}
+
+void device_handler_firmware_update(struct device_handler *handler,
+ const char *url)
+{
+ const struct platform *platform = platform_get();
+ const struct config *config = config_get();
+ const struct system_info *info = system_info_get();
+ struct pb_url *pb_url;
+
+ if (!platform || !platform->update_platform) {
+ pb_log("Platform does not support firmware updates\n");
+ return;
+ }
+
+ if (!info || !info->update_support) {
+ pb_log("Platform update method not specified\n");
+ return;
+ }
+
+ if (!config->platform_update) {
+ pb_log("Platform updates not enabled\n");
+ return;
+ }
+
+ if (system_info_update_status() == UPDATE_RUNNING) {
+ pb_log("Update already running!\n");
+ device_handler_status_err(handler, _("Update already running!"));
+ return;
+ }
+
+ pb_url = pb_url_parse(handler, url);
+ if (!pb_url) {
+ device_handler_status_err(handler,
+ _("Failed to parse firmware update URL"));
+ return;
+ }
+
+ if (!pb_url->file) {
+ device_handler_status_err(handler,
+ _("URL doesn't specify firmware image"));
+ return;
+ }
+
+ load_url_async(handler, pb_url, device_handler_firmware_update_cb,
+ handler, NULL, handler);
+}
+
+
/**
* context_commit - Commit a temporary discovery context to the handler,
* and notify the clients about any new options / devices
@@ -1971,6 +2085,89 @@ void device_sync_snapshots(struct device_handler *handler, const char *device)
}
}
+static void device_handler_load_update_data_cb(struct load_url_result *result,
+ void *data)
+{
+ const struct system_info *info = system_info_get();
+ struct firmware_version *platform_new;
+ struct device_handler *handler = data;
+ char *buf, *update_url = NULL;
+ struct config *config;
+ bool update;
+ int rc, len;
+
+
+ if (result->status != LOAD_OK) {
+ pb_log("Failed to load metadata file\n");
+ return;
+ }
+
+ if (!handler)
+ return;
+
+ rc = read_file(handler, result->local, &buf, &len);
+ if (rc) {
+ pb_log("Failed to read metadata file\n");
+ device_handler_status_err(handler,
+ _("Metadata check failed\n"));
+ return;
+ }
+
+ rc = parse_metadata(handler, buf, len, &platform_new, &update_url);
+ system_info_update_platform_versions_new(platform_new, rc > 0 ? rc : 0);
+
+ if (rc <= 0) {
+ pb_log("Failed to parse metadata information\n");
+ return;
+ }
+
+ update = compare_metadata_versions(handler, info->platform_primary,
+ info->n_primary, info->platform_new, info->n_new);
+ if (update && update_url) {
+ device_handler_status_info(handler, _("Update available!"));
+ config = config_copy(handler, config_get());
+ talloc_free(config->update_source);
+ config->update_source = talloc_strdup(config, update_url);
+ rc = config_set(config);
+ if (!rc)
+ discover_server_notify_config(handler->server, config);
+ talloc_free(update_url);
+ } else if (update)
+ device_handler_status_info(handler,
+ _("Update available but no source specified"));
+ else
+ device_handler_status_info(handler, _("No updates"));
+
+ talloc_free(result->url);
+}
+
+static int device_handler_load_update_data(struct device_handler *handler,
+ const char *url)
+{
+ struct load_url_result *result;
+ struct pb_url *pb_url;
+
+ if (!url)
+ return 0;
+
+ pb_url = pb_url_parse(handler, url);
+ if (!pb_url || !pb_url->file) {
+ pb_log("Metadata url missing file: %s\n", url);
+ if (pb_url)
+ talloc_free(pb_url);
+ return -1;
+ }
+
+ result = load_url_async(handler, pb_url,
+ device_handler_load_update_data_cb,
+ handler, NULL, NULL);
+
+ if (!result)
+ pb_log("Failed to start load for metadata file\n");
+
+ return result ? 0 : 1;
+}
+
#else
void device_handler_discover_context_commit(
@@ -2024,4 +2221,17 @@ void device_sync_snapshots(
{
}
+static int device_handler_load_update_data(
+ struct device_handler *handler __attribute__((unused)),
+ const char *url __attribute__((unused)))
+{
+ return 0;
+}
+
+void device_handler_firmware_update(
+ struct device_handler *handler __attribute__((unused)),
+ const char *url __attribute__((unused)))
+{
+}
+
#endif
diff --git a/discover/device-handler.h b/discover/device-handler.h
index 771cd06..c1b5091 100644
--- a/discover/device-handler.h
+++ b/discover/device-handler.h
@@ -162,6 +162,8 @@ void device_handler_process_url(struct device_handler *handler,
const char *url, const char *mac, const char *ip);
void device_handler_install_plugin(struct device_handler *handler,
const char *plugin_file);
+void device_handler_firmware_update(struct device_handler *handler,
+ const char *url);
void device_handler_reinit(struct device_handler *handler);
int device_request_write(struct discover_device *dev, bool *release);
diff --git a/discover/discover-server.c b/discover/discover-server.c
index 57cf3b7..fbd742c 100644
--- a/discover/discover-server.c
+++ b/discover/discover-server.c
@@ -310,6 +310,15 @@ static int discover_server_process_message(void *arg)
device_handler_install_plugin(client->server->device_handler,
url);
break;
+
+ case PB_PROTOCOL_ACTION_FIRMWARE_UPDATE:
+ url = pb_protocol_deserialise_string((void *) client, message);
+
+ device_handler_firmware_update(client->server->device_handler,
+ url);
+
+ break;
+
default:
pb_log("%s: invalid action %d\n", __func__, message->action);
return 0;
diff --git a/discover/sysinfo.c b/discover/sysinfo.c
index 74d1eae..b72d750 100644
--- a/discover/sysinfo.c
+++ b/discover/sysinfo.c
@@ -142,6 +142,28 @@ void system_info_register_blockdev(const char *name, const char *uuid,
discover_server_notify_system_info(server, sysinfo);
}
+enum update_status system_info_update_status(void)
+{
+ return sysinfo->update_status;
+}
+
+void system_info_register_update_status(enum update_status status)
+{
+ sysinfo->update_status = status;
+ discover_server_notify_system_info(server, sysinfo);
+}
+
+void system_info_update_platform_versions_new(
+ struct firmware_version *platform_new, unsigned int n_new)
+{
+ if (sysinfo->n_new)
+ talloc_free(sysinfo->platform_new);
+ sysinfo->platform_new = platform_new;
+ sysinfo->n_new = n_new;
+
+ discover_server_notify_system_info(server, sysinfo);
+}
+
void system_info_init(struct discover_server *s)
{
server = s;
diff --git a/discover/sysinfo.h b/discover/sysinfo.h
index 835dfbe..8208626 100644
--- a/discover/sysinfo.h
+++ b/discover/sysinfo.h
@@ -14,9 +14,14 @@ void system_info_register_interface(unsigned int hwaddr_size, uint8_t *hwaddr,
const char *name, bool link);
void system_info_register_blockdev(const char *name, const char *uuid,
const char *mountpoint);
+void system_info_register_update_status(enum update_status status);
+void system_info_update_platform_versions_new(
+ struct firmware_version *platform_new, unsigned int n_new);
void system_info_init(struct discover_server *server);
void system_info_reinit(void);
+enum update_status system_info_update_status(void);
+
#endif /* SYSINFO_H */
diff --git a/lib/pb-config/pb-config.c b/lib/pb-config/pb-config.c
index 7fa925c..3500eb6 100644
--- a/lib/pb-config/pb-config.c
+++ b/lib/pb-config/pb-config.c
@@ -102,5 +102,13 @@ struct config *config_copy(void *ctx, const struct config *src)
else
dest->lang = NULL;
+ if (src->metadata_source)
+ dest->metadata_source = talloc_strdup(dest, src->metadata_source);
+
+ if (src->update_source)
+ dest->update_source = talloc_strdup(dest, src->update_source);
+
+ dest->platform_update = src->platform_update;
+
return dest;
}
diff --git a/lib/pb-protocol/pb-protocol.c b/lib/pb-protocol/pb-protocol.c
index 76a2665..cf4ddb4 100644
--- a/lib/pb-protocol/pb-protocol.c
+++ b/lib/pb-protocol/pb-protocol.c
@@ -281,6 +281,9 @@ int pb_protocol_system_info_len(const struct system_info *sysinfo)
4 + optional_strlen(bd_info->mountpoint);
}
+ len += 4 /* update_support */;
+ len += 4 /* update_status */;
+
/* BMC MAC */
len += HWADDR_SIZE;
@@ -351,6 +354,9 @@ int pb_protocol_config_len(const struct config *config)
len += 4; /* manual_console */
len += 4 + optional_strlen(config->lang);
+ len += 4 + optional_strlen(config->metadata_source);
+ len += 4 + optional_strlen(config->update_source);
+ len += 4; /* platform_update */
return len;
}
@@ -551,6 +557,13 @@ int pb_protocol_serialise_system_info(const struct system_info *sysinfo,
memset(pos, 0, HWADDR_SIZE);
pos += HWADDR_SIZE;
+ *(bool *)pos = __cpu_to_be32(sysinfo->update_support);
+ pos += sizeof(bool);
+
+ *(enum update_status *)pos = sysinfo->update_status;
+ pos += sizeof(enum device_type);
+
+
assert(pos <= buf + buf_len);
(void)buf_len;
@@ -657,6 +670,12 @@ int pb_protocol_serialise_config(const struct config *config,
pos += pb_protocol_serialise_string(pos, config->lang);
+ pos += pb_protocol_serialise_string(pos, config->metadata_source);
+ pos += pb_protocol_serialise_string(pos, config->update_source);
+
+ *(uint32_t *)pos = config->platform_update;
+ pos += 4;
+
assert(pos <= buf + buf_len);
(void)buf_len;
@@ -1112,6 +1131,12 @@ int pb_protocol_deserialise_system_info(struct system_info *sysinfo,
pos += HWADDR_SIZE;
len -= HWADDR_SIZE;
+ sysinfo->update_support = *(bool *)pos;
+ pos += sizeof(sysinfo->update_support);
+
+ sysinfo->update_status = *(enum update_status *)(pos);
+ pos += sizeof(enum device_type);
+
rc = 0;
out:
return rc;
@@ -1266,6 +1291,20 @@ int pb_protocol_deserialise_config(struct config *config,
config->lang = str;
+ if (read_string(config, &pos, &len, &str))
+ goto out;
+
+ config->metadata_source = str;
+
+ if (read_string(config, &pos, &len, &str))
+ goto out;
+
+ config->update_source = str;
+
+ if (read_u32(&pos, &len, &tmp))
+ goto out;
+ config->platform_update = !!tmp;
+
rc = 0;
out:
diff --git a/lib/pb-protocol/pb-protocol.h b/lib/pb-protocol/pb-protocol.h
index 250c2d1..2b827d2 100644
--- a/lib/pb-protocol/pb-protocol.h
+++ b/lib/pb-protocol/pb-protocol.h
@@ -26,6 +26,8 @@ enum pb_protocol_action {
PB_PROTOCOL_ACTION_PLUGIN_OPTION_ADD = 0xc,
PB_PROTOCOL_ACTION_PLUGINS_REMOVE = 0xd,
PB_PROTOCOL_ACTION_PLUGIN_INSTALL = 0xe,
+ PB_PROTOCOL_ACTION_FIRMWARE_UPDATE = 0xf,
+ PB_PROTOCOL_ACTION_FIRMWARE_COMPLETE = 0x10,
};
struct pb_protocol_message {
diff --git a/lib/types/types.c b/lib/types/types.c
index d7f4ead..b61c033 100644
--- a/lib/types/types.c
+++ b/lib/types/types.c
@@ -87,3 +87,19 @@ bool config_autoboot_active(const struct config *config)
return true;
}
+
+const char *update_status_display_name(enum update_status status)
+{
+ switch (status) {
+ case UPDATE_NONE:
+ return _("No update running");
+ case UPDATE_RUNNING:
+ return _("Update running");
+ case UPDATE_COMPLETED:
+ return _("Update completed");
+ case UPDATE_ERROR:
+ return _("Error running update");
+ default:
+ return _("Unknown");
+ }
+}
diff --git a/lib/types/types.h b/lib/types/types.h
index f4312e6..03dad2b 100644
--- a/lib/types/types.h
+++ b/lib/types/types.h
@@ -24,10 +24,18 @@ enum ipmi_bootdev {
IPMI_BOOTDEV_INVALID = 0xff,
};
+enum update_status {
+ UPDATE_NONE,
+ UPDATE_RUNNING,
+ UPDATE_COMPLETED,
+ UPDATE_ERROR
+};
+
const char *ipmi_bootdev_display_name(enum ipmi_bootdev bootdev);
const char *device_type_display_name(enum device_type type);
const char *device_type_name(enum device_type type);
enum device_type find_device_type(const char *str);
+const char *update_status_display_name(enum update_status status);
struct device {
char *id;
@@ -143,6 +151,9 @@ struct system_info {
unsigned int n_interfaces;
struct blockdev_info **blockdevs;
unsigned int n_blockdevs;
+
+ bool update_support;
+ enum update_status update_status;
};
#define HWADDR_SIZE 6
@@ -204,12 +215,16 @@ struct config {
bool manual_console;
char *lang;
+ char *metadata_source;
+
/* not user-settable */
unsigned int n_consoles;
char **consoles;
bool disable_snapshots;
bool safe_mode;
bool debug;
+ char *update_source;
+ bool platform_update;
};
bool config_autoboot_active(const struct config *config);
diff --git a/test/parser/handler.c b/test/parser/handler.c
index a9856b4..a2d2c58 100644
--- a/test/parser/handler.c
+++ b/test/parser/handler.c
@@ -62,6 +62,16 @@ void system_info_register_blockdev(const char *name, const char *uuid,
(void)mountpoint;
}
+void system_info_register_update_status(bool complete)
+{
+ (void)complete;
+}
+
+enum update_status system_info_update_status(void)
+{
+ assert(false);
+}
+
void network_register_device(struct network *network,
struct discover_device *dev)
{
--
2.15.1
More information about the Petitboot
mailing list