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

[PATCH] initial implementation of debconf frontend for conffile conflicts



when available, dpkg will now spawn a debconf-based frontend (which can be
found in /usr/share/dpkg/dpkg-promptconf.sh) for resolving conffile conflict

see TODO.debconf for a list of open questions and/or possible issues.
---
 TODO.debconf               |   70 +++++++++++++++++++++++++++++++
 debian/control             |    2 +-
 debian/dpkg.install        |    1 +
 debian/dpkg.templates      |   13 ++++++
 debian/po/POTFILES.in      |    1 +
 debian/po/templates.pot    |   59 ++++++++++++++++++++++++++
 debian/rules               |    2 +
 lib/dpkg.h                 |    2 +
 scripts/dpkg-confprompt.sh |   98 ++++++++++++++++++++++++++++++++++++++++++++
 src/configure.c            |   75 +++++++++++++++++++++++----------
 10 files changed, 299 insertions(+), 24 deletions(-)
 create mode 100644 TODO.debconf
 create mode 100644 debian/dpkg.templates
 create mode 100644 debian/po/POTFILES.in
 create mode 100644 debian/po/templates.pot
 create mode 100755 scripts/dpkg-confprompt.sh

diff --git a/TODO.debconf b/TODO.debconf
new file mode 100644
index 0000000..6db3177
--- /dev/null
+++ b/TODO.debconf
@@ -0,0 +1,70 @@
+- could my debconf "detection" be done better?
+
+  currently i just call debconf via system(), and i catch the particular
+  failure case where debconf fails to execute (return status = 127)
+
+- should i be using m_malloc() or varbuf functions for my string mangling?
+  
+  more of a general question.  currently i'm doing m_malloc() for stuff like
+  assembling a cmdline to pass to system, etc.  but i wonder if i should be 
+  using the varbuf related funcitons instead.
+
+- figure out how the templates should be managed within the source package
+
+  currently it's installed via the standard dh_installdebconf, but this is
+  a little awkward in the situation of dpkg, since dpkg itself does not use
+  the templates in the maintainer scripts and thus extra actions need to be
+  taken to register the templates files.  perhaps the file shouldn't be
+  traditionally installed at all, and instead kept somewhere outside of
+  <admindir>/info, since we rely on it existing somewhere and it doesn't
+  feel right poking around in there.  
+  
+  also, this means that there is a second po directory in debian, which
+  makes life a little more difficult for translators, and removes the
+  ability to share a common set of strings between the debconf stuff
+  and the rest of dpkg.
+
+- debconf script implementation
+
+  currently this is done with a small shell script.  while it uses mostly
+  built-in functions, it does mean at least 3 extra exec() calls per conffile
+  conflict (sh -c from system(), debconf, and then the script itself, plus
+  any non-builtin functions that these call).  this could be made slightly
+  better in a few ways i can think of:
+
+  - use an exec() family function instead of system, saving one sh -c exec()
+  - enhance the program to persist over the invocation of dpkg, talking
+    over a socket or similar.
+  
+  the script could also be re-implemented in C, and it's relatively simple
+  since it's basically printf/fgets calls wrapped in some logic,  but i don't
+  see it being particularly faster--the only advantage i see is that it would
+  allow to share a bit of code (it would still need to be executed seperately,
+  as far as i can tell, due to needing to interface with debconf as an external
+  program
+
+- setting and respecting defaults in debconf frontend
+
+  currently the debconf frontend always defaults to "keep installed version",
+  but in some cases the default has been changed and needs to be communicated
+  into the frontend wrapper.
+
+- per-package/file registration of templates?
+
+  currently we use a single template that is subtituted over and over again
+  with the various questions.  however, it's worth consideration if we want
+  to register the same template multiple times to different locations
+  (i.e. register dpkg/confprompt dpkg/confprompt/foo/etc/foo.cfg), so that
+  the answers to questions can persist like other debconf questions.
+
+- what should happen when someone selects "cancel"?
+
+  currently the window goes away, and pops up again :)
+
+- depends/recommends on debconf?
+
+  do we want to force/recommend having debconf available?  also, the frontend
+  script uses the X_LOADTEMPLATEFILE interface, which is only available
+  after debconf 1.5.19 and cdebconf 0.106.  however, if anything goes wrong
+  with the new frontend (lack of debconf, or internal failure of the
+  frontend), dpkg falls back to the "traditional" prompt.
diff --git a/debian/control b/debian/control
index 8393bf4..7bb3ef3 100644
--- a/debian/control
+++ b/debian/control
@@ -13,7 +13,7 @@ Standards-Version: 3.7.3
 Build-Depends: debhelper (>= 4.1.81), pkg-config, po4a (>= 0.23),
  libncursesw5-dev, zlib1g-dev (>= 1:1.1.3-19.1), libbz2-dev,
  libselinux1-dev (>= 1.28-4) [!hurd-i386 !kfreebsd-i386 !kfreebsd-amd64],
- libtimedate-perl, libio-string-perl
+ libtimedate-perl, libio-string-perl, po-debconf
 
 Package: dpkg
 Architecture: any
diff --git a/debian/dpkg.install b/debian/dpkg.install
index 2f54618..bcace45 100644
--- a/debian/dpkg.install
+++ b/debian/dpkg.install
@@ -1,5 +1,6 @@
 ../dpkg.cfg etc/dpkg
 ../archtable usr/share/dpkg
+../../scripts/dpkg-confprompt.sh usr/share/dpkg
 
 etc/alternatives
 etc/dpkg/origins
diff --git a/debian/dpkg.templates b/debian/dpkg.templates
new file mode 100644
index 0000000..8778993
--- /dev/null
+++ b/debian/dpkg.templates
@@ -0,0 +1,13 @@
+Template: dpkg/promptconfaction
+Type: select
+_Choices: Install the package maintainer's version, Keep the currently installed version, Show the differences between the current and new versions, Invoke a shell to investigate
+Default: Keep the currently installed version
+_Description: Select how dpkg should resolve the conflicts for ${file}
+ Conflicts have been detected while updating a conffile in the package ${pkg}.
+ .
+ File: ${file}
+ .
+ Reason: ${reason}
+ .
+ Select from the provided options which action should be taken to resolve
+ the conflicts.
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
new file mode 100644
index 0000000..b775c53
--- /dev/null
+++ b/debian/po/POTFILES.in
@@ -0,0 +1 @@
+[type: gettext/rfc822deb] dpkg.templates
diff --git a/debian/po/templates.pot b/debian/po/templates.pot
new file mode 100644
index 0000000..b45a96d
--- /dev/null
+++ b/debian/po/templates.pot
@@ -0,0 +1,59 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: dpkg@packages.debian.org\n"
+"POT-Creation-Date: 2008-03-15 11:59+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. Type: select
+#. Choices
+#: ../dpkg.templates:1001
+msgid ""
+"Install the package maintainer's version, Keep the currently installed "
+"version, Show the differences between the current and new versions, Invoke a "
+"shell to investigate"
+msgstr ""
+
+#. Type: select
+#. Description
+#: ../dpkg.templates:1002
+msgid "Select how dpkg should resolve the conflicts for ${file}"
+msgstr ""
+
+#. Type: select
+#. Description
+#: ../dpkg.templates:1002
+msgid ""
+"Conflicts have been detected while updating a conffile in the package ${pkg}."
+msgstr ""
+
+#. Type: select
+#. Description
+#: ../dpkg.templates:1002
+msgid "File: ${file}"
+msgstr ""
+
+#. Type: select
+#. Description
+#: ../dpkg.templates:1002
+msgid "Reason: ${reason}"
+msgstr ""
+
+#. Type: select
+#. Description
+#: ../dpkg.templates:1002
+msgid ""
+"Select from the provided options which action should be taken to resolve the "
+"conflicts."
+msgstr ""
diff --git a/debian/rules b/debian/rules
index 73855d8..edbbc3e 100755
--- a/debian/rules
+++ b/debian/rules
@@ -84,6 +84,7 @@ binary-arch: install
 
 	dh_installchangelogs -a ChangeLog
 	dh_installdocs -a
+	dh_installdebconf -a
 
 	install -d debian/dpkg/usr/share/lintian/overrides
 	install -m 644 debian/dpkg.lintian-overrides \
@@ -131,6 +132,7 @@ clean:
 
 	rm -rf build-tree
 	dh_clean
+	debconf-updatepo
 
 
 .PHONY: build install binary-arch binary-indep binary clean
diff --git a/lib/dpkg.h b/lib/dpkg.h
index 7e2e428..10fbb46 100644
--- a/lib/dpkg.h
+++ b/lib/dpkg.h
@@ -146,6 +146,8 @@
 #define FIND		"find"
 #define SHELL		"sh"
 #define DIFF		"diff"
+#define DEBCONF		"debconf"
+#define DEBCONF_PROMPTCONF	"/usr/share/dpkg/dpkg-confprompt.sh"
 
 #define SHELLENVIR	"SHELL"
 
diff --git a/scripts/dpkg-confprompt.sh b/scripts/dpkg-confprompt.sh
new file mode 100755
index 0000000..2e9b2d0
--- /dev/null
+++ b/scripts/dpkg-confprompt.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+# internal script for debconf-based management of conffiles
+# calling convention:
+#	dpkg-confprompt.sh <pkg> <cfgfile> "<reason>"
+# initial implementation by sean finney
+# copyright (c) 2008 sean finney <seanius@debian.org>
+# this file may be used in accordance with the dpkg software copyright
+
+
+# debug function 
+say(){
+	[ -z "$DPKG_DEBCONF_DEBUG" ] || echo "$@" >&2
+}
+
+# properly escape newlines and other junk from pre-translated strings
+# sent to us by the dpkg program
+escape(){
+	local foo nonempty
+	echo "$1" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/ *==> *//g' | \
+	while read foo; do
+		if [ -n "$foo" ]; then 
+			if [ -n "$nonempty" ]; then
+				printf "\\\\n"
+			else
+				nonempty="yup"
+			fi
+			printf "$foo"
+		fi
+	done
+	echo
+}
+
+# read the response from the debconf system, return the debconf status code
+# the (global) variable res stores the resulting string
+readres(){
+	local status
+	read status res
+	say "readres = status: $status res: $res"
+	return $status
+}
+
+pkg="$1"
+file="$2"
+reason="`escape \"$3 \"`"
+# XXX maybe we don't want this
+#defact="`escape \"$3 \"`"
+
+# force-register the templates, since dpkg will not do this itself due
+# to not using debconf in any of the maintainer scripts
+echo x_loadtemplatefile /var/lib/dpkg/info/dpkg.templates dpkg
+readres || exit 255
+
+# reset the value, since we share this template across multiple packages
+# and conffiles the stored value is worthless
+echo reset dpkg/promptconfaction
+readres || exit 255
+
+# perform some substitutions
+echo subst dpkg/promptconfaction pkg "$pkg"
+readres || exit 255
+echo subst dpkg/promptconfaction file "$file"
+readres || exit 255
+echo subst dpkg/promptconfaction reason "$reason"
+readres || exit 255
+# XXX maybe we don't want this
+#echo subst dpkg/promptconfaction defact "$defact"
+#readres || exit 255
+
+# ask the answer and get the response
+echo input high dpkg/promptconfaction
+readres || exit 255
+echo go
+readres || exit 255
+echo get dpkg/promptconfaction
+readres || exit 255
+
+# based on the answer, return the integer value of the equivalent character,
+# had dpkg handled the question itself from the cmdline.  any non-ascii value
+# (> 127) implies something has gone wrong internally.
+case $res in
+"Install the package maintainer's version")
+	code=121 # 'y'
+	;;
+"Keep the currently installed version")
+	code=110 # 'n'
+	;;
+"Show the differences between the current and new versions")
+	code=100 # 'd'
+	;;
+"Invoke a shell to investigate")
+	code=122 # 'z'
+	;;
+*)
+	say "wtf am i supposed to do with '$res'?"
+	code=255
+	;;
+esac
+exit $code
diff --git a/src/configure.c b/src/configure.c
index c6cba8e..8da09cf 100644
--- a/src/configure.c
+++ b/src/configure.c
@@ -54,6 +54,8 @@ static void suspend(void);
 static enum conffopt promptconfaction(const char* package, const char* cfgfile,
 		const char* realold, const char* realnew, int useredited, 
 		int distedited, enum conffopt what);
+static int debconf_promptconf(const char *pkg, const char *conffile, 
+                              const char *reason);
 
 void deferred_configure(struct pkginfo *pkg) {
 	/* The algorithm for deciding what to configure first is as follows:
@@ -571,7 +573,7 @@ static enum conffopt promptconfaction(const char* package, const char* cfgfile,
 					_("\n ==> Modified (by you or by a script) since installation.\n") :
 					_("\n ==> Deleted (by you or by a script) since installation.\n"));
 
-			fprintf(stderr, distedited ?
+			reason = ( distedited ?
 					_(" ==> Package distributor has shipped an updated version.\n") :
 					_("     Version in package is the same as at last installation.\n"));
 		}
@@ -603,38 +605,45 @@ static enum conffopt promptconfaction(const char* package, const char* cfgfile,
 				cc = 'y';
 				break;
 			}
-		}
-
-
-		fprintf(stderr,
+		} 
+
+		/* try to use debconf to resolve the problem */
+		cc = debconf_promptconf(package, cfgfile, reason);
+		/* if 127 or higher is returned then there was a problem executing
+		 * the script (or other internall error) and we fall back to the 
+		 * oldschool way */
+		if (cc >= 127) {
+			cc= 0;
+			fprintf(stderr,
 				_("   What would you like to do about it ?  Your options are:\n"
 					"    Y or I  : install the package maintainer's version\n"
 					"    N or O  : keep your currently-installed version\n"
 					"      D     : show the differences between the versions\n"
 					"      Z     : background this process to examine the situation\n"));
 
-		if (what & cfof_keep)
-			fprintf(stderr, _(" The default action is to keep your current version.\n"));
-		else if (what & cfof_install)
-			fprintf(stderr, _(" The default action is to install the new version.\n"));
+			if (what & cfof_keep)
+				fprintf(stderr, _(" The default action is to keep your current version.\n"));
+			else if (what & cfof_install)
+				fprintf(stderr, _(" The default action is to install the new version.\n"));
 
-		s= strrchr(cfgfile,'/');
-		if (!s || !*++s) s= cfgfile;
-		fprintf(stderr, "*** %s (Y/I/N/O/D/Z) %s ? ",
-				s,
-				(what & cfof_keep) ? _("[default=N]") :
-				(what & cfof_install) ? _("[default=Y]") : _("[no default]"));
+			s= strrchr(cfgfile,'/');
+			if (!s || !*++s) s= cfgfile;
+			fprintf(stderr, "*** %s (Y/I/N/O/D/Z) %s ? ",
+					s,
+					(what & cfof_keep) ? _("[default=N]") :
+					(what & cfof_install) ? _("[default=Y]") : _("[no default]"));
 
-		if (ferror(stderr))
-			ohshite(_("error writing to stderr, discovered before conffile prompt"));
+			if (ferror(stderr))
+				ohshite(_("error writing to stderr, discovered before conffile prompt"));
 
-		cc= 0;
-		while ((c= getchar()) != EOF && c != '\n')
-			if (!isspace(c) && !cc) cc= tolower(c);
+			while ((c= getchar()) != EOF && c != '\n')
+				if (!isspace(c) && !cc) cc= tolower(c);
 
-		if (c == EOF) {
-			if (ferror(stdin)) ohshite(_("read error on stdin at conffile prompt"));
-			ohshit(_("EOF on stdin at conffile prompt"));
+			if (c == EOF) {
+				if (ferror(stdin)) 
+					ohshite(_("read error on stdin at conffile prompt"));
+				ohshit(_("EOF on stdin at conffile prompt"));
+			}
 		}
 
 		if (!cc) {
@@ -673,3 +682,23 @@ static enum conffopt promptconfaction(const char* package, const char* cfgfile,
 
 	return what;
 }
+
+static int debconf_promptconf(const char *pkg, const char *cfgfile, 
+		const char *reason){
+	char *cmd=NULL;
+	size_t cmdsz=0;
+	int ret=0;
+
+	/* <debconf> <confpromptscript> <pkg> <cfgfile> "<reason>"\0 */
+	cmdsz = strlen(DEBCONF" "DEBCONF_PROMPTCONF" ")+strlen(pkg)+1
+	        + strlen(cfgfile) + 2 + strlen(reason) + 2;
+	cmd = m_malloc(cmdsz);
+	snprintf(cmd, cmdsz, "%s %s %s %s \"%s\"", DEBCONF, DEBCONF_PROMPTCONF,
+	         pkg, cfgfile, reason);
+	debug(dbg_conff, "debconf_promptconf %s %s = ", pkg, cfgfile);
+	ret=WEXITSTATUS(system(cmd));
+	if(ret <= 127) debug(dbg_conff, "%d ('%c')\n", ret, (char)ret);
+	else debug(dbg_conff, "%d\n", ret);
+	free(cmd);
+	return ret;
+}
-- 
1.5.4.3


Reply to: