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