fwupd/src/fu-release.c
Richard Hughes 76138349e2 Fix a potential critical warning when parsing firmware
PVS: The 'filename' pointer was utilized before it was verified against nullptr.
2022-09-09 19:23:29 +01:00

1044 lines
32 KiB
C

/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuRelease"
#include "config.h"
#include "fu-device-private.h"
#include "fu-keyring-utils.h"
#include "fu-release-common.h"
#include "fu-release.h"
/**
* FuRelease:
*
* An installable entity that has been loaded and verified for a specific device.
*
* See also: [class@FwupdRelease]
*/
struct _FuRelease {
FwupdRelease parent_instance;
FuEngineRequest *request;
FuDevice *device;
FwupdRemote *remote;
FuConfig *config;
GBytes *blob_fw;
FwupdReleaseFlags trust_flags;
gboolean is_downgrade;
GPtrArray *soft_reqs; /* nullable, element-type XbNode */
GPtrArray *hard_reqs; /* nullable, element-type XbNode */
};
G_DEFINE_TYPE(FuRelease, fu_release, FWUPD_TYPE_RELEASE)
/**
* fu_release_set_request:
* @self: a #FuRelease
* @request: (nullable): a #FuEngineRequest
*
* Sets the user request which created this operation.
**/
void
fu_release_set_request(FuRelease *self, FuEngineRequest *request)
{
g_return_if_fail(FU_IS_RELEASE(self));
g_set_object(&self->request, request);
}
/**
* fu_release_get_request:
* @self: a #FuRelease
*
* Gets the user request which created this operation.
*
* Returns: (transfer none) (nullable): request
**/
FuEngineRequest *
fu_release_get_request(FuRelease *self)
{
g_return_val_if_fail(FU_IS_RELEASE(self), NULL);
return self->request;
}
/**
* fu_release_set_device:
* @self: a #FuRelease
* @device: (nullable): a #FuDevice
*
* Sets the device this release should use when checking requirements.
**/
void
fu_release_set_device(FuRelease *self, FuDevice *device)
{
g_return_if_fail(FU_IS_RELEASE(self));
g_set_object(&self->device, device);
}
/**
* fu_release_get_device:
* @self: a #FuRelease
*
* Gets the device this release was loaded for.
*
* Returns: (transfer none) (nullable): device
**/
FuDevice *
fu_release_get_device(FuRelease *self)
{
g_return_val_if_fail(FU_IS_RELEASE(self), NULL);
return self->device;
}
/**
* fu_release_get_fw_blob:
* @self: a #FuRelease
*
* Gets the firmware payload to use when installing this release.
*
* Returns: (transfer none) (nullable): data
**/
GBytes *
fu_release_get_fw_blob(FuRelease *self)
{
g_return_val_if_fail(FU_IS_RELEASE(self), NULL);
return self->blob_fw;
}
/**
* fu_release_get_soft_reqs:
* @self: a #FuRelease
*
* Gets the additional soft requirements that need to be checked in the engine.
*
* Returns: (transfer none) (nullable) (element-type XbNode): nodes
**/
GPtrArray *
fu_release_get_soft_reqs(FuRelease *self)
{
g_return_val_if_fail(FU_IS_RELEASE(self), NULL);
return self->soft_reqs;
}
/**
* fu_release_get_soft_reqs:
* @self: a #FuRelease
*
* Gets the additional hard requirements that need to be checked in the engine.
*
* Returns: (transfer none) (nullable) (element-type XbNode): nodes
**/
GPtrArray *
fu_release_get_hard_reqs(FuRelease *self)
{
g_return_val_if_fail(FU_IS_RELEASE(self), NULL);
return self->hard_reqs;
}
/**
* fu_release_set_remote:
* @self: a #FuRelease
* @remote: (nullable): a #FwupdRemote
*
* Sets the remote this release should use when loading. This is typically set by the engine by
*watching the `remote-id` property to be set and then querying the internal cached list of
*`FuRemote`s.
**/
void
fu_release_set_remote(FuRelease *self, FwupdRemote *remote)
{
g_return_if_fail(FU_IS_RELEASE(self));
g_set_object(&self->remote, remote);
}
/**
* fu_release_set_config:
* @self: a #FuRelease
* @config: (nullable): a #FuConfig
*
* Sets the config to use when loading. The config may be used for things like ordering attributes
*like protocol priority.
**/
void
fu_release_set_config(FuRelease *self, FuConfig *config)
{
g_return_if_fail(FU_IS_RELEASE(self));
g_set_object(&self->config, config);
}
static gchar *
fu_release_get_localized_xpath(FuRelease *self, const gchar *element)
{
GString *xpath = g_string_new(element);
const gchar *locale = NULL;
/* optional; not set in tests */
if (self->request != NULL)
locale = fu_engine_request_get_locale(self->request);
/* prefer the users locale if set */
if (locale != NULL) {
g_autofree gchar *xpath_locale = NULL;
xpath_locale = g_strdup_printf("%s[@xml:lang='%s']|", element, locale);
g_string_prepend(xpath, xpath_locale);
}
return g_string_free(xpath, FALSE);
}
/* convert hex and decimal versions to dotted style */
static gchar *
fu_release_get_release_version(FuRelease *self, const gchar *version, GError **error)
{
FwupdVersionFormat fmt = fu_device_get_version_format(self->device);
guint64 ver_uint32;
g_autoptr(GError) error_local = NULL;
/* already dotted notation */
if (g_strstr_len(version, -1, ".") != NULL)
return g_strdup(version);
/* don't touch my version! */
if (fmt == FWUPD_VERSION_FORMAT_PLAIN || fmt == FWUPD_VERSION_FORMAT_UNKNOWN)
return g_strdup(version);
/* parse as integer */
if (!fu_strtoull(version, &ver_uint32, 1, G_MAXUINT32, &error_local)) {
g_warning("invalid release version %s: %s", version, error_local->message);
return g_strdup(version);
}
/* convert to dotted decimal */
return fu_version_from_uint32((guint32)ver_uint32, fmt);
}
static gboolean
fu_release_load_artifact(FuRelease *self, XbNode *artifact, GError **error)
{
const gchar *filename;
guint64 size;
g_autoptr(GPtrArray) locations = NULL;
g_autoptr(GPtrArray) checksums = NULL;
/* filename */
filename = xb_node_query_text(artifact, "filename", NULL);
if (filename != NULL && !g_str_has_suffix(filename, ".cab")) {
/* some firmware archives was signed with <artifact type="binary"> where the
* checksums were the *content* checksums, not the *container* checksum */
g_debug("ignoring non-binary artifact entry: %s", filename);
return TRUE;
}
if (filename != NULL)
fwupd_release_set_filename(FWUPD_RELEASE(self), filename);
/* location */
locations = xb_node_query(artifact, "location", 0, NULL);
if (locations != NULL) {
for (guint i = 0; i < locations->len; i++) {
XbNode *n = g_ptr_array_index(locations, i);
g_autofree gchar *scheme = NULL;
/* check the scheme is allowed */
scheme = fu_release_uri_get_scheme(xb_node_get_text(n));
if (scheme != NULL) {
guint prio = fu_config_get_uri_scheme_prio(self->config, scheme);
if (prio == G_MAXUINT)
continue;
}
/* build the complete URI */
if (self->remote != NULL) {
g_autofree gchar *uri = NULL;
uri = fwupd_remote_build_firmware_uri(self->remote,
xb_node_get_text(n),
NULL);
if (uri != NULL) {
fwupd_release_add_location(FWUPD_RELEASE(self), uri);
continue;
}
}
fwupd_release_add_location(FWUPD_RELEASE(self), xb_node_get_text(n));
}
}
/* checksum */
checksums = xb_node_query(artifact, "checksum", 0, NULL);
if (checksums != NULL) {
for (guint i = 0; i < checksums->len; i++) {
XbNode *n = g_ptr_array_index(checksums, i);
fwupd_release_add_checksum(FWUPD_RELEASE(self), xb_node_get_text(n));
}
}
/* size */
size = xb_node_query_text_as_uint(artifact, "size[@type='installed']", NULL);
if (size != G_MAXUINT64)
fwupd_release_set_size(FWUPD_RELEASE(self), size);
/* success */
return TRUE;
}
static gint
fu_release_scheme_compare_cb(gconstpointer a, gconstpointer b, gpointer user_data)
{
FuRelease *self = FU_RELEASE(user_data);
const gchar *location1 = *((const gchar **)a);
const gchar *location2 = *((const gchar **)b);
g_autofree gchar *scheme1 = fu_release_uri_get_scheme(location1);
g_autofree gchar *scheme2 = fu_release_uri_get_scheme(location2);
guint prio1 = fu_config_get_uri_scheme_prio(self->config, scheme1);
guint prio2 = fu_config_get_uri_scheme_prio(self->config, scheme2);
if (prio1 < prio2)
return -1;
if (prio1 > prio2)
return 1;
return 0;
}
static gboolean
fu_release_check_requirements_version_check(FuRelease *self, GError **error)
{
if (self->hard_reqs != NULL) {
for (guint i = 0; i < self->hard_reqs->len; i++) {
XbNode *req = g_ptr_array_index(self->hard_reqs, i);
if (g_strcmp0(xb_node_get_element(req), "firmware") == 0 &&
xb_node_get_text(req) == NULL) {
return TRUE;
}
}
}
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no firmware requirement");
return FALSE;
}
static gchar *
fu_release_verfmts_to_string(GPtrArray *verfmts)
{
GString *str = g_string_new(NULL);
for (guint i = 0; i < verfmts->len; i++) {
XbNode *verfmt = g_ptr_array_index(verfmts, i);
const gchar *tmp = xb_node_get_text(verfmt);
g_string_append_printf(str, "%s;", tmp);
}
if (str->len > 0)
g_string_truncate(str, str->len - 1);
return g_string_free(str, FALSE);
}
static gboolean
fu_release_check_verfmt(FuRelease *self,
GPtrArray *verfmts,
FwupdInstallFlags flags,
GError **error)
{
FwupdVersionFormat fmt_dev = fu_device_get_version_format(self->device);
g_autofree gchar *verfmts_str = NULL;
/* no device format */
if (fmt_dev == FWUPD_VERSION_FORMAT_UNKNOWN && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
verfmts_str = fu_release_verfmts_to_string(verfmts);
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"release version format '%s' but no device version format",
verfmts_str);
return FALSE;
}
/* compare all version formats */
for (guint i = 0; i < verfmts->len; i++) {
XbNode *verfmt = g_ptr_array_index(verfmts, i);
const gchar *tmp = xb_node_get_text(verfmt);
FwupdVersionFormat fmt_rel = fwupd_version_format_from_string(tmp);
if (fmt_dev == fmt_rel)
return TRUE;
}
verfmts_str = fu_release_verfmts_to_string(verfmts);
if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Firmware version formats were different, "
"device was '%s' and release is '%s'",
fwupd_version_format_to_string(fmt_dev),
verfmts_str);
return FALSE;
}
g_warning("ignoring version format difference %s:%s",
fwupd_version_format_to_string(fmt_dev),
verfmts_str);
return TRUE;
}
/* these can all be done without the daemon */
static gboolean
fu_release_check_requirements(FuRelease *self,
XbNode *component,
XbNode *rel,
FwupdInstallFlags install_flags,
GError **error)
{
const gchar *branch_new;
const gchar *branch_old;
const gchar *protocol;
const gchar *version;
const gchar *version_lowest;
gboolean matches_guid = FALSE;
gint vercmp;
g_autoptr(GError) error_local = NULL;
g_autoptr(GPtrArray) provides = NULL;
g_autoptr(GPtrArray) verfmts = NULL;
/* does this component provide a GUID the device has */
provides = xb_node_query(component, "provides/firmware[@type='flashed']", 0, &error_local);
if (provides == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No supported devices found: %s",
error_local->message);
return FALSE;
}
for (guint i = 0; i < provides->len; i++) {
XbNode *provide = g_ptr_array_index(provides, i);
if (fu_device_has_guid(self->device, xb_node_get_text(provide))) {
matches_guid = TRUE;
break;
}
}
if (!matches_guid) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No supported devices found");
return FALSE;
}
/* device requires a version check */
if (fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED)) {
if (!fu_release_check_requirements_version_check(self, error)) {
g_prefix_error(error, "device requires firmware with a version check: ");
return FALSE;
}
}
/* does the protocol match */
protocol = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateProtocol']", NULL);
if (fu_device_get_protocols(self->device)->len != 0 && protocol != NULL &&
!fu_device_has_protocol(self->device, protocol) &&
(install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
g_autofree gchar *str = NULL;
str = fu_strjoin("|", fu_device_get_protocols(self->device));
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Device %s does not support %s, only %s",
fu_device_get_name(self->device),
protocol,
str);
return FALSE;
}
/* check the device is not locked */
if (fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_LOCKED)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Device %s [%s] is locked",
fu_device_get_name(self->device),
fu_device_get_id(self->device));
return FALSE;
}
/* check the branch is not switching */
branch_new = xb_node_query_text(component, "branch", NULL);
branch_old = fu_device_get_branch(self->device);
if ((install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0 &&
g_strcmp0(branch_old, branch_new) != 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Device %s [%s] would switch firmware branch from %s to %s",
fu_device_get_name(self->device),
fu_device_get_id(self->device),
branch_old != NULL ? branch_old : "default",
branch_new != NULL ? branch_new : "default");
return FALSE;
}
/* no update abilities */
if (!fu_engine_request_has_feature_flag(self->request, FWUPD_FEATURE_FLAG_SHOW_PROBLEMS) &&
!fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_UPDATABLE)) {
g_autoptr(GString) str = g_string_new(NULL);
g_string_append_printf(str,
"Device %s [%s] does not currently allow updates",
fu_device_get_name(self->device),
fu_device_get_id(self->device));
if (fu_device_get_update_error(self->device) != NULL) {
g_string_append_printf(str,
": %s",
fu_device_get_update_error(self->device));
}
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, str->str);
return FALSE;
}
/* called with online update, test if device is supposed to allow this */
if ((install_flags & FWUPD_INSTALL_FLAG_OFFLINE) == 0 &&
(install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0 &&
fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Device %s [%s] only allows offline updates",
fu_device_get_name(self->device),
fu_device_get_id(self->device));
return FALSE;
}
/* get device */
version = fu_device_get_version(self->device);
if (version == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device %s [%s] has no firmware version",
fu_device_get_name(self->device),
fu_device_get_id(self->device));
return FALSE;
}
/* check the version formats match if set in the release */
if ((install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0 &&
(install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0) {
verfmts =
xb_node_query(component, "custom/value[@key='LVFS::VersionFormat']", 0, NULL);
if (verfmts != NULL) {
if (!fu_release_check_verfmt(self, verfmts, install_flags, error))
return FALSE;
}
}
/* compare to the lowest supported version, if it exists */
version_lowest = fu_device_get_version_lowest(self->device);
if (version_lowest != NULL &&
fu_version_compare(version_lowest,
fu_release_get_version(self),
fu_device_get_version_format(self->device)) > 0 &&
(install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Specified firmware is older than the minimum "
"required version '%s < %s'",
version,
version_lowest);
return FALSE;
}
/* is this a downgrade or re-install */
vercmp = fu_version_compare(version,
fu_release_get_version(self),
fu_device_get_version_format(self->device));
if (fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE) &&
vercmp >= 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Device only supports version upgrades");
return FALSE;
}
if (vercmp == 0 && (install_flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_SAME,
"Specified firmware is already installed '%s'",
fu_release_get_version(self));
return FALSE;
}
self->is_downgrade = vercmp > 0;
if (self->is_downgrade && (install_flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) == 0 &&
(install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_NEWER,
"Specified firmware is older than installed '%s < %s'",
fu_release_get_version(self),
version);
return FALSE;
}
/* verify */
if (!fu_keyring_get_release_flags(rel, &self->trust_flags, &error_local)) {
if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
g_warning("Ignoring verification for %s: %s",
fu_device_get_name(self->device),
error_local->message);
} else {
g_propagate_error(error, g_steal_pointer(&error_local));
return FALSE;
}
}
return TRUE;
}
/**
* fu_release_load:
* @self: a #FuRelease
* @component: (not nullable): a #XbNode
* @rel_optional: (nullable): a #XbNode
* @install_flags: a #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: (nullable): optional return location for an error
*
* Loads then checks any requirements of this release. This will typically involve checking
* that the device can accept the component (the GUIDs match) and that the device can be
* upgraded with this firmware version.
*
* Returns: %TRUE if the release was loaded and the requirements passed
**/
gboolean
fu_release_load(FuRelease *self,
XbNode *component,
XbNode *rel_optional,
FwupdInstallFlags install_flags,
GError **error)
{
const gchar *tmp;
guint64 tmp64;
GBytes *blob_fw_tmp;
g_autofree gchar *name_xpath = NULL;
g_autofree gchar *namevs_xpath = NULL;
g_autofree gchar *summary_xpath = NULL;
g_autofree gchar *description_xpath = NULL;
g_autoptr(GPtrArray) cats = NULL;
g_autoptr(GPtrArray) tags = NULL;
g_autoptr(GPtrArray) issues = NULL;
g_autoptr(XbNode) artifact = NULL;
g_autoptr(XbNode) description = NULL;
g_autoptr(XbNode) rel = NULL;
g_autoptr(GError) error_soft = NULL;
g_autoptr(GError) error_hard = NULL;
g_return_val_if_fail(FU_IS_RELEASE(self), FALSE);
g_return_val_if_fail(XB_IS_NODE(component), FALSE);
g_return_val_if_fail(rel_optional == NULL || XB_IS_NODE(rel_optional), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
g_return_val_if_fail(fwupd_release_get_appstream_id(FWUPD_RELEASE(self)) == NULL, FALSE);
/* set from the component */
tmp = xb_node_query_text(component, "id", NULL);
if (tmp != NULL)
fwupd_release_set_appstream_id(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "url[@type='homepage']", NULL);
if (tmp != NULL)
fwupd_release_set_homepage(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "project_license", NULL);
if (tmp != NULL)
fwupd_release_set_license(FWUPD_RELEASE(self), tmp);
name_xpath = fu_release_get_localized_xpath(self, "name");
tmp = xb_node_query_text(component, name_xpath, NULL);
if (tmp != NULL)
fwupd_release_set_name(FWUPD_RELEASE(self), tmp);
summary_xpath = fu_release_get_localized_xpath(self, "summary");
tmp = xb_node_query_text(component, summary_xpath, NULL);
if (tmp != NULL)
fwupd_release_set_summary(FWUPD_RELEASE(self), tmp);
namevs_xpath = fu_release_get_localized_xpath(self, "name_variant_suffix");
tmp = xb_node_query_text(component, namevs_xpath, NULL);
if (tmp != NULL)
fwupd_release_set_name_variant_suffix(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "branch", NULL);
if (tmp != NULL)
fwupd_release_set_branch(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "developer_name", NULL);
if (tmp != NULL)
fwupd_release_set_vendor(FWUPD_RELEASE(self), tmp);
/* use default release */
if (rel_optional == NULL) {
g_autoptr(GError) error_local = NULL;
#if LIBXMLB_CHECK_VERSION(0, 2, 0)
g_autoptr(XbQuery) query = NULL;
query = xb_query_new_full(xb_node_get_silo(component),
"releases/release",
XB_QUERY_FLAG_FORCE_NODE_CACHE,
error);
if (query == NULL)
return FALSE;
rel = xb_node_query_first_full(component, query, &error_local);
#else
rel = xb_node_query_first(component, "releases/release", &error_local);
#endif
if (rel == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"failed to get default release: %s",
error_local->message);
return FALSE;
}
} else {
rel = g_object_ref(rel_optional);
}
/* the version is fixed up with the device format */
tmp = xb_node_get_attr(rel, "version");
if (tmp == NULL) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version unset");
return FALSE;
}
if (self->device != NULL) {
g_autofree gchar *version_rel = NULL;
version_rel = fu_release_get_release_version(self, tmp, error);
if (version_rel == NULL)
return FALSE;
fwupd_release_set_version(FWUPD_RELEASE(self), version_rel);
} else {
fwupd_release_set_version(FWUPD_RELEASE(self), tmp);
}
/* optional release ID -- currently a integer but maybe namespaced in the future */
fwupd_release_set_id(FWUPD_RELEASE(self), xb_node_get_attr(rel, "id"));
/* find the remote */
tmp = xb_node_query_text(component, "../custom/value[@key='fwupd::RemoteId']", NULL);
if (tmp != NULL)
fwupd_release_set_remote_id(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "../custom/value[@key='LVFS::Distributor']", NULL);
if (g_strcmp0(tmp, "community") == 0)
fwupd_release_add_flag(FWUPD_RELEASE(self), FWUPD_RELEASE_FLAG_IS_COMMUNITY);
/* this is the more modern way to do this */
artifact = xb_node_query_first(rel, "artifacts/artifact[@type='binary']", NULL);
if (artifact != NULL) {
if (!fu_release_load_artifact(self, artifact, error))
return FALSE;
}
description_xpath = fu_release_get_localized_xpath(self, "description");
description = xb_node_query_first(rel, description_xpath, NULL);
if (description != NULL) {
g_autofree gchar *xml = NULL;
g_autoptr(GString) str = NULL;
xml = xb_node_export(description, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, NULL);
str = g_string_new(xml);
if (self->device != NULL && self->request != NULL &&
fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_AFFECTS_FDE) &&
!fu_engine_request_has_feature_flag(self->request,
FWUPD_FEATURE_FLAG_FDE_WARNING)) {
g_string_prepend(
str,
"<p>Some of the platform secrets may be invalidated when "
"updating this firmware. Please ensure you have the volume "
"recovery key before continuing.</p>");
}
if (fwupd_release_has_flag(FWUPD_RELEASE(self), FWUPD_RELEASE_FLAG_IS_COMMUNITY) &&
self->request != NULL &&
!fu_engine_request_has_feature_flag(self->request,
FWUPD_FEATURE_FLAG_COMMUNITY_TEXT)) {
g_string_prepend(
str,
"<p>This firmware is provided by LVFS community "
"members and is not provided (or supported) by the original "
"hardware vendor. "
"Installing this update may also void any device warranty.</p>");
}
if (str->len > 0)
fwupd_release_set_description(FWUPD_RELEASE(self), str->str);
}
if (artifact == NULL) {
tmp = xb_node_query_text(rel, "location", NULL);
if (tmp != NULL) {
g_autofree gchar *uri = NULL;
if (self->remote != NULL)
uri = fwupd_remote_build_firmware_uri(self->remote, tmp, NULL);
if (uri == NULL)
uri = g_strdup(tmp);
fwupd_release_add_location(FWUPD_RELEASE(self), uri);
}
}
if (fwupd_release_get_locations(FWUPD_RELEASE(self))->len == 0 && self->remote != NULL &&
fwupd_remote_get_kind(self->remote) == FWUPD_REMOTE_KIND_DIRECTORY) {
tmp = xb_node_query_text(component,
"../custom/value[@key='fwupd::FilenameCache']",
NULL);
if (tmp != NULL) {
g_autofree gchar *uri = g_strdup_printf("file://%s", tmp);
fwupd_release_add_location(FWUPD_RELEASE(self), uri);
}
}
if (artifact == NULL) {
tmp = xb_node_query_text(rel, "checksum[@target='content']", NULL);
if (tmp != NULL)
fwupd_release_set_filename(FWUPD_RELEASE(self), tmp);
}
tmp = xb_node_query_text(rel, "url[@type='details']", NULL);
if (tmp != NULL)
fwupd_release_set_details_url(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(rel, "url[@type='source']", NULL);
if (tmp != NULL)
fwupd_release_set_source_url(FWUPD_RELEASE(self), tmp);
if (artifact == NULL) {
g_autoptr(GPtrArray) checksums = NULL;
checksums = xb_node_query(rel, "checksum[@target='container']", 0, NULL);
if (checksums != NULL) {
for (guint i = 0; i < checksums->len; i++) {
XbNode *n = g_ptr_array_index(checksums, i);
if (xb_node_get_text(n) == NULL)
continue;
fwupd_release_add_checksum(FWUPD_RELEASE(self),
xb_node_get_text(n));
}
}
}
if (artifact == NULL) {
tmp64 = xb_node_query_text_as_uint(rel, "size[@type='installed']", NULL);
if (tmp64 != G_MAXUINT64)
fwupd_release_set_size(FWUPD_RELEASE(self), tmp64);
}
if (fwupd_release_get_size(FWUPD_RELEASE(self)) == 0) {
GBytes *sz = xb_node_get_data(rel, "fwupd::ReleaseSize");
if (sz != NULL) {
const guint64 *sizeptr = g_bytes_get_data(sz, NULL);
fwupd_release_set_size(FWUPD_RELEASE(self), *sizeptr);
}
}
tmp = xb_node_get_attr(rel, "urgency");
if (tmp != NULL)
fwupd_release_set_urgency(FWUPD_RELEASE(self),
fwupd_release_urgency_from_string(tmp));
tmp64 = xb_node_get_attr_as_uint(rel, "install_duration");
if (tmp64 != G_MAXUINT64)
fwupd_release_set_install_duration(FWUPD_RELEASE(self), tmp64);
tmp64 = xb_node_get_attr_as_uint(rel, "timestamp");
if (tmp64 != G_MAXUINT64)
fwupd_release_set_created(FWUPD_RELEASE(self), tmp64);
cats = xb_node_query(component, "categories/category", 0, NULL);
if (cats != NULL) {
for (guint i = 0; i < cats->len; i++) {
XbNode *n = g_ptr_array_index(cats, i);
fwupd_release_add_category(FWUPD_RELEASE(self), xb_node_get_text(n));
}
}
tags = xb_node_query(component, "tags/tag[@namespace=$'lvfs']", 0, NULL);
if (tags != NULL) {
for (guint i = 0; i < tags->len; i++) {
XbNode *tag = g_ptr_array_index(tags, i);
fwupd_release_add_tag(FWUPD_RELEASE(self), xb_node_get_text(tag));
}
}
issues = xb_node_query(rel, "issues/issue", 0, NULL);
if (issues != NULL) {
for (guint i = 0; i < issues->len; i++) {
XbNode *n = g_ptr_array_index(issues, i);
fwupd_release_add_issue(FWUPD_RELEASE(self), xb_node_get_text(n));
}
}
tmp = xb_node_query_text(component, "screenshots/screenshot/caption", NULL);
if (tmp != NULL)
fwupd_release_set_detach_caption(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "screenshots/screenshot/image", NULL);
if (tmp != NULL) {
if (self->remote != NULL) {
g_autofree gchar *img = NULL;
img = fwupd_remote_build_firmware_uri(self->remote, tmp, error);
if (img == NULL)
return FALSE;
fwupd_release_set_detach_image(FWUPD_RELEASE(self), img);
} else {
fwupd_release_set_detach_image(FWUPD_RELEASE(self), tmp);
}
}
tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateProtocol']", NULL);
if (tmp != NULL)
fwupd_release_set_protocol(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateMessage']", NULL);
if (tmp != NULL)
fwupd_release_set_update_message(FWUPD_RELEASE(self), tmp);
tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateImage']", NULL);
if (tmp != NULL) {
if (self->remote != NULL) {
g_autofree gchar *img = NULL;
img = fwupd_remote_build_firmware_uri(self->remote, tmp, error);
if (img == NULL)
return FALSE;
fwupd_release_set_update_image(FWUPD_RELEASE(self), img);
} else {
fwupd_release_set_update_image(FWUPD_RELEASE(self), tmp);
}
}
/* hard and soft requirements */
self->hard_reqs = xb_node_query(component, "requires/*", 0, &error_hard);
if (self->hard_reqs == NULL) {
if (!g_error_matches(error_hard, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
!g_error_matches(error_hard, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) {
g_propagate_error(error, g_steal_pointer(&error_hard));
return FALSE;
}
}
self->soft_reqs = xb_node_query(component, "suggests/*|recommends/*", 0, &error_soft);
if (self->soft_reqs == NULL) {
if (!g_error_matches(error_soft, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
!g_error_matches(error_soft, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) {
g_propagate_error(error, g_steal_pointer(&error_soft));
return FALSE;
}
}
/* get per-release firmware blob */
blob_fw_tmp = xb_node_get_data(rel, "fwupd::FirmwareBlob");
if (blob_fw_tmp != NULL)
self->blob_fw = g_bytes_ref(blob_fw_tmp);
/* to build the firmware */
tmp = g_object_get_data(G_OBJECT(component), "fwupd::BuilderScript");
if (tmp != NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"fwupd::BuilderScript is no longer supported");
return FALSE;
}
/* sort the locations by scheme */
if (self->config != NULL) {
g_ptr_array_sort_with_data(fwupd_release_get_locations(FWUPD_RELEASE(self)),
fu_release_scheme_compare_cb,
self);
}
/* check requirements for device */
if (self->device != NULL && self->request != NULL &&
fu_engine_request_get_kind(self->request) == FU_ENGINE_REQUEST_KIND_ACTIVE) {
if (!fu_release_check_requirements(self, component, rel, install_flags, error))
return FALSE;
}
/* success */
return TRUE;
}
/**
* fu_release_get_trust_flags:
* @self: a #FuRelease
*
* Gets the trust flags for this task.
*
* NOTE: This is only set after fu_release_load() has been called successfully, and
* is only valid when a request has been set.
*
* Returns: the #FwupdReleaseFlags, e.g. #FWUPD_TRUST_FLAG_PAYLOAD
**/
FwupdReleaseFlags
fu_release_get_trust_flags(FuRelease *self)
{
g_return_val_if_fail(FU_IS_RELEASE(self), FALSE);
return self->trust_flags;
}
/**
* fu_release_get_action_id:
* @self: a #FuEngine
*
* Gets the PolicyKit action ID to use for the install operation.
*
* Returns: string, e.g. `org.freedesktop.fwupd.update-internal-trusted`
**/
const gchar *
fu_release_get_action_id(FuRelease *self)
{
/* relax authentication checks for removable devices */
if (!fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_INTERNAL)) {
if (self->is_downgrade) {
if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD)
return "org.freedesktop.fwupd.downgrade-hotplug-trusted";
return "org.freedesktop.fwupd.downgrade-hotplug";
}
if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD)
return "org.freedesktop.fwupd.update-hotplug-trusted";
return "org.freedesktop.fwupd.update-hotplug";
}
/* internal device */
if (self->is_downgrade) {
if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD)
return "org.freedesktop.fwupd.downgrade-internal-trusted";
return "org.freedesktop.fwupd.downgrade-internal";
}
if (self->trust_flags & FWUPD_TRUST_FLAG_PAYLOAD)
return "org.freedesktop.fwupd.update-internal-trusted";
return "org.freedesktop.fwupd.update-internal";
}
/**
* fu_release_compare:
* @release1: first task to compare.
* @release2: second task to compare.
*
* Compares two install tasks.
*
* Returns: 1, 0 or -1 if @release1 is greater, equal, or less than @release2, respectively.
**/
gint
fu_release_compare(FuRelease *release1, FuRelease *release2)
{
FuDevice *device1 = fu_release_get_device(release1);
FuDevice *device2 = fu_release_get_device(release2);
if (fu_device_get_order(device1) < fu_device_get_order(device2))
return -1;
if (fu_device_get_order(device1) > fu_device_get_order(device2))
return 1;
return 0;
}
static void
fu_release_init(FuRelease *self)
{
self->trust_flags = FWUPD_TRUST_FLAG_NONE;
}
static void
fu_release_finalize(GObject *obj)
{
FuRelease *self = FU_RELEASE(obj);
if (self->request != NULL)
g_object_unref(self->request);
if (self->device != NULL)
g_object_unref(self->device);
if (self->remote != NULL)
g_object_unref(self->remote);
if (self->config != NULL)
g_object_unref(self->config);
if (self->blob_fw != NULL)
g_bytes_unref(self->blob_fw);
if (self->soft_reqs != NULL)
g_ptr_array_unref(self->soft_reqs);
if (self->hard_reqs != NULL)
g_ptr_array_unref(self->hard_reqs);
G_OBJECT_CLASS(fu_release_parent_class)->finalize(obj);
}
static void
fu_release_class_init(FuReleaseClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fu_release_finalize;
}
FuRelease *
fu_release_new(void)
{
FuRelease *self;
self = g_object_new(FU_TYPE_RELEASE, NULL);
return FU_RELEASE(self);
}