1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/**         @file  modmgr.c
 *         @brief  Module manager implementation
 *          @date  25/11/2017
 *        @author  François Cerbelle (Fanfan), francois@cerbelle.net
 *     @copyright  Copyright (c) 2017, François Cerbelle
 *
 *   @internal
 *       Compiler  gcc
 *  Last modified  2024-07-31 18:19
 *   Organization  Cerbelle.net
 *        Company  Home
 *
 *   This source code is released for free distribution under the terms of the
 *   GNU General Public License as published by the Free Software Foundation.
 */


#include "modmgr.h"
#include "gettext.h"
#define _(String) gettext (String)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <limits.h>
#ifndef PATH_MAX
#  define PATH_MAX 255
#endif

#include <string.h>
#include <ltdl.h>

#if LIBPTHREAD
# include <pthread.h>
#endif

#include "debug/assert.h"
#include "debug/memory.h"

/** Module list item structure */
typedef struct modules_s {
    lt_dlhandle handle;
    moduleinfo_t* modinfo;
    struct modules_s *next;
} modules_t;

/** Module list head */
static modules_t* modules = NULL;

static char * dlerrordup (char *errormsg)
{
    char *error = (char *) lt_dlerror ();
    if (error && !errormsg)
        errormsg = strdup (error);
    return errormsg;
}

static uint8_t modmgr_error(const char* contextname)
{
    char* errormsg=NULL;

    errormsg = dlerrordup (errormsg);
    if (NULL != errormsg) {
        fprintf(stderr,"%s: %s\n",contextname,errormsg);
        free(errormsg);
        return 1;
    }
    DBG_PRINTF("%s: OK",contextname);
    return 0;
}

static void modmgr_init_real();
/** NOOP init function called after real init occured */
static void modmgr_init_noop()
{
    DBG_MSG("modmgr already initialized.");
    return;
}
/** Initialization functor on real init first and then on NOOP version */
static void (*modmgr_init_ptr)() = modmgr_init_real;

/** Sanity/Debug check at exit to detect unloaded zombies modules */
static void modmgr_atexit()
{
    DBG_MSG("Validate that module list is empty.");
    /* Checks that all modules were unloaded before exit */
    /* Module list should be either not initialized or with sentinel only */
    ASSERT((modules==NULL)||(modules->handle==NULL));
    /* Finished with ltdl now. */
    if (0!=lt_dlexit ()) {
        modmgr_error("lt_dlexit");
        /* We should abort here, but aborting in atexit() would be stupid */
    }
}

/** modmgr real init function called executed at the first invocation */
static void modmgr_init_real()
{
    /* Needs to be called once and only once */
    ASSERT(modules == NULL);

    DBG_MSG("LTDL initialization");
    if (0 != lt_dlinit ())
        modmgr_error("lt_dlinit");

    /* Initialize the module list with a sentinel */
    DBG_MSG("Module list initialization");
    modules = (modules_t*)malloc(sizeof(modules_t));
    if ( NULL == modules ) {
        fprintf(stderr,_("Critical: Module manager lacks RAM (OOM) to initialize.\n"));
        abort();
    }
    modules->handle = NULL;
    modules->modinfo = NULL;
    modules->next = NULL;
    DBG_MSG("Register an atexit healthcheck and cleanup fonction");
    atexit(modmgr_atexit);

    /* Avoid double calls */
    modmgr_init_ptr = modmgr_init_noop;
}

/** Reset and initialize the modules search path */
int modmgr_setpath (const char* path)
{
    int result;
    DBG_PRINTF("path=%s",path);
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    /* Set the module search path. */
    result = lt_dlsetsearchpath (path);
    if (0!=result)
        modmgr_error("lt_dlsetsearchpath");
    DBG_PRINTF("search path = %s",lt_dlgetsearchpath());
    return result;
}

/** Add a path at the end (lowest prio) of the search path */
int modmgr_addpath (const char* path)
{
    int result=0;
    DBG_PRINTF("path=%s",path);
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    /* Add a module search path. */
    if ((NULL==path)||(0==path[0])) {
        fprintf(stderr,_("Module manager can not add a null or empty path.\n"));
    } else {
        result = lt_dladdsearchdir (path);
        if (0!=result)
            modmgr_error("lt_dladdsearchdir");
    }
    DBG_PRINTF("search path = %s",lt_dlgetsearchpath());
    return result;
}

/** Insert (with higher prio) a path before the specified one (from the current search path) */
int modmgr_insertpath (const char* before, const char* path)
{
    int result=0;
    DBG_PRINTF("before=%s, path=%s",before,path);
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    /* Insert a module search path. */
    if ((NULL==path)||(0==path[0])) {
        fprintf(stderr,_("Module manager can not insert a null or empty path.\n"));
    } else {
        result = lt_dlinsertsearchdir (before,path);
        if (0!=result)
            modmgr_error("lt_dlinsertsearchdir");
    }
    DBG_PRINTF("search path = %s",lt_dlgetsearchpath());
    return result;
}

/** Get a read-only pointer on the current search path */
const char* modmgr_getpath ()
{
    DBG_TRACE;
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    return lt_dlgetsearchpath();
}

/** Load a module, initialize it, add it to the list and return an opaque handle */
modmgr_module_t modmgr_load(const char* modfile)
{
    modules_t* module;
    moduleinfo_t* (*l_onLoad)();
    modules_t* it;

    DBG_PRINTF("modfile=%s",modfile);
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    if ((NULL==modfile)||(0==modfile[0])) {
        fprintf(stderr,_("Module manager can not load a null or empty modfile.\n"));
        return NULL;
    }

    module = (modules_t*)malloc(sizeof(modules_t));
    if ( NULL == module ) {
        /* TRANSLATORS: module filename */
        fprintf(stderr,_("Module manager lacks RAM (OOM) to create a module structure for %s.\n"),modfile);
        return NULL;
    }

    DBG_PRINTF("Loading module file %s",modfile);
    module->handle = lt_dlopenext (modfile);
    if (NULL==module->handle) {
        modmgr_error("lt_dlopenext");
        fprintf(stderr,_("Module manager can not load %s.\n"),modfile);
        free(module);
        return NULL;
    }

#ifndef NDEBUG
    {
        const lt_dlinfo *li = lt_dlgetinfo(module->handle);
        if (0!=modmgr_error("lt_dlgetinfo")) {
            DBG_MSG("lt_dlgetinfo failed");
            abort();
        }
        DBG_PRINTF ("ltinfo: filename = %s ", li->filename);
        DBG_PRINTF ("ltinfo: name(ref_count) = %s(%d)", li->name, li->ref_count);
    }
#endif

    DBG_PRINTF("Search loaded module (%p) in the module list",module->handle);
    it = modules;
    while ((it)&&(it->handle!=module->handle))
        it = it->next;

    /* Module already loaded and initialized, free structure and return module */
    if (NULL != it) {
        DBG_PRINTF("Module (%p) already loaded, no initialization",module->handle);
        free(module);
        return it;
    }
#ifndef NDEBUG
    else
        DBG_PRINTF("Module (%p) not yet loaded, keep and initialize",module->handle);
#endif

    /* Find the entry points. */
    *(void**)(&l_onLoad) = lt_dlsym (module->handle, "onLoad");

    /* Mandatory entry point not found, cancel the load */
    if (0!=modmgr_error("lt_dlsym")) {
        /* TRANSLATORS: module filename */
        fprintf(stderr,_("Module manager can not find the entry point (onLoad) not found (%s).\n"),modfile);
        /* Unload to decrement the ref counter */
        if (0!=lt_dlclose(module->handle)) {
            modmgr_error("lt_dlclose");
            /* TRANSLATORS: module filename */
            fprintf(stderr,_("Critical: Module manager can not unload partially loaded invalid module (%s).\n"),modfile);
            /* Do not cleanup, immediate abort to help debugging */
            abort();
        }
        free(module);
        return NULL;
    }

    /* Execute entry point to initialize, if found */
    DBG_PRINTF("Module (%p) entry point found (%p), initializing",module->handle, l_onLoad);
    module->modinfo = l_onLoad();
    if (module->modinfo == NULL) {
        fprintf(stderr,_("Invalid module(%s): entry point (onLoad) did not return module info.\n"),modfile);
        free(module);
        return NULL;
    }

#ifndef NDEBUG
    DBG_PRINTF ("modinfo: name (version) = %s (v%d.%d.%d)",
                module->modinfo->moduleName,
                module->modinfo->moduleMajor,
                module->modinfo->moduleMinor,
                module->modinfo->modulePatch);
    DBG_PRINTF ("modinfo: description = %s",
                module->modinfo->moduleDesc);
    DBG_PRINTF ("modinfo: URL = %s",
                module->modinfo->moduleURL);
    DBG_PRINTF ("modinfo: author (email) = %s (%s)",
                module->modinfo->moduleAuthor,
                module->modinfo->moduleEmail);
    DBG_PRINTF ("modinfo: license = %s",
                module->modinfo->moduleLicense);
#endif

    /* Add module structure to module list */
    DBG_PRINTF("Module (%p) registration in the list",module->handle);
    /** @todo critical section */
    module->next = modules;
    modules = module;

    DBG_PRINTF("Return the module(%p) handle(%p)",module->handle, module);
    return module;
}

/** Decrement the usage counter, if last usage, remove from the module list and call onUnload */
void modmgr_unload(modmgr_module_t module)
{
    modules_t *it;
    modules_t *prevmodule;
    uint8_t (*l_onUnload)();

    DBG_PRINTF("module(%p)",module);
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    if (NULL==module) {
        fprintf(stderr,_("Module manager can not unload a null module.\n"));
        return;
    }

    /* Find the module if loaded */
    it = modules;
    prevmodule = NULL;
    while ((it)&&(module!=it)) {
        prevmodule = it;
        it = it->next;
    }
    ASSERT(module==it); /* Module not found */

    {
        /* Get the reference (loading) counter */
        const lt_dlinfo *li;
        ASSERT(NULL!=module->handle);
        li = lt_dlgetinfo(module->handle);
        if (0!=modmgr_error("lt_dlgetinfo")) {
            DBG_MSG("lt_dlgetinfo failed");
            fprintf(stderr,_("Critical: Module manager can not get module counter before unload.\n"));
            abort();
        }
        DBG_PRINTF ("ltinfo = %s (%s:%d)",
                    li->filename, li->name,
                    li->ref_count);
        /* Execute onUnLoad only if really unloading the very last occurence */
        if (1==li->ref_count) {
            DBG_MSG("Last module usage : call onUnLoad");
            *(void**)(&l_onUnload) = lt_dlsym (it->handle, "onUnload");
            if (0!=modmgr_error("lt_dlsym")) {
                fprintf(stderr,_("Module manager can not find the module exit point (onUnload).\n"));
            } else {
                uint8_t result;
                result = 0;
                /* Call the exit point function. */
                result = l_onUnload();
                if (result != 0)
                    /* TRANSLATORS: result code, return value */
                    fprintf(stderr,_("Module manager received an error i(%d) from the module exit point (onUnload).\n"),result);
                DBG_PRINTF ("onUnLoad retCode : %d", result);
            }

            /*  Remove module record from module list */
            if (NULL!=prevmodule)
                prevmodule->next = it->next;
            else
                modules = it->next;
            /* Actually Unload because this was the last usage */
            if (0!=lt_dlclose(module->handle))
                modmgr_error("lt_dlclose");
            free(it);
        } else {
            DBG_MSG("Module still used, do not call onUnLoad");
            /* Unload : Only to decrement the ref counter */
            if (0!=lt_dlclose(module->handle))
                modmgr_error("lt_dlclose");
        }
    }
}

/** Output the list of modules */
void modmgr_list()
{
    modules_t* it;

    DBG_TRACE;
    modmgr_init_ptr();
    ASSERT(modules != NULL);

    it = modules;
    printf(_("--- Module list :\n"));
    while (it) {
        /* If not on the sentinel */
        if (it->handle) {
            fprintf ( stderr,
                      "ltinfo = %s (%s:%d), "
                      "name (version) = %s (v%d.%d.%d), "
                      "description = %s, "
                      "URL = %s, "
                      "author (email) = %s (%s), "
                      "license = %s\n",
                      lt_dlgetinfo(it->handle)->filename,
                      lt_dlgetinfo(it->handle)->name,
                      lt_dlgetinfo(it->handle)->ref_count,
                      it->modinfo->moduleName,
                      it->modinfo->moduleMajor,
                      it->modinfo->moduleMinor,
                      it->modinfo->modulePatch,
                      it->modinfo->moduleDesc,
                      it->modinfo->moduleURL,
                      it->modinfo->moduleAuthor,
                      it->modinfo->moduleEmail,
                      it->modinfo->moduleLicense
                    );
        }
        it = it->next;
    }
    return;
}

/** Resolve a module symbol pointer */
void* modmgr_getsymbol(const modmgr_module_t module, const char* szSymbol)
{
    modules_t* it;
    void* pSymbol;

    DBG_PRINTF("module=%p, Symbol=%s",module,szSymbol);

    if (NULL==module) {
        fprintf(stderr,_("Module manager can not resolve a symbol from a null module.\n"));
        return NULL;
    }

    if ((NULL==szSymbol)||(0==szSymbol[0])) {
        fprintf(stderr,_("Module manager can not resolve a null or empty symbol name.\n"));
        return NULL;
    }

    /* Search the module */
    it = modules;
    while ((it)&&(it!=module))<--- Assuming that condition 'it' is not redundant
        it = it->next;
    ASSERT(NULL!=it);
    ASSERT(module==it);

    pSymbol = lt_dlsym (it->handle, szSymbol);<--- Null pointer dereference
    if (0!=modmgr_error("lt_dlsym")) {
        return NULL;
    }

    return pSymbol;
}