summaryrefslogtreecommitdiff
path: root/diff/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'diff/diff.c')
-rw-r--r--diff/diff.c1267
1 files changed, 1267 insertions, 0 deletions
diff --git a/diff/diff.c b/diff/diff.c
new file mode 100644
index 0000000..f5d82d5
--- /dev/null
+++ b/diff/diff.c
@@ -0,0 +1,1267 @@
+/* GNU DIFF entry routine.
+ Copyright (C) 1988, 1989, 1992, 1993, 1994, 1997, 1998 Free Software Foundation, Inc.
+
+This file is part of GNU DIFF.
+
+GNU DIFF is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU DIFF is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+*/
+
+/* GNU DIFF was written by Mike Haertel, David Hayes,
+ Richard Stallman, Len Tower, and Paul Eggert. */
+
+#define GDIFF_MAIN
+#include "diff.h"
+#include <signal.h>
+#include "error.h"
+#include "getopt.h"
+
+#ifdef HAVE_FNMATCH
+# include <fnmatch.h> /* This is supposed to be available on Posix systems */
+#else /* HAVE_FNMATCH */
+# include "fnmatch.h" /* Our substitute */
+#endif /* HAVE_FNMATCH */
+
+#ifndef DEFAULT_WIDTH
+#define DEFAULT_WIDTH 130
+#endif
+
+#ifndef GUTTER_WIDTH_MINIMUM
+#define GUTTER_WIDTH_MINIMUM 3
+#endif
+
+/* diff.c has a real initialize_main function. */
+#ifdef initialize_main
+#undef initialize_main
+#endif
+
+static char const *filetype PARAMS((struct stat const *));
+static char *option_list PARAMS((char **, int));
+static int add_exclude_file PARAMS((char const *));
+static int ck_atoi PARAMS((char const *, int *));
+static int compare_files PARAMS((char const *, char const *, char const *, char const *, int));
+static int specify_format PARAMS((char **, char *));
+static void add_exclude PARAMS((char const *));
+static void add_regexp PARAMS((struct regexp_list **, char const *));
+static void specify_style PARAMS((enum output_style));
+static int try_help PARAMS((char const *));
+static void check_output PARAMS((FILE *));
+static void usage PARAMS((void));
+static void initialize_main PARAMS((int *, char ***));
+
+/* Nonzero for -r: if comparing two directories,
+ compare their common subdirectories recursively. */
+
+static int recursive;
+
+/* For debugging: don't do discard_confusing_lines. */
+
+int no_discards;
+
+#if HAVE_SETMODE
+/* I/O mode: nonzero only if using binary input/output. */
+static int binary_I_O;
+#endif
+
+/* Return a string containing the command options with which diff was invoked.
+ Spaces appear between what were separate ARGV-elements.
+ There is a space at the beginning but none at the end.
+ If there were no options, the result is an empty string.
+
+ Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
+ the length of that vector. */
+
+static char *
+option_list (optionvec, count)
+ char **optionvec; /* Was `vector', but that collides on Alliant. */
+ int count;
+{
+ int i;
+ size_t length = 0;
+ char *result;
+
+ for (i = 0; i < count; i++)
+ length += strlen (optionvec[i]) + 1;
+
+ result = xmalloc (length + 1);
+ result[0] = 0;
+
+ for (i = 0; i < count; i++)
+ {
+ strcat (result, " ");
+ strcat (result, optionvec[i]);
+ }
+
+ return result;
+}
+
+/* Convert STR to a positive integer, storing the result in *OUT.
+ If STR is not a valid integer, return -1 (otherwise 0). */
+static int
+ck_atoi (str, out)
+ char const *str;
+ int *out;
+{
+ char const *p;
+ for (p = str; *p; p++)
+ if (*p < '0' || *p > '9')
+ return -1;
+
+ *out = atoi (optarg);
+ return 0;
+}
+
+/* Keep track of excluded file name patterns. */
+
+static char const **exclude;
+static int exclude_alloc, exclude_count;
+
+int
+excluded_filename (f)
+ char const *f;
+{
+ int i;
+ for (i = 0; i < exclude_count; i++)
+ if (fnmatch (exclude[i], f, 0) == 0)
+ return 1;
+ return 0;
+}
+
+static void
+add_exclude (pattern)
+ char const *pattern;
+{
+ if (exclude_alloc <= exclude_count)
+ exclude = (char const **)
+ (exclude_alloc == 0
+ ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
+ : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
+
+ exclude[exclude_count++] = pattern;
+}
+
+static int
+add_exclude_file (name)
+ char const *name;
+{
+ struct file_data f;
+ char *p, *q, *lim;
+
+ f.name = optarg;
+ f.desc = (strcmp (optarg, "-") == 0
+ ? STDIN_FILENO
+ : open (optarg, O_RDONLY, 0));
+ if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
+ return -1;
+
+ sip (&f, 1);
+ slurp (&f);
+
+ for (p = f.buffer, lim = p + f.buffered_chars; p < lim; p = q)
+ {
+ q = (char *) memchr (p, '\n', lim - p);
+ if (!q)
+ q = lim;
+ *q++ = 0;
+ add_exclude (p);
+ }
+
+ return close (f.desc);
+}
+
+/* The numbers 129- that appear in the fourth element of some entries
+ tell the big switch in `diff_run' how to process those options. */
+
+static struct option const longopts[] =
+{
+ {"ignore-blank-lines", 0, 0, 'B'},
+ {"context", 2, 0, 'C'},
+ {"ifdef", 1, 0, 'D'},
+ {"show-function-line", 1, 0, 'F'},
+ {"speed-large-files", 0, 0, 'H'},
+ {"ignore-matching-lines", 1, 0, 'I'},
+ {"label", 1, 0, 'L'},
+ {"file-label", 1, 0, 'L'}, /* An alias, no longer recommended */
+ {"new-file", 0, 0, 'N'},
+ {"entire-new-file", 0, 0, 'N'}, /* An alias, no longer recommended */
+ {"unidirectional-new-file", 0, 0, 'P'},
+ {"starting-file", 1, 0, 'S'},
+ {"initial-tab", 0, 0, 'T'},
+ {"width", 1, 0, 'W'},
+ {"text", 0, 0, 'a'},
+ {"ascii", 0, 0, 'a'}, /* An alias, no longer recommended */
+ {"ignore-space-change", 0, 0, 'b'},
+ {"minimal", 0, 0, 'd'},
+ {"ed", 0, 0, 'e'},
+ {"forward-ed", 0, 0, 'f'},
+ {"ignore-case", 0, 0, 'i'},
+ {"paginate", 0, 0, 'l'},
+ {"print", 0, 0, 'l'}, /* An alias, no longer recommended */
+ {"rcs", 0, 0, 'n'},
+ {"show-c-function", 0, 0, 'p'},
+ {"brief", 0, 0, 'q'},
+ {"recursive", 0, 0, 'r'},
+ {"report-identical-files", 0, 0, 's'},
+ {"expand-tabs", 0, 0, 't'},
+ {"version", 0, 0, 'v'},
+ {"ignore-all-space", 0, 0, 'w'},
+ {"exclude", 1, 0, 'x'},
+ {"exclude-from", 1, 0, 'X'},
+ {"side-by-side", 0, 0, 'y'},
+ {"unified", 2, 0, 'U'},
+ {"left-column", 0, 0, 129},
+ {"suppress-common-lines", 0, 0, 130},
+ {"sdiff-merge-assist", 0, 0, 131},
+ {"old-line-format", 1, 0, 132},
+ {"new-line-format", 1, 0, 133},
+ {"unchanged-line-format", 1, 0, 134},
+ {"line-format", 1, 0, 135},
+ {"old-group-format", 1, 0, 136},
+ {"new-group-format", 1, 0, 137},
+ {"unchanged-group-format", 1, 0, 138},
+ {"changed-group-format", 1, 0, 139},
+ {"horizon-lines", 1, 0, 140},
+ {"help", 0, 0, 141},
+ {"binary", 0, 0, 142},
+ {0, 0, 0, 0}
+};
+
+
+
+int
+diff_run (argc, argv, out, callbacks_arg)
+ int argc;
+ char *argv[];
+ const char *out;
+ const struct diff_callbacks *callbacks_arg;
+{
+ int val;
+ int c;
+ int prev = -1;
+ int width = DEFAULT_WIDTH;
+ int show_c_function = 0;
+ int optind_old;
+ int opened_file = 0;
+
+ callbacks = callbacks_arg;
+
+ /* Do our initializations. */
+ initialize_main (&argc, &argv);
+ optind_old = optind;
+ optind = 0;
+
+ /* Set the jump buffer, so that diff may abort execution without
+ terminating the process. */
+ val = setjmp (diff_abort_buf);
+ if (val != 0)
+ {
+ optind = optind_old;
+ if (opened_file)
+ fclose (outfile);
+ return val;
+ }
+
+ /* Decode the options. */
+ while ((c = getopt_long (argc, argv,
+ "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
+ longopts, 0)) != EOF)
+ {
+ switch (c)
+ {
+ /* All digits combine in decimal to specify the context-size. */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ if (context == -1)
+ context = 0;
+ /* If a context length has already been specified,
+ more digits allowed only if they follow right after the others.
+ Reject two separate runs of digits, or digits after -C. */
+ else if (prev < '0' || prev > '9')
+ fatal ("context length specified twice");
+
+ context = context * 10 + c - '0';
+ break;
+
+ case 'a':
+ /* Treat all files as text files; never treat as binary. */
+ always_text_flag = 1;
+ break;
+
+ case 'b':
+ /* Ignore changes in amount of white space. */
+ ignore_space_change_flag = 1;
+ ignore_some_changes = 1;
+ ignore_some_line_changes = 1;
+ break;
+
+ case 'B':
+ /* Ignore changes affecting only blank lines. */
+ ignore_blank_lines_flag = 1;
+ ignore_some_changes = 1;
+ break;
+
+ case 'C': /* +context[=lines] */
+ case 'U': /* +unified[=lines] */
+ if (optarg)
+ {
+ if (context >= 0)
+ fatal ("context length specified twice");
+
+ if (ck_atoi (optarg, &context))
+ fatal ("invalid context length argument");
+ }
+
+ /* Falls through. */
+ case 'c':
+ /* Make context-style output. */
+ specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
+ break;
+
+ case 'd':
+ /* Don't discard lines. This makes things slower (sometimes much
+ slower) but will find a guaranteed minimal set of changes. */
+ no_discards = 1;
+ break;
+
+ case 'D':
+ /* Make merged #ifdef output. */
+ specify_style (OUTPUT_IFDEF);
+ {
+ int i, err = 0;
+ static char const C_ifdef_group_formats[] =
+ "#ifndef %s\n%%<#endif /* not %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c%%=%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
+ char *b = xmalloc (sizeof (C_ifdef_group_formats)
+ + 7 * strlen(optarg) - 14 /* 7*"%s" */
+ - 8 /* 5*"%%" + 3*"%c" */);
+ sprintf (b, C_ifdef_group_formats,
+ optarg, optarg, 0,
+ optarg, optarg, 0, 0,
+ optarg, optarg, optarg);
+ for (i = 0; i < 4; i++)
+ {
+ err |= specify_format (&group_format[i], b);
+ b += strlen (b) + 1;
+ }
+ if (err)
+ diff_error ("conflicting #ifdef formats", 0, 0);
+ }
+ break;
+
+ case 'e':
+ /* Make output that is a valid `ed' script. */
+ specify_style (OUTPUT_ED);
+ break;
+
+ case 'f':
+ /* Make output that looks vaguely like an `ed' script
+ but has changes in the order they appear in the file. */
+ specify_style (OUTPUT_FORWARD_ED);
+ break;
+
+ case 'F':
+ /* Show, for each set of changes, the previous line that
+ matches the specified regexp. Currently affects only
+ context-style output. */
+ add_regexp (&function_regexp_list, optarg);
+ break;
+
+ case 'h':
+ /* Split the files into chunks of around 1500 lines
+ for faster processing. Usually does not change the result.
+
+ This currently has no effect. */
+ break;
+
+ case 'H':
+ /* Turn on heuristics that speed processing of large files
+ with a small density of changes. */
+ heuristic = 1;
+ break;
+
+ case 'i':
+ /* Ignore changes in case. */
+ ignore_case_flag = 1;
+ ignore_some_changes = 1;
+ ignore_some_line_changes = 1;
+ break;
+
+ case 'I':
+ /* Ignore changes affecting only lines that match the
+ specified regexp. */
+ add_regexp (&ignore_regexp_list, optarg);
+ ignore_some_changes = 1;
+ break;
+
+ case 'l':
+ /* Pass the output through `pr' to paginate it. */
+ paginate_flag = 1;
+#if !defined(SIGCHLD) && defined(SIGCLD)
+#define SIGCHLD SIGCLD
+#endif
+#ifdef SIGCHLD
+ /* Pagination requires forking and waiting, and
+ System V fork+wait does not work if SIGCHLD is ignored. */
+ signal (SIGCHLD, SIG_DFL);
+#endif
+ break;
+
+ case 'L':
+ /* Specify file labels for `-c' output headers. */
+ 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':
+ /* Output RCS-style diffs, like `-f' except that each command
+ specifies the number of lines affected. */
+ specify_style (OUTPUT_RCS);
+ break;
+
+ case 'N':
+ /* When comparing directories, if a file appears only in one
+ directory, treat it as present but empty in the other. */
+ entire_new_file_flag = 1;
+ break;
+
+ case 'p':
+ /* Make context-style output and show name of last C function. */
+ show_c_function = 1;
+ add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
+ break;
+
+ case 'P':
+ /* When comparing directories, if a file appears only in
+ the second directory of the two,
+ treat it as present but empty in the other. */
+ unidirectional_new_file_flag = 1;
+ break;
+
+ case 'q':
+ no_details_flag = 1;
+ break;
+
+ case 'r':
+ /* When comparing directories,
+ recursively compare any subdirectories found. */
+ recursive = 1;
+ break;
+
+ case 's':
+ /* Print a message if the files are the same. */
+ print_file_same_flag = 1;
+ break;
+
+ case 'S':
+ /* When comparing directories, start with the specified
+ file name. This is used for resuming an aborted comparison. */
+ dir_start_file = optarg;
+ break;
+
+ case 't':
+ /* Expand tabs to spaces in the output so that it preserves
+ the alignment of the input files. */
+ tab_expand_flag = 1;
+ break;
+
+ case 'T':
+ /* Use a tab in the output, rather than a space, before the
+ text of an input line, so as to keep the proper alignment
+ in the input line without changing the characters in it. */
+ tab_align_flag = 1;
+ break;
+
+ case 'u':
+ /* Output the context diff in unidiff format. */
+ specify_style (OUTPUT_UNIFIED);
+ break;
+
+ case 'v':
+ if (callbacks && callbacks->write_stdout)
+ {
+ (*callbacks->write_stdout) ("diff - GNU diffutils version ");
+ (*callbacks->write_stdout) (diff_version_string);
+ (*callbacks->write_stdout) ("\n");
+ }
+ else
+ printf ("diff - GNU diffutils version %s\n", diff_version_string);
+ return 0;
+
+ case 'w':
+ /* Ignore horizontal white space when comparing lines. */
+ ignore_all_space_flag = 1;
+ ignore_some_changes = 1;
+ ignore_some_line_changes = 1;
+ break;
+
+ case 'x':
+ add_exclude (optarg);
+ break;
+
+ case 'X':
+ if (add_exclude_file (optarg) != 0)
+ pfatal_with_name (optarg);
+ break;
+
+ case 'y':
+ /* Use side-by-side (sdiff-style) columnar output. */
+ specify_style (OUTPUT_SDIFF);
+ break;
+
+ case 'W':
+ /* Set the line width for OUTPUT_SDIFF. */
+ if (ck_atoi (optarg, &width) || width <= 0)
+ fatal ("column width must be a positive integer");
+ break;
+
+ case 129:
+ sdiff_left_only = 1;
+ break;
+
+ case 130:
+ sdiff_skip_common_lines = 1;
+ break;
+
+ case 131:
+ /* sdiff-style columns output. */
+ specify_style (OUTPUT_SDIFF);
+ sdiff_help_sdiff = 1;
+ break;
+
+ case 132:
+ case 133:
+ case 134:
+ specify_style (OUTPUT_IFDEF);
+ if (specify_format (&line_format[c - 132], optarg) != 0)
+ diff_error ("conflicting line format", 0, 0);
+ break;
+
+ case 135:
+ specify_style (OUTPUT_IFDEF);
+ {
+ int i, err = 0;
+ for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
+ err |= specify_format (&line_format[i], optarg);
+ if (err)
+ diff_error ("conflicting line format", 0, 0);
+ }
+ break;
+
+ case 136:
+ case 137:
+ case 138:
+ case 139:
+ specify_style (OUTPUT_IFDEF);
+ if (specify_format (&group_format[c - 136], optarg) != 0)
+ diff_error ("conflicting group format", 0, 0);
+ break;
+
+ case 140:
+ if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0)
+ fatal ("horizon must be a nonnegative integer");
+ break;
+
+ case 141:
+ usage ();
+ if (! callbacks || ! callbacks->write_stdout)
+ check_output (stdout);
+ return 0;
+
+ case 142:
+ /* Use binary I/O when reading and writing data.
+ On Posix hosts, this has no effect. */
+#if HAVE_SETMODE
+ binary_I_O = 1;
+# if 0
+ /* Because this code is leftover from pre-library days,
+ there is no way to set stdout back to the default mode
+ when we are done. As it turns out, I think the only
+ parts of CVS that pass out == NULL, and thus cause diff
+ to write to stdout, are "cvs diff" and "cvs rdiff". So
+ I'm not going to worry about this too much yet. */
+ setmode (STDOUT_FILENO, O_BINARY);
+# else
+ if (out == NULL)
+ error (0, 0, "warning: did not set stdout to binary mode");
+# endif
+#endif
+ break;
+
+ default:
+ return try_help (0);
+ }
+ prev = c;
+ }
+
+ if (argc - optind != 2)
+ return try_help (argc - optind < 2 ? "missing operand" : "extra operand");
+
+ {
+ /*
+ * We maximize first the half line width, and then the gutter width,
+ * according to the following constraints:
+ * 1. Two half lines plus a gutter must fit in a line.
+ * 2. If the half line width is nonzero:
+ * a. The gutter width is at least GUTTER_WIDTH_MINIMUM.
+ * b. If tabs are not expanded to spaces,
+ * a half line plus a gutter is an integral number of tabs,
+ * so that tabs in the right column line up.
+ */
+ int t = tab_expand_flag ? 1 : TAB_WIDTH;
+ int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t) * t;
+ sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
+ sdiff_column2_offset = sdiff_half_width ? off : width;
+ }
+
+ if (show_c_function && output_style != OUTPUT_UNIFIED)
+ specify_style (OUTPUT_CONTEXT);
+
+ if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
+ context = 0;
+ else if (context == -1)
+ /* Default amount of context for -c. */
+ context = 3;
+
+ if (output_style == OUTPUT_IFDEF)
+ {
+ /* Format arrays are char *, not char const *,
+ because integer formats are temporarily modified.
+ But it is safe to assign a constant like "%=" to a format array,
+ since "%=" does not format any integers. */
+ int i;
+ 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[UNCHANGED] ? group_format[UNCHANGED] : "%<";
+ if (!group_format[NEW])
+ group_format[NEW]
+ = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
+ 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 ? sdiff_skip_common_lines : 1);
+
+ switch_string = option_list (argv + 1, optind - 1);
+
+ if (callbacks && callbacks->write_output)
+ {
+ if (out != NULL)
+ {
+ diff_error ("write callback with output file", 0, 0);
+ return 2;
+ }
+ }
+ else
+ {
+ if (out == NULL)
+ outfile = stdout;
+ else
+ {
+#if HAVE_SETMODE
+ /* A diff which is full of ^Z and such isn't going to work
+ very well in text mode. */
+ if (binary_I_O)
+ outfile = fopen (out, "wb");
+ else
+#endif
+ outfile = fopen (out, "w");
+ if (outfile == NULL)
+ {
+ perror_with_name ("could not open output file");
+ return 2;
+ }
+ opened_file = 1;
+ }
+ }
+
+ val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
+
+ /* Print any messages that were saved up for last. */
+ print_message_queue ();
+
+ free (switch_string);
+
+ optind = optind_old;
+
+ if (! callbacks || ! callbacks->write_output)
+ check_output (outfile);
+
+ if (opened_file)
+ if (fclose (outfile) != 0)
+ perror_with_name ("close error on output file");
+
+ return val;
+}
+
+/* Add the compiled form of regexp PATTERN to REGLIST. */
+
+static void
+add_regexp (reglist, pattern)
+ struct regexp_list **reglist;
+ char const *pattern;
+{
+ struct regexp_list *r;
+ char const *m;
+
+ r = (struct regexp_list *) xmalloc (sizeof (*r));
+ bzero (r, sizeof (*r));
+ r->buf.fastmap = xmalloc (256);
+ m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
+ if (m != 0)
+ diff_error ("%s: %s", pattern, m);
+
+ /* Add to the start of the list, since it's easier than the end. */
+ r->next = *reglist;
+ *reglist = r;
+}
+
+static int
+try_help (reason)
+ char const *reason;
+{
+ if (reason)
+ diff_error ("%s", reason, 0);
+ diff_error ("Try `%s --help' for more information.", diff_program_name, 0);
+ return 2;
+}
+
+static void
+check_output (file)
+ FILE *file;
+{
+ if (ferror (file) || fflush (file) != 0)
+ fatal ("write error");
+}
+
+static char const * const option_help[] = {
+"-i --ignore-case Consider upper- and lower-case to be the same.",
+"-w --ignore-all-space Ignore all white space.",
+"-b --ignore-space-change Ignore changes in the amount of white space.",
+"-B --ignore-blank-lines Ignore changes whose lines are all blank.",
+"-I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.",
+#if HAVE_SETMODE
+"--binary Read and write data in binary mode.",
+#endif
+"-a --text Treat all files as text.\n",
+"-c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.",
+"-u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.",
+" -NUM Use NUM context lines.",
+" -L LABEL --label LABEL Use LABEL instead of file name.",
+" -p --show-c-function Show which C function each change is in.",
+" -F RE --show-function-line=RE Show the most recent line matching RE.",
+"-q --brief Output only whether files differ.",
+"-e --ed Output an ed script.",
+"-n --rcs Output an RCS format diff.",
+"-y --side-by-side Output in two columns.",
+" -W NUM --width=NUM Output at most NUM (default 130) characters per line.",
+" --left-column Output only the left column of common lines.",
+" --suppress-common-lines Do not output common lines.",
+"-DNAME --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.",
+"--GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.",
+"--line-format=LFMT Similar, but format all input lines with LFMT.",
+"--LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.",
+" LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.",
+" GFMT may contain:",
+" %< lines from FILE1",
+" %> lines from FILE2",
+" %= lines common to FILE1 and FILE2",
+" %[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER",
+" LETTERs are as follows for new group, lower case for old group:",
+" F first line number",
+" L last line number",
+" N number of lines = L-F+1",
+" E F-1",
+" M L+1",
+" LFMT may contain:",
+" %L contents of line",
+" %l contents of line, excluding any trailing newline",
+" %[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number",
+" Either GFMT or LFMT may contain:",
+" %% %",
+" %c'C' the single character C",
+" %c'\\OOO' the character with octal code OOO\n",
+"-l --paginate Pass the output through `pr' to paginate it.",
+"-t --expand-tabs Expand tabs to spaces in output.",
+"-T --initial-tab Make tabs line up by prepending a tab.\n",
+"-r --recursive Recursively compare any subdirectories found.",
+"-N --new-file Treat absent files as empty.",
+"-P --unidirectional-new-file Treat absent first files as empty.",
+"-s --report-identical-files Report when two files are the same.",
+"-x PAT --exclude=PAT Exclude files that match PAT.",
+"-X FILE --exclude-from=FILE Exclude files that match any pattern in FILE.",
+"-S FILE --starting-file=FILE Start with FILE when comparing directories.\n",
+"--horizon-lines=NUM Keep NUM lines of the common prefix and suffix.",
+"-d --minimal Try hard to find a smaller set of changes.",
+"-H --speed-large-files Assume large files and many scattered small changes.\n",
+"-v --version Output version info.",
+"--help Output this help.",
+0
+};
+
+static void
+usage ()
+{
+ char const * const *p;
+
+ if (callbacks && callbacks->write_stdout)
+ {
+ (*callbacks->write_stdout) ("Usage: ");
+ (*callbacks->write_stdout) (diff_program_name);
+ (*callbacks->write_stdout) (" [OPTION]... FILE1 FILE2\n\n");
+ for (p = option_help; *p; p++)
+ {
+ (*callbacks->write_stdout) (" ");
+ (*callbacks->write_stdout) (*p);
+ (*callbacks->write_stdout) ("\n");
+ }
+ (*callbacks->write_stdout)
+ ("\nIf FILE1 or FILE2 is `-', read standard input.\n");
+ }
+ else
+ {
+ printf ("Usage: %s [OPTION]... FILE1 FILE2\n\n", diff_program_name);
+ for (p = option_help; *p; p++)
+ printf (" %s\n", *p);
+ printf ("\nIf FILE1 or FILE2 is `-', read standard input.\n");
+ }
+}
+
+static int
+specify_format (var, value)
+ char **var;
+ char *value;
+{
+ int err = *var ? strcmp (*var, value) : 0;
+ *var = value;
+ return err;
+}
+
+static void
+specify_style (style)
+ enum output_style style;
+{
+ if (output_style != OUTPUT_NORMAL
+ && output_style != style)
+ diff_error ("conflicting specifications of output style", 0, 0);
+ output_style = style;
+}
+
+static char const *
+filetype (st)
+ struct stat const *st;
+{
+ /* See Posix.2 section 4.17.6.1.1 and Table 5-1 for these formats.
+ To keep diagnostics grammatical, the returned string must start
+ with a consonant. */
+
+ if (S_ISREG (st->st_mode))
+ {
+ if (st->st_size == 0)
+ return "regular empty file";
+ /* Posix.2 section 5.14.2 seems to suggest that we must read the file
+ and guess whether it's C, Fortran, etc., but this is somewhat useless
+ and doesn't reflect historical practice. We're allowed to guess
+ wrong, so we don't bother to read the file. */
+ return "regular file";
+ }
+ if (S_ISDIR (st->st_mode)) return "directory";
+
+ /* other Posix.1 file types */
+#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
+
+ /* other Posix.1b file types */
+#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
+
+ /* other popular file types */
+ /* S_ISLNK is impossible with `fstat' and `stat'. */
+#ifdef S_ISSOCK
+ if (S_ISSOCK (st->st_mode)) return "socket";
+#endif
+
+ return "weird file";
+}
+
+/* Compare two files (or dirs) with specified names
+ DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
+ (if DIR0 is 0, then the name is just NAME0, etc.)
+ This is self-contained; it opens the files and closes them.
+
+ Value is 0 if files are the same, 1 if different,
+ 2 if there is a problem opening them. */
+
+static int
+compare_files (dir0, name0, dir1, name1, depth)
+ char const *dir0, *dir1;
+ char const *name0, *name1;
+ int depth;
+{
+ struct file_data inf[2];
+ register int i;
+ int val;
+ int same_files;
+ int failed = 0;
+ char *free0 = 0, *free1 = 0;
+
+ /* If this is directory comparison, perhaps we have a file
+ that exists only in one of the directories.
+ If so, just print a message to that effect. */
+
+ if (! ((name0 != 0 && name1 != 0)
+ || (unidirectional_new_file_flag && name1 != 0)
+ || entire_new_file_flag))
+ {
+ char const *name = name0 == 0 ? name1 : name0;
+ char const *dir = name0 == 0 ? dir1 : dir0;
+ message ("Only in %s: %s\n", dir, name);
+ /* Return 1 so that diff_dirs will return 1 ("some files differ"). */
+ return 1;
+ }
+
+ bzero (inf, sizeof (inf));
+
+ /* Mark any nonexistent file with -1 in the desc field. */
+ /* Mark unopened files (e.g. directories) with -2. */
+
+ inf[0].desc = name0 == 0 ? -1 : -2;
+ inf[1].desc = name1 == 0 ? -1 : -2;
+
+ /* Now record the full name of each file, including nonexistent ones. */
+
+ if (name0 == 0)
+ name0 = name1;
+ if (name1 == 0)
+ name1 = name0;
+
+ inf[0].name = dir0 == 0 ? name0 : (free0 = dir_file_pathname (dir0, name0));
+ inf[1].name = dir1 == 0 ? name1 : (free1 = dir_file_pathname (dir1, name1));
+
+ /* Stat the files. Record whether they are directories. */
+
+ for (i = 0; i <= 1; i++)
+ {
+ if (inf[i].desc != -1)
+ {
+ int stat_result;
+
+ if (i && filename_cmp (inf[i].name, inf[0].name) == 0)
+ {
+ inf[i].stat = inf[0].stat;
+ stat_result = 0;
+ }
+ else if (strcmp (inf[i].name, "-") == 0)
+ {
+ inf[i].desc = STDIN_FILENO;
+ stat_result = fstat (STDIN_FILENO, &inf[i].stat);
+ if (stat_result == 0 && S_ISREG (inf[i].stat.st_mode))
+ {
+ off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
+ if (pos == -1)
+ stat_result = -1;
+ else
+ {
+ if (pos <= inf[i].stat.st_size)
+ inf[i].stat.st_size -= pos;
+ else
+ inf[i].stat.st_size = 0;
+ /* Posix.2 4.17.6.1.4 requires current time for stdin. */
+ time (&inf[i].stat.st_mtime);
+ }
+ }
+ }
+ else
+ stat_result = stat (inf[i].name, &inf[i].stat);
+
+ if (stat_result != 0)
+ {
+ perror_with_name (inf[i].name);
+ failed = 1;
+ }
+ else
+ {
+ inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
+ if (inf[1 - i].desc == -1)
+ {
+ inf[1 - i].dir_p = inf[i].dir_p;
+ inf[1 - i].stat.st_mode = inf[i].stat.st_mode;
+ }
+ }
+ }
+ }
+
+ if (! failed && depth == 0 && inf[0].dir_p != inf[1].dir_p)
+ {
+ /* If one is a directory, and it was specified in the command line,
+ use the file in that dir with the other file's basename. */
+
+ int fnm_arg = inf[0].dir_p;
+ int dir_arg = 1 - fnm_arg;
+ char const *fnm = inf[fnm_arg].name;
+ char const *dir = inf[dir_arg].name;
+ char const *p = filename_lastdirchar (fnm);
+ char const *filename = inf[dir_arg].name
+ = dir_file_pathname (dir, p ? p + 1 : fnm);
+
+ if (strcmp (fnm, "-") == 0)
+ fatal ("can't compare - to a directory");
+
+ if (stat (filename, &inf[dir_arg].stat) != 0)
+ {
+ perror_with_name (filename);
+ failed = 1;
+ }
+ else
+ inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
+ }
+
+ if (failed)
+ {
+
+ /* If either file should exist but does not, return 2. */
+
+ val = 2;
+
+ }
+ else if ((same_files = inf[0].desc != -1 && inf[1].desc != -1
+ && 0 < same_file (&inf[0].stat, &inf[1].stat))
+ && no_diff_means_no_output)
+ {
+ /* The two named files are actually the same physical file.
+ We know they are identical without actually reading them. */
+
+ val = 0;
+ }
+ else if (inf[0].dir_p & inf[1].dir_p)
+ {
+ if (output_style == OUTPUT_IFDEF)
+ fatal ("-D option not supported with directories");
+
+ /* If both are directories, compare the files in them. */
+
+ if (depth > 0 && !recursive)
+ {
+ /* But don't compare dir contents one level down
+ unless -r was specified. */
+ message ("Common subdirectories: %s and %s\n",
+ inf[0].name, inf[1].name);
+ val = 0;
+ }
+ else
+ {
+ val = diff_dirs (inf, compare_files, depth);
+ }
+
+ }
+ else if ((inf[0].dir_p | inf[1].dir_p)
+ || (depth > 0
+ && (! S_ISREG (inf[0].stat.st_mode)
+ || ! S_ISREG (inf[1].stat.st_mode))))
+ {
+ /* Perhaps we have a subdirectory that exists only in one directory.
+ If so, just print a message to that effect. */
+
+ if (inf[0].desc == -1 || inf[1].desc == -1)
+ {
+ if ((inf[0].dir_p | inf[1].dir_p)
+ && recursive
+ && (entire_new_file_flag
+ || (unidirectional_new_file_flag && inf[0].desc == -1)))
+ val = diff_dirs (inf, compare_files, depth);
+ else
+ {
+ char const *dir = (inf[0].desc == -1) ? dir1 : dir0;
+ /* See Posix.2 section 4.17.6.1.1 for this format. */
+ message ("Only in %s: %s\n", dir, name0);
+ val = 1;
+ }
+ }
+ else
+ {
+ /* We have two files that are not to be compared. */
+
+ /* See Posix.2 section 4.17.6.1.1 for this format. */
+ message5 ("File %s is a %s while file %s is a %s\n",
+ inf[0].name, filetype (&inf[0].stat),
+ inf[1].name, filetype (&inf[1].stat));
+
+ /* This is a difference. */
+ val = 1;
+ }
+ }
+ else if ((no_details_flag & ~ignore_some_changes)
+ && inf[0].stat.st_size != inf[1].stat.st_size
+ && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
+ && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
+ {
+ message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
+ val = 1;
+ }
+ else
+ {
+ /* Both exist and neither is a directory. */
+
+ /* Open the files and record their descriptors. */
+
+ if (inf[0].desc == -2)
+ if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
+ {
+ perror_with_name (inf[0].name);
+ failed = 1;
+ }
+ if (inf[1].desc == -2)
+ {
+ if (same_files)
+ inf[1].desc = inf[0].desc;
+ else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
+ {
+ perror_with_name (inf[1].name);
+ failed = 1;
+ }
+ }
+
+#if HAVE_SETMODE
+ if (binary_I_O)
+ for (i = 0; i <= 1; i++)
+ if (0 <= inf[i].desc)
+ setmode (inf[i].desc, O_BINARY);
+#endif
+
+ /* Compare the files, if no error was found. */
+
+ val = failed ? 2 : diff_2_files (inf, depth);
+
+ /* Close the file descriptors. */
+
+ if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
+ {
+ perror_with_name (inf[0].name);
+ val = 2;
+ }
+ if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
+ && close (inf[1].desc) != 0)
+ {
+ perror_with_name (inf[1].name);
+ val = 2;
+ }
+ }
+
+ /* Now the comparison has been done, if no error prevented it,
+ and VAL is the value this function will return. */
+
+ if (val == 0 && !inf[0].dir_p)
+ {
+ if (print_file_same_flag)
+ message ("Files %s and %s are identical\n",
+ inf[0].name, inf[1].name);
+ }
+ else
+ flush_output ();
+
+ if (free0)
+ free (free0);
+ if (free1)
+ free (free1);
+
+ return val;
+}
+
+/* Initialize status variables and flag variables used in libdiff,
+ to permit repeated calls to diff_run. */
+
+static void
+initialize_main (argcp, argvp)
+ int *argcp;
+ char ***argvp;
+{
+ /* These variables really must be reset each time diff_run is called. */
+ output_style = OUTPUT_NORMAL;
+ context = -1;
+ file_label[0] = NULL;
+ file_label[1] = NULL;
+ diff_program_name = (*argvp)[0];
+ outfile = NULL;
+
+ /* Reset these also, just for safety's sake. (If one invocation turns
+ on ignore_case_flag, it must be turned off before diff_run is called
+ again. But it is possible to make many diffs before encountering
+ such a problem. */
+ recursive = 0;
+ no_discards = 0;
+#if HAVE_SETMODE
+ binary_I_O = 0;
+#endif
+ no_diff_means_no_output = 0;
+ always_text_flag = 0;
+ horizon_lines = 0;
+ ignore_space_change_flag = 0;
+ ignore_all_space_flag = 0;
+ ignore_blank_lines_flag = 0;
+ ignore_some_line_changes = 0;
+ ignore_some_changes = 0;
+ ignore_case_flag = 0;
+ function_regexp_list = NULL;
+ ignore_regexp_list = NULL;
+ no_details_flag = 0;
+ print_file_same_flag = 0;
+ tab_align_flag = 0;
+ tab_expand_flag = 0;
+ dir_start_file = NULL;
+ entire_new_file_flag = 0;
+ unidirectional_new_file_flag = 0;
+ paginate_flag = 0;
+ bzero (group_format, sizeof (group_format));
+ bzero (line_format, sizeof (line_format));
+ sdiff_help_sdiff = 0;
+ sdiff_left_only = 0;
+ sdiff_skip_common_lines = 0;
+ sdiff_half_width = 0;
+ sdiff_column2_offset = 0;
+ switch_string = NULL;
+ heuristic = 0;
+ bzero (files, sizeof (files));
+}