/* * Copyright (C) 2017-2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include "fu-util-common.h" #include "fu-device.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif #define SYSTEMD_FWUPD_UNIT "fwupd.service" #define SYSTEMD_SNAP_FWUPD_UNIT "snap.fwupd.fwupd.service" const gchar * fu_util_get_systemd_unit (void) { if (g_getenv ("SNAP") != NULL) return SYSTEMD_SNAP_FWUPD_UNIT; return SYSTEMD_FWUPD_UNIT; } static const gchar * fu_util_get_expected_command (const gchar *target) { if (g_strcmp0 (target, SYSTEMD_SNAP_FWUPD_UNIT) == 0) return "fwupd.fwupdmgr"; return "fwupdmgr"; } gboolean fu_util_using_correct_daemon (GError **error) { g_autofree gchar *default_target = NULL; g_autoptr(GError) error_local = NULL; const gchar *target = fu_util_get_systemd_unit (); default_target = fu_systemd_get_default_target (&error_local); if (default_target == NULL) { g_debug ("Systemd isn't accessible: %s\n", error_local->message); return TRUE; } if (!fu_systemd_unit_check_exists (target, &error_local)) { g_debug ("wrong target: %s\n", error_local->message); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Mismatched daemon and client, use %s instead"), fu_util_get_expected_command (target)); return FALSE; } return TRUE; } void fu_util_print_data (const gchar *title, const gchar *msg) { gsize title_len; g_auto(GStrv) lines = NULL; if (msg == NULL) return; g_print ("%s:", title); /* pad */ title_len = strlen (title) + 1; lines = g_strsplit (msg, "\n", -1); for (guint j = 0; lines[j] != NULL; j++) { for (gsize i = title_len; i < 25; i++) g_print (" "); g_print ("%s\n", lines[j]); title_len = 0; } } guint fu_util_prompt_for_number (guint maxnum) { gint retval; guint answer = 0; do { char buffer[64]; /* swallow the \n at end of line too */ if (!fgets (buffer, sizeof (buffer), stdin)) break; if (strlen (buffer) == sizeof (buffer) - 1) continue; /* get a number */ retval = sscanf (buffer, "%u", &answer); /* positive */ if (retval == 1 && answer <= maxnum) break; /* TRANSLATORS: the user isn't reading the question */ g_print (_("Please enter a number from 0 to %u: "), maxnum); } while (TRUE); return answer; } gboolean fu_util_prompt_for_boolean (gboolean def) { do { char buffer[4]; if (!fgets (buffer, sizeof (buffer), stdin)) continue; if (strlen (buffer) == sizeof (buffer) - 1) continue; if (g_strcmp0 (buffer, "\n") == 0) return def; buffer[0] = g_ascii_toupper (buffer[0]); if (g_strcmp0 (buffer, "Y\n") == 0) return TRUE; if (g_strcmp0 (buffer, "N\n") == 0) return FALSE; } while (TRUE); return FALSE; } gboolean fu_util_print_device_tree (GNode *n, gpointer data) { FwupdDevice *dev = FWUPD_DEVICE (n->data); const gchar *name; g_autoptr(GString) str = g_string_new (NULL); /* root node */ if (dev == NULL) { g_print ("○\n"); return FALSE; } /* add previous branches */ for (GNode *c = n->parent; c->parent != NULL; c = c->parent) { if (g_node_next_sibling (c) == NULL) g_string_prepend (str, " "); else g_string_prepend (str, "│ "); } /* add this branch */ if (g_node_last_sibling (n) == n) g_string_append (str, "└─ "); else g_string_append (str, "├─ "); /* dump to the console */ name = fwupd_device_get_name (dev); if (name == NULL) name = "Unknown device"; g_string_append (str, name); for (guint i = strlen (name) + 2 * g_node_depth (n); i < 45; i++) g_string_append_c (str, ' '); g_print ("%s %s\n", str->str, fu_device_get_id (dev)); return FALSE; } gboolean fu_util_is_interesting_device (FwupdDevice *dev) { if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE)) return TRUE; if (fwupd_device_get_update_error (dev) != NULL) return TRUE; return FALSE; } gchar * fu_util_get_user_cache_path (const gchar *fn) { g_autofree gchar *basename = g_path_get_basename (fn); g_autofree gchar *cachedir_legacy = NULL; /* return the legacy path if it exists rather than renaming it to * prevent problems when using old and new versions of fwupd */ cachedir_legacy = g_build_filename (g_get_user_cache_dir (), "fwupdmgr", NULL); if (g_file_test (cachedir_legacy, G_FILE_TEST_IS_DIR)) return g_build_filename (cachedir_legacy, basename, NULL); return g_build_filename (g_get_user_cache_dir (), "fwupd", basename, NULL); } gchar * fu_util_get_versions (void) { GString *string = g_string_new (""); g_string_append_printf (string, "client version:\t%i.%i.%i\n", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); #ifdef FWUPD_GIT_DESCRIBE g_string_append_printf (string, "checkout info:\t%s\n", FWUPD_GIT_DESCRIBE); #endif g_string_append_printf (string, "compile-time dependency versions\n"); g_string_append_printf (string, "\tgusb:\t%d.%d.%d\n", G_USB_MAJOR_VERSION, G_USB_MINOR_VERSION, G_USB_MICRO_VERSION); #ifdef EFIVAR_LIBRARY_VERSION g_string_append_printf (string, "\tefivar:\t%s", EFIVAR_LIBRARY_VERSION); #endif return g_string_free (string, FALSE); } static gboolean fu_util_update_shutdown (GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; #ifdef HAVE_LOGIND /* shutdown using logind */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PowerOff", g_variant_new ("(b)", TRUE), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #elif defined(HAVE_CONSOLEKIT) /* shutdown using ConsoleKit */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Stop", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "No supported backend compiled in to perform the operation."); #endif return val != NULL; } gboolean fu_util_update_reboot (GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; #ifdef HAVE_LOGIND /* reboot using logind */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Reboot", g_variant_new ("(b)", TRUE), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #elif defined(HAVE_CONSOLEKIT) /* reboot using ConsoleKit */ val = g_dbus_connection_call_sync (connection, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Restart", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #else g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "No supported backend compiled in to perform the operation."); #endif return val != NULL; } gboolean fu_util_prompt_complete (FwupdDeviceFlags flags, gboolean prompt, GError **error) { if (flags & FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) { if (prompt) { g_print ("\n%s %s [Y|n]: ", /* TRANSLATORS: explain why we want to shutdown */ _("An update requires the system to shutdown to complete."), /* TRANSLATORS: shutdown to apply the update */ _("Shutdown now?")); if (!fu_util_prompt_for_boolean (TRUE)) return TRUE; } return fu_util_update_shutdown (error); } if (flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) { if (prompt) { g_print ("\n%s %s [Y|n]: ", /* TRANSLATORS: explain why we want to reboot */ _("An update requires a reboot to complete."), /* TRANSLATORS: reboot to apply the update */ _("Restart now?")); if (!fu_util_prompt_for_boolean (TRUE)) return TRUE; } return fu_util_update_reboot (error); } return TRUE; } static void fu_util_cmd_free (FuUtilCmd *item) { g_free (item->name); g_free (item->arguments); g_free (item->description); g_free (item); } GPtrArray * fu_util_cmd_array_new (void) { return g_ptr_array_new_with_free_func ((GDestroyNotify) fu_util_cmd_free); } static gint fu_util_cmd_sort_cb (FuUtilCmd **item1, FuUtilCmd **item2) { return g_strcmp0 ((*item1)->name, (*item2)->name); } void fu_util_cmd_array_sort (GPtrArray *array) { g_ptr_array_sort (array, (GCompareFunc) fu_util_cmd_sort_cb); } void fu_util_cmd_array_add (GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilCmdFunc callback) { g_auto(GStrv) names = NULL; g_return_if_fail (name != NULL); g_return_if_fail (description != NULL); g_return_if_fail (callback != NULL); /* add each one */ names = g_strsplit (name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilCmd *item = g_new0 (FuUtilCmd, 1); item->name = g_strdup (names[i]); if (i == 0) { item->description = g_strdup (description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf (_("Alias to %s"), names[0]); } item->arguments = g_strdup (arguments); item->callback = callback; g_ptr_array_add (array, item); } } gboolean fu_util_cmd_array_run (GPtrArray *array, FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < array->len; i++) { FuUtilCmd *item = g_ptr_array_index (array, i); if (g_strcmp0 (item->name, command) == 0) return item->callback (priv, values, error); } /* not found */ g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Command not found")); return FALSE; } gchar * fu_util_cmd_array_to_string (GPtrArray *array) { gsize len; const gsize max_len = 35; GString *string; /* print each command */ string = g_string_new (""); for (guint i = 0; i < array->len; i++) { FuUtilCmd *item = g_ptr_array_index (array, i); g_string_append (string, " "); g_string_append (string, item->name); len = strlen (item->name) + 2; if (item->arguments != NULL) { g_string_append (string, " "); g_string_append (string, item->arguments); len += strlen (item->arguments) + 1; } if (len < max_len) { for (gsize j = len; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } else { g_string_append_c (string, '\n'); for (gsize j = 0; j < max_len + 1; j++) g_string_append_c (string, ' '); g_string_append (string, item->description); g_string_append_c (string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size (string, string->len - 1); return g_string_free (string, FALSE); } SoupSession * fu_util_setup_networking (GError **error) { const gchar *http_proxy; g_autofree gchar *user_agent = NULL; g_autoptr(SoupSession) session = NULL; /* create the soup session */ user_agent = fwupd_build_user_agent (PACKAGE_NAME, PACKAGE_VERSION); session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, SOUP_SESSION_TIMEOUT, 60, NULL); if (session == NULL) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to setup networking"); return NULL; } /* set the proxy */ http_proxy = g_getenv ("https_proxy"); if (http_proxy == NULL) http_proxy = g_getenv ("HTTPS_PROXY"); if (http_proxy == NULL) http_proxy = g_getenv ("http_proxy"); if (http_proxy == NULL) http_proxy = g_getenv ("HTTP_PROXY"); if (http_proxy != NULL) { g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy); if (proxy_uri == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid proxy URI: %s", http_proxy); return NULL; } g_object_set (session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); } /* this disables the double-compression of the firmware.xml.gz file */ soup_session_remove_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER); return g_steal_pointer (&session); } gchar * fu_util_release_get_name (FwupdRelease *release) { const gchar *name = fwupd_release_get_name (release); GPtrArray *cats = fwupd_release_get_categories (release); for (guint i = 0; i < cats->len; i++) { const gchar *cat = g_ptr_array_index (cats, i); if (g_strcmp0 (cat, "X-Device") == 0) { /* TRANSLATORS: a specific part of hardware, * the first %s is the device name, e.g. 'Unifying Receiver` */ return g_strdup_printf (_("%s Device Update"), name); } if (g_strcmp0 (cat, "X-System") == 0) { /* TRANSLATORS: the entire system, e.g. all internal devices, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s System Update"), name); } if (g_strcmp0 (cat, "X-EmbeddedController") == 0) { /* TRANSLATORS: the EC is typically the keyboard controller chip, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Embedded Controller Update"), name); } if (g_strcmp0 (cat, "X-ManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s ME Update"), name); } if (g_strcmp0 (cat, "X-Controller") == 0) { /* TRANSLATORS: the controller is a device that has other devices * plugged into it, for example ThunderBolt, FireWire or USB, * the first %s is the device name, e.g. 'Intel ThunderBolt` */ return g_strdup_printf (_("%s Controller Update"), name); } } /* TRANSLATORS: this is the fallback where we don't know if the release * is updating the system, the device, or a device class, or something else -- * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf (_("%s Update"), name); } static GPtrArray * fu_util_strsplit_words (const gchar *text, guint line_len) { g_auto(GStrv) tokens = NULL; g_autoptr(GPtrArray) lines = g_ptr_array_new (); g_autoptr(GString) curline = g_string_new (NULL); /* sanity check */ if (text == NULL || text[0] == '\0') return NULL; if (line_len == 0) return NULL; /* tokenize the string */ tokens = g_strsplit (text, " ", -1); for (guint i = 0; tokens[i] != NULL; i++) { /* current line plus new token is okay */ if (curline->len + strlen (tokens[i]) < line_len) { g_string_append_printf (curline, "%s ", tokens[i]); continue; } /* too long, so remove space, add newline and dump */ if (curline->len > 0) g_string_truncate (curline, curline->len - 1); g_ptr_array_add (lines, g_strdup (curline->str)); g_string_truncate (curline, 0); g_string_append_printf (curline, "%s ", tokens[i]); } /* any incomplete line? */ if (curline->len > 0) { g_string_truncate (curline, curline->len - 1); g_ptr_array_add (lines, g_strdup (curline->str)); } return g_steal_pointer (&lines); } static void fu_util_warning_box_line (const gchar *start, const gchar *text, const gchar *end, const gchar *padding, guint width) { guint offset = 0; if (start != NULL) { offset += g_utf8_strlen (start, -1); g_print ("%s", start); } if (text != NULL) { offset += g_utf8_strlen (text, -1); g_print ("%s", text); } if (end != NULL) offset += g_utf8_strlen (end, -1); for (guint i = offset; i < width; i++) g_print ("%s", padding); if (end != NULL) g_print ("%s\n", end); } void fu_util_warning_box (const gchar *str, guint width) { g_auto(GStrv) split = g_strsplit (str, "\n", -1); /* header */ fu_util_warning_box_line ("╔", NULL, "╗", "═", width); /* body */ for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GPtrArray) lines = fu_util_strsplit_words (split[i], width - 4); if (lines == NULL) continue; for (guint j = 0; j < lines->len; j++) { const gchar *line = g_ptr_array_index (lines, j); fu_util_warning_box_line ("║ ", line, " ║", " ", width); } fu_util_warning_box_line ("║", NULL, "║", " ", width); } /* footer */ fu_util_warning_box_line ("╚", NULL, "╝", "═", width); }