Coverage Report

Created: 2025-10-13 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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