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