2023-02-08 18:12:00 +01:00
|
|
|
/* PipeWire */
|
|
|
|
|
/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */
|
|
|
|
|
/* SPDX-License-Identifier: MIT */
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/un.h>
|
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
#include <netinet/tcp.h>
|
|
|
|
|
#include <netinet/ip.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
|
|
|
#include <systemd/sd-daemon.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-05-12 17:17:08 +02:00
|
|
|
#include <spa/utils/cleanup.h>
|
2021-06-18 23:46:50 +02:00
|
|
|
#include <spa/utils/defs.h>
|
|
|
|
|
#include <spa/utils/json.h>
|
|
|
|
|
#include <spa/utils/result.h>
|
|
|
|
|
#include <pipewire/pipewire.h>
|
|
|
|
|
|
|
|
|
|
#include "client.h"
|
|
|
|
|
#include "commands.h"
|
|
|
|
|
#include "defs.h"
|
|
|
|
|
#include "internal.h"
|
2021-09-22 09:28:54 +10:00
|
|
|
#include "log.h"
|
2021-06-18 23:46:50 +02:00
|
|
|
#include "message.h"
|
|
|
|
|
#include "reply.h"
|
|
|
|
|
#include "server.h"
|
|
|
|
|
#include "stream.h"
|
|
|
|
|
#include "utils.h"
|
2022-07-30 21:25:03 +03:00
|
|
|
#include "flatpak-utils.h"
|
2023-11-22 11:26:16 +01:00
|
|
|
#ifdef HAVE_SNAP
|
|
|
|
|
#include "snap-policy.h"
|
|
|
|
|
#endif
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
#define LISTEN_BACKLOG 32
|
|
|
|
|
#define MAX_CLIENTS 64
|
|
|
|
|
|
2024-01-04 17:40:57 +01:00
|
|
|
PW_LOG_TOPIC_EXTERN(pulse_conn);
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
static int handle_packet(struct client *client, struct message *msg)
|
|
|
|
|
{
|
|
|
|
|
uint32_t command, tag;
|
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
|
|
if (message_get(msg,
|
|
|
|
|
TAG_U32, &command,
|
|
|
|
|
TAG_U32, &tag,
|
|
|
|
|
TAG_INVALID) < 0) {
|
|
|
|
|
res = -EPROTO;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pw_log_debug("client %p: received packet command:%u tag:%u",
|
|
|
|
|
client, command, tag);
|
|
|
|
|
|
|
|
|
|
if (command >= COMMAND_MAX) {
|
|
|
|
|
res = -EINVAL;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-04 17:40:57 +01:00
|
|
|
if (pw_log_topic_custom_enabled(SPA_LOG_LEVEL_INFO, pulse_conn)) {
|
2021-06-18 23:46:50 +02:00
|
|
|
pw_log_debug("client %p: command:%s", client, commands[command].name);
|
2024-01-11 12:05:29 +01:00
|
|
|
message_dump(SPA_LOG_LEVEL_INFO, "<<", msg);
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-30 21:36:04 +01:00
|
|
|
const struct command *cmd = &commands[command];
|
|
|
|
|
if (cmd->run == NULL) {
|
2021-06-18 23:46:50 +02:00
|
|
|
res = -ENOTSUP;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-30 21:36:04 +01:00
|
|
|
if (!client->authenticated && !SPA_FLAG_IS_SET(cmd->access, COMMAND_ACCESS_WITHOUT_AUTH)) {
|
|
|
|
|
res = -EACCES;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (client->manager == NULL && !SPA_FLAG_IS_SET(cmd->access, COMMAND_ACCESS_WITHOUT_MANAGER)) {
|
|
|
|
|
res = -EACCES;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = cmd->run(client, command, tag, msg);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
finish:
|
2022-06-16 19:54:13 +02:00
|
|
|
message_free(msg, false, false);
|
2021-06-18 23:46:50 +02:00
|
|
|
if (res < 0)
|
|
|
|
|
reply_error(client, command, tag, res);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 16:16:57 +01:00
|
|
|
static void stream_clear_data(struct stream *stream,
|
|
|
|
|
uint32_t offset, uint32_t len)
|
|
|
|
|
{
|
|
|
|
|
uint32_t l0 = SPA_MIN(len, MAXLENGTH - offset), l1 = len - l0;
|
|
|
|
|
sample_spec_silence(&stream->ss, SPA_PTROFF(stream->buffer, offset, void), l0);
|
|
|
|
|
if (SPA_UNLIKELY(l1 > 0))
|
|
|
|
|
sample_spec_silence(&stream->ss, stream->buffer, l1);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
static int handle_memblock(struct client *client, struct message *msg)
|
|
|
|
|
{
|
|
|
|
|
struct stream *stream;
|
|
|
|
|
uint32_t channel, flags, index;
|
|
|
|
|
int64_t offset, diff;
|
|
|
|
|
int32_t filled;
|
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
|
|
channel = ntohl(client->desc.channel);
|
|
|
|
|
offset = (int64_t) (
|
|
|
|
|
(((uint64_t) ntohl(client->desc.offset_hi)) << 32) |
|
|
|
|
|
(((uint64_t) ntohl(client->desc.offset_lo))));
|
|
|
|
|
flags = ntohl(client->desc.flags);
|
|
|
|
|
|
|
|
|
|
pw_log_debug("client %p: received memblock channel:%d offset:%" PRIi64 " flags:%08x size:%u",
|
|
|
|
|
client, channel, offset, flags, msg->length);
|
|
|
|
|
|
|
|
|
|
stream = pw_map_lookup(&client->streams, channel);
|
|
|
|
|
if (stream == NULL || stream->type == STREAM_TYPE_RECORD) {
|
2021-11-16 12:33:23 +01:00
|
|
|
pw_log_info("client %p [%s]: received memblock for unknown channel %d",
|
|
|
|
|
client, client->name, channel);
|
2021-06-18 23:46:50 +02:00
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
filled = spa_ringbuffer_get_write_index(&stream->ring, &index);
|
|
|
|
|
pw_log_debug("new block %p %p/%u filled:%d index:%d flags:%02x offset:%" PRIu64,
|
|
|
|
|
msg, msg->data, msg->length, filled, index, flags, offset);
|
|
|
|
|
|
|
|
|
|
switch (flags & FLAG_SEEKMASK) {
|
|
|
|
|
case SEEK_RELATIVE:
|
|
|
|
|
diff = offset;
|
|
|
|
|
break;
|
|
|
|
|
case SEEK_ABSOLUTE:
|
|
|
|
|
diff = offset - (int64_t)stream->write_index;
|
|
|
|
|
break;
|
|
|
|
|
case SEEK_RELATIVE_ON_READ:
|
|
|
|
|
case SEEK_RELATIVE_END:
|
|
|
|
|
diff = offset - (int64_t)filled;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
pw_log_warn("client %p [%s]: received memblock frame with invalid seek mode: %" PRIu32,
|
|
|
|
|
client, client->name, (uint32_t)(flags & FLAG_SEEKMASK));
|
|
|
|
|
res = -EPROTO;
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 16:16:57 +01:00
|
|
|
if (diff > 0) {
|
|
|
|
|
pw_log_debug("clear gap of %"PRIu64, diff);
|
|
|
|
|
/* if we jump forwards, clear the data we skipped because we might otherwise
|
|
|
|
|
* play back old data. FIXME, if the write pointer goes backwards and
|
|
|
|
|
* forwards, this might clear valid data. We should probably keep track of
|
|
|
|
|
* the highest write pointer and only clear when we go past that one. */
|
|
|
|
|
stream_clear_data(stream, index % MAXLENGTH, SPA_MIN(diff, MAXLENGTH));
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
index += diff;
|
|
|
|
|
filled += diff;
|
|
|
|
|
stream->write_index += diff;
|
2022-09-10 16:06:45 +02:00
|
|
|
if ((flags & FLAG_SEEKMASK) == SEEK_RELATIVE)
|
|
|
|
|
stream->requested -= diff;
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
if (filled < 0) {
|
|
|
|
|
/* underrun, reported on reader side */
|
|
|
|
|
} else if (filled + msg->length > stream->attr.maxlength) {
|
|
|
|
|
/* overrun */
|
|
|
|
|
stream_send_overflow(stream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* always write data to ringbuffer, we expect the other side
|
|
|
|
|
* to recover */
|
|
|
|
|
spa_ringbuffer_write_data(&stream->ring,
|
2022-01-28 16:21:03 +01:00
|
|
|
stream->buffer, MAXLENGTH,
|
|
|
|
|
index % MAXLENGTH,
|
2021-06-18 23:46:50 +02:00
|
|
|
msg->data,
|
2022-01-28 16:21:03 +01:00
|
|
|
SPA_MIN(msg->length, MAXLENGTH));
|
2021-06-18 23:46:50 +02:00
|
|
|
index += msg->length;
|
|
|
|
|
spa_ringbuffer_write_update(&stream->ring, index);
|
2022-09-08 09:14:19 +02:00
|
|
|
|
|
|
|
|
stream->write_index += msg->length;
|
|
|
|
|
stream->requested -= msg->length;
|
2021-06-18 23:46:50 +02:00
|
|
|
|
2022-01-10 12:05:29 +01:00
|
|
|
stream_send_request(stream);
|
2022-01-07 10:59:41 +01:00
|
|
|
|
2022-11-20 16:13:22 +01:00
|
|
|
if (stream->is_paused && !stream->corked)
|
|
|
|
|
stream_set_paused(stream, false, "new data");
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
finish:
|
2022-06-16 19:54:13 +02:00
|
|
|
message_free(msg, false, false);
|
2021-06-18 23:46:50 +02:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int do_read(struct client *client)
|
|
|
|
|
{
|
|
|
|
|
struct impl * const impl = client->impl;
|
|
|
|
|
size_t size;
|
|
|
|
|
int res = 0;
|
|
|
|
|
void *data;
|
|
|
|
|
|
|
|
|
|
if (client->in_index < sizeof(client->desc)) {
|
|
|
|
|
data = SPA_PTROFF(&client->desc, client->in_index, void);
|
|
|
|
|
size = sizeof(client->desc) - client->in_index;
|
|
|
|
|
} else {
|
|
|
|
|
uint32_t idx = client->in_index - sizeof(client->desc);
|
|
|
|
|
|
2021-11-25 09:48:34 +01:00
|
|
|
if (client->message == NULL || client->message->length < idx) {
|
2021-06-18 23:46:50 +02:00
|
|
|
res = -EPROTO;
|
|
|
|
|
goto exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data = SPA_PTROFF(client->message->data, idx, void);
|
|
|
|
|
size = client->message->length - idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
ssize_t r = recv(client->source->fd, data, size, MSG_DONTWAIT);
|
|
|
|
|
|
|
|
|
|
if (r == 0 && size != 0) {
|
|
|
|
|
res = -EPIPE;
|
|
|
|
|
goto exit;
|
|
|
|
|
} else if (r < 0) {
|
|
|
|
|
if (errno == EINTR)
|
|
|
|
|
continue;
|
|
|
|
|
res = -errno;
|
2022-02-17 16:03:55 +01:00
|
|
|
if (res != -EAGAIN && res != -EWOULDBLOCK &&
|
|
|
|
|
res != -EPIPE && res != -ECONNRESET)
|
2021-06-18 23:46:50 +02:00
|
|
|
pw_log_warn("recv client:%p res %zd: %m", client, r);
|
|
|
|
|
goto exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client->in_index += r;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (client->in_index == sizeof(client->desc)) {
|
|
|
|
|
uint32_t flags, length, channel;
|
|
|
|
|
|
|
|
|
|
flags = ntohl(client->desc.flags);
|
|
|
|
|
if ((flags & FLAG_SHMMASK) != 0) {
|
|
|
|
|
res = -EPROTO;
|
|
|
|
|
goto exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
length = ntohl(client->desc.length);
|
|
|
|
|
if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) {
|
|
|
|
|
pw_log_warn("client %p: received invalid frame size: %u",
|
|
|
|
|
client, length);
|
|
|
|
|
res = -EPROTO;
|
|
|
|
|
goto exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channel = ntohl(client->desc.channel);
|
|
|
|
|
if (channel == (uint32_t) -1) {
|
|
|
|
|
if (flags != 0) {
|
|
|
|
|
pw_log_warn("client %p: received packet frame with invalid flags",
|
|
|
|
|
client);
|
|
|
|
|
res = -EPROTO;
|
|
|
|
|
goto exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (client->message)
|
2022-06-16 19:54:13 +02:00
|
|
|
message_free(client->message, false, false);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
client->message = message_alloc(impl, channel, length);
|
|
|
|
|
} else if (client->message &&
|
|
|
|
|
client->in_index >= client->message->length + sizeof(client->desc)) {
|
|
|
|
|
struct message * const msg = client->message;
|
|
|
|
|
|
|
|
|
|
client->message = NULL;
|
|
|
|
|
client->in_index = 0;
|
|
|
|
|
|
|
|
|
|
if (msg->channel == (uint32_t)-1)
|
|
|
|
|
res = handle_packet(client, msg);
|
|
|
|
|
else
|
|
|
|
|
res = handle_memblock(client, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exit:
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_client_data(void *data, int fd, uint32_t mask)
|
|
|
|
|
{
|
|
|
|
|
struct client * const client = data;
|
|
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
client->ref++;
|
|
|
|
|
|
|
|
|
|
if (mask & SPA_IO_HUP) {
|
|
|
|
|
res = -EPIPE;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mask & SPA_IO_ERR) {
|
|
|
|
|
res = -EIO;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mask & SPA_IO_IN) {
|
|
|
|
|
pw_log_trace("client %p: can read", client);
|
|
|
|
|
while (true) {
|
|
|
|
|
res = do_read(client);
|
|
|
|
|
if (res < 0) {
|
|
|
|
|
if (res != -EAGAIN && res != -EWOULDBLOCK)
|
|
|
|
|
goto error;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
if (mask & SPA_IO_OUT || client->new_msg_since_last_flush) {
|
2021-06-18 23:46:50 +02:00
|
|
|
res = client_flush_messages(client);
|
2021-11-11 12:36:43 +01:00
|
|
|
if (res < 0)
|
2021-06-18 23:46:50 +02:00
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
/* drop the reference that was acquired at the beginning of the function */
|
|
|
|
|
client_unref(client);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
switch (res) {
|
|
|
|
|
case -EPIPE:
|
2022-02-17 16:11:22 +01:00
|
|
|
case -ECONNRESET:
|
2021-06-18 23:46:50 +02:00
|
|
|
pw_log_info("server %p: client %p [%s] disconnected",
|
|
|
|
|
client->server, client, client->name);
|
|
|
|
|
SPA_FALLTHROUGH;
|
|
|
|
|
case -EPROTO:
|
|
|
|
|
/*
|
|
|
|
|
* drop the server's reference to the client
|
|
|
|
|
* (if it hasn't been dropped already),
|
|
|
|
|
* it is guaranteed that this will not call `client_free()`
|
|
|
|
|
* since at the beginning of this function an extra reference
|
|
|
|
|
* has been acquired which will keep the client alive
|
|
|
|
|
*/
|
|
|
|
|
if (client_detach(client))
|
|
|
|
|
client_unref(client);
|
|
|
|
|
|
|
|
|
|
/* then disconnect the client */
|
|
|
|
|
client_disconnect(client);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
pw_log_error("server %p: client %p [%s] error %d (%s)",
|
|
|
|
|
client->server, client, client->name, res, spa_strerror(res));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_connect(void *data, int fd, uint32_t mask)
|
|
|
|
|
{
|
|
|
|
|
struct server * const server = data;
|
|
|
|
|
struct impl * const impl = server->impl;
|
|
|
|
|
struct sockaddr_storage name;
|
|
|
|
|
socklen_t length;
|
2021-07-25 02:12:07 +02:00
|
|
|
int client_fd, val;
|
2021-06-18 23:46:50 +02:00
|
|
|
struct client *client = NULL;
|
2022-01-04 14:26:08 +01:00
|
|
|
const char *client_access = NULL;
|
2024-10-30 19:16:35 +02:00
|
|
|
const char *error_reason = NULL;
|
2021-07-25 02:12:07 +02:00
|
|
|
pid_t pid;
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
length = sizeof(name);
|
|
|
|
|
client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC);
|
|
|
|
|
if (client_fd < 0) {
|
|
|
|
|
if (errno == EMFILE || errno == ENFILE) {
|
|
|
|
|
if (server->n_clients > 0) {
|
2021-07-06 17:55:16 +02:00
|
|
|
int m = server->source->mask;
|
|
|
|
|
SPA_FLAG_CLEAR(m, SPA_IO_IN);
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_update_io(impl->main_loop, server->source, m);
|
2021-06-18 23:46:50 +02:00
|
|
|
server->wait_clients++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-13 19:53:06 +01:00
|
|
|
if (server->n_clients >= server->max_clients) {
|
2021-06-18 23:46:50 +02:00
|
|
|
close(client_fd);
|
2024-10-30 19:16:35 +02:00
|
|
|
error_reason = "too many client application connections";
|
2021-06-18 23:46:50 +02:00
|
|
|
errno = ECONNREFUSED;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 23:24:41 +01:00
|
|
|
client = client_new(server);
|
2021-06-18 23:46:50 +02:00
|
|
|
if (client == NULL)
|
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
|
|
pw_log_debug("server %p: new client %p fd:%d", server, client, client_fd);
|
|
|
|
|
|
2025-05-30 11:59:35 +02:00
|
|
|
client->source = pw_loop_add_io(impl->main_loop,
|
2021-06-18 23:46:50 +02:00
|
|
|
client_fd,
|
|
|
|
|
SPA_IO_ERR | SPA_IO_HUP | SPA_IO_IN,
|
|
|
|
|
true, on_client_data, client);
|
|
|
|
|
if (client->source == NULL)
|
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
|
|
client->props = pw_properties_new(
|
|
|
|
|
PW_KEY_CLIENT_API, "pipewire-pulse",
|
2022-04-22 14:54:27 +02:00
|
|
|
"config.ext", pw_properties_get(impl->props, "config.ext"),
|
2021-06-18 23:46:50 +02:00
|
|
|
NULL);
|
|
|
|
|
if (client->props == NULL)
|
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
|
|
pw_properties_setf(client->props,
|
|
|
|
|
"pulse.server.type", "%s",
|
|
|
|
|
server->addr.ss_family == AF_UNIX ? "unix" : "tcp");
|
|
|
|
|
|
|
|
|
|
client->routes = pw_properties_new(NULL, NULL);
|
|
|
|
|
if (client->routes == NULL)
|
|
|
|
|
goto error;
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (server->client_access[0] != '\0')
|
|
|
|
|
client_access = server->client_access;
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
if (server->addr.ss_family == AF_UNIX) {
|
2025-05-11 13:45:34 +03:00
|
|
|
spa_autofree char *app_id = NULL, *snap_app_id = NULL, *devices = NULL, *instance_id = NULL;
|
2023-11-22 11:26:16 +01:00
|
|
|
#ifdef HAVE_SNAP
|
|
|
|
|
pw_sandbox_access_t snap_access;
|
|
|
|
|
#endif
|
2022-07-31 02:32:32 +03:00
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
#ifdef SO_PRIORITY
|
|
|
|
|
val = 6;
|
|
|
|
|
if (setsockopt(client_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
|
|
|
|
|
pw_log_warn("setsockopt(SO_PRIORITY) failed: %m");
|
|
|
|
|
#endif
|
|
|
|
|
pid = get_client_pid(client, client_fd);
|
2025-05-11 13:45:34 +03:00
|
|
|
if (pid != 0 && pw_check_flatpak(pid, &app_id, &instance_id, &devices) == 1) {
|
2022-07-31 15:25:20 +03:00
|
|
|
/*
|
|
|
|
|
* XXX: we should really use Portal client access here
|
|
|
|
|
*
|
|
|
|
|
* However, session managers currently support only camera
|
|
|
|
|
* permissions, and the XDG Portal doesn't have a "Sound Manager"
|
|
|
|
|
* permission defined. So for now, use access=flatpak, and determine
|
|
|
|
|
* extra permissions here.
|
|
|
|
|
*
|
|
|
|
|
* The application has access to the Pulseaudio socket,
|
|
|
|
|
* and with real PA it would always then have full sound access.
|
|
|
|
|
* We'll restrict the full access here behind devices=all;
|
|
|
|
|
* if the application can access all devices it can then
|
|
|
|
|
* also sound and camera devices directly, so granting also the
|
|
|
|
|
* Manager permissions here is reasonable.
|
|
|
|
|
*
|
|
|
|
|
* The "Manager" permission in any case is also currently not safe
|
|
|
|
|
* as the session manager does not check any permission store
|
|
|
|
|
* for it.
|
|
|
|
|
*/
|
2022-01-04 14:26:08 +01:00
|
|
|
client_access = "flatpak";
|
2022-07-31 02:32:32 +03:00
|
|
|
pw_properties_set(client->props, "pipewire.access.portal.app_id",
|
|
|
|
|
app_id);
|
2025-05-11 13:45:34 +03:00
|
|
|
pw_properties_set(client->props, "pipewire.access.portal.instance_id",
|
|
|
|
|
instance_id);
|
2022-07-31 15:25:20 +03:00
|
|
|
|
|
|
|
|
if (devices && (spa_streq(devices, "all") ||
|
|
|
|
|
spa_strstartswith(devices, "all;") ||
|
|
|
|
|
strstr(devices, ";all;")))
|
|
|
|
|
pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, "Manager");
|
|
|
|
|
else
|
|
|
|
|
pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, NULL);
|
2022-07-31 02:32:32 +03:00
|
|
|
}
|
2023-11-22 11:26:16 +01:00
|
|
|
// check SNAP permissions
|
|
|
|
|
#ifdef HAVE_SNAP
|
2023-11-22 13:15:42 +01:00
|
|
|
snap_access = pw_snap_get_audio_permissions(client, client_fd, &snap_app_id);
|
2023-11-22 11:26:16 +01:00
|
|
|
if ((snap_access & PW_SANDBOX_ACCESS_NOT_A_SANDBOX) == 0) {
|
2023-11-22 13:15:42 +01:00
|
|
|
pw_properties_set(client->props, PW_KEY_SNAP_ID, snap_app_id);
|
2023-11-22 11:26:16 +01:00
|
|
|
|
|
|
|
|
pw_properties_set(client->props,
|
2023-11-22 13:50:25 +01:00
|
|
|
PW_KEY_SNAP_PLAYBACK_ALLOWED,
|
|
|
|
|
(snap_access & PW_SANDBOX_ACCESS_PLAYBACK) ? "true" : "false");
|
2023-11-22 11:26:16 +01:00
|
|
|
|
|
|
|
|
pw_properties_set(client->props,
|
2023-11-22 13:50:25 +01:00
|
|
|
PW_KEY_SNAP_RECORD_ALLOWED,
|
|
|
|
|
(snap_access & PW_SANDBOX_ACCESS_RECORD) ? "true" : "false");
|
2023-11-22 11:26:16 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
else if (server->addr.ss_family == AF_INET || server->addr.ss_family == AF_INET6) {
|
2022-01-04 14:26:08 +01:00
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
val = 1;
|
|
|
|
|
if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
|
|
|
|
|
pw_log_warn("setsockopt(TCP_NODELAY) failed: %m");
|
|
|
|
|
|
|
|
|
|
if (server->addr.ss_family == AF_INET) {
|
|
|
|
|
val = IPTOS_LOWDELAY;
|
|
|
|
|
if (setsockopt(client_fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0)
|
|
|
|
|
pw_log_warn("setsockopt(IP_TOS) failed: %m");
|
|
|
|
|
}
|
2022-01-04 14:26:08 +01:00
|
|
|
if (client_access == NULL)
|
|
|
|
|
client_access = "restricted";
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
2022-01-04 14:26:08 +01:00
|
|
|
pw_properties_set(client->props, PW_KEY_CLIENT_ACCESS, client_access);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
error:
|
2024-10-30 19:16:35 +02:00
|
|
|
pw_log_error("server %p: %s: %m", server,
|
|
|
|
|
error_reason ? error_reason : "failed to create client");
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
if (client)
|
|
|
|
|
client_free(client);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
static int parse_unix_address(const char *address, struct sockaddr_storage *addrs, int len)
|
2021-06-18 23:46:50 +02:00
|
|
|
{
|
2022-01-04 14:26:08 +01:00
|
|
|
struct sockaddr_un addr = {0};
|
2021-06-18 23:46:50 +02:00
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
if (address[0] != '/') {
|
|
|
|
|
char runtime_dir[PATH_MAX];
|
|
|
|
|
|
2022-06-09 17:06:07 +02:00
|
|
|
if ((res = get_runtime_dir(runtime_dir, sizeof(runtime_dir))) < 0)
|
2021-06-18 23:46:50 +02:00
|
|
|
return res;
|
|
|
|
|
|
|
|
|
|
res = snprintf(addr.sun_path, sizeof(addr.sun_path),
|
|
|
|
|
"%s/%s", runtime_dir, address);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
res = snprintf(addr.sun_path, sizeof(addr.sun_path),
|
|
|
|
|
"%s", address);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res < 0)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
if ((size_t) res >= sizeof(addr.sun_path)) {
|
|
|
|
|
pw_log_warn("'%s...' too long", addr.sun_path);
|
|
|
|
|
return -ENAMETOOLONG;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (len < 1)
|
|
|
|
|
return -ENOSPC;
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
memcpy(&addrs[0], &addr, sizeof(addr));
|
|
|
|
|
return 1;
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_SYSTEMD
|
|
|
|
|
static int check_systemd_activation(const char *path)
|
|
|
|
|
{
|
|
|
|
|
const int n = sd_listen_fds(0);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
const int fd = SD_LISTEN_FDS_START + i;
|
|
|
|
|
|
|
|
|
|
if (sd_is_socket_unix(fd, SOCK_STREAM, 1, path, 0) > 0)
|
|
|
|
|
return fd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
static inline int check_systemd_activation(SPA_UNUSED const char *path)
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
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("server %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;
|
|
|
|
|
pw_log_info("server %p: socket() failed: %m", server);
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (stat(addr_un->sun_path, &socket_stat) < 0) {
|
|
|
|
|
if (errno != ENOENT) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_warn("server %p: stat('%s') failed: %m",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) {
|
|
|
|
|
if (!S_ISSOCK(socket_stat.st_mode)) {
|
|
|
|
|
res = -EEXIST;
|
|
|
|
|
pw_log_warn("server %p: '%s' exists and is not a socket",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* socket is there, check if it's stale */
|
|
|
|
|
if (!is_stale_socket(fd, addr_un)) {
|
|
|
|
|
res = -EADDRINUSE;
|
|
|
|
|
pw_log_warn("server %p: socket '%s' is in use",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pw_log_warn("server %p: unlinking stale socket '%s'",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
|
|
|
|
|
if (unlink(addr_un->sun_path) < 0)
|
|
|
|
|
pw_log_warn("server %p: unlink('%s') failed: %m",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
}
|
|
|
|
|
if (bind(fd, (const struct sockaddr *) addr_un, SUN_LEN(addr_un)) < 0) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_warn("server %p: bind() to '%s' failed: %m",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-20 11:49:52 +02:00
|
|
|
if (chmod(addr_un->sun_path, 0777) < 0)
|
|
|
|
|
pw_log_warn("server %p: chmod('%s') failed: %m",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (listen(fd, server->listen_backlog) < 0) {
|
2021-06-18 23:46:50 +02:00
|
|
|
res = -errno;
|
|
|
|
|
pw_log_warn("server %p: listen() on '%s' failed: %m",
|
|
|
|
|
server, addr_un->sun_path);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pw_log_info("server %p: listening on unix:%s", server, addr_un->sun_path);
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
server->addr = *addr;
|
|
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
|
|
|
|
|
|
error_close:
|
|
|
|
|
close(fd);
|
|
|
|
|
error:
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (address[0] != '[')
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
address += 1;
|
|
|
|
|
|
|
|
|
|
end = strchr(address, ']');
|
|
|
|
|
if (end == NULL)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
len = end - address;
|
|
|
|
|
if (len >= sizeof(addr_str))
|
|
|
|
|
return -ENAMETOOLONG;
|
|
|
|
|
|
|
|
|
|
memcpy(addr_str, address, len);
|
|
|
|
|
addr_str[len] = '\0';
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
addr.sin6_port = htons(res);
|
|
|
|
|
addr.sin6_family = AF_INET6;
|
|
|
|
|
|
|
|
|
|
*out = addr;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_ipv4_address(const char *address, struct sockaddr_in *out)
|
|
|
|
|
{
|
|
|
|
|
char addr_str[INET_ADDRSTRLEN];
|
|
|
|
|
struct sockaddr_in addr = {0};
|
|
|
|
|
size_t len;
|
|
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
len = strspn(address, "0123456789.");
|
|
|
|
|
if (len == 0)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
if (len >= sizeof(addr_str))
|
|
|
|
|
return -ENAMETOOLONG;
|
|
|
|
|
|
|
|
|
|
memcpy(addr_str, address, len);
|
|
|
|
|
addr_str[len] = '\0';
|
|
|
|
|
|
|
|
|
|
res = inet_pton(AF_INET, addr_str, &addr.sin_addr.s_addr);
|
|
|
|
|
if (res < 0)
|
|
|
|
|
return -errno;
|
|
|
|
|
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;
|
2021-09-06 15:37:19 +10:00
|
|
|
bool is_ipv6 = false;
|
2021-06-18 23:46:50 +02:00
|
|
|
int port;
|
|
|
|
|
|
|
|
|
|
switch (addr->ss_family) {
|
|
|
|
|
case AF_INET:
|
|
|
|
|
src = &((struct sockaddr_in *) addr)->sin_addr.s_addr;
|
|
|
|
|
port = ntohs(((struct sockaddr_in *) addr)->sin_port);
|
|
|
|
|
break;
|
|
|
|
|
case AF_INET6:
|
2021-09-06 15:37:19 +10:00
|
|
|
is_ipv6 = true;
|
2021-06-18 23:46:50 +02:00
|
|
|
src = &((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr;
|
|
|
|
|
port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return -EAFNOSUPPORT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (inet_ntop(addr->ss_family, src, ip, sizeof(ip)) == NULL)
|
|
|
|
|
return -errno;
|
|
|
|
|
|
2021-09-06 15:37:19 +10:00
|
|
|
return snprintf(buffer, buflen, "%s%s%s:%d",
|
|
|
|
|
is_ipv6 ? "[" : "",
|
|
|
|
|
ip,
|
|
|
|
|
is_ipv6 ? "]" : "",
|
|
|
|
|
port);
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
static int parse_ip_address(const char *address, struct sockaddr_storage *addrs, int len)
|
2021-06-18 23:46:50 +02:00
|
|
|
{
|
|
|
|
|
char ip[FORMATTED_IP_ADDR_STRLEN];
|
2022-01-04 14:26:08 +01:00
|
|
|
struct sockaddr_storage addr;
|
2021-06-18 23:46:50 +02:00
|
|
|
int res;
|
|
|
|
|
|
|
|
|
|
res = parse_ipv6_address(address, (struct sockaddr_in6 *) &addr);
|
|
|
|
|
if (res == 0) {
|
2022-01-04 14:26:08 +01:00
|
|
|
if (len < 1)
|
|
|
|
|
return -ENOSPC;
|
|
|
|
|
addrs[0] = addr;
|
|
|
|
|
return 1;
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = parse_ipv4_address(address, (struct sockaddr_in *) &addr);
|
|
|
|
|
if (res == 0) {
|
2022-01-04 14:26:08 +01:00
|
|
|
if (len < 1)
|
|
|
|
|
return -ENOSPC;
|
|
|
|
|
addrs[0] = addr;
|
|
|
|
|
return 1;
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = parse_port(address);
|
|
|
|
|
if (res < 0)
|
|
|
|
|
return res;
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (len < 2)
|
|
|
|
|
return -ENOSPC;
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
snprintf(ip, sizeof(ip), "0.0.0.0:%d", res);
|
|
|
|
|
spa_assert_se(parse_ipv4_address(ip, (struct sockaddr_in *) &addr) == 0);
|
2022-12-01 20:17:08 +01:00
|
|
|
addrs[0] = addr;
|
|
|
|
|
|
|
|
|
|
snprintf(ip, sizeof(ip), "[::]:%d", res);
|
|
|
|
|
spa_assert_se(parse_ipv6_address(ip, (struct sockaddr_in6 *) &addr) == 0);
|
2022-01-04 14:26:08 +01:00
|
|
|
addrs[1] = addr;
|
2022-12-01 20:17:08 +01:00
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
return 2;
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
pw_log_warn("server %p: socket() failed: %m", server);
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
int on = 1;
|
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
|
|
|
|
|
pw_log_warn("server %p: setsockopt(SO_REUSEADDR) failed: %m", server);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (addr->ss_family == AF_INET6) {
|
|
|
|
|
int on = 1;
|
|
|
|
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
|
|
|
|
|
pw_log_warn("server %p: setsockopt(IPV6_V6ONLY) failed: %m", server);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bind(fd, (const struct sockaddr *) addr, get_ip_address_length(addr)) < 0) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_warn("server %p: bind() failed: %m", server);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (listen(fd, server->listen_backlog) < 0) {
|
2021-06-18 23:46:50 +02:00
|
|
|
res = -errno;
|
|
|
|
|
pw_log_warn("server %p: listen() failed: %m", server);
|
|
|
|
|
goto error_close;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spa_assert_se(format_ip_address(addr, ip, sizeof(ip)) >= 0);
|
|
|
|
|
pw_log_info("server %p: listening on tcp:%s", server, ip);
|
|
|
|
|
|
|
|
|
|
server->addr = *addr;
|
|
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
|
|
|
|
|
|
error_close:
|
|
|
|
|
close(fd);
|
|
|
|
|
error:
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct server *server_new(struct impl *impl)
|
|
|
|
|
{
|
|
|
|
|
struct server * const server = calloc(1, sizeof(*server));
|
|
|
|
|
if (server == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
server->impl = impl;
|
|
|
|
|
server->addr.ss_family = AF_UNSPEC;
|
|
|
|
|
spa_list_init(&server->clients);
|
|
|
|
|
spa_list_append(&impl->servers, &server->link);
|
|
|
|
|
|
|
|
|
|
pw_log_debug("server %p: new", server);
|
|
|
|
|
|
|
|
|
|
return server;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int server_start(struct server *server, const struct sockaddr_storage *addr)
|
|
|
|
|
{
|
2022-11-17 18:31:23 +01:00
|
|
|
struct impl * const impl = server->impl;
|
2021-06-18 23:46:50 +02:00
|
|
|
int res = 0, fd;
|
|
|
|
|
|
|
|
|
|
switch (addr->ss_family) {
|
|
|
|
|
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;
|
|
|
|
|
|
2025-05-30 11:59:35 +02:00
|
|
|
server->source = pw_loop_add_io(impl->main_loop, fd, SPA_IO_IN, true, on_connect, server);
|
2021-06-18 23:46:50 +02:00
|
|
|
if (server->source == NULL) {
|
|
|
|
|
res = -errno;
|
|
|
|
|
pw_log_error("server %p: can't create server source: %m", impl);
|
|
|
|
|
}
|
2022-11-17 18:31:23 +01:00
|
|
|
if (res >= 0)
|
|
|
|
|
spa_hook_list_call(&impl->hooks, struct impl_events, server_started, 0, server);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
static int parse_address(const char *address, struct sockaddr_storage *addrs, int len)
|
2021-06-18 23:46:50 +02:00
|
|
|
{
|
2021-11-11 23:32:40 +01:00
|
|
|
if (spa_strstartswith(address, "tcp:"))
|
2022-01-04 14:26:08 +01:00
|
|
|
return parse_ip_address(address + strlen("tcp:"), addrs, len);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
2021-11-11 23:32:40 +01:00
|
|
|
if (spa_strstartswith(address, "unix:"))
|
2022-01-04 14:26:08 +01:00
|
|
|
return parse_unix_address(address + strlen("unix:"), addrs, len);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
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 servers_create_and_start(struct impl *impl, const char *addresses, struct pw_array *servers)
|
|
|
|
|
{
|
2022-01-04 14:26:08 +01:00
|
|
|
int len, res, count = 0, err = 0; /* store the first error to return when no servers could be created */
|
|
|
|
|
const char *v;
|
2024-09-13 13:09:54 +02:00
|
|
|
struct spa_json it[2];
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
/* 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` */
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
/* [ <server-spec> ... ] */
|
2024-09-13 13:09:54 +02:00
|
|
|
if (spa_json_begin_array(&it[0], addresses, strlen(addresses)) < 0)
|
2021-06-18 23:46:50 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
/* a server-spec is either an address or an object */
|
2024-09-13 13:09:54 +02:00
|
|
|
while ((len = spa_json_next(&it[0], &v)) > 0) {
|
2022-01-04 14:26:08 +01:00
|
|
|
char addr_str[FORMATTED_SOCKET_ADDR_STRLEN] = { 0 };
|
|
|
|
|
char key[128], client_access[64] = { 0 };
|
2022-01-21 10:25:46 +01:00
|
|
|
struct sockaddr_storage addrs[2];
|
2022-01-04 14:26:08 +01:00
|
|
|
int i, max_clients = MAX_CLIENTS, listen_backlog = LISTEN_BACKLOG, n_addr;
|
|
|
|
|
|
|
|
|
|
if (spa_json_is_object(v, len)) {
|
2024-09-13 13:09:54 +02:00
|
|
|
spa_json_enter(&it[0], &it[1]);
|
2024-09-13 16:26:36 +02:00
|
|
|
while ((len = spa_json_object_next(&it[1], key, sizeof(key), &v)) > 0) {
|
2022-01-04 14:26:08 +01:00
|
|
|
if (spa_streq(key, "address")) {
|
|
|
|
|
spa_json_parse_stringn(v, len, addr_str, sizeof(addr_str));
|
|
|
|
|
} else if (spa_streq(key, "max-clients")) {
|
|
|
|
|
spa_json_parse_int(v, len, &max_clients);
|
|
|
|
|
} else if (spa_streq(key, "listen-backlog")) {
|
|
|
|
|
spa_json_parse_int(v, len, &listen_backlog);
|
|
|
|
|
} else if (spa_streq(key, "client.access")) {
|
|
|
|
|
spa_json_parse_stringn(v, len, client_access, sizeof(client_access));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
spa_json_parse_stringn(v, len, addr_str, sizeof(addr_str));
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-21 10:25:46 +01:00
|
|
|
n_addr = parse_address(addr_str, addrs, SPA_N_ELEMENTS(addrs));
|
2022-01-04 14:26:08 +01:00
|
|
|
if (n_addr < 0) {
|
|
|
|
|
pw_log_warn("pulse-server %p: failed to parse address '%s': %s",
|
|
|
|
|
impl, addr_str, spa_strerror(n_addr));
|
|
|
|
|
UPDATE_ERR(n_addr);
|
2021-06-18 23:46:50 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
/* try to create sockets for each address in the list */
|
|
|
|
|
for (i = 0; i < n_addr; i++) {
|
2022-01-21 10:25:46 +01:00
|
|
|
const struct sockaddr_storage *addr = &addrs[i];
|
2022-01-04 14:26:08 +01:00
|
|
|
struct server * const server = server_new(impl);
|
2022-01-21 10:25:46 +01:00
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (server == NULL) {
|
|
|
|
|
UPDATE_ERR(-errno);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-06-18 23:46:50 +02:00
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
server->max_clients = max_clients;
|
|
|
|
|
server->listen_backlog = listen_backlog;
|
|
|
|
|
memcpy(server->client_access, client_access, sizeof(client_access));
|
2021-06-18 23:46:50 +02:00
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
res = server_start(server, addr);
|
|
|
|
|
if (res < 0) {
|
|
|
|
|
spa_assert_se(format_socket_address(addr, addr_str, sizeof(addr_str)) >= 0);
|
|
|
|
|
pw_log_warn("pulse-server %p: failed to start server on '%s': %s",
|
|
|
|
|
impl, addr_str, spa_strerror(res));
|
|
|
|
|
UPDATE_ERR(res);
|
|
|
|
|
server_free(server);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-06-18 23:46:50 +02:00
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
if (servers != NULL)
|
|
|
|
|
pw_array_add_ptr(servers, server);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
2022-01-04 14:26:08 +01:00
|
|
|
count += 1;
|
|
|
|
|
}
|
2021-06-18 23:46:50 +02:00
|
|
|
}
|
|
|
|
|
if (count == 0) {
|
|
|
|
|
UPDATE_ERR(-EINVAL);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
|
|
|
|
|
#undef UPDATE_ERR
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void server_free(struct server *server)
|
|
|
|
|
{
|
|
|
|
|
struct impl * const impl = server->impl;
|
|
|
|
|
struct client *c, *t;
|
|
|
|
|
|
|
|
|
|
pw_log_debug("server %p: free", server);
|
|
|
|
|
|
|
|
|
|
spa_list_remove(&server->link);
|
|
|
|
|
|
|
|
|
|
spa_list_for_each_safe(c, t, &server->clients, link) {
|
|
|
|
|
spa_assert_se(client_detach(c));
|
|
|
|
|
client_unref(c);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 18:31:23 +01:00
|
|
|
spa_hook_list_call(&impl->hooks, struct impl_events, server_stopped, 0, server);
|
|
|
|
|
|
2021-06-18 23:46:50 +02:00
|
|
|
if (server->source)
|
2025-05-30 11:59:35 +02:00
|
|
|
pw_loop_destroy_source(impl->main_loop, server->source);
|
2021-06-18 23:46:50 +02:00
|
|
|
|
|
|
|
|
if (server->addr.ss_family == AF_UNIX && !server->activated)
|
|
|
|
|
unlink(((const struct sockaddr_un *) &server->addr)->sun_path);
|
|
|
|
|
|
|
|
|
|
free(server);
|
|
|
|
|
}
|