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 or e1:\tEdit then use the left version.\n\
861er or e2:\tEdit then use the right version.\n\
862e:\tDiscard both versions then edit a new one.\n\
863l or 1:\tUse the left version.\n\
864r or 2:\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 '1': case '2': case 'l': case 'r':
921	    case 's': case 'v': case 'q':
922	      if (skip_white () != '\n')
923		{
924		  give_help ();
925		  flush_line ();
926		  continue;
927		}
928	      gotcmd = true;
929	      break;
930
931	    case 'e':
932	      cmd1 = skip_white ();
933	      switch (cmd1)
934		{
935		case '1': case '2': case 'b': case 'd': case 'l': case 'r':
936		  if (skip_white () != '\n')
937		    {
938		      give_help ();
939		      flush_line ();
940		      continue;
941		    }
942		  gotcmd = true;
943		  break;
944		case '\n':
945		  gotcmd = true;
946		  break;
947		default:
948		  give_help ();
949		  flush_line ();
950		  continue;
951		}
952	      break;
953
954	    case EOF:
955	      if (feof (stdin))
956		{
957		  gotcmd = true;
958		  cmd0 = 'q';
959		  break;
960		}
961	      /* Fall through.  */
962	    default:
963	      flush_line ();
964	      /* Fall through.  */
965	    case '\n':
966	      give_help ();
967	      continue;
968	    }
969	}
970
971      switch (cmd0)
972	{
973	case '1': case 'l':
974	  lf_copy (left, llen, outfile);
975	  lf_skip (right, rlen);
976	  return true;
977	case '2': case 'r':
978	  lf_copy (right, rlen, outfile);
979	  lf_skip (left, llen);
980	  return true;
981	case 's':
982	  suppress_common_lines = true;
983	  break;
984	case 'v':
985	  suppress_common_lines = false;
986	  break;
987	case 'q':
988	  return false;
989	case 'e':
990	  {
991	    int fd;
992
993	    if (tmpname)
994	      tmp = fopen (tmpname, "w");
995	    else
996	      {
997		if ((fd = temporary_file ()) < 0)
998		  perror_fatal ("mkstemp");
999		tmp = fdopen (fd, "w");
1000	      }
1001
1002	    if (! tmp)
1003	      perror_fatal (tmpname);
1004
1005	    switch (cmd1)
1006	      {
1007	      case 'd':
1008		if (llen)
1009		  {
1010		    if (llen == 1)
1011		      fprintf (tmp, "--- %s %ld\n", lname, (long int) lline);
1012		    else
1013		      fprintf (tmp, "--- %s %ld,%ld\n", lname,
1014			       (long int) lline,
1015			       (long int) (lline + llen - 1));
1016		  }
1017		/* Fall through.  */
1018	      case '1': case 'b': case 'l':
1019		lf_copy (left, llen, tmp);
1020		break;
1021
1022	      default:
1023		lf_skip (left, llen);
1024		break;
1025	      }
1026
1027	    switch (cmd1)
1028	      {
1029	      case 'd':
1030		if (rlen)
1031		  {
1032		    if (rlen == 1)
1033		      fprintf (tmp, "+++ %s %ld\n", rname, (long int) rline);
1034		    else
1035		      fprintf (tmp, "+++ %s %ld,%ld\n", rname,
1036			       (long int) rline,
1037			       (long int) (rline + rlen - 1));
1038		  }
1039		/* Fall through.  */
1040	      case '2': case 'b': case 'r':
1041		lf_copy (right, rlen, tmp);
1042		break;
1043
1044	      default:
1045		lf_skip (right, rlen);
1046		break;
1047	      }
1048
1049	    ck_fclose (tmp);
1050
1051	    {
1052	      int wstatus;
1053	      int werrno = 0;
1054	      ignore_SIGINT = true;
1055	      checksigs ();
1056
1057	      {
1058#if ! (HAVE_WORKING_FORK || HAVE_WORKING_VFORK)
1059		char *command =
1060		  xmalloc (quote_system_arg (0, editor_program)
1061			   + 1 + strlen (tmpname) + 1);
1062		sprintf (command + quote_system_arg (command, editor_program),
1063			 " %s", tmpname);
1064		wstatus = system (command);
1065		if (wstatus == -1)
1066		  werrno = errno;
1067		free (command);
1068#else
1069		pid_t pid;
1070
1071		pid = vfork ();
1072		if (pid == 0)
1073		  {
1074		    char const *argv[3];
1075		    int i = 0;
1076
1077		    argv[i++] = editor_program;
1078		    argv[i++] = tmpname;
1079		    argv[i] = 0;
1080
1081		    execvp (editor_program, (char **) argv);
1082		    _exit (errno == ENOENT ? 127 : 126);
1083		  }
1084
1085		if (pid < 0)
1086		  perror_fatal ("fork");
1087
1088		while (waitpid (pid, &wstatus, 0) < 0)
1089		  if (errno == EINTR)
1090		    checksigs ();
1091		  else
1092		    perror_fatal ("waitpid");
1093#endif
1094	      }
1095
1096	      ignore_SIGINT = false;
1097	      check_child_status (werrno, wstatus, EXIT_SUCCESS,
1098				  editor_program);
1099	    }
1100
1101	    {
1102	      char buf[SDIFF_BUFSIZE];
1103	      size_t size;
1104	      tmp = ck_fopen (tmpname, "r");
1105	      while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0)
1106		{
1107		  checksigs ();
1108		  ck_fwrite (buf, size, outfile);
1109		}
1110	      ck_fclose (tmp);
1111	    }
1112	    return true;
1113	  }
1114	default:
1115	  give_help ();
1116	  break;
1117	}
1118    }
1119}
1120
1121/* Alternately reveal bursts of diff output and handle user commands.  */
1122static bool
1123interact (struct line_filter *diff,
1124	  struct line_filter *left, char const *lname,
1125	  struct line_filter *right, char const *rname,
1126	  FILE *outfile)
1127{
1128  lin lline = 1, rline = 1;
1129
1130  for (;;)
1131    {
1132      char diff_help[256];
1133      int snarfed = lf_snarf (diff, diff_help, sizeof diff_help);
1134
1135      if (snarfed <= 0)
1136	return snarfed != 0;
1137
1138      checksigs ();
1139
1140      if (diff_help[0] == ' ')
1141	puts (diff_help + 1);
1142      else
1143	{
1144	  char *numend;
1145	  uintmax_t val;
1146	  lin llen, rlen, lenmax;
1147	  errno = 0;
1148	  llen = val = strtoumax (diff_help + 1, &numend, 10);
1149	  if (llen < 0 || llen != val || errno || *numend != ',')
1150	    fatal (diff_help);
1151	  rlen = val = strtoumax (numend + 1, &numend, 10);
1152	  if (rlen < 0 || rlen != val || errno || *numend)
1153	    fatal (diff_help);
1154
1155	  lenmax = MAX (llen, rlen);
1156
1157	  switch (diff_help[0])
1158	    {
1159	    case 'i':
1160	      if (suppress_common_lines)
1161		lf_skip (diff, lenmax);
1162	      else
1163		lf_copy (diff, lenmax, stdout);
1164
1165	      lf_copy (left, llen, outfile);
1166	      lf_skip (right, rlen);
1167	      break;
1168
1169	    case 'c':
1170	      lf_copy (diff, lenmax, stdout);
1171	      if (! edit (left, lname, lline, llen,
1172			  right, rname, rline, rlen,
1173			  outfile))
1174		return false;
1175	      break;
1176
1177	    default:
1178	      fatal (diff_help);
1179	    }
1180
1181	  lline += llen;
1182	  rline += rlen;
1183	}
1184    }
1185}
1186
1187/* Return true if DIR is an existing directory.  */
1188static bool
1189diraccess (char const *dir)
1190{
1191  struct stat buf;
1192  return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode);
1193}
1194
1195#ifndef P_tmpdir
1196# define P_tmpdir "/tmp"
1197#endif
1198#ifndef TMPDIR_ENV
1199# define TMPDIR_ENV "TMPDIR"
1200#endif
1201
1202/* Open a temporary file and return its file descriptor.  Put into
1203   tmpname the address of a newly allocated buffer that holds the
1204   file's name.  Use the prefix "sdiff".  */
1205static int
1206temporary_file (void)
1207{
1208  char const *tmpdir = getenv (TMPDIR_ENV);
1209  char const *dir = tmpdir ? tmpdir : P_tmpdir;
1210  char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1);
1211  int fd;
1212  int e;
1213  sigset_t procmask;
1214  sigset_t blocked;
1215  sprintf (buf, "%s/sdiffXXXXXX", dir);
1216  sigemptyset (&blocked);
1217  sigaddset (&blocked, SIGINT);
1218  sigprocmask (SIG_BLOCK, &blocked, &procmask);
1219  fd = mkstemp (buf);
1220  e = errno;
1221  if (0 <= fd)
1222    tmpname = buf;
1223  sigprocmask (SIG_SETMASK, &procmask, 0);
1224  errno = e;
1225  return fd;
1226}
1227