Coverage Report

Created: 2026-10-19 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/net-snmp/snmplib/callback.c
Line
Count
Source
1
/*
2
 * callback.c: A generic callback mechanism 
3
 */
4
/* Portions of this file are subject to the following copyright(s).  See
5
 * the Net-SNMP's COPYING file for more details and other copyrights
6
 * that may apply:
7
 */
8
/*
9
 * Portions of this file are copyrighted by:
10
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
11
 * Use is subject to license terms specified in the COPYING file
12
 * distributed with the Net-SNMP package.
13
 */
14
/** @defgroup callback A generic callback mechanism 
15
 *  @ingroup library
16
 * 
17
 *  @{
18
 */
19
#include <net-snmp/net-snmp-config.h>
20
#include <net-snmp/net-snmp-features.h>
21
#include <sys/types.h>
22
#include <stdio.h>
23
#ifdef HAVE_STDLIB_H
24
#include <stdlib.h>
25
#endif
26
#ifdef HAVE_NETINET_IN_H
27
#include <netinet/in.h>
28
#endif
29
#ifdef HAVE_STRING_H
30
#include <string.h>
31
#else
32
#include <strings.h>
33
#endif
34
35
#ifdef HAVE_UNISTD_H
36
#include <unistd.h>
37
#endif
38
39
#ifdef HAVE_SYS_SOCKET_H
40
#include <sys/socket.h>
41
#endif
42
#if !defined(mingw32) && defined(HAVE_SYS_TIME_H)
43
#include <sys/time.h>
44
#endif
45
46
#include <net-snmp/types.h>
47
#include <net-snmp/output_api.h>
48
#include <net-snmp/utilities.h>
49
50
#include <net-snmp/library/callback.h>
51
#include <net-snmp/library/snmp_api.h>
52
53
netsnmp_feature_child_of(callbacks_all, libnetsnmp);
54
55
netsnmp_feature_child_of(callback_count, callbacks_all);
56
netsnmp_feature_child_of(callback_list, callbacks_all);
57
58
/*
59
 * the inline callback methods use major/minor to index into arrays.
60
 * all users in this function do range checking before calling these
61
 * functions, so it is redundant for them to check again. But if you
62
 * want to be paranoid, define this var, and additional range checks
63
 * will be performed.
64
 * #define NETSNMP_PARANOID_LEVEL_HIGH 1 
65
 */
66
67
static int _callback_need_init = 1;
68
static struct snmp_gen_callback
69
               *thecallbacks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS];
70
71
#define CALLBACK_NAME_LOGGING 1
72
#ifdef CALLBACK_NAME_LOGGING
73
static const char *types[MAX_CALLBACK_IDS] = { "LIB", "APP" };
74
static const char *lib[MAX_CALLBACK_SUBIDS] = {
75
    "POST_READ_CONFIG", /* 0 */
76
    "STORE_DATA", /* 1 */
77
    "SHUTDOWN", /* 2 */
78
    "POST_PREMIB_READ_CONFIG", /* 3 */
79
    "LOGGING", /* 4 */
80
    "SESSION_INIT", /* 5 */
81
    NULL, /* 6 */
82
    NULL, /* 7 */
83
    NULL, /* 8 */
84
    NULL, /* 9 */
85
    NULL, /* 10 */
86
    NULL, /* 11 */
87
    NULL, /* 12 */
88
    NULL, /* 13 */
89
    NULL, /* 14 */
90
    NULL /* 15 */
91
};
92
#endif
93
94
/*
95
 * extremely simplistic locking, just to find problems were the
96
 * callback list is modified while being traversed. Not intended
97
 * to do any real protection, or in any way imply that this code
98
 * has been evaluated for use in a multi-threaded environment.
99
 * In 5.2, it was a single lock. For 5.3, it has been updated to
100
 * a lock per callback, since a particular callback may trigger
101
 * registration/unregistration of other callbacks (eg AgentX
102
 * subagents do this).
103
 */
104
#define LOCK_PER_CALLBACK_SUBID 1
105
#ifdef LOCK_PER_CALLBACK_SUBID
106
static int _locks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS];
107
2.67k
#define CALLBACK_LOCK(maj,min) ++_locks[maj][min]
108
2.67k
#define CALLBACK_UNLOCK(maj,min) --_locks[maj][min]
109
5.34k
#define CALLBACK_LOCK_COUNT(maj,min) _locks[maj][min]
110
#else
111
static int _lock;
112
#define CALLBACK_LOCK(maj,min) ++_lock
113
#define CALLBACK_UNLOCK(maj,min) --_lock
114
#define CALLBACK_LOCK_COUNT(maj,min) _lock
115
#endif
116
117
NETSNMP_STATIC_INLINE int
118
_callback_lock(int major, int minor, const char* warn, int do_assert)
119
2.67k
{
120
2.67k
    int lock_holded=0;
121
2.67k
    NETSNMP_SELECT_TIMEVAL lock_time;
122
123
#ifdef NETSNMP_PARANOID_LEVEL_HIGH
124
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
125
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS) {
126
        netsnmp_assert("bad callback id");
127
        return 1;
128
    }
129
#endif
130
    
131
2.67k
#ifdef CALLBACK_NAME_LOGGING
132
2.67k
    DEBUGMSGTL(("9:callback:lock", "locked (%s,%s)\n",
133
2.67k
                types[major], (SNMP_CALLBACK_LIBRARY == major) ?
134
2.67k
                SNMP_STRORNULL(lib[minor]) : "null"));
135
2.67k
#endif
136
2.67k
    while (CALLBACK_LOCK_COUNT(major,minor) >= 1 && ++lock_holded < 100) {
137
0
  lock_time.tv_sec = 0;
138
0
  lock_time.tv_usec = 1000;
139
0
  select(0, NULL, NULL, NULL, &lock_time);
140
0
    }
141
142
2.67k
    if(lock_holded >= 100) {
143
0
        if (NULL != warn)
144
0
            snmp_log(LOG_WARNING,
145
0
                     "lock in _callback_lock sleeps more than 100 milliseconds in %s\n", warn);
146
0
        if (do_assert)
147
0
            netsnmp_assert(lock_holded < 100);
148
        
149
0
        return 1;
150
0
    }
151
152
2.67k
    CALLBACK_LOCK(major,minor);
153
2.67k
    return 0;
154
2.67k
}
155
156
NETSNMP_STATIC_INLINE void
157
_callback_unlock(int major, int minor)
158
2.67k
{
159
#ifdef NETSNMP_PARANOID_LEVEL_HIGH
160
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
161
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS) {
162
        netsnmp_assert("bad callback id");
163
        return;
164
    }
165
#endif
166
    
167
2.67k
    CALLBACK_UNLOCK(major,minor);
168
169
2.67k
    if (CALLBACK_LOCK_COUNT(major,minor) == 0) {
170
2.67k
        struct snmp_gen_callback *scp, **prevNext;
171
2.67k
        scp = thecallbacks[major][minor];
172
2.67k
        prevNext = &(thecallbacks[major][minor]);
173
5.35k
        while (scp != NULL) {
174
2.68k
            if (scp->sc_callback == NULL) {
175
0
                *prevNext = scp->next;
176
0
                SNMP_FREE(scp);
177
0
                scp = *prevNext;
178
2.68k
            } else {
179
2.68k
                prevNext = &(scp->next);
180
2.68k
                scp = scp->next;
181
2.68k
            }
182
2.68k
        }
183
2.67k
    }
184
185
2.67k
#ifdef CALLBACK_NAME_LOGGING
186
2.67k
    DEBUGMSGTL(("9:callback:lock", "unlocked (%s,%s)\n",
187
2.67k
                types[major], (SNMP_CALLBACK_LIBRARY == major) ?
188
2.67k
                SNMP_STRORNULL(lib[minor]) : "null"));
189
2.67k
#endif
190
2.67k
}
191
192
193
/*
194
 * the chicken. or the egg.  You pick. 
195
 */
196
void
197
init_callbacks(void)
198
2
{
199
    /*
200
     * (poses a problem if you put init_callbacks() inside of
201
     * init_snmp() and then want the app to register a callback before
202
     * init_snmp() is called in the first place.  -- Wes 
203
     */
204
2
    if (0 == _callback_need_init)
205
1
        return;
206
    
207
1
    _callback_need_init = 0;
208
    
209
1
    memset(thecallbacks, 0, sizeof(thecallbacks)); 
210
1
#ifdef LOCK_PER_CALLBACK_SUBID
211
1
    memset(_locks, 0, sizeof(_locks));
212
#else
213
    _lock = 0;
214
#endif
215
    
216
1
    DEBUGMSGTL(("callback", "initialized\n"));
217
1
}
218
219
/**
220
 * This function registers a generic callback function.  The major and
221
 * minor values are used to set the new_callback function into a global 
222
 * static multi-dimensional array of type struct snmp_gen_callback.  
223
 * The function makes sure to append this callback function at the end
224
 * of the link list, snmp_gen_callback->next.
225
 *
226
 * @param major is the SNMP callback major type used
227
 *    - SNMP_CALLBACK_LIBRARY
228
 *              - SNMP_CALLBACK_APPLICATION
229
 *
230
 * @param minor is the SNMP callback minor type used
231
 *    - SNMP_CALLBACK_POST_READ_CONFIG
232
 *    - SNMP_CALLBACK_STORE_DATA          
233
 *    - SNMP_CALLBACK_SHUTDOWN            
234
 *    - SNMP_CALLBACK_POST_PREMIB_READ_CONFIG 
235
 *    - SNMP_CALLBACK_LOGGING     
236
 *    - SNMP_CALLBACK_SESSION_INIT         
237
 *
238
 * @param new_callback is the callback function that is registered.
239
 *
240
 * @param arg when not NULL is a void pointer used whenever new_callback 
241
 *  function is exercised. Ownership is transferred to the twodimensional
242
 *      thecallbacks[][] array. The function clear_callback() will deallocate
243
 *      the memory pointed at by calling free().
244
 *
245
 * @return 
246
 *  Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or minor is >=
247
 *  MAX_CALLBACK_SUBIDS or a snmp_gen_callback pointer could not be 
248
 *  allocated, otherwise SNMPERR_SUCCESS is returned.
249
 *  - \#define MAX_CALLBACK_IDS    2
250
 *  - \#define MAX_CALLBACK_SUBIDS 16
251
 *
252
 * @see snmp_call_callbacks
253
 * @see snmp_unregister_callback
254
 */
255
int
256
snmp_register_callback(int major, int minor, SNMPCallback * new_callback,
257
                       void *arg)
258
11
{
259
11
    return netsnmp_register_callback( major, minor, new_callback, arg,
260
11
                                      NETSNMP_CALLBACK_DEFAULT_PRIORITY);
261
11
}
262
263
/**
264
 * Register a callback function.
265
 *
266
 * @param major        Major callback event type.
267
 * @param minor        Minor callback event type.
268
 * @param new_callback Callback function being registered.
269
 * @param arg          Argument that will be passed to the callback function.
270
 * @param priority     Handler invocation priority. When multiple handlers have
271
 *   been registered for the same (major, minor) callback event type, handlers
272
 *   with the numerically lowest priority will be invoked first. Handlers with
273
 *   identical priority are invoked in the order they have been registered.
274
 *
275
 * @see snmp_register_callback
276
 */
277
int
278
netsnmp_register_callback(int major, int minor, SNMPCallback * new_callback,
279
                          void *arg, int priority)
280
11
{
281
11
    struct snmp_gen_callback *newscp = NULL, *scp = NULL;
282
283
11
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
284
11
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS) {
285
0
        return SNMPERR_GENERR;
286
0
    }
287
288
11
    if (_callback_need_init)
289
1
        init_callbacks();
290
291
11
    _callback_lock(major,minor, "netsnmp_register_callback", 1);
292
    
293
11
    if ((newscp = SNMP_MALLOC_STRUCT(snmp_gen_callback)) == NULL) {
294
0
        _callback_unlock(major,minor);
295
0
        return SNMPERR_GENERR;
296
11
    } else {
297
11
        struct snmp_gen_callback **prevNext = &(thecallbacks[major][minor]);
298
299
11
        newscp->priority = priority;
300
11
        newscp->sc_client_arg = arg;
301
11
        newscp->sc_callback = new_callback;
302
11
        newscp->next = NULL;
303
304
20
        for (scp = thecallbacks[major][minor]; scp != NULL;
305
11
             scp = scp->next) {
306
9
            if (newscp->priority < scp->priority) {
307
0
                newscp->next = scp;
308
0
                break;
309
0
            }
310
9
            prevNext = &(scp->next);
311
9
        }
312
313
11
        *prevNext = newscp;
314
315
11
        DEBUGMSGTL(("callback", "registered (%d,%d) at %p with priority %d\n",
316
11
                    major, minor, newscp, priority));
317
11
        _callback_unlock(major,minor);
318
11
        return SNMPERR_SUCCESS;
319
11
    }
320
11
}
321
322
/**
323
 * This function calls the callback function for each registered callback of
324
 * type major and minor.
325
 *
326
 * @param major is the SNMP callback major type used
327
 *
328
 * @param minor is the SNMP callback minor type used
329
 *
330
 * @param caller_arg is a void pointer which is sent in as the callback's 
331
 *  serverarg parameter, if needed.
332
 *
333
 * @return Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or
334
 * minor is >= MAX_CALLBACK_SUBIDS, otherwise SNMPERR_SUCCESS is returned.
335
 *
336
 * @see snmp_register_callback
337
 * @see snmp_unregister_callback
338
 */
339
int
340
snmp_call_callbacks(int major, int minor, void *caller_arg)
341
2.66k
{
342
2.66k
    struct snmp_gen_callback *scp;
343
2.66k
    unsigned int    count = 0;
344
    
345
2.66k
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
346
2.66k
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS) {
347
0
        return SNMPERR_GENERR;
348
0
    }
349
    
350
2.66k
    if (_callback_need_init)
351
0
        init_callbacks();
352
353
2.66k
#ifdef LOCK_PER_CALLBACK_SUBID
354
2.66k
    _callback_lock(major,minor,"snmp_call_callbacks", 1);
355
#else
356
    /*
357
     * Notes:
358
     * - this gets hit the first time a trap is sent after a new trap
359
     *   destination has been added (session init cb during send trap cb)
360
     */
361
    _callback_lock(major,minor, NULL, 0);
362
#endif
363
364
2.66k
    DEBUGMSGTL(("callback", "START calling callbacks for maj=%d min=%d\n",
365
2.66k
                major, minor));
366
367
    /*
368
     * for each registered callback of type major and minor 
369
     */
370
5.32k
    for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) {
371
372
        /*
373
         * skip unregistered callbacks
374
         */
375
2.66k
        if(NULL == scp->sc_callback)
376
0
            continue;
377
378
2.66k
        DEBUGMSGTL(("callback", "calling a callback for maj=%d min=%d\n",
379
2.66k
                    major, minor));
380
381
        /*
382
         * call them 
383
         */
384
2.66k
        (*(scp->sc_callback)) (major, minor, caller_arg,
385
2.66k
                               scp->sc_client_arg);
386
2.66k
        count++;
387
2.66k
    }
388
389
2.66k
    DEBUGMSGTL(("callback",
390
2.66k
                "END calling callbacks for maj=%d min=%d (%d called)\n",
391
2.66k
                major, minor, count));
392
393
2.66k
    _callback_unlock(major,minor);
394
2.66k
    return SNMPERR_SUCCESS;
395
2.66k
}
396
397
#ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT
398
int
399
snmp_count_callbacks(int major, int minor)
400
0
{
401
0
    int             count = 0;
402
0
    struct snmp_gen_callback *scp;
403
404
0
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
405
0
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS) {
406
0
        return SNMPERR_GENERR;
407
0
    }
408
    
409
0
    if (_callback_need_init)
410
0
        init_callbacks();
411
412
0
    for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) {
413
0
        count++;
414
0
    }
415
416
0
    return count;
417
0
}
418
#endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT */
419
420
int
421
snmp_callback_available(int major, int minor)
422
0
{
423
0
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
424
0
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS) {
425
0
        return SNMPERR_GENERR;
426
0
    }
427
    
428
0
    if (_callback_need_init)
429
0
        init_callbacks();
430
431
0
    if (thecallbacks[major][minor] != NULL) {
432
0
        return SNMPERR_SUCCESS;
433
0
    }
434
435
0
    return SNMPERR_GENERR;
436
0
}
437
438
/**
439
 * This function unregisters a specified callback function given a major
440
 * and minor type.
441
 *
442
 * @param major is the SNMP callback major type used
443
 *
444
 * @param minor is the SNMP callback minor type used
445
 *
446
 * @param target is the callback function that will be unregistered.
447
 *
448
 * @param arg is a void pointer used for comparison against the registered 
449
 *  callback's sc_client_arg variable.
450
 *
451
 * @param matchargs is an integer used to bypass the comparison of arg and the
452
 *  callback's sc_client_arg variable only when matchargs is set to 0.
453
 *
454
 *
455
 * @return
456
 *        Returns the number of callbacks that were unregistered.
457
 *
458
 * @see snmp_register_callback
459
 * @see snmp_call_callbacks
460
 */
461
462
int
463
snmp_unregister_callback(int major, int minor, SNMPCallback * target,
464
                         void *arg, int matchargs)
465
0
{
466
0
    struct snmp_gen_callback *scp;
467
0
    struct snmp_gen_callback **prevNext;
468
0
    int             count = 0;
469
0
    int             lock_failed;
470
471
0
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
472
0
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS)
473
0
        return SNMPERR_GENERR;
474
475
0
    if (_callback_need_init)
476
0
        init_callbacks();
477
    /*
478
     * Notes;
479
     * - this gets hit at shutdown, during cleanup. No easy fix.
480
     */
481
0
    lock_failed = _callback_lock(major,minor,"snmp_unregister_callback", 0);
482
483
0
    scp = thecallbacks[major][minor];
484
0
    prevNext = &(thecallbacks[major][minor]);
485
486
0
    while (scp != NULL) {
487
0
        if ((scp->sc_callback == target) &&
488
0
            (!matchargs || (scp->sc_client_arg == arg))) {
489
0
            DEBUGMSGTL(("callback", "unregistering (%d,%d) at %p\n", major,
490
0
                        minor, scp));
491
0
            if(!lock_failed && 1 == CALLBACK_LOCK_COUNT(major,minor)) {
492
0
                *prevNext = scp->next;
493
0
                SNMP_FREE(scp);
494
0
                scp = *prevNext;
495
0
            }
496
0
            else {
497
0
                scp->sc_callback = NULL;
498
                /** set cleanup flag? */
499
0
            }
500
0
            count++;
501
0
        } else {
502
0
            prevNext = &(scp->next);
503
0
            scp = scp->next;
504
0
        }
505
0
    }
506
507
0
    if (!lock_failed)
508
0
        _callback_unlock(major,minor);
509
0
    return count;
510
0
}
511
512
/**
513
 * find and clear client args that match ptr
514
 *
515
 * @param ptr  pointer to search for
516
 * @param i    callback id to start at
517
 * @param j    callback subid to start at
518
 */
519
int
520
netsnmp_callback_clear_client_arg(void *ptr, int i, int j)
521
2.65k
{
522
2.65k
    struct snmp_gen_callback *scp = NULL;
523
2.65k
    int rc = 0;
524
525
2.65k
    if (i < 0 || i >= MAX_CALLBACK_IDS ||
526
2.65k
        j < 0 || j >= MAX_CALLBACK_SUBIDS)
527
0
        return SNMPERR_GENERR;
528
529
    /*
530
     * don't init i and j before loop, since the caller specified
531
     * the starting point explicitly. But *after* the i loop has
532
     * finished executing once, init j to 0 for the next pass
533
     * through the subids.
534
     */
535
7.97k
    for (; i < MAX_CALLBACK_IDS; i++,j=0) {
536
100k
        for (; j < MAX_CALLBACK_SUBIDS; j++) {
537
95.6k
            scp = thecallbacks[i][j]; 
538
124k
            while (scp != NULL) {
539
29.2k
                if ((NULL != scp->sc_callback) &&
540
29.2k
                    (scp->sc_client_arg != NULL) &&
541
2.65k
                    (scp->sc_client_arg == ptr)) {
542
0
                    DEBUGMSGTL(("9:callback", "  clearing %p at [%d,%d]\n", ptr, i, j));
543
0
                    scp->sc_client_arg = NULL;
544
0
                    ++rc;
545
0
                }
546
29.2k
                scp = scp->next;
547
29.2k
            }
548
95.6k
        }
549
5.31k
    }
550
551
2.65k
    if (0 != rc) {
552
0
        DEBUGMSGTL(("callback", "removed %d client args\n", rc));
553
0
    }
554
555
2.65k
    return rc;
556
2.65k
}
557
558
void
559
clear_callback(void)
560
0
{
561
0
    unsigned int i = 0, j = 0;
562
0
    struct snmp_gen_callback *scp = NULL;
563
564
0
    if (_callback_need_init)
565
0
        init_callbacks();
566
567
0
    DEBUGMSGTL(("callback", "clear callback\n"));
568
0
    for (i = 0; i < MAX_CALLBACK_IDS; i++) {
569
0
        for (j = 0; j < MAX_CALLBACK_SUBIDS; j++) {
570
0
            _callback_lock(i,j, "clear_callback", 1);
571
0
            scp = thecallbacks[i][j];
572
0
            while (scp != NULL) {
573
0
                thecallbacks[i][j] = scp->next;
574
                /*
575
                 * if there is a client arg, check for duplicates
576
                 * and then free it.
577
                 */
578
0
                if ((NULL != scp->sc_callback) &&
579
0
                    (scp->sc_client_arg != NULL)) {
580
0
                    void *tmp_arg;
581
                    /*
582
                     * save the client arg, then set it to null so that it
583
                     * won't look like a duplicate, then check for duplicates
584
                     * starting at the current i,j (earlier dups should have
585
                     * already been found) and free the pointer.
586
                     */
587
0
                    tmp_arg = scp->sc_client_arg;
588
0
                    scp->sc_client_arg = NULL;
589
0
                    DEBUGMSGTL(("9:callback", "  freeing %p at [%d,%d]\n", tmp_arg, i, j));
590
0
                    (void)netsnmp_callback_clear_client_arg(tmp_arg, i, j);
591
0
                    free(tmp_arg);
592
0
                }
593
0
                SNMP_FREE(scp);
594
0
                scp = thecallbacks[i][j];
595
0
            }
596
0
            _callback_unlock(i,j);
597
0
        }
598
0
    }
599
0
}
600
601
#ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_LIST
602
struct snmp_gen_callback *
603
snmp_callback_list(int major, int minor)
604
0
{
605
0
    if (major < 0 || major >= MAX_CALLBACK_IDS ||
606
0
        minor < 0 || minor >= MAX_CALLBACK_SUBIDS)
607
0
        return NULL;
608
609
0
    if (_callback_need_init)
610
0
        init_callbacks();
611
612
0
    return (thecallbacks[major][minor]);
613
0
}
614
#endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_LIST */
615
/**  @} */