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