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