2 This file is part of PulseAudio.
4 Copyright 2004-2006 Lennart Poettering
5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
31 #include <pulse/xmalloc.h>
32 #include <pulse/proplist.h>
34 #include <pulsecore/core-subscribe.h>
35 #include <pulsecore/log.h>
36 #include <pulsecore/core-util.h>
37 #include <pulsecore/macro.h>
38 #include <pulsecore/ltdl-helper.h>
39 #include <pulsecore/modinfo.h>
43 #define PA_SYMBOL_INIT "pa__init"
44 #define PA_SYMBOL_DONE "pa__done"
45 #define PA_SYMBOL_LOAD_ONCE "pa__load_once"
46 #define PA_SYMBOL_GET_N_USED "pa__get_n_used"
47 #define PA_SYMBOL_GET_DEPRECATE "pa__get_deprecated"
48 #define PA_SYMBOL_GET_VERSION "pa__get_version"
50 bool pa_module_exists(const char *name) {
51 const char *paths, *state = NULL;
52 char *n, *p, *pathname;
57 if (name[0] == PA_PATH_SEP_CHAR) {
58 result = access(name, F_OK) == 0 ? true : false;
59 pa_log_debug("Checking for existence of '%s': %s", name, result ? "success" : "failure");
64 if (!(paths = lt_dlgetsearchpath()))
67 /* strip .so from the end of name, if present */
70 if (p && pa_streq(p, PA_SOEXT))
73 while ((p = pa_split(paths, ":", &state))) {
74 pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "%s" PA_SOEXT, p, n);
75 result = access(pathname, F_OK) == 0 ? true : false;
76 pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure");
86 if (PA_UNLIKELY(pa_run_from_build_tree())) {
87 while ((p = pa_split(paths, ":", &state))) {
89 pathname = pa_sprintf_malloc("%s" PA_PATH_SEP "src" PA_PATH_SEP "modules" PA_PATH_SEP "%s" PA_SOEXT, p, n);
91 pathname = pa_sprintf_malloc("%s" PA_PATH_SEP ".libs" PA_PATH_SEP "%s" PA_SOEXT, p, n);
93 result = access(pathname, F_OK) == 0 ? true : false;
94 pa_log_debug("Checking for existence of '%s': %s", pathname, result ? "success" : "failure");
108 void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data) {
112 pa_dynarray_append(m->hooks, pa_hook_connect(hook, prio, cb, data));
115 int pa_module_load(pa_module** module, pa_core *c, const char *name, const char *argument) {
117 const char *(*get_version)(void);
118 bool (*load_once)(void);
119 const char* (*get_deprecated)(void);
127 if (c->disallow_module_loading) {
128 errcode = -PA_ERR_ACCESS;
132 m = pa_xnew(pa_module, 1);
133 m->name = pa_xstrdup(name);
134 m->argument = pa_xstrdup(argument);
135 m->load_once = false;
136 m->proplist = pa_proplist_new();
137 m->hooks = pa_dynarray_new((pa_free_cb_t) pa_hook_slot_free);
138 m->index = PA_IDXSET_INVALID;
140 if (!(m->dl = lt_dlopenext(name))) {
141 /* We used to print the error that is returned by lt_dlerror(), but
142 * lt_dlerror() is useless. It returns pretty much always "file not
143 * found". That's because if there are any problems with loading the
144 * module with normal loaders, libltdl falls back to the "preload"
145 * loader, which never finds anything, and therefore says "file not
147 pa_log("Failed to open module \"%s\".", name);
148 errcode = -PA_ERR_IO;
152 if ((get_version = (const char *(*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_VERSION))) {
153 const char *version = get_version();
155 if (!pa_safe_streq(version, PACKAGE_VERSION)) {
156 pa_log("Module \"%s\" version (%s) doesn't match the expected version (%s).",
157 name, pa_strnull(version), PACKAGE_VERSION);
158 errcode = -PA_ERR_IO;
162 pa_log("Symbol \"%s\" not found in module \"%s\".", PA_SYMBOL_GET_VERSION, name);
163 errcode = -PA_ERR_IO;
167 if ((load_once = (bool (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) {
169 m->load_once = load_once();
174 /* OK, the module only wants to be loaded once, let's make sure it is */
176 PA_IDXSET_FOREACH(i, c->modules, idx) {
177 if (pa_streq(name, i->name)) {
178 pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name);
179 errcode = -PA_ERR_EXIST;
186 if ((get_deprecated = (const char* (*) (void)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_DEPRECATE))) {
189 if ((t = get_deprecated()))
190 pa_log_warn("%s is deprecated: %s", name, t);
193 if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) {
194 pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name);
195 errcode = -PA_ERR_IO;
199 m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE);
200 m->get_n_used = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_GET_N_USED);
203 m->unload_requested = false;
205 pa_assert_se(pa_idxset_put(c->modules, m, &m->index) >= 0);
206 pa_assert(m->index != PA_IDXSET_INVALID);
208 if ((rval = m->init(m)) < 0) {
209 if (rval == -PA_MODULE_ERR_SKIP) {
210 errcode = -PA_ERR_NOENTITY;
213 pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : "");
214 errcode = -PA_ERR_IO;
218 pa_log_info("Loaded \"%s\" (index: #%u; argument: \"%s\").", m->name, m->index, m->argument ? m->argument : "");
220 pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_NEW, m->index);
222 if ((mi = pa_modinfo_get_by_handle(m->dl, name))) {
224 if (mi->author && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_AUTHOR))
225 pa_proplist_sets(m->proplist, PA_PROP_MODULE_AUTHOR, mi->author);
227 if (mi->description && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_DESCRIPTION))
228 pa_proplist_sets(m->proplist, PA_PROP_MODULE_DESCRIPTION, mi->description);
230 if (mi->version && !pa_proplist_contains(m->proplist, PA_PROP_MODULE_VERSION))
231 pa_proplist_sets(m->proplist, PA_PROP_MODULE_VERSION, mi->version);
236 pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_NEW], m);
245 if (m->index != PA_IDXSET_INVALID)
246 pa_idxset_remove_by_index(c->modules, m->index);
249 pa_dynarray_free(m->hooks);
252 pa_proplist_free(m->proplist);
254 pa_xfree(m->argument);
268 static void postponed_dlclose(pa_mainloop_api *api, void *userdata) {
269 lt_dlhandle dl = userdata;
274 static void pa_module_free(pa_module *m) {
278 pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index);
279 pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_UNLINK], m);
282 pa_dynarray_free(m->hooks);
290 pa_proplist_free(m->proplist);
292 /* If a module unloads itself with pa_module_unload(), we can't call
293 * lt_dlclose() here, because otherwise pa_module_unload() may return to a
294 * code location that has been removed from memory. Therefore, let's
295 * postpone the lt_dlclose() call a bit.
297 * Apparently lt_dlclose() doesn't always remove the module from memory,
298 * but it can happen, as can be seen here:
299 * https://bugs.freedesktop.org/show_bug.cgi?id=96831 */
300 pa_mainloop_api_once(m->core->mainloop, postponed_dlclose, m->dl);
302 pa_hashmap_remove(m->core->modules_pending_unload, m);
304 pa_log_info("Unloaded \"%s\" (index: #%u).", m->name, m->index);
306 pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_REMOVE, m->index);
309 pa_xfree(m->argument);
313 void pa_module_unload(pa_module *m, bool force) {
316 if (m->core->disallow_module_loading && !force)
319 if (!(m = pa_idxset_remove_by_data(m->core->modules, m, NULL)))
325 void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force) {
328 pa_assert(idx != PA_IDXSET_INVALID);
330 if (c->disallow_module_loading && !force)
333 if (!(m = pa_idxset_remove_by_index(c->modules, idx)))
339 void pa_module_unload_all(pa_core *c) {
346 pa_assert(c->modules);
348 if (pa_idxset_isempty(c->modules))
351 /* Unload modules in reverse order by default */
352 indices = pa_xnew(uint32_t, pa_idxset_size(c->modules));
354 PA_IDXSET_FOREACH(m, c->modules, state)
355 indices[i++] = state;
356 pa_assert(i == (int) pa_idxset_size(c->modules));
358 for (; i >= 0; i--) {
359 m = pa_idxset_remove_by_index(c->modules, indices[i]);
365 /* Just in case module unloading caused more modules to load */
366 PA_IDXSET_FOREACH(m, c->modules, state)
367 pa_log_warn("After module unload, module '%s' was still loaded!", m->name);
368 c->disallow_module_loading = 1;
369 pa_idxset_remove_all(c->modules, (pa_free_cb_t) pa_module_free);
370 pa_assert(pa_idxset_isempty(c->modules));
372 if (c->module_defer_unload_event) {
373 c->mainloop->defer_free(c->module_defer_unload_event);
374 c->module_defer_unload_event = NULL;
376 pa_assert(pa_hashmap_isempty(c->modules_pending_unload));
379 static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) {
380 pa_core *c = PA_CORE(userdata);
383 pa_core_assert_ref(c);
384 api->defer_enable(e, 0);
386 while ((m = pa_hashmap_first(c->modules_pending_unload)))
387 pa_module_unload(m, true);
390 void pa_module_unload_request(pa_module *m, bool force) {
393 if (m->core->disallow_module_loading && !force)
396 m->unload_requested = true;
397 pa_hashmap_put(m->core->modules_pending_unload, m, m);
399 if (!m->core->module_defer_unload_event)
400 m->core->module_defer_unload_event = m->core->mainloop->defer_new(m->core->mainloop, defer_cb, m->core);
402 m->core->mainloop->defer_enable(m->core->module_defer_unload_event, 1);
405 void pa_module_unload_request_by_index(pa_core *c, uint32_t idx, bool force) {
409 if (!(m = pa_idxset_get_by_index(c->modules, idx)))
412 pa_module_unload_request(m, force);
415 int pa_module_get_n_used(pa_module*m) {
421 return m->get_n_used(m);
424 void pa_module_update_proplist(pa_module *m, pa_update_mode_t mode, pa_proplist *p) {
428 pa_proplist_update(m->proplist, mode, p);
430 pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index);
431 pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_PROPLIST_CHANGED], m);