]> git.meshlink.io Git - catta/blob - avahi-compat-howl/compat.c
12a82fa31fc2c45d9080f941f9386dc8b976c558
[catta] / avahi-compat-howl / compat.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5
6   avahi is free software; you can redistribute it and/or modify it
7   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   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <assert.h>
27
28 #include <pthread.h>
29
30 #include <avahi-common/strlst.h>
31 #include <avahi-common/malloc.h>
32 #include <avahi-common/domain.h>
33 #include <avahi-common/simple-watch.h>
34 #include <avahi-common/error.h>
35 #include <avahi-common/llist.h>
36
37 #include <avahi-client/client.h>
38 #include <avahi-client/publish.h>
39 #include <avahi-client/lookup.h>
40
41 #include "howl.h"
42 #include "warn.h"
43
44 #define OID_MAX 50
45
46 enum {
47     COMMAND_POLL = 'p',
48     COMMAND_QUIT = 'q',
49     COMMAND_POLL_DONE = 'P',
50     COMMAND_POLL_FAILED = 'F'
51 };
52
53 typedef enum {
54     OID_UNUSED = 0,
55     OID_SERVICE_BROWSER,
56     OID_SERVICE_RESOLVER,
57     OID_DOMAIN_BROWSER,
58     OID_ENTRY_GROUP
59 } oid_type;
60
61 typedef struct service_data service_data;
62
63 typedef struct oid_data {
64     oid_type type;
65     sw_opaque extra;
66     sw_discovery discovery;
67     void *object;
68     sw_result (*reply)(void);
69     service_data *service_data;
70 } oid_data;
71
72
73 struct service_data {
74     char *name, *regtype, *domain, *host;
75     uint16_t port;
76     AvahiIfIndex interface;
77     AvahiStringList *txt;
78     AVAHI_LLIST_FIELDS(service_data, services);
79 };
80
81 struct _sw_discovery {
82     int n_ref;
83     AvahiSimplePoll *simple_poll;
84     AvahiClient *client;
85
86     oid_data oid_table[OID_MAX];
87     sw_discovery_oid oid_index;
88
89     int thread_fd, main_fd;
90
91     pthread_t thread;
92     int thread_running;
93
94     pthread_mutex_t mutex, salt_mutex;
95
96     AVAHI_LLIST_HEAD(service_data, services);
97 };
98
99 #define ASSERT_SUCCESS(r) { int __ret = (r); assert(__ret == 0); }
100
101 #define OID_GET_INDEX(data) ((sw_discovery_oid) (((data) - ((data)->discovery->oid_table))))
102
103 static sw_discovery discovery_ref(sw_discovery self);
104 static void discovery_unref(sw_discovery self);
105
106 static const char *add_trailing_dot(const char *s, char *buf, size_t buf_len) {
107     if (!s)
108         return NULL;
109
110     if (*s == 0)
111         return s;
112
113     if (s[strlen(s)-1] == '.')
114         return s;
115
116     snprintf(buf, buf_len, "%s.", s);
117     return buf;
118 }
119
120 static sw_result map_error(int error) {
121     switch (error) {
122         case AVAHI_OK:
123             return SW_OKAY;
124
125         case AVAHI_ERR_NO_MEMORY:
126             return SW_E_MEM;
127     }
128
129     return SW_E_UNKNOWN;
130 }
131
132 static int read_command(int fd) {
133     ssize_t r;
134     char command;
135
136     assert(fd >= 0);
137
138     if ((r = read(fd, &command, 1)) != 1) {
139         fprintf(stderr, __FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
140         return -1;
141     }
142
143     return command;
144 }
145
146 static int write_command(int fd, char reply) {
147     assert(fd >= 0);
148
149     if (write(fd, &reply, 1) != 1) {
150         fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
151         return -1;
152     }
153
154     return 0;
155 }
156
157 static int poll_func(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) {
158     sw_discovery self = userdata;
159     int ret;
160
161     assert(self);
162
163     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
164     ret = poll(ufds, nfds, timeout);
165     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
166
167     return ret;
168 }
169
170 static void * thread_func(void *data) {
171     sw_discovery self = data;
172     sigset_t mask;
173
174     sigfillset(&mask);
175     pthread_sigmask(SIG_BLOCK, &mask, NULL);
176
177     self->thread = pthread_self();
178     self->thread_running = 1;
179
180     for (;;) {
181         char command;
182
183         if ((command = read_command(self->thread_fd)) < 0)
184             break;
185
186 /*         fprintf(stderr, "Command: %c\n", command); */
187
188         switch (command) {
189
190             case COMMAND_POLL: {
191                 int ret;
192
193                 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
194
195                 for (;;) {
196                     errno = 0;
197
198                     if ((ret = avahi_simple_poll_run(self->simple_poll)) < 0) {
199
200                         if (errno == EINTR)
201                             continue;
202
203                         fprintf(stderr, __FILE__": avahi_simple_poll_run() failed: %s\n", strerror(errno));
204                     }
205
206                     break;
207                 }
208
209                 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
210
211                 if (write_command(self->thread_fd, ret < 0 ? COMMAND_POLL_FAILED : COMMAND_POLL_DONE) < 0)
212                     break;
213
214                 break;
215             }
216
217             case COMMAND_QUIT:
218                 return NULL;
219         }
220
221     }
222
223     return NULL;
224 }
225
226 static int oid_alloc(sw_discovery self, oid_type type) {
227     sw_discovery_oid i;
228     assert(self);
229
230     for (i = 0; i < OID_MAX; i++) {
231
232         while (self->oid_index >= OID_MAX)
233             self->oid_index -= OID_MAX;
234
235         if (self->oid_table[self->oid_index].type == OID_UNUSED) {
236             self->oid_table[self->oid_index].type = type;
237             self->oid_table[self->oid_index].discovery = self;
238
239             assert(OID_GET_INDEX(&self->oid_table[self->oid_index]) == self->oid_index);
240
241             return self->oid_index ++;
242         }
243
244         self->oid_index ++;
245     }
246
247     /* No free entry found */
248
249     return (sw_discovery_oid) -1;
250 }
251
252 static void oid_release(sw_discovery self, sw_discovery_oid oid) {
253     assert(self);
254     assert(oid < OID_MAX);
255
256     assert(self->oid_table[oid].type != OID_UNUSED);
257
258     self->oid_table[oid].type = OID_UNUSED;
259     self->oid_table[oid].discovery = NULL;
260     self->oid_table[oid].reply = NULL;
261     self->oid_table[oid].object = NULL;
262     self->oid_table[oid].extra = NULL;
263     self->oid_table[oid].service_data = NULL;
264 }
265
266 static oid_data* oid_get(sw_discovery self, sw_discovery_oid oid) {
267     assert(self);
268
269     if (oid >= OID_MAX)
270         return NULL;
271
272     if (self->oid_table[oid].type == OID_UNUSED)
273         return NULL;
274
275     return &self->oid_table[oid];
276 }
277
278 static service_data* service_data_new(sw_discovery self) {
279     service_data *sdata;
280
281     assert(self);
282
283     if (!(sdata = avahi_new0(service_data, 1)))
284         return NULL;
285
286     AVAHI_LLIST_PREPEND(service_data, services, self->services, sdata);
287
288     return sdata;
289
290 }
291
292 static void service_data_free(sw_discovery self, service_data* sdata) {
293     assert(self);
294     assert(sdata);
295
296     AVAHI_LLIST_REMOVE(service_data, services, self->services, sdata);
297
298     avahi_free(sdata->name);
299     avahi_free(sdata->regtype);
300     avahi_free(sdata->domain);
301     avahi_free(sdata->host);
302     avahi_string_list_free(sdata->txt);
303     avahi_free(sdata);
304 }
305
306 static void reg_client_callback(oid_data *data, AvahiClientState state);
307
308 static void client_callback(AvahiClient *s, AvahiClientState state, void* userdata) {
309     sw_discovery self = userdata;
310     sw_discovery_oid oid;
311
312     assert(s);
313     assert(self);
314
315     discovery_ref(self);
316
317     for (oid = 0; oid < OID_MAX; oid++) {
318
319         switch (self->oid_table[oid].type) {
320
321             case OID_ENTRY_GROUP:
322                 reg_client_callback(&self->oid_table[oid], state);
323                 break;
324
325             case OID_DOMAIN_BROWSER:
326             case OID_SERVICE_BROWSER:
327                 ((sw_discovery_browse_reply) self->oid_table[oid].reply)(self, oid, SW_DISCOVERY_BROWSE_INVALID, 0, NULL, NULL, NULL, self->oid_table[oid].extra);
328                 break;
329
330             case OID_SERVICE_RESOLVER:
331             case OID_UNUSED:
332                 ;
333         }
334     }
335
336     discovery_unref(self);
337 }
338
339 sw_result sw_discovery_init(sw_discovery * self) {
340     int fd[2] = { -1, -1};
341     sw_result result = SW_E_UNKNOWN;
342     pthread_mutexattr_t mutex_attr;
343     int error;
344
345     assert(self);
346
347     AVAHI_WARN_LINKAGE;
348
349     *self = NULL;
350
351     if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
352         goto fail;
353
354     if (!(*self = avahi_new(struct _sw_discovery, 1))) {
355         result = SW_E_MEM;
356         goto fail;
357     }
358
359     (*self)->n_ref = 1;
360     (*self)->thread_fd = fd[0];
361     (*self)->main_fd = fd[1];
362
363     (*self)->client = NULL;
364     (*self)->simple_poll = NULL;
365
366     memset((*self)->oid_table, 0, sizeof((*self)->oid_table));
367     (*self)->oid_index = 0;
368
369     (*self)->thread_running = 0;
370
371     AVAHI_LLIST_HEAD_INIT(service_info, (*self)->services);
372
373     ASSERT_SUCCESS(pthread_mutexattr_init(&mutex_attr));
374     pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
375     ASSERT_SUCCESS(pthread_mutex_init(&(*self)->mutex, &mutex_attr));
376     ASSERT_SUCCESS(pthread_mutex_init(&(*self)->salt_mutex, &mutex_attr));
377
378     if (!((*self)->simple_poll = avahi_simple_poll_new()))
379         goto fail;
380
381     avahi_simple_poll_set_func((*self)->simple_poll, poll_func, *self);
382
383     if (!((*self)->client = avahi_client_new(avahi_simple_poll_get((*self)->simple_poll), 0, client_callback, *self, &error))) {
384         result = map_error(error);
385         goto fail;
386     }
387
388     /* Start simple poll */
389     if (avahi_simple_poll_prepare((*self)->simple_poll, -1) < 0)
390         goto fail;
391
392     /* Queue an initial POLL command for the thread */
393     if (write_command((*self)->main_fd, COMMAND_POLL) < 0)
394         goto fail;
395
396     if (pthread_create(&(*self)->thread, NULL, thread_func, *self) != 0)
397         goto fail;
398
399     (*self)->thread_running = 1;
400
401     return SW_OKAY;
402
403 fail:
404
405     if (*self)
406         sw_discovery_fina(*self);
407
408     return result;
409 }
410
411 static int stop_thread(sw_discovery self) {
412     assert(self);
413
414     if (!self->thread_running)
415         return 0;
416
417     if (write_command(self->main_fd, COMMAND_QUIT) < 0)
418         return -1;
419
420     avahi_simple_poll_wakeup(self->simple_poll);
421
422     ASSERT_SUCCESS(pthread_join(self->thread, NULL));
423     self->thread_running = 0;
424     return 0;
425 }
426
427 static sw_discovery discovery_ref(sw_discovery self) {
428     assert(self);
429     assert(self->n_ref >= 1);
430
431     self->n_ref++;
432
433     return self;
434 }
435
436 static void discovery_unref(sw_discovery self) {
437     assert(self);
438     assert(self->n_ref >= 1);
439
440     if (--self->n_ref > 0)
441         return;
442
443     stop_thread(self);
444
445     if (self->client)
446         avahi_client_free(self->client);
447
448     if (self->simple_poll)
449         avahi_simple_poll_free(self->simple_poll);
450
451     if (self->thread_fd >= 0)
452         close(self->thread_fd);
453
454     if (self->main_fd >= 0)
455         close(self->main_fd);
456
457     ASSERT_SUCCESS(pthread_mutex_destroy(&self->mutex));
458     ASSERT_SUCCESS(pthread_mutex_destroy(&self->salt_mutex));
459
460     while (self->services)
461         service_data_free(self, self->services);
462
463     avahi_free(self);
464 }
465
466 sw_result sw_discovery_fina(sw_discovery self) {
467     assert(self);
468
469     AVAHI_WARN_LINKAGE;
470
471     stop_thread(self);
472     discovery_unref(self);
473
474     return SW_OKAY;
475 }
476
477 sw_result sw_discovery_run(sw_discovery self) {
478     assert(self);
479
480     AVAHI_WARN_LINKAGE;
481
482     return sw_salt_run((sw_salt) self);
483 }
484
485 sw_result sw_discovery_stop_run(sw_discovery self) {
486     assert(self);
487
488     AVAHI_WARN_LINKAGE;
489
490     return sw_salt_stop_run((sw_salt) self);
491 }
492
493 int sw_discovery_socket(sw_discovery self) {
494     assert(self);
495
496     AVAHI_WARN_LINKAGE;
497
498     return self->main_fd;
499 }
500
501 sw_result sw_discovery_read_socket(sw_discovery self) {
502     sw_result result = SW_E_UNKNOWN;
503
504     assert(self);
505
506     discovery_ref(self);
507
508     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
509
510     /* Cleanup notification socket */
511     if (read_command(self->main_fd) != COMMAND_POLL_DONE)
512         goto finish;
513
514     if (avahi_simple_poll_dispatch(self->simple_poll) < 0)
515         goto finish;
516
517     if (self->n_ref > 1) /* Perhaps we should die */
518
519         /* Dispatch events */
520         if (avahi_simple_poll_prepare(self->simple_poll, -1) < 0)
521             goto finish;
522
523     if (self->n_ref > 1)
524
525         /* Request the poll */
526         if (write_command(self->main_fd, COMMAND_POLL) < 0)
527             goto finish;
528
529     result = SW_OKAY;
530
531 finish:
532
533     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
534
535     discovery_unref(self);
536
537     return result;
538 }
539
540 sw_result sw_discovery_salt(sw_discovery self, sw_salt *salt) {
541     assert(self);
542     assert(salt);
543
544     AVAHI_WARN_LINKAGE;
545
546     *salt = (sw_salt) self;
547
548     return SW_OKAY;
549 }
550
551 sw_result sw_salt_step(sw_salt self, sw_uint32 * msec) {
552     struct pollfd p;
553     int r;
554     sw_result result;
555
556     AVAHI_WARN_LINKAGE;
557
558     if (!((sw_discovery) self)->thread_running)
559         return SW_E_UNKNOWN;
560
561     memset(&p, 0, sizeof(p));
562     p.fd = ((sw_discovery) self)->main_fd;
563     p.events = POLLIN;
564
565     if ((r = poll(&p, 1, msec ? (int) *msec : -1)) < 0) {
566
567         /* Don't treat EINTR as error */
568         if (errno == EINTR)
569             return SW_OKAY;
570
571         return SW_E_UNKNOWN;
572
573     } else if (r == 0) {
574
575         /* Timeoout */
576         return SW_OKAY;
577
578     } else {
579         /* Success */
580
581         if (p.revents != POLLIN)
582             return SW_E_UNKNOWN;
583
584         if ((result = sw_discovery_read_socket((sw_discovery) self)) != SW_OKAY)
585             return result;
586     }
587
588     return SW_OKAY;
589 }
590
591 sw_result sw_salt_run(sw_salt self) {
592     sw_result ret;
593
594     AVAHI_WARN_LINKAGE;
595
596     assert(self);
597
598     for (;;)
599         if ((ret = sw_salt_step(self, NULL)) != SW_OKAY)
600             return ret;
601 }
602
603 sw_result sw_salt_stop_run(sw_salt self) {
604     AVAHI_WARN_LINKAGE;
605
606     assert(self);
607
608     if (stop_thread((sw_discovery) self) < 0)
609         return SW_E_UNKNOWN;
610
611     return SW_OKAY;
612 }
613
614 sw_result sw_salt_lock(sw_salt self) {
615     AVAHI_WARN_LINKAGE;
616
617     assert(self);
618     ASSERT_SUCCESS(pthread_mutex_lock(&((sw_discovery) self)->salt_mutex));
619
620     return SW_OKAY;
621 }
622
623 sw_result sw_salt_unlock(sw_salt self) {
624     assert(self);
625
626     AVAHI_WARN_LINKAGE;
627
628     ASSERT_SUCCESS(pthread_mutex_unlock(&((sw_discovery) self)->salt_mutex));
629
630     return SW_OKAY;
631 }
632
633 static void reg_report_status(oid_data *data, sw_discovery_publish_status status) {
634     sw_discovery_publish_reply reply;
635
636     assert(data);
637
638     reply = (sw_discovery_publish_reply) data->reply;
639
640     reply(data->discovery,
641           OID_GET_INDEX(data),
642           status,
643           data->extra);
644 }
645
646 static int reg_create_service(oid_data *data) {
647     int ret;
648     const char *real_type;
649
650     assert(data);
651
652     real_type = avahi_get_type_from_subtype(data->service_data->regtype);
653
654     if ((ret = avahi_entry_group_add_service_strlst(
655              data->object,
656              data->service_data->interface,
657              AVAHI_PROTO_INET,
658              0,
659              data->service_data->name,
660              real_type ? real_type : data->service_data->regtype,
661              data->service_data->domain,
662              data->service_data->host,
663              data->service_data->port,
664              data->service_data->txt)) < 0)
665         return ret;
666
667     if (real_type) {
668         /* Create a subtype entry */
669
670         if (avahi_entry_group_add_service_subtype(
671                 data->object,
672                 data->service_data->interface,
673                 AVAHI_PROTO_INET,
674                 0,
675                 data->service_data->name,
676                 real_type,
677                 data->service_data->domain,
678                 data->service_data->regtype) < 0)
679             return ret;
680
681     }
682
683     if ((ret = avahi_entry_group_commit(data->object)) < 0)
684         return ret;
685
686     return 0;
687 }
688
689 static void reg_client_callback(oid_data *data, AvahiClientState state) {
690     assert(data);
691
692     /* We've not been setup completely */
693     if (!data->object)
694         return;
695
696     switch (state) {
697         case AVAHI_CLIENT_FAILURE:
698             reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
699             break;
700
701         case AVAHI_CLIENT_S_RUNNING: {
702             int ret;
703
704             /* Register the service */
705             if ((ret = reg_create_service(data)) < 0) {
706                 reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
707                 return;
708             }
709
710             break;
711         }
712
713         case AVAHI_CLIENT_S_COLLISION:
714         case AVAHI_CLIENT_S_REGISTERING:
715
716             /* Remove our entry */
717             avahi_entry_group_reset(data->object);
718             break;
719
720         case AVAHI_CLIENT_CONNECTING:
721             /* Ignore */
722             break;
723     }
724
725 }
726
727 static void reg_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
728     oid_data *data = userdata;
729
730     assert(g);
731     assert(data);
732
733     switch (state) {
734         case AVAHI_ENTRY_GROUP_ESTABLISHED:
735
736             reg_report_status(data, SW_DISCOVERY_PUBLISH_STARTED);
737             break;
738
739         case AVAHI_ENTRY_GROUP_COLLISION:
740
741             reg_report_status(data, SW_DISCOVERY_PUBLISH_NAME_COLLISION);
742             break;
743
744         case AVAHI_ENTRY_GROUP_REGISTERING:
745         case AVAHI_ENTRY_GROUP_UNCOMMITED:
746             /* Ignore */
747             break;
748
749         case AVAHI_ENTRY_GROUP_FAILURE:
750             reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
751             break;
752
753     }
754 }
755
756 sw_result sw_discovery_publish(
757     sw_discovery self,
758     sw_uint32 interface_index,
759     sw_const_string name,
760     sw_const_string type,
761     sw_const_string domain,
762     sw_const_string host,
763     sw_port port,
764     sw_octets text_record,
765     sw_uint32 text_record_len,
766     sw_discovery_publish_reply reply,
767     sw_opaque extra,
768     sw_discovery_oid * oid) {
769
770     oid_data *data;
771     sw_result result = SW_E_UNKNOWN;
772     service_data *sdata;
773     AvahiStringList *txt = NULL;
774
775     assert(self);
776     assert(name);
777     assert(type);
778     assert(reply);
779     assert(oid);
780
781     AVAHI_WARN_LINKAGE;
782
783     if (text_record && text_record_len > 0)
784         if (avahi_string_list_parse(text_record, text_record_len, &txt) < 0)
785             return SW_E_UNKNOWN;
786
787     if ((*oid = oid_alloc(self, OID_ENTRY_GROUP)) == (sw_discovery_oid) -1) {
788         avahi_string_list_free(txt);
789         return SW_E_UNKNOWN;
790     }
791
792     if (!(sdata = service_data_new(self))) {
793         avahi_string_list_free(txt);
794         oid_release(self, *oid);
795         return SW_E_MEM;
796     }
797
798     data = oid_get(self, *oid);
799     assert(data);
800     data->reply = (sw_result (*)(void)) reply;
801     data->extra = extra;
802     data->service_data = sdata;
803
804     sdata->interface = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
805     sdata->name = avahi_strdup(name);
806     sdata->regtype = type ? avahi_normalize_name_strdup(type) : NULL;
807     sdata->domain = domain ? avahi_normalize_name_strdup(domain) : NULL;
808     sdata->host = host ? avahi_normalize_name_strdup(host) : NULL;
809     sdata->port = port;
810     sdata->txt = txt;
811
812     /* Some OOM checking would be cool here */
813
814     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
815
816     if (!(data->object = avahi_entry_group_new(self->client, reg_entry_group_callback, data))) {
817         result = map_error(avahi_client_errno(self->client));
818         goto finish;
819     }
820
821     if (avahi_client_get_state(self->client) == AVAHI_CLIENT_S_RUNNING) {
822         int error;
823
824         if ((error = reg_create_service(data)) < 0) {
825             result = map_error(error);
826             goto finish;
827         }
828     }
829
830     result = SW_OKAY;
831
832 finish:
833
834     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
835
836     if (result != SW_OKAY)
837         if (*oid != (sw_discovery_oid) -1)
838             sw_discovery_cancel(self, *oid);
839
840     return result;
841 }
842
843 static void domain_browser_callback(
844     AvahiDomainBrowser *b,
845     AvahiIfIndex interface,
846     AVAHI_GCC_UNUSED AvahiProtocol protocol,
847     AvahiBrowserEvent event,
848     const char *domain,
849     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
850     void *userdata) {
851
852     oid_data* data = userdata;
853     sw_discovery_browse_reply reply;
854     static char domain_fixed[AVAHI_DOMAIN_NAME_MAX];
855
856     assert(b);
857     assert(data);
858
859     reply = (sw_discovery_browse_reply) data->reply;
860
861     domain  = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
862
863     switch (event) {
864         case AVAHI_BROWSER_NEW:
865             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_DOMAIN, interface, NULL, NULL, domain, data->extra);
866             break;
867
868         case AVAHI_BROWSER_REMOVE:
869             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_DOMAIN, interface, NULL, NULL, domain, data->extra);
870             break;
871
872         case AVAHI_BROWSER_FAILURE:
873             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, NULL, NULL, domain, data->extra);
874             break;
875
876         case AVAHI_BROWSER_CACHE_EXHAUSTED:
877         case AVAHI_BROWSER_ALL_FOR_NOW:
878             break;
879     }
880 }
881
882 sw_result sw_discovery_browse_domains(
883     sw_discovery self,
884     sw_uint32 interface_index,
885     sw_discovery_browse_reply reply,
886     sw_opaque extra,
887     sw_discovery_oid * oid) {
888
889     oid_data *data;
890     AvahiIfIndex ifindex;
891     sw_result result = SW_E_UNKNOWN;
892
893     assert(self);
894     assert(reply);
895     assert(oid);
896
897     AVAHI_WARN_LINKAGE;
898
899     if ((*oid = oid_alloc(self, OID_DOMAIN_BROWSER)) == (sw_discovery_oid) -1)
900         return SW_E_UNKNOWN;
901
902     data = oid_get(self, *oid);
903     assert(data);
904     data->reply = (sw_result (*)(void)) reply;
905     data->extra = extra;
906
907     ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
908
909     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
910
911     if (!(data->object = avahi_domain_browser_new(self->client, ifindex, AVAHI_PROTO_INET, NULL, AVAHI_DOMAIN_BROWSER_BROWSE, 0, domain_browser_callback, data))) {
912         result = map_error(avahi_client_errno(self->client));
913         goto finish;
914     }
915
916     result = SW_OKAY;
917
918 finish:
919
920     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
921
922     if (result != SW_OKAY)
923         if (*oid != (sw_discovery_oid) -1)
924             sw_discovery_cancel(self, *oid);
925
926     return result;
927 }
928
929 static void service_resolver_callback(
930     AvahiServiceResolver *r,
931     AvahiIfIndex interface,
932     AVAHI_GCC_UNUSED AvahiProtocol protocol,
933     AvahiResolverEvent event,
934     const char *name,
935     const char *type,
936     const char *domain,
937     const char *host_name,
938     const AvahiAddress *a,
939     uint16_t port,
940     AvahiStringList *txt,
941     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
942     void *userdata) {
943
944     oid_data* data = userdata;
945     sw_discovery_resolve_reply reply;
946
947     assert(r);
948     assert(data);
949
950     reply = (sw_discovery_resolve_reply) data->reply;
951
952     switch (event) {
953         case AVAHI_RESOLVER_FOUND: {
954
955             char host_name_fixed[AVAHI_DOMAIN_NAME_MAX];
956             uint8_t *p = NULL;
957             size_t l = 0;
958             sw_ipv4_address addr;
959
960             sw_ipv4_address_init_from_saddr(&addr, a->data.ipv4.address);
961
962             host_name = add_trailing_dot(host_name, host_name_fixed, sizeof(host_name_fixed));
963
964             if ((p = avahi_new0(uint8_t, (l = avahi_string_list_serialize(txt, NULL, 0))+1)))
965                 avahi_string_list_serialize(txt, p, l);
966
967             reply(data->discovery, OID_GET_INDEX(data), interface, name, type, domain, addr, port, p, l, data->extra);
968
969             avahi_free(p);
970             break;
971         }
972
973         case AVAHI_RESOLVER_FAILURE:
974
975             /* Apparently there is no way in HOWL to inform about failed resolvings ... */
976
977             avahi_warn("A service failed to resolve in the HOWL compatiblity layer of Avahi which is used by '%s'. "
978                        "Since the HOWL API doesn't offer any means to inform the application about this, we have to ignore the failure. "
979                        "Please fix your application to use the native API of Avahi!",
980                        avahi_exe_name());
981
982             break;
983     }
984 }
985
986 sw_result sw_discovery_resolve(
987     sw_discovery self,
988     sw_uint32 interface_index,
989     sw_const_string name,
990     sw_const_string type,
991     sw_const_string domain,
992     sw_discovery_resolve_reply reply,
993     sw_opaque extra,
994     sw_discovery_oid * oid) {
995
996     oid_data *data;
997     AvahiIfIndex ifindex;
998     sw_result result = SW_E_UNKNOWN;
999
1000     assert(self);
1001     assert(name);
1002     assert(type);
1003     assert(reply);
1004     assert(oid);
1005
1006     AVAHI_WARN_LINKAGE;
1007
1008     if ((*oid = oid_alloc(self, OID_SERVICE_RESOLVER)) == (sw_discovery_oid) -1)
1009         return SW_E_UNKNOWN;
1010
1011     data = oid_get(self, *oid);
1012     assert(data);
1013     data->reply = (sw_result (*)(void)) reply;
1014     data->extra = extra;
1015
1016     ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
1017
1018     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
1019
1020     if (!(data->object = avahi_service_resolver_new(self->client, ifindex, AVAHI_PROTO_INET, name, type, domain, AVAHI_PROTO_INET, 0, service_resolver_callback, data))) {
1021         result = map_error(avahi_client_errno(self->client));
1022         goto finish;
1023     }
1024
1025     result = SW_OKAY;
1026
1027 finish:
1028
1029     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
1030
1031     if (result != SW_OKAY)
1032         if (*oid != (sw_discovery_oid) -1)
1033             sw_discovery_cancel(self, *oid);
1034
1035     return result;
1036 }
1037
1038 static void service_browser_callback(
1039     AvahiServiceBrowser *b,
1040     AvahiIfIndex interface,
1041     AVAHI_GCC_UNUSED AvahiProtocol protocol,
1042     AvahiBrowserEvent event,
1043     const char *name,
1044     const char *type,
1045     const char *domain,
1046     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
1047     void *userdata) {
1048
1049     oid_data* data = userdata;
1050     char type_fixed[AVAHI_DOMAIN_NAME_MAX], domain_fixed[AVAHI_DOMAIN_NAME_MAX];
1051     sw_discovery_browse_reply reply;
1052
1053     assert(b);
1054     assert(data);
1055
1056     reply = (sw_discovery_browse_reply) data->reply;
1057
1058     type = add_trailing_dot(type, type_fixed, sizeof(type_fixed));
1059     domain = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
1060
1061     switch (event) {
1062         case AVAHI_BROWSER_NEW:
1063             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_SERVICE, interface, name, type, domain, data->extra);
1064             break;
1065
1066         case AVAHI_BROWSER_REMOVE:
1067             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_SERVICE, interface, name, type, domain, data->extra);
1068             break;
1069
1070         case AVAHI_BROWSER_FAILURE:
1071             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, name, type, domain, data->extra);
1072             break;
1073
1074         case AVAHI_BROWSER_CACHE_EXHAUSTED:
1075         case AVAHI_BROWSER_ALL_FOR_NOW:
1076             break;
1077     }
1078 }
1079
1080 sw_result sw_discovery_browse(
1081     sw_discovery self,
1082     sw_uint32 interface_index,
1083     sw_const_string type,
1084     sw_const_string domain,
1085     sw_discovery_browse_reply reply,
1086     sw_opaque extra,
1087     sw_discovery_oid * oid) {
1088
1089     oid_data *data;
1090     AvahiIfIndex ifindex;
1091     sw_result result = SW_E_UNKNOWN;
1092
1093     assert(self);
1094     assert(type);
1095     assert(reply);
1096     assert(oid);
1097
1098     AVAHI_WARN_LINKAGE;
1099
1100     if ((*oid = oid_alloc(self, OID_SERVICE_BROWSER)) == (sw_discovery_oid) -1)
1101         return SW_E_UNKNOWN;
1102
1103     data = oid_get(self, *oid);
1104     assert(data);
1105     data->reply = (sw_result (*)(void)) reply;
1106     data->extra = extra;
1107
1108     ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
1109
1110     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
1111
1112     if (!(data->object = avahi_service_browser_new(self->client, ifindex, AVAHI_PROTO_INET, type, domain, 0, service_browser_callback, data))) {
1113         result = map_error(avahi_client_errno(self->client));
1114         goto finish;
1115     }
1116
1117     result = SW_OKAY;
1118
1119 finish:
1120
1121     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
1122
1123     if (result != SW_OKAY)
1124         if (*oid != (sw_discovery_oid) -1)
1125             sw_discovery_cancel(self, *oid);
1126
1127     return result;
1128 }
1129
1130 sw_result sw_discovery_cancel(sw_discovery self, sw_discovery_oid oid) {
1131     oid_data *data;
1132     assert(self);
1133
1134     AVAHI_WARN_LINKAGE;
1135
1136     if (!(data = oid_get(self, oid)))
1137         return SW_E_UNKNOWN;
1138
1139     if (data->object) {
1140         switch (data->type) {
1141             case OID_SERVICE_BROWSER:
1142                 avahi_service_browser_free(data->object);
1143                 break;
1144
1145             case OID_SERVICE_RESOLVER:
1146                 avahi_service_resolver_free(data->object);
1147                 break;
1148
1149             case OID_DOMAIN_BROWSER:
1150                 avahi_domain_browser_free(data->object);
1151                 break;
1152
1153             case OID_ENTRY_GROUP:
1154                 avahi_entry_group_free(data->object);
1155                 break;
1156
1157             case OID_UNUSED:
1158             ;
1159         }
1160     }
1161
1162     if (data->service_data) {
1163         assert(data->type == OID_ENTRY_GROUP);
1164         service_data_free(self, data->service_data);
1165     }
1166
1167     oid_release(self, oid);
1168
1169     return SW_OKAY;
1170 }
1171
1172 sw_result sw_discovery_init_with_flags(
1173     sw_discovery * self,
1174     sw_discovery_init_flags flags) {
1175
1176     assert(self);
1177
1178     AVAHI_WARN_LINKAGE;
1179
1180     if (flags != SW_DISCOVERY_USE_SHARED_SERVICE)
1181         return SW_E_NO_IMPL;
1182
1183     return sw_discovery_init(self);
1184 }