mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-04 13:30:12 -05:00
pulse-server: add support for IPv6
Add support for listening on IPv6 addresses. The following address formats are supported: * tcp:[<ipv6-addr>]:<port>, * tcp:<ipv4-addr>:<port>, * tcp:<port>, and * unix:<path>. The IP addresses are parsed using `inet_pton()`, only the formats supported by that function are accepted. The IPv6 address must be surrounded by square brackets, they do not mean "optional" here. Specifying only the port is equivalent to the following two addresses: * [::]:<port>, and * 0.0.0.0:<port>. Address parsing has been made stricter: the port must always be specified explicitly. Fixes #1216.
This commit is contained in:
parent
279470bc07
commit
48f03f85a7
4 changed files with 521 additions and 191 deletions
|
|
@ -33,7 +33,9 @@ context.modules = [
|
||||||
# the addresses this server listens on
|
# the addresses this server listens on
|
||||||
server.address = [
|
server.address = [
|
||||||
"unix:native"
|
"unix:native"
|
||||||
# "tcp:4713"
|
# "tcp:4713" # IPv4 and IPv6 on all addresses
|
||||||
|
# "tcp:[::]:9999" # IPv6 on all addresses
|
||||||
|
# "tcp:127.0.0.1:8888" # IPv4 on a single address
|
||||||
]
|
]
|
||||||
#pulse.min.req = 256/48000 # 5ms
|
#pulse.min.req = 256/48000 # 5ms
|
||||||
#pulse.default.req = 960/48000 # 20 milliseconds
|
#pulse.default.req = 960/48000 # 20 milliseconds
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
|
||||||
|
|
||||||
#include <spa/utils/defs.h>
|
#include <spa/utils/defs.h>
|
||||||
#include <spa/utils/ringbuffer.h>
|
#include <spa/utils/ringbuffer.h>
|
||||||
|
|
@ -195,11 +194,7 @@ struct server {
|
||||||
struct spa_list link;
|
struct spa_list link;
|
||||||
struct impl *impl;
|
struct impl *impl;
|
||||||
|
|
||||||
#define SERVER_TYPE_INVALID 0
|
struct sockaddr_storage addr;
|
||||||
#define SERVER_TYPE_UNIX 1
|
|
||||||
#define SERVER_TYPE_INET 2
|
|
||||||
uint32_t type;
|
|
||||||
struct sockaddr_un addr;
|
|
||||||
|
|
||||||
struct spa_source *source;
|
struct spa_source *source;
|
||||||
struct spa_list clients;
|
struct spa_list clients;
|
||||||
|
|
@ -230,7 +225,7 @@ struct impl {
|
||||||
struct stats stat;
|
struct stats stat;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server *create_server(struct impl *impl, const char *address);
|
int create_and_start_servers(struct impl *impl, const char *addresses, struct pw_array *servers);
|
||||||
void server_free(struct server *server);
|
void server_free(struct server *server);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
struct module_native_protocol_tcp_data {
|
struct module_native_protocol_tcp_data {
|
||||||
struct module *module;
|
struct module *module;
|
||||||
struct server *server;
|
struct pw_array servers;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int module_native_protocol_tcp_load(struct client *client, struct module *module)
|
static int module_native_protocol_tcp_load(struct client *client, struct module *module)
|
||||||
|
|
@ -45,26 +45,34 @@ static int module_native_protocol_tcp_load(struct client *client, struct module
|
||||||
struct module_native_protocol_tcp_data *data = module->user_data;
|
struct module_native_protocol_tcp_data *data = module->user_data;
|
||||||
struct impl *impl = client->impl;
|
struct impl *impl = client->impl;
|
||||||
const char *address;
|
const char *address;
|
||||||
|
int res;
|
||||||
|
|
||||||
if ((address = pw_properties_get(module->props, "pulse.tcp")) == NULL)
|
if ((address = pw_properties_get(module->props, "pulse.tcp")) == NULL)
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
if ((data->server = create_server(impl, address)) == NULL)
|
pw_array_init(&data->servers, sizeof(struct server *));
|
||||||
return -errno;
|
|
||||||
|
res = create_and_start_servers(impl, address, &data->servers);
|
||||||
|
if (res < 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
pw_log_info("loaded module %p id:%u name:%s", module, module->idx, module->name);
|
pw_log_info("loaded module %p id:%u name:%s", module, module->idx, module->name);
|
||||||
module_emit_loaded(module, 0);
|
module_emit_loaded(module, 0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int module_native_protocol_tcp_unload(struct client *client, struct module *module)
|
static int module_native_protocol_tcp_unload(struct client *client, struct module *module)
|
||||||
{
|
{
|
||||||
struct module_native_protocol_tcp_data *d = module->user_data;
|
struct module_native_protocol_tcp_data *d = module->user_data;
|
||||||
|
struct server **s;
|
||||||
|
|
||||||
pw_log_info("unload module %p id:%u name:%s", module, module->idx, module->name);
|
pw_log_info("unload module %p id:%u name:%s", module, module->idx, module->name);
|
||||||
|
|
||||||
if (d->server != NULL)
|
pw_array_for_each (s, &d->servers)
|
||||||
server_free(d->server);
|
server_free(*s);
|
||||||
|
|
||||||
|
pw_array_clear(&d->servers);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -104,8 +112,8 @@ struct module *create_module_native_protocol_tcp(struct impl *impl, const char *
|
||||||
|
|
||||||
listen = pw_properties_get(props, "listen");
|
listen = pw_properties_get(props, "listen");
|
||||||
|
|
||||||
pw_properties_setf(props, "pulse.tcp", "tcp:%s%s%s",
|
pw_properties_setf(props, "pulse.tcp", "[ \"tcp:%s%s%s\" ]",
|
||||||
listen ? listen : "", listen ? ":" : "", port);
|
listen ? listen : "", listen ? ":" : "", port);
|
||||||
|
|
||||||
module = module_new(impl, &module_native_protocol_tcp_methods, sizeof(*d));
|
module = module_new(impl, &module_native_protocol_tcp_methods, sizeof(*d));
|
||||||
if (module == NULL) {
|
if (module == NULL) {
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,8 @@
|
||||||
#define DEFAULT_FORMAT "F32"
|
#define DEFAULT_FORMAT "F32"
|
||||||
#define DEFAULT_POSITION "[ FL FR ]"
|
#define DEFAULT_POSITION "[ FL FR ]"
|
||||||
|
|
||||||
|
#define LISTEN_BACKLOG 32
|
||||||
|
|
||||||
#define MAX_FORMATS 32
|
#define MAX_FORMATS 32
|
||||||
|
|
||||||
#include "format.c"
|
#include "format.c"
|
||||||
|
|
@ -5711,7 +5713,7 @@ on_connect(void *data, int fd, uint32_t mask)
|
||||||
{
|
{
|
||||||
struct server *server = data;
|
struct server *server = data;
|
||||||
struct impl *impl = server->impl;
|
struct impl *impl = server->impl;
|
||||||
struct sockaddr_un name;
|
struct sockaddr_storage name;
|
||||||
socklen_t length;
|
socklen_t length;
|
||||||
int client_fd, val, pid;
|
int client_fd, val, pid;
|
||||||
struct client *client;
|
struct client *client;
|
||||||
|
|
@ -5738,7 +5740,7 @@ on_connect(void *data, int fd, uint32_t mask)
|
||||||
|
|
||||||
pw_properties_setf(client->props,
|
pw_properties_setf(client->props,
|
||||||
"pulse.server.type", "%s",
|
"pulse.server.type", "%s",
|
||||||
server->type == SERVER_TYPE_INET ? "tcp" : "unix");
|
server->addr.ss_family == AF_UNIX ? "unix" : "tcp");
|
||||||
|
|
||||||
client->routes = pw_properties_new(NULL, NULL);
|
client->routes = pw_properties_new(NULL, NULL);
|
||||||
if (client->routes == NULL)
|
if (client->routes == NULL)
|
||||||
|
|
@ -5751,26 +5753,26 @@ on_connect(void *data, int fd, uint32_t mask)
|
||||||
|
|
||||||
pw_log_debug(NAME": client %p fd:%d", client, client_fd);
|
pw_log_debug(NAME": client %p fd:%d", client, client_fd);
|
||||||
|
|
||||||
if (server->type == SERVER_TYPE_UNIX) {
|
if (server->addr.ss_family == AF_UNIX) {
|
||||||
val = 6;
|
|
||||||
#ifdef SO_PRIORITY
|
#ifdef SO_PRIORITY
|
||||||
if (setsockopt(client_fd, SOL_SOCKET, SO_PRIORITY,
|
val = 6;
|
||||||
(const void *) &val, sizeof(val)) < 0)
|
if (setsockopt(client_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
|
||||||
pw_log_warn("SO_PRIORITY failed: %m");
|
pw_log_warn("setsockopt(SO_PRIORITY) failed: %m");
|
||||||
#endif
|
#endif
|
||||||
pid = get_client_pid(client, client_fd);
|
pid = get_client_pid(client, client_fd);
|
||||||
if (pid != 0 && check_flatpak(client, pid) == 1)
|
if (pid != 0 && check_flatpak(client, pid) == 1)
|
||||||
pw_properties_set(client->props, PW_KEY_CLIENT_ACCESS, "flatpak");
|
pw_properties_set(client->props, PW_KEY_CLIENT_ACCESS, "flatpak");
|
||||||
} else if (server->type == SERVER_TYPE_INET) {
|
}
|
||||||
|
else if (server->addr.ss_family == AF_INET || server->addr.ss_family == AF_INET6) {
|
||||||
val = 1;
|
val = 1;
|
||||||
if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY,
|
if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
|
||||||
(const void *) &val, sizeof(val)) < 0)
|
pw_log_warn("setsockopt(TCP_NODELAY) failed: %m");
|
||||||
pw_log_warn("TCP_NODELAY failed: %m");
|
|
||||||
|
|
||||||
val = IPTOS_LOWDELAY;
|
if (server->addr.ss_family == AF_INET) {
|
||||||
if (setsockopt(client_fd, IPPROTO_IP, IP_TOS,
|
val = IPTOS_LOWDELAY;
|
||||||
(const void *) &val, sizeof(val)) < 0)
|
if (setsockopt(client_fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0)
|
||||||
pw_log_warn("IP_TOS failed: %m");
|
pw_log_warn("setsockopt(IP_TOS) failed: %m");
|
||||||
|
}
|
||||||
|
|
||||||
pw_properties_set(client->props, PW_KEY_CLIENT_ACCESS, "restricted");
|
pw_properties_set(client->props, PW_KEY_CLIENT_ACCESS, "restricted");
|
||||||
}
|
}
|
||||||
|
|
@ -5844,8 +5846,8 @@ void server_free(struct server *server)
|
||||||
client_free(c);
|
client_free(c);
|
||||||
if (server->source)
|
if (server->source)
|
||||||
pw_loop_destroy_source(impl->loop, server->source);
|
pw_loop_destroy_source(impl->loop, server->source);
|
||||||
if (server->type == SERVER_TYPE_UNIX && !server->activated)
|
if (server->addr.ss_family == AF_UNIX && !server->activated)
|
||||||
unlink(server->addr.sun_path);
|
unlink(((const struct sockaddr_un *) &server->addr)->sun_path);
|
||||||
free(server);
|
free(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5864,99 +5866,143 @@ get_server_name(struct pw_context *context)
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_stale_socket(struct server *server, int fd)
|
static int parse_unix_address(const char *address, struct pw_array *addrs)
|
||||||
{
|
{
|
||||||
socklen_t size;
|
struct sockaddr_un addr = {0}, *s;
|
||||||
|
char runtime_dir[PATH_MAX];
|
||||||
|
int res;
|
||||||
|
|
||||||
size = offsetof(struct sockaddr_un, sun_path) + strlen(server->addr.sun_path);
|
if ((res = get_runtime_dir(runtime_dir, sizeof(runtime_dir), "pulse")) < 0)
|
||||||
if (connect(fd, (struct sockaddr *)&server->addr, size) < 0) {
|
return res;
|
||||||
|
|
||||||
|
res = snprintf(addr.sun_path, sizeof(addr.sun_path),
|
||||||
|
"%s/%s", runtime_dir, address);
|
||||||
|
|
||||||
|
if (res < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if ((size_t) res >= sizeof(addr.sun_path)) {
|
||||||
|
pw_log_error(NAME": '%s/%s' too long",
|
||||||
|
runtime_dir, address);
|
||||||
|
return -ENAMETOOLONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = pw_array_add(addrs, sizeof(struct sockaddr_storage));
|
||||||
|
if (s == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
|
*s = addr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef SUN_LEN
|
||||||
|
#define SUN_LEN(addr_un) \
|
||||||
|
(offsetof(struct sockaddr_un, sun_path) + strlen((addr_un)->sun_path))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool is_stale_socket(int fd, const struct sockaddr_un *addr_un)
|
||||||
|
{
|
||||||
|
if (connect(fd, (const struct sockaddr *) addr_un, SUN_LEN(addr_un)) < 0) {
|
||||||
if (errno == ECONNREFUSED)
|
if (errno == ECONNREFUSED)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int make_local_socket(struct server *server, const char *name)
|
|
||||||
{
|
|
||||||
char runtime_dir[PATH_MAX];
|
|
||||||
socklen_t size;
|
|
||||||
int name_size, fd, res;
|
|
||||||
struct stat socket_stat;
|
|
||||||
bool activated = false;
|
|
||||||
|
|
||||||
if ((res = get_runtime_dir(runtime_dir, sizeof(runtime_dir), "pulse")) < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
server->addr.sun_family = AF_LOCAL;
|
|
||||||
name_size = snprintf(server->addr.sun_path, sizeof(server->addr.sun_path),
|
|
||||||
"%s/%s", runtime_dir, name) + 1;
|
|
||||||
|
|
||||||
if (name_size > (int) sizeof(server->addr.sun_path)) {
|
|
||||||
pw_log_error(NAME" %p: %s/%s too long",
|
|
||||||
server, runtime_dir, name);
|
|
||||||
res = -ENAMETOOLONG;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
size = offsetof(struct sockaddr_un, sun_path) + strlen(server->addr.sun_path);
|
|
||||||
|
|
||||||
#ifdef HAVE_SYSTEMD
|
#ifdef HAVE_SYSTEMD
|
||||||
{
|
static int check_systemd_activation(const char *path)
|
||||||
int i, n = sd_listen_fds(0);
|
{
|
||||||
for (i = 0; i < n; ++i) {
|
const int n = sd_listen_fds(0);
|
||||||
if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM,
|
|
||||||
1, server->addr.sun_path, 0) > 0) {
|
for (int i = 0; i < n; i++) {
|
||||||
fd = SD_LISTEN_FDS_START + i;
|
const int fd = SD_LISTEN_FDS_START + i;
|
||||||
activated = true;
|
|
||||||
pw_log_info("server %p: Found socket activation socket for '%s'",
|
if (sd_is_socket_unix(fd, SOCK_STREAM, 1, path, 0) > 0)
|
||||||
server, server->addr.sun_path);
|
return fd;
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static inline int check_systemd_activation(SPA_UNUSED const char *path)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
static int start_unix_server(struct server *server, const struct sockaddr_storage *addr)
|
||||||
|
{
|
||||||
|
const struct sockaddr_un * const addr_un = (const struct sockaddr_un *) addr;
|
||||||
|
struct stat socket_stat;
|
||||||
|
int fd, res;
|
||||||
|
|
||||||
|
spa_assert(addr_un->sun_family == AF_UNIX);
|
||||||
|
|
||||||
|
fd = check_systemd_activation(addr_un->sun_path);
|
||||||
|
if (fd >= 0) {
|
||||||
|
server->activated = true;
|
||||||
|
pw_log_info(NAME" %p: found systemd socket activation socket for '%s'",
|
||||||
|
server, addr_un->sun_path);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
server->activated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = socket(addr_un->sun_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
||||||
|
if (fd < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_info(NAME" %p: socket() failed: %m", server);
|
pw_log_info(NAME" %p: socket() failed: %m", server);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (stat(server->addr.sun_path, &socket_stat) < 0) {
|
|
||||||
|
if (stat(addr_un->sun_path, &socket_stat) < 0) {
|
||||||
if (errno != ENOENT) {
|
if (errno != ENOENT) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: stat() %s failed: %m",
|
pw_log_warn(NAME" %p: stat('%s') failed: %m",
|
||||||
server, server->addr.sun_path);
|
server, addr_un->sun_path);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
} else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) {
|
}
|
||||||
|
else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) {
|
||||||
/* socket is there, check if it's stale */
|
/* socket is there, check if it's stale */
|
||||||
if (!is_stale_socket(server, fd)) {
|
if (!is_stale_socket(fd, addr_un)) {
|
||||||
res = -EBUSY;
|
res = -EADDRINUSE;
|
||||||
pw_log_info(NAME" %p: socket %s is in use", server,
|
pw_log_warn(NAME" %p: socket '%s' is in use",
|
||||||
server->addr.sun_path);
|
server, addr_un->sun_path);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
pw_log_warn(NAME" %p: unlink stale socket %s", server,
|
|
||||||
server->addr.sun_path);
|
pw_log_warn(NAME" %p: unlinking stale socket '%s'",
|
||||||
unlink(server->addr.sun_path);
|
server, addr_un->sun_path);
|
||||||
|
|
||||||
|
if (unlink(addr_un->sun_path) < 0)
|
||||||
|
pw_log_warn(NAME" %p: unlink('%s') failed: %m",
|
||||||
|
server, addr_un->sun_path);
|
||||||
}
|
}
|
||||||
if (bind(fd, (struct sockaddr *) &server->addr, size) < 0) {
|
|
||||||
|
if (bind(fd, (const struct sockaddr *) addr_un, SUN_LEN(addr_un)) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: bind() to %s failed: %m", server,
|
pw_log_warn(NAME" %p: bind() to '%s' failed: %m",
|
||||||
server->addr.sun_path);
|
server, addr_un->sun_path);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
if (listen(fd, 128) < 0) {
|
|
||||||
|
if (listen(fd, LISTEN_BACKLOG) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: listen() on %s failed: %m", server,
|
pw_log_warn(NAME" %p: listen() on '%s' failed: %m",
|
||||||
server->addr.sun_path);
|
server, addr_un->sun_path);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
pw_log_info(NAME" listening on unix:%s", server->addr.sun_path);
|
|
||||||
#ifdef HAVE_SYSTEMD
|
pw_log_info(NAME" %p: listening on unix:%s", server, addr_un->sun_path);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
#endif
|
server->addr = *addr;
|
||||||
server->activated = activated;
|
|
||||||
server->type = SERVER_TYPE_UNIX;
|
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
|
|
||||||
|
|
@ -5966,83 +6012,234 @@ error:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int create_pid_file(void) {
|
static int parse_port(const char *port)
|
||||||
|
{
|
||||||
|
const char *end;
|
||||||
|
long p;
|
||||||
|
|
||||||
|
if (port[0] == ':')
|
||||||
|
port += 1;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
p = strtol(port, (char **) &end, 0);
|
||||||
|
|
||||||
|
if (errno != 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (end == port || *end != '\0')
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (!(1 <= p && p <= 65535))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_ipv6_address(const char *address, struct sockaddr_in6 *out)
|
||||||
|
{
|
||||||
|
char addr_str[INET6_ADDRSTRLEN];
|
||||||
|
struct sockaddr_in6 addr = {0};
|
||||||
|
const char *end;
|
||||||
|
size_t len;
|
||||||
int res;
|
int res;
|
||||||
char pid_file[PATH_MAX];
|
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
if ((res = get_runtime_dir(pid_file, sizeof(pid_file), "pulse")) < 0) {
|
if (address[0] != '[')
|
||||||
return res;
|
return -EINVAL;
|
||||||
}
|
|
||||||
if (strlen(pid_file) > PATH_MAX - 5) {
|
address += 1;
|
||||||
pw_log_error(NAME" %s/pid too long", pid_file);
|
|
||||||
|
end = strchr(address, ']');
|
||||||
|
if (end == NULL)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
len = end - address;
|
||||||
|
if (len >= sizeof(addr_str))
|
||||||
return -ENAMETOOLONG;
|
return -ENAMETOOLONG;
|
||||||
}
|
|
||||||
strcat(pid_file, "/pid");
|
|
||||||
|
|
||||||
if ((f = fopen(pid_file, "w")) == NULL) {
|
memcpy(addr_str, address, len);
|
||||||
res = -errno;
|
addr_str[len] = '\0';
|
||||||
pw_log_error(NAME" failed to open pid file");
|
|
||||||
|
res = inet_pton(AF_INET6, addr_str, &addr.sin6_addr.s6_addr);
|
||||||
|
if (res < 0)
|
||||||
|
return -errno;
|
||||||
|
if (res == 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
res = parse_port(end + 1);
|
||||||
|
if (res < 0)
|
||||||
return res;
|
return res;
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(f, "%lu\n", (unsigned long)getpid());
|
addr.sin6_port = htons(res);
|
||||||
fclose(f);
|
addr.sin6_family = AF_INET6;
|
||||||
|
|
||||||
|
*out = addr;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int make_inet_socket(struct server *server, const char *name)
|
static int parse_ipv4_address(const char *address, struct sockaddr_in *out)
|
||||||
{
|
{
|
||||||
struct sockaddr_in addr;
|
char addr_str[INET_ADDRSTRLEN];
|
||||||
int res, fd, on;
|
struct sockaddr_in addr = {0};
|
||||||
uint32_t address = INADDR_ANY;
|
size_t len;
|
||||||
uint16_t port;
|
int res;
|
||||||
char *col;
|
|
||||||
|
|
||||||
col = strchr(name, ':');
|
len = strspn(address, "0123456789.");
|
||||||
if (col) {
|
if (len == 0)
|
||||||
struct in_addr ipv4;
|
return -EINVAL;
|
||||||
char *n;
|
if (len >= sizeof(addr_str))
|
||||||
port = atoi(col+1);
|
return -ENAMETOOLONG;
|
||||||
n = strndupa(name, col - name);
|
|
||||||
if (inet_pton(AF_INET, n, &ipv4) > 0)
|
memcpy(addr_str, address, len);
|
||||||
address = ntohl(ipv4.s_addr);
|
addr_str[len] = '\0';
|
||||||
else
|
|
||||||
address = INADDR_ANY;
|
res = inet_pton(AF_INET, addr_str, &addr.sin_addr.s_addr);
|
||||||
} else {
|
if (res < 0)
|
||||||
address = INADDR_ANY;
|
return -errno;
|
||||||
port = atoi(name);
|
if (res == 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
res = parse_port(address + len);
|
||||||
|
if (res < 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
addr.sin_port = htons(res);
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
|
||||||
|
*out = addr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FORMATTED_IP_ADDR_STRLEN (INET6_ADDRSTRLEN + 2 + 1 + 5)
|
||||||
|
|
||||||
|
static int format_ip_address(const struct sockaddr_storage *addr, char *buffer, size_t buflen)
|
||||||
|
{
|
||||||
|
char ip[INET6_ADDRSTRLEN];
|
||||||
|
const void *src;
|
||||||
|
const char *fmt;
|
||||||
|
int port;
|
||||||
|
|
||||||
|
switch (addr->ss_family) {
|
||||||
|
case AF_INET:
|
||||||
|
fmt = "%s:%d";
|
||||||
|
src = &((struct sockaddr_in *) addr)->sin_addr.s_addr;
|
||||||
|
port = ntohs(((struct sockaddr_in *) addr)->sin_port);
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
fmt = "[%s]:%d";
|
||||||
|
src = &((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr;
|
||||||
|
port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EAFNOSUPPORT;
|
||||||
}
|
}
|
||||||
if (port == 0)
|
|
||||||
port = PW_PROTOCOL_PULSE_DEFAULT_PORT;
|
|
||||||
|
|
||||||
if ((fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
|
if (inet_ntop(addr->ss_family, src, ip, sizeof(ip)) == NULL)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
return snprintf(buffer, buflen, fmt, ip, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_ip_address_length(const struct sockaddr_storage *addr)
|
||||||
|
{
|
||||||
|
switch (addr->ss_family) {
|
||||||
|
case AF_INET:
|
||||||
|
return sizeof(struct sockaddr_in);
|
||||||
|
case AF_INET6:
|
||||||
|
return sizeof(struct sockaddr_in6);
|
||||||
|
default:
|
||||||
|
return -EAFNOSUPPORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_ip_address(const char *address, struct pw_array *addrs)
|
||||||
|
{
|
||||||
|
char ip[FORMATTED_IP_ADDR_STRLEN];
|
||||||
|
struct sockaddr_storage addr, *s;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = parse_ipv6_address(address, (struct sockaddr_in6 *) &addr);
|
||||||
|
if (res == 0) {
|
||||||
|
s = pw_array_add(addrs, sizeof(*s));
|
||||||
|
if (s == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
*s = addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = parse_ipv4_address(address, (struct sockaddr_in *) &addr);
|
||||||
|
if (res == 0) {
|
||||||
|
s = pw_array_add(addrs, sizeof(*s));
|
||||||
|
if (s == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
*s = addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = parse_port(address);
|
||||||
|
if (res < 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
s = pw_array_add(addrs, sizeof(*s) * 2);
|
||||||
|
if (s == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
snprintf(ip, sizeof(ip), "[::]:%d", res);
|
||||||
|
spa_assert_se(parse_ipv6_address(ip, (struct sockaddr_in6 *) &addr) == 0);
|
||||||
|
*s++ = addr;
|
||||||
|
|
||||||
|
snprintf(ip, sizeof(ip), "0.0.0.0:%d", res);
|
||||||
|
spa_assert_se(parse_ipv4_address(ip, (struct sockaddr_in *) &addr) == 0);
|
||||||
|
*s++ = addr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int start_ip_server(struct server *server, const struct sockaddr_storage *addr)
|
||||||
|
{
|
||||||
|
char ip[FORMATTED_IP_ADDR_STRLEN];
|
||||||
|
int fd, res;
|
||||||
|
|
||||||
|
spa_assert(addr->ss_family == AF_INET || addr->ss_family == AF_INET6);
|
||||||
|
|
||||||
|
fd = socket(addr->ss_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_TCP);
|
||||||
|
if (fd < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: socket() failed: %m", server);
|
pw_log_warn(NAME" %p: socket() failed: %m", server);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
on = 1;
|
{
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0)
|
int on = 1;
|
||||||
pw_log_warn(NAME" %p: setsockopt(): %m", server);
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
|
||||||
|
pw_log_warn(NAME" %p: setsockopt(SO_REUSEADDR) failed: %m", server);
|
||||||
|
}
|
||||||
|
|
||||||
spa_zero(addr);
|
if (addr->ss_family == AF_INET6) {
|
||||||
addr.sin_family = AF_INET;
|
int on = 1;
|
||||||
addr.sin_port = htons(port);
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
|
||||||
addr.sin_addr.s_addr = htonl(address);
|
pw_log_warn(NAME" %p: setsockopt(IPV6_V6ONLY) failed: %m", server);
|
||||||
|
}
|
||||||
|
|
||||||
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
|
if (bind(fd, (const struct sockaddr *) addr, get_ip_address_length(addr)) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: bind() failed: %m", server);
|
pw_log_warn(NAME" %p: bind() failed: %m", server);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
if (listen(fd, 5) < 0) {
|
|
||||||
|
if (listen(fd, LISTEN_BACKLOG) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: listen() failed: %m", server);
|
pw_log_warn(NAME" %p: listen() failed: %m", server);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
server->type = SERVER_TYPE_INET;
|
|
||||||
pw_log_info(NAME" listening on tcp:%08x:%u", address, port);
|
spa_assert_se(format_ip_address(addr, ip, sizeof(ip)) >= 0);
|
||||||
|
pw_log_info(NAME" %p: listening on tcp:%s", server, ip);
|
||||||
|
|
||||||
|
server->addr = *addr;
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
|
|
||||||
|
|
@ -6052,45 +6249,178 @@ error:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct server *create_server(struct impl *impl, const char *address)
|
static struct server *server_new(struct impl *impl)
|
||||||
{
|
{
|
||||||
int fd, res;
|
struct server * const server = calloc(1, sizeof(*server));
|
||||||
struct server *server;
|
|
||||||
|
|
||||||
server = calloc(1, sizeof(struct server));
|
|
||||||
if (server == NULL)
|
if (server == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
server->impl = impl;
|
server->impl = impl;
|
||||||
|
server->addr.ss_family = AF_UNSPEC;
|
||||||
spa_list_init(&server->clients);
|
spa_list_init(&server->clients);
|
||||||
spa_list_append(&impl->servers, &server->link);
|
spa_list_append(&impl->servers, &server->link);
|
||||||
|
|
||||||
if (strstr(address, "unix:") == address) {
|
return server;
|
||||||
fd = make_local_socket(server, address+5);
|
}
|
||||||
} else if (strstr(address, "tcp:") == address) {
|
|
||||||
fd = make_inet_socket(server, address+4);
|
static int server_start(struct server *server, const struct sockaddr_storage *addr)
|
||||||
} else {
|
{
|
||||||
fd = -EINVAL;
|
const struct impl * const impl = server->impl;
|
||||||
}
|
int res = 0, fd;
|
||||||
if (fd < 0) {
|
|
||||||
res = fd;
|
switch (addr->ss_family) {
|
||||||
goto error;
|
case AF_INET:
|
||||||
|
case AF_INET6:
|
||||||
|
fd = start_ip_server(server, addr);
|
||||||
|
break;
|
||||||
|
case AF_UNIX:
|
||||||
|
fd = start_unix_server(server, addr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* shouldn't happen */
|
||||||
|
fd = -EAFNOSUPPORT;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
return fd;
|
||||||
|
|
||||||
server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_connect, server);
|
server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_connect, server);
|
||||||
if (server->source == NULL) {
|
if (server->source == NULL) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error(NAME" %p: can't create server source: %m", impl);
|
pw_log_error(NAME" %p: can't create server source: %m", impl);
|
||||||
goto error_close;
|
|
||||||
}
|
}
|
||||||
return server;
|
|
||||||
|
|
||||||
error_close:
|
return res;
|
||||||
close(fd);
|
}
|
||||||
error:
|
|
||||||
server_free(server);
|
|
||||||
errno = -res;
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
|
static int parse_address(const char *address, struct pw_array *addrs)
|
||||||
|
{
|
||||||
|
if (strncmp(address, "tcp:", strlen("tcp:")) == 0)
|
||||||
|
return parse_ip_address(address + strlen("tcp:"), addrs);
|
||||||
|
|
||||||
|
if (strncmp(address, "unix:", strlen("unix:")) == 0)
|
||||||
|
return parse_unix_address(address + strlen("unix:"), addrs);
|
||||||
|
|
||||||
|
return -EAFNOSUPPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SUN_PATH_SIZE (sizeof(((struct sockaddr_un *) NULL)->sun_path))
|
||||||
|
#define FORMATTED_UNIX_ADDR_STRLEN (SUN_PATH_SIZE + 5)
|
||||||
|
#define FORMATTED_TCP_ADDR_STRLEN (FORMATTED_IP_ADDR_STRLEN + 4)
|
||||||
|
#define FORMATTED_SOCKET_ADDR_STRLEN \
|
||||||
|
(FORMATTED_UNIX_ADDR_STRLEN > FORMATTED_TCP_ADDR_STRLEN ? \
|
||||||
|
FORMATTED_UNIX_ADDR_STRLEN : \
|
||||||
|
FORMATTED_TCP_ADDR_STRLEN)
|
||||||
|
|
||||||
|
static int format_socket_address(const struct sockaddr_storage *addr, char *buffer, size_t buflen)
|
||||||
|
{
|
||||||
|
if (addr->ss_family == AF_INET || addr->ss_family == AF_INET6) {
|
||||||
|
char ip[FORMATTED_IP_ADDR_STRLEN];
|
||||||
|
|
||||||
|
spa_assert_se(format_ip_address(addr, ip, sizeof(ip)) >= 0);
|
||||||
|
|
||||||
|
return snprintf(buffer, buflen, "tcp:%s", ip);
|
||||||
|
}
|
||||||
|
else if (addr->ss_family == AF_UNIX) {
|
||||||
|
const struct sockaddr_un *addr_un = (const struct sockaddr_un *) addr;
|
||||||
|
|
||||||
|
return snprintf(buffer, buflen, "unix:%s", addr_un->sun_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EAFNOSUPPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int create_and_start_servers(struct impl *impl, const char *addresses, struct pw_array *servers)
|
||||||
|
{
|
||||||
|
struct pw_array addrs = PW_ARRAY_INIT(sizeof(struct sockaddr_storage));
|
||||||
|
const struct sockaddr_storage *addr;
|
||||||
|
char addr_str[FORMATTED_SOCKET_ADDR_STRLEN];
|
||||||
|
int res, count = 0, err = 0; /* store the first error to return when no servers could be created */
|
||||||
|
struct spa_json it[2];
|
||||||
|
|
||||||
|
/* update `err` if it hasn't been set to an errno */
|
||||||
|
#define UPDATE_ERR(e) do { if (err == 0) err = (e); } while (false)
|
||||||
|
|
||||||
|
/* collect addresses into an array of `struct sockaddr_storage` */
|
||||||
|
spa_json_init(&it[0], addresses, strlen(addresses));
|
||||||
|
|
||||||
|
if (spa_json_enter_array(&it[0], &it[1]) < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
while (spa_json_get_string(&it[1], addr_str, sizeof(addr_str) - 1) > 0) {
|
||||||
|
res = parse_address(addr_str, &addrs);
|
||||||
|
if (res < 0) {
|
||||||
|
pw_log_warn(NAME" %p: failed to parse address '%s': %s",
|
||||||
|
impl, addr_str, spa_strerror(res));
|
||||||
|
|
||||||
|
UPDATE_ERR(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* try to create sockets for each address in the list */
|
||||||
|
pw_array_for_each (addr, &addrs) {
|
||||||
|
struct server * const server = server_new(impl);
|
||||||
|
if (server == NULL) {
|
||||||
|
UPDATE_ERR(-errno);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = server_start(server, addr);
|
||||||
|
if (res < 0) {
|
||||||
|
spa_assert_se(format_socket_address(addr, addr_str, sizeof(addr_str)) >= 0);
|
||||||
|
pw_log_warn(NAME" %p: failed to start server on '%s': %s",
|
||||||
|
impl, addr_str, spa_strerror(res));
|
||||||
|
|
||||||
|
UPDATE_ERR(res);
|
||||||
|
server_free(server);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (servers != NULL)
|
||||||
|
pw_array_add_ptr(servers, server);
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw_array_clear(&addrs);
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
UPDATE_ERR(-EINVAL);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
|
||||||
|
#undef UPDATE_ERR
|
||||||
|
}
|
||||||
|
|
||||||
|
static int create_pid_file(void) {
|
||||||
|
char pid_file[PATH_MAX];
|
||||||
|
FILE *f;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if ((res = get_runtime_dir(pid_file, sizeof(pid_file), "pulse")) < 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (strlen(pid_file) > PATH_MAX - 5) {
|
||||||
|
pw_log_error(NAME": path too long: %s/pid", pid_file);
|
||||||
|
return -ENAMETOOLONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcat(pid_file, "/pid");
|
||||||
|
|
||||||
|
if ((f = fopen(pid_file, "w")) == NULL) {
|
||||||
|
res = -errno;
|
||||||
|
pw_log_error(NAME": failed to open pid file: %m");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "%lu\n", (unsigned long) getpid());
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int impl_free_sample(void *item, void *data)
|
static int impl_free_sample(void *item, void *data)
|
||||||
|
|
@ -6218,21 +6548,17 @@ static void load_defaults(struct defs *def, struct pw_properties *props)
|
||||||
struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context,
|
struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context,
|
||||||
struct pw_properties *props, size_t user_data_size)
|
struct pw_properties *props, size_t user_data_size)
|
||||||
{
|
{
|
||||||
struct impl *impl;
|
|
||||||
const char *str;
|
|
||||||
struct spa_json it[2];
|
|
||||||
char value[512];
|
|
||||||
const struct spa_support *support;
|
const struct spa_support *support;
|
||||||
struct spa_cpu *cpu;
|
struct spa_cpu *cpu;
|
||||||
uint32_t n_support;
|
uint32_t n_support;
|
||||||
int res;
|
struct impl *impl;
|
||||||
size_t ncreated = 0;
|
const char *str;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl) + user_data_size);
|
impl = calloc(1, sizeof(*impl) + user_data_size);
|
||||||
if (impl == NULL)
|
if (impl == NULL)
|
||||||
goto error_exit;
|
goto error_exit;
|
||||||
|
|
||||||
|
|
||||||
if (props == NULL)
|
if (props == NULL)
|
||||||
props = pw_properties_new(NULL, NULL);
|
props = pw_properties_new(NULL, NULL);
|
||||||
if (props == NULL)
|
if (props == NULL)
|
||||||
|
|
@ -6278,36 +6604,35 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context,
|
||||||
get_server_name(context));
|
get_server_name(context));
|
||||||
str = pw_properties_get(props, "server.address");
|
str = pw_properties_get(props, "server.address");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str == NULL)
|
if (str == NULL)
|
||||||
goto error_free;
|
goto error_free;
|
||||||
|
|
||||||
spa_json_init(&it[0], str, strlen(str));
|
if ((res = create_and_start_servers(impl, str, NULL)) < 0) {
|
||||||
if (spa_json_enter_array(&it[0], &it[1]) > 0) {
|
pw_log_error(NAME" %p: no servers could be started: %s",
|
||||||
while (spa_json_get_string(&it[1], value, sizeof(value)-1) > 0) {
|
impl, spa_strerror(res));
|
||||||
if (create_server(impl, value)) {
|
|
||||||
ncreated++;
|
|
||||||
} else {
|
|
||||||
pw_log_warn(NAME" %p: can't create server for %s: %m",
|
|
||||||
impl, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ncreated == 0)
|
|
||||||
goto error_free;
|
goto error_free;
|
||||||
|
}
|
||||||
|
|
||||||
if ((res = create_pid_file()) < 0) {
|
if ((res = create_pid_file()) < 0) {
|
||||||
pw_log_warn(NAME" %p: can't create pid file: %s",
|
pw_log_warn(NAME" %p: can't create pid file: %s",
|
||||||
impl, spa_strerror(res));
|
impl, spa_strerror(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl->dbus_name = dbus_request_name(context, "org.pulseaudio.Server");
|
impl->dbus_name = dbus_request_name(context, "org.pulseaudio.Server");
|
||||||
|
|
||||||
return (struct pw_protocol_pulse*)impl;
|
return (struct pw_protocol_pulse *) impl;
|
||||||
|
|
||||||
error_free:
|
error_free:
|
||||||
free(impl);
|
free(impl);
|
||||||
|
|
||||||
error_exit:
|
error_exit:
|
||||||
if (props != NULL)
|
if (props != NULL)
|
||||||
pw_properties_free(props);
|
pw_properties_free(props);
|
||||||
|
|
||||||
|
if (res < 0)
|
||||||
|
errno = -res;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue