/* * 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 #include #include #include #include #include #include #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; }