1/*	$NetBSD: sdiff.c,v 1.2 2016/01/13 03:39:28 christos Exp $	*/
2
3/* sdiff - side-by-side merge of file differences
4
5   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 2001, 2002 Free
6   Software Foundation, Inc.
7
8   This file is part of GNU DIFF.
9
10   GNU DIFF is free software; you can redistribute it and/or modify
11   it under the terms of the GNU General Public License as published by
12   the Free Software Foundation; either version 2, or (at your option)
13   any later version.
14
15   GNU DIFF is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18   See the GNU General Public License for more details.
19
20   You should have received a copy of the GNU General Public License
21   along with this program; see the file COPYING.
22   If not, write to the Free Software Foundation,
23   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
24
25#include "system.h"
26
27#include <c-stack.h>
28#include <dirname.h>
29#include <error.h>
30#include <exitfail.h>
31#include <freesoft.h>
32#include <getopt.h>
33#include <quotesys.h>
34#include <stdio.h>
35#include <xalloc.h>
36
37static char const authorship_msgid[] = N_("Written by Thomas Lord.");
38
39static char const copyright_string[] =
40  "Copyright (C) 2002 Free Software Foundation, Inc.";
41
42extern char const version_string[];
43
44/* Size of chunks read from files which must be parsed into lines.  */
45#define SDIFF_BUFSIZE ((size_t) 65536)
46
47char *program_name;
48
49static char const *editor_program = DEFAULT_EDITOR_PROGRAM;
50static char const **diffargv;
51
52static char * volatile tmpname;
53static FILE *tmp;
54
55#if HAVE_WORKING_FORK || HAVE_WORKING_VFORK
56static pid_t volatile diffpid;
57#endif
58
59struct line_filter;
60
61static RETSIGTYPE catchsig (int);
62static bool edit (struct line_filter *, char const *, lin, lin, struct line_filter *, char const *, lin, lin, FILE *);
63static bool interact (struct line_filter *, struct line_filter *, char const *, struct line_filter *, char const *, FILE *);
64static void checksigs (void);
65static void diffarg (char const *);
66static void fatal (char const *) __attribute__((noreturn));
67static void perror_fatal (char const *) __attribute__((noreturn));
68static void trapsigs (void);
69static void untrapsig (int);
70
71#define NUM_SIGS (sizeof sigs / sizeof *sigs)
72static int const sigs[] = {
73#ifdef SIGHUP
74       SIGHUP,
75#endif
76#ifdef SIGQUIT
77       SIGQUIT,
78#endif
79#ifdef SIGTERM
80       SIGTERM,
81#endif
82#ifdef SIGXCPU
83       SIGXCPU,
84#endif
85#ifdef SIGXFSZ
86       SIGXFSZ,
87#endif
88       SIGINT,
89       SIGPIPE
90};
91#define handler_index_of_SIGINT (NUM_SIGS - 2)
92#define handler_index_of_SIGPIPE (NUM_SIGS - 1)
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, RETSIGTYPE (*) (int));
99#else
100  static RETSIGTYPE (*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# define sigprocmask(how, n, o) \
119    ((how) == SIG_BLOCK ? *(o) = sigblock (*(n)) : sigsetmask (*(n)))
120#endif
121
122static bool diraccess (char const *);
123static int temporary_file (void);
124
125/* Options: */
126
127/* Name of output file if -o specified.  */
128static char const *output;
129
130/* Do not print common lines.  */
131static bool suppress_common_lines;
132
133/* Value for the long option that does not have single-letter equivalents.  */
134enum
135{
136  DIFF_PROGRAM_OPTION = CHAR_MAX + 1,
137  HELP_OPTION,
138  STRIP_TRAILING_CR_OPTION
139};
140
141static struct option const longopts[] =
142{
143  {"diff-program", 1, 0, DIFF_PROGRAM_OPTION},
144  {"expand-tabs", 0, 0, 't'},
145  {"help", 0, 0, HELP_OPTION},
146  {"ignore-all-space", 0, 0, 'W'}, /* swap W and w for historical reasons */
147  {"ignore-blank-lines", 0, 0, 'B'},
148  {"ignore-case", 0, 0, 'i'},
149  {"ignore-matching-lines", 1, 0, 'I'},
150  {"ignore-space-change", 0, 0, 'b'},
151  {"ignore-tab-expansion", 0, 0, 'E'},
152  {"left-column", 0, 0, 'l'},
153  {"minimal", 0, 0, 'd'},
154  {"output", 1, 0, 'o'},
155  {"speed-large-files", 0, 0, 'H'},
156  {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
157  {"suppress-common-lines", 0, 0, 's'},
158  {"text", 0, 0, 'a'},
159  {"version", 0, 0, 'v'},
160  {"width", 1, 0, 'w'},
161  {0, 0, 0, 0}
162};
163
164static void try_help (char const *, char const *) __attribute__((noreturn));
165static void
166try_help (char const *reason_msgid, char const *operand)
167{
168  if (reason_msgid)
169    error (0, 0, _(reason_msgid), operand);
170  error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
171	 program_name);
172  abort ();
173}
174
175static void
176check_stdout (void)
177{
178  if (ferror (stdout))
179    fatal ("write failed");
180  else if (fclose (stdout) != 0)
181    perror_fatal (_("standard output"));
182}
183
184static char const * const option_help_msgid[] = {
185  N_("-o FILE  --output=FILE  Operate interactively, sending output to FILE."),
186  "",
187  N_("-i  --ignore-case  Consider upper- and lower-case to be the same."),
188  N_("-E  --ignore-tab-expansion  Ignore changes due to tab expansion."),
189  N_("-b  --ignore-space-change  Ignore changes in the amount of white space."),
190  N_("-W  --ignore-all-space  Ignore all white space."),
191  N_("-B  --ignore-blank-lines  Ignore changes whose lines are all blank."),
192  N_("-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE."),
193  N_("--strip-trailing-cr  Strip trailing carriage return on input."),
194  N_("-a  --text  Treat all files as text."),
195  "",
196  N_("-w NUM  --width=NUM  Output at most NUM (default 130) columns per line."),
197  N_("-l  --left-column  Output only the left column of common lines."),
198  N_("-s  --suppress-common-lines  Do not output common lines."),
199  "",
200  N_("-t  --expand-tabs  Expand tabs to spaces in output."),
201  "",
202  N_("-d  --minimal  Try hard to find a smaller set of changes."),
203  N_("-H  --speed-large-files  Assume large files and many scattered small changes."),
204  N_("--diff-program=PROGRAM  Use PROGRAM to compare files."),
205  "",
206  N_("-v  --version  Output version info."),
207  N_("--help  Output this help."),
208  0
209};
210
211static void
212usage (void)
213{
214  char const * const *p;
215
216  printf (_("Usage: %s [OPTION]... FILE1 FILE2\n"), program_name);
217  printf ("%s\n\n", _("Side-by-side merge of file differences."));
218  for (p = option_help_msgid;  *p;  p++)
219    if (**p)
220      printf ("  %s\n", _(*p));
221    else
222      putchar ('\n');
223  printf ("\n%s\n\n%s\n",
224	  _("If a FILE is `-', read standard input."),
225	  _("Report bugs to <bug-gnu-utils@gnu.org>."));
226}
227
228static void
229cleanup (void)
230{
231#if HAVE_WORKING_FORK || HAVE_WORKING_VFORK
232  if (0 < diffpid)
233    kill (diffpid, SIGPIPE);
234#endif
235  if (tmpname)
236    unlink (tmpname);
237}
238
239static void exiterr (void) __attribute__((noreturn));
240static void
241exiterr (void)
242{
243  cleanup ();
244  untrapsig (0);
245  checksigs ();
246  exit (EXIT_TROUBLE);
247}
248
249static void
250fatal (char const *msgid)
251{
252  error (0, 0, "%s", _(msgid));
253  exiterr ();
254}
255
256static void
257perror_fatal (char const *msg)
258{
259  int e = errno;
260  checksigs ();
261  error (0, e, "%s", msg);
262  exiterr ();
263}
264
265static void
266check_child_status (int werrno, int wstatus, int max_ok_status,
267		    char const *subsidiary_program)
268{
269  int status = (! werrno && WIFEXITED (wstatus)
270		? WEXITSTATUS (wstatus)
271		: INT_MAX);
272
273  if (max_ok_status < status)
274    {
275      error (0, werrno,
276	     _(status == 126
277	       ? "subsidiary program `%s' could not be invoked"
278	       : status == 127
279	       ? "subsidiary program `%s' not found"
280	       : status == INT_MAX
281	       ? "subsidiary program `%s' failed"
282	       : "subsidiary program `%s' failed (exit status %d)"),
283	     subsidiary_program, status);
284      exiterr ();
285    }
286}
287
288static FILE *
289ck_fopen (char const *fname, char const *type)
290{
291  FILE *r = fopen (fname, type);
292  if (! r)
293    perror_fatal (fname);
294  return r;
295}
296
297static void
298ck_fclose (FILE *f)
299{
300  if (fclose (f))
301    perror_fatal ("fclose");
302}
303
304static size_t
305ck_fread (char *buf, size_t size, FILE *f)
306{
307  size_t r = fread (buf, sizeof (char), size, f);
308  if (r == 0 && ferror (f))
309    perror_fatal (_("read failed"));
310  return r;
311}
312
313static void
314ck_fwrite (char const *buf, size_t size, FILE *f)
315{
316  if (fwrite (buf, sizeof (char), size, f) != size)
317    perror_fatal (_("write failed"));
318}
319
320static void
321ck_fflush (FILE *f)
322{
323  if (fflush (f) != 0)
324    perror_fatal (_("write failed"));
325}
326
327static char const *
328expand_name (char *name, bool is_dir, char const *other_name)
329{
330  if (strcmp (name, "-") == 0)
331    fatal ("cannot interactively merge standard input");
332  if (! is_dir)
333    return name;
334  else
335    {
336      /* Yield NAME/BASE, where BASE is OTHER_NAME's basename.  */
337      char const *base = base_name (other_name);
338      size_t namelen = strlen (name), baselen = strlen (base);
339      bool insert_slash = *base_name (name) && name[namelen - 1] != '/';
340      char *r = xmalloc (namelen + insert_slash + baselen + 1);
341      memcpy (r, name, namelen);
342      r[namelen] = '/';
343      memcpy (r + namelen + insert_slash, base, baselen + 1);
344      return r;
345    }
346}
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
448
449
450int
451main (int argc, char *argv[])
452{
453  int opt;
454  char const *prog;
455
456  exit_failure = EXIT_TROUBLE;
457  initialize_main (&argc, &argv);
458  program_name = argv[0];
459  setlocale (LC_ALL, "");
460  bindtextdomain (PACKAGE, LOCALEDIR);
461  textdomain (PACKAGE);
462  c_stack_action (c_stack_die);
463
464  prog = getenv ("EDITOR");
465  if (prog)
466    editor_program = prog;
467
468  diffarg (DEFAULT_DIFF_PROGRAM);
469
470  /* parse command line args */
471  while ((opt = getopt_long (argc, argv, "abBdHiI:lo:stvw:W", longopts, 0))
472	 != -1)
473    {
474      switch (opt)
475	{
476	case 'a':
477	  diffarg ("-a");
478	  break;
479
480	case 'b':
481	  diffarg ("-b");
482	  break;
483
484	case 'B':
485	  diffarg ("-B");
486	  break;
487
488	case 'd':
489	  diffarg ("-d");
490	  break;
491
492	case 'E':
493	  diffarg ("-E");
494	  break;
495
496	case 'H':
497	  diffarg ("-H");
498	  break;
499
500	case 'i':
501	  diffarg ("-i");
502	  break;
503
504	case 'I':
505	  diffarg ("-I");
506	  diffarg (optarg);
507	  break;
508
509	case 'l':
510	  diffarg ("--left-column");
511	  break;
512
513	case 'o':
514	  output = optarg;
515	  break;
516
517	case 's':
518	  suppress_common_lines = 1;
519	  break;
520
521	case 't':
522	  diffarg ("-t");
523	  break;
524
525	case 'v':
526	  printf ("sdiff %s\n%s\n\n%s\n\n%s\n",
527		  version_string, copyright_string,
528		  _(free_software_msgid), _(authorship_msgid));
529	  check_stdout ();
530	  return EXIT_SUCCESS;
531
532	case 'w':
533	  diffarg ("-W");
534	  diffarg (optarg);
535	  break;
536
537	case 'W':
538	  diffarg ("-w");
539	  break;
540
541	case DIFF_PROGRAM_OPTION:
542	  diffargv[0] = optarg;
543	  break;
544
545	case HELP_OPTION:
546	  usage ();
547	  check_stdout ();
548	  return EXIT_SUCCESS;
549
550	case STRIP_TRAILING_CR_OPTION:
551	  diffarg ("--strip-trailing-cr");
552	  break;
553
554	default:
555	  try_help (0, 0);
556	}
557    }
558
559  if (argc - optind != 2)
560    {
561      if (argc - optind < 2)
562	try_help ("missing operand after `%s'", argv[argc - 1]);
563      else
564	try_help ("extra operand `%s'", argv[optind + 2]);
565    }
566
567  if (! output)
568    {
569      /* easy case: diff does everything for us */
570      if (suppress_common_lines)
571	diffarg ("--suppress-common-lines");
572      diffarg ("-y");
573      diffarg ("--");
574      diffarg (argv[optind]);
575      diffarg (argv[optind + 1]);
576      diffarg (0);
577      execvp (diffargv[0], (char **) diffargv);
578      perror_fatal (diffargv[0]);
579    }
580  else
581    {
582      char const *lname, *rname;
583      FILE *left, *right, *out, *diffout;
584      bool interact_ok;
585      struct line_filter lfilt;
586      struct line_filter rfilt;
587      struct line_filter diff_filt;
588      bool leftdir = diraccess (argv[optind]);
589      bool rightdir = diraccess (argv[optind + 1]);
590
591      if (leftdir & rightdir)
592	fatal ("both files to be compared are directories");
593
594      lname = expand_name (argv[optind], leftdir, argv[optind + 1]);
595      left = ck_fopen (lname, "r");
596      rname = expand_name (argv[optind + 1], rightdir, argv[optind]);
597      right = ck_fopen (rname, "r");
598      out = ck_fopen (output, "w");
599
600      diffarg ("--sdiff-merge-assist");
601      diffarg ("--");
602      diffarg (argv[optind]);
603      diffarg (argv[optind + 1]);
604      diffarg (0);
605
606      trapsigs ();
607
608#if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
609      {
610	size_t cmdsize = 1;
611	char *p, *command;
612	int i;
613
614	for (i = 0;  diffargv[i];  i++)
615	  cmdsize += quote_system_arg (0, diffargv[i]) + 1;
616	command = p = xmalloc (cmdsize);
617	for (i = 0;  diffargv[i];  i++)
618	  {
619	    p += quote_system_arg (p, diffargv[i]);
620	    *p++ = ' ';
621	  }
622	p[-1] = 0;
623	errno = 0;
624	diffout = popen (command, "r");
625	if (! diffout)
626	  perror_fatal (command);
627	free (command);
628      }
629#else
630      {
631	int diff_fds[2];
632# if HAVE_WORKING_VFORK
633	sigset_t procmask;
634	sigset_t blocked;
635# endif
636
637	if (pipe (diff_fds) != 0)
638	  perror_fatal ("pipe");
639
640# if HAVE_WORKING_VFORK
641	/* Block SIGINT and SIGPIPE.  */
642	sigemptyset (&blocked);
643	sigaddset (&blocked, SIGINT);
644	sigaddset (&blocked, SIGPIPE);
645	sigprocmask (SIG_BLOCK, &blocked, &procmask);
646# endif
647	diffpid = vfork ();
648	if (diffpid < 0)
649	  perror_fatal ("fork");
650	if (! diffpid)
651	  {
652	    /* Alter the child's SIGINT and SIGPIPE handlers;
653	       this may munge the parent.
654	       The child ignores SIGINT in case the user interrupts the editor.
655	       The child does not ignore SIGPIPE, even if the parent does.  */
656	    if (initial_handler (handler_index_of_SIGINT) != SIG_IGN)
657	      signal_handler (SIGINT, SIG_IGN);
658	    signal_handler (SIGPIPE, SIG_DFL);
659# if HAVE_WORKING_VFORK
660	    /* Stop blocking SIGINT and SIGPIPE in the child.  */
661	    sigprocmask (SIG_SETMASK, &procmask, 0);
662# endif
663	    close (diff_fds[0]);
664	    if (diff_fds[1] != STDOUT_FILENO)
665	      {
666		dup2 (diff_fds[1], STDOUT_FILENO);
667		close (diff_fds[1]);
668	      }
669
670	    execvp (diffargv[0], (char **) diffargv);
671	    _exit (errno == ENOEXEC ? 126 : 127);
672	  }
673
674# if HAVE_WORKING_VFORK
675	/* Restore the parent's SIGINT and SIGPIPE behavior.  */
676	if (initial_handler (handler_index_of_SIGINT) != SIG_IGN)
677	  signal_handler (SIGINT, catchsig);
678	if (initial_handler (handler_index_of_SIGPIPE) != SIG_IGN)
679	  signal_handler (SIGPIPE, catchsig);
680	else
681	  signal_handler (SIGPIPE, SIG_IGN);
682
683	/* Stop blocking SIGINT and SIGPIPE in the parent.  */
684	sigprocmask (SIG_SETMASK, &procmask, 0);
685# endif
686
687	close (diff_fds[1]);
688	diffout = fdopen (diff_fds[0], "r");
689	if (! diffout)
690	  perror_fatal ("fdopen");
691      }
692#endif
693
694      lf_init (&diff_filt, diffout);
695      lf_init (&lfilt, left);
696      lf_init (&rfilt, right);
697
698      interact_ok = interact (&diff_filt, &lfilt, lname, &rfilt, rname, out);
699
700      ck_fclose (left);
701      ck_fclose (right);
702      ck_fclose (out);
703
704      {
705	int wstatus;
706	int werrno = 0;
707
708#if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
709	wstatus = pclose (diffout);
710	if (wstatus == -1)
711	  werrno = errno;
712#else
713	ck_fclose (diffout);
714	while (waitpid (diffpid, &wstatus, 0) < 0)
715	  if (errno == EINTR)
716	    checksigs ();
717	  else
718	    perror_fatal ("waitpid");
719	diffpid = 0;
720#endif
721
722	if (tmpname)
723	  {
724	    unlink (tmpname);
725	    tmpname = 0;
726	  }
727
728	if (! interact_ok)
729	  exiterr ();
730
731	check_child_status (werrno, wstatus, EXIT_FAILURE, diffargv[0]);
732	untrapsig (0);
733	checksigs ();
734	exit (WEXITSTATUS (wstatus));
735      }
736    }
737  return EXIT_SUCCESS;			/* Fool `-Wall'.  */
738}
739
740static void
741diffarg (char const *a)
742{
743  static size_t diffargs, diffarglim;
744
745  if (diffargs == diffarglim)
746    {
747      if (! diffarglim)
748	diffarglim = 16;
749      else if (PTRDIFF_MAX / (2 * sizeof *diffargv) <= diffarglim)
750	xalloc_die ();
751      else
752	diffarglim *= 2;
753      diffargv = xrealloc (diffargv, diffarglim * sizeof *diffargv);
754    }
755  diffargv[diffargs++] = a;
756}
757
758
759
760
761/* Signal handling */
762
763static bool volatile ignore_SIGINT;
764static int volatile signal_received;
765static bool sigs_trapped;
766
767static RETSIGTYPE
768catchsig (int s)
769{
770#if ! HAVE_SIGACTION
771  signal (s, SIG_IGN);
772#endif
773  if (! (s == SIGINT && ignore_SIGINT))
774    signal_received = s;
775}
776
777#if HAVE_SIGACTION
778static struct sigaction catchaction;
779
780static void
781signal_handler (int sig, RETSIGTYPE (*handler) (int))
782{
783  catchaction.sa_handler = handler;
784  sigaction (sig, &catchaction, 0);
785}
786#endif
787
788static void
789trapsigs (void)
790{
791  int i;
792
793#if HAVE_SIGACTION
794  catchaction.sa_flags = SA_RESTART;
795  sigemptyset (&catchaction.sa_mask);
796  for (i = 0;  i < NUM_SIGS;  i++)
797    sigaddset (&catchaction.sa_mask, sigs[i]);
798#endif
799
800  for (i = 0;  i < NUM_SIGS;  i++)
801    {
802#if HAVE_SIGACTION
803      sigaction (sigs[i], 0, &initial_action[i]);
804#else
805      initial_action[i] = signal (sigs[i], SIG_IGN);
806#endif
807      if (initial_handler (i) != SIG_IGN)
808	signal_handler (sigs[i], catchsig);
809    }
810
811#ifdef SIGCHLD
812  /* System V fork+wait does not work if SIGCHLD is ignored.  */
813  signal (SIGCHLD, SIG_DFL);
814#endif
815
816  sigs_trapped = 1;
817}
818
819/* Untrap signal S, or all trapped signals if S is zero.  */
820static void
821untrapsig (int s)
822{
823  int i;
824
825  if (sigs_trapped)
826    for (i = 0;  i < NUM_SIGS;  i++)
827      if ((! s || sigs[i] == s)  &&  initial_handler (i) != SIG_IGN)
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/* Exit if a signal has been received.  */
836static void
837checksigs (void)
838{
839  int s = signal_received;
840  if (s)
841    {
842      cleanup ();
843
844      /* Yield an exit status indicating that a signal was received.  */
845      untrapsig (s);
846      kill (getpid (), s);
847
848      /* That didn't work, so exit with error status.  */
849      exit (EXIT_TROUBLE);
850    }
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 = 0;
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 = 1;
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 = 1;
942		  break;
943		case '\n':
944		  gotcmd = 1;
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 = 1;
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 1;
976	case 'r':
977	  lf_copy (right, rlen, outfile);
978	  lf_skip (left, llen);
979	  return 1;
980	case 's':
981	  suppress_common_lines = 1;
982	  break;
983	case 'v':
984	  suppress_common_lines = 0;
985	  break;
986	case 'q':
987	  return 0;
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) lline);
1011		    else
1012		      fprintf (tmp, "--- %s %ld,%ld\n", lname,
1013			       (long) lline, (long) (lline + llen - 1));
1014		  }
1015		/* Fall through.  */
1016	      case 'b': case 'l':
1017		lf_copy (left, llen, tmp);
1018		break;
1019
1020	      default:
1021		lf_skip (left, llen);
1022		break;
1023	      }
1024
1025	    switch (cmd1)
1026	      {
1027	      case 'd':
1028		if (rlen)
1029		  {
1030		    if (rlen == 1)
1031		      fprintf (tmp, "+++ %s %ld\n", rname, (long) rline);
1032		    else
1033		      fprintf (tmp, "+++ %s %ld,%ld\n", rname,
1034			       (long) rline, (long) (rline + rlen - 1));
1035		  }
1036		/* Fall through.  */
1037	      case 'b': case 'r':
1038		lf_copy (right, rlen, tmp);
1039		break;
1040
1041	      default:
1042		lf_skip (right, rlen);
1043		break;
1044	      }
1045
1046	    ck_fclose (tmp);
1047
1048	    {
1049	      int wstatus;
1050	      int werrno = 0;
1051	      ignore_SIGINT = 1;
1052	      checksigs ();
1053
1054	      {
1055#if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
1056		char *command =
1057		  xmalloc (quote_system_arg (0, editor_program)
1058			   + 1 + strlen (tmpname) + 1);
1059		sprintf (command + quote_system_arg (command, editor_program),
1060			 " %s", tmpname);
1061		wstatus = system (command);
1062		if (wstatus == -1)
1063		  werrno = errno;
1064		free (command);
1065#else
1066		pid_t pid;
1067
1068		pid = vfork ();
1069		if (pid == 0)
1070		  {
1071		    char const *argv[3];
1072		    int i = 0;
1073
1074		    argv[i++] = editor_program;
1075		    argv[i++] = tmpname;
1076		    argv[i] = 0;
1077
1078		    execvp (editor_program, (char **) argv);
1079		    _exit (errno == ENOEXEC ? 126 : 127);
1080		  }
1081
1082		if (pid < 0)
1083		  perror_fatal ("fork");
1084
1085		while (waitpid (pid, &wstatus, 0) < 0)
1086		  if (errno == EINTR)
1087		    checksigs ();
1088		  else
1089		    perror_fatal ("waitpid");
1090#endif
1091	      }
1092
1093	      ignore_SIGINT = 0;
1094	      check_child_status (werrno, wstatus, EXIT_SUCCESS,
1095				  editor_program);
1096	    }
1097
1098	    {
1099	      char buf[SDIFF_BUFSIZE];
1100	      size_t size;
1101	      tmp = ck_fopen (tmpname, "r");
1102	      while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0)
1103		{
1104		  checksigs ();
1105		  ck_fwrite (buf, size, outfile);
1106		}
1107	      ck_fclose (tmp);
1108	    }
1109	    return 1;
1110	  }
1111	default:
1112	  give_help ();
1113	  break;
1114	}
1115    }
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 0;
1174	      break;
1175
1176	    default:
1177	      fatal (diff_help);
1178	    }
1179
1180	  lline += llen;
1181	  rline += rlen;
1182	}
1183    }
1184}
1185
1186/* Return nonzero 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