mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-25 22:40:50 +00:00

Using fu_common_strnsplit() has the drawback that a malicious user (or a fuzzer!) could create a file with 5,000,000 newlines, and then pass that into any parser that tokenizes into lines. This causes millions of tiny allocations and quickly dirties hundreds of megabytes of RSS due to heap overheads. Rather than splitting a huge array and then processing each line, set up a callback to process each line and only allocate the next string if the token was parsed correctly. This means that we don't even dup the buffer before we start parsing, rather than allocating everything and then failing at the first hurdle. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=38696
3620 lines
92 KiB
C
3620 lines
92 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "FuCommon"
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef HAVE_GIO_UNIX
|
|
#include <gio/gunixinputstream.h>
|
|
#endif
|
|
#include <glib/gstdio.h>
|
|
|
|
#ifdef HAVE_KENV_H
|
|
#include <kenv.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_CPUID_H
|
|
#include <cpuid.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_UTSNAME_H
|
|
#include <sys/utsname.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBARCHIVE
|
|
#include <archive.h>
|
|
#include <archive_entry.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fwupd-error.h"
|
|
|
|
#include "fu-common-private.h"
|
|
#include "fu-common-version.h"
|
|
#include "fu-firmware.h"
|
|
#include "fu-volume-private.h"
|
|
|
|
/**
|
|
* fu_common_rmtree:
|
|
* @directory: a directory name
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Recursively removes a directory.
|
|
*
|
|
* Returns: %TRUE for success, %FALSE otherwise
|
|
*
|
|
* Since: 0.9.7
|
|
**/
|
|
gboolean
|
|
fu_common_rmtree(const gchar *directory, GError **error)
|
|
{
|
|
const gchar *filename;
|
|
g_autoptr(GDir) dir = NULL;
|
|
|
|
g_return_val_if_fail(directory != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* try to open */
|
|
g_debug("removing %s", directory);
|
|
dir = g_dir_open(directory, 0, error);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
|
|
/* find each */
|
|
while ((filename = g_dir_read_name(dir))) {
|
|
g_autofree gchar *src = NULL;
|
|
src = g_build_filename(directory, filename, NULL);
|
|
if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
|
|
if (!fu_common_rmtree(src, error))
|
|
return FALSE;
|
|
} else {
|
|
if (g_unlink(src) != 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Failed to delete: %s",
|
|
src);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
if (g_remove(directory) != 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Failed to delete: %s",
|
|
directory);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_common_get_file_list_internal(GPtrArray *files, const gchar *directory, GError **error)
|
|
{
|
|
const gchar *filename;
|
|
g_autoptr(GDir) dir = NULL;
|
|
|
|
/* try to open */
|
|
dir = g_dir_open(directory, 0, error);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
|
|
/* find each */
|
|
while ((filename = g_dir_read_name(dir))) {
|
|
g_autofree gchar *src = g_build_filename(directory, filename, NULL);
|
|
if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
|
|
if (!fu_common_get_file_list_internal(files, src, error))
|
|
return FALSE;
|
|
} else {
|
|
g_ptr_array_add(files, g_steal_pointer(&src));
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_files_recursive:
|
|
* @path: a directory name
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Returns every file found under @directory, and any subdirectory.
|
|
* If any path under @directory cannot be accessed due to permissions an error
|
|
* will be returned.
|
|
*
|
|
* Returns: (transfer container) (element-type utf8): array of files, or %NULL for error
|
|
*
|
|
* Since: 1.0.6
|
|
**/
|
|
GPtrArray *
|
|
fu_common_get_files_recursive(const gchar *path, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free);
|
|
|
|
g_return_val_if_fail(path != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
if (!fu_common_get_file_list_internal(files, path, error))
|
|
return NULL;
|
|
return g_steal_pointer(&files);
|
|
}
|
|
/**
|
|
* fu_common_mkdir_parent:
|
|
* @filename: a full pathname
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Creates any required directories, including any parent directories.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 0.9.7
|
|
**/
|
|
gboolean
|
|
fu_common_mkdir_parent(const gchar *filename, GError **error)
|
|
{
|
|
g_autofree gchar *parent = NULL;
|
|
|
|
g_return_val_if_fail(filename != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
parent = g_path_get_dirname(filename);
|
|
if (!g_file_test(parent, G_FILE_TEST_IS_DIR))
|
|
g_debug("creating path %s", parent);
|
|
if (g_mkdir_with_parents(parent, 0755) == -1) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Failed to create '%s': %s",
|
|
parent,
|
|
g_strerror(errno));
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_set_contents_bytes:
|
|
* @filename: a filename
|
|
* @bytes: data to write
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Writes a blob of data to a filename, creating the parent directories as
|
|
* required.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 0.9.5
|
|
**/
|
|
gboolean
|
|
fu_common_set_contents_bytes(const gchar *filename, GBytes *bytes, GError **error)
|
|
{
|
|
const gchar *data;
|
|
gsize size;
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autoptr(GFile) file_parent = NULL;
|
|
|
|
g_return_val_if_fail(filename != NULL, FALSE);
|
|
g_return_val_if_fail(bytes != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
file = g_file_new_for_path(filename);
|
|
file_parent = g_file_get_parent(file);
|
|
if (!g_file_query_exists(file_parent, NULL)) {
|
|
if (!g_file_make_directory_with_parents(file_parent, NULL, error))
|
|
return FALSE;
|
|
}
|
|
data = g_bytes_get_data(bytes, &size);
|
|
g_debug("writing %s with %" G_GSIZE_FORMAT " bytes", filename, size);
|
|
return g_file_set_contents(filename, data, size, error);
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_contents_bytes:
|
|
* @filename: a filename
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Reads a blob of data from a file.
|
|
*
|
|
* Returns: a #GBytes, or %NULL for failure
|
|
*
|
|
* Since: 0.9.7
|
|
**/
|
|
GBytes *
|
|
fu_common_get_contents_bytes(const gchar *filename, GError **error)
|
|
{
|
|
gchar *data = NULL;
|
|
gsize len = 0;
|
|
|
|
g_return_val_if_fail(filename != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
if (!g_file_get_contents(filename, &data, &len, error))
|
|
return NULL;
|
|
g_debug("reading %s with %" G_GSIZE_FORMAT " bytes", filename, len);
|
|
return g_bytes_new_take(data, len);
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_contents_fd:
|
|
* @fd: a file descriptor
|
|
* @count: the maximum number of bytes to read
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Reads a blob from a specific file descriptor.
|
|
*
|
|
* Note: this will close the fd when done
|
|
*
|
|
* Returns: (transfer full): a #GBytes, or %NULL
|
|
*
|
|
* Since: 0.9.5
|
|
**/
|
|
GBytes *
|
|
fu_common_get_contents_fd(gint fd, gsize count, GError **error)
|
|
{
|
|
#ifdef HAVE_GIO_UNIX
|
|
guint8 tmp[0x8000] = {0x0};
|
|
g_autoptr(GByteArray) buf = g_byte_array_new();
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(GInputStream) stream = NULL;
|
|
|
|
g_return_val_if_fail(fd > 0, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* this is invalid */
|
|
if (count == 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"A maximum read size must be specified");
|
|
return NULL;
|
|
}
|
|
|
|
/* read the entire fd to a data blob */
|
|
stream = g_unix_input_stream_new(fd, TRUE);
|
|
|
|
/* read from stream in 32kB chunks */
|
|
while (TRUE) {
|
|
gssize sz;
|
|
sz = g_input_stream_read(stream, tmp, sizeof(tmp), NULL, &error_local);
|
|
if (sz == 0)
|
|
break;
|
|
if (sz < 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
error_local->message);
|
|
return NULL;
|
|
}
|
|
g_byte_array_append(buf, tmp, sz);
|
|
if (buf->len > count) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"cannot read from fd: 0x%x > 0x%x",
|
|
buf->len,
|
|
(guint)count);
|
|
return NULL;
|
|
}
|
|
}
|
|
return g_byte_array_free_to_bytes(g_steal_pointer(&buf));
|
|
#else
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Not supported as <glib-unix.h> is unavailable");
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_LIBARCHIVE
|
|
static gboolean
|
|
fu_common_extract_archive_entry(struct archive_entry *entry, const gchar *dir)
|
|
{
|
|
const gchar *tmp;
|
|
g_autofree gchar *buf = NULL;
|
|
|
|
/* no output file */
|
|
if (archive_entry_pathname(entry) == NULL)
|
|
return FALSE;
|
|
|
|
/* update output path */
|
|
tmp = archive_entry_pathname(entry);
|
|
buf = g_build_filename(dir, tmp, NULL);
|
|
archive_entry_update_pathname_utf8(entry, buf);
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* fu_common_extract_archive:
|
|
* @blob: data archive as a blob
|
|
* @dir: a directory name to extract to
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Extracts an archive to a directory.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 0.9.7
|
|
**/
|
|
gboolean
|
|
fu_common_extract_archive(GBytes *blob, const gchar *dir, GError **error)
|
|
{
|
|
#ifdef HAVE_LIBARCHIVE
|
|
gboolean ret = TRUE;
|
|
int r;
|
|
struct archive *arch = NULL;
|
|
struct archive_entry *entry;
|
|
|
|
g_return_val_if_fail(blob != NULL, FALSE);
|
|
g_return_val_if_fail(dir != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* decompress anything matching either glob */
|
|
g_debug("decompressing into %s", dir);
|
|
arch = archive_read_new();
|
|
archive_read_support_format_all(arch);
|
|
archive_read_support_filter_all(arch);
|
|
r = archive_read_open_memory(arch,
|
|
(void *)g_bytes_get_data(blob, NULL),
|
|
(size_t)g_bytes_get_size(blob));
|
|
if (r != 0) {
|
|
ret = FALSE;
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Cannot open: %s",
|
|
archive_error_string(arch));
|
|
goto out;
|
|
}
|
|
for (;;) {
|
|
gboolean valid;
|
|
r = archive_read_next_header(arch, &entry);
|
|
if (r == ARCHIVE_EOF)
|
|
break;
|
|
if (r != ARCHIVE_OK) {
|
|
ret = FALSE;
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Cannot read header: %s",
|
|
archive_error_string(arch));
|
|
goto out;
|
|
}
|
|
|
|
/* only extract if valid */
|
|
valid = fu_common_extract_archive_entry(entry, dir);
|
|
if (!valid)
|
|
continue;
|
|
r = archive_read_extract(arch, entry, 0);
|
|
if (r != ARCHIVE_OK) {
|
|
ret = FALSE;
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"Cannot extract: %s",
|
|
archive_error_string(arch));
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
if (arch != NULL) {
|
|
archive_read_close(arch);
|
|
archive_read_free(arch);
|
|
}
|
|
return ret;
|
|
#else
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing libarchive support");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
fu_common_add_argv(GPtrArray *argv, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3);
|
|
|
|
static void
|
|
fu_common_add_argv(GPtrArray *argv, const gchar *fmt, ...)
|
|
{
|
|
va_list args;
|
|
g_autofree gchar *tmp = NULL;
|
|
g_auto(GStrv) split = NULL;
|
|
|
|
va_start(args, fmt);
|
|
tmp = g_strdup_vprintf(fmt, args);
|
|
va_end(args);
|
|
|
|
split = g_strsplit(tmp, " ", -1);
|
|
for (guint i = 0; split[i] != NULL; i++)
|
|
g_ptr_array_add(argv, g_strdup(split[i]));
|
|
}
|
|
|
|
/**
|
|
* fu_common_find_program_in_path:
|
|
* @basename: the program to search
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Looks for a program in the PATH variable
|
|
*
|
|
* Returns: a new #gchar, or %NULL for error
|
|
*
|
|
* Since: 1.1.2
|
|
**/
|
|
gchar *
|
|
fu_common_find_program_in_path(const gchar *basename, GError **error)
|
|
{
|
|
gchar *fn = g_find_program_in_path(basename);
|
|
if (fn == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing executable %s in PATH",
|
|
basename);
|
|
return NULL;
|
|
}
|
|
return fn;
|
|
}
|
|
|
|
static gboolean
|
|
fu_common_test_namespace_support(GError **error)
|
|
{
|
|
/* test if CONFIG_USER_NS is valid */
|
|
if (!g_file_test("/proc/self/ns/user", G_FILE_TEST_IS_SYMLINK)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"missing CONFIG_USER_NS in kernel");
|
|
return FALSE;
|
|
}
|
|
if (g_file_test("/proc/sys/kernel/unprivileged_userns_clone", G_FILE_TEST_EXISTS)) {
|
|
g_autofree gchar *clone = NULL;
|
|
if (!g_file_get_contents("/proc/sys/kernel/unprivileged_userns_clone",
|
|
&clone,
|
|
NULL,
|
|
error))
|
|
return FALSE;
|
|
if (g_ascii_strtoll(clone, NULL, 10) == 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"unprivileged user namespace clones disabled by distro");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_firmware_builder:
|
|
* @bytes: the data to use
|
|
* @script_fn: Name of the script to run in the tarball, e.g. `startup.sh`
|
|
* @output_fn: Name of the generated firmware, e.g. `firmware.bin`
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Builds a firmware file using tools from the host session in a bubblewrap
|
|
* jail. Several things happen during build:
|
|
*
|
|
* 1. The @bytes data is untarred to a temporary location
|
|
* 2. A bubblewrap container is set up
|
|
* 3. The startup.sh script is run inside the container
|
|
* 4. The firmware.bin is extracted from the container
|
|
* 5. The temporary location is deleted
|
|
*
|
|
* Returns: a new #GBytes, or %NULL for error
|
|
*
|
|
* Since: 0.9.7
|
|
**/
|
|
GBytes *
|
|
fu_common_firmware_builder(GBytes *bytes,
|
|
const gchar *script_fn,
|
|
const gchar *output_fn,
|
|
GError **error)
|
|
{
|
|
gint rc = 0;
|
|
g_autofree gchar *argv_str = NULL;
|
|
g_autofree gchar *bwrap_fn = NULL;
|
|
g_autofree gchar *localstatebuilderdir = NULL;
|
|
g_autofree gchar *localstatedir = NULL;
|
|
g_autofree gchar *output2_fn = NULL;
|
|
g_autofree gchar *standard_error = NULL;
|
|
g_autofree gchar *standard_output = NULL;
|
|
g_autofree gchar *tmpdir = NULL;
|
|
g_autoptr(GBytes) firmware_blob = NULL;
|
|
g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func(g_free);
|
|
|
|
g_return_val_if_fail(bytes != NULL, NULL);
|
|
g_return_val_if_fail(script_fn != NULL, NULL);
|
|
g_return_val_if_fail(output_fn != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* find bwrap in the path */
|
|
bwrap_fn = fu_common_find_program_in_path("bwrap", error);
|
|
if (bwrap_fn == NULL)
|
|
return NULL;
|
|
|
|
/* test if CONFIG_USER_NS is valid */
|
|
if (!fu_common_test_namespace_support(error))
|
|
return NULL;
|
|
|
|
/* untar file to temp location */
|
|
tmpdir = g_dir_make_tmp("fwupd-gen-XXXXXX", error);
|
|
if (tmpdir == NULL)
|
|
return NULL;
|
|
if (!fu_common_extract_archive(bytes, tmpdir, error))
|
|
return NULL;
|
|
|
|
/* this is shared with the plugins */
|
|
localstatedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
|
localstatebuilderdir = g_build_filename(localstatedir, "builder", NULL);
|
|
|
|
/* launch bubblewrap and generate firmware */
|
|
g_ptr_array_add(argv, g_steal_pointer(&bwrap_fn));
|
|
fu_common_add_argv(argv, "--die-with-parent");
|
|
fu_common_add_argv(argv, "--ro-bind /usr /usr");
|
|
fu_common_add_argv(argv, "--ro-bind /lib /lib");
|
|
fu_common_add_argv(argv, "--ro-bind-try /lib64 /lib64");
|
|
fu_common_add_argv(argv, "--ro-bind /bin /bin");
|
|
fu_common_add_argv(argv, "--ro-bind /sbin /sbin");
|
|
fu_common_add_argv(argv, "--dir /tmp");
|
|
fu_common_add_argv(argv, "--dir /var");
|
|
fu_common_add_argv(argv, "--bind %s /tmp", tmpdir);
|
|
if (g_file_test(localstatebuilderdir, G_FILE_TEST_EXISTS))
|
|
fu_common_add_argv(argv, "--ro-bind %s /boot", localstatebuilderdir);
|
|
fu_common_add_argv(argv, "--dev /dev");
|
|
fu_common_add_argv(argv, "--chdir /tmp");
|
|
fu_common_add_argv(argv, "--unshare-all");
|
|
fu_common_add_argv(argv, "/tmp/%s", script_fn);
|
|
g_ptr_array_add(argv, NULL);
|
|
argv_str = g_strjoinv(" ", (gchar **)argv->pdata);
|
|
g_debug("running '%s' in %s", argv_str, tmpdir);
|
|
if (!g_spawn_sync("/tmp",
|
|
(gchar **)argv->pdata,
|
|
NULL,
|
|
G_SPAWN_SEARCH_PATH,
|
|
NULL,
|
|
NULL, /* child_setup */
|
|
&standard_output,
|
|
&standard_error,
|
|
&rc,
|
|
error)) {
|
|
g_prefix_error(error, "failed to run '%s': ", argv_str);
|
|
return NULL;
|
|
}
|
|
if (standard_output != NULL && standard_output[0] != '\0')
|
|
g_debug("console output was: %s", standard_output);
|
|
if (rc != 0) {
|
|
FwupdError code = FWUPD_ERROR_INTERNAL;
|
|
if (errno == ENOTTY)
|
|
code = FWUPD_ERROR_PERMISSION_DENIED;
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
code,
|
|
"failed to build firmware: %s",
|
|
standard_error);
|
|
return NULL;
|
|
}
|
|
|
|
/* get generated file */
|
|
output2_fn = g_build_filename(tmpdir, output_fn, NULL);
|
|
firmware_blob = fu_common_get_contents_bytes(output2_fn, error);
|
|
if (firmware_blob == NULL)
|
|
return NULL;
|
|
|
|
/* cleanup temp directory */
|
|
if (!fu_common_rmtree(tmpdir, error))
|
|
return NULL;
|
|
|
|
/* success */
|
|
return g_steal_pointer(&firmware_blob);
|
|
}
|
|
|
|
typedef struct {
|
|
FuOutputHandler handler_cb;
|
|
gpointer handler_user_data;
|
|
GMainLoop *loop;
|
|
GSource *source;
|
|
GInputStream *stream;
|
|
GCancellable *cancellable;
|
|
guint timeout_id;
|
|
} FuCommonSpawnHelper;
|
|
|
|
static void
|
|
fu_common_spawn_create_pollable_source(FuCommonSpawnHelper *helper);
|
|
|
|
static gboolean
|
|
fu_common_spawn_source_pollable_cb(GObject *stream, gpointer user_data)
|
|
{
|
|
FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *)user_data;
|
|
gchar buffer[1024];
|
|
gssize sz;
|
|
g_auto(GStrv) split = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
/* read from stream */
|
|
sz = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(stream),
|
|
buffer,
|
|
sizeof(buffer) - 1,
|
|
NULL,
|
|
&error);
|
|
if (sz < 0) {
|
|
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
|
|
g_warning("failed to get read from nonblocking fd: %s", error->message);
|
|
}
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* no read possible */
|
|
if (sz == 0)
|
|
g_main_loop_quit(helper->loop);
|
|
|
|
/* emit lines */
|
|
if (helper->handler_cb != NULL) {
|
|
buffer[sz] = '\0';
|
|
split = g_strsplit(buffer, "\n", -1);
|
|
for (guint i = 0; split[i] != NULL; i++) {
|
|
if (split[i][0] == '\0')
|
|
continue;
|
|
helper->handler_cb(split[i], helper->handler_user_data);
|
|
}
|
|
}
|
|
|
|
/* set up the source for the next read */
|
|
fu_common_spawn_create_pollable_source(helper);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
fu_common_spawn_create_pollable_source(FuCommonSpawnHelper *helper)
|
|
{
|
|
if (helper->source != NULL)
|
|
g_source_destroy(helper->source);
|
|
helper->source =
|
|
g_pollable_input_stream_create_source(G_POLLABLE_INPUT_STREAM(helper->stream),
|
|
helper->cancellable);
|
|
g_source_attach(helper->source, NULL);
|
|
g_source_set_callback(helper->source,
|
|
(GSourceFunc)fu_common_spawn_source_pollable_cb,
|
|
helper,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
fu_common_spawn_helper_free(FuCommonSpawnHelper *helper)
|
|
{
|
|
g_object_unref(helper->cancellable);
|
|
if (helper->stream != NULL)
|
|
g_object_unref(helper->stream);
|
|
if (helper->source != NULL)
|
|
g_source_destroy(helper->source);
|
|
if (helper->loop != NULL)
|
|
g_main_loop_unref(helper->loop);
|
|
if (helper->timeout_id != 0)
|
|
g_source_remove(helper->timeout_id);
|
|
g_free(helper);
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
|
|
#pragma clang diagnostic pop
|
|
|
|
static gboolean
|
|
fu_common_spawn_timeout_cb(gpointer user_data)
|
|
{
|
|
FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *)user_data;
|
|
g_cancellable_cancel(helper->cancellable);
|
|
g_main_loop_quit(helper->loop);
|
|
helper->timeout_id = 0;
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
fu_common_spawn_cancelled_cb(GCancellable *cancellable, FuCommonSpawnHelper *helper)
|
|
{
|
|
/* just propagate */
|
|
g_cancellable_cancel(helper->cancellable);
|
|
}
|
|
|
|
/**
|
|
* fu_common_spawn_sync:
|
|
* @argv: the argument list to run
|
|
* @handler_cb: (scope call) (nullable): optional #FuOutputHandler
|
|
* @handler_user_data: (nullable): the user data to pass to @handler_cb
|
|
* @timeout_ms: a timeout in ms, or 0 for no limit
|
|
* @cancellable: (nullable): optional #GCancellable
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Runs a subprocess and waits for it to exit. Any output on standard out or
|
|
* standard error will be forwarded to @handler_cb as whole lines.
|
|
*
|
|
* Returns: %TRUE for success
|
|
*
|
|
* Since: 0.9.7
|
|
**/
|
|
gboolean
|
|
fu_common_spawn_sync(const gchar *const *argv,
|
|
FuOutputHandler handler_cb,
|
|
gpointer handler_user_data,
|
|
guint timeout_ms,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuCommonSpawnHelper) helper = NULL;
|
|
g_autoptr(GSubprocess) subprocess = NULL;
|
|
g_autofree gchar *argv_str = NULL;
|
|
gulong cancellable_id = 0;
|
|
|
|
g_return_val_if_fail(argv != NULL, FALSE);
|
|
g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* create subprocess */
|
|
argv_str = g_strjoinv(" ", (gchar **)argv);
|
|
g_debug("running '%s'", argv_str);
|
|
subprocess =
|
|
g_subprocess_newv(argv,
|
|
G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE,
|
|
error);
|
|
if (subprocess == NULL)
|
|
return FALSE;
|
|
|
|
/* watch for process to exit */
|
|
helper = g_new0(FuCommonSpawnHelper, 1);
|
|
helper->handler_cb = handler_cb;
|
|
helper->handler_user_data = handler_user_data;
|
|
helper->loop = g_main_loop_new(NULL, FALSE);
|
|
helper->stream = g_subprocess_get_stdout_pipe(subprocess);
|
|
|
|
/* always create a cancellable, and connect up the parent */
|
|
helper->cancellable = g_cancellable_new();
|
|
if (cancellable != NULL) {
|
|
cancellable_id = g_cancellable_connect(cancellable,
|
|
G_CALLBACK(fu_common_spawn_cancelled_cb),
|
|
helper,
|
|
NULL);
|
|
}
|
|
|
|
/* allow timeout */
|
|
if (timeout_ms > 0) {
|
|
helper->timeout_id = g_timeout_add(timeout_ms, fu_common_spawn_timeout_cb, helper);
|
|
}
|
|
fu_common_spawn_create_pollable_source(helper);
|
|
g_main_loop_run(helper->loop);
|
|
g_cancellable_disconnect(cancellable, cancellable_id);
|
|
if (g_cancellable_set_error_if_cancelled(helper->cancellable, error))
|
|
return FALSE;
|
|
return g_subprocess_wait_check(subprocess, cancellable, error);
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint16:
|
|
* @buf: a writable buffer
|
|
* @val_native: a value in host byte-order
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
*
|
|
* Writes a value to a buffer using a specified endian.
|
|
*
|
|
* Since: 1.0.3
|
|
**/
|
|
void
|
|
fu_common_write_uint16(guint8 *buf, guint16 val_native, FuEndianType endian)
|
|
{
|
|
guint16 val_hw;
|
|
switch (endian) {
|
|
case G_BIG_ENDIAN:
|
|
val_hw = GUINT16_TO_BE(val_native);
|
|
break;
|
|
case G_LITTLE_ENDIAN:
|
|
val_hw = GUINT16_TO_LE(val_native);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
memcpy(buf, &val_hw, sizeof(val_hw));
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint32:
|
|
* @buf: a writable buffer
|
|
* @val_native: a value in host byte-order
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
*
|
|
* Writes a value to a buffer using a specified endian.
|
|
*
|
|
* Since: 1.0.3
|
|
**/
|
|
void
|
|
fu_common_write_uint32(guint8 *buf, guint32 val_native, FuEndianType endian)
|
|
{
|
|
guint32 val_hw;
|
|
switch (endian) {
|
|
case G_BIG_ENDIAN:
|
|
val_hw = GUINT32_TO_BE(val_native);
|
|
break;
|
|
case G_LITTLE_ENDIAN:
|
|
val_hw = GUINT32_TO_LE(val_native);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
memcpy(buf, &val_hw, sizeof(val_hw));
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint64:
|
|
* @buf: a writable buffer
|
|
* @val_native: a value in host byte-order
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
*
|
|
* Writes a value to a buffer using a specified endian.
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
void
|
|
fu_common_write_uint64(guint8 *buf, guint64 val_native, FuEndianType endian)
|
|
{
|
|
guint64 val_hw;
|
|
switch (endian) {
|
|
case G_BIG_ENDIAN:
|
|
val_hw = GUINT64_TO_BE(val_native);
|
|
break;
|
|
case G_LITTLE_ENDIAN:
|
|
val_hw = GUINT64_TO_LE(val_native);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
memcpy(buf, &val_hw, sizeof(val_hw));
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint16:
|
|
* @buf: a readable buffer
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
*
|
|
* Read a value from a buffer using a specified endian.
|
|
*
|
|
* Returns: a value in host byte-order
|
|
*
|
|
* Since: 1.0.3
|
|
**/
|
|
guint16
|
|
fu_common_read_uint16(const guint8 *buf, FuEndianType endian)
|
|
{
|
|
guint16 val_hw, val_native;
|
|
memcpy(&val_hw, buf, sizeof(val_hw));
|
|
switch (endian) {
|
|
case G_BIG_ENDIAN:
|
|
val_native = GUINT16_FROM_BE(val_hw);
|
|
break;
|
|
case G_LITTLE_ENDIAN:
|
|
val_native = GUINT16_FROM_LE(val_hw);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
return val_native;
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint32:
|
|
* @buf: a readable buffer
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
*
|
|
* Read a value from a buffer using a specified endian.
|
|
*
|
|
* Returns: a value in host byte-order
|
|
*
|
|
* Since: 1.0.3
|
|
**/
|
|
guint32
|
|
fu_common_read_uint32(const guint8 *buf, FuEndianType endian)
|
|
{
|
|
guint32 val_hw, val_native;
|
|
memcpy(&val_hw, buf, sizeof(val_hw));
|
|
switch (endian) {
|
|
case G_BIG_ENDIAN:
|
|
val_native = GUINT32_FROM_BE(val_hw);
|
|
break;
|
|
case G_LITTLE_ENDIAN:
|
|
val_native = GUINT32_FROM_LE(val_hw);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
return val_native;
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint64:
|
|
* @buf: a readable buffer
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
*
|
|
* Read a value from a buffer using a specified endian.
|
|
*
|
|
* Returns: a value in host byte-order
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
guint64
|
|
fu_common_read_uint64(const guint8 *buf, FuEndianType endian)
|
|
{
|
|
guint64 val_hw, val_native;
|
|
memcpy(&val_hw, buf, sizeof(val_hw));
|
|
switch (endian) {
|
|
case G_BIG_ENDIAN:
|
|
val_native = GUINT64_FROM_BE(val_hw);
|
|
break;
|
|
case G_LITTLE_ENDIAN:
|
|
val_native = GUINT64_FROM_LE(val_hw);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
return val_native;
|
|
}
|
|
|
|
/**
|
|
* fu_common_strtoull:
|
|
* @str: a string, e.g. `0x1234`
|
|
*
|
|
* Converts a string value to an integer. Values are assumed base 10, unless
|
|
* prefixed with "0x" where they are parsed as base 16.
|
|
*
|
|
* Returns: integer value, or 0x0 for error
|
|
*
|
|
* Since: 1.1.2
|
|
**/
|
|
guint64
|
|
fu_common_strtoull(const gchar *str)
|
|
{
|
|
guint base = 10;
|
|
if (str == NULL)
|
|
return 0x0;
|
|
if (g_str_has_prefix(str, "0x")) {
|
|
str += 2;
|
|
base = 16;
|
|
}
|
|
return g_ascii_strtoull(str, NULL, base);
|
|
}
|
|
|
|
/**
|
|
* fu_common_strstrip:
|
|
* @str: a string, e.g. ` test `
|
|
*
|
|
* Removes leading and trailing whitespace from a constant string.
|
|
*
|
|
* Returns: newly allocated string
|
|
*
|
|
* Since: 1.1.2
|
|
**/
|
|
gchar *
|
|
fu_common_strstrip(const gchar *str)
|
|
{
|
|
guint head = G_MAXUINT;
|
|
guint tail = 0;
|
|
|
|
g_return_val_if_fail(str != NULL, NULL);
|
|
|
|
/* find first non-space char */
|
|
for (guint i = 0; str[i] != '\0'; i++) {
|
|
if (str[i] != ' ') {
|
|
head = i;
|
|
break;
|
|
}
|
|
}
|
|
if (head == G_MAXUINT)
|
|
return g_strdup("");
|
|
|
|
/* find last non-space char */
|
|
for (guint i = head; str[i] != '\0'; i++) {
|
|
if (!g_ascii_isspace(str[i]))
|
|
tail = i;
|
|
}
|
|
return g_strndup(str + head, tail - head + 1);
|
|
}
|
|
|
|
static const GError *
|
|
fu_common_error_array_find(GPtrArray *errors, FwupdError error_code)
|
|
{
|
|
for (guint j = 0; j < errors->len; j++) {
|
|
const GError *error = g_ptr_array_index(errors, j);
|
|
if (g_error_matches(error, FWUPD_ERROR, error_code))
|
|
return error;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static guint
|
|
fu_common_error_array_count(GPtrArray *errors, FwupdError error_code)
|
|
{
|
|
guint cnt = 0;
|
|
for (guint j = 0; j < errors->len; j++) {
|
|
const GError *error = g_ptr_array_index(errors, j);
|
|
if (g_error_matches(error, FWUPD_ERROR, error_code))
|
|
cnt++;
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
static gboolean
|
|
fu_common_error_array_matches_any(GPtrArray *errors, FwupdError *error_codes)
|
|
{
|
|
for (guint j = 0; j < errors->len; j++) {
|
|
const GError *error = g_ptr_array_index(errors, j);
|
|
gboolean matches_any = FALSE;
|
|
for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) {
|
|
if (g_error_matches(error, FWUPD_ERROR, error_codes[i])) {
|
|
matches_any = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!matches_any)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_error_array_get_best:
|
|
* @errors: (element-type GError): array of errors
|
|
*
|
|
* Finds the 'best' error to show the user from a array of errors, creating a
|
|
* completely bespoke error where required.
|
|
*
|
|
* Returns: (transfer full): a #GError, never %NULL
|
|
*
|
|
* Since: 1.0.8
|
|
**/
|
|
GError *
|
|
fu_common_error_array_get_best(GPtrArray *errors)
|
|
{
|
|
FwupdError err_prio[] = {FWUPD_ERROR_INVALID_FILE,
|
|
FWUPD_ERROR_VERSION_SAME,
|
|
FWUPD_ERROR_VERSION_NEWER,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
FWUPD_ERROR_INTERNAL,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
FWUPD_ERROR_LAST};
|
|
FwupdError err_all_uptodate[] = {FWUPD_ERROR_VERSION_SAME,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
FWUPD_ERROR_LAST};
|
|
FwupdError err_all_newer[] = {FWUPD_ERROR_VERSION_NEWER,
|
|
FWUPD_ERROR_VERSION_SAME,
|
|
FWUPD_ERROR_NOT_FOUND,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
FWUPD_ERROR_LAST};
|
|
|
|
/* are all the errors either GUID-not-matched or version-same? */
|
|
if (fu_common_error_array_count(errors, FWUPD_ERROR_VERSION_SAME) > 1 &&
|
|
fu_common_error_array_matches_any(errors, err_all_uptodate)) {
|
|
return g_error_new(FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"All updatable firmware is already installed");
|
|
}
|
|
|
|
/* are all the errors either GUID-not-matched or version same or newer? */
|
|
if (fu_common_error_array_count(errors, FWUPD_ERROR_VERSION_NEWER) > 1 &&
|
|
fu_common_error_array_matches_any(errors, err_all_newer)) {
|
|
return g_error_new(FWUPD_ERROR,
|
|
FWUPD_ERROR_NOTHING_TO_DO,
|
|
"All updatable devices already have newer versions");
|
|
}
|
|
|
|
/* get the most important single error */
|
|
for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) {
|
|
const GError *error_tmp = fu_common_error_array_find(errors, err_prio[i]);
|
|
if (error_tmp != NULL)
|
|
return g_error_copy(error_tmp);
|
|
}
|
|
|
|
/* fall back to something */
|
|
return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found");
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_path:
|
|
* @path_kind: a #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG
|
|
*
|
|
* Gets a fwupd-specific system path. These can be overridden with various
|
|
* environment variables, for instance %FWUPD_DATADIR.
|
|
*
|
|
* Returns: a system path, or %NULL if invalid
|
|
*
|
|
* Since: 1.0.8
|
|
**/
|
|
gchar *
|
|
fu_common_get_path(FuPathKind path_kind)
|
|
{
|
|
const gchar *tmp;
|
|
g_autofree gchar *basedir = NULL;
|
|
|
|
switch (path_kind) {
|
|
/* /var */
|
|
case FU_PATH_KIND_LOCALSTATEDIR:
|
|
tmp = g_getenv("FWUPD_LOCALSTATEDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
tmp = g_getenv("SNAP_USER_DATA");
|
|
if (tmp != NULL)
|
|
return g_build_filename(tmp, FWUPD_LOCALSTATEDIR, NULL);
|
|
return g_build_filename(FWUPD_LOCALSTATEDIR, NULL);
|
|
/* /proc */
|
|
case FU_PATH_KIND_PROCFS:
|
|
tmp = g_getenv("FWUPD_PROCFS");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/proc");
|
|
/* /sys/firmware */
|
|
case FU_PATH_KIND_SYSFSDIR_FW:
|
|
tmp = g_getenv("FWUPD_SYSFSFWDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/firmware");
|
|
/* /sys/class/tpm */
|
|
case FU_PATH_KIND_SYSFSDIR_TPM:
|
|
tmp = g_getenv("FWUPD_SYSFSTPMDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/class/tpm");
|
|
/* /sys/bus/platform/drivers */
|
|
case FU_PATH_KIND_SYSFSDIR_DRIVERS:
|
|
tmp = g_getenv("FWUPD_SYSFSDRIVERDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/bus/platform/drivers");
|
|
/* /sys/kernel/security */
|
|
case FU_PATH_KIND_SYSFSDIR_SECURITY:
|
|
tmp = g_getenv("FWUPD_SYSFSSECURITYDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/kernel/security");
|
|
/* /sys/firmware/acpi/tables */
|
|
case FU_PATH_KIND_ACPI_TABLES:
|
|
tmp = g_getenv("FWUPD_ACPITABLESDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/firmware/acpi/tables");
|
|
/* /sys/module/firmware_class/parameters/path */
|
|
case FU_PATH_KIND_FIRMWARE_SEARCH:
|
|
tmp = g_getenv("FWUPD_FIRMWARESEARCH");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/module/firmware_class/parameters/path");
|
|
/* /etc */
|
|
case FU_PATH_KIND_SYSCONFDIR:
|
|
tmp = g_getenv("FWUPD_SYSCONFDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
tmp = g_getenv("SNAP_USER_DATA");
|
|
if (tmp != NULL)
|
|
return g_build_filename(tmp, FWUPD_SYSCONFDIR, NULL);
|
|
return g_strdup(FWUPD_SYSCONFDIR);
|
|
/* /usr/lib/<triplet>/fwupd-plugins-3 */
|
|
case FU_PATH_KIND_PLUGINDIR_PKG:
|
|
tmp = g_getenv("FWUPD_PLUGINDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
tmp = g_getenv("SNAP");
|
|
if (tmp != NULL)
|
|
return g_build_filename(tmp, FWUPD_PLUGINDIR, NULL);
|
|
return g_build_filename(FWUPD_PLUGINDIR, NULL);
|
|
/* /usr/share/fwupd */
|
|
case FU_PATH_KIND_DATADIR_PKG:
|
|
tmp = g_getenv("FWUPD_DATADIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
tmp = g_getenv("SNAP");
|
|
if (tmp != NULL)
|
|
return g_build_filename(tmp, FWUPD_DATADIR, PACKAGE_NAME, NULL);
|
|
return g_build_filename(FWUPD_DATADIR, PACKAGE_NAME, NULL);
|
|
/* /usr/share/fwupd/quirks.d */
|
|
case FU_PATH_KIND_DATADIR_QUIRKS:
|
|
tmp = g_getenv("FWUPD_DATADIR_QUIRKS");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
basedir = fu_common_get_path(FU_PATH_KIND_DATADIR_PKG);
|
|
return g_build_filename(basedir, "quirks.d", NULL);
|
|
/* /usr/libexec/fwupd/efi */
|
|
case FU_PATH_KIND_EFIAPPDIR:
|
|
tmp = g_getenv("FWUPD_EFIAPPDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
#ifdef EFI_APP_LOCATION
|
|
tmp = g_getenv("SNAP");
|
|
if (tmp != NULL)
|
|
return g_build_filename(tmp, EFI_APP_LOCATION, NULL);
|
|
return g_strdup(EFI_APP_LOCATION);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
/* /etc/fwupd */
|
|
case FU_PATH_KIND_SYSCONFDIR_PKG:
|
|
tmp = g_getenv("CONFIGURATION_DIRECTORY");
|
|
if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS))
|
|
return g_build_filename(tmp, NULL);
|
|
basedir = fu_common_get_path(FU_PATH_KIND_SYSCONFDIR);
|
|
return g_build_filename(basedir, PACKAGE_NAME, NULL);
|
|
/* /var/lib/fwupd */
|
|
case FU_PATH_KIND_LOCALSTATEDIR_PKG:
|
|
tmp = g_getenv("STATE_DIRECTORY");
|
|
if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS))
|
|
return g_build_filename(tmp, NULL);
|
|
basedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR);
|
|
return g_build_filename(basedir, "lib", PACKAGE_NAME, NULL);
|
|
/* /var/lib/fwupd/quirks.d */
|
|
case FU_PATH_KIND_LOCALSTATEDIR_QUIRKS:
|
|
tmp = g_getenv("FWUPD_LOCALSTATEDIR_QUIRKS");
|
|
if (tmp != NULL)
|
|
return g_build_filename(tmp, NULL);
|
|
basedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR_PKG);
|
|
return g_build_filename(basedir, "quirks.d", NULL);
|
|
/* /var/cache/fwupd */
|
|
case FU_PATH_KIND_CACHEDIR_PKG:
|
|
tmp = g_getenv("CACHE_DIRECTORY");
|
|
if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS))
|
|
return g_build_filename(tmp, NULL);
|
|
basedir = fu_common_get_path(FU_PATH_KIND_LOCALSTATEDIR);
|
|
return g_build_filename(basedir, "cache", PACKAGE_NAME, NULL);
|
|
/* /run/lock */
|
|
case FU_PATH_KIND_LOCKDIR:
|
|
return g_strdup("/run/lock");
|
|
/* /sys/class/firmware-attributes */
|
|
case FU_PATH_KIND_SYSFSDIR_FW_ATTRIB:
|
|
tmp = g_getenv("FWUPD_SYSFSFWATTRIBDIR");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/sys/class/firmware-attributes");
|
|
case FU_PATH_KIND_OFFLINE_TRIGGER:
|
|
tmp = g_getenv("FWUPD_OFFLINE_TRIGGER");
|
|
if (tmp != NULL)
|
|
return g_strdup(tmp);
|
|
return g_strdup("/system-update");
|
|
case FU_PATH_KIND_POLKIT_ACTIONS:
|
|
#ifdef POLKIT_ACTIONDIR
|
|
return g_strdup(POLKIT_ACTIONDIR);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
/* this shouldn't happen */
|
|
default:
|
|
g_warning("cannot build path for unknown kind %u", path_kind);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_common_string_replace:
|
|
* @string: the #GString to operate on
|
|
* @search: the text to search for
|
|
* @replace: the text to use for substitutions
|
|
*
|
|
* Performs multiple search and replace operations on the given string.
|
|
*
|
|
* Returns: the number of replacements done, or 0 if @search is not found.
|
|
*
|
|
* Since: 1.2.0
|
|
**/
|
|
guint
|
|
fu_common_string_replace(GString *string, const gchar *search, const gchar *replace)
|
|
{
|
|
gchar *tmp;
|
|
guint count = 0;
|
|
gsize search_idx = 0;
|
|
gsize replace_len;
|
|
gsize search_len;
|
|
|
|
g_return_val_if_fail(string != NULL, 0);
|
|
g_return_val_if_fail(search != NULL, 0);
|
|
g_return_val_if_fail(replace != NULL, 0);
|
|
|
|
/* nothing to do */
|
|
if (string->len == 0)
|
|
return 0;
|
|
|
|
search_len = strlen(search);
|
|
replace_len = strlen(replace);
|
|
|
|
do {
|
|
tmp = g_strstr_len(string->str + search_idx, -1, search);
|
|
if (tmp == NULL)
|
|
break;
|
|
|
|
/* advance the counter in case @replace contains @search */
|
|
search_idx = (gsize)(tmp - string->str);
|
|
|
|
/* reallocate the string if required */
|
|
if (search_len > replace_len) {
|
|
g_string_erase(string,
|
|
(gssize)search_idx,
|
|
(gssize)(search_len - replace_len));
|
|
memcpy(tmp, replace, replace_len);
|
|
} else if (search_len < replace_len) {
|
|
g_string_insert_len(string,
|
|
(gssize)search_idx,
|
|
replace,
|
|
(gssize)(replace_len - search_len));
|
|
/* we have to treat this specially as it could have
|
|
* been reallocated when the insertion happened */
|
|
memcpy(string->str + search_idx, replace, replace_len);
|
|
} else {
|
|
/* just memcmp in the new string */
|
|
memcpy(tmp, replace, replace_len);
|
|
}
|
|
search_idx += replace_len;
|
|
count++;
|
|
} while (TRUE);
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* fu_common_strwidth:
|
|
* @text: the string to operate on
|
|
*
|
|
* Returns the width of the string in displayed characters on the console.
|
|
*
|
|
* Returns: width of text
|
|
*
|
|
* Since: 1.3.2
|
|
**/
|
|
gsize
|
|
fu_common_strwidth(const gchar *text)
|
|
{
|
|
const gchar *p = text;
|
|
gsize width = 0;
|
|
|
|
g_return_val_if_fail(text != NULL, 0);
|
|
|
|
while (*p) {
|
|
gunichar c = g_utf8_get_char(p);
|
|
if (g_unichar_iswide(c))
|
|
width += 2;
|
|
else if (!g_unichar_iszerowidth(c))
|
|
width += 1;
|
|
p = g_utf8_next_char(p);
|
|
}
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* fu_common_string_append_kv:
|
|
* @str: a #GString
|
|
* @idt: the indent
|
|
* @key: a string to append
|
|
* @value: a string to append
|
|
*
|
|
* Appends a key and string value to a string
|
|
*
|
|
* Since: 1.2.4
|
|
*/
|
|
void
|
|
fu_common_string_append_kv(GString *str, guint idt, const gchar *key, const gchar *value)
|
|
{
|
|
const guint align = 24;
|
|
gsize keysz;
|
|
|
|
g_return_if_fail(idt * 2 < align);
|
|
|
|
/* ignore */
|
|
if (key == NULL)
|
|
return;
|
|
for (gsize i = 0; i < idt; i++)
|
|
g_string_append(str, " ");
|
|
if (key[0] != '\0') {
|
|
g_string_append_printf(str, "%s:", key);
|
|
keysz = (idt * 2) + fu_common_strwidth(key) + 1;
|
|
} else {
|
|
keysz = idt * 2;
|
|
}
|
|
if (value != NULL) {
|
|
g_auto(GStrv) split = NULL;
|
|
split = g_strsplit(value, "\n", -1);
|
|
for (guint i = 0; split[i] != NULL; i++) {
|
|
if (i == 0) {
|
|
for (gsize j = keysz; j < align; j++)
|
|
g_string_append(str, " ");
|
|
} else {
|
|
g_string_append(str, "\n");
|
|
for (gsize j = 0; j < idt; j++)
|
|
g_string_append(str, " ");
|
|
}
|
|
g_string_append(str, split[i]);
|
|
}
|
|
}
|
|
g_string_append(str, "\n");
|
|
}
|
|
|
|
/**
|
|
* fu_common_string_append_ku:
|
|
* @str: a #GString
|
|
* @idt: the indent
|
|
* @key: a string to append
|
|
* @value: guint64
|
|
*
|
|
* Appends a key and unsigned integer to a string
|
|
*
|
|
* Since: 1.2.4
|
|
*/
|
|
void
|
|
fu_common_string_append_ku(GString *str, guint idt, const gchar *key, guint64 value)
|
|
{
|
|
g_autofree gchar *tmp = g_strdup_printf("%" G_GUINT64_FORMAT, value);
|
|
fu_common_string_append_kv(str, idt, key, tmp);
|
|
}
|
|
|
|
/**
|
|
* fu_common_string_append_kx:
|
|
* @str: a #GString
|
|
* @idt: the indent
|
|
* @key: a string to append
|
|
* @value: guint64
|
|
*
|
|
* Appends a key and hex integer to a string
|
|
*
|
|
* Since: 1.2.4
|
|
*/
|
|
void
|
|
fu_common_string_append_kx(GString *str, guint idt, const gchar *key, guint64 value)
|
|
{
|
|
g_autofree gchar *tmp = g_strdup_printf("0x%x", (guint)value);
|
|
fu_common_string_append_kv(str, idt, key, tmp);
|
|
}
|
|
|
|
/**
|
|
* fu_common_string_append_kb:
|
|
* @str: a #GString
|
|
* @idt: the indent
|
|
* @key: a string to append
|
|
* @value: Boolean
|
|
*
|
|
* Appends a key and boolean value to a string
|
|
*
|
|
* Since: 1.2.4
|
|
*/
|
|
void
|
|
fu_common_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value)
|
|
{
|
|
fu_common_string_append_kv(str, idt, key, value ? "true" : "false");
|
|
}
|
|
|
|
/**
|
|
* fu_common_dump_full:
|
|
* @log_domain: (nullable): optional log domain, typically %G_LOG_DOMAIN
|
|
* @title: (nullable): optional prefix title
|
|
* @data: buffer to print
|
|
* @len: the size of @data
|
|
* @columns: break new lines after this many bytes
|
|
* @flags: dump flags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII
|
|
*
|
|
* Dumps a raw buffer to the screen.
|
|
*
|
|
* Since: 1.2.4
|
|
**/
|
|
void
|
|
fu_common_dump_full(const gchar *log_domain,
|
|
const gchar *title,
|
|
const guint8 *data,
|
|
gsize len,
|
|
guint columns,
|
|
FuDumpFlags flags)
|
|
{
|
|
g_autoptr(GString) str = g_string_new(NULL);
|
|
|
|
/* optional */
|
|
if (title != NULL)
|
|
g_string_append_printf(str, "%s:", title);
|
|
|
|
/* if more than can fit on one line then start afresh */
|
|
if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
|
|
g_string_append(str, "\n");
|
|
} else {
|
|
for (gsize i = str->len; i < 16; i++)
|
|
g_string_append(str, " ");
|
|
}
|
|
|
|
/* offset line */
|
|
if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) {
|
|
g_string_append(str, " │ ");
|
|
for (gsize i = 0; i < columns; i++)
|
|
g_string_append_printf(str, "%02x ", (guint)i);
|
|
g_string_append(str, "\n───────┼");
|
|
for (gsize i = 0; i < columns; i++)
|
|
g_string_append(str, "───");
|
|
g_string_append_printf(str, "\n0x%04x │ ", (guint)0);
|
|
}
|
|
|
|
/* print each row */
|
|
for (gsize i = 0; i < len; i++) {
|
|
g_string_append_printf(str, "%02x ", data[i]);
|
|
|
|
/* optionally print ASCII char */
|
|
if (flags & FU_DUMP_FLAGS_SHOW_ASCII) {
|
|
if (g_ascii_isprint(data[i]))
|
|
g_string_append_printf(str, "[%c] ", data[i]);
|
|
else
|
|
g_string_append(str, "[?] ");
|
|
}
|
|
|
|
/* new row required */
|
|
if (i > 0 && i != len - 1 && (i + 1) % columns == 0) {
|
|
g_string_append(str, "\n");
|
|
if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES)
|
|
g_string_append_printf(str, "0x%04x │ ", (guint)i + 1);
|
|
}
|
|
}
|
|
g_log(log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str);
|
|
}
|
|
|
|
/**
|
|
* fu_common_dump_raw:
|
|
* @log_domain: (nullable): optional log domain, typically %G_LOG_DOMAIN
|
|
* @title: (nullable): optional prefix title
|
|
* @data: buffer to print
|
|
* @len: the size of @data
|
|
*
|
|
* Dumps a raw buffer to the screen.
|
|
*
|
|
* Since: 1.2.2
|
|
**/
|
|
void
|
|
fu_common_dump_raw(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len)
|
|
{
|
|
FuDumpFlags flags = FU_DUMP_FLAGS_NONE;
|
|
if (len > 64)
|
|
flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES;
|
|
fu_common_dump_full(log_domain, title, data, len, 32, flags);
|
|
}
|
|
|
|
/**
|
|
* fu_common_dump_bytes:
|
|
* @log_domain: (nullable): optional log domain, typically %G_LOG_DOMAIN
|
|
* @title: (nullable): optional prefix title
|
|
* @bytes: data blob
|
|
*
|
|
* Dumps a byte buffer to the screen.
|
|
*
|
|
* Since: 1.2.2
|
|
**/
|
|
void
|
|
fu_common_dump_bytes(const gchar *log_domain, const gchar *title, GBytes *bytes)
|
|
{
|
|
gsize len = 0;
|
|
const guint8 *data = g_bytes_get_data(bytes, &len);
|
|
fu_common_dump_raw(log_domain, title, data, len);
|
|
}
|
|
|
|
/**
|
|
* fu_common_bytes_align:
|
|
* @bytes: data blob
|
|
* @blksz: block size in bytes
|
|
* @padval: the byte used to pad the byte buffer
|
|
*
|
|
* Aligns a block of memory to @blksize using the @padval value; if
|
|
* the block is already aligned then the original @bytes is returned.
|
|
*
|
|
* Returns: (transfer full): a #GBytes, possibly @bytes
|
|
*
|
|
* Since: 1.2.4
|
|
**/
|
|
GBytes *
|
|
fu_common_bytes_align(GBytes *bytes, gsize blksz, gchar padval)
|
|
{
|
|
const guint8 *data;
|
|
gsize sz;
|
|
|
|
g_return_val_if_fail(bytes != NULL, NULL);
|
|
g_return_val_if_fail(blksz > 0, NULL);
|
|
|
|
/* pad */
|
|
data = g_bytes_get_data(bytes, &sz);
|
|
if (sz % blksz != 0) {
|
|
gsize sz_align = ((sz / blksz) + 1) * blksz;
|
|
guint8 *data_align = g_malloc(sz_align);
|
|
memcpy(data_align, data, sz);
|
|
memset(data_align + sz, padval, sz_align - sz);
|
|
g_debug("aligning 0x%x bytes to 0x%x", (guint)sz, (guint)sz_align);
|
|
return g_bytes_new_take(data_align, sz_align);
|
|
}
|
|
|
|
/* perfectly aligned */
|
|
return g_bytes_ref(bytes);
|
|
}
|
|
|
|
/**
|
|
* fu_common_bytes_is_empty:
|
|
* @bytes: data blob
|
|
*
|
|
* Checks if a byte array are just empty (0xff) bytes.
|
|
*
|
|
* Returns: %TRUE if @bytes is empty
|
|
*
|
|
* Since: 1.2.6
|
|
**/
|
|
gboolean
|
|
fu_common_bytes_is_empty(GBytes *bytes)
|
|
{
|
|
gsize sz = 0;
|
|
const guint8 *buf = g_bytes_get_data(bytes, &sz);
|
|
for (gsize i = 0; i < sz; i++) {
|
|
if (buf[i] != 0xff)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_bytes_compare_raw:
|
|
* @buf1: a buffer
|
|
* @bufsz1: sizeof @buf1
|
|
* @buf2: another buffer
|
|
* @bufsz2: sizeof @buf2
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Compares the buffers for equality.
|
|
*
|
|
* Returns: %TRUE if @buf1 and @buf2 are identical
|
|
*
|
|
* Since: 1.3.2
|
|
**/
|
|
gboolean
|
|
fu_common_bytes_compare_raw(const guint8 *buf1,
|
|
gsize bufsz1,
|
|
const guint8 *buf2,
|
|
gsize bufsz2,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail(buf1 != NULL, FALSE);
|
|
g_return_val_if_fail(buf2 != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* not the same length */
|
|
if (bufsz1 != bufsz2) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"got %" G_GSIZE_FORMAT " bytes, expected "
|
|
"%" G_GSIZE_FORMAT,
|
|
bufsz1,
|
|
bufsz2);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check matches */
|
|
for (guint i = 0x0; i < bufsz1; i++) {
|
|
if (buf1[i] != buf2[i]) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"got 0x%02x, expected 0x%02x @ 0x%04x",
|
|
buf1[i],
|
|
buf2[i],
|
|
i);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_bytes_compare:
|
|
* @bytes1: a data blob
|
|
* @bytes2: another #GBytes
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Compares the buffers for equality.
|
|
*
|
|
* Returns: %TRUE if @bytes1 and @bytes2 are identical
|
|
*
|
|
* Since: 1.2.6
|
|
**/
|
|
gboolean
|
|
fu_common_bytes_compare(GBytes *bytes1, GBytes *bytes2, GError **error)
|
|
{
|
|
const guint8 *buf1;
|
|
const guint8 *buf2;
|
|
gsize bufsz1;
|
|
gsize bufsz2;
|
|
|
|
g_return_val_if_fail(bytes1 != NULL, FALSE);
|
|
g_return_val_if_fail(bytes2 != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
buf1 = g_bytes_get_data(bytes1, &bufsz1);
|
|
buf2 = g_bytes_get_data(bytes2, &bufsz2);
|
|
return fu_common_bytes_compare_raw(buf1, bufsz1, buf2, bufsz2, error);
|
|
}
|
|
|
|
/**
|
|
* fu_common_bytes_pad:
|
|
* @bytes: data blob
|
|
* @sz: the desired size in bytes
|
|
*
|
|
* Pads a GBytes to a minimum @sz with `0xff`.
|
|
*
|
|
* Returns: (transfer full): a data blob
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
GBytes *
|
|
fu_common_bytes_pad(GBytes *bytes, gsize sz)
|
|
{
|
|
gsize bytes_sz;
|
|
|
|
g_return_val_if_fail(bytes != NULL, NULL);
|
|
g_return_val_if_fail(sz != 0, NULL);
|
|
|
|
/* pad */
|
|
bytes_sz = g_bytes_get_size(bytes);
|
|
if (bytes_sz < sz) {
|
|
const guint8 *data = g_bytes_get_data(bytes, NULL);
|
|
guint8 *data_new = g_malloc(sz);
|
|
memcpy(data_new, data, bytes_sz);
|
|
memset(data_new + bytes_sz, 0xff, sz - bytes_sz);
|
|
return g_bytes_new_take(data_new, sz);
|
|
}
|
|
|
|
/* not required */
|
|
return g_bytes_ref(bytes);
|
|
}
|
|
|
|
/**
|
|
* fu_common_bytes_new_offset:
|
|
* @bytes: data blob
|
|
* @offset: where subsection starts at
|
|
* @length: length of subsection
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Creates a #GBytes which is a subsection of another #GBytes.
|
|
*
|
|
* Returns: (transfer full): a #GBytes, or #NULL if range is invalid
|
|
*
|
|
* Since: 1.5.4
|
|
**/
|
|
GBytes *
|
|
fu_common_bytes_new_offset(GBytes *bytes, gsize offset, gsize length, GError **error)
|
|
{
|
|
g_return_val_if_fail(bytes != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* sanity check */
|
|
if (offset + length > g_bytes_get_size(bytes)) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"cannot create bytes @0x%02x for 0x%02x "
|
|
"as buffer only 0x%04x bytes in size",
|
|
(guint)offset,
|
|
(guint)length,
|
|
(guint)g_bytes_get_size(bytes));
|
|
return NULL;
|
|
}
|
|
return g_bytes_new_from_bytes(bytes, offset, length);
|
|
}
|
|
|
|
/**
|
|
* fu_common_realpath:
|
|
* @filename: a filename
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Finds the canonicalized absolute filename for a path.
|
|
*
|
|
* Returns: a filename, or %NULL if invalid or not found
|
|
*
|
|
* Since: 1.2.6
|
|
**/
|
|
gchar *
|
|
fu_common_realpath(const gchar *filename, GError **error)
|
|
{
|
|
char full_tmp[PATH_MAX];
|
|
|
|
g_return_val_if_fail(filename != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
#ifdef HAVE_REALPATH
|
|
if (realpath(filename, full_tmp) == NULL) {
|
|
#else
|
|
if (_fullpath(full_tmp, filename, sizeof(full_tmp)) == NULL) {
|
|
#endif
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"cannot resolve path: %s",
|
|
strerror(errno));
|
|
return NULL;
|
|
}
|
|
if (!g_file_test(full_tmp, G_FILE_TEST_EXISTS)) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"cannot find path: %s",
|
|
full_tmp);
|
|
return NULL;
|
|
}
|
|
return g_strdup(full_tmp);
|
|
}
|
|
|
|
/**
|
|
* fu_common_fnmatch:
|
|
* @pattern: a glob pattern, e.g. `*foo*`
|
|
* @str: a string to match against the pattern, e.g. `bazfoobar`
|
|
*
|
|
* Matches a string against a glob pattern.
|
|
*
|
|
* Returns: %TRUE if the string matched
|
|
*
|
|
* Since: 1.3.5
|
|
**/
|
|
gboolean
|
|
fu_common_fnmatch(const gchar *pattern, const gchar *str)
|
|
{
|
|
g_return_val_if_fail(pattern != NULL, FALSE);
|
|
g_return_val_if_fail(str != NULL, FALSE);
|
|
return fu_common_fnmatch_impl(pattern, str);
|
|
}
|
|
|
|
static gint
|
|
fu_common_filename_glob_sort_cb(gconstpointer a, gconstpointer b)
|
|
{
|
|
return g_strcmp0(*(const gchar **)a, *(const gchar **)b);
|
|
}
|
|
|
|
/**
|
|
* fu_common_filename_glob:
|
|
* @directory: a directory path
|
|
* @pattern: a glob pattern, e.g. `*foo*`
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Returns all the filenames that match a specific glob pattern.
|
|
* Any results are sorted. No matching files will set @error.
|
|
*
|
|
* Returns: (element-type utf8) (transfer container): matching files, or %NULL
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
GPtrArray *
|
|
fu_common_filename_glob(const gchar *directory, const gchar *pattern, GError **error)
|
|
{
|
|
const gchar *basename;
|
|
g_autoptr(GDir) dir = NULL;
|
|
g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free);
|
|
|
|
g_return_val_if_fail(directory != NULL, NULL);
|
|
g_return_val_if_fail(pattern != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
dir = g_dir_open(directory, 0, error);
|
|
if (dir == NULL)
|
|
return NULL;
|
|
while ((basename = g_dir_read_name(dir)) != NULL) {
|
|
if (!fu_common_fnmatch(pattern, basename))
|
|
continue;
|
|
g_ptr_array_add(files, g_build_filename(directory, basename, NULL));
|
|
}
|
|
if (files->len == 0) {
|
|
g_set_error_literal(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"no files matched pattern");
|
|
return NULL;
|
|
}
|
|
g_ptr_array_sort(files, fu_common_filename_glob_sort_cb);
|
|
return g_steal_pointer(&files);
|
|
}
|
|
|
|
/**
|
|
* fu_common_strnsplit:
|
|
* @str: a string to split
|
|
* @sz: size of @str
|
|
* @delimiter: a string which specifies the places at which to split the string
|
|
* @max_tokens: the maximum number of pieces to split @str into
|
|
*
|
|
* Splits a string into a maximum of @max_tokens pieces, using the given
|
|
* delimiter. If @max_tokens is reached, the remainder of string is appended
|
|
* to the last token.
|
|
*
|
|
* Returns: (transfer full): a newly-allocated NULL-terminated array of strings
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
gchar **
|
|
fu_common_strnsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens)
|
|
{
|
|
if (str[sz - 1] != '\0') {
|
|
g_autofree gchar *str2 = g_strndup(str, sz);
|
|
return g_strsplit(str2, delimiter, max_tokens);
|
|
}
|
|
return g_strsplit(str, delimiter, max_tokens);
|
|
}
|
|
|
|
/**
|
|
* fu_common_strnsplit_full:
|
|
* @str: a string to split
|
|
* @sz: size of @str, or -1 for unknown
|
|
* @delimiter: a string which specifies the places at which to split the string
|
|
* @callback: (scope call): a #FuCommonStrsplitFunc.
|
|
* @user_data: user data
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Splits the string, calling the given function for each
|
|
* of the tokens found. If any @callback returns %FALSE scanning is aborted.
|
|
*
|
|
* Use this function in preference to fu_common_strnsplit() when the input file is untrusted,
|
|
* and you don't want to allocate a GStrv with billions of one byte items.
|
|
*
|
|
* Returns: %TRUE if no @callback returned FALSE
|
|
*
|
|
* Since: 1.7.0
|
|
*/
|
|
gboolean
|
|
fu_common_strnsplit_full(const gchar *str,
|
|
gssize sz,
|
|
const gchar *delimiter,
|
|
FuCommonStrsplitFunc callback,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
gsize delimiter_sz;
|
|
gsize str_sz;
|
|
guint found_idx = 0;
|
|
guint token_idx = 0;
|
|
|
|
g_return_val_if_fail(str != NULL, FALSE);
|
|
g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE);
|
|
g_return_val_if_fail(callback != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* make known */
|
|
str_sz = sz != -1 ? (gsize)sz : strlen(str);
|
|
delimiter_sz = strlen(delimiter);
|
|
|
|
/* cannot split */
|
|
if (delimiter_sz > str_sz) {
|
|
g_autoptr(GString) token = g_string_new(str);
|
|
return callback(token, token_idx, user_data, error);
|
|
}
|
|
|
|
/* start splittin' */
|
|
for (gsize i = 0; i < (str_sz - delimiter_sz) + 1;) {
|
|
if (strncmp(str + i, delimiter, delimiter_sz) == 0) {
|
|
g_autoptr(GString) token = g_string_new(NULL);
|
|
g_string_append_len(token, str + found_idx, i - found_idx);
|
|
if (!callback(token, token_idx++, user_data, error))
|
|
return FALSE;
|
|
i += delimiter_sz;
|
|
found_idx = i;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* any bits left over? */
|
|
if (found_idx != str_sz) {
|
|
g_autoptr(GString) token = g_string_new(NULL);
|
|
g_string_append_len(token, str + found_idx, str_sz - found_idx);
|
|
if (!callback(token, token_idx, user_data, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_strsafe:
|
|
* @str: (nullable): a string to make safe for printing
|
|
* @maxsz: maximum size of returned string
|
|
*
|
|
* Converts a string into something that can be safely printed.
|
|
*
|
|
* Returns: (transfer full): safe string, or %NULL if there was nothing valid
|
|
*
|
|
* Since: 1.5.5
|
|
**/
|
|
gchar *
|
|
fu_common_strsafe(const gchar *str, gsize maxsz)
|
|
{
|
|
gboolean valid = FALSE;
|
|
g_autoptr(GString) tmp = NULL;
|
|
|
|
/* sanity check */
|
|
if (str == NULL || maxsz == 0)
|
|
return NULL;
|
|
|
|
/* replace non-printable chars with '.' */
|
|
tmp = g_string_sized_new(maxsz);
|
|
for (gsize i = 0; i < maxsz && str[i] != '\0'; i++) {
|
|
if (!g_ascii_isprint(str[i])) {
|
|
g_string_append_c(tmp, '.');
|
|
continue;
|
|
}
|
|
g_string_append_c(tmp, str[i]);
|
|
if (!g_ascii_isspace(str[i]))
|
|
valid = TRUE;
|
|
}
|
|
|
|
/* if just junk, don't return 'all dots' */
|
|
if (tmp->len == 0 || !valid)
|
|
return NULL;
|
|
return g_string_free(g_steal_pointer(&tmp), FALSE);
|
|
}
|
|
|
|
/**
|
|
* fu_common_strjoin_array:
|
|
* @separator: (nullable): string to insert between each of the strings
|
|
* @array: (element-type utf8): a #GPtrArray
|
|
*
|
|
* Joins an array of strings together to form one long string, with the optional
|
|
* separator inserted between each of them.
|
|
*
|
|
* If @array has no items, the return value will be an empty string.
|
|
* If @array contains a single item, separator will not appear in the resulting
|
|
* string.
|
|
*
|
|
* Returns: a string
|
|
*
|
|
* Since: 1.5.6
|
|
**/
|
|
gchar *
|
|
fu_common_strjoin_array(const gchar *separator, GPtrArray *array)
|
|
{
|
|
g_autofree const gchar **strv = NULL;
|
|
|
|
g_return_val_if_fail(array != NULL, NULL);
|
|
|
|
strv = g_new0(const gchar *, array->len + 1);
|
|
for (guint i = 0; i < array->len; i++)
|
|
strv[i] = g_ptr_array_index(array, i);
|
|
return g_strjoinv(separator, (gchar **)strv);
|
|
}
|
|
|
|
/**
|
|
* fu_memcpy_safe:
|
|
* @dst: destination buffer
|
|
* @dst_sz: maximum size of @dst, typically `sizeof(dst)`
|
|
* @dst_offset: offset in bytes into @dst to copy to
|
|
* @src: source buffer
|
|
* @src_sz: maximum size of @dst, typically `sizeof(src)`
|
|
* @src_offset: offset in bytes into @src to copy from
|
|
* @n: number of bytes to copy from @src+@offset from
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Copies some memory using memcpy in a safe way. Providing the buffer sizes
|
|
* of both the destination and the source allows us to check for buffer overflow.
|
|
*
|
|
* Providing the buffer offsets also allows us to check reading past the end of
|
|
* the source buffer. For this reason the caller should NEVER add an offset to
|
|
* @src or @dst.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if the bytes were copied, %FALSE otherwise
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
gboolean
|
|
fu_memcpy_safe(guint8 *dst,
|
|
gsize dst_sz,
|
|
gsize dst_offset,
|
|
const guint8 *src,
|
|
gsize src_sz,
|
|
gsize src_offset,
|
|
gsize n,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail(dst != NULL, FALSE);
|
|
g_return_val_if_fail(src != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
if (n == 0)
|
|
return TRUE;
|
|
|
|
if (n > src_sz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"attempted to read 0x%02x bytes from buffer of 0x%02x",
|
|
(guint)n,
|
|
(guint)src_sz);
|
|
return FALSE;
|
|
}
|
|
if (n + src_offset > src_sz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"attempted to read 0x%02x bytes at offset 0x%02x from buffer of 0x%02x",
|
|
(guint)n,
|
|
(guint)src_offset,
|
|
(guint)src_sz);
|
|
return FALSE;
|
|
}
|
|
if (n > dst_sz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"attempted to write 0x%02x bytes to buffer of 0x%02x",
|
|
(guint)n,
|
|
(guint)dst_sz);
|
|
return FALSE;
|
|
}
|
|
if (n + dst_offset > dst_sz) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"attempted to write 0x%02x bytes at offset 0x%02x to buffer of 0x%02x",
|
|
(guint)n,
|
|
(guint)dst_offset,
|
|
(guint)dst_sz);
|
|
return FALSE;
|
|
}
|
|
|
|
/* phew! */
|
|
memcpy(dst + dst_offset, src + src_offset, n);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_memdup_safe:
|
|
* @src: source buffer
|
|
* @n: number of bytes to copy from @src
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Duplicates some memory using memdup in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* NOTE: This function intentionally limits allocation size to 1GB.
|
|
*
|
|
* Returns: (transfer full): block of allocated memory, or %NULL for an error.
|
|
*
|
|
* Since: 1.5.6
|
|
**/
|
|
guint8 *
|
|
fu_memdup_safe(const guint8 *src, gsize n, GError **error)
|
|
{
|
|
/* sanity check */
|
|
if (n > 0x40000000) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"cannot allocate %uGB of memory",
|
|
(guint)(n / 0x40000000));
|
|
return NULL;
|
|
}
|
|
|
|
#if GLIB_CHECK_VERSION(2, 67, 3)
|
|
/* linear block of memory */
|
|
return g_memdup2(src, n);
|
|
#else
|
|
return g_memdup(src, (guint)n);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint8_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to copy from
|
|
* @value: (out) (nullable): the parsed value
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Read a value from a buffer in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was set, %FALSE otherwise
|
|
*
|
|
* Since: 1.3.3
|
|
**/
|
|
gboolean
|
|
fu_common_read_uint8_safe(const guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint8 *value,
|
|
GError **error)
|
|
{
|
|
guint8 tmp;
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!fu_memcpy_safe(&tmp,
|
|
sizeof(tmp),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset, /* src */
|
|
sizeof(tmp),
|
|
error))
|
|
return FALSE;
|
|
if (value != NULL)
|
|
*value = tmp;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint16_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to copy from
|
|
* @value: (out) (nullable): the parsed value
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Read a value from a buffer using a specified endian in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was set, %FALSE otherwise
|
|
*
|
|
* Since: 1.3.3
|
|
**/
|
|
gboolean
|
|
fu_common_read_uint16_safe(const guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint16 *value,
|
|
FuEndianType endian,
|
|
GError **error)
|
|
{
|
|
guint8 dst[2] = {0x0};
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!fu_memcpy_safe(dst,
|
|
sizeof(dst),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset, /* src */
|
|
sizeof(dst),
|
|
error))
|
|
return FALSE;
|
|
if (value != NULL)
|
|
*value = fu_common_read_uint16(dst, endian);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint32_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to copy from
|
|
* @value: (out) (nullable): the parsed value
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Read a value from a buffer using a specified endian in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was set, %FALSE otherwise
|
|
*
|
|
* Since: 1.3.3
|
|
**/
|
|
gboolean
|
|
fu_common_read_uint32_safe(const guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint32 *value,
|
|
FuEndianType endian,
|
|
GError **error)
|
|
{
|
|
guint8 dst[4] = {0x0};
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!fu_memcpy_safe(dst,
|
|
sizeof(dst),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset, /* src */
|
|
sizeof(dst),
|
|
error))
|
|
return FALSE;
|
|
if (value != NULL)
|
|
*value = fu_common_read_uint32(dst, endian);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_read_uint64_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to copy from
|
|
* @value: (out) (nullable): the parsed value
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Read a value from a buffer using a specified endian in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was set, %FALSE otherwise
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
gboolean
|
|
fu_common_read_uint64_safe(const guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint64 *value,
|
|
FuEndianType endian,
|
|
GError **error)
|
|
{
|
|
guint8 dst[8] = {0x0};
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
if (!fu_memcpy_safe(dst,
|
|
sizeof(dst),
|
|
0x0, /* dst */
|
|
buf,
|
|
bufsz,
|
|
offset, /* src */
|
|
sizeof(dst),
|
|
error))
|
|
return FALSE;
|
|
if (value != NULL)
|
|
*value = fu_common_read_uint64(dst, endian);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint8_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to write to
|
|
* @value: the value to write
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Write a value to a buffer in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was written, %FALSE otherwise
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
gboolean
|
|
fu_common_write_uint8_safe(guint8 *buf, gsize bufsz, gsize offset, guint8 value, GError **error)
|
|
{
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
return fu_memcpy_safe(buf,
|
|
bufsz,
|
|
offset, /* dst */
|
|
&value,
|
|
sizeof(value),
|
|
0x0, /* src */
|
|
sizeof(value),
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint16_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to write to
|
|
* @value: the value to write
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Write a value to a buffer using a specified endian in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was written, %FALSE otherwise
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
gboolean
|
|
fu_common_write_uint16_safe(guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint16 value,
|
|
FuEndianType endian,
|
|
GError **error)
|
|
{
|
|
guint8 tmp[2] = {0x0};
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
fu_common_write_uint16(tmp, value, endian);
|
|
return fu_memcpy_safe(buf,
|
|
bufsz,
|
|
offset, /* dst */
|
|
tmp,
|
|
sizeof(tmp),
|
|
0x0, /* src */
|
|
sizeof(tmp),
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint32_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to write to
|
|
* @value: the value to write
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Write a value to a buffer using a specified endian in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was written, %FALSE otherwise
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
gboolean
|
|
fu_common_write_uint32_safe(guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint32 value,
|
|
FuEndianType endian,
|
|
GError **error)
|
|
{
|
|
guint8 tmp[4] = {0x0};
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
fu_common_write_uint32(tmp, value, endian);
|
|
return fu_memcpy_safe(buf,
|
|
bufsz,
|
|
offset, /* dst */
|
|
tmp,
|
|
sizeof(tmp),
|
|
0x0, /* src */
|
|
sizeof(tmp),
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* fu_common_write_uint64_safe:
|
|
* @buf: source buffer
|
|
* @bufsz: maximum size of @buf, typically `sizeof(buf)`
|
|
* @offset: offset in bytes into @buf to write to
|
|
* @value: the value to write
|
|
* @endian: an endian type, e.g. %G_LITTLE_ENDIAN
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Write a value to a buffer using a specified endian in a safe way.
|
|
*
|
|
* You don't need to use this function in "obviously correct" cases, nor should
|
|
* you use it when performance is a concern. Only us it when you're not sure if
|
|
* malicious data from a device or firmware could cause memory corruption.
|
|
*
|
|
* Returns: %TRUE if @value was written, %FALSE otherwise
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
gboolean
|
|
fu_common_write_uint64_safe(guint8 *buf,
|
|
gsize bufsz,
|
|
gsize offset,
|
|
guint64 value,
|
|
FuEndianType endian,
|
|
GError **error)
|
|
{
|
|
guint8 tmp[8] = {0x0};
|
|
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
fu_common_write_uint64(tmp, value, endian);
|
|
return fu_memcpy_safe(buf,
|
|
bufsz,
|
|
offset, /* dst */
|
|
tmp,
|
|
sizeof(tmp),
|
|
0x0, /* src */
|
|
sizeof(tmp),
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_append_uint8:
|
|
* @array: a #GByteArray
|
|
* @data: value
|
|
*
|
|
* Adds a 8 bit integer to a byte array.
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_byte_array_append_uint8(GByteArray *array, guint8 data)
|
|
{
|
|
g_byte_array_append(array, &data, sizeof(data));
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_append_uint16:
|
|
* @array: a #GByteArray
|
|
* @data: value
|
|
* @endian: endian type, e.g. #G_LITTLE_ENDIAN
|
|
*
|
|
* Adds a 16 bit integer to a byte array.
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_byte_array_append_uint16(GByteArray *array, guint16 data, FuEndianType endian)
|
|
{
|
|
guint8 buf[2];
|
|
fu_common_write_uint16(buf, data, endian);
|
|
g_byte_array_append(array, buf, sizeof(buf));
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_append_uint32:
|
|
* @array: a #GByteArray
|
|
* @data: value
|
|
* @endian: endian type, e.g. #G_LITTLE_ENDIAN
|
|
*
|
|
* Adds a 32 bit integer to a byte array.
|
|
*
|
|
* Since: 1.3.1
|
|
**/
|
|
void
|
|
fu_byte_array_append_uint32(GByteArray *array, guint32 data, FuEndianType endian)
|
|
{
|
|
guint8 buf[4];
|
|
fu_common_write_uint32(buf, data, endian);
|
|
g_byte_array_append(array, buf, sizeof(buf));
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_append_uint64:
|
|
* @array: a #GByteArray
|
|
* @data: value
|
|
* @endian: endian type, e.g. #G_LITTLE_ENDIAN
|
|
*
|
|
* Adds a 64 bit integer to a byte array.
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
void
|
|
fu_byte_array_append_uint64(GByteArray *array, guint64 data, FuEndianType endian)
|
|
{
|
|
guint8 buf[8];
|
|
fu_common_write_uint64(buf, data, endian);
|
|
g_byte_array_append(array, buf, sizeof(buf));
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_append_bytes:
|
|
* @array: a #GByteArray
|
|
* @bytes: data blob
|
|
*
|
|
* Adds the contents of a GBytes to a byte array.
|
|
*
|
|
* Since: 1.5.8
|
|
**/
|
|
void
|
|
fu_byte_array_append_bytes(GByteArray *array, GBytes *bytes)
|
|
{
|
|
g_byte_array_append(array, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes));
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_set_size_full:
|
|
* @array: a #GByteArray
|
|
* @length: the new size of the GByteArray
|
|
* @data: the byte used to pad the array
|
|
*
|
|
* Sets the size of the GByteArray, expanding with @data as required.
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
void
|
|
fu_byte_array_set_size_full(GByteArray *array, guint length, guint8 data)
|
|
{
|
|
guint oldlength = array->len;
|
|
g_byte_array_set_size(array, length);
|
|
if (length > oldlength)
|
|
memset(array->data + oldlength, data, length - oldlength);
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_set_size:
|
|
* @array: a #GByteArray
|
|
* @length: the new size of the GByteArray
|
|
*
|
|
* Sets the size of the GByteArray, expanding it with NULs if necessary.
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
void
|
|
fu_byte_array_set_size(GByteArray *array, guint length)
|
|
{
|
|
return fu_byte_array_set_size_full(array, length, 0x0);
|
|
}
|
|
|
|
/**
|
|
* fu_byte_array_align_up:
|
|
* @array: a #GByteArray
|
|
* @alignment: align to this power of 2
|
|
* @data: the byte used to pad the array
|
|
*
|
|
* Align a byte array length to a power of 2 boundary, where @alignment is the
|
|
* bit position to align to. If @alignment is zero then @array is unchanged.
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
void
|
|
fu_byte_array_align_up(GByteArray *array, guint8 alignment, guint8 data)
|
|
{
|
|
fu_byte_array_set_size_full(array, fu_common_align_up(array->len, alignment), data);
|
|
}
|
|
|
|
/**
|
|
* fu_common_kernel_locked_down:
|
|
*
|
|
* Determines if kernel lockdown in effect
|
|
*
|
|
* Since: 1.3.8
|
|
**/
|
|
gboolean
|
|
fu_common_kernel_locked_down(void)
|
|
{
|
|
#ifdef __linux__
|
|
gsize len = 0;
|
|
g_autofree gchar *dir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_SECURITY);
|
|
g_autofree gchar *fname = g_build_filename(dir, "lockdown", NULL);
|
|
g_autofree gchar *data = NULL;
|
|
g_auto(GStrv) options = NULL;
|
|
|
|
if (!g_file_test(fname, G_FILE_TEST_EXISTS))
|
|
return FALSE;
|
|
if (!g_file_get_contents(fname, &data, &len, NULL))
|
|
return FALSE;
|
|
if (len < 1)
|
|
return FALSE;
|
|
options = g_strsplit(data, " ", -1);
|
|
for (guint i = 0; options[i] != NULL; i++) {
|
|
if (g_strcmp0(options[i], "[none]") == 0)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
#else
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* fu_common_check_kernel_version :
|
|
* @minimum_kernel: (not nullable): The minimum kernel version to check against
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Determines if the system is running at least a certain required kernel version
|
|
*
|
|
* Since: 1.6.2
|
|
**/
|
|
gboolean
|
|
fu_common_check_kernel_version(const gchar *minimum_kernel, GError **error)
|
|
{
|
|
#ifdef HAVE_UTSNAME_H
|
|
struct utsname name_tmp;
|
|
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
g_return_val_if_fail(minimum_kernel != NULL, FALSE);
|
|
|
|
memset(&name_tmp, 0, sizeof(struct utsname));
|
|
if (uname(&name_tmp) < 0) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to read kernel version");
|
|
return FALSE;
|
|
}
|
|
if (fu_common_vercmp_full(name_tmp.release, minimum_kernel, FWUPD_VERSION_FORMAT_TRIPLET) <
|
|
0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"kernel %s doesn't meet minimum %s",
|
|
name_tmp.release,
|
|
minimum_kernel);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
#else
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"platform doesn't support checking for minimum Linux kernel");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* fu_common_cpuid:
|
|
* @leaf: the CPUID level, now called the 'leaf' by Intel
|
|
* @eax: (out) (nullable): EAX register
|
|
* @ebx: (out) (nullable): EBX register
|
|
* @ecx: (out) (nullable): ECX register
|
|
* @edx: (out) (nullable): EDX register
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Calls CPUID and returns the registers for the given leaf.
|
|
*
|
|
* Returns: %TRUE if the registers are set.
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
gboolean
|
|
fu_common_cpuid(guint32 leaf,
|
|
guint32 *eax,
|
|
guint32 *ebx,
|
|
guint32 *ecx,
|
|
guint32 *edx,
|
|
GError **error)
|
|
{
|
|
#ifdef HAVE_CPUID_H
|
|
guint eax_tmp = 0;
|
|
guint ebx_tmp = 0;
|
|
guint ecx_tmp = 0;
|
|
guint edx_tmp = 0;
|
|
|
|
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
|
|
|
|
/* get vendor */
|
|
__get_cpuid_count(leaf, 0x0, &eax_tmp, &ebx_tmp, &ecx_tmp, &edx_tmp);
|
|
if (eax != NULL)
|
|
*eax = eax_tmp;
|
|
if (ebx != NULL)
|
|
*ebx = ebx_tmp;
|
|
if (ecx != NULL)
|
|
*ecx = ecx_tmp;
|
|
if (edx != NULL)
|
|
*edx = edx_tmp;
|
|
return TRUE;
|
|
#else
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no <cpuid.h> support");
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_cpu_vendor:
|
|
*
|
|
* Uses CPUID to discover the CPU vendor.
|
|
*
|
|
* Returns: a CPU vendor, e.g. %FU_CPU_VENDOR_AMD if the vendor was AMD.
|
|
*
|
|
* Since: 1.5.5
|
|
**/
|
|
FuCpuVendor
|
|
fu_common_get_cpu_vendor(void)
|
|
{
|
|
#ifdef HAVE_CPUID_H
|
|
guint ebx = 0;
|
|
guint ecx = 0;
|
|
guint edx = 0;
|
|
|
|
if (fu_common_cpuid(0x0, NULL, &ebx, &ecx, &edx, NULL)) {
|
|
if (ebx == signature_INTEL_ebx && edx == signature_INTEL_edx &&
|
|
ecx == signature_INTEL_ecx) {
|
|
return FU_CPU_VENDOR_INTEL;
|
|
}
|
|
if (ebx == signature_AMD_ebx && edx == signature_AMD_edx &&
|
|
ecx == signature_AMD_ecx) {
|
|
return FU_CPU_VENDOR_AMD;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* failed */
|
|
return FU_CPU_VENDOR_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* fu_common_is_live_media:
|
|
*
|
|
* Checks if the user is running from a live media using various heuristics.
|
|
*
|
|
* Returns: %TRUE if live
|
|
*
|
|
* Since: 1.4.6
|
|
**/
|
|
gboolean
|
|
fu_common_is_live_media(void)
|
|
{
|
|
gsize bufsz = 0;
|
|
g_autofree gchar *buf = NULL;
|
|
g_auto(GStrv) tokens = NULL;
|
|
const gchar *args[] = {
|
|
"rd.live.image",
|
|
"boot=live",
|
|
NULL, /* last entry */
|
|
};
|
|
if (g_file_test("/cdrom/.disk/info", G_FILE_TEST_EXISTS))
|
|
return TRUE;
|
|
if (!g_file_get_contents("/proc/cmdline", &buf, &bufsz, NULL))
|
|
return FALSE;
|
|
if (bufsz == 0)
|
|
return FALSE;
|
|
tokens = fu_common_strnsplit(buf, bufsz - 1, " ", -1);
|
|
for (guint i = 0; args[i] != NULL; i++) {
|
|
if (g_strv_contains((const gchar *const *)tokens, args[i]))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_memory_size:
|
|
*
|
|
* Returns the size of physical memory.
|
|
*
|
|
* Returns: bytes
|
|
*
|
|
* Since: 1.5.6
|
|
**/
|
|
guint64
|
|
fu_common_get_memory_size(void)
|
|
{
|
|
return fu_common_get_memory_size_impl();
|
|
}
|
|
|
|
const gchar *
|
|
fu_common_convert_to_gpt_type(const gchar *type)
|
|
{
|
|
struct {
|
|
const gchar *gpt;
|
|
const gchar *mbrs[4];
|
|
} typeguids[] = {{"c12a7328-f81f-11d2-ba4b-00a0c93ec93b", /* esp */
|
|
{"0xef", "efi", NULL}},
|
|
{"ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", /* fat32 */
|
|
{"0x0b", "fat32", "fat32lba", NULL}},
|
|
{NULL, {NULL}}};
|
|
for (guint i = 0; typeguids[i].gpt != NULL; i++) {
|
|
for (guint j = 0; typeguids[i].mbrs[j] != NULL; j++) {
|
|
if (g_strcmp0(type, typeguids[i].mbrs[j]) == 0)
|
|
return typeguids[i].gpt;
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_volumes_by_kind:
|
|
* @kind: a volume kind, typically a GUID
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Finds all volumes of a specific partition type
|
|
*
|
|
* Returns: (transfer container) (element-type FuVolume): a #GPtrArray, or %NULL if the kind was not
|
|
*found
|
|
*
|
|
* Since: 1.4.6
|
|
**/
|
|
GPtrArray *
|
|
fu_common_get_volumes_by_kind(const gchar *kind, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
g_autoptr(GPtrArray) volumes = NULL;
|
|
|
|
g_return_val_if_fail(kind != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
devices = fu_common_get_block_devices(error);
|
|
if (devices == NULL)
|
|
return NULL;
|
|
volumes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
GDBusProxy *proxy_blk = g_ptr_array_index(devices, i);
|
|
const gchar *type_str;
|
|
g_autoptr(FuVolume) vol = NULL;
|
|
g_autoptr(GDBusProxy) proxy_part = NULL;
|
|
g_autoptr(GDBusProxy) proxy_fs = NULL;
|
|
g_autoptr(GVariant) val = NULL;
|
|
|
|
proxy_part = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk),
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
NULL,
|
|
UDISKS_DBUS_SERVICE,
|
|
g_dbus_proxy_get_object_path(proxy_blk),
|
|
UDISKS_DBUS_INTERFACE_PARTITION,
|
|
NULL,
|
|
error);
|
|
if (proxy_part == NULL) {
|
|
g_prefix_error(error,
|
|
"failed to initialize d-bus proxy %s: ",
|
|
g_dbus_proxy_get_object_path(proxy_blk));
|
|
return NULL;
|
|
}
|
|
val = g_dbus_proxy_get_cached_property(proxy_part, "Type");
|
|
if (val == NULL)
|
|
continue;
|
|
|
|
g_variant_get(val, "&s", &type_str);
|
|
proxy_fs = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk),
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
NULL,
|
|
UDISKS_DBUS_SERVICE,
|
|
g_dbus_proxy_get_object_path(proxy_blk),
|
|
UDISKS_DBUS_INTERFACE_FILESYSTEM,
|
|
NULL,
|
|
error);
|
|
if (proxy_fs == NULL) {
|
|
g_prefix_error(error,
|
|
"failed to initialize d-bus proxy %s: ",
|
|
g_dbus_proxy_get_object_path(proxy_blk));
|
|
return NULL;
|
|
}
|
|
vol = g_object_new(FU_TYPE_VOLUME,
|
|
"proxy-block",
|
|
proxy_blk,
|
|
"proxy-filesystem",
|
|
proxy_fs,
|
|
NULL);
|
|
|
|
/* convert reported type to GPT type */
|
|
type_str = fu_common_convert_to_gpt_type(type_str);
|
|
g_debug("device %s, type: %s, internal: %d, fs: %s",
|
|
g_dbus_proxy_get_object_path(proxy_blk),
|
|
type_str,
|
|
fu_volume_is_internal(vol),
|
|
fu_volume_get_id_type(vol));
|
|
if (g_strcmp0(type_str, kind) != 0)
|
|
continue;
|
|
g_ptr_array_add(volumes, g_steal_pointer(&vol));
|
|
}
|
|
if (volumes->len == 0) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes of type %s", kind);
|
|
return NULL;
|
|
}
|
|
return g_steal_pointer(&volumes);
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_volume_by_device:
|
|
* @device: a device string, typically starting with `/dev/`
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Finds the first volume from the specified device.
|
|
*
|
|
* Returns: (transfer full): a volume, or %NULL if the device was not found
|
|
*
|
|
* Since: 1.5.1
|
|
**/
|
|
FuVolume *
|
|
fu_common_get_volume_by_device(const gchar *device, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
|
|
g_return_val_if_fail(device != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* find matching block device */
|
|
devices = fu_common_get_block_devices(error);
|
|
if (devices == NULL)
|
|
return NULL;
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
GDBusProxy *proxy_blk = g_ptr_array_index(devices, i);
|
|
g_autoptr(GVariant) val = NULL;
|
|
val = g_dbus_proxy_get_cached_property(proxy_blk, "Device");
|
|
if (val == NULL)
|
|
continue;
|
|
if (g_strcmp0(g_variant_get_bytestring(val), device) == 0) {
|
|
return g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, NULL);
|
|
}
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes for device %s", device);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_volume_by_devnum:
|
|
* @devnum: a device number
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Finds the first volume from the specified device.
|
|
*
|
|
* Returns: (transfer full): a volume, or %NULL if the device was not found
|
|
*
|
|
* Since: 1.5.1
|
|
**/
|
|
FuVolume *
|
|
fu_common_get_volume_by_devnum(guint32 devnum, GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) devices = NULL;
|
|
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* find matching block device */
|
|
devices = fu_common_get_block_devices(error);
|
|
if (devices == NULL)
|
|
return NULL;
|
|
for (guint i = 0; i < devices->len; i++) {
|
|
GDBusProxy *proxy_blk = g_ptr_array_index(devices, i);
|
|
g_autoptr(GVariant) val = NULL;
|
|
val = g_dbus_proxy_get_cached_property(proxy_blk, "DeviceNumber");
|
|
if (val == NULL)
|
|
continue;
|
|
if (devnum == g_variant_get_uint64(val)) {
|
|
return g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, NULL);
|
|
}
|
|
}
|
|
|
|
/* failed */
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes for devnum %u", devnum);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_esp_default:
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Gets the platform default ESP
|
|
*
|
|
* Returns: (transfer full): a volume, or %NULL if the ESP was not found
|
|
*
|
|
* Since: 1.4.6
|
|
**/
|
|
FuVolume *
|
|
fu_common_get_esp_default(GError **error)
|
|
{
|
|
const gchar *path_tmp;
|
|
gboolean has_internal = FALSE;
|
|
g_autoptr(GPtrArray) volumes_fstab = g_ptr_array_new();
|
|
g_autoptr(GPtrArray) volumes_mtab = g_ptr_array_new();
|
|
g_autoptr(GPtrArray) volumes_vfat = g_ptr_array_new();
|
|
g_autoptr(GPtrArray) volumes = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
/* for the test suite use local directory for ESP */
|
|
path_tmp = g_getenv("FWUPD_UEFI_ESP_PATH");
|
|
if (path_tmp != NULL)
|
|
return fu_volume_new_from_mount_path(path_tmp);
|
|
|
|
volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_ESP, &error_local);
|
|
if (volumes == NULL) {
|
|
g_debug("%s, falling back to %s", error_local->message, FU_VOLUME_KIND_BDP);
|
|
volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_BDP, error);
|
|
if (volumes == NULL) {
|
|
g_prefix_error(error, "%s: ", error_local->message);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* are there _any_ internal vfat partitions?
|
|
* remember HintSystem is just that -- a hint! */
|
|
for (guint i = 0; i < volumes->len; i++) {
|
|
FuVolume *vol = g_ptr_array_index(volumes, i);
|
|
g_autofree gchar *type = fu_volume_get_id_type(vol);
|
|
if (g_strcmp0(type, "vfat") == 0 && fu_volume_is_internal(vol)) {
|
|
has_internal = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* filter to vfat partitions */
|
|
for (guint i = 0; i < volumes->len; i++) {
|
|
FuVolume *vol = g_ptr_array_index(volumes, i);
|
|
g_autofree gchar *type = fu_volume_get_id_type(vol);
|
|
if (type == NULL)
|
|
continue;
|
|
if (has_internal && !fu_volume_is_internal(vol))
|
|
continue;
|
|
if (g_strcmp0(type, "vfat") == 0)
|
|
g_ptr_array_add(volumes_vfat, vol);
|
|
}
|
|
if (volumes_vfat->len == 0) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "No ESP found");
|
|
return NULL;
|
|
}
|
|
for (guint i = 0; i < volumes_vfat->len; i++) {
|
|
FuVolume *vol = g_ptr_array_index(volumes_vfat, i);
|
|
g_ptr_array_add(fu_volume_is_mounted(vol) ? volumes_mtab : volumes_fstab, vol);
|
|
}
|
|
if (volumes_mtab->len == 1) {
|
|
FuVolume *vol = g_ptr_array_index(volumes_mtab, 0);
|
|
return g_object_ref(vol);
|
|
}
|
|
if (volumes_mtab->len == 0 && volumes_fstab->len == 1) {
|
|
FuVolume *vol = g_ptr_array_index(volumes_fstab, 0);
|
|
return g_object_ref(vol);
|
|
}
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "More than one available ESP");
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_esp_for_path:
|
|
* @esp_path: a path to the ESP
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Gets the platform ESP using a UNIX or UDisks path
|
|
*
|
|
* Returns: (transfer full): a #volume, or %NULL if the ESP was not found
|
|
*
|
|
* Since: 1.4.6
|
|
**/
|
|
FuVolume *
|
|
fu_common_get_esp_for_path(const gchar *esp_path, GError **error)
|
|
{
|
|
g_autofree gchar *basename = NULL;
|
|
g_autoptr(GPtrArray) volumes = NULL;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_return_val_if_fail(esp_path != NULL, NULL);
|
|
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
|
|
|
|
volumes = fu_common_get_volumes_by_kind(FU_VOLUME_KIND_ESP, &error_local);
|
|
if (volumes == NULL) {
|
|
/* check if it's a valid directory already */
|
|
if (g_file_test(esp_path, G_FILE_TEST_IS_DIR))
|
|
return fu_volume_new_from_mount_path(esp_path);
|
|
g_propagate_error(error, g_steal_pointer(&error_local));
|
|
return NULL;
|
|
}
|
|
basename = g_path_get_basename(esp_path);
|
|
for (guint i = 0; i < volumes->len; i++) {
|
|
FuVolume *vol = g_ptr_array_index(volumes, i);
|
|
g_autofree gchar *vol_basename =
|
|
g_path_get_basename(fu_volume_get_mount_point(vol));
|
|
if (g_strcmp0(basename, vol_basename) == 0)
|
|
return g_object_ref(vol);
|
|
}
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_FILENAME,
|
|
"No ESP with path %s",
|
|
esp_path);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_common_crc8:
|
|
* @buf: memory buffer
|
|
* @bufsz: size of @buf
|
|
*
|
|
* Returns the cyclic redundancy check value for the given memory buffer.
|
|
*
|
|
* Returns: CRC value
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
guint8
|
|
fu_common_crc8(const guint8 *buf, gsize bufsz)
|
|
{
|
|
guint32 crc = 0;
|
|
for (gsize j = bufsz; j > 0; j--) {
|
|
crc ^= (*(buf++) << 8);
|
|
for (guint32 i = 8; i; i--) {
|
|
if (crc & 0x8000)
|
|
crc ^= (0x1070 << 3);
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
return ~((guint8)(crc >> 8));
|
|
}
|
|
|
|
/**
|
|
* fu_common_crc16_full:
|
|
* @buf: memory buffer
|
|
* @bufsz: size of @buf
|
|
* @crc: initial CRC value, typically 0xFFFF
|
|
* @polynomial: CRC polynomial, typically 0xA001 for IBM or 0x1021 for CCITT
|
|
*
|
|
* Returns the cyclic redundancy check value for the given memory buffer.
|
|
*
|
|
* Returns: CRC value
|
|
*
|
|
* Since: 1.6.2
|
|
**/
|
|
guint16
|
|
fu_common_crc16_full(const guint8 *buf, gsize bufsz, guint16 crc, guint16 polynomial)
|
|
{
|
|
for (gsize len = bufsz; len > 0; len--) {
|
|
crc = (guint16)(crc ^ (*buf++));
|
|
for (guint8 i = 0; i < 8; i++) {
|
|
if (crc & 0x1) {
|
|
crc = (crc >> 1) ^ polynomial;
|
|
} else {
|
|
crc >>= 1;
|
|
}
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
/**
|
|
* fu_common_crc16:
|
|
* @buf: memory buffer
|
|
* @bufsz: size of @buf
|
|
*
|
|
* Returns the CRC-16-IBM cyclic redundancy value for the given memory buffer.
|
|
*
|
|
* Returns: CRC value
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
guint16
|
|
fu_common_crc16(const guint8 *buf, gsize bufsz)
|
|
{
|
|
return fu_common_crc16_full(buf, bufsz, 0xFFFF, 0xA001);
|
|
}
|
|
|
|
/**
|
|
* fu_common_crc32_full:
|
|
* @buf: memory buffer
|
|
* @bufsz: size of @buf
|
|
* @crc: initial CRC value, typically 0xFFFFFFFF
|
|
* @polynomial: CRC polynomial, typically 0xEDB88320
|
|
*
|
|
* Returns the cyclic redundancy check value for the given memory buffer.
|
|
*
|
|
* Returns: CRC value
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
guint32
|
|
fu_common_crc32_full(const guint8 *buf, gsize bufsz, guint32 crc, guint32 polynomial)
|
|
{
|
|
for (guint32 idx = 0; idx < bufsz; idx++) {
|
|
guint8 data = *buf++;
|
|
crc = crc ^ data;
|
|
for (guint32 bit = 0; bit < 8; bit++) {
|
|
guint32 mask = -(crc & 1);
|
|
crc = (crc >> 1) ^ (polynomial & mask);
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
/**
|
|
* fu_common_crc32:
|
|
* @buf: memory buffer
|
|
* @bufsz: size of @buf
|
|
*
|
|
* Returns the cyclic redundancy check value for the given memory buffer.
|
|
*
|
|
* Returns: CRC value
|
|
*
|
|
* Since: 1.5.0
|
|
**/
|
|
guint32
|
|
fu_common_crc32(const guint8 *buf, gsize bufsz)
|
|
{
|
|
return fu_common_crc32_full(buf, bufsz, 0xFFFFFFFF, 0xEDB88320);
|
|
}
|
|
|
|
/**
|
|
* fu_common_uri_get_scheme:
|
|
* @uri: valid URI, e.g. `https://foo.bar/baz`
|
|
*
|
|
* Returns the USI scheme for the given URI.
|
|
*
|
|
* Returns: scheme value, or %NULL if invalid, e.g. `https`
|
|
*
|
|
* Since: 1.5.6
|
|
**/
|
|
gchar *
|
|
fu_common_uri_get_scheme(const gchar *uri)
|
|
{
|
|
gchar *tmp;
|
|
|
|
g_return_val_if_fail(uri != NULL, NULL);
|
|
|
|
tmp = g_strstr_len(uri, -1, ":");
|
|
if (tmp == NULL || tmp[0] == '\0')
|
|
return NULL;
|
|
return g_utf8_strdown(uri, tmp - uri);
|
|
}
|
|
|
|
/**
|
|
* fu_common_align_up:
|
|
* @value: value to align
|
|
* @alignment: align to this power of 2, where 0x1F is the maximum value of 2GB
|
|
*
|
|
* Align a value to a power of 2 boundary, where @alignment is the bit position
|
|
* to align to. If @alignment is zero then @value is always returned unchanged.
|
|
*
|
|
* Returns: aligned value, which will be the same as @value if already aligned,
|
|
* or %G_MAXSIZE if the value would overflow
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
gsize
|
|
fu_common_align_up(gsize value, guint8 alignment)
|
|
{
|
|
gsize value_new;
|
|
guint32 mask = 1 << alignment;
|
|
|
|
g_return_val_if_fail(alignment <= FU_FIRMWARE_ALIGNMENT_2G, G_MAXSIZE);
|
|
|
|
/* no alignment required */
|
|
if ((value & (mask - 1)) == 0)
|
|
return value;
|
|
|
|
/* increment up to the next alignment value */
|
|
value_new = value + mask;
|
|
value_new &= ~(mask - 1);
|
|
|
|
/* overflow */
|
|
if (value_new < value)
|
|
return G_MAXSIZE;
|
|
|
|
/* success */
|
|
return value_new;
|
|
}
|
|
|
|
/**
|
|
* fu_battery_state_to_string:
|
|
* @battery_state: a battery state, e.g. %FU_BATTERY_STATE_FULLY_CHARGED
|
|
*
|
|
* Converts an enumerated type to a string.
|
|
*
|
|
* Returns: a string, or %NULL for invalid
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
const gchar *
|
|
fu_battery_state_to_string(FuBatteryState battery_state)
|
|
{
|
|
if (battery_state == FU_BATTERY_STATE_UNKNOWN)
|
|
return "unknown";
|
|
if (battery_state == FU_BATTERY_STATE_CHARGING)
|
|
return "charging";
|
|
if (battery_state == FU_BATTERY_STATE_DISCHARGING)
|
|
return "discharging";
|
|
if (battery_state == FU_BATTERY_STATE_EMPTY)
|
|
return "empty";
|
|
if (battery_state == FU_BATTERY_STATE_FULLY_CHARGED)
|
|
return "fully-charged";
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* fu_bytes_get_data_safe:
|
|
* @bytes: data blob
|
|
* @bufsz: (out) (optional): location to return size of byte data
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Get the byte data in the #GBytes. This data should not be modified.
|
|
* This function will always return the same pointer for a given #GBytes.
|
|
*
|
|
* If the size of @bytes is zero, then %NULL is returned and the @error is set,
|
|
* which differs in behavior to that of g_bytes_get_data().
|
|
*
|
|
* This may be useful when calling g_mapped_file_new() on a zero-length file.
|
|
*
|
|
* Returns: a pointer to the byte data, or %NULL.
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
const guint8 *
|
|
fu_bytes_get_data_safe(GBytes *bytes, gsize *bufsz, GError **error)
|
|
{
|
|
const guint8 *buf = g_bytes_get_data(bytes, bufsz);
|
|
if (buf == NULL) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid data");
|
|
return NULL;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* fu_xmlb_builder_insert_kv:
|
|
* @bn: #XbBuilderNode
|
|
* @key: string key
|
|
* @value: string value
|
|
*
|
|
* Convenience function to add an XML node with a string value. If @value is %NULL
|
|
* then no member is added.
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
void
|
|
fu_xmlb_builder_insert_kv(XbBuilderNode *bn, const gchar *key, const gchar *value)
|
|
{
|
|
if (value == NULL)
|
|
return;
|
|
xb_builder_node_insert_text(bn, key, value, NULL);
|
|
}
|
|
|
|
/**
|
|
* fu_xmlb_builder_insert_kx:
|
|
* @bn: #XbBuilderNode
|
|
* @key: string key
|
|
* @value: integer value
|
|
*
|
|
* Convenience function to add an XML node with an integer value. If @value is 0
|
|
* then no member is added.
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
void
|
|
fu_xmlb_builder_insert_kx(XbBuilderNode *bn, const gchar *key, guint64 value)
|
|
{
|
|
g_autofree gchar *value_hex = NULL;
|
|
if (value == 0)
|
|
return;
|
|
value_hex = g_strdup_printf("0x%x", (guint)value);
|
|
xb_builder_node_insert_text(bn, key, value_hex, NULL);
|
|
}
|
|
|
|
/**
|
|
* fu_xmlb_builder_insert_kb:
|
|
* @bn: #XbBuilderNode
|
|
* @key: string key
|
|
* @value: boolean value
|
|
*
|
|
* Convenience function to add an XML node with a boolean value.
|
|
*
|
|
* Since: 1.6.0
|
|
**/
|
|
void
|
|
fu_xmlb_builder_insert_kb(XbBuilderNode *bn, const gchar *key, gboolean value)
|
|
{
|
|
xb_builder_node_insert_text(bn, key, value ? "true" : "false", NULL);
|
|
}
|
|
|
|
/**
|
|
* fu_common_get_firmware_search_path:
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Reads the FU_PATH_KIND_FIRMWARE_SEARCH and
|
|
* returns its contents
|
|
*
|
|
* Returns: a pointer to a gchar array
|
|
*
|
|
* Since: 1.6.2
|
|
**/
|
|
gchar *
|
|
fu_common_get_firmware_search_path(GError **error)
|
|
{
|
|
gsize sz = 0;
|
|
g_autofree gchar *sys_fw_search_path = NULL;
|
|
g_autofree gchar *contents = NULL;
|
|
|
|
sys_fw_search_path = fu_common_get_path(FU_PATH_KIND_FIRMWARE_SEARCH);
|
|
if (!g_file_get_contents(sys_fw_search_path, &contents, &sz, error))
|
|
return NULL;
|
|
|
|
/* remove newline character */
|
|
if (contents != NULL && sz > 0 && contents[sz - 1] == '\n')
|
|
contents[sz - 1] = 0;
|
|
|
|
g_debug("read firmware search path (%" G_GSIZE_FORMAT "): %s", sz, contents);
|
|
|
|
return g_steal_pointer(&contents);
|
|
}
|
|
|
|
/**
|
|
* fu_common_set_firmware_search_path:
|
|
* @path: NUL-terminated string
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Writes path to the FU_PATH_KIND_FIRMWARE_SEARCH
|
|
*
|
|
* Returns: %TRUE if successful
|
|
*
|
|
* Since: 1.6.2
|
|
**/
|
|
gboolean
|
|
fu_common_set_firmware_search_path(const gchar *path, GError **error)
|
|
{
|
|
#if GLIB_CHECK_VERSION(2, 66, 0)
|
|
g_autofree gchar *sys_fw_search_path_prm = NULL;
|
|
|
|
g_return_val_if_fail(path != NULL, FALSE);
|
|
g_return_val_if_fail(strlen(path) < PATH_MAX, FALSE);
|
|
|
|
sys_fw_search_path_prm = fu_common_get_path(FU_PATH_KIND_FIRMWARE_SEARCH);
|
|
|
|
g_debug("writing firmware search path (%" G_GSIZE_FORMAT "): %s", strlen(path), path);
|
|
|
|
return g_file_set_contents_full(sys_fw_search_path_prm,
|
|
path,
|
|
strlen(path),
|
|
G_FILE_SET_CONTENTS_NONE,
|
|
0644,
|
|
error);
|
|
#else
|
|
FILE *fd;
|
|
gsize res;
|
|
g_autofree gchar *sys_fw_search_path_prm = NULL;
|
|
|
|
g_return_val_if_fail(path != NULL, FALSE);
|
|
g_return_val_if_fail(strlen(path) < PATH_MAX, FALSE);
|
|
|
|
sys_fw_search_path_prm = fu_common_get_path(FU_PATH_KIND_FIRMWARE_SEARCH);
|
|
/* g_file_set_contents will try to create backup files in sysfs, so use fopen here */
|
|
fd = fopen(sys_fw_search_path_prm, "w");
|
|
if (fd == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_PERMISSION_DENIED,
|
|
"Failed to open %s: %s",
|
|
sys_fw_search_path_prm,
|
|
g_strerror(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug("writing firmware search path (%" G_GSIZE_FORMAT "): %s", strlen(path), path);
|
|
|
|
res = fwrite(path, sizeof(gchar), strlen(path), fd);
|
|
|
|
fclose(fd);
|
|
|
|
if (res != strlen(path)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"Failed to write firmware search path: %s",
|
|
g_strerror(errno));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* fu_common_reset_firmware_search_path:
|
|
* @error: (nullable): optional return location for an error
|
|
*
|
|
* Resets the FU_PATH_KIND_FIRMWARE_SEARCH to an empty string
|
|
*
|
|
* Returns: %TRUE if successful
|
|
*
|
|
* Since: 1.6.2
|
|
**/
|
|
gboolean
|
|
fu_common_reset_firmware_search_path(GError **error)
|
|
{
|
|
const gchar *contents = " ";
|
|
|
|
return fu_common_set_firmware_search_path(contents, error);
|
|
}
|