From: Richard Hughes Date: Sat, 21 Jan 2023 13:37:49 +0000 (+0000) Subject: Allow desktop software to inhibit the system to prevent all updates X-Git-Url: https://git.proxmox.com/?a=commitdiff_plain;h=3457715749be384e8f5d79e2e216c38aa7985a4e;p=fwupd.git Allow desktop software to inhibit the system to prevent all updates On edge hardware a process may want to disable firmware updates as it might be a bad time to allow an upgrade. --- diff --git a/data/bash-completion/fwupdmgr b/data/bash-completion/fwupdmgr index 95646cf37..8fb864fd8 100644 --- a/data/bash-completion/fwupdmgr +++ b/data/bash-completion/fwupdmgr @@ -20,6 +20,8 @@ _fwupdmgr_cmd_list=( 'get-updates' 'get-upgrades' 'get-plugins' + 'inhibit' + 'uninhibit' 'install' 'local-install' 'modify-config' diff --git a/data/fish-completion/fwupdmgr.fish b/data/fish-completion/fwupdmgr.fish index e2746ac15..aab972279 100644 --- a/data/fish-completion/fwupdmgr.fish +++ b/data/fish-completion/fwupdmgr.fish @@ -64,6 +64,8 @@ complete -c fwupdmgr -n '__fish_use_subcommand' -x -a unlock -d 'Unlocks the dev complete -c fwupdmgr -n '__fish_use_subcommand' -x -a update -d 'Updates all firmware to latest versions available' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify -d 'Checks cryptographic hash matches firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify-update -d 'Update the stored cryptographic hash with current ROM contents' +complete -c fwupdmgr -n '__fish_use_subcommand' -x -a inhibit -d 'Inhibit the system to prevent upgrades' +complete -c fwupdmgr -n '__fish_use_subcommand' -x -a uninhibit -d 'Uninhibit the system to allow upgrades' # commands exclusively consuming device IDs set -l deviceid_consumers activate clear-results downgrade get-releases get-results get-updates reinstall switch-branch unlock update verify verify-update diff --git a/libfwupd/fwupd-client-sync.c b/libfwupd/fwupd-client-sync.c index a1505a47e..294786efe 100644 --- a/libfwupd/fwupd-client-sync.c +++ b/libfwupd/fwupd-client-sync.c @@ -673,6 +673,113 @@ fwupd_client_unlock(FwupdClient *self, } return TRUE; } + +static void +fwupd_client_inhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + FwupdClientHelper *helper = (FwupdClientHelper *)user_data; + helper->str = fwupd_client_inhibit_finish(FWUPD_CLIENT(source), res, &helper->error); + g_main_loop_quit(helper->loop); +} + +/** + * fwupd_client_inhibit: + * @self: a #FwupdClient + * @reason: (not nullable): the inhibit reason, e.g. `user active` + * @cancellable: (nullable): optional #GCancellable + * @error: (nullable): optional return location for an error + * + * Marks all devices as unavailable for update. Update is only available if there is no other + * inhibit imposed by other applications or by the system (e.g. low power state). + * + * The same application can inhibit the system multiple times. + * + * Returns: (transfer full): a string to use for [method@FwupdClient.uninhibit_async], + * or %NULL for failure + * + * Since: 1.8.11 + **/ +gchar * +fwupd_client_inhibit(FwupdClient *self, + const gchar *reason, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FwupdClientHelper) helper = NULL; + + g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); + g_return_val_if_fail(reason != NULL, NULL); + g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + + /* connect */ + if (!fwupd_client_connect(self, cancellable, error)) + return NULL; + + /* call async version and run loop until complete */ + helper = fwupd_client_helper_new(self); + fwupd_client_inhibit_async(self, reason, cancellable, fwupd_client_inhibit_cb, helper); + g_main_loop_run(helper->loop); + if (helper->str == NULL) { + g_propagate_error(error, g_steal_pointer(&helper->error)); + return NULL; + } + return g_steal_pointer(&helper->str); +} + +static void +fwupd_client_uninhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + FwupdClientHelper *helper = (FwupdClientHelper *)user_data; + helper->ret = fwupd_client_uninhibit_finish(FWUPD_CLIENT(source), res, &helper->error); + g_main_loop_quit(helper->loop); +} + +/** + * fwupd_client_uninhibit: + * @self: a #FwupdClient + * @uninhibit_id: (not nullable): the inhibit ID + * @cancellable: (nullable): optional #GCancellable + * @error: (nullable): optional return location for an error + * + * Removes the inhibit token added by the application. + * + * Returns: %TRUE for success + * + * Since: 1.8.11 + **/ +gboolean +fwupd_client_uninhibit(FwupdClient *self, + const gchar *inhibit_id, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FwupdClientHelper) helper = NULL; + + g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); + g_return_val_if_fail(inhibit_id != NULL, FALSE); + g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + /* connect */ + if (!fwupd_client_connect(self, cancellable, error)) + return FALSE; + + /* call async version and run loop until complete */ + helper = fwupd_client_helper_new(self); + fwupd_client_uninhibit_async(self, + inhibit_id, + cancellable, + fwupd_client_uninhibit_cb, + helper); + g_main_loop_run(helper->loop); + if (!helper->ret) { + g_propagate_error(error, g_steal_pointer(&helper->error)); + return FALSE; + } + return TRUE; +} + static void fwupd_client_modify_config_cb(GObject *source, GAsyncResult *res, gpointer user_data) { diff --git a/libfwupd/fwupd-client-sync.h b/libfwupd/fwupd-client-sync.h index bc38cf1e6..bbac721fa 100644 --- a/libfwupd/fwupd-client-sync.h +++ b/libfwupd/fwupd-client-sync.h @@ -66,6 +66,16 @@ fwupd_client_unlock(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; +gchar * +fwupd_client_inhibit(FwupdClient *self, + const gchar *reason, + GCancellable *cancellable, + GError **error) G_GNUC_WARN_UNUSED_RESULT; +gboolean +fwupd_client_uninhibit(FwupdClient *self, + const gchar *inhibit_id, + GCancellable *cancellable, + GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fwupd_client_modify_config(FwupdClient *self, const gchar *key, diff --git a/libfwupd/fwupd-client.c b/libfwupd/fwupd-client.c index 2976a908b..77753b2d7 100644 --- a/libfwupd/fwupd-client.c +++ b/libfwupd/fwupd-client.c @@ -5390,6 +5390,168 @@ fwupd_client_upload_bytes_finish(FwupdClient *self, GAsyncResult *res, GError ** return g_task_propagate_pointer(G_TASK(res), error); } +static void +fwupd_client_inhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + g_autofree gchar *inhibit_id = NULL; + g_autoptr(GTask) task = G_TASK(user_data); + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) val = NULL; + + val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (val == NULL) { + fwupd_client_fixup_dbus_error(error); + g_task_return_error(task, g_steal_pointer(&error)); + return; + } + + /* success */ + g_variant_get(val, "(s)", &inhibit_id); + g_task_return_pointer(task, g_steal_pointer(&inhibit_id), g_free); +} + +/** + * fwupd_client_inhibit_async: + * @self: a #FwupdClient + * @reason: (not nullable): the inhibit reason, e.g. `user active` + * @cancellable: (nullable): optional #GCancellable + * @callback: the function to run on completion + * @callback_data: the data to pass to @callback + * + * Marks all devices as unavailable for update. Update is only available if there is no other + * inhibit imposed by other applications or by the system (e.g. low power state). + * + * The same application can inhibit the system multiple times. + * + * Since: 1.8.11 + **/ +void +fwupd_client_inhibit_async(FwupdClient *self, + const gchar *reason, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + FwupdClientPrivate *priv = GET_PRIVATE(self); + g_autoptr(GTask) task = NULL; + + g_return_if_fail(FWUPD_IS_CLIENT(self)); + g_return_if_fail(reason != NULL); + g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); + g_return_if_fail(priv->proxy != NULL); + + /* call into daemon */ + task = g_task_new(self, cancellable, callback, callback_data); + g_dbus_proxy_call(priv->proxy, + "Inhibit", + g_variant_new("(s)", reason), + G_DBUS_CALL_FLAGS_NONE, + FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, + cancellable, + fwupd_client_inhibit_cb, + g_steal_pointer(&task)); +} + +/** + * fwupd_client_inhibit_finish: + * @self: a #FwupdClient + * @res: (not nullable): the asynchronous result + * @error: (nullable): optional return location for an error + * + * Gets the result of [method@FwupdClient.inhibit_async]. + * + * Returns: (transfer full): a string to use for [method@FwupdClient.uninhibit_async], + * or %NULL for failure + * + * Since: 1.8.11 + **/ +gchar * +fwupd_client_inhibit_finish(FwupdClient *self, GAsyncResult *res, GError **error) +{ + g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); + g_return_val_if_fail(g_task_is_valid(res, self), NULL); + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + return g_task_propagate_pointer(G_TASK(res), error); +} + +static void +fwupd_client_uninhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK(user_data); + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) val = NULL; + + val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (val == NULL) { + fwupd_client_fixup_dbus_error(error); + g_task_return_error(task, g_steal_pointer(&error)); + return; + } + + /* success */ + g_task_return_boolean(task, TRUE); +} + +/** + * fwupd_client_uninhibit_async: + * @self: a #FwupdClient + * @uninhibit_id: (not nullable): the inhibit ID + * @cancellable: (nullable): optional #GCancellable + * @callback: the function to run on completion + * @callback_data: the data to pass to @callback + * + * Removes the inhibit token added by the application. + * + * Since: 1.8.11 + **/ +void +fwupd_client_uninhibit_async(FwupdClient *self, + const gchar *inhibit_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + FwupdClientPrivate *priv = GET_PRIVATE(self); + g_autoptr(GTask) task = NULL; + + g_return_if_fail(FWUPD_IS_CLIENT(self)); + g_return_if_fail(inhibit_id != NULL); + g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); + g_return_if_fail(priv->proxy != NULL); + + /* call into daemon */ + task = g_task_new(self, cancellable, callback, callback_data); + g_dbus_proxy_call(priv->proxy, + "Uninhibit", + g_variant_new("(s)", inhibit_id), + G_DBUS_CALL_FLAGS_NONE, + FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, + cancellable, + fwupd_client_uninhibit_cb, + g_steal_pointer(&task)); +} + +/** + * fwupd_client_uninhibit_finish: + * @self: a #FwupdClient + * @res: (not nullable): the asynchronous result + * @error: (nullable): optional return location for an error + * + * Gets the result of [method@FwupdClient.uninhibit_async]. + * + * Returns: %TRUE for success + * + * Since: 1.8.11 + **/ +gboolean +fwupd_client_uninhibit_finish(FwupdClient *self, GAsyncResult *res, GError **error) +{ + g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); + g_return_val_if_fail(g_task_is_valid(res, self), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + return g_task_propagate_boolean(G_TASK(res), error); +} + /** * fwupd_client_add_hint: * @self: a #FwupdClient diff --git a/libfwupd/fwupd-client.h b/libfwupd/fwupd-client.h index d6d9d9e84..ed7d6a3a9 100644 --- a/libfwupd/fwupd-client.h +++ b/libfwupd/fwupd-client.h @@ -376,6 +376,26 @@ GHashTable * fwupd_client_get_report_metadata_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT; +void +fwupd_client_inhibit_async(FwupdClient *self, + const gchar *reason, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data); +gchar * +fwupd_client_inhibit_finish(FwupdClient *self, + GAsyncResult *res, + GError **error) G_GNUC_WARN_UNUSED_RESULT; +void +fwupd_client_uninhibit_async(FwupdClient *self, + const gchar *inhibit_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data); +gboolean +fwupd_client_uninhibit_finish(FwupdClient *self, + GAsyncResult *res, + GError **error) G_GNUC_WARN_UNUSED_RESULT; FwupdStatus fwupd_client_get_status(FwupdClient *self); diff --git a/libfwupd/fwupd-enums.c b/libfwupd/fwupd-enums.c index f81ff0b8d..96839b960 100644 --- a/libfwupd/fwupd-enums.c +++ b/libfwupd/fwupd-enums.c @@ -362,6 +362,8 @@ fwupd_device_problem_to_string(FwupdDeviceProblem device_problem) return "is-emulated"; if (device_problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE) return "missing-license"; + if (device_problem == FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT) + return "system-inhibit"; if (device_problem == FWUPD_DEVICE_PROBLEM_UNKNOWN) return "unknown"; return NULL; @@ -398,6 +400,8 @@ fwupd_device_problem_from_string(const gchar *device_problem) return FWUPD_DEVICE_PROBLEM_IS_EMULATED; if (g_strcmp0(device_problem, "missing-license") == 0) return FWUPD_DEVICE_PROBLEM_MISSING_LICENSE; + if (g_strcmp0(device_problem, "system-inhibit") == 0) + return FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT; return FWUPD_DEVICE_PROBLEM_UNKNOWN; } diff --git a/libfwupd/fwupd-enums.h b/libfwupd/fwupd-enums.h index 4ea6524be..f811984e9 100644 --- a/libfwupd/fwupd-enums.h +++ b/libfwupd/fwupd-enums.h @@ -615,6 +615,14 @@ typedef guint64 FwupdDeviceFlags; * Since 1.8.6 */ #define FWUPD_DEVICE_PROBLEM_MISSING_LICENSE (1u << 7) +/** + * FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT: + * + * The device cannot be updated due to a system-wide inhibit. + * + * Since 1.8.10 + */ +#define FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT (1u << 8) /** * FWUPD_DEVICE_PROBLEM_UNKNOWN: * diff --git a/libfwupd/fwupd-self-test.c b/libfwupd/fwupd-self-test.c index 872f0342c..dc753f990 100644 --- a/libfwupd/fwupd-self-test.c +++ b/libfwupd/fwupd-self-test.c @@ -164,7 +164,7 @@ fwupd_enums_func(void) g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_device_flag_from_string(tmp), ==, i); } - for (guint64 i = 1; i <= FWUPD_DEVICE_PROBLEM_IS_EMULATED; i *= 2) { + for (guint64 i = 1; i <= FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT; i *= 2) { const gchar *tmp = fwupd_device_problem_to_string(i); if (tmp == NULL) g_warning("missing device problem 0x%x", (guint)i); diff --git a/libfwupd/fwupd.map b/libfwupd/fwupd.map index 3ae22323c..8df8c0392 100644 --- a/libfwupd/fwupd.map +++ b/libfwupd/fwupd.map @@ -901,3 +901,14 @@ LIBFWUPD_1.8.8 { fwupd_report_to_variant; local: *; } LIBFWUPD_1.8.7; + +LIBFWUPD_1.8.11 { + global: + fwupd_client_inhibit; + fwupd_client_inhibit_async; + fwupd_client_inhibit_finish; + fwupd_client_uninhibit; + fwupd_client_uninhibit_async; + fwupd_client_uninhibit_finish; + local: *; +} LIBFWUPD_1.8.8; diff --git a/libfwupdplugin/fu-context.h b/libfwupdplugin/fu-context.h index acdcbb376..a0fafb91c 100644 --- a/libfwupdplugin/fu-context.h +++ b/libfwupdplugin/fu-context.h @@ -54,6 +54,15 @@ typedef void (*FuContextLookupIter)(FuContext *self, **/ #define FU_CONTEXT_FLAG_SAVE_EVENTS (1u << 0) +/** + * FU_CONTEXT_FLAG_SYSTEM_INHIBIT: + * + * All devices are not updatable due to a system-wide inhibit. + * + * Since: 1.8.10 + **/ +#define FU_CONTEXT_FLAG_SYSTEM_INHIBIT (1u << 1) + /** * FuContextFlags: * diff --git a/src/fu-daemon.c b/src/fu-daemon.c index 4ff7b849b..19423decc 100644 --- a/src/fu-daemon.c +++ b/src/fu-daemon.c @@ -51,6 +51,7 @@ struct _FuDaemon { gboolean update_in_progress; gboolean pending_stop; FuDaemonMachineKind machine_kind; + GPtrArray *system_inhibits; }; G_DEFINE_TYPE(FuDaemon, fu_daemon, G_TYPE_OBJECT) @@ -1015,6 +1016,51 @@ fu_daemon_schedule_process_quit(FuDaemon *self) self->process_quit_id = g_idle_add(fu_daemon_schedule_process_quit_cb, self); } +typedef struct { + gchar *id; + gchar *sender; + guint watcher_id; +} FuDaemonSystemInhibit; + +static void +fu_daemon_system_inhibit_free(FuDaemonSystemInhibit *inhibit) +{ + g_bus_unwatch_name(inhibit->watcher_id); + g_free(inhibit->id); + g_free(inhibit->sender); + g_free(inhibit); +} + +static void +fu_daemon_ensure_system_inhibit(FuDaemon *self) +{ + FuContext *ctx = fu_engine_get_context(self->engine); + if (self->system_inhibits->len > 0) { + fu_context_add_flag(ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT); + return; + } + fu_context_remove_flag(ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT); +} + +static void +fu_daemon_inhibit_name_vanished_cb(GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + FuDaemon *self = FU_DAEMON(user_data); + for (guint i = 0; i < self->system_inhibits->len; i++) { + FuDaemonSystemInhibit *inhibit = g_ptr_array_index(self->system_inhibits, i); + if (g_strcmp0(inhibit->sender, name) == 0) { + g_debug("removing %s as %s vanished without calling Uninhibit", + inhibit->id, + name); + g_ptr_array_remove_index(self->system_inhibits, i); + fu_daemon_ensure_system_inhibit(self); + break; + } + } +} + static void fu_daemon_daemon_method_call(GDBusConnection *connection, const gchar *sender, @@ -1626,6 +1672,59 @@ fu_daemon_daemon_method_call(GDBusConnection *connection, g_dbus_method_invocation_return_value(invocation, NULL); return; } + if (g_strcmp0(method_name, "Inhibit") == 0) { + FuDaemonSystemInhibit *inhibit; + const gchar *reason = NULL; + + g_variant_get(parameters, "(&s)", &reason); + g_debug("Called %s(%s)", method_name, reason); + + /* watch */ + inhibit = g_new0(FuDaemonSystemInhibit, 1); + inhibit->sender = g_strdup(sender); + inhibit->id = g_strdup_printf("dbus-%i", g_random_int_range(1, G_MAXINT - 1)); + inhibit->watcher_id = + g_bus_watch_name_on_connection(self->connection, + sender, + G_BUS_NAME_WATCHER_FLAGS_NONE, + NULL, + fu_daemon_inhibit_name_vanished_cb, + self, + NULL); + g_ptr_array_add(self->system_inhibits, inhibit); + fu_daemon_ensure_system_inhibit(self); + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(s)", inhibit->id)); + return; + } + if (g_strcmp0(method_name, "Uninhibit") == 0) { + const gchar *inhibit_id = NULL; + gboolean found = FALSE; + + g_variant_get(parameters, "(&s)", &inhibit_id); + g_debug("Called %s(%s)", method_name, inhibit_id); + + /* find by id, then uninhibit device */ + for (guint i = 0; i < self->system_inhibits->len; i++) { + FuDaemonSystemInhibit *inhibit = + g_ptr_array_index(self->system_inhibits, i); + if (g_strcmp0(inhibit->id, inhibit_id) == 0) { + g_ptr_array_remove_index(self->system_inhibits, i); + fu_daemon_ensure_system_inhibit(self); + found = TRUE; + break; + } + } + if (!found) { + g_dbus_method_invocation_return_error_literal(invocation, + FWUPD_ERROR, + FWUPD_ERROR_NOT_FOUND, + "Cannot find inhibit ID"); + return; + } + g_dbus_method_invocation_return_value(invocation, NULL); + return; + } if (g_strcmp0(method_name, "Install") == 0) { #ifdef HAVE_GIO_UNIX @@ -2175,6 +2274,8 @@ fu_daemon_init(FuDaemon *self) g_free, (GDestroyNotify)fu_daemon_sender_item_free); self->loop = g_main_loop_new(NULL, FALSE); + self->system_inhibits = + g_ptr_array_new_with_free_func((GDestroyNotify)fu_daemon_system_inhibit_free); } static void @@ -2182,6 +2283,7 @@ fu_daemon_finalize(GObject *obj) { FuDaemon *self = FU_DAEMON(obj); + g_ptr_array_unref(self->system_inhibits); g_hash_table_unref(self->sender_items); if (self->process_quit_id != 0) g_source_remove(self->process_quit_id); diff --git a/src/fu-engine.c b/src/fu-engine.c index 9e72616d9..fb7aeb575 100644 --- a/src/fu-engine.c +++ b/src/fu-engine.c @@ -404,6 +404,16 @@ fu_engine_ensure_device_lid_inhibit(FuEngine *self, FuDevice *device) fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED); } +static void +fu_engine_ensure_device_system_inhibit(FuEngine *self, FuDevice *device) +{ + if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT)) { + fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT); + return; + } + fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT); +} + static gboolean fu_engine_acquiesce_timeout_cb(gpointer user_data) { @@ -442,6 +452,7 @@ fu_engine_device_added_cb(FuDeviceList *device_list, FuDevice *device, FuEngine fu_engine_watch_device(self, device); fu_engine_ensure_device_battery_inhibit(self, device); fu_engine_ensure_device_lid_inhibit(self, device); + fu_engine_ensure_device_system_inhibit(self, device); fu_engine_acquiesce_reset(self); g_signal_emit(self, signals[SIGNAL_DEVICE_ADDED], 0, device); } @@ -8331,6 +8342,7 @@ fu_engine_context_battery_changed_cb(FuContext *ctx, GParamSpec *pspec, FuEngine FuDevice *device = g_ptr_array_index(devices, i); fu_engine_ensure_device_battery_inhibit(self, device); fu_engine_ensure_device_lid_inhibit(self, device); + fu_engine_ensure_device_system_inhibit(self, device); } } @@ -8407,6 +8419,10 @@ fu_engine_init(FuEngine *self) "notify::battery-threshold", G_CALLBACK(fu_engine_context_battery_changed_cb), self); + g_signal_connect(FU_CONTEXT(self->ctx), + "notify::flags", + G_CALLBACK(fu_engine_context_battery_changed_cb), + self); g_signal_connect(FU_CONFIG(self->config), "changed", diff --git a/src/fu-util-common.c b/src/fu-util-common.c index a591c67ab..125cdfad5 100644 --- a/src/fu-util-common.c +++ b/src/fu-util-common.c @@ -1402,6 +1402,10 @@ fu_util_device_problem_to_string(FwupdClient *client, FwupdDevice *dev, FwupdDev /* TRANSLATORS: The device cannot be updated due to missing vendor's license." */ return g_strdup(_("Device requires a software license to update")); } + if (problem == FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT) { + /* TRANSLATORS: an application is preventing system updates */ + return g_strdup(_("All devices are prevented from update by system inhibit")); + } return NULL; } diff --git a/src/fu-util.c b/src/fu-util.c index bd95fdf95..da91a1b4d 100644 --- a/src/fu-util.c +++ b/src/fu-util.c @@ -51,6 +51,7 @@ typedef enum { struct FuUtilPrivate { GCancellable *cancellable; GMainContext *main_ctx; + GMainLoop *loop; GOptionContext *context; FwupdInstallFlags flags; FwupdClientDownloadFlags download_flags; @@ -977,6 +978,48 @@ fu_util_device_test_filename(FuUtilPrivate *priv, return TRUE; } +static gboolean +fu_util_inhibit(FuUtilPrivate *priv, gchar **values, GError **error) +{ + const gchar *reason = "not set"; + g_autofree gchar *inhibit_id = NULL; + g_autoptr(GString) str = g_string_new(NULL); + + if (g_strv_length(values) > 0) + reason = values[1]; + + /* inhibit then wait */ + inhibit_id = fwupd_client_inhibit(priv->client, reason, priv->cancellable, error); + if (inhibit_id == NULL) + return FALSE; + + /* TRANSLATORS: the inhibit ID is a short string like dbus-123456 */ + g_string_append_printf(str, _("Inhibit ID is %s."), inhibit_id); + g_string_append(str, "\n"); + /* TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the program */ + g_string_append(str, _("Use CTRL^C to cancel.")); + /* TRANSLATORS: this CLI tool is now preventing system updates */ + fu_util_warning_box(_("System Update Inhibited"), str->str, 80); + g_main_loop_run(priv->loop); + return TRUE; +} + +static gboolean +fu_util_uninhibit(FuUtilPrivate *priv, gchar **values, GError **error) +{ + /* one argument required */ + if (g_strv_length(values) != 1) { + g_set_error_literal(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_ARGS, + "Invalid arguments, expected INHIBIT-ID"); + return FALSE; + } + + /* just uninhibit with the token */ + return fwupd_client_uninhibit(priv->client, values[0], priv->cancellable, error); +} + static gboolean fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error) { @@ -3612,6 +3655,7 @@ fu_util_private_free(FuUtilPrivate *priv) if (priv->current_device != NULL) g_object_unref(priv->current_device); g_ptr_array_unref(priv->post_requests); + g_main_loop_unref(priv->loop); g_main_context_unref(priv->main_ctx); g_object_unref(priv->cancellable); g_object_unref(priv->progressbar); @@ -3987,6 +4031,17 @@ fu_util_setup_interactive(FuUtilPrivate *priv, GError **error) return fu_util_setup_interactive_console(error); } +static void +fu_util_cancelled_cb(GCancellable *cancellable, gpointer user_data) +{ + FuUtilPrivate *priv = (FuUtilPrivate *)user_data; + if (!g_main_loop_is_running(priv->loop)) + return; + /* TRANSLATORS: this is from ctrl+c */ + g_print("%s\n", _("Cancelled")); + g_main_loop_quit(priv->loop); +} + int main(int argc, char *argv[]) { @@ -4220,6 +4275,7 @@ main(int argc, char *argv[]) /* create helper object */ priv->main_ctx = g_main_context_new(); + priv->loop = g_main_loop_new(priv->main_ctx, FALSE); priv->progressbar = fu_progressbar_new(); priv->post_requests = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); fu_progressbar_set_main_context(priv->progressbar, priv->main_ctx); @@ -4456,6 +4512,20 @@ main(int argc, char *argv[]) /* TRANSLATORS: command description */ _("Test a device using a JSON manifest"), fu_util_device_test); + fu_util_cmd_array_add(cmd_array, + "inhibit", + /* TRANSLATORS: command argument: uppercase, spaces->dashes */ + _("[REASON]"), + /* TRANSLATORS: command description */ + _("Inhibit the system to prevent upgrades"), + fu_util_inhibit); + fu_util_cmd_array_add(cmd_array, + "uninhibit", + /* TRANSLATORS: command argument: uppercase, spaces->dashes */ + _("INHIBIT-ID"), + /* TRANSLATORS: command description */ + _("Uninhibit the system to allow upgrades"), + fu_util_uninhibit); fu_util_cmd_array_add( cmd_array, "get-bios-settings,get-bios-setting", @@ -4475,6 +4545,10 @@ main(int argc, char *argv[]) /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new(); fu_util_setup_signal_handlers(priv); + g_signal_connect(G_CANCELLABLE(priv->cancellable), + "cancelled", + G_CALLBACK(fu_util_cancelled_cb), + priv); /* sort by command name */ fu_util_cmd_array_sort(cmd_array); diff --git a/src/org.freedesktop.fwupd.xml b/src/org.freedesktop.fwupd.xml index 7e58cceee..f9008260e 100644 --- a/src/org.freedesktop.fwupd.xml +++ b/src/org.freedesktop.fwupd.xml @@ -900,6 +900,52 @@ + + + + + + Marks the system as unavailable for update. + + + + + + + + A reason, e.g. device is being used to capture content. + + + + + + + + The token that was used for inhibiting. + + + + + + + + + + + Removes the inhibit token added by the application, but only if there is no other + inhibit imposed by other applications or by the system (e.g. low power state). + + + + + + + The token to use for uninhibiting. + + + + +