Coverage Report

Created: 2026-07-05 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/netcdf-c/libdispatch/dudfplugins.c
Line
Count
Source
1
/* Copyright 2026, UCAR/Unidata.
2
   See the COPYRIGHT file for more information. */
3
4
/**
5
 * @file
6
 * @internal This file contains functions for loading UDF plugins from RC files.
7
 *
8
 * @author Ed Hartnett
9
 * @date 2/2/26
10
 */
11
12
#include "config.h"
13
#include <stdlib.h>
14
#include <string.h>
15
#include <stdio.h>
16
#include "netcdf.h"
17
#include "netcdf_dispatch.h"
18
#include "nclog.h"
19
#include "ncrc.h"
20
#include "ncudfplugins.h"
21
22
/* Platform-specific dynamic loading headers */
23
#ifdef _WIN32
24
#include <windows.h>
25
#else
26
#include <dlfcn.h>
27
#endif
28
29
/**
30
 * Load a dynamic library (platform-specific).
31
 *
32
 * @param path Full path to the library file.
33
 * @return Handle to the loaded library, or NULL on failure.
34
 *
35
 * @author Edward Hartnett
36
 * @date 2/2/26
37
 */
38
static void*
39
load_library(const char* path)
40
0
{
41
0
    void* handle = NULL;
42
    
43
#ifdef _WIN32
44
    handle = (void*)LoadLibraryA(path);
45
    if (!handle) {
46
        DWORD err = GetLastError();
47
        nclog(NCLOGERR, "LoadLibrary failed for %s: error %lu", path, err);
48
    }
49
#else
50
0
    handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
51
0
    if (!handle) {
52
0
        nclog(NCLOGERR, "dlopen failed for %s: %s", path, dlerror());
53
0
    }
54
0
#endif
55
    
56
0
    return handle;
57
0
}
58
59
/**
60
 * Get a symbol from a loaded library (platform-specific).
61
 *
62
 * @param handle Handle to the loaded library.
63
 * @param symbol Name of the symbol to retrieve.
64
 * @return Pointer to the symbol, or NULL on failure.
65
 *
66
 * @author Edward Hartnett
67
 * @date 2/2/26
68
 */
69
static void*
70
get_symbol(void* handle, const char* symbol)
71
0
{
72
0
    void* sym = NULL;
73
    
74
#ifdef _WIN32
75
    sym = (void*)GetProcAddress((HMODULE)handle, symbol);
76
    if (!sym) {
77
        DWORD err = GetLastError();
78
        nclog(NCLOGERR, "GetProcAddress failed for %s: error %lu", symbol, err);
79
    }
80
#else
81
0
    sym = dlsym(handle, symbol);
82
0
    if (!sym) {
83
0
        nclog(NCLOGERR, "dlsym failed for %s: %s", symbol, dlerror());
84
0
    }
85
0
#endif
86
    
87
0
    return sym;
88
0
}
89
90
/**
91
 * Load a single UDF plugin library and call its initialization function.
92
 *
93
 * @param udf_number UDF slot number (0-9).
94
 * @param library_path Full path to the plugin library.
95
 * @param init_func Name of the initialization function.
96
 * @param magic Optional magic number string (can be NULL).
97
 * @return NC_NOERR on success, error code on failure.
98
 *
99
 * @author Edward Hartnett
100
 * @date 2/2/26
101
 */
102
static int
103
load_udf_plugin(int udf_number, const char* library_path,
104
                const char* init_func, const char* magic)
105
0
{
106
0
    int stat = NC_NOERR;
107
0
    void* handle = NULL;
108
0
    int mode_flag;
109
#ifndef HAVE_NETCDF_UDF_SELF_REGISTRATION
110
    NC_Dispatch* dispatch_table = NULL;
111
    char magic_check[NC_MAX_MAGIC_NUMBER_LEN + 1];
112
#endif
113
    
114
    /* Determine mode flag from UDF number.
115
     * Use explicit array to avoid bit-collision bugs from shifting. */
116
0
    {
117
0
        int udf_flags[NC_MAX_UDF_FORMATS] = {
118
0
            NC_UDF0, NC_UDF1, NC_UDF2, NC_UDF3, NC_UDF4,
119
0
            NC_UDF5, NC_UDF6, NC_UDF7, NC_UDF8, NC_UDF9
120
0
        };
121
0
        mode_flag = udf_flags[udf_number];
122
0
    }
123
    
124
    /* Load the library */
125
0
    handle = load_library(library_path);
126
0
    if (!handle) {
127
0
        stat = NC_ENOTNC;
128
0
        goto done;
129
0
    }
130
    
131
0
#ifdef HAVE_NETCDF_UDF_SELF_REGISTRATION
132
    /* Self-registration mode: init function returns NC_Dispatch* */
133
0
    {
134
0
        NC_Dispatch* (*init_function)(void) = NULL;
135
0
        NC_Dispatch* table = NULL;
136
        
137
        /* Get the initialization function */
138
0
        init_function = (NC_Dispatch* (*)(void))get_symbol(handle, init_func);
139
0
        if (!init_function) {
140
0
            stat = NC_ENOTNC;
141
0
            goto done;
142
0
        }
143
        
144
        /* Call the initialization function to get the dispatch table */
145
0
        table = init_function();
146
0
        if (!table) {
147
0
            nclog(NCLOGERR, "Plugin init function %s returned NULL", init_func);
148
0
            stat = NC_ENOTNC;
149
0
            goto done;
150
0
        }
151
        
152
        /* Verify dispatch ABI version */
153
0
        if (table->dispatch_version != NC_DISPATCH_VERSION) {
154
0
            nclog(NCLOGERR, "Plugin dispatch ABI mismatch for UDF%d: expected %d, got %d",
155
0
                  udf_number, NC_DISPATCH_VERSION, table->dispatch_version);
156
0
            stat = NC_EINVAL;
157
0
            goto done;
158
0
        }
159
        
160
        /* Register the dispatch table returned by the plugin */
161
0
        if ((stat = nc_def_user_format(mode_flag, table, (char*)magic))) {
162
0
            nclog(NCLOGERR, "Failed to register dispatch table for UDF%d: %d",
163
0
                  udf_number, stat);
164
0
            goto done;
165
0
        }
166
0
    }
167
#else
168
    /* Legacy mode: init function returns int and registers itself */
169
    {
170
        int (*init_function)(void) = NULL;
171
        
172
        /* Get the initialization function */
173
        init_function = (int (*)(void))get_symbol(handle, init_func);
174
        if (!init_function) {
175
            stat = NC_ENOTNC;
176
            goto done;
177
        }
178
        
179
        /* Call the initialization function */
180
        if ((stat = init_function())) {
181
            nclog(NCLOGERR, "Plugin init function %s failed: %d", init_func, stat);
182
            goto done;
183
        }
184
        
185
        /* Verify the dispatch table was registered */
186
        memset(magic_check, 0, sizeof(magic_check));
187
        if ((stat = nc_inq_user_format(mode_flag, &dispatch_table, magic_check))) {
188
            nclog(NCLOGERR, "Plugin did not register dispatch table for UDF%d", udf_number);
189
            goto done;
190
        }
191
        
192
        if (dispatch_table == NULL) {
193
            nclog(NCLOGERR, "Plugin registered NULL dispatch table for UDF%d", udf_number);
194
            stat = NC_EINVAL;
195
            goto done;
196
        }
197
        
198
        /* Verify dispatch ABI version */
199
        if (dispatch_table->dispatch_version != NC_DISPATCH_VERSION) {
200
            nclog(NCLOGERR, "Plugin dispatch ABI mismatch for UDF%d: expected %d, got %d",
201
                  udf_number, NC_DISPATCH_VERSION, dispatch_table->dispatch_version);
202
            stat = NC_EINVAL;
203
            goto done;
204
        }
205
        
206
        /* Optionally verify magic number matches */
207
        if (magic != NULL && strlen(magic_check) > 0) {
208
            if (strcmp(magic, magic_check) != 0) {
209
                nclog(NCLOGWARN, "Plugin magic number mismatch for UDF%d: expected %s, got %s",
210
                      udf_number, magic, magic_check);
211
            }
212
        }
213
    }
214
#endif
215
    
216
0
    nclog(NCLOGNOTE, "Successfully loaded UDF%d plugin from %s", 
217
0
          udf_number, library_path);
218
    
219
0
done:
220
    /* Handles are intentionally not closed; the OS will reclaim at process exit.
221
     * The dispatch table and its functions must remain accessible. */
222
0
    return stat;
223
0
}
224
225
/**
226
 * Load and initialize all UDF plugins from RC file configuration.
227
 *
228
 * This function loops through all 10 UDF slots (0-9) and checks for
229
 * corresponding RC file entries. If both LIBRARY and INIT keys are
230
 * present for a slot, it attempts to load that plugin.
231
 *
232
 * @return NC_NOERR (always succeeds, even if plugins fail to load).
233
 *
234
 * @author Edward Hartnett
235
 * @date 2/2/26
236
 */
237
int
238
NC_udf_load_plugins(void)
239
1
{
240
1
    int stat = NC_NOERR;
241
    
242
    /* Loop through all 10 UDF slots */
243
11
    for (int i = 0; i < NC_MAX_UDF_FORMATS; i++) {
244
10
        char key_lib[64], key_init[64], key_magic[64];
245
10
        const char* lib = NULL;
246
10
        const char* init = NULL;
247
10
        const char* magic = NULL;
248
        
249
        /* Build RC key names for this UDF slot */
250
10
        snprintf(key_lib, sizeof(key_lib), "NETCDF.UDF%d.LIBRARY", i);
251
10
        snprintf(key_init, sizeof(key_init), "NETCDF.UDF%d.INIT", i);
252
10
        snprintf(key_magic, sizeof(key_magic), "NETCDF.UDF%d.MAGIC", i);
253
        
254
        /* Look up RC values */
255
10
        lib = NC_rclookup(key_lib, NULL, NULL);
256
10
        init = NC_rclookup(key_init, NULL, NULL);
257
10
        magic = NC_rclookup(key_magic, NULL, NULL);
258
        
259
        /* If both LIBRARY and INIT are present, try to load the plugin */
260
10
        if (lib && init) {
261
0
            if ((stat = load_udf_plugin(i, lib, init, magic))) {
262
0
                nclog(NCLOGWARN, "Failed to load UDF%d plugin from %s: %d", i, lib, stat);
263
0
            }
264
10
        } else if (lib || init) {
265
            /* Warn about partial configuration */
266
0
            nclog(NCLOGWARN, "Ignoring partial UDF%d configuration "
267
0
                  "(both NETCDF.UDF%d.LIBRARY and NETCDF.UDF%d.INIT are required)",
268
0
                  i, i, i);
269
0
        }
270
10
    }
271
    
272
    /* Always return success - plugin loading failures are not fatal */
273
1
    return NC_NOERR;
274
1
}