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