[PATCH] dpkg-m-h: improve test coverage, document use with non-conffiles, fix some minor issues

Richard Hansen <[email protected]>
Newsgroups gmane.linux.debian.devel.dpkg.general
Message-ID <[email protected]>
Greetings dpkg devs,

I was trying to understand how dpkg-maintscript-helper's rm_conffile and 
mv_conffile commands handle (or not) config files that are not conffiles 
-- either shipped but not in the Conffiles status field, or not shipped 
and generated during postinst configure (e.g., ucf).  The documentation 
is not clear, so I wrote some tests and documentation improvements.

Attached is a series of patches (apply them by running `git am *.patch`) 
that do the following:

   * Add extensive tests for rm_conffile and mv_conffile.
   * Document how rm_conffile and mv_conffile handle (or not) files not 
listed in the package's conffiles.
   * Fix some (minor) issues uncovered by the tests.

I can use 'git send-email' to send a separate email per commit if that 
would be preferred; I didn't want to spam the mailing list without 
knowing your preferences first.

Feedback would be appreciated.

Thanks,
Richard
0001-tests-Move-.gitignore-out-of-dpkginst.patch (text/x-patch, 1.9 KB)
From 104ca2345078fb390941f2dace021b2f92c8f4cd Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Wed, 12 Feb 2025 17:16:02 -0500
Subject: [PATCH 01/10] tests: Move .gitignore out of dpkginst

Now the initial state of dpkginst is empty which makes it easier for
tests to ensure that everything was cleaned up after purge.  For
example:

    # Ensure that purge removed everything.
    test -z "$$(ls -A '$(DPKG_INSTDIR)')"
---
 tests/.gitignore          |  2 ++
 tests/Test.mk             | 13 ++++++++++++-
 tests/dpkginst/.gitignore |  1 -
 3 files changed, 14 insertions(+), 2 deletions(-)
 delete mode 100644 tests/dpkginst/.gitignore

diff --git a/tests/.gitignore b/tests/.gitignore
index 7498a3d16..3d3853d94 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,3 +1,5 @@
 !Makefile
 *.deb
 /.pkg-tests.conf
+/dpkginst/
+dpkginst.stamp
diff --git a/tests/Test.mk b/tests/Test.mk
index a830d5d49..a13a508b8 100644
--- a/tests/Test.mk
+++ b/tests/Test.mk
@@ -110,14 +110,25 @@ pkg_is_not_installed = $(call stdout_has,$(PKG_STATUS) $(1) 2>/dev/null, ok not-
 pkg_status_is = $(call stdout_is,$(PKG_STATUS) $(1),$(2))
 pkg_field_is = $(call stdout_is,$(DPKG_QUERY) -f '$${$(2)}' -W $(1),$(3))
 
-%.deb: %
+# Avoid using $(DPKG_INSTDIR) as a dependency because it would be immediately
+# expanded, making it impossible for individual tests to override DPKG_INSTDIR.
+# (Variable expansion in a recipe is deferred.)
+dpkginst.stamp: | dpkginst
+	touch '$@'
+.PHONY: dpkginst
+dpkginst:
+	mkdir -p '$(DPKG_INSTDIR)'
+
+%.deb: % dpkginst.stamp
 	$(DPKG_BUILD_DEB) $< $@
 
 TEST_CASES :=
 
 build: build-hook $(DEB)
+build-hook: dpkginst.stamp
 
 test: build test-case test-clean
+test-case: dpkginst.stamp
 
 clean: clean-hook
 	$(RM) $(DEB)
diff --git a/tests/dpkginst/.gitignore b/tests/dpkginst/.gitignore
deleted file mode 100644
index 72e8ffc0d..000000000
--- a/tests/dpkginst/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
-- 
2.48.1
0002-tests-dpkg-m-h-New-rm_conffile-tests-to-improve-cove.patch (text/x-patch, 14.3 KB)
From 7108bde0838f3393f347fa502a8e05454cfe1a15 Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Thu, 13 Feb 2025 02:46:44 -0500
Subject: [PATCH 02/10] tests: dpkg-m-h: New rm_conffile tests to improve
 coverage

This commit adds exhaustive (I hope) test cases for rm_conffile.

Current state of rm_conffile:

Removing a conffile: dpkg-maintscript-helper works as expected.

Removing a shipped-but-non-conffile: dpkg-maintscript-helper works as
expected, except:

  * When upgrading, dpkg-m-h treats all non-conffiles as if they were
    modified, so it creates unnecessary .dpkg-bak files even for
    unmodified files.  (These unnecessary .dpkg-bak files are not
    created when the user does remove-then-install instead of upgrade
    because dpkg deletes the non-conffile files before the new
    version's postinst runs, not after.)  A TODO comment was added.

  * When upgrading, dpkg-m-h backs up modified files.  I consider this
    behavior to be a feature, not a bug, but it differs from the
    behavior of mv_conffile (which does not preserve modifications to
    non-conffiles) and from the remove-then-install case (where it is
    impossible to make a backup because dpkg deletes the modified
    non-conffile before preinst has the opportunity to make a backup).

Removing a non-shipped file: dpkg-maintscript-helper silently ignores
any attempt to remove a file not listed in `dpkg-query -L`, as
expected.  As a consequence, dpkg-m-h cannot be used to remove a
config file created during postinst by ucf or via debconf answers.
---
 tests/Makefile                                |   1 +
 tests/t-maintscript-rm/Makefile               | 226 ++++++++++++++++++
 .../pkg-rm-0/DEBIAN/conffiles                 |   1 +
 .../t-maintscript-rm/pkg-rm-0/DEBIAN/control  |   7 +
 .../t-maintscript-rm/pkg-rm-0/DEBIAN/postinst |   8 +
 tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postrm |   7 +
 tests/t-maintscript-rm/pkg-rm-0/conf          |   1 +
 tests/t-maintscript-rm/pkg-rm-0/nonconf       |   1 +
 .../t-maintscript-rm/pkg-rm-1/DEBIAN/control  |   7 +
 .../t-maintscript-rm/pkg-rm-1/DEBIAN/postinst |   9 +
 tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postrm |   5 +
 .../t-maintscript-rm/pkg-rm-1/DEBIAN/preinst  |   8 +
 12 files changed, 281 insertions(+)
 create mode 100644 tests/t-maintscript-rm/Makefile
 create mode 100644 tests/t-maintscript-rm/pkg-rm-0/DEBIAN/conffiles
 create mode 100644 tests/t-maintscript-rm/pkg-rm-0/DEBIAN/control
 create mode 100755 tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postinst
 create mode 100755 tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postrm
 create mode 100644 tests/t-maintscript-rm/pkg-rm-0/conf
 create mode 100644 tests/t-maintscript-rm/pkg-rm-0/nonconf
 create mode 100644 tests/t-maintscript-rm/pkg-rm-1/DEBIAN/control
 create mode 100755 tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postinst
 create mode 100755 tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postrm
 create mode 100755 tests/t-maintscript-rm/pkg-rm-1/DEBIAN/preinst

diff --git a/tests/Makefile b/tests/Makefile
index bdd9dd318..ff66e108f 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -41,6 +41,7 @@ ifdef DPKG_AS_ROOT
 TESTS_PASS += t-unpack-device
 endif
 TESTS_PASS += t-maintscript-leak
+TESTS_PASS += t-maintscript-rm
 TESTS_PASS += t-filtering
 TESTS_PASS += t-depends
 TESTS_PASS += t-dir-leftover-parents
diff --git a/tests/t-maintscript-rm/Makefile b/tests/t-maintscript-rm/Makefile
new file mode 100644
index 000000000..d1d342fc1
--- /dev/null
+++ b/tests/t-maintscript-rm/Makefile
@@ -0,0 +1,226 @@
+TESTS_DEB := pkg-rm-0 pkg-rm-1
+
+include ../Test.mk
+
+# Explanation of the cases:
+#   - conf: pkg-rm-0 ships this and lists it in its conffiles
+#   - nonconf: pkg-rm-0 ships this but does NOT list it in its conffiles
+#   - unowned: pkg-rm-0 does NOT ship this file; it is generated in postinst
+cases = conf nonconf unowned
+
+define check-clean =
+out=$$(ls -A '$(DPKG_INSTDIR)') || exit 1 && test -z "$$out" || { \
+  printf %s\\n "install directory "'$(DPKG_INSTDIR)'" not clean:" "$$out" >&2; \
+  exit 1; \
+}
+endef
+
+define prepare-unmodified =
+$(check-clean)
+$(DPKG_INSTALL) pkg-rm-0.deb
+$(foreach case,$(cases),$\
+test -f '$(DPKG_INSTDIR)/$(case)'
+test "$$(cat '$(DPKG_INSTDIR)/$(case)')" = "original"
+)
+endef
+
+define prepare-modified =
+$(prepare-unmodified)
+$(foreach case,$(cases),$\
+$(BEROOT) sh -c "echo modified >'"'$(DPKG_INSTDIR)/$(case)'"'"
+test -f '$(DPKG_INSTDIR)/$(case)'
+test "$$(cat '$(DPKG_INSTDIR)/$(case)')" = "modified"
+)
+endef
+
+define prepare-abort =
+$(BEROOT) touch '$(DPKG_INSTDIR)'/fail-preinst
+$(DPKG_INSTALL) pkg-rm-1.deb || true
+$(BEROOT) rm -f '$(DPKG_INSTDIR)'/fail-preinst
+endef
+
+define clean-up =
+# Cleaning up leftover /$(1) which should have contents $(2)
+test -f '$(DPKG_INSTDIR)/$(1)'
+test "$$(cat '$(DPKG_INSTDIR)/$(1)')" = '$(2)'
+$(BEROOT) rm -f '$(DPKG_INSTDIR)/$(1)'
+endef
+
+define check-nexists =
+test ! -f '$(DPKG_INSTDIR)/$(1)'
+endef
+
+define check-exists =
+test -f '$(DPKG_INSTDIR)/$(1)'
+test "$$(cat '$(DPKG_INSTDIR)/$(1)')" = '$(2)'
+endef
+
+define check-config-file =
+# $(1)($(2))
+$(if $(3),$\
+  $(call check-exists,$(2),$(3)),$\
+  $(call check-nexists,$(2)))
+$(call check-nexists,$(2).dpkg-backup)
+$(if $(4),$\
+  $(call check-exists,$(2).dpkg-bak,$(4)),$\
+  $(call check-nexists,$(2).dpkg-bak))
+$(call check-nexists,$(2).dpkg-remove)
+
+endef
+
+# Asserts that $(1) and backup files do not exist.
+check-removed-clean = \
+  $(call check-config-file,check-removed-clean,$(1),,)
+
+# Asserts that case $(1) with "modified" contents was removed, with
+# $(1).dpkg-bak containing $(2).
+check-removed-with-backup = \
+  $(call check-config-file,check-removed-with-backup,$(1),,$(2))
+
+# Asserts that the attempt to remove $(1) was ignored; it still exists with
+# contents $(2).  No backup files exist.
+check-noop = \
+  $(call check-config-file,check-noop,$(1),$(2),)
+
+TEST_CASES += test-install
+test-install:
+	$(check-clean)
+	$(DPKG_INSTALL) pkg-rm-1.deb
+	$(call check-removed-clean,conf)
+	$(call check-removed-clean,nonconf)
+	$(call check-removed-clean,unowned)
+	$(DPKG_PURGE) pkg-rm
+	$(check-clean)
+
+TEST_CASES += test-unmodified-upgrade
+test-unmodified-upgrade:
+	$(prepare-unmodified)
+	$(DPKG_INSTALL) pkg-rm-1.deb
+	$(call check-removed-clean,conf)
+	# TODO: rm_conffile mistakenly treats unmodified files that are shipped
+	# but not in conffiles as if they were modified.
+	$(call check-removed-with-backup,nonconf,original)
+	# Attempts to remove unowned files are ignored, and pkg-rm-1
+	# intentionally does not clean up any unowned files installed by
+	# pkg-rm-0.
+	$(call check-noop,unowned,original)
+	$(DPKG_PURGE) pkg-rm
+	$(call clean-up,unowned,original)
+	$(check-clean)
+
+TEST_CASES += test-modified-upgrade
+test-modified-upgrade:
+	$(prepare-modified)
+	$(DPKG_INSTALL) pkg-rm-1.deb
+	$(call check-removed-with-backup,conf,modified)
+	$(call check-removed-with-backup,nonconf,modified)
+	# Attempts to remove unowned files are ignored, and pkg-rm-1
+	# intentionally does not clean up any unowned files installed by
+	# pkg-rm-0.
+	$(call check-noop,unowned,modified)
+	$(DPKG_PURGE) pkg-rm
+	$(call clean-up,unowned,modified)
+	$(check-clean)
+
+TEST_CASES += test-unmodified-remove-install
+test-unmodified-remove-install:
+	$(prepare-unmodified)
+	$(DPKG_REMOVE) pkg-rm
+	$(DPKG_INSTALL) pkg-rm-1.deb
+	$(call check-removed-clean,conf)
+	$(call check-removed-clean,nonconf)
+	# Attempts to remove unowned files are ignored, and pkg-rm-1
+	# intentionally does not clean up any unowned files installed by
+	# pkg-rm-0.
+	$(call check-noop,unowned,original)
+	$(DPKG_PURGE) pkg-rm
+	$(call clean-up,unowned,original)
+	$(check-clean)
+
+TEST_CASES += test-modified-remove-install
+test-modified-remove-install:
+	$(prepare-modified)
+	$(DPKG_REMOVE) pkg-rm
+	$(DPKG_INSTALL) pkg-rm-1.deb
+	$(call check-removed-with-backup,conf,modified)
+	# When an old version of a package is removed before the new version is
+	# installed (as opposed to upgrading from the old to the new), it is
+	# impossible for dpkg-m-h to back up the modified contents of a file
+	# that was shipped but not listed in conffiles.  This is because dpkg
+	# unconditionally deletes each non-conffile when removing a package, and
+	# does so without making a backup even if the file is modified.  This
+	# removal happens before the new version's preinst runs; contrast this
+	# with package upgrades where the new version's preinst runs before
+	# the old version's files are deleted.
+	$(call check-removed-clean,nonconf)
+	# Attempts to remove unowned files are ignored, and pkg-rm-1
+	# intentionally does not clean up any unowned files installed by
+	# pkg-rm-0.
+	$(call check-noop,unowned,modified)
+	$(DPKG_PURGE) pkg-rm
+	$(call clean-up,unowned,modified)
+	$(check-clean)
+
+TEST_CASES += test-unmodified-abort
+test-unmodified-abort:
+	$(prepare-unmodified)
+	$(prepare-abort)
+	$(call check-noop,conf,original)
+	$(call check-noop,nonconf,original)
+	$(call check-noop,unowned,original)
+	$(DPKG_PURGE) pkg-rm
+	$(check-clean)
+
+TEST_CASES += test-modified-abort
+test-modified-abort:
+	$(prepare-modified)
+	$(prepare-abort)
+	$(call check-noop,conf,modified)
+	$(call check-noop,nonconf,modified)
+	$(call check-noop,unowned,modified)
+	$(DPKG_PURGE) pkg-rm
+	$(check-clean)
+
+TEST_CASES += test-unmodified-unpack-purge
+test-unmodified-unpack-purge:
+	$(prepare-unmodified)
+	$(DPKG_UNPACK) pkg-rm-1.deb
+# We don't really care about the state of the files between unpacking and
+# purging:
+#   - There's no defined semantics for the 'unpacked' state.
+#   - A package is not expected to be in the 'unpacked' state for long on a real
+#     system, so it's better to test the possible final states.
+#   - The state of the files depends on dpkg implementation choices, which
+#     should be tested elsewhere.
+	$(DPKG_PURGE) pkg-rm
+	# Attempts to remove unowned files are ignored, and pkg-rm-1
+	# intentionally does not clean up any unowned files installed by
+	# pkg-rm-0.
+	$(call clean-up,unowned,original)
+	$(check-clean)
+
+TEST_CASES += test-modified-unpack-purge
+test-modified-unpack-purge:
+	$(prepare-modified)
+	$(DPKG_UNPACK) pkg-rm-1.deb
+# We don't really care about the state of the files between unpacking and
+# purging:
+#   - There's no defined semantics for the 'unpacked' state.
+#   - A package is not expected to be in the 'unpacked' state for long on a real
+#     system, so it's better to test the possible final states.
+#   - The state of the files depends on dpkg implementation choices, which
+#     should be tested elsewhere.
+	$(DPKG_PURGE) pkg-rm
+	# Attempts to remove unowned files are ignored, and pkg-rm-1
+	# intentionally does not clean up any unowned files installed by
+	# pkg-rm-0.
+	$(call clean-up,unowned,modified)
+	$(check-clean)
+
+.PHONY: $(TEST_CASES)
+
+test-case: $(TEST_CASES)
+
+test-clean:
+	$(DPKG_PURGE) pkg-rm
+	$(check-clean)
diff --git a/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/conffiles b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/conffiles
new file mode 100644
index 000000000..c9a5c234a
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/conffiles
@@ -0,0 +1 @@
+/conf
diff --git a/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/control b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/control
new file mode 100644
index 000000000..8f2f0d3c2
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: pkg-rm
+Version: 0
+Section: test
+Priority: extra
+Maintainer: Dpkg Developers <[email protected]>
+Architecture: all
+Description: test package - rm_conffile
diff --git a/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postinst b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postinst
new file mode 100755
index 000000000..0669af0ba
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postinst
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+case $1 in
+  configure)
+    # Simulate creation of the config file via ucf or debconf.
+    echo original >$DPKG_ROOT/unowned
+    ;;
+esac
diff --git a/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postrm b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postrm
new file mode 100755
index 000000000..47c82c688
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-0/DEBIAN/postrm
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -e
+case $1 in
+  purge)
+    rm -f "$DPKG_ROOT"/unowned
+    ;;
+esac
diff --git a/tests/t-maintscript-rm/pkg-rm-0/conf b/tests/t-maintscript-rm/pkg-rm-0/conf
new file mode 100644
index 000000000..4b48deed3
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-0/conf
@@ -0,0 +1 @@
+original
diff --git a/tests/t-maintscript-rm/pkg-rm-0/nonconf b/tests/t-maintscript-rm/pkg-rm-0/nonconf
new file mode 100644
index 000000000..4b48deed3
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-0/nonconf
@@ -0,0 +1 @@
+original
diff --git a/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/control b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/control
new file mode 100644
index 000000000..dae93a783
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/control
@@ -0,0 +1,7 @@
+Package: pkg-rm
+Version: 1
+Section: test
+Priority: extra
+Maintainer: Dpkg Developers <[email protected]>
+Architecture: all
+Description: test package - rm_conffile
diff --git a/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postinst b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postinst
new file mode 100755
index 000000000..84501c341
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postinst
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+# dpkg-maintscript-helper's rm_conffile command doesn't support unowned files,
+# so the postinst script would normally delete the unowned file itself and not
+# attempt to use rm_conffile.  rm_conffile is intentionally misused here to test
+# its behavior when given an unowned file.
+for tc in conf nonconf unowned; do
+  dpkg-maintscript-helper rm_conffile "/$tc" 0 -- "$@"
+done
diff --git a/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postrm b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postrm
new file mode 100755
index 000000000..344ea141e
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/postrm
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -e
+for tc in conf nonconf unowned; do
+  dpkg-maintscript-helper rm_conffile "/$tc" 0 -- "$@"
+done
diff --git a/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/preinst b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/preinst
new file mode 100755
index 000000000..c3ea10bd8
--- /dev/null
+++ b/tests/t-maintscript-rm/pkg-rm-1/DEBIAN/preinst
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+for tc in conf nonconf unowned; do
+  dpkg-maintscript-helper rm_conffile "/$tc" 0 -- "$@"
+done
+if [ -e "$DPKG_ROOT/fail-preinst" ]; then
+  exit 1
+fi
-- 
2.48.1
0003-tests-dpkg-m-h-New-mv_conffile-tests-to-improve-cove.patch (text/x-patch, 21.7 KB)
From 430ab82615903c336aab972eaed5cf9b0cbc4c3d Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Thu, 30 Jan 2025 20:53:16 -0500
Subject: [PATCH 03/10] tests: dpkg-m-h: New mv_conffile tests to improve
 coverage

This commit adds exhaustive (I hope) test cases for mv_conffile.

Current state of mv_conffile:

Moving a conffile: dpkg-maintscript-helper works as expected
regardless of whether the destination is another conffile path, a
shipped-but-non-conffile path, or a non-shipped path (e.g., normally
created during postinst by ucf or via debconf answers).  However,
there are a couple of minor purge bugs, marked with TODO comments:

  * If (a) the src conffile is unmodified and (b) the new package is
    unpacked but never configured (or configuration failed before
    mv_conffile was invoked) and (c) the package is purged, then
    dpkg-maintscript-helper will not delete the .dpkg-remove file it
    created during preinst.

  * If (a) the src conffile is modified and (b) the dst path is a
    non-shipped path and (c) the dst path exists before mv_conffile is
    invoked in postinst and (d) the package is purged, then the
    .dpkg-new file created by dpkg-m-h during postinst is not deleted.
    (This is not a problem for the conffile and
    shipped-but-non-conffile cases thanks to reliance on
    undocumented(?) dpkg behavior.)

Moving a shipped-but-non-conffile: dpkg-maintscript-helper does not
preserve the contents of a modified src file if it was shipped in the
previously installed version but was not listed as a conffile.  This
could be problematic if the previously installed version erroneously
omitted the file from conffiles.  If the user performs
remove-then-install instead of upgrade, it is impossible for dpkg-m-h
to preserve modifications because dpkg deletes the modified
non-conffile before the new version's preinst runs.  However, for
upgrades it is technically possible to preserve modifications.  Doing
so would align with the current behavior of rm_conffile (which creates
a backup of a modified non-conffile) but would not be consistent with
the remove-then-install behavior.

Moving a non-shipped file: dpkg-maintscript-helper silently ignores
any attempt to move a file not listed in `dpkg-query -L`, as expected.
As a consequence, dpkg-m-h cannot be used to rename a config file
created during postinst by ucf or via debconf answers.
---
 tests/Makefile                       |   1 +
 tests/t-maintscript-mv/.gitignore    |   4 +
 tests/t-maintscript-mv/Makefile      | 391 +++++++++++++++++++++++++++
 tests/t-maintscript-mv/control.in    |   7 +
 tests/t-maintscript-mv/postinst-0.in |  10 +
 tests/t-maintscript-mv/postinst-1.in |  17 ++
 tests/t-maintscript-mv/postrm-0.in   |   9 +
 tests/t-maintscript-mv/postrm-1.in   |   9 +
 tests/t-maintscript-mv/preinst-1.in  |   8 +
 9 files changed, 456 insertions(+)
 create mode 100644 tests/t-maintscript-mv/.gitignore
 create mode 100644 tests/t-maintscript-mv/Makefile
 create mode 100644 tests/t-maintscript-mv/control.in
 create mode 100755 tests/t-maintscript-mv/postinst-0.in
 create mode 100755 tests/t-maintscript-mv/postinst-1.in
 create mode 100755 tests/t-maintscript-mv/postrm-0.in
 create mode 100755 tests/t-maintscript-mv/postrm-1.in
 create mode 100755 tests/t-maintscript-mv/preinst-1.in

diff --git a/tests/Makefile b/tests/Makefile
index ff66e108f..8414dec21 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -41,6 +41,7 @@ ifdef DPKG_AS_ROOT
 TESTS_PASS += t-unpack-device
 endif
 TESTS_PASS += t-maintscript-leak
+TESTS_PASS += t-maintscript-mv
 TESTS_PASS += t-maintscript-rm
 TESTS_PASS += t-filtering
 TESTS_PASS += t-depends
diff --git a/tests/t-maintscript-mv/.gitignore b/tests/t-maintscript-mv/.gitignore
new file mode 100644
index 000000000..baca1433c
--- /dev/null
+++ b/tests/t-maintscript-mv/.gitignore
@@ -0,0 +1,4 @@
+/control-0.in
+/control-1.in
+/pkg-mv-0/
+/pkg-mv-1/
diff --git a/tests/t-maintscript-mv/Makefile b/tests/t-maintscript-mv/Makefile
new file mode 100644
index 000000000..79a11412b
--- /dev/null
+++ b/tests/t-maintscript-mv/Makefile
@@ -0,0 +1,391 @@
+TESTS_DEB := pkg-mv-0 pkg-mv-1
+
+include ../Test.mk
+
+define newline =
+
+
+endef
+
+# Source file (installed by pkg-mv-0.deb) to destination file (installed by
+# pkg-mv-1.deb) combinations:
+#   - conf-to-*: src file is shipped and listed in pkg-mv-0's conffiles
+#   - nonconf-to-*: src file is shipped in pkg-mv-0 but is not in conffiles
+#   - unowned-to-*: src file is created in pkg-mv-0's postinst, not shipped
+#   - *-to-conf: dst file is shipped and listed in pkg-mv-1's conffiles
+#   - *-to-nonconf: dst file is shipped in pkg-mv-1 but is not in conffiles
+#   - *-to-unowned: dst file is created in pkg-mv-1's postinst, not shipped
+cases = \
+  $(foreach src,conf nonconf unowned,$\
+    $(foreach dst,conf nonconf unowned,$\
+      $(src)-to-$(dst)))
+
+$(foreach case,$(cases),$\
+  $(if $(filter conf-to-%,$(case)),$(eval conffiles-0 += $(case)-src))$\
+  $(if $(filter nonconf-to-%,$(case)),$(eval nonconffiles-0 += $(case)-src))$\
+  $(if $(filter unowned-to-%,$(case)),$(eval unowned-0 += $(case)-src))$\
+  $(if $(filter %-to-conf,$(case)),$(eval conffiles-1 += $(case)-dst))$\
+  $(if $(filter %-to-nonconf,$(case)),$(eval nonconffiles-1 += $(case)-dst))$\
+  $(if $(filter %-to-unowned,$(case)),$(eval unowned-1 += $(case)-dst)))
+
+controlfiles-0 = conffiles control postinst postrm
+controlfiles-1 = conffiles control preinst postinst postrm
+
+# $(1) is the package version number (either 0 or 1)
+define pkg-mv-template =
+controlfiles-src-$(1) = $$(controlfiles-$(1):%=pkg-mv-$(1)/DEBIAN/%)
+shipped-$(1) = $$(conffiles-$(1)) $$(nonconffiles-$(1))
+shipped-src-$(1) = $$(shipped-$(1):%=pkg-mv-$(1)/%)
+
+$$(shipped-src-$(1)): | pkg-mv-$(1)
+
+pkg-mv-$(1).deb: \
+  pkg-mv-$(1)/DEBIAN/conffiles \
+  $$(shipped-src-$(1)) \
+  $$(controlfiles-src-$(1))
+
+pkg-mv-$(1)/DEBIAN/%: %-$(1).in Makefile | pkg-mv-$(1)/DEBIAN
+	rm -f '$$@'.tmp
+	sed \
+	  -e 's|%cases%|$$(cases)|g' \
+	  -e 's|%unowned%|$$(unowned-$(v))|g' \
+	  -e 's|%version%|$(v)|g' \
+	  '$$<' >'$$@'.tmp
+	case '$$<' in control-*|conffiles-*);; *) chmod +x '$$@'.tmp;; esac
+	mv '$$@'.tmp '$$@'
+
+%-$(1).in: %.in
+	cp '$$<' '$$@'
+
+pkg-mv-$(1)/DEBIAN/conffiles: Makefile | pkg-mv-$(1)/DEBIAN
+	rm -f '$$@'.tmp
+	$$(foreach cf,$$(conffiles-$(1)),$\
+	  printf %s\\n '/$$(cf)' >>'$$@'.tmp$$(newline))
+	mv '$$@'.tmp '$$@'
+
+pkg-mv-$(1)/DEBIAN: | pkg-mv-$(1)
+
+pkg-mv-$(1)/DEBIAN pkg-mv-$(1):
+	mkdir -p '$$@'
+endef
+
+$(foreach v,0 1,$(eval $(call pkg-mv-template,$(v))))
+
+$(shipped-src-0):
+	echo old >'$@'
+
+$(shipped-src-1):
+	echo new >'$@'
+
+define check-clean =
+out=$$(ls -A '$(DPKG_INSTDIR)') || exit 1 && test -z "$$out" || { \
+  printf %s\\n "install directory "'$(DPKG_INSTDIR)'" not clean:" "$$out" >&2; \
+  exit 1; \
+}
+endef
+
+define prepare-unmodified =
+$(check-clean)
+$(DPKG_INSTALL) pkg-mv-0.deb
+$(foreach case,$(cases),$\
+test -f '$(DPKG_INSTDIR)/$(case)'-src
+test "$$(cat '$(DPKG_INSTDIR)/$(case)'-src)" = "old"
+)
+endef
+
+define prepare-modified =
+$(prepare-unmodified)
+$(foreach case,$(cases),$\
+$(BEROOT) sh -c "echo modified >'$(DPKG_INSTDIR)/$(case)'-src"
+test -f '$(DPKG_INSTDIR)/$(case)'-src
+test "$$(cat '$(DPKG_INSTDIR)/$(case)'-src)" = "modified"
+)
+endef
+
+define prepare-abort =
+$(BEROOT) touch '$(DPKG_INSTDIR)'/fail-preinst
+$(DPKG_INSTALL) pkg-mv-1.deb || true
+$(BEROOT) rm -f '$(DPKG_INSTDIR)'/fail-preinst
+endef
+
+define clean-up =
+# Cleaning up leftover /$(1) which should have contents $(2)
+test -f '$(DPKG_INSTDIR)/$(1)'
+test "$$(cat '$(DPKG_INSTDIR)/$(1)')" = '$(2)'
+$(BEROOT) rm -f '$(DPKG_INSTDIR)/$(1)'
+endef
+
+define check-nexists =
+test ! -f '$(DPKG_INSTDIR)/$(1)'
+endef
+
+define check-exists =
+test -f '$(DPKG_INSTDIR)/$(1)'
+test "$$(cat '$(DPKG_INSTDIR)/$(1)')" = '$(2)'
+endef
+
+define check-config-files =
+# $(1)($(2))
+$(if $(3),$\
+  $(call check-exists,$(2)-src,$(3)),$\
+  $(call check-nexists,$(2)-src))
+$(call check-nexists,$(2)-src.dpkg-remove)
+$(if $(4),$\
+  $(call check-exists,$(2)-dst,$(4)),$\
+  $(call check-nexists,$(2)-dst))
+$(if $(5),$\
+  $(call check-exists,$(2)-dst.dpkg-new,$(5)),$\
+  $(call check-nexists,$(2)-dst.dpkg-new))
+
+endef
+
+# Asserts that case $(1) was cleanly installed/upgraded with "new" contents.
+check-clean-installed = \
+  $(call check-config-files,check-clean-installed,$(1),,new,)
+
+# Asserts that the attempt to upgrade case $(1) was ignored, dst has "new"
+# contents, and src still exists with contents $(2).
+check-upgrade-ignored = \
+  $(call check-config-files,check-upgrade-ignored,$(1),$(2),new,)
+
+# Asserts that case $(1) with "modified" contents was cleanly upgraded.
+check-upgraded-modified = \
+  $(call check-config-files,check-upgraded-modified,$(1),,modified,new)
+
+# Asserts that no changes were made for case $(1); src still contains $(2).
+check-noop = $(call check-config-files,check-noop,$(1),$(2),,)
+
+TEST_CASES += test-install
+test-install:
+	$(check-clean)
+	$(DPKG_INSTALL) pkg-mv-1.deb
+	$(foreach case,$(cases),$(call check-clean-installed,$(case)))
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).
+	$(call clean-up,conf-to-unowned-dst,new)
+	$(call clean-up,nonconf-to-unowned-dst,new)
+	$(call clean-up,unowned-to-unowned-dst,new)
+	$(check-clean)
+
+TEST_CASES += test-unmodified-upgrade
+test-unmodified-upgrade:
+	$(prepare-unmodified)
+	$(DPKG_INSTALL) pkg-mv-1.deb
+	$(call check-clean-installed,conf-to-conf)
+	$(call check-clean-installed,conf-to-nonconf)
+	$(call check-clean-installed,conf-to-unowned)
+	$(call check-clean-installed,nonconf-to-conf)
+	$(call check-clean-installed,nonconf-to-nonconf)
+	$(call check-clean-installed,nonconf-to-unowned)
+	# Attempts to move unowned files are ignored, and pkg-mv-1 intentionally
+	# does not clean up any unowned files installed by pkg-mv-0.
+	$(call check-upgrade-ignored,unowned-to-conf,old)
+	$(call check-upgrade-ignored,unowned-to-nonconf,old)
+	$(call check-upgrade-ignored,unowned-to-unowned,old)
+
+	######## Test cleanup.
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).
+	$(call clean-up,unowned-to-conf-src,old)
+	$(call clean-up,unowned-to-nonconf-src,old)
+	$(call clean-up,unowned-to-unowned-src,old)
+	$(call clean-up,conf-to-unowned-dst,new)
+	$(call clean-up,nonconf-to-unowned-dst,new)
+	$(call clean-up,unowned-to-unowned-dst,new)
+	$(check-clean)
+
+TEST_CASES += test-modified-upgrade
+test-modified-upgrade:
+	$(prepare-modified)
+	$(DPKG_INSTALL) pkg-mv-1.deb
+	$(call check-upgraded-modified,conf-to-conf)
+	$(call check-upgraded-modified,conf-to-nonconf)
+	$(call check-upgraded-modified,conf-to-unowned)
+	# TODO: dpkg-m-h could check each nonconf-to-*-src against the package's
+	# md5sums control file and back it up if modified.  That would prevent
+	# loss of configuration data if the nonconf file was erroneously omitted
+	# from conffiles in the previous version of the package.
+	$(call check-clean-installed,nonconf-to-conf)
+	$(call check-clean-installed,nonconf-to-nonconf)
+	$(call check-clean-installed,nonconf-to-unowned)
+	# Attempts to move unowned files are ignored, and pkg-mv-1 intentionally
+	# does not clean up any unowned files installed by pkg-mv-0.
+	$(call check-upgrade-ignored,unowned-to-conf,modified)
+	$(call check-upgrade-ignored,unowned-to-nonconf,modified)
+	$(call check-upgrade-ignored,unowned-to-unowned,modified)
+
+	######## Test cleanup.
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).
+	$(call clean-up,unowned-to-conf-src,modified)
+	$(call clean-up,unowned-to-nonconf-src,modified)
+	$(call clean-up,unowned-to-unowned-src,modified)
+	$(call clean-up,conf-to-unowned-dst,modified)
+	# TODO: dpkg deletes the modified src before postinst has the chance to
+	# move it to dst (hence "new" instead of "modified").
+	$(call clean-up,nonconf-to-unowned-dst,new)
+	# Attempts to move unowned files are ignored, so the modified src does
+	# not propagate to the dst (hence "new" instead of "modified").
+	$(call clean-up,unowned-to-unowned-dst,new)
+	# TODO: Either ignore attempts to move to an unowned dst or delete the
+	# .dpkg-new in postrm purge.
+	$(call clean-up,conf-to-unowned-dst.dpkg-new,new)
+	$(check-clean)
+
+TEST_CASES += test-unmodified-remove-install
+test-unmodified-remove-install:
+	$(prepare-unmodified)
+	$(DPKG_REMOVE) pkg-mv
+	$(DPKG_INSTALL) pkg-mv-1.deb
+	$(call check-clean-installed,conf-to-conf)
+	$(call check-clean-installed,conf-to-nonconf)
+	$(call check-clean-installed,conf-to-unowned)
+	$(call check-clean-installed,nonconf-to-conf)
+	$(call check-clean-installed,nonconf-to-nonconf)
+	$(call check-clean-installed,nonconf-to-unowned)
+	# Attempts to move unowned files are ignored, and pkg-mv-1 intentionally
+	# does not clean up any unowned files installed by pkg-mv-0.
+	$(call check-upgrade-ignored,unowned-to-conf,old)
+	$(call check-upgrade-ignored,unowned-to-nonconf,old)
+	$(call check-upgrade-ignored,unowned-to-unowned,old)
+
+	######## Test cleanup.
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).
+	$(call clean-up,unowned-to-conf-src,old)
+	$(call clean-up,unowned-to-nonconf-src,old)
+	$(call clean-up,unowned-to-unowned-src,old)
+	$(call clean-up,conf-to-unowned-dst,new)
+	$(call clean-up,nonconf-to-unowned-dst,new)
+	$(call clean-up,unowned-to-unowned-dst,new)
+	$(check-clean)
+
+TEST_CASES += test-modified-remove-install
+test-modified-remove-install:
+	$(prepare-modified)
+	$(DPKG_REMOVE) pkg-mv
+	$(DPKG_INSTALL) pkg-mv-1.deb
+	$(call check-upgraded-modified,conf-to-conf)
+	$(call check-upgraded-modified,conf-to-nonconf)
+	$(call check-upgraded-modified,conf-to-unowned)
+	# When an old version of a package is removed before the new version is
+	# installed (as opposed to upgrading from the old to the new), it is
+	# impossible for dpkg-m-h to preserve the modified contents of a file
+	# that was shipped but not listed in conffiles.  This is because dpkg
+	# unconditionally deletes each non-conffile when removing a package, and
+	# does so without making a backup even if the file is modified.  This
+	# removal happens before the new version's preinst runs; contrast this
+	# with package upgrades where the new version's preinst runs before the
+	# old version's files are deleted.
+	$(call check-clean-installed,nonconf-to-conf)
+	$(call check-clean-installed,nonconf-to-nonconf)
+	$(call check-clean-installed,nonconf-to-unowned)
+	# Attempts to move unowned files are ignored, and pkg-mv-1 intentionally
+	# does not clean up any unowned files installed by pkg-mv-0.
+	$(call check-upgrade-ignored,unowned-to-conf,modified)
+	$(call check-upgrade-ignored,unowned-to-nonconf,modified)
+	$(call check-upgrade-ignored,unowned-to-unowned,modified)
+
+	######## Test cleanup.
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).
+	$(call clean-up,unowned-to-conf-src,modified)
+	$(call clean-up,unowned-to-nonconf-src,modified)
+	$(call clean-up,unowned-to-unowned-src,modified)
+	$(call clean-up,conf-to-unowned-dst,modified)
+	# dpkg-m-h can't preserve the modified contents of nonconf files when
+	# the package is removed then installed (hence "new" instead of
+	# "modified").
+	$(call clean-up,nonconf-to-unowned-dst,new)
+	# Attempts to move unowned files are ignored, so the modified src does
+	# not propagate to the dst (hence "new" instead of "modified").
+	$(call clean-up,unowned-to-unowned-dst,new)
+	# TODO: Either ignore attempts to move to an unowned dst or delete the
+	# .dpkg-new in postrm purge.
+	$(call clean-up,conf-to-unowned-dst.dpkg-new,new)
+	$(check-clean)
+
+TEST_CASES += test-unmodified-abort
+test-unmodified-abort:
+	$(prepare-unmodified)
+	$(prepare-abort)
+	$(foreach case,$(cases),$(call check-noop,$(case),old))
+	$(DPKG_PURGE) pkg-mv
+	$(check-clean)
+
+TEST_CASES += test-modified-abort
+test-modified-abort:
+	$(prepare-modified)
+	$(prepare-abort)
+	$(foreach case,$(cases),$(call check-noop,$(case),modified))
+	$(DPKG_PURGE) pkg-mv
+	$(check-clean)
+
+TEST_CASES += test-unmodified-unpack-purge
+test-unmodified-unpack-purge:
+	$(prepare-unmodified)
+	$(DPKG_UNPACK) pkg-mv-1.deb
+# We don't really care about the state of the files between unpacking and
+# purging:
+#   - The semantics of the 'unpacked' state is out of scope for this test.
+#   - A package is not expected to be in the 'unpacked' state for long on a real
+#     system, so it's better to test the possible final states.
+#   - The state of the files depends on dpkg implementation choices, which
+#     should be tested elsewhere.
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).  Note: pkg-mv-1
+	# didn't get a chance to create the *-to-unowned-dst files so they don't
+	# need to be cleaned up.
+	$(call clean-up,unowned-to-conf-src,old)
+	$(call clean-up,unowned-to-nonconf-src,old)
+	$(call clean-up,unowned-to-unowned-src,old)
+	# TODO: Delete the .dpkg-remove files in postrm purge.
+	$(call clean-up,conf-to-conf-src.dpkg-remove,old)
+	$(call clean-up,conf-to-nonconf-src.dpkg-remove,old)
+	$(call clean-up,conf-to-unowned-src.dpkg-remove,old)
+	$(check-clean)
+
+TEST_CASES += test-modified-unpack-purge
+test-modified-unpack-purge:
+	$(prepare-modified)
+	$(DPKG_UNPACK) pkg-mv-1.deb
+# We don't really care about the state of the files between unpacking and
+# purging:
+#   - The semantics of the 'unpacked' state is out of scope for this test.
+#   - A package is not expected to be in the 'unpacked' state for long on a real
+#     system, so it's better to test the possible final states.
+#   - The state of the files depends on dpkg implementation choices, which
+#     should be tested elsewhere.
+	$(DPKG_PURGE) pkg-mv
+	# mv_conffile doesn't support unowned src or dst files, and the test
+	# package intentionally does not clean them up (so that mv_conffile's
+	# behavior with unowned files can be tested here).  Note: pkg-mv-1
+	# didn't get a chance to create the *-to-unowned-dst files so they don't
+	# need to be cleaned up.
+	$(call clean-up,unowned-to-conf-src,modified)
+	$(call clean-up,unowned-to-nonconf-src,modified)
+	$(call clean-up,unowned-to-unowned-src,modified)
+	$(check-clean)
+
+.PHONY: $(TEST_CASES)
+
+test-case: $(TEST_CASES)
+
+test-clean:
+	$(DPKG_PURGE) pkg-mv
+	$(check-clean)
+
+clean-hook:
+	rm -rf $(TESTS_DEB)
diff --git a/tests/t-maintscript-mv/control.in b/tests/t-maintscript-mv/control.in
new file mode 100644
index 000000000..0b9a37ff3
--- /dev/null
+++ b/tests/t-maintscript-mv/control.in
@@ -0,0 +1,7 @@
+Package: pkg-mv
+Version: %version%
+Section: test
+Priority: extra
+Maintainer: Dpkg Developers <[email protected]>
+Architecture: all
+Description: test package - mv_conffile
diff --git a/tests/t-maintscript-mv/postinst-0.in b/tests/t-maintscript-mv/postinst-0.in
new file mode 100755
index 000000000..5592eae6d
--- /dev/null
+++ b/tests/t-maintscript-mv/postinst-0.in
@@ -0,0 +1,10 @@
+#!/bin/sh
+set -e
+case $1 in
+  configure)
+    # Simulate creation of the config file via ucf or debconf.
+    for f in %unowned%; do
+      echo old >$DPKG_ROOT/$f
+    done
+    ;;
+esac
diff --git a/tests/t-maintscript-mv/postinst-1.in b/tests/t-maintscript-mv/postinst-1.in
new file mode 100755
index 000000000..7280f6333
--- /dev/null
+++ b/tests/t-maintscript-mv/postinst-1.in
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -e
+case $1 in
+  configure)
+    # Simulate creation of the new config file via ucf or debconf.
+    for f in %unowned%; do
+      echo new >$DPKG_ROOT/$f
+    done
+    ;;
+esac
+# dpkg-maintscript-helper's mv_conffile command doesn't support unowned src
+# files, so the postinst script would normally rename the unowned file itself
+# and not attempt to use mv_conffile.  mv_conffile is intentionally misused here
+# to test its behavior when given an unowned src.
+for tc in %cases%; do
+  dpkg-maintscript-helper mv_conffile "/$tc-src" "/$tc-dst" 0 -- "$@"
+done
diff --git a/tests/t-maintscript-mv/postrm-0.in b/tests/t-maintscript-mv/postrm-0.in
new file mode 100755
index 000000000..ea13ab288
--- /dev/null
+++ b/tests/t-maintscript-mv/postrm-0.in
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+case $1 in
+  purge)
+    for f in %unowned%; do
+      rm -f "$DPKG_ROOT/$f"
+    done
+    ;;
+esac
diff --git a/tests/t-maintscript-mv/postrm-1.in b/tests/t-maintscript-mv/postrm-1.in
new file mode 100755
index 000000000..8f30a8c04
--- /dev/null
+++ b/tests/t-maintscript-mv/postrm-1.in
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+# dpkg-maintscript-helper's mv_conffile command doesn't support unowned dst
+# files, so the postrm script would normally delete the unowned file itself
+# during purge.  mv_conffile is intentionally misused here to test its behavior
+# when given an unowned dst.
+for tc in %cases%; do
+  dpkg-maintscript-helper mv_conffile "/$tc-src" "/$tc-dst" 0 -- "$@"
+done
diff --git a/tests/t-maintscript-mv/preinst-1.in b/tests/t-maintscript-mv/preinst-1.in
new file mode 100755
index 000000000..72b9b3cfa
--- /dev/null
+++ b/tests/t-maintscript-mv/preinst-1.in
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+for tc in %cases%; do
+  dpkg-maintscript-helper mv_conffile "/$tc-src" "/$tc-dst" 0 -- "$@"
+done
+if [ -e "$DPKG_ROOT/fail-preinst" ]; then
+  exit 1
+fi
-- 
2.48.1
0004-man-dpkg-m-h-rm-mv-_conffile-is-only-meant-for-Conff.patch (text/x-patch, 2.7 KB)
From d5070c6e57feb412018270f7d8b9032c0775c5b3 Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Wed, 29 Jan 2025 19:23:46 -0500
Subject: [PATCH 04/10] man: dpkg-m-h: {rm,mv}_conffile is only meant for
 ${Conffiles}

---
 man/dpkg-maintscript-helper.pod | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/man/dpkg-maintscript-helper.pod b/man/dpkg-maintscript-helper.pod
index ee938e911..2adaeba2a 100644
--- a/man/dpkg-maintscript-helper.pod
+++ b/man/dpkg-maintscript-helper.pod
@@ -153,6 +153,17 @@ Z<>
 =back
 
 I<conffile> is the filename of the conffile to remove.
+I<conffile> must not be shipped in the version of the package invoking
+B<rm_conffile>.
+This command has no effect (no error, no warning) if either I<conffile> does not
+exist on the user's system or if I<conffile> is not listed in the files
+installed by the package (i.e., the output of B<dpkg-query --listfiles>).
+In particular, this command does nothing if I<conffile> was created in
+B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
+L<debconf(7)> questions).
+The behavior is undefined if I<conffile> exists and is listed in the files
+installed by the package but is not listed in the package's B<Conffiles> status
+field.
 
 Current implementation: in the B<preinst>, it checks if the conffile
 was modified and renames it either to I<conffile>B<.dpkg-remove> (if not
@@ -189,6 +200,24 @@ Z<>
 I<old-conffile> and I<new-conffile> are the old and new name of the
 conffile to rename.
 
+I<old-conffile> must not be shipped in the version of the package invoking
+B<mv_conffile>.
+This command has no effect (no error, no warning) if either I<old-conffile> does
+not exist on the user's system or if I<old-conffile> is not listed in the files
+installed by the package (i.e., the output of B<dpkg-query --listfiles>).
+In particular, this command does nothing if I<old-conffile> was created in
+B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
+L<debconf(7)> questions).
+The behavior is undefined if I<old-conffile> exists and is listed in the files
+installed by the package but is not listed in the package's B<Conffiles> status
+field.
+
+The behavior is undefined if I<new-conffile> is not listed in the package's
+I<conffiles> control file (i.e., I<DEBIAN/conffiles> during package creation).
+In particular, the behavior is undefined if I<new-conffile> is created in
+B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
+L<debconf(7)> questions).
+
 Current implementation: the B<preinst> checks if the conffile has been
 modified, if yes it's left on place otherwise it's renamed to
 I<old-conffile>B<.dpkg-remove>.
-- 
2.48.1
0005-man-dpkg-m-h-mv_conffile-doesn-t-prompt-if-orig-conf.patch (text/x-patch, 1.2 KB)
From 4745f5825d922f6f6427c5ad9e67fb5264b6157d Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Mon, 24 Feb 2025 02:22:37 -0500
Subject: [PATCH 05/10] man: dpkg-m-h: mv_conffile doesn't prompt if orig
 conffile changed

---
 man/dpkg-maintscript-helper.pod | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/man/dpkg-maintscript-helper.pod b/man/dpkg-maintscript-helper.pod
index 2adaeba2a..5ba5423f1 100644
--- a/man/dpkg-maintscript-helper.pod
+++ b/man/dpkg-maintscript-helper.pod
@@ -227,6 +227,13 @@ to I<new-conffile> if I<old-conffile> is still available.
 On
 abort-upgrade/abort-install, the B<postrm> renames
 I<old-conffile>B<.dpkg-remove> back to I<old-conffile> if required.
+B<mv_conffile> does not currently prompt the user to update a moved conffile if
+the new unmodified version differs from the old unmodified version (see L<bug
+#711598|https://bugs.debian.org/711598>).
+If the package installs the new unmodified config file to I<new-conffile> before
+it invokes B<mv_conffile> in B<postinst> during configuration, B<mv_conffile>
+will rename it to I<new-conffile>B<.dpkg-new> for user convenience or for
+post-processing in B<postinst> after B<mv_conffile> returns.
 
 =head1 SYMLINK AND DIRECTORY SWITCHES
 
-- 
2.48.1
0006-dpkg-m-h-Improve-robustness-of-conffile-md5-extracti.patch (text/x-patch, 2.1 KB)
From 54f42ef220df185a93024aacc3b1305ff0b60180 Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Wed, 29 Jan 2025 19:27:51 -0500
Subject: [PATCH 06/10] dpkg-m-h: Improve robustness of conffile md5 extraction

This avoids problems if the conffile name contains regex
metacharacters.
---
 src/dpkg-maintscript-helper.sh | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/dpkg-maintscript-helper.sh b/src/dpkg-maintscript-helper.sh
index c7de6edf9..155c017ce 100755
--- a/src/dpkg-maintscript-helper.sh
+++ b/src/dpkg-maintscript-helper.sh
@@ -98,9 +98,8 @@ prepare_rm_conffile() {
   ensure_package_owns_file "$PACKAGE" "$CONFFILE" || return 0
 
   local md5sum old_md5sum
+  old_md5sum=$(conffile_md5 "$PACKAGE" "$CONFFILE")
   md5sum="$(md5sum "$DPKG_ROOT$CONFFILE" | sed -e 's/ .*//')"
-  old_md5sum="$(dpkg-query -W -f='${Conffiles}' "$PACKAGE" | \
-    sed -n -e "\\'^ $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
   if [ "$md5sum" != "$old_md5sum" ]; then
     mv -f "$DPKG_ROOT$CONFFILE" "$DPKG_ROOT$CONFFILE.dpkg-backup"
   else
@@ -212,9 +211,8 @@ prepare_mv_conffile() {
   ensure_package_owns_file "$PACKAGE" "$CONFFILE" || return 0
 
   local md5sum old_md5sum
+  old_md5sum=$(conffile_md5 "$PACKAGE" "$CONFFILE")
   md5sum="$(md5sum "$DPKG_ROOT$CONFFILE" | sed -e 's/ .*//')"
-  old_md5sum="$(dpkg-query -W -f='${Conffiles}' "$PACKAGE" | \
-    sed -n -e "\\'^ $CONFFILE ' { s/ obsolete$//; s/.* //; p }")"
   if [ "$md5sum" = "$old_md5sum" ]; then
     mv -f "$DPKG_ROOT$CONFFILE" "$DPKG_ROOT$CONFFILE.dpkg-remove"
   fi
@@ -526,6 +524,21 @@ validate_optional_version() {
   fi
 }
 
+conffile_md5() {
+  local out line
+  out=$(dpkg-query -W -f '${Conffiles}' "$1")
+  [ -n "$out" ] || return 0
+  # Setting IFS to <space><tab> strips leading and trailing space from the line.
+  while IFS=' 	' read -r line; do
+    line=${line% obsolete}
+    [ "${line% *}" = "$2" ] || continue
+    printf %s\\n "${line##* }"
+    return 0
+  done <<EOF
+${out}
+EOF
+}
+
 ensure_package_owns_file() {
   local PACKAGE="$1"
   local FILE="$2"
-- 
2.48.1
0007-dpkg-m-h-rm_conffile-mv_conffile-Skip-if-not-a-conff.patch (text/x-patch, 6.9 KB)
From c9d4f6bfe8a690cc44edb204786fc5f7b506024e Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Wed, 29 Jan 2025 19:32:43 -0500
Subject: [PATCH 07/10] dpkg-m-h: rm_conffile, mv_conffile: Skip if not a
 conffile

Before this commit, if the user ran `rm_conffile` or `mv_conffile` on
a non-conffile belonging to the package, it would treat the file as if
it was a modified conffile:
  * `ensure_package_owns_file` returns true on non-conffiles because
    it is based on `dpkg-query -L`, which includes non-conffiles.
  * `rm_conffile` and `mv_conffile` did not return if the file was
    missing from `Conffiles`.  Instead, they behaved as if the file's
    original md5 was the empty string, which always compares unequal
    to any real md5.

Now `rm_conffile` and `mv_conffile` silently ignore non-conffiles
because they test file existence in the `Conffiles` field, not
existence in the complete list of package files.
---
 man/dpkg-maintscript-helper.pod | 14 ++++----------
 src/dpkg-maintscript-helper.sh  | 17 +++++++++++------
 tests/t-maintscript-rm/Makefile | 10 ++++++----
 3 files changed, 21 insertions(+), 20 deletions(-)

diff --git a/man/dpkg-maintscript-helper.pod b/man/dpkg-maintscript-helper.pod
index 5ba5423f1..129a3963b 100644
--- a/man/dpkg-maintscript-helper.pod
+++ b/man/dpkg-maintscript-helper.pod
@@ -156,14 +156,11 @@ I<conffile> is the filename of the conffile to remove.
 I<conffile> must not be shipped in the version of the package invoking
 B<rm_conffile>.
 This command has no effect (no error, no warning) if either I<conffile> does not
-exist on the user's system or if I<conffile> is not listed in the files
-installed by the package (i.e., the output of B<dpkg-query --listfiles>).
+exist on the user's system or if I<conffile> is not listed in the package's
+B<Conffiles> status field.
 In particular, this command does nothing if I<conffile> was created in
 B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
 L<debconf(7)> questions).
-The behavior is undefined if I<conffile> exists and is listed in the files
-installed by the package but is not listed in the package's B<Conffiles> status
-field.
 
 Current implementation: in the B<preinst>, it checks if the conffile
 was modified and renames it either to I<conffile>B<.dpkg-remove> (if not
@@ -203,14 +200,11 @@ conffile to rename.
 I<old-conffile> must not be shipped in the version of the package invoking
 B<mv_conffile>.
 This command has no effect (no error, no warning) if either I<old-conffile> does
-not exist on the user's system or if I<old-conffile> is not listed in the files
-installed by the package (i.e., the output of B<dpkg-query --listfiles>).
+not exist on the user's system or if I<old-conffile> is not listed in the
+package's B<Conffiles> status field.
 In particular, this command does nothing if I<old-conffile> was created in
 B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
 L<debconf(7)> questions).
-The behavior is undefined if I<old-conffile> exists and is listed in the files
-installed by the package but is not listed in the package's B<Conffiles> status
-field.
 
 The behavior is undefined if I<new-conffile> is not listed in the package's
 I<conffiles> control file (i.e., I<DEBIAN/conffiles> during package creation).
diff --git a/src/dpkg-maintscript-helper.sh b/src/dpkg-maintscript-helper.sh
index 155c017ce..8ef957a0e 100755
--- a/src/dpkg-maintscript-helper.sh
+++ b/src/dpkg-maintscript-helper.sh
@@ -95,10 +95,10 @@ prepare_rm_conffile() {
   local PACKAGE="$2"
 
   [ -e "$DPKG_ROOT$CONFFILE" ] || return 0
-  ensure_package_owns_file "$PACKAGE" "$CONFFILE" || return 0
 
   local md5sum old_md5sum
   old_md5sum=$(conffile_md5 "$PACKAGE" "$CONFFILE")
+  [ -n "$old_md5sum" ] || return 0
   md5sum="$(md5sum "$DPKG_ROOT$CONFFILE" | sed -e 's/ .*//')"
   if [ "$md5sum" != "$old_md5sum" ]; then
     mv -f "$DPKG_ROOT$CONFFILE" "$DPKG_ROOT$CONFFILE.dpkg-backup"
@@ -125,7 +125,7 @@ abort_rm_conffile() {
   local CONFFILE="$1"
   local PACKAGE="$2"
 
-  ensure_package_owns_file "$PACKAGE" "$CONFFILE" || return 0
+  package_owns_conffile "$PACKAGE" "$CONFFILE" || return 0
 
   if [ -e "$DPKG_ROOT$CONFFILE.dpkg-remove" ]; then
     echo "Reinstalling $DPKG_ROOT$CONFFILE that was moved away"
@@ -208,10 +208,9 @@ prepare_mv_conffile() {
 
   [ -e "$DPKG_ROOT$CONFFILE" ] || return 0
 
-  ensure_package_owns_file "$PACKAGE" "$CONFFILE" || return 0
-
   local md5sum old_md5sum
   old_md5sum=$(conffile_md5 "$PACKAGE" "$CONFFILE")
+  [ -n "$old_md5sum" ] || return 0
   md5sum="$(md5sum "$DPKG_ROOT$CONFFILE" | sed -e 's/ .*//')"
   if [ "$md5sum" = "$old_md5sum" ]; then
     mv -f "$DPKG_ROOT$CONFFILE" "$DPKG_ROOT$CONFFILE.dpkg-remove"
@@ -226,7 +225,7 @@ finish_mv_conffile() {
   rm -f "$DPKG_ROOT$OLDCONFFILE.dpkg-remove"
 
   [ -e "$DPKG_ROOT$OLDCONFFILE" ] || return 0
-  ensure_package_owns_file "$PACKAGE" "$OLDCONFFILE" || return 0
+  package_owns_conffile "$PACKAGE" "$OLDCONFFILE" || return 0
 
   echo "Preserving user changes to $DPKG_ROOT$NEWCONFFILE (renamed from $DPKG_ROOT$OLDCONFFILE)..."
   if [ -e "$DPKG_ROOT$NEWCONFFILE" ]; then
@@ -239,7 +238,7 @@ abort_mv_conffile() {
   local CONFFILE="$1"
   local PACKAGE="$2"
 
-  ensure_package_owns_file "$PACKAGE" "$CONFFILE" || return 0
+  package_owns_conffile "$PACKAGE" "$CONFFILE" || return 0
 
   if [ -e "$DPKG_ROOT$CONFFILE.dpkg-remove" ]; then
     echo "Reinstalling $DPKG_ROOT$CONFFILE that was moved away"
@@ -539,6 +538,12 @@ ${out}
 EOF
 }
 
+package_owns_conffile() {
+  local md5
+  md5=$(conffile_md5 "$@")
+  [ -n "$md5" ] && true
+}
+
 ensure_package_owns_file() {
   local PACKAGE="$1"
   local FILE="$2"
diff --git a/tests/t-maintscript-rm/Makefile b/tests/t-maintscript-rm/Makefile
index d1d342fc1..efe2a7682 100644
--- a/tests/t-maintscript-rm/Makefile
+++ b/tests/t-maintscript-rm/Makefile
@@ -97,9 +97,7 @@ test-unmodified-upgrade:
 	$(prepare-unmodified)
 	$(DPKG_INSTALL) pkg-rm-1.deb
 	$(call check-removed-clean,conf)
-	# TODO: rm_conffile mistakenly treats unmodified files that are shipped
-	# but not in conffiles as if they were modified.
-	$(call check-removed-with-backup,nonconf,original)
+	$(call check-removed-clean,nonconf)
 	# Attempts to remove unowned files are ignored, and pkg-rm-1
 	# intentionally does not clean up any unowned files installed by
 	# pkg-rm-0.
@@ -113,7 +111,11 @@ test-modified-upgrade:
 	$(prepare-modified)
 	$(DPKG_INSTALL) pkg-rm-1.deb
 	$(call check-removed-with-backup,conf,modified)
-	$(call check-removed-with-backup,nonconf,modified)
+	# TODO: dpkg-m-h could check the nonconf file against the package's
+	# md5sums control file and back it up if modified.  That would prevent
+	# loss of configuration data if the nonconf file was erroneously omitted
+	# from conffiles in the previous version of the package.
+	$(call check-removed-clean,nonconf)
 	# Attempts to remove unowned files are ignored, and pkg-rm-1
 	# intentionally does not clean up any unowned files installed by
 	# pkg-rm-0.
-- 
2.48.1
0008-dpkg-m-h-mv_conffile-Delete-.dpkg-remove-on-purge.patch (text/x-patch, 1.6 KB)
From fe33acbf7b74bedf1cd9e4b2c31aa82d9eb056cd Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Fri, 31 Jan 2025 17:55:29 -0500
Subject: [PATCH 08/10] dpkg-m-h: mv_conffile: Delete .dpkg-remove on purge

---
 src/dpkg-maintscript-helper.sh  | 5 +++++
 tests/t-maintscript-mv/Makefile | 4 ----
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/dpkg-maintscript-helper.sh b/src/dpkg-maintscript-helper.sh
index 8ef957a0e..b328a99c3 100755
--- a/src/dpkg-maintscript-helper.sh
+++ b/src/dpkg-maintscript-helper.sh
@@ -190,6 +190,11 @@ mv_conffile() {
     fi
     ;;
   postrm)
+    if [ "$1" = "purge" ]; then
+      # $OLDCONFFILE.dpkg-remove is deleted here in case the package was
+      # unpacked but never configured before it was purged.
+      rm -f "$DPKG_ROOT$OLDCONFFILE.dpkg-remove"
+    fi
     if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
        [ -n "$2" ] &&
        dpkg --compare-versions -- "$2" le-nl "$LASTVERSION"; then
diff --git a/tests/t-maintscript-mv/Makefile b/tests/t-maintscript-mv/Makefile
index 79a11412b..c15c8aad4 100644
--- a/tests/t-maintscript-mv/Makefile
+++ b/tests/t-maintscript-mv/Makefile
@@ -351,10 +351,6 @@ test-unmodified-unpack-purge:
 	$(call clean-up,unowned-to-conf-src,old)
 	$(call clean-up,unowned-to-nonconf-src,old)
 	$(call clean-up,unowned-to-unowned-src,old)
-	# TODO: Delete the .dpkg-remove files in postrm purge.
-	$(call clean-up,conf-to-conf-src.dpkg-remove,old)
-	$(call clean-up,conf-to-nonconf-src.dpkg-remove,old)
-	$(call clean-up,conf-to-unowned-src.dpkg-remove,old)
 	$(check-clean)
 
 TEST_CASES += test-modified-unpack-purge
-- 
2.48.1
0009-dpkg-m-h-mv_conffile-Delete-.dpkg-new-on-purge.patch (text/x-patch, 2.4 KB)
From 59fb217c5f1aaa7395bc3dcb6abab519a63c348d Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Sat, 1 Feb 2025 02:36:56 -0500
Subject: [PATCH 09/10] dpkg-m-h: mv_conffile: Delete .dpkg-new on purge

---
 src/dpkg-maintscript-helper.sh  | 8 ++++++++
 tests/t-maintscript-mv/Makefile | 6 ------
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/dpkg-maintscript-helper.sh b/src/dpkg-maintscript-helper.sh
index b328a99c3..0eea4f434 100755
--- a/src/dpkg-maintscript-helper.sh
+++ b/src/dpkg-maintscript-helper.sh
@@ -194,6 +194,14 @@ mv_conffile() {
       # $OLDCONFFILE.dpkg-remove is deleted here in case the package was
       # unpacked but never configured before it was purged.
       rm -f "$DPKG_ROOT$OLDCONFFILE.dpkg-remove"
+      # dpkg deletes *.dpkg-new corresponding to shipped files (both conffiles
+      # and non-conffiles) during purge, but $NEWCONFFILE might not be a shipped
+      # file (e.g., it might be created during postinst via ucf(1) or using
+      # debconf(7) answers).
+      rm -f "$DPKG_ROOT$NEWCONFFILE.dpkg-new"
+      # $NEWCONFFILE is not deleted during purge here: dpkg itself deletes
+      # shipped files, and the package's postrm script is expected to delete
+      # non-shipped (created during postinst) config files itself.
     fi
     if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
        [ -n "$2" ] &&
diff --git a/tests/t-maintscript-mv/Makefile b/tests/t-maintscript-mv/Makefile
index c15c8aad4..b56e18143 100644
--- a/tests/t-maintscript-mv/Makefile
+++ b/tests/t-maintscript-mv/Makefile
@@ -233,9 +233,6 @@ test-modified-upgrade:
 	# Attempts to move unowned files are ignored, so the modified src does
 	# not propagate to the dst (hence "new" instead of "modified").
 	$(call clean-up,unowned-to-unowned-dst,new)
-	# TODO: Either ignore attempts to move to an unowned dst or delete the
-	# .dpkg-new in postrm purge.
-	$(call clean-up,conf-to-unowned-dst.dpkg-new,new)
 	$(check-clean)
 
 TEST_CASES += test-unmodified-remove-install
@@ -310,9 +307,6 @@ test-modified-remove-install:
 	# Attempts to move unowned files are ignored, so the modified src does
 	# not propagate to the dst (hence "new" instead of "modified").
 	$(call clean-up,unowned-to-unowned-dst,new)
-	# TODO: Either ignore attempts to move to an unowned dst or delete the
-	# .dpkg-new in postrm purge.
-	$(call clean-up,conf-to-unowned-dst.dpkg-new,new)
 	$(check-clean)
 
 TEST_CASES += test-unmodified-abort
-- 
2.48.1
0010-man-dpkg-m-h-mv_conffile-now-supports-non-conffile-d.patch (text/x-patch, 3.1 KB)
From 61f719ec116520c8fa93ca1e28a239422eed3a30 Mon Sep 17 00:00:00 2001
From: Richard Hansen <[email protected]>
Date: Mon, 24 Feb 2025 02:37:49 -0500
Subject: [PATCH 10/10] man: dpkg-m-h: mv_conffile now supports non-conffile
 destination

---
 man/dpkg-maintscript-helper.pod | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/man/dpkg-maintscript-helper.pod b/man/dpkg-maintscript-helper.pod
index 129a3963b..6ae29575d 100644
--- a/man/dpkg-maintscript-helper.pod
+++ b/man/dpkg-maintscript-helper.pod
@@ -190,12 +190,12 @@ scripts:
 
 Z<>
  dpkg-maintscript-helper mv_conffile \
-    I<old-conffile> I<new-conffile> I<prior-version> I<package> -- "$@"
+    I<old-conffile> I<new-configfile> I<prior-version> I<package> -- "$@"
 
 =back
 
-I<old-conffile> and I<new-conffile> are the old and new name of the
-conffile to rename.
+I<old-conffile> is the name of the conffile to rename, and I<new-configfile> is
+its new name.
 
 I<old-conffile> must not be shipped in the version of the package invoking
 B<mv_conffile>.
@@ -206,28 +206,27 @@ In particular, this command does nothing if I<old-conffile> was created in
 B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
 L<debconf(7)> questions).
 
-The behavior is undefined if I<new-conffile> is not listed in the package's
-I<conffiles> control file (i.e., I<DEBIAN/conffiles> during package creation).
-In particular, the behavior is undefined if I<new-conffile> is created in
-B<postinst> (e.g., copied via L<ucf(1)> or generated using answers to
-L<debconf(7)> questions).
+The package is not required to ship I<new-configfile> or create it during
+configuration before invoking B<mv_conffile>.
+The package is responsible for ensuring that I<new-configfile> does not belong
+to another package.
 
 Current implementation: the B<preinst> checks if the conffile has been
 modified, if yes it's left on place otherwise it's renamed to
 I<old-conffile>B<.dpkg-remove>.
 On configuration, the B<postinst>
 removes I<old-conffile>B<.dpkg-remove> and renames I<old-conffile>
-to I<new-conffile> if I<old-conffile> is still available.
+to I<new-configfile> if I<old-conffile> is still available.
 On
 abort-upgrade/abort-install, the B<postrm> renames
 I<old-conffile>B<.dpkg-remove> back to I<old-conffile> if required.
 B<mv_conffile> does not currently prompt the user to update a moved conffile if
 the new unmodified version differs from the old unmodified version (see L<bug
 #711598|https://bugs.debian.org/711598>).
-If the package installs the new unmodified config file to I<new-conffile> before
-it invokes B<mv_conffile> in B<postinst> during configuration, B<mv_conffile>
-will rename it to I<new-conffile>B<.dpkg-new> for user convenience or for
-post-processing in B<postinst> after B<mv_conffile> returns.
+If the package installs the new unmodified config file to I<new-configfile>
+before it invokes B<mv_conffile> in B<postinst> during configuration,
+B<mv_conffile> will rename it to I<new-configfile>B<.dpkg-new> for user
+convenience or for post-processing in B<postinst> after B<mv_conffile> returns.
 
 =head1 SYMLINK AND DIRECTORY SWITCHES
 
-- 
2.48.1
OpenPGP_signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEfoCctB7fyOzE09bW0GvrRa1X4hQFAme8Ix0ACgkQ0GvrRa1X
4hQDuA/9G9qxrzivLuC5XgCd2gXmVrgEH3qcWJ5KiO38IMIh2S7BbRIzx5dCQtDm
gG53uw9N/WFHa/KlnC5+13F0OGukZpxpqln03gxLY3LsWEPHGdDOTi8SsaECDkYR
MzUikyC5Fo6XhfHRp4hbEUwAn6FCAjkyI8dsjF4WvDtlbLuJ3M02R8HqBg2ILjys
YEQldH5BJ9okoZQAQ15Jc+A7Jji+mUYhrFK8YVFApRnUAcNCjCin56SuQDEnZthP
CKrDe/CTPfysqFwdEmtH+ZS1C3VTFy7m0j4jNGjHP2zk5U3vReILMJ53jHNLK4kI
B5jz+Y/xnyhv7AVRh6yJrWilbamd1FGVARuH7vwwJhuVHXGiiOVCiYisZLYSj3Hk
sAkynXbeTXBoPuPj/qdyEob5OIvDXw13TR9fBwRT8848VyoV3kJ3h8diLsZShjuj
d+oXmECY/AF8k1XQv8yMjWaFgZcnee9mvISga9t9LA9o1NmybashoRAHCmy8Qwh1
1lFWMraed4CAGSnG9gvXCjCHroqQBBDsughqajdXmhBLW66bDyB4ajonlrf1XHNe
tdw7AJk1eI1vcBnD2YXCmYSYg1FPJl8TTLGeAzUtWrVvDhHBtHeEbvdQs4/lIvZG
4SNazzMxFHzLIVCzQvjfJE4OrhapE6RiaNnOAezQ+V7dTBOZY7o=
=sHdX
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.