sdiff.c revision 170754
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
43char *program_name;
44
45static char const *editor_program = DEFAULT_EDITOR_PROGRAM;
46static char const **diffargv;
47
48static char * volatile tmpname;
49static FILE *tmp;
50
51#if HAVE_WORKING_FORK || HAVE_WORKING_VFORK
52static pid_t volatile diffpid;
53#endif
54
55struct line_filter;
56
57static void catchsig (int);
58static bool edit (struct line_filter *, char const *, lin, lin, struct line_filter *, char const *, lin, lin, FILE *);
59static bool interact (struct line_filter *, struct line_filter *, char const *, struct line_filter *, char const *, FILE *);
60static void checksigs (void);
61static void diffarg (char const *);
62static void fatal (char const *) __attribute__((noreturn));
63static void perror_fatal (char const *) __attribute__((noreturn));
64static void trapsigs (void);
65static void untrapsig (int);
66
67#define NUM_SIGS (sizeof sigs / sizeof *sigs)
68static 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
118static bool diraccess (char const *);
119static int temporary_file (void);
120
121/* Options: */
122
123/* Name of output file if -o specified.  */
124static char const *output;
125
126/* Do not print common lines.  */
127static bool suppress_common_lines;
128
129/* Value for the long option that does not have single-letter equivalents.  */
130enum
131{
132  DIFF_PROGRAM_OPTION = CHAR_MAX + 1,
133  HELP_OPTION,
134  STRIP_TRAILING_CR_OPTION,
135  TABSIZE_OPTION
136};
137
138static 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
162static void try_help (char const *, char const *) __attribute__((noreturn));
163static void
164try_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
173static void
174check_stdout (void)
175{
176  if (ferror (stdout))
177    fatal ("write failed");
178  else if (fclose (stdout) != 0)
179    perror_fatal (_("standard output"));
180}
181
182static 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
210static void
211usage (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.  */
230static void
231cleanup (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
241static void exiterr (void) __attribute__((noreturn));
242static void
243exiterr (void)
244{
245  cleanup (0);
246  untrapsig (0);
247  checksigs ();
248  exit (EXIT_TROUBLE);
249}
250
251static void
252fatal (char const *msgid)
253{
254  error (0, 0, "%s", _(msgid));
255  exiterr ();
256}
257
258static void
259perror_fatal (char const *msg)
260{
261  int e = errno;
262  checksigs ();
263  error (0, e, "%s", msg);
264  exiterr ();
265}
266
267static void
268check_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
290static FILE *
291ck_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
299static void
300ck_fclose (FILE *f)
301{
302  if (fclose (f))
303    perror_fatal ("fclose");
304}
305
306static size_t
307ck_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
315static void
316ck_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
322static void
323ck_fflush (FILE *f)
324{
325  if (fflush (f) != 0)
326    perror_fatal (_("write failed"));
327}
328
329static char const *
330expand_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
350struct line_filter {
351  FILE *infile;
352  char *bufpos;
353  char *buffer;
354  char *buflim;
355};
356
357static void
358lf_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 */
366static size_t
367lf_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 */
378static void
379lf_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 */
404static void
405lf_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.  */
424static int
425lf_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
448int
449main (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
742static void
743diffarg (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
762static bool volatile ignore_SIGINT;
763static int volatile signal_received;
764static bool sigs_trapped;
765
766static void
767catchsig (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
777static struct sigaction catchaction;
778
779static void
780signal_handler (int sig, void (*handler) (int))
781{
782  catchaction.sa_handler = handler;
783  sigaction (sig, &catchaction, 0);
784}
785#endif
786
787static void
788trapsigs (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.  */
819static void
820untrapsig (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.  */
837static void
838checksigs (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
854static void
855give_help (void)
856{
857  fprintf (stderr, "%s", _("\
858ed:\tEdit then use both versions, each decorated with a header.\n\
859eb:\tEdit then use both versions.\n\
860el:\tEdit then use the left version.\n\
861er:\tEdit then use the right version.\n\
862e:\tEdit a new version.\n\
863l:\tUse the left version.\n\
864r:\tUse the right version.\n\
865s:\tSilently include common lines.\n\
866v:\tVerbosely include common lines.\n\
867q:\tQuit.\n\
868"));
869}
870
871static int
872skip_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
887static void
888flush_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 */
899static bool
900edit (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 'l': case 'r': case 's': case 'v': case 'q':
921	      if (skip_white () != '\n')
922		{
923		  give_help ();
924		  flush_line ();
925		  continue;
926		}
927	      gotcmd = true;
928	      break;
929
930	    case 'e':
931	      cmd1 = skip_white ();
932	      switch (cmd1)
933		{
934		case 'b': case 'd': case 'l': case 'r':
935		  if (skip_white () != '\n')
936		    {
937		      give_help ();
938		      flush_line ();
939		      continue;
940		    }
941		  gotcmd = true;
942		  break;
943		case '\n':
944		  gotcmd = true;
945		  break;
946		default:
947		  give_help ();
948		  flush_line ();
949		  continue;
950		}
951	      break;
952
953	    case EOF:
954	      if (feof (stdin))
955		{
956		  gotcmd = true;
957		  cmd0 = 'q';
958		  break;
959		}
960	      /* Fall through.  */
961	    default:
962	      flush_line ();
963	      /* Fall through.  */
964	    case '\n':
965	      give_help ();
966	      continue;
967	    }
968	}
969
970      switch (cmd0)
971	{
972	case 'l':
973	  lf_copy (left, llen, outfile);
974	  lf_skip (right, rlen);
975	  return true;
976	case 'r':
977	  lf_copy (right, rlen, outfile);
978	  lf_skip (left, llen);
979	  return true;
980	case 's':
981	  suppress_common_lines = true;
982	  break;
983	case 'v':
984	  suppress_common_lines = false;
985	  break;
986	case 'q':
987	  return false;
988	case 'e':
989	  {
990	    int fd;
991
992	    if (tmpname)
993	      tmp = fopen (tmpname, "w");
994	    else
995	      {
996		if ((fd = temporary_file ()) < 0)
997		  perror_fatal ("mkstemp");
998		tmp = fdopen (fd, "w");
999	      }
1000
1001	    if (! tmp)
1002	      perror_fatal (tmpname);
1003
1004	    switch (cmd1)
1005	      {
1006	      case 'd':
1007		if (llen)
1008		  {
1009		    if (llen == 1)
1010		      fprintf (tmp, "--- %s %ld\n", lname, (long int) lline);
1011		    else
1012		      fprintf (tmp, "--- %s %ld,%ld\n", lname,
1013			       (long int) lline,
1014			       (long int) (lline + llen - 1));
1015		  }
1016		/* Fall through.  */
1017	      case 'b': case 'l':
1018		lf_copy (left, llen, tmp);
1019		break;
1020
1021	      default:
1022		lf_skip (left, llen);
1023		break;
1024	      }
1025
1026	    switch (cmd1)
1027	      {
1028	      case 'd':
1029		if (rlen)
1030		  {
1031		    if (rlen == 1)
1032		      fprintf (tmp, "+++ %s %ld\n", rname, (long int) rline);
1033		    else
1034		      fprintf (tmp, "+++ %s %ld,%ld\n", rname,
1035			       (long int) rline,
1036			       (long int) (rline + rlen - 1));
1037		  }
1038		/* Fall through.  */
1039	      case 'b': case 'r':
1040		lf_copy (right, rlen, tmp);
1041		break;
1042
1043	      default:
1044		lf_skip (right, rlen);
1045		break;
1046	      }
1047
1048	    ck_fclose (tmp);
1049
1050	    {
1051	      int wstatus;
1052	      int werrno = 0;
1053	      ignore_SIGINT = true;
1054	      checksigs ();
1055
1056	      {
1057#if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
1058		char *command =
1059		  xmalloc (quote_system_arg (0, editor_program)
1060			   + 1 + strlen (tmpname) + 1);
1061		sprintf (command + quote_system_arg (command, editor_program),
1062			 " %s", tmpname);
1063		wstatus = system (command);
1064		if (wstatus == -1)
1065		  werrno = errno;
1066		free (command);
1067#else
1068		pid_t pid;
1069
1070		pid = vfork ();
1071		if (pid == 0)
1072		  {
1073		    char const *argv[3];
1074		    int i = 0;
1075
1076		    argv[i++] = editor_program;
1077		    argv[i++] = tmpname;
1078		    argv[i] = 0;
1079
1080		    execvp (editor_program, (char **) argv);
1081		    _exit (errno == ENOENT ? 127 : 126);
1082		  }
1083
1084		if (pid < 0)
1085		  perror_fatal ("fork");
1086
1087		while (waitpid (pid, &wstatus, 0) < 0)
1088		  if (errno == EINTR)
1089		    checksigs ();
1090		  else
1091		    perror_fatal ("waitpid");
1092#endif
1093	      }
1094
1095	      ignore_SIGINT = false;
1096	      check_child_status (werrno, wstatus, EXIT_SUCCESS,
1097				  editor_program);
1098	    }
1099
1100	    {
1101	      char buf[SDIFF_BUFSIZE];
1102	      size_t size;
1103	      tmp = ck_fopen (tmpname, "r");
1104	      while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0)
1105		{
1106		  checksigs ();
1107		  ck_fwrite (buf, size, outfile);
1108		}
1109	      ck_fclose (tmp);
1110	    }
1111	    return true;
1112	  }
1113	default:
1114	  give_help ();
1115	  break;
1116	}
1117    }
1118}
1119
1120/* Alternately reveal bursts of diff output and handle user commands.  */
1121static bool
1122interact (struct line_filter *diff,
1123	  struct line_filter *left, char const *lname,
1124	  struct line_filter *right, char const *rname,
1125	  FILE *outfile)
1126{
1127  lin lline = 1, rline = 1;
1128
1129  for (;;)
1130    {
1131      char diff_help[256];
1132      int snarfed = lf_snarf (diff, diff_help, sizeof diff_help);
1133
1134      if (snarfed <= 0)
1135	return snarfed != 0;
1136
1137      checksigs ();
1138
1139      if (diff_help[0] == ' ')
1140	puts (diff_help + 1);
1141      else
1142	{
1143	  char *numend;
1144	  uintmax_t val;
1145	  lin llen, rlen, lenmax;
1146	  errno = 0;
1147	  llen = val = strtoumax (diff_help + 1, &numend, 10);
1148	  if (llen < 0 || llen != val || errno || *numend != ',')
1149	    fatal (diff_help);
1150	  rlen = val = strtoumax (numend + 1, &numend, 10);
1151	  if (rlen < 0 || rlen != val || errno || *numend)
1152	    fatal (diff_help);
1153
1154	  lenmax = MAX (llen, rlen);
1155
1156	  switch (diff_help[0])
1157	    {
1158	    case 'i':
1159	      if (suppress_common_lines)
1160		lf_skip (diff, lenmax);
1161	      else
1162		lf_copy (diff, lenmax, stdout);
1163
1164	      lf_copy (left, llen, outfile);
1165	      lf_skip (right, rlen);
1166	      break;
1167
1168	    case 'c':
1169	      lf_copy (diff, lenmax, stdout);
1170	      if (! edit (left, lname, lline, llen,
1171			  right, rname, rline, rlen,
1172			  outfile))
1173		return false;
1174	      break;
1175
1176	    default:
1177	      fatal (diff_help);
1178	    }
1179
1180	  lline += llen;
1181	  rline += rlen;
1182	}
1183    }
1184}
1185
1186/* Return true if DIR is an existing directory.  */
1187static bool
1188diraccess (char const *dir)
1189{
1190  struct stat buf;
1191  return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode);
1192}
1193
1194#ifndef P_tmpdir
1195# define P_tmpdir "/tmp"
1196#endif
1197#ifndef TMPDIR_ENV
1198# define TMPDIR_ENV "TMPDIR"
1199#endif
1200
1201/* Open a temporary file and return its file descriptor.  Put into
1202   tmpname the address of a newly allocated buffer that holds the
1203   file's name.  Use the prefix "sdiff".  */
1204static int
1205temporary_file (void)
1206{
1207  char const *tmpdir = getenv (TMPDIR_ENV);
1208  char const *dir = tmpdir ? tmpdir : P_tmpdir;
1209  char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1);
1210  int fd;
1211  int e;
1212  sigset_t procmask;
1213  sigset_t blocked;
1214  sprintf (buf, "%s/sdiffXXXXXX", dir);
1215  sigemptyset (&blocked);
1216  sigaddset (&blocked, SIGINT);
1217  sigprocmask (SIG_BLOCK, &blocked, &procmask);
1218  fd = mkstemp (buf);
1219  e = errno;
1220  if (0 <= fd)
1221    tmpname = buf;
1222  sigprocmask (SIG_SETMASK, &procmask, 0);
1223  errno = e;
1224  return fd;
1225}
1226