mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-02 05:27:11 +00:00
417 lines
10 KiB
C
417 lines
10 KiB
C
/*
|
|
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2015 Peter Jones <pjones@redhat.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <gio/gunixinputstream.h>
|
|
#include <gio/gunixoutputstream.h>
|
|
#include <linux/fs.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "fwupd-error.h"
|
|
|
|
#include "fu-common.h"
|
|
#include "fu-efivar-impl.h"
|
|
|
|
static gchar *
|
|
fu_efivar_get_path(void)
|
|
{
|
|
g_autofree gchar *sysfsfwdir = fu_common_get_path(FU_PATH_KIND_SYSFSDIR_FW);
|
|
return g_build_filename(sysfsfwdir, "efi", "efivars", NULL);
|
|
}
|
|
|
|
static gchar *
|
|
fu_efivar_get_filename(const gchar *guid, const gchar *name)
|
|
{
|
|
g_autofree gchar *efivardir = fu_efivar_get_path();
|
|
return g_strdup_printf("%s/%s-%s", efivardir, name, guid);
|
|
}
|
|
|
|
gboolean
|
|
fu_efivar_supported_impl(GError **error)
|
|
{
|
|
g_autofree gchar *efivardir = fu_efivar_get_path();
|
|
if (!g_file_test(efivardir, G_FILE_TEST_IS_DIR)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"kernel efivars support missing: %s",
|
|
efivardir);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_efivar_set_immutable_fd(int fd, gboolean value, gboolean *value_old, GError **error)
|
|
{
|
|
guint flags;
|
|
gboolean is_immutable;
|
|
int rc;
|
|
|
|
/* get existing status */
|
|
rc = ioctl(fd, FS_IOC_GETFLAGS, &flags);
|
|
if (rc < 0) {
|
|
/* check for tmpfs */
|
|
if (errno == ENOTTY || errno == ENOSYS) {
|
|
is_immutable = FALSE;
|
|
} else {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to get flags: %s",
|
|
strerror(errno));
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
is_immutable = (flags & FS_IMMUTABLE_FL) > 0;
|
|
}
|
|
|
|
/* save the old value */
|
|
if (value_old != NULL)
|
|
*value_old = is_immutable;
|
|
|
|
/* is this already correct */
|
|
if (value) {
|
|
if (is_immutable)
|
|
return TRUE;
|
|
flags |= FS_IMMUTABLE_FL;
|
|
} else {
|
|
if (!is_immutable)
|
|
return TRUE;
|
|
flags &= ~FS_IMMUTABLE_FL;
|
|
}
|
|
|
|
/* set the new status */
|
|
rc = ioctl(fd, FS_IOC_SETFLAGS, &flags);
|
|
if (rc < 0) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to set flags: %s",
|
|
strerror(errno));
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_efivar_set_immutable(const gchar *fn, gboolean value, gboolean *value_old, GError **error)
|
|
{
|
|
gint fd;
|
|
g_autoptr(GInputStream) istr = NULL;
|
|
|
|
/* open file readonly */
|
|
fd = open(fn, O_RDONLY);
|
|
if (fd < 0) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_FILENAME,
|
|
"failed to open: %s",
|
|
strerror(errno));
|
|
return FALSE;
|
|
}
|
|
istr = g_unix_input_stream_new(fd, TRUE);
|
|
return fu_efivar_set_immutable_fd(fd, value, value_old, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error)
|
|
{
|
|
g_autofree gchar *fn = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
|
|
fn = fu_efivar_get_filename(guid, name);
|
|
file = g_file_new_for_path(fn);
|
|
if (!g_file_query_exists(file, NULL))
|
|
return TRUE;
|
|
if (!fu_efivar_set_immutable(fn, FALSE, NULL, error)) {
|
|
g_prefix_error(error, "failed to set %s as mutable: ", fn);
|
|
return FALSE;
|
|
}
|
|
return g_file_delete(file, NULL, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error)
|
|
{
|
|
const gchar *fn;
|
|
g_autofree gchar *nameguid_glob = NULL;
|
|
g_autofree gchar *efivardir = fu_efivar_get_path();
|
|
g_autoptr(GDir) dir = NULL;
|
|
|
|
dir = g_dir_open(efivardir, 0, error);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
nameguid_glob = g_strdup_printf("%s-%s", name_glob, guid);
|
|
while ((fn = g_dir_read_name(dir)) != NULL) {
|
|
if (fu_common_fnmatch(nameguid_glob, fn)) {
|
|
g_autofree gchar *keyfn = g_build_filename(efivardir, fn, NULL);
|
|
g_autoptr(GFile) file = g_file_new_for_path(keyfn);
|
|
if (!fu_efivar_set_immutable(keyfn, FALSE, NULL, error)) {
|
|
g_prefix_error(error, "failed to set %s as mutable: ", keyfn);
|
|
return FALSE;
|
|
}
|
|
if (!g_file_delete(file, NULL, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_efivar_exists_guid(const gchar *guid)
|
|
{
|
|
const gchar *fn;
|
|
g_autofree gchar *efivardir = fu_efivar_get_path();
|
|
g_autoptr(GDir) dir = NULL;
|
|
|
|
dir = g_dir_open(efivardir, 0, NULL);
|
|
if (dir == NULL)
|
|
return FALSE;
|
|
while ((fn = g_dir_read_name(dir)) != NULL) {
|
|
if (g_str_has_suffix(fn, guid))
|
|
return TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_efivar_exists_impl(const gchar *guid, const gchar *name)
|
|
{
|
|
g_autofree gchar *fn = NULL;
|
|
|
|
/* any name */
|
|
if (name == NULL)
|
|
return fu_efivar_exists_guid(guid);
|
|
|
|
fn = fu_efivar_get_filename(guid, name);
|
|
return g_file_test(fn, G_FILE_TEST_EXISTS);
|
|
}
|
|
|
|
gboolean
|
|
fu_efivar_get_data_impl(const gchar *guid,
|
|
const gchar *name,
|
|
guint8 **data,
|
|
gsize *data_sz,
|
|
guint32 *attr,
|
|
GError **error)
|
|
{
|
|
gssize attr_sz;
|
|
gssize data_sz_tmp;
|
|
guint32 attr_tmp;
|
|
guint64 sz;
|
|
g_autofree gchar *fn = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autoptr(GFileInfo) info = NULL;
|
|
g_autoptr(GInputStream) istr = NULL;
|
|
|
|
/* open file as stream */
|
|
fn = fu_efivar_get_filename(guid, name);
|
|
file = g_file_new_for_path(fn);
|
|
istr = G_INPUT_STREAM(g_file_read(file, NULL, error));
|
|
if (istr == NULL)
|
|
return FALSE;
|
|
info = g_file_input_stream_query_info(G_FILE_INPUT_STREAM(istr),
|
|
G_FILE_ATTRIBUTE_STANDARD_SIZE,
|
|
NULL,
|
|
error);
|
|
if (info == NULL) {
|
|
g_prefix_error(error, "failed to get stream info: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get total stream size */
|
|
sz = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
|
|
if (sz < 4) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"efivars file too small: %" G_GUINT64_FORMAT,
|
|
sz);
|
|
return FALSE;
|
|
}
|
|
|
|
/* read out the attributes */
|
|
attr_sz = g_input_stream_read(istr, &attr_tmp, sizeof(attr_tmp), NULL, error);
|
|
if (attr_sz == -1) {
|
|
g_prefix_error(error, "failed to read attr: ");
|
|
return FALSE;
|
|
}
|
|
if (attr != NULL)
|
|
*attr = attr_tmp;
|
|
|
|
/* read out the data */
|
|
data_sz_tmp = sz - sizeof(attr_tmp);
|
|
if (data_sz_tmp == 0) {
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no data to read");
|
|
return FALSE;
|
|
}
|
|
if (data_sz != NULL)
|
|
*data_sz = data_sz_tmp;
|
|
if (data != NULL) {
|
|
g_autofree guint8 *data_tmp = g_malloc0(data_sz_tmp);
|
|
if (!g_input_stream_read_all(istr, data_tmp, data_sz_tmp, NULL, NULL, error)) {
|
|
g_prefix_error(error, "failed to read data: ");
|
|
return FALSE;
|
|
}
|
|
*data = g_steal_pointer(&data_tmp);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
GPtrArray *
|
|
fu_efivar_get_names_impl(const gchar *guid, GError **error)
|
|
{
|
|
const gchar *name_guid;
|
|
g_autofree gchar *path = fu_efivar_get_path();
|
|
g_autoptr(GDir) dir = NULL;
|
|
g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func(g_free);
|
|
|
|
/* find names with matching GUID */
|
|
dir = g_dir_open(path, 0, error);
|
|
if (dir == NULL)
|
|
return NULL;
|
|
while ((name_guid = g_dir_read_name(dir)) != NULL) {
|
|
gsize name_guidsz = strlen(name_guid);
|
|
if (name_guidsz < 38)
|
|
continue;
|
|
if (g_strcmp0(name_guid + name_guidsz - 36, guid) == 0) {
|
|
g_ptr_array_add(names, g_strndup(name_guid, name_guidsz - 37));
|
|
}
|
|
}
|
|
|
|
/* nothing found */
|
|
if (names->len == 0) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no names for GUID %s", guid);
|
|
return NULL;
|
|
}
|
|
|
|
/* success */
|
|
return g_steal_pointer(&names);
|
|
}
|
|
|
|
GFileMonitor *
|
|
fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error)
|
|
{
|
|
g_autofree gchar *fn = NULL;
|
|
g_autoptr(GFile) file = NULL;
|
|
g_autoptr(GFileMonitor) monitor = NULL;
|
|
|
|
fn = fu_efivar_get_filename(guid, name);
|
|
file = g_file_new_for_path(fn);
|
|
monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, error);
|
|
if (monitor == NULL)
|
|
return NULL;
|
|
g_file_monitor_set_rate_limit(monitor, 5000);
|
|
return g_steal_pointer(&monitor);
|
|
}
|
|
|
|
guint64
|
|
fu_efivar_space_used_impl(GError **error)
|
|
{
|
|
const gchar *fn;
|
|
guint64 total = 0;
|
|
g_autoptr(GDir) dir = NULL;
|
|
g_autofree gchar *path = fu_efivar_get_path();
|
|
|
|
/* stat each file */
|
|
dir = g_dir_open(path, 0, error);
|
|
if (dir == NULL)
|
|
return G_MAXUINT64;
|
|
while ((fn = g_dir_read_name(dir)) != NULL) {
|
|
guint64 sz;
|
|
g_autofree gchar *pathfn = g_build_filename(path, fn, NULL);
|
|
g_autoptr(GFile) file = g_file_new_for_path(pathfn);
|
|
g_autoptr(GFileInfo) info = NULL;
|
|
|
|
info = g_file_query_info(file,
|
|
G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE
|
|
"," G_FILE_ATTRIBUTE_STANDARD_SIZE,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
NULL,
|
|
error);
|
|
if (info == NULL)
|
|
return G_MAXUINT64;
|
|
sz = g_file_info_get_attribute_uint64(info,
|
|
G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE);
|
|
if (sz == 0)
|
|
sz = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
|
|
total += sz;
|
|
}
|
|
|
|
/* success */
|
|
return total;
|
|
}
|
|
|
|
gboolean
|
|
fu_efivar_set_data_impl(const gchar *guid,
|
|
const gchar *name,
|
|
const guint8 *data,
|
|
gsize sz,
|
|
guint32 attr,
|
|
GError **error)
|
|
{
|
|
int fd;
|
|
int open_wflags;
|
|
gboolean was_immutable;
|
|
g_autofree gchar *fn = fu_efivar_get_filename(guid, name);
|
|
g_autofree guint8 *buf = g_malloc0(sizeof(guint32) + sz);
|
|
g_autoptr(GFile) file = g_file_new_for_path(fn);
|
|
g_autoptr(GOutputStream) ostr = NULL;
|
|
|
|
/* create empty file so we can clear the immutable bit before writing */
|
|
if (!g_file_query_exists(file, NULL)) {
|
|
g_autoptr(GFileOutputStream) ostr_tmp = NULL;
|
|
ostr_tmp = g_file_create(file, G_FILE_CREATE_NONE, NULL, error);
|
|
if (ostr_tmp == NULL)
|
|
return FALSE;
|
|
if (!g_output_stream_close(G_OUTPUT_STREAM(ostr_tmp), NULL, error)) {
|
|
g_prefix_error(error, "failed to touch efivarfs: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (!fu_efivar_set_immutable(fn, FALSE, &was_immutable, error)) {
|
|
g_prefix_error(error, "failed to set %s as mutable: ", fn);
|
|
return FALSE;
|
|
}
|
|
|
|
/* open file for writing, optionally append */
|
|
open_wflags = O_WRONLY;
|
|
if (attr & FU_EFIVAR_ATTR_APPEND_WRITE)
|
|
open_wflags |= O_APPEND;
|
|
fd = open(fn, open_wflags);
|
|
if (fd < 0) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to open %s: %s",
|
|
fn,
|
|
strerror(errno));
|
|
return FALSE;
|
|
}
|
|
ostr = g_unix_output_stream_new(fd, TRUE);
|
|
memcpy(buf, &attr, sizeof(attr));
|
|
memcpy(buf + sizeof(attr), data, sz);
|
|
if (g_output_stream_write(ostr, buf, sizeof(attr) + sz, NULL, error) < 0) {
|
|
g_prefix_error(error, "failed to write data to efivarfs: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* set as immutable again */
|
|
if (was_immutable && !fu_efivar_set_immutable(fn, TRUE, NULL, error)) {
|
|
g_prefix_error(error, "failed to set %s as immutable: ", fn);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|