/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 | | |