badvpn/ncd/modules/load_module.c

321 lines
8.6 KiB
C

/**
* @file load_module.c
* @author Ambroz Bizjak <ambrop7@gmail.com>
*
* @section LICENSE
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @section DESCRIPTION
*
* Synopsis:
* load_module(string name)
*/
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <dlfcn.h>
#include <misc/balloc.h>
#include <misc/concat_strings.h>
#include <misc/strdup.h>
#include <misc/offset.h>
#include <misc/debug.h>
#include <structure/LinkedList0.h>
#include <ncd/module_common.h>
#include <generated/blog_channel_ncd_load_module.h>
struct global {
LinkedList0 modules_list;
};
struct module {
char *name;
void *lib_handle;
int ncdmodule_loaded;
LinkedList0Node modules_list_node;
};
static struct module * find_module (const char *name, struct global *g)
{
for (LinkedList0Node *ln = LinkedList0_GetFirst(&g->modules_list); ln; ln = LinkedList0Node_Next(ln)) {
struct module *mod = UPPER_OBJECT(ln, struct module, modules_list_node);
if (!strcmp(mod->name, name)) {
return mod;
}
}
return NULL;
}
static struct module * module_init (const char *name, NCDModuleInst *i)
{
struct global *g = ModuleGlobal(i);
ASSERT(!find_module(name, g))
struct module *mod = BAlloc(sizeof(*mod));
if (!mod) {
ModuleLog(i, BLOG_ERROR, "BAlloc failed");
goto fail0;
}
mod->name = b_strdup(name);
if (!mod->name) {
ModuleLog(i, BLOG_ERROR, "b_strdup failed");
goto fail1;
}
mod->lib_handle = NULL;
mod->ncdmodule_loaded = 0;
LinkedList0_Prepend(&g->modules_list, &mod->modules_list_node);
return mod;
fail1:
BFree(mod);
fail0:
return NULL;
}
static void module_free (struct module *mod, struct global *g)
{
LinkedList0_Remove(&g->modules_list, &mod->modules_list_node);
if (mod->lib_handle) {
if (dlclose(mod->lib_handle) != 0) {
BLog(BLOG_ERROR, "dlclose failed");
}
}
BFree(mod->name);
BFree(mod);
}
static char * x_read_link (const char *path)
{
size_t size = 32;
char *buf = BAlloc(size + 1);
if (!buf) {
goto fail0;
}
ssize_t link_size;
while (1) {
link_size = readlink(path, buf, size);
if (link_size < 0) {
goto fail1;
}
if (link_size >= 0 && link_size < size) {
break;
}
if (size > SIZE_MAX / 2 || 2 * size > SIZE_MAX - 1) {
goto fail1;
}
size *= 2;
char *new_buf = BRealloc(buf, size + 1);
if (!new_buf) {
goto fail1;
}
buf = new_buf;
}
buf[link_size] = '\0';
return buf;
fail1:
BFree(buf);
fail0:
return NULL;
}
static char * find_module_library (NCDModuleInst *i, const char *module_name)
{
char *ret = NULL;
char *self = x_read_link("/proc/self/exe");
if (!self) {
ModuleLog(i, BLOG_ERROR, "failed to read /proc/self/exe");
goto fail0;
}
char *slash = strrchr(self, '/');
if (!slash) {
ModuleLog(i, BLOG_ERROR, "contents of /proc/self/exe do not have a slash");
goto fail1;
}
*slash = '\0';
const char *paths[] = {"../lib/badvpn-ncd", "../mcvpn", NULL};
size_t j;
for (j = 0; paths[j]; j++) {
char *module_path = concat_strings(6, self, "/", paths[j], "/libncdmodule_", module_name, ".so");
if (!module_path) {
ModuleLog(i, BLOG_ERROR, "concat_strings failed");
goto fail1;
}
if (access(module_path, F_OK) == 0) {
ret = module_path;
break;
}
BFree(module_path);
}
if (!paths[j]) {
ModuleLog(i, BLOG_ERROR, "failed to find module");
}
fail1:
BFree(self);
fail0:
return ret;
}
static int func_globalinit (struct NCDInterpModuleGroup *group, const struct NCDModuleInst_iparams *params)
{
struct global *g = BAlloc(sizeof(*g));
if (!g) {
BLog(BLOG_ERROR, "BAlloc failed");
return 0;
}
group->group_state = g;
LinkedList0_Init(&g->modules_list);
return 1;
}
static void func_globalfree (struct NCDInterpModuleGroup *group)
{
struct global *g = group->group_state;
LinkedList0Node *ln;
while ((ln = LinkedList0_GetFirst(&g->modules_list))) {
struct module *mod = UPPER_OBJECT(ln, struct module, modules_list_node);
module_free(mod, g);
}
BFree(g);
}
static void func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
{
// check arguments
NCDValRef name_arg;
if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
ModuleLog(i, BLOG_ERROR, "wrong arity");
goto fail0;
}
if (!NCDVal_IsStringNoNulls(name_arg)) {
ModuleLog(i, BLOG_ERROR, "wrong type");
goto fail0;
}
NCDValNullTermString name_nts;
if (!NCDVal_StringNullTerminate(name_arg, &name_nts)) {
ModuleLog(i, BLOG_ERROR, "NCDVal_StringNullTerminate failed");
goto fail0;
}
struct module *mod = find_module(name_nts.data, ModuleGlobal(i));
ASSERT(!mod || mod->lib_handle)
if (!mod) {
mod = module_init(name_nts.data, i);
if (!mod) {
ModuleLog(i, BLOG_ERROR, "module_init failed");
goto fail1;
}
// find module library
char *module_path = find_module_library(i, name_nts.data);
if (!module_path) {
module_free(mod, ModuleGlobal(i));
goto fail1;
}
// load it as a dynamic library
mod->lib_handle = dlopen(module_path, RTLD_NOW);
BFree(module_path);
if (!mod->lib_handle) {
ModuleLog(i, BLOG_ERROR, "dlopen failed");
module_free(mod, ModuleGlobal(i));
goto fail1;
}
}
if (!mod->ncdmodule_loaded) {
// build name of NCDModuleGroup structure symbol
char *group_symbol = concat_strings(2, "ncdmodule_", name_nts.data);
if (!group_symbol) {
ModuleLog(i, BLOG_ERROR, "concat_strings failed");
goto fail1;
}
// resolve NCDModuleGroup structure symbol
void *group = dlsym(mod->lib_handle, group_symbol);
BFree(group_symbol);
if (!group) {
ModuleLog(i, BLOG_ERROR, "dlsym failed");
goto fail1;
}
// load module group
if (!NCDModuleInst_Backend_InterpLoadGroup(i, (struct NCDModuleGroup *)group)) {
ModuleLog(i, BLOG_ERROR, "NCDModuleInst_Backend_InterpLoadGroup failed");
goto fail1;
}
mod->ncdmodule_loaded = 1;
}
NCDValNullTermString_Free(&name_nts);
// signal up
NCDModuleInst_Backend_Up(i);
return;
fail1:
NCDValNullTermString_Free(&name_nts);
fail0:
NCDModuleInst_Backend_DeadError(i);
}
static struct NCDModule modules[] = {
{
.type = "load_module",
.func_new2 = func_new
}, {
.type = NULL
}
};
const struct NCDModuleGroup ncdmodule_load_module = {
.func_globalinit = func_globalinit,
.func_globalfree = func_globalfree,
.modules = modules
};