/* $Id$ */ /*** This file is part of avahi. avahi is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. avahi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with avahi; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "howl.h" #include "warn.h" #define OID_MAX 50 enum { COMMAND_POLL = 'p', COMMAND_QUIT = 'q', COMMAND_POLL_DONE = 'P', COMMAND_POLL_FAILED = 'F' }; typedef enum { OID_UNUSED = 0, OID_SERVICE_BROWSER, OID_SERVICE_RESOLVER, OID_DOMAIN_BROWSER, OID_ENTRY_GROUP } oid_type; typedef struct service_data service_data; typedef struct oid_data { oid_type type; sw_opaque extra; sw_discovery discovery; void *object; sw_result (*reply)(void); service_data *service_data; } oid_data; struct service_data { char *name, *regtype, *domain, *host; uint16_t port; AvahiIfIndex interface; AvahiStringList *txt; AVAHI_LLIST_FIELDS(service_data, services); }; struct _sw_discovery { int n_ref; AvahiSimplePoll *simple_poll; AvahiClient *client; oid_data oid_table[OID_MAX]; sw_discovery_oid oid_index; int thread_fd, main_fd; pthread_t thread; int thread_running; pthread_mutex_t mutex, salt_mutex; AVAHI_LLIST_HEAD(service_data, services); }; #define ASSERT_SUCCESS(r) { int __ret = (r); assert(__ret == 0); } #define OID_GET_INDEX(data) ((sw_discovery_oid) (((data) - ((data)->discovery->oid_table)))) static sw_discovery discovery_ref(sw_discovery self); static void discovery_unref(sw_discovery self); static const char *add_trailing_dot(const char *s, char *buf, size_t buf_len) { if (!s) return NULL; if (*s == 0) return s; if (s[strlen(s)-1] == '.') return s; snprintf(buf, buf_len, "%s.", s); return buf; } static sw_result map_error(int error) { switch (error) { case AVAHI_OK: return SW_OKAY; case AVAHI_ERR_NO_MEMORY: return SW_E_MEM; } return SW_E_UNKNOWN; } static int read_command(int fd) { ssize_t r; char command; assert(fd >= 0); if ((r = read(fd, &command, 1)) != 1) { fprintf(stderr, __FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF"); return -1; } return command; } static int write_command(int fd, char reply) { assert(fd >= 0); if (write(fd, &reply, 1) != 1) { fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno)); return -1; } return 0; } static int poll_func(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) { sw_discovery self = userdata; int ret; assert(self); ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); ret = poll(ufds, nfds, timeout); ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); return ret; } static void * thread_func(void *data) { sw_discovery self = data; sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_BLOCK, &mask, NULL); self->thread = pthread_self(); self->thread_running = 1; for (;;) { char command; if ((command = read_command(self->thread_fd)) < 0) break; /* fprintf(stderr, "Command: %c\n", command); */ switch (command) { case COMMAND_POLL: { int ret; ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); for (;;) { errno = 0; if ((ret = avahi_simple_poll_run(self->simple_poll)) < 0) { if (errno == EINTR) continue; fprintf(stderr, __FILE__": avahi_simple_poll_run() failed: %s\n", strerror(errno)); } break; } ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); if (write_command(self->thread_fd, ret < 0 ? COMMAND_POLL_FAILED : COMMAND_POLL_DONE) < 0) break; break; } case COMMAND_QUIT: return NULL; } } return NULL; } static int oid_alloc(sw_discovery self, oid_type type) { sw_discovery_oid i; assert(self); for (i = 0; i < OID_MAX; i++) { while (self->oid_index >= OID_MAX) self->oid_index -= OID_MAX; if (self->oid_table[self->oid_index].type == OID_UNUSED) { self->oid_table[self->oid_index].type = type; self->oid_table[self->oid_index].discovery = self; assert(OID_GET_INDEX(&self->oid_table[self->oid_index]) == self->oid_index); return self->oid_index ++; } self->oid_index ++; } /* No free entry found */ return (sw_discovery_oid) -1; } static void oid_release(sw_discovery self, sw_discovery_oid oid) { assert(self); assert(oid < OID_MAX); assert(self->oid_table[oid].type != OID_UNUSED); self->oid_table[oid].type = OID_UNUSED; self->oid_table[oid].discovery = NULL; self->oid_table[oid].reply = NULL; self->oid_table[oid].object = NULL; self->oid_table[oid].extra = NULL; self->oid_table[oid].service_data = NULL; } static oid_data* oid_get(sw_discovery self, sw_discovery_oid oid) { assert(self); if (oid >= OID_MAX) return NULL; if (self->oid_table[oid].type == OID_UNUSED) return NULL; return &self->oid_table[oid]; } static service_data* service_data_new(sw_discovery self) { service_data *sdata; assert(self); if (!(sdata = avahi_new0(service_data, 1))) return NULL; AVAHI_LLIST_PREPEND(service_data, services, self->services, sdata); return sdata; } static void service_data_free(sw_discovery self, service_data* sdata) { assert(self); assert(sdata); AVAHI_LLIST_REMOVE(service_data, services, self->services, sdata); avahi_free(sdata->name); avahi_free(sdata->regtype); avahi_free(sdata->domain); avahi_free(sdata->host); avahi_string_list_free(sdata->txt); avahi_free(sdata); } static void reg_client_callback(oid_data *data, AvahiClientState state); static void client_callback(AvahiClient *s, AvahiClientState state, void* userdata) { sw_discovery self = userdata; sw_discovery_oid oid; assert(s); assert(self); discovery_ref(self); for (oid = 0; oid < OID_MAX; oid++) { switch (self->oid_table[oid].type) { case OID_ENTRY_GROUP: reg_client_callback(&self->oid_table[oid], state); break; case OID_DOMAIN_BROWSER: case OID_SERVICE_BROWSER: ((sw_discovery_browse_reply) self->oid_table[oid].reply)(self, oid, SW_DISCOVERY_BROWSE_INVALID, 0, NULL, NULL, NULL, self->oid_table[oid].extra); break; case OID_SERVICE_RESOLVER: case OID_UNUSED: ; } } discovery_unref(self); } sw_result sw_discovery_init(sw_discovery * self) { int fd[2] = { -1, -1}; sw_result result = SW_E_UNKNOWN; pthread_mutexattr_t mutex_attr; int error; assert(self); AVAHI_WARN_LINKAGE; *self = NULL; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) goto fail; if (!(*self = avahi_new(struct _sw_discovery, 1))) { result = SW_E_MEM; goto fail; } (*self)->n_ref = 1; (*self)->thread_fd = fd[0]; (*self)->main_fd = fd[1]; (*self)->client = NULL; (*self)->simple_poll = NULL; memset((*self)->oid_table, 0, sizeof((*self)->oid_table)); (*self)->oid_index = 0; (*self)->thread_running = 0; AVAHI_LLIST_HEAD_INIT(service_info, (*self)->services); ASSERT_SUCCESS(pthread_mutexattr_init(&mutex_attr)); pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE); ASSERT_SUCCESS(pthread_mutex_init(&(*self)->mutex, &mutex_attr)); ASSERT_SUCCESS(pthread_mutex_init(&(*self)->salt_mutex, &mutex_attr)); if (!((*self)->simple_poll = avahi_simple_poll_new())) goto fail; avahi_simple_poll_set_func((*self)->simple_poll, poll_func, *self); if (!((*self)->client = avahi_client_new(avahi_simple_poll_get((*self)->simple_poll), 0, client_callback, *self, &error))) { result = map_error(error); goto fail; } /* Start simple poll */ if (avahi_simple_poll_prepare((*self)->simple_poll, -1) < 0) goto fail; /* Queue an initial POLL command for the thread */ if (write_command((*self)->main_fd, COMMAND_POLL) < 0) goto fail; if (pthread_create(&(*self)->thread, NULL, thread_func, *self) != 0) goto fail; (*self)->thread_running = 1; return SW_OKAY; fail: if (*self) sw_discovery_fina(*self); return result; } static int stop_thread(sw_discovery self) { assert(self); if (!self->thread_running) return 0; if (write_command(self->main_fd, COMMAND_QUIT) < 0) return -1; avahi_simple_poll_wakeup(self->simple_poll); ASSERT_SUCCESS(pthread_join(self->thread, NULL)); self->thread_running = 0; return 0; } static sw_discovery discovery_ref(sw_discovery self) { assert(self); assert(self->n_ref >= 1); self->n_ref++; return self; } static void discovery_unref(sw_discovery self) { assert(self); assert(self->n_ref >= 1); if (--self->n_ref > 0) return; stop_thread(self); if (self->client) avahi_client_free(self->client); if (self->simple_poll) avahi_simple_poll_free(self->simple_poll); if (self->thread_fd >= 0) close(self->thread_fd); if (self->main_fd >= 0) close(self->main_fd); ASSERT_SUCCESS(pthread_mutex_destroy(&self->mutex)); ASSERT_SUCCESS(pthread_mutex_destroy(&self->salt_mutex)); while (self->services) service_data_free(self, self->services); avahi_free(self); } sw_result sw_discovery_fina(sw_discovery self) { assert(self); AVAHI_WARN_LINKAGE; stop_thread(self); discovery_unref(self); return SW_OKAY; } sw_result sw_discovery_run(sw_discovery self) { assert(self); AVAHI_WARN_LINKAGE; return sw_salt_run((sw_salt) self); } sw_result sw_discovery_stop_run(sw_discovery self) { assert(self); AVAHI_WARN_LINKAGE; return sw_salt_stop_run((sw_salt) self); } int sw_discovery_socket(sw_discovery self) { assert(self); AVAHI_WARN_LINKAGE; return self->main_fd; } sw_result sw_discovery_read_socket(sw_discovery self) { sw_result result = SW_E_UNKNOWN; assert(self); discovery_ref(self); ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); /* Cleanup notification socket */ if (read_command(self->main_fd) != COMMAND_POLL_DONE) goto finish; if (avahi_simple_poll_dispatch(self->simple_poll) < 0) goto finish; if (self->n_ref > 1) /* Perhaps we should die */ /* Dispatch events */ if (avahi_simple_poll_prepare(self->simple_poll, -1) < 0) goto finish; if (self->n_ref > 1) /* Request the poll */ if (write_command(self->main_fd, COMMAND_POLL) < 0) goto finish; result = SW_OKAY; finish: ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); discovery_unref(self); return result; } sw_result sw_discovery_salt(sw_discovery self, sw_salt *salt) { assert(self); assert(salt); AVAHI_WARN_LINKAGE; *salt = (sw_salt) self; return SW_OKAY; } sw_result sw_salt_step(sw_salt self, sw_uint32 * msec) { struct pollfd p; int r; sw_result result; AVAHI_WARN_LINKAGE; if (!((sw_discovery) self)->thread_running) return SW_E_UNKNOWN; memset(&p, 0, sizeof(p)); p.fd = ((sw_discovery) self)->main_fd; p.events = POLLIN; if ((r = poll(&p, 1, msec ? (int) *msec : -1)) < 0) { /* Don't treat EINTR as error */ if (errno == EINTR) return SW_OKAY; return SW_E_UNKNOWN; } else if (r == 0) { /* Timeoout */ return SW_OKAY; } else { /* Success */ if (p.revents != POLLIN) return SW_E_UNKNOWN; if ((result = sw_discovery_read_socket((sw_discovery) self)) != SW_OKAY) return result; } return SW_OKAY; } sw_result sw_salt_run(sw_salt self) { sw_result ret; AVAHI_WARN_LINKAGE; assert(self); for (;;) if ((ret = sw_salt_step(self, NULL)) != SW_OKAY) return ret; } sw_result sw_salt_stop_run(sw_salt self) { AVAHI_WARN_LINKAGE; assert(self); if (stop_thread((sw_discovery) self) < 0) return SW_E_UNKNOWN; return SW_OKAY; } sw_result sw_salt_lock(sw_salt self) { AVAHI_WARN_LINKAGE; assert(self); ASSERT_SUCCESS(pthread_mutex_lock(&((sw_discovery) self)->salt_mutex)); return SW_OKAY; } sw_result sw_salt_unlock(sw_salt self) { assert(self); AVAHI_WARN_LINKAGE; ASSERT_SUCCESS(pthread_mutex_unlock(&((sw_discovery) self)->salt_mutex)); return SW_OKAY; } static void reg_report_status(oid_data *data, sw_discovery_publish_status status) { sw_discovery_publish_reply reply; assert(data); reply = (sw_discovery_publish_reply) data->reply; reply(data->discovery, OID_GET_INDEX(data), status, data->extra); } static int reg_create_service(oid_data *data) { int ret; const char *real_type; assert(data); real_type = avahi_get_type_from_subtype(data->service_data->regtype); if ((ret = avahi_entry_group_add_service_strlst( data->object, data->service_data->interface, AVAHI_PROTO_INET, 0, data->service_data->name, real_type ? real_type : data->service_data->regtype, data->service_data->domain, data->service_data->host, data->service_data->port, data->service_data->txt)) < 0) return ret; if (real_type) { /* Create a subtype entry */ if (avahi_entry_group_add_service_subtype( data->object, data->service_data->interface, AVAHI_PROTO_INET, 0, data->service_data->name, real_type, data->service_data->domain, data->service_data->regtype) < 0) return ret; } if ((ret = avahi_entry_group_commit(data->object)) < 0) return ret; return 0; } static void reg_client_callback(oid_data *data, AvahiClientState state) { assert(data); /* We've not been setup completely */ if (!data->object) return; switch (state) { case AVAHI_CLIENT_FAILURE: reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID); break; case AVAHI_CLIENT_S_RUNNING: { int ret; /* Register the service */ if ((ret = reg_create_service(data)) < 0) { reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID); return; } break; } case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_REGISTERING: /* Remove our entry */ avahi_entry_group_reset(data->object); break; case AVAHI_CLIENT_CONNECTING: /* Ignore */ break; } } static void reg_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { oid_data *data = userdata; assert(g); assert(data); switch (state) { case AVAHI_ENTRY_GROUP_ESTABLISHED: reg_report_status(data, SW_DISCOVERY_PUBLISH_STARTED); break; case AVAHI_ENTRY_GROUP_COLLISION: reg_report_status(data, SW_DISCOVERY_PUBLISH_NAME_COLLISION); break; case AVAHI_ENTRY_GROUP_REGISTERING: case AVAHI_ENTRY_GROUP_UNCOMMITED: /* Ignore */ break; case AVAHI_ENTRY_GROUP_FAILURE: reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID); break; } } sw_result sw_discovery_publish( sw_discovery self, sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_const_string host, sw_port port, sw_octets text_record, sw_uint32 text_record_len, sw_discovery_publish_reply reply, sw_opaque extra, sw_discovery_oid * oid) { oid_data *data; sw_result result = SW_E_UNKNOWN; service_data *sdata; AvahiStringList *txt = NULL; assert(self); assert(name); assert(type); assert(reply); assert(oid); AVAHI_WARN_LINKAGE; if (text_record && text_record_len > 0) if (avahi_string_list_parse(text_record, text_record_len, &txt) < 0) return SW_E_UNKNOWN; if ((*oid = oid_alloc(self, OID_ENTRY_GROUP)) == (sw_discovery_oid) -1) { avahi_string_list_free(txt); return SW_E_UNKNOWN; } if (!(sdata = service_data_new(self))) { avahi_string_list_free(txt); oid_release(self, *oid); return SW_E_MEM; } data = oid_get(self, *oid); assert(data); data->reply = (sw_result (*)(void)) reply; data->extra = extra; data->service_data = sdata; sdata->interface = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index; sdata->name = avahi_strdup(name); sdata->regtype = type ? avahi_normalize_name_strdup(type) : NULL; sdata->domain = domain ? avahi_normalize_name_strdup(domain) : NULL; sdata->host = host ? avahi_normalize_name_strdup(host) : NULL; sdata->port = port; sdata->txt = txt; /* Some OOM checking would be cool here */ ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); if (!(data->object = avahi_entry_group_new(self->client, reg_entry_group_callback, data))) { result = map_error(avahi_client_errno(self->client)); goto finish; } if (avahi_client_get_state(self->client) == AVAHI_CLIENT_S_RUNNING) { int error; if ((error = reg_create_service(data)) < 0) { result = map_error(error); goto finish; } } result = SW_OKAY; finish: ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); if (result != SW_OKAY) if (*oid != (sw_discovery_oid) -1) sw_discovery_cancel(self, *oid); return result; } static void domain_browser_callback( AvahiDomainBrowser *b, AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiBrowserEvent event, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata) { oid_data* data = userdata; sw_discovery_browse_reply reply; static char domain_fixed[AVAHI_DOMAIN_NAME_MAX]; assert(b); assert(data); reply = (sw_discovery_browse_reply) data->reply; domain = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed)); switch (event) { case AVAHI_BROWSER_NEW: reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_DOMAIN, interface, NULL, NULL, domain, data->extra); break; case AVAHI_BROWSER_REMOVE: reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_DOMAIN, interface, NULL, NULL, domain, data->extra); break; case AVAHI_BROWSER_FAILURE: reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, NULL, NULL, domain, data->extra); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: case AVAHI_BROWSER_ALL_FOR_NOW: break; } } sw_result sw_discovery_browse_domains( sw_discovery self, sw_uint32 interface_index, sw_discovery_browse_reply reply, sw_opaque extra, sw_discovery_oid * oid) { oid_data *data; AvahiIfIndex ifindex; sw_result result = SW_E_UNKNOWN; assert(self); assert(reply); assert(oid); AVAHI_WARN_LINKAGE; if ((*oid = oid_alloc(self, OID_DOMAIN_BROWSER)) == (sw_discovery_oid) -1) return SW_E_UNKNOWN; data = oid_get(self, *oid); assert(data); data->reply = (sw_result (*)(void)) reply; data->extra = extra; ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index; ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); if (!(data->object = avahi_domain_browser_new(self->client, ifindex, AVAHI_PROTO_INET, NULL, AVAHI_DOMAIN_BROWSER_BROWSE, 0, domain_browser_callback, data))) { result = map_error(avahi_client_errno(self->client)); goto finish; } result = SW_OKAY; finish: ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); if (result != SW_OKAY) if (*oid != (sw_discovery_oid) -1) sw_discovery_cancel(self, *oid); return result; } static void service_resolver_callback( AvahiServiceResolver *r, AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata) { oid_data* data = userdata; sw_discovery_resolve_reply reply; assert(r); assert(data); reply = (sw_discovery_resolve_reply) data->reply; switch (event) { case AVAHI_RESOLVER_FOUND: { char host_name_fixed[AVAHI_DOMAIN_NAME_MAX]; uint8_t *p = NULL; size_t l = 0; sw_ipv4_address addr; sw_ipv4_address_init_from_saddr(&addr, a->data.ipv4.address); host_name = add_trailing_dot(host_name, host_name_fixed, sizeof(host_name_fixed)); if ((p = avahi_new0(uint8_t, (l = avahi_string_list_serialize(txt, NULL, 0))+1))) avahi_string_list_serialize(txt, p, l); reply(data->discovery, OID_GET_INDEX(data), interface, name, type, domain, addr, port, p, l, data->extra); avahi_free(p); break; } case AVAHI_RESOLVER_FAILURE: /* Apparently there is no way in HOWL to inform about failed resolvings ... */ avahi_warn("A service failed to resolve in the HOWL compatiblity layer of Avahi which is used by '%s'. " "Since the HOWL API doesn't offer any means to inform the application about this, we have to ignore the failure. " "Please fix your application to use the native API of Avahi!", avahi_exe_name()); break; } } sw_result sw_discovery_resolve( sw_discovery self, sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_discovery_resolve_reply reply, sw_opaque extra, sw_discovery_oid * oid) { oid_data *data; AvahiIfIndex ifindex; sw_result result = SW_E_UNKNOWN; assert(self); assert(name); assert(type); assert(reply); assert(oid); AVAHI_WARN_LINKAGE; if ((*oid = oid_alloc(self, OID_SERVICE_RESOLVER)) == (sw_discovery_oid) -1) return SW_E_UNKNOWN; data = oid_get(self, *oid); assert(data); data->reply = (sw_result (*)(void)) reply; data->extra = extra; ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index; ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); if (!(data->object = avahi_service_resolver_new(self->client, ifindex, AVAHI_PROTO_INET, name, type, domain, AVAHI_PROTO_INET, 0, service_resolver_callback, data))) { result = map_error(avahi_client_errno(self->client)); goto finish; } result = SW_OKAY; finish: ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); if (result != SW_OKAY) if (*oid != (sw_discovery_oid) -1) sw_discovery_cancel(self, *oid); return result; } static void service_browser_callback( AvahiServiceBrowser *b, AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata) { oid_data* data = userdata; char type_fixed[AVAHI_DOMAIN_NAME_MAX], domain_fixed[AVAHI_DOMAIN_NAME_MAX]; sw_discovery_browse_reply reply; assert(b); assert(data); reply = (sw_discovery_browse_reply) data->reply; type = add_trailing_dot(type, type_fixed, sizeof(type_fixed)); domain = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed)); switch (event) { case AVAHI_BROWSER_NEW: reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_SERVICE, interface, name, type, domain, data->extra); break; case AVAHI_BROWSER_REMOVE: reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_SERVICE, interface, name, type, domain, data->extra); break; case AVAHI_BROWSER_FAILURE: reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, name, type, domain, data->extra); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: case AVAHI_BROWSER_ALL_FOR_NOW: break; } } sw_result sw_discovery_browse( sw_discovery self, sw_uint32 interface_index, sw_const_string type, sw_const_string domain, sw_discovery_browse_reply reply, sw_opaque extra, sw_discovery_oid * oid) { oid_data *data; AvahiIfIndex ifindex; sw_result result = SW_E_UNKNOWN; assert(self); assert(type); assert(reply); assert(oid); AVAHI_WARN_LINKAGE; if ((*oid = oid_alloc(self, OID_SERVICE_BROWSER)) == (sw_discovery_oid) -1) return SW_E_UNKNOWN; data = oid_get(self, *oid); assert(data); data->reply = (sw_result (*)(void)) reply; data->extra = extra; ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index; ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); if (!(data->object = avahi_service_browser_new(self->client, ifindex, AVAHI_PROTO_INET, type, domain, 0, service_browser_callback, data))) { result = map_error(avahi_client_errno(self->client)); goto finish; } result = SW_OKAY; finish: ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); if (result != SW_OKAY) if (*oid != (sw_discovery_oid) -1) sw_discovery_cancel(self, *oid); return result; } sw_result sw_discovery_cancel(sw_discovery self, sw_discovery_oid oid) { oid_data *data; assert(self); AVAHI_WARN_LINKAGE; if (!(data = oid_get(self, oid))) return SW_E_UNKNOWN; if (data->object) { switch (data->type) { case OID_SERVICE_BROWSER: avahi_service_browser_free(data->object); break; case OID_SERVICE_RESOLVER: avahi_service_resolver_free(data->object); break; case OID_DOMAIN_BROWSER: avahi_domain_browser_free(data->object); break; case OID_ENTRY_GROUP: avahi_entry_group_free(data->object); break; case OID_UNUSED: ; } } if (data->service_data) { assert(data->type == OID_ENTRY_GROUP); service_data_free(self, data->service_data); } oid_release(self, oid); return SW_OKAY; } sw_result sw_discovery_init_with_flags( sw_discovery * self, sw_discovery_init_flags flags) { assert(self); AVAHI_WARN_LINKAGE; if (flags != SW_DISCOVERY_USE_SHARED_SERVICE) return SW_E_NO_IMPL; return sw_discovery_init(self); }