| /* |
| * 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 */ |
| |
| |