Line data Source code
1 : /** @file modmgr.c
2 : * @brief Module manager implementation
3 : * @date 25/11/2017
4 : * @author François Cerbelle (Fanfan), francois@cerbelle.net
5 : * @copyright Copyright (c) 2017, François Cerbelle
6 : *
7 : * @internal
8 : * Compiler gcc
9 : * Last modified 2024-07-02 09:51
10 : * Organization Cerbelle.net
11 : * Company Home
12 : *
13 : * This source code is released for free distribution under the terms of the
14 : * GNU General Public License as published by the Free Software Foundation.
15 : */
16 :
17 :
18 : #include "modmgr.h"
19 : #include "gettext.h"
20 : #define _(String) gettext (String)
21 :
22 : #include <stdio.h>
23 : #include <stdlib.h>
24 : #include <string.h>
25 :
26 : #include <limits.h>
27 : #ifndef PATH_MAX
28 : # define PATH_MAX 255
29 : #endif
30 :
31 : #include <string.h>
32 : #include <ltdl.h>
33 :
34 : #if LIBPTHREAD
35 : # include <pthread.h>
36 : #endif
37 :
38 : #include "debug/assert.h"
39 : #include "debug/memory.h"
40 :
41 : /** Module list item structure */
42 : typedef struct modules_s {
43 : lt_dlhandle handle;
44 : moduleinfo_t* modinfo;
45 : struct modules_s *next;
46 : } modules_t;
47 :
48 : /** Module list head */
49 : static modules_t* modules = NULL;
50 :
51 867 : static char * dlerrordup (char *errormsg)
52 : {
53 867 : char *error = (char *) lt_dlerror ();
54 867 : if (error && !errormsg)
55 806 : errormsg = strdup (error);
56 867 : return errormsg;
57 : }
58 :
59 867 : static uint8_t modmgr_error(const char* contextname)
60 : {
61 867 : char* errormsg=NULL;
62 :
63 867 : errormsg = dlerrordup (errormsg);
64 867 : if (NULL != errormsg) {
65 5 : fprintf(stderr,"%s: %s\n",contextname,errormsg);
66 5 : free(errormsg);
67 5 : return 1;
68 : }
69 862 : DBG_PRINTF("%s: OK",contextname);
70 862 : return 0;
71 : }
72 :
73 : static void modmgr_init_real();
74 : /** NOOP init function called after real init occured */
75 13595 : static void modmgr_init_noop()
76 : {
77 13595 : DBG_MSG("modmgr already initialized.");
78 13595 : return;
79 : }
80 : /** Initialization functor on real init first and then on NOOP version */
81 : static void (*modmgr_init_ptr)() = modmgr_init_real;
82 :
83 : /** Sanity/Debug check at exit to detect unloaded zombies modules */
84 2158 : static void modmgr_atexit()
85 : {
86 2158 : DBG_MSG("Validate that module list is empty.");
87 : /* Checks that all modules were unloaded before exit */
88 : /* Module list should be either not initialized or with sentinel only */
89 2158 : ASSERT((modules==NULL)||(modules->handle==NULL));
90 : /* Finished with ltdl now. */
91 2158 : if (0!=lt_dlexit ()) {
92 0 : modmgr_error("lt_dlexit");
93 : /* We should abort here, but aborting in atexit() would be stupid */
94 : }
95 2158 : }
96 :
97 : /** modmgr real init function called executed at the first invocation */
98 2158 : static void modmgr_init_real()
99 : {
100 : /* Needs to be called once and only once */
101 2158 : ASSERT(modules == NULL);
102 :
103 2158 : DBG_MSG("LTDL initialization");
104 2158 : if (0 != lt_dlinit ())
105 0 : modmgr_error("lt_dlinit");
106 :
107 : /* Initialize the module list with a sentinel */
108 2158 : DBG_MSG("Module list initialization");
109 2158 : modules = (modules_t*)malloc(sizeof(modules_t));
110 2158 : if ( NULL == modules ) {
111 0 : fprintf(stderr,_("Critical: Module manager lacks RAM (OOM) to initialize.\n"));
112 0 : abort();
113 : }
114 2158 : modules->handle = NULL;
115 2158 : modules->modinfo = NULL;
116 2158 : modules->next = NULL;
117 2158 : DBG_MSG("Register an atexit healthcheck and cleanup fonction");
118 2158 : atexit(modmgr_atexit);
119 :
120 : /* Avoid double calls */
121 2158 : modmgr_init_ptr = modmgr_init_noop;
122 2158 : }
123 :
124 : /** Reset and initialize the modules search path */
125 3414 : int modmgr_setpath (const char* path)
126 : {
127 : int result;
128 3414 : DBG_PRINTF("path=%s",path);
129 3414 : modmgr_init_ptr();
130 3414 : ASSERT(modules != NULL);
131 :
132 : /* Set the module search path. */
133 3414 : result = lt_dlsetsearchpath (path);
134 3414 : if (0!=result)
135 306 : modmgr_error("lt_dlsetsearchpath");
136 3414 : DBG_PRINTF("search path = %s",lt_dlgetsearchpath());
137 3414 : return result;
138 : }
139 :
140 : /** Add a path at the end (lowest prio) of the search path */
141 33 : int modmgr_addpath (const char* path)
142 : {
143 33 : int result=0;
144 33 : DBG_PRINTF("path=%s",path);
145 33 : modmgr_init_ptr();
146 33 : ASSERT(modules != NULL);
147 :
148 : /* Add a module search path. */
149 33 : if ((NULL==path)||(0==path[0])) {
150 0 : fprintf(stderr,_("Module manager can not add a null or empty path.\n"));
151 : } else {
152 33 : result = lt_dladdsearchdir (path);
153 33 : if (0!=result)
154 0 : modmgr_error("lt_dladdsearchdir");
155 : }
156 33 : DBG_PRINTF("search path = %s",lt_dlgetsearchpath());
157 33 : return result;
158 : }
159 :
160 : /** Insert (with higher prio) a path before the specified one (from the current search path) */
161 1640 : int modmgr_insertpath (const char* before, const char* path)
162 : {
163 1640 : int result=0;
164 1640 : DBG_PRINTF("before=%s, path=%s",before,path);
165 1640 : modmgr_init_ptr();
166 1640 : ASSERT(modules != NULL);
167 :
168 : /* Insert a module search path. */
169 1640 : if ((NULL==path)||(0==path[0])) {
170 1109 : fprintf(stderr,_("Module manager can not insert a null or empty path.\n"));
171 : } else {
172 531 : result = lt_dlinsertsearchdir (before,path);
173 531 : if (0!=result)
174 495 : modmgr_error("lt_dlinsertsearchdir");
175 : }
176 1640 : DBG_PRINTF("search path = %s",lt_dlgetsearchpath());
177 1640 : return result;
178 : }
179 :
180 : /** Get a read-only pointer on the current search path */
181 10573 : const char* modmgr_getpath ()
182 : {
183 10573 : DBG_TRACE;
184 10573 : modmgr_init_ptr();
185 10573 : ASSERT(modules != NULL);
186 :
187 10573 : return lt_dlgetsearchpath();
188 : }
189 :
190 : /** Load a module, initialize it, add it to the list and return an opaque handle */
191 18 : modmgr_module_t modmgr_load(const char* modfile)
192 : {
193 : modules_t* module;
194 : moduleinfo_t* (*l_onLoad)();
195 : modules_t* it;
196 :
197 18 : DBG_PRINTF("modfile=%s",modfile);
198 18 : modmgr_init_ptr();
199 18 : ASSERT(modules != NULL);
200 :
201 18 : if ((NULL==modfile)||(0==modfile[0])) {
202 0 : fprintf(stderr,_("Module manager can not load a null or empty modfile.\n"));
203 0 : return NULL;
204 : }
205 :
206 18 : module = (modules_t*)malloc(sizeof(modules_t));
207 18 : if ( NULL == module ) {
208 : /* TRANSLATORS: module filename */
209 0 : fprintf(stderr,_("Module manager lacks RAM (OOM) to create a module structure for %s.\n"),modfile);
210 0 : return NULL;
211 : }
212 :
213 18 : DBG_PRINTF("Loading module file %s",modfile);
214 18 : module->handle = lt_dlopenext (modfile);
215 18 : if (NULL==module->handle) {
216 5 : modmgr_error("lt_dlopenext");
217 5 : fprintf(stderr,_("Module manager can not load %s.\n"),modfile);
218 5 : free(module);
219 5 : return NULL;
220 : }
221 :
222 : #ifndef NDEBUG
223 : {
224 13 : const lt_dlinfo *li = lt_dlgetinfo(module->handle);
225 13 : if (0!=modmgr_error("lt_dlgetinfo")) {
226 0 : DBG_MSG("lt_dlgetinfo failed");
227 0 : abort();
228 : }
229 13 : DBG_PRINTF ("ltinfo: filename = %s ", li->filename);
230 13 : DBG_PRINTF ("ltinfo: name(ref_count) = %s(%d)", li->name, li->ref_count);
231 : }
232 : #endif
233 :
234 13 : DBG_PRINTF("Search loaded module (%p) in the module list",module->handle);
235 13 : it = modules;
236 26 : while ((it)&&(it->handle!=module->handle))
237 13 : it = it->next;
238 :
239 : /* Module already loaded and initialized, free structure and return module */
240 13 : if (NULL != it) {
241 2 : DBG_PRINTF("Module (%p) already loaded, no initialization",module->handle);
242 2 : free(module);
243 2 : return it;
244 : }
245 : #ifndef NDEBUG
246 : else
247 11 : DBG_PRINTF("Module (%p) not yet loaded, keep and initialize",module->handle);
248 : #endif
249 :
250 : /* Find the entry points. */
251 11 : *(void**)(&l_onLoad) = lt_dlsym (module->handle, "onLoad");
252 :
253 : /* Mandatory entry point not found, cancel the load */
254 11 : if (0!=modmgr_error("lt_dlsym")) {
255 : /* TRANSLATORS: module filename */
256 0 : fprintf(stderr,_("Module manager can not find the entry point (onLoad) not found (%s).\n"),modfile);
257 : /* Unload to decrement the ref counter */
258 0 : if (0!=lt_dlclose(module->handle)) {
259 0 : modmgr_error("lt_dlclose");
260 : /* TRANSLATORS: module filename */
261 0 : fprintf(stderr,_("Critical: Module manager can not unload partially loaded invalid module (%s).\n"),modfile);
262 : /* Do not cleanup, immediate abort to help debugging */
263 0 : abort();
264 : }
265 0 : free(module);
266 0 : return NULL;
267 : }
268 :
269 : /* Execute entry point to initialize, if found */
270 11 : DBG_PRINTF("Module (%p) entry point found (%p), initializing",module->handle, l_onLoad);
271 11 : module->modinfo = l_onLoad();
272 11 : if (module->modinfo == NULL) {
273 0 : fprintf(stderr,_("Invalid module(%s): entry point (onLoad) did not return module info.\n"),modfile);
274 0 : free(module);
275 0 : return NULL;
276 : }
277 :
278 : #ifndef NDEBUG
279 11 : DBG_PRINTF ("modinfo: name (version) = %s (v%d.%d.%d)",
280 : module->modinfo->moduleName,
281 : module->modinfo->moduleMajor,
282 : module->modinfo->moduleMinor,
283 : module->modinfo->modulePatch);
284 11 : DBG_PRINTF ("modinfo: description = %s",
285 : module->modinfo->moduleDesc);
286 11 : DBG_PRINTF ("modinfo: URL = %s",
287 : module->modinfo->moduleURL);
288 11 : DBG_PRINTF ("modinfo: author (email) = %s (%s)",
289 : module->modinfo->moduleAuthor,
290 : module->modinfo->moduleEmail);
291 11 : DBG_PRINTF ("modinfo: license = %s",
292 : module->modinfo->moduleLicense);
293 : #endif
294 :
295 : /* Add module structure to module list */
296 11 : DBG_PRINTF("Module (%p) registration in the list",module->handle);
297 : /** @todo critical section */
298 11 : module->next = modules;
299 11 : modules = module;
300 :
301 11 : DBG_PRINTF("Return the module(%p) handle(%p)",module->handle, module);
302 11 : return module;
303 : }
304 :
305 : /** Decrement the usage counter, if last usage, remove from the module list and call onUnload */
306 13 : void modmgr_unload(modmgr_module_t module)
307 : {
308 : modules_t *it;
309 : modules_t *prevmodule;
310 : uint8_t (*l_onUnload)();
311 :
312 13 : DBG_PRINTF("module(%p)",module);
313 13 : modmgr_init_ptr();
314 13 : ASSERT(modules != NULL);
315 :
316 13 : if (NULL==module) {
317 0 : fprintf(stderr,_("Module manager can not unload a null module.\n"));
318 0 : return;
319 : }
320 :
321 : /* Find the module if loaded */
322 13 : it = modules;
323 13 : prevmodule = NULL;
324 15 : while ((it)&&(module!=it)) {
325 2 : prevmodule = it;
326 2 : it = it->next;
327 : }
328 13 : ASSERT(module==it); /* Module not found */
329 :
330 : {
331 : /* Get the reference (loading) counter */
332 : const lt_dlinfo *li;
333 13 : ASSERT(NULL!=module->handle);
334 13 : li = lt_dlgetinfo(module->handle);
335 13 : if (0!=modmgr_error("lt_dlgetinfo")) {
336 0 : DBG_MSG("lt_dlgetinfo failed");
337 0 : fprintf(stderr,_("Critical: Module manager can not get module counter before unload.\n"));
338 0 : abort();
339 : }
340 13 : DBG_PRINTF ("ltinfo = %s (%s:%d)",
341 : li->filename, li->name,
342 : li->ref_count);
343 : /* Execute onUnLoad only if really unloading the very last occurence */
344 13 : if (1==li->ref_count) {
345 11 : DBG_MSG("Last module usage : call onUnLoad");
346 11 : *(void**)(&l_onUnload) = lt_dlsym (it->handle, "onUnload");
347 11 : if (0!=modmgr_error("lt_dlsym")) {
348 0 : fprintf(stderr,_("Module manager can not find the module exit point (onUnload).\n"));
349 : } else {
350 : uint8_t result;
351 11 : result = 0;
352 : /* Call the exit point function. */
353 11 : result = l_onUnload();
354 11 : if (result != 0)
355 : /* TRANSLATORS: result code, return value */
356 0 : fprintf(stderr,_("Module manager received an error i(%d) from the module exit point (onUnload).\n"),result);
357 11 : DBG_PRINTF ("onUnLoad retCode : %d", result);
358 : }
359 :
360 : /* Remove module record from module list */
361 11 : if (NULL!=prevmodule)
362 2 : prevmodule->next = it->next;
363 : else
364 9 : modules = it->next;
365 : /* Actually Unload because this was the last usage */
366 11 : if (0!=lt_dlclose(module->handle))
367 0 : modmgr_error("lt_dlclose");
368 11 : free(it);
369 : } else {
370 2 : DBG_MSG("Module still used, do not call onUnLoad");
371 : /* Unload : Only to decrement the ref counter */
372 2 : if (0!=lt_dlclose(module->handle))
373 0 : modmgr_error("lt_dlclose");
374 : }
375 : }
376 : }
377 :
378 : /** Output the list of modules */
379 62 : void modmgr_list()
380 : {
381 : modules_t* it;
382 :
383 62 : DBG_TRACE;
384 62 : modmgr_init_ptr();
385 62 : ASSERT(modules != NULL);
386 :
387 62 : it = modules;
388 62 : fprintf(stderr,_("--- Module list : BEGIN ------------------------------\n"));
389 144 : while (it) {
390 : /* If not on the sentinel */
391 82 : if (it->handle) {
392 40 : fprintf ( stderr,
393 : "ltinfo = %s (%s:%d), "
394 : "name (version) = %s (v%d.%d.%d), "
395 : "description = %s, "
396 : "URL = %s, "
397 : "author (email) = %s (%s), "
398 : "license = %s\n",
399 20 : lt_dlgetinfo(it->handle)->filename,
400 20 : lt_dlgetinfo(it->handle)->name,
401 20 : lt_dlgetinfo(it->handle)->ref_count,
402 20 : it->modinfo->moduleName,
403 20 : it->modinfo->moduleMajor,
404 20 : it->modinfo->moduleMinor,
405 20 : it->modinfo->modulePatch,
406 20 : it->modinfo->moduleDesc,
407 20 : it->modinfo->moduleURL,
408 20 : it->modinfo->moduleAuthor,
409 20 : it->modinfo->moduleEmail,
410 20 : it->modinfo->moduleLicense
411 : );
412 : }
413 82 : it = it->next;
414 : }
415 62 : fprintf(stderr,_("--- Module list : END --------------------------------\n"));
416 62 : return;
417 : }
418 :
419 : /** Resolve a module symbol pointer */
420 18 : void* modmgr_getsymbol(const modmgr_module_t module, const char* szSymbol)
421 : {
422 : modules_t* it;
423 : void* pSymbol;
424 :
425 18 : DBG_PRINTF("module=%p, Symbol=%s",module,szSymbol);
426 :
427 18 : if (NULL==module) {
428 5 : fprintf(stderr,_("Module manager can not resolve a symbol from a null module.\n"));
429 5 : return NULL;
430 : }
431 :
432 13 : if ((NULL==szSymbol)||(0==szSymbol[0])) {
433 0 : fprintf(stderr,_("Module manager can not resolve a null or empty symbol name.\n"));
434 0 : return NULL;
435 : }
436 :
437 : /* Search the module */
438 13 : it = modules;
439 13 : while ((it)&&(it!=module))
440 0 : it = it->next;
441 13 : ASSERT(NULL!=it);
442 13 : ASSERT(module==it);
443 :
444 13 : pSymbol = lt_dlsym (it->handle, szSymbol);
445 13 : if (0!=modmgr_error("lt_dlsym")) {
446 0 : return NULL;
447 : }
448 :
449 13 : return pSymbol;
450 : }
|