blob: c8493e4e979b71576bcdbbb73ce0e356811f7bf6 [file] [log] [blame]
/*
* object_monitor.c
*
* $Id$
*
* functions and data structures for cooperating code to monitor objects.
*
* WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
* WARNING! WARNING!
* WARNING! WARNING!
* WARNING! This code is under active development WARNING!
* WARNING! and is subject to change at any time. WARNING!
* WARNING! WARNING!
* WARNING! WARNING!
* WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/library/container.h>
#include <net-snmp/library/snmp_assert.h>
#include "net-snmp/agent/object_monitor.h"
#if ! defined TRUE
# define TRUE 1
#elif TRUE != 1
error "TRUE != 1"
#endif
/**************************************************************************
*
* Private data structures
*
**************************************************************************/
/*
* individual callback info for an object
*/
typedef struct monitor_info_s {
/** priority for this callback */
int priority;
/** handler that registred to watch this object */
netsnmp_mib_handler *watcher;
/** events that the watcher cares about */
unsigned int events;
/** callback function */
netsnmp_object_monitor_callback *cb;
/** pointer to data from the watcher */
void *watcher_data;
struct monitor_info_s *next;
} monitor_info;
/*
* list of watchers for a given object
*/
typedef struct watcher_list_s {
/** netsnmp_index must be first! */
netsnmp_index monitored_object;
monitor_info *head;
} watcher_list;
/*
* temp holder for ordered list of callbacks
*/
typedef struct callback_placeholder_s {
monitor_info *mi;
netsnmp_monitor_callback_header *cbh;
struct callback_placeholder_s *next;
} callback_placeholder;
/**************************************************************************
*
*
*
**************************************************************************/
/*
* local statics
*/
static char need_init = 1;
static netsnmp_container *monitored_objects = NULL;
static netsnmp_monitor_callback_header *callback_pending_list;
static callback_placeholder *callback_ready_list;
/*
* local prototypes
*/
static watcher_list *find_watchers(oid * object, size_t oid_len);
static int insert_watcher(oid *, size_t, monitor_info *);
static int check_registered(unsigned int event, oid * o, int o_l,
watcher_list ** pWl, monitor_info ** pMi);
static void move_pending_to_ready(void);
/**************************************************************************
*
* Public functions
*
**************************************************************************/
/*
*
*/
void
netsnmp_monitor_init(void)
{
if (!need_init)
return;
callback_pending_list = NULL;
callback_ready_list = NULL;
monitored_objects = netsnmp_container_get("object_monitor:binary_array");
if (NULL != monitored_objects)
need_init = 0;
monitored_objects->compare = netsnmp_compare_netsnmp_index;
monitored_objects->ncompare = netsnmp_ncompare_netsnmp_index;
return;
}
/**************************************************************************
*
* Registration functions
*
**************************************************************************/
/**
* Register a callback for the specified object.
*
* @param object pointer to the OID of the object to monitor.
* @param oid_len length of the OID pointed to by object.
* @param priority the priority to associate with this callback. A
* higher number indicates higher priority. This
* allows multiple callbacks for the same object to
* coordinate the order in which they are called. If
* two callbacks register with the same priority, the
* order is undefined.
* @param events the events which the callback is interested in.
* @param watcher_data pointer to data that will be supplied to the
* callback method when an event occurs.
* @param cb pointer to the function to be called when an event occurs.
*
* NOTE: the combination of the object, priority and watcher_data must
* be unique, as they are the parameters for unregistering a
* callback.
*
* @return SNMPERR_NOERR registration was successful
* @return SNMPERR_MALLOC memory allocation failed
* @return SNMPERR_VALUE the combination of the object, priority and
* watcher_data is not unique.
*/
int
netsnmp_monitor_register(oid * object, size_t oid_len, int priority,
unsigned int events, void *watcher_data,
netsnmp_object_monitor_callback * cb)
{
monitor_info *mi;
int rc;
netsnmp_assert(need_init == 0);
mi = calloc(1, sizeof(monitor_info));
if (NULL == mi)
return SNMPERR_MALLOC;
mi->priority = priority;
mi->events = events;
mi->watcher_data = watcher_data;
mi->cb = cb;
rc = insert_watcher(object, oid_len, mi);
if (rc != SNMPERR_SUCCESS)
free(mi);
return rc;
}
/**
* Unregister a callback for the specified object.
*
* @param object pointer to the OID of the object to monitor.
* @param oid_len length of the OID pointed to by object.
* @param priority the priority to associate with this callback.
* @param wd pointer to data that was supplied when the
* callback was registered.
* @param cb pointer to the function to be called when an event occurs.
*/
int
netsnmp_monitor_unregister(oid * object, size_t oid_len, int priority,
void *wd, netsnmp_object_monitor_callback * cb)
{
monitor_info *mi, *last;
watcher_list *wl = find_watchers(object, oid_len);
if (NULL == wl)
return SNMPERR_GENERR;
last = NULL;
mi = wl->head;
while (mi) {
if ((mi->cb == cb) && (mi->priority == priority) &&
(mi->watcher_data == wd))
break;
last = mi;
mi = mi->next;
}
if (NULL == mi)
return SNMPERR_GENERR;
if (NULL == last)
wl->head = mi->next;
else
last->next = mi->next;
if (NULL == wl->head) {
CONTAINER_REMOVE(monitored_objects, wl);
free(wl->monitored_object.oids);
free(wl);
}
free(mi);
return SNMPERR_SUCCESS;
}
/**************************************************************************
*
* object monitor functions
*
**************************************************************************/
/**
* Notifies the object monitor of an event.
*
* The object monitor funtions will save the callback information
* until all varbinds in the current PDU have been processed and
* a response has been sent. At that time, the object monitor will
* determine if there are any watchers monitoring for the event.
*
* NOTE: the actual type of the callback structure may vary. The
* object monitor functions require only that the start of
* the structure match the netsnmp_monitor_callback_header
* structure. It is up to the watcher and monitored objects
* to agree on the format of other data.
*
* @param cbh pointer to a callback header.
*/
void
netsnmp_notify_monitor(netsnmp_monitor_callback_header * cbh)
{
netsnmp_assert(need_init == 0);
/*
* put processing of until response has been sent
*/
cbh->private = callback_pending_list;
callback_pending_list = cbh;
return;
}
/**
* check to see if a registration exists for an object/event combination
*
* @param event the event type to check for
* @param o the oid to check for
* @param o_l the length of the oid
*
* @returns TRUE(1) if a callback is registerd
* @returns FALSE(0) if no callback is registered
*/
int
netsnmp_monitor_check_registered(int event, oid * o, int o_l)
{
return check_registered(event, o, o_l, NULL, NULL);
}
/**
* Process all pending callbacks
*
* NOTE: this method is not in the public header, as it should only be
* called in one place, in the agent.
*/
void
netsnmp_monitor_process_callbacks(void)
{
netsnmp_assert(need_init == 0);
netsnmp_assert(NULL == callback_ready_list);
if (NULL == callback_pending_list) {
DEBUGMSGT(("object_monitor", "No callbacks to process"));
return;
}
DEBUGMSG(("object_monitor", "Checking for registered " "callbacks."));
/*
* move an pending notification which has a registered watcher to the
* ready list. Free any other notifications.
*/
move_pending_to_ready();
/*
* call callbacks
*/
while (callback_ready_list) {
/*
* pop off the first item
*/
callback_placeholder *current_cbr;
current_cbr = callback_ready_list;
callback_ready_list = current_cbr->next;
/*
* setup, then call callback
*/
current_cbr->cbh->watcher_data = current_cbr->mi->watcher_data;
current_cbr->cbh->priority = current_cbr->mi->priority;
(*current_cbr->mi->cb) (current_cbr->cbh);
/*
* release memory (don't free current_cbr->mi)
*/
if (--(current_cbr->cbh->refs) == 0) {
free(current_cbr->cbh->monitored_object.oids);
free(current_cbr->cbh);
}
free(current_cbr);
/*
* check for any new pending notifications
*/
move_pending_to_ready();
}
netsnmp_assert(callback_ready_list == NULL);
netsnmp_assert(callback_pending_list = NULL);
return;
}
/**************************************************************************
*
* COOPERATIVE helpers
*
**************************************************************************/
/**
* Notifies the object monitor of a cooperative event.
*
* This convenience function will build a
* ::netsnmp_monitor_callback_header and call
* netsnmp_notify_monitor().
*
* @param event the event type
* @param o pointer to the oid of the object sending the event
* @param o_len the lenght of the oid
* @param o_steal set to true if the function may keep the pointer
* to the memory (and free it later). set to false
* to make the function allocate and copy the oid.
* @param object_info pointer to data supplied by the object for
* the callback. This pointer must remain valid,
* will be provided to each callback registered
* for the object (i.e. it will not be copied or
* freed).
*/
void
netsnmp_notify_cooperative(int event, oid * o, size_t o_len, char o_steal,
void *object_info)
{
netsnmp_monitor_callback_cooperative *cbh;
netsnmp_assert(need_init == 0);
cbh = SNMP_MALLOC_TYPEDEF(netsnmp_monitor_callback_cooperative);
if (NULL == cbh) {
snmp_log(LOG_ERR, "could not allocate memory for "
"cooperative callback");
return;
}
cbh->hdr.event = event;
cbh->hdr.object_info = object_info;
cbh->hdr.monitored_object.len = o_len;
if (o_steal) {
cbh->hdr.monitored_object.oids = o;
} else {
cbh->hdr.monitored_object.oids = snmp_duplicate_objid(o, o_len);
}
netsnmp_notify_monitor((netsnmp_monitor_callback_header *) cbh);
}
/** @cond */
/*************************************************************************
*************************************************************************
*************************************************************************
* WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
* WARNING! WARNING!
* WARNING! WARNING!
* WARNING! This code is under active development WARNING!
* WARNING! and is subject to change at any time. WARNING!
* WARNING! WARNING!
* WARNING! WARNING!
* WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
*************************************************************************
*************************************************************************
*************************************************************************
*/
static watcher_list *
find_watchers(oid * object, size_t oid_len)
{
netsnmp_index oah;
oah.oids = object;
oah.len = oid_len;
return (watcher_list *)CONTAINER_FIND(monitored_objects, &oah);
}
static int
insert_watcher(oid * object, size_t oid_len, monitor_info * mi)
{
watcher_list *wl = find_watchers(object, oid_len);
int rc = SNMPERR_SUCCESS;
if (NULL != wl) {
monitor_info *last, *current;
netsnmp_assert(wl->head != NULL);
last = NULL;
current = wl->head;
while (current) {
if (mi->priority == current->priority) {
/*
* check for duplicate
*/
if (mi->watcher_data == current->watcher_data)
return SNMPERR_VALUE; /** duplicate! */
} else if (mi->priority > current->priority) {
break;
}
last = current;
current = current->next;
}
if (NULL == last) {
mi->next = wl->head;
wl->head = mi;
} else {
mi->next = last->next;
last->next = mi;
}
} else {
/*
* first watcher for this oid; set up list
*/
wl = SNMP_MALLOC_TYPEDEF(watcher_list);
if (NULL == wl)
return SNMPERR_MALLOC;
/*
* copy index oid
*/
wl->monitored_object.len = oid_len;
wl->monitored_object.oids = malloc(sizeof(oid) * oid_len);
if (NULL == wl->monitored_object.oids) {
free(wl);
return SNMPERR_MALLOC;
}
memcpy(wl->monitored_object.oids, object, sizeof(oid) * oid_len);
/*
* add watcher, and insert into array
*/
wl->head = mi;
mi->next = NULL;
rc = CONTAINER_INSERT(monitored_objects, wl);
if (rc) {
free(wl->monitored_object.oids);
free(wl);
return rc;
}
}
return rc;
}
/**
* @internal
* check to see if a registration exists for an object/event combination
*
* @param event the event type to check for
* @param o the oid to check for
* @param o_l the length of the oid
* @param pWl if a pointer to a watcher_list pointer is supplied,
* upon return the watcher list pointer will be set to
* the watcher list for the object, or NULL if there are
* no watchers for the object.
* @param pMi if a pointer to a monitor_info pointer is supplied,
* upon return the pointer will be set to the first
* monitor_info object for the specified event.
*
* @returns TRUE(1) if a callback is registerd
* @returns FALSE(0) if no callback is registered
*/
static int
check_registered(unsigned int event, oid * o, int o_l,
watcher_list ** pWl, monitor_info ** pMi)
{
watcher_list *wl;
monitor_info *mi;
netsnmp_assert(need_init == 0);
/*
* check to see if anyone has registered for callbacks
* for the object.
*/
wl = find_watchers(o, o_l);
if (pWl)
*pWl = wl;
if (NULL == wl)
return 0;
/*
* check if any watchers are watching for this specific event
*/
for (mi = wl->head; mi; mi = mi->next) {
if (mi->events & event) {
if (pMi)
*pMi = mi;
return TRUE;
}
}
return 0;
}
/**
*@internal
*/
inline void
insert_ready(callback_placeholder * new_cbr)
{
callback_placeholder *current_cbr, *last_cbr;
/*
* insert in callback ready list
*/
last_cbr = NULL;
current_cbr = callback_ready_list;
while (current_cbr) {
if (new_cbr->mi->priority > current_cbr->mi->priority)
break;
last_cbr = current_cbr;
current_cbr = current_cbr->next;
}
if (NULL == last_cbr) {
new_cbr->next = callback_ready_list;
callback_ready_list = new_cbr;
} else {
new_cbr->next = last_cbr->next;
last_cbr->next = new_cbr;
}
}
/**
*@internal
*
* move an pending notification which has a registered watcher to the
* ready list. Free any other notifications.
*/
static void
move_pending_to_ready(void)
{
/*
* check to see if anyone has registered for callbacks
* for each object.
*/
while (callback_pending_list) {
watcher_list *wl;
monitor_info *mi;
netsnmp_monitor_callback_header *cbp;
/*
* pop off first item
*/
cbp = callback_pending_list;
callback_pending_list = cbp->private; /** next */
if (0 == check_registered(cbp->event, cbp->monitored_object.oids,
cbp->monitored_object.len, &wl,
&mi)) {
/*
* nobody watching, free memory
*/
free(cbp);
continue;
}
/*
* Found at least one; check the rest of the list and
* save callback for processing
*/
for (; mi; mi = mi->next) {
callback_placeholder *new_cbr;
if (0 == (mi->events & cbp->event))
continue;
/*
* create temprory placeholder.
*
* I hate to allocate memory here, as I'd like this code to
* be fast and lean. But I don't have time to think of another
* solution os this will have to do for now.
*
* I need a list of monitor_info (mi) objects for each
* callback which has registered for the event, and want
* that list sorted by the priority required by the watcher.
*/
new_cbr = SNMP_MALLOC_TYPEDEF(callback_placeholder);
if (NULL == new_cbr) {
snmp_log(LOG_ERR, "malloc failed, callback dropped.");
continue;
}
new_cbr->cbh = cbp;
new_cbr->mi = mi;
++cbp->refs;
/*
* insert in callback ready list
*/
insert_ready(new_cbr);
} /** end mi loop */
} /** end cbp loop */
netsnmp_assert(callback_pending_list == NULL);
}
#if defined TESTING_OBJECT_MONITOR
/**************************************************************************
*
* (untested) TEST CODE
*
*/
void
dummy_callback(netsnmp_monitor_callback_header * cbh)
{
printf("Callback received.\n");
}
void
dump_watchers(netsnmp_index *oah, void *)
{
watcher_list *wl = (watcher_list *) oah;
netsnmp_monitor_callback_header *cbh = wl->head;
printf("Watcher List for OID ");
print_objid(wl->hdr->oids, wl->hdr->len);
printf("\n");
while (cbh) {
printf("Priority = %d;, Events = %d; Watcher Data = 0x%x\n",
cbh->priority, cbh->events, cbh->watcher_data);
cbh = cbh->private;
}
}
void
main(int argc, char **argv)
{
oid object[3] = { 1, 3, 6 };
int object_len = 3;
int rc;
/*
* init
*/
netsnmp_monitor_init();
/*
* insert an object
*/
rc = netsnmp_monitor_register(object, object_len, 0,
EVENT_ROW_ADD, (void *) 0xdeadbeef,
dummy_callback);
printf("insert an object: %d\n", rc);
/*
* insert same object, new priority
*/
netsnmp_monitor_register(object, object_len, 10,
EVENT_ROW_ADD, (void *) 0xdeadbeef,
dummy_callback);
printf("insert same object, new priority: %d\n", rc);
/*
* insert same object, same priority, new data
*/
netsnmp_monitor_register(object, object_len, 10,
EVENT_ROW_ADD, (void *) 0xbeefdead,
dummy_callback);
printf("insert same object, same priority, new data: %d\n", rc);
/*
* insert same object, same priority, same data
*/
netsnmp_monitor_register(object, object_len, 10,
EVENT_ROW_ADD, (void *) 0xbeefdead,
dummy_callback);
printf("insert same object, same priority, new data: %d\n", rc);
/*
* dump table
*/
CONTAINER_FOR_EACH(monitored_objects, dump_watchers, NULL);
}
#endif /** defined TESTING_OBJECT_MONITOR */
/** @endcond */