[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: cdebconf plugins (custom widgets)



On Mon, Apr 18, 2005 at 12:09:23PM -0400, Joey Hess wrote:
> Colin Watson wrote:
> > There are a few open questions remaining:
> > 
> >   * With this design, udeb code is likely to look something like "if
> >     this all-dancing widget is supported, then use it; otherwise, do it
> >     by hand pretty much the way it looks at the moment". My sample
> >     implementation exports the list of supported plugins as "plugin-foo"
> >     capabilities in the CAPB response, but I wonder if a new SUPPORTS
> >     command (so you could do "db_supports whizzy-new-widget") mightn't
> >     be better. On the other hand, that's more client-side divergence
> >     from debconf, and maybe a simple /bin/debconf-supports command would
> >     be simpler.
> 
> I actually like the capb approach. I think we may eventually want to
> expand it so a given widget can cause multiple capbs to be set.

Mmm, yeah. That would probably be straightforward to do if we wanted to;
it'd just be an extra dlsym to specify in the interface.

> >   * I envisage custom widgets being fairly complex in some cases, with
> >     several items of data being returned at once: for example, consider
> >     a combined language/country chooser widget that dynamically updates
> >     the list of countries presented as you change the selected language,
> >     or something like that. It's hard (and arguably a bad idea) to put
> >     much structured data in a single question, though. I don't have a
> >     clear answer to this at the moment; maybe the right approach is for
> >     a complex widget to register extra templates for itself, and just
> >     put the answers there, so that 'db_input foo/whizzy' gives you
> >     answers to foo/whizzy/language and foo/whizzy/country, or something
> >     like that.
> 
> Well you could implement the container question type (see versions of
> the debconf spec before it was removed). Then you have essentially,
> nestable structs of questions.

Can you point me to where I would find the old spec? I see comments
about it in debconf's svn log, but the actual files don't seem to be in
the history ...

> > The required prototype for this function depends on the frontend. For newt
> > and text, it is (again with hyphens in <type> substituted with underscores):
> > 
> >   int <frontend>_handler_<type>(struct frontend *obj, struct question *q)
> > 
> > For gtk, it is:
> > 
> >   int <frontend>_handler_<type>(struct frontend *obj, struct question *q, GtkWidget *questionbox);
> 
> Maybe mention that the return code is DC_OK or DC_GOBACK.

Done in the version of the spec that I checked in earlier today.

I also noticed that the patch I sent was broken: it loaded all plugins
at frontend startup time, which obviously doesn't work because we want
to be able to install udebs containing plugins later on (I blame lack of
sleep and aeroplane timezones). Here's a ripped-apart and rearranged
version. I added a new 'name' member to struct frontend in the process
to hold the frontend name (since I need it to look up plugins), although
I was slightly surprised it wasn't there already ...

Cheers,

-- 
Colin Watson                                       [cjwatson@debian.org]
Index: src/Makefile.in
===================================================================
--- src/Makefile.in	(revision 26856)
+++ src/Makefile.in	(working copy)
@@ -19,7 +19,7 @@
 
 LIBOBJS=commands.opic configuration.opic confmodule.opic debug.opic \
 	database.opic debconfclient.opic frontend.opic priority.opic \
-	strutl.opic question.opic template.opic rfc822.opic
+	strutl.opic question.opic template.opic rfc822.opic plugin.opic
 
 CLILIBOBJS=debconfclient.opic
 
Index: src/commands.c
===================================================================
--- src/commands.c	(revision 26856)
+++ src/commands.c	(working copy)
@@ -4,6 +4,7 @@
 #include "database.h"
 #include "question.h"
 #include "template.h"
+#include "plugin.h"
 #include "strutl.h"
 
 #include <dlfcn.h>
@@ -112,7 +113,10 @@
     int i;
     char *argv[32];
     int argc;
-    char *out;
+    char *out, *outend;
+    size_t outalloc;
+    struct plugin *plugin;
+    void *plugin_state;
 
     argc = strcmdsplit(arg, argv, DIM(argv));
     /* FIXME: frontend.h should provide a method to prevent direct
@@ -122,7 +126,28 @@
         if (strcmp(argv[i], "backup") == 0)
             mod->frontend->capability |= DCF_CAPB_BACKUP;
 
-    asprintf(&out, "%u multiselect backup", CMDSTATUS_SUCCESS);
+    if (asprintf(&out, "%u multiselect backup", CMDSTATUS_SUCCESS) == -1)
+        DIE("Out of memory");
+
+    plugin_state = NULL;
+    outend = strchr(out, '\0');
+    outalloc = outend - out + 1;
+    while ((plugin = plugin_iterate(mod->frontend, &plugin_state)) != NULL) {
+        size_t namelen;
+        char *newout;
+
+        namelen = strlen(plugin->name);
+        outalloc += 8 + namelen;
+        newout = realloc(out, outalloc);
+        if (!newout)
+            DIE("Out of memory");
+        outend = newout + (outend - out);
+        out = newout;
+        outend = mempcpy(outend, " plugin-", 8);
+        outend = mempcpy(outend, plugin->name, namelen);
+        *outend++ = '\0';
+    }
+
     return out;
 }
 
Index: src/frontend.c
===================================================================
--- src/frontend.c	(revision 26856)
+++ src/frontend.c	(working copy)
@@ -1,5 +1,6 @@
 #include "common.h"
 #include "configuration.h"
+#include "plugin.h"
 #include "database.h"
 #include "frontend.h"
 #include "question.h"
@@ -149,12 +150,13 @@
     setenv("DEBIAN_FRONTEND", modname, 1);
     obj = NEW(struct frontend);
     memset(obj, 0, sizeof(struct frontend));
+
+    modpath = cfg->get(cfg, "global::module_path::frontend", 0);
+    if (modpath == NULL)
+	DIE("Frontend module path not defined (global::module_path::frontend)");
+
     if (strcmp(modname, "none") != 0)
     {
-        modpath = cfg->get(cfg, "global::module_path::frontend", 0);
-        if (modpath == NULL)
-            DIE("Frontend module path not defined (global::module_path::frontend)");
-
         q = qdb->methods.get(qdb, "debconf/frontend");
         if (q)
 	    question_setvalue(q, modname);
@@ -170,6 +172,7 @@
 	
 	memcpy(&obj->methods, mod, sizeof(struct frontend_module));
     }
+    obj->name = strdup(modname);
 	obj->handle = dlh;
 	obj->config = cfg;
 	obj->tdb = tdb;
@@ -177,7 +180,12 @@
     snprintf(obj->configpath, sizeof(obj->configpath),
         "frontend::instance::%s", modname);
 
+    if (asprintf(&obj->pluginpath, "%s/%s", modpath, modname) == -1) {
+        frontend_delete(obj);
+        return NULL;
+    }
 
+
 #define SETMETHOD(method) if (obj->methods.method == NULL) obj->methods.method = frontend_##method
 
 	SETMETHOD(initialize);
@@ -220,6 +228,7 @@
 	DELETE(obj->title);
 	DELETE(obj->info);
     DELETE(obj->progress_title);
+    DELETE(obj->pluginpath);
 	DELETE(obj);
 }
 
Index: src/frontend.h
===================================================================
--- src/frontend.h	(revision 26856)
+++ src/frontend.h	(working copy)
@@ -39,6 +39,8 @@
 };
 
 struct frontend {
+    /* module name */
+    char *name;
 	/* module handle */
 	void *handle;
 	/* configuration data */
@@ -64,6 +66,8 @@
 	
 	/* methods */
     struct frontend_module methods;
+    /* path to plugins */
+    char *pluginpath;
 };
 
 struct frontend *frontend_new(struct configuration *, struct template_db *, struct question_db *);
Index: src/modules/frontend/text/text.c
===================================================================
--- src/modules/frontend/text/text.c	(revision 26856)
+++ src/modules/frontend/text/text.c	(working copy)
@@ -44,6 +44,7 @@
 #include "question.h"
 #include "frontend.h"
 #include "database.h"
+#include "plugin.h"
 #include "strutl.h"
 
 #include <ctype.h>
@@ -59,6 +60,8 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+typedef int (text_handler)(struct frontend *obj, struct question *q);
+
 #define q_get_extended_description(q)   question_get_field((q), "", "extended_description")
 #define q_get_description(q)		question_get_field((q), "", "description")
 #define q_get_choices(q)		question_get_field((q), "", "choices")
@@ -756,7 +759,7 @@
 /* ----------------------------------------------------------------------- */
 struct question_handlers {
 	const char *type;
-	int (*handler)(struct frontend *obj, struct question *q);
+	text_handler *handler;
 } question_handlers[] = {
 	{ "boolean",	text_handler_boolean },
 	{ "multiselect", text_handler_multiselect },
@@ -766,6 +769,7 @@
 	{ "string",	text_handler_string },
 	{ "text",	text_handler_text },
 	{ "error",	text_handler_error },
+	{ "",		NULL },
 };
 
 /*
@@ -816,9 +820,24 @@
 
 	while (q != NULL) {
 		for (i = 0; i < DIM(question_handlers); i++) {
-			if (strcmp(q->template->type, question_handlers[i].type) == 0) 
+			text_handler *handler;
+			struct plugin *plugin = NULL;
+
+			if (*question_handlers[i].type)
+				handler = question_handlers[i].handler;
+			else {
+				plugin = plugin_find(obj, q->template->type);
+				if (plugin) {
+					INFO(INFO_DEBUG, "Found plugin for %s", q->template->type);
+					handler = (text_handler *) plugin->handler;
+				} else {
+					INFO(INFO_DEBUG, "No plugin for %s", q->template->type);
+					continue;
+				}
+			}
+
+			if (plugin || strcmp(q->template->type, question_handlers[i].type) == 0)
 			{
-
 				if (display_title)
 				{
 					/* TODO: can't tell if we called go()
@@ -832,13 +851,18 @@
 					display_title = 0;
 				}
 				text_handler_displaydesc(obj, q);
-				ret = question_handlers[i].handler(obj, q);
+				ret = handler(obj, q);
 				if (ret == DC_OK)
 					obj->qdb->methods.set(obj->qdb, q);
 				else if (ret == DC_GOBACK && q->prev != NULL)
 					q = q->prev;
-				else
+				else {
+					if (plugin)
+						plugin_delete(plugin);
 					return ret;
+				}
+				if (plugin)
+					plugin_delete(plugin);
 				break;
 			}
 		}
Index: src/modules/frontend/gtk/gtk.c
===================================================================
--- src/modules/frontend/gtk/gtk.c	(revision 26856)
+++ src/modules/frontend/gtk/gtk.c	(working copy)
@@ -49,6 +49,7 @@
 #include "question.h"
 #include "frontend.h"
 #include "database.h"
+#include "plugin.h"
 #include "strutl.h"
 #include "cdebconf_gtk.h"
 
@@ -63,8 +64,12 @@
 #include <sys/types.h>
 #include <dlfcn.h>
 
+#include <debian-installer/slist.h>
+
 #include <gtk/gtk.h>
 
+typedef int (gtk_handler)(struct frontend *obj, struct question *q, GtkWidget *questionbox);
+
 #define q_get_extended_description(q)   question_get_field((q), "", "extended_description")
 #define q_get_description(q)  		question_get_field((q), "", "description")
 #define q_get_choices(q)		question_get_field((q), "", "choices")
@@ -746,7 +751,7 @@
 /* ----------------------------------------------------------------------- */
 struct question_handlers {
     const char *type;
-    int (*handler)(struct frontend *obj, struct question *q, GtkWidget *questionbox);
+    gtk_handler *handler;
 } question_handlers[] = {
     { "boolean",        gtkhandler_boolean },
     { "multiselect",    gtkhandler_multiselect },
@@ -756,7 +761,8 @@
     { "string",	        gtkhandler_string },
     { "error",	        gtkhandler_note },
 //  { "custom",         gtkhandler_custom },
-    { "text",           gtkhandler_text }
+    { "text",           gtkhandler_text },
+    { "",               NULL },
 };
 
 void set_window_properties(GtkWidget *window)
@@ -840,6 +846,11 @@
     return DC_OK;
 }
 
+static void gtk_plugin_destroy_notify(void *data)
+{
+    plugin_delete((struct plugin *) data);
+}
+
 static int gtk_go(struct frontend *obj)
 {
     struct frontend_data *data = (struct frontend_data *) obj->data;
@@ -847,6 +858,7 @@
     int i;
     int ret;
     GtkWidget *questionbox;
+    di_slist *plugins;
 
     if (q == NULL) return DC_OK;
 
@@ -860,17 +872,38 @@
        gtk_label_set_text(GTK_LABEL(data->description_label), 
        q_get_extended_description(q)); 
     */
+    plugins = di_slist_alloc();
     while (q != 0)
     {
-        for (i = 0; i < DIM(question_handlers); i++)
-            if (strcmp(q->template->type, question_handlers[i].type) == 0)
+        for (i = 0; i < DIM(question_handlers); i++) {
+            gtk_handler *handler;
+            struct plugin *plugin = NULL;
+
+            if (*question_handlers[i].type)
+                handler = question_handlers[i].handler;
+            else {
+                plugin = plugin_find(obj, q->template->type);
+                if (plugin) {
+                    INFO(INFO_DEBUG, "Found plugin for %s", q->template->type);
+                    handler = (gtk_handler *) plugin->handler;
+                    di_slist_append(plugins, plugin);
+                } else {
+                    INFO(INFO_DEBUG, "No plugin for %s", q->template->type);
+                    continue;
+                }
+            }
+
+            if (plugin || strcmp(q->template->type, question_handlers[i].type) == 0)
             {
-                ret = question_handlers[i].handler(obj, q, questionbox);
+                ret = handler(obj, q, questionbox);
                 if (ret != DC_OK)
                 {
+                    di_slist_destroy(plugins, &gtk_plugin_destroy_notify);
                     return ret;
                 }
+                break;
             }
+        }
         q = q->next;
     }
     q = obj->questions;
@@ -889,6 +922,7 @@
 	    q = q->next;
 	}
     }
+    di_slist_destroy(plugins, &gtk_plugin_destroy_notify);
     gtk_widget_destroy(questionbox);
 
     /* FIXME
Index: src/modules/frontend/newt/newt.c
===================================================================
--- src/modules/frontend/newt/newt.c	(revision 26856)
+++ src/modules/frontend/newt/newt.c	(working copy)
@@ -41,6 +41,7 @@
 #include "question.h"
 #include "frontend.h"
 #include "database.h"
+#include "plugin.h"
 #include "strutl.h"
 
 #include <ctype.h>
@@ -70,6 +71,8 @@
     int           scale_textbox_height;
 };
 
+typedef int (newt_handler)(struct frontend *obj, struct question *q);
+
 #define q_get_extended_description(q)   question_get_field((q), "", "extended_description")
 #define q_get_description(q)  		question_get_field((q), "", "description")
 #define q_get_choices(q)		question_get_field((q), "", "choices")
@@ -961,7 +964,7 @@
 /* ----------------------------------------------------------------------- */
 struct question_handlers {
 	const char *type;
-	int (*handler)(struct frontend *obj, struct question *q);
+	newt_handler *handler;
 } question_handlers[] = {
 	{ "boolean",	newt_handler_boolean },         // OK
 	{ "multiselect", newt_handler_multiselect },
@@ -970,7 +973,8 @@
 	{ "password",	newt_handler_password },        // OK
 	{ "note",	newt_handler_note },            // OK
 	{ "text",	newt_handler_text },
-	{ "error",      newt_handler_error },
+	{ "error",	newt_handler_error },
+	{ "",		NULL },
 };
 
 /*
@@ -1018,7 +1022,23 @@
     cleared = 0;
     while (q != NULL) {
         for (i = 0; i < DIM(question_handlers); i++) {
-            if (strcmp(q->template->type, question_handlers[i].type) == 0) {
+            newt_handler *handler;
+            struct plugin *plugin = NULL;
+
+            if (*question_handlers[i].type)
+                handler = question_handlers[i].handler;
+            else {
+                plugin = plugin_find(obj, q->template->type);
+                if (plugin) {
+                    INFO(INFO_DEBUG, "Found plugin for %s", q->template->type);
+                    handler = (newt_handler *) plugin->handler;
+                } else {
+                    INFO(INFO_DEBUG, "No plugin for %s", q->template->type);
+                    continue;
+                }
+            }
+
+            if (plugin || strcmp(q->template->type, question_handlers[i].type) == 0) {
                 if (!cleared && !data->scale_form) {
                     cleared = 1;
                     newtInit();
@@ -1030,7 +1050,7 @@
                         newtDrawRootText(0, 0, text);
                     free(text);
                 }
-                ret = question_handlers[i].handler(obj, q);
+                ret = handler(obj, q);
                 if (ret == DC_OK)
                     obj->qdb->methods.set(obj->qdb, q);
                 else if (ret == DC_GOBACK && q->prev != NULL)
@@ -1038,8 +1058,12 @@
                 else {
                     if (cleared && !data->scale_form)
                         newtFinished();
+                    if (plugin)
+                        plugin_delete(plugin);
                     return ret;
                 }
+                if (plugin)
+                    plugin_delete(plugin);
                 break;
             }
         }
Index: src/test/test.config
===================================================================
--- src/test/test.config	(revision 26856)
+++ src/test/test.config	(working copy)
@@ -58,7 +58,7 @@
 read ans
 debug "ANS: $ans"
 
-for t in boolean multiselect note password select string text; do
+for t in boolean multiselect note password select string text help; do
 	askquestion "test/$t" critical
 done
 
Index: src/test/backup.config
===================================================================
--- src/test/backup.config	(revision 26856)
+++ src/test/backup.config	(working copy)
@@ -11,7 +11,7 @@
 db_register test/string test/string-register
 db_fset test/string-register seen false
 
-for type in string text-subst boolean multiselect note password select string text
+for type in string text-subst boolean multiselect note password select string text help
 do
     db_fset test/$type seen false
 done
@@ -65,6 +65,10 @@
         db_input critical test/text || [ $? -eq 30 ]
         ;;
     
+      10)
+        db_input critical test/help || [ $? -eq 30 ]
+        ;;
+    
       *)
         break
         ;;
Index: src/test/test.templates
===================================================================
--- src/test/test.templates	(revision 26856)
+++ src/test/test.templates	(working copy)
@@ -60,3 +60,8 @@
 Description: Enter some text - subst test
  This is the prompt for a text-type question.
  ${TEXT}
+
+Template: test/help
+Type: help
+Description: Test help text
+ This is some test help text, running inside the "help" custom widget.
Index: src/plugin.c
===================================================================
--- src/plugin.c	(revision 0)
+++ src/plugin.c	(revision 0)
@@ -0,0 +1,129 @@
+#include "common.h"
+#include "frontend.h"
+#include "plugin.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <dlfcn.h>
+
+/* Convert hyphens to underscores. Returns allocated string. */
+static char *make_symbol_name(const char *name)
+{
+    char *symbol, *symbolp;
+
+    symbol = strdup(name);
+    for (symbolp = symbol; *symbolp; ++symbolp) {
+        if (*symbolp == '-')
+            *symbolp = '_';
+    }
+    return symbol;
+}
+
+struct plugin *plugin_new(const char *frontend, const char *filename)
+{
+    struct plugin *plugin = NEW(struct plugin);
+    const char *base;
+    size_t baselen, symbollen;
+    char *typesymbol, *symbol;
+
+    base = strrchr(filename, '/');
+    if (base)
+        ++base;
+    else
+        base = filename;
+
+    baselen = strlen(base);
+    /* base must be plugin-<type>.so */
+    if (baselen < 11)
+        return NULL;
+    if (strncmp(base, "plugin-", 7) != 0)
+        return NULL;
+    if (strncmp(base + baselen - 3, ".so", 3) != 0)
+        return NULL;
+
+    plugin->name = malloc(baselen - 9);
+    strncpy(plugin->name, base + 7, baselen - 10);
+    plugin->name[baselen - 10] = '\0';
+    typesymbol = make_symbol_name(plugin->name);
+
+    plugin->module = dlopen(filename, RTLD_LAZY);
+    if (plugin->module == NULL) {
+        INFO(INFO_WARN, "Cannot load plugin module %s: %s",
+             filename, dlerror());
+        free(plugin->name);
+        DELETE(plugin);
+        return NULL;
+    }
+
+    symbollen = strlen(frontend) + 9 + strlen(plugin->name) + 1;
+    symbol = malloc(symbollen);
+    snprintf(symbol, symbollen, "%s_handler_%s", frontend, typesymbol);
+
+    plugin->handler = dlsym(plugin->module, symbol);
+    if (plugin->handler == NULL) {
+        INFO(INFO_WARN, "Malformed plugin module %s", filename);
+        plugin_delete(plugin);
+        return NULL;
+    }
+
+    return plugin;
+}
+
+void plugin_delete(struct plugin *plugin)
+{
+    INFO(INFO_VERBOSE, "Unloading plugin module %s", plugin->name);
+    dlclose(plugin->module);
+    free(plugin->name);
+    DELETE(plugin);
+}
+
+struct plugin *plugin_find(struct frontend *frontend, const char *name)
+{
+    char *filename;
+    struct plugin *plugin;
+
+    if (asprintf(&filename, "%s/plugin-%s.so",
+                 frontend->pluginpath, name) == -1)
+        DIE("Out of memory");
+    INFO(INFO_VERBOSE, "Trying to load plugin from %s", filename);
+    plugin = plugin_new(frontend->name, filename);
+    free(filename);
+
+    return plugin;
+}
+
+struct plugin *plugin_iterate(struct frontend *frontend, void **state)
+{
+    DIR *plugin_dir = *state;
+    struct dirent *plugin_dirent;
+
+    if (!plugin_dir) {
+        *state = plugin_dir = opendir(frontend->pluginpath);
+        if (!plugin_dir) {
+            if (errno != ENOENT)
+                INFO(INFO_WARN, "Cannot open plugin directory %s: %s",
+                     frontend->pluginpath, strerror(errno));
+            return NULL;
+        }
+    }
+
+    while ((plugin_dirent = readdir(plugin_dir)) != NULL) {
+        char *filename;
+        struct plugin *plugin;
+
+        if (asprintf(&filename, "%s/%s",
+                     frontend->pluginpath, plugin_dirent->d_name) == -1)
+            DIE("Out of memory");
+        plugin = plugin_new(frontend->name, filename);
+        free(filename);
+        if (plugin)
+            return plugin;
+    }
+
+    closedir(plugin_dir);
+    return NULL;
+}
Index: src/plugin.h
===================================================================
--- src/plugin.h	(revision 0)
+++ src/plugin.h	(revision 0)
@@ -0,0 +1,26 @@
+/**
+ * @file plugin.h
+ * @brief interfaces for handling custom debconf widget implementations
+ */
+#ifndef _PLUGIN_H_
+#define _PLUGIN_H_
+
+struct configuration;
+struct frontend;
+struct question;
+
+struct plugin {
+    /** type name */
+    char *name;
+    /** library module handle */
+    void *module;
+    /** handler function */
+    void *handler;
+};
+
+struct plugin *plugin_new(const char *frontend, const char *filename);
+void plugin_delete(struct plugin *plugin);
+struct plugin *plugin_find(struct frontend *frontend, const char *name);
+struct plugin *plugin_iterate(struct frontend *frontend, void **state);
+
+#endif

Reply to: