blob: 520d463e2c3532675b4a5c101c9dd38b9f9efd8d [file] [log] [blame]
#include <net-snmp/net-snmp-config.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>
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.
*
*
* 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
*
* @{
*/
/** 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;
}
/** find existing cache
*/
netsnmp_cache *
netsnmp_cache_find_by_oid(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;
}
/** returns a cache
*/
netsnmp_cache *
netsnmp_cache_create(int timeout, NetsnmpCacheLoad * load_hook,
NetsnmpCacheFree * free_hook,
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;
}
/** 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 %d 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 %d 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;
}
/** 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,
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;
}
return ret;
}
/** functionally the same as calling netsnmp_register_handler() but also
* injects a cache handler at the same time for you. */
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);
}
/** functionally the same as calling netsnmp_register_handler() but also
* injects a cache handler at the same time for you. */
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);
}
NETSNMP_STATIC_INLINE char *
_build_cache_name(const char *name)
{
char *dup = 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_agent_get_list_data(reqinfo, cache_name);
SNMP_FREE(cache_name);
return result;
}
/** Extract the cache information for a given request (PDU) */
netsnmp_cache *
netsnmp_extract_cache_info(netsnmp_agent_request_info * reqinfo)
{
return netsnmp_cache_reqinfo_extract(reqinfo, CACHE_NAME);
}
/** 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->valid || (NULL == cache->timestamp) || (-1 == cache->timeout))
cache->expired = 1;
else
cache->expired = atime_ready(cache->timestamp, 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.
*/
int
netsnmp_is_cache_valid(netsnmp_agent_request_info * reqinfo)
{
return netsnmp_cache_is_valid(reqinfo, CACHE_NAME);
}
/** 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), "%p", 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:
case MODE_SET_RESERVE1: {
/*
* 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))
return SNMP_ERR_NOERROR;
/*
* 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' */
}
return SNMP_ERR_NOERROR;
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' */
return SNMP_ERR_NOERROR;
/*
* 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' */
return SNMP_ERR_NOERROR;
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;
}
netsnmp_request_set_error_all(requests, SNMP_ERR_GENERR);
return SNMP_ERR_GENERR; /* should never get here */
}
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;
}
if (cache->timestamp)
atime_setMarker(cache->timestamp);
else
cache->timestamp = atime_newMarker();
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);
}
}
/** @} */