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