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