/* * Portions Copyright 1998 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Copyright (c) 1993 The Regents of the University of Michigan. * All rights reserved. * * cache.c - local caching support for LDAP */ #ifndef NO_CACHE #ifndef lint static char copyright[] = "@(#) Copyright (c) 1993 The Regents of the University of Michigan.\nAll rights reserved.\n"; #endif #include #include #ifdef MACOS #include #include #include "macos.h" #else /* MACOS */ #if defined( DOS ) || defined( _WIN32 ) #include #include "msdos.h" #ifdef NCSA #include "externs.h" #endif /* NCSA */ #ifdef WINSOCK #include #endif /* WINSOCK */ #else /* DOS */ #include #include #endif /* DOS */ #endif /* MACOS */ #include "lber.h" #include "ldap.h" #include "ldap-private.h" #include "ldap-int.h" #ifdef NEEDPROTOS static int cache_hash( BerElement *ber ); static LDAPMessage *msg_dup( LDAPMessage *msg ); static int request_cmp( BerElement *req1, BerElement *req2 ); static int chain_contains_dn( LDAPMessage *msg, char *dn ); static ssize_t msg_size( LDAPMessage *msg ); static void check_cache_memused( LDAPCache *lc ); static void uncache_entry_or_req( LDAP *ld, char *dn, int msgid ); #else /* NEEDPROTOS */ static int cache_hash(); static LDAPMessage *msg_dup(); static int request_cmp(); static int chain_contains_dn(); static ssize_t msg_size(); static void check_cache_memused(); static void uncache_entry_or_req(); #endif /* NEEDPROTOS */ int ldap_enable_cache( LDAP *ld, time_t timeout, ssize_t maxmem ) { #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif if ( ld->ld_cache == NULLLDCACHE ) { if (( ld->ld_cache = (LDAPCache *)malloc( sizeof( LDAPCache ))) == NULLLDCACHE ) { ld->ld_errno = LDAP_NO_MEMORY; #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif return( -1 ); } (void) memset( ld->ld_cache, 0, sizeof( LDAPCache )); ld->ld_cache->lc_memused = sizeof( LDAPCache ); } ld->ld_cache->lc_timeout = timeout; ld->ld_cache->lc_maxmem = maxmem; check_cache_memused( ld->ld_cache ); ld->ld_cache->lc_enabled = 1; #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif return( 0 ); } void ldap_disable_cache( LDAP *ld ) { #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif if ( ld->ld_cache != NULLLDCACHE ) { ld->ld_cache->lc_enabled = 0; } #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif } void ldap_set_cache_options( LDAP *ld, unsigned int opts ) { #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif if ( ld->ld_cache != NULLLDCACHE ) { ld->ld_cache->lc_options = opts; } #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif } void ldap_destroy_cache( LDAP *ld ) { #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif if ( ld->ld_cache != NULLLDCACHE ) { ldap_flush_cache( ld ); free( (char *)ld->ld_cache ); ld->ld_cache = NULLLDCACHE; } #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif } void ldap_flush_cache( LDAP *ld ) { int i; LDAPMessage *m, *next; #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 90, "ldap_flush_cache\n"), 0, 0, 0 ); if ( ld->ld_cache != NULLLDCACHE ) { /* delete all requests in the queue */ for ( m = ld->ld_cache->lc_requests; m != NULLMSG; m = next ) { next = m->lm_next; ldap_msgfree( m ); } ld->ld_cache->lc_requests = NULLMSG; /* delete all messages in the cache */ for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) { for ( m = ld->ld_cache->lc_buckets[ i ]; m != NULLMSG; m = next ) { next = m->lm_next; ldap_msgfree( m ); } ld->ld_cache->lc_buckets[ i ] = NULLMSG; } ld->ld_cache->lc_memused = sizeof( LDAPCache ); } #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif } void ldap_uncache_request( LDAP *ld, int msgid ) { #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 91, "ldap_uncache_request %1$d ld_cache %2$x\n"), msgid, ld->ld_cache, 0 ); uncache_entry_or_req( ld, NULL, msgid ); #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif } void ldap_uncache_entry( LDAP *ld, char *dn ) { #if defined( SUN ) && defined( _REENTRANT ) LOCK_LDAP(ld); #endif Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 92, "ldap_uncache_entry %1$s ld_cache %2$x\n"), dn, ld->ld_cache, 0 ); uncache_entry_or_req( ld, dn, 0 ); #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_LDAP(ld); #endif } static void uncache_entry_or_req( LDAP *ld, char *dn, /* if non-NULL, uncache entry */ int msgid ) /* request to uncache (if dn == NULL) */ { int i; LDAPMessage *m, *prev, *next; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 93, "ldap_uncache_entry_or_req dn %1$s msgid %2$d ld_cache %3$x\n"), dn, msgid, ld->ld_cache ); if ( ld->ld_cache == NULLLDCACHE ) { return; } /* first check the request queue */ prev = NULLMSG; for ( m = ld->ld_cache->lc_requests; m != NULLMSG; m = next ) { next = m->lm_next; if (( dn != NULL && chain_contains_dn( m, dn )) || ( dn == NULL && m->lm_msgid == msgid )) { if ( prev == NULLMSG ) { ld->ld_cache->lc_requests = next; } else { prev->lm_next = next; } ld->ld_cache->lc_memused -= msg_size( m ); ldap_msgfree( m ); } else { prev = m; } } /* now check the rest of the cache */ for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) { prev = NULLMSG; for ( m = ld->ld_cache->lc_buckets[ i ]; m != NULLMSG; m = next ) { next = m->lm_next; if (( dn != NULL && chain_contains_dn( m, dn )) || ( dn == NULL && m->lm_msgid == msgid )) { if ( prev == NULLMSG ) { ld->ld_cache->lc_buckets[ i ] = next; } else { prev->lm_next = next; } ld->ld_cache->lc_memused -= msg_size( m ); ldap_msgfree( m ); } else { prev = m; } } } } void add_request_to_cache( LDAP *ld, unsigned int msgtype, BerElement *request ) { LDAPMessage *new; size_t len; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 94, "add_request_to_cache\n"), 0, 0, 0 ); ld->ld_errno = LDAP_SUCCESS; if ( ld->ld_cache == NULLLDCACHE || ( ld->ld_cache->lc_enabled == 0 )) { return; } if (( new = (LDAPMessage *) calloc( 1, sizeof(LDAPMessage) )) != NULL ) { if (( new->lm_ber = alloc_ber_with_options( ld )) == NULLBER ) { free( (char *)new ); return; } len = request->ber_ptr - request->ber_buf; if (( new->lm_ber->ber_buf = (char *) malloc( len )) == NULL ) { ber_free( new->lm_ber, 0 ); free( (char *)new ); ld->ld_errno = LDAP_NO_MEMORY; return; } SAFEMEMCPY( new->lm_ber->ber_buf, request->ber_buf, len ); new->lm_ber->ber_ptr = new->lm_ber->ber_buf; new->lm_ber->ber_end = new->lm_ber->ber_buf + len; new->lm_msgid = ld->ld_msgid; new->lm_msgtype = (int) msgtype;; new->lm_next = ld->ld_cache->lc_requests; ld->ld_cache->lc_requests = new; } else { ld->ld_errno = LDAP_NO_MEMORY; } } void add_result_to_cache( LDAP *ld, LDAPMessage *result ) { LDAPMessage *m, **mp, *req, *new, *prev; int err, keep; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 95, "add_result_to_cache: id %1$d, type %2$d\n"), result->lm_msgid, result->lm_msgtype, 0 ); if ( ld->ld_cache == NULLLDCACHE || ( ld->ld_cache->lc_enabled == 0 )) { Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 96, "artc: cache disabled\n"), 0, 0, 0 ); return; } if ( result->lm_msgtype != LDAP_RES_SEARCH_ENTRY && result->lm_msgtype != LDAP_RES_SEARCH_RESULT && result->lm_msgtype != LDAP_RES_SEARCH_REFERENCE && result->lm_msgtype != LDAP_RES_COMPARE ) { /* * only cache search and compare operations */ Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 97, "artc: only caching search & compare operations\n"), 0, 0, 0 ); return; } /* * if corresponding request is in the lc_requests list, add this * result to it. if this result completes the results for the * request, add the request/result chain to the cache proper. */ prev = NULLMSG; for ( m = ld->ld_cache->lc_requests; m != NULL; m = m->lm_next ) { if ( m->lm_msgid == result->lm_msgid ) { break; } prev = m; } if ( m != NULLMSG ) { /* found request; add to end of chain */ req = m; for ( ; m->lm_chain != NULLMSG; m = m->lm_chain ) ; if (( new = msg_dup( result )) != NULLMSG ) { new->lm_chain = NULLMSG; m->lm_chain = new; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 98, "artc: result added to cache request chain\n"), 0, 0, 0 ); } if ( result->lm_msgtype == LDAP_RES_SEARCH_RESULT || result->lm_msgtype == LDAP_RES_COMPARE ) { /* * this result completes the chain of results * add to cache proper if appropriate */ keep = 0; /* pessimistic */ err = ldap_result2error( ld, result, 0 ); if ( err == LDAP_SUCCESS || ( result->lm_msgtype == LDAP_RES_COMPARE && ( err == LDAP_COMPARE_FALSE || err == LDAP_COMPARE_TRUE || err == LDAP_NO_SUCH_ATTRIBUTE ))) { keep = 1; } if ( ld->ld_cache->lc_options == 0 ) { if ( err == LDAP_SIZELIMIT_EXCEEDED ) { keep = 1; } } else if (( ld->ld_cache->lc_options & LDAP_CACHE_OPT_CACHEALLERRS ) != 0 ) { keep = 1; } if ( prev == NULLMSG ) { ld->ld_cache->lc_requests = req->lm_next; } else { prev->lm_next = req->lm_next; } if ( !keep ) { Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 99, "artc: not caching result with error %d\n"), err, 0, 0 ); ldap_msgfree( req ); } else { mp = &ld->ld_cache->lc_buckets[ cache_hash( req->lm_ber ) ]; req->lm_next = *mp; *mp = req; req->lm_time = time( NULL ); ld->ld_cache->lc_memused += msg_size( req ); check_cache_memused( ld->ld_cache ); Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 100, "artc: cached result with error %d\n"), err, 0, 0 ); } } } else { Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 101, "artc: msgid not in request list\n"), 0, 0, 0 ); } } /* * look in the cache for this request * return 0 if found, -1 if not * if found, the corresponding result messages are added to the incoming * queue with the correct (new) msgid so that subsequent ldap_result calls * will find them. */ int check_cache( LDAP *ld, unsigned int msgtype, BerElement *request ) { LDAPMessage *m, *new, *prev, *next; BerElement reqber; int first, hash; unsigned long validtime; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 102, "check_cache\n"), 0, 0, 0 ); if ( ld->ld_cache == NULLLDCACHE || ( ld->ld_cache->lc_enabled == 0 )) { return( -1 ); } reqber.ber_buf = reqber.ber_ptr = request->ber_buf; reqber.ber_end = request->ber_ptr; validtime = time( NULL ) - ld->ld_cache->lc_timeout; prev = NULLMSG; hash = cache_hash( &reqber ); for ( m = ld->ld_cache->lc_buckets[ hash ]; m != NULLMSG; m = next ) { Debug( LDAP_DEBUG_TRACE,catgets(slapdcat, 1, 103, "cc: examining id %1$d,type %2$d\n"), m->lm_msgid, m->lm_msgtype, 0 ); if ( m->lm_time < validtime ) { /* delete expired message */ next = m->lm_next; if ( prev == NULL ) { ld->ld_cache->lc_buckets[ hash ] = next; } else { prev->lm_next = next; } Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 104, "cc: expired id %d\n"), m->lm_msgid, 0, 0 ); ld->ld_cache->lc_memused -= msg_size( m ); ldap_msgfree( m ); } else { if ( m->lm_msgtype == msgtype && request_cmp( m->lm_ber, &reqber ) == 0 ) { break; } next = m->lm_next; prev = m; } } if ( m == NULLMSG ) { return( -1 ); } /* * add duplicates of responses to incoming queue */ first = 1; #if defined( SUN ) && defined( _REENTRANT ) LOCK_RESPONSE(ld); #endif for ( m = m->lm_chain; m != NULLMSG; m = m->lm_chain ) { if (( new = msg_dup( m )) == NULLMSG ) { #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_RESPONSE(ld); #endif return( -1 ); } new->lm_msgid = ld->ld_msgid; new->lm_chain = NULLMSG; if ( first ) { new->lm_next = ld->ld_responses; ld->ld_responses = new; first = 0; } else { prev->lm_chain = new; } prev = new; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 105, "cc: added type %d\n"), new->lm_msgtype, 0, 0 ); } Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 106, "cc: result returned from cache\n"), 0, 0, 0 ); #if defined( SUN ) && defined( _REENTRANT ) UNLOCK_RESPONSE(ld); #endif return( 0 ); } static int cache_hash( BerElement *ber ) { BerElement bercpy; unsigned int len; /* * just take the length of the packet and mod with # of buckets */ bercpy = *ber; if ( ber_skip_tag( &bercpy, &len ) == LBER_ERROR || ber_scanf( &bercpy, "x" ) == LBER_ERROR ) { len = 0; /* punt: just return zero */ } else { len = (int) ( bercpy.ber_end - bercpy.ber_ptr ); } Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 107, "cache_hash: len is %1$ld, returning %2$ld\n"), len, len % LDAP_CACHE_BUCKETS, 0 ); return ( len % LDAP_CACHE_BUCKETS ); } static LDAPMessage * msg_dup( LDAPMessage *msg ) { LDAPMessage *new; size_t len; if (( new = (LDAPMessage *)malloc( sizeof(LDAPMessage))) != NULL ) { *new = *msg; /* struct copy */ if (( new->lm_ber = ber_dup( msg->lm_ber )) == NULLBER ) { free( (char *)new ); return( NULLMSG ); } len = msg->lm_ber->ber_end - msg->lm_ber->ber_buf; if (( new->lm_ber->ber_buf = (char *) malloc( len )) == NULL ) { ber_free( new->lm_ber, 0 ); free( (char *)new ); return( NULLMSG ); } SAFEMEMCPY( new->lm_ber->ber_buf, msg->lm_ber->ber_buf, len ); new->lm_ber->ber_ptr = new->lm_ber->ber_buf + ( msg->lm_ber->ber_ptr - msg->lm_ber->ber_buf ); new->lm_ber->ber_end = new->lm_ber->ber_buf + len; } return( new ); } static int request_cmp( BerElement *req1, BerElement *req2 ) { unsigned int len; size_t slen; BerElement r1, r2; r1 = *req1; /* struct copies */ r2 = *req2; /* * skip the enclosing tags (sequence markers) and the msg ids */ if ( ber_skip_tag( &r1, &len ) == LBER_ERROR || ber_scanf( &r1, "x" ) == LBER_ERROR ) { return( -1 ); } if ( ber_skip_tag( &r2, &len ) == LBER_ERROR || ber_scanf( &r2, "x" ) == LBER_ERROR ) { return( -1 ); } /* * check remaining length and bytes if necessary */ if (( slen = r1.ber_end - r1.ber_ptr ) != r2.ber_end - r2.ber_ptr ) { return( -1 ); /* different lengths */ } return( memcmp( r1.ber_ptr, r2.ber_ptr, slen )); } static int chain_contains_dn( LDAPMessage *msg, char *dn ) { LDAPMessage *m; BerElement ber; int msgid; char *s; int rc; /* * first check the base or dn of the request */ ber = *msg->lm_ber; /* struct copy */ if ( ber_scanf( &ber, "{i{a", &msgid, &s ) != LBER_ERROR ) { rc = ( strcasecmp( dn, s ) == 0 ) ? 1 : 0; free( s ); if ( rc != 0 ) { return( rc ); } } if ( msg->lm_msgtype == LDAP_REQ_COMPARE ) { return( 0 ); } /* * now check the dn of each search result */ rc = 0; for ( m = msg->lm_chain; m != NULLMSG && rc == 0 ; m = m->lm_chain ) { if ( m->lm_msgtype != LDAP_RES_SEARCH_ENTRY ) { continue; } ber = *m->lm_ber; /* struct copy */ if ( ber_scanf( &ber, "{a", &s ) != LBER_ERROR ) { rc = ( strcasecmp( dn, s ) == 0 ) ? 1 : 0; free( s ); } } return( rc ); } static ssize_t msg_size( LDAPMessage *msg ) { LDAPMessage *m; ssize_t size; size = 0; for ( m = msg; m != NULLMSG; m = m->lm_chain ) { size += sizeof( LDAPMessage ) + m->lm_ber->ber_end - m->lm_ber->ber_buf; } return( size ); } #define THRESHOLD_FACTOR 3 / 4 #define SIZE_FACTOR 2 / 3 static void check_cache_memused( LDAPCache *lc ) { /* * this routine is called to check if the cache is too big (lc_maxmem > * minimum cache size and lc_memused > lc_maxmem). If too big, it reduces * the cache size to < SIZE_FACTOR * lc_maxmem. The algorithm is as follows: * remove_threshold = lc_timeout seconds; * do { * remove everything older than remove_threshold seconds; * remove_threshold = remove_threshold * THRESHOLD_FACTOR; * } while ( cache size is > SIZE_FACTOR * lc_maxmem ) */ int i; unsigned long remove_threshold, validtime; LDAPMessage *m, *prev, *next; Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 108, "check_cache_memused: %1$ld bytes in use (%2$ld max)\n"), lc->lc_memused, lc->lc_maxmem, 0 ); if ( lc->lc_maxmem <= sizeof( LDAPCache ) || lc->lc_memused <= lc->lc_maxmem * SIZE_FACTOR ) { return; } remove_threshold = lc->lc_timeout; while ( lc->lc_memused > lc->lc_maxmem * SIZE_FACTOR ) { validtime = time( NULL ) - remove_threshold; for ( i = 0; i < LDAP_CACHE_BUCKETS; ++i ) { prev = NULLMSG; for ( m = lc->lc_buckets[ i ]; m != NULLMSG; m = next ) { next = m->lm_next; if ( m->lm_time < validtime ) { if ( prev == NULLMSG ) { lc->lc_buckets[ i ] = next; } else { prev->lm_next = next; } lc->lc_memused -= msg_size( m ); Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 109, "ccm: removed %d\n"), m->lm_msgid, 0, 0 ); ldap_msgfree( m ); } else { prev = m; } } } remove_threshold *= THRESHOLD_FACTOR; } Debug( LDAP_DEBUG_TRACE, catgets(slapdcat, 1, 110, "ccm: reduced usage to %ld bytes\n"), lc->lc_memused, 0, 0 ); } #endif /* !NO_CACHE */