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