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