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