Line 0
Link Here
|
|
|
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 */ |