summaryrefslogtreecommitdiffstats
path: root/lib/module.c
blob: 83ae77370977733a8e75084fc3850f3a7e6afb17 (plain)
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
/*  Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
 *  SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <stdlib.h>
#include <dlfcn.h>
#include <contrib/cleanup.h>

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


/* List of embedded modules.  These aren't (un)loaded. */
int iterate_init(struct kr_module *self);
int validate_init(struct kr_module *self);
int cache_init(struct kr_module *self);
kr_module_init_cb kr_module_get_embedded(const char *name)
{
	if (strcmp(name, "iterate") == 0)
		return iterate_init;
	if (strcmp(name, "validate") == 0)
		return validate_init;
	if (strcmp(name, "cache") == 0)
		return cache_init;
	return NULL;
}

/** 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)
{
	if (kr_fails_assert(module && name && path))
		return kr_error(EINVAL);
	/* Absolute or relative path (then only library search path is used). */
	auto_free char *lib_path = kr_strcatdup(4, path, "/", 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)
{
	module->init = kr_module_get_embedded(module->name);
	if (module->init) {
		return kr_ok();
	}
	#pragma GCC diagnostic push
	#pragma GCC diagnostic ignored "-Wpedantic" /* casts after load_symbol() */
	/* Check if it's embedded first */
	/* Load dynamic library module */
	auto_free char *m_prefix = kr_strcatdup(2, module->name, "_");

	/* Check ABI version, return error on mismatch. */
	module_api_cb *api = load_symbol(module->lib, m_prefix, "api");
	if (api == NULL) {
		return kr_error(ENOENT);
	}
	if (api() != api_required) {
		return kr_error(ENOTSUP);
	}

	/* Load ABI by symbol names. */
	#define ML(symname) module->symname = \
		load_symbol(module->lib, m_prefix, #symname)
	ML(init);
	ML(deinit);
	ML(config);
	#undef ML
	if (load_symbol(module->lib, m_prefix, "layer")
	    || load_symbol(module->lib, m_prefix, "props")) {
		/* In case someone re-compiled against new kresd
		 * but haven't actually changed the symbols. */
		kr_log_error(SYSTEM, "module %s requires upgrade.  Please refer to "
			"https://knot-resolver.readthedocs.io/en/stable/upgrading.html",
			module->name);
		return kr_error(ENOTSUP);
	}

	return kr_ok();
	#pragma GCC diagnostic pop
}

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. */
	if (!path || load_library(module, name, path) != 0) {
		module->lib = RTLD_DEFAULT;
	}

	/* Try to load module ABI. */
	int ret = load_sym_c(module, KR_MODULE_API);
	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));
}