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

[PATCH] implement m option for conffile merging during conflict resolution



this is still kinda proof-of-conceptish, most notably
two things need to be resolved before finalizing:

- ensure that the conffile db references the right paths
  during all accesses (i.e. the packaged conffile vs the
  "resolved" conffile name).
- decide on the behavior/interface.  currently if the
  merge is succesful it goes ahead and copies it over
  and pretends that the user said n/k.  maybe merging
  should involve an extra inspection step.
---
 lib/dpkg.h      |    1 +
 src/conffiles.c |   51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/conffiles.h |    6 ++++++
 src/configure.c |   19 ++++++++++++++++---
 4 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/lib/dpkg.h b/lib/dpkg.h
index de46930..a9e00f8 100644
--- a/lib/dpkg.h
+++ b/lib/dpkg.h
@@ -145,6 +145,7 @@
 #define FIND		"find"
 #define SHELL		"sh"
 #define DIFF		"diff"
+#define DIFF3		"diff3"
 
 #define SHELLENVIR	"SHELL"
 
diff --git a/src/conffiles.c b/src/conffiles.c
index fd9b47d..fd444ec 100644
--- a/src/conffiles.c
+++ b/src/conffiles.c
@@ -143,3 +143,54 @@ void conff_db_remove(const char *pkg, conff_flag f){
   ensure_pathname_nonexisting(cfgdb);
   free(cfgdb);
 }
+
+int conff_automerge(const char *pkg, const char *pathname){
+  int res, tmpfd, mrgfd;
+  char tmpfn[] = "dpkg-merge.XXXXXX"; /* temporary file name */
+  size_t cmdsz=0;
+  char *cmdbuf = NULL, *cnfold = NULL, *cnfnew = NULL;
+
+  /* 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);
+
+  if( (tmpfd = mkstemp(tmpfn)) == -1 ){
+    ohshite("conff_automerge: mkstemp failed");
+  }
+  if( fchmod(tmpfd, (S_IRUSR|S_IWUSR)) || close(tmpfd) ) {
+    ohshite("conff_automerge: chmod/close failed");
+  }
+
+  /* DIFF3 + " -m " + new + " " + old + " " + cur + " > " + tmpfile + '\0' */
+  cmdsz = strlen(DIFF3) + 4 + strlen(cnfnew) + 1 + strlen(cnfold) 
+          + 1 + strlen(pathname) + 3 + strlen(tmpfn) + 1;
+  cmdbuf = m_malloc(cmdsz);
+  snprintf(cmdbuf, cmdsz, "%s -m %s %s %s > %s", 
+           DIFF3, cnfnew, cnfold, pathname, tmpfn);
+
+  /* let's give it a go! */
+  if( (res = system(cmdbuf)) == 0 ){
+    /* copy the merge result by fd->fd, to respect symlinks */
+    if( (tmpfd = open(tmpfn, O_RDONLY)) == -1 ) {
+      ohshite("conff_automerge: re-opening merge tmpfile %s failed", tmpfn);
+    }
+    if( (mrgfd = open(pathname, O_WRONLY)) == -1 ) {
+      ohshite("conff_automerge: opening %s for merging failed", pathname);
+    }
+    fd_fd_copy(tmpfd, mrgfd, -1, "merging conffile %s", pathname);
+    if( close(tmpfd) || close(mrgfd) ){
+      ohshite("conff_automerge: closing merge files for %s failed", pathname);
+    }
+  }
+
+  if(unlink(tmpfn)){
+    ohshite("conff_automerge: unlink merge tmpfile failed");
+  }
+
+  free(cmdbuf);
+  free(cnfnew);
+  free(cnfold);
+
+  return res;
+}
+
diff --git a/src/conffiles.h b/src/conffiles.h
index 2de5eed..484034f 100644
--- a/src/conffiles.h
+++ b/src/conffiles.h
@@ -44,5 +44,11 @@ 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
+ */
+/* XXX remember to check for symlink corner cases */
+int conff_automerge(const char *pkg, const char *pathname);
+
 #endif /* CONFFILES_H */
 
diff --git a/src/configure.c b/src/configure.c
index e0afb6d..901f511 100644
--- a/src/configure.c
+++ b/src/configure.c
@@ -52,7 +52,7 @@ static void md5hash(struct pkginfo *pkg, char **hashbuf, const char *fn);
 static void copyfileperm(const char* source, const char* target);
 static void showdiff(const char* old, const char* new);
 static void suspend(void);
-static enum conffopt promptconfaction(const char* cfgfile, const char* realold,
+static enum conffopt promptconfaction(const char* pkg, const char* cfgfile, const char* realold,
 		const char* realnew, int useredited, int distedited,
 		enum conffopt what);
 
@@ -224,7 +224,7 @@ void deferred_configure(struct pkginfo *pkg) {
 					"deferred_configure `%s' (= `%s') useredited=%d distedited=%d what=%o",
 					conff->name, cdr.buf, useredited, distedited, what);
 
-			what=promptconfaction(conff->name, cdr.buf, cdr2.buf, useredited, distedited, what);
+			what=promptconfaction(pkg->name, conff->name, cdr.buf, cdr2.buf, useredited, distedited, what);
 
 			switch (what & ~(cfof_isnew|cfof_userrmd)) {
 				case cfo_keep | cfof_backup:
@@ -526,7 +526,7 @@ static void suspend(void) {
 
 /* Select what to do with a configuration file.
  */
-static enum conffopt promptconfaction(const char* cfgfile, const char* realold,
+static enum conffopt promptconfaction(const char* pkg, const char* cfgfile, const char* realold,
 		const char* realnew, int useredited, int distedited,
 		enum conffopt what) {
 	const char *s;
@@ -615,6 +615,7 @@ static enum conffopt promptconfaction(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)
@@ -650,6 +651,18 @@ static enum conffopt promptconfaction(const char* cfgfile, const char* realold,
 		if (cc == 'd')
 			showdiff(realold, realnew);
 
+		if (cc == 'm') {
+			int res;
+			printf("automagic merging of %s aka %s\n", cfgfile, realold);
+			if( conff_automerge(pkg, cfgfile) == 0){
+			    printf("yay! merge worked :)\n");
+				/* so let's just pretend they said "keep my version" ;) */
+				cc = 'n';
+			} else {
+				printf("boo! merge failed :(\n");
+			}
+		}
+
 		if (cc == 'z')
 			suspend();
 
-- 
1.5.4.3


Reply to: