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