/* * table_container.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 #include #include #include #include #if HAVE_STRING_H #include #else #include #endif #include #include #include netsnmp_feature_provide(table_container); netsnmp_feature_child_of(table_container, table_container_all); netsnmp_feature_child_of(table_container_replace_row, table_container_all); netsnmp_feature_child_of(table_container_extract, table_container_all); netsnmp_feature_child_of(table_container_management, table_container_all); netsnmp_feature_child_of(table_container_row_remove, table_container_all); netsnmp_feature_child_of(table_container_row_insert, table_container_all); netsnmp_feature_child_of(table_container_all, mib_helpers); #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 */ /* * PRIVATE structure for holding important info for each table. */ typedef struct container_table_data_s { /** Number of handlers whose myvoid pointer points to this structure. */ int refcnt; /** registration info for the table */ netsnmp_table_registration_info *tblreg_info; /** container for the table rows */ netsnmp_container *table; /* * mutex_type lock; */ /* what type of key do we want? */ char key_type; } container_table_data; /** @defgroup table_container table_container * Helps you implement a table when data can be found via a netsnmp_container. * @ingroup table * * The table_container handler is used (automatically) in conjuntion * with the @link table table@endlink handler. * * This handler will use the index information provided by * the @link table @endlink handler to find the row needed to process * the request. * * The container must use one of 3 key types. It is the sub-handler's * responsibility to ensure that the container and key type match (unless * neither is specified, in which case a default will be used.) * * The current key types are: * * TABLE_CONTAINER_KEY_NETSNMP_INDEX * The container should do comparisons based on a key that may be cast * to a netsnmp index (netsnmp_index *). This index contains only the * index portion of the OID, not the entire OID. * * TABLE_CONTAINER_KEY_VARBIND_INDEX * The container should do comparisons based on a key that may be cast * to a netsnmp variable list (netsnmp_variable_list *). This variable * list will contain one varbind for each index component. * * TABLE_CONTAINER_KEY_VARBIND_RAW (NOTE: unimplemented) * While not yet implemented, future plans include passing the request * varbind with the full OID to a container. * * If a key type is not specified at registration time, the default key type * of TABLE_CONTAINER_KEY_NETSNMP_INDEX will be used. If a container is * provided, or the handler name is aliased to a container type, the container * must use a netsnmp index. * * If no container is provided, a lookup will be made based on the * sub-handler's name, or if that isn't found, "table_container". The * table_container key type will be netsnmp_index. * * The container must, at a minimum, implement find and find_next. If a NULL * key is passed to the container, it must return the first item, if any. * All containers provided by net-snmp fulfil this requirement. * * This handler will only register to process 'data lookup' modes. In * traditional net-snmp modes, that is any GET-like mode (GET, GET-NEXT, * GET-BULK) or the first phase of a SET (RESERVE1). In the new baby-steps * mode, DATA_LOOKUP is it's own mode, and is a pre-cursor to other modes. * * When called, the handler will call the appropriate container method * with the appropriate key type. If a row was not found, the result depends * on the mode. * * GET Processing * An exact match must be found. If one is not, the error NOSUCHINSTANCE * is set. * * GET-NEXT / GET-BULK * If no row is found, the column number will be increased (using any * valid_columns structure that may have been provided), and the first row * will be retrieved. If no first row is found, the processed flag will be * set, so that the sub-handler can skip any processing related to the * request. The agent will notice this unsatisfied request, and attempt to * pass it to the next appropriate handler. * * SET * If the hander did not register with the HANDLER_CAN_NOT_CREATE flag * set in the registration modes, it is assumed that this is a row * creation request and a NULL row is added to the request's data list. * The sub-handler is responsbile for dealing with any row creation * contraints and inserting any newly created rows into the container * and the request's data list. * * If a row is found, it will be inserted into * the request's data list. The sub-handler may retrieve it by calling * netsnmp_container_table_extract_context(request); * * NOTE NOTE NOTE: * * This helper and it's API are still being tested and are subject to change. * * @{ */ static int _container_table_handler(netsnmp_mib_handler *handler, netsnmp_handler_registration *reginfo, netsnmp_agent_request_info *agtreq_info, netsnmp_request_info *requests); static void * _find_next_row(netsnmp_container *c, netsnmp_table_request_info *tblreq, void * key); /********************************************************************** ********************************************************************** * * * * * PUBLIC Registration functions * * * * * ********************************************************************** **********************************************************************/ /* ================================== * * Container Table API: Table maintenance * * ================================== */ #ifndef NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_MANAGEMENT container_table_data * netsnmp_tcontainer_create_table( const char *name, netsnmp_container *container, long flags ) { container_table_data *table; table = SNMP_MALLOC_TYPEDEF(container_table_data); if (!table) return NULL; if (container) table->table = container; else { table->table = netsnmp_container_find("table_container"); if (!table->table) { SNMP_FREE(table); return NULL; } } if (flags) table->key_type = (char)(flags & 0x03); /* Use lowest two bits */ else table->key_type = TABLE_CONTAINER_KEY_NETSNMP_INDEX; if (!table->table->compare) table->table->compare = netsnmp_compare_netsnmp_index; if (!table->table->ncompare) table->table->ncompare = netsnmp_ncompare_netsnmp_index; return table; } void netsnmp_tcontainer_delete_table( container_table_data *table ) { if (!table) return; if (table->table) CONTAINER_FREE(table->table); SNMP_FREE(table); return; } #endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_MANAGEMENT */ /* * The various standalone row operation routines * (create/clone/copy/delete) * will be specific to a particular table, * so can't be implemented here. */ int netsnmp_tcontainer_add_row( container_table_data *table, netsnmp_index *row ) { if (!table || !table->table || !row) return -1; CONTAINER_INSERT( table->table, row ); return 0; } netsnmp_index * netsnmp_tcontainer_remove_row( container_table_data *table, netsnmp_index *row ) { if (!table || !table->table || !row) return NULL; CONTAINER_REMOVE( table->table, row ); return NULL; } #ifndef NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_REPLACE_ROW int netsnmp_tcontainer_replace_row( container_table_data *table, netsnmp_index *old_row, netsnmp_index *new_row ) { if (!table || !table->table || !old_row || !new_row) return -1; netsnmp_tcontainer_remove_row( table, old_row ); netsnmp_tcontainer_add_row( table, new_row ); return 0; } #endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_REPLACE_ROW */ /* netsnmp_tcontainer_remove_delete_row() will be table-specific too */ /* ================================== * * Container Table API: MIB maintenance * * ================================== */ static container_table_data * netsnmp_container_table_data_clone(container_table_data *tad) { ++tad->refcnt; return tad; } static void netsnmp_container_table_data_free(container_table_data *tad) { if (--tad->refcnt == 0) free(tad); } /** returns a netsnmp_mib_handler object for the table_container helper */ netsnmp_mib_handler * netsnmp_container_table_handler_get(netsnmp_table_registration_info *tabreg, netsnmp_container *container, char key_type) { container_table_data *tad; netsnmp_mib_handler *handler; if (NULL == tabreg) { snmp_log(LOG_ERR, "bad param in netsnmp_container_table_register\n"); return NULL; } tad = SNMP_MALLOC_TYPEDEF(container_table_data); handler = netsnmp_create_handler("table_container", _container_table_handler); if((NULL == tad) || (NULL == handler)) { if(tad) free(tad); /* SNMP_FREE wasted on locals */ if(handler) free(handler); /* SNMP_FREE wasted on locals */ snmp_log(LOG_ERR, "malloc failure in netsnmp_container_table_register\n"); return NULL; } tad->refcnt = 1; tad->tblreg_info = tabreg; /* we need it too, but it really is not ours */ if(key_type) tad->key_type = key_type; else tad->key_type = TABLE_CONTAINER_KEY_NETSNMP_INDEX; if(NULL == container) container = netsnmp_container_find("table_container"); tad->table = container; if (NULL==container->compare) container->compare = netsnmp_compare_netsnmp_index; if (NULL==container->ncompare) container->ncompare = netsnmp_ncompare_netsnmp_index; handler->myvoid = (void*)tad; handler->data_clone = (void *(*)(void *))netsnmp_container_table_data_clone; handler->data_free = (void (*)(void *))netsnmp_container_table_data_free; handler->flags |= MIB_HANDLER_AUTO_NEXT; return handler; } int netsnmp_container_table_register(netsnmp_handler_registration *reginfo, netsnmp_table_registration_info *tabreg, netsnmp_container *container, char key_type ) { netsnmp_mib_handler *handler; if ((NULL == reginfo) || (NULL == reginfo->handler) || (NULL == tabreg)) { snmp_log(LOG_ERR, "bad param in netsnmp_container_table_register\n"); netsnmp_handler_registration_free(reginfo); return SNMPERR_GENERR; } if (NULL==container) container = netsnmp_container_find(reginfo->handlerName); handler = netsnmp_container_table_handler_get(tabreg, container, key_type); if (!handler || (netsnmp_inject_handler(reginfo, handler) != SNMPERR_SUCCESS)) { snmp_log(LOG_ERR, "could not create container table handler\n"); netsnmp_handler_free(handler); netsnmp_handler_registration_free(reginfo); return MIB_REGISTRATION_FAILED; } return netsnmp_register_table(reginfo, tabreg); } int netsnmp_container_table_unregister(netsnmp_handler_registration *reginfo) { container_table_data *tad; if (!reginfo) return MIB_UNREGISTRATION_FAILED; tad = (container_table_data *) netsnmp_find_handler_data_by_name(reginfo, "table_container"); if (tad) { CONTAINER_FREE( tad->table ); tad->table = NULL; /* * Note: don't free the memory tad points at here - that is done * by netsnmp_container_table_data_free(). */ } return netsnmp_unregister_table( reginfo ); } /** retrieve the container used by the table_container helper */ #ifndef NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_EXTRACT netsnmp_container* netsnmp_container_table_container_extract(netsnmp_request_info *request) { return (netsnmp_container *) netsnmp_request_get_list_data(request, TABLE_CONTAINER_CONTAINER); } #endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_EXTRACT */ #ifndef NETSNMP_USE_INLINE /** find the context data used by the table_container helper */ void * netsnmp_container_table_row_extract(netsnmp_request_info *request) { /* * NOTE: this function must match in table_container.c and table_container.h. * if you change one, change them both! */ return netsnmp_request_get_list_data(request, TABLE_CONTAINER_ROW); } /** find the context data used by the table_container helper */ void * netsnmp_container_table_extract_context(netsnmp_request_info *request) { /* * NOTE: this function must match in table_container.c and table_container.h. * if you change one, change them both! */ return netsnmp_request_get_list_data(request, TABLE_CONTAINER_ROW); } #endif /* inline */ #ifndef NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_ROW_INSERT /** inserts a newly created table_container entry into a request list */ void netsnmp_container_table_row_insert(netsnmp_request_info *request, netsnmp_index *row) { netsnmp_request_info *req; netsnmp_table_request_info *table_info = NULL; netsnmp_variable_list *this_index = NULL; netsnmp_variable_list *that_index = NULL; oid base_oid[] = {0, 0}; /* Make sure index OIDs are legal! */ oid this_oid[MAX_OID_LEN]; oid that_oid[MAX_OID_LEN]; size_t this_oid_len, that_oid_len; if (!request) return; /* * We'll add the new row information to any request * structure with the same index values as the request * passed in (which includes that one!). * * So construct an OID based on these index values. */ table_info = netsnmp_extract_table_info(request); this_index = table_info->indexes; build_oid_noalloc(this_oid, MAX_OID_LEN, &this_oid_len, base_oid, 2, this_index); /* * We need to look through the whole of the request list * (as received by the current handler), as there's no * guarantee that this routine will be called by the first * varbind that refers to this row. * In particular, a RowStatus controlled row creation * may easily occur later in the variable list. * * So first, we rewind to the head of the list.... */ for (req=request; req->prev; req=req->prev) ; /* * ... and then start looking for matching indexes * (by constructing OIDs from these index values) */ for (; req; req=req->next) { if (req->processed) continue; table_info = netsnmp_extract_table_info(req); that_index = table_info->indexes; build_oid_noalloc(that_oid, MAX_OID_LEN, &that_oid_len, base_oid, 2, that_index); /* * This request has the same index values, * so add the newly-created row information. */ if (snmp_oid_compare(this_oid, this_oid_len, that_oid, that_oid_len) == 0) { netsnmp_request_add_list_data(req, netsnmp_create_data_list(TABLE_CONTAINER_ROW, row, NULL)); } } } #endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_ROW_INSERT */ #ifndef NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_ROW_REMOVE /** removes a table_container entry from a request list */ void netsnmp_container_table_row_remove(netsnmp_request_info *request, netsnmp_index *row) { netsnmp_request_info *req; netsnmp_table_request_info *table_info = NULL; netsnmp_variable_list *this_index = NULL; netsnmp_variable_list *that_index = NULL; oid base_oid[] = {0, 0}; /* Make sure index OIDs are legal! */ oid this_oid[MAX_OID_LEN]; oid that_oid[MAX_OID_LEN]; size_t this_oid_len, that_oid_len; if (!request) return; /* * We'll add the new row information to any request * structure with the same index values as the request * passed in (which includes that one!). * * So construct an OID based on these index values. */ table_info = netsnmp_extract_table_info(request); this_index = table_info->indexes; build_oid_noalloc(this_oid, MAX_OID_LEN, &this_oid_len, base_oid, 2, this_index); /* * We need to look through the whole of the request list * (as received by the current handler), as there's no * guarantee that this routine will be called by the first * varbind that refers to this row. * In particular, a RowStatus controlled row creation * may easily occur later in the variable list. * * So first, we rewind to the head of the list.... */ for (req=request; req->prev; req=req->prev) ; /* * ... and then start looking for matching indexes * (by constructing OIDs from these index values) */ for (; req; req=req->next) { if (req->processed) continue; table_info = netsnmp_extract_table_info(req); that_index = table_info->indexes; build_oid_noalloc(that_oid, MAX_OID_LEN, &that_oid_len, base_oid, 2, that_index); /* * This request has the same index values, * so add the newly-created row information. */ if (snmp_oid_compare(this_oid, this_oid_len, that_oid, that_oid_len) == 0) { netsnmp_request_remove_list_data(req, TABLE_CONTAINER_ROW); } } } #endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER_ROW_REMOVE */ /** @cond */ /********************************************************************** ********************************************************************** * * * * * DATA LOOKUP functions * * * * * ********************************************************************** **********************************************************************/ NETSNMP_STATIC_INLINE void _set_key( container_table_data * tad, netsnmp_request_info *request, netsnmp_table_request_info *tblreq_info, void **key, netsnmp_index *index ) { if (TABLE_CONTAINER_KEY_NETSNMP_INDEX == tad->key_type) { index->oids = tblreq_info->index_oid; index->len = tblreq_info->index_oid_len; *key = index; } else if (TABLE_CONTAINER_KEY_VARBIND_INDEX == tad->key_type) { *key = tblreq_info->indexes; } #if 0 else if (TABLE_CONTAINER_KEY_VARBIND_RAW == tad->key_type) { *key = request->requestvb; } #endif else *key = NULL; } NETSNMP_STATIC_INLINE void _data_lookup(netsnmp_handler_registration *reginfo, netsnmp_agent_request_info *agtreq_info, netsnmp_request_info *request, container_table_data * tad) { netsnmp_index *row = NULL; netsnmp_table_request_info *tblreq_info; netsnmp_variable_list *var; netsnmp_index index; void *key; var = request->requestvb; DEBUGIF("table_container") { DEBUGMSGTL(("table_container", " data_lookup oid:")); DEBUGMSGOID(("table_container", var->name, var->name_length)); DEBUGMSG(("table_container", "\n")); } /* * Get pointer to the table information for this request. This * information was saved by table_helper_handler. */ tblreq_info = netsnmp_extract_table_info(request); /** the table_helper_handler should enforce column boundaries. */ netsnmp_assert((NULL != tblreq_info) && (tblreq_info->colnum <= tad->tblreg_info->max_column)); if ((agtreq_info->mode == MODE_GETNEXT) || (agtreq_info->mode == MODE_GETBULK)) { /* * find the row. This will automatically move to the next * column, if necessary. */ _set_key( tad, request, tblreq_info, &key, &index ); row = (netsnmp_index*)_find_next_row(tad->table, tblreq_info, key); if (row) { /* * update indexes in tblreq_info (index & varbind), * then update request varbind oid */ if(TABLE_CONTAINER_KEY_NETSNMP_INDEX == tad->key_type) { tblreq_info->index_oid_len = row->len; memcpy(tblreq_info->index_oid, row->oids, row->len * sizeof(oid)); netsnmp_update_variable_list_from_index(tblreq_info); } else if (TABLE_CONTAINER_KEY_VARBIND_INDEX == tad->key_type) { /** xxx-rks: shouldn't tblreq_info->indexes be updated before we call this?? */ netsnmp_update_indexes_from_variable_list(tblreq_info); } if (TABLE_CONTAINER_KEY_VARBIND_RAW != tad->key_type) { netsnmp_table_build_oid_from_index(reginfo, request, tblreq_info); } } else { /* * no results found. Flag the request so lower handlers will * ignore it, but it is not an error - getnext will move * on to another handler to process this request. */ netsnmp_set_request_error(agtreq_info, request, SNMP_ENDOFMIBVIEW); DEBUGMSGTL(("table_container", "no row found\n")); } } /** GETNEXT/GETBULK */ else { _set_key( tad, request, tblreq_info, &key, &index ); row = (netsnmp_index*)CONTAINER_FIND(tad->table, key); if (NULL == row) { /* * not results found. For a get, that is an error */ DEBUGMSGTL(("table_container", "no row found\n")); #ifndef NETSNMP_NO_WRITE_SUPPORT if((agtreq_info->mode != MODE_SET_RESERVE1) || /* get */ (reginfo->modes & HANDLER_CAN_NOT_CREATE)) { /* no create */ #endif /* NETSNMP_NO_WRITE_SUPPORT */ netsnmp_set_request_error(agtreq_info, request, SNMP_NOSUCHINSTANCE); #ifndef NETSNMP_NO_WRITE_SUPPORT } #endif /* NETSNMP_NO_WRITE_SUPPORT */ } } /** GET/SET */ /* * save the data and table in the request. */ if (SNMP_ENDOFMIBVIEW != request->requestvb->type) { if (NULL != row) netsnmp_request_add_list_data(request, netsnmp_create_data_list (TABLE_CONTAINER_ROW, row, NULL)); netsnmp_request_add_list_data(request, netsnmp_create_data_list (TABLE_CONTAINER_CONTAINER, tad->table, NULL)); } } /********************************************************************** ********************************************************************** * * * * * netsnmp_table_container_helper_handler() * * * * * ********************************************************************** **********************************************************************/ static int _container_table_handler(netsnmp_mib_handler *handler, netsnmp_handler_registration *reginfo, netsnmp_agent_request_info *agtreq_info, netsnmp_request_info *requests) { int rc = SNMP_ERR_NOERROR; int oldmode, need_processing = 0; container_table_data *tad; /** sanity checks */ netsnmp_assert((NULL != handler) && (NULL != handler->myvoid)); netsnmp_assert((NULL != reginfo) && (NULL != agtreq_info)); DEBUGMSGTL(("table_container", "Mode %s, Got request:\n", se_find_label_in_slist("agent_mode",agtreq_info->mode))); /* * First off, get our pointer from the handler. This * lets us get to the table registration information we * saved in get_table_container_handler(), as well as the * container where the actual table data is stored. */ tad = (container_table_data *)handler->myvoid; /* * only do data lookup for first pass * * xxx-rks: this should really be handled up one level. we should * be able to say what modes we want to be called for during table * registration. */ oldmode = agtreq_info->mode; if(MODE_IS_GET(oldmode) #ifndef NETSNMP_NO_WRITE_SUPPORT || (MODE_SET_RESERVE1 == oldmode) #endif /* NETSNMP_NO_WRITE_SUPPORT */ ) { netsnmp_request_info *curr_request; /* * Loop through each of the requests, and * try to find the appropriate row from the container. */ for (curr_request = requests; curr_request; curr_request = curr_request->next) { /* * skip anything that doesn't need processing. */ if (curr_request->processed != 0) { DEBUGMSGTL(("table_container", "already processed\n")); continue; } /* * find data for this request */ _data_lookup(reginfo, agtreq_info, curr_request, tad); if(curr_request->processed) continue; ++need_processing; } /** for ( ... requests ... ) */ } /* * send GET instead of GETNEXT to sub-handlers * xxx-rks: again, this should be handled further up. */ if ((oldmode == MODE_GETNEXT) && (handler->next)) { /* * tell agent handlder not to auto call next handler */ handler->flags |= MIB_HANDLER_AUTO_NEXT_OVERRIDE_ONCE; /* * if we found rows to process, pretend to be a get request * and call handler below us. */ if(need_processing > 0) { agtreq_info->mode = MODE_GET; rc = netsnmp_call_next_handler(handler, reginfo, agtreq_info, requests); if (rc != SNMP_ERR_NOERROR) { DEBUGMSGTL(("table_container", "next handler returned %d\n", rc)); } agtreq_info->mode = oldmode; /* restore saved mode */ } } return rc; } /** @endcond */ /* ================================== * * Container Table API: Row operations * * ================================== */ static void * _find_next_row(netsnmp_container *c, netsnmp_table_request_info *tblreq, void * key) { void *row = NULL; if (!c || !tblreq || !tblreq->reg_info ) { snmp_log(LOG_ERR,"_find_next_row param error\n"); return NULL; } /* * table helper should have made sure we aren't below our minimum column */ netsnmp_assert(tblreq->colnum >= tblreq->reg_info->min_column); /* * if no indexes then use first row. */ if(tblreq->number_indexes == 0) { row = CONTAINER_FIRST(c); } else { if(NULL == key) { netsnmp_index index; index.oids = tblreq->index_oid; index.len = tblreq->index_oid_len; row = CONTAINER_NEXT(c, &index); } else row = CONTAINER_NEXT(c, key); /* * we don't have a row, but we might be at the end of a * column, so try the next column. */ if (NULL == row) { /* * don't set tblreq next_col unless we know there is one, * so we don't mess up table handler sparse table processing. */ oid next_col = netsnmp_table_next_column(tblreq); if (0 != next_col) { tblreq->colnum = next_col; row = CONTAINER_FIRST(c); } } } return row; } /** * deprecated, backwards compatability only * * expected impact to remove: none * - used between helpers, shouldn't have been used by end users * * replacement: none * - never should have been a public method in the first place */ netsnmp_index * netsnmp_table_index_find_next_row(netsnmp_container *c, netsnmp_table_request_info *tblreq) { return (netsnmp_index*)_find_next_row(c, tblreq, NULL ); } /* ================================== * * Container Table API: Index operations * * ================================== */ #else /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER */ netsnmp_feature_unused(table_container); #endif /* NETSNMP_FEATURE_REMOVE_TABLE_CONTAINER */ /** @} */