tags 1006655 + patch
thanks

I've attached a patch implementing an rm_conffile_if_unmodifed command
for dpkg-maintscript-helper, as well as a declarative version for
debian/conffiles.

I've included documentation and tests for both.

Using this has the same effect as rm_conffile if the file is unmodified.
However, if the file is modified, this leaves the file in place under
the existing name (treating it as an obsolete conffile so that it still
gets removed if purging the package).

Thus, unlike rm_conffile, this version is useful for packages that still
support reading the configuration file if present, and thus don't want
to move existing modified configuration aside with a .dpkg-bak suffix,
because that'll prevent the modified configuration from being used.

- Josh Triplett
>From f3f04a4288076471b9f963f75d10470ba7e446a6 Mon Sep 17 00:00:00 2001
Message-ID: <f3f04a4288076471b9f963f75d10470ba7e446a6.1716931259.git.j...@joshtriplett.org>
From: Josh Triplett <j...@joshtriplett.org>
Date: Tue, 28 May 2024 11:11:06 -0700
Subject: [PATCH] Implement rm_conffile_if_unmodified

Sometimes, a package wants to no longer install a conffile in new
versions; for instance, the conffile might still be supported, but the
default version is not needed for new installations. In this case, the
existing `rm_conffile` doesn't suffice, because it will rename modified
versions with a .dpkg-bak suffix.

Add a new `rm_conffile_if_unmodified` (also available as
`rm_conffile --if-unmodified`) to handle this case.

Provide a declarative version `remove-if-unmodified` as well.

Document these, and add tests for them.

Closes: #1006655

Signed-off-by: Josh Triplett <j...@joshtriplett.org>
---
 lib/dpkg/dpkg-db.h                            |  1 +
 lib/dpkg/dump.c                               |  2 +
 lib/dpkg/fields.c                             | 10 +++-
 lib/dpkg/fsys.h                               |  2 +
 man/deb-conffiles.pod                         | 19 ++++++-
 man/dpkg-maintscript-helper.pod               | 43 ++++++++++++++--
 src/deb/build.c                               |  7 ++-
 src/dpkg-maintscript-helper.sh                | 50 +++++++++++++++----
 src/main/archives.c                           |  7 ++-
 src/main/configure.c                          |  8 ++-
 src/main/help.c                               |  4 +-
 src/main/unpack.c                             | 26 ++++++----
 tests/t-conffile-declarative-removal/Makefile | 21 +++++++-
 .../pkg-conffile-v1/DEBIAN/conffiles          |  1 +
 .../test-conffile-to-be-removed-if-unmodified |  0
 .../pkg-conffile-v2/DEBIAN/conffiles          |  1 +
 tests/t-conffile-obsolete/Makefile            | 18 ++++++-
 .../pkg-conff-obsolete-0/DEBIAN/conffiles     |  1 +
 .../pkg-conff-obsolete-0/test-conffile-2      |  1 +
 .../pkg-conff-obsolete-2/DEBIAN/postinst      |  3 ++
 .../pkg-conff-obsolete-2/DEBIAN/postrm        |  3 ++
 .../pkg-conff-obsolete-2/DEBIAN/preinst       |  3 ++
 22 files changed, 197 insertions(+), 34 deletions(-)
 create mode 100644 tests/t-conffile-declarative-removal/pkg-conffile-v1/test-dir/test-conffile-to-be-removed-if-unmodified
 create mode 100644 tests/t-conffile-obsolete/pkg-conff-obsolete-0/test-conffile-2

diff --git a/lib/dpkg/dpkg-db.h b/lib/dpkg/dpkg-db.h
index 61f220506..072222881 100644
--- a/lib/dpkg/dpkg-db.h
+++ b/lib/dpkg/dpkg-db.h
@@ -83,6 +83,7 @@ struct conffile {
   const char *hash;
   bool obsolete;
   bool remove_on_upgrade;
+  bool remove_if_unmodified;
 };
 
 struct archivedetails {
diff --git a/lib/dpkg/dump.c b/lib/dpkg/dump.c
index 8a1e30481..db4a823ef 100644
--- a/lib/dpkg/dump.c
+++ b/lib/dpkg/dump.c
@@ -394,6 +394,8 @@ w_conffiles(struct varbuf *vb,
       varbuf_add_str(vb, " obsolete");
     if (i->remove_on_upgrade)
       varbuf_add_str(vb, " remove-on-upgrade");
+    if (i->remove_if_unmodified)
+      varbuf_add_str(vb, " remove-if-unmodified");
   }
   if (flags&fw_printheader)
     varbuf_add_char(vb, '\n');
diff --git a/lib/dpkg/fields.c b/lib/dpkg/fields.c
index 525dc0a15..a67f0bc90 100644
--- a/lib/dpkg/fields.c
+++ b/lib/dpkg/fields.c
@@ -355,6 +355,7 @@ f_conffiles(struct pkginfo *pkg, struct pkgbin *pkgbin,
 {
   static const char obsolete_str[]= "obsolete";
   static const char remove_on_upgrade_str[] = "remove-on-upgrade";
+  static const char remove_if_unmodified_str[] = "remove-if-unmodified";
   struct conffile **lastp;
 
   lastp = &pkgbin->conffiles;
@@ -363,7 +364,7 @@ f_conffiles(struct pkginfo *pkg, struct pkgbin *pkgbin,
     const char *endent, *endfn, *hashstart;
     char *newptr;
     int c, namelen, hashlen;
-    bool obsolete, remove_on_upgrade;
+    bool obsolete, remove_on_upgrade, remove_if_unmodified;
 
     c= *value++;
     if (c == '\n') continue;
@@ -381,6 +382,12 @@ f_conffiles(struct pkginfo *pkg, struct pkgbin *pkgbin,
       conffvalue_lastword(value, endfn, endent, &hashstart, &hashlen, &endfn,
                           ps);
 
+    remove_if_unmodified = (hashlen == sizeof(remove_if_unmodified_str) - 1 &&
+                         memcmp(hashstart, remove_if_unmodified_str, hashlen) == 0);
+    if (remove_if_unmodified)
+      conffvalue_lastword(value, endfn, endent, &hashstart, &hashlen, &endfn,
+                          ps);
+
     obsolete= (hashlen == sizeof(obsolete_str)-1 &&
                memcmp(hashstart, obsolete_str, hashlen) == 0);
     if (obsolete)
@@ -405,6 +412,7 @@ f_conffiles(struct pkginfo *pkg, struct pkgbin *pkgbin,
     newlink->hash= newptr;
     newlink->obsolete= obsolete;
     newlink->remove_on_upgrade = remove_on_upgrade;
+    newlink->remove_if_unmodified = remove_if_unmodified;
     newlink->next =NULL;
     *lastp= newlink;
     lastp= &newlink->next;
diff --git a/lib/dpkg/fsys.h b/lib/dpkg/fsys.h
index ede5cdff8..63256e10a 100644
--- a/lib/dpkg/fsys.h
+++ b/lib/dpkg/fsys.h
@@ -85,6 +85,8 @@ enum DPKG_ATTR_ENUM_FLAGS fsys_namenode_flags {
 	FNNF_FILTERED			= DPKG_BIT(9),
 	/** Conffile removal requested by upgrade. */
 	FNNF_RM_CONFF_ON_UPGRADE	= DPKG_BIT(10),
+	/** Conffile removal requested by upgrade if unmodified. */
+	FNNF_RM_CONFF_IF_UNMODIFIED	= DPKG_BIT(11),
 };
 
 /**
diff --git a/man/deb-conffiles.pod b/man/deb-conffiles.pod
index e3a57b189..353c8e9f8 100644
--- a/man/deb-conffiles.pod
+++ b/man/deb-conffiles.pod
@@ -39,11 +39,25 @@ not accepted.
 Files without a flag should exist in the binary package, otherwise L<dpkg(1)>
 will ignore them.
 
-There is currently only one flag supported, B<remove-on-upgrade>, to mark
-that a conffile needs to be removed on the next upgrade (since dpkg 1.20.6).
+There are currently two flags supported:
+
+=over
+
+=item B<remove-on-upgrade> (since dpkg 1.20.6)
+
+to mark that a conffile needs to be removed on the next upgrade.
 These files must not exist in the binary package, as both L<dpkg(1)> and
 L<dpkg-deb(1)> will not accept building nor processing such binary packages.
 
+=item B<remove-if-unmodified> (since dpkg 1.22.7)
+
+to mark that a conffile needs to be removed on the next upgrade if unmodified,
+but left in place if modified.
+These files must not exist in the binary package, as both L<dpkg(1)> and
+L<dpkg-deb(1)> will not accept building nor processing such binary packages.
+
+=back
+
 =head1 EXAMPLE
 
  %CONFDIR%/alternatives/README
@@ -51,6 +65,7 @@ L<dpkg-deb(1)> will not accept building nor processing such binary packages.
  %PKGCONFDIR%/dpkg.cfg
  %CONFDIR%/logrotate.d/dpkg
  remove-on-upgrade /etc/some-old-file.conf
+ remove-if-unmodified /etc/old-but-still-supported.conf
 
 =head1 SEE ALSO
 
diff --git a/man/dpkg-maintscript-helper.pod b/man/dpkg-maintscript-helper.pod
index ee938e911..177e76902 100644
--- a/man/dpkg-maintscript-helper.pod
+++ b/man/dpkg-maintscript-helper.pod
@@ -33,7 +33,9 @@ I<command> [I<parameter>...] B<--> I<maint-script-parameter>...
 
 =item B<supports> I<command>
 
-=item B<rm_conffile> I<conffile> [I<prior-version> [I<package>]]
+=item B<rm_conffile> [I<--if-unmodified>] I<conffile> [I<prior-version> [I<package>]]
+
+=item B<rm_conffile_if_unmodified> I<conffile> [I<prior-version> [I<package>]]
 
 =item B<mv_conffile> I<old-conffile> I<new-conffile> [I<prior-version> [I<package>]]
 
@@ -130,7 +132,7 @@ it must explicitly do so and B<dpkg-maintscript-helper> can be used
 to implement graceful deletion and moving of conffiles within maintainer
 scripts.
 
-=head2 Removing a conffile
+=head2 Removing an obsolete conffile
 
 B<Note>: This can be replaced in most cases by the C<remove-on-upgrade>
 flag in F<DEBIAN/conffiles> (since dpkg 1.20.6), see L<deb-conffiles(5)>.
@@ -165,6 +167,35 @@ If the package upgrade aborts, the B<postrm> reinstalls the original conffile.
 During purge, the B<postrm> will also delete the
 B<.dpkg-bak> file kept up to now.
 
+=head2 Removing a conffile if unmodified
+
+B<Note>: This can be replaced in most cases by the C<remove-if-unmodified>
+flag in F<DEBIAN/conffiles> (since dpkg 1.22.7), see L<deb-conffiles(5)>.
+
+Sometimes, a package wants to no longer install a conffile in new versions;
+for instance, the conffile might still be supported, but the default version
+is not needed for new installations.
+In this case, the package should remove the conffile from disk, but only if
+the user has not modified it.
+If there are local modifications, they should be preserved.
+If the package upgrade aborts, the conffile should not disappear.
+
+All of this is implemented by putting the following shell snippet in the
+B<preinst>, B<postinst> and B<postrm> maintainer scripts:
+
+=over
+
+Z<>
+ dpkg-maintscript-helper rm_conffile --if-unmodified \
+    I<conffile> I<prior-version> I<package> -- "$@"
+
+=back
+
+I<conffile> is the filename of the conffile to remove.
+
+This uses the same implementation as C<rm_conffile>, but leaves modified
+versions in place rather than renaming them.
+
 =head2 Renaming a conffile
 
 If a conffile is moved from one location to another, you need to make sure
@@ -305,11 +336,12 @@ required version of B<dpkg> has been unpacked before.
 The required version
 depends on the command used, for B<rm_conffile> and B<mv_conffile>
 it is 1.15.7.2, for B<symlink_to_dir> and B<dir_to_symlink>
-it is 1.17.14:
+it is 1.17.14, and for B<rm_conffile_if_unmodified> or the I<--if-unmodified>
+option to B<rm_conffile> it is 1.22.7:
 
 =over
 
- Pre-Depends: dpkg (>= 1.17.14)
+ Pre-Depends: dpkg (>= 1.22.7)
 
 =back
 
@@ -333,6 +365,9 @@ B<supports> command will check if the environment variables as set
 by dpkg and required by the script are present, and will consider it a
 failure in case the environment is not sufficient.
 
+Note that B<supports rm_conffile_if_unmodified> also indicates the
+availability of the I<--if-unmodified> option to B<rm_conffile>.
+
 =head1 ENVIRONMENT
 
 =over
diff --git a/src/deb/build.c b/src/deb/build.c
index 5da2e76cd..d146434da 100644
--- a/src/deb/build.c
+++ b/src/deb/build.c
@@ -272,6 +272,7 @@ check_conffiles(const char *ctrldir, const char *rootdir)
     char *conffilename = conffilenamebuf;
     int n;
     bool remove_on_upgrade = false;
+    bool remove_if_unmodified = false;
 
     n = strlen(conffilename);
     if (!n)
@@ -317,6 +318,8 @@ check_conffiles(const char *ctrldir, const char *rootdir)
 
       if (strcmp(flag, "remove-on-upgrade") == 0)
         remove_on_upgrade = true;
+      else if (strcmp(flag, "remove-if-unmodified") == 0)
+        remove_if_unmodified = true;
       else
         ohshit(_("unknown flag '%s' for conffile '%s'"), flag, conffilename);
     }
@@ -328,11 +331,11 @@ check_conffiles(const char *ctrldir, const char *rootdir)
         if ((n > 1) && c_isspace(conffilename[n - 1]))
           warning(_("conffile filename '%s' contains trailing white spaces"),
                   conffilename);
-        if (!remove_on_upgrade)
+        if (!remove_on_upgrade && !remove_if_unmodified)
           ohshit(_("conffile '%.250s' does not appear in package"), conffilename);
       } else
         ohshite(_("conffile '%.250s' is not stattable"), conffilename);
-    } else if (remove_on_upgrade) {
+    } else if (remove_on_upgrade || remove_if_unmodified) {
         ohshit(_("conffile '%s' is present but is requested to be removed"),
                conffilename);
     } else if (!S_ISREG(controlstab.st_mode)) {
diff --git a/src/dpkg-maintscript-helper.sh b/src/dpkg-maintscript-helper.sh
index d799aa8fe..b8b3cb329 100755
--- a/src/dpkg-maintscript-helper.sh
+++ b/src/dpkg-maintscript-helper.sh
@@ -27,6 +27,12 @@
 ## Functions to remove an obsolete conffile during upgrade
 ##
 rm_conffile() {
+  local IF_UNMODIFIED=
+  if [ "$1" = "--if-unmodified" ]; then
+    IF_UNMODIFIED="$1"
+    shift
+  fi
+
   local CONFFILE="$1"
   local LASTVERSION="$2"
   local PACKAGE="$3"
@@ -63,13 +69,13 @@ rm_conffile() {
   preinst)
     if [ "$1" = "install" -o "$1" = "upgrade" ] && [ -n "$2" ] &&
        dpkg --compare-versions -- "$2" le-nl "$LASTVERSION"; then
-      prepare_rm_conffile "$CONFFILE" "$PACKAGE"
+      prepare_rm_conffile $IF_UNMODIFIED "$CONFFILE" "$PACKAGE"
     fi
     ;;
   postinst)
     if [ "$1" = "configure" ] && [ -n "$2" ] &&
        dpkg --compare-versions -- "$2" le-nl "$LASTVERSION"; then
-      finish_rm_conffile "$CONFFILE"
+      finish_rm_conffile $IF_UNMODIFIED "$CONFFILE"
     fi
     ;;
   postrm)
@@ -81,7 +87,7 @@ rm_conffile() {
     if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
        [ -n "$2" ] &&
        dpkg --compare-versions -- "$2" le-nl "$LASTVERSION"; then
-      abort_rm_conffile "$CONFFILE" "$PACKAGE"
+      abort_rm_conffile $IF_UNMODIFIED "$CONFFILE" "$PACKAGE"
     fi
     ;;
   *)
@@ -91,6 +97,12 @@ rm_conffile() {
 }
 
 prepare_rm_conffile() {
+  local IF_UNMODIFIED=
+  if [ "$1" = "--if-unmodified" ]; then
+    IF_UNMODIFIED="$1"
+    shift
+  fi
+
   local CONFFILE="$1"
   local PACKAGE="$2"
 
@@ -102,16 +114,24 @@ prepare_rm_conffile() {
   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"
+    if [ -z "$IF_UNMODIFIED" ]; then
+      mv -f "$DPKG_ROOT$CONFFILE" "$DPKG_ROOT$CONFFILE.dpkg-backup"
+    fi
   else
     mv -f "$DPKG_ROOT$CONFFILE" "$DPKG_ROOT$CONFFILE.dpkg-remove"
   fi
 }
 
 finish_rm_conffile() {
+  local IF_UNMODIFIED=
+  if [ "$1" = "--if-unmodified" ]; then
+    IF_UNMODIFIED="$1"
+    shift
+  fi
+
   local CONFFILE="$1"
 
-  if [ -e "$DPKG_ROOT$CONFFILE.dpkg-backup" ]; then
+  if [ -z "$IF_UNMODIFIED" ] && [ -e "$DPKG_ROOT$CONFFILE.dpkg-backup" ]; then
     echo "Obsolete conffile $DPKG_ROOT$CONFFILE has been modified by you."
     echo "Saving as $DPKG_ROOT$CONFFILE.dpkg-bak ..."
     mv -f "$DPKG_ROOT$CONFFILE.dpkg-backup" "$DPKG_ROOT$CONFFILE.dpkg-bak"
@@ -123,6 +143,12 @@ finish_rm_conffile() {
 }
 
 abort_rm_conffile() {
+  local IF_UNMODIFIED=
+  if [ "$1" = "--if-unmodified" ]; then
+    IF_UNMODIFIED="$1"
+    shift
+  fi
+
   local CONFFILE="$1"
   local PACKAGE="$2"
 
@@ -132,7 +158,7 @@ abort_rm_conffile() {
     echo "Reinstalling $DPKG_ROOT$CONFFILE that was moved away"
     mv "$DPKG_ROOT$CONFFILE.dpkg-remove" "$DPKG_ROOT$CONFFILE"
   fi
-  if [ -e "$DPKG_ROOT$CONFFILE.dpkg-backup" ]; then
+  if [ -z "$IF_UNMODIFIED" ] && [ -e "$DPKG_ROOT$CONFFILE.dpkg-backup" ]; then
     echo "Reinstalling $DPKG_ROOT$CONFFILE that was backed-up"
     mv "$DPKG_ROOT$CONFFILE.dpkg-backup" "$DPKG_ROOT$CONFFILE"
   fi
@@ -569,9 +595,12 @@ Usage: $PROGNAME <command> <parameter>... -- <maintainer-script-parameter>...
 Commands:
   supports <command>
         Returns 0 (success) if the given command is supported, 1 otherwise.
-  rm_conffile <conffile> [<last-version> [<package>]]
+  rm_conffile [--if-unmodified] <conffile> [<last-version> [<package>]]
         Remove obsolete conffile. Must be called in preinst, postinst and
-        postrm.
+        postrm. If given --if-unmodified, act as rm_conffile_if_unmodfied.
+  rm_conffile_if_unmodified <conffile> [<last-version> [<package>]]
+        Remove still-supported conffile if unmodified. Must be called in
+        preinst, postinst and postrm.
   mv_conffile <old-conf> <new-conf> [<last-version> [<package>]]
         Rename a conffile. Must be called in preinst, postinst and postrm.
   symlink_to_dir <pathname> <old-symlink-target> [<last-version> [<package>]]
@@ -615,7 +644,7 @@ shift
 case "$command" in
 supports)
   case "$1" in
-  rm_conffile|mv_conffile|symlink_to_dir|dir_to_symlink)
+  rm_conffile|rm_conffile_if_unmodified|mv_conffile|symlink_to_dir|dir_to_symlink)
     code=0
     ;;
   *)
@@ -635,6 +664,9 @@ supports)
 rm_conffile)
   rm_conffile "$@"
   ;;
+rm_conffile_if_unmodified)
+  rm_conffile --if-unmodified "$@"
+  ;;
 mv_conffile)
   mv_conffile "$@"
   ;;
diff --git a/src/main/archives.c b/src/main/archives.c
index fa703f048..f33d514ca 100644
--- a/src/main/archives.c
+++ b/src/main/archives.c
@@ -240,7 +240,9 @@ md5hash_prev_conffile(struct pkginfo *pkg, char *oldhash, const char *oldname,
                              &otherpkg->configversion) != 0)
       continue;
     for (conff = otherpkg->installed.conffiles; conff; conff = conff->next) {
-      if (conff->obsolete || conff->remove_on_upgrade)
+      if (conff->obsolete ||
+          conff->remove_on_upgrade ||
+          conff->remove_if_unmodified)
         continue;
       if (strcmp(conff->name, namenode->name) == 0)
         break;
@@ -682,7 +684,8 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti)
 
   namenode = fsys_hash_find_node(ti->name, FHFF_NONE);
 
-  if (namenode->flags & FNNF_RM_CONFF_ON_UPGRADE)
+  if ((namenode->flags & FNNF_RM_CONFF_ON_UPGRADE) ||
+      (namenode->flags & FNNF_RM_CONFF_IF_UNMODIFIED))
     ohshit(_("conffile '%s' marked for removal on upgrade, shipped in package"),
            ti->name);
 
diff --git a/src/main/configure.c b/src/main/configure.c
index b12b06808..5831447a3 100644
--- a/src/main/configure.c
+++ b/src/main/configure.c
@@ -356,7 +356,9 @@ deferred_configure_ghost_conffile(struct pkginfo *pkg, struct conffile *conff)
 
 		for (otherconff = otherpkg->installed.conffiles; otherconff;
 		     otherconff = otherconff->next) {
-			if (otherconff->obsolete || otherconff->remove_on_upgrade)
+			if (otherconff->obsolete ||
+                            otherconff->remove_on_upgrade ||
+                            otherconff->remove_if_unmodified)
 				continue;
 
 			/* Check if we need to propagate the new hash from
@@ -652,7 +654,9 @@ deferred_configure(struct pkginfo *pkg)
 		 * ‘*.dpkg-new’ no longer exists we assume that we've
 		 * already processed this one. */
 		for (conff = pkg->installed.conffiles; conff; conff = conff->next) {
-			if (conff->obsolete || conff->remove_on_upgrade)
+			if (conff->obsolete ||
+                            conff->remove_on_upgrade ||
+                            conff->remove_if_unmodified)
 				continue;
 			deferred_configure_conffile(pkg, conff);
 		}
diff --git a/src/main/help.c b/src/main/help.c
index 59e730e1c..8774dd677 100644
--- a/src/main/help.c
+++ b/src/main/help.c
@@ -196,7 +196,9 @@ dir_has_conffiles(struct fsys_namenode *file, struct pkginfo *pkg)
         pkg_name(pkg, pnaw_always));
   namelen = strlen(file->name);
   for (conff= pkg->installed.conffiles; conff; conff= conff->next) {
-      if (conff->obsolete || conff->remove_on_upgrade)
+      if (conff->obsolete ||
+          conff->remove_on_upgrade ||
+          conff->remove_if_unmodified)
         continue;
       if (strncmp(file->name, conff->name, namelen) == 0 &&
           strlen(conff->name) > namelen && conff->name[namelen] == '/') {
diff --git a/src/main/unpack.c b/src/main/unpack.c
index 20d30c6c8..0708b437a 100644
--- a/src/main/unpack.c
+++ b/src/main/unpack.c
@@ -386,6 +386,9 @@ deb_parse_conffiles(const struct pkginfo *pkg, const char *control_conffiles,
       if (strcmp(flag, "remove-on-upgrade") == 0) {
         confflags |= FNNF_RM_CONFF_ON_UPGRADE;
         confflags &= ~FNNF_NEW_CONFF;
+      } else if (strcmp(flag, "remove-if-unmodified") == 0) {
+        confflags |= FNNF_RM_CONFF_IF_UNMODIFIED;
+        confflags &= ~FNNF_NEW_CONFF;
       } else {
         if (c_isspace(flag[0]))
           warning(_("line with conffile filename '%s' has leading white spaces"),
@@ -657,15 +660,17 @@ pkg_remove_conffile_on_upgrade(struct pkginfo *pkg, struct fsys_namenode *nameno
   }
 
   /* Otherwise, preserve the modified conffile. */
-  varbuf_rollback(&cdrext_state);
-  varbuf_add_str(&cdrext, DPKGOLDEXT);
+  if (namenode->flags & FNNF_RM_CONFF_ON_UPGRADE) {
+      varbuf_rollback(&cdrext_state);
+      varbuf_add_str(&cdrext, DPKGOLDEXT);
 
-  printf(_("Obsolete conffile '%s' has been modified by you.\n"), cdr.buf);
-  printf(_("Saving as %s ...\n"), cdrext.buf);
-  if (rename(cdr.buf, cdrext.buf) < 0)
-    warning(_("%s: cannot rename obsolete conffile '%s' to '%s': %s"),
-            pkg_name(pkg, pnaw_nonambig),
-            cdr.buf, cdrext.buf, strerror(errno));
+      printf(_("Obsolete conffile '%s' has been modified by you.\n"), cdr.buf);
+      printf(_("Saving as %s ...\n"), cdrext.buf);
+      if (rename(cdr.buf, cdrext.buf) < 0)
+        warning(_("%s: cannot rename obsolete conffile '%s' to '%s': %s"),
+                pkg_name(pkg, pnaw_nonambig),
+                cdr.buf, cdrext.buf, strerror(errno));
+  }
 }
 
 static void
@@ -687,7 +692,8 @@ pkg_remove_old_files(struct pkginfo *pkg,
     debug(dbg_conffdetail, "%s: removing conffile '%s' for %s?", __func__,
           cfile->namenode->name, pkg_name(pkg, pnaw_always));
 
-    if (!(cfile->namenode->flags & FNNF_RM_CONFF_ON_UPGRADE))
+    if (!(cfile->namenode->flags & FNNF_RM_CONFF_ON_UPGRADE) &&
+        !(cfile->namenode->flags & FNNF_RM_CONFF_IF_UNMODIFIED))
       continue;
 
     pkg_remove_conffile_on_upgrade(pkg, cfile->namenode);
@@ -922,6 +928,8 @@ pkg_update_fields(struct pkginfo *pkg, struct fsys_namenode_queue *newconffiles)
     newiconff->obsolete = !!(cfile->namenode->flags & FNNF_OBS_CONFF);
     newiconff->remove_on_upgrade = !!(
         cfile->namenode->flags & FNNF_RM_CONFF_ON_UPGRADE);
+    newiconff->remove_if_unmodified = !!(
+        cfile->namenode->flags & FNNF_RM_CONFF_IF_UNMODIFIED);
     *iconffileslastp = newiconff;
     iconffileslastp = &newiconff->next;
   }
diff --git a/tests/t-conffile-declarative-removal/Makefile b/tests/t-conffile-declarative-removal/Makefile
index 0d0e77b86..d74adb2ab 100644
--- a/tests/t-conffile-declarative-removal/Makefile
+++ b/tests/t-conffile-declarative-removal/Makefile
@@ -22,45 +22,64 @@ test-case:
 	$(DPKG_INSTALL) pkg-conffile-v1.deb
 	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 	$(DPKG_INSTALL) pkg-conffile-v2.deb
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 
 	# Reset.
 	$(DPKG_PURGE) pkg-conffile
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 
 	# Unmodified case; but force it to happen via two invocations.
 	$(DPKG_INSTALL) pkg-conffile-v1.deb
 	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 	$(DPKG_UNPACK) pkg-conffile-v2.deb
 	# The old conffile is removed during unpack.
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 	# After configure, the file is still removed.
 	$(DPKG_CONFIGURE) pkg-conffile
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 
 	# Reset.
 	$(DPKG_PURGE) pkg-conffile
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 
-	# Modified; should store .dpkg-old.
+	# Modified; should store .dpkg-old for remove-on-upgrade, or leave in place for remove-if-unmodified.
 	$(DPKG_INSTALL) pkg-conffile-v1.deb
 	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	echo "modify this" >"$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
+	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	echo "modify this" >"$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
 	$(DPKG_INSTALL) pkg-conffile-v2.deb
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified.dpkg-old"
 
 	# Reset.
 	$(DPKG_PURGE) pkg-conffile
 	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed"
 	$(RM) "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed.dpkg-old"
+	test ! -f "$(DPKG_INSTDIR)/test-dir/test-conffile-to-be-removed-if-unmodified"
 
 	# Queue a remove on upgrade, and move ownership.
 	$(DPKG_INSTALL) pkg-conffile-v1.deb
diff --git a/tests/t-conffile-declarative-removal/pkg-conffile-v1/DEBIAN/conffiles b/tests/t-conffile-declarative-removal/pkg-conffile-v1/DEBIAN/conffiles
index c4138c4fa..4627abe1a 100644
--- a/tests/t-conffile-declarative-removal/pkg-conffile-v1/DEBIAN/conffiles
+++ b/tests/t-conffile-declarative-removal/pkg-conffile-v1/DEBIAN/conffiles
@@ -1 +1,2 @@
 /test-dir/test-conffile-to-be-removed
+/test-dir/test-conffile-to-be-removed-if-unmodified
diff --git a/tests/t-conffile-declarative-removal/pkg-conffile-v1/test-dir/test-conffile-to-be-removed-if-unmodified b/tests/t-conffile-declarative-removal/pkg-conffile-v1/test-dir/test-conffile-to-be-removed-if-unmodified
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/t-conffile-declarative-removal/pkg-conffile-v2/DEBIAN/conffiles b/tests/t-conffile-declarative-removal/pkg-conffile-v2/DEBIAN/conffiles
index 587753be1..58fee74b1 100644
--- a/tests/t-conffile-declarative-removal/pkg-conffile-v2/DEBIAN/conffiles
+++ b/tests/t-conffile-declarative-removal/pkg-conffile-v2/DEBIAN/conffiles
@@ -1 +1,2 @@
 remove-on-upgrade /test-dir/test-conffile-to-be-removed
+remove-if-unmodified /test-dir/test-conffile-to-be-removed-if-unmodified
diff --git a/tests/t-conffile-obsolete/Makefile b/tests/t-conffile-obsolete/Makefile
index 55d9477f5..3ed7d6267 100644
--- a/tests/t-conffile-obsolete/Makefile
+++ b/tests/t-conffile-obsolete/Makefile
@@ -6,27 +6,39 @@ test-case:
 	$(DPKG_INSTALL) pkg-conff-obsolete-0.deb
 	$(DPKG_INSTALL) pkg-conff-obsolete-1.deb
 	test -f '$(DPKG_INSTDIR)/test-conffile'
+	test -f '$(DPKG_INSTDIR)/test-conffile-2'
 	$(DPKG_PURGE) pkg-conff-obsolete
 	test ! -f '$(DPKG_INSTDIR)/test-conffile'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2'
 	# Unmodified conffile is autoremoved for the user
 	$(DPKG_INSTALL) pkg-conff-obsolete-0.deb
 	$(DPKG_UNPACK) pkg-conff-obsolete-2.deb
 	test -f '$(DPKG_INSTDIR)/test-conffile.dpkg-remove'
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-backup'
+	test -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-remove'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-backup'
 	$(DPKG_CONFIGURE) pkg-conff-obsolete
 	test ! -f '$(DPKG_INSTDIR)/test-conffile'
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-bak'
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-backup'
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-remove'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-bak'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-backup'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-remove'
 	$(DPKG_PURGE) pkg-conff-obsolete
-	# Modified conffile is moved away in .dpkg-bak for the user
+	# Modified conffile 1 is moved away in .dpkg-bak for the user
 	$(DPKG_INSTALL) pkg-conff-obsolete-0.deb
 	$(BEROOT) sh -c "echo foo >>'$(DPKG_INSTDIR)/test-conffile'"
+	$(BEROOT) sh -c "echo foo >>'$(DPKG_INSTDIR)/test-conffile-2'"
 	$(DPKG_INSTALL) pkg-conff-obsolete-2.deb
 	test ! -f '$(DPKG_INSTDIR)/test-conffile'
 	test -f '$(DPKG_INSTDIR)/test-conffile.dpkg-bak'
+	test -f '$(DPKG_INSTDIR)/test-conffile-2'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-bak'
 	$(DPKG_PURGE) pkg-conff-obsolete
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-bak'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2'
 	# Abort-upgrade restores the to-be-removed conffile
 	$(DPKG_INSTALL) pkg-conff-obsolete-0.deb
 	$(BEROOT) touch '$(DPKG_INSTDIR)/fail-preinst'
@@ -36,6 +48,10 @@ test-case:
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-remove'
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-bak'
 	test ! -f '$(DPKG_INSTDIR)/test-conffile.dpkg-backup'
+	test -f '$(DPKG_INSTDIR)/test-conffile-2'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-remove'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-bak'
+	test ! -f '$(DPKG_INSTDIR)/test-conffile-2.dpkg-backup'
 
 test-clean:
 	$(DPKG_PURGE) pkg-conff-obsolete
diff --git a/tests/t-conffile-obsolete/pkg-conff-obsolete-0/DEBIAN/conffiles b/tests/t-conffile-obsolete/pkg-conff-obsolete-0/DEBIAN/conffiles
index 4fad7e9dc..fa95b58e0 100644
--- a/tests/t-conffile-obsolete/pkg-conff-obsolete-0/DEBIAN/conffiles
+++ b/tests/t-conffile-obsolete/pkg-conff-obsolete-0/DEBIAN/conffiles
@@ -1 +1,2 @@
 /test-conffile
+/test-conffile-2
diff --git a/tests/t-conffile-obsolete/pkg-conff-obsolete-0/test-conffile-2 b/tests/t-conffile-obsolete/pkg-conff-obsolete-0/test-conffile-2
new file mode 100644
index 000000000..6fbc8b586
--- /dev/null
+++ b/tests/t-conffile-obsolete/pkg-conff-obsolete-0/test-conffile-2
@@ -0,0 +1 @@
+test init
diff --git a/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postinst b/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postinst
index c6b0782d4..54d282e02 100755
--- a/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postinst
+++ b/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postinst
@@ -2,3 +2,6 @@
 if dpkg-maintscript-helper supports rm_conffile; then
     dpkg-maintscript-helper rm_conffile /test-conffile 0 -- "$@"
 fi
+if dpkg-maintscript-helper supports rm_conffile_if_unmodified; then
+    dpkg-maintscript-helper rm_conffile --if-unmodified /test-conffile-2 0 -- "$@"
+fi
diff --git a/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postrm b/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postrm
index c6b0782d4..54d282e02 100755
--- a/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postrm
+++ b/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/postrm
@@ -2,3 +2,6 @@
 if dpkg-maintscript-helper supports rm_conffile; then
     dpkg-maintscript-helper rm_conffile /test-conffile 0 -- "$@"
 fi
+if dpkg-maintscript-helper supports rm_conffile_if_unmodified; then
+    dpkg-maintscript-helper rm_conffile --if-unmodified /test-conffile-2 0 -- "$@"
+fi
diff --git a/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/preinst b/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/preinst
index c0e592b0e..0dd3eeb26 100755
--- a/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/preinst
+++ b/tests/t-conffile-obsolete/pkg-conff-obsolete-2/DEBIAN/preinst
@@ -2,6 +2,9 @@
 if dpkg-maintscript-helper supports rm_conffile; then
     dpkg-maintscript-helper rm_conffile /test-conffile 0 -- "$@"
 fi
+if dpkg-maintscript-helper supports rm_conffile_if_unmodified; then
+    dpkg-maintscript-helper rm_conffile --if-unmodified /test-conffile-2 0 -- "$@"
+fi
 
 if [ -e "$DPKG_ROOT/fail-preinst" ]; then
     exit 1
-- 
2.45.1

Reply via email to