When a virtual sink is loaded and there is only one sound card, then during a profile switch, all sinks and sources can become temporarily unavailable. In that situation, the virtual sink will become the default sink, although it is not connected to a master sink due to the move operation initiated by the profile switch. If module-always sink is loaded, it will load a null-sink in that situation. If also module-switch-on-connect is present, it will change the default sink to the new null sink and try to move the sink-inputs from the virtual sink to the null sink. This leads to a segfault because the master sink of the virtual sink is invalid. This patch fixes the issue by disallowing a moving virtual sink to become the default sink. The same applies to the source side. The is_filter_{sink,source}_moving() functions were stolen from one of Tanu's previous unapplied patches. Buglink: https://bugs.freedesktop.org/show_bug.cgi?id=100277 --- src/pulsecore/core.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c index 52e51db1..91a9da84 100644 --- a/src/pulsecore/core.c +++ b/src/pulsecore/core.c @@ -258,6 +258,23 @@ void pa_core_set_configured_default_source(pa_core *core, pa_source *source) { pa_core_update_default_source(core); } +/* Test if a sink is a moving filter sink */ +static bool is_filter_sink_moving(pa_sink *s) { + pa_sink *sink = s; + + if (!sink) + return false; + + while (sink->input_to_master) { + sink = sink->input_to_master->sink; + + if (!sink) + return true; + } + + return false; +} + /* a < b -> return -1 * a == b -> return 0 * a > b -> return 1 */ @@ -266,6 +283,12 @@ static int compare_sinks(pa_sink *a, pa_sink *b) { core = a->core; + /* A moving filter sink is always worse than any other sink */ + if (is_filter_sink_moving(a) && !is_filter_sink_moving(b)) + return -1; + if (!is_filter_sink_moving(a) && is_filter_sink_moving(b)) + return 1; + /* Available sinks always beat unavailable sinks. */ if (a->active_port && a->active_port->available == PA_AVAILABLE_NO && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) @@ -314,6 +337,10 @@ void pa_core_update_default_sink(pa_core *core) { best = sink; } + /* A moving filter sink cannot be the default sink */ + if (is_filter_sink_moving(best)) + best = NULL; + old_default_sink = core->default_sink; if (best == old_default_sink) @@ -332,6 +359,23 @@ void pa_core_update_default_sink(pa_core *core) { pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], core->default_sink); } +/* Test if a source is a moving filter source */ +static bool is_filter_source_moving(pa_source *o) { + pa_source *source = o; + + if (!source) + return false; + + while (source->output_from_master) { + source = source->output_from_master->source; + + if (!source) + return true; + } + + return false; +} + /* a < b -> return -1 * a == b -> return 0 * a > b -> return 1 */ @@ -340,6 +384,12 @@ static int compare_sources(pa_source *a, pa_source *b) { core = a->core; + /* A moving filter source is always worse than any other source */ + if (is_filter_source_moving(a) && !is_filter_source_moving(b)) + return -1; + if (!is_filter_source_moving(a) && is_filter_source_moving(b)) + return 1; + /* Available sources always beat unavailable sources. */ if (a->active_port && a->active_port->available == PA_AVAILABLE_NO && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) @@ -398,6 +448,10 @@ void pa_core_update_default_source(pa_core *core) { best = source; } + /* A moving filter source cannot be the default source */ + if (is_filter_source_moving(best)) + best = NULL; + old_default_source = core->default_source; if (best == old_default_source) -- 2.11.0