diff options
Diffstat (limited to 'agent/object_monitor.c')
-rw-r--r-- | agent/object_monitor.c | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/agent/object_monitor.c b/agent/object_monitor.c new file mode 100644 index 0000000..2ab8c9b --- /dev/null +++ b/agent/object_monitor.c @@ -0,0 +1,757 @@ +/* + * 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 +#elsif 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 */ + + |