]> git.meshlink.io Git - catta/blob - libavahi-core/cache.c
autotoolize
[catta] / libavahi-core / cache.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 #include <string.h>
23
24 #include "util.h"
25 #include "cache.h"
26
27 static void remove_entry(AvahiCache *c, AvahiCacheEntry *e) {
28     AvahiCacheEntry *t;
29
30     g_assert(c);
31     g_assert(e);
32
33 /*     g_message("removing from cache: %p %p", c, e); */
34
35     /* Remove from hash table */
36     t = g_hash_table_lookup(c->hash_table, e->record->key);
37     AVAHI_LLIST_REMOVE(AvahiCacheEntry, by_key, t, e);
38     if (t)
39         g_hash_table_replace(c->hash_table, t->record->key, t);
40     else
41         g_hash_table_remove(c->hash_table, e->record->key);
42
43     /* Remove from linked list */
44     AVAHI_LLIST_REMOVE(AvahiCacheEntry, entry, c->entries, e);
45         
46     if (e->time_event)
47         avahi_time_event_queue_remove(c->server->time_event_queue, e->time_event);
48
49     avahi_subscription_notify(c->server, c->interface, e->record, AVAHI_SUBSCRIPTION_REMOVE);
50
51     avahi_record_unref(e->record);
52     
53     g_free(e);
54 }
55
56 AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *iface) {
57     AvahiCache *c;
58     g_assert(server);
59
60     c = g_new(AvahiCache, 1);
61     c->server = server;
62     c->interface = iface;
63     c->hash_table = g_hash_table_new((GHashFunc) avahi_key_hash, (GEqualFunc) avahi_key_equal);
64
65     AVAHI_LLIST_HEAD_INIT(AvahiCacheEntry, c->entries);
66     
67     return c;
68 }
69
70 void avahi_cache_free(AvahiCache *c) {
71     g_assert(c);
72
73     while (c->entries)
74         remove_entry(c, c->entries);
75     
76     g_hash_table_destroy(c->hash_table);
77     
78     g_free(c);
79 }
80
81 AvahiCacheEntry *avahi_cache_lookup_key(AvahiCache *c, AvahiKey *k) {
82     g_assert(c);
83     g_assert(k);
84
85     g_assert(!avahi_key_is_pattern(k));
86     
87     return g_hash_table_lookup(c->hash_table, k);
88 }
89
90 gpointer avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, gpointer userdata) {
91     gpointer ret;
92     
93     g_assert(c);
94     g_assert(pattern);
95     g_assert(cb);
96     
97     if (avahi_key_is_pattern(pattern)) {
98         AvahiCacheEntry *e, *n;
99         
100         for (e = c->entries; e; e = n) {
101             n = e->entry_next;
102             
103             if (avahi_key_pattern_match(pattern, e->record->key))
104                 if ((ret = cb(c, pattern, e, userdata)))
105                     return ret;
106         }
107         
108     } else {
109         AvahiCacheEntry *e, *n;
110
111         for (e = avahi_cache_lookup_key(c, pattern); e; e = n) {
112             n = e->by_key_next;
113                 
114             if ((ret = cb(c, pattern, e, userdata)))
115                 return ret;
116         }
117     }
118
119     return NULL;
120 }
121
122 static gpointer lookup_record_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) {
123     g_assert(c);
124     g_assert(pattern);
125     g_assert(e);
126
127     if (avahi_record_equal_no_ttl(e->record, userdata))
128         return e;
129
130     return NULL;
131 }
132
133 AvahiCacheEntry *avahi_cache_lookup_record(AvahiCache *c, AvahiRecord *r) {
134     g_assert(c);
135     g_assert(r);
136
137     return avahi_cache_walk(c, r->key, lookup_record_callback, r);
138 }
139
140 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, guint percent);
141
142 static void elapse_func(AvahiTimeEvent *t, void *userdata) {
143     AvahiCacheEntry *e = userdata;
144     
145     g_assert(t);
146     g_assert(e);
147
148     if (e->state == AVAHI_CACHE_FINAL) {
149         remove_entry(e->cache, e);
150         g_message("Removing entry from cache due to expiration");
151     } else {
152         guint percent = 0;
153     
154         switch (e->state) {
155             case AVAHI_CACHE_VALID:
156                 e->state = AVAHI_CACHE_EXPIRY1;
157                 percent = 85;
158                 break;
159                 
160             case AVAHI_CACHE_EXPIRY1:
161                 e->state = AVAHI_CACHE_EXPIRY2;
162                 percent = 90;
163                 break;
164             case AVAHI_CACHE_EXPIRY2:
165                 e->state = AVAHI_CACHE_EXPIRY3;
166                 percent = 95;
167                 break;
168                 
169             case AVAHI_CACHE_EXPIRY3:
170                 e->state = AVAHI_CACHE_FINAL;
171                 percent = 100;
172                 break;
173
174             default:
175                 ;
176         }
177
178         g_assert(percent > 0);
179
180         g_message("Requesting cache entry update at %i%%.", percent);
181
182         /* Request a cache update, if we are subscribed to this entry */
183         if (avahi_is_subscribed(e->cache->server, e->record->key))
184             avahi_interface_post_query(e->cache->interface, e->record->key, TRUE);
185
186         /* Check again later */
187         next_expiry(e->cache, e, percent);
188     }
189 }
190
191 static void update_time_event(AvahiCache *c, AvahiCacheEntry *e) {
192     g_assert(c);
193     g_assert(e);
194     
195     if (e->time_event)
196         avahi_time_event_queue_update(c->server->time_event_queue, e->time_event, &e->expiry);
197     else
198         e->time_event = avahi_time_event_queue_add(c->server->time_event_queue, &e->expiry, elapse_func, e);
199 }
200
201 static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, guint percent) {
202     gulong usec;
203
204     g_assert(c);
205     g_assert(e);
206     g_assert(percent > 0 && percent <= 100);
207
208     e->expiry = e->timestamp;
209
210     usec = e->record->ttl * 10000;
211
212     /* 2% jitter */
213     usec = g_random_int_range(usec*percent, usec*(percent+2));
214     
215     g_time_val_add(&e->expiry, usec);
216     update_time_event(c, e);
217 }
218
219 void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const AvahiAddress *a) {
220     AvahiCacheEntry *e, *t;
221     gchar *txt;
222     
223     g_assert(c);
224     g_assert(r && r->ref >= 1);
225
226     g_message("cache update: %s", (txt = avahi_record_to_string(r)));
227     g_free(txt);
228
229     if (r->ttl == 0) {
230
231         /* This is a goodbye request */
232
233         if ((e = avahi_cache_lookup_record(c, r))) {
234
235             e->state = AVAHI_CACHE_FINAL;
236             g_get_current_time(&e->timestamp);
237             e->expiry = e->timestamp;
238             g_time_val_add(&e->expiry, 1000000); /* 1s */
239             update_time_event(c, e);
240         }
241
242     } else {
243
244         /* This is an update request */
245
246         if ((t = e = avahi_cache_lookup_key(c, r->key))) {
247         
248             if (unique) {
249                 
250                 /* For unique records, remove all entries but one */
251                 while (e->by_key_next)
252                     remove_entry(c, e->by_key_next);
253                 
254             } else {
255                 
256                 /* For non-unique record, look for exactly the same entry */
257                 for (; e; e = e->by_key_next)
258                     if (avahi_record_equal_no_ttl(e->record, r))
259                         break;
260             }
261         }
262     
263         if (e) {
264             
265 /*         g_message("found matching cache entry"); */
266             
267             /* We are the first in the linked list so let's replace the hash table key with the new one */
268             if (e->by_key_prev == NULL)
269                 g_hash_table_replace(c->hash_table, r->key, e);
270             
271             /* Notify subscribers */
272             if (!avahi_record_equal_no_ttl(e->record, r))
273                 avahi_subscription_notify(c->server, c->interface, r, AVAHI_SUBSCRIPTION_CHANGE);    
274             
275             /* Update the record */
276             avahi_record_unref(e->record);
277             e->record = avahi_record_ref(r);
278             
279         } else {
280             /* No entry found, therefore we create a new one */
281             
282 /*         g_message("couldn't find matching cache entry"); */
283             
284             e = g_new(AvahiCacheEntry, 1);
285             e->cache = c;
286             e->time_event = NULL;
287             e->record = avahi_record_ref(r);
288
289             /* Append to hash table */
290             AVAHI_LLIST_PREPEND(AvahiCacheEntry, by_key, t, e);
291             g_hash_table_replace(c->hash_table, e->record->key, t);
292
293             /* Append to linked list */
294             AVAHI_LLIST_PREPEND(AvahiCacheEntry, entry, c->entries, e);
295
296             /* Notify subscribers */
297             avahi_subscription_notify(c->server, c->interface, e->record, AVAHI_SUBSCRIPTION_NEW);
298         } 
299         
300         e->origin = *a;
301         g_get_current_time(&e->timestamp);
302         next_expiry(c, e, 80);
303         e->state = AVAHI_CACHE_VALID;
304     }
305 }
306
307 static gpointer drop_key_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, gpointer userdata) {
308     g_assert(c);
309     g_assert(pattern);
310     g_assert(e);
311
312     remove_entry(c, e);
313     return NULL;
314 }
315
316 void avahi_cache_drop_key(AvahiCache *c, AvahiKey *k) {
317     g_assert(c);
318     g_assert(k);
319
320     avahi_cache_walk(c, k, drop_key_callback, NULL);
321 }
322
323 void avahi_cache_drop_record(AvahiCache *c, AvahiRecord *r) {
324     AvahiCacheEntry *e;
325     
326     g_assert(c);
327     g_assert(r);
328
329     if ((e = avahi_cache_lookup_record(c, r))) 
330         remove_entry(c, e);
331 }
332
333 static void func(gpointer key, gpointer data, gpointer userdata) {
334     AvahiCacheEntry *e = data;
335     AvahiKey *k = key;
336     gchar *t;
337
338     g_assert(k);
339     g_assert(e);
340     
341     t = avahi_record_to_string(e->record);
342     fprintf((FILE*) userdata, "%s\n", t);
343     g_free(t);
344 }
345
346 void avahi_cache_dump(AvahiCache *c, FILE *f) {
347     g_assert(c);
348     g_assert(f);
349
350     fprintf(f, ";;; CACHE DUMP FOLLOWS ;;;\n");
351     g_hash_table_foreach(c->hash_table, func, f);
352 }
353
354 gboolean avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) {
355     GTimeVal now;
356     guint age;
357     
358     g_assert(c);
359     g_assert(e);
360
361     g_get_current_time(&now);
362
363     age = avahi_timeval_diff(&now, &e->timestamp)/1000000;
364
365     g_message("age: %u, ttl/2: %u", age, e->record->ttl);
366     
367     return age >= e->record->ttl/2;
368 }