blob: e3cb54fbbf3758dc67c658416b994809ab2228e0 [file] [log] [blame]
Richard Hughes02c90d82018-08-09 12:13:03 +01001/*
Richard Hughesb5976832018-05-18 10:02:09 +01002 * Copyright (C) 2015-2018 Richard Hughes <richard@hughsie.com>
3 *
Mario Limonciello51308e62018-05-28 20:05:46 -05004 * SPDX-License-Identifier: LGPL-2.1+
Richard Hughesb5976832018-05-18 10:02:09 +01005 */
6
Richard Hughesb08e7bc2018-09-11 10:51:13 +01007#define G_LOG_DOMAIN "FuMain"
8
Richard Hughesb5976832018-05-18 10:02:09 +01009#include "config.h"
10
11#include <fwupd.h>
Mario Limonciello3f243a92019-01-21 22:05:23 -060012#include <glib/gstdio.h>
Richard Hughesb5976832018-05-18 10:02:09 +010013#include <glib/gi18n.h>
14#include <glib-unix.h>
15#include <locale.h>
16#include <stdlib.h>
17#include <unistd.h>
Richard Hughes3d178be2018-08-30 11:14:24 +010018#include <libsoup/soup.h>
Richard Hughesb5976832018-05-18 10:02:09 +010019
Mario Limonciello7a3df4b2019-01-31 10:27:22 -060020#include "fu-device-private.h"
Richard Hughes98ca9932018-05-18 10:24:07 +010021#include "fu-engine.h"
Mario Limonciello96a0dd52019-02-25 13:50:03 -060022#include "fu-history.h"
Richard Hughes8c71a3f2018-05-22 19:19:52 +010023#include "fu-plugin-private.h"
Richard Hughesb5976832018-05-18 10:02:09 +010024#include "fu-progressbar.h"
25#include "fu-smbios.h"
26#include "fu-util-common.h"
Mario Limonciellofde47732018-09-11 12:20:58 -050027#include "fu-debug.h"
Mario Limonciello1e35e4c2019-01-28 11:13:02 -060028#include "fwupd-common-private.h"
Richard Hughesb5976832018-05-18 10:02:09 +010029
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050030#define SYSTEMD_SERVICE "org.freedesktop.systemd1"
31#define SYSTEMD_OBJECT_PATH "/org/freedesktop/systemd1"
32#define SYSTEMD_MANAGER_INTERFACE "org.freedesktop.systemd1.Manager"
33#define SYSTEMD_FWUPD_UNIT "fwupd.service"
34
Richard Hughesb5976832018-05-18 10:02:09 +010035/* custom return code */
36#define EXIT_NOTHING_TO_DO 2
37
Mario Limonciello3f243a92019-01-21 22:05:23 -060038typedef enum {
39 FU_UTIL_OPERATION_UNKNOWN,
40 FU_UTIL_OPERATION_UPDATE,
41 FU_UTIL_OPERATION_INSTALL,
42 FU_UTIL_OPERATION_LAST
43} FuUtilOperation;
44
Richard Hughesb5976832018-05-18 10:02:09 +010045typedef struct {
46 GCancellable *cancellable;
47 GMainLoop *loop;
48 GOptionContext *context;
49 GPtrArray *cmd_array;
Richard Hughes98ca9932018-05-18 10:24:07 +010050 FuEngine *engine;
Richard Hughesb5976832018-05-18 10:02:09 +010051 FuProgressbar *progressbar;
Mario Limonciello3f243a92019-01-21 22:05:23 -060052 gboolean no_reboot_check;
Mario Limonciello53ce25d2019-02-01 16:09:03 +000053 gboolean prepare_blob;
54 gboolean cleanup_blob;
Richard Hughes460226a2018-05-21 20:56:21 +010055 FwupdInstallFlags flags;
Mario Limoncielloba9e5b92018-05-21 16:02:32 -050056 gboolean show_all_devices;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -050057 /* only valid in update and downgrade */
Mario Limonciello3f243a92019-01-21 22:05:23 -060058 FuUtilOperation current_operation;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -050059 FwupdDevice *current_device;
Mario Limonciello32241f42019-01-24 10:12:41 -060060 gchar *current_message;
Mario Limonciello3f243a92019-01-21 22:05:23 -060061 FwupdDeviceFlags completion_flags;
Richard Hughesb5976832018-05-18 10:02:09 +010062} FuUtilPrivate;
63
64typedef gboolean (*FuUtilPrivateCb) (FuUtilPrivate *util,
65 gchar **values,
66 GError **error);
67
68typedef struct {
69 gchar *name;
70 gchar *arguments;
71 gchar *description;
72 FuUtilPrivateCb callback;
73} FuUtilItem;
74
75static void
76fu_util_item_free (FuUtilItem *item)
77{
78 g_free (item->name);
79 g_free (item->arguments);
80 g_free (item->description);
81 g_free (item);
82}
83
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050084static gboolean
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -060085fu_util_stop_daemon (GError **error)
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050086{
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050087 g_autoptr(GDBusConnection) connection = NULL;
88 g_autoptr(GDBusProxy) proxy = NULL;
89 g_autoptr(GVariant) val = NULL;
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050090
91 /* try to stop any already running daemon */
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -060092 connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
Mario Limonciello8101bfc2019-02-07 13:47:44 +000093 if (connection == NULL) {
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -060094 g_prefix_error (error, "failed to get bus: ");
95 return FALSE;
Mario Limonciello8101bfc2019-02-07 13:47:44 +000096 }
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050097 proxy = g_dbus_proxy_new_sync (connection,
98 G_DBUS_PROXY_FLAGS_NONE,
99 NULL,
100 SYSTEMD_SERVICE,
101 SYSTEMD_OBJECT_PATH,
102 SYSTEMD_MANAGER_INTERFACE,
103 NULL,
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -0600104 error);
Mario Limonciello67b82af2018-11-01 13:37:00 -0500105 if (proxy == NULL) {
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -0600106 g_prefix_error (error, "failed to find %s: ", SYSTEMD_SERVICE);
107 return FALSE;
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500108 }
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -0600109 val = g_dbus_proxy_call_sync (proxy,
110 "GetUnit",
111 g_variant_new ("(s)",
112 SYSTEMD_FWUPD_UNIT),
113 G_DBUS_CALL_FLAGS_NONE,
114 -1,
115 NULL,
116 error);
117 if (val == NULL) {
118 g_prefix_error (error, "failed to find %s: ", SYSTEMD_FWUPD_UNIT);
119 return FALSE;
120 }
121 g_variant_unref (val);
122 val = g_dbus_proxy_call_sync (proxy,
123 "StopUnit",
124 g_variant_new ("(ss)",
125 SYSTEMD_FWUPD_UNIT,
126 "replace"),
127 G_DBUS_CALL_FLAGS_NONE,
128 -1,
129 NULL,
130 error);
131 return val != NULL;
132}
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500133
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -0600134static gboolean
135fu_util_start_engine (FuUtilPrivate *priv, GError **error)
136{
137 g_autoptr(GError) error_local = NULL;
138
139 if (!fu_util_stop_daemon (&error_local))
140 g_debug ("Failed top stop daemon: %s", error_local->message);
Richard Hughesf425d292019-01-18 17:57:39 +0000141 if (!fu_engine_load (priv->engine, error))
142 return FALSE;
143 if (fu_engine_get_tainted (priv->engine)) {
144 g_printerr ("WARNING: This tool has loaded 3rd party code and "
145 "is no longer supported by the upstream developers!\n");
146 }
147 return TRUE;
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500148}
149
Richard Hughesb5976832018-05-18 10:02:09 +0100150static gint
151fu_sort_command_name_cb (FuUtilItem **item1, FuUtilItem **item2)
152{
153 return g_strcmp0 ((*item1)->name, (*item2)->name);
154}
155
156static void
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500157fu_util_maybe_prefix_sandbox_error (const gchar *value, GError **error)
158{
159 g_autofree gchar *path = g_path_get_dirname (value);
160 if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
161 g_prefix_error (error,
162 "Unable to access %s. You may need to copy %s to %s: ",
163 path, value, g_getenv ("HOME"));
164 }
165}
166
167static void
Richard Hughesb5976832018-05-18 10:02:09 +0100168fu_util_add (GPtrArray *array,
169 const gchar *name,
170 const gchar *arguments,
171 const gchar *description,
172 FuUtilPrivateCb callback)
173{
174 g_auto(GStrv) names = NULL;
175
176 g_return_if_fail (name != NULL);
177 g_return_if_fail (description != NULL);
178 g_return_if_fail (callback != NULL);
179
180 /* add each one */
181 names = g_strsplit (name, ",", -1);
182 for (guint i = 0; names[i] != NULL; i++) {
183 FuUtilItem *item = g_new0 (FuUtilItem, 1);
184 item->name = g_strdup (names[i]);
185 if (i == 0) {
186 item->description = g_strdup (description);
187 } else {
188 /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */
189 item->description = g_strdup_printf (_("Alias to %s"),
190 names[0]);
191 }
192 item->arguments = g_strdup (arguments);
193 item->callback = callback;
194 g_ptr_array_add (array, item);
195 }
196}
197
198static gchar *
199fu_util_get_descriptions (GPtrArray *array)
200{
201 gsize len;
202 const gsize max_len = 35;
203 GString *string;
204
205 /* print each command */
206 string = g_string_new ("");
207 for (guint i = 0; i < array->len; i++) {
208 FuUtilItem *item = g_ptr_array_index (array, i);
209 g_string_append (string, " ");
210 g_string_append (string, item->name);
211 len = strlen (item->name) + 2;
212 if (item->arguments != NULL) {
213 g_string_append (string, " ");
214 g_string_append (string, item->arguments);
215 len += strlen (item->arguments) + 1;
216 }
217 if (len < max_len) {
218 for (gsize j = len; j < max_len + 1; j++)
219 g_string_append_c (string, ' ');
220 g_string_append (string, item->description);
221 g_string_append_c (string, '\n');
222 } else {
223 g_string_append_c (string, '\n');
224 for (gsize j = 0; j < max_len + 1; j++)
225 g_string_append_c (string, ' ');
226 g_string_append (string, item->description);
227 g_string_append_c (string, '\n');
228 }
229 }
230
231 /* remove trailing newline */
232 if (string->len > 0)
233 g_string_set_size (string, string->len - 1);
234
235 return g_string_free (string, FALSE);
236}
237
238static gboolean
239fu_util_run (FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error)
240{
241 /* find command */
242 for (guint i = 0; i < priv->cmd_array->len; i++) {
243 FuUtilItem *item = g_ptr_array_index (priv->cmd_array, i);
244 if (g_strcmp0 (item->name, command) == 0)
245 return item->callback (priv, values, error);
246 }
247
248 /* not found */
249 g_set_error_literal (error,
250 FWUPD_ERROR,
251 FWUPD_ERROR_INVALID_ARGS,
252 /* TRANSLATORS: error message */
253 _("Command not found"));
254 return FALSE;
255}
256
257static void
258fu_util_cancelled_cb (GCancellable *cancellable, gpointer user_data)
259{
260 FuUtilPrivate *priv = (FuUtilPrivate *) user_data;
261 /* TRANSLATORS: this is when a device ctrl+c's a watch */
262 g_print ("%s\n", _("Cancelled"));
263 g_main_loop_quit (priv->loop);
264}
265
266static gboolean
267fu_util_smbios_dump (FuUtilPrivate *priv, gchar **values, GError **error)
268{
269 g_autofree gchar *tmp = NULL;
270 g_autoptr(FuSmbios) smbios = NULL;
271 if (g_strv_length (values) < 1) {
272 g_set_error_literal (error,
273 FWUPD_ERROR,
274 FWUPD_ERROR_INVALID_ARGS,
275 "Invalid arguments");
276 return FALSE;
277 }
278 smbios = fu_smbios_new ();
279 if (!fu_smbios_setup_from_file (smbios, values[0], error))
280 return FALSE;
281 tmp = fu_smbios_to_string (smbios);
282 g_print ("%s\n", tmp);
283 return TRUE;
284}
285
Richard Hughesb5976832018-05-18 10:02:09 +0100286static gboolean
287fu_util_sigint_cb (gpointer user_data)
288{
289 FuUtilPrivate *priv = (FuUtilPrivate *) user_data;
290 g_debug ("Handling SIGINT");
291 g_cancellable_cancel (priv->cancellable);
292 return FALSE;
293}
294
295static void
296fu_util_private_free (FuUtilPrivate *priv)
297{
298 if (priv->cmd_array != NULL)
299 g_ptr_array_unref (priv->cmd_array);
Mario Limonciellocc50e1a2018-08-14 17:45:24 -0500300 if (priv->current_device != NULL)
301 g_object_unref (priv->current_device);
Richard Hughes98ca9932018-05-18 10:24:07 +0100302 if (priv->engine != NULL)
303 g_object_unref (priv->engine);
Richard Hughesb5976832018-05-18 10:02:09 +0100304 if (priv->loop != NULL)
305 g_main_loop_unref (priv->loop);
306 if (priv->cancellable != NULL)
307 g_object_unref (priv->cancellable);
308 if (priv->progressbar != NULL)
309 g_object_unref (priv->progressbar);
310 if (priv->context != NULL)
311 g_option_context_free (priv->context);
Mario Limonciello32241f42019-01-24 10:12:41 -0600312 g_free (priv->current_message);
Richard Hughesb5976832018-05-18 10:02:09 +0100313 g_free (priv);
314}
315
316#pragma clang diagnostic push
317#pragma clang diagnostic ignored "-Wunused-function"
318G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free)
319#pragma clang diagnostic pop
320
Richard Hughes98ca9932018-05-18 10:24:07 +0100321
322static void
323fu_main_engine_device_added_cb (FuEngine *engine,
324 FuDevice *device,
325 FuUtilPrivate *priv)
326{
327 g_autofree gchar *tmp = fu_device_to_string (device);
328 g_debug ("ADDED:\n%s", tmp);
329}
330
331static void
332fu_main_engine_device_removed_cb (FuEngine *engine,
333 FuDevice *device,
334 FuUtilPrivate *priv)
335{
336 g_autofree gchar *tmp = fu_device_to_string (device);
337 g_debug ("REMOVED:\n%s", tmp);
338}
339
340static void
Richard Hughes98ca9932018-05-18 10:24:07 +0100341fu_main_engine_status_changed_cb (FuEngine *engine,
342 FwupdStatus status,
343 FuUtilPrivate *priv)
344{
345 fu_progressbar_update (priv->progressbar, status, 0);
346}
347
348static void
349fu_main_engine_percentage_changed_cb (FuEngine *engine,
350 guint percentage,
351 FuUtilPrivate *priv)
352{
353 fu_progressbar_update (priv->progressbar, FWUPD_STATUS_UNKNOWN, percentage);
354}
355
356static gboolean
357fu_util_watch (FuUtilPrivate *priv, gchar **values, GError **error)
358{
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500359 if (!fu_util_start_engine (priv, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100360 return FALSE;
361 g_main_loop_run (priv->loop);
362 return TRUE;
363}
364
Richard Hughes8c71a3f2018-05-22 19:19:52 +0100365static gint
366fu_util_plugin_name_sort_cb (FuPlugin **item1, FuPlugin **item2)
367{
368 return fu_plugin_name_compare (*item1, *item2);
369}
370
371static gboolean
372fu_util_get_plugins (FuUtilPrivate *priv, gchar **values, GError **error)
373{
374 GPtrArray *plugins;
375 guint cnt = 0;
376
377 /* load engine */
378 if (!fu_engine_load_plugins (priv->engine, error))
379 return FALSE;
380
381 /* print */
382 plugins = fu_engine_get_plugins (priv->engine);
383 g_ptr_array_sort (plugins, (GCompareFunc) fu_util_plugin_name_sort_cb);
384 for (guint i = 0; i < plugins->len; i++) {
385 FuPlugin *plugin = g_ptr_array_index (plugins, i);
386 if (!fu_plugin_get_enabled (plugin))
387 continue;
388 g_print ("%s\n", fu_plugin_get_name (plugin));
389 cnt++;
390 }
391 if (cnt == 0) {
392 /* TRANSLATORS: nothing found */
393 g_print ("%s\n", _("No plugins found"));
394 return TRUE;
395 }
396
397 return TRUE;
398}
399
Richard Hughes98ca9932018-05-18 10:24:07 +0100400static gboolean
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600401fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error)
402{
403 g_autoptr(GPtrArray) devices = NULL;
404
405 /* load engine */
406 if (!fu_util_start_engine (priv, error))
407 return FALSE;
408
409 /* get devices from daemon */
410 devices = fu_engine_get_devices (priv->engine, error);
411 if (devices == NULL)
412 return FALSE;
413 for (guint i = 0; i < devices->len; i++) {
414 FwupdDevice *dev = g_ptr_array_index (devices, i);
415 g_autoptr(GPtrArray) rels = NULL;
416 g_autoptr(GError) error_local = NULL;
417
418 /* not going to have results, so save a D-Bus round-trip */
419 if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED))
420 continue;
421
422 /* get the releases for this device and filter for validity */
423 rels = fu_engine_get_upgrades (priv->engine,
424 fwupd_device_get_id (dev),
425 &error_local);
426 if (rels == NULL) {
427 g_printerr ("%s\n", error_local->message);
428 continue;
429 }
430 g_print ("%s", fwupd_device_to_string (dev));
431 g_print (" Release information:\n");
432 /* print all releases */
433 for (guint j = 0; j < rels->len; j++) {
434 FwupdRelease *rel = g_ptr_array_index (rels, j);
435 g_print ("%s\n", fwupd_release_to_string (rel));
436 }
437 }
438
439 /* success */
440 return TRUE;
441}
442
443static gboolean
Mario Limonciello716ab272018-05-29 12:34:37 -0500444fu_util_get_details (FuUtilPrivate *priv, gchar **values, GError **error)
445{
446 g_autoptr(GPtrArray) array = NULL;
447 gint fd;
448
449 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500450 if (!fu_util_start_engine (priv, error))
Mario Limonciello716ab272018-05-29 12:34:37 -0500451 return FALSE;
452
453 /* check args */
454 if (g_strv_length (values) != 1) {
455 g_set_error_literal (error,
456 FWUPD_ERROR,
457 FWUPD_ERROR_INVALID_ARGS,
458 "Invalid arguments");
459 return FALSE;
460 }
461
462 /* open file */
463 fd = open (values[0], O_RDONLY);
464 if (fd < 0) {
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500465 fu_util_maybe_prefix_sandbox_error (values[0], error);
Mario Limonciello716ab272018-05-29 12:34:37 -0500466 g_set_error (error,
467 FWUPD_ERROR,
468 FWUPD_ERROR_INVALID_FILE,
469 "failed to open %s",
470 values[0]);
471 return FALSE;
472 }
473 array = fu_engine_get_details (priv->engine, fd, error);
474 close (fd);
475
476 if (array == NULL)
477 return FALSE;
478 for (guint i = 0; i < array->len; i++) {
479 FwupdDevice *dev = g_ptr_array_index (array, i);
480 g_autofree gchar *tmp = NULL;
481 tmp = fwupd_device_to_string (dev);
Mario Limoncielloece90a42018-07-25 17:35:55 -0500482 g_print ("%s\n", tmp);
Mario Limonciello716ab272018-05-29 12:34:37 -0500483 }
484 return TRUE;
485}
486
487static gboolean
Richard Hughes98ca9932018-05-18 10:24:07 +0100488fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error)
489{
490 g_autoptr(GPtrArray) devs = NULL;
491
492 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500493 if (!fu_util_start_engine (priv, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100494 return FALSE;
495
496 /* print */
497 devs = fu_engine_get_devices (priv->engine, error);
498 if (devs == NULL)
499 return FALSE;
500 if (devs->len == 0) {
501 /* TRANSLATORS: nothing attached */
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500502 g_print ("%s\n", _("No hardware detected with firmware update capability"));
Richard Hughes98ca9932018-05-18 10:24:07 +0100503 return TRUE;
504 }
505 for (guint i = 0; i < devs->len; i++) {
506 FwupdDevice *dev = g_ptr_array_index (devs, i);
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500507 if (priv->show_all_devices || fu_util_is_interesting_device (dev)) {
508 g_autofree gchar *tmp = fwupd_device_to_string (dev);
509 g_print ("%s\n", tmp);
510 }
Richard Hughes98ca9932018-05-18 10:24:07 +0100511 }
512
513 return TRUE;
514}
515
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500516static void
Richard Hughes0d1577e2018-05-29 13:59:06 +0100517fu_util_build_device_tree (FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FuDevice *dev)
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500518{
519 for (guint i = 0; i < devs->len; i++) {
Richard Hughes0d1577e2018-05-29 13:59:06 +0100520 FuDevice *dev_tmp = g_ptr_array_index (devs, i);
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500521 if (!priv->show_all_devices &&
522 !fu_util_is_interesting_device (FWUPD_DEVICE (dev_tmp)))
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500523 continue;
Richard Hughes0d1577e2018-05-29 13:59:06 +0100524 if (fu_device_get_parent (dev_tmp) == dev) {
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500525 GNode *child = g_node_append_data (root, dev_tmp);
526 fu_util_build_device_tree (priv, child, devs, dev_tmp);
527 }
528 }
529}
530
531static gboolean
532fu_util_get_topology (FuUtilPrivate *priv, gchar **values, GError **error)
533{
534 g_autoptr(GNode) root = g_node_new (NULL);
535 g_autoptr(GPtrArray) devs = NULL;
536
537 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500538 if (!fu_util_start_engine (priv, error))
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500539 return FALSE;
540
541 /* print */
542 devs = fu_engine_get_devices (priv->engine, error);
543 if (devs == NULL)
544 return FALSE;
545
546 /* print */
547 if (devs->len == 0) {
548 /* TRANSLATORS: nothing attached that can be upgraded */
549 g_print ("%s\n", _("No hardware detected with firmware update capability"));
550 return TRUE;
551 }
552 fu_util_build_device_tree (priv, root, devs, NULL);
553 g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
554 fu_util_print_device_tree, priv);
555
556 return TRUE;
557}
558
559
Richard Hughes98ca9932018-05-18 10:24:07 +0100560static FuDevice *
561fu_util_prompt_for_device (FuUtilPrivate *priv, GError **error)
562{
563 FuDevice *dev;
564 guint idx;
565 g_autoptr(GPtrArray) devices = NULL;
566
567 /* get devices from daemon */
568 devices = fu_engine_get_devices (priv->engine, error);
569 if (devices == NULL)
570 return NULL;
571
572 /* exactly one */
573 if (devices->len == 1) {
574 dev = g_ptr_array_index (devices, 0);
575 return g_object_ref (dev);
576 }
577
578 /* TRANSLATORS: get interactive prompt */
579 g_print ("%s\n", _("Choose a device:"));
580 /* TRANSLATORS: this is to abort the interactive prompt */
581 g_print ("0.\t%s\n", _("Cancel"));
582 for (guint i = 0; i < devices->len; i++) {
583 dev = g_ptr_array_index (devices, i);
584 g_print ("%u.\t%s (%s)\n",
585 i + 1,
586 fu_device_get_id (dev),
587 fu_device_get_name (dev));
588 }
589 idx = fu_util_prompt_for_number (devices->len);
590 if (idx == 0) {
591 g_set_error_literal (error,
592 FWUPD_ERROR,
593 FWUPD_ERROR_NOTHING_TO_DO,
594 "Request canceled");
595 return NULL;
596 }
597 dev = g_ptr_array_index (devices, idx - 1);
598 return g_object_ref (dev);
599}
600
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500601static void
Mario Limonciello3f243a92019-01-21 22:05:23 -0600602fu_util_update_device_changed_cb (FwupdClient *client,
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500603 FwupdDevice *device,
604 FuUtilPrivate *priv)
605{
606 g_autofree gchar *str = NULL;
607
608 /* same as last time, so ignore */
609 if (priv->current_device != NULL &&
610 fwupd_device_compare (priv->current_device, device) == 0)
611 return;
612
613 /* show message in progressbar */
Mario Limonciello3f243a92019-01-21 22:05:23 -0600614 if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) {
615 /* TRANSLATORS: %1 is a device name */
616 str = g_strdup_printf (_("Updating %s…"),
617 fwupd_device_get_name (device));
618 fu_progressbar_set_title (priv->progressbar, str);
619 } else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) {
620 /* TRANSLATORS: %1 is a device name */
621 str = g_strdup_printf (_("Installing on %s…"),
622 fwupd_device_get_name (device));
623 fu_progressbar_set_title (priv->progressbar, str);
624 } else {
625 g_warning ("no FuUtilOperation set");
626 }
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500627 g_set_object (&priv->current_device, device);
Mario Limonciello3f243a92019-01-21 22:05:23 -0600628
629 if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN))
630 priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN;
631 else if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT))
632 priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT;
Mario Limonciello32241f42019-01-24 10:12:41 -0600633
634 if (priv->current_message == NULL) {
635 const gchar *tmp = fwupd_device_get_update_message (priv->current_device);
636 if (tmp != NULL)
637 priv->current_message = g_strdup (tmp);
638 }
639}
640
641static void
642fu_util_display_current_message (FuUtilPrivate *priv)
643{
644 if (priv->current_message == NULL)
645 return;
646 g_print ("%s\n", priv->current_message);
647 g_clear_pointer (&priv->current_message, g_free);
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500648}
649
Richard Hughes98ca9932018-05-18 10:24:07 +0100650static gboolean
651fu_util_install_blob (FuUtilPrivate *priv, gchar **values, GError **error)
652{
653 g_autoptr(FuDevice) device = NULL;
654 g_autoptr(GBytes) blob_fw = NULL;
655
656 /* invalid args */
657 if (g_strv_length (values) == 0) {
658 g_set_error_literal (error,
659 FWUPD_ERROR,
660 FWUPD_ERROR_INVALID_ARGS,
661 "Invalid arguments");
662 return FALSE;
663 }
664
665 /* parse blob */
666 blob_fw = fu_common_get_contents_bytes (values[0], error);
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500667 if (blob_fw == NULL) {
668 fu_util_maybe_prefix_sandbox_error (values[0], error);
Richard Hughes98ca9932018-05-18 10:24:07 +0100669 return FALSE;
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500670 }
Richard Hughes98ca9932018-05-18 10:24:07 +0100671
672 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500673 if (!fu_util_start_engine (priv, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100674 return FALSE;
675
676 /* get device */
677 if (g_strv_length (values) >= 2) {
678 device = fu_engine_get_device (priv->engine, values[1], error);
679 if (device == NULL)
680 return FALSE;
681 } else {
682 device = fu_util_prompt_for_device (priv, error);
683 if (device == NULL)
684 return FALSE;
685 }
686
Mario Limonciello3f243a92019-01-21 22:05:23 -0600687 priv->current_operation = FU_UTIL_OPERATION_INSTALL;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500688 g_signal_connect (priv->engine, "device-changed",
Mario Limonciello3f243a92019-01-21 22:05:23 -0600689 G_CALLBACK (fu_util_update_device_changed_cb), priv);
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500690
Richard Hughes98ca9932018-05-18 10:24:07 +0100691 /* write bare firmware */
Mario Limonciello53ce25d2019-02-01 16:09:03 +0000692 if (priv->prepare_blob) {
693 g_autoptr(GPtrArray) devices = NULL;
694 devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
695 g_ptr_array_add (devices, g_object_ref (device));
696 if (!fu_engine_composite_prepare (priv->engine, devices, error)) {
697 g_prefix_error (error, "failed to prepare composite action: ");
698 return FALSE;
699 }
700 }
Richard Hughes84af6e72019-02-01 18:19:41 +0000701 if (!fu_engine_install_blob (priv->engine, device, blob_fw, priv->flags, error))
Mario Limonciello3f243a92019-01-21 22:05:23 -0600702 return FALSE;
Mario Limonciello53ce25d2019-02-01 16:09:03 +0000703 if (priv->cleanup_blob) {
704 g_autoptr(FuDevice) device_new = NULL;
705 g_autoptr(GError) error_local = NULL;
706
707 /* get the possibly new device from the old ID */
708 device_new = fu_engine_get_device (priv->engine,
709 fu_device_get_id (device),
710 &error_local);
711 if (device_new == NULL) {
712 g_debug ("failed to find new device: %s",
713 error_local->message);
714 } else {
Mario Limonciellobb3fa5e2019-02-07 21:13:02 -0600715 g_autoptr(GPtrArray) devices_new = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
Mario Limonciello53ce25d2019-02-01 16:09:03 +0000716 g_ptr_array_add (devices_new, g_steal_pointer (&device_new));
717 if (!fu_engine_composite_cleanup (priv->engine, devices_new, error)) {
718 g_prefix_error (error, "failed to cleanup composite action: ");
719 return FALSE;
720 }
721 }
722 }
Mario Limonciello3f243a92019-01-21 22:05:23 -0600723
Mario Limonciello32241f42019-01-24 10:12:41 -0600724 fu_util_display_current_message (priv);
725
Mario Limonciello3f243a92019-01-21 22:05:23 -0600726 /* success */
727 return fu_util_prompt_complete (priv->completion_flags, TRUE, error);
Richard Hughes98ca9932018-05-18 10:24:07 +0100728}
729
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100730static gint
731fu_util_install_task_sort_cb (gconstpointer a, gconstpointer b)
732{
733 FuInstallTask *task1 = *((FuInstallTask **) a);
734 FuInstallTask *task2 = *((FuInstallTask **) b);
735 return fu_install_task_compare (task1, task2);
736}
737
738static gboolean
Richard Hughes3d178be2018-08-30 11:14:24 +0100739fu_util_download_out_of_process (const gchar *uri, const gchar *fn, GError **error)
740{
741 const gchar *argv[][5] = { { "wget", uri, "-o", fn, NULL },
742 { "curl", uri, "--output", fn, NULL },
743 { NULL } };
744 for (guint i = 0; argv[i][0] != NULL; i++) {
745 g_autoptr(GError) error_local = NULL;
746 if (!fu_common_find_program_in_path (argv[i][0], &error_local)) {
747 g_debug ("%s", error_local->message);
748 continue;
749 }
Richard Hughesb768e4d2019-02-26 13:55:18 +0000750 return fu_common_spawn_sync (argv[i], NULL, NULL, 0, NULL, error);
Richard Hughes3d178be2018-08-30 11:14:24 +0100751 }
752 g_set_error_literal (error,
753 FWUPD_ERROR,
754 FWUPD_ERROR_NOT_FOUND,
755 "no supported out-of-process downloaders found");
756 return FALSE;
757}
758
759static gchar *
760fu_util_download_if_required (FuUtilPrivate *priv, const gchar *perhapsfn, GError **error)
761{
762 g_autofree gchar *filename = NULL;
763 g_autoptr(SoupURI) uri = NULL;
764
765 /* a local file */
766 uri = soup_uri_new (perhapsfn);
767 if (uri == NULL)
768 return g_strdup (perhapsfn);
769
770 /* download the firmware to a cachedir */
771 filename = fu_util_get_user_cache_path (perhapsfn);
772 if (!fu_common_mkdir_parent (filename, error))
773 return NULL;
774 if (!fu_util_download_out_of_process (perhapsfn, filename, error))
775 return NULL;
776 return g_steal_pointer (&filename);
777}
778
779static gboolean
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100780fu_util_install (FuUtilPrivate *priv, gchar **values, GError **error)
781{
Richard Hughes3d178be2018-08-30 11:14:24 +0100782 g_autofree gchar *filename = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100783 g_autoptr(GBytes) blob_cab = NULL;
Richard Hughes481aa2a2018-09-18 20:51:46 +0100784 g_autoptr(GPtrArray) components = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100785 g_autoptr(GPtrArray) devices_possible = NULL;
786 g_autoptr(GPtrArray) errors = NULL;
787 g_autoptr(GPtrArray) install_tasks = NULL;
Richard Hughes481aa2a2018-09-18 20:51:46 +0100788 g_autoptr(XbSilo) silo = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100789
Mario Limonciello8949e892018-05-25 08:03:06 -0500790 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500791 if (!fu_util_start_engine (priv, error))
Mario Limonciello8949e892018-05-25 08:03:06 -0500792 return FALSE;
793
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100794 /* handle both forms */
795 if (g_strv_length (values) == 1) {
796 devices_possible = fu_engine_get_devices (priv->engine, error);
797 if (devices_possible == NULL)
798 return FALSE;
799 } else if (g_strv_length (values) == 2) {
800 FuDevice *device = fu_engine_get_device (priv->engine,
801 values[1],
802 error);
803 if (device == NULL)
804 return FALSE;
805 devices_possible = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
806 g_ptr_array_add (devices_possible, device);
807 } else {
808 g_set_error_literal (error,
809 FWUPD_ERROR,
810 FWUPD_ERROR_INVALID_ARGS,
811 "Invalid arguments");
812 return FALSE;
813 }
814
Richard Hughes3d178be2018-08-30 11:14:24 +0100815 /* download if required */
816 filename = fu_util_download_if_required (priv, values[0], error);
817 if (filename == NULL)
818 return FALSE;
819
Richard Hughes481aa2a2018-09-18 20:51:46 +0100820 /* parse silo */
Richard Hughes3d178be2018-08-30 11:14:24 +0100821 blob_cab = fu_common_get_contents_bytes (filename, error);
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500822 if (blob_cab == NULL) {
Richard Hughes3d178be2018-08-30 11:14:24 +0100823 fu_util_maybe_prefix_sandbox_error (filename, error);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100824 return FALSE;
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500825 }
Richard Hughes481aa2a2018-09-18 20:51:46 +0100826 silo = fu_engine_get_silo_from_blob (priv->engine, blob_cab, error);
827 if (silo == NULL)
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100828 return FALSE;
Mario Limonciello51ddf182019-01-26 00:31:58 -0600829 components = xb_silo_query (silo, "components/component", 0, error);
Richard Hughes481aa2a2018-09-18 20:51:46 +0100830 if (components == NULL)
831 return FALSE;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100832
Richard Hughes481aa2a2018-09-18 20:51:46 +0100833 /* for each component in the silo */
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100834 errors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_error_free);
835 install_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
Richard Hughes481aa2a2018-09-18 20:51:46 +0100836 for (guint i = 0; i < components->len; i++) {
837 XbNode *component = g_ptr_array_index (components, i);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100838
839 /* do any devices pass the requirements */
840 for (guint j = 0; j < devices_possible->len; j++) {
841 FuDevice *device = g_ptr_array_index (devices_possible, j);
842 g_autoptr(FuInstallTask) task = NULL;
843 g_autoptr(GError) error_local = NULL;
844
845 /* is this component valid for the device */
Richard Hughes481aa2a2018-09-18 20:51:46 +0100846 task = fu_install_task_new (device, component);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100847 if (!fu_engine_check_requirements (priv->engine,
Richard Hughes460226a2018-05-21 20:56:21 +0100848 task, priv->flags,
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100849 &error_local)) {
850 g_debug ("requirement on %s:%s failed: %s",
851 fu_device_get_id (device),
Richard Hughes481aa2a2018-09-18 20:51:46 +0100852 xb_node_query_text (component, "id", NULL),
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100853 error_local->message);
854 g_ptr_array_add (errors, g_steal_pointer (&error_local));
855 continue;
856 }
857
Mario Limonciello7a3df4b2019-01-31 10:27:22 -0600858 /* if component should have an update message from CAB */
859 fu_device_incorporate_from_component (device, component);
860
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100861 /* success */
862 g_ptr_array_add (install_tasks, g_steal_pointer (&task));
863 }
864 }
865
866 /* order the install tasks by the device priority */
867 g_ptr_array_sort (install_tasks, fu_util_install_task_sort_cb);
868
869 /* nothing suitable */
870 if (install_tasks->len == 0) {
871 GError *error_tmp = fu_common_error_array_get_best (errors);
872 g_propagate_error (error, error_tmp);
873 return FALSE;
874 }
875
Mario Limonciello3f243a92019-01-21 22:05:23 -0600876 priv->current_operation = FU_UTIL_OPERATION_INSTALL;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500877 g_signal_connect (priv->engine, "device-changed",
Mario Limonciello3f243a92019-01-21 22:05:23 -0600878 G_CALLBACK (fu_util_update_device_changed_cb), priv);
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500879
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100880 /* install all the tasks */
Richard Hughesdbd8c762018-06-15 20:31:40 +0100881 if (!fu_engine_install_tasks (priv->engine, install_tasks, blob_cab, priv->flags, error))
882 return FALSE;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100883
Mario Limonciello32241f42019-01-24 10:12:41 -0600884 fu_util_display_current_message (priv);
885
Mario Limonciello3f243a92019-01-21 22:05:23 -0600886 /* we don't want to ask anything */
887 if (priv->no_reboot_check) {
888 g_debug ("skipping reboot check");
889 return TRUE;
890 }
891
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100892 /* success */
Mario Limonciello3f243a92019-01-21 22:05:23 -0600893 return fu_util_prompt_complete (priv->completion_flags, TRUE, error);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100894}
895
Richard Hughes98ca9932018-05-18 10:24:07 +0100896static gboolean
Mario Limonciello46aaee82019-01-10 12:58:00 -0600897fu_util_update (FuUtilPrivate *priv, gchar **values, GError **error)
898{
899 g_autoptr(GPtrArray) devices = NULL;
900
901 /* load engine */
902 if (!fu_util_start_engine (priv, error))
903 return FALSE;
904
Mario Limonciello3f243a92019-01-21 22:05:23 -0600905 priv->current_operation = FU_UTIL_OPERATION_UPDATE;
906 g_signal_connect (priv->engine, "device-changed",
907 G_CALLBACK (fu_util_update_device_changed_cb), priv);
908
Mario Limonciello46aaee82019-01-10 12:58:00 -0600909 devices = fu_engine_get_devices (priv->engine, error);
Mario Limonciello387bda42019-02-07 14:20:22 +0000910 if (devices == NULL)
911 return FALSE;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600912 for (guint i = 0; i < devices->len; i++) {
913 FwupdDevice *dev = g_ptr_array_index (devices, i);
914 FwupdRelease *rel;
915 const gchar *remote_id;
916 const gchar *device_id;
917 const gchar *uri_tmp;
918 g_autoptr(GPtrArray) rels = NULL;
919 g_autoptr(GError) error_local = NULL;
920
921 if (!fu_util_is_interesting_device (dev))
922 continue;
923 /* only show stuff that has metadata available */
924 if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED))
925 continue;
926
927 device_id = fu_device_get_id (dev);
928 rels = fu_engine_get_upgrades (priv->engine, device_id, &error_local);
929 if (rels == NULL) {
930 g_printerr ("%s\n", error_local->message);
931 continue;
932 }
933
934 rel = g_ptr_array_index (rels, 0);
935 uri_tmp = fwupd_release_get_uri (rel);
936 remote_id = fwupd_release_get_remote_id (rel);
937 if (remote_id != NULL) {
938 FwupdRemote *remote;
939 g_auto(GStrv) argv = NULL;
940
941 remote = fu_engine_get_remote_by_id (priv->engine,
942 remote_id,
943 &error_local);
944 if (remote == NULL) {
945 g_printerr ("%s\n", error_local->message);
946 continue;
947 }
948
949 argv = g_new0 (gchar *, 2);
950 /* local remotes have the firmware already */
Mario Limonciello4f24d0b2019-01-26 01:19:59 -0600951 if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_LOCAL) {
Mario Limonciello46aaee82019-01-10 12:58:00 -0600952 const gchar *fn_cache = fwupd_remote_get_filename_cache (remote);
953 g_autofree gchar *path = g_path_get_dirname (fn_cache);
954 argv[0] = g_build_filename (path, uri_tmp, NULL);
Mario Limonciello4f24d0b2019-01-26 01:19:59 -0600955 } else if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DIRECTORY) {
956 argv[0] = g_strdup (uri_tmp + 7);
Mario Limonciello46aaee82019-01-10 12:58:00 -0600957 /* web remote, fu_util_install will download file */
958 } else {
959 argv[0] = fwupd_remote_build_firmware_uri (remote, uri_tmp, error);
960 }
961 if (!fu_util_install (priv, argv, &error_local)) {
962 g_printerr ("%s\n", error_local->message);
963 continue;
964 }
Mario Limonciello32241f42019-01-24 10:12:41 -0600965 fu_util_display_current_message (priv);
Mario Limonciello46aaee82019-01-10 12:58:00 -0600966 }
967 }
Mario Limonciello3f243a92019-01-21 22:05:23 -0600968
969 /* we don't want to ask anything */
970 if (priv->no_reboot_check) {
971 g_debug ("skipping reboot check");
972 return TRUE;
973 }
974
975 return fu_util_prompt_complete (priv->completion_flags, TRUE, error);
Mario Limonciello46aaee82019-01-10 12:58:00 -0600976}
977
978static gboolean
Richard Hughes98ca9932018-05-18 10:24:07 +0100979fu_util_detach (FuUtilPrivate *priv, gchar **values, GError **error)
980{
981 g_autoptr(FuDevice) device = NULL;
Richard Hughes2a679cd2018-09-04 21:13:23 +0100982 g_autoptr(FuDeviceLocker) locker = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +0100983
984 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500985 if (!fu_util_start_engine (priv, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100986 return FALSE;
987
988 /* invalid args */
989 if (g_strv_length (values) == 0) {
990 g_set_error_literal (error,
991 FWUPD_ERROR,
992 FWUPD_ERROR_INVALID_ARGS,
993 "Invalid arguments");
994 return FALSE;
995 }
996
997 /* get device */
998 if (g_strv_length (values) >= 1) {
999 device = fu_engine_get_device (priv->engine, values[0], error);
1000 if (device == NULL)
1001 return FALSE;
1002 } else {
1003 device = fu_util_prompt_for_device (priv, error);
1004 if (device == NULL)
1005 return FALSE;
1006 }
1007
1008 /* run vfunc */
Richard Hughes2a679cd2018-09-04 21:13:23 +01001009 locker = fu_device_locker_new (device, error);
1010 if (locker == NULL)
1011 return FALSE;
Richard Hughes98ca9932018-05-18 10:24:07 +01001012 return fu_device_detach (device, error);
1013}
1014
1015static gboolean
1016fu_util_attach (FuUtilPrivate *priv, gchar **values, GError **error)
1017{
1018 g_autoptr(FuDevice) device = NULL;
Richard Hughes2a679cd2018-09-04 21:13:23 +01001019 g_autoptr(FuDeviceLocker) locker = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +01001020
1021 /* load engine */
Mario Limoncielloe61c94d2018-10-11 10:49:55 -05001022 if (!fu_util_start_engine (priv, error))
Richard Hughes98ca9932018-05-18 10:24:07 +01001023 return FALSE;
1024
1025 /* invalid args */
1026 if (g_strv_length (values) == 0) {
1027 g_set_error_literal (error,
1028 FWUPD_ERROR,
1029 FWUPD_ERROR_INVALID_ARGS,
1030 "Invalid arguments");
1031 return FALSE;
1032 }
1033
1034 /* get device */
1035 if (g_strv_length (values) >= 1) {
1036 device = fu_engine_get_device (priv->engine, values[0], error);
1037 if (device == NULL)
1038 return FALSE;
1039 } else {
1040 device = fu_util_prompt_for_device (priv, error);
1041 if (device == NULL)
1042 return FALSE;
1043 }
1044
1045 /* run vfunc */
Richard Hughes2a679cd2018-09-04 21:13:23 +01001046 locker = fu_device_locker_new (device, error);
1047 if (locker == NULL)
1048 return FALSE;
Richard Hughes98ca9932018-05-18 10:24:07 +01001049 return fu_device_attach (device, error);
1050}
1051
Richard Hughes1d894f12018-08-31 13:05:51 +01001052static gboolean
Mario Limonciello96a0dd52019-02-25 13:50:03 -06001053fu_util_activate (FuUtilPrivate *priv, gchar **values, GError **error)
1054{
1055 gboolean has_pending = FALSE;
1056 g_autoptr(FuHistory) history = fu_history_new ();
1057 g_autoptr(GPtrArray) devices = NULL;
1058
1059 /* check the history database before starting the daemon */
1060 if (g_strv_length (values) == 0) {
1061 devices = fu_history_get_devices (history, error);
1062 if (devices == NULL)
1063 return FALSE;
1064 } else if (g_strv_length (values) == 1) {
1065 FuDevice *device;
1066 device = fu_history_get_device_by_id (history, values[0], error);
1067 if (device == NULL)
1068 return FALSE;
1069 devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
1070 g_ptr_array_add (devices, device);
1071 } else {
1072 g_set_error_literal (error,
1073 FWUPD_ERROR,
1074 FWUPD_ERROR_INVALID_ARGS,
1075 "Invalid arguments");
1076 return FALSE;
1077 }
1078
1079 /* nothing to do */
1080 for (guint i = 0; i < devices->len; i++) {
1081 FuDevice *dev = g_ptr_array_index (devices, i);
1082 if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) {
1083 fu_engine_add_plugin_filter (priv->engine,
1084 fu_device_get_plugin (dev));
1085 has_pending = TRUE;
1086 }
1087 }
1088 if (!has_pending) {
1089 g_set_error_literal (error,
1090 FWUPD_ERROR,
1091 FWUPD_ERROR_NOTHING_TO_DO,
1092 "No firmware to activate");
1093 return FALSE;
1094
1095 }
1096
1097 /* load engine */
1098 if (!fu_util_start_engine (priv, error))
1099 return FALSE;
1100
1101 /* activate anything with _NEEDS_ACTIVATION */
1102 for (guint i = 0; i < devices->len; i++) {
1103 FuDevice *device = g_ptr_array_index (devices, i);
1104 if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION))
1105 continue;
1106 /* TRANSLATORS: shown when shutting down to switch to the new version */
1107 g_print ("%s %s…\n", _("Activating firmware update"), fu_device_get_name (device));
1108 if (!fu_engine_activate (priv->engine, fu_device_get_id (device), error))
1109 return FALSE;
1110 }
1111
1112 return TRUE;
1113}
1114
1115static gboolean
Richard Hughes1d894f12018-08-31 13:05:51 +01001116fu_util_hwids (FuUtilPrivate *priv, gchar **values, GError **error)
1117{
1118 g_autoptr(FuSmbios) smbios = fu_smbios_new ();
1119 g_autoptr(FuHwids) hwids = fu_hwids_new ();
1120 const gchar *hwid_keys[] = {
1121 FU_HWIDS_KEY_BIOS_VENDOR,
1122 FU_HWIDS_KEY_BIOS_VERSION,
1123 FU_HWIDS_KEY_BIOS_MAJOR_RELEASE,
1124 FU_HWIDS_KEY_BIOS_MINOR_RELEASE,
1125 FU_HWIDS_KEY_MANUFACTURER,
1126 FU_HWIDS_KEY_FAMILY,
1127 FU_HWIDS_KEY_PRODUCT_NAME,
1128 FU_HWIDS_KEY_PRODUCT_SKU,
1129 FU_HWIDS_KEY_ENCLOSURE_KIND,
1130 FU_HWIDS_KEY_BASEBOARD_MANUFACTURER,
1131 FU_HWIDS_KEY_BASEBOARD_PRODUCT,
1132 NULL };
1133
1134 /* read DMI data */
1135 if (g_strv_length (values) == 0) {
1136 if (!fu_smbios_setup (smbios, error))
1137 return FALSE;
1138 } else if (g_strv_length (values) == 1) {
1139 if (!fu_smbios_setup_from_file (smbios, values[0], error))
1140 return FALSE;
1141 } else {
1142 g_set_error_literal (error,
1143 FWUPD_ERROR,
1144 FWUPD_ERROR_INVALID_ARGS,
1145 "Invalid arguments");
1146 return FALSE;
1147 }
1148 if (!fu_hwids_setup (hwids, smbios, error))
1149 return FALSE;
1150
1151 /* show debug output */
1152 g_print ("Computer Information\n");
1153 g_print ("--------------------\n");
1154 for (guint i = 0; hwid_keys[i] != NULL; i++) {
1155 const gchar *tmp = fu_hwids_get_value (hwids, hwid_keys[i]);
1156 if (tmp == NULL)
1157 continue;
1158 if (g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MAJOR_RELEASE) == 0 ||
1159 g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MINOR_RELEASE) == 0) {
1160 guint64 val = g_ascii_strtoull (tmp, NULL, 16);
1161 g_print ("%s: %" G_GUINT64_FORMAT "\n", hwid_keys[i], val);
1162 } else {
1163 g_print ("%s: %s\n", hwid_keys[i], tmp);
1164 }
1165 }
1166
1167 /* show GUIDs */
1168 g_print ("\nHardware IDs\n");
1169 g_print ("------------\n");
1170 for (guint i = 0; i < 15; i++) {
1171 const gchar *keys = NULL;
1172 g_autofree gchar *guid = NULL;
1173 g_autofree gchar *key = NULL;
1174 g_autofree gchar *keys_str = NULL;
1175 g_auto(GStrv) keysv = NULL;
1176 g_autoptr(GError) error_local = NULL;
1177
1178 /* get the GUID */
1179 key = g_strdup_printf ("HardwareID-%u", i);
1180 keys = fu_hwids_get_replace_keys (hwids, key);
1181 guid = fu_hwids_get_guid (hwids, key, &error_local);
1182 if (guid == NULL) {
1183 g_print ("%s\n", error_local->message);
1184 continue;
1185 }
1186
1187 /* show what makes up the GUID */
1188 keysv = g_strsplit (keys, "&", -1);
1189 keys_str = g_strjoinv (" + ", keysv);
1190 g_print ("{%s} <- %s\n", guid, keys_str);
1191 }
1192
1193 return TRUE;
1194}
1195
Mario Limonciellof6d01b12018-10-18 12:57:10 -05001196static gboolean
1197fu_util_firmware_builder (FuUtilPrivate *priv, gchar **values, GError **error)
1198{
1199 const gchar *script_fn = "startup.sh";
1200 const gchar *output_fn = "firmware.bin";
1201 g_autoptr(GBytes) archive_blob = NULL;
1202 g_autoptr(GBytes) firmware_blob = NULL;
1203 if (g_strv_length (values) < 2) {
1204 g_set_error_literal (error,
1205 FWUPD_ERROR,
1206 FWUPD_ERROR_INVALID_ARGS,
1207 "Invalid arguments");
1208 return FALSE;
1209 }
1210 archive_blob = fu_common_get_contents_bytes (values[0], error);
1211 if (archive_blob == NULL)
1212 return FALSE;
1213 if (g_strv_length (values) > 2)
1214 script_fn = values[2];
1215 if (g_strv_length (values) > 3)
1216 output_fn = values[3];
1217 firmware_blob = fu_common_firmware_builder (archive_blob, script_fn, output_fn, error);
1218 if (firmware_blob == NULL)
1219 return FALSE;
1220 return fu_common_set_contents_bytes (values[1], firmware_blob, error);
1221}
1222
Mario Limonciello62f84862018-10-18 13:15:23 -05001223static void
1224fu_util_device_added_cb (FwupdClient *client,
1225 FwupdDevice *device,
1226 gpointer user_data)
1227{
1228 g_autofree gchar *tmp = fwupd_device_to_string (device);
1229 /* TRANSLATORS: this is when a device is hotplugged */
1230 g_print ("%s\n%s", _("Device added:"), tmp);
1231}
1232
1233static void
1234fu_util_device_removed_cb (FwupdClient *client,
1235 FwupdDevice *device,
1236 gpointer user_data)
1237{
1238 g_autofree gchar *tmp = fwupd_device_to_string (device);
1239 /* TRANSLATORS: this is when a device is hotplugged */
1240 g_print ("%s\n%s", _("Device removed:"), tmp);
1241}
1242
1243static void
1244fu_util_device_changed_cb (FwupdClient *client,
1245 FwupdDevice *device,
1246 gpointer user_data)
1247{
1248 g_autofree gchar *tmp = fwupd_device_to_string (device);
1249 /* TRANSLATORS: this is when a device has been updated */
1250 g_print ("%s\n%s", _("Device changed:"), tmp);
1251}
1252
1253static void
1254fu_util_changed_cb (FwupdClient *client, gpointer user_data)
1255{
1256 /* TRANSLATORS: this is when the daemon state changes */
1257 g_print ("%s\n", _("Changed"));
1258}
1259
1260static gboolean
1261fu_util_monitor (FuUtilPrivate *priv, gchar **values, GError **error)
1262{
1263 g_autoptr(FwupdClient) client = fwupd_client_new ();
1264
1265 /* get all the devices */
1266 if (!fwupd_client_connect (client, priv->cancellable, error))
1267 return FALSE;
1268
1269 /* watch for any hotplugged device */
1270 g_signal_connect (client, "changed",
1271 G_CALLBACK (fu_util_changed_cb), priv);
1272 g_signal_connect (client, "device-added",
1273 G_CALLBACK (fu_util_device_added_cb), priv);
1274 g_signal_connect (client, "device-removed",
1275 G_CALLBACK (fu_util_device_removed_cb), priv);
1276 g_signal_connect (client, "device-changed",
1277 G_CALLBACK (fu_util_device_changed_cb), priv);
1278 g_signal_connect (priv->cancellable, "cancelled",
1279 G_CALLBACK (fu_util_cancelled_cb), priv);
1280 g_main_loop_run (priv->loop);
1281 return TRUE;
1282}
1283
Richard Hughesb5976832018-05-18 10:02:09 +01001284int
1285main (int argc, char *argv[])
1286{
Richard Hughes460226a2018-05-21 20:56:21 +01001287 gboolean allow_older = FALSE;
1288 gboolean allow_reinstall = FALSE;
Richard Hughesb5976832018-05-18 10:02:09 +01001289 gboolean force = FALSE;
1290 gboolean ret;
Mario Limonciello2d4b7a52018-09-12 22:08:04 -05001291 gboolean version = FALSE;
Mario Limonciello5d7aa402019-02-04 09:35:58 -06001292 gboolean interactive = isatty (fileno (stdout)) != 0;
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001293 g_auto(GStrv) plugin_glob = NULL;
Richard Hughesb5976832018-05-18 10:02:09 +01001294 g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1);
1295 g_autoptr(GError) error = NULL;
1296 g_autofree gchar *cmd_descriptions = NULL;
1297 const GOptionEntry options[] = {
Mario Limonciello2d4b7a52018-09-12 22:08:04 -05001298 { "version", '\0', 0, G_OPTION_ARG_NONE, &version,
1299 /* TRANSLATORS: command line option */
1300 _("Show client and daemon versions"), NULL },
Richard Hughes460226a2018-05-21 20:56:21 +01001301 { "allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall,
1302 /* TRANSLATORS: command line option */
1303 _("Allow re-installing existing firmware versions"), NULL },
1304 { "allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older,
1305 /* TRANSLATORS: command line option */
1306 _("Allow downgrading firmware versions"), NULL },
Richard Hughesb5976832018-05-18 10:02:09 +01001307 { "force", '\0', 0, G_OPTION_ARG_NONE, &force,
1308 /* TRANSLATORS: command line option */
1309 _("Override plugin warning"), NULL },
Mario Limonciello3f243a92019-01-21 22:05:23 -06001310 { "no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check,
1311 /* TRANSLATORS: command line option */
1312 _("Do not check for reboot after update"), NULL },
Mario Limoncielloba9e5b92018-05-21 16:02:32 -05001313 { "show-all-devices", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all_devices,
1314 /* TRANSLATORS: command line option */
1315 _("Show devices that are not updatable"), NULL },
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001316 { "plugin-whitelist", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &plugin_glob,
1317 /* TRANSLATORS: command line option */
1318 _("Manually whitelist specific plugins"), NULL },
Mario Limonciello8402cea2019-02-07 20:25:31 -06001319 { "prepare", '\0', 0, G_OPTION_ARG_NONE, &priv->prepare_blob,
Mario Limonciello53ce25d2019-02-01 16:09:03 +00001320 /* TRANSLATORS: command line option */
1321 _("Run the plugin composite prepare routine when using install-blob"), NULL },
Mario Limonciello8402cea2019-02-07 20:25:31 -06001322 { "cleanup", '\0', 0, G_OPTION_ARG_NONE, &priv->cleanup_blob,
Mario Limonciello53ce25d2019-02-01 16:09:03 +00001323 /* TRANSLATORS: command line option */
1324 _("Run the plugin composite cleanup routine when using install-blob"), NULL },
1325
Richard Hughesb5976832018-05-18 10:02:09 +01001326 { NULL}
1327 };
1328
1329 setlocale (LC_ALL, "");
1330
1331 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1332 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1333 textdomain (GETTEXT_PACKAGE);
1334
1335 /* ensure root user */
Mario Limonciello5d7aa402019-02-04 09:35:58 -06001336 if (interactive && (getuid () != 0 || geteuid () != 0))
Richard Hughesb5976832018-05-18 10:02:09 +01001337 /* TRANSLATORS: we're poking around as a power user */
Mario Limonciellob900c092018-05-22 14:22:21 -05001338 g_printerr ("%s\n", _("This program may only work correctly as root"));
Richard Hughesb5976832018-05-18 10:02:09 +01001339
1340 /* create helper object */
1341 priv->loop = g_main_loop_new (NULL, FALSE);
1342 priv->progressbar = fu_progressbar_new ();
1343
1344 /* add commands */
1345 priv->cmd_array = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_util_item_free);
1346 fu_util_add (priv->cmd_array,
Mario Limonciellof6d01b12018-10-18 12:57:10 -05001347 "build-firmware",
1348 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]",
1349 /* TRANSLATORS: command description */
1350 _("Build firmware using a sandbox"),
1351 fu_util_firmware_builder);
1352 fu_util_add (priv->cmd_array,
Richard Hughesb5976832018-05-18 10:02:09 +01001353 "smbios-dump",
1354 "FILE",
1355 /* TRANSLATORS: command description */
1356 _("Dump SMBIOS data from a file"),
1357 fu_util_smbios_dump);
Richard Hughes98ca9932018-05-18 10:24:07 +01001358 fu_util_add (priv->cmd_array,
Richard Hughes8c71a3f2018-05-22 19:19:52 +01001359 "get-plugins",
1360 NULL,
1361 /* TRANSLATORS: command description */
1362 _("Get all enabled plugins registered with the system"),
1363 fu_util_get_plugins);
1364 fu_util_add (priv->cmd_array,
Mario Limonciello716ab272018-05-29 12:34:37 -05001365 "get-details",
1366 NULL,
1367 /* TRANSLATORS: command description */
1368 _("Gets details about a firmware file"),
1369 fu_util_get_details);
1370 fu_util_add (priv->cmd_array,
Mario Limonciello1e35e4c2019-01-28 11:13:02 -06001371 "get-updates",
1372 NULL,
1373 /* TRANSLATORS: command description */
1374 _("Gets the list of updates for connected hardware"),
1375 fu_util_get_updates);
1376 fu_util_add (priv->cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001377 "get-devices",
1378 NULL,
1379 /* TRANSLATORS: command description */
1380 _("Get all devices that support firmware updates"),
1381 fu_util_get_devices);
1382 fu_util_add (priv->cmd_array,
Mario Limoncielloba9e5b92018-05-21 16:02:32 -05001383 "get-topology",
1384 NULL,
1385 /* TRANSLATORS: command description */
1386 _("Get all devices according to the system topology"),
1387 fu_util_get_topology);
1388 fu_util_add (priv->cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001389 "watch",
1390 NULL,
1391 /* TRANSLATORS: command description */
Piotr DrÄ…g472fa592018-06-06 14:53:48 +02001392 _("Watch for hardware changes"),
Richard Hughes98ca9932018-05-18 10:24:07 +01001393 fu_util_watch);
1394 fu_util_add (priv->cmd_array,
1395 "install-blob",
1396 "FILENAME DEVICE-ID",
1397 /* TRANSLATORS: command description */
1398 _("Install a firmware blob on a device"),
1399 fu_util_install_blob);
1400 fu_util_add (priv->cmd_array,
Richard Hughesa36c9cf2018-05-20 10:41:44 +01001401 "install",
1402 "FILE [ID]",
1403 /* TRANSLATORS: command description */
1404 _("Install a firmware file on this hardware"),
1405 fu_util_install);
1406 fu_util_add (priv->cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001407 "attach",
1408 "DEVICE-ID",
1409 /* TRANSLATORS: command description */
1410 _("Attach to firmware mode"),
1411 fu_util_attach);
1412 fu_util_add (priv->cmd_array,
1413 "detach",
1414 "DEVICE-ID",
1415 /* TRANSLATORS: command description */
1416 _("Detach to bootloader mode"),
1417 fu_util_detach);
Richard Hughes1d894f12018-08-31 13:05:51 +01001418 fu_util_add (priv->cmd_array,
Mario Limonciello96a0dd52019-02-25 13:50:03 -06001419 "activate",
1420 "[DEVICE-ID]",
1421 /* TRANSLATORS: command description */
1422 _("Activate pending devices"),
1423 fu_util_activate);
1424 fu_util_add (priv->cmd_array,
Richard Hughes1d894f12018-08-31 13:05:51 +01001425 "hwids",
1426 "[FILE]",
1427 /* TRANSLATORS: command description */
1428 _("Return all the hardware IDs for the machine"),
1429 fu_util_hwids);
Mario Limonciello62f84862018-10-18 13:15:23 -05001430 fu_util_add (priv->cmd_array,
1431 "monitor",
1432 NULL,
1433 /* TRANSLATORS: command description */
1434 _("Monitor the daemon for events"),
1435 fu_util_monitor);
Mario Limonciello46aaee82019-01-10 12:58:00 -06001436 fu_util_add (priv->cmd_array,
1437 "update",
1438 NULL,
1439 /* TRANSLATORS: command description */
1440 _("Update all devices that match local metadata"),
1441 fu_util_update);
Richard Hughesb5976832018-05-18 10:02:09 +01001442
1443 /* do stuff on ctrl+c */
1444 priv->cancellable = g_cancellable_new ();
1445 g_unix_signal_add_full (G_PRIORITY_DEFAULT,
1446 SIGINT, fu_util_sigint_cb,
1447 priv, NULL);
1448 g_signal_connect (priv->cancellable, "cancelled",
1449 G_CALLBACK (fu_util_cancelled_cb), priv);
1450
1451 /* sort by command name */
1452 g_ptr_array_sort (priv->cmd_array,
1453 (GCompareFunc) fu_sort_command_name_cb);
1454
Mario Limonciello3f243a92019-01-21 22:05:23 -06001455 /* non-TTY consoles cannot answer questions */
Mario Limonciello5d7aa402019-02-04 09:35:58 -06001456 if (!interactive) {
Mario Limonciello3f243a92019-01-21 22:05:23 -06001457 priv->no_reboot_check = TRUE;
Mario Limonciello9b31e6f2019-01-31 15:42:19 -06001458 fu_progressbar_set_interactive (priv->progressbar, FALSE);
1459 }
Mario Limonciello3f243a92019-01-21 22:05:23 -06001460
Richard Hughesb5976832018-05-18 10:02:09 +01001461 /* get a list of the commands */
1462 priv->context = g_option_context_new (NULL);
1463 cmd_descriptions = fu_util_get_descriptions (priv->cmd_array);
1464 g_option_context_set_summary (priv->context, cmd_descriptions);
1465 g_option_context_set_description (priv->context,
1466 "This tool allows an administrator to use the fwupd plugins "
1467 "without being installed on the host system.");
1468
1469 /* TRANSLATORS: program name */
1470 g_set_application_name (_("Firmware Utility"));
1471 g_option_context_add_main_entries (priv->context, options, NULL);
Mario Limonciellofde47732018-09-11 12:20:58 -05001472 g_option_context_add_group (priv->context, fu_debug_get_option_group ());
Richard Hughesb5976832018-05-18 10:02:09 +01001473 ret = g_option_context_parse (priv->context, &argc, &argv, &error);
1474 if (!ret) {
1475 /* TRANSLATORS: the user didn't read the man page */
1476 g_print ("%s: %s\n", _("Failed to parse arguments"),
1477 error->message);
1478 return EXIT_FAILURE;
1479 }
1480
Richard Hughes460226a2018-05-21 20:56:21 +01001481 /* set flags */
1482 priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY;
1483 if (allow_reinstall)
1484 priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL;
1485 if (allow_older)
1486 priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER;
1487 if (force)
1488 priv->flags |= FWUPD_INSTALL_FLAG_FORCE;
1489
Richard Hughes98ca9932018-05-18 10:24:07 +01001490 /* load engine */
1491 priv->engine = fu_engine_new (FU_APP_FLAGS_NO_IDLE_SOURCES);
1492 g_signal_connect (priv->engine, "device-added",
1493 G_CALLBACK (fu_main_engine_device_added_cb),
1494 priv);
1495 g_signal_connect (priv->engine, "device-removed",
1496 G_CALLBACK (fu_main_engine_device_removed_cb),
1497 priv);
Richard Hughes98ca9932018-05-18 10:24:07 +01001498 g_signal_connect (priv->engine, "status-changed",
1499 G_CALLBACK (fu_main_engine_status_changed_cb),
1500 priv);
1501 g_signal_connect (priv->engine, "percentage-changed",
1502 G_CALLBACK (fu_main_engine_percentage_changed_cb),
1503 priv);
1504
Mario Limonciello2d4b7a52018-09-12 22:08:04 -05001505 /* just show versions and exit */
1506 if (version) {
1507 g_autofree gchar *version_str = fu_util_get_versions ();
1508 g_print ("%s\n", version_str);
1509 return EXIT_SUCCESS;
1510 }
1511
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001512 /* any plugin whitelist specified */
1513 for (guint i = 0; plugin_glob != NULL && plugin_glob[i] != NULL; i++)
1514 fu_engine_add_plugin_filter (priv->engine, plugin_glob[i]);
1515
Richard Hughesb5976832018-05-18 10:02:09 +01001516 /* run the specified command */
1517 ret = fu_util_run (priv, argv[1], (gchar**) &argv[2], &error);
1518 if (!ret) {
1519 if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) {
1520 g_autofree gchar *tmp = NULL;
1521 tmp = g_option_context_get_help (priv->context, TRUE, NULL);
1522 g_print ("%s\n\n%s", error->message, tmp);
1523 return EXIT_FAILURE;
1524 }
1525 if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
1526 g_print ("%s\n", error->message);
1527 return EXIT_NOTHING_TO_DO;
1528 }
1529 g_print ("%s\n", error->message);
1530 return EXIT_FAILURE;
1531 }
1532
1533 /* success */
1534 return EXIT_SUCCESS;
1535}