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