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