/* $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 "howl.h" #include "warn.h" #define OID_MAX 50 enum { COMMAND_POLL = 'p', COMMAND_QUIT = 'q', COMMAND_POLL_DONE = 'P' }; typedef enum { OID_UNUSED = 0, OID_SERVICE_BROWSER, OID_SERVICE_RESOLVER, OID_DOMAIN_BROWSER, OID_ENTRY_GROUP } oid_type; typedef struct oid_data { oid_type type; sw_opaque extra; sw_discovery discovery; void *object; sw_result (*reply)(void); } oid_data; 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; }; #define ASSERT_SUCCESS(r) { int __ret = (r); assert(__ret == 0); } #define OID_GET_INDEX(data) ((sw_discovery_oid) (((data) - ((data)->discovery->oid_table)))) 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: ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex)); if (avahi_simple_poll_run(self->simple_poll) < 0) { fprintf(stderr, __FILE__": avahi_simple_poll_run() failed.\n"); ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); break; } ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex)); if (write_command(self->thread_fd, 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; } 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]; } 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; ASSERT_SUCCESS(pthread_mutexattr_init(&mutex_attr)); pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE); ASSERT_SUCCESS(pthread_mutex_init(&(*self)->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), NULL, *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 discover_ref(sw_discovery self) { assert(self); assert(self->n_ref >= 1); self->n_ref++; return self; } static void discover_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)); avahi_free(self); } sw_result sw_discovery_fina(sw_discovery self) { assert(self); AVAHI_WARN_LINKAGE; stop_thread(self); discover_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); discover_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)); discover_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; for (;;) if ((ret = sw_salt_step(self, NULL)) != SW_OKAY) return ret; } sw_result sw_salt_stop_run(sw_salt self) { AVAHI_WARN_LINKAGE; if (stop_thread((sw_discovery) self) < 0) return SW_E_UNKNOWN; return SW_OKAY; } 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) { AVAHI_WARN_UNSUPPORTED; return SW_E_NO_IMPL; } 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) { AVAHI_WARN_UNSUPPORTED; return SW_E_NO_IMPL; } static void service_resolver_callback( AvahiServiceResolver *r, AvahiIfIndex interface, 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, 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) oid_release(self, *oid); return result; } static void service_browser_callback( AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, 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) oid_release(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; 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_UNUSED: ; } oid_release(self, oid); return SW_OKAY; }