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