xref: /dragonfly/contrib/diffutils/src/diff.c (revision 6ea1f93e)
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 
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 *
option_list(char ** optionvec,int count)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
exclude_options(void)266 exclude_options (void)
267 {
268   return EXCLUDE_WILDCARDS | (ignore_file_name_case ? FNM_CASEFOLD : 0);
269 }
270 
271 int
main(int argc,char ** argv)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
add_regexp(struct regexp_list * reglist,char const * pattern)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
summarize_regexp_list(struct regexp_list * reglist)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
try_help(char const * reason_msgid,char const * operand)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
check_stdout(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
usage(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
specify_value(char const ** var,char const * value,char const * option)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
specify_style(enum output_style style)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
specify_colors_style(char const * value)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 
1064 /* Set the last-modified time of *ST to be the current time.  */
1065 
1066 static void
set_mtime_to_now(struct stat * st)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 
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
compare_files(struct comparison const * parent,char const * name0,char const * name1)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 }
1476