net-snmp/agent/helpers/table_dataset.c

1499 lines
50 KiB
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 (c) 2016 VMware, Inc. All rights reserved.
* Use is subject to license terms specified in the COPYING file
* distributed with the Net-SNMP package.
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <net-snmp/net-snmp-features.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/table_dataset.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
netsnmp_feature_child_of(table_dataset_all, mib_helpers)
netsnmp_feature_child_of(table_dataset, table_dataset_all)
netsnmp_feature_child_of(table_dataset_remove_row, table_dataset_all)
netsnmp_feature_child_of(table_data_set_column, table_dataset_all)
netsnmp_feature_child_of(table_dataset_get_newrow, table_dataset_all)
netsnmp_feature_child_of(table_set_add_indexes, table_dataset_all)
netsnmp_feature_child_of(delete_table_data_set, table_dataset_all)
netsnmp_feature_child_of(table_set_multi_add_default_row, table_dataset_all)
netsnmp_feature_child_of(table_dataset_unregister_auto_data_table, table_dataset_all)
#ifdef NETSNMP_FEATURE_REQUIRE_TABLE_DATASET
netsnmp_feature_require(table_get_or_create_row_stash)
netsnmp_feature_require(table_data_delete_table)
netsnmp_feature_require(table_data)
netsnmp_feature_require(oid_stash_get_data)
netsnmp_feature_require(oid_stash_add_data)
#endif /* NETSNMP_FEATURE_REQUIRE_TABLE_DATASET */
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_DATASET
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_feature_require(oid_stash)
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
#ifndef NETSNMP_DISABLE_MIB_LOADING
netsnmp_feature_require(mib_to_asn_type)
#endif /* NETSNMP_DISABLE_MIB_LOADING */
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
*
* ================================== */
/** deletes a single dataset table data.
* returns the (possibly still good) next pointer of the deleted data object.
*/
NETSNMP_STATIC_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_set_storage*)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 */
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_DATASET_REMOVE_ROW
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);
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_DATASET_REMOVE_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);
}
/** 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;
}
#ifndef NETSNMP_FEATURE_REMOVE_DELETE_TABLE_DATA_SET
void netsnmp_delete_table_data_set(netsnmp_table_data_set *table_set)
{
netsnmp_table_data_set_storage *ptr, *next;
netsnmp_table_row *prow, *pnextrow;
for (ptr = table_set->default_row; ptr; ptr = next) {
next = ptr->next;
free(ptr);
}
table_set->default_row = NULL;
for (prow = table_set->table->first_row; prow; prow = pnextrow) {
pnextrow = prow->next;
netsnmp_table_dataset_remove_and_delete_row(table_set, prow);
}
table_set->table->first_row = NULL;
netsnmp_table_data_delete_table(table_set->table);
free(table_set);
}
#endif /* NETSNMP_FEATURE_REMOVE_DELETE_TABLE_DATA_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) {
*newrowdata = netsnmp_memdup(data,
sizeof(netsnmp_table_data_set_storage));
if (!*newrowdata) {
netsnmp_table_dataset_delete_row(newrow);
return NULL;
}
if (data->data.voidp) {
(*newrowdata)->data.voidp =
netsnmp_memdup(data->data.voidp, data->data_len);
if (!(*newrowdata)->data.voidp) {
netsnmp_table_dataset_delete_row(newrow);
return NULL;
}
}
}
}
return newrow;
}
/* ==================================
*
* 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);
#ifndef NETSNMP_NO_WRITE_SUPPORT
if (defrow->writable)
netsnmp_mark_row_column_writable(row, defrow->column, 1);
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
}
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);
if (new_col == NULL)
return SNMPERR_GENERR;
new_col->type = type;
new_col->writable = writable;
new_col->column = column;
if (default_value) {
new_col->data.voidp = netsnmp_memdup(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.
*/
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_SET_MULTI_ADD_DEFAULT_ROW
void
netsnmp_table_set_multi_add_default_row(netsnmp_table_data_set *tset, ...)
{
va_list debugargs;
unsigned int column;
int type, writable;
void *data;
size_t data_len;
va_start(debugargs, tset);
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);
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_SET_MULTI_ADD_DEFAULT_ROW */
/* ==================================
*
* 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)
{
netsnmp_mib_handler *handler;
int ret;
if (NULL == table_info) {
/*
* allocate the table if one wasn't allocated
*/
table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
if (table_info == NULL)
return SNMP_ERR_GENERR;
}
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;
}
handler = netsnmp_get_table_data_set_handler(data_set);
if (!handler ||
(netsnmp_inject_handler(reginfo, handler) != SNMPERR_SUCCESS)) {
snmp_log(LOG_ERR, "could not create table data set handler\n");
netsnmp_handler_free(handler);
netsnmp_handler_registration_free(reginfo);
return MIB_REGISTRATION_FAILED;
}
ret = netsnmp_register_table_data(reginfo, data_set->table,
table_info);
if (ret == SNMPERR_SUCCESS && reginfo->handler)
netsnmp_handler_owns_table_info(reginfo->handler->next);
return ret;
}
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);
if (newrowstash != NULL) {
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;
netsnmp_table_request_info *table_info;
netsnmp_request_info *request;
netsnmp_table_row *row = NULL;
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_oid_stash_node **stashp = NULL;
netsnmp_table_row *newrow = NULL;
newrow_stash *newrowstash = NULL;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
if (!handler)
return SNMPERR_GENERR;
DEBUGMSGTL(("netsnmp_table_data_set", "handler starting\n"));
for (request = requests; request; request = request->next) {
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_table_data_set *datatable =
(netsnmp_table_data_set *) handler->myvoid;
const oid * const suffix =
requests->requestvb->name + reginfo->rootoid_len + 2;
const size_t suffix_len =
requests->requestvb->name_length - (reginfo->rootoid_len + 2);
#endif /* NETSNMP_NO_WRITE_SUPPORT */
if (request->processed)
continue;
/*
* extract our stored data and table info
*/
row = netsnmp_extract_table_row(request);
table_info = netsnmp_extract_table_info(request);
#ifndef NETSNMP_NO_WRITE_SUPPORT
if (MODE_IS_SET(reqinfo->mode)) {
/*
* 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.
*/
stashp = netsnmp_table_dataset_get_or_create_stash(reqinfo,
datatable,
table_info);
if (NULL == stashp) {
netsnmp_set_request_error(reqinfo, request, SNMP_ERR_GENERR);
continue;
}
newrowstash
= (newrow_stash*)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);
if (newrowstash == NULL) {
netsnmp_set_request_error(reqinfo, request,
SNMP_ERR_GENERR);
continue;
}
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;
}
#endif /* NETSNMP_NO_WRITE_SUPPORT */
if (row)
data = (netsnmp_table_data_set_storage *) row->data;
if (!row || !table_info || !data) {
if (!table_info
#ifndef NETSNMP_NO_WRITE_SUPPORT
|| !MODE_IS_SET(reqinfo->mode)
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
) {
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->type == SNMP_NOSUCHINSTANCE) {
netsnmp_set_request_error(reqinfo, request,
SNMP_NOSUCHINSTANCE);
} else {
/*
* Note: data->data.voidp can be NULL, e.g. when a zero-length
* octet string has been stored in the table cache.
*/
netsnmp_table_data_build_result(reginfo, reqinfo, request,
row,
table_info->colnum,
data->type,
(u_char*)data->data.voidp,
data->data_len);
}
break;
#ifndef NETSNMP_NO_WRITE_SUPPORT
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 = (u_char *)
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;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
default:
snmp_log(LOG_ERR,
"table_dataset: unknown mode passed into the handler\n");
return SNMP_ERR_GENERR;
}
}
/* 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
*/
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_DATA_SET_COLUMN
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_table_data_set_storage*)netsnmp_extract_table_row_data( request );
if (data) {
data = netsnmp_table_data_set_find_column(data, column);
}
return data;
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_DATA_SET_COLUMN */
/* ==================================
*
* 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, free)); /* XXX */
}
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_DATASET_UNREGISTER_AUTO_DATA_TABLE
/** Undo the effect of netsnmp_register_auto_data_table().
*/
void
netsnmp_unregister_auto_data_table(netsnmp_table_data_set *table_set,
char *registration_name)
{
netsnmp_remove_list_node(&auto_tables, registration_name
? registration_name : table_set->table->name);
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_DATASET_UNREGISTER_AUTO_DATA_TABLE */
#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 table 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(%ld) 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;
/* fallthrough */
case MIB_ACCESS_READWRITE:
case MIB_ACCESS_WRITEONLY:
canwrite = 1;
/* fallthrough */
case MIB_ACCESS_READONLY:
DEBUGMSGTL(("table_set_add_table",
"adding column %ld 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, sizeof(tname));
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 = sizeof(buf);
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 = sizeof(buf);
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);
#ifndef NETSNMP_NO_WRITE_SUPPORT
if (dr->writable)
netsnmp_mark_row_column_writable(row, dr->column, 1); /* make writable */
#endif /* !NETSNMP_NO_WRITE_SUPPORT */
}
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);
}
}
#ifndef NETSNMP_NO_WRITE_SUPPORT
netsnmp_oid_stash_node **
netsnmp_table_dataset_get_or_create_stash(netsnmp_agent_request_info *reqinfo,
netsnmp_table_data_set *datatable,
netsnmp_table_request_info *table_info)
{
netsnmp_oid_stash_node **stashp = NULL;
char buf[256]; /* is this reasonable size?? */
size_t len;
int rc;
rc = snprintf(buf, sizeof(buf), "dataset_row_stash:%s:",
datatable->table->name);
if ((-1 == rc) || ((size_t)rc >= sizeof(buf))) {
snmp_log(LOG_ERR,"%s handler name too long\n", datatable->table->name);
return NULL;
}
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);
return NULL;
}
stashp = (netsnmp_oid_stash_node **)
netsnmp_table_get_or_create_row_stash(reqinfo, (u_char *) buf);
return stashp;
}
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_DATASET_GET_NEWROW
netsnmp_table_row *
netsnmp_table_dataset_get_newrow(netsnmp_request_info *request,
netsnmp_agent_request_info *reqinfo,
int rootoid_len,
netsnmp_table_data_set *datatable,
netsnmp_table_request_info *table_info)
{
oid * const suffix = request->requestvb->name + rootoid_len + 2;
size_t suffix_len = request->requestvb->name_length - (rootoid_len + 2);
netsnmp_oid_stash_node **stashp;
newrow_stash *newrowstash;
stashp = netsnmp_table_dataset_get_or_create_stash(reqinfo, datatable,
table_info);
if (NULL == stashp)
return NULL;
newrowstash = (newrow_stash*)netsnmp_oid_stash_get_data(*stashp, suffix, suffix_len);
if (NULL == newrowstash)
return NULL;
return newrowstash->newrow;
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_DATASET_GET_NEWROW */
#endif /* NETSNMP_NO_WRITE_SUPPORT */
/* ==================================
*
* 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;
}
#ifndef NETSNMP_NO_WRITE_SUPPORT
/**
* 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 = (struct netsnmp_table_data_set_storage_s*)row->data;
row->data = data;
} else {
data->writable = writable;
}
return SNMPERR_SUCCESS;
}
#endif /* NETSNMP_NO_WRITE_SUPPORT */
/**
* Sets a given column in a row with data given a type, value,
* and length. Data is memdup'ed by the function, at least if
* type != SNMP_NOSUCHINSTANCE and if value_len > 0.
*
* @param[in] row Pointer to the row to be modified.
* @param[in] column Index of the column to be modified.
* @param[in] type Either the ASN type of the value to be set or
* SNMP_NOSUCHINSTANCE.
* @param[in] value If type != SNMP_NOSUCHINSTANCE, pointer to the
* new value. May be NULL if value_len == 0, e.g. when storing a
* zero-length octet string. Ignored when type == SNMP_NOSUCHINSTANCE.
* @param[in] value_len If type != SNMP_NOSUCHINSTANCE, number of bytes
* occupied by *value. Ignored when type == SNMP_NOSUCHINSTANCE.
*
* @return SNMPERR_SUCCESS upon success; SNMPERR_MALLOC when out of memory;
* or SNMPERR_GENERR when row == 0 or when type does not match the datatype
* of the data stored in *row.
*
*/
int
netsnmp_set_row_column(netsnmp_table_row *row, unsigned int column,
int type, const void *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 = (struct netsnmp_table_data_set_storage_s*)row->data;
row->data = data;
}
/* Transitions from / to SNMP_NOSUCHINSTANCE are allowed, but no other transitions. */
if (data->type != type && data->type != SNMP_NOSUCHINSTANCE
&& type != SNMP_NOSUCHINSTANCE)
return SNMPERR_GENERR;
/* Return now if neither the type nor the data itself has been modified. */
if (data->type == type && data->data_len == value_len
&& (value == NULL || memcmp(&data->data.string, value, value_len) == 0))
return SNMPERR_SUCCESS;
/* Reallocate memory and store the new value. */
data->data.voidp = realloc(data->data.voidp, value ? value_len : 0);
if (value && value_len && !data->data.voidp) {
data->data_len = 0;
data->type = SNMP_NOSUCHINSTANCE;
snmp_log(LOG_CRIT, "no memory in netsnmp_set_row_column");
return SNMPERR_MALLOC;
}
if (value && value_len)
memcpy(data->data.string, value, value_len);
data->type = type;
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. */
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_SET_ADD_INDEXES
void
netsnmp_table_set_add_indexes(netsnmp_table_data_set *tset,
...)
{
va_list debugargs;
int type;
va_start(debugargs, tset);
if (tset)
while ((type = va_arg(debugargs, int)) != 0)
netsnmp_table_data_add_index(tset->table, (u_char)type);
va_end(debugargs);
}
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_SET_ADD_INDEXES */
#else /* NETSNMP_FEATURE_REMOVE_TABLE_DATASET */
netsnmp_feature_unused(table_dataset);
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_DATASET */
/** @}
*/