/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 | } |