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;
+}

Reply via email to