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

[PATCH 3/3] dpkg --configure: optionally tolerate not-installed packages in argv



When renaming a package "pkg-old" to "pkg-new", it would be nice if
upgrading pkg-old automatically ensures that pkg-new is installed and
pkg-old removed.  In fact, Debian policy has provided a facility that
should allow this since early on: if the new version of pkg-old
depends on a version of pkg-new that replaces all its files, it will
"disappear", providing the desired seemless upgrade.

dpkg learned to support this with version 1.10.22, with the fix to
bug #202997 (previously, dpkg would try to configure the disappeared
package and error out because it was confused).

Unfortunately, this was not enough to fix the problem for upgrades
driven by APT.  APT will unpacks and configures packages in separate
dpkg runs.  But unlike dpkg, APT does not have the information to
quickly tell whether a package it plans to configure is not installed.
So it is doomed to always be confused.

Fix this by providing a new --ignore-not-installed option for APT
to use.  If this option is supplied with --configure, any packages
on the command line that are not installed will be ignored on the
assumption that they recently disappeared.

Based on suggestions by Daniel Kobras and Ian Jackson.

Reference: <17545.29786.400376.667991@davenant.relativity.greenend.org.uk>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
That’s the end of the series.  Thanks for reading.

Yours,
Jonathan

 configure.ac                      |    1 +
 debian/changelog                  |    7 ++
 man/dpkg.1                        |    7 ++
 src/Makefile.am                   |    2 +
 src/main.c                        |    4 +-
 src/main.h                        |    2 +-
 src/packages.c                    |    7 ++
 src/test/Makefile.am              |    5 +
 src/test/t-disappear-configure.sh |  208 +++++++++++++++++++++++++++++++++++++
 9 files changed, 241 insertions(+), 2 deletions(-)
 create mode 100644 src/test/Makefile.am
 create mode 100755 src/test/t-disappear-configure.sh

diff --git a/configure.ac b/configure.ac
index 8b7e213..1b45d6f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -148,6 +148,7 @@ AC_CONFIG_FILES([ Makefile
 		  scripts/Makefile
 		  scripts/po/Makefile.in
 		  src/Makefile
+		  src/test/Makefile
 		  utils/Makefile ])
 AC_CONFIG_HEADERS([config.h])
 AC_OUTPUT
diff --git a/debian/changelog b/debian/changelog
index adaa95f..c84a224 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -15,6 +15,13 @@ dpkg (1.15.6.2) UNRELEASED; urgency=low
   * Improve explanation of --all option in dpkg-parsechangelog(1). Thanks to
     Jari Aalto. Closes: #575706
 
+  [ Jonathan Nieder ]
+  * dpkg now supports an --ignore-not-installed option to silently tolerate
+    requests to configure a package that is not installed.
+    Frontends should use this option when configuring packages they just
+    unpacked to avoid confusion from disappearing transitional packages.
+    This is the logical continuation of #202997.
+
   [ Updated dpkg translations ]
   * German (Sven Joachim).
 
diff --git a/man/dpkg.1 b/man/dpkg.1
index 0f6b337..5530c31 100644
--- a/man/dpkg.1
+++ b/man/dpkg.1
@@ -449,6 +449,13 @@ Install a package even if it fails authenticity check.
 Ignore dependency-checking for specified packages (actually, checking is
 performed, but only warnings about conflicts are given, nothing else).
 .TP
+\fB\-\-ignore\-not\-installed
+Silently tolerate attempts to configure a package that is
+not\-installed.  Front-ends should use this option when unpacking
+and configuring packages in separate steps, since a package could
+\fIdisappear\fP before there is a chance to configure it if another
+package replaces all its files.
+.TP
 \fB\-\-new\fP, \fB\-\-old\fP
 Select new or old binary package format. This is a \fBdpkg\-deb\fP(1)
 option.
diff --git a/src/Makefile.am b/src/Makefile.am
index b3f2fe5..f680b44 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,5 +1,7 @@
 ## Process this file with automake to produce Makefile.in
 
+SUBDIRS = test
+
 localedir = $(datadir)/locale
 pkgconfdir = $(sysconfdir)/@PACKAGE@
 
diff --git a/src/main.c b/src/main.c
index 9f3a5b7..ccee956 100644
--- a/src/main.c
+++ b/src/main.c
@@ -135,6 +135,7 @@ usage(const struct cmdinfo *ci, const char *value)
 "  --log=<filename>           Log status changes and actions to <filename>.\n"
 "  --ignore-depends=<package>,...\n"
 "                             Ignore dependencies involving <package>.\n"
+"  --ignore-not-installed     Tolerate attempt to configure disappeared package.\n"
 "  --force-...                Override problems (see --force-help).\n"
 "  --no-force-...|--refuse-...\n"
 "                             Stop when problems encountered.\n"
@@ -169,7 +170,7 @@ const char printforhelp[]= N_(
 
 const struct cmdinfo *cipaction = NULL;
 int f_pending=0, f_recursive=0, f_alsoselect=1, f_skipsame=0, f_noact=0;
-int f_autodeconf=0, f_nodebsig=0;
+int f_autodeconf=0, f_nodebsig=0, f_ignoreuninst=0;
 int f_triggers = 0;
 unsigned long f_debug=0;
 /* Change fc_overwrite to 1 to enable force-overwrite by default */
@@ -513,6 +514,7 @@ static const struct cmdinfo cmdinfos[]= {
   { "admindir",          0,   1, NULL,          &admindir, NULL,          0 },
   { "instdir",           0,   1, NULL,          &instdir,  NULL,          0 },
   { "ignore-depends",    0,   1, NULL,          NULL,      ignoredepends, 0 },
+  { "ignore-not-installed", 0, 0, &f_ignoreuninst, NULL,    NULL,    1 },
   { "force",             0,   2, NULL,          NULL,      setforce,      1 },
   { "refuse",            0,   2, NULL,          NULL,      setforce,      0 },
   { "no-force",          0,   2, NULL,          NULL,      setforce,      0 },
diff --git a/src/main.h b/src/main.h
index 90cec0d..15c173f 100644
--- a/src/main.h
+++ b/src/main.h
@@ -121,7 +121,7 @@ extern const char *const statusstrings[];
 
 extern const struct cmdinfo *cipaction;
 extern int f_pending, f_recursive, f_alsoselect, f_skipsame, f_noact;
-extern int f_autodeconf, f_nodebsig;
+extern int f_autodeconf, f_nodebsig, f_ignoreuninst;
 extern int f_triggers;
 extern unsigned long f_debug;
 extern int fc_downgrade, fc_configureany, fc_hold, fc_removereinstreq, fc_overwrite;
diff --git a/src/packages.c b/src/packages.c
index f180434..1b2b8e9 100644
--- a/src/packages.c
+++ b/src/packages.c
@@ -234,6 +234,13 @@ void process_queue(void) {
         break;
       /* Fall through. */
     case act_configure:
+      /*
+       * When unpacking and configuring in separate dpkg runs,
+       * front-ends should use --ignore-not-installed to avoid
+       * attempts to configure pkgs that disappeared.
+       */
+      if (f_ignoreuninst && pkg->status == stat_notinstalled)
+        break;
       /* Do whatever is most needed. */
       if (pkg->trigpend_head)
         trigproc(pkg);
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
new file mode 100644
index 0000000..8a47d5a
--- /dev/null
+++ b/src/test/Makefile.am
@@ -0,0 +1,5 @@
+# Process this file with automake to produce Makefile.in
+
+TESTS_ENVIRONMENT = admindir=$(admindir)
+
+TESTS = t-disappear-configure.sh
diff --git a/src/test/t-disappear-configure.sh b/src/test/t-disappear-configure.sh
new file mode 100755
index 0000000..67cd554
--- /dev/null
+++ b/src/test/t-disappear-configure.sh
@@ -0,0 +1,208 @@
+#!/bin/sh
+set -e
+
+if test "$(id -u)" = 0
+then
+	echo >&2 "skipping test. Run as non-root if you're curious."
+	exit 77
+fi
+
+: ${dpkg=$(pwd)/../dpkg}
+: ${dpkg_deb=$(pwd)/../../dpkg-deb/dpkg-deb}
+: ${dpkg_gencontrol=$(pwd)/../../scripts/dpkg-gencontrol}
+: ${admindir=/var/lib/dpkg}
+trash=
+trap -- 'test -z "$trash" || rm -fr "$trash"' EXIT
+trash=$(mktemp --tmpdir -d dpkg-test.XXXXXXXXXX)
+
+cd "$trash"
+
+debiandir() {
+	local pkgname version extra_deps description
+	pkgname=$1
+	version=$2
+	extra_deps=$3
+	description=$4
+
+	cat <<-EOF >debian/changelog
+	$pkgname ($version) test; urgency=low
+
+	  * Test package.
+
+	 -- Jonathan Nieder <jrnieder@gmail.com>  Thu, 08 Apr 2010 04:11:25 -0500
+	EOF
+	cat <<-EOF >debian/copyright
+	This package is in the public domain.
+	You may freely use, modify, distribute, and relicense it.
+	EOF
+	cat <<-EOF >debian/control
+	Source: $pkgname
+	Section: admin
+	Priority: extra
+	Maintainer: Jonathan Nieder <jrnieder@gmail.com>
+
+	Package: $pkgname
+	Architecture: all
+	${extra_deps}Description: Test package
+	 $description
+	EOF
+}
+docdir() {
+	local pkg readme
+	pkg=$1
+	readme=$2
+	echo "$readme" > "staging/usr/share/doc/$pkg/README"
+	cp debian/copyright "staging/usr/share/doc/$pkg/"
+	cp debian/changelog "staging/usr/share/doc/$pkg/changelog.Debian"
+	gzip -9 "staging/usr/share/doc/$pkg/changelog.Debian"
+}
+
+# Create a simple package.
+mkdir -p debian
+mkdir -p staging/usr/share/doc/pkg-old
+mkdir -p staging/DEBIAN
+pdir=staging
+debiandir pkg-old 0.1 '' \
+'This is a test package with a really lousy name.
+ .
+ Some day someone should get around to changing it.'
+docdir pkg-old 'old readme'
+"$dpkg_gencontrol" -ppkg-old -Pstaging
+"$dpkg_deb" --build staging .
+test -e pkg-old_0.1_all.deb
+rm -fr debian
+rm -fr staging
+
+# Unrelated package.
+mkdir -p debian
+mkdir -p staging/usr/share/doc/unrelated
+mkdir -p staging/DEBIAN
+pdir=staging
+debiandir unrelated 1 '' \
+'This package ensure dpkg does not try to remove the root directory.'
+docdir unrelated 'content!'
+"$dpkg_gencontrol" -punrelated -Pstaging
+"$dpkg_deb" --build staging .
+test -e unrelated_1_all.deb
+rm -fr debian
+rm -fr staging
+
+# Renamed version.
+mkdir -p debian
+mkdir -p staging/usr/share/doc/pkg-new
+mkdir -p staging/DEBIAN
+debiandir pkg-new 0.2 'Conflicts: pkg-old (<< 0.2)
+Replaces: pkg-old
+Provides: pkg-old
+' \
+	'This test package has a very nice name.'
+docdir pkg-new 'new readme'
+ln -s pkg-new staging/usr/share/doc/pkg-old
+"$dpkg_gencontrol" -ppkg-new -Pstaging
+"$dpkg_deb" --build staging .
+test -e pkg-new_0.2_all.deb
+rm -fr debian
+rm -fr staging
+
+# Transitional package.
+mkdir -p debian
+mkdir -p staging/usr/share/doc/
+mkdir -p staging/DEBIAN
+debiandir pkg-old 0.2 'Depends: pkg-new
+' \
+	'This is a transitional dummy package.  You may safely remove it.'
+ln -s pkg-new staging/usr/share/doc/pkg-old
+"$dpkg_gencontrol" -ppkg-old -Pstaging
+"$dpkg_deb" --build staging .
+test -e pkg-old_0.2_all.deb
+rm -fr debian
+rm -fr staging
+
+# Write administration dir.
+mkdir -p ".$admindir/info"
+mkdir -p ".$admindir/triggers"
+mkdir -p ".$admindir/updates"
+> ".$admindir/status"
+> ".$admindir/available"
+
+dpkg() {
+	"$dpkg" --root="$trash" --force-not-root --force-bad-path "$@"
+}
+unknown() {
+	local pkg
+	pkg=$1
+	! LC_ALL=C dpkg --list "$pkg" 2>msg
+	grep 'No packages found' msg
+}
+known_notinstalled() {
+	local pkg
+	pkg=$1
+	dpkg --list "$pkg" >list
+	grep '^.n' list
+}
+unpacked_version() {
+	local pkg version
+	pkg=$1
+	version=$2
+	"$dpkg" --root="$trash" --force-not-root --force-bad-path \
+		--list "$pkg" >list
+	fgrep "$version" list | tee line
+	grep '^.U' line
+}
+installed_version() {
+	local pkg version
+	pkg=$1
+	version=$2
+	"$dpkg" --root="$trash" --force-not-root --force-bad-path \
+		--list "$pkg" >list
+	fgrep "$version" list | tee line
+	grep '^.i' line
+}
+
+dpkg --install unrelated_1_all.deb
+
+unknown pkg-old
+unknown pkg-new
+
+dpkg --install pkg-old_0.1_all.deb
+
+installed_version pkg-old 0.1
+unknown pkg-new
+
+# The amazing disappearing package trick!
+dpkg --install pkg-old_0.2_all.deb pkg-new_0.2_all.deb
+
+known_notinstalled pkg-old
+installed_version pkg-new 0.2
+
+dpkg --purge pkg-old pkg-new
+
+known_notinstalled pkg-old
+known_notinstalled pkg-new
+
+dpkg --install pkg-old_0.1_all.deb
+
+installed_version pkg-old 0.1
+known_notinstalled pkg-new
+
+dpkg --unpack pkg-old_0.2_all.deb pkg-new_0.2_all.deb
+
+known_notinstalled pkg-old
+unpacked_version pkg-new 0.2
+
+# The trick fails for upgrades driven by some naïve frontends.
+! dpkg --configure pkg-old pkg-new
+
+known_notinstalled pkg-old
+
+dpkg --purge pkg-old pkg-new
+dpkg --unpack pkg-old_0.2_all.deb pkg-new_0.2_all.deb
+
+known_notinstalled pkg-old
+unpacked_version pkg-new 0.2
+
+# With the --ignore-not-installed option, it succeeds.
+dpkg --ignore-not-installed --configure pkg-old pkg-new
+
+known_notinstalled pkg-old
+installed_version pkg-new 0.2
-- 
1.7.0.4


Reply to: