937 lines
33 KiB
C
937 lines
33 KiB
C
|
/*
|
||
|
* table_array.c
|
||
|
* $Id$
|
||
|
*
|
||
|
* 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-includes.h>
|
||
|
#include <net-snmp/agent/net-snmp-agent-includes.h>
|
||
|
|
||
|
#include <net-snmp/agent/table_array.h>
|
||
|
|
||
|
#if HAVE_STRING_H
|
||
|
#include <string.h>
|
||
|
#else
|
||
|
#include <strings.h>
|
||
|
#endif
|
||
|
|
||
|
#include <net-snmp/agent/table.h>
|
||
|
#include <net-snmp/library/container.h>
|
||
|
#include <net-snmp/library/snmp_assert.h>
|
||
|
|
||
|
netsnmp_feature_child_of(table_array_all, mib_helpers)
|
||
|
|
||
|
netsnmp_feature_child_of(table_array_register,table_array_all)
|
||
|
netsnmp_feature_child_of(table_array_find_table_array_handler,table_array_all)
|
||
|
netsnmp_feature_child_of(table_array_extract_array_context,table_array_all)
|
||
|
netsnmp_feature_child_of(table_array_check_row_status,table_array_all)
|
||
|
|
||
|
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER
|
||
|
|
||
|
/*
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_BEGIN -1
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_RESERVE1 0
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_RESERVE2 1
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_ACTION 2
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_COMMIT 3
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_FREE 4
|
||
|
* snmp.h:#define SNMP_MSG_INTERNAL_SET_UNDO 5
|
||
|
*/
|
||
|
|
||
|
static const char *mode_name[] = {
|
||
|
"Reserve 1",
|
||
|
"Reserve 2",
|
||
|
"Action",
|
||
|
"Commit",
|
||
|
"Free",
|
||
|
"Undo"
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* PRIVATE structure for holding important info for each table.
|
||
|
*/
|
||
|
typedef struct table_container_data_s {
|
||
|
|
||
|
/** registration info for the table */
|
||
|
netsnmp_table_registration_info *tblreg_info;
|
||
|
|
||
|
/** container for the table rows */
|
||
|
netsnmp_container *table;
|
||
|
|
||
|
/*
|
||
|
* mutex_type lock;
|
||
|
*/
|
||
|
|
||
|
/** do we want to group rows with the same index
|
||
|
* together when calling callbacks? */
|
||
|
int group_rows;
|
||
|
|
||
|
/** callbacks for this table */
|
||
|
netsnmp_table_array_callbacks *cb;
|
||
|
|
||
|
} table_container_data;
|
||
|
|
||
|
/** @defgroup table_array table_array
|
||
|
* Helps you implement a table when data can be stored locally. The data is stored in a sorted array, using a binary search for lookups.
|
||
|
* @ingroup table
|
||
|
*
|
||
|
* The table_array handler is used (automatically) in conjuntion
|
||
|
* with the @link table table@endlink handler. It is primarily
|
||
|
* intended to be used with the mib2c configuration file
|
||
|
* mib2c.array-user.conf.
|
||
|
*
|
||
|
* The code generated by mib2c is useful when you have control of
|
||
|
* the data for each row. If you cannot control when rows are added
|
||
|
* and deleted (or at least be notified of changes to row data),
|
||
|
* then this handler is probably not for you.
|
||
|
*
|
||
|
* This handler makes use of callbacks (function pointers) to
|
||
|
* handle various tasks. Code is generated for each callback,
|
||
|
* but will need to be reviewed and flushed out by the user.
|
||
|
*
|
||
|
* NOTE NOTE NOTE: Once place where mib2c is somewhat lacking
|
||
|
* is with regards to tables with external indices. If your
|
||
|
* table makes use of one or more external indices, please
|
||
|
* review the generated code very carefully for comments
|
||
|
* regarding external indices.
|
||
|
*
|
||
|
* NOTE NOTE NOTE: This helper, the API and callbacks are still
|
||
|
* being tested and may change.
|
||
|
*
|
||
|
* The generated code will define a structure for storage of table
|
||
|
* related data. This structure must be used, as it contains the index
|
||
|
* OID for the row, which is used for keeping the array sorted. You can
|
||
|
* add addition fields or data to the structure for your own use.
|
||
|
*
|
||
|
* The generated code will also have code to handle SNMP-SET processing.
|
||
|
* If your table does not support any SET operations, simply comment
|
||
|
* out the \#define \<PREFIX\>_SET_HANDLING (where \<PREFIX\> is your
|
||
|
* table name) in the header file.
|
||
|
*
|
||
|
* SET processing modifies the row in-place. The duplicate_row
|
||
|
* callback will be called to save a copy of the original row.
|
||
|
* In the event of a failure before the commite phase, the
|
||
|
* row_copy callback will be called to restore the original row
|
||
|
* from the copy.
|
||
|
*
|
||
|
* Code will be generated to handle row creation. This code may be
|
||
|
* disabled by commenting out the \#define \<PREFIX\>_ROW_CREATION
|
||
|
* in the header file.
|
||
|
*
|
||
|
* If your table contains a RowStatus object, by default the
|
||
|
* code will not allow object in an active row to be modified.
|
||
|
* To allow active rows to be modified, remove the comment block
|
||
|
* around the \#define \<PREFIX\>_CAN_MODIFY_ACTIVE_ROW in the header
|
||
|
* file.
|
||
|
*
|
||
|
* Code will be generated to maintain a secondary index for all
|
||
|
* rows, stored in a binary tree. This is very useful for finding
|
||
|
* rows by a key other than the OID index. By default, the functions
|
||
|
* for maintaining this tree will be based on a character string.
|
||
|
* NOTE: this will likely be made into a more generic mechanism,
|
||
|
* using new callback methods, in the near future.
|
||
|
*
|
||
|
* The generated code contains many TODO comments. Make sure you
|
||
|
* check each one to see if it applies to your code. Examples include
|
||
|
* checking indices for syntax (ranges, etc), initializing default
|
||
|
* values in newly created rows, checking for row activation and
|
||
|
* deactivation requirements, etc.
|
||
|
*
|
||
|
* @{
|
||
|
*/
|
||
|
|
||
|
/**********************************************************************
|
||
|
**********************************************************************
|
||
|
* *
|
||
|
* *
|
||
|
* PUBLIC Registration functions *
|
||
|
* *
|
||
|
* *
|
||
|
**********************************************************************
|
||
|
**********************************************************************/
|
||
|
/** register specified callbacks for the specified table/oid. If the
|
||
|
group_rows parameter is set, the row related callbacks will be
|
||
|
called once for each unique row index. Otherwise, each callback
|
||
|
will be called only once, for all objects.
|
||
|
*/
|
||
|
int
|
||
|
netsnmp_table_container_register(netsnmp_handler_registration *reginfo,
|
||
|
netsnmp_table_registration_info *tabreg,
|
||
|
netsnmp_table_array_callbacks *cb,
|
||
|
netsnmp_container *container,
|
||
|
int group_rows)
|
||
|
{
|
||
|
table_container_data *tad = SNMP_MALLOC_TYPEDEF(table_container_data);
|
||
|
if (!tad)
|
||
|
return SNMPERR_GENERR;
|
||
|
tad->tblreg_info = tabreg; /* we need it too, but it really is not ours */
|
||
|
|
||
|
if (!cb) {
|
||
|
snmp_log(LOG_ERR, "table_array registration with no callbacks\n" );
|
||
|
free(tad); /* SNMP_FREE is overkill for local var */
|
||
|
return SNMPERR_GENERR;
|
||
|
}
|
||
|
/*
|
||
|
* check for required callbacks
|
||
|
*/
|
||
|
if ((cb->can_set &&
|
||
|
((NULL==cb->duplicate_row) || (NULL==cb->delete_row) ||
|
||
|
(NULL==cb->row_copy)) )) {
|
||
|
snmp_log(LOG_ERR, "table_array registration with incomplete "
|
||
|
"callback structure.\n");
|
||
|
free(tad); /* SNMP_FREE is overkill for local var */
|
||
|
return SNMPERR_GENERR;
|
||
|
}
|
||
|
|
||
|
if (NULL==container) {
|
||
|
tad->table = netsnmp_container_find("table_array");
|
||
|
snmp_log(LOG_ERR, "table_array couldn't allocate container\n" );
|
||
|
free(tad); /* SNMP_FREE is overkill for local var */
|
||
|
return SNMPERR_GENERR;
|
||
|
} else
|
||
|
tad->table = container;
|
||
|
if (NULL==tad->table->compare)
|
||
|
tad->table->compare = netsnmp_compare_netsnmp_index;
|
||
|
if (NULL==tad->table->ncompare)
|
||
|
tad->table->ncompare = netsnmp_ncompare_netsnmp_index;
|
||
|
|
||
|
tad->cb = cb;
|
||
|
|
||
|
reginfo->handler->myvoid = tad;
|
||
|
|
||
|
return netsnmp_register_table(reginfo, tabreg);
|
||
|
}
|
||
|
|
||
|
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_REGISTER
|
||
|
int
|
||
|
netsnmp_table_array_register(netsnmp_handler_registration *reginfo,
|
||
|
netsnmp_table_registration_info *tabreg,
|
||
|
netsnmp_table_array_callbacks *cb,
|
||
|
netsnmp_container *container,
|
||
|
int group_rows)
|
||
|
{
|
||
|
netsnmp_mib_handler *handler =
|
||
|
netsnmp_create_handler(reginfo->handlerName,
|
||
|
netsnmp_table_array_helper_handler);
|
||
|
if (!handler ||
|
||
|
(netsnmp_inject_handler(reginfo, handler) != SNMPERR_SUCCESS)) {
|
||
|
snmp_log(LOG_ERR, "could not create table array handler\n");
|
||
|
netsnmp_handler_free(handler);
|
||
|
netsnmp_handler_registration_free(reginfo);
|
||
|
return SNMP_ERR_GENERR;
|
||
|
}
|
||
|
|
||
|
return netsnmp_table_container_register(reginfo, tabreg, cb,
|
||
|
container, group_rows);
|
||
|
}
|
||
|
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_REGISTER */
|
||
|
|
||
|
/** find the handler for the table_array helper. */
|
||
|
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_FIND_TABLE_ARRAY_HANDLER
|
||
|
netsnmp_mib_handler *
|
||
|
netsnmp_find_table_array_handler(netsnmp_handler_registration *reginfo)
|
||
|
{
|
||
|
netsnmp_mib_handler *mh;
|
||
|
if (!reginfo)
|
||
|
return NULL;
|
||
|
mh = reginfo->handler;
|
||
|
while (mh) {
|
||
|
if (mh->access_method == netsnmp_table_array_helper_handler)
|
||
|
break;
|
||
|
mh = mh->next;
|
||
|
}
|
||
|
|
||
|
return mh;
|
||
|
}
|
||
|
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_FIND_TABLE_ARRAY_HANDLER */
|
||
|
|
||
|
/** find the context data used by the table_array helper */
|
||
|
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_EXTRACT_ARRAY_CONTEXT
|
||
|
netsnmp_container *
|
||
|
netsnmp_extract_array_context(netsnmp_request_info *request)
|
||
|
{
|
||
|
return (netsnmp_container*)netsnmp_request_get_list_data(request, TABLE_ARRAY_NAME);
|
||
|
}
|
||
|
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_EXTRACT_ARRAY_CONTEXT */
|
||
|
|
||
|
/** this function is called to validate RowStatus transitions. */
|
||
|
#ifndef NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_CHECK_ROW_STATUS
|
||
|
int
|
||
|
netsnmp_table_array_check_row_status(netsnmp_table_array_callbacks *cb,
|
||
|
netsnmp_request_group *ag,
|
||
|
long *rs_new, long *rs_old)
|
||
|
{
|
||
|
netsnmp_index *row_ctx;
|
||
|
netsnmp_index *undo_ctx;
|
||
|
if (!ag || !cb)
|
||
|
return SNMPERR_GENERR;
|
||
|
row_ctx = ag->existing_row;
|
||
|
undo_ctx = ag->undo_info;
|
||
|
|
||
|
/*
|
||
|
* xxx-rks: revisit row delete scenario
|
||
|
*/
|
||
|
if (row_ctx) {
|
||
|
/*
|
||
|
* either a new row, or change to old row
|
||
|
*/
|
||
|
/*
|
||
|
* is it set to active?
|
||
|
*/
|
||
|
if (RS_IS_GOING_ACTIVE(*rs_new)) {
|
||
|
/*
|
||
|
* is it ready to be active?
|
||
|
*/
|
||
|
if ((NULL==cb->can_activate) ||
|
||
|
cb->can_activate(undo_ctx, row_ctx, ag))
|
||
|
*rs_new = RS_ACTIVE;
|
||
|
else
|
||
|
return SNMP_ERR_INCONSISTENTVALUE;
|
||
|
} else {
|
||
|
/*
|
||
|
* not going active
|
||
|
*/
|
||
|
if (undo_ctx) {
|
||
|
/*
|
||
|
* change
|
||
|
*/
|
||
|
if (RS_IS_ACTIVE(*rs_old)) {
|
||
|
/*
|
||
|
* check pre-reqs for deactivation
|
||
|
*/
|
||
|
if (cb->can_deactivate &&
|
||
|
!cb->can_deactivate(undo_ctx, row_ctx, ag)) {
|
||
|
return SNMP_ERR_INCONSISTENTVALUE;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/*
|
||
|
* new row
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
if (*rs_new != RS_DESTROY) {
|
||
|
if ((NULL==cb->can_activate) ||
|
||
|
cb->can_activate(undo_ctx, row_ctx, ag))
|
||
|
*rs_new = RS_NOTINSERVICE;
|
||
|
else
|
||
|
*rs_new = RS_NOTREADY;
|
||
|
} else {
|
||
|
if (cb->can_delete && !cb->can_delete(undo_ctx, row_ctx, ag)) {
|
||
|
return SNMP_ERR_INCONSISTENTVALUE;
|
||
|
}
|
||
|
ag->row_deleted = 1;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/*
|
||
|
* check pre-reqs for delete row
|
||
|
*/
|
||
|
if (cb->can_delete && !cb->can_delete(undo_ctx, row_ctx, ag)) {
|
||
|
return SNMP_ERR_INCONSISTENTVALUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return SNMP_ERR_NOERROR;
|
||
|
}
|
||
|
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_ARRAY_CHECK_ROW_STATUS */
|
||
|
|
||
|
/** @} */
|
||
|
|
||
|
/** @cond */
|
||
|
/**********************************************************************
|
||
|
**********************************************************************
|
||
|
**********************************************************************
|
||
|
**********************************************************************
|
||
|
* *
|
||
|
* *
|
||
|
* *
|
||
|
* *
|
||
|
* EVERYTHING BELOW THIS IS PRIVATE IMPLEMENTATION DETAILS. *
|
||
|
* *
|
||
|
* *
|
||
|
* *
|
||
|
* *
|
||
|
**********************************************************************
|
||
|
**********************************************************************
|
||
|
**********************************************************************
|
||
|
**********************************************************************/
|
||
|
|
||
|
/**********************************************************************
|
||
|
**********************************************************************
|
||
|
* *
|
||
|
* *
|
||
|
* Structures, Utility/convenience functions *
|
||
|
* *
|
||
|
* *
|
||
|
**********************************************************************
|
||
|
**********************************************************************/
|
||
|
/*
|
||
|
* context info for SET requests
|
||
|
*/
|
||
|
#ifndef NETSNMP_NO_WRITE_SUPPORT
|
||
|
typedef struct set_context_s {
|
||
|
netsnmp_agent_request_info *agtreq_info;
|
||
|
table_container_data *tad;
|
||
|
int status;
|
||
|
} set_context;
|
||
|
#endif /* NETSNMP_NO_WRITE_SUPPORT */
|
||
|
|
||
|
void
|
||
|
build_new_oid(netsnmp_handler_registration *reginfo,
|
||
|
netsnmp_table_request_info *tblreq_info,
|
||
|
netsnmp_index *row, netsnmp_request_info *current)
|
||
|
{
|
||
|
oid coloid[MAX_OID_LEN];
|
||
|
|
||
|
if (!tblreq_info || !reginfo || !row || !current)
|
||
|
return;
|
||
|
|
||
|
memcpy(coloid, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid));
|
||
|
|
||
|
/** table.entry */
|
||
|
coloid[reginfo->rootoid_len] = 1;
|
||
|
|
||
|
/** table.entry.column */
|
||
|
coloid[reginfo->rootoid_len + 1] = tblreq_info->colnum;
|
||
|
|
||
|
/** table.entry.column.index */
|
||
|
memcpy(&coloid[reginfo->rootoid_len + 2], row->oids,
|
||
|
row->len * sizeof(oid));
|
||
|
|
||
|
snmp_set_var_objid(current->requestvb, coloid,
|
||
|
reginfo->rootoid_len + 2 + row->len);
|
||
|
}
|
||
|
|
||
|
/**********************************************************************
|
||
|
**********************************************************************
|
||
|
* *
|
||
|
* *
|
||
|
* GET procession functions *
|
||
|
* *
|
||
|
* *
|
||
|
**********************************************************************
|
||
|
**********************************************************************/
|
||
|
int
|
||
|
process_get_requests(netsnmp_handler_registration *reginfo,
|
||
|
netsnmp_agent_request_info *agtreq_info,
|
||
|
netsnmp_request_info *requests,
|
||
|
table_container_data * tad)
|
||
|
{
|
||
|
int rc = SNMP_ERR_NOERROR;
|
||
|
netsnmp_request_info *current;
|
||
|
netsnmp_index *row = NULL;
|
||
|
netsnmp_table_request_info *tblreq_info;
|
||
|
netsnmp_variable_list *var;
|
||
|
|
||
|
/*
|
||
|
* Loop through each of the requests, and
|
||
|
* try to find the appropriate row from the container.
|
||
|
*/
|
||
|
for (current = requests; current; current = current->next) {
|
||
|
|
||
|
var = current->requestvb;
|
||
|
DEBUGMSGTL(("table_array:get",
|
||
|
" process_get_request oid:"));
|
||
|
DEBUGMSGOID(("table_array:get", var->name,
|
||
|
var->name_length));
|
||
|
DEBUGMSG(("table_array:get", "\n"));
|
||
|
|
||
|
/*
|
||
|
* skip anything that doesn't need processing.
|
||
|
*/
|
||
|
if (current->processed != 0) {
|
||
|
DEBUGMSGTL(("table_array:get", "already processed\n"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get pointer to the table information for this request. This
|
||
|
* information was saved by table_helper_handler. When
|
||
|
* debugging, we double check a few assumptions. For example,
|
||
|
* the table_helper_handler should enforce column boundaries.
|
||
|
*/
|
||
|
tblreq_info = netsnmp_extract_table_info(current);
|
||
|
netsnmp_assert(tblreq_info->colnum <= tad->tblreg_info->max_column);
|
||
|
|
||
|
if ((agtreq_info->mode == MODE_GETNEXT) ||
|
||
|
(agtreq_info->mode == MODE_GETBULK)) {
|
||
|
/*
|
||
|
* find the row
|
||
|
*/
|
||
|
row = netsnmp_table_index_find_next_row(tad->table, tblreq_info);
|
||
|
if (!row) {
|
||
|
/*
|
||
|
* no results found.
|
||
|
*
|
||
|
* xxx-rks: how do we skip this entry for the next handler,
|
||
|
* but still allow it a chance to hit another handler?
|
||
|
*/
|
||
|
DEBUGMSGTL(("table_array:get", "no row found\n"));
|
||
|
netsnmp_set_request_error(agtreq_info, current,
|
||
|
SNMP_ENDOFMIBVIEW);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* * if data was found, make sure it has the column we want
|
||
|
*/
|
||
|
/* xxx-rks: add suport for sparse tables */
|
||
|
|
||
|
/*
|
||
|
* build new oid
|
||
|
*/
|
||
|
build_new_oid(reginfo, tblreq_info, row, current);
|
||
|
|
||
|
} /** GETNEXT/GETBULK */
|
||
|
else {
|
||
|
netsnmp_index index;
|
||
|
index.oids = tblreq_info->index_oid;
|
||
|
index.len = tblreq_info->index_oid_len;
|
||
|
|
||
|
row = (netsnmp_index*)CONTAINER_FIND(tad->table, &index);
|
||
|
if (!row) {
|
||
|
DEBUGMSGTL(("table_array:get", "no row found\n"));
|
||
|
netsnmp_set_request_error(agtreq_info, current,
|
||
|
SNMP_NOSUCHINSTANCE);
|
||
|
continue;
|
||
|
}
|
||
|
} /** GET */
|
||
|
|
||
|
/*
|
||
|
* get the data
|
||
|
*/
|
||
|
rc = tad->cb->get_value(current, row, tblreq_info);
|
||
|
|
||
|
} /** for ( ... requests ... ) */
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/**********************************************************************
|
||
|
**********************************************************************
|
||
|
* *
|
||
|
* *
|
||
|
* SET procession functions *
|
||
|
* *
|
||
|
* *
|
||
|
**********************************************************************
|
||
|
**********************************************************************/
|
||
|
|
||
|
void
|
||
|
group_requests(netsnmp_agent_request_info *agtreq_info,
|
||
|
netsnmp_request_info *requests,
|
||
|
netsnmp_container *request_group, table_container_data * tad)
|
||
|
{
|
||
|
netsnmp_table_request_info *tblreq_info;
|
||
|
netsnmp_index *row, *tmp, index;
|
||
|
netsnmp_request_info *current;
|
||
|
netsnmp_request_group *g;
|
||
|
netsnmp_request_group_item *i;
|
||
|
|
||
|
for (current = requests; current; current = current->next) {
|
||
|
/*
|
||
|
* skip anything that doesn't need processing.
|
||
|
*/
|
||
|
if (current->processed != 0) {
|
||
|
DEBUGMSGTL(("table_array:group",
|
||
|
"already processed\n"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 3.2.1 Setup and paranoia
|
||
|
* *
|
||
|
* * Get pointer to the table information for this request. This
|
||
|
* * information was saved by table_helper_handler. When
|
||
|
* * debugging, we double check a few assumptions. For example,
|
||
|
* * the table_helper_handler should enforce column boundaries.
|
||
|
*/
|
||
|
row = NULL;
|
||
|
tblreq_info = netsnmp_extract_table_info(current);
|
||
|
netsnmp_assert(tblreq_info->colnum <= tad->tblreg_info->max_column);
|
||
|
|
||
|
/*
|
||
|
* search for index
|
||
|
*/
|
||
|
index.oids = tblreq_info->index_oid;
|
||
|
index.len = tblreq_info->index_oid_len;
|
||
|
tmp = (netsnmp_index*)CONTAINER_FIND(request_group, &index);
|
||
|
if (tmp) {
|
||
|
DEBUGMSGTL(("table_array:group",
|
||
|
" existing group:"));
|
||
|
DEBUGMSGOID(("table_array:group", index.oids,
|
||
|
index.len));
|
||
|
DEBUGMSG(("table_array:group", "\n"));
|
||
|
g = (netsnmp_request_group *) tmp;
|
||
|
i = SNMP_MALLOC_TYPEDEF(netsnmp_request_group_item);
|
||
|
if (i == NULL)
|
||
|
return;
|
||
|
i->ri = current;
|
||
|
i->tri = tblreq_info;
|
||
|
i->next = g->list;
|
||
|
g->list = i;
|
||
|
|
||
|
/** xxx-rks: store map of colnum to request */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
DEBUGMSGTL(("table_array:group", " new group"));
|
||
|
DEBUGMSGOID(("table_array:group", index.oids,
|
||
|
index.len));
|
||
|
DEBUGMSG(("table_array:group", "\n"));
|
||
|
g = SNMP_MALLOC_TYPEDEF(netsnmp_request_group);
|
||
|
i = SNMP_MALLOC_TYPEDEF(netsnmp_request_group_item);
|
||
|
if (i == NULL || g == NULL) {
|
||
|
SNMP_FREE(i);
|
||
|
SNMP_FREE(g);
|
||
|
return;
|
||
|
}
|
||
|
g->list = i;
|
||
|
g->table = tad->table;
|
||
|
i->ri = current;
|
||
|
i->tri = tblreq_info;
|
||
|
/** xxx-rks: store map of colnum to request */
|
||
|
|
||
|
/*
|
||
|
* search for row. all changes are made to the original row,
|
||
|
* later, we'll make a copy in undo_info before we start processing.
|
||
|
*/
|
||
|
row = g->existing_row = (netsnmp_index*)CONTAINER_FIND(tad->table, &index);
|
||
|
if (!g->existing_row) {
|
||
|
if (!tad->cb->create_row) {
|
||
|
#ifndef NETSNMP_NO_WRITE_SUPPORT
|
||
|
if(MODE_IS_SET(agtreq_info->mode))
|
||
|
netsnmp_set_request_error(agtreq_info, current,
|
||
|
SNMP_ERR_NOTWRITABLE);
|
||
|
else
|
||
|
#endif /* NETSNMP_NO_WRITE_SUPPORT */
|
||
|
netsnmp_set_request_error(agtreq_info, current,
|
||
|
SNMP_NOSUCHINSTANCE);
|
||
|
free(g);
|
||
|
free(i);
|
||
|
continue;
|
||
|
}
|
||
|
/** use undo_info temporarily */
|
||
|
row = g->existing_row = tad->cb->create_row(&index);
|
||
|
if (!row) {
|
||
|
/* xxx-rks : parameter to create_row to allow
|
||
|
* for better error reporting. */
|
||
|
netsnmp_set_request_error(agtreq_info, current,
|
||
|
SNMP_ERR_GENERR);
|
||
|
free(g);
|
||
|
free(i);
|
||
|
continue;
|
||
|
}
|
||
|
g->row_created = 1;
|
||
|
}
|
||
|
|
||
|
g->index.oids = row->oids;
|
||
|
g->index.len = row->len;
|
||
|
|
||
|
CONTAINER_INSERT(request_group, g);
|
||
|
|
||
|
} /** for( current ... ) */
|
||
|
}
|
||
|
|
||
|
#ifndef NETSNMP_NO_WRITE_SUPPORT
|
||
|
static void
|
||
|
release_netsnmp_request_group(netsnmp_index *g, void *v)
|
||
|
{
|
||
|
netsnmp_request_group_item *tmp;
|
||
|
netsnmp_request_group *group = (netsnmp_request_group *) g;
|
||
|
|
||
|
if (!g)
|
||
|
return;
|
||
|
while (group->list) {
|
||
|
tmp = group->list;
|
||
|
group->list = tmp->next;
|
||
|
free(tmp);
|
||
|
}
|
||
|
|
||
|
free(group);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
release_netsnmp_request_groups(void *vp)
|
||
|
{
|
||
|
netsnmp_container *c = (netsnmp_container*)vp;
|
||
|
CONTAINER_FOR_EACH(c, (netsnmp_container_obj_func*)
|
||
|
release_netsnmp_request_group, NULL);
|
||
|
CONTAINER_FREE(c);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
process_set_group(netsnmp_index *o, void *c)
|
||
|
{
|
||
|
/* xxx-rks: should we continue processing after an error?? */
|
||
|
set_context *context = (set_context *) c;
|
||
|
netsnmp_request_group *ag = (netsnmp_request_group *) o;
|
||
|
int rc = SNMP_ERR_NOERROR;
|
||
|
|
||
|
switch (context->agtreq_info->mode) {
|
||
|
|
||
|
case MODE_SET_RESERVE1:/** -> SET_RESERVE2 || SET_FREE */
|
||
|
|
||
|
/*
|
||
|
* if not a new row, save undo info
|
||
|
*/
|
||
|
if (ag->row_created == 0) {
|
||
|
if (context->tad->cb->duplicate_row)
|
||
|
ag->undo_info = context->tad->cb->duplicate_row(ag->existing_row);
|
||
|
else
|
||
|
ag->undo_info = NULL;
|
||
|
if (NULL == ag->undo_info) {
|
||
|
rc = SNMP_ERR_RESOURCEUNAVAILABLE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (context->tad->cb->set_reserve1)
|
||
|
context->tad->cb->set_reserve1(ag);
|
||
|
break;
|
||
|
|
||
|
case MODE_SET_RESERVE2:/** -> SET_ACTION || SET_FREE */
|
||
|
if (context->tad->cb->set_reserve2)
|
||
|
context->tad->cb->set_reserve2(ag);
|
||
|
break;
|
||
|
|
||
|
case MODE_SET_ACTION:/** -> SET_COMMIT || SET_UNDO */
|
||
|
if (context->tad->cb->set_action)
|
||
|
context->tad->cb->set_action(ag);
|
||
|
break;
|
||
|
|
||
|
case MODE_SET_COMMIT:/** FINAL CHANCE ON SUCCESS */
|
||
|
if (ag->row_created == 0) {
|
||
|
/*
|
||
|
* this is an existing row, has it been deleted?
|
||
|
*/
|
||
|
if (ag->row_deleted == 1) {
|
||
|
DEBUGMSGT((TABLE_ARRAY_NAME, "action: deleting row\n"));
|
||
|
if (CONTAINER_REMOVE(ag->table, ag->existing_row) != 0) {
|
||
|
rc = SNMP_ERR_COMMITFAILED;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else if (ag->row_deleted == 0) {
|
||
|
/*
|
||
|
* new row (that hasn't been deleted) should be inserted
|
||
|
*/
|
||
|
DEBUGMSGT((TABLE_ARRAY_NAME, "action: inserting row\n"));
|
||
|
if (CONTAINER_INSERT(ag->table, ag->existing_row) != 0) {
|
||
|
rc = SNMP_ERR_COMMITFAILED;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (context->tad->cb->set_commit)
|
||
|
context->tad->cb->set_commit(ag);
|
||
|
|
||
|
/** no more use for undo_info, so free it */
|
||
|
if (ag->undo_info) {
|
||
|
context->tad->cb->delete_row(ag->undo_info);
|
||
|
ag->undo_info = NULL;
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
/* XXX-rks: finish row cooperative notifications
|
||
|
* if the table has requested it, send cooperative notifications
|
||
|
* for row operations.
|
||
|
*/
|
||
|
if (context->tad->notifications) {
|
||
|
if (ag->undo_info) {
|
||
|
if (!ag->existing_row)
|
||
|
netsnmp_monitor_notify(EVENT_ROW_DEL);
|
||
|
else
|
||
|
netsnmp_monitor_notify(EVENT_ROW_MOD);
|
||
|
}
|
||
|
else
|
||
|
netsnmp_monitor_notify(EVENT_ROW_ADD);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ((ag->row_created == 0) && (ag->row_deleted == 1)) {
|
||
|
context->tad->cb->delete_row(ag->existing_row);
|
||
|
ag->existing_row = NULL;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MODE_SET_FREE:/** FINAL CHANCE ON FAILURE */
|
||
|
if (context->tad->cb->set_free)
|
||
|
context->tad->cb->set_free(ag);
|
||
|
|
||
|
/** no more use for undo_info, so free it */
|
||
|
if (ag->row_created == 1) {
|
||
|
if (context->tad->cb->delete_row)
|
||
|
context->tad->cb->delete_row(ag->existing_row);
|
||
|
ag->existing_row = NULL;
|
||
|
}
|
||
|
else {
|
||
|
if (context->tad->cb->delete_row)
|
||
|
context->tad->cb->delete_row(ag->undo_info);
|
||
|
ag->undo_info = NULL;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MODE_SET_UNDO:/** FINAL CHANCE ON FAILURE */
|
||
|
/*
|
||
|
* status already set - don't change it now
|
||
|
*/
|
||
|
if (context->tad->cb->set_undo)
|
||
|
context->tad->cb->set_undo(ag);
|
||
|
|
||
|
/*
|
||
|
* no more use for undo_info, so free it
|
||
|
*/
|
||
|
if (ag->row_created == 0) {
|
||
|
/*
|
||
|
* restore old values
|
||
|
*/
|
||
|
context->tad->cb->row_copy(ag->existing_row, ag->undo_info);
|
||
|
context->tad->cb->delete_row(ag->undo_info);
|
||
|
ag->undo_info = NULL;
|
||
|
}
|
||
|
else {
|
||
|
context->tad->cb->delete_row(ag->existing_row);
|
||
|
ag->existing_row = NULL;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
snmp_log(LOG_ERR, "unknown mode processing SET for "
|
||
|
"netsnmp_table_array_helper_handler\n");
|
||
|
rc = SNMP_ERR_GENERR;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (rc)
|
||
|
netsnmp_set_request_error(context->agtreq_info,
|
||
|
ag->list->ri, rc);
|
||
|
|
||
|
}
|
||
|
|
||
|
int
|
||
|
process_set_requests(netsnmp_agent_request_info *agtreq_info,
|
||
|
netsnmp_request_info *requests,
|
||
|
table_container_data * tad, char *handler_name)
|
||
|
{
|
||
|
set_context context;
|
||
|
netsnmp_container *request_group;
|
||
|
|
||
|
/*
|
||
|
* create and save structure for set info
|
||
|
*/
|
||
|
request_group = (netsnmp_container*) netsnmp_agent_get_list_data
|
||
|
(agtreq_info, handler_name);
|
||
|
if (request_group == NULL) {
|
||
|
netsnmp_data_list *tmp;
|
||
|
request_group = netsnmp_container_find("request_group:"
|
||
|
"table_container");
|
||
|
request_group->compare = netsnmp_compare_netsnmp_index;
|
||
|
request_group->ncompare = netsnmp_ncompare_netsnmp_index;
|
||
|
|
||
|
DEBUGMSGTL(("table_array", "Grouping requests by oid\n"));
|
||
|
|
||
|
tmp = netsnmp_create_data_list(handler_name,
|
||
|
request_group,
|
||
|
release_netsnmp_request_groups);
|
||
|
netsnmp_agent_add_list_data(agtreq_info, tmp);
|
||
|
/*
|
||
|
* group requests.
|
||
|
*/
|
||
|
group_requests(agtreq_info, requests, request_group, tad);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* process each group one at a time
|
||
|
*/
|
||
|
context.agtreq_info = agtreq_info;
|
||
|
context.tad = tad;
|
||
|
context.status = SNMP_ERR_NOERROR;
|
||
|
CONTAINER_FOR_EACH(request_group,
|
||
|
(netsnmp_container_obj_func*)process_set_group,
|
||
|
&context);
|
||
|
|
||
|
return context.status;
|
||
|
}
|
||
|
#endif /* NETSNMP_NO_WRITE_SUPPORT */
|
||
|
|
||
|
|
||
|
/**********************************************************************
|
||
|
**********************************************************************
|
||
|
* *
|
||
|
* *
|
||
|
* netsnmp_table_array_helper_handler() *
|
||
|
* *
|
||
|
* *
|
||
|
**********************************************************************
|
||
|
**********************************************************************/
|
||
|
int
|
||
|
netsnmp_table_array_helper_handler(netsnmp_mib_handler *handler,
|
||
|
netsnmp_handler_registration *reginfo,
|
||
|
netsnmp_agent_request_info *agtreq_info,
|
||
|
netsnmp_request_info *requests)
|
||
|
{
|
||
|
|
||
|
/*
|
||
|
* First off, get our pointer from the handler. This
|
||
|
* lets us get to the table registration information we
|
||
|
* saved in get_table_array_handler(), as well as the
|
||
|
* container where the actual table data is stored.
|
||
|
*/
|
||
|
int rc = SNMP_ERR_NOERROR;
|
||
|
table_container_data *tad = (table_container_data *)handler->myvoid;
|
||
|
|
||
|
if (agtreq_info->mode < 0 || agtreq_info->mode > 5) {
|
||
|
DEBUGMSGTL(("table_array", "Mode %d, Got request:\n",
|
||
|
agtreq_info->mode));
|
||
|
} else {
|
||
|
DEBUGMSGTL(("table_array", "Mode %s, Got request:\n",
|
||
|
mode_name[agtreq_info->mode]));
|
||
|
}
|
||
|
|
||
|
#ifndef NETSNMP_NO_WRITE_SUPPORT
|
||
|
if (MODE_IS_SET(agtreq_info->mode)) {
|
||
|
/*
|
||
|
* netsnmp_mutex_lock(&tad->lock);
|
||
|
*/
|
||
|
rc = process_set_requests(agtreq_info, requests,
|
||
|
tad, handler->handler_name);
|
||
|
/*
|
||
|
* netsnmp_mutex_unlock(&tad->lock);
|
||
|
*/
|
||
|
} else
|
||
|
#endif /* NETSNMP_NO_WRITE_SUPPORT */
|
||
|
rc = process_get_requests(reginfo, agtreq_info, requests, tad);
|
||
|
|
||
|
if (rc != SNMP_ERR_NOERROR) {
|
||
|
DEBUGMSGTL(("table_array", "processing returned rc %d\n", rc));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Now we've done our processing. If there is another handler below us,
|
||
|
* call them.
|
||
|
*/
|
||
|
if (handler->next) {
|
||
|
rc = netsnmp_call_next_handler(handler, reginfo, agtreq_info, requests);
|
||
|
if (rc != SNMP_ERR_NOERROR) {
|
||
|
DEBUGMSGTL(("table_array", "next handler returned rc %d\n", rc));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
#endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER */
|
||
|
/** @endcond */
|