1 /* GNU DIFF main routine.
2 Copyright (C) 1988, 1989, 1992, 1993, 1994 Free Software Foundation, Inc.
4 This file is part of GNU DIFF.
6 GNU DIFF is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU DIFF is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU DIFF; see the file COPYING. If not, write to
18 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
20 /* GNU DIFF was written by Mike Haertel, David Hayes,
21 Richard Stallman, Len Tower, and Paul Eggert. */
35 #define DEFAULT_WIDTH 130
38 #ifndef GUTTER_WIDTH_MINIMUM
39 #define GUTTER_WIDTH_MINIMUM 3
42 static char const *filetype PARAMS((struct stat const *));
43 static char *option_list PARAMS((char **, int));
44 static int add_exclude_file PARAMS((char const *));
45 static int ck_atoi PARAMS((char const *, int *));
46 static int compare_files PARAMS((char const *, char const *, char const *, char const *, int));
47 static int specify_format PARAMS((char **, char *));
48 static void add_exclude PARAMS((char const *));
49 static void add_regexp PARAMS((struct regexp_list **, char const *));
50 static void specify_style PARAMS((enum output_style));
51 static void try_help PARAMS((char const *));
52 static void check_stdout PARAMS((void));
53 static void usage PARAMS((void));
55 /* Nonzero for -r: if comparing two directories,
56 compare their common subdirectories recursively. */
60 /* For debugging: don't do discard_confusing_lines. */
65 /* I/O mode: nonzero only if using binary input/output. */
66 static int binary_I_O;
69 /* Return a string containing the command options with which diff was invoked.
70 Spaces appear between what were separate ARGV-elements.
71 There is a space at the beginning but none at the end.
72 If there were no options, the result is an empty string.
74 Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
75 the length of that vector. */
78 option_list (optionvec, count)
79 char **optionvec; /* Was `vector', but that collides on Alliant. */
86 for (i = 0; i < count; i++)
87 length += strlen (optionvec[i]) + 1;
89 result = xmalloc (length + 1);
92 for (i = 0; i < count; i++)
95 strcat (result, optionvec[i]);
101 /* Convert STR to a positive integer, storing the result in *OUT.
102 If STR is not a valid integer, return -1 (otherwise 0). */
109 for (p = str; *p; p++)
110 if (*p < '0' || *p > '9')
113 *out = atoi (optarg);
117 /* Keep track of excluded file name patterns. */
119 static char const **exclude;
120 static int exclude_alloc, exclude_count;
123 excluded_filename (f)
127 for (i = 0; i < exclude_count; i++)
128 if (fnmatch (exclude[i], f, 0) == 0)
134 add_exclude (pattern)
137 if (exclude_alloc <= exclude_count)
138 exclude = (char const **)
140 ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
141 : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
143 exclude[exclude_count++] = pattern;
147 add_exclude_file (name)
154 f.desc = (strcmp (optarg, "-") == 0
156 : open (optarg, O_RDONLY, 0));
157 if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
163 for (p = f.buffer, lim = p + f.buffered_chars; p < lim; p = q)
165 q = (char *) memchr (p, '\n', lim - p);
172 return close (f.desc);
175 /* The numbers 129- that appear in the fourth element of some entries
176 tell the big switch in `main' how to process those options. */
178 static struct option const longopts[] =
180 {"ignore-blank-lines", 0, 0, 'B'},
181 {"context", 2, 0, 'C'},
182 {"ifdef", 1, 0, 'D'},
183 {"show-function-line", 1, 0, 'F'},
184 {"speed-large-files", 0, 0, 'H'},
185 {"ignore-matching-lines", 1, 0, 'I'},
186 {"label", 1, 0, 'L'},
187 {"file-label", 1, 0, 'L'}, /* An alias, no longer recommended */
188 {"new-file", 0, 0, 'N'},
189 {"entire-new-file", 0, 0, 'N'}, /* An alias, no longer recommended */
190 {"unidirectional-new-file", 0, 0, 'P'},
191 {"starting-file", 1, 0, 'S'},
192 {"initial-tab", 0, 0, 'T'},
193 {"width", 1, 0, 'W'},
195 {"ascii", 0, 0, 'a'}, /* An alias, no longer recommended */
196 {"ignore-space-change", 0, 0, 'b'},
197 {"minimal", 0, 0, 'd'},
199 {"forward-ed", 0, 0, 'f'},
200 {"ignore-case", 0, 0, 'i'},
201 {"paginate", 0, 0, 'l'},
202 {"print", 0, 0, 'l'}, /* An alias, no longer recommended */
204 {"show-c-function", 0, 0, 'p'},
205 {"brief", 0, 0, 'q'},
206 {"recursive", 0, 0, 'r'},
207 {"report-identical-files", 0, 0, 's'},
208 {"expand-tabs", 0, 0, 't'},
209 {"version", 0, 0, 'v'},
210 {"ignore-all-space", 0, 0, 'w'},
211 {"exclude", 1, 0, 'x'},
212 {"exclude-from", 1, 0, 'X'},
213 {"side-by-side", 0, 0, 'y'},
214 {"unified", 2, 0, 'U'},
215 {"left-column", 0, 0, 129},
216 {"suppress-common-lines", 0, 0, 130},
217 {"sdiff-merge-assist", 0, 0, 131},
218 {"old-line-format", 1, 0, 132},
219 {"new-line-format", 1, 0, 133},
220 {"unchanged-line-format", 1, 0, 134},
221 {"line-format", 1, 0, 135},
222 {"old-group-format", 1, 0, 136},
223 {"new-group-format", 1, 0, 137},
224 {"unchanged-group-format", 1, 0, 138},
225 {"changed-group-format", 1, 0, 139},
226 {"horizon-lines", 1, 0, 140},
228 {"binary", 0, 0, 142},
240 int width = DEFAULT_WIDTH;
241 int show_c_function = 0;
244 setlocale(LC_ALL, "");
246 /* Do our initializations. */
247 initialize_main (&argc, &argv);
248 program_name = argv[0];
249 output_style = OUTPUT_NORMAL;
252 /* Decode the options. */
254 while ((c = getopt_long (argc, argv,
255 "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
256 longopts, 0)) != EOF)
260 /* All digits combine in decimal to specify the context-size. */
273 /* If a context length has already been specified,
274 more digits allowed only if they follow right after the others.
275 Reject two separate runs of digits, or digits after -C. */
276 else if (prev < '0' || prev > '9')
277 fatal ("context length specified twice");
279 context = context * 10 + c - '0';
283 /* Treat all files as text files; never treat as binary. */
284 always_text_flag = 1;
288 /* Ignore changes in amount of white space. */
289 ignore_space_change_flag = 1;
290 ignore_some_changes = 1;
291 ignore_some_line_changes = 1;
295 /* Ignore changes affecting only blank lines. */
296 ignore_blank_lines_flag = 1;
297 ignore_some_changes = 1;
300 case 'C': /* +context[=lines] */
301 case 'U': /* +unified[=lines] */
305 fatal ("context length specified twice");
307 if (ck_atoi (optarg, &context))
308 fatal ("invalid context length argument");
313 /* Make context-style output. */
314 specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
318 /* Don't discard lines. This makes things slower (sometimes much
319 slower) but will find a guaranteed minimal set of changes. */
324 /* Make merged #ifdef output. */
325 specify_style (OUTPUT_IFDEF);
328 static char const C_ifdef_group_formats[] =
329 "#ifndef %s\n%%<#endif /* not %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c%%=%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
330 char *b = xmalloc (sizeof (C_ifdef_group_formats)
331 + 7 * strlen(optarg) - 14 /* 7*"%s" */
332 - 8 /* 5*"%%" + 3*"%c" */);
333 sprintf (b, C_ifdef_group_formats,
335 optarg, optarg, 0, 0,
336 optarg, optarg, optarg);
337 for (i = 0; i < 4; i++)
339 err |= specify_format (&group_format[i], b);
343 error ("conflicting #ifdef formats", 0, 0);
348 /* Make output that is a valid `ed' script. */
349 specify_style (OUTPUT_ED);
353 /* Make output that looks vaguely like an `ed' script
354 but has changes in the order they appear in the file. */
355 specify_style (OUTPUT_FORWARD_ED);
359 /* Show, for each set of changes, the previous line that
360 matches the specified regexp. Currently affects only
361 context-style output. */
362 add_regexp (&function_regexp_list, optarg);
366 /* Split the files into chunks of around 1500 lines
367 for faster processing. Usually does not change the result.
369 This currently has no effect. */
373 /* Turn on heuristics that speed processing of large files
374 with a small density of changes. */
379 /* Ignore changes in case. */
380 ignore_case_flag = 1;
381 ignore_some_changes = 1;
382 ignore_some_line_changes = 1;
386 /* Ignore changes affecting only lines that match the
388 add_regexp (&ignore_regexp_list, optarg);
389 ignore_some_changes = 1;
393 /* Pass the output through `pr' to paginate it. */
395 #if !defined(SIGCHLD) && defined(SIGCLD)
396 #define SIGCHLD SIGCLD
399 /* Pagination requires forking and waiting, and
400 System V fork+wait does not work if SIGCHLD is ignored. */
401 signal (SIGCHLD, SIG_DFL);
406 /* Specify file labels for `-c' output headers. */
408 file_label[0] = optarg;
409 else if (!file_label[1])
410 file_label[1] = optarg;
412 fatal ("too many file label options");
416 /* Output RCS-style diffs, like `-f' except that each command
417 specifies the number of lines affected. */
418 specify_style (OUTPUT_RCS);
422 /* When comparing directories, if a file appears only in one
423 directory, treat it as present but empty in the other. */
424 entire_new_file_flag = 1;
428 /* Make context-style output and show name of last C function. */
430 add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
434 /* When comparing directories, if a file appears only in
435 the second directory of the two,
436 treat it as present but empty in the other. */
437 unidirectional_new_file_flag = 1;
445 /* When comparing directories,
446 recursively compare any subdirectories found. */
451 /* Print a message if the files are the same. */
452 print_file_same_flag = 1;
456 /* When comparing directories, start with the specified
457 file name. This is used for resuming an aborted comparison. */
458 dir_start_file = optarg;
462 /* Expand tabs to spaces in the output so that it preserves
463 the alignment of the input files. */
468 /* Use a tab in the output, rather than a space, before the
469 text of an input line, so as to keep the proper alignment
470 in the input line without changing the characters in it. */
475 /* Output the context diff in unidiff format. */
476 specify_style (OUTPUT_UNIFIED);
480 printf ("diff - GNU diffutils version %s\n", version_string);
484 /* Ignore horizontal white space when comparing lines. */
485 ignore_all_space_flag = 1;
486 ignore_some_changes = 1;
487 ignore_some_line_changes = 1;
491 add_exclude (optarg);
495 if (add_exclude_file (optarg) != 0)
496 pfatal_with_name (optarg);
500 /* Use side-by-side (sdiff-style) columnar output. */
501 specify_style (OUTPUT_SDIFF);
505 /* Set the line width for OUTPUT_SDIFF. */
506 if (ck_atoi (optarg, &width) || width <= 0)
507 fatal ("column width must be a positive integer");
515 sdiff_skip_common_lines = 1;
519 /* sdiff-style columns output. */
520 specify_style (OUTPUT_SDIFF);
521 sdiff_help_sdiff = 1;
527 specify_style (OUTPUT_IFDEF);
528 if (specify_format (&line_format[c - 132], optarg) != 0)
529 error ("conflicting line format", 0, 0);
533 specify_style (OUTPUT_IFDEF);
536 for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
537 err |= specify_format (&line_format[i], optarg);
539 error ("conflicting line format", 0, 0);
547 specify_style (OUTPUT_IFDEF);
548 if (specify_format (&group_format[c - 136], optarg) != 0)
549 error ("conflicting group format", 0, 0);
553 if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0)
554 fatal ("horizon must be a nonnegative integer");
563 /* Use binary I/O when reading and writing data.
564 On Posix hosts, this has no effect. */
567 setmode (STDOUT_FILENO, O_BINARY);
577 if (argc - optind != 2)
578 try_help (argc - optind < 2 ? "missing operand" : "extra operand");
583 * We maximize first the half line width, and then the gutter width,
584 * according to the following constraints:
585 * 1. Two half lines plus a gutter must fit in a line.
586 * 2. If the half line width is nonzero:
587 * a. The gutter width is at least GUTTER_WIDTH_MINIMUM.
588 * b. If tabs are not expanded to spaces,
589 * a half line plus a gutter is an integral number of tabs,
590 * so that tabs in the right column line up.
592 int t = tab_expand_flag ? 1 : TAB_WIDTH;
593 int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t) * t;
594 sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
595 sdiff_column2_offset = sdiff_half_width ? off : width;
598 if (show_c_function && output_style != OUTPUT_UNIFIED)
599 specify_style (OUTPUT_CONTEXT);
601 if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
603 else if (context == -1)
604 /* Default amount of context for -c. */
607 if (output_style == OUTPUT_IFDEF)
609 /* Format arrays are char *, not char const *,
610 because integer formats are temporarily modified.
611 But it is safe to assign a constant like "%=" to a format array,
612 since "%=" does not format any integers. */
614 for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
616 line_format[i] = "%l\n";
617 if (!group_format[OLD])
619 = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
620 if (!group_format[NEW])
622 = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
623 if (!group_format[UNCHANGED])
624 group_format[UNCHANGED] = "%=";
625 if (!group_format[CHANGED])
626 group_format[CHANGED] = concat (group_format[OLD],
627 group_format[NEW], "");
630 no_diff_means_no_output =
631 (output_style == OUTPUT_IFDEF ?
632 (!*group_format[UNCHANGED]
633 || (strcmp (group_format[UNCHANGED], "%=") == 0
634 && !*line_format[UNCHANGED]))
635 : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
637 switch_string = option_list (argv + 1, optind - 1);
639 val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
641 /* Print any messages that were saved up for last. */
642 print_message_queue ();
649 /* Add the compiled form of regexp PATTERN to REGLIST. */
652 add_regexp (reglist, pattern)
653 struct regexp_list **reglist;
656 struct regexp_list *r;
659 r = (struct regexp_list *) xmalloc (sizeof (*r));
660 bzero (r, sizeof (*r));
661 r->buf.fastmap = xmalloc (256);
662 m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
664 error ("%s: %s", pattern, m);
666 /* Add to the start of the list, since it's easier than the end. */
676 error ("%s", reason, 0);
677 error ("Try `%s --help' for more information.", program_name, 0);
684 if (ferror (stdout) || fclose (stdout) != 0)
685 fatal ("write error");
688 static char const * const option_help[] = {
689 "-i --ignore-case Consider upper- and lower-case to be the same.",
690 "-w --ignore-all-space Ignore all white space.",
691 "-b --ignore-space-change Ignore changes in the amount of white space.",
692 "-B --ignore-blank-lines Ignore changes whose lines are all blank.",
693 "-I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.",
695 "--binary Read and write data in binary mode.",
697 "-a --text Treat all files as text.\n",
698 "-c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.",
699 "-u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.",
700 " -NUM Use NUM context lines.",
701 " -L LABEL --label LABEL Use LABEL instead of file name.",
702 " -p --show-c-function Show which C function each change is in.",
703 " -F RE --show-function-line=RE Show the most recent line matching RE.",
704 "-q --brief Output only whether files differ.",
705 "-e --ed Output an ed script.",
706 "-n --rcs Output an RCS format diff.",
707 "-y --side-by-side Output in two columns.",
708 " -w NUM --width=NUM Output at most NUM (default 130) characters per line.",
709 " --left-column Output only the left column of common lines.",
710 " --suppress-common-lines Do not output common lines.",
711 "-DNAME --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.",
712 "--GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.",
713 "--line-format=LFMT Similar, but format all input lines with LFMT.",
714 "--LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.",
715 " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.",
716 " GFMT may contain:",
717 " %< lines from FILE1",
718 " %> lines from FILE2",
719 " %= lines common to FILE1 and FILE2",
720 " %[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER",
721 " LETTERs are as follows for new group, lower case for old group:",
722 " F first line number",
723 " L last line number",
724 " N number of lines = L-F+1",
727 " LFMT may contain:",
728 " %L contents of line",
729 " %l contents of line, excluding any trailing newline",
730 " %[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number",
731 " Either GFMT or LFMT may contain:",
733 " %c'C' the single character C",
734 " %c'\\OOO' the character with octal code OOO\n",
735 "-l --paginate Pass the output through `pr' to paginate it.",
736 "-t --expand-tabs Expand tabs to spaces in output.",
737 "-T --initial-tab Make tabs line up by prepending a tab.\n",
738 "-r --recursive Recursively compare any subdirectories found.",
739 "-N --new-file Treat absent files as empty.",
740 "-P --unidirectional-new-file Treat absent first files as empty.",
741 "-s --report-identical-files Report when two files are the same.",
742 "-x PAT --exclude=PAT Exclude files that match PAT.",
743 "-X FILE --exclude-from=FILE Exclude files that match any pattern in FILE.",
744 "-S FILE --starting-file=FILE Start with FILE when comparing directories.\n",
745 "--horizon-lines=NUM Keep NUM lines of the common prefix and suffix.",
746 "-d --minimal Try hard to find a smaller set of changes.",
747 "-H --speed-large-files Assume large files and many scattered small changes.\n",
748 "-v --version Output version info.",
749 "--help Output this help.",
756 char const * const *p;
758 printf ("Usage: %s [OPTION]... FILE1 FILE2\n\n", program_name);
759 for (p = option_help; *p; p++)
760 printf (" %s\n", *p);
761 printf ("\nIf FILE1 or FILE2 is `-', read standard input.\n");
765 specify_format (var, value)
769 int err = *var ? strcmp (*var, value) : 0;
775 specify_style (style)
776 enum output_style style;
778 if (output_style != OUTPUT_NORMAL
779 && output_style != style)
780 error ("conflicting specifications of output style", 0, 0);
781 output_style = style;
786 struct stat const *st;
788 /* See Posix.2 section 4.17.6.1.1 and Table 5-1 for these formats.
789 To keep diagnostics grammatical, the returned string must start
792 if (S_ISREG (st->st_mode))
794 if (st->st_size == 0)
795 return "regular empty file";
796 /* Posix.2 section 5.14.2 seems to suggest that we must read the file
797 and guess whether it's C, Fortran, etc., but this is somewhat useless
798 and doesn't reflect historical practice. We're allowed to guess
799 wrong, so we don't bother to read the file. */
800 return "regular file";
802 if (S_ISDIR (st->st_mode)) return "directory";
804 /* other Posix.1 file types */
806 if (S_ISBLK (st->st_mode)) return "block special file";
809 if (S_ISCHR (st->st_mode)) return "character special file";
812 if (S_ISFIFO (st->st_mode)) return "fifo";
815 /* other Posix.1b file types */
817 if (S_TYPEISMQ (st)) return "message queue";
820 if (S_TYPEISSEM (st)) return "semaphore";
823 if (S_TYPEISSHM (st)) return "shared memory object";
826 /* other popular file types */
827 /* S_ISLNK is impossible with `fstat' and `stat'. */
829 if (S_ISSOCK (st->st_mode)) return "socket";
835 /* Compare two files (or dirs) with specified names
836 DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
837 (if DIR0 is 0, then the name is just NAME0, etc.)
838 This is self-contained; it opens the files and closes them.
840 Value is 0 if files are the same, 1 if different,
841 2 if there is a problem opening them. */
844 compare_files (dir0, name0, dir1, name1, depth)
845 char const *dir0, *dir1;
846 char const *name0, *name1;
849 struct file_data inf[2];
854 char *free0 = 0, *free1 = 0;
856 /* If this is directory comparison, perhaps we have a file
857 that exists only in one of the directories.
858 If so, just print a message to that effect. */
860 if (! ((name0 != 0 && name1 != 0)
861 || (unidirectional_new_file_flag && name1 != 0)
862 || entire_new_file_flag))
864 char const *name = name0 == 0 ? name1 : name0;
865 char const *dir = name0 == 0 ? dir1 : dir0;
866 message ("Only in %s: %s\n", dir, name);
867 /* Return 1 so that diff_dirs will return 1 ("some files differ"). */
871 bzero (inf, sizeof (inf));
873 /* Mark any nonexistent file with -1 in the desc field. */
874 /* Mark unopened files (e.g. directories) with -2. */
876 inf[0].desc = name0 == 0 ? -1 : -2;
877 inf[1].desc = name1 == 0 ? -1 : -2;
879 /* Now record the full name of each file, including nonexistent ones. */
886 inf[0].name = dir0 == 0 ? name0 : (free0 = dir_file_pathname (dir0, name0));
887 inf[1].name = dir1 == 0 ? name1 : (free1 = dir_file_pathname (dir1, name1));
889 /* Stat the files. Record whether they are directories. */
891 for (i = 0; i <= 1; i++)
893 if (inf[i].desc != -1)
897 if (i && filename_cmp (inf[i].name, inf[0].name) == 0)
899 inf[i].stat = inf[0].stat;
902 else if (strcmp (inf[i].name, "-") == 0)
904 inf[i].desc = STDIN_FILENO;
905 stat_result = fstat (STDIN_FILENO, &inf[i].stat);
906 if (stat_result == 0 && S_ISREG (inf[i].stat.st_mode))
908 off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
913 if (pos <= inf[i].stat.st_size)
914 inf[i].stat.st_size -= pos;
916 inf[i].stat.st_size = 0;
917 /* Posix.2 4.17.6.1.4 requires current time for stdin. */
918 time (&inf[i].stat.st_mtime);
923 stat_result = stat (inf[i].name, &inf[i].stat);
925 if (stat_result != 0)
927 perror_with_name (inf[i].name);
932 inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
933 if (inf[1 - i].desc == -1)
935 inf[1 - i].dir_p = inf[i].dir_p;
936 inf[1 - i].stat.st_mode = inf[i].stat.st_mode;
942 if (! failed && depth == 0 && inf[0].dir_p != inf[1].dir_p)
944 /* If one is a directory, and it was specified in the command line,
945 use the file in that dir with the other file's basename. */
947 int fnm_arg = inf[0].dir_p;
948 int dir_arg = 1 - fnm_arg;
949 char const *fnm = inf[fnm_arg].name;
950 char const *dir = inf[dir_arg].name;
951 char const *p = filename_lastdirchar (fnm);
952 char const *filename = inf[dir_arg].name
953 = dir_file_pathname (dir, p ? p + 1 : fnm);
955 if (strcmp (fnm, "-") == 0)
956 fatal ("can't compare - to a directory");
958 if (stat (filename, &inf[dir_arg].stat) != 0)
960 perror_with_name (filename);
964 inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
970 /* If either file should exist but does not, return 2. */
975 else if ((same_files = inf[0].desc != -1 && inf[1].desc != -1
976 && 0 < same_file (&inf[0].stat, &inf[1].stat))
977 && no_diff_means_no_output)
979 /* The two named files are actually the same physical file.
980 We know they are identical without actually reading them. */
984 else if (inf[0].dir_p & inf[1].dir_p)
986 if (output_style == OUTPUT_IFDEF)
987 fatal ("-D option not supported with directories");
989 /* If both are directories, compare the files in them. */
991 if (depth > 0 && !recursive)
993 /* But don't compare dir contents one level down
994 unless -r was specified. */
995 message ("Common subdirectories: %s and %s\n",
996 inf[0].name, inf[1].name);
1001 val = diff_dirs (inf, compare_files, depth);
1005 else if ((inf[0].dir_p | inf[1].dir_p)
1007 && (! S_ISREG (inf[0].stat.st_mode)
1008 || ! S_ISREG (inf[1].stat.st_mode))))
1010 /* Perhaps we have a subdirectory that exists only in one directory.
1011 If so, just print a message to that effect. */
1013 if (inf[0].desc == -1 || inf[1].desc == -1)
1015 if ((inf[0].dir_p | inf[1].dir_p)
1017 && (entire_new_file_flag
1018 || (unidirectional_new_file_flag && inf[0].desc == -1)))
1019 val = diff_dirs (inf, compare_files, depth);
1022 char const *dir = (inf[0].desc == -1) ? dir1 : dir0;
1023 /* See Posix.2 section 4.17.6.1.1 for this format. */
1024 message ("Only in %s: %s\n", dir, name0);
1030 /* We have two files that are not to be compared. */
1032 /* See Posix.2 section 4.17.6.1.1 for this format. */
1033 message5 ("File %s is a %s while file %s is a %s\n",
1034 inf[0].name, filetype (&inf[0].stat),
1035 inf[1].name, filetype (&inf[1].stat));
1037 /* This is a difference. */
1041 else if ((no_details_flag & ~ignore_some_changes)
1042 && inf[0].stat.st_size != inf[1].stat.st_size
1043 && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
1044 && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
1046 message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
1051 /* Both exist and neither is a directory. */
1053 /* Open the files and record their descriptors. */
1055 if (inf[0].desc == -2)
1056 if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
1058 perror_with_name (inf[0].name);
1061 if (inf[1].desc == -2)
1063 inf[1].desc = inf[0].desc;
1064 else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
1066 perror_with_name (inf[1].name);
1072 for (i = 0; i <= 1; i++)
1073 if (0 <= inf[i].desc)
1074 setmode (inf[i].desc, O_BINARY);
1077 /* Compare the files, if no error was found. */
1079 val = failed ? 2 : diff_2_files (inf, depth);
1081 /* Close the file descriptors. */
1083 if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
1085 perror_with_name (inf[0].name);
1088 if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
1089 && close (inf[1].desc) != 0)
1091 perror_with_name (inf[1].name);
1096 /* Now the comparison has been done, if no error prevented it,
1097 and VAL is the value this function will return. */
1099 if (val == 0 && !inf[0].dir_p)
1101 if (print_file_same_flag)
1102 message ("Files %s and %s are identical\n",
1103 inf[0].name, inf[1].name);