Upgrade diffutils from 3.0 to 3.2 on the vendor branch
[dragonfly.git] / contrib / diffutils / src / sdiff.c
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 \f
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 \f
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   bindtextdomain (PACKAGE, LOCALEDIR);
453   textdomain (PACKAGE);
454   c_stack_action (cleanup);
455
456   prog = getenv ("EDITOR");
457   if (prog)
458     editor_program = prog;
459
460   diffarg (DEFAULT_DIFF_PROGRAM);
461
462   /* parse command line args */
463   while ((opt = getopt_long (argc, argv, "abBdEHiI:lo:stvw:WZ", longopts, 0))
464          != -1)
465     {
466       switch (opt)
467         {
468         case 'a':
469           diffarg ("-a");
470           break;
471
472         case 'b':
473           diffarg ("-b");
474           break;
475
476         case 'B':
477           diffarg ("-B");
478           break;
479
480         case 'd':
481           diffarg ("-d");
482           break;
483
484         case 'E':
485           diffarg ("-E");
486           break;
487
488         case 'H':
489           diffarg ("-H");
490           break;
491
492         case 'i':
493           diffarg ("-i");
494           break;
495
496         case 'I':
497           diffarg ("-I");
498           diffarg (optarg);
499           break;
500
501         case 'l':
502           diffarg ("--left-column");
503           break;
504
505         case 'o':
506           output = optarg;
507           break;
508
509         case 's':
510           suppress_common_lines = true;
511           break;
512
513         case 't':
514           diffarg ("-t");
515           break;
516
517         case 'v':
518           version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version,
519                        AUTHORS, (char *) NULL);
520           check_stdout ();
521           return EXIT_SUCCESS;
522
523         case 'w':
524           diffarg ("-W");
525           diffarg (optarg);
526           break;
527
528         case 'W':
529           diffarg ("-w");
530           break;
531
532         case 'Z':
533           diffarg ("-Z");
534           break;
535
536         case DIFF_PROGRAM_OPTION:
537           diffargv[0] = optarg;
538           break;
539
540         case HELP_OPTION:
541           usage ();
542           check_stdout ();
543           return EXIT_SUCCESS;
544
545         case STRIP_TRAILING_CR_OPTION:
546           diffarg ("--strip-trailing-cr");
547           break;
548
549         case TABSIZE_OPTION:
550           diffarg ("--tabsize");
551           diffarg (optarg);
552           break;
553
554         default:
555           try_help (0, 0);
556         }
557     }
558
559   if (argc - optind != 2)
560     {
561       if (argc - optind < 2)
562         try_help ("missing operand after `%s'", argv[argc - 1]);
563       else
564         try_help ("extra operand `%s'", argv[optind + 2]);
565     }
566
567   if (! output)
568     {
569       /* easy case: diff does everything for us */
570       if (suppress_common_lines)
571         diffarg ("--suppress-common-lines");
572       diffarg ("-y");
573       diffarg ("--");
574       diffarg (argv[optind]);
575       diffarg (argv[optind + 1]);
576       diffarg (0);
577       execvp (diffargv[0], (char **) diffargv);
578       perror_fatal (diffargv[0]);
579     }
580   else
581     {
582       char const *lname, *rname;
583       FILE *left, *right, *out, *diffout;
584       bool interact_ok;
585       struct line_filter lfilt;
586       struct line_filter rfilt;
587       struct line_filter diff_filt;
588       bool leftdir = diraccess (argv[optind]);
589       bool rightdir = diraccess (argv[optind + 1]);
590
591       if (leftdir & rightdir)
592         fatal ("both files to be compared are directories");
593
594       lname = expand_name (argv[optind], leftdir, argv[optind + 1]);
595       left = ck_fopen (lname, "r");
596       rname = expand_name (argv[optind + 1], rightdir, argv[optind]);
597       right = ck_fopen (rname, "r");
598       out = ck_fopen (output, "w");
599
600       diffarg ("--sdiff-merge-assist");
601       diffarg ("--");
602       diffarg (argv[optind]);
603       diffarg (argv[optind + 1]);
604       diffarg (0);
605
606       trapsigs ();
607
608 #if ! HAVE_WORKING_FORK
609       {
610         size_t cmdsize = 1;
611         char *p, *command;
612         int i;
613
614         for (i = 0;  diffargv[i];  i++)
615           cmdsize += shell_quote_length (diffargv[i]) + 1;
616         command = p = xmalloc (cmdsize);
617         for (i = 0;  diffargv[i];  i++)
618           {
619             p = shell_quote_copy (p, diffargv[i]);
620             *p++ = ' ';
621           }
622         p[-1] = 0;
623         errno = 0;
624         diffout = popen (command, "r");
625         if (! diffout)
626           perror_fatal (command);
627         free (command);
628       }
629 #else
630       {
631         int diff_fds[2];
632
633         if (pipe (diff_fds) != 0)
634           perror_fatal ("pipe");
635
636         diffpid = fork ();
637         if (diffpid < 0)
638           perror_fatal ("fork");
639         if (! diffpid)
640           {
641             /* Alter the child's SIGINT and SIGPIPE handlers;
642                this may munge the parent.
643                The child ignores SIGINT in case the user interrupts the editor.
644                The child does not ignore SIGPIPE, even if the parent does.  */
645             if (initial_handler (handler_index_of_SIGINT) != SIG_IGN)
646               signal_handler (SIGINT, SIG_IGN);
647             signal_handler (SIGPIPE, SIG_DFL);
648             close (diff_fds[0]);
649             if (diff_fds[1] != STDOUT_FILENO)
650               {
651                 dup2 (diff_fds[1], STDOUT_FILENO);
652                 close (diff_fds[1]);
653               }
654
655             execvp (diffargv[0], (char **) diffargv);
656             _exit (errno == ENOENT ? 127 : 126);
657           }
658
659         close (diff_fds[1]);
660         diffout = fdopen (diff_fds[0], "r");
661         if (! diffout)
662           perror_fatal ("fdopen");
663       }
664 #endif
665
666       lf_init (&diff_filt, diffout);
667       lf_init (&lfilt, left);
668       lf_init (&rfilt, right);
669
670       interact_ok = interact (&diff_filt, &lfilt, lname, &rfilt, rname, out);
671
672       ck_fclose (left);
673       ck_fclose (right);
674       ck_fclose (out);
675
676       {
677         int wstatus;
678         int werrno = 0;
679
680 #if ! HAVE_WORKING_FORK
681         wstatus = pclose (diffout);
682         if (wstatus == -1)
683           werrno = errno;
684 #else
685         ck_fclose (diffout);
686         while (waitpid (diffpid, &wstatus, 0) < 0)
687           if (errno == EINTR)
688             checksigs ();
689           else
690             perror_fatal ("waitpid");
691         diffpid = 0;
692 #endif
693
694         if (tmpname)
695           {
696             unlink (tmpname);
697             tmpname = 0;
698           }
699
700         if (! interact_ok)
701           exiterr ();
702
703         check_child_status (werrno, wstatus, EXIT_FAILURE, diffargv[0]);
704         untrapsig (0);
705         checksigs ();
706         exit (WEXITSTATUS (wstatus));
707       }
708     }
709   return EXIT_SUCCESS;                  /* Fool `-Wall'.  */
710 }
711
712 static void
713 diffarg (char const *a)
714 {
715   static size_t diffargs, diffarglim;
716
717   if (diffargs == diffarglim)
718     {
719       if (! diffarglim)
720         diffarglim = 16;
721       else if (PTRDIFF_MAX / (2 * sizeof *diffargv) <= diffarglim)
722         xalloc_die ();
723       else
724         diffarglim *= 2;
725       diffargv = xrealloc (diffargv, diffarglim * sizeof *diffargv);
726     }
727   diffargv[diffargs++] = a;
728 }
729 \f
730 /* Signal handling */
731
732 static bool volatile ignore_SIGINT;
733 static int volatile signal_received;
734 static bool sigs_trapped;
735
736 static void
737 catchsig (int s)
738 {
739 #if ! HAVE_SIGACTION
740   signal (s, SIG_IGN);
741 #endif
742   if (! (s == SIGINT && ignore_SIGINT))
743     signal_received = s;
744 }
745
746 #if HAVE_SIGACTION
747 static struct sigaction catchaction;
748
749 static void
750 signal_handler (int sig, void (*handler) (int))
751 {
752   catchaction.sa_handler = handler;
753   sigaction (sig, &catchaction, 0);
754 }
755 #endif
756
757 static void
758 trapsigs (void)
759 {
760   int i;
761
762 #if HAVE_SIGACTION
763   catchaction.sa_flags = SA_RESTART;
764   sigemptyset (&catchaction.sa_mask);
765   for (i = 0;  i < NUM_SIGS;  i++)
766     sigaddset (&catchaction.sa_mask, sigs[i]);
767 #endif
768
769   for (i = 0;  i < NUM_SIGS;  i++)
770     {
771 #if HAVE_SIGACTION
772       sigaction (sigs[i], 0, &initial_action[i]);
773 #else
774       initial_action[i] = signal (sigs[i], SIG_IGN);
775 #endif
776       if (initial_handler (i) != SIG_IGN)
777         signal_handler (sigs[i], catchsig);
778     }
779
780 #ifdef SIGCHLD
781   /* System V fork+wait does not work if SIGCHLD is ignored.  */
782   signal (SIGCHLD, SIG_DFL);
783 #endif
784
785   sigs_trapped = true;
786 }
787
788 /* Untrap signal S, or all trapped signals if S is zero.  */
789 static void
790 untrapsig (int s)
791 {
792   int i;
793
794   if (sigs_trapped)
795     for (i = 0;  i < NUM_SIGS;  i++)
796       if ((! s || sigs[i] == s)  &&  initial_handler (i) != SIG_IGN)
797         {
798 #if HAVE_SIGACTION
799           sigaction (sigs[i], &initial_action[i], 0);
800 #else
801           signal (sigs[i], initial_action[i]);
802 #endif
803         }
804 }
805
806 /* Exit if a signal has been received.  */
807 static void
808 checksigs (void)
809 {
810   int s = signal_received;
811   if (s)
812     {
813       cleanup (0);
814
815       /* Yield an exit status indicating that a signal was received.  */
816       untrapsig (s);
817       kill (getpid (), s);
818
819       /* That didn't work, so exit with error status.  */
820       exit (EXIT_TROUBLE);
821     }
822 }
823 \f
824 static void
825 give_help (void)
826 {
827   fprintf (stderr, "%s", _("\
828 ed:\tEdit then use both versions, each decorated with a header.\n\
829 eb:\tEdit then use both versions.\n\
830 el or e1:\tEdit then use the left version.\n\
831 er or e2:\tEdit then use the right version.\n\
832 e:\tDiscard both versions then edit a new one.\n\
833 l or 1:\tUse the left version.\n\
834 r or 2:\tUse the right version.\n\
835 s:\tSilently include common lines.\n\
836 v:\tVerbosely include common lines.\n\
837 q:\tQuit.\n\
838 "));
839 }
840
841 static int
842 skip_white (void)
843 {
844   int c;
845   for (;;)
846     {
847       c = getchar ();
848       if (! isspace (c) || c == '\n')
849         break;
850       checksigs ();
851     }
852   if (ferror (stdin))
853     perror_fatal (_("read failed"));
854   return c;
855 }
856
857 static void
858 flush_line (void)
859 {
860   int c;
861   while ((c = getchar ()) != '\n' && c != EOF)
862     continue;
863   if (ferror (stdin))
864     perror_fatal (_("read failed"));
865 }
866
867
868 /* interpret an edit command */
869 static bool
870 edit (struct line_filter *left, char const *lname, lin lline, lin llen,
871       struct line_filter *right, char const *rname, lin rline, lin rlen,
872       FILE *outfile)
873 {
874   for (;;)
875     {
876       int cmd0 IF_LINT (= 0);
877       int cmd1 IF_LINT (= 0);
878       bool gotcmd = false;
879
880       while (! gotcmd)
881         {
882           if (putchar ('%') != '%')
883             perror_fatal (_("write failed"));
884           ck_fflush (stdout);
885
886           cmd0 = skip_white ();
887           switch (cmd0)
888             {
889             case '1': case '2': case 'l': case 'r':
890             case 's': case 'v': case 'q':
891               if (skip_white () != '\n')
892                 {
893                   give_help ();
894                   flush_line ();
895                   continue;
896                 }
897               gotcmd = true;
898               break;
899
900             case 'e':
901               cmd1 = skip_white ();
902               switch (cmd1)
903                 {
904                 case '1': case '2': case 'b': case 'd': case 'l': case 'r':
905                   if (skip_white () != '\n')
906                     {
907                       give_help ();
908                       flush_line ();
909                       continue;
910                     }
911                   gotcmd = true;
912                   break;
913                 case '\n':
914                   gotcmd = true;
915                   break;
916                 default:
917                   give_help ();
918                   flush_line ();
919                   continue;
920                 }
921               break;
922
923             case EOF:
924               if (feof (stdin))
925                 {
926                   gotcmd = true;
927                   cmd0 = 'q';
928                   break;
929                 }
930               /* Fall through.  */
931             default:
932               flush_line ();
933               /* Fall through.  */
934             case '\n':
935               give_help ();
936               continue;
937             }
938         }
939
940       switch (cmd0)
941         {
942         case '1': case 'l':
943           lf_copy (left, llen, outfile);
944           lf_skip (right, rlen);
945           return true;
946         case '2': case 'r':
947           lf_copy (right, rlen, outfile);
948           lf_skip (left, llen);
949           return true;
950         case 's':
951           suppress_common_lines = true;
952           break;
953         case 'v':
954           suppress_common_lines = false;
955           break;
956         case 'q':
957           return false;
958         case 'e':
959           {
960             int fd;
961
962             if (tmpname)
963               tmp = fopen (tmpname, "w");
964             else
965               {
966                 if ((fd = temporary_file ()) < 0)
967                   perror_fatal ("mkstemp");
968                 tmp = fdopen (fd, "w");
969               }
970
971             if (! tmp)
972               perror_fatal (tmpname);
973
974             switch (cmd1)
975               {
976               case 'd':
977                 if (llen)
978                   {
979                     if (llen == 1)
980                       fprintf (tmp, "--- %s %ld\n", lname, (long int) lline);
981                     else
982                       fprintf (tmp, "--- %s %ld,%ld\n", lname,
983                                (long int) lline,
984                                (long int) (lline + llen - 1));
985                   }
986                 /* Fall through.  */
987               case '1': case 'b': case 'l':
988                 lf_copy (left, llen, tmp);
989                 break;
990
991               default:
992                 lf_skip (left, llen);
993                 break;
994               }
995
996             switch (cmd1)
997               {
998               case 'd':
999                 if (rlen)
1000                   {
1001                     if (rlen == 1)
1002                       fprintf (tmp, "+++ %s %ld\n", rname, (long int) rline);
1003                     else
1004                       fprintf (tmp, "+++ %s %ld,%ld\n", rname,
1005                                (long int) rline,
1006                                (long int) (rline + rlen - 1));
1007                   }
1008                 /* Fall through.  */
1009               case '2': case 'b': case 'r':
1010                 lf_copy (right, rlen, tmp);
1011                 break;
1012
1013               default:
1014                 lf_skip (right, rlen);
1015                 break;
1016               }
1017
1018             ck_fclose (tmp);
1019
1020             {
1021               int wstatus;
1022               int werrno = 0;
1023               ignore_SIGINT = true;
1024               checksigs ();
1025
1026               {
1027 #if ! HAVE_WORKING_FORK
1028                 char *command =
1029                   xmalloc (shell_quote_length (editor_program)
1030                            + 1 + strlen (tmpname) + 1);
1031                 sprintf (shell_quote_copy (command, editor_program),
1032                          " %s", tmpname);
1033                 wstatus = system (command);
1034                 if (wstatus == -1)
1035                   werrno = errno;
1036                 free (command);
1037 #else
1038                 pid_t pid;
1039
1040                 pid = fork ();
1041                 if (pid == 0)
1042                   {
1043                     char const *argv[3];
1044                     int i = 0;
1045
1046                     argv[i++] = editor_program;
1047                     argv[i++] = tmpname;
1048                     argv[i] = 0;
1049
1050                     execvp (editor_program, (char **) argv);
1051                     _exit (errno == ENOENT ? 127 : 126);
1052                   }
1053
1054                 if (pid < 0)
1055                   perror_fatal ("fork");
1056
1057                 while (waitpid (pid, &wstatus, 0) < 0)
1058                   if (errno == EINTR)
1059                     checksigs ();
1060                   else
1061                     perror_fatal ("waitpid");
1062 #endif
1063               }
1064
1065               ignore_SIGINT = false;
1066               check_child_status (werrno, wstatus, EXIT_SUCCESS,
1067                                   editor_program);
1068             }
1069
1070             {
1071               char buf[SDIFF_BUFSIZE];
1072               size_t size;
1073               tmp = ck_fopen (tmpname, "r");
1074               while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0)
1075                 {
1076                   checksigs ();
1077                   ck_fwrite (buf, size, outfile);
1078                 }
1079               ck_fclose (tmp);
1080             }
1081             return true;
1082           }
1083         default:
1084           give_help ();
1085           break;
1086         }
1087     }
1088 }
1089 \f
1090 /* Alternately reveal bursts of diff output and handle user commands.  */
1091 static bool
1092 interact (struct line_filter *diff,
1093           struct line_filter *left, char const *lname,
1094           struct line_filter *right, char const *rname,
1095           FILE *outfile)
1096 {
1097   lin lline = 1, rline = 1;
1098
1099   for (;;)
1100     {
1101       char diff_help[256];
1102       int snarfed = lf_snarf (diff, diff_help, sizeof diff_help);
1103
1104       if (snarfed <= 0)
1105         return snarfed != 0;
1106
1107       checksigs ();
1108
1109       if (diff_help[0] == ' ')
1110         puts (diff_help + 1);
1111       else
1112         {
1113           char *numend;
1114           uintmax_t val;
1115           lin llen, rlen, lenmax;
1116           errno = 0;
1117           llen = val = strtoumax (diff_help + 1, &numend, 10);
1118           if (llen < 0 || llen != val || errno || *numend != ',')
1119             fatal (diff_help);
1120           rlen = val = strtoumax (numend + 1, &numend, 10);
1121           if (rlen < 0 || rlen != val || errno || *numend)
1122             fatal (diff_help);
1123
1124           lenmax = MAX (llen, rlen);
1125
1126           switch (diff_help[0])
1127             {
1128             case 'i':
1129               if (suppress_common_lines)
1130                 lf_skip (diff, lenmax);
1131               else
1132                 lf_copy (diff, lenmax, stdout);
1133
1134               lf_copy (left, llen, outfile);
1135               lf_skip (right, rlen);
1136               break;
1137
1138             case 'c':
1139               lf_copy (diff, lenmax, stdout);
1140               if (! edit (left, lname, lline, llen,
1141                           right, rname, rline, rlen,
1142                           outfile))
1143                 return false;
1144               break;
1145
1146             default:
1147               fatal (diff_help);
1148             }
1149
1150           lline += llen;
1151           rline += rlen;
1152         }
1153     }
1154 }
1155
1156 /* Return true if DIR is an existing directory.  */
1157 static bool
1158 diraccess (char const *dir)
1159 {
1160   struct stat buf;
1161   return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode);
1162 }
1163
1164 #ifndef P_tmpdir
1165 # define P_tmpdir "/tmp"
1166 #endif
1167 #ifndef TMPDIR_ENV
1168 # define TMPDIR_ENV "TMPDIR"
1169 #endif
1170
1171 /* Open a temporary file and return its file descriptor.  Put into
1172    tmpname the address of a newly allocated buffer that holds the
1173    file's name.  Use the prefix "sdiff".  */
1174 static int
1175 temporary_file (void)
1176 {
1177   char const *tmpdir = getenv (TMPDIR_ENV);
1178   char const *dir = tmpdir ? tmpdir : P_tmpdir;
1179   char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1);
1180   int fd;
1181   int e;
1182   sigset_t procmask;
1183   sigset_t blocked;
1184   sprintf (buf, "%s/sdiffXXXXXX", dir);
1185   sigemptyset (&blocked);
1186   sigaddset (&blocked, SIGINT);
1187   sigprocmask (SIG_BLOCK, &blocked, &procmask);
1188   fd = mkstemp (buf);
1189   e = errno;
1190   if (0 <= fd)
1191     tmpname = buf;
1192   sigprocmask (SIG_SETMASK, &procmask, 0);
1193   errno = e;
1194   return fd;
1195 }