1/* sdiff - side-by-side merge of file differences
2
3   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 2001, 2002 Free
4   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
25#include <c-stack.h>
26#include <dirname.h>
27#include <error.h>
28#include <exitfail.h>
29#include <freesoft.h>
30#include <getopt.h>
31#include <quotesys.h>
32#include <stdio.h>
33#include <xalloc.h>
34
35static char const authorship_msgid[] = N_("Written by Thomas Lord.");
36
37static char const copyright_string[] =
38  "Copyright (C) 2002 Free Software Foundation, Inc.";
39
40extern char const version_string[];
41
42/* Size of chunks read from files which must be parsed into lines.  */
43#define SDIFF_BUFSIZE ((size_t) 65536)
44
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