--- /dev/null
+/*
+ * table_iterator.c
+ */
+/** @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.
+ */
+
+#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>
+
+#if HAVE_DMALLOC_H
+#include <dmalloc.h>
+#endif
+
+/*
+ * doesn't work yet, but shouldn't be serialized (for efficiency)
+ */
+#undef NOT_SERIALIZED
+#define TABLE_ITERATOR_LAST_CONTEXT "ti_last_c"
+
+/** 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 =
+ netsnmp_create_handler(TABLE_ITERATOR_NAME,
+ netsnmp_table_iterator_helper_handler);
+
+ if (!me || !iinfo)
+ return NULL;
+
+ me->myvoid = iinfo;
+ return me;
+}
+
+
+/** registers a table after attaching it to a table_iterator helper */
+int
+netsnmp_register_table_iterator(netsnmp_handler_registration *reginfo,
+ netsnmp_iterator_info *iinfo)
+{
+ netsnmp_inject_handler(reginfo,
+ netsnmp_get_table_iterator_handler(iinfo));
+ if (!iinfo)
+ return SNMPERR_GENERR;
+
+#ifndef NOT_SERIALIZED
+ netsnmp_inject_handler(reginfo, netsnmp_get_serialize_handler());
+#endif
+ return netsnmp_register_table(reginfo, iinfo->table_reginfo);
+}
+
+/** extracts the table_iterator specific data from a request */
+NETSNMP_INLINE void *
+netsnmp_extract_iterator_context(netsnmp_request_info *request)
+{
+ return netsnmp_request_get_list_data(request, TABLE_ITERATOR_NAME);
+}
+
+/** 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;
+ oid coloid[MAX_OID_LEN];
+ size_t coloid_len;
+ int ret;
+ static oid myname[MAX_OID_LEN];
+ static int myname_len;
+ int oldmode;
+ netsnmp_iterator_info *iinfo;
+
+ 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;
+ }
+
+ /*
+ * XXXWWW: deal with SET caching
+ */
+
+#ifdef NOT_SERIALIZED
+ while (requests) /* XXX: currently only serialized */
+#endif
+ {
+ /*
+ * XXXWWW: optimize by reversing loops (look through data only once)
+ */
+ netsnmp_variable_list *results = NULL;
+ netsnmp_variable_list *index_search = NULL; /* WWW: move up? */
+ netsnmp_variable_list *free_this_index_search = NULL;
+ netsnmp_table_request_info *table_info =
+ netsnmp_extract_table_info(requests);
+ void *callback_loop_context = NULL;
+ void *callback_data_context = NULL;
+ void *callback_data_keep = NULL;
+
+ if (requests->processed != 0) {
+#ifdef NOT_SERIALIZED
+ continue;
+#else
+ return SNMP_ERR_NOERROR;
+#endif
+ }
+
+ if (reqinfo->mode != MODE_GET && reqinfo->mode != MODE_GETNEXT && reqinfo->mode != MODE_GETBULK && /* XXX */
+ reqinfo->mode != MODE_SET_RESERVE1) {
+ goto skip_processing;
+ }
+
+
+ if (table_info->colnum > tbl_info->max_column) {
+ requests->processed = 1;
+#ifdef NOT_SERIALIZED
+ break;
+#else
+ return SNMP_ERR_NOERROR;
+#endif
+ }
+
+ /*
+ * XXX: if loop through everything, these are never free'd
+ * since iterator returns NULL and thus we forget about
+ * these
+ */
+
+ index_search = snmp_clone_varbind(table_info->indexes);
+ if (!index_search) {
+ /*
+ * hmmm.... invalid table?
+ */
+ snmp_log(LOG_WARNING,
+ "invalid index list or failed malloc for table %s\n",
+ reginfo->handlerName);
+ return SNMP_ERR_NOERROR;
+ }
+
+ free_this_index_search = index_search;
+
+ /*
+ * below our minimum column?
+ */
+ if (table_info->colnum < tbl_info->min_column) {
+ results =
+ (iinfo->get_first_data_point) (&callback_loop_context,
+ &callback_data_context,
+ index_search, iinfo);
+ if (iinfo->free_loop_context) {
+ (iinfo->free_loop_context) (callback_loop_context, iinfo);
+ callback_loop_context = NULL;
+ }
+ goto got_results;
+ }
+
+ /*
+ * XXX: do "only got some indexes"
+ */
+
+ /*
+ * find the next legal result to return
+ */
+ /*
+ * find the first node
+ */
+ index_search =
+ (iinfo->get_first_data_point) (&callback_loop_context,
+ &callback_data_context,
+ index_search, iinfo);
+ /*
+ * table.entry.column node
+ */
+ coloid[reginfo->rootoid_len + 1] = table_info->colnum;
+
+ switch (reqinfo->mode) {
+ case MODE_GETNEXT:
+ case MODE_GETBULK: /* XXXWWW */
+ /*
+ * loop through all data and find next one
+ */
+ while (index_search) {
+ /*
+ * compare the node with previous results
+ */
+ if (netsnmp_check_getnext_reply
+ (requests, coloid, coloid_len, index_search,
+ &results)) {
+
+ /*
+ * result is our current choice, so keep a pointer to
+ * the data that the lower handler wants us to
+ * remember (possibly freeing the last known "good"
+ * result data pointer)
+ */
+ if (callback_data_keep && iinfo->free_data_context) {
+ (iinfo->free_data_context) (callback_data_keep,
+ iinfo);
+ callback_data_keep = NULL;
+ }
+ if (iinfo->make_data_context && !callback_data_context) {
+ callback_data_context =
+ (iinfo->
+ make_data_context) (callback_loop_context,
+ iinfo);
+
+ }
+ callback_data_keep = callback_data_context;
+ callback_data_context = NULL;
+ } else {
+ if (callback_data_context && iinfo->free_data_context)
+ (iinfo->free_data_context) (callback_data_context,
+ iinfo);
+ callback_data_context = NULL;
+ }
+
+ /*
+ * get the next node in the data chain
+ */
+ index_search =
+ (iinfo->get_next_data_point) (&callback_loop_context,
+ &callback_data_context,
+ index_search, iinfo);
+
+ if (!index_search && !results &&
+ tbl_info->max_column > table_info->colnum) {
+ /*
+ * restart loop. XXX: Should cache this better
+ */
+ table_info->colnum++;
+ coloid[reginfo->rootoid_len + 1] = table_info->colnum;
+ if (free_this_index_search != NULL)
+ snmp_free_varbind(free_this_index_search);
+ index_search = snmp_clone_varbind(table_info->indexes);
+ free_this_index_search = index_search;
+
+ if (callback_loop_context &&
+ iinfo->free_loop_context_at_end) {
+ (iinfo->free_loop_context_at_end)(callback_loop_context,
+ iinfo);
+ callback_loop_context = NULL;
+ }
+ if (iinfo->free_loop_context && callback_loop_context) {
+ (iinfo->free_loop_context) (callback_loop_context,
+ iinfo);
+ callback_loop_context = NULL;
+ }
+ if (callback_data_context && iinfo->free_data_context) {
+ (iinfo->free_data_context) (callback_data_context,
+ iinfo);
+ callback_data_context = NULL;
+ }
+
+ index_search =
+ (iinfo->
+ get_first_data_point) (&callback_loop_context,
+ &callback_data_context,
+ index_search, iinfo);
+ }
+ }
+
+ break;
+
+ case MODE_GET:
+ case MODE_SET_RESERVE1:
+ /*
+ * loop through all data till exact results are found
+ */
+
+ while (index_search) {
+ build_oid_noalloc(myname, MAX_OID_LEN, &myname_len,
+ coloid, coloid_len, index_search);
+ if (snmp_oid_compare(myname, myname_len,
+ requests->requestvb->name,
+ requests->requestvb->name_length) ==
+ 0) {
+ /*
+ * found the exact match, so we're done
+ */
+ if (iinfo->make_data_context && !callback_data_context) {
+ callback_data_context =
+ (iinfo->
+ make_data_context) (callback_loop_context,
+ iinfo);
+
+ }
+ callback_data_keep = callback_data_context;
+ callback_data_context = NULL;
+ results = snmp_clone_varbind(index_search);
+ snmp_set_var_objid(results, myname, myname_len);
+ goto got_results;
+ } else {
+ /*
+ * free not-needed data context
+ */
+ if (callback_data_context && iinfo->free_data_context) {
+ (iinfo->free_data_context) (callback_data_context,
+ iinfo);
+ callback_data_context = NULL;
+ }
+
+ }
+
+ /*
+ * get the next node in the data chain
+ */
+ index_search =
+ (iinfo->get_next_data_point) (&callback_loop_context,
+ &callback_data_context,
+ index_search, iinfo);
+ }
+ break;
+
+ default:
+ /*
+ * the rest of the set states have been dealt with already
+ */
+ goto got_results;
+ }
+
+ /*
+ * XXX: free index_search?
+ */
+ if (callback_loop_context && iinfo->free_loop_context) {
+ (iinfo->free_loop_context) (callback_loop_context, iinfo);
+ callback_loop_context = NULL;
+ }
+
+ got_results: /* not milk */
+
+ /*
+ * This free_data_context call is required in the event that your
+ * get_next_data_point method allocates new memory, even during the
+ * calls where it eventually returns a NULL
+ */
+ if (callback_data_context && iinfo->free_data_context) {
+ (iinfo->free_data_context) (callback_data_context,
+ iinfo);
+ callback_data_context = NULL;
+ }
+
+ if (!results && !MODE_IS_SET(reqinfo->mode)) {
+ /*
+ * no results found.
+ */
+ /*
+ * XXX: check for at least one entry at the very top
+ */
+#ifdef NOT_SERIALIZED
+ break;
+#else
+ if (callback_loop_context && iinfo->free_loop_context_at_end) {
+ (iinfo->free_loop_context_at_end) (callback_loop_context,
+ iinfo);
+ callback_loop_context = NULL;
+ }
+ if (free_this_index_search != NULL) {
+ snmp_free_varbind(free_this_index_search);
+ }
+ return SNMP_ERR_NOERROR;
+#endif
+ }
+
+ skip_processing:
+ /*
+ * OK, here results should be a pointer to the data that we
+ * actually need to GET
+ */
+ oldmode = reqinfo->mode;
+ if (reqinfo->mode == MODE_GETNEXT || reqinfo->mode == MODE_GETBULK) { /* XXX */
+ snmp_set_var_objid(requests->requestvb, results->name,
+ results->name_length);
+ reqinfo->mode = MODE_GET;
+ }
+ if (reqinfo->mode == MODE_GET || reqinfo->mode == MODE_GETNEXT || reqinfo->mode == MODE_GETBULK || /* XXX */
+ reqinfo->mode == MODE_SET_RESERVE1) {
+ /*
+ * first (or only) pass stuff
+ */
+ /*
+ * let set requsets use previously constructed data
+ */
+ snmp_free_varbind(results);
+ if (callback_data_keep)
+ netsnmp_request_add_list_data(requests,
+ netsnmp_create_data_list
+ (TABLE_ITERATOR_NAME,
+ callback_data_keep, NULL));
+ netsnmp_request_add_list_data(requests,
+ netsnmp_create_data_list
+ (TABLE_ITERATOR_LAST_CONTEXT,
+ callback_loop_context, NULL));
+ }
+
+ DEBUGMSGTL(("table_iterator", "doing mode: %s\n",
+ se_find_label_in_slist("agent_mode", oldmode)));
+ ret =
+ netsnmp_call_next_handler(handler, reginfo, reqinfo, requests);
+ if (oldmode == MODE_GETNEXT || oldmode == MODE_GETBULK) { /* XXX */
+ if (requests->requestvb->type == ASN_NULL ||
+ requests->requestvb->type == SNMP_NOSUCHINSTANCE) {
+ /*
+ * get next skipped this value for this column, we
+ * need to keep searching forward
+ */
+ requests->requestvb->type = ASN_PRIV_RETRY;
+ }
+ reqinfo->mode = oldmode;
+ }
+
+ callback_data_keep =
+ netsnmp_request_get_list_data(requests, TABLE_ITERATOR_NAME);
+ callback_loop_context =
+ netsnmp_request_get_list_data(requests,
+ TABLE_ITERATOR_LAST_CONTEXT);
+
+ /*
+ * This has to be done to prevent a memory leak. Notice that on
+ * SET_RESERVE1 we're assigning something to
+ * 'free_this_index_search' at the beginning of this handler (right
+ * above the line that says 'below our minimum column?'),
+ * but we're not given a chance to free it below with the other
+ * SET modes, hence our doing it here.
+ */
+ if (reqinfo->mode == MODE_SET_RESERVE1) {
+ if (free_this_index_search) {
+ snmp_free_varbind(free_this_index_search);
+ free_this_index_search = NULL;
+ }
+ }
+ if (reqinfo->mode == MODE_GET || reqinfo->mode == MODE_GETNEXT ||
+ reqinfo->mode == MODE_GETBULK || /* XXX */
+ reqinfo->mode == MODE_SET_FREE ||
+ reqinfo->mode == MODE_SET_UNDO ||
+ reqinfo->mode == MODE_SET_COMMIT) {
+ if (callback_data_keep && iinfo->free_data_context) {
+ (iinfo->free_data_context) (callback_data_keep, iinfo);
+ callback_data_keep = NULL;
+ }
+
+ if (free_this_index_search) {
+ snmp_free_varbind(free_this_index_search);
+ free_this_index_search = NULL;
+ }
+#ifndef NOT_SERIALIZED
+ if (callback_loop_context && iinfo->free_loop_context_at_end) {
+ (iinfo->free_loop_context_at_end) (callback_loop_context,
+ iinfo);
+ callback_loop_context = NULL;
+ }
+#endif
+ }
+#ifdef NOT_SERIALIZED
+ return ret;
+#else
+ requests = requests->next;
+#endif
+ }
+#ifdef NOT_SERIALIZED
+ if (reqinfo->mode == MODE_GET || reqinfo->mode == MODE_GETNEXT || reqinfo->mode == MODE_GETBULK || /* XXX */
+ reqinfo->mode == MODE_SET_FREE ||
+ reqinfo->mode == MODE_SET_UNDO ||
+ reqinfo->mode == MODE_SET_COMMIT) {
+ if (callback_loop_context && iinfo->free_loop_context_at_end) {
+ (iinfo->free_loop_context_at_end) (callback_loop_context,
+ iinfo);
+ callback_loop_context = NULL;
+ }
+ }
+#endif
+ return SNMP_ERR_NOERROR;
+}