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