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