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