diff -Naur dpkg-1.10.25/include/dpkg.h.in dpkg-1.10.25-new/include/dpkg.h.in --- dpkg-1.10.25/include/dpkg.h.in 2004-11-11 22:10:03.000000000 +0200 +++ dpkg-1.10.25-new/include/dpkg.h.in 2004-11-19 15:34:13.000000000 +0200 @@ -97,6 +97,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 -Naur dpkg-1.10.25/main/configure.c dpkg-1.10.25-new/main/configure.c --- dpkg-1.10.25/main/configure.c 2004-11-11 22:10:03.000000000 +0200 +++ dpkg-1.10.25-new/main/configure.c 2004-11-19 15:34:13.000000000 +0200 @@ -47,8 +47,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); @@ -430,8 +433,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='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 */ @@ -439,18 +463,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) @@ -460,10 +490,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")); @@ -473,10 +511,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 @@ -514,17 +555,112 @@ } +/* 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; +} + + /* Select what to do with a configuration file. */ static enum conffopt promptconfaction(const char* cfgfile, const char* realold, 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"); + } + do { fprintf(stderr, _("\nConfiguration file `%s'"), cfgfile); if (strcmp(cfgfile, realold)) @@ -573,13 +709,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")); @@ -588,8 +724,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]")); @@ -614,8 +750,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)); diff -Naur dpkg-1.10.25/man/en/dpkg.8.sgml dpkg-1.10.25-new/man/en/dpkg.8.sgml --- dpkg-1.10.25/man/en/dpkg.8.sgml 2004-11-11 22:10:04.000000000 +0200 +++ dpkg-1.10.25-new/man/en/dpkg.8.sgml 2004-11-19 15:34:13.000000000 +0200 @@ -1403,6 +1403,15 @@ + MERGE2 + + + The program dpkg will execute when the user wants + to merge updated conffiles. + + + + COLUMNS