summaryrefslogtreecommitdiff
path: root/snmplib/callback.c
diff options
context:
space:
mode:
Diffstat (limited to 'snmplib/callback.c')
-rw-r--r--snmplib/callback.c585
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 */
+/** @} */