/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB 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 . ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "module.h" #define PA_SYMBOL_INIT "pa__init" #define PA_SYMBOL_DONE "pa__done" #define PA_SYMBOL_LOAD_ONCE "pa__load_once" #define PA_SYMBOL_GET_N_USED "pa__get_n_used" #define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated" #define PA_SYMBOL_GET_VERSION "pa__get_version" bool pa_module_exists(const char *name) { const char *paths, *state = NULL; char *n, *p, *pathname; bool result; pa_assert(name); if (name[0] == PA_PATH_SEP_CHAR) { result = access(name, F_OK) == 0 ? true : false; pa_log_debug("Checking for existence of '%s': %s", name, result ? "success" : "failure"); if (result) return true; } if (!(paths = lt_dlgetsearchpath())) return false; /* strip .so from the end of name, if present */ n = pa_xstrdup(name); p = strrchr(n, '.'); if (p && pa_streq(p, PA_SOEXT)) p[0] = 0; while ((p = pa_split(paths, ":", &state))) { pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s" PA_SOEXT, p, n); result = access(pathname, F_OK) == 0 ? true : false; pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure"); pa_xfree(pathname); pa_xfree(p); if (result) { pa_xfree(n); return true; } } state = NULL; if (PA_UNLIKELY(pa_run_from_build_tree())) { while ((p = pa_split(paths, ":", &state))) { #ifdef MESON_BUILD pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "src" PA_PATH_SEP "modules" PA_PATH_SEP "%s" PA_SOEXT, p, n); #else pathname = pa_sprintf_malloc("%s" PA_PATH_SEP ".libs" PA_PATH_SEP "%s" PA_SOEXT, p, n); #endif result = access(pathname, F_OK) == 0 ? true : false; pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure"); pa_xfree(pathname); pa_xfree(p); if (result) { pa_xfree(n); return true; } } } pa_xfree(n); return false; } void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) { pa_assert(m); pa_assert(hook); pa_assert(m->hooks); pa_dynarray_append(m->hooks, pa_hook_connect(hook, prio, cb, data)); } int pa_module_load(pa_module** module, pa_core *c, const char *name, const char *argument) { pa_module *m = NULL; const char *(*get_version)(void); bool (*load_once)(void); const char* (*get_deprecated)(void); pa_modinfo *mi; int errcode, rval; pa_assert(module); pa_assert(c); pa_assert(name); if (c->disallow_module_loading) { errcode = -PA_ERR_ACCESS; goto fail; } m = pa_xnew(pa_module, 1); m->name = pa_xstrdup(name); m->argument = pa_xstrdup(argument); m->load_once = false; m->proplist = pa_proplist_new(); m->hooks = pa_dynarray_new((pa_free_cb_t) pa_hook_slot_free); m->index = PA_IDXSET_INVALID; if (!(m->dl = lt_dlopenext(name))) { /* We used to print the error that is returned by lt_dlerror(), but * lt_dlerror() is useless. It returns pretty much always "file not * found". That's because if there are any problems with loading the * module with normal loaders, libltdl falls back to the "preload" * loader, which never finds anything, and therefore says "file not * found". */ pa_log("Failed to open module \"%s\".", name); errcode = -PA_ERR_IO; goto fail; } if ((get_version = (const char *(*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_VERSION))) { const char *version = get_version(); if (!pa_safe_streq(version, PACKAGE_VERSION)) { pa_log("Module \"%s\" version (%s) doesn't match the expected version (%s).", name, pa_strnull(version), PACKAGE_VERSION); errcode = -PA_ERR_IO; goto fail; } } else { pa_log("Symbol \"%s\" not found in module \"%s\".", PA_SYMBOL_GET_VERSION, name); errcode = -PA_ERR_IO; goto fail; } if ((load_once = (bool (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) { m->load_once = load_once(); if (m->load_once) { pa_module *i; uint32_t idx; /* OK, the module only wants to be loaded once, let's make sure it is */ PA_IDXSET_FOREACH(i, c->modules, idx) { if (pa_streq(name, i->name)) { pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name); errcode = -PA_ERR_EXIST; goto fail; } } } } if ((get_deprecated = (const char* (*) (void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_DEPRECATE))) { const char *t; if ((t = get_deprecated())) pa_log_warn("%s is deprecated: %s", name, t); } if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) { pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name); errcode = -PA_ERR_IO; goto fail; } m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE); m->get_n_used = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_N_USED); m->userdata = NULL; m->core = c; m->unload_requested = false; pa_assert_se(pa_idxset_put(c->modules, m, &m->index) >= 0); pa_assert(m->index != PA_IDXSET_INVALID); if ((rval = m->init(m)) < 0) { if (rval == -PA_MODULE_ERR_SKIP) { errcode = -PA_ERR_NOENTITY; goto fail; } pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : ""); errcode = -PA_ERR_IO; goto fail; } pa_log_info("Loaded \"%s\" (index: #%u; argument: \"%s\").", m->name, m->index, m->argument ? m->argument : ""); pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_NEW, m->index); if ((mi = pa_modinfo_get_by_handle(m->dl, name))) { if (mi->author && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_AUTHOR)) pa_proplist_sets(m->proplist, PA_PROP_MODULE_AUTHOR, mi->author); if (mi->description && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_DESCRIPTION)) pa_proplist_sets(m->proplist, PA_PROP_MODULE_DESCRIPTION, mi->description); if (mi->version && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_VERSION)) pa_proplist_sets(m->proplist, PA_PROP_MODULE_VERSION, mi->version); pa_modinfo_free(mi); } pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_NEW], m); *module = m; return 0; fail: if (m) { if (m->index != PA_IDXSET_INVALID) pa_idxset_remove_by_index(c->modules, m->index); if (m->hooks) pa_dynarray_free(m->hooks); if (m->proplist) pa_proplist_free(m->proplist); pa_xfree(m->argument); pa_xfree(m->name); if (m->dl) lt_dlclose(m->dl); pa_xfree(m); } *module = NULL; return errcode; } static void postponed_dlclose(pa_mainloop_api *api, void *userdata) { lt_dlhandle dl = userdata; lt_dlclose(dl); } static void pa_module_free(pa_module *m) { pa_assert(m); pa_assert(m->core); pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index); pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_UNLINK], m); if (m->hooks) { pa_dynarray_free(m->hooks); m->hooks = NULL; } if (m->done) m->done(m); if (m->proplist) pa_proplist_free(m->proplist); /* If a module unloads itself with pa_module_unload(), we can't call * lt_dlclose() here, because otherwise pa_module_unload() may return to a * code location that has been removed from memory. Therefore, let's * postpone the lt_dlclose() call a bit. * * Apparently lt_dlclose() doesn't always remove the module from memory, * but it can happen, as can be seen here: * https://bugs.freedesktop.org/show_bug.cgi?id=96831 */ pa_mainloop_api_once(m->core->mainloop, postponed_dlclose, m->dl); pa_hashmap_remove(m->core->modules_pending_unload, m); pa_log_info("Unloaded \"%s\" (index: #%u).", m->name, m->index); pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_REMOVE, m->index); pa_xfree(m->name); pa_xfree(m->argument); pa_xfree(m); } void pa_module_unload(pa_module *m, bool force) { pa_assert(m); if (m->core->disallow_module_loading && !force) return; if (!(m = pa_idxset_remove_by_data(m->core->modules, m, NULL))) return; pa_module_free(m); } void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force) { pa_module *m; pa_assert(c); pa_assert(idx != PA_IDXSET_INVALID); if (c->disallow_module_loading && !force) return; if (!(m = pa_idxset_remove_by_index(c->modules, idx))) return; pa_module_free(m); } void pa_module_unload_all(pa_core *c) { pa_module *m; uint32_t *indices; uint32_t state; int i; pa_assert(c); pa_assert(c->modules); if (pa_idxset_isempty(c->modules)) return; /* Unload modules in reverse order by default */ indices = pa_xnew(uint32_t, pa_idxset_size(c->modules)); i = 0; PA_IDXSET_FOREACH(m, c->modules, state) indices[i++] = state; pa_assert(i == (int) pa_idxset_size(c->modules)); i--; for (; i >= 0; i--) { m = pa_idxset_remove_by_index(c->modules, indices[i]); if (m) pa_module_free(m); } pa_xfree(indices); /* Just in case module unloading caused more modules to load */ PA_IDXSET_FOREACH(m, c->modules, state) pa_log_warn("After module unload, module '%s' was still loaded!", m->name); c->disallow_module_loading = 1; pa_idxset_remove_all(c->modules, (pa_free_cb_t) pa_module_free); pa_assert(pa_idxset_isempty(c->modules)); if (c->module_defer_unload_event) { c->mainloop->defer_free(c->module_defer_unload_event); c->module_defer_unload_event = NULL; } pa_assert(pa_hashmap_isempty(c->modules_pending_unload)); } static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) { pa_core *c = PA_CORE(userdata); pa_module *m; pa_core_assert_ref(c); api->defer_enable(e, 0); while ((m = pa_hashmap_first(c->modules_pending_unload))) pa_module_unload(m, true); } void pa_module_unload_request(pa_module *m, bool force) { pa_assert(m); if (m->core->disallow_module_loading && !force) return; m->unload_requested = true; pa_hashmap_put(m->core->modules_pending_unload, m, m); if (!m->core->module_defer_unload_event) m->core->module_defer_unload_event = m->core->mainloop->defer_new(m->core->mainloop, defer_cb, m->core); m->core->mainloop->defer_enable(m->core->module_defer_unload_event, 1); } void pa_module_unload_request_by_index(pa_core *c, uint32_t idx, bool force) { pa_module *m; pa_assert(c); if (!(m = pa_idxset_get_by_index(c->modules, idx))) return; pa_module_unload_request(m, force); } int pa_module_get_n_used(pa_module*m) { pa_assert(m); if (!m->get_n_used) return -1; return m->get_n_used(m); } void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist *p) { pa_assert(m); if (p) pa_proplist_update(m->proplist, mode, p); pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index); pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_PROPLIST_CHANGED], m); }