From a178999a7851ee4bfe1d70da1b2f5953febbe1a2 Mon Sep 17 00:00:00 2001
From: Niels Thykier <niels@thykier.net>
Date: Wed, 22 Apr 2020 15:38:46 +0000
Subject: [PATCH] WIP: Support rm_conffile natively in dpkg via
 DEBIAN/conffiles

Missing bits:

 * dpkg-deb support (use --nocheck for now)
 * Make code use the correct code-style.

Signed-off-by: Niels Thykier <niels@thykier.net>
---
 lib/dpkg/dpkg-db.h |  1 +
 lib/dpkg/fsys.h    |  2 ++
 src/configure.c    | 26 ++++++++++++++++++++++++++
 src/unpack.c       | 38 ++++++++++++++++++++++++++++++++++----
 4 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/lib/dpkg/dpkg-db.h b/lib/dpkg/dpkg-db.h
index 1ed116bd4b..9073f9c79c 100644
--- a/lib/dpkg/dpkg-db.h
+++ b/lib/dpkg/dpkg-db.h
@@ -82,6 +82,7 @@ struct conffile {
   const char *name;
   const char *hash;
   bool obsolete;
+  bool remove_on_upgrade;
 };
 
 struct archivedetails {
diff --git a/lib/dpkg/fsys.h b/lib/dpkg/fsys.h
index 8b9107472c..93af901559 100644
--- a/lib/dpkg/fsys.h
+++ b/lib/dpkg/fsys.h
@@ -77,6 +77,8 @@ enum fsys_namenode_flags {
 	FNNF_DEFERRED_RENAME		= DPKG_BIT(8),
 	/** Path being filtered. */
 	FNNF_FILTERED			= DPKG_BIT(9),
+	/** Conffile removal requested by upgrade */
+	FNNF_RM_CONFF_ON_UPGRADE	= DPKG_BIT(10),
 };
 
 /**
diff --git a/src/configure.c b/src/configure.c
index 12f290451b..32199d4ae6 100644
--- a/src/configure.c
+++ b/src/configure.c
@@ -401,6 +401,32 @@ deferred_configure_conffile(struct pkginfo *pkg, struct conffile *conff)
 	cdr2rest = cdr2.buf + strlen(cdr.buf);
 	/* From now on we can just strcpy(cdr2rest, extension); */
 
+	if (conff->remove_on_upgrade) {
+		if (strcmp(currenthash, NONEXISTENTFLAG) == 0) {
+			/* Already removed (e.g. by local admin) */
+			return;
+		}
+		if (strcmp(conff->hash, currenthash) != 0) {
+			/* rename */
+                        strcpy(cdr2rest, DPKGOLDEXT);
+
+			printf(_("Obsolete conffile '%.250s' has been modified by you.\n"), cdr.buf);
+			printf(_("Saving as %.250s ...\n"), cdr2.buf);
+			if (rename(cdr.buf, cdr2.buf))
+				warning(_("%s: unable to rename obsolete conffile '%.250s' to '%.250s': %s"),
+                                    pkg_name(pkg, pnaw_nonambig), cdr.buf,
+                                    cdr2.buf, strerror(errno));
+		} else {
+                    printf(_("Removing obsolete conffile %.250s ...\n"),
+                        cdr.buf);
+                    if (unlink(cdr.buf) && errno != ENOENT)
+                            warning(_("%s: failed to remove '%.250s': %s"),
+                                    pkg_name(pkg, pnaw_nonambig), cdr.buf,
+                                    strerror(errno));
+                }
+		return;
+	}
+
 	strcpy(cdr2rest, DPKGNEWEXT);
 	/* If the .dpkg-new file is no longer there, ignore this one. */
 	if (lstat(cdr2.buf, &stab)) {
diff --git a/src/unpack.c b/src/unpack.c
index ee453a88d1..427ca61dfa 100644
--- a/src/unpack.c
+++ b/src/unpack.c
@@ -317,6 +317,14 @@ pkg_deconfigure_others(struct pkginfo *pkg)
   }
 }
 
+static char *rtrim_space(char *line_end, const char *const line_start) {
+    while (line_end > line_start && c_isspace(line_end[-1]))
+      --line_end;
+    if (line_end > line_start)
+      *line_end = '\0';
+    return line_end;
+}
+
 /**
  * Read the conffiles, and copy the hashes across.
  */
@@ -343,6 +351,8 @@ deb_parse_conffiles(struct pkginfo *pkg, const char *control_conffiles,
     struct fsys_namenode_list *newconff;
     struct conffile *searchconff;
     char *p;
+    char *flags_pos;
+    enum fsys_namenode_flags confflags = FNNF_NEW_CONFF;
 
     p = conffilenamebuf + strlen(conffilenamebuf);
     if (p == conffilenamebuf)
@@ -350,11 +360,29 @@ deb_parse_conffiles(struct pkginfo *pkg, const char *control_conffiles,
     if (p[-1] != '\n')
       ohshit(_("conffile name '%s' is too long, or missing final newline"),
              conffilenamebuf);
-    while (p > conffilenamebuf && c_isspace(p[-1]))
-      --p;
+    p = rtrim_space(p, conffilenamebuf);
     if (p == conffilenamebuf)
       continue;
-    *p = '\0';
+    flags_pos = strstr(conffilenamebuf, ":");
+    if (flags_pos) {
+        p = rtrim_space(flags_pos - 1, conffilenamebuf);
+        if (p == conffilenamebuf) {
+          ohshit(_("No conffile name before flags in line '%.250s'"),
+                 conffilenamebuf);
+        }
+        *flags_pos = '\0';
+        do {
+            flags_pos++;
+        } while (c_isspace(*flags_pos));
+        /* FIXME: how do we handle it if file /is/ present along with this flag? */
+        if (strcmp(flags_pos, "remove-on-upgrade") == 0) {
+            confflags |= FNNF_RM_CONFF_ON_UPGRADE;
+            confflags &= ~FNNF_NEW_CONFF;
+        } else {
+          ohshit(_("Unknown conffile flag '%.250s' for conffile '%.250s'"),
+                 flags_pos, conffilenamebuf);
+        }
+    }
 
     namenode = fsys_hash_find_node(conffilenamebuf, 0);
     namenode->oldhash = NEWCONFFILEFLAG;
@@ -408,7 +436,7 @@ deb_parse_conffiles(struct pkginfo *pkg, const char *control_conffiles,
       debug(dbg_conff, "process_archive conffile '%s' no package, no hash",
             newconff->namenode->name);
     }
-    newconff->namenode->flags |= FNNF_NEW_CONFF;
+    newconff->namenode->flags |= confflags;
   }
 
   if (ferror(conff))
@@ -569,6 +597,7 @@ pkg_remove_old_files(struct pkginfo *pkg,
     struct fsys_namenode *usenode;
 
     if ((namenode->flags & FNNF_NEW_CONFF) ||
+        (namenode->flags & FNNF_RM_CONFF_ON_UPGRADE) ||
         (namenode->flags & FNNF_NEW_INARCHIVE))
       continue;
 
@@ -788,6 +817,7 @@ pkg_update_fields(struct pkginfo *pkg, struct fsys_namenode_queue *newconffiles)
     newiconff->name = nfstrsave(cfile->namenode->name);
     newiconff->hash = nfstrsave(cfile->namenode->oldhash);
     newiconff->obsolete = !!(cfile->namenode->flags & FNNF_OBS_CONFF);
+    newiconff->remove_on_upgrade = !!(cfile->namenode->flags & FNNF_RM_CONFF_ON_UPGRADE);
     *iconffileslastp = newiconff;
     iconffileslastp = &newiconff->next;
   }
-- 
2.26.1

