Coverage Report

Created: 2025-08-11 06:35

/src/net-snmp/agent/helpers/cache_handler.c
Line
Count
Source (jump to first uncovered line)
1
/* Portions of this file are subject to the following copyright(s).  See
2
 * the Net-SNMP's COPYING file for more details and other copyrights
3
 * that may apply:
4
 */
5
/*
6
 * Portions of this file are copyrighted by:
7
 * Copyright (C) 2007 Apple, Inc. All rights reserved.
8
 * Use is subject to license terms specified in the COPYING file
9
 * distributed with the Net-SNMP package.
10
 *
11
 * Portions of this file are copyrighted by:
12
 * Copyright (c) 2016 VMware, Inc. All rights reserved.
13
 * Use is subject to license terms specified in the COPYING file
14
 * distributed with the Net-SNMP package.
15
 */
16
#include <net-snmp/net-snmp-config.h>
17
#include <net-snmp/net-snmp-features.h>
18
19
#ifdef HAVE_MALLOC_H
20
#include <malloc.h>
21
#endif
22
23
#ifdef HAVE_STRING_H
24
#include <string.h>
25
#else
26
#include <strings.h>
27
#endif
28
29
#include <net-snmp/net-snmp-includes.h>
30
#include <net-snmp/agent/net-snmp-agent-includes.h>
31
32
#include <net-snmp/agent/cache_handler.h>
33
34
netsnmp_feature_child_of(cache_handler, mib_helpers);
35
36
netsnmp_feature_child_of(cache_find_by_oid, cache_handler);
37
netsnmp_feature_child_of(cache_get_head, cache_handler);
38
39
static netsnmp_cache  *cache_head = NULL;
40
static int             cache_outstanding_valid = 0;
41
static int             _cache_load( netsnmp_cache *cache );
42
43
0
#define CACHE_RELEASE_FREQUENCY 60      /* Check for expired caches every 60s */
44
45
void            release_cached_resources(unsigned int regNo,
46
                                         void *clientargs);
47
48
/** @defgroup cache_handler cache_handler
49
 *  Maintains a cache of data for use by lower level handlers.
50
 *  @ingroup utilities
51
 *  This helper checks to see whether the data has been loaded "recently"
52
 *  (according to the timeout for that particular cache) and calls the
53
 *  registered "load_cache" routine if necessary.
54
 *  The lower handlers can then work with this local cached data.
55
 *
56
 *  A timeout value of -1 will cause netsnmp_cache_check_expired() to
57
 *  always return true, and thus the cache will be reloaded for every
58
 *  request.
59
 *
60
 *  To minimize resource use by the agent, a periodic callback checks for
61
 *  expired caches, and will call the free_cache function for any expired
62
 *  cache.
63
 *
64
 *  The load_cache routine should return a negative number if the cache
65
 *  was not successfully loaded. 0 or any positive number indicates success.
66
 *
67
 *
68
 *  Several flags can be set to affect the operations on the cache.
69
 *
70
 *  If NETSNMP_CACHE_DONT_INVALIDATE_ON_SET is set, the free_cache method
71
 *  will not be called after a set request has processed. It is assumed that
72
 *  the lower mib handler using the cache has maintained cache consistency.
73
 *
74
 *  If NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD is set, the free_cache method
75
 *  will not be called before the load_cache method is called. It is assumed
76
 *  that the load_cache routine will properly deal with being called with a
77
 *  valid cache.
78
 *
79
 *  If NETSNMP_CACHE_DONT_FREE_EXPIRED is set, the free_cache method will
80
 *  not be called with the cache expires. The expired flag will be set, but
81
 *  the valid flag will not be cleared. It is assumed that the load_cache
82
 *  routine will properly deal with being called with a valid cache.
83
 *
84
 *  If NETSNMP_CACHE_PRELOAD is set when a the cache handler is created,
85
 *  the cache load routine will be called immediately.
86
 *
87
 *  If NETSNMP_CACHE_DONT_AUTO_RELEASE is set, the periodic callback that
88
 *  checks for expired caches will skip the cache. The cache will only be
89
 *  checked for expiration when a request triggers the cache handler. This
90
 *  is useful if the cache has it's own periodic callback to keep the cache
91
 *  fresh.
92
 *
93
 *  If NETSNMP_CACHE_AUTO_RELOAD is set, a timer will be set up to reload
94
 *  the cache when it expires. This is useful for keeping the cache fresh,
95
 *  even in the absence of incoming snmp requests.
96
 *
97
 *  If NETSNMP_CACHE_RESET_TIMER_ON_USE is set, the expiry timer will be
98
 *  reset on each cache access. In practice the 'timeout' becomes a timer
99
 *  which triggers when the cache is no longer needed. This is useful
100
 *  if the cache is automatically kept synchronized: e.g. by receiving
101
 *  change notifications from Netlink, inotify or similar. This should
102
 *  not be used if cache is not synchronized automatically as it would
103
 *  result in stale cache information when if polling happens too fast.
104
 *
105
 *
106
 *  Here are some suggestions for some common situations.
107
 *
108
 *  Cached File:
109
 *      If your table is based on a file that may periodically change,
110
 *      you can test the modification date to see if the file has
111
 *      changed since the last cache load. To get the cache helper to call
112
 *      the load function for every request, set the timeout to -1, which
113
 *      will cause the cache to always report that it is expired. This means
114
 *      that you will want to prevent the agent from flushing the cache when
115
 *      it has expired, and you will have to flush it manually if you
116
 *      detect that the file has changed. To accomplish this, set the
117
 *      following flags:
118
 *
119
 *          NETSNMP_CACHE_DONT_FREE_EXPIRED
120
 *          NETSNMP_CACHE_DONT_AUTO_RELEASE
121
 *
122
 *
123
 *  Constant (periodic) reload:
124
 *      If you want the cache kept up to date regularly, even if no requests
125
 *      for the table are received, you can have your cache load routine
126
 *      called periodically. This is very useful if you need to monitor the
127
 *      data for changes (eg a <i>LastChanged</i> object). You will need to
128
 *      prevent the agent from flushing the cache when it expires. Set the
129
 *      cache timeout to the frequency, in seconds, that you wish to
130
 *      reload your cache, and set the following flags:
131
 *
132
 *          NETSNMP_CACHE_DONT_FREE_EXPIRED
133
 *          NETSNMP_CACHE_DONT_AUTO_RELEASE
134
 *          NETSNMP_CACHE_AUTO_RELOAD
135
 *
136
 *  Dynamically updated, unloaded after timeout:
137
 *      If the cache is kept up to date dynamically by listening for
138
 *      change notifications somehow, but it should not be in memory
139
 *      if it's not needed. Set the following flag:
140
 *
141
 *          NETSNMP_CACHE_RESET_TIMER_ON_USE
142
 *
143
 *  @{
144
 */
145
146
static void
147
_cache_free( netsnmp_cache *cache );
148
149
#ifndef NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD
150
/** get cache head
151
 * @internal
152
 * unadvertised function to get cache head. You really should not
153
 * do this, since the internal storage mechanism might change.
154
 */
155
netsnmp_cache *
156
netsnmp_cache_get_head(void)
157
0
{
158
0
    return cache_head;
159
0
}
160
#endif /* NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD */
161
162
#ifndef NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID
163
/** find existing cache
164
 */
165
netsnmp_cache *
166
netsnmp_cache_find_by_oid(const oid * rootoid, int rootoid_len)
167
0
{
168
0
    netsnmp_cache  *cache;
169
170
0
    for (cache = cache_head; cache; cache = cache->next) {
171
0
        if (0 == netsnmp_oid_equals(cache->rootoid, cache->rootoid_len,
172
0
                                    rootoid, rootoid_len))
173
0
            return cache;
174
0
    }
175
    
176
0
    return NULL;
177
0
}
178
#endif /* NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID */
179
180
/** returns a cache
181
 */
182
netsnmp_cache *
183
netsnmp_cache_create(int timeout, NetsnmpCacheLoad * load_hook,
184
                     NetsnmpCacheFree * free_hook,
185
                     const oid * rootoid, int rootoid_len)
186
3.20k
{
187
3.20k
    netsnmp_cache  *cache = NULL;
188
189
3.20k
    cache = SNMP_MALLOC_TYPEDEF(netsnmp_cache);
190
3.20k
    if (NULL == cache) {
191
0
        snmp_log(LOG_ERR,"malloc error in netsnmp_cache_create\n");
192
0
        return NULL;
193
0
    }
194
3.20k
    cache->timeout = timeout;
195
3.20k
    cache->load_cache = load_hook;
196
3.20k
    cache->free_cache = free_hook;
197
3.20k
    cache->enabled = 1;
198
199
3.20k
    if(0 == cache->timeout)
200
0
        cache->timeout = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID,
201
0
                                            NETSNMP_DS_AGENT_CACHE_TIMEOUT);
202
203
    
204
    /*
205
     * Add the registered OID information, and tack
206
     * this onto the list for cache SNMP management
207
     *
208
     * Note that this list is not ordered.
209
     *    table_iterator rules again!
210
     */
211
3.20k
    if (rootoid) {
212
0
        cache->rootoid = snmp_duplicate_objid(rootoid, rootoid_len);
213
0
        cache->rootoid_len = rootoid_len;
214
0
        cache->next = cache_head;
215
0
        if (cache_head)
216
0
            cache_head->prev = cache;
217
0
        cache_head = cache;
218
0
    }
219
220
3.20k
    return cache;
221
3.20k
}
222
223
static void *
224
netsnmp_cache_ref(void *p)
225
0
{
226
0
    netsnmp_cache *cache = p;
227
228
0
    cache->refcnt++;
229
0
    return cache;
230
0
}
231
232
static void
233
netsnmp_cache_deref(void *p)
234
3.20k
{
235
3.20k
    netsnmp_cache *cache = p;
236
237
3.20k
    if (--cache->refcnt == 0) {
238
3.20k
        netsnmp_cache_remove(cache);
239
3.20k
        netsnmp_cache_free(cache);
240
3.20k
    }
241
3.20k
}
242
243
/** frees a cache
244
 */
245
int
246
netsnmp_cache_free(netsnmp_cache *cache)
247
3.20k
{
248
3.20k
    netsnmp_cache  *pos;
249
250
3.20k
    if (NULL == cache)
251
0
        return SNMPERR_SUCCESS;
252
253
3.20k
    for (pos = cache_head; pos; pos = pos->next) {
254
0
        if (pos == cache) {
255
0
            size_t          out_len = 0;
256
0
            size_t          buf_len = 0;
257
0
            char           *buf = NULL;
258
259
0
            sprint_realloc_objid((u_char **) &buf, &buf_len, &out_len,
260
0
                                 1, pos->rootoid, pos->rootoid_len);
261
0
            snmp_log(LOG_WARNING,
262
0
                     "not freeing cache with root OID %s (still in list)\n",
263
0
                     buf);
264
0
            free(buf);
265
0
            return SNMP_ERR_GENERR;
266
0
        }
267
0
    }
268
269
3.20k
    if(0 != cache->timer_id)
270
0
        netsnmp_cache_timer_stop(cache);
271
272
3.20k
    if (cache->valid)
273
0
        _cache_free(cache);
274
275
3.20k
    if (cache->timestampM)
276
0
  free(cache->timestampM);
277
278
3.20k
    if (cache->rootoid)
279
0
        free(cache->rootoid);
280
281
3.20k
    free(cache);
282
283
3.20k
    return SNMPERR_SUCCESS;
284
3.20k
}
285
286
/** removes a cache
287
 */
288
int
289
netsnmp_cache_remove(netsnmp_cache *cache)
290
3.20k
{
291
3.20k
    netsnmp_cache  *cur,*prev;
292
293
3.20k
    if (!cache || !cache_head)
294
3.20k
        return -1;
295
296
0
    if (cache == cache_head) {
297
0
        cache_head = cache_head->next;
298
0
        if (cache_head)
299
0
            cache_head->prev = NULL;
300
0
        return 0;
301
0
    }
302
303
0
    prev = cache_head;
304
0
    cur = cache_head->next;
305
0
    for (; cur; prev = cur, cur = cur->next) {
306
0
        if (cache == cur) {
307
0
            prev->next = cur->next;
308
0
            if (cur->next)
309
0
                cur->next->prev = cur->prev;
310
0
            return 0;
311
0
        }
312
0
    }
313
0
    return -1;
314
0
}
315
316
/** callback function to call cache load function */
317
static void
318
_timer_reload(unsigned int regNo, void *clientargs)
319
0
{
320
0
    netsnmp_cache *cache = (netsnmp_cache *)clientargs;
321
322
0
    DEBUGMSGT(("cache_timer:start", "loading cache %p\n", cache));
323
324
0
    cache->expired = 1;
325
326
0
    _cache_load(cache);
327
0
}
328
329
/** starts the recurring cache_load callback */
330
unsigned int
331
netsnmp_cache_timer_start(netsnmp_cache *cache)
332
0
{
333
0
    if(NULL == cache)
334
0
        return 0;
335
336
0
    DEBUGMSGTL(( "cache_timer:start", "OID: "));
337
0
    DEBUGMSGOID(("cache_timer:start", cache->rootoid, cache->rootoid_len));
338
0
    DEBUGMSG((   "cache_timer:start", "\n"));
339
340
0
    if(0 != cache->timer_id) {
341
0
        snmp_log(LOG_WARNING, "cache has existing timer id.\n");
342
0
        return cache->timer_id;
343
0
    }
344
    
345
0
    if(! (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)) {
346
0
        snmp_log(LOG_ERR,
347
0
                 "cache_timer_start called but auto_reload not set.\n");
348
0
        return 0;
349
0
    }
350
351
0
    cache->timer_id = snmp_alarm_register(cache->timeout, SA_REPEAT,
352
0
                                          _timer_reload, cache);
353
0
    if(0 == cache->timer_id) {
354
0
        snmp_log(LOG_ERR,"could not register alarm\n");
355
0
        return 0;
356
0
    }
357
358
0
    DEBUGMSGT(("cache_timer:start",
359
0
               "starting timer %lu for cache %p\n", cache->timer_id, cache));
360
0
    return cache->timer_id;
361
0
}
362
363
/** stops the recurring cache_load callback */
364
void
365
netsnmp_cache_timer_stop(netsnmp_cache *cache)
366
0
{
367
0
    if(NULL == cache)
368
0
        return;
369
370
0
    if(0 == cache->timer_id) {
371
0
        snmp_log(LOG_WARNING, "cache has no timer id.\n");
372
0
        return;
373
0
    }
374
375
0
    DEBUGMSGT(("cache_timer:stop",
376
0
               "stopping timer %lu for cache %p\n", cache->timer_id, cache));
377
378
0
    snmp_alarm_unregister(cache->timer_id);
379
0
    cache->timer_id = 0;
380
0
}
381
382
383
/** returns a cache handler that can be injected into a given handler chain.  
384
 */
385
netsnmp_mib_handler *
386
netsnmp_cache_handler_get(netsnmp_cache* cache)
387
3.20k
{
388
3.20k
    netsnmp_mib_handler *ret = NULL;
389
    
390
3.20k
    ret = netsnmp_create_handler("cache_handler",
391
3.20k
                                 netsnmp_cache_helper_handler);
392
3.20k
    if (ret) {
393
3.20k
        ret->flags |= MIB_HANDLER_AUTO_NEXT;
394
3.20k
        ret->myvoid = (void *) cache;
395
        
396
3.20k
        if(NULL != cache) {
397
3.20k
            if ((cache->flags & NETSNMP_CACHE_PRELOAD) && ! cache->valid) {
398
                /*
399
                 * load cache, ignore rc
400
                 * (failed load doesn't affect registration)
401
                 */
402
0
                (void)_cache_load(cache);
403
0
            }
404
3.20k
            if (cache->flags & NETSNMP_CACHE_AUTO_RELOAD && !cache->timer_id)
405
0
                netsnmp_cache_timer_start(cache);
406
            
407
3.20k
        }
408
3.20k
    }
409
3.20k
    return ret;
410
3.20k
}
411
412
/** Makes sure that memory allocated for the cache is freed when the handler
413
 *  is unregistered.
414
 */
415
void netsnmp_cache_handler_owns_cache(netsnmp_mib_handler *handler)
416
3.20k
{
417
3.20k
    netsnmp_assert(handler->myvoid);
418
3.20k
    ((netsnmp_cache *)handler->myvoid)->refcnt++;
419
3.20k
    handler->data_clone = netsnmp_cache_ref;
420
3.20k
    handler->data_free = netsnmp_cache_deref;
421
3.20k
}
422
423
/** returns a cache handler that can be injected into a given handler chain.  
424
 */
425
netsnmp_mib_handler *
426
netsnmp_get_cache_handler(int timeout, NetsnmpCacheLoad * load_hook,
427
                          NetsnmpCacheFree * free_hook,
428
                          const oid * rootoid, int rootoid_len)
429
0
{
430
0
    netsnmp_mib_handler *ret = NULL;
431
0
    netsnmp_cache  *cache = NULL;
432
433
0
    ret = netsnmp_cache_handler_get(NULL);
434
0
    if (ret) {
435
0
        cache = netsnmp_cache_create(timeout, load_hook, free_hook,
436
0
                                     rootoid, rootoid_len);
437
0
        ret->myvoid = (void *) cache;
438
0
        netsnmp_cache_handler_owns_cache(ret);
439
0
    }
440
0
    return ret;
441
0
}
442
443
#if !defined(NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER) || !defined(NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER)
444
static int
445
_cache_handler_register(netsnmp_handler_registration * reginfo,
446
                        netsnmp_mib_handler *handler)
447
0
{
448
    /** success path */
449
0
    if (reginfo && handler &&
450
0
        (netsnmp_inject_handler(reginfo, handler) == SNMPERR_SUCCESS))
451
0
        return netsnmp_register_handler(reginfo);
452
453
    /** error path */
454
0
    snmp_log(LOG_ERR, "could not register cache handler\n");
455
456
0
    if (handler)
457
0
        netsnmp_handler_free(handler);
458
459
0
    netsnmp_handler_registration_free(reginfo);
460
461
0
    return MIB_REGISTRATION_FAILED;
462
0
}
463
#endif
464
465
/** functionally the same as calling netsnmp_register_handler() but also
466
 * injects a cache handler at the same time for you. */
467
netsnmp_feature_child_of(netsnmp_cache_handler_register,netsnmp_unused);
468
#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER
469
int
470
netsnmp_cache_handler_register(netsnmp_handler_registration * reginfo,
471
                               netsnmp_cache* cache)
472
0
{
473
0
    if ((NULL == reginfo) || (NULL == cache)) {
474
0
        snmp_log(LOG_ERR, "bad param in netsnmp_cache_handler_register\n");
475
0
        netsnmp_handler_registration_free(reginfo);
476
0
        return MIB_REGISTRATION_FAILED;
477
0
    }
478
479
0
    return _cache_handler_register(reginfo, netsnmp_cache_handler_get(cache));
480
0
}
481
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER */
482
483
/** functionally the same as calling netsnmp_register_handler() but also
484
 * injects a cache handler at the same time for you. */
485
netsnmp_feature_child_of(netsnmp_register_cache_handler,netsnmp_unused);
486
#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER
487
int
488
netsnmp_register_cache_handler(netsnmp_handler_registration * reginfo,
489
                               int timeout, NetsnmpCacheLoad * load_hook,
490
                               NetsnmpCacheFree * free_hook)
491
0
{
492
0
    netsnmp_mib_handler *handler;
493
494
0
    if (NULL == reginfo) {
495
0
        snmp_log(LOG_ERR, "bad param in netsnmp_cache_handler_register\n");
496
0
        netsnmp_handler_registration_free(reginfo);
497
0
        return MIB_REGISTRATION_FAILED;
498
0
    }
499
500
0
    handler =  netsnmp_get_cache_handler(timeout, load_hook, free_hook,
501
0
                                         reginfo->rootoid,
502
0
                                         reginfo->rootoid_len);
503
504
0
    return _cache_handler_register(reginfo, handler);
505
0
}
506
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER */
507
508
static char *
509
_build_cache_name(const char *name)
510
0
{
511
0
    char *dup = (char*)malloc(strlen(name) + strlen(CACHE_NAME) + 2);
512
0
    if (NULL == dup)
513
0
        return NULL;
514
0
    sprintf(dup, "%s:%s", CACHE_NAME, name);
515
0
    return dup;
516
0
}
517
518
/** Insert the cache information for a given request (PDU) */
519
void
520
netsnmp_cache_reqinfo_insert(netsnmp_cache* cache,
521
                             netsnmp_agent_request_info * reqinfo,
522
                             const char *name)
523
0
{
524
0
    char *cache_name = _build_cache_name(name);
525
0
    if (NULL == netsnmp_agent_get_list_data(reqinfo, cache_name)) {
526
0
        DEBUGMSGTL(("verbose:helper:cache_handler", " adding '%s' to %p\n",
527
0
                    cache_name, reqinfo));
528
0
        netsnmp_agent_add_list_data(reqinfo,
529
0
                                    netsnmp_create_data_list(cache_name,
530
0
                                                             cache, NULL));
531
0
    }
532
0
    SNMP_FREE(cache_name);
533
0
}
534
535
/** Extract the cache information for a given request (PDU) */
536
netsnmp_cache  *
537
netsnmp_cache_reqinfo_extract(netsnmp_agent_request_info * reqinfo,
538
                              const char *name)
539
0
{
540
0
    netsnmp_cache  *result;
541
0
    char *cache_name = _build_cache_name(name);
542
0
    result = (netsnmp_cache*)netsnmp_agent_get_list_data(reqinfo, cache_name);
543
0
    SNMP_FREE(cache_name);
544
0
    return result;
545
0
}
546
547
/** Extract the cache information for a given request (PDU) */
548
netsnmp_feature_child_of(netsnmp_extract_cache_info,netsnmp_unused);
549
#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO
550
netsnmp_cache  *
551
netsnmp_extract_cache_info(netsnmp_agent_request_info * reqinfo)
552
0
{
553
0
    return netsnmp_cache_reqinfo_extract(reqinfo, CACHE_NAME);
554
0
}
555
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO */
556
557
558
/** Check if the cache timeout has passed. Sets and return the expired flag. */
559
int
560
netsnmp_cache_check_expired(netsnmp_cache *cache)
561
0
{
562
0
    if(NULL == cache)
563
0
        return 0;
564
0
    if (cache->expired)
565
0
        return 1;
566
0
    if(!cache->valid || (NULL == cache->timestampM) || (-1 == cache->timeout))
567
0
        cache->expired = 1;
568
0
    else
569
0
        cache->expired = netsnmp_ready_monotonic(cache->timestampM,
570
0
                                                 1000 * cache->timeout);
571
    
572
0
    return cache->expired;
573
0
}
574
575
/** Reload the cache if required */
576
int
577
netsnmp_cache_check_and_reload(netsnmp_cache * cache)
578
0
{
579
0
    if (!cache) {
580
0
        DEBUGMSGT(("helper:cache_handler", " no cache\n"));
581
0
        return 0; /* ?? or -1 */
582
0
    }
583
0
    if (!cache->valid || netsnmp_cache_check_expired(cache))
584
0
        return _cache_load( cache );
585
0
    else {
586
0
        DEBUGMSGT(("helper:cache_handler", " cached (%d)\n",
587
0
                   cache->timeout));
588
0
        return 0;
589
0
    }
590
0
}
591
592
/** Is the cache valid for a given request? */
593
int
594
netsnmp_cache_is_valid(netsnmp_agent_request_info * reqinfo, 
595
                       const char* name)
596
0
{
597
0
    netsnmp_cache  *cache = netsnmp_cache_reqinfo_extract(reqinfo, name);
598
0
    return (cache && cache->valid);
599
0
}
600
601
/** Is the cache valid for a given request?
602
 * for backwards compatability. netsnmp_cache_is_valid() is preferred.
603
 */
604
netsnmp_feature_child_of(netsnmp_is_cache_valid,netsnmp_unused);
605
#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID
606
int
607
netsnmp_is_cache_valid(netsnmp_agent_request_info * reqinfo)
608
0
{
609
0
    return netsnmp_cache_is_valid(reqinfo, CACHE_NAME);
610
0
}
611
#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID */
612
613
/** Implements the cache handler */
614
int
615
netsnmp_cache_helper_handler(netsnmp_mib_handler * handler,
616
                             netsnmp_handler_registration * reginfo,
617
                             netsnmp_agent_request_info * reqinfo,
618
                             netsnmp_request_info * requests)
619
0
{
620
0
    char addrstr[32];
621
622
0
    netsnmp_cache  *cache = NULL;
623
0
    netsnmp_handler_args cache_hint;
624
625
0
    DEBUGMSGTL(("helper:cache_handler", "Got request (%d) for %s: ",
626
0
                reqinfo->mode, reginfo->handlerName));
627
0
    DEBUGMSGOID(("helper:cache_handler", reginfo->rootoid,
628
0
                 reginfo->rootoid_len));
629
0
    DEBUGMSG(("helper:cache_handler", "\n"));
630
631
0
    netsnmp_assert(handler->flags & MIB_HANDLER_AUTO_NEXT);
632
633
0
    cache = (netsnmp_cache *) handler->myvoid;
634
0
    if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
635
0
                               NETSNMP_DS_AGENT_NO_CACHING) ||
636
0
        !cache || !cache->enabled || !cache->load_cache) {
637
0
        DEBUGMSGT(("helper:cache_handler", " caching disabled or "
638
0
                   "cache not found, disabled or had no load method\n"));
639
0
        return SNMP_ERR_NOERROR;
640
0
    }
641
0
    snprintf(addrstr, sizeof(addrstr), "%p", cache);
642
0
    DEBUGMSGTL(("helper:cache_handler", "using cache %s: ", addrstr));
643
0
    DEBUGMSGOID(("helper:cache_handler", cache->rootoid, cache->rootoid_len));
644
0
    DEBUGMSG(("helper:cache_handler", "\n"));
645
646
    /*
647
     * Make the handler-chain parameters available to
648
     * the cache_load hook routine.
649
     */
650
0
    cache_hint.handler = handler;
651
0
    cache_hint.reginfo = reginfo;
652
0
    cache_hint.reqinfo = reqinfo;
653
0
    cache_hint.requests = requests;
654
0
    cache->cache_hint = &cache_hint;
655
656
0
    switch (reqinfo->mode) {
657
658
0
    case MODE_GET:
659
0
    case MODE_GETNEXT:
660
0
    case MODE_GETBULK:
661
0
#ifndef NETSNMP_NO_WRITE_SUPPORT
662
0
    case MODE_SET_RESERVE1:
663
0
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
664
665
        /*
666
         * only touch cache once per pdu request, to prevent a cache
667
         * reload while a module is using cached data.
668
         *
669
         * XXX: this won't catch a request reloading the cache while
670
         * a previous (delegated) request is still using the cache.
671
         * maybe use a reference counter?
672
         */
673
0
        if (netsnmp_cache_is_valid(reqinfo, addrstr))
674
0
            break;
675
676
        /*
677
         * call the load hook, and update the cache timestamp.
678
         * If it's not already there, add to reqinfo
679
         */
680
0
        netsnmp_cache_check_and_reload(cache);
681
0
        netsnmp_cache_reqinfo_insert(cache, reqinfo, addrstr);
682
        /** next handler called automatically - 'AUTO_NEXT' */
683
0
        break;
684
685
0
#ifndef NETSNMP_NO_WRITE_SUPPORT
686
0
    case MODE_SET_RESERVE2:
687
0
    case MODE_SET_FREE:
688
0
    case MODE_SET_ACTION:
689
0
    case MODE_SET_UNDO:
690
0
        netsnmp_assert(netsnmp_cache_is_valid(reqinfo, addrstr));
691
        /** next handler called automatically - 'AUTO_NEXT' */
692
0
        break;
693
694
        /*
695
         * A (successful) SET request wouldn't typically trigger a reload of
696
         *  the cache, but might well invalidate the current contents.
697
         * Only do this on the last pass through.
698
         */
699
0
    case MODE_SET_COMMIT:
700
0
        if (cache->valid && 
701
0
            ! (cache->flags & NETSNMP_CACHE_DONT_INVALIDATE_ON_SET) ) {
702
0
            cache->free_cache(cache, cache->magic);
703
0
            cache->valid = 0;
704
0
        }
705
        /** next handler called automatically - 'AUTO_NEXT' */
706
0
        break;
707
0
#endif /* NETSNMP_NO_WRITE_SUPPORT */
708
709
0
    default:
710
0
        snmp_log(LOG_WARNING, "cache_handler: Unrecognised mode (%d)\n",
711
0
                 reqinfo->mode);
712
0
        netsnmp_request_set_error_all(requests, SNMP_ERR_GENERR);
713
0
        return SNMP_ERR_GENERR;
714
0
    }
715
0
    if (cache->flags & NETSNMP_CACHE_RESET_TIMER_ON_USE)
716
0
        netsnmp_set_monotonic_marker(&cache->timestampM);
717
0
    return SNMP_ERR_NOERROR;
718
0
}
719
720
static void
721
_cache_free( netsnmp_cache *cache )
722
0
{
723
0
    if (NULL != cache->free_cache) {
724
0
        cache->free_cache(cache, cache->magic);
725
0
        cache->valid = 0;
726
0
    }
727
0
}
728
729
static int
730
_cache_load( netsnmp_cache *cache )
731
0
{
732
0
    int ret = -1;
733
734
    /*
735
     * If we've got a valid cache, then release it before reloading
736
     */
737
0
    if (cache->valid &&
738
0
        (! (cache->flags & NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD)))
739
0
        _cache_free(cache);
740
741
0
    if ( cache->load_cache)
742
0
        ret = cache->load_cache(cache, cache->magic);
743
0
    if (ret < 0) {
744
0
        DEBUGMSGT(("helper:cache_handler", " load failed (%d)\n", ret));
745
0
        cache->valid = 0;
746
0
        return ret;
747
0
    }
748
0
    cache->valid = 1;
749
0
    cache->expired = 0;
750
751
    /*
752
     * If we didn't previously have any valid caches outstanding,
753
     *   then schedule a pass of the auto-release routine.
754
     */
755
0
    if ((!cache_outstanding_valid) &&
756
0
        (! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))) {
757
0
        snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
758
0
                            0, release_cached_resources, NULL);
759
0
        cache_outstanding_valid = 1;
760
0
    }
761
0
    netsnmp_set_monotonic_marker(&cache->timestampM);
762
0
    DEBUGMSGT(("helper:cache_handler", " loaded (%d)\n", cache->timeout));
763
764
0
    return ret;
765
0
}
766
767
768
769
/** run regularly to automatically release cached resources.
770
 * xxx - method to prevent cache from expiring while a request
771
 *     is being processed (e.g. delegated request). proposal:
772
 *     set a flag, which would be cleared when request finished
773
 *     (which could be accomplished by a dummy data list item in
774
 *     agent req info & custom free function).
775
 */
776
void
777
release_cached_resources(unsigned int regNo, void *clientargs)
778
0
{
779
0
    netsnmp_cache  *cache = NULL;
780
0
    int do_trim = 0;
781
782
0
    cache_outstanding_valid = 0;
783
0
    DEBUGMSGTL(("helper:cache_handler", "running auto-release\n"));
784
0
    for (cache = cache_head; cache; cache = cache->next) {
785
0
        DEBUGMSGTL(("helper:cache_handler"," checking %p (flags 0x%x)\n",
786
0
                     cache, cache->flags));
787
0
        if (cache->valid &&
788
0
            ! (cache->flags & NETSNMP_CACHE_DONT_AUTO_RELEASE)) {
789
0
            DEBUGMSGTL(("helper:cache_handler","  releasing %p\n", cache));
790
            /*
791
             * Check to see if this cache has timed out.
792
             * If so, release the cached resources.
793
             * Otherwise, note that we still have at
794
             *   least one active cache.
795
             */
796
0
            if (netsnmp_cache_check_expired(cache)) {
797
0
                if(! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED)) {
798
0
                    _cache_free(cache);
799
0
                    if (cache->free_cache && !cache->timer_id)
800
0
                        do_trim = 1;
801
0
                }
802
0
            } else {
803
0
                cache_outstanding_valid = 1;
804
0
            }
805
0
        }
806
0
    }
807
808
0
    if (do_trim) {
809
0
#ifdef HAVE_MALLOC_TRIM
810
0
        malloc_trim(0);
811
0
#endif
812
0
    }
813
814
    /*
815
     * If there are any caches still valid & active,
816
     *   then schedule another pass.
817
     */
818
0
    if (cache_outstanding_valid) {
819
0
        snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
820
0
                            0, release_cached_resources, NULL);
821
0
    }
822
0
}
823
/**  @} */
824