diff options
Diffstat (limited to 'snmplib/callback.c')
-rw-r--r-- | snmplib/callback.c | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/snmplib/callback.c b/snmplib/callback.c new file mode 100644 index 0000000..dfe537a --- /dev/null +++ b/snmplib/callback.c @@ -0,0 +1,585 @@ +/* + * callback.c: A generic callback mechanism + */ +/* 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 © 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms specified in the COPYING file + * distributed with the Net-SNMP package. + */ +/** @defgroup callback A generic callback mechanism + * @ingroup library + * + * @{ + */ +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-features.h> +#include <sys/types.h> +#include <stdio.h> +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_DMALLOC_H +#include <dmalloc.h> +#endif + +#if HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if !defined(mingw32) && defined(HAVE_SYS_TIME_H) +#include <sys/time.h> +#endif + +#include <net-snmp/types.h> +#include <net-snmp/output_api.h> +#include <net-snmp/utilities.h> + +#include <net-snmp/library/callback.h> +#include <net-snmp/library/snmp_api.h> + +netsnmp_feature_child_of(callbacks_all, libnetsnmp) + +netsnmp_feature_child_of(callback_count, callbacks_all) +netsnmp_feature_child_of(callback_list, callbacks_all) + +/* + * the inline callback methods use major/minor to index into arrays. + * all users in this function do range checking before calling these + * functions, so it is redundant for them to check again. But if you + * want to be paranoid, define this var, and additional range checks + * will be performed. + * #define NETSNMP_PARANOID_LEVEL_HIGH 1 + */ + +static int _callback_need_init = 1; +static struct snmp_gen_callback + *thecallbacks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS]; + +#define CALLBACK_NAME_LOGGING 1 +#ifdef CALLBACK_NAME_LOGGING +static const char *types[MAX_CALLBACK_IDS] = { "LIB", "APP" }; +static const char *lib[MAX_CALLBACK_SUBIDS] = { + "POST_READ_CONFIG", /* 0 */ + "STORE_DATA", /* 1 */ + "SHUTDOWN", /* 2 */ + "POST_PREMIB_READ_CONFIG", /* 3 */ + "LOGGING", /* 4 */ + "SESSION_INIT", /* 5 */ + NULL, /* 6 */ + NULL, /* 7 */ + NULL, /* 8 */ + NULL, /* 9 */ + NULL, /* 10 */ + NULL, /* 11 */ + NULL, /* 12 */ + NULL, /* 13 */ + NULL, /* 14 */ + NULL /* 15 */ +}; +#endif + +/* + * extremely simplistic locking, just to find problems were the + * callback list is modified while being traversed. Not intended + * to do any real protection, or in any way imply that this code + * has been evaluated for use in a multi-threaded environment. + * In 5.2, it was a single lock. For 5.3, it has been updated to + * a lock per callback, since a particular callback may trigger + * registration/unregistration of other callbacks (eg AgentX + * subagents do this). + */ +#define LOCK_PER_CALLBACK_SUBID 1 +#ifdef LOCK_PER_CALLBACK_SUBID +static int _locks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS]; +#define CALLBACK_LOCK(maj,min) ++_locks[maj][min] +#define CALLBACK_UNLOCK(maj,min) --_locks[maj][min] +#define CALLBACK_LOCK_COUNT(maj,min) _locks[maj][min] +#else +static int _lock; +#define CALLBACK_LOCK(maj,min) ++_lock +#define CALLBACK_UNLOCK(maj,min) --_lock +#define CALLBACK_LOCK_COUNT(maj,min) _lock +#endif + +NETSNMP_STATIC_INLINE int +_callback_lock(int major, int minor, const char* warn, int do_assert) +{ + int lock_holded=0; + struct timeval lock_time = { 0, 1000 }; + +#ifdef NETSNMP_PARANOID_LEVEL_HIGH + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) { + netsnmp_assert("bad callback id"); + return 1; + } +#endif + +#ifdef CALLBACK_NAME_LOGGING + DEBUGMSGTL(("9:callback:lock", "locked (%s,%s)\n", + types[major], (SNMP_CALLBACK_LIBRARY == major) ? + SNMP_STRORNULL(lib[minor]) : "null")); +#endif + while (CALLBACK_LOCK_COUNT(major,minor) >= 1 && ++lock_holded < 100) + select(0, NULL, NULL, NULL, &lock_time); + + if(lock_holded >= 100) { + if (NULL != warn) + snmp_log(LOG_WARNING, + "lock in _callback_lock sleeps more than 100 milliseconds in %s\n", warn); + if (do_assert) + netsnmp_assert(lock_holded < 100); + + return 1; + } + + CALLBACK_LOCK(major,minor); + return 0; +} + +NETSNMP_STATIC_INLINE void +_callback_unlock(int major, int minor) +{ +#ifdef NETSNMP_PARANOID_LEVEL_HIGH + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) { + netsnmp_assert("bad callback id"); + return; + } +#endif + + CALLBACK_UNLOCK(major,minor); + +#ifdef CALLBACK_NAME_LOGGING + DEBUGMSGTL(("9:callback:lock", "unlocked (%s,%s)\n", + types[major], (SNMP_CALLBACK_LIBRARY == major) ? + SNMP_STRORNULL(lib[minor]) : "null")); +#endif +} + + +/* + * the chicken. or the egg. You pick. + */ +void +init_callbacks(void) +{ + /* + * (poses a problem if you put init_callbacks() inside of + * init_snmp() and then want the app to register a callback before + * init_snmp() is called in the first place. -- Wes + */ + if (0 == _callback_need_init) + return; + + _callback_need_init = 0; + + memset(thecallbacks, 0, sizeof(thecallbacks)); +#ifdef LOCK_PER_CALLBACK_SUBID + memset(_locks, 0, sizeof(_locks)); +#else + _lock = 0; +#endif + + DEBUGMSGTL(("callback", "initialized\n")); +} + +/** + * This function registers a generic callback function. The major and + * minor values are used to set the new_callback function into a global + * static multi-dimensional array of type struct snmp_gen_callback. + * The function makes sure to append this callback function at the end + * of the link list, snmp_gen_callback->next. + * + * @param major is the SNMP callback major type used + * - SNMP_CALLBACK_LIBRARY + * - SNMP_CALLBACK_APPLICATION + * + * @param minor is the SNMP callback minor type used + * - SNMP_CALLBACK_POST_READ_CONFIG + * - SNMP_CALLBACK_STORE_DATA + * - SNMP_CALLBACK_SHUTDOWN + * - SNMP_CALLBACK_POST_PREMIB_READ_CONFIG + * - SNMP_CALLBACK_LOGGING + * - SNMP_CALLBACK_SESSION_INIT + * + * @param new_callback is the callback function that is registered. + * + * @param arg when not NULL is a void pointer used whenever new_callback + * function is exercised. Ownership is transferred to the twodimensional + * thecallbacks[][] array. The function clear_callback() will deallocate + * the memory pointed at by calling free(). + * + * @return + * Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or minor is >= + * MAX_CALLBACK_SUBIDS or a snmp_gen_callback pointer could not be + * allocated, otherwise SNMPERR_SUCCESS is returned. + * - \#define MAX_CALLBACK_IDS 2 + * - \#define MAX_CALLBACK_SUBIDS 16 + * + * @see snmp_call_callbacks + * @see snmp_unregister_callback + */ +int +snmp_register_callback(int major, int minor, SNMPCallback * new_callback, + void *arg) +{ + return netsnmp_register_callback( major, minor, new_callback, arg, + NETSNMP_CALLBACK_DEFAULT_PRIORITY); +} + +/** + * Register a callback function. + * + * @param major Major callback event type. + * @param minor Minor callback event type. + * @param new_callback Callback function being registered. + * @param arg Argument that will be passed to the callback function. + * @param priority Handler invocation priority. When multiple handlers have + * been registered for the same (major, minor) callback event type, handlers + * with the numerically lowest priority will be invoked first. Handlers with + * identical priority are invoked in the order they have been registered. + * + * @see snmp_register_callback + */ +int +netsnmp_register_callback(int major, int minor, SNMPCallback * new_callback, + void *arg, int priority) +{ + struct snmp_gen_callback *newscp = NULL, *scp = NULL; + struct snmp_gen_callback **prevNext = &(thecallbacks[major][minor]); + + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) { + return SNMPERR_GENERR; + } + + if (_callback_need_init) + init_callbacks(); + + _callback_lock(major,minor, "netsnmp_register_callback", 1); + + if ((newscp = SNMP_MALLOC_STRUCT(snmp_gen_callback)) == NULL) { + _callback_unlock(major,minor); + return SNMPERR_GENERR; + } else { + newscp->priority = priority; + newscp->sc_client_arg = arg; + newscp->sc_callback = new_callback; + newscp->next = NULL; + + for (scp = thecallbacks[major][minor]; scp != NULL; + scp = scp->next) { + if (newscp->priority < scp->priority) { + newscp->next = scp; + break; + } + prevNext = &(scp->next); + } + + *prevNext = newscp; + + DEBUGMSGTL(("callback", "registered (%d,%d) at %p with priority %d\n", + major, minor, newscp, priority)); + _callback_unlock(major,minor); + return SNMPERR_SUCCESS; + } +} + +/** + * This function calls the callback function for each registered callback of + * type major and minor. + * + * @param major is the SNMP callback major type used + * + * @param minor is the SNMP callback minor type used + * + * @param caller_arg is a void pointer which is sent in as the callback's + * serverarg parameter, if needed. + * + * @return Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or + * minor is >= MAX_CALLBACK_SUBIDS, otherwise SNMPERR_SUCCESS is returned. + * + * @see snmp_register_callback + * @see snmp_unregister_callback + */ +int +snmp_call_callbacks(int major, int minor, void *caller_arg) +{ + struct snmp_gen_callback *scp; + unsigned int count = 0; + + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) { + return SNMPERR_GENERR; + } + + if (_callback_need_init) + init_callbacks(); + +#ifdef LOCK_PER_CALLBACK_SUBID + _callback_lock(major,minor,"snmp_call_callbacks", 1); +#else + /* + * Notes: + * - this gets hit the first time a trap is sent after a new trap + * destination has been added (session init cb during send trap cb) + */ + _callback_lock(major,minor, NULL, 0); +#endif + + DEBUGMSGTL(("callback", "START calling callbacks for maj=%d min=%d\n", + major, minor)); + + /* + * for each registered callback of type major and minor + */ + for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) { + + /* + * skip unregistered callbacks + */ + if(NULL == scp->sc_callback) + continue; + + DEBUGMSGTL(("callback", "calling a callback for maj=%d min=%d\n", + major, minor)); + + /* + * call them + */ + (*(scp->sc_callback)) (major, minor, caller_arg, + scp->sc_client_arg); + count++; + } + + DEBUGMSGTL(("callback", + "END calling callbacks for maj=%d min=%d (%d called)\n", + major, minor, count)); + + _callback_unlock(major,minor); + return SNMPERR_SUCCESS; +} + +#ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT +int +snmp_count_callbacks(int major, int minor) +{ + int count = 0; + struct snmp_gen_callback *scp; + + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) { + return SNMPERR_GENERR; + } + + if (_callback_need_init) + init_callbacks(); + + for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) { + count++; + } + + return count; +} +#endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT */ + +int +snmp_callback_available(int major, int minor) +{ + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) { + return SNMPERR_GENERR; + } + + if (_callback_need_init) + init_callbacks(); + + if (thecallbacks[major][minor] != NULL) { + return SNMPERR_SUCCESS; + } + + return SNMPERR_GENERR; +} + +/** + * This function unregisters a specified callback function given a major + * and minor type. + * + * Note: no bound checking on major and minor. + * + * @param major is the SNMP callback major type used + * + * @param minor is the SNMP callback minor type used + * + * @param target is the callback function that will be unregistered. + * + * @param arg is a void pointer used for comparison against the registered + * callback's sc_client_arg variable. + * + * @param matchargs is an integer used to bypass the comparison of arg and the + * callback's sc_client_arg variable only when matchargs is set to 0. + * + * + * @return + * Returns the number of callbacks that were unregistered. + * + * @see snmp_register_callback + * @see snmp_call_callbacks + */ + +int +snmp_unregister_callback(int major, int minor, SNMPCallback * target, + void *arg, int matchargs) +{ + struct snmp_gen_callback *scp = thecallbacks[major][minor]; + struct snmp_gen_callback **prevNext = &(thecallbacks[major][minor]); + int count = 0; + + if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) + return SNMPERR_GENERR; + + if (_callback_need_init) + init_callbacks(); + +#ifdef LOCK_PER_CALLBACK_SUBID + _callback_lock(major,minor,"snmp_unregister_callback", 1); +#else + /* + * Notes; + * - this gets hit at shutdown, during cleanup. No easy fix. + */ + _callback_lock(major,minor,"snmp_unregister_callback", 0); +#endif + + while (scp != NULL) { + if ((scp->sc_callback == target) && + (!matchargs || (scp->sc_client_arg == arg))) { + DEBUGMSGTL(("callback", "unregistering (%d,%d) at %p\n", major, + minor, scp)); + if(1 == CALLBACK_LOCK_COUNT(major,minor)) { + *prevNext = scp->next; + SNMP_FREE(scp); + scp = *prevNext; + } + else { + scp->sc_callback = NULL; + /** set cleanup flag? */ + } + count++; + } else { + prevNext = &(scp->next); + scp = scp->next; + } + } + + _callback_unlock(major,minor); + return count; +} + +/** + * find and clear client args that match ptr + * + * @param ptr pointer to search for + * @param i callback id to start at + * @param j callback subid to start at + */ +int +netsnmp_callback_clear_client_arg(void *ptr, int i, int j) +{ + struct snmp_gen_callback *scp = NULL; + int rc = 0; + + /* + * don't init i and j before loop, since the caller specified + * the starting point explicitly. But *after* the i loop has + * finished executing once, init j to 0 for the next pass + * through the subids. + */ + for (; i < MAX_CALLBACK_IDS; i++,j=0) { + for (; j < MAX_CALLBACK_SUBIDS; j++) { + scp = thecallbacks[i][j]; + while (scp != NULL) { + if ((NULL != scp->sc_callback) && + (scp->sc_client_arg != NULL) && + (scp->sc_client_arg == ptr)) { + DEBUGMSGTL(("9:callback", " clearing %p at [%d,%d]\n", ptr, i, j)); + scp->sc_client_arg = NULL; + ++rc; + } + scp = scp->next; + } + } + } + + if (0 != rc) { + DEBUGMSGTL(("callback", "removed %d client args\n", rc)); + } + + return rc; +} + +void +clear_callback(void) +{ + unsigned int i = 0, j = 0; + struct snmp_gen_callback *scp = NULL; + + if (_callback_need_init) + init_callbacks(); + + DEBUGMSGTL(("callback", "clear callback\n")); + for (i = 0; i < MAX_CALLBACK_IDS; i++) { + for (j = 0; j < MAX_CALLBACK_SUBIDS; j++) { + _callback_lock(i,j, "clear_callback", 1); + scp = thecallbacks[i][j]; + while (scp != NULL) { + thecallbacks[i][j] = scp->next; + /* + * if there is a client arg, check for duplicates + * and then free it. + */ + if ((NULL != scp->sc_callback) && + (scp->sc_client_arg != NULL)) { + void *tmp_arg; + /* + * save the client arg, then set it to null so that it + * won't look like a duplicate, then check for duplicates + * starting at the current i,j (earlier dups should have + * already been found) and free the pointer. + */ + tmp_arg = scp->sc_client_arg; + scp->sc_client_arg = NULL; + DEBUGMSGTL(("9:callback", " freeing %p at [%d,%d]\n", tmp_arg, i, j)); + (void)netsnmp_callback_clear_client_arg(tmp_arg, i, j); + free(tmp_arg); + } + SNMP_FREE(scp); + scp = thecallbacks[i][j]; + } + _callback_unlock(i,j); + } + } +} + +#ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_LIST +struct snmp_gen_callback * +snmp_callback_list(int major, int minor) +{ + if (_callback_need_init) + init_callbacks(); + + return (thecallbacks[major][minor]); +} +#endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_LIST */ +/** @} */ |