4 This file is part of avahi.
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.
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.
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
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 #include <avahi-client/client.h>
46 COMMAND_POLL_DONE = 'P'
57 typedef struct service_data service_data;
59 typedef struct oid_data {
62 sw_discovery discovery;
64 sw_result (*reply)(void);
65 service_data *service_data;
70 char *name, *regtype, *domain, *host;
72 AvahiIfIndex interface;
74 AVAHI_LLIST_FIELDS(service_data, services);
77 struct _sw_discovery {
79 AvahiSimplePoll *simple_poll;
82 oid_data oid_table[OID_MAX];
83 sw_discovery_oid oid_index;
85 int thread_fd, main_fd;
90 pthread_mutex_t mutex;
92 AVAHI_LLIST_HEAD(service_data, services);
95 #define ASSERT_SUCCESS(r) { int __ret = (r); assert(__ret == 0); }
97 #define OID_GET_INDEX(data) ((sw_discovery_oid) (((data) - ((data)->discovery->oid_table))))
99 static sw_discovery discovery_ref(sw_discovery self);
100 static void discovery_unref(sw_discovery self);
102 static const char *add_trailing_dot(const char *s, char *buf, size_t buf_len) {
109 if (s[strlen(s)-1] == '.')
112 snprintf(buf, buf_len, "%s.", s);
116 static sw_result map_error(int error) {
121 case AVAHI_ERR_NO_MEMORY:
128 static int read_command(int fd) {
134 if ((r = read(fd, &command, 1)) != 1) {
135 fprintf(stderr, __FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
142 static int write_command(int fd, char reply) {
145 if (write(fd, &reply, 1) != 1) {
146 fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
153 static int poll_func(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) {
154 sw_discovery self = userdata;
159 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
160 ret = poll(ufds, nfds, timeout);
161 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
166 static void * thread_func(void *data) {
167 sw_discovery self = data;
171 pthread_sigmask(SIG_BLOCK, &mask, NULL);
173 self->thread = pthread_self();
174 self->thread_running = 1;
179 if ((command = read_command(self->thread_fd)) < 0)
182 /* fprintf(stderr, "Command: %c\n", command); */
188 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
190 if (avahi_simple_poll_run(self->simple_poll) < 0) {
191 fprintf(stderr, __FILE__": avahi_simple_poll_run() failed.\n");
192 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
196 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
198 if (write_command(self->thread_fd, COMMAND_POLL_DONE) < 0)
212 static int oid_alloc(sw_discovery self, oid_type type) {
216 for (i = 0; i < OID_MAX; i++) {
218 while (self->oid_index >= OID_MAX)
219 self->oid_index -= OID_MAX;
221 if (self->oid_table[self->oid_index].type == OID_UNUSED) {
222 self->oid_table[self->oid_index].type = type;
223 self->oid_table[self->oid_index].discovery = self;
225 assert(OID_GET_INDEX(&self->oid_table[self->oid_index]) == self->oid_index);
227 return self->oid_index ++;
233 /* No free entry found */
235 return (sw_discovery_oid) -1;
238 static void oid_release(sw_discovery self, sw_discovery_oid oid) {
240 assert(oid < OID_MAX);
242 assert(self->oid_table[oid].type != OID_UNUSED);
244 self->oid_table[oid].type = OID_UNUSED;
245 self->oid_table[oid].discovery = NULL;
246 self->oid_table[oid].reply = NULL;
247 self->oid_table[oid].object = NULL;
248 self->oid_table[oid].extra = NULL;
249 self->oid_table[oid].service_data = NULL;
252 static oid_data* oid_get(sw_discovery self, sw_discovery_oid oid) {
258 if (self->oid_table[oid].type == OID_UNUSED)
261 return &self->oid_table[oid];
264 static service_data* service_data_new(sw_discovery self) {
269 if (!(sdata = avahi_new0(service_data, 1)))
272 AVAHI_LLIST_PREPEND(service_data, services, self->services, sdata);
278 static void service_data_free(sw_discovery self, service_data* sdata) {
282 AVAHI_LLIST_REMOVE(service_data, services, self->services, sdata);
284 avahi_free(sdata->name);
285 avahi_free(sdata->regtype);
286 avahi_free(sdata->domain);
287 avahi_free(sdata->host);
288 avahi_string_list_free(sdata->txt);
292 static void reg_client_callback(oid_data *data, AvahiClientState state);
294 static void client_callback(AvahiClient *s, AvahiClientState state, void* userdata) {
295 sw_discovery self = userdata;
296 sw_discovery_oid oid;
303 for (oid = 0; oid < OID_MAX; oid++) {
305 switch (self->oid_table[oid].type) {
307 case OID_ENTRY_GROUP:
308 reg_client_callback(&self->oid_table[oid], state);
311 case OID_DOMAIN_BROWSER:
312 case OID_SERVICE_BROWSER:
313 ((sw_discovery_browse_reply) self->oid_table[oid].reply)(self, oid, SW_DISCOVERY_BROWSE_INVALID, 0, NULL, NULL, NULL, self->oid_table[oid].extra);
316 case OID_SERVICE_RESOLVER:
322 discovery_unref(self);
325 sw_result sw_discovery_init(sw_discovery * self) {
326 int fd[2] = { -1, -1};
327 sw_result result = SW_E_UNKNOWN;
328 pthread_mutexattr_t mutex_attr;
337 if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
340 if (!(*self = avahi_new(struct _sw_discovery, 1))) {
346 (*self)->thread_fd = fd[0];
347 (*self)->main_fd = fd[1];
349 (*self)->client = NULL;
350 (*self)->simple_poll = NULL;
352 memset((*self)->oid_table, 0, sizeof((*self)->oid_table));
353 (*self)->oid_index = 0;
355 (*self)->thread_running = 0;
357 AVAHI_LLIST_HEAD_INIT(service_info, (*self)->services);
359 ASSERT_SUCCESS(pthread_mutexattr_init(&mutex_attr));
360 pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
361 ASSERT_SUCCESS(pthread_mutex_init(&(*self)->mutex, &mutex_attr));
363 if (!((*self)->simple_poll = avahi_simple_poll_new()))
366 avahi_simple_poll_set_func((*self)->simple_poll, poll_func, *self);
368 if (!((*self)->client = avahi_client_new(avahi_simple_poll_get((*self)->simple_poll), client_callback, *self, &error))) {
369 result = map_error(error);
373 /* Start simple poll */
374 if (avahi_simple_poll_prepare((*self)->simple_poll, -1) < 0)
377 /* Queue an initial POLL command for the thread */
378 if (write_command((*self)->main_fd, COMMAND_POLL) < 0)
381 if (pthread_create(&(*self)->thread, NULL, thread_func, *self) != 0)
384 (*self)->thread_running = 1;
391 sw_discovery_fina(*self);
396 static int stop_thread(sw_discovery self) {
399 if (!self->thread_running)
402 if (write_command(self->main_fd, COMMAND_QUIT) < 0)
405 avahi_simple_poll_wakeup(self->simple_poll);
407 ASSERT_SUCCESS(pthread_join(self->thread, NULL));
408 self->thread_running = 0;
412 static sw_discovery discovery_ref(sw_discovery self) {
414 assert(self->n_ref >= 1);
421 static void discovery_unref(sw_discovery self) {
423 assert(self->n_ref >= 1);
425 if (--self->n_ref > 0)
431 avahi_client_free(self->client);
433 if (self->simple_poll)
434 avahi_simple_poll_free(self->simple_poll);
436 if (self->thread_fd >= 0)
437 close(self->thread_fd);
439 if (self->main_fd >= 0)
440 close(self->main_fd);
442 ASSERT_SUCCESS(pthread_mutex_destroy(&self->mutex));
444 while (self->services)
445 service_data_free(self, self->services);
450 sw_result sw_discovery_fina(sw_discovery self) {
456 discovery_unref(self);
461 sw_result sw_discovery_run(sw_discovery self) {
466 return sw_salt_run((sw_salt) self);
469 sw_result sw_discovery_stop_run(sw_discovery self) {
474 return sw_salt_stop_run((sw_salt) self);
477 int sw_discovery_socket(sw_discovery self) {
482 return self->main_fd;
485 sw_result sw_discovery_read_socket(sw_discovery self) {
486 sw_result result = SW_E_UNKNOWN;
492 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
494 /* Cleanup notification socket */
495 if (read_command(self->main_fd) != COMMAND_POLL_DONE)
498 if (avahi_simple_poll_dispatch(self->simple_poll) < 0)
501 if (self->n_ref > 1) /* Perhaps we should die */
503 /* Dispatch events */
504 if (avahi_simple_poll_prepare(self->simple_poll, -1) < 0)
509 /* Request the poll */
510 if (write_command(self->main_fd, COMMAND_POLL) < 0)
517 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
519 discovery_unref(self);
524 sw_result sw_discovery_salt(sw_discovery self, sw_salt *salt) {
530 *salt = (sw_salt) self;
535 sw_result sw_salt_step(sw_salt self, sw_uint32 * msec) {
542 if (!((sw_discovery) self)->thread_running)
545 memset(&p, 0, sizeof(p));
546 p.fd = ((sw_discovery) self)->main_fd;
549 if ((r = poll(&p, 1, msec ? (int) *msec : -1)) < 0) {
551 /* Don't treat EINTR as error */
565 if (p.revents != POLLIN)
568 if ((result = sw_discovery_read_socket((sw_discovery) self)) != SW_OKAY)
575 sw_result sw_salt_run(sw_salt self) {
581 if ((ret = sw_salt_step(self, NULL)) != SW_OKAY)
585 sw_result sw_salt_stop_run(sw_salt self) {
588 if (stop_thread((sw_discovery) self) < 0)
594 static void reg_report_status(oid_data *data, sw_discovery_publish_status status) {
595 sw_discovery_publish_reply reply;
599 reply = (sw_discovery_publish_reply) data->reply;
601 reply(data->discovery,
607 static int reg_create_service(oid_data *data) {
609 const char *real_type;
613 real_type = avahi_get_type_from_subtype(data->service_data->regtype);
615 if ((ret = avahi_entry_group_add_service_strlst(
617 data->service_data->interface,
620 data->service_data->name,
621 real_type ? real_type : data->service_data->regtype,
622 data->service_data->domain,
623 data->service_data->host,
624 data->service_data->port,
625 data->service_data->txt)) < 0)
629 /* Create a subtype entry */
631 if (avahi_entry_group_add_service_subtype(
633 data->service_data->interface,
636 data->service_data->name,
638 data->service_data->domain,
639 data->service_data->regtype) < 0)
644 if ((ret = avahi_entry_group_commit(data->object)) < 0)
650 static void reg_client_callback(oid_data *data, AvahiClientState state) {
653 /* We've not been setup completely */
658 case AVAHI_CLIENT_DISCONNECTED:
659 reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
662 case AVAHI_CLIENT_S_RUNNING: {
665 /* Register the service */
666 if ((ret = reg_create_service(data)) < 0) {
667 reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
674 case AVAHI_CLIENT_S_COLLISION:
676 /* Remove our entry */
677 avahi_entry_group_reset(data->object);
680 case AVAHI_CLIENT_S_INVALID:
681 case AVAHI_CLIENT_S_REGISTERING:
688 static void reg_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
689 oid_data *data = userdata;
695 case AVAHI_ENTRY_GROUP_ESTABLISHED:
697 reg_report_status(data, SW_DISCOVERY_PUBLISH_STARTED);
700 case AVAHI_ENTRY_GROUP_COLLISION:
702 reg_report_status(data, SW_DISCOVERY_PUBLISH_NAME_COLLISION);
705 case AVAHI_ENTRY_GROUP_REGISTERING:
706 case AVAHI_ENTRY_GROUP_UNCOMMITED:
712 sw_result sw_discovery_publish(
714 sw_uint32 interface_index,
715 sw_const_string name,
716 sw_const_string type,
717 sw_const_string domain,
718 sw_const_string host,
720 sw_octets text_record,
721 sw_uint32 text_record_len,
722 sw_discovery_publish_reply reply,
724 sw_discovery_oid * oid) {
727 sw_result result = SW_E_UNKNOWN;
738 if ((*oid = oid_alloc(self, OID_ENTRY_GROUP)) == (sw_discovery_oid) -1)
741 if (!(sdata = service_data_new(self))) {
742 oid_release(self, *oid);
746 data = oid_get(self, *oid);
748 data->reply = (sw_result (*)(void)) reply;
750 data->service_data = sdata;
752 sdata->interface = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
753 sdata->name = avahi_strdup(name);
754 sdata->regtype = type ? avahi_normalize_name_strdup(type) : NULL;
755 sdata->domain = domain ? avahi_normalize_name_strdup(domain) : NULL;
756 sdata->host = host ? avahi_normalize_name_strdup(host) : NULL;
758 sdata->txt = text_record && text_record_len > 0 ? avahi_string_list_parse(text_record, text_record_len) : NULL;
760 /* Some OOM checking would be cool here */
762 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
764 if (!(data->object = avahi_entry_group_new(self->client, reg_entry_group_callback, data))) {
765 result = map_error(avahi_client_errno(self->client));
769 if (avahi_client_get_state(self->client) == AVAHI_CLIENT_S_RUNNING) {
772 if ((error = reg_create_service(data)) < 0) {
773 result = map_error(error);
782 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
784 if (result != SW_OKAY)
785 if (*oid != (sw_discovery_oid) -1)
786 sw_discovery_cancel(self, *oid);
791 static void domain_browser_callback(
792 AvahiDomainBrowser *b,
793 AvahiIfIndex interface,
794 AvahiProtocol protocol,
795 AvahiBrowserEvent event,
797 AvahiLookupResultFlags flags,
800 oid_data* data = userdata;
801 sw_discovery_browse_reply reply;
802 static char domain_fixed[AVAHI_DOMAIN_NAME_MAX];
807 reply = (sw_discovery_browse_reply) data->reply;
809 domain = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
812 case AVAHI_BROWSER_NEW:
813 reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_DOMAIN, interface, NULL, NULL, domain, data->extra);
816 case AVAHI_BROWSER_REMOVE:
817 reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_DOMAIN, interface, NULL, NULL, domain, data->extra);
820 case AVAHI_BROWSER_FAILURE:
821 reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, NULL, NULL, domain, data->extra);
824 case AVAHI_BROWSER_CACHE_EXHAUSTED:
825 case AVAHI_BROWSER_ALL_FOR_NOW:
830 sw_result sw_discovery_browse_domains(
832 sw_uint32 interface_index,
833 sw_discovery_browse_reply reply,
835 sw_discovery_oid * oid) {
838 AvahiIfIndex ifindex;
839 sw_result result = SW_E_UNKNOWN;
847 if ((*oid = oid_alloc(self, OID_DOMAIN_BROWSER)) == (sw_discovery_oid) -1)
850 data = oid_get(self, *oid);
852 data->reply = (sw_result (*)(void)) reply;
855 ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
857 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
859 if (!(data->object = avahi_domain_browser_new(self->client, ifindex, AVAHI_PROTO_INET, NULL, AVAHI_DOMAIN_BROWSER_BROWSE, 0, domain_browser_callback, data))) {
860 result = map_error(avahi_client_errno(self->client));
868 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
870 if (result != SW_OKAY)
871 if (*oid != (sw_discovery_oid) -1)
872 sw_discovery_cancel(self, *oid);
877 static void service_resolver_callback(
878 AvahiServiceResolver *r,
879 AvahiIfIndex interface,
880 AvahiProtocol protocol,
881 AvahiResolverEvent event,
885 const char *host_name,
886 const AvahiAddress *a,
888 AvahiStringList *txt,
889 AvahiLookupResultFlags flags,
892 oid_data* data = userdata;
893 sw_discovery_resolve_reply reply;
898 reply = (sw_discovery_resolve_reply) data->reply;
901 case AVAHI_RESOLVER_FOUND: {
903 char host_name_fixed[AVAHI_DOMAIN_NAME_MAX];
906 sw_ipv4_address addr;
908 sw_ipv4_address_init_from_saddr(&addr, a->data.ipv4.address);
910 host_name = add_trailing_dot(host_name, host_name_fixed, sizeof(host_name_fixed));
912 if ((p = avahi_new0(uint8_t, (l = avahi_string_list_serialize(txt, NULL, 0))+1)))
913 avahi_string_list_serialize(txt, p, l);
915 reply(data->discovery, OID_GET_INDEX(data), interface, name, type, domain, addr, port, p, l, data->extra);
921 case AVAHI_RESOLVER_FAILURE:
923 /* Apparently there is no way in HOWL to inform about failed resolvings ... */
925 avahi_warn("A service failed to resolve in the HOWL compatiblity layer of Avahi which is used by '%s'. "
926 "Since the HOWL API doesn't offer any means to inform the application about this, we have to ignore the failure. "
927 "Please fix your application to use the native API of Avahi!",
934 sw_result sw_discovery_resolve(
936 sw_uint32 interface_index,
937 sw_const_string name,
938 sw_const_string type,
939 sw_const_string domain,
940 sw_discovery_resolve_reply reply,
942 sw_discovery_oid * oid) {
945 AvahiIfIndex ifindex;
946 sw_result result = SW_E_UNKNOWN;
956 if ((*oid = oid_alloc(self, OID_SERVICE_RESOLVER)) == (sw_discovery_oid) -1)
959 data = oid_get(self, *oid);
961 data->reply = (sw_result (*)(void)) reply;
964 ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
966 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
968 if (!(data->object = avahi_service_resolver_new(self->client, ifindex, AVAHI_PROTO_INET, name, type, domain, AVAHI_PROTO_INET, 0, service_resolver_callback, data))) {
969 result = map_error(avahi_client_errno(self->client));
977 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
979 if (result != SW_OKAY)
980 if (*oid != (sw_discovery_oid) -1)
981 sw_discovery_cancel(self, *oid);
986 static void service_browser_callback(
987 AvahiServiceBrowser *b,
988 AvahiIfIndex interface,
989 AvahiProtocol protocol,
990 AvahiBrowserEvent event,
994 AvahiLookupResultFlags flags,
997 oid_data* data = userdata;
998 char type_fixed[AVAHI_DOMAIN_NAME_MAX], domain_fixed[AVAHI_DOMAIN_NAME_MAX];
999 sw_discovery_browse_reply reply;
1004 reply = (sw_discovery_browse_reply) data->reply;
1006 type = add_trailing_dot(type, type_fixed, sizeof(type_fixed));
1007 domain = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
1010 case AVAHI_BROWSER_NEW:
1011 reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_SERVICE, interface, name, type, domain, data->extra);
1014 case AVAHI_BROWSER_REMOVE:
1015 reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_SERVICE, interface, name, type, domain, data->extra);
1018 case AVAHI_BROWSER_FAILURE:
1019 reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, name, type, domain, data->extra);
1022 case AVAHI_BROWSER_CACHE_EXHAUSTED:
1023 case AVAHI_BROWSER_ALL_FOR_NOW:
1028 sw_result sw_discovery_browse(
1030 sw_uint32 interface_index,
1031 sw_const_string type,
1032 sw_const_string domain,
1033 sw_discovery_browse_reply reply,
1035 sw_discovery_oid * oid) {
1038 AvahiIfIndex ifindex;
1039 sw_result result = SW_E_UNKNOWN;
1048 if ((*oid = oid_alloc(self, OID_SERVICE_BROWSER)) == (sw_discovery_oid) -1)
1049 return SW_E_UNKNOWN;
1051 data = oid_get(self, *oid);
1053 data->reply = (sw_result (*)(void)) reply;
1054 data->extra = extra;
1056 ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
1058 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
1060 if (!(data->object = avahi_service_browser_new(self->client, ifindex, AVAHI_PROTO_INET, type, domain, 0, service_browser_callback, data))) {
1061 result = map_error(avahi_client_errno(self->client));
1069 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
1071 if (result != SW_OKAY)
1072 if (*oid != (sw_discovery_oid) -1)
1073 sw_discovery_cancel(self, *oid);
1078 sw_result sw_discovery_cancel(sw_discovery self, sw_discovery_oid oid) {
1084 if (!(data = oid_get(self, oid)))
1085 return SW_E_UNKNOWN;
1088 switch (data->type) {
1089 case OID_SERVICE_BROWSER:
1090 avahi_service_browser_free(data->object);
1093 case OID_SERVICE_RESOLVER:
1094 avahi_service_resolver_free(data->object);
1097 case OID_DOMAIN_BROWSER:
1098 avahi_domain_browser_free(data->object);
1101 case OID_ENTRY_GROUP:
1102 avahi_entry_group_free(data->object);
1110 if (data->service_data) {
1111 assert(data->type == OID_ENTRY_GROUP);
1112 service_data_free(self, data->service_data);
1115 oid_release(self, oid);
1120 sw_result sw_discovery_init_with_flags(
1121 sw_discovery * self,
1122 sw_discovery_init_flags flags) {
1128 if (flags != SW_DISCOVERY_USE_SHARED_SERVICE)
1129 return SW_E_NO_IMPL;
1131 return sw_discovery_init(self);