1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2008 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 <pulse/xmalloc.h> 32 33#include <pulsecore/module.h> 34#include <pulsecore/log.h> 35#include <pulsecore/hashmap.h> 36#include <pulsecore/idxset.h> 37#include <pulsecore/modargs.h> 38#include <pulsecore/dbus-shared.h> 39 40PA_MODULE_AUTHOR("Lennart Poettering"); 41PA_MODULE_DESCRIPTION("Create a client for each ConsoleKit session of this user"); 42PA_MODULE_VERSION(PACKAGE_VERSION); 43PA_MODULE_LOAD_ONCE(true); 44 45static const char* const valid_modargs[] = { 46 NULL 47}; 48 49struct session { 50 char *id; 51 pa_client *client; 52}; 53 54struct userdata { 55 pa_module *module; 56 pa_core *core; 57 pa_dbus_connection *connection; 58 pa_hashmap *sessions; 59 bool filter_added; 60}; 61 62static void add_session(struct userdata *u, const char *id) { 63 DBusError error; 64 DBusMessage *m = NULL, *reply = NULL; 65 uint32_t uid; 66 struct session *session; 67 pa_client_new_data data; 68 69 dbus_error_init(&error); 70 71 if (pa_hashmap_get(u->sessions, id)) { 72 pa_log_warn("Duplicate session %s, ignoring.", id); 73 return; 74 } 75 76 if (!(m = dbus_message_new_method_call("org.freedesktop.ConsoleKit", id, "org.freedesktop.ConsoleKit.Session", "GetUnixUser"))) { 77 pa_log("Failed to allocate GetUnixUser() method call."); 78 goto fail; 79 } 80 81 if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { 82 pa_log("GetUnixUser() call failed: %s: %s", error.name, error.message); 83 goto fail; 84 } 85 86 /* CK 0.3 this changed from int32 to uint32 */ 87 if (!dbus_message_get_args(reply, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID)) { 88 dbus_error_free(&error); 89 90 if (!dbus_message_get_args(reply, &error, DBUS_TYPE_INT32, &uid, DBUS_TYPE_INVALID)) { 91 pa_log("Failed to parse GetUnixUser() result: %s: %s", error.name, error.message); 92 goto fail; 93 } 94 } 95 96 /* We only care about our own sessions */ 97 if ((uid_t) uid != getuid()) 98 goto fail; 99 100 session = pa_xnew(struct session, 1); 101 session->id = pa_xstrdup(id); 102 103 pa_client_new_data_init(&data); 104 data.module = u->module; 105 data.driver = __FILE__; 106 pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "ConsoleKit Session %s", id); 107 pa_proplist_sets(data.proplist, "console-kit.session", id); 108 session->client = pa_client_new(u->core, &data); 109 pa_client_new_data_done(&data); 110 111 if (!session->client) { 112 pa_xfree(session->id); 113 pa_xfree(session); 114 goto fail; 115 } 116 117 pa_hashmap_put(u->sessions, session->id, session); 118 119 pa_log_debug("Added new session %s", id); 120 121 /* Positive exit_idle_time is only useful when we have no session tracking 122 * capability, so we can set it to 0 now that we have detected a session. 123 * The benefit of setting exit_idle_time to 0 is that pulseaudio will exit 124 * immediately when the session ends. That in turn is useful, because some 125 * systems (those that use pam_systemd but don't use systemd for managing 126 * pulseaudio) clean $XDG_RUNTIME_DIR on logout, but fail to terminate all 127 * services that depend on the files in $XDG_RUNTIME_DIR. The directory 128 * contains our sockets, and if the sockets are removed without terminating 129 * pulseaudio, a quick relogin will likely cause trouble, because a new 130 * instance will be spawned while the old instance is still running. */ 131 if (u->core->exit_idle_time > 0) 132 pa_core_set_exit_idle_time(u->core, 0); 133 134fail: 135 136 if (m) 137 dbus_message_unref(m); 138 139 if (reply) 140 dbus_message_unref(reply); 141 142 dbus_error_free(&error); 143} 144 145static void free_session(struct session *session) { 146 pa_assert(session); 147 148 pa_log_debug("Removing session %s", session->id); 149 150 pa_client_free(session->client); 151 pa_xfree(session->id); 152 pa_xfree(session); 153} 154 155static void remove_session(struct userdata *u, const char *id) { 156 pa_assert(u); 157 pa_assert(id); 158 159 pa_hashmap_remove_and_free(u->sessions, id); 160} 161 162static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { 163 struct userdata *u = userdata; 164 DBusError error; 165 const char *path; 166 167 pa_assert(bus); 168 pa_assert(message); 169 pa_assert(u); 170 171 dbus_error_init(&error); 172 173 if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionAdded")) { 174 175 /* CK API changed to match spec in 0.3 */ 176 if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { 177 dbus_error_free(&error); 178 179 if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID)) { 180 pa_log_error("Failed to parse SessionAdded message: %s: %s", error.name, error.message); 181 goto finish; 182 } 183 } 184 185 add_session(u, path); 186 187 } else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) { 188 189 /* CK API changed to match spec in 0.3 */ 190 if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { 191 dbus_error_free(&error); 192 193 if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID)) { 194 pa_log_error("Failed to parse SessionRemoved message: %s: %s", error.name, error.message); 195 goto finish; 196 } 197 } 198 199 remove_session(u, path); 200 } 201 202finish: 203 dbus_error_free(&error); 204 205 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 206} 207 208static int get_session_list(struct userdata *u) { 209 DBusError error; 210 DBusMessage *m = NULL, *reply = NULL; 211 uint32_t uid; 212 DBusMessageIter iter, sub; 213 int ret = -1; 214 215 pa_assert(u); 216 217 dbus_error_init(&error); 218 219 if (!(m = dbus_message_new_method_call("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "GetSessionsForUnixUser"))) { 220 pa_log("Failed to allocate GetSessionsForUnixUser() method call."); 221 goto fail; 222 } 223 224 uid = (uint32_t) getuid(); 225 if (!(dbus_message_append_args(m, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID))) { 226 pa_log("Failed to append arguments to GetSessionsForUnixUser() method call."); 227 goto fail; 228 } 229 230 if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { 231 pa_log("GetSessionsForUnixUser() call failed: %s: %s", error.name, error.message); 232 goto fail; 233 } 234 235 dbus_message_iter_init(reply, &iter); 236 237 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || 238 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_OBJECT_PATH) { 239 pa_log("Failed to parse GetSessionsForUnixUser() result."); 240 goto fail; 241 } 242 243 dbus_message_iter_recurse(&iter, &sub); 244 245 for (;;) { 246 int at; 247 const char *id; 248 249 if ((at = dbus_message_iter_get_arg_type(&sub)) == DBUS_TYPE_INVALID) 250 break; 251 252 pa_assert(at == DBUS_TYPE_OBJECT_PATH); 253 dbus_message_iter_get_basic(&sub, &id); 254 255 add_session(u, id); 256 257 dbus_message_iter_next(&sub); 258 } 259 260 ret = 0; 261 262fail: 263 264 if (m) 265 dbus_message_unref(m); 266 267 if (reply) 268 dbus_message_unref(reply); 269 270 dbus_error_free(&error); 271 272 return ret; 273} 274 275int pa__init(pa_module*m) { 276 DBusError error; 277 pa_dbus_connection *connection; 278 struct userdata *u = NULL; 279 pa_modargs *ma; 280 281 pa_assert(m); 282 283 dbus_error_init(&error); 284 285 /* If systemd's logind service is running, we shouldn't watch ConsoleKit 286 * but login */ 287 if (access("/run/systemd/seats/", F_OK) >= 0) 288 return 0; 289 290 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 291 pa_log("Failed to parse module arguments"); 292 goto fail; 293 } 294 295 if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { 296 297 if (connection) 298 pa_dbus_connection_unref(connection); 299 300 pa_log_error("Unable to contact D-Bus system bus: %s: %s", error.name, error.message); 301 goto fail; 302 } 303 304 m->userdata = u = pa_xnew0(struct userdata, 1); 305 u->core = m->core; 306 u->module = m; 307 u->connection = connection; 308 u->sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session); 309 310 if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), filter_cb, u, NULL)) { 311 pa_log_error("Failed to add filter function"); 312 goto fail; 313 } 314 315 u->filter_added = true; 316 317 if (pa_dbus_add_matches( 318 pa_dbus_connection_get(connection), &error, 319 "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionAdded'", 320 "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionRemoved'", NULL) < 0) { 321 pa_log_error("Unable to subscribe to ConsoleKit signals: %s: %s", error.name, error.message); 322 goto fail; 323 } 324 325 if (get_session_list(u) < 0) 326 goto fail; 327 328 pa_modargs_free(ma); 329 330 return 0; 331 332fail: 333 if (ma) 334 pa_modargs_free(ma); 335 336 dbus_error_free(&error); 337 pa__done(m); 338 339 return -1; 340} 341 342void pa__done(pa_module *m) { 343 struct userdata *u; 344 345 pa_assert(m); 346 347 if (!(u = m->userdata)) 348 return; 349 350 if (u->sessions) 351 pa_hashmap_free(u->sessions); 352 353 if (u->connection) { 354 pa_dbus_remove_matches( 355 pa_dbus_connection_get(u->connection), 356 "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionAdded'", 357 "type='signal',sender='org.freedesktop.ConsoleKit',interface='org.freedesktop.ConsoleKit.Seat',member='SessionRemoved'", NULL); 358 359 if (u->filter_added) 360 dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u); 361 362 pa_dbus_connection_unref(u->connection); 363 } 364 365 pa_xfree(u); 366} 367