aboutsummaryrefslogtreecommitdiff
path: root/plugin/src
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-06-16 09:15:34 -0700
committer3gg <3gg@shellblade.net>2023-06-16 09:15:34 -0700
commit2f8ff39a8d95b95288875d92abb74b1428713906 (patch)
tree95c23359a86b539c4b11f42ad8694ac6682d1b41 /plugin/src
parentbfabb435e5c5bf313005c4636747fce59eb4ca6f (diff)
Add plugin library.
Diffstat (limited to 'plugin/src')
-rw-r--r--plugin/src/plugin.c250
1 files changed, 250 insertions, 0 deletions
diff --git a/plugin/src/plugin.c b/plugin/src/plugin.c
new file mode 100644
index 0000000..f65132f
--- /dev/null
+++ b/plugin/src/plugin.c
@@ -0,0 +1,250 @@
1#include "plugin.h"
2
3#include "cstring.h"
4#include "list.h"
5#include "log/log.h" // TODO: Use the error library instead. Move it to clib.
6
7#include <assert.h>
8#include <stdbool.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include <errno.h>
13#include <linux/limits.h>
14#include <poll.h>
15#include <sys/inotify.h>
16#include <unistd.h>
17
18// Watching for IN_CREATE leads the plugin engine to try to reload a plugin's
19// shared library before the compiler has fully written to it.
20static const int WATCH_MASK = IN_CLOSE_WRITE;
21
22typedef struct Plugin {
23 void* handle; // First member so that Plugin can be cast to handle.
24 void* state; // Plugin's internal state.
25 bool reloaded; // Whether the plugin has been reloaded state needs to be
26 // re-created.
27 PluginEngine* eng; // So that the public API can do stuff with just a Plugin*.
28 mstring filename;
29} Plugin;
30
31DEF_LIST(Plugin);
32
33typedef struct PluginEngine {
34 int inotify_instance;
35 int dir_watch; // inotify watch on the plugins directory.
36 Plugin_list plugins;
37 mstring plugins_dir;
38} PluginEngine;
39
40// -----------------------------------------------------------------------------
41// Plugin.
42// -----------------------------------------------------------------------------
43
44static mstring plugin_lib_name(const Plugin* plugin) {
45 return mstring_concat(
46 mstring_make("lib"), mstring_concat_cstr(plugin->filename, ".so"));
47}
48
49static mstring plugin_lib_path(const Plugin* plugin) {
50 return mstring_concat(plugin->eng->plugins_dir, plugin_lib_name(plugin));
51}
52
53static bool load_library(Plugin* plugin) {
54 assert(plugin);
55 assert(plugin->eng);
56
57 // Handle reloading a previously-loaded library.
58 if (plugin->handle) {
59 dlclose(plugin->handle);
60 plugin->handle = 0;
61 }
62
63 const mstring lib = plugin_lib_path(plugin);
64
65 // If the plugin fails to load, make sure to keep the plugin's old handle to
66 // handle the error gracefully. This handles reload failures, specifically.
67 void* handle = 0;
68 if ((handle = dlopen(mstring_cstr(&lib), RTLD_NOW))) {
69 LOGD("Plugin [%s] loaded successfully", mstring_cstr(&plugin->filename));
70 plugin->handle = handle;
71 return true;
72 } else {
73 LOGE("dlopen() failed: %s", dlerror());
74 }
75
76 return false;
77}
78
79static void destroy_plugin(Plugin* plugin) {
80 if (plugin) {
81 if (plugin->handle) {
82 dlclose(plugin->handle);
83 plugin->handle = 0;
84 }
85 if (plugin->state) {
86 free(plugin->state);
87 plugin->state = 0;
88 }
89 }
90}
91
92Plugin* load_plugin(PluginEngine* eng, const char* filename) {
93 assert(eng);
94 assert(filename);
95
96 Plugin plugin = (Plugin){.eng = eng, .filename = mstring_make(filename)};
97
98 if (!load_library(&plugin)) {
99 return 0;
100 }
101
102 list_push(eng->plugins, plugin);
103 return &eng->plugins.head->val;
104}
105
106void delete_plugin(Plugin** pPlugin) {
107 assert(pPlugin);
108 Plugin* plugin = *pPlugin;
109 if (plugin) {
110 assert(plugin->eng);
111 destroy_plugin(plugin);
112 list_remove_ptr(plugin->eng->plugins, plugin);
113 *pPlugin = 0;
114 }
115}
116
117static void delete_plugin_state(Plugin* plugin) {
118 if (plugin->state) {
119 free(plugin->state);
120 plugin->state = 0;
121 }
122}
123
124void set_plugin_state(Plugin* plugin, void* state) {
125 assert(plugin);
126 delete_plugin_state(plugin);
127 plugin->state = state;
128}
129
130void* get_plugin_state(Plugin* plugin) {
131 assert(plugin);
132 return plugin->state;
133}
134
135bool plugin_reloaded(Plugin* plugin) {
136 assert(plugin);
137 const bool reloaded = plugin->reloaded;
138 plugin->reloaded = false;
139 return reloaded;
140}
141
142// -----------------------------------------------------------------------------
143// Plugin Engine.
144// -----------------------------------------------------------------------------
145
146PluginEngine* new_plugin_engine(const PluginEngineDesc* desc) {
147 PluginEngine* eng = 0;
148
149 if (!(eng = calloc(1, sizeof(PluginEngine)))) {
150 goto cleanup;
151 }
152 eng->plugins = make_list(Plugin);
153 eng->plugins_dir = mstring_concat_cstr(mstring_make(desc->plugins_dir), "/");
154
155 LOGD("Watch plugins directory: %s", mstring_cstr(&eng->plugins_dir));
156
157 if ((eng->inotify_instance = inotify_init()) == -1) {
158 LOGE("Failed to create inotify instance");
159 goto cleanup;
160 }
161 if ((eng->dir_watch = inotify_add_watch(
162 eng->inotify_instance, mstring_cstr(&eng->plugins_dir),
163 WATCH_MASK)) == -1) {
164 LOGE("Failed to watch directory: %s", mstring_cstr(&eng->plugins_dir));
165 goto cleanup;
166 }
167
168 return eng;
169
170cleanup:
171 delete_plugin_engine(&eng);
172 return 0;
173}
174
175void delete_plugin_engine(PluginEngine** pEng) {
176 assert(pEng);
177 PluginEngine* eng = *pEng;
178 if (eng) {
179 list_foreach_mut(eng->plugins, { destroy_plugin(value); });
180 del_list(eng->plugins);
181 if (eng->dir_watch != -1) {
182 inotify_rm_watch(eng->dir_watch, eng->inotify_instance);
183 close(eng->dir_watch);
184 eng->dir_watch = 0;
185 }
186 if (eng->inotify_instance != -1) {
187 close(eng->inotify_instance);
188 }
189 free(eng);
190 *pEng = 0;
191 }
192}
193
194void plugin_engine_update(PluginEngine* eng) {
195 assert(eng);
196
197 struct pollfd pollfds[1] = {
198 {eng->inotify_instance, POLLIN, 0}
199 };
200
201 int ret = 0;
202 while ((ret = poll(pollfds, 1, 0)) != 0) {
203 if (ret > 0) {
204 const struct pollfd* pfd = &pollfds[0];
205 if (pfd->revents & POLLIN) {
206 // inotify instances don't like to be partially read, and the events,
207 // when watching a directory, have a variable-length file name.
208 uint8_t buf[sizeof(struct inotify_event) + NAME_MAX + 1] = {0};
209 ssize_t length = read(eng->inotify_instance, &buf, sizeof(buf));
210 if (length == -1) {
211 LOGE(
212 "read() on inotify instance failed with error [%d]: %s", errno,
213 strerror(errno));
214 break;
215 }
216 const uint8_t* next = buf;
217 const uint8_t* end = buf + sizeof(buf);
218 while (next < end) {
219 const struct inotify_event* event = (const struct inotify_event*)next;
220 if (event->mask & WATCH_MASK) {
221 if (event->wd == eng->dir_watch) {
222 if (event->len > 0) {
223 // Name does not include directory, e.g., libfoo.so
224 const mstring file = mstring_make(event->name);
225 list_foreach_mut(eng->plugins, {
226 Plugin* plugin = value;
227 if (mstring_eq(file, plugin_lib_name(plugin))) {
228 if (load_library(plugin)) {
229 plugin->reloaded = true;
230 }
231 break;
232 }
233 });
234 }
235 }
236 }
237 next += sizeof(struct inotify_event) + event->len;
238 }
239 }
240 if ((pfd->revents & POLLERR) || (pfd->revents & POLLHUP) ||
241 (pfd->revents & POLLNVAL)) {
242 LOGE("inotify instance is in a bad state");
243 break;
244 }
245 } else if (ret == -1) {
246 LOGE("poll() failed with error [%d]: %s", errno, strerror(errno));
247 break;
248 }
249 }
250}