1 /*
2 * Portions of this file are subject to the following copyright(s). See
3 * the Net-SNMP's COPYING file for more details and other copyrights
4 * that may apply:
5 *
6 * Portions of this file are copyrighted by:
7 * Copyright (c) 2016 VMware, Inc. All rights reserved.
8 * Use is subject to license terms specified in the COPYING file
9 * distributed with the Net-SNMP package.
10 */
11
12 #include <net-snmp/net-snmp-config.h>
13 #include <net-snmp/net-snmp-features.h>
14
15 #include <net-snmp/net-snmp-includes.h>
16 #include <net-snmp/agent/net-snmp-agent-includes.h>
17
18 #include <net-snmp/agent/row_merge.h>
19
20 #include <stdint.h>
21
22 #if HAVE_STRING_H
23 #include <string.h>
24 #else
25 #include <strings.h>
26 #endif
27
28 netsnmp_feature_provide(row_merge);
29 netsnmp_feature_child_of(row_merge, row_merge_all);
30 netsnmp_feature_child_of(row_merge_all, mib_helpers);
31
32
33 #ifndef NETSNMP_FEATURE_REMOVE_ROW_MERGE
34 /** @defgroup row_merge row_merge
35 * Calls sub handlers with request for one row at a time.
36 * @ingroup utilities
37 * This helper splits a whole bunch of requests into chunks based on the row
38 * index that they refer to, and passes all requests for a given row to the lower handlers.
39 * This is useful for handlers that don't want to process multiple rows at the
40 * same time, but are happy to iterate through the request list for a single row.
41 * @{
42 */
43
44 /** returns a row_merge handler that can be injected into a given
45 * handler chain.
46 */
47 netsnmp_mib_handler *
netsnmp_get_row_merge_handler(int prefix_len)48 netsnmp_get_row_merge_handler(int prefix_len)
49 {
50 netsnmp_mib_handler *ret = NULL;
51 ret = netsnmp_create_handler("row_merge",
52 netsnmp_row_merge_helper_handler);
53 if (ret) {
54 ret->myvoid = (void *)(intptr_t)prefix_len;
55 }
56 return ret;
57 }
58
59 /** functionally the same as calling netsnmp_register_handler() but also
60 * injects a row_merge handler at the same time for you. */
61 netsnmp_feature_child_of(register_row_merge, row_merge_all);
62 #ifndef NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE
63 int
netsnmp_register_row_merge(netsnmp_handler_registration * reginfo)64 netsnmp_register_row_merge(netsnmp_handler_registration *reginfo)
65 {
66 netsnmp_mib_handler *handler;
67
68 if (!reginfo)
69 return MIB_REGISTRATION_FAILED;
70
71 handler = netsnmp_get_row_merge_handler(reginfo->rootoid_len+1);
72 if (handler &&
73 (netsnmp_inject_handler(reginfo, handler) == SNMPERR_SUCCESS))
74 return netsnmp_register_handler(reginfo);
75
76 snmp_log(LOG_ERR, "failed to register row_merge\n");
77 netsnmp_handler_free(handler);
78 netsnmp_handler_registration_free(reginfo);
79
80 return MIB_REGISTRATION_FAILED;
81 }
82 #endif /* NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE */
83
84 static void
_rm_status_free(void * mem)85 _rm_status_free(void *mem)
86 {
87 netsnmp_row_merge_status *rm_status = (netsnmp_row_merge_status*)mem;
88
89 if (NULL != rm_status->saved_requests)
90 free(rm_status->saved_requests);
91
92 if (NULL != rm_status->saved_status)
93 free(rm_status->saved_status);
94
95 free(mem);
96 }
97
98
99 /** retrieve row_merge_status
100 */
101 netsnmp_row_merge_status *
netsnmp_row_merge_status_get(netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,int create_missing)102 netsnmp_row_merge_status_get(netsnmp_handler_registration *reginfo,
103 netsnmp_agent_request_info *reqinfo,
104 int create_missing)
105 {
106 netsnmp_row_merge_status *rm_status;
107 char buf[64];
108 int rc;
109
110 /*
111 * see if we've already been here
112 */
113 rc = snprintf(buf, sizeof(buf), "row_merge:%p", reginfo);
114 if ((-1 == rc) || ((size_t)rc >= sizeof(buf))) {
115 snmp_log(LOG_ERR,"error creating key\n");
116 return NULL;
117 }
118
119 rm_status = (netsnmp_row_merge_status*)netsnmp_agent_get_list_data(reqinfo, buf);
120 if ((NULL == rm_status) && create_missing) {
121 netsnmp_data_list *data_list;
122
123 rm_status = SNMP_MALLOC_TYPEDEF(netsnmp_row_merge_status);
124 if (NULL == rm_status) {
125 snmp_log(LOG_ERR,"error allocating memory\n");
126 return NULL;
127 }
128 data_list = netsnmp_create_data_list(buf, rm_status,
129 _rm_status_free);
130 if (NULL == data_list) {
131 free(rm_status);
132 return NULL;
133 }
134 netsnmp_agent_add_list_data(reqinfo, data_list);
135 }
136
137 return rm_status;
138 }
139
140 /** Determine if this is the first row
141 *
142 * returns 1 if this is the first row for this pass of the handler.
143 */
144 int
netsnmp_row_merge_status_first(netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo)145 netsnmp_row_merge_status_first(netsnmp_handler_registration *reginfo,
146 netsnmp_agent_request_info *reqinfo)
147 {
148 netsnmp_row_merge_status *rm_status;
149
150 /*
151 * find status
152 */
153 rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0);
154 if (NULL == rm_status)
155 return 0;
156
157 return (rm_status->count == 1) ? 1 : (rm_status->current == 1);
158 }
159
160 /** Determine if this is the last row
161 *
162 * returns 1 if this is the last row for this pass of the handler.
163 */
164 int
netsnmp_row_merge_status_last(netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo)165 netsnmp_row_merge_status_last(netsnmp_handler_registration *reginfo,
166 netsnmp_agent_request_info *reqinfo)
167 {
168 netsnmp_row_merge_status *rm_status;
169
170 /*
171 * find status
172 */
173 rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0);
174 if (NULL == rm_status)
175 return 0;
176
177 return (rm_status->count == 1) ? 1 :
178 (rm_status->current == rm_status->rows);
179 }
180
181
182 #define ROW_MERGE_WAITING 0
183 #define ROW_MERGE_ACTIVE 1
184 #define ROW_MERGE_DONE 2
185 #define ROW_MERGE_HEAD 3
186
187 /** Implements the row_merge handler */
188 int
netsnmp_row_merge_helper_handler(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)189 netsnmp_row_merge_helper_handler(netsnmp_mib_handler *handler,
190 netsnmp_handler_registration *reginfo,
191 netsnmp_agent_request_info *reqinfo,
192 netsnmp_request_info *requests)
193 {
194 netsnmp_request_info *request, **saved_requests;
195 char *saved_status;
196 netsnmp_row_merge_status *rm_status;
197 int i, j, ret, tail, count, final_rc = SNMP_ERR_NOERROR;
198
199 /*
200 * Use the prefix length as supplied during registration, rather
201 * than trying to second-guess what the MIB implementer wanted.
202 */
203 int SKIP_OID = (int)(intptr_t)handler->myvoid;
204
205 DEBUGMSGTL(("helper:row_merge", "Got request (%d): ", SKIP_OID));
206 DEBUGMSGOID(("helper:row_merge", reginfo->rootoid, reginfo->rootoid_len));
207 DEBUGMSG(("helper:row_merge", "\n"));
208
209 /*
210 * find or create status
211 */
212 rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 1);
213
214 /*
215 * Count the requests, and set up an array to keep
216 * track of the original order.
217 */
218 for (count = 0, request = requests; request; request = request->next) {
219 DEBUGIF("helper:row_merge") {
220 DEBUGMSGTL(("helper:row_merge", " got varbind: "));
221 DEBUGMSGOID(("helper:row_merge", request->requestvb->name,
222 request->requestvb->name_length));
223 DEBUGMSG(("helper:row_merge", "\n"));
224 }
225 count++;
226 }
227
228 /*
229 * Optimization: skip all this if there is just one request
230 */
231 if(count == 1) {
232 rm_status->count = count;
233 if (requests->processed)
234 return SNMP_ERR_NOERROR;
235 return netsnmp_call_next_handler(handler, reginfo, reqinfo, requests);
236 }
237
238 /*
239 * we really should only have to do this once, instead of every pass.
240 * as a precaution, we'll do it every time, but put in some asserts
241 * to see if we have to.
242 */
243 /*
244 * if the count changed, re-do everything
245 */
246 if ((0 != rm_status->count) && (rm_status->count != count)) {
247 /*
248 * ok, i know next/bulk can cause this condition. Probably
249 * GET, too. need to rethink this mode counting. maybe
250 * add the mode to the rm_status structure? xxx-rks
251 */
252 if ((reqinfo->mode != MODE_GET) &&
253 (reqinfo->mode != MODE_GETNEXT) &&
254 (reqinfo->mode != MODE_GETBULK)) {
255 netsnmp_assert((NULL != rm_status->saved_requests) &&
256 (NULL != rm_status->saved_status));
257 }
258 DEBUGMSGTL(("helper:row_merge", "count changed! do over...\n"));
259
260 SNMP_FREE(rm_status->saved_requests);
261 SNMP_FREE(rm_status->saved_status);
262
263 rm_status->count = 0;
264 rm_status->rows = 0;
265 }
266
267 if (0 == rm_status->count) {
268 /*
269 * allocate memory for saved structure
270 */
271 rm_status->saved_requests =
272 (netsnmp_request_info**)calloc(count+1,
273 sizeof(netsnmp_request_info*));
274 rm_status->saved_status = (char*)calloc(count,sizeof(char));
275 }
276
277 saved_status = rm_status->saved_status;
278 saved_requests = rm_status->saved_requests;
279
280 /*
281 * set up saved requests, and set any processed requests to done
282 */
283 i = 0;
284 for (request = requests; request; request = request->next, i++) {
285 if (request->processed) {
286 saved_status[i] = ROW_MERGE_DONE;
287 DEBUGMSGTL(("helper:row_merge", " skipping processed oid: "));
288 DEBUGMSGOID(("helper:row_merge", request->requestvb->name,
289 request->requestvb->name_length));
290 DEBUGMSG(("helper:row_merge", "\n"));
291 }
292 else
293 saved_status[i] = ROW_MERGE_WAITING;
294 if (0 != rm_status->count)
295 netsnmp_assert(saved_requests[i] == request);
296 saved_requests[i] = request;
297 saved_requests[i]->prev = NULL;
298 }
299 saved_requests[i] = NULL;
300
301 /*
302 * Note that saved_requests[count] is valid
303 * (because of the 'count+1' in the calloc above),
304 * but NULL (since it's past the end of the list).
305 * This simplifies the re-linking later.
306 */
307
308 /*
309 * Work through the (unprocessed) requests in order.
310 * For each of these, search the rest of the list for any
311 * matching indexes, and link them into a new list.
312 */
313 for (i=0; i<count; i++) {
314 if (saved_status[i] != ROW_MERGE_WAITING)
315 continue;
316
317 if (0 == rm_status->count)
318 rm_status->rows++;
319 DEBUGMSGTL(("helper:row_merge", " row %d oid[%d]: ", rm_status->rows, i));
320 DEBUGMSGOID(("helper:row_merge", saved_requests[i]->requestvb->name,
321 saved_requests[i]->requestvb->name_length));
322 DEBUGMSG(("helper:row_merge", "\n"));
323
324 saved_requests[i]->next = NULL;
325 saved_status[i] = ROW_MERGE_HEAD;
326 tail = i;
327 for (j=i+1; j<count; j++) {
328 if (saved_status[j] != ROW_MERGE_WAITING)
329 continue;
330
331 DEBUGMSGTL(("helper:row_merge", "? oid[%d]: ", j));
332 DEBUGMSGOID(("helper:row_merge",
333 saved_requests[j]->requestvb->name,
334 saved_requests[j]->requestvb->name_length));
335 if (!snmp_oid_compare(
336 saved_requests[i]->requestvb->name+SKIP_OID,
337 saved_requests[i]->requestvb->name_length-SKIP_OID,
338 saved_requests[j]->requestvb->name+SKIP_OID,
339 saved_requests[j]->requestvb->name_length-SKIP_OID)) {
340 DEBUGMSG(("helper:row_merge", " match\n"));
341 saved_requests[tail]->next = saved_requests[j];
342 saved_requests[j]->next = NULL;
343 saved_requests[j]->prev = saved_requests[tail];
344 saved_status[j] = ROW_MERGE_ACTIVE;
345 tail = j;
346 }
347 else
348 DEBUGMSG(("helper:row_merge", " no match\n"));
349 }
350 }
351
352 /*
353 * not that we have a list for each row, call next handler...
354 */
355 if (0 == rm_status->count)
356 rm_status->count = count;
357 rm_status->current = 0;
358 for (i=0; i<count; i++) {
359 if (saved_status[i] != ROW_MERGE_HEAD)
360 continue;
361
362 /*
363 * found the head of a new row,
364 * call the next handler with this list
365 */
366 rm_status->current++;
367 ret = netsnmp_call_next_handler(handler, reginfo, reqinfo,
368 saved_requests[i]);
369 if (ret != SNMP_ERR_NOERROR) {
370 snmp_log(LOG_WARNING,
371 "bad rc (%d) from next handler in row_merge\n", ret);
372 if (SNMP_ERR_NOERROR == final_rc)
373 final_rc = ret;
374 }
375 }
376
377 /*
378 * restore original linked list
379 */
380 for (i=0; i<count; i++) {
381 saved_requests[i]->next = saved_requests[i+1];
382 if (i>0)
383 saved_requests[i]->prev = saved_requests[i-1];
384 }
385
386 return final_rc;
387 }
388
389 /**
390 * initializes the row_merge helper which then registers a row_merge
391 * handler as a run-time injectable handler for configuration file
392 * use.
393 */
394 void
netsnmp_init_row_merge(void)395 netsnmp_init_row_merge(void)
396 {
397 netsnmp_register_handler_by_name("row_merge",
398 netsnmp_get_row_merge_handler(-1));
399 }
400 #else /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */
401 netsnmp_feature_unused(row_merge);
402 #endif /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */
403
404
405 /** @} */
406
407