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