-
Marek Vavruša authored
memcached is a distributed caching system, it is a good fit for building resolvers with shared and replicated cache
587450c7
Forked from
Knot projects / Knot Resolver
8498 commits behind the upstream repository.
module.c 5.94 KiB
/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <unistd.h>
#include "lib/defines.h"
#include "lib/utils.h"
#include "lib/module.h"
/** Library extension. */
#if defined(__APPLE__)
#define LIBEXT ".dylib"
#elif _WIN32
#define LIBEXT ".lib"
#else
#define LIBEXT ".so"
#endif
/** Check ABI version, return error on mismatch. */
#define ABI_CHECK(m, prefix, symname, required) do { \
module_api_cb *_api = NULL; \
*(void **) (&_api) = load_symbol((m)->lib, (prefix), (symname)); \
if (_api == NULL) { \
return kr_error(ENOENT); \
} \
if (_api() != (required)) { \
return kr_error(ENOTSUP); \
} \
} while (0)
/** Load ABI by symbol names. */
#define ABI_LOAD(m, prefix, s_init, s_deinit, s_config, s_layer, s_prop) do { \
module_prop_cb *module_prop = NULL; \
*(void **) (&(m)->init) = load_symbol((m)->lib, (prefix), (s_init)); \
*(void **) (&(m)->deinit) = load_symbol((m)->lib, (prefix), (s_deinit)); \
*(void **) (&(m)->config) = load_symbol((m)->lib, (prefix), (s_config)); \
*(void **) (&(m)->layer) = load_symbol((m)->lib, (prefix), (s_layer)); \
*(void **) (&module_prop) = load_symbol((m)->lib, (prefix), (s_prop)); \
if (module_prop != NULL) { \
(m)->props = module_prop(); \
} \
} while(0)
/** Load prefixed symbol. */
static void *load_symbol(void *lib, const char *prefix, const char *name)
{
auto_free char *symbol = kr_strcatdup(2, prefix, name);
return dlsym(lib, symbol);
}
static int load_library(struct kr_module *module, const char *name, const char *path)
{
/* Absolute or relative path (then only library search path is used). */
auto_free char *lib_path = NULL;
if (path != NULL) {
lib_path = kr_strcatdup(4, path, "/", name, LIBEXT);
} else {
lib_path = kr_strcatdup(2, name, LIBEXT);
}
if (lib_path == NULL) {
return kr_error(ENOMEM);
}
/* Workaround for buggy _fini/__attribute__((destructor)) and dlclose(),
* this keeps the library mapped until the program finishes though. */
module->lib = dlopen(lib_path, RTLD_NOW | RTLD_NODELETE);
if (module->lib) {
return kr_ok();
}
return kr_error(ENOENT);
}
/** Load C module symbols. */
static int load_sym_c(struct kr_module *module, uint32_t api_required)
{
auto_free char *module_prefix = kr_strcatdup(2, module->name, "_");
ABI_CHECK(module, module_prefix, "api", api_required);
ABI_LOAD(module, module_prefix, "init", "deinit", "config", "layer", "props");
return kr_ok();
}
/** Bootstrap Go runtime from module. */
static int bootstrap_libgo(struct kr_module *module)
{
/* Check if linked against compatible libgo */
void (*go_check)(void) = dlsym(module->lib, "runtime_check");
void (*go_args)(int, void*) = dlsym(module->lib, "runtime_args");
void (*go_init_os)(void) = dlsym(module->lib, "runtime_osinit");
void (*go_init_sched)(void) = dlsym(module->lib, "runtime_schedinit");
void (*go_init_main)(void) = dlsym(module->lib, "__go_init_main");
if ((go_check && go_args && go_init_os && go_init_sched && go_init_main) == false) {
return kr_error(EINVAL);
}
/*
* Bootstrap runtime - this is minimal runtime, we would need a running scheduler
* and gc for coroutines and memory allocation. That would require a custom "world loop",
* message passing, and either runtime sharing or module isolation.
* https://github.com/gcc-mirror/gcc/blob/gcc-4_9_2-release/libgo/runtime/proc.c#L457
*/
char *fake_argv[2] = {
getenv("_"),
NULL
};
go_check();
go_args(1, fake_argv);
go_init_os();
go_init_sched();
go_init_main();
return kr_ok();
}
/** Load Go module symbols. */
static int load_ffi_go(struct kr_module *module, uint32_t api_required)
{
/* Bootstrap libgo */
int ret = bootstrap_libgo(module);
if (ret != 0) {
return ret;
}
/* Enforced prefix for now. */
const char *module_prefix = "main.";
ABI_CHECK(module, module_prefix, "Api", api_required);
ABI_LOAD(module, module_prefix, "Init", "Deinit", "Config", "Layer", "Props");
return kr_ok();
}
int kr_module_load(struct kr_module *module, const char *name, const char *path)
{
if (module == NULL || name == NULL) {
return kr_error(EINVAL);
}
/* Initialize, keep userdata */
void *data = module->data;
memset(module, 0, sizeof(struct kr_module));
module->data = data;
module->name = strdup(name);
if (module->name == NULL) {
return kr_error(ENOMEM);
}
/* Search for module library, use current namespace if not found. */
if (load_library(module, name, path) != 0) {
/* Expand HOME env variable, as the linker may not expand it. */
auto_free char *local_path = kr_strcatdup(2, getenv("HOME"), "/.local" MODULEDIR);
if (load_library(module, name, local_path) != 0) {
if (load_library(module, name, PREFIX MODULEDIR) != 0) {
module->lib = RTLD_DEFAULT;
}
}
}
/* Try to load module ABI. */
int ret = load_sym_c(module, KR_MODULE_API);
if (ret != 0 && module->lib != RTLD_DEFAULT) {
ret = load_ffi_go(module, KR_MODULE_API);
}
/* Module constructor. */
if (ret == 0 && module->init) {
ret = module->init(module);
}
if (ret != 0) {
kr_module_unload(module);
}
return ret;
}
void kr_module_unload(struct kr_module *module)
{
if (module == NULL) {
return;
}
if (module->deinit) {
module->deinit(module);
}
if (module->lib && module->lib != RTLD_DEFAULT) {
dlclose(module->lib);
}
free(module->name);
memset(module, 0, sizeof(struct kr_module));
}