(root)/
diffutils-3.10/
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-2023 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  static char const 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  enum { SDIFF_BUFSIZE = 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 _Noreturn void fatal (char const *);
      67  static _Noreturn void perror_fatal (char const *);
      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 _Noreturn void
     155  try_help (char const *reason_msgid, char const *operand)
     156  {
     157    if (reason_msgid)
     158      error (0, 0, _(reason_msgid), operand);
     159    die (EXIT_TROUBLE, 0, _("Try '%s --help' for more information."),
     160           program_name);
     161  }
     162  
     163  static void
     164  check_stdout (void)
     165  {
     166    if (ferror (stdout))
     167      fatal ("write failed");
     168    else if (fclose (stdout) != 0)
     169      perror_fatal (_("standard output"));
     170  }
     171  
     172  static char const * const option_help_msgid[] = {
     173    N_("-o, --output=FILE            operate interactively, sending output to FILE"),
     174    "",
     175    N_("-i, --ignore-case            consider upper- and lower-case to be the same"),
     176    N_("-E, --ignore-tab-expansion   ignore changes due to tab expansion"),
     177    N_("-Z, --ignore-trailing-space  ignore white space at line end"),
     178    N_("-b, --ignore-space-change    ignore changes in the amount of white space"),
     179    N_("-W, --ignore-all-space       ignore all white space"),
     180    N_("-B, --ignore-blank-lines     ignore changes whose lines are all blank"),
     181    N_("-I, --ignore-matching-lines=RE  ignore changes all whose lines match RE"),
     182    N_("    --strip-trailing-cr      strip trailing carriage return on input"),
     183    N_("-a, --text                   treat all files as text"),
     184    "",
     185    N_("-w, --width=NUM              output at most NUM (default 130) print columns"),
     186    N_("-l, --left-column            output only the left column of common lines"),
     187    N_("-s, --suppress-common-lines  do not output common lines"),
     188    "",
     189    N_("-t, --expand-tabs            expand tabs to spaces in output"),
     190    N_("    --tabsize=NUM            tab stops at every NUM (default 8) print columns"),
     191    "",
     192    N_("-d, --minimal                try hard to find a smaller set of changes"),
     193    N_("-H, --speed-large-files      assume large files, many scattered small changes"),
     194    N_("    --diff-program=PROGRAM   use PROGRAM to compare files"),
     195    "",
     196    N_("    --help                   display this help and exit"),
     197    N_("-v, --version                output version information and exit"),
     198    0
     199  };
     200  
     201  static void
     202  usage (void)
     203  {
     204    char const * const *p;
     205  
     206    printf (_("Usage: %s [OPTION]... FILE1 FILE2\n"), program_name);
     207    printf ("%s\n\n",
     208            _("Side-by-side merge of differences between FILE1 and FILE2."));
     209  
     210    fputs (_("\
     211  Mandatory arguments to long options are mandatory for short options too.\n\
     212  "), stdout);
     213    for (p = option_help_msgid;  *p;  p++)
     214      if (**p)
     215        printf ("  %s\n", _(*p));
     216      else
     217        putchar ('\n');
     218    printf ("\n%s\n%s\n",
     219            _("If a FILE is '-', read standard input."),
     220            _("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."));
     221    emit_bug_reporting_address ();
     222  }
     223  
     224  /* Clean up after a signal or other failure.  This function is
     225     async-signal-safe.  */
     226  static void
     227  cleanup (_GL_UNUSED int signo)
     228  {
     229  #if HAVE_WORKING_FORK
     230    if (0 < diffpid)
     231      kill (diffpid, SIGPIPE);
     232  #endif
     233    if (tmpname)
     234      unlink (tmpname);
     235  }
     236  
     237  static _Noreturn void
     238  exiterr (void)
     239  {
     240    cleanup (0);
     241    untrapsig (0);
     242    checksigs ();
     243    exit (EXIT_TROUBLE);
     244  }
     245  
     246  static void
     247  fatal (char const *msgid)
     248  {
     249    error (0, 0, "%s", _(msgid));
     250    exiterr ();
     251  }
     252  
     253  static void
     254  perror_fatal (char const *msg)
     255  {
     256    int e = errno;
     257    checksigs ();
     258    error (0, e, "%s", msg);
     259    exiterr ();
     260  }
     261  
     262  static void
     263  check_child_status (int werrno, int wstatus, int max_ok_status,
     264                      char const *subsidiary_program)
     265  {
     266    int status = (! werrno && WIFEXITED (wstatus)
     267                  ? WEXITSTATUS (wstatus)
     268                  : INT_MAX);
     269  
     270    if (max_ok_status < status)
     271      {
     272        error (0, werrno,
     273               _(status == 126
     274                 ? "subsidiary program '%s' could not be invoked"
     275                 : status == 127
     276                 ? "subsidiary program '%s' not found"
     277                 : status == INT_MAX
     278                 ? "subsidiary program '%s' failed"
     279                 : "subsidiary program '%s' failed (exit status %d)"),
     280               subsidiary_program, status);
     281        exiterr ();
     282      }
     283  }
     284  
     285  static FILE *
     286  ck_fopen (char const *fname, char const *type)
     287  {
     288    FILE *r = fopen (fname, type);
     289    if (! r)
     290      perror_fatal (fname);
     291    return r;
     292  }
     293  
     294  static void
     295  ck_fclose (FILE *f)
     296  {
     297    if (fclose (f))
     298      perror_fatal ("fclose");
     299  }
     300  
     301  static size_t
     302  ck_fread (char *buf, size_t size, FILE *f)
     303  {
     304    size_t r = fread (buf, sizeof (char), size, f);
     305    if (r == 0 && ferror (f))
     306      perror_fatal (_("read failed"));
     307    return r;
     308  }
     309  
     310  static void
     311  ck_fwrite (char const *buf, size_t size, FILE *f)
     312  {
     313    if (fwrite (buf, sizeof (char), size, f) != size)
     314      perror_fatal (_("write failed"));
     315  }
     316  
     317  static void
     318  ck_fflush (FILE *f)
     319  {
     320    if (fflush (f) != 0)
     321      perror_fatal (_("write failed"));
     322  }
     323  
     324  static char const *
     325  expand_name (char *name, bool is_dir, char const *other_name)
     326  {
     327    if (STREQ (name, "-"))
     328      fatal ("cannot interactively merge standard input");
     329    if (! is_dir)
     330      return name;
     331    else
     332      {
     333        /* Yield NAME/BASE, where BASE is OTHER_NAME's basename.  */
     334        char const *base = last_component (other_name);
     335        size_t namelen = strlen (name), baselen = base_len (base);
     336        bool insert_slash = *last_component (name) && name[namelen - 1] != '/';
     337        char *r = xmalloc (namelen + insert_slash + baselen + 1);
     338        char *p = stpcpy (r, name);
     339        *p = '/';
     340        p = mempcpy (p + insert_slash, base, baselen);
     341        *p = '\0';
     342        return r;
     343      }
     344  }
     345  
     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 = rawmemchr (lf->bufpos, '\n');
     382        if (lf->bufpos == lf->buflim)
     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 = rawmemchr (lf->bufpos, '\n');
     406        if (lf->bufpos == lf->buflim)
     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 = rawmemchr (start, '\n');
     427        size_t s = next - start;
     428        if (bufsize <= s)
     429          return 0;
     430        buffer = mempcpy (buffer, start, s);
     431        bufsize -= s;
     432        if (next < lf->buflim)
     433          {
     434            *buffer = 0;
     435            lf->bufpos = next + 1;
     436            return 1;
     437          }
     438        if (! lf_refill (lf))
     439          return s ? 0 : EOF;
     440      }
     441  }
     442  
     443  int
     444  main (int argc, char *argv[])
     445  {
     446    int opt;
     447    char const *prog;
     448  
     449    exit_failure = EXIT_TROUBLE;
     450    initialize_main (&argc, &argv);
     451    set_program_name (argv[0]);
     452    setlocale (LC_ALL, "");
     453    bindtextdomain (PACKAGE, LOCALEDIR);
     454    textdomain (PACKAGE);
     455    c_stack_action (cleanup);
     456    xstdopen ();
     457  
     458    prog = getenv ("EDITOR");
     459    if (prog)
     460      editor_program = prog;
     461  
     462    diffarg (DEFAULT_DIFF_PROGRAM);
     463  
     464    /* parse command line args */
     465    while ((opt = getopt_long (argc, argv, "abBdEHiI:lo:stvw:WZ", longopts, 0))
     466           != -1)
     467      {
     468        switch (opt)
     469          {
     470          case 'a':
     471            diffarg ("-a");
     472            break;
     473  
     474          case 'b':
     475            diffarg ("-b");
     476            break;
     477  
     478          case 'B':
     479            diffarg ("-B");
     480            break;
     481  
     482          case 'd':
     483            diffarg ("-d");
     484            break;
     485  
     486          case 'E':
     487            diffarg ("-E");
     488            break;
     489  
     490          case 'H':
     491            diffarg ("-H");
     492            break;
     493  
     494          case 'i':
     495            diffarg ("-i");
     496            break;
     497  
     498          case 'I':
     499            diffarg ("-I");
     500            diffarg (optarg);
     501            break;
     502  
     503          case 'l':
     504            diffarg ("--left-column");
     505            break;
     506  
     507          case 'o':
     508            output = optarg;
     509            break;
     510  
     511          case 's':
     512            suppress_common_lines = true;
     513            break;
     514  
     515          case 't':
     516            diffarg ("-t");
     517            break;
     518  
     519          case 'v':
     520            version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version,
     521                         AUTHORS, nullptr);
     522            check_stdout ();
     523            return EXIT_SUCCESS;
     524  
     525          case 'w':
     526            diffarg ("-W");
     527            diffarg (optarg);
     528            break;
     529  
     530          case 'W':
     531            diffarg ("-w");
     532            break;
     533  
     534          case 'Z':
     535            diffarg ("-Z");
     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
     611        {
     612          char *command = system_quote_argv (SCI_SYSTEM, (char **) diffargv);
     613          errno = 0;
     614          diffout = popen (command, "r");
     615          if (! diffout)
     616            perror_fatal (command);
     617          free (command);
     618        }
     619  #else
     620        {
     621          int diff_fds[2];
     622  
     623          if (pipe (diff_fds) != 0)
     624            perror_fatal ("pipe");
     625  
     626          diffpid = fork ();
     627          if (diffpid < 0)
     628            perror_fatal ("fork");
     629          if (! diffpid)
     630            {
     631              /* Alter the child's SIGINT and SIGPIPE handlers;
     632                 this may munge the parent.
     633                 The child ignores SIGINT in case the user interrupts the editor.
     634                 The child does not ignore SIGPIPE, even if the parent does.  */
     635              if (initial_handler (handler_index_of_SIGINT) != SIG_IGN)
     636                signal_handler (SIGINT, SIG_IGN);
     637              signal_handler (SIGPIPE, SIG_DFL);
     638              close (diff_fds[0]);
     639              if (diff_fds[1] != STDOUT_FILENO)
     640                {
     641                  dup2 (diff_fds[1], STDOUT_FILENO);
     642                  close (diff_fds[1]);
     643                }
     644  
     645              execvp (diffargv[0], (char **) diffargv);
     646              _exit (errno == ENOENT ? 127 : 126);
     647            }
     648  
     649          close (diff_fds[1]);
     650          diffout = fdopen (diff_fds[0], "r");
     651          if (! diffout)
     652            perror_fatal ("fdopen");
     653        }
     654  #endif
     655  
     656        lf_init (&diff_filt, diffout);
     657        lf_init (&lfilt, left);
     658        lf_init (&rfilt, right);
     659  
     660        interact_ok = interact (&diff_filt, &lfilt, lname, &rfilt, rname, out);
     661  
     662        ck_fclose (left);
     663        ck_fclose (right);
     664        ck_fclose (out);
     665  
     666        {
     667          int wstatus;
     668          int werrno = 0;
     669  
     670  #if ! HAVE_WORKING_FORK
     671          wstatus = pclose (diffout);
     672          if (wstatus == -1)
     673            werrno = errno;
     674  #else
     675          ck_fclose (diffout);
     676          while (waitpid (diffpid, &wstatus, 0) < 0)
     677            if (errno == EINTR)
     678              checksigs ();
     679            else
     680              perror_fatal ("waitpid");
     681          diffpid = 0;
     682  #endif
     683  
     684          if (tmpname)
     685            {
     686              unlink (tmpname);
     687              tmpname = 0;
     688            }
     689  
     690          if (! interact_ok)
     691            exiterr ();
     692  
     693          check_child_status (werrno, wstatus, EXIT_FAILURE, diffargv[0]);
     694          untrapsig (0);
     695          checksigs ();
     696          exit (WEXITSTATUS (wstatus));
     697        }
     698      }
     699    return EXIT_SUCCESS;			/* Fool '-Wall'.  */
     700  }
     701  
     702  static void
     703  diffarg (char const *a)
     704  {
     705    static size_t diffargs, diffarglim;
     706  
     707    if (diffargs == diffarglim)
     708      {
     709        if (! diffarglim)
     710          diffarglim = 16;
     711        else if (PTRDIFF_MAX / (2 * sizeof *diffargv) <= diffarglim)
     712          xalloc_die ();
     713        else
     714          diffarglim *= 2;
     715        diffargv = xrealloc (diffargv, diffarglim * sizeof *diffargv);
     716      }
     717    diffargv[diffargs++] = a;
     718  }
     719  
     720  /* Signal handling */
     721  
     722  static bool volatile ignore_SIGINT;
     723  static int volatile signal_received;
     724  static bool sigs_trapped;
     725  
     726  static void
     727  catchsig (int s)
     728  {
     729  #if ! HAVE_SIGACTION
     730    signal (s, SIG_IGN);
     731  #endif
     732    if (! (s == SIGINT && ignore_SIGINT))
     733      signal_received = s;
     734  }
     735  
     736  #if HAVE_SIGACTION
     737  static struct sigaction catchaction;
     738  
     739  static void
     740  signal_handler (int sig, void (*handler) (int))
     741  {
     742    catchaction.sa_handler = handler;
     743    sigaction (sig, &catchaction, 0);
     744  }
     745  #endif
     746  
     747  static void
     748  trapsigs (void)
     749  {
     750    int i;
     751  
     752  #if HAVE_SIGACTION
     753    catchaction.sa_flags = SA_RESTART;
     754    sigemptyset (&catchaction.sa_mask);
     755    for (i = 0;  i < NUM_SIGS;  i++)
     756      sigaddset (&catchaction.sa_mask, sigs[i]);
     757  #endif
     758  
     759    for (i = 0;  i < NUM_SIGS;  i++)
     760      {
     761  #if HAVE_SIGACTION
     762        sigaction (sigs[i], 0, &initial_action[i]);
     763  #else
     764        initial_action[i] = signal (sigs[i], SIG_IGN);
     765  #endif
     766        if (initial_handler (i) != SIG_IGN)
     767          signal_handler (sigs[i], catchsig);
     768      }
     769  
     770  #ifdef SIGCHLD
     771    /* System V fork+wait does not work if SIGCHLD is ignored.  */
     772    signal (SIGCHLD, SIG_DFL);
     773  #endif
     774  
     775    sigs_trapped = true;
     776  }
     777  
     778  /* Untrap signal S, or all trapped signals if S is zero.  */
     779  static void
     780  untrapsig (int s)
     781  {
     782    int i;
     783  
     784    if (sigs_trapped)
     785      for (i = 0;  i < NUM_SIGS;  i++)
     786        if ((! s || sigs[i] == s)  &&  initial_handler (i) != SIG_IGN)
     787          {
     788  #if HAVE_SIGACTION
     789            sigaction (sigs[i], &initial_action[i], 0);
     790  #else
     791            signal (sigs[i], initial_action[i]);
     792  #endif
     793          }
     794  }
     795  
     796  /* Exit if a signal has been received.  */
     797  static void
     798  checksigs (void)
     799  {
     800    int s = signal_received;
     801    if (s)
     802      {
     803        cleanup (0);
     804  
     805        /* Yield an exit status indicating that a signal was received.  */
     806        untrapsig (s);
     807        raise (s);
     808  
     809        /* That didn't work, so exit with error status.  */
     810        exit (EXIT_TROUBLE);
     811      }
     812  }
     813  
     814  static void
     815  give_help (void)
     816  {
     817    fprintf (stderr, "%s", _("\
     818  ed:\tEdit then use both versions, each decorated with a header.\n\
     819  eb:\tEdit then use both versions.\n\
     820  el or e1:\tEdit then use the left version.\n\
     821  er or e2:\tEdit then use the right version.\n\
     822  e:\tDiscard both versions then edit a new one.\n\
     823  l or 1:\tUse the left version.\n\
     824  r or 2:\tUse the right version.\n\
     825  s:\tSilently include common lines.\n\
     826  v:\tVerbosely include common lines.\n\
     827  q:\tQuit.\n\
     828  "));
     829  }
     830  
     831  static int
     832  skip_white (void)
     833  {
     834    int c;
     835    for (;;)
     836      {
     837        c = getchar ();
     838        if (! isspace (c) || c == '\n')
     839          break;
     840        checksigs ();
     841      }
     842    if (ferror (stdin))
     843      perror_fatal (_("read failed"));
     844    return c;
     845  }
     846  
     847  static void
     848  flush_line (void)
     849  {
     850    int c;
     851    while ((c = getchar ()) != '\n' && c != EOF)
     852      continue;
     853    if (ferror (stdin))
     854      perror_fatal (_("read failed"));
     855  }
     856  
     857  
     858  /* interpret an edit command */
     859  static bool
     860  edit (struct line_filter *left, char const *lname, lin lline, lin llen,
     861        struct line_filter *right, char const *rname, lin rline, lin rlen,
     862        FILE *outfile)
     863  {
     864    for (;;)
     865      {
     866        int cmd0;
     867        int cmd1 IF_LINT (= 0);  /* IF_LINT due to GCC bug 101770.  */
     868        bool gotcmd = false;
     869  
     870        while (! gotcmd)
     871          {
     872            if (putchar ('%') != '%')
     873              perror_fatal (_("write failed"));
     874            ck_fflush (stdout);
     875  
     876            cmd0 = skip_white ();
     877            switch (cmd0)
     878              {
     879              case '1': case '2': case 'l': case 'r':
     880              case 's': case 'v': case 'q':
     881                if (skip_white () != '\n')
     882                  {
     883                    give_help ();
     884                    flush_line ();
     885                    continue;
     886                  }
     887                gotcmd = true;
     888                break;
     889  
     890              case 'e':
     891                cmd1 = skip_white ();
     892                switch (cmd1)
     893                  {
     894                  case '1': case '2': case 'b': case 'd': case 'l': case 'r':
     895                    if (skip_white () != '\n')
     896                      {
     897                        give_help ();
     898                        flush_line ();
     899                        continue;
     900                      }
     901                    gotcmd = true;
     902                    break;
     903                  case '\n':
     904                    gotcmd = true;
     905                    break;
     906                  default:
     907                    give_help ();
     908                    flush_line ();
     909                    continue;
     910                  }
     911                break;
     912  
     913              case EOF:
     914                if (feof (stdin))
     915                  {
     916                    gotcmd = true;
     917                    cmd0 = 'q';
     918                    break;
     919                  }
     920                FALLTHROUGH;
     921              default:
     922                flush_line ();
     923                FALLTHROUGH;
     924              case '\n':
     925                give_help ();
     926                continue;
     927              }
     928          }
     929  
     930        switch (cmd0)
     931          {
     932          case '1': case 'l':
     933            lf_copy (left, llen, outfile);
     934            lf_skip (right, rlen);
     935            return true;
     936          case '2': case 'r':
     937            lf_copy (right, rlen, outfile);
     938            lf_skip (left, llen);
     939            return true;
     940          case 's':
     941            suppress_common_lines = true;
     942            break;
     943          case 'v':
     944            suppress_common_lines = false;
     945            break;
     946          case 'q':
     947            return false;
     948          case 'e':
     949            {
     950              int fd;
     951  
     952              if (tmpname)
     953                tmp = fopen (tmpname, "w");
     954              else
     955                {
     956                  if ((fd = temporary_file ()) < 0)
     957                    perror_fatal ("mkstemp");
     958                  tmp = fdopen (fd, "w");
     959                }
     960  
     961              if (! tmp)
     962                perror_fatal (tmpname);
     963  
     964              switch (cmd1)
     965                {
     966                case 'd':
     967                  if (llen)
     968  		  {
     969  		    if (llen == 1)
     970  		      fprintf (tmp, "--- %s %"pI"d\n", lname, lline);
     971  		    else
     972  		      fprintf (tmp, "--- %s %"pI"d,%"pI"d\n", lname, lline,
     973  			       lline + llen - 1);
     974  		  }
     975                  FALLTHROUGH;
     976                case '1': case 'b': case 'l':
     977                  lf_copy (left, llen, tmp);
     978                  break;
     979  
     980                default:
     981                  lf_skip (left, llen);
     982                  break;
     983                }
     984  
     985              switch (cmd1)
     986                {
     987                case 'd':
     988                  if (rlen)
     989  		  {
     990  		    if (rlen == 1)
     991  		      fprintf (tmp, "+++ %s %"pI"d\n", rname, rline);
     992  		    else
     993  		      fprintf (tmp, "+++ %s %"pI"d,%"pI"d\n", rname, rline,
     994  			     rline + rlen - 1);
     995  		  }
     996                  FALLTHROUGH;
     997                case '2': case 'b': case 'r':
     998                  lf_copy (right, rlen, tmp);
     999                  break;
    1000  
    1001                default:
    1002                  lf_skip (right, rlen);
    1003                  break;
    1004                }
    1005  
    1006              ck_fclose (tmp);
    1007  
    1008              {
    1009                int wstatus;
    1010                int werrno = 0;
    1011                char const *argv[3];
    1012  
    1013                ignore_SIGINT = true;
    1014                checksigs ();
    1015                argv[0] = editor_program;
    1016                argv[1] = tmpname;
    1017                argv[2] = 0;
    1018  
    1019                {
    1020  #if ! HAVE_WORKING_FORK
    1021                  char *command = system_quote_argv (SCI_SYSTEM, (char **) argv);
    1022                  wstatus = system (command);
    1023                  if (wstatus == -1)
    1024                    werrno = errno;
    1025                  free (command);
    1026  #else
    1027                  pid_t pid;
    1028  
    1029                  pid = fork ();
    1030                  if (pid == 0)
    1031                    {
    1032                      execvp (editor_program, (char **) argv);
    1033                      _exit (errno == ENOENT ? 127 : 126);
    1034                    }
    1035  
    1036                  if (pid < 0)
    1037                    perror_fatal ("fork");
    1038  
    1039                  while (waitpid (pid, &wstatus, 0) < 0)
    1040                    if (errno == EINTR)
    1041                      checksigs ();
    1042                    else
    1043                      perror_fatal ("waitpid");
    1044  #endif
    1045                }
    1046  
    1047                ignore_SIGINT = false;
    1048                check_child_status (werrno, wstatus, EXIT_SUCCESS,
    1049                                    editor_program);
    1050              }
    1051  
    1052              {
    1053                char buf[SDIFF_BUFSIZE];
    1054                size_t size;
    1055                tmp = ck_fopen (tmpname, "r");
    1056                while ((size = ck_fread (buf, SDIFF_BUFSIZE, tmp)) != 0)
    1057                  {
    1058                    checksigs ();
    1059                    ck_fwrite (buf, size, outfile);
    1060                  }
    1061                ck_fclose (tmp);
    1062              }
    1063              return true;
    1064            }
    1065          default:
    1066            give_help ();
    1067            break;
    1068          }
    1069      }
    1070  }
    1071  
    1072  /* Alternately reveal bursts of diff output and handle user commands.  */
    1073  static bool
    1074  interact (struct line_filter *diff,
    1075            struct line_filter *left, char const *lname,
    1076            struct line_filter *right, char const *rname,
    1077            FILE *outfile)
    1078  {
    1079    lin lline = 1, rline = 1;
    1080  
    1081    for (;;)
    1082      {
    1083        char diff_help[256];
    1084        int snarfed = lf_snarf (diff, diff_help, sizeof diff_help);
    1085  
    1086        if (snarfed <= 0)
    1087          return snarfed != 0;
    1088  
    1089        checksigs ();
    1090  
    1091        if (diff_help[0] == ' ')
    1092          puts (diff_help + 1);
    1093        else
    1094          {
    1095            char *numend;
    1096            intmax_t val;
    1097            lin llen, rlen, lenmax;
    1098            errno = 0;
    1099            val = strtoimax (diff_help + 1, &numend, 10);
    1100            if (! (0 <= val && val <= LIN_MAX) || errno || *numend != ',')
    1101              fatal (diff_help);
    1102            llen = val;
    1103            val = strtoimax (numend + 1, &numend, 10);
    1104            if (! (0 <= val && val <= LIN_MAX) || errno || *numend)
    1105              fatal (diff_help);
    1106            rlen = val;
    1107  
    1108            lenmax = MAX (llen, rlen);
    1109  
    1110            switch (diff_help[0])
    1111              {
    1112              case 'i':
    1113                if (suppress_common_lines)
    1114                  lf_skip (diff, lenmax);
    1115                else
    1116                  lf_copy (diff, lenmax, stdout);
    1117  
    1118                lf_copy (left, llen, outfile);
    1119                lf_skip (right, rlen);
    1120                break;
    1121  
    1122              case 'c':
    1123                lf_copy (diff, lenmax, stdout);
    1124                if (! edit (left, lname, lline, llen,
    1125                            right, rname, rline, rlen,
    1126                            outfile))
    1127                  return false;
    1128                break;
    1129  
    1130              default:
    1131                fatal (diff_help);
    1132              }
    1133  
    1134            lline += llen;
    1135            rline += rlen;
    1136          }
    1137      }
    1138  }
    1139  
    1140  /* Return true if DIR is an existing directory.  */
    1141  static bool
    1142  diraccess (char const *dir)
    1143  {
    1144    struct stat buf;
    1145    return stat (dir, &buf) == 0 && S_ISDIR (buf.st_mode);
    1146  }
    1147  
    1148  #ifndef P_tmpdir
    1149  # define P_tmpdir "/tmp"
    1150  #endif
    1151  #ifndef TMPDIR_ENV
    1152  # define TMPDIR_ENV "TMPDIR"
    1153  #endif
    1154  
    1155  /* Open a temporary file and return its file descriptor.  Put into
    1156     tmpname the address of a newly allocated buffer that holds the
    1157     file's name.  Use the prefix "sdiff".  */
    1158  static int
    1159  temporary_file (void)
    1160  {
    1161    char const *tmpdir = getenv (TMPDIR_ENV);
    1162    char const *dir = tmpdir ? tmpdir : P_tmpdir;
    1163    char *buf = xmalloc (strlen (dir) + 1 + 5 + 6 + 1);
    1164    strcpy (stpcpy (buf, dir), "/sdiffXXXXXX");
    1165    int fd = mkstemp (buf);
    1166    if (fd < 0)
    1167      free (buf);
    1168    else
    1169      tmpname = buf;
    1170    return fd;
    1171  }