lib: add functions to search in configuration/data directories

The previous approach to loading config files was to ask WP for the
directory and then search those for the config files. This patch changes the
approach - a caller now asks WP to search for a specific config file or
iterate over a config file directory.

This allows us to implement a directory lookup order, i.e.
"wireplumber.conf" may be in XDG_CONFIG_DIR, /etc/,
/usr/share and the first one found is used.

For configuration directories, the new method iterates over all matching
entries (files + directories) and invokes a callback for each entry.

This enables distributions to ship default files in /usr/share/wireplumber
but have admins and users override them on a local basis. For lua scripts in
particular, overriding a distribution-provided file with an empty file
effectively disables it, adding a file adds it in the right sort order.
This commit is contained in:
Peter Hutterer
2021-07-07 16:09:34 +10:00
committed by George Kiagiadakis
parent 153e84c8b8
commit d38c3fb4cc
6 changed files with 261 additions and 95 deletions

View File

@@ -120,18 +120,24 @@ and ignored. Possible commands are
Location of configuration files
-------------------------------
WirePlumber's default location of its configuration files is determined at
compile time by the build system. Typically, it ends up being `/etc/wireplumber`.
WirePlumber's default locations of its configuration files are determined at
compile time by the build system. Typically, those end up being
`XDG_CONFIG_DIR/wireplumber`, `/etc/wireplumber`, and
`/usr/share/wireplumber`, in that order of priority.
In more detail, this is controlled by the `--sysconfdir` meson option. When
this is set to an absolute path, such as `/etc`, the location of the
configuration files is set to be `$sysconfdir/wireplumber`. When this is set
to a relative path, such as `etc`, then the installation prefix (`--prefix`)
In more detail, the latter two are controlled by the `--sysconfdir` and `--datadir`
meson options. When those are set to an absolute path, such as `/etc`, the
location of the configuration files is set to be `$sysconfdir/wireplumber`.
When set to a relative path, such as `etc`, then the installation prefix (`--prefix`)
is prepended to the path: `$prefix/$sysconfdir/wireplumber`
WirePlumber expects its `wireplumber.conf` to reside in that directory.
It is possible to override that at runtime by setting the
`WIREPLUMBER_CONFIG_FILE` environment variable::
The three locations are intended for custom user configuration,
host-specific configuration and distribution-provided configuration,
respectively. At runtime, WirePlumber will search the directories
for the highest-priority directory to contain the `wireplumber.conf`
configuration file. This allows a user or system administrator to easily
override the distribution provided configuration files by placing an equally
named file in the respective directory.
It is possible to override the lookup path at runtime by passing the
`--config-file` or `-c` option::
@@ -144,6 +150,8 @@ by setting the `WIREPLUMBER_CONFIG_DIR` environment variable::
WIREPLUMBER_CONFIG_DIR=src/config wireplumber
If `WIREPLUMBER_CONFIG_DIR` is set, the default locations are ignored.
Location of modules
-------------------

View File

@@ -153,4 +153,206 @@ wp_get_data_dir (void)
return data_dir;
}
static gchar *
check_path (const gchar *basedir, const gchar *subdir, const gchar *filename)
{
g_autofree gchar *path = g_build_filename (basedir,
subdir ? subdir : filename,
subdir ? filename : NULL,
NULL);
g_autofree gchar *abspath = g_canonicalize_filename (path, NULL);
wp_trace ("checking %s", abspath);
if (g_file_test (abspath, G_FILE_TEST_IS_REGULAR))
return g_steal_pointer (&abspath);
return NULL;
}
/*!
* \brief Flags to specify lookup directories
* Use one of the `WP_CDIR_SET` values instead of the values directly.
*/
typedef enum {
/* config group */
WP_CDIR_ENV_CONFIG = (1 << 0), /**< $WIREPLUMBER_CONFIG_DIR */
WP_CDIR_XDG_HOME = (1 << 1), /**< XDG_CONFIG_HOME */
WP_CDIR_ETC = (1 << 2), /**< /etc/wireplumber */
/* data group */
WP_CDIR_ENV_DATA = (1 << 10), /**< $WIREPLUMBER_DATA_DIR */
WP_CDIR_USR = (1 << 11), /**< /usr/share/wireplumber */
/** The set for system-only files */
WP_CDIR_SET_SYSTEM = WP_CDIR_ETC | WP_CDIR_USR | WP_CDIR_ENV_DATA,
/** The set for user + system files */
WP_CDIR_SET_USER = WP_CDIR_ENV_CONFIG | WP_CDIR_XDG_HOME | WP_CDIR_SET_SYSTEM,
} WpConfigDir;
static GPtrArray *
lookup_dirs (guint flags)
{
g_autoptr(GPtrArray) dirs = g_ptr_array_new_with_free_func (g_free);
const gchar *dir;
/* Compile the list of lookup directories in priority order:
* - environment variables
* - XDG directories
* - etc
* - /usr/share/....
*
* Note that environment variables *replace* the equivalent config directory
* group.
*/
if ((flags & WP_CDIR_ENV_CONFIG) &&
(dir = g_getenv ("WIREPLUMBER_CONFIG_DIR"))) {
g_ptr_array_add (dirs, g_canonicalize_filename (dir, NULL));
} else {
if ((flags & WP_CDIR_XDG_HOME) && (dir = wp_get_xdg_config_dir ()))
g_ptr_array_add (dirs, (gpointer)g_strdup (dir));
if (flags & WP_CDIR_ETC)
g_ptr_array_add (dirs, (gpointer)g_strdup (WIREPLUMBER_DEFAULT_CONFIG_DIR));
}
/* data dirs */
if ((flags & WP_CDIR_ENV_DATA) && (dir = g_getenv ("WIREPLUMBER_DATA_DIR"))) {
g_ptr_array_add (dirs, g_canonicalize_filename (dir, NULL));
} else if (flags & WP_CDIR_USR) {
g_ptr_array_add (dirs,
(gpointer)g_canonicalize_filename(WIREPLUMBER_DEFAULT_DATA_DIR, NULL));
}
return g_steal_pointer (&dirs);
}
/*!
* \brief Returns the full path of \a filename as found in
* the hierarchy of configuration and data directories.
* \returns An allocated string with the configuration file path or NULL if
* the file was not found.
*/
gchar *
wp_find_config_file (const gchar *filename, const char *subdir)
{
g_autoptr(GPtrArray) dirs = lookup_dirs (WP_CDIR_SET_USER);
if (g_path_is_absolute (filename))
return g_strdup (filename);
for (guint i = 0; i < dirs->len; i++) {
gchar *path = check_path (g_ptr_array_index (dirs, i),
subdir, filename);
if (path)
return path;
}
return NULL;
}
/*!
* \brief Returns the full path of \a filename as found in
* the hierarchy of system-only configuration and data directories.
* \returns An allocated string with the configuration file path or NULL if
* the file was not found.
*/
gchar *
wp_find_sysconfig_file (const gchar *filename, const char *subdir)
{
g_autoptr(GPtrArray) dirs = lookup_dirs (WP_CDIR_SET_SYSTEM);
if (g_path_is_absolute (filename))
return g_strdup (filename);
for (guint i = 0; i < dirs->len; i++) {
gchar *path = check_path (g_ptr_array_index (dirs, i),
subdir, filename);
if (path)
return path;
}
return NULL;
}
/**
* \brief Iterates over configuration files in the \a subdir and calls the
* \a func for each file.
*
* Files are sorted across the hierarchy of configuration and data
* directories with files in higher-priority directories shadowing files in
* lower-priority directories. Files are only checked for existence, a
* caller must be able to handle read errors.
*
* If the \a func returns a negative errno the iteration stops and that
* errno is returned to the caller. The \a func should set \a error on
* failure.
*
* Note that \a func is called for directories too, it is the responsibility
* of the caller to ignore or recurse into those.
*
* \param subdir The name of the subdirectory to search for in the
* configuration directories
* \param suffix The filename suffix, NULL matches all entries
* \param func The callback to invoke for each file.
* \param user_data Passed through to \a func
* \param error Passed through to \a func
*
* \return the number of files on success or a negative errno on failure
*/
int
wp_iter_config_files (const gchar *subdir, const gchar *suffix,
wp_file_iter_func func, gpointer user_data,
GError **error)
{
g_autoptr(GHashTable) ht = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
g_autoptr(GPtrArray) dirs = NULL;
gint count = 0;
if (subdir == NULL)
subdir = ".";
/* Note: this list is highest-priority first */
dirs = lookup_dirs (WP_CDIR_SET_USER);
/* Store all filenames with their full path in the hashtable, overriding
* previous values. We need to run backwards through the list for that */
for (guint i = dirs->len; i > 0; i--) {
g_autofree gchar *dirpath = g_build_filename (g_ptr_array_index (dirs, i - 1),
subdir, NULL);
g_autoptr(GDir) dir = g_dir_open (dirpath, 0, NULL);
wp_trace ("searching config dir: %s", dirpath);
if (dir) {
const gchar *filename;
while ((filename = g_dir_read_name (dir))) {
if (suffix && !g_str_has_suffix (filename, suffix))
continue;
g_hash_table_replace (ht, g_strdup (filename),
g_build_filename (dirpath, filename, NULL));
}
}
}
if (g_hash_table_size (ht) == 0)
return 0;
/* Sort by filename */
g_autoptr(GList) keys = g_hash_table_get_keys (ht);
keys = g_list_sort (keys, (GCompareFunc)g_strcmp0);
/* Now we have our filenames in a sorted order so we can call the callback */
for (GList *elem = g_list_first (keys); elem; elem = g_list_next (elem)) {
const gchar *path = g_hash_table_lookup (ht, elem->data);
gint rc = func (path, user_data, error);
if (rc < 0)
return rc;
count++;
}
return count;
}
/*! \} */

View File

@@ -77,6 +77,20 @@ const gchar * wp_get_config_dir (void);
WP_API
const gchar * wp_get_data_dir (void);
WP_API
gchar * wp_find_config_file (const gchar *filename, const char *subdir);
WP_API
gchar * wp_find_sysconfig_file (const gchar *filename, const char *subdir);
typedef gint (*wp_file_iter_func)(const gchar *filename, gpointer user_data,
GError **error);
WP_API
gint wp_iter_config_files (const gchar *subdir, const gchar *suffix,
wp_file_iter_func func, gpointer user_data,
GError **error);
G_END_DECLS
#endif

View File

@@ -131,39 +131,7 @@ find_script (const gchar * script, gboolean daemon)
g_file_test (script, G_FILE_TEST_IS_REGULAR))
return g_strdup (script);
/* /etc/wireplumber/scripts */
{
g_autofree gchar * file = g_build_filename (
wp_get_config_dir (), "scripts", script, NULL);
wp_trace ("trying %s", file);
if (g_file_test (file, G_FILE_TEST_IS_REGULAR))
return g_steal_pointer (&file);
}
{
g_autofree gchar * file = g_build_filename (
wp_get_data_dir (), "scripts", script, NULL);
wp_trace ("trying %s", file);
if (g_file_test (file, G_FILE_TEST_IS_REGULAR))
return g_steal_pointer (&file);
}
/* {XDG_DATA_DIRS,/usr/local/share,/usr/share}/wireplumber/scripts */
const gchar * const * data_dirs = g_get_system_data_dirs ();
for (; *data_dirs; data_dirs++) {
g_autofree gchar * file = g_build_filename (
*data_dirs, "wireplumber", "scripts", script, NULL);
wp_trace ("trying %s", file);
if (g_file_test (file, G_FILE_TEST_IS_REGULAR))
return g_steal_pointer (&file);
}
return NULL;
return wp_find_sysconfig_file (script, "scripts");
}
static gboolean

View File

@@ -8,6 +8,7 @@
#include <wp/wp.h>
#include <wplua/wplua.h>
#include <errno.h>
static gboolean
load_components (lua_State *L, WpCore * core, GError ** error)
@@ -78,9 +79,17 @@ done:
}
static gint
sort_filelist (gconstpointer a, gconstpointer b)
load_file (const gchar *path, gpointer data, GError **error)
{
return g_strcmp0 (*(const gchar **) a, *(const gchar **) b);
lua_State *L = data;
if (g_file_test (path, G_FILE_TEST_IS_DIR))
return 0;
wp_info ("loading config file: %s", path);
if (!wplua_load_path (L, path, 0, 0, error))
return -EINVAL;
return 0;
}
gboolean
@@ -89,51 +98,24 @@ wp_lua_scripting_load_configuration (const gchar * conf_file,
{
g_autofree gchar * path = NULL;
g_autoptr (lua_State) L = wplua_new ();
gboolean found = FALSE;
gint nfiles = 0;
wplua_enable_sandbox (L, WP_LUA_SANDBOX_MINIMAL_STD);
/* load conf_file itself */
path = g_build_filename (wp_get_config_dir (), conf_file, NULL);
if (g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
path = wp_find_config_file (conf_file, NULL);
if (path) {
wp_info ("loading config file: %s", path);
if (!wplua_load_path (L, path, 0, 0, error))
return FALSE;
found = TRUE;
nfiles = 1;
}
g_clear_pointer (&path, g_free);
/* aggregate split files from the ${conf_file}.d subdirectory */
path = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.d",
wp_get_config_dir (), conf_file);
if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
g_autoptr (GDir) conf_dir = g_dir_open (path, 0, error);
if (!conf_dir)
return FALSE;
path = g_strdup_printf ("%s.d", conf_file);
nfiles += wp_iter_config_files (path, ".lua", load_file, L, error);
/* sort files before loading them */
g_autoptr (GPtrArray) filenames = g_ptr_array_new ();
const gchar *filename = NULL;
while ((filename = g_dir_read_name (conf_dir))) {
/* Only parse files that have the proper extension */
if (g_str_has_suffix (filename, ".lua")) {
g_ptr_array_add (filenames, (gpointer) filename);
}
}
g_ptr_array_sort (filenames, sort_filelist);
/* load */
for (guint i = 0; i < filenames->len; i++) {
g_autofree gchar * file = g_build_filename (path,
g_ptr_array_index (filenames, i), NULL);
wp_info ("loading config file: %s", file);
if (!wplua_load_path (L, file, 0, 0, error))
return FALSE;
found = TRUE;
}
}
if (!found) {
if (nfiles == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Could not locate configuration file '%s'", conf_file);
return FALSE;

View File

@@ -327,22 +327,6 @@ init_done (WpCore * core, GAsyncResult * res, WpDaemon * d)
}
}
static gchar *
find_config (const gchar *config_file_name)
{
g_autofree gchar *path = NULL;
if (!config_file_name)
config_file_name = "wireplumber.conf";
if (g_path_is_absolute (config_file_name))
path = g_strdup (config_file_name);
else
path = g_build_filename (wp_get_config_dir (), config_file_name, NULL);
return g_canonicalize_filename (path, NULL);
}
gint
main (gint argc, gchar **argv)
{
@@ -361,7 +345,15 @@ main (gint argc, gchar **argv)
return WP_EXIT_USAGE;
}
config_file_path = find_config (config_file);
if (!config_file)
config_file = "wireplumber.conf";
config_file_path = wp_find_config_file (config_file, NULL);
if (config_file_path == NULL) {
fprintf (stderr, "Unable to find the required configuration file %s\n",
config_file);
return WP_EXIT_CONFIG;
}
properties = wp_properties_new (
PW_KEY_CONFIG_NAME, config_file_path,