tools: add pw-config

Add a tool to debug how config files are loaded and merged.
This commit is contained in:
Wim Taymans 2023-04-19 18:06:22 +02:00
parent 543965a8c3
commit d1aeb8144b
2 changed files with 229 additions and 0 deletions

View file

@ -1,5 +1,6 @@
tools_sources = [
[ 'pw-mon', [ 'pw-mon.c' ] ],
[ 'pw-config', [ 'pw-config.c' ] ],
[ 'pw-dot', [ 'pw-dot.c' ] ],
[ 'pw-dump', [ 'pw-dump.c' ] ],
[ 'pw-profiler', [ 'pw-profiler.c' ] ],

228
src/tools/pw-config.c Normal file
View file

@ -0,0 +1,228 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#include "config.h"
#include <getopt.h>
#include <signal.h>
#include <locale.h>
#include <spa/utils/result.h>
#include <spa/utils/json.h>
#include "pipewire/pipewire.h"
#include "pipewire/log.h"
#define DEFAULT_NAME "pipewire.conf"
#define DEFAULT_PREFIX ""
struct data {
const char *opt_name;
const char *opt_prefix;
const char *opt_cmd;
bool opt_recurse;
bool opt_newline;
struct pw_properties *conf;
struct pw_properties *assemble;
int count;
bool array;
};
static void print_all_properties(struct data *d, struct pw_properties *props)
{
pw_properties_serialize_dict(stdout,
&props->dict,
(d->opt_newline ? PW_PROPERTIES_FLAG_NL : 0) |
(d->opt_recurse ? PW_PROPERTIES_FLAG_RECURSE : 0) |
(d->array ? PW_PROPERTIES_FLAG_ARRAY : 0) |
PW_PROPERTIES_FLAG_ENCLOSE);
fprintf(stdout, "\n");
}
static void list_paths(struct data *d)
{
const struct spa_dict_item *it;
spa_dict_for_each(it, &d->conf->dict) {
if (spa_strstartswith(it->key, "config.path")) {
pw_properties_set(d->assemble, it->key, it->value);
}
if (spa_strstartswith(it->key, "override.") &&
spa_strendswith(it->key, ".config.path")) {
pw_properties_set(d->assemble, it->key, it->value);
}
}
}
static int do_merge_section(void *data, const char *location, const char *section,
const char *str, size_t len)
{
struct data *d = data;
struct spa_json it[2];
int l;
const char *value;
spa_json_init(&it[0], str, len);
if ((l = spa_json_next(&it[0], &value)) <= 0)
return 0;
if (spa_json_is_array(value, l)) {
char key[128];
spa_json_enter(&it[0], &it[1]);
while ((l = spa_json_next(&it[1], &value)) > 0) {
if (spa_json_is_container(value, l))
l = spa_json_container_len(&it[1], value, l);
snprintf(key, sizeof(key), "%d-%s", d->count++, location);
pw_properties_setf(d->assemble, key, "%.*s", l, value);
}
d->array = true;
}
else if (spa_json_is_object(value, l)) {
pw_properties_update_string(d->assemble, str, len);
}
return 0;
}
static int do_list_section(void *data, const char *location, const char *section,
const char *str, size_t len)
{
struct data *d = data;
char key[128];
snprintf(key, sizeof(key), "%d-%s", d->count++, location);
pw_properties_setf(d->assemble, key, "%.*s", (int)len, str);
return 0;
}
static void section_for_each(struct data *d, const char *section,
int (*callback) (void *data, const char *location, const char *section,
const char *str, size_t len))
{
const char *str;
char key[128];
pw_conf_section_for_each(&d->conf->dict, section, callback, d);
str = pw_properties_get(d->assemble, "config.ext");
if (str != NULL) {
snprintf(key, sizeof(key), "%s.%s", section, str);
pw_conf_section_for_each(&d->conf->dict, key, callback, d);
}
}
static void show_help(const char *name, bool error)
{
fprintf(error ? stderr : stdout, "%1$s : PipeWire config manager.\n"
"Usage:\n"
" %1$s [options] paths List config paths (default action)\n"
" %1$s [options] list [SECTION] List config section\n"
" %1$s [options] merge SECTION Merge a config section\n\n"
"Options:\n"
" -h, --help Show this help\n"
" --version Show version\n"
" -n, --name Config Name (default '%2$s')\n"
" -p, --prefix Config Prefix (default '%3$s')\n"
" -L, --no-newline Omit newline after values\n"
" -r, --recurse Dump values recursively\n",
name, DEFAULT_NAME, DEFAULT_PREFIX);
}
int main(int argc, char *argv[])
{
struct data d = { 0, };
struct pw_properties *props = NULL;
int res = 0, c;
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "name", required_argument, NULL, 'n' },
{ "prefix", required_argument, NULL, 'p' },
{ "no-newline", no_argument, NULL, 'L' },
{ "recurse", no_argument, NULL, 'r' },
{ NULL, 0, NULL, 0}
};
d.opt_name = DEFAULT_NAME;
d.opt_prefix = NULL;
d.opt_recurse = false;
d.opt_newline = true;
d.opt_cmd = "paths";
pw_init(&argc, &argv);
while ((c = getopt_long(argc, argv, "hVn:p:Lr", long_options, NULL)) != -1) {
switch (c) {
case 'h':
show_help(argv[0], false);
return 0;
case 'V':
printf("%s\n"
"Compiled with libpipewire %s\n"
"Linked with libpipewire %s\n",
argv[0],
pw_get_headers_version(),
pw_get_library_version());
return 0;
case 'n':
d.opt_name = optarg;
break;
case 'p':
d.opt_prefix = optarg;
break;
case 'L':
d.opt_newline = false;
break;
case 'r':
d.opt_recurse = true;
break;
default:
show_help(argv[0], true);
return -1;
}
}
if (optind < argc)
d.opt_cmd = argv[optind++];
props = pw_properties_new(
PW_KEY_CONFIG_NAME, d.opt_name,
PW_KEY_CONFIG_PREFIX, d.opt_prefix,
NULL);
d.conf = pw_properties_new(NULL, NULL);
if ((res = pw_conf_load_conf_for_context (props, d.conf)) < 0) {
fprintf(stderr, "error loading config: %s\n", spa_strerror(res));
goto done;
}
d.assemble = pw_properties_new(NULL, NULL);
if (spa_streq(d.opt_cmd, "paths")) {
list_paths(&d);
}
else if (spa_streq(d.opt_cmd, "list")) {
if (optind == argc) {
pw_properties_update(d.assemble, &d.conf->dict);
} else {
section_for_each(&d, argv[optind++], do_list_section);
}
}
else if (spa_streq(d.opt_cmd, "merge")) {
if (optind == argc) {
fprintf(stderr, "%s requires a section\n", d.opt_cmd);
res = -EINVAL;
goto done;
}
section_for_each(&d, argv[optind++], do_merge_section);
}
print_all_properties(&d, d.assemble);
done:
pw_properties_free(d.conf);
pw_properties_free(d.assemble);
pw_properties_free(props);
pw_deinit();
return res;
}