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