]> git.proxmox.com Git - fwupd.git/commitdiff
Allow desktop software to inhibit the system to prevent all updates
authorRichard Hughes <richard@hughsie.com>
Sat, 21 Jan 2023 13:37:49 +0000 (13:37 +0000)
committerMario Limonciello <mario.limonciello@amd.com>
Thu, 23 Feb 2023 19:04:10 +0000 (13:04 -0600)
On edge hardware a process may want to disable firmware updates as it might be
a bad time to allow an upgrade.

16 files changed:
data/bash-completion/fwupdmgr
data/fish-completion/fwupdmgr.fish
libfwupd/fwupd-client-sync.c
libfwupd/fwupd-client-sync.h
libfwupd/fwupd-client.c
libfwupd/fwupd-client.h
libfwupd/fwupd-enums.c
libfwupd/fwupd-enums.h
libfwupd/fwupd-self-test.c
libfwupd/fwupd.map
libfwupdplugin/fu-context.h
src/fu-daemon.c
src/fu-engine.c
src/fu-util-common.c
src/fu-util.c
src/org.freedesktop.fwupd.xml

index 95646cf3787662cd02b4623cc1ea7f54ed81ab31..8fb864fd88060f57cefa9e24552f9ce95c1a8e46 100644 (file)
@@ -20,6 +20,8 @@ _fwupdmgr_cmd_list=(
        'get-updates'
        'get-upgrades'
        'get-plugins'
+       'inhibit'
+       'uninhibit'
        'install'
        'local-install'
        'modify-config'
index e2746ac15b05c97d39466e16bcdb3591fac0ae7c..aab972279860ce9ac207ea0399313f6f238f2f8e 100644 (file)
@@ -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
index a1505a47e73200342ed388a4d19793110fa14538..294786efeb6e9ad6106490e000b52dc0f30eb39e 100644 (file)
@@ -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)
 {
index bc38cf1e6b690a45962cbd0b8033361628d2ce35..bbac721fa97cef16245a860da97a6287a26159e2 100644 (file)
@@ -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,
index 2976a908b1fbc4188e6c355a7bd099d1f19df26f..77753b2d74283b5c7492023f8325e872f87d7a5e 100644 (file)
@@ -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
index d6d9d9e84d5f54950df6cb9de01cbd65eba44df0..ed7d6a3a9ff334ef0391ec648110c58cf95a7d0f 100644 (file)
@@ -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);
index f81ff0b8d736662de75114b52e2592532f4e8469..96839b9600163538cd742c5aa4d2b3685642ac44 100644 (file)
@@ -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;
 }
 
index 4ea6524be7fe35fdf16c09c20f89ec3784b00646..f811984e9db39d5c126604bdefadbd059cbb8391 100644 (file)
@@ -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:
  *
index 872f0342c67f7b14b8df0b930a1494cb9f7011c4..dc753f990708e5fcb8628db8a06f851d52b53202 100644 (file)
@@ -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);
index 3ae22323c324792bafaea87fc49092bdc8a3950c..8df8c03928b1f854b1a82699798808e1d98f82e4 100644 (file)
@@ -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;
index acdcbb3768eac878687d23ef60f608af639b0d61..a0fafb91c3f3d676ffe66e23a29d0b5ab9f2e6cb 100644 (file)
@@ -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:
  *
index 4ff7b849b2ab87a39fc0566c7620ebf36b9fa2b7..19423decc3b002ba5b5b1d2499aaed8d2cd26bb5 100644 (file)
@@ -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);
index 9e72616d9e67e1f34a406726a00786dd374a0b34..fb7aeb575d684e1434ee12131982cc45128fa528 100644 (file)
@@ -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",
index a591c67ab26492ccd45eaf1a71e7575585f7aca7..125cdfad5b643ab7b0b8f4a6bb2491401ef38ae4 100644 (file)
@@ -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;
 }
 
index bd95fdf9502a93cce4111845032b07cc92ec0deb..da91a1b4dfcbb9d1e57082dc97ea481a8c05c83a 100644 (file)
@@ -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);
index 7e58cceee75eb23093a20f7c0cb4528947fad9e3..f9008260e166cbd8e6c5c6faac6dc1916300240a 100644 (file)
       </arg>
     </method>
 
+    <!--***********************************************************-->
+    <method name='Inhibit'>
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Marks the system as unavailable for update.
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+      <arg type='s' name='reason' direction='in'>
+        <doc:doc>
+          <doc:summary>
+            <doc:para>
+              A reason, e.g. <doc:tt>device is being used to capture content</doc:tt>.
+            </doc:para>
+          </doc:summary>
+        </doc:doc>
+      </arg>
+      <arg type='s' name='inhibit_id' direction='out'>
+        <doc:doc>
+          <doc:summary>
+            <doc:para>The token that was used for inhibiting.</doc:para>
+          </doc:summary>
+        </doc:doc>
+      </arg>
+    </method>
+
+    <!--***********************************************************-->
+    <method name='Uninhibit'>
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            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).
+          </doc:para>
+        </doc:description>
+      </doc:doc>
+      <arg type='s' name='inhibit_id' direction='in'>
+        <doc:doc>
+          <doc:summary>
+            <doc:para>The token to use for uninhibiting.</doc:para>
+          </doc:summary>
+        </doc:doc>
+      </arg>
+    </method>
+
     <!--***********************************************************-->
     <method name='Quit'>
       <doc:doc>