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