]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/afp_avahi.c
Use pthread_sigmask instead of sigprocmask. correctly restart zeroconf with SIGHUP...
[netatalk.git] / etc / afpd / afp_avahi.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2 /*
3  * Author:  Daniel S. Haischt <me@daniel.stefan.haischt.name>
4  * Purpose: Avahi based Zeroconf support
5  * Docs:    http://avahi.org/download/doxygen/
6  *
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #ifdef HAVE_AVAHI
14
15 #include <unistd.h>
16
17 #include <avahi-common/strlst.h>
18
19 #include <atalk/logger.h>
20 #include <atalk/util.h>
21 #include <atalk/dsi.h>
22 #include <atalk/unicode.h>
23
24 #include "afp_avahi.h"
25 #include "afp_config.h"
26 #include "volume.h"
27
28 /*****************************************************************
29  * Global variables
30  *****************************************************************/
31 struct context *ctx = NULL;
32
33 /*****************************************************************
34  * Private functions
35  *****************************************************************/
36
37 static void publish_reply(AvahiEntryGroup *g,
38                           AvahiEntryGroupState state,
39                           void *userdata);
40
41 /*
42  * This function tries to register the AFP DNS
43  * SRV service type.
44  */
45 static void register_stuff(void) {
46         uint port;
47         const AFPConfig *config;
48         const struct vol *volume;
49         DSI *dsi;
50         const char *name;
51         AvahiStringList *strlist = NULL;
52         char        tmpname[256];
53
54   assert(ctx->client);
55
56   if (!ctx->group) {
57     if (!(ctx->group = avahi_entry_group_new(ctx->client, publish_reply, ctx))) {
58       LOG(log_error, logtype_afpd, "Failed to create entry group: %s",
59           avahi_strerror(avahi_client_errno(ctx->client)));
60       goto fail;
61     }
62   }
63
64   if (avahi_entry_group_is_empty(ctx->group)) {
65     /* Register our service */
66
67                 /* Build AFP volumes list */
68                 int i = 0;
69                 strlist = avahi_string_list_add_printf(strlist, "sys=waMa=0,adVF=0x100");
70                 
71                 for (volume = getvolumes(); volume; volume = volume->v_next) {
72
73                         if (convert_string(CH_UCS2, CH_UTF8_MAC, volume->v_name, -1, tmpname, 255) <= 0)
74                                 goto fail;
75
76                         if (volume->v_flags & AFPVOL_TM) {
77                                 if (volume->v_uuid) {
78                                         LOG(log_info, logtype_afpd, "Registering volume '%s' with UUID: '%s' for TimeMachine",
79                                                         volume->v_localname, volume->v_uuid);
80                                         strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1,adVU=%s",
81                                                                                                                                                                                                  i++, tmpname, volume->v_uuid);
82                                 } else {
83                                         LOG(log_warning, logtype_afpd, "Registering volume '%s' for TimeMachine. But UUID is invalid.",
84                                                         volume->v_localname);
85                                         strlist = avahi_string_list_add_printf(strlist, "dk%u=adVN=%s,adVF=0xa1",
86                                                                                                                                                                                                  i++, tmpname);
87                                 }       
88                         }
89                 }
90
91                 /* AFP server */
92                 for (config = ctx->configs; config; config = config->next) {
93
94                         dsi = (DSI *)config->obj.handle;
95                         name = config->obj.options.server ?
96                                 config->obj.options.server : config->obj.options.hostname;
97                         port = getip_port((struct sockaddr *)&dsi->server);
98
99                         if (avahi_entry_group_add_service(ctx->group,
100                                                                                                                                                                 AVAHI_IF_UNSPEC,
101                                                                                                                                                                 AVAHI_PROTO_UNSPEC,
102                                                                                                                                                                 0,
103                                                                                                                                                                 name,
104                                                                                                                                                                 AFP_DNS_SERVICE_TYPE,
105                                                                                                                                                                 NULL,
106                                                                                                                                                                 NULL,
107                                                                                                                                                                 port,
108                                                                                                                                                                 NULL) < 0) {
109                                 LOG(log_error, logtype_afpd, "Failed to add service: %s",
110                                                 avahi_strerror(avahi_client_errno(ctx->client)));
111                                 goto fail;
112                         }
113
114                         if (i && avahi_entry_group_add_service_strlst(ctx->group,
115                                                                                                                                                                                                                 AVAHI_IF_UNSPEC,
116                                                                                                                                                                                                                 AVAHI_PROTO_UNSPEC,
117                                                                                                                                                                                                                 0,
118                                                                                                                                                                                                                 name,
119                                                                                                                                                                                                                 "_adisk._tcp",
120                                                                                                                                                                                                                 NULL,
121                                                                                                                                                                                                                 NULL,
122                                                                                                                                                                                                                 9, /* discard */
123                                                                                                                                                                                                                 strlist) < 0) {
124                                 LOG(log_error, logtype_afpd, "Failed to add service: %s",
125                                                 avahi_strerror(avahi_client_errno(ctx->client)));
126                                 goto fail;
127                         }       /* if */
128                 }       /* for config*/
129
130                 if (avahi_entry_group_commit(ctx->group) < 0) {
131                         LOG(log_error, logtype_afpd, "Failed to commit entry group: %s",
132                                         avahi_strerror(avahi_client_errno(ctx->client)));
133                         goto fail;
134                 }
135
136         }       /* if avahi_entry_group_is_empty*/
137
138   return;
139
140 fail:
141         avahi_client_free (ctx->client);
142         avahi_threaded_poll_quit(ctx->threaded_poll);
143 }
144
145 /* Called when publishing of service data completes */
146 static void publish_reply(AvahiEntryGroup *g,
147                           AvahiEntryGroupState state,
148                           AVAHI_GCC_UNUSED void *userdata)
149 {
150   assert(ctx->group == NULL || g == ctx->group);
151
152   switch (state) {
153
154   case AVAHI_ENTRY_GROUP_ESTABLISHED :
155     /* The entry group has been established successfully */
156                 LOG(log_debug, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED");
157     break;
158
159   case AVAHI_ENTRY_GROUP_COLLISION:
160                 /* With multiple names there's no way to know which one collided */
161     LOG(log_error, logtype_afpd, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION",
162                                 avahi_strerror(avahi_client_errno(ctx->client)));
163                 avahi_client_free(avahi_entry_group_get_client(g));
164                 avahi_threaded_poll_quit(ctx->threaded_poll);
165     break;
166                 
167   case AVAHI_ENTRY_GROUP_FAILURE:
168     LOG(log_error, logtype_afpd, "Failed to register service: %s",
169                                 avahi_strerror(avahi_client_errno(ctx->client)));
170                 avahi_client_free(avahi_entry_group_get_client(g));
171                 avahi_threaded_poll_quit(ctx->threaded_poll);
172                 break;
173
174         case AVAHI_ENTRY_GROUP_UNCOMMITED:
175                 break;
176   case AVAHI_ENTRY_GROUP_REGISTERING:
177                 break;
178   }
179 }
180
181 static void client_callback(AvahiClient *client,
182                             AvahiClientState state,
183                             void *userdata)
184 {
185   ctx->client = client;
186
187   switch (state) {
188   case AVAHI_CLIENT_S_RUNNING:
189     /* The server has startup successfully and registered its host
190      * name on the network, so it's time to create our services */
191     if (!ctx->group)
192       register_stuff();
193     break;
194
195   case AVAHI_CLIENT_S_COLLISION:
196     if (ctx->group)
197       avahi_entry_group_reset(ctx->group);
198     break;
199
200   case AVAHI_CLIENT_FAILURE: {
201     if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
202       int error;
203
204       avahi_client_free(ctx->client);
205       ctx->client = NULL;
206       ctx->group = NULL;
207
208       /* Reconnect to the server */
209       if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
210                                            AVAHI_CLIENT_NO_FAIL,
211                                            client_callback,
212                                            ctx,
213                                            &error))) {
214
215         LOG(log_error, logtype_afpd, "Failed to contact server: %s",
216             avahi_strerror(error));
217
218         avahi_client_free (ctx->client);
219         avahi_threaded_poll_quit(ctx->threaded_poll);
220       }
221
222                 } else {
223                         LOG(log_error, logtype_afpd, "Client failure: %s",
224                                         avahi_strerror(avahi_client_errno(client)));
225                         avahi_client_free (ctx->client);
226                         avahi_threaded_poll_quit(ctx->threaded_poll);
227                 }
228     break;
229   }
230
231   case AVAHI_CLIENT_S_REGISTERING:
232                 break;
233   case AVAHI_CLIENT_CONNECTING:
234     break;
235   }
236 }
237
238 /************************************************************************
239  * Public funcions
240  ************************************************************************/
241
242 /*
243  * Tries to setup the Zeroconf thread and any
244  * neccessary config setting.
245  */
246 void av_zeroconf_setup(const AFPConfig *configs) {
247   int error, ret;
248
249   /* initialize the struct that holds our config settings. */
250   if (ctx) {
251                 LOG(log_debug, logtype_afpd, "Resetting zeroconf records");
252                 avahi_entry_group_reset(ctx->group);
253         } else {
254                 ctx = calloc(1, sizeof(struct context));
255                 ctx->configs = configs;
256                 assert(ctx);
257         }
258
259 /* first of all we need to initialize our threading env */
260   if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
261      goto fail;
262   }
263
264 /* now we need to acquire a client */
265   if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
266                                        AVAHI_CLIENT_NO_FAIL,
267                                        client_callback,
268                                        NULL,
269                                        &error))) {
270     LOG(log_error, logtype_afpd, "Failed to create client object: %s",
271         avahi_strerror(avahi_client_errno(ctx->client)));
272     goto fail;
273   }
274
275   return;
276
277 fail:
278   if (ctx)
279     av_zeroconf_unregister();
280
281   return;
282 }
283
284 /*
285  * This function finally runs the loop impl.
286  */
287 int av_zeroconf_run(void) {
288   int ret;
289
290   /* Finally, start the event loop thread */
291   if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
292     LOG(log_error, logtype_afpd, "Failed to create thread: %s",
293         avahi_strerror(avahi_client_errno(ctx->client)));
294     goto fail;
295   } else {
296     LOG(log_info, logtype_afpd, "Successfully started avahi loop.");
297   }
298
299   ctx->thread_running = 1;
300   return 0;
301
302 fail:
303         if (ctx)
304     av_zeroconf_unregister();
305
306   return -1;
307 }
308
309 /*
310  * Tries to shutdown this loop impl.
311  * Call this function from outside this thread.
312  */
313 void av_zeroconf_shutdown() {
314   /* Call this when the app shuts down */
315   avahi_threaded_poll_stop(ctx->threaded_poll);
316   avahi_client_free(ctx->client);
317   avahi_threaded_poll_free(ctx->threaded_poll);
318   free(ctx);
319         ctx = NULL;
320 }
321
322 /*
323  * Tries to shutdown this loop impl.
324  * Call this function from inside this thread.
325  */
326 int av_zeroconf_unregister() {
327   if (ctx->thread_running) {
328     /* First, block the event loop */
329     avahi_threaded_poll_lock(ctx->threaded_poll);
330
331     /* Than, do your stuff */
332     avahi_threaded_poll_quit(ctx->threaded_poll);
333
334     /* Finally, unblock the event loop */
335     avahi_threaded_poll_unlock(ctx->threaded_poll);
336     ctx->thread_running = 0;
337   }
338
339   if (ctx->client)
340     avahi_client_free(ctx->client);
341
342   if (ctx->threaded_poll)
343     avahi_threaded_poll_free(ctx->threaded_poll);
344
345   free(ctx);
346         ctx = NULL;
347
348   return 0;
349 }
350
351 #endif /* USE_AVAHI */
352