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