1 /* PipeWire
2  *
3  * Copyright © 2018 Wim Taymans
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the next
13  * paragraph) shall be included in all copies or substantial portions of the
14  * Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  */
24 
25 #include <errno.h>
26 #include <unistd.h>
27 #include <time.h>
28 #include <stdio.h>
29 
30 #include <pipewire/impl.h>
31 #include <pipewire/private.h>
32 
33 #include <spa/debug/types.h>
34 #include <spa/utils/string.h>
35 
36 PW_LOG_TOPIC_EXTERN(log_global);
37 #define PW_LOG_TOPIC_DEFAULT log_global
38 
39 static uint64_t serial = 0;
40 
41 /** \cond */
42 struct impl {
43 	struct pw_global this;
44 };
45 /** \endcond */
46 
47 SPA_EXPORT
pw_global_get_permissions(struct pw_global * global,struct pw_impl_client * client)48 uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_impl_client *client)
49 {
50 	if (client->permission_func == NULL)
51 		return PW_PERM_ALL;
52 
53 	return client->permission_func(global, client, client->permission_data);
54 }
55 
56 /** Create a new global
57  *
58  * \param context a context object
59  * \param type the type of the global
60  * \param version the version of the type
61  * \param properties extra properties
62  * \param func a function to bind to this global
63  * \param object the associated object
64  * \return a result global
65  *
66  */
67 SPA_EXPORT
68 struct pw_global *
pw_global_new(struct pw_context * context,const char * type,uint32_t version,struct pw_properties * properties,pw_global_bind_func_t func,void * object)69 pw_global_new(struct pw_context *context,
70 	      const char *type,
71 	      uint32_t version,
72 	      struct pw_properties *properties,
73 	      pw_global_bind_func_t func,
74 	      void *object)
75 {
76 	struct impl *impl;
77 	struct pw_global *this;
78 	int res;
79 
80 	if (properties == NULL)
81 		properties = pw_properties_new(NULL, NULL);
82 	if (properties == NULL)
83 		return NULL;
84 
85 	impl = calloc(1, sizeof(struct impl));
86 	if (impl == NULL) {
87 		res = -errno;
88 		goto error_cleanup;
89 	}
90 
91 	this = &impl->this;
92 
93 	this->context = context;
94 	this->type = type;
95 	this->version = version;
96 	this->func = func;
97 	this->object = object;
98 	this->properties = properties;
99 	this->id = pw_map_insert_new(&context->globals, this);
100 	if (this->id == SPA_ID_INVALID) {
101 		res = -errno;
102 		pw_log_error("%p: can't allocate new id: %m", this);
103 		goto error_free;
104 	}
105 	this->serial = SPA_ID_INVALID;
106 
107 	spa_list_init(&this->resource_list);
108 	spa_hook_list_init(&this->listener_list);
109 
110 	pw_log_debug("%p: new %s %d", this, this->type, this->id);
111 
112 	return this;
113 
114 error_free:
115 	free(impl);
116 error_cleanup:
117 	pw_properties_free(properties);
118 	errno = -res;
119 	return NULL;
120 }
121 
122 SPA_EXPORT
pw_global_get_serial(struct pw_global * global)123 uint64_t pw_global_get_serial(struct pw_global *global)
124 {
125 	if (global->serial == SPA_ID_INVALID)
126 		global->serial = serial++;
127 	if ((uint32_t)serial == SPA_ID_INVALID)
128 		serial++;
129 	return global->serial;
130 }
131 
132 /** register a global to the context registry
133  *
134  * \param global a global to add
135  * \return 0 on success < 0 errno value on failure
136  *
137  */
138 SPA_EXPORT
pw_global_register(struct pw_global * global)139 int pw_global_register(struct pw_global *global)
140 {
141 	struct pw_resource *registry;
142 	struct pw_context *context = global->context;
143 
144 	if (global->registered)
145 		return -EEXIST;
146 
147 	spa_list_append(&context->global_list, &global->link);
148 	global->registered = true;
149 
150 	spa_list_for_each(registry, &context->registry_resource_list, link) {
151 		uint32_t permissions = pw_global_get_permissions(global, registry->client);
152 		pw_log_debug("registry %p: global %d %08x serial:%"PRIu64,
153 				registry, global->id, permissions, global->serial);
154 		if (PW_PERM_IS_R(permissions))
155 			pw_registry_resource_global(registry,
156 						    global->id,
157 						    permissions,
158 						    global->type,
159 						    global->version,
160 						    &global->properties->dict);
161 	}
162 
163 	pw_log_debug("%p: registered %u", global, global->id);
164 	pw_context_emit_global_added(context, global);
165 
166 	return 0;
167 }
168 
global_unregister(struct pw_global * global)169 static int global_unregister(struct pw_global *global)
170 {
171 	struct pw_context *context = global->context;
172 	struct pw_resource *resource;
173 
174 	if (!global->registered)
175 		return 0;
176 
177 	spa_list_for_each(resource, &context->registry_resource_list, link) {
178 		uint32_t permissions = pw_global_get_permissions(global, resource->client);
179 		pw_log_debug("registry %p: global %d %08x", resource, global->id, permissions);
180 		if (PW_PERM_IS_R(permissions))
181 			pw_registry_resource_global_remove(resource, global->id);
182 	}
183 
184 	spa_list_remove(&global->link);
185 	global->registered = false;
186 	global->serial = SPA_ID_INVALID;
187 
188 	pw_log_debug("%p: unregistered %u", global, global->id);
189 	pw_context_emit_global_removed(context, global);
190 
191 	return 0;
192 }
193 
194 SPA_EXPORT
pw_global_get_context(struct pw_global * global)195 struct pw_context *pw_global_get_context(struct pw_global *global)
196 {
197 	return global->context;
198 }
199 
200 SPA_EXPORT
pw_global_get_type(struct pw_global * global)201 const char * pw_global_get_type(struct pw_global *global)
202 {
203 	return global->type;
204 }
205 
206 SPA_EXPORT
pw_global_is_type(struct pw_global * global,const char * type)207 bool pw_global_is_type(struct pw_global *global, const char *type)
208 {
209 	return spa_streq(global->type, type);
210 }
211 
212 SPA_EXPORT
pw_global_get_version(struct pw_global * global)213 uint32_t pw_global_get_version(struct pw_global *global)
214 {
215 	return global->version;
216 }
217 
218 SPA_EXPORT
pw_global_get_properties(struct pw_global * global)219 const struct pw_properties *pw_global_get_properties(struct pw_global *global)
220 {
221 	return global->properties;
222 }
223 
224 SPA_EXPORT
pw_global_update_keys(struct pw_global * global,const struct spa_dict * dict,const char * const keys[])225 int pw_global_update_keys(struct pw_global *global,
226 		     const struct spa_dict *dict, const char * const keys[])
227 {
228 	if (global->registered)
229 		return -EINVAL;
230 	return pw_properties_update_keys(global->properties, dict, keys);
231 }
232 
233 SPA_EXPORT
pw_global_get_object(struct pw_global * global)234 void * pw_global_get_object(struct pw_global *global)
235 {
236 	return global->object;
237 }
238 
239 SPA_EXPORT
pw_global_get_id(struct pw_global * global)240 uint32_t pw_global_get_id(struct pw_global *global)
241 {
242 	return global->id;
243 }
244 
245 SPA_EXPORT
pw_global_add_resource(struct pw_global * global,struct pw_resource * resource)246 int pw_global_add_resource(struct pw_global *global, struct pw_resource *resource)
247 {
248 	resource->global = global;
249 	pw_log_debug("%p: resource %p id:%d global:%d", global, resource,
250 			resource->id, global->id);
251 	spa_list_append(&global->resource_list, &resource->link);
252 	pw_resource_set_bound_id(resource, global->id);
253 	return 0;
254 }
255 
256 SPA_EXPORT
pw_global_for_each_resource(struct pw_global * global,int (* callback)(void * data,struct pw_resource * resource),void * data)257 int pw_global_for_each_resource(struct pw_global *global,
258 			   int (*callback) (void *data, struct pw_resource *resource),
259 			   void *data)
260 {
261 	struct pw_resource *resource, *t;
262 	int res;
263 
264 	spa_list_for_each_safe(resource, t, &global->resource_list, link)
265 		if ((res = callback(data, resource)) != 0)
266 			return res;
267 	return 0;
268 }
269 
270 SPA_EXPORT
pw_global_add_listener(struct pw_global * global,struct spa_hook * listener,const struct pw_global_events * events,void * data)271 void pw_global_add_listener(struct pw_global *global,
272 			    struct spa_hook *listener,
273 			    const struct pw_global_events *events,
274 			    void *data)
275 {
276 	spa_hook_list_append(&global->listener_list, listener, events, data);
277 }
278 
279 /** Bind to a global
280  *
281  * \param global the global to bind to
282  * \param client the client that binds
283  * \param permissions the \ref pw_permission
284  * \param version the version
285  * \param id the id of the resource
286  *
287  * Let \a client bind to \a global with the given version and id.
288  * After binding, the client and the global object will be able to
289  * exchange messages on the proxy/resource with \a id.
290  *
291  */
292 SPA_EXPORT int
pw_global_bind(struct pw_global * global,struct pw_impl_client * client,uint32_t permissions,uint32_t version,uint32_t id)293 pw_global_bind(struct pw_global *global, struct pw_impl_client *client, uint32_t permissions,
294               uint32_t version, uint32_t id)
295 {
296 	int res;
297 
298 	if (global->version < version)
299 		goto error_version;
300 
301 	if ((res = global->func(global->object, client, permissions, version, id)) < 0)
302 		goto error_bind;
303 
304 	return res;
305 
306 error_version:
307 	res = -EPROTO;
308 	if (client->core_resource)
309 		pw_core_resource_errorf(client->core_resource, id, client->recv_seq,
310 				res, "id %d: interface version %d < %d",
311 				id, global->version, version);
312 	goto error_exit;
313 error_bind:
314 	if (client->core_resource)
315 		pw_core_resource_errorf(client->core_resource, id, client->recv_seq,
316 			res, "can't bind global %u/%u: %d (%s)", id, version,
317 			res, spa_strerror(res));
318 	goto error_exit;
319 
320 error_exit:
321 	pw_log_error("%p: can't bind global %u/%u: %d (%s)", global, id,
322 			version, res, spa_strerror(res));
323 	pw_map_insert_at(&client->objects, id, NULL);
324 	if (client->core_resource)
325 		pw_core_resource_remove_id(client->core_resource, id);
326 	return res;
327 }
328 
329 SPA_EXPORT
pw_global_update_permissions(struct pw_global * global,struct pw_impl_client * client,uint32_t old_permissions,uint32_t new_permissions)330 int pw_global_update_permissions(struct pw_global *global, struct pw_impl_client *client,
331 		uint32_t old_permissions, uint32_t new_permissions)
332 {
333 	struct pw_context *context = global->context;
334 	struct pw_resource *resource, *t;
335 	bool do_hide, do_show;
336 
337 	do_hide = PW_PERM_IS_R(old_permissions) && !PW_PERM_IS_R(new_permissions);
338 	do_show = !PW_PERM_IS_R(old_permissions) && PW_PERM_IS_R(new_permissions);
339 
340 	pw_log_debug("%p: client %p permissions changed %d %08x -> %08x",
341 			global, client, global->id, old_permissions, new_permissions);
342 
343 	pw_global_emit_permissions_changed(global, client, old_permissions, new_permissions);
344 
345 	spa_list_for_each(resource, &context->registry_resource_list, link) {
346 		if (resource->client != client)
347 			continue;
348 
349 		if (do_hide) {
350 			pw_log_debug("client %p: resource %p hide global %d",
351 					client, resource, global->id);
352 			pw_registry_resource_global_remove(resource, global->id);
353 		}
354 		else if (do_show) {
355 			pw_log_debug("client %p: resource %p show global %d serial:%"PRIu64,
356 					client, resource, global->id,
357 					global->serial);
358 			pw_registry_resource_global(resource,
359 						    global->id,
360 						    new_permissions,
361 						    global->type,
362 						    global->version,
363 						    &global->properties->dict);
364 		}
365 	}
366 
367 	spa_list_for_each_safe(resource, t, &global->resource_list, link) {
368 		if (resource->client != client)
369 			continue;
370 
371 		/* don't ever destroy the core resource */
372 		if (!PW_PERM_IS_R(new_permissions) && global->id != PW_ID_CORE)
373 			pw_resource_destroy(resource);
374 		else
375 			resource->permissions = new_permissions;
376 	}
377 	return 0;
378 }
379 
380 /** Destroy a global
381  *
382  * \param global a global to destroy
383  *
384  */
385 SPA_EXPORT
pw_global_destroy(struct pw_global * global)386 void pw_global_destroy(struct pw_global *global)
387 {
388 	struct pw_resource *resource;
389 	struct pw_context *context = global->context;
390 
391 	global->destroyed = true;
392 
393 	pw_log_debug("%p: destroy %u", global, global->id);
394 	pw_global_emit_destroy(global);
395 
396 	spa_list_consume(resource, &global->resource_list, link)
397 		pw_resource_destroy(resource);
398 
399 	global_unregister(global);
400 
401 	pw_log_debug("%p: free", global);
402 	pw_global_emit_free(global);
403 
404 	pw_map_remove(&context->globals, global->id);
405 	spa_hook_list_clean(&global->listener_list);
406 
407 	pw_properties_free(global->properties);
408 
409 	free(global);
410 }
411