1*f8c23a2bSchristos /* $NetBSD: diff.c,v 1.2 2016/01/13 03:39:28 christos Exp $ */
275f6d617Schristos
375f6d617Schristos /* diff - compare files line by line
475f6d617Schristos
575f6d617Schristos Copyright (C) 1988, 1989, 1992, 1993, 1994, 1996, 1998, 2001, 2002
675f6d617Schristos Free Software Foundation, Inc.
775f6d617Schristos
875f6d617Schristos This file is part of GNU DIFF.
975f6d617Schristos
1075f6d617Schristos GNU DIFF is free software; you can redistribute it and/or modify
1175f6d617Schristos it under the terms of the GNU General Public License as published by
1275f6d617Schristos the Free Software Foundation; either version 2, or (at your option)
1375f6d617Schristos any later version.
1475f6d617Schristos
1575f6d617Schristos GNU DIFF is distributed in the hope that it will be useful,
1675f6d617Schristos but WITHOUT ANY WARRANTY; without even the implied warranty of
1775f6d617Schristos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1875f6d617Schristos See the GNU General Public License for more details.
1975f6d617Schristos
2075f6d617Schristos You should have received a copy of the GNU General Public License
2175f6d617Schristos along with GNU DIFF; see the file COPYING.
2275f6d617Schristos If not, write to the Free Software Foundation,
2375f6d617Schristos 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
2475f6d617Schristos
2575f6d617Schristos #define GDIFF_MAIN
2675f6d617Schristos #include "diff.h"
2775f6d617Schristos #include <c-stack.h>
2875f6d617Schristos #include <dirname.h>
2975f6d617Schristos #include <error.h>
3075f6d617Schristos #include <exclude.h>
3175f6d617Schristos #include <exitfail.h>
3275f6d617Schristos #include <fnmatch.h>
3375f6d617Schristos #include <freesoft.h>
3475f6d617Schristos #include <getopt.h>
3575f6d617Schristos #include <hard-locale.h>
3675f6d617Schristos #include <prepargs.h>
3775f6d617Schristos #include <quotesys.h>
3875f6d617Schristos #include <regex.h>
3975f6d617Schristos #include <setmode.h>
4075f6d617Schristos #include <xalloc.h>
41*f8c23a2bSchristos #include <posixver.h>
4275f6d617Schristos
4375f6d617Schristos static char const authorship_msgid[] =
4475f6d617Schristos N_("Written by Paul Eggert, Mike Haertel, David Hayes,\n\
4575f6d617Schristos Richard Stallman, and Len Tower.");
4675f6d617Schristos
4775f6d617Schristos static char const copyright_string[] =
4875f6d617Schristos "Copyright (C) 2002 Free Software Foundation, Inc.";
4975f6d617Schristos
5075f6d617Schristos #ifndef GUTTER_WIDTH_MINIMUM
5175f6d617Schristos # define GUTTER_WIDTH_MINIMUM 3
5275f6d617Schristos #endif
5375f6d617Schristos
5475f6d617Schristos struct regexp_list
5575f6d617Schristos {
5675f6d617Schristos char *regexps; /* chars representing disjunction of the regexps */
5775f6d617Schristos size_t len; /* chars used in `regexps' */
5875f6d617Schristos size_t size; /* size malloc'ed for `regexps'; 0 if not malloc'ed */
5975f6d617Schristos bool multiple_regexps;/* Does `regexps' represent a disjunction? */
6075f6d617Schristos struct re_pattern_buffer *buf;
6175f6d617Schristos };
6275f6d617Schristos
6375f6d617Schristos static int compare_files (struct comparison const *, char const *, char const *);
6475f6d617Schristos static void add_regexp (struct regexp_list *, char const *);
6575f6d617Schristos static void summarize_regexp_list (struct regexp_list *);
6675f6d617Schristos static void specify_style (enum output_style);
6775f6d617Schristos static void specify_value (char const **, char const *, char const *);
6875f6d617Schristos static void try_help (char const *, char const *) __attribute__((noreturn));
6975f6d617Schristos static void check_stdout (void);
7075f6d617Schristos static void usage (void);
7175f6d617Schristos
7275f6d617Schristos /* If comparing directories, compare their common subdirectories
7375f6d617Schristos recursively. */
7475f6d617Schristos static bool recursive;
7575f6d617Schristos
7675f6d617Schristos /* In context diffs, show previous lines that match these regexps. */
7775f6d617Schristos static struct regexp_list function_regexp_list;
7875f6d617Schristos
7975f6d617Schristos /* Ignore changes affecting only lines that match these regexps. */
8075f6d617Schristos static struct regexp_list ignore_regexp_list;
8175f6d617Schristos
8275f6d617Schristos #if HAVE_SETMODE_DOS
8375f6d617Schristos /* Use binary I/O when reading and writing data (--binary).
8475f6d617Schristos On POSIX hosts, this has no effect. */
8575f6d617Schristos static bool binary;
8675f6d617Schristos #endif
8775f6d617Schristos
8875f6d617Schristos /* When comparing directories, if a file appears only in one
8975f6d617Schristos directory, treat it as present but empty in the other (-N).
9075f6d617Schristos Then `patch' would create the file with appropriate contents. */
9175f6d617Schristos static bool new_file;
9275f6d617Schristos
9375f6d617Schristos /* When comparing directories, if a file appears only in the second
9475f6d617Schristos directory of the two, treat it as present but empty in the other
9575f6d617Schristos (--unidirectional-new-file).
9675f6d617Schristos Then `patch' would create the file with appropriate contents. */
9775f6d617Schristos static bool unidirectional_new_file;
9875f6d617Schristos
9975f6d617Schristos /* Report files compared that are the same (-s).
10075f6d617Schristos Normally nothing is output when that happens. */
10175f6d617Schristos static bool report_identical_files;
10275f6d617Schristos
10375f6d617Schristos
10475f6d617Schristos /* Return a string containing the command options with which diff was invoked.
10575f6d617Schristos Spaces appear between what were separate ARGV-elements.
10675f6d617Schristos There is a space at the beginning but none at the end.
10775f6d617Schristos If there were no options, the result is an empty string.
10875f6d617Schristos
10975f6d617Schristos Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
11075f6d617Schristos the length of that vector. */
11175f6d617Schristos
11275f6d617Schristos static char *
option_list(char ** optionvec,int count)11375f6d617Schristos option_list (char **optionvec, int count)
11475f6d617Schristos {
11575f6d617Schristos int i;
11675f6d617Schristos size_t size = 1;
11775f6d617Schristos char *result;
11875f6d617Schristos char *p;
11975f6d617Schristos
12075f6d617Schristos for (i = 0; i < count; i++)
12175f6d617Schristos size += 1 + quote_system_arg ((char *) 0, optionvec[i]);
12275f6d617Schristos
12375f6d617Schristos p = result = xmalloc (size);
12475f6d617Schristos
12575f6d617Schristos for (i = 0; i < count; i++)
12675f6d617Schristos {
12775f6d617Schristos *p++ = ' ';
12875f6d617Schristos p += quote_system_arg (p, optionvec[i]);
12975f6d617Schristos }
13075f6d617Schristos
13175f6d617Schristos *p = 0;
13275f6d617Schristos return result;
13375f6d617Schristos }
13475f6d617Schristos
13575f6d617Schristos
13675f6d617Schristos /* Return an option value suitable for add_exclude. */
13775f6d617Schristos
13875f6d617Schristos static int
exclude_options(void)13975f6d617Schristos exclude_options (void)
14075f6d617Schristos {
14175f6d617Schristos return EXCLUDE_WILDCARDS | (ignore_file_name_case ? FNM_CASEFOLD : 0);
14275f6d617Schristos }
14375f6d617Schristos
14475f6d617Schristos static char const shortopts[] =
14575f6d617Schristos "0123456789abBcC:dD:eEfF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y";
14675f6d617Schristos
14775f6d617Schristos /* Values for long options that do not have single-letter equivalents. */
14875f6d617Schristos enum
14975f6d617Schristos {
15075f6d617Schristos BINARY_OPTION = CHAR_MAX + 1,
15175f6d617Schristos FROM_FILE_OPTION,
15275f6d617Schristos HELP_OPTION,
15375f6d617Schristos HORIZON_LINES_OPTION,
15475f6d617Schristos IGNORE_FILE_NAME_CASE_OPTION,
15575f6d617Schristos INHIBIT_HUNK_MERGE_OPTION,
15675f6d617Schristos LEFT_COLUMN_OPTION,
15775f6d617Schristos LINE_FORMAT_OPTION,
15875f6d617Schristos NO_IGNORE_FILE_NAME_CASE_OPTION,
15975f6d617Schristos NORMAL_OPTION,
16075f6d617Schristos SDIFF_MERGE_ASSIST_OPTION,
16175f6d617Schristos STRIP_TRAILING_CR_OPTION,
16275f6d617Schristos SUPPRESS_COMMON_LINES_OPTION,
16375f6d617Schristos TO_FILE_OPTION,
16475f6d617Schristos
16575f6d617Schristos /* These options must be in sequence. */
16675f6d617Schristos UNCHANGED_LINE_FORMAT_OPTION,
16775f6d617Schristos OLD_LINE_FORMAT_OPTION,
16875f6d617Schristos NEW_LINE_FORMAT_OPTION,
16975f6d617Schristos
17075f6d617Schristos /* These options must be in sequence. */
17175f6d617Schristos UNCHANGED_GROUP_FORMAT_OPTION,
17275f6d617Schristos OLD_GROUP_FORMAT_OPTION,
17375f6d617Schristos NEW_GROUP_FORMAT_OPTION,
17475f6d617Schristos CHANGED_GROUP_FORMAT_OPTION
17575f6d617Schristos };
17675f6d617Schristos
17775f6d617Schristos static char const group_format_option[][sizeof "--unchanged-group-format"] =
17875f6d617Schristos {
17975f6d617Schristos "--unchanged-group-format",
18075f6d617Schristos "--old-group-format",
18175f6d617Schristos "--new-group-format",
18275f6d617Schristos "--changed-group-format"
18375f6d617Schristos };
18475f6d617Schristos
18575f6d617Schristos static char const line_format_option[][sizeof "--unchanged-line-format"] =
18675f6d617Schristos {
18775f6d617Schristos "--unchanged-line-format",
18875f6d617Schristos "--old-line-format",
18975f6d617Schristos "--new-line-format"
19075f6d617Schristos };
19175f6d617Schristos
19275f6d617Schristos static struct option const longopts[] =
19375f6d617Schristos {
19475f6d617Schristos {"binary", 0, 0, BINARY_OPTION},
19575f6d617Schristos {"brief", 0, 0, 'q'},
19675f6d617Schristos {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
19775f6d617Schristos {"context", 2, 0, 'C'},
19875f6d617Schristos {"ed", 0, 0, 'e'},
19975f6d617Schristos {"exclude", 1, 0, 'x'},
20075f6d617Schristos {"exclude-from", 1, 0, 'X'},
20175f6d617Schristos {"expand-tabs", 0, 0, 't'},
20275f6d617Schristos {"forward-ed", 0, 0, 'f'},
20375f6d617Schristos {"from-file", 1, 0, FROM_FILE_OPTION},
20475f6d617Schristos {"help", 0, 0, HELP_OPTION},
20575f6d617Schristos {"horizon-lines", 1, 0, HORIZON_LINES_OPTION},
20675f6d617Schristos {"ifdef", 1, 0, 'D'},
20775f6d617Schristos {"ignore-all-space", 0, 0, 'w'},
20875f6d617Schristos {"ignore-blank-lines", 0, 0, 'B'},
20975f6d617Schristos {"ignore-case", 0, 0, 'i'},
21075f6d617Schristos {"ignore-file-name-case", 0, 0, IGNORE_FILE_NAME_CASE_OPTION},
21175f6d617Schristos {"ignore-matching-lines", 1, 0, 'I'},
21275f6d617Schristos {"ignore-space-change", 0, 0, 'b'},
21375f6d617Schristos {"ignore-tab-expansion", 0, 0, 'E'},
21475f6d617Schristos {"inhibit-hunk-merge", 0, 0, INHIBIT_HUNK_MERGE_OPTION},
21575f6d617Schristos {"initial-tab", 0, 0, 'T'},
21675f6d617Schristos {"label", 1, 0, 'L'},
21775f6d617Schristos {"left-column", 0, 0, LEFT_COLUMN_OPTION},
21875f6d617Schristos {"line-format", 1, 0, LINE_FORMAT_OPTION},
21975f6d617Schristos {"minimal", 0, 0, 'd'},
22075f6d617Schristos {"new-file", 0, 0, 'N'},
22175f6d617Schristos {"new-group-format", 1, 0, NEW_GROUP_FORMAT_OPTION},
22275f6d617Schristos {"new-line-format", 1, 0, NEW_LINE_FORMAT_OPTION},
22375f6d617Schristos {"no-ignore-file-name-case", 0, 0, NO_IGNORE_FILE_NAME_CASE_OPTION},
22475f6d617Schristos {"normal", 0, 0, NORMAL_OPTION},
22575f6d617Schristos {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
22675f6d617Schristos {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
22775f6d617Schristos {"paginate", 0, 0, 'l'},
22875f6d617Schristos {"rcs", 0, 0, 'n'},
22975f6d617Schristos {"recursive", 0, 0, 'r'},
23075f6d617Schristos {"report-identical-files", 0, 0, 's'},
23175f6d617Schristos {"sdiff-merge-assist", 0, 0, SDIFF_MERGE_ASSIST_OPTION},
23275f6d617Schristos {"show-c-function", 0, 0, 'p'},
23375f6d617Schristos {"show-function-line", 1, 0, 'F'},
23475f6d617Schristos {"side-by-side", 0, 0, 'y'},
23575f6d617Schristos {"speed-large-files", 0, 0, 'H'},
23675f6d617Schristos {"starting-file", 1, 0, 'S'},
23775f6d617Schristos {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
23875f6d617Schristos {"suppress-common-lines", 0, 0, SUPPRESS_COMMON_LINES_OPTION},
23975f6d617Schristos {"text", 0, 0, 'a'},
24075f6d617Schristos {"to-file", 1, 0, TO_FILE_OPTION},
24175f6d617Schristos {"unchanged-group-format", 1, 0, UNCHANGED_GROUP_FORMAT_OPTION},
24275f6d617Schristos {"unchanged-line-format", 1, 0, UNCHANGED_LINE_FORMAT_OPTION},
24375f6d617Schristos {"unidirectional-new-file", 0, 0, 'P'},
24475f6d617Schristos {"unified", 2, 0, 'U'},
24575f6d617Schristos {"version", 0, 0, 'v'},
24675f6d617Schristos {"width", 1, 0, 'W'},
24775f6d617Schristos {0, 0, 0, 0}
24875f6d617Schristos };
24975f6d617Schristos
25075f6d617Schristos int
main(int argc,char ** argv)25175f6d617Schristos main (int argc, char **argv)
25275f6d617Schristos {
25375f6d617Schristos int exit_status = EXIT_SUCCESS;
25475f6d617Schristos int c;
25575f6d617Schristos int i;
25675f6d617Schristos int prev = -1;
25775f6d617Schristos lin ocontext = -1;
25875f6d617Schristos bool explicit_context = 0;
25975f6d617Schristos int width = 0;
26075f6d617Schristos bool show_c_function = 0;
26175f6d617Schristos char const *from_file = 0;
26275f6d617Schristos char const *to_file = 0;
26375f6d617Schristos uintmax_t numval;
26475f6d617Schristos char *numend;
26575f6d617Schristos
26675f6d617Schristos /* Do our initializations. */
26775f6d617Schristos exit_failure = 2;
26875f6d617Schristos initialize_main (&argc, &argv);
26975f6d617Schristos program_name = argv[0];
27075f6d617Schristos setlocale (LC_ALL, "");
27175f6d617Schristos bindtextdomain (PACKAGE, LOCALEDIR);
27275f6d617Schristos textdomain (PACKAGE);
27375f6d617Schristos c_stack_action (c_stack_die);
27475f6d617Schristos function_regexp_list.buf = &function_regexp;
27575f6d617Schristos ignore_regexp_list.buf = &ignore_regexp;
27675f6d617Schristos re_set_syntax (RE_SYNTAX_GREP | RE_NO_POSIX_BACKTRACKING);
27775f6d617Schristos excluded = new_exclude ();
27875f6d617Schristos
27975f6d617Schristos /* Decode the options. */
28075f6d617Schristos
28175f6d617Schristos while ((c = getopt_long (argc, argv, shortopts, longopts, 0)) != -1)
28275f6d617Schristos {
28375f6d617Schristos switch (c)
28475f6d617Schristos {
28575f6d617Schristos case 0:
28675f6d617Schristos break;
28775f6d617Schristos
28875f6d617Schristos case '0':
28975f6d617Schristos case '1':
29075f6d617Schristos case '2':
29175f6d617Schristos case '3':
29275f6d617Schristos case '4':
29375f6d617Schristos case '5':
29475f6d617Schristos case '6':
29575f6d617Schristos case '7':
29675f6d617Schristos case '8':
29775f6d617Schristos case '9':
29875f6d617Schristos if (! ISDIGIT (prev))
29975f6d617Schristos ocontext = c - '0';
30075f6d617Schristos else if (LIN_MAX / 10 < ocontext
30175f6d617Schristos || ((ocontext = 10 * ocontext + c - '0') < 0))
30275f6d617Schristos ocontext = LIN_MAX;
30375f6d617Schristos break;
30475f6d617Schristos
30575f6d617Schristos case 'a':
30675f6d617Schristos text = 1;
30775f6d617Schristos break;
30875f6d617Schristos
30975f6d617Schristos case 'b':
31075f6d617Schristos if (ignore_white_space < IGNORE_SPACE_CHANGE)
31175f6d617Schristos ignore_white_space = IGNORE_SPACE_CHANGE;
31275f6d617Schristos break;
31375f6d617Schristos
31475f6d617Schristos case 'B':
31575f6d617Schristos ignore_blank_lines = 1;
31675f6d617Schristos break;
31775f6d617Schristos
31875f6d617Schristos case 'C': /* +context[=lines] */
31975f6d617Schristos case 'U': /* +unified[=lines] */
32075f6d617Schristos {
32175f6d617Schristos if (optarg)
32275f6d617Schristos {
32375f6d617Schristos numval = strtoumax (optarg, &numend, 10);
32475f6d617Schristos if (*numend)
32575f6d617Schristos try_help ("invalid context length `%s'", optarg);
32675f6d617Schristos if (LIN_MAX < numval)
32775f6d617Schristos numval = LIN_MAX;
32875f6d617Schristos }
32975f6d617Schristos else
33075f6d617Schristos numval = 3;
33175f6d617Schristos
33275f6d617Schristos specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
33375f6d617Schristos if (context < numval)
33475f6d617Schristos context = numval;
33575f6d617Schristos explicit_context = 1;
33675f6d617Schristos }
33775f6d617Schristos break;
33875f6d617Schristos
33975f6d617Schristos case 'c':
34075f6d617Schristos specify_style (OUTPUT_CONTEXT);
34175f6d617Schristos if (context < 3)
34275f6d617Schristos context = 3;
34375f6d617Schristos break;
34475f6d617Schristos
34575f6d617Schristos case 'd':
34675f6d617Schristos minimal = 1;
34775f6d617Schristos break;
34875f6d617Schristos
34975f6d617Schristos case 'D':
35075f6d617Schristos specify_style (OUTPUT_IFDEF);
35175f6d617Schristos {
35275f6d617Schristos static char const C_ifdef_group_formats[] =
35375f6d617Schristos "%%=%c#ifndef %s\n%%<#endif /* ! %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
35475f6d617Schristos char *b = xmalloc (sizeof C_ifdef_group_formats
35575f6d617Schristos + 7 * strlen (optarg) - 14 /* 7*"%s" */
35675f6d617Schristos - 8 /* 5*"%%" + 3*"%c" */);
35775f6d617Schristos sprintf (b, C_ifdef_group_formats,
35875f6d617Schristos 0,
35975f6d617Schristos optarg, optarg, 0,
36075f6d617Schristos optarg, optarg, 0,
36175f6d617Schristos optarg, optarg, optarg);
36275f6d617Schristos for (i = 0; i < sizeof group_format / sizeof *group_format; i++)
36375f6d617Schristos {
36475f6d617Schristos specify_value (&group_format[i], b, "-D");
36575f6d617Schristos b += strlen (b) + 1;
36675f6d617Schristos }
36775f6d617Schristos }
36875f6d617Schristos break;
36975f6d617Schristos
37075f6d617Schristos case 'e':
37175f6d617Schristos specify_style (OUTPUT_ED);
37275f6d617Schristos break;
37375f6d617Schristos
37475f6d617Schristos case 'E':
37575f6d617Schristos if (ignore_white_space < IGNORE_TAB_EXPANSION)
37675f6d617Schristos ignore_white_space = IGNORE_TAB_EXPANSION;
37775f6d617Schristos break;
37875f6d617Schristos
37975f6d617Schristos case 'f':
38075f6d617Schristos specify_style (OUTPUT_FORWARD_ED);
38175f6d617Schristos break;
38275f6d617Schristos
38375f6d617Schristos case 'F':
38475f6d617Schristos add_regexp (&function_regexp_list, optarg);
38575f6d617Schristos break;
38675f6d617Schristos
38775f6d617Schristos case 'h':
38875f6d617Schristos /* Split the files into chunks for faster processing.
38975f6d617Schristos Usually does not change the result.
39075f6d617Schristos
39175f6d617Schristos This currently has no effect. */
39275f6d617Schristos break;
39375f6d617Schristos
39475f6d617Schristos case 'H':
39575f6d617Schristos speed_large_files = 1;
39675f6d617Schristos break;
39775f6d617Schristos
39875f6d617Schristos case 'i':
39975f6d617Schristos ignore_case = 1;
40075f6d617Schristos break;
40175f6d617Schristos
40275f6d617Schristos case 'I':
40375f6d617Schristos add_regexp (&ignore_regexp_list, optarg);
40475f6d617Schristos break;
40575f6d617Schristos
40675f6d617Schristos case 'l':
40775f6d617Schristos if (!pr_program[0])
40875f6d617Schristos try_help ("pagination not supported on this host", 0);
40975f6d617Schristos paginate = 1;
41075f6d617Schristos #ifdef SIGCHLD
41175f6d617Schristos /* Pagination requires forking and waiting, and
41275f6d617Schristos System V fork+wait does not work if SIGCHLD is ignored. */
41375f6d617Schristos signal (SIGCHLD, SIG_DFL);
41475f6d617Schristos #endif
41575f6d617Schristos break;
41675f6d617Schristos
41775f6d617Schristos case 'L':
41875f6d617Schristos if (!file_label[0])
41975f6d617Schristos file_label[0] = optarg;
42075f6d617Schristos else if (!file_label[1])
42175f6d617Schristos file_label[1] = optarg;
42275f6d617Schristos else
42375f6d617Schristos fatal ("too many file label options");
42475f6d617Schristos break;
42575f6d617Schristos
42675f6d617Schristos case 'n':
42775f6d617Schristos specify_style (OUTPUT_RCS);
42875f6d617Schristos break;
42975f6d617Schristos
43075f6d617Schristos case 'N':
43175f6d617Schristos new_file = 1;
43275f6d617Schristos break;
43375f6d617Schristos
43475f6d617Schristos case 'p':
43575f6d617Schristos show_c_function = 1;
43675f6d617Schristos add_regexp (&function_regexp_list, "^[[:alpha:]$_]");
43775f6d617Schristos break;
43875f6d617Schristos
43975f6d617Schristos case 'P':
44075f6d617Schristos unidirectional_new_file = 1;
44175f6d617Schristos break;
44275f6d617Schristos
44375f6d617Schristos case 'q':
44475f6d617Schristos brief = 1;
44575f6d617Schristos break;
44675f6d617Schristos
44775f6d617Schristos case 'r':
44875f6d617Schristos recursive = 1;
44975f6d617Schristos break;
45075f6d617Schristos
45175f6d617Schristos case 's':
45275f6d617Schristos report_identical_files = 1;
45375f6d617Schristos break;
45475f6d617Schristos
45575f6d617Schristos case 'S':
45675f6d617Schristos specify_value (&starting_file, optarg, "-S");
45775f6d617Schristos break;
45875f6d617Schristos
45975f6d617Schristos case 't':
46075f6d617Schristos expand_tabs = 1;
46175f6d617Schristos break;
46275f6d617Schristos
46375f6d617Schristos case 'T':
46475f6d617Schristos initial_tab = 1;
46575f6d617Schristos break;
46675f6d617Schristos
46775f6d617Schristos case 'u':
46875f6d617Schristos specify_style (OUTPUT_UNIFIED);
46975f6d617Schristos if (context < 3)
47075f6d617Schristos context = 3;
47175f6d617Schristos break;
47275f6d617Schristos
47375f6d617Schristos case 'v':
47475f6d617Schristos printf ("diff %s\n%s\n\n%s\n\n%s\n",
47575f6d617Schristos version_string, copyright_string,
47675f6d617Schristos _(free_software_msgid), _(authorship_msgid));
47775f6d617Schristos check_stdout ();
47875f6d617Schristos return EXIT_SUCCESS;
47975f6d617Schristos
48075f6d617Schristos case 'w':
48175f6d617Schristos ignore_white_space = IGNORE_ALL_SPACE;
48275f6d617Schristos break;
48375f6d617Schristos
48475f6d617Schristos case 'x':
48575f6d617Schristos add_exclude (excluded, optarg, exclude_options ());
48675f6d617Schristos break;
48775f6d617Schristos
48875f6d617Schristos case 'X':
48975f6d617Schristos if (add_exclude_file (add_exclude, excluded, optarg,
49075f6d617Schristos exclude_options (), '\n'))
49175f6d617Schristos pfatal_with_name (optarg);
49275f6d617Schristos break;
49375f6d617Schristos
49475f6d617Schristos case 'y':
49575f6d617Schristos specify_style (OUTPUT_SDIFF);
49675f6d617Schristos break;
49775f6d617Schristos
49875f6d617Schristos case 'W':
49975f6d617Schristos numval = strtoumax (optarg, &numend, 10);
50075f6d617Schristos if (! (0 < numval && numval <= INT_MAX) || *numend)
50175f6d617Schristos try_help ("invalid width `%s'", optarg);
50275f6d617Schristos if (width != numval)
50375f6d617Schristos {
50475f6d617Schristos if (width)
50575f6d617Schristos fatal ("conflicting width options");
50675f6d617Schristos width = numval;
50775f6d617Schristos }
50875f6d617Schristos break;
50975f6d617Schristos
51075f6d617Schristos case BINARY_OPTION:
51175f6d617Schristos #if HAVE_SETMODE_DOS
51275f6d617Schristos binary = 1;
51375f6d617Schristos set_binary_mode (STDOUT_FILENO, 1);
51475f6d617Schristos #endif
51575f6d617Schristos break;
51675f6d617Schristos
51775f6d617Schristos case FROM_FILE_OPTION:
51875f6d617Schristos specify_value (&from_file, optarg, "--from-file");
51975f6d617Schristos break;
52075f6d617Schristos
52175f6d617Schristos case HELP_OPTION:
52275f6d617Schristos usage ();
52375f6d617Schristos check_stdout ();
52475f6d617Schristos return EXIT_SUCCESS;
52575f6d617Schristos
52675f6d617Schristos case HORIZON_LINES_OPTION:
52775f6d617Schristos numval = strtoumax (optarg, &numend, 10);
52875f6d617Schristos if (*numend)
52975f6d617Schristos try_help ("invalid horizon length `%s'", optarg);
53075f6d617Schristos horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
53175f6d617Schristos break;
53275f6d617Schristos
53375f6d617Schristos case IGNORE_FILE_NAME_CASE_OPTION:
53475f6d617Schristos ignore_file_name_case = 1;
53575f6d617Schristos break;
53675f6d617Schristos
53775f6d617Schristos case INHIBIT_HUNK_MERGE_OPTION:
53875f6d617Schristos /* This option is obsolete, but accept it for backward
53975f6d617Schristos compatibility. */
54075f6d617Schristos break;
54175f6d617Schristos
54275f6d617Schristos case LEFT_COLUMN_OPTION:
54375f6d617Schristos left_column = 1;
54475f6d617Schristos break;
54575f6d617Schristos
54675f6d617Schristos case LINE_FORMAT_OPTION:
54775f6d617Schristos specify_style (OUTPUT_IFDEF);
54875f6d617Schristos for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
54975f6d617Schristos specify_value (&line_format[i], optarg, "--line-format");
55075f6d617Schristos break;
55175f6d617Schristos
55275f6d617Schristos case NO_IGNORE_FILE_NAME_CASE_OPTION:
55375f6d617Schristos ignore_file_name_case = 0;
55475f6d617Schristos break;
55575f6d617Schristos
55675f6d617Schristos case NORMAL_OPTION:
55775f6d617Schristos specify_style (OUTPUT_NORMAL);
55875f6d617Schristos break;
55975f6d617Schristos
56075f6d617Schristos case SDIFF_MERGE_ASSIST_OPTION:
56175f6d617Schristos specify_style (OUTPUT_SDIFF);
56275f6d617Schristos sdiff_merge_assist = 1;
56375f6d617Schristos break;
56475f6d617Schristos
56575f6d617Schristos case STRIP_TRAILING_CR_OPTION:
56675f6d617Schristos strip_trailing_cr = 1;
56775f6d617Schristos break;
56875f6d617Schristos
56975f6d617Schristos case SUPPRESS_COMMON_LINES_OPTION:
57075f6d617Schristos suppress_common_lines = 1;
57175f6d617Schristos break;
57275f6d617Schristos
57375f6d617Schristos case TO_FILE_OPTION:
57475f6d617Schristos specify_value (&to_file, optarg, "--to-file");
57575f6d617Schristos break;
57675f6d617Schristos
57775f6d617Schristos case UNCHANGED_LINE_FORMAT_OPTION:
57875f6d617Schristos case OLD_LINE_FORMAT_OPTION:
57975f6d617Schristos case NEW_LINE_FORMAT_OPTION:
58075f6d617Schristos specify_style (OUTPUT_IFDEF);
58175f6d617Schristos c -= UNCHANGED_LINE_FORMAT_OPTION;
58275f6d617Schristos specify_value (&line_format[c], optarg, line_format_option[c]);
58375f6d617Schristos break;
58475f6d617Schristos
58575f6d617Schristos case UNCHANGED_GROUP_FORMAT_OPTION:
58675f6d617Schristos case OLD_GROUP_FORMAT_OPTION:
58775f6d617Schristos case NEW_GROUP_FORMAT_OPTION:
58875f6d617Schristos case CHANGED_GROUP_FORMAT_OPTION:
58975f6d617Schristos specify_style (OUTPUT_IFDEF);
59075f6d617Schristos c -= UNCHANGED_GROUP_FORMAT_OPTION;
59175f6d617Schristos specify_value (&group_format[c], optarg, group_format_option[c]);
59275f6d617Schristos break;
59375f6d617Schristos
59475f6d617Schristos default:
59575f6d617Schristos try_help (0, 0);
59675f6d617Schristos }
59775f6d617Schristos prev = c;
59875f6d617Schristos }
59975f6d617Schristos
60075f6d617Schristos if (output_style == OUTPUT_UNSPECIFIED)
60175f6d617Schristos {
60275f6d617Schristos if (show_c_function)
60375f6d617Schristos {
60475f6d617Schristos specify_style (OUTPUT_CONTEXT);
60575f6d617Schristos if (ocontext < 0)
60675f6d617Schristos context = 3;
60775f6d617Schristos }
60875f6d617Schristos else
60975f6d617Schristos specify_style (OUTPUT_NORMAL);
61075f6d617Schristos }
61175f6d617Schristos
61275f6d617Schristos if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
61375f6d617Schristos time_format = "%Y-%m-%d %H:%M:%S.%N %z";
61475f6d617Schristos else
61575f6d617Schristos {
61675f6d617Schristos /* See POSIX 1003.1-2001 for this format. */
61775f6d617Schristos time_format = "%a %b %e %T %Y";
61875f6d617Schristos }
61975f6d617Schristos
62075f6d617Schristos if (0 <= ocontext)
62175f6d617Schristos {
62275f6d617Schristos bool modern_usage = 200112 <= posix2_version ();
62375f6d617Schristos
62475f6d617Schristos if ((output_style == OUTPUT_CONTEXT
62575f6d617Schristos || output_style == OUTPUT_UNIFIED)
62675f6d617Schristos && (context < ocontext
62775f6d617Schristos || (ocontext < context && ! explicit_context)))
62875f6d617Schristos {
62975f6d617Schristos if (modern_usage)
63075f6d617Schristos {
63175f6d617Schristos error (0, 0,
63275f6d617Schristos _("`-%ld' option is obsolete; use `-%c %ld'"),
63375f6d617Schristos (long) ocontext,
63475f6d617Schristos output_style == OUTPUT_CONTEXT ? 'C' : 'U',
63575f6d617Schristos (long) ocontext);
63675f6d617Schristos try_help (0, 0);
63775f6d617Schristos }
63875f6d617Schristos context = ocontext;
63975f6d617Schristos }
64075f6d617Schristos else
64175f6d617Schristos {
64275f6d617Schristos if (modern_usage)
64375f6d617Schristos {
64475f6d617Schristos error (0, 0, _("`-%ld' option is obsolete; omit it"),
64575f6d617Schristos (long) ocontext);
64675f6d617Schristos try_help (0, 0);
64775f6d617Schristos }
64875f6d617Schristos }
64975f6d617Schristos }
65075f6d617Schristos
65175f6d617Schristos {
65275f6d617Schristos /*
65375f6d617Schristos * We maximize first the half line width, and then the gutter width,
65475f6d617Schristos * according to the following constraints:
65575f6d617Schristos * 1. Two half lines plus a gutter must fit in a line.
65675f6d617Schristos * 2. If the half line width is nonzero:
65775f6d617Schristos * a. The gutter width is at least GUTTER_WIDTH_MINIMUM.
65875f6d617Schristos * b. If tabs are not expanded to spaces,
65975f6d617Schristos * a half line plus a gutter is an integral number of tabs,
66075f6d617Schristos * so that tabs in the right column line up.
66175f6d617Schristos */
66275f6d617Schristos unsigned int t = expand_tabs ? 1 : TAB_WIDTH;
66375f6d617Schristos int w = width ? width : 130;
66475f6d617Schristos int off = (w + t + GUTTER_WIDTH_MINIMUM) / (2 * t) * t;
66575f6d617Schristos sdiff_half_width = MAX (0, MIN (off - GUTTER_WIDTH_MINIMUM, w - off)),
66675f6d617Schristos sdiff_column2_offset = sdiff_half_width ? off : w;
66775f6d617Schristos }
66875f6d617Schristos
66975f6d617Schristos /* Make the horizon at least as large as the context, so that
67075f6d617Schristos shift_boundaries has more freedom to shift the first and last hunks. */
67175f6d617Schristos if (horizon_lines < context)
67275f6d617Schristos horizon_lines = context;
67375f6d617Schristos
67475f6d617Schristos summarize_regexp_list (&function_regexp_list);
67575f6d617Schristos summarize_regexp_list (&ignore_regexp_list);
67675f6d617Schristos
67775f6d617Schristos if (output_style == OUTPUT_IFDEF)
67875f6d617Schristos {
67975f6d617Schristos for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
68075f6d617Schristos if (!line_format[i])
68175f6d617Schristos line_format[i] = "%l\n";
68275f6d617Schristos if (!group_format[OLD])
68375f6d617Schristos group_format[OLD]
68475f6d617Schristos = group_format[CHANGED] ? group_format[CHANGED] : "%<";
68575f6d617Schristos if (!group_format[NEW])
68675f6d617Schristos group_format[NEW]
68775f6d617Schristos = group_format[CHANGED] ? group_format[CHANGED] : "%>";
68875f6d617Schristos if (!group_format[UNCHANGED])
68975f6d617Schristos group_format[UNCHANGED] = "%=";
69075f6d617Schristos if (!group_format[CHANGED])
69175f6d617Schristos group_format[CHANGED] = concat (group_format[OLD],
69275f6d617Schristos group_format[NEW], "");
69375f6d617Schristos }
69475f6d617Schristos
69575f6d617Schristos no_diff_means_no_output =
69675f6d617Schristos (output_style == OUTPUT_IFDEF ?
69775f6d617Schristos (!*group_format[UNCHANGED]
69875f6d617Schristos || (strcmp (group_format[UNCHANGED], "%=") == 0
69975f6d617Schristos && !*line_format[UNCHANGED]))
70075f6d617Schristos : (output_style != OUTPUT_SDIFF) | suppress_common_lines);
70175f6d617Schristos
70275f6d617Schristos files_can_be_treated_as_binary =
70375f6d617Schristos (brief
70475f6d617Schristos & ~ (ignore_blank_lines | ignore_case | strip_trailing_cr
70575f6d617Schristos | (ignore_regexp_list.regexps || ignore_white_space)));
70675f6d617Schristos
70775f6d617Schristos switch_string = option_list (argv + 1, optind - 1);
70875f6d617Schristos
70975f6d617Schristos if (from_file)
71075f6d617Schristos {
71175f6d617Schristos if (to_file)
71275f6d617Schristos fatal ("--from-file and --to-file both specified");
71375f6d617Schristos else
71475f6d617Schristos for (; optind < argc; optind++)
71575f6d617Schristos {
71675f6d617Schristos int status = compare_files ((struct comparison *) 0,
71775f6d617Schristos from_file, argv[optind]);
71875f6d617Schristos if (exit_status < status)
71975f6d617Schristos exit_status = status;
72075f6d617Schristos }
72175f6d617Schristos }
72275f6d617Schristos else
72375f6d617Schristos {
72475f6d617Schristos if (to_file)
72575f6d617Schristos for (; optind < argc; optind++)
72675f6d617Schristos {
72775f6d617Schristos int status = compare_files ((struct comparison *) 0,
72875f6d617Schristos argv[optind], to_file);
72975f6d617Schristos if (exit_status < status)
73075f6d617Schristos exit_status = status;
73175f6d617Schristos }
73275f6d617Schristos else
73375f6d617Schristos {
73475f6d617Schristos if (argc - optind != 2)
73575f6d617Schristos {
73675f6d617Schristos if (argc - optind < 2)
73775f6d617Schristos try_help ("missing operand after `%s'", argv[argc - 1]);
73875f6d617Schristos else
73975f6d617Schristos try_help ("extra operand `%s'", argv[optind + 2]);
74075f6d617Schristos }
74175f6d617Schristos
74275f6d617Schristos exit_status = compare_files ((struct comparison *) 0,
74375f6d617Schristos argv[optind], argv[optind + 1]);
74475f6d617Schristos }
74575f6d617Schristos }
74675f6d617Schristos
74775f6d617Schristos /* Print any messages that were saved up for last. */
74875f6d617Schristos print_message_queue ();
74975f6d617Schristos
75075f6d617Schristos check_stdout ();
75175f6d617Schristos exit (exit_status);
75275f6d617Schristos return exit_status;
75375f6d617Schristos }
75475f6d617Schristos
75575f6d617Schristos /* Append to REGLIST the regexp PATTERN. */
75675f6d617Schristos
75775f6d617Schristos static void
add_regexp(struct regexp_list * reglist,char const * pattern)75875f6d617Schristos add_regexp (struct regexp_list *reglist, char const *pattern)
75975f6d617Schristos {
76075f6d617Schristos size_t patlen = strlen (pattern);
76175f6d617Schristos char const *m = re_compile_pattern (pattern, patlen, reglist->buf);
76275f6d617Schristos
76375f6d617Schristos if (m != 0)
76475f6d617Schristos error (0, 0, "%s: %s", pattern, m);
76575f6d617Schristos else
76675f6d617Schristos {
76775f6d617Schristos char *regexps = reglist->regexps;
76875f6d617Schristos size_t len = reglist->len;
76975f6d617Schristos bool multiple_regexps = reglist->multiple_regexps = regexps != 0;
77075f6d617Schristos size_t newlen = reglist->len = len + 2 * multiple_regexps + patlen;
77175f6d617Schristos size_t size = reglist->size;
77275f6d617Schristos
77375f6d617Schristos if (size <= newlen)
77475f6d617Schristos {
77575f6d617Schristos if (!size)
77675f6d617Schristos size = 1;
77775f6d617Schristos
77875f6d617Schristos do size *= 2;
77975f6d617Schristos while (size <= newlen);
78075f6d617Schristos
78175f6d617Schristos reglist->size = size;
78275f6d617Schristos reglist->regexps = regexps = xrealloc (regexps, size);
78375f6d617Schristos }
78475f6d617Schristos if (multiple_regexps)
78575f6d617Schristos {
78675f6d617Schristos regexps[len++] = '\\';
78775f6d617Schristos regexps[len++] = '|';
78875f6d617Schristos }
78975f6d617Schristos memcpy (regexps + len, pattern, patlen + 1);
79075f6d617Schristos }
79175f6d617Schristos }
79275f6d617Schristos
79375f6d617Schristos /* Ensure that REGLIST represents the disjunction of its regexps.
79475f6d617Schristos This is done here, rather than earlier, to avoid O(N^2) behavior. */
79575f6d617Schristos
79675f6d617Schristos static void
summarize_regexp_list(struct regexp_list * reglist)79775f6d617Schristos summarize_regexp_list (struct regexp_list *reglist)
79875f6d617Schristos {
79975f6d617Schristos if (reglist->regexps)
80075f6d617Schristos {
80175f6d617Schristos /* At least one regexp was specified. Allocate a fastmap for it. */
80275f6d617Schristos reglist->buf->fastmap = xmalloc (1 << CHAR_BIT);
80375f6d617Schristos if (reglist->multiple_regexps)
80475f6d617Schristos {
80575f6d617Schristos /* Compile the disjunction of the regexps.
80675f6d617Schristos (If just one regexp was specified, it is already compiled.) */
80775f6d617Schristos char const *m = re_compile_pattern (reglist->regexps, reglist->len,
80875f6d617Schristos reglist->buf);
80975f6d617Schristos if (m != 0)
81075f6d617Schristos error (EXIT_TROUBLE, 0, "%s: %s", reglist->regexps, m);
81175f6d617Schristos }
81275f6d617Schristos }
81375f6d617Schristos }
81475f6d617Schristos
81575f6d617Schristos static void
try_help(char const * reason_msgid,char const * operand)81675f6d617Schristos try_help (char const *reason_msgid, char const *operand)
81775f6d617Schristos {
81875f6d617Schristos if (reason_msgid)
81975f6d617Schristos error (0, 0, _(reason_msgid), operand);
82075f6d617Schristos error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
82175f6d617Schristos program_name);
82275f6d617Schristos abort ();
82375f6d617Schristos }
82475f6d617Schristos
82575f6d617Schristos static void
check_stdout(void)82675f6d617Schristos check_stdout (void)
82775f6d617Schristos {
82875f6d617Schristos if (ferror (stdout))
82975f6d617Schristos fatal ("write failed");
83075f6d617Schristos else if (fclose (stdout) != 0)
83175f6d617Schristos pfatal_with_name (_("standard output"));
83275f6d617Schristos }
83375f6d617Schristos
83475f6d617Schristos static char const * const option_help_msgid[] = {
83575f6d617Schristos N_("Compare files line by line."),
83675f6d617Schristos "",
83775f6d617Schristos N_("-i --ignore-case Ignore case differences in file contents."),
83875f6d617Schristos N_("--ignore-file-name-case Ignore case when comparing file names."),
83975f6d617Schristos N_("--no-ignore-file-name-case Consider case when comparing file names."),
84075f6d617Schristos N_("-E --ignore-tab-expansion Ignore changes due to tab expansion."),
84175f6d617Schristos N_("-b --ignore-space-change Ignore changes in the amount of white space."),
84275f6d617Schristos N_("-w --ignore-all-space Ignore all white space."),
84375f6d617Schristos N_("-B --ignore-blank-lines Ignore changes whose lines are all blank."),
84475f6d617Schristos N_("-I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE."),
84575f6d617Schristos N_("--strip-trailing-cr Strip trailing carriage return on input."),
84675f6d617Schristos #if HAVE_SETMODE_DOS
84775f6d617Schristos N_("--binary Read and write data in binary mode."),
84875f6d617Schristos #endif
84975f6d617Schristos N_("-a --text Treat all files as text."),
85075f6d617Schristos "",
85175f6d617Schristos N_("-c -C NUM --context[=NUM] Output NUM (default 3) lines of copied context.\n\
85275f6d617Schristos -u -U NUM --unified[=NUM] Output NUM (default 3) lines of unified context.\n\
85375f6d617Schristos --label LABEL Use LABEL instead of file name.\n\
85475f6d617Schristos -p --show-c-function Show which C function each change is in.\n\
85575f6d617Schristos -F RE --show-function-line=RE Show the most recent line matching RE."),
85675f6d617Schristos N_("-q --brief Output only whether files differ."),
85775f6d617Schristos N_("-e --ed Output an ed script."),
85875f6d617Schristos N_("--normal Output a normal diff."),
85975f6d617Schristos N_("-n --rcs Output an RCS format diff."),
86075f6d617Schristos N_("-y --side-by-side Output in two columns.\n\
86175f6d617Schristos -W NUM --width=NUM Output at most NUM (default 130) print columns.\n\
86275f6d617Schristos --left-column Output only the left column of common lines.\n\
86375f6d617Schristos --suppress-common-lines Do not output common lines."),
86475f6d617Schristos N_("-D NAME --ifdef=NAME Output merged file to show `#ifdef NAME' diffs."),
86575f6d617Schristos N_("--GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT."),
86675f6d617Schristos N_("--line-format=LFMT Similar, but format all input lines with LFMT."),
86775f6d617Schristos N_("--LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT."),
86875f6d617Schristos N_(" LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'."),
86975f6d617Schristos N_(" GFMT may contain:\n\
87075f6d617Schristos %< lines from FILE1\n\
87175f6d617Schristos %> lines from FILE2\n\
87275f6d617Schristos %= lines common to FILE1 and FILE2\n\
87375f6d617Schristos %[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n\
87475f6d617Schristos LETTERs are as follows for new group, lower case for old group:\n\
87575f6d617Schristos F first line number\n\
87675f6d617Schristos L last line number\n\
87775f6d617Schristos N number of lines = L-F+1\n\
87875f6d617Schristos E F-1\n\
87975f6d617Schristos M L+1"),
88075f6d617Schristos N_(" LFMT may contain:\n\
88175f6d617Schristos %L contents of line\n\
88275f6d617Schristos %l contents of line, excluding any trailing newline\n\
88375f6d617Schristos %[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number"),
88475f6d617Schristos N_(" Either GFMT or LFMT may contain:\n\
88575f6d617Schristos %% %\n\
88675f6d617Schristos %c'C' the single character C\n\
88775f6d617Schristos %c'\\OOO' the character with octal code OOO"),
88875f6d617Schristos "",
88975f6d617Schristos N_("-l --paginate Pass the output through `pr' to paginate it."),
89075f6d617Schristos N_("-t --expand-tabs Expand tabs to spaces in output."),
89175f6d617Schristos N_("-T --initial-tab Make tabs line up by prepending a tab."),
89275f6d617Schristos "",
89375f6d617Schristos N_("-r --recursive Recursively compare any subdirectories found."),
89475f6d617Schristos N_("-N --new-file Treat absent files as empty."),
89575f6d617Schristos N_("--unidirectional-new-file Treat absent first files as empty."),
89675f6d617Schristos N_("-s --report-identical-files Report when two files are the same."),
89775f6d617Schristos N_("-x PAT --exclude=PAT Exclude files that match PAT."),
89875f6d617Schristos N_("-X FILE --exclude-from=FILE Exclude files that match any pattern in FILE."),
89975f6d617Schristos N_("-S FILE --starting-file=FILE Start with FILE when comparing directories."),
90075f6d617Schristos N_("--from-file=FILE1 Compare FILE1 to all operands. FILE1 can be a directory."),
90175f6d617Schristos N_("--to-file=FILE2 Compare all operands to FILE2. FILE2 can be a directory."),
90275f6d617Schristos "",
90375f6d617Schristos N_("--horizon-lines=NUM Keep NUM lines of the common prefix and suffix."),
90475f6d617Schristos N_("-d --minimal Try hard to find a smaller set of changes."),
90575f6d617Schristos N_("--speed-large-files Assume large files and many scattered small changes."),
90675f6d617Schristos "",
90775f6d617Schristos N_("-v --version Output version info."),
90875f6d617Schristos N_("--help Output this help."),
90975f6d617Schristos "",
91075f6d617Schristos N_("FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'."),
91175f6d617Schristos N_("If --from-file or --to-file is given, there are no restrictions on FILES."),
91275f6d617Schristos N_("If a FILE is `-', read standard input."),
91375f6d617Schristos "",
91475f6d617Schristos N_("Report bugs to <bug-gnu-utils@gnu.org>."),
91575f6d617Schristos 0
91675f6d617Schristos };
91775f6d617Schristos
91875f6d617Schristos static void
usage(void)91975f6d617Schristos usage (void)
92075f6d617Schristos {
92175f6d617Schristos char const * const *p;
92275f6d617Schristos
92375f6d617Schristos printf (_("Usage: %s [OPTION]... FILES\n"), program_name);
92475f6d617Schristos
92575f6d617Schristos for (p = option_help_msgid; *p; p++)
92675f6d617Schristos {
92775f6d617Schristos if (!**p)
92875f6d617Schristos putchar ('\n');
92975f6d617Schristos else
93075f6d617Schristos {
93175f6d617Schristos char const *msg = _(*p);
93275f6d617Schristos char const *nl;
93375f6d617Schristos while ((nl = strchr (msg, '\n')))
93475f6d617Schristos {
93575f6d617Schristos int msglen = nl + 1 - msg;
93675f6d617Schristos printf (" %.*s", msglen, msg);
93775f6d617Schristos msg = nl + 1;
93875f6d617Schristos }
93975f6d617Schristos
94075f6d617Schristos printf (" %s\n" + 2 * (*msg != ' ' && *msg != '-'), msg);
94175f6d617Schristos }
94275f6d617Schristos }
94375f6d617Schristos }
94475f6d617Schristos
94575f6d617Schristos /* Set VAR to VALUE, reporting an OPTION error if this is a
94675f6d617Schristos conflict. */
94775f6d617Schristos static void
specify_value(char const ** var,char const * value,char const * option)94875f6d617Schristos specify_value (char const **var, char const *value, char const *option)
94975f6d617Schristos {
95075f6d617Schristos if (*var && strcmp (*var, value) != 0)
95175f6d617Schristos {
95275f6d617Schristos error (0, 0, _("conflicting %s option value `%s'"), option, value);
95375f6d617Schristos try_help (0, 0);
95475f6d617Schristos }
95575f6d617Schristos *var = value;
95675f6d617Schristos }
95775f6d617Schristos
95875f6d617Schristos /* Set the output style to STYLE, diagnosing conflicts. */
95975f6d617Schristos static void
specify_style(enum output_style style)96075f6d617Schristos specify_style (enum output_style style)
96175f6d617Schristos {
96275f6d617Schristos if (output_style != style)
96375f6d617Schristos {
96475f6d617Schristos if (output_style != OUTPUT_UNSPECIFIED)
96575f6d617Schristos try_help ("conflicting output style options", 0);
96675f6d617Schristos output_style = style;
96775f6d617Schristos }
96875f6d617Schristos }
96975f6d617Schristos
97075f6d617Schristos static char const *
filetype(struct stat const * st)97175f6d617Schristos filetype (struct stat const *st)
97275f6d617Schristos {
97375f6d617Schristos /* See POSIX 1003.1-2001 for these formats.
97475f6d617Schristos
97575f6d617Schristos To keep diagnostics grammatical in English, the returned string
97675f6d617Schristos must start with a consonant. */
97775f6d617Schristos
97875f6d617Schristos if (S_ISREG (st->st_mode))
97975f6d617Schristos return st->st_size == 0 ? _("regular empty file") : _("regular file");
98075f6d617Schristos
98175f6d617Schristos if (S_ISDIR (st->st_mode)) return _("directory");
98275f6d617Schristos
98375f6d617Schristos #ifdef S_ISBLK
98475f6d617Schristos if (S_ISBLK (st->st_mode)) return _("block special file");
98575f6d617Schristos #endif
98675f6d617Schristos #ifdef S_ISCHR
98775f6d617Schristos if (S_ISCHR (st->st_mode)) return _("character special file");
98875f6d617Schristos #endif
98975f6d617Schristos #ifdef S_ISFIFO
99075f6d617Schristos if (S_ISFIFO (st->st_mode)) return _("fifo");
99175f6d617Schristos #endif
99275f6d617Schristos /* S_ISLNK is impossible with `fstat' and `stat'. */
99375f6d617Schristos #ifdef S_ISSOCK
99475f6d617Schristos if (S_ISSOCK (st->st_mode)) return _("socket");
99575f6d617Schristos #endif
99675f6d617Schristos #ifdef S_TYPEISMQ
99775f6d617Schristos if (S_TYPEISMQ (st)) return _("message queue");
99875f6d617Schristos #endif
99975f6d617Schristos #ifdef S_TYPEISSEM
100075f6d617Schristos if (S_TYPEISSEM (st)) return _("semaphore");
100175f6d617Schristos #endif
100275f6d617Schristos #ifdef S_TYPEISSHM
100375f6d617Schristos if (S_TYPEISSHM (st)) return _("shared memory object");
100475f6d617Schristos #endif
100575f6d617Schristos #ifdef S_TYPEISTMO
100675f6d617Schristos if (S_TYPEISTMO (st)) return _("typed memory object");
100775f6d617Schristos #endif
100875f6d617Schristos
100975f6d617Schristos return _("weird file");
101075f6d617Schristos }
101175f6d617Schristos
101275f6d617Schristos /* Set the last-modified time of *ST to be the current time. */
101375f6d617Schristos
101475f6d617Schristos static void
set_mtime_to_now(struct stat * st)101575f6d617Schristos set_mtime_to_now (struct stat *st)
101675f6d617Schristos {
101775f6d617Schristos #ifdef ST_MTIM_NSEC
101875f6d617Schristos
101975f6d617Schristos # if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME
102075f6d617Schristos if (clock_gettime (CLOCK_REALTIME, &st->st_mtim) == 0)
102175f6d617Schristos return;
102275f6d617Schristos # endif
102375f6d617Schristos
102475f6d617Schristos # if HAVE_GETTIMEOFDAY
102575f6d617Schristos {
102675f6d617Schristos struct timeval timeval;
102775f6d617Schristos if (gettimeofday (&timeval, NULL) == 0)
102875f6d617Schristos {
102975f6d617Schristos st->st_mtime = timeval.tv_sec;
103075f6d617Schristos st->st_mtim.ST_MTIM_NSEC = timeval.tv_usec * 1000;
103175f6d617Schristos return;
103275f6d617Schristos }
103375f6d617Schristos }
103475f6d617Schristos # endif
103575f6d617Schristos
103675f6d617Schristos #endif /* ST_MTIM_NSEC */
103775f6d617Schristos
103875f6d617Schristos time (&st->st_mtime);
103975f6d617Schristos }
104075f6d617Schristos
104175f6d617Schristos /* Compare two files (or dirs) with parent comparison PARENT
104275f6d617Schristos and names NAME0 and NAME1.
104375f6d617Schristos (If PARENT is 0, then the first name is just NAME0, etc.)
104475f6d617Schristos This is self-contained; it opens the files and closes them.
104575f6d617Schristos
104675f6d617Schristos Value is EXIT_SUCCESS if files are the same, EXIT_FAILURE if
104775f6d617Schristos different, EXIT_TROUBLE if there is a problem opening them. */
104875f6d617Schristos
104975f6d617Schristos static int
compare_files(struct comparison const * parent,char const * name0,char const * name1)105075f6d617Schristos compare_files (struct comparison const *parent,
105175f6d617Schristos char const *name0,
105275f6d617Schristos char const *name1)
105375f6d617Schristos {
105475f6d617Schristos struct comparison cmp;
105575f6d617Schristos #define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
105675f6d617Schristos register int f;
105775f6d617Schristos int status = EXIT_SUCCESS;
105875f6d617Schristos bool same_files;
105975f6d617Schristos char *free0, *free1;
106075f6d617Schristos
106175f6d617Schristos /* If this is directory comparison, perhaps we have a file
106275f6d617Schristos that exists only in one of the directories.
106375f6d617Schristos If so, just print a message to that effect. */
106475f6d617Schristos
106575f6d617Schristos if (! ((name0 && name1)
106675f6d617Schristos || (unidirectional_new_file && name1)
106775f6d617Schristos || new_file))
106875f6d617Schristos {
106975f6d617Schristos char const *name = name0 == 0 ? name1 : name0;
107075f6d617Schristos char const *dir = parent->file[name0 == 0].name;
107175f6d617Schristos
107275f6d617Schristos /* See POSIX 1003.1-2001 for this format. */
107375f6d617Schristos message ("Only in %s: %s\n", dir, name);
107475f6d617Schristos
107575f6d617Schristos /* Return EXIT_FAILURE so that diff_dirs will return
107675f6d617Schristos EXIT_FAILURE ("some files differ"). */
107775f6d617Schristos return EXIT_FAILURE;
107875f6d617Schristos }
107975f6d617Schristos
108075f6d617Schristos memset (cmp.file, 0, sizeof cmp.file);
108175f6d617Schristos cmp.parent = parent;
108275f6d617Schristos
108375f6d617Schristos /* cmp.file[f].desc markers */
108475f6d617Schristos #define NONEXISTENT (-1) /* nonexistent file */
108575f6d617Schristos #define UNOPENED (-2) /* unopened file (e.g. directory) */
108675f6d617Schristos #define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded errno value */
108775f6d617Schristos
108875f6d617Schristos #define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of ERRNO_ENCODE */
108975f6d617Schristos
109075f6d617Schristos cmp.file[0].desc = name0 == 0 ? NONEXISTENT : UNOPENED;
109175f6d617Schristos cmp.file[1].desc = name1 == 0 ? NONEXISTENT : UNOPENED;
109275f6d617Schristos
109375f6d617Schristos /* Now record the full name of each file, including nonexistent ones. */
109475f6d617Schristos
109575f6d617Schristos if (name0 == 0)
109675f6d617Schristos name0 = name1;
109775f6d617Schristos if (name1 == 0)
109875f6d617Schristos name1 = name0;
109975f6d617Schristos
110075f6d617Schristos if (!parent)
110175f6d617Schristos {
110275f6d617Schristos free0 = 0;
110375f6d617Schristos free1 = 0;
110475f6d617Schristos cmp.file[0].name = name0;
110575f6d617Schristos cmp.file[1].name = name1;
110675f6d617Schristos }
110775f6d617Schristos else
110875f6d617Schristos {
110975f6d617Schristos cmp.file[0].name = free0
111075f6d617Schristos = dir_file_pathname (parent->file[0].name, name0);
111175f6d617Schristos cmp.file[1].name = free1
111275f6d617Schristos = dir_file_pathname (parent->file[1].name, name1);
111375f6d617Schristos }
111475f6d617Schristos
111575f6d617Schristos /* Stat the files. */
111675f6d617Schristos
111775f6d617Schristos for (f = 0; f < 2; f++)
111875f6d617Schristos {
111975f6d617Schristos if (cmp.file[f].desc != NONEXISTENT)
112075f6d617Schristos {
112175f6d617Schristos if (f && file_name_cmp (cmp.file[f].name, cmp.file[0].name) == 0)
112275f6d617Schristos {
112375f6d617Schristos cmp.file[f].desc = cmp.file[0].desc;
112475f6d617Schristos cmp.file[f].stat = cmp.file[0].stat;
112575f6d617Schristos }
112675f6d617Schristos else if (strcmp (cmp.file[f].name, "-") == 0)
112775f6d617Schristos {
112875f6d617Schristos cmp.file[f].desc = STDIN_FILENO;
112975f6d617Schristos if (fstat (STDIN_FILENO, &cmp.file[f].stat) != 0)
113075f6d617Schristos cmp.file[f].desc = ERRNO_ENCODE (errno);
113175f6d617Schristos else
113275f6d617Schristos {
113375f6d617Schristos if (S_ISREG (cmp.file[f].stat.st_mode))
113475f6d617Schristos {
113575f6d617Schristos off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
113675f6d617Schristos if (pos < 0)
113775f6d617Schristos cmp.file[f].desc = ERRNO_ENCODE (errno);
113875f6d617Schristos else
113975f6d617Schristos cmp.file[f].stat.st_size =
114075f6d617Schristos MAX (0, cmp.file[f].stat.st_size - pos);
114175f6d617Schristos }
114275f6d617Schristos
114375f6d617Schristos /* POSIX 1003.1-2001 requires current time for
114475f6d617Schristos stdin. */
114575f6d617Schristos set_mtime_to_now (&cmp.file[f].stat);
114675f6d617Schristos }
114775f6d617Schristos }
114875f6d617Schristos else if (stat (cmp.file[f].name, &cmp.file[f].stat) != 0)
114975f6d617Schristos cmp.file[f].desc = ERRNO_ENCODE (errno);
115075f6d617Schristos }
115175f6d617Schristos }
115275f6d617Schristos
115375f6d617Schristos /* Mark files as nonexistent at the top level as needed for -N and
115475f6d617Schristos --unidirectional-new-file. */
115575f6d617Schristos if (! parent)
115675f6d617Schristos {
115775f6d617Schristos if ((new_file | unidirectional_new_file)
115875f6d617Schristos && cmp.file[0].desc == ERRNO_ENCODE (ENOENT)
115975f6d617Schristos && cmp.file[1].desc == UNOPENED)
116075f6d617Schristos cmp.file[0].desc = NONEXISTENT;
116175f6d617Schristos
116275f6d617Schristos if (new_file
116375f6d617Schristos && cmp.file[0].desc == UNOPENED
116475f6d617Schristos && cmp.file[1].desc == ERRNO_ENCODE (ENOENT))
116575f6d617Schristos cmp.file[1].desc = NONEXISTENT;
116675f6d617Schristos }
116775f6d617Schristos
116875f6d617Schristos for (f = 0; f < 2; f++)
116975f6d617Schristos if (cmp.file[f].desc == NONEXISTENT)
117075f6d617Schristos cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
117175f6d617Schristos
117275f6d617Schristos for (f = 0; f < 2; f++)
117375f6d617Schristos {
117475f6d617Schristos int e = ERRNO_DECODE (cmp.file[f].desc);
117575f6d617Schristos if (0 <= e)
117675f6d617Schristos {
117775f6d617Schristos errno = e;
117875f6d617Schristos perror_with_name (cmp.file[f].name);
117975f6d617Schristos status = EXIT_TROUBLE;
118075f6d617Schristos }
118175f6d617Schristos }
118275f6d617Schristos
118375f6d617Schristos if (status == EXIT_SUCCESS && ! parent && DIR_P (0) != DIR_P (1))
118475f6d617Schristos {
118575f6d617Schristos /* If one is a directory, and it was specified in the command line,
118675f6d617Schristos use the file in that dir with the other file's basename. */
118775f6d617Schristos
118875f6d617Schristos int fnm_arg = DIR_P (0);
118975f6d617Schristos int dir_arg = 1 - fnm_arg;
119075f6d617Schristos char const *fnm = cmp.file[fnm_arg].name;
119175f6d617Schristos char const *dir = cmp.file[dir_arg].name;
119275f6d617Schristos char const *filename = cmp.file[dir_arg].name = free0
119375f6d617Schristos = dir_file_pathname (dir, base_name (fnm));
119475f6d617Schristos
119575f6d617Schristos if (strcmp (fnm, "-") == 0)
119675f6d617Schristos fatal ("cannot compare `-' to a directory");
119775f6d617Schristos
119875f6d617Schristos if (stat (filename, &cmp.file[dir_arg].stat) != 0)
119975f6d617Schristos {
120075f6d617Schristos perror_with_name (filename);
120175f6d617Schristos status = EXIT_TROUBLE;
120275f6d617Schristos }
120375f6d617Schristos }
120475f6d617Schristos
120575f6d617Schristos if (status != EXIT_SUCCESS)
120675f6d617Schristos {
120775f6d617Schristos /* One of the files should exist but does not. */
120875f6d617Schristos }
120975f6d617Schristos else if ((same_files
121075f6d617Schristos = (cmp.file[0].desc != NONEXISTENT
121175f6d617Schristos && cmp.file[1].desc != NONEXISTENT
1212*f8c23a2bSchristos && (same_special_file (&cmp.file[0].stat, &cmp.file[1].stat)
1213*f8c23a2bSchristos || (0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
121475f6d617Schristos && same_file_attributes (&cmp.file[0].stat,
1215*f8c23a2bSchristos &cmp.file[1].stat)))))
121675f6d617Schristos && no_diff_means_no_output)
121775f6d617Schristos {
121875f6d617Schristos /* The two named files are actually the same physical file.
121975f6d617Schristos We know they are identical without actually reading them. */
122075f6d617Schristos }
122175f6d617Schristos else if (DIR_P (0) & DIR_P (1))
122275f6d617Schristos {
122375f6d617Schristos if (output_style == OUTPUT_IFDEF)
122475f6d617Schristos fatal ("-D option not supported with directories");
122575f6d617Schristos
122675f6d617Schristos /* If both are directories, compare the files in them. */
122775f6d617Schristos
122875f6d617Schristos if (parent && !recursive)
122975f6d617Schristos {
123075f6d617Schristos /* But don't compare dir contents one level down
123175f6d617Schristos unless -r was specified.
123275f6d617Schristos See POSIX 1003.1-2001 for this format. */
123375f6d617Schristos message ("Common subdirectories: %s and %s\n",
123475f6d617Schristos cmp.file[0].name, cmp.file[1].name);
123575f6d617Schristos }
123675f6d617Schristos else
123775f6d617Schristos status = diff_dirs (&cmp, compare_files);
123875f6d617Schristos }
123975f6d617Schristos else if ((DIR_P (0) | DIR_P (1))
124075f6d617Schristos || (parent
124175f6d617Schristos && (! S_ISREG (cmp.file[0].stat.st_mode)
124275f6d617Schristos || ! S_ISREG (cmp.file[1].stat.st_mode))))
124375f6d617Schristos {
124475f6d617Schristos if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT)
124575f6d617Schristos {
124675f6d617Schristos /* We have a subdirectory that exists only in one directory. */
124775f6d617Schristos
124875f6d617Schristos if ((DIR_P (0) | DIR_P (1))
124975f6d617Schristos && recursive
125075f6d617Schristos && (new_file
125175f6d617Schristos || (unidirectional_new_file
125275f6d617Schristos && cmp.file[0].desc == NONEXISTENT)))
125375f6d617Schristos status = diff_dirs (&cmp, compare_files);
125475f6d617Schristos else
125575f6d617Schristos {
125675f6d617Schristos char const *dir
125775f6d617Schristos = parent->file[cmp.file[0].desc == NONEXISTENT].name;
125875f6d617Schristos
125975f6d617Schristos /* See POSIX 1003.1-2001 for this format. */
126075f6d617Schristos message ("Only in %s: %s\n", dir, name0);
126175f6d617Schristos
126275f6d617Schristos status = EXIT_FAILURE;
126375f6d617Schristos }
126475f6d617Schristos }
126575f6d617Schristos else
126675f6d617Schristos {
126775f6d617Schristos /* We have two files that are not to be compared. */
126875f6d617Schristos
126975f6d617Schristos /* See POSIX 1003.1-2001 for this format. */
127075f6d617Schristos message5 ("File %s is a %s while file %s is a %s\n",
127175f6d617Schristos file_label[0] ? file_label[0] : cmp.file[0].name,
127275f6d617Schristos filetype (&cmp.file[0].stat),
127375f6d617Schristos file_label[1] ? file_label[1] : cmp.file[1].name,
127475f6d617Schristos filetype (&cmp.file[1].stat));
127575f6d617Schristos
127675f6d617Schristos /* This is a difference. */
127775f6d617Schristos status = EXIT_FAILURE;
127875f6d617Schristos }
127975f6d617Schristos }
128075f6d617Schristos else if (files_can_be_treated_as_binary
128175f6d617Schristos && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size
128275f6d617Schristos && (cmp.file[0].desc == NONEXISTENT
128375f6d617Schristos || S_ISREG (cmp.file[0].stat.st_mode))
128475f6d617Schristos && (cmp.file[1].desc == NONEXISTENT
128575f6d617Schristos || S_ISREG (cmp.file[1].stat.st_mode)))
128675f6d617Schristos {
128775f6d617Schristos message ("Files %s and %s differ\n",
128875f6d617Schristos file_label[0] ? file_label[0] : cmp.file[0].name,
128975f6d617Schristos file_label[1] ? file_label[1] : cmp.file[1].name);
129075f6d617Schristos status = EXIT_FAILURE;
129175f6d617Schristos }
129275f6d617Schristos else
129375f6d617Schristos {
129475f6d617Schristos /* Both exist and neither is a directory. */
129575f6d617Schristos
129675f6d617Schristos /* Open the files and record their descriptors. */
129775f6d617Schristos
129875f6d617Schristos if (cmp.file[0].desc == UNOPENED)
129975f6d617Schristos if ((cmp.file[0].desc = open (cmp.file[0].name, O_RDONLY, 0)) < 0)
130075f6d617Schristos {
130175f6d617Schristos perror_with_name (cmp.file[0].name);
130275f6d617Schristos status = EXIT_TROUBLE;
130375f6d617Schristos }
130475f6d617Schristos if (cmp.file[1].desc == UNOPENED)
130575f6d617Schristos {
130675f6d617Schristos if (same_files)
130775f6d617Schristos cmp.file[1].desc = cmp.file[0].desc;
130875f6d617Schristos else if ((cmp.file[1].desc = open (cmp.file[1].name, O_RDONLY, 0))
130975f6d617Schristos < 0)
131075f6d617Schristos {
131175f6d617Schristos perror_with_name (cmp.file[1].name);
131275f6d617Schristos status = EXIT_TROUBLE;
131375f6d617Schristos }
131475f6d617Schristos }
131575f6d617Schristos
131675f6d617Schristos #if HAVE_SETMODE_DOS
131775f6d617Schristos if (binary)
131875f6d617Schristos for (f = 0; f < 2; f++)
131975f6d617Schristos if (0 <= cmp.file[f].desc)
132075f6d617Schristos set_binary_mode (cmp.file[f].desc, 1);
132175f6d617Schristos #endif
132275f6d617Schristos
132375f6d617Schristos /* Compare the files, if no error was found. */
132475f6d617Schristos
132575f6d617Schristos if (status == EXIT_SUCCESS)
132675f6d617Schristos status = diff_2_files (&cmp);
132775f6d617Schristos
132875f6d617Schristos /* Close the file descriptors. */
132975f6d617Schristos
133075f6d617Schristos if (0 <= cmp.file[0].desc && close (cmp.file[0].desc) != 0)
133175f6d617Schristos {
133275f6d617Schristos perror_with_name (cmp.file[0].name);
133375f6d617Schristos status = EXIT_TROUBLE;
133475f6d617Schristos }
133575f6d617Schristos if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
133675f6d617Schristos && close (cmp.file[1].desc) != 0)
133775f6d617Schristos {
133875f6d617Schristos perror_with_name (cmp.file[1].name);
133975f6d617Schristos status = EXIT_TROUBLE;
134075f6d617Schristos }
134175f6d617Schristos }
134275f6d617Schristos
134375f6d617Schristos /* Now the comparison has been done, if no error prevented it,
134475f6d617Schristos and STATUS is the value this function will return. */
134575f6d617Schristos
134675f6d617Schristos if (status == EXIT_SUCCESS)
134775f6d617Schristos {
134875f6d617Schristos if (report_identical_files && !DIR_P (0))
134975f6d617Schristos message ("Files %s and %s are identical\n",
135075f6d617Schristos file_label[0] ? file_label[0] : cmp.file[0].name,
135175f6d617Schristos file_label[1] ? file_label[1] : cmp.file[1].name);
135275f6d617Schristos }
135375f6d617Schristos else
135475f6d617Schristos {
135575f6d617Schristos /* Flush stdout so that the user sees differences immediately.
135675f6d617Schristos This can hurt performance, unfortunately. */
135775f6d617Schristos if (fflush (stdout) != 0)
135875f6d617Schristos pfatal_with_name (_("standard output"));
135975f6d617Schristos }
136075f6d617Schristos
136175f6d617Schristos if (free0)
136275f6d617Schristos free (free0);
136375f6d617Schristos if (free1)
136475f6d617Schristos free (free1);
136575f6d617Schristos
136675f6d617Schristos return status;
136775f6d617Schristos }
1368