blob: 7aa3d87aca36d1b48d66cf1cb69dd409a7abb7a0 [file] [log] [blame]
Richard Hughes02c90d82018-08-09 12:13:03 +01001/*
Richard Hughes5c9b1fc2021-01-07 14:20:49 +00002 * Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
Richard Hughesf7616402018-05-18 09:53:18 +01003 *
Mario Limonciello51308e62018-05-28 20:05:46 -05004 * SPDX-License-Identifier: LGPL-2.1+
Richard Hughesf7616402018-05-18 09:53:18 +01005 */
6
Mario Limonciello0887b672019-08-27 10:16:18 -05007#define G_LOG_DOMAIN "FuMain"
8
Richard Hughesf7616402018-05-18 09:53:18 +01009#include <config.h>
Mario Limonciello2d4b7a52018-09-12 22:08:04 -050010
Richard Hughesf7616402018-05-18 09:53:18 +010011#include <stdio.h>
12#include <glib/gi18n.h>
Richard Hughes1a3d3b32021-01-13 18:43:44 +000013#ifdef HAVE_GUSB
Mario Limonciello2d4b7a52018-09-12 22:08:04 -050014#include <gusb.h>
Richard Hughes1a3d3b32021-01-13 18:43:44 +000015#endif
Mario Limonciellofee8f492019-08-18 12:16:07 -050016#include <xmlb.h>
Richard Hughes7bcb8d42020-10-08 15:47:47 +010017#include <fwupd.h>
Richard Hughes67473f12021-01-07 17:02:23 +000018#ifdef HAVE_LIBCURL
Richard Hughes3a73c342020-11-13 13:25:22 +000019#include <curl/curl.h>
Richard Hughes67473f12021-01-07 17:02:23 +000020#endif
Richard Hughesf7616402018-05-18 09:53:18 +010021
Mario Limonciellofee8f492019-08-18 12:16:07 -050022#include "fu-common.h"
Mario Limonciello02085a02020-09-11 14:59:35 -050023#include "fu-device-private.h"
Richard Hughesf7616402018-05-18 09:53:18 +010024#include "fu-util-common.h"
Mario Limoncielloba9e5b92018-05-21 16:02:32 -050025#include "fu-device.h"
Richard Hughesb246bca2020-05-18 14:31:35 +010026#include "fu-security-attr.h"
Richard Hughes196c6c62020-05-11 19:42:47 +010027#include "fu-security-attrs.h"
Richard Hughesf7616402018-05-18 09:53:18 +010028
Richard Hughes3d005222019-05-17 14:02:41 +010029#ifdef HAVE_SYSTEMD
30#include "fu-systemd.h"
31#endif
32
Richard Hughesfb9cfff2019-04-17 15:01:03 +010033#define SYSTEMD_FWUPD_UNIT "fwupd.service"
Mario Limoncielloea527ca2019-05-07 15:50:46 -050034#define SYSTEMD_SNAP_FWUPD_UNIT "snap.fwupd.fwupd.service"
35
Richard Hughes3d005222019-05-17 14:02:41 +010036const gchar *
Mario Limoncielloea527ca2019-05-07 15:50:46 -050037fu_util_get_systemd_unit (void)
38{
39 if (g_getenv ("SNAP") != NULL)
40 return SYSTEMD_SNAP_FWUPD_UNIT;
41 return SYSTEMD_FWUPD_UNIT;
42}
Richard Hughesfb9cfff2019-04-17 15:01:03 +010043
Richard Hughes7bcb8d42020-10-08 15:47:47 +010044gchar *
45fu_util_term_format (const gchar *text, FuUtilTermColor fg_color)
46{
47 return g_strdup_printf ("\033[%um\033[1m%s\033[0m", fg_color, text);
48}
49
Richard Hughesd92ccca2019-05-20 11:28:31 +010050#ifdef HAVE_SYSTEMD
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050051static const gchar *
52fu_util_get_expected_command (const gchar *target)
53{
Richard Hughesad755e52019-05-16 12:53:15 +010054 if (g_strcmp0 (target, SYSTEMD_SNAP_FWUPD_UNIT) == 0)
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050055 return "fwupd.fwupdmgr";
56 return "fwupdmgr";
57}
Richard Hughesd92ccca2019-05-20 11:28:31 +010058#endif
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050059
60gboolean
61fu_util_using_correct_daemon (GError **error)
62{
Richard Hughesd92ccca2019-05-20 11:28:31 +010063#ifdef HAVE_SYSTEMD
Richard Hughes3d005222019-05-17 14:02:41 +010064 g_autofree gchar *default_target = NULL;
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050065 g_autoptr(GError) error_local = NULL;
66 const gchar *target = fu_util_get_systemd_unit ();
67
Richard Hughes3d005222019-05-17 14:02:41 +010068 default_target = fu_systemd_get_default_target (&error_local);
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050069 if (default_target == NULL) {
70 g_debug ("Systemd isn't accessible: %s\n", error_local->message);
71 return TRUE;
72 }
Richard Hughes3d005222019-05-17 14:02:41 +010073 if (!fu_systemd_unit_check_exists (target, &error_local)) {
74 g_debug ("wrong target: %s\n", error_local->message);
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050075 g_set_error (error,
76 FWUPD_ERROR,
77 FWUPD_ERROR_INVALID_ARGS,
78 /* TRANSLATORS: error message */
79 _("Mismatched daemon and client, use %s instead"),
80 fu_util_get_expected_command (target));
81 return FALSE;
82 }
Richard Hughesd92ccca2019-05-20 11:28:31 +010083#endif
Mario Limonciello88f8b7f2019-05-07 15:52:39 -050084 return TRUE;
85}
86
Richard Hughesf7616402018-05-18 09:53:18 +010087void
88fu_util_print_data (const gchar *title, const gchar *msg)
89{
90 gsize title_len;
91 g_auto(GStrv) lines = NULL;
92
93 if (msg == NULL)
94 return;
95 g_print ("%s:", title);
96
97 /* pad */
Richard Hughesae96a1f2019-09-23 11:16:36 +010098 title_len = fu_common_strwidth (title) + 1;
Richard Hughesf7616402018-05-18 09:53:18 +010099 lines = g_strsplit (msg, "\n", -1);
100 for (guint j = 0; lines[j] != NULL; j++) {
101 for (gsize i = title_len; i < 25; i++)
102 g_print (" ");
103 g_print ("%s\n", lines[j]);
104 title_len = 0;
105 }
106}
107
108guint
109fu_util_prompt_for_number (guint maxnum)
110{
111 gint retval;
112 guint answer = 0;
113
114 do {
115 char buffer[64];
116
117 /* swallow the \n at end of line too */
118 if (!fgets (buffer, sizeof (buffer), stdin))
119 break;
120 if (strlen (buffer) == sizeof (buffer) - 1)
121 continue;
122
123 /* get a number */
124 retval = sscanf (buffer, "%u", &answer);
125
126 /* positive */
127 if (retval == 1 && answer <= maxnum)
128 break;
129
130 /* TRANSLATORS: the user isn't reading the question */
131 g_print (_("Please enter a number from 0 to %u: "), maxnum);
132 } while (TRUE);
133 return answer;
134}
135
136gboolean
137fu_util_prompt_for_boolean (gboolean def)
138{
139 do {
140 char buffer[4];
141 if (!fgets (buffer, sizeof (buffer), stdin))
142 continue;
143 if (strlen (buffer) == sizeof (buffer) - 1)
144 continue;
145 if (g_strcmp0 (buffer, "\n") == 0)
146 return def;
147 buffer[0] = g_ascii_toupper (buffer[0]);
148 if (g_strcmp0 (buffer, "Y\n") == 0)
149 return TRUE;
150 if (g_strcmp0 (buffer, "N\n") == 0)
151 return FALSE;
152 } while (TRUE);
153 return FALSE;
154}
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500155
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500156static gboolean
157fu_util_traverse_tree (GNode *n, gpointer data)
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500158{
Richard Hughesc67b4e72019-08-27 09:48:42 +0100159 guint idx = g_node_depth (n) - 1;
160 g_autofree gchar *tmp = NULL;
161 g_auto(GStrv) split = NULL;
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500162
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500163 /* get split lines */
164 if (FWUPD_IS_DEVICE (n->data)) {
165 FwupdDevice *dev = FWUPD_DEVICE (n->data);
166 tmp = fu_util_device_to_string (dev, idx);
167 } else if (FWUPD_IS_REMOTE (n->data)) {
168 FwupdRemote *remote = FWUPD_REMOTE (n->data);
169 tmp = fu_util_remote_to_string (remote, idx);
170 } else if (FWUPD_IS_RELEASE (n->data)) {
171 FwupdRelease *release = FWUPD_RELEASE (n->data);
172 tmp = fu_util_release_to_string (release, idx);
173 g_debug ("%s", tmp);
174 }
175
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500176 /* root node */
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500177 if (n->data == NULL && g_getenv ("FWUPD_VERBOSE") == NULL) {
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500178 const gchar *str = data;
179 g_print ("%s\n│\n", str != NULL ? str : "○");
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500180 return FALSE;
181 }
Mario Limonciello20cc9ee2019-09-05 07:27:26 -0500182
Richard Hughesc67b4e72019-08-27 09:48:42 +0100183 if (n->parent == NULL)
184 return FALSE;
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500185
Mario Limonciello17e9bf52019-08-27 09:03:19 -0500186 if (tmp == NULL)
187 return FALSE;
Richard Hughesc67b4e72019-08-27 09:48:42 +0100188 split = g_strsplit (tmp, "\n", -1);
189 for (guint i = 0; split[i] != NULL; i++) {
190 g_autoptr(GString) str = g_string_new (NULL);
191
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500192 /* header */
Richard Hughesc67b4e72019-08-27 09:48:42 +0100193 if (i == 0) {
194 if (g_node_next_sibling (n) == NULL)
195 g_string_prepend (str, "└─");
196 else
197 g_string_prepend (str, "├─");
198
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500199 /* properties */
Richard Hughesc67b4e72019-08-27 09:48:42 +0100200 } else {
201 g_string_prepend (str, n->children == NULL ? " " : " │");
202 g_string_prepend (str, g_node_next_sibling (n) == NULL ? " " : "│");
203 g_string_append (str, " ");
204 }
205
206 /* ancestors */
207 for (GNode *c = n->parent; c->parent != NULL; c = c->parent) {
208 if (g_node_next_sibling (c) != NULL || idx == 0) {
209 g_string_prepend (str, "│ ");
210 continue;
211 }
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500212 g_string_prepend (str, " ");
Richard Hughesc67b4e72019-08-27 09:48:42 +0100213 }
214
215 /* empty line */
216 if (split[i][0] == '\0') {
217 g_print ("%s\n", str->str);
218 continue;
219 }
220
221 /* dump to the console */
222 g_string_append (str, split[i] + (idx * 2));
223 g_print ("%s\n", str->str);
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500224 }
225
Mario Limoncielloba9e5b92018-05-21 16:02:32 -0500226 return FALSE;
227}
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500228
Mario Limonciello4250d9d2019-08-29 09:53:44 -0500229void
230fu_util_print_tree (GNode *n, gpointer data)
231{
232 g_node_traverse (n, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
233 fu_util_traverse_tree, data);
234}
235
Richard Hughes95c78762020-01-06 14:02:38 +0000236static gboolean
237fu_util_is_interesting_child (FwupdDevice *dev)
238{
239 GPtrArray *children = fwupd_device_get_children (dev);
240 for (guint i = 0; i < children->len; i++) {
241 FwupdDevice *child = g_ptr_array_index (children, i);
242 if (fu_util_is_interesting_device (child))
243 return TRUE;
244 }
245 return FALSE;
246}
247
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500248gboolean
249fu_util_is_interesting_device (FwupdDevice *dev)
250{
251 if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE))
252 return TRUE;
253 if (fwupd_device_get_update_error (dev) != NULL)
254 return TRUE;
Mario Limonciello3996af32019-09-05 10:28:52 -0500255 /* device not plugged in, get-details */
256 if (fwupd_device_get_flags (dev) == 0)
257 return TRUE;
Richard Hughes95c78762020-01-06 14:02:38 +0000258 if (fu_util_is_interesting_child (dev))
259 return TRUE;
Mario Limonciellod1775bc2018-07-17 00:28:52 -0500260 return FALSE;
261}
Richard Hughes798cb062018-08-30 14:17:42 +0100262
263gchar *
264fu_util_get_user_cache_path (const gchar *fn)
265{
Mario Limonciellob390b142019-07-15 21:05:44 -0500266 const gchar *root = g_get_user_cache_dir ();
Richard Hughes798cb062018-08-30 14:17:42 +0100267 g_autofree gchar *basename = g_path_get_basename (fn);
268 g_autofree gchar *cachedir_legacy = NULL;
269
Mario Limonciellob390b142019-07-15 21:05:44 -0500270 /* if run from a systemd unit, use the cache directory set there */
271 if (g_getenv ("CACHE_DIRECTORY") != NULL)
272 root = g_getenv ("CACHE_DIRECTORY");
273
Richard Hughes798cb062018-08-30 14:17:42 +0100274 /* return the legacy path if it exists rather than renaming it to
275 * prevent problems when using old and new versions of fwupd */
Mario Limonciellob390b142019-07-15 21:05:44 -0500276 cachedir_legacy = g_build_filename (root, "fwupdmgr", NULL);
Richard Hughes798cb062018-08-30 14:17:42 +0100277 if (g_file_test (cachedir_legacy, G_FILE_TEST_IS_DIR))
278 return g_build_filename (cachedir_legacy, basename, NULL);
279
Mario Limonciellob390b142019-07-15 21:05:44 -0500280 return g_build_filename (root, "fwupd", basename, NULL);
Richard Hughes798cb062018-08-30 14:17:42 +0100281}
Mario Limonciello2d4b7a52018-09-12 22:08:04 -0500282
283gchar *
Mario Limonciello429a5122019-10-31 10:45:31 -0500284fu_util_get_versions (void)
285{
286 GString *string = g_string_new ("");
Mario Limonciello429a5122019-10-31 10:45:31 -0500287
Richard Hughesfe4b3ea2020-03-30 10:53:20 +0100288 g_string_append_printf (string, "client version:\t%s\n", SOURCE_VERSION);
Mario Limonciello2d4b7a52018-09-12 22:08:04 -0500289 g_string_append_printf (string,
290 "compile-time dependency versions\n");
Richard Hughes1a3d3b32021-01-13 18:43:44 +0000291#ifdef HAVE_GUSB
Mario Limonciello2d4b7a52018-09-12 22:08:04 -0500292 g_string_append_printf (string,
Mario Limonciello2d4b7a52018-09-12 22:08:04 -0500293 "\tgusb:\t%d.%d.%d\n",
294 G_USB_MAJOR_VERSION,
295 G_USB_MINOR_VERSION,
296 G_USB_MICRO_VERSION);
Richard Hughes1a3d3b32021-01-13 18:43:44 +0000297#endif
Mario Limonciello2d4b7a52018-09-12 22:08:04 -0500298#ifdef EFIVAR_LIBRARY_VERSION
299 g_string_append_printf (string,
300 "\tefivar:\t%s",
301 EFIVAR_LIBRARY_VERSION);
302#endif
303 return g_string_free (string, FALSE);
304}
Mario Limonciello3f243a92019-01-21 22:05:23 -0600305
306static gboolean
307fu_util_update_shutdown (GError **error)
308{
309 g_autoptr(GDBusConnection) connection = NULL;
310 g_autoptr(GVariant) val = NULL;
311
312 connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
313 if (connection == NULL)
314 return FALSE;
315
Marcin Sucharskide2b70b2019-02-24 00:28:10 +0100316#ifdef HAVE_LOGIND
Mario Limonciello3f243a92019-01-21 22:05:23 -0600317 /* shutdown using logind */
318 val = g_dbus_connection_call_sync (connection,
319 "org.freedesktop.login1",
320 "/org/freedesktop/login1",
321 "org.freedesktop.login1.Manager",
322 "PowerOff",
323 g_variant_new ("(b)", TRUE),
324 NULL,
325 G_DBUS_CALL_FLAGS_NONE,
326 -1,
327 NULL,
328 error);
329#elif defined(HAVE_CONSOLEKIT)
330 /* shutdown using ConsoleKit */
331 val = g_dbus_connection_call_sync (connection,
332 "org.freedesktop.ConsoleKit",
333 "/org/freedesktop/ConsoleKit/Manager",
334 "org.freedesktop.ConsoleKit.Manager",
335 "Stop",
336 NULL,
337 NULL,
338 G_DBUS_CALL_FLAGS_NONE,
339 -1,
340 NULL,
341 error);
342#else
343 g_set_error_literal (error,
344 FWUPD_ERROR,
345 FWUPD_ERROR_INVALID_ARGS,
346 "No supported backend compiled in to perform the operation.");
347#endif
348 return val != NULL;
349}
350
Richard Hughes4499d192019-03-08 11:44:44 +0000351gboolean
Mario Limonciello3f243a92019-01-21 22:05:23 -0600352fu_util_update_reboot (GError **error)
353{
354 g_autoptr(GDBusConnection) connection = NULL;
355 g_autoptr(GVariant) val = NULL;
356
357 connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
358 if (connection == NULL)
359 return FALSE;
360
Marcin Sucharskide2b70b2019-02-24 00:28:10 +0100361#ifdef HAVE_LOGIND
Mario Limonciello3f243a92019-01-21 22:05:23 -0600362 /* reboot using logind */
363 val = g_dbus_connection_call_sync (connection,
364 "org.freedesktop.login1",
365 "/org/freedesktop/login1",
366 "org.freedesktop.login1.Manager",
367 "Reboot",
368 g_variant_new ("(b)", TRUE),
369 NULL,
370 G_DBUS_CALL_FLAGS_NONE,
371 -1,
372 NULL,
373 error);
374#elif defined(HAVE_CONSOLEKIT)
375 /* reboot using ConsoleKit */
376 val = g_dbus_connection_call_sync (connection,
377 "org.freedesktop.ConsoleKit",
378 "/org/freedesktop/ConsoleKit/Manager",
379 "org.freedesktop.ConsoleKit.Manager",
380 "Restart",
381 NULL,
382 NULL,
383 G_DBUS_CALL_FLAGS_NONE,
384 -1,
385 NULL,
386 error);
387#else
388 g_set_error_literal (error,
389 FWUPD_ERROR,
390 FWUPD_ERROR_INVALID_ARGS,
391 "No supported backend compiled in to perform the operation.");
392#endif
393 return val != NULL;
394}
395
396gboolean
Mario Limonciello98b95162019-10-30 09:20:43 -0500397fu_util_prompt_warning (FwupdDevice *device, const gchar *machine, GError **error)
398{
399 FwupdDeviceFlags flags;
400 g_autofree gchar *str = NULL;
401
402 /* device is already in bootloader mode */
403 flags = fwupd_device_get_flags (device);
404 if (flags & FWUPD_DEVICE_FLAG_IS_BOOTLOADER)
405 return TRUE;
406
407 /* device may reboot */
408 if ((flags & FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) == 0) {
409 /* TRANSLATORS: warn the user before updating, %1 is a device name */
410 str = g_strdup_printf (_("%s and all connected devices may not be usable while updating."),
411 fwupd_device_get_name (device));
412 /* device can get bricked */
413 } else if ((flags & FWUPD_DEVICE_FLAG_SELF_RECOVERY) == 0) {
414 /* external device */
415 if ((flags & FWUPD_DEVICE_FLAG_INTERNAL) == 0) {
416 /* TRANSLATORS: warn the user before updating, %1 is a device name */
417 str = g_strdup_printf (_("%s must remain connected for the duration of the update to avoid damage."),
418 fwupd_device_get_name (device));
419 } else if (flags & FWUPD_DEVICE_FLAG_REQUIRE_AC) {
420 /* TRANSLATORS: warn the user before updating, %1 is a machine name */
421 str = g_strdup_printf (_("%s must remain plugged into a power source for the duration of the update to avoid damage."),
422 machine);
423 }
424 }
425 if (str != NULL) {
426 g_print ("%s %s [Y|n]: ",
427 str,
428 /* TRANSLATORS: prompt to apply the update */
429 _("Continue with update?"));
430 if (!fu_util_prompt_for_boolean (TRUE)) {
431 g_set_error_literal (error,
432 FWUPD_ERROR,
433 FWUPD_ERROR_NOTHING_TO_DO,
434 "Request canceled");
435 return FALSE;
436 }
437 }
438
439 return TRUE;
440}
441
442gboolean
Mario Limonciello3f243a92019-01-21 22:05:23 -0600443fu_util_prompt_complete (FwupdDeviceFlags flags, gboolean prompt, GError **error)
444{
445 if (flags & FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) {
446 if (prompt) {
Mario Limonciello5d94afa2019-12-11 06:45:49 -0600447 g_print ("\n%s %s [y|N]: ",
Mario Limonciello3f243a92019-01-21 22:05:23 -0600448 /* TRANSLATORS: explain why we want to shutdown */
449 _("An update requires the system to shutdown to complete."),
450 /* TRANSLATORS: shutdown to apply the update */
451 _("Shutdown now?"));
Mario Limoncielloc5efb212019-11-25 07:45:18 -0600452 if (!fu_util_prompt_for_boolean (FALSE))
Mario Limonciello3f243a92019-01-21 22:05:23 -0600453 return TRUE;
454 }
455 return fu_util_update_shutdown (error);
456 }
457 if (flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) {
458 if (prompt) {
Mario Limonciello5d94afa2019-12-11 06:45:49 -0600459 g_print ("\n%s %s [y|N]: ",
Mario Limonciello3f243a92019-01-21 22:05:23 -0600460 /* TRANSLATORS: explain why we want to reboot */
461 _("An update requires a reboot to complete."),
462 /* TRANSLATORS: reboot to apply the update */
463 _("Restart now?"));
Mario Limoncielloc5efb212019-11-25 07:45:18 -0600464 if (!fu_util_prompt_for_boolean (FALSE))
Mario Limonciello3f243a92019-01-21 22:05:23 -0600465 return TRUE;
466 }
467 return fu_util_update_reboot (error);
468 }
469
470 return TRUE;
471}
Richard Hughesc77e1112019-03-01 10:16:26 +0000472
473static void
474fu_util_cmd_free (FuUtilCmd *item)
475{
476 g_free (item->name);
477 g_free (item->arguments);
478 g_free (item->description);
479 g_free (item);
480}
481
482GPtrArray *
483fu_util_cmd_array_new (void)
484{
485 return g_ptr_array_new_with_free_func ((GDestroyNotify) fu_util_cmd_free);
486}
487
488static gint
489fu_util_cmd_sort_cb (FuUtilCmd **item1, FuUtilCmd **item2)
490{
491 return g_strcmp0 ((*item1)->name, (*item2)->name);
492}
493
494void
495fu_util_cmd_array_sort (GPtrArray *array)
496{
497 g_ptr_array_sort (array, (GCompareFunc) fu_util_cmd_sort_cb);
498}
499
500void
501fu_util_cmd_array_add (GPtrArray *array,
502 const gchar *name,
503 const gchar *arguments,
504 const gchar *description,
505 FuUtilCmdFunc callback)
506{
507 g_auto(GStrv) names = NULL;
508
509 g_return_if_fail (name != NULL);
510 g_return_if_fail (description != NULL);
511 g_return_if_fail (callback != NULL);
512
513 /* add each one */
514 names = g_strsplit (name, ",", -1);
515 for (guint i = 0; names[i] != NULL; i++) {
516 FuUtilCmd *item = g_new0 (FuUtilCmd, 1);
517 item->name = g_strdup (names[i]);
518 if (i == 0) {
519 item->description = g_strdup (description);
520 } else {
521 /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */
522 item->description = g_strdup_printf (_("Alias to %s"),
523 names[0]);
524 }
525 item->arguments = g_strdup (arguments);
526 item->callback = callback;
527 g_ptr_array_add (array, item);
528 }
529}
530
531gboolean
532fu_util_cmd_array_run (GPtrArray *array,
533 FuUtilPrivate *priv,
534 const gchar *command,
535 gchar **values,
536 GError **error)
537{
Mario Limonciello428ed8a2020-03-03 13:57:46 -0600538 g_auto(GStrv) values_copy = g_new0 (gchar *, g_strv_length (values) + 1);
539
540 /* clear out bash completion sentinel */
541 for (guint i = 0; values[i] != NULL; i++) {
542 if (g_strcmp0 (values[i], "{") == 0)
543 break;
544 values_copy[i] = g_strdup (values[i]);
545 }
546
Richard Hughesc77e1112019-03-01 10:16:26 +0000547 /* find command */
548 for (guint i = 0; i < array->len; i++) {
549 FuUtilCmd *item = g_ptr_array_index (array, i);
550 if (g_strcmp0 (item->name, command) == 0)
Mario Limonciello428ed8a2020-03-03 13:57:46 -0600551 return item->callback (priv, values_copy, error);
Richard Hughesc77e1112019-03-01 10:16:26 +0000552 }
553
554 /* not found */
555 g_set_error_literal (error,
556 FWUPD_ERROR,
557 FWUPD_ERROR_INVALID_ARGS,
558 /* TRANSLATORS: error message */
559 _("Command not found"));
560 return FALSE;
561}
562
563gchar *
564fu_util_cmd_array_to_string (GPtrArray *array)
565{
566 gsize len;
567 const gsize max_len = 35;
568 GString *string;
569
570 /* print each command */
571 string = g_string_new ("");
572 for (guint i = 0; i < array->len; i++) {
573 FuUtilCmd *item = g_ptr_array_index (array, i);
574 g_string_append (string, " ");
575 g_string_append (string, item->name);
Richard Hughesae96a1f2019-09-23 11:16:36 +0100576 len = fu_common_strwidth (item->name) + 2;
Richard Hughesc77e1112019-03-01 10:16:26 +0000577 if (item->arguments != NULL) {
578 g_string_append (string, " ");
579 g_string_append (string, item->arguments);
Richard Hughesae96a1f2019-09-23 11:16:36 +0100580 len += fu_common_strwidth (item->arguments) + 1;
Richard Hughesc77e1112019-03-01 10:16:26 +0000581 }
582 if (len < max_len) {
583 for (gsize j = len; j < max_len + 1; j++)
584 g_string_append_c (string, ' ');
585 g_string_append (string, item->description);
586 g_string_append_c (string, '\n');
587 } else {
588 g_string_append_c (string, '\n');
589 for (gsize j = 0; j < max_len + 1; j++)
590 g_string_append_c (string, ' ');
591 g_string_append (string, item->description);
592 g_string_append_c (string, '\n');
593 }
594 }
595
596 /* remove trailing newline */
597 if (string->len > 0)
598 g_string_set_size (string, string->len - 1);
599
600 return g_string_free (string, FALSE);
601}
Richard Hughes71206672019-03-01 10:24:57 +0000602
Richard Hughes1c62ade2020-10-15 14:58:19 +0100603const gchar *
Richard Hughes89ee9ed2021-01-20 16:15:29 +0000604fu_util_branch_for_display (const gchar *branch)
Richard Hughes1c62ade2020-10-15 14:58:19 +0100605{
Richard Hughes89ee9ed2021-01-20 16:15:29 +0000606 if (branch == NULL) {
Richard Hughes1c62ade2020-10-15 14:58:19 +0100607 /* TRANSLATORS: this is the default branch name when unset */
608 return _("default");
609 }
Richard Hughes89ee9ed2021-01-20 16:15:29 +0000610 return branch;
Richard Hughes1c62ade2020-10-15 14:58:19 +0100611}
612
Richard Hughes02ac92c2019-03-29 17:56:46 +0000613gchar *
614fu_util_release_get_name (FwupdRelease *release)
615{
616 const gchar *name = fwupd_release_get_name (release);
617 GPtrArray *cats = fwupd_release_get_categories (release);
618
619 for (guint i = 0; i < cats->len; i++) {
620 const gchar *cat = g_ptr_array_index (cats, i);
621 if (g_strcmp0 (cat, "X-Device") == 0) {
622 /* TRANSLATORS: a specific part of hardware,
623 * the first %s is the device name, e.g. 'Unifying Receiver` */
624 return g_strdup_printf (_("%s Device Update"), name);
625 }
Richard Hughes04afb392020-08-17 16:20:24 +0100626 if (g_strcmp0 (cat, "X-Configuration") == 0) {
627 /* TRANSLATORS: a specific part of hardware,
628 * the first %s is the device name, e.g. 'Secure Boot` */
629 return g_strdup_printf (_("%s Configuration Update"), name);
630 }
Richard Hughes02ac92c2019-03-29 17:56:46 +0000631 if (g_strcmp0 (cat, "X-System") == 0) {
632 /* TRANSLATORS: the entire system, e.g. all internal devices,
633 * the first %s is the device name, e.g. 'ThinkPad P50` */
634 return g_strdup_printf (_("%s System Update"), name);
635 }
636 if (g_strcmp0 (cat, "X-EmbeddedController") == 0) {
637 /* TRANSLATORS: the EC is typically the keyboard controller chip,
638 * the first %s is the device name, e.g. 'ThinkPad P50` */
639 return g_strdup_printf (_("%s Embedded Controller Update"), name);
640 }
641 if (g_strcmp0 (cat, "X-ManagementEngine") == 0) {
642 /* TRANSLATORS: ME stands for Management Engine, the Intel AMT thing,
643 * the first %s is the device name, e.g. 'ThinkPad P50` */
644 return g_strdup_printf (_("%s ME Update"), name);
645 }
Richard Hughesb30f82f2019-05-20 09:29:24 +0100646 if (g_strcmp0 (cat, "X-CorporateManagementEngine") == 0) {
647 /* TRANSLATORS: ME stands for Management Engine (with Intel AMT),
648 * where the first %s is the device name, e.g. 'ThinkPad P50` */
649 return g_strdup_printf (_("%s Corporate ME Update"), name);
650 }
651 if (g_strcmp0 (cat, "X-ConsumerManagementEngine") == 0) {
652 /* TRANSLATORS: ME stands for Management Engine, where
653 * the first %s is the device name, e.g. 'ThinkPad P50` */
654 return g_strdup_printf (_("%s Consumer ME Update"), name);
655 }
Richard Hughes02ac92c2019-03-29 17:56:46 +0000656 if (g_strcmp0 (cat, "X-Controller") == 0) {
657 /* TRANSLATORS: the controller is a device that has other devices
658 * plugged into it, for example ThunderBolt, FireWire or USB,
659 * the first %s is the device name, e.g. 'Intel ThunderBolt` */
660 return g_strdup_printf (_("%s Controller Update"), name);
661 }
Richard Hughes011511e2019-08-06 09:49:06 +0100662 if (g_strcmp0 (cat, "X-ThunderboltController") == 0) {
663 /* TRANSLATORS: the Thunderbolt controller is a device that
664 * has other high speed Thunderbolt devices plugged into it;
665 * the first %s is the system name, e.g. 'ThinkPad P50` */
666 return g_strdup_printf (_("%s Thunderbolt Controller Update"), name);
667 }
Mario Limonciello7a9bb7e2020-04-02 10:28:41 -0500668 if (g_strcmp0 (cat, "X-CpuMicrocode") == 0) {
669 /* TRANSLATORS: the CPU microcode is firmware loaded onto the CPU
670 * at system bootup */
671 return g_strdup_printf (_("%s CPU Microcode Update"), name);
672 }
Richard Hughes2d3b8c82021-03-22 17:40:06 +0000673 if (g_strcmp0 (cat, "X-Battery") == 0) {
674 /* TRANSLATORS: battery refers to the system power source */
675 return g_strdup_printf (_("%s Battery Update"), name);
676 }
677 if (g_strcmp0 (cat, "X-Camera") == 0) {
678 /* TRANSLATORS: camera can refer to the laptop internal
679 * camera in the bezel or external USB webcam */
680 return g_strdup_printf (_("%s Camera Update"), name);
681 }
682 if (g_strcmp0 (cat, "X-TPM") == 0) {
683 /* TRANSLATORS: TPM refers to a Trusted Platform Module */
684 return g_strdup_printf (_("%s TPM Update"), name);
685 }
686 if (g_strcmp0 (cat, "X-Touchpad") == 0) {
687 /* TRANSLATORS: TouchPad refers to a flat input device */
688 return g_strdup_printf (_("%s Touchpad Update"), name);
689 }
690 if (g_strcmp0 (cat, "X-Mouse") == 0) {
691 /* TRANSLATORS: Mouse refers to a handheld input device */
692 return g_strdup_printf (_("%s Mouse Update"), name);
693 }
694 if (g_strcmp0 (cat, "X-Keyboard") == 0) {
695 /* TRANSLATORS: Keyboard refers to an input device for typing */
696 return g_strdup_printf (_("%s Keyboard Update"), name);
697 }
Richard Hughes02ac92c2019-03-29 17:56:46 +0000698 }
699
700 /* TRANSLATORS: this is the fallback where we don't know if the release
701 * is updating the system, the device, or a device class, or something else --
702 * the first %s is the device name, e.g. 'ThinkPad P50` */
703 return g_strdup_printf (_("%s Update"), name);
704}
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100705
706static GPtrArray *
707fu_util_strsplit_words (const gchar *text, guint line_len)
708{
709 g_auto(GStrv) tokens = NULL;
710 g_autoptr(GPtrArray) lines = g_ptr_array_new ();
711 g_autoptr(GString) curline = g_string_new (NULL);
712
713 /* sanity check */
714 if (text == NULL || text[0] == '\0')
715 return NULL;
716 if (line_len == 0)
717 return NULL;
718
719 /* tokenize the string */
720 tokens = g_strsplit (text, " ", -1);
721 for (guint i = 0; tokens[i] != NULL; i++) {
722
723 /* current line plus new token is okay */
Richard Hughesae96a1f2019-09-23 11:16:36 +0100724 if (curline->len + fu_common_strwidth (tokens[i]) < line_len) {
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100725 g_string_append_printf (curline, "%s ", tokens[i]);
726 continue;
727 }
728
729 /* too long, so remove space, add newline and dump */
730 if (curline->len > 0)
731 g_string_truncate (curline, curline->len - 1);
732 g_ptr_array_add (lines, g_strdup (curline->str));
733 g_string_truncate (curline, 0);
734 g_string_append_printf (curline, "%s ", tokens[i]);
735 }
736
737 /* any incomplete line? */
738 if (curline->len > 0) {
739 g_string_truncate (curline, curline->len - 1);
740 g_ptr_array_add (lines, g_strdup (curline->str));
741 }
742 return g_steal_pointer (&lines);
743}
744
745static void
746fu_util_warning_box_line (const gchar *start,
747 const gchar *text,
748 const gchar *end,
749 const gchar *padding,
750 guint width)
751{
752 guint offset = 0;
753 if (start != NULL) {
Richard Hughesae96a1f2019-09-23 11:16:36 +0100754 offset += fu_common_strwidth (start);
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100755 g_print ("%s", start);
756 }
757 if (text != NULL) {
Richard Hughesae96a1f2019-09-23 11:16:36 +0100758 offset += fu_common_strwidth (text);
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100759 g_print ("%s", text);
760 }
761 if (end != NULL)
Richard Hughesae96a1f2019-09-23 11:16:36 +0100762 offset += fu_common_strwidth (end);
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100763 for (guint i = offset; i < width; i++)
764 g_print ("%s", padding);
765 if (end != NULL)
766 g_print ("%s\n", end);
767}
768
769void
Richard Hughese69f0f52021-04-22 11:10:23 +0100770fu_util_warning_box (const gchar *title, const gchar *body, guint width)
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100771{
Richard Hughese69f0f52021-04-22 11:10:23 +0100772 /* nothing to do */
773 if (title == NULL && body == NULL)
774 return;
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100775
776 /* header */
777 fu_util_warning_box_line ("╔", NULL, "╗", "═", width);
778
Richard Hughese69f0f52021-04-22 11:10:23 +0100779 /* optional title */
780 if (title != NULL) {
781 g_autoptr(GPtrArray) lines = fu_util_strsplit_words (title, width - 4);
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100782 for (guint j = 0; j < lines->len; j++) {
783 const gchar *line = g_ptr_array_index (lines, j);
784 fu_util_warning_box_line ("║ ", line, " ║", " ", width);
785 }
Richard Hughese69f0f52021-04-22 11:10:23 +0100786 }
787
788 /* join */
789 if (title != NULL && body != NULL)
790 fu_util_warning_box_line ("╠", NULL, "╣", "═", width);
791
792 /* optional body */
793 if (body != NULL) {
794 g_auto(GStrv) split = g_strsplit (body, "\n", -1);
795 for (guint i = 0; split[i] != NULL; i++) {
796 g_autoptr(GPtrArray) lines = fu_util_strsplit_words (split[i], width - 4);
797 if (lines == NULL)
798 continue;
799 for (guint j = 0; j < lines->len; j++) {
800 const gchar *line = g_ptr_array_index (lines, j);
801 fu_util_warning_box_line ("║ ", line, " ║", " ", width);
802 }
803 }
Richard Hughesacfa4ef2019-05-01 12:18:25 +0100804 }
805
806 /* footer */
807 fu_util_warning_box_line ("╚", NULL, "╝", "═", width);
808}
Richard Hughes747f5702019-08-06 14:27:26 +0100809
810gboolean
811fu_util_parse_filter_flags (const gchar *filter, FwupdDeviceFlags *include,
812 FwupdDeviceFlags *exclude, GError **error)
813{
814 FwupdDeviceFlags tmp;
815 g_auto(GStrv) strv = g_strsplit (filter, ",", -1);
816
817 g_return_val_if_fail (include != NULL, FALSE);
818 g_return_val_if_fail (exclude != NULL, FALSE);
819
820 for (guint i = 0; strv[i] != NULL; i++) {
821 if (g_str_has_prefix (strv[i], "~")) {
822 tmp = fwupd_device_flag_from_string (strv[i] + 1);
823 if (tmp == FWUPD_DEVICE_FLAG_UNKNOWN) {
824 g_set_error (error,
825 FWUPD_ERROR,
826 FWUPD_ERROR_NOT_SUPPORTED,
827 "Unknown device flag %s",
828 strv[i] + 1);
829 return FALSE;
830 }
831 if ((tmp & *include) > 0) {
832 g_set_error (error,
833 FWUPD_ERROR,
834 FWUPD_ERROR_NOT_SUPPORTED,
835 "Filter %s already included",
836 fwupd_device_flag_to_string (tmp));
837 return FALSE;
838 }
839 if ((tmp & *exclude) > 0) {
840 g_set_error (error,
841 FWUPD_ERROR,
842 FWUPD_ERROR_NOT_SUPPORTED,
843 "Filter %s already excluded",
844 fwupd_device_flag_to_string (tmp));
845 return FALSE;
846 }
847 *exclude |= tmp;
848 } else {
849 tmp = fwupd_device_flag_from_string (strv[i]);
850 if (tmp == FWUPD_DEVICE_FLAG_UNKNOWN) {
851 g_set_error (error,
852 FWUPD_ERROR,
853 FWUPD_ERROR_NOT_SUPPORTED,
854 "Unknown device flag %s",
855 strv[i]);
856 return FALSE;
857 }
858 if ((tmp & *exclude) > 0) {
859 g_set_error (error,
860 FWUPD_ERROR,
861 FWUPD_ERROR_NOT_SUPPORTED,
862 "Filter %s already excluded",
863 fwupd_device_flag_to_string (tmp));
864 return FALSE;
865 }
866 if ((tmp & *include) > 0) {
867 g_set_error (error,
868 FWUPD_ERROR,
869 FWUPD_ERROR_NOT_SUPPORTED,
870 "Filter %s already included",
871 fwupd_device_flag_to_string (tmp));
872 return FALSE;
873 }
874 *include |= tmp;
875 }
876 }
877
878 return TRUE;
879}
Mario Limonciellofee8f492019-08-18 12:16:07 -0500880
Richard Hughes72869512020-09-01 15:07:20 +0100881typedef struct {
882 guint cnt;
883 GString *str;
884} FuUtilConvertHelper;
885
886static gboolean
887fu_util_convert_description_head_cb (XbNode *n, gpointer user_data)
888{
889 FuUtilConvertHelper *helper = (FuUtilConvertHelper *) user_data;
890 helper->cnt++;
891
892 /* start */
893 if (g_strcmp0 (xb_node_get_element (n), "em") == 0) {
894 g_string_append (helper->str, "\033[3m");
895 } else if (g_strcmp0 (xb_node_get_element (n), "strong") == 0) {
896 g_string_append (helper->str, "\033[1m");
897 } else if (g_strcmp0 (xb_node_get_element (n), "code") == 0) {
898 g_string_append (helper->str, "`");
899 } else if (g_strcmp0 (xb_node_get_element (n), "li") == 0) {
900 g_string_append (helper->str, "• ");
901 } else if (g_strcmp0 (xb_node_get_element (n), "p") == 0 ||
902 g_strcmp0 (xb_node_get_element (n), "ul") == 0 ||
903 g_strcmp0 (xb_node_get_element (n), "ol") == 0) {
904 g_string_append (helper->str, "\n");
905 }
906
907 /* text */
908 if (xb_node_get_text (n) != NULL)
909 g_string_append (helper->str, xb_node_get_text (n));
910
911 return FALSE;
912}
913
914static gboolean
915fu_util_convert_description_tail_cb (XbNode *n, gpointer user_data)
916{
917 FuUtilConvertHelper *helper = (FuUtilConvertHelper *) user_data;
918 helper->cnt++;
919
920 /* end */
921 if (g_strcmp0 (xb_node_get_element (n), "em") == 0 ||
922 g_strcmp0 (xb_node_get_element (n), "strong") == 0) {
923 g_string_append (helper->str, "\033[0m");
924 } else if (g_strcmp0 (xb_node_get_element (n), "code") == 0) {
925 g_string_append (helper->str, "`");
926 } else if (g_strcmp0 (xb_node_get_element (n), "li") == 0) {
927 g_string_append (helper->str, "\n");
928 } else if (g_strcmp0 (xb_node_get_element (n), "p") == 0) {
929 g_string_append (helper->str, "\n");
930 }
931
932 /* tail */
933 if (xb_node_get_tail (n) != NULL)
934 g_string_append (helper->str, xb_node_get_tail (n));
935
936 return FALSE;
937}
938
Mario Limonciellofee8f492019-08-18 12:16:07 -0500939gchar *
940fu_util_convert_description (const gchar *xml, GError **error)
941{
942 g_autoptr(GString) str = g_string_new (NULL);
943 g_autoptr(XbNode) n = NULL;
944 g_autoptr(XbSilo) silo = NULL;
Richard Hughes72869512020-09-01 15:07:20 +0100945 FuUtilConvertHelper helper = {
946 .cnt = 0,
947 .str = str,
948 };
Mario Limonciellofee8f492019-08-18 12:16:07 -0500949
950 /* parse XML */
951 silo = xb_silo_new_from_xml (xml, error);
952 if (silo == NULL)
953 return NULL;
954
Richard Hughes72869512020-09-01 15:07:20 +0100955 /* convert to something we can show on the console */
Mario Limonciellofee8f492019-08-18 12:16:07 -0500956 n = xb_silo_get_root (silo);
Richard Hughes72869512020-09-01 15:07:20 +0100957 xb_node_transmogrify (n,
958 fu_util_convert_description_head_cb,
959 fu_util_convert_description_tail_cb,
960 &helper);
Mario Limonciellofee8f492019-08-18 12:16:07 -0500961
Mario Limonciellofee8f492019-08-18 12:16:07 -0500962 /* success */
Mario Limonciello26563d72019-09-04 15:33:46 -0500963 return fu_common_strstrip (str->str);
Mario Limonciellofee8f492019-08-18 12:16:07 -0500964}
965
Mario Limoncielloeb442ea2020-01-10 10:56:05 -0600966/**
967 * fu_util_time_to_str:
968 * @tmp: the time in seconds
969 *
970 * Converts a timestamp to a 'pretty' translated string
971 *
972 * Return value: (transfer full): A string
973 *
974 * Since: 1.3.7
975 **/
976gchar *
Mario Limonciellofee8f492019-08-18 12:16:07 -0500977fu_util_time_to_str (guint64 tmp)
978{
979 g_return_val_if_fail (tmp != 0, NULL);
980
981 /* seconds */
982 if (tmp < 60) {
983 /* TRANSLATORS: duration in seconds */
984 return g_strdup_printf (ngettext ("%u second", "%u seconds",
985 (gint) tmp),
986 (guint) tmp);
987 }
988
989 /* minutes */
990 tmp /= 60;
991 if (tmp < 60) {
992 /* TRANSLATORS: duration in minutes */
993 return g_strdup_printf (ngettext ("%u minute", "%u minutes",
994 (gint) tmp),
995 (guint) tmp);
996 }
997
998 /* hours */
999 tmp /= 60;
1000 if (tmp < 60) {
1001 /* TRANSLATORS: duration in minutes */
1002 return g_strdup_printf (ngettext ("%u hour", "%u hours",
1003 (gint) tmp),
1004 (guint) tmp);
1005 }
1006
1007 /* days */
1008 tmp /= 24;
1009 /* TRANSLATORS: duration in days! */
1010 return g_strdup_printf (ngettext ("%u day", "%u days",
1011 (gint) tmp),
1012 (guint) tmp);
1013}
1014
Mario Limonciellofd877a02019-10-17 10:28:56 -05001015static gchar *
1016fu_util_device_flag_to_string (guint64 device_flag)
1017{
1018 if (device_flag == FWUPD_DEVICE_FLAG_NONE) {
1019 return NULL;
1020 }
1021 if (device_flag == FWUPD_DEVICE_FLAG_INTERNAL) {
1022 /* TRANSLATORS: Device cannot be removed easily*/
1023 return _("Internal device");
1024 }
Richard Hughes8500b4f2020-04-17 11:47:04 +01001025 if (device_flag == FWUPD_DEVICE_FLAG_UPDATABLE ||
1026 device_flag == FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN) {
Mario Limonciellofd877a02019-10-17 10:28:56 -05001027 /* TRANSLATORS: Device is updatable in this or any other mode */
1028 return _("Updatable");
1029 }
1030 if (device_flag == FWUPD_DEVICE_FLAG_ONLY_OFFLINE) {
1031 /* TRANSLATORS: Update can only be done from offline mode */
1032 return _("Update requires a reboot");
1033 }
1034 if (device_flag == FWUPD_DEVICE_FLAG_REQUIRE_AC) {
1035 /* TRANSLATORS: Must be plugged in to an outlet */
mendel5f75ff422020-09-19 21:58:19 +02001036 return _("System requires external power source");
Mario Limonciellofd877a02019-10-17 10:28:56 -05001037 }
1038 if (device_flag == FWUPD_DEVICE_FLAG_LOCKED) {
1039 /* TRANSLATORS: Is locked and can be unlocked */
1040 return _("Device is locked");
1041 }
1042 if (device_flag == FWUPD_DEVICE_FLAG_SUPPORTED) {
1043 /* TRANSLATORS: Is found in current metadata */
1044 return _("Supported on remote server");
1045 }
1046 if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) {
1047 /* TRANSLATORS: Requires a bootloader mode to be manually enabled by the user */
1048 return _("Requires a bootloader");
1049 }
1050 if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_REBOOT) {
1051 /* TRANSLATORS: Requires a reboot to apply firmware or to reload hardware */
1052 return _("Needs a reboot after installation");
1053 }
1054 if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) {
1055 /* TRANSLATORS: Requires system shutdown to apply firmware */
1056 return _("Needs shutdown after installation");
1057 }
1058 if (device_flag == FWUPD_DEVICE_FLAG_REPORTED) {
1059 /* TRANSLATORS: Has been reported to a metadata server */
1060 return _("Reported to remote server");
1061 }
1062 if (device_flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
1063 /* TRANSLATORS: User has been notified */
1064 return _("User has been notified");
1065 }
1066 if (device_flag == FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) {
1067 /* skip */
1068 return NULL;
1069 }
1070 if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST) {
1071 /* TRANSLATORS: Install composite firmware on the parent before the child */
1072 return _("Install to parent device first");
1073 }
1074 if (device_flag == FWUPD_DEVICE_FLAG_IS_BOOTLOADER) {
1075 /* TRANSLATORS: Is currently in bootloader mode */
1076 return _("Is in bootloader mode");
1077 }
1078 if (device_flag == FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) {
1079 /* TRANSLATORS: The hardware is waiting to be replugged */
1080 return _("Hardware is waiting to be replugged");
1081 }
1082 if (device_flag == FWUPD_DEVICE_FLAG_IGNORE_VALIDATION) {
1083 /* TRANSLATORS: Ignore validation safety checks when flashing this device */
1084 return _("Ignore validation safety checks");
1085 }
1086 if (device_flag == FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED) {
1087 /* skip */
1088 return NULL;
1089 }
Mario Limonciellofd877a02019-10-17 10:28:56 -05001090 if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) {
1091 /* TRANSLATORS: Device update needs to be separately activated */
1092 return _("Device update needs activation");
1093 }
Mario Limonciellofd877a02019-10-17 10:28:56 -05001094 if (device_flag == FWUPD_DEVICE_FLAG_HISTORICAL) {
1095 /* skip */
1096 return NULL;
1097 }
Mario Limonciellofd877a02019-10-17 10:28:56 -05001098 if (device_flag == FWUPD_DEVICE_FLAG_WILL_DISAPPEAR) {
1099 /* TRANSLATORS: Device will not return after update completes */
1100 return _("Device will not re-appear after update completes");
1101 }
1102 if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY) {
1103 /* TRANSLATORS: Device supports some form of checksum verification */
1104 return _("Cryptographic hash verification is available");
1105 }
1106 if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) {
1107 /* skip */
1108 return NULL;
1109 }
1110 if (device_flag == FWUPD_DEVICE_FLAG_DUAL_IMAGE) {
1111 /* TRANSLATORS: Device supports a safety mechanism for flashing */
1112 return _("Device stages updates");
1113 }
1114 if (device_flag == FWUPD_DEVICE_FLAG_SELF_RECOVERY) {
1115 /* TRANSLATORS: Device supports a safety mechanism for flashing */
1116 return _("Device can recover flash failures");
1117 }
1118 if (device_flag == FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) {
1119 /* TRANSLATORS: Device remains usable during update */
1120 return _("Device is usable for the duration of the update");
1121 }
Richard Hughes1eb7c742020-01-07 10:58:32 +00001122 if (device_flag == FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED) {
1123 /* TRANSLATORS: a version check is required for all firmware */
1124 return _("Device firmware is required to have a version check");
1125 }
Richard Hughesaf140732019-12-22 18:42:12 +00001126 if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) {
1127 /* TRANSLATORS: a version check is required for all firmware */
1128 return _("Device is required to install all provided releases");
1129 }
Richard Hughes460c4b72020-09-25 20:59:28 +01001130 if (device_flag == FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES) {
1131 /* TRANSLATORS: there is more than one supplier of the firmware */
1132 return _("Device supports switching to a different branch of firmware");
1133 }
Richard Hughes1a612582020-09-29 19:39:38 +01001134 if (device_flag == FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL) {
1135 /* TRANSLATORS: save the old firmware to disk before installing the new one */
1136 return _("Device will backup firmware before installing");
1137 }
Mario Limonciello768a2682020-04-29 11:18:43 -05001138 if (device_flag == FWUPD_DEVICE_FLAG_SKIPS_RESTART) {
1139 /* skip */
1140 return NULL;
1141 }
Mario Limonciellofd877a02019-10-17 10:28:56 -05001142 if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN) {
1143 return NULL;
1144 }
1145 return NULL;
1146}
1147
Richard Hughesb1edfec2021-01-13 13:59:10 +00001148static const gchar *
1149fu_util_update_state_to_string (FwupdUpdateState update_state)
1150{
1151 if (update_state == FWUPD_UPDATE_STATE_PENDING) {
1152 /* TRANSLATORS: The update state of the specific device */
1153 return _("Pending");
1154 }
1155 if (update_state == FWUPD_UPDATE_STATE_SUCCESS) {
1156 /* TRANSLATORS: The update state of the specific device */
1157 return _("Success");
1158 }
1159 if (update_state == FWUPD_UPDATE_STATE_FAILED) {
1160 /* TRANSLATORS: The update state of the specific device */
1161 return _("Failed");
1162 }
1163 if (update_state == FWUPD_UPDATE_STATE_FAILED_TRANSIENT) {
1164 /* TRANSLATORS: The update state of the specific device */
1165 return _("Transient failure");
1166 }
1167 if (update_state == FWUPD_UPDATE_STATE_NEEDS_REBOOT) {
1168 /* TRANSLATORS: The update state of the specific device */
1169 return _("Needs reboot");
1170 }
1171 return NULL;
1172}
1173
Mario Limonciellofee8f492019-08-18 12:16:07 -05001174gchar *
1175fu_util_device_to_string (FwupdDevice *dev, guint idt)
1176{
1177 FwupdUpdateState state;
Richard Hughes71db86f2019-09-11 10:25:47 +01001178 GPtrArray *guids = fwupd_device_get_guids (dev);
Richard Hughese52e1b42021-01-26 09:59:15 +00001179 GPtrArray *vendor_ids = fwupd_device_get_vendor_ids (dev);
Richard Hughes71db86f2019-09-11 10:25:47 +01001180 GPtrArray *instance_ids = fwupd_device_get_instance_ids (dev);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001181 const gchar *tmp;
1182 const gchar *tmp2;
1183 guint64 flags = fwupd_device_get_flags (dev);
Mario Limonciello3be596b2019-09-20 10:10:08 -05001184 guint64 modified = fwupd_device_get_modified (dev);
Richard Hughes71db86f2019-09-11 10:25:47 +01001185 g_autoptr(GHashTable) ids = NULL;
Richard Hughesf68f5312021-04-20 13:55:08 +01001186 g_autoptr(GString) str = g_string_new (NULL);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001187
1188 /* some fields are intentionally not included and are only shown in --verbose */
1189 if (g_getenv ("FWUPD_VERBOSE") != NULL) {
1190 g_autofree gchar *debug_str = NULL;
1191 debug_str = fwupd_device_to_string (dev);
Mario Limonciello17e9bf52019-08-27 09:03:19 -05001192 g_debug ("%s", debug_str);
1193 return NULL;
Mario Limonciellofee8f492019-08-18 12:16:07 -05001194 }
1195
Mario Limonciello764fc2a2019-08-27 10:02:59 -05001196 tmp = fwupd_device_get_name (dev);
1197 if (tmp == NULL) {
1198 /* TRANSLATORS: Name of hardware */
1199 tmp = _("Unknown Device");
1200 }
1201 fu_common_string_append_kv (str, idt, tmp, NULL);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001202
Mario Limonciello764fc2a2019-08-27 10:02:59 -05001203 tmp = fwupd_device_get_id (dev);
1204 if (tmp != NULL) {
1205 /* TRANSLATORS: ID for hardware, typically a SHA1 sum */
1206 fu_common_string_append_kv (str, idt + 1, _("Device ID"), tmp);
1207 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001208
1209 /* summary */
1210 tmp = fwupd_device_get_summary (dev);
1211 if (tmp != NULL) {
1212 /* TRANSLATORS: one line summary of device */
1213 fu_common_string_append_kv (str, idt + 1, _("Summary"), tmp);
1214 }
1215
1216 /* description */
1217 tmp = fwupd_device_get_description (dev);
1218 if (tmp != NULL) {
1219 g_autofree gchar *desc = NULL;
1220 desc = fu_util_convert_description (tmp, NULL);
1221 /* TRANSLATORS: multiline description of device */
1222 fu_common_string_append_kv (str, idt + 1, _("Description"), desc);
1223 }
1224
1225 /* versions */
1226 tmp = fwupd_device_get_version (dev);
1227 if (tmp != NULL) {
Mario Limonciello3be596b2019-09-20 10:10:08 -05001228 if (flags & FWUPD_DEVICE_FLAG_HISTORICAL) {
1229 /* TRANSLATORS: version number of previous firmware */
1230 fu_common_string_append_kv (str, idt + 1, _("Previous version"), tmp);
1231 } else {
1232 /* TRANSLATORS: version number of current firmware */
1233 fu_common_string_append_kv (str, idt + 1, _("Current version"), tmp);
1234 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001235 }
1236 tmp = fwupd_device_get_version_lowest (dev);
1237 if (tmp != NULL) {
1238 /* TRANSLATORS: smallest version number installable on device */
1239 fu_common_string_append_kv (str, idt + 1, _("Minimum Version"), tmp);
1240 }
1241 tmp = fwupd_device_get_version_bootloader (dev);
1242 if (tmp != NULL) {
1243 /* TRANSLATORS: firmware version of bootloader */
1244 fu_common_string_append_kv (str, idt + 1, _("Bootloader Version"), tmp);
1245 }
1246
1247 /* vendor */
1248 tmp = fwupd_device_get_vendor (dev);
Richard Hughese52e1b42021-01-26 09:59:15 +00001249 if (tmp != NULL && vendor_ids->len > 0) {
1250 g_autofree gchar *strv = fu_common_strjoin_array ("|", vendor_ids);
1251 g_autofree gchar *both = g_strdup_printf ("%s (%s)", tmp, strv);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001252 /* TRANSLATORS: manufacturer of hardware */
1253 fu_common_string_append_kv (str, idt + 1, _("Vendor"), both);
1254 } else if (tmp != NULL) {
1255 /* TRANSLATORS: manufacturer of hardware */
1256 fu_common_string_append_kv (str, idt + 1, _("Vendor"), tmp);
Richard Hughese52e1b42021-01-26 09:59:15 +00001257 } else if (vendor_ids->len > 0) {
1258 g_autofree gchar *strv = fu_common_strjoin_array ("|", vendor_ids);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001259 /* TRANSLATORS: manufacturer of hardware */
Richard Hughese52e1b42021-01-26 09:59:15 +00001260 fu_common_string_append_kv (str, idt + 1, _("Vendor"), strv);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001261 }
1262
Richard Hughes71f713e2021-01-15 11:51:10 +00001263 /* branch */
1264 if (fwupd_device_get_branch (dev) != NULL) {
1265 /* TRANSLATORS: the stream of firmware, e.g. nonfree or open-source */
1266 fu_common_string_append_kv (str, idt + 1, _("Release Branch"),
1267 fwupd_device_get_branch (dev));
1268 }
1269
Mario Limonciellofee8f492019-08-18 12:16:07 -05001270 /* install duration */
1271 if (fwupd_device_get_install_duration (dev) > 0) {
1272 g_autofree gchar *time = fu_util_time_to_str (fwupd_device_get_install_duration (dev));
1273 /* TRANSLATORS: length of time the update takes to apply */
1274 fu_common_string_append_kv (str, idt + 1, _("Install Duration"), time);
1275 }
1276
1277 /* serial # */
1278 tmp = fwupd_device_get_serial (dev);
1279 if (tmp != NULL) {
1280 /* TRANSLATORS: serial number of hardware */
1281 fu_common_string_append_kv (str, idt + 1, _("Serial Number"), tmp);
1282 }
1283
1284 /* update state */
1285 state = fwupd_device_get_update_state (dev);
1286 if (state != FWUPD_UPDATE_STATE_UNKNOWN) {
1287 /* TRANSLATORS: hardware state, e.g. "pending" */
1288 fu_common_string_append_kv (str, idt + 1, _("Update State"),
Richard Hughesb1edfec2021-01-13 13:59:10 +00001289 fu_util_update_state_to_string (state));
Mario Limoncielloc2721c82020-06-25 09:42:12 -05001290
1291 if (state == FWUPD_UPDATE_STATE_SUCCESS) {
1292 tmp = fwupd_device_get_update_message (dev);
1293 if (tmp != NULL) {
1294 /* TRANSLATORS: helpful messages from last update */
1295 fu_common_string_append_kv (str, idt + 1, _("Update Message"), tmp);
1296 }
1297 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001298 }
1299 tmp = fwupd_device_get_update_error (dev);
1300 if (tmp != NULL) {
1301 /* TRANSLATORS: error message from last update attempt */
1302 fu_common_string_append_kv (str, idt + 1, _("Update Error"), tmp);
1303 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001304
Mario Limonciello3be596b2019-09-20 10:10:08 -05001305 /* modified date: for history devices */
1306 if (modified > 0) {
1307 g_autoptr(GDateTime) date = NULL;
1308 g_autofree gchar *time_str = NULL;
1309 date = g_date_time_new_from_unix_utc (modified);
1310 time_str = g_date_time_format (date, "%F %R");
1311 /* TRANSLATORS: the original time/date the device was modified */
1312 fu_common_string_append_kv (str, idt +1, _("Last modified"), time_str);
1313 }
1314
Richard Hughes71db86f2019-09-11 10:25:47 +01001315 /* all GUIDs for this hardware, with IDs if available */
1316 ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1317 for (guint i = 0; i < instance_ids->len; i++) {
1318 const gchar *instance_id = g_ptr_array_index (instance_ids, i);
1319 g_hash_table_insert (ids,
1320 fwupd_guid_hash_string (instance_id),
1321 g_strdup (instance_id));
1322 }
1323 for (guint i = 0; i < guids->len; i++) {
1324 const gchar *guid = g_ptr_array_index (guids, i);
1325 const gchar *instance_id = g_hash_table_lookup (ids, guid);
1326 g_autofree gchar *guid_src = NULL;
1327
1328 /* instance IDs are only available as root */
1329 if (instance_id == NULL) {
1330 guid_src = g_strdup (guid);
1331 } else {
1332 guid_src = g_strdup_printf ("%s ← %s", guid, instance_id);
1333 }
1334 if (i == 0) {
1335 /* TRANSLATORS: global ID common to all similar hardware */
1336 fu_common_string_append_kv (str, idt + 1, ngettext ("GUID", "GUIDs", guids->len), guid_src);
1337 } else {
1338 fu_common_string_append_kv (str, idt + 1, "", guid_src);
1339 }
1340 }
Mario Limonciellofd877a02019-10-17 10:28:56 -05001341
1342 /* TRANSLATORS: description of device ability */
1343 tmp = _("Device Flags");
1344 for (guint i = 0; i < 64; i++) {
1345 if ((flags & ((guint64) 1 << i)) == 0)
1346 continue;
1347 tmp2 = fu_util_device_flag_to_string ((guint64) 1 << i);
1348 if (tmp2 == NULL)
1349 continue;
1350 /* header */
1351 if (tmp != NULL) {
1352 g_autofree gchar *bullet = NULL;
1353 bullet = g_strdup_printf ("• %s", tmp2);
1354 fu_common_string_append_kv (str, idt + 1, tmp, bullet);
1355 tmp = NULL;
1356 } else {
1357 g_autofree gchar *bullet = NULL;
1358 bullet = g_strdup_printf ("• %s", tmp2);
1359 fu_common_string_append_kv (str, idt + 1, "", bullet);
1360 }
1361 }
1362
Richard Hughesf68f5312021-04-20 13:55:08 +01001363 return g_string_free (g_steal_pointer (&str), FALSE);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001364}
1365
Richard Hughes7bcb8d42020-10-08 15:47:47 +01001366const gchar *
1367fu_util_plugin_flag_to_string (FwupdPluginFlags plugin_flag)
1368{
1369 if (plugin_flag == FWUPD_PLUGIN_FLAG_UNKNOWN)
1370 return NULL;
1371 if (plugin_flag == FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE)
1372 return NULL;
1373 if (plugin_flag == FWUPD_PLUGIN_FLAG_USER_WARNING)
1374 return NULL;
Richard Hughesd94286b2021-03-01 21:12:18 +00001375 if (plugin_flag == FWUPD_PLUGIN_FLAG_REQUIRE_HWID)
1376 return NULL;
Richard Hughes7bcb8d42020-10-08 15:47:47 +01001377 if (plugin_flag == FWUPD_PLUGIN_FLAG_NONE) {
1378 /* TRANSLATORS: Plugin is active and in use */
1379 return _("Enabled");
1380 }
1381 if (plugin_flag == FWUPD_PLUGIN_FLAG_DISABLED) {
1382 /* TRANSLATORS: Plugin is inactive and not used */
1383 return _("Disabled");
1384 }
1385 if (plugin_flag == FWUPD_PLUGIN_FLAG_NO_HARDWARE) {
1386 /* TRANSLATORS: not required for this system */
1387 return _("Required hardware was not found");
1388 }
1389 if (plugin_flag == FWUPD_PLUGIN_FLAG_LEGACY_BIOS) {
1390 /* TRANSLATORS: system is not booted in UEFI mode */
Richard Hughese0822722021-04-19 11:03:41 +01001391 return _("UEFI firmware can not be updated in legacy BIOS mode");
Richard Hughes7bcb8d42020-10-08 15:47:47 +01001392 }
1393 if (plugin_flag == FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED) {
1394 /* TRANSLATORS: capsule updates are an optional BIOS feature */
Mario Limonciello797da4f2021-01-12 12:38:51 -06001395 return _("UEFI capsule updates not available or enabled in firmware setup");
Richard Hughes7bcb8d42020-10-08 15:47:47 +01001396 }
1397 if (plugin_flag == FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED) {
1398 /* TRANSLATORS: user needs to run a command */
1399 return _("Firmware updates disabled; run 'fwupdmgr unlock' to enable");
1400 }
1401 if (plugin_flag == FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED) {
1402 /* TRANSLATORS: the user is using Gentoo/Arch and has screwed something up */
1403 return _("Required efivarfs filesystem was not found");
1404 }
1405 if (plugin_flag == FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND) {
1406 /* TRANSLATORS: partition refers to something on disk, again, hey Arch users */
1407 return _("UEFI ESP partition not detected or configured");
1408 }
Mario Limoncielloc3a81732020-10-20 09:16:18 -05001409 if (plugin_flag == FWUPD_PLUGIN_FLAG_FAILED_OPEN) {
1410 /* TRANSLATORS: Failed to open plugin, hey Arch users */
1411 return _("Plugin dependencies missing");
1412 }
Richard Hughes7bcb8d42020-10-08 15:47:47 +01001413
1414 /* fall back for unknown types */
1415 return fwupd_plugin_flag_to_string (plugin_flag);
1416}
1417
1418static gchar *
1419fu_util_plugin_flag_to_cli_text (FwupdPluginFlags plugin_flag)
1420{
1421 switch (plugin_flag) {
1422 case FWUPD_PLUGIN_FLAG_UNKNOWN:
1423 case FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE:
1424 case FWUPD_PLUGIN_FLAG_USER_WARNING:
Richard Hughesd94286b2021-03-01 21:12:18 +00001425 case FWUPD_PLUGIN_FLAG_REQUIRE_HWID:
Richard Hughes7bcb8d42020-10-08 15:47:47 +01001426 return NULL;
1427 case FWUPD_PLUGIN_FLAG_NONE:
1428 return fu_util_term_format (fu_util_plugin_flag_to_string (plugin_flag),
1429 FU_UTIL_CLI_COLOR_GREEN);
1430 case FWUPD_PLUGIN_FLAG_DISABLED:
1431 case FWUPD_PLUGIN_FLAG_NO_HARDWARE:
1432 return fu_util_term_format (fu_util_plugin_flag_to_string (plugin_flag),
1433 FU_UTIL_CLI_COLOR_BLACK);
1434 case FWUPD_PLUGIN_FLAG_LEGACY_BIOS:
1435 case FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED:
1436 case FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED:
1437 case FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED:
1438 case FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND:
1439 return fu_util_term_format (fu_util_plugin_flag_to_string (plugin_flag),
1440 FU_UTIL_TERM_COLOR_RED);
1441 default:
1442 break;
1443 }
1444
1445 /* fall back for unknown types */
1446 return g_strdup (fwupd_plugin_flag_to_string (plugin_flag));
1447}
1448
1449gchar *
1450fu_util_plugin_to_string (FwupdPlugin *plugin, guint idt)
1451{
1452 GString *str = g_string_new (NULL);
1453 const gchar *hdr;
1454 guint64 flags = fwupd_plugin_get_flags (plugin);
1455
1456 fu_common_string_append_kv (str, idt, fwupd_plugin_get_name (plugin), NULL);
1457
1458 /* TRANSLATORS: description of plugin state, e.g. disabled */
1459 hdr = _("Flags");
1460 if (flags == 0x0) {
1461 const gchar *tmp = fu_util_plugin_flag_to_cli_text (flags);
1462 g_autofree gchar *li = g_strdup_printf ("• %s", tmp);
1463 fu_common_string_append_kv (str, idt + 1, hdr, li);
1464 } else {
1465 for (guint i = 0; i < 64; i++) {
1466 g_autofree gchar *li = NULL;
1467 g_autofree gchar *tmp = NULL;
1468 if ((flags & ((guint64) 1 << i)) == 0)
1469 continue;
1470 tmp = fu_util_plugin_flag_to_cli_text ((guint64) 1 << i);
1471 if (tmp == NULL)
1472 continue;
1473 li = g_strdup_printf ("• %s", tmp);
1474 fu_common_string_append_kv (str, idt + 1, hdr, li);
1475
1476 /* clear header */
1477 hdr = "";
1478 }
1479 }
1480
1481 return g_string_free (str, FALSE);
1482}
1483
Richard Hughes07184d32019-09-27 08:57:28 +01001484static const gchar *
1485fu_util_license_to_string (const gchar *license)
1486{
1487 if (license == NULL) {
1488 /* TRANSLATORS: we don't know the license of the update */
1489 return _("Unknown");
1490 }
1491 if (g_strcmp0 (license, "LicenseRef-proprietary") == 0 ||
1492 g_strcmp0 (license, "proprietary") == 0) {
1493 /* TRANSLATORS: a non-free software license */
1494 return _("Proprietary");
1495 }
1496 return license;
1497}
1498
Richard Hughes52c1a4d2020-04-02 11:21:06 +01001499static const gchar *
1500fu_util_release_urgency_to_string (FwupdReleaseUrgency release_urgency)
1501{
1502 if (release_urgency == FWUPD_RELEASE_URGENCY_LOW) {
1503 /* TRANSLATORS: the release urgency */
1504 return _("Low");
1505 }
1506 if (release_urgency == FWUPD_RELEASE_URGENCY_MEDIUM) {
1507 /* TRANSLATORS: the release urgency */
1508 return _("Medium");
1509 }
1510 if (release_urgency == FWUPD_RELEASE_URGENCY_HIGH) {
1511 /* TRANSLATORS: the release urgency */
1512 return _("High");
1513 }
1514 if (release_urgency == FWUPD_RELEASE_URGENCY_CRITICAL) {
1515 /* TRANSLATORS: the release urgency */
1516 return _("Critical");
1517 }
1518 /* TRANSLATORS: unknown release urgency */
1519 return _("Unknown");
1520}
1521
Mario Limonciellofee8f492019-08-18 12:16:07 -05001522gchar *
1523fu_util_release_to_string (FwupdRelease *rel, guint idt)
1524{
Richard Hughes0ad59cb2019-09-16 10:33:05 +01001525 GPtrArray *issues = fwupd_release_get_issues (rel);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001526 GString *str = g_string_new (NULL);
1527 guint64 flags = fwupd_release_get_flags (rel);
1528 g_autoptr(GString) flags_str = g_string_new (NULL);
1529
1530 g_return_val_if_fail (FWUPD_IS_RELEASE (rel), NULL);
1531
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001532 fu_common_string_append_kv (str, idt, fwupd_release_get_name (rel), NULL);
1533
Mario Limonciellofee8f492019-08-18 12:16:07 -05001534 /* TRANSLATORS: version number of new firmware */
Mario Limonciello3be596b2019-09-20 10:10:08 -05001535 fu_common_string_append_kv (str, idt + 1 , _("New version"),
Mario Limonciellofee8f492019-08-18 12:16:07 -05001536 fwupd_release_get_version (rel));
1537
1538 if (fwupd_release_get_remote_id (rel) != NULL) {
1539 /* TRANSLATORS: the server the file is coming from */
1540 fu_common_string_append_kv (str, idt + 1, _("Remote ID"),
1541 fwupd_release_get_remote_id (rel));
1542 }
Richard Hughes460c4b72020-09-25 20:59:28 +01001543 if (fwupd_release_get_branch (rel) != NULL) {
1544 /* TRANSLATORS: the stream of firmware, e.g. nonfree or open-source */
1545 fu_common_string_append_kv (str, idt + 1, _("Branch"),
1546 fwupd_release_get_branch (rel));
1547 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001548 if (fwupd_release_get_summary (rel) != NULL) {
1549 /* TRANSLATORS: one line summary of device */
1550 fu_common_string_append_kv (str, idt + 1, _("Summary"),
1551 fwupd_release_get_summary (rel));
1552 }
Richard Hughesf54ddf42019-09-21 12:36:14 +01001553 if (fwupd_release_get_name_variant_suffix (rel) != NULL) {
1554 /* TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') */
1555 fu_common_string_append_kv (str, idt + 1, _("Variant"),
1556 fwupd_release_get_name_variant_suffix (rel));
1557 }
Richard Hughes07184d32019-09-27 08:57:28 +01001558 /* TRANSLATORS: e.g. GPLv2+, Proprietary etc */
1559 fu_common_string_append_kv (str, idt + 1, _("License"),
1560 fu_util_license_to_string (fwupd_release_get_license (rel)));
Mario Limonciellofee8f492019-08-18 12:16:07 -05001561 if (fwupd_release_get_size (rel) != 0) {
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001562 g_autofree gchar *tmp = NULL;
1563 tmp = g_format_size (fwupd_release_get_size (rel));
Mario Limonciellofee8f492019-08-18 12:16:07 -05001564 /* TRANSLATORS: file size of the download */
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001565 fu_common_string_append_kv (str, idt + 1, _("Size"), tmp);
Mario Limonciellofee8f492019-08-18 12:16:07 -05001566 }
Richard Hughes14797f82020-04-02 10:52:04 +01001567 if (fwupd_release_get_created (rel) != 0) {
1568 gint64 value = (gint64) fwupd_release_get_created (rel);
1569 g_autoptr(GDateTime) date = g_date_time_new_from_unix_utc (value);
1570 g_autofree gchar *tmp = g_date_time_format (date, "%F");
1571 /* TRANSLATORS: when the update was built */
1572 fu_common_string_append_kv (str, idt + 1, _("Created"), tmp);
1573 }
Richard Hughes52c1a4d2020-04-02 11:21:06 +01001574 if (fwupd_release_get_urgency (rel) != FWUPD_RELEASE_URGENCY_UNKNOWN) {
1575 FwupdReleaseUrgency tmp = fwupd_release_get_urgency (rel);
1576 /* TRANSLATORS: how important the release is */
1577 fu_common_string_append_kv (str, idt + 1, _("Urgency"),
1578 fu_util_release_urgency_to_string (tmp));
1579 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001580 if (fwupd_release_get_details_url (rel) != NULL) {
1581 /* TRANSLATORS: more details about the update link */
1582 fu_common_string_append_kv (str, idt + 1, _("Details"),
1583 fwupd_release_get_details_url (rel));
1584 }
1585 if (fwupd_release_get_source_url (rel) != NULL) {
1586 /* TRANSLATORS: source (as in code) link */
1587 fu_common_string_append_kv (str, idt + 1, _("Source"),
1588 fwupd_release_get_source_url (rel));
1589 }
1590 if (fwupd_release_get_vendor (rel) != NULL) {
1591 /* TRANSLATORS: manufacturer of hardware */
1592 fu_common_string_append_kv (str, idt + 1, _("Vendor"),
1593 fwupd_release_get_vendor (rel));
1594 }
1595 if (fwupd_release_get_install_duration (rel) != 0) {
1596 g_autofree gchar *tmp = fu_util_time_to_str (fwupd_release_get_install_duration (rel));
1597 /* TRANSLATORS: length of time the update takes to apply */
1598 fu_common_string_append_kv (str, idt + 1, _("Duration"), tmp);
1599 }
1600 if (fwupd_release_get_update_message (rel) != NULL) {
1601 /* TRANSLATORS: helpful messages for the update */
1602 fu_common_string_append_kv (str, idt + 1, _("Update Message"),
1603 fwupd_release_get_update_message (rel));
1604 }
1605
1606 for (guint i = 0; i < 64; i++) {
1607 if ((flags & ((guint64) 1 << i)) == 0)
1608 continue;
1609 g_string_append_printf (flags_str, "%s|",
1610 fwupd_release_flag_to_string ((guint64) 1 << i));
1611 }
Mario Limonciello3be596b2019-09-20 10:10:08 -05001612 if (flags_str->len > 0) {
Mario Limonciellofee8f492019-08-18 12:16:07 -05001613 g_string_truncate (flags_str, flags_str->len - 1);
1614 /* TRANSLATORS: release properties */
1615 fu_common_string_append_kv (str, idt + 1, _("Flags"), flags_str->str);
1616 }
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001617 if (fwupd_release_get_description (rel) != NULL) {
1618 g_autofree gchar *desc = NULL;
1619 desc = fu_util_convert_description (fwupd_release_get_description (rel), NULL);
1620 /* TRANSLATORS: multiline description of device */
1621 fu_common_string_append_kv (str, idt + 1, _("Description"), desc);
1622 }
Richard Hughes0ad59cb2019-09-16 10:33:05 +01001623 for (guint i = 0; i < issues->len; i++) {
1624 const gchar *issue = g_ptr_array_index (issues, i);
1625 if (i == 0) {
1626 /* TRANSLATORS: issue fixed with the release, e.g. CVE */
1627 fu_common_string_append_kv (str, idt + 1, ngettext ("Issue", "Issues", issues->len), issue);
1628 } else {
1629 fu_common_string_append_kv (str, idt + 1, "", issue);
1630 }
1631 }
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001632
1633 return g_string_free (str, FALSE);
1634}
1635
1636gchar *
1637fu_util_remote_to_string (FwupdRemote *remote, guint idt)
1638{
1639 GString *str = g_string_new (NULL);
1640 FwupdRemoteKind kind = fwupd_remote_get_kind (remote);
1641 FwupdKeyringKind keyring_kind = fwupd_remote_get_keyring_kind (remote);
1642 const gchar *tmp;
1643 gint priority;
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001644
1645 g_return_val_if_fail (FWUPD_IS_REMOTE (remote), NULL);
1646
1647 fu_common_string_append_kv (str, idt,
1648 fwupd_remote_get_title (remote), NULL);
1649
1650 /* TRANSLATORS: remote identifier, e.g. lvfs-testing */
1651 fu_common_string_append_kv (str, idt + 1, _("Remote ID"),
1652 fwupd_remote_get_id (remote));
1653
1654 /* TRANSLATORS: remote type, e.g. remote or local */
1655 fu_common_string_append_kv (str, idt + 1, _("Type"),
1656 fwupd_remote_kind_to_string (kind));
1657
1658 /* TRANSLATORS: keyring type, e.g. GPG or PKCS7 */
1659 if (keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) {
1660 fu_common_string_append_kv (str, idt + 1, _("Keyring"),
1661 fwupd_keyring_kind_to_string (keyring_kind));
1662 }
1663
1664 /* TRANSLATORS: if the remote is enabled */
1665 fu_common_string_append_kv (str, idt + 1, _("Enabled"),
1666 fwupd_remote_get_enabled (remote) ? "true" : "false");
1667
1668 tmp = fwupd_remote_get_checksum (remote);
1669 if (tmp != NULL) {
1670 /* TRANSLATORS: remote checksum */
1671 fu_common_string_append_kv (str, idt + 1, _("Checksum"), tmp);
1672 }
1673
1674 /* optional parameters */
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001675 if (kind == FWUPD_REMOTE_KIND_DOWNLOAD &&
Richard Hughes1ca03162020-12-08 16:09:05 +00001676 fwupd_remote_get_age (remote) > 0 &&
1677 fwupd_remote_get_age (remote) != G_MAXUINT64) {
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001678 const gchar *unit = "s";
Richard Hughes1ca03162020-12-08 16:09:05 +00001679 gdouble age = fwupd_remote_get_age (remote);
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001680 g_autofree gchar *age_str = NULL;
1681 if (age > 60) {
1682 age /= 60.f;
1683 unit = "m";
1684 }
1685 if (age > 60) {
1686 age /= 60.f;
1687 unit = "h";
1688 }
1689 if (age > 24) {
1690 age /= 24.f;
1691 unit = "d";
1692 }
1693 if (age > 7) {
1694 age /= 7.f;
1695 unit = "w";
1696 }
1697 age_str = g_strdup_printf ("%.2f%s", age, unit);
1698 /* TRANSLATORS: the age of the metadata */
1699 fu_common_string_append_kv (str, idt + 1, _("Age"), age_str);
1700 }
1701 priority = fwupd_remote_get_priority (remote);
1702 if (priority != 0) {
1703 g_autofree gchar *priority_str = NULL;
1704 priority_str = g_strdup_printf ("%i", priority);
1705 /* TRANSLATORS: the numeric priority */
1706 fu_common_string_append_kv (str, idt + 1, _("Priority"), priority_str);
1707 }
1708 tmp = fwupd_remote_get_username (remote);
1709 if (tmp != NULL) {
1710 /* TRANSLATORS: remote filename base */
1711 fu_common_string_append_kv (str, idt + 1, _("Username"), tmp);
1712 }
1713 tmp = fwupd_remote_get_password (remote);
1714 if (tmp != NULL) {
Richard Hughesae96a1f2019-09-23 11:16:36 +01001715 g_autofree gchar *hidden = g_strnfill (fu_common_strwidth (tmp), '*');
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001716 /* TRANSLATORS: remote filename base */
1717 fu_common_string_append_kv (str, idt + 1, _("Password"), hidden);
1718 }
1719 tmp = fwupd_remote_get_filename_cache (remote);
1720 if (tmp != NULL) {
1721 /* TRANSLATORS: filename of the local file */
1722 fu_common_string_append_kv (str, idt + 1, _("Filename"), tmp);
1723 }
1724 tmp = fwupd_remote_get_filename_cache_sig (remote);
1725 if (tmp != NULL) {
1726 /* TRANSLATORS: filename of the local file */
1727 fu_common_string_append_kv (str, idt + 1, _("Filename Signature"), tmp);
1728 }
1729 tmp = fwupd_remote_get_metadata_uri (remote);
1730 if (tmp != NULL) {
1731 /* TRANSLATORS: remote URI */
1732 fu_common_string_append_kv (str, idt + 1, _("Metadata URI"), tmp);
1733 }
1734 tmp = fwupd_remote_get_metadata_uri_sig (remote);
1735 if (tmp != NULL) {
1736 /* TRANSLATORS: remote URI */
1737 fu_common_string_append_kv (str, idt + 1, _("Metadata Signature"), tmp);
1738 }
1739 tmp = fwupd_remote_get_firmware_base_uri (remote);
1740 if (tmp != NULL) {
1741 /* TRANSLATORS: remote URI */
1742 fu_common_string_append_kv (str, idt + 1, _("Firmware Base URI"), tmp);
1743 }
1744 tmp = fwupd_remote_get_report_uri (remote);
1745 if (tmp != NULL) {
1746 /* TRANSLATORS: URI to send success/failure reports */
1747 fu_common_string_append_kv (str, idt + 1, _("Report URI"), tmp);
Mario Limonciello34c366a2019-09-24 11:24:24 -05001748 /* TRANSLATORS: Boolean value to automatically send reports */
1749 fu_common_string_append_kv (str, idt + 1, _("Automatic Reporting"),
1750 fwupd_remote_get_automatic_reports (remote) ? "true" : "false");
Mario Limonciello4250d9d2019-08-29 09:53:44 -05001751 }
Mario Limonciellofee8f492019-08-18 12:16:07 -05001752
1753 return g_string_free (str, FALSE);
1754}
Richard Hughes196c6c62020-05-11 19:42:47 +01001755
1756static void
Richard Hughes5c82b942020-09-14 12:24:06 +01001757fu_security_attr_append_str (FwupdSecurityAttr *attr, GString *str, FuSecurityAttrToStringFlags flags)
Richard Hughes196c6c62020-05-11 19:42:47 +01001758{
Richard Hughes5c82b942020-09-14 12:24:06 +01001759 g_autofree gchar *name = NULL;
1760
1761 /* hide obsoletes by default */
1762 if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED) &&
1763 (flags & FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES) == 0)
1764 return;
1765
1766 name = fu_security_attr_get_name (attr);
Richard Hughes4d2c0f82020-07-07 12:02:30 +01001767 if (name == NULL)
1768 name = g_strdup (fwupd_security_attr_get_appstream_id (attr));
Richard Hughes1b97ee22020-05-14 20:48:06 +01001769 if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) {
Richard Hughesdcd32eb2020-05-18 20:58:08 +01001770 g_string_append (str, "✦ ");
Richard Hughes1b97ee22020-05-14 20:48:06 +01001771 } else if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) {
Richard Hughesb246bca2020-05-18 14:31:35 +01001772 g_string_append (str, "✔ ");
Richard Hughes196c6c62020-05-11 19:42:47 +01001773 } else {
Richard Hughesb246bca2020-05-18 14:31:35 +01001774 g_string_append (str, "✘ ");
Richard Hughes196c6c62020-05-11 19:42:47 +01001775 }
Richard Hughesb99df2e2020-07-01 19:28:35 +01001776 g_string_append_printf (str, "%s:", name);
1777 for (guint i = fu_common_strwidth (name); i < 30; i++)
Richard Hughesb246bca2020-05-18 14:31:35 +01001778 g_string_append (str, " ");
1779 if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) {
1780 g_string_append_printf (str, "\033[37m\033[1m%s\033[0m", fu_security_attr_get_result (attr));
1781 } else if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) {
1782 g_string_append_printf (str, "\033[32m\033[1m%s\033[0m", fu_security_attr_get_result (attr));
Richard Hughes196c6c62020-05-11 19:42:47 +01001783 } else {
Richard Hughesb246bca2020-05-18 14:31:35 +01001784 g_string_append_printf (str, "\033[31m\033[1m%s\033[0m", fu_security_attr_get_result (attr));
1785 }
Richard Hughes5c82b942020-09-14 12:24:06 +01001786 if ((flags & FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS) > 0 &&
1787 fwupd_security_attr_get_url (attr) != NULL) {
Richard Hughes196c6c62020-05-11 19:42:47 +01001788 g_string_append_printf (str, ": %s",
Richard Hughesb246bca2020-05-18 14:31:35 +01001789 fwupd_security_attr_get_url (attr));
Richard Hughes196c6c62020-05-11 19:42:47 +01001790 }
Richard Hughesfa34c312020-06-30 20:37:21 +01001791 if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) {
1792 /* TRANSLATORS: this is shown as a suffix for obsoleted tests */
1793 g_string_append_printf (str, " %s", _("(obsoleted)"));
1794 }
Richard Hughes196c6c62020-05-11 19:42:47 +01001795 g_string_append_printf (str, "\n");
1796}
1797
1798gchar *
Richard Hughes5c82b942020-09-14 12:24:06 +01001799fu_util_security_attrs_to_string (GPtrArray *attrs, FuSecurityAttrToStringFlags strflags)
Richard Hughes196c6c62020-05-11 19:42:47 +01001800{
1801 FwupdSecurityAttrFlags flags = FWUPD_SECURITY_ATTR_FLAG_NONE;
1802 const FwupdSecurityAttrFlags hpi_suffixes[] = {
Richard Hughes196c6c62020-05-11 19:42:47 +01001803 FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE,
1804 FWUPD_SECURITY_ATTR_FLAG_NONE,
1805 };
1806 GString *str = g_string_new (NULL);
Mario Limonciello6ed9cbd2020-05-13 10:40:45 -05001807 gboolean low_help = FALSE;
Mario Limonciellob8a57e52020-05-13 09:34:29 -05001808 gboolean runtime_help = FALSE;
Mario Limonciellob5638402020-06-25 09:34:54 -05001809 gboolean pcr0_help = FALSE;
Richard Hughes196c6c62020-05-11 19:42:47 +01001810
1811 for (guint j = 1; j <= FWUPD_SECURITY_ATTR_LEVEL_LAST; j++) {
1812 gboolean has_header = FALSE;
1813 for (guint i = 0; i < attrs->len; i++) {
1814 FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i);
1815 if (fwupd_security_attr_get_level (attr) != j)
1816 continue;
1817 if (!has_header) {
1818 g_string_append_printf (str, "\n\033[1mHSI-%u\033[0m\n", j);
1819 has_header = TRUE;
1820 }
Richard Hughes5c82b942020-09-14 12:24:06 +01001821 fu_security_attr_append_str (attr, str, strflags);
Mario Limonciello6ed9cbd2020-05-13 10:40:45 -05001822 /* make sure they have at least HSI-1 */
1823 if (j < FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT &&
1824 !fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS))
1825 low_help = TRUE;
Mario Limonciellob5638402020-06-25 09:34:54 -05001826
1827 /* check for PCR0 not matching */
1828 if (g_strcmp0 (fwupd_security_attr_get_appstream_id (attr),
1829 FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0) == 0 &&
1830 fwupd_security_attr_get_result (attr) == FWUPD_SECURITY_ATTR_RESULT_NOT_VALID)
1831 pcr0_help = TRUE;
Richard Hughes196c6c62020-05-11 19:42:47 +01001832 }
1833 }
1834 for (guint i = 0; i < attrs->len; i++) {
1835 FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i);
1836 flags |= fwupd_security_attr_get_flags (attr);
1837 }
1838 for (guint j = 0; hpi_suffixes[j] != FWUPD_SECURITY_ATTR_FLAG_NONE; j++) {
1839 if (flags & hpi_suffixes[j]) {
1840 g_string_append_printf (str, "\n\033[1m%s -%s\033[0m\n",
1841 /* TRANSLATORS: this is the HSI suffix */
1842 _("Runtime Suffix"),
1843 fwupd_security_attr_flag_to_suffix (hpi_suffixes[j]));
1844 for (guint i = 0; i < attrs->len; i++) {
1845 FwupdSecurityAttr *attr = g_ptr_array_index (attrs, i);
1846 if (!fwupd_security_attr_has_flag (attr, hpi_suffixes[j]))
1847 continue;
Mario Limonciellob8a57e52020-05-13 09:34:29 -05001848 if (fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE) &&
1849 !fwupd_security_attr_has_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS))
1850 runtime_help = TRUE;
Richard Hughes5c82b942020-09-14 12:24:06 +01001851 fu_security_attr_append_str (attr, str, strflags);
Richard Hughes196c6c62020-05-11 19:42:47 +01001852 }
1853 }
1854 }
Mario Limonciellob8a57e52020-05-13 09:34:29 -05001855
Mario Limonciello6ed9cbd2020-05-13 10:40:45 -05001856 if (low_help) {
Richard Hughesc11bed42020-05-19 20:10:45 +01001857 g_string_append_printf (str, "\n%s\n » %s\n",
Mario Limonciello6ed9cbd2020-05-13 10:40:45 -05001858 /* TRANSLATORS: this is instructions on how to improve the HSI security level */
Richard Hughesc11bed42020-05-19 20:10:45 +01001859 _("This system has a low HSI security level."),
Mario Limonciello6ed9cbd2020-05-13 10:40:45 -05001860 "https://github.com/fwupd/fwupd/wiki/Low-host-security-level");
1861 }
Mario Limonciellob8a57e52020-05-13 09:34:29 -05001862 if (runtime_help) {
Richard Hughesc11bed42020-05-19 20:10:45 +01001863 g_string_append_printf (str, "\n%s\n » %s\n",
Mario Limonciellob8a57e52020-05-13 09:34:29 -05001864 /* TRANSLATORS: this is instructions on how to improve the HSI suffix */
Richard Hughesc11bed42020-05-19 20:10:45 +01001865 _("This system has HSI runtime issues."),
Mario Limonciellob8a57e52020-05-13 09:34:29 -05001866 "https://github.com/fwupd/fwupd/wiki/Host-security-ID-runtime-issues");
1867 }
1868
Mario Limonciellob5638402020-06-25 09:34:54 -05001869 if (pcr0_help) {
1870 g_string_append_printf (str, "\n%s\n » %s\n",
1871 /* TRANSLATORS: this is more background on a security measurement problem */
Richard Hughes907fd152020-07-08 21:08:46 +01001872 _("The TPM PCR0 differs from reconstruction."),
Mario Limonciellob5638402020-06-25 09:34:54 -05001873 "https://github.com/fwupd/fwupd/wiki/TPM-PCR0-differs-from-reconstruction");
1874
1875 }
1876
Richard Hughes196c6c62020-05-11 19:42:47 +01001877 return g_string_free (str, FALSE);
1878}
Richard Hughes6d9ae622020-06-16 15:20:23 +01001879
1880gboolean
Richard Hughes9b6d6162020-07-07 16:24:57 +01001881fu_util_send_report (FwupdClient *client,
Richard Hughes6d9ae622020-06-16 15:20:23 +01001882 const gchar *report_uri,
1883 const gchar *data,
1884 const gchar *sig,
1885 gchar **uri, /* (nullable) (out) */
1886 GError **error)
1887{
1888 const gchar *server_msg = NULL;
Richard Hughes6d9ae622020-06-16 15:20:23 +01001889 JsonNode *json_root;
1890 JsonObject *json_object;
Richard Hughes9b6d6162020-07-07 16:24:57 +01001891 g_autofree gchar *str = NULL;
1892 g_autoptr(GBytes) upload_response = NULL;
Richard Hughes6d9ae622020-06-16 15:20:23 +01001893 g_autoptr(JsonParser) json_parser = NULL;
Richard Hughes6d9ae622020-06-16 15:20:23 +01001894
1895 /* POST request */
Richard Hughes9b6d6162020-07-07 16:24:57 +01001896 upload_response = fwupd_client_upload_bytes (client, report_uri, data, sig,
1897 FWUPD_CLIENT_UPLOAD_FLAG_NONE,
1898 NULL, error);
1899 if (upload_response == NULL)
1900 return FALSE;
Richard Hughes6d9ae622020-06-16 15:20:23 +01001901
1902 /* server returned nothing, and probably exploded in a ball of flames */
Richard Hughes9b6d6162020-07-07 16:24:57 +01001903 if (g_bytes_get_size (upload_response) == 0) {
Richard Hughes6d9ae622020-06-16 15:20:23 +01001904 g_set_error (error,
1905 FWUPD_ERROR,
1906 FWUPD_ERROR_INVALID_FILE,
Richard Hughes9b6d6162020-07-07 16:24:57 +01001907 "Failed to upload to %s",
1908 report_uri);
Richard Hughes6d9ae622020-06-16 15:20:23 +01001909 return FALSE;
1910 }
1911
1912 /* parse JSON reply */
1913 json_parser = json_parser_new ();
Richard Hughes9b6d6162020-07-07 16:24:57 +01001914 str = g_strndup (g_bytes_get_data (upload_response, NULL),
1915 g_bytes_get_size (upload_response));
1916 if (!json_parser_load_from_data (json_parser, str, -1, error)) {
Richard Hughes6d9ae622020-06-16 15:20:23 +01001917 g_prefix_error (error, "Failed to parse JSON response from '%s': ", str);
1918 return FALSE;
1919 }
1920 json_root = json_parser_get_root (json_parser);
1921 if (json_root == NULL) {
Richard Hughes6d9ae622020-06-16 15:20:23 +01001922 g_set_error (error,
1923 FWUPD_ERROR,
1924 FWUPD_ERROR_PERMISSION_DENIED,
1925 "JSON response was malformed: '%s'", str);
1926 return FALSE;
1927 }
1928 json_object = json_node_get_object (json_root);
1929 if (json_object == NULL) {
Richard Hughes6d9ae622020-06-16 15:20:23 +01001930 g_set_error (error,
1931 FWUPD_ERROR,
1932 FWUPD_ERROR_PERMISSION_DENIED,
1933 "JSON response object was malformed: '%s'", str);
1934 return FALSE;
1935 }
1936
1937 /* get any optional server message */
1938 if (json_object_has_member (json_object, "msg"))
1939 server_msg = json_object_get_string_member (json_object, "msg");
1940
1941 /* server reported failed */
1942 if (!json_object_get_boolean_member (json_object, "success")) {
1943 g_set_error (error,
1944 FWUPD_ERROR,
1945 FWUPD_ERROR_PERMISSION_DENIED,
1946 "Server rejected report: %s",
1947 server_msg != NULL ? server_msg : "unspecified");
1948 return FALSE;
1949 }
1950
1951 /* server wanted us to see the message */
1952 if (server_msg != NULL) {
1953 g_debug ("server message: %s", server_msg);
1954 if (g_strstr_len (server_msg, -1, "known issue") != NULL &&
1955 json_object_has_member (json_object, "uri")) {
1956 if (uri != NULL)
1957 *uri = g_strdup (json_object_get_string_member (json_object, "uri"));
1958 }
1959 }
1960
Richard Hughes6d9ae622020-06-16 15:20:23 +01001961 /* success */
1962 return TRUE;
1963}
Mario Limoncielloeb7be162020-07-01 15:38:47 -05001964
1965gint
1966fu_util_sort_devices_by_flags_cb (gconstpointer a, gconstpointer b)
1967{
1968 FuDevice *dev_a = *((FuDevice **) a);
1969 FuDevice *dev_b = *((FuDevice **) b);
1970
1971 if ((!fu_device_has_flag (dev_a, FWUPD_DEVICE_FLAG_UPDATABLE) &&
1972 fu_device_has_flag (dev_b, FWUPD_DEVICE_FLAG_UPDATABLE)) ||
1973 (!fu_device_has_flag (dev_a, FWUPD_DEVICE_FLAG_SUPPORTED) &&
1974 fu_device_has_flag (dev_b, FWUPD_DEVICE_FLAG_SUPPORTED)))
1975 return -1;
1976 if ((fu_device_has_flag (dev_a, FWUPD_DEVICE_FLAG_UPDATABLE) &&
1977 !fu_device_has_flag (dev_b, FWUPD_DEVICE_FLAG_UPDATABLE)) ||
1978 (fu_device_has_flag (dev_a, FWUPD_DEVICE_FLAG_SUPPORTED) &&
1979 !fu_device_has_flag (dev_b, FWUPD_DEVICE_FLAG_SUPPORTED)))
1980 return 1;
1981
1982 return 0;
1983}
Mario Limonciello02085a02020-09-11 14:59:35 -05001984
1985static gint
1986fu_util_device_order_compare (FuDevice *device1, FuDevice *device2)
1987{
1988 if (fu_device_get_order (device1) < fu_device_get_order (device2))
1989 return -1;
1990 if (fu_device_get_order (device1) > fu_device_get_order (device2))
1991 return 1;
1992 return 0;
1993}
1994
1995gint
1996fu_util_device_order_sort_cb (gconstpointer a, gconstpointer b)
1997{
1998 FuDevice *device_a = *((FuDevice **) a);
1999 FuDevice *device_b = *((FuDevice **) b);
2000 return fu_util_device_order_compare (device_a, device_b);
2001}
Mario Limonciello02f2cc32020-10-20 15:39:47 -05002002
2003
2004gboolean
2005fu_util_switch_branch_warning (FwupdDevice *dev,
2006 FwupdRelease *rel,
2007 gboolean assume_yes,
2008 GError **error)
2009{
2010 const gchar *desc_markup = NULL;
2011 g_autofree gchar *desc_plain = NULL;
Richard Hughese69f0f52021-04-22 11:10:23 +01002012 g_autofree gchar *title = NULL;
Mario Limonciello02f2cc32020-10-20 15:39:47 -05002013 g_autoptr(GString) desc_full = g_string_new (NULL);
2014
2015 /* warn the user if the vendor is different */
2016 if (g_strcmp0 (fwupd_device_get_vendor (dev), fwupd_release_get_vendor (rel)) != 0) {
2017 /* TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name */
2018 g_string_append_printf (desc_full, _("The firmware from %s is not "
2019 "supplied by %s, the hardware vendor."),
2020 fwupd_release_get_vendor (rel),
2021 fwupd_device_get_vendor (dev));
2022 g_string_append (desc_full, "\n\n");
2023 /* TRANSLATORS: %1 is the device vendor name */
2024 g_string_append_printf (desc_full, _("Your hardware may be damaged using this firmware, "
2025 "and installing this release may void any warranty "
2026 "with %s."),
2027 fwupd_device_get_vendor (dev));
2028 g_string_append (desc_full, "\n\n");
2029 }
2030
2031 /* from the <description> in the AppStream data */
2032 desc_markup = fwupd_release_get_description (rel);
2033 if (desc_markup == NULL)
2034 return TRUE;
2035 desc_plain = fu_util_convert_description (desc_markup, error);
2036 if (desc_plain == NULL)
2037 return FALSE;
2038 g_string_append (desc_full, desc_plain);
2039
Richard Hughese69f0f52021-04-22 11:10:23 +01002040 /* TRANSLATORS: show and ask user to confirm --
2041 * %1 is the old branch name, %2 is the new branch name */
2042 title = g_strdup_printf (_("Switch branch from %s to %s?"),
2043 fu_util_branch_for_display (fwupd_device_get_branch (dev)),
2044 fu_util_branch_for_display (fwupd_release_get_branch (rel)));
2045 fu_util_warning_box (title, desc_full->str, 80);
Mario Limonciello02f2cc32020-10-20 15:39:47 -05002046 if (!assume_yes) {
2047 /* ask for permission */
2048 g_print ("\n%s [y|N]: ",
2049 /* TRANSLATORS: should the branch be changed */
2050 _("Do you understand the consequences of changing the firmware branch?"));
2051 if (!fu_util_prompt_for_boolean (FALSE)) {
2052 g_set_error_literal (error,
2053 FWUPD_ERROR,
2054 FWUPD_ERROR_NOTHING_TO_DO,
2055 "Declined branch switch");
2056 return FALSE;
2057 }
2058 }
2059 return TRUE;
2060}
Mario Limonciellobd60de12020-11-11 13:09:43 -06002061
2062void
2063fu_util_show_unsupported_warn (void)
2064{
2065#ifndef SUPPORTED_BUILD
2066 g_autofree gchar *fmt = NULL;
2067 /* TRANSLATORS: this is a prefix on the console */
2068 fmt = fu_util_term_format (_("WARNING:"), FU_UTIL_CLI_COLOR_YELLOW);
2069 /* TRANSLATORS: unsupported build of the package */
2070 g_printerr ("%s %s\n", fmt, _("This package has not been validated, it may not work properly."));
2071#endif
2072}
Richard Hughes3a73c342020-11-13 13:25:22 +00002073
Richard Hughes00640f42020-12-07 10:25:12 +00002074#ifdef HAVE_LIBCURL_7_62_0
Richard Hughes3a73c342020-11-13 13:25:22 +00002075G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup)
Richard Hughes00640f42020-12-07 10:25:12 +00002076#endif
Richard Hughes3a73c342020-11-13 13:25:22 +00002077
2078gboolean
2079fu_util_is_url (const gchar *perhaps_url)
2080{
Richard Hughes00640f42020-12-07 10:25:12 +00002081#ifdef HAVE_LIBCURL_7_62_0
Richard Hughes3a73c342020-11-13 13:25:22 +00002082 g_autoptr(CURLU) h = curl_url ();
2083 return curl_url_set (h, CURLUPART_URL, perhaps_url, 0) == CURLUE_OK;
Richard Hughes00640f42020-12-07 10:25:12 +00002084#else
2085 return g_str_has_prefix (perhaps_url, "http://") ||
2086 g_str_has_prefix (perhaps_url, "https://");
2087#endif
Richard Hughes3a73c342020-11-13 13:25:22 +00002088}