1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1999 Alexander Larsson
3 *
4 * plug-ins.c: plugin loading handling interfaces for dia
5 * Copyright (C) 2000 James Henstridge
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 #include <config.h>
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 #include <libxml/xmlmemory.h>
35 #include <libxml/parser.h>
36 #include <libxml/tree.h>
37 #include "dia_xml_libxml.h"
38 #include "dia_xml.h"
39
40 #include "plug-ins.h"
41 #include "intl.h"
42 #include "message.h"
43 #include "dia_dirs.h"
44
45 #ifdef G_OS_WIN32
46 #define Rectangle Win32Rectangle
47 #define WIN32_LEAN_AND_MEAN
48 #include <pango/pangowin32.h>
49 #undef Rectangle
50 #endif
51
52 struct _PluginInfo {
53 GModule *module;
54 gchar *filename; /* plugin filename */
55
56 gboolean is_loaded;
57 gboolean inhibit_load;
58
59 gchar *name;
60 gchar *description;
61
62 PluginInitFunc init_func;
63 PluginCanUnloadFunc can_unload_func;
64 PluginUnloadFunc unload_func;
65 };
66
67
68 static GList *plugins = NULL;
69
70 static void ensure_pluginrc(void);
71 static void free_pluginrc(void);
72 static gboolean plugin_load_inhibited(const gchar *filename);
73 static void info_fill_from_pluginrc(PluginInfo *info);
74
75 gboolean
dia_plugin_info_init(PluginInfo * info,gchar * name,gchar * description,PluginCanUnloadFunc can_unload_func,PluginUnloadFunc unload_func)76 dia_plugin_info_init(PluginInfo *info, gchar *name, gchar *description,
77 PluginCanUnloadFunc can_unload_func,
78 PluginUnloadFunc unload_func)
79 {
80 g_free(info->name);
81 info->name = g_strdup(name);
82 g_free(info->description);
83 info->description = g_strdup(description);
84 info->can_unload_func = can_unload_func;
85 info->unload_func = unload_func;
86
87 return TRUE;
88 }
89
90 gchar *
dia_plugin_check_version(gint version)91 dia_plugin_check_version(gint version)
92 {
93 if (version != DIA_PLUGIN_API_VERSION)
94 return "Wrong plugin API version";
95 else
96 return NULL;
97 }
98
99
100 /* functions for use by dia ... */
101
102 const gchar *
dia_plugin_get_filename(PluginInfo * info)103 dia_plugin_get_filename(PluginInfo *info)
104 {
105 return info->filename;
106 }
107
108 const gchar *
dia_plugin_get_name(PluginInfo * info)109 dia_plugin_get_name(PluginInfo *info)
110 {
111 return info->name ? info->name : _("???");
112 }
113
114 const gchar *
dia_plugin_get_description(PluginInfo * info)115 dia_plugin_get_description(PluginInfo *info)
116 {
117 return info->description;
118 }
119
120 const gpointer *
dia_plugin_get_symbol(PluginInfo * info,const gchar * name)121 dia_plugin_get_symbol(PluginInfo *info, const gchar* name)
122 {
123 gpointer symbol;
124
125 if (!info->module)
126 return NULL;
127
128 if (g_module_symbol (info->module, name, &symbol))
129 return symbol;
130
131 return NULL;
132 }
133
134 gboolean
dia_plugin_can_unload(PluginInfo * info)135 dia_plugin_can_unload(PluginInfo *info)
136 {
137 return (info->can_unload_func != NULL) && (* info->can_unload_func)(info);
138 }
139
140 gboolean
dia_plugin_is_loaded(PluginInfo * info)141 dia_plugin_is_loaded(PluginInfo *info)
142 {
143 return info->is_loaded;
144 }
145
146 gboolean
dia_plugin_get_inhibit_load(PluginInfo * info)147 dia_plugin_get_inhibit_load(PluginInfo *info)
148 {
149 return info->inhibit_load;
150 }
151
152 void
dia_plugin_set_inhibit_load(PluginInfo * info,gboolean inhibit_load)153 dia_plugin_set_inhibit_load(PluginInfo *info, gboolean inhibit_load)
154 {
155 info->inhibit_load = inhibit_load;
156 }
157
158 void
dia_plugin_load(PluginInfo * info)159 dia_plugin_load(PluginInfo *info)
160 {
161 #ifdef G_OS_WIN32
162 guint error_mode;
163 #endif
164 g_return_if_fail(info != NULL);
165 g_return_if_fail(info->filename != NULL);
166
167 if (info->is_loaded)
168 return;
169
170 #ifdef G_OS_WIN32
171 /* suppress the systems error dialog, we can handle a plug-in not loadable */
172 error_mode = SetErrorMode (SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
173 #endif
174
175 dia_log_message ("plug-in '%s'", info->filename);
176 info->module = g_module_open(info->filename, G_MODULE_BIND_LAZY);
177 #ifdef G_OS_WIN32
178 SetErrorMode (error_mode);
179 #endif
180 if (!info->module) {
181 gchar *msg_utf8 = NULL;
182 /* at least on windows the systems error message is not that useful */
183 if (!g_file_test(info->filename, G_FILE_TEST_EXISTS))
184 msg_utf8 = g_locale_to_utf8 (g_module_error(), -1, NULL, NULL, NULL);
185 else
186 msg_utf8 = g_strdup_printf (_("Missing dependencies for '%s'?"), info->filename);
187 /* this is eating the conversion */
188 info->description = msg_utf8;
189 return;
190 }
191
192 info->init_func = NULL;
193 if (!g_module_symbol(info->module, "dia_plugin_init",
194 (gpointer)&info->init_func)) {
195 g_module_close(info->module);
196 info->module = NULL;
197 info->description = g_strdup(_("Missing symbol 'dia_plugin_init'"));
198 return;
199 }
200
201 if ((* info->init_func)(info) != DIA_PLUGIN_INIT_OK) {
202 /* plugin displayed an error message */
203 g_module_close(info->module);
204 info->module = NULL;
205 info->description = g_strdup(_("dia_plugin_init() call failed"));
206 return;
207 }
208
209 /* Corrupt? */
210 if (info->description == NULL) {
211 g_module_close(info->module);
212 info->module = NULL;
213 info->description = g_strdup(_("dia_plugin_init() call failed"));
214 return;
215 }
216
217 info->is_loaded = TRUE;
218
219 return;
220 }
221
222 void
dia_plugin_unload(PluginInfo * info)223 dia_plugin_unload(PluginInfo *info)
224 {
225 g_return_if_fail(info != NULL);
226 g_return_if_fail(info->filename != NULL);
227
228 if (!info->is_loaded)
229 return;
230
231 if (!dia_plugin_can_unload(info)) {
232 message(_("%s Plugin could not be unloaded"), info->name);
233 return;
234 }
235 /* perform plugin cleanup */
236 if (info->unload_func)
237 (* info->unload_func)(info);
238 g_module_close(info->module);
239 info->module = NULL;
240 info->init_func = NULL;
241 info->can_unload_func = NULL;
242 info->unload_func = NULL;
243
244 info->is_loaded = FALSE;
245 }
246
247 void
dia_register_plugin(const gchar * filename)248 dia_register_plugin(const gchar *filename)
249 {
250 GList *tmp;
251 PluginInfo *info;
252
253 /* check if plugin has already been registered */
254 for (tmp = plugins; tmp != NULL; tmp = tmp->next) {
255 info = tmp->data;
256 if (!strcmp(info->filename, filename))
257 return;
258 }
259
260 /* If trying to load libdia, abort */
261 if (strstr(filename, "libdia.")) {
262 return;
263 }
264
265 /* set up plugin info structure */
266 info = g_new0(PluginInfo, 1);
267 info->filename = g_strdup(filename);
268 info->is_loaded = FALSE;
269 info->inhibit_load = FALSE;
270
271 /* check whether loading of the plugin has been inhibited */
272 if (plugin_load_inhibited(filename))
273 info_fill_from_pluginrc(info);
274 else
275 dia_plugin_load(info);
276
277 plugins = g_list_prepend(plugins, info);
278 }
279
280 static gboolean
this_is_a_plugin(const gchar * name)281 this_is_a_plugin(const gchar *name)
282 {
283 return g_str_has_suffix(name, G_MODULE_SUFFIX);
284 }
285
286 typedef void (*ForEachInDirDoFunc)(const gchar *name);
287 typedef gboolean (*ForEachInDirFilterFunc)(const gchar *name);
288
289 static void
for_each_in_dir(const gchar * directory,ForEachInDirDoFunc dofunc,ForEachInDirFilterFunc filter)290 for_each_in_dir(const gchar *directory, ForEachInDirDoFunc dofunc,
291 ForEachInDirFilterFunc filter)
292 {
293 struct stat statbuf;
294 const char *dentry;
295 GDir *dp;
296 GError *error = NULL;
297
298 if (stat(directory, &statbuf) < 0)
299 return;
300
301 dp = g_dir_open(directory, 0, &error);
302 if (dp == NULL) {
303 message_warning(_("Could not open `%s'\n`%s'"), directory, error->message);
304 g_error_free (error);
305 return;
306 }
307
308 while ((dentry = g_dir_read_name(dp)) != NULL) {
309 gchar *name = g_strconcat(directory,G_DIR_SEPARATOR_S,dentry,NULL);
310
311 if (filter(name)) dofunc(name);
312 g_free(name);
313 }
314 g_dir_close(dp);
315 }
316
317 static gboolean
directory_filter(const gchar * name)318 directory_filter(const gchar *name)
319 {
320 guint len = strlen(name);
321
322 if (0 == strcmp(G_DIR_SEPARATOR_S ".",
323 &name[len-strlen(G_DIR_SEPARATOR_S ".")])) return FALSE;
324 if (0 == strcmp(G_DIR_SEPARATOR_S "..",
325 &name[len-strlen(G_DIR_SEPARATOR_S "..")])) return FALSE;
326
327 if (!g_file_test (name, G_FILE_TEST_IS_DIR))
328 return FALSE;
329
330 return TRUE;
331 }
332
333 static gboolean
dia_plugin_filter(const gchar * name)334 dia_plugin_filter(const gchar *name)
335 {
336 if (!g_file_test (name, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_DIR))
337 return FALSE;
338
339 return this_is_a_plugin(name);
340 }
341
342 static void
walk_dirs_for_plugins(const gchar * dirname)343 walk_dirs_for_plugins(const gchar *dirname)
344 {
345 for_each_in_dir(dirname,walk_dirs_for_plugins,directory_filter);
346 for_each_in_dir(dirname,dia_register_plugin,dia_plugin_filter);
347 }
348
349 #define RECURSE (G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S)
350
351 void
dia_register_plugins_in_dir(const gchar * directory)352 dia_register_plugins_in_dir(const gchar *directory)
353 {
354 guint reclen = strlen(RECURSE);
355 guint len = strlen(directory);
356
357 if ((len >= reclen) &&
358 (0 == strcmp(&directory[len-reclen],RECURSE))) {
359 gchar *dirbase = g_strndup(directory,len-reclen);
360 for_each_in_dir(dirbase,walk_dirs_for_plugins,directory_filter);
361 g_free(dirbase);
362 };
363 /* intentional fallback. */
364 for_each_in_dir(directory,dia_register_plugin,dia_plugin_filter);
365 }
366
367 void
dia_register_plugins(void)368 dia_register_plugins(void)
369 {
370 const gchar *library_path;
371 const gchar *lib_dir;
372
373 library_path = g_getenv("DIA_LIB_PATH");
374
375 lib_dir = dia_config_filename("objects");
376
377 if (lib_dir != NULL) {
378 dia_register_plugins_in_dir(lib_dir);
379 g_free((char *)lib_dir);
380 }
381
382 if (library_path != NULL) {
383 gchar **paths = g_strsplit(library_path, G_SEARCHPATH_SEPARATOR_S, 0);
384 gint i;
385
386 for (i = 0; paths[i] != NULL; i++) {
387 dia_register_plugins_in_dir(paths[i]);
388 }
389 g_strfreev(paths);
390 } else {
391 library_path = dia_get_lib_directory("dia");
392
393 dia_register_plugins_in_dir(library_path);
394 g_free((char *)library_path);
395 }
396 /* FIXME: this isn't needed anymore */
397 free_pluginrc();
398 }
399
400 void
dia_register_builtin_plugin(PluginInitFunc init_func)401 dia_register_builtin_plugin(PluginInitFunc init_func)
402 {
403 PluginInfo *info;
404
405 info = g_new0(PluginInfo, 1);
406 info->filename = "<builtin>";
407 info->is_loaded = TRUE;
408 info->inhibit_load = FALSE;
409
410 info->init_func = init_func;
411
412 if ((* init_func)(info) != DIA_PLUGIN_INIT_OK) {
413 g_free(info);
414 return;
415 }
416 plugins = g_list_prepend(plugins, info);
417 }
418
419
420 GList *
dia_list_plugins(void)421 dia_list_plugins(void)
422 {
423 return plugins;
424 }
425
426 static xmlDocPtr pluginrc = NULL;
427 /* format is:
428 <plugins>
429 <plugin filename="filename">
430 <name></name>
431 <description></description>
432 <inhibit-load/>
433 </plugin>
434 </plugins>
435 */
436
437 static void
ensure_pluginrc(void)438 ensure_pluginrc(void)
439 {
440 gchar *filename;
441
442 if (pluginrc)
443 return;
444 filename = dia_config_filename("pluginrc");
445 if (g_file_test (filename, G_FILE_TEST_IS_REGULAR))
446 pluginrc = xmlDiaParseFile(filename);
447 else
448 pluginrc = NULL;
449
450 g_free(filename);
451
452 if (!pluginrc) {
453 pluginrc = xmlNewDoc((const xmlChar *)"1.0");
454 pluginrc->encoding = xmlStrdup((const xmlChar *)"UTF-8");
455 xmlDocSetRootElement(pluginrc,
456 xmlNewDocNode(pluginrc, NULL, (const xmlChar *)"plugins", NULL));
457 }
458 }
459
460 static void
free_pluginrc(void)461 free_pluginrc(void)
462 {
463 if (pluginrc) {
464 xmlFreeDoc(pluginrc);
465 pluginrc = NULL;
466 }
467 }
468
469 /* whether we should prevent loading on startup */
470 static gboolean
plugin_load_inhibited(const gchar * filename)471 plugin_load_inhibited(const gchar *filename)
472 {
473 xmlNodePtr node;
474
475 ensure_pluginrc();
476 for (node = pluginrc->xmlRootNode->xmlChildrenNode;
477 node != NULL;
478 node = node->next) {
479 xmlChar *node_filename;
480
481 if (xmlIsBlankNode(node)) continue;
482
483 if (node->type != XML_ELEMENT_NODE || xmlStrcmp(node->name, (const xmlChar *)"plugin") != 0)
484 continue;
485 node_filename = xmlGetProp(node, (const xmlChar *)"filename");
486 if (node_filename && !strcmp(filename, (gchar *)node_filename)) {
487 xmlNodePtr node2;
488
489 xmlFree(node_filename);
490 for (node2 = node->xmlChildrenNode;
491 node2 != NULL;
492 node2 = node2->next) {
493 if (xmlIsBlankNode(node2)) continue;
494 if (node2->type == XML_ELEMENT_NODE &&
495 !xmlStrcmp(node2->name, (const xmlChar *)"inhibit-load"))
496 return TRUE;
497 }
498 return FALSE;
499 }
500 if (node_filename) xmlFree(node_filename);
501 }
502 return FALSE;
503 }
504
505 static void
info_fill_from_pluginrc(PluginInfo * info)506 info_fill_from_pluginrc(PluginInfo *info)
507 {
508 xmlNodePtr node;
509
510 info->module = NULL;
511 info->name = NULL;
512 info->description = NULL;
513 info->is_loaded = FALSE;
514 info->inhibit_load = TRUE;
515 info->init_func = NULL;
516 info->can_unload_func = NULL;
517 info->unload_func = NULL;
518
519 ensure_pluginrc();
520 for (node = pluginrc->xmlRootNode->xmlChildrenNode;
521 node != NULL;
522 node = node->next) {
523 xmlChar *node_filename;
524
525 if (xmlIsBlankNode(node)) continue;
526
527 if (node->type != XML_ELEMENT_NODE || xmlStrcmp(node->name, (const xmlChar *)"plugin") != 0)
528 continue;
529 node_filename = xmlGetProp(node, (const xmlChar *)"filename");
530 if (node_filename && !strcmp(info->filename, (char *) node_filename)) {
531 xmlNodePtr node2;
532
533 xmlFree(node_filename);
534 for (node2 = node->xmlChildrenNode;
535 node2 != NULL;
536 node2 = node2->next) {
537 gchar *content;
538
539 if (xmlIsBlankNode(node2)) continue;
540
541 if (node2->type != XML_ELEMENT_NODE)
542 continue;
543 content = (gchar *)xmlNodeGetContent(node2);
544 if (!xmlStrcmp(node2->name, (const xmlChar *)"name")) {
545 g_free(info->name);
546 info->name = g_strdup(content);
547 } else if (!xmlStrcmp(node2->name, (const xmlChar *)"description")) {
548 g_free(info->description);
549 info->description = g_strdup(content);
550 }
551 xmlFree(content);
552 }
553 break;
554 }
555 if (node_filename) xmlFree(node_filename);
556 }
557 }
558
559 void
dia_pluginrc_write(void)560 dia_pluginrc_write(void)
561 {
562 gchar *filename;
563 GList *tmp;
564
565 ensure_pluginrc();
566 for (tmp = plugins; tmp != NULL; tmp = tmp->next) {
567 PluginInfo *info = tmp->data;
568 xmlNodePtr node, pluginnode, datanode;
569
570 if (info == NULL) {
571 continue;
572 }
573
574 pluginnode = xmlNewNode(NULL, (const xmlChar *)"plugin");
575 datanode = xmlNewChild(pluginnode, NULL, (const xmlChar *)"name", (xmlChar *)info->name);
576 /* FIXME: UNICODE_WORK_IN_PROGRESS why is this reencoding necessary ?*/
577 {
578 xmlChar *enc = xmlEncodeEntitiesReentrant(pluginnode->doc,
579 (xmlChar *)info->description);
580 datanode = xmlNewChild(pluginnode, NULL, (const xmlChar *)"description", enc);
581 xmlFree(enc);
582 }
583 if (info->inhibit_load)
584 datanode = xmlNewChild(pluginnode, NULL, (const xmlChar *)"inhibit-load", NULL);
585
586 for (node = pluginrc->xmlRootNode->xmlChildrenNode;
587 node != NULL;
588 node = node->next) {
589 xmlChar *node_filename;
590
591 if (xmlIsBlankNode(node)) continue;
592
593 if (node->type != XML_ELEMENT_NODE || xmlStrcmp(node->name, (const xmlChar *)"plugin") != 0)
594 continue;
595 node_filename = xmlGetProp(node, (const xmlChar *)"filename");
596 if (node_filename && !strcmp(info->filename, (char *) node_filename)) {
597 xmlFree(node_filename);
598 xmlReplaceNode(node, pluginnode);
599 xmlFreeNode(node);
600 break;
601 }
602 if (node_filename) xmlFree(node_filename);
603 }
604 /* node wasn't in document ... */
605 if (!node)
606 xmlAddChild(pluginrc->xmlRootNode, pluginnode);
607 /* have to call this after adding node to document */
608 xmlSetProp(pluginnode, (const xmlChar *)"filename", (xmlChar *)info->filename);
609 }
610
611 filename = dia_config_filename("pluginrc");
612
613 xmlDiaSaveFile(filename,pluginrc);
614
615 g_free(filename);
616 free_pluginrc();
617 }
618
619
620