]> git.meshlink.io Git - catta/blob - avahi-gobject/ga-entry-group.c
fix avahi_netlink_new to allow multiple netlinks per process
[catta] / avahi-gobject / ga-entry-group.c
1 /*
2  * ga-entry-group.c - Source for GaEntryGroup
3  * Copyright (C) 2006-2007 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <avahi-common/malloc.h>
29
30 #include "ga-error.h"
31 #include "ga-entry-group.h"
32 #include "ga-entry-group-enumtypes.h"
33
34 G_DEFINE_TYPE(GaEntryGroup, ga_entry_group, G_TYPE_OBJECT)
35
36 static void _free_service(gpointer data);
37
38 /* signal enum */
39 enum {
40     STATE_CHANGED,
41     LAST_SIGNAL
42 };
43
44 static guint signals[LAST_SIGNAL] = { 0 };
45
46 /* properties */
47 enum {
48     PROP_STATE = 1
49 };
50
51 /* private structures */
52 typedef struct _GaEntryGroupPrivate GaEntryGroupPrivate;
53
54 struct _GaEntryGroupPrivate {
55     GaEntryGroupState state;
56     GaClient *client;
57     AvahiEntryGroup *group;
58     GHashTable *services;
59     gboolean dispose_has_run;
60 };
61
62 typedef struct _GaEntryGroupServicePrivate GaEntryGroupServicePrivate;
63
64 struct _GaEntryGroupServicePrivate {
65     GaEntryGroupService public;
66     GaEntryGroup *group;
67     gboolean frozen;
68     GHashTable *entries;
69 };
70
71 typedef struct _GaEntryGroupServiceEntry GaEntryGroupServiceEntry;
72
73 struct _GaEntryGroupServiceEntry {
74     guint8 *value;
75     gsize size;
76 };
77
78
79 #define GA_ENTRY_GROUP_GET_PRIVATE(o)     (G_TYPE_INSTANCE_GET_PRIVATE ((o), GA_TYPE_ENTRY_GROUP, GaEntryGroupPrivate))
80
81 static void ga_entry_group_init(GaEntryGroup * obj) {
82     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(obj);
83     /* allocate any data required by the object here */
84     priv->state = GA_ENTRY_GROUP_STATE_UNCOMMITED;
85     priv->client = NULL;
86     priv->group = NULL;
87     priv->services = g_hash_table_new_full(g_direct_hash,
88                                            g_direct_equal,
89                                            NULL, _free_service);
90 }
91
92 static void ga_entry_group_dispose(GObject * object);
93 static void ga_entry_group_finalize(GObject * object);
94
95 static void ga_entry_group_get_property(GObject * object,
96                             guint property_id,
97                             GValue * value, GParamSpec * pspec) {
98     GaEntryGroup *group = GA_ENTRY_GROUP(object);
99     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
100
101     switch (property_id) {
102         case PROP_STATE:
103             g_value_set_enum(value, priv->state);
104             break;
105         default:
106             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
107             break;
108     }
109 }
110
111 static void ga_entry_group_class_init(GaEntryGroupClass * ga_entry_group_class) {
112     GObjectClass *object_class = G_OBJECT_CLASS(ga_entry_group_class);
113     GParamSpec *param_spec;
114
115     g_type_class_add_private(ga_entry_group_class,
116                              sizeof (GaEntryGroupPrivate));
117
118     object_class->dispose = ga_entry_group_dispose;
119     object_class->finalize = ga_entry_group_finalize;
120     object_class->get_property = ga_entry_group_get_property;
121
122     param_spec = g_param_spec_enum("state", "Entry Group state",
123                                    "The state of the avahi entry group",
124                                    GA_TYPE_ENTRY_GROUP_STATE,
125                                    GA_ENTRY_GROUP_STATE_UNCOMMITED,
126                                    G_PARAM_READABLE |
127                                    G_PARAM_STATIC_NAME |
128                                    G_PARAM_STATIC_BLURB);
129     g_object_class_install_property(object_class, PROP_STATE, param_spec);
130
131     signals[STATE_CHANGED] =
132             g_signal_new("state-changed",
133                          G_OBJECT_CLASS_TYPE(ga_entry_group_class),
134                          G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
135                          0,
136                          NULL, NULL,
137                          g_cclosure_marshal_VOID__ENUM,
138                          G_TYPE_NONE, 1, GA_TYPE_ENTRY_GROUP_STATE);
139 }
140
141 void ga_entry_group_dispose(GObject * object) {
142     GaEntryGroup *self = GA_ENTRY_GROUP(object);
143     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
144
145     if (priv->dispose_has_run)
146         return;
147     priv->dispose_has_run = TRUE;
148
149     /* release any references held by the object here */
150     if (priv->group) {
151         avahi_entry_group_free(priv->group);
152         priv->group = NULL;
153     }
154
155     if (priv->client) {
156         g_object_unref(priv->client);
157         priv->client = NULL;
158     }
159
160     if (G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose)
161         G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose(object);
162 }
163
164 void ga_entry_group_finalize(GObject * object) {
165     GaEntryGroup *self = GA_ENTRY_GROUP(object);
166     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
167
168     /* free any data held directly by the object here */
169     g_hash_table_destroy(priv->services);
170     priv->services = NULL;
171
172     G_OBJECT_CLASS(ga_entry_group_parent_class)->finalize(object);
173 }
174
175 static void _free_service(gpointer data) {
176     GaEntryGroupService *s = (GaEntryGroupService *) data;
177     GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) s;
178     g_free(s->name);
179     g_free(s->type);
180     g_free(s->domain);
181     g_free(s->host);
182     g_hash_table_destroy(p->entries);
183     g_free(s);
184 }
185
186 static GQuark detail_for_state(AvahiEntryGroupState state) {
187     static struct {
188         AvahiEntryGroupState state;
189         const gchar *name;
190         GQuark quark;
191     } states[] = {
192         { AVAHI_ENTRY_GROUP_UNCOMMITED, "uncommited", 0},
193         { AVAHI_ENTRY_GROUP_REGISTERING, "registering", 0},
194         { AVAHI_ENTRY_GROUP_ESTABLISHED, "established", 0},
195         { AVAHI_ENTRY_GROUP_COLLISION, "collistion", 0},
196         { AVAHI_ENTRY_GROUP_FAILURE, "failure", 0},
197         { 0, NULL, 0}
198     };
199     int i;
200
201     for (i = 0; states[i].name != NULL; i++) {
202         if (state != states[i].state)
203             continue;
204
205         if (!states[i].quark)
206             states[i].quark = g_quark_from_static_string(states[i].name);
207         return states[i].quark;
208     }
209     g_assert_not_reached();
210 }
211
212 static void _avahi_entry_group_cb(AvahiEntryGroup * g,
213                       AvahiEntryGroupState state, void *data) {
214     GaEntryGroup *self = GA_ENTRY_GROUP(data);
215     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
216
217     /* Avahi can call the callback before return from _client_new */
218     if (priv->group == NULL)
219         priv->group = g;
220
221     g_assert(g == priv->group);
222     priv->state = state;
223     g_signal_emit(self, signals[STATE_CHANGED],
224                   detail_for_state(state), state);
225 }
226
227 GaEntryGroup *ga_entry_group_new(void) {
228     return g_object_new(GA_TYPE_ENTRY_GROUP, NULL);
229 }
230
231 static guint _entry_hash(gconstpointer v) {
232     const GaEntryGroupServiceEntry *entry =
233             (const GaEntryGroupServiceEntry *) v;
234     guint32 h = 0;
235     guint i;
236
237     for (i = 0; i < entry->size; i++) {
238         h = (h << 5) - h + entry->value[i];
239     }
240
241     return h;
242 }
243
244 static gboolean _entry_equal(gconstpointer a, gconstpointer b) {
245     const GaEntryGroupServiceEntry *aentry =
246             (const GaEntryGroupServiceEntry *) a;
247     const GaEntryGroupServiceEntry *bentry =
248             (const GaEntryGroupServiceEntry *) b;
249
250     if (aentry->size != bentry->size) {
251         return FALSE;
252     }
253
254     return memcmp(aentry->value, bentry->value, aentry->size) == 0;
255 }
256
257 static GaEntryGroupServiceEntry *_new_entry(const guint8 * value, gsize size) {
258     GaEntryGroupServiceEntry *entry;
259
260     if (value == NULL) {
261         return NULL;
262     }
263
264     entry = g_slice_new(GaEntryGroupServiceEntry);
265     entry->value = g_malloc(size + 1);
266     memcpy(entry->value, value, size);
267     /* for string keys, make sure it's NUL-terminated too */
268     entry->value[size] = 0;
269     entry->size = size;
270
271     return entry;
272 }
273
274 static void _set_entry(GHashTable * table, const guint8 * key, gsize ksize,
275            const guint8 * value, gsize vsize) {
276
277     g_hash_table_insert(table, _new_entry(key, ksize),
278                         _new_entry(value, vsize));
279 }
280
281 static void _free_entry(gpointer data) {
282     GaEntryGroupServiceEntry *entry = (GaEntryGroupServiceEntry *) data;
283
284     if (entry == NULL) {
285         return;
286     }
287
288     g_free(entry->value);
289     g_slice_free(GaEntryGroupServiceEntry, entry);
290 }
291
292 static GHashTable *_string_list_to_hash(AvahiStringList * list) {
293     GHashTable *ret;
294     AvahiStringList *t;
295
296     ret = g_hash_table_new_full(_entry_hash,
297                                 _entry_equal, _free_entry, _free_entry);
298
299     for (t = list; t != NULL; t = avahi_string_list_get_next(t)) {
300         gchar *key;
301         gchar *value;
302         gsize size;
303         int r;
304
305         /* list_get_pair only fails if if memory allocation fails. Normal glib
306          * behaviour is to assert/abort when that happens */
307         r = avahi_string_list_get_pair(t, &key, &value, &size);
308         g_assert(r == 0);
309
310         if (value == NULL) {
311             _set_entry(ret, t->text, t->size, NULL, 0);
312         } else {
313             _set_entry(ret, (const guint8 *) key, strlen(key),
314                        (const guint8 *) value, size);
315         }
316         avahi_free(key);
317         avahi_free(value);
318     }
319     return ret;
320 }
321
322 static void _hash_to_string_list_foreach(gpointer key, gpointer value, gpointer data) {
323     AvahiStringList **list = (AvahiStringList **) data;
324     GaEntryGroupServiceEntry *kentry = (GaEntryGroupServiceEntry *) key;
325     GaEntryGroupServiceEntry *ventry = (GaEntryGroupServiceEntry *) value;
326
327     if (value != NULL) {
328         *list = avahi_string_list_add_pair_arbitrary(*list,
329                                                      (gchar *) kentry->value,
330                                                      ventry->value,
331                                                      ventry->size);
332     } else {
333         *list = avahi_string_list_add_arbitrary(*list,
334                                                 kentry->value, kentry->size);
335     }
336 }
337
338 static AvahiStringList *_hash_to_string_list(GHashTable * table) {
339     AvahiStringList *list = NULL;
340     g_hash_table_foreach(table, _hash_to_string_list_foreach,
341                          (gpointer) & list);
342     return list;
343 }
344
345 GaEntryGroupService *ga_entry_group_add_service_strlist(GaEntryGroup * group,
346                                                         const gchar * name,
347                                                         const gchar * type,
348                                                         guint16 port,
349                                                         GError ** error,
350                                                         AvahiStringList *
351                                                         txt) {
352     return ga_entry_group_add_service_full_strlist(group, AVAHI_IF_UNSPEC,
353                                                    AVAHI_PROTO_UNSPEC, 0,
354                                                    name, type, NULL, NULL,
355                                                    port, error, txt);
356 }
357
358 GaEntryGroupService *ga_entry_group_add_service_full_strlist(GaEntryGroup *
359                                                              group,
360                                                              AvahiIfIndex
361                                                              interface,
362                                                              AvahiProtocol
363                                                              protocol,
364                                                              AvahiPublishFlags
365                                                              flags,
366                                                              const gchar *
367                                                              name,
368                                                              const gchar *
369                                                              type,
370                                                              const gchar *
371                                                              domain,
372                                                              const gchar *
373                                                              host,
374                                                              guint16 port,
375                                                              GError ** error,
376                                                              AvahiStringList *
377                                                              txt) {
378     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
379     GaEntryGroupServicePrivate *service = NULL;
380     int ret;
381
382     ret = avahi_entry_group_add_service_strlst(priv->group,
383                                                interface, protocol,
384                                                flags,
385                                                name, type,
386                                                domain, host, port, txt);
387     if (ret) {
388         if (error != NULL) {
389             *error = g_error_new(GA_ERROR, ret,
390                                  "Adding service to group failed: %s",
391                                  avahi_strerror(ret));
392         }
393         goto out;
394     }
395
396     service = g_new0(GaEntryGroupServicePrivate, 1);
397     service->public.interface = interface;
398     service->public.protocol = protocol;
399     service->public.flags = flags;
400     service->public.name = g_strdup(name);
401     service->public.type = g_strdup(type);
402     service->public.domain = g_strdup(domain);
403     service->public.host = g_strdup(host);
404     service->public.port = port;
405     service->group = group;
406     service->frozen = FALSE;
407     service->entries = _string_list_to_hash(txt);
408     g_hash_table_insert(priv->services, group, service);
409   out:
410     return (GaEntryGroupService *) service;
411 }
412
413 GaEntryGroupService *ga_entry_group_add_service(GaEntryGroup * group,
414                                                 const gchar * name,
415                                                 const gchar * type,
416                                                 guint16 port,
417                                                 GError ** error, ...) {
418     GaEntryGroupService *ret;
419     AvahiStringList *txt = NULL;
420     va_list va;
421     va_start(va, error);
422     txt = avahi_string_list_new_va(va);
423
424     ret = ga_entry_group_add_service_full_strlist(group,
425                                                   AVAHI_IF_UNSPEC,
426                                                   AVAHI_PROTO_UNSPEC,
427                                                   0,
428                                                   name, type,
429                                                   NULL, NULL,
430                                                   port, error, txt);
431     avahi_string_list_free(txt);
432     va_end(va);
433     return ret;
434 }
435
436 GaEntryGroupService *ga_entry_group_add_service_full(GaEntryGroup * group,
437                                                      AvahiIfIndex interface,
438                                                      AvahiProtocol protocol,
439                                                      AvahiPublishFlags flags,
440                                                      const gchar * name,
441                                                      const gchar * type,
442                                                      const gchar * domain,
443                                                      const gchar * host,
444                                                      guint16 port,
445                                                      GError ** error, ...) {
446     GaEntryGroupService *ret;
447     AvahiStringList *txt = NULL;
448     va_list va;
449
450     va_start(va, error);
451     txt = avahi_string_list_new_va(va);
452
453     ret = ga_entry_group_add_service_full_strlist(group,
454                                                   interface, protocol,
455                                                   flags,
456                                                   name, type,
457                                                   domain, host,
458                                                   port, error, txt);
459     avahi_string_list_free(txt);
460     va_end(va);
461     return ret;
462 }
463
464 gboolean ga_entry_group_add_record(GaEntryGroup * group,
465                           AvahiPublishFlags flags,
466                           const gchar * name,
467                           guint16 type,
468                           guint32 ttl,
469                           const void *rdata, gsize size, GError ** error) {
470     return ga_entry_group_add_record_full(group,
471                                           AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
472                                           flags, name, AVAHI_DNS_CLASS_IN,
473                                           type, ttl, rdata, size, error);
474 }
475
476 gboolean ga_entry_group_add_record_full(GaEntryGroup * group,
477                                AvahiIfIndex interface,
478                                AvahiProtocol protocol,
479                                AvahiPublishFlags flags,
480                                const gchar * name,
481                                guint16 clazz,
482                                guint16 type,
483                                guint32 ttl,
484                                const void *rdata,
485                                gsize size, GError ** error) {
486     int ret;
487     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
488     g_assert(group != NULL && priv->group != NULL);
489
490     ret = avahi_entry_group_add_record(priv->group, interface, protocol,
491                                        flags, name, clazz, type, ttl, rdata,
492                                        size);
493     if (ret) {
494         if (error != NULL) {
495             *error = g_error_new(GA_ERROR, ret,
496                                  "Setting raw record failed: %s",
497                                  avahi_strerror(ret));
498         }
499         return FALSE;
500     }
501     return TRUE;
502 }
503
504
505 void ga_entry_group_service_freeze(GaEntryGroupService * service) {
506     GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) service;
507     p->frozen = TRUE;
508 }
509
510 gboolean ga_entry_group_service_thaw(GaEntryGroupService * service, GError ** error) {
511     GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
512     int ret;
513     gboolean result = TRUE;
514
515     AvahiStringList *txt = _hash_to_string_list(priv->entries);
516     ret = avahi_entry_group_update_service_txt_strlst
517             (GA_ENTRY_GROUP_GET_PRIVATE(priv->group)->group,
518              service->interface, service->protocol, service->flags,
519              service->name, service->type, service->domain, txt);
520     if (ret) {
521         if (error != NULL) {
522             *error = g_error_new(GA_ERROR, ret,
523                                  "Updating txt record failed: %s",
524                                  avahi_strerror(ret));
525         }
526         result = FALSE;
527     }
528
529     avahi_string_list_free(txt);
530     priv->frozen = FALSE;
531     return result;
532 }
533
534 gboolean ga_entry_group_service_set(GaEntryGroupService * service,
535                            const gchar * key, const gchar * value,
536                            GError ** error) {
537     return ga_entry_group_service_set_arbitrary(service, key,
538                                                 (const guint8 *) value,
539                                                 strlen(value), error);
540
541 }
542
543 gboolean ga_entry_group_service_set_arbitrary(GaEntryGroupService * service,
544                                      const gchar * key, const guint8 * value,
545                                      gsize size, GError ** error) {
546     GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
547
548     _set_entry(priv->entries, (const guint8 *) key, strlen(key), value, size);
549
550     if (!priv->frozen)
551         return ga_entry_group_service_thaw(service, error);
552     else
553         return TRUE;
554 }
555
556 gboolean ga_entry_group_service_remove_key(GaEntryGroupService * service,
557                                   const gchar * key, GError ** error) {
558     GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
559     GaEntryGroupServiceEntry entry;
560
561     entry.value = (void*) key;
562     entry.size = strlen(key);
563
564     g_hash_table_remove(priv->entries, &entry);
565
566     if (!priv->frozen)
567         return ga_entry_group_service_thaw(service, error);
568     else
569         return TRUE;
570 }
571
572
573 gboolean ga_entry_group_attach(GaEntryGroup * group,
574                       GaClient * client, GError ** error) {
575     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
576
577     g_return_val_if_fail(client->avahi_client, FALSE);
578     g_assert(priv->client == NULL || priv->client == client);
579     g_assert(priv->group == NULL);
580
581     priv->client = client;
582     g_object_ref(client);
583
584     priv->group = avahi_entry_group_new(client->avahi_client,
585                                         _avahi_entry_group_cb, group);
586     if (priv->group == NULL) {
587         if (error != NULL) {
588             int aerrno = avahi_client_errno(client->avahi_client);
589             *error = g_error_new(GA_ERROR, aerrno,
590                                  "Attaching group failed: %s",
591                                  avahi_strerror(aerrno));
592         }
593         return FALSE;
594     }
595     return TRUE;
596 }
597
598 gboolean ga_entry_group_commit(GaEntryGroup * group, GError ** error) {
599     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
600     int ret;
601     ret = avahi_entry_group_commit(priv->group);
602     if (ret) {
603         if (error != NULL) {
604             *error = g_error_new(GA_ERROR, ret,
605                                  "Committing group failed: %s",
606                                  avahi_strerror(ret));
607         }
608         return FALSE;
609     }
610     return TRUE;
611 }
612
613 gboolean ga_entry_group_reset(GaEntryGroup * group, GError ** error) {
614     GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
615     int ret;
616     ret = avahi_entry_group_reset(priv->group);
617     if (ret) {
618         if (error != NULL) {
619             *error = g_error_new(GA_ERROR, ret,
620                                  "Resetting group failed: %s",
621                                  avahi_strerror(ret));
622         }
623         return FALSE;
624     }
625     return TRUE;
626 }