summaryrefslogtreecommitdiff
path: root/agent/helpers/cache_handler.c
diff options
context:
space:
mode:
Diffstat (limited to 'agent/helpers/cache_handler.c')
-rw-r--r--agent/helpers/cache_handler.c771
1 files changed, 771 insertions, 0 deletions
diff --git a/agent/helpers/cache_handler.c b/agent/helpers/cache_handler.c
new file mode 100644
index 0000000..520a4cf
--- /dev/null
+++ b/agent/helpers/cache_handler.c
@@ -0,0 +1,771 @@
+/* Portions of this file are subject to the following copyright(s). See
+ * the Net-SNMP's COPYING file for more details and other copyrights
+ * that may apply:
+ */
+/*
+ * Portions of this file are copyrighted by:
+ * Copyright (C) 2007 Apple, Inc. All rights reserved.
+ * Use is subject to license terms specified in the COPYING file
+ * distributed with the Net-SNMP package.
+ */
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-features.h>
+
+#if HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+
+#include <net-snmp/agent/cache_handler.h>
+
+netsnmp_feature_child_of(cache_handler, mib_helpers)
+
+netsnmp_feature_child_of(cache_find_by_oid, cache_handler)
+netsnmp_feature_child_of(cache_get_head, cache_handler)
+
+static netsnmp_cache *cache_head = NULL;
+static int cache_outstanding_valid = 0;
+static int _cache_load( netsnmp_cache *cache );
+
+#define CACHE_RELEASE_FREQUENCY 60 /* Check for expired caches every 60s */
+
+void release_cached_resources(unsigned int regNo,
+ void *clientargs);
+
+/** @defgroup cache_handler cache_handler
+ * Maintains a cache of data for use by lower level handlers.
+ * @ingroup utilities
+ * This helper checks to see whether the data has been loaded "recently"
+ * (according to the timeout for that particular cache) and calls the
+ * registered "load_cache" routine if necessary.
+ * The lower handlers can then work with this local cached data.
+ *
+ * A timeout value of -1 will cause netsnmp_cache_check_expired() to
+ * always return true, and thus the cache will be reloaded for every
+ * request.
+ *
+ * To minimze resource use by the agent, a periodic callback checks for
+ * expired caches, and will call the free_cache function for any expired
+ * cache.
+ *
+ * The load_cache routine should return a negative number if the cache
+ * was not successfully loaded. 0 or any positive number indicates successs.
+ *
+ *
+ * Several flags can be set to affect the operations on the cache.
+ *
+ * If NETSNMP_CACHE_DONT_INVALIDATE_ON_SET is set, the free_cache method
+ * will not be called after a set request has processed. It is assumed that
+ * the lower mib handler using the cache has maintained cache consistency.
+ *
+ * If NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD is set, the free_cache method
+ * will not be called before the load_cache method is called. It is assumed
+ * that the load_cache routine will properly deal with being called with a
+ * valid cache.
+ *
+ * If NETSNMP_CACHE_DONT_FREE_EXPIRED is set, the free_cache method will
+ * not be called with the cache expires. The expired flag will be set, but
+ * the valid flag will not be cleared. It is assumed that the load_cache
+ * routine will properly deal with being called with a valid cache.
+ *
+ * If NETSNMP_CACHE_PRELOAD is set when a the cache handler is created,
+ * the cache load routine will be called immediately.
+ *
+ * If NETSNMP_CACHE_DONT_AUTO_RELEASE is set, the periodic callback that
+ * checks for expired caches will skip the cache. The cache will only be
+ * checked for expiration when a request triggers the cache handler. This
+ * is useful if the cache has it's own periodic callback to keep the cache
+ * fresh.
+ *
+ * If NETSNMP_CACHE_AUTO_RELOAD is set, a timer will be set up to reload
+ * the cache when it expires. This is useful for keeping the cache fresh,
+ * even in the absence of incoming snmp requests.
+ *
+ * If NETSNMP_CACHE_RESET_TIMER_ON_USE is set, the expiry timer will be
+ * reset on each cache access. In practice the 'timeout' becomes a timer
+ * which triggers when the cache is no longer needed. This is useful
+ * if the cache is automatically kept synchronized: e.g. by receiving
+ * change notifications from Netlink, inotify or similar. This should
+ * not be used if cache is not synchronized automatically as it would
+ * result in stale cache information when if polling happens too fast.
+ *
+ *
+ * Here are some suggestions for some common situations.
+ *
+ * Cached File:
+ * If your table is based on a file that may periodically change,
+ * you can test the modification date to see if the file has
+ * changed since the last cache load. To get the cache helper to call
+ * the load function for every request, set the timeout to -1, which
+ * will cause the cache to always report that it is expired. This means
+ * that you will want to prevent the agent from flushing the cache when
+ * it has expired, and you will have to flush it manually if you
+ * detect that the file has changed. To accomplish this, set the
+ * following flags:
+ *
+ * NETSNMP_CACHE_DONT_FREE_EXPIRED
+ * NETSNMP_CACHE_DONT_AUTO_RELEASE
+ *
+ *
+ * Constant (periodic) reload:
+ * If you want the cache kept up to date regularly, even if no requests
+ * for the table are received, you can have your cache load routine
+ * called periodically. This is very useful if you need to monitor the
+ * data for changes (eg a <i>LastChanged</i> object). You will need to
+ * prevent the agent from flushing the cache when it expires. Set the
+ * cache timeout to the frequency, in seconds, that you wish to
+ * reload your cache, and set the following flags:
+ *
+ * NETSNMP_CACHE_DONT_FREE_EXPIRED
+ * NETSNMP_CACHE_DONT_AUTO_RELEASE
+ * NETSNMP_CACHE_AUTO_RELOAD
+ *
+ * Dynamically updated, unloaded after timeout:
+ * If the cache is kept up to date dynamically by listening for
+ * change notifications somehow, but it should not be in memory
+ * if it's not needed. Set the following flag:
+ *
+ * NETSNMP_CACHE_RESET_TIMER_ON_USE
+ *
+ * @{
+ */
+
+static void
+_cache_free( netsnmp_cache *cache );
+
+#ifndef NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD
+/** get cache head
+ * @internal
+ * unadvertised function to get cache head. You really should not
+ * do this, since the internal storage mechanism might change.
+ */
+netsnmp_cache *
+netsnmp_cache_get_head(void)
+{
+ return cache_head;
+}
+#endif /* NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD */
+
+#ifndef NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID
+/** find existing cache
+ */
+netsnmp_cache *
+netsnmp_cache_find_by_oid(const oid * rootoid, int rootoid_len)
+{
+ netsnmp_cache *cache;
+
+ for (cache = cache_head; cache; cache = cache->next) {
+ if (0 == netsnmp_oid_equals(cache->rootoid, cache->rootoid_len,
+ rootoid, rootoid_len))
+ return cache;
+ }
+
+ return NULL;
+}
+#endif /* NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID */
+
+/** returns a cache
+ */
+netsnmp_cache *
+netsnmp_cache_create(int timeout, NetsnmpCacheLoad * load_hook,
+ NetsnmpCacheFree * free_hook,
+ const oid * rootoid, int rootoid_len)
+{
+ netsnmp_cache *cache = NULL;
+
+ cache = SNMP_MALLOC_TYPEDEF(netsnmp_cache);
+ if (NULL == cache) {
+ snmp_log(LOG_ERR,"malloc error in netsnmp_cache_create\n");
+ return NULL;
+ }
+ cache->timeout = timeout;
+ cache->load_cache = load_hook;
+ cache->free_cache = free_hook;
+ cache->enabled = 1;
+
+ if(0 == cache->timeout)
+ cache->timeout = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID,
+ NETSNMP_DS_AGENT_CACHE_TIMEOUT);
+
+
+ /*
+ * Add the registered OID information, and tack
+ * this onto the list for cache SNMP management
+ *
+ * Note that this list is not ordered.
+ * table_iterator rules again!
+ */
+ if (rootoid) {
+ cache->rootoid = snmp_duplicate_objid(rootoid, rootoid_len);
+ cache->rootoid_len = rootoid_len;
+ cache->next = cache_head;
+ if (cache_head)
+ cache_head->prev = cache;
+ cache_head = cache;
+ }
+
+ return cache;
+}
+
+static netsnmp_cache *
+netsnmp_cache_ref(netsnmp_cache *cache)
+{
+ cache->refcnt++;
+ return cache;
+}
+
+static void
+netsnmp_cache_deref(netsnmp_cache *cache)
+{
+ if (--cache->refcnt == 0) {
+ netsnmp_cache_remove(cache);
+ netsnmp_cache_free(cache);
+ }
+}
+
+/** frees a cache
+ */
+int
+netsnmp_cache_free(netsnmp_cache *cache)
+{
+ netsnmp_cache *pos;
+
+ if (NULL == cache)
+ return SNMPERR_SUCCESS;
+
+ for (pos = cache_head; pos; pos = pos->next) {
+ if (pos == cache) {
+ size_t out_len = 0;
+ size_t buf_len = 0;
+ char *buf = NULL;
+
+ sprint_realloc_objid((u_char **) &buf, &buf_len, &out_len,
+ 1, pos->rootoid, pos->rootoid_len);
+ snmp_log(LOG_WARNING,
+ "not freeing cache with root OID %s (still in list)\n",
+ buf);
+ free(buf);
+ return SNMP_ERR_GENERR;
+ }
+ }
+
+ if(0 != cache->timer_id)
+ netsnmp_cache_timer_stop(cache);
+
+ if (cache->valid)
+ _cache_free(cache);
+
+ if (cache->timestampM)
+ free(cache->timestampM);
+
+ if (cache->rootoid)
+ free(cache->rootoid);
+
+ free(cache);
+
+ return SNMPERR_SUCCESS;
+}
+
+/** removes a cache
+ */
+int
+netsnmp_cache_remove(netsnmp_cache *cache)
+{
+ netsnmp_cache *cur,*prev;
+
+ if (!cache || !cache_head)
+ return -1;
+
+ if (cache == cache_head) {
+ cache_head = cache_head->next;
+ if (cache_head)
+ cache_head->prev = NULL;
+ return 0;
+ }
+
+ prev = cache_head;
+ cur = cache_head->next;
+ for (; cur; prev = cur, cur = cur->next) {
+ if (cache == cur) {
+ prev->next = cur->next;
+ if (cur->next)
+ cur->next->prev = cur->prev;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/** callback function to call cache load function */
+static void
+_timer_reload(unsigned int regNo, void *clientargs)
+{
+ netsnmp_cache *cache = (netsnmp_cache *)clientargs;
+
+ DEBUGMSGT(("cache_timer:start", "loading cache %p\n", cache));
+
+ cache->expired = 1;
+
+ _cache_load(cache);
+}
+
+/** starts the recurring cache_load callback */
+unsigned int
+netsnmp_cache_timer_start(netsnmp_cache *cache)
+{
+ if(NULL == cache)
+ return 0;
+
+ DEBUGMSGTL(( "cache_timer:start", "OID: "));
+ DEBUGMSGOID(("cache_timer:start", cache->rootoid, cache->rootoid_len));
+ DEBUGMSG(( "cache_timer:start", "\n"));
+
+ if(0 != cache->timer_id) {
+ snmp_log(LOG_WARNING, "cache has existing timer id.\n");
+ return cache->timer_id;
+ }
+
+ if(! (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)) {
+ snmp_log(LOG_ERR,
+ "cache_timer_start called but auto_reload not set.\n");
+ return 0;
+ }
+
+ cache->timer_id = snmp_alarm_register(cache->timeout, SA_REPEAT,
+ _timer_reload, cache);
+ if(0 == cache->timer_id) {
+ snmp_log(LOG_ERR,"could not register alarm\n");
+ return 0;
+ }
+
+ cache->flags &= ~NETSNMP_CACHE_AUTO_RELOAD;
+ DEBUGMSGT(("cache_timer:start",
+ "starting timer %lu for cache %p\n", cache->timer_id, cache));
+ return cache->timer_id;
+}
+
+/** stops the recurring cache_load callback */
+void
+netsnmp_cache_timer_stop(netsnmp_cache *cache)
+{
+ if(NULL == cache)
+ return;
+
+ if(0 == cache->timer_id) {
+ snmp_log(LOG_WARNING, "cache has no timer id.\n");
+ return;
+ }
+
+ DEBUGMSGT(("cache_timer:stop",
+ "stopping timer %lu for cache %p\n", cache->timer_id, cache));
+
+ snmp_alarm_unregister(cache->timer_id);
+ cache->flags |= NETSNMP_CACHE_AUTO_RELOAD;
+}
+
+
+/** returns a cache handler that can be injected into a given handler chain.
+ */
+netsnmp_mib_handler *
+netsnmp_cache_handler_get(netsnmp_cache* cache)
+{
+ netsnmp_mib_handler *ret = NULL;
+
+ ret = netsnmp_create_handler("cache_handler",
+ netsnmp_cache_helper_handler);
+ if (ret) {
+ ret->flags |= MIB_HANDLER_AUTO_NEXT;
+ ret->myvoid = (void *) cache;
+
+ if(NULL != cache) {
+ if ((cache->flags & NETSNMP_CACHE_PRELOAD) && ! cache->valid) {
+ /*
+ * load cache, ignore rc
+ * (failed load doesn't affect registration)
+ */
+ (void)_cache_load(cache);
+ }
+ if (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)
+ netsnmp_cache_timer_start(cache);
+
+ }
+ }
+ return ret;
+}
+
+/** Makes sure that memory allocated for the cache is freed when the handler
+ * is unregistered.
+ */
+void netsnmp_cache_handler_owns_cache(netsnmp_mib_handler *handler)
+{
+ netsnmp_assert(handler->myvoid);
+ ((netsnmp_cache *)handler->myvoid)->refcnt++;
+ handler->data_clone = (void *(*)(void *))netsnmp_cache_ref;
+ handler->data_free = (void(*)(void*))netsnmp_cache_deref;
+}
+
+/** returns a cache handler that can be injected into a given handler chain.
+ */
+netsnmp_mib_handler *
+netsnmp_get_cache_handler(int timeout, NetsnmpCacheLoad * load_hook,
+ NetsnmpCacheFree * free_hook,
+ const oid * rootoid, int rootoid_len)
+{
+ netsnmp_mib_handler *ret = NULL;
+ netsnmp_cache *cache = NULL;
+
+ ret = netsnmp_cache_handler_get(NULL);
+ if (ret) {
+ cache = netsnmp_cache_create(timeout, load_hook, free_hook,
+ rootoid, rootoid_len);
+ ret->myvoid = (void *) cache;
+ netsnmp_cache_handler_owns_cache(ret);
+ }
+ return ret;
+}
+
+/** functionally the same as calling netsnmp_register_handler() but also
+ * injects a cache handler at the same time for you. */
+netsnmp_feature_child_of(netsnmp_cache_handler_register,netsnmp_unused)
+#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER
+int
+netsnmp_cache_handler_register(netsnmp_handler_registration * reginfo,
+ netsnmp_cache* cache)
+{
+ netsnmp_mib_handler *handler = NULL;
+ handler = netsnmp_cache_handler_get(cache);
+
+ netsnmp_inject_handler(reginfo, handler);
+ return netsnmp_register_handler(reginfo);
+}
+#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER */
+
+/** functionally the same as calling netsnmp_register_handler() but also
+ * injects a cache handler at the same time for you. */
+netsnmp_feature_child_of(netsnmp_register_cache_handler,netsnmp_unused)
+#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER
+int
+netsnmp_register_cache_handler(netsnmp_handler_registration * reginfo,
+ int timeout, NetsnmpCacheLoad * load_hook,
+ NetsnmpCacheFree * free_hook)
+{
+ netsnmp_mib_handler *handler = NULL;
+ handler = netsnmp_get_cache_handler(timeout, load_hook, free_hook,
+ reginfo->rootoid,
+ reginfo->rootoid_len);
+
+ netsnmp_inject_handler(reginfo, handler);
+ return netsnmp_register_handler(reginfo);
+}
+#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER */
+
+static char *
+_build_cache_name(const char *name)
+{
+ char *dup = (char*)malloc(strlen(name) + strlen(CACHE_NAME) + 2);
+ if (NULL == dup)
+ return NULL;
+ sprintf(dup, "%s:%s", CACHE_NAME, name);
+ return dup;
+}
+
+/** Insert the cache information for a given request (PDU) */
+void
+netsnmp_cache_reqinfo_insert(netsnmp_cache* cache,
+ netsnmp_agent_request_info * reqinfo,
+ const char *name)
+{
+ char *cache_name = _build_cache_name(name);
+ if (NULL == netsnmp_agent_get_list_data(reqinfo, cache_name)) {
+ DEBUGMSGTL(("verbose:helper:cache_handler", " adding '%s' to %p\n",
+ cache_name, reqinfo));
+ netsnmp_agent_add_list_data(reqinfo,
+ netsnmp_create_data_list(cache_name,
+ cache, NULL));
+ }
+ SNMP_FREE(cache_name);
+}
+
+/** Extract the cache information for a given request (PDU) */
+netsnmp_cache *
+netsnmp_cache_reqinfo_extract(netsnmp_agent_request_info * reqinfo,
+ const char *name)
+{
+ netsnmp_cache *result;
+ char *cache_name = _build_cache_name(name);
+ result = (netsnmp_cache*)netsnmp_agent_get_list_data(reqinfo, cache_name);
+ SNMP_FREE(cache_name);
+ return result;
+}
+
+/** Extract the cache information for a given request (PDU) */
+netsnmp_feature_child_of(netsnmp_extract_cache_info,netsnmp_unused)
+#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO
+netsnmp_cache *
+netsnmp_extract_cache_info(netsnmp_agent_request_info * reqinfo)
+{
+ return netsnmp_cache_reqinfo_extract(reqinfo, CACHE_NAME);
+}
+#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO */
+
+
+/** Check if the cache timeout has passed. Sets and return the expired flag. */
+int
+netsnmp_cache_check_expired(netsnmp_cache *cache)
+{
+ if(NULL == cache)
+ return 0;
+ if (cache->expired)
+ return 1;
+ if(!cache->valid || (NULL == cache->timestampM) || (-1 == cache->timeout))
+ cache->expired = 1;
+ else
+ cache->expired = netsnmp_ready_monotonic(cache->timestampM,
+ 1000 * cache->timeout);
+
+ return cache->expired;
+}
+
+/** Reload the cache if required */
+int
+netsnmp_cache_check_and_reload(netsnmp_cache * cache)
+{
+ if (!cache) {
+ DEBUGMSGT(("helper:cache_handler", " no cache\n"));
+ return 0; /* ?? or -1 */
+ }
+ if (!cache->valid || netsnmp_cache_check_expired(cache))
+ return _cache_load( cache );
+ else {
+ DEBUGMSGT(("helper:cache_handler", " cached (%d)\n",
+ cache->timeout));
+ return 0;
+ }
+}
+
+/** Is the cache valid for a given request? */
+int
+netsnmp_cache_is_valid(netsnmp_agent_request_info * reqinfo,
+ const char* name)
+{
+ netsnmp_cache *cache = netsnmp_cache_reqinfo_extract(reqinfo, name);
+ return (cache && cache->valid);
+}
+
+/** Is the cache valid for a given request?
+ * for backwards compatability. netsnmp_cache_is_valid() is preferred.
+ */
+netsnmp_feature_child_of(netsnmp_is_cache_valid,netsnmp_unused)
+#ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID
+int
+netsnmp_is_cache_valid(netsnmp_agent_request_info * reqinfo)
+{
+ return netsnmp_cache_is_valid(reqinfo, CACHE_NAME);
+}
+#endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID */
+
+/** Implements the cache handler */
+int
+netsnmp_cache_helper_handler(netsnmp_mib_handler * handler,
+ netsnmp_handler_registration * reginfo,
+ netsnmp_agent_request_info * reqinfo,
+ netsnmp_request_info * requests)
+{
+ char addrstr[32];
+
+ netsnmp_cache *cache = NULL;
+ netsnmp_handler_args cache_hint;
+
+ DEBUGMSGTL(("helper:cache_handler", "Got request (%d) for %s: ",
+ reqinfo->mode, reginfo->handlerName));
+ DEBUGMSGOID(("helper:cache_handler", reginfo->rootoid,
+ reginfo->rootoid_len));
+ DEBUGMSG(("helper:cache_handler", "\n"));
+
+ netsnmp_assert(handler->flags & MIB_HANDLER_AUTO_NEXT);
+
+ cache = (netsnmp_cache *) handler->myvoid;
+ if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
+ NETSNMP_DS_AGENT_NO_CACHING) ||
+ !cache || !cache->enabled || !cache->load_cache) {
+ DEBUGMSGT(("helper:cache_handler", " caching disabled or "
+ "cache not found, disabled or had no load method\n"));
+ return SNMP_ERR_NOERROR;
+ }
+ snprintf(addrstr,sizeof(addrstr), "%ld", (long int)cache);
+ DEBUGMSGTL(("helper:cache_handler", "using cache %s: ", addrstr));
+ DEBUGMSGOID(("helper:cache_handler", cache->rootoid, cache->rootoid_len));
+ DEBUGMSG(("helper:cache_handler", "\n"));
+
+ /*
+ * Make the handler-chain parameters available to
+ * the cache_load hook routine.
+ */
+ cache_hint.handler = handler;
+ cache_hint.reginfo = reginfo;
+ cache_hint.reqinfo = reqinfo;
+ cache_hint.requests = requests;
+ cache->cache_hint = &cache_hint;
+
+ switch (reqinfo->mode) {
+
+ case MODE_GET:
+ case MODE_GETNEXT:
+ case MODE_GETBULK:
+#ifndef NETSNMP_NO_WRITE_SUPPORT
+ case MODE_SET_RESERVE1:
+#endif /* !NETSNMP_NO_WRITE_SUPPORT */
+
+ /*
+ * only touch cache once per pdu request, to prevent a cache
+ * reload while a module is using cached data.
+ *
+ * XXX: this won't catch a request reloading the cache while
+ * a previous (delegated) request is still using the cache.
+ * maybe use a reference counter?
+ */
+ if (netsnmp_cache_is_valid(reqinfo, addrstr))
+ break;
+
+ /*
+ * call the load hook, and update the cache timestamp.
+ * If it's not already there, add to reqinfo
+ */
+ netsnmp_cache_check_and_reload(cache);
+ netsnmp_cache_reqinfo_insert(cache, reqinfo, addrstr);
+ /** next handler called automatically - 'AUTO_NEXT' */
+ break;
+
+#ifndef NETSNMP_NO_WRITE_SUPPORT
+ case MODE_SET_RESERVE2:
+ case MODE_SET_FREE:
+ case MODE_SET_ACTION:
+ case MODE_SET_UNDO:
+ netsnmp_assert(netsnmp_cache_is_valid(reqinfo, addrstr));
+ /** next handler called automatically - 'AUTO_NEXT' */
+ break;
+
+ /*
+ * A (successful) SET request wouldn't typically trigger a reload of
+ * the cache, but might well invalidate the current contents.
+ * Only do this on the last pass through.
+ */
+ case MODE_SET_COMMIT:
+ if (cache->valid &&
+ ! (cache->flags & NETSNMP_CACHE_DONT_INVALIDATE_ON_SET) ) {
+ cache->free_cache(cache, cache->magic);
+ cache->valid = 0;
+ }
+ /** next handler called automatically - 'AUTO_NEXT' */
+ break;
+#endif /* NETSNMP_NO_WRITE_SUPPORT */
+
+ default:
+ snmp_log(LOG_WARNING, "cache_handler: Unrecognised mode (%d)\n",
+ reqinfo->mode);
+ netsnmp_request_set_error_all(requests, SNMP_ERR_GENERR);
+ return SNMP_ERR_GENERR;
+ }
+ if (cache->flags & NETSNMP_CACHE_RESET_TIMER_ON_USE)
+ netsnmp_set_monotonic_marker(&cache->timestampM);
+ return SNMP_ERR_NOERROR;
+}
+
+static void
+_cache_free( netsnmp_cache *cache )
+{
+ if (NULL != cache->free_cache) {
+ cache->free_cache(cache, cache->magic);
+ cache->valid = 0;
+ }
+}
+
+static int
+_cache_load( netsnmp_cache *cache )
+{
+ int ret = -1;
+
+ /*
+ * If we've got a valid cache, then release it before reloading
+ */
+ if (cache->valid &&
+ (! (cache->flags & NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD)))
+ _cache_free(cache);
+
+ if ( cache->load_cache)
+ ret = cache->load_cache(cache, cache->magic);
+ if (ret < 0) {
+ DEBUGMSGT(("helper:cache_handler", " load failed (%d)\n", ret));
+ cache->valid = 0;
+ return ret;
+ }
+ cache->valid = 1;
+ cache->expired = 0;
+
+ /*
+ * If we didn't previously have any valid caches outstanding,
+ * then schedule a pass of the auto-release routine.
+ */
+ if ((!cache_outstanding_valid) &&
+ (! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))) {
+ snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
+ 0, release_cached_resources, NULL);
+ cache_outstanding_valid = 1;
+ }
+ netsnmp_set_monotonic_marker(&cache->timestampM);
+ DEBUGMSGT(("helper:cache_handler", " loaded (%d)\n", cache->timeout));
+
+ return ret;
+}
+
+
+
+/** run regularly to automatically release cached resources.
+ * xxx - method to prevent cache from expiring while a request
+ * is being processed (e.g. delegated request). proposal:
+ * set a flag, which would be cleared when request finished
+ * (which could be acomplished by a dummy data list item in
+ * agent req info & custom free function).
+ */
+void
+release_cached_resources(unsigned int regNo, void *clientargs)
+{
+ netsnmp_cache *cache = NULL;
+
+ cache_outstanding_valid = 0;
+ DEBUGMSGTL(("helper:cache_handler", "running auto-release\n"));
+ for (cache = cache_head; cache; cache = cache->next) {
+ DEBUGMSGTL(("helper:cache_handler"," checking %p (flags 0x%x)\n",
+ cache, cache->flags));
+ if (cache->valid &&
+ ! (cache->flags & NETSNMP_CACHE_DONT_AUTO_RELEASE)) {
+ DEBUGMSGTL(("helper:cache_handler"," releasing %p\n", cache));
+ /*
+ * Check to see if this cache has timed out.
+ * If so, release the cached resources.
+ * Otherwise, note that we still have at
+ * least one active cache.
+ */
+ if (netsnmp_cache_check_expired(cache)) {
+ if(! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))
+ _cache_free(cache);
+ } else {
+ cache_outstanding_valid = 1;
+ }
+ }
+ }
+ /*
+ * If there are any caches still valid & active,
+ * then schedule another pass.
+ */
+ if (cache_outstanding_valid) {
+ snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
+ 0, release_cached_resources, NULL);
+ }
+}
+/** @} */
+