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