xref: /dflybsd-src/contrib/diffutils/src/diff.c (revision ec6257fc30945063d93beb15307f3b475b9b04a1)
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:nNpPqrsS: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   /* Decode the options.  */
276 
277   while ((c = getopt_long (argc, argv, shortopts, longopts, 0)) != -1)
278     {
279       switch (c)
280 	{
281 	case 0:
282 	  break;
283 
284 	case '0':
285 	case '1':
286 	case '2':
287 	case '3':
288 	case '4':
289 	case '5':
290 	case '6':
291 	case '7':
292 	case '8':
293 	case '9':
294 	  if (! ISDIGIT (prev))
295 	    ocontext = c - '0';
296 	  else if (LIN_MAX / 10 < ocontext
297 		   || ((ocontext = 10 * ocontext + c - '0') < 0))
298 	    ocontext = LIN_MAX;
299 	  break;
300 
301 	case 'a':
302 	  text = true;
303 	  break;
304 
305 	case 'b':
306 	  if (ignore_white_space < IGNORE_SPACE_CHANGE)
307 	    ignore_white_space = IGNORE_SPACE_CHANGE;
308 	  break;
309 
310 	case 'B':
311 	  ignore_blank_lines = true;
312 	  break;
313 
314 	case 'C':
315 	case 'U':
316 	  {
317 	    if (optarg)
318 	      {
319 		numval = strtoumax (optarg, &numend, 10);
320 		if (*numend)
321 		  try_help ("invalid context length `%s'", optarg);
322 		if (LIN_MAX < numval)
323 		  numval = LIN_MAX;
324 	      }
325 	    else
326 	      numval = 3;
327 
328 	    specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
329 	    if (context < numval)
330 	      context = numval;
331 	    explicit_context = true;
332 	  }
333 	  break;
334 
335 	case 'c':
336 	  specify_style (OUTPUT_CONTEXT);
337 	  if (context < 3)
338 	    context = 3;
339 	  break;
340 
341 	case 'd':
342 	  minimal = true;
343 	  break;
344 
345 	case 'D':
346 	  specify_style (OUTPUT_IFDEF);
347 	  {
348 	    static char const C_ifdef_group_formats[] =
349 	      "%%=%c#ifndef %s\n%%<#endif /* ! %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
350 	    char *b = xmalloc (sizeof C_ifdef_group_formats
351 			       + 7 * strlen (optarg) - 14 /* 7*"%s" */
352 			       - 8 /* 5*"%%" + 3*"%c" */);
353 	    sprintf (b, C_ifdef_group_formats,
354 		     0,
355 		     optarg, optarg, 0,
356 		     optarg, optarg, 0,
357 		     optarg, optarg, optarg);
358 	    for (i = 0; i < sizeof group_format / sizeof *group_format; i++)
359 	      {
360 		specify_value (&group_format[i], b, "-D");
361 		b += strlen (b) + 1;
362 	      }
363 	  }
364 	  break;
365 
366 	case 'e':
367 	  specify_style (OUTPUT_ED);
368 	  break;
369 
370 	case 'E':
371 	  if (ignore_white_space < IGNORE_TAB_EXPANSION)
372 	    ignore_white_space = IGNORE_TAB_EXPANSION;
373 	  break;
374 
375 	case 'f':
376 	  specify_style (OUTPUT_FORWARD_ED);
377 	  break;
378 
379 	case 'F':
380 	  add_regexp (&function_regexp_list, optarg);
381 	  break;
382 
383 	case 'h':
384 	  /* Split the files into chunks for faster processing.
385 	     Usually does not change the result.
386 
387 	     This currently has no effect.  */
388 	  break;
389 
390 	case 'H':
391 	  speed_large_files = true;
392 	  break;
393 
394 	case 'i':
395 	  ignore_case = true;
396 	  break;
397 
398 	case 'I':
399 	  add_regexp (&ignore_regexp_list, optarg);
400 	  break;
401 
402 	case 'l':
403 	  if (!pr_program[0])
404 	    try_help ("pagination not supported on this host", 0);
405 	  paginate = true;
406 #ifdef SIGCHLD
407 	  /* Pagination requires forking and waiting, and
408 	     System V fork+wait does not work if SIGCHLD is ignored.  */
409 	  signal (SIGCHLD, SIG_DFL);
410 #endif
411 	  break;
412 
413 	case 'L':
414 	  if (!file_label[0])
415 	    file_label[0] = optarg;
416 	  else if (!file_label[1])
417 	    file_label[1] = optarg;
418 	  else
419 	    fatal ("too many file label options");
420 	  break;
421 
422 	case 'n':
423 	  specify_style (OUTPUT_RCS);
424 	  break;
425 
426 	case 'N':
427 	  new_file = true;
428 	  break;
429 
430 	case 'p':
431 	  show_c_function = true;
432 	  add_regexp (&function_regexp_list, "^[[:alpha:]$_]");
433 	  break;
434 
435 	case 'P':
436 	  unidirectional_new_file = true;
437 	  break;
438 
439 	case 'q':
440 	  brief = true;
441 	  break;
442 
443 	case 'r':
444 	  recursive = true;
445 	  break;
446 
447 	case 's':
448 	  report_identical_files = true;
449 	  break;
450 
451 	case 'S':
452 	  specify_value (&starting_file, optarg, "-S");
453 	  break;
454 
455 	case 't':
456 	  expand_tabs = true;
457 	  break;
458 
459 	case 'T':
460 	  initial_tab = true;
461 	  break;
462 
463 	case 'u':
464 	  specify_style (OUTPUT_UNIFIED);
465 	  if (context < 3)
466 	    context = 3;
467 	  break;
468 
469 	case 'v':
470 	  version_etc (stdout, "diff", PACKAGE_NAME, PACKAGE_VERSION,
471 		       "Paul Eggert", "Mike Haertel", "David Hayes",
472 		       "Richard Stallman", "Len Tower", (char *) 0);
473 	  check_stdout ();
474 	  return EXIT_SUCCESS;
475 
476 	case 'w':
477 	  ignore_white_space = IGNORE_ALL_SPACE;
478 	  break;
479 
480 	case 'x':
481 	  add_exclude (excluded, optarg, exclude_options ());
482 	  break;
483 
484 	case 'X':
485 	  if (add_exclude_file (add_exclude, excluded, optarg,
486 				exclude_options (), '\n'))
487 	    pfatal_with_name (optarg);
488 	  break;
489 
490 	case 'y':
491 	  specify_style (OUTPUT_SDIFF);
492 	  break;
493 
494 	case 'W':
495 	  numval = strtoumax (optarg, &numend, 10);
496 	  if (! (0 < numval && numval <= SIZE_MAX) || *numend)
497 	    try_help ("invalid width `%s'", optarg);
498 	  if (width != numval)
499 	    {
500 	      if (width)
501 		fatal ("conflicting width options");
502 	      width = numval;
503 	    }
504 	  break;
505 
506 	case BINARY_OPTION:
507 #if HAVE_SETMODE_DOS
508 	  binary = true;
509 	  set_binary_mode (STDOUT_FILENO, true);
510 #endif
511 	  break;
512 
513 	case FROM_FILE_OPTION:
514 	  specify_value (&from_file, optarg, "--from-file");
515 	  break;
516 
517 	case HELP_OPTION:
518 	  usage ();
519 	  check_stdout ();
520 	  return EXIT_SUCCESS;
521 
522 	case HORIZON_LINES_OPTION:
523 	  numval = strtoumax (optarg, &numend, 10);
524 	  if (*numend)
525 	    try_help ("invalid horizon length `%s'", optarg);
526 	  horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
527 	  break;
528 
529 	case IGNORE_FILE_NAME_CASE_OPTION:
530 	  ignore_file_name_case = true;
531 	  break;
532 
533 	case INHIBIT_HUNK_MERGE_OPTION:
534 	  /* This option is obsolete, but accept it for backward
535              compatibility.  */
536 	  break;
537 
538 	case LEFT_COLUMN_OPTION:
539 	  left_column = true;
540 	  break;
541 
542 	case LINE_FORMAT_OPTION:
543 	  specify_style (OUTPUT_IFDEF);
544 	  for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
545 	    specify_value (&line_format[i], optarg, "--line-format");
546 	  break;
547 
548 	case NO_IGNORE_FILE_NAME_CASE_OPTION:
549 	  ignore_file_name_case = false;
550 	  break;
551 
552 	case NORMAL_OPTION:
553 	  specify_style (OUTPUT_NORMAL);
554 	  break;
555 
556 	case SDIFF_MERGE_ASSIST_OPTION:
557 	  specify_style (OUTPUT_SDIFF);
558 	  sdiff_merge_assist = true;
559 	  break;
560 
561 	case STRIP_TRAILING_CR_OPTION:
562 	  strip_trailing_cr = true;
563 	  break;
564 
565 	case SUPPRESS_COMMON_LINES_OPTION:
566 	  suppress_common_lines = true;
567 	  break;
568 
569 	case TABSIZE_OPTION:
570 	  numval = strtoumax (optarg, &numend, 10);
571 	  if (! (0 < numval && numval <= SIZE_MAX) || *numend)
572 	    try_help ("invalid tabsize `%s'", optarg);
573 	  if (tabsize != numval)
574 	    {
575 	      if (tabsize)
576 		fatal ("conflicting tabsize options");
577 	      tabsize = numval;
578 	    }
579 	  break;
580 
581 	case TO_FILE_OPTION:
582 	  specify_value (&to_file, optarg, "--to-file");
583 	  break;
584 
585 	case UNCHANGED_LINE_FORMAT_OPTION:
586 	case OLD_LINE_FORMAT_OPTION:
587 	case NEW_LINE_FORMAT_OPTION:
588 	  specify_style (OUTPUT_IFDEF);
589 	  c -= UNCHANGED_LINE_FORMAT_OPTION;
590 	  specify_value (&line_format[c], optarg, line_format_option[c]);
591 	  break;
592 
593 	case UNCHANGED_GROUP_FORMAT_OPTION:
594 	case OLD_GROUP_FORMAT_OPTION:
595 	case NEW_GROUP_FORMAT_OPTION:
596 	case CHANGED_GROUP_FORMAT_OPTION:
597 	  specify_style (OUTPUT_IFDEF);
598 	  c -= UNCHANGED_GROUP_FORMAT_OPTION;
599 	  specify_value (&group_format[c], optarg, group_format_option[c]);
600 	  break;
601 
602 	default:
603 	  try_help (0, 0);
604 	}
605       prev = c;
606     }
607 
608   if (output_style == OUTPUT_UNSPECIFIED)
609     {
610       if (show_c_function)
611 	{
612 	  specify_style (OUTPUT_CONTEXT);
613 	  if (ocontext < 0)
614 	    context = 3;
615 	}
616       else
617 	specify_style (OUTPUT_NORMAL);
618     }
619 
620   if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
621     {
622 #ifdef ST_MTIM_NSEC
623       time_format = "%Y-%m-%d %H:%M:%S.%N %z";
624 #else
625       time_format = "%Y-%m-%d %H:%M:%S %z";
626 #endif
627     }
628   else
629     {
630       /* See POSIX 1003.1-2001 for this format.  */
631       time_format = "%a %b %e %T %Y";
632     }
633 
634   if (0 <= ocontext)
635     {
636       bool modern_usage = 200112 <= posix2_version ();
637 
638       if ((output_style == OUTPUT_CONTEXT
639 	   || output_style == OUTPUT_UNIFIED)
640 	  && (context < ocontext
641 	      || (ocontext < context && ! explicit_context)))
642 	{
643 	  if (modern_usage)
644 	    {
645 	      error (0, 0,
646 		     _("`-%ld' option is obsolete; use `-%c %ld'"),
647 		     (long int) ocontext,
648 		     output_style == OUTPUT_CONTEXT ? 'C' : 'U',
649 		     (long int) ocontext);
650 	      try_help (0, 0);
651 	    }
652 	  context = ocontext;
653 	}
654       else
655 	{
656 	  if (modern_usage)
657 	    {
658 	      error (0, 0, _("`-%ld' option is obsolete; omit it"),
659 		     (long int) ocontext);
660 	      try_help (0, 0);
661 	    }
662 	}
663     }
664 
665   if (! tabsize)
666     tabsize = 8;
667   if (! width)
668     width = 130;
669 
670   {
671     /* Maximize first the half line width, and then the gutter width,
672        according to the following constraints:
673 
674 	1.  Two half lines plus a gutter must fit in a line.
675 	2.  If the half line width is nonzero:
676 	    a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
677 	    b.  If tabs are not expanded to spaces,
678 		a half line plus a gutter is an integral number of tabs,
679 		so that tabs in the right column line up.  */
680 
681     intmax_t t = expand_tabs ? 1 : tabsize;
682     intmax_t w = width;
683     intmax_t off = (w + t + GUTTER_WIDTH_MINIMUM) / (2 * t)  *  t;
684     sdiff_half_width = MAX (0, MIN (off - GUTTER_WIDTH_MINIMUM, w - off)),
685     sdiff_column2_offset = sdiff_half_width ? off : w;
686   }
687 
688   /* Make the horizon at least as large as the context, so that
689      shift_boundaries has more freedom to shift the first and last hunks.  */
690   if (horizon_lines < context)
691     horizon_lines = context;
692 
693   summarize_regexp_list (&function_regexp_list);
694   summarize_regexp_list (&ignore_regexp_list);
695 
696   if (output_style == OUTPUT_IFDEF)
697     {
698       for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
699 	if (!line_format[i])
700 	  line_format[i] = "%l\n";
701       if (!group_format[OLD])
702 	group_format[OLD]
703 	  = group_format[CHANGED] ? group_format[CHANGED] : "%<";
704       if (!group_format[NEW])
705 	group_format[NEW]
706 	  = group_format[CHANGED] ? group_format[CHANGED] : "%>";
707       if (!group_format[UNCHANGED])
708 	group_format[UNCHANGED] = "%=";
709       if (!group_format[CHANGED])
710 	group_format[CHANGED] = concat (group_format[OLD],
711 					group_format[NEW], "");
712     }
713 
714   no_diff_means_no_output =
715     (output_style == OUTPUT_IFDEF ?
716       (!*group_format[UNCHANGED]
717        || (strcmp (group_format[UNCHANGED], "%=") == 0
718 	   && !*line_format[UNCHANGED]))
719      : (output_style != OUTPUT_SDIFF) | suppress_common_lines);
720 
721   files_can_be_treated_as_binary =
722     (brief & binary
723      & ~ (ignore_blank_lines | ignore_case | strip_trailing_cr
724 	  | (ignore_regexp_list.regexps || ignore_white_space)));
725 
726   switch_string = option_list (argv + 1, optind - 1);
727 
728   if (from_file)
729     {
730       if (to_file)
731 	fatal ("--from-file and --to-file both specified");
732       else
733 	for (; optind < argc; optind++)
734 	  {
735 	    int status = compare_files ((struct comparison *) 0,
736 					from_file, argv[optind]);
737 	    if (exit_status < status)
738 	      exit_status = status;
739 	  }
740     }
741   else
742     {
743       if (to_file)
744 	for (; optind < argc; optind++)
745 	  {
746 	    int status = compare_files ((struct comparison *) 0,
747 					argv[optind], to_file);
748 	    if (exit_status < status)
749 	      exit_status = status;
750 	  }
751       else
752 	{
753 	  if (argc - optind != 2)
754 	    {
755 	      if (argc - optind < 2)
756 		try_help ("missing operand after `%s'", argv[argc - 1]);
757 	      else
758 		try_help ("extra operand `%s'", argv[optind + 2]);
759 	    }
760 
761 	  exit_status = compare_files ((struct comparison *) 0,
762 				       argv[optind], argv[optind + 1]);
763 	}
764     }
765 
766   /* Print any messages that were saved up for last.  */
767   print_message_queue ();
768 
769   check_stdout ();
770   exit (exit_status);
771   return exit_status;
772 }
773 
774 /* Append to REGLIST the regexp PATTERN.  */
775 
776 static void
777 add_regexp (struct regexp_list *reglist, char const *pattern)
778 {
779   size_t patlen = strlen (pattern);
780   char const *m = re_compile_pattern (pattern, patlen, reglist->buf);
781 
782   if (m != 0)
783     error (0, 0, "%s: %s", pattern, m);
784   else
785     {
786       char *regexps = reglist->regexps;
787       size_t len = reglist->len;
788       bool multiple_regexps = reglist->multiple_regexps = regexps != 0;
789       size_t newlen = reglist->len = len + 2 * multiple_regexps + patlen;
790       size_t size = reglist->size;
791 
792       if (size <= newlen)
793 	{
794 	  if (!size)
795 	    size = 1;
796 
797 	  do size *= 2;
798 	  while (size <= newlen);
799 
800 	  reglist->size = size;
801 	  reglist->regexps = regexps = xrealloc (regexps, size);
802 	}
803       if (multiple_regexps)
804 	{
805 	  regexps[len++] = '\\';
806 	  regexps[len++] = '|';
807 	}
808       memcpy (regexps + len, pattern, patlen + 1);
809     }
810 }
811 
812 /* Ensure that REGLIST represents the disjunction of its regexps.
813    This is done here, rather than earlier, to avoid O(N^2) behavior.  */
814 
815 static void
816 summarize_regexp_list (struct regexp_list *reglist)
817 {
818   if (reglist->regexps)
819     {
820       /* At least one regexp was specified.  Allocate a fastmap for it.  */
821       reglist->buf->fastmap = xmalloc (1 << CHAR_BIT);
822       if (reglist->multiple_regexps)
823 	{
824 	  /* Compile the disjunction of the regexps.
825 	     (If just one regexp was specified, it is already compiled.)  */
826 	  char const *m = re_compile_pattern (reglist->regexps, reglist->len,
827 					      reglist->buf);
828 	  if (m != 0)
829 	    error (EXIT_TROUBLE, 0, "%s: %s", reglist->regexps, m);
830 	}
831     }
832 }
833 
834 static void
835 try_help (char const *reason_msgid, char const *operand)
836 {
837   if (reason_msgid)
838     error (0, 0, _(reason_msgid), operand);
839   error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
840 	 program_name);
841   abort ();
842 }
843 
844 static void
845 check_stdout (void)
846 {
847   if (ferror (stdout))
848     fatal ("write failed");
849   else if (fclose (stdout) != 0)
850     pfatal_with_name (_("standard output"));
851 }
852 
853 static char const * const option_help_msgid[] = {
854   N_("Compare files line by line."),
855   "",
856   N_("-i  --ignore-case  Ignore case differences in file contents."),
857   N_("--ignore-file-name-case  Ignore case when comparing file names."),
858   N_("--no-ignore-file-name-case  Consider case when comparing file names."),
859   N_("-E  --ignore-tab-expansion  Ignore changes due to tab expansion."),
860   N_("-b  --ignore-space-change  Ignore changes in the amount of white space."),
861   N_("-w  --ignore-all-space  Ignore all white space."),
862   N_("-B  --ignore-blank-lines  Ignore changes whose lines are all blank."),
863   N_("-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE."),
864   N_("--strip-trailing-cr  Strip trailing carriage return on input."),
865 #if HAVE_SETMODE_DOS
866   N_("--binary  Read and write data in binary mode."),
867 #endif
868   N_("-a  --text  Treat all files as text."),
869   "",
870   N_("-c  -C NUM  --context[=NUM]  Output NUM (default 3) lines of copied context.\n\
871 -u  -U NUM  --unified[=NUM]  Output NUM (default 3) lines of unified context.\n\
872   --label LABEL  Use LABEL instead of file name.\n\
873   -p  --show-c-function  Show which C function each change is in.\n\
874   -F RE  --show-function-line=RE  Show the most recent line matching RE."),
875   N_("-q  --brief  Output only whether files differ."),
876   N_("-e  --ed  Output an ed script."),
877   N_("--normal  Output a normal diff."),
878   N_("-n  --rcs  Output an RCS format diff."),
879   N_("-y  --side-by-side  Output in two columns.\n\
880   -W NUM  --width=NUM  Output at most NUM (default 130) print columns.\n\
881   --left-column  Output only the left column of common lines.\n\
882   --suppress-common-lines  Do not output common lines."),
883   N_("-D NAME  --ifdef=NAME  Output merged file to show `#ifdef NAME' diffs."),
884   N_("--GTYPE-group-format=GFMT  Similar, but format GTYPE input groups with GFMT."),
885   N_("--line-format=LFMT  Similar, but format all input lines with LFMT."),
886   N_("--LTYPE-line-format=LFMT  Similar, but format LTYPE input lines with LFMT."),
887   N_("  LTYPE is `old', `new', or `unchanged'.  GTYPE is LTYPE or `changed'."),
888   N_("  GFMT may contain:\n\
889     %<  lines from FILE1\n\
890     %>  lines from FILE2\n\
891     %=  lines common to FILE1 and FILE2\n\
892     %[-][WIDTH][.[PREC]]{doxX}LETTER  printf-style spec for LETTER\n\
893       LETTERs are as follows for new group, lower case for old group:\n\
894         F  first line number\n\
895         L  last line number\n\
896         N  number of lines = L-F+1\n\
897         E  F-1\n\
898         M  L+1"),
899   N_("  LFMT may contain:\n\
900     %L  contents of line\n\
901     %l  contents of line, excluding any trailing newline\n\
902     %[-][WIDTH][.[PREC]]{doxX}n  printf-style spec for input line number"),
903   N_("  Either GFMT or LFMT may contain:\n\
904     %%  %\n\
905     %c'C'  the single character C\n\
906     %c'\\OOO'  the character with octal code OOO"),
907   "",
908   N_("-l  --paginate  Pass the output through `pr' to paginate it."),
909   N_("-t  --expand-tabs  Expand tabs to spaces in output."),
910   N_("-T  --initial-tab  Make tabs line up by prepending a tab."),
911   N_("--tabsize=NUM  Tab stops are every NUM (default 8) print columns."),
912   "",
913   N_("-r  --recursive  Recursively compare any subdirectories found."),
914   N_("-N  --new-file  Treat absent files as empty."),
915   N_("--unidirectional-new-file  Treat absent first files as empty."),
916   N_("-s  --report-identical-files  Report when two files are the same."),
917   N_("-x PAT  --exclude=PAT  Exclude files that match PAT."),
918   N_("-X FILE  --exclude-from=FILE  Exclude files that match any pattern in FILE."),
919   N_("-S FILE  --starting-file=FILE  Start with FILE when comparing directories."),
920   N_("--from-file=FILE1  Compare FILE1 to all operands.  FILE1 can be a directory."),
921   N_("--to-file=FILE2  Compare all operands to FILE2.  FILE2 can be a directory."),
922   "",
923   N_("--horizon-lines=NUM  Keep NUM lines of the common prefix and suffix."),
924   N_("-d  --minimal  Try hard to find a smaller set of changes."),
925   N_("--speed-large-files  Assume large files and many scattered small changes."),
926   "",
927   N_("-v  --version  Output version info."),
928   N_("--help  Output this help."),
929   "",
930   N_("FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'."),
931   N_("If --from-file or --to-file is given, there are no restrictions on FILES."),
932   N_("If a FILE is `-', read standard input."),
933   N_("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."),
934   "",
935   N_("Report bugs to <bug-gnu-utils@gnu.org>."),
936   0
937 };
938 
939 static void
940 usage (void)
941 {
942   char const * const *p;
943 
944   printf (_("Usage: %s [OPTION]... FILES\n"), program_name);
945 
946   for (p = option_help_msgid;  *p;  p++)
947     {
948       if (!**p)
949 	putchar ('\n');
950       else
951 	{
952 	  char const *msg = _(*p);
953 	  char const *nl;
954 	  while ((nl = strchr (msg, '\n')))
955 	    {
956 	      int msglen = nl + 1 - msg;
957 	      printf ("  %.*s", msglen, msg);
958 	      msg = nl + 1;
959 	    }
960 
961 	  printf ("  %s\n" + 2 * (*msg != ' ' && *msg != '-'), msg);
962 	}
963     }
964 }
965 
966 /* Set VAR to VALUE, reporting an OPTION error if this is a
967    conflict.  */
968 static void
969 specify_value (char const **var, char const *value, char const *option)
970 {
971   if (*var && strcmp (*var, value) != 0)
972     {
973       error (0, 0, _("conflicting %s option value `%s'"), option, value);
974       try_help (0, 0);
975     }
976   *var = value;
977 }
978 
979 /* Set the output style to STYLE, diagnosing conflicts.  */
980 static void
981 specify_style (enum output_style style)
982 {
983   if (output_style != style)
984     {
985       if (output_style != OUTPUT_UNSPECIFIED)
986 	try_help ("conflicting output style options", 0);
987       output_style = style;
988     }
989 }
990 
991 /* Set the last-modified time of *ST to be the current time.  */
992 
993 static void
994 set_mtime_to_now (struct stat *st)
995 {
996 #ifdef ST_MTIM_NSEC
997 
998 # if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME
999   if (clock_gettime (CLOCK_REALTIME, &st->st_mtim) == 0)
1000     return;
1001 # endif
1002 
1003 # if HAVE_GETTIMEOFDAY
1004   {
1005     struct timeval timeval;
1006     if (gettimeofday (&timeval, 0) == 0)
1007       {
1008 	st->st_mtime = timeval.tv_sec;
1009 	st->st_mtim.ST_MTIM_NSEC = timeval.tv_usec * 1000;
1010 	return;
1011       }
1012   }
1013 # endif
1014 
1015 #endif /* ST_MTIM_NSEC */
1016 
1017   time (&st->st_mtime);
1018 }
1019 
1020 /* Compare two files (or dirs) with parent comparison PARENT
1021    and names NAME0 and NAME1.
1022    (If PARENT is 0, 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, *free1;
1039 
1040   /* If this is directory comparison, perhaps we have a file
1041      that exists only in one of the directories.
1042      If so, just print a message to that effect.  */
1043 
1044   if (! ((name0 && name1)
1045 	 || (unidirectional_new_file && name1)
1046 	 || new_file))
1047     {
1048       char const *name = name0 == 0 ? name1 : name0;
1049       char const *dir = parent->file[name0 == 0].name;
1050 
1051       /* See POSIX 1003.1-2001 for this format.  */
1052       message ("Only in %s: %s\n", dir, name);
1053 
1054       /* Return EXIT_FAILURE so that diff_dirs will return
1055 	 EXIT_FAILURE ("some files differ").  */
1056       return EXIT_FAILURE;
1057     }
1058 
1059   memset (cmp.file, 0, sizeof cmp.file);
1060   cmp.parent = parent;
1061 
1062   /* cmp.file[f].desc markers */
1063 #define NONEXISTENT (-1) /* nonexistent file */
1064 #define UNOPENED (-2) /* unopened file (e.g. directory) */
1065 #define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded errno value */
1066 
1067 #define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of ERRNO_ENCODE */
1068 
1069   cmp.file[0].desc = name0 == 0 ? NONEXISTENT : UNOPENED;
1070   cmp.file[1].desc = name1 == 0 ? NONEXISTENT : UNOPENED;
1071 
1072   /* Now record the full name of each file, including nonexistent ones.  */
1073 
1074   if (name0 == 0)
1075     name0 = name1;
1076   if (name1 == 0)
1077     name1 = name0;
1078 
1079   if (!parent)
1080     {
1081       free0 = 0;
1082       free1 = 0;
1083       cmp.file[0].name = name0;
1084       cmp.file[1].name = name1;
1085     }
1086   else
1087     {
1088       cmp.file[0].name = free0
1089 	= dir_file_pathname (parent->file[0].name, name0);
1090       cmp.file[1].name = free1
1091 	= dir_file_pathname (parent->file[1].name, name1);
1092     }
1093 
1094   /* Stat the files.  */
1095 
1096   for (f = 0; f < 2; f++)
1097     {
1098       if (cmp.file[f].desc != NONEXISTENT)
1099 	{
1100 	  if (f && file_name_cmp (cmp.file[f].name, cmp.file[0].name) == 0)
1101 	    {
1102 	      cmp.file[f].desc = cmp.file[0].desc;
1103 	      cmp.file[f].stat = cmp.file[0].stat;
1104 	    }
1105 	  else if (strcmp (cmp.file[f].name, "-") == 0)
1106 	    {
1107 	      cmp.file[f].desc = STDIN_FILENO;
1108 	      if (fstat (STDIN_FILENO, &cmp.file[f].stat) != 0)
1109 		cmp.file[f].desc = ERRNO_ENCODE (errno);
1110 	      else
1111 		{
1112 		  if (S_ISREG (cmp.file[f].stat.st_mode))
1113 		    {
1114 		      off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
1115 		      if (pos < 0)
1116 			cmp.file[f].desc = ERRNO_ENCODE (errno);
1117 		      else
1118 			cmp.file[f].stat.st_size =
1119 			  MAX (0, cmp.file[f].stat.st_size - pos);
1120 		    }
1121 
1122 		  /* POSIX 1003.1-2001 requires current time for
1123 		     stdin.  */
1124 		  set_mtime_to_now (&cmp.file[f].stat);
1125 		}
1126 	    }
1127 	  else if (stat (cmp.file[f].name, &cmp.file[f].stat) != 0)
1128 	    cmp.file[f].desc = ERRNO_ENCODE (errno);
1129 	}
1130     }
1131 
1132   /* Mark files as nonexistent as needed for -N and -P, if they are
1133      inaccessible empty regular files (the kind of files that 'patch'
1134      creates to indicate nonexistent backups), or if they are
1135      top-level files that do not exist but their counterparts do
1136      exist.  */
1137   for (f = 0; f < 2; f++)
1138     if ((new_file || (f == 0 && unidirectional_new_file))
1139 	&& (cmp.file[f].desc == UNOPENED
1140 	    ? (S_ISREG (cmp.file[f].stat.st_mode)
1141 	       && ! (cmp.file[f].stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))
1142 	       && cmp.file[f].stat.st_size == 0)
1143 	    : (cmp.file[f].desc == ERRNO_ENCODE (ENOENT)
1144 	       && ! parent
1145 	       && cmp.file[1 - f].desc == UNOPENED)))
1146       cmp.file[f].desc = NONEXISTENT;
1147 
1148   for (f = 0; f < 2; f++)
1149     if (cmp.file[f].desc == NONEXISTENT)
1150       {
1151 	memset (&cmp.file[f].stat, 0, sizeof cmp.file[f].stat);
1152 	cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
1153       }
1154 
1155   for (f = 0; f < 2; f++)
1156     {
1157       int e = ERRNO_DECODE (cmp.file[f].desc);
1158       if (0 <= e)
1159 	{
1160 	  errno = e;
1161 	  perror_with_name (cmp.file[f].name);
1162 	  status = EXIT_TROUBLE;
1163 	}
1164     }
1165 
1166   if (status == EXIT_SUCCESS && ! parent && DIR_P (0) != DIR_P (1))
1167     {
1168       /* If one is a directory, and it was specified in the command line,
1169 	 use the file in that dir with the other file's basename.  */
1170 
1171       int fnm_arg = DIR_P (0);
1172       int dir_arg = 1 - fnm_arg;
1173       char const *fnm = cmp.file[fnm_arg].name;
1174       char const *dir = cmp.file[dir_arg].name;
1175       char const *filename = cmp.file[dir_arg].name = free0
1176 	= dir_file_pathname (dir, base_name (fnm));
1177 
1178       if (strcmp (fnm, "-") == 0)
1179 	fatal ("cannot compare `-' to a directory");
1180 
1181       if (stat (filename, &cmp.file[dir_arg].stat) != 0)
1182 	{
1183 	  perror_with_name (filename);
1184 	  status = EXIT_TROUBLE;
1185 	}
1186     }
1187 
1188   if (status != EXIT_SUCCESS)
1189     {
1190       /* One of the files should exist but does not.  */
1191     }
1192   else if (cmp.file[0].desc == NONEXISTENT
1193 	   && cmp.file[1].desc == NONEXISTENT)
1194     {
1195       /* Neither file "exists", so there's nothing to compare.  */
1196     }
1197   else if ((same_files
1198 	    = (cmp.file[0].desc != NONEXISTENT
1199 	       && cmp.file[1].desc != NONEXISTENT
1200 	       && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
1201 	       && same_file_attributes (&cmp.file[0].stat,
1202 					&cmp.file[1].stat)))
1203 	   && no_diff_means_no_output)
1204     {
1205       /* The two named files are actually the same physical file.
1206 	 We know they are identical without actually reading them.  */
1207     }
1208   else if (DIR_P (0) & DIR_P (1))
1209     {
1210       if (output_style == OUTPUT_IFDEF)
1211 	fatal ("-D option not supported with directories");
1212 
1213       /* If both are directories, compare the files in them.  */
1214 
1215       if (parent && !recursive)
1216 	{
1217 	  /* But don't compare dir contents one level down
1218 	     unless -r was specified.
1219 	     See POSIX 1003.1-2001 for this format.  */
1220 	  message ("Common subdirectories: %s and %s\n",
1221 		   cmp.file[0].name, cmp.file[1].name);
1222 	}
1223       else
1224 	status = diff_dirs (&cmp, compare_files);
1225     }
1226   else if ((DIR_P (0) | DIR_P (1))
1227 	   || (parent
1228 	       && (! S_ISREG (cmp.file[0].stat.st_mode)
1229 		   || ! S_ISREG (cmp.file[1].stat.st_mode))))
1230     {
1231       if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT)
1232 	{
1233 	  /* We have a subdirectory that exists only in one directory.  */
1234 
1235 	  if ((DIR_P (0) | DIR_P (1))
1236 	      && recursive
1237 	      && (new_file
1238 		  || (unidirectional_new_file
1239 		      && cmp.file[0].desc == NONEXISTENT)))
1240 	    status = diff_dirs (&cmp, compare_files);
1241 	  else
1242 	    {
1243 	      char const *dir
1244 		= parent->file[cmp.file[0].desc == NONEXISTENT].name;
1245 
1246 	      /* See POSIX 1003.1-2001 for this format.  */
1247 	      message ("Only in %s: %s\n", dir, name0);
1248 
1249 	      status = EXIT_FAILURE;
1250 	    }
1251 	}
1252       else
1253 	{
1254 	  /* We have two files that are not to be compared.  */
1255 
1256 	  /* See POSIX 1003.1-2001 for this format.  */
1257 	  message5 ("File %s is a %s while file %s is a %s\n",
1258 		    file_label[0] ? file_label[0] : cmp.file[0].name,
1259 		    file_type (&cmp.file[0].stat),
1260 		    file_label[1] ? file_label[1] : cmp.file[1].name,
1261 		    file_type (&cmp.file[1].stat));
1262 
1263 	  /* This is a difference.  */
1264 	  status = EXIT_FAILURE;
1265 	}
1266     }
1267   else if (files_can_be_treated_as_binary
1268 	   && S_ISREG (cmp.file[0].stat.st_mode)
1269 	   && S_ISREG (cmp.file[1].stat.st_mode)
1270 	   && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size)
1271     {
1272       message ("Files %s and %s differ\n",
1273 	       file_label[0] ? file_label[0] : cmp.file[0].name,
1274 	       file_label[1] ? file_label[1] : cmp.file[1].name);
1275       status = EXIT_FAILURE;
1276     }
1277   else
1278     {
1279       /* Both exist and neither is a directory.  */
1280 
1281       /* Open the files and record their descriptors.  */
1282 
1283       if (cmp.file[0].desc == UNOPENED)
1284 	if ((cmp.file[0].desc = open (cmp.file[0].name, O_RDONLY, 0)) < 0)
1285 	  {
1286 	    perror_with_name (cmp.file[0].name);
1287 	    status = EXIT_TROUBLE;
1288 	  }
1289       if (cmp.file[1].desc == UNOPENED)
1290 	{
1291 	  if (same_files)
1292 	    cmp.file[1].desc = cmp.file[0].desc;
1293 	  else if ((cmp.file[1].desc = open (cmp.file[1].name, O_RDONLY, 0))
1294 		   < 0)
1295 	    {
1296 	      perror_with_name (cmp.file[1].name);
1297 	      status = EXIT_TROUBLE;
1298 	    }
1299 	}
1300 
1301 #if HAVE_SETMODE_DOS
1302       if (binary)
1303 	for (f = 0; f < 2; f++)
1304 	  if (0 <= cmp.file[f].desc)
1305 	    set_binary_mode (cmp.file[f].desc, true);
1306 #endif
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   if (free0)
1347     free (free0);
1348   if (free1)
1349     free (free1);
1350 
1351   return status;
1352 }
1353