mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
module-protocol-native: add module argument 'sockets'
Add module argument 'sockets' for creating multiple sockets clients can connect to. Also allow setting socket file permissions.
This commit is contained in:
parent
7a0b400c18
commit
72462ebd07
5 changed files with 229 additions and 11 deletions
|
|
@ -232,6 +232,7 @@ check_headers = [
|
||||||
['sys/random.h', 'HAVE_SYS_RANDOM_H'],
|
['sys/random.h', 'HAVE_SYS_RANDOM_H'],
|
||||||
['sys/vfs.h', 'HAVE_SYS_VFS_H'],
|
['sys/vfs.h', 'HAVE_SYS_VFS_H'],
|
||||||
['pwd.h', 'HAVE_PWD_H'],
|
['pwd.h', 'HAVE_PWD_H'],
|
||||||
|
['grp.h', 'HAVE_GRP_H'],
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach h : check_headers
|
foreach h : check_headers
|
||||||
|
|
@ -247,6 +248,10 @@ summary({'systemd conf data': systemd.found()}, bool_yn: true)
|
||||||
summary({'libsystemd': systemd_dep.found()}, bool_yn: true)
|
summary({'libsystemd': systemd_dep.found()}, bool_yn: true)
|
||||||
cdata.set('HAVE_SYSTEMD', systemd.found() and systemd_dep.found())
|
cdata.set('HAVE_SYSTEMD', systemd.found() and systemd_dep.found())
|
||||||
|
|
||||||
|
selinux_dep = dependency('libselinux', required: get_option('selinux'))
|
||||||
|
summary({'libselinux': selinux_dep.found()}, bool_yn: true)
|
||||||
|
cdata.set('HAVE_SELINUX', selinux_dep.found())
|
||||||
|
|
||||||
configinc = include_directories('.')
|
configinc = include_directories('.')
|
||||||
includes_inc = include_directories('include')
|
includes_inc = include_directories('include')
|
||||||
pipewire_inc = include_directories('src')
|
pipewire_inc = include_directories('src')
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@ option('systemd-user-service',
|
||||||
description: 'Install systemd user service file (ignored without systemd)',
|
description: 'Install systemd user service file (ignored without systemd)',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
value: 'enabled')
|
value: 'enabled')
|
||||||
|
option('selinux',
|
||||||
|
description: 'Enable SELinux integration',
|
||||||
|
type: 'feature',
|
||||||
|
value: 'auto')
|
||||||
option('pipewire-alsa',
|
option('pipewire-alsa',
|
||||||
description: 'Enable pipewire-alsa integration',
|
description: 'Enable pipewire-alsa integration',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,12 @@ context.modules = [
|
||||||
}
|
}
|
||||||
|
|
||||||
# The native communication protocol.
|
# The native communication protocol.
|
||||||
{ name = libpipewire-module-protocol-native }
|
{ name = libpipewire-module-protocol-native
|
||||||
|
args = {
|
||||||
|
# List of server Unix sockets, and optionally permissions
|
||||||
|
#sockets = [ { name = "pipewire-0" }, { name = "pipewire-manager-0" } ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# The profile module. Allows application to access profiler
|
# The profile module. Allows application to access profiler
|
||||||
# and performance data. It provides an interface that is used
|
# and performance data. It provides an interface that is used
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,10 @@ if systemd_dep.found()
|
||||||
pipewire_module_protocol_deps += systemd_dep
|
pipewire_module_protocol_deps += systemd_dep
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if selinux_dep.found()
|
||||||
|
pipewire_module_protocol_deps += selinux_dep
|
||||||
|
endif
|
||||||
|
|
||||||
pipewire_module_protocol_native = shared_library('pipewire-module-protocol-native',
|
pipewire_module_protocol_native = shared_library('pipewire-module-protocol-native',
|
||||||
[ 'module-protocol-native.c',
|
[ 'module-protocol-native.c',
|
||||||
'module-protocol-native/local-socket.c',
|
'module-protocol-native/local-socket.c',
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,13 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/file.h>
|
#include <sys/file.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
#ifdef HAVE_PWD_H
|
#ifdef HAVE_PWD_H
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_GRP_H
|
||||||
|
#include <grp.h>
|
||||||
|
#endif
|
||||||
#if defined(__FreeBSD__) || defined(__MidnightBSD__)
|
#if defined(__FreeBSD__) || defined(__MidnightBSD__)
|
||||||
#include <sys/ucred.h>
|
#include <sys/ucred.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -27,13 +31,19 @@
|
||||||
#include <spa/pod/builder.h>
|
#include <spa/pod/builder.h>
|
||||||
#include <spa/utils/result.h>
|
#include <spa/utils/result.h>
|
||||||
#include <spa/utils/string.h>
|
#include <spa/utils/string.h>
|
||||||
|
#include <spa/utils/json.h>
|
||||||
|
|
||||||
#ifdef HAVE_SYSTEMD
|
#ifdef HAVE_SYSTEMD
|
||||||
#include <systemd/sd-daemon.h>
|
#include <systemd/sd-daemon.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SELINUX
|
||||||
|
#include <selinux/selinux.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <pipewire/impl.h>
|
#include <pipewire/impl.h>
|
||||||
#include <pipewire/extensions/protocol-native.h>
|
#include <pipewire/extensions/protocol-native.h>
|
||||||
|
#include <pipewire/cleanup.h>
|
||||||
|
|
||||||
#include "pipewire/private.h"
|
#include "pipewire/private.h"
|
||||||
|
|
||||||
|
|
@ -63,7 +73,17 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME);
|
||||||
*
|
*
|
||||||
* ## Module Options
|
* ## Module Options
|
||||||
*
|
*
|
||||||
* The module has no options.
|
* The module supports the following arguments:
|
||||||
|
*
|
||||||
|
* - `sockets`: `[ { name = "socket-name", owner = "owner", group = "group", mode = "mode", selinux.context = "context" }, ... ]`
|
||||||
|
*
|
||||||
|
* Array of Unix socket names and (optionally) owner/permissions to serve,
|
||||||
|
* if the context is a server. If not absolute paths, the sockets are created
|
||||||
|
* in the default runtime directory. If not specified, one socket with
|
||||||
|
* a default name is created.
|
||||||
|
*
|
||||||
|
* The permissions have no effect for sockets from Systemd socket activation.
|
||||||
|
* Those should be configured via the systemd.socket(5) mechanism.
|
||||||
*
|
*
|
||||||
* ## General Options
|
* ## General Options
|
||||||
*
|
*
|
||||||
|
|
@ -107,6 +127,13 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME);
|
||||||
{ name = libpipewire-module-protocol-native }
|
{ name = libpipewire-module-protocol-native }
|
||||||
* ]
|
* ]
|
||||||
*\endcode
|
*\endcode
|
||||||
|
*
|
||||||
|
*\code{.unparsed}
|
||||||
|
* context.modules = [
|
||||||
|
* { name = libpipewire-module-protocol-native,
|
||||||
|
* args = { sockets = [ { name = "pipewire-0" }, { name = "pipewire-1" } ] } }
|
||||||
|
* ]
|
||||||
|
*\endcode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef UNIX_PATH_MAX
|
#ifndef UNIX_PATH_MAX
|
||||||
|
|
@ -165,12 +192,23 @@ static void client_unref(struct client *impl)
|
||||||
free(impl);
|
free(impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct socket_info {
|
||||||
|
char *name;
|
||||||
|
uid_t uid;
|
||||||
|
gid_t gid;
|
||||||
|
int mode;
|
||||||
|
char *selinux_context;
|
||||||
|
unsigned int has_owner:1;
|
||||||
|
unsigned int has_mode:1;
|
||||||
|
};
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
struct pw_protocol_server this;
|
struct pw_protocol_server this;
|
||||||
|
|
||||||
int fd_lock;
|
int fd_lock;
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN];
|
char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN];
|
||||||
|
struct socket_info socket_info;
|
||||||
|
|
||||||
struct pw_loop *loop;
|
struct pw_loop *loop;
|
||||||
struct spa_source *source;
|
struct spa_source *source;
|
||||||
|
|
@ -789,6 +827,31 @@ error:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int set_socket_permissions(struct server *s)
|
||||||
|
{
|
||||||
|
struct socket_info *info = &s->socket_info;
|
||||||
|
const char *path = s->addr.sun_path;
|
||||||
|
|
||||||
|
if (info->has_owner)
|
||||||
|
if (chown(path, info->uid, info->gid) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (info->has_mode)
|
||||||
|
if (chmod(path, info->mode) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (info->selinux_context) {
|
||||||
|
#ifdef HAVE_SELINUX
|
||||||
|
if (setfilecon(path, info->selinux_context) < 0)
|
||||||
|
return -errno;
|
||||||
|
#else
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int add_socket(struct pw_protocol *protocol, struct server *s)
|
static int add_socket(struct pw_protocol *protocol, struct server *s)
|
||||||
{
|
{
|
||||||
socklen_t size;
|
socklen_t size;
|
||||||
|
|
@ -836,11 +899,22 @@ static int add_socket(struct pw_protocol *protocol, struct server *s)
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((res = set_socket_permissions(s)) < 0) {
|
||||||
|
errno = -res;
|
||||||
|
pw_log_error("server %p: failed to set socket %s permissions: %m",
|
||||||
|
s, s->socket_info.name);
|
||||||
|
goto error_close;
|
||||||
|
}
|
||||||
|
|
||||||
if (listen(fd, 128) < 0) {
|
if (listen(fd, 128) < 0) {
|
||||||
res = -errno;
|
res = -errno;
|
||||||
pw_log_error("server %p: listen() failed with error: %m", s);
|
pw_log_error("server %p: listen() failed with error: %m", s);
|
||||||
goto error_close;
|
goto error_close;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (s->socket_info.has_owner || s->socket_info.has_mode || s->socket_info.selinux_context)
|
||||||
|
pw_log_info("server %p: permissions ignored for socket %s from systemd",
|
||||||
|
s, s->socket_info.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
res = write_socket_address(s);
|
res = write_socket_address(s);
|
||||||
|
|
@ -1250,6 +1324,8 @@ static void destroy_server(struct pw_protocol_server *server)
|
||||||
unlink(s->lock_addr);
|
unlink(s->lock_addr);
|
||||||
if (s->fd_lock != -1)
|
if (s->fd_lock != -1)
|
||||||
close(s->fd_lock);
|
close(s->fd_lock);
|
||||||
|
free(s->socket_info.name);
|
||||||
|
free(s->socket_info.selinux_context);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1311,9 +1387,10 @@ create_server(struct pw_protocol *protocol,
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct pw_protocol_server *
|
static struct pw_protocol_server *
|
||||||
impl_add_server(struct pw_protocol *protocol,
|
add_server(struct pw_protocol *protocol,
|
||||||
struct pw_impl_core *core,
|
struct pw_impl_core *core,
|
||||||
const struct spa_dict *props)
|
const struct spa_dict *props,
|
||||||
|
struct socket_info *socket_info)
|
||||||
{
|
{
|
||||||
struct pw_protocol_server *this;
|
struct pw_protocol_server *this;
|
||||||
struct server *s;
|
struct server *s;
|
||||||
|
|
@ -1325,7 +1402,16 @@ impl_add_server(struct pw_protocol *protocol,
|
||||||
|
|
||||||
this = &s->this;
|
this = &s->this;
|
||||||
|
|
||||||
name = get_server_name(props);
|
if (socket_info) {
|
||||||
|
s->socket_info = *socket_info;
|
||||||
|
s->socket_info.name = strdup(socket_info->name);
|
||||||
|
s->socket_info.selinux_context = socket_info->selinux_context ?
|
||||||
|
strdup(socket_info->selinux_context) : NULL;
|
||||||
|
name = socket_info->name;
|
||||||
|
} else {
|
||||||
|
name = get_server_name(props);
|
||||||
|
s->socket_info.name = strdup(name);
|
||||||
|
}
|
||||||
|
|
||||||
if ((res = init_socket_name(s, name)) < 0)
|
if ((res = init_socket_name(s, name)) < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
@ -1349,6 +1435,14 @@ error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct pw_protocol_server *
|
||||||
|
impl_add_server(struct pw_protocol *protocol,
|
||||||
|
struct pw_impl_core *core,
|
||||||
|
const struct spa_dict *props)
|
||||||
|
{
|
||||||
|
return add_server(protocol, core, props, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct pw_protocol_implementation protocol_impl = {
|
static const struct pw_protocol_implementation protocol_impl = {
|
||||||
PW_VERSION_PROTOCOL_IMPLEMENTATION,
|
PW_VERSION_PROTOCOL_IMPLEMENTATION,
|
||||||
.new_client = impl_new_client,
|
.new_client = impl_new_client,
|
||||||
|
|
@ -1462,14 +1556,121 @@ static int need_server(struct pw_context *context, const struct spa_dict *props)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int create_servers(struct pw_protocol *this, struct pw_impl_core *core,
|
||||||
|
const struct pw_properties *props, const struct pw_properties *args)
|
||||||
|
{
|
||||||
|
const char *sockets = args ? pw_properties_get(args, "sockets") : NULL;
|
||||||
|
struct spa_json it[3];
|
||||||
|
|
||||||
|
if (sockets == NULL) {
|
||||||
|
if (add_server(this, core, &props->dict, NULL) == NULL)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
spa_json_init(&it[0], sockets, strlen(sockets));
|
||||||
|
|
||||||
|
if (spa_json_enter_array(&it[0], &it[1]) <= 0)
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
while (spa_json_enter_object(&it[1], &it[2]) > 0) {
|
||||||
|
struct socket_info info = {0};
|
||||||
|
char key[256];
|
||||||
|
char name[PATH_MAX];
|
||||||
|
char selinux_context[PATH_MAX];
|
||||||
|
|
||||||
|
info.uid = getuid();
|
||||||
|
info.gid = getgid();
|
||||||
|
|
||||||
|
while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
|
||||||
|
const char *value;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if ((len = spa_json_next(&it[2], &value)) <= 0)
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
if (spa_streq(key, "name")) {
|
||||||
|
if (spa_json_parse_stringn(value, len, name, sizeof(name)) < 0)
|
||||||
|
goto error_invalid;
|
||||||
|
info.name = name;
|
||||||
|
} else if (spa_streq(key, "selinux.context")) {
|
||||||
|
if (spa_json_parse_stringn(value, len, selinux_context, sizeof(selinux_context)) < 0)
|
||||||
|
goto error_invalid;
|
||||||
|
info.selinux_context = selinux_context;
|
||||||
|
} else if (spa_streq(key, "owner")) {
|
||||||
|
char buffer[16384];
|
||||||
|
char owner[PATH_MAX];
|
||||||
|
struct passwd pwd, *result = NULL;
|
||||||
|
int64_t val;
|
||||||
|
|
||||||
|
if (spa_json_parse_stringn(value, len, owner, sizeof(owner)) < 0)
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
if (spa_atoi64(owner, &val, 10))
|
||||||
|
info.uid = val;
|
||||||
|
else if (getpwnam_r(owner, &pwd, buffer, sizeof(buffer), &result) == 0 && result)
|
||||||
|
info.uid = result->pw_uid;
|
||||||
|
else
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
info.has_owner = true;
|
||||||
|
} else if (spa_streq(key, "group")) {
|
||||||
|
char buffer[16384];
|
||||||
|
char group[PATH_MAX];
|
||||||
|
struct group grp, *result = NULL;
|
||||||
|
int64_t val;
|
||||||
|
|
||||||
|
if (spa_json_parse_stringn(value, len, group, sizeof(group)) < 0)
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
if (spa_atoi64(group, &val, 10))
|
||||||
|
info.gid = val;
|
||||||
|
else if (getgrnam_r(group, &grp, buffer, sizeof(buffer), &result) == 0 && result)
|
||||||
|
info.gid = result->gr_gid;
|
||||||
|
else
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
info.has_owner = true;
|
||||||
|
} else if (spa_streq(key, "mode")) {
|
||||||
|
char mode[PATH_MAX];
|
||||||
|
int64_t val;
|
||||||
|
|
||||||
|
if (spa_json_parse_stringn(value, len, mode, sizeof(mode)) < 0)
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
if (spa_atoi64(mode, &val, 0))
|
||||||
|
info.mode = val;
|
||||||
|
else
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
info.has_mode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.name == NULL)
|
||||||
|
goto error_invalid;
|
||||||
|
|
||||||
|
if (add_server(this, core, &props->dict, &info) == NULL)
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error_invalid:
|
||||||
|
pw_log_error("invalid module 'sockets' argument: %s", sockets);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
SPA_EXPORT
|
SPA_EXPORT
|
||||||
int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
int pipewire__module_init(struct pw_impl_module *module, const char *args_str)
|
||||||
{
|
{
|
||||||
struct pw_context *context = pw_impl_module_get_context(module);
|
struct pw_context *context = pw_impl_module_get_context(module);
|
||||||
struct pw_protocol *this;
|
struct pw_protocol *this;
|
||||||
struct pw_impl_core *core = context->core;
|
struct pw_impl_core *core = context->core;
|
||||||
struct protocol_data *d;
|
struct protocol_data *d;
|
||||||
const struct pw_properties *props;
|
const struct pw_properties *props;
|
||||||
|
spa_autoptr(pw_properties) args = NULL;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
PW_LOG_TOPIC_INIT(mod_topic);
|
PW_LOG_TOPIC_INIT(mod_topic);
|
||||||
|
|
@ -1480,6 +1681,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
return -EEXIST;
|
return -EEXIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args = args_str ? pw_properties_new_string(args_str) : NULL;
|
||||||
|
|
||||||
this = pw_protocol_new(context, PW_TYPE_INFO_PROTOCOL_Native, sizeof(struct protocol_data));
|
this = pw_protocol_new(context, PW_TYPE_INFO_PROTOCOL_Native, sizeof(struct protocol_data));
|
||||||
if (this == NULL)
|
if (this == NULL)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
@ -1501,12 +1704,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
props = pw_context_get_properties(context);
|
props = pw_context_get_properties(context);
|
||||||
d->local = create_server(this, core, &props->dict);
|
d->local = create_server(this, core, &props->dict);
|
||||||
|
|
||||||
if (need_server(context, &props->dict)) {
|
if (need_server(context, &props->dict))
|
||||||
if (impl_add_server(this, core, &props->dict) == NULL) {
|
if ((res = create_servers(this, core, props, args)) < 0)
|
||||||
res = -errno;
|
|
||||||
goto error_cleanup;
|
goto error_cleanup;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pw_impl_module_add_listener(module, &d->module_listener, &module_events, d);
|
pw_impl_module_add_listener(module, &d->module_listener, &module_events, d);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue