mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 22:40:50 +00:00

It's actually quite hard to build a front-end for fwupd at the moment as you're never sure when the progress bar is going to zip back to 0% and start all over again. Some plugins go 0..100% for write, others go 0..100% for erase, then again for write, then *again* for verify. By creating a helper object we can easily split up the progress of the specific task, e.g. write_firmware(). We can encode at the plugin level "the erase takes 50% of the time, the write takes 40% and the read takes 10%". This means we can have a progressbar which goes up just once at a consistent speed.
844 lines
27 KiB
C
844 lines
27 KiB
C
/*
|
|
* 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))
|
|
|
|
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);
|
|
}
|
|
if (json_object_has_member(json_obj, "HotPluggable")) {
|
|
/* this is better than the heuristic we get from the device name */
|
|
if (json_object_get_boolean_member(json_obj, "HotPluggable"))
|
|
fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
|
|
else
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* ReleaseDate may or may not have a timezone */
|
|
if (json_object_has_member(member, "ReleaseDate")) {
|
|
const gchar *tmp = json_object_get_string_member(member, "ReleaseDate");
|
|
if (tmp != NULL && tmp[0] != '\0') {
|
|
g_autoptr(GDateTime) dt = NULL;
|
|
g_autoptr(GTimeZone) tz = g_time_zone_new_utc();
|
|
dt = g_date_time_new_from_iso8601(tmp, tz);
|
|
if (dt != NULL) {
|
|
guint64 unixtime = (guint64)g_date_time_to_unix(dt);
|
|
fu_device_set_version_build_date(dev, unixtime);
|
|
} else {
|
|
g_warning("failed to parse ISO8601 %s", 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;
|
|
FuProgress *progress;
|
|
} 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_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateAssignment") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyInProgress") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyCompleted") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyInProgress") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "Update.1.1.TransferringToComponent") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "Update.1.1.VerifyingAtComponent") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "Update.1.1.UpdateInProgress") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "Update.1.1.UpdateSuccessful") == 0) {
|
|
fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE);
|
|
return;
|
|
}
|
|
if (g_strcmp0(message_id, "Update.1.1.InstallingOnComponent") == 0) {
|
|
fu_progress_set_status(ctx->progress, 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_progress_set_percentage(ctx->progress, (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(FuProgress *progress, 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;
|
|
ctx->progress = g_object_ref(progress);
|
|
return ctx;
|
|
}
|
|
|
|
static void
|
|
fu_redfish_device_poll_ctx_free(FuRedfishDevicePollCtx *ctx)
|
|
{
|
|
g_hash_table_unref(ctx->messages_seen);
|
|
g_object_unref(ctx->progress);
|
|
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,
|
|
FuProgress *progress,
|
|
GError **error)
|
|
{
|
|
const guint timeout = 2400;
|
|
g_autoptr(GTimer) timer = g_timer_new();
|
|
g_autoptr(FuRedfishDevicePollCtx) ctx = fu_redfish_device_poll_ctx_new(progress, 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");
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS,
|
|
"wildcard-targets");
|
|
}
|
|
|
|
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);
|
|
}
|