2011-02-23 [colin] 3.7.8cvs59
[claws.git] / src / common / plugin.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2011 Hiroyuki Yamamoto and the Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include <stdio.h>
26 #ifdef HAVE_VALGRIND
27 #include <valgrind.h>
28 #endif
29
30 #include "defs.h"
31 #include <glib.h>
32 #ifdef ENABLE_NLS
33 #include <glib/gi18n.h>
34 #else
35 #define _(a) (a)
36 #define N_(a) (a)
37 #endif
38 #include <gmodule.h>
39
40 #include "utils.h"
41 #include "plugin.h"
42 #include "prefs.h"
43 #include "claws.h"
44 #include "timing.h"
45
46 struct _Plugin
47 {
48         gchar   *filename;
49         GModule *module;
50         const gchar *(*name) (void);
51         const gchar *(*desc) (void);
52         const gchar *(*version) (void);
53         const gchar *(*type) (void);
54         const gchar *(*licence) (void);
55         struct PluginFeature *(*provides) (void);
56         
57         GSList *rdeps;
58         gchar *error;
59         gboolean unloaded_hidden;
60 };
61
62 const gchar *plugin_feature_names[] =
63         { N_("Nothing"),
64           N_("a viewer"),
65           N_("a MIME parser"),
66           N_("folders"),
67           N_("filtering"),
68           N_("a privacy interface"),
69           N_("a notifier"),
70           N_("an utility"),
71           N_("things"),
72           NULL };
73
74 /**
75  * List of all loaded plugins
76  */
77 GSList *plugins = NULL;
78 GSList *plugin_types = NULL;
79
80 /* 
81  * List of plugins unloaded for some fixable reason
82  */
83 static GSList *unloaded_plugins = NULL;
84
85 static gint list_find_by_string(gconstpointer data, gconstpointer str)
86 {
87         return strcmp((gchar *)data, (gchar *)str) ? TRUE : FALSE;
88 }
89
90 static gint list_find_by_plugin_filename(const Plugin *plugin, const gchar *filename)
91 {
92         /* FIXME: There is a problem in case of symlinks or when a
93            user tries to load a plugin with the same name from a
94            different directory.  I think it would be better to compare
95            only the basename of the filename here (case-insensitive on
96            W32). */
97         cm_return_val_if_fail(plugin, 1);
98         cm_return_val_if_fail(plugin->filename, 1);
99         cm_return_val_if_fail(filename, 1);
100         return strcmp(filename, plugin->filename);
101 }
102
103 void plugin_save_list(void)
104 {
105         gchar *rcpath, *block;
106         PrefFile *pfile;
107         GSList *type_cur, *plugin_cur;
108         Plugin *plugin;
109         
110         for (type_cur = plugin_types; type_cur != NULL; type_cur = g_slist_next(type_cur)) {
111                 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
112 #ifdef G_OS_WIN32
113                 block = g_strconcat("PluginsWin32_", type_cur->data, NULL);
114 #else
115                 block = g_strconcat("Plugins_", type_cur->data, NULL);
116 #endif
117                 if ((pfile = prefs_write_open(rcpath)) == NULL ||
118                     (prefs_set_block_label(pfile, block) < 0)) {
119                         g_warning("failed to write plugin list\n");
120                         g_free(rcpath);
121                         return;
122                 }
123                 g_free(block);
124
125                 for (plugin_cur = plugins; plugin_cur != NULL; plugin_cur = g_slist_next(plugin_cur)) {
126                         plugin = (Plugin *) plugin_cur->data;
127                         
128                         if (plugin->unloaded_hidden)
129                                 continue;
130
131                         if (!strcmp(plugin->type(), type_cur->data))
132                                 if (fprintf(pfile->fp, "%s\n", plugin->filename) < 0)
133                                         goto revert;
134                 }
135                 for (plugin_cur = unloaded_plugins; plugin_cur != NULL; plugin_cur = g_slist_next(plugin_cur)) {
136                         plugin = (Plugin *) plugin_cur->data;
137
138                         if (plugin->unloaded_hidden)
139                                 continue;
140                         
141                         if (!strcmp(plugin->type(), type_cur->data))
142                                 if (fprintf(pfile->fp, "%s\n", plugin->filename) < 0)
143                                         goto revert;
144                 }
145                 if (fprintf(pfile->fp, "\n") < 0)
146                         goto revert;
147
148                 if (prefs_file_close(pfile) < 0)
149                         g_warning("failed to write plugin list\n");
150
151                 g_free(rcpath); 
152                 
153                 continue;
154
155 revert:
156                 g_warning("failed to write plugin list\n");
157                 if (prefs_file_close_revert(pfile) < 0)
158                         g_warning("failed to revert plugin list\n");
159
160                 g_free(rcpath); 
161         }
162 }
163
164 static gboolean plugin_is_loaded(const gchar *filename)
165 {
166         return (g_slist_find_custom(plugins, filename, 
167                   (GCompareFunc)list_find_by_plugin_filename) != NULL);
168 }
169
170 static Plugin *plugin_get_by_filename(const gchar *filename)
171 {
172         GSList *cur = plugins;
173         for(; cur; cur = cur->next) {
174                 Plugin *p = (Plugin *)cur->data;
175                 if (!strcmp(p->filename, filename)) {
176                         return p;
177                 } 
178         }
179         return NULL;
180 }
181
182 /** 
183  * Loads a plugin dependancies
184  * 
185  * Plugin dependancies are, optionnaly, listed in a file in
186  * get_plugin_dir()/$pluginname.deps.
187  * \param filename The filename of the plugin for which we have to load deps
188  * \param error The location where an error string can be stored
189  * \return 0 on success, -1 otherwise
190  */
191 static gint plugin_load_deps(const gchar *filename, gchar **error)
192 {
193         gchar *tmp;
194         gchar *deps_file = NULL;
195         FILE *fp = NULL;
196         gchar buf[BUFFSIZE];
197         gchar *p;
198
199         tmp = g_strdup(filename);
200         if( (p = strrchr(tmp, '.')) )
201           *p = '\0';
202         deps_file = g_strconcat(tmp, ".deps", NULL);
203         g_free(tmp);
204         
205         fp = g_fopen(deps_file, "rb");
206         g_free(deps_file);
207         
208         if (!fp)
209                 return 0;
210         
211         while (fgets(buf, sizeof(buf), fp) != NULL) {
212                 Plugin *dep_plugin = NULL;
213                 gchar *path = NULL;
214                 buf[strlen(buf)-1]='\0'; /* chop off \n */
215                 path = g_strconcat(get_plugin_dir(), buf,
216                                 ".", G_MODULE_SUFFIX, NULL);
217                 if ((dep_plugin = plugin_get_by_filename(path)) == NULL) {
218                         debug_print("trying to load %s\n", path);
219                         dep_plugin = plugin_load(path, error);
220                         if (dep_plugin == NULL) {
221                                 g_free(path);
222                                 fclose(fp);
223                                 return -1;
224                         }
225                 }
226                 g_free(path);
227                 if (!g_slist_find_custom(dep_plugin->rdeps, 
228                                 (gpointer) filename, list_find_by_string)) {
229                         debug_print("adding %s to %s rdeps\n",
230                                 filename,
231                                 dep_plugin->filename);
232                         dep_plugin->rdeps = 
233                                 g_slist_append(dep_plugin->rdeps, 
234                                         g_strdup(filename));
235                 }
236         }
237         fclose(fp);
238         return 0;
239 }
240
241 static void plugin_unload_rdeps(Plugin *plugin)
242 {
243         GSList *cur = plugin->rdeps;
244         debug_print("removing %s rdeps\n", plugin->filename);
245         while (cur) {
246                 gchar *file = (gchar *)cur->data;
247                 Plugin *rdep_plugin = file?plugin_get_by_filename(file):NULL;
248                 debug_print(" rdep %s: %p\n", file, rdep_plugin);
249                 if (rdep_plugin) {
250                         plugin_unload(rdep_plugin);
251                 }
252                 g_free(file);
253                 cur = cur->next;
254         }
255         g_slist_free(plugin->rdeps);
256         plugin->rdeps = NULL;
257 }
258
259 static void plugin_remove_from_unloaded_list (const gchar *filename)
260 {
261         GSList *item = g_slist_find_custom(unloaded_plugins, 
262                                 (gpointer) filename, (GCompareFunc)list_find_by_plugin_filename);
263         Plugin *unloaded_plugin = item ? ((Plugin *)item->data):NULL;
264         if (unloaded_plugin != NULL) {
265                 debug_print("removing %s from unloaded list\n", unloaded_plugin->filename);
266                 unloaded_plugins = g_slist_remove(unloaded_plugins, unloaded_plugin);
267                 g_module_close(unloaded_plugin->module);
268                 g_free(unloaded_plugin->filename);
269                 g_free(unloaded_plugin->error);
270                 g_free(unloaded_plugin);
271         }
272 }
273
274 static gchar *plugin_check_features(struct PluginFeature *features) {
275         int i = 0, j = 0;
276         GSList *cur = plugins;
277
278         if (features == NULL)
279                 return NULL;
280         for(; cur; cur = cur->next) {
281                 Plugin *p = (Plugin *)cur->data;
282                 struct PluginFeature *cur_features = p->provides();
283                 if (p->unloaded_hidden)
284                         continue;
285                 for (j = 0; cur_features[j].type != PLUGIN_NOTHING; j++) {
286                         for (i = 0; features[i].type != PLUGIN_NOTHING; i++) {
287                                 if (cur_features[j].type == features[i].type &&
288                                     !strcmp(cur_features[j].subtype, features[i].subtype)) {
289                                         return g_strdup_printf(_(
290                                                 "This plugin provides %s (%s), which is "
291                                                 "already provided by the %s plugin."),
292                                                 _(plugin_feature_names[features[i].type]), 
293                                                 _(features[i].subtype),
294                                                 p->name());
295                                 }
296                         }
297                 }
298         }
299
300         return NULL;
301 }
302 /**
303  * Loads a plugin
304  *
305  * \param filename The filename of the plugin to load
306  * \param error The location where an error string can be stored
307  * \return the plugin on success, NULL otherwise
308  */
309 Plugin *plugin_load(const gchar *filename, gchar **error)
310 {
311         Plugin *plugin;
312         gint (*plugin_init) (gchar **error);
313         gpointer plugin_name, plugin_desc, plugin_version;
314         const gchar *(*plugin_type)(void);
315         const gchar *(*plugin_licence)(void);
316         struct PluginFeature *(*plugin_provides)(void);
317         gint ok;
318         START_TIMING((filename?filename:"NULL plugin"));
319         cm_return_val_if_fail(filename != NULL, NULL);
320         cm_return_val_if_fail(error != NULL, NULL);
321
322         /* check duplicate plugin path name */
323         if (plugin_is_loaded(filename)) {
324                 plugin = plugin_get_by_filename(filename);
325                 if (plugin->unloaded_hidden) {
326                         /* reshow it */
327                         goto init_plugin;
328                 } else {
329                         *error = g_strdup(_("Plugin already loaded"));
330                         return NULL;            
331                 }
332         }                              
333         
334         plugin_remove_from_unloaded_list(filename);
335         
336         if (plugin_load_deps(filename, error) < 0)
337                 return NULL;
338         plugin = g_new0(Plugin, 1);
339         if (plugin == NULL) {
340                 *error = g_strdup(_("Failed to allocate memory for Plugin"));
341                 return NULL;
342         }
343
344         debug_print("trying to load `%s'\n", filename);
345         plugin->module = g_module_open(filename, 0);
346         if (plugin->module == NULL) {
347                 *error = g_strdup(g_module_error());
348                 g_free(plugin);
349                 return NULL;
350         }
351
352 init_plugin:
353         if (!g_module_symbol(plugin->module, "plugin_name", &plugin_name) ||
354             !g_module_symbol(plugin->module, "plugin_desc", &plugin_desc) ||
355             !g_module_symbol(plugin->module, "plugin_version", &plugin_version) ||
356             !g_module_symbol(plugin->module, "plugin_type", (gpointer)&plugin_type) ||
357             !g_module_symbol(plugin->module, "plugin_licence", (gpointer)&plugin_licence) ||
358             !g_module_symbol(plugin->module, "plugin_provides", (gpointer)&plugin_provides) ||
359             !g_module_symbol(plugin->module, "plugin_init", (gpointer)&plugin_init)) {
360                 *error = g_strdup(g_module_error());
361                 if (plugin->unloaded_hidden)
362                         return NULL;
363                 g_module_close(plugin->module);
364                 g_free(plugin);
365                 return NULL;
366         }
367         
368         if (strcmp(plugin_licence(), "GPL2+") && strncmp(plugin_licence(), "GPL3", strlen("GPL3"))
369         &&  strncmp(plugin_licence(), "GPL2+-compatible", strlen("GPL2+-compatible"))) {
370                 *error = g_strdup(_("This module is not licensed under a GPL v2 or later compatible license."));
371                 if (plugin->unloaded_hidden)
372                         return NULL;
373                 g_module_close(plugin->module);
374                 g_free(plugin);
375                 return NULL;
376         }
377
378         if (!strcmp(plugin_type(), "GTK")) {
379                 *error = g_strdup(_("This module is for Claws Mail GTK1."));
380                 if (plugin->unloaded_hidden)
381                         return NULL;
382                 g_module_close(plugin->module);
383                 g_free(plugin);
384                 return NULL;
385         }
386
387         if ((*error = plugin_check_features(plugin_provides())) != NULL) {
388                 if (plugin->unloaded_hidden)
389                         return NULL;
390                 g_module_close(plugin->module);
391                 g_free(plugin);
392                 return NULL;
393         }
394         plugin->name = plugin_name;
395         plugin->desc = plugin_desc;
396         plugin->version = plugin_version;
397         plugin->type = plugin_type;
398         plugin->licence = plugin_licence;
399         plugin->provides = plugin_provides;
400         plugin->filename = g_strdup(filename);
401         plugin->error = NULL;
402
403         if ((ok = plugin_init(error)) < 0) {
404                 if (*error)
405                         plugin->error = g_strdup(*error);
406                 unloaded_plugins = g_slist_append(unloaded_plugins, plugin);
407                 return NULL;
408         }
409
410         if (!plugin->unloaded_hidden)
411                 plugins = g_slist_append(plugins, plugin);
412         plugin->unloaded_hidden = FALSE;
413
414         debug_print("Plugin %s (from file %s) loaded\n", plugin->name(), filename);
415         END_TIMING();
416         return plugin;
417 }
418
419 void plugin_unload(Plugin *plugin)
420 {
421         gboolean (*plugin_done) (void);
422         gboolean can_unload = TRUE;
423
424         plugin_unload_rdeps(plugin);
425
426         if (plugin->unloaded_hidden)
427                 return;
428
429         if (plugin->error) {
430                 plugin_remove_from_unloaded_list(plugin->filename);
431                 return;
432         }
433         if (g_module_symbol(plugin->module, "plugin_done", (gpointer) &plugin_done)) {
434                 can_unload = plugin_done();
435         }
436
437         if (can_unload) {
438 #ifdef HAVE_VALGRIND
439                 if (!RUNNING_ON_VALGRIND) {
440                         g_module_close(plugin->module);
441                 }
442 #else
443                 g_module_close(plugin->module);
444 #endif
445                 plugins = g_slist_remove(plugins, plugin);
446                 g_free(plugin->filename);
447                 g_free(plugin);
448         } else {
449                 plugin->unloaded_hidden = TRUE;
450         }
451
452 }
453
454 void plugin_load_all(const gchar *type)
455 {
456         gchar *rcpath;
457         gchar buf[BUFFSIZE];
458         PrefFile *pfile;
459         gchar *error = NULL, *block;
460
461         plugin_types = g_slist_append(plugin_types, g_strdup(type));
462
463         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL); 
464 #ifdef G_OS_WIN32
465         block = g_strconcat("PluginsWin32_", type, NULL);
466 #else
467         block = g_strconcat("Plugins_", type, NULL);
468 #endif
469         if ((pfile = prefs_read_open(rcpath)) == NULL ||
470             (prefs_set_block_label(pfile, block) < 0)) {
471                 g_free(rcpath);
472                 if (pfile)
473                         prefs_file_close(pfile);
474                 return;
475         }
476         g_free(block);
477
478         while (fgets(buf, sizeof(buf), pfile->fp) != NULL) {
479                 if (buf[0] == '[')
480                         break;
481
482                 g_strstrip(buf);
483                 if ((buf[0] != '\0') && (plugin_load(buf, &error) == NULL)) {
484                         g_warning("plugin loading error: %s\n", error);
485                         g_free(error);
486                 }                                                       
487         }
488         prefs_file_close(pfile);
489
490         g_free(rcpath);
491 }
492
493 void plugin_unload_all(const gchar *type)
494 {
495         GSList *list, *cur;
496
497         list = g_slist_copy(plugins);
498         list = g_slist_reverse(list);
499
500         for(cur = list; cur != NULL; cur = g_slist_next(cur)) {
501                 Plugin *plugin = (Plugin *) cur->data;
502                 
503                 if (!strcmp(type, plugin->type()))
504                         plugin_unload(plugin);
505         }
506         g_slist_free(list);
507
508         cur = g_slist_find_custom(plugin_types, (gpointer) type, list_find_by_string);
509         if (cur) {
510                 g_free(cur->data);
511                 plugin_types = g_slist_remove(plugin_types, cur);
512         }
513 }
514
515
516 /* Load those plugins we always want to use.  No error output; just
517  * try. */
518 void plugin_load_standard_plugins (void)
519 {
520         static const char *names[] = {
521 #ifdef G_OS_WIN32 
522                 "pgpmime",
523                 "pgpinline",
524 #else
525                 /* post-2.5 maybe 
526                 "bogofilter", */
527 #endif
528                 NULL
529         };
530         int i;
531         gchar *error, *filename;
532         
533         for (i=0; names[i]; i++) {
534                 /* Simple hack to check whether the plugin has already
535                  * been loaded but checking only for the basename. */
536                 GSList *cur = plugins;
537                 for(; cur; cur = cur->next) {
538                         Plugin *p = (Plugin *)cur->data;
539                         if (strstr(p->filename, names[i]))
540                                 break;
541                 }
542                 if (!cur) { /* Not yet loaded. */
543                         /* FIXME: get_plugin_dir () returns with a trailing
544                          * (back)slash; this should be fixed so that we can use
545                          * g_module_build_path here. */
546 #ifdef G_OS_WIN32 
547                         filename = g_strconcat (get_plugin_dir(),
548                                                 names[i], NULL);
549 #else
550                         filename = g_strconcat (get_plugin_dir(),
551                                                 names[i], ".", G_MODULE_SUFFIX, NULL);
552 #endif
553                         error = NULL;
554                         plugin_load(filename, &error);
555                         g_free (error);
556                         g_free(filename);
557                 }
558         }
559 }
560
561 GSList *plugin_get_list(void)
562 {
563         GSList *new = NULL;
564         GSList *cur = plugins;
565         for (; cur; cur = cur->next) {
566                 Plugin *p = (Plugin *)cur->data;
567                 if (!p->unloaded_hidden)
568                         new = g_slist_prepend(new, p);
569         }
570         new = g_slist_reverse(new);
571         return new;
572 }
573
574 Plugin *plugin_get_loaded_by_name(const gchar *name) 
575 {
576         Plugin *plugin = NULL;
577         GSList *new, *cur; 
578         new = plugin_get_list();
579         for (cur = new; cur; cur = g_slist_next(cur)) {
580                 plugin = (Plugin *)cur->data;
581                 if (!g_ascii_strcasecmp(plugin->name(), name)) 
582                         break;
583                 else 
584                         plugin = NULL;
585         }
586         g_slist_free(new);
587         return plugin;
588 }
589
590 GSList *plugin_get_unloaded_list(void)
591 {
592         return g_slist_copy(unloaded_plugins);
593 }
594
595 const gchar *plugin_get_name(Plugin *plugin)
596 {
597         return plugin->name();
598 }
599
600 const gchar *plugin_get_desc(Plugin *plugin)
601 {
602         return plugin->desc();
603 }
604
605 const gchar *plugin_get_version(Plugin *plugin)
606 {
607         return plugin->version();
608 }
609
610 const gchar *plugin_get_error(Plugin *plugin)
611 {
612         return plugin->error;
613 }
614
615 /* Generally called in plugin_init() function of each plugin. It check the
616  * minimal and compiled version of claws binary required by the plugin.
617  * If (@minimum_claws_version == 0 || @compiled_claws_version == 0), don't
618  * check the corresponding version.
619  *
620  * If an error occurs {
621  *      If @error == NULL { don't allocate error string. }
622  *      If @error != NULL { error string is allocated and must be freed after 
623  *                              call function. }
624  * }
625  * Returns: FALSE if an error occurs, TRUE if all is OK.
626  */
627 gint check_plugin_version(guint32 minimum_claws_version,
628                          guint32 compiled_claws_version,
629                          const gchar *plugin_name,
630                          gchar **error)
631 {
632         guint32 claws_version = claws_get_version();
633
634         if (compiled_claws_version != 0 && claws_version > compiled_claws_version) {
635                 if (error != NULL) {
636                         *error = (plugin_name && *plugin_name)
637                                 ? g_strdup_printf(_("Your version of Claws Mail is newer than the "
638                                                         "version the '%s' plugin was built with."),
639                                                 plugin_name)
640                                 : g_strdup(_("Your version of Claws Mail is newer than the "
641                                                         "version the plugin was built with."));
642                 }
643                 return FALSE;
644         }
645
646         if (minimum_claws_version != 0 && claws_version < minimum_claws_version) {
647                 if (error != NULL) {
648                         *error = (plugin_name && *plugin_name)
649                                 ? g_strdup_printf(_("Your version of Claws Mail is too old for "
650                                                         "the '%s' plugin."), plugin_name)
651                                 : g_strdup(_("Your version of Claws Mail is too old for the plugin."));
652                 }
653                 return FALSE;
654         }
655         return TRUE;
656 }