blob: f6b3ba9db1a5056ffb2723476d257d8f219192f1 [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>
12#include <glib/gi18n.h>
13#include <glib-unix.h>
14#include <locale.h>
15#include <stdlib.h>
16#include <unistd.h>
Richard Hughes3d178be2018-08-30 11:14:24 +010017#include <libsoup/soup.h>
Richard Hughesb5976832018-05-18 10:02:09 +010018
Richard Hughes98ca9932018-05-18 10:24:07 +010019#include "fu-engine.h"
Richard Hughes8c71a3f2018-05-22 19:19:52 +010020#include "fu-plugin-private.h"
Richard Hughesb5976832018-05-18 10:02:09 +010021#include "fu-progressbar.h"
22#include "fu-smbios.h"
23#include "fu-util-common.h"
24
25/* this is only valid in this file */
26#define FWUPD_ERROR_INVALID_ARGS (FWUPD_ERROR_LAST+1)
27
28/* custom return code */
29#define EXIT_NOTHING_TO_DO 2
30
31typedef struct {
32 GCancellable *cancellable;
33 GMainLoop *loop;
34 GOptionContext *context;
35 GPtrArray *cmd_array;
Richard Hughes98ca9932018-05-18 10:24:07 +010036 FuEngine *engine;
Richard Hughesb5976832018-05-18 10:02:09 +010037 FuProgressbar *progressbar;
Richard Hughes460226a2018-05-21 20:56:21 +010038 FwupdInstallFlags flags;
Mario Limoncielloba9e5b92018-05-21 16:02:32 -050039 gboolean show_all_devices;
Mario Limonciello9eb66fe2018-08-10 11:32:44 -050040 /* only valid in update and downgrade */
41 FwupdDevice *current_device;
Richard Hughesb5976832018-05-18 10:02:09 +010042} FuUtilPrivate;
43
44typedef gboolean (*FuUtilPrivateCb) (FuUtilPrivate *util,
45 gchar **values,
46 GError **error);
47
48typedef struct {
49 gchar *name;
50 gchar *arguments;
51 gchar *description;
52 FuUtilPrivateCb callback;
53} FuUtilItem;
54
55static void
56fu_util_item_free (FuUtilItem *item)
57{
58 g_free (item->name);
59 g_free (item->arguments);
60 g_free (item->description);
61 g_free (item);
62}
63
64static gint
65fu_sort_command_name_cb (FuUtilItem **item1, FuUtilItem **item2)
66{
67 return g_strcmp0 ((*item1)->name, (*item2)->name);
68}
69
70static void
Mario Limonciellob72aa8c2018-06-08 09:24:36 -050071fu_util_maybe_prefix_sandbox_error (const gchar *value, GError **error)
72{
73 g_autofree gchar *path = g_path_get_dirname (value);
74 if (!g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
75 g_prefix_error (error,
76 "Unable to access %s. You may need to copy %s to %s: ",
77 path, value, g_getenv ("HOME"));
78 }
79}
80
81static void
Richard Hughesb5976832018-05-18 10:02:09 +010082fu_util_add (GPtrArray *array,
83 const gchar *name,
84 const gchar *arguments,
85 const gchar *description,
86 FuUtilPrivateCb callback)
87{
88 g_auto(GStrv) names = NULL;
89
90 g_return_if_fail (name != NULL);
91 g_return_if_fail (description != NULL);
92 g_return_if_fail (callback != NULL);
93
94 /* add each one */
95 names = g_strsplit (name, ",", -1);
96 for (guint i = 0; names[i] != NULL; i++) {
97 FuUtilItem *item = g_new0 (FuUtilItem, 1);
98 item->name = g_strdup (names[i]);
99 if (i == 0) {
100 item->description = g_strdup (description);
101 } else {
102 /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */
103 item->description = g_strdup_printf (_("Alias to %s"),
104 names[0]);
105 }
106 item->arguments = g_strdup (arguments);
107 item->callback = callback;
108 g_ptr_array_add (array, item);
109 }
110}
111
112static gchar *
113fu_util_get_descriptions (GPtrArray *array)
114{
115 gsize len;
116 const gsize max_len = 35;
117 GString *string;
118
119 /* print each command */
120 string = g_string_new ("");
121 for (guint i = 0; i < array->len; i++) {
122 FuUtilItem *item = g_ptr_array_index (array, i);
123 g_string_append (string, " ");
124 g_string_append (string, item->name);
125 len = strlen (item->name) + 2;
126 if (item->arguments != NULL) {
127 g_string_append (string, " ");
128 g_string_append (string, item->arguments);
129 len += strlen (item->arguments) + 1;
130 }
131 if (len < max_len) {
132 for (gsize j = len; j < max_len + 1; j++)
133 g_string_append_c (string, ' ');
134 g_string_append (string, item->description);
135 g_string_append_c (string, '\n');
136 } else {
137 g_string_append_c (string, '\n');
138 for (gsize j = 0; j < max_len + 1; j++)
139 g_string_append_c (string, ' ');
140 g_string_append (string, item->description);
141 g_string_append_c (string, '\n');
142 }
143 }
144
145 /* remove trailing newline */
146 if (string->len > 0)
147 g_string_set_size (string, string->len - 1);
148
149 return g_string_free (string, FALSE);
150}
151
152static gboolean
153fu_util_run (FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error)
154{
155 /* find command */
156 for (guint i = 0; i < priv->cmd_array->len; i++) {
157 FuUtilItem *item = g_ptr_array_index (priv->cmd_array, i);
158 if (g_strcmp0 (item->name, command) == 0)
159 return item->callback (priv, values, error);
160 }
161
162 /* not found */
163 g_set_error_literal (error,
164 FWUPD_ERROR,
165 FWUPD_ERROR_INVALID_ARGS,
166 /* TRANSLATORS: error message */
167 _("Command not found"));
168 return FALSE;
169}
170
171static void
172fu_util_cancelled_cb (GCancellable *cancellable, gpointer user_data)
173{
174 FuUtilPrivate *priv = (FuUtilPrivate *) user_data;
175 /* TRANSLATORS: this is when a device ctrl+c's a watch */
176 g_print ("%s\n", _("Cancelled"));
177 g_main_loop_quit (priv->loop);
178}
179
180static gboolean
181fu_util_smbios_dump (FuUtilPrivate *priv, gchar **values, GError **error)
182{
183 g_autofree gchar *tmp = NULL;
184 g_autoptr(FuSmbios) smbios = NULL;
185 if (g_strv_length (values) < 1) {
186 g_set_error_literal (error,
187 FWUPD_ERROR,
188 FWUPD_ERROR_INVALID_ARGS,
189 "Invalid arguments");
190 return FALSE;
191 }
192 smbios = fu_smbios_new ();
193 if (!fu_smbios_setup_from_file (smbios, values[0], error))
194 return FALSE;
195 tmp = fu_smbios_to_string (smbios);
196 g_print ("%s\n", tmp);
197 return TRUE;
198}
199
200static void
201fu_util_ignore_cb (const gchar *log_domain, GLogLevelFlags log_level,
202 const gchar *message, gpointer user_data)
203{
204}
205
206static gboolean
207fu_util_sigint_cb (gpointer user_data)
208{
209 FuUtilPrivate *priv = (FuUtilPrivate *) user_data;
210 g_debug ("Handling SIGINT");
211 g_cancellable_cancel (priv->cancellable);
212 return FALSE;
213}
214
215static void
216fu_util_private_free (FuUtilPrivate *priv)
217{
218 if (priv->cmd_array != NULL)
219 g_ptr_array_unref (priv->cmd_array);
Mario Limonciellocc50e1a2018-08-14 17:45:24 -0500220 if (priv->current_device != NULL)
221 g_object_unref (priv->current_device);
Richard Hughes98ca9932018-05-18 10:24:07 +0100222 if (priv->engine != NULL)
223 g_object_unref (priv->engine);
Richard Hughesb5976832018-05-18 10:02:09 +0100224 if (priv->loop != NULL)
225 g_main_loop_unref (priv->loop);
226 if (priv->cancellable != NULL)
227 g_object_unref (priv->cancellable);
228 if (priv->progressbar != NULL)
229 g_object_unref (priv->progressbar);
230 if (priv->context != NULL)
231 g_option_context_free (priv->context);
232 g_free (priv);
233}
234
235#pragma clang diagnostic push
236#pragma clang diagnostic ignored "-Wunused-function"
237G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free)
238#pragma clang diagnostic pop
239
Richard Hughes98ca9932018-05-18 10:24:07 +0100240
241static void
242fu_main_engine_device_added_cb (FuEngine *engine,
243 FuDevice *device,
244 FuUtilPrivate *priv)
245{
246 g_autofree gchar *tmp = fu_device_to_string (device);
247 g_debug ("ADDED:\n%s", tmp);
248}
249
250static void
251fu_main_engine_device_removed_cb (FuEngine *engine,
252 FuDevice *device,
253 FuUtilPrivate *priv)
254{
255 g_autofree gchar *tmp = fu_device_to_string (device);
256 g_debug ("REMOVED:\n%s", tmp);
257}
258
259static void
Richard Hughes98ca9932018-05-18 10:24:07 +0100260fu_main_engine_status_changed_cb (FuEngine *engine,
261 FwupdStatus status,
262 FuUtilPrivate *priv)
263{
264 fu_progressbar_update (priv->progressbar, status, 0);
265}
266
267static void
268fu_main_engine_percentage_changed_cb (FuEngine *engine,
269 guint percentage,
270 FuUtilPrivate *priv)
271{
272 fu_progressbar_update (priv->progressbar, FWUPD_STATUS_UNKNOWN, percentage);
273}
274
275static gboolean
276fu_util_watch (FuUtilPrivate *priv, gchar **values, GError **error)
277{
278 if (!fu_engine_load (priv->engine, error))
279 return FALSE;
280 g_main_loop_run (priv->loop);
281 return TRUE;
282}
283
Richard Hughes8c71a3f2018-05-22 19:19:52 +0100284static gint
285fu_util_plugin_name_sort_cb (FuPlugin **item1, FuPlugin **item2)
286{
287 return fu_plugin_name_compare (*item1, *item2);
288}
289
290static gboolean
291fu_util_get_plugins (FuUtilPrivate *priv, gchar **values, GError **error)
292{
293 GPtrArray *plugins;
294 guint cnt = 0;
295
296 /* load engine */
297 if (!fu_engine_load_plugins (priv->engine, error))
298 return FALSE;
299
300 /* print */
301 plugins = fu_engine_get_plugins (priv->engine);
302 g_ptr_array_sort (plugins, (GCompareFunc) fu_util_plugin_name_sort_cb);
303 for (guint i = 0; i < plugins->len; i++) {
304 FuPlugin *plugin = g_ptr_array_index (plugins, i);
305 if (!fu_plugin_get_enabled (plugin))
306 continue;
307 g_print ("%s\n", fu_plugin_get_name (plugin));
308 cnt++;
309 }
310 if (cnt == 0) {
311 /* TRANSLATORS: nothing found */
312 g_print ("%s\n", _("No plugins found"));
313 return TRUE;
314 }
315
316 return TRUE;
317}
318
Richard Hughes98ca9932018-05-18 10:24:07 +0100319static gboolean
Mario Limonciello716ab272018-05-29 12:34:37 -0500320fu_util_get_details (FuUtilPrivate *priv, gchar **values, GError **error)
321{
322 g_autoptr(GPtrArray) array = NULL;
323 gint fd;
324
325 /* load engine */
326 if (!fu_engine_load (priv->engine, error))
327 return FALSE;
328
329 /* check args */
330 if (g_strv_length (values) != 1) {
331 g_set_error_literal (error,
332 FWUPD_ERROR,
333 FWUPD_ERROR_INVALID_ARGS,
334 "Invalid arguments");
335 return FALSE;
336 }
337
338 /* open file */
339 fd = open (values[0], O_RDONLY);
340 if (fd < 0) {
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500341 fu_util_maybe_prefix_sandbox_error (values[0], error);
Mario Limonciello716ab272018-05-29 12:34:37 -0500342 g_set_error (error,
343 FWUPD_ERROR,
344 FWUPD_ERROR_INVALID_FILE,
345 "failed to open %s",
346 values[0]);
347 return FALSE;
348 }
349 array = fu_engine_get_details (priv->engine, fd, error);
350 close (fd);
351
352 if (array == NULL)
353 return FALSE;
354 for (guint i = 0; i < array->len; i++) {
355 FwupdDevice *dev = g_ptr_array_index (array, i);
356 g_autofree gchar *tmp = NULL;
357 tmp = fwupd_device_to_string (dev);
Mario Limoncielloece90a42018-07-25 17:35:55 -0500358 g_print ("%s\n", tmp);
Mario Limonciello716ab272018-05-29 12:34:37 -0500359 }
360 return TRUE;
361}
362
363static gboolean
Richard Hughes98ca9932018-05-18 10:24:07 +0100364fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error)
365{
366 g_autoptr(GPtrArray) devs = NULL;
367
368 /* load engine */
369 if (!fu_engine_load (priv->engine, error))
370 return FALSE;
371
372 /* print */
373 devs = fu_engine_get_devices (priv->engine, error);
374 if (devs == NULL)
375 return FALSE;
376 if (devs->len == 0) {
377 /* TRANSLATORS: nothing attached */
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500378 g_print ("%s\n", _("No hardware detected with firmware update capability"));
Richard Hughes98ca9932018-05-18 10:24:07 +0100379 return TRUE;
380 }
381 for (guint i = 0; i < devs->len; i++) {
382 FwupdDevice *dev = g_ptr_array_index (devs, i);
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500383 if (priv->show_all_devices || fu_util_is_interesting_device (dev)) {
384 g_autofree gchar *tmp = fwupd_device_to_string (dev);
385 g_print ("%s\n", tmp);
386 }
Richard Hughes98ca9932018-05-18 10:24:07 +0100387 }
388
389 return TRUE;
390}
391
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500392static void
Richard Hughes0d1577e2018-05-29 13:59:06 +0100393fu_util_build_device_tree (FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FuDevice *dev)
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500394{
395 for (guint i = 0; i < devs->len; i++) {
Richard Hughes0d1577e2018-05-29 13:59:06 +0100396 FuDevice *dev_tmp = g_ptr_array_index (devs, i);
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500397 if (!priv->show_all_devices &&
398 !fu_util_is_interesting_device (FWUPD_DEVICE (dev_tmp)))
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500399 continue;
Richard Hughes0d1577e2018-05-29 13:59:06 +0100400 if (fu_device_get_parent (dev_tmp) == dev) {
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500401 GNode *child = g_node_append_data (root, dev_tmp);
402 fu_util_build_device_tree (priv, child, devs, dev_tmp);
403 }
404 }
405}
406
407static gboolean
408fu_util_get_topology (FuUtilPrivate *priv, gchar **values, GError **error)
409{
410 g_autoptr(GNode) root = g_node_new (NULL);
411 g_autoptr(GPtrArray) devs = NULL;
412
413 /* load engine */
414 if (!fu_engine_load (priv->engine, error))
415 return FALSE;
416
417 /* print */
418 devs = fu_engine_get_devices (priv->engine, error);
419 if (devs == NULL)
420 return FALSE;
421
422 /* print */
423 if (devs->len == 0) {
424 /* TRANSLATORS: nothing attached that can be upgraded */
425 g_print ("%s\n", _("No hardware detected with firmware update capability"));
426 return TRUE;
427 }
428 fu_util_build_device_tree (priv, root, devs, NULL);
429 g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
430 fu_util_print_device_tree, priv);
431
432 return TRUE;
433}
434
435
Richard Hughes98ca9932018-05-18 10:24:07 +0100436static FuDevice *
437fu_util_prompt_for_device (FuUtilPrivate *priv, GError **error)
438{
439 FuDevice *dev;
440 guint idx;
441 g_autoptr(GPtrArray) devices = NULL;
442
443 /* get devices from daemon */
444 devices = fu_engine_get_devices (priv->engine, error);
445 if (devices == NULL)
446 return NULL;
447
448 /* exactly one */
449 if (devices->len == 1) {
450 dev = g_ptr_array_index (devices, 0);
451 return g_object_ref (dev);
452 }
453
454 /* TRANSLATORS: get interactive prompt */
455 g_print ("%s\n", _("Choose a device:"));
456 /* TRANSLATORS: this is to abort the interactive prompt */
457 g_print ("0.\t%s\n", _("Cancel"));
458 for (guint i = 0; i < devices->len; i++) {
459 dev = g_ptr_array_index (devices, i);
460 g_print ("%u.\t%s (%s)\n",
461 i + 1,
462 fu_device_get_id (dev),
463 fu_device_get_name (dev));
464 }
465 idx = fu_util_prompt_for_number (devices->len);
466 if (idx == 0) {
467 g_set_error_literal (error,
468 FWUPD_ERROR,
469 FWUPD_ERROR_NOTHING_TO_DO,
470 "Request canceled");
471 return NULL;
472 }
473 dev = g_ptr_array_index (devices, idx - 1);
474 return g_object_ref (dev);
475}
476
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500477static void
478fu_util_install_device_changed_cb (FwupdClient *client,
479 FwupdDevice *device,
480 FuUtilPrivate *priv)
481{
482 g_autofree gchar *str = NULL;
483
484 /* same as last time, so ignore */
485 if (priv->current_device != NULL &&
486 fwupd_device_compare (priv->current_device, device) == 0)
487 return;
488
489 /* show message in progressbar */
490 /* TRANSLATORS: %1 is a device name */
491 str = g_strdup_printf (_("Installing %s"),
492 fwupd_device_get_name (device));
493 fu_progressbar_set_title (priv->progressbar, str);
494 g_set_object (&priv->current_device, device);
495}
496
Richard Hughes98ca9932018-05-18 10:24:07 +0100497static gboolean
498fu_util_install_blob (FuUtilPrivate *priv, gchar **values, GError **error)
499{
500 g_autoptr(FuDevice) device = NULL;
501 g_autoptr(GBytes) blob_fw = NULL;
502
503 /* invalid args */
504 if (g_strv_length (values) == 0) {
505 g_set_error_literal (error,
506 FWUPD_ERROR,
507 FWUPD_ERROR_INVALID_ARGS,
508 "Invalid arguments");
509 return FALSE;
510 }
511
512 /* parse blob */
513 blob_fw = fu_common_get_contents_bytes (values[0], error);
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500514 if (blob_fw == NULL) {
515 fu_util_maybe_prefix_sandbox_error (values[0], error);
Richard Hughes98ca9932018-05-18 10:24:07 +0100516 return FALSE;
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500517 }
Richard Hughes98ca9932018-05-18 10:24:07 +0100518
519 /* load engine */
520 if (!fu_engine_load (priv->engine, error))
521 return FALSE;
522
523 /* get device */
524 if (g_strv_length (values) >= 2) {
525 device = fu_engine_get_device (priv->engine, values[1], error);
526 if (device == NULL)
527 return FALSE;
528 } else {
529 device = fu_util_prompt_for_device (priv, error);
530 if (device == NULL)
531 return FALSE;
532 }
533
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500534 g_signal_connect (priv->engine, "device-changed",
535 G_CALLBACK (fu_util_install_device_changed_cb), priv);
536
Richard Hughes98ca9932018-05-18 10:24:07 +0100537 /* write bare firmware */
538 return fu_engine_install_blob (priv->engine, device,
539 NULL, /* blob_cab */
540 blob_fw,
541 NULL, /* version */
Richard Hughes460226a2018-05-21 20:56:21 +0100542 priv->flags,
Richard Hughes98ca9932018-05-18 10:24:07 +0100543 error);
544}
545
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100546static gint
547fu_util_install_task_sort_cb (gconstpointer a, gconstpointer b)
548{
549 FuInstallTask *task1 = *((FuInstallTask **) a);
550 FuInstallTask *task2 = *((FuInstallTask **) b);
551 return fu_install_task_compare (task1, task2);
552}
553
554static gboolean
Richard Hughes3d178be2018-08-30 11:14:24 +0100555fu_util_download_out_of_process (const gchar *uri, const gchar *fn, GError **error)
556{
557 const gchar *argv[][5] = { { "wget", uri, "-o", fn, NULL },
558 { "curl", uri, "--output", fn, NULL },
559 { NULL } };
560 for (guint i = 0; argv[i][0] != NULL; i++) {
561 g_autoptr(GError) error_local = NULL;
562 if (!fu_common_find_program_in_path (argv[i][0], &error_local)) {
563 g_debug ("%s", error_local->message);
564 continue;
565 }
566 return fu_common_spawn_sync (argv[i], NULL, NULL, NULL, error);
567 }
568 g_set_error_literal (error,
569 FWUPD_ERROR,
570 FWUPD_ERROR_NOT_FOUND,
571 "no supported out-of-process downloaders found");
572 return FALSE;
573}
574
575static gchar *
576fu_util_download_if_required (FuUtilPrivate *priv, const gchar *perhapsfn, GError **error)
577{
578 g_autofree gchar *filename = NULL;
579 g_autoptr(SoupURI) uri = NULL;
580
581 /* a local file */
582 uri = soup_uri_new (perhapsfn);
583 if (uri == NULL)
584 return g_strdup (perhapsfn);
585
586 /* download the firmware to a cachedir */
587 filename = fu_util_get_user_cache_path (perhapsfn);
588 if (!fu_common_mkdir_parent (filename, error))
589 return NULL;
590 if (!fu_util_download_out_of_process (perhapsfn, filename, error))
591 return NULL;
592 return g_steal_pointer (&filename);
593}
594
595static gboolean
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100596fu_util_install (FuUtilPrivate *priv, gchar **values, GError **error)
597{
598 GPtrArray *apps;
Richard Hughes3d178be2018-08-30 11:14:24 +0100599 g_autofree gchar *filename = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100600 g_autoptr(AsStore) store = NULL;
601 g_autoptr(GBytes) blob_cab = NULL;
602 g_autoptr(GPtrArray) devices_possible = NULL;
603 g_autoptr(GPtrArray) errors = NULL;
604 g_autoptr(GPtrArray) install_tasks = NULL;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100605
Mario Limonciello8949e892018-05-25 08:03:06 -0500606 /* load engine */
607 if (!fu_engine_load (priv->engine, error))
608 return FALSE;
609
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100610 /* handle both forms */
611 if (g_strv_length (values) == 1) {
612 devices_possible = fu_engine_get_devices (priv->engine, error);
613 if (devices_possible == NULL)
614 return FALSE;
615 } else if (g_strv_length (values) == 2) {
616 FuDevice *device = fu_engine_get_device (priv->engine,
617 values[1],
618 error);
619 if (device == NULL)
620 return FALSE;
621 devices_possible = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
622 g_ptr_array_add (devices_possible, device);
623 } else {
624 g_set_error_literal (error,
625 FWUPD_ERROR,
626 FWUPD_ERROR_INVALID_ARGS,
627 "Invalid arguments");
628 return FALSE;
629 }
630
Richard Hughes3d178be2018-08-30 11:14:24 +0100631 /* download if required */
632 filename = fu_util_download_if_required (priv, values[0], error);
633 if (filename == NULL)
634 return FALSE;
635
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100636 /* parse store */
Richard Hughes3d178be2018-08-30 11:14:24 +0100637 blob_cab = fu_common_get_contents_bytes (filename, error);
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500638 if (blob_cab == NULL) {
Richard Hughes3d178be2018-08-30 11:14:24 +0100639 fu_util_maybe_prefix_sandbox_error (filename, error);
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100640 return FALSE;
Mario Limonciellob72aa8c2018-06-08 09:24:36 -0500641 }
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100642 store = fu_engine_get_store_from_blob (priv->engine, blob_cab, error);
643 if (store == NULL)
644 return FALSE;
645 apps = as_store_get_apps (store);
646
647 /* for each component in the store */
648 errors = g_ptr_array_new_with_free_func ((GDestroyNotify) g_error_free);
649 install_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
650 for (guint i = 0; i < apps->len; i++) {
651 AsApp *app = g_ptr_array_index (apps, i);
652
653 /* do any devices pass the requirements */
654 for (guint j = 0; j < devices_possible->len; j++) {
655 FuDevice *device = g_ptr_array_index (devices_possible, j);
656 g_autoptr(FuInstallTask) task = NULL;
657 g_autoptr(GError) error_local = NULL;
658
659 /* is this component valid for the device */
660 task = fu_install_task_new (device, app);
661 if (!fu_engine_check_requirements (priv->engine,
Richard Hughes460226a2018-05-21 20:56:21 +0100662 task, priv->flags,
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100663 &error_local)) {
664 g_debug ("requirement on %s:%s failed: %s",
665 fu_device_get_id (device),
666 as_app_get_id (app),
667 error_local->message);
668 g_ptr_array_add (errors, g_steal_pointer (&error_local));
669 continue;
670 }
671
672 /* success */
673 g_ptr_array_add (install_tasks, g_steal_pointer (&task));
674 }
675 }
676
677 /* order the install tasks by the device priority */
678 g_ptr_array_sort (install_tasks, fu_util_install_task_sort_cb);
679
680 /* nothing suitable */
681 if (install_tasks->len == 0) {
682 GError *error_tmp = fu_common_error_array_get_best (errors);
683 g_propagate_error (error, error_tmp);
684 return FALSE;
685 }
686
Mario Limonciello9eb66fe2018-08-10 11:32:44 -0500687 g_signal_connect (priv->engine, "device-changed",
688 G_CALLBACK (fu_util_install_device_changed_cb), priv);
689
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100690 /* install all the tasks */
Richard Hughesdbd8c762018-06-15 20:31:40 +0100691 if (!fu_engine_install_tasks (priv->engine, install_tasks, blob_cab, priv->flags, error))
692 return FALSE;
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100693
694 /* success */
695 return TRUE;
696}
697
Richard Hughes98ca9932018-05-18 10:24:07 +0100698static gboolean
699fu_util_detach (FuUtilPrivate *priv, gchar **values, GError **error)
700{
701 g_autoptr(FuDevice) device = NULL;
Richard Hughes2a679cd2018-09-04 21:13:23 +0100702 g_autoptr(FuDeviceLocker) locker = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +0100703
704 /* load engine */
705 if (!fu_engine_load (priv->engine, error))
706 return FALSE;
707
708 /* invalid args */
709 if (g_strv_length (values) == 0) {
710 g_set_error_literal (error,
711 FWUPD_ERROR,
712 FWUPD_ERROR_INVALID_ARGS,
713 "Invalid arguments");
714 return FALSE;
715 }
716
717 /* get device */
718 if (g_strv_length (values) >= 1) {
719 device = fu_engine_get_device (priv->engine, values[0], error);
720 if (device == NULL)
721 return FALSE;
722 } else {
723 device = fu_util_prompt_for_device (priv, error);
724 if (device == NULL)
725 return FALSE;
726 }
727
728 /* run vfunc */
Richard Hughes2a679cd2018-09-04 21:13:23 +0100729 locker = fu_device_locker_new (device, error);
730 if (locker == NULL)
731 return FALSE;
Richard Hughes98ca9932018-05-18 10:24:07 +0100732 return fu_device_detach (device, error);
733}
734
735static gboolean
736fu_util_attach (FuUtilPrivate *priv, gchar **values, GError **error)
737{
738 g_autoptr(FuDevice) device = NULL;
Richard Hughes2a679cd2018-09-04 21:13:23 +0100739 g_autoptr(FuDeviceLocker) locker = NULL;
Richard Hughes98ca9932018-05-18 10:24:07 +0100740
741 /* load engine */
742 if (!fu_engine_load (priv->engine, error))
743 return FALSE;
744
745 /* invalid args */
746 if (g_strv_length (values) == 0) {
747 g_set_error_literal (error,
748 FWUPD_ERROR,
749 FWUPD_ERROR_INVALID_ARGS,
750 "Invalid arguments");
751 return FALSE;
752 }
753
754 /* get device */
755 if (g_strv_length (values) >= 1) {
756 device = fu_engine_get_device (priv->engine, values[0], error);
757 if (device == NULL)
758 return FALSE;
759 } else {
760 device = fu_util_prompt_for_device (priv, error);
761 if (device == NULL)
762 return FALSE;
763 }
764
765 /* run vfunc */
Richard Hughes2a679cd2018-09-04 21:13:23 +0100766 locker = fu_device_locker_new (device, error);
767 if (locker == NULL)
768 return FALSE;
Richard Hughes98ca9932018-05-18 10:24:07 +0100769 return fu_device_attach (device, error);
770}
771
Richard Hughes1d894f12018-08-31 13:05:51 +0100772static gboolean
773fu_util_hwids (FuUtilPrivate *priv, gchar **values, GError **error)
774{
775 g_autoptr(FuSmbios) smbios = fu_smbios_new ();
776 g_autoptr(FuHwids) hwids = fu_hwids_new ();
777 const gchar *hwid_keys[] = {
778 FU_HWIDS_KEY_BIOS_VENDOR,
779 FU_HWIDS_KEY_BIOS_VERSION,
780 FU_HWIDS_KEY_BIOS_MAJOR_RELEASE,
781 FU_HWIDS_KEY_BIOS_MINOR_RELEASE,
782 FU_HWIDS_KEY_MANUFACTURER,
783 FU_HWIDS_KEY_FAMILY,
784 FU_HWIDS_KEY_PRODUCT_NAME,
785 FU_HWIDS_KEY_PRODUCT_SKU,
786 FU_HWIDS_KEY_ENCLOSURE_KIND,
787 FU_HWIDS_KEY_BASEBOARD_MANUFACTURER,
788 FU_HWIDS_KEY_BASEBOARD_PRODUCT,
789 NULL };
790
791 /* read DMI data */
792 if (g_strv_length (values) == 0) {
793 if (!fu_smbios_setup (smbios, error))
794 return FALSE;
795 } else if (g_strv_length (values) == 1) {
796 if (!fu_smbios_setup_from_file (smbios, values[0], error))
797 return FALSE;
798 } else {
799 g_set_error_literal (error,
800 FWUPD_ERROR,
801 FWUPD_ERROR_INVALID_ARGS,
802 "Invalid arguments");
803 return FALSE;
804 }
805 if (!fu_hwids_setup (hwids, smbios, error))
806 return FALSE;
807
808 /* show debug output */
809 g_print ("Computer Information\n");
810 g_print ("--------------------\n");
811 for (guint i = 0; hwid_keys[i] != NULL; i++) {
812 const gchar *tmp = fu_hwids_get_value (hwids, hwid_keys[i]);
813 if (tmp == NULL)
814 continue;
815 if (g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MAJOR_RELEASE) == 0 ||
816 g_strcmp0 (hwid_keys[i], FU_HWIDS_KEY_BIOS_MINOR_RELEASE) == 0) {
817 guint64 val = g_ascii_strtoull (tmp, NULL, 16);
818 g_print ("%s: %" G_GUINT64_FORMAT "\n", hwid_keys[i], val);
819 } else {
820 g_print ("%s: %s\n", hwid_keys[i], tmp);
821 }
822 }
823
824 /* show GUIDs */
825 g_print ("\nHardware IDs\n");
826 g_print ("------------\n");
827 for (guint i = 0; i < 15; i++) {
828 const gchar *keys = NULL;
829 g_autofree gchar *guid = NULL;
830 g_autofree gchar *key = NULL;
831 g_autofree gchar *keys_str = NULL;
832 g_auto(GStrv) keysv = NULL;
833 g_autoptr(GError) error_local = NULL;
834
835 /* get the GUID */
836 key = g_strdup_printf ("HardwareID-%u", i);
837 keys = fu_hwids_get_replace_keys (hwids, key);
838 guid = fu_hwids_get_guid (hwids, key, &error_local);
839 if (guid == NULL) {
840 g_print ("%s\n", error_local->message);
841 continue;
842 }
843
844 /* show what makes up the GUID */
845 keysv = g_strsplit (keys, "&", -1);
846 keys_str = g_strjoinv (" + ", keysv);
847 g_print ("{%s} <- %s\n", guid, keys_str);
848 }
849
850 return TRUE;
851}
852
Richard Hughesb5976832018-05-18 10:02:09 +0100853int
854main (int argc, char *argv[])
855{
Richard Hughes460226a2018-05-21 20:56:21 +0100856 gboolean allow_older = FALSE;
857 gboolean allow_reinstall = FALSE;
Richard Hughesb5976832018-05-18 10:02:09 +0100858 gboolean force = FALSE;
859 gboolean ret;
860 gboolean verbose = FALSE;
Richard Hughesc02ee4d2018-05-22 15:46:03 +0100861 g_auto(GStrv) plugin_glob = NULL;
Richard Hughesb5976832018-05-18 10:02:09 +0100862 g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1);
863 g_autoptr(GError) error = NULL;
864 g_autofree gchar *cmd_descriptions = NULL;
865 const GOptionEntry options[] = {
866 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
867 /* TRANSLATORS: command line option */
868 _("Show extra debugging information"), NULL },
Richard Hughes460226a2018-05-21 20:56:21 +0100869 { "allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall,
870 /* TRANSLATORS: command line option */
871 _("Allow re-installing existing firmware versions"), NULL },
872 { "allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older,
873 /* TRANSLATORS: command line option */
874 _("Allow downgrading firmware versions"), NULL },
Richard Hughesb5976832018-05-18 10:02:09 +0100875 { "force", '\0', 0, G_OPTION_ARG_NONE, &force,
876 /* TRANSLATORS: command line option */
877 _("Override plugin warning"), NULL },
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500878 { "show-all-devices", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all_devices,
879 /* TRANSLATORS: command line option */
880 _("Show devices that are not updatable"), NULL },
Richard Hughesc02ee4d2018-05-22 15:46:03 +0100881 { "plugin-whitelist", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &plugin_glob,
882 /* TRANSLATORS: command line option */
883 _("Manually whitelist specific plugins"), NULL },
Richard Hughesb5976832018-05-18 10:02:09 +0100884 { NULL}
885 };
886
887 setlocale (LC_ALL, "");
888
889 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
890 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
891 textdomain (GETTEXT_PACKAGE);
892
893 /* ensure root user */
Mario Limonciellob900c092018-05-22 14:22:21 -0500894 if (getuid () != 0 || geteuid () != 0)
Richard Hughesb5976832018-05-18 10:02:09 +0100895 /* TRANSLATORS: we're poking around as a power user */
Mario Limonciellob900c092018-05-22 14:22:21 -0500896 g_printerr ("%s\n", _("This program may only work correctly as root"));
Richard Hughesb5976832018-05-18 10:02:09 +0100897
898 /* create helper object */
899 priv->loop = g_main_loop_new (NULL, FALSE);
900 priv->progressbar = fu_progressbar_new ();
901
902 /* add commands */
903 priv->cmd_array = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_util_item_free);
904 fu_util_add (priv->cmd_array,
905 "smbios-dump",
906 "FILE",
907 /* TRANSLATORS: command description */
908 _("Dump SMBIOS data from a file"),
909 fu_util_smbios_dump);
Richard Hughes98ca9932018-05-18 10:24:07 +0100910 fu_util_add (priv->cmd_array,
Richard Hughes8c71a3f2018-05-22 19:19:52 +0100911 "get-plugins",
912 NULL,
913 /* TRANSLATORS: command description */
914 _("Get all enabled plugins registered with the system"),
915 fu_util_get_plugins);
916 fu_util_add (priv->cmd_array,
Mario Limonciello716ab272018-05-29 12:34:37 -0500917 "get-details",
918 NULL,
919 /* TRANSLATORS: command description */
920 _("Gets details about a firmware file"),
921 fu_util_get_details);
922 fu_util_add (priv->cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +0100923 "get-devices",
924 NULL,
925 /* TRANSLATORS: command description */
926 _("Get all devices that support firmware updates"),
927 fu_util_get_devices);
928 fu_util_add (priv->cmd_array,
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500929 "get-topology",
930 NULL,
931 /* TRANSLATORS: command description */
932 _("Get all devices according to the system topology"),
933 fu_util_get_topology);
934 fu_util_add (priv->cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +0100935 "watch",
936 NULL,
937 /* TRANSLATORS: command description */
Piotr Drąg472fa592018-06-06 14:53:48 +0200938 _("Watch for hardware changes"),
Richard Hughes98ca9932018-05-18 10:24:07 +0100939 fu_util_watch);
940 fu_util_add (priv->cmd_array,
941 "install-blob",
942 "FILENAME DEVICE-ID",
943 /* TRANSLATORS: command description */
944 _("Install a firmware blob on a device"),
945 fu_util_install_blob);
946 fu_util_add (priv->cmd_array,
Richard Hughesa36c9cf2018-05-20 10:41:44 +0100947 "install",
948 "FILE [ID]",
949 /* TRANSLATORS: command description */
950 _("Install a firmware file on this hardware"),
951 fu_util_install);
952 fu_util_add (priv->cmd_array,
Richard Hughes98ca9932018-05-18 10:24:07 +0100953 "attach",
954 "DEVICE-ID",
955 /* TRANSLATORS: command description */
956 _("Attach to firmware mode"),
957 fu_util_attach);
958 fu_util_add (priv->cmd_array,
959 "detach",
960 "DEVICE-ID",
961 /* TRANSLATORS: command description */
962 _("Detach to bootloader mode"),
963 fu_util_detach);
Richard Hughes1d894f12018-08-31 13:05:51 +0100964 fu_util_add (priv->cmd_array,
965 "hwids",
966 "[FILE]",
967 /* TRANSLATORS: command description */
968 _("Return all the hardware IDs for the machine"),
969 fu_util_hwids);
Richard Hughesb5976832018-05-18 10:02:09 +0100970
971 /* do stuff on ctrl+c */
972 priv->cancellable = g_cancellable_new ();
973 g_unix_signal_add_full (G_PRIORITY_DEFAULT,
974 SIGINT, fu_util_sigint_cb,
975 priv, NULL);
976 g_signal_connect (priv->cancellable, "cancelled",
977 G_CALLBACK (fu_util_cancelled_cb), priv);
978
979 /* sort by command name */
980 g_ptr_array_sort (priv->cmd_array,
981 (GCompareFunc) fu_sort_command_name_cb);
982
983 /* get a list of the commands */
984 priv->context = g_option_context_new (NULL);
985 cmd_descriptions = fu_util_get_descriptions (priv->cmd_array);
986 g_option_context_set_summary (priv->context, cmd_descriptions);
987 g_option_context_set_description (priv->context,
988 "This tool allows an administrator to use the fwupd plugins "
989 "without being installed on the host system.");
990
991 /* TRANSLATORS: program name */
992 g_set_application_name (_("Firmware Utility"));
993 g_option_context_add_main_entries (priv->context, options, NULL);
994 ret = g_option_context_parse (priv->context, &argc, &argv, &error);
995 if (!ret) {
996 /* TRANSLATORS: the user didn't read the man page */
997 g_print ("%s: %s\n", _("Failed to parse arguments"),
998 error->message);
999 return EXIT_FAILURE;
1000 }
1001
1002 /* set verbose? */
1003 if (verbose) {
1004 g_setenv ("G_MESSAGES_DEBUG", "all", FALSE);
1005 } else {
1006 g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
1007 fu_util_ignore_cb, NULL);
1008 }
1009
Richard Hughes460226a2018-05-21 20:56:21 +01001010 /* set flags */
1011 priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY;
1012 if (allow_reinstall)
1013 priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL;
1014 if (allow_older)
1015 priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER;
1016 if (force)
1017 priv->flags |= FWUPD_INSTALL_FLAG_FORCE;
1018
Richard Hughes98ca9932018-05-18 10:24:07 +01001019 /* load engine */
1020 priv->engine = fu_engine_new (FU_APP_FLAGS_NO_IDLE_SOURCES);
1021 g_signal_connect (priv->engine, "device-added",
1022 G_CALLBACK (fu_main_engine_device_added_cb),
1023 priv);
1024 g_signal_connect (priv->engine, "device-removed",
1025 G_CALLBACK (fu_main_engine_device_removed_cb),
1026 priv);
Richard Hughes98ca9932018-05-18 10:24:07 +01001027 g_signal_connect (priv->engine, "status-changed",
1028 G_CALLBACK (fu_main_engine_status_changed_cb),
1029 priv);
1030 g_signal_connect (priv->engine, "percentage-changed",
1031 G_CALLBACK (fu_main_engine_percentage_changed_cb),
1032 priv);
1033
Richard Hughesc02ee4d2018-05-22 15:46:03 +01001034 /* any plugin whitelist specified */
1035 for (guint i = 0; plugin_glob != NULL && plugin_glob[i] != NULL; i++)
1036 fu_engine_add_plugin_filter (priv->engine, plugin_glob[i]);
1037
Richard Hughesb5976832018-05-18 10:02:09 +01001038 /* run the specified command */
1039 ret = fu_util_run (priv, argv[1], (gchar**) &argv[2], &error);
1040 if (!ret) {
1041 if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) {
1042 g_autofree gchar *tmp = NULL;
1043 tmp = g_option_context_get_help (priv->context, TRUE, NULL);
1044 g_print ("%s\n\n%s", error->message, tmp);
1045 return EXIT_FAILURE;
1046 }
1047 if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
1048 g_print ("%s\n", error->message);
1049 return EXIT_NOTHING_TO_DO;
1050 }
1051 g_print ("%s\n", error->message);
1052 return EXIT_FAILURE;
1053 }
1054
1055 /* success */
1056 return EXIT_SUCCESS;
1057}