Coverage Report

Created: 2026-02-26 06:18

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
0
    if (udf_number == 0)
116
0
        mode_flag = NC_UDF0;
117
0
    else if (udf_number == 1)
118
0
        mode_flag = NC_UDF1;
119
0
    else
120
0
        mode_flag = NC_UDF2 << (udf_number - 2);
121
    
122
    /* Load the library */
123
0
    handle = load_library(library_path);
124
0
    if (!handle) {
125
0
        stat = NC_ENOTNC;
126
0
        goto done;
127
0
    }
128
    
129
0
#ifdef HAVE_NETCDF_UDF_SELF_REGISTRATION
130
    /* Self-registration mode: init function returns NC_Dispatch* */
131
0
    {
132
0
        NC_Dispatch* (*init_function)(void) = NULL;
133
0
        NC_Dispatch* table = NULL;
134
        
135
        /* Get the initialization function */
136
0
        init_function = (NC_Dispatch* (*)(void))get_symbol(handle, init_func);
137
0
        if (!init_function) {
138
0
            stat = NC_ENOTNC;
139
0
            goto done;
140
0
        }
141
        
142
        /* Call the initialization function to get the dispatch table */
143
0
        table = init_function();
144
0
        if (!table) {
145
0
            nclog(NCLOGERR, "Plugin init function %s returned NULL", init_func);
146
0
            stat = NC_ENOTNC;
147
0
            goto done;
148
0
        }
149
        
150
        /* Verify dispatch ABI version */
151
0
        if (table->dispatch_version != NC_DISPATCH_VERSION) {
152
0
            nclog(NCLOGERR, "Plugin dispatch ABI mismatch for UDF%d: expected %d, got %d",
153
0
                  udf_number, NC_DISPATCH_VERSION, table->dispatch_version);
154
0
            stat = NC_EINVAL;
155
0
            goto done;
156
0
        }
157
        
158
        /* Register the dispatch table returned by the plugin */
159
0
        if ((stat = nc_def_user_format(mode_flag, table, (char*)magic))) {
160
0
            nclog(NCLOGERR, "Failed to register dispatch table for UDF%d: %d",
161
0
                  udf_number, stat);
162
0
            goto done;
163
0
        }
164
0
    }
165
#else
166
    /* Legacy mode: init function returns int and registers itself */
167
    {
168
        int (*init_function)(void) = NULL;
169
        
170
        /* Get the initialization function */
171
        init_function = (int (*)(void))get_symbol(handle, init_func);
172
        if (!init_function) {
173
            stat = NC_ENOTNC;
174
            goto done;
175
        }
176
        
177
        /* Call the initialization function */
178
        if ((stat = init_function())) {
179
            nclog(NCLOGERR, "Plugin init function %s failed: %d", init_func, stat);
180
            goto done;
181
        }
182
        
183
        /* Verify the dispatch table was registered */
184
        memset(magic_check, 0, sizeof(magic_check));
185
        if ((stat = nc_inq_user_format(mode_flag, &dispatch_table, magic_check))) {
186
            nclog(NCLOGERR, "Plugin did not register dispatch table for UDF%d", udf_number);
187
            goto done;
188
        }
189
        
190
        if (dispatch_table == NULL) {
191
            nclog(NCLOGERR, "Plugin registered NULL dispatch table for UDF%d", udf_number);
192
            stat = NC_EINVAL;
193
            goto done;
194
        }
195
        
196
        /* Verify dispatch ABI version */
197
        if (dispatch_table->dispatch_version != NC_DISPATCH_VERSION) {
198
            nclog(NCLOGERR, "Plugin dispatch ABI mismatch for UDF%d: expected %d, got %d",
199
                  udf_number, NC_DISPATCH_VERSION, dispatch_table->dispatch_version);
200
            stat = NC_EINVAL;
201
            goto done;
202
        }
203
        
204
        /* Optionally verify magic number matches */
205
        if (magic != NULL && strlen(magic_check) > 0) {
206
            if (strcmp(magic, magic_check) != 0) {
207
                nclog(NCLOGWARN, "Plugin magic number mismatch for UDF%d: expected %s, got %s",
208
                      udf_number, magic, magic_check);
209
            }
210
        }
211
    }
212
#endif
213
    
214
0
    nclog(NCLOGNOTE, "Successfully loaded UDF%d plugin from %s", 
215
0
          udf_number, library_path);
216
    
217
0
done:
218
    /* Handles are intentionally not closed; the OS will reclaim at process exit.
219
     * The dispatch table and its functions must remain accessible. */
220
0
    return stat;
221
0
}
222
223
/**
224
 * Load and initialize all UDF plugins from RC file configuration.
225
 *
226
 * This function loops through all 10 UDF slots (0-9) and checks for
227
 * corresponding RC file entries. If both LIBRARY and INIT keys are
228
 * present for a slot, it attempts to load that plugin.
229
 *
230
 * @return NC_NOERR (always succeeds, even if plugins fail to load).
231
 *
232
 * @author Edward Hartnett
233
 * @date 2/2/26
234
 */
235
int
236
NC_udf_load_plugins(void)
237
1
{
238
1
    int stat = NC_NOERR;
239
    
240
    /* Loop through all 10 UDF slots */
241
11
    for (int i = 0; i < NC_MAX_UDF_FORMATS; i++) {
242
10
        char key_lib[64], key_init[64], key_magic[64];
243
10
        const char* lib = NULL;
244
10
        const char* init = NULL;
245
10
        const char* magic = NULL;
246
        
247
        /* Build RC key names for this UDF slot */
248
10
        snprintf(key_lib, sizeof(key_lib), "NETCDF.UDF%d.LIBRARY", i);
249
10
        snprintf(key_init, sizeof(key_init), "NETCDF.UDF%d.INIT", i);
250
10
        snprintf(key_magic, sizeof(key_magic), "NETCDF.UDF%d.MAGIC", i);
251
        
252
        /* Look up RC values */
253
10
        lib = NC_rclookup(key_lib, NULL, NULL);
254
10
        init = NC_rclookup(key_init, NULL, NULL);
255
10
        magic = NC_rclookup(key_magic, NULL, NULL);
256
        
257
        /* If both LIBRARY and INIT are present, try to load the plugin */
258
10
        if (lib && init) {
259
0
            if ((stat = load_udf_plugin(i, lib, init, magic))) {
260
0
                nclog(NCLOGWARN, "Failed to load UDF%d plugin from %s: %d", i, lib, stat);
261
0
            }
262
10
        } else if (lib || init) {
263
            /* Warn about partial configuration */
264
0
            nclog(NCLOGWARN, "Ignoring partial UDF%d configuration "
265
0
                  "(both NETCDF.UDF%d.LIBRARY and NETCDF.UDF%d.INIT are required)",
266
0
                  i, i, i);
267
0
        }
268
10
    }
269
    
270
    /* Always return success - plugin loading failures are not fatal */
271
1
    return NC_NOERR;
272
1
}