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