1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2012 Lennart Poettering 5 6 PulseAudio is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published 8 by the Free Software Foundation; either version 2.1 of the License, 9 or (at your option) any later version. 10 11 PulseAudio is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <stdio.h> 25#include <unistd.h> 26#include <stdlib.h> 27#include <errno.h> 28#include <stdlib.h> 29#include <sys/types.h> 30 31#include <systemd/sd-login.h> 32 33#include <pulse/xmalloc.h> 34 35#include <pulsecore/module.h> 36#include <pulsecore/log.h> 37#include <pulsecore/hashmap.h> 38#include <pulsecore/idxset.h> 39#include <pulsecore/modargs.h> 40 41PA_MODULE_AUTHOR("Lennart Poettering"); 42PA_MODULE_DESCRIPTION("Create a client for each login session of this user"); 43PA_MODULE_VERSION(PACKAGE_VERSION); 44PA_MODULE_LOAD_ONCE(true); 45 46static const char* const valid_modargs[] = { 47 NULL 48}; 49 50struct session { 51 char *id; 52 pa_client *client; 53}; 54 55struct userdata { 56 pa_module *module; 57 pa_core *core; 58 pa_hashmap *sessions, *previous_sessions; 59 sd_login_monitor *monitor; 60 pa_io_event *io; 61}; 62 63static int add_session(struct userdata *u, const char *id) { 64 struct session *session; 65 pa_client_new_data data; 66 67 session = pa_xnew(struct session, 1); 68 session->id = pa_xstrdup(id); 69 70 pa_client_new_data_init(&data); 71 data.module = u->module; 72 data.driver = __FILE__; 73 pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "Login Session %s", id); 74 pa_proplist_sets(data.proplist, "systemd-login.session", id); 75 session->client = pa_client_new(u->core, &data); 76 pa_client_new_data_done(&data); 77 78 if (!session->client) { 79 pa_xfree(session->id); 80 pa_xfree(session); 81 return -1; 82 } 83 84 pa_hashmap_put(u->sessions, session->id, session); 85 86 pa_log_debug("Added new session %s", id); 87 88 /* Positive exit_idle_time is only useful when we have no session tracking 89 * capability, so we can set it to 0 now that we have detected a session. 90 * The benefit of setting exit_idle_time to 0 is that pulseaudio will exit 91 * immediately when the session ends. That in turn is useful, because some 92 * systems (those that use pam_systemd but don't use systemd for managing 93 * pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all 94 * services that depend on the files in $XDG_RUNTIME_DIR. The directory 95 * contains our sockets, and if the sockets are removed without terminating 96 * pulseaudio, a quick relogin will likely cause trouble, because a new 97 * instance will be spawned while the old instance is still running. */ 98 if (u->core->exit_idle_time > 0) 99 pa_core_set_exit_idle_time(u->core, 0); 100 101 return 0; 102} 103 104static void free_session(struct session *session) { 105 pa_assert(session); 106 107 pa_log_debug("Removing session %s", session->id); 108 109 pa_client_free(session->client); 110 pa_xfree(session->id); 111 pa_xfree(session); 112} 113 114static int get_session_list(struct userdata *u) { 115 int r; 116 char **sessions; 117 pa_hashmap *h; 118 struct session *o; 119 120 pa_assert(u); 121 122 r = sd_uid_get_sessions(getuid(), 0, &sessions); 123 if (r < 0) 124 return -1; 125 126 /* We copy all sessions that still exist from one hashmap to the 127 * other and then flush the remaining ones */ 128 129 h = u->previous_sessions; 130 u->previous_sessions = u->sessions; 131 u->sessions = h; 132 133 if (sessions) { 134 char **s; 135 136 /* Note that the sessions array is allocated with libc's 137 * malloc()/free() calls, hence do not use pa_xfree() to free 138 * this here. */ 139 140 for (s = sessions; *s; s++) { 141 o = pa_hashmap_remove(u->previous_sessions, *s); 142 if (o) 143 pa_hashmap_put(u->sessions, o->id, o); 144 else 145 add_session(u, *s); 146 147 free(*s); 148 } 149 150 free(sessions); 151 } 152 153 pa_hashmap_remove_all(u->previous_sessions); 154 155 return 0; 156} 157 158static void monitor_cb( 159 pa_mainloop_api*a, 160 pa_io_event* e, 161 int fd, 162 pa_io_event_flags_t events, 163 void *userdata) { 164 165 struct userdata *u = userdata; 166 167 pa_assert(u); 168 169 sd_login_monitor_flush(u->monitor); 170 get_session_list(u); 171} 172 173int pa__init(pa_module *m) { 174 struct userdata *u = NULL; 175 pa_modargs *ma; 176 sd_login_monitor *monitor = NULL; 177 int r; 178 179 pa_assert(m); 180 181 /* If we are not actually running logind become a NOP */ 182 if (access("/run/systemd/seats/", F_OK) < 0) 183 return 0; 184 185 ma = pa_modargs_new(m->argument, valid_modargs); 186 if (!ma) { 187 pa_log("Failed to parse module arguments"); 188 goto fail; 189 } 190 191 r = sd_login_monitor_new("session", &monitor); 192 if (r < 0) { 193 pa_log("Failed to create session monitor: %s", strerror(-r)); 194 goto fail; 195 } 196 197 m->userdata = u = pa_xnew0(struct userdata, 1); 198 u->core = m->core; 199 u->module = m; 200 u->sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); 201 u->previous_sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); 202 u->monitor = monitor; 203 204 u->io = u->core->mainloop->io_new(u->core->mainloop, sd_login_monitor_get_fd(monitor), PA_IO_EVENT_INPUT, monitor_cb, u); 205 206 if (get_session_list(u) < 0) 207 goto fail; 208 209 pa_modargs_free(ma); 210 211 return 0; 212 213fail: 214 if (ma) 215 pa_modargs_free(ma); 216 217 pa__done(m); 218 219 return -1; 220} 221 222void pa__done(pa_module *m) { 223 struct userdata *u; 224 225 pa_assert(m); 226 227 u = m->userdata; 228 if (!u) 229 return; 230 231 if (u->sessions) { 232 pa_hashmap_free(u->sessions); 233 pa_hashmap_free(u->previous_sessions); 234 } 235 236 if (u->io) 237 m->core->mainloop->io_free(u->io); 238 239 if (u->monitor) 240 sd_login_monitor_unref(u->monitor); 241 242 pa_xfree(u); 243} 244