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:

committed by
George Kiagiadakis

parent
153e84c8b8
commit
d38c3fb4cc
@@ -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
|
||||
-------------------
|
||||
|
||||
|
202
lib/wp/wp.c
202
lib/wp/wp.c
@@ -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;
|
||||
}
|
||||
|
||||
/*! \} */
|
||||
|
14
lib/wp/wp.h
14
lib/wp/wp.h
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
26
src/main.c
26
src/main.c
@@ -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,
|
||||
|
Reference in New Issue
Block a user