daemon: rework config parsing

Replace config parsing for something more flexible based on json.
This commit is contained in:
Wim Taymans 2020-12-31 16:42:41 +01:00
parent 1bd90dc666
commit 49d11acde0
7 changed files with 458 additions and 905 deletions

View file

@ -1,445 +0,0 @@
/* PipeWire
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
* @author Linus Svensson <linus.svensson@axis.com>
* Copyright © 2018 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <sys/wait.h>
#include <pipewire/impl.h>
#include "command.h"
/** \cond */
static struct pw_command *parse_command_help(struct pw_properties *properties, const char *line, char **err);
static struct pw_command *parse_command_set_prop(struct pw_properties *properties, const char *line, char **err);
static struct pw_command *parse_command_add_spa_lib(struct pw_properties *properties, const char *line, char **err);
static struct pw_command *parse_command_module_load(struct pw_properties *properties, const char *line, char **err);
static struct pw_command *parse_command_create_object(struct pw_properties *properties, const char *line, char **err);
static struct pw_command *parse_command_exec(struct pw_properties *properties, const char *line, char **err);
struct impl {
struct pw_command this;
int first_arg;
};
typedef struct pw_command *(*pw_command_parse_func_t) (struct pw_properties *properties, const char *line, char **err);
struct command_parse {
const char *name;
const char *description;
pw_command_parse_func_t func;
};
static const struct command_parse parsers[] = {
{"help", "Show this help", parse_command_help},
{"set-prop", "Set a property", parse_command_set_prop},
{"add-spa-lib", "Add a library that provides a spa factory name regex", parse_command_add_spa_lib},
{"load-module", "Load a module", parse_command_module_load},
{"create-object", "Create an object from a factory", parse_command_create_object},
{"exec", "Execute a program", parse_command_exec},
{NULL, NULL, NULL }
};
static const char whitespace[] = " \t";
/** \endcond */
static int
execute_command_help(struct pw_command *command, struct pw_context *context, char **err)
{
int i;
fputs("Available commands:\n", stdout);
for (i = 0; parsers[i].name; i++)
fprintf(stdout, " %20.20s\t%s\n", parsers[i].name, parsers[i].description);
return 0;
}
static struct pw_command *parse_command_help(struct pw_properties *properties, const char *line, char **err)
{
struct impl *impl;
struct pw_command *this;
impl = calloc(1, sizeof(struct impl));
if (impl == NULL)
goto no_mem;
this = &impl->this;
this->func = execute_command_help;
this->args = pw_split_strv(line, whitespace, 1, &this->n_args);
return this;
no_mem:
*err = spa_aprintf("alloc failed: %m");
return NULL;
}
static int
execute_command_set_prop(struct pw_command *command, struct pw_context *context, char **err)
{
return 0;
}
static struct pw_command *parse_command_set_prop(struct pw_properties *properties, const char *line, char **err)
{
struct impl *impl;
struct pw_command *this;
impl = calloc(1, sizeof(struct impl));
if (impl == NULL)
goto error_alloc;
this = &impl->this;
this->func = execute_command_set_prop;
this->args = pw_split_strv(line, whitespace, 4, &this->n_args);
if (this->n_args < 3)
goto error_arguments;
pw_log_debug("set property: '%s' = '%s'", this->args[1], this->args[2]);
pw_properties_set(properties, this->args[1], this->args[2]);
if (strcmp(this->args[1], SPA_KEY_LOG_LEVEL) == 0) {
pw_log_set_level(atoi(this->args[2]));
setenv("PIPEWIRE_DEBUG", this->args[2], 1);
}
return this;
error_arguments:
*err = spa_aprintf("%s requires <property-name> <value>", this->args[0]);
pw_free_strv(this->args);
free(impl);
return NULL;
error_alloc:
*err = spa_aprintf("alloc failed: %m");
return NULL;
}
static int
execute_command_add_spa_lib(struct pw_command *command, struct pw_context *context, char **err)
{
int res;
res = pw_context_add_spa_lib(context, command->args[1], command->args[2]);
if (res < 0) {
*err = spa_aprintf("could not add spa library \"%s\"", command->args[1]);
return res;
}
return 0;
}
static struct pw_command *parse_command_add_spa_lib(struct pw_properties *properties, const char *line, char **err)
{
struct impl *impl;
struct pw_command *this;
impl = calloc(1, sizeof(struct impl));
if (impl == NULL)
goto no_mem;
this = &impl->this;
this->func = execute_command_add_spa_lib;
this->args = pw_split_strv(line, whitespace, 4, &this->n_args);
if (this->n_args < 3)
goto no_library;
return this;
no_library:
*err = spa_aprintf("%s requires <factory-regex> <library-name>", this->args[0]);
pw_free_strv(this->args);
free(impl);
return NULL;
no_mem:
*err = spa_aprintf("alloc failed: %m");
return NULL;
}
static bool has_option(struct pw_command *this, int first_arg, const char *option)
{
int arg;
for (arg = 1; arg < first_arg; arg++) {
if (strstr(this->args[arg], "-") == this->args[arg]) {
if (strcmp(this->args[arg], option) == 0)
return true;
}
}
return false;
}
static int
execute_command_module_load(struct pw_command *command, struct pw_context *context, char **err)
{
struct pw_impl_module *module;
struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
int arg = impl->first_arg;
module = pw_context_load_module(context, command->args[arg], command->args[arg+1], NULL);
if (module == NULL) {
if (errno == ENOENT && has_option(command, arg, "-ifexists")) {
pw_log_debug("skipping unavailable module %s", command->args[arg]);
return 0;
}
*err = spa_aprintf("could not load module \"%s\": %m", command->args[arg]);
return -errno;
}
return 0;
}
static struct pw_command *parse_command_module_load(struct pw_properties *properties, const char *line, char **err)
{
struct impl *impl;
struct pw_command *this;
int arg;
impl = calloc(1, sizeof(struct impl));
if (impl == NULL)
goto no_mem;
this = &impl->this;
this->func = execute_command_module_load;
this->args = pw_split_strv(line, whitespace, INT_MAX, &this->n_args);
for (arg = 1; arg < this->n_args; arg++) {
if (strstr(this->args[arg], "-") != this->args[arg])
break;
}
if (arg + 1 > this->n_args)
goto no_module;
pw_free_strv(this->args);
this->args = pw_split_strv(line, whitespace, arg + 2, &this->n_args);
impl->first_arg = arg;
return this;
no_module:
*err = spa_aprintf("%s requires a module name", this->args[0]);
pw_free_strv(this->args);
free(impl);
return NULL;
no_mem:
*err = spa_aprintf("alloc failed: %m");
return NULL;
}
static int
execute_command_create_object(struct pw_command *command, struct pw_context *context, char **err)
{
struct pw_impl_factory *factory;
struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
int arg = impl->first_arg;
void *obj;
pw_log_debug("find factory %s", command->args[arg]);
factory = pw_context_find_factory(context, command->args[arg]);
if (factory == NULL) {
if (has_option(command, arg, "-nofail"))
return 0;
pw_log_error("can't find factory %s", command->args[arg]);
return -ENOENT;
}
pw_log_debug("create object with args %s", command->args[arg+1]);
obj = pw_impl_factory_create_object(factory,
NULL, NULL, 0,
pw_properties_new_string(command->args[arg+1]),
SPA_ID_INVALID);
if (obj == NULL) {
if (has_option(command, arg, "-nofail"))
return 0;
pw_log_error("can't create object from factory %s: %m", command->args[arg]);
return -errno;
}
return 0;
}
static struct pw_command *parse_command_create_object(struct pw_properties *properties, const char *line, char **err)
{
struct impl *impl;
struct pw_command *this;
int arg;
impl = calloc(1, sizeof(struct impl));
if (impl == NULL)
goto no_mem;
this = &impl->this;
this->func = execute_command_create_object;
this->args = pw_split_strv(line, whitespace, INT_MAX, &this->n_args);
for (arg = 1; arg < this->n_args; arg++) {
if (strstr(this->args[arg], "-") != this->args[arg])
break;
}
if (arg > this->n_args)
goto no_factory;
pw_free_strv(this->args);
this->args = pw_split_strv(line, whitespace, arg + 2, &this->n_args);
impl->first_arg = arg;
return this;
no_factory:
*err = spa_aprintf("%s requires <factory-name> [<key>=<value> ...]", this->args[0]);
pw_free_strv(this->args);
free(impl);
return NULL;
no_mem:
*err = spa_aprintf("alloc failed: %m");
return NULL;
}
static int
execute_command_exec(struct pw_command *command, struct pw_context *context, char **err)
{
int pid, res;
pid = fork();
if (pid == 0) {
pw_log_info("exec %s", command->args[1]);
res = execvp(command->args[1], &command->args[1]);
if (res == -1) {
res = -errno;
*err = spa_aprintf("'%s': %m", command->args[1]);
return res;
}
}
else {
int status;
res = waitpid(pid, &status, WNOHANG);
pw_log_info("exec got pid %d res:%d status:%d", pid, res, status);
}
return 0;
}
static struct pw_command *parse_command_exec(struct pw_properties *properties, const char *line, char **err)
{
struct impl *impl;
struct pw_command *this;
impl = calloc(1, sizeof(struct impl));
if (impl == NULL)
goto no_mem;
this = &impl->this;
this->func = execute_command_exec;
this->args = pw_split_strv(line, whitespace, INT_MAX, &this->n_args);
if (this->n_args < 1)
goto no_executable;
return this;
no_executable:
*err = spa_aprintf("requires an executable name");
pw_free_strv(this->args);
free(impl);
return NULL;
no_mem:
*err = spa_aprintf("alloc failed: %m");
return NULL;
}
/** Free command
*
* \param command a command to free
*
* Free all resources assicated with \a command.
*
* \memberof pw_command
*/
SPA_EXPORT
void pw_command_free(struct pw_command *command)
{
struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this);
spa_list_remove(&command->link);
pw_free_strv(command->args);
free(impl);
}
/** Parses a command line
* \param line command line to parse
* \param[out] err Return location for an error
* \return The command or NULL when \a err is set.
*
* Parses a command line, \a line, and return the parsed command.
* A command can later be executed with \ref pw_command_run()
*
* \memberof pw_command
*/
SPA_EXPORT
struct pw_command *pw_command_parse(struct pw_properties *properties, const char *line, char **err)
{
struct pw_command *command = NULL;
const struct command_parse *parse;
char *name;
size_t len;
len = strcspn(line, whitespace);
name = strndup(line, len);
for (parse = parsers; parse->name != NULL; parse++) {
if (strcmp(name, parse->name) == 0) {
command = parse->func(properties, line, err);
goto out;
}
}
*err = spa_aprintf("Command \"%s\" does not exist", name);
out:
free(name);
return command;
}
/** Run a command
*
* \param command A \ref pw_command
* \param context A \ref pw_context
* \param err Return location for an error string, or NULL
* \return 0 on success, < 0 on error
*
* \memberof pw_command
*/
SPA_EXPORT
int pw_command_run(struct pw_command *command, struct pw_context *context, char **err)
{
return command->func(command, context, err);
}

View file

@ -1,63 +0,0 @@
/* PipeWire
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
* @author Linus Svensson <linus.svensson@axis.com>
* Copyright © 2018 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef PIPEWIRE_COMMAND_H
#define PIPEWIRE_COMMAND_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pipewire/context.h>
struct pw_command;
typedef int (*pw_command_func_t) (struct pw_command *command, struct pw_context *context, char **err);
/** \class pw_command
*
* A configuration command
*/
struct pw_command {
struct spa_list link; /**< link in list of commands */
pw_command_func_t func;
char **args;
uint32_t id; /**< id of command */
int n_args;
};
struct pw_command *
pw_command_parse(struct pw_properties *properties, const char *line, char **err);
void
pw_command_free(struct pw_command *command);
int pw_command_run(struct pw_command *command, struct pw_context *context, char **err);
#ifdef __cplusplus
}
#endif
#endif /* PIPEWIRE_COMMAND_H */

View file

@ -1,212 +0,0 @@
/* PipeWire
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
* @author Linus Svensson <linus.svensson@axis.com>
* Copyright © 2018 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <pipewire/pipewire.h>
#include "daemon/command.h"
#include "daemon/daemon-config.h"
#define DEFAULT_CONFIG_FILE PIPEWIRE_CONFIG_DIR "/pipewire.conf"
static int
parse_line(struct pw_daemon_config *config,
const char *filename, char *line, unsigned int lineno, char **err)
{
struct pw_command *command = NULL;
char *p;
char *local_err = NULL;
/* search for comments */
if ((p = strchr(line, '#')))
*p = '\0';
/* remove whitespaces */
line = pw_strip(line, "\n\r \t");
if (*line == '\0') /* empty line */
goto out;
if ((command = pw_command_parse(config->properties, line, &local_err)) == NULL)
goto error_parse;
spa_list_append(&config->commands, &command->link);
out:
return 0;
error_parse:
*err = spa_aprintf("%s:%u: %s", filename, lineno, local_err);
free(local_err);
return -EINVAL;
}
/**
* pw_daemon_config_new:
*
* Returns a new empty #struct pw_daemon_config.
*/
struct pw_daemon_config *pw_daemon_config_new(struct pw_properties *properties)
{
struct pw_daemon_config *config;
config = calloc(1, sizeof(struct pw_daemon_config));
if (config == NULL)
goto error_exit;
config->properties = properties;
spa_list_init(&config->commands);
return config;
error_exit:
return NULL;
}
/**
* pw_daemon_config_free:
* @config: A #struct pw_daemon_config
*
* Free all resources associated to @config.
*/
void pw_daemon_config_free(struct pw_daemon_config *config)
{
struct pw_command *cmd;
spa_list_consume(cmd, &config->commands, link)
pw_command_free(cmd);
free(config);
}
/**
* pw_daemon_config_load_file:
* @config: A #struct pw_daemon_config
* @filename: A filename
* @err: Return location for an error string
*
* Loads PipeWire config from @filename.
*
* Returns: 0 on success, otherwise < 0 and @err is set.
*/
int pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err)
{
unsigned int line;
FILE *f;
char buf[4096];
pw_log_debug("deamon-config %p: loading configuration file '%s'", config, filename);
if ((f = fopen(filename, "r")) == NULL) {
*err = spa_aprintf("failed to open configuration file '%s': %s", filename,
strerror(errno));
goto open_error;
}
line = 0;
while (!feof(f)) {
if (!fgets(buf, sizeof(buf), f)) {
if (feof(f))
break;
*err = spa_aprintf("failed to read configuration file '%s': %s",
filename, strerror(errno));
goto read_error;
}
line++;
if (parse_line(config, filename, buf, line, err) != 0)
goto parse_failed;
}
fclose(f);
return 0;
parse_failed:
read_error:
fclose(f);
open_error:
return -EINVAL;
}
/**
* pw_daemon_config_load:
* @config: A #struct pw_daemon_config
* @err: Return location for a #GError, or %NULL
*
* Loads the default config file for PipeWire. The filename can be overridden with
* an environment variable PIPEWIRE_CONFIG_FILE.
*
* Return: 0 on success, otherwise < 0 and @err is set.
*/
int pw_daemon_config_load(struct pw_daemon_config *config, char **err)
{
const char *filename;
filename = getenv("PIPEWIRE_CONFIG_FILE");
if (filename != NULL && *filename != '\0') {
pw_log_debug("PIPEWIRE_CONFIG_FILE set to: %s", filename);
} else {
filename = DEFAULT_CONFIG_FILE;
}
return pw_daemon_config_load_file(config, filename, err);
}
/**
* pw_daemon_config_run_commands:
* @config: A #struct pw_daemon_config
* @context: A #struct pw_context
*
* Run all commands that have been parsed. The list of commands will be cleared
* when this function has been called.
*
* Returns: 0 if all commands where executed with success, otherwise < 0.
*/
int pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_context *context)
{
char *err = NULL;
int ret = 0;
struct pw_command *command;
spa_list_for_each(command, &config->commands, link) {
if ((ret = pw_command_run(command, context, &err)) < 0) {
pw_log_error("could not run command %s: %s", command->args[0], err);
free(err);
break;
}
}
spa_list_consume(command, &config->commands, link)
pw_command_free(command);
return ret;
}

View file

@ -1,54 +0,0 @@
/* PipeWire
* Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
* @author Linus Svensson <linus.svensson@axis.com>
* Copyright © 2018 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef PIPEWIRE_DAEMON_CONFIG_H
#define PIPEWIRE_DAEMON_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <pipewire/context.h>
struct pw_daemon_config {
struct spa_list commands;
struct pw_properties *properties;
};
struct pw_daemon_config * pw_daemon_config_new(struct pw_properties *properties);
void pw_daemon_config_free(struct pw_daemon_config *config);
int pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err);
int pw_daemon_config_load(struct pw_daemon_config *config, char **err);
int pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_context *context);
#ifdef __cplusplus
}
#endif
#endif /* PIPEWIRE_DAEMON_CONFIG_H */

View file

@ -24,39 +24,341 @@
#include <signal.h>
#include <getopt.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <spa/utils/result.h>
#include <spa/utils/json.h>
#include <pipewire/impl.h>
#include "config.h"
#include "daemon-config.h"
static const char *daemon_name;
#define NAME "daemon"
#define DEFAULT_CONFIG_FILE "pipewire.conf"
struct data {
struct pw_context *context;
struct pw_main_loop *loop;
const char *daemon_name;
struct pw_properties *conf;
};
static int load_config(struct data *d)
{
const char *path;
char filename[PATH_MAX], *data;
struct stat sbuf;
int fd;
path = getenv("PIPEWIRE_CONFIG_FILE");
if (path == NULL) {
const char *dir;
dir = getenv("PIPEWIRE_CONFIG_DIR");
if (dir == NULL)
dir = PIPEWIRE_CONFIG_DIR;
if (dir == NULL)
return -ENOENT;
snprintf(filename, sizeof(filename), "%s/%s",
dir, DEFAULT_CONFIG_FILE);
path = filename;
}
if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) {
pw_log_warn(NAME" %p: error loading config '%s': %m", d, path);
return -errno;
}
pw_log_info(NAME" %p: loading config '%s'", d, path);
if (fstat(fd, &sbuf) < 0)
goto error_close;
if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
goto error_close;
close(fd);
pw_properties_update_string(d->conf, data, sbuf.st_size);
munmap(data, sbuf.st_size);
return 0;
error_close:
close(fd);
return -errno;
}
static int parse_spa_libs(struct data *d, const char *str)
{
struct spa_json it[2];
char key[512], value[512];
spa_json_init(&it[0], str, strlen(str));
if (spa_json_enter_object(&it[0], &it[1]) < 0)
return -EINVAL;
while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
const char *val;
if (key[0] == '#') {
if (spa_json_next(&it[1], &val) <= 0)
break;
}
else if (spa_json_get_string(&it[1], value, sizeof(value)-1) > 0) {
pw_context_add_spa_lib(d->context, key, value);
}
}
return 0;
}
static int load_module(struct data *d, const char *key, const char *args, const char *flags)
{
if (pw_context_load_module(d->context, key, args, NULL) == NULL) {
if (errno == ENOENT && flags && strstr(flags, "ifexists") != NULL) {
pw_log_debug(NAME" %p: skipping unavailable module %s",
d, key);
} else {
pw_log_error(NAME" %p: could not load module \"%s\": %m",
d, key);
return -errno;
}
}
return 0;
}
static int parse_modules(struct data *d, const char *str)
{
struct spa_json it[3];
char key[512];
int res = 0;
spa_json_init(&it[0], str, strlen(str));
if (spa_json_enter_object(&it[0], &it[1]) < 0)
return -EINVAL;
while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
const char *val;
char *args = NULL, *flags = NULL;
int len;
if ((len = spa_json_next(&it[1], &val)) <= 0)
break;
if (key[0] == '#')
continue;
if (spa_json_is_object(val, len)) {
char arg[512], aval[1024];
spa_json_enter(&it[1], &it[2]);
while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) {
if (spa_json_get_string(&it[2], aval, sizeof(aval)-1) <= 0)
break;
if (strcmp(arg, "args") == 0) {
args = strdup(aval);
} else if (strcmp(arg, "flags") == 0) {
flags = strdup(aval);
}
}
}
else if (!spa_json_is_null(val, len))
break;
res = load_module(d, key, args, flags);
free(args);
free(flags);
if (res < 0)
break;
}
return res;
}
static int create_object(struct data *d, const char *key, const char *args, const char *flags)
{
struct pw_impl_factory *factory;
void *obj;
pw_log_debug("find factory %s", key);
factory = pw_context_find_factory(d->context, key);
if (factory == NULL) {
if (flags && strstr(flags, "nofail") != NULL)
return 0;
pw_log_error("can't find factory %s", key);
return -ENOENT;
}
pw_log_debug("create object with args %s", args);
obj = pw_impl_factory_create_object(factory,
NULL, NULL, 0,
args ? pw_properties_new_string(args) : NULL,
SPA_ID_INVALID);
if (obj == NULL) {
if (flags && strstr(flags, "nofail") != NULL)
return 0;
pw_log_error("can't create object from factory %s: %m", key);
return -errno;
}
return 0;
}
static int parse_objects(struct data *d, const char *str)
{
struct spa_json it[3];
char key[512];
int res = 0;
spa_json_init(&it[0], str, strlen(str));
if (spa_json_enter_object(&it[0], &it[1]) < 0)
return -EINVAL;
while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
const char *val;
char *args = NULL, *flags = NULL;
int len;
if ((len = spa_json_next(&it[1], &val)) <= 0)
break;
if (key[0] == '#')
continue;
if (spa_json_is_object(val, len)) {
char arg[512], aval[1024];
spa_json_enter(&it[1], &it[2]);
while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) {
if (spa_json_get_string(&it[2], aval, sizeof(aval)-1) <= 0)
break;
if (strcmp(arg, "args") == 0) {
args = strdup(aval);
} else if (strcmp(arg, "flags") == 0) {
flags = strdup(aval);
}
}
}
else if (!spa_json_is_null(val, len))
break;
res = create_object(d, key, args, flags);
free(args);
free(flags);
if (res < 0)
break;
}
return res;
}
static int do_exec(struct data *d, const char *key, const char *args)
{
int pid, res, n_args;
pid = fork();
if (pid == 0) {
char *cmd, **argv;
cmd = spa_aprintf("%s %s", key, args);
argv = pw_split_strv(cmd, " \t", INT_MAX, &n_args);
free(cmd);
pw_log_info("exec %s '%s'", key, args);
res = execvp(key, argv);
pw_free_strv(argv);
if (res == -1) {
res = -errno;
pw_log_error("execvp error '%s': %m", key);
return res;
}
}
else {
int status;
res = waitpid(pid, &status, WNOHANG);
pw_log_info("exec got pid %d res:%d status:%d", pid, res, status);
}
return 0;
}
static int parse_exec(struct data *d, const char *str)
{
struct spa_json it[3];
char key[512];
int res = 0;
spa_json_init(&it[0], str, strlen(str));
if (spa_json_enter_object(&it[0], &it[1]) < 0)
return -EINVAL;
while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
const char *val;
char *args = NULL;
int len;
if ((len = spa_json_next(&it[1], &val)) <= 0)
break;
if (key[0] == '#')
continue;
if (spa_json_is_object(val, len)) {
char arg[512], aval[1024];
spa_json_enter(&it[1], &it[2]);
while (spa_json_get_string(&it[2], arg, sizeof(arg)-1) > 0) {
if (spa_json_get_string(&it[2], aval, sizeof(aval)-1) <= 0)
break;
if (strcmp(arg, "args") == 0)
args = strdup(aval);
}
}
else if (!spa_json_is_null(val, len))
break;
res = do_exec(d, key, args);
free(args);
if (res < 0)
break;
}
return res;
}
static void do_quit(void *data, int signal_number)
{
struct pw_main_loop *loop = data;
pw_main_loop_quit(loop);
struct data *d = data;
pw_main_loop_quit(d->loop);
}
static void show_help(const char *name)
static void show_help(struct data *d, const char *name)
{
fprintf(stdout, "%s [options]\n"
" -h, --help Show this help\n"
" --version Show version\n"
" -n, --name Daemon name (Default %s)\n",
name,
daemon_name);
d->daemon_name);
}
int main(int argc, char *argv[])
{
struct pw_context *context;
struct pw_main_loop *loop;
struct pw_daemon_config *config;
struct data d;
struct pw_properties *properties;
char *err = NULL;
const char *str;
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
@ -66,19 +368,30 @@ int main(int argc, char *argv[])
};
int c, res;
if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0)
fprintf(stderr, "can't set PIPEWIRE_INTERNAL env: %m");
spa_zero(d);
pw_init(&argc, &argv);
if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0)
fprintf(stderr, "can't PIPEWIRE_INTERNAL env: %m");
if ((d.conf = pw_properties_new(NULL, NULL)) == NULL) {
pw_log_error("failed to create config: %m");
return -1;
}
daemon_name = getenv("PIPEWIRE_CORE");
if (daemon_name == NULL)
daemon_name = PW_DEFAULT_REMOTE;
if ((res = load_config(&d)) < 0) {
pw_log_error("failed to load config: %s", spa_strerror(res));
return -1;
}
d.daemon_name = getenv("PIPEWIRE_CORE");
if (d.daemon_name == NULL)
d.daemon_name = PW_DEFAULT_REMOTE;
while ((c = getopt_long(argc, argv, "hVn:", long_options, NULL)) != -1) {
switch (c) {
case 'h' :
show_help(argv[0]);
show_help(&d, argv[0]);
return 0;
case 'V' :
fprintf(stdout, "%s\n"
@ -89,8 +402,8 @@ int main(int argc, char *argv[])
pw_get_library_version());
return 0;
case 'n' :
daemon_name = optarg;
fprintf(stdout, "set name %s\n", daemon_name);
d.daemon_name = optarg;
fprintf(stdout, "set name %s\n", d.daemon_name);
break;
default:
return -1;
@ -98,47 +411,57 @@ int main(int argc, char *argv[])
}
properties = pw_properties_new(
PW_KEY_CORE_NAME, daemon_name,
PW_KEY_CORE_NAME, d.daemon_name,
PW_KEY_CONTEXT_PROFILE_MODULES, "none",
PW_KEY_CORE_DAEMON, "true", NULL);
/* parse configuration */
config = pw_daemon_config_new(properties);
if (pw_daemon_config_load(config, &err) < 0) {
pw_log_error("failed to parse config: %s", err);
free(err);
return -1;
}
if ((str = pw_properties_get(d.conf, "properties")) != NULL)
pw_properties_update_string(properties, str, strlen(str));
loop = pw_main_loop_new(&properties->dict);
if (loop == NULL) {
d.loop = pw_main_loop_new(&properties->dict);
if (d.loop == NULL) {
pw_log_error("failed to create main-loop: %m");
return -1;
}
pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGINT, do_quit, loop);
pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGTERM, do_quit, loop);
pw_loop_add_signal(pw_main_loop_get_loop(d.loop), SIGINT, do_quit, &d);
pw_loop_add_signal(pw_main_loop_get_loop(d.loop), SIGTERM, do_quit, &d);
context = pw_context_new(pw_main_loop_get_loop(loop), properties, 0);
if (context == NULL) {
d.context = pw_context_new(pw_main_loop_get_loop(d.loop), properties, 0);
if (d.context == NULL) {
pw_log_error("failed to create context: %m");
return -1;
}
if ((res = pw_daemon_config_run_commands(config, context)) < 0) {
pw_log_error("failed to run config commands: %s", spa_strerror(res));
pw_main_loop_quit(loop);
return -1;
if ((str = pw_properties_get(d.conf, "spa-libs")) != NULL)
parse_spa_libs(&d, str);
if ((str = pw_properties_get(d.conf, "modules")) != NULL) {
if ((res = parse_modules(&d, str)) < 0) {
pw_log_error("failed to load modules: %s", spa_strerror(res));
return -1;
}
}
if ((str = pw_properties_get(d.conf, "objects")) != NULL) {
if ((res = parse_objects(&d, str)) < 0) {
pw_log_error("failed to load objects: %s", spa_strerror(res));
return -1;
}
}
if ((str = pw_properties_get(d.conf, "exec")) != NULL) {
if ((res = parse_exec(&d, str)) < 0) {
pw_log_error("failed to exec: %s", spa_strerror(res));
return -1;
}
}
pw_log_info("start main loop");
pw_main_loop_run(loop);
pw_main_loop_run(d.loop);
pw_log_info("leave main loop");
pw_daemon_config_free(config);
pw_context_destroy(context);
pw_main_loop_destroy(loop);
pw_properties_free(d.conf);
pw_context_destroy(d.context);
pw_main_loop_destroy(d.loop);
pw_deinit();
return 0;

View file

@ -1,14 +1,7 @@
pipewire_daemon_sources = [
'command.c',
'daemon-config.c',
'main.c',
]
pipewire_daemon_headers = [
'command.h',
'daemon-config.h',
]
pipewire_c_args = [
'-DHAVE_CONFIG_H',
'-D_GNU_SOURCE',

View file

@ -1,90 +1,101 @@
#daemon config file for PipeWire version @VERSION@
{
properties = {
## configure properties in the system
#library.name.system = support/libspa-support
#context.data-loop.library.name.system = support/libspa-support
#link.max-buffers = 64
link.max-buffers = 16 # version < 3 clients can't handle more
#mem.allow-mlock = true
#log.level = 2
## set-prop is used to configure properties in the system
#
#set-prop library.name.system support/libspa-support
#set-prop context.data-loop.library.name.system support/libspa-support
#set-prop link.max-buffers 64
set-prop link.max-buffers 16 # version < 3 clients can't handle more
#set-prop mem.allow-mlock true
#set-prop log.level 2
## Properties for the DSP configuration
#default.clock.rate = 48000
#default.clock.quantum = 1024
#default.clock.min-quantum = 32
#default.clock.max-quantum = 8192
#default.video.width = 640
#default.video.height = 480
#default.video.rate.num = 25
#default.video.rate.denom = 1
}
## Properties for the DSP configuration
#
#set-prop default.clock.rate 48000
#set-prop default.clock.quantum 1024
#set-prop default.clock.min-quantum 32
#set-prop default.clock.max-quantum 8192
#set-prop default.video.width 640
#set-prop default.video.height 480
#set-prop default.video.rate.num 25
#set-prop default.video.rate.denom 1
spa-libs = {
## <factory-name regex> = <library-name>
#
# used to find spa factory names. It maps an spa factory name
# regular expression to a library name that should contain
# that factory.
#
audio.convert* = audioconvert/libspa-audioconvert
api.alsa.* = alsa/libspa-alsa
api.v4l2.* = v4l2/libspa-v4l2
api.libcamera.* = libcamera/libspa-libcamera
api.bluez5.* = bluez5/libspa-bluez5
api.vulkan.* = vulkan/libspa-vulkan
api.jack.* = jack/libspa-jack
support.* = support/libspa-support
#videotestsrc = videotestsrc/libspa-videotestsrc
#audiotestsrc = audiotestsrc/libspa-audiotestsrc
}
## add-spa-lib <factory-name regex> <library-name>
#
# used to find spa factory names. It maps an spa factory name
# regular expression to a library name that should contain
# that factory.
#
add-spa-lib audio.convert* audioconvert/libspa-audioconvert
add-spa-lib api.alsa.* alsa/libspa-alsa
add-spa-lib api.v4l2.* v4l2/libspa-v4l2
add-spa-lib api.libcamera.* libcamera/libspa-libcamera
add-spa-lib api.bluez5.* bluez5/libspa-bluez5
add-spa-lib api.vulkan.* vulkan/libspa-vulkan
add-spa-lib api.jack.* jack/libspa-jack
add-spa-lib support.* support/libspa-support
#add-spa-lib videotestsrc videotestsrc/libspa-videotestsrc
#add-spa-lib audiotestsrc audiotestsrc/libspa-audiotestsrc
modules = {
## <module-name> = { [args = "<key>=<value> ..."]
# [flags = ifexists] }
#
# Loads a module with the given parameters. Normally failure is
# fatal if the module is not found, unless -ifexists is given.
#
libpipewire-module-rtkit = { "#args" = "rt.prio=20 rt.time.soft=200000 rt.time.hard=200000" flags=ifexists }
libpipewire-module-protocol-native = null
libpipewire-module-profiler = null
libpipewire-module-metadata = null
libpipewire-module-spa-device-factory = null
libpipewire-module-spa-node-factory = null
libpipewire-module-client-node = null
libpipewire-module-client-device = null
libpipewire-module-portal = null
libpipewire-module-access = { "#args" = "access.allowed=@media_session_path@ access.force=flatpak" }
libpipewire-module-adapter = null
libpipewire-module-link-factory = null
libpipewire-module-session-manager = null
}
## load-module [-ifexists] <module-name> [<key>=<value> ...]
#
# Loads a module with the given parameters. Normally failure is
# fatal if the module is not found, unless -ifexists is given.
#
load-module libpipewire-module-rtkit # rt.prio=20 rt.time.soft=200000 rt.time.hard=200000
load-module libpipewire-module-protocol-native
load-module libpipewire-module-profiler
load-module libpipewire-module-metadata
load-module libpipewire-module-spa-device-factory
load-module libpipewire-module-spa-node-factory
load-module libpipewire-module-client-node
load-module libpipewire-module-client-device
load-module libpipewire-module-portal
load-module libpipewire-module-access # access.allowed=@media_session_path@ access.force=flatpak
load-module libpipewire-module-adapter
load-module libpipewire-module-link-factory
load-module libpipewire-module-session-manager
objects = {
## <factory-name> = { [args = "<key>=<value> ..."]
# [flags = nofail] }
#
# Creates an object from a PipeWire factory with the given parameters.
# If nofail is given, errors are ignored (and no object is created)
#
#spa-node-factory = { args = "factory.name=videotestsrc node.name=videotestsrc Spa:Pod:Object:Param:Props:patternType=1" }
#spa-device-factory = { args = "factory.name=api.jack.device foo=bar" flags = nofail }
#spa-device-factory = { args = "factory.name=api.alsa.enum.udev" }
#spa-device-factory = { args = "factory.name=api.alsa.seq.bridge node.name=Internal-MIDI-Bridge" }
#adapter = { args = "factory.name=audiotestsrc node.name=my-test" }
#spa-node-factory = { args = "factory.name=api.vulkan.compute.source node.name=my-compute-source" }
spa-node-factory = { args = "factory.name=support.node.driver node.name=Dummy priority.driver=8000" }
}
## create-object [-nofail] <factory-name> [<key>=<value> ...]
#
# Creates an object from a PipeWire factory with the given parameters.
# If -nofail is given, errors are ignored (and no object is created)
#
#create-object spa-node-factory factory.name=videotestsrc node.name=videotestsrc Spa:Pod:Object:Param:Props:patternType=1
#create-object -nofail spa-device-factory factory.name=api.jack.device foo=bar
#create-object spa-device-factory factory.name=api.alsa.enum.udev
#create-object spa-device-factory factory.name=api.alsa.seq.bridge node.name=Internal-MIDI-Bridge
#create-object adapter factory.name=audiotestsrc node.name=my-test
#create-object spa-node-factory factory.name=api.vulkan.compute.source node.name=my-compute-source
create-object spa-node-factory factory.name=support.node.driver node.name=Dummy priority.driver=8000
## exec <program-name>
#
# Execute the given program.
#
# Start the session manager. Run the session manager with -h for
# options.
#
# The bluetooth module is disabled by default because it causes
# conflicts with PulseAudio. If you disable PulseAudio or don't
# load its bluetooth module, you can enable it here with -e bluez5
#
exec @media_session_path@
#
# You can optionally start the pulseaudio-server here as well
# but it better to start it as a systemd service.
# It can be interesting to start another daemon here that listens
# on another address with the -a option (eg. -a tcp:4713)
#
#exec @pipewire_pulse_path@
exec = {
## <program-name> = { [args = "<arguments>"] }
#
# Execute the given program with arguments.
#
# Start the session manager. Run the session manager with -h for
# options.
#
# The bluetooth module is disabled by default because it causes
# conflicts with PulseAudio. If you disable PulseAudio or don't
# load its bluetooth module, you can enable it here with -e bluez5
#
"@media_session_path@" = { args = ""}
#
# You can optionally start the pulseaudio-server here as well
# but it better to start it as a systemd service.
# It can be interesting to start another daemon here that listens
# on another address with the -a option (eg. -a tcp:4713)
#
#"@pipewire_pulse_path@" = { "#args" = "-a tcp:4713" }
}
}