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