Skip to content
Snippets Groups Projects
module.c 4.41 KiB
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <unistd.h>

#include "lib/defines.h"
#include "lib/utils.h"
#include "lib/module.h"

/*! \brief Library extension. */
static inline const char *library_ext(void)
{
#if defined(__APPLE__)
	return ".dylib";
#elif _WIN32
	return ".lib";
#else
	return ".so";
#endif	
}

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)
{
	const char *ext = library_ext();
	auto_free char *lib_path = NULL;
	if (path != NULL) {
		lib_path = kr_strcatdup(4, path, "/", name, ext);
	} else {
		lib_path = kr_strcatdup(2, name, ext);
	}
	if (lib_path == NULL) {
		return kr_error(ENOMEM);
	}

	module->lib = dlopen(lib_path, RTLD_LAZY);
	if (module->lib) {
		return kr_ok();
	}

	return kr_error(ENOENT);
}

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();
}


static int load_libgo(struct kr_module *module, module_api_cb **module_api)
{
	/* Bootstrap libgo */
	int ret = bootstrap_libgo(module);
	if (ret != 0) {
		return ret;
	}

	/* Enforced prefix for now. */
	const char *module_prefix = "main.";
	
	*(void **) (module_api)      = load_symbol(module->lib, module_prefix, "Api");
	*(void **) (&module->init)   = load_symbol(module->lib, module_prefix, "Init");
	*(void **) (&module->deinit) = load_symbol(module->lib, module_prefix, "Deinit");
	*(void **) (&module->config) = load_symbol(module->lib, module_prefix, "Config");
	*(void **) (&module->layer)  = load_symbol(module->lib, module_prefix, "Layer");

	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);
	}

	/* Search for module library. */
	memset(module, 0, sizeof(struct kr_module));
	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) {	
			}
		}
	}

	/* It's okay if it fails, then current exec space is searched. */
	if (module->lib == NULL) {
		module->lib = RTLD_DEFAULT;
	}

	/* Load all symbols. */
 	auto_free char *module_prefix = kr_strcatdup(2, name, "_");	
	*(void **) (&module->init)   = load_symbol(module->lib, module_prefix, "init");
	*(void **) (&module->deinit) = load_symbol(module->lib, module_prefix, "deinit");
	*(void **) (&module->config) = load_symbol(module->lib, module_prefix, "config");
	*(void **) (&module->layer)  = load_symbol(module->lib, module_prefix, "layer");

	/* API check for non-built-in libraries. */
	if (module->lib != RTLD_DEFAULT) {
		module_api_cb *module_api = NULL;
		*(void **) (&module_api) = load_symbol(module->lib, module_prefix, "api");
		if (module_api == NULL) {
			int ret = load_libgo(module, &module_api);
			if (ret != 0) {
				return ret;
			}
		}
		/* Check module API version (if declared). */
		if (module_api && module_api() != KR_MODULE_API) {
			return kr_error(ENOTSUP);
		}
	}

	/* Initialize module */
	if (module->init) {
		module->init(module);
	}

	return kr_ok();
}

void kr_module_unload(struct kr_module *module)
{
	if (module->deinit) {
		module->deinit(module);
	}

	if (module->lib && module->lib != RTLD_DEFAULT) {
		dlclose(module->lib);
	}
}