xref: /freebsd/contrib/diff/src/sdiff.c (revision 8abfbe5a79b19bb95430f574d970843607f5809c)
1  /* sdiff - side-by-side merge of file differences
2  
3     Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 2001, 2002, 2004
4     Free Software Foundation, Inc.
5  
6     This file is part of GNU DIFF.
7  
8     GNU DIFF is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2, or (at your option)
11     any later version.
12  
13     GNU DIFF is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16     See the GNU General Public License for more details.
17  
18     You should have received a copy of the GNU General Public License
19     along with this program; see the file COPYING.
20     If not, write to the Free Software Foundation,
21     59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22  
23  #include "system.h"
24  #include "paths.h"
25  
26  #include <stdio.h>
27  #include <unlocked-io.h>
28  
29  #include <c-stack.h>
30  #include <dirname.h>
31  #include <error.h>
32  #include <exit.h>
33  #include <exitfail.h>
34  #include <file-type.h>
35  #include <getopt.h>
36  #include <quotesys.h>
37  #include <version-etc.h>
38  #include <xalloc.h>
39  
40  /* Size of chunks read from files which must be parsed into lines.  */
41  #define SDIFF_BUFSIZE ((size_t) 65536)
42  
43  char *program_name;
44  
45  static char const *editor_program = DEFAULT_EDITOR_PROGRAM;
46  static char const **diffargv;
47  
48  static char * volatile tmpname;
49  static FILE *tmp;
50  
51  #if HAVE_WORKING_FORK || HAVE_WORKING_VFORK
52  static pid_t volatile diffpid;
53  #endif
54  
55  struct line_filter;
56  
57  static void catchsig (int);
58  static bool edit (struct line_filter *, char const *, lin, lin, struct line_filter *, char const *, lin, lin, FILE *);
59  static bool interact (struct line_filter *, struct line_filter *, char const *, struct line_filter *, char const *, FILE *);
60  static void checksigs (void);
61  static void diffarg (char const *);
62  static void fatal (char const *) __attribute__((noreturn));
63  static void perror_fatal (char const *) __attribute__((noreturn));
64  static void trapsigs (void);
65  static void untrapsig (int);
66  
67  #define NUM_SIGS (sizeof sigs / sizeof *sigs)
68  static int const sigs[] = {
69  #ifdef SIGHUP
70         SIGHUP,
71  #endif
72  #ifdef SIGQUIT
73         SIGQUIT,
74  #endif
75  #ifdef SIGTERM
76         SIGTERM,
77  #endif
78  #ifdef SIGXCPU
79         SIGXCPU,
80  #endif
81  #ifdef SIGXFSZ
82         SIGXFSZ,
83  #endif
84         SIGINT,
85         SIGPIPE
86  };
87  #define handler_index_of_SIGINT (NUM_SIGS - 2)
88  #define handler_index_of_SIGPIPE (NUM_SIGS - 1)
89  
90  #if HAVE_SIGACTION
91    /* Prefer `sigaction' if available, since `signal' can lose signals.  */
92    static struct sigaction initial_action[NUM_SIGS];
93  # define initial_handler(i) (initial_action[i].sa_handler)
94    static void signal_handler (int, void (*) (int));
95  #else
96    static void (*initial_action[NUM_SIGS]) ();
97  # define initial_handler(i) (initial_action[i])
98  # define signal_handler(sig, handler) signal (sig, handler)
99  #endif
100  
101  #if ! HAVE_SIGPROCMASK
102  # define sigset_t int
103  # define sigemptyset(s) (*(s) = 0)
104  # ifndef sigmask
105  #  define sigmask(sig) (1 << ((sig) - 1))
106  # endif
107  # define sigaddset(s, sig) (*(s) |= sigmask (sig))
108  # ifndef SIG_BLOCK
109  #  define SIG_BLOCK 0
110  # endif
111  # ifndef SIG_SETMASK
112  #  define SIG_SETMASK (! SIG_BLOCK)
113  # endif
114  # define sigprocmask(how, n, o) \
115      ((how) == SIG_BLOCK ? *(o) = sigblock (*(n)) : sigsetmask (*(n)))
116  #endif
117  
118  static bool diraccess (char const *);
119  static int temporary_file (void);
120  
121  /* Options: */
122  
123  /* Name of output file if -o specified.  */
124  static char const *output;
125  
126  /* Do not print common lines.  */
127  static bool suppress_common_lines;
128  
129  /* Value for the long option that does not have single-letter equivalents.  */
130  enum
131  {
132    DIFF_PROGRAM_OPTION = CHAR_MAX + 1,
133    HELP_OPTION,
134    STRIP_TRAILING_CR_OPTION,
135    TABSIZE_OPTION
136  };
137  
138  static struct option const longopts[] =
139  {
140    {"diff-program", 1, 0, DIFF_PROGRAM_OPTION},
141    {"expand-tabs", 0, 0, 't'},
142    {"help", 0, 0, HELP_OPTION},
143    {"ignore-all-space", 0, 0, 'W'}, /* swap W and w for historical reasons */
144    {"ignore-blank-lines", 0, 0, 'B'},
145    {"ignore-case", 0, 0, 'i'},
146    {"ignore-matching-lines", 1, 0, 'I'},
147    {"ignore-space-change", 0, 0, 'b'},
148    {"ignore-tab-expansion", 0, 0, 'E'},
149    {"left-column", 0, 0, 'l'},
150    {"minimal", 0, 0, 'd'},
151    {"output", 1, 0, 'o'},
152    {"speed-large-files", 0, 0, 'H'},
153    {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
154    {"suppress-common-lines", 0, 0, 's'},
155    {"tabsize", 1, 0, TABSIZE_OPTION},
156    {"text", 0, 0, 'a'},
157    {"version", 0, 0, 'v'},
158    {"width", 1, 0, 'w'},
159    {0, 0, 0, 0}
160  };
161  
162  static void try_help (char const *, char const *) __attribute__((noreturn));
163  static void
164  try_help (char const *reason_msgid, char const *operand)
165  {
166    if (reason_msgid)
167      error (0, 0, _(reason_msgid), operand);
168    error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
169  	 program_name);
170    abort ();
171  }
172  
173  static void
174  check_stdout (void)
175  {
176    if (ferror (stdout))
177      fatal ("write failed");
178    else if (fclose (stdout) != 0)
179      perror_fatal (_("standard output"));
180  }
181  
182  static char const * const option_help_msgid[] = {
183    N_("-o FILE  --output=FILE  Operate interactively, sending output to FILE."),
184    "",
185    N_("-i  --ignore-case  Consider upper- and lower-case to be the same."),
186    N_("-E  --ignore-tab-expansion  Ignore changes due to tab expansion."),
187    N_("-b  --ignore-space-change  Ignore changes in the amount of white space."),
188    N_("-W  --ignore-all-space  Ignore all white space."),
189    N_("-B  --ignore-blank-lines  Ignore changes whose lines are all blank."),
190    N_("-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE."),
191    N_("--strip-trailing-cr  Strip trailing carriage return on input."),
192    N_("-a  --text  Treat all files as text."),
193    "",
194    N_("-w NUM  --width=NUM  Output at most NUM (default 130) print columns."),
195    N_("-l  --left-column  Output only the left column of common lines."),
196    N_("-s  --suppress-common-lines  Do not output common lines."),
197    "",
198    N_("-t  --expand-tabs  Expand tabs to spaces in output."),
199    N_("--tabsize=NUM  Tab stops are every NUM (default 8) print columns."),
200    "",
201    N_("-d  --minimal  Try hard to find a smaller set of changes."),
202    N_("-H  --speed-large-files  Assume large files and many scattered small changes."),
203    N_("--diff-program=PROGRAM  Use PROGRAM to compare files."),
204    "",
205    N_("-v  --version  Output version info."),
206    N_("--help  Output this help."),
207    0
208  };
209  
210  static void
211  usage (void)
212  {
213    char const * const *p;
214  
215    printf (_("Usage: %s [OPTION]... FILE1 FILE2\n"), program_name);
216    printf ("%s\n\n", _("Side-by-side merge of file differences."));
217    for (p = option_help_msgid;  *p;  p++)
218      if (**p)
219        printf ("  %s\n", _(*p));
220      else
221        putchar ('\n');
222    printf ("\n%s\n%s\n\n%s\n",
223  	  _("If a FILE is `-', read standard input."),
224  	  _("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."),
225  	  _("Report bugs to <bug-gnu-utils@gnu.org>."));
226  }
227  
228  /* Clean up after a signal or other failure.  This function is
229     async-signal-safe.  */
230  static void
231  cleanup (int signo __attribute__((unused)))
232  {
233  #if HAVE_WORKING_FORK || HAVE_WORKING_VFORK
234    if (0 < diffpid)
235      kill (diffpid, SIGPIPE);
236  #endif
237    if (tmpname)
238      unlink (tmpname);
239  }
240  
241  static void exiterr (void) __attribute__((noreturn));
242  static void
243  exiterr (void)
244  {
245    cleanup (0);
246    untrapsig (0);
247    checksigs ();
248    exit (EXIT_TROUBLE);
249  }
250  
251  static void
252  fatal (char const *msgid)
253  {
254    error (0, 0, "%s", _(msgid));
255    exiterr ();
256  }
257  
258  static void
259  perror_fatal (char const *msg)
260  {
261    int e = errno;
262    checksigs ();
263    error (0, e, "%s", msg);
264    exiterr ();
265  }
266  
267  static void
268  check_child_status (int werrno, int wstatus, int max_ok_status,
269  		    char const *subsidiary_program)
270  {
271    int status = (! werrno && WIFEXITED (wstatus)
272  		? WEXITSTATUS (wstatus)
273  		: INT_MAX);
274  
275    if (max_ok_status < status)
276      {
277        error (0, werrno,
278  	     _(status == 126
279  	       ? "subsidiary program `%s' could not be invoked"
280  	       : status == 127
281  	       ? "subsidiary program `%s' not found"
282  	       : status == INT_MAX
283  	       ? "subsidiary program `%s' failed"
284  	       : "subsidiary program `%s' failed (exit status %d)"),
285  	     subsidiary_program, status);
286        exiterr ();
287      }
288  }
289  
290  static FILE *
291  ck_fopen (char const *fname, char const *type)
292  {
293    FILE *r = fopen (fname, type);
294    if (! r)
295      perror_fatal (fname);
296    return r;
297  }
298  
299  static void
300  ck_fclose (FILE *f)
301  {
302    if (fclose (f))
303      perror_fatal ("fclose");
304  }
305  
306  static size_t
307  ck_fread (char *buf, size_t size, FILE *f)
308  {
309    size_t r = fread (buf, sizeof (char), size, f);
310    if (r == 0 && ferror (f))
311      perror_fatal (_("read failed"));
312    return r;
313  }
314  
315  static void
316  ck_fwrite (char const *buf, size_t size, FILE *f)
317  {
318    if (fwrite (buf, sizeof (char), size, f) != size)
319      perror_fatal (_("write failed"));
320  }
321  
322  static void
323  ck_fflush (FILE *f)
324  {
325    if (fflush (f) != 0)
326      perror_fatal (_("write failed"));
327  }
328  
329  static char const *
330  expand_name (char *name, bool is_dir, char const *other_name)
331  {
332    if (strcmp (name, "-") == 0)
333      fatal ("cannot interactively merge standard input");
334    if (! is_dir)
335      return name;
336    else
337      {
338        /* Yield NAME/BASE, where BASE is OTHER_NAME's basename.  */
339        char const *base = base_name (other_name);
340        size_t namelen = strlen (name), baselen = strlen (base);
341        bool insert_slash = *base_name (name) && name[namelen - 1] != '/';
342        char *r = xmalloc (namelen + insert_slash + baselen + 1);
343        memcpy (r, name, namelen);
344        r[namelen] = '/';
345        memcpy (r + namelen + insert_slash, base, baselen + 1);
346        return r;
347      }
348  }
349  
350  struct line_filter {
351    FILE *infile;
352    char *bufpos;
353    char *buffer;
354    char *buflim;
355  };
356  
357  static void
358  lf_init (struct line_filter *lf, FILE *infile)
359  {
360    lf->infile = infile;
361    lf->bufpos = lf->buffer = lf->buflim = xmalloc (SDIFF_BUFSIZE + 1);
362    lf->buflim[0] = '\n';
363  }
364  
365  /* Fill an exhausted line_filter buffer from its INFILE */
366  static size_t
367  lf_refill (struct line_filter *lf)
368  {
369    size_t s = ck_fread (lf->buffer, SDIFF_BUFSIZE, lf->infile);
370    lf->bufpos = lf->buffer;
371    lf->buflim = lf->buffer + s;
372    lf->buflim[0] = '\n';
373    checksigs ();
374    return s;
375  }
376  
377  /* Advance LINES on LF's infile, copying lines to OUTFILE */
378  static void
379  lf_copy (struct line_filter *lf, lin lines, FILE *outfile)
380  {
381    char *start = lf->bufpos;
382  
383    while (lines)
384      {
385        lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos);
386        if (! lf->bufpos)
387  	{
388  	  ck_fwrite (start, lf->buflim - start, outfile);
389  	  if (! lf_refill (lf))
390  	    return;
391  	  start = lf->bufpos;
392  	}
393        else
394  	{
395  	  --lines;
396  	  ++lf->bufpos;
397  	}
398      }
399  
400    ck_fwrite (start, lf->bufpos - start, outfile);
401  }
402  
403  /* Advance LINES on LF's infile without doing output */
404  static void
405  lf_skip (struct line_filter *lf, lin lines)
406  {
407    while (lines)
408      {
409        lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos);
410        if (! lf->bufpos)
411  	{
412  	  if (! lf_refill (lf))
413  	    break;
414  	}
415        else
416  	{
417  	  --lines;
418  	  ++lf->bufpos;
419  	}
420      }
421  }
422  
423  /* Snarf a line into a buffer.  Return EOF if EOF, 0 if error, 1 if OK.  */
424  static int
425  lf_snarf (struct line_filter *lf, char *buffer, size_t bufsize)
426  {
427    for (;;)
428      {
429        char *start = lf->bufpos;
430        char *next = (char *) memchr (start, '\n', lf->buflim + 1 - start);
431        size_t s = next - start;
432        if (bufsize <= s)
433  	return 0;
434        memcpy (buffer, start, s);
435        if (next < lf->buflim)
436  	{
437  	  buffer[s] = 0;
438  	  lf->bufpos = next + 1;
439  	  return 1;
440  	}
441        if (! lf_refill (lf))
442  	return s ? 0 : EOF;
443        buffer += s;
444        bufsize -= s;
445      }
446  }
447  
448  int
449  main (int argc, char *argv[])
450  {
451    int opt;
452    char const *prog;
453  
454    exit_failure = EXIT_TROUBLE;
455    initialize_main (&argc, &argv);
456    program_name = argv[0];
457    setlocale (LC_ALL, "");
458    bindtextdomain (PACKAGE, LOCALEDIR);
459    textdomain (PACKAGE);
460    c_stack_action (cleanup);
461  
462    prog = getenv ("EDITOR");
463    if (prog)
464      editor_program = prog;
465  
466    diffarg (DEFAULT_DIFF_PROGRAM);
467  
468    /* parse command line args */
469    while ((opt = getopt_long (argc, argv, "abBdEHiI:lo:stvw:W", longopts, 0))
470  	 != -1)
471      {
472        switch (opt)
473  	{
474  	case 'a':
475  	  diffarg ("-a");
476  	  break;
477  
478  	case 'b':
479  	  diffarg ("-b");
480  	  break;
481  
482  	case 'B':
483  	  diffarg ("-B");
484  	  break;
485  
486  	case 'd':
487  	  diffarg ("-d");
488  	  break;
489  
490  	case 'E':
491  	  diffarg ("-E");
492  	  break;
493  
494  	case 'H':
495  	  diffarg ("-H");
496  	  break;
497  
498  	case 'i':
499  	  diffarg ("-i");
500  	  break;
501  
502  	case 'I':
503  	  diffarg ("-I");
504  	  diffarg (optarg);
505  	  break;
506  
507  	case 'l':
508  	  diffarg ("--left-column");
509  	  break;
510  
511  	case 'o':
512  	  output = optarg;
513  	  break;
514  
515  	case 's':
516  	  suppress_common_lines = true;
517  	  break;
518  
519  	case 't':
520  	  diffarg ("-t");
521  	  break;
522  
523  	case 'v':
524  	  version_etc (stdout, "sdiff", PACKAGE_NAME, PACKAGE_VERSION,
525  		       "Thomas Lord", (char *) 0);
526  	  check_stdout ();
527  	  return EXIT_SUCCESS;
528  
529  	case 'w':
530  	  diffarg ("-W");
531  	  diffarg (optarg);
532  	  break;
533  
534  	case 'W':
535  	  diffarg ("-w");
536  	  break;
537  
538  	case DIFF_PROGRAM_OPTION:
539  	  diffargv[0] = optarg;
540  	  break;
541  
542  	case HELP_OPTION:
543  	  usage ();
544  	  check_stdout ();
545  	  return EXIT_SUCCESS;
546  
547  	case STRIP_TRAILING_CR_OPTION:
548  	  diffarg ("--strip-trailing-cr");
549  	  break;
550  
551  	case TABSIZE_OPTION:
552  	  diffarg ("--tabsize");
553  	  diffarg (optarg);
554  	  break;
555  
556  	default:
557  	  try_help (0, 0);
558  	}
559      }
560  
561    if (argc - optind != 2)
562      {
563        if (argc - optind < 2)
564  	try_help ("missing operand after `%s'", argv[argc - 1]);
565        else
566  	try_help ("extra operand `%s'", argv[optind + 2]);
567      }
568  
569    if (! output)
570      {
571        /* easy case: diff does everything for us */
572        if (suppress_common_lines)
573  	diffarg ("--suppress-common-lines");
574        diffarg ("-y");
575        diffarg ("--");
576        diffarg (argv[optind]);
577        diffarg (argv[optind + 1]);
578        diffarg (0);
579        execvp (diffargv[0], (char **) diffargv);
580        perror_fatal (diffargv[0]);
581      }
582    else
583      {
584        char const *lname, *rname;
585        FILE *left, *right, *out, *diffout;
586        bool interact_ok;
587        struct line_filter lfilt;
588        struct line_filter rfilt;
589        struct line_filter diff_filt;
590        bool leftdir = diraccess (argv[optind]);
591        bool rightdir = diraccess (argv[optind + 1]);
592  
593        if (leftdir & rightdir)
594  	fatal ("both files to be compared are directories");
595  
596        lname = expand_name (argv[optind], leftdir, argv[optind + 1]);
597        left = ck_fopen (lname, "r");
598        rname = expand_name (argv[optind + 1], rightdir, argv[optind]);
599        right = ck_fopen (rname, "r");
600        out = ck_fopen (output, "w");
601  
602        diffarg ("--sdiff-merge-assist");
603        diffarg ("--");
604        diffarg (argv[optind]);
605        diffarg (argv[optind + 1]);
606        diffarg (0);
607  
608        trapsigs ();
609  
610  #if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
611        {
612  	size_t cmdsize = 1;
613  	char *p, *command;
614  	int i;
615  
616  	for (i = 0;  diffargv[i];  i++)
617  	  cmdsize += quote_system_arg (0, diffargv[i]) + 1;
618  	command = p = xmalloc (cmdsize);
619  	for (i = 0;  diffargv[i];  i++)
620  	  {
621  	    p += quote_system_arg (p, diffargv[i]);
622  	    *p++ = ' ';
623  	  }
624  	p[-1] = 0;
625  	errno = 0;
626  	diffout = popen (command, "r");
627  	if (! diffout)
628  	  perror_fatal (command);
629  	free (command);
630        }
631  #else
632        {
633  	int diff_fds[2];
634  # if HAVE_WORKING_VFORK
635  	sigset_t procmask;
636  	sigset_t blocked;
637  # endif
638  
639  	if (pipe (diff_fds) != 0)
640  	  perror_fatal ("pipe");
641  
642  # if HAVE_WORKING_VFORK
643  	/* Block SIGINT and SIGPIPE.  */
644  	sigemptyset (&blocked);
645  	sigaddset (&blocked, SIGINT);
646  	sigaddset (&blocked, SIGPIPE);
647  	sigprocmask (SIG_BLOCK, &blocked, &procmask);
648  # endif
649  	diffpid = vfork ();
650  	if (diffpid < 0)
651  	  perror_fatal ("fork");
652  	if (! diffpid)
653  	  {
654  	    /* Alter the child's SIGINT and SIGPIPE handlers;
655  	       this may munge the parent.
656  	       The child ignores SIGINT in case the user interrupts the editor.
657  	       The child does not ignore SIGPIPE, even if the parent does.  */
658  	    if (initial_handler (handler_index_of_SIGINT) != SIG_IGN)
659  	      signal_handler (SIGINT, SIG_IGN);
660  	    signal_handler (SIGPIPE, SIG_DFL);
661  # if HAVE_WORKING_VFORK
662  	    /* Stop blocking SIGINT and SIGPIPE in the child.  */
663  	    sigprocmask (SIG_SETMASK, &procmask, 0);
664  # endif
665  	    close (diff_fds[0]);
666  	    if (diff_fds[1] != STDOUT_FILENO)
667  	      {
668  		dup2 (diff_fds[1], STDOUT_FILENO);
669  		close (diff_fds[1]);
670  	      }
671  
672  	    execvp (diffargv[0], (char **) diffargv);
673  	    _exit (errno == ENOENT ? 127 : 126);
674  	  }
675  
676  # if HAVE_WORKING_VFORK
677  	/* Restore the parent's SIGINT and SIGPIPE behavior.  */
678  	if (initial_handler (handler_index_of_SIGINT) != SIG_IGN)
679  	  signal_handler (SIGINT, catchsig);
680  	if (initial_handler (handler_index_of_SIGPIPE) != SIG_IGN)
681  	  signal_handler (SIGPIPE, catchsig);
682  	else
683  	  signal_handler (SIGPIPE, SIG_IGN);
684  
685  	/* Stop blocking SIGINT and SIGPIPE in the parent.  */
686  	sigprocmask (SIG_SETMASK, &procmask, 0);
687  # endif
688  
689  	close (diff_fds[1]);
690  	diffout = fdopen (diff_fds[0], "r");
691  	if (! diffout)
692  	  perror_fatal ("fdopen");
693        }
694  #endif
695  
696        lf_init (&diff_filt, diffout);
697        lf_init (&lfilt, left);
698        lf_init (&rfilt, right);
699  
700        interact_ok = interact (&diff_filt, &lfilt, lname, &rfilt, rname, out);
701  
702        ck_fclose (left);
703        ck_fclose (right);
704        ck_fclose (out);
705  
706        {
707  	int wstatus;
708  	int werrno = 0;
709  
710  #if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
711  	wstatus = pclose (diffout);
712  	if (wstatus == -1)
713  	  werrno = errno;
714  #else
715  	ck_fclose (diffout);
716  	while (waitpid (diffpid, &wstatus, 0) < 0)
717  	  if (errno == EINTR)
718  	    checksigs ();
719  	  else
720  	    perror_fatal ("waitpid");
721  	diffpid = 0;
722  #endif
723  
724  	if (tmpname)
725  	  {
726  	    unlink (tmpname);
727  	    tmpname = 0;
728  	  }
729  
730  	if (! interact_ok)
731  	  exiterr ();
732  
733  	check_child_status (werrno, wstatus, EXIT_FAILURE, diffargv[0]);
734  	untrapsig (0);
735  	checksigs ();
736  	exit (WEXITSTATUS (wstatus));
737        }
738      }
739    return EXIT_SUCCESS;			/* Fool `-Wall'.  */
740  }
741  
742  static void
743  diffarg (char const *a)
744  {
745    static size_t diffargs, diffarglim;
746  
747    if (diffargs == diffarglim)
748      {
749        if (! diffarglim)
750  	diffarglim = 16;
751        else if (PTRDIFF_MAX / (2 * sizeof *diffargv) <= diffarglim)
752  	xalloc_die ();
753        else
754  	diffarglim *= 2;
755        diffargv = xrealloc (diffargv, diffarglim * sizeof *diffargv);
756      }
757    diffargv[diffargs++] = a;
758  }
759  
760  /* Signal handling */
761  
762  static bool volatile ignore_SIGINT;
763  static int volatile signal_received;
764  static bool sigs_trapped;
765  
766  static void
767  catchsig (int s)
768  {
769  #if ! HAVE_SIGACTION
770    signal (s, SIG_IGN);
771  #endif
772    if (! (s == SIGINT && ignore_SIGINT))
773      signal_received = s;
774  }
775  
776  #if HAVE_SIGACTION
777  static struct sigaction catchaction;
778  
779  static void
780  signal_handler (int sig, void (*handler) (int))
781  {
782    catchaction.sa_handler = handler;
783    sigaction (sig, &catchaction, 0);
784  }
785  #endif
786  
787  static void
788  trapsigs (void)
789  {
790    int i;
791  
792  #if HAVE_SIGACTION
793    catchaction.sa_flags = SA_RESTART;
794    sigemptyset (&catchaction.sa_mask);
795    for (i = 0;  i < NUM_SIGS;  i++)
796      sigaddset (&catchaction.sa_mask, sigs[i]);
797  #endif
798  
799    for (i = 0;  i < NUM_SIGS;  i++)
800      {
801  #if HAVE_SIGACTION
802        sigaction (sigs[i], 0, &initial_action[i]);
803  #else
804        initial_action[i] = signal (sigs[i], SIG_IGN);
805  #endif
806        if (initial_handler (i) != SIG_IGN)
807  	signal_handler (sigs[i], catchsig);
808      }
809  
810  #ifdef SIGCHLD
811    /* System V fork+wait does not work if SIGCHLD is ignored.  */
812    signal (SIGCHLD, SIG_DFL);
813  #endif
814  
815    sigs_trapped = true;
816  }
817  
818  /* Untrap signal S, or all trapped signals if S is zero.  */
819  static void
820  untrapsig (int s)
821  {
822    int i;
823  
824    if (sigs_trapped)
825      for (i = 0;  i < NUM_SIGS;  i++)
826        if ((! s || sigs[i] == s)  &&  initial_handler (i) != SIG_IGN)
827  	{
828  #if HAVE_SIGACTION
829  	  sigaction (sigs[i], &initial_action[i], 0);
830  #else
831  	  signal (sigs[i], initial_action[i]);
832  #endif
833  	}
834  }
835  
836  /* Exit if a signal has been received.  */
837  static void
838  checksigs (void)
839  {
840    int s = signal_received;
841    if (s)
842      {
843        cleanup (0);
844  
845        /* Yield an exit status indicating that a signal was received.  */
846        untrapsig (s);
847        kill (getpid (), s);
848  
849        /* That didn't work, so exit with error status.  */
850        exit (EXIT_TROUBLE);
851      }
852  }
853  
854  static void
855  give_help (void)
856  {
857    fprintf (stderr, "%s", _("\
858  ed:\tEdit then use both versions, each decorated with a header.\n\
859  eb:\tEdit then use both versions.\n\
860  el or e1:\tEdit then use the left version.\n\
861  er or e2:\tEdit then use the right version.\n\
862  e:\tDiscard both versions then edit a new one.\n\
863  l or 1:\tUse the left version.\n\
864  r or 2:\tUse the right version.\n\
865  s:\tSilently include common lines.\n\
866  v:\tVerbosely include common lines.\n\
867  q:\tQuit.\n\
868  "));
869  }
870  
871  static int
872  skip_white (void)
873  {
874    int c;
875    for (;;)
876      {
877        c = getchar ();
878        if (! isspace (c) || c == '\n')
879  	break;
880        checksigs ();
881      }
882    if (ferror (stdin))
883      perror_fatal (_("read failed"));
884    return c;
885  }
886  
887  static void
888  flush_line (void)
889  {
890    int c;
891    while ((c = getchar ()) != '\n' && c != EOF)
892      continue;
893    if (ferror (stdin))
894      perror_fatal (_("read failed"));
895  }
896  
897  
898  /* interpret an edit command */
899  static bool
900  edit (struct line_filter *left, char const *lname, lin lline, lin llen,
901        struct line_filter *right, char const *rname, lin rline, lin rlen,
902        FILE *outfile)
903  {
904    for (;;)
905      {
906        int cmd0, cmd1;
907        bool gotcmd = false;
908  
909        cmd1 = 0; /* Pacify `gcc -W'.  */
910  
911        while (! gotcmd)
912  	{
913  	  if (putchar ('%') != '%')
914  	    perror_fatal (_("write failed"));
915  	  ck_fflush (stdout);
916  
917  	  cmd0 = skip_white ();
918  	  switch (cmd0)
919  	    {
920  	    case '1': case '2': case 'l': case 'r':
921  	    case 's': case 'v': case 'q':
922  	      if (skip_white () != '\n')
923  		{
924  		  give_help ();
925  		  flush_line ();
926  		  continue;
927  		}
928  	      gotcmd = true;
929  	      break;
930  
931  	    case 'e':
932  	      cmd1 = skip_white ();
933  	      switch (cmd1)
934  		{
935  		case '1': case '2': case 'b': case 'd': case 'l': case 'r':
936  		  if (skip_white () != '\n')
937  		    {
938  		      give_help ();
939  		      flush_line ();
940  		      continue;
941  		    }
942  		  gotcmd = true;
943  		  break;
944  		case '\n':
945  		  gotcmd = true;
946  		  break;
947  		default:
948  		  give_help ();
949  		  flush_line ();
950  		  continue;
951  		}
952  	      break;
953  
954  	    case EOF:
955  	      if (feof (stdin))
956  		{
957  		  gotcmd = true;
958  		  cmd0 = 'q';
959  		  break;
960  		}
961  	      /* Fall through.  */
962  	    default:
963  	      flush_line ();
964  	      /* Fall through.  */
965  	    case '\n':
966  	      give_help ();
967  	      continue;
968  	    }
969  	}
970  
971        switch (cmd0)
972  	{
973  	case '1': case 'l':
974  	  lf_copy (left, llen, outfile);
975  	  lf_skip (right, rlen);
976  	  return true;
977  	case '2': case 'r':
978  	  lf_copy (right, rlen, outfile);
979  	  lf_skip (left, llen);
980  	  return true;
981  	case 's':
982  	  suppress_common_lines = true;
983  	  break;
984  	case 'v':
985  	  suppress_common_lines = false;
986  	  break;
987  	case 'q':
988  	  return false;
989  	case 'e':
990  	  {
991  	    int fd;
992  
993  	    if (tmpname)
994  	      tmp = fopen (tmpname, "w");
995  	    else
996  	      {
997  		if ((fd = temporary_file ()) < 0)
998  		  perror_fatal ("mkstemp");
999  		tmp = fdopen (fd, "w");
1000  	      }
1001  
1002  	    if (! tmp)
1003  	      perror_fatal (tmpname);
1004  
1005  	    switch (cmd1)
1006  	      {
1007  	      case 'd':
1008  		if (llen)
1009  		  {
1010  		    if (llen == 1)
1011  		      fprintf (tmp, "--- %s %ld\n", lname, (long int) lline);
1012  		    else
1013  		      fprintf (tmp, "--- %s %ld,%ld\n", lname,
1014  			       (long int) lline,
1015  			       (long int) (lline + llen - 1));
1016  		  }
1017  		/* Fall through.  */
1018  	      case '1': case 'b': case 'l':
1019  		lf_copy (left, llen, tmp);
1020  		break;
1021  
1022  	      default:
1023  		lf_skip (left, llen);
1024  		break;
1025  	      }
1026  
1027  	    switch (cmd1)
1028  	      {
1029  	      case 'd':
1030  		if (rlen)
1031  		  {
1032  		    if (rlen == 1)
1033  		      fprintf (tmp, "+++ %s %ld\n", rname, (long int) rline);
1034  		    else
1035  		      fprintf (tmp, "+++ %s %ld,%ld\n", rname,
1036  			       (long int) rline,
1037  			       (long int) (rline + rlen - 1));
1038  		  }
1039  		/* Fall through.  */
1040  	      case '2': case 'b': case 'r':
1041  		lf_copy (right, rlen, tmp);
1042  		break;
1043  
1044  	      default:
1045  		lf_skip (right, rlen);
1046  		break;
1047  	      }
1048  
1049  	    ck_fclose (tmp);
1050  
1051  	    {
1052  	      int wstatus;
1053  	      int werrno = 0;
1054  	      ignore_SIGINT = true;
1055  	      checksigs ();
1056  
1057  	      {
1058  #if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
1059  		char *command =
1060  		  xmalloc (quote_system_arg (0, editor_program)
1061  			   + 1 + strlen (tmpname) + 1);
1062  		sprintf (command + quote_system_arg (command, editor_program),
1063  			 " %s", tmpname);
1064  		wstatus = system (command);
1065  		if (wstatus == -1)
1066  		  werrno = errno;
1067  		free (command);
1068  #else
1069  		pid_t pid;
1070  
1071  		pid = vfork ();
1072  		if (pid == 0)
1073  		  {
1074  		    char const *argv[3];
1075  		    int i = 0;
1076  
1077  		    argv[i++] = editor_program;
1078  		    argv[i++] = tmpname;
1079  		    argv[i] = 0;
1080  
1081  		    execvp (editor_program, (char **) argv);
1082  		    _exit (errno == ENOENT ? 127 : 126);
1083  		  }
1084  
1085  		if (pid < 0)
1086  		  perror_fatal ("fork");
1087  
1088  		while (waitpid (pid, &wstatus, 0) < 0)
1089  		  if (errno == EINTR)
1090  		    checksigs ();
1091  		  else
1092  		    perror_fatal ("waitpid");
1093  #endif
1094  	      }
1095  
1096  	      ignore_SIGINT = false;
1097  	      check_child_status (werrno, wstatus, EXIT_SUCCESS,
1098  				  editor_program);
1099  	    }
1100  
1101  	    {
1102  	      char buf[SDIFF_BUFSIZE];
1103  	      size_t size;
1104  	      tmp = ck_fopen (tmpname, "r");
1105  	      while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0)
1106  		{
1107  		  checksigs ();
1108  		  ck_fwrite (buf, size, outfile);
1109  		}
1110  	      ck_fclose (tmp);
1111  	    }
1112  	    return true;
1113  	  }
1114  	default:
1115  	  give_help ();
1116  	  break;
1117  	}
1118      }
1119  }
1120  
1121  /* Alternately reveal bursts of diff output and handle user commands.  */
1122  static bool
1123  interact (struct line_filter *diff,
1124  	  struct line_filter *left, char const *lname,
1125  	  struct line_filter *right, char const *rname,
1126  	  FILE *outfile)
1127  {
1128    lin lline = 1, rline = 1;
1129  
1130    for (;;)
1131      {
1132        char diff_help[256];
1133        int snarfed = lf_snarf (diff, diff_help, sizeof diff_help);
1134  
1135        if (snarfed <= 0)
1136  	return snarfed != 0;
1137  
1138        checksigs ();
1139  
1140        if (diff_help[0] == ' ')
1141  	puts (diff_help + 1);
1142        else
1143  	{
1144  	  char *numend;
1145  	  uintmax_t val;
1146  	  lin llen, rlen, lenmax;
1147  	  errno = 0;
1148  	  llen = val = strtoumax (diff_help + 1, &numend, 10);
1149  	  if (llen < 0 || llen != val || errno || *numend != ',')
1150  	    fatal (diff_help);
1151  	  rlen = val = strtoumax (numend + 1, &numend, 10);
1152  	  if (rlen < 0 || rlen != val || errno || *numend)
1153  	    fatal (diff_help);
1154  
1155  	  lenmax = MAX (llen, rlen);
1156  
1157  	  switch (diff_help[0])
1158  	    {
1159  	    case 'i':
1160  	      if (suppress_common_lines)
1161  		lf_skip (diff, lenmax);
1162  	      else
1163  		lf_copy (diff, lenmax, stdout);
1164  
1165  	      lf_copy (left, llen, outfile);
1166  	      lf_skip (right, rlen);
1167  	      break;
1168  
1169  	    case 'c':
1170  	      lf_copy (diff, lenmax, stdout);
1171  	      if (! edit (left, lname, lline, llen,
1172  			  right, rname, rline, rlen,
1173  			  outfile))
1174  		return false;
1175  	      break;
1176  
1177  	    default:
1178  	      fatal (diff_help);
1179  	    }
1180  
1181  	  lline += llen;
1182  	  rline += rlen;
1183  	}
1184      }
1185  }
1186  
1187  /* Return true if DIR is an existing directory.  */
1188  static bool
1189  diraccess (char const *dir)
1190  {
1191    struct stat buf;
1192    return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode);
1193  }
1194  
1195  #ifndef P_tmpdir
1196  # define P_tmpdir "/tmp"
1197  #endif
1198  #ifndef TMPDIR_ENV
1199  # define TMPDIR_ENV "TMPDIR"
1200  #endif
1201  
1202  /* Open a temporary file and return its file descriptor.  Put into
1203     tmpname the address of a newly allocated buffer that holds the
1204     file's name.  Use the prefix "sdiff".  */
1205  static int
1206  temporary_file (void)
1207  {
1208    char const *tmpdir = getenv (TMPDIR_ENV);
1209    char const *dir = tmpdir ? tmpdir : P_tmpdir;
1210    char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1);
1211    int fd;
1212    int e;
1213    sigset_t procmask;
1214    sigset_t blocked;
1215    sprintf (buf, "%s/sdiffXXXXXX", dir);
1216    sigemptyset (&blocked);
1217    sigaddset (&blocked, SIGINT);
1218    sigprocmask (SIG_BLOCK, &blocked, &procmask);
1219    fd = mkstemp (buf);
1220    e = errno;
1221    if (0 <= fd)
1222      tmpname = buf;
1223    sigprocmask (SIG_SETMASK, &procmask, 0);
1224    errno = e;
1225    return fd;
1226  }
1227