summaryrefslogtreecommitdiff
path: root/agent/object_monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'agent/object_monitor.c')
-rw-r--r--agent/object_monitor.c757
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 */
+
+