]> git.sur5r.net Git - openldap/blob - libraries/libldap/cache.c
The world compiles and links....
[openldap] / libraries / libldap / cache.c
1 /*
2  *  Copyright (c) 1993 The Regents of the University of Michigan.
3  *  All rights reserved.
4  *
5  *  cache.c - local caching support for LDAP
6  */
7
8 #include "portable.h"
9
10
11 #ifndef lint 
12 static char copyright[] = "@(#) Copyright (c) 1993 The Regents of the University of Michigan.\nAll rights reserved.\n";
13 #endif
14
15 #include <stdio.h>
16 #include <stdlib.h>
17
18 #include <ac/socket.h>
19 #include <ac/string.h>
20 #include <ac/time.h>
21
22 #include "lber.h"
23 #include "ldap.h"
24 #include "ldap-int.h"
25
26 #ifndef LDAP_NOCACHE
27
28 static int              cache_hash LDAP_P(( BerElement *ber ));
29 static LDAPMessage      *msg_dup LDAP_P(( LDAPMessage *msg ));
30 static int              request_cmp LDAP_P(( BerElement *req1, BerElement *req2 ));
31 static int              chain_contains_dn LDAP_P(( LDAPMessage *msg, char *dn ));
32 static long             msg_size LDAP_P(( LDAPMessage *msg ));
33 static void             check_cache_memused LDAP_P(( LDAPCache *lc ));
34 static void             uncache_entry_or_req LDAP_P(( LDAP *ld, char *dn, int msgid ));
35
36 #endif
37
38 int
39 ldap_enable_cache( LDAP *ld, long timeout, long maxmem )
40 {
41 #ifndef LDAP_NOCACHE
42         if ( ld->ld_cache == NULLLDCACHE ) {
43                 if (( ld->ld_cache = (LDAPCache *)malloc( sizeof( LDAPCache )))
44                     == NULLLDCACHE ) {
45                         ld->ld_errno = LDAP_NO_MEMORY;
46                         return( -1 );
47                 }
48                 (void) memset( ld->ld_cache, 0, sizeof( LDAPCache ));
49                 ld->ld_cache->lc_memused = sizeof( LDAPCache );
50         }
51
52         ld->ld_cache->lc_timeout = timeout;
53         ld->ld_cache->lc_maxmem = maxmem;
54         check_cache_memused( ld->ld_cache );
55         ld->ld_cache->lc_enabled = 1;
56         return( 0 );
57 #else 
58         return( -1 );
59 #endif
60 }
61
62
63 void
64 ldap_disable_cache( LDAP *ld )
65 {
66 #ifndef LDAP_NOCACHE
67         if ( ld->ld_cache != NULLLDCACHE ) {
68                 ld->ld_cache->lc_enabled = 0;
69         }
70 #endif
71 }
72
73
74
75 void
76 ldap_set_cache_options( LDAP *ld, unsigned long opts )
77 {
78 #ifndef LDAP_NOCACHE
79         if ( ld->ld_cache != NULLLDCACHE ) {
80                 ld->ld_cache->lc_options = opts;
81         }
82 #endif
83 }
84         
85
86 void
87 ldap_destroy_cache( LDAP *ld )
88 {
89 #ifndef LDAP_NOCACHE
90         if ( ld->ld_cache != NULLLDCACHE ) {
91                 ldap_flush_cache( ld );
92                 free( (char *)ld->ld_cache );
93                 ld->ld_cache = NULLLDCACHE;
94         }
95 #endif
96 }
97
98
99 void
100 ldap_flush_cache( LDAP *ld )
101 {
102 #ifndef LDAP_NOCACHE
103         int             i;
104         LDAPMessage     *m, *next;
105
106         Debug( LDAP_DEBUG_TRACE, "ldap_flush_cache\n", 0, 0, 0 );
107
108         if ( ld->ld_cache != NULLLDCACHE ) {
109                 /* delete all requests in the queue */
110                 for ( m = ld->ld_cache->lc_requests; m != NULLMSG; m = next ) {
111                         next = m->lm_next;
112                         ldap_msgfree( m );
113                 }
114                 ld->ld_cache->lc_requests = NULLMSG;
115
116                 /* delete all messages in the cache */
117                 for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) {
118                         for ( m = ld->ld_cache->lc_buckets[ i ];
119                             m != NULLMSG; m = next ) {
120                                 next = m->lm_next;
121                                 ldap_msgfree( m );
122                         }
123                         ld->ld_cache->lc_buckets[ i ] = NULLMSG;
124                 }
125                 ld->ld_cache->lc_memused = sizeof( LDAPCache );
126         }
127 #endif
128 }
129
130
131 void
132 ldap_uncache_request( LDAP *ld, int msgid )
133 {
134 #ifndef LDAP_NOCACHE
135         Debug( LDAP_DEBUG_TRACE, "ldap_uncache_request %d ld_cache %lx\n",
136             msgid, (long) ld->ld_cache, 0 );
137
138         uncache_entry_or_req( ld, NULL, msgid );
139 #endif
140 }
141
142
143 void
144 ldap_uncache_entry( LDAP *ld, char *dn )
145 {
146 #ifndef LDAP_NOCACHE
147         Debug( LDAP_DEBUG_TRACE, "ldap_uncache_entry %s ld_cache %lx\n",
148             dn, (long) ld->ld_cache, 0 );
149
150         uncache_entry_or_req( ld, dn, 0 );
151 #endif
152 }
153
154
155 #ifndef LDAP_NOCACHE
156
157 static void
158 uncache_entry_or_req( LDAP *ld,
159         char *dn,               /* if non-NULL, uncache entry */
160         int msgid )             /* request to uncache (if dn == NULL) */
161 {
162         int             i;
163         LDAPMessage     *m, *prev, *next;
164
165         Debug( LDAP_DEBUG_TRACE,
166             "ldap_uncache_entry_or_req  dn %s  msgid %d  ld_cache %lx\n",
167             dn, msgid, (long) ld->ld_cache );
168
169         if ( ld->ld_cache == NULLLDCACHE ) {
170             return;
171         }
172
173         /* first check the request queue */
174         prev = NULLMSG;
175         for ( m = ld->ld_cache->lc_requests; m != NULLMSG; m = next ) {
176                 next = m->lm_next;
177                 if (( dn != NULL && chain_contains_dn( m, dn )) ||
178                         ( dn == NULL && m->lm_msgid == msgid )) {
179                         if ( prev == NULLMSG ) {
180                                 ld->ld_cache->lc_requests = next;
181                         } else {
182                                 prev->lm_next = next;
183                         }
184                         ld->ld_cache->lc_memused -= msg_size( m );
185                         ldap_msgfree( m );
186                 } else {
187                         prev = m;
188                 }
189         }
190
191         /* now check the rest of the cache */
192         for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) {
193                 prev = NULLMSG;
194                 for ( m = ld->ld_cache->lc_buckets[ i ]; m != NULLMSG;
195                     m = next ) {
196                         next = m->lm_next;
197                         if (( dn != NULL && chain_contains_dn( m, dn )) ||
198                                 ( dn == NULL && m->lm_msgid == msgid )) {
199                                 if ( prev == NULLMSG ) {
200                                         ld->ld_cache->lc_buckets[ i ] = next;
201                                 } else {
202                                         prev->lm_next = next;
203                                 }
204                                 ld->ld_cache->lc_memused -= msg_size( m );
205                                 ldap_msgfree( m );
206                         } else {
207                                 prev = m;
208                         }
209                 }
210         }
211 }
212
213 #endif
214
215 void
216 ldap_add_request_to_cache( LDAP *ld, unsigned long msgtype, BerElement *request )
217 {
218 #ifndef LDAP_NOCACHE
219         LDAPMessage     *new;
220         long            len;
221
222         Debug( LDAP_DEBUG_TRACE, "ldap_add_request_to_cache\n", 0, 0, 0 );
223
224         ld->ld_errno = LDAP_SUCCESS;
225         if ( ld->ld_cache == NULLLDCACHE ||
226             ( ld->ld_cache->lc_enabled == 0 )) {
227                 return;
228         }
229
230         if (( new = (LDAPMessage *) calloc( 1, sizeof(LDAPMessage) ))
231             != NULL ) {
232                 if (( new->lm_ber = ldap_alloc_ber_with_options( ld )) == NULLBER ) {
233                         free( (char *)new );
234                         return;
235                 }
236                 len = request->ber_ptr - request->ber_buf;
237                 if (( new->lm_ber->ber_buf = (char *) malloc( (size_t)len ))
238                     == NULL ) {
239                         ber_free( new->lm_ber, 0 );
240                         free( (char *)new );
241                         ld->ld_errno = LDAP_NO_MEMORY;
242                         return;
243                 }
244                 SAFEMEMCPY( new->lm_ber->ber_buf, request->ber_buf,
245                     (size_t)len );
246                 new->lm_ber->ber_ptr = new->lm_ber->ber_buf;
247                 new->lm_ber->ber_end = new->lm_ber->ber_buf + len;
248                 new->lm_msgid = ld->ld_msgid;
249                 new->lm_msgtype = msgtype;;
250                 new->lm_next = ld->ld_cache->lc_requests;
251                 ld->ld_cache->lc_requests = new;
252         } else {
253                 ld->ld_errno = LDAP_NO_MEMORY;
254         }
255 #endif
256 }
257
258
259 void
260 ldap_add_result_to_cache( LDAP *ld, LDAPMessage *result )
261 {
262 #ifndef LDAP_NOCACHE
263         LDAPMessage     *m, **mp, *req, *new, *prev;
264         int             err, keep;
265
266         Debug( LDAP_DEBUG_TRACE, "ldap_add_result_to_cache: id %d, type %d\n", 
267                 result->lm_msgid, result->lm_msgtype, 0 );
268
269         if ( ld->ld_cache == NULLLDCACHE ||
270             ( ld->ld_cache->lc_enabled == 0 )) {
271                 Debug( LDAP_DEBUG_TRACE, "artc: cache disabled\n", 0, 0, 0 );
272                 return;
273         }
274
275         if ( result->lm_msgtype != LDAP_RES_SEARCH_ENTRY &&
276             result->lm_msgtype != LDAP_RES_SEARCH_RESULT &&
277             result->lm_msgtype != LDAP_RES_COMPARE ) {
278                 /*
279                  * only cache search and compare operations
280                  */
281                 Debug( LDAP_DEBUG_TRACE,
282                     "artc: only caching search & compare operations\n", 0, 0, 0 );
283                 return;
284         }
285
286         /*
287          * if corresponding request is in the lc_requests list, add this
288          * result to it.  if this result completes the results for the
289          * request, add the request/result chain to the cache proper.
290          */
291         prev = NULLMSG;
292         for ( m = ld->ld_cache->lc_requests; m != NULL; m = m->lm_next ) {
293                 if ( m->lm_msgid == result->lm_msgid ) {
294                         break;
295                 }
296                 prev = m;
297         }
298
299         if ( m != NULLMSG ) {   /* found request; add to end of chain */
300                 req = m;
301                 for ( ; m->lm_chain != NULLMSG; m = m->lm_chain )
302                         ;
303                 if (( new = msg_dup( result )) != NULLMSG ) {
304                         new->lm_chain = NULLMSG;
305                         m->lm_chain = new;
306                         Debug( LDAP_DEBUG_TRACE,
307                             "artc: result added to cache request chain\n",
308                             0, 0, 0 );
309                 }
310                 if ( result->lm_msgtype == LDAP_RES_SEARCH_RESULT ||
311                     result->lm_msgtype == LDAP_RES_COMPARE ) {
312                         /*
313                          * this result completes the chain of results
314                          * add to cache proper if appropriate
315                          */
316                         keep = 0;       /* pessimistic */
317                         err = ldap_result2error( ld, result, 0 );
318                         if ( err == LDAP_SUCCESS ||
319                             ( result->lm_msgtype == LDAP_RES_COMPARE &&
320                             ( err == LDAP_COMPARE_FALSE ||
321                             err == LDAP_COMPARE_TRUE ||
322                             err == LDAP_NO_SUCH_ATTRIBUTE ))) {
323                                 keep = 1;
324                         }
325
326                         if ( ld->ld_cache->lc_options == 0 ) {
327                                 if ( err == LDAP_SIZELIMIT_EXCEEDED ) {
328                                     keep = 1;
329                                 }
330                         } else if (( ld->ld_cache->lc_options &
331                                 LDAP_CACHE_OPT_CACHEALLERRS ) != 0 ) {
332                                 keep = 1;
333                         }
334
335                         if ( prev == NULLMSG ) {
336                                 ld->ld_cache->lc_requests = req->lm_next;
337                         } else {
338                                 prev->lm_next = req->lm_next;
339                         }
340
341                         if ( !keep ) {
342                                 Debug( LDAP_DEBUG_TRACE,
343                                     "artc: not caching result with error %d\n",
344                                     err, 0, 0 );
345                                 ldap_msgfree( req );
346                         } else {
347                                 mp = &ld->ld_cache->lc_buckets[
348                                     cache_hash( req->lm_ber ) ];
349                                 req->lm_next = *mp;
350                                 *mp = req;
351                                 req->lm_time = (long) time( NULL );
352                                 ld->ld_cache->lc_memused += msg_size( req );
353                                 check_cache_memused( ld->ld_cache );
354                                 Debug( LDAP_DEBUG_TRACE,
355                                     "artc: cached result with error %d\n",
356                                     err, 0, 0 );
357                         }
358                 }
359         } else {
360                 Debug( LDAP_DEBUG_TRACE, "artc: msgid not in request list\n",
361                     0, 0, 0 );
362         }
363 #endif
364 }
365
366
367 /*
368  * look in the cache for this request
369  * return 0 if found, -1 if not
370  * if found, the corresponding result messages are added to the incoming
371  * queue with the correct (new) msgid so that subsequent ldap_result calls
372  * will find them.
373  */
374 int
375 ldap_check_cache( LDAP *ld, unsigned long msgtype, BerElement *request )
376 {
377 #ifndef LDAP_NOCACHE
378         LDAPMessage     *m, *new, *prev, *next;
379         BerElement      reqber;
380         int             first, hash;
381         unsigned long   validtime;
382
383         Debug( LDAP_DEBUG_TRACE, "ldap_check_cache\n", 0, 0, 0 );
384
385         if ( ld->ld_cache == NULLLDCACHE ||
386             ( ld->ld_cache->lc_enabled == 0 )) {
387                 return( -1 );
388         }
389
390         reqber.ber_buf = reqber.ber_ptr = request->ber_buf;
391         reqber.ber_end = request->ber_ptr;
392
393         validtime = (long)time( NULL ) - ld->ld_cache->lc_timeout;
394
395         prev = NULLMSG;
396         hash = cache_hash( &reqber );
397         for ( m = ld->ld_cache->lc_buckets[ hash ]; m != NULLMSG; m = next ) {
398                 Debug( LDAP_DEBUG_TRACE,"cc: examining id %d,type %d\n",
399                     m->lm_msgid, m->lm_msgtype, 0 );
400                 if ( m->lm_time < validtime ) {
401                         /* delete expired message */
402                         next = m->lm_next;
403                         if ( prev == NULL ) {
404                                 ld->ld_cache->lc_buckets[ hash ] = next;
405                         } else {
406                                 prev->lm_next = next;
407                         }
408                         Debug( LDAP_DEBUG_TRACE, "cc: expired id %d\n",
409                             m->lm_msgid, 0, 0 );
410                         ld->ld_cache->lc_memused -= msg_size( m );
411                         ldap_msgfree( m );
412                 } else {
413                     if ( m->lm_msgtype == (int)msgtype &&
414                         request_cmp( m->lm_ber, &reqber ) == 0 ) {
415                             break;
416                     }
417                     next = m->lm_next;
418                     prev = m;
419                 }
420         }
421
422         if ( m == NULLMSG ) {
423                 return( -1 );
424         }
425
426         /*
427          * add duplicates of responses to incoming queue
428          */
429         first = 1;
430         for ( m = m->lm_chain; m != NULLMSG; m = m->lm_chain ) {
431                 if (( new = msg_dup( m )) == NULLMSG ) {
432                         return( -1 );
433                 }
434
435                 new->lm_msgid = ld->ld_msgid;
436                 new->lm_chain = NULLMSG;
437                 if ( first ) {
438                         new->lm_next = ld->ld_responses;
439                         ld->ld_responses = new;
440                         first = 0;
441                 } else {
442                         prev->lm_chain = new;
443                 }
444                 prev = new;
445                 Debug( LDAP_DEBUG_TRACE, "cc: added type %d\n",
446                     new->lm_msgtype, 0, 0 );
447         }
448
449         Debug( LDAP_DEBUG_TRACE, "cc: result returned from cache\n", 0, 0, 0 );
450         return( 0 );
451 #else
452         return( -1 );
453 #endif
454 }
455
456 #ifndef LDAP_NOCACHE
457
458 static int
459 cache_hash( BerElement *ber )
460 {
461         BerElement      bercpy;
462         unsigned long   len;
463
464         /*
465          * just take the length of the packet and mod with # of buckets
466          */
467         bercpy = *ber;
468         if ( ber_skip_tag( &bercpy, &len ) == LBER_ERROR
469                 || ber_scanf( &bercpy, "x" ) == LBER_ERROR ) {
470             len = 0;    /* punt: just return zero */
471         } else {
472             len = bercpy.ber_end - bercpy.ber_ptr;
473         }
474
475         Debug( LDAP_DEBUG_TRACE, "cache_hash: len is %ld, returning %ld\n",
476             len, len % LDAP_CACHE_BUCKETS, 0 );
477         return( (int) ( len % LDAP_CACHE_BUCKETS ));
478 }
479
480
481 static LDAPMessage *
482 msg_dup( LDAPMessage *msg )
483 {
484         LDAPMessage     *new;
485         long            len;
486
487         if (( new = (LDAPMessage *)malloc( sizeof(LDAPMessage))) != NULL ) {
488                 *new = *msg;    /* struct copy */
489                 if (( new->lm_ber = ber_dup( msg->lm_ber )) == NULLBER ) {
490                         free( (char *)new );
491                         return( NULLMSG );
492                 }
493                 len = msg->lm_ber->ber_end - msg->lm_ber->ber_buf;
494                 if (( new->lm_ber->ber_buf = (char *) malloc(
495                     (size_t)len )) == NULL ) {
496                         ber_free( new->lm_ber, 0 );
497                         free( (char *)new );
498                         return( NULLMSG );
499                 }
500                 SAFEMEMCPY( new->lm_ber->ber_buf, msg->lm_ber->ber_buf,
501                     (size_t)len );
502                 new->lm_ber->ber_ptr = new->lm_ber->ber_buf +
503                         ( msg->lm_ber->ber_ptr - msg->lm_ber->ber_buf );
504                 new->lm_ber->ber_end = new->lm_ber->ber_buf + len;
505         }
506
507         return( new );
508 }
509
510
511 static int
512 request_cmp( BerElement *req1, BerElement *req2 )
513 {
514         unsigned long   len;
515         BerElement      r1, r2;
516
517         r1 = *req1;     /* struct copies */
518         r2 = *req2;
519
520         /*
521          * skip the enclosing tags (sequence markers) and the msg ids
522          */
523         if ( ber_skip_tag( &r1, &len ) == LBER_ERROR || ber_scanf( &r1, "x" )
524             == LBER_ERROR ) {
525             return( -1 );
526         }
527         if ( ber_skip_tag( &r2, &len ) == LBER_ERROR || ber_scanf( &r2, "x" ) 
528             == LBER_ERROR ) {
529             return( -1 );
530         }
531
532         /*
533          * check remaining length and bytes if necessary
534          */
535         if (( len = r1.ber_end - r1.ber_ptr ) !=
536                 (unsigned long) (r2.ber_end - r2.ber_ptr) )
537         {
538                 return( -1 );   /* different lengths */
539         }
540         return( memcmp( r1.ber_ptr, r2.ber_ptr, (size_t)len ));
541 }       
542
543
544 static int
545 chain_contains_dn( LDAPMessage *msg, char *dn )
546 {
547         LDAPMessage     *m;
548         BerElement      ber;
549         long            msgid;
550         char            *s;
551         int             rc;
552
553
554         /*
555          * first check the base or dn of the request
556          */
557         ber = *msg->lm_ber;     /* struct copy */
558         if ( ber_scanf( &ber, "{i{a", &msgid, &s ) != LBER_ERROR ) {
559             rc = ( strcasecmp( dn, s ) == 0 ) ? 1 : 0;
560             free( s );
561             if ( rc != 0 ) {
562                 return( rc );
563             }
564         }
565
566         if ( msg->lm_msgtype == LDAP_REQ_COMPARE ) {
567                 return( 0 );
568         }
569
570         /*
571          * now check the dn of each search result
572          */
573         rc = 0;
574         for ( m = msg->lm_chain; m != NULLMSG && rc == 0 ; m = m->lm_chain ) {
575                 if ( m->lm_msgtype != LDAP_RES_SEARCH_ENTRY ) {
576                         continue;
577                 }
578                 ber = *m->lm_ber;       /* struct copy */
579                 if ( ber_scanf( &ber, "{a", &s ) != LBER_ERROR ) {
580                         rc = ( strcasecmp( dn, s ) == 0 ) ? 1 : 0;
581                         free( s );
582                 }
583         }
584
585         return( rc );
586 }
587
588
589 static long
590 msg_size( LDAPMessage *msg )
591 {
592         LDAPMessage     *m;
593         long            size;
594
595         size = 0;
596         for ( m = msg; m != NULLMSG; m = m->lm_chain ) {
597                 size += ( sizeof( LDAPMessage ) + m->lm_ber->ber_end -
598                     m->lm_ber->ber_buf );
599         }
600
601         return( size );
602 }
603
604
605 #define THRESHOLD_FACTOR        3 / 4
606 #define SIZE_FACTOR             2 / 3
607
608 static void
609 check_cache_memused( LDAPCache *lc )
610 {
611 /*
612  * this routine is called to check if the cache is too big (lc_maxmem >
613  * minimum cache size and lc_memused > lc_maxmem).  If too big, it reduces
614  * the cache size to < SIZE_FACTOR * lc_maxmem. The algorithm is as follows:
615  *    remove_threshold = lc_timeout seconds;
616  *    do {
617  *        remove everything older than remove_threshold seconds;
618  *        remove_threshold = remove_threshold * THRESHOLD_FACTOR;
619  *    } while ( cache size is > SIZE_FACTOR * lc_maxmem )
620  */
621         int             i;
622         unsigned long   remove_threshold, validtime;
623         LDAPMessage     *m, *prev, *next;
624
625         Debug( LDAP_DEBUG_TRACE, "check_cache_memused: %ld bytes in use (%ld max)\n",
626             lc->lc_memused, lc->lc_maxmem, 0 );
627
628         if ( lc->lc_maxmem <= sizeof( LDAPCache )
629             || lc->lc_memused <= lc->lc_maxmem * SIZE_FACTOR ) {
630                 return;
631         }
632
633         remove_threshold = lc->lc_timeout;
634         while ( lc->lc_memused > lc->lc_maxmem * SIZE_FACTOR ) {
635                 validtime = (long)time( NULL ) - remove_threshold;
636                 for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) {
637                         prev = NULLMSG;
638                         for ( m = lc->lc_buckets[ i ]; m != NULLMSG;
639                             m = next ) {
640                                 next = m->lm_next;
641                                 if ( m->lm_time < validtime ) {
642                                         if ( prev == NULLMSG ) {
643                                                 lc->lc_buckets[ i ] = next;
644                                         } else {
645                                                 prev->lm_next = next;
646                                         }
647                                         lc->lc_memused -= msg_size( m );
648                                         Debug( LDAP_DEBUG_TRACE,
649                                             "ccm: removed %d\n",
650                                             m->lm_msgid, 0, 0 );
651                                         ldap_msgfree( m );
652                                 } else {
653                                         prev = m;
654                                 }
655                         }
656                 }
657                 remove_threshold *= THRESHOLD_FACTOR;
658         }
659
660         Debug( LDAP_DEBUG_TRACE, "ccm: reduced usage to %ld bytes\n",
661             lc->lc_memused, 0, 0 );
662 }
663
664 #endif /* !NO_CACHE */