]> code.delx.au - pulseaudio/blob - src/modules/module-zeroconf-publish.c
remaining s/assert/pa_assert/ and refcnt.h modernizations
[pulseaudio] / src / modules / module-zeroconf-publish.c
1 /* $Id$ */
2
3 /***
4 This file is part of PulseAudio.
5
6 Copyright 2004-2006 Lennart Poettering
7
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
12
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include <avahi-client/client.h>
34 #include <avahi-client/publish.h>
35 #include <avahi-common/alternative.h>
36 #include <avahi-common/error.h>
37 #include <avahi-common/domain.h>
38
39 #include <pulse/xmalloc.h>
40 #include <pulse/util.h>
41
42 #include <pulsecore/autoload.h>
43 #include <pulsecore/sink.h>
44 #include <pulsecore/source.h>
45 #include <pulsecore/native-common.h>
46 #include <pulsecore/core-util.h>
47 #include <pulsecore/log.h>
48 #include <pulsecore/core-subscribe.h>
49 #include <pulsecore/dynarray.h>
50 #include <pulsecore/modargs.h>
51 #include <pulsecore/avahi-wrap.h>
52 #include <pulsecore/endianmacros.h>
53
54 #include "module-zeroconf-publish-symdef.h"
55
56 PA_MODULE_AUTHOR("Lennart Poettering")
57 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
58 PA_MODULE_VERSION(PACKAGE_VERSION)
59 PA_MODULE_USAGE("port=<IP port number>")
60
61 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
62 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
63 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
64
65 static const char* const valid_modargs[] = {
66 "port",
67 NULL
68 };
69
70 struct service {
71 struct userdata *userdata;
72 AvahiEntryGroup *entry_group;
73 char *service_name;
74 char *name;
75 enum { UNPUBLISHED, PUBLISHED_REAL, PUBLISHED_AUTOLOAD } published ;
76
77 struct {
78 int valid;
79 pa_namereg_type_t type;
80 uint32_t index;
81 } loaded;
82
83 struct {
84 int valid;
85 pa_namereg_type_t type;
86 uint32_t index;
87 } autoload;
88 };
89
90 struct userdata {
91 pa_core *core;
92 AvahiPoll *avahi_poll;
93 AvahiClient *client;
94 pa_hashmap *services;
95 pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
96 pa_subscription *subscription;
97 char *service_name;
98
99 AvahiEntryGroup *main_entry_group;
100
101 uint16_t port;
102 };
103
104 static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) {
105 pa_assert(u);
106 pa_assert(s);
107 pa_assert(s->loaded.valid);
108 pa_assert(ret_ss);
109 pa_assert(ret_description);
110
111 if (s->loaded.type == PA_NAMEREG_SINK) {
112 pa_sink *sink = PA_SINK(pa_idxset_get_by_index(u->core->sinks, s->loaded.index));
113 pa_sink_assert_ref(sink);
114 *ret_ss = sink->sample_spec;
115 *ret_description = sink->description;
116
117 } else if (s->loaded.type == PA_NAMEREG_SOURCE) {
118 pa_source *source = PA_SOURCE(pa_idxset_get_by_index(u->core->sources, s->loaded.index));
119 pa_source_assert_ref(source);
120 *ret_ss = source->sample_spec;
121 *ret_description = source->description;
122 } else
123 pa_assert_not_reached();
124 }
125
126 static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
127 char s[128];
128
129 pa_core_assert_ref(c);
130
131 l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
132 l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s)));
133 l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));
134 l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie);
135
136 return l;
137 }
138
139 static int publish_service(struct userdata *u, struct service *s);
140
141 static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
142 struct service *s = userdata;
143
144 pa_assert(s);
145
146 if (state == AVAHI_ENTRY_GROUP_COLLISION) {
147 char *t;
148
149 t = avahi_alternative_service_name(s->service_name);
150 pa_xfree(s->service_name);
151 s->service_name = t;
152
153 publish_service(s->userdata, s);
154 }
155 }
156
157 static int publish_service(struct userdata *u, struct service *s) {
158 int r = -1;
159 AvahiStringList *txt = NULL;
160
161 pa_assert(u);
162 pa_assert(s);
163
164 if (!u->client || avahi_client_get_state(u->client) != AVAHI_CLIENT_S_RUNNING)
165 return 0;
166
167 if ((s->published == PUBLISHED_REAL && s->loaded.valid) ||
168 (s->published == PUBLISHED_AUTOLOAD && s->autoload.valid && !s->loaded.valid))
169 return 0;
170
171 if (s->published != UNPUBLISHED) {
172 avahi_entry_group_reset(s->entry_group);
173 s->published = UNPUBLISHED;
174 }
175
176 if (s->loaded.valid || s->autoload.valid) {
177 pa_namereg_type_t type;
178
179 if (!s->entry_group) {
180 if (!(s->entry_group = avahi_entry_group_new(u->client, service_entry_group_callback, s))) {
181 pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(u->client)));
182 goto finish;
183 }
184 }
185
186 txt = avahi_string_list_add_pair(txt, "device", s->name);
187 txt = txt_record_server_data(u->core, txt);
188
189 if (s->loaded.valid) {
190 char *description;
191 pa_sample_spec ss;
192
193 get_service_data(u, s, &ss, &description);
194
195 txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
196 txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
197 txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
198 if (description)
199 txt = avahi_string_list_add_pair(txt, "description", description);
200
201 type = s->loaded.type;
202 } else if (s->autoload.valid)
203 type = s->autoload.type;
204
205 if (avahi_entry_group_add_service_strlst(
206 s->entry_group,
207 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
208 0,
209 s->service_name,
210 type == PA_NAMEREG_SINK ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
211 NULL,
212 NULL,
213 u->port,
214 txt) < 0) {
215
216 pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(u->client)));
217 goto finish;
218 }
219
220 if (avahi_entry_group_commit(s->entry_group) < 0) {
221 pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(u->client)));
222 goto finish;
223 }
224
225 if (s->loaded.valid)
226 s->published = PUBLISHED_REAL;
227 else if (s->autoload.valid)
228 s->published = PUBLISHED_AUTOLOAD;
229 }
230
231 r = 0;
232
233 finish:
234
235 if (s->published == UNPUBLISHED) {
236 /* Remove this service */
237
238 if (s->entry_group)
239 avahi_entry_group_free(s->entry_group);
240
241 pa_hashmap_remove(u->services, s->name);
242 pa_xfree(s->name);
243 pa_xfree(s->service_name);
244 pa_xfree(s);
245 }
246
247 if (txt)
248 avahi_string_list_free(txt);
249
250 return r;
251 }
252
253 static struct service *get_service(struct userdata *u, const char *name, const char *description) {
254 struct service *s;
255 char hn[64], un[64];
256
257 if ((s = pa_hashmap_get(u->services, name)))
258 return s;
259
260 s = pa_xnew(struct service, 1);
261 s->userdata = u;
262 s->entry_group = NULL;
263 s->published = UNPUBLISHED;
264 s->name = pa_xstrdup(name);
265 s->loaded.valid = s->autoload.valid = 0;
266 s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn)), description ? description : s->name), AVAHI_LABEL_MAX-1);
267
268 pa_hashmap_put(u->services, s->name, s);
269
270 return s;
271 }
272
273 static int publish_sink(struct userdata *u, pa_sink *s) {
274 struct service *svc;
275 int ret;
276
277 pa_assert(u);
278 pa_sink_assert_ref(s);
279
280 svc = get_service(u, s->name, s->description);
281 if (svc->loaded.valid)
282 return publish_service(u, svc);
283
284 svc->loaded.valid = 1;
285 svc->loaded.type = PA_NAMEREG_SINK;
286 svc->loaded.index = s->index;
287
288 if ((ret = publish_service(u, svc)) < 0)
289 return ret;
290
291 pa_dynarray_put(u->sink_dynarray, s->index, svc);
292 return ret;
293 }
294
295 static int publish_source(struct userdata *u, pa_source *s) {
296 struct service *svc;
297 int ret;
298
299 pa_assert(u);
300 pa_source_assert_ref(s);
301
302 svc = get_service(u, s->name, s->description);
303 if (svc->loaded.valid)
304 return publish_service(u, svc);
305
306 svc->loaded.valid = 1;
307 svc->loaded.type = PA_NAMEREG_SOURCE;
308 svc->loaded.index = s->index;
309
310 pa_dynarray_put(u->source_dynarray, s->index, svc);
311
312 if ((ret = publish_service(u, svc)) < 0)
313 return ret;
314
315 pa_dynarray_put(u->sink_dynarray, s->index, svc);
316 return ret;
317 }
318
319 static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
320 struct service *svc;
321 int ret;
322
323 pa_assert(u);
324 pa_assert(s);
325
326 svc = get_service(u, s->name, NULL);
327 if (svc->autoload.valid)
328 return publish_service(u, svc);
329
330 svc->autoload.valid = 1;
331 svc->autoload.type = s->type;
332 svc->autoload.index = s->index;
333
334 if ((ret = publish_service(u, svc)) < 0)
335 return ret;
336
337 pa_dynarray_put(u->autoload_dynarray, s->index, svc);
338 return ret;
339 }
340
341 static int remove_sink(struct userdata *u, uint32_t idx) {
342 struct service *svc;
343
344 pa_assert(u);
345 pa_assert(idx != PA_INVALID_INDEX);
346
347 if (!(svc = pa_dynarray_get(u->sink_dynarray, idx)))
348 return 0;
349
350 if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
351 return 0;
352
353 svc->loaded.valid = 0;
354 pa_dynarray_put(u->sink_dynarray, idx, NULL);
355
356 return publish_service(u, svc);
357 }
358
359 static int remove_source(struct userdata *u, uint32_t idx) {
360 struct service *svc;
361
362 pa_assert(u);
363 pa_assert(idx != PA_INVALID_INDEX);
364
365 if (!(svc = pa_dynarray_get(u->source_dynarray, idx)))
366 return 0;
367
368 if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
369 return 0;
370
371 svc->loaded.valid = 0;
372 pa_dynarray_put(u->source_dynarray, idx, NULL);
373
374 return publish_service(u, svc);
375 }
376
377 static int remove_autoload(struct userdata *u, uint32_t idx) {
378 struct service *svc;
379
380 pa_assert(u);
381 pa_assert(idx != PA_INVALID_INDEX);
382
383 if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx)))
384 return 0;
385
386 if (!svc->autoload.valid)
387 return 0;
388
389 svc->autoload.valid = 0;
390 pa_dynarray_put(u->autoload_dynarray, idx, NULL);
391
392 return publish_service(u, svc);
393 }
394
395 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
396 struct userdata *u = userdata;
397
398 pa_assert(u);
399 pa_core_assert_ref(c);
400
401 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
402
403 case PA_SUBSCRIPTION_EVENT_SINK: {
404 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
405 pa_sink *sink;
406
407 if ((sink = PA_SINK(pa_idxset_get_by_index(c->sinks, idx)))) {
408 if (publish_sink(u, sink) < 0)
409 goto fail;
410 }
411 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
412 if (remove_sink(u, idx) < 0)
413 goto fail;
414 }
415
416 break;
417
418 case PA_SUBSCRIPTION_EVENT_SOURCE:
419
420 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
421 pa_source *source;
422
423 if ((source = PA_SOURCE(pa_idxset_get_by_index(c->sources, idx)))) {
424 if (publish_source(u, source) < 0)
425 goto fail;
426 }
427 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
428 if (remove_source(u, idx) < 0)
429 goto fail;
430 }
431
432 break;
433
434 case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
435 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
436 pa_autoload_entry *autoload;
437
438 if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, idx))) {
439 if (publish_autoload(u, autoload) < 0)
440 goto fail;
441 }
442 } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
443 if (remove_autoload(u, idx) < 0)
444 goto fail;
445 }
446
447 break;
448 }
449
450 return;
451
452 fail:
453 if (u->subscription) {
454 pa_subscription_free(u->subscription);
455 u->subscription = NULL;
456 }
457 }
458
459 static int publish_main_service(struct userdata *u);
460
461 static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
462 struct userdata *u = userdata;
463 pa_assert(u);
464
465 if (state == AVAHI_ENTRY_GROUP_COLLISION) {
466 char *t;
467
468 t = avahi_alternative_service_name(u->service_name);
469 pa_xfree(u->service_name);
470 u->service_name = t;
471
472 publish_main_service(u);
473 }
474 }
475
476 static int publish_main_service(struct userdata *u) {
477 AvahiStringList *txt = NULL;
478 int r = -1;
479
480 pa_assert(u);
481
482 if (!u->main_entry_group) {
483 if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
484 pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
485 goto fail;
486 }
487 } else
488 avahi_entry_group_reset(u->main_entry_group);
489
490 txt = txt_record_server_data(u->core, NULL);
491
492 if (avahi_entry_group_add_service_strlst(
493 u->main_entry_group,
494 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
495 0,
496 u->service_name,
497 SERVICE_TYPE_SERVER,
498 NULL,
499 NULL,
500 u->port,
501 txt) < 0) {
502
503 pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
504 goto fail;
505 }
506
507 if (avahi_entry_group_commit(u->main_entry_group) < 0) {
508 pa_log("avahi_entry_group_commit() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
509 goto fail;
510 }
511
512 r = 0;
513
514 fail:
515 avahi_string_list_free(txt);
516
517 return r;
518 }
519
520 static int publish_all_services(struct userdata *u) {
521 pa_sink *sink;
522 pa_source *source;
523 pa_autoload_entry *autoload;
524 int r = -1;
525 uint32_t idx;
526
527 pa_assert(u);
528
529 pa_log_debug("Publishing services in Zeroconf");
530
531 for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
532 if (publish_sink(u, sink) < 0)
533 goto fail;
534
535 for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
536 if (publish_source(u, source) < 0)
537 goto fail;
538
539 if (u->core->autoload_idxset)
540 for (autoload = pa_idxset_first(u->core->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(u->core->autoload_idxset, &idx))
541 if (publish_autoload(u, autoload) < 0)
542 goto fail;
543
544 if (publish_main_service(u) < 0)
545 goto fail;
546
547 r = 0;
548
549 fail:
550 return r;
551 }
552
553 static void unpublish_all_services(struct userdata *u, int rem) {
554 void *state = NULL;
555 struct service *s;
556
557 pa_assert(u);
558
559 pa_log_debug("Unpublishing services in Zeroconf");
560
561 while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
562 if (s->entry_group) {
563 if (rem) {
564 avahi_entry_group_free(s->entry_group);
565 s->entry_group = NULL;
566 } else
567 avahi_entry_group_reset(s->entry_group);
568 }
569
570 s->published = UNPUBLISHED;
571 }
572
573 if (u->main_entry_group) {
574 if (rem) {
575 avahi_entry_group_free(u->main_entry_group);
576 u->main_entry_group = NULL;
577 } else
578 avahi_entry_group_reset(u->main_entry_group);
579 }
580 }
581
582 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
583 struct userdata *u = userdata;
584
585 pa_assert(c);
586 pa_assert(u);
587
588 u->client = c;
589
590 switch (state) {
591 case AVAHI_CLIENT_S_RUNNING:
592 publish_all_services(u);
593 break;
594
595 case AVAHI_CLIENT_S_COLLISION:
596 unpublish_all_services(u, 0);
597 break;
598
599 case AVAHI_CLIENT_FAILURE:
600 if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
601 int error;
602 unpublish_all_services(u, 1);
603 avahi_client_free(u->client);
604
605 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error)))
606 pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
607 }
608
609 break;
610
611 default: ;
612 }
613 }
614
615 int pa__init(pa_module*m) {
616
617 struct userdata *u;
618 uint32_t port = PA_NATIVE_DEFAULT_PORT;
619 pa_modargs *ma = NULL;
620 char hn[256], un[256];
621 int error;
622
623 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
624 pa_log("failed to parse module arguments.");
625 goto fail;
626 }
627
628 if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) {
629 pa_log("invalid port specified.");
630 goto fail;
631 }
632
633 m->userdata = u = pa_xnew(struct userdata, 1);
634 u->core = m->core;
635 u->port = (uint16_t) port;
636
637 u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
638
639 u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
640 u->sink_dynarray = pa_dynarray_new();
641 u->source_dynarray = pa_dynarray_new();
642 u->autoload_dynarray = pa_dynarray_new();
643
644 u->subscription = pa_subscription_new(m->core,
645 PA_SUBSCRIPTION_MASK_SINK|
646 PA_SUBSCRIPTION_MASK_SOURCE|
647 PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
648
649 u->main_entry_group = NULL;
650
651 u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX);
652
653 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
654 pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
655 goto fail;
656 }
657
658 pa_modargs_free(ma);
659
660 return 0;
661
662 fail:
663 pa__done(m);
664
665 if (ma)
666 pa_modargs_free(ma);
667
668 return -1;
669 }
670
671 static void service_free(void *p, void *userdata) {
672 struct service *s = p;
673 struct userdata *u = userdata;
674
675 pa_assert(s);
676 pa_assert(u);
677
678 if (s->entry_group)
679 avahi_entry_group_free(s->entry_group);
680
681 pa_xfree(s->service_name);
682 pa_xfree(s->name);
683 pa_xfree(s);
684 }
685
686 void pa__done(pa_module*m) {
687 struct userdata*u;
688 pa_assert(m);
689
690 if (!(u = m->userdata))
691 return;
692
693 if (u->services)
694 pa_hashmap_free(u->services, service_free, u);
695
696 if (u->subscription)
697 pa_subscription_free(u->subscription);
698
699 if (u->sink_dynarray)
700 pa_dynarray_free(u->sink_dynarray, NULL, NULL);
701 if (u->source_dynarray)
702 pa_dynarray_free(u->source_dynarray, NULL, NULL);
703 if (u->autoload_dynarray)
704 pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
705
706 if (u->main_entry_group)
707 avahi_entry_group_free(u->main_entry_group);
708
709 if (u->client)
710 avahi_client_free(u->client);
711
712 if (u->avahi_poll)
713 pa_avahi_poll_free(u->avahi_poll);
714
715 pa_xfree(u->service_name);
716 pa_xfree(u);
717 }
718