diff options
Diffstat (limited to 'agent/helpers/table_iterator.c')
-rw-r--r-- | agent/helpers/table_iterator.c | 1089 |
1 files changed, 1089 insertions, 0 deletions
diff --git a/agent/helpers/table_iterator.c b/agent/helpers/table_iterator.c new file mode 100644 index 0000000..e7742ed --- /dev/null +++ b/agent/helpers/table_iterator.c @@ -0,0 +1,1089 @@ +/* + * table_iterator.c + */ +/* 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 table_iterator table_iterator + * The table iterator helper is designed to simplify the task of writing a table handler for the net-snmp agent when the data being accessed is not in an oid sorted form and must be accessed externally. + * @ingroup table + Functionally, it is a specialized version of the more + generic table helper but easies the burden of GETNEXT processing by + manually looping through all the data indexes retrieved through + function calls which should be supplied by the module that wishes + help. The module the table_iterator helps should, afterwards, + never be called for the case of "MODE_GETNEXT" and only for the GET + and SET related modes instead. + + The fundamental notion between the table iterator is that it + allows your code to iterate over each "row" within your data + storage mechanism, without requiring that it be sorted in a + SNMP-index-compliant manner. Through the get_first_data_point and + get_next_data_point hooks, the table_iterator helper will + repeatedly call your hooks to find the "proper" row of data that + needs processing. The following concepts are important: + + - A loop context is a pointer which indicates where in the + current processing of a set of rows you currently are. Allows + the get_*_data_point routines to move from one row to the next, + once the iterator handler has identified the appropriate row for + this request, the job of the loop context is done. The + most simple example would be a pointer to an integer which + simply counts rows from 1 to X. More commonly, it might be a + pointer to a linked list node, or someother internal or + external reference to a data set (file seek value, array + pointer, ...). If allocated during iteration, either the + free_loop_context_at_end (preferably) or the free_loop_context + pointers should be set. + + - A data context is something that your handler code can use + in order to retrieve the rest of the data for the needed + row. This data can be accessed in your handler via + netsnmp_extract_iterator_context api with the netsnmp_request_info + structure that's passed in. + The important difference between a loop context and a + data context is that multiple data contexts can be kept by the + table_iterator helper, where as only one loop context will + ever be held by the table_iterator helper. If allocated + during iteration the free_data_context pointer should be set + to an appropriate function. + + The table iterator operates in a series of steps that call your + code hooks from your netsnmp_iterator_info registration pointer. + + - the get_first_data_point hook is called at the beginning of + processing. It should set the variable list to a list of + indexes for the given table. It should also set the + loop_context and maybe a data_context which you will get a + pointer back to when it needs to call your code to retrieve + actual data later. The list of indexes should be returned + after being update. + + - the get_next_data_point hook is then called repeatedly and is + passed the loop context and the data context for it to update. + The indexes, loop context and data context should all be + updated if more data is available, otherwise they should be + left alone and a NULL should be returned. Ideally, it should + update the loop context without the need to reallocate it. If + reallocation is necessary for every iterative step, then the + free_loop_context function pointer should be set. If not, + then the free_loop_context_at_end pointer should be set, which + is more efficient since a malloc/free will only be performed + once for every iteration. + * + * @{ + */ + +#include <net-snmp/net-snmp-config.h> + +#if HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif + +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> + +#include <net-snmp/agent/table.h> +#include <net-snmp/agent/serialize.h> +#include <net-snmp/agent/table_iterator.h> +#include <net-snmp/agent/stash_cache.h> + +/* ================================== + * + * Iterator API: Table maintenance + * + * ================================== */ + + /* + * Iterator-based tables are typically maintained by external + * code, and this helper is really only concerned with + * mapping between a walk through this local representation, + * and the requirements of SNMP table ordering. + * However, there's a case to be made for considering the + * iterator info structure as encapsulating the table, so + * it's probably worth defining the table creation/deletion + * routines from the generic API. + * + * Time will show whether this is a sensible approach or not. + */ +netsnmp_iterator_info * +netsnmp_iterator_create_table( Netsnmp_First_Data_Point *firstDP, + Netsnmp_Next_Data_Point *nextDP, + Netsnmp_First_Data_Point *getidx, + netsnmp_variable_list *indexes) +{ + netsnmp_iterator_info *iinfo = + SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info); + + if ( !iinfo ) + return NULL; + + if ( indexes ) + iinfo->indexes = snmp_clone_varbind(indexes); + iinfo->get_first_data_point = firstDP; + iinfo->get_next_data_point = nextDP; + iinfo->get_row_indexes = getidx; + + return iinfo; +} + +void +netsnmp_iterator_delete_table( netsnmp_iterator_info *iinfo ) +{ + if (!iinfo) + return; + + if (iinfo->indexes) { + snmp_free_varbind( iinfo->indexes ); + iinfo->indexes = NULL; + } + SNMP_FREE( iinfo ); +} + + /* + * The rest of the table maintenance section of the + * generic table API is Not Applicable to this helper. + * + * The contents of a iterator-based table will be + * maintained by the table-specific module itself. + */ + +/* ================================== + * + * Iterator API: MIB maintenance + * + * ================================== */ + +/** returns a netsnmp_mib_handler object for the table_iterator helper */ +netsnmp_mib_handler * +netsnmp_get_table_iterator_handler(netsnmp_iterator_info *iinfo) +{ + netsnmp_mib_handler *me; + + if (!iinfo) + return NULL; + + me = + netsnmp_create_handler(TABLE_ITERATOR_NAME, + netsnmp_table_iterator_helper_handler); + + if (!me) + return NULL; + + me->myvoid = iinfo; + return me; +} + +/** + * Creates and registers a table iterator helper handler calling + * netsnmp_create_handler with a handler name set to TABLE_ITERATOR_NAME + * and access method, netsnmp_table_iterator_helper_handler. + * + * If NOT_SERIALIZED is not defined the function injects the serialize + * handler into the calling chain prior to calling netsnmp_register_table. + * + * @param reginfo is a pointer to a netsnmp_handler_registration struct + * + * @param iinfo is a pointer to a netsnmp_iterator_info struct + * + * @return MIB_REGISTERED_OK is returned if the registration was a success. + * Failures are MIB_REGISTRATION_FAILED, MIB_DUPLICATE_REGISTRATION. + * If iinfo is NULL, SNMPERR_GENERR is returned. + * + */ +int +netsnmp_register_table_iterator(netsnmp_handler_registration *reginfo, + netsnmp_iterator_info *iinfo) +{ + reginfo->modes |= HANDLER_CAN_STASH; + netsnmp_inject_handler(reginfo, + netsnmp_get_table_iterator_handler(iinfo)); + if (!iinfo) + return SNMPERR_GENERR; + if (!iinfo->indexes && iinfo->table_reginfo && + iinfo->table_reginfo->indexes ) + iinfo->indexes = snmp_clone_varbind( iinfo->table_reginfo->indexes ); + + return netsnmp_register_table(reginfo, iinfo->table_reginfo); +} + +/** extracts the table_iterator specific data from a request. + * This function extracts the table iterator specific data from a + * netsnmp_request_info object. Calls netsnmp_request_get_list_data + * with request->parent_data set with data from a request that was added + * previously by a module and TABLE_ITERATOR_NAME handler name. + * + * @param request the netsnmp request info structure + * + * @return a void pointer(request->parent_data->data), otherwise NULL is + * returned if request is NULL or request->parent_data is NULL or + * request->parent_data object is not found.the net + * + */ +NETSNMP_INLINE void * +netsnmp_extract_iterator_context(netsnmp_request_info *request) +{ + return netsnmp_request_get_list_data(request, TABLE_ITERATOR_NAME); +} + +/** inserts table_iterator specific data for a newly + * created row into a request */ +NETSNMP_INLINE void +netsnmp_insert_iterator_context(netsnmp_request_info *request, void *data) +{ + netsnmp_request_info *req; + netsnmp_table_request_info *table_info = NULL; + netsnmp_variable_list *this_index = NULL; + netsnmp_variable_list *that_index = NULL; + oid base_oid[] = {0, 0}; /* Make sure index OIDs are legal! */ + oid this_oid[MAX_OID_LEN]; + oid that_oid[MAX_OID_LEN]; + size_t this_oid_len, that_oid_len; + + if (!request) + return; + + /* + * We'll add the new row information to any request + * structure with the same index values as the request + * passed in (which includes that one!). + * + * So construct an OID based on these index values. + */ + + table_info = netsnmp_extract_table_info(request); + this_index = table_info->indexes; + build_oid_noalloc(this_oid, MAX_OID_LEN, &this_oid_len, + base_oid, 2, this_index); + + /* + * We need to look through the whole of the request list + * (as received by the current handler), as there's no + * guarantee that this routine will be called by the first + * varbind that refers to this row. + * In particular, a RowStatus controlled row creation + * may easily occur later in the variable list. + * + * So first, we rewind to the head of the list.... + */ + for (req=request; req->prev; req=req->prev) + ; + + /* + * ... and then start looking for matching indexes + * (by constructing OIDs from these index values) + */ + for (; req; req=req->next) { + table_info = netsnmp_extract_table_info(req); + that_index = table_info->indexes; + build_oid_noalloc(that_oid, MAX_OID_LEN, &that_oid_len, + base_oid, 2, that_index); + + /* + * This request has the same index values, + * so add the newly-created row information. + */ + if (snmp_oid_compare(this_oid, this_oid_len, + that_oid, that_oid_len) == 0) { + netsnmp_request_add_list_data(req, + netsnmp_create_data_list(TABLE_ITERATOR_NAME, data, NULL)); + } + } +} + +#define TI_REQUEST_CACHE "ti_cache" + +typedef struct ti_cache_info_s { + oid best_match[MAX_OID_LEN]; + size_t best_match_len; + void *data_context; + Netsnmp_Free_Data_Context *free_context; + netsnmp_iterator_info *iinfo; + netsnmp_variable_list *results; +} ti_cache_info; + +static void +netsnmp_free_ti_cache(void *it) { + ti_cache_info *beer = it; + if (!it) return; + if (beer->data_context && beer->free_context) { + (beer->free_context)(beer->data_context, beer->iinfo); + } + if (beer->results) { + snmp_free_varbind(beer->results); + } + free(beer); +} + +/* caches information (in the request) we'll need at a later point in time */ +static ti_cache_info * +netsnmp_iterator_remember(netsnmp_request_info *request, + oid *oid_to_save, + size_t oid_to_save_len, + void *callback_data_context, + void *callback_loop_context, + netsnmp_iterator_info *iinfo) +{ + ti_cache_info *ti_info; + + if (!request || !oid_to_save || oid_to_save_len > MAX_OID_LEN) + return NULL; + + /* extract existing cached state */ + ti_info = netsnmp_request_get_list_data(request, TI_REQUEST_CACHE); + + /* no existing cached state. make a new one. */ + if (!ti_info) { + ti_info = SNMP_MALLOC_TYPEDEF(ti_cache_info); + netsnmp_request_add_list_data(request, + netsnmp_create_data_list + (TI_REQUEST_CACHE, + ti_info, + netsnmp_free_ti_cache)); + } + + /* free existing cache before replacing */ + if (ti_info->data_context && ti_info->free_context) + (ti_info->free_context)(ti_info->data_context, iinfo); + + /* maybe generate it from the loop context? */ + if (iinfo->make_data_context && !callback_data_context) { + callback_data_context = + (iinfo->make_data_context)(callback_loop_context, iinfo); + + } + + /* save data as requested */ + ti_info->data_context = callback_data_context; + ti_info->free_context = iinfo->free_data_context; + ti_info->best_match_len = oid_to_save_len; + ti_info->iinfo = iinfo; + if (oid_to_save_len) + memcpy(ti_info->best_match, oid_to_save, oid_to_save_len * sizeof(oid)); + + return ti_info; +} + +#define TABLE_ITERATOR_NOTAGAIN 255 +/* implements the table_iterator helper */ +int +netsnmp_table_iterator_helper_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + netsnmp_table_registration_info *tbl_info; + netsnmp_table_request_info *table_info = NULL; + oid coloid[MAX_OID_LEN]; + size_t coloid_len; + int ret = SNMP_ERR_NOERROR; + static oid myname[MAX_OID_LEN]; + size_t myname_len; + int oldmode = 0; + netsnmp_iterator_info *iinfo; + int notdone; + int hintok = 0; + netsnmp_request_info *request, *reqtmp = NULL; + netsnmp_variable_list *index_search = NULL; + netsnmp_variable_list *free_this_index_search = NULL; + void *callback_loop_context = NULL, *last_loop_context; + void *callback_data_context = NULL; + ti_cache_info *ti_info = NULL; + int request_count = 0; + netsnmp_oid_stash_node **cinfo = NULL; + netsnmp_variable_list *old_indexes = NULL, *vb; + netsnmp_table_registration_info *table_reg_info = NULL; + int i; + netsnmp_data_list *ldata = NULL; + + iinfo = (netsnmp_iterator_info *) handler->myvoid; + if (!iinfo || !reginfo || !reqinfo) + return SNMPERR_GENERR; + + tbl_info = iinfo->table_reginfo; + + /* + * copy in the table registration oid for later use + */ + coloid_len = reginfo->rootoid_len + 2; + memcpy(coloid, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid)); + coloid[reginfo->rootoid_len] = 1; /* table.entry node */ + + /* + * illegally got here if these functions aren't defined + */ + if (iinfo->get_first_data_point == NULL || + iinfo->get_next_data_point == NULL) { + snmp_log(LOG_ERR, + "table_iterator helper called without data accessor functions\n"); + return SNMP_ERR_GENERR; + } + + /* preliminary analysis */ + switch (reqinfo->mode) { + case MODE_GET_STASH: + cinfo = netsnmp_extract_stash_cache(reqinfo); + table_reg_info = netsnmp_find_table_registration_info(reginfo); + + /* XXX: move this malloc to stash_cache handler? */ + reqtmp = SNMP_MALLOC_TYPEDEF(netsnmp_request_info); + reqtmp->subtree = requests->subtree; + table_info = netsnmp_extract_table_info(requests); + netsnmp_request_add_list_data(reqtmp, + netsnmp_create_data_list + (TABLE_HANDLER_NAME, + (void *) table_info, NULL)); + + /* remember the indexes that were originally parsed. */ + old_indexes = table_info->indexes; + break; + + case MODE_GETNEXT: + for(request = requests ; request; request = request->next) { + if (request->processed) + continue; + table_info = netsnmp_extract_table_info(request); + if (table_info->colnum < tbl_info->min_column - 1) { + /* XXX: optimize better than this */ + /* for now, just increase to colnum-1 */ + /* we need to jump to the lowest result of the min_column + and take it, comparing to nothing from the request */ + table_info->colnum = tbl_info->min_column - 1; + } else if (table_info->colnum > tbl_info->max_column) { + request->processed = TABLE_ITERATOR_NOTAGAIN; + } + + ti_info = + netsnmp_request_get_list_data(request, TI_REQUEST_CACHE); + if (!ti_info) { + ti_info = SNMP_MALLOC_TYPEDEF(ti_cache_info); + netsnmp_request_add_list_data(request, + netsnmp_create_data_list + (TI_REQUEST_CACHE, + ti_info, + netsnmp_free_ti_cache)); + } + + /* XXX: if no valid requests, don't even loop below */ + } + break; + } + + /* + * collect all information for each needed row + */ + if (reqinfo->mode == MODE_GET || + reqinfo->mode == MODE_GETNEXT || + reqinfo->mode == MODE_GET_STASH || + reqinfo->mode == MODE_SET_RESERVE1) { + /* + * Count the number of request in the list, + * so that we'll know when we're finished + */ + for(request = requests ; request; request = request->next) + if (!request->processed) + request_count++; + notdone = 1; + hintok = 1; + while(notdone) { + notdone = 0; + + /* find first data point */ + if (!index_search) { + if (free_this_index_search) { + /* previously done */ + index_search = free_this_index_search; + } else { + for(request=requests ; request; request=request->next) { + table_info = netsnmp_extract_table_info(request); + if (table_info) + break; + } + if (!table_info) { + snmp_log(LOG_WARNING, + "no valid requests for iterator table %s\n", + reginfo->handlerName); + netsnmp_free_request_data_sets(reqtmp); + SNMP_FREE(reqtmp); + return SNMP_ERR_NOERROR; + } + index_search = snmp_clone_varbind(table_info->indexes); + free_this_index_search = index_search; + + /* setup, malloc search data: */ + if (!index_search) { + /* + * hmmm.... invalid table? + */ + snmp_log(LOG_WARNING, + "invalid index list or failed malloc for table %s\n", + reginfo->handlerName); + netsnmp_free_request_data_sets(reqtmp); + SNMP_FREE(reqtmp); + return SNMP_ERR_NOERROR; + } + } + } + + /* if sorted, pass in a hint */ + if (hintok && (iinfo->flags & NETSNMP_ITERATOR_FLAG_SORTED)) { + callback_loop_context = table_info; + } + index_search = + (iinfo->get_first_data_point) (&callback_loop_context, + &callback_data_context, + index_search, iinfo); + + /* loop over each data point */ + while(index_search) { + + /* remember to free this later */ + free_this_index_search = index_search; + + /* compare against each request*/ + for(request = requests ; request; request = request->next) { + if (request->processed) + continue; + + /* XXX: store in an array for faster retrival */ + table_info = netsnmp_extract_table_info(request); + coloid[reginfo->rootoid_len + 1] = table_info->colnum; + + ti_info = + netsnmp_request_get_list_data(request, TI_REQUEST_CACHE); + + switch(reqinfo->mode) { + case MODE_GET: + case MODE_SET_RESERVE1: + /* looking for exact matches */ + build_oid_noalloc(myname, MAX_OID_LEN, &myname_len, + coloid, coloid_len, index_search); + if (snmp_oid_compare(myname, myname_len, + request->requestvb->name, + request->requestvb->name_length) == 0) { + /* keep this */ + netsnmp_iterator_remember(request, + myname, myname_len, + callback_data_context, + callback_loop_context, iinfo); + request_count--; /* One less to look for */ + } else { + if (iinfo->free_data_context && callback_data_context) { + (iinfo->free_data_context)(callback_data_context, + iinfo); + } + } + break; + + case MODE_GET_STASH: + /* collect data for each column for every row */ + build_oid_noalloc(myname, MAX_OID_LEN, &myname_len, + coloid, coloid_len, index_search); + reqinfo->mode = MODE_GET; + if (reqtmp) + ldata = + netsnmp_get_list_node(reqtmp->parent_data, + TABLE_ITERATOR_NAME); + if (!ldata) { + netsnmp_request_add_list_data(reqtmp, + netsnmp_create_data_list + (TABLE_ITERATOR_NAME, + callback_data_context, + NULL)); + } else { + /* may have changed */ + ldata->data = callback_data_context; + } + + table_info->indexes = index_search; + for(i = table_reg_info->min_column; + i <= (int)table_reg_info->max_column; i++) { + myname[reginfo->rootoid_len + 1] = i; + table_info->colnum = i; + vb = reqtmp->requestvb = + SNMP_MALLOC_TYPEDEF(netsnmp_variable_list); + vb->type = ASN_NULL; + snmp_set_var_objid(vb, myname, myname_len); + netsnmp_call_next_handler(handler, reginfo, + reqinfo, reqtmp); + reqtmp->requestvb = NULL; + reqtmp->processed = 0; + if (vb->type != ASN_NULL) { /* XXX, not all */ + netsnmp_oid_stash_add_data(cinfo, myname, + myname_len, vb); + } else { + snmp_free_var(vb); + } + } + reqinfo->mode = MODE_GET_STASH; + break; + + case MODE_GETNEXT: + /* looking for "next" matches */ + if (netsnmp_check_getnext_reply + (request, coloid, coloid_len, index_search, + &ti_info->results)) { + netsnmp_iterator_remember(request, + ti_info->results->name, + ti_info->results->name_length, + callback_data_context, + callback_loop_context, iinfo); + /* + * If we've been told that the rows are sorted, + * then the first valid one we find + * must be the right one. + */ + if (iinfo->flags & NETSNMP_ITERATOR_FLAG_SORTED) + request_count--; + + } else { + if (iinfo->free_data_context && callback_data_context) { + (iinfo->free_data_context)(callback_data_context, + iinfo); + } + } + break; + + case MODE_SET_RESERVE2: + case MODE_SET_FREE: + case MODE_SET_UNDO: + case MODE_SET_COMMIT: + /* needed processing already done in RESERVE1 */ + break; + + default: + snmp_log(LOG_ERR, + "table_iterator called with unsupported mode\n"); + break; /* XXX return */ + + } + } + + /* Is there any point in carrying on? */ + if (!request_count) + break; + /* get the next search possibility */ + last_loop_context = callback_loop_context; + index_search = + (iinfo->get_next_data_point) (&callback_loop_context, + &callback_data_context, + index_search, iinfo); + if (iinfo->free_loop_context && last_loop_context && + callback_data_context != last_loop_context) { + (iinfo->free_loop_context) (last_loop_context, iinfo); + last_loop_context = NULL; + } + } + + /* free loop context before going on */ + if (callback_loop_context && iinfo->free_loop_context_at_end) { + (iinfo->free_loop_context_at_end) (callback_loop_context, + iinfo); + callback_loop_context = NULL; + } + + /* decide which (GETNEXT) requests are not yet filled */ + if (reqinfo->mode == MODE_GETNEXT) { + for(request = requests ; request; request = request->next) { + if (request->processed) + continue; + ti_info = + netsnmp_request_get_list_data(request, + TI_REQUEST_CACHE); + if (!ti_info->results) { + int nc; + table_info = netsnmp_extract_table_info(request); + nc = netsnmp_table_next_column(table_info); + if (0 == nc) { + coloid[reginfo->rootoid_len+1] = table_info->colnum+1; + snmp_set_var_objid(request->requestvb, + coloid, reginfo->rootoid_len+2); + request->processed = TABLE_ITERATOR_NOTAGAIN; + break; + } else { + table_info->colnum = nc; + hintok = 0; + notdone = 1; + } + } + } + } + } + } + + if (reqinfo->mode == MODE_GET || + reqinfo->mode == MODE_GETNEXT || + reqinfo->mode == MODE_SET_RESERVE1) { + /* per request last minute processing */ + for(request = requests ; request; request = request->next) { + if (request->processed) + continue; + ti_info = + netsnmp_request_get_list_data(request, TI_REQUEST_CACHE); + table_info = + netsnmp_extract_table_info(request); + + if (!ti_info) + continue; + + switch(reqinfo->mode) { + + case MODE_GETNEXT: + if (ti_info->best_match_len) + snmp_set_var_objid(request->requestvb, ti_info->best_match, + ti_info->best_match_len); + else { + coloid[reginfo->rootoid_len+1] = + netsnmp_table_next_column(table_info); + if (0 == coloid[reginfo->rootoid_len+1]) { + /* out of range. */ + coloid[reginfo->rootoid_len+1] = tbl_info->max_column + 1; + request->processed = TABLE_ITERATOR_NOTAGAIN; + } + snmp_set_var_objid(request->requestvb, + coloid, reginfo->rootoid_len+2); + request->processed = 1; + } + snmp_free_varbind(table_info->indexes); + table_info->indexes = snmp_clone_varbind(ti_info->results); + /* FALL THROUGH */ + + case MODE_GET: + case MODE_SET_RESERVE1: + if (ti_info->data_context) + /* we don't add a free pointer, since it's in the + TI_REQUEST_CACHE instead */ + netsnmp_request_add_list_data(request, + netsnmp_create_data_list + (TABLE_ITERATOR_NAME, + ti_info->data_context, + NULL)); + break; + + default: + break; + } + } + + /* we change all GETNEXT operations into GET operations. + why? because we're just so nice to the lower levels. + maybe someday they'll pay us for it. doubtful though. */ + oldmode = reqinfo->mode; + if (reqinfo->mode == MODE_GETNEXT) { + reqinfo->mode = MODE_GET; + } + } else if (reqinfo->mode == MODE_GET_STASH) { + netsnmp_free_request_data_sets(reqtmp); + SNMP_FREE(reqtmp); + table_info->indexes = old_indexes; + } + + + /* Finally, we get to call the next handler below us. Boy, wasn't + all that simple? They better be glad they don't have to do it! */ + if (reqinfo->mode != MODE_GET_STASH) { + DEBUGMSGTL(("table_iterator", "call subhandler for mode: %s\n", + se_find_label_in_slist("agent_mode", oldmode))); + ret = + netsnmp_call_next_handler(handler, reginfo, reqinfo, requests); + } + + /* reverse the previously saved mode if we were a getnext */ + if (oldmode == MODE_GETNEXT) { + reqinfo->mode = oldmode; + } + + /* cleanup */ + if (free_this_index_search) + snmp_free_varbind(free_this_index_search); + + return ret; +} + +/* ================================== + * + * Iterator API: Row operations + * + * ================================== */ + +void * +netsnmp_iterator_row_first( netsnmp_iterator_info *iinfo ) { + netsnmp_variable_list *vp1, *vp2; + void *ctx1, *ctx2; + + if (!iinfo) + return NULL; + + vp1 = snmp_clone_varbind(iinfo->indexes); + vp2 = iinfo->get_first_data_point( &ctx1, &ctx2, vp1, iinfo ); + + if (!vp2) + ctx2 = NULL; + + /* free loop context ?? */ + snmp_free_varbind( vp1 ); + return ctx2; /* or *ctx2 ?? */ +} + +void * +netsnmp_iterator_row_get( netsnmp_iterator_info *iinfo, void *row ) +{ + netsnmp_variable_list *vp1, *vp2; + void *ctx1, *ctx2; + + if (!iinfo || !row) + return NULL; + + /* + * This routine relies on being able to + * determine the indexes for a given row. + */ + if (!iinfo->get_row_indexes) + return NULL; + + vp1 = snmp_clone_varbind(iinfo->indexes); + ctx1 = row; /* Probably only need one of these ... */ + ctx2 = row; + vp2 = iinfo->get_row_indexes( &ctx1, &ctx2, vp1, iinfo ); + + ctx2 = NULL; + if (vp2) { + ctx2 = netsnmp_iterator_row_get_byidx( iinfo, vp2 ); + } + snmp_free_varbind( vp1 ); + return ctx2; +} + +void * +netsnmp_iterator_row_next( netsnmp_iterator_info *iinfo, void *row ) +{ + netsnmp_variable_list *vp1, *vp2; + void *ctx1, *ctx2; + + if (!iinfo || !row) + return NULL; + + /* + * This routine relies on being able to + * determine the indexes for a given row. + */ + if (!iinfo->get_row_indexes) + return NULL; + + vp1 = snmp_clone_varbind(iinfo->indexes); + ctx1 = row; /* Probably only need one of these ... */ + ctx2 = row; + vp2 = iinfo->get_row_indexes( &ctx1, &ctx2, vp1, iinfo ); + + ctx2 = NULL; + if (vp2) { + ctx2 = netsnmp_iterator_row_next_byidx( iinfo, vp2 ); + } + snmp_free_varbind( vp1 ); + return ctx2; +} + +void * +netsnmp_iterator_row_get_byidx( netsnmp_iterator_info *iinfo, + netsnmp_variable_list *indexes ) +{ + oid dummy[] = {0,0}; /* Keep 'build_oid' happy */ + oid instance[MAX_OID_LEN]; + size_t len = MAX_OID_LEN; + + if (!iinfo || !indexes) + return NULL; + + build_oid_noalloc(instance, MAX_OID_LEN, &len, + dummy, 2, indexes); + return netsnmp_iterator_row_get_byoid( iinfo, instance+2, len-2 ); +} + +void * +netsnmp_iterator_row_next_byidx( netsnmp_iterator_info *iinfo, + netsnmp_variable_list *indexes ) +{ + oid dummy[] = {0,0}; + oid instance[MAX_OID_LEN]; + size_t len = MAX_OID_LEN; + + if (!iinfo || !indexes) + return NULL; + + build_oid_noalloc(instance, MAX_OID_LEN, &len, + dummy, 2, indexes); + return netsnmp_iterator_row_next_byoid( iinfo, instance+2, len-2 ); +} + +void * +netsnmp_iterator_row_get_byoid( netsnmp_iterator_info *iinfo, + oid *instance, size_t len ) +{ + oid dummy[] = {0,0}; + oid this_inst[ MAX_OID_LEN]; + size_t this_len; + netsnmp_variable_list *vp1, *vp2; + void *ctx1, *ctx2; + int n; + + if (!iinfo || !iinfo->get_first_data_point + || !iinfo->get_next_data_point ) + return NULL; + + if ( !instance || !len ) + return NULL; + + vp1 = snmp_clone_varbind(iinfo->indexes); + vp2 = iinfo->get_first_data_point( &ctx1, &ctx2, vp1, iinfo ); + DEBUGMSGTL(("table:iterator:get", "first DP: %x %x %x\n", + ctx1, ctx2, vp2)); + + /* XXX - free context ? */ + + while ( vp2 ) { + this_len = MAX_OID_LEN; + build_oid_noalloc(this_inst, MAX_OID_LEN, &this_len, dummy, 2, vp2); + n = snmp_oid_compare( instance, len, this_inst+2, this_len-2 ); + if ( n == 0 ) + break; /* Found matching row */ + + if (( n > 0) && + (iinfo->flags & NETSNMP_ITERATOR_FLAG_SORTED)) { + vp2 = NULL; /* Row not present */ + break; + } + + vp2 = iinfo->get_next_data_point( &ctx1, &ctx2, vp2, iinfo ); + DEBUGMSGTL(("table:iterator:get", "next DP: %x %x %x\n", + ctx1, ctx2, vp2)); + /* XXX - free context ? */ + } + + /* XXX - final free context ? */ + snmp_free_varbind( vp1 ); + + return ( vp2 ? ctx2 : NULL ); +} + +void * +netsnmp_iterator_row_next_byoid( netsnmp_iterator_info *iinfo, + oid *instance, size_t len ) +{ + oid dummy[] = {0,0}; + oid this_inst[ MAX_OID_LEN]; + size_t this_len; + oid best_inst[ MAX_OID_LEN]; + size_t best_len = 0; + netsnmp_variable_list *vp1, *vp2; + void *ctx1, *ctx2; + int n; + + if (!iinfo || !iinfo->get_first_data_point + || !iinfo->get_next_data_point ) + return NULL; + + vp1 = snmp_clone_varbind(iinfo->indexes); + vp2 = iinfo->get_first_data_point( &ctx1, &ctx2, vp1, iinfo ); + DEBUGMSGTL(("table:iterator:get", "first DP: %x %x %x\n", + ctx1, ctx2, vp2)); + + if ( !instance || !len ) { + snmp_free_varbind( vp1 ); + return ( vp2 ? ctx2 : NULL ); /* First entry */ + } + + /* XXX - free context ? */ + + while ( vp2 ) { + this_len = MAX_OID_LEN; + build_oid_noalloc(this_inst, MAX_OID_LEN, &this_len, dummy, 2, vp2); + n = snmp_oid_compare( instance, len, this_inst+2, this_len-2 ); + + /* + * Look for the best-fit candidate for the next row + * (bearing in mind the rows may not be ordered "correctly") + */ + if ( n > 0 ) { + if ( best_len == 0 ) { + memcpy( best_inst, this_inst, sizeof( this_inst )); + best_len = this_len; + if (iinfo->flags & NETSNMP_ITERATOR_FLAG_SORTED) + break; + } else { + n = snmp_oid_compare( best_inst, best_len, this_inst, this_len ); + if ( n < 0 ) { + memcpy( best_inst, this_inst, sizeof( this_inst )); + best_len = this_len; + if (iinfo->flags & NETSNMP_ITERATOR_FLAG_SORTED) + break; + } + } + } + + vp2 = iinfo->get_next_data_point( &ctx1, &ctx2, vp2, iinfo ); + DEBUGMSGTL(("table:iterator:get", "next DP: %x %x %x\n", + ctx1, ctx2, vp2)); + /* XXX - free context ? */ + } + + /* XXX - final free context ? */ + snmp_free_varbind( vp1 ); + + return ( vp2 ? ctx2 : NULL ); +} + +int +netsnmp_iterator_row_count( netsnmp_iterator_info *iinfo ) +{ + netsnmp_variable_list *vp1, *vp2; + void *ctx1, *ctx2; + int i=0; + + if (!iinfo || !iinfo->get_first_data_point + || !iinfo->get_next_data_point ) + return 0; + + vp1 = snmp_clone_varbind(iinfo->indexes); + vp2 = iinfo->get_first_data_point( &ctx1, &ctx2, vp1, iinfo ); + if (!vp2) { + snmp_free_varbind( vp1 ); + return 0; + } + + DEBUGMSGTL(("table:iterator:count", "first DP: %x %x %x\n", + ctx1, ctx2, vp2)); + + /* XXX - free context ? */ + + while (vp2) { + i++; + vp2 = iinfo->get_next_data_point( &ctx1, &ctx2, vp2, iinfo ); + DEBUGMSGTL(("table:iterator:count", "next DP: %x %x %x (%d)\n", + ctx1, ctx2, vp2, i)); + /* XXX - free context ? */ + } + + /* XXX - final free context ? */ + snmp_free_varbind( vp1 ); + return i; +} + + +/* ================================== + * + * Iterator API: Index operations + * + * ================================== */ + + +/** @} */ |