blob: f27808759ae2a881365760014640f169511bce63 [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"
Mario Limonciello3143bad2019-02-27 07:31:00 -060029#include "fwupd-device-private.h"
Richard Hughesb5976832018-05-18 10:02:09 +010030
Richard Hughes3d005222019-05-17 14:02:41 +010031#ifdef HAVE_SYSTEMD
32#include "fu-systemd.h"
33#endif
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 Hughesc77e1112019-03-01 10:16:26 +000045struct FuUtilPrivate {
Richard Hughesb5976832018-05-18 10:02:09 +010046 GCancellable *cancellable;
47 GMainLoop *loop;
48 GOptionContext *context;
Richard Hughes98ca9932018-05-18 10:24:07 +010049 FuEngine *engine;
Richard Hughesb5976832018-05-18 10:02:09 +010050 FuProgressbar *progressbar;
Mario Limonciello3f243a92019-01-21 22:05:23 -060051 gboolean no_reboot_check;
Mario Limonciello53ce25d2019-02-01 16:09:03 +000052 gboolean prepare_blob;
53 gboolean cleanup_blob;
Mario Limonciello3143bad2019-02-27 07:31:00 -060054 gboolean enable_json_state;
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 Hughes747f5702019-08-06 14:27:26 +010062 FwupdDeviceFlags filter_include;
63 FwupdDeviceFlags filter_exclude;
Richard Hughesc77e1112019-03-01 10:16:26 +000064};
Richard Hughesb5976832018-05-18 10:02:09 +010065
Mario Limoncielloe61c94d2018-10-11 10:49:55 -050066static gboolean
Mario Limonciello3143bad2019-02-27 07:31:00 -060067fu_util_save_current_state (FuUtilPrivate *priv, GError **error)
68{
69 g_autoptr(JsonBuilder) builder = NULL;
70 g_autoptr(JsonGenerator) json_generator = NULL;
71 g_autoptr(JsonNode) json_root = NULL;
72 g_autoptr(GPtrArray) devices = NULL;
73 g_autofree gchar *state = NULL;
74 g_autofree gchar *dirname = NULL;
75 g_autofree gchar *filename = NULL;
76
77 if (!priv->enable_json_state)
78 return TRUE;
79
80 devices = fu_engine_get_devices (priv->engine, error);
81 if (devices == NULL)
82 return FALSE;
83
84 /* create header */
85 builder = json_builder_new ();
86 json_builder_begin_object (builder);
87
88 /* add each device */
89 json_builder_set_member_name (builder, "Devices");
90 json_builder_begin_array (builder);
91 for (guint i = 0; i < devices->len; i++) {
92 FwupdDevice *dev = g_ptr_array_index (devices, i);
93 json_builder_begin_object (builder);
94 fwupd_device_to_json (dev, builder);
95 json_builder_end_object (builder);
96 }
97 json_builder_end_array (builder);
98 json_builder_end_object (builder);
99
100 /* export as a string */
101 json_root = json_builder_get_root (builder);
102 json_generator = json_generator_new ();
103 json_generator_set_pretty (json_generator, TRUE);
104 json_generator_set_root (json_generator, json_root);
105 state = json_generator_to_data (json_generator, NULL);
106 if (state == NULL)
107 return FALSE;
108 dirname = fu_common_get_path (FU_PATH_KIND_LOCALSTATEDIR_PKG);
109 filename = g_build_filename (dirname, "state.json", NULL);
110 return g_file_set_contents (filename, state, -1, error);
111}
112
113static gboolean
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000114fu_util_start_engine (FuUtilPrivate *priv, FuEngineLoadFlags flags, GError **error)
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -0600115{
116 g_autoptr(GError) error_local = NULL;
117
Richard Hughesd92ccca2019-05-20 11:28:31 +0100118#ifdef HAVE_SYSTEMD
Richard Hughes3d005222019-05-17 14:02:41 +0100119 if (!fu_systemd_unit_stop (fu_util_get_systemd_unit (), &error_local))
Mario Limoncielloe8dd4d72019-02-26 15:28:04 -0600120 g_debug ("Failed top stop daemon: %s", error_local->message);
Richard Hughesd92ccca2019-05-20 11:28:31 +0100121#endif
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000122 if (!fu_engine_load (priv->engine, flags, error))
Richard Hughesf425d292019-01-18 17:57:39 +0000123 return FALSE;
124 if (fu_engine_get_tainted (priv->engine)) {
125 g_printerr ("WARNING: This tool has loaded 3rd party code and "
126 "is no longer supported by the upstream developers!\n");
127 }
128 return TRUE;
Mario Limoncielloe61c94d2018-10-11 10:49:55 -0500129}
130
Richard Hughesb5976832018-05-18 10:02:09 +0100131static void
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500132fu_util_maybe_prefix_sandbox_error (const gchar *value, GError **error)
133{
134 g_autofree gchar *path = g_path_get_dirname (value);
135 if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
136 g_prefix_error (error,
137 "Unable to access %s. You may need to copy %s to %s: ",
138 path, value, g_getenv ("HOME"));
139 }
140}
141
142static void
Richard Hughesb5976832018-05-18 10:02:09 +0100143fu_util_cancelled_cb (GCancellable *cancellable, gpointer user_data)
144{
145 FuUtilPrivate *priv = (FuUtilPrivate *) user_data;
146 /* TRANSLATORS: this is when a device ctrl+c's a watch */
147 g_print ("%s\n", _("Cancelled"));
148 g_main_loop_quit (priv->loop);
149}
150
151static gboolean
152fu_util_smbios_dump (FuUtilPrivate *priv, gchar **values, GError **error)
153{
154 g_autofree gchar *tmp = NULL;
155 g_autoptr(FuSmbios) smbios = NULL;
156 if (g_strv_length (values) < 1) {
157 g_set_error_literal (error,
158 FWUPD_ERROR,
159 FWUPD_ERROR_INVALID_ARGS,
160 "Invalid arguments");
161 return FALSE;
162 }
163 smbios = fu_smbios_new ();
164 if (!fu_smbios_setup_from_file (smbios, values[0], error))
165 return FALSE;
166 tmp = fu_smbios_to_string (smbios);
167 g_print ("%s\n", tmp);
168 return TRUE;
169}
170
Richard Hughesb5976832018-05-18 10:02:09 +0100171static gboolean
172fu_util_sigint_cb (gpointer user_data)
173{
174 FuUtilPrivate *priv = (FuUtilPrivate *) user_data;
175 g_debug ("Handling SIGINT");
176 g_cancellable_cancel (priv->cancellable);
177 return FALSE;
178}
179
180static void
181fu_util_private_free (FuUtilPrivate *priv)
182{
Mario Limonciellocc50e1a2018-08-14 17:45:24 -0500183 if (priv->current_device != NULL)
184 g_object_unref (priv->current_device);
Richard Hughes98ca9932018-05-18 10:24:07 +0100185 if (priv->engine != NULL)
186 g_object_unref (priv->engine);
Richard Hughesb5976832018-05-18 10:02:09 +0100187 if (priv->loop != NULL)
188 g_main_loop_unref (priv->loop);
189 if (priv->cancellable != NULL)
190 g_object_unref (priv->cancellable);
191 if (priv->progressbar != NULL)
192 g_object_unref (priv->progressbar);
193 if (priv->context != NULL)
194 g_option_context_free (priv->context);
Mario Limonciello32241f42019-01-24 10:12:41 -0600195 g_free (priv->current_message);
Richard Hughesb5976832018-05-18 10:02:09 +0100196 g_free (priv);
197}
198
199#pragma clang diagnostic push
200#pragma clang diagnostic ignored "-Wunused-function"
201G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free)
202#pragma clang diagnostic pop
203
Richard Hughes98ca9932018-05-18 10:24:07 +0100204
205static void
206fu_main_engine_device_added_cb (FuEngine *engine,
207 FuDevice *device,
208 FuUtilPrivate *priv)
209{
210 g_autofree gchar *tmp = fu_device_to_string (device);
211 g_debug ("ADDED:\n%s", tmp);
212}
213
214static void
215fu_main_engine_device_removed_cb (FuEngine *engine,
216 FuDevice *device,
217 FuUtilPrivate *priv)
218{
219 g_autofree gchar *tmp = fu_device_to_string (device);
220 g_debug ("REMOVED:\n%s", tmp);
221}
222
223static void
Richard Hughes98ca9932018-05-18 10:24:07 +0100224fu_main_engine_status_changed_cb (FuEngine *engine,
225 FwupdStatus status,
226 FuUtilPrivate *priv)
227{
228 fu_progressbar_update (priv->progressbar, status, 0);
229}
230
231static void
232fu_main_engine_percentage_changed_cb (FuEngine *engine,
233 guint percentage,
234 FuUtilPrivate *priv)
235{
236 fu_progressbar_update (priv->progressbar, FWUPD_STATUS_UNKNOWN, percentage);
237}
238
239static gboolean
240fu_util_watch (FuUtilPrivate *priv, gchar **values, GError **error)
241{
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000242 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100243 return FALSE;
244 g_main_loop_run (priv->loop);
245 return TRUE;
246}
247
Richard Hughes8c71a3f2018-05-22 19:19:52 +0100248static gint
249fu_util_plugin_name_sort_cb (FuPlugin **item1, FuPlugin **item2)
250{
251 return fu_plugin_name_compare (*item1, *item2);
252}
253
254static gboolean
255fu_util_get_plugins (FuUtilPrivate *priv, gchar **values, GError **error)
256{
257 GPtrArray *plugins;
258 guint cnt = 0;
259
260 /* load engine */
261 if (!fu_engine_load_plugins (priv->engine, error))
262 return FALSE;
263
264 /* print */
265 plugins = fu_engine_get_plugins (priv->engine);
266 g_ptr_array_sort (plugins, (GCompareFunc) fu_util_plugin_name_sort_cb);
267 for (guint i = 0; i < plugins->len; i++) {
268 FuPlugin *plugin = g_ptr_array_index (plugins, i);
269 if (!fu_plugin_get_enabled (plugin))
270 continue;
271 g_print ("%s\n", fu_plugin_get_name (plugin));
272 cnt++;
273 }
274 if (cnt == 0) {
275 /* TRANSLATORS: nothing found */
276 g_print ("%s\n", _("No plugins found"));
277 return TRUE;
278 }
279
280 return TRUE;
281}
282
Richard Hughes98ca9932018-05-18 10:24:07 +0100283static gboolean
Richard Hughes747f5702019-08-06 14:27:26 +0100284fu_util_filter_device (FuUtilPrivate *priv, FwupdDevice *dev)
285{
286 if (priv->filter_include != FWUPD_DEVICE_FLAG_NONE) {
287 if (!fwupd_device_has_flag (dev, priv->filter_include))
288 return FALSE;
289 }
290 if (priv->filter_exclude != FWUPD_DEVICE_FLAG_NONE) {
291 if (fwupd_device_has_flag (dev, priv->filter_exclude))
292 return FALSE;
293 }
294 return TRUE;
295}
296
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500297static gchar *
298fu_util_get_tree_title (FuUtilPrivate *priv)
299{
300 return g_strdup (fu_engine_get_host_product (priv->engine));
301}
302
Richard Hughes747f5702019-08-06 14:27:26 +0100303static gboolean
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600304fu_util_get_updates (FuUtilPrivate *priv, gchar **values, GError **error)
305{
306 g_autoptr(GPtrArray) devices = NULL;
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500307 g_autoptr(GNode) root = g_node_new (NULL);
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500308 g_autofree gchar *title = fu_util_get_tree_title (priv);
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600309
310 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000311 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600312 return FALSE;
313
314 /* get devices from daemon */
315 devices = fu_engine_get_devices (priv->engine, error);
316 if (devices == NULL)
317 return FALSE;
318 for (guint i = 0; i < devices->len; i++) {
319 FwupdDevice *dev = g_ptr_array_index (devices, i);
320 g_autoptr(GPtrArray) rels = NULL;
321 g_autoptr(GError) error_local = NULL;
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500322 GNode *child;
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600323
Richard Hughes747f5702019-08-06 14:27:26 +0100324 /* not going to have results, so save a engine round-trip */
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600325 if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED))
326 continue;
Richard Hughes747f5702019-08-06 14:27:26 +0100327 if (!fu_util_filter_device (priv, dev))
328 continue;
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600329
330 /* get the releases for this device and filter for validity */
331 rels = fu_engine_get_upgrades (priv->engine,
332 fwupd_device_get_id (dev),
333 &error_local);
334 if (rels == NULL) {
335 g_printerr ("%s\n", error_local->message);
336 continue;
337 }
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500338 child = g_node_append_data (root, dev);
339
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600340 for (guint j = 0; j < rels->len; j++) {
341 FwupdRelease *rel = g_ptr_array_index (rels, j);
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500342 g_node_append_data (child, g_object_ref (rel));
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600343 }
344 }
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500345 if (g_node_n_nodes (root, G_TRAVERSE_ALL) > 1)
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500346 fu_util_print_tree (root, title);
Mario Limonciello3143bad2019-02-27 07:31:00 -0600347 /* save the device state for other applications to see */
348 if (!fu_util_save_current_state (priv, error))
349 return FALSE;
350
Mario Limonciello1e35e4c2019-01-28 11:13:02 -0600351 /* success */
352 return TRUE;
353}
354
355static gboolean
Mario Limonciello716ab272018-05-29 12:34:37 -0500356fu_util_get_details (FuUtilPrivate *priv, gchar **values, GError **error)
357{
358 g_autoptr(GPtrArray) array = NULL;
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500359 g_autoptr(GNode) root = g_node_new (NULL);
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500360 g_autofree gchar *title = fu_util_get_tree_title (priv);
Mario Limonciello716ab272018-05-29 12:34:37 -0500361 gint fd;
362
363 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000364 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Mario Limonciello716ab272018-05-29 12:34:37 -0500365 return FALSE;
366
367 /* check args */
368 if (g_strv_length (values) != 1) {
369 g_set_error_literal (error,
370 FWUPD_ERROR,
371 FWUPD_ERROR_INVALID_ARGS,
372 "Invalid arguments");
373 return FALSE;
374 }
375
376 /* open file */
377 fd = open (values[0], O_RDONLY);
378 if (fd < 0) {
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500379 fu_util_maybe_prefix_sandbox_error (values[0], error);
Mario Limonciello716ab272018-05-29 12:34:37 -0500380 g_set_error (error,
381 FWUPD_ERROR,
382 FWUPD_ERROR_INVALID_FILE,
383 "failed to open %s",
384 values[0]);
385 return FALSE;
386 }
387 array = fu_engine_get_details (priv->engine, fd, error);
388 close (fd);
389
390 if (array == NULL)
391 return FALSE;
392 for (guint i = 0; i < array->len; i++) {
393 FwupdDevice *dev = g_ptr_array_index (array, i);
Richard Hughes747f5702019-08-06 14:27:26 +0100394 if (!fu_util_filter_device (priv, dev))
395 continue;
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500396 g_node_append_data (root, dev);
Mario Limonciello716ab272018-05-29 12:34:37 -0500397 }
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500398 fu_util_print_tree (root, title);
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500399
Mario Limonciello716ab272018-05-29 12:34:37 -0500400 return TRUE;
401}
402
403static gboolean
Richard Hughes747f5702019-08-06 14:27:26 +0100404fu_util_get_device_flags (FuUtilPrivate *priv, gchar **values, GError **error)
405{
406 g_autoptr(GString) str = g_string_new (NULL);
407
408 for (FwupdDeviceFlags i = FWUPD_DEVICE_FLAG_INTERNAL; i < FWUPD_DEVICE_FLAG_UNKNOWN; i<<=1) {
409 const gchar *tmp = fwupd_device_flag_to_string (i);
410 if (tmp == NULL)
411 break;
412 if (i != FWUPD_DEVICE_FLAG_INTERNAL)
413 g_string_append (str, " ");
414 g_string_append (str, tmp);
415 g_string_append (str, " ~");
416 g_string_append (str, tmp);
417 }
418 g_print ("%s\n", str->str);
419
420 return TRUE;
421}
422
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500423static void
Richard Hughes0d1577e2018-05-29 13:59:06 +0100424fu_util_build_device_tree (FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FuDevice *dev)
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500425{
426 for (guint i = 0; i < devs->len; i++) {
Richard Hughes0d1577e2018-05-29 13:59:06 +0100427 FuDevice *dev_tmp = g_ptr_array_index (devs, i);
Richard Hughes747f5702019-08-06 14:27:26 +0100428 if (!fu_util_filter_device (priv, FWUPD_DEVICE (dev_tmp)))
429 continue;
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500430 if (!priv->show_all_devices &&
431 !fu_util_is_interesting_device (FWUPD_DEVICE (dev_tmp)))
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500432 continue;
Richard Hughes0d1577e2018-05-29 13:59:06 +0100433 if (fu_device_get_parent (dev_tmp) == dev) {
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500434 GNode *child = g_node_append_data (root, dev_tmp);
435 fu_util_build_device_tree (priv, child, devs, dev_tmp);
436 }
437 }
438}
439
440static gboolean
Mario Limonciello1a9127d2019-08-27 11:32:51 +0100441fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error)
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500442{
443 g_autoptr(GNode) root = g_node_new (NULL);
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500444 g_autofree gchar *title = fu_util_get_tree_title (priv);
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500445 g_autoptr(GPtrArray) devs = NULL;
446
447 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000448 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500449 return FALSE;
450
451 /* print */
452 devs = fu_engine_get_devices (priv->engine, error);
453 if (devs == NULL)
454 return FALSE;
455
456 /* print */
457 if (devs->len == 0) {
458 /* TRANSLATORS: nothing attached that can be upgraded */
459 g_print ("%s\n", _("No hardware detected with firmware update capability"));
460 return TRUE;
461 }
462 fu_util_build_device_tree (priv, root, devs, NULL);
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500463 fu_util_print_tree (root, title);
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500464
Mario Limonciello1a9127d2019-08-27 11:32:51 +0100465 /* save the device state for other applications to see */
466 return fu_util_save_current_state (priv, error);
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500467}
468
Richard Hughes98ca9932018-05-18 10:24:07 +0100469static FuDevice *
470fu_util_prompt_for_device (FuUtilPrivate *priv, GError **error)
471{
472 FuDevice *dev;
473 guint idx;
474 g_autoptr(GPtrArray) devices = NULL;
Richard Hughes747f5702019-08-06 14:27:26 +0100475 g_autoptr(GPtrArray) devices_filtered = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +0100476
477 /* get devices from daemon */
478 devices = fu_engine_get_devices (priv->engine, error);
479 if (devices == NULL)
480 return NULL;
481
Richard Hughes747f5702019-08-06 14:27:26 +0100482 /* filter results */
483 devices_filtered = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
484 for (guint i = 0; i < devices->len; i++) {
485 dev = g_ptr_array_index (devices, i);
486 if (!fu_util_filter_device (priv, FWUPD_DEVICE (dev)))
487 continue;
488 if (!fwupd_device_has_flag (FWUPD_DEVICE (dev), FWUPD_DEVICE_FLAG_SUPPORTED))
489 continue;
490 g_ptr_array_add (devices_filtered, g_object_ref (dev));
491 }
492
493 /* nothing */
494 if (devices_filtered->len == 0) {
495 g_set_error_literal (error,
496 FWUPD_ERROR,
497 FWUPD_ERROR_NOTHING_TO_DO,
498 "No supported devices");
499 return NULL;
500 }
501
Richard Hughes98ca9932018-05-18 10:24:07 +0100502 /* exactly one */
Richard Hughes747f5702019-08-06 14:27:26 +0100503 if (devices_filtered->len == 1) {
504 dev = g_ptr_array_index (devices_filtered, 0);
Richard Hughes98ca9932018-05-18 10:24:07 +0100505 return g_object_ref (dev);
506 }
507
508 /* TRANSLATORS: get interactive prompt */
509 g_print ("%s\n", _("Choose a device:"));
510 /* TRANSLATORS: this is to abort the interactive prompt */
511 g_print ("0.\t%s\n", _("Cancel"));
Richard Hughes747f5702019-08-06 14:27:26 +0100512 for (guint i = 0; i < devices_filtered->len; i++) {
513 dev = g_ptr_array_index (devices_filtered, i);
Richard Hughes98ca9932018-05-18 10:24:07 +0100514 g_print ("%u.\t%s (%s)\n",
515 i + 1,
516 fu_device_get_id (dev),
517 fu_device_get_name (dev));
518 }
Richard Hughes747f5702019-08-06 14:27:26 +0100519 idx = fu_util_prompt_for_number (devices_filtered->len);
Richard Hughes98ca9932018-05-18 10:24:07 +0100520 if (idx == 0) {
521 g_set_error_literal (error,
522 FWUPD_ERROR,
523 FWUPD_ERROR_NOTHING_TO_DO,
524 "Request canceled");
525 return NULL;
526 }
Richard Hughes747f5702019-08-06 14:27:26 +0100527 dev = g_ptr_array_index (devices_filtered, idx - 1);
Richard Hughes98ca9932018-05-18 10:24:07 +0100528 return g_object_ref (dev);
529}
530
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500531static void
Mario Limonciello3f243a92019-01-21 22:05:23 -0600532fu_util_update_device_changed_cb (FwupdClient *client,
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500533 FwupdDevice *device,
534 FuUtilPrivate *priv)
535{
536 g_autofree gchar *str = NULL;
537
Richard Hughes809abea2019-03-23 11:06:18 +0000538 /* allowed to set whenever the device has changed */
539 if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN))
540 priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN;
541 if (fwupd_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT))
542 priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT;
543
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500544 /* same as last time, so ignore */
545 if (priv->current_device != NULL &&
546 fwupd_device_compare (priv->current_device, device) == 0)
547 return;
548
549 /* show message in progressbar */
Mario Limonciello3f243a92019-01-21 22:05:23 -0600550 if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) {
551 /* TRANSLATORS: %1 is a device name */
552 str = g_strdup_printf (_("Updating %s…"),
553 fwupd_device_get_name (device));
554 fu_progressbar_set_title (priv->progressbar, str);
555 } else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) {
556 /* TRANSLATORS: %1 is a device name */
557 str = g_strdup_printf (_("Installing on %s…"),
558 fwupd_device_get_name (device));
559 fu_progressbar_set_title (priv->progressbar, str);
560 } else {
561 g_warning ("no FuUtilOperation set");
562 }
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500563 g_set_object (&priv->current_device, device);
Mario Limonciello3f243a92019-01-21 22:05:23 -0600564
Mario Limonciello32241f42019-01-24 10:12:41 -0600565 if (priv->current_message == NULL) {
566 const gchar *tmp = fwupd_device_get_update_message (priv->current_device);
567 if (tmp != NULL)
568 priv->current_message = g_strdup (tmp);
569 }
570}
571
572static void
573fu_util_display_current_message (FuUtilPrivate *priv)
574{
575 if (priv->current_message == NULL)
576 return;
577 g_print ("%s\n", priv->current_message);
578 g_clear_pointer (&priv->current_message, g_free);
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500579}
580
Richard Hughes98ca9932018-05-18 10:24:07 +0100581static gboolean
582fu_util_install_blob (FuUtilPrivate *priv, gchar **values, GError **error)
583{
584 g_autoptr(FuDevice) device = NULL;
585 g_autoptr(GBytes) blob_fw = NULL;
586
587 /* invalid args */
588 if (g_strv_length (values) == 0) {
589 g_set_error_literal (error,
590 FWUPD_ERROR,
591 FWUPD_ERROR_INVALID_ARGS,
592 "Invalid arguments");
593 return FALSE;
594 }
595
596 /* parse blob */
597 blob_fw = fu_common_get_contents_bytes (values[0], error);
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500598 if (blob_fw == NULL) {
599 fu_util_maybe_prefix_sandbox_error (values[0], error);
Richard Hughes98ca9932018-05-18 10:24:07 +0100600 return FALSE;
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500601 }
Richard Hughes98ca9932018-05-18 10:24:07 +0100602
603 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000604 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100605 return FALSE;
606
607 /* get device */
608 if (g_strv_length (values) >= 2) {
609 device = fu_engine_get_device (priv->engine, values[1], error);
610 if (device == NULL)
611 return FALSE;
612 } else {
613 device = fu_util_prompt_for_device (priv, error);
614 if (device == NULL)
615 return FALSE;
616 }
617
Mario Limonciello3f243a92019-01-21 22:05:23 -0600618 priv->current_operation = FU_UTIL_OPERATION_INSTALL;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500619 g_signal_connect (priv->engine, "device-changed",
Mario Limonciello3f243a92019-01-21 22:05:23 -0600620 G_CALLBACK (fu_util_update_device_changed_cb), priv);
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500621
Richard Hughes98ca9932018-05-18 10:24:07 +0100622 /* write bare firmware */
Mario Limonciello53ce25d2019-02-01 16:09:03 +0000623 if (priv->prepare_blob) {
624 g_autoptr(GPtrArray) devices = NULL;
625 devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
626 g_ptr_array_add (devices, g_object_ref (device));
627 if (!fu_engine_composite_prepare (priv->engine, devices, error)) {
628 g_prefix_error (error, "failed to prepare composite action: ");
629 return FALSE;
630 }
631 }
Mario Limonciello035818b2019-03-26 11:12:16 -0500632 priv->flags = FWUPD_INSTALL_FLAG_NO_HISTORY;
Richard Hughes84af6e72019-02-01 18:19:41 +0000633 if (!fu_engine_install_blob (priv->engine, device, blob_fw, priv->flags, error))
Mario Limonciello3f243a92019-01-21 22:05:23 -0600634 return FALSE;
Mario Limonciello53ce25d2019-02-01 16:09:03 +0000635 if (priv->cleanup_blob) {
636 g_autoptr(FuDevice) device_new = NULL;
637 g_autoptr(GError) error_local = NULL;
638
639 /* get the possibly new device from the old ID */
640 device_new = fu_engine_get_device (priv->engine,
641 fu_device_get_id (device),
642 &error_local);
643 if (device_new == NULL) {
644 g_debug ("failed to find new device: %s",
645 error_local->message);
646 } else {
Mario Limonciellobb3fa5e2019-02-07 21:13:02 -0600647 g_autoptr(GPtrArray) devices_new = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
Mario Limonciello53ce25d2019-02-01 16:09:03 +0000648 g_ptr_array_add (devices_new, g_steal_pointer (&device_new));
649 if (!fu_engine_composite_cleanup (priv->engine, devices_new, error)) {
650 g_prefix_error (error, "failed to cleanup composite action: ");
651 return FALSE;
652 }
653 }
654 }
Mario Limonciello3f243a92019-01-21 22:05:23 -0600655
Mario Limonciello32241f42019-01-24 10:12:41 -0600656 fu_util_display_current_message (priv);
657
Mario Limonciello3f243a92019-01-21 22:05:23 -0600658 /* success */
659 return fu_util_prompt_complete (priv->completion_flags, TRUE, error);
Richard Hughes98ca9932018-05-18 10:24:07 +0100660}
661
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100662static gint
663fu_util_install_task_sort_cb (gconstpointer a, gconstpointer b)
664{
665 FuInstallTask *task1 = *((FuInstallTask **) a);
666 FuInstallTask *task2 = *((FuInstallTask **) b);
667 return fu_install_task_compare (task1, task2);
668}
669
670static gboolean
Richard Hughes3d178be2018-08-30 11:14:24 +0100671fu_util_download_out_of_process (const gchar *uri, const gchar *fn, GError **error)
672{
673 const gchar *argv[][5] = { { "wget", uri, "-o", fn, NULL },
674 { "curl", uri, "--output", fn, NULL },
675 { NULL } };
676 for (guint i = 0; argv[i][0] != NULL; i++) {
677 g_autoptr(GError) error_local = NULL;
678 if (!fu_common_find_program_in_path (argv[i][0], &error_local)) {
679 g_debug ("%s", error_local->message);
680 continue;
681 }
Richard Hughesb768e4d2019-02-26 13:55:18 +0000682 return fu_common_spawn_sync (argv[i], NULL, NULL, 0, NULL, error);
Richard Hughes3d178be2018-08-30 11:14:24 +0100683 }
684 g_set_error_literal (error,
685 FWUPD_ERROR,
686 FWUPD_ERROR_NOT_FOUND,
687 "no supported out-of-process downloaders found");
688 return FALSE;
689}
690
691static gchar *
692fu_util_download_if_required (FuUtilPrivate *priv, const gchar *perhapsfn, GError **error)
693{
694 g_autofree gchar *filename = NULL;
695 g_autoptr(SoupURI) uri = NULL;
696
697 /* a local file */
698 uri = soup_uri_new (perhapsfn);
699 if (uri == NULL)
700 return g_strdup (perhapsfn);
701
702 /* download the firmware to a cachedir */
703 filename = fu_util_get_user_cache_path (perhapsfn);
704 if (!fu_common_mkdir_parent (filename, error))
705 return NULL;
706 if (!fu_util_download_out_of_process (perhapsfn, filename, error))
707 return NULL;
708 return g_steal_pointer (&filename);
709}
710
711static gboolean
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100712fu_util_install (FuUtilPrivate *priv, gchar **values, GError **error)
713{
Richard Hughes3d178be2018-08-30 11:14:24 +0100714 g_autofree gchar *filename = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100715 g_autoptr(GBytes) blob_cab = NULL;
Richard Hughes481aa2a2018-09-18 20:51:46 +0100716 g_autoptr(GPtrArray) components = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100717 g_autoptr(GPtrArray) devices_possible = NULL;
718 g_autoptr(GPtrArray) errors = NULL;
719 g_autoptr(GPtrArray) install_tasks = NULL;
Richard Hughes481aa2a2018-09-18 20:51:46 +0100720 g_autoptr(XbSilo) silo = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100721
Mario Limonciello8949e892018-05-25 08:03:06 -0500722 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000723 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Mario Limonciello8949e892018-05-25 08:03:06 -0500724 return FALSE;
725
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100726 /* handle both forms */
727 if (g_strv_length (values) == 1) {
728 devices_possible = fu_engine_get_devices (priv->engine, error);
729 if (devices_possible == NULL)
730 return FALSE;
731 } else if (g_strv_length (values) == 2) {
732 FuDevice *device = fu_engine_get_device (priv->engine,
733 values[1],
734 error);
735 if (device == NULL)
736 return FALSE;
737 devices_possible = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
738 g_ptr_array_add (devices_possible, device);
739 } else {
740 g_set_error_literal (error,
741 FWUPD_ERROR,
742 FWUPD_ERROR_INVALID_ARGS,
743 "Invalid arguments");
744 return FALSE;
745 }
746
Richard Hughes3d178be2018-08-30 11:14:24 +0100747 /* download if required */
748 filename = fu_util_download_if_required (priv, values[0], error);
749 if (filename == NULL)
750 return FALSE;
751
Richard Hughes481aa2a2018-09-18 20:51:46 +0100752 /* parse silo */
Richard Hughes3d178be2018-08-30 11:14:24 +0100753 blob_cab = fu_common_get_contents_bytes (filename, error);
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500754 if (blob_cab == NULL) {
Richard Hughes3d178be2018-08-30 11:14:24 +0100755 fu_util_maybe_prefix_sandbox_error (filename, error);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100756 return FALSE;
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500757 }
Richard Hughes481aa2a2018-09-18 20:51:46 +0100758 silo = fu_engine_get_silo_from_blob (priv->engine, blob_cab, error);
759 if (silo == NULL)
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100760 return FALSE;
Mario Limonciello51ddf182019-01-26 00:31:58 -0600761 components = xb_silo_query (silo, "components/component", 0, error);
Richard Hughes481aa2a2018-09-18 20:51:46 +0100762 if (components == NULL)
763 return FALSE;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100764
Richard Hughes481aa2a2018-09-18 20:51:46 +0100765 /* for each component in the silo */
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100766 errors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_error_free);
767 install_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
Richard Hughes481aa2a2018-09-18 20:51:46 +0100768 for (guint i = 0; i < components->len; i++) {
769 XbNode *component = g_ptr_array_index (components, i);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100770
771 /* do any devices pass the requirements */
772 for (guint j = 0; j < devices_possible->len; j++) {
773 FuDevice *device = g_ptr_array_index (devices_possible, j);
774 g_autoptr(FuInstallTask) task = NULL;
775 g_autoptr(GError) error_local = NULL;
776
777 /* is this component valid for the device */
Richard Hughes481aa2a2018-09-18 20:51:46 +0100778 task = fu_install_task_new (device, component);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100779 if (!fu_engine_check_requirements (priv->engine,
Richard Hughes460226a2018-05-21 20:56:21 +0100780 task, priv->flags,
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100781 &error_local)) {
782 g_debug ("requirement on %s:%s failed: %s",
783 fu_device_get_id (device),
Richard Hughes481aa2a2018-09-18 20:51:46 +0100784 xb_node_query_text (component, "id", NULL),
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100785 error_local->message);
786 g_ptr_array_add (errors, g_steal_pointer (&error_local));
787 continue;
788 }
789
Mario Limonciello7a3df4b2019-01-31 10:27:22 -0600790 /* if component should have an update message from CAB */
791 fu_device_incorporate_from_component (device, component);
792
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100793 /* success */
794 g_ptr_array_add (install_tasks, g_steal_pointer (&task));
795 }
796 }
797
798 /* order the install tasks by the device priority */
799 g_ptr_array_sort (install_tasks, fu_util_install_task_sort_cb);
800
801 /* nothing suitable */
802 if (install_tasks->len == 0) {
803 GError *error_tmp = fu_common_error_array_get_best (errors);
804 g_propagate_error (error, error_tmp);
805 return FALSE;
806 }
807
Mario Limonciello3f243a92019-01-21 22:05:23 -0600808 priv->current_operation = FU_UTIL_OPERATION_INSTALL;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500809 g_signal_connect (priv->engine, "device-changed",
Mario Limonciello3f243a92019-01-21 22:05:23 -0600810 G_CALLBACK (fu_util_update_device_changed_cb), priv);
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500811
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100812 /* install all the tasks */
Richard Hughesdbd8c762018-06-15 20:31:40 +0100813 if (!fu_engine_install_tasks (priv->engine, install_tasks, blob_cab, priv->flags, error))
814 return FALSE;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100815
Mario Limonciello32241f42019-01-24 10:12:41 -0600816 fu_util_display_current_message (priv);
817
Mario Limonciello3f243a92019-01-21 22:05:23 -0600818 /* we don't want to ask anything */
819 if (priv->no_reboot_check) {
820 g_debug ("skipping reboot check");
821 return TRUE;
822 }
823
Mario Limonciello3143bad2019-02-27 07:31:00 -0600824 /* save the device state for other applications to see */
825 if (!fu_util_save_current_state (priv, error))
826 return FALSE;
827
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100828 /* success */
Mario Limonciello3f243a92019-01-21 22:05:23 -0600829 return fu_util_prompt_complete (priv->completion_flags, TRUE, error);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100830}
831
Richard Hughes98ca9932018-05-18 10:24:07 +0100832static gboolean
Mario Limonciellofd734852019-08-01 16:41:42 -0500833fu_util_install_release (FuUtilPrivate *priv, FwupdRelease *rel, GError **error)
Mario Limonciello46aaee82019-01-10 12:58:00 -0600834{
Mario Limonciellofd734852019-08-01 16:41:42 -0500835 FwupdRemote *remote;
836 const gchar *remote_id;
837 const gchar *uri_tmp;
838 g_auto(GStrv) argv = NULL;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600839
Mario Limonciellofd734852019-08-01 16:41:42 -0500840 uri_tmp = fwupd_release_get_uri (rel);
841 if (uri_tmp == NULL) {
842 g_set_error_literal (error,
843 FWUPD_ERROR,
844 FWUPD_ERROR_INVALID_FILE,
845 "release missing URI");
846 return FALSE;
847 }
848 remote_id = fwupd_release_get_remote_id (rel);
849 if (remote_id == NULL) {
850 g_set_error (error,
851 FWUPD_ERROR,
852 FWUPD_ERROR_INVALID_FILE,
853 "failed to find remote for %s",
854 uri_tmp);
Richard Hughes747f5702019-08-06 14:27:26 +0100855 return FALSE;
Mario Limonciellofd734852019-08-01 16:41:42 -0500856 }
857
858 remote = fu_engine_get_remote_by_id (priv->engine,
859 remote_id,
860 error);
861 if (remote == NULL)
Mario Limonciello46aaee82019-01-10 12:58:00 -0600862 return FALSE;
863
Mario Limonciellofd734852019-08-01 16:41:42 -0500864 argv = g_new0 (gchar *, 2);
865 /* local remotes have the firmware already */
866 if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_LOCAL) {
867 const gchar *fn_cache = fwupd_remote_get_filename_cache (remote);
868 g_autofree gchar *path = g_path_get_dirname (fn_cache);
869 argv[0] = g_build_filename (path, uri_tmp, NULL);
870 } else if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_DIRECTORY) {
871 argv[0] = g_strdup (uri_tmp + 7);
872 /* web remote, fu_util_install will download file */
873 } else {
874 argv[0] = fwupd_remote_build_firmware_uri (remote, uri_tmp, error);
875 }
876 return fu_util_install (priv, argv, error);
877}
878
879static gboolean
880fu_util_update_all (FuUtilPrivate *priv, GError **error)
881{
882 g_autoptr(GPtrArray) devices = NULL;
Mario Limonciello3f243a92019-01-21 22:05:23 -0600883
Mario Limonciello46aaee82019-01-10 12:58:00 -0600884 devices = fu_engine_get_devices (priv->engine, error);
Mario Limonciello387bda42019-02-07 14:20:22 +0000885 if (devices == NULL)
886 return FALSE;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600887 for (guint i = 0; i < devices->len; i++) {
888 FwupdDevice *dev = g_ptr_array_index (devices, i);
889 FwupdRelease *rel;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600890 const gchar *device_id;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600891 g_autoptr(GPtrArray) rels = NULL;
892 g_autoptr(GError) error_local = NULL;
893
894 if (!fu_util_is_interesting_device (dev))
895 continue;
896 /* only show stuff that has metadata available */
897 if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED))
898 continue;
Richard Hughes747f5702019-08-06 14:27:26 +0100899 if (!fu_util_filter_device (priv, dev))
900 continue;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600901
902 device_id = fu_device_get_id (dev);
903 rels = fu_engine_get_upgrades (priv->engine, device_id, &error_local);
904 if (rels == NULL) {
905 g_printerr ("%s\n", error_local->message);
906 continue;
907 }
908
909 rel = g_ptr_array_index (rels, 0);
Mario Limonciellofd734852019-08-01 16:41:42 -0500910 if (!fu_util_install_release (priv, rel, &error_local)) {
911 g_printerr ("%s\n", error_local->message);
Richard Hughes747f5702019-08-06 14:27:26 +0100912 continue;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600913 }
Mario Limonciellofd734852019-08-01 16:41:42 -0500914 fu_util_display_current_message (priv);
915 }
916 return TRUE;
917}
918
919static gboolean
920fu_util_update_by_id (FuUtilPrivate *priv, const gchar *device_id, GError **error)
921{
922 FwupdRelease *rel;
923 g_autoptr(FuDevice) dev = NULL;
924 g_autoptr(GPtrArray) rels = NULL;
925
926 /* do not allow a partial device-id */
927 dev = fu_engine_get_device (priv->engine, device_id, error);
928 if (dev == NULL)
929 return FALSE;
930
931 /* get the releases for this device and filter for validity */
932 rels = fu_engine_get_upgrades (priv->engine, device_id, error);
933 if (rels == NULL)
934 return FALSE;
935 rel = g_ptr_array_index (rels, 0);
936 if (!fu_util_install_release (priv, rel, error))
937 return FALSE;
938 fu_util_display_current_message (priv);
939
940 return TRUE;
941}
942
943static gboolean
944fu_util_update (FuUtilPrivate *priv, gchar **values, GError **error)
945{
946 if (g_strv_length (values) > 1) {
947 g_set_error_literal (error,
948 FWUPD_ERROR,
949 FWUPD_ERROR_INVALID_ARGS,
950 "Invalid arguments");
951 return FALSE;
952 }
953
954 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
955 return FALSE;
956
957 priv->current_operation = FU_UTIL_OPERATION_UPDATE;
958 g_signal_connect (priv->engine, "device-changed",
959 G_CALLBACK (fu_util_update_device_changed_cb), priv);
960
961 if (g_strv_length (values) == 1) {
962 if (!fu_util_update_by_id (priv, values[0], error))
963 return FALSE;
964 } else {
965 if (!fu_util_update_all (priv, error))
966 return FALSE;
Mario Limonciello46aaee82019-01-10 12:58:00 -0600967 }
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
Mario Limonciello3143bad2019-02-27 07:31:00 -0600975 /* save the device state for other applications to see */
976 if (!fu_util_save_current_state (priv, error))
977 return FALSE;
978
Mario Limonciello3f243a92019-01-21 22:05:23 -0600979 return fu_util_prompt_complete (priv->completion_flags, TRUE, error);
Mario Limonciello46aaee82019-01-10 12:58:00 -0600980}
981
982static gboolean
Richard Hughes98ca9932018-05-18 10:24:07 +0100983fu_util_detach (FuUtilPrivate *priv, gchar **values, GError **error)
984{
985 g_autoptr(FuDevice) device = NULL;
Richard Hughes2a679cd2018-09-04 21:13:23 +0100986 g_autoptr(FuDeviceLocker) locker = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +0100987
988 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +0000989 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Richard Hughes98ca9932018-05-18 10:24:07 +0100990 return FALSE;
991
992 /* invalid args */
993 if (g_strv_length (values) == 0) {
994 g_set_error_literal (error,
995 FWUPD_ERROR,
996 FWUPD_ERROR_INVALID_ARGS,
997 "Invalid arguments");
998 return FALSE;
999 }
1000
1001 /* get device */
1002 if (g_strv_length (values) >= 1) {
1003 device = fu_engine_get_device (priv->engine, values[0], error);
1004 if (device == NULL)
1005 return FALSE;
1006 } else {
1007 device = fu_util_prompt_for_device (priv, error);
1008 if (device == NULL)
1009 return FALSE;
1010 }
1011
1012 /* run vfunc */
Richard Hughes2a679cd2018-09-04 21:13:23 +01001013 locker = fu_device_locker_new (device, error);
1014 if (locker == NULL)
1015 return FALSE;
Richard Hughes98ca9932018-05-18 10:24:07 +01001016 return fu_device_detach (device, error);
1017}
1018
1019static gboolean
1020fu_util_attach (FuUtilPrivate *priv, gchar **values, GError **error)
1021{
1022 g_autoptr(FuDevice) device = NULL;
Richard Hughes2a679cd2018-09-04 21:13:23 +01001023 g_autoptr(FuDeviceLocker) locker = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +01001024
1025 /* load engine */
Richard Hughesc8cc77c2019-03-07 11:57:24 +00001026 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
Richard Hughes98ca9932018-05-18 10:24:07 +01001027 return FALSE;
1028
1029 /* invalid args */
1030 if (g_strv_length (values) == 0) {
1031 g_set_error_literal (error,
1032 FWUPD_ERROR,
1033 FWUPD_ERROR_INVALID_ARGS,
1034 "Invalid arguments");
1035 return FALSE;
1036 }
1037
1038 /* get device */
1039 if (g_strv_length (values) >= 1) {
1040 device = fu_engine_get_device (priv->engine, values[0], error);
1041 if (device == NULL)
1042 return FALSE;
1043 } else {
1044 device = fu_util_prompt_for_device (priv, error);
1045 if (device == NULL)
1046 return FALSE;
1047 }
1048
1049 /* run vfunc */
Richard Hughes2a679cd2018-09-04 21:13:23 +01001050 locker = fu_device_locker_new (device, error);
1051 if (locker == NULL)
1052 return FALSE;
Richard Hughes98ca9932018-05-18 10:24:07 +01001053 return fu_device_attach (device, error);
1054}
1055
Richard Hughes1d894f12018-08-31 13:05:51 +01001056static gboolean
Mario Limonciello96a0dd52019-02-25 13:50:03 -06001057fu_util_activate (FuUtilPrivate *priv, gchar **values, GError **error)
1058{
1059 gboolean has_pending = FALSE;
1060 g_autoptr(FuHistory) history = fu_history_new ();
1061 g_autoptr(GPtrArray) devices = NULL;
1062
1063 /* check the history database before starting the daemon */
1064 if (g_strv_length (values) == 0) {
1065 devices = fu_history_get_devices (history, error);
1066 if (devices == NULL)
1067 return FALSE;
1068 } else if (g_strv_length (values) == 1) {
1069 FuDevice *device;
1070 device = fu_history_get_device_by_id (history, values[0], error);
1071 if (device == NULL)
1072 return FALSE;
1073 devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
1074 g_ptr_array_add (devices, device);
1075 } else {
1076 g_set_error_literal (error,
1077 FWUPD_ERROR,
1078 FWUPD_ERROR_INVALID_ARGS,
1079 "Invalid arguments");
1080 return FALSE;
1081 }
1082
1083 /* nothing to do */
1084 for (guint i = 0; i < devices->len; i++) {
1085 FuDevice *dev = g_ptr_array_index (devices, i);
1086 if (fu_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) {
1087 fu_engine_add_plugin_filter (priv->engine,
1088 fu_device_get_plugin (dev));
1089 has_pending = TRUE;
1090 }
1091 }
1092 if (!has_pending) {
1093 g_set_error_literal (error,
1094 FWUPD_ERROR,
1095 FWUPD_ERROR_NOTHING_TO_DO,
1096 "No firmware to activate");
1097 return FALSE;
1098
1099 }
1100
1101 /* load engine */
Richard Hughes88dc0f42019-03-07 11:58:11 +00001102 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_READONLY_FS, error))
Mario Limonciello96a0dd52019-02-25 13:50:03 -06001103 return FALSE;
1104
1105 /* activate anything with _NEEDS_ACTIVATION */
1106 for (guint i = 0; i < devices->len; i++) {
1107 FuDevice *device = g_ptr_array_index (devices, i);
Richard Hughes747f5702019-08-06 14:27:26 +01001108 if (!fu_util_filter_device (priv, FWUPD_DEVICE (device)))
1109 continue;
Mario Limonciello96a0dd52019-02-25 13:50:03 -06001110 if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION))
1111 continue;
1112 /* TRANSLATORS: shown when shutting down to switch to the new version */
1113 g_print ("%s %s…\n", _("Activating firmware update"), fu_device_get_name (device));
1114 if (!fu_engine_activate (priv->engine, fu_device_get_id (device), error))
1115 return FALSE;
1116 }
1117
1118 return TRUE;
1119}
1120
1121static gboolean
Richard Hughes1d894f12018-08-31 13:05:51 +01001122fu_util_hwids (FuUtilPrivate *priv, gchar **values, GError **error)
1123{
1124 g_autoptr(FuSmbios) smbios = fu_smbios_new ();
1125 g_autoptr(FuHwids) hwids = fu_hwids_new ();
1126 const gchar *hwid_keys[] = {
1127 FU_HWIDS_KEY_BIOS_VENDOR,
1128 FU_HWIDS_KEY_BIOS_VERSION,
1129 FU_HWIDS_KEY_BIOS_MAJOR_RELEASE,
1130 FU_HWIDS_KEY_BIOS_MINOR_RELEASE,
1131 FU_HWIDS_KEY_MANUFACTURER,
1132 FU_HWIDS_KEY_FAMILY,
1133 FU_HWIDS_KEY_PRODUCT_NAME,
1134 FU_HWIDS_KEY_PRODUCT_SKU,
1135 FU_HWIDS_KEY_ENCLOSURE_KIND,
1136 FU_HWIDS_KEY_BASEBOARD_MANUFACTURER,
1137 FU_HWIDS_KEY_BASEBOARD_PRODUCT,
1138 NULL };
1139
1140 /* read DMI data */
1141 if (g_strv_length (values) == 0) {
1142 if (!fu_smbios_setup (smbios, error))
1143 return FALSE;
1144 } else if (g_strv_length (values) == 1) {
1145 if (!fu_smbios_setup_from_file (smbios, values[0], error))
1146 return FALSE;
1147 } else {
1148 g_set_error_literal (error,
1149 FWUPD_ERROR,
1150 FWUPD_ERROR_INVALID_ARGS,
1151 "Invalid arguments");
1152 return FALSE;
1153 }
1154 if (!fu_hwids_setup (hwids, smbios, error))
1155 return FALSE;
1156
1157 /* show debug output */
1158 g_print ("Computer Information\n");
1159 g_print ("--------------------\n");
1160 for (guint i = 0; hwid_keys[i] != NULL; i++) {
1161 const gchar *tmp = fu_hwids_get_value (hwids, hwid_keys[i]);
1162 if (tmp == NULL)
1163 continue;
1164 if (g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MAJOR_RELEASE) == 0 ||
1165 g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MINOR_RELEASE) == 0) {
1166 guint64 val = g_ascii_strtoull (tmp, NULL, 16);
1167 g_print ("%s: %" G_GUINT64_FORMAT "\n", hwid_keys[i], val);
1168 } else {
1169 g_print ("%s: %s\n", hwid_keys[i], tmp);
1170 }
1171 }
1172
1173 /* show GUIDs */
1174 g_print ("\nHardware IDs\n");
1175 g_print ("------------\n");
1176 for (guint i = 0; i < 15; i++) {
1177 const gchar *keys = NULL;
1178 g_autofree gchar *guid = NULL;
1179 g_autofree gchar *key = NULL;
1180 g_autofree gchar *keys_str = NULL;
1181 g_auto(GStrv) keysv = NULL;
1182 g_autoptr(GError) error_local = NULL;
1183
1184 /* get the GUID */
1185 key = g_strdup_printf ("HardwareID-%u", i);
1186 keys = fu_hwids_get_replace_keys (hwids, key);
1187 guid = fu_hwids_get_guid (hwids, key, &error_local);
1188 if (guid == NULL) {
1189 g_print ("%s\n", error_local->message);
1190 continue;
1191 }
1192
1193 /* show what makes up the GUID */
1194 keysv = g_strsplit (keys, "&", -1);
1195 keys_str = g_strjoinv (" + ", keysv);
1196 g_print ("{%s} <- %s\n", guid, keys_str);
1197 }
1198
1199 return TRUE;
1200}
1201
Mario Limonciellof6d01b12018-10-18 12:57:10 -05001202static gboolean
1203fu_util_firmware_builder (FuUtilPrivate *priv, gchar **values, GError **error)
1204{
1205 const gchar *script_fn = "startup.sh";
1206 const gchar *output_fn = "firmware.bin";
1207 g_autoptr(GBytes) archive_blob = NULL;
1208 g_autoptr(GBytes) firmware_blob = NULL;
1209 if (g_strv_length (values) < 2) {
1210 g_set_error_literal (error,
1211 FWUPD_ERROR,
1212 FWUPD_ERROR_INVALID_ARGS,
1213 "Invalid arguments");
1214 return FALSE;
1215 }
1216 archive_blob = fu_common_get_contents_bytes (values[0], error);
1217 if (archive_blob == NULL)
1218 return FALSE;
1219 if (g_strv_length (values) > 2)
1220 script_fn = values[2];
1221 if (g_strv_length (values) > 3)
1222 output_fn = values[3];
1223 firmware_blob = fu_common_firmware_builder (archive_blob, script_fn, output_fn, error);
1224 if (firmware_blob == NULL)
1225 return FALSE;
1226 return fu_common_set_contents_bytes (values[1], firmware_blob, error);
1227}
1228
Richard Hughes3d607622019-03-07 16:59:27 +00001229static gboolean
1230fu_util_self_sign (FuUtilPrivate *priv, gchar **values, GError **error)
1231{
1232 g_autofree gchar *sig = NULL;
1233
1234 /* check args */
1235 if (g_strv_length (values) != 1) {
1236 g_set_error_literal (error,
1237 FWUPD_ERROR,
1238 FWUPD_ERROR_INVALID_ARGS,
1239 "Invalid arguments: value expected");
1240 return FALSE;
1241 }
1242
1243 /* start engine */
1244 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
1245 return FALSE;
1246 sig = fu_engine_self_sign (priv->engine, values[0],
1247 FU_KEYRING_SIGN_FLAG_ADD_TIMESTAMP |
1248 FU_KEYRING_SIGN_FLAG_ADD_CERT, error);
1249 if (sig == NULL)
1250 return FALSE;
1251 g_print ("%s\n", sig);
1252 return TRUE;
1253}
1254
Mario Limonciello62f84862018-10-18 13:15:23 -05001255static void
1256fu_util_device_added_cb (FwupdClient *client,
1257 FwupdDevice *device,
1258 gpointer user_data)
1259{
Mario Limonciellofee8f492019-08-18 12:16:07 -05001260 g_autofree gchar *tmp = fu_util_device_to_string (device, 0);
Mario Limonciello62f84862018-10-18 13:15:23 -05001261 /* TRANSLATORS: this is when a device is hotplugged */
1262 g_print ("%s\n%s", _("Device added:"), tmp);
1263}
1264
1265static void
1266fu_util_device_removed_cb (FwupdClient *client,
1267 FwupdDevice *device,
1268 gpointer user_data)
1269{
Mario Limonciellofee8f492019-08-18 12:16:07 -05001270 g_autofree gchar *tmp = fu_util_device_to_string (device, 0);
Mario Limonciello62f84862018-10-18 13:15:23 -05001271 /* TRANSLATORS: this is when a device is hotplugged */
1272 g_print ("%s\n%s", _("Device removed:"), tmp);
1273}
1274
1275static void
1276fu_util_device_changed_cb (FwupdClient *client,
1277 FwupdDevice *device,
1278 gpointer user_data)
1279{
Mario Limonciellofee8f492019-08-18 12:16:07 -05001280 g_autofree gchar *tmp = fu_util_device_to_string (device, 0);
Mario Limonciello62f84862018-10-18 13:15:23 -05001281 /* TRANSLATORS: this is when a device has been updated */
1282 g_print ("%s\n%s", _("Device changed:"), tmp);
1283}
1284
1285static void
1286fu_util_changed_cb (FwupdClient *client, gpointer user_data)
1287{
1288 /* TRANSLATORS: this is when the daemon state changes */
1289 g_print ("%s\n", _("Changed"));
1290}
1291
1292static gboolean
1293fu_util_monitor (FuUtilPrivate *priv, gchar **values, GError **error)
1294{
1295 g_autoptr(FwupdClient) client = fwupd_client_new ();
1296
1297 /* get all the devices */
1298 if (!fwupd_client_connect (client, priv->cancellable, error))
1299 return FALSE;
1300
1301 /* watch for any hotplugged device */
1302 g_signal_connect (client, "changed",
1303 G_CALLBACK (fu_util_changed_cb), priv);
1304 g_signal_connect (client, "device-added",
1305 G_CALLBACK (fu_util_device_added_cb), priv);
1306 g_signal_connect (client, "device-removed",
1307 G_CALLBACK (fu_util_device_removed_cb), priv);
1308 g_signal_connect (client, "device-changed",
1309 G_CALLBACK (fu_util_device_changed_cb), priv);
1310 g_signal_connect (priv->cancellable, "cancelled",
1311 G_CALLBACK (fu_util_cancelled_cb), priv);
1312 g_main_loop_run (priv->loop);
1313 return TRUE;
1314}
1315
Richard Hughes15684492019-03-15 16:27:50 +00001316static gboolean
1317fu_util_verify_update (FuUtilPrivate *priv, gchar **values, GError **error)
1318{
1319 g_autofree gchar *str = NULL;
1320 g_autoptr(FuDevice) dev = NULL;
1321
1322 /* load engine */
1323 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
1324 return FALSE;
1325
1326 /* get device */
1327 if (g_strv_length (values) == 1) {
1328 dev = fu_engine_get_device (priv->engine, values[1], error);
1329 if (dev == NULL)
1330 return FALSE;
1331 } else {
1332 dev = fu_util_prompt_for_device (priv, error);
1333 if (dev == NULL)
1334 return FALSE;
1335 }
1336
1337 /* add checksums */
1338 if (!fu_engine_verify_update (priv->engine, fu_device_get_id (dev), error))
1339 return FALSE;
1340
1341 /* show checksums */
1342 str = fu_device_to_string (dev);
1343 g_print ("%s\n", str);
1344 return TRUE;
1345}
1346
Mario Limonciellofe593942019-04-03 13:48:52 -05001347static gboolean
1348fu_util_get_history (FuUtilPrivate *priv, gchar **values, GError **error)
1349{
1350 g_autoptr(GPtrArray) devices = NULL;
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001351 g_autoptr(GNode) root = g_node_new (NULL);
Mario Limonciello20cc9ee2019-09-05 07:27:26 -05001352 g_autofree gchar *title = fu_util_get_tree_title (priv);
Mario Limonciellofe593942019-04-03 13:48:52 -05001353
1354 /* load engine */
1355 if (!fu_util_start_engine (priv, FU_ENGINE_LOAD_FLAG_NONE, error))
1356 return FALSE;
1357
1358 /* get all devices from the history database */
1359 devices = fu_engine_get_history (priv->engine, error);
1360 if (devices == NULL)
1361 return FALSE;
1362
1363 /* show each device */
1364 for (guint i = 0; i < devices->len; i++) {
1365 FwupdDevice *dev = g_ptr_array_index (devices, i);
Richard Hughes747f5702019-08-06 14:27:26 +01001366 if (!fu_util_filter_device (priv, dev))
1367 continue;
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001368 g_node_append_data (root, dev);
Mario Limonciellofe593942019-04-03 13:48:52 -05001369 }
Mario Limonciello20cc9ee2019-09-05 07:27:26 -05001370 fu_util_print_tree (root, title);
Mario Limonciellofe593942019-04-03 13:48:52 -05001371
1372 return TRUE;
1373}
1374
Richard Hughesb5976832018-05-18 10:02:09 +01001375int
1376main (int argc, char *argv[])
1377{
Richard Hughes460226a2018-05-21 20:56:21 +01001378 gboolean allow_older = FALSE;
1379 gboolean allow_reinstall = FALSE;
Richard Hughesb5976832018-05-18 10:02:09 +01001380 gboolean force = FALSE;
1381 gboolean ret;
Mario Limonciello2d4b7a52018-09-12 22:08:04 -05001382 gboolean version = FALSE;
Mario Limonciello5d7aa402019-02-04 09:35:58 -06001383 gboolean interactive = isatty (fileno (stdout)) != 0;
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001384 g_auto(GStrv) plugin_glob = NULL;
Richard Hughesb5976832018-05-18 10:02:09 +01001385 g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1);
1386 g_autoptr(GError) error = NULL;
Richard Hughesc77e1112019-03-01 10:16:26 +00001387 g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new ();
Richard Hughesb5976832018-05-18 10:02:09 +01001388 g_autofree gchar *cmd_descriptions = NULL;
Richard Hughes747f5702019-08-06 14:27:26 +01001389 g_autofree gchar *filter = NULL;
Richard Hughesb5976832018-05-18 10:02:09 +01001390 const GOptionEntry options[] = {
Mario Limonciello2d4b7a52018-09-12 22:08:04 -05001391 { "version", '\0', 0, G_OPTION_ARG_NONE, &version,
1392 /* TRANSLATORS: command line option */
1393 _("Show client and daemon versions"), NULL },
Richard Hughes460226a2018-05-21 20:56:21 +01001394 { "allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall,
1395 /* TRANSLATORS: command line option */
1396 _("Allow re-installing existing firmware versions"), NULL },
1397 { "allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older,
1398 /* TRANSLATORS: command line option */
1399 _("Allow downgrading firmware versions"), NULL },
Richard Hughesb5976832018-05-18 10:02:09 +01001400 { "force", '\0', 0, G_OPTION_ARG_NONE, &force,
1401 /* TRANSLATORS: command line option */
1402 _("Override plugin warning"), NULL },
Mario Limonciello3f243a92019-01-21 22:05:23 -06001403 { "no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check,
1404 /* TRANSLATORS: command line option */
1405 _("Do not check for reboot after update"), NULL },
Mario Limoncielloba9e5b92018-05-21 16:02:32 -05001406 { "show-all-devices", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all_devices,
1407 /* TRANSLATORS: command line option */
1408 _("Show devices that are not updatable"), NULL },
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001409 { "plugin-whitelist", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &plugin_glob,
1410 /* TRANSLATORS: command line option */
1411 _("Manually whitelist specific plugins"), NULL },
Mario Limonciello8402cea2019-02-07 20:25:31 -06001412 { "prepare", '\0', 0, G_OPTION_ARG_NONE, &priv->prepare_blob,
Mario Limonciello53ce25d2019-02-01 16:09:03 +00001413 /* TRANSLATORS: command line option */
1414 _("Run the plugin composite prepare routine when using install-blob"), NULL },
Mario Limonciello8402cea2019-02-07 20:25:31 -06001415 { "cleanup", '\0', 0, G_OPTION_ARG_NONE, &priv->cleanup_blob,
Mario Limonciello53ce25d2019-02-01 16:09:03 +00001416 /* TRANSLATORS: command line option */
1417 _("Run the plugin composite cleanup routine when using install-blob"), NULL },
Mario Limonciello3143bad2019-02-27 07:31:00 -06001418 { "enable-json-state", '\0', 0, G_OPTION_ARG_NONE, &priv->enable_json_state,
1419 /* TRANSLATORS: command line option */
1420 _("Save device state into a JSON file between executions"), NULL },
Richard Hughes747f5702019-08-06 14:27:26 +01001421 { "filter", '\0', 0, G_OPTION_ARG_STRING, &filter,
1422 /* TRANSLATORS: command line option */
1423 _("Filter with a set of device flags using a ~ prefix to "
1424 "exclude, e.g. 'internal,~needs-reboot'"), NULL },
Richard Hughesb5976832018-05-18 10:02:09 +01001425 { NULL}
1426 };
1427
1428 setlocale (LC_ALL, "");
1429
1430 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1431 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1432 textdomain (GETTEXT_PACKAGE);
1433
1434 /* ensure root user */
Mario Limonciello5d7aa402019-02-04 09:35:58 -06001435 if (interactive && (getuid () != 0 || geteuid () != 0))
Richard Hughesb5976832018-05-18 10:02:09 +01001436 /* TRANSLATORS: we're poking around as a power user */
Mario Limonciellob900c092018-05-22 14:22:21 -05001437 g_printerr ("%s\n", _("This program may only work correctly as root"));
Richard Hughesb5976832018-05-18 10:02:09 +01001438
1439 /* create helper object */
1440 priv->loop = g_main_loop_new (NULL, FALSE);
1441 priv->progressbar = fu_progressbar_new ();
1442
1443 /* add commands */
Richard Hughesc77e1112019-03-01 10:16:26 +00001444 fu_util_cmd_array_add (cmd_array,
Mario Limonciellof6d01b12018-10-18 12:57:10 -05001445 "build-firmware",
1446 "FILE-IN FILE-OUT [SCRIPT] [OUTPUT]",
1447 /* TRANSLATORS: command description */
1448 _("Build firmware using a sandbox"),
1449 fu_util_firmware_builder);
Richard Hughesc77e1112019-03-01 10:16:26 +00001450 fu_util_cmd_array_add (cmd_array,
Richard Hughesb5976832018-05-18 10:02:09 +01001451 "smbios-dump",
1452 "FILE",
1453 /* TRANSLATORS: command description */
1454 _("Dump SMBIOS data from a file"),
1455 fu_util_smbios_dump);
Richard Hughesc77e1112019-03-01 10:16:26 +00001456 fu_util_cmd_array_add (cmd_array,
Richard Hughes8c71a3f2018-05-22 19:19:52 +01001457 "get-plugins",
1458 NULL,
1459 /* TRANSLATORS: command description */
1460 _("Get all enabled plugins registered with the system"),
1461 fu_util_get_plugins);
Richard Hughesc77e1112019-03-01 10:16:26 +00001462 fu_util_cmd_array_add (cmd_array,
Mario Limonciello716ab272018-05-29 12:34:37 -05001463 "get-details",
1464 NULL,
1465 /* TRANSLATORS: command description */
1466 _("Gets details about a firmware file"),
1467 fu_util_get_details);
Richard Hughesc77e1112019-03-01 10:16:26 +00001468 fu_util_cmd_array_add (cmd_array,
Mario Limonciellofe593942019-04-03 13:48:52 -05001469 "get-history",
1470 NULL,
1471 /* TRANSLATORS: command description */
1472 _("Show history of firmware updates"),
1473 fu_util_get_history);
1474 fu_util_cmd_array_add (cmd_array,
Mario Limonciellodfff18e2019-08-29 11:51:41 -05001475 "get-updates,get-upgrades",
Mario Limonciello1e35e4c2019-01-28 11:13:02 -06001476 NULL,
1477 /* TRANSLATORS: command description */
1478 _("Gets the list of updates for connected hardware"),
1479 fu_util_get_updates);
Richard Hughesc77e1112019-03-01 10:16:26 +00001480 fu_util_cmd_array_add (cmd_array,
Mario Limonciello1a9127d2019-08-27 11:32:51 +01001481 "get-devices,get-topology",
Richard Hughes98ca9932018-05-18 10:24:07 +01001482 NULL,
1483 /* TRANSLATORS: command description */
1484 _("Get all devices that support firmware updates"),
1485 fu_util_get_devices);
Richard Hughesc77e1112019-03-01 10:16:26 +00001486 fu_util_cmd_array_add (cmd_array,
Richard Hughes747f5702019-08-06 14:27:26 +01001487 "get-device-flags",
1488 NULL,
1489 /* TRANSLATORS: command description */
1490 _("Get all device flags supported by fwupd"),
1491 fu_util_get_device_flags);
1492 fu_util_cmd_array_add (cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001493 "watch",
1494 NULL,
1495 /* TRANSLATORS: command description */
Piotr DrÄ…g472fa592018-06-06 14:53:48 +02001496 _("Watch for hardware changes"),
Richard Hughes98ca9932018-05-18 10:24:07 +01001497 fu_util_watch);
Richard Hughesc77e1112019-03-01 10:16:26 +00001498 fu_util_cmd_array_add (cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001499 "install-blob",
1500 "FILENAME DEVICE-ID",
1501 /* TRANSLATORS: command description */
1502 _("Install a firmware blob on a device"),
1503 fu_util_install_blob);
Richard Hughesc77e1112019-03-01 10:16:26 +00001504 fu_util_cmd_array_add (cmd_array,
Richard Hughesa36c9cf2018-05-20 10:41:44 +01001505 "install",
1506 "FILE [ID]",
1507 /* TRANSLATORS: command description */
1508 _("Install a firmware file on this hardware"),
1509 fu_util_install);
Richard Hughesc77e1112019-03-01 10:16:26 +00001510 fu_util_cmd_array_add (cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001511 "attach",
1512 "DEVICE-ID",
1513 /* TRANSLATORS: command description */
1514 _("Attach to firmware mode"),
1515 fu_util_attach);
Richard Hughesc77e1112019-03-01 10:16:26 +00001516 fu_util_cmd_array_add (cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +01001517 "detach",
1518 "DEVICE-ID",
1519 /* TRANSLATORS: command description */
1520 _("Detach to bootloader mode"),
1521 fu_util_detach);
Richard Hughesc77e1112019-03-01 10:16:26 +00001522 fu_util_cmd_array_add (cmd_array,
Mario Limonciello96a0dd52019-02-25 13:50:03 -06001523 "activate",
1524 "[DEVICE-ID]",
1525 /* TRANSLATORS: command description */
1526 _("Activate pending devices"),
1527 fu_util_activate);
Richard Hughesc77e1112019-03-01 10:16:26 +00001528 fu_util_cmd_array_add (cmd_array,
Richard Hughes1d894f12018-08-31 13:05:51 +01001529 "hwids",
1530 "[FILE]",
1531 /* TRANSLATORS: command description */
1532 _("Return all the hardware IDs for the machine"),
1533 fu_util_hwids);
Richard Hughesc77e1112019-03-01 10:16:26 +00001534 fu_util_cmd_array_add (cmd_array,
Mario Limonciello62f84862018-10-18 13:15:23 -05001535 "monitor",
1536 NULL,
1537 /* TRANSLATORS: command description */
1538 _("Monitor the daemon for events"),
1539 fu_util_monitor);
Richard Hughesc77e1112019-03-01 10:16:26 +00001540 fu_util_cmd_array_add (cmd_array,
Mario Limonciellodfff18e2019-08-29 11:51:41 -05001541 "update,upgrade",
Mario Limonciello46aaee82019-01-10 12:58:00 -06001542 NULL,
1543 /* TRANSLATORS: command description */
1544 _("Update all devices that match local metadata"),
1545 fu_util_update);
Richard Hughes3d607622019-03-07 16:59:27 +00001546 fu_util_cmd_array_add (cmd_array,
1547 "self-sign",
1548 "TEXT",
1549 /* TRANSLATORS: command description */
Richard Hughes12a021d2019-03-27 09:23:24 +00001550 C_("command-description",
1551 "Sign data using the client certificate"),
Richard Hughes3d607622019-03-07 16:59:27 +00001552 fu_util_self_sign);
Richard Hughes15684492019-03-15 16:27:50 +00001553 fu_util_cmd_array_add (cmd_array,
1554 "verify-update",
1555 "[DEVICE_ID]",
1556 /* TRANSLATORS: command description */
1557 _("Update the stored metadata with current contents"),
1558 fu_util_verify_update);
Richard Hughesb5976832018-05-18 10:02:09 +01001559
1560 /* do stuff on ctrl+c */
1561 priv->cancellable = g_cancellable_new ();
1562 g_unix_signal_add_full (G_PRIORITY_DEFAULT,
1563 SIGINT, fu_util_sigint_cb,
1564 priv, NULL);
1565 g_signal_connect (priv->cancellable, "cancelled",
1566 G_CALLBACK (fu_util_cancelled_cb), priv);
1567
1568 /* sort by command name */
Richard Hughesc77e1112019-03-01 10:16:26 +00001569 fu_util_cmd_array_sort (cmd_array);
Richard Hughesb5976832018-05-18 10:02:09 +01001570
Mario Limonciello3f243a92019-01-21 22:05:23 -06001571 /* non-TTY consoles cannot answer questions */
Mario Limonciello5d7aa402019-02-04 09:35:58 -06001572 if (!interactive) {
Mario Limonciello3f243a92019-01-21 22:05:23 -06001573 priv->no_reboot_check = TRUE;
Mario Limonciello9b31e6f2019-01-31 15:42:19 -06001574 fu_progressbar_set_interactive (priv->progressbar, FALSE);
1575 }
Mario Limonciello3f243a92019-01-21 22:05:23 -06001576
Richard Hughesb5976832018-05-18 10:02:09 +01001577 /* get a list of the commands */
1578 priv->context = g_option_context_new (NULL);
Richard Hughesc77e1112019-03-01 10:16:26 +00001579 cmd_descriptions = fu_util_cmd_array_to_string (cmd_array);
Richard Hughesb5976832018-05-18 10:02:09 +01001580 g_option_context_set_summary (priv->context, cmd_descriptions);
1581 g_option_context_set_description (priv->context,
1582 "This tool allows an administrator to use the fwupd plugins "
1583 "without being installed on the host system.");
1584
1585 /* TRANSLATORS: program name */
1586 g_set_application_name (_("Firmware Utility"));
1587 g_option_context_add_main_entries (priv->context, options, NULL);
Mario Limonciellofde47732018-09-11 12:20:58 -05001588 g_option_context_add_group (priv->context, fu_debug_get_option_group ());
Richard Hughesb5976832018-05-18 10:02:09 +01001589 ret = g_option_context_parse (priv->context, &argc, &argv, &error);
1590 if (!ret) {
1591 /* TRANSLATORS: the user didn't read the man page */
1592 g_print ("%s: %s\n", _("Failed to parse arguments"),
1593 error->message);
1594 return EXIT_FAILURE;
1595 }
1596
Richard Hughes747f5702019-08-06 14:27:26 +01001597 /* parse filter flags */
1598 if (filter != NULL) {
1599 if (!fu_util_parse_filter_flags (filter,
1600 &priv->filter_include,
1601 &priv->filter_exclude,
1602 &error)) {
1603 /* TRANSLATORS: the user didn't read the man page */
1604 g_print ("%s: %s\n", _("Failed to parse flags for --filter"),
1605 error->message);
1606 return EXIT_FAILURE;
1607 }
1608 }
1609
1610
Richard Hughes460226a2018-05-21 20:56:21 +01001611 /* set flags */
Richard Hughes460226a2018-05-21 20:56:21 +01001612 if (allow_reinstall)
1613 priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL;
1614 if (allow_older)
1615 priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER;
1616 if (force)
1617 priv->flags |= FWUPD_INSTALL_FLAG_FORCE;
1618
Richard Hughes98ca9932018-05-18 10:24:07 +01001619 /* load engine */
1620 priv->engine = fu_engine_new (FU_APP_FLAGS_NO_IDLE_SOURCES);
1621 g_signal_connect (priv->engine, "device-added",
1622 G_CALLBACK (fu_main_engine_device_added_cb),
1623 priv);
1624 g_signal_connect (priv->engine, "device-removed",
1625 G_CALLBACK (fu_main_engine_device_removed_cb),
1626 priv);
Richard Hughes98ca9932018-05-18 10:24:07 +01001627 g_signal_connect (priv->engine, "status-changed",
1628 G_CALLBACK (fu_main_engine_status_changed_cb),
1629 priv);
1630 g_signal_connect (priv->engine, "percentage-changed",
1631 G_CALLBACK (fu_main_engine_percentage_changed_cb),
1632 priv);
1633
Mario Limonciello2d4b7a52018-09-12 22:08:04 -05001634 /* just show versions and exit */
1635 if (version) {
1636 g_autofree gchar *version_str = fu_util_get_versions ();
1637 g_print ("%s\n", version_str);
1638 return EXIT_SUCCESS;
1639 }
1640
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001641 /* any plugin whitelist specified */
1642 for (guint i = 0; plugin_glob != NULL && plugin_glob[i] != NULL; i++)
1643 fu_engine_add_plugin_filter (priv->engine, plugin_glob[i]);
1644
Richard Hughesb5976832018-05-18 10:02:09 +01001645 /* run the specified command */
Richard Hughesc77e1112019-03-01 10:16:26 +00001646 ret = fu_util_cmd_array_run (cmd_array, priv, argv[1], (gchar**) &argv[2], &error);
Richard Hughesb5976832018-05-18 10:02:09 +01001647 if (!ret) {
1648 if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) {
1649 g_autofree gchar *tmp = NULL;
1650 tmp = g_option_context_get_help (priv->context, TRUE, NULL);
1651 g_print ("%s\n\n%s", error->message, tmp);
1652 return EXIT_FAILURE;
1653 }
1654 if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
1655 g_print ("%s\n", error->message);
1656 return EXIT_NOTHING_TO_DO;
1657 }
1658 g_print ("%s\n", error->message);
1659 return EXIT_FAILURE;
1660 }
1661
1662 /* success */
1663 return EXIT_SUCCESS;
1664}