1 /* PipeWire
2  * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
3  *	@author Linus Svensson <linus.svensson@axis.com>
4  * Copyright © 2018 Wim Taymans
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the next
14  * paragraph) shall be included in all copies or substantial portions of the
15  * Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23  * DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include "config.h"
27 
28 #include <stdio.h>
29 #include <dlfcn.h>
30 #include <dirent.h>
31 #include <limits.h>
32 #include <sys/stat.h>
33 #include <errno.h>
34 
35 #include <spa/utils/string.h>
36 
37 #include "pipewire/impl.h"
38 #include "pipewire/private.h"
39 
40 PW_LOG_TOPIC_EXTERN(log_module);
41 #define PW_LOG_TOPIC_DEFAULT log_module
42 
43 /** \cond */
44 struct impl {
45 	struct pw_impl_module this;
46 	void *hnd;
47 };
48 
49 #define pw_module_resource_info(r,...)	pw_resource_call(r,struct pw_module_events,info,0,__VA_ARGS__)
50 
51 
52 /** \endcond */
53 
find_module(const char * path,const char * name,int level)54 static char *find_module(const char *path, const char *name, int level)
55 {
56 	char *filename;
57 	struct dirent *entry;
58 	struct stat s;
59 	DIR *dir;
60 	int res;
61 
62 	filename = spa_aprintf("%s/%s.so", path, name);
63 	if (filename == NULL)
64 		return NULL;
65 
66 	if (stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
67 		/* found a regular file with name */
68 		return filename;
69 	}
70 
71 	free(filename);
72 	filename = NULL;
73 
74 	/* now recurse down in subdirectories and look for it there */
75 	if (level <= 0)
76 		return NULL;
77 
78 	dir = opendir(path);
79 	if (dir == NULL) {
80 		res = -errno;
81 		pw_log_warn("could not open %s: %m", path);
82 		errno = -res;
83 		return NULL;
84 	}
85 
86 	while ((entry = readdir(dir))) {
87 		char *newpath;
88 
89 		if (spa_streq(entry->d_name, ".") || spa_streq(entry->d_name, ".."))
90 			continue;
91 
92 		newpath = spa_aprintf("%s/%s", path, entry->d_name);
93 		if (newpath == NULL)
94 			break;
95 
96 		if (stat(newpath, &s) == 0 && S_ISDIR(s.st_mode))
97 			filename = find_module(newpath, name, level - 1);
98 
99 		free(newpath);
100 
101 		if (filename != NULL)
102 			break;
103 	}
104 
105 	closedir(dir);
106 
107 	return filename;
108 }
109 
110 static int
global_bind(void * _data,struct pw_impl_client * client,uint32_t permissions,uint32_t version,uint32_t id)111 global_bind(void *_data, struct pw_impl_client *client, uint32_t permissions,
112 		 uint32_t version, uint32_t id)
113 {
114 	struct pw_impl_module *this = _data;
115 	struct pw_global *global = this->global;
116 	struct pw_resource *resource;
117 
118 	resource = pw_resource_new(client, id, permissions, global->type, version, 0);
119 	if (resource == NULL)
120 		goto error_resource;
121 
122 	pw_log_debug("%p: bound to %d", this, resource->id);
123 	pw_global_add_resource(global, resource);
124 
125 	this->info.change_mask = PW_MODULE_CHANGE_MASK_ALL;
126 	pw_module_resource_info(resource, &this->info);
127 	this->info.change_mask = 0;
128 
129 	return 0;
130 
131 error_resource:
132 	pw_log_error("%p: can't create module resource: %m", this);
133 	return -errno;
134 }
135 
global_destroy(void * object)136 static void global_destroy(void *object)
137 {
138 	struct pw_impl_module *module = object;
139 	spa_hook_remove(&module->global_listener);
140 	module->global = NULL;
141 	pw_impl_module_destroy(module);
142 }
143 
144 static const struct pw_global_events global_events = {
145 	PW_VERSION_GLOBAL_EVENTS,
146 	.destroy = global_destroy,
147 };
148 
149 /** Load a module
150  *
151  * \param context a \ref pw_context
152  * \param name name of the module to load
153  * \param args A string with arguments for the module
154  * \param properties extra global properties
155  * \return A \ref pw_impl_module if the module could be loaded, or NULL on failure.
156  *
157  */
158 SPA_EXPORT
159 struct pw_impl_module *
pw_context_load_module(struct pw_context * context,const char * name,const char * args,struct pw_properties * properties)160 pw_context_load_module(struct pw_context *context,
161 	       const char *name, const char *args,
162 	       struct pw_properties *properties)
163 {
164 	struct pw_impl_module *this;
165 	struct impl *impl;
166 	void *hnd;
167 	char *filename = NULL;
168 	const char *module_dir;
169 	int res;
170 	pw_impl_module_init_func_t init_func;
171 	const char *state = NULL, *p;
172 	size_t len;
173 	char path_part[PATH_MAX];
174 
175 	module_dir = getenv("PIPEWIRE_MODULE_DIR");
176 	if (module_dir == NULL) {
177 		module_dir = MODULEDIR;
178 		pw_log_debug("moduledir set to: %s", module_dir);
179 	}
180 	else {
181 		pw_log_debug("PIPEWIRE_MODULE_DIR set to: %s", module_dir);
182 	}
183 
184 	while ((p = pw_split_walk(module_dir, ":", &len, &state))) {
185 		if ((res = spa_scnprintf(path_part, sizeof(path_part), "%.*s", (int)len, p)) > 0) {
186 			filename = find_module(path_part, name, 8);
187 			if (filename != NULL) {
188 				pw_log_debug("trying to load module: %s (%s) args(%s)", name, filename, args);
189 
190 				hnd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
191 				if (hnd != NULL)
192 					break;
193 
194 				free(filename);
195 				filename = NULL;
196 			}
197 		}
198 	}
199 
200 	if (filename == NULL)
201 		goto error_not_found;
202 	if (hnd == NULL)
203 		goto error_open_failed;
204 
205 	if ((init_func = dlsym(hnd, PIPEWIRE_SYMBOL_MODULE_INIT)) == NULL)
206 		goto error_no_pw_module;
207 
208 	if (properties == NULL)
209 		properties = pw_properties_new(NULL, NULL);
210 	if (properties == NULL)
211 		goto error_no_mem;
212 
213 	impl = calloc(1, sizeof(struct impl));
214 	if (impl == NULL)
215 		goto error_no_mem;
216 
217 	impl->hnd = hnd;
218 	hnd = NULL;
219 
220 	this = &impl->this;
221 	this->context = context;
222 	this->properties = properties;
223 	properties = NULL;
224 
225 	spa_hook_list_init(&this->listener_list);
226 
227 	pw_properties_set(this->properties, PW_KEY_MODULE_NAME, name);
228 
229 	this->info.name = name ? strdup(name) : NULL;
230 	this->info.filename = filename;
231 	filename = NULL;
232 	this->info.args = args ? strdup(args) : NULL;
233 
234 	this->global = pw_global_new(context,
235 				     PW_TYPE_INTERFACE_Module,
236 				     PW_VERSION_MODULE,
237 				     pw_properties_new(
238 					     PW_KEY_MODULE_NAME, name,
239 					     NULL),
240 				     global_bind,
241 				     this);
242 
243 	if (this->global == NULL)
244 		goto error_no_global;
245 
246 	spa_list_prepend(&context->module_list, &this->link);
247 
248 	this->info.id = this->global->id;
249 	pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id);
250 	pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
251 			pw_global_get_serial(this->global));
252 	this->info.props = &this->properties->dict;
253 
254 	pw_impl_module_emit_initialized(this);
255 
256 	pw_global_add_listener(this->global, &this->global_listener, &global_events, this);
257 
258 	if ((res = init_func(this, args)) < 0)
259 		goto error_init_failed;
260 
261 	pw_global_register(this->global);
262 
263 	pw_impl_module_emit_registered(this);
264 
265 	pw_log_debug("%p: loaded module: %s", this, this->info.name);
266 
267 	return this;
268 
269 error_not_found:
270 	res = -ENOENT;
271 	pw_log_error("No module \"%s\" was found", name);
272 	goto error_cleanup;
273 error_open_failed:
274 	res = -ENOENT;
275 	pw_log_error("Failed to open module: \"%s\" %s", filename, dlerror());
276 	goto error_free_filename;
277 error_no_pw_module:
278 	res = -ENOSYS;
279 	pw_log_error("\"%s\": is not a pipewire module", filename);
280 	goto error_close;
281 error_no_mem:
282 	res = -errno;
283 	pw_log_error("can't allocate module: %m");
284 	goto error_close;
285 error_no_global:
286 	res = -errno;
287 	pw_log_error("\"%s\": failed to create global: %m", this->info.filename);
288 	goto error_free_module;
289 error_init_failed:
290 	pw_log_debug("\"%s\": failed to initialize: %s", this->info.filename, spa_strerror(res));
291 	goto error_free_module;
292 
293 error_free_module:
294 	pw_impl_module_destroy(this);
295 error_close:
296 	if (hnd)
297 		dlclose(hnd);
298 error_free_filename:
299 	if (filename)
300 		free(filename);
301 error_cleanup:
302 	pw_properties_free(properties);
303 	errno = -res;
304 	return NULL;
305 }
306 
307 /** Destroy a module
308  * \param module the module to destroy
309  */
310 SPA_EXPORT
pw_impl_module_destroy(struct pw_impl_module * module)311 void pw_impl_module_destroy(struct pw_impl_module *module)
312 {
313 	struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this);
314 
315 	pw_log_debug("%p: destroy", module);
316 	pw_impl_module_emit_destroy(module);
317 
318 	if (module->global) {
319 		spa_list_remove(&module->link);
320 		spa_hook_remove(&module->global_listener);
321 		pw_global_destroy(module->global);
322 	}
323 
324 	pw_log_debug("%p: free", module);
325 	pw_impl_module_emit_free(module);
326 	free((char *) module->info.name);
327 	free((char *) module->info.filename);
328 	free((char *) module->info.args);
329 
330 	pw_properties_free(module->properties);
331 
332 	spa_hook_list_clean(&module->listener_list);
333 
334 	if (!pw_in_valgrind() && dlclose(impl->hnd) != 0)
335 		pw_log_warn("%p: dlclose failed: %s", module, dlerror());
336 	free(impl);
337 }
338 
339 SPA_EXPORT
340 struct pw_context *
pw_impl_module_get_context(struct pw_impl_module * module)341 pw_impl_module_get_context(struct pw_impl_module *module)
342 {
343 	return module->context;
344 }
345 
346 SPA_EXPORT
pw_impl_module_get_global(struct pw_impl_module * module)347 struct pw_global * pw_impl_module_get_global(struct pw_impl_module *module)
348 {
349 	return module->global;
350 }
351 
352 SPA_EXPORT
pw_impl_module_get_properties(struct pw_impl_module * module)353 const struct pw_properties *pw_impl_module_get_properties(struct pw_impl_module *module)
354 {
355 	return module->properties;
356 }
357 
358 SPA_EXPORT
pw_impl_module_update_properties(struct pw_impl_module * module,const struct spa_dict * dict)359 int pw_impl_module_update_properties(struct pw_impl_module *module, const struct spa_dict *dict)
360 {
361 	struct pw_resource *resource;
362 	int changed;
363 
364 	changed = pw_properties_update(module->properties, dict);
365 	module->info.props = &module->properties->dict;
366 
367 	pw_log_debug("%p: updated %d properties", module, changed);
368 
369 	if (!changed)
370 		return 0;
371 
372 	module->info.change_mask |= PW_MODULE_CHANGE_MASK_PROPS;
373 	if (module->global)
374 		spa_list_for_each(resource, &module->global->resource_list, link)
375 			pw_module_resource_info(resource, &module->info);
376 	module->info.change_mask = 0;
377 
378 	return changed;
379 }
380 
381 SPA_EXPORT
382 const struct pw_module_info *
pw_impl_module_get_info(struct pw_impl_module * module)383 pw_impl_module_get_info(struct pw_impl_module *module)
384 {
385 	return &module->info;
386 }
387 
388 SPA_EXPORT
pw_impl_module_add_listener(struct pw_impl_module * module,struct spa_hook * listener,const struct pw_impl_module_events * events,void * data)389 void pw_impl_module_add_listener(struct pw_impl_module *module,
390 			    struct spa_hook *listener,
391 			    const struct pw_impl_module_events *events,
392 			    void *data)
393 {
394 	spa_hook_list_append(&module->listener_list, listener, events, data);
395 }
396