3
* $Id: table_array.c,v 5.19.2.2 2004/03/14 06:05:39 rstory Exp $
6
#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/agent/table_array.h>
19
#include <net-snmp/library/container.h>
20
#include <net-snmp/library/snmp_assert.h>
27
* snmp.h:#define SNMP_MSG_INTERNAL_SET_BEGIN -1
28
* snmp.h:#define SNMP_MSG_INTERNAL_SET_RESERVE1 0
29
* snmp.h:#define SNMP_MSG_INTERNAL_SET_RESERVE2 1
30
* snmp.h:#define SNMP_MSG_INTERNAL_SET_ACTION 2
31
* snmp.h:#define SNMP_MSG_INTERNAL_SET_COMMIT 3
32
* snmp.h:#define SNMP_MSG_INTERNAL_SET_FREE 4
33
* snmp.h:#define SNMP_MSG_INTERNAL_SET_UNDO 5
36
static const char *mode_name[] = {
46
* PRIVATE structure for holding important info for each table.
48
typedef struct table_container_data_s {
50
/** registration info for the table */
51
netsnmp_table_registration_info *tblreg_info;
53
/** container for the table rows */
54
netsnmp_container *table;
60
/** do we want to group rows with the same index
61
* together when calling callbacks? */
64
/** callbacks for this table */
65
netsnmp_table_array_callbacks *cb;
67
} table_container_data;
69
/** @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.
72
* The table_array handler is used (automatically) in conjuntion
73
* with the @link table table@endlink handler. It is primarily
74
* intended to be used with the mib2c configuration file
75
* mib2c.array-user.conf.
77
* The code generated by mib2c is useful when you have control of
78
* the data for each row. If you cannot control when rows are added
79
* and deleted (or at least be notified of changes to row data),
80
* then this handler is probably not for you.
82
* This handler makes use of callbacks (function pointers) to
83
* handle various tasks. Code is generated for each callback,
84
* but will need to be reviewed and flushed out by the user.
86
* NOTE NOTE NOTE: Once place where mib2c is somewhat lacking
87
* is with regards to tables with external indices. If your
88
* table makes use of one or more external indices, please
89
* review the generated code very carefully for comments
90
* regarding external indices.
92
* NOTE NOTE NOTE: This helper, the API and callbacks are still
93
* being tested and may change.
95
* The generated code will define a structure for storage of table
96
* related data. This structure must be used, as it contains the index
97
* OID for the row, which is used for keeping the array sorted. You can
98
* add addition fields or data to the structure for your own use.
100
* The generated code will also have code to handle SNMP-SET processing.
101
* If your table does not support any SET operations, simply comment
102
* out the #define <PREFIX>_SET_HANDLING (where <PREFIX> is your
103
* table name) in the header file.
105
* SET processing modifies the row in-place. The duplicate_row
106
* callback will be called to save a copy of the original row.
107
* In the event of a failure before the commite phase, the
108
* row_copy callback will be called to restore the original row
111
* Code will be generated to handle row creation. This code may be
112
* disabled by commenting out the #define <PREFIX>_ROW_CREATION
113
* in the header file.
115
* If your table contains a RowStatus object, by default the
116
* code will not allow object in an active row to be modified.
117
* To allow active rows to be modified, remove the comment block
118
* around the #define <PREFIX>_CAN_MODIFY_ACTIVE_ROW in the header
121
* Code will be generated to maintain a secondary index for all
122
* rows, stored in a binary tree. This is very useful for finding
123
* rows by a key other than the OID index. By default, the functions
124
* for maintaining this tree will be based on a character string.
125
* NOTE: this will likely be made into a more generic mechanism,
126
* using new callback methods, in the near future.
128
* The generated code contains many TODO comments. Make sure you
129
* check each one to see if it applies to your code. Examples include
130
* checking indices for syntax (ranges, etc), initializing default
131
* values in newly created rows, checking for row activation and
132
* deactivation requirements, etc.
137
/**********************************************************************
138
**********************************************************************
141
* PUBLIC Registration functions *
144
**********************************************************************
145
**********************************************************************/
146
/** register specified callbacks for the specified table/oid. If the
147
group_rows parameter is set, the row related callbacks will be
148
called once for each unique row index. Otherwise, each callback
149
will be called only once, for all objects.
152
netsnmp_table_container_register(netsnmp_handler_registration *reginfo,
153
netsnmp_table_registration_info *tabreg,
154
netsnmp_table_array_callbacks *cb,
155
netsnmp_container *container,
158
table_container_data *tad = SNMP_MALLOC_TYPEDEF(table_container_data);
160
return SNMPERR_GENERR;
161
tad->tblreg_info = tabreg; /* we need it too, but it really is not ours */
164
snmp_log(LOG_ERR, "table_array registration with no callbacks\n" );
165
return SNMPERR_GENERR;
168
* check for required callbacks
171
((NULL==cb->duplicate_row) || (NULL==cb->delete_row) ||
172
(NULL==cb->row_copy)) )) {
173
snmp_log(LOG_ERR, "table_array registration with incomplete "
174
"callback structure.\n");
175
return SNMPERR_GENERR;
179
tad->table = netsnmp_container_find("table_array");
181
tad->table = container;
182
if (NULL==container->compare)
183
container->compare = netsnmp_compare_netsnmp_index;
184
if (NULL==container->ncompare)
185
container->ncompare = netsnmp_ncompare_netsnmp_index;
189
reginfo->handler->myvoid = tad;
191
return netsnmp_register_table(reginfo, tabreg);
194
/** find the handler for the table_array helper. */
195
netsnmp_mib_handler *
196
netsnmp_find_table_array_handler(netsnmp_handler_registration *reginfo)
198
netsnmp_mib_handler *mh;
201
mh = reginfo->handler;
203
if (mh->access_method == netsnmp_table_array_helper_handler)
211
/** find the context data used by the table_array helper */
213
netsnmp_extract_array_context(netsnmp_request_info *request)
215
return netsnmp_request_get_list_data(request, TABLE_ARRAY_NAME);
218
/** this function is called to validate RowStatus transitions. */
220
netsnmp_table_array_check_row_status(netsnmp_table_array_callbacks *cb,
221
netsnmp_request_group *ag,
222
long *rs_new, long *rs_old)
224
netsnmp_index *row_ctx;
225
netsnmp_index *undo_ctx;
227
return SNMPERR_GENERR;
228
row_ctx = ag->existing_row;
229
undo_ctx = ag->undo_info;
232
* xxx-rks: revisit row delete scenario
236
* either a new row, or change to old row
239
* is it set to active?
241
if (RS_IS_GOING_ACTIVE(*rs_new)) {
243
* is it ready to be active?
245
if ((NULL==cb->can_activate) ||
246
cb->can_activate(undo_ctx, row_ctx, ag))
249
return SNMP_ERR_INCONSISTENTVALUE;
258
if (RS_IS_ACTIVE(*rs_old)) {
260
* check pre-reqs for deactivation
262
if (cb->can_deactivate &&
263
!cb->can_deactivate(undo_ctx, row_ctx, ag)) {
264
return SNMP_ERR_INCONSISTENTVALUE;
273
if (*rs_new != RS_DESTROY) {
274
if ((NULL==cb->can_activate) ||
275
cb->can_activate(undo_ctx, row_ctx, ag))
276
*rs_new = RS_NOTINSERVICE;
278
*rs_new = RS_NOTREADY;
280
if (cb->can_delete && !cb->can_delete(undo_ctx, row_ctx, ag)) {
281
return SNMP_ERR_INCONSISTENTVALUE;
288
* check pre-reqs for delete row
290
if (cb->can_delete && !cb->can_delete(undo_ctx, row_ctx, ag)) {
291
return SNMP_ERR_INCONSISTENTVALUE;
295
return SNMP_ERR_NOERROR;
300
#ifndef DOXYGEN_SHOULD_SKIP_THIS
301
/**********************************************************************
302
**********************************************************************
303
**********************************************************************
304
**********************************************************************
309
* EVERYTHING BELOW THIS IS PRIVATE IMPLEMENTATION DETAILS. *
314
**********************************************************************
315
**********************************************************************
316
**********************************************************************
317
**********************************************************************/
319
/**********************************************************************
320
**********************************************************************
323
* Structures, Utility/convenience functions *
326
**********************************************************************
327
**********************************************************************/
329
* context info for SET requests
331
typedef struct set_context_s {
332
netsnmp_agent_request_info *agtreq_info;
333
table_container_data *tad;
338
release_netsnmp_request_group(netsnmp_index *g, void *v)
340
netsnmp_request_group_item *tmp;
341
netsnmp_request_group *group = (netsnmp_request_group *) g;
345
while (group->list) {
347
group->list = tmp->next;
355
release_netsnmp_request_groups(void *vp)
357
netsnmp_container *c = (netsnmp_container*)vp;
358
CONTAINER_FOR_EACH(c, (netsnmp_container_obj_func*)
359
release_netsnmp_request_group, NULL);
364
build_new_oid(netsnmp_handler_registration *reginfo,
365
netsnmp_table_request_info *tblreq_info,
366
netsnmp_index *row, netsnmp_request_info *current)
368
oid coloid[MAX_OID_LEN];
371
if (!tblreq_info || !reginfo || !row || !current)
374
coloid_len = reginfo->rootoid_len + 2;
375
memcpy(coloid, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid));
378
coloid[reginfo->rootoid_len] = 1;
380
/** table.entry.column */
381
coloid[reginfo->rootoid_len + 1] = tblreq_info->colnum;
383
/** table.entry.column.index */
384
memcpy(&coloid[reginfo->rootoid_len + 2], row->oids,
385
row->len * sizeof(oid));
387
snmp_set_var_objid(current->requestvb, coloid,
388
reginfo->rootoid_len + 2 + row->len);
391
/**********************************************************************
392
**********************************************************************
395
* GET procession functions *
398
**********************************************************************
399
**********************************************************************/
401
process_get_requests(netsnmp_handler_registration *reginfo,
402
netsnmp_agent_request_info *agtreq_info,
403
netsnmp_request_info *requests,
404
table_container_data * tad)
406
int rc = SNMP_ERR_NOERROR;
407
netsnmp_request_info *current;
408
netsnmp_index *row = NULL;
409
netsnmp_table_request_info *tblreq_info;
410
netsnmp_variable_list *var;
413
* Loop through each of the requests, and
414
* try to find the appropriate row from the container.
416
for (current = requests; current; current = current->next) {
418
var = current->requestvb;
419
DEBUGMSGTL(("table_array:get",
420
" process_get_request oid:"));
421
DEBUGMSGOID(("table_array:get", var->name,
423
DEBUGMSG(("table_array:get", "\n"));
426
* skip anything that doesn't need processing.
428
if (current->processed != 0) {
429
DEBUGMSGTL(("table_array:get", "already processed\n"));
434
* Get pointer to the table information for this request. This
435
* information was saved by table_helper_handler. When
436
* debugging, we double check a few assumptions. For example,
437
* the table_helper_handler should enforce column boundaries.
439
tblreq_info = netsnmp_extract_table_info(current);
440
netsnmp_assert(tblreq_info->colnum <= tad->tblreg_info->max_column);
442
if ((agtreq_info->mode == MODE_GETNEXT) ||
443
(agtreq_info->mode == MODE_GETBULK)) {
447
row = netsnmp_table_index_find_next_row(tad->table, tblreq_info);
452
* xxx-rks: how do we skip this entry for the next handler,
453
* but still allow it a chance to hit another handler?
455
DEBUGMSGTL(("table_array:get", "no row found\n"));
460
* * if data was found, make sure it has the column we want
462
/* xxx-rks: add suport for sparse tables */
467
build_new_oid(reginfo, tblreq_info, row, current);
469
} /** GETNEXT/GETBULK */
472
index.oids = tblreq_info->index_oid;
473
index.len = tblreq_info->index_oid_len;
475
row = CONTAINER_FIND(tad->table, &index);
477
DEBUGMSGTL(("table_array:get", "no row found\n"));
478
netsnmp_set_request_error(agtreq_info, current,
479
SNMP_NOSUCHINSTANCE);
487
rc = tad->cb->get_value(current, row, tblreq_info);
489
} /** for ( ... requests ... ) */
494
/**********************************************************************
495
**********************************************************************
498
* SET procession functions *
501
**********************************************************************
502
**********************************************************************/
505
group_requests(netsnmp_agent_request_info *agtreq_info,
506
netsnmp_request_info *requests,
507
netsnmp_container *request_group, table_container_data * tad)
509
netsnmp_table_request_info *tblreq_info;
510
netsnmp_variable_list *var;
511
netsnmp_index *row, *tmp, index;
512
netsnmp_request_info *current;
513
netsnmp_request_group *g;
514
netsnmp_request_group_item *i;
516
for (current = requests; current; current = current->next) {
518
var = current->requestvb;
521
* skip anything that doesn't need processing.
523
if (current->processed != 0) {
524
DEBUGMSGTL(("table_array:group",
525
"already processed\n"));
530
* 3.2.1 Setup and paranoia
532
* * Get pointer to the table information for this request. This
533
* * information was saved by table_helper_handler. When
534
* * debugging, we double check a few assumptions. For example,
535
* * the table_helper_handler should enforce column boundaries.
538
tblreq_info = netsnmp_extract_table_info(current);
539
netsnmp_assert(tblreq_info->colnum <= tad->tblreg_info->max_column);
544
index.oids = tblreq_info->index_oid;
545
index.len = tblreq_info->index_oid_len;
546
tmp = CONTAINER_FIND(request_group, &index);
548
DEBUGMSGTL(("table_array:group",
549
" existing group:"));
550
DEBUGMSGOID(("table_array:group", index.oids,
552
DEBUGMSG(("table_array:group", "\n"));
553
g = (netsnmp_request_group *) tmp;
554
i = SNMP_MALLOC_TYPEDEF(netsnmp_request_group_item);
556
i->tri = tblreq_info;
560
/** xxx-rks: store map of colnum to request */
564
DEBUGMSGTL(("table_array:group", " new group"));
565
DEBUGMSGOID(("table_array:group", index.oids,
567
DEBUGMSG(("table_array:group", "\n"));
568
g = SNMP_MALLOC_TYPEDEF(netsnmp_request_group);
569
i = SNMP_MALLOC_TYPEDEF(netsnmp_request_group_item);
571
g->table = tad->table;
573
i->tri = tblreq_info;
574
/** xxx-rks: store map of colnum to request */
577
* search for row. all changes are made to the original row,
578
* later, we'll make a copy in undo_info before we start processing.
580
row = g->existing_row = CONTAINER_FIND(tad->table, &index);
581
if (!g->existing_row) {
582
if (!tad->cb->create_row) {
583
if(MODE_IS_SET(agtreq_info->mode))
584
netsnmp_set_request_error(agtreq_info, current,
585
SNMP_ERR_NOTWRITABLE);
587
netsnmp_set_request_error(agtreq_info, current,
588
SNMP_NOSUCHINSTANCE);
593
/** use undo_info temporarily */
594
row = g->existing_row = tad->cb->create_row(&index);
596
/* xxx-rks : parameter to create_row to allow
597
* for better error reporting. */
598
netsnmp_set_request_error(agtreq_info, current,
607
g->index.oids = row->oids;
608
g->index.len = row->len;
610
CONTAINER_INSERT(request_group, g);
612
} /** for( current ... ) */
616
process_set_group(netsnmp_index *o, void *c)
618
/* xxx-rks: should we continue processing after an error?? */
619
set_context *context = (set_context *) c;
620
netsnmp_request_group *ag = (netsnmp_request_group *) o;
621
int rc = SNMP_ERR_NOERROR;
623
switch (context->agtreq_info->mode) {
625
case MODE_SET_RESERVE1:/** -> SET_RESERVE2 || SET_FREE */
628
* if not a new row, save undo info
630
if (ag->row_created == 0) {
631
ag->undo_info = context->tad->cb->duplicate_row(ag->existing_row);
632
if (NULL == ag->undo_info) {
633
rc = SNMP_ERR_RESOURCEUNAVAILABLE;
638
if (context->tad->cb->set_reserve1)
639
context->tad->cb->set_reserve1(ag);
642
case MODE_SET_RESERVE2:/** -> SET_ACTION || SET_FREE */
643
if (context->tad->cb->set_reserve2)
644
context->tad->cb->set_reserve2(ag);
647
case MODE_SET_ACTION:/** -> SET_COMMIT || SET_UNDO */
648
if (context->tad->cb->set_action)
649
context->tad->cb->set_action(ag);
652
case MODE_SET_COMMIT:/** FINAL CHANCE ON SUCCESS */
653
if (ag->row_created == 0) {
655
* this is an existing row, has it been deleted?
657
if (ag->row_deleted == 1) {
658
DEBUGMSGT((TABLE_ARRAY_NAME, "action: deleting row\n"));
659
if (CONTAINER_REMOVE(ag->table, ag->existing_row) != 0) {
660
rc = SNMP_ERR_COMMITFAILED;
664
} else if (ag->row_deleted == 0) {
666
* new row (that hasn't been deleted) should be inserted
668
DEBUGMSGT((TABLE_ARRAY_NAME, "action: inserting row\n"));
669
if (CONTAINER_INSERT(ag->table, ag->existing_row) != 0) {
670
rc = SNMP_ERR_COMMITFAILED;
675
if (context->tad->cb->set_commit)
676
context->tad->cb->set_commit(ag);
678
/** no more use for undo_info, so free it */
680
context->tad->cb->delete_row(ag->undo_info);
681
ag->undo_info = NULL;
685
/* XXX-rks: finish row cooperative notifications
686
* if the table has requested it, send cooperative notifications
687
* for row operations.
689
if (context->tad->notifications) {
691
if (!ag->existing_row)
692
netsnmp_monitor_notify(EVENT_ROW_DEL);
694
netsnmp_monitor_notify(EVENT_ROW_MOD);
697
netsnmp_monitor_notify(EVENT_ROW_ADD);
702
case MODE_SET_FREE:/** FINAL CHANCE ON FAILURE */
703
if (context->tad->cb->set_free)
704
context->tad->cb->set_free(ag);
706
/** no more use for undo_info, so free it */
707
if (ag->row_created == 1) {
708
context->tad->cb->delete_row(ag->existing_row);
709
ag->existing_row = NULL;
712
context->tad->cb->delete_row(ag->undo_info);
713
ag->undo_info = NULL;
717
case MODE_SET_UNDO:/** FINAL CHANCE ON FAILURE */
718
if (ag->row_created == 0) {
720
* this row existed before.
722
if (ag->row_deleted == 1) {
724
* re-insert undo_info
726
DEBUGMSGT((TABLE_ARRAY_NAME, "undo: re-inserting row\n"));
727
if (CONTAINER_INSERT(ag->table, ag->existing_row) != 0) {
728
rc = SNMP_ERR_UNDOFAILED;
732
} else if (ag->row_deleted == 0) {
734
* new row that wasn't deleted should be removed
736
DEBUGMSGT((TABLE_ARRAY_NAME, "undo: removing new row\n"));
737
if (CONTAINER_REMOVE(ag->table, ag->existing_row) != 0) {
738
rc = SNMP_ERR_UNDOFAILED;
744
* status already set - don't change it now
746
if (context->tad->cb->set_undo)
747
context->tad->cb->set_undo(ag);
750
* no more use for undo_info, so free it
752
if (ag->row_created == 0) {
756
context->tad->cb->row_copy(ag->existing_row, ag->undo_info);
757
context->tad->cb->delete_row(ag->undo_info);
758
ag->undo_info = NULL;
761
context->tad->cb->delete_row(ag->existing_row);
762
ag->existing_row = NULL;
767
snmp_log(LOG_ERR, "unknown mode processing SET for "
768
"netsnmp_table_array_helper_handler\n");
769
rc = SNMP_ERR_GENERR;
774
netsnmp_set_request_error(context->agtreq_info,
780
process_set_requests(netsnmp_agent_request_info *agtreq_info,
781
netsnmp_request_info *requests,
782
table_container_data * tad, char *handler_name)
785
netsnmp_container *request_group;
788
* create and save structure for set info
790
request_group = (netsnmp_container*) netsnmp_agent_get_list_data
791
(agtreq_info, handler_name);
792
if (request_group == NULL) {
793
netsnmp_data_list *tmp;
794
request_group = netsnmp_container_find("request_group:"
796
request_group->compare = netsnmp_compare_netsnmp_index;
797
request_group->ncompare = netsnmp_ncompare_netsnmp_index;
799
DEBUGMSGTL(("table_array", "Grouping requests by oid\n"));
801
tmp = netsnmp_create_data_list(handler_name,
803
release_netsnmp_request_groups);
804
netsnmp_agent_add_list_data(agtreq_info, tmp);
808
group_requests(agtreq_info, requests, request_group, tad);
812
* process each group one at a time
814
context.agtreq_info = agtreq_info;
816
context.status = SNMP_ERR_NOERROR;
817
CONTAINER_FOR_EACH(request_group,
818
(netsnmp_container_obj_func*)process_set_group,
821
return context.status;
825
/**********************************************************************
826
**********************************************************************
829
* netsnmp_table_array_helper_handler() *
832
**********************************************************************
833
**********************************************************************/
835
netsnmp_table_array_helper_handler(netsnmp_mib_handler *handler,
836
netsnmp_handler_registration *reginfo,
837
netsnmp_agent_request_info *agtreq_info,
838
netsnmp_request_info *requests)
842
* First off, get our pointer from the handler. This
843
* lets us get to the table registration information we
844
* saved in get_table_array_handler(), as well as the
845
* container where the actual table data is stored.
847
int rc = SNMP_ERR_NOERROR;
848
table_container_data *tad = (table_container_data *)handler->myvoid;
850
if (agtreq_info->mode < 0 || agtreq_info->mode > 5) {
851
DEBUGMSGTL(("table_array", "Mode %d, Got request:\n",
854
DEBUGMSGTL(("table_array", "Mode %s, Got request:\n",
855
mode_name[agtreq_info->mode]));
858
if (MODE_IS_SET(agtreq_info->mode)) {
860
* netsnmp_mutex_lock(&tad->lock);
862
rc = process_set_requests(agtreq_info, requests,
863
tad, handler->handler_name);
865
* netsnmp_mutex_unlock(&tad->lock);
868
rc = process_get_requests(reginfo, agtreq_info, requests, tad);
870
if (rc != SNMP_ERR_NOERROR) {
871
DEBUGMSGTL(("table_array", "processing returned rc %d\n", rc));
875
* Now we've done our processing. If there is another handler below us,
879
rc = netsnmp_call_next_handler(handler, reginfo, agtreq_info, requests);
880
if (rc != SNMP_ERR_NOERROR) {
881
DEBUGMSGTL(("table_array", "next handler returned rc %d\n", rc));
887
#endif /** DOXYGEN_SHOULD_SKIP_THIS */