]> git.meshlink.io Git - catta/blob - avahi-daemon/static-services.c
* add C examples
[catta] / avahi-daemon / static-services.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5  
6   avahi is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10  
11   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <sys/stat.h>
27 #include <glob.h>
28 #include <limits.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33
34 #include <glib.h>
35 #include <expat.h>
36
37 #include <avahi-core/llist.h>
38 #include <avahi-core/log.h>
39
40 #include "main.h"
41 #include "static-services.h"
42
43 typedef struct StaticService StaticService;
44 typedef struct StaticServiceGroup StaticServiceGroup;
45
46 struct StaticService {
47     StaticServiceGroup *group;
48     
49     gchar *type;
50     gchar *domain_name;
51     gchar *host_name;
52     guint16 port;
53
54     AvahiStringList *txt_records;
55     
56     AVAHI_LLIST_FIELDS(StaticService, services);
57 };
58
59 struct StaticServiceGroup {
60     gchar *filename;
61     time_t mtime;
62
63     gchar *name, *chosen_name;
64     gboolean replace_wildcards;
65
66     AvahiEntryGroup *entry_group;
67     AVAHI_LLIST_HEAD(StaticService, services);
68     AVAHI_LLIST_FIELDS(StaticServiceGroup, groups);
69 };
70
71 static AVAHI_LLIST_HEAD(StaticServiceGroup, groups) = NULL;
72
73 static gchar *replacestr(const gchar *pattern, const gchar *a, const gchar *b) {
74     gchar *r = NULL, *e, *n;
75
76     while ((e = strstr(pattern, a))) {
77         gchar *k;
78
79         k = g_strndup(pattern, e - pattern);
80         if (r)
81             n = g_strconcat(r, k, b, NULL);
82         else
83             n = g_strconcat(k, b, NULL);
84
85         g_free(k);
86         g_free(r);
87         r = n;
88
89         pattern = e + strlen(a);
90     }
91
92     if (!r)
93         return g_strdup(pattern);
94
95     n = g_strconcat(r, pattern, NULL);
96     g_free(r);
97
98     return n;
99 }
100
101 static void add_static_service_group_to_server(StaticServiceGroup *g);
102 static void remove_static_service_group_from_server(StaticServiceGroup *g);
103
104 static StaticService *static_service_new(StaticServiceGroup *group) {
105     StaticService *s;
106     
107     g_assert(group);
108     s = g_new(StaticService, 1);
109     s->group = group;
110
111     s->type = s->host_name = s->domain_name = NULL;
112     s->port = 0;
113
114     s->txt_records = NULL;
115
116     AVAHI_LLIST_PREPEND(StaticService, services, group->services, s);
117
118     return s;
119 }
120
121 static StaticServiceGroup *static_service_group_new(gchar *filename) {
122     StaticServiceGroup *g;
123     g_assert(filename);
124
125     g = g_new(StaticServiceGroup, 1);
126     g->filename = g_strdup(filename);
127     g->mtime = 0;
128     g->name = g->chosen_name = NULL;
129     g->replace_wildcards = FALSE;
130     g->entry_group = NULL;
131
132     AVAHI_LLIST_HEAD_INIT(StaticService, g->services);
133     AVAHI_LLIST_PREPEND(StaticServiceGroup, groups, groups, g);
134
135     return g;
136 }
137
138 static void static_service_free(StaticService *s) {
139     g_assert(s);
140     
141     AVAHI_LLIST_REMOVE(StaticService, services, s->group->services, s);
142
143     g_free(s->type);
144     g_free(s->host_name);
145     g_free(s->domain_name);
146
147     avahi_string_list_free(s->txt_records);
148     
149     g_free(s);
150 }
151
152 static void static_service_group_free(StaticServiceGroup *g) {
153     g_assert(g);
154
155     if (g->entry_group)
156         avahi_entry_group_free(g->entry_group);
157
158     while (g->services)
159         static_service_free(g->services);
160
161     AVAHI_LLIST_REMOVE(StaticServiceGroup, groups, groups, g);
162
163     g_free(g->filename);
164     g_free(g->name);
165     g_free(g->chosen_name);
166     g_free(g);
167 }
168
169 static void entry_group_callback(AvahiServer *s, AvahiEntryGroup *eg, AvahiEntryGroupState state, gpointer userdata) {
170     StaticServiceGroup *g = userdata;
171     
172     g_assert(s);
173     g_assert(g);
174     
175     if (state == AVAHI_ENTRY_GROUP_COLLISION) {
176         gchar *n;
177
178         remove_static_service_group_from_server(g);
179
180         n = avahi_alternative_service_name(g->chosen_name);
181         g_free(g->chosen_name);
182         g->chosen_name = n;
183
184         avahi_log_notice("Service name conflict for \"%s\" (%s), retrying with \"%s\".", g->name, g->filename, g->chosen_name);
185
186         add_static_service_group_to_server(g);
187     } else if (state == AVAHI_ENTRY_GROUP_ESTABLISHED)
188         avahi_log_info("Service \"%s\" (%s) successfully established.", g->chosen_name, g->filename);
189 }
190
191 static void add_static_service_group_to_server(StaticServiceGroup *g) {
192     StaticService *s;
193
194     g_assert(g);
195     
196     if (g->entry_group)
197         return;
198     
199     if (g->chosen_name)
200         g_free(g->chosen_name);
201     
202     if (g->replace_wildcards)
203         g->chosen_name = replacestr(g->name, "%h", avahi_server_get_host_name(avahi_server));
204     else
205         g->chosen_name = g_strdup(g->name);
206
207     if (!g->entry_group)
208         g->entry_group = avahi_entry_group_new(avahi_server, entry_group_callback, g);
209
210     g_assert(avahi_entry_group_is_empty(g->entry_group));
211     
212     for (s = g->services; s; s = s->services_next) {
213
214         if (avahi_server_add_service_strlst(
215                 avahi_server,
216                 g->entry_group,
217                 -1, AF_UNSPEC,
218                 g->chosen_name, s->type, 
219                 s->domain_name, s->host_name, s->port,
220                 s->txt_records) < 0) {
221             avahi_log_error("Failed to add service '%s' of type '%s', ignoring service group (%s)", g->chosen_name, s->type, g->filename);
222             remove_static_service_group_from_server(g);
223             return;
224         }
225     }
226
227     avahi_entry_group_commit(g->entry_group);
228 }
229
230 static void remove_static_service_group_from_server(StaticServiceGroup *g) {
231     g_assert(g);
232
233     if (g->entry_group)
234         avahi_entry_group_reset(g->entry_group);
235 }
236
237 typedef enum {
238     XML_TAG_INVALID,
239     XML_TAG_SERVICE_GROUP,
240     XML_TAG_NAME,
241     XML_TAG_SERVICE,
242     XML_TAG_TYPE,
243     XML_TAG_DOMAIN_NAME,
244     XML_TAG_HOST_NAME,
245     XML_TAG_PORT,
246     XML_TAG_TXT_RECORD
247 } xml_tag_name;
248
249 struct xml_userdata {
250     StaticServiceGroup *group;
251     StaticService *service;
252     xml_tag_name current_tag;
253     gboolean failed;
254     gchar *buf;
255 };
256
257 static void XMLCALL xml_start(void *data, const char *el, const char *attr[]) {
258     struct xml_userdata *u = data;
259     
260     g_assert(u);
261
262     if (u->failed)
263         return;
264
265     if (u->current_tag == XML_TAG_INVALID && strcmp(el, "service-group") == 0) {
266
267         if (attr[0])
268             goto invalid_attr;
269
270         u->current_tag = XML_TAG_SERVICE_GROUP;
271     } else if (u->current_tag == XML_TAG_SERVICE_GROUP && strcmp(el, "name") == 0) {
272         u->current_tag = XML_TAG_NAME;
273
274         if (attr[0]) {
275             if (strcmp(attr[0], "replace-wildcards") == 0) 
276                 u->group->replace_wildcards = strcmp(attr[1], "yes") == 0;
277             else
278                 goto invalid_attr;
279         }
280
281         if (attr[2])
282             goto invalid_attr;
283         
284             
285         
286     } else if (u->current_tag == XML_TAG_SERVICE_GROUP && strcmp(el, "service") == 0) {
287         if (attr[0])
288             goto invalid_attr;
289
290         g_assert(!u->service);
291         u->service = static_service_new(u->group);
292
293         u->current_tag = XML_TAG_SERVICE;
294     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "type") == 0) {
295         if (attr[0])
296             goto invalid_attr;
297
298         u->current_tag = XML_TAG_TYPE;
299     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "domain-name") == 0) {
300         if (attr[0])
301             goto invalid_attr;
302         
303         u->current_tag = XML_TAG_DOMAIN_NAME;
304     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "host-name") == 0) {
305         if (attr[0])
306             goto invalid_attr;
307         
308         u->current_tag = XML_TAG_HOST_NAME;
309     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "port") == 0) {
310         if (attr[0])
311             goto invalid_attr;
312         
313         u->current_tag = XML_TAG_PORT;
314     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "txt-record") == 0) {
315         if (attr[0])
316             goto invalid_attr;
317         
318         u->current_tag = XML_TAG_TXT_RECORD;
319     } else {
320         avahi_log_error("%s: parse failure: didn't expect element <%s>.", u->group->filename, el);
321         u->failed = TRUE;
322     }
323
324     return;
325
326 invalid_attr:
327     avahi_log_error("%s: parse failure: invalid attribute for element <%s>.", u->group->filename, el);
328     u->failed = TRUE;
329     return;
330 }
331     
332 static void XMLCALL xml_end(void *data, const char *el) {
333     struct xml_userdata *u = data;
334     g_assert(u);
335
336     if (u->failed)
337         return;
338     
339     switch (u->current_tag) {
340         case XML_TAG_SERVICE_GROUP:
341
342             if (!u->group->name || !u->group->services) {
343                 avahi_log_error("%s: parse failure: service group incomplete.", u->group->filename);
344                 u->failed = TRUE;
345                 return;
346             }
347             
348             u->current_tag = XML_TAG_INVALID;
349             break;
350
351         case XML_TAG_SERVICE:
352
353             if (u->service->port == 0 || !u->service->type) {
354                 avahi_log_error("%s: parse failure: service incomplete.", u->group->filename);
355                 u->failed = TRUE;
356                 return;
357             }
358             
359             u->service = NULL;
360             u->current_tag = XML_TAG_SERVICE_GROUP;
361             break;
362
363         case XML_TAG_NAME:
364             u->current_tag = XML_TAG_SERVICE_GROUP;
365             break;
366
367         case XML_TAG_PORT: {
368             int p;
369             g_assert(u->service);
370             
371             p = u->buf ? atoi(u->buf) : 0;
372
373             if (p <= 0 || p > 0xFFFF) {
374                 avahi_log_error("%s: parse failure: invalid port specification \"%s\".", u->group->filename, u->buf);
375                 u->failed = TRUE;
376                 return;
377             }
378
379             u->service->port = (guint16) p;
380
381             u->current_tag = XML_TAG_SERVICE;
382             break;
383         }
384
385         case XML_TAG_TXT_RECORD: {
386             g_assert(u->service);
387             
388             u->service->txt_records = avahi_string_list_add(u->service->txt_records, u->buf ? u->buf : "");
389             u->current_tag = XML_TAG_SERVICE;
390             break;
391         }
392             
393         case XML_TAG_TYPE:
394         case XML_TAG_DOMAIN_NAME:
395         case XML_TAG_HOST_NAME:
396             u->current_tag = XML_TAG_SERVICE;
397             break;
398
399         case XML_TAG_INVALID:
400             ;
401     }
402
403     g_free(u->buf);
404     u->buf = NULL;
405 }
406
407 static gchar *append_cdata(gchar *t, const gchar *n, int length) {
408     gchar *r, *k;
409     
410     if (!length)
411         return t;
412
413     k = g_strndup(n, length);
414
415     if (t) {
416         r = g_strconcat(t, k, NULL);
417         g_free(k);
418         g_free(t);
419     } else
420         r = k;
421     
422     return r;
423 }
424
425 static void XMLCALL xml_cdata(void *data, const XML_Char *s, int len) {
426     struct xml_userdata *u = data;
427     g_assert(u);
428
429     if (u->failed)
430         return;
431
432     switch (u->current_tag) {
433         case XML_TAG_NAME:
434             u->group->name = append_cdata(u->group->name, s, len);
435             break;
436             
437         case XML_TAG_TYPE:
438             g_assert(u->service);
439             u->service->type = append_cdata(u->service->type, s, len);
440             break;
441
442         case XML_TAG_DOMAIN_NAME:
443             g_assert(u->service);
444             u->service->domain_name = append_cdata(u->service->domain_name, s, len);
445             break;
446
447         case XML_TAG_HOST_NAME:
448             g_assert(u->service);
449             u->service->host_name = append_cdata(u->service->host_name, s, len);
450             break;
451
452         case XML_TAG_PORT:
453         case XML_TAG_TXT_RECORD:
454             u->buf = append_cdata(u->buf, s, len);
455             break;
456
457         case XML_TAG_SERVICE_GROUP:
458         case XML_TAG_SERVICE:
459         case XML_TAG_INVALID:
460             ;
461     }
462 }
463
464 static gint static_service_group_load(StaticServiceGroup *g) {
465     XML_Parser parser = NULL;
466     gint fd = -1;
467     struct xml_userdata u;
468     gint r = -1;
469     struct stat st;
470     ssize_t n;
471
472     g_assert(g);
473
474     u.buf = NULL;
475     u.group = g;
476     u.service = NULL;
477     u.current_tag = XML_TAG_INVALID;
478     u.failed = FALSE;
479
480     /* Cleanup old data in this service group, if available */
481     remove_static_service_group_from_server(g);
482     while (g->services)
483         static_service_free(g->services);
484
485     g_free(g->name);
486     g_free(g->chosen_name);
487     g->name = g->chosen_name = NULL;
488     g->replace_wildcards = FALSE;
489     
490     if (!(parser = XML_ParserCreate(NULL))) {
491         avahi_log_error("XML_ParserCreate() failed.");
492         goto finish;
493     }
494
495     if ((fd = open(g->filename, O_RDONLY)) < 0) {
496         avahi_log_error("open(\"%s\", O_RDONLY): %s", g->filename, strerror(errno));
497         goto finish;
498     }
499
500     if (fstat(fd, &st) < 0) {
501         avahi_log_error("fstat(): %s", strerror(errno));
502         goto finish;
503     }
504
505     g->mtime = st.st_mtime;
506     
507     XML_SetUserData(parser, &u);
508
509     XML_SetElementHandler(parser, xml_start, xml_end);
510     XML_SetCharacterDataHandler(parser, xml_cdata);
511
512     do {
513         void *buffer;
514
515 #define BUFSIZE (10*1024)
516
517         if (!(buffer = XML_GetBuffer(parser, BUFSIZE))) {
518             avahi_log_error("XML_GetBuffer() failed.");
519             goto finish;
520         }
521
522         if ((n = read(fd, buffer, BUFSIZE)) < 0) {
523             avahi_log_error("read(): %s\n", strerror(errno));
524             goto finish;
525         }
526
527         if (!XML_ParseBuffer(parser, n, n == 0)) {
528             avahi_log_error("XML_ParseBuffer() failed at line %d: %s.\n", XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser)));
529             goto finish;
530         }
531
532     } while (n != 0);
533
534     if (!u.failed)
535         r = 0;
536
537 finish:
538
539     if (fd >= 0)
540         close(fd);
541
542     if (parser)
543         XML_ParserFree(parser);
544
545     g_free(u.buf);
546
547     return r;
548 }
549
550 static void load_file(gchar *n) {
551     StaticServiceGroup *g;
552     g_assert(n);
553
554     for (g = groups; g; g = g->groups_next)
555         if (strcmp(g->filename, n) == 0)
556             return;
557
558     avahi_log_info("Loading service file %s", n);
559     
560     g = static_service_group_new(n);
561     if (static_service_group_load(g) < 0) {
562         avahi_log_error("Failed to load service group file %s, ignoring.", g->filename);
563         static_service_group_free(g);
564     }
565 }
566
567 void static_service_load(void) {
568     StaticServiceGroup *g, *n;
569     glob_t globbuf;
570     gchar **p;
571
572     for (g = groups; g; g = n) {
573         struct stat st;
574
575         n = g->groups_next;
576
577         if (stat(g->filename, &st) < 0) {
578
579             if (errno == ENOENT)
580                 avahi_log_info("Service group file %s vanished, removing services.", g->filename);
581             else
582                 avahi_log_warn("Failed to stat() file %s, ignoring: %s", g->filename, strerror(errno));
583             
584             static_service_group_free(g);
585         } else if (st.st_mtime != g->mtime) {
586             avahi_log_info("Service group file %s changed, reloading.", g->filename);
587             
588             if (static_service_group_load(g) < 0) {
589                 avahi_log_warn("Failed to load service group file %s, removing service.", g->filename);
590                 static_service_group_free(g);
591             }
592         }
593     }
594
595     memset(&globbuf, 0, sizeof(globbuf));
596     if (glob(AVAHI_SERVICE_DIR "/*.service", GLOB_ERR, NULL, &globbuf) != 0)
597         avahi_log_error("Failed to read service directory.");
598     else {
599         for (p = globbuf.gl_pathv; *p; p++)
600             load_file(*p);
601         
602         globfree(&globbuf);
603     }
604 }
605
606 void static_service_free_all(void) {
607     
608     while (groups)
609         static_service_group_free(groups);
610 }
611
612 void static_service_add_to_server(void) {
613     StaticServiceGroup *g;
614
615     for (g = groups; g; g = g->groups_next)
616         add_static_service_group_to_server(g);
617 }
618
619 void static_service_remove_from_server(void) {
620     StaticServiceGroup *g;
621
622     for (g = groups; g; g = g->groups_next)
623         remove_static_service_group_from_server(g);
624 }