xref: /freebsd/contrib/diff/src/sdiff.c (revision e6bfd18d21b225af6a0ed67ceeaf1293b7b9eba5)
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