(root)/
diffutils-3.10/
src/
diff.c
       1  /* GNU diff - compare files line by line
       2  
       3     Copyright (C) 1988-1989, 1992-1994, 1996, 1998, 2001-2002, 2004, 2006-2007,
       4     2009-2013, 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  #define GDIFF_MAIN
      22  #include "diff.h"
      23  #include "die.h"
      24  #include <assert.h>
      25  #include "paths.h"
      26  #include <c-stack.h>
      27  #include <dirname.h>
      28  #include <error.h>
      29  #include <exclude.h>
      30  #include <exitfail.h>
      31  #include <filenamecat.h>
      32  #include <file-type.h>
      33  #include <fnmatch.h>
      34  #include <getopt.h>
      35  #include <hard-locale.h>
      36  #include <progname.h>
      37  #include <sh-quote.h>
      38  #include <stat-time.h>
      39  #include <timespec.h>
      40  #include <version-etc.h>
      41  #include <xalloc.h>
      42  #include <xreadlink.h>
      43  #include <xstdopen.h>
      44  #include <binary-io.h>
      45  
      46  /* The official name of this program (e.g., no 'g' prefix).  */
      47  static char const PROGRAM_NAME[] = "diff";
      48  
      49  #define AUTHORS \
      50    proper_name ("Paul Eggert"), \
      51    proper_name ("Mike Haertel"), \
      52    proper_name ("David Hayes"), \
      53    proper_name ("Richard Stallman"), \
      54    proper_name ("Len Tower")
      55  
      56  #ifndef GUTTER_WIDTH_MINIMUM
      57  # define GUTTER_WIDTH_MINIMUM 3
      58  #endif
      59  
      60  struct regexp_list
      61  {
      62    char *regexps;	/* chars representing disjunction of the regexps */
      63    size_t len;		/* chars used in 'regexps' */
      64    size_t size;		/* size malloc'ed for 'regexps'; 0 if not malloc'ed */
      65    bool multiple_regexps;/* Does 'regexps' represent a disjunction?  */
      66    struct re_pattern_buffer *buf;
      67  };
      68  
      69  static int compare_files (struct comparison const *, char const *, char const *);
      70  static void add_regexp (struct regexp_list *, char const *);
      71  static void summarize_regexp_list (struct regexp_list *);
      72  static void specify_style (enum output_style);
      73  static void specify_value (char const **, char const *, char const *);
      74  static void specify_colors_style (char const *);
      75  static _Noreturn void try_help (char const *, char const *);
      76  static void check_stdout (void);
      77  static void usage (void);
      78  
      79  /* If comparing directories, compare their common subdirectories
      80     recursively.  */
      81  static bool recursive;
      82  
      83  /* In context diffs, show previous lines that match these regexps.  */
      84  static struct regexp_list function_regexp_list;
      85  
      86  /* Ignore changes affecting only lines that match these regexps.  */
      87  static struct regexp_list ignore_regexp_list;
      88  
      89  #if O_BINARY
      90  /* Use binary I/O when reading and writing data (--binary).
      91     On POSIX hosts, this has no effect.  */
      92  static bool binary;
      93  #else
      94  enum { binary = true };
      95  #endif
      96  
      97  /* If one file is missing, treat it as present but empty (-N).  */
      98  static bool new_file;
      99  
     100  /* If the first file is missing, treat it as present but empty
     101     (--unidirectional-new-file).  */
     102  static bool unidirectional_new_file;
     103  
     104  /* Report files compared that are the same (-s).
     105     Normally nothing is output when that happens.  */
     106  static bool report_identical_files;
     107  
     108  /* Do not treat directories specially.  */
     109  static bool no_directory;
     110  
     111  static char const shortopts[] =
     112  "0123456789abBcC:dD:eEfF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:yZ";
     113  
     114  /* Values for long options that do not have single-letter equivalents.  */
     115  enum
     116  {
     117    BINARY_OPTION = CHAR_MAX + 1,
     118    FROM_FILE_OPTION,
     119    HELP_OPTION,
     120    HORIZON_LINES_OPTION,
     121    IGNORE_FILE_NAME_CASE_OPTION,
     122    INHIBIT_HUNK_MERGE_OPTION,
     123    LEFT_COLUMN_OPTION,
     124    LINE_FORMAT_OPTION,
     125    NO_DEREFERENCE_OPTION,
     126    NO_IGNORE_FILE_NAME_CASE_OPTION,
     127    NORMAL_OPTION,
     128    SDIFF_MERGE_ASSIST_OPTION,
     129    STRIP_TRAILING_CR_OPTION,
     130    SUPPRESS_BLANK_EMPTY_OPTION,
     131    SUPPRESS_COMMON_LINES_OPTION,
     132    TABSIZE_OPTION,
     133    TO_FILE_OPTION,
     134  
     135    /* These options must be in sequence.  */
     136    UNCHANGED_LINE_FORMAT_OPTION,
     137    OLD_LINE_FORMAT_OPTION,
     138    NEW_LINE_FORMAT_OPTION,
     139  
     140    /* These options must be in sequence.  */
     141    UNCHANGED_GROUP_FORMAT_OPTION,
     142    OLD_GROUP_FORMAT_OPTION,
     143    NEW_GROUP_FORMAT_OPTION,
     144    CHANGED_GROUP_FORMAT_OPTION,
     145  
     146    COLOR_OPTION,
     147    COLOR_PALETTE_OPTION,
     148  
     149    NO_DIRECTORY_OPTION,
     150    PRESUME_OUTPUT_TTY_OPTION,
     151  };
     152  
     153  static char const group_format_option[][sizeof "--unchanged-group-format"] =
     154    {
     155      "--unchanged-group-format",
     156      "--old-group-format",
     157      "--new-group-format",
     158      "--changed-group-format"
     159    };
     160  
     161  static char const line_format_option[][sizeof "--unchanged-line-format"] =
     162    {
     163      "--unchanged-line-format",
     164      "--old-line-format",
     165      "--new-line-format"
     166    };
     167  
     168  static struct option const longopts[] =
     169  {
     170    {"binary", 0, 0, BINARY_OPTION},
     171    {"brief", 0, 0, 'q'},
     172    {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
     173    {"color", 2, 0, COLOR_OPTION},
     174    {"context", 2, 0, 'C'},
     175    {"ed", 0, 0, 'e'},
     176    {"exclude", 1, 0, 'x'},
     177    {"exclude-from", 1, 0, 'X'},
     178    {"expand-tabs", 0, 0, 't'},
     179    {"forward-ed", 0, 0, 'f'},
     180    {"from-file", 1, 0, FROM_FILE_OPTION},
     181    {"help", 0, 0, HELP_OPTION},
     182    {"horizon-lines", 1, 0, HORIZON_LINES_OPTION},
     183    {"ifdef", 1, 0, 'D'},
     184    {"ignore-all-space", 0, 0, 'w'},
     185    {"ignore-blank-lines", 0, 0, 'B'},
     186    {"ignore-case", 0, 0, 'i'},
     187    {"ignore-file-name-case", 0, 0, IGNORE_FILE_NAME_CASE_OPTION},
     188    {"ignore-matching-lines", 1, 0, 'I'},
     189    {"ignore-space-change", 0, 0, 'b'},
     190    {"ignore-tab-expansion", 0, 0, 'E'},
     191    {"ignore-trailing-space", 0, 0, 'Z'},
     192    {"inhibit-hunk-merge", 0, 0, INHIBIT_HUNK_MERGE_OPTION},
     193    {"initial-tab", 0, 0, 'T'},
     194    {"label", 1, 0, 'L'},
     195    {"left-column", 0, 0, LEFT_COLUMN_OPTION},
     196    {"line-format", 1, 0, LINE_FORMAT_OPTION},
     197    {"minimal", 0, 0, 'd'},
     198    {"new-file", 0, 0, 'N'},
     199    {"new-group-format", 1, 0, NEW_GROUP_FORMAT_OPTION},
     200    {"new-line-format", 1, 0, NEW_LINE_FORMAT_OPTION},
     201    {"no-dereference", 0, 0, NO_DEREFERENCE_OPTION},
     202    {"no-ignore-file-name-case", 0, 0, NO_IGNORE_FILE_NAME_CASE_OPTION},
     203    {"normal", 0, 0, NORMAL_OPTION},
     204    {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
     205    {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
     206    {"paginate", 0, 0, 'l'},
     207    {"palette", 1, 0, COLOR_PALETTE_OPTION},
     208    {"rcs", 0, 0, 'n'},
     209    {"recursive", 0, 0, 'r'},
     210    {"report-identical-files", 0, 0, 's'},
     211    {"sdiff-merge-assist", 0, 0, SDIFF_MERGE_ASSIST_OPTION},
     212    {"show-c-function", 0, 0, 'p'},
     213    {"show-function-line", 1, 0, 'F'},
     214    {"side-by-side", 0, 0, 'y'},
     215    {"speed-large-files", 0, 0, 'H'},
     216    {"starting-file", 1, 0, 'S'},
     217    {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
     218    {"suppress-blank-empty", 0, 0, SUPPRESS_BLANK_EMPTY_OPTION},
     219    {"suppress-common-lines", 0, 0, SUPPRESS_COMMON_LINES_OPTION},
     220    {"tabsize", 1, 0, TABSIZE_OPTION},
     221    {"text", 0, 0, 'a'},
     222    {"to-file", 1, 0, TO_FILE_OPTION},
     223    {"unchanged-group-format", 1, 0, UNCHANGED_GROUP_FORMAT_OPTION},
     224    {"unchanged-line-format", 1, 0, UNCHANGED_LINE_FORMAT_OPTION},
     225    {"unidirectional-new-file", 0, 0, 'P'},
     226    {"unified", 2, 0, 'U'},
     227    {"version", 0, 0, 'v'},
     228    {"width", 1, 0, 'W'},
     229  
     230    /* This is solely for diff3.  Do not document.  */
     231    {"-no-directory", no_argument, nullptr, NO_DIRECTORY_OPTION},
     232  
     233    /* This is solely for testing.  Do not document.  */
     234    {"-presume-output-tty", no_argument, nullptr, PRESUME_OUTPUT_TTY_OPTION},
     235    {0, 0, 0, 0}
     236  };
     237  
     238  /* Return a string containing the command options with which diff was invoked.
     239     Spaces appear between what were separate ARGV-elements.
     240     There is a space at the beginning but none at the end.
     241     If there were no options, the result is an empty string.
     242  
     243     Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
     244     the length of that vector.  */
     245  
     246  static char *
     247  option_list (char **optionvec, int count)
     248  {
     249    int i;
     250    size_t size = 1;
     251    char *result;
     252    char *p;
     253  
     254    for (i = 0; i < count; i++)
     255      {
     256        size_t optsize = 1 + shell_quote_length (optionvec[i]);
     257        if (INT_ADD_WRAPV (optsize, size, &size))
     258  	xalloc_die ();
     259      }
     260  
     261    p = result = xmalloc (size);
     262  
     263    for (i = 0; i < count; i++)
     264      {
     265        *p++ = ' ';
     266        p = shell_quote_copy (p, optionvec[i]);
     267      }
     268  
     269    *p = '\0';
     270    return result;
     271  }
     272  
     273  
     274  /* Return an option value suitable for add_exclude.  */
     275  
     276  static int
     277  exclude_options (void)
     278  {
     279    return EXCLUDE_WILDCARDS | (ignore_file_name_case ? FNM_CASEFOLD : 0);
     280  }
     281  
     282  int
     283  main (int argc, char **argv)
     284  {
     285    int exit_status = EXIT_SUCCESS;
     286    int c;
     287    int i;
     288    int prev = -1;
     289    lin ocontext = -1;
     290    bool explicit_context = false;
     291    size_t width = 0;
     292    bool show_c_function = false;
     293    char const *from_file = nullptr;
     294    char const *to_file = nullptr;
     295    intmax_t numval;
     296    char *numend;
     297  
     298    /* Do our initializations.  */
     299    exit_failure = EXIT_TROUBLE;
     300    initialize_main (&argc, &argv);
     301    set_program_name (argv[0]);
     302    setlocale (LC_ALL, "");
     303    bindtextdomain (PACKAGE, LOCALEDIR);
     304    textdomain (PACKAGE);
     305    c_stack_action (0);
     306    function_regexp_list.buf = &function_regexp;
     307    ignore_regexp_list.buf = &ignore_regexp;
     308    re_set_syntax (RE_SYNTAX_GREP | RE_NO_POSIX_BACKTRACKING);
     309    excluded = new_exclude ();
     310    presume_output_tty = false;
     311    xstdopen ();
     312  
     313    /* Decode the options.  */
     314  
     315    while ((c = getopt_long (argc, argv, shortopts, longopts, nullptr)) != -1)
     316      {
     317        switch (c)
     318          {
     319          case 0:
     320            break;
     321  
     322          case '0':
     323          case '1':
     324          case '2':
     325          case '3':
     326          case '4':
     327          case '5':
     328          case '6':
     329          case '7':
     330          case '8':
     331          case '9':
     332            ocontext = (! ISDIGIT (prev)
     333                        ? c - '0'
     334                        : (ocontext - (c - '0' <= CONTEXT_MAX % 10)
     335                           < CONTEXT_MAX / 10)
     336                        ? 10 * ocontext + (c - '0')
     337                        : CONTEXT_MAX);
     338            break;
     339  
     340          case 'a':
     341            text = true;
     342            break;
     343  
     344          case 'b':
     345            if (ignore_white_space < IGNORE_SPACE_CHANGE)
     346              ignore_white_space = IGNORE_SPACE_CHANGE;
     347            break;
     348  
     349          case 'Z':
     350            if (ignore_white_space < IGNORE_SPACE_CHANGE)
     351              ignore_white_space |= IGNORE_TRAILING_SPACE;
     352            break;
     353  
     354          case 'B':
     355            ignore_blank_lines = true;
     356            break;
     357  
     358          case 'C':
     359          case 'U':
     360            {
     361              if (optarg)
     362                {
     363                  numval = strtoimax (optarg, &numend, 10);
     364                  if (*numend || numval < 0)
     365                    try_help ("invalid context length '%s'", optarg);
     366                  if (CONTEXT_MAX < numval)
     367                    numval = CONTEXT_MAX;
     368                }
     369              else
     370                numval = 3;
     371  
     372              specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
     373              if (context < numval)
     374                context = numval;
     375              explicit_context = true;
     376            }
     377            break;
     378  
     379          case 'c':
     380            specify_style (OUTPUT_CONTEXT);
     381            if (context < 3)
     382              context = 3;
     383            break;
     384  
     385          case 'd':
     386            minimal = true;
     387            break;
     388  
     389          case 'D':
     390            specify_style (OUTPUT_IFDEF);
     391            {
     392  	    static char const C_ifdef_group_formats[]
     393  	      = (/* UNCHANGED */
     394  		 "%="
     395  		 "\0"
     396  
     397  		 /* OLD */
     398  		 "#ifndef @\n"
     399  		 "%<"
     400  		 "#endif /* ! @ */\n"
     401  		 "\0"
     402  
     403  		 /* NEW */
     404  		 "#ifdef @\n"
     405  		 "%>"
     406  		 "#endif /* @ */\n"
     407  		 "\0"
     408  
     409  		 /* CHANGED */
     410  		 "#ifndef @\n"
     411  		 "%<"
     412  		 "#else /* @ */\n"
     413  		 "%>"
     414  		 "#endif /* @ */\n");
     415  
     416  	    size_t alloc = strlen (optarg);
     417  	    if (INT_MULTIPLY_WRAPV (alloc, 7, &alloc)
     418  		|| INT_ADD_WRAPV (alloc,
     419  				  sizeof C_ifdef_group_formats - 7 /* 7*"@" */,
     420  				  &alloc))
     421  	      xalloc_die ();
     422  	    char *b = xmalloc (alloc);
     423  	    char *base = b;
     424  	    int changes = 0;
     425  
     426  	    for (i = 0; i < sizeof C_ifdef_group_formats; i++)
     427  	      {
     428  		char ch = C_ifdef_group_formats[i];
     429  		switch (ch)
     430  		  {
     431  		  default:
     432  		    *b++ = ch;
     433  		    break;
     434  
     435  		  case '@':
     436  		    b = stpcpy (b, optarg);
     437  		    break;
     438  
     439  		  case '\0':
     440  		    *b++ = ch;
     441  		    specify_value (&group_format[changes++], base, "-D");
     442  		    base = b;
     443  		    break;
     444  		  }
     445  	      }
     446            }
     447            break;
     448  
     449          case 'e':
     450            specify_style (OUTPUT_ED);
     451            break;
     452  
     453          case 'E':
     454            if (ignore_white_space < IGNORE_SPACE_CHANGE)
     455              ignore_white_space |= IGNORE_TAB_EXPANSION;
     456            break;
     457  
     458          case 'f':
     459            specify_style (OUTPUT_FORWARD_ED);
     460            break;
     461  
     462          case 'F':
     463            add_regexp (&function_regexp_list, optarg);
     464            break;
     465  
     466          case 'h':
     467            /* Split the files into chunks for faster processing.
     468               Usually does not change the result.
     469  
     470               This currently has no effect.  */
     471            break;
     472  
     473          case 'H':
     474            speed_large_files = true;
     475            break;
     476  
     477          case 'i':
     478            ignore_case = true;
     479            break;
     480  
     481          case 'I':
     482            add_regexp (&ignore_regexp_list, optarg);
     483            break;
     484  
     485          case 'l':
     486            if (!pr_program[0])
     487              try_help ("pagination not supported on this host", nullptr);
     488            paginate = true;
     489  #ifdef SIGCHLD
     490            /* Pagination requires forking and waiting, and
     491               System V fork+wait does not work if SIGCHLD is ignored.  */
     492            signal (SIGCHLD, SIG_DFL);
     493  #endif
     494            break;
     495  
     496          case 'L':
     497            if (!file_label[0])
     498              file_label[0] = optarg;
     499            else if (!file_label[1])
     500              file_label[1] = optarg;
     501            else
     502              fatal ("too many file label options");
     503            break;
     504  
     505          case 'n':
     506            specify_style (OUTPUT_RCS);
     507            break;
     508  
     509          case 'N':
     510            new_file = true;
     511            break;
     512  
     513          case 'p':
     514            show_c_function = true;
     515            add_regexp (&function_regexp_list, "^[[:alpha:]$_]");
     516            break;
     517  
     518          case 'P':
     519            unidirectional_new_file = true;
     520            break;
     521  
     522          case 'q':
     523            brief = true;
     524            break;
     525  
     526          case 'r':
     527            recursive = true;
     528            break;
     529  
     530          case 's':
     531            report_identical_files = true;
     532            break;
     533  
     534          case 'S':
     535            specify_value (&starting_file, optarg, "-S");
     536            break;
     537  
     538          case 't':
     539            expand_tabs = true;
     540            break;
     541  
     542          case 'T':
     543            initial_tab = true;
     544            break;
     545  
     546          case 'u':
     547            specify_style (OUTPUT_UNIFIED);
     548            if (context < 3)
     549              context = 3;
     550            break;
     551  
     552          case 'v':
     553            version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version,
     554                         AUTHORS, nullptr);
     555            check_stdout ();
     556            return EXIT_SUCCESS;
     557  
     558          case 'w':
     559            ignore_white_space = IGNORE_ALL_SPACE;
     560            break;
     561  
     562          case 'x':
     563            add_exclude (excluded, optarg, exclude_options ());
     564            break;
     565  
     566          case 'X':
     567            if (add_exclude_file (add_exclude, excluded, optarg,
     568                                  exclude_options (), '\n'))
     569              pfatal_with_name (optarg);
     570            break;
     571  
     572          case 'y':
     573            specify_style (OUTPUT_SDIFF);
     574            break;
     575  
     576          case 'W':
     577            numval = strtoimax (optarg, &numend, 10);
     578            if (! (0 < numval && numval <= SIZE_MAX) || *numend)
     579              try_help ("invalid width '%s'", optarg);
     580            if (width != numval)
     581              {
     582                if (width)
     583                  fatal ("conflicting width options");
     584                width = numval;
     585              }
     586            break;
     587  
     588          case BINARY_OPTION:
     589  #if O_BINARY
     590            binary = true;
     591            if (! isatty (STDOUT_FILENO))
     592              set_binary_mode (STDOUT_FILENO, O_BINARY);
     593  #endif
     594            break;
     595  
     596          case FROM_FILE_OPTION:
     597            specify_value (&from_file, optarg, "--from-file");
     598            break;
     599  
     600          case HELP_OPTION:
     601            usage ();
     602            check_stdout ();
     603            return EXIT_SUCCESS;
     604  
     605          case HORIZON_LINES_OPTION:
     606            numval = strtoimax (optarg, &numend, 10);
     607            if (*numend || numval < 0)
     608              try_help ("invalid horizon length '%s'", optarg);
     609            horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
     610            break;
     611  
     612          case IGNORE_FILE_NAME_CASE_OPTION:
     613            ignore_file_name_case = true;
     614            break;
     615  
     616          case INHIBIT_HUNK_MERGE_OPTION:
     617            /* This option is obsolete, but accept it for backward
     618               compatibility.  */
     619            break;
     620  
     621          case LEFT_COLUMN_OPTION:
     622            left_column = true;
     623            break;
     624  
     625          case LINE_FORMAT_OPTION:
     626            specify_style (OUTPUT_IFDEF);
     627            for (i = 0; i < sizeof line_format / sizeof line_format[0]; i++)
     628              specify_value (&line_format[i], optarg, "--line-format");
     629            break;
     630  
     631          case NO_DEREFERENCE_OPTION:
     632            no_dereference_symlinks = true;
     633            break;
     634  
     635          case NO_IGNORE_FILE_NAME_CASE_OPTION:
     636            ignore_file_name_case = false;
     637            break;
     638  
     639          case NORMAL_OPTION:
     640            specify_style (OUTPUT_NORMAL);
     641            break;
     642  
     643          case SDIFF_MERGE_ASSIST_OPTION:
     644            specify_style (OUTPUT_SDIFF);
     645            sdiff_merge_assist = true;
     646            break;
     647  
     648          case STRIP_TRAILING_CR_OPTION:
     649            strip_trailing_cr = true;
     650            break;
     651  
     652          case SUPPRESS_BLANK_EMPTY_OPTION:
     653            suppress_blank_empty = true;
     654            break;
     655  
     656          case SUPPRESS_COMMON_LINES_OPTION:
     657            suppress_common_lines = true;
     658            break;
     659  
     660          case TABSIZE_OPTION:
     661            numval = strtoimax (optarg, &numend, 10);
     662            if (! (0 < numval && numval <= SIZE_MAX - GUTTER_WIDTH_MINIMUM)
     663                || *numend)
     664              try_help ("invalid tabsize '%s'", optarg);
     665            if (tabsize != numval)
     666              {
     667                if (tabsize)
     668                  fatal ("conflicting tabsize options");
     669                tabsize = numval;
     670              }
     671            break;
     672  
     673          case TO_FILE_OPTION:
     674            specify_value (&to_file, optarg, "--to-file");
     675            break;
     676  
     677          case UNCHANGED_LINE_FORMAT_OPTION:
     678          case OLD_LINE_FORMAT_OPTION:
     679          case NEW_LINE_FORMAT_OPTION:
     680            specify_style (OUTPUT_IFDEF);
     681            c -= UNCHANGED_LINE_FORMAT_OPTION;
     682            specify_value (&line_format[c], optarg, line_format_option[c]);
     683            break;
     684  
     685          case UNCHANGED_GROUP_FORMAT_OPTION:
     686          case OLD_GROUP_FORMAT_OPTION:
     687          case NEW_GROUP_FORMAT_OPTION:
     688          case CHANGED_GROUP_FORMAT_OPTION:
     689            specify_style (OUTPUT_IFDEF);
     690            c -= UNCHANGED_GROUP_FORMAT_OPTION;
     691            specify_value (&group_format[c], optarg, group_format_option[c]);
     692            break;
     693  
     694          case COLOR_OPTION:
     695            specify_colors_style (optarg);
     696            break;
     697  
     698          case COLOR_PALETTE_OPTION:
     699            set_color_palette (optarg);
     700            break;
     701  
     702          case NO_DIRECTORY_OPTION:
     703            no_directory = true;
     704            break;
     705  
     706          case PRESUME_OUTPUT_TTY_OPTION:
     707            presume_output_tty = true;
     708            break;
     709  
     710          default:
     711            try_help (nullptr, nullptr);
     712          }
     713        prev = c;
     714      }
     715  
     716    if (colors_style == AUTO)
     717      {
     718        char const *t = getenv ("TERM");
     719        if (t && STREQ (t, "dumb"))
     720          colors_style = NEVER;
     721      }
     722  
     723    if (output_style == OUTPUT_UNSPECIFIED)
     724      {
     725        if (show_c_function)
     726          {
     727            specify_style (OUTPUT_CONTEXT);
     728            if (ocontext < 0)
     729              context = 3;
     730          }
     731        else
     732          specify_style (OUTPUT_NORMAL);
     733      }
     734  
     735    if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
     736      {
     737  #if (defined STAT_TIMESPEC || defined STAT_TIMESPEC_NS \
     738       || defined HAVE_STRUCT_STAT_ST_SPARE1)
     739        time_format = "%Y-%m-%d %H:%M:%S.%N %z";
     740  #else
     741        time_format = "%Y-%m-%d %H:%M:%S %z";
     742  #endif
     743  #if !HAVE_TM_GMTOFF
     744        localtz = tzalloc (getenv ("TZ"));
     745  #endif
     746      }
     747    else
     748      {
     749        /* See POSIX 1003.1-2001 for this format.  */
     750        time_format = "%a %b %e %T %Y";
     751      }
     752  
     753    if (0 <= ocontext
     754        && (output_style == OUTPUT_CONTEXT
     755            || output_style == OUTPUT_UNIFIED)
     756        && (context < ocontext
     757            || (ocontext < context && ! explicit_context)))
     758      context = ocontext;
     759  
     760    if (! tabsize)
     761      tabsize = 8;
     762    if (! width)
     763      width = 130;
     764  
     765    {
     766      /* Maximize first the half line width, and then the gutter width,
     767         according to the following constraints:
     768  
     769          1.  Two half lines plus a gutter must fit in a line.
     770          2.  If the half line width is nonzero:
     771              a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
     772              b.  If tabs are not expanded to spaces,
     773                  a half line plus a gutter is an integral number of tabs,
     774                  so that tabs in the right column line up.  */
     775  
     776      size_t t = expand_tabs ? 1 : tabsize;
     777      size_t w = width;
     778      size_t t_plus_g = t + GUTTER_WIDTH_MINIMUM;
     779      size_t unaligned_off = (w >> 1) + (t_plus_g >> 1) + (w & t_plus_g & 1);
     780      size_t off = unaligned_off - unaligned_off % t;
     781      sdiff_half_width = (off <= GUTTER_WIDTH_MINIMUM || w <= off
     782                          ? 0
     783                          : MIN (off - GUTTER_WIDTH_MINIMUM, w - off));
     784      sdiff_column2_offset = sdiff_half_width ? off : w;
     785    }
     786  
     787    /* Make the horizon at least as large as the context, so that
     788       shift_boundaries has more freedom to shift the first and last hunks.  */
     789    if (horizon_lines < context)
     790      horizon_lines = context;
     791  
     792    summarize_regexp_list (&function_regexp_list);
     793    summarize_regexp_list (&ignore_regexp_list);
     794  
     795    if (output_style == OUTPUT_IFDEF)
     796      {
     797        for (i = 0; i < sizeof line_format / sizeof line_format[0]; i++)
     798          if (!line_format[i])
     799            line_format[i] = "%l\n";
     800        if (!group_format[OLD])
     801          group_format[OLD]
     802            = group_format[CHANGED] ? group_format[CHANGED] : "%<";
     803        if (!group_format[NEW])
     804          group_format[NEW]
     805            = group_format[CHANGED] ? group_format[CHANGED] : "%>";
     806        if (!group_format[UNCHANGED])
     807          group_format[UNCHANGED] = "%=";
     808        if (!group_format[CHANGED])
     809  	{
     810  	  char *p = xmalloc (strlen (group_format[OLD])
     811  			     + strlen (group_format[NEW]) + 1);
     812  	  group_format[CHANGED] = p;
     813  	  strcpy (stpcpy (p, group_format[OLD]), group_format[NEW]);
     814  	}
     815      }
     816  
     817    no_diff_means_no_output =
     818      (output_style == OUTPUT_IFDEF ?
     819        (!*group_format[UNCHANGED]
     820         || (STREQ (group_format[UNCHANGED], "%=")
     821             && !*line_format[UNCHANGED]))
     822       : (output_style != OUTPUT_SDIFF) | suppress_common_lines);
     823  
     824    files_can_be_treated_as_binary =
     825      (brief & binary
     826       & ~ (ignore_blank_lines | ignore_case | strip_trailing_cr
     827            | (ignore_regexp_list.regexps || ignore_white_space)));
     828  
     829    switch_string = option_list (argv + 1, optind - 1);
     830  
     831    if (from_file)
     832      {
     833        if (to_file)
     834          fatal ("--from-file and --to-file both specified");
     835        else
     836          for (; optind < argc; optind++)
     837            {
     838              int status = compare_files (nullptr, from_file, argv[optind]);
     839              if (exit_status < status)
     840                exit_status = status;
     841            }
     842      }
     843    else
     844      {
     845        if (to_file)
     846          for (; optind < argc; optind++)
     847            {
     848              int status = compare_files (nullptr, argv[optind], to_file);
     849              if (exit_status < status)
     850                exit_status = status;
     851            }
     852        else
     853          {
     854            if (argc - optind != 2)
     855              {
     856                if (argc - optind < 2)
     857                  try_help ("missing operand after '%s'", argv[argc - 1]);
     858                else
     859                  try_help ("extra operand '%s'", argv[optind + 2]);
     860              }
     861  
     862            exit_status = compare_files (nullptr, argv[optind], argv[optind + 1]);
     863          }
     864      }
     865  
     866    /* Print any messages that were saved up for last.  */
     867    print_message_queue ();
     868  
     869    check_stdout ();
     870    cleanup_signal_handlers ();
     871    return exit_status;
     872  }
     873  
     874  /* Append to REGLIST the regexp PATTERN.  */
     875  
     876  static void
     877  add_regexp (struct regexp_list *reglist, char const *pattern)
     878  {
     879    size_t patlen = strlen (pattern);
     880    char const *m = re_compile_pattern (pattern, patlen, reglist->buf);
     881  
     882    if (m != 0)
     883      error (EXIT_TROUBLE, 0, "%s: %s", pattern, m);
     884    else
     885      {
     886        char *regexps = reglist->regexps;
     887        size_t len = reglist->len;
     888        bool multiple_regexps = reglist->multiple_regexps = regexps != 0;
     889        size_t newlen = reglist->len = len + 2 * multiple_regexps + patlen;
     890        size_t size = reglist->size;
     891  
     892        if (size <= newlen)
     893          {
     894            if (!size)
     895              size = 1;
     896  
     897            do size *= 2;
     898            while (size <= newlen);
     899  
     900            reglist->size = size;
     901            reglist->regexps = regexps = xrealloc (regexps, size);
     902          }
     903        if (multiple_regexps)
     904          {
     905            regexps[len++] = '\\';
     906            regexps[len++] = '|';
     907          }
     908        memcpy (regexps + len, pattern, patlen + 1);
     909      }
     910  }
     911  
     912  /* Ensure that REGLIST represents the disjunction of its regexps.
     913     This is done here, rather than earlier, to avoid O(N^2) behavior.  */
     914  
     915  static void
     916  summarize_regexp_list (struct regexp_list *reglist)
     917  {
     918    if (reglist->regexps)
     919      {
     920        /* At least one regexp was specified.  Allocate a fastmap for it.  */
     921        reglist->buf->fastmap = xmalloc (1 << CHAR_BIT);
     922        if (reglist->multiple_regexps)
     923          {
     924            /* Compile the disjunction of the regexps.
     925               (If just one regexp was specified, it is already compiled.)  */
     926            char const *m = re_compile_pattern (reglist->regexps, reglist->len,
     927                                                reglist->buf);
     928            if (m)
     929              die (EXIT_TROUBLE, 0, "%s: %s", reglist->regexps, m);
     930          }
     931      }
     932  }
     933  
     934  static void
     935  try_help (char const *reason_msgid, char const *operand)
     936  {
     937    if (reason_msgid)
     938      error (0, 0, _(reason_msgid), operand);
     939    die (EXIT_TROUBLE, 0, _("Try '%s --help' for more information."),
     940           program_name);
     941  }
     942  
     943  static void
     944  check_stdout (void)
     945  {
     946    if (ferror (stdout))
     947      fatal ("write failed");
     948    else if (fclose (stdout) != 0)
     949      pfatal_with_name (_("standard output"));
     950  }
     951  
     952  static char const * const option_help_msgid[] = {
     953    N_("    --normal                  output a normal diff (the default)"),
     954    N_("-q, --brief                   report only when files differ"),
     955    N_("-s, --report-identical-files  report when two files are the same"),
     956    N_("-c, -C NUM, --context[=NUM]   output NUM (default 3) lines of copied context"),
     957    N_("-u, -U NUM, --unified[=NUM]   output NUM (default 3) lines of unified context"),
     958    N_("-e, --ed                      output an ed script"),
     959    N_("-n, --rcs                     output an RCS format diff"),
     960    N_("-y, --side-by-side            output in two columns"),
     961    N_("-W, --width=NUM               output at most NUM (default 130) print columns"),
     962    N_("    --left-column             output only the left column of common lines"),
     963    N_("    --suppress-common-lines   do not output common lines"),
     964    "",
     965    N_("-p, --show-c-function         show which C function each change is in"),
     966    N_("-F, --show-function-line=RE   show the most recent line matching RE"),
     967    N_("    --label LABEL             use LABEL instead of file name and timestamp\n"
     968       "                                (can be repeated)"),
     969    "",
     970    N_("-t, --expand-tabs             expand tabs to spaces in output"),
     971    N_("-T, --initial-tab             make tabs line up by prepending a tab"),
     972    N_("    --tabsize=NUM             tab stops every NUM (default 8) print columns"),
     973    N_("    --suppress-blank-empty    suppress space or tab before empty output lines"),
     974    N_("-l, --paginate                pass output through 'pr' to paginate it"),
     975    "",
     976    N_("-r, --recursive                 recursively compare any subdirectories found"),
     977    N_("    --no-dereference            don't follow symbolic links"),
     978    N_("-N, --new-file                  treat absent files as empty"),
     979    N_("    --unidirectional-new-file   treat absent first files as empty"),
     980    N_("    --ignore-file-name-case     ignore case when comparing file names"),
     981    N_("    --no-ignore-file-name-case  consider case when comparing file names"),
     982    N_("-x, --exclude=PAT               exclude files that match PAT"),
     983    N_("-X, --exclude-from=FILE         exclude files that match any pattern in FILE"),
     984    N_("-S, --starting-file=FILE        start with FILE when comparing directories"),
     985    N_("    --from-file=FILE1           compare FILE1 to all operands;\n"
     986       "                                  FILE1 can be a directory"),
     987    N_("    --to-file=FILE2             compare all operands to FILE2;\n"
     988       "                                  FILE2 can be a directory"),
     989    "",
     990    N_("-i, --ignore-case               ignore case differences in file contents"),
     991    N_("-E, --ignore-tab-expansion      ignore changes due to tab expansion"),
     992    N_("-Z, --ignore-trailing-space     ignore white space at line end"),
     993    N_("-b, --ignore-space-change       ignore changes in the amount of white space"),
     994    N_("-w, --ignore-all-space          ignore all white space"),
     995    N_("-B, --ignore-blank-lines        ignore changes where lines are all blank"),
     996    N_("-I, --ignore-matching-lines=RE  ignore changes where all lines match RE"),
     997    "",
     998    N_("-a, --text                      treat all files as text"),
     999    N_("    --strip-trailing-cr         strip trailing carriage return on input"),
    1000  #if O_BINARY
    1001    N_("    --binary                    read and write data in binary mode"),
    1002  #endif
    1003    "",
    1004    N_("-D, --ifdef=NAME                output merged file with '#ifdef NAME' diffs"),
    1005    N_("    --GTYPE-group-format=GFMT   format GTYPE input groups with GFMT"),
    1006    N_("    --line-format=LFMT          format all input lines with LFMT"),
    1007    N_("    --LTYPE-line-format=LFMT    format LTYPE input lines with LFMT"),
    1008    N_("  These format options provide fine-grained control over the output\n"
    1009       "    of diff, generalizing -D/--ifdef."),
    1010    N_("  LTYPE is 'old', 'new', or 'unchanged'.  GTYPE is LTYPE or 'changed'."),
    1011    N_("  GFMT (only) may contain:\n\
    1012      %<  lines from FILE1\n\
    1013      %>  lines from FILE2\n\
    1014      %=  lines common to FILE1 and FILE2\n\
    1015      %[-][WIDTH][.[PREC]]{doxX}LETTER  printf-style spec for LETTER\n\
    1016        LETTERs are as follows for new group, lower case for old group:\n\
    1017          F  first line number\n\
    1018          L  last line number\n\
    1019          N  number of lines = L-F+1\n\
    1020          E  F-1\n\
    1021          M  L+1\n\
    1022      %(A=B?T:E)  if A equals B then T else E"),
    1023    N_("  LFMT (only) may contain:\n\
    1024      %L  contents of line\n\
    1025      %l  contents of line, excluding any trailing newline\n\
    1026      %[-][WIDTH][.[PREC]]{doxX}n  printf-style spec for input line number"),
    1027    N_("  Both GFMT and LFMT may contain:\n\
    1028      %%  %\n\
    1029      %c'C'  the single character C\n\
    1030      %c'\\OOO'  the character with octal code OOO\n\
    1031      C    the character C (other characters represent themselves)"),
    1032    "",
    1033    N_("-d, --minimal            try hard to find a smaller set of changes"),
    1034    N_("    --horizon-lines=NUM  keep NUM lines of the common prefix and suffix"),
    1035    N_("    --speed-large-files  assume large files and many scattered small changes"),
    1036    N_("    --color[=WHEN]       color output; WHEN is 'never', 'always', or 'auto';\n"
    1037       "                           plain --color means --color='auto'"),
    1038    N_("    --palette=PALETTE    the colors to use when --color is active; PALETTE is\n"
    1039       "                           a colon-separated list of terminfo capabilities"),
    1040    "",
    1041    N_("    --help               display this help and exit"),
    1042    N_("-v, --version            output version information and exit"),
    1043    "",
    1044    N_("FILES are 'FILE1 FILE2' or 'DIR1 DIR2' or 'DIR FILE' or 'FILE DIR'."),
    1045    N_("If --from-file or --to-file is given, there are no restrictions on FILE(s)."),
    1046    N_("If a FILE is '-', read standard input."),
    1047    N_("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."),
    1048    0
    1049  };
    1050  
    1051  static void
    1052  usage (void)
    1053  {
    1054    char const * const *p;
    1055  
    1056    printf (_("Usage: %s [OPTION]... FILES\n"), program_name);
    1057    printf ("%s\n\n", _("Compare FILES line by line."));
    1058  
    1059    fputs (_("\
    1060  Mandatory arguments to long options are mandatory for short options too.\n\
    1061  "), stdout);
    1062  
    1063    for (p = option_help_msgid;  *p;  p++)
    1064      {
    1065        if (!**p)
    1066          putchar ('\n');
    1067        else
    1068          {
    1069            char const *msg = _(*p);
    1070            char const *nl;
    1071            while ((nl = strchr (msg, '\n')))
    1072              {
    1073                int msglen = nl + 1 - msg;
    1074                /* This assertion is solely to avoid a warning from
    1075                   gcc's -Wformat-overflow=.  */
    1076                assert (msglen < 4096);
    1077                printf ("  %.*s", msglen, msg);
    1078                msg = nl + 1;
    1079              }
    1080  
    1081            printf (&"  %s\n"[2 * (*msg != ' ' && *msg != '-')], msg);
    1082          }
    1083      }
    1084    emit_bug_reporting_address ();
    1085  }
    1086  
    1087  /* Set VAR to VALUE, reporting an OPTION error if this is a
    1088     conflict.  */
    1089  static void
    1090  specify_value (char const **var, char const *value, char const *option)
    1091  {
    1092    if (*var && ! STREQ (*var, value))
    1093      {
    1094        error (0, 0, _("conflicting %s option value '%s'"), option, value);
    1095        try_help (nullptr, nullptr);
    1096      }
    1097    *var = value;
    1098  }
    1099  
    1100  /* Set the output style to STYLE, diagnosing conflicts.  */
    1101  static void
    1102  specify_style (enum output_style style)
    1103  {
    1104    if (output_style != style)
    1105      {
    1106        if (output_style != OUTPUT_UNSPECIFIED)
    1107          try_help ("conflicting output style options", nullptr);
    1108        output_style = style;
    1109      }
    1110  }
    1111  
    1112  /* Set the color mode.  */
    1113  static void
    1114  specify_colors_style (char const *value)
    1115  {
    1116    if (value == nullptr || STREQ (value, "auto"))
    1117      colors_style = AUTO;
    1118    else if (STREQ (value, "always"))
    1119      colors_style = ALWAYS;
    1120    else if (STREQ (value, "never"))
    1121      colors_style = NEVER;
    1122    else
    1123      try_help ("invalid color '%s'", value);
    1124  }
    1125  
    1126  
    1127  /* Set the last-modified time of *ST to be the current time.  */
    1128  
    1129  static void
    1130  set_mtime_to_now (struct stat *st)
    1131  {
    1132  #ifdef STAT_TIMESPEC
    1133    gettime (&STAT_TIMESPEC (st, st_mtim));
    1134  #else
    1135    struct timespec t;
    1136    gettime (&t);
    1137    st->st_mtime = t.tv_sec;
    1138  # if defined STAT_TIMESPEC_NS
    1139    STAT_TIMESPEC_NS (st, st_mtim) = t.tv_nsec;
    1140  # elif defined HAVE_STRUCT_STAT_ST_SPARE1
    1141    st->st_spare1 = t.tv_nsec / 1000;
    1142  # endif
    1143  #endif
    1144  }
    1145  
    1146  /* cmp.file[f].desc markers */
    1147  enum { NONEXISTENT = -1 }; /* nonexistent file */
    1148  enum { UNOPENED = -2 }; /* unopened file (e.g. directory) */
    1149  
    1150  /* encoded errno value */
    1151  static int
    1152  errno_encode (int err)
    1153  {
    1154    return -3 - err;
    1155  }
    1156  
    1157  /* inverse of errno_encode */
    1158  static int
    1159  errno_decode (int desc)
    1160  {
    1161    return -3 - desc;
    1162  }
    1163  
    1164  /* Compare two files (or dirs) with parent comparison PARENT
    1165     and names NAME0 and NAME1.
    1166     (If PARENT is null, then the first name is just NAME0, etc.)
    1167     This is self-contained; it opens the files and closes them.
    1168  
    1169     Value is EXIT_SUCCESS if files are the same, EXIT_FAILURE if
    1170     different, EXIT_TROUBLE if there is a problem opening them.  */
    1171  
    1172  static int
    1173  compare_files (struct comparison const *parent,
    1174                 char const *name0,
    1175                 char const *name1)
    1176  {
    1177    struct comparison cmp;
    1178  #define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
    1179    register int f;
    1180    int status = EXIT_SUCCESS;
    1181    bool same_files;
    1182    char *free0;
    1183    char *free1;
    1184  
    1185    /* If this is directory comparison, perhaps we have a file
    1186       that exists only in one of the directories.
    1187       If so, just print a message to that effect.  */
    1188  
    1189    if (! ((name0 && name1)
    1190           || (unidirectional_new_file && name1)
    1191           || new_file))
    1192      {
    1193        char const *name = name0 ? name0 : name1;
    1194        char const *dir = parent->file[!name0].name;
    1195  
    1196        /* See POSIX 1003.1-2001 for this format.  */
    1197        message ("Only in %s: %s\n", dir, name);
    1198  
    1199        /* Return EXIT_FAILURE so that diff_dirs will return
    1200           EXIT_FAILURE ("some files differ").  */
    1201        return EXIT_FAILURE;
    1202      }
    1203  
    1204    memset (cmp.file, 0, sizeof cmp.file);
    1205    cmp.parent = parent;
    1206  
    1207    cmp.file[0].desc = name0 ? UNOPENED : NONEXISTENT;
    1208    cmp.file[1].desc = name1 ? UNOPENED : NONEXISTENT;
    1209  
    1210    /* Now record the full name of each file, including nonexistent ones.  */
    1211  
    1212    if (!name0)
    1213      name0 = name1;
    1214    if (!name1)
    1215      name1 = name0;
    1216  
    1217    if (!parent)
    1218      {
    1219        free0 = nullptr;
    1220        free1 = nullptr;
    1221        cmp.file[0].name = name0;
    1222        cmp.file[1].name = name1;
    1223      }
    1224    else
    1225      {
    1226        cmp.file[0].name = free0
    1227          = file_name_concat (parent->file[0].name, name0, nullptr);
    1228        cmp.file[1].name = free1
    1229          = file_name_concat (parent->file[1].name, name1, nullptr);
    1230      }
    1231  
    1232    /* Stat the files.  */
    1233  
    1234    for (f = 0; f < 2; f++)
    1235      {
    1236        if (cmp.file[f].desc != NONEXISTENT)
    1237          {
    1238            if (f && file_name_cmp (cmp.file[f].name, cmp.file[0].name) == 0)
    1239              {
    1240                cmp.file[f].desc = cmp.file[0].desc;
    1241                cmp.file[f].stat = cmp.file[0].stat;
    1242              }
    1243            else if (STREQ (cmp.file[f].name, "-"))
    1244              {
    1245                cmp.file[f].desc = STDIN_FILENO;
    1246                if (binary && ! isatty (STDIN_FILENO))
    1247                  set_binary_mode (STDIN_FILENO, O_BINARY);
    1248                if (fstat (STDIN_FILENO, &cmp.file[f].stat) != 0)
    1249                  cmp.file[f].desc = errno_encode (errno);
    1250                else
    1251                  {
    1252                    if (S_ISREG (cmp.file[f].stat.st_mode))
    1253                      {
    1254                        off_t pos = lseek (STDIN_FILENO, 0, SEEK_CUR);
    1255                        if (pos < 0)
    1256                          cmp.file[f].desc = errno_encode (errno);
    1257                        else
    1258                          cmp.file[f].stat.st_size =
    1259                            MAX (0, cmp.file[f].stat.st_size - pos);
    1260                      }
    1261  
    1262                    /* POSIX 1003.1-2001 requires current time for
    1263                       stdin.  */
    1264                    set_mtime_to_now (&cmp.file[f].stat);
    1265                  }
    1266              }
    1267            else if ((no_dereference_symlinks
    1268                      ? lstat (cmp.file[f].name, &cmp.file[f].stat)
    1269                      : stat (cmp.file[f].name, &cmp.file[f].stat))
    1270                     != 0)
    1271              cmp.file[f].desc = errno_encode (errno);
    1272          }
    1273      }
    1274  
    1275    /* Mark files as nonexistent as needed for -N and -P, if they are
    1276       inaccessible empty regular files (the kind of files that 'patch'
    1277       creates to indicate nonexistent backups), or if they are
    1278       top-level files that do not exist but their counterparts do
    1279       exist.  */
    1280    for (f = 0; f < 2; f++)
    1281      if ((new_file || (f == 0 && unidirectional_new_file))
    1282          && (cmp.file[f].desc == UNOPENED
    1283              ? (S_ISREG (cmp.file[f].stat.st_mode)
    1284                 && ! (cmp.file[f].stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))
    1285                 && cmp.file[f].stat.st_size == 0)
    1286              : ((cmp.file[f].desc == errno_encode (ENOENT)
    1287                  || cmp.file[f].desc == errno_encode (EBADF))
    1288                 && ! parent
    1289                 && (cmp.file[1 - f].desc == UNOPENED
    1290                     || cmp.file[1 - f].desc == STDIN_FILENO))))
    1291        cmp.file[f].desc = NONEXISTENT;
    1292  
    1293    for (f = 0; f < 2; f++)
    1294      if (cmp.file[f].desc == NONEXISTENT)
    1295        {
    1296          memset (&cmp.file[f].stat, 0, sizeof cmp.file[f].stat);
    1297          cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
    1298        }
    1299  
    1300    for (f = 0; f < 2; f++)
    1301      {
    1302        int e = errno_decode (cmp.file[f].desc);
    1303        if (0 <= e)
    1304          {
    1305            errno = e;
    1306            perror_with_name (cmp.file[f].name);
    1307            status = EXIT_TROUBLE;
    1308          }
    1309      }
    1310  
    1311    if (status == EXIT_SUCCESS && ! parent && !no_directory
    1312        && DIR_P (0) != DIR_P (1))
    1313      {
    1314        /* If one is a directory, and it was specified in the command line,
    1315           use the file in that dir with the other file's basename.  */
    1316  
    1317        int fnm_arg = DIR_P (0);
    1318        int dir_arg = 1 - fnm_arg;
    1319        char const *fnm = cmp.file[fnm_arg].name;
    1320        char const *dir = cmp.file[dir_arg].name;
    1321        char const *filename = cmp.file[dir_arg].name = free0
    1322          = find_dir_file_pathname (dir, last_component (fnm));
    1323  
    1324        if (STREQ (fnm, "-"))
    1325          fatal ("cannot compare '-' to a directory");
    1326  
    1327        if ((no_dereference_symlinks
    1328             ? lstat (filename, &cmp.file[dir_arg].stat)
    1329             : stat (filename, &cmp.file[dir_arg].stat))
    1330            != 0)
    1331          {
    1332            perror_with_name (filename);
    1333            status = EXIT_TROUBLE;
    1334          }
    1335      }
    1336  
    1337    if (status != EXIT_SUCCESS)
    1338      {
    1339        /* One of the files should exist but does not.  */
    1340      }
    1341    else if (cmp.file[0].desc == NONEXISTENT
    1342             && cmp.file[1].desc == NONEXISTENT)
    1343      {
    1344        /* Neither file "exists", so there's nothing to compare.  */
    1345      }
    1346    else if ((same_files
    1347              = (cmp.file[0].desc != NONEXISTENT
    1348                 && cmp.file[1].desc != NONEXISTENT
    1349                 && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
    1350                 && same_file_attributes (&cmp.file[0].stat,
    1351                                          &cmp.file[1].stat)))
    1352             && no_diff_means_no_output)
    1353      {
    1354        /* The two named files are actually the same physical file.
    1355           We know they are identical without actually reading them.  */
    1356      }
    1357    else if (DIR_P (0) & DIR_P (1))
    1358      {
    1359        if (output_style == OUTPUT_IFDEF)
    1360          fatal ("-D option not supported with directories");
    1361  
    1362        /* If both are directories, compare the files in them.  */
    1363  
    1364        if (parent && !recursive)
    1365          {
    1366            /* But don't compare dir contents one level down
    1367               unless -r was specified.
    1368               See POSIX 1003.1-2001 for this format.  */
    1369            message ("Common subdirectories: %s and %s\n",
    1370                     cmp.file[0].name, cmp.file[1].name);
    1371          }
    1372        else
    1373          status = diff_dirs (&cmp, compare_files);
    1374      }
    1375    else if ((DIR_P (0) | DIR_P (1))
    1376             || (parent
    1377                 && !((S_ISREG (cmp.file[0].stat.st_mode)
    1378                       || S_ISLNK (cmp.file[0].stat.st_mode))
    1379                      && (S_ISREG (cmp.file[1].stat.st_mode)
    1380                          || S_ISLNK  (cmp.file[1].stat.st_mode)))))
    1381      {
    1382        if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT)
    1383          {
    1384            /* We have a subdirectory that exists only in one directory.  */
    1385  
    1386            if ((DIR_P (0) | DIR_P (1))
    1387                && recursive
    1388                && (new_file
    1389                    || (unidirectional_new_file
    1390                        && cmp.file[0].desc == NONEXISTENT)))
    1391              status = diff_dirs (&cmp, compare_files);
    1392            else
    1393              {
    1394                char const *dir;
    1395  
    1396                /* PARENT must be non-null here.  */
    1397                assert (parent);
    1398                dir = parent->file[cmp.file[0].desc == NONEXISTENT].name;
    1399  
    1400                /* See POSIX 1003.1-2001 for this format.  */
    1401                message ("Only in %s: %s\n", dir, name0);
    1402  
    1403                status = EXIT_FAILURE;
    1404              }
    1405          }
    1406        else
    1407          {
    1408            /* We have two files that are not to be compared.  */
    1409  
    1410            /* See POSIX 1003.1-2001 for this format.  */
    1411            message ("File %s is a %s while file %s is a %s\n",
    1412  		   file_label[0] ? file_label[0] : cmp.file[0].name,
    1413  		   file_type (&cmp.file[0].stat),
    1414  		   file_label[1] ? file_label[1] : cmp.file[1].name,
    1415  		   file_type (&cmp.file[1].stat));
    1416  
    1417            /* This is a difference.  */
    1418            status = EXIT_FAILURE;
    1419          }
    1420      }
    1421    else if (S_ISLNK (cmp.file[0].stat.st_mode)
    1422             || S_ISLNK (cmp.file[1].stat.st_mode))
    1423      {
    1424        /* We get here only if we use lstat(), not stat().  */
    1425        assert (no_dereference_symlinks);
    1426  
    1427        if (S_ISLNK (cmp.file[0].stat.st_mode)
    1428            && S_ISLNK (cmp.file[1].stat.st_mode))
    1429          {
    1430            /* Compare the values of the symbolic links.  */
    1431            char *link_value[2] = { nullptr, nullptr };
    1432  
    1433            for (f = 0; f < 2; f++)
    1434              {
    1435                link_value[f] = xreadlink (cmp.file[f].name);
    1436                if (link_value[f] == nullptr)
    1437                  {
    1438                    perror_with_name (cmp.file[f].name);
    1439                    status = EXIT_TROUBLE;
    1440                    break;
    1441                  }
    1442              }
    1443            if (status == EXIT_SUCCESS)
    1444              {
    1445                if ( ! STREQ (link_value[0], link_value[1]))
    1446                  {
    1447                    message ("Symbolic links %s and %s differ\n",
    1448                             cmp.file[0].name, cmp.file[1].name);
    1449                    /* This is a difference.  */
    1450                    status = EXIT_FAILURE;
    1451                  }
    1452              }
    1453            for (f = 0; f < 2; f++)
    1454              free (link_value[f]);
    1455          }
    1456        else
    1457          {
    1458            /* We have two files that are not to be compared, because
    1459               one of them is a symbolic link and the other one is not.  */
    1460  
    1461            message ("File %s is a %s while file %s is a %s\n",
    1462  		   file_label[0] ? file_label[0] : cmp.file[0].name,
    1463  		   file_type (&cmp.file[0].stat),
    1464  		   file_label[1] ? file_label[1] : cmp.file[1].name,
    1465  		   file_type (&cmp.file[1].stat));
    1466  
    1467            /* This is a difference.  */
    1468            status = EXIT_FAILURE;
    1469          }
    1470      }
    1471    else if (files_can_be_treated_as_binary
    1472             && S_ISREG (cmp.file[0].stat.st_mode)
    1473             && S_ISREG (cmp.file[1].stat.st_mode)
    1474             && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size
    1475             && 0 < cmp.file[0].stat.st_size
    1476             && 0 < cmp.file[1].stat.st_size)
    1477      {
    1478        message ("Files %s and %s differ\n",
    1479                 file_label[0] ? file_label[0] : cmp.file[0].name,
    1480                 file_label[1] ? file_label[1] : cmp.file[1].name);
    1481        status = EXIT_FAILURE;
    1482      }
    1483    else
    1484      {
    1485        /* Both exist and neither is a directory.  */
    1486  
    1487        /* Open the files and record their descriptors.  */
    1488  
    1489        int oflags = O_RDONLY | (binary ? O_BINARY : 0);
    1490  
    1491        if (cmp.file[0].desc == UNOPENED)
    1492          if ((cmp.file[0].desc = open (cmp.file[0].name, oflags, 0)) < 0)
    1493            {
    1494              perror_with_name (cmp.file[0].name);
    1495              status = EXIT_TROUBLE;
    1496            }
    1497        if (cmp.file[1].desc == UNOPENED)
    1498          {
    1499            if (same_files)
    1500              cmp.file[1].desc = cmp.file[0].desc;
    1501            else if ((cmp.file[1].desc = open (cmp.file[1].name, oflags, 0)) < 0)
    1502              {
    1503                perror_with_name (cmp.file[1].name);
    1504                status = EXIT_TROUBLE;
    1505              }
    1506          }
    1507  
    1508        /* Compare the files, if no error was found.  */
    1509  
    1510        if (status == EXIT_SUCCESS)
    1511          status = diff_2_files (&cmp);
    1512  
    1513        /* Close the file descriptors.  */
    1514  
    1515        if (0 <= cmp.file[0].desc && close (cmp.file[0].desc) != 0)
    1516          {
    1517            perror_with_name (cmp.file[0].name);
    1518            status = EXIT_TROUBLE;
    1519          }
    1520        if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
    1521            && close (cmp.file[1].desc) != 0)
    1522          {
    1523            perror_with_name (cmp.file[1].name);
    1524            status = EXIT_TROUBLE;
    1525          }
    1526      }
    1527  
    1528    /* Now the comparison has been done, if no error prevented it,
    1529       and STATUS is the value this function will return.  */
    1530  
    1531    if (status == EXIT_SUCCESS)
    1532      {
    1533        if (report_identical_files && !DIR_P (0))
    1534          message ("Files %s and %s are identical\n",
    1535                   file_label[0] ? file_label[0] : cmp.file[0].name,
    1536                   file_label[1] ? file_label[1] : cmp.file[1].name);
    1537      }
    1538    else
    1539      {
    1540        /* Flush stdout so that the user sees differences immediately.
    1541           This can hurt performance, unfortunately.  */
    1542        if (fflush (stdout) != 0)
    1543          pfatal_with_name (_("standard output"));
    1544      }
    1545  
    1546    free (free0);
    1547    free (free1);
    1548  
    1549    return status;
    1550  }