diff options
Diffstat (limited to 'src/zeroconf-avahi.c')
-rw-r--r-- | src/zeroconf-avahi.c | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c new file mode 100644 index 000000000..688e2ab24 --- /dev/null +++ b/src/zeroconf-avahi.c @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2003-2008 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "zeroconf-internal.h" +#include "listen.h" +#include "utils.h" +#include "ioops.h" + +#include <glib.h> + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "avahi" + +static struct ioOps zeroConfIo; + +static char *avahiName; +static int avahiRunning; +static AvahiPoll avahiPoll; +static AvahiClient *avahiClient; +static AvahiEntryGroup *avahiGroup; + +static int avahiFdset(fd_set * rfds, fd_set * wfds, fd_set * efds); +static int avahiFdconsume(int fdCount, fd_set * rfds, fd_set * wfds, + fd_set * efds); + +struct AvahiWatch { + struct AvahiWatch *prev; + struct AvahiWatch *next; + int fd; + AvahiWatchEvent requestedEvent; + AvahiWatchEvent observedEvent; + AvahiWatchCallback callback; + void *userdata; +}; + +struct AvahiTimeout { + struct AvahiTimeout *prev; + struct AvahiTimeout *next; + struct timeval expiry; + int enabled; + AvahiTimeoutCallback callback; + void *userdata; +}; + +static AvahiWatch *avahiWatchList; +static AvahiTimeout *avahiTimeoutList; + +static AvahiWatch *avahiWatchNew(G_GNUC_UNUSED const AvahiPoll * api, int fd, + AvahiWatchEvent event, + AvahiWatchCallback callback, void *userdata) +{ + struct AvahiWatch *newWatch = xmalloc(sizeof(struct AvahiWatch)); + + newWatch->fd = fd; + newWatch->requestedEvent = event; + newWatch->observedEvent = 0; + newWatch->callback = callback; + newWatch->userdata = userdata; + + /* Insert at front of list */ + newWatch->next = avahiWatchList; + avahiWatchList = newWatch; + newWatch->prev = NULL; + if (newWatch->next) + newWatch->next->prev = newWatch; + + return newWatch; +} + +static void avahiWatchUpdate(AvahiWatch * w, AvahiWatchEvent event) +{ + assert(w != NULL); + w->requestedEvent = event; +} + +static AvahiWatchEvent avahiWatchGetEvents(AvahiWatch * w) +{ + assert(w != NULL); + return w->observedEvent; +} + +static void avahiWatchFree(AvahiWatch * w) +{ + assert(w != NULL); + + if (avahiWatchList == w) + avahiWatchList = w->next; + else if (w->prev != NULL) + w->prev->next = w->next; + + free(w); +} + +static void avahiCheckExpiry(AvahiTimeout * t) +{ + assert(t != NULL); + if (t->enabled) { + struct timeval now; + gettimeofday(&now, NULL); + if (timercmp(&now, &(t->expiry), >)) { + t->enabled = 0; + t->callback(t, t->userdata); + } + } +} + +static void avahiTimeoutUpdate(AvahiTimeout * t, const struct timeval *tv) +{ + assert(t != NULL); + if (tv) { + t->enabled = 1; + t->expiry.tv_sec = tv->tv_sec; + t->expiry.tv_usec = tv->tv_usec; + } else { + t->enabled = 0; + } +} + +static void avahiTimeoutFree(AvahiTimeout * t) +{ + assert(t != NULL); + + if (avahiTimeoutList == t) + avahiTimeoutList = t->next; + else if (t->prev != NULL) + t->prev->next = t->next; + + free(t); +} + +static AvahiTimeout *avahiTimeoutNew(G_GNUC_UNUSED const AvahiPoll * api, + const struct timeval *tv, + AvahiTimeoutCallback callback, + void *userdata) +{ + struct AvahiTimeout *newTimeout = xmalloc(sizeof(struct AvahiTimeout)); + + newTimeout->callback = callback; + newTimeout->userdata = userdata; + + avahiTimeoutUpdate(newTimeout, tv); + + /* Insert at front of list */ + newTimeout->next = avahiTimeoutList; + avahiTimeoutList = newTimeout; + newTimeout->prev = NULL; + if (newTimeout->next) + newTimeout->next->prev = newTimeout; + + return newTimeout; +} + +static void avahiRegisterService(AvahiClient * c); + +/* Callback when the EntryGroup changes state */ +static void avahiGroupCallback(AvahiEntryGroup * g, + AvahiEntryGroupState state, + G_GNUC_UNUSED void *userdata) +{ + char *n; + assert(g); + + g_debug("Service group changed to state %d", state); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + /* The entry group has been established successfully */ + g_message("Service '%s' successfully established.", + avahiName); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: + /* A service name collision happened. Let's pick a new name */ + n = avahi_alternative_service_name(avahiName); + avahi_free(avahiName); + avahiName = n; + + g_message("Service name collision, renaming service to '%s'", + avahiName); + + /* And recreate the services */ + avahiRegisterService(avahi_entry_group_get_client(g)); + break; + + case AVAHI_ENTRY_GROUP_FAILURE: + g_warning("Entry group failure: %s", + avahi_strerror(avahi_client_errno + (avahi_entry_group_get_client(g)))); + /* Some kind of failure happened while we were registering our services */ + avahiRunning = 0; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + g_debug("Service group is UNCOMMITED"); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + g_debug("Service group is REGISTERING"); + } +} + +/* Registers a new service with avahi */ +static void avahiRegisterService(AvahiClient * c) +{ + int ret; + assert(c); + g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName); + + /* If this is the first time we're called, + * let's create a new entry group */ + if (!avahiGroup) { + avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); + if (!avahiGroup) { + g_warning("Failed to create avahi EntryGroup: %s", + avahi_strerror(avahi_client_errno(c))); + goto fail; + } + } + + /* Add the service */ + /* TODO: This currently binds to ALL interfaces. + * We could maybe add a service per actual bound interface, + * if that's better. */ + ret = avahi_entry_group_add_service(avahiGroup, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, avahiName, SERVICE_TYPE, NULL, + NULL, boundPort, NULL); + if (ret < 0) { + g_warning("Failed to add service %s: %s", SERVICE_TYPE, + avahi_strerror(ret)); + goto fail; + } + + /* Tell the server to register the service group */ + ret = avahi_entry_group_commit(avahiGroup); + if (ret < 0) { + g_warning("Failed to commit service group: %s", + avahi_strerror(ret)); + goto fail; + } + return; + +fail: + avahiRunning = 0; +} + +/* Callback when avahi changes state */ +static void avahiClientCallback(AvahiClient * c, AvahiClientState state, + G_GNUC_UNUSED void *userdata) +{ + int reason; + assert(c); + + /* Called whenever the client or server state changes */ + g_debug("Client changed to state %d", state); + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + g_debug("Client is RUNNING"); + + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if (!avahiGroup) + avahiRegisterService(c); + break; + + case AVAHI_CLIENT_FAILURE: + reason = avahi_client_errno(c); + if (reason == AVAHI_ERR_DISCONNECTED) { + g_message("Client Disconnected, will reconnect shortly"); + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = NULL; + } + if (avahiClient) + avahi_client_free(avahiClient); + avahiClient = + avahi_client_new(&avahiPoll, + AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, + &reason); + if (!avahiClient) { + g_warning("Could not reconnect: %s", + avahi_strerror(reason)); + avahiRunning = 0; + } + } else { + g_warning("Client failure: %s (terminal)", + avahi_strerror(reason)); + avahiRunning = 0; + } + break; + + case AVAHI_CLIENT_S_COLLISION: + g_debug("Client is COLLISION"); + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if (avahiGroup) { + g_debug("Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + case AVAHI_CLIENT_S_REGISTERING: + g_debug("Client is REGISTERING"); + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + + if (avahiGroup) { + g_debug("Resetting group"); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_CONNECTING: + g_debug("Client is CONNECTING"); + } +} + +static int avahiFdset(fd_set * rfds, fd_set * wfds, fd_set * efds) +{ + AvahiWatch *w; + int maxfd = -1; + if (!avahiRunning) + return maxfd; + for (w = avahiWatchList; w != NULL; w = w->next) { + if (w->requestedEvent & AVAHI_WATCH_IN) { + FD_SET(w->fd, rfds); + } + if (w->requestedEvent & AVAHI_WATCH_OUT) { + FD_SET(w->fd, wfds); + } + if (w->requestedEvent & AVAHI_WATCH_ERR) { + FD_SET(w->fd, efds); + } + if (w->requestedEvent & AVAHI_WATCH_HUP) { + g_warning("No support for HUP events! (ignoring)"); + } + + if (w->fd > maxfd) + maxfd = w->fd; + } + return maxfd; +} + +static int avahiFdconsume(int fdCount, fd_set * rfds, fd_set * wfds, + fd_set * efds) +{ + int retval = fdCount; + AvahiTimeout *t; + AvahiWatch *w = avahiWatchList; + + while (w != NULL && retval > 0) { + AvahiWatch *current = w; + current->observedEvent = 0; + if (FD_ISSET(current->fd, rfds)) { + current->observedEvent |= AVAHI_WATCH_IN; + FD_CLR(current->fd, rfds); + retval--; + } + if (FD_ISSET(current->fd, wfds)) { + current->observedEvent |= AVAHI_WATCH_OUT; + FD_CLR(current->fd, wfds); + retval--; + } + if (FD_ISSET(current->fd, efds)) { + current->observedEvent |= AVAHI_WATCH_ERR; + FD_CLR(current->fd, efds); + retval--; + } + + /* Advance to the next one right now, in case the callback + * removes itself + */ + w = w->next; + + if (current->observedEvent && avahiRunning) { + current->callback(current, current->fd, + current->observedEvent, + current->userdata); + } + } + + t = avahiTimeoutList; + while (t != NULL && avahiRunning) { + AvahiTimeout *current = t; + + /* Advance to the next one right now, in case the callback + * removes itself + */ + t = t->next; + avahiCheckExpiry(current); + } + + return retval; +} + +void init_avahi(const char *serviceName) +{ + int error; + g_debug("Initializing interface"); + + if (!avahi_is_valid_service_name(serviceName)) + g_error("Invalid zeroconf_name \"%s\"", serviceName); + + avahiName = avahi_strdup(serviceName); + + avahiRunning = 1; + + avahiPoll.userdata = NULL; + avahiPoll.watch_new = avahiWatchNew; + avahiPoll.watch_update = avahiWatchUpdate; + avahiPoll.watch_get_events = avahiWatchGetEvents; + avahiPoll.watch_free = avahiWatchFree; + avahiPoll.timeout_new = avahiTimeoutNew; + avahiPoll.timeout_update = avahiTimeoutUpdate; + avahiPoll.timeout_free = avahiTimeoutFree; + + avahiClient = avahi_client_new(&avahiPoll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &error); + + if (!avahiClient) { + g_warning("Failed to create client: %s", + avahi_strerror(error)); + goto fail; + } + + zeroConfIo.fdset = avahiFdset; + zeroConfIo.consume = avahiFdconsume; + registerIO(&zeroConfIo); + + return; + +fail: + avahi_finish(); +} + +void avahi_finish(void) +{ + g_debug("Shutting down interface"); + deregisterIO(&zeroConfIo); + + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = NULL; + } + + if (avahiClient) { + avahi_client_free(avahiClient); + avahiClient = NULL; + } + + avahi_free(avahiName); + avahiName = NULL; +} |