summaryrefslogtreecommitdiff
path: root/agent/helpers/table_dataset.c
diff options
context:
space:
mode:
Diffstat (limited to 'agent/helpers/table_dataset.c')
-rw-r--r--agent/helpers/table_dataset.c1316
1 files changed, 1316 insertions, 0 deletions
diff --git a/agent/helpers/table_dataset.c b/agent/helpers/table_dataset.c
new file mode 100644
index 0000000..354c006
--- /dev/null
+++ b/agent/helpers/table_dataset.c
@@ -0,0 +1,1316 @@
+#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>
+
+static netsnmp_data_list *auto_tables;
+
+typedef struct data_set_tables_s {
+ netsnmp_table_data_set *table_set;
+} data_set_tables;
+
+typedef struct data_set_cache_s {
+ void *data;
+ size_t data_len;
+} data_set_cache;
+
+#define STATE_ACTION 1
+#define STATE_COMMIT 2
+#define STATE_UNDO 3
+#define STATE_FREE 4
+
+typedef struct newrow_stash_s {
+ netsnmp_table_row *newrow;
+ int state;
+ int created;
+ int deleted;
+} newrow_stash;
+
+/** @defgroup table_dataset table_dataset
+ * Helps you implement a table with automatted storage.
+ * @ingroup table_data
+ *
+ * This handler helps you implement a table where all the data is
+ * expected to be stored within the agent itself and not in some
+ * external storage location. It handles all MIB requests including
+ * GETs, GETNEXTs and SETs. It's possible to simply create a table
+ * without actually ever defining a handler to be called when SNMP
+ * requests come in. To use the data, you can either attach a
+ * sub-handler that merely uses/manipulates the data further when
+ * requests come in, or you can loop through it externally when it's
+ * actually needed. This handler is most useful in cases where a
+ * table is holding configuration data for something which gets
+ * triggered via another event.
+ *
+ * NOTE NOTE NOTE: This helper isn't complete and is likely to change
+ * somewhat over time. Specifically, the way it stores data
+ * internally may change drastically.
+ *
+ * @{
+ */
+
+void
+netsnmp_init_table_dataset(void) {
+#ifndef NETSNMP_DISABLE_MIB_LOADING
+ register_app_config_handler("table",
+ netsnmp_config_parse_table_set, NULL,
+ "tableoid");
+#endif /* NETSNMP_DISABLE_MIB_LOADING */
+ register_app_config_handler("add_row", netsnmp_config_parse_add_row,
+ NULL, "table_name indexes... values...");
+}
+
+/* ==================================
+ *
+ * Data Set API: Table maintenance
+ *
+ * ================================== */
+
+/** Create a netsnmp_table_data_set structure given a table_data definition */
+netsnmp_table_data_set *
+netsnmp_create_table_data_set(const char *table_name)
+{
+ netsnmp_table_data_set *table_set =
+ SNMP_MALLOC_TYPEDEF(netsnmp_table_data_set);
+ if (!table_set)
+ return NULL;
+ table_set->table = netsnmp_create_table_data(table_name);
+ return table_set;
+}
+
+/** clones a dataset row, including all data. */
+netsnmp_table_row *
+netsnmp_table_data_set_clone_row(netsnmp_table_row *row)
+{
+ netsnmp_table_data_set_storage *data, **newrowdata;
+ netsnmp_table_row *newrow;
+
+ if (!row)
+ return NULL;
+
+ newrow = netsnmp_table_data_clone_row(row);
+ if (!newrow)
+ return NULL;
+
+ data = (netsnmp_table_data_set_storage *) row->data;
+
+ if (data) {
+ for (newrowdata =
+ (netsnmp_table_data_set_storage **) &(newrow->data); data;
+ newrowdata = &((*newrowdata)->next), data = data->next) {
+
+ memdup((u_char **) newrowdata, (u_char *) data,
+ sizeof(netsnmp_table_data_set_storage));
+ if (!*newrowdata) {
+ netsnmp_table_dataset_delete_row(newrow);
+ return NULL;
+ }
+
+ if (data->data.voidp) {
+ memdup((u_char **) & ((*newrowdata)->data.voidp),
+ (u_char *) data->data.voidp, data->data_len);
+ if (!(*newrowdata)->data.voidp) {
+ netsnmp_table_dataset_delete_row(newrow);
+ return NULL;
+ }
+ }
+ }
+ }
+ return newrow;
+}
+
+/** deletes a single dataset table data.
+ * returns the (possibly still good) next pointer of the deleted data object.
+ */
+NETSNMP_INLINE netsnmp_table_data_set_storage *
+netsnmp_table_dataset_delete_data(netsnmp_table_data_set_storage *data)
+{
+ netsnmp_table_data_set_storage *nextPtr = NULL;
+ if (data) {
+ nextPtr = data->next;
+ SNMP_FREE(data->data.voidp);
+ }
+ SNMP_FREE(data);
+ return nextPtr;
+}
+
+/** deletes all the data from this node and beyond in the linked list */
+NETSNMP_INLINE void
+netsnmp_table_dataset_delete_all_data(netsnmp_table_data_set_storage *data)
+{
+
+ while (data) {
+ data = netsnmp_table_dataset_delete_data(data);
+ }
+}
+
+/** deletes all the data from this node and beyond in the linked list */
+NETSNMP_INLINE void
+netsnmp_table_dataset_delete_row(netsnmp_table_row *row)
+{
+ netsnmp_table_data_set_storage *data;
+
+ if (!row)
+ return;
+
+ data = netsnmp_table_data_delete_row(row);
+ netsnmp_table_dataset_delete_all_data(data);
+}
+
+/** adds a new row to a dataset table */
+NETSNMP_INLINE void
+netsnmp_table_dataset_add_row(netsnmp_table_data_set *table,
+ netsnmp_table_row *row)
+{
+ if (!table)
+ return;
+ netsnmp_table_data_add_row(table->table, row);
+}
+
+/** adds a new row to a dataset table */
+NETSNMP_INLINE void
+netsnmp_table_dataset_replace_row(netsnmp_table_data_set *table,
+ netsnmp_table_row *origrow,
+ netsnmp_table_row *newrow)
+{
+ if (!table)
+ return;
+ netsnmp_table_data_replace_row(table->table, origrow, newrow);
+}
+
+/** removes a row from the table, but doesn't delete/free the column values */
+NETSNMP_INLINE void
+netsnmp_table_dataset_remove_row(netsnmp_table_data_set *table,
+ netsnmp_table_row *row)
+{
+ if (!table)
+ return;
+
+ netsnmp_table_data_remove_and_delete_row(table->table, row);
+}
+
+/** removes a row from the table and then deletes it (and all its data) */
+NETSNMP_INLINE void
+netsnmp_table_dataset_remove_and_delete_row(netsnmp_table_data_set *table,
+ netsnmp_table_row *row)
+{
+ netsnmp_table_data_set_storage *data;
+
+ if (!table)
+ return;
+
+ data = (netsnmp_table_data_set_storage *)
+ netsnmp_table_data_remove_and_delete_row(table->table, row);
+
+ netsnmp_table_dataset_delete_all_data(data);
+}
+
+/* ==================================
+ *
+ * Data Set API: Default row operations
+ *
+ * ================================== */
+
+/** creates a new row from an existing defined default set */
+netsnmp_table_row *
+netsnmp_table_data_set_create_row_from_defaults
+ (netsnmp_table_data_set_storage *defrow)
+{
+ netsnmp_table_row *row;
+ row = netsnmp_create_table_data_row();
+ if (!row)
+ return NULL;
+ for (; defrow; defrow = defrow->next) {
+ netsnmp_set_row_column(row, defrow->column, defrow->type,
+ defrow->data.voidp, defrow->data_len);
+ if (defrow->writable)
+ netsnmp_mark_row_column_writable(row, defrow->column, 1);
+
+ }
+ return row;
+}
+
+/** adds a new default row to a table_set.
+ * Arguments should be the table_set, column number, variable type and
+ * finally a 1 if it is allowed to be writable, or a 0 if not. If the
+ * default_value field is not NULL, it will be used to populate new
+ * valuse in that column fro newly created rows. It is copied into the
+ * storage template (free your calling argument).
+ *
+ * returns SNMPERR_SUCCESS or SNMPERR_FAILURE
+ */
+int
+netsnmp_table_set_add_default_row(netsnmp_table_data_set *table_set,
+ unsigned int column,
+ int type, int writable,
+ void *default_value,
+ size_t default_value_len)
+{
+ netsnmp_table_data_set_storage *new_col, *ptr, *pptr;
+
+ if (!table_set)
+ return SNMPERR_GENERR;
+
+ /*
+ * double check
+ */
+ new_col =
+ netsnmp_table_data_set_find_column(table_set->default_row, column);
+ if (new_col != NULL) {
+ if (new_col->type == type && new_col->writable == writable)
+ return SNMPERR_SUCCESS;
+ return SNMPERR_GENERR;
+ }
+
+ new_col = SNMP_MALLOC_TYPEDEF(netsnmp_table_data_set_storage);
+ new_col->type = type;
+ new_col->writable = writable;
+ new_col->column = column;
+ if (default_value) {
+ memdup((u_char **) & (new_col->data.voidp),
+ (u_char *) default_value, default_value_len);
+ new_col->data_len = default_value_len;
+ }
+ if (table_set->default_row == NULL)
+ table_set->default_row = new_col;
+ else {
+ /* sort in order just because (needed for add_row support) */
+ for (ptr = table_set->default_row, pptr = NULL;
+ ptr;
+ pptr = ptr, ptr = ptr->next) {
+ if (ptr->column > column) {
+ new_col->next = ptr;
+ if (pptr)
+ pptr->next = new_col;
+ else
+ table_set->default_row = new_col;
+ return SNMPERR_SUCCESS;
+ }
+ }
+ if (pptr)
+ pptr->next = new_col;
+ else
+ snmp_log(LOG_ERR,"Shouldn't have gotten here: table_dataset/add_row");
+ }
+ return SNMPERR_SUCCESS;
+}
+
+/** adds multiple data column definitions to each row. Functionally,
+ * this is a wrapper around calling netsnmp_table_set_add_default_row
+ * repeatedly for you.
+ */
+void
+#if HAVE_STDARG_H
+netsnmp_table_set_multi_add_default_row(netsnmp_table_data_set *tset, ...)
+#else
+netsnmp_table_set_multi_add_default_row(va_dcl
+ )
+ va_dcl
+#endif
+{
+ va_list debugargs;
+ unsigned int column;
+ int type, writable;
+ void *data;
+ size_t data_len;
+
+#if HAVE_STDARG_H
+ va_start(debugargs, tset);
+#else
+ netsnmp_table_data_set *tset;
+
+ va_start(debugargs);
+ tset = va_arg(debugargs, netsnmp_table_data_set *);
+#endif
+
+ while ((column = va_arg(debugargs, unsigned int)) != 0) {
+ type = va_arg(debugargs, int);
+ writable = va_arg(debugargs, int);
+ data = va_arg(debugargs, void *);
+ data_len = va_arg(debugargs, size_t);
+ netsnmp_table_set_add_default_row(tset, column, type, writable,
+ data, data_len);
+ }
+
+ va_end(debugargs);
+}
+
+
+/* ==================================
+ *
+ * Data Set API: MIB maintenance
+ *
+ * ================================== */
+
+/** Given a netsnmp_table_data_set definition, create a handler for it */
+netsnmp_mib_handler *
+netsnmp_get_table_data_set_handler(netsnmp_table_data_set *data_set)
+{
+ netsnmp_mib_handler *ret = NULL;
+
+ if (!data_set) {
+ snmp_log(LOG_INFO,
+ "netsnmp_get_table_data_set_handler(NULL) called\n");
+ return NULL;
+ }
+
+ ret =
+ netsnmp_create_handler(TABLE_DATA_SET_NAME,
+ netsnmp_table_data_set_helper_handler);
+ if (ret) {
+ ret->flags |= MIB_HANDLER_AUTO_NEXT;
+ ret->myvoid = (void *) data_set;
+ }
+ return ret;
+}
+
+/** register a given data_set at a given oid (specified in the
+ netsnmp_handler_registration pointer). The
+ reginfo->handler->access_method *may* be null if the call doesn't
+ ever want to be called for SNMP operations.
+*/
+int
+netsnmp_register_table_data_set(netsnmp_handler_registration *reginfo,
+ netsnmp_table_data_set *data_set,
+ netsnmp_table_registration_info *table_info)
+{
+ if (NULL == table_info) {
+ /*
+ * allocate the table if one wasn't allocated
+ */
+ table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
+ }
+
+ if (NULL == table_info->indexes && data_set->table->indexes_template) {
+ /*
+ * copy the indexes in
+ */
+ table_info->indexes =
+ snmp_clone_varbind(data_set->table->indexes_template);
+ }
+
+ if ((!table_info->min_column || !table_info->max_column) &&
+ (data_set->default_row)) {
+ /*
+ * determine min/max columns
+ */
+ unsigned int mincol = 0xffffffff, maxcol = 0;
+ netsnmp_table_data_set_storage *row;
+
+ for (row = data_set->default_row; row; row = row->next) {
+ mincol = SNMP_MIN(mincol, row->column);
+ maxcol = SNMP_MAX(maxcol, row->column);
+ }
+ if (!table_info->min_column)
+ table_info->min_column = mincol;
+ if (!table_info->max_column)
+ table_info->max_column = maxcol;
+ }
+
+ netsnmp_inject_handler(reginfo,
+ netsnmp_get_table_data_set_handler(data_set));
+ return netsnmp_register_table_data(reginfo, data_set->table,
+ table_info);
+}
+
+newrow_stash *
+netsnmp_table_data_set_create_newrowstash
+ (netsnmp_table_data_set *datatable,
+ netsnmp_table_request_info *table_info)
+{
+ newrow_stash *newrowstash = NULL;
+ netsnmp_table_row *newrow = NULL;
+
+ newrowstash = SNMP_MALLOC_TYPEDEF(newrow_stash);
+ newrowstash->created = 1;
+ newrow = netsnmp_table_data_set_create_row_from_defaults
+ (datatable->default_row);
+ newrow->indexes = snmp_clone_varbind(table_info->indexes);
+ newrowstash->newrow = newrow;
+
+ return newrowstash;
+}
+
+/* implements the table data helper. This is the routine that takes
+ * care of all SNMP requests coming into the table. */
+int
+netsnmp_table_data_set_helper_handler(netsnmp_mib_handler *handler,
+ netsnmp_handler_registration
+ *reginfo,
+ netsnmp_agent_request_info *reqinfo,
+ netsnmp_request_info *requests)
+{
+ netsnmp_table_data_set_storage *data = NULL;
+ newrow_stash *newrowstash = NULL;
+ netsnmp_table_row *row, *newrow = NULL;
+ netsnmp_table_request_info *table_info;
+ netsnmp_request_info *request;
+ oid *suffix;
+ size_t suffix_len;
+ netsnmp_oid_stash_node **stashp = NULL;
+
+ if (!handler)
+ return SNMPERR_GENERR;
+
+ DEBUGMSGTL(("netsnmp_table_data_set", "handler starting\n"));
+ for (request = requests; request; request = request->next) {
+ netsnmp_table_data_set *datatable =
+ (netsnmp_table_data_set *) handler->myvoid;
+ if (request->processed)
+ continue;
+
+ /*
+ * extract our stored data and table info
+ */
+ row = netsnmp_extract_table_row(request);
+ table_info = netsnmp_extract_table_info(request);
+ suffix = requests->requestvb->name + reginfo->rootoid_len + 2;
+ suffix_len = requests->requestvb->name_length -
+ (reginfo->rootoid_len + 2);
+
+ if (MODE_IS_SET(reqinfo->mode)) {
+
+ char buf[256]; /* is this reasonable size?? */
+ int rc;
+ size_t len;
+
+ /*
+ * use a cached copy of the row for modification
+ */
+
+ /*
+ * cache location: may have been created already by other
+ * SET requests in the same master request.
+ */
+ rc = snprintf(buf, sizeof(buf), "dataset_row_stash:%s:",
+ datatable->table->name);
+ if ((-1 == rc) || (rc >= sizeof(buf))) {
+ snmp_log(LOG_ERR,"%s handler name too long\n",
+ datatable->table->name);
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_GENERR);
+ continue;
+ }
+ len = sizeof(buf) - rc;
+ rc = snprint_objid(&buf[rc], len, table_info->index_oid,
+ table_info->index_oid_len);
+ if (-1 == rc) {
+ snmp_log(LOG_ERR,"%s oid or name too long\n",
+ datatable->table->name);
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_GENERR);
+ continue;
+ }
+ stashp = (netsnmp_oid_stash_node **)
+ netsnmp_table_get_or_create_row_stash(reqinfo, buf);
+
+ newrowstash
+ = netsnmp_oid_stash_get_data(*stashp, suffix, suffix_len);
+
+ if (!newrowstash) {
+ if (!row) {
+ if (datatable->allow_creation) {
+ /*
+ * entirely new row. Create the row from the template
+ */
+ newrowstash =
+ netsnmp_table_data_set_create_newrowstash(
+ datatable, table_info);
+ newrow = newrowstash->newrow;
+ } else if (datatable->rowstatus_column == 0) {
+ /*
+ * A RowStatus object may be used to control the
+ * creation of a new row. But if this object
+ * isn't declared (and the table isn't marked as
+ * 'auto-create'), then we can't create a new row.
+ */
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_NOCREATION);
+ continue;
+ }
+ } else {
+ /*
+ * existing row that needs to be modified
+ */
+ newrowstash = SNMP_MALLOC_TYPEDEF(newrow_stash);
+ newrow = netsnmp_table_data_set_clone_row(row);
+ newrowstash->newrow = newrow;
+ }
+ netsnmp_oid_stash_add_data(stashp, suffix, suffix_len,
+ newrowstash);
+ } else {
+ newrow = newrowstash->newrow;
+ }
+ /*
+ * all future SET data modification operations use this
+ * temp pointer
+ */
+ if (reqinfo->mode == MODE_SET_RESERVE1 ||
+ reqinfo->mode == MODE_SET_RESERVE2)
+ row = newrow;
+ }
+
+ if (row)
+ data = (netsnmp_table_data_set_storage *) row->data;
+
+ if (!row || !table_info || !data) {
+ if (!MODE_IS_SET(reqinfo->mode) || !table_info) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_NOSUCHINSTANCE);
+ continue;
+ }
+ }
+
+ data =
+ netsnmp_table_data_set_find_column(data, table_info->colnum);
+
+ switch (reqinfo->mode) {
+ case MODE_GET:
+ case MODE_GETNEXT:
+ case MODE_GETBULK: /* XXXWWW */
+ if (data && data->data.voidp)
+ netsnmp_table_data_build_result(reginfo, reqinfo, request,
+ row,
+ table_info->colnum,
+ data->type,
+ data->data.voidp,
+ data->data_len);
+ break;
+
+ case MODE_SET_RESERVE1:
+ if (data) {
+ /*
+ * Can we modify the existing row?
+ */
+ if (!data->writable) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_NOTWRITABLE);
+ } else if (request->requestvb->type != data->type) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_WRONGTYPE);
+ }
+ } else if (datatable->rowstatus_column == table_info->colnum) {
+ /*
+ * Otherwise, this is where we create a new row using
+ * the RowStatus object (essentially duplicating the
+ * steps followed earlier in the 'allow_creation' case)
+ */
+ switch (*(request->requestvb->val.integer)) {
+ case RS_CREATEANDGO:
+ case RS_CREATEANDWAIT:
+ newrowstash =
+ netsnmp_table_data_set_create_newrowstash(
+ datatable, table_info);
+ newrow = newrowstash->newrow;
+ row = newrow;
+ netsnmp_oid_stash_add_data(stashp, suffix, suffix_len,
+ newrowstash);
+ }
+ }
+ break;
+
+ case MODE_SET_RESERVE2:
+ /*
+ * If the agent receives a SET request for an object in a non-existant
+ * row, then the RESERVE1 pass will create the row automatically.
+ *
+ * But since the row doesn't exist at that point, the test for whether
+ * the object is writable or not will be skipped. So we need to check
+ * for this possibility again here.
+ *
+ * Similarly, if row creation is under the control of the RowStatus
+ * object (i.e. allow_creation == 0), but this particular request
+ * doesn't include such an object, then the row won't have been created,
+ * and the writable check will also have been skipped. Again - check here.
+ */
+ if (data && data->writable == 0) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_NOTWRITABLE);
+ continue;
+ }
+ if (datatable->rowstatus_column == table_info->colnum) {
+ switch (*(request->requestvb->val.integer)) {
+ case RS_ACTIVE:
+ case RS_NOTINSERVICE:
+ /*
+ * Can only operate on pre-existing rows.
+ */
+ if (!newrowstash || newrowstash->created) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_INCONSISTENTVALUE);
+ continue;
+ }
+ break;
+
+ case RS_CREATEANDGO:
+ case RS_CREATEANDWAIT:
+ /*
+ * Can only operate on newly created rows.
+ */
+ if (!(newrowstash && newrowstash->created)) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_INCONSISTENTVALUE);
+ continue;
+ }
+ break;
+
+ case RS_DESTROY:
+ /*
+ * Can operate on new or pre-existing rows.
+ */
+ break;
+
+ case RS_NOTREADY:
+ default:
+ /*
+ * Not a valid value to Set
+ */
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_WRONGVALUE);
+ continue;
+ }
+ }
+ if (!data ) {
+ netsnmp_set_request_error(reqinfo, request,
+ SNMP_ERR_NOCREATION);
+ continue;
+ }
+
+ /*
+ * modify row and set new value
+ */
+ SNMP_FREE(data->data.string);
+ data->data.string =
+ netsnmp_strdup_and_null(request->requestvb->val.string,
+ request->requestvb->val_len);
+ if (!data->data.string) {
+ netsnmp_set_request_error(reqinfo, requests,
+ SNMP_ERR_RESOURCEUNAVAILABLE);
+ }
+ data->data_len = request->requestvb->val_len;
+
+ if (datatable->rowstatus_column == table_info->colnum) {
+ switch (*(request->requestvb->val.integer)) {
+ case RS_CREATEANDGO:
+ /*
+ * XXX: check legality
+ */
+ *(data->data.integer) = RS_ACTIVE;
+ break;
+
+ case RS_CREATEANDWAIT:
+ /*
+ * XXX: check legality
+ */
+ *(data->data.integer) = RS_NOTINSERVICE;
+ break;
+
+ case RS_DESTROY:
+ newrowstash->deleted = 1;
+ break;
+ }
+ }
+ break;
+
+ case MODE_SET_ACTION:
+
+ /*
+ * Install the new row into the stored table.
+ * Do this only *once* per row ....
+ */
+ if (newrowstash->state != STATE_ACTION) {
+ newrowstash->state = STATE_ACTION;
+ if (newrowstash->created) {
+ netsnmp_table_dataset_add_row(datatable, newrow);
+ } else {
+ netsnmp_table_dataset_replace_row(datatable,
+ row, newrow);
+ }
+ }
+ /*
+ * ... but every (relevant) varbind in the request will
+ * need to know about this new row, so update the
+ * per-request row information regardless
+ */
+ if (newrowstash->created) {
+ netsnmp_request_add_list_data(request,
+ netsnmp_create_data_list(TABLE_DATA_NAME,
+ newrow, NULL));
+ }
+ break;
+
+ case MODE_SET_UNDO:
+ /*
+ * extract the new row, replace with the old or delete
+ */
+ if (newrowstash->state != STATE_UNDO) {
+ newrowstash->state = STATE_UNDO;
+ if (newrowstash->created) {
+ netsnmp_table_dataset_remove_and_delete_row(datatable, newrow);
+ } else {
+ netsnmp_table_dataset_replace_row(datatable,
+ newrow, row);
+ netsnmp_table_dataset_delete_row(newrow);
+ }
+ newrow = NULL;
+ }
+ break;
+
+ case MODE_SET_COMMIT:
+ if (newrowstash->state != STATE_COMMIT) {
+ newrowstash->state = STATE_COMMIT;
+ if (!newrowstash->created) {
+ netsnmp_request_info *req;
+ netsnmp_table_dataset_delete_row(row);
+
+ /* Walk the request list to update the reference to the old row w/ th new one */
+ for (req = requests; req; req=req->next) {
+
+ /*
+ * For requests that have the old row values,
+ * so add the newly-created row information.
+ */
+ if ((netsnmp_table_row *) netsnmp_extract_table_row(req) == row) {
+ netsnmp_request_remove_list_data(req, TABLE_DATA_ROW);
+ netsnmp_request_add_list_data(req,
+ netsnmp_create_data_list(TABLE_DATA_ROW, newrow, NULL));
+ }
+ }
+
+ row = NULL;
+ }
+ if (newrowstash->deleted) {
+ netsnmp_table_dataset_remove_and_delete_row(datatable, newrow);
+ newrow = NULL;
+ }
+ }
+ break;
+
+ case MODE_SET_FREE:
+ if (newrowstash && newrowstash->state != STATE_FREE) {
+ newrowstash->state = STATE_FREE;
+ netsnmp_table_dataset_delete_row(newrow);
+ newrow = NULL;
+ }
+ break;
+ }
+ }
+
+ /* next handler called automatically - 'AUTO_NEXT' */
+ return SNMP_ERR_NOERROR;
+}
+
+/**
+ * extracts a netsnmp_table_data_set pointer from a given request
+ */
+NETSNMP_INLINE netsnmp_table_data_set *
+netsnmp_extract_table_data_set(netsnmp_request_info *request)
+{
+ return (netsnmp_table_data_set *)
+ netsnmp_request_get_list_data(request, TABLE_DATA_SET_NAME);
+}
+
+/**
+ * extracts a netsnmp_table_data_set pointer from a given request
+ */
+netsnmp_table_data_set_storage *
+netsnmp_extract_table_data_set_column(netsnmp_request_info *request,
+ unsigned int column)
+{
+ netsnmp_table_data_set_storage *data =
+ netsnmp_extract_table_row_data( request );
+ if (data) {
+ data = netsnmp_table_data_set_find_column(data, column);
+ }
+ return data;
+}
+
+
+/* ==================================
+ *
+ * Data Set API: Config-based operation
+ *
+ * ================================== */
+
+/** registers a table_dataset so that the "add_row" snmpd.conf token
+ * can be used to add data to this table. If registration_name is
+ * NULL then the name used when the table was created will be used
+ * instead.
+ *
+ * @todo create a properly free'ing registeration pointer for the
+ * datalist, and get the datalist freed at shutdown.
+ */
+void
+netsnmp_register_auto_data_table(netsnmp_table_data_set *table_set,
+ char *registration_name)
+{
+ data_set_tables *tables;
+ tables = SNMP_MALLOC_TYPEDEF(data_set_tables);
+ if (!tables)
+ return;
+ tables->table_set = table_set;
+ if (!registration_name) {
+ registration_name = table_set->table->name;
+ }
+ netsnmp_add_list_data(&auto_tables, netsnmp_create_data_list(registration_name, tables, NULL)); /* XXX */
+}
+
+#ifndef NETSNMP_DISABLE_MIB_LOADING
+static void
+_table_set_add_indexes(netsnmp_table_data_set *table_set, struct tree *tp)
+{
+ oid name[MAX_OID_LEN];
+ size_t name_length = MAX_OID_LEN;
+ struct index_list *index;
+ struct tree *indexnode;
+ u_char type;
+ int fixed_len = 0;
+
+ /*
+ * loop through indexes and add types
+ */
+ for (index = tp->indexes; index; index = index->next) {
+ if (!snmp_parse_oid(index->ilabel, name, &name_length) ||
+ (NULL ==
+ (indexnode = get_tree(name, name_length, get_tree_head())))) {
+ config_pwarn("can't instatiate table since "
+ "I don't know anything about one index");
+ snmp_log(LOG_WARNING, " index %s not found in tree\n",
+ index->ilabel);
+ return; /* xxx mem leak */
+ }
+
+ type = mib_to_asn_type(indexnode->type);
+ if (type == (u_char) - 1) {
+ config_pwarn("unknown index type");
+ return; /* xxx mem leak */
+ }
+ /*
+ * if implied, mark it as such. also mark fixed length
+ * octet strings as implied (ie no length prefix) as well.
+ * */
+ if ((TYPE_OCTETSTR == indexnode->type) && /* octet str */
+ (NULL != indexnode->ranges) && /* & has range */
+ (NULL == indexnode->ranges->next) && /* but only one */
+ (indexnode->ranges->high == /* & high==low */
+ indexnode->ranges->low)) {
+ type |= ASN_PRIVATE;
+ fixed_len = indexnode->ranges->high;
+ }
+ else if (index->isimplied)
+ type |= ASN_PRIVATE;
+
+ DEBUGMSGTL(("table_set_add_table",
+ "adding default index of type %d\n", type));
+ netsnmp_table_dataset_add_index(table_set, type);
+
+ /*
+ * hack alert: for fixed lenght strings, save the
+ * lenght for use during oid parsing.
+ */
+ if (fixed_len) {
+ /*
+ * find last (just added) index
+ */
+ netsnmp_variable_list *var = table_set->table->indexes_template;
+ while (NULL != var->next_variable)
+ var = var->next_variable;
+ var->val_len = fixed_len;
+ }
+ }
+}
+/** @internal */
+void
+netsnmp_config_parse_table_set(const char *token, char *line)
+{
+ oid table_name[MAX_OID_LEN];
+ size_t table_name_length = MAX_OID_LEN;
+ struct tree *tp;
+ netsnmp_table_data_set *table_set;
+ data_set_tables *tables;
+ unsigned int mincol = 0xffffff, maxcol = 0;
+ char *pos;
+
+ /*
+ * instatiate a fake table based on MIB information
+ */
+ DEBUGMSGTL(("9:table_set_add_table", "processing '%s'\n", line));
+ if (NULL != (pos = strchr(line,' '))) {
+ config_pwarn("ignoring extra tokens on line");
+ snmp_log(LOG_WARNING," ignoring '%s'\n", pos);
+ *pos = '\0';
+ }
+
+ /*
+ * check for duplicate table
+ */
+ tables = (data_set_tables *) netsnmp_get_list_data(auto_tables, line);
+ if (NULL != tables) {
+ config_pwarn("duplicate table definition");
+ return;
+ }
+
+ /*
+ * parse oid and find tree structure
+ */
+ if (!snmp_parse_oid(line, table_name, &table_name_length)) {
+ config_pwarn
+ ("can't instatiate table since I can't parse the table name");
+ return;
+ }
+ if(NULL == (tp = get_tree(table_name, table_name_length,
+ get_tree_head()))) {
+ config_pwarn("can't instatiate table since "
+ "I can't find mib information about it");
+ return;
+ }
+
+ if (NULL == (tp = tp->child_list) || NULL == tp->child_list) {
+ config_pwarn("can't instatiate table since it doesn't appear to be "
+ "a proper table (no children)");
+ return;
+ }
+
+ table_set = netsnmp_create_table_data_set(line);
+
+ /*
+ * check for augments indexes
+ */
+ if (NULL != tp->augments) {
+ oid name[MAX_OID_LEN];
+ size_t name_length = MAX_OID_LEN;
+ struct tree *tp2;
+
+ if (!snmp_parse_oid(tp->augments, name, &name_length)) {
+ config_pwarn("I can't parse the augment tabel name");
+ snmp_log(LOG_WARNING, " can't parse %s\n", tp->augments);
+ SNMP_FREE (table_set);
+ return;
+ }
+ if(NULL == (tp2 = get_tree(name, name_length, get_tree_head()))) {
+ config_pwarn("can't instatiate table since "
+ "I can't find mib information about augment table");
+ snmp_log(LOG_WARNING, " table %s not found in tree\n",
+ tp->augments);
+ SNMP_FREE (table_set);
+ return;
+ }
+ _table_set_add_indexes(table_set, tp2);
+ }
+
+ _table_set_add_indexes(table_set, tp);
+
+ /*
+ * loop through children and add each column info
+ */
+ for (tp = tp->child_list; tp; tp = tp->next_peer) {
+ int canwrite = 0;
+ u_char type;
+ type = mib_to_asn_type(tp->type);
+ if (type == (u_char) - 1) {
+ config_pwarn("unknown column type");
+ SNMP_FREE (table_set);
+ return; /* xxx mem leak */
+ }
+
+ DEBUGMSGTL(("table_set_add_table",
+ "adding column %s(%d) of type %d (access %d)\n",
+ tp->label, tp->subid, type, tp->access));
+
+ switch (tp->access) {
+ case MIB_ACCESS_CREATE:
+ table_set->allow_creation = 1;
+ case MIB_ACCESS_READWRITE:
+ case MIB_ACCESS_WRITEONLY:
+ canwrite = 1;
+ case MIB_ACCESS_READONLY:
+ DEBUGMSGTL(("table_set_add_table",
+ "adding column %d of type %d\n", tp->subid, type));
+ netsnmp_table_set_add_default_row(table_set, tp->subid, type,
+ canwrite, NULL, 0);
+ mincol = SNMP_MIN(mincol, tp->subid);
+ maxcol = SNMP_MAX(maxcol, tp->subid);
+ break;
+
+ case MIB_ACCESS_NOACCESS:
+ case MIB_ACCESS_NOTIFY:
+ break;
+
+ default:
+ config_pwarn("unknown column access type");
+ break;
+ }
+ }
+
+ /*
+ * register the table
+ */
+ netsnmp_register_table_data_set(netsnmp_create_handler_registration
+ (line, NULL, table_name,
+ table_name_length,
+ HANDLER_CAN_RWRITE), table_set, NULL);
+
+ netsnmp_register_auto_data_table(table_set, NULL);
+}
+#endif /* NETSNMP_DISABLE_MIB_LOADING */
+
+/** @internal */
+void
+netsnmp_config_parse_add_row(const char *token, char *line)
+{
+ char buf[SNMP_MAXBUF_MEDIUM];
+ char tname[SNMP_MAXBUF_MEDIUM];
+ size_t buf_size;
+ int rc;
+
+ data_set_tables *tables;
+ netsnmp_variable_list *vb; /* containing only types */
+ netsnmp_table_row *row;
+ netsnmp_table_data_set_storage *dr;
+
+ line = copy_nword(line, tname, SNMP_MAXBUF_MEDIUM);
+
+ tables = (data_set_tables *) netsnmp_get_list_data(auto_tables, tname);
+ if (!tables) {
+ config_pwarn("Unknown table trying to add a row");
+ return;
+ }
+
+ /*
+ * do the indexes first
+ */
+ row = netsnmp_create_table_data_row();
+
+ for (vb = tables->table_set->table->indexes_template; vb;
+ vb = vb->next_variable) {
+ if (!line) {
+ config_pwarn("missing an index value");
+ SNMP_FREE (row);
+ return;
+ }
+
+ DEBUGMSGTL(("table_set_add_row", "adding index of type %d\n",
+ vb->type));
+ buf_size = SNMP_MAXBUF_MEDIUM;
+ line = read_config_read_memory(vb->type, line, buf, &buf_size);
+ netsnmp_table_row_add_index(row, vb->type, buf, buf_size);
+ }
+
+ /*
+ * then do the data
+ */
+ for (dr = tables->table_set->default_row; dr; dr = dr->next) {
+ if (!line) {
+ config_pwarn("missing a data value. "
+ "All columns must be specified.");
+ snmp_log(LOG_WARNING," can't find value for column %d\n",
+ dr->column - 1);
+ SNMP_FREE (row);
+ return;
+ }
+
+ buf_size = SNMP_MAXBUF_MEDIUM;
+ line = read_config_read_memory(dr->type, line, buf, &buf_size);
+ DEBUGMSGTL(("table_set_add_row",
+ "adding data at column %d of type %d\n", dr->column,
+ dr->type));
+ netsnmp_set_row_column(row, dr->column, dr->type, buf, buf_size);
+ if (dr->writable)
+ netsnmp_mark_row_column_writable(row, dr->column, 1); /* make writable */
+ }
+ rc = netsnmp_table_data_add_row(tables->table_set->table, row);
+ if (SNMPERR_SUCCESS != rc) {
+ config_pwarn("error adding table row");
+ }
+ if (NULL != line) {
+ config_pwarn("extra data value. Too many columns specified.");
+ snmp_log(LOG_WARNING," extra data '%s'\n", line);
+ }
+}
+
+
+/* ==================================
+ *
+ * Data Set API: Row operations
+ *
+ * ================================== */
+
+/** returns the first row in the table */
+netsnmp_table_row *
+netsnmp_table_data_set_get_first_row(netsnmp_table_data_set *table)
+{
+ return netsnmp_table_data_get_first_row(table->table);
+}
+
+/** returns the next row in the table */
+netsnmp_table_row *
+netsnmp_table_data_set_get_next_row(netsnmp_table_data_set *table,
+ netsnmp_table_row *row)
+{
+ return netsnmp_table_data_get_next_row(table->table, row);
+}
+
+int
+netsnmp_table_set_num_rows(netsnmp_table_data_set *table)
+{
+ if (!table)
+ return 0;
+ return netsnmp_table_data_num_rows(table->table);
+}
+
+/* ==================================
+ *
+ * Data Set API: Column operations
+ *
+ * ================================== */
+
+/** Finds a column within a given storage set, given the pointer to
+ the start of the storage set list.
+*/
+netsnmp_table_data_set_storage *
+netsnmp_table_data_set_find_column(netsnmp_table_data_set_storage *start,
+ unsigned int column)
+{
+ while (start && start->column != column)
+ start = start->next;
+ return start;
+}
+
+/**
+ * marks a given column in a row as writable or not.
+ */
+int
+netsnmp_mark_row_column_writable(netsnmp_table_row *row, int column,
+ int writable)
+{
+ netsnmp_table_data_set_storage *data;
+
+ if (!row)
+ return SNMPERR_GENERR;
+
+ data = (netsnmp_table_data_set_storage *) row->data;
+ data = netsnmp_table_data_set_find_column(data, column);
+
+ if (!data) {
+ /*
+ * create it
+ */
+ data = SNMP_MALLOC_TYPEDEF(netsnmp_table_data_set_storage);
+ if (!data) {
+ snmp_log(LOG_CRIT, "no memory in netsnmp_set_row_column");
+ return SNMPERR_MALLOC;
+ }
+ data->column = column;
+ data->writable = writable;
+ data->next = row->data;
+ row->data = data;
+ } else {
+ data->writable = writable;
+ }
+ return SNMPERR_SUCCESS;
+}
+
+/**
+ * sets a given column in a row with data given a type, value, and
+ * length. Data is memdup'ed by the function.
+ */
+int
+netsnmp_set_row_column(netsnmp_table_row *row, unsigned int column,
+ int type, const char *value, size_t value_len)
+{
+ netsnmp_table_data_set_storage *data;
+
+ if (!row)
+ return SNMPERR_GENERR;
+
+ data = (netsnmp_table_data_set_storage *) row->data;
+ data = netsnmp_table_data_set_find_column(data, column);
+
+ if (!data) {
+ /*
+ * create it
+ */
+ data = SNMP_MALLOC_TYPEDEF(netsnmp_table_data_set_storage);
+ if (!data) {
+ snmp_log(LOG_CRIT, "no memory in netsnmp_set_row_column");
+ return SNMPERR_MALLOC;
+ }
+
+ data->column = column;
+ data->type = type;
+ data->next = row->data;
+ row->data = data;
+ }
+
+ if (value) {
+ if (data->type != type)
+ return SNMPERR_GENERR;
+
+ SNMP_FREE(data->data.voidp);
+ if (value_len) {
+ if (memdup(&data->data.string, value, (value_len)) !=
+ SNMPERR_SUCCESS) {
+ snmp_log(LOG_CRIT, "no memory in netsnmp_set_row_column");
+ return SNMPERR_MALLOC;
+ }
+ } else {
+ data->data.string = malloc(1);
+ }
+ data->data_len = value_len;
+ }
+ return SNMPERR_SUCCESS;
+}
+
+/* ==================================
+ *
+ * Data Set API: Index operations
+ *
+ * ================================== */
+
+/** adds an index to the table. Call this repeatly for each index. */
+void
+netsnmp_table_dataset_add_index(netsnmp_table_data_set *table, u_char type)
+{
+ if (!table)
+ return;
+ netsnmp_table_data_add_index(table->table, type);
+}
+
+/** adds multiple indexes to a table_dataset helper object.
+ * To end the list, use a 0 after the list of ASN index types. */
+void
+#if HAVE_STDARG_H
+netsnmp_table_set_add_indexes(netsnmp_table_data_set *tset,
+ ...)
+#else
+netsnmp_table_set_add_indexes(va_alist)
+ va_dcl
+#endif
+{
+ va_list debugargs;
+ int type;
+
+#if HAVE_STDARG_H
+ va_start(debugargs, tset);
+#else
+ netsnmp_table_data_set *tset;
+
+ va_start(debugargs);
+ tset = va_arg(debugargs, netsnmp_table_data_set *);
+#endif
+
+ if (tset)
+ while ((type = va_arg(debugargs, int)) != 0)
+ netsnmp_table_data_add_index(tset->table, type);
+
+ va_end(debugargs);
+}
+
+/** @}
+ */