1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2004-2006 Lennart Poettering
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27
28 #include <avahi-client/client.h>
29 #include <avahi-client/publish.h>
30 #include <avahi-common/alternative.h>
31 #include <avahi-common/error.h>
32 #include <avahi-common/domain.h>
33
34 #include <pulse/xmalloc.h>
35 #include <pulse/util.h>
36 #include <pulse/thread-mainloop.h>
37
38 #include <pulsecore/parseaddr.h>
39 #include <pulsecore/sink.h>
40 #include <pulsecore/source.h>
41 #include <pulsecore/native-common.h>
42 #include <pulsecore/core-util.h>
43 #include <pulsecore/log.h>
44 #include <pulsecore/dynarray.h>
45 #include <pulsecore/modargs.h>
46 #include <pulsecore/avahi-wrap.h>
47 #include <pulsecore/protocol-native.h>
48
49 #ifdef HAVE_DBUS
50 #include <pulsecore/dbus-shared.h>
51
52 #define HOSTNAME_DBUS_INTERFACE "org.freedesktop.hostname1"
53 #define HOSTNAME_DBUS_PATH "/org/freedesktop/hostname1"
54 #define HOSTNAME_DBUS_ICON_PROPERTY "IconName"
55 #endif
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_LOAD_ONCE(true);
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 #define SERVICE_SUBTYPE_SINK_HARDWARE "_hardware._sub."SERVICE_TYPE_SINK
66 #define SERVICE_SUBTYPE_SINK_VIRTUAL "_virtual._sub."SERVICE_TYPE_SINK
67 #define SERVICE_SUBTYPE_SOURCE_HARDWARE "_hardware._sub."SERVICE_TYPE_SOURCE
68 #define SERVICE_SUBTYPE_SOURCE_VIRTUAL "_virtual._sub."SERVICE_TYPE_SOURCE
69 #define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE
70 #define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE
71
72 /*
73 * Note: Because the core avahi-client calls result in synchronous D-Bus
74 * communication, calling any of those functions in the PA mainloop context
75 * could lead to the mainloop being blocked for long periods.
76 *
77 * To avoid this, we create a threaded-mainloop for Avahi calls, and push all
78 * D-Bus communication into that thread. The thumb-rule for the split is:
79 *
80 * 1. If access to PA data structures is needed, use the PA mainloop context
81 *
82 * 2. If a (blocking) avahi-client call is needed, use the Avahi mainloop
83 *
84 * We do have message queue to pass messages from the Avahi mainloop to the PA
85 * mainloop.
86 */
87
88 static const char* const valid_modargs[] = {
89 NULL
90 };
91
92 struct avahi_msg {
93 pa_msgobject parent;
94 };
95
96 typedef struct avahi_msg avahi_msg;
97 PA_DEFINE_PRIVATE_CLASS(avahi_msg, pa_msgobject);
98
99 enum {
100 AVAHI_MESSAGE_PUBLISH_ALL,
101 AVAHI_MESSAGE_SHUTDOWN_START,
102 AVAHI_MESSAGE_SHUTDOWN_COMPLETE,
103 };
104
105 enum service_subtype {
106 SUBTYPE_HARDWARE,
107 SUBTYPE_VIRTUAL,
108 SUBTYPE_MONITOR
109 };
110
111 struct service {
112 void *key;
113
114 struct userdata *userdata;
115 AvahiEntryGroup *entry_group;
116 char *service_name;
117 const char *service_type;
118 enum service_subtype subtype;
119
120 char *name;
121 bool is_sink;
122 pa_sample_spec ss;
123 pa_channel_map cm;
124 pa_proplist *proplist;
125 };
126
127 struct userdata {
128 pa_thread_mq thread_mq;
129 pa_rtpoll *rtpoll;
130 avahi_msg *msg;
131
132 pa_core *core;
133 pa_module *module;
134 pa_mainloop_api *api;
135 pa_threaded_mainloop *mainloop;
136
137 AvahiPoll *avahi_poll;
138 AvahiClient *client;
139
140 pa_hashmap *services; /* protect with mainloop lock */
141 char *service_name;
142 char *icon_name;
143
144 AvahiEntryGroup *main_entry_group;
145
146 pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
147
148 pa_native_protocol *native;
149
150 bool shutting_down; /* Used in the main thread. */
151 bool client_freed; /* Used in the Avahi thread. */
152 };
153
154 /* Runs in PA mainloop context */
get_service_data(struct service *s, pa_object *device)155 static void get_service_data(struct service *s, pa_object *device) {
156 pa_assert(s);
157
158 if (pa_sink_isinstance(device)) {
159 pa_sink *sink = PA_SINK(device);
160
161 s->is_sink = true;
162 s->service_type = SERVICE_TYPE_SINK;
163 s->ss = sink->sample_spec;
164 s->cm = sink->channel_map;
165 s->name = pa_xstrdup(sink->name);
166 s->proplist = pa_proplist_copy(sink->proplist);
167 s->subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
168
169 } else if (pa_source_isinstance(device)) {
170 pa_source *source = PA_SOURCE(device);
171
172 s->is_sink = false;
173 s->service_type = SERVICE_TYPE_SOURCE;
174 s->ss = source->sample_spec;
175 s->cm = source->channel_map;
176 s->name = pa_xstrdup(source->name);
177 s->proplist = pa_proplist_copy(source->proplist);
178 s->subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
179
180 } else
181 pa_assert_not_reached();
182 }
183
184 /* Can be used in either PA or Avahi mainloop context since the bits of u->core
185 * that we access don't change after startup. */
txt_record_server_data(pa_core *c, AvahiStringList *l)186 static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
187 char s[128];
188 char *t;
189
190 pa_assert(c);
191
192 l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
193
194 t = pa_get_user_name_malloc();
195 l = avahi_string_list_add_pair(l, "user-name", t);
196 pa_xfree(t);
197
198 t = pa_machine_id();
199 l = avahi_string_list_add_pair(l, "machine-id", t);
200 pa_xfree(t);
201
202 t = pa_uname_string();
203 l = avahi_string_list_add_pair(l, "uname", t);
204 pa_xfree(t);
205
206 l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));
207 l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie);
208
209 return l;
210 }
211
212 static void publish_service(pa_mainloop_api *api, void *service);
213
214 /* Runs in Avahi mainloop context */
service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)215 static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
216 struct service *s = userdata;
217
218 pa_assert(s);
219
220 switch (state) {
221
222 case AVAHI_ENTRY_GROUP_ESTABLISHED:
223 pa_log_info("Successfully established service %s.", s->service_name);
224 break;
225
226 case AVAHI_ENTRY_GROUP_COLLISION: {
227 char *t;
228
229 t = avahi_alternative_service_name(s->service_name);
230 pa_log_info("Name collision, renaming %s to %s.", s->service_name, t);
231 pa_xfree(s->service_name);
232 s->service_name = t;
233
234 publish_service(NULL, s);
235 break;
236 }
237
238 case AVAHI_ENTRY_GROUP_FAILURE: {
239 pa_log("Failed to register service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
240
241 avahi_entry_group_free(g);
242 s->entry_group = NULL;
243
244 break;
245 }
246
247 case AVAHI_ENTRY_GROUP_UNCOMMITED:
248 case AVAHI_ENTRY_GROUP_REGISTERING:
249 ;
250 }
251 }
252
253 static void service_free(struct service *s);
254
255 /* Can run in either context */
compute_port(struct userdata *u)256 static uint16_t compute_port(struct userdata *u) {
257 pa_strlist *i;
258
259 pa_assert(u);
260
261 for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
262 pa_parsed_address a;
263
264 if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
265 (a.type == PA_PARSED_ADDRESS_TCP4 ||
266 a.type == PA_PARSED_ADDRESS_TCP6 ||
267 a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
268 a.port > 0) {
269
270 pa_xfree(a.path_or_host);
271 return a.port;
272 }
273
274 pa_xfree(a.path_or_host);
275 }
276
277 return PA_NATIVE_DEFAULT_PORT;
278 }
279
280 /* Runs in Avahi mainloop context */
publish_service(pa_mainloop_api *api PA_GCC_UNUSED, void *service)281 static void publish_service(pa_mainloop_api *api PA_GCC_UNUSED, void *service) {
282 struct service *s = (struct service *) service;
283 int r = -1;
284 AvahiStringList *txt = NULL;
285 char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
286 const char *t;
287
288 const char * const subtype_text[] = {
289 [SUBTYPE_HARDWARE] = "hardware",
290 [SUBTYPE_VIRTUAL] = "virtual",
291 [SUBTYPE_MONITOR] = "monitor"
292 };
293
294 pa_assert(s);
295
296 if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING)
297 return;
298
299 if (!s->entry_group) {
300 if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) {
301 pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
302 goto finish;
303 }
304 } else
305 avahi_entry_group_reset(s->entry_group);
306
307 txt = txt_record_server_data(s->userdata->core, txt);
308
309 txt = avahi_string_list_add_pair(txt, "device", s->name);
310 txt = avahi_string_list_add_printf(txt, "rate=%u", s->ss.rate);
311 txt = avahi_string_list_add_printf(txt, "channels=%u", s->ss.channels);
312 txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(s->ss.format));
313 txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &s->cm));
314 txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[s->subtype]);
315
316 if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)))
317 txt = avahi_string_list_add_pair(txt, "description", t);
318 if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_VENDOR_NAME)))
319 txt = avahi_string_list_add_pair(txt, "vendor-name", t);
320 if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
321 txt = avahi_string_list_add_pair(txt, "product-name", t);
322 if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS)))
323 txt = avahi_string_list_add_pair(txt, "class", t);
324 if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_FORM_FACTOR)))
325 txt = avahi_string_list_add_pair(txt, "form-factor", t);
326
327 if (s->userdata->icon_name) {
328 txt = avahi_string_list_add_pair(txt, "icon-name", s->userdata->icon_name);
329 } else if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_ICON_NAME))) {
330 txt = avahi_string_list_add_pair(txt, "icon-name", t);
331 }
332
333 if (avahi_entry_group_add_service_strlst(
334 s->entry_group,
335 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
336 0,
337 s->service_name,
338 s->service_type,
339 NULL,
340 NULL,
341 compute_port(s->userdata),
342 txt) < 0) {
343
344 pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
345 goto finish;
346 }
347
348 if (avahi_entry_group_add_service_subtype(
349 s->entry_group,
350 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
351 0,
352 s->service_name,
353 s->service_type,
354 NULL,
355 s->is_sink ? (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) :
356 (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (s->subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) {
357
358 pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
359 goto finish;
360 }
361
362 if (!s->is_sink && s->subtype != SUBTYPE_MONITOR) {
363 if (avahi_entry_group_add_service_subtype(
364 s->entry_group,
365 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
366 0,
367 s->service_name,
368 SERVICE_TYPE_SOURCE,
369 NULL,
370 SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) {
371
372 pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
373 goto finish;
374 }
375 }
376
377 if (avahi_entry_group_commit(s->entry_group) < 0) {
378 pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
379 goto finish;
380 }
381
382 r = 0;
383 pa_log_debug("Successfully created entry group for %s.", s->service_name);
384
385 finish:
386
387 /* Remove this service */
388 if (r < 0)
389 pa_hashmap_remove_and_free(s->userdata->services, s->key);
390
391 avahi_string_list_free(txt);
392 }
393
394 /* Runs in PA mainloop context */
get_service(struct userdata *u, pa_object *device)395 static struct service *get_service(struct userdata *u, pa_object *device) {
396 struct service *s;
397 char *hn, *un;
398 const char *n;
399
400 pa_assert(u);
401 pa_object_assert_ref(device);
402
403 pa_threaded_mainloop_lock(u->mainloop);
404
405 if ((s = pa_hashmap_get(u->services, device)))
406 goto out;
407
408 s = pa_xnew(struct service, 1);
409 s->key = device;
410 s->userdata = u;
411 s->entry_group = NULL;
412
413 get_service_data(s, device);
414
415 if (!(n = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)))
416 n = s->name;
417
418 hn = pa_get_host_name_malloc();
419 un = pa_get_user_name_malloc();
420
421 s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), AVAHI_LABEL_MAX-1);
422
423 pa_xfree(un);
424 pa_xfree(hn);
425
426 pa_hashmap_put(u->services, device, s);
427
428 out:
429 pa_threaded_mainloop_unlock(u->mainloop);
430
431 return s;
432 }
433
434 /* Run from Avahi mainloop context */
service_free(struct service *s)435 static void service_free(struct service *s) {
436 pa_assert(s);
437
438 if (s->entry_group) {
439 pa_log_debug("Removing entry group for %s.", s->service_name);
440 avahi_entry_group_free(s->entry_group);
441 }
442
443 pa_xfree(s->service_name);
444
445 pa_xfree(s->name);
446 pa_proplist_free(s->proplist);
447
448 pa_xfree(s);
449 }
450
451 /* Runs in PA mainloop context */
shall_ignore(pa_object *o)452 static bool shall_ignore(pa_object *o) {
453 pa_object_assert_ref(o);
454
455 if (pa_sink_isinstance(o))
456 return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
457
458 if (pa_source_isinstance(o))
459 return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
460
461 pa_assert_not_reached();
462 }
463
464 /* Runs in PA mainloop context */
device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u)465 static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
466 pa_assert(c);
467 pa_object_assert_ref(o);
468
469 if (!shall_ignore(o)) {
470 pa_threaded_mainloop_lock(u->mainloop);
471 pa_mainloop_api_once(u->api, publish_service, get_service(u, o));
472 pa_threaded_mainloop_unlock(u->mainloop);
473 }
474
475 return PA_HOOK_OK;
476 }
477
478 /* Runs in PA mainloop context */
device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u)479 static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
480 pa_assert(c);
481 pa_object_assert_ref(o);
482
483 pa_threaded_mainloop_lock(u->mainloop);
484 pa_hashmap_remove_and_free(u->services, o);
485 pa_threaded_mainloop_unlock(u->mainloop);
486
487 return PA_HOOK_OK;
488 }
489
490 static int publish_main_service(struct userdata *u);
491
492 /* Runs in Avahi mainloop context */
main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)493 static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
494 struct userdata *u = userdata;
495 pa_assert(u);
496
497 switch (state) {
498
499 case AVAHI_ENTRY_GROUP_ESTABLISHED:
500 pa_log_info("Successfully established main service.");
501 break;
502
503 case AVAHI_ENTRY_GROUP_COLLISION: {
504 char *t;
505
506 t = avahi_alternative_service_name(u->service_name);
507 pa_log_info("Name collision: renaming main service %s to %s.", u->service_name, t);
508 pa_xfree(u->service_name);
509 u->service_name = t;
510
511 publish_main_service(u);
512 break;
513 }
514
515 case AVAHI_ENTRY_GROUP_FAILURE: {
516 pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
517
518 avahi_entry_group_free(g);
519 u->main_entry_group = NULL;
520 break;
521 }
522
523 case AVAHI_ENTRY_GROUP_UNCOMMITED:
524 case AVAHI_ENTRY_GROUP_REGISTERING:
525 break;
526 }
527 }
528
529 /* Runs in Avahi mainloop context */
publish_main_service(struct userdata *u)530 static int publish_main_service(struct userdata *u) {
531 AvahiStringList *txt = NULL;
532 int r = -1;
533
534 pa_assert(u);
535
536 if (!u->main_entry_group) {
537 if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
538 pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
539 goto fail;
540 }
541 } else
542 avahi_entry_group_reset(u->main_entry_group);
543
544 txt = txt_record_server_data(u->core, txt);
545
546 if (avahi_entry_group_add_service_strlst(
547 u->main_entry_group,
548 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
549 0,
550 u->service_name,
551 SERVICE_TYPE_SERVER,
552 NULL,
553 NULL,
554 compute_port(u),
555 txt) < 0) {
556
557 pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
558 goto fail;
559 }
560
561 if (avahi_entry_group_commit(u->main_entry_group) < 0) {
562 pa_log("avahi_entry_group_commit() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
563 goto fail;
564 }
565
566 r = 0;
567
568 fail:
569 avahi_string_list_free(txt);
570
571 return r;
572 }
573
574 /* Runs in PA mainloop context */
publish_all_services(struct userdata *u)575 static int publish_all_services(struct userdata *u) {
576 pa_sink *sink;
577 pa_source *source;
578 int r = -1;
579 uint32_t idx;
580
581 pa_assert(u);
582
583 pa_log_debug("Publishing services in Zeroconf");
584
585 for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
586 if (!shall_ignore(PA_OBJECT(sink))) {
587 pa_threaded_mainloop_lock(u->mainloop);
588 pa_mainloop_api_once(u->api, publish_service, get_service(u, PA_OBJECT(sink)));
589 pa_threaded_mainloop_unlock(u->mainloop);
590 }
591
592 for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
593 if (!shall_ignore(PA_OBJECT(source))) {
594 pa_threaded_mainloop_lock(u->mainloop);
595 pa_mainloop_api_once(u->api, publish_service, get_service(u, PA_OBJECT(source)));
596 pa_threaded_mainloop_unlock(u->mainloop);
597 }
598
599 if (publish_main_service(u) < 0)
600 goto fail;
601
602 r = 0;
603
604 fail:
605 return r;
606 }
607
608 /* Runs in Avahi mainloop context */
unpublish_all_services(struct userdata *u, bool rem)609 static void unpublish_all_services(struct userdata *u, bool rem) {
610 void *state = NULL;
611 struct service *s;
612
613 pa_assert(u);
614
615 pa_log_debug("Unpublishing services in Zeroconf");
616
617 while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
618 if (s->entry_group) {
619 if (rem) {
620 pa_log_debug("Removing entry group for %s.", s->service_name);
621 avahi_entry_group_free(s->entry_group);
622 s->entry_group = NULL;
623 } else {
624 avahi_entry_group_reset(s->entry_group);
625 pa_log_debug("Resetting entry group for %s.", s->service_name);
626 }
627 }
628 }
629
630 if (u->main_entry_group) {
631 if (rem) {
632 pa_log_debug("Removing main entry group.");
633 avahi_entry_group_free(u->main_entry_group);
634 u->main_entry_group = NULL;
635 } else {
636 avahi_entry_group_reset(u->main_entry_group);
637 pa_log_debug("Resetting main entry group.");
638 }
639 }
640 }
641
642 /* Runs in PA mainloop context */
avahi_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk)643 static int avahi_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
644 struct userdata *u = (struct userdata *) data;
645
646 pa_assert(u);
647
648 if (u->shutting_down || u->module->unload_requested)
649 return 0;
650
651 switch (code) {
652 case AVAHI_MESSAGE_PUBLISH_ALL:
653 publish_all_services(u);
654 break;
655
656 case AVAHI_MESSAGE_SHUTDOWN_START:
657 pa_module_unload(u->module, true);
658 break;
659
660 default:
661 pa_assert_not_reached();
662 }
663
664 return 0;
665 }
666
667 #ifdef HAVE_DBUS
get_icon_name(pa_module*m)668 static char *get_icon_name(pa_module*m) {
669 const char *interface = HOSTNAME_DBUS_INTERFACE;
670 const char *property = HOSTNAME_DBUS_ICON_PROPERTY;
671 char *icon_name = NULL;
672 pa_dbus_connection *bus;
673 DBusError error;
674 DBusMessageIter args;
675 DBusMessage *msg = NULL;
676 DBusMessage *reply = NULL;
677 DBusConnection *conn = NULL;
678 DBusMessageIter sub;
679
680 dbus_error_init(&error);
681
682 if (!(bus = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error))) {
683 pa_log("Failed to get system bus connection: %s", error.message);
684 dbus_error_free(&error);
685 goto out;
686 }
687
688 conn = pa_dbus_connection_get(bus);
689
690 msg = dbus_message_new_method_call(HOSTNAME_DBUS_INTERFACE,
691 HOSTNAME_DBUS_PATH,
692 DBUS_INTERFACE_PROPERTIES,
693 "Get");
694 dbus_message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID);
695
696 if ((reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &error)) == NULL) {
697 pa_log("Failed to send: %s:%s", error.name, error.message);
698 dbus_error_free(&error);
699 goto out;
700 }
701
702 dbus_message_iter_init(reply, &args);
703 if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_VARIANT) {
704 pa_log("Incorrect reply type");
705 goto out;
706 }
707
708 dbus_message_iter_recurse(&args, &sub);
709
710 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
711 pa_log("Incorrect value type");
712 goto out;
713 }
714
715 dbus_message_iter_get_basic(&sub, &icon_name);
716 icon_name = pa_xstrdup(icon_name);
717
718 out:
719 if (reply)
720 dbus_message_unref(reply);
721
722 if (msg)
723 dbus_message_unref(msg);
724
725 if (bus)
726 pa_dbus_connection_unref(bus);
727
728 return icon_name;
729 }
730 #endif
731
732 /* Runs in Avahi mainloop context */
client_callback(AvahiClient *c, AvahiClientState state, void *userdata)733 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
734 struct userdata *u = userdata;
735
736 pa_assert(c);
737 pa_assert(u);
738
739 u->client = c;
740
741 switch (state) {
742 case AVAHI_CLIENT_S_RUNNING:
743 /* Collect all sinks/sources, and publish them */
744 pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_PUBLISH_ALL, u, 0, NULL, NULL);
745
746 #ifdef HAVE_DBUS
747 /* Request icon name through D-BUS */
748 u->icon_name = get_icon_name(u->module);
749 #endif
750
751 break;
752
753 case AVAHI_CLIENT_S_COLLISION:
754 pa_log_debug("Host name collision");
755 unpublish_all_services(u, false);
756 break;
757
758 case AVAHI_CLIENT_FAILURE:
759 if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
760 int error;
761
762 pa_log_debug("Avahi daemon disconnected.");
763
764 unpublish_all_services(u, true);
765 avahi_client_free(u->client);
766
767 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
768 pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
769 pa_module_unload_request(u->module, true);
770 }
771 }
772
773 break;
774
775 default: ;
776 }
777 }
778
779 /* Runs in Avahi mainloop context */
create_client(pa_mainloop_api *api PA_GCC_UNUSED, void *userdata)780 static void create_client(pa_mainloop_api *api PA_GCC_UNUSED, void *userdata) {
781 struct userdata *u = (struct userdata *) userdata;
782 int error;
783
784 /* create_client() and client_free() are called via defer events. If the
785 * two defer events are created very quickly one after another, we can't
786 * assume that the defer event that runs create_client() will be dispatched
787 * before the defer event that runs client_free() (at the time of writing,
788 * pa_mainloop actually always dispatches queued defer events in reverse
789 * creation order). For that reason we must be prepared for the case where
790 * client_free() has already been called. */
791 if (u->client_freed)
792 return;
793
794 pa_thread_mq_install(&u->thread_mq);
795
796 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
797 pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
798 goto fail;
799 }
800
801 pa_log_debug("Started Avahi threaded mainloop");
802
803 return;
804
805 fail:
806 pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_SHUTDOWN_START, u, 0, NULL, NULL);
807 }
808
pa__init(pa_module*m)809 int pa__init(pa_module*m) {
810
811 struct userdata *u;
812 pa_modargs *ma = NULL;
813 char *hn, *un;
814
815 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
816 pa_log("Failed to parse module arguments.");
817 goto fail;
818 }
819
820 m->userdata = u = pa_xnew0(struct userdata, 1);
821 u->core = m->core;
822 u->module = m;
823 u->native = pa_native_protocol_get(u->core);
824
825 u->rtpoll = pa_rtpoll_new();
826 u->mainloop = pa_threaded_mainloop_new();
827 u->api = pa_threaded_mainloop_get_api(u->mainloop);
828
829 if (pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll) < 0) {
830 pa_log("pa_thread_mq_init() failed.");
831 goto fail;
832 }
833
834 u->msg = pa_msgobject_new(avahi_msg);
835 u->msg->parent.process_msg = avahi_process_msg;
836
837 u->avahi_poll = pa_avahi_poll_new(u->api);
838
839 u->services = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) service_free);
840
841 u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
842 u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
843 u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
844 u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
845 u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
846 u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
847
848 un = pa_get_user_name_malloc();
849 hn = pa_get_host_name_malloc();
850 u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), AVAHI_LABEL_MAX-1);
851 pa_xfree(un);
852 pa_xfree(hn);
853
854 pa_threaded_mainloop_set_name(u->mainloop, "avahi-ml");
855 if (pa_threaded_mainloop_start(u->mainloop) < 0)
856 goto fail;
857
858 pa_threaded_mainloop_lock(u->mainloop);
859 pa_mainloop_api_once(u->api, create_client, u);
860 pa_threaded_mainloop_unlock(u->mainloop);
861
862 pa_modargs_free(ma);
863
864 return 0;
865
866 fail:
867 pa__done(m);
868
869 if (ma)
870 pa_modargs_free(ma);
871
872 return -1;
873 }
874
875 /* Runs in Avahi mainloop context */
client_free(pa_mainloop_api *api PA_GCC_UNUSED, void *userdata)876 static void client_free(pa_mainloop_api *api PA_GCC_UNUSED, void *userdata) {
877 struct userdata *u = (struct userdata *) userdata;
878
879 pa_hashmap_free(u->services);
880
881 if (u->main_entry_group)
882 avahi_entry_group_free(u->main_entry_group);
883
884 if (u->client)
885 avahi_client_free(u->client);
886
887 if (u->avahi_poll)
888 pa_avahi_poll_free(u->avahi_poll);
889
890 pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_SHUTDOWN_COMPLETE, u, 0, NULL, NULL);
891
892 u->client_freed = true;
893 }
894
pa__done(pa_module*m)895 void pa__done(pa_module*m) {
896 struct userdata*u;
897 pa_assert(m);
898
899 if (!(u = m->userdata))
900 return;
901
902 u->shutting_down = true;
903
904 pa_threaded_mainloop_lock(u->mainloop);
905 pa_mainloop_api_once(u->api, client_free, u);
906 pa_threaded_mainloop_unlock(u->mainloop);
907 pa_asyncmsgq_wait_for(u->thread_mq.outq, AVAHI_MESSAGE_SHUTDOWN_COMPLETE);
908
909 pa_threaded_mainloop_stop(u->mainloop);
910 pa_threaded_mainloop_free(u->mainloop);
911
912 pa_thread_mq_done(&u->thread_mq);
913 pa_rtpoll_free(u->rtpoll);
914
915 if (u->sink_new_slot)
916 pa_hook_slot_free(u->sink_new_slot);
917 if (u->source_new_slot)
918 pa_hook_slot_free(u->source_new_slot);
919 if (u->sink_changed_slot)
920 pa_hook_slot_free(u->sink_changed_slot);
921 if (u->source_changed_slot)
922 pa_hook_slot_free(u->source_changed_slot);
923 if (u->sink_unlink_slot)
924 pa_hook_slot_free(u->sink_unlink_slot);
925 if (u->source_unlink_slot)
926 pa_hook_slot_free(u->source_unlink_slot);
927
928 if (u->native)
929 pa_native_protocol_unref(u->native);
930
931 pa_xfree(u->msg);
932 pa_xfree(u->service_name);
933 pa_xfree(u->icon_name);
934 pa_xfree(u);
935 }
936