[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: