1 | /* diff - compare files line by line
|
---|
2 |
|
---|
3 | Copyright (C) 1988, 1989, 1992, 1993, 1994, 1996, 1998, 2001, 2002
|
---|
4 | Free Software Foundation, Inc.
|
---|
5 |
|
---|
6 | This file is part of GNU DIFF.
|
---|
7 |
|
---|
8 | GNU DIFF is free software; you can redistribute it and/or modify
|
---|
9 | it under the terms of the GNU General Public License as published by
|
---|
10 | the Free Software Foundation; either version 2, or (at your option)
|
---|
11 | any later version.
|
---|
12 |
|
---|
13 | GNU DIFF is distributed in the hope that it will be useful,
|
---|
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
---|
16 | See the GNU General Public License for more details.
|
---|
17 |
|
---|
18 | You should have received a copy of the GNU General Public License
|
---|
19 | along with GNU DIFF; see the file COPYING.
|
---|
20 | If not, write to the Free Software Foundation,
|
---|
21 | 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
---|
22 |
|
---|
23 | #define GDIFF_MAIN
|
---|
24 | #include "diff.h"
|
---|
25 | #include <c-stack.h>
|
---|
26 | #include <dirname.h>
|
---|
27 | #include <error.h>
|
---|
28 | #include <exclude.h>
|
---|
29 | #include <exitfail.h>
|
---|
30 | #include <fnmatch.h>
|
---|
31 | #include <freesoft.h>
|
---|
32 | #include <getopt.h>
|
---|
33 | #include <hard-locale.h>
|
---|
34 | #include <prepargs.h>
|
---|
35 | #include <quotesys.h>
|
---|
36 | #include <regex.h>
|
---|
37 | #include <setmode.h>
|
---|
38 | #include <xalloc.h>
|
---|
39 |
|
---|
40 | static char const authorship_msgid[] =
|
---|
41 | N_("Written by Paul Eggert, Mike Haertel, David Hayes,\n\
|
---|
42 | Richard Stallman, and Len Tower.");
|
---|
43 |
|
---|
44 | static char const copyright_string[] =
|
---|
45 | "Copyright (C) 2002 Free Software Foundation, Inc.";
|
---|
46 |
|
---|
47 | #ifndef GUTTER_WIDTH_MINIMUM
|
---|
48 | # define GUTTER_WIDTH_MINIMUM 3
|
---|
49 | #endif
|
---|
50 |
|
---|
51 | struct regexp_list
|
---|
52 | {
|
---|
53 | char *regexps; /* chars representing disjunction of the regexps */
|
---|
54 | size_t len; /* chars used in `regexps' */
|
---|
55 | size_t size; /* size malloc'ed for `regexps'; 0 if not malloc'ed */
|
---|
56 | bool multiple_regexps;/* Does `regexps' represent a disjunction? */
|
---|
57 | struct re_pattern_buffer *buf;
|
---|
58 | };
|
---|
59 |
|
---|
60 | static int compare_files (struct comparison const *, char const *, char const *);
|
---|
61 | static void add_regexp (struct regexp_list *, char const *);
|
---|
62 | static void summarize_regexp_list (struct regexp_list *);
|
---|
63 | static void specify_style (enum output_style);
|
---|
64 | static void specify_value (char const **, char const *, char const *);
|
---|
65 | static void try_help (char const *, char const *) __attribute__((noreturn));
|
---|
66 | static void check_stdout (void);
|
---|
67 | static void usage (void);
|
---|
68 |
|
---|
69 | /* If comparing directories, compare their common subdirectories
|
---|
70 | recursively. */
|
---|
71 | static bool recursive;
|
---|
72 |
|
---|
73 | /* In context diffs, show previous lines that match these regexps. */
|
---|
74 | static struct regexp_list function_regexp_list;
|
---|
75 |
|
---|
76 | /* Ignore changes affecting only lines that match these regexps. */
|
---|
77 | static struct regexp_list ignore_regexp_list;
|
---|
78 |
|
---|
79 | #if HAVE_SETMODE_DOS
|
---|
80 | /* Use binary I/O when reading and writing data (--binary).
|
---|
81 | On POSIX hosts, this has no effect. */
|
---|
82 | static bool binary;
|
---|
83 | #endif
|
---|
84 |
|
---|
85 | /* When comparing directories, if a file appears only in one
|
---|
86 | directory, treat it as present but empty in the other (-N).
|
---|
87 | Then `patch' would create the file with appropriate contents. */
|
---|
88 | static bool new_file;
|
---|
89 |
|
---|
90 | /* When comparing directories, if a file appears only in the second
|
---|
91 | directory of the two, treat it as present but empty in the other
|
---|
92 | (--unidirectional-new-file).
|
---|
93 | Then `patch' would create the file with appropriate contents. */
|
---|
94 | static bool unidirectional_new_file;
|
---|
95 |
|
---|
96 | /* Report files compared that are the same (-s).
|
---|
97 | Normally nothing is output when that happens. */
|
---|
98 | static bool report_identical_files;
|
---|
99 |
|
---|
100 |
|
---|
101 | /* Return a string containing the command options with which diff was invoked.
|
---|
102 | Spaces appear between what were separate ARGV-elements.
|
---|
103 | There is a space at the beginning but none at the end.
|
---|
104 | If there were no options, the result is an empty string.
|
---|
105 |
|
---|
106 | Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
|
---|
107 | the length of that vector. */
|
---|
108 |
|
---|
109 | static char *
|
---|
110 | option_list (char **optionvec, int count)
|
---|
111 | {
|
---|
112 | int i;
|
---|
113 | size_t size = 1;
|
---|
114 | char *result;
|
---|
115 | char *p;
|
---|
116 |
|
---|
117 | for (i = 0; i < count; i++)
|
---|
118 | size += 1 + quote_system_arg ((char *) 0, optionvec[i]);
|
---|
119 |
|
---|
120 | p = result = xmalloc (size);
|
---|
121 |
|
---|
122 | for (i = 0; i < count; i++)
|
---|
123 | {
|
---|
124 | *p++ = ' ';
|
---|
125 | p += quote_system_arg (p, optionvec[i]);
|
---|
126 | }
|
---|
127 |
|
---|
128 | *p = 0;
|
---|
129 | return result;
|
---|
130 | }
|
---|
131 |
|
---|
132 |
|
---|
133 | /* Return an option value suitable for add_exclude. */
|
---|
134 |
|
---|
135 | static int
|
---|
136 | exclude_options (void)
|
---|
137 | {
|
---|
138 | return EXCLUDE_WILDCARDS | (ignore_file_name_case ? FNM_CASEFOLD : 0);
|
---|
139 | }
|
---|
140 | |
---|
141 |
|
---|
142 | static char const shortopts[] =
|
---|
143 | "0123456789abBcC:dD:eEfF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y";
|
---|
144 |
|
---|
145 | /* Values for long options that do not have single-letter equivalents. */
|
---|
146 | enum
|
---|
147 | {
|
---|
148 | BINARY_OPTION = CHAR_MAX + 1,
|
---|
149 | FROM_FILE_OPTION,
|
---|
150 | HELP_OPTION,
|
---|
151 | HORIZON_LINES_OPTION,
|
---|
152 | IGNORE_FILE_NAME_CASE_OPTION,
|
---|
153 | INHIBIT_HUNK_MERGE_OPTION,
|
---|
154 | LEFT_COLUMN_OPTION,
|
---|
155 | LINE_FORMAT_OPTION,
|
---|
156 | NO_IGNORE_FILE_NAME_CASE_OPTION,
|
---|
157 | NORMAL_OPTION,
|
---|
158 | SDIFF_MERGE_ASSIST_OPTION,
|
---|
159 | STRIP_TRAILING_CR_OPTION,
|
---|
160 | SUPPRESS_COMMON_LINES_OPTION,
|
---|
161 | TO_FILE_OPTION,
|
---|
162 |
|
---|
163 | /* These options must be in sequence. */
|
---|
164 | UNCHANGED_LINE_FORMAT_OPTION,
|
---|
165 | OLD_LINE_FORMAT_OPTION,
|
---|
166 | NEW_LINE_FORMAT_OPTION,
|
---|
167 |
|
---|
168 | /* These options must be in sequence. */
|
---|
169 | UNCHANGED_GROUP_FORMAT_OPTION,
|
---|
170 | OLD_GROUP_FORMAT_OPTION,
|
---|
171 | NEW_GROUP_FORMAT_OPTION,
|
---|
172 | CHANGED_GROUP_FORMAT_OPTION
|
---|
173 | };
|
---|
174 |
|
---|
175 | static char const group_format_option[][sizeof "--unchanged-group-format"] =
|
---|
176 | {
|
---|
177 | "--unchanged-group-format",
|
---|
178 | "--old-group-format",
|
---|
179 | "--new-group-format",
|
---|
180 | "--changed-group-format"
|
---|
181 | };
|
---|
182 |
|
---|
183 | static char const line_format_option[][sizeof "--unchanged-line-format"] =
|
---|
184 | {
|
---|
185 | "--unchanged-line-format",
|
---|
186 | "--old-line-format",
|
---|
187 | "--new-line-format"
|
---|
188 | };
|
---|
189 |
|
---|
190 | static struct option const longopts[] =
|
---|
191 | {
|
---|
192 | {"binary", 0, 0, BINARY_OPTION},
|
---|
193 | {"brief", 0, 0, 'q'},
|
---|
194 | {"changed-group-format", 1, 0, CHANGED_GROUP_FORMAT_OPTION},
|
---|
195 | {"context", 2, 0, 'C'},
|
---|
196 | {"ed", 0, 0, 'e'},
|
---|
197 | {"exclude", 1, 0, 'x'},
|
---|
198 | {"exclude-from", 1, 0, 'X'},
|
---|
199 | {"expand-tabs", 0, 0, 't'},
|
---|
200 | {"forward-ed", 0, 0, 'f'},
|
---|
201 | {"from-file", 1, 0, FROM_FILE_OPTION},
|
---|
202 | {"help", 0, 0, HELP_OPTION},
|
---|
203 | {"horizon-lines", 1, 0, HORIZON_LINES_OPTION},
|
---|
204 | {"ifdef", 1, 0, 'D'},
|
---|
205 | {"ignore-all-space", 0, 0, 'w'},
|
---|
206 | {"ignore-blank-lines", 0, 0, 'B'},
|
---|
207 | {"ignore-case", 0, 0, 'i'},
|
---|
208 | {"ignore-file-name-case", 0, 0, IGNORE_FILE_NAME_CASE_OPTION},
|
---|
209 | {"ignore-matching-lines", 1, 0, 'I'},
|
---|
210 | {"ignore-space-change", 0, 0, 'b'},
|
---|
211 | {"ignore-tab-expansion", 0, 0, 'E'},
|
---|
212 | {"inhibit-hunk-merge", 0, 0, INHIBIT_HUNK_MERGE_OPTION},
|
---|
213 | {"initial-tab", 0, 0, 'T'},
|
---|
214 | {"label", 1, 0, 'L'},
|
---|
215 | {"left-column", 0, 0, LEFT_COLUMN_OPTION},
|
---|
216 | {"line-format", 1, 0, LINE_FORMAT_OPTION},
|
---|
217 | {"minimal", 0, 0, 'd'},
|
---|
218 | {"new-file", 0, 0, 'N'},
|
---|
219 | {"new-group-format", 1, 0, NEW_GROUP_FORMAT_OPTION},
|
---|
220 | {"new-line-format", 1, 0, NEW_LINE_FORMAT_OPTION},
|
---|
221 | {"no-ignore-file-name-case", 0, 0, NO_IGNORE_FILE_NAME_CASE_OPTION},
|
---|
222 | {"normal", 0, 0, NORMAL_OPTION},
|
---|
223 | {"old-group-format", 1, 0, OLD_GROUP_FORMAT_OPTION},
|
---|
224 | {"old-line-format", 1, 0, OLD_LINE_FORMAT_OPTION},
|
---|
225 | {"paginate", 0, 0, 'l'},
|
---|
226 | {"rcs", 0, 0, 'n'},
|
---|
227 | {"recursive", 0, 0, 'r'},
|
---|
228 | {"report-identical-files", 0, 0, 's'},
|
---|
229 | {"sdiff-merge-assist", 0, 0, SDIFF_MERGE_ASSIST_OPTION},
|
---|
230 | {"show-c-function", 0, 0, 'p'},
|
---|
231 | {"show-function-line", 1, 0, 'F'},
|
---|
232 | {"side-by-side", 0, 0, 'y'},
|
---|
233 | {"speed-large-files", 0, 0, 'H'},
|
---|
234 | {"starting-file", 1, 0, 'S'},
|
---|
235 | {"strip-trailing-cr", 0, 0, STRIP_TRAILING_CR_OPTION},
|
---|
236 | {"suppress-common-lines", 0, 0, SUPPRESS_COMMON_LINES_OPTION},
|
---|
237 | {"text", 0, 0, 'a'},
|
---|
238 | {"to-file", 1, 0, TO_FILE_OPTION},
|
---|
239 | {"unchanged-group-format", 1, 0, UNCHANGED_GROUP_FORMAT_OPTION},
|
---|
240 | {"unchanged-line-format", 1, 0, UNCHANGED_LINE_FORMAT_OPTION},
|
---|
241 | {"unidirectional-new-file", 0, 0, 'P'},
|
---|
242 | {"unified", 2, 0, 'U'},
|
---|
243 | {"version", 0, 0, 'v'},
|
---|
244 | {"width", 1, 0, 'W'},
|
---|
245 | {0, 0, 0, 0}
|
---|
246 | };
|
---|
247 |
|
---|
248 | int
|
---|
249 | main (int argc, char **argv)
|
---|
250 | {
|
---|
251 | int exit_status = EXIT_SUCCESS;
|
---|
252 | int c;
|
---|
253 | int i;
|
---|
254 | int prev = -1;
|
---|
255 | lin ocontext = -1;
|
---|
256 | bool explicit_context = 0;
|
---|
257 | int width = 0;
|
---|
258 | bool show_c_function = 0;
|
---|
259 | char const *from_file = 0;
|
---|
260 | char const *to_file = 0;
|
---|
261 | uintmax_t numval;
|
---|
262 | char *numend;
|
---|
263 |
|
---|
264 | /* Do our initializations. */
|
---|
265 | exit_failure = 2;
|
---|
266 | initialize_main (&argc, &argv);
|
---|
267 | program_name = argv[0];
|
---|
268 | setlocale (LC_ALL, "");
|
---|
269 | bindtextdomain (PACKAGE, LOCALEDIR);
|
---|
270 | textdomain (PACKAGE);
|
---|
271 | c_stack_action (c_stack_die);
|
---|
272 | function_regexp_list.buf = &function_regexp;
|
---|
273 | ignore_regexp_list.buf = &ignore_regexp;
|
---|
274 | re_set_syntax (RE_SYNTAX_GREP | RE_NO_POSIX_BACKTRACKING);
|
---|
275 | excluded = new_exclude ();
|
---|
276 |
|
---|
277 | /* Decode the options. */
|
---|
278 |
|
---|
279 | while ((c = getopt_long (argc, argv, shortopts, longopts, 0)) != -1)
|
---|
280 | {
|
---|
281 | switch (c)
|
---|
282 | {
|
---|
283 | case 0:
|
---|
284 | break;
|
---|
285 |
|
---|
286 | case '0':
|
---|
287 | case '1':
|
---|
288 | case '2':
|
---|
289 | case '3':
|
---|
290 | case '4':
|
---|
291 | case '5':
|
---|
292 | case '6':
|
---|
293 | case '7':
|
---|
294 | case '8':
|
---|
295 | case '9':
|
---|
296 | if (! ISDIGIT (prev))
|
---|
297 | ocontext = c - '0';
|
---|
298 | else if (LIN_MAX / 10 < ocontext
|
---|
299 | || ((ocontext = 10 * ocontext + c - '0') < 0))
|
---|
300 | ocontext = LIN_MAX;
|
---|
301 | break;
|
---|
302 |
|
---|
303 | case 'a':
|
---|
304 | text = 1;
|
---|
305 | break;
|
---|
306 |
|
---|
307 | case 'b':
|
---|
308 | if (ignore_white_space < IGNORE_SPACE_CHANGE)
|
---|
309 | ignore_white_space = IGNORE_SPACE_CHANGE;
|
---|
310 | break;
|
---|
311 |
|
---|
312 | case 'B':
|
---|
313 | ignore_blank_lines = 1;
|
---|
314 | break;
|
---|
315 |
|
---|
316 | case 'C': /* +context[=lines] */
|
---|
317 | case 'U': /* +unified[=lines] */
|
---|
318 | {
|
---|
319 | if (optarg)
|
---|
320 | {
|
---|
321 | numval = strtoumax (optarg, &numend, 10);
|
---|
322 | if (*numend)
|
---|
323 | try_help ("invalid context length `%s'", optarg);
|
---|
324 | if (LIN_MAX < numval)
|
---|
325 | numval = LIN_MAX;
|
---|
326 | }
|
---|
327 | else
|
---|
328 | numval = 3;
|
---|
329 |
|
---|
330 | specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
|
---|
331 | if (context < numval)
|
---|
332 | context = numval;
|
---|
333 | explicit_context = 1;
|
---|
334 | }
|
---|
335 | break;
|
---|
336 |
|
---|
337 | case 'c':
|
---|
338 | specify_style (OUTPUT_CONTEXT);
|
---|
339 | if (context < 3)
|
---|
340 | context = 3;
|
---|
341 | break;
|
---|
342 |
|
---|
343 | case 'd':
|
---|
344 | minimal = 1;
|
---|
345 | break;
|
---|
346 |
|
---|
347 | case 'D':
|
---|
348 | specify_style (OUTPUT_IFDEF);
|
---|
349 | {
|
---|
350 | static char const C_ifdef_group_formats[] =
|
---|
351 | "%%=%c#ifndef %s\n%%<#endif /* ! %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
|
---|
352 | char *b = xmalloc (sizeof C_ifdef_group_formats
|
---|
353 | + 7 * strlen (optarg) - 14 /* 7*"%s" */
|
---|
354 | - 8 /* 5*"%%" + 3*"%c" */);
|
---|
355 | sprintf (b, C_ifdef_group_formats,
|
---|
356 | 0,
|
---|
357 | optarg, optarg, 0,
|
---|
358 | optarg, optarg, 0,
|
---|
359 | optarg, optarg, optarg);
|
---|
360 | for (i = 0; i < sizeof group_format / sizeof *group_format; i++)
|
---|
361 | {
|
---|
362 | specify_value (&group_format[i], b, "-D");
|
---|
363 | b += strlen (b) + 1;
|
---|
364 | }
|
---|
365 | }
|
---|
366 | break;
|
---|
367 |
|
---|
368 | case 'e':
|
---|
369 | specify_style (OUTPUT_ED);
|
---|
370 | break;
|
---|
371 |
|
---|
372 | case 'E':
|
---|
373 | if (ignore_white_space < IGNORE_TAB_EXPANSION)
|
---|
374 | ignore_white_space = IGNORE_TAB_EXPANSION;
|
---|
375 | break;
|
---|
376 |
|
---|
377 | case 'f':
|
---|
378 | specify_style (OUTPUT_FORWARD_ED);
|
---|
379 | break;
|
---|
380 |
|
---|
381 | case 'F':
|
---|
382 | add_regexp (&function_regexp_list, optarg);
|
---|
383 | break;
|
---|
384 |
|
---|
385 | case 'h':
|
---|
386 | /* Split the files into chunks for faster processing.
|
---|
387 | Usually does not change the result.
|
---|
388 |
|
---|
389 | This currently has no effect. */
|
---|
390 | break;
|
---|
391 |
|
---|
392 | case 'H':
|
---|
393 | speed_large_files = 1;
|
---|
394 | break;
|
---|
395 |
|
---|
396 | case 'i':
|
---|
397 | ignore_case = 1;
|
---|
398 | break;
|
---|
399 |
|
---|
400 | case 'I':
|
---|
401 | add_regexp (&ignore_regexp_list, optarg);
|
---|
402 | break;
|
---|
403 |
|
---|
404 | case 'l':
|
---|
405 | if (!pr_program[0])
|
---|
406 | try_help ("pagination not supported on this host", 0);
|
---|
407 | paginate = 1;
|
---|
408 | #ifdef SIGCHLD
|
---|
409 | /* Pagination requires forking and waiting, and
|
---|
410 | System V fork+wait does not work if SIGCHLD is ignored. */
|
---|
411 | signal (SIGCHLD, SIG_DFL);
|
---|
412 | #endif
|
---|
413 | break;
|
---|
414 |
|
---|
415 | case 'L':
|
---|
416 | if (!file_label[0])
|
---|
417 | file_label[0] = optarg;
|
---|
418 | else if (!file_label[1])
|
---|
419 | file_label[1] = optarg;
|
---|
420 | else
|
---|
421 | fatal ("too many file label options");
|
---|
422 | break;
|
---|
423 |
|
---|
424 | case 'n':
|
---|
425 | specify_style (OUTPUT_RCS);
|
---|
426 | break;
|
---|
427 |
|
---|
428 | case 'N':
|
---|
429 | new_file = 1;
|
---|
430 | break;
|
---|
431 |
|
---|
432 | case 'p':
|
---|
433 | show_c_function = 1;
|
---|
434 | add_regexp (&function_regexp_list, "^[[:alpha:]$_]");
|
---|
435 | break;
|
---|
436 |
|
---|
437 | case 'P':
|
---|
438 | unidirectional_new_file = 1;
|
---|
439 | break;
|
---|
440 |
|
---|
441 | case 'q':
|
---|
442 | brief = 1;
|
---|
443 | break;
|
---|
444 |
|
---|
445 | case 'r':
|
---|
446 | recursive = 1;
|
---|
447 | break;
|
---|
448 |
|
---|
449 | case 's':
|
---|
450 | report_identical_files = 1;
|
---|
451 | break;
|
---|
452 |
|
---|
453 | case 'S':
|
---|
454 | specify_value (&starting_file, optarg, "-S");
|
---|
455 | break;
|
---|
456 |
|
---|
457 | case 't':
|
---|
458 | expand_tabs = 1;
|
---|
459 | break;
|
---|
460 |
|
---|
461 | case 'T':
|
---|
462 | initial_tab = 1;
|
---|
463 | break;
|
---|
464 |
|
---|
465 | case 'u':
|
---|
466 | specify_style (OUTPUT_UNIFIED);
|
---|
467 | if (context < 3)
|
---|
468 | context = 3;
|
---|
469 | break;
|
---|
470 |
|
---|
471 | case 'v':
|
---|
472 | printf ("diff %s\n%s\n\n%s\n\n%s\n",
|
---|
473 | version_string, copyright_string,
|
---|
474 | _(free_software_msgid), _(authorship_msgid));
|
---|
475 | check_stdout ();
|
---|
476 | return EXIT_SUCCESS;
|
---|
477 |
|
---|
478 | case 'w':
|
---|
479 | ignore_white_space = IGNORE_ALL_SPACE;
|
---|
480 | break;
|
---|
481 |
|
---|
482 | case 'x':
|
---|
483 | add_exclude (excluded, optarg, exclude_options ());
|
---|
484 | break;
|
---|
485 |
|
---|
486 | case 'X':
|
---|
487 | if (add_exclude_file (add_exclude, excluded, optarg,
|
---|
488 | exclude_options (), '\n'))
|
---|
489 | pfatal_with_name (optarg);
|
---|
490 | break;
|
---|
491 |
|
---|
492 | case 'y':
|
---|
493 | specify_style (OUTPUT_SDIFF);
|
---|
494 | break;
|
---|
495 |
|
---|
496 | case 'W':
|
---|
497 | numval = strtoumax (optarg, &numend, 10);
|
---|
498 | if (! (0 < numval && numval <= INT_MAX) || *numend)
|
---|
499 | try_help ("invalid width `%s'", optarg);
|
---|
500 | if (width != numval)
|
---|
501 | {
|
---|
502 | if (width)
|
---|
503 | fatal ("conflicting width options");
|
---|
504 | width = numval;
|
---|
505 | }
|
---|
506 | break;
|
---|
507 |
|
---|
508 | case BINARY_OPTION:
|
---|
509 | #if HAVE_SETMODE_DOS
|
---|
510 | binary = 1;
|
---|
511 | set_binary_mode (STDOUT_FILENO, 1);
|
---|
512 | #endif
|
---|
513 | break;
|
---|
514 |
|
---|
515 | case FROM_FILE_OPTION:
|
---|
516 | specify_value (&from_file, optarg, "--from-file");
|
---|
517 | break;
|
---|
518 |
|
---|
519 | case HELP_OPTION:
|
---|
520 | usage ();
|
---|
521 | check_stdout ();
|
---|
522 | return EXIT_SUCCESS;
|
---|
523 |
|
---|
524 | case HORIZON_LINES_OPTION:
|
---|
525 | numval = strtoumax (optarg, &numend, 10);
|
---|
526 | if (*numend)
|
---|
527 | try_help ("invalid horizon length `%s'", optarg);
|
---|
528 | horizon_lines = MAX (horizon_lines, MIN (numval, LIN_MAX));
|
---|
529 | break;
|
---|
530 |
|
---|
531 | case IGNORE_FILE_NAME_CASE_OPTION:
|
---|
532 | ignore_file_name_case = 1;
|
---|
533 | break;
|
---|
534 |
|
---|
535 | case INHIBIT_HUNK_MERGE_OPTION:
|
---|
536 | /* This option is obsolete, but accept it for backward
|
---|
537 | compatibility. */
|
---|
538 | break;
|
---|
539 |
|
---|
540 | case LEFT_COLUMN_OPTION:
|
---|
541 | left_column = 1;
|
---|
542 | break;
|
---|
543 |
|
---|
544 | case LINE_FORMAT_OPTION:
|
---|
545 | specify_style (OUTPUT_IFDEF);
|
---|
546 | for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
|
---|
547 | specify_value (&line_format[i], optarg, "--line-format");
|
---|
548 | break;
|
---|
549 |
|
---|
550 | case NO_IGNORE_FILE_NAME_CASE_OPTION:
|
---|
551 | ignore_file_name_case = 0;
|
---|
552 | break;
|
---|
553 |
|
---|
554 | case NORMAL_OPTION:
|
---|
555 | specify_style (OUTPUT_NORMAL);
|
---|
556 | break;
|
---|
557 |
|
---|
558 | case SDIFF_MERGE_ASSIST_OPTION:
|
---|
559 | specify_style (OUTPUT_SDIFF);
|
---|
560 | sdiff_merge_assist = 1;
|
---|
561 | break;
|
---|
562 |
|
---|
563 | case STRIP_TRAILING_CR_OPTION:
|
---|
564 | strip_trailing_cr = 1;
|
---|
565 | break;
|
---|
566 |
|
---|
567 | case SUPPRESS_COMMON_LINES_OPTION:
|
---|
568 | suppress_common_lines = 1;
|
---|
569 | break;
|
---|
570 |
|
---|
571 | case TO_FILE_OPTION:
|
---|
572 | specify_value (&to_file, optarg, "--to-file");
|
---|
573 | break;
|
---|
574 |
|
---|
575 | case UNCHANGED_LINE_FORMAT_OPTION:
|
---|
576 | case OLD_LINE_FORMAT_OPTION:
|
---|
577 | case NEW_LINE_FORMAT_OPTION:
|
---|
578 | specify_style (OUTPUT_IFDEF);
|
---|
579 | c -= UNCHANGED_LINE_FORMAT_OPTION;
|
---|
580 | specify_value (&line_format[c], optarg, line_format_option[c]);
|
---|
581 | break;
|
---|
582 |
|
---|
583 | case UNCHANGED_GROUP_FORMAT_OPTION:
|
---|
584 | case OLD_GROUP_FORMAT_OPTION:
|
---|
585 | case NEW_GROUP_FORMAT_OPTION:
|
---|
586 | case CHANGED_GROUP_FORMAT_OPTION:
|
---|
587 | specify_style (OUTPUT_IFDEF);
|
---|
588 | c -= UNCHANGED_GROUP_FORMAT_OPTION;
|
---|
589 | specify_value (&group_format[c], optarg, group_format_option[c]);
|
---|
590 | break;
|
---|
591 |
|
---|
592 | default:
|
---|
593 | try_help (0, 0);
|
---|
594 | }
|
---|
595 | prev = c;
|
---|
596 | }
|
---|
597 |
|
---|
598 | if (output_style == OUTPUT_UNSPECIFIED)
|
---|
599 | {
|
---|
600 | if (show_c_function)
|
---|
601 | {
|
---|
602 | specify_style (OUTPUT_CONTEXT);
|
---|
603 | if (ocontext < 0)
|
---|
604 | context = 3;
|
---|
605 | }
|
---|
606 | else
|
---|
607 | specify_style (OUTPUT_NORMAL);
|
---|
608 | }
|
---|
609 |
|
---|
610 | if (output_style != OUTPUT_CONTEXT || hard_locale (LC_TIME))
|
---|
611 | time_format = "%Y-%m-%d %H:%M:%S.%N %z";
|
---|
612 | else
|
---|
613 | {
|
---|
614 | /* See POSIX 1003.1-2001 for this format. */
|
---|
615 | time_format = "%a %b %e %T %Y";
|
---|
616 | }
|
---|
617 |
|
---|
618 | if (0 <= ocontext)
|
---|
619 | {
|
---|
620 | bool modern_usage = 200112 <= posix2_version ();
|
---|
621 |
|
---|
622 | if ((output_style == OUTPUT_CONTEXT
|
---|
623 | || output_style == OUTPUT_UNIFIED)
|
---|
624 | && (context < ocontext
|
---|
625 | || (ocontext < context && ! explicit_context)))
|
---|
626 | {
|
---|
627 | if (modern_usage)
|
---|
628 | {
|
---|
629 | error (0, 0,
|
---|
630 | _("`-%ld' option is obsolete; use `-%c %ld'"),
|
---|
631 | (long) ocontext,
|
---|
632 | output_style == OUTPUT_CONTEXT ? 'C' : 'U',
|
---|
633 | (long) ocontext);
|
---|
634 | try_help (0, 0);
|
---|
635 | }
|
---|
636 | context = ocontext;
|
---|
637 | }
|
---|
638 | else
|
---|
639 | {
|
---|
640 | if (modern_usage)
|
---|
641 | {
|
---|
642 | error (0, 0, _("`-%ld' option is obsolete; omit it"),
|
---|
643 | (long) ocontext);
|
---|
644 | try_help (0, 0);
|
---|
645 | }
|
---|
646 | }
|
---|
647 | }
|
---|
648 |
|
---|
649 | {
|
---|
650 | /*
|
---|
651 | * We maximize first the half line width, and then the gutter width,
|
---|
652 | * according to the following constraints:
|
---|
653 | * 1. Two half lines plus a gutter must fit in a line.
|
---|
654 | * 2. If the half line width is nonzero:
|
---|
655 | * a. The gutter width is at least GUTTER_WIDTH_MINIMUM.
|
---|
656 | * b. If tabs are not expanded to spaces,
|
---|
657 | * a half line plus a gutter is an integral number of tabs,
|
---|
658 | * so that tabs in the right column line up.
|
---|
659 | */
|
---|
660 | unsigned int t = expand_tabs ? 1 : TAB_WIDTH;
|
---|
661 | int w = width ? width : 130;
|
---|
662 | int off = (w + t + GUTTER_WIDTH_MINIMUM) / (2 * t) * t;
|
---|
663 | sdiff_half_width = MAX (0, MIN (off - GUTTER_WIDTH_MINIMUM, w - off)),
|
---|
664 | sdiff_column2_offset = sdiff_half_width ? off : w;
|
---|
665 | }
|
---|
666 |
|
---|
667 | /* Make the horizon at least as large as the context, so that
|
---|
668 | shift_boundaries has more freedom to shift the first and last hunks. */
|
---|
669 | if (horizon_lines < context)
|
---|
670 | horizon_lines = context;
|
---|
671 |
|
---|
672 | summarize_regexp_list (&function_regexp_list);
|
---|
673 | summarize_regexp_list (&ignore_regexp_list);
|
---|
674 |
|
---|
675 | if (output_style == OUTPUT_IFDEF)
|
---|
676 | {
|
---|
677 | for (i = 0; i < sizeof line_format / sizeof *line_format; i++)
|
---|
678 | if (!line_format[i])
|
---|
679 | line_format[i] = "%l\n";
|
---|
680 | if (!group_format[OLD])
|
---|
681 | group_format[OLD]
|
---|
682 | = group_format[CHANGED] ? group_format[CHANGED] : "%<";
|
---|
683 | if (!group_format[NEW])
|
---|
684 | group_format[NEW]
|
---|
685 | = group_format[CHANGED] ? group_format[CHANGED] : "%>";
|
---|
686 | if (!group_format[UNCHANGED])
|
---|
687 | group_format[UNCHANGED] = "%=";
|
---|
688 | if (!group_format[CHANGED])
|
---|
689 | group_format[CHANGED] = concat (group_format[OLD],
|
---|
690 | group_format[NEW], "");
|
---|
691 | }
|
---|
692 |
|
---|
693 | no_diff_means_no_output =
|
---|
694 | (output_style == OUTPUT_IFDEF ?
|
---|
695 | (!*group_format[UNCHANGED]
|
---|
696 | || (strcmp (group_format[UNCHANGED], "%=") == 0
|
---|
697 | && !*line_format[UNCHANGED]))
|
---|
698 | : (output_style != OUTPUT_SDIFF) | suppress_common_lines);
|
---|
699 |
|
---|
700 | files_can_be_treated_as_binary =
|
---|
701 | (brief
|
---|
702 | & ~ (ignore_blank_lines | ignore_case | strip_trailing_cr
|
---|
703 | | (ignore_regexp_list.regexps || ignore_white_space)));
|
---|
704 |
|
---|
705 | switch_string = option_list (argv + 1, optind - 1);
|
---|
706 |
|
---|
707 | if (from_file)
|
---|
708 | {
|
---|
709 | if (to_file)
|
---|
710 | fatal ("--from-file and --to-file both specified");
|
---|
711 | else
|
---|
712 | for (; optind < argc; optind++)
|
---|
713 | {
|
---|
714 | int status = compare_files ((struct comparison *) 0,
|
---|
715 | from_file, argv[optind]);
|
---|
716 | if (exit_status < status)
|
---|
717 | exit_status = status;
|
---|
718 | }
|
---|
719 | }
|
---|
720 | else
|
---|
721 | {
|
---|
722 | if (to_file)
|
---|
723 | for (; optind < argc; optind++)
|
---|
724 | {
|
---|
725 | int status = compare_files ((struct comparison *) 0,
|
---|
726 | argv[optind], to_file);
|
---|
727 | if (exit_status < status)
|
---|
728 | exit_status = status;
|
---|
729 | }
|
---|
730 | else
|
---|
731 | {
|
---|
732 | if (argc - optind != 2)
|
---|
733 | {
|
---|
734 | if (argc - optind < 2)
|
---|
735 | try_help ("missing operand after `%s'", argv[argc - 1]);
|
---|
736 | else
|
---|
737 | try_help ("extra operand `%s'", argv[optind + 2]);
|
---|
738 | }
|
---|
739 |
|
---|
740 | exit_status = compare_files ((struct comparison *) 0,
|
---|
741 | argv[optind], argv[optind + 1]);
|
---|
742 | }
|
---|
743 | }
|
---|
744 |
|
---|
745 | /* Print any messages that were saved up for last. */
|
---|
746 | print_message_queue ();
|
---|
747 |
|
---|
748 | check_stdout ();
|
---|
749 | exit (exit_status);
|
---|
750 | return exit_status;
|
---|
751 | }
|
---|
752 |
|
---|
753 | /* Append to REGLIST the regexp PATTERN. */
|
---|
754 |
|
---|
755 | static void
|
---|
756 | add_regexp (struct regexp_list *reglist, char const *pattern)
|
---|
757 | {
|
---|
758 | size_t patlen = strlen (pattern);
|
---|
759 | char const *m = re_compile_pattern (pattern, patlen, reglist->buf);
|
---|
760 |
|
---|
761 | if (m != 0)
|
---|
762 | error (0, 0, "%s: %s", pattern, m);
|
---|
763 | else
|
---|
764 | {
|
---|
765 | char *regexps = reglist->regexps;
|
---|
766 | size_t len = reglist->len;
|
---|
767 | bool multiple_regexps = reglist->multiple_regexps = regexps != 0;
|
---|
768 | size_t newlen = reglist->len = len + 2 * multiple_regexps + patlen;
|
---|
769 | size_t size = reglist->size;
|
---|
770 |
|
---|
771 | if (size <= newlen)
|
---|
772 | {
|
---|
773 | if (!size)
|
---|
774 | size = 1;
|
---|
775 |
|
---|
776 | do size *= 2;
|
---|
777 | while (size <= newlen);
|
---|
778 |
|
---|
779 | reglist->size = size;
|
---|
780 | reglist->regexps = regexps = xrealloc (regexps, size);
|
---|
781 | }
|
---|
782 | if (multiple_regexps)
|
---|
783 | {
|
---|
784 | regexps[len++] = '\\';
|
---|
785 | regexps[len++] = '|';
|
---|
786 | }
|
---|
787 | memcpy (regexps + len, pattern, patlen + 1);
|
---|
788 | }
|
---|
789 | }
|
---|
790 |
|
---|
791 | /* Ensure that REGLIST represents the disjunction of its regexps.
|
---|
792 | This is done here, rather than earlier, to avoid O(N^2) behavior. */
|
---|
793 |
|
---|
794 | static void
|
---|
795 | summarize_regexp_list (struct regexp_list *reglist)
|
---|
796 | {
|
---|
797 | if (reglist->regexps)
|
---|
798 | {
|
---|
799 | /* At least one regexp was specified. Allocate a fastmap for it. */
|
---|
800 | reglist->buf->fastmap = xmalloc (1 << CHAR_BIT);
|
---|
801 | if (reglist->multiple_regexps)
|
---|
802 | {
|
---|
803 | /* Compile the disjunction of the regexps.
|
---|
804 | (If just one regexp was specified, it is already compiled.) */
|
---|
805 | char const *m = re_compile_pattern (reglist->regexps, reglist->len,
|
---|
806 | reglist->buf);
|
---|
807 | if (m != 0)
|
---|
808 | error (EXIT_TROUBLE, 0, "%s: %s", reglist->regexps, m);
|
---|
809 | }
|
---|
810 | }
|
---|
811 | }
|
---|
812 |
|
---|
813 | static void
|
---|
814 | try_help (char const *reason_msgid, char const *operand)
|
---|
815 | {
|
---|
816 | if (reason_msgid)
|
---|
817 | error (0, 0, _(reason_msgid), operand);
|
---|
818 | error (EXIT_TROUBLE, 0, _("Try `%s --help' for more information."),
|
---|
819 | program_name);
|
---|
820 | abort ();
|
---|
821 | }
|
---|
822 |
|
---|
823 | static void
|
---|
824 | check_stdout (void)
|
---|
825 | {
|
---|
826 | if (ferror (stdout))
|
---|
827 | fatal ("write failed");
|
---|
828 | else if (fclose (stdout) != 0)
|
---|
829 | pfatal_with_name (_("standard output"));
|
---|
830 | }
|
---|
831 |
|
---|
832 | static char const * const option_help_msgid[] = {
|
---|
833 | N_("Compare files line by line."),
|
---|
834 | "",
|
---|
835 | N_("-i --ignore-case Ignore case differences in file contents."),
|
---|
836 | N_("--ignore-file-name-case Ignore case when comparing file names."),
|
---|
837 | N_("--no-ignore-file-name-case Consider case when comparing file names."),
|
---|
838 | N_("-E --ignore-tab-expansion Ignore changes due to tab expansion."),
|
---|
839 | N_("-b --ignore-space-change Ignore changes in the amount of white space."),
|
---|
840 | N_("-w --ignore-all-space Ignore all white space."),
|
---|
841 | N_("-B --ignore-blank-lines Ignore changes whose lines are all blank."),
|
---|
842 | N_("-I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE."),
|
---|
843 | N_("--strip-trailing-cr Strip trailing carriage return on input."),
|
---|
844 | #if HAVE_SETMODE_DOS
|
---|
845 | N_("--binary Read and write data in binary mode."),
|
---|
846 | #endif
|
---|
847 | N_("-a --text Treat all files as text."),
|
---|
848 | "",
|
---|
849 | N_("-c -C NUM --context[=NUM] Output NUM (default 3) lines of copied context.\n\
|
---|
850 | -u -U NUM --unified[=NUM] Output NUM (default 3) lines of unified context.\n\
|
---|
851 | --label LABEL Use LABEL instead of file name.\n\
|
---|
852 | -p --show-c-function Show which C function each change is in.\n\
|
---|
853 | -F RE --show-function-line=RE Show the most recent line matching RE."),
|
---|
854 | N_("-q --brief Output only whether files differ."),
|
---|
855 | N_("-e --ed Output an ed script."),
|
---|
856 | N_("--normal Output a normal diff."),
|
---|
857 | N_("-n --rcs Output an RCS format diff."),
|
---|
858 | N_("-y --side-by-side Output in two columns.\n\
|
---|
859 | -W NUM --width=NUM Output at most NUM (default 130) print columns.\n\
|
---|
860 | --left-column Output only the left column of common lines.\n\
|
---|
861 | --suppress-common-lines Do not output common lines."),
|
---|
862 | N_("-D NAME --ifdef=NAME Output merged file to show `#ifdef NAME' diffs."),
|
---|
863 | N_("--GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT."),
|
---|
864 | N_("--line-format=LFMT Similar, but format all input lines with LFMT."),
|
---|
865 | N_("--LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT."),
|
---|
866 | N_(" LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'."),
|
---|
867 | N_(" GFMT may contain:\n\
|
---|
868 | %< lines from FILE1\n\
|
---|
869 | %> lines from FILE2\n\
|
---|
870 | %= lines common to FILE1 and FILE2\n\
|
---|
871 | %[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n\
|
---|
872 | LETTERs are as follows for new group, lower case for old group:\n\
|
---|
873 | F first line number\n\
|
---|
874 | L last line number\n\
|
---|
875 | N number of lines = L-F+1\n\
|
---|
876 | E F-1\n\
|
---|
877 | M L+1"),
|
---|
878 | N_(" LFMT may contain:\n\
|
---|
879 | %L contents of line\n\
|
---|
880 | %l contents of line, excluding any trailing newline\n\
|
---|
881 | %[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number"),
|
---|
882 | N_(" Either GFMT or LFMT may contain:\n\
|
---|
883 | %% %\n\
|
---|
884 | %c'C' the single character C\n\
|
---|
885 | %c'\\OOO' the character with octal code OOO"),
|
---|
886 | "",
|
---|
887 | N_("-l --paginate Pass the output through `pr' to paginate it."),
|
---|
888 | N_("-t --expand-tabs Expand tabs to spaces in output."),
|
---|
889 | N_("-T --initial-tab Make tabs line up by prepending a tab."),
|
---|
890 | "",
|
---|
891 | N_("-r --recursive Recursively compare any subdirectories found."),
|
---|
892 | N_("-N --new-file Treat absent files as empty."),
|
---|
893 | N_("--unidirectional-new-file Treat absent first files as empty."),
|
---|
894 | N_("-s --report-identical-files Report when two files are the same."),
|
---|
895 | N_("-x PAT --exclude=PAT Exclude files that match PAT."),
|
---|
896 | N_("-X FILE --exclude-from=FILE Exclude files that match any pattern in FILE."),
|
---|
897 | N_("-S FILE --starting-file=FILE Start with FILE when comparing directories."),
|
---|
898 | N_("--from-file=FILE1 Compare FILE1 to all operands. FILE1 can be a directory."),
|
---|
899 | N_("--to-file=FILE2 Compare all operands to FILE2. FILE2 can be a directory."),
|
---|
900 | "",
|
---|
901 | N_("--horizon-lines=NUM Keep NUM lines of the common prefix and suffix."),
|
---|
902 | N_("-d --minimal Try hard to find a smaller set of changes."),
|
---|
903 | N_("--speed-large-files Assume large files and many scattered small changes."),
|
---|
904 | "",
|
---|
905 | N_("-v --version Output version info."),
|
---|
906 | N_("--help Output this help."),
|
---|
907 | "",
|
---|
908 | N_("FILES are `FILE1 FILE2' or `DIR1 DIR2' or `DIR FILE...' or `FILE... DIR'."),
|
---|
909 | N_("If --from-file or --to-file is given, there are no restrictions on FILES."),
|
---|
910 | N_("If a FILE is `-', read standard input."),
|
---|
911 | "",
|
---|
912 | N_("Report bugs to <bug-gnu-utils@gnu.org>."),
|
---|
913 | 0
|
---|
914 | };
|
---|
915 |
|
---|
916 | static void
|
---|
917 | usage (void)
|
---|
918 | {
|
---|
919 | char const * const *p;
|
---|
920 |
|
---|
921 | printf (_("Usage: %s [OPTION]... FILES\n"), program_name);
|
---|
922 |
|
---|
923 | for (p = option_help_msgid; *p; p++)
|
---|
924 | {
|
---|
925 | if (!**p)
|
---|
926 | putchar ('\n');
|
---|
927 | else
|
---|
928 | {
|
---|
929 | char const *msg = _(*p);
|
---|
930 | char const *nl;
|
---|
931 | while ((nl = strchr (msg, '\n')))
|
---|
932 | {
|
---|
933 | int msglen = nl + 1 - msg;
|
---|
934 | printf (" %.*s", msglen, msg);
|
---|
935 | msg = nl + 1;
|
---|
936 | }
|
---|
937 |
|
---|
938 | printf (" %s\n" + 2 * (*msg != ' ' && *msg != '-'), msg);
|
---|
939 | }
|
---|
940 | }
|
---|
941 | }
|
---|
942 |
|
---|
943 | /* Set VAR to VALUE, reporting an OPTION error if this is a
|
---|
944 | conflict. */
|
---|
945 | static void
|
---|
946 | specify_value (char const **var, char const *value, char const *option)
|
---|
947 | {
|
---|
948 | if (*var && strcmp (*var, value) != 0)
|
---|
949 | {
|
---|
950 | error (0, 0, _("conflicting %s option value `%s'"), option, value);
|
---|
951 | try_help (0, 0);
|
---|
952 | }
|
---|
953 | *var = value;
|
---|
954 | }
|
---|
955 |
|
---|
956 | /* Set the output style to STYLE, diagnosing conflicts. */
|
---|
957 | static void
|
---|
958 | specify_style (enum output_style style)
|
---|
959 | {
|
---|
960 | if (output_style != style)
|
---|
961 | {
|
---|
962 | if (output_style != OUTPUT_UNSPECIFIED)
|
---|
963 | try_help ("conflicting output style options", 0);
|
---|
964 | output_style = style;
|
---|
965 | }
|
---|
966 | }
|
---|
967 | |
---|
968 |
|
---|
969 | static char const *
|
---|
970 | filetype (struct stat const *st)
|
---|
971 | {
|
---|
972 | /* See POSIX 1003.1-2001 for these formats.
|
---|
973 |
|
---|
974 | To keep diagnostics grammatical in English, the returned string
|
---|
975 | must start with a consonant. */
|
---|
976 |
|
---|
977 | if (S_ISREG (st->st_mode))
|
---|
978 | return st->st_size == 0 ? _("regular empty file") : _("regular file");
|
---|
979 |
|
---|
980 | if (S_ISDIR (st->st_mode)) return _("directory");
|
---|
981 |
|
---|
982 | #ifdef S_ISBLK
|
---|
983 | if (S_ISBLK (st->st_mode)) return _("block special file");
|
---|
984 | #endif
|
---|
985 | #ifdef S_ISCHR
|
---|
986 | if (S_ISCHR (st->st_mode)) return _("character special file");
|
---|
987 | #endif
|
---|
988 | #ifdef S_ISFIFO
|
---|
989 | if (S_ISFIFO (st->st_mode)) return _("fifo");
|
---|
990 | #endif
|
---|
991 | /* S_ISLNK is impossible with `fstat' and `stat'. */
|
---|
992 | #ifdef S_ISSOCK
|
---|
993 | if (S_ISSOCK (st->st_mode)) return _("socket");
|
---|
994 | #endif
|
---|
995 | #ifdef S_TYPEISMQ
|
---|
996 | if (S_TYPEISMQ (st)) return _("message queue");
|
---|
997 | #endif
|
---|
998 | #ifdef S_TYPEISSEM
|
---|
999 | if (S_TYPEISSEM (st)) return _("semaphore");
|
---|
1000 | #endif
|
---|
1001 | #ifdef S_TYPEISSHM
|
---|
1002 | if (S_TYPEISSHM (st)) return _("shared memory object");
|
---|
1003 | #endif
|
---|
1004 | #ifdef S_TYPEISTMO
|
---|
1005 | if (S_TYPEISTMO (st)) return _("typed memory object");
|
---|
1006 | #endif
|
---|
1007 |
|
---|
1008 | return _("weird file");
|
---|
1009 | }
|
---|
1010 | |
---|
1011 |
|
---|
1012 | /* Set the last-modified time of *ST to be the current time. */
|
---|
1013 |
|
---|
1014 | static void
|
---|
1015 | set_mtime_to_now (struct stat *st)
|
---|
1016 | {
|
---|
1017 | #ifdef ST_MTIM_NSEC
|
---|
1018 |
|
---|
1019 | # if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME
|
---|
1020 | if (clock_gettime (CLOCK_REALTIME, &st->st_mtim) == 0)
|
---|
1021 | return;
|
---|
1022 | # endif
|
---|
1023 |
|
---|
1024 | # if HAVE_GETTIMEOFDAY
|
---|
1025 | {
|
---|
1026 | struct timeval timeval;
|
---|
1027 | if (gettimeofday (&timeval, NULL) == 0)
|
---|
1028 | {
|
---|
1029 | st->st_mtime = timeval.tv_sec;
|
---|
1030 | st->st_mtim.ST_MTIM_NSEC = timeval.tv_usec * 1000;
|
---|
1031 | return;
|
---|
1032 | }
|
---|
1033 | }
|
---|
1034 | # endif
|
---|
1035 |
|
---|
1036 | #endif /* ST_MTIM_NSEC */
|
---|
1037 |
|
---|
1038 | time (&st->st_mtime);
|
---|
1039 | }
|
---|
1040 | |
---|
1041 |
|
---|
1042 | /* Compare two files (or dirs) with parent comparison PARENT
|
---|
1043 | and names NAME0 and NAME1.
|
---|
1044 | (If PARENT is 0, then the first name is just NAME0, etc.)
|
---|
1045 | This is self-contained; it opens the files and closes them.
|
---|
1046 |
|
---|
1047 | Value is EXIT_SUCCESS if files are the same, EXIT_FAILURE if
|
---|
1048 | different, EXIT_TROUBLE if there is a problem opening them. */
|
---|
1049 |
|
---|
1050 | static int
|
---|
1051 | compare_files (struct comparison const *parent,
|
---|
1052 | char const *name0,
|
---|
1053 | char const *name1)
|
---|
1054 | {
|
---|
1055 | struct comparison cmp;
|
---|
1056 | #define DIR_P(f) (S_ISDIR (cmp.file[f].stat.st_mode) != 0)
|
---|
1057 | register int f;
|
---|
1058 | int status = EXIT_SUCCESS;
|
---|
1059 | bool same_files;
|
---|
1060 | char *free0, *free1;
|
---|
1061 |
|
---|
1062 | /* If this is directory comparison, perhaps we have a file
|
---|
1063 | that exists only in one of the directories.
|
---|
1064 | If so, just print a message to that effect. */
|
---|
1065 |
|
---|
1066 | if (! ((name0 && name1)
|
---|
1067 | || (unidirectional_new_file && name1)
|
---|
1068 | || new_file))
|
---|
1069 | {
|
---|
1070 | char const *name = name0 == 0 ? name1 : name0;
|
---|
1071 | char const *dir = parent->file[name0 == 0].name;
|
---|
1072 |
|
---|
1073 | /* See POSIX 1003.1-2001 for this format. */
|
---|
1074 | message ("Only in %s: %s\n", dir, name);
|
---|
1075 |
|
---|
1076 | /* Return EXIT_FAILURE so that diff_dirs will return
|
---|
1077 | EXIT_FAILURE ("some files differ"). */
|
---|
1078 | return EXIT_FAILURE;
|
---|
1079 | }
|
---|
1080 |
|
---|
1081 | memset (cmp.file, 0, sizeof cmp.file);
|
---|
1082 | cmp.parent = parent;
|
---|
1083 |
|
---|
1084 | /* cmp.file[f].desc markers */
|
---|
1085 | #define NONEXISTENT (-1) /* nonexistent file */
|
---|
1086 | #define UNOPENED (-2) /* unopened file (e.g. directory) */
|
---|
1087 | #define ERRNO_ENCODE(errno) (-3 - (errno)) /* encoded errno value */
|
---|
1088 |
|
---|
1089 | #define ERRNO_DECODE(desc) (-3 - (desc)) /* inverse of ERRNO_ENCODE */
|
---|
1090 |
|
---|
1091 | cmp.file[0].desc = name0 == 0 ? NONEXISTENT : UNOPENED;
|
---|
1092 | cmp.file[1].desc = name1 == 0 ? NONEXISTENT : UNOPENED;
|
---|
1093 |
|
---|
1094 | /* Now record the full name of each file, including nonexistent ones. */
|
---|
1095 |
|
---|
1096 | if (name0 == 0)
|
---|
1097 | name0 = name1;
|
---|
1098 | if (name1 == 0)
|
---|
1099 | name1 = name0;
|
---|
1100 |
|
---|
1101 | if (!parent)
|
---|
1102 | {
|
---|
1103 | free0 = 0;
|
---|
1104 | free1 = 0;
|
---|
1105 | cmp.file[0].name = name0;
|
---|
1106 | cmp.file[1].name = name1;
|
---|
1107 | }
|
---|
1108 | else
|
---|
1109 | {
|
---|
1110 | cmp.file[0].name = free0
|
---|
1111 | = dir_file_pathname (parent->file[0].name, name0);
|
---|
1112 | cmp.file[1].name = free1
|
---|
1113 | = dir_file_pathname (parent->file[1].name, name1);
|
---|
1114 | }
|
---|
1115 |
|
---|
1116 | /* Stat the files. */
|
---|
1117 |
|
---|
1118 | for (f = 0; f < 2; f++)
|
---|
1119 | {
|
---|
1120 | if (cmp.file[f].desc != NONEXISTENT)
|
---|
1121 | {
|
---|
1122 | if (f && file_name_cmp (cmp.file[f].name, cmp.file[0].name) == 0)
|
---|
1123 | {
|
---|
1124 | cmp.file[f].desc = cmp.file[0].desc;
|
---|
1125 | cmp.file[f].stat = cmp.file[0].stat;
|
---|
1126 | }
|
---|
1127 | else if (strcmp (cmp.file[f].name, "-") == 0)
|
---|
1128 | {
|
---|
1129 | cmp.file[f].desc = STDIN_FILENO;
|
---|
1130 | if (fstat (STDIN_FILENO, &cmp.file[f].stat) != 0)
|
---|
1131 | cmp.file[f].desc = ERRNO_ENCODE (errno);
|
---|
1132 | else
|
---|
1133 | {
|
---|
1134 | if (S_ISREG (cmp.file[f].stat.st_mode))
|
---|
1135 | {
|
---|
1136 | off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
|
---|
1137 | if (pos < 0)
|
---|
1138 | cmp.file[f].desc = ERRNO_ENCODE (errno);
|
---|
1139 | else
|
---|
1140 | cmp.file[f].stat.st_size =
|
---|
1141 | MAX (0, cmp.file[f].stat.st_size - pos);
|
---|
1142 | }
|
---|
1143 |
|
---|
1144 | /* POSIX 1003.1-2001 requires current time for
|
---|
1145 | stdin. */
|
---|
1146 | set_mtime_to_now (&cmp.file[f].stat);
|
---|
1147 | }
|
---|
1148 | }
|
---|
1149 | else if (stat (cmp.file[f].name, &cmp.file[f].stat) != 0)
|
---|
1150 | cmp.file[f].desc = ERRNO_ENCODE (errno);
|
---|
1151 | }
|
---|
1152 | }
|
---|
1153 |
|
---|
1154 | /* Mark files as nonexistent at the top level as needed for -N and
|
---|
1155 | --unidirectional-new-file. */
|
---|
1156 | if (! parent)
|
---|
1157 | {
|
---|
1158 | if ((new_file | unidirectional_new_file)
|
---|
1159 | && cmp.file[0].desc == ERRNO_ENCODE (ENOENT)
|
---|
1160 | && cmp.file[1].desc == UNOPENED)
|
---|
1161 | cmp.file[0].desc = NONEXISTENT;
|
---|
1162 |
|
---|
1163 | if (new_file
|
---|
1164 | && cmp.file[0].desc == UNOPENED
|
---|
1165 | && cmp.file[1].desc == ERRNO_ENCODE (ENOENT))
|
---|
1166 | cmp.file[1].desc = NONEXISTENT;
|
---|
1167 | }
|
---|
1168 |
|
---|
1169 | for (f = 0; f < 2; f++)
|
---|
1170 | if (cmp.file[f].desc == NONEXISTENT)
|
---|
1171 | cmp.file[f].stat.st_mode = cmp.file[1 - f].stat.st_mode;
|
---|
1172 |
|
---|
1173 | for (f = 0; f < 2; f++)
|
---|
1174 | {
|
---|
1175 | int e = ERRNO_DECODE (cmp.file[f].desc);
|
---|
1176 | if (0 <= e)
|
---|
1177 | {
|
---|
1178 | errno = e;
|
---|
1179 | perror_with_name (cmp.file[f].name);
|
---|
1180 | status = EXIT_TROUBLE;
|
---|
1181 | }
|
---|
1182 | }
|
---|
1183 |
|
---|
1184 | if (status == EXIT_SUCCESS && ! parent && DIR_P (0) != DIR_P (1))
|
---|
1185 | {
|
---|
1186 | /* If one is a directory, and it was specified in the command line,
|
---|
1187 | use the file in that dir with the other file's basename. */
|
---|
1188 |
|
---|
1189 | int fnm_arg = DIR_P (0);
|
---|
1190 | int dir_arg = 1 - fnm_arg;
|
---|
1191 | char const *fnm = cmp.file[fnm_arg].name;
|
---|
1192 | char const *dir = cmp.file[dir_arg].name;
|
---|
1193 | char const *filename = cmp.file[dir_arg].name = free0
|
---|
1194 | = dir_file_pathname (dir, base_name (fnm));
|
---|
1195 |
|
---|
1196 | if (strcmp (fnm, "-") == 0)
|
---|
1197 | fatal ("cannot compare `-' to a directory");
|
---|
1198 |
|
---|
1199 | if (stat (filename, &cmp.file[dir_arg].stat) != 0)
|
---|
1200 | {
|
---|
1201 | perror_with_name (filename);
|
---|
1202 | status = EXIT_TROUBLE;
|
---|
1203 | }
|
---|
1204 | }
|
---|
1205 |
|
---|
1206 | if (status != EXIT_SUCCESS)
|
---|
1207 | {
|
---|
1208 | /* One of the files should exist but does not. */
|
---|
1209 | }
|
---|
1210 | else if ((same_files
|
---|
1211 | = (cmp.file[0].desc != NONEXISTENT
|
---|
1212 | && cmp.file[1].desc != NONEXISTENT
|
---|
1213 | && 0 < same_file (&cmp.file[0].stat, &cmp.file[1].stat)
|
---|
1214 | && same_file_attributes (&cmp.file[0].stat,
|
---|
1215 | &cmp.file[1].stat)))
|
---|
1216 | && no_diff_means_no_output)
|
---|
1217 | {
|
---|
1218 | /* The two named files are actually the same physical file.
|
---|
1219 | We know they are identical without actually reading them. */
|
---|
1220 | }
|
---|
1221 | else if (DIR_P (0) & DIR_P (1))
|
---|
1222 | {
|
---|
1223 | if (output_style == OUTPUT_IFDEF)
|
---|
1224 | fatal ("-D option not supported with directories");
|
---|
1225 |
|
---|
1226 | /* If both are directories, compare the files in them. */
|
---|
1227 |
|
---|
1228 | if (parent && !recursive)
|
---|
1229 | {
|
---|
1230 | /* But don't compare dir contents one level down
|
---|
1231 | unless -r was specified.
|
---|
1232 | See POSIX 1003.1-2001 for this format. */
|
---|
1233 | message ("Common subdirectories: %s and %s\n",
|
---|
1234 | cmp.file[0].name, cmp.file[1].name);
|
---|
1235 | }
|
---|
1236 | else
|
---|
1237 | status = diff_dirs (&cmp, compare_files);
|
---|
1238 | }
|
---|
1239 | else if ((DIR_P (0) | DIR_P (1))
|
---|
1240 | || (parent
|
---|
1241 | && (! S_ISREG (cmp.file[0].stat.st_mode)
|
---|
1242 | || ! S_ISREG (cmp.file[1].stat.st_mode))))
|
---|
1243 | {
|
---|
1244 | if (cmp.file[0].desc == NONEXISTENT || cmp.file[1].desc == NONEXISTENT)
|
---|
1245 | {
|
---|
1246 | /* We have a subdirectory that exists only in one directory. */
|
---|
1247 |
|
---|
1248 | if ((DIR_P (0) | DIR_P (1))
|
---|
1249 | && recursive
|
---|
1250 | && (new_file
|
---|
1251 | || (unidirectional_new_file
|
---|
1252 | && cmp.file[0].desc == NONEXISTENT)))
|
---|
1253 | status = diff_dirs (&cmp, compare_files);
|
---|
1254 | else
|
---|
1255 | {
|
---|
1256 | char const *dir
|
---|
1257 | = parent->file[cmp.file[0].desc == NONEXISTENT].name;
|
---|
1258 |
|
---|
1259 | /* See POSIX 1003.1-2001 for this format. */
|
---|
1260 | message ("Only in %s: %s\n", dir, name0);
|
---|
1261 |
|
---|
1262 | status = EXIT_FAILURE;
|
---|
1263 | }
|
---|
1264 | }
|
---|
1265 | else
|
---|
1266 | {
|
---|
1267 | /* We have two files that are not to be compared. */
|
---|
1268 |
|
---|
1269 | /* See POSIX 1003.1-2001 for this format. */
|
---|
1270 | message5 ("File %s is a %s while file %s is a %s\n",
|
---|
1271 | file_label[0] ? file_label[0] : cmp.file[0].name,
|
---|
1272 | filetype (&cmp.file[0].stat),
|
---|
1273 | file_label[1] ? file_label[1] : cmp.file[1].name,
|
---|
1274 | filetype (&cmp.file[1].stat));
|
---|
1275 |
|
---|
1276 | /* This is a difference. */
|
---|
1277 | status = EXIT_FAILURE;
|
---|
1278 | }
|
---|
1279 | }
|
---|
1280 | else if (files_can_be_treated_as_binary
|
---|
1281 | && cmp.file[0].stat.st_size != cmp.file[1].stat.st_size
|
---|
1282 | && (cmp.file[0].desc == NONEXISTENT
|
---|
1283 | || S_ISREG (cmp.file[0].stat.st_mode))
|
---|
1284 | && (cmp.file[1].desc == NONEXISTENT
|
---|
1285 | || S_ISREG (cmp.file[1].stat.st_mode)))
|
---|
1286 | {
|
---|
1287 | message ("Files %s and %s differ\n",
|
---|
1288 | file_label[0] ? file_label[0] : cmp.file[0].name,
|
---|
1289 | file_label[1] ? file_label[1] : cmp.file[1].name);
|
---|
1290 | status = EXIT_FAILURE;
|
---|
1291 | }
|
---|
1292 | else
|
---|
1293 | {
|
---|
1294 | /* Both exist and neither is a directory. */
|
---|
1295 |
|
---|
1296 | /* Open the files and record their descriptors. */
|
---|
1297 |
|
---|
1298 | if (cmp.file[0].desc == UNOPENED)
|
---|
1299 | if ((cmp.file[0].desc = open (cmp.file[0].name, O_RDONLY, 0)) < 0)
|
---|
1300 | {
|
---|
1301 | perror_with_name (cmp.file[0].name);
|
---|
1302 | status = EXIT_TROUBLE;
|
---|
1303 | }
|
---|
1304 | if (cmp.file[1].desc == UNOPENED)
|
---|
1305 | {
|
---|
1306 | if (same_files)
|
---|
1307 | cmp.file[1].desc = cmp.file[0].desc;
|
---|
1308 | else if ((cmp.file[1].desc = open (cmp.file[1].name, O_RDONLY, 0))
|
---|
1309 | < 0)
|
---|
1310 | {
|
---|
1311 | perror_with_name (cmp.file[1].name);
|
---|
1312 | status = EXIT_TROUBLE;
|
---|
1313 | }
|
---|
1314 | }
|
---|
1315 |
|
---|
1316 | #if HAVE_SETMODE_DOS
|
---|
1317 | if (binary)
|
---|
1318 | for (f = 0; f < 2; f++)
|
---|
1319 | if (0 <= cmp.file[f].desc)
|
---|
1320 | set_binary_mode (cmp.file[f].desc, 1);
|
---|
1321 | #endif
|
---|
1322 |
|
---|
1323 | /* Compare the files, if no error was found. */
|
---|
1324 |
|
---|
1325 | if (status == EXIT_SUCCESS)
|
---|
1326 | status = diff_2_files (&cmp);
|
---|
1327 |
|
---|
1328 | /* Close the file descriptors. */
|
---|
1329 |
|
---|
1330 | if (0 <= cmp.file[0].desc && close (cmp.file[0].desc) != 0)
|
---|
1331 | {
|
---|
1332 | perror_with_name (cmp.file[0].name);
|
---|
1333 | status = EXIT_TROUBLE;
|
---|
1334 | }
|
---|
1335 | if (0 <= cmp.file[1].desc && cmp.file[0].desc != cmp.file[1].desc
|
---|
1336 | && close (cmp.file[1].desc) != 0)
|
---|
1337 | {
|
---|
1338 | perror_with_name (cmp.file[1].name);
|
---|
1339 | status = EXIT_TROUBLE;
|
---|
1340 | }
|
---|
1341 | }
|
---|
1342 |
|
---|
1343 | /* Now the comparison has been done, if no error prevented it,
|
---|
1344 | and STATUS is the value this function will return. */
|
---|
1345 |
|
---|
1346 | if (status == EXIT_SUCCESS)
|
---|
1347 | {
|
---|
1348 | if (report_identical_files && !DIR_P (0))
|
---|
1349 | message ("Files %s and %s are identical\n",
|
---|
1350 | file_label[0] ? file_label[0] : cmp.file[0].name,
|
---|
1351 | file_label[1] ? file_label[1] : cmp.file[1].name);
|
---|
1352 | }
|
---|
1353 | else
|
---|
1354 | {
|
---|
1355 | /* Flush stdout so that the user sees differences immediately.
|
---|
1356 | This can hurt performance, unfortunately. */
|
---|
1357 | if (fflush (stdout) != 0)
|
---|
1358 | pfatal_with_name (_("standard output"));
|
---|
1359 | }
|
---|
1360 |
|
---|
1361 | if (free0)
|
---|
1362 | free (free0);
|
---|
1363 | if (free1)
|
---|
1364 | free (free1);
|
---|
1365 |
|
---|
1366 | return status;
|
---|
1367 | }
|
---|