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