fwupd/src/fu-util-common.c
Richard Hughes 3d00522dd3 Check if the system is offline during install, rather than at startup
This splits out the systemd functionality to a new file, but makes no other
logic changes.
2019-05-18 08:02:29 -07:00

641 lines
16 KiB
C

/*
* Copyright (C) 2017-2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include <config.h>
#include <stdio.h>
#include <glib/gi18n.h>
#include <gusb.h>
#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);
}