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