#define GDIFF_MAIN
#include "diff.h"
#include <c-stack.h>
#include <dirname.h>
#include <error.h>
#include <exclude.h>
#include <exitfail.h>
#include <fnmatch.h>
#include <freesoft.h>
#include <getopt.h>
#include <hard-locale.h>
#include <prepargs.h>
#include <quotesys.h>
#include <regex.h>
#include <setmode.h>
#include <xalloc.h>
static char const authorship_msgid[] =
N_("Written by Paul Eggert, Mike Haertel, David Hayes,\n\
Richard Stallman, and Len Tower.");
static char const copyright_string[] =
"Copyright (C) 2002 Free Software Foundation, Inc.";
#ifndef GUTTER_WIDTH_MINIMUM
# define GUTTER_WIDTH_MINIMUM 3
#endif
struct regexp_list
{
char *regexps;
size_t len;
size_t size;
bool multiple_regexps;
struct re_pattern_buffer *buf;
};
static int compare_files (struct comparison const *, char const *, char const *);
static void add_regexp (struct regexp_list *, char const *);
static void summarize_regexp_list (struct regexp_list *);
static void specify_style (enum output_style);
static void specify_value (char const **, char const *, char const *);
static void try_help (char const *, char const *) __attribute__((noreturn));
static void check_stdout (void);
static void usage (void);
static bool recursive;
static struct regexp_list function_regexp_list;
static struct regexp_list ignore_regexp_list;
#if HAVE_SETMODE_DOS
static bool binary;
#endif
static bool new_file;
static bool unidirectional_new_file;
static bool report_identical_files;
static char *
option_list (char **optionvec, int count)
{
int i;
size_t size = 1;
char *result;
char *p;
for (i = 0; i < count; i++)
size += 1 + quote_system_arg ((char *) 0, optionvec[i]);
p = result = xmalloc (size);
for (i = 0; i < count; i++)
{
*p++ = ' ';
p += quote_system_arg (p, optionvec[i]);
}
*p = 0;
return result;
}
static int
exclude_options (void)
{
return EXCLUDE_WILDCARDS | (ignore_file_name_case ? FNM_CASEFOLD : 0);
}
static char const shortopts[] =
"0123456789abBcC:dD:eEfF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y";
enum
{
BINARY_OPTION = CHAR_MAX + 1,
FROM_FILE_OPTION,
HELP_OPTION,
HORIZON_LINES_OPTION,
IGNORE_FILE_NAME_CASE_OPTION,
INHIBIT_HUNK_MERGE_OPTION,
LEFT_COLUMN_OPTION,
LINE_FORMAT_OPTION,
NO_IGNORE_FILE_NAME_CASE_OPTION,
NORMAL_OPTION,
SDIFF_MERGE_ASSIST_OPTION,
STRIP_TRAILING_CR_OPTION,
SUPPRESS_COMMON_LINES_OPTION,
TO_FILE_OPTION,
UNCHANGED_LINE_FORMAT_OPTION,
OLD_LINE_FORMAT_OPTION,
NEW_LINE_FORMAT_OPTION,
UNCHANGED_GROUP_FORMAT_OPTION,
OLD_GROUP_FORMAT_OPTION,
NEW_GROUP_FORMAT_OPTION,
CHANGED_GROUP_FORMAT_OPTION
};
static char const group_format_option[][sizeof "--unchanged-group-format"] =
{
"--unchanged-group-format",
"--old-group-format",
"--new-group-format",
"--changed-group-format"
};
static char const line_format_option[][sizeof "--unchanged-line-format"] =
{
"--unchanged-line-format",
"--old-line-format",
"--new-line-format"
};
static struct option const longopts[] =
{
{"binary", 0, 0, BINARY_OPTION},
{"brief", 0, 0, 'q'},
{"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
{"context", 2, 0, 'C'},
{"ed", 0, 0, 'e'},
{"exclude", 1, 0, 'x'},
{"exclude-from", 1, 0, 'X'},
{"expand-tabs", 0, 0, 't'},
{"forward-ed", 0, 0, 'f'},
{"from-file", 1, 0, FROM_FILE_OPTION},
{"help", 0, 0, HELP_OPTION},
{"horizon-lines", 1, 0, HORIZON_LINES_OPTION},
{"ifdef", 1, 0, 'D'},
{"ignore-all-space", 0, 0, 'w'},
{"ignore-blank-lines", 0, 0, 'B'},
{"ignore-case", 0, 0, 'i'},
{"ignore-file-name-case", 0, 0, IGNORE_FILE_NAME_CASE_OPTION},
{"ignore-matching-lines", 1, 0, 'I'},
{"ignore-space-change", 0, 0, 'b'},
{"ignore-tab-expansion", 0, 0, 'E'},
{"inhibit-hunk-merge", 0, 0, INHIBIT_HUNK_MERGE_OPTION},
{"initial-tab", 0, 0, 'T'},
{"label", 1, 0, 'L'},
{"left-column", 0, 0, LEFT_COLUMN_OPTION},
{"line-format", 1, 0, LINE_FORMAT_OPTION},
{"minimal", 0, 0, 'd'},
{"new-file", 0, 0, 'N'},
{"new-group-format", 1, 0, NEW_GROUP_FORMAT_OPTION},
{"new-line-format", 1, 0, NEW_LINE_FORMAT_OPTION},
{"no-ignore-file-name-case", 0, 0, NO_IGNORE_FILE_NAME_CASE_OPTION},
{"normal", 0, 0, NORMAL_OPTION},
{"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
{"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
{"paginate", 0, 0, 'l'},
{"rcs", 0, 0, 'n'},
{"recursive", 0, 0, 'r'},
{"report-identical-files", 0, 0, 's'},
{"sdiff-merge-assist", 0, 0, SDIFF_MERGE_ASSIST_OPTION},
{"show-c-function", 0, 0, 'p'},
{"show-function-line", 1, 0, 'F'},
{"side-by-side", 0, 0, 'y'},
{"speed-large-files", 0, 0, 'H'},
{"starting-file", 1, 0, 'S'},
{"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
{"suppress-common-lines", 0, 0, SUPPRESS_COMMON_LINES_OPTION},
{"text", 0, 0, 'a'},
{"to-file", 1, 0, TO_FILE_OPTION},
{"unchanged-group-format", 1, 0, UNCHANGED_GROUP_FORMAT_OPTION},
{"unchanged-line-format", 1, 0, UNCHANGED_LINE_FORMAT_OPTION},
{"unidirectional-new-file", 0, 0, 'P'},
{"unified", 2, 0, 'U'},
{"version", 0, 0, 'v'},
{"width", 1, 0, 'W'},
{0, 0, 0, 0}
};
int
main (int argc, char **argv)
{
int exit_status = EXIT_SUCCESS;
int c;
int i;
int prev = -1;
lin ocontext = -1;
bool explicit_context = 0;
int width = 0;
bool show_c_function = 0;
char const *from_file = 0;
char const *to_file = 0;
uintmax_t numval;
char *numend;
exit_failure = 2;
initialize_main (&argc, &argv);
program_name = argv[0];
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
c_stack_action (c_stack_die);
function_regexp_list.buf = &function_regexp;
ignore_regexp_list.buf = &ignore_regexp;
re_set_syntax (RE_SYNTAX_GREP | RE_NO_POSIX_BACKTRACKING);
excluded = new_exclude ();
while ((c = getopt_long (argc, argv, shortopts, longopts, 0)) != -1)
{
switch (c)
{
case 0:
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (! ISDIGIT (prev))
ocontext = c - '0';
else if (LIN_MAX / 10 < ocontext
|| ((ocontext = 10 * ocontext + c - '0') < 0))
ocontext = LIN_MAX;
break;
case 'a':
text = 1;
break;
case 'b':
if (ignore_white_space < IGNORE_SPACE_CHANGE)
ignore_white_space = IGNORE_SPACE_CHANGE;
break;
case 'B':
ignore_blank_lines = 1;
break;
case 'C':
case 'U':
{
if (optarg)
{
numval = strtoumax (optarg, &numend, 10);
if (*numend)
try_help ("invalid context length `%s'", optarg);
if (LIN_MAX < numval)
numval = LIN_MAX;
}
else
numval = 3;
specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
if (context < numval)
context = numval;
explicit_context = 1;
}
break;
case 'c':
specify_style (OUTPUT_CONTEXT);
if (context < 3)
context = 3;
break;
case 'd':
minimal = 1;
break;
case 'D':
specify_style (OUTPUT_IFDEF);
{
static char const C_ifdef_group_formats[] =
"%%=%c#ifndef %s\n%%<#endif /* ! %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
char *b = xmalloc (sizeof C_ifdef_group_formats
+ 7 * strlen (optarg) - 14
- 8 );
sprintf (b, C_ifdef_group_formats,
0,
optarg, optarg, 0,
optarg, optarg, 0,
optarg, optarg, optarg);
for (i = 0; i < sizeof group_format / sizeof *group_format; i++)
{
specify_value (&group_format[i], b, "-D");
b += strlen (b) + 1;
}
}
break;
case 'e':
specify_style (OUTPUT_ED);
break;
case 'E':
if (ignore_white_space < IGNORE_TAB_EXPANSION)
ignore_white_space = IGNORE_TAB_EXPANSION;
break;
case 'f':
specify_style (OUTPUT_FORWARD_ED);
break;
case 'F':
add_regexp (&function_regexp_list, optarg);
break;
case 'h':
break;
case 'H':
speed_large_files = 1;
break;
case 'i':
ignore_case = 1;
break;
case 'I':
add_regexp (&ignore_regexp_list, optarg);
break;
case 'l':
if (!pr_program[0])
try_help ("pagination not supported on this host", 0);
paginate = 1;
#ifdef SIGCHLD
signal (SIGCHLD, SIG_DFL);
#endif
break;
case 'L':
if (!file_label[0])
file_label[0] = optarg;
else if (!file_label[1])
file_label[1] = optarg;
else
fatal ("too many file label options");
break;
case 'n':
specify_style (OUTPUT_RCS);
break;
case 'N':
new_file = 1;
break;
case 'p':
show_c_function = 1;
add_regexp (&function_regexp_list, "^[[:alpha:]$_]");
break;
case 'P':
unidirectional_new_file = 1;
break;
case 'q':
brief = 1;
break;
case 'r':
recursive = 1;
break;
case 's':
report_identical_files = 1;
break;
case 'S':
specify_value (&starting_file, optarg, "-S");
break;
case 't':
expand_tabs = 1;
break;
case 'T':
initial_tab = 1;
break;
case 'u':
specify_style (OUTPUT_UNIFIED);
if (context < 3)
context = 3;
break;
case 'v':
printf ("diff %s\n%s\n\n%s\n\n%s\n",
version_string, copyright_string,
_(free_software_msgid), _(authorship_msgid));
check_stdout ();
return EXIT_SUCCESS;
case 'w':
ignore_white_space = IGNORE_ALL_SPACE;
break;
case 'x':
add_exclude (excluded, optarg, exclude_options ());
break;
case 'X':
if (add_exclude_file (add_exclude, excluded, optarg,
exclude_options (), '\n'))
pfatal_with_name (optarg);
break;
case 'y':
specify_style (OUTPUT_SDIFF);
break;
case 'W':
numval = strtoumax (optarg, &numend, 10);
if (! (0 < numval && numval <= INT_MAX) || *numend)
try_help ("invalid width `%s'", optarg);
if (width != numval)
{
if (width)
fatal ("conflicting width options");
width = numval;
}
break;
case BINARY_OPTION:
#if HAVE_SETMODE_DOS
binary = 1;
set_binary_mode (STDOUT_FILENO, 1);
#endif
break;
case FROM_FILE_OPTION:
specify_value (&from_file, optarg, "--from-file");
break;
case HELP_OPTION:
usage ();
check_stdout ();
return EXIT_SUCCESS;
case HORIZON_LINES_OPTION:
numval = strtoumax (optarg, &numend, 10);
if (*numend)
try_help ("invalid horizon length `%s'", optarg);
horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
break;
case IGNORE_FILE_NAME_CASE_OPTION:
ignore_file_name_case = 1;
break;
case INHIBIT_HUNK_MERGE_OPTION:
break;
case LEFT_COLUMN_OPTION:
left_column = 1;
break;
case LINE_FORMAT_OPTION:
specify_style (OUTPUT_IFDEF);
for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
specify_value (&line_format[i], optarg, "--line-format");
break;
case NO_IGNORE_FILE_NAME_CASE_OPTION:
ignore_file_name_case = 0;
break;
case NORMAL_OPTION:
specify_style (OUTPUT_NORMAL);
break;
case SDIFF_MERGE_ASSIST_OPTION:
specify_style (OUTPUT_SDIFF);
sdiff_merge_assist = 1;
break;
case STRIP_TRAILING_CR_OPTION:
strip_trailing_cr = 1;
break;
case SUPPRESS_COMMON_LINES_OPTION:
suppress_common_lines = 1;
break;
case TO_FILE_OPTION:
specify_value (&to_file, optarg, "--to-file");
break;
case UNCHANGED_LINE_FORMAT_OPTION:
case OLD_LINE_FORMAT_OPTION:
case NEW_LINE_FORMAT_OPTION:
specify_style (OUTPUT_IFDEF);
c -= UNCHANGED_LINE_FORMAT_OPTION;
specify_value (&line_format[c], optarg, line_format_option[c]);
break;
case UNCHANGED_GROUP_FORMAT_OPTION:
case OLD_GROUP_FORMAT_OPTION:
case NEW_GROUP_FORMAT_OPTION:
case CHANGED_GROUP_FORMAT_OPTION:
specify_style (OUTPUT_IFDEF);
c -= UNCHANGED_GROUP_FORMAT_OPTION;
specify_value (&group_format[c], optarg, group_format_option[c]);
break;
default:
try_help (0, 0);
}
prev = c;
}
if (output_style == OUTPUT_UNSPECIFIED)
{
if (show_c_function)
{
specify_style (OUTPUT_CONTEXT);
if (ocontext < 0)
context = 3;
}
else
specify_style (OUTPUT_NORMAL);
}
if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
time_format = "%Y-%m-%d %H:%M:%S.%N %z";
else
{
time_format = "%a %b %e %T %Y";
}
if (0 <= ocontext)
{
bool modern_usage = 200112 <= posix2_version ();
if ((output_style == OUTPUT_CONTEXT
|| output_style == OUTPUT_UNIFIED)
&& (context < ocontext
|| (ocontext < context && ! explicit_context)))
{
if (modern_usage)
{
error (0, 0,
_("`-%ld' option is obsolete; use `-%c %ld'"),
(long) ocontext,
output_style == OUTPUT_CONTEXT ? 'C' : 'U',
(long) ocontext);
try_help (0, 0);
}
context = ocontext;
}
else
{
if (modern_usage)
{
error (0, 0, _("`-%ld' option is obsolete; omit it"),
(long) ocontext);
try_help (0, 0);
}
}
}
{
unsigned int t = expand_tabs ? 1 : TAB_WIDTH;
int w = width ? width : 130;
int off = (w + t + GUTTER_WIDTH_MINIMUM) / (2 * t) * t;
sdiff_half_width = MAX (0, MIN (off - GUTTER_WIDTH_MINIMUM, w - off)),
sdiff_column2_offset = sdiff_half_width ? off : w;
}
if (horizon_lines < context)
horizon_lines = context;
summarize_regexp_list (&function_regexp_list);
summarize_regexp_list (&ignore_regexp_list);
if (output_style == OUTPUT_IFDEF)
{
for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
if (!line_format[i])
line_format[i] = "%l\n";
if (!group_format[OLD])
group_format[OLD]
= group_format[CHANGED] ? group_format[CHANGED] : "%<";
if (!group_format[NEW])
group_format[NEW]
= group_format[CHANGED] ? group_format[CHANGED] : "%>";
if (!group_format[UNCHANGED])
group_format[UNCHANGED] = "%=";
if (!group_format[CHANGED])
group_format[CHANGED] = concat (group_format[OLD],
group_format[NEW], "");
}
no_diff_means_no_output =
(output_style == OUTPUT_IFDEF ?
(!*group_format[UNCHANGED]
|| (strcmp (group_format[UNCHANGED], "%=") == 0
&& !*line_format[UNCHANGED]))
: (output_style != OUTPUT_SDIFF) | suppress_common_lines);
files_can_be_treated_as_binary =
(brief
& ~ (ignore_blank_lines | ignore_case | strip_trailing_cr
| (ignore_regexp_list.regexps || ignore_white_space)));
switch_string = option_list (argv + 1, optind - 1);
if (from_file)
{
if (to_file)
fatal ("--from-file and --to-file both specified");
else
for (; optind < argc; optind++)
{
int status = compare_files ((struct comparison *) 0,
from_file, argv[optind]);
if (exit_status < status)
exit_status = status;
}
}
else
{
if (to_file)
for (; optind < argc; optind++)
{
int status = compare_files ((struct comparison *) 0,
argv[optind], to_file);
if (exit_status < status)
exit_status = status;
}
else
{
if (argc - optind != 2)
{
if (argc - optind < 2)
try_help ("missing operand after `%s'", argv[argc - 1]);
else
try_help ("extra operand `%s'", argv[optind + 2]);
}
exit_status = compare_files ((struct comparison *) 0,
argv[optind], argv[optind + 1]);
}
}
print_message_queue ();
check_stdout ();
exit (exit_status);
return exit_status;
}
static void
add_regexp (struct regexp_list *reglist, char const *pattern)
{
size_t patlen = strlen (pattern);
char const *m = re_compile_pattern (pattern, patlen, reglist->buf);
if (m != 0)
error (0, 0, "%s: %s", pattern, m);
else
{
char *regexps = reglist->regexps;
size_t len = reglist->len;
bool multiple_regexps = reglist->multiple_regexps = regexps != 0;
size_t newlen = reglist->len = len + 2 * multiple_regexps + patlen;
size_t size = reglist->size;
if (size <= newlen)
{
if (!size)
size = 1;
do size *= 2;
while (size <= newlen);
reglist->size = size;
reglist->regexps = regexps = xrealloc (regexps, size);
}
if (multiple_regexps)
{
regexps[len++] = '\\';
regexps[len++] = '|';
}
memcpy (regexps + len, pattern, patlen + 1);
}
}
static void
summarize_regexp_list (struct regexp_list *reglist)
{
if (reglist->regexps)
{
reglist->buf->fastmap = xmalloc (1 << CHAR_BIT);
if (reglist->multiple_regexps)
{
char const *m = re_compile_pattern (reglist->regexps, reglist->len,
reglist->buf);
if (m != 0)
error (EXIT_TROUBLE, 0, "%s: %s", reglist->regexps, m);
}
}
}
static void
try_help (char const *reason_msgid, char const *operand)
{
if (reason_msgid)
error (0, 0, _(reason_msgid), operand);
error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
program_name);
abort ();
}
static void
check_stdout (void)
{
if (ferror (stdout))
fatal ("write failed");
else if (fclose (stdout) != 0)
pfatal_with_name (_("standard output"));
}
static char const * const option_help_msgid[] = {
N_("Compare files line by line."),
"",
N_("-i --ignore-case Ignore case differences in file contents."),
N_("--ignore-file-name-case Ignore case when comparing file names."),
N_("--no-ignore-file-name-case Consider case when comparing file names."),
N_("-E --ignore-tab-expansion Ignore changes due to tab expansion."),
N_("-b --ignore-space-change Ignore changes in the amount of white space."),
N_("-w --ignore-all-space Ignore all white space."),
N_("-B --ignore-blank-lines Ignore changes whose lines are all blank."),
N_("-I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE."),
N_("--strip-trailing-cr Strip trailing carriage return on input."),
#if HAVE_SETMODE_DOS
N_("--binary Read and write data in binary mode."),
#endif
N_("-a --text Treat all files as text."),
"",
N_("-c -C NUM --context[=NUM] Output NUM (default 3) lines of copied context.\n\
-u -U NUM --unified[=NUM] Output NUM (default 3) lines of unified context.\n\
--label LABEL Use LABEL instead of file name.\n\
-p --show-c-function Show which C function each change is in.\n\
-F RE --show-function-line=RE Show the most recent line matching RE."),
N_("-q --brief Output only whether files differ."),
N_("-e --ed Output an ed script."),
N_("--normal Output a normal diff."),
N_("-n --rcs Output an RCS format diff."),
N_("-y --side-by-side Output in two columns.\n\
-W NUM --width=NUM Output at most NUM (default 130) print columns.\n\
--left-column Output only the left column of common lines.\n\
--suppress-common-lines Do not output common lines."),
N_("-D NAME --ifdef=NAME Output merged file to show `#ifdef NAME' diffs."),
N_("--GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT."),
N_("--line-format=LFMT Similar, but format all input lines with LFMT."),
N_("--LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT."),
N_(" LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'."),
N_(" GFMT may contain:\n\
%< lines from FILE1\n\
%> lines from FILE2\n\
%= lines common to FILE1 and FILE2\n\
%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n\
LETTERs are as follows for new group, lower case for old group:\n\
F first line number\n\
L last line number\n\
N number of lines = L-F+1\n\
E F-1\n\
M L+1"),
N_(" LFMT may contain:\n\
%L contents of line\n\
%l contents of line, excluding any trailing newline\n\
%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number"),
N_(" Either GFMT or LFMT may contain:\n\
%% %\n\
%c'C' the single character C\n\
%c'\\OOO' the character with octal code OOO"),
"",
N_("-l --paginate Pass the output through `pr' to paginate it."),
N_("-t --expand-tabs Expand tabs to spaces in output."),
N_("-T --initial-tab Make tabs line up by prepending a tab."),
"",
N_("-r --recursive Recursively compare any subdirectories found."),
N_("-N --new-file Treat absent files as empty."),
N_("--unidirectional-new-file Treat absent first files as empty."),
N_("-s --report-identical-files Report when two files are the same."),
N_("-x PAT --exclude=PAT Exclude files that match PAT."),
N_("-X FILE --exclude-from=FILE Exclude files that match any pattern in FILE."),
N_("-S FILE --starting-file=FILE Start with FILE when comparing directories."),
N_("--from-file=FILE1 Compare FILE1 to all operands. FILE1 can be a directory."),
N_("--to-file=FILE2 Compare all operands to FILE2. FILE2 can be a directory."),
"",
N_("--horizon-lines=NUM Keep NUM lines of the common prefix and suffix."),
N_("-d --minimal Try hard to find a smaller set of changes."),
N_("--speed-large-files Assume large files and many scattered small changes."),
"",
N_("-v --version Output version info."),
N_("--help Output this help."),
"",
N_("FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'."),
N_("If --from-file or --to-file is given, there are no restrictions on FILES."),
N_("If a FILE is `-', read standard input."),
"",
N_("Report bugs to <bug-gnu-utils@gnu.org>."),
0
};
static void
usage (void)
{
char const * const *p;
printf (_("Usage: %s [OPTION]... FILES\n"), program_name);
for (p = option_help_msgid; *p; p++)
{
if (!**p)
putchar ('\n');
else
{
char const *msg = _(*p);
char const *nl;
while ((nl = strchr (msg, '\n')))
{
int msglen = nl + 1 - msg;
printf (" %.*s", msglen, msg);
msg = nl + 1;
}
printf (" %s\n" + 2 * (*msg != ' ' && *msg != '-'), msg);
}
}
}
static void
specify_value (char const **var, char const *value, char const *option)
{
if (*var && strcmp (*var, value) != 0)
{
error (0, 0, _("conflicting %s option value `%s'"), option, value);
try_help (0, 0);
}
*var = value;
}
static void
specify_style (enum output_style style)
{
if (output_style != style)
{
if (output_style != OUTPUT_UNSPECIFIED)
try_help ("conflicting output style options", 0);
output_style = style;
}
}
static char const *
filetype (struct stat const *st)
{
if (S_ISREG (st->st_mode))
return st->st_size == 0 ? _("regular empty file") : _("regular file");
if (S_ISDIR (st->st_mode)) return _("directory");
#ifdef S_ISBLK
if (S_ISBLK (st->st_mode)) return _("block special file");
#endif
#ifdef S_ISCHR
if (S_ISCHR (st->st_mode)) return _("character special file");
#endif
#ifdef S_ISFIFO
if (S_ISFIFO (st->st_mode)) return _("fifo");
#endif
#ifdef S_ISSOCK
if (S_ISSOCK (st->st_mode)) return _("socket");
#endif
#ifdef S_TYPEISMQ
if (S_TYPEISMQ (st)) return _("message queue");
#endif
#ifdef S_TYPEISSEM
if (S_TYPEISSEM (st)) return _("semaphore");
#endif
#ifdef S_TYPEISSHM
if (S_TYPEISSHM (st)) return _("shared memory object");
#endif
#ifdef S_TYPEISTMO
if (S_TYPEISTMO (st)) return _("typed memory object");
#endif
return _("weird file");
}
static void
set_mtime_to_now (struct stat *st)
{
#ifdef ST_MTIM_NSEC
# if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME
if (clock_gettime (CLOCK_REALTIME, &st->st_mtim) == 0)
return;
# endif
# if HAVE_GETTIMEOFDAY
{
struct timeval timeval;
if (gettimeofday (&timeval, NULL) == 0)
{
st->st_mtime = timeval.tv_sec;
st->st_mtim.ST_MTIM_NSEC = timeval.tv_usec * 1000;
return;
}
}
# endif
#endif
time (&st->st_mtime);
}
static int
compare_files (struct comparison const *parent,
char const *name0,
char const *name1)
{
struct comparison cmp;
#define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
register int f;
int status = EXIT_SUCCESS;
bool same_files;
char *free0, *free1;
if (! ((name0 && name1)
|| (unidirectional_new_file && name1)
|| new_file))
{
char const *name = name0 == 0 ? name1 : name0;
char const *dir = parent->file[name0 == 0].name;
message ("Only in %s: %s\n", dir, name);
return EXIT_FAILURE;
}
memset (cmp.file, 0, sizeof cmp.file);
cmp.parent = parent;
#define NONEXISTENT (-1)
#define UNOPENED (-2)
#define ERRNO_ENCODE(errno) (-3 - (errno))
#define ERRNO_DECODE(desc) (-3 - (desc))
cmp.file[0].desc = name0 == 0 ? NONEXISTENT : UNOPENED;
cmp.file[1].desc = name1 == 0 ? NONEXISTENT : UNOPENED;
if (name0 == 0)
name0 = name1;
if (name1 == 0)
name1 = name0;
if (!parent)
{
free0 = 0;
free1 = 0;
cmp.file[0].name = name0;
cmp.file[1].name = name1;
}
else
{
cmp.file[0].name = free0
= dir_file_pathname (parent->file[0].name, name0);
cmp.file[1].name = free1
= dir_file_pathname (parent->file[1].name, name1);
}
for (f = 0; f < 2; f++)
{
if (cmp.file[f].desc != NONEXISTENT)
{
if (f && file_name_cmp (cmp.file[f].name, cmp.file[0].name) == 0)
{
cmp.file[f].desc = cmp.file[0].desc;
cmp.file[f].stat = cmp.file[0].stat;
}
else if (strcmp (cmp.file[f].name, "-") == 0)
{
cmp.file[f].desc = STDIN_FILENO;
if (fstat (STDIN_FILENO, &cmp.file[f].stat) != 0)
cmp.file[f].desc = ERRNO_ENCODE (errno);
else
{
if (S_ISREG (cmp.file[f].stat.st_mode))
{
off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
if (pos < 0)
cmp.file[f].desc = ERRNO_ENCODE (errno);
else
cmp.file[f].stat.st_size =
MAX (0, cmp.file[f].stat.st_size - pos);
}
set_mtime_to_now (&cmp.file[f].stat);
}
}
else if (stat (cmp.file[f].name, &cmp.file[f].stat) != 0)
cmp.file[f].desc = ERRNO_ENCODE (errno);
}
}
if (! parent)
{
if ((new_file | unidirectional_new_file)
&& cmp.file[0].desc == ERRNO_ENCODE (ENOENT)
&& cmp.file[1].desc == UNOPENED)
cmp.file[0].desc = NONEXISTENT;
if (new_file
&& cmp.file[0].desc == UNOPENED
&& cmp.file[1].desc == ERRNO_ENCODE (ENOENT))
cmp.file[1].desc = NONEXISTENT;
}
for (f = 0; f < 2; f++)
if (cmp.file[f].desc == NONEXISTENT)
cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
for (f = 0; f < 2; f++)
{
int e = ERRNO_DECODE (cmp.file[f].desc);
if (0 <= e)
{
errno = e;
perror_with_name (cmp.file[f].name);
status = EXIT_TROUBLE;
}
}
if (status == EXIT_SUCCESS && ! parent && DIR_P (0) != DIR_P (1))
{
int fnm_arg = DIR_P (0);
int dir_arg = 1 - fnm_arg;
char const *fnm = cmp.file[fnm_arg].name;
char const *dir = cmp.file[dir_arg].name;
char const *filename = cmp.file[dir_arg].name = free0
= dir_file_pathname (dir, base_name (fnm));
if (strcmp (fnm, "-") == 0)
fatal ("cannot compare `-' to a directory");
if (stat (filename, &cmp.file[dir_arg].stat) != 0)
{
perror_with_name (filename);
status = EXIT_TROUBLE;
}
}
if (status != EXIT_SUCCESS)
{
}
else if ((same_files
= (cmp.file[0].desc != NONEXISTENT
&& cmp.file[1].desc != NONEXISTENT
&& 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
&& same_file_attributes (&cmp.file[0].stat,
&cmp.file[1].stat)))
&& no_diff_means_no_output)
{
}
else if (DIR_P (0) & DIR_P (1))
{
if (output_style == OUTPUT_IFDEF)
fatal ("-D option not supported with directories");
if (parent && !recursive)
{
message ("Common subdirectories: %s and %s\n",
cmp.file[0].name, cmp.file[1].name);
}
else
status = diff_dirs (&cmp, compare_files);
}
else if ((DIR_P (0) | DIR_P (1))
|| (parent
&& (! S_ISREG (cmp.file[0].stat.st_mode)
|| ! S_ISREG (cmp.file[1].stat.st_mode))))
{
if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT)
{
if ((DIR_P (0) | DIR_P (1))
&& recursive
&& (new_file
|| (unidirectional_new_file
&& cmp.file[0].desc == NONEXISTENT)))
status = diff_dirs (&cmp, compare_files);
else
{
char const *dir
= parent->file[cmp.file[0].desc == NONEXISTENT].name;
message ("Only in %s: %s\n", dir, name0);
status = EXIT_FAILURE;
}
}
else
{
message5 ("File %s is a %s while file %s is a %s\n",
file_label[0] ? file_label[0] : cmp.file[0].name,
filetype (&cmp.file[0].stat),
file_label[1] ? file_label[1] : cmp.file[1].name,
filetype (&cmp.file[1].stat));
status = EXIT_FAILURE;
}
}
else if (files_can_be_treated_as_binary
&& cmp.file[0].stat.st_size != cmp.file[1].stat.st_size
&& (cmp.file[0].desc == NONEXISTENT
|| S_ISREG (cmp.file[0].stat.st_mode))
&& (cmp.file[1].desc == NONEXISTENT
|| S_ISREG (cmp.file[1].stat.st_mode)))
{
message ("Files %s and %s differ\n",
file_label[0] ? file_label[0] : cmp.file[0].name,
file_label[1] ? file_label[1] : cmp.file[1].name);
status = EXIT_FAILURE;
}
else
{
if (cmp.file[0].desc == UNOPENED)
if ((cmp.file[0].desc = open (cmp.file[0].name, O_RDONLY, 0)) < 0)
{
perror_with_name (cmp.file[0].name);
status = EXIT_TROUBLE;
}
if (cmp.file[1].desc == UNOPENED)
{
if (same_files)
cmp.file[1].desc = cmp.file[0].desc;
else if ((cmp.file[1].desc = open (cmp.file[1].name, O_RDONLY, 0))
< 0)
{
perror_with_name (cmp.file[1].name);
status = EXIT_TROUBLE;
}
}
#if HAVE_SETMODE_DOS
if (binary)
for (f = 0; f < 2; f++)
if (0 <= cmp.file[f].desc)
set_binary_mode (cmp.file[f].desc, 1);
#endif
if (status == EXIT_SUCCESS)
status = diff_2_files (&cmp);
if (0 <= cmp.file[0].desc && close (cmp.file[0].desc) != 0)
{
perror_with_name (cmp.file[0].name);
status = EXIT_TROUBLE;
}
if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
&& close (cmp.file[1].desc) != 0)
{
perror_with_name (cmp.file[1].name);
status = EXIT_TROUBLE;
}
}
if (status == EXIT_SUCCESS)
{
if (report_identical_files && !DIR_P (0))
message ("Files %s and %s are identical\n",
file_label[0] ? file_label[0] : cmp.file[0].name,
file_label[1] ? file_label[1] : cmp.file[1].name);
}
else
{
if (fflush (stdout) != 0)
pfatal_with_name (_("standard output"));
}
if (free0)
free (free0);
if (free1)
free (free1);
return status;
}