pulseaudio/src/modules/x11/module-x11-xsmp.c
Igor V. Kovalenko 15c9ee5fec x11: Close session manager connection on ICE I/O error callback
When ICE I/O error occurs ICE connection is closed via IceCloseConnection.
This causes crash while releasing session manager connection later because
this ICE connection was initiated and is managed by session manager, and it will
attempt to close this ICE connection again.

Fix this by closing session manager connection instead.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/650>
2021-11-03 15:29:02 +00:00

363 lines
10 KiB
C

/***
This file is part of PulseAudio.
Copyright 2004-2006 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/SM/SMlib.h>
#include <pulse/xmalloc.h>
#include <pulse/util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/x11wrap.h>
PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("X11 session management");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(false);
PA_MODULE_USAGE("session_manager=<session manager string> display=<X11 display>");
static bool ice_in_use = false;
static const char* const valid_modargs[] = {
"session_manager",
"display",
"xauthority",
NULL
};
struct userdata {
pa_core *core;
pa_module *module;
pa_client *client;
SmcConn connection;
pa_x11_wrapper *x11_wrapper;
pa_x11_client *x11_client;
};
typedef struct {
IceConn connection;
struct userdata *userdata;
} ice_io_callback_data;
static void* ice_io_cb_data_new(IceConn connection, struct userdata *userdata) {
ice_io_callback_data *data = pa_xnew(ice_io_callback_data, 1);
data->connection = connection;
data->userdata = userdata;
return data;
}
static void ice_io_cb_data_destroy(pa_mainloop_api*a, pa_io_event *e, void *userdata) {
pa_assert(userdata);
pa_xfree(userdata);
}
static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) {
struct userdata *u = userdata;
pa_assert(w);
pa_assert(u);
pa_assert(u->x11_wrapper == w);
pa_log_debug("X11 client kill callback called");
if (u->connection) {
SmcCloseConnection(u->connection, 0, NULL);
u->connection = NULL;
}
if (u->x11_client) {
pa_x11_client_free(u->x11_client);
u->x11_client = NULL;
}
if (u->x11_wrapper) {
pa_x11_wrapper_unref(u->x11_wrapper);
u->x11_wrapper = NULL;
}
pa_module_unload_request(u->module, true);
}
static void close_xsmp_connection(struct userdata *userdata) {
pa_assert(userdata);
if (userdata->connection) {
SmcCloseConnection(userdata->connection, 0, NULL);
userdata->connection = NULL;
}
pa_x11_wrapper_kill_deferred(userdata->x11_wrapper);
}
static void die_cb(SmcConn connection, SmPointer client_data) {
struct userdata *u = client_data;
pa_assert(u);
pa_log_debug("Got die message from XSMP.");
close_xsmp_connection(u);
}
static void save_complete_cb(SmcConn connection, SmPointer client_data) {
}
static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) {
SmcSaveYourselfDone(connection, True);
}
static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) {
SmcSaveYourselfDone(connection, True);
}
static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
ice_io_callback_data *io_data = userdata;
pa_assert(io_data);
if (IceProcessMessages(io_data->connection, NULL, NULL) == IceProcessMessagesIOError) {
pa_log_debug("IceProcessMessages: I/O error, closing XSMP.");
IceSetShutdownNegotiation(io_data->connection, False);
/* SM owns this connection, close via SmcCloseConnection() */
close_xsmp_connection(io_data->userdata);
}
}
static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) {
struct userdata *u = client_data;
pa_assert(u);
if (opening) {
*watch_data = u->core->mainloop->io_new(
u->core->mainloop,
IceConnectionNumber(connection),
PA_IO_EVENT_INPUT,
ice_io_cb,
ice_io_cb_data_new(connection, u));
u->core->mainloop->io_set_destroy(*watch_data, ice_io_cb_data_destroy);
} else
u->core->mainloop->io_free(*watch_data);
}
static IceIOErrorHandler ice_installed_handler;
/* We call any handler installed before (or after) module is loaded but
avoid calling the default libICE handler which does an exit() */
static void ice_io_error_handler(IceConn iceConn) {
pa_log_warn("ICE I/O error handler called");
if (ice_installed_handler)
(*ice_installed_handler) (iceConn);
}
int pa__init(pa_module*m) {
pa_modargs *ma = NULL;
char t[256], *vendor, *client_id;
SmcCallbacks callbacks;
SmProp prop_program, prop_user;
SmProp *prop_list[2];
SmPropValue val_program, val_user;
struct userdata *u;
const char *e;
pa_client_new_data data;
pa_assert(m);
if (ice_in_use) {
pa_log("module-x11-xsmp may not be loaded twice.");
return -1;
} else {
IceIOErrorHandler default_handler;
ice_installed_handler = IceSetIOErrorHandler (NULL);
default_handler = IceSetIOErrorHandler (ice_io_error_handler);
if (ice_installed_handler == default_handler)
ice_installed_handler = NULL;
IceSetIOErrorHandler(ice_io_error_handler);
}
m->userdata = u = pa_xnew(struct userdata, 1);
u->core = m->core;
u->module = m;
u->client = NULL;
u->connection = NULL;
u->x11_wrapper = NULL;
IceAddConnectionWatch(new_ice_connection, u);
ice_in_use = true;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments");
goto fail;
}
if (pa_modargs_get_value(ma, "xauthority", NULL)) {
if (setenv("XAUTHORITY", pa_modargs_get_value(ma, "xauthority", NULL), 1)) {
pa_log("setenv() for $XAUTHORITY failed");
goto fail;
}
}
if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
goto fail;
u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u);
e = pa_modargs_get_value(ma, "session_manager", NULL);
if (!e && !getenv("SESSION_MANAGER")) {
pa_log("X11 session manager not running.");
goto fail;
}
memset(&callbacks, 0, sizeof(callbacks));
callbacks.die.callback = die_cb;
callbacks.die.client_data = u;
callbacks.save_yourself.callback = save_yourself_cb;
callbacks.save_yourself.client_data = m->core;
callbacks.save_complete.callback = save_complete_cb;
callbacks.save_complete.client_data = m->core;
callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb;
callbacks.shutdown_cancelled.client_data = m->core;
if (!(u->connection = SmcOpenConnection(
(char*) e, m->core,
SmProtoMajor, SmProtoMinor,
SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask,
&callbacks, NULL, &client_id,
sizeof(t), t))) {
pa_log("Failed to open connection to session manager: %s", t);
goto fail;
}
prop_program.name = (char*) SmProgram;
prop_program.type = (char*) SmARRAY8;
val_program.value = (char*) PACKAGE_NAME;
val_program.length = (int) strlen(val_program.value);
prop_program.num_vals = 1;
prop_program.vals = &val_program;
prop_list[0] = &prop_program;
prop_user.name = (char*) SmUserID;
prop_user.type = (char*) SmARRAY8;
pa_get_user_name(t, sizeof(t));
val_user.value = t;
val_user.length = (int) strlen(val_user.value);
prop_user.num_vals = 1;
prop_user.vals = &val_user;
prop_list[1] = &prop_user;
SmcSetProperties(u->connection, PA_ELEMENTSOF(prop_list), prop_list);
pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(u->connection), client_id);
pa_client_new_data_init(&data);
data.module = m;
data.driver = __FILE__;
pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "XSMP Session on %s as %s", vendor, client_id);
pa_proplist_sets(data.proplist, "xsmp.vendor", vendor);
pa_proplist_sets(data.proplist, "xsmp.client.id", client_id);
u->client = pa_client_new(u->core, &data);
pa_client_new_data_done(&data);
free(vendor);
free(client_id);
if (!u->client)
goto fail;
/* Positive exit_idle_time is only useful when we have no session tracking
* capability, so we can set it to 0 now that we have detected a session.
* The benefit of setting exit_idle_time to 0 is that pulseaudio will exit
* immediately when the session ends. That in turn is useful, because some
* systems (those that use pam_systemd but don't use systemd for managing
* pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all
* services that depend on the files in $XDG_RUNTIME_DIR. The directory
* contains our sockets, and if the sockets are removed without terminating
* pulseaudio, a quick relogin will likely cause trouble, because a new
* instance will be spawned while the old instance is still running. */
if (u->core->exit_idle_time > 0)
pa_core_set_exit_idle_time(u->core, 0);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(m);
return -1;
}
void pa__done(pa_module*m) {
struct userdata *u;
pa_assert(m);
/* set original ICE I/O error handler and forget it */
IceSetIOErrorHandler(ice_installed_handler);
ice_installed_handler = NULL;
if ((u = m->userdata)) {
if (u->connection)
SmcCloseConnection(u->connection, 0, NULL);
if (u->client)
pa_client_free(u->client);
if (u->x11_client)
pa_x11_client_free(u->x11_client);
if (u->x11_wrapper)
pa_x11_wrapper_unref(u->x11_wrapper);
}
if (ice_in_use) {
IceRemoveConnectionWatch(new_ice_connection, u);
ice_in_use = false;
}
if (u)
pa_xfree(u);
}