]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/afp_avahi.c
First patch import, missing files
[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 "afp_avahi.h"
16
17 static void publish_reply(AvahiEntryGroup *g,
18                           AvahiEntryGroupState state,
19                           void *userdata);
20
21 /*
22  * This function tries to register the AFP DNS
23  * SRV service type.
24  */
25 static void register_stuff(struct context *ctx) {
26   char r[128];
27   int ret;
28
29   assert(ctx->client);
30
31   if (!ctx->group) {
32
33     if (!(ctx->group = avahi_entry_group_new(ctx->client,
34                                              publish_reply,
35                                              ctx))) {
36       LOG(log_error,
37           logtype_afpd,
38           "Failed to create entry group: %s\n",
39           avahi_strerror(avahi_client_errno(ctx->client)));
40       goto fail;
41     }
42
43   }
44
45   LOG(log_info, logtype_afpd, "Adding service '%s'\n", ctx->name);
46
47   if (avahi_entry_group_is_empty(ctx->group)) {
48     /* Register our service */
49
50     if (avahi_entry_group_add_service(ctx->group,
51                                       AVAHI_IF_UNSPEC,
52                                       AVAHI_PROTO_UNSPEC,
53                                       0,
54                                       ctx->name,
55                                       AFP_DNS_SERVICE_TYPE,
56                                       NULL,
57                                       NULL,
58                                       ctx->port,
59                                       NULL) < 0) {
60       LOG(log_error,
61           logtype_afpd,
62           "Failed to add service: %s\n",
63           avahi_strerror(avahi_client_errno(ctx->client)));
64       goto fail;
65     }
66
67     if (avahi_entry_group_commit(ctx->group) < 0) {
68       LOG(log_error,
69           logtype_afpd,
70           "Failed to commit entry group: %s\n",
71           avahi_strerror(avahi_client_errno(ctx->client)));
72       goto fail;
73     }
74   }
75
76   return;
77
78   fail:
79     avahi_client_free (ctx->client);
80 #ifndef HAVE_AVAHI_THREADED_POLL
81     avahi_simple_poll_quit(ctx->simple_poll);
82 #else
83     avahi_threaded_poll_quit(ctx->threaded_poll);
84 #endif
85 }
86
87 /* Called when publishing of service data completes */
88 static void publish_reply(AvahiEntryGroup *g,
89                           AvahiEntryGroupState state,
90                           AVAHI_GCC_UNUSED void *userdata)
91 {
92   struct context *ctx = userdata;
93
94   assert(g == ctx->group);
95
96   switch (state) {
97
98   case AVAHI_ENTRY_GROUP_ESTABLISHED :
99     /* The entry group has been established successfully */
100     break;
101
102   case AVAHI_ENTRY_GROUP_COLLISION: {
103     char *n;
104
105     /* Pick a new name for our service */
106
107     n = avahi_alternative_service_name(ctx->name);
108     assert(n);
109
110     avahi_free(ctx->name);
111     ctx->name = n;
112
113     register_stuff(ctx);
114     break;
115   }
116
117   case AVAHI_ENTRY_GROUP_FAILURE: {
118     LOG(log_error,
119         logtype_afpd,
120         "Failed to register service: %s\n",
121         avahi_strerror(avahi_client_errno(ctx->client)));
122     avahi_client_free (avahi_entry_group_get_client(g));
123 #ifndef HAVE_AVAHI_THREADED_POLL
124     avahi_simple_poll_quit(ctx->simple_poll);
125 #else
126     avahi_threaded_poll_quit(ctx->threaded_poll);
127 #endif
128     break;
129   }
130
131   case AVAHI_ENTRY_GROUP_UNCOMMITED:
132   case AVAHI_ENTRY_GROUP_REGISTERING:
133     ;
134   }
135 }
136
137 static void client_callback(AvahiClient *client,
138                             AvahiClientState state,
139                             void *userdata)
140 {
141   struct context *ctx = userdata;
142
143   ctx->client = client;
144
145   switch (state) {
146
147   case AVAHI_CLIENT_S_RUNNING:
148
149     /* The server has startup successfully and registered its host
150      * name on the network, so it's time to create our services */
151     if (!ctx->group)
152       register_stuff(ctx);
153     break;
154
155   case AVAHI_CLIENT_S_COLLISION:
156
157     if (ctx->group)
158       avahi_entry_group_reset(ctx->group);
159     break;
160
161   case AVAHI_CLIENT_FAILURE: {
162
163     if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
164       int error;
165
166       avahi_client_free(ctx->client);
167       ctx->client = NULL;
168       ctx->group = NULL;
169
170       /* Reconnect to the server */
171
172 #ifndef HAVE_AVAHI_THREADED_POLL
173       if (!(ctx->client = avahi_client_new(avahi_simple_poll_get(ctx->simple_poll),
174 #else
175       if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
176 #endif
177                                            AVAHI_CLIENT_NO_FAIL,
178                                            client_callback,
179                                            ctx,
180                                            &error))) {
181
182         LOG(log_error,
183             logtype_afpd,
184             "Failed to contact server: %s\n",
185             avahi_strerror(error));
186
187         avahi_client_free (ctx->client);
188 #ifndef HAVE_AVAHI_THREADED_POLL
189         avahi_simple_poll_quit(ctx->simple_poll);
190 #else
191         avahi_threaded_poll_quit(ctx->threaded_poll);
192 #endif
193       }
194
195       } else {
196         LOG(log_error,
197             logtype_afpd,
198             "Client failure: %s\n",
199             avahi_strerror(avahi_client_errno(client)));
200
201         avahi_client_free (ctx->client);
202 #ifndef HAVE_AVAHI_THREADED_POLL
203         avahi_simple_poll_quit(ctx->simple_poll);
204 #else
205         avahi_threaded_poll_quit(ctx->threaded_poll);
206 #endif
207       }
208
209     break;
210   }
211
212   case AVAHI_CLIENT_S_REGISTERING:
213   case AVAHI_CLIENT_CONNECTING:
214     ;
215   }
216 }
217
218 static void* thread(void *userdata) {
219 #ifndef HAVE_AVAHI_THREADED_POLL
220   struct context *ctx = userdata;
221   sigset_t mask;
222   int r;
223
224   /* Make sure that signals are delivered to the main thread */
225   sigfillset(&mask);
226   pthread_sigmask(SIG_BLOCK, &mask, NULL);
227     
228   pthread_mutex_lock(&ctx->mutex);
229
230   /* Run the main loop */
231   LOG(log_info, logtype_afpd, "Starting avahi loop...");
232   r = avahi_simple_poll_loop(ctx->simple_poll);
233
234   /* Cleanup some stuff */
235   if (ctx->client)
236     avahi_client_free(ctx->client);
237   ctx->client = NULL;
238   ctx->group = NULL;
239     
240   pthread_mutex_unlock(&ctx->mutex);
241 #endif    
242   return NULL;
243 }
244
245 static int poll_func(struct pollfd *ufds,
246                      unsigned int nfds,
247                      int timeout,
248                      void *userdata) {
249 #ifndef HAVE_AVAHI_THREADED_POLL
250   pthread_mutex_t *mutex = userdata;
251   int r;
252
253   /* Before entering poll() we unlock the mutex, so that
254    * avahi_simple_poll_quit() can succeed from another thread. */
255     
256   pthread_mutex_unlock(mutex);
257   r = poll(ufds, nfds, timeout);
258   pthread_mutex_lock(mutex);
259
260   return r;
261 #else
262   return 0;
263 #endif
264 }
265
266 /*
267  * Tries to setup the Zeroconf thread and any
268  * neccessary config setting.
269  */
270 void* av_zeroconf_setup(unsigned long port, const char *name) {
271   struct context *ctx = NULL;
272
273   /* default service name, if there's none in
274    * the config file.
275    */
276   char service[256] = "AFP Server on ";
277   int error, ret;
278
279   /* initialize the struct that holds our
280    * config settings.
281    */
282   ctx = malloc(sizeof(struct context));
283   assert(ctx);
284   ctx->client = NULL;
285   ctx->group = NULL;
286 #ifndef HAVE_AVAHI_THREADED_POLL
287   ctx->simple_poll = NULL;
288   pthread_mutex_init(&ctx->mutex, NULL);
289 #else
290   ctx->threaded_poll = NULL;
291 #endif
292   ctx->thread_running = 0;
293
294   LOG(log_info,
295       logtype_afpd,
296       "Setting port for Zeroconf service to: %i.\n",
297       port);  
298   ctx->port = port;
299
300   /* Prepare service name */
301   if (!name) {
302     LOG(log_info,
303         logtype_afpd,
304         "Assigning default service name.\n");
305     gethostname(service+14, sizeof(service)-15);
306     service[sizeof(service)-1] = 0;
307
308     ctx->name = strdup(service);
309   }
310   else {
311     ctx->name = strdup(name);
312   }
313
314   assert(ctx->name);
315
316 /* first of all we need to initialize our threading env */
317 #ifdef HAVE_AVAHI_THREADED_POLL
318   if (!(ctx->threaded_poll = avahi_threaded_poll_new())) {
319      goto fail;
320   }
321 #else
322   if (!(ctx->simple_poll = avahi_simple_poll_new())) {
323       LOG(log_error,
324           logtype_afpd,
325           "Failed to create event loop object.\n");
326       goto fail;
327   }
328
329   avahi_simple_poll_set_func(ctx->simple_poll, poll_func, &ctx->mutex);
330 #endif
331
332 /* now we need to acquire a client */
333 #ifdef HAVE_AVAHI_THREADED_POLL
334   if (!(ctx->client = avahi_client_new(avahi_threaded_poll_get(ctx->threaded_poll),
335                                        AVAHI_CLIENT_NO_FAIL,
336                                        client_callback,
337                                        ctx,
338                                        &error))) {
339     LOG(log_error,
340         logtype_afpd,
341         "Failed to create client object: %s\n",
342         avahi_strerror(avahi_client_errno(ctx->client)));
343     goto fail;
344   }
345 #else
346   if (!(ctx->client = avahi_client_new(avahi_simple_poll_get(ctx->simple_poll),
347                                        AVAHI_CLIENT_NO_FAIL,
348                                        client_callback,
349                                        ctx,
350                                        &error))) {
351     LOG(log_error,
352         logtype_afpd,
353         "Failed to create client object: %s\n",
354         avahi_strerror(avahi_client_errno(ctx->client)));
355     goto fail;
356   }
357 #endif
358
359   return ctx;
360
361 fail:
362
363   if (ctx)
364     av_zeroconf_unregister(ctx);
365
366   return NULL;
367 }
368
369 /*
370  * This function finally runs the loop impl.
371  */
372 int av_zeroconf_run(void *u) {
373   struct context *ctx = u;
374   int ret;
375
376 #ifdef HAVE_AVAHI_THREADED_POLL
377   /* Finally, start the event loop thread */
378   if (avahi_threaded_poll_start(ctx->threaded_poll) < 0) {
379     LOG(log_error,
380         logtype_afpd,
381         "Failed to create thread: %s\n",
382         avahi_strerror(avahi_client_errno(ctx->client)));
383     goto fail;
384   } else {
385     LOG(log_info, logtype_afpd, "Successfully started avahi loop.\n");
386   }
387 #else
388   /* Create the mDNS event handler */
389   if ((ret = pthread_create(&ctx->thread_id, NULL, thread, ctx)) < 0) {
390     LOG(log_error,
391         logtype_afpd,
392         "Failed to create thread: %s\n", strerror(ret));
393     goto fail;
394   } else {
395     LOG(log_info, logtype_afpd, "Successfully started avahi loop.\n");
396   }
397 #endif
398
399   ctx->thread_running = 1;
400
401   return 0;
402
403 fail:
404
405   if (ctx)
406     av_zeroconf_unregister(ctx);
407
408   return -1;
409 }
410
411 /*
412  * Used to lock access to the loop.
413  * Currently unused.
414  */
415 void av_zeroconf_lock(void *u) {
416 #ifdef HAVE_AVAHI_THREADED_POLL
417   struct context *ctx = u;
418
419   avahi_threaded_poll_lock(ctx->threaded_poll);
420 #endif
421 }
422
423 /*
424  * Used to unlock access to the loop.
425  * Currently unused.
426  */
427 void av_zeroconf_unlock(void *u) {
428 #ifdef HAVE_AVAHI_THREADED_POLL
429   struct context *ctx = u;
430
431   avahi_threaded_poll_unlock(ctx->threaded_poll);
432 #endif
433 }
434
435 /*
436  * Tries to shutdown this loop impl.
437  * Call this function from outside this thread.
438  */
439 void av_zeroconf_shutdown(void *u) {
440   struct context *ctx = u;
441
442   /* Call this when the app shuts down */
443 #ifdef HAVE_AVAHI_THREADED_POLL
444   avahi_threaded_poll_stop(ctx->threaded_poll);
445   avahi_free(ctx->name);
446   avahi_client_free(ctx->client);
447   avahi_threaded_poll_free(ctx->threaded_poll);
448 #else
449   av_zeroconf_unregister(ctx);
450 #endif
451 }
452
453 /*
454  * Tries to shutdown this loop impl.
455  * Call this function from inside this thread.
456  */
457 int av_zeroconf_unregister(void *u) {
458   struct context *ctx = u;
459
460   if (ctx->thread_running) {
461 #ifndef HAVE_AVAHI_THREADED_POLL
462     pthread_mutex_lock(&ctx->mutex);
463     avahi_simple_poll_quit(ctx->simple_poll);
464     pthread_mutex_unlock(&ctx->mutex);
465
466     pthread_join(ctx->thread_id, NULL);
467 #else
468     /* First, block the event loop */
469     avahi_threaded_poll_lock(ctx->threaded_poll);
470
471     /* Than, do your stuff */
472     avahi_threaded_poll_quit(ctx->threaded_poll);
473
474     /* Finally, unblock the event loop */
475     avahi_threaded_poll_unlock(ctx->threaded_poll);
476 #endif
477     ctx->thread_running = 0;
478   }
479
480   avahi_free(ctx->name);
481
482   if (ctx->client)
483     avahi_client_free(ctx->client);
484
485 #ifndef HAVE_AVAHI_THREADED_POLL
486   if (ctx->simple_poll)
487     avahi_simple_poll_free(ctx->simple_poll);
488
489   pthread_mutex_destroy(&ctx->mutex);
490 #else
491   if (ctx->threaded_poll)
492     avahi_threaded_poll_free(ctx->threaded_poll);
493 #endif
494
495   free(ctx);
496
497   return 0;
498 }
499
500 #endif /* USE_AVAHI */