fwupd/src/fu-engine.c
Richard Hughes 473c520e38 Add in extra metadata to the uploaded reports
Save the dependency versions, architecture and the kernel versions in the
history database as metadata as it may be different to the time of upload.
2018-01-11 21:28:22 +00:00

3543 lines
99 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2015-2017 Richard Hughes <richard@hughsie.com>
*
* Licensed under the GNU Lesser General Public License Version 2.1
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include <appstream-glib.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include <glib-object.h>
#include <string.h>
#include <sys/utsname.h>
#include "fwupd-common-private.h"
#include "fwupd-enums-private.h"
#include "fwupd-error.h"
#include "fwupd-release-private.h"
#include "fwupd-remote-private.h"
#include "fwupd-resources.h"
#include "fu-common-cab.h"
#include "fu-common.h"
#include "fu-config.h"
#include "fu-debug.h"
#include "fu-device-list.h"
#include "fu-device-private.h"
#include "fu-engine.h"
#include "fu-hwids.h"
#include "fu-keyring.h"
#include "fu-history.h"
#include "fu-plugin.h"
#include "fu-plugin-list.h"
#include "fu-plugin-private.h"
#include "fu-quirks.h"
#include "fu-smbios.h"
#ifdef ENABLE_GPG
#include "fu-keyring-gpg.h"
#endif
#ifdef ENABLE_PKCS7
#include "fu-keyring-pkcs7.h"
#endif
static void fu_engine_finalize (GObject *obj);
struct _FuEngine
{
GObject parent_instance;
GUsbContext *usb_ctx;
FuConfig *config;
FuDeviceList *device_list;
FwupdStatus status;
guint percentage;
FuHistory *history;
AsProfile *profile;
AsStore *store;
gboolean coldplug_running;
guint coldplug_id;
guint coldplug_delay;
FuPluginList *plugin_list;
GPtrArray *supported_guids;
FuSmbios *smbios;
FuHwids *hwids;
FuQuirks *quirks;
};
enum {
SIGNAL_CHANGED,
SIGNAL_DEVICE_ADDED,
SIGNAL_DEVICE_REMOVED,
SIGNAL_DEVICE_CHANGED,
SIGNAL_STATUS_CHANGED,
SIGNAL_PERCENTAGE_CHANGED,
SIGNAL_LAST
};
static guint signals[SIGNAL_LAST] = { 0 };
G_DEFINE_TYPE (FuEngine, fu_engine, G_TYPE_OBJECT)
#define FU_ENGINE_REQUIREMENT_FIRMWARE_RUNTIME NULL /* yes, NULL */
#define FU_ENGINE_REQUIREMENT_FIRMWARE_BOOTLOADER "bootloader"
#define FU_ENGINE_REQUIREMENT_FIRMWARE_VENDOR "vendor-id"
#define FU_ENGINE_REQUIREMENT_ID_FWUPD "org.freedesktop.fwupd"
static void
fu_engine_emit_changed (FuEngine *self)
{
g_signal_emit (self, signals[SIGNAL_CHANGED], 0);
}
static void
fu_engine_emit_device_changed (FuEngine *self, FuDevice *device)
{
g_signal_emit (self, signals[SIGNAL_DEVICE_CHANGED], 0, device);
}
/**
* fu_engine_get_status:
* @self: A #FuEngine
*
* Gets the current engine status.
*
* Returns: a #FwupdStatus, e.g. %FWUPD_STATUS_DECOMPRESSING
**/
FwupdStatus
fu_engine_get_status (FuEngine *self)
{
g_return_val_if_fail (FU_IS_ENGINE (self), 0);
return self->status;
}
/**
* fu_engine_profile_dump:
* @self: A #FuEngine
*
* Dumps the engine profiling state to the console.
**/
void
fu_engine_profile_dump (FuEngine *self)
{
g_return_if_fail (FU_IS_ENGINE (self));
as_profile_set_duration_min (self->profile, 1);
as_profile_dump (self->profile);
}
static void
fu_engine_set_status (FuEngine *self, FwupdStatus status)
{
if (self->status == status)
return;
self->status = status;
/* emit changed */
g_debug ("Emitting PropertyChanged('Status'='%s')",
fwupd_status_to_string (status));
g_signal_emit (self, signals[SIGNAL_STATUS_CHANGED], 0, status);
}
static void
fu_engine_set_percentage (FuEngine *self, guint percentage)
{
if (self->percentage == percentage)
return;
self->percentage = percentage;
/* emit changed */
g_signal_emit (self, signals[SIGNAL_PERCENTAGE_CHANGED], 0, percentage);
}
static void
fu_engine_progress_notify_cb (FuDevice *device, GParamSpec *pspec, FuEngine *self)
{
fu_engine_set_percentage (self, fu_device_get_progress (device));
}
static void
fu_engine_status_notify_cb (FuDevice *device, GParamSpec *pspec, FuEngine *self)
{
fu_engine_set_status (self, fu_device_get_status (device));
}
static void
fu_engine_watch_device (FuEngine *self, FuDevice *device)
{
FuDevice *device_old = fu_device_list_get_old (self->device_list, device);
if (device_old != NULL) {
g_signal_handlers_disconnect_by_func (device_old,
fu_engine_progress_notify_cb,
self);
g_signal_handlers_disconnect_by_func (device_old,
fu_engine_status_notify_cb,
self);
}
g_signal_connect (device, "notify::progress",
G_CALLBACK (fu_engine_progress_notify_cb), self);
g_signal_connect (device, "notify::status",
G_CALLBACK (fu_engine_status_notify_cb), self);
}
static void
fu_engine_device_added_cb (FuDeviceList *device_list, FuDevice *device, FuEngine *self)
{
fu_engine_watch_device (self, device);
g_signal_emit (self, signals[SIGNAL_DEVICE_ADDED], 0, device);
}
static void
fu_engine_device_removed_cb (FuDeviceList *device_list, FuDevice *device, FuEngine *self)
{
g_signal_handlers_disconnect_by_data (device, self);
g_signal_emit (self, signals[SIGNAL_DEVICE_REMOVED], 0, device);
}
static void
fu_engine_device_changed_cb (FuDeviceList *device_list, FuDevice *device, FuEngine *self)
{
fu_engine_watch_device (self, device);
fu_engine_emit_device_changed (self, device);
}
static const gchar *
_as_release_get_metadata_item (AsRelease *release, const gchar *key)
{
GBytes *blob = as_release_get_blob (release, key);
if (blob == NULL)
return NULL;
return (const gchar *) g_bytes_get_data (blob, NULL);
}
static void
fu_engine_set_release_from_appstream (FuEngine *self,
FwupdRelease *rel,
AsApp *app,
AsRelease *release)
{
AsChecksum *csum;
FwupdRemote *remote = NULL;
const gchar *tmp;
const gchar *remote_id;
/* set from the AsApp */
fwupd_release_set_appstream_id (rel, as_app_get_id (app));
fwupd_release_set_homepage (rel, as_app_get_url_item (app, AS_URL_KIND_HOMEPAGE));
fwupd_release_set_license (rel, as_app_get_project_license (app));
fwupd_release_set_name (rel, as_app_get_name (app, NULL));
fwupd_release_set_summary (rel, as_app_get_comment (app, NULL));
fwupd_release_set_vendor (rel, as_app_get_developer_name (app, NULL));
fwupd_release_set_appstream_id (rel, as_app_get_id (app));
/* find the remote */
remote_id = _as_release_get_metadata_item (release, "fwupd::RemoteId");
if (remote_id != NULL) {
fwupd_release_set_remote_id (rel, remote_id);
remote = fu_config_get_remote_by_id (self->config, remote_id);
if (remote == NULL) {
g_warning ("no remote found for release %s",
as_release_get_version (release));
}
}
tmp = as_release_get_version (release);
if (tmp != NULL)
fwupd_release_set_version (rel, tmp);
tmp = as_release_get_description (release, NULL);
if (tmp != NULL)
fwupd_release_set_description (rel, tmp);
tmp = as_release_get_location_default (release);
if (tmp != NULL) {
g_autofree gchar *uri = NULL;
if (remote != NULL)
uri = fwupd_remote_build_firmware_uri (remote, tmp, NULL);
if (uri == NULL)
uri = g_strdup (tmp);
fwupd_release_set_uri (rel, uri);
}
csum = as_release_get_checksum_by_target (release, AS_CHECKSUM_TARGET_CONTENT);
if (csum != NULL) {
tmp = as_checksum_get_filename (csum);
if (tmp != NULL)
fwupd_release_set_filename (rel, tmp);
}
csum = as_release_get_checksum_by_target (release, AS_CHECKSUM_TARGET_CONTAINER);
if (csum != NULL) {
tmp = as_checksum_get_value (csum);
if (tmp != NULL)
fwupd_release_add_checksum (rel, tmp);
}
fwupd_release_set_size (rel, as_release_get_size (release, AS_SIZE_KIND_INSTALLED));
}
static FuKeyring *
fu_engine_get_keyring_for_kind (FwupdKeyringKind kind, GError **error)
{
if (kind == FWUPD_KEYRING_KIND_GPG) {
#ifdef ENABLE_GPG
return fu_keyring_gpg_new ();
#else
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not compiled with GPG support");
return NULL;
#endif
}
if (kind == FWUPD_KEYRING_KIND_PKCS7) {
#ifdef ENABLE_PKCS7
return fu_keyring_pkcs7_new ();
#else
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Not compiled with PKCS7 support");
return NULL;
#endif
}
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Keyring kind %s not supported",
fwupd_keyring_kind_to_string (kind));
return NULL;
}
static gboolean
fu_engine_get_release_trust_flags (AsRelease *release,
FwupdTrustFlags *trust_flags,
GError **error)
{
AsChecksum *csum_tmp;
FwupdKeyringKind keyring_kind = FWUPD_KEYRING_KIND_UNKNOWN;
GBytes *blob_payload;
GBytes *blob_signature;
const gchar *fn;
g_autofree gchar *pki_dir = NULL;
g_autoptr(GError) error_local = NULL;
g_autoptr(FuKeyring) kr = NULL;
g_autoptr(FuKeyringResult) kr_result = NULL;
struct {
FwupdKeyringKind kind;
const gchar *ext;
} keyrings[] = {
{ FWUPD_KEYRING_KIND_GPG, "asc" },
{ FWUPD_KEYRING_KIND_PKCS7, "p7b" },
{ FWUPD_KEYRING_KIND_PKCS7, "p7c" },
{ FWUPD_KEYRING_KIND_NONE, NULL }
};
/* no filename? */
csum_tmp = as_release_get_checksum_by_target (release, AS_CHECKSUM_TARGET_CONTENT);
fn = as_checksum_get_filename (csum_tmp);
if (fn == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no filename");
return FALSE;
}
/* no signature == no trust */
for (guint i = 0; keyrings[i].ext != NULL; i++) {
g_autofree gchar *fn_tmp = g_strdup_printf ("%s.%s", fn, keyrings[i].ext);
blob_signature = as_release_get_blob (release, fn_tmp);
if (blob_signature != NULL) {
keyring_kind = keyrings[i].kind;
break;
}
}
if (keyring_kind == FWUPD_KEYRING_KIND_UNKNOWN) {
g_debug ("firmware archive contained no signature");
return TRUE;
}
/* get payload */
blob_payload = as_release_get_blob (release, fn);
if (blob_payload == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no payload");
return FALSE;
}
/* check we were installed correctly */
pki_dir = g_build_filename (SYSCONFDIR, "pki", "fwupd", NULL);
if (!g_file_test (pki_dir, G_FILE_TEST_EXISTS)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"PKI directory %s not found", pki_dir);
return FALSE;
}
/* verify against the system trusted keys */
kr = fu_engine_get_keyring_for_kind (keyring_kind, error);
if (kr == NULL)
return FALSE;
if (!fu_keyring_setup (kr, error)) {
g_prefix_error (error, "failed to set up %s keyring: ",
fu_keyring_get_name (kr));
return FALSE;
}
if (!fu_keyring_add_public_keys (kr, pki_dir, error)) {
g_prefix_error (error, "failed to add public keys to %s keyring: ",
fu_keyring_get_name (kr));
return FALSE;
}
kr_result = fu_keyring_verify_data (kr, blob_payload, blob_signature, &error_local);
if (kr_result == NULL) {
g_warning ("untrusted as failed to verify from %s keyring: %s",
fu_keyring_get_name (kr),
error_local->message);
return TRUE;
}
/* awesome! */
g_debug ("marking payload as trusted");
*trust_flags |= FWUPD_TRUST_FLAG_PAYLOAD;
return TRUE;
}
/**
* fu_engine_unlock:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Unlocks a device.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_unlock (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
FuPlugin *plugin;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (device_id != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* check the device exists */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return FALSE;
/* get the plugin */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
error);
if (plugin == NULL)
return FALSE;
/* run the correct plugin that added this */
if (!fu_plugin_runner_unlock (plugin, device, error))
return FALSE;
/* make the UI update */
fu_engine_emit_device_changed (self, device);
fu_engine_emit_changed (self);
return TRUE;
}
static AsApp *
fu_engine_verify_update_device_to_app (FuDevice *device)
{
AsApp *app = NULL;
GPtrArray *checksums;
g_autofree gchar *id = NULL;
g_autoptr(AsFormat) format = NULL;
g_autoptr(AsProvide) prov = NULL;
g_autoptr(AsRelease) rel = NULL;
/* make a plausible ID */
id = g_strdup_printf ("%s.firmware", fu_device_get_guid_default (device));
/* add app to store */
app = as_app_new ();
as_app_set_id (app, id);
as_app_set_kind (app, AS_APP_KIND_FIRMWARE);
rel = as_release_new ();
as_release_set_version (rel, fu_device_get_version (device));
checksums = fu_device_get_checksums (device);
for (guint j = 0; j < checksums->len; j++) {
const gchar *checksum = g_ptr_array_index (checksums, j);
g_autoptr(AsChecksum) csum = as_checksum_new ();
as_checksum_set_kind (csum, fwupd_checksum_guess_kind (checksum));
as_checksum_set_value (csum, checksum);
as_checksum_set_target (csum, AS_CHECKSUM_TARGET_CONTENT);
as_release_add_checksum (rel, csum);
}
as_app_add_release (app, rel);
prov = as_provide_new ();
as_provide_set_kind (prov, AS_PROVIDE_KIND_FIRMWARE_FLASHED);
as_provide_set_value (prov, fu_device_get_guid_default (device));
as_app_add_provide (app, prov);
format = as_format_new ();
as_format_set_kind (format, AS_FORMAT_KIND_UNKNOWN);
as_app_add_format (app, format);
return app;
}
static AsStore *
fu_engine_load_verify_store (GError **error)
{
const gchar *fn = "/var/lib/fwupd/verify.xml";
g_autoptr(AsStore) store = NULL;
g_autoptr(GFile) file = NULL;
/* load existing store */
store = as_store_new ();
as_store_set_api_version (store, 0.9);
file = g_file_new_for_path (fn);
if (g_file_query_exists (file, NULL)) {
if (!as_store_from_file (store, file, NULL, NULL, error))
return NULL;
}
return g_steal_pointer (&store);
}
/**
* fu_engine_modify_remote:
* @self: A #FuEngine
* @remote_id: A remote ID
* @key: the key, e.g. `Enabled`
* @value: the key, e.g. `true`
* @error: A #GError, or %NULL
*
* Updates the verification store entry for a specific device.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_modify_remote (FuEngine *self,
const gchar *remote_id,
const gchar *key,
const gchar *value,
GError **error)
{
FwupdRemote *remote;
const gchar *filename;
const gchar *keys[] = { "Enabled", "MetadataURI", "FirmwareBaseURI", NULL };
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
/* check remote is valid */
remote = fu_config_get_remote_by_id (self->config, remote_id);
if (remote == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"remote %s not found", remote_id);
return FALSE;
}
/* check keys are valid */
if (!g_strv_contains (keys, key)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"key %s not supported", key);
return FALSE;
}
/* modify the remote filename */
filename = fwupd_remote_get_filename_source (remote);
if (!g_key_file_load_from_file (keyfile, filename,
G_KEY_FILE_KEEP_COMMENTS,
error)) {
g_prefix_error (error, "failed to load %s: ", filename);
return FALSE;
}
g_key_file_set_string (keyfile, "fwupd Remote", key, value);
return g_key_file_save_to_file (keyfile, filename, error);
}
/**
* fu_engine_modify_device:
* @self: A #FuEngine
* @device_id: A device ID
* @key: the key, e.g. `Flags`
* @value: the key, e.g. `reported`
* @error: A #GError, or %NULL
*
* Sets the reported flag for a specific device. This ensures that other
* front-end clients for fwupd do not report the same event.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_modify_device (FuEngine *self,
const gchar *device_id,
const gchar *key,
const gchar *value,
GError **error)
{
g_autoptr(FuDevice) device = NULL;
/* find the correct device */
device = fu_history_get_device (self->history, device_id, error);
if (device == NULL)
return FALSE;
/* support adding a subset of the device flags */
if (g_strcmp0 (key, "Flags") == 0) {
FwupdDeviceFlags flag = fwupd_device_flag_from_string (value);
if (flag == FWUPD_DEVICE_FLAG_UNKNOWN) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"key %s not a valid flag", key);
return FALSE;
}
if (fu_device_has_flag (device, flag)) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"device already has that flag");
return FALSE;
}
if (flag != FWUPD_DEVICE_FLAG_REPORTED) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"flag %s cannot be set from client", key);
return FALSE;
}
return fu_history_set_device_flags (self->history, device,
fu_device_get_flags (device) | flag,
error);
}
/* others invalid */
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"key %s not supported", key);
return FALSE;
}
/**
* fu_engine_verify_update:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Updates the verification store entry for a specific device.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_verify_update (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
FuPlugin *plugin;
GPtrArray *checksums;
const gchar *fn = "/var/lib/fwupd/verify.xml";
g_autoptr(AsApp) app = NULL;
g_autoptr(AsStore) store = NULL;
g_autoptr(GFile) file = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (device_id != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* check the devices still exists */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return FALSE;
/* get the plugin */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
error);
if (plugin == NULL)
return FALSE;
/* get the checksum */
checksums = fu_device_get_checksums (device);
if (checksums->len == 0) {
if (!fu_plugin_runner_verify (plugin, device,
FU_PLUGIN_VERIFY_FLAG_NONE,
error))
return FALSE;
fu_engine_emit_device_changed (self, device);
}
/* we got nothing */
if (checksums->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"device verification not supported");
return FALSE;
}
/* load existing store */
store = fu_engine_load_verify_store (error);
if (store == NULL)
return FALSE;
/* add to store */
app = fu_engine_verify_update_device_to_app (device);
as_store_remove_app_by_id (store, as_app_get_id (app));
as_store_add_app (store, app);
/* write */
g_debug ("writing %s", fn);
file = g_file_new_for_path (fn);
return as_store_to_file (store, file,
AS_NODE_TO_XML_FLAG_ADD_HEADER |
AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE,
NULL, error);
}
static AsApp *
fu_engine_store_get_app_by_guids (AsStore *store, FuDevice *device)
{
GPtrArray *guids = fu_device_get_guids (device);
for (guint i = 0; i < guids->len; i++) {
AsApp *app = NULL;
app = as_store_get_app_by_provide (store,
AS_PROVIDE_KIND_FIRMWARE_FLASHED,
g_ptr_array_index (guids, i));
if (app != NULL)
return app;
}
return NULL;
}
/**
* fu_engine_verify:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Verifies a device firmware checksum using the verification store entry.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_verify (FuEngine *self, const gchar *device_id, GError **error)
{
AsApp *app;
AsChecksum *csum;
AsRelease *release;
FuDevice *device = NULL;
FuPlugin *plugin;
GPtrArray *checksums;
const gchar *hash = NULL;
const gchar *version = NULL;
g_autoptr(AsStore) store = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (device_id != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* check the id exists */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return FALSE;
/* get the plugin */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
error);
if (plugin == NULL)
return FALSE;
/* set the device firmware hash */
if (!fu_plugin_runner_verify (plugin, device,
FU_PLUGIN_VERIFY_FLAG_NONE, error))
return FALSE;
/* find component in metadata */
store = fu_engine_load_verify_store (error);
if (store == NULL)
return FALSE;
app = fu_engine_store_get_app_by_guids (store, device);
if (app == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No metadata");
return FALSE;
}
/* find version in metadata */
version = fu_device_get_version (device);
release = as_app_get_release (app, version);
if (release == NULL) {
/* try again with the system metadata */
app = fu_engine_store_get_app_by_guids (self->store, device);
if (app == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No system metadata");
return FALSE;
}
release = as_app_get_release (app, version);
}
if (release == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No version %s", version);
return FALSE;
}
/* find checksum */
csum = as_release_get_checksum_by_target (release, AS_CHECKSUM_TARGET_CONTENT);
if (csum == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No content checksum for %s", version);
return FALSE;
}
/* get the matching checksum */
checksums = fu_device_get_checksums (device);
if (checksums->len == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No device checksums for %s", version);
return FALSE;
}
for (guint j = 0; j < checksums->len; j++) {
const gchar *hash_tmp = g_ptr_array_index (checksums, j);
GChecksumType hash_kind = fwupd_checksum_guess_kind (hash_tmp);
if (as_checksum_get_kind (csum) == hash_kind) {
hash = hash_tmp;
break;
}
}
if (hash == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"No matching hash kind for %s", version);
return FALSE;
}
if (g_strcmp0 (as_checksum_get_value (csum), hash) != 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"For v%s expected %s, got %s",
version,
as_checksum_get_value (csum),
hash);
return FALSE;
}
/* success */
return TRUE;
}
static AsScreenshot *
_as_app_get_screenshot_default (AsApp *app)
{
#if AS_CHECK_VERSION(0,7,3)
return as_app_get_screenshot_default (app);
#else
GPtrArray *array = as_app_get_screenshots (app);
if (array->len == 0)
return NULL;
return g_ptr_array_index (array, 0);
#endif
}
static GPtrArray *
_as_store_get_apps_by_provide (AsStore *store, AsProvideKind kind, const gchar *value)
{
#if AS_CHECK_VERSION(0,7,5)
return as_store_get_apps_by_provide (store, kind, value);
#else
GPtrArray *apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
GPtrArray *array = as_store_get_apps (store);
for (guint i = 0; i < array->len; i++) {
AsApp *app = g_ptr_array_index (array, i);
GPtrArray *provides = as_app_get_provides (app);
for (guint j = 0; j < provides->len; j++) {
AsProvide *tmp = g_ptr_array_index (provides, j);
if (kind != as_provide_get_kind (tmp))
continue;
if (g_strcmp0 (as_provide_get_value (tmp), value) != 0)
continue;
g_ptr_array_add (apps, g_object_ref (app));
}
}
return apps;
#endif
}
static gboolean
fu_engine_check_version_requirement (AsApp *app,
AsRequireKind kind,
const gchar *id,
const gchar *version,
GError **error)
{
AsRequire *req;
g_autoptr(GError) error_local = NULL;
/* check args */
if (version == NULL) {
g_debug ("no parameter given for %s{%s}",
as_require_kind_to_string (kind), id);
return TRUE;
}
/* does requirement exist */
req = as_app_get_require_by_value (app, kind, id);
if (req == NULL) {
g_debug ("no requirement on %s{%s}",
as_require_kind_to_string (kind), id);
return TRUE;
}
/* check version */
if (!as_require_version_compare (req, version, &error_local)) {
/* firmware */
if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_FIRMWARE_RUNTIME) == 0) {
if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with firmware version %s, requires >= %s",
version, as_require_get_version (req));
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with firmware version: %s",
error_local->message);
}
return FALSE;
}
/* fwupd */
if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_ID_FWUPD) == 0) {
if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with fwupd version %s, requires >= %s",
version, as_require_get_version (req));
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with fwupd version: %s",
error_local->message);
}
return FALSE;
}
/* bootloader */
if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_FIRMWARE_BOOTLOADER) == 0) {
if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with bootloader version %s, requires >= %s",
version, as_require_get_version (req));
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with bootloader version: %s",
error_local->message);
}
return FALSE;
}
/* vendor */
if (g_strcmp0 (id, FU_ENGINE_REQUIREMENT_FIRMWARE_VENDOR) == 0) {
if (as_require_get_compare (req) == AS_REQUIRE_COMPARE_GE) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with vendor %s, requires >= %s",
version, as_require_get_version (req));
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with vendor: %s",
error_local->message);
}
return FALSE;
}
/* anything else */
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Not compatible with %s: %s",
id, error_local->message);
return FALSE;
}
/* success */
g_debug ("requirement %s %s %s on %s passed",
as_require_get_version (req),
as_require_compare_to_string (as_require_get_compare (req)),
version, id);
return TRUE;
}
#if AS_CHECK_VERSION(0,7,4)
static gboolean
fu_engine_check_hardware_requirement (FuEngine *self, AsApp *app, GError **error)
{
GPtrArray *requires = as_app_get_requires (app);
/* check each HWID requirement */
for (guint i = 0; i < requires->len; i++) {
AsRequire *req = g_ptr_array_index (requires, i);
if (as_require_get_kind (req) != AS_REQUIRE_KIND_HARDWARE)
continue;
if (!fu_hwids_has_guid (self->hwids, as_require_get_value (req))) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no HWIDs matched %s",
as_require_get_value (req));
return FALSE;
}
g_debug ("HWID provided %s", as_require_get_value (req));
}
/* success */
return TRUE;
}
#endif
static gboolean
fu_engine_check_requirements (FuEngine *self, AsApp *app, FuDevice *device, GError **error)
{
/* make sure requirements are satisfied */
if (!fu_engine_check_version_requirement (app,
AS_REQUIRE_KIND_ID,
FU_ENGINE_REQUIREMENT_ID_FWUPD,
VERSION,
error)) {
return FALSE;
}
#if AS_CHECK_VERSION(0,7,4)
if (!fu_engine_check_hardware_requirement (self, app, error))
return FALSE;
#endif
if (device != NULL) {
if (!fu_engine_check_version_requirement (app,
AS_REQUIRE_KIND_FIRMWARE,
FU_ENGINE_REQUIREMENT_FIRMWARE_RUNTIME,
fu_device_get_version (device),
error)) {
return FALSE;
}
if (!fu_engine_check_version_requirement (app,
AS_REQUIRE_KIND_FIRMWARE,
FU_ENGINE_REQUIREMENT_FIRMWARE_BOOTLOADER,
fu_device_get_version_bootloader (device),
error)) {
return FALSE;
}
if (!fu_engine_check_version_requirement (app,
AS_REQUIRE_KIND_FIRMWARE,
FU_ENGINE_REQUIREMENT_FIRMWARE_VENDOR,
fu_device_get_vendor_id (device),
error)) {
return FALSE;
}
}
/* success */
return TRUE;
}
static void
fu_engine_vendor_fixup_provide_value (AsApp *app)
{
GPtrArray *provides;
/* no quirk required */
if (as_app_get_kind (app) != AS_APP_KIND_FIRMWARE)
return;
/* fix each provide to be a GUID */
provides = as_app_get_provides (app);
for (guint i = 0; i < provides->len; i++) {
AsProvide *prov = g_ptr_array_index (provides, i);
const gchar *value = as_provide_get_value (prov);
g_autofree gchar *guid = NULL;
if (as_provide_get_kind (prov) != AS_PROVIDE_KIND_FIRMWARE_FLASHED)
continue;
if (as_utils_guid_is_valid (value))
continue;
guid = as_utils_guid_from_string (value);
as_provide_set_value (prov, guid);
}
}
static void
fu_engine_vendor_quirk_release_version (FuEngine *self, AsApp *app)
{
AsVersionParseFlag flags = AS_VERSION_PARSE_FLAG_USE_TRIPLET;
GPtrArray *releases;
const gchar *quirk;
/* no quirk required */
if (as_app_get_kind (app) != AS_APP_KIND_FIRMWARE)
return;
/* any quirks match */
quirk = fu_quirks_lookup_by_glob (self->quirks,
FU_QUIRKS_DAEMON_VERSION_FORMAT,
as_app_get_id (app));
if (g_strcmp0 (quirk, "none") == 0)
flags = AS_VERSION_PARSE_FLAG_NONE;
/* fix each release */
releases = as_app_get_releases (app);
for (guint i = 0; i < releases->len; i++) {
AsRelease *rel;
const gchar *version;
guint64 ver_uint32;
g_autofree gchar *version_new = NULL;
rel = g_ptr_array_index (releases, i);
version = as_release_get_version (rel);
if (version == NULL)
continue;
if (g_strstr_len (version, -1, ".") != NULL)
continue;
/* metainfo files use hex and the LVFS uses decimal */
if (g_str_has_prefix (version, "0x")) {
ver_uint32 = g_ascii_strtoull (version + 2, NULL, 16);
} else {
ver_uint32 = g_ascii_strtoull (version, NULL, 10);
}
if (ver_uint32 == 0)
continue;
/* convert to dotted decimal */
version_new = as_utils_version_from_uint32 ((guint32) ver_uint32, flags);
as_release_set_version (rel, version_new);
}
}
static gchar *
fu_engine_get_guids_from_store (AsStore *store)
{
AsProvide *prov;
GPtrArray *provides;
GPtrArray *apps;
GString *str = g_string_new ("");
/* return a string with all the firmware apps in the store */
apps = as_store_get_apps (store);
for (guint i = 0; i < apps->len; i++) {
AsApp *app = AS_APP (g_ptr_array_index (apps, i));
provides = as_app_get_provides (app);
for (guint j = 0; j < provides->len; j++) {
prov = AS_PROVIDE (g_ptr_array_index (provides, j));
if (as_provide_get_kind (prov) != AS_PROVIDE_KIND_FIRMWARE_FLASHED)
continue;
g_string_append_printf (str, "%s,", as_provide_get_value (prov));
}
}
if (str->len == 0) {
g_string_free (str, TRUE);
return NULL;
}
g_string_truncate (str, str->len - 1);
return g_string_free (str, FALSE);
}
static FuDevice *
fu_engine_get_item_by_wildcard (FuEngine *self, AsStore *store, GError **error)
{
g_autofree gchar *guids = NULL;
g_autoptr(GPtrArray) devices = NULL;
devices = fu_device_list_get_all (self->device_list);
for (guint i = 0; i < devices->len; i++) {
FuDevice *device_tmp = g_ptr_array_index (devices, i);
if (fu_engine_store_get_app_by_guids (store, device_tmp) != NULL)
return device_tmp;
}
guids = fu_engine_get_guids_from_store (store);
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Firmware is not for any attached hardware: got %s",
guids);
return NULL;
}
static GHashTable *
fu_engine_get_report_metadata (FuEngine *self)
{
GHashTable *hash;
struct utsname name_tmp = { 0 };
/* used by pretty much every plugin and are hard deps of fwupd */
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert (hash,
g_strdup ("FwupdVersion"),
g_strdup (VERSION));
g_hash_table_insert (hash,
g_strdup ("AppstreamGlibVersion"),
g_strdup_printf ("%i.%i.%i",
AS_MAJOR_VERSION,
AS_MINOR_VERSION,
AS_MICRO_VERSION));
g_hash_table_insert (hash,
g_strdup ("GUsbVersion"),
g_strdup_printf ("%i.%i.%i",
G_USB_MAJOR_VERSION,
G_USB_MINOR_VERSION,
G_USB_MICRO_VERSION));
/* kernel version is often important for debugging failures */
if (uname (&name_tmp) >= 0) {
g_hash_table_insert (hash,
g_strdup ("CpuArchitecture"),
g_strdup (name_tmp.machine));
g_hash_table_insert (hash,
g_strdup ("KernelVersion"),
g_strdup (name_tmp.release));
}
return hash;
}
/**
* fu_engine_install:
* @self: A #FuEngine
* @device_id: A device ID
* @store: The #AsStore with the firmware metadata
* @blob_cab: The #GBytes of the .cab file
* @flags: The #FwupdInstallFlags, e.g. %FWUPD_DEVICE_FLAG_UPDATABLE
* @error: A #GError, or %NULL
*
* Installs a specfic firmware file on a device.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_install (FuEngine *self,
const gchar *device_id,
AsStore *store,
GBytes *blob_cab,
FwupdInstallFlags flags,
GError **error)
{
AsApp *app;
AsChecksum *csum_tmp;
AsRelease *rel;
FuDevice *device;
FuPlugin *plugin;
GBytes *blob_fw;
GPtrArray *plugins;
const gchar *tmp;
const gchar *version;
gboolean is_downgrade;
gint vercmp;
g_autofree gchar *checksum = NULL;
g_autofree gchar *device_id_orig = NULL;
g_autoptr(FwupdRelease) release_history = fwupd_release_new ();
g_autoptr(GBytes) blob_fw2 = NULL;
g_autoptr(GError) error_local = NULL;
g_autoptr(GHashTable) metadata_hash = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (device_id != NULL, FALSE);
g_return_val_if_fail (AS_IS_STORE (store), FALSE);
g_return_val_if_fail (blob_cab != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* wildcard */
if (g_strcmp0 (device_id, FWUPD_DEVICE_ID_ANY) == 0) {
device = fu_engine_get_item_by_wildcard (self, store, error);
if (device == NULL)
return FALSE;
} else {
/* find the specific device */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return FALSE;
}
/* check the device is not locked */
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_LOCKED)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device %s is locked",
device_id);
return FALSE;
}
/* no update abilities */
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device %s does not currently allow updates",
fu_device_get_id (device));
return FALSE;
}
/* called with online update, test if device is supposed to allow this */
if ((flags & FWUPD_INSTALL_FLAG_OFFLINE) == 0 &&
fu_device_has_flag (device, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Device %s only allows offline updates",
device_id);
return FALSE;
}
/* find from guid */
app = fu_engine_store_get_app_by_guids (store, device);
if (app == NULL) {
g_autofree gchar *guid = NULL;
guid = fu_engine_get_guids_from_store (store);
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Firmware is not for this hw: required %s got %s",
fu_device_get_guid_default (device), guid);
return FALSE;
}
/* not in bootloader mode */
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) {
const gchar *caption = NULL;
AsScreenshot *ss = _as_app_get_screenshot_default (app);
if (ss != NULL)
caption = as_screenshot_get_caption (ss, NULL);
if (caption != NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device %s needs to manually be put in update mode: %s",
fu_device_get_name (device), caption);
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device %s needs to manually be put in update mode",
fu_device_get_name (device));
}
return FALSE;
}
/* possibly convert the version from 0x to dotted */
fu_engine_vendor_quirk_release_version (self, app);
/* possibly convert the flashed provide to a GUID */
fu_engine_vendor_fixup_provide_value (app);
/* check we can install it */
if (!fu_engine_check_requirements (self, app, device, error))
return FALSE;
/* parse the DriverVer */
rel = as_app_get_release_default (app);
if (rel == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"No releases in the firmware component");
return FALSE;
}
/* get the blob */
csum_tmp = as_release_get_checksum_by_target (rel, AS_CHECKSUM_TARGET_CONTENT);
tmp = as_checksum_get_filename (csum_tmp);
if (tmp == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"No checksum filename");
return FALSE;
}
/* not all devices have to use the same blob */
blob_fw = as_release_get_blob (rel, tmp);
if (blob_fw == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"Failed to get firmware blob");
return FALSE;
}
/* use a bubblewrap helper script to build the firmware */
tmp = as_app_get_metadata_item (app, "fwupd::BuilderScript");
if (tmp != NULL) {
const gchar *tmp2 = as_app_get_metadata_item (app, "fwupd::BuilderOutput");
if (tmp2 == NULL)
tmp2 = "firmware.bin";
blob_fw2 = fu_common_firmware_builder (blob_fw, tmp, tmp2, error);
if (blob_fw2 == NULL)
return FALSE;
} else {
blob_fw2 = g_bytes_ref (blob_fw);
}
/* test the firmware is not an empty blob */
if (g_bytes_get_size (blob_fw2) == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"Firmware is invalid as has zero size");
return FALSE;
}
version = as_release_get_version (rel);
/* compare to the lowest supported version, if it exists */
tmp = fu_device_get_version_lowest (device);
if (tmp != NULL && as_utils_vercmp (tmp, version) > 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_NEWER,
"Specified firmware is older than the minimum "
"required version '%s < %s'", tmp, version);
return FALSE;
}
/* get the plugin */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
error);
if (plugin == NULL)
return FALSE;
/* compare the versions of what we have installed */
tmp = fu_device_get_version (device);
if (tmp == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device %s does not yet have a current version",
device_id);
return FALSE;
}
vercmp = as_utils_vercmp (tmp, version);
if (vercmp == 0 && (flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_SAME,
"Specified firmware is already installed '%s'",
tmp);
return FALSE;
}
is_downgrade = vercmp > 0;
if (is_downgrade && (flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_NEWER,
"Specified firmware is older than installed '%s < %s'",
tmp, version);
return FALSE;
}
/* signal to all the plugins the update is about to happen */
plugins = fu_plugin_list_get_all (self->plugin_list);
for (guint j = 0; j < plugins->len; j++) {
FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j);
if (!fu_plugin_runner_update_prepare (plugin_tmp, device, error))
return FALSE;
}
/* save the chosen device ID in case the device goes away */
device_id_orig = g_strdup (fu_device_get_id (device));
/* mark this as modified even if we actually fail to do the update */
fu_device_set_modified (device, (guint64) g_get_real_time () / G_USEC_PER_SEC);
/* build the version metadata */
metadata_hash = fu_engine_get_report_metadata (self);
fwupd_release_add_metadata (release_history, metadata_hash);
/* add device to database */
checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, blob_cab);
fwupd_release_set_version (release_history, version);
fwupd_release_add_checksum (release_history, checksum);
fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED);
if (!fu_history_remove_device (self->history, device, error))
return FALSE;
if (!fu_history_add_device (self->history, device, release_history, error))
return FALSE;
/* do the update */
if (!fu_plugin_runner_update_detach (plugin, device, error))
return FALSE;
device = fu_device_list_find_by_id (self->device_list, device_id_orig, error);
if (device == NULL)
return FALSE;
if (!fu_plugin_runner_update (plugin,
device,
blob_cab,
blob_fw2,
flags,
&error_local)) {
g_autoptr(GError) error_attach = NULL;
/* save to database */
fu_history_set_update_state (self->history, device, FWUPD_UPDATE_STATE_FAILED, NULL);
fu_history_set_error_msg (self->history, device, error_local->message, NULL);
g_propagate_error (error, g_steal_pointer (&error_local));
/* attack back into runtime */
if (!fu_plugin_runner_update_attach (plugin,
device,
&error_attach)) {
g_warning ("failed to attach device after failed update: %s",
error_attach->message);
}
for (guint j = 0; j < plugins->len; j++) {
FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j);
g_autoptr(GError) error_cleanup = NULL;
if (!fu_plugin_runner_update_cleanup (plugin_tmp,
device,
&error_cleanup)) {
g_warning ("failed to update-cleanup "
"after failed update: %s",
error_cleanup->message);
}
}
fu_device_set_status (device, FWUPD_STATUS_IDLE);
return FALSE;
}
device = fu_device_list_find_by_id (self->device_list, device_id_orig, error);
if (device == NULL)
return FALSE;
if (!fu_plugin_runner_update_attach (plugin, device, error))
return FALSE;
/* get the new version number */
device = fu_device_list_find_by_id (self->device_list, device_id_orig, error);
if (device == NULL)
return FALSE;
if (!fu_plugin_runner_update_reload (plugin, device, error))
return FALSE;
/* signal to all the plugins the update has happened */
for (guint j = 0; j < plugins->len; j++) {
FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j);
g_autoptr(GError) error_cleanup = NULL;
if (!fu_plugin_runner_update_cleanup (plugin_tmp, device, &error_cleanup)) {
g_warning ("failed to update-cleanup: %s",
error_cleanup->message);
}
}
/* make the UI update */
fu_device_set_status (device, FWUPD_STATUS_IDLE);
fu_engine_emit_device_changed (self, device);
fu_engine_emit_changed (self);
/* update database */
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) {
if (!fu_history_set_update_state (self->history, device,
FWUPD_UPDATE_STATE_NEEDS_REBOOT,
error))
return FALSE;
} else {
if (!fu_history_set_update_state (self->history, device,
FWUPD_UPDATE_STATE_SUCCESS,
error))
return FALSE;
}
/* success */
return TRUE;
}
static FuDevice *
fu_engine_get_item_by_id_fallback_history (FuEngine *self, const gchar *id, GError **error)
{
FuDevice *dev;
FuPlugin *plugin;
FuDevice *device = NULL;
FwupdUpdateState update_state;
const gchar *tmp;
g_autoptr(GPtrArray) devices = NULL;
/* not a wildcard */
if (g_strcmp0 (id, FWUPD_DEVICE_ID_ANY) != 0)
return fu_device_list_find_by_id (self->device_list, id, error);
/* allow '*' for any */
devices = fu_history_get_devices (self->history, error);
if (devices == NULL)
return NULL;
for (guint i = 0; i < devices->len; i++) {
dev = g_ptr_array_index (devices, i);
update_state = fu_device_get_update_state (dev);
if (update_state == FWUPD_UPDATE_STATE_UNKNOWN)
continue;
if (update_state == FWUPD_UPDATE_STATE_PENDING)
continue;
/* if the device is not still connected, fake a FuDevice */
device = fu_device_list_find_by_id (self->device_list, fu_device_get_id (dev), NULL);
if (device == NULL) {
tmp = fu_device_get_plugin (dev);
plugin = fu_plugin_list_find_by_name (self->plugin_list, tmp, error);
if (plugin == NULL)
return NULL;
fu_device_list_add (self->device_list, dev);
/* FIXME: just a boolean on FuDevice? */
fu_device_set_metadata (dev, "FakeDevice", "TRUE");
}
break;
}
/* no device found */
if (device == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no suitable devices found");
}
return device;
}
/**
* fu_engine_get_action_id_for_device:
* @self: A #FuEngine
* @device_id: A device ID
* @store: The #AsStore with the firmware metadata
* @flags: The #FwupdInstallFlags, e.g. %FWUPD_DEVICE_FLAG_UPDATABLE
* @error: A #GError, or %NULL
*
* Gets the PolicyKit action ID to use for the install operation.
*
* Returns: string, e.g. `org.freedesktop.fwupd.update-internal-trusted`
**/
const gchar *
fu_engine_get_action_id_for_device (FuEngine *self,
const gchar *device_id,
AsStore *store,
FwupdInstallFlags flags,
GError **error)
{
AsApp *app;
AsRelease *release;
FuDevice *device;
FwupdTrustFlags trust_flags = FWUPD_TRUST_FLAG_NONE;
const gchar *version;
const gchar *version_release;
gboolean is_downgrade;
gboolean is_trusted;
gint vercmp;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (AS_IS_STORE (store), NULL);
g_return_val_if_fail (device_id != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* wildcard */
if (g_strcmp0 (device_id, FWUPD_DEVICE_ID_ANY) == 0) {
device = fu_engine_get_item_by_wildcard (self, store, error);
if (device == NULL)
return NULL;
} else {
/* find the specific device */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return NULL;
}
/* get device */
version = fu_device_get_version (device);
if (version == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Device with ID %s has no firmware version",
device_id);
return NULL;
}
/* match the GUIDs in the XML */
app = fu_engine_store_get_app_by_guids (store, device);
if (app == NULL) {
g_autofree gchar *guid = NULL;
guid = fu_engine_get_guids_from_store (store);
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"firmware is not for this hw: required %s got %s",
fu_device_get_guid_default (device), guid);
return NULL;
}
/* possibly convert the version from 0x to dotted */
fu_engine_vendor_quirk_release_version (self, app);
/* possibly convert the flashed provide to a GUID */
fu_engine_vendor_fixup_provide_value (app);
/* get latest release */
release = as_app_get_release_default (app);
if (release == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"%s [%s] has no firmware update metadata",
fu_device_get_id (device),
fu_device_get_name (device));
return NULL;
}
/* is this a downgrade or re-install */
version_release = as_release_get_version (release);
if (version_release == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Release has no firmware version");
return NULL;
}
vercmp = as_utils_vercmp (version, version_release);
if (vercmp == 0 && (flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_SAME,
"Specified firmware is already installed '%s'",
version_release);
return NULL;
}
is_downgrade = vercmp > 0;
if (is_downgrade && (flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_VERSION_NEWER,
"Specified firmware is older than installed '%s < %s'",
version_release, version);
return NULL;
}
/* verify */
if (!fu_engine_get_release_trust_flags (release, &trust_flags, error))
return NULL;
is_trusted = (trust_flags & FWUPD_TRUST_FLAG_PAYLOAD) > 0;
/* relax authentication checks for removable devices */
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_INTERNAL)) {
if (is_downgrade)
return "org.freedesktop.fwupd.downgrade-hotplug";
if (is_trusted)
return "org.freedesktop.fwupd.update-hotplug-trusted";
return "org.freedesktop.fwupd.update-hotplug";
}
/* internal device */
if (is_downgrade)
return "org.freedesktop.fwupd.downgrade-internal";
if (is_trusted)
return "org.freedesktop.fwupd.update-internal-trusted";
return "org.freedesktop.fwupd.update-internal";
}
static AsRelease *
_as_app_get_release_by_version (AsApp *app, const gchar *version)
{
#if AS_CHECK_VERSION(0,7,3)
return as_app_get_release_by_version (app, version);
#else
GPtrArray *releases = as_app_get_releases (app);
for (guint i = 0; i < releases->len; i++) {
AsRelease *release = g_ptr_array_index (releases, i);
if (g_strcmp0 (version, as_release_get_version (release)) == 0)
return release;
}
return NULL;
#endif
}
static void
fu_engine_add_component_to_store (FuEngine *self, AsApp *app)
{
AsApp *app_old = as_store_get_app_by_id (self->store, as_app_get_id (app));
GPtrArray *releases = as_app_get_releases (app);
/* possibly convert the version from 0x to dotted */
fu_engine_vendor_quirk_release_version (self, app);
/* possibly convert the flashed provide to a GUID */
fu_engine_vendor_fixup_provide_value (app);
/* the app does not already exist */
if (app_old == NULL) {
as_store_add_app (self->store, app);
return;
}
/* add releases that do not exist from a higher priority remote */
for (guint j = 0; j < releases->len; j++) {
AsRelease *release = g_ptr_array_index (releases, j);
AsRelease *release_old;
const gchar *version = as_release_get_version (release);
release_old = _as_app_get_release_by_version (app_old, version);
if (release_old != NULL) {
g_debug ("skipping release %s that already exists for %s",
version, as_app_get_id (app_old));
continue;
}
g_debug ("adding release %s to existing %s",
version, as_app_get_id (app_old));
as_app_add_release (app_old, release);
}
}
static gboolean
fu_engine_load_metadata_from_file (FuEngine *self,
const gchar *path,
const gchar *remote_id,
GError **error)
{
GPtrArray *apps;
g_autoptr(AsStore) store = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GBytes) remote_blob = NULL;
/* load the store locally until we know it is valid */
store = as_store_new ();
file = g_file_new_for_path (path);
if (!as_store_from_file (store, file, NULL, NULL, error))
return FALSE;
/* save the remote to the release */
if (remote_id != NULL && remote_id[0] != '\0')
remote_blob = g_bytes_new (remote_id, strlen (remote_id) + 1);
/* add the new application from the store */
apps = as_store_get_apps (store);
for (guint i = 0; i < apps->len; i++) {
AsApp *app = g_ptr_array_index (apps, i);
/* save the remote-id to all the releases for this component */
if (remote_blob != NULL) {
GPtrArray *releases = as_app_get_releases (app);
for (guint j = 0; j < releases->len; j++) {
AsRelease *release = g_ptr_array_index (releases, j);
as_release_set_blob (release,
"fwupd::RemoteId",
remote_blob);
}
}
/* either add component, or merge in new releases */
fu_engine_add_component_to_store (self, app);
}
return TRUE;
}
static gboolean
fu_engine_load_metadata_store (FuEngine *self, GError **error)
{
GPtrArray *apps;
GPtrArray *remotes;
g_autofree gchar *guids_str = NULL;
g_autoptr(GPtrArray) devices = NULL;
/* clear existing store */
as_store_remove_all (self->store);
/* load each enabled metadata file */
remotes = fu_config_get_remotes (self->config);
for (guint i = 0; i < remotes->len; i++) {
const gchar *path = NULL;
g_autoptr(GError) error_local = NULL;
FwupdRemote *remote = g_ptr_array_index (remotes, i);
if (!fwupd_remote_get_enabled (remote)) {
g_debug ("remote %s not enabled, so skipping",
fwupd_remote_get_id (remote));
continue;
}
path = fwupd_remote_get_filename_cache (remote);
if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
g_debug ("no %s, so skipping", path);
continue;
}
if (!fu_engine_load_metadata_from_file (self, path,
fwupd_remote_get_id (remote),
&error_local)) {
g_warning ("failed to load remote %s: %s",
fwupd_remote_get_id (remote),
error_local->message);
continue;
}
}
/* print what we've got */
apps = as_store_get_apps (self->store);
if (apps->len == 0) {
g_debug ("no devices in store");
} else {
g_debug ("devices now in store:");
for (guint i = 0; i < apps->len; i++) {
AsApp *app = g_ptr_array_index (apps, i);
GPtrArray *releases = as_app_get_releases (app);
g_autoptr(GString) releases_str = g_string_new (NULL);
for (guint j = 0; j < releases->len; j++) {
AsRelease *release = g_ptr_array_index (releases, j);
g_string_append_printf (releases_str, "%s,",
as_release_get_version (release));
if (j >= 2) {
g_string_append (releases_str, "…,");
break;
}
}
if (releases_str->len > 1)
g_string_truncate (releases_str, releases_str->len - 1);
g_debug ("%u\t%s\t%s [%s]", i + 1,
as_app_get_id (app),
as_app_get_name (app, NULL),
releases_str->str);
}
}
/* update the list of supported GUIDs */
g_ptr_array_set_size (self->supported_guids, 0);
guids_str = fu_engine_get_guids_from_store (self->store);
if (guids_str != NULL) {
g_auto(GStrv) guids = g_strsplit (guids_str, ",", -1);
for (guint i = 0; guids[i] != NULL; i++) {
g_ptr_array_add (self->supported_guids,
g_steal_pointer (&guids[i]));
}
}
/* are any devices now supported? */
devices = fu_device_list_get_all (self->device_list);
for (guint i = 0; i < devices->len; i++) {
FuDevice *device = g_ptr_array_index (devices, i);
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED))
fu_engine_emit_device_changed (self, device);
}
return TRUE;
}
static FuKeyringResult *
fu_engine_get_existing_keyring_result (FuEngine *self,
FuKeyring *kr,
FwupdRemote *remote,
GError **error)
{
g_autoptr(GBytes) blob = NULL;
g_autoptr(GBytes) blob_sig = NULL;
blob = fu_common_get_contents_bytes (fwupd_remote_get_filename_cache (remote), error);
if (blob == NULL)
return NULL;
blob_sig = fu_common_get_contents_bytes (fwupd_remote_get_filename_cache_sig (remote), error);
if (blob_sig == NULL)
return NULL;
return fu_keyring_verify_data (kr, blob, blob_sig, error);
}
/**
* fu_engine_update_metadata:
* @self: A #FuEngine
* @remote_id: A remote ID, e.g. `lvfs`
* @fd: file descriptor of the metadata
* @fd_sig: file descriptor of the metadata signature
* @error: A #GError, or %NULL
*
* Updates the metadata for a specific remote.
*
* Note: this will close the fds when done
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_update_metadata (FuEngine *self, const gchar *remote_id,
gint fd, gint fd_sig, GError **error)
{
FwupdKeyringKind keyring_kind;
FwupdRemote *remote;
g_autoptr(GBytes) bytes_raw = NULL;
g_autoptr(GBytes) bytes_sig = NULL;
g_autoptr(GInputStream) stream_fd = NULL;
g_autoptr(GInputStream) stream_sig = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (remote_id != NULL, FALSE);
g_return_val_if_fail (fd > 0, FALSE);
g_return_val_if_fail (fd_sig > 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* ensures the fd's are closed on error */
stream_fd = g_unix_input_stream_new (fd, TRUE);
stream_sig = g_unix_input_stream_new (fd_sig, TRUE);
/* check remote is valid */
remote = fu_config_get_remote_by_id (self->config, remote_id);
if (remote == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"remote %s not found", remote_id);
return FALSE;
}
if (!fwupd_remote_get_enabled (remote)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"remote %s not enabled", remote_id);
return FALSE;
}
/* read the entire file into memory */
bytes_raw = g_input_stream_read_bytes (stream_fd, 0x100000, NULL, error);
if (bytes_raw == NULL)
return FALSE;
/* read signature */
bytes_sig = g_input_stream_read_bytes (stream_sig, 0x800, NULL, error);
if (bytes_sig == NULL)
return FALSE;
/* verify file */
keyring_kind = fwupd_remote_get_keyring_kind (remote);
if (keyring_kind != FWUPD_KEYRING_KIND_NONE) {
g_autoptr(FuKeyring) kr = NULL;
g_autoptr(FuKeyringResult) kr_result = NULL;
g_autoptr(FuKeyringResult) kr_result_old = NULL;
g_autoptr(GError) error_local = NULL;
kr = fu_engine_get_keyring_for_kind (keyring_kind, error);
if (kr == NULL)
return FALSE;
if (!fu_keyring_setup (kr, error))
return FALSE;
if (!fu_keyring_add_public_keys (kr, "/etc/pki/fwupd-metadata", error))
return FALSE;
kr_result = fu_keyring_verify_data (kr, bytes_raw, bytes_sig, error);
if (kr_result == NULL)
return FALSE;
/* verify the metadata was signed later than the existing
* metadata for this remote to mitigate a rollback attack */
kr_result_old = fu_engine_get_existing_keyring_result (self, kr,
remote,
&error_local);
if (kr_result_old == NULL) {
if (g_error_matches (error_local,
G_FILE_ERROR,
G_FILE_ERROR_NOENT)) {
g_debug ("no existing valid keyrings: %s",
error_local->message);
} else {
g_warning ("could not get existing keyring result: %s",
error_local->message);
}
} else {
gint64 delta = 0;
if (fu_keyring_result_get_timestamp (kr_result) > 0 &&
fu_keyring_result_get_timestamp (kr_result_old) > 0) {
delta = fu_keyring_result_get_timestamp (kr_result) -
fu_keyring_result_get_timestamp (kr_result_old);
}
if (delta < 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"new signing timestamp was %"
G_GINT64_FORMAT " seconds older",
-delta);
return FALSE;
} else if (delta > 0) {
g_debug ("timestamp increased, so no rollback");
}
}
}
/* save XML and signature to remotes.d */
if (!fu_common_set_contents_bytes (fwupd_remote_get_filename_cache (remote),
bytes_raw, error))
return FALSE;
if (keyring_kind != FWUPD_KEYRING_KIND_NONE) {
if (!fu_common_set_contents_bytes (fwupd_remote_get_filename_cache_sig (remote),
bytes_sig, error))
return FALSE;
}
return fu_engine_load_metadata_store (self, error);
}
static gboolean
fu_engine_is_device_supported (FuEngine *self, FuDevice *device)
{
AsApp *app;
/* no device version */
if (fu_device_get_version (device) == NULL)
return FALSE;
/* match the GUIDs in the XML */
app = fu_engine_store_get_app_by_guids (self->store, device);
if (app == NULL)
return FALSE;
/* success */
return TRUE;
}
/**
* fu_engine_get_store_from_blob:
* @self: A #FuEngine
* @blob_cab: A #GBytes
* @error: A #GError, or %NULL
*
* Creates an AppStream store from a .cab file blob.
*
* Returns: (transfer container): a #AsStore, or %NULL
**/
AsStore *
fu_engine_get_store_from_blob (FuEngine *self, GBytes *blob_cab, GError **error)
{
g_autofree gchar *checksum = NULL;
g_autoptr(AsStore) store = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (blob_cab != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* load file */
fu_engine_set_status (self, FWUPD_STATUS_DECOMPRESSING);
store = fu_common_store_from_cab_bytes (blob_cab,
fu_engine_get_archive_size_max (self),
error);
if (store == NULL)
return NULL;
/* get a checksum of the file and use it as the origin */
checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, blob_cab);
as_store_set_origin (store, checksum);
fu_engine_set_status (self, FWUPD_STATUS_IDLE);
return g_steal_pointer (&store);
}
static FwupdDevice *
fu_engine_get_result_from_app (FuEngine *self, AsApp *app, GError **error)
{
FwupdTrustFlags trust_flags = FWUPD_TRUST_FLAG_NONE;
AsRelease *release;
GPtrArray *provides;
g_autoptr(FwupdDevice) dev = NULL;
g_autoptr(FwupdRelease) rel = NULL;
dev = fwupd_device_new ();
provides = as_app_get_provides (app);
for (guint i = 0; i < provides->len; i++) {
AsProvide *prov = AS_PROVIDE (g_ptr_array_index (provides, i));
FuDevice *device;
const gchar *guid;
/* not firmware */
if (as_provide_get_kind (prov) != AS_PROVIDE_KIND_FIRMWARE_FLASHED)
continue;
/* is a online or offline update appropriate */
guid = as_provide_get_value (prov);
if (guid == NULL)
continue;
device = fu_device_list_find_by_guid (self->device_list, guid, NULL);
if (device != NULL) {
fwupd_device_set_name (dev, fu_device_get_name (device));
fwupd_device_set_flags (dev, fu_device_get_flags (device));
fwupd_device_set_id (dev, fu_device_get_id (device));
}
/* add GUID */
fwupd_device_add_guid (dev, guid);
}
if (fwupd_device_get_guids(dev)->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"component has no GUIDs");
return NULL;
}
/* check we can install it */
if (!fu_engine_check_requirements (self, app, NULL, error))
return NULL;
/* verify trust */
release = as_app_get_release_default (app);
if (!fu_engine_get_release_trust_flags (release, &trust_flags, error))
return NULL;
/* possibly convert the version from 0x to dotted */
fu_engine_vendor_quirk_release_version (self, app);
/* possibly convert the flashed provide to a GUID */
fu_engine_vendor_fixup_provide_value (app);
/* create a result with all the metadata in */
fwupd_device_set_description (dev, as_app_get_description (app, NULL));
rel = fwupd_release_new ();
fwupd_release_set_trust_flags (rel, trust_flags);
fu_engine_set_release_from_appstream (self, rel, app, release);
fwupd_device_add_release (dev, rel);
return g_steal_pointer (&dev);
}
/**
* fu_engine_get_details:
* @self: A #FuEngine
* @fd: A file descriptor
* @error: A #GError, or %NULL
*
* Gets the details about a local file.
*
* Note: this will close the fd when done
*
* Returns: (transfer container) (element-type FwupdDevice): results
**/
GPtrArray *
fu_engine_get_details (FuEngine *self, gint fd, GError **error)
{
GPtrArray *apps;
g_autoptr(AsStore) store = NULL;
g_autoptr(GBytes) blob = NULL;
g_autoptr(GPtrArray) details = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (fd > 0, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* get all apps */
blob = fu_common_get_contents_fd (fd,
fu_engine_get_archive_size_max (self),
error);
if (blob == NULL)
return NULL;
store = fu_engine_get_store_from_blob (self, blob, error);
if (store == NULL)
return NULL;
apps = as_store_get_apps (store);
if (apps->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no components");
return NULL;
}
/* create results with all the metadata in */
details = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
for (guint i = 0; i < apps->len; i++) {
AsApp *app = g_ptr_array_index (apps, i);
FwupdDevice *res = NULL;
/* check we can install it */
if (!fu_engine_check_requirements (self, app, NULL, error))
return NULL;
as_app_set_origin (app, as_store_get_origin (store));
res = fu_engine_get_result_from_app (self, app, error);
if (res == NULL)
return NULL;
g_ptr_array_add (details, res);
}
return g_steal_pointer (&details);
}
/**
* fu_engine_get_devices:
* @self: A #FuEngine
* @error: A #GError, or %NULL
*
* Gets the list of devices.
*
* Returns: (transfer container) (element-type FwupdDevice): results
**/
GPtrArray *
fu_engine_get_devices (FuEngine *self, GError **error)
{
g_autoptr(GPtrArray) devices = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
devices = fu_device_list_get_active (self->device_list);
if (devices->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No detected devices");
return NULL;
}
return g_steal_pointer (&devices);
}
/* finds the remote-id for the first firmware in the store that matches this
* container checksum */
static const gchar *
fu_engine_get_remote_id_for_checksum (FuEngine *self, const gchar *csum)
{
GPtrArray *array = as_store_get_apps (self->store);
for (guint i = 0; i < array->len; i++) {
AsApp *app = g_ptr_array_index (array, i);
GPtrArray *releases = as_app_get_releases (app);
for (guint j = 0; j < releases->len; j++) {
AsRelease *release = g_ptr_array_index (releases, j);
AsChecksum *checksum;
checksum = as_release_get_checksum_by_target (release,
AS_CHECKSUM_TARGET_CONTAINER);
if (checksum == NULL)
continue;
if (g_strcmp0 (csum, as_checksum_get_value (checksum)) == 0)
return _as_release_get_metadata_item (release, "fwupd::RemoteId");
}
}
return NULL;
}
/**
* fu_engine_get_history:
* @self: A #FuEngine
* @error: A #GError, or %NULL
*
* Gets the list of history.
*
* Returns: (transfer container) (element-type FwupdDevice): results
**/
GPtrArray *
fu_engine_get_history (FuEngine *self, GError **error)
{
g_autoptr(GPtrArray) devices = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
devices = fu_history_get_devices (self->history, error);
if (devices == NULL)
return NULL;
if (devices->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No history");
return NULL;
}
/* try to set the remote ID for each device */
for (guint i = 0; i < devices->len; i++) {
FuDevice *dev = g_ptr_array_index (devices, i);
FwupdRelease *rel;
GPtrArray *csums;
/* get the checksums */
rel = fu_device_get_release_default (dev);
if (rel == NULL)
continue;
/* find the checksum that matches */
csums = fwupd_release_get_checksums (rel);
for (guint j = 0; j < csums->len; j++) {
const gchar *csum = g_ptr_array_index (csums, j);
const gchar *remote_id = fu_engine_get_remote_id_for_checksum (self, csum);
if (remote_id != NULL) {
fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED);
fwupd_release_set_remote_id (rel, remote_id);
break;
}
}
}
return g_steal_pointer (&devices);
}
/**
* fu_engine_get_remotes:
* @self: A #FuEngine
* @error: A #GError, or %NULL
*
* Gets the list of remotes in use by the engine.
*
* Returns: (transfer container) (element-type FwupdRemote): results
**/
GPtrArray *
fu_engine_get_remotes (FuEngine *self, GError **error)
{
GPtrArray *remotes;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
remotes = fu_config_get_remotes (self->config);
if (remotes->len == 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"No remotes configured");
return NULL;
}
return g_ptr_array_ref (remotes);
}
static gint
fu_engine_sort_releases_cb (gconstpointer a, gconstpointer b)
{
FwupdRelease *rel_a = FWUPD_RELEASE (*((FwupdRelease **) a));
FwupdRelease *rel_b = FWUPD_RELEASE (*((FwupdRelease **) b));
return as_utils_vercmp (fwupd_release_get_version (rel_b),
fwupd_release_get_version (rel_a));
}
static AsApp *
fu_engine_filter_apps_by_requirements (FuEngine *self, GPtrArray *apps,
FuDevice *device, GError **error)
{
g_autoptr(GError) error_all = NULL;
/* find the first component that passes all the requirements */
for (guint i = 0; i < apps->len; i++) {
g_autoptr(GError) error_local = NULL;
AsApp *app_tmp = AS_APP (g_ptr_array_index (apps, i));
if (!fu_engine_check_requirements (self, app_tmp, device, &error_local)) {
if (error_all != NULL) {
error_all = g_steal_pointer (&error_local);
continue;
}
/* assume the domain and code is the same */
g_prefix_error (&error_all, "%s, ", error_local->message);
continue;
}
return g_object_ref (app_tmp);
}
/* return the compound error */
g_propagate_error (error, g_steal_pointer (&error_all));
return NULL;
}
static GPtrArray *
fu_engine_get_releases_for_device (FuEngine *self, FuDevice *device, GError **error)
{
GPtrArray *device_guids;
GPtrArray *releases;
const gchar *version;
/* get device version */
version = fu_device_get_version (device);
if (version == NULL) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no version set");
return NULL;
}
/* only show devices that can be updated */
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_UPDATABLE)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"ignoring %s [%s] as not updatable",
fu_device_get_id (device),
fu_device_get_name (device));
return NULL;
}
releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
device_guids = fu_device_get_guids (device);
for (guint i = 0; i < device_guids->len; i++) {
GPtrArray *releases_tmp;
g_autoptr(AsApp) app = NULL;
g_autoptr(GPtrArray) apps = NULL;
const gchar *guid = g_ptr_array_index (device_guids, i);
/* get all the components that provide this GUID */
apps = _as_store_get_apps_by_provide (self->store,
AS_PROVIDE_KIND_FIRMWARE_FLASHED,
guid);
if (apps->len == 0)
continue;
/* filter by requirements */
app = fu_engine_filter_apps_by_requirements (self, apps, device, error);
if (app == NULL)
return NULL;
/* get all releases */
releases_tmp = as_app_get_releases (app);
for (guint j = 0; j < releases_tmp->len; j++) {
AsRelease *release = g_ptr_array_index (releases_tmp, j);
GPtrArray *checksums;
g_autoptr(FwupdRelease) rel = fwupd_release_new ();
/* create new FwupdRelease for the AsRelease */
fu_engine_set_release_from_appstream (self, rel, app, release);
/* invalid */
if (fwupd_release_get_uri (rel) == NULL)
continue;
checksums = fwupd_release_get_checksums (rel);
if (checksums->len == 0)
continue;
/* success */
g_ptr_array_add (releases, g_steal_pointer (&rel));
}
}
return releases;
}
/**
* fu_engine_get_releases:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Gets the releases available for a specific device.
*
* Returns: (transfer container) (element-type FwupdDevice): results
**/
GPtrArray *
fu_engine_get_releases (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
g_autoptr(GPtrArray) releases = NULL;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (device_id != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* find the device */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return NULL;
/* get all the releases for the device */
releases = fu_engine_get_releases_for_device (self, device, error);
if (releases == NULL)
return NULL;
if (releases->len == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No releases for device");
return NULL;
}
g_ptr_array_sort (releases, fu_engine_sort_releases_cb);
return g_steal_pointer (&releases);
}
/**
* fu_engine_get_downgrades:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Gets the downgrades available for a specific device.
*
* Returns: (transfer container) (element-type FwupdDevice): results
**/
GPtrArray *
fu_engine_get_downgrades (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
g_autoptr(GPtrArray) releases = NULL;
g_autoptr(GPtrArray) releases_tmp = NULL;
g_autoptr(GString) error_str = g_string_new (NULL);
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (device_id != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* find the device */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return NULL;
/* get all the releases for the device */
releases_tmp = fu_engine_get_releases_for_device (self, device, error);
if (releases_tmp == NULL)
return NULL;
releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
for (guint i = 0; i < releases_tmp->len; i++) {
FwupdRelease *rel_tmp = g_ptr_array_index (releases_tmp, i);
gint vercmp;
/* only include older firmware */
vercmp = as_utils_vercmp (fwupd_release_get_version (rel_tmp),
fu_device_get_version (device));
if (vercmp == 0) {
g_string_append_printf (error_str, "%s=same, ",
fwupd_release_get_version (rel_tmp));
g_debug ("ignoring %s as the same as %s",
fwupd_release_get_version (rel_tmp),
fu_device_get_version (device));
continue;
}
if (vercmp > 0) {
g_string_append_printf (error_str, "%s=newer, ",
fwupd_release_get_version (rel_tmp));
g_debug ("ignoring %s as newer than %s",
fwupd_release_get_version (rel_tmp),
fu_device_get_version (device));
continue;
}
/* don't show releases we are not allowed to dowgrade to */
if (fu_device_get_version_lowest (device) != NULL) {
if (as_utils_vercmp (fwupd_release_get_version (rel_tmp),
fu_device_get_version_lowest (device)) <= 0) {
g_string_append_printf (error_str, "%s=lowest, ",
fwupd_release_get_version (rel_tmp));
g_debug ("ignoring %s as older than lowest %s",
fwupd_release_get_version (rel_tmp),
fu_device_get_version_lowest (device));
continue;
}
}
g_ptr_array_add (releases, g_object_ref (rel_tmp));
}
if (error_str->len > 2)
g_string_truncate (error_str, error_str->len - 2);
if (releases->len == 0) {
if (error_str->len > 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No downgrades for device, current is %s: %s",
fu_device_get_version (device),
error_str->str);
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No downgrades for device, current is %s",
fu_device_get_version (device));
}
return NULL;
}
g_ptr_array_sort (releases, fu_engine_sort_releases_cb);
return g_steal_pointer (&releases);
}
/**
* fu_engine_get_upgrades:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Gets the upgrades available for a specific device.
*
* Returns: (transfer container) (element-type FwupdDevice): results
**/
GPtrArray *
fu_engine_get_upgrades (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
g_autoptr(GPtrArray) releases = NULL;
g_autoptr(GPtrArray) releases_tmp = NULL;
g_autoptr(GString) error_str = g_string_new (NULL);
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (device_id != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* find the device */
device = fu_device_list_find_by_id (self->device_list, device_id, error);
if (device == NULL)
return NULL;
/* get all the releases for the device */
releases_tmp = fu_engine_get_releases_for_device (self, device, error);
if (releases_tmp == NULL)
return NULL;
releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
for (guint i = 0; i < releases_tmp->len; i++) {
FwupdRelease *rel_tmp = g_ptr_array_index (releases_tmp, i);
gint vercmp;
/* only include older firmware */
vercmp = as_utils_vercmp (fwupd_release_get_version (rel_tmp),
fu_device_get_version (device));
if (vercmp == 0) {
g_string_append_printf (error_str, "%s=same, ",
fwupd_release_get_version (rel_tmp));
g_debug ("ignoring %s as the same as %s",
fwupd_release_get_version (rel_tmp),
fu_device_get_version (device));
continue;
}
if (vercmp < 0) {
g_string_append_printf (error_str, "%s=older, ",
fwupd_release_get_version (rel_tmp));
g_debug ("ignoring %s as older than %s",
fwupd_release_get_version (rel_tmp),
fu_device_get_version (device));
continue;
}
g_ptr_array_add (releases, g_object_ref (rel_tmp));
}
if (error_str->len > 2)
g_string_truncate (error_str, error_str->len - 2);
if (releases->len == 0) {
if (error_str->len > 0) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No upgrades for device, current is %s: %s",
fu_device_get_version (device),
error_str->str);
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"No upgrades for device, current is %s",
fu_device_get_version (device));
}
return NULL;
}
g_ptr_array_sort (releases, fu_engine_sort_releases_cb);
return g_steal_pointer (&releases);
}
/**
* fu_engine_clear_results:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Clear the historical state of a specific device operation.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_clear_results (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
FuPlugin *plugin;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (device_id != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* find the device */
device = fu_engine_get_item_by_id_fallback_history (self, device_id, error);
if (device == NULL)
return FALSE;
/* get the plugin */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
error);
if (plugin == NULL)
return FALSE;
/* call into the plugin */
return fu_plugin_runner_clear_results (plugin, device, error);
}
/**
* fu_engine_get_results:
* @self: A #FuEngine
* @device_id: A device ID
* @error: A #GError, or %NULL
*
* Gets the historical state of a specific device operation.
*
* Returns: (transfer container): a #FwupdDevice, or %NULL
**/
FwupdDevice *
fu_engine_get_results (FuEngine *self, const gchar *device_id, GError **error)
{
FuDevice *device;
FuPlugin *plugin;
g_return_val_if_fail (FU_IS_ENGINE (self), NULL);
g_return_val_if_fail (device_id != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* find the device */
device = fu_engine_get_item_by_id_fallback_history (self, device_id, error);
if (device == NULL)
return NULL;
/* get the plugin */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
error);
if (plugin == NULL)
return FALSE;
/* call into the plugin */
if (!fu_plugin_runner_get_results (plugin, device, error))
return NULL;
return g_object_ref (FWUPD_DEVICE (device));
}
static void
fu_engine_plugins_setup (FuEngine *self)
{
GPtrArray *plugins;
g_autoptr(AsProfileTask) ptask = NULL;
ptask = as_profile_start_literal (self->profile, "FuEngine:setup");
g_assert (ptask != NULL);
plugins = fu_plugin_list_get_all (self->plugin_list);
for (guint i = 0; i < plugins->len; i++) {
g_autoptr(GError) error = NULL;
g_autoptr(AsProfileTask) ptask2 = NULL;
FuPlugin *plugin = g_ptr_array_index (plugins, i);
ptask2 = as_profile_start (self->profile,
"FuEngine:setup{%s}",
fu_plugin_get_name (plugin));
g_assert (ptask2 != NULL);
if (!fu_plugin_runner_startup (plugin, &error)) {
fu_plugin_set_enabled (plugin, FALSE);
g_message ("disabling plugin because: %s", error->message);
}
}
}
static void
fu_engine_plugins_coldplug (FuEngine *self)
{
GPtrArray *plugins;
g_autoptr(AsProfileTask) ptask = NULL;
g_autoptr(GString) str = g_string_new (NULL);
/* don't allow coldplug to be scheduled when in coldplug */
self->coldplug_running = TRUE;
/* prepare */
plugins = fu_plugin_list_get_all (self->plugin_list);
for (guint i = 0; i < plugins->len; i++) {
g_autoptr(GError) error = NULL;
FuPlugin *plugin = g_ptr_array_index (plugins, i);
if (!fu_plugin_runner_coldplug_prepare (plugin, &error))
g_warning ("failed to prepare coldplug: %s", error->message);
}
/* do this in one place */
if (self->coldplug_delay > 0) {
g_debug ("sleeping for %ums", self->coldplug_delay);
g_usleep (self->coldplug_delay * 1000);
}
/* exec */
ptask = as_profile_start_literal (self->profile, "FuEngine:coldplug");
g_assert (ptask != NULL);
for (guint i = 0; i < plugins->len; i++) {
g_autoptr(GError) error = NULL;
g_autoptr(AsProfileTask) ptask2 = NULL;
FuPlugin *plugin = g_ptr_array_index (plugins, i);
ptask2 = as_profile_start (self->profile,
"FuEngine:coldplug{%s}",
fu_plugin_get_name (plugin));
g_assert (ptask2 != NULL);
if (!fu_plugin_runner_coldplug (plugin, &error)) {
fu_plugin_set_enabled (plugin, FALSE);
g_message ("disabling plugin because: %s", error->message);
}
}
/* cleanup */
for (guint i = 0; i < plugins->len; i++) {
g_autoptr(GError) error = NULL;
FuPlugin *plugin = g_ptr_array_index (plugins, i);
if (!fu_plugin_runner_coldplug_cleanup (plugin, &error))
g_warning ("failed to cleanup coldplug: %s", error->message);
}
/* print what we do have */
for (guint i = 0; i < plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index (plugins, i);
if (!fu_plugin_get_enabled (plugin))
continue;
g_string_append_printf (str, "%s, ", fu_plugin_get_name (plugin));
}
if (str->len > 2) {
g_string_truncate (str, str->len - 2);
g_message ("using plugins: %s", str->str);
}
/* we can recoldplug from this point on */
self->coldplug_running = FALSE;
}
static void
fu_engine_plugin_device_register (FuEngine *self, FuDevice *device)
{
GPtrArray *plugins;
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED)) {
g_warning ("already registered %s, ignoring",
fu_device_get_id (device));
return;
}
plugins = fu_plugin_list_get_all (self->plugin_list);
for (guint i = 0; i < plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index (plugins, i);
fu_plugin_runner_device_register (plugin, device);
}
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_REGISTERED);
}
static void
fu_engine_plugin_device_register_cb (FuPlugin *plugin,
FuDevice *device,
gpointer user_data)
{
FuEngine *self = FU_ENGINE (user_data);
fu_engine_plugin_device_register (self, device);
}
static void
fu_engine_plugin_device_added_cb (FuPlugin *plugin,
FuDevice *device,
gpointer user_data)
{
FuEngine *self = (FuEngine *) user_data;
fu_engine_add_device (self, device);
}
void
fu_engine_add_device (FuEngine *self, FuDevice *device)
{
GPtrArray *blacklisted_devices;
GPtrArray *device_guids;
/* device has no GUIDs set! */
device_guids = fu_device_get_guids (device);
if (device_guids->len == 0) {
g_warning ("no GUIDs for device %s [%s]",
fu_device_get_id (device),
fu_device_get_name (device));
return;
}
/* is this GUID blacklisted */
blacklisted_devices = fu_config_get_blacklist_devices (self->config);
for (guint i = 0; i < blacklisted_devices->len; i++) {
const gchar *blacklisted_guid = g_ptr_array_index (blacklisted_devices, i);
for (guint j = 0; j < device_guids->len; j++) {
const gchar *device_guid = g_ptr_array_index (device_guids, j);
if (g_strcmp0 (blacklisted_guid, device_guid) == 0) {
g_debug ("%s is blacklisted [%s], ignoring from %s",
fu_device_get_id (device), device_guid,
fu_device_get_plugin (device));
return;
}
}
}
/* notify all plugins about this new device */
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_REGISTERED))
fu_engine_plugin_device_register (self, device);
/* match the metadata at this point so clients can tell if the
* device is worthy */
if (fu_engine_is_device_supported (self, device))
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_SUPPORTED);
/* create new device */
fu_device_list_add (self->device_list, device);
}
static void
fu_engine_plugin_device_removed_cb (FuPlugin *plugin,
FuDevice *device,
gpointer user_data)
{
FuEngine *self = (FuEngine *) user_data;
FuDevice *device_tmp;
FuPlugin *plugin_old;
g_autoptr(GError) error = NULL;
device_tmp = fu_device_list_find_by_id (self->device_list,
fu_device_get_id (device),
&error);
if (device_tmp == NULL) {
g_debug ("%s", error->message);
return;
}
/* get the plugin */
plugin_old = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (device),
&error);
if (plugin_old == NULL) {
g_debug ("%s", error->message);
return;
}
/* check this came from the same plugin */
if (g_strcmp0 (fu_plugin_get_name (plugin),
fu_plugin_get_name (plugin_old)) != 0) {
g_debug ("ignoring duplicate removal from %s",
fu_plugin_get_name (plugin));
return;
}
/* make the UI update */
fu_device_list_remove (self->device_list, device);
fu_engine_emit_changed (self);
}
static gboolean
fu_engine_recoldplug_delay_cb (gpointer user_data)
{
FuEngine *self = (FuEngine *) user_data;
g_debug ("performing a recoldplug");
fu_engine_plugins_coldplug (self);
self->coldplug_id = 0;
return FALSE;
}
static void
fu_engine_plugin_recoldplug_cb (FuPlugin *plugin, FuEngine *self)
{
if (self->coldplug_running) {
g_warning ("coldplug already running, cannot recoldplug");
return;
}
g_debug ("scheduling a recoldplug");
if (self->coldplug_id != 0)
g_source_remove (self->coldplug_id);
self->coldplug_id = g_timeout_add (1500, fu_engine_recoldplug_delay_cb, self);
}
static void
fu_engine_plugin_set_coldplug_delay_cb (FuPlugin *plugin, guint duration, FuEngine *self)
{
self->coldplug_delay = MAX (self->coldplug_delay, duration);
g_debug ("got coldplug delay of %ums, global maximum is now %ums",
duration, self->coldplug_delay);
}
/* for the self tests to use */
void
fu_engine_add_plugin (FuEngine *self, FuPlugin *plugin)
{
fu_plugin_list_add (self->plugin_list, plugin);
}
static gboolean
fu_engine_load_plugins (FuEngine *self, GError **error)
{
const gchar *fn;
g_autoptr(GDir) dir = NULL;
g_autoptr(AsProfileTask) ptask = NULL;
/* profile */
ptask = as_profile_start_literal (self->profile, "FuEngine:load-plugins");
g_assert (ptask != NULL);
/* search */
dir = g_dir_open (PLUGINDIR, 0, error);
if (dir == NULL)
return FALSE;
while ((fn = g_dir_read_name (dir)) != NULL) {
GPtrArray *blacklist;
g_autofree gchar *filename = NULL;
g_autoptr(FuPlugin) plugin = NULL;
g_autoptr(GError) error_local = NULL;
/* ignore non-plugins */
if (!g_str_has_suffix (fn, ".so"))
continue;
/* open module */
filename = g_build_filename (PLUGINDIR, fn, NULL);
plugin = fu_plugin_new ();
fu_plugin_set_usb_context (plugin, self->usb_ctx);
fu_plugin_set_hwids (plugin, self->hwids);
fu_plugin_set_smbios (plugin, self->smbios);
fu_plugin_set_supported (plugin, self->supported_guids);
fu_plugin_set_quirks (plugin, self->quirks);
g_debug ("adding plugin %s", filename);
if (!fu_plugin_open (plugin, filename, &error_local)) {
g_warning ("failed to open plugin %s: %s",
filename, error_local->message);
continue;
}
/* is blacklisted */
blacklist = fu_config_get_blacklist_plugins (self->config);
for (guint i = 0; i < blacklist->len; i++) {
const gchar *name = g_ptr_array_index (blacklist, i);
if (g_strcmp0 (name, fu_plugin_get_name (plugin)) == 0) {
fu_plugin_set_enabled (plugin, FALSE);
break;
}
}
if (!fu_plugin_get_enabled (plugin)) {
g_debug ("%s blacklisted by config",
fu_plugin_get_name (plugin));
continue;
}
/* watch for changes */
g_signal_connect (plugin, "device-added",
G_CALLBACK (fu_engine_plugin_device_added_cb),
self);
g_signal_connect (plugin, "device-removed",
G_CALLBACK (fu_engine_plugin_device_removed_cb),
self);
g_signal_connect (plugin, "device-register",
G_CALLBACK (fu_engine_plugin_device_register_cb),
self);
g_signal_connect (plugin, "recoldplug",
G_CALLBACK (fu_engine_plugin_recoldplug_cb),
self);
g_signal_connect (plugin, "set-coldplug-delay",
G_CALLBACK (fu_engine_plugin_set_coldplug_delay_cb),
self);
/* add */
fu_plugin_list_add (self->plugin_list, plugin);
}
/* depsolve into the correct order */
if (!fu_plugin_list_depsolve (self->plugin_list, error))
return FALSE;
/* success */
return TRUE;
}
/**
* fu_engine_check_plugins_pending:
* @self: A #FuEngine
* @error: A #GError, or %NULL
*
* Checks if any plugins have pending devices to be added.
*
* Returns: %FALSE if any plugins have pending devices.
**/
gboolean
fu_engine_check_plugins_pending (FuEngine *self, GError **error)
{
GPtrArray *plugins;
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
plugins = fu_plugin_list_get_all (self->plugin_list);
for (guint i = 0; i < plugins->len; i++) {
FuPlugin *plugin = g_ptr_array_index (plugins, i);
if (fu_plugin_has_device_delay (plugin)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"%s pending",
fu_plugin_get_name (plugin));
return FALSE;
}
}
return TRUE;
}
static gboolean
fu_engine_cleanup_state (GError **error)
{
const gchar *filenames[] = {
"/var/cache/app-info/xmls/fwupd-verify.xml",
"/var/cache/app-info/xmls/fwupd.xml",
NULL };
for (guint i = 0; filenames[i] != NULL; i++) {
g_autoptr(GFile) file = g_file_new_for_path (filenames[i]);
if (g_file_query_exists (file, NULL)) {
if (!g_file_delete (file, NULL, error))
return FALSE;
}
}
return TRUE;
}
guint64
fu_engine_get_archive_size_max (FuEngine *self)
{
return fu_config_get_archive_size_max (self->config);
}
static void
fu_engine_usb_device_removed_cb (GUsbContext *ctx,
GUsbDevice *usb_device,
FuEngine *self)
{
g_autoptr(GPtrArray) devices = NULL;
/* go through each device and remove any that match */
devices = fu_device_list_get_all (self->device_list);
for (guint i = 0; i < devices->len; i++) {
FuDevice *device = g_ptr_array_index (devices, i);
if (!FU_IS_USB_DEVICE (device))
continue;
if (g_strcmp0 (fu_device_get_platform_id (device),
g_usb_device_get_platform_id (usb_device)) == 0) {
g_debug ("auto-removing FuUsbDevice");
fu_device_list_remove (self->device_list, device);
}
}
}
static void
fu_engine_usb_device_added_cb (GUsbContext *ctx,
GUsbDevice *usb_device,
FuEngine *self)
{
GPtrArray *plugins = fu_plugin_list_get_all (self->plugin_list);
/* call into each plugin */
for (guint j = 0; j < plugins->len; j++) {
FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j);
g_autoptr(GError) error = NULL;
if (!fu_plugin_runner_usb_device_added (plugin_tmp, usb_device, &error)) {
if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
g_debug ("ignoring: %s", error->message);
continue;
}
g_warning ("failed to add USB device: %s", error->message);
}
}
}
static void
fu_engine_load_quirks (FuEngine *self)
{
g_autoptr(AsProfileTask) ptask = NULL;
g_autoptr(GError) error = NULL;
/* profile */
ptask = as_profile_start_literal (self->profile, "FuEngine:load-quirks");
g_assert (ptask != NULL);
if (!fu_quirks_load (self->quirks, &error))
g_warning ("Failed to load quirks: %s", error->message);
}
static void
fu_engine_load_smbios (FuEngine *self)
{
g_autoptr(AsProfileTask) ptask = NULL;
g_autoptr(GError) error = NULL;
/* profile */
ptask = as_profile_start_literal (self->profile, "FuEngine:load-smbios");
g_assert (ptask != NULL);
if (!fu_smbios_setup (self->smbios, &error))
g_warning ("Failed to load SMBIOS: %s", error->message);
}
static void
fu_engine_load_hwids (FuEngine *self)
{
g_autoptr(AsProfileTask) ptask = NULL;
g_autoptr(GError) error = NULL;
/* profile */
ptask = as_profile_start_literal (self->profile, "FuEngine:load-hwids");
g_assert (ptask != NULL);
if (!fu_hwids_setup (self->hwids, self->smbios, &error))
g_warning ("Failed to load HWIDs: %s", error->message);
}
static gboolean
fu_engine_update_history_device (FuEngine *self, FuDevice *dev_history, GError **error)
{
FuDevice *dev;
FuPlugin *plugin;
FwupdRelease *rel_history;
/* is in the device list */
dev = fu_device_list_find_by_id (self->device_list,
fu_device_get_id (dev_history),
error);
if (dev == NULL)
return FALSE;
/* does the installed version match what we tried to install
* before fwupd was restarted */
rel_history = fu_device_get_release_default (dev_history);
if (rel_history == NULL) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"no release for history FuDevice");
return FALSE;
}
/* the system is running with the new firmware version */
if (g_strcmp0 (fu_device_get_version (dev),
fwupd_release_get_version (rel_history)) == 0) {
g_debug ("installed version %s matching history %s",
fu_device_get_version (dev),
fwupd_release_get_version (rel_history));
if (!fu_history_set_update_state (self->history, dev_history,
FWUPD_UPDATE_STATE_SUCCESS,
error))
return FALSE;
return TRUE;
}
/* find the plugin that started the update */
plugin = fu_plugin_list_find_by_name (self->plugin_list,
fu_device_get_plugin (dev),
error);
if (plugin == NULL)
return FALSE;
/* the plugin knows the update state */
if (!fu_plugin_runner_get_results (plugin, dev, error))
return FALSE;
if (fu_device_get_update_state (dev) != FWUPD_UPDATE_STATE_NEEDS_REBOOT) {
if (!fu_history_set_update_state (self->history, dev,
fu_device_get_update_state (dev),
error))
return FALSE;
if (!fu_history_set_error_msg (self->history, dev,
fu_device_get_update_error (dev),
error))
return FALSE;
}
return TRUE;
}
static gboolean
fu_engine_update_history_database (FuEngine *self, GError **error)
{
g_autoptr(GPtrArray) devices = NULL;
/* get any devices */
devices = fu_history_get_devices (self->history, error);
if (devices == NULL)
return FALSE;
for (guint i = 0; i < devices->len; i++) {
FuDevice *dev = g_ptr_array_index (devices, i);
g_autoptr(GError) error_local = NULL;
/* not in the required state */
if (fu_device_get_update_state (dev) != FWUPD_UPDATE_STATE_NEEDS_REBOOT)
continue;
/* try to save the new update-state, but ignoring any error */
if (!fu_engine_update_history_device (self, dev, &error_local))
g_warning ("%s", error_local->message);
}
return TRUE;
}
/**
* fu_engine_load:
* @self: A #FuEngine
* @error: A #GError, or %NULL
*
* Load the firmware update engine so it is ready for use.
*
* Returns: %TRUE for success
**/
gboolean
fu_engine_load (FuEngine *self, GError **error)
{
g_autoptr(AsProfileTask) ptask = NULL;
/* profile */
ptask = as_profile_start_literal (self->profile, "FuEngine:load");
g_assert (ptask != NULL);
g_return_val_if_fail (FU_IS_ENGINE (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* read config file */
if (!fu_config_load (self->config, error)) {
g_prefix_error (error, "Failed to load config: ");
return FALSE;
}
/* load quirks, SMBIOS and the hwids */
fu_engine_load_smbios (self);
fu_engine_load_hwids (self);
fu_engine_load_quirks (self);
/* load AppStream metadata */
as_store_add_filter (self->store, AS_APP_KIND_FIRMWARE);
if (!fu_engine_load_metadata_store (self, error)) {
g_prefix_error (error, "Failed to load AppStream data: ");
return FALSE;
}
/* set shared USB context */
self->usb_ctx = g_usb_context_new (error);
if (self->usb_ctx == NULL) {
g_prefix_error (error, "Failed to get USB context: ");
return FALSE;
}
/* delete old data files */
if (!fu_engine_cleanup_state (error)) {
g_prefix_error (error, "Failed to clean up: ");
return FALSE;
}
/* load plugin */
if (!fu_engine_load_plugins (self, error)) {
g_prefix_error (error, "Failed to load plugins: ");
return FALSE;
}
/* watch the device list for updates and proxy */
g_signal_connect (self->device_list, "added",
G_CALLBACK (fu_engine_device_added_cb),
self);
g_signal_connect (self->device_list, "removed",
G_CALLBACK (fu_engine_device_removed_cb),
self);
g_signal_connect (self->device_list, "changed",
G_CALLBACK (fu_engine_device_changed_cb),
self);
/* add devices */
fu_engine_plugins_setup (self);
fu_engine_plugins_coldplug (self);
/* coldplug USB devices */
g_signal_connect (self->usb_ctx, "device-added",
G_CALLBACK (fu_engine_usb_device_added_cb),
self);
g_signal_connect (self->usb_ctx, "device-removed",
G_CALLBACK (fu_engine_usb_device_removed_cb),
self);
g_usb_context_enumerate (self->usb_ctx);
/* update the db for devices that were updated during the reboot */
if (!fu_engine_update_history_database (self, error))
return FALSE;
/* success */
return TRUE;
}
static void
fu_engine_class_init (FuEngineClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fu_engine_finalize;
signals[SIGNAL_CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[SIGNAL_DEVICE_ADDED] =
g_signal_new ("device-added",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, FU_TYPE_DEVICE);
signals[SIGNAL_DEVICE_REMOVED] =
g_signal_new ("device-removed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, FU_TYPE_DEVICE);
signals[SIGNAL_DEVICE_CHANGED] =
g_signal_new ("device-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, FU_TYPE_DEVICE);
signals[SIGNAL_STATUS_CHANGED] =
g_signal_new ("status-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
signals[SIGNAL_PERCENTAGE_CHANGED] =
g_signal_new ("percentage-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
}
static void
fu_engine_init (FuEngine *self)
{
self->percentage = 0;
self->status = FWUPD_STATUS_IDLE;
self->config = fu_config_new ();
self->device_list = fu_device_list_new ();
self->smbios = fu_smbios_new ();
self->hwids = fu_hwids_new ();
self->quirks = fu_quirks_new ();
self->history = fu_history_new ();
self->plugin_list = fu_plugin_list_new ();
self->profile = as_profile_new ();
self->store = as_store_new ();
self->supported_guids = g_ptr_array_new_with_free_func (g_free);
}
static void
fu_engine_finalize (GObject *obj)
{
FuEngine *self = FU_ENGINE (obj);
if (self->usb_ctx != NULL)
g_object_unref (self->usb_ctx);
if (self->coldplug_id != 0)
g_source_remove (self->coldplug_id);
g_object_unref (self->config);
g_object_unref (self->smbios);
g_object_unref (self->quirks);
g_object_unref (self->hwids);
g_object_unref (self->history);
g_object_unref (self->plugin_list);
g_object_unref (self->profile);
g_object_unref (self->store);
g_object_unref (self->device_list);
g_ptr_array_unref (self->supported_guids);
G_OBJECT_CLASS (fu_engine_parent_class)->finalize (obj);
}
FuEngine *
fu_engine_new (void)
{
FuEngine *self;
self = g_object_new (FU_TYPE_ENGINE, NULL);
return FU_ENGINE (self);
}