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

[PATCH 6/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 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/conffiles.c |   58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/conffiles.h |    5 ++++
 src/configure.c |   14 ++++++++++++-
 4 files changed, 78 insertions(+), 1 deletions(-)

diff --git a/lib/dpkg/dpkg.h b/lib/dpkg/dpkg.h
index 335a826..9205b24 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/conffiles.c b/src/conffiles.c
index fe426d7..1a57ae4 100644
--- a/src/conffiles.c
+++ b/src/conffiles.c
@@ -8,11 +8,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"
 
@@ -173,6 +175,62 @@ conff_db_remove(const char *pkg, conff_flag f)
 	free(cfgdb);
 }
 
+int conff_automerge(const char *pkg, const char *pathname)
+{
+	int res, merge_fd;
+	size_t cmd_sz = 0, merge_output_fname_sz = 0;
+	char *cmdbuf = NULL, *cnfold = NULL, *cnfnew = NULL;
+	char *merge_output_fname;
+
+	/* i.e. <path> + ".dpkg-merge" + '\0' */
+	merge_output_fname_sz = strlen(pathname) + strlen(DPKGMERGEEXT) + 1;
+	merge_output_fname = m_malloc(merge_output_fname_sz);
+	sprintf(merge_output_fname, "%s%s", pathname, DPKGMERGEEXT);
+
+	/* the 'old' version is actually the current dist version in the db */
+	cnfold = conff_db_path(pkg, pathname, conff_db_cur);
+	cnfnew = conff_db_path(pkg, pathname, conff_db_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("conff_automerge: can not open %s for merge output",
+		        merge_output_fname);
+	else if (close(merge_fd))
+		ohshite("conff_automerge: close failed");
+
+	/* copy file permissions from the original file */
+	file_copyfileperm(pathname, merge_output_fname);
+
+	/* DIFF3 + " -m " + new + " " + old + " " + cur + " > " + out + '\0' */
+	cmd_sz = strlen(DIFF3) + 4 + strlen(cnfnew) + 1 + strlen(cnfold) + 1 + 
+	         strlen(pathname) + 3 + merge_output_fname_sz + 1;
+	cmdbuf = m_malloc(cmd_sz);
+	snprintf(cmdbuf, cmd_sz, "%s -m %s %s %s > %s", DIFF3, cnfnew, cnfold, 
+	         pathname, 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, pathname) == -1)
+			ohshite("conff_automerge: can not rename %s", 
+			        merge_output_fname);
+	} 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(cnfnew);
+	free(cnfold);
+
+	return res;
+}
+
 /*
  * vim: noet ts=8 sw=8
  */
diff --git a/src/conffiles.h b/src/conffiles.h
index 9cf6e5b..1e76a72 100644
--- a/src/conffiles.h
+++ b/src/conffiles.h
@@ -46,5 +46,10 @@ void conff_db_remove(const char *pkg, conff_flag f);
  */
 void conff_commit_new(const char *pkg);
 
+/* attempt to automagically merge the 3-way delta for conffile at pathname
+ * passes the return value of diff3, aka nonzero on merge failure
+ */
+int conff_automerge(const char *pkg, const char *pathname);
+
 #endif /* CONFFILES_H */
 
diff --git a/src/configure.c b/src/configure.c
index 8b7b424..8393e7a 100644
--- a/src/configure.c
+++ b/src/configure.c
@@ -636,6 +636,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)
@@ -646,7 +647,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]") :
@@ -680,6 +681,17 @@ 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( conff_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: