comparison mcabber/mcabber/modules.c @ 1735:5093b5ca1572

New modules loading scheme
author Myhailo Danylenko <isbear@ukrpost.net>
date Thu, 04 Mar 2010 13:03:20 +0200
parents
children 15e1f3957786
comparison
equal deleted inserted replaced
1734:eae4a2637f2c 1735:5093b5ca1572
1 /*
2 * modules.c -- modules handling
3 *
4 * Copyright (C) 2010 Myhailo Danylenko <isbear@ukrpost.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA
20 */
21
22 #include <glib.h>
23 #include <gmodule.h>
24 #include <string.h>
25
26 #include "settings.h"
27 #include "config.h"
28 #include "modules.h"
29 #include "logprint.h"
30 #include "utils.h"
31
32 // Information about loaded module
33 typedef struct {
34 guint refcount;
35 gboolean locked;
36 gchar *name;
37 GModule *module;
38 GSList *dependencies;
39 module_info_t *info;
40 } loaded_module_t;
41
42 // Registry of loaded modules
43 // FIXME This should be a hash table
44 // but this needs long thinking and will not affect external interfaces
45 static GSList *loaded_modules = NULL;
46
47 static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2)
48 {
49 const loaded_module_t *module = arg1;
50 const char *name = arg2;
51 return g_strcmp0(module->name, name);
52 }
53
54 // module_load(modulename, manual, force)
55 // Tries to load specified module and any modules, that this module
56 // depends on. Returns NULL on success or constant error string in a
57 // case of error. Error message not necessarily indicates error.
58 const gchar *module_load(const gchar *arg, gboolean manual, gboolean force)
59 {
60 GModule *mod;
61 module_info_t *info;
62 GSList *deps = NULL;
63
64 if (!arg || !*arg)
65 return "Missing module name";
66
67 { // Check if module is already loaded
68 GSList *lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
69
70 if (lmod) {
71 loaded_module_t *module = lmod->data;
72
73 if (manual) {
74 if (!module->locked) {
75 module->locked = TRUE;
76 module->refcount += 1;
77 return force ? NULL : "Module is already automatically loaded, marked as manually loaded";
78 } else
79 return force ? NULL : "Module is already loaded";
80 } else {
81 module->refcount += 1;
82 return NULL;
83 }
84 }
85 }
86
87 { // Load module
88 gchar *mdir = expand_filename(settings_opt_get("modules_dir"));
89 gchar *path = g_module_build_path(mdir ? mdir : PKGLIB_DIR, arg);
90 g_free(mdir);
91 mod = g_module_open(path, G_MODULE_BIND_LAZY);
92 g_free(path);
93 if (!mod)
94 return g_module_error();
95 }
96
97 { // Obtain module information structure
98 gchar *varname = g_strdup_printf("info_%s", arg);
99 gpointer var = NULL;
100
101 // convert to a valid symbol name
102 g_strcanon(varname, "abcdefghijklmnopqrstuvwxyz0123456789", '_');
103
104 if (!g_module_symbol(mod, varname, &var)) {
105 if (!force) {
106 g_free(varname);
107 return "Module provides no information structure";
108 }
109
110 scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Module provides no information structure.");
111 }
112
113 g_free(varname);
114 info = var;
115 }
116
117 // Version check
118 if (info && info->mcabber_version && *(info->mcabber_version)
119 && (strcmp(info->mcabber_version, PACKAGE_VERSION) > 0)) {
120 if (!force) {
121 g_module_close(mod);
122 return "Module requires newer version of mcabber";
123 }
124
125 scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Module requires newer version of mcabber.");
126 }
127
128 // Load dependencies
129 if (info && info->requires) {
130 const gchar **dep;
131
132 for (dep = info->requires; *dep; ++dep) {
133 const gchar *err = module_load(*dep, FALSE, FALSE);
134
135 if (err) {
136 GSList *mel;
137 scr_LogPrint(LPRINT_LOGNORM, "Error loading dependency module %s: %s.", *dep, err);
138
139 // Unload already loaded dependencies
140 for (mel = deps; mel; mel = mel->next) {
141 gchar *ldmname = mel->data;
142 err = module_unload(ldmname, FALSE, FALSE);
143 scr_LogPrint(LPRINT_LOGNORM, "Error unloading dependency module %s: %s.", ldmname, err);
144 g_free(ldmname);
145 }
146 g_slist_free(deps);
147
148 // Unload module
149 if (!g_module_close(mod))
150 scr_LogPrint(LPRINT_LOGNORM, "Error unloading module %s: %s.", arg, g_module_error());
151 return "Dependency problems";
152 }
153
154 deps = g_slist_append(deps, g_strdup(*dep));
155 }
156 }
157
158 { // Register module
159 loaded_module_t *module = g_new(loaded_module_t, 1);
160
161 module->refcount = 1;
162 module->locked = manual;
163 module->name = g_strdup(arg);
164 module->module = mod;
165 module->info = info;
166 module->dependencies = deps;
167
168 loaded_modules = g_slist_prepend(loaded_modules, module);
169 }
170
171 // Run initialization routine
172 if (info && info->init)
173 info->init();
174
175 // XXX Run hk_loaded_module hook (and move this line there)
176 scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg);
177
178 return NULL;
179 }
180
181 // module_unload(modulename, manual, force)
182 // Unload specified module and any automatically loaded modules
183 // that are no more required.
184 const gchar *module_unload(const gchar *arg, gboolean manual, gboolean force)
185 {
186 GSList *lmod;
187 loaded_module_t *module;
188 module_info_t *info;
189
190 if (!arg || !*arg)
191 return "Missing module name";
192
193 lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
194 if (!lmod)
195 return "Module not found";
196
197 module = lmod->data;
198
199 // Check if user can unload this module
200 if (manual) {
201 if (!module->locked) {
202 if (force)
203 scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Manually unloading automatically loaded module.");
204 else
205 return "Module is not loaded manually";
206 }
207 module->locked = FALSE;
208 }
209
210 // Check refcount
211 module->refcount -= 1;
212 if (module->refcount > 0) {
213 if (force)
214 scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Refcount is not zero (%u).", module->refcount);
215 else
216 return manual ? "Module is required by some other modules" : NULL;
217 }
218
219 info = module->info;
220
221 // Run uninitialization routine
222 if (info && info->uninit)
223 info->uninit();
224 // XXX Prevent uninitialization routine to be called again
225 module->info = NULL;
226
227 // Unload module
228 if (!g_module_close(module->module))
229 return g_module_error(); // XXX destroy structure?
230
231 { // Unload dependencies
232 GSList *dep;
233 for (dep = module->dependencies; dep; dep = dep->next) {
234 gchar *ldmname = dep->data;
235 const gchar *err = module_unload(ldmname, FALSE, FALSE);
236 if (err) // XXX
237 scr_LogPrint(LPRINT_LOGNORM, "Error unloading automatically loaded module %s: %s.", ldmname, err);
238 g_free(ldmname);
239 }
240 g_slist_free(module->dependencies);
241 module->dependencies = NULL;
242 }
243
244 // Destroy structure
245 loaded_modules = g_slist_delete_link(loaded_modules, lmod);
246 g_free(module->name);
247 g_free(module);
248
249 scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg);
250
251 return NULL;
252 }
253
254 // module_list_print(void)
255 // Prints into status buffer and log list of the currently loaded
256 // modules.
257 void module_list_print(void)
258 {
259 GSList *mel;
260 gsize maxlen = 0;
261 gchar *format;
262 GString *message;
263
264 if (!loaded_modules) {
265 scr_LogPrint(LPRINT_LOGNORM, "No modules loaded.");
266 return;
267 }
268
269 // Counnt maximum module name length
270 for (mel = loaded_modules; mel; mel = mel -> next) {
271 loaded_module_t *module = mel->data;
272 gsize len = strlen(module->name);
273 if (len > maxlen)
274 maxlen = len;
275 }
276
277 // Create format string
278 format = g_strdup_printf("%%-%us %%2u (%%c)", maxlen);
279
280 // Fill the message to be printed
281 message = g_string_new("Loaded modules:\n");
282 for (mel = loaded_modules; mel; mel = mel -> next) {
283 loaded_module_t *module = mel->data;
284 GSList *dep;
285
286 g_string_append_printf(message, format, module->name, module->refcount, module->locked ? 'M' : 'A');
287
288 // Append loaded module dependencies
289 if (module->dependencies) {
290 g_string_append(message, " depends: ");
291
292 for (dep = module->dependencies; dep; dep = dep->next) {
293 const gchar *name = dep->data;
294 g_string_append(message, name);
295 g_string_append(message, ", ");
296 }
297
298 // Chop extra ", "
299 g_string_truncate(message, message->len - 2);
300 }
301
302 g_string_append_c(message, '\n');
303 }
304
305 // Chop extra "\n"
306 g_string_truncate(message, message->len - 1);
307
308 scr_LogPrint(LPRINT_LOGNORM, "%s", message->str);
309
310 g_string_free(message, TRUE);
311 g_free(format);
312 }
313
314 // modules_init()
315 // Initializes module system.
316 void modules_init(void)
317 {
318 }
319
320 // modules_deinit()
321 // Unloads all the modules.
322 void modules_deinit(void)
323 {
324 GSList *mel;
325
326 // We need only manually loaded modules
327 for (mel = loaded_modules; mel; mel = mel->next) {
328 loaded_module_t *module = mel->data;
329 if (module->locked)
330 break;
331 }
332
333 while (mel) {
334 loaded_module_t *module = mel->data;
335 const gchar *err;
336
337 // Find next manually loaded module to treat
338 for (mel = mel->next; mel; mel = mel->next) {
339 loaded_module_t *module = mel->data;
340 if (module->locked)
341 break;
342 }
343
344 // Unload module
345 scr_LogPrint(LPRINT_LOGNORM, "Unloading module %s.", module->name);
346 err = module_unload(module->name, TRUE, FALSE);
347 if (err)
348 scr_LogPrint(LPRINT_LOGNORM, "* Module unloading failed: %s.", err);
349 }
350 }
351
352 /* vim: set expandtab cindent cinoptions=>2\:2(0 ts=2 sw=2: For Vim users... */