root/src/libsir/src/sirplugins.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. _sir_plugin_load
  2. _sir_plugin_probe
  3. _sir_plugin_getexport
  4. _sir_plugin_unload
  5. _sir_plugin_add
  6. _sir_plugin_rem
  7. _sir_plugin_destroy
  8. _sir_plugin_cache_pred_id
  9. _sir_plugin_cache_add
  10. _sir_plugin_cache_find_id
  11. _sir_plugin_cache_find
  12. _sir_plugin_cache_rem
  13. _sir_plugin_cache_destroy
  14. _sir_plugin_cache_dispatch

   1 /*
   2  * sirplugins.c
   3  *
   4  * Version: 2.2.5
   5  *
   6  * -----------------------------------------------------------------------------
   7  *
   8  * SPDX-License-Identifier: MIT
   9  *
  10  * Copyright (c) 2018-2024 Ryan M. Lederman <lederman@gmail.com>
  11  *
  12  * Permission is hereby granted, free of charge, to any person obtaining a copy of
  13  * this software and associated documentation files (the "Software"), to deal in
  14  * the Software without restriction, including without limitation the rights to
  15  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  16  * the Software, and to permit persons to whom the Software is furnished to do so,
  17  * subject to the following conditions:
  18  *
  19  * The above copyright notice and this permission notice shall be included in all
  20  * copies or substantial portions of the Software.
  21  *
  22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  24  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  25  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  26  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  27  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28  *
  29  * -----------------------------------------------------------------------------
  30  */
  31 
  32 #include "sir/plugins.h"
  33 #include "sir/internal.h"
  34 
  35 sirpluginid _sir_plugin_load(const char* path) {
     /* [previous][next][first][last][top][bottom][index][help] */
  36 #if !defined(SIR_NO_PLUGINS)
  37     (void)_sir_seterror(_SIR_E_NOERROR);
  38 
  39     if (!_sir_sanity() || !_sir_validstr(path))
  40         return 0U;
  41 
  42     sir_plugin* plugin = (sir_plugin*)calloc(1, sizeof(sir_plugin));
  43     if (!plugin)
  44         return _sir_handleerr(errno);
  45 
  46 # if !defined (__WIN__)
  47     plugin->handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
  48     if (!plugin->handle) {
  49         const char* err = dlerror();
  50         _sir_selflog("error: dlopen('%s') failed (%s)", path, _SIR_PRNSTR(err));
  51         _sir_plugin_destroy(&plugin);
  52         return _sir_handleerr(errno);
  53     }
  54 # else /* __WIN__ */
  55     UINT old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
  56     plugin->handle      = LoadLibraryA(path);
  57     SetErrorMode(old_error_mode);
  58     if (!plugin->handle) {
  59         DWORD err = GetLastError();
  60         _sir_selflog("error: LoadLibraryA(%s) failed (%lu)", path, err);
  61         _sir_plugin_destroy(&plugin);
  62         return _sir_handlewin32err(err);
  63     }
  64 # endif
  65 
  66     plugin->loaded = true;
  67     plugin->path   = strndup(path, strnlen(path, SIR_MAXPATH));
  68 
  69     if (!_sir_validstrnofail(plugin->path)) {
  70         _sir_plugin_destroy(&plugin);
  71         return _sir_handleerr(errno);
  72     }
  73 
  74     _sir_selflog("loaded plugin (path: '%s', addr: %p); probing...", //-V576
  75         plugin->path, plugin->handle);
  76 
  77     return _sir_plugin_probe(plugin);
  78 #else
  79     SIR_UNUSED(path);
  80     return 0U;
  81 #endif
  82 }
  83 
  84 sirpluginid _sir_plugin_probe(sir_plugin* plugin) {
     /* [previous][next][first][last][top][bottom][index][help] */
  85 #if !defined(SIR_NO_PLUGINS)
  86     sirpluginid retval = 0U;
  87     if (plugin) {
  88 # if SIR_PLUGIN_VCURRENT == SIR_PLUGIN_V1
  89        /* if/when new versions of plugin interfaces are introduced, we will need to
  90         * modify/extend the following code:
  91         *
  92         * - remove the enclosing #if
  93         * - get the v1 exports (all versions will have v1 exports), resolve them,
  94         * and call sir_plugin_query.
  95         * - switch on version returned to resolve additional exports. this will
  96         * necessitate additional versioned interface structures as members of the
  97         * sir_plugin struct, e.g. ifacev1, ifacev2). */
  98         plugin->iface.query   = (sir_plugin_queryfn)
  99             _sir_plugin_getexport(plugin->handle, SIR_PLUGIN_EXPORT_QUERY);
 100         plugin->iface.init    = (sir_plugin_initfn)
 101             _sir_plugin_getexport(plugin->handle, SIR_PLUGIN_EXPORT_INIT);
 102         plugin->iface.write   = (sir_plugin_writefn)
 103             _sir_plugin_getexport(plugin->handle, SIR_PLUGIN_EXPORT_WRITE);
 104         plugin->iface.cleanup = (sir_plugin_cleanupfn)
 105             _sir_plugin_getexport(plugin->handle, SIR_PLUGIN_EXPORT_CLEANUP);
 106 
 107         if (!plugin->iface.query || !plugin->iface.init ||
 108             !plugin->iface.write || !plugin->iface.cleanup) {
 109             _sir_selflog("error: export(s) not resolved for plugin (path:"
 110                          " '%s', addr: %p)!", plugin->path, plugin->handle);
 111             _sir_selflog("exports (query: %"PRIxPTR", init: %"PRIxPTR", write:"
 112                          " %"PRIxPTR", cleanup; %"PRIxPTR")",
 113                          (uintptr_t)plugin->iface.query, (uintptr_t)plugin->iface.init,
 114                          (uintptr_t)plugin->iface.write, (uintptr_t)plugin->iface.cleanup);
 115             _sir_plugin_destroy(&plugin);
 116             return _sir_seterror(_SIR_E_PLUGINBAD);
 117         }
 118 # else
 119 #  error "plugin version not implemented"
 120 # endif
 121         /* query the plugin for information. */
 122         if (!plugin->iface.query(&plugin->info)) {
 123             _sir_selflog("error: plugin (path: '%s', addr: %p) returned false from"
 124                          " query fn!", plugin->path, plugin->handle);
 125             _sir_plugin_destroy(&plugin);
 126             (void)_sir_seterror(_SIR_E_PLUGINERR);
 127             return 0U;
 128         }
 129 
 130         /* verify version. */
 131         if (!plugin->info.iface_ver || plugin->info.iface_ver > SIR_PLUGIN_VCURRENT) {
 132             _sir_selflog("error: plugin (path: '%s', addr: %p) has version"
 133                          " %"PRIu8"; libsir has %d", plugin->path, plugin->handle,
 134                         plugin->info.iface_ver, SIR_PLUGIN_VCURRENT);
 135             _sir_plugin_destroy(&plugin);
 136             (void)_sir_seterror(_SIR_E_PLUGINVER);
 137             return 0U;
 138         }
 139 
 140         bool data_valid = true;
 141 
 142         /* verify level registration bitmask. */
 143         if (!_sir_validlevels(plugin->info.levels)) {
 144             _sir_selflog("error: plugin (path: '%s', addr: %p) has invalid levels"
 145                          " %04"PRIx16, plugin->path, plugin->handle, plugin->info.levels);
 146             data_valid = false;
 147         }
 148 
 149         /* verify formatting options bitmask. */
 150         if (!_sir_validopts(plugin->info.opts)) {
 151             _sir_selflog("error: plugin (path: '%s', addr: %p) has invalid opts"
 152                          " %08"PRIx32, plugin->path, plugin->handle, plugin->info.opts);
 153             data_valid = false;
 154         }
 155 
 156         /* verify strings */
 157         if (!_sir_validstrnofail(plugin->info.author) ||
 158             !_sir_validstrnofail(plugin->info.desc)) {
 159             _sir_selflog("error: plugin (path: '%s', addr: %p) has invalid author"
 160                          " or description", plugin->path, plugin->handle);
 161             data_valid = false;
 162         }
 163 
 164         /* if any category of data is invalid, fail and unload. */
 165         if (!data_valid) {
 166             _sir_plugin_destroy(&plugin);
 167             (void)_sir_seterror(_SIR_E_PLUGINDAT);
 168             return 0U;
 169         }
 170 
 171         /* plugin is valid; tell it to initialize, assign it an id,
 172          * print its information, and add to cache. */
 173         if (!plugin->iface.init()) {
 174             _sir_selflog("error: plugin (path: '%s', addr: %p) failed to initialize!",
 175                 plugin->path, plugin->handle);
 176             _sir_plugin_destroy(&plugin);
 177             (void)_sir_seterror(_SIR_E_PLUGINERR);
 178             return 0U;
 179         }
 180 
 181         plugin->id    = FNV32_1a((const uint8_t*)&plugin->iface, sizeof(sir_pluginiface));
 182         plugin->valid = true;
 183 
 184         _sir_selflog("successfully validated plugin (path: '%s', id: %08"PRIx32"); properties:"
 185                      SIR_EOL "{"
 186                      SIR_EOL "\tversion = %"PRIu8".%"PRIu8".%"PRIu8
 187                      SIR_EOL "\tlevels = %04"PRIx16
 188                      SIR_EOL "\topts = %08"PRIx32
 189                      SIR_EOL "\tauthor = '%s'"
 190                      SIR_EOL "\tdesc = '%s'"
 191                      SIR_EOL "\tcaps = %016"PRIx64
 192                      SIR_EOL "}",
 193                      plugin->path, plugin->id, plugin->info.maj_ver, plugin->info.min_ver,
 194                      plugin->info.bld_ver, plugin->info.levels, plugin->info.opts,
 195                      _SIR_PRNSTR(plugin->info.author), _SIR_PRNSTR(plugin->info.desc),
 196                      plugin->info.caps);
 197 
 198         retval = _sir_plugin_add(plugin);
 199         if (0U == retval) {
 200             _sir_selflog("error: failed to add plugin (path: '%s', addr: %p) to"
 201                          " cache; unloading", plugin->path, plugin->handle);
 202             _sir_plugin_destroy(&plugin);
 203         }
 204     }
 205 
 206     return retval;
 207 #else
 208     SIR_UNUSED(plugin);
 209     return 0U;
 210 #endif
 211 }
 212 
 213 sir_pluginexport _sir_plugin_getexport(sir_pluginhandle handle, const char* name) {
     /* [previous][next][first][last][top][bottom][index][help] */
 214 #if !defined(SIR_NO_PLUGINS)
 215     if (!_sir_validptr(handle) || !_sir_validstr(name))
 216         return NULL;
 217 
 218 # if !defined(__WIN__)
 219     sir_pluginexport addr = NULL;
 220     *(void**)(&addr) = dlsym(handle, name);
 221 
 222     if (!addr) {
 223         const char* err = dlerror();
 224         _sir_selflog("error: dlsym(%p, '%s') failed (%s)", handle, name,
 225             _SIR_PRNSTR(err));
 226         (void)_sir_handleerr(errno);
 227         return NULL;
 228     }
 229 # else /* __WIN__ */
 230     sir_pluginexport addr = GetProcAddress(handle, name);
 231     if (!addr) {
 232         DWORD err = GetLastError();
 233         _sir_selflog("error: GetProcAddress(%p, '%s') failed (%lu)", handle,
 234             name, err);
 235         (void)_sir_handlewin32err(err);
 236         return NULL;
 237     }
 238 # endif
 239     _sir_selflog("successfully resolved plugin export (name: '%s', addr: %"
 240                  PRIxPTR")", name, (uintptr_t)addr);
 241     return addr;
 242 #else
 243     SIR_UNUSED(handle);
 244     SIR_UNUSED(name);
 245     return NULL;
 246 #endif
 247 }
 248 
 249 bool _sir_plugin_unload(sir_plugin* plugin) {
     /* [previous][next][first][last][top][bottom][index][help] */
 250 #if !defined(SIR_NO_PLUGINS)
 251     if (!_sir_validptrnofail(plugin) || !_sir_validptrnofail(plugin->handle)) {
 252         _sir_selflog("error: plugin object (%p) or handle (%p) are null;"
 253                      " cannot unload!", (void*)plugin, (plugin ? plugin->handle : NULL));
 254         return false;
 255     }
 256 
 257     /* if the plugin cleanup export was resolved, call it. */
 258     if (plugin->iface.cleanup && !plugin->iface.cleanup())
 259         _sir_selflog("warning: plugin (path: '%s', addr: %p) reports unsuccessful"
 260                      " cleanup!", plugin->path, plugin->handle);
 261 
 262 # if !defined(__WIN__)
 263     if (0 != dlclose(plugin->handle)) {
 264         const char* err = dlerror();
 265         _sir_selflog("error: dlclose(%p) failed (%s)", plugin->handle, _SIR_PRNSTR(err));
 266         return _sir_handleerr(errno);
 267     }
 268 # else /* __WIN__ */
 269     if (!FreeLibrary(plugin->handle)) {
 270         DWORD err = GetLastError();
 271         _sir_selflog("error: FreeLibrary(%p) failed (%lu)", plugin->handle, err);
 272         return _sir_handlewin32err(err);
 273     }
 274 # endif
 275 
 276     plugin->handle = NULL;
 277     plugin->loaded = false;
 278     _sir_selflog("unloaded plugin (path: '%s', id: %08"PRIx32")", plugin->path,
 279         plugin->id);
 280     return true;
 281 #else
 282     SIR_UNUSED(plugin);
 283     return false;
 284 #endif
 285 }
 286 
 287 sirpluginid _sir_plugin_add(sir_plugin* plugin) {
     /* [previous][next][first][last][top][bottom][index][help] */
 288 #if !defined(SIR_NO_PLUGINS)
 289     sirpluginid retval = 0U;
 290 
 291     if (_sir_validptr(plugin)) {
 292         _SIR_LOCK_SECTION(sir_plugincache, spc, SIRMI_PLUGINCACHE, 0U);
 293         retval = _sir_plugin_cache_add(spc, plugin);
 294         _SIR_UNLOCK_SECTION(SIRMI_PLUGINCACHE);
 295     }
 296 
 297     return retval;
 298 #else
 299     SIR_UNUSED(plugin);
 300     return 0U;
 301 #endif
 302 }
 303 
 304 bool _sir_plugin_rem(sirpluginid id) {
     /* [previous][next][first][last][top][bottom][index][help] */
 305 #if !defined(SIR_NO_PLUGINS)
 306     (void)_sir_seterror(_SIR_E_NOERROR);
 307 
 308     if (!_sir_sanity())
 309         return false;
 310 
 311     _SIR_LOCK_SECTION(sir_plugincache, spc, SIRMI_PLUGINCACHE, false);
 312     bool retval = _sir_plugin_cache_rem(spc, id);
 313     _SIR_UNLOCK_SECTION(SIRMI_PLUGINCACHE);
 314 
 315     return retval;
 316 #else
 317     SIR_UNUSED(id);
 318     return false;
 319 #endif
 320 }
 321 
 322 void _sir_plugin_destroy(sir_plugin** plugin) {
     /* [previous][next][first][last][top][bottom][index][help] */
 323 #if !defined(SIR_NO_PLUGINS)
 324     if (_sir_validptrptr(plugin) && _sir_validptr(*plugin)) {
 325         bool unloaded = _sir_plugin_unload(*plugin);
 326         SIR_ASSERT_UNUSED(unloaded, unloaded);
 327 
 328         _sir_safefree(&(*plugin)->path);
 329         _sir_safefree(plugin);
 330     }
 331 #else
 332     SIR_UNUSED(plugin);
 333 #endif
 334 }
 335 
 336 bool _sir_plugin_cache_pred_id(const void* match, const sir_plugin* iter) {
     /* [previous][next][first][last][top][bottom][index][help] */
 337 #if !defined(SIR_NO_PLUGINS)
 338     return iter->id == *((const sirpluginid*)match);
 339 #else
 340     SIR_UNUSED(match);
 341     SIR_UNUSED(iter);
 342     return false;
 343 #endif
 344 }
 345 
 346 sirpluginid _sir_plugin_cache_add(sir_plugincache* spc, sir_plugin* plugin) {
     /* [previous][next][first][last][top][bottom][index][help] */
 347 #if !defined(SIR_NO_PLUGINS)
 348     if (!_sir_validptr(spc) || !_sir_validptr(plugin))
 349         return 0U;
 350 
 351     if (spc->count >= SIR_MAXPLUGINS) {
 352         (void)_sir_seterror(_SIR_E_NOROOM);
 353         return 0U;
 354     }
 355 
 356     const sir_plugin* existing = _sir_plugin_cache_find_id(spc, plugin->id);
 357     if (NULL != existing) {
 358         _sir_selflog("error: already have plugin (path: '%s', id: %08"PRIx32")",
 359             existing->path, plugin->id);
 360         (void)_sir_seterror(_SIR_E_DUPITEM);
 361         return 0U;
 362     }
 363 
 364     _sir_selflog("adding plugin (path: %s, id: %08"PRIx32"); count = %zu",
 365     plugin->path, plugin->id, spc->count + 1);
 366     spc->plugins[spc->count++] = plugin;
 367     return plugin->id;
 368 #else
 369     SIR_UNUSED(spc);
 370     SIR_UNUSED(plugin);
 371     return 0U;
 372 #endif
 373 }
 374 
 375 sir_plugin* _sir_plugin_cache_find_id(const sir_plugincache* spc, sirpluginid id) {
     /* [previous][next][first][last][top][bottom][index][help] */
 376 #if !defined(SIR_NO_PLUGINS)
 377     return _sir_plugin_cache_find(spc, &id, &_sir_plugin_cache_pred_id);
 378 #else
 379     SIR_UNUSED(spc);
 380     SIR_UNUSED(id);
 381     return NULL;
 382 #endif
 383 }
 384 
 385 sir_plugin* _sir_plugin_cache_find(const sir_plugincache* spc, const void* match,
     /* [previous][next][first][last][top][bottom][index][help] */
 386     sir_plugin_pred pred) {
 387 #if !defined(SIR_NO_PLUGINS)
 388     if (!_sir_validptr(spc) || !_sir_validptr(match) || !_sir_validfnptr(pred))
 389         return NULL;
 390 
 391     for (size_t n = 0; n < spc->count; n++) {
 392         if (pred(match, spc->plugins[n]))
 393             return spc->plugins[n];
 394     }
 395 
 396     return NULL;
 397 #else
 398     SIR_UNUSED(spc);
 399     SIR_UNUSED(match);
 400     SIR_UNUSED(pred);
 401     return NULL;
 402 #endif
 403 }
 404 
 405 bool _sir_plugin_cache_rem(sir_plugincache* spc, sirpluginid id) {
     /* [previous][next][first][last][top][bottom][index][help] */
 406 #if !defined(SIR_NO_PLUGINS)
 407     if (!_sir_validptr(spc))
 408         return false;
 409 
 410     for (size_t n = 0; n < spc->count; n++) {
 411         if (spc->plugins[n]->id == id) {
 412             _sir_selflog("removing plugin (path: '%s', id: %"PRIx32"); count = %zu",
 413                 spc->plugins[n]->path, spc->plugins[n]->id, spc->count - 1);
 414 
 415             _sir_plugin_destroy(&spc->plugins[n]);
 416 
 417             for (size_t i = n; i < spc->count - 1; i++) {
 418                 spc->plugins[i] = spc->plugins[i + 1];
 419                 spc->plugins[i + 1] = NULL;
 420             }
 421 
 422             spc->count--;
 423             return true;
 424         }
 425     }
 426 
 427     return _sir_seterror(_SIR_E_NOITEM);
 428 #else
 429     SIR_UNUSED(spc);
 430     SIR_UNUSED(id);
 431     return false;
 432 #endif
 433 }
 434 
 435 bool _sir_plugin_cache_destroy(sir_plugincache* spc) {
     /* [previous][next][first][last][top][bottom][index][help] */
 436 #if !defined(SIR_NO_PLUGINS)
 437     if (!_sir_validptr(spc))
 438         return false;
 439 
 440     while (spc->count > 0) {
 441         size_t idx = spc->count - 1;
 442         _sir_plugin_destroy(&spc->plugins[idx]);
 443         spc->plugins[idx] = NULL;
 444         spc->count--;
 445     }
 446 
 447     (void)memset(spc, 0, sizeof(sir_plugincache));
 448     return true;
 449 #else
 450     SIR_UNUSED(spc);
 451     return false;
 452 #endif
 453 }
 454 
 455 bool _sir_plugin_cache_dispatch(const sir_plugincache* spc, sir_level level, sirbuf* buf,
     /* [previous][next][first][last][top][bottom][index][help] */
 456     size_t* dispatched, size_t* wanted) {
 457 #if !defined(SIR_NO_PLUGINS)
 458     if (!_sir_validptr(spc) || !_sir_validlevel(level) || !_sir_validptr(buf) ||
 459         !_sir_validptr(dispatched) || !_sir_validptr(wanted))
 460         return false;
 461 
 462     const char* wrote    = NULL;
 463     sir_options lastopts = 0;
 464 
 465     *dispatched = 0;
 466     *wanted     = 0;
 467 
 468     for (size_t n = 0; n < spc->count; n++) {
 469         if (!_sir_bittest(spc->plugins[n]->info.levels, level)) {
 470             _sir_selflog("level %04"PRIx16" not set in level mask (%04"PRIx16
 471                          ") for plugin (path: '%s', id: %08"PRIx32"); skipping",
 472                          level, spc->plugins[n]->info.levels, spc->plugins[n]->path,
 473                          spc->plugins[n]->id);
 474             continue;
 475         }
 476 
 477         (*wanted)++;
 478 
 479         if (!wrote || spc->plugins[n]->info.opts != lastopts) {
 480             wrote = _sir_format(false, spc->plugins[n]->info.opts, buf);
 481             SIR_ASSERT(wrote);
 482             lastopts = spc->plugins[n]->info.opts;
 483         }
 484 
 485         if (wrote && spc->plugins[n]->iface.write(level, wrote)) {
 486             (*dispatched)++;
 487         } else {
 488             _sir_selflog("error: write to plugin (path: '%s', id: %08"PRIx32")"
 489                          " failed!", spc->plugins[n]->path, spc->plugins[n]->id);
 490         }
 491     }
 492 
 493     return (*dispatched == *wanted);
 494 #else
 495     SIR_UNUSED(spc);
 496     SIR_UNUSED(level);
 497     SIR_UNUSED(buf);
 498     SIR_UNUSED(dispatched);
 499     SIR_UNUSED(wanted);
 500     return false;
 501 #endif
 502 }

/* [previous][next][first][last][top][bottom][index][help] */