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