fwupd/libfwupdplugin/fu-path.c

520 lines
14 KiB
C

/*
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuCommon"
#include "config.h"
#include <errno.h>
#include <glib/gstdio.h>
#ifdef _WIN32
#include <stdlib.h>
#endif
#include "fwupd-error.h"
#include "fu-path-private.h"
/**
* fu_path_rmtree:
* @directory: a directory name
* @error: (nullable): optional return location for an error
*
* Recursively removes a directory.
*
* Returns: %TRUE for success, %FALSE otherwise
*
* Since: 1.8.2
**/
gboolean
fu_path_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_path_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_path_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_SYMLINK))
continue;
if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
if (!fu_path_get_file_list_internal(files, src, error))
return FALSE;
} else {
g_ptr_array_add(files, g_steal_pointer(&src));
}
}
return TRUE;
}
/**
* fu_path_get_files:
* @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.8.2
**/
GPtrArray *
fu_path_get_files(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_path_get_file_list_internal(files, path, error))
return NULL;
return g_steal_pointer(&files);
}
/**
* fu_path_mkdir:
* @dirname: a directory name
* @error: (nullable): optional return location for an error
*
* Creates any required directories, including any parent directories.
*
* Returns: %TRUE for success
*
* Since: 1.8.2
**/
gboolean
fu_path_mkdir(const gchar *dirname, GError **error)
{
g_return_val_if_fail(dirname != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (!g_file_test(dirname, G_FILE_TEST_IS_DIR))
g_debug("creating path %s", dirname);
if (g_mkdir_with_parents(dirname, 0755) == -1) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"Failed to create '%s': %s",
dirname,
g_strerror(errno));
return FALSE;
}
return TRUE;
}
/**
* fu_path_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: 1.8.2
**/
gboolean
fu_path_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);
return fu_path_mkdir(parent, error);
}
/**
* fu_path_find_program:
* @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.8.2
**/
gchar *
fu_path_find_program(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;
}
/**
* fu_path_get_win32_basedir:
*
* Gets the base directory that fwupd has been launched from on Windows.
* This is the directory containing all subdirectories (IE 'C:\Program Files (x86)\fwupd\')
*
* Returns: The system path, or %NULL if invalid
*
* Since: 1.8.2
**/
static gchar *
fu_path_get_win32_basedir(void)
{
#ifdef _WIN32
char drive_buf[_MAX_DRIVE];
char dir_buf[_MAX_DIR];
_splitpath(_pgmptr, drive_buf, dir_buf, NULL, NULL);
return g_build_filename(drive_buf, dir_buf, "..", NULL);
#endif
return NULL;
}
/**
* fu_path_from_kind:
* @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.8.2
**/
gchar *
fu_path_from_kind(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);
#ifdef _WIN32
return g_build_filename(g_getenv("USERPROFILE"),
PACKAGE_NAME,
FWUPD_LOCALSTATEDIR,
NULL);
#else
tmp = g_getenv("SNAP_USER_DATA");
if (tmp != NULL)
return g_build_filename(tmp, FWUPD_LOCALSTATEDIR, NULL);
return g_build_filename(FWUPD_LOCALSTATEDIR, NULL);
#endif
/* /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/class/dmi/id */
case FU_PATH_KIND_SYSFSDIR_DMI:
tmp = g_getenv("FWUPD_SYSFSDMIDIR");
if (tmp != NULL)
return g_strdup(tmp);
return g_strdup("/sys/class/dmi/id");
/* /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);
basedir = fu_path_get_win32_basedir();
if (basedir != NULL)
return g_build_filename(basedir, FWUPD_SYSCONFDIR, NULL);
return g_strdup(FWUPD_SYSCONFDIR);
/* /usr/lib/<triplet>/fwupd-#VERSION# */
case FU_PATH_KIND_LIBDIR_PKG:
tmp = g_getenv("FWUPD_LIBDIR_PKG");
if (tmp != NULL)
return g_strdup(tmp);
tmp = g_getenv("SNAP");
if (tmp != NULL)
return g_build_filename(tmp, FWUPD_LIBDIR_PKG, NULL);
basedir = fu_path_get_win32_basedir();
if (basedir != NULL)
return g_build_filename(basedir, FWUPD_LIBDIR_PKG, NULL);
return g_build_filename(FWUPD_LIBDIR_PKG, 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);
basedir = fu_path_get_win32_basedir();
if (basedir != NULL)
return g_build_filename(basedir, 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_path_from_kind(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_path_from_kind(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_path_from_kind(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_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG);
return g_build_filename(basedir, "quirks.d", NULL);
/* /var/lib/fwupd/metadata */
case FU_PATH_KIND_LOCALSTATEDIR_METADATA:
tmp = g_getenv("FWUPD_LOCALSTATEDIR_METADATA");
if (tmp != NULL)
return g_build_filename(tmp, NULL);
basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG);
return g_build_filename(basedir, "metadata", NULL);
/* /var/lib/fwupd/remotes.d */
case FU_PATH_KIND_LOCALSTATEDIR_REMOTES:
tmp = g_getenv("FWUPD_LOCALSTATEDIR_REMOTES");
if (tmp != NULL)
return g_build_filename(tmp, NULL);
basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG);
return g_build_filename(basedir, "remotes.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_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR);
return g_build_filename(basedir, "cache", PACKAGE_NAME, NULL);
/* /var/etc/fwupd */
case FU_PATH_KIND_LOCALCONFDIR_PKG:
tmp = g_getenv("LOCALCONF_DIRECTORY");
if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS))
return g_build_filename(tmp, NULL);
basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR);
return g_build_filename(basedir, "etc", PACKAGE_NAME, NULL);
/* /run/lock */
case FU_PATH_KIND_LOCKDIR:
tmp = g_getenv("FWUPD_LOCKDIR");
if (tmp != NULL)
return g_strdup(tmp);
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
/* C:\Program Files (x86)\fwupd\ */
case FU_PATH_KIND_WIN32_BASEDIR:
return fu_path_get_win32_basedir();
/* this shouldn't happen */
default:
g_warning("cannot build path for unknown kind %u", path_kind);
}
return NULL;
}
/**
* fu_path_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.8.2
**/
gboolean
fu_path_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_path_fnmatch_impl(pattern, str);
}
static gint
fu_path_glob_sort_cb(gconstpointer a, gconstpointer b)
{
return g_strcmp0(*(const gchar **)a, *(const gchar **)b);
}
/**
* fu_path_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.8.2
**/
GPtrArray *
fu_path_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_path_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_path_glob_sort_cb);
return g_steal_pointer(&files);
}