2 * Author: Daniel S. Haischt <me@daniel.stefan.haischt.name>
3 * Purpose: Avahi based Zeroconf support
4 * Docs: http://avahi.org/download/doxygen/
16 #include <avahi-common/strlst.h>
18 #include <atalk/logger.h>
19 #include <atalk/util.h>
20 #include <atalk/dsi.h>
21 #include <atalk/unicode.h>
23 #include "afp_avahi.h"
24 #include "afp_config.h"
27 /*****************************************************************
29 *****************************************************************/
30 struct context *ctx = NULL;
32 /*****************************************************************
34 *****************************************************************/
36 static void publish_reply(AvahiEntryGroup *g,
37 AvahiEntryGroupState state,
41 * This function tries to register the AFP DNS
44 static void register_stuff(void) {
46 const AFPConfig *config;
47 const struct vol *volume;
49 char name[MAXINSTANCENAMELEN+1];
50 AvahiStringList *strlist = NULL;
56 if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) {
57 LOG(log_error, logtype_afpd, "Failed to create entry group: %s",
58 avahi_strerror(avahi_client_errno(ctx->client)));
63 if (avahi_entry_group_is_empty(ctx->group)) {
64 /* Register our service */
66 /* Build AFP volumes list */
68 strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100");
70 for (volume = getvolumes(); volume; volume = volume->v_next) {
72 if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_name, -1, tmpname, 255) <= 0) {
73 LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine");
77 if (volume->v_flags & AFPVOL_TM) {
79 LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine",
80 volume->v_localname, volume->v_uuid);
81 strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s",
82 i++, tmpname, volume->v_uuid);
84 LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.",
86 strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1",
93 for (config = ctx->configs; config; config = config->next) {
95 dsi = (DSI *)config->obj.handle;
96 port = getip_port((struct sockaddr *)&dsi->server);
98 if (convert_string(config->obj.options.unixcharset, CH_UTF8,
99 config->obj.options.server ? config->obj.options.server : config->obj.options.hostname, -1,
100 name, MAXINSTANCENAMELEN) <= 0) {
101 LOG ( log_error, logtype_afpd, "Could not set Zeroconf instance name");
105 if (avahi_entry_group_add_service(ctx->group,
110 AFP_DNS_SERVICE_TYPE,
115 LOG(log_error, logtype_afpd, "Failed to add service: %s",
116 avahi_strerror(avahi_client_errno(ctx->client)));
120 if (i && avahi_entry_group_add_service_strlst(ctx->group,
130 LOG(log_error, logtype_afpd, "Failed to add service: %s",
131 avahi_strerror(avahi_client_errno(ctx->client)));
136 if (avahi_entry_group_commit(ctx->group) < 0) {
137 LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
138 avahi_strerror(avahi_client_errno(ctx->client)));
142 } /* if avahi_entry_group_is_empty*/
147 avahi_client_free (ctx->client);
148 avahi_threaded_poll_quit(ctx->threaded_poll);
151 /* Called when publishing of service data completes */
152 static void publish_reply(AvahiEntryGroup *g,
153 AvahiEntryGroupState state,
154 AVAHI_GCC_UNUSED void *userdata)
156 assert(ctx->group == NULL || g == ctx->group);
160 case AVAHI_ENTRY_GROUP_ESTABLISHED :
161 /* The entry group has been established successfully */
162 LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
165 case AVAHI_ENTRY_GROUP_COLLISION:
166 /* With multiple names there's no way to know which one collided */
167 LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
168 avahi_strerror(avahi_client_errno(ctx->client)));
169 avahi_client_free(avahi_entry_group_get_client(g));
170 avahi_threaded_poll_quit(ctx->threaded_poll);
173 case AVAHI_ENTRY_GROUP_FAILURE:
174 LOG(log_error, logtype_afpd, "Failed to register service: %s",
175 avahi_strerror(avahi_client_errno(ctx->client)));
176 avahi_client_free(avahi_entry_group_get_client(g));
177 avahi_threaded_poll_quit(ctx->threaded_poll);
180 case AVAHI_ENTRY_GROUP_UNCOMMITED:
182 case AVAHI_ENTRY_GROUP_REGISTERING:
187 static void client_callback(AvahiClient *client,
188 AvahiClientState state,
191 ctx->client = client;
194 case AVAHI_CLIENT_S_RUNNING:
195 /* The server has startup successfully and registered its host
196 * name on the network, so it's time to create our services */
201 case AVAHI_CLIENT_S_COLLISION:
203 avahi_entry_group_reset(ctx->group);
206 case AVAHI_CLIENT_FAILURE: {
207 if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
210 avahi_client_free(ctx->client);
214 /* Reconnect to the server */
215 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
216 AVAHI_CLIENT_NO_FAIL,
221 LOG(log_error, logtype_afpd, "Failed to contact server: %s",
222 avahi_strerror(error));
224 avahi_client_free (ctx->client);
225 avahi_threaded_poll_quit(ctx->threaded_poll);
229 LOG(log_error, logtype_afpd, "Client failure: %s",
230 avahi_strerror(avahi_client_errno(client)));
231 avahi_client_free (ctx->client);
232 avahi_threaded_poll_quit(ctx->threaded_poll);
237 case AVAHI_CLIENT_S_REGISTERING:
239 case AVAHI_CLIENT_CONNECTING:
244 /************************************************************************
246 ************************************************************************/
249 * Tries to setup the Zeroconf thread and any
250 * neccessary config setting.
252 void av_zeroconf_setup(const AFPConfig *configs) {
255 /* initialize the struct that holds our config settings. */
257 LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
258 avahi_entry_group_reset(ctx->group);
260 ctx = calloc(1, sizeof(struct context));
261 ctx->configs = configs;
265 /* first of all we need to initialize our threading env */
266 if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
270 /* now we need to acquire a client */
271 if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
272 AVAHI_CLIENT_NO_FAIL,
276 LOG(log_error, logtype_afpd, "Failed to create client object: %s",
277 avahi_strerror(avahi_client_errno(ctx->client)));
285 av_zeroconf_unregister();
291 * This function finally runs the loop impl.
293 int av_zeroconf_run(void) {
296 /* Finally, start the event loop thread */
297 if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
298 LOG(log_error, logtype_afpd, "Failed to create thread: %s",
299 avahi_strerror(avahi_client_errno(ctx->client)));
302 LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
305 ctx->thread_running = 1;
310 av_zeroconf_unregister();
316 * Tries to shutdown this loop impl.
317 * Call this function from outside this thread.
319 void av_zeroconf_shutdown() {
320 /* Call this when the app shuts down */
321 avahi_threaded_poll_stop(ctx->threaded_poll);
322 avahi_client_free(ctx->client);
323 avahi_threaded_poll_free(ctx->threaded_poll);
329 * Tries to shutdown this loop impl.
330 * Call this function from inside this thread.
332 int av_zeroconf_unregister() {
333 if (ctx->thread_running) {
334 /* First, block the event loop */
335 avahi_threaded_poll_lock(ctx->threaded_poll);
337 /* Than, do your stuff */
338 avahi_threaded_poll_quit(ctx->threaded_poll);
340 /* Finally, unblock the event loop */
341 avahi_threaded_poll_unlock(ctx->threaded_poll);
342 ctx->thread_running = 0;
346 avahi_client_free(ctx->client);
348 if (ctx->threaded_poll)
349 avahi_threaded_poll_free(ctx->threaded_poll);
357 #endif /* USE_AVAHI */