2 * Copyright (c) 1993 The Regents of the University of Michigan.
5 * cache.c - local caching support for LDAP
13 static char copyright[] = "@(#) Copyright (c) 1993 The Regents of the University of Michigan.\nAll rights reserved.\n";
19 #include <ac/socket.h>
20 #include <ac/string.h>
27 static int cache_hash LDAP_P(( BerElement *ber ));
28 static LDAPMessage *msg_dup LDAP_P(( LDAPMessage *msg ));
29 static int request_cmp LDAP_P(( BerElement *req1, BerElement *req2 ));
30 static int chain_contains_dn LDAP_P(( LDAPMessage *msg, char *dn ));
31 static long msg_size LDAP_P(( LDAPMessage *msg ));
32 static void check_cache_memused LDAP_P(( LDAPCache *lc ));
33 static void uncache_entry_or_req LDAP_P(( LDAP *ld, char *dn, int msgid ));
36 ldap_enable_cache( LDAP *ld, long timeout, long maxmem )
38 if ( ld->ld_cache == NULLLDCACHE ) {
39 if (( ld->ld_cache = (LDAPCache *)malloc( sizeof( LDAPCache )))
41 ld->ld_errno = LDAP_NO_MEMORY;
44 (void) memset( ld->ld_cache, 0, sizeof( LDAPCache ));
45 ld->ld_cache->lc_memused = sizeof( LDAPCache );
48 ld->ld_cache->lc_timeout = timeout;
49 ld->ld_cache->lc_maxmem = maxmem;
50 check_cache_memused( ld->ld_cache );
51 ld->ld_cache->lc_enabled = 1;
57 ldap_disable_cache( LDAP *ld )
59 if ( ld->ld_cache != NULLLDCACHE ) {
60 ld->ld_cache->lc_enabled = 0;
67 ldap_set_cache_options( LDAP *ld, unsigned long opts )
69 if ( ld->ld_cache != NULLLDCACHE ) {
70 ld->ld_cache->lc_options = opts;
76 ldap_destroy_cache( LDAP *ld )
78 if ( ld->ld_cache != NULLLDCACHE ) {
79 ldap_flush_cache( ld );
80 free( (char *)ld->ld_cache );
81 ld->ld_cache = NULLLDCACHE;
87 ldap_flush_cache( LDAP *ld )
90 LDAPMessage *m, *next;
92 Debug( LDAP_DEBUG_TRACE, "ldap_flush_cache\n", 0, 0, 0 );
94 if ( ld->ld_cache != NULLLDCACHE ) {
95 /* delete all requests in the queue */
96 for ( m = ld->ld_cache->lc_requests; m != NULLMSG; m = next ) {
100 ld->ld_cache->lc_requests = NULLMSG;
102 /* delete all messages in the cache */
103 for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) {
104 for ( m = ld->ld_cache->lc_buckets[ i ];
105 m != NULLMSG; m = next ) {
109 ld->ld_cache->lc_buckets[ i ] = NULLMSG;
111 ld->ld_cache->lc_memused = sizeof( LDAPCache );
117 ldap_uncache_request( LDAP *ld, int msgid )
119 Debug( LDAP_DEBUG_TRACE, "ldap_uncache_request %d ld_cache %lx\n",
120 msgid, (long) ld->ld_cache, 0 );
122 uncache_entry_or_req( ld, NULL, msgid );
127 ldap_uncache_entry( LDAP *ld, char *dn )
129 Debug( LDAP_DEBUG_TRACE, "ldap_uncache_entry %s ld_cache %lx\n",
130 dn, (long) ld->ld_cache, 0 );
132 uncache_entry_or_req( ld, dn, 0 );
137 uncache_entry_or_req( LDAP *ld,
138 char *dn, /* if non-NULL, uncache entry */
139 int msgid ) /* request to uncache (if dn == NULL) */
142 LDAPMessage *m, *prev, *next;
144 Debug( LDAP_DEBUG_TRACE,
145 "ldap_uncache_entry_or_req dn %s msgid %d ld_cache %lx\n",
146 dn, msgid, (long) ld->ld_cache );
148 if ( ld->ld_cache == NULLLDCACHE ) {
152 /* first check the request queue */
154 for ( m = ld->ld_cache->lc_requests; m != NULLMSG; m = next ) {
156 if (( dn != NULL && chain_contains_dn( m, dn )) ||
157 ( dn == NULL && m->lm_msgid == msgid )) {
158 if ( prev == NULLMSG ) {
159 ld->ld_cache->lc_requests = next;
161 prev->lm_next = next;
163 ld->ld_cache->lc_memused -= msg_size( m );
170 /* now check the rest of the cache */
171 for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) {
173 for ( m = ld->ld_cache->lc_buckets[ i ]; m != NULLMSG;
176 if (( dn != NULL && chain_contains_dn( m, dn )) ||
177 ( dn == NULL && m->lm_msgid == msgid )) {
178 if ( prev == NULLMSG ) {
179 ld->ld_cache->lc_buckets[ i ] = next;
181 prev->lm_next = next;
183 ld->ld_cache->lc_memused -= msg_size( m );
194 ldap_add_request_to_cache( LDAP *ld, unsigned long msgtype, BerElement *request )
199 Debug( LDAP_DEBUG_TRACE, "ldap_add_request_to_cache\n", 0, 0, 0 );
201 ld->ld_errno = LDAP_SUCCESS;
202 if ( ld->ld_cache == NULLLDCACHE ||
203 ( ld->ld_cache->lc_enabled == 0 )) {
207 if (( new = (LDAPMessage *) calloc( 1, sizeof(LDAPMessage) ))
209 if (( new->lm_ber = ldap_alloc_ber_with_options( ld )) == NULLBER ) {
213 len = request->ber_ptr - request->ber_buf;
214 if (( new->lm_ber->ber_buf = (char *) malloc( (size_t)len ))
216 ber_free( new->lm_ber, 0 );
218 ld->ld_errno = LDAP_NO_MEMORY;
221 SAFEMEMCPY( new->lm_ber->ber_buf, request->ber_buf,
223 new->lm_ber->ber_ptr = new->lm_ber->ber_buf;
224 new->lm_ber->ber_end = new->lm_ber->ber_buf + len;
225 new->lm_msgid = ld->ld_msgid;
226 new->lm_msgtype = msgtype;;
227 new->lm_next = ld->ld_cache->lc_requests;
228 ld->ld_cache->lc_requests = new;
230 ld->ld_errno = LDAP_NO_MEMORY;
236 ldap_add_result_to_cache( LDAP *ld, LDAPMessage *result )
238 LDAPMessage *m, **mp, *req, *new, *prev;
241 Debug( LDAP_DEBUG_TRACE, "ldap_add_result_to_cache: id %d, type %d\n",
242 result->lm_msgid, result->lm_msgtype, 0 );
244 if ( ld->ld_cache == NULLLDCACHE ||
245 ( ld->ld_cache->lc_enabled == 0 )) {
246 Debug( LDAP_DEBUG_TRACE, "artc: cache disabled\n", 0, 0, 0 );
250 if ( result->lm_msgtype != LDAP_RES_SEARCH_ENTRY &&
251 result->lm_msgtype != LDAP_RES_SEARCH_RESULT &&
252 result->lm_msgtype != LDAP_RES_COMPARE ) {
254 * only cache search and compare operations
256 Debug( LDAP_DEBUG_TRACE,
257 "artc: only caching search & compare operations\n", 0, 0, 0 );
262 * if corresponding request is in the lc_requests list, add this
263 * result to it. if this result completes the results for the
264 * request, add the request/result chain to the cache proper.
267 for ( m = ld->ld_cache->lc_requests; m != NULL; m = m->lm_next ) {
268 if ( m->lm_msgid == result->lm_msgid ) {
274 if ( m != NULLMSG ) { /* found request; add to end of chain */
276 for ( ; m->lm_chain != NULLMSG; m = m->lm_chain )
278 if (( new = msg_dup( result )) != NULLMSG ) {
279 new->lm_chain = NULLMSG;
281 Debug( LDAP_DEBUG_TRACE,
282 "artc: result added to cache request chain\n",
285 if ( result->lm_msgtype == LDAP_RES_SEARCH_RESULT ||
286 result->lm_msgtype == LDAP_RES_COMPARE ) {
288 * this result completes the chain of results
289 * add to cache proper if appropriate
291 keep = 0; /* pessimistic */
292 err = ldap_result2error( ld, result, 0 );
293 if ( err == LDAP_SUCCESS ||
294 ( result->lm_msgtype == LDAP_RES_COMPARE &&
295 ( err == LDAP_COMPARE_FALSE ||
296 err == LDAP_COMPARE_TRUE ||
297 err == LDAP_NO_SUCH_ATTRIBUTE ))) {
301 if ( ld->ld_cache->lc_options == 0 ) {
302 if ( err == LDAP_SIZELIMIT_EXCEEDED ) {
305 } else if (( ld->ld_cache->lc_options &
306 LDAP_CACHE_OPT_CACHEALLERRS ) != 0 ) {
310 if ( prev == NULLMSG ) {
311 ld->ld_cache->lc_requests = req->lm_next;
313 prev->lm_next = req->lm_next;
317 Debug( LDAP_DEBUG_TRACE,
318 "artc: not caching result with error %d\n",
322 mp = &ld->ld_cache->lc_buckets[
323 cache_hash( req->lm_ber ) ];
326 req->lm_time = (long) time( NULL );
327 ld->ld_cache->lc_memused += msg_size( req );
328 check_cache_memused( ld->ld_cache );
329 Debug( LDAP_DEBUG_TRACE,
330 "artc: cached result with error %d\n",
335 Debug( LDAP_DEBUG_TRACE, "artc: msgid not in request list\n",
342 * look in the cache for this request
343 * return 0 if found, -1 if not
344 * if found, the corresponding result messages are added to the incoming
345 * queue with the correct (new) msgid so that subsequent ldap_result calls
349 ldap_check_cache( LDAP *ld, unsigned long msgtype, BerElement *request )
351 LDAPMessage *m, *new, *prev, *next;
354 unsigned long validtime;
356 Debug( LDAP_DEBUG_TRACE, "ldap_check_cache\n", 0, 0, 0 );
358 if ( ld->ld_cache == NULLLDCACHE ||
359 ( ld->ld_cache->lc_enabled == 0 )) {
363 reqber.ber_buf = reqber.ber_ptr = request->ber_buf;
364 reqber.ber_end = request->ber_ptr;
366 validtime = (long)time( NULL ) - ld->ld_cache->lc_timeout;
369 hash = cache_hash( &reqber );
370 for ( m = ld->ld_cache->lc_buckets[ hash ]; m != NULLMSG; m = next ) {
371 Debug( LDAP_DEBUG_TRACE,"cc: examining id %d,type %d\n",
372 m->lm_msgid, m->lm_msgtype, 0 );
373 if ( m->lm_time < validtime ) {
374 /* delete expired message */
376 if ( prev == NULL ) {
377 ld->ld_cache->lc_buckets[ hash ] = next;
379 prev->lm_next = next;
381 Debug( LDAP_DEBUG_TRACE, "cc: expired id %d\n",
383 ld->ld_cache->lc_memused -= msg_size( m );
386 if ( m->lm_msgtype == (int)msgtype &&
387 request_cmp( m->lm_ber, &reqber ) == 0 ) {
395 if ( m == NULLMSG ) {
400 * add duplicates of responses to incoming queue
403 for ( m = m->lm_chain; m != NULLMSG; m = m->lm_chain ) {
404 if (( new = msg_dup( m )) == NULLMSG ) {
408 new->lm_msgid = ld->ld_msgid;
409 new->lm_chain = NULLMSG;
411 new->lm_next = ld->ld_responses;
412 ld->ld_responses = new;
415 prev->lm_chain = new;
418 Debug( LDAP_DEBUG_TRACE, "cc: added type %d\n",
419 new->lm_msgtype, 0, 0 );
422 Debug( LDAP_DEBUG_TRACE, "cc: result returned from cache\n", 0, 0, 0 );
428 cache_hash( BerElement *ber )
434 * just take the length of the packet and mod with # of buckets
437 if ( ber_skip_tag( &bercpy, &len ) == LBER_ERROR
438 || ber_scanf( &bercpy, "x" ) == LBER_ERROR ) {
439 len = 0; /* punt: just return zero */
441 len = bercpy.ber_end - bercpy.ber_ptr;
444 Debug( LDAP_DEBUG_TRACE, "cache_hash: len is %ld, returning %ld\n",
445 len, len % LDAP_CACHE_BUCKETS, 0 );
446 return( (int) ( len % LDAP_CACHE_BUCKETS ));
451 msg_dup( LDAPMessage *msg )
456 if (( new = (LDAPMessage *)malloc( sizeof(LDAPMessage))) != NULL ) {
457 *new = *msg; /* struct copy */
458 if (( new->lm_ber = ber_dup( msg->lm_ber )) == NULLBER ) {
462 len = msg->lm_ber->ber_end - msg->lm_ber->ber_buf;
463 if (( new->lm_ber->ber_buf = (char *) malloc(
464 (size_t)len )) == NULL ) {
465 ber_free( new->lm_ber, 0 );
469 SAFEMEMCPY( new->lm_ber->ber_buf, msg->lm_ber->ber_buf,
471 new->lm_ber->ber_ptr = new->lm_ber->ber_buf +
472 ( msg->lm_ber->ber_ptr - msg->lm_ber->ber_buf );
473 new->lm_ber->ber_end = new->lm_ber->ber_buf + len;
481 request_cmp( BerElement *req1, BerElement *req2 )
486 r1 = *req1; /* struct copies */
490 * skip the enclosing tags (sequence markers) and the msg ids
492 if ( ber_skip_tag( &r1, &len ) == LBER_ERROR || ber_scanf( &r1, "x" )
496 if ( ber_skip_tag( &r2, &len ) == LBER_ERROR || ber_scanf( &r2, "x" )
502 * check remaining length and bytes if necessary
504 if (( len = r1.ber_end - r1.ber_ptr ) !=
505 (unsigned long) (r2.ber_end - r2.ber_ptr) )
507 return( -1 ); /* different lengths */
509 return( memcmp( r1.ber_ptr, r2.ber_ptr, (size_t)len ));
514 chain_contains_dn( LDAPMessage *msg, char *dn )
524 * first check the base or dn of the request
526 ber = *msg->lm_ber; /* struct copy */
527 if ( ber_scanf( &ber, "{i{a", &msgid, &s ) != LBER_ERROR ) {
528 rc = ( strcasecmp( dn, s ) == 0 ) ? 1 : 0;
535 if ( msg->lm_msgtype == LDAP_REQ_COMPARE ) {
540 * now check the dn of each search result
543 for ( m = msg->lm_chain; m != NULLMSG && rc == 0 ; m = m->lm_chain ) {
544 if ( m->lm_msgtype != LDAP_RES_SEARCH_ENTRY ) {
547 ber = *m->lm_ber; /* struct copy */
548 if ( ber_scanf( &ber, "{a", &s ) != LBER_ERROR ) {
549 rc = ( strcasecmp( dn, s ) == 0 ) ? 1 : 0;
559 msg_size( LDAPMessage *msg )
565 for ( m = msg; m != NULLMSG; m = m->lm_chain ) {
566 size += ( sizeof( LDAPMessage ) + m->lm_ber->ber_end -
567 m->lm_ber->ber_buf );
574 #define THRESHOLD_FACTOR 3 / 4
575 #define SIZE_FACTOR 2 / 3
578 check_cache_memused( LDAPCache *lc )
581 * this routine is called to check if the cache is too big (lc_maxmem >
582 * minimum cache size and lc_memused > lc_maxmem). If too big, it reduces
583 * the cache size to < SIZE_FACTOR * lc_maxmem. The algorithm is as follows:
584 * remove_threshold = lc_timeout seconds;
586 * remove everything older than remove_threshold seconds;
587 * remove_threshold = remove_threshold * THRESHOLD_FACTOR;
588 * } while ( cache size is > SIZE_FACTOR * lc_maxmem )
591 unsigned long remove_threshold, validtime;
592 LDAPMessage *m, *prev, *next;
594 Debug( LDAP_DEBUG_TRACE, "check_cache_memused: %ld bytes in use (%ld max)\n",
595 lc->lc_memused, lc->lc_maxmem, 0 );
597 if ( lc->lc_maxmem <= sizeof( LDAPCache )
598 || lc->lc_memused <= lc->lc_maxmem * SIZE_FACTOR ) {
602 remove_threshold = lc->lc_timeout;
603 while ( lc->lc_memused > lc->lc_maxmem * SIZE_FACTOR ) {
604 validtime = (long)time( NULL ) - remove_threshold;
605 for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) {
607 for ( m = lc->lc_buckets[ i ]; m != NULLMSG;
610 if ( m->lm_time < validtime ) {
611 if ( prev == NULLMSG ) {
612 lc->lc_buckets[ i ] = next;
614 prev->lm_next = next;
616 lc->lc_memused -= msg_size( m );
617 Debug( LDAP_DEBUG_TRACE,
626 remove_threshold *= THRESHOLD_FACTOR;
629 Debug( LDAP_DEBUG_TRACE, "ccm: reduced usage to %ld bytes\n",
630 lc->lc_memused, 0, 0 );
633 #endif /* !NO_CACHE */