1170754Sdelphij/* diff - compare files line by line
2170754Sdelphij
3170754Sdelphij   Copyright (C) 1988, 1989, 1992, 1993, 1994, 1996, 1998, 2001, 2002,
4170754Sdelphij   2004 Free Software Foundation, Inc.
5170754Sdelphij
6170754Sdelphij   This file is part of GNU DIFF.
7170754Sdelphij
8170754Sdelphij   GNU DIFF is free software; you can redistribute it and/or modify
9170754Sdelphij   it under the terms of the GNU General Public License as published by
10170754Sdelphij   the Free Software Foundation; either version 2, or (at your option)
11170754Sdelphij   any later version.
12170754Sdelphij
13170754Sdelphij   GNU DIFF is distributed in the hope that it will be useful,
14170754Sdelphij   but WITHOUT ANY WARRANTY; without even the implied warranty of
15170754Sdelphij   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16170754Sdelphij   See the GNU General Public License for more details.
17170754Sdelphij
18170754Sdelphij   You should have received a copy of the GNU General Public License
19170754Sdelphij   along with GNU DIFF; see the file COPYING.
20170754Sdelphij   If not, write to the Free Software Foundation,
21170754Sdelphij   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22170754Sdelphij
23170754Sdelphij#define GDIFF_MAIN
24170754Sdelphij#include "diff.h"
25170754Sdelphij#include "paths.h"
26170754Sdelphij#include <c-stack.h>
27170754Sdelphij#include <dirname.h>
28170754Sdelphij#include <error.h>
29170754Sdelphij#include <exclude.h>
30170754Sdelphij#include <exit.h>
31170754Sdelphij#include <exitfail.h>
32170754Sdelphij#include <file-type.h>
33170754Sdelphij#include <fnmatch.h>
34170754Sdelphij#include <getopt.h>
35170754Sdelphij#include <hard-locale.h>
36170754Sdelphij#include <posixver.h>
37170754Sdelphij#include <prepargs.h>
38170754Sdelphij#include <quotesys.h>
39170754Sdelphij#include <setmode.h>
40170754Sdelphij#include <version-etc.h>
41170754Sdelphij#include <xalloc.h>
42170754Sdelphij
43170754Sdelphij#ifndef GUTTER_WIDTH_MINIMUM
44170754Sdelphij# define GUTTER_WIDTH_MINIMUM 3
45170754Sdelphij#endif
46170754Sdelphij
47170754Sdelphijstruct regexp_list
48170754Sdelphij{
49170754Sdelphij  char *regexps;	/* chars representing disjunction of the regexps */
50170754Sdelphij  size_t len;		/* chars used in `regexps' */
51170754Sdelphij  size_t size;		/* size malloc'ed for `regexps'; 0 if not malloc'ed */
52170754Sdelphij  bool multiple_regexps;/* Does `regexps' represent a disjunction?  */
53170754Sdelphij  struct re_pattern_buffer *buf;
54170754Sdelphij};
55170754Sdelphij
56170754Sdelphijstatic int compare_files (struct comparison const *, char const *, char const *);
57170754Sdelphijstatic void add_regexp (struct regexp_list *, char const *);
58170754Sdelphijstatic void summarize_regexp_list (struct regexp_list *);
59170754Sdelphijstatic void specify_style (enum output_style);
60170754Sdelphijstatic void specify_value (char const **, char const *, char const *);
61170754Sdelphijstatic void try_help (char const *, char const *) __attribute__((noreturn));
62170754Sdelphijstatic void check_stdout (void);
63170754Sdelphijstatic void usage (void);
64170754Sdelphij
65170754Sdelphij/* If comparing directories, compare their common subdirectories
66170754Sdelphij   recursively.  */
67170754Sdelphijstatic bool recursive;
68170754Sdelphij
69170754Sdelphij/* In context diffs, show previous lines that match these regexps.  */
70170754Sdelphijstatic struct regexp_list function_regexp_list;
71170754Sdelphij
72170754Sdelphij/* Ignore changes affecting only lines that match these regexps.  */
73170754Sdelphijstatic struct regexp_list ignore_regexp_list;
74170754Sdelphij
75170754Sdelphij#if HAVE_SETMODE_DOS
76170754Sdelphij/* Use binary I/O when reading and writing data (--binary).
77170754Sdelphij   On POSIX hosts, this has no effect.  */
78170754Sdelphijstatic bool binary;
79170754Sdelphij#else
80170754Sdelphijenum { binary = true };
81170754Sdelphij#endif
82170754Sdelphij
83170754Sdelphij/* When comparing directories, if a file appears only in one
84170754Sdelphij   directory, treat it as present but empty in the other (-N).
85170754Sdelphij   Then `patch' would create the file with appropriate contents.  */
86170754Sdelphijstatic bool new_file;
87170754Sdelphij
88170754Sdelphij/* When comparing directories, if a file appears only in the second
89170754Sdelphij   directory of the two, treat it as present but empty in the other
90170754Sdelphij   (--unidirectional-new-file).
91170754Sdelphij   Then `patch' would create the file with appropriate contents.  */
92170754Sdelphijstatic bool unidirectional_new_file;
93170754Sdelphij
94170754Sdelphij/* Report files compared that are the same (-s).
95170754Sdelphij   Normally nothing is output when that happens.  */
96170754Sdelphijstatic bool report_identical_files;
97170754Sdelphij
98170754Sdelphij
99170754Sdelphij/* Return a string containing the command options with which diff was invoked.
100170754Sdelphij   Spaces appear between what were separate ARGV-elements.
101170754Sdelphij   There is a space at the beginning but none at the end.
102170754Sdelphij   If there were no options, the result is an empty string.
103170754Sdelphij
104170754Sdelphij   Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
105170754Sdelphij   the length of that vector.  */
106170754Sdelphij
107170754Sdelphijstatic char *
108170754Sdelphijoption_list (char **optionvec, int count)
109170754Sdelphij{
110170754Sdelphij  int i;
111170754Sdelphij  size_t size = 1;
112170754Sdelphij  char *result;
113170754Sdelphij  char *p;
114170754Sdelphij
115170754Sdelphij  for (i = 0; i < count; i++)
116170754Sdelphij    size += 1 + quote_system_arg ((char *) 0, optionvec[i]);
117170754Sdelphij
118170754Sdelphij  p = result = xmalloc (size);
119170754Sdelphij
120170754Sdelphij  for (i = 0; i < count; i++)
121170754Sdelphij    {
122170754Sdelphij      *p++ = ' ';
123170754Sdelphij      p += quote_system_arg (p, optionvec[i]);
124170754Sdelphij    }
125170754Sdelphij
126170754Sdelphij  *p = 0;
127170754Sdelphij  return result;
128170754Sdelphij}
129170754Sdelphij
130170754Sdelphij
131170754Sdelphij/* Return an option value suitable for add_exclude.  */
132170754Sdelphij
133170754Sdelphijstatic int
134170754Sdelphijexclude_options (void)
135170754Sdelphij{
136170754Sdelphij  return EXCLUDE_WILDCARDS | (ignore_file_name_case ? FNM_CASEFOLD : 0);
137170754Sdelphij}
138170754Sdelphij
139170754Sdelphijstatic char const shortopts[] =
140239360Sobrien"0123456789abBcC:dD:eEfF:hHiI:lL:nNopPqrsS:tTuU:vwW:x:X:y";
141170754Sdelphij
142170754Sdelphij/* Values for long options that do not have single-letter equivalents.  */
143170754Sdelphijenum
144170754Sdelphij{
145170754Sdelphij  BINARY_OPTION = CHAR_MAX + 1,
146170754Sdelphij  FROM_FILE_OPTION,
147170754Sdelphij  HELP_OPTION,
148170754Sdelphij  HORIZON_LINES_OPTION,
149170754Sdelphij  IGNORE_FILE_NAME_CASE_OPTION,
150170754Sdelphij  INHIBIT_HUNK_MERGE_OPTION,
151170754Sdelphij  LEFT_COLUMN_OPTION,
152170754Sdelphij  LINE_FORMAT_OPTION,
153170754Sdelphij  NO_IGNORE_FILE_NAME_CASE_OPTION,
154170754Sdelphij  NORMAL_OPTION,
155170754Sdelphij  SDIFF_MERGE_ASSIST_OPTION,
156170754Sdelphij  STRIP_TRAILING_CR_OPTION,
157170754Sdelphij  SUPPRESS_COMMON_LINES_OPTION,
158170754Sdelphij  TABSIZE_OPTION,
159170754Sdelphij  TO_FILE_OPTION,
160170754Sdelphij
161170754Sdelphij  /* These options must be in sequence.  */
162170754Sdelphij  UNCHANGED_LINE_FORMAT_OPTION,
163170754Sdelphij  OLD_LINE_FORMAT_OPTION,
164170754Sdelphij  NEW_LINE_FORMAT_OPTION,
165170754Sdelphij
166170754Sdelphij  /* These options must be in sequence.  */
167170754Sdelphij  UNCHANGED_GROUP_FORMAT_OPTION,
168170754Sdelphij  OLD_GROUP_FORMAT_OPTION,
169170754Sdelphij  NEW_GROUP_FORMAT_OPTION,
170170754Sdelphij  CHANGED_GROUP_FORMAT_OPTION
171170754Sdelphij};
172170754Sdelphij
173170754Sdelphijstatic char const group_format_option[][sizeof "--unchanged-group-format"] =
174170754Sdelphij  {
175170754Sdelphij    "--unchanged-group-format",
176170754Sdelphij    "--old-group-format",
177170754Sdelphij    "--new-group-format",
178170754Sdelphij    "--changed-group-format"
179170754Sdelphij  };
180170754Sdelphij
181170754Sdelphijstatic char const line_format_option[][sizeof "--unchanged-line-format"] =
182170754Sdelphij  {
183170754Sdelphij    "--unchanged-line-format",
184170754Sdelphij    "--old-line-format",
185170754Sdelphij    "--new-line-format"
186170754Sdelphij  };
187170754Sdelphij
188170754Sdelphijstatic struct option const longopts[] =
189170754Sdelphij{
190170754Sdelphij  {"binary", 0, 0, BINARY_OPTION},
191170754Sdelphij  {"brief", 0, 0, 'q'},
192170754Sdelphij  {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
193170754Sdelphij  {"context", 2, 0, 'C'},
194170754Sdelphij  {"ed", 0, 0, 'e'},
195170754Sdelphij  {"exclude", 1, 0, 'x'},
196170754Sdelphij  {"exclude-from", 1, 0, 'X'},
197170754Sdelphij  {"expand-tabs", 0, 0, 't'},
198170754Sdelphij  {"forward-ed", 0, 0, 'f'},
199170754Sdelphij  {"from-file", 1, 0, FROM_FILE_OPTION},
200170754Sdelphij  {"help", 0, 0, HELP_OPTION},
201170754Sdelphij  {"horizon-lines", 1, 0, HORIZON_LINES_OPTION},
202170754Sdelphij  {"ifdef", 1, 0, 'D'},
203170754Sdelphij  {"ignore-all-space", 0, 0, 'w'},
204170754Sdelphij  {"ignore-blank-lines", 0, 0, 'B'},
205170754Sdelphij  {"ignore-case", 0, 0, 'i'},
206170754Sdelphij  {"ignore-file-name-case", 0, 0, IGNORE_FILE_NAME_CASE_OPTION},
207170754Sdelphij  {"ignore-matching-lines", 1, 0, 'I'},
208170754Sdelphij  {"ignore-space-change", 0, 0, 'b'},
209170754Sdelphij  {"ignore-tab-expansion", 0, 0, 'E'},
210170754Sdelphij  {"inhibit-hunk-merge", 0, 0, INHIBIT_HUNK_MERGE_OPTION},
211170754Sdelphij  {"initial-tab", 0, 0, 'T'},
212170754Sdelphij  {"label", 1, 0, 'L'},
213170754Sdelphij  {"left-column", 0, 0, LEFT_COLUMN_OPTION},
214170754Sdelphij  {"line-format", 1, 0, LINE_FORMAT_OPTION},
215170754Sdelphij  {"minimal", 0, 0, 'd'},
216170754Sdelphij  {"new-file", 0, 0, 'N'},
217170754Sdelphij  {"new-group-format", 1, 0, NEW_GROUP_FORMAT_OPTION},
218170754Sdelphij  {"new-line-format", 1, 0, NEW_LINE_FORMAT_OPTION},
219170754Sdelphij  {"no-ignore-file-name-case", 0, 0, NO_IGNORE_FILE_NAME_CASE_OPTION},
220170754Sdelphij  {"normal", 0, 0, NORMAL_OPTION},
221170754Sdelphij  {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
222170754Sdelphij  {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
223170754Sdelphij  {"paginate", 0, 0, 'l'},
224170754Sdelphij  {"rcs", 0, 0, 'n'},
225170754Sdelphij  {"recursive", 0, 0, 'r'},
226170754Sdelphij  {"report-identical-files", 0, 0, 's'},
227170754Sdelphij  {"sdiff-merge-assist", 0, 0, SDIFF_MERGE_ASSIST_OPTION},
228170754Sdelphij  {"show-c-function", 0, 0, 'p'},
229170754Sdelphij  {"show-function-line", 1, 0, 'F'},
230170754Sdelphij  {"side-by-side", 0, 0, 'y'},
231170754Sdelphij  {"speed-large-files", 0, 0, 'H'},
232170754Sdelphij  {"starting-file", 1, 0, 'S'},
233170754Sdelphij  {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
234170754Sdelphij  {"suppress-common-lines", 0, 0, SUPPRESS_COMMON_LINES_OPTION},
235170754Sdelphij  {"tabsize", 1, 0, TABSIZE_OPTION},
236170754Sdelphij  {"text", 0, 0, 'a'},
237170754Sdelphij  {"to-file", 1, 0, TO_FILE_OPTION},
238170754Sdelphij  {"unchanged-group-format", 1, 0, UNCHANGED_GROUP_FORMAT_OPTION},
239170754Sdelphij  {"unchanged-line-format", 1, 0, UNCHANGED_LINE_FORMAT_OPTION},
240170754Sdelphij  {"unidirectional-new-file", 0, 0, 'P'},
241170754Sdelphij  {"unified", 2, 0, 'U'},
242170754Sdelphij  {"version", 0, 0, 'v'},
243170754Sdelphij  {"width", 1, 0, 'W'},
244170754Sdelphij  {0, 0, 0, 0}
245170754Sdelphij};
246170754Sdelphij
247170754Sdelphijint
248170754Sdelphijmain (int argc, char **argv)
249170754Sdelphij{
250170754Sdelphij  int exit_status = EXIT_SUCCESS;
251170754Sdelphij  int c;
252170754Sdelphij  int i;
253170754Sdelphij  int prev = -1;
254170754Sdelphij  lin ocontext = -1;
255170754Sdelphij  bool explicit_context = false;
256170754Sdelphij  size_t width = 0;
257170754Sdelphij  bool show_c_function = false;
258170754Sdelphij  char const *from_file = 0;
259170754Sdelphij  char const *to_file = 0;
260170754Sdelphij  uintmax_t numval;
261170754Sdelphij  char *numend;
262170754Sdelphij
263170754Sdelphij  /* Do our initializations.  */
264170754Sdelphij  exit_failure = 2;
265170754Sdelphij  initialize_main (&argc, &argv);
266170754Sdelphij  program_name = argv[0];
267170754Sdelphij  setlocale (LC_ALL, "");
268170754Sdelphij  textdomain (PACKAGE);
269170754Sdelphij  c_stack_action (0);
270170754Sdelphij  function_regexp_list.buf = &function_regexp;
271170754Sdelphij  ignore_regexp_list.buf = &ignore_regexp;
272239360Sobrien  re_set_syntax (RE_SYNTAX_GREP);
273170754Sdelphij  excluded = new_exclude ();
274170754Sdelphij
275239360Sobrien  prepend_default_options (getenv ("DIFF_OPTIONS"), &argc, &argv);
276239360Sobrien
277170754Sdelphij  /* Decode the options.  */
278170754Sdelphij
279170754Sdelphij  while ((c = getopt_long (argc, argv, shortopts, longopts, 0)) != -1)
280170754Sdelphij    {
281170754Sdelphij      switch (c)
282170754Sdelphij	{
283170754Sdelphij	case 0:
284170754Sdelphij	  break;
285170754Sdelphij
286170754Sdelphij	case '0':
287170754Sdelphij	case '1':
288170754Sdelphij	case '2':
289170754Sdelphij	case '3':
290170754Sdelphij	case '4':
291170754Sdelphij	case '5':
292170754Sdelphij	case '6':
293170754Sdelphij	case '7':
294170754Sdelphij	case '8':
295170754Sdelphij	case '9':
296170754Sdelphij	  if (! ISDIGIT (prev))
297170754Sdelphij	    ocontext = c - '0';
298170754Sdelphij	  else if (LIN_MAX / 10 < ocontext
299170754Sdelphij		   || ((ocontext = 10 * ocontext + c - '0') < 0))
300170754Sdelphij	    ocontext = LIN_MAX;
301170754Sdelphij	  break;
302170754Sdelphij
303170754Sdelphij	case 'a':
304170754Sdelphij	  text = true;
305170754Sdelphij	  break;
306170754Sdelphij
307170754Sdelphij	case 'b':
308170754Sdelphij	  if (ignore_white_space < IGNORE_SPACE_CHANGE)
309170754Sdelphij	    ignore_white_space = IGNORE_SPACE_CHANGE;
310170754Sdelphij	  break;
311170754Sdelphij
312170754Sdelphij	case 'B':
313170754Sdelphij	  ignore_blank_lines = true;
314170754Sdelphij	  break;
315170754Sdelphij
316170754Sdelphij	case 'C':
317170754Sdelphij	case 'U':
318170754Sdelphij	  {
319170754Sdelphij	    if (optarg)
320170754Sdelphij	      {
321170754Sdelphij		numval = strtoumax (optarg, &numend, 10);
322170754Sdelphij		if (*numend)
323170754Sdelphij		  try_help ("invalid context length `%s'", optarg);
324170754Sdelphij		if (LIN_MAX < numval)
325170754Sdelphij		  numval = LIN_MAX;
326170754Sdelphij	      }
327170754Sdelphij	    else
328170754Sdelphij	      numval = 3;
329170754Sdelphij
330170754Sdelphij	    specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
331170754Sdelphij	    if (context < numval)
332170754Sdelphij	      context = numval;
333170754Sdelphij	    explicit_context = true;
334170754Sdelphij	  }
335170754Sdelphij	  break;
336170754Sdelphij
337170754Sdelphij	case 'c':
338170754Sdelphij	  specify_style (OUTPUT_CONTEXT);
339170754Sdelphij	  if (context < 3)
340170754Sdelphij	    context = 3;
341170754Sdelphij	  break;
342170754Sdelphij
343170754Sdelphij	case 'd':
344170754Sdelphij	  minimal = true;
345170754Sdelphij	  break;
346170754Sdelphij
347170754Sdelphij	case 'D':
348170754Sdelphij	  specify_style (OUTPUT_IFDEF);
349170754Sdelphij	  {
350170754Sdelphij	    static char const C_ifdef_group_formats[] =
351170754Sdelphij	      "%%=%c#ifndef %s\n%%<#endif /* ! %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
352170754Sdelphij	    char *b = xmalloc (sizeof C_ifdef_group_formats
353170754Sdelphij			       + 7 * strlen (optarg) - 14 /* 7*"%s" */
354170754Sdelphij			       - 8 /* 5*"%%" + 3*"%c" */);
355170754Sdelphij	    sprintf (b, C_ifdef_group_formats,
356170754Sdelphij		     0,
357170754Sdelphij		     optarg, optarg, 0,
358170754Sdelphij		     optarg, optarg, 0,
359170754Sdelphij		     optarg, optarg, optarg);
360170754Sdelphij	    for (i = 0; i < sizeof group_format / sizeof *group_format; i++)
361170754Sdelphij	      {
362170754Sdelphij		specify_value (&group_format[i], b, "-D");
363170754Sdelphij		b += strlen (b) + 1;
364170754Sdelphij	      }
365170754Sdelphij	  }
366170754Sdelphij	  break;
367170754Sdelphij
368170754Sdelphij	case 'e':
369170754Sdelphij	  specify_style (OUTPUT_ED);
370170754Sdelphij	  break;
371170754Sdelphij
372170754Sdelphij	case 'E':
373170754Sdelphij	  if (ignore_white_space < IGNORE_TAB_EXPANSION)
374170754Sdelphij	    ignore_white_space = IGNORE_TAB_EXPANSION;
375170754Sdelphij	  break;
376170754Sdelphij
377170754Sdelphij	case 'f':
378170754Sdelphij	  specify_style (OUTPUT_FORWARD_ED);
379170754Sdelphij	  break;
380170754Sdelphij
381170754Sdelphij	case 'F':
382170754Sdelphij	  add_regexp (&function_regexp_list, optarg);
383170754Sdelphij	  break;
384170754Sdelphij
385170754Sdelphij	case 'h':
386170754Sdelphij	  /* Split the files into chunks for faster processing.
387170754Sdelphij	     Usually does not change the result.
388170754Sdelphij
389170754Sdelphij	     This currently has no effect.  */
390170754Sdelphij	  break;
391170754Sdelphij
392170754Sdelphij	case 'H':
393170754Sdelphij	  speed_large_files = true;
394170754Sdelphij	  break;
395170754Sdelphij
396170754Sdelphij	case 'i':
397170754Sdelphij	  ignore_case = true;
398170754Sdelphij	  break;
399170754Sdelphij
400170754Sdelphij	case 'I':
401170754Sdelphij	  add_regexp (&ignore_regexp_list, optarg);
402170754Sdelphij	  break;
403170754Sdelphij
404170754Sdelphij	case 'l':
405170754Sdelphij	  if (!pr_program[0])
406170754Sdelphij	    try_help ("pagination not supported on this host", 0);
407170754Sdelphij	  paginate = true;
408170754Sdelphij#ifdef SIGCHLD
409170754Sdelphij	  /* Pagination requires forking and waiting, and
410170754Sdelphij	     System V fork+wait does not work if SIGCHLD is ignored.  */
411170754Sdelphij	  signal (SIGCHLD, SIG_DFL);
412170754Sdelphij#endif
413170754Sdelphij	  break;
414170754Sdelphij
415170754Sdelphij	case 'L':
416170754Sdelphij	  if (!file_label[0])
417170754Sdelphij	    file_label[0] = optarg;
418170754Sdelphij	  else if (!file_label[1])
419170754Sdelphij	    file_label[1] = optarg;
420170754Sdelphij	  else
421170754Sdelphij	    fatal ("too many file label options");
422170754Sdelphij	  break;
423170754Sdelphij
424170754Sdelphij	case 'n':
425170754Sdelphij	  specify_style (OUTPUT_RCS);
426170754Sdelphij	  break;
427170754Sdelphij
428170754Sdelphij	case 'N':
429170754Sdelphij	  new_file = true;
430170754Sdelphij	  break;
431170754Sdelphij
432239360Sobrien	case 'o':
433239360Sobrien	  /* Output in the old tradition style.  */
434239360Sobrien	  specify_style (OUTPUT_NORMAL);
435239360Sobrien	  break;
436239360Sobrien
437170754Sdelphij	case 'p':
438170754Sdelphij	  show_c_function = true;
439170754Sdelphij	  add_regexp (&function_regexp_list, "^[[:alpha:]$_]");
440170754Sdelphij	  break;
441170754Sdelphij
442170754Sdelphij	case 'P':
443170754Sdelphij	  unidirectional_new_file = true;
444170754Sdelphij	  break;
445170754Sdelphij
446170754Sdelphij	case 'q':
447170754Sdelphij	  brief = true;
448170754Sdelphij	  break;
449170754Sdelphij
450170754Sdelphij	case 'r':
451170754Sdelphij	  recursive = true;
452170754Sdelphij	  break;
453170754Sdelphij
454170754Sdelphij	case 's':
455170754Sdelphij	  report_identical_files = true;
456170754Sdelphij	  break;
457170754Sdelphij
458170754Sdelphij	case 'S':
459170754Sdelphij	  specify_value (&starting_file, optarg, "-S");
460170754Sdelphij	  break;
461170754Sdelphij
462170754Sdelphij	case 't':
463170754Sdelphij	  expand_tabs = true;
464170754Sdelphij	  break;
465170754Sdelphij
466170754Sdelphij	case 'T':
467170754Sdelphij	  initial_tab = true;
468170754Sdelphij	  break;
469170754Sdelphij
470170754Sdelphij	case 'u':
471170754Sdelphij	  specify_style (OUTPUT_UNIFIED);
472170754Sdelphij	  if (context < 3)
473170754Sdelphij	    context = 3;
474170754Sdelphij	  break;
475170754Sdelphij
476170754Sdelphij	case 'v':
477170754Sdelphij	  version_etc (stdout, "diff", PACKAGE_NAME, PACKAGE_VERSION,
478170754Sdelphij		       "Paul Eggert", "Mike Haertel", "David Hayes",
479170754Sdelphij		       "Richard Stallman", "Len Tower", (char *) 0);
480170754Sdelphij	  check_stdout ();
481170754Sdelphij	  return EXIT_SUCCESS;
482170754Sdelphij
483170754Sdelphij	case 'w':
484170754Sdelphij	  ignore_white_space = IGNORE_ALL_SPACE;
485170754Sdelphij	  break;
486170754Sdelphij
487170754Sdelphij	case 'x':
488170754Sdelphij	  add_exclude (excluded, optarg, exclude_options ());
489170754Sdelphij	  break;
490170754Sdelphij
491170754Sdelphij	case 'X':
492170754Sdelphij	  if (add_exclude_file (add_exclude, excluded, optarg,
493170754Sdelphij				exclude_options (), '\n'))
494170754Sdelphij	    pfatal_with_name (optarg);
495170754Sdelphij	  break;
496170754Sdelphij
497170754Sdelphij	case 'y':
498170754Sdelphij	  specify_style (OUTPUT_SDIFF);
499170754Sdelphij	  break;
500170754Sdelphij
501170754Sdelphij	case 'W':
502170754Sdelphij	  numval = strtoumax (optarg, &numend, 10);
503170754Sdelphij	  if (! (0 < numval && numval <= SIZE_MAX) || *numend)
504170754Sdelphij	    try_help ("invalid width `%s'", optarg);
505170754Sdelphij	  if (width != numval)
506170754Sdelphij	    {
507170754Sdelphij	      if (width)
508170754Sdelphij		fatal ("conflicting width options");
509170754Sdelphij	      width = numval;
510170754Sdelphij	    }
511170754Sdelphij	  break;
512170754Sdelphij
513170754Sdelphij	case BINARY_OPTION:
514170754Sdelphij#if HAVE_SETMODE_DOS
515170754Sdelphij	  binary = true;
516170754Sdelphij	  set_binary_mode (STDOUT_FILENO, true);
517170754Sdelphij#endif
518170754Sdelphij	  break;
519170754Sdelphij
520170754Sdelphij	case FROM_FILE_OPTION:
521170754Sdelphij	  specify_value (&from_file, optarg, "--from-file");
522170754Sdelphij	  break;
523170754Sdelphij
524170754Sdelphij	case HELP_OPTION:
525170754Sdelphij	  usage ();
526170754Sdelphij	  check_stdout ();
527170754Sdelphij	  return EXIT_SUCCESS;
528170754Sdelphij
529170754Sdelphij	case HORIZON_LINES_OPTION:
530170754Sdelphij	  numval = strtoumax (optarg, &numend, 10);
531170754Sdelphij	  if (*numend)
532170754Sdelphij	    try_help ("invalid horizon length `%s'", optarg);
533170754Sdelphij	  horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
534170754Sdelphij	  break;
535170754Sdelphij
536170754Sdelphij	case IGNORE_FILE_NAME_CASE_OPTION:
537170754Sdelphij	  ignore_file_name_case = true;
538170754Sdelphij	  break;
539170754Sdelphij
540170754Sdelphij	case INHIBIT_HUNK_MERGE_OPTION:
541170754Sdelphij	  /* This option is obsolete, but accept it for backward
542170754Sdelphij             compatibility.  */
543170754Sdelphij	  break;
544170754Sdelphij
545170754Sdelphij	case LEFT_COLUMN_OPTION:
546170754Sdelphij	  left_column = true;
547170754Sdelphij	  break;
548170754Sdelphij
549170754Sdelphij	case LINE_FORMAT_OPTION:
550170754Sdelphij	  specify_style (OUTPUT_IFDEF);
551170754Sdelphij	  for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
552170754Sdelphij	    specify_value (&line_format[i], optarg, "--line-format");
553170754Sdelphij	  break;
554170754Sdelphij
555170754Sdelphij	case NO_IGNORE_FILE_NAME_CASE_OPTION:
556170754Sdelphij	  ignore_file_name_case = false;
557170754Sdelphij	  break;
558170754Sdelphij
559170754Sdelphij	case NORMAL_OPTION:
560170754Sdelphij	  specify_style (OUTPUT_NORMAL);
561170754Sdelphij	  break;
562170754Sdelphij
563170754Sdelphij	case SDIFF_MERGE_ASSIST_OPTION:
564170754Sdelphij	  specify_style (OUTPUT_SDIFF);
565170754Sdelphij	  sdiff_merge_assist = true;
566170754Sdelphij	  break;
567170754Sdelphij
568170754Sdelphij	case STRIP_TRAILING_CR_OPTION:
569170754Sdelphij	  strip_trailing_cr = true;
570170754Sdelphij	  break;
571170754Sdelphij
572170754Sdelphij	case SUPPRESS_COMMON_LINES_OPTION:
573170754Sdelphij	  suppress_common_lines = true;
574170754Sdelphij	  break;
575170754Sdelphij
576170754Sdelphij	case TABSIZE_OPTION:
577170754Sdelphij	  numval = strtoumax (optarg, &numend, 10);
578170754Sdelphij	  if (! (0 < numval && numval <= SIZE_MAX) || *numend)
579170754Sdelphij	    try_help ("invalid tabsize `%s'", optarg);
580170754Sdelphij	  if (tabsize != numval)
581170754Sdelphij	    {
582170754Sdelphij	      if (tabsize)
583170754Sdelphij		fatal ("conflicting tabsize options");
584170754Sdelphij	      tabsize = numval;
585170754Sdelphij	    }
586170754Sdelphij	  break;
587170754Sdelphij
588170754Sdelphij	case TO_FILE_OPTION:
589170754Sdelphij	  specify_value (&to_file, optarg, "--to-file");
590170754Sdelphij	  break;
591170754Sdelphij
592170754Sdelphij	case UNCHANGED_LINE_FORMAT_OPTION:
593170754Sdelphij	case OLD_LINE_FORMAT_OPTION:
594170754Sdelphij	case NEW_LINE_FORMAT_OPTION:
595170754Sdelphij	  specify_style (OUTPUT_IFDEF);
596170754Sdelphij	  c -= UNCHANGED_LINE_FORMAT_OPTION;
597170754Sdelphij	  specify_value (&line_format[c], optarg, line_format_option[c]);
598170754Sdelphij	  break;
599170754Sdelphij
600170754Sdelphij	case UNCHANGED_GROUP_FORMAT_OPTION:
601170754Sdelphij	case OLD_GROUP_FORMAT_OPTION:
602170754Sdelphij	case NEW_GROUP_FORMAT_OPTION:
603170754Sdelphij	case CHANGED_GROUP_FORMAT_OPTION:
604170754Sdelphij	  specify_style (OUTPUT_IFDEF);
605170754Sdelphij	  c -= UNCHANGED_GROUP_FORMAT_OPTION;
606170754Sdelphij	  specify_value (&group_format[c], optarg, group_format_option[c]);
607170754Sdelphij	  break;
608170754Sdelphij
609170754Sdelphij	default:
610170754Sdelphij	  try_help (0, 0);
611170754Sdelphij	}
612170754Sdelphij      prev = c;
613170754Sdelphij    }
614170754Sdelphij
615170754Sdelphij  if (output_style == OUTPUT_UNSPECIFIED)
616170754Sdelphij    {
617170754Sdelphij      if (show_c_function)
618170754Sdelphij	{
619170754Sdelphij	  specify_style (OUTPUT_CONTEXT);
620170754Sdelphij	  if (ocontext < 0)
621170754Sdelphij	    context = 3;
622170754Sdelphij	}
623170754Sdelphij      else
624170754Sdelphij	specify_style (OUTPUT_NORMAL);
625170754Sdelphij    }
626170754Sdelphij
627170754Sdelphij  if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
628170754Sdelphij    {
629170754Sdelphij#ifdef ST_MTIM_NSEC
630170754Sdelphij      time_format = "%Y-%m-%d %H:%M:%S.%N %z";
631170754Sdelphij#else
632170754Sdelphij      time_format = "%Y-%m-%d %H:%M:%S %z";
633170754Sdelphij#endif
634170754Sdelphij    }
635170754Sdelphij  else
636170754Sdelphij    {
637170754Sdelphij      /* See POSIX 1003.1-2001 for this format.  */
638170754Sdelphij      time_format = "%a %b %e %T %Y";
639170754Sdelphij    }
640170754Sdelphij
641170754Sdelphij  if (0 <= ocontext)
642170754Sdelphij    {
643170754Sdelphij      bool modern_usage = 200112 <= posix2_version ();
644170754Sdelphij
645170754Sdelphij      if ((output_style == OUTPUT_CONTEXT
646170754Sdelphij	   || output_style == OUTPUT_UNIFIED)
647170754Sdelphij	  && (context < ocontext
648170754Sdelphij	      || (ocontext < context && ! explicit_context)))
649170754Sdelphij	{
650170754Sdelphij	  if (modern_usage)
651170754Sdelphij	    {
652170754Sdelphij	      error (0, 0,
653170754Sdelphij		     _("`-%ld' option is obsolete; use `-%c %ld'"),
654170754Sdelphij		     (long int) ocontext,
655170754Sdelphij		     output_style == OUTPUT_CONTEXT ? 'C' : 'U',
656170754Sdelphij		     (long int) ocontext);
657170754Sdelphij	      try_help (0, 0);
658170754Sdelphij	    }
659170754Sdelphij	  context = ocontext;
660170754Sdelphij	}
661170754Sdelphij      else
662170754Sdelphij	{
663170754Sdelphij	  if (modern_usage)
664170754Sdelphij	    {
665170754Sdelphij	      error (0, 0, _("`-%ld' option is obsolete; omit it"),
666170754Sdelphij		     (long int) ocontext);
667170754Sdelphij	      try_help (0, 0);
668170754Sdelphij	    }
669170754Sdelphij	}
670170754Sdelphij    }
671170754Sdelphij
672170754Sdelphij  if (! tabsize)
673170754Sdelphij    tabsize = 8;
674170754Sdelphij  if (! width)
675170754Sdelphij    width = 130;
676170754Sdelphij
677170754Sdelphij  {
678170754Sdelphij    /* Maximize first the half line width, and then the gutter width,
679170754Sdelphij       according to the following constraints:
680170754Sdelphij
681170754Sdelphij	1.  Two half lines plus a gutter must fit in a line.
682170754Sdelphij	2.  If the half line width is nonzero:
683170754Sdelphij	    a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
684170754Sdelphij	    b.  If tabs are not expanded to spaces,
685170754Sdelphij		a half line plus a gutter is an integral number of tabs,
686170754Sdelphij		so that tabs in the right column line up.  */
687170754Sdelphij
688170754Sdelphij    intmax_t t = expand_tabs ? 1 : tabsize;
689170754Sdelphij    intmax_t w = width;
690170754Sdelphij    intmax_t off = (w + t + GUTTER_WIDTH_MINIMUM) / (2 * t)  *  t;
691170754Sdelphij    sdiff_half_width = MAX (0, MIN (off - GUTTER_WIDTH_MINIMUM, w - off)),
692170754Sdelphij    sdiff_column2_offset = sdiff_half_width ? off : w;
693170754Sdelphij  }
694170754Sdelphij
695170754Sdelphij  /* Make the horizon at least as large as the context, so that
696170754Sdelphij     shift_boundaries has more freedom to shift the first and last hunks.  */
697170754Sdelphij  if (horizon_lines < context)
698170754Sdelphij    horizon_lines = context;
699170754Sdelphij
700170754Sdelphij  summarize_regexp_list (&function_regexp_list);
701170754Sdelphij  summarize_regexp_list (&ignore_regexp_list);
702170754Sdelphij
703170754Sdelphij  if (output_style == OUTPUT_IFDEF)
704170754Sdelphij    {
705170754Sdelphij      for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
706170754Sdelphij	if (!line_format[i])
707170754Sdelphij	  line_format[i] = "%l\n";
708170754Sdelphij      if (!group_format[OLD])
709170754Sdelphij	group_format[OLD]
710170754Sdelphij	  = group_format[CHANGED] ? group_format[CHANGED] : "%<";
711170754Sdelphij      if (!group_format[NEW])
712170754Sdelphij	group_format[NEW]
713170754Sdelphij	  = group_format[CHANGED] ? group_format[CHANGED] : "%>";
714170754Sdelphij      if (!group_format[UNCHANGED])
715170754Sdelphij	group_format[UNCHANGED] = "%=";
716170754Sdelphij      if (!group_format[CHANGED])
717170754Sdelphij	group_format[CHANGED] = concat (group_format[OLD],
718170754Sdelphij					group_format[NEW], "");
719170754Sdelphij    }
720170754Sdelphij
721170754Sdelphij  no_diff_means_no_output =
722170754Sdelphij    (output_style == OUTPUT_IFDEF ?
723170754Sdelphij      (!*group_format[UNCHANGED]
724170754Sdelphij       || (strcmp (group_format[UNCHANGED], "%=") == 0
725170754Sdelphij	   && !*line_format[UNCHANGED]))
726170754Sdelphij     : (output_style != OUTPUT_SDIFF) | suppress_common_lines);
727170754Sdelphij
728170754Sdelphij  files_can_be_treated_as_binary =
729170754Sdelphij    (brief & binary
730170754Sdelphij     & ~ (ignore_blank_lines | ignore_case | strip_trailing_cr
731170754Sdelphij	  | (ignore_regexp_list.regexps || ignore_white_space)));
732170754Sdelphij
733170754Sdelphij  switch_string = option_list (argv + 1, optind - 1);
734170754Sdelphij
735170754Sdelphij  if (from_file)
736170754Sdelphij    {
737170754Sdelphij      if (to_file)
738170754Sdelphij	fatal ("--from-file and --to-file both specified");
739170754Sdelphij      else
740170754Sdelphij	for (; optind < argc; optind++)
741170754Sdelphij	  {
742170754Sdelphij	    int status = compare_files ((struct comparison *) 0,
743170754Sdelphij					from_file, argv[optind]);
744170754Sdelphij	    if (exit_status < status)
745170754Sdelphij	      exit_status = status;
746170754Sdelphij	  }
747170754Sdelphij    }
748170754Sdelphij  else
749170754Sdelphij    {
750170754Sdelphij      if (to_file)
751170754Sdelphij	for (; optind < argc; optind++)
752170754Sdelphij	  {
753170754Sdelphij	    int status = compare_files ((struct comparison *) 0,
754170754Sdelphij					argv[optind], to_file);
755170754Sdelphij	    if (exit_status < status)
756170754Sdelphij	      exit_status = status;
757170754Sdelphij	  }
758170754Sdelphij      else
759170754Sdelphij	{
760170754Sdelphij	  if (argc - optind != 2)
761170754Sdelphij	    {
762170754Sdelphij	      if (argc - optind < 2)
763170754Sdelphij		try_help ("missing operand after `%s'", argv[argc - 1]);
764170754Sdelphij	      else
765170754Sdelphij		try_help ("extra operand `%s'", argv[optind + 2]);
766170754Sdelphij	    }
767170754Sdelphij
768170754Sdelphij	  exit_status = compare_files ((struct comparison *) 0,
769170754Sdelphij				       argv[optind], argv[optind + 1]);
770170754Sdelphij	}
771170754Sdelphij    }
772170754Sdelphij
773170754Sdelphij  /* Print any messages that were saved up for last.  */
774170754Sdelphij  print_message_queue ();
775170754Sdelphij
776170754Sdelphij  check_stdout ();
777170754Sdelphij  exit (exit_status);
778170754Sdelphij  return exit_status;
779170754Sdelphij}
780170754Sdelphij
781170754Sdelphij/* Append to REGLIST the regexp PATTERN.  */
782170754Sdelphij
783170754Sdelphijstatic void
784170754Sdelphijadd_regexp (struct regexp_list *reglist, char const *pattern)
785170754Sdelphij{
786170754Sdelphij  size_t patlen = strlen (pattern);
787170754Sdelphij  char const *m = re_compile_pattern (pattern, patlen, reglist->buf);
788170754Sdelphij
789170754Sdelphij  if (m != 0)
790170754Sdelphij    error (0, 0, "%s: %s", pattern, m);
791170754Sdelphij  else
792170754Sdelphij    {
793170754Sdelphij      char *regexps = reglist->regexps;
794170754Sdelphij      size_t len = reglist->len;
795170754Sdelphij      bool multiple_regexps = reglist->multiple_regexps = regexps != 0;
796170754Sdelphij      size_t newlen = reglist->len = len + 2 * multiple_regexps + patlen;
797170754Sdelphij      size_t size = reglist->size;
798170754Sdelphij
799170754Sdelphij      if (size <= newlen)
800170754Sdelphij	{
801170754Sdelphij	  if (!size)
802170754Sdelphij	    size = 1;
803170754Sdelphij
804170754Sdelphij	  do size *= 2;
805170754Sdelphij	  while (size <= newlen);
806170754Sdelphij
807170754Sdelphij	  reglist->size = size;
808170754Sdelphij	  reglist->regexps = regexps = xrealloc (regexps, size);
809170754Sdelphij	}
810170754Sdelphij      if (multiple_regexps)
811170754Sdelphij	{
812170754Sdelphij	  regexps[len++] = '\\';
813170754Sdelphij	  regexps[len++] = '|';
814170754Sdelphij	}
815170754Sdelphij      memcpy (regexps + len, pattern, patlen + 1);
816170754Sdelphij    }
817170754Sdelphij}
818170754Sdelphij
819170754Sdelphij/* Ensure that REGLIST represents the disjunction of its regexps.
820170754Sdelphij   This is done here, rather than earlier, to avoid O(N^2) behavior.  */
821170754Sdelphij
822170754Sdelphijstatic void
823170754Sdelphijsummarize_regexp_list (struct regexp_list *reglist)
824170754Sdelphij{
825170754Sdelphij  if (reglist->regexps)
826170754Sdelphij    {
827170754Sdelphij      /* At least one regexp was specified.  Allocate a fastmap for it.  */
828170754Sdelphij      reglist->buf->fastmap = xmalloc (1 << CHAR_BIT);
829170754Sdelphij      if (reglist->multiple_regexps)
830170754Sdelphij	{
831170754Sdelphij	  /* Compile the disjunction of the regexps.
832170754Sdelphij	     (If just one regexp was specified, it is already compiled.)  */
833170754Sdelphij	  char const *m = re_compile_pattern (reglist->regexps, reglist->len,
834170754Sdelphij					      reglist->buf);
835170754Sdelphij	  if (m != 0)
836170754Sdelphij	    error (EXIT_TROUBLE, 0, "%s: %s", reglist->regexps, m);
837170754Sdelphij	}
838170754Sdelphij    }
839170754Sdelphij}
840170754Sdelphij
841170754Sdelphijstatic void
842170754Sdelphijtry_help (char const *reason_msgid, char const *operand)
843170754Sdelphij{
844170754Sdelphij  if (reason_msgid)
845170754Sdelphij    error (0, 0, _(reason_msgid), operand);
846170754Sdelphij  error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
847170754Sdelphij	 program_name);
848170754Sdelphij  abort ();
849170754Sdelphij}
850170754Sdelphij
851170754Sdelphijstatic void
852170754Sdelphijcheck_stdout (void)
853170754Sdelphij{
854170754Sdelphij  if (ferror (stdout))
855170754Sdelphij    fatal ("write failed");
856170754Sdelphij  else if (fclose (stdout) != 0)
857170754Sdelphij    pfatal_with_name (_("standard output"));
858170754Sdelphij}
859170754Sdelphij
860170754Sdelphijstatic char const * const option_help_msgid[] = {
861170754Sdelphij  N_("Compare files line by line."),
862170754Sdelphij  "",
863170754Sdelphij  N_("-i  --ignore-case  Ignore case differences in file contents."),
864170754Sdelphij  N_("--ignore-file-name-case  Ignore case when comparing file names."),
865170754Sdelphij  N_("--no-ignore-file-name-case  Consider case when comparing file names."),
866170754Sdelphij  N_("-E  --ignore-tab-expansion  Ignore changes due to tab expansion."),
867170754Sdelphij  N_("-b  --ignore-space-change  Ignore changes in the amount of white space."),
868170754Sdelphij  N_("-w  --ignore-all-space  Ignore all white space."),
869170754Sdelphij  N_("-B  --ignore-blank-lines  Ignore changes whose lines are all blank."),
870170754Sdelphij  N_("-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE."),
871170754Sdelphij  N_("--strip-trailing-cr  Strip trailing carriage return on input."),
872170754Sdelphij#if HAVE_SETMODE_DOS
873170754Sdelphij  N_("--binary  Read and write data in binary mode."),
874170754Sdelphij#endif
875170754Sdelphij  N_("-a  --text  Treat all files as text."),
876170754Sdelphij  "",
877170754Sdelphij  N_("-c  -C NUM  --context[=NUM]  Output NUM (default 3) lines of copied context.\n\
878170754Sdelphij-u  -U NUM  --unified[=NUM]  Output NUM (default 3) lines of unified context.\n\
879170754Sdelphij  --label LABEL  Use LABEL instead of file name.\n\
880170754Sdelphij  -p  --show-c-function  Show which C function each change is in.\n\
881170754Sdelphij  -F RE  --show-function-line=RE  Show the most recent line matching RE."),
882170754Sdelphij  N_("-q  --brief  Output only whether files differ."),
883170754Sdelphij  N_("-e  --ed  Output an ed script."),
884170754Sdelphij  N_("--normal  Output a normal diff."),
885170754Sdelphij  N_("-n  --rcs  Output an RCS format diff."),
886170754Sdelphij  N_("-y  --side-by-side  Output in two columns.\n\
887170754Sdelphij  -W NUM  --width=NUM  Output at most NUM (default 130) print columns.\n\
888170754Sdelphij  --left-column  Output only the left column of common lines.\n\
889170754Sdelphij  --suppress-common-lines  Do not output common lines."),
890170754Sdelphij  N_("-D NAME  --ifdef=NAME  Output merged file to show `#ifdef NAME' diffs."),
891170754Sdelphij  N_("--GTYPE-group-format=GFMT  Similar, but format GTYPE input groups with GFMT."),
892170754Sdelphij  N_("--line-format=LFMT  Similar, but format all input lines with LFMT."),
893170754Sdelphij  N_("--LTYPE-line-format=LFMT  Similar, but format LTYPE input lines with LFMT."),
894170754Sdelphij  N_("  LTYPE is `old', `new', or `unchanged'.  GTYPE is LTYPE or `changed'."),
895170754Sdelphij  N_("  GFMT may contain:\n\
896170754Sdelphij    %<  lines from FILE1\n\
897170754Sdelphij    %>  lines from FILE2\n\
898170754Sdelphij    %=  lines common to FILE1 and FILE2\n\
899170754Sdelphij    %[-][WIDTH][.[PREC]]{doxX}LETTER  printf-style spec for LETTER\n\
900170754Sdelphij      LETTERs are as follows for new group, lower case for old group:\n\
901170754Sdelphij        F  first line number\n\
902170754Sdelphij        L  last line number\n\
903170754Sdelphij        N  number of lines = L-F+1\n\
904170754Sdelphij        E  F-1\n\
905170754Sdelphij        M  L+1"),
906170754Sdelphij  N_("  LFMT may contain:\n\
907170754Sdelphij    %L  contents of line\n\
908170754Sdelphij    %l  contents of line, excluding any trailing newline\n\
909170754Sdelphij    %[-][WIDTH][.[PREC]]{doxX}n  printf-style spec for input line number"),
910170754Sdelphij  N_("  Either GFMT or LFMT may contain:\n\
911170754Sdelphij    %%  %\n\
912170754Sdelphij    %c'C'  the single character C\n\
913170754Sdelphij    %c'\\OOO'  the character with octal code OOO"),
914170754Sdelphij  "",
915170754Sdelphij  N_("-l  --paginate  Pass the output through `pr' to paginate it."),
916170754Sdelphij  N_("-t  --expand-tabs  Expand tabs to spaces in output."),
917170754Sdelphij  N_("-T  --initial-tab  Make tabs line up by prepending a tab."),
918170754Sdelphij  N_("--tabsize=NUM  Tab stops are every NUM (default 8) print columns."),
919170754Sdelphij  "",
920170754Sdelphij  N_("-r  --recursive  Recursively compare any subdirectories found."),
921170754Sdelphij  N_("-N  --new-file  Treat absent files as empty."),
922170754Sdelphij  N_("--unidirectional-new-file  Treat absent first files as empty."),
923170754Sdelphij  N_("-s  --report-identical-files  Report when two files are the same."),
924170754Sdelphij  N_("-x PAT  --exclude=PAT  Exclude files that match PAT."),
925170754Sdelphij  N_("-X FILE  --exclude-from=FILE  Exclude files that match any pattern in FILE."),
926170754Sdelphij  N_("-S FILE  --starting-file=FILE  Start with FILE when comparing directories."),
927170754Sdelphij  N_("--from-file=FILE1  Compare FILE1 to all operands.  FILE1 can be a directory."),
928170754Sdelphij  N_("--to-file=FILE2  Compare all operands to FILE2.  FILE2 can be a directory."),
929170754Sdelphij  "",
930170754Sdelphij  N_("--horizon-lines=NUM  Keep NUM lines of the common prefix and suffix."),
931170754Sdelphij  N_("-d  --minimal  Try hard to find a smaller set of changes."),
932170754Sdelphij  N_("--speed-large-files  Assume large files and many scattered small changes."),
933170754Sdelphij  "",
934170754Sdelphij  N_("-v  --version  Output version info."),
935170754Sdelphij  N_("--help  Output this help."),
936170754Sdelphij  "",
937170754Sdelphij  N_("FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'."),
938170754Sdelphij  N_("If --from-file or --to-file is given, there are no restrictions on FILES."),
939170754Sdelphij  N_("If a FILE is `-', read standard input."),
940170754Sdelphij  N_("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."),
941170754Sdelphij  "",
942170754Sdelphij  N_("Report bugs to <bug-gnu-utils@gnu.org>."),
943170754Sdelphij  0
944170754Sdelphij};
945170754Sdelphij
946170754Sdelphijstatic void
947170754Sdelphijusage (void)
948170754Sdelphij{
949170754Sdelphij  char const * const *p;
950170754Sdelphij
951170754Sdelphij  printf (_("Usage: %s [OPTION]... FILES\n"), program_name);
952170754Sdelphij
953170754Sdelphij  for (p = option_help_msgid;  *p;  p++)
954170754Sdelphij    {
955170754Sdelphij      if (!**p)
956170754Sdelphij	putchar ('\n');
957170754Sdelphij      else
958170754Sdelphij	{
959170754Sdelphij	  char const *msg = _(*p);
960170754Sdelphij	  char const *nl;
961170754Sdelphij	  while ((nl = strchr (msg, '\n')))
962170754Sdelphij	    {
963170754Sdelphij	      int msglen = nl + 1 - msg;
964170754Sdelphij	      printf ("  %.*s", msglen, msg);
965170754Sdelphij	      msg = nl + 1;
966170754Sdelphij	    }
967170754Sdelphij
968170754Sdelphij	  printf ("  %s\n" + 2 * (*msg != ' ' && *msg != '-'), msg);
969170754Sdelphij	}
970170754Sdelphij    }
971170754Sdelphij}
972170754Sdelphij
973170754Sdelphij/* Set VAR to VALUE, reporting an OPTION error if this is a
974170754Sdelphij   conflict.  */
975170754Sdelphijstatic void
976170754Sdelphijspecify_value (char const **var, char const *value, char const *option)
977170754Sdelphij{
978170754Sdelphij  if (*var && strcmp (*var, value) != 0)
979170754Sdelphij    {
980170754Sdelphij      error (0, 0, _("conflicting %s option value `%s'"), option, value);
981170754Sdelphij      try_help (0, 0);
982170754Sdelphij    }
983170754Sdelphij  *var = value;
984170754Sdelphij}
985170754Sdelphij
986170754Sdelphij/* Set the output style to STYLE, diagnosing conflicts.  */
987170754Sdelphijstatic void
988170754Sdelphijspecify_style (enum output_style style)
989170754Sdelphij{
990170754Sdelphij  if (output_style != style)
991170754Sdelphij    {
992170754Sdelphij      output_style = style;
993170754Sdelphij    }
994170754Sdelphij}
995170754Sdelphij
996170754Sdelphij/* Set the last-modified time of *ST to be the current time.  */
997170754Sdelphij
998170754Sdelphijstatic void
999170754Sdelphijset_mtime_to_now (struct stat *st)
1000170754Sdelphij{
1001170754Sdelphij#ifdef ST_MTIM_NSEC
1002170754Sdelphij
1003170754Sdelphij# if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME
1004170754Sdelphij  if (clock_gettime (CLOCK_REALTIME, &st->st_mtim) == 0)
1005170754Sdelphij    return;
1006170754Sdelphij# endif
1007170754Sdelphij
1008170754Sdelphij# if HAVE_GETTIMEOFDAY
1009170754Sdelphij  {
1010170754Sdelphij    struct timeval timeval;
1011170754Sdelphij    if (gettimeofday (&timeval, 0) == 0)
1012170754Sdelphij      {
1013170754Sdelphij	st->st_mtime = timeval.tv_sec;
1014170754Sdelphij	st->st_mtim.ST_MTIM_NSEC = timeval.tv_usec * 1000;
1015170754Sdelphij	return;
1016170754Sdelphij      }
1017170754Sdelphij  }
1018170754Sdelphij# endif
1019170754Sdelphij
1020170754Sdelphij#endif /* ST_MTIM_NSEC */
1021170754Sdelphij
1022170754Sdelphij  time (&st->st_mtime);
1023170754Sdelphij}
1024170754Sdelphij
1025170754Sdelphij/* Compare two files (or dirs) with parent comparison PARENT
1026170754Sdelphij   and names NAME0 and NAME1.
1027170754Sdelphij   (If PARENT is 0, then the first name is just NAME0, etc.)
1028170754Sdelphij   This is self-contained; it opens the files and closes them.
1029170754Sdelphij
1030170754Sdelphij   Value is EXIT_SUCCESS if files are the same, EXIT_FAILURE if
1031170754Sdelphij   different, EXIT_TROUBLE if there is a problem opening them.  */
1032170754Sdelphij
1033170754Sdelphijstatic int
1034170754Sdelphijcompare_files (struct comparison const *parent,
1035170754Sdelphij	       char const *name0,
1036170754Sdelphij	       char const *name1)
1037170754Sdelphij{
1038170754Sdelphij  struct comparison cmp;
1039170754Sdelphij#define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
1040170754Sdelphij  register int f;
1041170754Sdelphij  int status = EXIT_SUCCESS;
1042170754Sdelphij  bool same_files;
1043170754Sdelphij  char *free0, *free1;
1044170754Sdelphij
1045170754Sdelphij  /* If this is directory comparison, perhaps we have a file
1046170754Sdelphij     that exists only in one of the directories.
1047170754Sdelphij     If so, just print a message to that effect.  */
1048170754Sdelphij
1049170754Sdelphij  if (! ((name0 && name1)
1050170754Sdelphij	 || (unidirectional_new_file && name1)
1051170754Sdelphij	 || new_file))
1052170754Sdelphij    {
1053170754Sdelphij      char const *name = name0 == 0 ? name1 : name0;
1054170754Sdelphij      char const *dir = parent->file[name0 == 0].name;
1055170754Sdelphij
1056170754Sdelphij      /* See POSIX 1003.1-2001 for this format.  */
1057170754Sdelphij      message ("Only in %s: %s\n", dir, name);
1058170754Sdelphij
1059170754Sdelphij      /* Return EXIT_FAILURE so that diff_dirs will return
1060170754Sdelphij	 EXIT_FAILURE ("some files differ").  */
1061170754Sdelphij      return EXIT_FAILURE;
1062170754Sdelphij    }
1063170754Sdelphij
1064170754Sdelphij  memset (cmp.file, 0, sizeof cmp.file);
1065170754Sdelphij  cmp.parent = parent;
1066170754Sdelphij
1067170754Sdelphij  /* cmp.file[f].desc markers */
1068170754Sdelphij#define NONEXISTENT (-1) /* nonexistent file */
1069170754Sdelphij#define UNOPENED (-2) /* unopened file (e.g. directory) */
1070170754Sdelphij#define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded errno value */
1071170754Sdelphij
1072170754Sdelphij#define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of ERRNO_ENCODE */
1073170754Sdelphij
1074170754Sdelphij  cmp.file[0].desc = name0 == 0 ? NONEXISTENT : UNOPENED;
1075170754Sdelphij  cmp.file[1].desc = name1 == 0 ? NONEXISTENT : UNOPENED;
1076170754Sdelphij
1077170754Sdelphij  /* Now record the full name of each file, including nonexistent ones.  */
1078170754Sdelphij
1079170754Sdelphij  if (name0 == 0)
1080170754Sdelphij    name0 = name1;
1081170754Sdelphij  if (name1 == 0)
1082170754Sdelphij    name1 = name0;
1083170754Sdelphij
1084170754Sdelphij  if (!parent)
1085170754Sdelphij    {
1086170754Sdelphij      free0 = 0;
1087170754Sdelphij      free1 = 0;
1088170754Sdelphij      cmp.file[0].name = name0;
1089170754Sdelphij      cmp.file[1].name = name1;
1090170754Sdelphij    }
1091170754Sdelphij  else
1092170754Sdelphij    {
1093170754Sdelphij      cmp.file[0].name = free0
1094170754Sdelphij	= dir_file_pathname (parent->file[0].name, name0);
1095170754Sdelphij      cmp.file[1].name = free1
1096170754Sdelphij	= dir_file_pathname (parent->file[1].name, name1);
1097170754Sdelphij    }
1098170754Sdelphij
1099170754Sdelphij  /* Stat the files.  */
1100170754Sdelphij
1101170754Sdelphij  for (f = 0; f < 2; f++)
1102170754Sdelphij    {
1103170754Sdelphij      if (cmp.file[f].desc != NONEXISTENT)
1104170754Sdelphij	{
1105170754Sdelphij	  if (f && file_name_cmp (cmp.file[f].name, cmp.file[0].name) == 0)
1106170754Sdelphij	    {
1107170754Sdelphij	      cmp.file[f].desc = cmp.file[0].desc;
1108170754Sdelphij	      cmp.file[f].stat = cmp.file[0].stat;
1109170754Sdelphij	    }
1110170754Sdelphij	  else if (strcmp (cmp.file[f].name, "-") == 0)
1111170754Sdelphij	    {
1112170754Sdelphij	      cmp.file[f].desc = STDIN_FILENO;
1113170754Sdelphij	      if (fstat (STDIN_FILENO, &cmp.file[f].stat) != 0)
1114170754Sdelphij		cmp.file[f].desc = ERRNO_ENCODE (errno);
1115170754Sdelphij	      else
1116170754Sdelphij		{
1117170754Sdelphij		  if (S_ISREG (cmp.file[f].stat.st_mode))
1118170754Sdelphij		    {
1119170754Sdelphij		      off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
1120170754Sdelphij		      if (pos < 0)
1121170754Sdelphij			cmp.file[f].desc = ERRNO_ENCODE (errno);
1122170754Sdelphij		      else
1123170754Sdelphij			cmp.file[f].stat.st_size =
1124170754Sdelphij			  MAX (0, cmp.file[f].stat.st_size - pos);
1125170754Sdelphij		    }
1126170754Sdelphij
1127170754Sdelphij		  /* POSIX 1003.1-2001 requires current time for
1128170754Sdelphij		     stdin.  */
1129170754Sdelphij		  set_mtime_to_now (&cmp.file[f].stat);
1130170754Sdelphij		}
1131170754Sdelphij	    }
1132170754Sdelphij	  else if (stat (cmp.file[f].name, &cmp.file[f].stat) != 0)
1133170754Sdelphij	    cmp.file[f].desc = ERRNO_ENCODE (errno);
1134170754Sdelphij	}
1135170754Sdelphij    }
1136170754Sdelphij
1137170754Sdelphij  /* Mark files as nonexistent as needed for -N and -P, if they are
1138170754Sdelphij     inaccessible empty regular files (the kind of files that 'patch'
1139170754Sdelphij     creates to indicate nonexistent backups), or if they are
1140170754Sdelphij     top-level files that do not exist but their counterparts do
1141170754Sdelphij     exist.  */
1142170754Sdelphij  for (f = 0; f < 2; f++)
1143170754Sdelphij    if ((new_file || (f == 0 && unidirectional_new_file))
1144170754Sdelphij	&& (cmp.file[f].desc == UNOPENED
1145170754Sdelphij	    ? (S_ISREG (cmp.file[f].stat.st_mode)
1146170754Sdelphij	       && ! (cmp.file[f].stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))
1147170754Sdelphij	       && cmp.file[f].stat.st_size == 0)
1148170754Sdelphij	    : (cmp.file[f].desc == ERRNO_ENCODE (ENOENT)
1149170754Sdelphij	       && ! parent
1150170754Sdelphij	       && cmp.file[1 - f].desc == UNOPENED)))
1151170754Sdelphij      cmp.file[f].desc = NONEXISTENT;
1152170754Sdelphij
1153170754Sdelphij  for (f = 0; f < 2; f++)
1154170754Sdelphij    if (cmp.file[f].desc == NONEXISTENT)
1155170754Sdelphij      {
1156170754Sdelphij	memset (&cmp.file[f].stat, 0, sizeof cmp.file[f].stat);
1157170754Sdelphij	cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
1158170754Sdelphij      }
1159170754Sdelphij
1160170754Sdelphij  for (f = 0; f < 2; f++)
1161170754Sdelphij    {
1162170754Sdelphij      int e = ERRNO_DECODE (cmp.file[f].desc);
1163170754Sdelphij      if (0 <= e)
1164170754Sdelphij	{
1165170754Sdelphij	  errno = e;
1166170754Sdelphij	  perror_with_name (cmp.file[f].name);
1167170754Sdelphij	  status = EXIT_TROUBLE;
1168170754Sdelphij	}
1169170754Sdelphij    }
1170170754Sdelphij
1171170754Sdelphij  if (status == EXIT_SUCCESS && ! parent && DIR_P (0) != DIR_P (1))
1172170754Sdelphij    {
1173170754Sdelphij      /* If one is a directory, and it was specified in the command line,
1174170754Sdelphij	 use the file in that dir with the other file's basename.  */
1175170754Sdelphij
1176170754Sdelphij      int fnm_arg = DIR_P (0);
1177170754Sdelphij      int dir_arg = 1 - fnm_arg;
1178170754Sdelphij      char const *fnm = cmp.file[fnm_arg].name;
1179170754Sdelphij      char const *dir = cmp.file[dir_arg].name;
1180170754Sdelphij      char const *filename = cmp.file[dir_arg].name = free0
1181170754Sdelphij	= dir_file_pathname (dir, base_name (fnm));
1182170754Sdelphij
1183170754Sdelphij      if (strcmp (fnm, "-") == 0)
1184170754Sdelphij	fatal ("cannot compare `-' to a directory");
1185170754Sdelphij
1186170754Sdelphij      if (stat (filename, &cmp.file[dir_arg].stat) != 0)
1187170754Sdelphij	{
1188170754Sdelphij	  perror_with_name (filename);
1189170754Sdelphij	  status = EXIT_TROUBLE;
1190170754Sdelphij	}
1191170754Sdelphij    }
1192170754Sdelphij
1193170754Sdelphij  if (status != EXIT_SUCCESS)
1194170754Sdelphij    {
1195170754Sdelphij      /* One of the files should exist but does not.  */
1196170754Sdelphij    }
1197170754Sdelphij  else if (cmp.file[0].desc == NONEXISTENT
1198170754Sdelphij	   && cmp.file[1].desc == NONEXISTENT)
1199170754Sdelphij    {
1200170754Sdelphij      /* Neither file "exists", so there's nothing to compare.  */
1201170754Sdelphij    }
1202170754Sdelphij  else if ((same_files
1203170754Sdelphij	    = (cmp.file[0].desc != NONEXISTENT
1204170754Sdelphij	       && cmp.file[1].desc != NONEXISTENT
1205170754Sdelphij	       && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
1206170754Sdelphij	       && same_file_attributes (&cmp.file[0].stat,
1207170754Sdelphij					&cmp.file[1].stat)))
1208170754Sdelphij	   && no_diff_means_no_output)
1209170754Sdelphij    {
1210170754Sdelphij      /* The two named files are actually the same physical file.
1211170754Sdelphij	 We know they are identical without actually reading them.  */
1212170754Sdelphij    }
1213170754Sdelphij  else if (DIR_P (0) & DIR_P (1))
1214170754Sdelphij    {
1215170754Sdelphij      if (output_style == OUTPUT_IFDEF)
1216170754Sdelphij	fatal ("-D option not supported with directories");
1217170754Sdelphij
1218170754Sdelphij      /* If both are directories, compare the files in them.  */
1219170754Sdelphij
1220170754Sdelphij      if (parent && !recursive)
1221170754Sdelphij	{
1222170754Sdelphij	  /* But don't compare dir contents one level down
1223170754Sdelphij	     unless -r was specified.
1224170754Sdelphij	     See POSIX 1003.1-2001 for this format.  */
1225170754Sdelphij	  message ("Common subdirectories: %s and %s\n",
1226170754Sdelphij		   cmp.file[0].name, cmp.file[1].name);
1227170754Sdelphij	}
1228170754Sdelphij      else
1229170754Sdelphij	status = diff_dirs (&cmp, compare_files);
1230170754Sdelphij    }
1231170754Sdelphij  else if ((DIR_P (0) | DIR_P (1))
1232170754Sdelphij	   || (parent
1233170754Sdelphij	       && (! S_ISREG (cmp.file[0].stat.st_mode)
1234170754Sdelphij		   || ! S_ISREG (cmp.file[1].stat.st_mode))))
1235170754Sdelphij    {
1236170754Sdelphij      if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT)
1237170754Sdelphij	{
1238170754Sdelphij	  /* We have a subdirectory that exists only in one directory.  */
1239170754Sdelphij
1240170754Sdelphij	  if ((DIR_P (0) | DIR_P (1))
1241170754Sdelphij	      && recursive
1242170754Sdelphij	      && (new_file
1243170754Sdelphij		  || (unidirectional_new_file
1244170754Sdelphij		      && cmp.file[0].desc == NONEXISTENT)))
1245170754Sdelphij	    status = diff_dirs (&cmp, compare_files);
1246170754Sdelphij	  else
1247170754Sdelphij	    {
1248170754Sdelphij	      char const *dir
1249170754Sdelphij		= parent->file[cmp.file[0].desc == NONEXISTENT].name;
1250170754Sdelphij
1251170754Sdelphij	      /* See POSIX 1003.1-2001 for this format.  */
1252170754Sdelphij	      message ("Only in %s: %s\n", dir, name0);
1253170754Sdelphij
1254170754Sdelphij	      status = EXIT_FAILURE;
1255170754Sdelphij	    }
1256170754Sdelphij	}
1257170754Sdelphij      else
1258170754Sdelphij	{
1259170754Sdelphij	  /* We have two files that are not to be compared.  */
1260170754Sdelphij
1261170754Sdelphij	  /* See POSIX 1003.1-2001 for this format.  */
1262170754Sdelphij	  message5 ("File %s is a %s while file %s is a %s\n",
1263170754Sdelphij		    file_label[0] ? file_label[0] : cmp.file[0].name,
1264170754Sdelphij		    file_type (&cmp.file[0].stat),
1265170754Sdelphij		    file_label[1] ? file_label[1] : cmp.file[1].name,
1266170754Sdelphij		    file_type (&cmp.file[1].stat));
1267170754Sdelphij
1268170754Sdelphij	  /* This is a difference.  */
1269170754Sdelphij	  status = EXIT_FAILURE;
1270170754Sdelphij	}
1271170754Sdelphij    }
1272170754Sdelphij  else if (files_can_be_treated_as_binary
1273170754Sdelphij	   && S_ISREG (cmp.file[0].stat.st_mode)
1274170754Sdelphij	   && S_ISREG (cmp.file[1].stat.st_mode)
1275170754Sdelphij	   && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size)
1276170754Sdelphij    {
1277170754Sdelphij      message ("Files %s and %s differ\n",
1278170754Sdelphij	       file_label[0] ? file_label[0] : cmp.file[0].name,
1279170754Sdelphij	       file_label[1] ? file_label[1] : cmp.file[1].name);
1280170754Sdelphij      status = EXIT_FAILURE;
1281170754Sdelphij    }
1282170754Sdelphij  else
1283170754Sdelphij    {
1284170754Sdelphij      /* Both exist and neither is a directory.  */
1285170754Sdelphij
1286170754Sdelphij      /* Open the files and record their descriptors.  */
1287170754Sdelphij
1288170754Sdelphij      if (cmp.file[0].desc == UNOPENED)
1289170754Sdelphij	if ((cmp.file[0].desc = open (cmp.file[0].name, O_RDONLY, 0)) < 0)
1290170754Sdelphij	  {
1291170754Sdelphij	    perror_with_name (cmp.file[0].name);
1292170754Sdelphij	    status = EXIT_TROUBLE;
1293170754Sdelphij	  }
1294170754Sdelphij      if (cmp.file[1].desc == UNOPENED)
1295170754Sdelphij	{
1296170754Sdelphij	  if (same_files)
1297170754Sdelphij	    cmp.file[1].desc = cmp.file[0].desc;
1298170754Sdelphij	  else if ((cmp.file[1].desc = open (cmp.file[1].name, O_RDONLY, 0))
1299170754Sdelphij		   < 0)
1300170754Sdelphij	    {
1301170754Sdelphij	      perror_with_name (cmp.file[1].name);
1302170754Sdelphij	      status = EXIT_TROUBLE;
1303170754Sdelphij	    }
1304170754Sdelphij	}
1305170754Sdelphij
1306170754Sdelphij#if HAVE_SETMODE_DOS
1307170754Sdelphij      if (binary)
1308170754Sdelphij	for (f = 0; f < 2; f++)
1309170754Sdelphij	  if (0 <= cmp.file[f].desc)
1310170754Sdelphij	    set_binary_mode (cmp.file[f].desc, true);
1311170754Sdelphij#endif
1312170754Sdelphij
1313170754Sdelphij      /* Compare the files, if no error was found.  */
1314170754Sdelphij
1315170754Sdelphij      if (status == EXIT_SUCCESS)
1316170754Sdelphij	status = diff_2_files (&cmp);
1317170754Sdelphij
1318170754Sdelphij      /* Close the file descriptors.  */
1319170754Sdelphij
1320170754Sdelphij      if (0 <= cmp.file[0].desc && close (cmp.file[0].desc) != 0)
1321170754Sdelphij	{
1322170754Sdelphij	  perror_with_name (cmp.file[0].name);
1323170754Sdelphij	  status = EXIT_TROUBLE;
1324170754Sdelphij	}
1325170754Sdelphij      if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
1326170754Sdelphij	  && close (cmp.file[1].desc) != 0)
1327170754Sdelphij	{
1328170754Sdelphij	  perror_with_name (cmp.file[1].name);
1329170754Sdelphij	  status = EXIT_TROUBLE;
1330170754Sdelphij	}
1331170754Sdelphij    }
1332170754Sdelphij
1333170754Sdelphij  /* Now the comparison has been done, if no error prevented it,
1334170754Sdelphij     and STATUS is the value this function will return.  */
1335170754Sdelphij
1336170754Sdelphij  if (status == EXIT_SUCCESS)
1337170754Sdelphij    {
1338170754Sdelphij      if (report_identical_files && !DIR_P (0))
1339170754Sdelphij	message ("Files %s and %s are identical\n",
1340170754Sdelphij		 file_label[0] ? file_label[0] : cmp.file[0].name,
1341170754Sdelphij		 file_label[1] ? file_label[1] : cmp.file[1].name);
1342170754Sdelphij    }
1343170754Sdelphij  else
1344170754Sdelphij    {
1345170754Sdelphij      /* Flush stdout so that the user sees differences immediately.
1346170754Sdelphij	 This can hurt performance, unfortunately.  */
1347170754Sdelphij      if (fflush (stdout) != 0)
1348170754Sdelphij	pfatal_with_name (_("standard output"));
1349170754Sdelphij    }
1350170754Sdelphij
1351170754Sdelphij  if (free0)
1352170754Sdelphij    free (free0);
1353170754Sdelphij  if (free1)
1354170754Sdelphij    free (free1);
1355170754Sdelphij
1356170754Sdelphij  return status;
1357170754Sdelphij}
1358