diff --git a/lib/wp/base-dirs.c b/lib/wp/base-dirs.c new file mode 100644 index 00000000..c26bd451 --- /dev/null +++ b/lib/wp/base-dirs.c @@ -0,0 +1,292 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include "base-dirs.h" +#include "log.h" + +WP_DEFINE_LOCAL_LOG_TOPIC ("wp-base-dirs") + +/*! + * \defgroup wpbasedirs Base Directories File Lookup + */ + +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; +} + +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 config directories + * - /etc/ + * - /usr/share/.... + * + * Note that wireplumber environment variables *replace* other directories. + */ + if ((flags & WP_LOOKUP_DIR_ENV_CONFIG) && + (dir = g_getenv ("WIREPLUMBER_CONFIG_DIR"))) { + g_auto (GStrv) env_dirs = g_strsplit (dir, G_SEARCHPATH_SEPARATOR_S, 0); + for (guint i = 0; env_dirs[i]; i++) { + g_ptr_array_add (dirs, g_canonicalize_filename (env_dirs[i], NULL)); + } + } + else if ((flags & WP_LOOKUP_DIR_ENV_DATA) && + (dir = g_getenv ("WIREPLUMBER_DATA_DIR"))) { + g_auto (GStrv) env_dirs = g_strsplit (dir, G_SEARCHPATH_SEPARATOR_S, 0); + for (guint i = 0; env_dirs[i]; i++) { + g_ptr_array_add (dirs, g_canonicalize_filename (env_dirs[i], NULL)); + } + } + else { + if (flags & WP_LOOKUP_DIR_XDG_CONFIG_HOME) { + dir = g_get_user_config_dir (); + g_ptr_array_add (dirs, g_build_filename (dir, "wireplumber", NULL)); + } + if (flags & WP_LOOKUP_DIR_ETC) + g_ptr_array_add (dirs, + g_canonicalize_filename (WIREPLUMBER_DEFAULT_CONFIG_DIR, NULL)); + if (flags & WP_LOOKUP_DIR_PREFIX_SHARE) + g_ptr_array_add (dirs, + 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. + * + * \ingroup wpbasedirs + * \param dirs the directories to look into + * \param filename the name of the file to search for + * \param subdir (nullable): the name of the subdirectory to search in, + * inside the configuration directories + * \returns (transfer full): An allocated string with the configuration + * file path or NULL if the file was not found. + * \since 0.4.2 + */ +gchar * +wp_find_file (WpLookupDirs dirs, const gchar *filename, const gchar *subdir) +{ + g_autoptr(GPtrArray) dir_paths = lookup_dirs (dirs); + + if (g_path_is_absolute (filename)) + return g_strdup (filename); + + for (guint i = 0; i < dir_paths->len; i++) { + gchar *path = check_path (g_ptr_array_index (dir_paths, i), + subdir, filename); + if (path) + return path; + } + + return NULL; +} + +struct conffile_iterator_item +{ + gchar *filename; + gchar *path; +}; + +static void +conffile_iterator_item_clear (struct conffile_iterator_item *item) +{ + g_free (item->filename); + g_free (item->path); +} + +struct conffile_iterator_data +{ + GArray *items; + guint idx; +}; + +static void +conffile_iterator_reset (WpIterator *it) +{ + struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); + it_data->idx = 0; +} + +static gboolean +conffile_iterator_next (WpIterator *it, GValue *item) +{ + struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); + + if (it_data->idx < it_data->items->len) { + const gchar *path = g_array_index (it_data->items, + struct conffile_iterator_item, it_data->idx).path; + it_data->idx++; + g_value_init (item, G_TYPE_STRING); + g_value_set_string (item, path); + return TRUE; + } + return FALSE; +} + +static gboolean +conffile_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret, + gpointer data) +{ + struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); + + for (guint i = 0; i < it_data->items->len; i++) { + g_auto (GValue) item = G_VALUE_INIT; + const gchar *path = g_array_index (it_data->items, + struct conffile_iterator_item, i).path; + g_value_init (&item, G_TYPE_STRING); + g_value_set_string (&item, path); + if (!func (&item, ret, data)) + return FALSE; + } + return TRUE; +} + +static void +conffile_iterator_finalize (WpIterator *it) +{ + struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); + g_clear_pointer (&it_data->items, g_array_unref); +} + +static const WpIteratorMethods conffile_iterator_methods = { + .version = WP_ITERATOR_METHODS_VERSION, + .reset = conffile_iterator_reset, + .next = conffile_iterator_next, + .fold = conffile_iterator_fold, + .finalize = conffile_iterator_finalize, +}; + +static gint +conffile_iterator_item_compare (const struct conffile_iterator_item *a, + const struct conffile_iterator_item *b) +{ + return g_strcmp0 (a->filename, b->filename); +} + +/*! + * \brief Creates an iterator to iterate over configuration files in the + * \a subdir of the configuration directories + * + * The configuration directories are determined by the \a dirs parameter. + * The \a subdir parameter is the name of the subdirectory to search in, + * inside the configuration directories. If \a subdir is NULL, the base path + * of each configuration directory is used. + * + * The \a suffix parameter is the filename suffix to match. If \a suffix is + * NULL, all files are matched. + * + * The iterator will iterate over the absolute paths of the configuration + * files found, in the order of priority of the directories, starting from + * the lowest priority directory (e.g. /usr/share/wireplumber) and ending + * with the highest priority directory (e.g. $XDG_CONFIG_HOME/wireplumber). + * + * Files within each directory are also sorted by filename. + * + * \ingroup wpbasedirs + * \param dirs the directories to look into + * \param subdir (nullable): the name of the subdirectory to search in, + * inside the configuration directories + * \param suffix (nullable): The filename suffix, NULL matches all entries + * \returns (transfer full): a new iterator iterating over strings which are + * absolute paths to the files found + * \since 0.4.2 + */ +WpIterator * +wp_new_files_iterator (WpLookupDirs dirs, const gchar *subdir, + const gchar *suffix) +{ + g_autoptr (GArray) items = + g_array_new (FALSE, FALSE, sizeof (struct conffile_iterator_item)); + g_autoptr (GPtrArray) dir_paths = NULL; + + g_array_set_clear_func (items, (GDestroyNotify) conffile_iterator_item_clear); + + if (subdir == NULL) + subdir = "."; + + /* Note: this list is highest-priority first */ + dir_paths = lookup_dirs (dirs); + + /* Run backwards through the list to get files in lowest-priority-first order */ + for (guint i = dir_paths->len; i > 0; i--) { + g_autofree gchar *dirpath = + g_build_filename (g_ptr_array_index (dir_paths, i - 1), subdir, NULL); + g_autoptr (GDir) dir = g_dir_open (dirpath, 0, NULL); + + if (dir) { + g_autoptr (GArray) dir_items = g_array_new (FALSE, FALSE, + sizeof (struct conffile_iterator_item)); + + wp_trace ("searching dir: %s", dirpath); + + /* Store all filenames with their full path in the local array */ + const gchar *filename; + while ((filename = g_dir_read_name (dir))) { + if (filename[0] == '.') + continue; + + if (suffix && !g_str_has_suffix (filename, suffix)) + continue; + + /* verify the file is regular and canonicalize the path */ + g_autofree gchar *path = check_path (dirpath, NULL, filename); + if (!path) + continue; + + /* remove item with the same filename from the global items array, + so that lower priority files can be shadowed */ + for (guint j = 0; j < items->len; j++) { + struct conffile_iterator_item *item = &g_array_index (items, + struct conffile_iterator_item, j); + if (g_strcmp0 (item->filename, filename) == 0) { + g_array_remove_index (items, j); + break; + } + } + + /* append in the local array */ + g_array_append_val (dir_items, ((struct conffile_iterator_item) { + .filename = g_strdup (filename), + .path = g_steal_pointer (&path), + })); + } + + /* Sort files of the current dir by filename */ + g_array_sort (dir_items, (GCompareFunc) conffile_iterator_item_compare); + + /* Append the sorted files to the global array */ + g_array_append_vals (items, dir_items->data, dir_items->len); + } + } + + /* Construct iterator */ + WpIterator *it = wp_iterator_new (&conffile_iterator_methods, + sizeof (struct conffile_iterator_data)); + struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); + it_data->items = g_steal_pointer (&items); + it_data->idx = 0; + return g_steal_pointer (&it); +} diff --git a/lib/wp/base-dirs.h b/lib/wp/base-dirs.h new file mode 100644 index 00000000..402ab3a3 --- /dev/null +++ b/lib/wp/base-dirs.h @@ -0,0 +1,40 @@ +/* WirePlumber + * + * Copyright © 2024 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_BASE_DIRS_H__ +#define __WIREPLUMBER_BASE_DIRS_H__ + +#include "defs.h" +#include "iterator.h" + +G_BEGIN_DECLS + +/*! + * \brief Flags to specify lookup directories + * \ingroup wpbasedirs + */ +typedef enum { /*< flags >*/ + WP_LOOKUP_DIR_ENV_CONFIG = (1 << 0), /*!< $WIREPLUMBER_CONFIG_DIR */ + WP_LOOKUP_DIR_ENV_DATA = (1 << 1), /*!< $WIREPLUMBER_DATA_DIR */ + + WP_LOOKUP_DIR_XDG_CONFIG_HOME = (1 << 10), /*!< XDG_CONFIG_HOME/wireplumber */ + WP_LOOKUP_DIR_ETC = (1 << 11), /*!< ($prefix)/etc/wireplumber */ + WP_LOOKUP_DIR_PREFIX_SHARE = (1 << 12), /*!< $prefix/share/wireplumber */ +} WpLookupDirs; + +WP_API +gchar * wp_find_file (WpLookupDirs dirs, const gchar *filename, + const gchar *subdir); + +WP_API +WpIterator * wp_new_files_iterator (WpLookupDirs dirs, const gchar *subdir, + const gchar *suffix); + +G_END_DECLS + +#endif diff --git a/lib/wp/meson.build b/lib/wp/meson.build index ed8c2341..399d195e 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -1,4 +1,5 @@ wp_lib_sources = files( + 'base-dirs.c', 'client.c', 'component-loader.c', 'conf.c', @@ -43,6 +44,7 @@ wp_lib_priv_sources = files( ) wp_lib_headers = files( + 'base-dirs.h', 'client.h', 'component-loader.h', 'conf.h', diff --git a/lib/wp/wp.c b/lib/wp/wp.c index 621a1ecc..b97208dd 100644 --- a/lib/wp/wp.c +++ b/lib/wp/wp.c @@ -94,279 +94,3 @@ wp_get_module_dir (void) } /*! \} */ - -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; -} - -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 config directories - * - /etc/ - * - /usr/share/.... - * - * Note that wireplumber environment variables *replace* other directories. - */ - if ((flags & WP_LOOKUP_DIR_ENV_CONFIG) && - (dir = g_getenv ("WIREPLUMBER_CONFIG_DIR"))) { - g_auto (GStrv) env_dirs = g_strsplit (dir, G_SEARCHPATH_SEPARATOR_S, 0); - for (guint i = 0; env_dirs[i]; i++) { - g_ptr_array_add (dirs, g_canonicalize_filename (env_dirs[i], NULL)); - } - } - else if ((flags & WP_LOOKUP_DIR_ENV_DATA) && - (dir = g_getenv ("WIREPLUMBER_DATA_DIR"))) { - g_auto (GStrv) env_dirs = g_strsplit (dir, G_SEARCHPATH_SEPARATOR_S, 0); - for (guint i = 0; env_dirs[i]; i++) { - g_ptr_array_add (dirs, g_canonicalize_filename (env_dirs[i], NULL)); - } - } - else { - if (flags & WP_LOOKUP_DIR_XDG_CONFIG_HOME) { - dir = g_get_user_config_dir (); - g_ptr_array_add (dirs, g_build_filename (dir, "wireplumber", NULL)); - } - if (flags & WP_LOOKUP_DIR_ETC) - g_ptr_array_add (dirs, - g_canonicalize_filename (WIREPLUMBER_DEFAULT_CONFIG_DIR, NULL)); - if (flags & WP_LOOKUP_DIR_PREFIX_SHARE) - g_ptr_array_add (dirs, - 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. - * - * \ingroup wp - * \param dirs the directories to look into - * \param filename the name of the file to search for - * \param subdir (nullable): the name of the subdirectory to search in, - * inside the configuration directories - * \returns (transfer full): An allocated string with the configuration - * file path or NULL if the file was not found. - * \since 0.4.2 - */ -gchar * -wp_find_file (WpLookupDirs dirs, const gchar *filename, const gchar *subdir) -{ - g_autoptr(GPtrArray) dir_paths = lookup_dirs (dirs); - - if (g_path_is_absolute (filename)) - return g_strdup (filename); - - for (guint i = 0; i < dir_paths->len; i++) { - gchar *path = check_path (g_ptr_array_index (dir_paths, i), - subdir, filename); - if (path) - return path; - } - - return NULL; -} - -struct conffile_iterator_item -{ - gchar *filename; - gchar *path; -}; - -static void -conffile_iterator_item_clear (struct conffile_iterator_item *item) -{ - g_free (item->filename); - g_free (item->path); -} - -struct conffile_iterator_data -{ - GArray *items; - guint idx; -}; - -static void -conffile_iterator_reset (WpIterator *it) -{ - struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); - it_data->idx = 0; -} - -static gboolean -conffile_iterator_next (WpIterator *it, GValue *item) -{ - struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); - - if (it_data->idx < it_data->items->len) { - const gchar *path = g_array_index (it_data->items, - struct conffile_iterator_item, it_data->idx).path; - it_data->idx++; - g_value_init (item, G_TYPE_STRING); - g_value_set_string (item, path); - return TRUE; - } - return FALSE; -} - -static gboolean -conffile_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret, - gpointer data) -{ - struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); - - for (guint i = 0; i < it_data->items->len; i++) { - g_auto (GValue) item = G_VALUE_INIT; - const gchar *path = g_array_index (it_data->items, - struct conffile_iterator_item, i).path; - g_value_init (&item, G_TYPE_STRING); - g_value_set_string (&item, path); - if (!func (&item, ret, data)) - return FALSE; - } - return TRUE; -} - -static void -conffile_iterator_finalize (WpIterator *it) -{ - struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); - g_clear_pointer (&it_data->items, g_array_unref); -} - -static const WpIteratorMethods conffile_iterator_methods = { - .version = WP_ITERATOR_METHODS_VERSION, - .reset = conffile_iterator_reset, - .next = conffile_iterator_next, - .fold = conffile_iterator_fold, - .finalize = conffile_iterator_finalize, -}; - -static gint -conffile_iterator_item_compare (const struct conffile_iterator_item *a, - const struct conffile_iterator_item *b) -{ - return g_strcmp0 (a->filename, b->filename); -} - -/*! - * \brief Creates an iterator to iterate over configuration files in the - * \a subdir of the configuration directories - * - * The configuration directories are determined by the \a dirs parameter. - * The \a subdir parameter is the name of the subdirectory to search in, - * inside the configuration directories. If \a subdir is NULL, the base path - * of each configuration directory is used. - * - * The \a suffix parameter is the filename suffix to match. If \a suffix is - * NULL, all files are matched. - * - * The iterator will iterate over the absolute paths of the configuration - * files found, in the order of priority of the directories, starting from - * the lowest priority directory (e.g. /usr/share/wireplumber) and ending - * with the highest priority directory (e.g. $XDG_CONFIG_HOME/wireplumber). - * - * Files within each directory are also sorted by filename. - * - * \ingroup wp - * \param dirs the directories to look into - * \param subdir (nullable): the name of the subdirectory to search in, - * inside the configuration directories - * \param suffix (nullable): The filename suffix, NULL matches all entries - * \returns (transfer full): a new iterator iterating over strings which are - * absolute paths to the files found - * \since 0.4.2 - */ -WpIterator * -wp_new_files_iterator (WpLookupDirs dirs, const gchar *subdir, - const gchar *suffix) -{ - g_autoptr (GArray) items = - g_array_new (FALSE, FALSE, sizeof (struct conffile_iterator_item)); - g_autoptr (GPtrArray) dir_paths = NULL; - - g_array_set_clear_func (items, (GDestroyNotify) conffile_iterator_item_clear); - - if (subdir == NULL) - subdir = "."; - - /* Note: this list is highest-priority first */ - dir_paths = lookup_dirs (dirs); - - /* Run backwards through the list to get files in lowest-priority-first order */ - for (guint i = dir_paths->len; i > 0; i--) { - g_autofree gchar *dirpath = - g_build_filename (g_ptr_array_index (dir_paths, i - 1), subdir, NULL); - g_autoptr (GDir) dir = g_dir_open (dirpath, 0, NULL); - - if (dir) { - g_autoptr (GArray) dir_items = g_array_new (FALSE, FALSE, - sizeof (struct conffile_iterator_item)); - - wp_trace ("searching dir: %s", dirpath); - - /* Store all filenames with their full path in the local array */ - const gchar *filename; - while ((filename = g_dir_read_name (dir))) { - if (filename[0] == '.') - continue; - - if (suffix && !g_str_has_suffix (filename, suffix)) - continue; - - /* verify the file is regular and canonicalize the path */ - g_autofree gchar *path = check_path (dirpath, NULL, filename); - if (!path) - continue; - - /* remove item with the same filename from the global items array, - so that lower priority files can be shadowed */ - for (guint j = 0; j < items->len; j++) { - struct conffile_iterator_item *item = &g_array_index (items, - struct conffile_iterator_item, j); - if (g_strcmp0 (item->filename, filename) == 0) { - g_array_remove_index (items, j); - break; - } - } - - /* append in the local array */ - g_array_append_val (dir_items, ((struct conffile_iterator_item) { - .filename = g_strdup (filename), - .path = g_steal_pointer (&path), - })); - } - - /* Sort files of the current dir by filename */ - g_array_sort (dir_items, (GCompareFunc) conffile_iterator_item_compare); - - /* Append the sorted files to the global array */ - g_array_append_vals (items, dir_items->data, dir_items->len); - } - } - - /* Construct iterator */ - WpIterator *it = wp_iterator_new (&conffile_iterator_methods, - sizeof (struct conffile_iterator_data)); - struct conffile_iterator_data *it_data = wp_iterator_get_user_data (it); - it_data->items = g_steal_pointer (&items); - it_data->idx = 0; - return g_steal_pointer (&it); -} diff --git a/lib/wp/wp.h b/lib/wp/wp.h index 04409da9..944857d5 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -9,6 +9,7 @@ #ifndef __WIREPLUMBER_WP_H__ #define __WIREPLUMBER_WP_H__ +#include "base-dirs.h" #include "client.h" #include "component-loader.h" #include "conf.h" @@ -78,27 +79,6 @@ const char * wp_get_library_api_version (void); WP_API const gchar * wp_get_module_dir (void); -/*! - * \brief Flags to specify lookup directories - * \ingroup wp - */ -typedef enum { /*< flags >*/ - WP_LOOKUP_DIR_ENV_CONFIG = (1 << 0), /*!< $WIREPLUMBER_CONFIG_DIR */ - WP_LOOKUP_DIR_ENV_DATA = (1 << 1), /*!< $WIREPLUMBER_DATA_DIR */ - - WP_LOOKUP_DIR_XDG_CONFIG_HOME = (1 << 10), /*!< XDG_CONFIG_HOME/wireplumber */ - WP_LOOKUP_DIR_ETC = (1 << 11), /*!< ($prefix)/etc/wireplumber */ - WP_LOOKUP_DIR_PREFIX_SHARE = (1 << 12), /*!< $prefix/share/wireplumber */ -} WpLookupDirs; - -WP_API -gchar * wp_find_file (WpLookupDirs dirs, const gchar *filename, - const gchar *subdir); - -WP_API -WpIterator * wp_new_files_iterator (WpLookupDirs dirs, const gchar *subdir, - const gchar *suffix); - G_END_DECLS #endif