1 /*
2 * callback.c: A generic callback mechanism
3 */
4 /* Portions of this file are subject to the following copyright(s). See
5 * the Net-SNMP's COPYING file for more details and other copyrights
6 * that may apply:
7 */
8 /*
9 * Portions of this file are copyrighted by:
10 * Copyright � 2003 Sun Microsystems, Inc. All rights reserved.
11 * Use is subject to license terms specified in the COPYING file
12 * distributed with the Net-SNMP package.
13 */
14 /** @defgroup callback A generic callback mechanism
15 * @ingroup library
16 *
17 * @{
18 */
19 #include <net-snmp/net-snmp-config.h>
20 #include <net-snmp/net-snmp-features.h>
21 #include <sys/types.h>
22 #include <stdio.h>
23 #if HAVE_STDLIB_H
24 #include <stdlib.h>
25 #endif
26 #if HAVE_NETINET_IN_H
27 #include <netinet/in.h>
28 #endif
29 #if HAVE_STRING_H
30 #include <string.h>
31 #else
32 #include <strings.h>
33 #endif
34
35 #if HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38
39 #if HAVE_SYS_SOCKET_H
40 #include <sys/socket.h>
41 #endif
42 #if !defined(mingw32) && defined(HAVE_SYS_TIME_H)
43 #include <sys/time.h>
44 #endif
45
46 #include <net-snmp/types.h>
47 #include <net-snmp/output_api.h>
48 #include <net-snmp/utilities.h>
49
50 #include <net-snmp/library/callback.h>
51 #include <net-snmp/library/snmp_api.h>
52
53 netsnmp_feature_child_of(callbacks_all, libnetsnmp);
54
55 netsnmp_feature_child_of(callback_count, callbacks_all);
56 netsnmp_feature_child_of(callback_list, callbacks_all);
57
58 /*
59 * the inline callback methods use major/minor to index into arrays.
60 * all users in this function do range checking before calling these
61 * functions, so it is redundant for them to check again. But if you
62 * want to be paranoid, define this var, and additional range checks
63 * will be performed.
64 * #define NETSNMP_PARANOID_LEVEL_HIGH 1
65 */
66
67 static int _callback_need_init = 1;
68 static struct snmp_gen_callback
69 *thecallbacks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS];
70
71 #define CALLBACK_NAME_LOGGING 1
72 #ifdef CALLBACK_NAME_LOGGING
73 static const char *types[MAX_CALLBACK_IDS] = { "LIB", "APP" };
74 static const char *lib[MAX_CALLBACK_SUBIDS] = {
75 "POST_READ_CONFIG", /* 0 */
76 "STORE_DATA", /* 1 */
77 "SHUTDOWN", /* 2 */
78 "POST_PREMIB_READ_CONFIG", /* 3 */
79 "LOGGING", /* 4 */
80 "SESSION_INIT", /* 5 */
81 NULL, /* 6 */
82 NULL, /* 7 */
83 NULL, /* 8 */
84 NULL, /* 9 */
85 NULL, /* 10 */
86 NULL, /* 11 */
87 NULL, /* 12 */
88 NULL, /* 13 */
89 NULL, /* 14 */
90 NULL /* 15 */
91 };
92 #endif
93
94 /*
95 * extremely simplistic locking, just to find problems were the
96 * callback list is modified while being traversed. Not intended
97 * to do any real protection, or in any way imply that this code
98 * has been evaluated for use in a multi-threaded environment.
99 * In 5.2, it was a single lock. For 5.3, it has been updated to
100 * a lock per callback, since a particular callback may trigger
101 * registration/unregistration of other callbacks (eg AgentX
102 * subagents do this).
103 */
104 #define LOCK_PER_CALLBACK_SUBID 1
105 #ifdef LOCK_PER_CALLBACK_SUBID
106 static int _locks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS];
107 #define CALLBACK_LOCK(maj,min) ++_locks[maj][min]
108 #define CALLBACK_UNLOCK(maj,min) --_locks[maj][min]
109 #define CALLBACK_LOCK_COUNT(maj,min) _locks[maj][min]
110 #else
111 static int _lock;
112 #define CALLBACK_LOCK(maj,min) ++_lock
113 #define CALLBACK_UNLOCK(maj,min) --_lock
114 #define CALLBACK_LOCK_COUNT(maj,min) _lock
115 #endif
116
117 NETSNMP_STATIC_INLINE int
_callback_lock(int major,int minor,const char * warn,int do_assert)118 _callback_lock(int major, int minor, const char* warn, int do_assert)
119 {
120 int lock_holded=0;
121 NETSNMP_SELECT_TIMEVAL lock_time = { 0, 1000 };
122
123 #ifdef NETSNMP_PARANOID_LEVEL_HIGH
124 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
125 netsnmp_assert("bad callback id");
126 return 1;
127 }
128 #endif
129
130 #ifdef CALLBACK_NAME_LOGGING
131 DEBUGMSGTL(("9:callback:lock", "locked (%s,%s)\n",
132 types[major], (SNMP_CALLBACK_LIBRARY == major) ?
133 SNMP_STRORNULL(lib[minor]) : "null"));
134 #endif
135 while (CALLBACK_LOCK_COUNT(major,minor) >= 1 && ++lock_holded < 100)
136 select(0, NULL, NULL, NULL, &lock_time);
137
138 if(lock_holded >= 100) {
139 if (NULL != warn)
140 snmp_log(LOG_WARNING,
141 "lock in _callback_lock sleeps more than 100 milliseconds in %s\n", warn);
142 if (do_assert)
143 netsnmp_assert(lock_holded < 100);
144
145 return 1;
146 }
147
148 CALLBACK_LOCK(major,minor);
149 return 0;
150 }
151
152 NETSNMP_STATIC_INLINE void
_callback_unlock(int major,int minor)153 _callback_unlock(int major, int minor)
154 {
155 #ifdef NETSNMP_PARANOID_LEVEL_HIGH
156 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
157 netsnmp_assert("bad callback id");
158 return;
159 }
160 #endif
161
162 CALLBACK_UNLOCK(major,minor);
163
164 #ifdef CALLBACK_NAME_LOGGING
165 DEBUGMSGTL(("9:callback:lock", "unlocked (%s,%s)\n",
166 types[major], (SNMP_CALLBACK_LIBRARY == major) ?
167 SNMP_STRORNULL(lib[minor]) : "null"));
168 #endif
169 }
170
171
172 /*
173 * the chicken. or the egg. You pick.
174 */
175 void
init_callbacks(void)176 init_callbacks(void)
177 {
178 /*
179 * (poses a problem if you put init_callbacks() inside of
180 * init_snmp() and then want the app to register a callback before
181 * init_snmp() is called in the first place. -- Wes
182 */
183 if (0 == _callback_need_init)
184 return;
185
186 _callback_need_init = 0;
187
188 memset(thecallbacks, 0, sizeof(thecallbacks));
189 #ifdef LOCK_PER_CALLBACK_SUBID
190 memset(_locks, 0, sizeof(_locks));
191 #else
192 _lock = 0;
193 #endif
194
195 DEBUGMSGTL(("callback", "initialized\n"));
196 }
197
198 /**
199 * This function registers a generic callback function. The major and
200 * minor values are used to set the new_callback function into a global
201 * static multi-dimensional array of type struct snmp_gen_callback.
202 * The function makes sure to append this callback function at the end
203 * of the link list, snmp_gen_callback->next.
204 *
205 * @param major is the SNMP callback major type used
206 * - SNMP_CALLBACK_LIBRARY
207 * - SNMP_CALLBACK_APPLICATION
208 *
209 * @param minor is the SNMP callback minor type used
210 * - SNMP_CALLBACK_POST_READ_CONFIG
211 * - SNMP_CALLBACK_STORE_DATA
212 * - SNMP_CALLBACK_SHUTDOWN
213 * - SNMP_CALLBACK_POST_PREMIB_READ_CONFIG
214 * - SNMP_CALLBACK_LOGGING
215 * - SNMP_CALLBACK_SESSION_INIT
216 *
217 * @param new_callback is the callback function that is registered.
218 *
219 * @param arg when not NULL is a void pointer used whenever new_callback
220 * function is exercised. Ownership is transferred to the twodimensional
221 * thecallbacks[][] array. The function clear_callback() will deallocate
222 * the memory pointed at by calling free().
223 *
224 * @return
225 * Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or minor is >=
226 * MAX_CALLBACK_SUBIDS or a snmp_gen_callback pointer could not be
227 * allocated, otherwise SNMPERR_SUCCESS is returned.
228 * - \#define MAX_CALLBACK_IDS 2
229 * - \#define MAX_CALLBACK_SUBIDS 16
230 *
231 * @see snmp_call_callbacks
232 * @see snmp_unregister_callback
233 */
234 int
snmp_register_callback(int major,int minor,SNMPCallback * new_callback,void * arg)235 snmp_register_callback(int major, int minor, SNMPCallback * new_callback,
236 void *arg)
237 {
238 return netsnmp_register_callback( major, minor, new_callback, arg,
239 NETSNMP_CALLBACK_DEFAULT_PRIORITY);
240 }
241
242 /**
243 * Register a callback function.
244 *
245 * @param major Major callback event type.
246 * @param minor Minor callback event type.
247 * @param new_callback Callback function being registered.
248 * @param arg Argument that will be passed to the callback function.
249 * @param priority Handler invocation priority. When multiple handlers have
250 * been registered for the same (major, minor) callback event type, handlers
251 * with the numerically lowest priority will be invoked first. Handlers with
252 * identical priority are invoked in the order they have been registered.
253 *
254 * @see snmp_register_callback
255 */
256 int
netsnmp_register_callback(int major,int minor,SNMPCallback * new_callback,void * arg,int priority)257 netsnmp_register_callback(int major, int minor, SNMPCallback * new_callback,
258 void *arg, int priority)
259 {
260 struct snmp_gen_callback *newscp = NULL, *scp = NULL;
261 struct snmp_gen_callback **prevNext = &(thecallbacks[major][minor]);
262
263 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
264 return SNMPERR_GENERR;
265 }
266
267 if (_callback_need_init)
268 init_callbacks();
269
270 _callback_lock(major,minor, "netsnmp_register_callback", 1);
271
272 if ((newscp = SNMP_MALLOC_STRUCT(snmp_gen_callback)) == NULL) {
273 _callback_unlock(major,minor);
274 return SNMPERR_GENERR;
275 } else {
276 newscp->priority = priority;
277 newscp->sc_client_arg = arg;
278 newscp->sc_callback = new_callback;
279 newscp->next = NULL;
280
281 for (scp = thecallbacks[major][minor]; scp != NULL;
282 scp = scp->next) {
283 if (newscp->priority < scp->priority) {
284 newscp->next = scp;
285 break;
286 }
287 prevNext = &(scp->next);
288 }
289
290 *prevNext = newscp;
291
292 DEBUGMSGTL(("callback", "registered (%d,%d) at %p with priority %d\n",
293 major, minor, newscp, priority));
294 _callback_unlock(major,minor);
295 return SNMPERR_SUCCESS;
296 }
297 }
298
299 /**
300 * This function calls the callback function for each registered callback of
301 * type major and minor.
302 *
303 * @param major is the SNMP callback major type used
304 *
305 * @param minor is the SNMP callback minor type used
306 *
307 * @param caller_arg is a void pointer which is sent in as the callback's
308 * serverarg parameter, if needed.
309 *
310 * @return Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or
311 * minor is >= MAX_CALLBACK_SUBIDS, otherwise SNMPERR_SUCCESS is returned.
312 *
313 * @see snmp_register_callback
314 * @see snmp_unregister_callback
315 */
316 int
snmp_call_callbacks(int major,int minor,void * caller_arg)317 snmp_call_callbacks(int major, int minor, void *caller_arg)
318 {
319 struct snmp_gen_callback *scp;
320 unsigned int count = 0;
321
322 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
323 return SNMPERR_GENERR;
324 }
325
326 if (_callback_need_init)
327 init_callbacks();
328
329 #ifdef LOCK_PER_CALLBACK_SUBID
330 _callback_lock(major,minor,"snmp_call_callbacks", 1);
331 #else
332 /*
333 * Notes:
334 * - this gets hit the first time a trap is sent after a new trap
335 * destination has been added (session init cb during send trap cb)
336 */
337 _callback_lock(major,minor, NULL, 0);
338 #endif
339
340 DEBUGMSGTL(("callback", "START calling callbacks for maj=%d min=%d\n",
341 major, minor));
342
343 /*
344 * for each registered callback of type major and minor
345 */
346 for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) {
347
348 /*
349 * skip unregistered callbacks
350 */
351 if(NULL == scp->sc_callback)
352 continue;
353
354 DEBUGMSGTL(("callback", "calling a callback for maj=%d min=%d\n",
355 major, minor));
356
357 /*
358 * call them
359 */
360 (*(scp->sc_callback)) (major, minor, caller_arg,
361 scp->sc_client_arg);
362 count++;
363 }
364
365 DEBUGMSGTL(("callback",
366 "END calling callbacks for maj=%d min=%d (%d called)\n",
367 major, minor, count));
368
369 _callback_unlock(major,minor);
370 return SNMPERR_SUCCESS;
371 }
372
373 #ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT
374 int
snmp_count_callbacks(int major,int minor)375 snmp_count_callbacks(int major, int minor)
376 {
377 int count = 0;
378 struct snmp_gen_callback *scp;
379
380 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
381 return SNMPERR_GENERR;
382 }
383
384 if (_callback_need_init)
385 init_callbacks();
386
387 for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) {
388 count++;
389 }
390
391 return count;
392 }
393 #endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT */
394
395 int
snmp_callback_available(int major,int minor)396 snmp_callback_available(int major, int minor)
397 {
398 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
399 return SNMPERR_GENERR;
400 }
401
402 if (_callback_need_init)
403 init_callbacks();
404
405 if (thecallbacks[major][minor] != NULL) {
406 return SNMPERR_SUCCESS;
407 }
408
409 return SNMPERR_GENERR;
410 }
411
412 /**
413 * This function unregisters a specified callback function given a major
414 * and minor type.
415 *
416 * Note: no bound checking on major and minor.
417 *
418 * @param major is the SNMP callback major type used
419 *
420 * @param minor is the SNMP callback minor type used
421 *
422 * @param target is the callback function that will be unregistered.
423 *
424 * @param arg is a void pointer used for comparison against the registered
425 * callback's sc_client_arg variable.
426 *
427 * @param matchargs is an integer used to bypass the comparison of arg and the
428 * callback's sc_client_arg variable only when matchargs is set to 0.
429 *
430 *
431 * @return
432 * Returns the number of callbacks that were unregistered.
433 *
434 * @see snmp_register_callback
435 * @see snmp_call_callbacks
436 */
437
438 int
snmp_unregister_callback(int major,int minor,SNMPCallback * target,void * arg,int matchargs)439 snmp_unregister_callback(int major, int minor, SNMPCallback * target,
440 void *arg, int matchargs)
441 {
442 struct snmp_gen_callback *scp;
443 struct snmp_gen_callback **prevNext;
444 int count = 0;
445
446 if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS)
447 return SNMPERR_GENERR;
448
449 scp = thecallbacks[major][minor];
450 prevNext = &(thecallbacks[major][minor]);
451
452 if (_callback_need_init)
453 init_callbacks();
454
455 #ifdef LOCK_PER_CALLBACK_SUBID
456 _callback_lock(major,minor,"snmp_unregister_callback", 1);
457 #else
458 /*
459 * Notes;
460 * - this gets hit at shutdown, during cleanup. No easy fix.
461 */
462 _callback_lock(major,minor,"snmp_unregister_callback", 0);
463 #endif
464
465 while (scp != NULL) {
466 if ((scp->sc_callback == target) &&
467 (!matchargs || (scp->sc_client_arg == arg))) {
468 DEBUGMSGTL(("callback", "unregistering (%d,%d) at %p\n", major,
469 minor, scp));
470 if(1 == CALLBACK_LOCK_COUNT(major,minor)) {
471 *prevNext = scp->next;
472 SNMP_FREE(scp);
473 scp = *prevNext;
474 }
475 else {
476 scp->sc_callback = NULL;
477 /** set cleanup flag? */
478 }
479 count++;
480 } else {
481 prevNext = &(scp->next);
482 scp = scp->next;
483 }
484 }
485
486 _callback_unlock(major,minor);
487 return count;
488 }
489
490 /**
491 * find and clear client args that match ptr
492 *
493 * @param ptr pointer to search for
494 * @param i callback id to start at
495 * @param j callback subid to start at
496 */
497 int
netsnmp_callback_clear_client_arg(void * ptr,int i,int j)498 netsnmp_callback_clear_client_arg(void *ptr, int i, int j)
499 {
500 struct snmp_gen_callback *scp = NULL;
501 int rc = 0;
502
503 /*
504 * don't init i and j before loop, since the caller specified
505 * the starting point explicitly. But *after* the i loop has
506 * finished executing once, init j to 0 for the next pass
507 * through the subids.
508 */
509 for (; i < MAX_CALLBACK_IDS; i++,j=0) {
510 for (; j < MAX_CALLBACK_SUBIDS; j++) {
511 scp = thecallbacks[i][j];
512 while (scp != NULL) {
513 if ((NULL != scp->sc_callback) &&
514 (scp->sc_client_arg != NULL) &&
515 (scp->sc_client_arg == ptr)) {
516 DEBUGMSGTL(("9:callback", " clearing %p at [%d,%d]\n", ptr, i, j));
517 scp->sc_client_arg = NULL;
518 ++rc;
519 }
520 scp = scp->next;
521 }
522 }
523 }
524
525 if (0 != rc) {
526 DEBUGMSGTL(("callback", "removed %d client args\n", rc));
527 }
528
529 return rc;
530 }
531
532 void
clear_callback(void)533 clear_callback(void)
534 {
535 unsigned int i = 0, j = 0;
536 struct snmp_gen_callback *scp = NULL;
537
538 if (_callback_need_init)
539 init_callbacks();
540
541 DEBUGMSGTL(("callback", "clear callback\n"));
542 for (i = 0; i < MAX_CALLBACK_IDS; i++) {
543 for (j = 0; j < MAX_CALLBACK_SUBIDS; j++) {
544 _callback_lock(i,j, "clear_callback", 1);
545 scp = thecallbacks[i][j];
546 while (scp != NULL) {
547 thecallbacks[i][j] = scp->next;
548 /*
549 * if there is a client arg, check for duplicates
550 * and then free it.
551 */
552 if ((NULL != scp->sc_callback) &&
553 (scp->sc_client_arg != NULL)) {
554 void *tmp_arg;
555 /*
556 * save the client arg, then set it to null so that it
557 * won't look like a duplicate, then check for duplicates
558 * starting at the current i,j (earlier dups should have
559 * already been found) and free the pointer.
560 */
561 tmp_arg = scp->sc_client_arg;
562 scp->sc_client_arg = NULL;
563 DEBUGMSGTL(("9:callback", " freeing %p at [%d,%d]\n", tmp_arg, i, j));
564 (void)netsnmp_callback_clear_client_arg(tmp_arg, i, j);
565 free(tmp_arg);
566 }
567 SNMP_FREE(scp);
568 scp = thecallbacks[i][j];
569 }
570 _callback_unlock(i,j);
571 }
572 }
573 }
574
575 #ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_LIST
576 struct snmp_gen_callback *
snmp_callback_list(int major,int minor)577 snmp_callback_list(int major, int minor)
578 {
579 if (_callback_need_init)
580 init_callbacks();
581
582 return (thecallbacks[major][minor]);
583 }
584 #endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_LIST */
585 /** @} */
586