upload http
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / experimental / util_ldap_cache_mgr.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * util_ldap_cache_mgr.c: LDAP cache manager things
19  * 
20  * Original code from auth_ldap module for Apache v1.3:
21  * Copyright 1998, 1999 Enbridge Pipelines Inc. 
22  * Copyright 1999-2001 Dave Carrigan
23  */
24
25 #include <apr_ldap.h>
26 #include "util_ldap.h"
27 #include "util_ldap_cache.h"
28 #include <apr_strings.h>
29
30 #ifdef APU_HAS_LDAP
31
32 /* only here until strdup is gone */
33 #include <string.h>
34
35 /* here till malloc is gone */
36 #include <stdlib.h>
37
38 static const unsigned long primes[] =
39 {
40   11,
41   19,
42   37,
43   73,
44   109,
45   163,
46   251,
47   367,
48   557,
49   823,
50   1237,
51   1861,
52   2777,
53   4177,
54   6247,
55   9371,
56   14057,
57   21089,
58   31627,
59   47431,
60   71143,
61   106721,
62   160073,
63   240101,
64   360163,
65   540217,
66   810343,
67   1215497,
68   1823231,
69   2734867,
70   4102283,
71   6153409,
72   9230113,
73   13845163,
74   0
75 };
76
77 void util_ald_free(util_ald_cache_t *cache, const void *ptr)
78 {
79 #if APR_HAS_SHARED_MEMORY
80     if (cache->rmm_addr) {
81         if (ptr)
82             /* Free in shared memory */
83             apr_rmm_free(cache->rmm_addr, apr_rmm_offset_get(cache->rmm_addr, (void *)ptr));
84     }
85     else {
86         if (ptr)
87             /* Cache shm is not used */
88             free((void *)ptr);
89     }
90 #else
91     if (ptr)
92         free((void *)ptr);
93 #endif
94 }
95
96 void *util_ald_alloc(util_ald_cache_t *cache, unsigned long size)
97 {
98     if (0 == size)
99         return NULL;
100 #if APR_HAS_SHARED_MEMORY
101     if (cache->rmm_addr) {
102         /* allocate from shared memory */
103         apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, size);
104         return block ? (void *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
105     }
106     else {
107         /* Cache shm is not used */
108         return (void *)calloc(sizeof(char), size);
109     }
110 #else
111     return (void *)calloc(sizeof(char), size);
112 #endif
113 }
114
115 const char *util_ald_strdup(util_ald_cache_t *cache, const char *s)
116 {
117 #if APR_HAS_SHARED_MEMORY
118     if (cache->rmm_addr) {
119         /* allocate from shared memory */
120         apr_rmm_off_t block = apr_rmm_calloc(cache->rmm_addr, strlen(s)+1);
121         char *buf = block ? (char *)apr_rmm_addr_get(cache->rmm_addr, block) : NULL;
122         if (buf) {
123             strcpy(buf, s);
124             return buf;
125         }
126         else {
127             return NULL;
128         }
129     } else {
130         /* Cache shm is not used */
131         return strdup(s);
132     }
133 #else
134     return strdup(s);
135 #endif
136 }
137
138
139 /*
140  * Computes the hash on a set of strings. The first argument is the number
141  * of strings to hash, the rest of the args are strings. 
142  * Algorithm taken from glibc.
143  */
144 unsigned long util_ald_hash_string(int nstr, ...)
145 {
146     int i;
147     va_list args;
148     unsigned long h=0, g;
149     char *str, *p;
150   
151     va_start(args, nstr);
152     for (i=0; i < nstr; ++i) {
153         str = va_arg(args, char *);
154         for (p = str; *p; ++p) {
155             h = ( h << 4 ) + *p;
156             if ( ( g = h & 0xf0000000 ) ) {
157                 h = h ^ (g >> 24);
158                 h = h ^ g;
159             }
160         }
161     }
162     va_end(args);
163
164     return h;
165 }
166
167
168 /*
169   Purges a cache that has gotten full. We keep track of the time that we
170   added the entry that made the cache 3/4 full, then delete all entries
171   that were added before that time. It's pretty simplistic, but time to
172   purge is only O(n), which is more important.
173 */
174 void util_ald_cache_purge(util_ald_cache_t *cache)
175 {
176     unsigned long i;
177     util_cache_node_t *p, *q, **pp;
178     apr_time_t t;
179
180     if (!cache)
181         return;
182   
183     cache->last_purge = apr_time_now();
184     cache->npurged = 0;
185     cache->numpurges++;
186
187     for (i=0; i < cache->size; ++i) {
188         pp = cache->nodes + i;
189         p = *pp;
190         while (p != NULL) {
191             if (p->add_time < cache->marktime) {
192                 q = p->next;
193                 (*cache->free)(cache, p->payload);
194                 util_ald_free(cache, p);
195                 cache->numentries--;
196                 cache->npurged++;
197                 p = *pp = q;
198             }
199             else {
200                 pp = &(p->next);
201                 p = *pp;
202             }
203         }
204     }
205
206     t = apr_time_now();
207     cache->avg_purgetime = 
208          ((t - cache->last_purge) + (cache->avg_purgetime * (cache->numpurges-1))) / 
209          cache->numpurges;
210 }
211
212
213 /*
214  * create caches
215  */
216 util_url_node_t *util_ald_create_caches(util_ldap_state_t *st, const char *url)
217 {
218     util_url_node_t curl, *newcurl = NULL;
219     util_ald_cache_t *search_cache;
220     util_ald_cache_t *compare_cache;
221     util_ald_cache_t *dn_compare_cache;
222
223     /* create the three caches */
224     search_cache = util_ald_create_cache(st,
225                       util_ldap_search_node_hash,
226                       util_ldap_search_node_compare,
227                       util_ldap_search_node_copy,
228                       util_ldap_search_node_free,
229                       util_ldap_search_node_display);
230     compare_cache = util_ald_create_cache(st,
231                       util_ldap_compare_node_hash,
232                       util_ldap_compare_node_compare,
233                       util_ldap_compare_node_copy,
234                       util_ldap_compare_node_free,
235                       util_ldap_compare_node_display);
236     dn_compare_cache = util_ald_create_cache(st,
237                       util_ldap_dn_compare_node_hash,
238                       util_ldap_dn_compare_node_compare,
239                       util_ldap_dn_compare_node_copy,
240                       util_ldap_dn_compare_node_free,
241                       util_ldap_dn_compare_node_display);
242
243     /* check that all the caches initialised successfully */
244     if (search_cache && compare_cache && dn_compare_cache) {
245
246         /* The contents of this structure will be duplicated in shared
247            memory during the insert.  So use stack memory rather than
248            pool memory to avoid a memory leak. */
249         memset (&curl, 0, sizeof(util_url_node_t));
250         curl.url = url;
251         curl.search_cache = search_cache;
252         curl.compare_cache = compare_cache;
253         curl.dn_compare_cache = dn_compare_cache;
254
255         newcurl = util_ald_cache_insert(st->util_ldap_cache, &curl);
256
257     }
258
259     return newcurl;
260 }
261
262
263 util_ald_cache_t *util_ald_create_cache(util_ldap_state_t *st,
264                                 unsigned long (*hashfunc)(void *), 
265                                 int (*comparefunc)(void *, void *),
266                                 void * (*copyfunc)(util_ald_cache_t *cache, void *),
267                                 void (*freefunc)(util_ald_cache_t *cache, void *),
268                                 void (*displayfunc)(request_rec *r, util_ald_cache_t *cache, void *))
269 {
270     util_ald_cache_t *cache;
271     unsigned long i;
272
273     if (st->search_cache_size <= 0)
274         return NULL;
275
276 #if APR_HAS_SHARED_MEMORY
277     if (!st->cache_rmm) {
278         return NULL;
279     }
280     else {
281         apr_rmm_off_t block = apr_rmm_calloc(st->cache_rmm, sizeof(util_ald_cache_t));
282         cache = block ? (util_ald_cache_t *)apr_rmm_addr_get(st->cache_rmm, block) : NULL;
283     }
284 #else
285     cache = (util_ald_cache_t *)calloc(sizeof(util_ald_cache_t), 1);
286 #endif
287     if (!cache)
288         return NULL;
289
290 #if APR_HAS_SHARED_MEMORY
291     cache->rmm_addr = st->cache_rmm;
292     cache->shm_addr = st->cache_shm;
293 #endif
294     cache->maxentries = st->search_cache_size;
295     cache->numentries = 0;
296     cache->size = st->search_cache_size / 3;
297     if (cache->size < 64) cache->size = 64;
298         for (i = 0; primes[i] && primes[i] < cache->size; ++i) ;
299             cache->size = primes[i]? primes[i] : primes[i-1];
300
301     cache->nodes = (util_cache_node_t **)util_ald_alloc(cache, cache->size * sizeof(util_cache_node_t *));
302     if (!cache->nodes) {
303         util_ald_free(cache, cache);
304         return NULL;
305     }
306
307     for (i=0; i < cache->size; ++i)
308         cache->nodes[i] = NULL;
309
310     cache->hash = hashfunc;
311     cache->compare = comparefunc;
312     cache->copy = copyfunc;
313     cache->free = freefunc;
314     cache->display = displayfunc;
315
316     cache->fullmark = cache->maxentries / 4 * 3;
317     cache->marktime = 0;
318     cache->avg_purgetime = 0.0;
319     cache->numpurges = 0;
320     cache->last_purge = 0;
321     cache->npurged = 0;
322
323     cache->fetches = 0;
324     cache->hits = 0;
325     cache->inserts = 0;
326     cache->removes = 0;
327
328     return cache;
329 }
330
331 void util_ald_destroy_cache(util_ald_cache_t *cache)
332 {
333     unsigned long i;
334     util_cache_node_t *p, *q;
335
336     if (cache == NULL)
337         return;
338
339     for (i = 0; i < cache->size; ++i) {
340         p = cache->nodes[i];
341         q = NULL;
342         while (p != NULL) {
343             q = p->next;
344            (*cache->free)(cache, p->payload);
345            util_ald_free(cache, p);
346            p = q;
347         }
348     }
349     util_ald_free(cache, cache->nodes);
350     util_ald_free(cache, cache);
351 }
352
353 void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload)
354 {
355     int hashval;
356     util_cache_node_t *p;
357
358     if (cache == NULL)
359         return NULL;
360
361     cache->fetches++;
362
363     hashval = (*cache->hash)(payload) % cache->size;
364     for (p = cache->nodes[hashval]; 
365          p && !(*cache->compare)(p->payload, payload);
366     p = p->next) ;
367
368     if (p != NULL) {
369         cache->hits++;
370         return p->payload;
371     }
372     else {
373         return NULL;
374     }
375 }
376
377 /*
378  * Insert an item into the cache. 
379  * *** Does not catch duplicates!!! ***
380  */
381 void *util_ald_cache_insert(util_ald_cache_t *cache, void *payload)
382 {
383     int hashval;
384     util_cache_node_t *node;
385
386     /* sanity check */
387     if (cache == NULL || payload == NULL) {
388         return NULL;
389     }
390
391     /* check if we are full - if so, try purge */
392     if (cache->numentries >= cache->maxentries) {
393         util_ald_cache_purge(cache);
394         if (cache->numentries >= cache->maxentries) {
395             /* if the purge was not effective, we leave now to avoid an overflow */
396             return NULL;
397         }
398     }
399
400     /* should be safe to add an entry */
401     if ((node = (util_cache_node_t *)util_ald_alloc(cache, sizeof(util_cache_node_t))) == NULL) {
402         return NULL;
403     }
404
405     /* Take a copy of the payload before proceeeding. */
406     payload = (*cache->copy)(cache, payload);
407     if (!payload) {
408         util_ald_free(cache, node);
409         return NULL;
410     }
411
412     /* populate the entry */
413     cache->inserts++;
414     hashval = (*cache->hash)(payload) % cache->size;
415     node->add_time = apr_time_now();
416     node->payload = payload;
417     node->next = cache->nodes[hashval];
418     cache->nodes[hashval] = node;
419
420     /* if we reach the full mark, note the time we did so
421      * for the benefit of the purge function
422      */
423     if (++cache->numentries == cache->fullmark) {
424         cache->marktime=apr_time_now();
425     }
426
427     return node->payload;
428 }
429
430 void util_ald_cache_remove(util_ald_cache_t *cache, void *payload)
431 {
432     int hashval;
433     util_cache_node_t *p, *q;
434   
435     if (cache == NULL)
436         return;
437
438     cache->removes++;
439     hashval = (*cache->hash)(payload) % cache->size;
440     for (p = cache->nodes[hashval], q=NULL;
441          p && !(*cache->compare)(p->payload, payload);
442          p = p->next) {
443          q = p;
444     }
445
446     /* If p is null, it means that we couldn't find the node, so just return */
447     if (p == NULL)
448         return;
449
450     if (q == NULL) {
451         /* We found the node, and it's the first in the list */
452         cache->nodes[hashval] = p->next;
453     }
454     else {
455         /* We found the node and it's not the first in the list */
456         q->next = p->next;
457     }
458     (*cache->free)(cache, p->payload);
459     util_ald_free(cache, p);
460     cache->numentries--;
461 }
462
463 char *util_ald_cache_display_stats(request_rec *r, util_ald_cache_t *cache, char *name, char *id)
464 {
465     unsigned long i;
466     int totchainlen = 0;
467     int nchains = 0;
468     double chainlen;
469     util_cache_node_t *n;
470     char *buf, *buf2;
471     apr_pool_t *p = r->pool;
472
473     if (cache == NULL) {
474         return "";
475     }
476
477     for (i=0; i < cache->size; ++i) {
478         if (cache->nodes[i] != NULL) {
479             nchains++;
480             for (n = cache->nodes[i];
481                  n != NULL && n != n->next;
482                  n = n->next) {
483                 totchainlen++;
484             }
485         }
486     }
487     chainlen = nchains? (double)totchainlen / (double)nchains : 0;
488
489     if (id) {
490         buf2 = apr_psprintf(p, 
491                  "<a href=\"%s?%s\">%s</a>",
492              r->uri,
493              id,
494              name);
495     }
496     else {
497         buf2 = name;
498     }
499
500     buf = apr_psprintf(p, 
501              "<tr valign='top'>"
502              "<td nowrap>%s</td>"
503              "<td align='right' nowrap>%lu (%.0f%% full)</td>"
504              "<td align='right'>%.1f</td>"
505              "<td align='right'>%lu/%lu</td>"
506              "<td align='right'>%.0f%%</td>"
507              "<td align='right'>%lu/%lu</td>",
508          buf2,
509          cache->numentries, 
510          (double)cache->numentries / (double)cache->maxentries * 100.0,
511          chainlen,
512          cache->hits,
513          cache->fetches,
514          (cache->fetches > 0 ? (double)(cache->hits) / (double)(cache->fetches) * 100.0 : 100.0),
515          cache->inserts,
516          cache->removes);
517
518     if (cache->numpurges) {
519         char str_ctime[APR_CTIME_LEN];
520
521         apr_ctime(str_ctime, cache->last_purge);
522         buf = apr_psprintf(p,
523                  "%s"
524                  "<td align='right'>%lu</td>\n"
525                  "<td align='right' nowrap>%s</td>\n", 
526              buf,
527              cache->numpurges,
528              str_ctime);
529     }
530     else {
531         buf = apr_psprintf(p, 
532                  "%s<td colspan='2' align='center'>(none)</td>\n",
533              buf);
534     }
535
536     buf = apr_psprintf(p, "%s<td align='right'>%.2g</td>\n</tr>", buf, cache->avg_purgetime);
537
538     return buf;
539 }
540
541 char *util_ald_cache_display(request_rec *r, util_ldap_state_t *st)
542 {
543     unsigned long i,j;
544     char *buf, *t1, *t2, *t3;
545     char *id1, *id2, *id3;
546     char *argfmt = "cache=%s&id=%d&off=%d";
547     char *scanfmt = "cache=%4s&id=%u&off=%u%1s";
548     apr_pool_t *pool = r->pool;
549     util_cache_node_t *p = NULL;
550     util_url_node_t *n = NULL;
551
552     util_ald_cache_t *util_ldap_cache = st->util_ldap_cache;
553
554
555     if (!util_ldap_cache) {
556         return "<tr valign='top'><td nowrap colspan=7>Cache has not been enabled/initialised.</td></tr>";
557     }
558
559     if (r->args && strlen(r->args)) {
560         char cachetype[5], lint[2];
561         unsigned int id, off;
562         char date_str[APR_CTIME_LEN+1];
563
564         if ((3 == sscanf(r->args, scanfmt, cachetype, &id, &off, lint)) &&
565             (id < util_ldap_cache->size)) {
566
567             if ((p = util_ldap_cache->nodes[id]) != NULL) {
568                 n = (util_url_node_t *)p->payload;
569                 buf = (char*)n->url;
570             }
571             else {
572                 buf = "";
573             }
574
575             ap_rputs(apr_psprintf(r->pool, 
576                      "<p>\n"
577                      "<table border='0'>\n"
578                      "<tr>\n"
579                      "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name:</b></font></td>"
580                      "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s (%s)</b></font></td>"
581                      "</tr>\n"
582                      "</table>\n</p>\n",
583                  buf,
584                  cachetype[0] == 'm'? "Main" : 
585                                   (cachetype[0] == 's' ? "Search" : 
586                                    (cachetype[0] == 'c' ? "Compares" : "DNCompares"))), r);
587             
588             switch (cachetype[0]) {
589                 case 'm':
590                     if (util_ldap_cache->marktime) {
591                         apr_ctime(date_str, util_ldap_cache->marktime);
592                     }
593                     else
594                         date_str[0] = 0;
595
596                     ap_rputs(apr_psprintf(r->pool, 
597                             "<p>\n"
598                             "<table border='0'>\n"
599                             "<tr>\n"
600                             "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size:</b></font></td>"
601                             "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
602                             "</tr>\n"
603                             "<tr>\n"
604                             "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries:</b></font></td>"
605                             "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
606                             "</tr>\n"
607                             "<tr>\n"
608                             "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries:</b></font></td>"
609                             "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
610                             "</tr>\n"
611                             "<tr>\n"
612                             "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark:</b></font></td>"
613                             "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%ld</b></font></td>"
614                             "</tr>\n"
615                             "<tr>\n"
616                             "<td bgcolor='#000000'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time:</b></font></td>"
617                             "<td bgcolor='#ffffff'><font size='-1' face='Arial,Helvetica' color='#000000'><b>%s</b></font></td>"
618                             "</tr>\n"
619                             "</table>\n</p>\n",
620                         util_ldap_cache->size,
621                         util_ldap_cache->maxentries,
622                         util_ldap_cache->numentries,
623                         util_ldap_cache->fullmark,
624                         date_str), r);
625
626                     ap_rputs("<p>\n"
627                              "<table border='0'>\n"
628                              "<tr bgcolor='#000000'>\n"
629                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP URL</b></font></td>"
630                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Size</b></font></td>"
631                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Max Entries</b></font></td>"
632                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b># Entries</b></font></td>"
633                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark</b></font></td>"
634                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Full Mark Time</b></font></td>"
635                              "</tr>\n", r
636                             );
637                     for (i=0; i < util_ldap_cache->size; ++i) {
638                         for (p = util_ldap_cache->nodes[i]; p != NULL; p = p->next) {
639
640                             (*util_ldap_cache->display)(r, util_ldap_cache, p->payload);
641                         }
642                     }
643                     ap_rputs("</table>\n</p>\n", r);
644                     
645
646                     break;
647                 case 's':
648                     ap_rputs("<p>\n"
649                              "<table border='0'>\n"
650                              "<tr bgcolor='#000000'>\n"
651                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>LDAP Filter</b></font></td>"
652                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>User Name</b></font></td>"
653                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Bind</b></font></td>"
654                              "</tr>\n", r
655                             );
656                     if (n) {
657                         for (i=0; i < n->search_cache->size; ++i) {
658                             for (p = n->search_cache->nodes[i]; p != NULL; p = p->next) {
659     
660                                 (*n->search_cache->display)(r, n->search_cache, p->payload);
661                             }
662                         }
663                     }
664                     ap_rputs("</table>\n</p>\n", r);
665                     break;
666                 case 'c':
667                     ap_rputs("<p>\n"
668                              "<table border='0'>\n"
669                              "<tr bgcolor='#000000'>\n"
670                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>DN</b></font></td>"
671                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Attribute</b></font></td>"
672                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Value</b></font></td>"
673                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Last Compare</b></font></td>"
674                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Result</b></font></td>"
675                              "</tr>\n", r
676                             );
677                     if (n) {
678                         for (i=0; i < n->compare_cache->size; ++i) {
679                             for (p = n->compare_cache->nodes[i]; p != NULL; p = p->next) {
680     
681                                 (*n->compare_cache->display)(r, n->compare_cache, p->payload);
682                             }
683                         }
684                     }
685                     ap_rputs("</table>\n</p>\n", r);
686                     break;
687                 case 'd':
688                     ap_rputs("<p>\n"
689                              "<table border='0'>\n"
690                              "<tr bgcolor='#000000'>\n"
691                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Require DN</b></font></td>"
692                              "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Actual DN</b></font></td>"
693                              "</tr>\n", r
694                             );
695                     if (n) {
696                         for (i=0; i < n->dn_compare_cache->size; ++i) {
697                             for (p = n->dn_compare_cache->nodes[i]; p != NULL; p = p->next) {
698     
699                                 (*n->dn_compare_cache->display)(r, n->dn_compare_cache, p->payload);
700                             }
701                         }
702                     }
703                     ap_rputs("</table>\n</p>\n", r);
704                     break;
705                 default:
706                     break;
707             }
708
709         }
710         else {
711             buf = "";
712         }
713     }
714     else {
715         ap_rputs("<p>\n"
716                  "<table border='0'>\n"
717                  "<tr bgcolor='#000000'>\n"
718                  "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Cache Name</b></font></td>"
719                  "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Entries</b></font></td>"
720                  "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg. Chain Len.</b></font></td>"
721                  "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Hits</b></font></td>"
722                  "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Ins/Rem</b></font></td>"
723                  "<td colspan='2'><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Purges</b></font></td>"
724                  "<td><font size='-1' face='Arial,Helvetica' color='#ffffff'><b>Avg Purge Time</b></font></td>"
725                  "</tr>\n", r
726                 );
727
728
729         id1 = apr_psprintf(pool, argfmt, "main", 0, 0);
730         buf = util_ald_cache_display_stats(r, st->util_ldap_cache, "LDAP URL Cache", id1);
731     
732         for (i=0; i < util_ldap_cache->size; ++i) {
733             for (p = util_ldap_cache->nodes[i],j=0; p != NULL; p = p->next,j++) {
734     
735                 n = (util_url_node_t *)p->payload;
736     
737                 t1 = apr_psprintf(pool, "%s (Searches)", n->url);
738                 t2 = apr_psprintf(pool, "%s (Compares)", n->url);
739                 t3 = apr_psprintf(pool, "%s (DNCompares)", n->url);
740                 id1 = apr_psprintf(pool, argfmt, "srch", i, j);
741                 id2 = apr_psprintf(pool, argfmt, "cmpr", i, j);
742                 id3 = apr_psprintf(pool, argfmt, "dncp", i, j);
743     
744                 buf = apr_psprintf(pool, "%s\n\n"
745                                          "%s\n\n"
746                                          "%s\n\n"
747                                          "%s\n\n",
748                                          buf,
749                                          util_ald_cache_display_stats(r, n->search_cache, t1, id1),
750                                          util_ald_cache_display_stats(r, n->compare_cache, t2, id2),
751                                          util_ald_cache_display_stats(r, n->dn_compare_cache, t3, id3)
752                                   );
753             }
754         }
755         ap_rputs(buf, r);
756         ap_rputs("</table>\n</p>\n", r);
757     }
758
759     return buf;
760 }
761
762 #endif /* APU_HAS_LDAP */