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

[PATCH 5/6] Implement 'm' option for conffile merging during conflict resolution



The conflict resolution prompt has been updated with a new option for
automatic merging:

Configuration file `<file>'
 ==> Modified (by you or by a script) since installation.
 ==> Package distributor has shipped an updated version.
   What would you like to do about it ?  Your options are:
    Y or I  : install the package maintainer's version
    N or O  : keep your currently-installed version
      D     : show the differences between the versions
      M     : attempt to automatically merge the differences
      Z     : background this process to examine the situation
 The default action is to keep your current version.
*** foo.cfg (Y/I/N/O/D/M/Z) [default=N] ?

This functions by performing a 3-way diff using the new conffile, the
currently-installed conffile, and the pristine version of the conffile
shipped in the currently installed package (in the conffile database).

The merge output is stored into a temporary file (<file>.dpkg-merge).
On merge failure, the file is unlinked, the user is informed of the
failure, and the prompt is repeated. On success, the merge output is
renamed on top of the installed conffile, and then configuration
proceeds as though the user selected to keep the currently-installed
version.

Future implementations can extend this to allow for interactive inspection
of failed merges, piping the diff to a pager, or perhaps even alternative
diff3 implementations when available.
---
 lib/dpkg/dpkg.h  |    2 +
 src/conffiledb.c |   74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/conffiledb.h |   11 ++++++++
 src/configure.c  |   15 ++++++++++-
 4 files changed, 101 insertions(+), 1 deletions(-)

diff --git a/lib/dpkg/dpkg.h b/lib/dpkg/dpkg.h
index 9b06c73..425d1e4 100644
--- a/lib/dpkg/dpkg.h
+++ b/lib/dpkg/dpkg.h
@@ -58,6 +58,7 @@ DPKG_BEGIN_DECLS
 #define DPKGNEWEXT         ".dpkg-new"
 #define DPKGOLDEXT         ".dpkg-old"
 #define DPKGDISTEXT        ".dpkg-dist"
+#define DPKGMERGEEXT        ".dpkg-merge"
 
 #define CONTROLFILE        "control"
 #define CONFFILESFILE      "conffiles"
@@ -115,6 +116,7 @@ DPKG_BEGIN_DECLS
 #define RM		"rm"
 #define FIND		"find"
 #define DIFF		"diff"
+#define DIFF3		"diff3"
 
 #define FIND_EXPRSTARTCHARS "-(),!"
 
diff --git a/src/conffiledb.c b/src/conffiledb.c
index 46cbfaf..00feee8 100644
--- a/src/conffiledb.c
+++ b/src/conffiledb.c
@@ -9,11 +9,13 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <sys/wait.h>
 
 #include <dpkg/dpkg.h>
 #include <dpkg/i18n.h>
 #include <dpkg/buffer.h>
 #include <dpkg/path.h>
+#include <dpkg/file.h>
 #include <dpkg/dpkg-db.h>
 #include "main.h"
 
@@ -216,3 +218,75 @@ conffiledb_diff (const char *pkg, const char *path, conffiledb_base base)
 	free(cmd);
 	return WEXITSTATUS(status);
 }
+
+int conffiledb_automerge(const char *pkg, const char *path)
+{
+	int res, merge_fd, resolved_fd, path_fd;
+	size_t cmd_sz = 0, merge_output_fname_sz = 0;
+	char *cmdbuf = NULL, *cf_old = NULL, *cf_new = NULL;
+	char *cf_resolved = NULL, *merge_output_fname = NULL;
+
+	/* i.e. <path> + ".dpkg-merge" + '\0' */
+	merge_output_fname_sz = strlen(path) + strlen(DPKGMERGEEXT) + 1;
+	merge_output_fname = m_malloc(merge_output_fname_sz);
+	sprintf(merge_output_fname, "%s%s", path, DPKGMERGEEXT);
+
+	/* the 'old' version is actually the current dist version in the db */
+	cf_old = conffiledb_path(pkg, path, conffiledb_base_current);
+	cf_new = conffiledb_path(pkg, path, conffiledb_base_new);
+
+	/* create a file for merge output, ensuring it does not already exist */
+	merge_fd = open(merge_output_fname, O_CREAT|O_WRONLY|O_EXCL);
+	if (merge_fd == -1 )
+		ohshite("conffiledb_automerge: can't open %s for merge output",
+		        merge_output_fname);
+	else if (close(merge_fd))
+		ohshite("conffiledb_automerge: close failed");
+
+	/* copy file permissions from the original file */
+	file_copy_perms(path, merge_output_fname);
+
+	/* DIFF3 + " -m " + new + " " + old + " " + cur + " > " + out + '\0' */
+	cmd_sz = strlen(DIFF3) + 4 + strlen(cf_new) + 1 + strlen(cf_old) + 1 + 
+	         strlen(path) + 3 + merge_output_fname_sz + 1;
+	cmdbuf = m_malloc(cmd_sz);
+	snprintf(cmdbuf, cmd_sz, "%s -m %s %s %s > %s", DIFF3, cf_new, cf_old, 
+	         path, merge_output_fname);
+
+	/* let's give it a go! */
+	res = system(cmdbuf);
+	if (WEXITSTATUS(res) == 0) {
+		/* rename the merge output to the final destination */
+		if (rename(merge_output_fname, path) == -1)
+			ohshite("conffiledb_automerge: can not rename %s", 
+			        merge_output_fname);
+		/* and the also register the successful merge in the
+		 * "resolved" conffiles db, as another possible ancestor for
+		 * future merges */
+		path_fd = open(path, O_RDONLY);
+		if (path_fd < 0)
+			ohshite("conffiledb_automerge: can not open %s", path);
+		conffiledb_ensure_db(pkg, conffiledb_base_resolved);
+		cf_resolved = conffiledb_path(pkg, path, 
+		                              conffiledb_base_resolved);
+		resolved_fd = open(cf_resolved, O_WRONLY|O_CREAT|O_TRUNC);
+		if (resolved_fd < 0)
+			ohshite("conffiledb_automerge: can not open %s", 
+			        cf_resolved);
+		fd_fd_copy(path_fd, resolved_fd, -1, "conffiledb_automerge");
+	} else {
+		/* oh noes, merge failed :( */
+		if (unlink(merge_output_fname))
+			ohshite("conff_automerge: can not unlink %s",
+			        merge_output_fname);
+	}
+
+	free(merge_output_fname);
+	free(cmdbuf);
+	free(cf_new);
+	free(cf_old);
+	if (cf_resolved != NULL)
+		free(cf_resolved);
+
+	return res;
+}
diff --git a/src/conffiledb.h b/src/conffiledb.h
index 9278cf1..6738b55 100644
--- a/src/conffiledb.h
+++ b/src/conffiledb.h
@@ -152,4 +152,15 @@ void conffiledb_commit(const char *pkg, conffiledb_base from_base,
  */
 int conffiledb_diff(const char *pkg, const char *path, conffiledb_base base);
 
+/** Attempt to automagically merge a 3-way delta for a conffile.
+ *
+ * On a successful merge, the merged copy is also registered under the
+ * ::conffiledb_base_resolved basedir for pkg as a "resolved" version.
+ *
+ * @param pkg The package owning the conffile
+ * @param path The path to the installed conffile
+ * @return The exit status of the underlying call to diff3(1)
+ */
+int conffiledb_automerge(const char *pkg, const char *path);
+
 #endif /* CONFFILES_H */
diff --git a/src/configure.c b/src/configure.c
index 8d69c66..9b6a12a 100644
--- a/src/configure.c
+++ b/src/configure.c
@@ -654,6 +654,7 @@ promptconfaction(const char *pkg, const char *cfgfile, const char *realold,
 		          "    Y or I  : install the package maintainer's version\n"
 		          "    N or O  : keep your currently-installed version\n"
 		          "      D     : show the differences between the versions\n"
+		          "      M     : attempt to automatically merge the differences\n"
 		          "      Z     : background this process to examine the situation\n"));
 
 		if (what & cfof_keep)
@@ -664,7 +665,7 @@ promptconfaction(const char *pkg, const char *cfgfile, const char *realold,
 		s = strrchr(cfgfile, '/');
 		if (!s || !*++s)
 			s = cfgfile;
-		fprintf(stderr, "*** %s (Y/I/N/O/D/Z) %s ? ",
+		fprintf(stderr, "*** %s (Y/I/N/O/D/M/Z) %s ? ",
 		        s,
 		        (what & cfof_keep) ? _("[default=N]") :
 		        (what & cfof_install) ? _("[default=Y]") :
@@ -698,6 +699,18 @@ promptconfaction(const char *pkg, const char *cfgfile, const char *realold,
 		if (cc == 'd')
 			showdiff(realold, realnew);
 
+		if (cc == 'm') {
+			fprintf(stderr, _("attempting automatic merge of %s: "), cfgfile);
+			if( conffiledb_automerge(pkg, cfgfile) == 0){
+				fprintf(stderr, _("success.\n"));
+				/* so let's just pretend they said "keep my 
+				 * version" ;) */
+				cc = 'n';
+			} else {
+				fprintf(stderr, _("failed.\n"));
+			}
+		}
+
 		if (cc == 'z')
 			suspend();
 	} while (!strchr("yino", cc));
-- 
1.6.4.3


Reply to: