]> arthur.barton.de Git - netatalk.git/blobdiff - etc/netatalk/afp_avahi.c
afpd: move mDNS service registration to netatalk process
[netatalk.git] / etc / netatalk / afp_avahi.c
diff --git a/etc/netatalk/afp_avahi.c b/etc/netatalk/afp_avahi.c
new file mode 100644 (file)
index 0000000..63bc950
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ * Author:  Daniel S. Haischt <me@daniel.stefan.haischt.name>
+ * Purpose: Avahi based Zeroconf support
+ * Docs:    http://avahi.org/download/doxygen/
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_AVAHI
+
+#include <unistd.h>
+#include <time.h>
+
+#include <avahi-common/strlst.h>
+
+#include <atalk/logger.h>
+#include <atalk/util.h>
+#include <atalk/dsi.h>
+#include <atalk/unicode.h>
+#include <atalk/netatalk_conf.h>
+
+#include "afp_zeroconf.h"
+#include "afp_avahi.h"
+
+/*****************************************************************
+ * Global variables
+ *****************************************************************/
+struct context *ctx = NULL;
+
+/*****************************************************************
+ * Private functions
+ *****************************************************************/
+
+static void publish_reply(AvahiEntryGroup *g,
+                          AvahiEntryGroupState state,
+                          void *userdata);
+
+/*
+ * This function tries to register the AFP DNS
+ * SRV service type.
+ */
+static void register_stuff(void) {
+    uint port;
+    const struct vol *volume;
+    char name[MAXINSTANCENAMELEN+1];
+    AvahiStringList *strlist = NULL;
+    AvahiStringList *strlist2 = NULL;
+    char tmpname[256];
+
+    assert(ctx->client);
+
+    if (!ctx->group) {
+        if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) {
+            LOG(log_error, logtype_afpd, "Failed to create entry group: %s",
+                avahi_strerror(avahi_client_errno(ctx->client)));
+            goto fail;
+        }
+    }
+
+    if (avahi_entry_group_is_empty(ctx->group)) {
+        /* Register our service */
+
+        /* Build AFP volumes list */
+        int i = 0;
+        strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100");
+               
+        for (volume = getvolumes(); volume; volume = volume->v_next) {
+
+            if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_u8mname, -1, tmpname, 255) <= 0) {
+                LOG ( log_error, logtype_afpd, "Could not set Zeroconf volume name for TimeMachine");
+                goto fail;
+            }
+
+            if (volume->v_flags & AFPVOL_TM) {
+                if (volume->v_uuid) {
+                    LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine",
+                        volume->v_localname, volume->v_uuid);
+                    strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s",
+                                                           i++, tmpname, volume->v_uuid);
+                } else {
+                    LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.",
+                        volume->v_localname);
+                    strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1",
+                                                           i++, tmpname);
+                }      
+            }
+        }
+
+        port = atoi(ctx->obj->options.port);
+
+        LOG(log_info, logtype_afpd, "hostname: %s", ctx->obj->options.hostname);
+
+        if (convert_string(ctx->obj->options.unixcharset,
+                           CH_UTF8,
+                           ctx->obj->options.hostname,
+                           -1,
+                           name,
+                           MAXINSTANCENAMELEN) <= 0) {
+            LOG(log_error, logtype_afpd, "Could not set Zeroconf instance name: %s", ctx->obj->options.hostname);
+            goto fail;
+        }
+
+        LOG(log_info, logtype_afpd, "Registering server '%s' with Bonjour", name);            
+
+        if (avahi_entry_group_add_service(ctx->group,
+                                          AVAHI_IF_UNSPEC,
+                                          AVAHI_PROTO_UNSPEC,
+                                          0,
+                                          name,
+                                          AFP_DNS_SERVICE_TYPE,
+                                          NULL,
+                                          NULL,
+                                          port,
+                                          NULL) < 0) {
+            LOG(log_error, logtype_afpd, "Failed to add service: %s",
+                avahi_strerror(avahi_client_errno(ctx->client)));
+            goto fail;
+        }
+
+        if (i && avahi_entry_group_add_service_strlst(ctx->group,
+                                                      AVAHI_IF_UNSPEC,
+                                                      AVAHI_PROTO_UNSPEC,
+                                                      0,
+                                                      name,
+                                                      ADISK_SERVICE_TYPE,
+                                                      NULL,
+                                                      NULL,
+                                                      9, /* discard */
+                                                      strlist) < 0) {
+            LOG(log_error, logtype_afpd, "Failed to add service: %s",
+                avahi_strerror(avahi_client_errno(ctx->client)));
+            goto fail;
+        }      /* if */
+
+        if (ctx->obj->options.mimicmodel) {
+            strlist2 = avahi_string_list_add_printf(strlist2, "model=%s", ctx->obj->options.mimicmodel);
+            if (avahi_entry_group_add_service_strlst(ctx->group,
+                                                     AVAHI_IF_UNSPEC,
+                                                     AVAHI_PROTO_UNSPEC,
+                                                     0,
+                                                     name,
+                                                     DEV_INFO_SERVICE_TYPE,
+                                                     NULL,
+                                                     NULL,
+                                                     0,
+                                                     strlist2) < 0) {
+                LOG(log_error, logtype_afpd, "Failed to add service: %s",
+                    avahi_strerror(avahi_client_errno(ctx->client)));
+                goto fail;
+            }
+        } /* if (config->obj.options.mimicmodel) */
+
+        if (avahi_entry_group_commit(ctx->group) < 0) {
+            LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
+                avahi_strerror(avahi_client_errno(ctx->client)));
+            goto fail;
+        }
+
+    }  /* if avahi_entry_group_is_empty*/
+
+    return;
+
+fail:
+    time(NULL);
+//    avahi_threaded_poll_quit(ctx->threaded_poll);
+}
+
+/* Called when publishing of service data completes */
+static void publish_reply(AvahiEntryGroup *g,
+                          AvahiEntryGroupState state,
+                          AVAHI_GCC_UNUSED void *userdata)
+{
+    assert(ctx->group == NULL || g == ctx->group);
+
+    switch (state) {
+
+    case AVAHI_ENTRY_GROUP_ESTABLISHED :
+        /* The entry group has been established successfully */
+        LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
+        break;
+
+    case AVAHI_ENTRY_GROUP_COLLISION:
+        /* With multiple names there's no way to know which one collided */
+        LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
+            avahi_strerror(avahi_client_errno(ctx->client)));
+        avahi_threaded_poll_quit(ctx->threaded_poll);
+        break;
+               
+    case AVAHI_ENTRY_GROUP_FAILURE:
+        LOG(log_error, logtype_afpd, "Failed to register service: %s",
+            avahi_strerror(avahi_client_errno(ctx->client)));
+        avahi_threaded_poll_quit(ctx->threaded_poll);
+        break;
+
+    case AVAHI_ENTRY_GROUP_UNCOMMITED:
+        break;
+    case AVAHI_ENTRY_GROUP_REGISTERING:
+        break;
+    }
+}
+
+static void client_callback(AvahiClient *client,
+                            AvahiClientState state,
+                            void *userdata)
+{
+    ctx->client = client;
+
+    switch (state) {
+    case AVAHI_CLIENT_S_RUNNING:
+        /* The server has startup successfully and registered its host
+         * name on the network, so it's time to create our services */
+        if (!ctx->group)
+            register_stuff();
+        break;
+
+    case AVAHI_CLIENT_S_COLLISION:
+        if (ctx->group)
+            avahi_entry_group_reset(ctx->group);
+        break;
+
+    case AVAHI_CLIENT_FAILURE: {
+        if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
+            int error;
+
+            avahi_client_free(ctx->client);
+            ctx->client = NULL;
+            ctx->group = NULL;
+
+            /* Reconnect to the server */
+            if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
+                                                 AVAHI_CLIENT_NO_FAIL,
+                                                 client_callback,
+                                                 ctx,
+                                                 &error))) {
+
+                LOG(log_error, logtype_afpd, "Failed to contact server: %s",
+                    avahi_strerror(error));
+
+                avahi_threaded_poll_quit(ctx->threaded_poll);
+            }
+
+        } else {
+            LOG(log_error, logtype_afpd, "Client failure: %s",
+                avahi_strerror(avahi_client_errno(client)));
+            avahi_threaded_poll_quit(ctx->threaded_poll);
+        }
+        break;
+    }
+
+    case AVAHI_CLIENT_S_REGISTERING:
+        break;
+    case AVAHI_CLIENT_CONNECTING:
+        break;
+    }
+}
+
+/************************************************************************
+ * Public funcions
+ ************************************************************************/
+
+/*
+ * Tries to setup the Zeroconf thread and any
+ * neccessary config setting.
+ */
+void av_zeroconf_register(const AFPObj *obj) {
+    int error;
+
+    /* initialize the struct that holds our config settings. */
+    if (ctx) {
+        LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
+        avahi_entry_group_reset(ctx->group);
+    } else {
+        ctx = calloc(1, sizeof(struct context));
+        ctx->obj = obj;
+        assert(ctx);
+    }
+
+/* first of all we need to initialize our threading env */
+    if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
+        goto fail;
+    }
+
+/* now we need to acquire a client */
+    if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
+                                         AVAHI_CLIENT_NO_FAIL,
+                                         client_callback,
+                                         NULL,
+                                         &error))) {
+        LOG(log_error, logtype_afpd, "Failed to create client object: %s",
+            avahi_strerror(error));
+        goto fail;
+    }
+
+    if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
+        LOG(log_error, logtype_afpd, "Failed to create thread: %s",
+            avahi_strerror(avahi_client_errno(ctx->client)));
+        goto fail;
+    } else {
+        LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
+    }
+
+    ctx->thread_running = 1;
+    return;
+
+fail:
+    av_zeroconf_unregister();
+
+    return;
+}
+
+/*
+ * Tries to shutdown this loop impl.
+ * Call this function from inside this thread.
+ */
+int av_zeroconf_unregister() {
+    LOG(log_error, logtype_afpd, "av_zeroconf_unregister");
+
+    if (ctx) {
+        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_stop");
+        if (ctx->threaded_poll)
+            avahi_threaded_poll_stop(ctx->threaded_poll);
+        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_client_free");
+        if (ctx->client)
+            avahi_client_free(ctx->client);
+        LOG(log_error, logtype_afpd, "av_zeroconf_unregister: avahi_threaded_poll_free");
+        if (ctx->threaded_poll)
+            avahi_threaded_poll_free(ctx->threaded_poll);
+        free(ctx);
+        ctx = NULL;
+    }
+    return 0;
+}
+
+#endif /* USE_AVAHI */
+