153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2012 Lennart Poettering 553a5a1b3Sopenharmony_ci 653a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 753a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 853a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 953a5a1b3Sopenharmony_ci or (at your option) any later version. 1053a5a1b3Sopenharmony_ci 1153a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1253a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1353a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1453a5a1b3Sopenharmony_ci General Public License for more details. 1553a5a1b3Sopenharmony_ci 1653a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1753a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 1853a5a1b3Sopenharmony_ci***/ 1953a5a1b3Sopenharmony_ci 2053a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2153a5a1b3Sopenharmony_ci#include <config.h> 2253a5a1b3Sopenharmony_ci#endif 2353a5a1b3Sopenharmony_ci 2453a5a1b3Sopenharmony_ci#include <stdio.h> 2553a5a1b3Sopenharmony_ci#include <unistd.h> 2653a5a1b3Sopenharmony_ci#include <stdlib.h> 2753a5a1b3Sopenharmony_ci#include <errno.h> 2853a5a1b3Sopenharmony_ci#include <stdlib.h> 2953a5a1b3Sopenharmony_ci#include <sys/types.h> 3053a5a1b3Sopenharmony_ci 3153a5a1b3Sopenharmony_ci#include <systemd/sd-login.h> 3253a5a1b3Sopenharmony_ci 3353a5a1b3Sopenharmony_ci#include <pulse/xmalloc.h> 3453a5a1b3Sopenharmony_ci 3553a5a1b3Sopenharmony_ci#include <pulsecore/module.h> 3653a5a1b3Sopenharmony_ci#include <pulsecore/log.h> 3753a5a1b3Sopenharmony_ci#include <pulsecore/hashmap.h> 3853a5a1b3Sopenharmony_ci#include <pulsecore/idxset.h> 3953a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h> 4053a5a1b3Sopenharmony_ci 4153a5a1b3Sopenharmony_ciPA_MODULE_AUTHOR("Lennart Poettering"); 4253a5a1b3Sopenharmony_ciPA_MODULE_DESCRIPTION("Create a client for each login session of this user"); 4353a5a1b3Sopenharmony_ciPA_MODULE_VERSION(PACKAGE_VERSION); 4453a5a1b3Sopenharmony_ciPA_MODULE_LOAD_ONCE(true); 4553a5a1b3Sopenharmony_ci 4653a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = { 4753a5a1b3Sopenharmony_ci NULL 4853a5a1b3Sopenharmony_ci}; 4953a5a1b3Sopenharmony_ci 5053a5a1b3Sopenharmony_cistruct session { 5153a5a1b3Sopenharmony_ci char *id; 5253a5a1b3Sopenharmony_ci pa_client *client; 5353a5a1b3Sopenharmony_ci}; 5453a5a1b3Sopenharmony_ci 5553a5a1b3Sopenharmony_cistruct userdata { 5653a5a1b3Sopenharmony_ci pa_module *module; 5753a5a1b3Sopenharmony_ci pa_core *core; 5853a5a1b3Sopenharmony_ci pa_hashmap *sessions, *previous_sessions; 5953a5a1b3Sopenharmony_ci sd_login_monitor *monitor; 6053a5a1b3Sopenharmony_ci pa_io_event *io; 6153a5a1b3Sopenharmony_ci}; 6253a5a1b3Sopenharmony_ci 6353a5a1b3Sopenharmony_cistatic int add_session(struct userdata *u, const char *id) { 6453a5a1b3Sopenharmony_ci struct session *session; 6553a5a1b3Sopenharmony_ci pa_client_new_data data; 6653a5a1b3Sopenharmony_ci 6753a5a1b3Sopenharmony_ci session = pa_xnew(struct session, 1); 6853a5a1b3Sopenharmony_ci session->id = pa_xstrdup(id); 6953a5a1b3Sopenharmony_ci 7053a5a1b3Sopenharmony_ci pa_client_new_data_init(&data); 7153a5a1b3Sopenharmony_ci data.module = u->module; 7253a5a1b3Sopenharmony_ci data.driver = __FILE__; 7353a5a1b3Sopenharmony_ci pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "Login Session %s", id); 7453a5a1b3Sopenharmony_ci pa_proplist_sets(data.proplist, "systemd-login.session", id); 7553a5a1b3Sopenharmony_ci session->client = pa_client_new(u->core, &data); 7653a5a1b3Sopenharmony_ci pa_client_new_data_done(&data); 7753a5a1b3Sopenharmony_ci 7853a5a1b3Sopenharmony_ci if (!session->client) { 7953a5a1b3Sopenharmony_ci pa_xfree(session->id); 8053a5a1b3Sopenharmony_ci pa_xfree(session); 8153a5a1b3Sopenharmony_ci return -1; 8253a5a1b3Sopenharmony_ci } 8353a5a1b3Sopenharmony_ci 8453a5a1b3Sopenharmony_ci pa_hashmap_put(u->sessions, session->id, session); 8553a5a1b3Sopenharmony_ci 8653a5a1b3Sopenharmony_ci pa_log_debug("Added new session %s", id); 8753a5a1b3Sopenharmony_ci 8853a5a1b3Sopenharmony_ci /* Positive exit_idle_time is only useful when we have no session tracking 8953a5a1b3Sopenharmony_ci * capability, so we can set it to 0 now that we have detected a session. 9053a5a1b3Sopenharmony_ci * The benefit of setting exit_idle_time to 0 is that pulseaudio will exit 9153a5a1b3Sopenharmony_ci * immediately when the session ends. That in turn is useful, because some 9253a5a1b3Sopenharmony_ci * systems (those that use pam_systemd but don't use systemd for managing 9353a5a1b3Sopenharmony_ci * pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all 9453a5a1b3Sopenharmony_ci * services that depend on the files in $XDG_RUNTIME_DIR. The directory 9553a5a1b3Sopenharmony_ci * contains our sockets, and if the sockets are removed without terminating 9653a5a1b3Sopenharmony_ci * pulseaudio, a quick relogin will likely cause trouble, because a new 9753a5a1b3Sopenharmony_ci * instance will be spawned while the old instance is still running. */ 9853a5a1b3Sopenharmony_ci if (u->core->exit_idle_time > 0) 9953a5a1b3Sopenharmony_ci pa_core_set_exit_idle_time(u->core, 0); 10053a5a1b3Sopenharmony_ci 10153a5a1b3Sopenharmony_ci return 0; 10253a5a1b3Sopenharmony_ci} 10353a5a1b3Sopenharmony_ci 10453a5a1b3Sopenharmony_cistatic void free_session(struct session *session) { 10553a5a1b3Sopenharmony_ci pa_assert(session); 10653a5a1b3Sopenharmony_ci 10753a5a1b3Sopenharmony_ci pa_log_debug("Removing session %s", session->id); 10853a5a1b3Sopenharmony_ci 10953a5a1b3Sopenharmony_ci pa_client_free(session->client); 11053a5a1b3Sopenharmony_ci pa_xfree(session->id); 11153a5a1b3Sopenharmony_ci pa_xfree(session); 11253a5a1b3Sopenharmony_ci} 11353a5a1b3Sopenharmony_ci 11453a5a1b3Sopenharmony_cistatic int get_session_list(struct userdata *u) { 11553a5a1b3Sopenharmony_ci int r; 11653a5a1b3Sopenharmony_ci char **sessions; 11753a5a1b3Sopenharmony_ci pa_hashmap *h; 11853a5a1b3Sopenharmony_ci struct session *o; 11953a5a1b3Sopenharmony_ci 12053a5a1b3Sopenharmony_ci pa_assert(u); 12153a5a1b3Sopenharmony_ci 12253a5a1b3Sopenharmony_ci r = sd_uid_get_sessions(getuid(), 0, &sessions); 12353a5a1b3Sopenharmony_ci if (r < 0) 12453a5a1b3Sopenharmony_ci return -1; 12553a5a1b3Sopenharmony_ci 12653a5a1b3Sopenharmony_ci /* We copy all sessions that still exist from one hashmap to the 12753a5a1b3Sopenharmony_ci * other and then flush the remaining ones */ 12853a5a1b3Sopenharmony_ci 12953a5a1b3Sopenharmony_ci h = u->previous_sessions; 13053a5a1b3Sopenharmony_ci u->previous_sessions = u->sessions; 13153a5a1b3Sopenharmony_ci u->sessions = h; 13253a5a1b3Sopenharmony_ci 13353a5a1b3Sopenharmony_ci if (sessions) { 13453a5a1b3Sopenharmony_ci char **s; 13553a5a1b3Sopenharmony_ci 13653a5a1b3Sopenharmony_ci /* Note that the sessions array is allocated with libc's 13753a5a1b3Sopenharmony_ci * malloc()/free() calls, hence do not use pa_xfree() to free 13853a5a1b3Sopenharmony_ci * this here. */ 13953a5a1b3Sopenharmony_ci 14053a5a1b3Sopenharmony_ci for (s = sessions; *s; s++) { 14153a5a1b3Sopenharmony_ci o = pa_hashmap_remove(u->previous_sessions, *s); 14253a5a1b3Sopenharmony_ci if (o) 14353a5a1b3Sopenharmony_ci pa_hashmap_put(u->sessions, o->id, o); 14453a5a1b3Sopenharmony_ci else 14553a5a1b3Sopenharmony_ci add_session(u, *s); 14653a5a1b3Sopenharmony_ci 14753a5a1b3Sopenharmony_ci free(*s); 14853a5a1b3Sopenharmony_ci } 14953a5a1b3Sopenharmony_ci 15053a5a1b3Sopenharmony_ci free(sessions); 15153a5a1b3Sopenharmony_ci } 15253a5a1b3Sopenharmony_ci 15353a5a1b3Sopenharmony_ci pa_hashmap_remove_all(u->previous_sessions); 15453a5a1b3Sopenharmony_ci 15553a5a1b3Sopenharmony_ci return 0; 15653a5a1b3Sopenharmony_ci} 15753a5a1b3Sopenharmony_ci 15853a5a1b3Sopenharmony_cistatic void monitor_cb( 15953a5a1b3Sopenharmony_ci pa_mainloop_api*a, 16053a5a1b3Sopenharmony_ci pa_io_event* e, 16153a5a1b3Sopenharmony_ci int fd, 16253a5a1b3Sopenharmony_ci pa_io_event_flags_t events, 16353a5a1b3Sopenharmony_ci void *userdata) { 16453a5a1b3Sopenharmony_ci 16553a5a1b3Sopenharmony_ci struct userdata *u = userdata; 16653a5a1b3Sopenharmony_ci 16753a5a1b3Sopenharmony_ci pa_assert(u); 16853a5a1b3Sopenharmony_ci 16953a5a1b3Sopenharmony_ci sd_login_monitor_flush(u->monitor); 17053a5a1b3Sopenharmony_ci get_session_list(u); 17153a5a1b3Sopenharmony_ci} 17253a5a1b3Sopenharmony_ci 17353a5a1b3Sopenharmony_ciint pa__init(pa_module *m) { 17453a5a1b3Sopenharmony_ci struct userdata *u = NULL; 17553a5a1b3Sopenharmony_ci pa_modargs *ma; 17653a5a1b3Sopenharmony_ci sd_login_monitor *monitor = NULL; 17753a5a1b3Sopenharmony_ci int r; 17853a5a1b3Sopenharmony_ci 17953a5a1b3Sopenharmony_ci pa_assert(m); 18053a5a1b3Sopenharmony_ci 18153a5a1b3Sopenharmony_ci /* If we are not actually running logind become a NOP */ 18253a5a1b3Sopenharmony_ci if (access("/run/systemd/seats/", F_OK) < 0) 18353a5a1b3Sopenharmony_ci return 0; 18453a5a1b3Sopenharmony_ci 18553a5a1b3Sopenharmony_ci ma = pa_modargs_new(m->argument, valid_modargs); 18653a5a1b3Sopenharmony_ci if (!ma) { 18753a5a1b3Sopenharmony_ci pa_log("Failed to parse module arguments"); 18853a5a1b3Sopenharmony_ci goto fail; 18953a5a1b3Sopenharmony_ci } 19053a5a1b3Sopenharmony_ci 19153a5a1b3Sopenharmony_ci r = sd_login_monitor_new("session", &monitor); 19253a5a1b3Sopenharmony_ci if (r < 0) { 19353a5a1b3Sopenharmony_ci pa_log("Failed to create session monitor: %s", strerror(-r)); 19453a5a1b3Sopenharmony_ci goto fail; 19553a5a1b3Sopenharmony_ci } 19653a5a1b3Sopenharmony_ci 19753a5a1b3Sopenharmony_ci m->userdata = u = pa_xnew0(struct userdata, 1); 19853a5a1b3Sopenharmony_ci u->core = m->core; 19953a5a1b3Sopenharmony_ci u->module = m; 20053a5a1b3Sopenharmony_ci u->sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); 20153a5a1b3Sopenharmony_ci u->previous_sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); 20253a5a1b3Sopenharmony_ci u->monitor = monitor; 20353a5a1b3Sopenharmony_ci 20453a5a1b3Sopenharmony_ci u->io = u->core->mainloop->io_new(u->core->mainloop, sd_login_monitor_get_fd(monitor), PA_IO_EVENT_INPUT, monitor_cb, u); 20553a5a1b3Sopenharmony_ci 20653a5a1b3Sopenharmony_ci if (get_session_list(u) < 0) 20753a5a1b3Sopenharmony_ci goto fail; 20853a5a1b3Sopenharmony_ci 20953a5a1b3Sopenharmony_ci pa_modargs_free(ma); 21053a5a1b3Sopenharmony_ci 21153a5a1b3Sopenharmony_ci return 0; 21253a5a1b3Sopenharmony_ci 21353a5a1b3Sopenharmony_cifail: 21453a5a1b3Sopenharmony_ci if (ma) 21553a5a1b3Sopenharmony_ci pa_modargs_free(ma); 21653a5a1b3Sopenharmony_ci 21753a5a1b3Sopenharmony_ci pa__done(m); 21853a5a1b3Sopenharmony_ci 21953a5a1b3Sopenharmony_ci return -1; 22053a5a1b3Sopenharmony_ci} 22153a5a1b3Sopenharmony_ci 22253a5a1b3Sopenharmony_civoid pa__done(pa_module *m) { 22353a5a1b3Sopenharmony_ci struct userdata *u; 22453a5a1b3Sopenharmony_ci 22553a5a1b3Sopenharmony_ci pa_assert(m); 22653a5a1b3Sopenharmony_ci 22753a5a1b3Sopenharmony_ci u = m->userdata; 22853a5a1b3Sopenharmony_ci if (!u) 22953a5a1b3Sopenharmony_ci return; 23053a5a1b3Sopenharmony_ci 23153a5a1b3Sopenharmony_ci if (u->sessions) { 23253a5a1b3Sopenharmony_ci pa_hashmap_free(u->sessions); 23353a5a1b3Sopenharmony_ci pa_hashmap_free(u->previous_sessions); 23453a5a1b3Sopenharmony_ci } 23553a5a1b3Sopenharmony_ci 23653a5a1b3Sopenharmony_ci if (u->io) 23753a5a1b3Sopenharmony_ci m->core->mainloop->io_free(u->io); 23853a5a1b3Sopenharmony_ci 23953a5a1b3Sopenharmony_ci if (u->monitor) 24053a5a1b3Sopenharmony_ci sd_login_monitor_unref(u->monitor); 24153a5a1b3Sopenharmony_ci 24253a5a1b3Sopenharmony_ci pa_xfree(u); 24353a5a1b3Sopenharmony_ci} 244