conf: refactor configuration loading

Changes:

- Configuration files are no longer located by libpipewire,
  which allows us to control the paths that are being looked up.
  This is a requirement for installations where pipewire and
  wireplumber are built using different prefixes, in which case
  the configuration files of wireplumber end up being installed in
  a place that libpipewire doesn't look into...

- The location of conf files is now again $prefix/share/wireplumber,
  /etc/wireplumber and $XDG_CONFIG_HOME/wireplumber, instead of using
  the pipewire directories. Also, since the previous commits, we now
  also support $XDG_CONFIG_DIRS/wireplumber (typically /etc/xdg/wireplumber)
  and $XDG_DATA_DIRS/wireplumber for system-wide configuration.

- Since libpipewire doesn't expose the parser, we now also do the
  parsing of sections ourselves. This has the advantage that we can
  optimize it a bit for our use case.

- The WpConf API has changed to not be a singleton and it is a
  property of WpCore instead. The configuration is now expected
  to be opened before the core is created, which allows the caller
  to identify configuration errors in advance. By not being a singleton,
  we can also reuse the WpConf API to open other SPA-JSON files.

- WpConf also now has a lazy loading mechanism. The configuration
  files are mmap'ed and the various sections are located in advance,
  but not parsed until they are actually requested. Also, the sections
  are not copied in memory, unlike what happens in libpipewire. They
  are only copied when merging is needed.

- WpCore now disables loading of a configuration file in pw_context,
  if a WpConf is provided. This is to have complete control here.
  The 'context.spa-libs' and 'context.modules' sections are still
  loaded, but we load them in WpConf and pass them down to pw_context
  for parsing. If a WpConf is not provided, pw_context is left to load
  the default configuration file (client.conf normally).
This commit is contained in:
George Kiagiadakis
2024-02-28 12:11:38 +02:00
parent 64c233f3f4
commit 60382df63f
14 changed files with 710 additions and 145 deletions

View File

@@ -6,13 +6,14 @@
* SPDX-License-Identifier: MIT
*/
#include "core.h"
#include "conf.h"
#include "log.h"
#include "object-interest.h"
#include "json-utils.h"
#include "base-dirs.h"
#include "error.h"
#include <pipewire/pipewire.h>
#include <spa/utils/result.h>
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-conf")
@@ -26,19 +27,38 @@ WP_DEFINE_LOCAL_LOG_TOPIC ("wp-conf")
* configuration.
*/
typedef struct _WpConfSection WpConfSection;
struct _WpConfSection
{
gchar *name;
WpSpaJson *value;
gchar *location;
};
static void
wp_conf_section_clear (WpConfSection * section)
{
g_free (section->name);
g_clear_pointer (&section->value, wp_spa_json_unref);
g_free (section->location);
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (WpConfSection, wp_conf_section_clear)
struct _WpConf
{
GObject parent;
/* Props */
GWeakRef core;
gchar *name;
GHashTable *sections;
/* Private */
GArray *conf_sections; /* element-type: WpConfSection */
GPtrArray *files; /* element-type: GMappedFile* */
};
enum {
PROP_0,
PROP_CORE,
PROP_NAME,
};
G_DEFINE_TYPE (WpConf, wp_conf, G_TYPE_OBJECT)
@@ -46,10 +66,9 @@ G_DEFINE_TYPE (WpConf, wp_conf, G_TYPE_OBJECT)
static void
wp_conf_init (WpConf * self)
{
g_weak_ref_init (&self->core, NULL);
self->sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) wp_spa_json_unref);
self->conf_sections = g_array_new (FALSE, FALSE, sizeof (WpConfSection));
g_array_set_clear_func (self->conf_sections, (GDestroyNotify) wp_conf_section_clear);
self->files = g_ptr_array_new_with_free_func ((GDestroyNotify) g_mapped_file_unref);
}
static void
@@ -59,8 +78,8 @@ wp_conf_set_property (GObject * object, guint property_id,
WpConf *self = WP_CONF (object);
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&self->core, g_value_get_object (value));
case PROP_NAME:
self->name = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -75,8 +94,8 @@ wp_conf_get_property (GObject * object, guint property_id,
WpConf *self = WP_CONF (object);
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&self->core));
case PROP_NAME:
g_value_set_string (value, self->name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -89,8 +108,10 @@ wp_conf_finalize (GObject * object)
{
WpConf *self = WP_CONF (object);
g_clear_pointer (&self->sections, g_hash_table_unref);
g_weak_ref_clear (&self->core);
wp_conf_close (self);
g_clear_pointer (&self->conf_sections, g_array_unref);
g_clear_pointer (&self->files, g_ptr_array_unref);
g_clear_pointer (&self->name, g_free);
G_OBJECT_CLASS (wp_conf_parent_class)->finalize (object);
}
@@ -104,119 +125,282 @@ wp_conf_class_init (WpConfClass * klass)
object_class->set_property = wp_conf_set_property;
object_class->get_property = wp_conf_get_property;
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core", "The WpCore", WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name", "The name of the configuration file",
NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
/*!
* \brief Returns the WpConf instance that is associated with the
* given core.
* \brief Creates a new WpConf object
*
* This method will also create the instance and register it with the core
* if it had not been created before.
* This does not open the files, it only creates the object. For most use cases,
* you should use wp_conf_new_open() instead.
*
* \ingroup wpconf
* \param core the core
* \returns (transfer full): the WpConf instance
* \param name the name of the configuration file
* \param properties (transfer full) (nullable): unused, reserved for future use
* \returns (transfer full): a new WpConf object
*/
WpConf *
wp_conf_get_instance (WpCore *core)
wp_conf_new (const gchar * name, WpProperties * properties)
{
WpConf *conf = wp_core_find_object (core,
(GEqualFunc) WP_IS_CONF, NULL);
if (G_UNLIKELY (!conf)) {
conf = g_object_new (WP_TYPE_CONF,
"core", core,
NULL);
wp_core_register_object (core, g_object_ref (conf));
wp_info_object (conf, "created wpconf object");
}
return conf;
g_return_val_if_fail (name, NULL);
g_clear_pointer (&properties, wp_properties_unref);
return g_object_new (WP_TYPE_CONF, "name", name, NULL);
}
static gint
merge_section_cb (void *data, const char *location, const char *section,
const char *str, size_t len)
/*!
* \brief Creates a new WpConf object and opens the configuration file and its
* fragments, keeping them mapped in memory for further access.
*
* \ingroup wpconf
* \param name the name of the configuration file
* \param properties (transfer full) (nullable): unused, reserved for future use
* \param error (out) (nullable): return location for a GError, or NULL
* \returns (transfer full) (nullable): a new WpConf object, or NULL
* if an error occurred
*/
WpConf *
wp_conf_new_open (const gchar * name, WpProperties * properties, GError ** error)
{
WpSpaJson **res_section = (WpSpaJson **)data;
g_autoptr (WpSpaJson) json = NULL;
gboolean override;
g_return_val_if_fail (name, NULL);
g_return_val_if_fail (res_section, -EINVAL);
g_autoptr (WpConf) self = wp_conf_new (name, properties);
if (!wp_conf_open (self, error))
return NULL;
return g_steal_pointer (&self);
}
override = g_str_has_prefix (section, OVERRIDE_SECTION_PREFIX);
if (override)
section += strlen (OVERRIDE_SECTION_PREFIX);
static gboolean
open_and_load_sections (WpConf * self, const gchar *path, GError ** error)
{
g_autoptr (GMappedFile) file = g_mapped_file_new (path, FALSE, error);
if (!file)
return FALSE;
wp_debug ("loading section %s (override=%d) from %s", section, override,
location);
g_autoptr (WpSpaJson) json = wp_spa_json_new_wrap_stringn (
g_mapped_file_get_contents (file), g_mapped_file_get_length (file));
g_autoptr (WpSpaJsonParser) parser = wp_spa_json_parser_new_undefined (json);
g_autoptr (GArray) sections = g_array_new (FALSE, FALSE, sizeof (WpConfSection));
/* Only allow sections to be objects or arrays */
json = wp_spa_json_new_wrap_stringn (str, len);
if (!wp_spa_json_is_container (json)) {
wp_warning (
"skipping section %s from %s as it is not JSON object or array",
section, location);
return 0;
}
g_array_set_clear_func (sections, (GDestroyNotify) wp_conf_section_clear);
/* Merge section if it was defined previously and the 'override.' prefix is
* not used */
if (!override && *res_section) {
g_autoptr (WpSpaJson) merged =
wp_json_utils_merge_containers (*res_section, json);
if (!merged) {
wp_warning (
"skipping merge of %s from %s as JSON values are not compatible",
section, location);
return 0;
while (TRUE) {
g_auto (WpConfSection) section = { 0, };
g_autoptr (WpSpaJson) tmp = NULL;
/* parse the section name */
tmp = wp_spa_json_parser_get_json (parser);
if (!tmp)
break;
if (wp_spa_json_is_container (tmp) ||
wp_spa_json_is_int (tmp) ||
wp_spa_json_is_float (tmp) ||
wp_spa_json_is_boolean (tmp) ||
wp_spa_json_is_null (tmp))
{
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"invalid section name (not a string): %.*s",
(int) wp_spa_json_get_size (tmp), wp_spa_json_get_data (tmp));
return FALSE;
}
g_clear_pointer (res_section, wp_spa_json_unref);
*res_section = g_steal_pointer (&merged);
wp_debug ("section %s from %s loaded", location, section);
section.name = wp_spa_json_parse_string (tmp);
g_clear_pointer (&tmp, wp_spa_json_unref);
/* parse the section contents */
tmp = wp_spa_json_parser_get_json (parser);
if (!tmp) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"section '%s' has no value", section.name);
return FALSE;
}
section.value = g_steal_pointer (&tmp);
section.location = g_strdup (path);
g_array_append_val (sections, section);
memset (&section, 0, sizeof (section));
}
/* Otherwise always replace */
else {
g_clear_pointer (res_section, wp_spa_json_unref);
*res_section = g_steal_pointer (&json);
wp_debug ("section %s from %s loaded", location, section);
}
/* store the mapped file and the sections; note that the stored WpSpaJson
still point to the data in the GMappedFile, so this is why we keep the
GMappedFile alive */
g_ptr_array_add (self->files, g_steal_pointer (&file));
g_array_append_vals (self->conf_sections, sections->data, sections->len);
g_array_set_clear_func (sections, NULL);
return 0;
return TRUE;
}
static void
ensure_section_loaded (WpConf *self, const gchar *section)
/*!
* \brief Opens the configuration file and its fragments and keeps them
* mapped in memory for further access.
*
* \ingroup wpconf
* \param self the configuration
* \param error (out)(nullable): return location for a GError, or NULL
* \returns TRUE on success, FALSE on error
*/
gboolean
wp_conf_open (WpConf * self, GError ** error)
{
g_autoptr (WpCore) core = NULL;
struct pw_context *pw_ctx = NULL;
g_autoptr (WpSpaJson) json_section = NULL;
g_autofree gchar *override_section = NULL;
g_return_val_if_fail (WP_IS_CONF (self), FALSE);
if (g_hash_table_contains (self->sections, section))
return;
g_autofree gchar *path = NULL;
g_autoptr (WpIterator) iterator = NULL;
g_auto (GValue) value = G_VALUE_INIT;
core = g_weak_ref_get (&self->core);
g_return_if_fail (core);
pw_ctx = wp_core_get_pw_context (core);
g_return_if_fail (pw_ctx);
/* open the main file */
path = wp_base_dirs_find_file (WP_BASE_DIRS_CONFIGURATION, NULL, self->name);
if (path) {
wp_info_object (self, "opening main file: %s", path);
if (!open_and_load_sections (self, path, error))
return FALSE;
}
g_clear_pointer (&path, g_free);
pw_context_conf_section_for_each (pw_ctx, section, merge_section_cb,
&json_section);
override_section = g_strdup_printf (OVERRIDE_SECTION_PREFIX "%s", section);
pw_context_conf_section_for_each (pw_ctx, override_section, merge_section_cb,
&json_section);
/* open the .conf.d/ fragments */
path = g_strdup_printf ("%s.d", self->name);
iterator = wp_base_dirs_new_files_iterator (WP_BASE_DIRS_CONFIGURATION, path,
".conf");
if (json_section)
g_hash_table_insert (self->sections, g_strdup (section),
g_steal_pointer (&json_section));
for (; wp_iterator_next (iterator, &value); g_value_unset (&value)) {
const gchar *filename = g_value_get_string (&value);
wp_info_object (self, "opening fragment file: %s", filename);
g_autoptr (GError) e = NULL;
if (!open_and_load_sections (self, filename, &e)) {
wp_warning_object (self, "failed to open '%s': %s", filename, e->message);
continue;
}
}
if (self->files->len == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Could not locate configuration file '%s'", self->name);
return FALSE;
}
return TRUE;
}
/*!
* \brief Closes the configuration file and its fragments
*
* \ingroup wpconf
* \param self the configuration
*/
void
wp_conf_close (WpConf * self)
{
g_return_if_fail (WP_IS_CONF (self));
g_array_set_size (self->conf_sections, 0);
g_ptr_array_set_size (self->files, 0);
}
/*!
* \brief Tests if the configuration files are open
*
* \ingroup wpconf
* \param self the configuration
* \returns TRUE if the configuration files are open, FALSE otherwise
*/
gboolean
wp_conf_is_open (WpConf * self)
{
g_return_val_if_fail (WP_IS_CONF (self), FALSE);
return self->files->len > 0;
}
/*!
* \brief Gets the name of the configuration file
*
* \ingroup wpconf
* \param self the configuration
* \returns the name of the configuration file
*/
const gchar *
wp_conf_get_name (WpConf * self)
{
g_return_val_if_fail (WP_IS_CONF (self), NULL);
return self->name;
}
static WpSpaJson *
ensure_merged_section (WpConf * self, const gchar *section)
{
g_autoptr (WpSpaJson) merged = NULL;
WpConfSection *merged_section = NULL;
/* check if the section is already merged */
for (guint i = 0; i < self->conf_sections->len; i++) {
WpConfSection *s = &g_array_index (self->conf_sections, WpConfSection, i);
if (g_str_equal (s->name, section)) {
if (!s->location) {
wp_debug_object (self, "section %s is already merged", section);
return wp_spa_json_ref (s->value);
}
}
}
/* Iterate over the sections and merge them */
for (guint i = 0; i < self->conf_sections->len; i++) {
WpConfSection *s = &g_array_index (self->conf_sections, WpConfSection, i);
const gchar *s_name = s->name;
/* skip the "override." prefix and take a note */
gboolean override = g_str_has_prefix (s_name, OVERRIDE_SECTION_PREFIX);
if (override)
s_name += strlen (OVERRIDE_SECTION_PREFIX);
if (g_str_equal (s_name, section)) {
/* Merge sections if a previous value exists and
the 'override.' prefix is not present */
if (!override && merged) {
g_autoptr (WpSpaJson) new_merged =
wp_json_utils_merge_containers (merged, s->value);
if (!merged) {
wp_warning_object (self,
"skipping merge of '%s' from '%s' as JSON containers are not compatible",
section, s->location);
continue;
}
g_clear_pointer (&merged, wp_spa_json_unref);
merged = g_steal_pointer (&new_merged);
merged_section = NULL;
}
/* Otherwise always replace */
else {
g_clear_pointer (&merged, wp_spa_json_unref);
merged = wp_spa_json_ref (s->value);
merged_section = s;
}
}
}
/* cache the result */
if (merged_section) {
/* if the merged json came from a single location, just clear
the location from that WpConfSection to mark it as the result */
wp_info_object (self, "section '%s' is used as-is from '%s'", section,
merged_section->location);
g_clear_pointer (&merged_section->location, g_free);
} else if (merged) {
/* if the merged json came from multiple locations, create a new
WpConfSection to store it */
WpConfSection s = { g_strdup (section), wp_spa_json_ref (merged), NULL };
g_array_append_val (self->conf_sections, s);
wp_info_object (self, "section '%s' is merged from multiple locations",
section);
} else {
wp_info_object (self, "section '%s' is not defined", section);
}
return g_steal_pointer (&merged);
}
/*!
@@ -235,18 +419,16 @@ ensure_section_loaded (WpConf *self, const gchar *section)
WpSpaJson *
wp_conf_get_section (WpConf *self, const gchar *section, WpSpaJson *fallback)
{
WpSpaJson *s;
g_autoptr (WpSpaJson) s = NULL;
g_autoptr (WpSpaJson) fb = fallback;
g_return_val_if_fail (WP_IS_CONF (self), NULL);
ensure_section_loaded (self, section);
s = g_hash_table_lookup (self->sections, section);
s = ensure_merged_section (self, section);
if (!s)
return fb ? g_steal_pointer (&fb) : NULL;
return wp_spa_json_ref (s);
return g_steal_pointer (&s);
}
/*!
@@ -447,3 +629,88 @@ wp_conf_get_value_string (WpConf *self, const gchar *section,
return_fallback:
return fallback ? g_strdup (fallback) : NULL;
}
/*!
* \brief Updates the given properties with the values of a specific section
* from the configuration.
*
* \ingroup wpconf
* \param self the configuration
* \param section the section name
* \param props the properties to update
* \returns the number of properties updated
*/
gint
wp_conf_section_update_props (WpConf *self, const gchar *section,
WpProperties *props)
{
g_autoptr (WpSpaJson) json = NULL;
g_return_val_if_fail (WP_IS_CONF (self), -1);
g_return_val_if_fail (section, -1);
g_return_val_if_fail (props, -1);
json = wp_conf_get_section (self, section, NULL);
if (!json)
return 0;
return wp_properties_update_from_json (props, json);
}
#include "private/parse-conf-section.c"
/*!
* \brief Parses standard pw_context sections from \a conf
*
* \ingroup wpconf
* \param self the configuration
* \param context the associated pw_context
*/
void
wp_conf_parse_pw_context_sections (WpConf * self, struct pw_context * context)
{
gint res;
WpProperties *conf_wp;
struct pw_properties *conf_pw;
g_return_if_fail (WP_IS_CONF (self));
g_return_if_fail (context);
/* convert needed sections into a pipewire-style conf dictionary */
conf_wp = wp_properties_new ("config.path", "wpconf", NULL);
{
g_autoptr (WpSpaJson) j = wp_conf_get_section (self, "context.spa-libs", NULL);
if (j) {
g_autofree gchar *js = wp_spa_json_parse_string (j);
wp_properties_set (conf_wp, "context.spa-libs", js);
}
}
{
g_autoptr (WpSpaJson) j = wp_conf_get_section (self, "context.modules", NULL);
if (j) {
g_autofree gchar *js = wp_spa_json_parse_string (j);
wp_properties_set (conf_wp, "context.modules", js);
}
}
conf_pw = wp_properties_unref_and_take_pw_properties (conf_wp);
/* parse sections */
if ((res = _pw_context_parse_conf_section (context, conf_pw, "context.spa-libs")) < 0)
goto error;
wp_info_object (self, "parsed %d context.spa-libs items", res);
if ((res = _pw_context_parse_conf_section (context, conf_pw, "context.modules")) < 0)
goto error;
if (res > 0)
wp_info_object (self, "parsed %d context.modules items", res);
else
wp_warning_object (self, "no modules loaded from context.modules");
out:
pw_properties_free (conf_pw);
return;
error:
wp_critical_object (self, "failed to parse pw_context sections: %s",
spa_strerror (res));
goto out;
}

View File

@@ -14,6 +14,8 @@
G_BEGIN_DECLS
struct pw_context;
/*!
* \brief The WpConf GType
* \ingroup wpconf
@@ -24,7 +26,23 @@ WP_API
G_DECLARE_FINAL_TYPE (WpConf, wp_conf, WP, CONF, GObject)
WP_API
WpConf * wp_conf_get_instance (WpCore * core);
WpConf * wp_conf_new (const gchar * name, WpProperties * properties);
WP_API
WpConf * wp_conf_new_open (const gchar * name, WpProperties * properties,
GError ** error);
WP_API
gboolean wp_conf_open (WpConf * self, GError ** error);
WP_API
void wp_conf_close (WpConf * self);
WP_API
gboolean wp_conf_is_open (WpConf * self);
WP_API
const gchar * wp_conf_get_name (WpConf * self);
WP_API
WpSpaJson * wp_conf_get_section (WpConf *self, const gchar *section,
@@ -50,6 +68,14 @@ WP_API
gchar *wp_conf_get_value_string (WpConf *self,
const gchar *section, const gchar *key, const gchar *fallback);
WP_API
gint wp_conf_section_update_props (WpConf * self, const gchar * section,
WpProperties * props);
WP_API
void wp_conf_parse_pw_context_sections (WpConf * self,
struct pw_context * context);
G_END_DECLS
#endif

View File

@@ -96,6 +96,25 @@ wp_loop_source_new (void)
* objects that appear in the registry, making them accessible through
* the WpObjectManager API.
*
* The core is also responsible for loading components, which are defined in
* the main configuration file. Components are loaded when
* WP_CORE_FEATURE_COMPONENTS is activated.
*
* \b Configuration
*
* The main configuration file needs to be created and opened before the core
* is created, using the WpConf API. It is then passed to the core as an
* argument in the constructor.
*
* If a configuration file is not provided, the core will let the underlying
* `pw_context` load its own configuration, based on the rules that apply to
* all pipewire clients (e.g. it respects the `PIPEWIRE_CONFIG_NAME` environment
* variable and loads "client.conf" as a last resort).
*
* If a configuration file is provided, the core does not let the underlying
* `pw_context` load any configuration and instead uses the provided WpConf
* object.
*
* \gproperties
*
* \gproperty{g-main-context, GMainContext *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
@@ -110,6 +129,9 @@ wp_loop_source_new (void)
* \gproperty{pw-core, gpointer (struct pw_core *), G_PARAM_READABLE,
* The pipewire core}
*
* \gproperty{conf, WpConf *, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY,
* The main configuration file}
*
* \gsignals
*
* \par connected
@@ -151,6 +173,9 @@ struct _WpCore
struct spa_hook core_listener;
struct spa_hook proxy_core_listener;
/* the main configuration file */
WpConf *conf;
WpRegistry registry;
GHashTable *async_tasks; // <int seq, GTask*>
};
@@ -161,6 +186,7 @@ enum {
PROP_PROPERTIES,
PROP_PW_CONTEXT,
PROP_PW_CORE,
PROP_CONF,
};
enum {
@@ -287,6 +313,25 @@ wp_core_constructed (GObject *object)
struct pw_properties *p = NULL;
const gchar *str = NULL;
/* use our own configuration file, if specified */
if (self->conf) {
wp_info_object (self, "using configuration file: %s",
wp_conf_get_name (self->conf));
/* ensure we have our very own properties set,
since we are going to modify it */
self->properties = self->properties ?
wp_properties_ensure_unique_owner (self->properties) :
wp_properties_new_empty ();
/* load context.properties */
wp_conf_section_update_props (self->conf, "context.properties",
self->properties);
/* disable loading of a configuration file in pw_context */
wp_properties_set (self->properties, PW_KEY_CONFIG_NAME, "null");
}
/* properties are fully stored in the pw_context, no need to keep a copy */
p = self->properties ?
wp_properties_unref_and_take_pw_properties (self->properties) : NULL;
@@ -304,6 +349,10 @@ wp_core_constructed (GObject *object)
wp_warning ("ignoring invalid log.level in config file: %s", str);
}
/* parse pw_context specific configuration sections */
if (self->conf)
wp_conf_parse_pw_context_sections (self->conf, self->pw_context);
/* Init refcount */
grefcount *rc = pw_context_get_user_data (self->pw_context);
g_return_if_fail (rc);
@@ -345,6 +394,7 @@ wp_core_finalize (GObject * obj)
g_clear_pointer (&self->properties, wp_properties_unref);
g_clear_pointer (&self->g_main_context, g_main_context_unref);
g_clear_pointer (&self->async_tasks, g_hash_table_unref);
g_clear_object (&self->conf);
wp_debug_object (self, "WpCore destroyed");
@@ -370,6 +420,9 @@ wp_core_get_property (GObject * object, guint property_id,
case PROP_PW_CORE:
g_value_set_pointer (value, self->pw_core);
break;
case PROP_CONF:
g_value_set_object (value, self->conf);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -392,6 +445,9 @@ wp_core_set_property (GObject * object, guint property_id,
case PROP_PW_CONTEXT:
self->pw_context = g_value_get_pointer (value);
break;
case PROP_CONF:
self->conf = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -541,6 +597,11 @@ wp_core_class_init (WpCoreClass * klass)
g_param_spec_pointer ("pw-core", "pw-core", "The pipewire core",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CONF,
g_param_spec_object ("conf", "conf", "The main configuration file",
WP_TYPE_CONF,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
signals[SIGNAL_CONNECTED] = g_signal_new ("connected",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
@@ -555,16 +616,20 @@ wp_core_class_init (WpCoreClass * klass)
*
* \ingroup wpcore
* \param context (transfer none) (nullable): the GMainContext to use for events
* \param properties (transfer full) (nullable): additional properties, which are
* passed to pw_context_new() and pw_context_connect()
* \param conf (transfer full) (nullable): the main configuration file
* \param properties (transfer full) (nullable): additional properties, which
* are also passed to pw_context_new() and pw_context_connect()
* \returns (transfer full): a new WpCore
*/
WpCore *
wp_core_new (GMainContext *context, WpProperties * properties)
wp_core_new (GMainContext * context, WpConf * conf, WpProperties * properties)
{
g_autoptr (WpConf) c = conf;
g_autoptr (WpProperties) props = properties;
return g_object_new (WP_TYPE_CORE,
"g-main-context", context,
"conf", conf,
"properties", properties,
"pw-context", NULL,
NULL);
@@ -583,6 +648,7 @@ wp_core_clone (WpCore * self)
return g_object_new (WP_TYPE_CORE,
"core", self,
"g-main-context", self->g_main_context,
"conf", self->conf,
"properties", self->properties,
"pw-context", self->pw_context,
NULL);
@@ -618,6 +684,20 @@ wp_core_get_export_core (WpCore * self)
return wp_core_find_object (self, find_export_core, NULL);
}
/*!
* \brief Gets the main configuration file of the core
*
* \ingroup wpcore
* \param self the core
* \returns (transfer full) (nullable): the main configuration file
*/
WpConf *
wp_core_get_conf (WpCore * self)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
return self->conf ? g_object_ref (self->conf) : NULL;
}
/*!
* \brief Gets the GMainContext of the core
*

View File

@@ -12,6 +12,7 @@
#include "object.h"
#include "properties.h"
#include "spa-json.h"
#include "conf.h"
G_BEGIN_DECLS
@@ -41,7 +42,8 @@ G_DECLARE_FINAL_TYPE (WpCore, wp_core, WP, CORE, WpObject)
/* Basic */
WP_API
WpCore * wp_core_new (GMainContext *context, WpProperties * properties);
WpCore * wp_core_new (GMainContext * context, WpConf * conf,
WpProperties * properties);
WP_API
WpCore * wp_core_clone (WpCore * self);
@@ -49,6 +51,9 @@ WpCore * wp_core_clone (WpCore * self);
WP_API
WpCore * wp_core_get_export_core (WpCore * self);
WP_API
WpConf * wp_core_get_conf (WpCore * self);
WP_API
GMainContext * wp_core_get_g_main_context (WpCore * self);

View File

@@ -751,7 +751,7 @@ wp_internal_comp_loader_load (WpComponentLoader * self, WpCore * core,
if (g_str_equal (type, "profile")) {
/* component name is the profile name;
component list and profile features are loaded from config */
g_autoptr (WpConf) conf = wp_conf_get_instance (core);
g_autoptr (WpConf) conf = wp_core_get_conf (core);
g_autoptr (WpSpaJson) profile_json = NULL;
profile_json =

View File

@@ -0,0 +1,158 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
/*
This is a partial copy of functions from libpipewire's conf.c that is meant to
live here temporarily until pw_context_parse_conf_section() is fixed upstream.
See https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/1925
*/
#include <string.h>
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa/utils/cleanup.h>
#include <pipewire/impl.h>
struct data {
struct pw_context *context;
struct pw_properties *props;
int count;
};
/* context.spa-libs = {
* <factory-name regex> = <library-name>
* }
*/
static int parse_spa_libs(void *user_data, const char *location,
const char *section, const char *str, size_t len)
{
struct data *d = user_data;
struct pw_context *context = d->context;
struct spa_json it[2];
char key[512], value[512];
spa_json_init(&it[0], str, len);
if (spa_json_enter_object(&it[0], &it[1]) < 0) {
pw_log_error("config file error: context.spa-libs is not an object");
return -EINVAL;
}
while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
if (spa_json_get_string(&it[1], value, sizeof(value)) > 0) {
pw_context_add_spa_lib(context, key, value);
d->count++;
}
}
return 0;
}
static int load_module(struct pw_context *context, const char *key, const char *args, const char *flags)
{
if (pw_context_load_module(context, key, args, NULL) == NULL) {
if (errno == ENOENT && flags && strstr(flags, "ifexists") != NULL) {
pw_log_info("%p: skipping unavailable module %s",
context, key);
} else if (flags == NULL || strstr(flags, "nofail") == NULL) {
pw_log_error("%p: could not load mandatory module \"%s\": %m",
context, key);
return -errno;
} else {
pw_log_info("%p: could not load optional module \"%s\": %m",
context, key);
}
} else {
pw_log_info("%p: loaded module %s", context, key);
}
return 0;
}
/*
* context.modules = [
* { name = <module-name>
* ( args = { <key> = <value> ... } )
* ( flags = [ ( ifexists ) ( nofail ) ]
* ( condition = [ { key = value, .. } .. ] )
* }
* ]
*/
static int parse_modules(void *user_data, const char *location,
const char *section, const char *str, size_t len)
{
struct data *d = user_data;
struct pw_context *context = d->context;
struct spa_json it[4];
char key[512];
int res = 0;
spa_autofree char *s = strndup(str, len);
spa_json_init(&it[0], s, len);
if (spa_json_enter_array(&it[0], &it[1]) < 0) {
pw_log_error("config file error: context.modules is not an array");
return -EINVAL;
}
while (spa_json_enter_object(&it[1], &it[2]) > 0) {
char *name = NULL, *args = NULL, *flags = NULL;
bool have_match = true;
while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
const char *val;
int len;
if ((len = spa_json_next(&it[2], &val)) <= 0)
break;
if (spa_streq(key, "name")) {
name = (char*)val;
spa_json_parse_stringn(val, len, name, len+1);
} else if (spa_streq(key, "args")) {
if (spa_json_is_container(val, len))
len = spa_json_container_len(&it[2], val, len);
args = (char*)val;
spa_json_parse_stringn(val, len, args, len+1);
} else if (spa_streq(key, "flags")) {
if (spa_json_is_container(val, len))
len = spa_json_container_len(&it[2], val, len);
flags = (char*)val;
spa_json_parse_stringn(val, len, flags, len+1);
}
}
if (!have_match)
continue;
if (name != NULL)
res = load_module(context, name, args, flags);
if (res < 0)
break;
d->count++;
}
return res;
}
static int _pw_context_parse_conf_section(struct pw_context *context,
struct pw_properties *conf, const char *section)
{
struct data data = { .context = context };
int res;
if (spa_streq(section, "context.spa-libs"))
res = pw_conf_section_for_each(&conf->dict, section,
parse_spa_libs, &data);
else if (spa_streq(section, "context.modules"))
res = pw_conf_section_for_each(&conf->dict, section,
parse_modules, &data);
else
res = -EINVAL;
return res == 0 ? data.count : res;
}

View File

@@ -1621,12 +1621,15 @@ impl_module_new (lua_State *L)
static int
conf_get_section (lua_State *L)
{
g_autoptr (WpConf) conf = wp_conf_get_instance (get_wp_core (L));
g_autoptr (WpConf) conf = wp_core_get_conf (get_wp_core (L));
const char *section;
g_autoptr (WpSpaJson) fb = NULL;
g_autoptr (WpSpaJson) s = NULL;
g_return_val_if_fail (conf, 0);
if (!conf) {
lua_pushnil (L);
return 1;
}
section = luaL_checkstring (L, 1);
if (lua_isuserdata (L, 2)) {
@@ -1646,13 +1649,16 @@ conf_get_section (lua_State *L)
static int
conf_get_value (lua_State *L)
{
g_autoptr (WpConf) conf = wp_conf_get_instance (get_wp_core (L));
g_autoptr (WpConf) conf = wp_core_get_conf (get_wp_core (L));
const char *section;
const char *key;
g_autoptr (WpSpaJson) fb = NULL;
g_autoptr (WpSpaJson) v = NULL;
g_return_val_if_fail (conf, 0);
if (!conf) {
lua_pushnil (L);
return 1;
}
section = luaL_checkstring (L, 1);
key = luaL_checkstring (L, 2);
@@ -1673,12 +1679,15 @@ conf_get_value (lua_State *L)
static int
conf_get_value_boolean (lua_State *L)
{
g_autoptr (WpConf) conf = wp_conf_get_instance (get_wp_core (L));
g_autoptr (WpConf) conf = wp_core_get_conf (get_wp_core (L));
const char *section;
const char *key;
gboolean fb;
g_return_val_if_fail (conf, 0);
if (!conf) {
lua_pushnil (L);
return 1;
}
section = luaL_checkstring (L, 1);
key = luaL_checkstring (L, 2);
@@ -1691,12 +1700,15 @@ conf_get_value_boolean (lua_State *L)
static int
conf_get_value_int (lua_State *L)
{
g_autoptr (WpConf) conf = wp_conf_get_instance (get_wp_core (L));
g_autoptr (WpConf) conf = wp_core_get_conf (get_wp_core (L));
const char *section;
const char *key;
gint fb;
g_return_val_if_fail (conf, 0);
if (!conf) {
lua_pushnil (L);
return 1;
}
section = luaL_checkstring (L, 1);
key = luaL_checkstring (L, 2);
@@ -1709,12 +1721,15 @@ conf_get_value_int (lua_State *L)
static int
conf_get_value_float (lua_State *L)
{
g_autoptr (WpConf) conf = wp_conf_get_instance (get_wp_core (L));
g_autoptr (WpConf) conf = wp_core_get_conf (get_wp_core (L));
const char *section;
const char *key;
float fb;
g_return_val_if_fail (conf, 0);
if (!conf) {
lua_pushnil (L);
return 1;
}
section = luaL_checkstring (L, 1);
key = luaL_checkstring (L, 2);
@@ -1727,13 +1742,16 @@ conf_get_value_float (lua_State *L)
static int
conf_get_value_string (lua_State *L)
{
g_autoptr (WpConf) conf = wp_conf_get_instance (get_wp_core (L));
g_autoptr (WpConf) conf = wp_core_get_conf (get_wp_core (L));
const char *section;
const char *key;
const char *fb;
g_autofree gchar *str = NULL;
g_return_val_if_fail (conf, 0);
if (!conf) {
lua_pushnil (L);
return 1;
}
section = luaL_checkstring (L, 1);
key = luaL_checkstring (L, 2);

View File

@@ -86,7 +86,7 @@ load_configuration_settings (WpSettingsPlugin *self)
g_autoptr (WpProperties) res = wp_properties_new_empty ();
g_return_val_if_fail (core, NULL);
conf = wp_conf_get_instance (core);
conf = wp_core_get_conf (core);
g_return_val_if_fail (conf, NULL);
json = wp_conf_get_section (conf, "wireplumber.settings", NULL);
@@ -254,7 +254,7 @@ on_schema_metadata_activated (WpMetadata * m, GAsyncResult * res,
WpTransition *transition = WP_TRANSITION (user_data);
WpSettingsPlugin *self = wp_transition_get_source_object (transition);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
g_autoptr (WpConf) conf = wp_conf_get_instance (core);
g_autoptr (WpConf) conf = wp_core_get_conf (core);
g_autoptr (GError) error = NULL;
g_autoptr (WpSpaJson) schema_json = NULL;

View File

@@ -32,7 +32,7 @@ static GOptionEntry entries[] =
{ "version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &show_version,
"Show version", NULL },
{ "config-file", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &config_file,
"The context configuration file", NULL },
"The configuration file to use", NULL },
{ "profile", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &profile,
"The profile to load", NULL },
{ NULL }
@@ -129,7 +129,7 @@ main (gint argc, gchar **argv)
g_autoptr (GOptionContext) context = NULL;
g_autoptr (GError) error = NULL;
g_autoptr (WpProperties) properties = NULL;
const gchar *conf_env;
g_autoptr (WpConf) conf = NULL;
setlocale (LC_ALL, "");
setlocale (LC_NUMERIC, "C");
@@ -157,13 +157,15 @@ main (gint argc, gchar **argv)
if (!profile)
profile = "main";
/* Forward WIREPLUMBER_CONFIG_DIR to PIPEWIRE_CONFIG_DIR */
conf_env = g_getenv ("WIREPLUMBER_CONFIG_DIR");
if (conf_env)
g_setenv ("PIPEWIRE_CONFIG_DIR", conf_env, TRUE);
/* load configuration */
conf = wp_conf_new_open (config_file, NULL, &error);
if (!conf) {
fprintf (stderr, "Failed to load configuration: %s\n", error->message);
return WP_EXIT_CONFIG;
}
/* prepare core properties */
properties = wp_properties_new (
PW_KEY_CONFIG_NAME, config_file,
PW_KEY_APP_NAME, "WirePlumber",
"wireplumber.daemon", "true",
"wireplumber.profile", profile,
@@ -176,7 +178,8 @@ main (gint argc, gchar **argv)
/* init wireplumber daemon */
d.loop = g_main_loop_new (NULL, FALSE);
d.core = wp_core_new (NULL, g_steal_pointer (&properties));
d.core = wp_core_new (NULL, g_steal_pointer (&conf),
g_steal_pointer (&properties));
g_signal_connect (d.core, "disconnected", G_CALLBACK (on_disconnected), &d);
/* watch for exit signals */

View File

@@ -1888,7 +1888,7 @@ main (gint argc, gchar **argv)
ctl.context = g_option_context_new (
"COMMAND [COMMAND_OPTIONS] - WirePlumber Control CLI");
ctl.loop = g_main_loop_new (NULL, FALSE);
ctl.core = wp_core_new (NULL, NULL);
ctl.core = wp_core_new (NULL, NULL, NULL);
ctl.om = wp_object_manager_new ();
/* find the subcommand */

View File

@@ -232,7 +232,7 @@ main (gint argc, gchar **argv)
/* init wireplumber core */
d.loop = g_main_loop_new (NULL, FALSE);
d.core = wp_core_new (NULL, wp_properties_new (
d.core = wp_core_new (NULL, NULL, wp_properties_new (
PW_KEY_APP_NAME, "wpexec",
NULL));
g_signal_connect_swapped (d.core, "disconnected",

View File

@@ -72,6 +72,7 @@ static void
wp_base_test_fixture_setup (WpBaseTestFixture * self, WpBaseTestFlags flags)
{
g_autoptr (WpProperties) props = NULL;
g_autoptr (WpConf) conf = NULL;
/* init test server */
wp_test_server_setup (&self->server);
@@ -90,10 +91,15 @@ wp_base_test_fixture_setup (WpBaseTestFixture * self, WpBaseTestFlags flags)
/* init our core */
props = wp_properties_new (PW_KEY_REMOTE_NAME, self->server.name, NULL);
if (self->conf_file)
wp_properties_set (props, PW_KEY_CONFIG_NAME, self->conf_file);
if (self->conf_file) {
g_autoptr (GError) error = NULL;
conf = wp_conf_new_open (self->conf_file, NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (conf);
}
self->core = wp_core_new (self->context, wp_properties_ref (props));
self->core = wp_core_new (self->context, g_steal_pointer (&conf),
wp_properties_ref (props));
g_assert_true (self->core);
g_signal_connect (self->core, "disconnected",
@@ -108,7 +114,8 @@ wp_base_test_fixture_setup (WpBaseTestFixture * self, WpBaseTestFlags flags)
/* init the second client's core */
if (flags & WP_BASE_TEST_FLAG_CLIENT_CORE) {
self->client_core = wp_core_new (self->context, wp_properties_ref (props));
self->client_core = wp_core_new (self->context, NULL,
wp_properties_ref (props));
g_signal_connect (self->client_core, "disconnected",
(GCallback) disconnected_callback, self);

View File

@@ -5,27 +5,28 @@
*
* SPDX-License-Identifier: MIT
*/
#include "../common/base-test-fixture.h"
#include "../common/test-log.h"
typedef struct {
WpBaseTestFixture base;
WpConf *conf;
} TestConfFixture;
static void
test_conf_setup (TestConfFixture *self, gconstpointer user_data)
{
self->base.conf_file =
g_autoptr (GError) error = NULL;
g_autofree gchar *file =
g_strdup_printf ("%s/conf/wireplumber.conf", g_getenv ("G_TEST_SRCDIR"));
wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
self->conf = wp_conf_get_instance (self->base.core);
self->conf = wp_conf_new_open (file, NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (self->conf);
}
static void
test_conf_teardown (TestConfFixture *self, gconstpointer user_data)
{
g_clear_object (&self->conf);
wp_base_test_fixture_teardown (&self->base);
}
static void

View File

@@ -73,7 +73,7 @@ test_parsing_setup (TestSettingsFixture *self, gconstpointer user_data)
{
test_conf_file_setup (self, user_data);
g_autoptr (WpConf) conf = wp_conf_get_instance (self->base.core);
g_autoptr (WpConf) conf = wp_core_get_conf (self->base.core);
g_assert_nonnull (conf);
{