5 #include <net-snmp/net-snmp-config.h>
14 #include <net-snmp/net-snmp-includes.h>
15 #include <net-snmp/agent/net-snmp-agent-includes.h>
17 #include <net-snmp/agent/table.h>
18 #include <net-snmp/library/snmp_assert.h>
24 static void table_helper_cleanup(netsnmp_agent_request_info *reqinfo,
25 netsnmp_request_info *request,
27 static void table_data_free_func(void *data);
29 /** @defgroup table table: Helps you implement a table.
32 * This handler helps you implement a table by doing some of the
35 * This handler truly shows the power of the new handler mechanism.
36 * By creating a table handler and injecting it into your calling
37 * chain, or by using the netsnmp_register_table() function to register your
38 * table, you get access to some pre-parsed information.
39 * Specifically, the table handler pulls out the column number and
40 * indexes from the request oid so that you don't have to do the
41 * complex work to do that parsing within your own code.
43 * To do this, the table handler needs to know up front how your
44 * table is structured. To inform it about this, you fill in a
45 * table_registeration_info structure that is passed to the table
46 * handler. It contains the asn index types for the table as well as
47 * the minimum and maximum column that should be used.
52 /** Given a netsnmp_table_registration_info object, creates a table handler.
53 * You can use this table handler by injecting it into a calling
54 * chain. When the handler gets called, it'll do processing and
55 * store it's information into the request->parent_data structure.
58 netsnmp_get_table_handler(netsnmp_table_registration_info *tabreq)
60 netsnmp_mib_handler *ret = NULL;
63 snmp_log(LOG_INFO, "netsnmp_get_table_handler(NULL) called\n");
67 ret = netsnmp_create_handler(TABLE_HANDLER_NAME, table_helper_handler);
69 ret->myvoid = (void *) tabreq;
70 tabreq->number_indexes = count_varbinds(tabreq->indexes);
76 /** creates a table handler given the netsnmp_table_registration_info object,
77 * inserts it into the request chain and then calls
78 * netsnmp_register_handler() to register the table into the agent.
81 netsnmp_register_table(netsnmp_handler_registration *reginfo,
82 netsnmp_table_registration_info *tabreq)
84 netsnmp_inject_handler(reginfo, netsnmp_get_table_handler(tabreq));
85 return netsnmp_register_handler(reginfo);
88 /** extracts the processed table information from a given request.
89 * call this from subhandlers on a request to extract the processed
90 * netsnmp_request_info information. The resulting information includes the
91 * index values and the column number.
93 NETSNMP_INLINE netsnmp_table_request_info *
94 netsnmp_extract_table_info(netsnmp_request_info *request)
96 return (netsnmp_table_request_info *)
97 netsnmp_request_get_list_data(request, TABLE_HANDLER_NAME);
100 /** extracts the registered netsnmp_table_registration_info object from a
101 * netsnmp_handler_registration object */
102 netsnmp_table_registration_info *
103 netsnmp_find_table_registration_info(netsnmp_handler_registration *reginfo)
105 return (netsnmp_table_registration_info *)
106 netsnmp_find_handler_data_by_name(reginfo, TABLE_HANDLER_NAME);
109 /** implements the table helper handler */
111 table_helper_handler(netsnmp_mib_handler *handler,
112 netsnmp_handler_registration *reginfo,
113 netsnmp_agent_request_info *reqinfo,
114 netsnmp_request_info *requests)
117 netsnmp_request_info *request;
118 netsnmp_table_registration_info *tbl_info;
120 unsigned int oid_column_pos;
121 unsigned int tmp_idx, tmp_len;
122 int incomplete, out_of_range, cleaned_up = 0;
123 int status = SNMP_ERR_NOERROR, need_processing = 0;
125 netsnmp_table_request_info *tbl_req_info;
126 netsnmp_variable_list *vb;
128 if (!reginfo || !handler)
129 return SNMPERR_GENERR;
131 oid_index_pos = reginfo->rootoid_len + 2;
132 oid_column_pos = reginfo->rootoid_len + 1;
133 tbl_info = (netsnmp_table_registration_info *) handler->myvoid;
135 if ((!handler->myvoid) || (!tbl_info->indexes)) {
136 snmp_log(LOG_INFO, "improperly registered table found\n");
139 * XXX-rks: unregister table?
141 return SNMP_ERR_GENERR;
144 DEBUGMSGTL(("helper:table", "Got request for handler %s: base oid:",
145 handler->handler_name));
146 DEBUGMSGOID(("helper:table", reginfo->rootoid, reginfo->rootoid_len));
147 DEBUGMSG(("helper:table", "\n"));
150 * if the agent request info has a state reference, then this is a
151 * later pass of a set request and we can skip all the lookup stuff.
153 * xxx-rks: this might break for handlers which only handle one varbind
154 * at a time... those handlers should not save data by their handler_name
155 * in the netsnmp_agent_request_info.
157 if (netsnmp_agent_get_list_data(reqinfo, handler->next->handler_name)) {
158 if (MODE_IS_SET(reqinfo->mode)) {
159 return netsnmp_call_next_handler(handler, reginfo, reqinfo,
162 /** XXX-rks: memory leak. add cleanup handler? */
163 netsnmp_free_agent_data_sets(reqinfo);
167 if ( MODE_IS_SET(reqinfo->mode) &&
168 (reqinfo->mode != MODE_SET_RESERVE1)) {
170 * for later set modes, we can skip all the index parsing,
171 * and we always need to let child handlers have a chance
178 * for RESERVE1 and GETS, only continue if we have at least
183 * loop through requests
186 for (request = requests; request; request = request->next) {
187 netsnmp_variable_list *var = request->requestvb;
189 DEBUGMSGOID(("verbose:table", var->name, var->name_length));
190 DEBUGMSG(("verbose:table", "\n"));
192 if (request->processed) {
193 DEBUGMSG(("helper:table", "already processed\n"));
196 netsnmp_assert(request->status == SNMP_ERR_NOERROR);
199 * this should probably be handled further up
201 if ((reqinfo->mode == MODE_GET) && (var->type != ASN_NULL)) {
203 * valid request if ASN_NULL
205 DEBUGMSGTL(("helper:table",
206 " GET var type is not ASN_NULL\n"));
207 netsnmp_set_request_error(reqinfo, request,
212 if (reqinfo->mode == MODE_SET_RESERVE1) {
213 DEBUGIF("helper:table") {
215 size_t buf_len = 0, out_len = 0;
216 DEBUGMSGTL(("helper:table", " SET_REQUEST for OID: "));
217 DEBUGMSGOID(("helper:table", var->name, var->name_length));
219 if (sprint_realloc_by_type(&buf, &buf_len, &out_len, 1,
221 DEBUGMSG(("helper:table"," type=%d(%02x), value=%s\n",
222 var->type, var->type, buf));
225 DEBUGMSG(("helper:table",
226 " type=%d(%02x), value=%s [TRUNCATED]\n",
227 var->type, var->type, buf));
229 DEBUGMSG(("helper:table",
230 " type=%d(%02x), value=[NIL] [TRUNCATED]\n",
231 var->type, var->type));
241 * check to make sure its in table range
246 * if our root oid is > var->name and this is not a GETNEXT,
247 * then the oid is out of range. (only compare up to shorter
250 if (reginfo->rootoid_len > var->name_length)
251 tmp_len = var->name_length;
253 tmp_len = reginfo->rootoid_len;
254 if (snmp_oid_compare(reginfo->rootoid, reginfo->rootoid_len,
255 var->name, tmp_len) > 0) {
256 if (reqinfo->mode == MODE_GETNEXT) {
257 if (var->name != var->name_loc)
259 snmp_set_var_objid(var, reginfo->rootoid,
260 reginfo->rootoid_len);
262 DEBUGMSGTL(("helper:table", " oid is out of range.\n"));
267 * if var->name is longer than the root, make sure it is
268 * table.1 (table.ENTRY).
270 else if ((var->name_length > reginfo->rootoid_len) &&
271 (var->name[reginfo->rootoid_len] != 1)) {
272 if ((var->name[reginfo->rootoid_len] < 1) &&
273 (reqinfo->mode == MODE_GETNEXT)) {
274 var->name[reginfo->rootoid_len] = 1;
275 var->name_length = reginfo->rootoid_len;
278 DEBUGMSGTL(("helper:table", " oid is out of range.\n"));
282 * if it is not in range, then mark it in the request list
283 * because we can't process it. If the request is not a GETNEXT
284 * then set the error to NOSUCHOBJECT so nobody else wastes time
285 * trying to process it.
288 DEBUGMSGTL(("helper:table", " Not processed: "));
289 DEBUGMSGOID(("helper:table", var->name, var->name_length));
290 DEBUGMSG(("helper:table", "\n"));
293 * Reject requests of the form 'myTable.N' (N != 1)
295 if (reqinfo->mode != MODE_GETNEXT) {
296 table_helper_cleanup(reqinfo, request,
304 * Check column ranges; set-up to pull out indexes from OID.
308 tbl_req_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_request_info);
309 tbl_req_info->reg_info = tbl_info;
310 tbl_req_info->indexes = snmp_clone_varbind(tbl_info->indexes);
311 tbl_req_info->number_indexes = 0; /* none yet */
312 netsnmp_request_add_list_data(request,
313 netsnmp_create_data_list
315 (void *) tbl_req_info,
316 table_data_free_func));
318 if (var->name_length > oid_column_pos) {
320 * oid is long enough to contain index info
322 if (var->name[oid_column_pos] < tbl_info->min_column) {
323 if (reqinfo->mode == MODE_GETNEXT) {
325 * fix column, truncate useless index info
327 var->name_length = oid_column_pos;
328 tbl_req_info->colnum = tbl_info->min_column;
331 } else if (var->name[oid_column_pos] > tbl_info->max_column)
336 * this is out of range... remove from requests, free
339 DEBUGMSGTL(("helper:table",
340 " oid is out of range. Not processed: "));
341 DEBUGMSGOID(("helper:table", var->name, var->name_length));
342 DEBUGMSG(("helper:table", "\n"));
345 * Reject requests of the form 'myEntry.N' (invalid N)
347 if (reqinfo->mode != MODE_GETNEXT) {
348 table_helper_cleanup(reqinfo, request,
354 * use column verification
356 else if (tbl_info->valid_columns) {
357 tbl_req_info->colnum =
358 netsnmp_closest_column(var->name[oid_column_pos],
359 tbl_info->valid_columns);
360 if (tbl_req_info->colnum == 0)
362 if (tbl_req_info->colnum != var->name[oid_column_pos]) {
364 * different column! truncate useless index info
366 var->name_length = oid_column_pos;
370 * var->name_length may have changed - check again
372 if (var->name_length <= oid_column_pos) { /** none available */
373 tbl_req_info->index_oid_len = 0;
375 tbl_req_info->colnum = var->name[oid_column_pos];
376 tbl_req_info->index_oid_len =
377 var->name_length - oid_index_pos;
378 netsnmp_assert(tbl_req_info->index_oid_len < MAX_OID_LEN);
379 memcpy(tbl_req_info->index_oid, &var->name[oid_index_pos],
380 tbl_req_info->index_oid_len * sizeof(oid));
381 tmp_name = tbl_req_info->index_oid;
383 } else if (reqinfo->mode != MODE_GETNEXT) {
385 * oid is NOT long enough to contain index info, and this is
386 * NOT a GETNEXT, so we can't do anything with it.
388 * Reject requests of the form 'myTable' or 'myEntry'
390 table_helper_cleanup(reqinfo, request, SNMP_NOSUCHOBJECT);
394 * oid is NOT long enough to contain column or index info, so start
395 * at the minimum column. Set index oid len to 0 because we don't
396 * have any index info in the OID.
398 tbl_req_info->index_oid_len = 0;
399 tbl_req_info->colnum = tbl_info->min_column;
403 * set up tmp_len to be the number of OIDs we have beyond the column;
404 * these should be the index(s) for the table. If the index_oid_len
405 * is 0, set tmp_len to -1 so that when we try to parse the index below,
406 * we just zero fill everything.
408 if (tbl_req_info->index_oid_len == 0) {
412 tmp_len = tbl_req_info->index_oid_len;
416 * for each index type, try to extract the index from var->name
419 for (tmp_idx = 0, vb = tbl_req_info->indexes;
420 tmp_idx < tbl_info->number_indexes;
421 ++tmp_idx, vb = vb->next_variable) {
422 if (incomplete && tmp_len) {
424 * incomplete/illegal OID, set up dummy 0 to parse
426 DEBUGMSGTL(("helper:table",
427 " oid indexes not complete: "));
428 DEBUGMSGOID(("helper:table", var->name, var->name_length));
429 DEBUGMSG(("helper:table", "\n"));
432 * no sense in trying anymore if this is a GET/SET.
434 * Reject requests of the form 'myObject' (no instance)
436 if (reqinfo->mode != MODE_GETNEXT) {
437 table_helper_cleanup(reqinfo, requests,
438 SNMP_NOSUCHINSTANCE);
442 tmp_name = (oid *) & tmp_len;
446 * try and parse current index
448 if (parse_one_oid_index(&tmp_name, &tmp_len,
449 vb, 1) != SNMPERR_SUCCESS) {
451 tmp_len = -1; /* is this necessary? Better safe than
455 * do not count incomplete indexes
459 ++tbl_req_info->number_indexes; /** got one ok */
462 tmp_len = -1; /* is this necessary? Better safe
468 DEBUGIF("helper:table") {
472 size_t buf_len = 0, out_len = 0;
473 DEBUGMSGTL(("helper:table", " column: %d, indexes: %d",
474 tbl_req_info->colnum,
475 tbl_req_info->number_indexes));
476 for (vb = tbl_req_info->indexes, count = 0;
477 vb && count < tbl_info->number_indexes;
478 count++, vb = vb->next_variable) {
480 if (sprint_realloc_by_type(&buf, &buf_len, &out_len, 1,
482 DEBUGMSG(("helper:table",
483 " index: type=%d(%02x), value=%s",
484 vb->type, vb->type, buf));
487 DEBUGMSG(("helper:table",
488 " index: type=%d(%02x), value=%s [TRUNCATED]",
489 vb->type, vb->type, buf));
491 DEBUGMSG(("helper:table",
492 " index: type=%d(%02x), value=[NIL] [TRUNCATED]",
493 vb->type, vb->type));
500 DEBUGMSG(("helper:table", "\n"));
506 * do we have sufficent index info to continue?
509 if ((reqinfo->mode != MODE_GETNEXT) &&
510 ((tbl_req_info->number_indexes != tbl_info->number_indexes) ||
512 table_helper_cleanup(reqinfo, request, SNMP_NOSUCHINSTANCE);
515 netsnmp_assert(request->status == SNMP_ERR_NOERROR);
519 } /* for each request */
523 * * call our child access function
527 netsnmp_call_next_handler(handler, reginfo, reqinfo, requests);
532 /** Builds the result to be returned to the agent given the table information.
533 * Use this function to return results from lowel level handlers to
534 * the agent. It takes care of building the proper resulting oid
535 * (containing proper indexing) and inserts the result value into the
539 netsnmp_table_build_result(netsnmp_handler_registration *reginfo,
540 netsnmp_request_info *reqinfo,
541 netsnmp_table_request_info *table_info,
542 u_char type, u_char * result, size_t result_len)
545 netsnmp_variable_list *var;
547 if (!reqinfo || !table_info)
548 return SNMPERR_GENERR;
550 var = reqinfo->requestvb;
552 if (var->name != var->name_loc)
556 if (netsnmp_table_build_oid(reginfo, reqinfo, table_info) !=
558 return SNMPERR_GENERR;
560 snmp_set_var_typed_value(var, type, result, result_len);
562 return SNMPERR_SUCCESS;
566 /** given a registration info object, a request object and the table
567 * info object it builds the request->requestvb->name oid from the
568 * index values and column information found in the table_info
572 netsnmp_table_build_oid(netsnmp_handler_registration *reginfo,
573 netsnmp_request_info *reqinfo,
574 netsnmp_table_request_info *table_info)
576 oid tmpoid[MAX_OID_LEN];
577 netsnmp_variable_list *var;
579 if (!reginfo || !reqinfo || !table_info)
580 return SNMPERR_GENERR;
582 memcpy(tmpoid, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid));
583 tmpoid[reginfo->rootoid_len] = 1; /** .Entry */
584 tmpoid[reginfo->rootoid_len + 1] = table_info->colnum; /** .column */
586 var = reqinfo->requestvb;
587 if (build_oid(&var->name, &var->name_length,
588 tmpoid, reginfo->rootoid_len + 2, table_info->indexes)
590 return SNMPERR_GENERR;
592 return SNMPERR_SUCCESS;
595 /** Builds an oid from index information.
598 netsnmp_table_build_oid_from_index(netsnmp_handler_registration *reginfo,
599 netsnmp_request_info *reqinfo,
600 netsnmp_table_request_info *table_info)
602 oid tmpoid[MAX_OID_LEN];
603 netsnmp_variable_list *var;
606 if (!reginfo || !reqinfo || !table_info)
607 return SNMPERR_GENERR;
609 var = reqinfo->requestvb;
610 len = reginfo->rootoid_len;
611 memcpy(tmpoid, reginfo->rootoid, len * sizeof(oid));
612 tmpoid[len++] = 1; /* .Entry */
613 tmpoid[len++] = table_info->colnum; /* .column */
614 memcpy(&tmpoid[len], table_info->index_oid,
615 table_info->index_oid_len * sizeof(oid));
616 len += table_info->index_oid_len;
617 snmp_clone_mem((void **) &var->name, tmpoid, len * sizeof(oid));
618 var->name_length = len;
620 return SNMPERR_SUCCESS;
623 /** parses an OID into table indexses */
625 netsnmp_update_variable_list_from_index(netsnmp_table_request_info *tri)
628 return SNMPERR_GENERR;
630 return parse_oid_indexes(tri->index_oid, tri->index_oid_len,
634 /** builds an oid given a set of indexes. */
636 netsnmp_update_indexes_from_variable_list(netsnmp_table_request_info *tri)
639 return SNMPERR_GENERR;
641 return build_oid_noalloc(tri->index_oid, sizeof(tri->index_oid),
642 &tri->index_oid_len, NULL, 0, tri->indexes);
646 * checks the original request against the current data being passed in if
647 * its greater than the request oid but less than the current valid
648 * return, set the current valid return to the new value.
650 * returns 1 if outvar was replaced with the oid from newvar (success).
654 netsnmp_check_getnext_reply(netsnmp_request_info *request,
657 netsnmp_variable_list * newvar,
658 netsnmp_variable_list ** outvar)
660 oid myname[MAX_OID_LEN];
663 build_oid_noalloc(myname, MAX_OID_LEN, &myname_len,
664 prefix, prefix_len, newvar);
666 * is the build of the new indexes less than our current result
668 if ((!(*outvar) || snmp_oid_compare(myname + prefix_len,
669 myname_len - prefix_len,
670 (*outvar)->name + prefix_len,
671 (*outvar)->name_length -
674 * and greater than the requested oid
676 if (snmp_oid_compare(myname, myname_len,
677 request->requestvb->name,
678 request->requestvb->name_length) > 0) {
680 * the new result must be better than the old
683 *outvar = snmp_clone_varbind(newvar);
684 snmp_set_var_objid(*outvar, myname, myname_len);
698 table_data_free_func(void *data)
700 netsnmp_table_request_info *info = (netsnmp_table_request_info *) data;
703 snmp_free_varbind(info->indexes);
710 table_helper_cleanup(netsnmp_agent_request_info *reqinfo,
711 netsnmp_request_info *request, int status)
713 netsnmp_set_request_error(reqinfo, request, status);
714 netsnmp_free_request_data_sets(request);
717 request->parent_data = NULL;
722 netsnmp_closest_column(unsigned int current,
723 netsnmp_column_info *valid_columns)
725 unsigned int closest = 0;
729 if (valid_columns == NULL)
734 if (valid_columns->isRange) {
736 if (current < valid_columns->details.range[0]) {
737 if (valid_columns->details.range[0] < closest) {
738 closest = valid_columns->details.range[0];
740 } else if (current <= valid_columns->details.range[1]) {
742 done = 1; /* can not get any closer! */
748 if (current < valid_columns->details.list[0]) {
749 if (valid_columns->details.list[0] < closest)
750 closest = valid_columns->details.list[0];
755 valid_columns->details.list[(int)valid_columns->list_count])
756 continue; /* not in list range. */
758 for (idx = 0; idx < (int)valid_columns->list_count; ++idx) {
759 if (current == valid_columns->details.list[idx]) {
761 done = 1; /* can not get any closer! */
763 } else if (current < valid_columns->details.list[idx]) {
764 if (valid_columns->details.list[idx] < closest)
765 closest = valid_columns->details.list[idx];
766 break; /* list should be sorted */
772 valid_columns = valid_columns->next;
774 } while (!done && valid_columns);
781 netsnmp_table_helper_add_indexes(netsnmp_table_registration_info *tinfo,
784 netsnmp_table_helper_add_indexes(va_alist)
792 va_start(debugargs, tinfo);
794 netsnmp_table_registration_info *tinfo;
797 tinfo = va_arg(debugargs, table_info *);
800 while ((type = va_arg(debugargs, int)) != 0) {
801 netsnmp_table_helper_add_index(tinfo, type);
807 /** returns a row-wide place to store data in.
808 @todo This function will likely change to add free pointer functions. */
809 netsnmp_oid_stash_node **
810 netsnmp_table_get_or_create_row_stash(netsnmp_agent_request_info *reqinfo,
811 const u_char * storage_name)
813 netsnmp_oid_stash_node **stashp = NULL;
814 stashp = (netsnmp_oid_stash_node **)
815 netsnmp_agent_get_list_data(reqinfo, storage_name);
819 * hasn't be created yet. we create it here.
821 stashp = SNMP_MALLOC_TYPEDEF(netsnmp_oid_stash_node *);
824 return NULL; /* ack. out of mem */
826 netsnmp_agent_add_list_data(reqinfo,
830 netsnmp_create_data_list(storage_name,