589 lines
17 KiB
C
589 lines
17 KiB
C
/*
|
|
* 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;
|
|
struct snmp_gen_callback **prevNext;
|
|
int count = 0;
|
|
|
|
if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS)
|
|
return SNMPERR_GENERR;
|
|
|
|
scp = thecallbacks[major][minor];
|
|
prevNext = &(thecallbacks[major][minor]);
|
|
|
|
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 */
|
|
/** @} */
|