blob: fd2b63bcc6202ddaec9b11c9cc7e0eab29b30f64 [file] [log] [blame]
/*
* Copyright 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#define G_LOG_DOMAIN "FuEngine"
#include "config.h"
#include "fwupd-remote-private.h"
#include "fu-engine-requirements.h"
static gboolean
fu_engine_requirements_require_vercmp_part(const gchar *compare,
const gchar *version_req,
const gchar *version,
FwupdVersionFormat fmt,
GError **error)
{
gboolean ret = FALSE;
gint rc = 0;
if (g_strcmp0(compare, "eq") == 0) {
rc = fu_version_compare(version, version_req, fmt);
ret = rc == 0;
} else if (g_strcmp0(compare, "ne") == 0) {
rc = fu_version_compare(version, version_req, fmt);
ret = rc != 0;
} else if (g_strcmp0(compare, "lt") == 0) {
rc = fu_version_compare(version, version_req, fmt);
ret = rc < 0;
} else if (g_strcmp0(compare, "gt") == 0) {
rc = fu_version_compare(version, version_req, fmt);
ret = rc > 0;
} else if (g_strcmp0(compare, "le") == 0) {
rc = fu_version_compare(version, version_req, fmt);
ret = rc <= 0;
} else if (g_strcmp0(compare, "ge") == 0) {
rc = fu_version_compare(version, version_req, fmt);
ret = rc >= 0;
} else if (g_strcmp0(compare, "glob") == 0) {
ret = g_pattern_match_simple(version_req, version);
} else if (g_strcmp0(compare, "regex") == 0) {
ret = g_regex_match_simple(version_req, version, 0, 0);
} else {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to compare [%s] and [%s]",
version_req,
version);
return FALSE;
}
/* set error */
if (!ret) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"failed predicate [%s %s %s]",
version_req,
compare,
version);
return FALSE;
}
/* success */
return TRUE;
}
typedef struct {
FuRelease *release;
FwupdInstallFlags install_flags;
gchar *fwupd_version;
gboolean has_hardware_req;
gboolean has_not_hardware_req;
gboolean has_id_requirement_glob;
gboolean has_client_id_requirement_glob;
} FuEngineRequirementsHelper;
static void
fu_engine_requirements_helper_free(FuEngineRequirementsHelper *helper)
{
g_object_unref(helper->release);
g_free(helper->fwupd_version);
g_free(helper);
}
static gboolean
fu_engine_requirements_check_fwupd_version(FuEngineRequirementsHelper *helper,
const gchar *fwupd_version_req,
GError **error)
{
if (fu_version_compare(helper->fwupd_version,
fwupd_version_req,
FWUPD_VERSION_FORMAT_UNKNOWN) < 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"needs %s >= %s",
FWUPD_DBUS_SERVICE,
fwupd_version_req);
return FALSE;
}
return TRUE;
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuEngineRequirementsHelper, fu_engine_requirements_helper_free)
static gboolean
fu_engine_requirements_require_vercmp(XbNode *req,
const gchar *version,
FwupdVersionFormat fmt,
FuEngineRequirementsHelper *helper,
GError **error)
{
const gchar *compare = xb_node_get_attr(req, "compare");
const gchar *version_req = xb_node_get_attr(req, "version");
g_auto(GStrv) split = NULL;
/* parse globbed version, e.g. `1.9.*=1.9.7|1.8.*=1.8.23|2.0.15`, or just `2.0.5` */
split = g_strsplit(version_req, "|", 0);
for (guint i = 0; split[i] != NULL; i++) {
g_auto(GStrv) kv = g_strsplit(split[i], "=", 2);
if (g_strv_length(kv) > 1) {
helper->has_id_requirement_glob = TRUE;
if (!g_pattern_match_simple(kv[0], version)) {
g_debug("skipping vercmp %s as version %s", kv[0], version);
continue;
}
g_debug("checking vercmp %s as version %s", kv[1], version);
return fu_engine_requirements_require_vercmp_part(compare,
kv[1],
version,
fmt,
error);
}
return fu_engine_requirements_require_vercmp_part(compare,
kv[0],
version,
fmt,
error);
}
/* success */
return TRUE;
}
static gboolean
fu_engine_requirements_check_not_child(FuEngine *self,
XbNode *req,
FuDevice *device,
FuEngineRequirementsHelper *helper,
GError **error)
{
GPtrArray *children = fu_device_get_children(device);
/* only <firmware> supported */
if (g_strcmp0(xb_node_get_element(req), "firmware") != 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"cannot handle not-child %s requirement",
xb_node_get_element(req));
return FALSE;
}
/* check each child */
for (guint i = 0; i < children->len; i++) {
FuDevice *child = g_ptr_array_index(children, i);
const gchar *version = fu_device_get_version(child);
if (version == NULL) {
g_autofree gchar *id_display = fu_device_get_id_display(device);
g_autofree gchar *id_display_child = fu_device_get_id_display(child);
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no version provided by %s, child of %s",
id_display_child,
id_display);
return FALSE;
}
if (fu_engine_requirements_require_vercmp(req,
version,
fu_device_get_version_format(child),
helper,
NULL)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not compatible with child device version %s",
version);
return FALSE;
}
}
return TRUE;
}
static gboolean
fu_engine_requirements_check_vendor_id(FuEngine *self,
XbNode *req,
FuDevice *device,
GError **error)
{
GPtrArray *vendor_ids;
const gchar *vendor_ids_metadata;
g_autofree gchar *vendor_ids_device = NULL;
/* devices without vendor IDs should not exist! */
vendor_ids = fu_device_get_vendor_ids(device);
if (vendor_ids->len == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"device [%s] has no vendor ID",
fu_device_get_id(device));
return FALSE;
}
/* metadata with empty vendor IDs should not exist! */
vendor_ids_metadata = xb_node_get_attr(req, "version");
if (vendor_ids_metadata == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"metadata has no vendor ID");
return FALSE;
}
/* it is always safe to use a regex, even for simple strings */
vendor_ids_device = fu_strjoin("|", vendor_ids);
if (!g_regex_match_simple(vendor_ids_metadata, vendor_ids_device, 0, 0)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with vendor %s: got %s",
vendor_ids_device,
vendor_ids_metadata);
return FALSE;
}
/* success */
return TRUE;
}
/* nocheck:name */
static gboolean
_fu_device_has_guids_any(FuDevice *self, gchar **guids)
{
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(guids != NULL, FALSE);
for (guint i = 0; guids[i] != NULL; i++) {
if (fu_device_has_guid(self, guids[i]))
return TRUE;
}
return FALSE;
}
static gboolean
fu_engine_requirements_check_firmware(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FuDevice *device = fu_release_get_device(helper->release);
const gchar *version;
const gchar *depth_str;
gint64 depth = G_MAXINT64;
g_autoptr(FuDevice) device_actual = g_object_ref(device);
g_autoptr(GError) error_local = NULL;
g_auto(GStrv) guids = NULL;
/* self tests */
if (device == NULL)
return TRUE;
/* look at the parent device */
depth_str = xb_node_get_attr(req, "depth");
if (depth_str != NULL) {
if (!fu_strtoll(depth_str, &depth, -1, 10, FU_INTEGER_BASE_AUTO, error))
return FALSE;
for (gint64 i = 0; i < depth; i++) {
FuDevice *device_tmp = fu_device_get_parent(device_actual);
if (device_tmp == NULL) {
g_autofree gchar *id_display =
fu_device_get_id_display(device_actual);
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No parent device for %s "
"(%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT ")",
id_display,
i,
depth);
return FALSE;
}
g_set_object(&device_actual, device_tmp);
}
}
/* check fwupd version requirement */
if (depth < 0) {
if (!fu_engine_requirements_check_fwupd_version(helper, "1.9.7", error)) {
g_prefix_error_literal(error, "requirement child firmware: ");
return FALSE;
}
}
/* old firmware version */
if (xb_node_get_text(req) == NULL) {
version = fu_device_get_version(device_actual);
if (!fu_engine_requirements_require_vercmp(
req,
version,
fu_device_get_version_format(device_actual),
helper,
&error_local)) {
if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) {
g_set_error(
error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with firmware version %s, requires >= %s",
version,
xb_node_get_attr(req, "version"));
return FALSE;
}
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with firmware version: %s",
error_local->message);
return FALSE;
}
return TRUE;
}
/* bootloader version */
if (g_strcmp0(xb_node_get_text(req), "bootloader") == 0) {
version = fu_device_get_version_bootloader(device_actual);
if (!fu_engine_requirements_require_vercmp(
req,
version,
fu_device_get_version_format(device_actual),
helper,
&error_local)) {
if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) {
g_set_error(
error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not compatible with bootloader version %s, requires >= %s",
version,
xb_node_get_attr(req, "version"));
return FALSE;
}
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Bootloader is not compatible");
return FALSE;
}
return TRUE;
}
/* vendor ID */
if (g_strcmp0(xb_node_get_text(req), "vendor-id") == 0) {
if (helper->install_flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID)
return TRUE;
return fu_engine_requirements_check_vendor_id(self, req, device_actual, error);
}
/* child version */
if (g_strcmp0(xb_node_get_text(req), "not-child") == 0)
return fu_engine_requirements_check_not_child(self,
req,
device_actual,
helper,
error);
/* another device, specified by GUID|GUID|GUID */
guids = g_strsplit(xb_node_get_text(req), "|", -1);
for (guint i = 0; guids[i] != NULL; i++) {
if (!fwupd_guid_is_valid(guids[i])) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"%s is not a valid GUID",
guids[i]);
return FALSE;
}
}
/* find if any of the other devices exists */
if (depth == G_MAXINT64) {
g_autoptr(FuDevice) device_tmp = NULL;
for (guint i = 0; guids[i] != NULL; i++) {
g_autoptr(GPtrArray) devices =
fu_engine_get_devices_by_guid(self, guids[i], NULL);
if (devices != NULL && devices->len > 0) {
device_tmp = g_object_ref(g_ptr_array_index(devices, 0));
break;
}
}
if (device_tmp == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No other device %s found",
xb_node_get_text(req));
return FALSE;
}
g_set_object(&device_actual, device_tmp);
} else if (depth == -1) {
GPtrArray *children;
FuDevice *child = NULL;
/* look for a child */
children = fu_device_get_children(device);
for (guint i = 0; i < children->len; i++) {
child = g_ptr_array_index(children, i);
if (_fu_device_has_guids_any(child, guids))
break;
child = NULL;
}
if (child == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No child found with GUID of %s",
xb_node_get_text(req));
return FALSE;
}
g_set_object(&device_actual, child);
/* look for a sibling */
} else if (depth == 0) {
FuDevice *child = NULL;
FuDevice *parent = fu_device_get_parent(device_actual);
GPtrArray *children;
/* no parent, so look for GUIDs on this device */
if (parent == NULL) {
if (!_fu_device_has_guids_any(device_actual, guids)) {
g_autofree gchar *id_display =
fu_device_get_id_display(device_actual);
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No GUID of %s on device %s",
xb_node_get_text(req),
id_display);
return FALSE;
}
return TRUE;
}
children = fu_device_get_children(parent);
for (guint i = 0; i < children->len; i++) {
child = g_ptr_array_index(children, i);
if (_fu_device_has_guids_any(child, guids))
break;
child = NULL;
}
if (child == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No sibling found with GUID of %s",
xb_node_get_text(req));
return FALSE;
}
g_set_object(&device_actual, child);
/* verify the parent device has the GUID */
} else {
if (!_fu_device_has_guids_any(device_actual, guids)) {
g_autofree gchar *id_display = fu_device_get_id_display(device_actual);
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"No GUID of %s on parent device %s",
xb_node_get_text(req),
id_display);
return FALSE;
}
}
/* get the version of the other device */
version = fu_device_get_version(device_actual);
if (version != NULL && xb_node_get_attr(req, "compare") != NULL &&
!fu_engine_requirements_require_vercmp(req,
version,
fu_device_get_version_format(device_actual),
helper,
&error_local)) {
if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with %s version %s, requires >= %s",
fu_device_get_name(device_actual),
version,
xb_node_get_attr(req, "version"));
return FALSE;
}
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with %s: %s",
fu_device_get_name(device_actual),
error_local->message);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_engine_requirements_check_id(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FuContext *ctx = fu_engine_get_context(self);
g_autoptr(GError) error_local = NULL;
const gchar *version;
/* sanity check */
if (xb_node_get_text(req) == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no requirement value supplied");
return FALSE;
}
version = fu_context_get_runtime_version(ctx, xb_node_get_text(req));
if (version == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no version available for %s",
xb_node_get_text(req));
return FALSE;
}
if (!fu_engine_requirements_require_vercmp(req,
version,
FWUPD_VERSION_FORMAT_UNKNOWN,
helper,
&error_local)) {
if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with %s version %s, requires >= %s",
xb_node_get_text(req),
version,
xb_node_get_attr(req, "version"));
return FALSE;
}
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with %s version: %s",
xb_node_get_text(req),
error_local->message);
return FALSE;
}
g_debug("requirement %s %s %s -> %s passed",
xb_node_get_attr(req, "version"),
xb_node_get_attr(req, "compare"),
version,
xb_node_get_text(req));
return TRUE;
}
static gboolean
fu_engine_requirements_check_hardware(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FuContext *ctx = fu_engine_get_context(self);
FuDevice *device = fu_release_get_device(helper->release);
g_auto(GStrv) hwid_split = NULL;
/* skip for tests */
if (device == NULL || fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED))
return TRUE;
/* sanity check */
if (xb_node_get_text(req) == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no requirement value supplied");
return FALSE;
}
/* split and treat as OR */
hwid_split = g_strsplit(xb_node_get_text(req), "|", -1);
for (guint i = 0; hwid_split[i] != NULL; i++) {
if (fu_context_has_hwid_guid(ctx, hwid_split[i])) {
g_debug("HWID provided %s", hwid_split[i]);
return TRUE;
}
}
/* nothing matched */
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no HWIDs matched %s",
xb_node_get_text(req));
return FALSE;
}
static gboolean
fu_engine_requirements_check_not_hardware(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FuContext *ctx = fu_engine_get_context(self);
g_auto(GStrv) hwid_split = NULL;
/* check fwupd version requirement */
if (!fu_engine_requirements_check_fwupd_version(helper, "1.9.10", error)) {
g_prefix_error_literal(error, "requirement not_hardware: ");
return FALSE;
}
/* sanity check */
if (xb_node_get_text(req) == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no requirement value supplied");
return FALSE;
}
/* split and treat as OR */
hwid_split = g_strsplit(xb_node_get_text(req), "|", -1);
for (guint i = 0; hwid_split[i] != NULL; i++) {
if (fu_context_has_hwid_guid(ctx, hwid_split[i])) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"%s HWIDs matched",
hwid_split[i]);
return FALSE;
}
}
/* nothing matched */
return TRUE;
}
static gboolean
fu_engine_requirements_check_phased_update(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FwupdRemote *remote;
const gchar *archive_filename;
const gchar *host_machine_id;
gint32 seed;
guint64 phased_update = 0;
/* i'm feeling lucky */
if (helper->install_flags & FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS) {
g_info("ignoring phased_update requirement");
return TRUE;
}
/* check fwupd version requirement */
if (!fu_engine_requirements_check_fwupd_version(helper, "2.0.17", error)) {
g_prefix_error_literal(error, "requirement phased_update: ");
return FALSE;
}
/* sanity check */
remote = fu_release_get_remote(helper->release);
if (remote == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no assigned remote for firmware release");
return FALSE;
}
if (fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_NO_PHASED_UPDATES)) {
g_info("ignoring phased update requirement due to remote policy");
return TRUE;
}
if (xb_node_get_text(req) == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no phased_update value supplied");
return FALSE;
}
if (!fu_strtoull(xb_node_get_text(req),
&phased_update,
2,
1024,
FU_INTEGER_BASE_AUTO,
error)) {
g_prefix_error_literal(error, "expected integer for phased_update: ");
return FALSE;
}
/* use multiple seeds, but return uniformly distributed random numbers */
archive_filename = fu_release_get_filename(helper->release);
host_machine_id = fu_engine_get_host_machine_id(self);
if (host_machine_id == NULL || archive_filename == NULL) {
seed = (gint32)fwupd_remote_get_mtime(remote);
} else {
guint32 seed_buf[4] = {0};
guint seed_bufsz = 0;
g_autoptr(GRand) rand = NULL;
seed_buf[seed_bufsz++] = g_str_hash(host_machine_id);
seed_buf[seed_bufsz++] = g_str_hash(archive_filename);
seed_buf[seed_bufsz++] = (guint32)fwupd_remote_get_mtime(remote);
rand = g_rand_new_with_seed_array(seed_buf, seed_bufsz);
seed = g_rand_int_range(rand, 0, G_MAXINT);
}
/* does seed divide perfectly into the phased update factor */
if (seed % phased_update != 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"remote mtime meant that deployment was delayed");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_engine_requirements_check_client(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FuEngineRequest *request = fu_release_get_request(helper->release);
FwupdFeatureFlags feature_flags;
g_auto(GStrv) feature_split = NULL;
/* sanity check */
if (xb_node_get_text(req) == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no requirement value supplied");
return FALSE;
}
/* split and treat as AND */
feature_split = g_strsplit(xb_node_get_text(req), "|", -1);
feature_flags = fu_engine_request_get_feature_flags(request);
for (guint i = 0; feature_split[i] != NULL; i++) {
FuEngineCapabilityFlags capability_flag;
FwupdFeatureFlags feature_flag;
/* client feature */
feature_flag = fwupd_feature_flag_from_string(feature_split[i]);
if (feature_flag != FWUPD_FEATURE_FLAG_UNKNOWN) {
if ((feature_flags & feature_flag) == 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"client feature requirement %s not supported",
feature_split[i]);
return FALSE;
}
continue;
}
/* assumed by the daemon version, see https://github.com/fwupd/fwupd/pull/8949 */
capability_flag = fu_engine_capability_flags_from_string(feature_split[i]);
if (capability_flag != FU_ENGINE_CAPABILITY_FLAG_UNKNOWN) {
if (capability_flag == FU_ENGINE_CAPABILITY_FLAG_ID_REQUIREMENT_GLOB) {
helper->has_client_id_requirement_glob = TRUE;
continue;
}
}
/* not recognized */
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"client requirement %s unknown",
feature_split[i]);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_engine_requirements_check_hard(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
FuContext *ctx = fu_engine_get_context(self);
/* ensure component requirement */
if (g_strcmp0(xb_node_get_element(req), "id") == 0)
return fu_engine_requirements_check_id(self, req, helper, error);
/* ensure firmware requirement */
if (g_strcmp0(xb_node_get_element(req), "firmware") == 0)
return fu_engine_requirements_check_firmware(self, req, helper, error);
/* ensure hardware requirement */
if (g_strcmp0(xb_node_get_element(req), "hardware") == 0) {
helper->has_hardware_req = TRUE;
if (!fu_context_has_flag(ctx, FU_CONTEXT_FLAG_LOADED_HWINFO))
return TRUE;
return fu_engine_requirements_check_hardware(self, req, helper, error);
}
if (g_strcmp0(xb_node_get_element(req), "not_hardware") == 0) {
helper->has_not_hardware_req = TRUE;
if (!fu_context_has_flag(ctx, FU_CONTEXT_FLAG_LOADED_HWINFO))
return TRUE;
return fu_engine_requirements_check_not_hardware(self, req, helper, error);
}
/* support slowing down deployments */
if (g_strcmp0(xb_node_get_element(req), "phased_update") == 0)
return fu_engine_requirements_check_phased_update(self, req, helper, error);
/* ensure client requirement */
if (g_strcmp0(xb_node_get_element(req), "client") == 0)
return fu_engine_requirements_check_client(self, req, helper, error);
/* not supported */
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"cannot handle requirement type %s",
xb_node_get_element(req));
return FALSE;
}
static gboolean
fu_engine_requirements_check_soft(FuEngine *self,
XbNode *req,
FuEngineRequirementsHelper *helper,
GError **error)
{
g_autoptr(GError) error_local = NULL;
if (!fu_engine_requirements_check_hard(self, req, helper, &error_local)) {
if (helper->install_flags & FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS) {
g_info("ignoring soft-requirement: %s", error_local->message);
return TRUE;
}
g_propagate_error(error, g_steal_pointer(&error_local));
return FALSE;
}
return TRUE;
}
static gboolean
fu_engine_requirements_is_specific_req(XbNode *req)
{
if (g_strcmp0(xb_node_get_element(req), "firmware") == 0 &&
xb_node_get_attr(req, "depth") != NULL)
return TRUE;
if (g_strcmp0(xb_node_get_element(req), "hardware") == 0)
return TRUE;
return FALSE;
}
static gchar *
fu_engine_requirements_get_newest_fwupd_version(FuEngine *self, FuRelease *release, GError **error)
{
GPtrArray *reqs = fu_release_get_hard_reqs(release);
g_autoptr(GString) newest_version = g_string_new("1.0.0");
/* trivial case */
if (reqs == NULL)
return g_string_free(g_steal_pointer(&newest_version), FALSE);
/* find the newest fwupd requirement */
for (guint i = 0; i < reqs->len; i++) {
XbNode *req = g_ptr_array_index(reqs, i);
if (g_strcmp0(xb_node_get_text(req), FWUPD_DBUS_SERVICE) == 0 &&
g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) {
const gchar *version = xb_node_get_attr(req, "version");
g_auto(GStrv) split = NULL;
if (version == NULL) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no version provided for requirement %s",
xb_node_get_text(req));
return NULL;
}
/* only care about the fallback version if using globs */
split = g_strsplit(version, "|", -1);
for (guint j = 0; split[j] != NULL; j++) {
if (g_strstr_len(split[j], -1, "=") != NULL)
continue;
/* is this newer than what we have */
if (fu_version_compare(split[j],
newest_version->str,
FWUPD_VERSION_FORMAT_UNKNOWN) > 0) {
g_string_assign(newest_version, split[j]);
}
}
}
}
/* success */
return g_string_free(g_steal_pointer(&newest_version), FALSE);
}
gboolean
fu_engine_requirements_check(FuEngine *self,
FuRelease *release,
FwupdInstallFlags flags,
GError **error)
{
FuDevice *device = fu_release_get_device(release);
GPtrArray *reqs;
gboolean has_specific_requirement = FALSE;
g_autoptr(FuEngineRequirementsHelper) helper = g_new0(FuEngineRequirementsHelper, 1);
g_return_val_if_fail(FU_IS_ENGINE(self), FALSE);
g_return_val_if_fail(FU_IS_RELEASE(release), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* create a small helper with common data */
helper->release = g_object_ref(release);
helper->install_flags = flags;
/* get the newest fwupd version requirement */
helper->fwupd_version =
fu_engine_requirements_get_newest_fwupd_version(self, release, error);
if (helper->fwupd_version == NULL)
return FALSE;
/* sanity check */
if (device != NULL && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) &&
!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) {
g_autofree gchar *id_display = fu_device_get_id_display(device);
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"%s is not updatable",
id_display);
return FALSE;
}
/* verify protocol */
if (device != NULL && fu_release_get_protocol(release) != NULL &&
!fu_device_has_protocol(device, fu_release_get_protocol(release))) {
g_autofree gchar *protocols = fu_strjoin(",", fu_device_get_protocols(device));
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"release needs protocol %s but device has %s",
fu_release_get_protocol(release),
protocols);
return FALSE;
}
/* hard requirements */
reqs = fu_release_get_hard_reqs(release);
if (reqs != NULL) {
for (guint i = 0; i < reqs->len; i++) {
XbNode *req = g_ptr_array_index(reqs, i);
if (!fu_engine_requirements_check_hard(self, req, helper, error))
return FALSE;
if (fu_engine_requirements_is_specific_req(req))
has_specific_requirement = TRUE;
}
}
/* it does not make sense to allowlist and denylist at the same time */
if (helper->has_hardware_req && helper->has_not_hardware_req) {
g_set_error_literal(
error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"using hardware and not_hardware at the same time is not supported");
return FALSE;
}
/* if we're using ID requirements with globs we have to have a client requirement */
if (helper->has_id_requirement_glob && !helper->has_client_id_requirement_glob) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"using <id> version requirements with globs also needs "
"<client>id-requirement-glob</client>");
return FALSE;
}
/* if a device uses a generic ID (i.e. not matching the OEM) then check to make sure the
* firmware is specific enough, e.g. by using a CHID or depth requirement */
if (device != NULL && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) &&
fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_ENFORCE_REQUIRES) &&
!has_specific_requirement) {
#ifdef SUPPORTED_BUILD
g_set_error_literal(
error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"generic GUID requires a CHID, child, parent or sibling requirement");
return FALSE;
#else
if ((flags & FWUPD_INSTALL_FLAG_IGNORE_REQUIREMENTS) == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"generic GUID requires --force, a CHID, child, parent "
"or sibling requirement");
return FALSE;
}
g_info("ignoring enforce-requires requirement due to --force");
#endif
}
/* soft requirements */
reqs = fu_release_get_soft_reqs(release);
if (reqs != NULL) {
for (guint i = 0; i < reqs->len; i++) {
XbNode *req = g_ptr_array_index(reqs, i);
if (!fu_engine_requirements_check_soft(self, req, helper, error))
return FALSE;
}
}
/* success */
return TRUE;
}