media-session: fix linking nodes with targets to wrong nodes

For nodes that have node.target set, when the target is not available,
media-session links them to any available node.  If the target node
appears afterward, media-session would not re-link them, leading to
output being directed to wrong place (e.g. pavucontrol input monitor)
even though the intended target or a better fallback is available.

This occurs e.g. when devices are removed and re-added.

Fix this by (i) for reconnectable nodes, reconnect them if target
appears later, and (ii) for non-reconnectable nodes, raise error to the
client if node.target is set but not found (but proceed to fallback if
obj->target_node is set).

Also slightly reorganize policy-node.c:rescan_node for clarity.
This commit is contained in:
Pauli Virtanen 2021-08-07 15:24:15 +03:00
parent 780f2d645d
commit e2d810a9b9

View file

@ -762,12 +762,10 @@ static int rescan_node(struct impl *impl, struct node *n)
struct spa_dict *props;
const char *str;
bool exclusive, reconnect, autoconnect;
struct find_data find;
struct pw_node_info *info;
struct node *peer;
struct sm_object *obj;
uint32_t path_id;
bool follows_default;
if (!n->active) {
pw_log_debug(NAME " %p: node %d is not active", impl, n->id);
@ -794,18 +792,6 @@ static int rescan_node(struct impl *impl, struct node *n)
str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT);
reconnect = str ? !pw_properties_parse_bool(str) : true;
follows_default = (impl->streams_follow_default &&
n->type == NODE_TYPE_STREAM &&
reconnect &&
n->obj->target_node == NULL &&
((str = spa_dict_lookup(props, PW_KEY_NODE_TARGET)) == NULL ||
(uint32_t)atoi(str) == SPA_ID_INVALID));
if (n->peer != NULL && !follows_default) {
pw_log_debug(NAME " %p: node %d is already linked", impl, n->id);
return 0;
}
if ((str = spa_dict_lookup(props, PW_KEY_STREAM_DONT_REMIX)) != NULL)
n->dont_remix = pw_properties_parse_bool(str);
@ -837,42 +823,41 @@ static int rescan_node(struct impl *impl, struct node *n)
str = spa_dict_lookup(props, PW_KEY_NODE_EXCLUSIVE);
exclusive = str ? pw_properties_parse_bool(str) : false;
pw_log_debug(NAME " %p: exclusive:%d", impl, exclusive);
spa_zero(find);
find.impl = impl;
find.media = n->media;
find.capture_sink = n->capture_sink;
find.direction = n->direction;
find.exclusive = exclusive;
find.link_group = n->peer == NULL ? spa_dict_lookup(props, PW_KEY_NODE_LINK_GROUP) : NULL;
/* we always honour the target node asked for by the client */
path_id = SPA_ID_INVALID;
if ((str = spa_dict_lookup(props, PW_KEY_NODE_TARGET)) != NULL)
if ((str = spa_dict_lookup(props, PW_KEY_NODE_TARGET)) != NULL) {
bool has_target = ((uint32_t)atoi(str) != SPA_ID_INVALID);
path_id = find_device_for_name(impl, str);
if (!reconnect && has_target && path_id == SPA_ID_INVALID) {
/* don't use fallbacks for non-reconnecting nodes */
peer = NULL;
goto do_link;
}
}
if (path_id == SPA_ID_INVALID && n->obj->target_node != NULL)
path_id = find_device_for_name(impl, n->obj->target_node);
pw_log_info("trying to link node %d exclusive:%d reconnect:%d target:%d follows-default:%d", n->id,
exclusive, reconnect, path_id, follows_default);
if (n->peer != NULL) {
spa_list_for_each(peer, &impl->node_list, link)
find_node(&find, peer);
if (follows_default && find.node != NULL &&
find.node != n->peer && can_link(impl, n, find.node)) {
pw_log_debug(NAME " %p: node %d follows default, changed (%d -> %d)", impl, n->id,
n->peer->id, find.node->id);
unlink_nodes(n, n->peer);
} else {
pw_log_debug(NAME " %p: node %d already linked (not changing)", impl, n->id);
/* Do we need to check again where to link to? */
bool target_found = (path_id != SPA_ID_INVALID);
bool peer_is_target = (target_found && n->peer->obj->obj.id == path_id);
bool follows_default = (impl->streams_follow_default &&
n->type == NODE_TYPE_STREAM);
bool recheck = !peer_is_target && (follows_default || target_found) &&
reconnect;
if (!recheck) {
pw_log_debug(NAME " %p: node %d is already linked, peer-is-target:%d "
"follows-default:%d", impl, n->id, peer_is_target,
follows_default);
return 0;
}
}
pw_log_info("trying to link node %d exclusive:%d reconnect:%d target:%d",
n->id, exclusive, reconnect, path_id);
if (path_id != SPA_ID_INVALID) {
pw_log_debug(NAME " %p: target:%d", impl, path_id);
@ -884,23 +869,35 @@ static int rescan_node(struct impl *impl, struct node *n)
path_id, obj->type);
if (spa_streq(obj->type, PW_TYPE_INTERFACE_Node)) {
peer = sm_object_get_data(obj, SESSION_KEY);
if (peer == NULL)
return -ENOENT;
goto do_link;
}
}
pw_log_warn("node %d target:%d not found, find fallback:%d", n->id,
path_id, reconnect);
}
if (path_id == SPA_ID_INVALID && (reconnect || n->connect_count == 0)) {
if (find.node == NULL)
/* find fallback */
struct find_data find;
spa_zero(find);
find.impl = impl;
find.media = n->media;
find.capture_sink = n->capture_sink;
find.direction = n->direction;
find.exclusive = exclusive;
find.link_group = n->peer == NULL ? spa_dict_lookup(props, PW_KEY_NODE_LINK_GROUP) : NULL;
spa_list_for_each(peer, &impl->node_list, link)
find_node(&find, peer);
peer = find.node;
} else {
find.node = NULL;
peer = NULL;
}
if (find.node == NULL) {
do_link:
if (peer == NULL) {
struct sm_object *obj;
if (!reconnect) {
@ -925,8 +922,11 @@ static int rescan_node(struct impl *impl, struct node *n)
n->id, -ENOENT, "no node available");
}
return -ENOENT;
} else if (peer == n->peer) {
pw_log_debug(NAME " %p: node %d already linked to %d (not changing)",
impl, n->id, peer->id);
return 0;
}
peer = find.node;
if (exclusive && peer->obj->info->state == PW_NODE_STATE_RUNNING) {
pw_log_warn("node %d busy, can't get exclusive access", peer->id);
@ -934,15 +934,21 @@ static int rescan_node(struct impl *impl, struct node *n)
}
n->exclusive = exclusive;
pw_log_debug(NAME" %p: linking to node '%d'", impl, peer->id);
if (n->peer != NULL) {
pw_log_debug(NAME " %p: node %d change link (%d -> %d)", impl, n->id,
n->peer->id, peer->id);
unlink_nodes(n, n->peer);
}
do_link:
if (peer == n->failed_peer && n->failed_count > MAX_LINK_RETRY) {
/* Break rescan -> failed link -> rescan loop. */
pw_log_debug(NAME" %p: tried to link '%d' on last rescan, not retrying",
impl, peer->id);
return 0;
}
pw_log_debug(NAME" %p: linking node %d to node %d", impl, n->id, peer->id);
link_nodes(n, peer);
return 1;
}