Here's the up-to-date patch I promised to attach. To test it, install Imediff2 (or install sdiff and export MERGE2=sdiff) and then update some package with modified conffile(s).
If you don't have one handy and/or don't want to mess up any real packages' configs, you could try my dummy test package available at http://elonen.iki.fi/code/unofficial-debs/dpkg-merge/merge-test-package.tgz - Jarno
diff -Nadur dpkg-1.13.10/lib/dpkg.h dpkg-1.13.10+merge2/lib/dpkg.h --- dpkg-1.13.10/lib/dpkg.h 2005-06-06 07:07:12.000000000 +0300 +++ dpkg-1.13.10+merge2/lib/dpkg.h 2005-07-11 18:34:50.608996983 +0300 @@ -108,6 +108,8 @@ #define DEFAULTSHELL "sh" #define PAGERENV "PAGER" #define DEFAULTPAGER "pager" +#define MERGE2ENV "MERGE2" +#define DEFAULTMERGE2 "/usr/bin/merge2" #define IMETHODMAXLEN 50 #define IOPTIONMAXLEN IMETHODMAXLEN diff -Nadur dpkg-1.13.10/man/C/dpkg.1 dpkg-1.13.10+merge2/man/C/dpkg.1 --- dpkg-1.13.10/man/C/dpkg.1 2005-06-06 07:07:12.000000000 +0300 +++ dpkg-1.13.10+merge2/man/C/dpkg.1 2005-07-11 18:38:45.151853120 +0300 @@ -527,6 +527,8 @@ .B SHELL The program \fBdpkg\fP will execute while starting a new shell. .TP +.B MERGE2 +The program \fBdpkg\fB will execute when the user wants to merge updated conffiles. .B COLUMNS Sets the number of columns \fBdpkg\fP should use when displaying formatted text. Currently only used by \-l. diff -Nadur dpkg-1.13.10/src/configure.c dpkg-1.13.10+merge2/src/configure.c --- dpkg-1.13.10/src/configure.c 2005-06-10 12:38:12.000000000 +0300 +++ dpkg-1.13.10+merge2/src/configure.c 2005-07-11 18:45:40.978166158 +0300 @@ -48,8 +48,11 @@ static void md5hash(struct pkginfo *pkg, char **hashbuf, const char *fn); static void copyfileperm(const char* source, const char* target); +static char* quotemeta( const char* str ); static void showdiff(const char* old, const char* new); -static void suspend(void); +static const char* mergecommand(void); +static char mergefiles(const char* old, const char* new); +static void suspend(const char* old, const char* new); static enum conffopt promptconfaction(const char* cfgfile, const char* realold, const char* realnew, int useredited, int distedited, enum conffopt what); @@ -434,8 +437,29 @@ ohshite(_("unable to set mode of new dist conffile `%.250s'"), target); } +/* Returns a newly malloc()ated string with shell meta characters + escaped. NOTE: you have to free() the resulting string by yourself. */ +char* quotemeta( const char* str ) +{ + char *res, *dst; + unsigned i; + unsigned len = strlen(str); + res = (char*)malloc( len*2 + 1 ); + if ( !res ) + ohshite(_("failed allocate a string")); + dst = res; + for ( i=0; i<strlen(str); ++i ) { + if ( !((str[i]>='0' && str[i]<='9') || + (str[i]>='A' && str[i]<='Z') || + (str[i]>='a' && str[i]<='z'))) + *(dst++) = '\\'; + *(dst++) = str[i]; + } + *dst = 0; + return res; +} /* Show a diff between two files */ @@ -443,18 +467,24 @@ int pid; int r; int status; + struct sigaction oldintac, newintac; if (!(pid=m_fork())) { /* Child process */ const char* p; /* pager */ const char* s; /* shell */ char cmdbuf[1024]; /* command to run */ + char *old_escaped, *new_escaped; p=getenv(PAGERENV); if (!p || !*p) p=DEFAULTPAGER; - sprintf(cmdbuf, DIFF " -Nu %.250s %.250s | %.250s", old, new, p); + old_escaped = quotemeta( old ); + new_escaped = quotemeta( new ); + sprintf(cmdbuf, DIFF " -Nu %.250s %.250s | %.250s", old_escaped, new_escaped, p); + free( new_escaped ); + free( old_escaped ); s=getenv(SHELLENV); if (!s || !*s) @@ -464,10 +494,18 @@ ohshite(_("failed to run %s (%.250s)"), DIFF, cmdbuf); } + /* prevent dpkg from terminating even if the child process is aborted */ + newintac.sa_handler = SIG_IGN; + sigemptyset (&newintac.sa_mask); + newintac.sa_flags = 0; + sigaction (SIGINT, &newintac, &oldintac); + /* Parent process */ while (((r=waitpid(pid,&status,0))==-1) && (errno==EINTR)) ; + sigaction (SIGINT, &oldintac, NULL); + if (r!=pid) { onerr_abort++; ohshite(_("wait for shell failed")); @@ -477,10 +515,13 @@ /* Suspend dpkg temporarily */ -static void suspend(void) { +static void suspend(const char* old, const char* new) { const char* s; int pid; + fprintf(stderr, + _("\nPackage distributor's new version is '%.250s'.\n"), new ); + s= getenv(NOJOBCTRLSTOPENV); if (s && *s) { /* Do not job control to suspend but fork and start a new shell @@ -524,11 +565,18 @@ const char* realnew, int useredited, int distedited, enum conffopt what) { const char *s; + const char *mergestr = ""; + const char *mergeoptstr = ""; int c, cc; if (!(what&cfof_prompt)) return what; + if (mergecommand()) { + mergestr = _(" M : merge changes\n"); + mergeoptstr = _("/M"); + } + /* if there is a status pipe, send conffile-prompt there */ if (status_pipes) { static struct varbuf *status= NULL; @@ -597,13 +645,13 @@ } } - fprintf(stderr, _(" What would you like to do about it ? Your options are:\n" " 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" - " Z : background this process to examine the situation\n")); + "%s" + " Z : background this process to examine the situation\n"), mergestr); if (what & cfof_keep) fprintf(stderr, _(" The default action is to keep your current version.\n")); @@ -612,8 +660,8 @@ s= strrchr(cfgfile,'/'); if (!s || !*++s) s= cfgfile; - fprintf(stderr, "*** %s (Y/I/N/O/D/Z) %s ? ", - s, + fprintf(stderr, "*** %s (Y/I/N/O/D%s/Z) %s ? ", + s, mergeoptstr, (what & cfof_keep) ? _("[default=N]") : (what & cfof_install) ? _("[default=Y]") : _("[no default]")); @@ -638,8 +686,15 @@ if (cc == 'd') showdiff(realold, realnew); + if (mergecommand() && cc == 'm') { + if (mergefiles(realold, realnew)) + cc= 'y'; + else + fprintf(stderr, _("\n Command failed or was aborted. Changes were NOT merged.\n")); + } + if (cc == 'z') - suspend(); + suspend(realold, realnew); } while (!strchr("yino",cc)); @@ -663,3 +718,90 @@ return what; } + +/* Returns merge command if merge option is available and NULL otherwise + */ +static const char* mergecommand(void) { + struct stat stab; + const char* m; + m = getenv(MERGE2ENV); + if (m && *m) + return m; + else if (!stat(DEFAULTMERGE2,&stab)) + return DEFAULTMERGE2; + return NULL; +} + +/* Let the user merge given files. Returns 1 if the + * 'new' was overwritten with a merged file and 0 otherwise + */ +static char mergefiles(const char* old, const char* new) { + int pid; + int r; + int status; + struct stat st; + char merged[260]; /* name for merged file */ + char cmdbuf[1024]; /* command to run */ + const char* m; /* diff2 merge command */ + const char* s; /* shell */ + char *old_escaped, *new_escaped, *merged_escaped; + struct sigaction oldintac, newintac; + + sprintf(merged, "%.250s-merged", new); + + s=getenv(SHELLENV); + if (!s || !*s) + s=DEFAULTSHELL; + + if (!(pid=m_fork())) { + /* Child process */ + m=mergecommand(); + if (!m || !*m) + ohshite("BUG in dpkg: merge command was empty but" + "mergefiles() was called anyway."); + + old_escaped = quotemeta( old ); + merged_escaped = quotemeta( merged ); + new_escaped = quotemeta( new ); + sprintf(cmdbuf, "%.250s -o %.250s %.250s %.250s", + m, merged_escaped, old_escaped, new_escaped); + free(new_escaped); + free(merged_escaped); + free(old_escaped); + + execlp(s,s,"-c", cmdbuf, NULL); + ohshite(_("failed to run merge command (%.250s)"), cmdbuf); + } + + /* prevent dpkg from terminating even if the child process is aborted */ + newintac.sa_handler = SIG_IGN; + sigemptyset (&newintac.sa_mask); + newintac.sa_flags = 0; + sigaction (SIGINT, &newintac, &oldintac); + + /* Parent process */ + while (((r=waitpid(pid,&status,0))==-1) && (errno==EINTR)) + ; + + sigaction (SIGINT, &oldintac, NULL); + + if (r!=pid) { + onerr_abort++; + ohshite(_("wait for shell failed")); + } + + /* replace new conffile if merge command was succesful */ + if (status==0 && stat(merged, &st) == 0 && (S_ISREG(st.st_mode)) && st.st_size) { + copyfileperm(old, merged); + if ((unlink(new) && errno != ENOENT) || (rename(merged,new))) { + fprintf(stderr, _(" Merge failed: unable to write output file " + "`%.250s' over '%.250s': %s"), + merged, new, strerror(errno)); + return 0; + } + return 1; + } + + unlink(merged); /* don't worry if this fails, the file may not even exist */ + return 0; +}