blob: 4a8e4767935367ca67a30eca98c24f823bf10ccf [file] [log] [blame]
#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/row_merge.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
netsnmp_feature_provide(row_merge)
netsnmp_feature_child_of(row_merge, row_merge_all)
netsnmp_feature_child_of(row_merge_all, mib_helpers)
#ifndef NETSNMP_FEATURE_REMOVE_ROW_MERGE
/** @defgroup row_merge row_merge
* Calls sub handlers with request for one row at a time.
* @ingroup utilities
* This helper splits a whole bunch of requests into chunks based on the row
* index that they refer to, and passes all requests for a given row to the lower handlers.
* This is useful for handlers that don't want to process multiple rows at the
* same time, but are happy to iterate through the request list for a single row.
* @{
*/
/** returns a row_merge handler that can be injected into a given
* handler chain.
*/
netsnmp_mib_handler *
netsnmp_get_row_merge_handler(int prefix_len)
{
netsnmp_mib_handler *ret = NULL;
ret = netsnmp_create_handler("row_merge",
netsnmp_row_merge_helper_handler);
if (ret) {
ret->myvoid = (void *)(intptr_t)prefix_len;
}
return ret;
}
/** functionally the same as calling netsnmp_register_handler() but also
* injects a row_merge handler at the same time for you. */
netsnmp_feature_child_of(register_row_merge, row_merge_all)
#ifndef NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE
int
netsnmp_register_row_merge(netsnmp_handler_registration *reginfo)
{
netsnmp_inject_handler(reginfo,
netsnmp_get_row_merge_handler(reginfo->rootoid_len+1));
return netsnmp_register_handler(reginfo);
}
#endif /* NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE */
static void
_rm_status_free(void *mem)
{
netsnmp_row_merge_status *rm_status = (netsnmp_row_merge_status*)mem;
if (NULL != rm_status->saved_requests)
free(rm_status->saved_requests);
if (NULL != rm_status->saved_status)
free(rm_status->saved_status);
free(mem);
}
/** retrieve row_merge_status
*/
netsnmp_row_merge_status *
netsnmp_row_merge_status_get(netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
int create_missing)
{
netsnmp_row_merge_status *rm_status;
char buf[64];
int rc;
/*
* see if we've already been here
*/
rc = snprintf(buf, sizeof(buf), "row_merge:%p", reginfo);
if ((-1 == rc) || ((size_t)rc >= sizeof(buf))) {
snmp_log(LOG_ERR,"error creating key\n");
return NULL;
}
rm_status = (netsnmp_row_merge_status*)netsnmp_agent_get_list_data(reqinfo, buf);
if ((NULL == rm_status) && create_missing) {
netsnmp_data_list *data_list;
rm_status = SNMP_MALLOC_TYPEDEF(netsnmp_row_merge_status);
if (NULL == rm_status) {
snmp_log(LOG_ERR,"error allocating memory\n");
return NULL;
}
data_list = netsnmp_create_data_list(buf, rm_status,
_rm_status_free);
if (NULL == data_list) {
free(rm_status);
return NULL;
}
netsnmp_agent_add_list_data(reqinfo, data_list);
}
return rm_status;
}
/** Determine if this is the first row
*
* returns 1 if this is the first row for this pass of the handler.
*/
int
netsnmp_row_merge_status_first(netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo)
{
netsnmp_row_merge_status *rm_status;
/*
* find status
*/
rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0);
if (NULL == rm_status)
return 0;
return (rm_status->count == 1) ? 1 : (rm_status->current == 1);
}
/** Determine if this is the last row
*
* returns 1 if this is the last row for this pass of the handler.
*/
int
netsnmp_row_merge_status_last(netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo)
{
netsnmp_row_merge_status *rm_status;
/*
* find status
*/
rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0);
if (NULL == rm_status)
return 0;
return (rm_status->count == 1) ? 1 :
(rm_status->current == rm_status->rows);
}
#define ROW_MERGE_WAITING 0
#define ROW_MERGE_ACTIVE 1
#define ROW_MERGE_DONE 2
#define ROW_MERGE_HEAD 3
/** Implements the row_merge handler */
int
netsnmp_row_merge_helper_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
netsnmp_request_info *request, **saved_requests;
char *saved_status;
netsnmp_row_merge_status *rm_status;
int i, j, ret, tail, count, final_rc = SNMP_ERR_NOERROR;
/*
* Use the prefix length as supplied during registration, rather
* than trying to second-guess what the MIB implementer wanted.
*/
int SKIP_OID = (int)(intptr_t)handler->myvoid;
DEBUGMSGTL(("helper:row_merge", "Got request (%d): ", SKIP_OID));
DEBUGMSGOID(("helper:row_merge", reginfo->rootoid, reginfo->rootoid_len));
DEBUGMSG(("helper:row_merge", "\n"));
/*
* find or create status
*/
rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 1);
/*
* Count the requests, and set up an array to keep
* track of the original order.
*/
for (count = 0, request = requests; request; request = request->next) {
DEBUGIF("helper:row_merge") {
DEBUGMSGTL(("helper:row_merge", " got varbind: "));
DEBUGMSGOID(("helper:row_merge", request->requestvb->name,
request->requestvb->name_length));
DEBUGMSG(("helper:row_merge", "\n"));
}
count++;
}
/*
* Optimization: skip all this if there is just one request
*/
if(count == 1) {
rm_status->count = count;
if (requests->processed)
return SNMP_ERR_NOERROR;
return netsnmp_call_next_handler(handler, reginfo, reqinfo, requests);
}
/*
* we really should only have to do this once, instead of every pass.
* as a precaution, we'll do it every time, but put in some asserts
* to see if we have to.
*/
/*
* if the count changed, re-do everything
*/
if ((0 != rm_status->count) && (rm_status->count != count)) {
/*
* ok, i know next/bulk can cause this condition. Probably
* GET, too. need to rethink this mode counting. maybe
* add the mode to the rm_status structure? xxx-rks
*/
if ((reqinfo->mode != MODE_GET) &&
(reqinfo->mode != MODE_GETNEXT) &&
(reqinfo->mode != MODE_GETBULK)) {
netsnmp_assert((NULL != rm_status->saved_requests) &&
(NULL != rm_status->saved_status));
}
DEBUGMSGTL(("helper:row_merge", "count changed! do over...\n"));
SNMP_FREE(rm_status->saved_requests);
SNMP_FREE(rm_status->saved_status);
rm_status->count = 0;
rm_status->rows = 0;
}
if (0 == rm_status->count) {
/*
* allocate memory for saved structure
*/
rm_status->saved_requests =
(netsnmp_request_info**)calloc(count+1,
sizeof(netsnmp_request_info*));
rm_status->saved_status = (char*)calloc(count,sizeof(char));
}
saved_status = rm_status->saved_status;
saved_requests = rm_status->saved_requests;
/*
* set up saved requests, and set any processed requests to done
*/
i = 0;
for (request = requests; request; request = request->next, i++) {
if (request->processed) {
saved_status[i] = ROW_MERGE_DONE;
DEBUGMSGTL(("helper:row_merge", " skipping processed oid: "));
DEBUGMSGOID(("helper:row_merge", request->requestvb->name,
request->requestvb->name_length));
DEBUGMSG(("helper:row_merge", "\n"));
}
else
saved_status[i] = ROW_MERGE_WAITING;
if (0 != rm_status->count)
netsnmp_assert(saved_requests[i] == request);
saved_requests[i] = request;
saved_requests[i]->prev = NULL;
}
saved_requests[i] = NULL;
/*
* Note that saved_requests[count] is valid
* (because of the 'count+1' in the calloc above),
* but NULL (since it's past the end of the list).
* This simplifies the re-linking later.
*/
/*
* Work through the (unprocessed) requests in order.
* For each of these, search the rest of the list for any
* matching indexes, and link them into a new list.
*/
for (i=0; i<count; i++) {
if (saved_status[i] != ROW_MERGE_WAITING)
continue;
if (0 == rm_status->count)
rm_status->rows++;
DEBUGMSGTL(("helper:row_merge", " row %d oid[%d]: ", rm_status->rows, i));
DEBUGMSGOID(("helper:row_merge", saved_requests[i]->requestvb->name,
saved_requests[i]->requestvb->name_length));
DEBUGMSG(("helper:row_merge", "\n"));
saved_requests[i]->next = NULL;
saved_status[i] = ROW_MERGE_HEAD;
tail = i;
for (j=i+1; j<count; j++) {
if (saved_status[j] != ROW_MERGE_WAITING)
continue;
DEBUGMSGTL(("helper:row_merge", "? oid[%d]: ", j));
DEBUGMSGOID(("helper:row_merge",
saved_requests[j]->requestvb->name,
saved_requests[j]->requestvb->name_length));
if (!snmp_oid_compare(
saved_requests[i]->requestvb->name+SKIP_OID,
saved_requests[i]->requestvb->name_length-SKIP_OID,
saved_requests[j]->requestvb->name+SKIP_OID,
saved_requests[j]->requestvb->name_length-SKIP_OID)) {
DEBUGMSG(("helper:row_merge", " match\n"));
saved_requests[tail]->next = saved_requests[j];
saved_requests[j]->next = NULL;
saved_requests[j]->prev = saved_requests[tail];
saved_status[j] = ROW_MERGE_ACTIVE;
tail = j;
}
else
DEBUGMSG(("helper:row_merge", " no match\n"));
}
}
/*
* not that we have a list for each row, call next handler...
*/
if (0 == rm_status->count)
rm_status->count = count;
rm_status->current = 0;
for (i=0; i<count; i++) {
if (saved_status[i] != ROW_MERGE_HEAD)
continue;
/*
* found the head of a new row,
* call the next handler with this list
*/
rm_status->current++;
ret = netsnmp_call_next_handler(handler, reginfo, reqinfo,
saved_requests[i]);
if (ret != SNMP_ERR_NOERROR) {
snmp_log(LOG_WARNING,
"bad rc (%d) from next handler in row_merge\n", ret);
if (SNMP_ERR_NOERROR == final_rc)
final_rc = ret;
}
}
/*
* restore original linked list
*/
for (i=0; i<count; i++) {
saved_requests[i]->next = saved_requests[i+1];
if (i>0)
saved_requests[i]->prev = saved_requests[i-1];
}
return final_rc;
}
/**
* initializes the row_merge helper which then registers a row_merge
* handler as a run-time injectable handler for configuration file
* use.
*/
void
netsnmp_init_row_merge(void)
{
netsnmp_register_handler_by_name("row_merge",
netsnmp_get_row_merge_handler(-1));
}
#else /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */
netsnmp_feature_unused(row_merge);
#endif /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */
/** @} */