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