blob: 7b5d560840bf6c2373a9e7d1c6ad1ed2fdba0225 [file] [log] [blame]
/*
* Copyright 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#define G_LOG_DOMAIN "FuMain"
#include "config.h"
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#ifdef HAVE_MMAN_H
#include <sys/mman.h>
#endif
#include "fu-context-private.h"
#include "fu-daemon.h"
typedef struct {
FuEngine *engine;
GMainLoop *loop;
FuDaemonMachineKind machine_kind;
guint housekeeping_id;
gboolean update_in_progress;
gboolean pending_stop;
guint process_quit_id;
} FuDaemonPrivate;
G_DEFINE_TYPE_WITH_PRIVATE(FuDaemon, fu_daemon, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (fu_daemon_get_instance_private(o))
#define FU_DAEMON_HOUSEKEEPING_DELAY 10 /* seconds */
FuEngine *
fu_daemon_get_engine(FuDaemon *self)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DAEMON(self), NULL);
return priv->engine;
}
void
fu_daemon_set_machine_kind(FuDaemon *self, FuDaemonMachineKind machine_kind)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DAEMON(self));
priv->machine_kind = machine_kind;
}
FuDaemonMachineKind
fu_daemon_get_machine_kind(FuDaemon *self)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DAEMON(self), 0);
return priv->machine_kind;
}
void
fu_daemon_set_update_in_progress(FuDaemon *self, gboolean update_in_progress)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DAEMON(self));
priv->update_in_progress = update_in_progress;
}
gboolean
fu_daemon_get_pending_stop(FuDaemon *self)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DAEMON(self), FALSE);
return priv->pending_stop;
}
static gboolean
fu_daemon_schedule_housekeeping_cb(gpointer user_data)
{
FuDaemon *self = FU_DAEMON(user_data);
FuDaemonPrivate *priv = GET_PRIVATE(self);
FuContext *ctx = fu_engine_get_context(priv->engine);
#ifdef HAVE_MALLOC_TRIM
/* drop heap except one page */
malloc_trim(0);
#endif
/* anything that listens to the context can perform actions now */
fu_context_housekeeping(ctx);
/* success */
priv->housekeeping_id = 0;
return G_SOURCE_REMOVE;
}
void
fu_daemon_schedule_housekeeping(FuDaemon *self)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
guint delay = FU_DAEMON_HOUSEKEEPING_DELAY;
if (priv->update_in_progress)
return;
if (priv->housekeeping_id != 0)
g_source_remove(priv->housekeeping_id);
if (g_getenv("CI") != NULL)
delay = 1;
priv->housekeeping_id =
g_timeout_add_seconds(delay, fu_daemon_schedule_housekeeping_cb, self);
}
static gboolean
fu_daemon_schedule_process_quit_cb(gpointer user_data)
{
FuDaemon *self = FU_DAEMON(user_data);
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_autoptr(GError) error = NULL;
g_info("daemon asked to quit, shutting down");
priv->process_quit_id = 0;
if (!fu_daemon_stop(self, &error))
g_warning("failed to stop daemon, will wait: %s\n", error->message);
return G_SOURCE_REMOVE;
}
void
fu_daemon_schedule_process_quit(FuDaemon *self)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
/* busy? */
if (priv->update_in_progress) {
g_warning("asked to quit during a firmware update, ignoring");
return;
}
/* allow the daemon to respond to the request, then quit */
if (priv->process_quit_id != 0)
g_source_remove(priv->process_quit_id);
priv->process_quit_id = g_idle_add(fu_daemon_schedule_process_quit_cb, self);
}
static gboolean
fu_daemon_check_syscall_filtering(GError **error)
{
#ifdef HAVE_MMAN_H
if (g_getenv("FWUPD_SYSCALL_FILTER") != NULL) {
const gsize bufsz = 10;
g_autofree guint8 *buf = g_malloc0(bufsz);
if (mlock(buf, bufsz) == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_BROKEN_SYSTEM,
"syscall filtering is configured but not working");
munlock(buf, bufsz);
return FALSE;
}
g_debug("syscall filtering is working");
}
#endif
/* success */
return TRUE;
}
gboolean
fu_daemon_setup(FuDaemon *self, const gchar *socket_address, GError **error)
{
FuDaemonClass *klass = FU_DAEMON_GET_CLASS(self);
FuDaemonPrivate *priv = GET_PRIVATE(self);
FuEngine *engine = fu_daemon_get_engine(self);
const gchar *machine_kind = g_getenv("FWUPD_MACHINE_KIND");
guint timer_max_ms;
g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
g_autoptr(GTimer) timer = g_timer_new();
g_return_val_if_fail(FU_IS_DAEMON(self), FALSE);
g_return_val_if_fail(FU_IS_ENGINE(engine), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* check that the process manager is preventing access to dangerous system calls */
if (!fu_daemon_check_syscall_filtering(error))
return FALSE;
/* allow overriding for development */
if (machine_kind != NULL) {
priv->machine_kind = fu_daemon_machine_kind_from_string(machine_kind);
if (priv->machine_kind == FU_DAEMON_MACHINE_KIND_UNKNOWN) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_DATA,
"invalid machine kind specified: %s",
machine_kind);
return FALSE;
}
}
/* proxy on */
if (!klass->setup(self, socket_address, progress, error))
return FALSE;
/* how did we do */
timer_max_ms = fu_config_get_value_u64(FU_CONFIG(fu_engine_get_config(engine)),
"fwupd",
"IdleInhibitStartupThreshold");
if (timer_max_ms > 0) {
guint timer_ms = g_timer_elapsed(timer, NULL) * 1000.f;
if (timer_ms > timer_max_ms) {
g_autofree gchar *reason =
g_strdup_printf("daemon-startup-%ums-max-%ums", timer_ms, timer_max_ms);
fu_engine_idle_inhibit(engine, FU_IDLE_INHIBIT_TIMEOUT, reason);
}
}
/* a good place to do the traceback */
if (fu_progress_get_profile(progress)) {
g_autofree gchar *str = fu_progress_traceback(progress);
if (str != NULL)
g_print("\n%s\n", str);
}
/* success */
return TRUE;
}
gboolean
fu_daemon_start(FuDaemon *self, GError **error)
{
FuDaemonClass *klass = FU_DAEMON_GET_CLASS(self);
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DAEMON(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* super useful for debugging */
if (g_getenv("FWUPD_VERBOSE") != NULL) {
GHashTableIter iter;
const gchar *key;
const gchar *value;
g_autoptr(GHashTable) metadata = NULL;
g_autoptr(GString) str = g_string_new("report metadata:");
metadata = fu_engine_get_report_metadata(priv->engine, error);
if (metadata == NULL)
return FALSE;
g_hash_table_iter_init(&iter, metadata);
while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value))
g_string_append_printf(str, "\n - %s=%s", key, value);
g_debug("%s", str->str);
}
/* optional */
if (klass->start != NULL && !klass->start(self, error))
return FALSE;
fu_daemon_schedule_housekeeping(self);
g_main_loop_run(priv->loop);
return TRUE;
}
gboolean
fu_daemon_stop(FuDaemon *self, GError **error)
{
FuDaemonClass *klass = FU_DAEMON_GET_CLASS(self);
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DAEMON(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (priv->update_in_progress) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"in a firmware update, ignoring");
priv->pending_stop = TRUE;
return FALSE;
}
/* optional */
if (klass->stop != NULL && !klass->stop(self, error))
return FALSE;
g_main_loop_quit(priv->loop);
return TRUE;
}
static void
fu_daemon_init(FuDaemon *self)
{
FuDaemonPrivate *priv = GET_PRIVATE(self);
g_autoptr(FuContext) ctx = fu_context_new();
priv->engine = fu_engine_new(ctx);
priv->loop = g_main_loop_new(NULL, FALSE);
}
static void
fu_daemon_finalize(GObject *obj)
{
FuDaemon *self = FU_DAEMON(obj);
FuDaemonPrivate *priv = GET_PRIVATE(self);
if (priv->loop != NULL)
g_main_loop_unref(priv->loop);
if (priv->housekeeping_id != 0)
g_source_remove(priv->housekeeping_id);
if (priv->process_quit_id != 0)
g_source_remove(priv->process_quit_id);
if (priv->engine != NULL)
g_object_unref(priv->engine);
G_OBJECT_CLASS(fu_daemon_parent_class)->finalize(obj);
}
static void
fu_daemon_class_init(FuDaemonClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fu_daemon_finalize;
}