/src/net-snmp/agent/helpers/row_merge.c
Line | Count | Source |
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 | | #ifdef 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 * |
48 | | netsnmp_get_row_merge_handler(int prefix_len) |
49 | 3.28k | { |
50 | 3.28k | netsnmp_mib_handler *ret = NULL; |
51 | 3.28k | ret = netsnmp_create_handler("row_merge", |
52 | 3.28k | netsnmp_row_merge_helper_handler); |
53 | 3.28k | if (ret) { |
54 | 3.28k | ret->myvoid = (void *)(intptr_t)prefix_len; |
55 | 3.28k | } |
56 | 3.28k | return ret; |
57 | 3.28k | } |
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 |
64 | | netsnmp_register_row_merge(netsnmp_handler_registration *reginfo) |
65 | 0 | { |
66 | 0 | netsnmp_mib_handler *handler; |
67 | |
|
68 | 0 | if (!reginfo) |
69 | 0 | return MIB_REGISTRATION_FAILED; |
70 | | |
71 | 0 | handler = netsnmp_get_row_merge_handler(reginfo->rootoid_len+1); |
72 | 0 | if (handler && |
73 | 0 | (netsnmp_inject_handler(reginfo, handler) == SNMPERR_SUCCESS)) |
74 | 0 | return netsnmp_register_handler(reginfo); |
75 | | |
76 | 0 | snmp_log(LOG_ERR, "failed to register row_merge\n"); |
77 | 0 | netsnmp_handler_free(handler); |
78 | 0 | netsnmp_handler_registration_free(reginfo); |
79 | |
|
80 | 0 | return MIB_REGISTRATION_FAILED; |
81 | 0 | } |
82 | | #endif /* NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE */ |
83 | | |
84 | | static void |
85 | | _rm_status_free(void *mem) |
86 | 0 | { |
87 | 0 | netsnmp_row_merge_status *rm_status = (netsnmp_row_merge_status*)mem; |
88 | |
|
89 | 0 | if (NULL != rm_status->saved_requests) |
90 | 0 | free(rm_status->saved_requests); |
91 | |
|
92 | 0 | if (NULL != rm_status->saved_status) |
93 | 0 | free(rm_status->saved_status); |
94 | |
|
95 | 0 | free(mem); |
96 | 0 | } |
97 | | |
98 | | |
99 | | /** retrieve row_merge_status |
100 | | */ |
101 | | netsnmp_row_merge_status * |
102 | | netsnmp_row_merge_status_get(netsnmp_handler_registration *reginfo, |
103 | | netsnmp_agent_request_info *reqinfo, |
104 | | int create_missing) |
105 | 0 | { |
106 | 0 | netsnmp_row_merge_status *rm_status; |
107 | 0 | char buf[64]; |
108 | 0 | int rc; |
109 | | |
110 | | /* |
111 | | * see if we've already been here |
112 | | */ |
113 | 0 | rc = snprintf(buf, sizeof(buf), "row_merge:%p", reginfo); |
114 | 0 | if ((-1 == rc) || ((size_t)rc >= sizeof(buf))) { |
115 | 0 | snmp_log(LOG_ERR,"error creating key\n"); |
116 | 0 | return NULL; |
117 | 0 | } |
118 | | |
119 | 0 | rm_status = (netsnmp_row_merge_status*)netsnmp_agent_get_list_data(reqinfo, buf); |
120 | 0 | if ((NULL == rm_status) && create_missing) { |
121 | 0 | netsnmp_data_list *data_list; |
122 | | |
123 | 0 | rm_status = SNMP_MALLOC_TYPEDEF(netsnmp_row_merge_status); |
124 | 0 | if (NULL == rm_status) { |
125 | 0 | snmp_log(LOG_ERR,"error allocating memory\n"); |
126 | 0 | return NULL; |
127 | 0 | } |
128 | 0 | data_list = netsnmp_create_data_list(buf, rm_status, |
129 | 0 | _rm_status_free); |
130 | 0 | if (NULL == data_list) { |
131 | 0 | free(rm_status); |
132 | 0 | return NULL; |
133 | 0 | } |
134 | 0 | netsnmp_agent_add_list_data(reqinfo, data_list); |
135 | 0 | } |
136 | | |
137 | 0 | return rm_status; |
138 | 0 | } |
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 |
145 | | netsnmp_row_merge_status_first(netsnmp_handler_registration *reginfo, |
146 | | netsnmp_agent_request_info *reqinfo) |
147 | 0 | { |
148 | 0 | netsnmp_row_merge_status *rm_status; |
149 | | |
150 | | /* |
151 | | * find status |
152 | | */ |
153 | 0 | rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0); |
154 | 0 | if (NULL == rm_status) |
155 | 0 | return 0; |
156 | | |
157 | 0 | return (rm_status->count == 1) ? 1 : (rm_status->current == 1); |
158 | 0 | } |
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 |
165 | | netsnmp_row_merge_status_last(netsnmp_handler_registration *reginfo, |
166 | | netsnmp_agent_request_info *reqinfo) |
167 | 0 | { |
168 | 0 | netsnmp_row_merge_status *rm_status; |
169 | | |
170 | | /* |
171 | | * find status |
172 | | */ |
173 | 0 | rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0); |
174 | 0 | if (NULL == rm_status) |
175 | 0 | return 0; |
176 | | |
177 | 0 | return (rm_status->count == 1) ? 1 : |
178 | 0 | (rm_status->current == rm_status->rows); |
179 | 0 | } |
180 | | |
181 | | |
182 | 0 | #define ROW_MERGE_WAITING 0 |
183 | 0 | #define ROW_MERGE_ACTIVE 1 |
184 | 0 | #define ROW_MERGE_DONE 2 |
185 | 0 | #define ROW_MERGE_HEAD 3 |
186 | | |
187 | | /** Implements the row_merge handler */ |
188 | | int |
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 | 0 | { |
194 | 0 | netsnmp_request_info *request, **saved_requests; |
195 | 0 | char *saved_status; |
196 | 0 | netsnmp_row_merge_status *rm_status; |
197 | 0 | 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 | 0 | int SKIP_OID = (int)(intptr_t)handler->myvoid; |
204 | |
|
205 | 0 | DEBUGMSGTL(("helper:row_merge", "Got request (%d): ", SKIP_OID)); |
206 | 0 | DEBUGMSGOID(("helper:row_merge", reginfo->rootoid, reginfo->rootoid_len)); |
207 | 0 | DEBUGMSG(("helper:row_merge", "\n")); |
208 | | |
209 | | /* |
210 | | * find or create status |
211 | | */ |
212 | 0 | 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 | 0 | for (count = 0, request = requests; request; request = request->next) { |
219 | 0 | DEBUGIF("helper:row_merge") { |
220 | 0 | DEBUGMSGTL(("helper:row_merge", " got varbind: ")); |
221 | 0 | DEBUGMSGOID(("helper:row_merge", request->requestvb->name, |
222 | 0 | request->requestvb->name_length)); |
223 | 0 | DEBUGMSG(("helper:row_merge", "\n")); |
224 | 0 | } |
225 | 0 | count++; |
226 | 0 | } |
227 | | |
228 | | /* |
229 | | * Optimization: skip all this if there is just one request |
230 | | */ |
231 | 0 | if(count == 1) { |
232 | 0 | rm_status->count = count; |
233 | 0 | if (requests->processed) |
234 | 0 | return SNMP_ERR_NOERROR; |
235 | 0 | return netsnmp_call_next_handler(handler, reginfo, reqinfo, requests); |
236 | 0 | } |
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 | 0 | 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 | 0 | if ((reqinfo->mode != MODE_GET) && |
253 | 0 | (reqinfo->mode != MODE_GETNEXT) && |
254 | 0 | (reqinfo->mode != MODE_GETBULK)) { |
255 | 0 | netsnmp_assert((NULL != rm_status->saved_requests) && |
256 | 0 | (NULL != rm_status->saved_status)); |
257 | 0 | } |
258 | 0 | DEBUGMSGTL(("helper:row_merge", "count changed! do over...\n")); |
259 | |
|
260 | 0 | SNMP_FREE(rm_status->saved_requests); |
261 | 0 | SNMP_FREE(rm_status->saved_status); |
262 | | |
263 | 0 | rm_status->count = 0; |
264 | 0 | rm_status->rows = 0; |
265 | 0 | } |
266 | |
|
267 | 0 | if (0 == rm_status->count) { |
268 | | /* |
269 | | * allocate memory for saved structure |
270 | | */ |
271 | 0 | rm_status->saved_requests = calloc(count+1, |
272 | 0 | sizeof(netsnmp_request_info*)); |
273 | 0 | rm_status->saved_status = calloc(count, sizeof(char)); |
274 | 0 | } |
275 | |
|
276 | 0 | saved_status = rm_status->saved_status; |
277 | 0 | saved_requests = rm_status->saved_requests; |
278 | | |
279 | | /* |
280 | | * set up saved requests, and set any processed requests to done |
281 | | */ |
282 | 0 | i = 0; |
283 | 0 | for (request = requests; request; request = request->next, i++) { |
284 | 0 | if (request->processed) { |
285 | 0 | saved_status[i] = ROW_MERGE_DONE; |
286 | 0 | DEBUGMSGTL(("helper:row_merge", " skipping processed oid: ")); |
287 | 0 | DEBUGMSGOID(("helper:row_merge", request->requestvb->name, |
288 | 0 | request->requestvb->name_length)); |
289 | 0 | DEBUGMSG(("helper:row_merge", "\n")); |
290 | 0 | } |
291 | 0 | else |
292 | 0 | saved_status[i] = ROW_MERGE_WAITING; |
293 | 0 | if (0 != rm_status->count) |
294 | 0 | netsnmp_assert(saved_requests[i] == request); |
295 | 0 | saved_requests[i] = request; |
296 | 0 | saved_requests[i]->prev = NULL; |
297 | 0 | } |
298 | 0 | saved_requests[i] = NULL; |
299 | | |
300 | | /* |
301 | | * Note that saved_requests[count] is valid |
302 | | * (because of the 'count+1' in the calloc above), |
303 | | * but NULL (since it's past the end of the list). |
304 | | * This simplifies the re-linking later. |
305 | | */ |
306 | | |
307 | | /* |
308 | | * Work through the (unprocessed) requests in order. |
309 | | * For each of these, search the rest of the list for any |
310 | | * matching indexes, and link them into a new list. |
311 | | */ |
312 | 0 | for (i=0; i<count; i++) { |
313 | 0 | if (saved_status[i] != ROW_MERGE_WAITING) |
314 | 0 | continue; |
315 | | |
316 | 0 | if (0 == rm_status->count) |
317 | 0 | rm_status->rows++; |
318 | 0 | DEBUGMSGTL(("helper:row_merge", " row %d oid[%d]: ", rm_status->rows, i)); |
319 | 0 | DEBUGMSGOID(("helper:row_merge", saved_requests[i]->requestvb->name, |
320 | 0 | saved_requests[i]->requestvb->name_length)); |
321 | 0 | DEBUGMSG(("helper:row_merge", "\n")); |
322 | |
|
323 | 0 | saved_requests[i]->next = NULL; |
324 | 0 | saved_status[i] = ROW_MERGE_HEAD; |
325 | 0 | tail = i; |
326 | 0 | for (j=i+1; j<count; j++) { |
327 | 0 | if (saved_status[j] != ROW_MERGE_WAITING) |
328 | 0 | continue; |
329 | | |
330 | 0 | DEBUGMSGTL(("helper:row_merge", "? oid[%d]: ", j)); |
331 | 0 | DEBUGMSGOID(("helper:row_merge", |
332 | 0 | saved_requests[j]->requestvb->name, |
333 | 0 | saved_requests[j]->requestvb->name_length)); |
334 | 0 | if (!snmp_oid_compare( |
335 | 0 | saved_requests[i]->requestvb->name+SKIP_OID, |
336 | 0 | saved_requests[i]->requestvb->name_length-SKIP_OID, |
337 | 0 | saved_requests[j]->requestvb->name+SKIP_OID, |
338 | 0 | saved_requests[j]->requestvb->name_length-SKIP_OID)) { |
339 | 0 | DEBUGMSG(("helper:row_merge", " match\n")); |
340 | 0 | saved_requests[tail]->next = saved_requests[j]; |
341 | 0 | saved_requests[j]->next = NULL; |
342 | 0 | saved_requests[j]->prev = saved_requests[tail]; |
343 | 0 | saved_status[j] = ROW_MERGE_ACTIVE; |
344 | 0 | tail = j; |
345 | 0 | } |
346 | 0 | else |
347 | 0 | DEBUGMSG(("helper:row_merge", " no match\n")); |
348 | 0 | } |
349 | 0 | } |
350 | | |
351 | | /* |
352 | | * not that we have a list for each row, call next handler... |
353 | | */ |
354 | 0 | if (0 == rm_status->count) |
355 | 0 | rm_status->count = count; |
356 | 0 | rm_status->current = 0; |
357 | 0 | for (i=0; i<count; i++) { |
358 | 0 | if (saved_status[i] != ROW_MERGE_HEAD) |
359 | 0 | continue; |
360 | | |
361 | | /* |
362 | | * found the head of a new row, |
363 | | * call the next handler with this list |
364 | | */ |
365 | 0 | rm_status->current++; |
366 | 0 | ret = netsnmp_call_next_handler(handler, reginfo, reqinfo, |
367 | 0 | saved_requests[i]); |
368 | 0 | if (ret != SNMP_ERR_NOERROR) { |
369 | 0 | snmp_log(LOG_WARNING, |
370 | 0 | "bad rc (%d) from next handler in row_merge\n", ret); |
371 | 0 | if (SNMP_ERR_NOERROR == final_rc) |
372 | 0 | final_rc = ret; |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | /* |
377 | | * restore original linked list |
378 | | */ |
379 | 0 | for (i=0; i<count; i++) { |
380 | 0 | saved_requests[i]->next = saved_requests[i+1]; |
381 | 0 | if (i>0) |
382 | 0 | saved_requests[i]->prev = saved_requests[i-1]; |
383 | 0 | } |
384 | |
|
385 | 0 | return final_rc; |
386 | 0 | } |
387 | | |
388 | | /** |
389 | | * initializes the row_merge helper which then registers a row_merge |
390 | | * handler as a run-time injectable handler for configuration file |
391 | | * use. |
392 | | */ |
393 | | void |
394 | | netsnmp_init_row_merge(void) |
395 | 3.28k | { |
396 | 3.28k | netsnmp_register_handler_by_name("row_merge", |
397 | 3.28k | netsnmp_get_row_merge_handler(-1)); |
398 | 3.28k | } |
399 | | #else /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */ |
400 | | netsnmp_feature_unused(row_merge); |
401 | | #endif /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */ |
402 | | |
403 | | |
404 | | /** @} */ |
405 | | |