mirror of
https://git.proxmox.com/git/fwupd
synced 2026-01-07 09:48:40 +00:00
redfish: Refactor plugin and add some unit tests
This commit is contained in:
parent
5458480356
commit
a923ae2804
@ -21,6 +21,10 @@ cp -R ../../../!(build|dist) .
|
||||
popd
|
||||
chown nobody . -R
|
||||
|
||||
# install and run the custom redfish simulator
|
||||
pacman -Syu --noconfirm python-flask
|
||||
../plugins/redfish/tests/redfish.py &
|
||||
|
||||
# install and run TPM simulator necessary for plugins/uefi-capsule/uefi-self-test
|
||||
pacman -Syu --noconfirm swtpm tpm2-tools
|
||||
swtpm socket --tpm2 --server port=2321 --ctrl type=tcp,port=2322 --flags not-need-init --tpmstate "dir=$PWD" &
|
||||
|
||||
@ -23,8 +23,15 @@ This plugin supports the following protocol ID:
|
||||
GUID Generation
|
||||
---------------
|
||||
|
||||
These devices use the provided GUID provided in the `SoftwareId` parameter
|
||||
without modification. Devices without GUIDs are not supported.
|
||||
These devices use the provided GUID provided in the `SoftwareId` property
|
||||
without modification if it is a valid GUID. If the property is not a GUID then
|
||||
the vendor instance ID is used instead:
|
||||
|
||||
* `REDFISH\\VENDOR_${RedfishManufacturer}&SOFTWAREID_${RedfishSoftwareId}`
|
||||
|
||||
Additionally, this Instance ID is added for quirk and parent matching:
|
||||
|
||||
* `REDFISH\VENDOR_${RedfishManufacturer}&ID_${RedfishId}`
|
||||
|
||||
Update Behavior
|
||||
---------------
|
||||
@ -41,7 +48,7 @@ Setting Service IP Manually
|
||||
---------------------------
|
||||
|
||||
The service IP may not be automatically discoverable due to the absence of
|
||||
Type 0x42 entry in SMBIOS. In this case, you have to specify the service IP
|
||||
Type 42 entry in SMBIOS. In this case, you have to specify the service IP
|
||||
to RedfishUri in /etc/fwupd/redfish.conf
|
||||
|
||||
Take HPE Gen10 for example, the service IP can be found with the following
|
||||
|
||||
@ -17,27 +17,20 @@ struct FuPluginData {
|
||||
FuRedfishBackend *backend;
|
||||
};
|
||||
|
||||
gboolean
|
||||
fu_plugin_update (FuPlugin *plugin,
|
||||
FuDevice *device,
|
||||
GBytes *blob_fw,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
|
||||
return fu_redfish_backend_update (data->backend, device, blob_fw, error);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
g_autoptr(GPtrArray) devices = NULL;
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
|
||||
/* get the list of devices */
|
||||
if (!fu_backend_coldplug (FU_BACKEND (data->backend), error))
|
||||
if (!fu_backend_coldplug (FU_BACKEND (data->backend), &error_local)) {
|
||||
if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED))
|
||||
fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_AUTH_REQUIRED);
|
||||
g_propagate_error (error, g_steal_pointer (&error_local));
|
||||
return FALSE;
|
||||
}
|
||||
devices = fu_backend_get_devices (FU_BACKEND (data->backend));
|
||||
for (guint i = 0; i < devices->len; i++) {
|
||||
FuDevice *device = g_ptr_array_index (devices, i);
|
||||
@ -103,14 +96,22 @@ fu_redfish_plugin_discover_smbios_table (FuPlugin *plugin, GError **error)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
FuContext *ctx = fu_plugin_get_context (plugin);
|
||||
const gchar *smbios_data_fn;
|
||||
g_autofree gchar *hostname = NULL;
|
||||
g_autoptr(FuRedfishSmbios) redfish_smbios = fu_redfish_smbios_new ();
|
||||
g_autoptr(GBytes) smbios_data = NULL;
|
||||
|
||||
/* is optional */
|
||||
smbios_data = fu_context_get_smbios_data (ctx, REDFISH_SMBIOS_TABLE_TYPE);
|
||||
if (smbios_data == NULL)
|
||||
return TRUE;
|
||||
/* is optional if not in self tests */
|
||||
smbios_data_fn = g_getenv ("FWUPD_REDFISH_SMBIOS_DATA");
|
||||
if (smbios_data_fn != NULL) {
|
||||
smbios_data = fu_common_get_contents_bytes (smbios_data_fn, error);
|
||||
if (smbios_data == NULL)
|
||||
return FALSE;
|
||||
} else {
|
||||
smbios_data = fu_context_get_smbios_data (ctx, REDFISH_SMBIOS_TABLE_TYPE);
|
||||
if (smbios_data == NULL)
|
||||
return TRUE;
|
||||
}
|
||||
if (!fu_firmware_parse (FU_FIRMWARE (redfish_smbios),
|
||||
smbios_data, FWUPD_INSTALL_FLAG_NONE, error)) {
|
||||
g_prefix_error (error, "failed to parse SMBIOS table entry type 42: ");
|
||||
|
||||
@ -6,163 +6,93 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-redfish-backend.h"
|
||||
#include "fu-redfish-common.h"
|
||||
#include "fu-redfish-multipart-device.h"
|
||||
#include "fu-redfish-legacy-device.h"
|
||||
#include "fu-redfish-request.h"
|
||||
#include "fu-redfish-smbios.h"
|
||||
|
||||
struct _FuRedfishBackend
|
||||
{
|
||||
FuBackend parent_instance;
|
||||
CURL *curl;
|
||||
gchar *hostname;
|
||||
gchar *username;
|
||||
gchar *password;
|
||||
guint port;
|
||||
gchar *update_uri_path;
|
||||
gchar *push_uri_path;
|
||||
gboolean use_https;
|
||||
gboolean cacheck;
|
||||
gint64 max_image_size; /* bytes */
|
||||
GType device_gtype;
|
||||
GHashTable *request_cache; /* str:GByteArray */
|
||||
CURLSH *curlsh;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (FuRedfishBackend, fu_redfish_backend, FU_TYPE_BACKEND)
|
||||
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
typedef gchar curlptr;
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup)
|
||||
#endif
|
||||
|
||||
static size_t
|
||||
fu_redfish_backend_fetch_data_cb (char *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
GByteArray *buf = (GByteArray *) userdata;
|
||||
gsize realsize = size * nmemb;
|
||||
g_byte_array_append (buf, (const guint8 *) ptr, realsize);
|
||||
return realsize;
|
||||
}
|
||||
|
||||
static GBytes *
|
||||
fu_redfish_backend_fetch_data (FuRedfishBackend *self, const gchar *uri_path, GError **error)
|
||||
{
|
||||
CURLcode res;
|
||||
FuRedfishRequest *
|
||||
fu_redfish_backend_request_new (FuRedfishBackend *self)
|
||||
{
|
||||
FuRedfishRequest *request = g_object_new (FU_TYPE_REDFISH_REQUEST, NULL);
|
||||
CURL *curl;
|
||||
CURLU *uri;
|
||||
g_autofree gchar *user_agent = NULL;
|
||||
g_autofree gchar *port = g_strdup_printf ("%u", self->port);
|
||||
g_autoptr(GByteArray) buf = g_byte_array_new ();
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
g_autoptr(CURLU) uri = NULL;
|
||||
#else
|
||||
g_autofree gchar *uri = NULL;
|
||||
#endif
|
||||
|
||||
/* create URI */
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
uri = curl_url ();
|
||||
/* set the cache location */
|
||||
fu_redfish_request_set_cache (request, self->request_cache);
|
||||
fu_redfish_request_set_curlsh (request, self->curlsh);
|
||||
|
||||
/* set up defaults */
|
||||
curl = fu_redfish_request_get_curl (request);
|
||||
uri = fu_redfish_request_get_uri (request);
|
||||
curl_url_set (uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0);
|
||||
curl_url_set (uri, CURLUPART_PATH, uri_path, 0);
|
||||
curl_url_set (uri, CURLUPART_HOST, self->hostname, 0);
|
||||
curl_url_set (uri, CURLUPART_PORT, port, 0);
|
||||
if (curl_easy_setopt (self->curl, CURLOPT_CURLU, uri) != CURLE_OK) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to create message for URI");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
uri = g_strdup_printf ("%s://%s:%s%s",
|
||||
self->use_https ? "https" : "http",
|
||||
self->hostname,
|
||||
port,
|
||||
uri_path);
|
||||
if (curl_easy_setopt (self->curl, CURLOPT_URL, uri) != CURLE_OK) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to create message for URI");
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
curl_easy_setopt (self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_backend_fetch_data_cb);
|
||||
curl_easy_setopt (self->curl, CURLOPT_WRITEDATA, buf);
|
||||
res = curl_easy_perform (self->curl);
|
||||
if (res != CURLE_OK) {
|
||||
glong status_code = 0;
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
g_autoptr(curlptr) uri_str = NULL;
|
||||
#endif
|
||||
curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &status_code);
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
curl_url_get (uri, CURLUPART_URL, &uri_str, 0);
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to download %s: %s",
|
||||
uri_str, curl_easy_strerror (res));
|
||||
#else
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to download %s: %s",
|
||||
uri, curl_easy_strerror (res));
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
curl_easy_setopt (curl, CURLOPT_CURLU, uri);
|
||||
|
||||
return g_byte_array_free_to_bytes (g_steal_pointer (&buf));
|
||||
}
|
||||
/* since DSP0266 makes Basic Authorization a requirement,
|
||||
* it is safe to use Basic Auth for all implementations */
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPAUTH, (glong) CURLAUTH_BASIC);
|
||||
curl_easy_setopt (curl, CURLOPT_USERNAME, self->username);
|
||||
curl_easy_setopt (curl, CURLOPT_PASSWORD, self->password);
|
||||
|
||||
/* setup networking */
|
||||
user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION);
|
||||
curl_easy_setopt (curl, CURLOPT_USERAGENT , user_agent);
|
||||
curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 60L);
|
||||
if (!self->cacheck)
|
||||
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
|
||||
/* success */
|
||||
return request;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_backend_coldplug_member (FuRedfishBackend *self,
|
||||
JsonObject *member,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(FuDevice) dev = fu_device_new ();
|
||||
const gchar *guid = NULL;
|
||||
g_autofree gchar *guid_lower = NULL;
|
||||
g_autofree gchar *id = NULL;
|
||||
g_autoptr(FuDevice) dev = NULL;
|
||||
g_autoptr(FuDeviceLocker) locker = NULL;
|
||||
|
||||
if (json_object_has_member (member, "SoftwareId")) {
|
||||
guid = json_object_get_string_member (member, "SoftwareId");
|
||||
} else if (json_object_has_member (member, "Oem")) {
|
||||
JsonObject *oem = json_object_get_object_member (member, "Oem");
|
||||
if (oem != NULL && json_object_has_member (oem, "Hpe")) {
|
||||
JsonObject *hpe = json_object_get_object_member (oem, "Hpe");
|
||||
if (hpe != NULL && json_object_has_member (hpe, "DeviceClass"))
|
||||
guid = json_object_get_string_member (hpe, "DeviceClass");
|
||||
}
|
||||
}
|
||||
|
||||
/* skip the devices without guid */
|
||||
if (guid == NULL)
|
||||
return TRUE;
|
||||
|
||||
id = g_strdup_printf ("Redfish-Inventory-%s",
|
||||
json_object_get_string_member (member, "Id"));
|
||||
fu_device_set_id (dev, id);
|
||||
fu_device_add_protocol (dev, "org.dmtf.redfish");
|
||||
|
||||
guid_lower = g_ascii_strdown (guid, -1);
|
||||
fu_device_add_guid (dev, guid_lower);
|
||||
if (json_object_has_member (member, "Name"))
|
||||
fu_device_set_name (dev, json_object_get_string_member (member, "Name"));
|
||||
fu_device_set_summary (dev, "Redfish device");
|
||||
if (json_object_has_member (member, "Version"))
|
||||
fu_device_set_version (dev, json_object_get_string_member (member, "Version"));
|
||||
if (json_object_has_member (member, "LowestSupportedVersion"))
|
||||
fu_device_set_version_lowest (dev, json_object_get_string_member (member, "LowestSupportedVersion"));
|
||||
if (json_object_has_member (member, "Description"))
|
||||
fu_device_set_description (dev, json_object_get_string_member (member, "Description"));
|
||||
if (json_object_has_member (member, "Updateable")) {
|
||||
if (json_object_get_boolean_member (member, "Updateable"))
|
||||
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
} else {
|
||||
/* assume the device is updatable */
|
||||
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
}
|
||||
|
||||
/* success */
|
||||
/* create of the correct type */
|
||||
dev = g_object_new (self->device_gtype,
|
||||
"context", fu_backend_get_context (FU_BACKEND (self)),
|
||||
"backend", self,
|
||||
"member", member,
|
||||
NULL);
|
||||
locker = fu_device_locker_new (dev, error);
|
||||
if (locker == NULL)
|
||||
return FALSE;
|
||||
if (self->max_image_size != 0)
|
||||
fu_device_set_firmware_size_max (dev, (guint64) self->max_image_size);
|
||||
fu_backend_device_added (FU_BACKEND (self), dev);
|
||||
return TRUE;
|
||||
}
|
||||
@ -172,16 +102,12 @@ fu_redfish_backend_coldplug_collection (FuRedfishBackend *self,
|
||||
JsonObject *collection,
|
||||
GError **error)
|
||||
{
|
||||
JsonArray *members;
|
||||
JsonNode *node_root;
|
||||
JsonObject *member;
|
||||
|
||||
members = json_object_get_array_member (collection, "Members");
|
||||
JsonArray *members = json_object_get_array_member (collection, "Members");
|
||||
for (guint i = 0; i < json_array_get_length (members); i++) {
|
||||
g_autoptr(JsonParser) parser = json_parser_new ();
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
JsonObject *json_obj;
|
||||
JsonObject *member_id;
|
||||
const gchar *member_uri;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
|
||||
|
||||
member_id = json_array_get_object_element (members, i);
|
||||
member_uri = json_object_get_string_member (member_id, "@odata.id");
|
||||
@ -193,39 +119,15 @@ fu_redfish_backend_coldplug_collection (FuRedfishBackend *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* try to connect */
|
||||
blob = fu_redfish_backend_fetch_data (self, member_uri, error);
|
||||
if (blob == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* get the member object */
|
||||
if (!json_parser_load_from_data (parser,
|
||||
g_bytes_get_data (blob, NULL),
|
||||
(gssize) g_bytes_get_size (blob),
|
||||
error)) {
|
||||
g_prefix_error (error, "failed to parse node: ");
|
||||
return FALSE;
|
||||
}
|
||||
node_root = json_parser_get_root (parser);
|
||||
if (node_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no root node");
|
||||
return FALSE;
|
||||
}
|
||||
member = json_node_get_object (node_root);
|
||||
if (member == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no member object");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Create the device for the member */
|
||||
if (!fu_redfish_backend_coldplug_member (self, member, error))
|
||||
return FALSE;
|
||||
/* create the device for the member */
|
||||
if (!fu_redfish_request_perform (request,
|
||||
member_uri,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
if (!fu_redfish_backend_coldplug_member (self, json_obj, error))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@ -235,11 +137,9 @@ fu_redfish_backend_coldplug_inventory (FuRedfishBackend *self,
|
||||
JsonObject *inventory,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(JsonParser) parser = json_parser_new ();
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
JsonNode *node_root;
|
||||
JsonObject *collection;
|
||||
JsonObject *json_obj;
|
||||
const gchar *collection_uri;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
|
||||
|
||||
if (inventory == NULL) {
|
||||
g_set_error_literal (error,
|
||||
@ -258,47 +158,21 @@ fu_redfish_backend_coldplug_inventory (FuRedfishBackend *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* try to connect */
|
||||
blob = fu_redfish_backend_fetch_data (self, collection_uri, error);
|
||||
if (blob == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* get the inventory object */
|
||||
if (!json_parser_load_from_data (parser,
|
||||
g_bytes_get_data (blob, NULL),
|
||||
(gssize) g_bytes_get_size (blob),
|
||||
error)) {
|
||||
g_prefix_error (error, "failed to parse node: ");
|
||||
return FALSE;
|
||||
}
|
||||
node_root = json_parser_get_root (parser);
|
||||
if (node_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no root node");
|
||||
return FALSE;
|
||||
}
|
||||
collection = json_node_get_object (node_root);
|
||||
if (collection == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no collection object");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return fu_redfish_backend_coldplug_collection (self, collection, error);
|
||||
if (!fu_redfish_request_perform (request,
|
||||
collection_uri,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
return fu_redfish_backend_coldplug_collection (self, json_obj, error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_backend_coldplug (FuBackend *backend, GError **error)
|
||||
{
|
||||
FuRedfishBackend *self = FU_REDFISH_BACKEND (backend);
|
||||
JsonNode *node_root;
|
||||
JsonObject *obj_root = NULL;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
g_autoptr(JsonParser) parser = json_parser_new ();
|
||||
JsonObject *json_obj;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
|
||||
|
||||
/* nothing set */
|
||||
if (self->update_uri_path == NULL) {
|
||||
@ -309,182 +183,72 @@ fu_redfish_backend_coldplug (FuBackend *backend, GError **error)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* try to connect */
|
||||
blob = fu_redfish_backend_fetch_data (self, self->update_uri_path, error);
|
||||
if (blob == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* get the update service */
|
||||
if (!json_parser_load_from_data (parser,
|
||||
g_bytes_get_data (blob, NULL),
|
||||
(gssize) g_bytes_get_size (blob),
|
||||
error)) {
|
||||
g_prefix_error (error, "failed to parse node: ");
|
||||
return FALSE;
|
||||
if (!fu_redfish_request_perform (request,
|
||||
self->update_uri_path,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
if (!json_object_has_member (json_obj, "ServiceEnabled")) {
|
||||
if (!json_object_get_boolean_member (json_obj, "ServiceEnabled")) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"service is not enabled");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
node_root = json_parser_get_root (parser);
|
||||
if (node_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no root node");
|
||||
return FALSE;
|
||||
if (json_object_has_member (json_obj, "MultipartHttpPushUri")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "MultipartHttpPushUri");
|
||||
if (tmp != NULL) {
|
||||
self->device_gtype = FU_TYPE_REDFISH_MULTIPART_DEVICE;
|
||||
self->push_uri_path = g_strdup (tmp);
|
||||
}
|
||||
}
|
||||
obj_root = json_node_get_object (node_root);
|
||||
if (obj_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no root object");
|
||||
return FALSE;
|
||||
if (self->push_uri_path == NULL &&
|
||||
json_object_has_member (json_obj, "HttpPushUri")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "HttpPushUri");
|
||||
if (tmp != NULL) {
|
||||
self->device_gtype = FU_TYPE_REDFISH_LEGACY_DEVICE;
|
||||
self->push_uri_path = g_strdup (tmp);
|
||||
}
|
||||
}
|
||||
if (!json_object_get_boolean_member (obj_root, "ServiceEnabled")) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"service is not enabled");
|
||||
return FALSE;
|
||||
}
|
||||
if (!json_object_has_member (obj_root, "HttpPushUri")) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"HttpPushUri is not available");
|
||||
return FALSE;
|
||||
}
|
||||
self->push_uri_path = g_strdup (json_object_get_string_member (obj_root, "HttpPushUri"));
|
||||
if (self->push_uri_path == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"HttpPushUri is invalid");
|
||||
"HttpPushUri and MultipartHttpPushUri are invalid");
|
||||
return FALSE;
|
||||
}
|
||||
if (json_object_has_member (obj_root, "FirmwareInventory")) {
|
||||
JsonObject *tmp = json_object_get_object_member (obj_root, "FirmwareInventory");
|
||||
if (json_object_has_member (json_obj, "MaxImageSizeBytes")) {
|
||||
self->max_image_size =
|
||||
json_object_get_int_member (json_obj, "MaxImageSizeBytes");
|
||||
}
|
||||
if (json_object_has_member (json_obj, "FirmwareInventory")) {
|
||||
JsonObject *tmp = json_object_get_object_member (json_obj, "FirmwareInventory");
|
||||
return fu_redfish_backend_coldplug_inventory (self, tmp, error);
|
||||
}
|
||||
if (json_object_has_member (obj_root, "SoftwareInventory")) {
|
||||
JsonObject *tmp = json_object_get_object_member (obj_root, "SoftwareInventory");
|
||||
if (json_object_has_member (json_obj, "SoftwareInventory")) {
|
||||
JsonObject *tmp = json_object_get_object_member (json_obj, "SoftwareInventory");
|
||||
return fu_redfish_backend_coldplug_inventory (self, tmp, error);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free)
|
||||
|
||||
gboolean
|
||||
fu_redfish_backend_update (FuRedfishBackend *self, FuDevice *device, GBytes *blob_fw,
|
||||
GError **error)
|
||||
{
|
||||
CURLcode res;
|
||||
FwupdRelease *release;
|
||||
curl_mimepart *part;
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *port = g_strdup_printf ("%u", self->port);
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
g_autoptr(CURLU) uri = curl_url ();
|
||||
#else
|
||||
g_autofree gchar *uri = NULL;
|
||||
#endif
|
||||
g_autoptr(curl_mime) mime = curl_mime_init (self->curl);
|
||||
|
||||
/* Get the update version */
|
||||
release = fwupd_device_get_release_default (FWUPD_DEVICE (device));
|
||||
if (release != NULL) {
|
||||
filename = g_strdup_printf ("%s-%s.bin",
|
||||
fu_device_get_name (device),
|
||||
fwupd_release_get_version (release));
|
||||
} else {
|
||||
filename = g_strdup_printf ("%s.bin",
|
||||
fu_device_get_name (device));
|
||||
}
|
||||
|
||||
/* create URI */
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
curl_url_set (uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0);
|
||||
curl_url_set (uri, CURLUPART_PATH, self->push_uri_path, 0);
|
||||
curl_url_set (uri, CURLUPART_HOST, self->hostname, 0);
|
||||
curl_url_set (uri, CURLUPART_PORT, port, 0);
|
||||
if (curl_easy_setopt (self->curl, CURLOPT_CURLU, uri) != CURLE_OK) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to create message for URI");
|
||||
return FALSE;
|
||||
}
|
||||
#else
|
||||
uri = g_strdup_printf ("%s://%s:%s%s",
|
||||
self->use_https ? "https" : "http",
|
||||
self->hostname,
|
||||
port,
|
||||
self->push_uri_path);
|
||||
if (curl_easy_setopt (self->curl, CURLOPT_URL, uri) != CURLE_OK) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to create message for URI");
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Create the multipart request */
|
||||
curl_easy_setopt (self->curl, CURLOPT_MIMEPOST, mime);
|
||||
part = curl_mime_addpart (mime);
|
||||
curl_mime_data (part, g_bytes_get_data (blob_fw, NULL), g_bytes_get_size (blob_fw));
|
||||
curl_mime_type (part, "application/octet-stream");
|
||||
res = curl_easy_perform (self->curl);
|
||||
if (res != CURLE_OK) {
|
||||
glong status_code = 0;
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
g_autoptr(curlptr) uri_str = NULL;
|
||||
#endif
|
||||
curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &status_code);
|
||||
#ifdef HAVE_LIBCURL_7_62_0
|
||||
curl_url_get (uri, CURLUPART_URL, &uri_str, 0);
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to upload %s to %s: %s",
|
||||
filename, uri_str,
|
||||
curl_easy_strerror (res));
|
||||
return FALSE;
|
||||
#else
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to upload %s to %s: %s",
|
||||
filename, uri,
|
||||
curl_easy_strerror (res));
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_backend_setup (FuBackend *backend, GError **error)
|
||||
{
|
||||
FuRedfishBackend *self = FU_REDFISH_BACKEND (backend);
|
||||
JsonNode *node_root;
|
||||
JsonObject *obj_root = NULL;
|
||||
JsonObject *obj_update_service = NULL;
|
||||
JsonObject *json_obj;
|
||||
JsonObject *json_update_service = NULL;
|
||||
const gchar *data_id;
|
||||
const gchar *version = NULL;
|
||||
g_autofree gchar *user_agent = NULL;
|
||||
g_autoptr(GBytes) blob = NULL;
|
||||
g_autoptr(JsonParser) parser = json_parser_new ();
|
||||
const gchar *uuid = NULL;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (self);
|
||||
|
||||
/* sanity check */
|
||||
if (self->port == 0) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
"no port specified");
|
||||
return FALSE;
|
||||
}
|
||||
if (self->port > 0xffff) {
|
||||
if (self->port == 0 || self->port > G_MAXUINT16) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
@ -493,67 +257,33 @@ fu_redfish_backend_setup (FuBackend *backend, GError **error)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* setup networking */
|
||||
user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION);
|
||||
curl_easy_setopt (self->curl, CURLOPT_USERAGENT , user_agent);
|
||||
curl_easy_setopt (self->curl, CURLOPT_CONNECTTIMEOUT, 60L);
|
||||
if (self->cacheck == FALSE)
|
||||
curl_easy_setopt (self->curl, CURLOPT_SSL_VERIFYPEER , 0L);
|
||||
if (self->hostname != NULL)
|
||||
g_debug ("Hostname: %s", self->hostname);
|
||||
if (self->port != 0)
|
||||
g_debug ("Port: %u", self->port);
|
||||
|
||||
/* try to connect */
|
||||
blob = fu_redfish_backend_fetch_data (self, "/redfish/v1/", error);
|
||||
if (blob == NULL)
|
||||
if (!fu_redfish_request_perform (request,
|
||||
"/redfish/v1/",
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
if (json_object_has_member (json_obj, "ServiceVersion")) {
|
||||
version = json_object_get_string_member (json_obj, "ServiceVersion");
|
||||
} else if (json_object_has_member (json_obj, "RedfishVersion")) {
|
||||
version = json_object_get_string_member (json_obj, "RedfishVersion");
|
||||
}
|
||||
if (json_object_has_member (json_obj, "UUID"))
|
||||
uuid = json_object_get_string_member (json_obj, "UUID");
|
||||
g_debug ("Version: %s", version);
|
||||
g_debug ("UUID: %s", uuid);
|
||||
|
||||
/* get the update service */
|
||||
if (!json_parser_load_from_data (parser,
|
||||
g_bytes_get_data (blob, NULL),
|
||||
(gssize) g_bytes_get_size (blob),
|
||||
error)) {
|
||||
g_prefix_error (error, "failed to parse node: ");
|
||||
return FALSE;
|
||||
}
|
||||
node_root = json_parser_get_root (parser);
|
||||
if (node_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no root node");
|
||||
return FALSE;
|
||||
}
|
||||
obj_root = json_node_get_object (node_root);
|
||||
if (obj_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no root object");
|
||||
return FALSE;
|
||||
}
|
||||
if (json_object_has_member (obj_root, "ServiceVersion")) {
|
||||
version = json_object_get_string_member (obj_root,
|
||||
"ServiceVersion");
|
||||
} else if (json_object_has_member (obj_root, "RedfishVersion")) {
|
||||
version = json_object_get_string_member (obj_root,
|
||||
"RedfishVersion");
|
||||
}
|
||||
g_debug ("Version: %s", version);
|
||||
g_debug ("UUID: %s",
|
||||
json_object_get_string_member (obj_root, "UUID"));
|
||||
|
||||
if (json_object_has_member (obj_root, "UpdateService"))
|
||||
obj_update_service = json_object_get_object_member (obj_root, "UpdateService");
|
||||
if (obj_update_service == NULL) {
|
||||
if (json_object_has_member (json_obj, "UpdateService"))
|
||||
json_update_service = json_object_get_object_member (json_obj, "UpdateService");
|
||||
if (json_update_service == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"no UpdateService object");
|
||||
return FALSE;
|
||||
}
|
||||
data_id = json_object_get_string_member (obj_update_service, "@odata.id");
|
||||
data_id = json_object_get_string_member (json_update_service, "@odata.id");
|
||||
if (data_id == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
@ -593,24 +323,34 @@ fu_redfish_backend_set_cacheck (FuRedfishBackend *self, gboolean cacheck)
|
||||
void
|
||||
fu_redfish_backend_set_username (FuRedfishBackend *self, const gchar *username)
|
||||
{
|
||||
curl_easy_setopt (self->curl, CURLOPT_USERNAME, username);
|
||||
g_free (self->username);
|
||||
self->username = g_strdup (username);
|
||||
}
|
||||
|
||||
void
|
||||
fu_redfish_backend_set_password (FuRedfishBackend *self, const gchar *password)
|
||||
{
|
||||
curl_easy_setopt (self->curl, CURLOPT_PASSWORD, password);
|
||||
g_free (self->password);
|
||||
self->password = g_strdup (password);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
fu_redfish_backend_get_push_uri_path (FuRedfishBackend *self)
|
||||
{
|
||||
return self->push_uri_path;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_backend_finalize (GObject *object)
|
||||
{
|
||||
FuRedfishBackend *self = FU_REDFISH_BACKEND (object);
|
||||
if (self->curl != NULL)
|
||||
curl_easy_cleanup (self->curl);
|
||||
g_hash_table_unref (self->request_cache);
|
||||
curl_share_cleanup (self->curlsh);
|
||||
g_free (self->update_uri_path);
|
||||
g_free (self->push_uri_path);
|
||||
g_free (self->hostname);
|
||||
g_free (self->username);
|
||||
g_free (self->password);
|
||||
G_OBJECT_CLASS (fu_redfish_backend_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@ -627,11 +367,15 @@ fu_redfish_backend_class_init (FuRedfishBackendClass *klass)
|
||||
static void
|
||||
fu_redfish_backend_init (FuRedfishBackend *self)
|
||||
{
|
||||
self->curl = curl_easy_init ();
|
||||
|
||||
/* since DSP0266 makes Basic Authorization a requirement,
|
||||
* it is safe to use Basic Auth for all implementations */
|
||||
curl_easy_setopt (self->curl, CURLOPT_HTTPAUTH, (glong) CURLAUTH_BASIC);
|
||||
self->use_https = TRUE;
|
||||
self->device_gtype = FU_TYPE_REDFISH_DEVICE;
|
||||
self->request_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, (GDestroyNotify) g_byte_array_unref);
|
||||
self->curlsh = curl_share_init ();
|
||||
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
||||
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
||||
curl_share_setopt (self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
||||
}
|
||||
|
||||
FuRedfishBackend *
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-redfish-request.h"
|
||||
|
||||
#define FU_REDFISH_TYPE_BACKEND (fu_redfish_backend_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FuRedfishBackend, fu_redfish_backend, FU, REDFISH_BACKEND, FuBackend)
|
||||
@ -25,7 +27,5 @@ void fu_redfish_backend_set_https (FuRedfishBackend *self,
|
||||
gboolean use_https);
|
||||
void fu_redfish_backend_set_cacheck (FuRedfishBackend *self,
|
||||
gboolean cacheck);
|
||||
gboolean fu_redfish_backend_update (FuRedfishBackend *self,
|
||||
FuDevice *device,
|
||||
GBytes *blob_fw,
|
||||
GError **error);
|
||||
const gchar *fu_redfish_backend_get_push_uri_path (FuRedfishBackend *self);
|
||||
FuRedfishRequest *fu_redfish_backend_request_new (FuRedfishBackend *self);
|
||||
|
||||
@ -48,4 +48,86 @@ fu_redfish_common_buffer_to_mac (const guint8 *buffer)
|
||||
return g_string_free (str, FALSE);
|
||||
}
|
||||
|
||||
/* vim: set noexpandtab: */
|
||||
gchar *
|
||||
fu_redfish_common_fix_version (const gchar *version)
|
||||
{
|
||||
g_auto(GStrv) split = NULL;
|
||||
|
||||
g_return_val_if_fail (version != NULL, NULL);
|
||||
|
||||
/* not valid */
|
||||
if (g_strcmp0 (version, "-*") == 0)
|
||||
return NULL;
|
||||
|
||||
/* find the section preficed with "v" */
|
||||
split = g_strsplit (version, " ", -1);
|
||||
for (guint i = 0; split[i] != NULL; i++) {
|
||||
if (g_str_has_prefix (split[i], "v")) {
|
||||
g_debug ("using %s for %s", split[i] + 1, version);
|
||||
return g_strdup (split[i] + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* find the thing with dots */
|
||||
for (guint i = 0; split[i] != NULL; i++) {
|
||||
if (g_strstr_len (split[i], -1, ".")) {
|
||||
g_debug ("using %s for %s", split[i], version);
|
||||
return g_strdup (split[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* we failed to do anything clever */
|
||||
return g_strdup (version);
|
||||
}
|
||||
|
||||
/* parses a Lenovo XCC-format version like "11A-1.02" */
|
||||
gboolean
|
||||
fu_redfish_common_parse_version_lenovo (const gchar *version,
|
||||
gchar **out_build, /* out */
|
||||
gchar **out_version, /* out */
|
||||
GError **error)
|
||||
{
|
||||
g_auto(GStrv) versplit = g_strsplit (version, "-", -1);
|
||||
|
||||
/* sanity check */
|
||||
if (g_strv_length (versplit) != 2) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"not two sections");
|
||||
return FALSE;
|
||||
}
|
||||
if (strlen (versplit[0]) != 3) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"invalid length first section");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* milestone */
|
||||
if (!g_ascii_isdigit (versplit[0][0]) ||
|
||||
!g_ascii_isdigit (versplit[0][1])) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"milestone number invalid");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* build is only one letter from A -> Z */
|
||||
if (!g_ascii_isalpha (versplit[0][2])) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"build letter invalid");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
if (out_build != NULL)
|
||||
*out_build = g_strdup (versplit[0]);
|
||||
if (out_version != NULL)
|
||||
*out_version = g_strdup (versplit[1]);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -43,3 +43,9 @@
|
||||
gchar *fu_redfish_common_buffer_to_ipv4 (const guint8 *buffer);
|
||||
gchar *fu_redfish_common_buffer_to_ipv6 (const guint8 *buffer);
|
||||
gchar *fu_redfish_common_buffer_to_mac (const guint8 *buffer);
|
||||
|
||||
gchar *fu_redfish_common_fix_version (const gchar *version);
|
||||
gboolean fu_redfish_common_parse_version_lenovo (const gchar *version,
|
||||
gchar **out_build,
|
||||
gchar **out_version,
|
||||
GError **error);
|
||||
|
||||
836
plugins/redfish/fu-redfish-device.c
Normal file
836
plugins/redfish/fu-redfish-device.c
Normal file
@ -0,0 +1,836 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fu-redfish-backend.h"
|
||||
#include "fu-redfish-common.h"
|
||||
#include "fu-redfish-device.h"
|
||||
|
||||
typedef struct {
|
||||
FuRedfishBackend *backend;
|
||||
JsonObject *member;
|
||||
guint64 milestone;
|
||||
gchar *build;
|
||||
} FuRedfishDevicePrivate;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_BACKEND,
|
||||
PROP_MEMBER,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (FuRedfishDevice, fu_redfish_device, FU_TYPE_DEVICE)
|
||||
|
||||
#define GET_PRIVATE(o) (fu_redfish_device_get_instance_private (o))
|
||||
|
||||
/**
|
||||
* FU_REDFISH_DEVICE_FLAG_IS_BACKUP:
|
||||
*
|
||||
* The device is the other half of a dual image firmware.
|
||||
*/
|
||||
#define FU_REDFISH_DEVICE_FLAG_IS_BACKUP (1 << 0)
|
||||
|
||||
/**
|
||||
* FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD:
|
||||
*
|
||||
* Use unsigned development builds.
|
||||
*/
|
||||
#define FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD (1 << 1)
|
||||
|
||||
static void
|
||||
fu_redfish_device_to_string (FuDevice *device, guint idt, GString *str)
|
||||
{
|
||||
FuRedfishDevice *self = FU_REDFISH_DEVICE (device);
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
if (priv->milestone > 0x0)
|
||||
fu_common_string_append_kx (str, idt, "Milestone", priv->milestone);
|
||||
if (priv->build != NULL)
|
||||
fu_common_string_append_kv (str, idt, "Build", priv->build);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_set_device_class (FuRedfishDevice *self, const gchar *tmp)
|
||||
{
|
||||
if (g_strcmp0 (tmp, "NetworkController") == 0) {
|
||||
fu_device_add_icon (FU_DEVICE (self), "network-wired");
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (tmp, "MassStorageController") == 0) {
|
||||
fu_device_add_icon (FU_DEVICE (self), "drive-multidisk");
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (tmp, "DisplayController") == 0) {
|
||||
fu_device_add_icon (FU_DEVICE (self), "video-display");
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (tmp, "DockingStation") == 0) {
|
||||
fu_device_add_icon (FU_DEVICE (self), "dock");
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (tmp, "WirelessController") == 0) {
|
||||
fu_device_add_icon (FU_DEVICE (self), "network-wireless");
|
||||
return;
|
||||
}
|
||||
g_debug ("no icon mapping for %s", tmp);
|
||||
fu_device_add_icon (FU_DEVICE (self), "audio-card");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_device_probe_related_pcie_item (FuRedfishDevice *self,
|
||||
const gchar *uri,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
JsonObject *json_obj;
|
||||
const gchar *subsystem = "PCI";
|
||||
guint64 vendor_id = 0x0;
|
||||
guint64 model_id = 0x0;
|
||||
guint64 subsystem_vendor_id = 0x0;
|
||||
guint64 subsystem_model_id = 0x0;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
|
||||
|
||||
/* get URI */
|
||||
if (!fu_redfish_request_perform (request, uri,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
|
||||
/* optional properties */
|
||||
if (json_object_has_member (json_obj, "DeviceClass")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "DeviceClass");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
fu_redfish_device_set_device_class (self, tmp);
|
||||
}
|
||||
if (json_object_has_member (json_obj, "VendorId")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "VendorId");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
vendor_id = fu_common_strtoull (tmp);
|
||||
}
|
||||
if (json_object_has_member (json_obj, "DeviceId")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "DeviceId");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
model_id = fu_common_strtoull (tmp);
|
||||
}
|
||||
if (json_object_has_member (json_obj, "SubsystemVendorId")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "SubsystemVendorId");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
subsystem_vendor_id = fu_common_strtoull (tmp);
|
||||
}
|
||||
if (json_object_has_member (json_obj, "SubsystemId")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "SubsystemId");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
subsystem_model_id = fu_common_strtoull (tmp);
|
||||
}
|
||||
|
||||
/* add vendor ID */
|
||||
if (vendor_id != 0x0) {
|
||||
g_autofree gchar *vendor_id_str = NULL;
|
||||
vendor_id_str = g_strdup_printf ("PCI:0x%04X", (guint) vendor_id);
|
||||
fu_device_add_vendor_id (FU_DEVICE (self), vendor_id_str);
|
||||
}
|
||||
|
||||
/* add more instance IDs if possible */
|
||||
if (vendor_id != 0x0 && model_id != 0x0) {
|
||||
g_autofree gchar *devid1 = NULL;
|
||||
devid1 = g_strdup_printf ("%s\\VEN_%04X&DEV_%04X",
|
||||
subsystem, (guint) vendor_id, (guint) model_id);
|
||||
fu_device_add_instance_id (FU_DEVICE (self), devid1);
|
||||
}
|
||||
if (vendor_id != 0x0 && model_id != 0x0 &&
|
||||
subsystem_vendor_id != 0x0 && subsystem_model_id != 0x0) {
|
||||
g_autofree gchar *devid2 = NULL;
|
||||
devid2 = g_strdup_printf ("%s\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X",
|
||||
subsystem, (guint) vendor_id, (guint) model_id,
|
||||
(guint) subsystem_vendor_id,
|
||||
(guint) subsystem_model_id);
|
||||
fu_device_add_instance_id (FU_DEVICE (self), devid2);
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_device_probe_related_pcie_functions (FuRedfishDevice *self,
|
||||
const gchar *uri,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
JsonObject *json_obj;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
|
||||
|
||||
/* get URI */
|
||||
if (!fu_redfish_request_perform (request, uri,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
|
||||
if (json_object_has_member (json_obj, "Members")) {
|
||||
JsonArray *members_array = json_object_get_array_member (json_obj, "Members");
|
||||
for (guint i = 0; i < json_array_get_length (members_array); i++) {
|
||||
JsonObject *related_item;
|
||||
related_item = json_array_get_object_element (members_array, i);
|
||||
if (json_object_has_member (related_item, "@odata.id")) {
|
||||
const gchar *id = json_object_get_string_member (related_item, "@odata.id");
|
||||
if (!fu_redfish_device_probe_related_pcie_item (self, id, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_device_probe_related_item (FuRedfishDevice *self,
|
||||
const gchar *uri,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
JsonObject *json_obj;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
|
||||
|
||||
/* get URI */
|
||||
if (!fu_redfish_request_perform (request, uri,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
|
||||
error))
|
||||
return FALSE;
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
|
||||
/* optional properties */
|
||||
if (json_object_has_member (json_obj, "SerialNumber")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "SerialNumber");
|
||||
if (tmp != NULL && tmp[0] != '\0' && g_strcmp0 (tmp, "N/A") != 0)
|
||||
fu_device_set_serial (FU_DEVICE (self), tmp);
|
||||
}
|
||||
|
||||
/* sometimes an array, sometimes an object! */
|
||||
if (json_object_has_member (json_obj, "PCIeFunctions")) {
|
||||
JsonNode *pcie_functions = json_object_get_member (json_obj, "PCIeFunctions");
|
||||
if (JSON_NODE_HOLDS_OBJECT (pcie_functions)) {
|
||||
JsonObject *obj = json_node_get_object (pcie_functions);
|
||||
if (json_object_has_member (obj, "@odata.id")) {
|
||||
const gchar *id = json_object_get_string_member (obj, "@odata.id");
|
||||
if (!fu_redfish_device_probe_related_pcie_functions (self, id, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* parses a Lenovo XCC-format version like "11A-1.02" */
|
||||
static gboolean
|
||||
fu_redfish_device_set_version_lenovo (FuRedfishDevice *self,
|
||||
const gchar *version,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
g_autofree gchar *out_build = NULL;
|
||||
g_autofree gchar *out_version = NULL;
|
||||
|
||||
/* split up Lenovo format */
|
||||
if (!fu_redfish_common_parse_version_lenovo (version,
|
||||
&out_build,
|
||||
&out_version,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* split out milestone */
|
||||
priv->milestone = g_ascii_strtoull (out_build, NULL, 10);
|
||||
if (priv->milestone == 0) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"version milestone invalid");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* odd numbered builds are unsigned */
|
||||
if (priv->milestone % 2 != 0) {
|
||||
fu_device_add_private_flag (FU_DEVICE (self),
|
||||
FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD);
|
||||
}
|
||||
|
||||
/* build is only one letter from A -> Z */
|
||||
if (!g_ascii_isalpha (out_build[2])) {
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"build letter invalid");
|
||||
return FALSE;
|
||||
}
|
||||
priv->build = g_strndup (out_build + 2, 1);
|
||||
fu_device_set_version (FU_DEVICE (self), out_version);
|
||||
fu_device_set_version_format (FU_DEVICE (self),
|
||||
fu_common_version_guess_format (out_version));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_set_version (FuRedfishDevice *self, const gchar *tmp)
|
||||
{
|
||||
/* OEM specific */
|
||||
if (g_strcmp0 (fu_device_get_vendor (FU_DEVICE (self)), "Lenovo") == 0) {
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
if (!fu_redfish_device_set_version_lenovo (self, tmp, &error_local)) {
|
||||
g_debug ("failed to parse Lenovo version %s: %s",
|
||||
tmp, error_local->message);
|
||||
}
|
||||
}
|
||||
|
||||
/* fallback */
|
||||
if (fu_device_get_version (FU_DEVICE (self)) == NULL) {
|
||||
g_autofree gchar *ver = fu_redfish_common_fix_version (tmp);
|
||||
if (ver != NULL) {
|
||||
fu_device_set_version (FU_DEVICE (self), ver);
|
||||
fu_device_set_version_format (FU_DEVICE (self), fu_common_version_guess_format (ver));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_set_version_lowest (FuRedfishDevice *self, const gchar *tmp)
|
||||
{
|
||||
/* OEM specific */
|
||||
if (g_strcmp0 (fu_device_get_vendor (FU_DEVICE (self)), "Lenovo") == 0) {
|
||||
g_autoptr(GError) error_local = NULL;
|
||||
g_autofree gchar *out_version = NULL;
|
||||
if (!fu_redfish_common_parse_version_lenovo (tmp, NULL,
|
||||
&out_version,
|
||||
&error_local)) {
|
||||
g_debug ("failed to parse Lenovo version %s: %s",
|
||||
tmp, error_local->message);
|
||||
}
|
||||
fu_device_set_version_lowest (FU_DEVICE (self), out_version);
|
||||
}
|
||||
|
||||
/* fallback */
|
||||
if (fu_device_get_version_lowest (FU_DEVICE (self)) == NULL) {
|
||||
g_autofree gchar *ver = fu_redfish_common_fix_version (tmp);
|
||||
fu_device_set_version_lowest (FU_DEVICE (self), ver);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_set_name (FuRedfishDevice *self, const gchar *name)
|
||||
{
|
||||
/* useless */
|
||||
if (g_str_has_prefix (name, "Firmware:"))
|
||||
name += 9;
|
||||
|
||||
/* device type */
|
||||
if (g_str_has_prefix (name, "DEVICE-")) {
|
||||
name += 7;
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
|
||||
} else if (g_str_has_prefix (name, "DISK-")) {
|
||||
name += 5;
|
||||
fu_device_add_icon (FU_DEVICE (self), "drive-harddisk");
|
||||
} else if (g_str_has_prefix (name, "POWER-")) {
|
||||
name += 6;
|
||||
fu_device_add_icon (FU_DEVICE (self), "ac-adapter");
|
||||
fu_device_set_summary (FU_DEVICE (self), "Redfish power supply unit");
|
||||
} else {
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
|
||||
}
|
||||
|
||||
/* heuristics */
|
||||
if (g_strcmp0 (name, "BMC") == 0)
|
||||
fu_device_set_summary (FU_DEVICE (self), "Redfish baseboard management controller");
|
||||
if (g_str_has_suffix (name, "HBA") == 0)
|
||||
fu_device_set_summary (FU_DEVICE (self), "Redfish host bus adapter");
|
||||
|
||||
/* success */
|
||||
fu_device_set_name (FU_DEVICE (self), name);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_set_vendor (FuRedfishDevice *self, const gchar *vendor)
|
||||
{
|
||||
g_autofree gchar *vendor_upper = NULL;
|
||||
g_autofree gchar *vendor_id = NULL;
|
||||
|
||||
/* fixup a common mistake */
|
||||
if (g_strcmp0 (vendor, "LEN") == 0 ||
|
||||
g_strcmp0 (vendor, "LNVO") == 0)
|
||||
vendor = "Lenovo";
|
||||
fu_device_set_vendor (FU_DEVICE (self), vendor);
|
||||
|
||||
/* add vendor-id */
|
||||
vendor_upper = g_ascii_strup (vendor, -1);
|
||||
g_strdelimit (vendor_upper, " ", '_');
|
||||
vendor_id = g_strdup_printf ("REDFISH:%s", vendor_upper);
|
||||
fu_device_add_vendor_id (FU_DEVICE (self), vendor_id);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_device_probe (FuDevice *dev, GError **error)
|
||||
{
|
||||
FuRedfishDevice *self = FU_REDFISH_DEVICE (dev);
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
JsonObject *member = priv->member;
|
||||
const gchar *guid = NULL;
|
||||
g_autofree gchar *guid_lower = NULL;
|
||||
|
||||
/* required to POST later */
|
||||
if (!json_object_has_member (member, "@odata.id")) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_FOUND,
|
||||
"no @odata.id string");
|
||||
return FALSE;
|
||||
}
|
||||
fu_device_set_physical_id (dev, "Redfish-Inventory");
|
||||
fu_device_set_logical_id (dev, json_object_get_string_member (member, "@odata.id"));
|
||||
if (json_object_has_member (member, "Id")) {
|
||||
const gchar *tmp = json_object_get_string_member (member, "Id");
|
||||
if (tmp != NULL)
|
||||
fu_device_set_backend_id (dev, tmp);
|
||||
}
|
||||
|
||||
/* get SoftwareId, falling back to vendor-specific versions */
|
||||
if (json_object_has_member (member, "SoftwareId")) {
|
||||
guid = json_object_get_string_member (member, "SoftwareId");
|
||||
} else if (json_object_has_member (member, "Oem")) {
|
||||
JsonObject *oem = json_object_get_object_member (member, "Oem");
|
||||
if (oem != NULL && json_object_has_member (oem, "Hpe")) {
|
||||
JsonObject *hpe = json_object_get_object_member (oem, "Hpe");
|
||||
if (hpe != NULL && json_object_has_member (hpe, "DeviceClass"))
|
||||
guid = json_object_get_string_member (hpe, "DeviceClass");
|
||||
}
|
||||
}
|
||||
|
||||
/* GUID is required */
|
||||
if (guid == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_FOUND,
|
||||
"no GUID for device");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* device properties */
|
||||
if (json_object_has_member (member, "Manufacturer")) {
|
||||
const gchar *tmp = json_object_get_string_member (member, "Manufacturer");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
fu_redfish_device_set_vendor (self, tmp);
|
||||
}
|
||||
|
||||
/* the version can encode the instance ID suffix */
|
||||
if (json_object_has_member (member, "Version")) {
|
||||
const gchar *tmp = json_object_get_string_member (member, "Version");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
fu_redfish_device_set_version (self, tmp);
|
||||
}
|
||||
|
||||
/* some vendors use a GUID, others use an ID like BMC-AFBT-10 */
|
||||
guid_lower = g_ascii_strdown (guid, -1);
|
||||
if (fwupd_guid_is_valid (guid_lower)) {
|
||||
fu_device_add_guid (dev, guid_lower);
|
||||
} else if (fu_device_get_vendor (dev) != NULL) {
|
||||
const gchar *instance_id_suffix = "";
|
||||
g_autofree gchar *instance_id = NULL;
|
||||
if (fu_device_has_private_flag (dev, FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD))
|
||||
instance_id_suffix = "&TYPE_UNSIGNED";
|
||||
instance_id = g_strdup_printf ("REDFISH\\VENDOR_%s&SOFTWAREID_%s%s",
|
||||
fu_device_get_vendor (dev),
|
||||
guid,
|
||||
instance_id_suffix);
|
||||
g_strdelimit (instance_id, " ", '_');
|
||||
fu_device_add_instance_id (dev, instance_id);
|
||||
}
|
||||
|
||||
/* used for quirking and parenting */
|
||||
if (fu_device_get_vendor (dev) != NULL &&
|
||||
fu_device_get_backend_id (dev) != NULL) {
|
||||
g_autofree gchar *instance_id = NULL;
|
||||
instance_id = g_strdup_printf ("REDFISH\\VENDOR_%s&ID_%s",
|
||||
fu_device_get_vendor (dev),
|
||||
fu_device_get_backend_id (dev));
|
||||
fu_device_add_instance_id (dev, instance_id);
|
||||
}
|
||||
|
||||
if (json_object_has_member (member, "Name")) {
|
||||
const gchar *tmp = json_object_get_string_member (member, "Name");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
fu_redfish_device_set_name (self, tmp);
|
||||
}
|
||||
if (json_object_has_member (member, "LowestSupportedVersion")) {
|
||||
const gchar *tmp = json_object_get_string_member (member, "LowestSupportedVersion");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
fu_redfish_device_set_version_lowest (self, tmp);
|
||||
}
|
||||
if (json_object_has_member (member, "Description")) {
|
||||
const gchar *tmp = json_object_get_string_member (member, "Description");
|
||||
if (tmp != NULL && tmp[0] != '\0')
|
||||
fu_device_set_description (dev, tmp);
|
||||
}
|
||||
|
||||
/* reasons why the device might not be updatable */
|
||||
if (json_object_has_member (member, "Updateable")) {
|
||||
if (!json_object_get_boolean_member (member, "Updateable"))
|
||||
fu_device_remove_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
}
|
||||
if (fu_device_has_private_flag (dev, FU_REDFISH_DEVICE_FLAG_IS_BACKUP))
|
||||
fu_device_remove_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
|
||||
/* use related items to set extra instance IDs */
|
||||
if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE) &&
|
||||
json_object_has_member (member, "RelatedItem")) {
|
||||
JsonArray *related_item_array = json_object_get_array_member (member, "RelatedItem");
|
||||
for (guint i = 0; i < json_array_get_length (related_item_array); i++) {
|
||||
JsonObject *related_item;
|
||||
related_item = json_array_get_object_element (related_item_array, i);
|
||||
if (json_object_has_member (related_item, "@odata.id")) {
|
||||
const gchar *id = json_object_get_string_member (related_item, "@odata.id");
|
||||
if (!fu_redfish_device_probe_related_item (self, id, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
FuRedfishBackend *
|
||||
fu_redfish_device_get_backend (FuRedfishDevice *self)
|
||||
{
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
return priv->backend;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
FwupdError error_code;
|
||||
gchar *location;
|
||||
gboolean completed;
|
||||
GHashTable *messages_seen;
|
||||
} FuRedfishDevicePollCtx;
|
||||
|
||||
static void
|
||||
fu_redfish_device_poll_set_message_id (FuRedfishDevice *self,
|
||||
FuRedfishDevicePollCtx *ctx,
|
||||
const gchar *message_id)
|
||||
{
|
||||
/* ignore */
|
||||
if (g_strcmp0 (message_id, "TaskEvent.1.0.TaskProgressChanged") == 0 ||
|
||||
g_strcmp0 (message_id, "TaskEvent.1.0.TaskCompletedWarning") == 0 ||
|
||||
g_strcmp0 (message_id, "TaskEvent.1.0.TaskCompletedOK") == 0 ||
|
||||
g_strcmp0 (message_id, "Base.1.6.Success") == 0)
|
||||
return;
|
||||
|
||||
/* set flags */
|
||||
if (g_strcmp0 (message_id, "Base.1.10.ResetRequired") == 0) {
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
|
||||
return;
|
||||
}
|
||||
|
||||
/* set error code */
|
||||
if (g_strcmp0 (message_id, "Update.1.0.AwaitToActivate") == 0) {
|
||||
ctx->error_code = FWUPD_ERROR_NEEDS_USER_ACTION;
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.0.TransferFailed") == 0) {
|
||||
ctx->error_code = FWUPD_ERROR_WRITE;
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.0.ActivateFailed") == 0) {
|
||||
ctx->error_code = FWUPD_ERROR_INVALID_FILE;
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.0.VerificationFailed") == 0 ||
|
||||
g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyFailed") == 0) {
|
||||
ctx->error_code = FWUPD_ERROR_INVALID_FILE;
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.0.ApplyFailed") == 0) {
|
||||
ctx->error_code = FWUPD_ERROR_WRITE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* set status */
|
||||
if (g_strcmp0 (message_id, "Update.1.1.TargetDetermined") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_LOADING);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateAssignment") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_LOADING);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyInProgress") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyCompleted") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_IDLE);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyInProgress") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.1.TransferringToComponent") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_LOADING);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.1.VerifyingAtComponent") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.1.UpdateInProgress") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.1.UpdateSuccessful") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_IDLE);
|
||||
return;
|
||||
}
|
||||
if (g_strcmp0 (message_id, "Update.1.1.InstallingOnComponent") == 0) {
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_device_poll_task_once (FuRedfishDevice *self,
|
||||
FuRedfishDevicePollCtx *ctx,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
JsonObject *json_obj;
|
||||
const gchar *message = "Unknown failure";
|
||||
const gchar *state_tmp;
|
||||
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new (priv->backend);
|
||||
|
||||
/* create URI and poll */
|
||||
if (!fu_redfish_request_perform (request, ctx->location,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* percentage is optional */
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
if (json_object_has_member (json_obj, "PercentComplete")) {
|
||||
gint64 pc = json_object_get_int_member (json_obj, "PercentComplete");
|
||||
if (pc >= 0 && pc <= 100)
|
||||
fu_device_set_progress (FU_DEVICE (self), (guint) pc);
|
||||
}
|
||||
|
||||
/* print all messages we've not seen yet */
|
||||
if (json_object_has_member (json_obj, "Messages")) {
|
||||
JsonArray *json_msgs = json_object_get_array_member (json_obj, "Messages");
|
||||
guint json_msgs_sz = json_array_get_length (json_msgs);
|
||||
|
||||
for (guint i = 0; i < json_msgs_sz; i++) {
|
||||
JsonObject *json_message = json_array_get_object_element (json_msgs, i);
|
||||
const gchar *message_id = NULL;
|
||||
g_autofree gchar *message_key = NULL;
|
||||
|
||||
/* set additional device properties */
|
||||
if (json_object_has_member (json_message, "MessageId"))
|
||||
message_id = json_object_get_string_member (json_message, "MessageId");
|
||||
if (json_object_has_member (json_message, "Message"))
|
||||
message = json_object_get_string_member (json_message, "Message");
|
||||
|
||||
/* ignore messages we've seen before */
|
||||
message_key = g_strdup_printf ("%s;%s", message_id, message);
|
||||
if (g_hash_table_contains (ctx->messages_seen, message_key)) {
|
||||
g_debug ("ignoring %s", message_key);
|
||||
continue;
|
||||
}
|
||||
g_hash_table_add (ctx->messages_seen, g_steal_pointer (&message_key));
|
||||
|
||||
/* use the message */
|
||||
g_debug ("message #%u [%s]: %s", i, message_id, message);
|
||||
fu_redfish_device_poll_set_message_id (self, ctx, message_id);
|
||||
}
|
||||
}
|
||||
|
||||
/* use taskstate to set context */
|
||||
if (!json_object_has_member (json_obj, "TaskState")) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no TaskState for task manager");
|
||||
return FALSE;
|
||||
}
|
||||
state_tmp = json_object_get_string_member (json_obj, "TaskState");
|
||||
g_debug ("TaskState now %s", state_tmp);
|
||||
if (g_strcmp0 (state_tmp, "Completed") == 0) {
|
||||
ctx->completed = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
if (g_strcmp0 (state_tmp, "Cancelled") == 0) {
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_CANCELLED,
|
||||
"Task was cancelled");
|
||||
return FALSE;
|
||||
}
|
||||
if (g_strcmp0 (state_tmp, "Exception") == 0 ||
|
||||
g_strcmp0 (state_tmp, "UserIntervention") == 0) {
|
||||
g_set_error_literal (error, FWUPD_ERROR, ctx->error_code, message);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* try again */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static FuRedfishDevicePollCtx *
|
||||
fu_redfish_device_poll_ctx_new (const gchar *location)
|
||||
{
|
||||
FuRedfishDevicePollCtx *ctx = g_new0 (FuRedfishDevicePollCtx, 1);
|
||||
ctx->messages_seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
ctx->location = g_strdup (location);
|
||||
ctx->error_code = FWUPD_ERROR_INTERNAL;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_poll_ctx_free (FuRedfishDevicePollCtx *ctx)
|
||||
{
|
||||
g_hash_table_unref (ctx->messages_seen);
|
||||
g_free (ctx->location);
|
||||
g_free (ctx);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuRedfishDevicePollCtx, fu_redfish_device_poll_ctx_free)
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
gboolean
|
||||
fu_redfish_device_poll_task (FuRedfishDevice *self,
|
||||
const gchar *location,
|
||||
GError **error)
|
||||
{
|
||||
const guint timeout = 2400;
|
||||
g_autoptr(GTimer) timer = g_timer_new ();
|
||||
g_autoptr(FuRedfishDevicePollCtx) ctx = fu_redfish_device_poll_ctx_new (location);
|
||||
|
||||
/* sleep and then reprobe hardware */
|
||||
do {
|
||||
g_usleep (G_USEC_PER_SEC);
|
||||
if (!fu_redfish_device_poll_task_once (self, ctx, error))
|
||||
return FALSE;
|
||||
if (ctx->completed)
|
||||
return TRUE;
|
||||
} while (g_timer_elapsed (timer, NULL) < timeout);
|
||||
|
||||
/* success */
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to poll %s for success after %u seconds",
|
||||
location, timeout);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
FuRedfishDevice *self = FU_REDFISH_DEVICE (object);
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
switch (prop_id) {
|
||||
case PROP_BACKEND:
|
||||
g_value_set_object (value, priv->backend);
|
||||
break;
|
||||
case PROP_MEMBER:
|
||||
g_value_set_pointer (value, priv->member);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
FuRedfishDevice *self = FU_REDFISH_DEVICE (object);
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
switch (prop_id) {
|
||||
case PROP_BACKEND:
|
||||
g_set_object (&priv->backend, g_value_get_object (value));
|
||||
break;
|
||||
case PROP_MEMBER:
|
||||
priv->member = json_object_ref (g_value_get_pointer (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_init (FuRedfishDevice *self)
|
||||
{
|
||||
fu_device_set_summary (FU_DEVICE (self), "Redfish device");
|
||||
fu_device_add_protocol (FU_DEVICE (self), "org.dmtf.redfish");
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
|
||||
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
||||
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME);
|
||||
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT);
|
||||
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON);
|
||||
fu_device_register_private_flag (FU_DEVICE (self),
|
||||
FU_REDFISH_DEVICE_FLAG_IS_BACKUP,
|
||||
"is-backup");
|
||||
fu_device_register_private_flag (FU_DEVICE (self),
|
||||
FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD,
|
||||
"unsigned-build");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_finalize (GObject *object)
|
||||
{
|
||||
FuRedfishDevice *self = FU_REDFISH_DEVICE (object);
|
||||
FuRedfishDevicePrivate *priv = GET_PRIVATE (self);
|
||||
if (priv->backend != NULL)
|
||||
g_object_unref (priv->backend);
|
||||
if (priv->member != NULL)
|
||||
json_object_unref (priv->member);
|
||||
g_free (priv->build);
|
||||
G_OBJECT_CLASS (fu_redfish_device_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_device_class_init (FuRedfishDeviceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GParamSpec *pspec;
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
||||
|
||||
object_class->get_property = fu_redfish_device_get_property;
|
||||
object_class->set_property = fu_redfish_device_set_property;
|
||||
object_class->finalize = fu_redfish_device_finalize;
|
||||
|
||||
klass_device->to_string = fu_redfish_device_to_string;
|
||||
klass_device->probe = fu_redfish_device_probe;
|
||||
|
||||
pspec = g_param_spec_object ("backend", NULL, NULL,
|
||||
FU_TYPE_BACKEND,
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_STATIC_NAME);
|
||||
g_object_class_install_property (object_class, PROP_BACKEND, pspec);
|
||||
|
||||
pspec = g_param_spec_pointer ("member", NULL, NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_STATIC_NAME);
|
||||
g_object_class_install_property (object_class, PROP_MEMBER, pspec);
|
||||
}
|
||||
24
plugins/redfish/fu-redfish-device.h
Normal file
24
plugins/redfish/fu-redfish-device.h
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include "fu-redfish-backend.h"
|
||||
|
||||
#define FU_TYPE_REDFISH_DEVICE (fu_redfish_device_get_type ())
|
||||
G_DECLARE_DERIVABLE_TYPE (FuRedfishDevice, fu_redfish_device, FU, REDFISH_DEVICE, FuDevice)
|
||||
|
||||
struct _FuRedfishDeviceClass
|
||||
{
|
||||
FuDeviceClass parent_class;
|
||||
};
|
||||
|
||||
FuRedfishBackend *fu_redfish_device_get_backend (FuRedfishDevice *self);
|
||||
gboolean fu_redfish_device_poll_task (FuRedfishDevice *self,
|
||||
const gchar *location,
|
||||
GError **error);
|
||||
167
plugins/redfish/fu-redfish-legacy-device.c
Normal file
167
plugins/redfish/fu-redfish-legacy-device.c
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "fu-redfish-legacy-device.h"
|
||||
#include "fu-redfish-request.h"
|
||||
|
||||
struct _FuRedfishLegacyDevice {
|
||||
FuRedfishDevice parent_instance;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (FuRedfishLegacyDevice, fu_redfish_legacy_device, FU_TYPE_REDFISH_DEVICE)
|
||||
|
||||
static gboolean
|
||||
fu_redfish_legacy_device_detach (FuDevice *dev, GError **error)
|
||||
{
|
||||
FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE (dev);
|
||||
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
|
||||
CURL *curl;
|
||||
struct curl_slist *hs = NULL;
|
||||
g_autoptr(FuRedfishRequest) request = NULL;
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
g_autoptr(JsonBuilder) builder = json_builder_new ();
|
||||
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
|
||||
g_autoptr(JsonNode) json_root = NULL;
|
||||
|
||||
/* create header */
|
||||
json_builder_begin_object (builder);
|
||||
json_builder_set_member_name (builder, "HttpPushUriTargetsBusy");
|
||||
json_builder_add_boolean_value (builder, TRUE);
|
||||
json_builder_set_member_name (builder, "HttpPushUriTargets");
|
||||
json_builder_begin_array (builder);
|
||||
json_builder_add_string_value (builder, fu_device_get_logical_id (FU_DEVICE (self)));
|
||||
json_builder_end_array (builder);
|
||||
json_builder_end_object (builder);
|
||||
|
||||
/* export as a string */
|
||||
json_root = json_builder_get_root (builder);
|
||||
json_generator_set_pretty (json_generator, TRUE);
|
||||
json_generator_set_root (json_generator, json_root);
|
||||
json_generator_to_gstring (json_generator, str);
|
||||
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL)
|
||||
g_debug ("request: %s", str->str);
|
||||
|
||||
/* patch the two fields */
|
||||
request = fu_redfish_backend_request_new (backend);
|
||||
curl = fu_redfish_request_get_curl (request);
|
||||
curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, str->str);
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long) str->len);
|
||||
hs = curl_slist_append (hs, "Content-Type: application/json");
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, hs);
|
||||
return fu_redfish_request_perform (request, "/redfish/v1/UpdateService",
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_legacy_device_attach (FuDevice *dev, GError **error)
|
||||
{
|
||||
FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE (dev);
|
||||
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
|
||||
CURL *curl;
|
||||
struct curl_slist *hs = NULL;
|
||||
g_autoptr(FuRedfishRequest) request = NULL;
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
g_autoptr(JsonBuilder) builder = json_builder_new ();
|
||||
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
|
||||
g_autoptr(JsonNode) json_root = NULL;
|
||||
|
||||
/* create header */
|
||||
json_builder_begin_object (builder);
|
||||
json_builder_set_member_name (builder, "HttpPushUriTargetsBusy");
|
||||
json_builder_add_boolean_value (builder, FALSE);
|
||||
json_builder_set_member_name (builder, "HttpPushUriTargets");
|
||||
json_builder_begin_array (builder);
|
||||
json_builder_end_array (builder);
|
||||
json_builder_end_object (builder);
|
||||
|
||||
/* export as a string */
|
||||
json_root = json_builder_get_root (builder);
|
||||
json_generator_set_pretty (json_generator, TRUE);
|
||||
json_generator_set_root (json_generator, json_root);
|
||||
json_generator_to_gstring (json_generator, str);
|
||||
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL)
|
||||
g_debug ("request: %s", str->str);
|
||||
|
||||
/* patch the two fields */
|
||||
request = fu_redfish_backend_request_new (backend);
|
||||
curl = fu_redfish_request_get_curl (request);
|
||||
curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, str->str);
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long) str->len);
|
||||
hs = curl_slist_append (hs, "Content-Type: application/json");
|
||||
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, hs);
|
||||
return fu_redfish_request_perform (request, "/redfish/v1/UpdateService",
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_legacy_device_write_firmware (FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE (device);
|
||||
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
|
||||
CURL *curl;
|
||||
JsonObject *json_obj;
|
||||
const gchar *location;
|
||||
g_autoptr(FuRedfishRequest) request = NULL;
|
||||
g_autoptr(GBytes) fw = NULL;
|
||||
|
||||
/* get default image */
|
||||
fw = fu_firmware_get_bytes (firmware, error);
|
||||
if (fw == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* POST data */
|
||||
request = fu_redfish_backend_request_new (backend);
|
||||
curl = fu_redfish_request_get_curl (request);
|
||||
curl_easy_setopt (curl, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, g_bytes_get_data (fw, NULL));
|
||||
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, (long) g_bytes_get_size (fw));
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
|
||||
if (!fu_redfish_request_perform (request,
|
||||
fu_redfish_backend_get_push_uri_path (backend),
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* poll the task for progress */
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
if (!json_object_has_member (json_obj, "@odata.id")) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no task returned for %s",
|
||||
fu_redfish_backend_get_push_uri_path (backend));
|
||||
return FALSE;
|
||||
}
|
||||
location = json_object_get_string_member (json_obj, "@odata.id");
|
||||
return fu_redfish_device_poll_task (FU_REDFISH_DEVICE (self),
|
||||
location, error);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_legacy_device_init (FuRedfishLegacyDevice *self)
|
||||
{
|
||||
fu_device_set_summary (FU_DEVICE (self), "Redfish legacy device");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_legacy_device_class_init (FuRedfishLegacyDeviceClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
||||
klass_device->attach = fu_redfish_legacy_device_attach;
|
||||
klass_device->detach = fu_redfish_legacy_device_detach;
|
||||
klass_device->write_firmware = fu_redfish_legacy_device_write_firmware;
|
||||
}
|
||||
12
plugins/redfish/fu-redfish-legacy-device.h
Normal file
12
plugins/redfish/fu-redfish-legacy-device.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fu-redfish-device.h"
|
||||
|
||||
#define FU_TYPE_REDFISH_LEGACY_DEVICE (fu_redfish_legacy_device_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (FuRedfishLegacyDevice, fu_redfish_legacy_device, FU, REDFISH_LEGACY_DEVICE, FuRedfishDevice)
|
||||
142
plugins/redfish/fu-redfish-multipart-device.c
Normal file
142
plugins/redfish/fu-redfish-multipart-device.c
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "fu-redfish-multipart-device.h"
|
||||
#include "fu-redfish-request.h"
|
||||
|
||||
struct _FuRedfishMultipartDevice {
|
||||
FuRedfishDevice parent_instance;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (FuRedfishMultipartDevice, fu_redfish_multipart_device, FU_TYPE_REDFISH_DEVICE)
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free)
|
||||
|
||||
static GString *
|
||||
fu_redfish_multipart_device_get_parameters (FuRedfishMultipartDevice *self)
|
||||
{
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
g_autoptr(JsonBuilder) builder = json_builder_new ();
|
||||
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
|
||||
g_autoptr(JsonNode) json_root = NULL;
|
||||
|
||||
/* create header */
|
||||
json_builder_begin_object (builder);
|
||||
json_builder_set_member_name (builder, "Targets");
|
||||
json_builder_begin_array (builder);
|
||||
json_builder_add_string_value (builder, fu_device_get_logical_id (FU_DEVICE (self)));
|
||||
json_builder_end_array (builder);
|
||||
json_builder_set_member_name (builder, "@Redfish.OperationApplyTime");
|
||||
json_builder_add_string_value (builder, "Immediate");
|
||||
json_builder_end_object (builder);
|
||||
|
||||
/* export as a string */
|
||||
json_root = json_builder_get_root (builder);
|
||||
json_generator_set_pretty (json_generator, TRUE);
|
||||
json_generator_set_root (json_generator, json_root);
|
||||
json_generator_to_gstring (json_generator, str);
|
||||
return g_steal_pointer (&str);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_multipart_device_write_firmware (FuDevice *device,
|
||||
FuFirmware *firmware,
|
||||
FwupdInstallFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuRedfishMultipartDevice *self = FU_REDFISH_MULTIPART_DEVICE (device);
|
||||
FuRedfishBackend *backend = fu_redfish_device_get_backend (FU_REDFISH_DEVICE (self));
|
||||
CURL *curl;
|
||||
JsonObject *json_obj;
|
||||
curl_mimepart *part;
|
||||
const gchar *location;
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autoptr(curl_mime) mime = NULL;
|
||||
g_autoptr(FuRedfishRequest) request = NULL;
|
||||
g_autoptr(GBytes) fw = NULL;
|
||||
g_autoptr(GString) params = NULL;
|
||||
|
||||
/* get default image */
|
||||
fw = fu_firmware_get_bytes (firmware, error);
|
||||
if (fw == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* Get the update version */
|
||||
filename = g_strdup_printf ("%s.bin", fu_device_get_name (FU_DEVICE (self)));
|
||||
|
||||
/* create the multipart request */
|
||||
request = fu_redfish_backend_request_new (backend);
|
||||
curl = fu_redfish_request_get_curl (request);
|
||||
mime = curl_mime_init (curl);
|
||||
curl_easy_setopt (curl, CURLOPT_MIMEPOST, mime);
|
||||
|
||||
params = fu_redfish_multipart_device_get_parameters (self);
|
||||
part = curl_mime_addpart (mime);
|
||||
curl_mime_name (part, "UpdateParameters");
|
||||
curl_mime_type (part, "application/json");
|
||||
curl_mime_data (part, params->str, CURL_ZERO_TERMINATED);
|
||||
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL)
|
||||
g_debug ("request: %s", params->str);
|
||||
|
||||
part = curl_mime_addpart (mime);
|
||||
curl_mime_name (part, "UpdateFile");
|
||||
curl_mime_type (part, "application/octet-stream");
|
||||
curl_mime_filedata (part, filename);
|
||||
curl_mime_data (part, g_bytes_get_data (fw, NULL), g_bytes_get_size (fw));
|
||||
|
||||
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
|
||||
if (!fu_redfish_request_perform (request,
|
||||
fu_redfish_backend_get_push_uri_path (backend),
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
|
||||
error))
|
||||
return FALSE;
|
||||
if (fu_redfish_request_get_status_code (request) != 202) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to upload %s: %li",
|
||||
filename,
|
||||
fu_redfish_request_get_status_code (request));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* prefer the header, otherwise fall back to the response */
|
||||
json_obj = fu_redfish_request_get_json_object (request);
|
||||
if (json_object_has_member (json_obj, "TaskMonitor")) {
|
||||
const gchar *tmp = json_object_get_string_member (json_obj, "TaskMonitor");
|
||||
g_debug ("task manager for cleanup is %s", tmp);
|
||||
}
|
||||
|
||||
/* poll the task for progress */
|
||||
if (!json_object_has_member (json_obj, "@odata.id")) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no task returned for %s",
|
||||
fu_redfish_backend_get_push_uri_path (backend));
|
||||
return FALSE;
|
||||
}
|
||||
location = json_object_get_string_member (json_obj, "@odata.id");
|
||||
return fu_redfish_device_poll_task (FU_REDFISH_DEVICE (self),
|
||||
location, error);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_multipart_device_init (FuRedfishMultipartDevice *self)
|
||||
{
|
||||
fu_device_set_summary (FU_DEVICE (self), "Redfish multipart device");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_multipart_device_class_init (FuRedfishMultipartDeviceClass *klass)
|
||||
{
|
||||
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
||||
klass_device->write_firmware = fu_redfish_multipart_device_write_firmware;
|
||||
}
|
||||
12
plugins/redfish/fu-redfish-multipart-device.h
Normal file
12
plugins/redfish/fu-redfish-multipart-device.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fu-redfish-device.h"
|
||||
|
||||
#define FU_TYPE_REDFISH_MULTIPART_DEVICE (fu_redfish_multipart_device_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (FuRedfishMultipartDevice, fu_redfish_multipart_device, FU, REDFISH_MULTIPART_DEVICE, FuRedfishDevice)
|
||||
263
plugins/redfish/fu-redfish-request.c
Normal file
263
plugins/redfish/fu-redfish-request.c
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "fu-redfish-request.h"
|
||||
|
||||
struct _FuRedfishRequest {
|
||||
GObject parent_instance;
|
||||
CURL *curl;
|
||||
CURLU *uri;
|
||||
GByteArray *buf;
|
||||
glong status_code;
|
||||
JsonParser *json_parser;
|
||||
JsonObject *json_obj;
|
||||
GHashTable *cache; /* nullable */
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (FuRedfishRequest, fu_redfish_request, G_TYPE_OBJECT)
|
||||
|
||||
typedef gchar curlptr;
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free)
|
||||
|
||||
JsonObject *
|
||||
fu_redfish_request_get_json_object (FuRedfishRequest *self)
|
||||
{
|
||||
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), NULL);
|
||||
return self->json_obj;
|
||||
}
|
||||
|
||||
CURL *
|
||||
fu_redfish_request_get_curl (FuRedfishRequest *self)
|
||||
{
|
||||
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), NULL);
|
||||
return self->curl;
|
||||
}
|
||||
|
||||
CURLU *
|
||||
fu_redfish_request_get_uri (FuRedfishRequest *self)
|
||||
{
|
||||
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), NULL);
|
||||
return self->uri;
|
||||
}
|
||||
|
||||
glong
|
||||
fu_redfish_request_get_status_code (FuRedfishRequest *self)
|
||||
{
|
||||
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), G_MAXLONG);
|
||||
return self->status_code;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fu_redfish_request_load_json (FuRedfishRequest *self, GByteArray *buf, GError **error)
|
||||
{
|
||||
JsonNode *json_root;
|
||||
|
||||
/* load */
|
||||
if (buf->data == NULL || buf->len == 0) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"there was no JSON payload");
|
||||
return FALSE;
|
||||
}
|
||||
if (!json_parser_load_from_data (self->json_parser,
|
||||
(const gchar *) buf->data,
|
||||
(gssize) buf->len,
|
||||
error)) {
|
||||
return FALSE;
|
||||
}
|
||||
json_root = json_parser_get_root (self->json_parser);
|
||||
if (json_root == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no JSON root node");
|
||||
return FALSE;
|
||||
}
|
||||
self->json_obj = json_node_get_object (json_root);
|
||||
if (self->json_obj == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"no JSON object");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* dump for humans */
|
||||
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL) {
|
||||
g_autoptr(GString) str = g_string_new (NULL);
|
||||
g_autoptr(JsonBuilder) builder = json_builder_new ();
|
||||
g_autoptr(JsonGenerator) json_generator = json_generator_new ();
|
||||
json_generator_set_pretty (json_generator, TRUE);
|
||||
json_generator_set_root (json_generator, json_root);
|
||||
json_generator_to_gstring (json_generator, str);
|
||||
g_debug ("response: %s", str->str);
|
||||
}
|
||||
|
||||
/* unauthorized */
|
||||
if (json_object_has_member (self->json_obj, "error")) {
|
||||
FwupdError error_code = FWUPD_ERROR_INTERNAL;
|
||||
JsonObject *json_error;
|
||||
const gchar *id = NULL;
|
||||
const gchar *msg = "Unknown failure";
|
||||
|
||||
/* extended error present */
|
||||
json_error = json_object_get_object_member (self->json_obj, "error");
|
||||
if (json_object_has_member (json_error, "@Message.ExtendedInfo")) {
|
||||
JsonArray *json_error_array;
|
||||
json_error_array = json_object_get_array_member (json_error, "@Message.ExtendedInfo");
|
||||
if (json_array_get_length (json_error_array) > 0) {
|
||||
JsonObject *json_error2;
|
||||
json_error2 = json_array_get_object_element (json_error_array, 0);
|
||||
if (json_object_has_member (json_error2, "MessageId"))
|
||||
id = json_object_get_string_member (json_error2, "MessageId");
|
||||
if (json_object_has_member (json_error2, "Message"))
|
||||
msg = json_object_get_string_member (json_error2, "Message");
|
||||
}
|
||||
} else {
|
||||
if (json_object_has_member (json_error, "code"))
|
||||
id = json_object_get_string_member (json_error, "code");
|
||||
if (json_object_has_member (json_error, "message"))
|
||||
msg = json_object_get_string_member (json_error, "message");
|
||||
}
|
||||
if (g_strcmp0 (id, "Base.1.8.AccessDenied") == 0)
|
||||
error_code = FWUPD_ERROR_AUTH_FAILED;
|
||||
g_set_error_literal (error, FWUPD_ERROR, error_code, msg);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_redfish_request_perform (FuRedfishRequest *self,
|
||||
const gchar *path,
|
||||
FuRedfishRequestPerformFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
CURLcode res;
|
||||
g_autoptr(curlptr) uri_str = NULL;
|
||||
|
||||
g_return_val_if_fail (FU_IS_REDFISH_REQUEST (self), FALSE);
|
||||
g_return_val_if_fail (path != NULL, FALSE);
|
||||
g_return_val_if_fail (self->status_code == 0, FALSE);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
|
||||
/* already in cache? */
|
||||
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE &&
|
||||
self->cache != NULL) {
|
||||
GByteArray *buf = g_hash_table_lookup (self->cache, path);
|
||||
if (buf != NULL) {
|
||||
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON)
|
||||
return fu_redfish_request_load_json (self, buf, error);
|
||||
g_byte_array_unref (self->buf);
|
||||
self->buf = g_byte_array_ref (buf);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* do request */
|
||||
curl_url_set (self->uri, CURLUPART_PATH, path, 0);
|
||||
curl_url_get (self->uri, CURLUPART_URL, &uri_str, 0);
|
||||
res = curl_easy_perform (self->curl);
|
||||
curl_easy_getinfo (self->curl, CURLINFO_RESPONSE_CODE, &self->status_code);
|
||||
if (g_getenv ("FWUPD_REDFISH_VERBOSE") != NULL) {
|
||||
g_autofree gchar *str = NULL;
|
||||
str = g_strndup ((const gchar *) self->buf->data,
|
||||
self->buf->len);
|
||||
g_debug ("%s: %s [%li]", uri_str, str, self->status_code);
|
||||
}
|
||||
|
||||
/* check result */
|
||||
if (res != CURLE_OK) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INVALID_FILE,
|
||||
"failed to request %s: %s",
|
||||
uri_str, curl_easy_strerror (res));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* load JSON */
|
||||
if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON) {
|
||||
if (!fu_redfish_request_load_json (self, self->buf, error)) {
|
||||
g_prefix_error (error,
|
||||
"failed to parse %s: ",
|
||||
uri_str);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* save to cache */
|
||||
if (self->cache != NULL && path != NULL) {
|
||||
g_hash_table_insert (self->cache,
|
||||
g_strdup (path),
|
||||
g_byte_array_ref (self->buf));
|
||||
}
|
||||
|
||||
/* success */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static size_t
|
||||
fu_redfish_request_write_cb (char *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
GByteArray *buf = (GByteArray *) userdata;
|
||||
gsize realsize = size * nmemb;
|
||||
g_byte_array_append (buf, (const guint8 *) ptr, realsize);
|
||||
return realsize;
|
||||
}
|
||||
|
||||
void
|
||||
fu_redfish_request_set_cache (FuRedfishRequest *self, GHashTable *cache)
|
||||
{
|
||||
g_return_if_fail (FU_IS_REDFISH_REQUEST (self));
|
||||
g_return_if_fail (cache != NULL);
|
||||
g_return_if_fail (self->cache == NULL);
|
||||
self->cache = g_hash_table_ref (cache);
|
||||
}
|
||||
|
||||
void
|
||||
fu_redfish_request_set_curlsh (FuRedfishRequest *self, CURLSH *curlsh)
|
||||
{
|
||||
g_return_if_fail (FU_IS_REDFISH_REQUEST (self));
|
||||
g_return_if_fail (curlsh != NULL);
|
||||
curl_easy_setopt (self->curl, CURLOPT_SHARE, curlsh);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_request_init (FuRedfishRequest *self)
|
||||
{
|
||||
self->curl = curl_easy_init ();
|
||||
self->uri = curl_url ();
|
||||
self->buf = g_byte_array_new ();
|
||||
self->json_parser = json_parser_new ();
|
||||
curl_easy_setopt (self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_request_write_cb);
|
||||
curl_easy_setopt (self->curl, CURLOPT_WRITEDATA, self->buf);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_request_finalize (GObject *object)
|
||||
{
|
||||
FuRedfishRequest *self = FU_REDFISH_REQUEST (object);
|
||||
if (self->cache != NULL)
|
||||
g_hash_table_unref (self->cache);
|
||||
g_object_unref (self->json_parser);
|
||||
g_byte_array_unref (self->buf);
|
||||
curl_easy_cleanup (self->curl);
|
||||
curl_url_cleanup (self->uri);
|
||||
G_OBJECT_CLASS (fu_redfish_request_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_redfish_request_class_init (FuRedfishRequestClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
object_class->finalize = fu_redfish_request_finalize;
|
||||
}
|
||||
37
plugins/redfish/fu-redfish-request.h
Normal file
37
plugins/redfish/fu-redfish-request.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fwupdplugin.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#ifndef HAVE_LIBCURL_7_62_0
|
||||
#error libcurl >= 7.62.0 required
|
||||
#endif
|
||||
|
||||
#define FU_TYPE_REDFISH_REQUEST (fu_redfish_request_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (FuRedfishRequest, fu_redfish_request, FU, REDFISH_REQUEST, GObject)
|
||||
|
||||
typedef enum {
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_NONE = 0,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON = 1 << 0,
|
||||
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE = 1 << 1,
|
||||
} FuRedfishRequestPerformFlags;
|
||||
|
||||
gboolean fu_redfish_request_perform (FuRedfishRequest *self,
|
||||
const gchar *path,
|
||||
FuRedfishRequestPerformFlags flags,
|
||||
GError **error);
|
||||
JsonObject *fu_redfish_request_get_json_object (FuRedfishRequest *self);
|
||||
CURL *fu_redfish_request_get_curl (FuRedfishRequest *self);
|
||||
void fu_redfish_request_set_curlsh (FuRedfishRequest *self,
|
||||
CURLSH *curlsh);
|
||||
CURLU *fu_redfish_request_get_uri (FuRedfishRequest *self);
|
||||
glong fu_redfish_request_get_status_code (FuRedfishRequest *self);
|
||||
void fu_redfish_request_set_cache (FuRedfishRequest *self,
|
||||
GHashTable *cache);
|
||||
@ -8,9 +8,51 @@
|
||||
|
||||
#include <fwupd.h>
|
||||
|
||||
#include "fu-context-private.h"
|
||||
#include "fu-device-private.h"
|
||||
#include "fu-plugin-private.h"
|
||||
|
||||
#include "fu-redfish-common.h"
|
||||
#include "fu-redfish-network.h"
|
||||
|
||||
typedef struct {
|
||||
FuPlugin *plugin;
|
||||
} FuTest;
|
||||
|
||||
static void
|
||||
fu_test_self_init (FuTest *self)
|
||||
{
|
||||
gboolean ret;
|
||||
g_autoptr(FuContext) ctx = fu_context_new ();
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autofree gchar *pluginfn = NULL;
|
||||
|
||||
ret = fu_context_load_quirks (ctx,
|
||||
FU_QUIRKS_LOAD_FLAG_NO_CACHE |
|
||||
FU_QUIRKS_LOAD_FLAG_NO_VERIFY,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
|
||||
self->plugin = fu_plugin_new (ctx);
|
||||
pluginfn = g_build_filename (PLUGINBUILDDIR,
|
||||
"libfu_plugin_redfish." G_MODULE_SUFFIX,
|
||||
NULL);
|
||||
ret = fu_plugin_open (self->plugin, pluginfn, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
ret = fu_plugin_runner_startup (self->plugin, &error);
|
||||
if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) {
|
||||
g_test_skip ("no redfish.py running");
|
||||
return;
|
||||
}
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
ret = fu_plugin_runner_coldplug (self->plugin, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (ret);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_redfish_common_func (void)
|
||||
{
|
||||
@ -28,6 +70,55 @@ fu_test_redfish_common_func (void)
|
||||
g_assert_cmpstr (maca, ==, "00:01:02:03:04:05");
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_redfish_common_version_func (void)
|
||||
{
|
||||
struct {
|
||||
const gchar *in;
|
||||
const gchar *op;
|
||||
} strs[] = {
|
||||
{ "1.2.3", "1.2.3" },
|
||||
{ "P50 v1.2.3 PROD", "1.2.3" },
|
||||
{ "P50 1.2.3 DEV", "1.2.3" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
for (guint i = 0; strs[i].in != NULL; i++) {
|
||||
g_autofree gchar *tmp = fu_redfish_common_fix_version (strs[i].in);
|
||||
g_assert_cmpstr (tmp, ==, strs[i].op);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_redfish_common_lenovo_func (void)
|
||||
{
|
||||
struct {
|
||||
const gchar *in;
|
||||
gboolean ret;
|
||||
const gchar *build;
|
||||
const gchar *version;
|
||||
} values[] = {
|
||||
{ "11A-1.02", TRUE, "11A", "1.02" },
|
||||
{ "11A-0.00", TRUE, "11A", "0.00" },
|
||||
{ "99Z-9.99", TRUE, "99Z", "9.99" },
|
||||
{ "9-9-9.99", FALSE, NULL, NULL },
|
||||
{ "999-9.99", FALSE, NULL, NULL },
|
||||
{ "ACB-9.99", FALSE, NULL, NULL },
|
||||
{ NULL, FALSE, NULL, NULL }
|
||||
};
|
||||
for (guint i = 0; values[i].in != NULL; i++) {
|
||||
gboolean ret;
|
||||
g_autofree gchar *build = NULL;
|
||||
g_autofree gchar *version = NULL;
|
||||
ret = fu_redfish_common_parse_version_lenovo (values[i].in,
|
||||
&build,
|
||||
&version,
|
||||
NULL);
|
||||
g_assert_cmpint (ret, ==, values[i].ret);
|
||||
g_assert_cmpstr (build, ==, values[i].build);
|
||||
g_assert_cmpstr (version, ==, values[i].version);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_redfish_network_mac_addr_func (void)
|
||||
{
|
||||
@ -60,15 +151,127 @@ fu_test_redfish_network_vid_pid_func (void)
|
||||
g_assert_nonnull (ip_addr);
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_redfish_devices_func (gconstpointer user_data)
|
||||
{
|
||||
FuDevice *dev;
|
||||
FuTest *self = (FuTest *) user_data;
|
||||
GPtrArray *devices;
|
||||
g_autofree gchar *devstr0 = NULL;
|
||||
g_autofree gchar *devstr1 = NULL;
|
||||
|
||||
devices = fu_plugin_get_devices (self->plugin);
|
||||
g_assert_nonnull (devices);
|
||||
if (devices->len == 0) {
|
||||
g_test_skip ("no redfish support");
|
||||
return;
|
||||
}
|
||||
g_assert_cmpint (devices->len, ==, 2);
|
||||
|
||||
/* BMC */
|
||||
dev = g_ptr_array_index (devices, 1);
|
||||
devstr0 = fu_device_to_string (dev);
|
||||
g_debug ("%s", devstr0);
|
||||
g_assert_cmpstr (fu_device_get_id (dev), ==, "62c1cd95692c5225826cf8568a460427ea3b1827");
|
||||
g_assert_cmpstr (fu_device_get_name (dev), ==, "BMC Firmware");
|
||||
g_assert_cmpstr (fu_device_get_vendor (dev), ==, "Lenovo");
|
||||
g_assert_cmpstr (fu_device_get_version (dev), ==, "1.02");
|
||||
g_assert_cmpstr (fu_device_get_version_lowest (dev), ==, "0.12");
|
||||
g_assert_cmpint (fu_device_get_version_format (dev), ==, FWUPD_VERSION_FORMAT_PAIR);
|
||||
g_assert_true (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE));
|
||||
g_assert_true (fu_device_has_protocol (dev, "org.dmtf.redfish"));
|
||||
g_assert_true (fu_device_has_guid (dev, "REDFISH\\VENDOR_Lenovo&SOFTWAREID_UEFI-AFE1-6&TYPE_UNSIGNED"));
|
||||
g_assert_true (fu_device_has_vendor_id (dev, "REDFISH:LENOVO"));
|
||||
|
||||
/* BIOS */
|
||||
dev = g_ptr_array_index (devices, 0);
|
||||
devstr1 = fu_device_to_string (dev);
|
||||
g_debug ("%s", devstr1);
|
||||
g_assert_cmpstr (fu_device_get_id (dev), ==, "562313e34c756a05a2e878861377765582bbf971");
|
||||
g_assert_cmpstr (fu_device_get_name (dev), ==, "BIOS Firmware");
|
||||
g_assert_cmpstr (fu_device_get_vendor (dev), ==, "Contoso");
|
||||
g_assert_cmpstr (fu_device_get_version (dev), ==, "1.45");
|
||||
g_assert_cmpstr (fu_device_get_serial (dev), ==, "12345");
|
||||
g_assert_cmpstr (fu_device_get_version_lowest (dev), ==, "1.10");
|
||||
g_assert_cmpint (fu_device_get_version_format (dev), ==, FWUPD_VERSION_FORMAT_PAIR);
|
||||
g_assert_true (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE));
|
||||
g_assert_true (fu_device_has_icon (dev, "network-wired"));
|
||||
g_assert_true (fu_device_has_protocol (dev, "org.dmtf.redfish"));
|
||||
g_assert_true (fu_device_has_guid (dev, "fee82a67-6ce2-4625-9f44-237ad2402c28"));
|
||||
g_assert_true (fu_device_has_guid (dev, "a6d3294e-37e5-50aa-ae2f-c0c457af16f3"));
|
||||
g_assert_true (fu_device_has_vendor_id (dev, "REDFISH:CONTOSO"));
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_redfish_update_func (gconstpointer user_data)
|
||||
{
|
||||
FuDevice *dev;
|
||||
FuTest *self = (FuTest *) user_data;
|
||||
GPtrArray *devices;
|
||||
gboolean ret;
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GBytes) blob_fw = NULL;
|
||||
|
||||
devices = fu_plugin_get_devices (self->plugin);
|
||||
g_assert_nonnull (devices);
|
||||
if (devices->len == 0) {
|
||||
g_test_skip ("no redfish support");
|
||||
return;
|
||||
}
|
||||
g_assert_cmpint (devices->len, ==, 2);
|
||||
|
||||
/* BMC */
|
||||
dev = g_ptr_array_index (devices, 1);
|
||||
blob_fw = g_bytes_new_static ("hello", 5);
|
||||
ret = fu_plugin_runner_update (self->plugin, dev, blob_fw,
|
||||
FWUPD_INSTALL_FLAG_NONE, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
g_assert_true (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT));
|
||||
|
||||
/* try again */
|
||||
ret = fu_plugin_runner_update (self->plugin, dev, blob_fw,
|
||||
FWUPD_INSTALL_FLAG_NONE, &error);
|
||||
g_assert_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE);
|
||||
g_assert_false (ret);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
fu_test_self_free (FuTest *self)
|
||||
{
|
||||
if (self->plugin != NULL)
|
||||
g_object_unref (self->plugin);
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_self_free)
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(FuTest) self = g_new0 (FuTest, 1);
|
||||
g_autofree gchar *smbios_data_fn = NULL;
|
||||
|
||||
g_setenv ("FWUPD_REDFISH_VERBOSE", "1", TRUE);
|
||||
|
||||
smbios_data_fn = g_build_filename (TESTDATADIR, "redfish-smbios.bin", NULL);
|
||||
g_setenv ("FWUPD_REDFISH_SMBIOS_DATA", smbios_data_fn, TRUE);
|
||||
g_setenv ("FWUPD_SYSFSFWDIR", TESTDATADIR, TRUE);
|
||||
g_setenv ("CONFIGURATION_DIRECTORY", TESTDATADIR, TRUE);
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
|
||||
fu_test_self_init (self);
|
||||
g_test_add_func ("/redfish/common", fu_test_redfish_common_func);
|
||||
g_test_add_func ("/redfish/common{version}", fu_test_redfish_common_version_func);
|
||||
g_test_add_func ("/redfish/common{lenovo}", fu_test_redfish_common_lenovo_func);
|
||||
g_test_add_func ("/redfish/network{mac_addr}", fu_test_redfish_network_mac_addr_func);
|
||||
g_test_add_func ("/redfish/network{vid_pid}", fu_test_redfish_network_vid_pid_func);
|
||||
g_test_add_data_func ("/redfish/plugin{devices}", self, fu_test_redfish_devices_func);
|
||||
g_test_add_data_func ("/redfish/plugin{update}", self, fu_test_redfish_update_func);
|
||||
return g_test_run ();
|
||||
}
|
||||
|
||||
@ -4,13 +4,21 @@ if not get_option('curl')
|
||||
endif
|
||||
cargs = ['-DG_LOG_DOMAIN="FuPluginRedfish"']
|
||||
|
||||
install_data(['redfish.quirk'],
|
||||
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
|
||||
)
|
||||
|
||||
shared_module('fu_plugin_redfish',
|
||||
fu_hash,
|
||||
sources : [
|
||||
'fu-plugin-redfish.c',
|
||||
'fu-redfish-backend.c',
|
||||
'fu-redfish-common.c', # fuzzing
|
||||
'fu-redfish-device.c',
|
||||
'fu-redfish-legacy-device.c',
|
||||
'fu-redfish-multipart-device.c',
|
||||
'fu-redfish-network.c',
|
||||
'fu-redfish-request.c',
|
||||
'fu-redfish-smbios.c', # fuzzing
|
||||
],
|
||||
include_directories : [
|
||||
@ -36,6 +44,13 @@ install_data(['redfish.conf'],
|
||||
)
|
||||
|
||||
if get_option('tests')
|
||||
testdatadir = join_paths(meson.current_source_dir(), 'tests')
|
||||
testdatadirs = environment()
|
||||
testdatadirs.set('G_TEST_SRCDIR', meson.current_source_dir())
|
||||
testdatadirs.set('G_TEST_BUILDDIR', meson.current_build_dir())
|
||||
testdatadirs.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var')
|
||||
cargs += '-DTESTDATADIR="' + testdatadir + '"'
|
||||
cargs += '-DPLUGINBUILDDIR="' + meson.current_build_dir() + '"'
|
||||
e = executable(
|
||||
'redfish-self-test',
|
||||
fu_hash,
|
||||
@ -43,7 +58,11 @@ if get_option('tests')
|
||||
'fu-self-test.c',
|
||||
'fu-redfish-backend.c',
|
||||
'fu-redfish-common.c',
|
||||
'fu-redfish-device.c',
|
||||
'fu-redfish-legacy-device.c',
|
||||
'fu-redfish-multipart-device.c',
|
||||
'fu-redfish-network.c',
|
||||
'fu-redfish-request.c',
|
||||
'fu-redfish-smbios.c',
|
||||
],
|
||||
include_directories : [
|
||||
|
||||
16
plugins/redfish/redfish.quirk
Normal file
16
plugins/redfish/redfish.quirk
Normal file
@ -0,0 +1,16 @@
|
||||
[REDFISH\VENDOR_Lenovo&ID_BMC-Backup]
|
||||
ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary
|
||||
Flags = dual-image,is-backup
|
||||
|
||||
[REDFISH\VENDOR_Lenovo&ID_BMC-Primary]
|
||||
ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary
|
||||
Flags = dual-image
|
||||
|
||||
[REDFISH\VENDOR_Lenovo&ID_LXPM]
|
||||
Flags = ~updatable
|
||||
[REDFISH\VENDOR_Lenovo&ID_LXPMLinuxDriver]
|
||||
Flags = ~updatable
|
||||
ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM
|
||||
[REDFISH\VENDOR_Lenovo&ID_LXPMWindowsDriver]
|
||||
Flags = ~updatable
|
||||
ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM
|
||||
1
plugins/redfish/tests/daemon.conf
Symbolic link
1
plugins/redfish/tests/daemon.conf
Symbolic link
@ -0,0 +1 @@
|
||||
../../../data/daemon.conf
|
||||
Binary file not shown.
Binary file not shown.
1
plugins/redfish/tests/redfish-smbios.bin
Symbolic link
1
plugins/redfish/tests/redfish-smbios.bin
Symbolic link
@ -0,0 +1 @@
|
||||
../../../src/fuzzing/firmware/redfish-smbios.bin
|
||||
4
plugins/redfish/tests/redfish.conf
Normal file
4
plugins/redfish/tests/redfish.conf
Normal file
@ -0,0 +1,4 @@
|
||||
[redfish]
|
||||
Uri=http://localhost:4661
|
||||
Username=username2
|
||||
Password=password2
|
||||
269
plugins/redfish/tests/redfish.py
Executable file
269
plugins/redfish/tests/redfish.py
Executable file
@ -0,0 +1,269 @@
|
||||
#!/usr/bin/python3
|
||||
# pylint: disable=invalid-name,missing-docstring
|
||||
#
|
||||
# Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
|
||||
import json
|
||||
|
||||
from flask import Flask, Response, request, g
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
HARDCODED_USERNAME = "username2"
|
||||
HARDCODED_PASSWORD = "password2"
|
||||
|
||||
app._percentage: int = 0
|
||||
|
||||
|
||||
def _failure(msg: str, status=400):
|
||||
res = {
|
||||
"error": {"message": msg},
|
||||
}
|
||||
return Response(response=json.dumps(msg), status=401, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/")
|
||||
def index():
|
||||
|
||||
# reset counter
|
||||
app._percentage = 0
|
||||
|
||||
# check password from the config file
|
||||
try:
|
||||
if (
|
||||
request.authorization["username"] != HARDCODED_USERNAME
|
||||
or request.authorization["password"] != HARDCODED_PASSWORD
|
||||
):
|
||||
return _failure("unauthorised", status=401)
|
||||
except (KeyError, TypeError):
|
||||
return _failure("invalid")
|
||||
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/",
|
||||
"RedfishVersion": "1.6.0",
|
||||
"UUID": "92384634-2938-2342-8820-489239905423",
|
||||
"UpdateService": {"@odata.id": "/redfish/v1/UpdateService"},
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/UpdateService")
|
||||
def update_service():
|
||||
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/UpdateService",
|
||||
"@odata.type": "#UpdateService.v1_8_0.UpdateService",
|
||||
"FirmwareInventory": {
|
||||
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory"
|
||||
},
|
||||
"MultipartHttpPushUri": "/FWUpdate",
|
||||
"HttpPushUri": "/FWUpdate",
|
||||
"HttpPushUriOptions": {
|
||||
"HttpPushUriApplyTime": {
|
||||
"ApplyTime": "Immediate",
|
||||
}
|
||||
},
|
||||
"HttpPushUriOptionsBusy": False,
|
||||
"ServiceEnabled": True,
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/UpdateService/FirmwareInventory")
|
||||
def firmware_inventory():
|
||||
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
|
||||
"@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
|
||||
"Members": [
|
||||
{"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC"},
|
||||
{"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BIOS"},
|
||||
],
|
||||
"Members@odata.count": 2,
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/UpdateService/FirmwareInventory/BMC")
|
||||
def firmware_inventory_bmc():
|
||||
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC",
|
||||
"@odata.type": "#SoftwareInventory.v1_2_3.SoftwareInventory",
|
||||
"Id": "BMC",
|
||||
"LowestSupportedVersion": "11A-0.12",
|
||||
"Manufacturer": "Lenovo",
|
||||
"Name": "Lenovo BMC Firmware",
|
||||
"RelatedItem": [{"@odata.id": "/redfish/v1/Managers/BMC"}],
|
||||
"ReleaseDate": "2017-08-22T12:00:00",
|
||||
"SoftwareId": "UEFI-AFE1-6",
|
||||
"UefiDevicePaths": ["BMC(0x1,0x0ABCDEF)"],
|
||||
"Updateable": True,
|
||||
"Version": "11A-1.02",
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/Managers/BMC")
|
||||
def redfish_managers_bmc():
|
||||
|
||||
res = {}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions/slot_2.00")
|
||||
def firmware_chassis_pcie_function_slot2():
|
||||
|
||||
res = {
|
||||
"VendorId": "0x14e4",
|
||||
"FunctionId": 1,
|
||||
"SubsystemId": "0x4042",
|
||||
"DeviceClass": "NetworkController",
|
||||
"SubsystemVendorId": "0x17aa",
|
||||
"DeviceId": "0x165f",
|
||||
"RevisionId": "0x00",
|
||||
"ClassCode": "0x020000",
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions")
|
||||
def firmware_chassis_pcie_functions():
|
||||
|
||||
res = {
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions/slot_2.00"
|
||||
}
|
||||
],
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/Systems/437XR1138R2")
|
||||
def firmware_systems_slot7():
|
||||
|
||||
res = {
|
||||
"SerialNumber": "12345",
|
||||
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3",
|
||||
"PCIeFunctions": {
|
||||
"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions"
|
||||
},
|
||||
"DeviceType": "SingleFunction",
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/UpdateService/FirmwareInventory/BIOS")
|
||||
def firmware_inventory_bios():
|
||||
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BIOS",
|
||||
"@odata.type": "#SoftwareInventory.v1_2_3.SoftwareInventory",
|
||||
"Id": "BIOS",
|
||||
"LowestSupportedVersion": "P79 v1.10",
|
||||
"Manufacturer": "Contoso",
|
||||
"Name": "Contoso BIOS Firmware",
|
||||
"RelatedItem": [{"@odata.id": "/redfish/v1/Systems/437XR1138R2"}],
|
||||
"ReleaseDate": "2017-12-06T12:00:00",
|
||||
"SoftwareId": "FEE82A67-6CE2-4625-9F44-237AD2402C28",
|
||||
"Updateable": True,
|
||||
"Version": "P79 v1.45",
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/TaskService/999")
|
||||
def task_manager():
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/TaskService/999",
|
||||
"@odata.type": "#Task.v1_4_3.Task",
|
||||
"Id": "545",
|
||||
"Name": "Task 545",
|
||||
}
|
||||
return Response(json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/redfish/v1/TaskService/Tasks/545")
|
||||
def task_status():
|
||||
|
||||
res = {
|
||||
"@odata.id": "/redfish/v1/TaskService/Tasks/545",
|
||||
"@odata.type": "#Task.v1_4_3.Task",
|
||||
"Id": "545",
|
||||
"Name": "Task 545",
|
||||
"PercentComplete": app._percentage,
|
||||
}
|
||||
if app._percentage == 0:
|
||||
res["TaskState"] = "Running"
|
||||
elif app._percentage in [25, 50, 75]:
|
||||
res["TaskState"] = "Running"
|
||||
res["TaskStatus"] = "OK"
|
||||
res["Messages"] = [
|
||||
{
|
||||
"Message": "Applying image",
|
||||
"MessageId": "Update.1.1.TransferringToComponent",
|
||||
}
|
||||
]
|
||||
elif app._percentage == 100:
|
||||
res["TaskState"] = "Completed"
|
||||
res["TaskStatus"] = "OK"
|
||||
res["Messages"] = [
|
||||
{
|
||||
"Message": "Applying image",
|
||||
"MessageId": "Update.1.1.TransferringToComponent",
|
||||
},
|
||||
{
|
||||
"Message": "A reset is required",
|
||||
"MessageId": "Base.1.10.ResetRequired",
|
||||
},
|
||||
{
|
||||
"Message": "Task completed OK",
|
||||
"MessageId": "TaskEvent.1.0.TaskCompletedOK",
|
||||
},
|
||||
]
|
||||
else:
|
||||
res["TaskState"] = "Exception"
|
||||
res["TaskStatus"] = "Warning"
|
||||
res["Messages"] = [
|
||||
{
|
||||
"Message": "Error verifying image",
|
||||
"MessageId": "Update.1.0.ApplyFailed",
|
||||
"Severity": "Warning",
|
||||
}
|
||||
]
|
||||
app._percentage += 25
|
||||
return Response(response=json.dumps(res), status=200, mimetype="application/json")
|
||||
|
||||
|
||||
@app.route("/FWUpdate", methods=["POST"])
|
||||
def fwupdate():
|
||||
|
||||
data = json.loads(request.form["UpdateParameters"])
|
||||
if data["@Redfish.OperationApplyTime"] != "Immediate":
|
||||
return _failure("apply invalid")
|
||||
if data["Targets"][0] != "/redfish/v1/UpdateService/FirmwareInventory/BMC":
|
||||
return _failure("id invalid")
|
||||
fileitem = request.files["UpdateFile"]
|
||||
if not fileitem.filename.endswith(".bin"):
|
||||
return _failure("filename invalid")
|
||||
if fileitem.read().decode() != "hello":
|
||||
return _failure("data invalid")
|
||||
res = {
|
||||
"Version": "P79 v1.45",
|
||||
"@odata.id": "/redfish/v1/TaskService/Tasks/545",
|
||||
"TaskMonitor": "/redfish/v1/TaskService/999",
|
||||
}
|
||||
# Location set to the URI of a task monitor.
|
||||
return Response(
|
||||
json.dumps(res),
|
||||
status=202,
|
||||
mimetype="application/json",
|
||||
headers={"Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/545"},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=4661)
|
||||
Loading…
Reference in New Issue
Block a user