diff --git a/src/daemon/command.c b/src/daemon/command.c deleted file mode 100644 index f875f2733..000000000 --- a/src/daemon/command.c +++ /dev/null @@ -1,445 +0,0 @@ -/* PipeWire - * Copyright © 2016 Axis Communications - * @author Linus Svensson - * 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 -#include -#include -#include -#include -#include - -#include - -#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 ", 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 ", 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 [= ...]", 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); -} diff --git a/src/daemon/command.h b/src/daemon/command.h deleted file mode 100644 index f7cd1c294..000000000 --- a/src/daemon/command.h +++ /dev/null @@ -1,63 +0,0 @@ -/* PipeWire - * Copyright © 2016 Axis Communications - * @author Linus Svensson - * 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 - -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 */ diff --git a/src/daemon/daemon-config.c b/src/daemon/daemon-config.c deleted file mode 100644 index 81cf8a86a..000000000 --- a/src/daemon/daemon-config.c +++ /dev/null @@ -1,212 +0,0 @@ -/* PipeWire - * Copyright © 2016 Axis Communications - * @author Linus Svensson - * 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 -#include -#include - -#include - -#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; -} diff --git a/src/daemon/daemon-config.h b/src/daemon/daemon-config.h deleted file mode 100644 index 2c10b496e..000000000 --- a/src/daemon/daemon-config.h +++ /dev/null @@ -1,54 +0,0 @@ -/* PipeWire - * Copyright © 2016 Axis Communications - * @author Linus Svensson - * 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 - -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 */ diff --git a/src/daemon/main.c b/src/daemon/main.c index e0b0581da..ce63d3400 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -24,39 +24,341 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #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; diff --git a/src/daemon/meson.build b/src/daemon/meson.build index 0cf14b5f2..b7b6cf6e7 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -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', diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 6cb3c9c79..94ebd39af 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -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 = { + ## = + # + # 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 -# -# 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 = { + ## = { [args = "= ..."] + # [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] [= ...] -# -# 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 = { + ## = { [args = "= ..."] + # [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] [= ...] -# -# 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 -# -# 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 = { + ## = { [args = ""] } + # + # 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" } + } +}