net-snmp 5.7
cache_handler.c
00001 /* Portions of this file are subject to the following copyright(s).  See
00002  * the Net-SNMP's COPYING file for more details and other copyrights
00003  * that may apply:
00004  */
00005 /*
00006  * Portions of this file are copyrighted by:
00007  * Copyright (C) 2007 Apple, Inc. All rights reserved.
00008  * Use is subject to license terms specified in the COPYING file
00009  * distributed with the Net-SNMP package.
00010  */
00011 #include <net-snmp/net-snmp-config.h>
00012 #include <net-snmp/net-snmp-features.h>
00013 
00014 #if HAVE_STRING_H
00015 #include <string.h>
00016 #else
00017 #include <strings.h>
00018 #endif
00019 
00020 #include <net-snmp/net-snmp-includes.h>
00021 #include <net-snmp/agent/net-snmp-agent-includes.h>
00022 
00023 #include <net-snmp/agent/cache_handler.h>
00024 
00025 netsnmp_feature_child_of(cache_handler, mib_helpers)
00026 
00027 netsnmp_feature_child_of(cache_find_by_oid, cache_handler)
00028 netsnmp_feature_child_of(cache_get_head, cache_handler)
00029 
00030 static netsnmp_cache  *cache_head = NULL;
00031 static int             cache_outstanding_valid = 0;
00032 static int             _cache_load( netsnmp_cache *cache );
00033 
00034 #define CACHE_RELEASE_FREQUENCY 60      /* Check for expired caches every 60s */
00035 
00036 void            release_cached_resources(unsigned int regNo,
00037                                          void *clientargs);
00038 
00137 static void
00138 _cache_free( netsnmp_cache *cache );
00139 
00140 #ifndef NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD
00141 
00146 netsnmp_cache *
00147 netsnmp_cache_get_head(void)
00148 {
00149     return cache_head;
00150 }
00151 #endif /* NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD */
00152 
00153 #ifndef NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID
00154 
00156 netsnmp_cache *
00157 netsnmp_cache_find_by_oid(const oid * rootoid, int rootoid_len)
00158 {
00159     netsnmp_cache  *cache;
00160 
00161     for (cache = cache_head; cache; cache = cache->next) {
00162         if (0 == netsnmp_oid_equals(cache->rootoid, cache->rootoid_len,
00163                                     rootoid, rootoid_len))
00164             return cache;
00165     }
00166     
00167     return NULL;
00168 }
00169 #endif /* NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID */
00170 
00173 netsnmp_cache *
00174 netsnmp_cache_create(int timeout, NetsnmpCacheLoad * load_hook,
00175                      NetsnmpCacheFree * free_hook,
00176                      const oid * rootoid, int rootoid_len)
00177 {
00178     netsnmp_cache  *cache = NULL;
00179 
00180     cache = SNMP_MALLOC_TYPEDEF(netsnmp_cache);
00181     if (NULL == cache) {
00182         snmp_log(LOG_ERR,"malloc error in netsnmp_cache_create\n");
00183         return NULL;
00184     }
00185     cache->timeout = timeout;
00186     cache->load_cache = load_hook;
00187     cache->free_cache = free_hook;
00188     cache->enabled = 1;
00189 
00190     if(0 == cache->timeout)
00191         cache->timeout = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID,
00192                                             NETSNMP_DS_AGENT_CACHE_TIMEOUT);
00193 
00194     
00195     /*
00196      * Add the registered OID information, and tack
00197      * this onto the list for cache SNMP management
00198      *
00199      * Note that this list is not ordered.
00200      *    table_iterator rules again!
00201      */
00202     if (rootoid) {
00203         cache->rootoid = snmp_duplicate_objid(rootoid, rootoid_len);
00204         cache->rootoid_len = rootoid_len;
00205         cache->next = cache_head;
00206         if (cache_head)
00207             cache_head->prev = cache;
00208         cache_head = cache;
00209     }
00210 
00211     return cache;
00212 }
00213 
00214 static netsnmp_cache *
00215 netsnmp_cache_ref(netsnmp_cache *cache)
00216 {
00217     cache->refcnt++;
00218     return cache;
00219 }
00220 
00221 static void
00222 netsnmp_cache_deref(netsnmp_cache *cache)
00223 {
00224     if (--cache->refcnt == 0) {
00225         netsnmp_cache_remove(cache);
00226         netsnmp_cache_free(cache);
00227     }
00228 }
00229 
00232 int
00233 netsnmp_cache_free(netsnmp_cache *cache)
00234 {
00235     netsnmp_cache  *pos;
00236 
00237     if (NULL == cache)
00238         return SNMPERR_SUCCESS;
00239 
00240     for (pos = cache_head; pos; pos = pos->next) {
00241         if (pos == cache) {
00242             size_t          out_len = 0;
00243             size_t          buf_len = 0;
00244             char           *buf = NULL;
00245 
00246             sprint_realloc_objid((u_char **) &buf, &buf_len, &out_len,
00247                                  1, pos->rootoid, pos->rootoid_len);
00248             snmp_log(LOG_WARNING,
00249                      "not freeing cache with root OID %s (still in list)\n",
00250                      buf);
00251             free(buf);
00252             return SNMP_ERR_GENERR;
00253         }
00254     }
00255 
00256     if(0 != cache->timer_id)
00257         netsnmp_cache_timer_stop(cache);
00258 
00259     if (cache->valid)
00260         _cache_free(cache);
00261 
00262     if (cache->timestamp)
00263         free(cache->timestamp);
00264 
00265     if (cache->rootoid)
00266         free(cache->rootoid);
00267 
00268     free(cache);
00269 
00270     return SNMPERR_SUCCESS;
00271 }
00272 
00275 int
00276 netsnmp_cache_remove(netsnmp_cache *cache)
00277 {
00278     netsnmp_cache  *cur,*prev;
00279 
00280     if (!cache || !cache_head)
00281         return -1;
00282 
00283     if (cache == cache_head) {
00284         cache_head = cache_head->next;
00285         if (cache_head)
00286             cache_head->prev = NULL;
00287         return 0;
00288     }
00289 
00290     prev = cache_head;
00291     cur = cache_head->next;
00292     for (; cur; prev = cur, cur = cur->next) {
00293         if (cache == cur) {
00294             prev->next = cur->next;
00295             if (cur->next)
00296                 cur->next->prev = cur->prev;
00297             return 0;
00298         }
00299     }
00300     return -1;
00301 }
00302 
00304 static void
00305 _timer_reload(unsigned int regNo, void *clientargs)
00306 {
00307     netsnmp_cache *cache = (netsnmp_cache *)clientargs;
00308 
00309     DEBUGMSGT(("cache_timer:start", "loading cache %p\n", cache));
00310 
00311     cache->expired = 1;
00312 
00313     _cache_load(cache);
00314 }
00315 
00317 unsigned int
00318 netsnmp_cache_timer_start(netsnmp_cache *cache)
00319 {
00320     if(NULL == cache)
00321         return 0;
00322 
00323     DEBUGMSGTL(( "cache_timer:start", "OID: "));
00324     DEBUGMSGOID(("cache_timer:start", cache->rootoid, cache->rootoid_len));
00325     DEBUGMSG((   "cache_timer:start", "\n"));
00326 
00327     if(0 != cache->timer_id) {
00328         snmp_log(LOG_WARNING, "cache has existing timer id.\n");
00329         return cache->timer_id;
00330     }
00331     
00332     if(! (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)) {
00333         snmp_log(LOG_ERR,
00334                  "cache_timer_start called but auto_reload not set.\n");
00335         return 0;
00336     }
00337 
00338     cache->timer_id = snmp_alarm_register(cache->timeout, SA_REPEAT,
00339                                           _timer_reload, cache);
00340     if(0 == cache->timer_id) {
00341         snmp_log(LOG_ERR,"could not register alarm\n");
00342         return 0;
00343     }
00344 
00345     cache->flags &= ~NETSNMP_CACHE_AUTO_RELOAD;
00346     DEBUGMSGT(("cache_timer:start",
00347                "starting timer %lu for cache %p\n", cache->timer_id, cache));
00348     return cache->timer_id;
00349 }
00350 
00352 void
00353 netsnmp_cache_timer_stop(netsnmp_cache *cache)
00354 {
00355     if(NULL == cache)
00356         return;
00357 
00358     if(0 == cache->timer_id) {
00359         snmp_log(LOG_WARNING, "cache has no timer id.\n");
00360         return;
00361     }
00362 
00363     DEBUGMSGT(("cache_timer:stop",
00364                "stopping timer %lu for cache %p\n", cache->timer_id, cache));
00365 
00366     snmp_alarm_unregister(cache->timer_id);
00367     cache->flags |= NETSNMP_CACHE_AUTO_RELOAD;
00368 }
00369 
00370 
00373 netsnmp_mib_handler *
00374 netsnmp_cache_handler_get(netsnmp_cache* cache)
00375 {
00376     netsnmp_mib_handler *ret = NULL;
00377     
00378     ret = netsnmp_create_handler("cache_handler",
00379                                  netsnmp_cache_helper_handler);
00380     if (ret) {
00381         ret->flags |= MIB_HANDLER_AUTO_NEXT;
00382         ret->myvoid = (void *) cache;
00383         
00384         if(NULL != cache) {
00385             if ((cache->flags & NETSNMP_CACHE_PRELOAD) && ! cache->valid) {
00386                 /*
00387                  * load cache, ignore rc
00388                  * (failed load doesn't affect registration)
00389                  */
00390                 (void)_cache_load(cache);
00391             }
00392             if (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)
00393                 netsnmp_cache_timer_start(cache);
00394             
00395         }
00396     }
00397     return ret;
00398 }
00399 
00403 void netsnmp_cache_handler_owns_cache(netsnmp_mib_handler *handler)
00404 {
00405     netsnmp_assert(handler->myvoid);
00406     ((netsnmp_cache *)handler->myvoid)->refcnt++;
00407     handler->data_clone = (void *(*)(void *))netsnmp_cache_ref;
00408     handler->data_free = (void(*)(void*))netsnmp_cache_deref;
00409 }
00410 
00413 netsnmp_mib_handler *
00414 netsnmp_get_cache_handler(int timeout, NetsnmpCacheLoad * load_hook,
00415                           NetsnmpCacheFree * free_hook,
00416                           const oid * rootoid, int rootoid_len)
00417 {
00418     netsnmp_mib_handler *ret = NULL;
00419     netsnmp_cache  *cache = NULL;
00420 
00421     ret = netsnmp_cache_handler_get(NULL);
00422     if (ret) {
00423         cache = netsnmp_cache_create(timeout, load_hook, free_hook,
00424                                      rootoid, rootoid_len);
00425         ret->myvoid = (void *) cache;
00426         netsnmp_cache_handler_owns_cache(ret);
00427     }
00428     return ret;
00429 }
00430 
00433 netsnmp_feature_child_of(netsnmp_cache_handler_register,netsnmp_unused)
00434 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER
00435 int
00436 netsnmp_cache_handler_register(netsnmp_handler_registration * reginfo,
00437                                netsnmp_cache* cache)
00438 {
00439     netsnmp_mib_handler *handler = NULL;
00440     handler = netsnmp_cache_handler_get(cache);
00441 
00442     netsnmp_inject_handler(reginfo, handler);
00443     return netsnmp_register_handler(reginfo);
00444 }
00445 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER */
00446 
00449 netsnmp_feature_child_of(netsnmp_register_cache_handler,netsnmp_unused)
00450 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER
00451 int
00452 netsnmp_register_cache_handler(netsnmp_handler_registration * reginfo,
00453                                int timeout, NetsnmpCacheLoad * load_hook,
00454                                NetsnmpCacheFree * free_hook)
00455 {
00456     netsnmp_mib_handler *handler = NULL;
00457     handler = netsnmp_get_cache_handler(timeout, load_hook, free_hook,
00458                                         reginfo->rootoid,
00459                                         reginfo->rootoid_len);
00460 
00461     netsnmp_inject_handler(reginfo, handler);
00462     return netsnmp_register_handler(reginfo);
00463 }
00464 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER */
00465 
00466 static char *
00467 _build_cache_name(const char *name)
00468 {
00469     char *dup = (char*)malloc(strlen(name) + strlen(CACHE_NAME) + 2);
00470     if (NULL == dup)
00471         return NULL;
00472     sprintf(dup, "%s:%s", CACHE_NAME, name);
00473     return dup;
00474 }
00475 
00477 void
00478 netsnmp_cache_reqinfo_insert(netsnmp_cache* cache,
00479                              netsnmp_agent_request_info * reqinfo,
00480                              const char *name)
00481 {
00482     char *cache_name = _build_cache_name(name);
00483     if (NULL == netsnmp_agent_get_list_data(reqinfo, cache_name)) {
00484         DEBUGMSGTL(("verbose:helper:cache_handler", " adding '%s' to %p\n",
00485                     cache_name, reqinfo));
00486         netsnmp_agent_add_list_data(reqinfo,
00487                                     netsnmp_create_data_list(cache_name,
00488                                                              cache, NULL));
00489     }
00490     SNMP_FREE(cache_name);
00491 }
00492 
00494 netsnmp_cache  *
00495 netsnmp_cache_reqinfo_extract(netsnmp_agent_request_info * reqinfo,
00496                               const char *name)
00497 {
00498     netsnmp_cache  *result;
00499     char *cache_name = _build_cache_name(name);
00500     result = (netsnmp_cache*)netsnmp_agent_get_list_data(reqinfo, cache_name);
00501     SNMP_FREE(cache_name);
00502     return result;
00503 }
00504 
00506 netsnmp_feature_child_of(netsnmp_extract_cache_info,netsnmp_unused)
00507 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO
00508 netsnmp_cache  *
00509 netsnmp_extract_cache_info(netsnmp_agent_request_info * reqinfo)
00510 {
00511     return netsnmp_cache_reqinfo_extract(reqinfo, CACHE_NAME);
00512 }
00513 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO */
00514 
00515 
00517 int
00518 netsnmp_cache_check_expired(netsnmp_cache *cache)
00519 {
00520     if(NULL == cache)
00521         return 0;
00522     if (cache->expired)
00523         return 1;
00524     if(!cache->valid || (NULL == cache->timestamp) || (-1 == cache->timeout))
00525         cache->expired = 1;
00526     else
00527         cache->expired = atime_ready(cache->timestamp, 1000 * cache->timeout);
00528     
00529     return cache->expired;
00530 }
00531 
00533 int
00534 netsnmp_cache_check_and_reload(netsnmp_cache * cache)
00535 {
00536     if (!cache) {
00537         DEBUGMSGT(("helper:cache_handler", " no cache\n"));
00538         return 0;       /* ?? or -1 */
00539     }
00540     if (!cache->valid || netsnmp_cache_check_expired(cache))
00541         return _cache_load( cache );
00542     else {
00543         DEBUGMSGT(("helper:cache_handler", " cached (%d)\n",
00544                    cache->timeout));
00545         return 0;
00546     }
00547 }
00548 
00550 int
00551 netsnmp_cache_is_valid(netsnmp_agent_request_info * reqinfo, 
00552                        const char* name)
00553 {
00554     netsnmp_cache  *cache = netsnmp_cache_reqinfo_extract(reqinfo, name);
00555     return (cache && cache->valid);
00556 }
00557 
00561 netsnmp_feature_child_of(netsnmp_is_cache_valid,netsnmp_unused)
00562 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID
00563 int
00564 netsnmp_is_cache_valid(netsnmp_agent_request_info * reqinfo)
00565 {
00566     return netsnmp_cache_is_valid(reqinfo, CACHE_NAME);
00567 }
00568 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID */
00569 
00571 int
00572 netsnmp_cache_helper_handler(netsnmp_mib_handler * handler,
00573                              netsnmp_handler_registration * reginfo,
00574                              netsnmp_agent_request_info * reqinfo,
00575                              netsnmp_request_info * requests)
00576 {
00577     char addrstr[32];
00578 
00579     netsnmp_cache  *cache = NULL;
00580     netsnmp_handler_args cache_hint;
00581 
00582     DEBUGMSGTL(("helper:cache_handler", "Got request (%d) for %s: ",
00583                 reqinfo->mode, reginfo->handlerName));
00584     DEBUGMSGOID(("helper:cache_handler", reginfo->rootoid,
00585                  reginfo->rootoid_len));
00586     DEBUGMSG(("helper:cache_handler", "\n"));
00587 
00588     netsnmp_assert(handler->flags & MIB_HANDLER_AUTO_NEXT);
00589 
00590     cache = (netsnmp_cache *) handler->myvoid;
00591     if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
00592                                NETSNMP_DS_AGENT_NO_CACHING) ||
00593         !cache || !cache->enabled || !cache->load_cache) {
00594         DEBUGMSGT(("helper:cache_handler", " caching disabled or "
00595                    "cache not found, disabled or had no load method\n"));
00596         return SNMP_ERR_NOERROR;
00597     }
00598     snprintf(addrstr,sizeof(addrstr), "%ld", (long int)cache);
00599     DEBUGMSGTL(("helper:cache_handler", "using cache %s: ", addrstr));
00600     DEBUGMSGOID(("helper:cache_handler", cache->rootoid, cache->rootoid_len));
00601     DEBUGMSG(("helper:cache_handler", "\n"));
00602 
00603     /*
00604      * Make the handler-chain parameters available to
00605      * the cache_load hook routine.
00606      */
00607     cache_hint.handler = handler;
00608     cache_hint.reginfo = reginfo;
00609     cache_hint.reqinfo = reqinfo;
00610     cache_hint.requests = requests;
00611     cache->cache_hint = &cache_hint;
00612 
00613     switch (reqinfo->mode) {
00614 
00615     case MODE_GET:
00616     case MODE_GETNEXT:
00617     case MODE_GETBULK:
00618 #ifndef NETSNMP_NO_WRITE_SUPPORT
00619     case MODE_SET_RESERVE1:
00620 #endif /* !NETSNMP_NO_WRITE_SUPPORT */
00621 
00622         /*
00623          * only touch cache once per pdu request, to prevent a cache
00624          * reload while a module is using cached data.
00625          *
00626          * XXX: this won't catch a request reloading the cache while
00627          * a previous (delegated) request is still using the cache.
00628          * maybe use a reference counter?
00629          */
00630         if (netsnmp_cache_is_valid(reqinfo, addrstr))
00631             break;
00632 
00633         /*
00634          * call the load hook, and update the cache timestamp.
00635          * If it's not already there, add to reqinfo
00636          */
00637         netsnmp_cache_check_and_reload(cache);
00638         netsnmp_cache_reqinfo_insert(cache, reqinfo, addrstr);
00640         break;
00641 
00642 #ifndef NETSNMP_NO_WRITE_SUPPORT
00643     case MODE_SET_RESERVE2:
00644     case MODE_SET_FREE:
00645     case MODE_SET_ACTION:
00646     case MODE_SET_UNDO:
00647         netsnmp_assert(netsnmp_cache_is_valid(reqinfo, addrstr));
00649         break;
00650 
00651         /*
00652          * A (successful) SET request wouldn't typically trigger a reload of
00653          *  the cache, but might well invalidate the current contents.
00654          * Only do this on the last pass through.
00655          */
00656     case MODE_SET_COMMIT:
00657         if (cache->valid && 
00658             ! (cache->flags & NETSNMP_CACHE_DONT_INVALIDATE_ON_SET) ) {
00659             cache->free_cache(cache, cache->magic);
00660             cache->valid = 0;
00661         }
00663         break;
00664 #endif /* NETSNMP_NO_WRITE_SUPPORT */
00665 
00666     default:
00667         snmp_log(LOG_WARNING, "cache_handler: Unrecognised mode (%d)\n",
00668                  reqinfo->mode);
00669         netsnmp_request_set_error_all(requests, SNMP_ERR_GENERR);
00670         return SNMP_ERR_GENERR;
00671     }
00672     if (cache->flags & NETSNMP_CACHE_RESET_TIMER_ON_USE)
00673         atime_setMarker(cache->timestamp);
00674     return SNMP_ERR_NOERROR;
00675 }
00676 
00677 static void
00678 _cache_free( netsnmp_cache *cache )
00679 {
00680     if (NULL != cache->free_cache) {
00681         cache->free_cache(cache, cache->magic);
00682         cache->valid = 0;
00683     }
00684 }
00685 
00686 static int
00687 _cache_load( netsnmp_cache *cache )
00688 {
00689     int ret = -1;
00690 
00691     /*
00692      * If we've got a valid cache, then release it before reloading
00693      */
00694     if (cache->valid &&
00695         (! (cache->flags & NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD)))
00696         _cache_free(cache);
00697 
00698     if ( cache->load_cache)
00699         ret = cache->load_cache(cache, cache->magic);
00700     if (ret < 0) {
00701         DEBUGMSGT(("helper:cache_handler", " load failed (%d)\n", ret));
00702         cache->valid = 0;
00703         return ret;
00704     }
00705     cache->valid = 1;
00706     cache->expired = 0;
00707 
00708     /*
00709      * If we didn't previously have any valid caches outstanding,
00710      *   then schedule a pass of the auto-release routine.
00711      */
00712     if ((!cache_outstanding_valid) &&
00713         (! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))) {
00714         snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
00715                             0, release_cached_resources, NULL);
00716         cache_outstanding_valid = 1;
00717     }
00718     if (cache->timestamp)
00719         atime_setMarker(cache->timestamp);
00720     else
00721         cache->timestamp = atime_newMarker();
00722     DEBUGMSGT(("helper:cache_handler", " loaded (%d)\n", cache->timeout));
00723 
00724     return ret;
00725 }
00726 
00727 
00728 
00736 void
00737 release_cached_resources(unsigned int regNo, void *clientargs)
00738 {
00739     netsnmp_cache  *cache = NULL;
00740 
00741     cache_outstanding_valid = 0;
00742     DEBUGMSGTL(("helper:cache_handler", "running auto-release\n"));
00743     for (cache = cache_head; cache; cache = cache->next) {
00744         DEBUGMSGTL(("helper:cache_handler"," checking %p (flags 0x%x)\n",
00745                      cache, cache->flags));
00746         if (cache->valid &&
00747             ! (cache->flags & NETSNMP_CACHE_DONT_AUTO_RELEASE)) {
00748             DEBUGMSGTL(("helper:cache_handler","  releasing %p\n", cache));
00749             /*
00750              * Check to see if this cache has timed out.
00751              * If so, release the cached resources.
00752              * Otherwise, note that we still have at
00753              *   least one active cache.
00754              */
00755             if (netsnmp_cache_check_expired(cache)) {
00756                 if(! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))
00757                     _cache_free(cache);
00758             } else {
00759                 cache_outstanding_valid = 1;
00760             }
00761         }
00762     }
00763     /*
00764      * If there are any caches still valid & active,
00765      *   then schedule another pass.
00766      */
00767     if (cache_outstanding_valid) {
00768         snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
00769                             0, release_cached_resources, NULL);
00770     }
00771 }