]> git.meshlink.io Git - catta/blob - avahi-core/cache.c
* allow caller to specifiy additional CFLAGS arguments to bootstrap.sh
[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     AvahiUsec usec;
222 /*     gchar *txt; */
223
224     usec = ((AvahiUsec) e->record->ttl) * 10000;
225
226     /* 2% jitter */
227     usec = (AvahiUsec) g_random_double_range((gdouble) (usec*percent), (gdouble) (usec*(percent+2)));
228 /*     g_message("next expiry: %lli (%s)", usec / 1000000, txt = avahi_record_to_string(e->record)); */
229 /*     g_free(txt); */
230     
231     e->expiry = e->timestamp;
232     avahi_timeval_add(&e->expiry, usec);
233     
234 /*     g_message("wake up in +%lu seconds", e->expiry.tv_sec - e->timestamp.tv_sec); */
235     
236     update_time_event(c, e);
237 }
238
239 static void expire_in_one_second(AvahiCache *c, AvahiCacheEntry *e) {
240     g_assert(c);
241     g_assert(e);
242     
243     e->state = AVAHI_CACHE_FINAL;
244     g_get_current_time(&e->expiry);
245     avahi_timeval_add(&e->expiry, 1000000); /* 1s */
246     update_time_event(c, e);
247 }
248
249 void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean cache_flush, const AvahiAddress *a) {
250 /*     gchar *txt; */
251     
252     g_assert(c);
253     g_assert(r && r->ref >= 1);
254
255 /*     txt = avahi_record_to_string(r); */
256
257     if (r->ttl == 0) {
258         /* This is a goodbye request */
259
260         AvahiCacheEntry *e;
261
262         if ((e = avahi_cache_lookup_record(c, r)))
263             expire_in_one_second(c, e);
264
265     } else {
266         AvahiCacheEntry *e = NULL, *first;
267         GTimeVal now;
268
269         g_get_current_time(&now);
270
271         /* This is an update request */
272
273         if ((first = avahi_cache_lookup_key(c, r->key))) {
274             
275             if (cache_flush) {
276
277                 /* For unique entries drop all entries older than one second */
278                 for (e = first; e; e = e->by_key_next) {
279                     AvahiUsec t;
280
281                     t = avahi_timeval_diff(&now, &e->timestamp);
282
283                     if (t > 1000000)
284                         expire_in_one_second(c, e);
285                 }
286             }
287                 
288             /* Look for exactly the same entry */
289             for (e = first; e; e = e->by_key_next)
290                 if (avahi_record_equal_no_ttl(e->record, r))
291                     break;
292         }
293     
294         if (e) {
295             
296 /*             avahi_log_debug("found matching cache entry");  */
297
298             /* We need to update the hash table key if we replace the
299              * record */
300             if (e->by_key_prev == NULL)
301                 g_hash_table_replace(c->hash_table, r->key, e);
302             
303             /* Update the record */
304             avahi_record_unref(e->record);
305             e->record = avahi_record_ref(r);
306
307 /*             avahi_log_debug("cache: updating %s", txt);   */
308             
309         } else {
310             /* No entry found, therefore we create a new one */
311             
312 /*             avahi_log_debug("cache: couldn't find matching cache entry for %s", txt);   */
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 /*     g_free(txt);  */
343 }
344
345 struct dump_data {
346     AvahiDumpCallback callback;
347     gpointer userdata;
348 };
349
350 static void dump_callback(gpointer key, gpointer data, gpointer userdata) {
351     AvahiCacheEntry *e = data;
352     AvahiKey *k = key;
353     struct dump_data *dump_data = userdata;
354
355     g_assert(k);
356     g_assert(e);
357     g_assert(data);
358
359     for (; e; e = e->by_key_next) {
360         gchar *t = avahi_record_to_string(e->record);
361         dump_data->callback(t, dump_data->userdata);
362         g_free(t);
363     }
364 }
365
366 void avahi_cache_dump(AvahiCache *c, AvahiDumpCallback callback, gpointer userdata) {
367     struct dump_data data;
368
369     g_assert(c);
370     g_assert(callback);
371
372     callback(";;; CACHE DUMP FOLLOWS ;;;", userdata);
373
374     data.callback = callback;
375     data.userdata = userdata;
376
377     g_hash_table_foreach(c->hash_table, dump_callback, &data);
378 }
379
380 gboolean avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) {
381     GTimeVal now;
382     AvahiUsec age;
383     
384     g_assert(c);
385     g_assert(e);
386
387     g_get_current_time(&now);
388
389     age = avahi_timeval_diff(&now, &e->timestamp)/1000000;
390
391 /*     avahi_log_debug("age: %lli, ttl/2: %u", age, e->record->ttl);  */
392     
393     return age >= e->record->ttl/2;
394 }
395
396 void avahi_cache_flush(AvahiCache *c) {
397     g_assert(c);
398
399     while (c->entries)
400         remove_entry(c, c->entries);
401 }