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
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;
|
||||
}
|
||||
|
||||
/*! \} */
|
||||
|
Reference in New Issue
Block a user