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